about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-19 03:12:28 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-19 03:12:28 +0000
commit1fd361a1ea06e44286c213ca1f814f49306fdc43 (patch)
tree64c8c96cf54d8718847339a403e5e67b922e8c3f
downloadnetpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.gz
netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.xz
netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.zip
Create Subversion repository
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@1 9d0c8265-081b-0410-96cb-a4ca84ce46f8
-rw-r--r--GNUmakefile393
-rw-r--r--Makefile14
-rw-r--r--Makefile.common545
-rw-r--r--Makefile.config.in624
-rw-r--r--Makefile.srcdir1
-rw-r--r--README213
-rw-r--r--analyzer/Makefile48
-rw-r--r--analyzer/pamfile.c243
-rw-r--r--analyzer/pamsharpmap.c188
-rw-r--r--analyzer/pamsharpness.c153
-rw-r--r--analyzer/pamslice.c199
-rw-r--r--analyzer/pamsumm.c231
-rw-r--r--analyzer/pamtilt.c437
-rw-r--r--analyzer/pbmminkowski.c168
-rw-r--r--analyzer/pgmhist.c87
-rw-r--r--analyzer/pgmminkowski.c222
-rw-r--r--analyzer/pgmtexture.c1047
-rw-r--r--analyzer/pnmhistmap.c483
-rw-r--r--analyzer/pnmpsnr.c209
-rw-r--r--analyzer/ppmhist.c290
-rw-r--r--build_complete1
-rw-r--r--buildtools/Makefile46
-rw-r--r--buildtools/Makefile.manpage366
-rw-r--r--buildtools/README.pkg144
-rw-r--r--buildtools/config_template52
-rwxr-xr-xbuildtools/configure.pl2111
-rwxr-xr-xbuildtools/empty_depend19
-rwxr-xr-xbuildtools/endiangenbin0 -> 14005 bytes
-rw-r--r--buildtools/endiangen.c97
-rwxr-xr-xbuildtools/install.sh255
-rwxr-xr-xbuildtools/installnetpbm.pl919
-rwxr-xr-xbuildtools/installosf31
-rwxr-xr-xbuildtools/liboptbin0 -> 18008 bytes
-rw-r--r--buildtools/libopt.c541
-rwxr-xr-xbuildtools/make_merge.sh12
-rwxr-xr-xbuildtools/makecat18
-rwxr-xr-xbuildtools/makeman333
-rwxr-xr-xbuildtools/makepointerman91
-rwxr-xr-xbuildtools/mkinstalldirs41
-rwxr-xr-xbuildtools/stamp-date23
-rwxr-xr-xbuildtools/typegenbin0 -> 14159 bytes
-rw-r--r--buildtools/typegen.c113
-rwxr-xr-xconfigure18
-rw-r--r--converter/Makefile17
-rw-r--r--converter/bmp.h256
-rw-r--r--converter/other/Makefile238
-rw-r--r--converter/other/README.JPEG397
-rwxr-xr-xconverter/other/anytopnm544
-rw-r--r--converter/other/bmepsoe.c539
-rw-r--r--converter/other/bmepsoe.h79
-rw-r--r--converter/other/bmptopnm.c1477
-rw-r--r--converter/other/cameratopam/COPYRIGHT35
-rw-r--r--converter/other/cameratopam/Makefile37
-rw-r--r--converter/other/cameratopam/bayer.h43
-rw-r--r--converter/other/cameratopam/camera.c1781
-rw-r--r--converter/other/cameratopam/camera.h131
-rw-r--r--converter/other/cameratopam/cameratopam.c906
-rw-r--r--converter/other/cameratopam/canon.c172
-rw-r--r--converter/other/cameratopam/canon.h13
-rw-r--r--converter/other/cameratopam/decode.c172
-rw-r--r--converter/other/cameratopam/decode.h22
-rw-r--r--converter/other/cameratopam/dng.c73
-rw-r--r--converter/other/cameratopam/dng.h2
-rw-r--r--converter/other/cameratopam/foveon.c790
-rw-r--r--converter/other/cameratopam/foveon.h14
-rw-r--r--converter/other/cameratopam/global_variables.h55
-rw-r--r--converter/other/cameratopam/identify.c1183
-rw-r--r--converter/other/cameratopam/identify.h11
-rw-r--r--converter/other/cameratopam/ljpeg.c141
-rw-r--r--converter/other/cameratopam/ljpeg.h17
-rw-r--r--converter/other/cameratopam/util.c83
-rw-r--r--converter/other/cameratopam/util.h16
-rw-r--r--converter/other/dithers.h91
-rw-r--r--converter/other/exif.c1030
-rw-r--r--converter/other/exif.h56
-rw-r--r--converter/other/fiasco/Makefile59
-rw-r--r--converter/other/fiasco/README.netpbm41
-rw-r--r--converter/other/fiasco/binerror.c143
-rw-r--r--converter/other/fiasco/binerror.h50
-rw-r--r--converter/other/fiasco/buttons.c510
-rw-r--r--converter/other/fiasco/buttons.h50
-rw-r--r--converter/other/fiasco/codec/Makefile27
-rw-r--r--converter/other/fiasco/codec/approx.c702
-rw-r--r--converter/other/fiasco/codec/approx.h30
-rw-r--r--converter/other/fiasco/codec/bintree.c94
-rw-r--r--converter/other/fiasco/codec/bintree.h43
-rw-r--r--converter/other/fiasco/codec/coder.c965
-rw-r--r--converter/other/fiasco/codec/coder.h24
-rw-r--r--converter/other/fiasco/codec/coeff.c369
-rw-r--r--converter/other/fiasco/codec/coeff.h61
-rw-r--r--converter/other/fiasco/codec/control.c276
-rw-r--r--converter/other/fiasco/codec/control.h33
-rw-r--r--converter/other/fiasco/codec/cwfa.h107
-rw-r--r--converter/other/fiasco/codec/decoder.c1532
-rw-r--r--converter/other/fiasco/codec/decoder.h70
-rw-r--r--converter/other/fiasco/codec/dfiasco.c398
-rw-r--r--converter/other/fiasco/codec/dfiasco.h38
-rw-r--r--converter/other/fiasco/codec/domain-pool.c986
-rw-r--r--converter/other/fiasco/codec/domain-pool.h75
-rw-r--r--converter/other/fiasco/codec/ip.c324
-rw-r--r--converter/other/fiasco/codec/ip.h37
-rw-r--r--converter/other/fiasco/codec/motion.c338
-rw-r--r--converter/other/fiasco/codec/motion.h35
-rw-r--r--converter/other/fiasco/codec/mwfa.c864
-rw-r--r--converter/other/fiasco/codec/mwfa.h44
-rw-r--r--converter/other/fiasco/codec/options.c894
-rw-r--r--converter/other/fiasco/codec/options.h80
-rw-r--r--converter/other/fiasco/codec/prediction.c629
-rw-r--r--converter/other/fiasco/codec/prediction.h36
-rw-r--r--converter/other/fiasco/codec/subdivide.c650
-rw-r--r--converter/other/fiasco/codec/subdivide.h33
-rw-r--r--converter/other/fiasco/codec/tiling.c239
-rw-r--r--converter/other/fiasco/codec/tiling.h40
-rw-r--r--converter/other/fiasco/codec/wfa.h141
-rw-r--r--converter/other/fiasco/codec/wfalib.c774
-rw-r--r--converter/other/fiasco/codec/wfalib.h66
-rw-r--r--converter/other/fiasco/config.h96
-rw-r--r--converter/other/fiasco/display.c422
-rw-r--r--converter/other/fiasco/display.h49
-rw-r--r--converter/other/fiasco/doc/README.LIB51
-rw-r--r--converter/other/fiasco/doc/fiasco_c_options.31
-rw-r--r--converter/other/fiasco/doc/fiasco_c_options_delete.31
-rw-r--r--converter/other/fiasco/doc/fiasco_c_options_new.3432
-rw-r--r--converter/other/fiasco/doc/fiasco_c_options_set_basisfile.31
-rw-r--r--converter/other/fiasco/doc/fiasco_c_options_set_chroma_quality.31
-rw-r--r--converter/other/fiasco/doc/fiasco_c_options_set_comment.31
-rw-r--r--converter/other/fiasco/doc/fiasco_c_options_set_frame_pattern.31
-rw-r--r--converter/other/fiasco/doc/fiasco_c_options_set_optimizations.31
-rw-r--r--converter/other/fiasco/doc/fiasco_c_options_set_prediction.31
-rw-r--r--converter/other/fiasco/doc/fiasco_c_options_set_progress_meter.31
-rw-r--r--converter/other/fiasco/doc/fiasco_c_options_set_quantization.31
-rw-r--r--converter/other/fiasco/doc/fiasco_c_options_set_smoothing.31
-rw-r--r--converter/other/fiasco/doc/fiasco_c_options_set_tiling.31
-rw-r--r--converter/other/fiasco/doc/fiasco_c_options_set_title.31
-rw-r--r--converter/other/fiasco/doc/fiasco_c_options_set_video_param.31
-rw-r--r--converter/other/fiasco/doc/fiasco_coder.3106
-rw-r--r--converter/other/fiasco/doc/fiasco_d_options.31
-rw-r--r--converter/other/fiasco/doc/fiasco_d_options_delete.31
-rw-r--r--converter/other/fiasco/doc/fiasco_d_options_new.3122
-rw-r--r--converter/other/fiasco/doc/fiasco_d_options_set_4_2_0_format.31
-rw-r--r--converter/other/fiasco/doc/fiasco_d_options_set_magnification.31
-rw-r--r--converter/other/fiasco/doc/fiasco_d_options_set_smoothing.31
-rw-r--r--converter/other/fiasco/doc/fiasco_decoder.31
-rw-r--r--converter/other/fiasco/doc/fiasco_decoder_delete.31
-rw-r--r--converter/other/fiasco/doc/fiasco_decoder_get_comment.31
-rw-r--r--converter/other/fiasco/doc/fiasco_decoder_get_frame.31
-rw-r--r--converter/other/fiasco/doc/fiasco_decoder_get_framerate.31
-rw-r--r--converter/other/fiasco/doc/fiasco_decoder_get_height.31
-rw-r--r--converter/other/fiasco/doc/fiasco_decoder_get_length.31
-rw-r--r--converter/other/fiasco/doc/fiasco_decoder_get_title.31
-rw-r--r--converter/other/fiasco/doc/fiasco_decoder_get_width.31
-rw-r--r--converter/other/fiasco/doc/fiasco_decoder_is_color.31
-rw-r--r--converter/other/fiasco/doc/fiasco_decoder_new.3194
-rw-r--r--converter/other/fiasco/doc/fiasco_decoder_write_frame.31
-rw-r--r--converter/other/fiasco/doc/fiasco_get_error_message.341
-rw-r--r--converter/other/fiasco/doc/fiasco_get_verbosity.31
-rw-r--r--converter/other/fiasco/doc/fiasco_image.31
-rw-r--r--converter/other/fiasco/doc/fiasco_image_delete.31
-rw-r--r--converter/other/fiasco/doc/fiasco_image_get_height.31
-rw-r--r--converter/other/fiasco/doc/fiasco_image_get_width.31
-rw-r--r--converter/other/fiasco/doc/fiasco_image_is_color.31
-rw-r--r--converter/other/fiasco/doc/fiasco_image_new.395
-rw-r--r--converter/other/fiasco/doc/fiasco_options.31
-rw-r--r--converter/other/fiasco/doc/fiasco_options_delete.31
-rw-r--r--converter/other/fiasco/doc/fiasco_options_new.3441
-rw-r--r--converter/other/fiasco/doc/fiasco_options_set_4_2_0_format.31
-rw-r--r--converter/other/fiasco/doc/fiasco_options_set_basisfile.31
-rw-r--r--converter/other/fiasco/doc/fiasco_options_set_chroma_quality.31
-rw-r--r--converter/other/fiasco/doc/fiasco_options_set_frame_pattern.31
-rw-r--r--converter/other/fiasco/doc/fiasco_options_set_magnification.31
-rw-r--r--converter/other/fiasco/doc/fiasco_options_set_optimizations.31
-rw-r--r--converter/other/fiasco/doc/fiasco_options_set_prediction.31
-rw-r--r--converter/other/fiasco/doc/fiasco_options_set_progress_meter.31
-rw-r--r--converter/other/fiasco/doc/fiasco_options_set_quantization.31
-rw-r--r--converter/other/fiasco/doc/fiasco_options_set_smoothing.31
-rw-r--r--converter/other/fiasco/doc/fiasco_options_set_tiling.31
-rw-r--r--converter/other/fiasco/doc/fiasco_options_set_video_param.31
-rw-r--r--converter/other/fiasco/doc/fiasco_renderer.31
-rw-r--r--converter/other/fiasco/doc/fiasco_renderer_delete.31
-rw-r--r--converter/other/fiasco/doc/fiasco_renderer_new.3125
-rw-r--r--converter/other/fiasco/doc/fiasco_renderer_render.31
-rw-r--r--converter/other/fiasco/doc/fiasco_set_verbosity.346
-rw-r--r--converter/other/fiasco/fiasco.h425
-rw-r--r--converter/other/fiasco/fiascotopnm.c477
-rw-r--r--converter/other/fiasco/getopt.c1002
-rw-r--r--converter/other/fiasco/getopt.h129
-rw-r--r--converter/other/fiasco/getopt1.c189
-rw-r--r--converter/other/fiasco/input/Makefile26
-rw-r--r--converter/other/fiasco/input/basis.c141
-rw-r--r--converter/other/fiasco/input/basis.h26
-rw-r--r--converter/other/fiasco/input/matrices.c644
-rw-r--r--converter/other/fiasco/input/matrices.h27
-rw-r--r--converter/other/fiasco/input/mc.c334
-rw-r--r--converter/other/fiasco/input/mc.h28
-rw-r--r--converter/other/fiasco/input/nd.c237
-rw-r--r--converter/other/fiasco/input/nd.h28
-rw-r--r--converter/other/fiasco/input/read.c499
-rw-r--r--converter/other/fiasco/input/read.h31
-rw-r--r--converter/other/fiasco/input/tree.c303
-rw-r--r--converter/other/fiasco/input/tree.h28
-rw-r--r--converter/other/fiasco/input/weights.c200
-rw-r--r--converter/other/fiasco/input/weights.h27
-rw-r--r--converter/other/fiasco/lib/Makefile33
-rw-r--r--converter/other/fiasco/lib/arith.c708
-rw-r--r--converter/other/fiasco/lib/arith.h122
-rw-r--r--converter/other/fiasco/lib/bit-io.c327
-rw-r--r--converter/other/fiasco/lib/bit-io.h58
-rw-r--r--converter/other/fiasco/lib/dither.c1892
-rw-r--r--converter/other/fiasco/lib/dither.h27
-rw-r--r--converter/other/fiasco/lib/error.c326
-rw-r--r--converter/other/fiasco/lib/error.h39
-rw-r--r--converter/other/fiasco/lib/image.c512
-rw-r--r--converter/other/fiasco/lib/image.h59
-rw-r--r--converter/other/fiasco/lib/list.c258
-rw-r--r--converter/other/fiasco/lib/list.h72
-rw-r--r--converter/other/fiasco/lib/macros.h70
-rw-r--r--converter/other/fiasco/lib/misc.c563
-rw-r--r--converter/other/fiasco/lib/misc.h98
-rw-r--r--converter/other/fiasco/lib/mvcode.c14
-rw-r--r--converter/other/fiasco/lib/mvcode.h6
-rw-r--r--converter/other/fiasco/lib/rpf.c223
-rw-r--r--converter/other/fiasco/lib/rpf.h47
-rw-r--r--converter/other/fiasco/lib/types.h38
-rw-r--r--converter/other/fiasco/output/Makefile26
-rw-r--r--converter/other/fiasco/output/matrices.c547
-rw-r--r--converter/other/fiasco/output/matrices.h28
-rw-r--r--converter/other/fiasco/output/mc.c250
-rw-r--r--converter/other/fiasco/output/mc.h28
-rw-r--r--converter/other/fiasco/output/nd.c244
-rw-r--r--converter/other/fiasco/output/nd.h27
-rw-r--r--converter/other/fiasco/output/tree.c176
-rw-r--r--converter/other/fiasco/output/tree.h27
-rw-r--r--converter/other/fiasco/output/weights.c200
-rw-r--r--converter/other/fiasco/output/weights.h27
-rw-r--r--converter/other/fiasco/output/write.c250
-rw-r--r--converter/other/fiasco/output/write.h28
-rw-r--r--converter/other/fiasco/params.c727
-rw-r--r--converter/other/fiasco/params.h61
-rw-r--r--converter/other/fiasco/pnmtofiasco.c411
-rw-r--r--converter/other/fiasco/system.fiascorc120
-rw-r--r--converter/other/fitstopnm.c494
-rw-r--r--converter/other/gemtopnm.c305
-rw-r--r--converter/other/giftopnm.c1460
-rw-r--r--converter/other/hdifftopam.c156
-rw-r--r--converter/other/infotopam.c543
-rw-r--r--converter/other/jbig/ANNOUNCE243
-rw-r--r--converter/other/jbig/Makefile47
-rw-r--r--converter/other/jbig/README.Netpbm12
-rw-r--r--converter/other/jbig/jbig.c2905
-rw-r--r--converter/other/jbig/jbig.doc721
-rw-r--r--converter/other/jbig/jbig.h267
-rw-r--r--converter/other/jbig/jbig_tab.c428
-rw-r--r--converter/other/jbig/jbigtopnm.c286
-rw-r--r--converter/other/jbig/pnmtojbig.c463
-rw-r--r--converter/other/jpeg2000/Makefile77
-rw-r--r--converter/other/jpeg2000/jpeg2ktopam.c498
-rw-r--r--converter/other/jpeg2000/libjasper/Makefile28
-rw-r--r--converter/other/jpeg2000/libjasper/Makefile.common41
-rw-r--r--converter/other/jpeg2000/libjasper/README17
-rw-r--r--converter/other/jpeg2000/libjasper/base/Makefile21
-rw-r--r--converter/other/jpeg2000/libjasper/base/jas_debug.c186
-rw-r--r--converter/other/jpeg2000/libjasper/base/jas_getopt.c217
-rw-r--r--converter/other/jpeg2000/libjasper/base/jas_image.c968
-rw-r--r--converter/other/jpeg2000/libjasper/base/jas_init.c200
-rw-r--r--converter/other/jpeg2000/libjasper/base/jas_malloc.c167
-rw-r--r--converter/other/jpeg2000/libjasper/base/jas_seq.c475
-rw-r--r--converter/other/jpeg2000/libjasper/base/jas_stream.c1204
-rw-r--r--converter/other/jpeg2000/libjasper/base/jas_string.c145
-rw-r--r--converter/other/jpeg2000/libjasper/base/jas_tvp.c286
-rw-r--r--converter/other/jpeg2000/libjasper/base/jas_version.c116
-rw-r--r--converter/other/jpeg2000/libjasper/base/partlist1
-rw-r--r--converter/other/jpeg2000/libjasper/include/jasper/jas_debug.h161
-rw-r--r--converter/other/jpeg2000/libjasper/include/jasper/jas_fix.h406
-rw-r--r--converter/other/jpeg2000/libjasper/include/jasper/jas_getopt.h178
-rw-r--r--converter/other/jpeg2000/libjasper/include/jasper/jas_image.h593
-rw-r--r--converter/other/jpeg2000/libjasper/include/jasper/jas_init.h128
-rw-r--r--converter/other/jpeg2000/libjasper/include/jasper/jas_malloc.h171
-rw-r--r--converter/other/jpeg2000/libjasper/include/jasper/jas_math.h164
-rw-r--r--converter/other/jpeg2000/libjasper/include/jasper/jas_seq.h348
-rw-r--r--converter/other/jpeg2000/libjasper/include/jasper/jas_stream.h498
-rw-r--r--converter/other/jpeg2000/libjasper/include/jasper/jas_string.h143
-rw-r--r--converter/other/jpeg2000/libjasper/include/jasper/jas_tvp.h198
-rw-r--r--converter/other/jpeg2000/libjasper/include/jasper/jas_types.h14
-rw-r--r--converter/other/jpeg2000/libjasper/include/jasper/jas_types.h.orig228
-rw-r--r--converter/other/jpeg2000/libjasper/include/jasper/jas_version.h167
-rw-r--r--converter/other/jpeg2000/libjasper/include/jasper/jasper.h137
-rw-r--r--converter/other/jpeg2000/libjasper/jp2/Makefile19
-rw-r--r--converter/other/jpeg2000/libjasper/jp2/jp2_cod.c928
-rw-r--r--converter/other/jpeg2000/libjasper/jp2/jp2_cod.h347
-rw-r--r--converter/other/jpeg2000/libjasper/jp2/jp2_dec.c649
-rw-r--r--converter/other/jpeg2000/libjasper/jp2/jp2_dec.h134
-rw-r--r--converter/other/jpeg2000/libjasper/jp2/jp2_enc.c414
-rw-r--r--converter/other/jpeg2000/libjasper/jp2/partlist1
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/Makefile22
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_bs.c478
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_bs.h280
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_cod.h127
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_cs.c1614
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_cs.h812
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_dec.c2334
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_dec.h745
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_enc.c2624
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_enc.h695
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_fix.h193
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_flt.h129
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_math.c170
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_math.h155
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_mct.c337
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_mct.h160
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_mqcod.c228
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_mqcod.h174
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_mqdec.c339
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_mqdec.h320
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_mqenc.c441
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_mqenc.h285
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_qmfb.c1125
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_qmfb.h235
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_t1cod.c537
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_t1cod.h344
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_t1dec.c954
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_t1dec.h137
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_t1enc.c1008
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_t1enc.h142
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_t2cod.c733
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_t2cod.h348
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_t2dec.c626
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_t2dec.h144
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_t2enc.c704
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_t2enc.h155
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_tagtree.c426
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_tagtree.h216
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_tsfb.c662
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_tsfb.h222
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_util.c243
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/jpc_util.h126
-rw-r--r--converter/other/jpeg2000/libjasper/jpc/partlist1
-rw-r--r--converter/other/jpeg2000/libjasper/partlist4
-rw-r--r--converter/other/jpeg2000/libjasper_compat.h24
-rw-r--r--converter/other/jpeg2000/pamtojpeg2k.c522
-rw-r--r--converter/other/jpegdatasource.c183
-rw-r--r--converter/other/jpegdatasource.h18
-rw-r--r--converter/other/jpegtopnm.c970
-rw-r--r--converter/other/pamrgbatopng.c150
-rw-r--r--converter/other/pamtodjvurle.c293
-rw-r--r--converter/other/pamtofits.c304
-rw-r--r--converter/other/pamtohdiff.c142
-rw-r--r--converter/other/pamtohtmltbl.c284
-rw-r--r--converter/other/pamtopfm.c304
-rw-r--r--converter/other/pamtopnm.c155
-rw-r--r--converter/other/pamtosvg/Makefile55
-rw-r--r--converter/other/pamtosvg/README109
-rw-r--r--converter/other/pamtosvg/autotrace.c220
-rw-r--r--converter/other/pamtosvg/autotrace.h258
-rw-r--r--converter/other/pamtosvg/bitmap.c116
-rw-r--r--converter/other/pamtosvg/bitmap.h53
-rw-r--r--converter/other/pamtosvg/curve.c314
-rw-r--r--converter/other/pamtosvg/curve.h160
-rw-r--r--converter/other/pamtosvg/epsilon-equal.c19
-rw-r--r--converter/other/pamtosvg/epsilon-equal.h20
-rw-r--r--converter/other/pamtosvg/exception.c55
-rw-r--r--converter/other/pamtosvg/exception.h43
-rw-r--r--converter/other/pamtosvg/fit.c1923
-rw-r--r--converter/other/pamtosvg/fit.h34
-rw-r--r--converter/other/pamtosvg/image-header.h19
-rw-r--r--converter/other/pamtosvg/image-proc.c516
-rw-r--r--converter/other/pamtosvg/image-proc.h42
-rw-r--r--converter/other/pamtosvg/logreport.c17
-rw-r--r--converter/other/pamtosvg/logreport.h28
-rw-r--r--converter/other/pamtosvg/message.h47
-rw-r--r--converter/other/pamtosvg/output-svg.c130
-rw-r--r--converter/other/pamtosvg/output-svg.h37
-rw-r--r--converter/other/pamtosvg/pamtosvg.c395
-rw-r--r--converter/other/pamtosvg/pamtosvg.test6
-rw-r--r--converter/other/pamtosvg/point.h8
-rw-r--r--converter/other/pamtosvg/pxl-outline.c1370
-rw-r--r--converter/other/pamtosvg/pxl-outline.h79
-rw-r--r--converter/other/pamtosvg/spline.c193
-rw-r--r--converter/other/pamtosvg/spline.h90
-rw-r--r--converter/other/pamtosvg/testgrid.svg5
-rw-r--r--converter/other/pamtosvg/testline.svg5
-rw-r--r--converter/other/pamtosvg/thin-image.c373
-rw-r--r--converter/other/pamtosvg/thin-image.h37
-rw-r--r--converter/other/pamtosvg/vector.c254
-rw-r--r--converter/other/pamtosvg/vector.h71
-rw-r--r--converter/other/pamtotga.c572
-rw-r--r--converter/other/pamtotiff.c1110
-rw-r--r--converter/other/pamtouil.c438
-rw-r--r--converter/other/pamtoxvmini.c250
-rw-r--r--converter/other/pbmtopgm.c80
-rw-r--r--converter/other/pclxl.h390
-rw-r--r--converter/other/pfmtopam.c395
-rw-r--r--converter/other/pgmtopbm.c723
-rw-r--r--converter/other/pgmtoppm.c153
-rw-r--r--converter/other/pm_tiff.h41
-rw-r--r--converter/other/pngtopnm.c1096
-rw-r--r--converter/other/pngtxt.c315
-rw-r--r--converter/other/pngtxt.h13
-rw-r--r--converter/other/pnmtoddif.c611
-rw-r--r--converter/other/pnmtojpeg.c1098
-rw-r--r--converter/other/pnmtopalm/LICENSE16
-rw-r--r--converter/other/pnmtopalm/Makefile35
-rw-r--r--converter/other/pnmtopalm/README57
-rw-r--r--converter/other/pnmtopalm/gen_palm_colormap.c24
-rw-r--r--converter/other/pnmtopalm/palm.h66
-rw-r--r--converter/other/pnmtopalm/palmcolor8.map235
-rw-r--r--converter/other/pnmtopalm/palmcolormap.c277
-rw-r--r--converter/other/pnmtopalm/palmgray1.map4
-rw-r--r--converter/other/pnmtopalm/palmgray2.map6
-rw-r--r--converter/other/pnmtopalm/palmgray4.map8
-rw-r--r--converter/other/pnmtopalm/palmtopnm.c1131
-rw-r--r--converter/other/pnmtopalm/pnmtopalm.c1235
-rw-r--r--converter/other/pnmtopclxl.c1204
-rwxr-xr-xconverter/other/pnmtoplainpnm3
-rw-r--r--converter/other/pnmtopng.README101
-rw-r--r--converter/other/pnmtopng.c2745
-rw-r--r--converter/other/pnmtops.c1323
-rw-r--r--converter/other/pnmtorast.c310
-rw-r--r--converter/other/pnmtorle.c267
-rw-r--r--converter/other/pnmtosgi.c354
-rw-r--r--converter/other/pnmtosir.c146
-rw-r--r--converter/other/pnmtotiffcmyk.c1000
-rw-r--r--converter/other/pnmtoxwd.c505
-rw-r--r--converter/other/ppmtopgm.c95
-rw-r--r--converter/other/pstopnm.c899
-rwxr-xr-xconverter/other/pstopnm.csh301
-rw-r--r--converter/other/rast.c465
-rw-r--r--converter/other/rast.h111
-rw-r--r--converter/other/rasttopnm.c263
-rw-r--r--converter/other/rla.h45
-rw-r--r--converter/other/rlatopam.c433
-rw-r--r--converter/other/rletopnm.c497
-rw-r--r--converter/other/sgi.h33
-rw-r--r--converter/other/sgitopnm.c463
-rw-r--r--converter/other/sirtopnm.c95
-rw-r--r--converter/other/svgtopam.c940
-rw-r--r--converter/other/tiff.c468
-rw-r--r--converter/other/tifftopnm.c1062
-rw-r--r--converter/other/x10wd.h32
-rw-r--r--converter/other/x11wd.h82
-rw-r--r--converter/other/xwdtopnm.c1280
-rw-r--r--converter/other/zeisstopnm.c187
-rw-r--r--converter/pbm/Makefile78
-rw-r--r--converter/pbm/atktopbm.c362
-rw-r--r--converter/pbm/brushtopbm.c107
-rw-r--r--converter/pbm/cmuwm.h17
-rw-r--r--converter/pbm/cmuwmtopbm.c114
-rw-r--r--converter/pbm/ddbugtopbm.c173
-rw-r--r--converter/pbm/escp2topbm.c143
-rw-r--r--converter/pbm/g3.h146
-rw-r--r--converter/pbm/g3topbm.c739
-rw-r--r--converter/pbm/icontopbm.c159
-rw-r--r--converter/pbm/macp.h12
-rw-r--r--converter/pbm/macptopbm.c140
-rw-r--r--converter/pbm/mdaspec.txt239
-rw-r--r--converter/pbm/mdatopbm.c271
-rw-r--r--converter/pbm/mgr.h25
-rw-r--r--converter/pbm/mgrtopbm.c145
-rw-r--r--converter/pbm/mrftopbm.c210
-rw-r--r--converter/pbm/pbmto10x.c169
-rw-r--r--converter/pbm/pbmto4425.c179
-rw-r--r--converter/pbm/pbmtoascii.c165
-rw-r--r--converter/pbm/pbmtoatk.c188
-rw-r--r--converter/pbm/pbmtobbnbg.c152
-rw-r--r--converter/pbm/pbmtocmuwm.c117
-rw-r--r--converter/pbm/pbmtodjvurle.c140
-rw-r--r--converter/pbm/pbmtoepsi.c252
-rw-r--r--converter/pbm/pbmtoepson.c338
-rw-r--r--converter/pbm/pbmtoescp2.c186
-rw-r--r--converter/pbm/pbmtog3.c485
-rw-r--r--converter/pbm/pbmtog3.test23
-rw-r--r--converter/pbm/pbmtogem.c252
-rw-r--r--converter/pbm/pbmtogo.c309
-rw-r--r--converter/pbm/pbmtoibm23xx.c213
-rw-r--r--converter/pbm/pbmtoicon.c134
-rw-r--r--converter/pbm/pbmtolj.c564
-rw-r--r--converter/pbm/pbmtoln03.c260
-rw-r--r--converter/pbm/pbmtolps.c181
-rw-r--r--converter/pbm/pbmtomacp.c301
-rw-r--r--converter/pbm/pbmtomatrixorbital.c87
-rw-r--r--converter/pbm/pbmtomda.c201
-rw-r--r--converter/pbm/pbmtomgr.c120
-rw-r--r--converter/pbm/pbmtomrf.c338
-rw-r--r--converter/pbm/pbmtonokia.c225
-rw-r--r--converter/pbm/pbmtopi3.c123
-rw-r--r--converter/pbm/pbmtopk.c976
-rw-r--r--converter/pbm/pbmtoplot.c76
-rw-r--r--converter/pbm/pbmtoppa/CREDITS12
-rw-r--r--converter/pbm/pbmtoppa/INSTALL-MORE187
-rw-r--r--converter/pbm/pbmtoppa/LICENSE342
-rw-r--r--converter/pbm/pbmtoppa/Makefile25
-rw-r--r--converter/pbm/pbmtoppa/README.Netpbm30
-rw-r--r--converter/pbm/pbmtoppa/README.REDHAT29
-rw-r--r--converter/pbm/pbmtoppa/cutswath.c360
-rw-r--r--converter/pbm/pbmtoppa/cutswath.h4
-rw-r--r--converter/pbm/pbmtoppa/defaults.h65
-rw-r--r--converter/pbm/pbmtoppa/pbm.c135
-rw-r--r--converter/pbm/pbmtoppa/pbmtoppa.c449
-rw-r--r--converter/pbm/pbmtoppa/pbmtoppa.conf.hp100018
-rw-r--r--converter/pbm/pbmtoppa/pbmtoppa.conf.hp72018
-rw-r--r--converter/pbm/pbmtoppa/pbmtoppa.conf.hp82018
-rw-r--r--converter/pbm/pbmtoppa/ppa.c526
-rw-r--r--converter/pbm/pbmtoppa/ppa.h74
-rw-r--r--converter/pbm/pbmtoppa/ppapbm.h29
-rw-r--r--converter/pbm/pbmtopsg3.c374
-rw-r--r--converter/pbm/pbmtoptx.c101
-rw-r--r--converter/pbm/pbmtowbmp.c70
-rw-r--r--converter/pbm/pbmtox10bm.c120
-rw-r--r--converter/pbm/pbmtoxbm.c165
-rw-r--r--converter/pbm/pbmtoybm.c114
-rw-r--r--converter/pbm/pbmtozinc.c128
-rw-r--r--converter/pbm/pi3topbm.c112
-rw-r--r--converter/pbm/pktopbm.c442
-rw-r--r--converter/pbm/thinkjettopbm.c1792
-rw-r--r--converter/pbm/thinkjettopbm.l251
-rw-r--r--converter/pbm/wbmptopbm.c112
-rw-r--r--converter/pbm/xbmtopbm.c255
-rw-r--r--converter/pbm/ybmtopbm.c113
-rw-r--r--converter/pgm/Makefile23
-rw-r--r--converter/pgm/asciitopgm.c163
-rw-r--r--converter/pgm/bioradtopgm.c152
-rw-r--r--converter/pgm/fstopgm.c138
-rw-r--r--converter/pgm/hipstopgm.c181
-rw-r--r--converter/pgm/lispmtopgm.c172
-rw-r--r--converter/pgm/pgmtofs.c153
-rw-r--r--converter/pgm/pgmtolispm.c148
-rw-r--r--converter/pgm/pgmtopgm.c44
-rw-r--r--converter/pgm/psidtopgm.c128
-rw-r--r--converter/pgm/rawtopgm.c284
-rw-r--r--converter/pgm/sbigtopgm.c208
-rw-r--r--converter/pgm/spottopgm.c231
-rw-r--r--converter/ppm/411toppm.c261
-rw-r--r--converter/ppm/Makefile49
-rw-r--r--converter/ppm/autocad.h64
-rw-r--r--converter/ppm/eyuvtoppm.c338
-rw-r--r--converter/ppm/gouldtoppm.c122
-rw-r--r--converter/ppm/hpcdtoppm/Makefile25
-rw-r--r--converter/ppm/hpcdtoppm/README20
-rwxr-xr-xconverter/ppm/hpcdtoppm/hpcdtoppm25
-rwxr-xr-xconverter/ppm/hpcdtoppm/pcdovtoppm214
-rw-r--r--converter/ppm/ilbm.h256
-rw-r--r--converter/ppm/ilbmtoppm.c2381
-rw-r--r--converter/ppm/imgtoppm.c142
-rw-r--r--converter/ppm/leaftoppm.c216
-rw-r--r--converter/ppm/mitsu.h97
-rw-r--r--converter/ppm/mtvtoppm.c68
-rw-r--r--converter/ppm/neotoppm.c104
-rw-r--r--converter/ppm/pc1toppm.c233
-rw-r--r--converter/ppm/pcxstd.ppm19
-rw-r--r--converter/ppm/pcxtoppm.c710
-rw-r--r--converter/ppm/pi1toppm.c92
-rw-r--r--converter/ppm/picttoppm.c3788
-rw-r--r--converter/ppm/pjtoppm.c253
-rw-r--r--converter/ppm/ppmtoacad.c394
-rw-r--r--converter/ppm/ppmtoarbtxt.c505
-rw-r--r--converter/ppm/ppmtobmp.c795
-rw-r--r--converter/ppm/ppmtoeyuv.c396
-rw-r--r--converter/ppm/ppmtogif.c1681
-rw-r--r--converter/ppm/ppmtoicr.c320
-rw-r--r--converter/ppm/ppmtoilbm.c2362
-rw-r--r--converter/ppm/ppmtoleaf.c250
-rw-r--r--converter/ppm/ppmtolj.c297
-rw-r--r--converter/ppm/ppmtomitsu.c604
-rw-r--r--converter/ppm/ppmtompeg/BUGS43
-rw-r--r--converter/ppm/ppmtompeg/CHANGES180
-rw-r--r--converter/ppm/ppmtompeg/COPYRIGHT20
-rw-r--r--converter/ppm/ppmtompeg/HISTORY313
-rw-r--r--converter/ppm/ppmtompeg/LOGIC126
-rw-r--r--converter/ppm/ppmtompeg/Makefile140
-rw-r--r--converter/ppm/ppmtompeg/bframe.c1347
-rw-r--r--converter/ppm/ppmtompeg/bitio.c536
-rw-r--r--converter/ppm/ppmtompeg/block.c1045
-rw-r--r--converter/ppm/ppmtompeg/bsearch.c1088
-rw-r--r--converter/ppm/ppmtompeg/combine.c357
-rw-r--r--converter/ppm/ppmtompeg/docs/EXTENSIONS41
-rw-r--r--converter/ppm/ppmtompeg/docs/INPUT.FORMAT79
-rw-r--r--converter/ppm/ppmtompeg/docs/parallel.param142
-rw-r--r--converter/ppm/ppmtompeg/docs/param-summary14
-rw-r--r--converter/ppm/ppmtompeg/docs/template.param154
-rw-r--r--converter/ppm/ppmtompeg/docs/users-guide.ps3233
-rw-r--r--converter/ppm/ppmtompeg/examples/basicspeed.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/decoded.test81
-rw-r--r--converter/ppm/ppmtompeg/examples/decoded2.test81
-rw-r--r--converter/ppm/ppmtompeg/examples/decorig.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/default.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/fastspeed.param46
-rw-r--r--converter/ppm/ppmtompeg/examples/foobar.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/frame.test37
-rw-r--r--converter/ppm/ppmtompeg/examples/gop.combine11
-rw-r--r--converter/ppm/ppmtompeg/examples/gop.test43
-rw-r--r--converter/ppm/ppmtompeg/examples/gop1.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/hello.param60
-rw-r--r--converter/ppm/ppmtompeg/examples/i.test37
-rw-r--r--converter/ppm/ppmtompeg/examples/ispeed.jpeg.param48
-rw-r--r--converter/ppm/ppmtompeg/examples/ispeed.param45
-rw-r--r--converter/ppm/ppmtompeg/examples/jpeg.test66
-rw-r--r--converter/ppm/ppmtompeg/examples/jpeg19.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/jpegout19.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/localdisk.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/localremote.test80
-rw-r--r--converter/ppm/ppmtompeg/examples/luxo.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/luxo18.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/luxo19.5.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/luxo19.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/luxo2.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/luxo48.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/luxo49.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/luxo50.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/luxo52.5.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/luxo52.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/luxo53.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/luxobug.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/luxoskip.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/med.param36
-rw-r--r--converter/ppm/ppmtompeg/examples/med18.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/medspeed.param38
-rw-r--r--converter/ppm/ppmtompeg/examples/nametest.param60
-rw-r--r--converter/ppm/ppmtompeg/examples/nosearch.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/par3.param43
-rw-r--r--converter/ppm/ppmtompeg/examples/par4.param45
-rw-r--r--converter/ppm/ppmtompeg/examples/par5.param47
-rw-r--r--converter/ppm/ppmtompeg/examples/parallel.287
-rw-r--r--converter/ppm/ppmtompeg/examples/parallel.param141
-rw-r--r--converter/ppm/ppmtompeg/examples/parallel.test85
-rw-r--r--converter/ppm/ppmtompeg/examples/param.template107
-rw-r--r--converter/ppm/ppmtompeg/examples/payam.param37
-rw-r--r--converter/ppm/ppmtompeg/examples/payam18.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/pnm.param37
-rw-r--r--converter/ppm/ppmtompeg/examples/ppm.param37
-rw-r--r--converter/ppm/ppmtompeg/examples/prof.b.param48
-rw-r--r--converter/ppm/ppmtompeg/examples/prof.bhalf.param48
-rw-r--r--converter/ppm/ppmtompeg/examples/prof.param36
-rw-r--r--converter/ppm/ppmtompeg/examples/prof.ss.param48
-rw-r--r--converter/ppm/ppmtompeg/examples/prof2.param36
-rw-r--r--converter/ppm/ppmtompeg/examples/prof3.param36
-rw-r--r--converter/ppm/ppmtompeg/examples/prof4.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/remote.test85
-rw-r--r--converter/ppm/ppmtompeg/examples/search1.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search10.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search11.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search12.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search13.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search14.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search15.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search16.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search17.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search18.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search19.5.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/search19.param42
-rw-r--r--converter/ppm/ppmtompeg/examples/search2.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search20.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search21.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search22.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search23.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search24.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search25.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search26.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search27.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search28.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search29.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search3.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search30.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search31.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search32.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search33.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search34.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search35.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search36.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search37.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search38.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search39.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search4.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search40.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search41.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search42.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search43.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search44.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search45.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search46.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search47.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search48.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/search49.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/search5.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search50.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/search51.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search52.5.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/search52.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/search53.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/search6.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search7.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search8.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/search9.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/seq.param36
-rw-r--r--converter/ppm/ppmtompeg/examples/sequoia.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/sequoia19.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/sequoia2.param36
-rw-r--r--converter/ppm/ppmtompeg/examples/simple.param40
-rw-r--r--converter/ppm/ppmtompeg/examples/slice.param47
-rw-r--r--converter/ppm/ppmtompeg/examples/slimed.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/slowspeed.param38
-rw-r--r--converter/ppm/ppmtompeg/examples/stanford.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/subtest.param34
-rw-r--r--converter/ppm/ppmtompeg/examples/temp.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/template.param154
-rw-r--r--converter/ppm/ppmtompeg/examples/tennis.param37
-rw-r--r--converter/ppm/ppmtompeg/examples/tennis18.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/tennis19.5.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/tennis19.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/tennis48.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/tennis49.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/tennis50.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/tennis52.5.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/tennis52.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/tennis53.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/tennisbug.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/testp.param45
-rw-r--r--converter/ppm/ppmtompeg/examples/walk.param61
-rw-r--r--converter/ppm/ppmtompeg/examples/walk1.param61
-rw-r--r--converter/ppm/ppmtompeg/examples/walk18.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/walk19.5.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/walk19.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/walk2.param61
-rw-r--r--converter/ppm/ppmtompeg/examples/walk3.param61
-rw-r--r--converter/ppm/ppmtompeg/examples/walk4.param61
-rw-r--r--converter/ppm/ppmtompeg/examples/walk48.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/walk49.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/walk5.param62
-rw-r--r--converter/ppm/ppmtompeg/examples/walk50.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/walk52.5.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/walk52.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/walk53.param35
-rw-r--r--converter/ppm/ppmtompeg/examples/walk93.param62
-rw-r--r--converter/ppm/ppmtompeg/file.c322
-rw-r--r--converter/ppm/ppmtompeg/frame.c836
-rw-r--r--converter/ppm/ppmtompeg/frametype.c365
-rw-r--r--converter/ppm/ppmtompeg/fsize.c134
-rw-r--r--converter/ppm/ppmtompeg/gethostname.c26
-rw-r--r--converter/ppm/ppmtompeg/gethostname.h7
-rw-r--r--converter/ppm/ppmtompeg/headers/all.h95
-rw-r--r--converter/ppm/ppmtompeg/headers/ansi.h76
-rw-r--r--converter/ppm/ppmtompeg/headers/bitio.h140
-rw-r--r--converter/ppm/ppmtompeg/headers/block.h53
-rw-r--r--converter/ppm/ppmtompeg/headers/byteorder.h77
-rw-r--r--converter/ppm/ppmtompeg/headers/combine.h20
-rw-r--r--converter/ppm/ppmtompeg/headers/dct.h79
-rw-r--r--converter/ppm/ppmtompeg/headers/frame.h147
-rw-r--r--converter/ppm/ppmtompeg/headers/frames.h487
-rw-r--r--converter/ppm/ppmtompeg/headers/frametype.h17
-rw-r--r--converter/ppm/ppmtompeg/headers/fsize.h49
-rw-r--r--converter/ppm/ppmtompeg/headers/general.h174
-rw-r--r--converter/ppm/ppmtompeg/headers/huff.h34
-rw-r--r--converter/ppm/ppmtompeg/headers/input.h70
-rw-r--r--converter/ppm/ppmtompeg/headers/jpeg.h12
-rw-r--r--converter/ppm/ppmtompeg/headers/mheaders.h114
-rw-r--r--converter/ppm/ppmtompeg/headers/motion_search.h154
-rw-r--r--converter/ppm/ppmtompeg/headers/mpeg.h116
-rw-r--r--converter/ppm/ppmtompeg/headers/mproto.h132
-rw-r--r--converter/ppm/ppmtompeg/headers/mtypes.h109
-rw-r--r--converter/ppm/ppmtompeg/headers/opts.h125
-rw-r--r--converter/ppm/ppmtompeg/headers/parallel.h135
-rw-r--r--converter/ppm/ppmtompeg/headers/param.h87
-rw-r--r--converter/ppm/ppmtompeg/headers/postdct.h40
-rw-r--r--converter/ppm/ppmtompeg/headers/prototypes.h78
-rw-r--r--converter/ppm/ppmtompeg/headers/psocket.h41
-rw-r--r--converter/ppm/ppmtompeg/headers/rate.h204
-rw-r--r--converter/ppm/ppmtompeg/headers/readframe.h69
-rw-r--r--converter/ppm/ppmtompeg/headers/rgbtoycc.h39
-rw-r--r--converter/ppm/ppmtompeg/headers/specifics.h36
-rw-r--r--converter/ppm/ppmtompeg/huff.c131
-rw-r--r--converter/ppm/ppmtompeg/huff.h34
-rw-r--r--converter/ppm/ppmtompeg/huff.table172
-rw-r--r--converter/ppm/ppmtompeg/iframe.c1062
-rw-r--r--converter/ppm/ppmtompeg/input.c515
-rw-r--r--converter/ppm/ppmtompeg/jpeg.c634
-rw-r--r--converter/ppm/ppmtompeg/jrevdct.c1278
-rw-r--r--converter/ppm/ppmtompeg/memory.c71
-rw-r--r--converter/ppm/ppmtompeg/mfwddct.c393
-rw-r--r--converter/ppm/ppmtompeg/mheaders.c1192
-rw-r--r--converter/ppm/ppmtompeg/moutput.c442
-rw-r--r--converter/ppm/ppmtompeg/mpeg.c1717
-rw-r--r--converter/ppm/ppmtompeg/mquant.c44
-rw-r--r--converter/ppm/ppmtompeg/nojpeg.c61
-rw-r--r--converter/ppm/ppmtompeg/noparallel.c229
-rw-r--r--converter/ppm/ppmtompeg/opts.c536
-rw-r--r--converter/ppm/ppmtompeg/parallel.c2278
-rw-r--r--converter/ppm/ppmtompeg/param.c1077
-rw-r--r--converter/ppm/ppmtompeg/parse_huff.pl198
-rw-r--r--converter/ppm/ppmtompeg/pframe.c1072
-rw-r--r--converter/ppm/ppmtompeg/postdct.c626
-rw-r--r--converter/ppm/ppmtompeg/ppmtompeg.c722
-rw-r--r--converter/ppm/ppmtompeg/psearch.c989
-rw-r--r--converter/ppm/ppmtompeg/psocket.c452
-rw-r--r--converter/ppm/ppmtompeg/qtest.c63
-rw-r--r--converter/ppm/ppmtompeg/rate.c986
-rw-r--r--converter/ppm/ppmtompeg/readframe.c1016
-rw-r--r--converter/ppm/ppmtompeg/rgbtoycc.c204
-rw-r--r--converter/ppm/ppmtompeg/specifics.c688
-rw-r--r--converter/ppm/ppmtompeg/subsample.c280
-rw-r--r--converter/ppm/ppmtompeg/tst/README30
-rw-r--r--converter/ppm/ppmtompeg/tst/gop.param30
-rw-r--r--converter/ppm/ppmtompeg/tst/par3.param43
-rw-r--r--converter/ppm/ppmtompeg/tst/short.param31
-rwxr-xr-xconverter/ppm/ppmtompeg/tst/test_all19
-rw-r--r--converter/ppm/ppmtompeg/tst/ts.mpgbin0 -> 40978 bytes
-rw-r--r--converter/ppm/ppmtompeg/tst/ts.param29
-rw-r--r--converter/ppm/ppmtompeg/tst/ts.stat52
-rw-r--r--converter/ppm/ppmtompeg/tst/ts/stennis.30.jpgbin0 -> 19080 bytes
-rw-r--r--converter/ppm/ppmtompeg/tst/ts/stennis.31.jpgbin0 -> 18678 bytes
-rw-r--r--converter/ppm/ppmtompeg/tst/ts/stennis.32.jpgbin0 -> 18379 bytes
-rw-r--r--converter/ppm/ppmtompeg/tst/ts/stennis.33.jpgbin0 -> 18012 bytes
-rw-r--r--converter/ppm/ppmtompeg/tst/ts/stennis.34.jpgbin0 -> 17871 bytes
-rw-r--r--converter/ppm/ppmtompeg/tst/ts/stennis.35.jpgbin0 -> 17398 bytes
-rw-r--r--converter/ppm/ppmtompeg/tst/ts/stennis.36.jpgbin0 -> 17085 bytes
-rw-r--r--converter/ppm/ppmtompeg/tst/ts/stennis.37.jpgbin0 -> 16806 bytes
-rw-r--r--converter/ppm/ppmtompeg/tst/ts/stennis.38.jpgbin0 -> 16522 bytes
-rw-r--r--converter/ppm/ppmtompeg/tst/ts/stennis.39.jpgbin0 -> 16291 bytes
-rw-r--r--converter/ppm/ppmtompeg/tst/ts2.param33
-rw-r--r--converter/ppm/ppmtompeg/tst/ts2.stat52
-rw-r--r--converter/ppm/ppmtompeg/tst/ts3.param32
-rw-r--r--converter/ppm/ppmtompeg/tst/ts4.param56
-rw-r--r--converter/ppm/ppmtompeg/tst/tsd.param32
-rw-r--r--converter/ppm/ppmtompeg/tst/tsd.stat52
-rw-r--r--converter/ppm/ppmtompeg/tst/tstl.param31
-rw-r--r--converter/ppm/ppmtoneo.c123
-rw-r--r--converter/ppm/ppmtopcx.c906
-rw-r--r--converter/ppm/ppmtopi1.c120
-rw-r--r--converter/ppm/ppmtopict.c481
-rw-r--r--converter/ppm/ppmtopj.c256
-rw-r--r--converter/ppm/ppmtopjxl.c423
-rw-r--r--converter/ppm/ppmtoppm.c44
-rw-r--r--converter/ppm/ppmtopuzz.c96
-rw-r--r--converter/ppm/ppmtorgb3.c99
-rw-r--r--converter/ppm/ppmtosixel.c215
-rw-r--r--converter/ppm/ppmtoterm.c173
-rw-r--r--converter/ppm/ppmtowinicon.c881
-rw-r--r--converter/ppm/ppmtoxpm.c659
-rw-r--r--converter/ppm/ppmtoyuv.c97
-rw-r--r--converter/ppm/ppmtoyuvsplit.c197
-rw-r--r--converter/ppm/qrttoppm.c69
-rw-r--r--converter/ppm/rawtoppm.c244
-rw-r--r--converter/ppm/rgb3toppm.c88
-rw-r--r--converter/ppm/sldtoppm.c650
-rw-r--r--converter/ppm/spctoppm.c212
-rw-r--r--converter/ppm/sputoppm.c108
-rw-r--r--converter/ppm/tgatoppm.c462
-rw-r--r--converter/ppm/vidtoppm.c270
-rw-r--r--converter/ppm/winico.h88
-rw-r--r--converter/ppm/winico.html701
-rw-r--r--converter/ppm/winicontoppm.c899
-rw-r--r--converter/ppm/xim.h125
-rw-r--r--converter/ppm/ximtoppm.c438
-rw-r--r--converter/ppm/xpmtoppm.README69
-rw-r--r--converter/ppm/xpmtoppm.c832
-rw-r--r--converter/ppm/xvminitoppm.c207
-rw-r--r--converter/ppm/yuvsplittoppm.c251
-rw-r--r--converter/ppm/yuvtoppm.c115
-rw-r--r--converter/tga.h46
-rw-r--r--doc/COPYRIGHT.PATENT125
-rw-r--r--doc/GPL_LICENSE.txt340
-rw-r--r--doc/HISTORY3385
-rw-r--r--doc/INSTALL248
-rw-r--r--doc/Netpbm.programming358
-rw-r--r--doc/README.CYGWIN79
-rw-r--r--doc/README.DJGPP65
-rw-r--r--doc/USERDOC155
-rw-r--r--doc/copyright_summary382
-rw-r--r--doc/lgpl_v21.txt504
-rw-r--r--doc/netpbm.14
-rw-r--r--doc/netpbm.html3
-rw-r--r--editor/Makefile86
-rw-r--r--editor/dithers.h87
-rw-r--r--editor/pamaddnoise.c486
-rw-r--r--editor/pamcomp.c619
-rw-r--r--editor/pamcut.c573
-rw-r--r--editor/pamcut.test10
-rw-r--r--editor/pamdeinterlace.c132
-rw-r--r--editor/pamdice.c494
-rw-r--r--editor/pamdither.c319
-rw-r--r--editor/pamditherbw.c743
-rw-r--r--editor/pamedge.c203
-rw-r--r--editor/pamenlarge.c117
-rw-r--r--editor/pamenlarge.test8
-rw-r--r--editor/pamflip.c910
-rw-r--r--editor/pamflip.test12
-rw-r--r--editor/pamfunc.c221
-rw-r--r--editor/pammasksharpen.c192
-rw-r--r--editor/pammixinterlace.c173
-rw-r--r--editor/pamoil.c137
-rw-r--r--editor/pamperspective.c1331
-rw-r--r--editor/pampop9.c108
-rw-r--r--editor/pamscale.c2149
-rwxr-xr-xeditor/pamstretch-gen80
-rw-r--r--editor/pamstretch.c408
-rw-r--r--editor/pamthreshold.c623
-rw-r--r--editor/pbmclean.c239
-rw-r--r--editor/pbmlife.c114
-rw-r--r--editor/pbmmask.c222
-rw-r--r--editor/pbmpscale.c199
-rw-r--r--editor/pbmreduce.c208
-rw-r--r--editor/pgmabel.c316
-rw-r--r--editor/pgmbentley.c64
-rw-r--r--editor/pgmdeshadow.c343
-rw-r--r--editor/pgmenhance.c112
-rw-r--r--editor/pgmmedian.c462
-rw-r--r--editor/pgmmorphconv.c253
-rw-r--r--editor/pnmalias.c250
-rw-r--r--editor/pnmcat.c427
-rw-r--r--editor/pnmcomp.c459
-rw-r--r--editor/pnmconvol.c1989
-rw-r--r--editor/pnmcrop.c613
-rw-r--r--editor/pnmcut.c427
-rwxr-xr-xeditor/pnmflip80
-rw-r--r--editor/pnmgamma.c753
-rw-r--r--editor/pnmhisteq.c416
-rw-r--r--editor/pnmindex.c638
-rwxr-xr-xeditor/pnmindex.csh189
-rwxr-xr-xeditor/pnmindex.sh215
-rw-r--r--editor/pnminvert.c115
-rw-r--r--editor/pnminvert.test15
-rwxr-xr-xeditor/pnmmargin88
-rw-r--r--editor/pnmmontage.c439
-rw-r--r--editor/pnmnlfilt.c1028
-rw-r--r--editor/pnmnorm.c620
-rw-r--r--editor/pnmpad.c387
-rw-r--r--editor/pnmpaste.c185
-rwxr-xr-xeditor/pnmquant275
-rw-r--r--editor/pnmremap.c872
-rw-r--r--editor/pnmrotate.c808
-rw-r--r--editor/pnmscale.c748
-rw-r--r--editor/pnmscalefixed.c590
-rw-r--r--editor/pnmshear.c227
-rw-r--r--editor/pnmsmooth.README21
-rw-r--r--editor/pnmsmooth.c241
-rw-r--r--editor/pnmstitch.c2408
-rw-r--r--editor/pnmtile.c63
-rw-r--r--editor/ppm3d.c138
-rw-r--r--editor/ppmbrighten.c337
-rw-r--r--editor/ppmchange.c232
-rw-r--r--editor/ppmcolormask.c245
-rw-r--r--editor/ppmdim.c112
-rw-r--r--editor/ppmdist.c170
-rw-r--r--editor/ppmdither.c309
-rw-r--r--editor/ppmdraw.c885
-rwxr-xr-xeditor/ppmfade309
-rw-r--r--editor/ppmflash.c114
-rw-r--r--editor/ppmglobe.c172
-rw-r--r--editor/ppmlabel.c212
-rw-r--r--editor/ppmmix.c131
-rw-r--r--editor/ppmntsc.c499
-rwxr-xr-xeditor/ppmquant30
-rwxr-xr-xeditor/ppmquantall97
-rw-r--r--editor/ppmquantall.csh57
-rw-r--r--editor/ppmrelief.c90
-rwxr-xr-xeditor/ppmshadow273
-rw-r--r--editor/ppmshadow.doc627
-rw-r--r--editor/ppmshift.c137
-rw-r--r--editor/ppmspread.c127
-rw-r--r--editor/ppmtv.c105
-rw-r--r--generator/Makefile40
-rw-r--r--generator/pamgauss.c207
-rw-r--r--generator/pamgradient.c215
-rw-r--r--generator/pamseq.c202
-rw-r--r--generator/pamstereogram.c885
-rw-r--r--generator/pamstereogram.test70
-rw-r--r--generator/pbmmake.c184
-rw-r--r--generator/pbmmake.test9
-rw-r--r--generator/pbmpage.c291
-rw-r--r--generator/pbmtext.c732
-rw-r--r--generator/pbmtextps.c377
-rw-r--r--generator/pbmupc.c544
-rw-r--r--generator/pgmcrater.c383
-rw-r--r--generator/pgmkernel.c91
-rw-r--r--generator/pgmmake.c111
-rw-r--r--generator/pgmnoise.c77
-rw-r--r--generator/pgmramp.c170
-rw-r--r--generator/ppmcie.c1201
-rw-r--r--generator/ppmcolors.c87
-rw-r--r--generator/ppmforge.c1182
-rw-r--r--generator/ppmmake.c117
-rw-r--r--generator/ppmpat.c1060
-rwxr-xr-xgenerator/ppmrainbow74
-rw-r--r--generator/ppmrough.c292
-rw-r--r--generator/ppmwheel.c157
-rwxr-xr-xinstallnetpbm3
-rw-r--r--inttypes_netpbm.h8
-rw-r--r--lib/Makefile278
-rw-r--r--lib/bitio.c207
-rw-r--r--lib/bitio.h89
-rw-r--r--lib/colorname.c208
-rw-r--r--lib/colorname.h43
-rw-r--r--lib/compile.h5
-rw-r--r--lib/fileio.c170
-rw-r--r--lib/fileio.h22
-rw-r--r--lib/libpam.c1098
-rw-r--r--lib/libpam.h17
-rw-r--r--lib/libpamcolor.c102
-rw-r--r--lib/libpammap.c662
-rw-r--r--lib/libpamn.c542
-rw-r--r--lib/libpamread.c277
-rw-r--r--lib/libpamwrite.c397
-rw-r--r--lib/libpbm.h12
-rw-r--r--lib/libpbm1.c62
-rw-r--r--lib/libpbm2.c180
-rw-r--r--lib/libpbm3.c282
-rw-r--r--lib/libpbmfont.c1136
-rw-r--r--lib/libpbmvms.c734
-rw-r--r--lib/libpgm.h24
-rw-r--r--lib/libpgm1.c324
-rw-r--r--lib/libpgm2.c226
-rw-r--r--lib/libpm.c1494
-rw-r--r--lib/libpnm1.c222
-rw-r--r--lib/libpnm2.c128
-rw-r--r--lib/libpnm3.c393
-rw-r--r--lib/libppm.h15
-rw-r--r--lib/libppm1.c328
-rw-r--r--lib/libppm2.c208
-rw-r--r--lib/libppmcmap.c650
-rw-r--r--lib/libppmcolor.c710
-rw-r--r--lib/libppmd.c1177
-rw-r--r--lib/libppmfloyd.c270
-rw-r--r--lib/libppmfuzzy.c434
-rw-r--r--lib/libsystem.c319
-rw-r--r--lib/libsystem_dummy.c47
-rw-r--r--lib/lum.h123
-rw-r--r--lib/mkstemp.c8
-rw-r--r--lib/pam.h490
-rw-r--r--lib/pammap.h124
-rw-r--r--lib/path.c468
-rw-r--r--lib/pbm.h97
-rw-r--r--lib/pbmfont.h75
-rw-r--r--lib/pgm.h136
-rw-r--r--lib/pm.h346
-rw-r--r--lib/pm_gamma.h68
-rw-r--r--lib/pm_system.h43
-rw-r--r--lib/pnm.h134
-rw-r--r--lib/ppm.h300
-rw-r--r--lib/ppmcmap.h111
-rw-r--r--lib/ppmdfont.c133
-rw-r--r--lib/ppmdfont.h74
-rw-r--r--lib/ppmdraw.h303
-rw-r--r--lib/ppmfloyd.h67
-rw-r--r--lib/rgb.txt881
-rw-r--r--lib/standard.ppmdfontbin0 -> 3899 bytes
-rw-r--r--lib/standardppmdfont.c3177
-rw-r--r--lib/util/LICENSE.txt121
-rw-r--r--lib/util/Makefile29
-rw-r--r--lib/util/bitreverse.h46
-rw-r--r--lib/util/filename.c26
-rw-r--r--lib/util/filename.h7
-rw-r--r--lib/util/intcode.h86
-rw-r--r--lib/util/lexheader9
-rw-r--r--lib/util/mallocvar.h107
-rw-r--r--lib/util/nstring.c906
-rw-r--r--lib/util/nstring.h157
-rw-r--r--lib/util/pm_c_util.h62
-rw-r--r--lib/util/shhopt-1.1.6.lsm16
-rw-r--r--lib/util/shhopt.README200
-rw-r--r--lib/util/shhopt.c939
-rw-r--r--lib/util/shhopt.h240
-rw-r--r--lib/util/testnstring.c30
-rw-r--r--lib/util/wordaccess.h65
-rw-r--r--lib/util/wordaccess_64_le.h52
-rw-r--r--lib/util/wordaccess_gcc3_be.h40
-rw-r--r--lib/util/wordaccess_gcc3_le.h54
-rw-r--r--lib/util/wordaccess_generic.h89
-rwxr-xr-xmanweb427
-rw-r--r--netpbm.c80
-rw-r--r--other/Makefile75
-rw-r--r--other/pamarith.c503
-rw-r--r--other/pambayer.c297
-rw-r--r--other/pamchannel.c199
-rw-r--r--other/pamdepth.c175
-rw-r--r--other/pamendian.c70
-rw-r--r--other/pamlookup.c299
-rw-r--r--other/pampick.c250
-rw-r--r--other/pamsplit.c187
-rw-r--r--other/pamstack.c240
-rw-r--r--other/pamsummcol.c257
-rw-r--r--other/pamx/COPYRIGHT72
-rw-r--r--other/pamx/Makefile43
-rw-r--r--other/pamx/Makefile251
-rw-r--r--other/pamx/fill.c82
-rw-r--r--other/pamx/fill.h16
-rw-r--r--other/pamx/image.c331
-rw-r--r--other/pamx/image.h90
-rw-r--r--other/pamx/pamx.c364
-rw-r--r--other/pamx/send.c872
-rw-r--r--other/pamx/send.h38
-rw-r--r--other/pamx/valtomem.h65
-rw-r--r--other/pamx/window.c1209
-rw-r--r--other/pamx/window.h38
-rw-r--r--other/pamx/ximageinfo.h25
-rw-r--r--other/pnmcolormap.c973
-rw-r--r--other/ppmdcfont.c200
-rw-r--r--other/ppmddumpfont.c89
-rw-r--r--other/ppmdmkfont.c705
-rw-r--r--other/ppmsvgalib.c283
-rwxr-xr-xother/ppmtomap5
-rw-r--r--pm_config.h302
-rw-r--r--pm_config.in.h278
-rw-r--r--testgrid.pbm3
-rw-r--r--testimg.ppm4
-rw-r--r--urt/Makefile33
-rw-r--r--urt/README20
-rw-r--r--urt/Runput.c356
-rw-r--r--urt/Runput.h23
-rw-r--r--urt/cmd_name.c54
-rw-r--r--urt/rle.h492
-rw-r--r--urt/rle_addhist.c115
-rw-r--r--urt/rle_code.h70
-rw-r--r--urt/rle_config.h105
-rw-r--r--urt/rle_error.c118
-rw-r--r--urt/rle_getcom.c99
-rw-r--r--urt/rle_getrow.c562
-rw-r--r--urt/rle_getskip.c162
-rw-r--r--urt/rle_global.c85
-rw-r--r--urt/rle_hdr.c297
-rw-r--r--urt/rle_open_f.c310
-rw-r--r--urt/rle_put.h104
-rw-r--r--urt/rle_putcom.c169
-rw-r--r--urt/rle_putrow.c728
-rw-r--r--urt/rle_row_alc.c129
-rw-r--r--urt/scanargs.c948
-rw-r--r--urt/vaxshort.c50
-rw-r--r--urt/vaxshort.h5
-rw-r--r--version.h1
-rwxr-xr-xvms/Add_List.com59
-rwxr-xr-xvms/Make_PBMplus.com519
-rwxr-xr-xvms/Make_PBMplusShr.com280
-rw-r--r--vms/NetPBM.TeX12115
-rw-r--r--vms/PBMplus.hlp6814
-rw-r--r--vms/RGB.txt738
-rwxr-xr-xvms/SetUp.com37
-rwxr-xr-xvms/stamp-date.com25
1133 files changed, 318857 insertions, 0 deletions
diff --git a/GNUmakefile b/GNUmakefile
new file mode 100644
index 00000000..d26ddf50
--- /dev/null
+++ b/GNUmakefile
@@ -0,0 +1,393 @@
+# Makefile for Netpbm
+ 
+# Configuration should normally be done in the included file Makefile.config.
+
+# Targets in this file:
+#
+#   nonmerge:     Build everything, in the source directory.
+#   merge:        Build everything as merged executables, in the source dir
+#   package:      Make a package of Netpbm files ready to install
+#   
+#   The default target is either "merge" or "nonmerge", as determined by
+#   the DEFAULT_TARGET variable set by Makefile.config.
+
+# About the "merge" target: Normally the Makefiles build separate
+# executables for each program.  However, on some systems (especially
+# those without shared libraries) this can mean a lot of space.  In
+# this case you might try building a "merge" instead.  The idea here
+# is to link all the programs together into one huge executable, along
+# with a tiny dispatch program that runs one of the programs based on
+# the command name with which it was invoked.  You install the merged
+# executable with a file system link for the name of each program it
+# includes.  This is much more important when you're statically
+# linking than when you're using shared libraries.  On a Sun3 under
+# SunOS 3.5, where shared libraries are not available, the space for
+# executables went from 2970K to 370K in an older Netpbm.  On a
+# GNU/Linux IA32 system with shared libraries in 2002, it went from
+# 2949K to 1663K.
+
+# To build a "merge" system, just set DEFAULT_TARGET to "merge" instead
+# of "nomerge" in Makefile.config.  In that case, you should probably also
+# set NETPBMLIBTYPE to "unixstatic", since a shared library doesn't do you 
+# much good.
+
+# The CURDIR variable presents a problem because it was introduced in
+# GNU Make 3.77.  We need the CURDIR variable in order for our 'make
+# -C xxx -f xxx' commands to work.  If we used the obvious alternative
+# ".", that wouldn't work because it would refer to the directory
+# named in -C, not the directory the make file you are reading is
+# running in.  The -f option is necessary in order to have separate
+# source and object directories.
+
+ifeq ($(CURDIR)x,x)
+all package install:
+	@echo "YOU NEED AT LEAST VERSION 3.77 OF GNU MAKE TO BUILD NETPBM."
+	@echo "Netpbm's makefiles need the CURDIR variable that was "
+	@echo "introduced in 3.77.  Your version does not have CURDIR."
+	@echo
+	@echo "You can get a current GNU Make via http://www.gnu.org/software"
+	@echo 
+	@echo "If upgrading is impossible, try modifying GNUMakefile and "
+	@echo "Makefile.common to replace \$(CURDIR) with \$(shell /bin/pwd) "
+else
+
+
+include Makefile.srcdir
+BUILDDIR = $(CURDIR)
+SUBDIR = 
+VPATH=.:$(SRCDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+PRODUCT_SUBDIRS = lib converter analyzer editor generator other
+SUPPORT_SUBDIRS = urt buildtools
+
+SUBDIRS = $(PRODUCT_SUBDIRS) $(SUPPORT_SUBDIRS)
+
+SCRIPTS = manweb
+MANUALS1 = netpbm
+NOMERGEBINARIES = netpbm
+
+OBJECTS = netpbm.o
+
+default: $(DEFAULT_TARGET)
+	echo "EXISTENCE OF THIS FILE MEANS NETPBM HAS BEEN BUILT." \
+	  >build_complete
+	@echo ""
+	@echo "Netpbm is built.  The next step is normally to package it "
+	@echo "for installation by running "
+	@echo ""
+	@echo "    make package pkgdir=DIR"
+	@echo ""
+	@echo "to copy all the Netpbm files you need to install into the "
+	@echo "directory DIR.  Then you can proceed to install."
+
+all: nonmerge
+
+.PHONY: nonmerge
+nonmerge: $(PRODUCT_SUBDIRS:%=%/all)
+
+OMIT_CONFIG_RULE = 1
+OMIT_VERSION_H_RULE = 1
+OMIT_INTTYPES_RULE = 1
+include $(SRCDIR)/Makefile.common
+
+$(BUILDDIR)/Makefile.config: $(SRCDIR)/Makefile.config.in
+	$(SRCDIR)/configure $(SRCDIR)/Makefile.config.in
+
+
+# typegen is a utility program used by the make file below.
+TYPEGEN = $(BUILDDIR)/buildtools/typegen
+
+# endiangen is a utility program used by the make file below.
+ENDIANGEN = $(BUILDDIR)/buildtools/endiangen
+
+$(TYPEGEN): $(BUILDDIR)/buildtools
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/buildtools/Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+
+DELETEIT = (rm -f $@ || false)
+
+$(BUILDDIR)/inttypes_netpbm.h: $(TYPEGEN)
+	$(TYPEGEN) >$@ || $(DELETEIT)
+
+# We run a couple of programs on the build machine in computing the
+# contents of pm_config.h.  We need to give the user a way not to do
+# that or to override the results, because it doesn't work if he's
+# cross compiling.
+
+$(BUILDDIR)/pm_config.h: \
+  $(SRCDIR)/pm_config.in.h Makefile.config inttypes_netpbm.h $(ENDIANGEN)
+	echo '/* pm_config.h GENERATED BY A MAKE RULE */' >$@ || $(DELETEIT)
+	echo '#ifndef PM_CONFIG_H' >>$@ || $(DELETEIT)
+	echo '#define PM_CONFIG_H' >>$@ || $(DELETEIT)
+ifeq ($(INTTYPES_H)x,x)
+	echo '/* Don't need to #include any inttypes.h-type thing */
+else
+  ifeq ($(INTTYPES_H),"inttypes_netpbm.h")
+	cat inttypes_netpbm.h >>$@ || $(DELETEIT)
+  else
+	echo '#include $(INTTYPES_H)' >>$@ || $(DELETEIT)
+  endif
+endif
+ifeq ($(HAVE_INT64),Y)
+	echo "#define HAVE_INT64 1" >>$@ || $(DELETEIT)
+else
+	echo "#define HAVE_INT64 0" >>$@ || $(DELETEIT)
+endif	
+	echo '/* pm_config.h.in FOLLOWS ... */' >>$@ || $(DELETEIT)
+	cat $(SRCDIR)/pm_config.in.h >>$@ || $(DELETEIT)
+	$(ENDIANGEN) >>$@ || $(DELETEIT)
+	echo '#endif' >>$@ || $(DELETEIT)
+
+
+MAJOR := $(NETPBM_MAJOR_RELEASE)
+MINOR := $(NETPBM_MINOR_RELEASE)
+POINT := $(NETPBM_POINT_RELEASE)
+$(BUILDDIR)/version.h:
+	@rm -f $@
+	@echo "/* Generated by make file rule */" >$@
+	@echo "#define NETPBM_VERSION" \
+	  \"Netpbm $(MAJOR).$(MINOR).$(POINT)"\"" >$@
+
+
+.PHONY: install
+install:
+	@echo "After doing a 'make', do "
+	@echo ""
+	@echo "  make package pkgdir=DIR"
+	@echo ""
+	@echo "to copy all the Netpbm files you need to install into the "
+	@echo "directory DIR."
+	@echo ""
+	@echo "Then, do "
+	@echo ""
+	@echo "  ./installnetpbm"
+	@echo
+	@echo "to install from there to your system via an interactive.  "
+	@echo "dialog.  Or do it manually using simple copy commands and "
+	@echo "following instructions in the file DIR/README"
+
+.PHONY: package package_build init_package advise_installnetpbm
+package: build_complete package_build advise_installnetpbm
+
+build_complete:
+# The regular build creates this file as its last act, so if it doesn't exist,
+# that means either the user skipping the build step, or the build failed.
+	@echo "You must build Netpbm before you can package Netpbm. "
+	@echo "The usual way to do this is to type 'make' with no arguments."
+	@echo "If you did that, then the build apparently failed.  There "
+	@echo "should have been error messages indicating why.  If you "
+	@echo "can't fix the build problem, you can do 'make --keep-going' "
+	@echo "to force the build to continue with other parts that "
+	@echo "it may be able to build successfully, then do "
+	@echo "'make package --keep-going' to package whatever was "
+	@echo "successfully built."
+	@echo
+	@false;
+
+package_build: init_package install-run install-dev 
+
+MAJOR=$(NETPBM_MAJOR_RELEASE)
+MINOR=$(NETPBM_MINOR_RELEASE)
+POINT=$(NETPBM_POINT_RELEASE)
+
+init_package:
+	@if [ -d $(PKGDIR) ]; then \
+	  echo "Directory $(PKGDIR) already exists.  Please specify a "; \
+	  echo "directory that can be created fresh, like this: "; \
+	  echo "  make package PKGDIR=/tmp/newnetpbm "; \
+	  false; \
+	  fi
+	mkdir $(PKGDIR)
+	echo "Netpbm install package made by 'make package'" \
+	    >$(PKGDIR)/pkginfo
+	date >>$(PKGDIR)/pkginfo
+	echo Netpbm $(MAJOR).$(MINOR).$(POINT) >$(PKGDIR)/VERSION
+	$(INSTALL) -c -m 664 $(SRCDIR)/buildtools/README.pkg $(PKGDIR)/README
+	$(INSTALL) -c -m 664 $(SRCDIR)/buildtools/config_template \
+	  $(PKGDIR)/config_template
+
+advise_installnetpbm:
+	@echo
+	@echo "Netpbm has been successfully packaged under directory"
+	@echo "$(PKGDIR).  Run 'installnetpbm' to install it on your system."
+
+.PHONY: install-run
+ifeq ($(DEFAULT_TARGET),merge)
+install-run: install-merge
+else
+install-run: install-nonmerge 
+endif
+
+.PHONY: install-merge install-nonmerge
+install-merge: install.merge install.lib install.data \
+	install.manweb install.man
+
+install-nonmerge: install.bin install.lib install.data \
+	install.manweb install.man
+
+.PHONY: merge
+merge: lib/all netpbm
+
+MERGELIBS = 
+ifneq ($(ZLIB),NONE)
+  MERGELIBS += $(ZLIB)
+endif
+ifneq ($(JPEGLIB),NONE)
+  MERGELIBS += $(JPEGLIB)
+endif
+ifneq ($(TIFFLIB),NONE)
+  MERGELIBS += $(TIFFLIB)
+endif
+ifneq ($(URTLIB),NONE)
+  MERGELIBS += $(URTLIB)
+endif
+ifneq ($(LINUXSVGALIB),NONE)
+  MERGELIBS += $(LINUXSVGALIB)
+endif
+ifneq ($(X11LIB),NONE)
+  MERGELIBS += $(X11LIB)
+endif
+
+ifeq ($(shell libpng-config --version),)
+  PNGLD = $(shell $(LIBOPT) $(LIBOPTR) $(PNGLIB) $(ZLIB))
+else
+  PNGLD = $(shell libpng-config --ldflags)
+endif
+
+ifeq ($(shell xml2-config --version),)
+  XML2LD=
+else
+  XML2LD=$(shell xml2-config --libs)
+endif
+
+
+# If URTLIB is BUNDLED_URTLIB, then we're responsible for building it, which
+# means it needs to be a dependency:
+ifeq ($(URTLIB),$(BUNDLED_URTLIB))
+  URTLIBDEP = $(URTLIB)
+endif
+
+# We have two different ways to do the merge build:
+#  
+#   1) Each directory produces an object file merge.o containing all the code
+#      in that directory and its descendants that needs to go into the 'netpbm'
+#      program.  The make files do this recursively, via a link command that
+#      combines multiple relocateable object files into one.  All we do here
+#      at the top level is make merge.o and link it with netpbm.o and the
+#      libraries.
+#
+#      This is the clean way, and we use it whenever we can.  But we don't
+#      know how to do the link on every platform.
+#
+#   2) Each directory produces a list of all the object files in that 
+#      directory and its descendants that need to go into the 'netpbm'
+#      program.  This list is in a file called 'mergelist'.  The make files
+#      do this recursively.  Here at the top level, we make mergelist and
+#      then do one large link of everything listed in it, plus netpbm.o and
+#      the libraries.
+#
+#      This doesn't require any special link command like (1), but is
+#      not very clean.  The dependencies don't work right.  And at least
+#      one linker (on DJGPP) can't handle that many input files.
+
+ifeq ($(LDRELOC),NONE)
+  OBJECT_DEP = mergelist
+  OBJECT_LIST = `cat mergelist`
+else
+  OBJECT_DEP = merge.o
+  OBJECT_LIST = merge.o
+endif
+
+netpbm:%:%.o $(OBJECT_DEP) $(NETPBMLIB) $(URTLIBDEP) $(LIBOPT)
+# Note that LDFLAGS might contain -L options, so order is important.
+	$(LD) -o $@ $< $(OBJECT_LIST) \
+          $(LDFLAGS) $(shell $(LIBOPT) $(NETPBMLIB) $(MERGELIBS)) \
+	  $(PNGLD) $(XML2LD) $(MATHLIB) $(NETWORKLD) $(LADD)
+
+netpbm.o: mergetrylist
+
+install.merge: local.install.merge
+.PHONY: local.install.merge
+local.install.merge:
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmnoraw
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm gemtopbm
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnminterp
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pgmoil
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm ppmtojpeg
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm bmptoppm
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pgmnorm
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmfile
+
+ifneq ($(NETPBMLIBTYPE),unixstatic)
+install.lib: lib/install.lib
+else
+install.lib:
+endif
+
+.PHONY: install.manweb
+install.manweb: $(PKGDIR)/man/web/netpbm.url $(PKGDIR)/bin/doc.url
+
+$(PKGDIR)/man/web/netpbm.url: $(PKGDIR)/man/web
+	echo "$(NETPBM_DOCURL)" > $@
+	chmod $(INSTALL_PERM_MAN) $@
+
+$(PKGDIR)/bin/doc.url: $(PKGDIR)/bin
+	echo "$(NETPBM_DOCURL)" > $@
+	chmod $(INSTALL_PERM_MAN) $@
+
+.PHONY: install-dev
+# Note that you might install the development package and NOT the runtime
+# package.  If you have a special system for building stuff, maybe for 
+# multiple platforms, that's what you'd do.  Ergo, install.lib is here even
+# though it is also part of the runtime install.
+install-dev: install.hdr install.staticlib install.lib install.sharedlibstub
+
+.PHONY: install.hdr
+install.hdr: $(PKGDIR)/include
+	$(MAKE) -C lib -f $(SRCDIR)/lib/Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) install.hdr
+	$(INSTALL) -c -m $(INSTALL_PERM_HDR) \
+	    $(SRCDIR)/pm_config.h $(PKGDIR)/include
+
+ifeq ($(STATICLIB_TOO),y)
+BUILD_STATIC = y
+else
+  ifeq ($(NETPBMLIBTYPE),unixstatic)
+    BUILD_STATIC = y
+  else
+    BUILD_STATIC = n
+  endif
+endif
+
+.PHONY: install.staticlib
+install.staticlib: 
+ifeq ($(BUILD_STATIC),y)
+	$(MAKE) -C lib -f $(SRCDIR)/lib/Makefile \
+	SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) install.staticlib 
+endif
+
+.PHONY: install.sharedlibstub
+install.sharedlibstub:
+	$(MAKE) -C lib -f $(SRCDIR)/lib/Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) install.sharedlibstub 
+
+clean: localclean
+
+.PHONY: localclean
+localclean:
+	rm -f netpbm build_started build_complete
+	rm -f pm_config.h inttypes_netpbm.h version.h
+
+# Note that removing Makefile.config must be the last thing we do,
+# because no other makes will work after that is done.
+distclean: localdistclean
+.PHONY: localdistclean
+localdistclean:
+	-rm -f `find -type l`
+	-rm -f Makefile.config
+
+# The following endif is for the else block that contains virtually the
+# whole file, for the test of the existence of CURDIR.
+endif
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..2227eb5c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+# The Netpbm make files exploit features of GNU Make that other Makes
+# do not have.  Because it is a common mistake for users to try to build
+# Netpbm with a different Make, we have this make file that does nothing
+# but tell the user to use GNU Make.
+
+# If the user were using GNU Make now, this file would not get used because
+# GNU Make uses a make file named "GNUmakefile" in preference to "Makefile"
+# if it exists.  Netpbm is shipped with a "GNUmakefile".
+
+all merge install clean dep:
+	@echo "You must use GNU Make to build Netpbm.  You are running some "
+	@echo "other Make.  GNU Make may be installed on your system with "
+	@echo "the name 'gmake'.  If not, see http://www.gnu.org/software ."
+	@echo
diff --git a/Makefile.common b/Makefile.common
new file mode 100644
index 00000000..a6b6952e
--- /dev/null
+++ b/Makefile.common
@@ -0,0 +1,545 @@
+# -*-makefile-*-    <-- an Emacs control
+# This is a make file inclusion, to be included in all the
+# Netpbm make files.
+
+# This file is meant to contain rules that are substantially the same
+# in each of the pbm, pgm, ppm, and pnm subdirectory makes, to avoid
+# duplication of effort.
+
+# The following variables must be set in any make file that uses these
+# rules:
+#
+# SRCDIR: The top level directory of Netpbm source code.
+# BUILDDIR: The top level directory into which Netpbm is built (built,
+#   not installed).
+# SUBDIR: The directory, relative to BUILDDIR, of the current directory.
+#   It is also the directory, relative to SRCDIR, of source directory that
+#   corresponds to the current directory.  Note that you build in the 
+#   current directory, using files from the source directory.
+# SUBDIRS: list of subdirectories in which certain targets (e.g. 'clean')
+#   should be made recursively.
+# PKGDIR_DEFAULT: The place to put the packaged stuff for 'make package'
+#   if the user doesn't put "pkgdir=" on the Make command line.
+# PKGMANDIR: The subdirectory (e.g. "man" or "share/man" of the package
+#   directory root in which man pages should be packaged.
+# OBJECTS: .o files to be built from .c files with the standard rule.
+# PORTBINARIES: list of conventional executables to be built with the standard
+#   rule
+# MATHBINARIES: obsolete.
+# DATAFILES: list of files that should be installed in the "data" directory.
+# NETPBMLIBSUFFIX: the suffix, e.g. "so" for the main libraries we build,
+#   whatever type they may be.
+# STATICLIBSUFFIX: the suffix, e.g. "a" on a static library.  This need
+#   not be defined if the user doesn't want to build a static libraries in 
+#   addition to the main libraries.
+# BINARIES: list of all the executables that need to be installed.
+# INSTALL: command to use to copy files to where they belong
+# INSTALL_PERM_BIN: file permissions for installed binaries
+# INSTALL_PERM_LIB: ...same for libraries
+# INSTALL_PERM_HDR: ...same for headers
+# INSTALL_PERM_MAN: ...same for man pages
+# MERGE_OBJECTS: list of object files that go into the merged executable
+#   from the current directory (not subdirectories).  All of these are to
+#   be built with the standard rule for merged objects.  These names are
+#   relative to the current make directory (must not start with / ).
+# MERGEBINARIES: list of the programs that, in a merge build, are invoked
+#   via the merged Netpbm program
+# CC: C compiler command 
+# CPPFLAGS: C preprocessor options
+# CFLAGS: C compiler general options
+# LD: linker command
+# LINKERISCOMPILER: 'Y' if the linker invoked by LD is actually a compiler
+#   front end, so takes linker options in a different format
+# LDFLAGS: linker options 
+# LIBS or LOADLIBES: names of libraries to be added to all links
+# INCLUDES: Compiler option string to establish the search path for include
+#   files when compiling things or computing dependencies (make dep).
+#   current directory, corresponding source tree directory, and ./importinc
+#   are implied, so should not be in here.
+
+# In addition, there is CADD, which is extra C compilation flags and
+# is intended to be set on a make command line (e.g. 'make CADD=-g')
+# for flags that apply just to a particular build.
+
+# In addition, there is CFLAGS_PERSONAL, which is extra C
+# compilation flags and is expected to be set via environment variable
+# for flags that are particular to the person doing the build and not
+# specific to Netpbm.
+
+NETPBM_MAJOR_RELEASE = 10
+NETPBM_MINOR_RELEASE = 35
+NETPBM_POINT_RELEASE = 0
+
+INCLUDES2 := $(INCLUDES) -I$(SRCDIR)/$(SUBDIR) -I. -I importinc
+
+ifeq ($(NETPBMLIBTYPE),unixstatic)
+  NETPBMLIBFNAME = libnetpbm.$(STATICLIBSUFFIX)
+else
+  NETPBMLIBFNAME = $(NETPBMSHLIBPREFIX)netpbm$(DLLVER).$(NETPBMLIBSUFFIX)
+endif
+NETPBMLIB = $(BUILDDIR)/lib/$(NETPBMLIBFNAME)
+
+BUNDLED_URTLIB = $(BUILDDIR)/urt/librle.a
+
+# LIBS and LOADLIBES are commonly set as environment variables.
+# LOADLIBES is used by GNU Make's implicit .c->.o rule.  LIBS is used by
+# GNU Autoconf.
+
+LDLIBS = $(LOADLIBES) $(LIBS)
+
+# 'pkgdir' is meant to be set on the make command line.  Results are
+# disastrous if PKGDIR is a relative directory, and I don't know any
+# way to detect that case and fail, so I just add a '/' to the front
+# if it isn't already there.
+ifneq ($(pkgdir)x,x)
+  PKGDIR = $(patsubst //%, /%, /$(pkgdir))
+else
+  PKGDIR = $(PKGDIR_DEFAULT)
+endif
+
+#===========================================================================
+# We build a directory full of symbolic links to the intra-Netpbm public
+# header files just so the compile commands don't have to be littered
+# with long -I's.
+#===========================================================================
+
+# Note that the "root" headers are in the root of the build tree, not
+# the source tree.  All generated headers are in the root directory and
+# all root directory headers are generated.
+IMPORTINC_ROOT_HEADERS := pm_config.h inttypes_netpbm.h version.h
+
+IMPORTINC_LIB_HEADERS := \
+  pm.h pbm.h pgm.h ppm.h pnm.h pam.h bitio.h pbmfont.h ppmcmap.h \
+  pammap.h colorname.h ppmfloyd.h ppmdraw.h pm_system.h ppmdfont.h \
+  pm_gamma.h lum.h
+
+IMPORTINC_LIB_UTIL_HEADERS := \
+  bitreverse.h mallocvar.h nstring.h filename.h pm_c_util.h shhopt.h \
+  wordaccess.h wordaccess_64_le.h wordaccess_gcc3_be.h wordaccess_gcc3_le.h \
+  wordaccess_generic.h intcode.h \
+
+IMPORTINC_HEADERS := \
+  $(IMPORTINC_ROOT_HEADERS) \
+  $(IMPORTINC_LIB_HEADERS) \
+  $(IMPORTINC_LIB_UTIL_HEADERS)
+
+IMPORTINC_ROOT_FILES := $(IMPORTINC_ROOT_HEADERS:%=importinc/%)
+IMPORTINC_LIB_FILES := $(IMPORTINC_LIB_HEADERS:%=importinc/%)
+IMPORTINC_LIB_UTIL_FILES := $(IMPORTINC_LIB_UTIL_HEADERS:%=importinc/%)
+
+importinc: \
+  $(IMPORTINC_ROOT_FILES) \
+  $(IMPORTINC_LIB_FILES) \
+  $(IMPORTINC_LIB_UTIL_FILES) \
+
+$(IMPORTINC_ROOT_FILES):importinc/%:$(BUILDDIR)/%
+	mkdir -p importinc
+	rm -f $@
+	$(SYMLINK) $< $@
+
+$(IMPORTINC_LIB_FILES):importinc/%:$(SRCDIR)/lib/%
+	mkdir -p importinc
+	rm -f $@
+	$(SYMLINK) $< $@
+
+$(IMPORTINC_LIB_UTIL_FILES):importinc/%:$(SRCDIR)/lib/util/%
+	mkdir -p importinc
+	rm -f $@
+	$(SYMLINK) $< $@
+
+
+# We build the symbolic links to header files in the current directory
+# just so the compile commands don't have to be littered with -I's.
+
+bmp.h tga.h:%:$(SRCDIR)/converter/%
+	rm -f $@
+	$(SYMLINK) $< $@
+
+ifneq ($(OMIT_VERSION_H_RULE),1)
+
+$(BUILDDIR)/version.h:
+	$(MAKE) -C $(dir $@) $(notdir $@)
+endif
+
+ifneq ($(OMIT_CONFIG_RULE),1)
+$(BUILDDIR)/Makefile.config: $(SRCDIR)/Makefile.config.in
+	$(MAKE) -C $(dir $@) $(notdir $@)
+
+$(BUILDDIR)/pm_config.h:
+	$(MAKE) -C $(dir $@) $(notdir $@)
+endif
+
+ifneq ($(OMIT_INTTYPES_RULE),1)
+$(BUILDDIR)/inttypes_netpbm.h:
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/GNUmakefile $(notdir $@)
+endif
+
+# Note that any time you do a make on a fresh Netpbm source tree,
+# Make notices that 'Makefile.config', which the make files include, does not
+# exist and runs the "Makefile.config" target, which runs Configure.
+# If the "config" target were to run Configure as well, it would get run
+# twice in a row if you did a 'make config' on a fresh Netpbm source tree.
+# But we don't want to make "config" just a no-op, because someone might
+# try it after Makefile.config already exists, in order to make a new
+# Makefile.config.  Issuing a message as follows seems to make sense in 
+# both cases.
+.PHONY: config
+config:
+	@echo "To reconfigure the build, run 'configure'"
+
+# Rule to make C source from lex source.
+%.c:%.l
+	$(LEX) -t $< >$(notdir $@)
+
+# Rule to make regular object files, e.g. pnmtojpeg.o.
+
+$(OBJECTS): %.o: %.c importinc
+#############################################################################
+# Note that the user may have configured -I options into CFLAGS or CPPFLAGS.
+# -I. is needed when builddir != srcdir
+# Note about -o: There used to be systems that couldn't handle a space
+# between flag and value.  But we found a Solaris gcc on 2003.09.02 that
+# actually fails _without_ the space (it invokes Solaris 'as' with the
+# following command, which generates a "no input filename" error:
+# '/usr/ccs/bin/as -V -Qy -s -o/tmp/hello.o /var/tmp/ccpiNnia.s')
+# This rule has had the space since way before that, so it looks like
+# the space is no longer a problem for anyone.
+#############################################################################
+#
+# The NDEBUG macro says not to build code that assumes there are no bugs.
+# This makes the code go faster.  The main thing it does is tell the C library
+# to make assert() a no-op as opposed to generating code to check the
+# assertion and crash the program if it isn't really true.  You can add
+# -UNDEBUG (in any of various ways) to override this.
+#
+	$(CC) -c $(INCLUDES2) -DNDEBUG \
+	    $(CPPFLAGS) $(CFLAGS) $(CFLAGS_PERSONAL) $(CADD) -o $@ $<
+
+# libopt is a utility program used in the make file below.
+LIBOPT = $(BUILDDIR)/buildtools/libopt
+
+ifneq ($(OMIT_BUILDTOOL_RULE),1)
+$(LIBOPT) $(TYPEGEN): $(BUILDDIR)/buildtools
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/buildtools/Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+endif
+
+ifneq ($(OMIT_LIBRARY_RULE),1)
+$(NETPBMLIB): $(BUILDDIR)/lib
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/lib/Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+endif
+
+ifneq ($(OMIT_URT_RULE),1)
+$(BUNDLED_URTLIB): $(BUILDDIR)/urt
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/urt/Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+endif
+
+# Here are some notes from Nelson H. F. Beebe on April 16, 2002:
+#
+#   There are at least three incompatible kinds of command-line options
+#   that tell the compiler to instruct the linker to save library paths in
+#   the executable:
+#   
+#         -Wl,-rpath,/path/to/dir       gcc, g++, FreeBSD, SGI, Sun compilers
+#         -rpath /path/to/dir           Compaq/DEC, SGI compilers
+#         -Rdir:dir:dir	                Portland Group, Sun compilers
+#   
+#   Notice that SGI and Sun support two such flavors.
+#
+#   Plus, Scott Schwartz observed on March 25, 2003 that while his
+#   compiler understands -Wl, his linker does not understand -rpath.
+#   His compiler is "Sun WorkShop 6 update 2 C 5.3 2001/05/15".
+#
+#   Plus, Mike Saunders found in December 2003 that his Solaris 8 system
+#   (uname -a says 'SunOS cannonball.method.cx 5.8 Generic_108528-14 
+#   sun4u sparc SUNW,Ultra-1') with Gcc 2.95.3 requires the syntax
+#
+#         -Wl,-R,/path/to/dir
+#   
+#   This is apparently because Gcc invokes this linker for Saunders:
+#
+#      ld: Software Generation Utilities - Solaris Link Editors: 5.8-1.273
+#
+#   I'd say there are also Solaris systems where Gcc invokes the GNU linker
+#   and then the option would be -Wl,-rpath...
+#
+#   On IA32 Linux, at least, GNU ld takes -rpath.  It also has a -R option,
+#   but it is something else.
+
+ifneq ($(NETPBMLIB_RUNTIME_PATH)x,x)
+  ifeq ($(LINKERISCOMPILER),Y)
+    # Before Netpbm 10.14 (March 2003), it looks like we used -R
+    # instead of -Wl,-rpath on all but a few selected platforms as configured
+    # by Configure.  But that doesn't make sense, because we also used
+    # LD=$(CC) always.  Beebe's notes and Saunders' observation above
+    # above indicate that we need
+    # -Wl,... everywhere that a compiler is used, whether native or GNU, 
+    # to link.
+    RPATH = -Wl,$(RPATHOPTNAME),$(NETPBMLIB_RUNTIME_PATH)
+  else
+    RPATH = $(RPATHOPTNAME)$(NETPBMLIB_RUNTIME_PATH)
+  endif
+else
+  RPATH =
+endif
+
+# Rules for conventional single-object file executables
+
+# Before Netpbm 10.21 (March 2004), we kept separate lists of binaries
+# that require the math library and those that don't, so the binaries
+# that don't need it wouldn't have to link it.  But now libnetpbm
+# contains gamma correction routines, so it needs the math library,
+# and that means every Netpbm binary needs the math library, whether
+# it calls those routines or not.  So we will phase out the separate
+# lists, and for now we treat them identically.
+
+# Note that GNU C library sometimes defines math functions as inline
+# functions, so linking the math library isn't really necessary.  Late
+# model GNU C libraries do this only if you specify the -ffast-math
+# Gcc option (as told by the __FAST_MATH__ preprocessor macro).
+# Earlier ones do it regardless of __FAST_MATH__.
+
+MATHLIB ?= -lm
+$(PORTBINARIES) $(MATHBINARIES): %: %.o $(NETPBMLIB) $(LIBOPT)
+# Note that LDFLAGS might contain -L options, so order is important.
+# LDFLAGS is commonly set as an environment variable.
+	$(LD) -o $@ $@.o $(MATHLIB) $(shell $(LIBOPT) $(NETPBMLIB)) \
+	  $(LDFLAGS) $(LDLIBS) $(RPATH) $(LADD)
+
+
+# MERGE STUFF
+
+# .o2 is our suffix for an object file that has had it's main() changed
+# to e.g. main_pamcut().  We use them for the merge build.
+
+%.o2: %.c importinc
+# Note that the user may have configured -I options into CFLAGS.
+	$(CC) -c $(INCLUDES2) -DNDEBUG $(CFLAGS) \
+	  "-Dmain=main_$*" \
+          $(CFLAGS_MERGE) $(CFLAGS_PERSONAL) $(CADD) -o $@ $<
+
+# The "merge try list" is a file full of TRY macro invocations, one for
+# each Netpbm program in this directory or any subdirectory that can be
+# invoked via the merged Netpbm program.  You will find it #included in
+# netpbm.c.
+
+mergetrylist: $(SUBDIRS:%=%/mergetrylist) 
+	cat /dev/null $(SUBDIRS:%=%/mergetrylist) >$@
+	$(SRCDIR)/buildtools/make_merge.sh $(MERGEBINARIES) >>$@
+
+# The "merge list" is a list of all the object files from this directory and
+# any subdirectories that have to be linked into the merged Netpbm program.
+# They are absolute paths.
+
+mergelist: $(SUBDIRS:%=%/mergelist) $(MERGE_OBJECTS)
+	cat /dev/null $(SUBDIRS:%=%/mergelist) >$@
+	echo $(MERGE_OBJECTS:%=$(CURDIR)/%) >>$@
+
+# merge.o is the object file that contains all the code in this directory
+# that needs to be linked into the merged Netpbm program.  This is not used
+# today, but some day it will be used instead of mergelist (above).
+
+ifeq ($(MERGE_OBJECTS),)
+  MERGE_O_OBJECTS = empty.o
+else
+  MERGE_O_OBJECTS = $(MERGE_OBJECTS)
+endif
+
+merge.o: $(SUBDIRS:%=%/merge.o) $(MERGE_O_OBJECTS)
+	$(LDRELOC) -o $@ $^
+
+# empty.o is useful in doing a merge build.  Every directory must be able to
+# produce a merge.o file, but not every directory has anything to contribute
+# to the merge.
+empty.o: %.o: %.c
+	$(CC) -c $(CFLAGS_PERSONAL) $(CADD) $< -o $@
+empty.c:
+	cat /dev/null >empty.c
+
+###########################################################################
+# PACKAGING / INSTALLING
+###########################################################################
+
+# Some maintenance notes about $(INSTALL): Some install programs can
+# install multiple files in one shot; others can take only one file at
+# a time.  Some have a -c option; others ignore -c.  Some can take
+# permissions in mnemonic form (u=rwx,go=rx); others can't, but all
+# take the encoded form (755).  Some have a -d option to install
+# directories and never install them implicitly.  Others create
+# directories only implicitly.  Installbsd and OSF1 Install need a
+# space in "-m 755".  Others don't care.  2000.05.17.  OSF1 Install
+# takes only one parameter: the source file.  It picks a destination
+# directory by default, or you can specify it with a -f option.
+# 2000.06.15
+
+# DJGPP can do SYMKINKs for programs but not for ordinary files, so
+# it define SYMLINKEXE, other system don't need it
+ifeq ($(SYMLINKEXE)x,x)
+  SYMLINKEXE := $(SYMLINK)
+endif
+
+$(PKGDIR)/%:
+	$(SRCDIR)/buildtools/mkinstalldirs $@
+
+.PHONY: install.merge
+install.merge: $(NOMERGEBINARIES:%=%_installbin) $(SCRIPTS:%=%_installscript) \
+	$(MERGEBINARIES:%=%_installmerge) $(SUBDIRS:%=%/install.merge)
+
+%_installmerge: $(PKGDIR)/bin
+	cd $(PKGDIR)/bin ; rm -f $(@:%_installmerge=%)
+	cd $(PKGDIR)/bin ; $(SYMLINKEXE) netpbm$(EXE) $(@:%_installmerge=%)
+
+.PHONY: install.bin
+install.bin: $(BINARIES:%=%_installbin) $(SCRIPTS:%=%_installscript) \
+	$(SUBDIRS:%=%/install.bin)
+# Note that on Cygwin, the executables are actually pbmmake.exe, etc.
+# Make and Install know that pbmmake.exe counts as pbmmake.
+
+INSTALLBIN_TARGETS = $(BINARIES:%=%_installbin) netpbm_installbin
+.PHONY: $(INSTALLBIN_TARGETS)
+$(INSTALLBIN_TARGETS): $(PKGDIR)/bin
+	$(INSTALL) -c $(STRIPFLAG) -m $(INSTALL_PERM_BIN) \
+	  $(@:%_installbin=%) $<
+
+$(SCRIPTS:%=%_installscript): $(PKGDIR)/bin
+	$(INSTALL) -c -m $(INSTALL_PERM_BIN) \
+	  $(SRCDIR)/$(SUBDIR)/$(@:%_installscript=%) $<
+
+.PHONY: install.data
+install.data: $(DATAFILES:%=%_installdata) $(SUBDIRS:%=%/install.data)
+
+.PHONY: $(DATAFILES:%=%_installdata) 
+$(DATAFILES:%=%_installdata): $(PKGDIR)/misc
+	$(INSTALL) -c -m $(INSTALL_PERM_DATA) \
+	  $(SRCDIR)/$(SUBDIR)/$(@:%_installdata=%) $<
+
+
+.PHONY: install.man install.man1 install.man3 install.man5
+install.man: install.man1 install.man3 install.man5 \
+	$(SUBDIRS:%=%/install.man)
+
+MANUALS1 = $(BINARIES) $(SCRIPTS)
+
+PKGMANDIR = man
+
+install.man1: $(PKGDIR)/$(PKGMANDIR)/man1 $(MANUALS1:%=%_installman1)
+
+install.man3: $(PKGDIR)/$(PKGMANDIR)/man3 $(MANUALS3:%=%_installman3)
+
+install.man5: $(PKGDIR)/$(PKGMANDIR)/man5 $(MANUALS5:%=%_installman5)
+
+%_installman1: $(PKGDIR)/$(PKGMANDIR)/man1
+	perl -w $(SRCDIR)/buildtools/makepointerman $(@:%_installman1=%) \
+          $(NETPBM_DOCURL) $< 1 $(MANPAGE_FORMAT) $(INSTALL_PERM_MAN)
+
+%_installman3: $(PKGDIR)/$(PKGMANDIR)/man3
+	perl -w $(SRCDIR)/buildtools/makepointerman $(@:%_installman3=%) \
+          $(NETPBM_DOCURL) $< 3 $(MANPAGE_FORMAT) $(INSTALL_PERM_MAN)
+
+%_installman5: $(PKGDIR)/$(PKGMANDIR)/man5
+	perl -w $(SRCDIR)/buildtools/makepointerman $(@:%_installman5=%) \
+          $(NETPBM_DOCURL) $< 5 $(MANPAGE_FORMAT) $(INSTALL_PERM_MAN)
+
+.PHONY: clean
+
+ifneq ($(EXE)x,x)
+EXEPATTERN = *$(EXE)
+else
+EXEPATTERN = 
+endif
+clean: $(SUBDIRS:%=%/clean) thisdirclean-common
+
+.PHONY: thisdirclean-common
+thisdirclean-common:
+	-rm -f *.o *.o2 *.a *.so *.so.* *.dll *.dylib *.cat *~ *.i *.s \
+	  $(EXEPATTERN) *.def *.lnk \
+	  core *.core mergelist mergetrylist *.c1 empty.c \
+	  $(BINARIES) pm_types.h
+	-rm -rf importinc
+
+.PHONY: distclean
+distclean: $(SUBDIRS:%=%/distclean) thisdirclean-common
+	rm -f Makefile.depend
+
+DEP_SOURCES = $(wildcard *.c *.cpp *.cc)
+
+.PHONY: dep
+dep: $(SUBDIRS:%=%/dep) importinc
+# We use -MG here because of compile.h and version.h.  They need not exist
+# before the first make after a clean.
+
+ifneq ($(DEP_SOURCES)x,x)
+	$(CC) -MM -MG $(INCLUDES2) $(DEP_SOURCES) >Makefile.depend
+endif
+
+# Note: if I stack all these subdirectory targets into one rule, I get
+# weird behavior where e.g. make install-nonmerge causes all the
+# %/install.bin makes to happen recursively, but then lib/install.lib
+# is considered up to date and doesn't get rebuilt.
+%/install.bin:
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+%/install.lib:
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+%/install.man:
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+%/install.data:
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+%/install.merge:
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+$(SUBDIRS:%=%/all): %/all: $(CURDIR)/%
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+$(SUBDIRS:%=%/mergetrylist): %/mergetrylist: $(CURDIR)/% FORCE
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+$(SUBDIRS:%=%/mergelist): %/mergelist: $(CURDIR)/% FORCE
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+$(SUBDIRS:%=%/merge.o): %/merge.o: $(CURDIR)/% FORCE
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+$(SUBDIRS:%=%/clean): %/clean: $(CURDIR)/%
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+$(SUBDIRS:%=%/distclean): %/distclean: $(CURDIR)/%
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+$(SUBDIRS:%=%/dep): %/dep: $(CURDIR)/%
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+
+#Here is the rule to create the subdirectories.  If you're building in the
+#source tree, they already exist, but in a separate build directory, they may
+#not.
+
+ifneq ($(SUBDIR)x,x)
+# This hack stops us from having a warning due to the same target twice
+# when we're in the top level directory (because buildtools, etc are in
+# SUBDIRS).
+  DIRS2 = $(BUILDDIR)/buildtools $(BUILDDIR)/lib $(BUILDDIR)/urt
+endif
+
+$(SUBDIRS:%=$(CURDIR)/%) $(DIRS2):
+	mkdir $@
+
+
+# The automatic dependency generation is a pain in the butt and
+# totally unnecessary for people just installing the distributed code,
+# so to avoid needless failures in the field and a complex build, the
+# rule to generate Makefile.depend automatically simply creates an
+# empty file.  A developer may do 'make dep' to create a
+# Makefile.depend full of real dependencies.
+
+Makefile.depend:
+	cat /dev/null >Makefile.depend
+
+include Makefile.depend
+
+FORCE:
diff --git a/Makefile.config.in b/Makefile.config.in
new file mode 100644
index 00000000..84666cb1
--- /dev/null
+++ b/Makefile.config.in
@@ -0,0 +1,624 @@
+# This is a make file inclusion, to be included in all the Netpbm make
+# files.
+
+# This file is meant to contain variable settings that customize the
+# build for a particular target system configuration.
+
+# The distribution contains the file Makefile.config.in.  You edit
+# Makefile.config.in in ways relevant to your particular environment 
+# to create Makefile.config.  The "configure" program will do this
+# for you in simple cases.
+
+# Some of the variables that the including make file must set for this
+# file to work:
+#
+#  SRCDIR: The directory at the top of the Netpbm source tree.  Note that
+#  this is typically a relative directory, and it must be relative to the
+#  make file that includes this file.
+
+DEFAULT_TARGET = nonmerge
+#DEFAULT_TARGET = merge
+
+# Fiasco has some special requirements that make it fail to compile on
+# some systems, and since it isn't very important, just set this to "N"
+# and skip it on those systems unless you want to debug it and fix it.
+# OpenBSD:
+#BUILD_FIASCO = N
+BUILD_FIASCO = Y
+
+# The following are commands for the build process to use.  These values
+# do not get built into anything.
+
+# The C compiler (including macro preprocessor)
+#CC = gcc
+# Note that 'cc' is usually an alias for whatever is the main compiler
+# on a system, e.g. the GNU Compiler on Linux.
+CC = cc
+
+# The linker.
+LD = $(CC)
+#LD = ld
+#Tru64:
+#LD = cc
+#LD = gcc 
+
+#If the linker identified above is a compiler that invokes a linker
+#(as in 'cc foo.o -o foo'), set LINKERISCOMPILER.  The main difference is
+#that we expect a compiler to take linker options in the '-Wl,-opt1,val1'
+#syntax whereas the actual linker would take '-opt1 val1'.
+LINKERISCOMPILER=Y
+#If $(LD) is 'ld':
+#LINKERISCOMPILER=N
+
+#LINKER_CAN_DO_EXPLICIT_LIBRARY means the linker specified above can
+#take a library as just another link object argument, as in 'ld
+#pnmtojpeg.o /usr/local/lib/libjpeg.so ...'  as opposed to requiring a
+#-l option as in 'ld pnmtojpeg.o -L/usr/local/lib -l jpeg'.
+#This variable controls how 'libopt' gets built.  Note that with some
+#linkers, you can specify a shared library explicitly, but then it has
+#to live in that exact place at run time.  That's not good enough for us.
+
+LINKER_CAN_DO_EXPLICIT_LIBRARY=N
+#GNU:
+#LINKER_CAN_DO_EXPLICIT_LIBRARY=Y
+
+# This is the name of the header file that declares the types
+# uint32_t, etc.  This name is used as #include $(INTTYPES_H)  .
+# Set to null if the types come automatically without including anything.
+
+# We have a report (2005.09.17) that on IRIX 5.3 with the native IDO
+# cc, inttypes.h and sys/types.h conflict (and Netpbm programs include
+# sys/types for other things), so for that environment, <inttypes.h>
+# won't work, but "inttypes_netpbm.h" might.
+
+INTTYPES_H = <inttypes.h>
+# Linux libc5:
+#INTTYPES_H = <types.h>
+# Solaris:
+# Solaris has <sys/inttypes.h>, but it doesn't define int_fast2_t, etc.
+#INTTYPES_H = "inttypes_netpbm.h"
+# Others:
+#INTTYPES_H = <sys/stdint.h>
+#INTTYPES_H = <sys/types.h>
+# The automatically generated Netpbm version:
+#INTTYPES_H = "inttypes_netpbm.h"
+
+# HAVE_INT64 tells whether, assuming you include the header indicated by
+# INTTYPES_H, you have the int64_t type and related stuff.  (If you don't
+# the build will omit certain code that does 64 bit computations).
+HAVE_INT64 = Y
+#HAVE_INT64 = N
+
+# CC and LD are for building the Netpbm programs, which are not necessarily
+# intended to run on the same system on which Make is running.  But when we 
+# build a build tool such as Libopt, it is meant to run only on the same 
+# system on which the Make is running.  The variables below define programs
+# to use to compile and link build tools.
+CC_FOR_BUILD = $(CC)
+LD_FOR_BUILD = $(LD)
+CFLAGS_FOR_BUILD = $(CFLAGS)
+
+# MAKE is set automatically by Make to what was used to invoke Make.
+
+INSTALL = $(SRCDIR)/buildtools/install.sh
+#Solaris:
+#INSTALL = /usr/ucb/install
+#Tru64:
+#INSTALL = installbsd
+#OSF1:
+#INSTALL = $(SRCDIR)/buildtools/installosf
+#Red Hat Linux:
+#INSTALL = install
+
+# STRIPFLAG is the option you pass to the above install program to make it
+# strip unnecessary information out of binaries.
+STRIPFLAG = -s
+# If you don't want to strip the binaries, just leave it null:
+#STRIPFLAG = 
+
+SYMLINK = ln -s
+# At least some Windows environments don't have any concept of symbolic
+# links, but direct copies are usually a passable alternative.
+#SYMLINK = cp
+
+#MANPAGE_FORMAT is "nroff" or "cat".  It determines in what format the
+#pointer man pages are installed (ready to nroff, or ready to cat).  
+#A pointer man pages is just a single-paragraph pages that tells you there is
+#no man page for the program, to look at the HTML documentation instead.
+MANPAGE_FORMAT = nroff
+#MANPAGE_FORMAT = cat
+
+AR = ar
+RANLIB = ranlib
+# IRIX, SCO don't have Ranlib:
+#RANLIB = true
+
+# LEX is the beginning of a shell command that runs a Lex-like
+# pattern matcher generator.  Null string means there isn't any such
+# command.  That means the build will skip parts that need one.
+
+LEX = flex
+# Solaris:
+# LEX = flex -e
+# Windows Mingw:
+# LEX =
+# 
+# LEX = lex
+
+# C compiler options 
+
+# gcc:
+# -ansi and -Werror should work too, but are not included
+# by default because there's no point in daring the build to fail.
+# -pedantic isn't a problem because it causes at worst a warning.
+#CFLAGS = -O3 -ffast-math -pedantic -fno-common \
+#          -Wall -Wno-uninitialized -Wmissing-declarations -Wimplicit \
+#          -Wwrite-string -Wmissing-prototypes -Wundef
+# The merged programs have a main_XXX subroutine instead of main(),
+# which would cause a warning with -Wmissing-declarations or 
+# -Wmissing-prototypes.
+#CFLAGS_MERGE = -Wno-missing-declarations -Wno-missing-prototypes
+# A user of DEC Tru64 4.0F in May 2000 needed -DLONG_32 for ppmtompeg,
+# but word size-sensitive code was removed from parallel.c in September 2004.
+# A user of Tru64 5.1A in July 2003 needed NOT to have -DLONG_32.  In
+# theory, you need this if on your system, long is 32 bits and int is not.
+# But it may be completely irrelevant today.
+#Tru64:
+#CFLAGS = -O2 -std1 -DLONG_32
+#CFLAGS = -O2 -std1
+#AIX:
+#CFLAGS= -O3
+#HP-UX:
+#CFLAGS= -O3 -fPIC
+#IRIX:
+#CFLAGS= -n32 -O3
+#Amiga with GNU compiler:
+#CFLAGS= -m68020-60 -ffast-math -mstackextend 
+# You can add -noixemul for Amiga and successfully compile most of the 
+# programs.  (Of the remaining ones, if you can supply your own strtod() 
+# function, most of them will build with -noixemul).  So try building 
+# with 'make --keep-going CADD=-noixemul' first, then just 'make' to build
+# everything that failed for lack of the ixemul library in the first step.
+# That way, the parts that don't required the ixemul library won't indicate
+# a dependency on it.
+#OpenBSD:
+#CFLAGS = -I/usr/local/include
+
+# EXE is a suffix that the linker puts on any executable it generates.
+# In cygwin, this is .exe and most programs deal with its existence without
+# us having to know about it.  Some don't though, so set this:
+
+EXE =
+#Cygwin, DJGPP/Windows:
+#EXE = .exe
+  
+# linker options.  
+
+# LDFLAGS is often set as an environment variable;  A setting here overrides
+# it.  So either make sure you want to override it, or do a "LDFLAGS +=" here.
+
+# LDFLAGS is usually not the right place for a -L option, because we put
+# LDFLAGS _before_ our own -L options, so it would cancel out our
+# specific selection of libraries.  For example, if you say
+# LDFLAGS=/usr/local/lib and an old copy of the libnetpbm is in
+# /usr/local/lib, then you'd be linking against that old copy instead of
+# the copy you just built, which is located by a -L option later on the
+# link command.  LIBS is the right variable for adding -L options.  LIBS
+# goes after any of our make files' own -L options.
+
+# Eunice users may want to use -noshare so that the executables can
+# run standalone:
+#LDFLAGS += -noshare
+#Tru64:
+# Russ Allberry says on 2001.06.09 that -oldstyle_liblookup may be necessary
+# to keep from finding an ancient system libjpeg.so that isn't compatible with
+# NetPBM.  Michael Long found that /usr/local/lib is not in the default
+# search path, or not soon enough, and he was getting an old libjpeg that
+# caused all the jpeg symbol references to be unresolved.  He had installed
+# a new libjpeg in /usr/local/lib.
+#LDFLAGS += -call_shared -oldstyle_liblookup -L/usr/local/lib
+#AIX:
+#LDFLAGS += -L /usr/pubsw/lib
+#HP-UX:
+#LDFLAGS += -Wl,+b,/usr/pubsw/lib
+#IRIX:
+#LDFLAGS += -n32
+
+# Linker options for created Netpbm shared libraries.
+
+# Here, $(SONAME) resolves to the soname for the shared library being created.
+# The following are gcc options.  This works on GNU libc systems.
+LDSHLIB = -shared -Wl,-soname,$(SONAME)
+# You need -nostart instead of -shared on BeOS.  Though the BeOS compiler is
+# ostensibly gcc, it has the -nostart option, which is not mentioned in gcc
+# documentation and doesn't exist in at least one non-BeOS installation.
+# BeOS doesn't have sonames built in.
+#LDSHLIB = -nostart
+#LDSHLIB = -G
+# Solaris, SunOS with GNU Ld, SCO:
+# These systems have no soname option.
+#LDSHLIB = -shared
+# Solaris with Sun Ld:
+#LDSHLIB = -Wl,-Bdynamic,-G,-h,$(SONAME) 
+#Tru64:
+#LDSHLIB = -shared -expect_unresolved "*"
+#IRIX:
+#LDSHLIB = -shared -n32
+#AIX GNU compiler/linker:
+#LDSHLIB = -shared
+#AIX Visual Age C:
+#LDSHLIB = -qmkshrobj
+
+# LDRELOC is the command to combine two .o files (relocateable object files)
+# into a single .o file that can later be linked into something else.  NONE
+# means no such command is available.
+
+LDRELOC = NONE
+# GNU Ld:
+# Older GNU Ld misspells the option as --relocateable.  Newer GNU Ld
+# correctly spells it --relocatable.  The abbreviation --reloc works on
+# both.
+#LDRELOC = ld --reloc
+#LDRELOC = ld -r
+
+
+# On older systems, you have to make shared libraries out of position
+# independent code, so you need -fpic or fPIC here.  (The rule is: if
+# -fpic works, use it.  If it bombs, go to fPIC).  On newer systems,
+# it isn't necessary, but can save real memory at the expense of
+# execution speed.  Without position independent code, the library
+# loader may have to patch addresses into the executable text.  On an
+# older system, this would cause a program crash because the loader
+# would be writing into read-only shared memory.  But on newer
+# systems, the system silently creates a private mapping of the page
+# or segment being modified (the "copy on write" phenomenon).  So it
+# needs its own private real page frame.  In one experiment, A second
+# copy of Pbmtext used 16K less real memory when built with -fpic than
+# when built without.  2001.06.02.
+
+# We have seen -fPIC required on IA64 and AMD64 machines (GNU
+# compiler/linker).  Build-time linking fails without it.  I don't
+# know why -- history seems to be repeating itself.  2005.02.23.
+
+CFLAGS_SHLIB = 
+# Solaris or SunOS with gcc, and NetBSD:
+#CFLAGS_SHLIB = -fpic
+#CFLAGS_SHLIB = -fPIC
+# Sun compiler:
+#CFLAGS_SHLIB = -Kpic
+#CFLAGS_SHLIB = -KPIC
+
+# SHLIB_CLIB is the link option to include the C library in a shared library,
+# normally "-lc".  On typical systems, this serves no purpose.  On some,
+# though, it causes information about which C library to use to be recorded
+# in the shared library and thus choose the correct library among several or
+# avoid using an incompatible one.  But on some systems, the link fails.
+# On 2002.09.30, "John H. DuBois III" <spcecdt@armory.com> reports that on 
+# SCO OpenServer, he gets the following error message with -lc:
+#
+#  -lc; relocations referenced  ;  from file(s) /usr/ccs/lib/libc.so(random.o);
+#   fatal error: relocations remain against allocatable but non-writable 
+#   section: ; .text
+
+SHLIB_CLIB = -lc
+# SCO:
+SHLIB_CLIB =
+
+# On some systems you have to build into an executable the list of
+# directories where its dynamically linked libraries can be found at
+# run time.  This is typically done with a -R or -rpath linker
+# option.  Even on systems that don't require it, you might prefer to do
+# that rather than set up environment variables or configuration files
+# to tell the system where the libraries are.  A "Y" here means to put
+# the directory information in the executable at link time.
+
+NEED_RUNTIME_PATH = N
+# Solaris, SunOS, NetBSD, AIX:
+#NEED_RUNTIME_PATH = Y
+
+# RPATHOPTNAME is the option you use on the link command to specify
+# a runtime search path for a shared library.  It is meaningless unless
+# NEED_RUNTIME_PATH is Y.
+RPATHOPTNAME = -rpath
+
+# The following variables tell where your various libraries on which
+# Netpbm depends live.  The LIBxxx variable is a full file
+# specification of the link library (not necessarily the library used
+# at run time).  e.g. "/usr/local/lib/graphics/libjpeg.so".  It usually
+# doesn't matter if the library prefix and suffix are right -- you can
+# use "lib" and ".so" or ".a" regardless of what your system actually
+# uses because these just turn into "-L" and "-l" linker options
+# anyway.  ".a" implies a static library for some purposes, though.
+# If you don't have the library in question, use a value of NONE for
+# LIBxxx and the build will simply skip the programs that require that
+# library.  If the library is in your linker's (or the Netpbm build's)
+# default search path, leave off the directory part, e.g. "libjpeg.so".
+
+# The xxxHDR_DIR variable is the directory in which the interface
+# headers for the library live (e.g. /usr/include).  If they are in your
+# compiler's default search path, set this variable to null.
+
+# This is where the Netpbm shared libraries will reside when Netpbm is
+# fully installed.  In some configurations, the Netpbm builder builds
+# this information into the Netpbm executables.  This does NOT affect
+# where the Netpbm installer installs the libraries.  A null value
+# means the libraries are in a default search path used by the runtime
+# library loader.
+NETPBMLIB_RUNTIME_PATH = 
+#NETPBMLIB_RUNTIME_PATH = /usr/lib/netpbm
+
+# The TIFF library.  See above.  If you want to build the tiff
+# converters, you must have the tiff library already installed.
+
+TIFFLIB = NONE
+TIFFHDR_DIR =
+
+#TIFFLIB = libtiff.so
+#TIFFHDR_DIR = /usr/include/libtiff
+#NetBSD:
+#TIFFLIB = $(LOCALBASE)/lib/libtiff.so
+#TIFFHDR_DIR = $(LOCALBASE)/include
+# OSF, Tru64:
+#TIFFLIB = /usr/local1/DEC/lib/libtiff.so
+#TIFFHDR_DIR = /usr/local1/DEC/include
+
+# Some TIFF libraries do Jpeg and/or Z (flate) compression and thus any
+# program linked with the TIFF library needs a Jpeg and/or Z library.
+# Some TIFF libraries have such library statically linked in, but others
+# need it to be dynamically linked at program load time.
+# Make this 'N' if youf TIFF library doesn't need such dynamic linking.
+# As of 2005.01, the most usual build of the TIFF library appears to require
+# both.
+TIFFLIB_NEEDS_JPEG = Y
+TIFFLIB_NEEDS_Z = Y
+
+# The JPEG library.  See above.  If you want to build the jpeg
+# converters you must have the jpeg library already installed.
+
+# Tiff files can use JPEG compression, so the Tiff library can reference
+# the JPEG library.  If your Tiff library references a dynamic JPEG 
+# library, you must specify at least JPEGLIB here, or the Tiff
+# converters will not build.  Note that your Tiff library may have the
+# JPEG stuff statically linked in, in which case you won't need 
+# JPEGLIB in order to build the Tiff converters.
+
+JPEGLIB = NONE
+JPEGHDR_DIR =
+#JPEGLIB = libjpeg.so
+#JPEGHDR_DIR = /usr/include/jpeg
+# Netbsd:
+#JPEGLIB = ${LOCALBASE}/lib/libjpeg.so
+#JPEGHDR_DIR = ${LOCALBASE}/include
+# OSF, Tru64:
+#JPEGLIB = /usr/local1/DEC/libjpeg.so
+#JPEGHDR_DIR = /usr/local1/DEC/include
+# Typical:
+#JPEGLIB = /usr/local/lib/libjpeg.so
+#JPEGHDR_DIR = /usr/local/include
+# Don't build JPEG stuff:
+#JPEGLIB = NONE
+
+
+# The PNG library.  See above.  If you want to build the PNG
+# converters you must have the PNG library already installed.
+
+# The PNG library, by convention starting around April 2002, gets installed
+# with names that include a version number, such as libpng10.a and header
+# files in /usr/include/libpng10.  But there is conventionally an unnumbered
+# alias (e.g. libpng.a, /usr/include/libpng) for the preferred version.
+#
+# Recent versions of the library (since some time in the 2002-2006 period)
+# have an associated 'libpng-config' that tells how to link it.  The make
+# files will use that program if it exists (must be in the PATH).  In that
+# case, PNGLIB and PNGHDR_DIR are irrelevant, but PNGVER is still meaningful,
+# because the make file runs 'libpng$(PNGVER)-config'.
+
+PNGLIB = NONE
+PNGHDR_DIR =
+PNGVER = 
+#PNGLIB = libpng$(PNGVER).so
+#PNGHDR_DIR = /usr/include/libpng$(PNGVER)
+# NetBSD:
+#PNGLIB = $(LOCALBASE)/lib/libpng$(PNGVER).so
+#PNGHDR_DIR = $(LOCALBASE)/include
+# OSF/Tru64:
+#PNGLIB = /usr/local1/DEC/lib/libpng$(PNGVER).so
+#PNGHDR_DIR = /usr/local1/DEC/include
+
+# The zlib compression library.  See above.  You need it to build
+# anything that needs the PNG library (see above).  If you selected
+# NONE for the PNG library, it doesn't matter what you specify here --
+# it won't get used.
+#
+# If you have 'libpng-config' (see above), these are irrelevant.
+
+ZLIB = NONE
+ZHDR_DIR = 
+#ZLIB = libz.so
+
+# The JBIG lossless image compression library (aka JBIG-KIT):
+
+JBIGLIB = $(BUILDDIR)/converter/other/jbig/libjbig.a
+JBIGHDR_DIR = $(SRCDIR)/converter/other/jbig
+
+# The Jasper JPEG-2000 image compression library (aka JasPer):
+JASPERLIB = $(INTERNAL_JASPERLIB)
+JASPERHDR_DIR = $(INTERNAL_JASPERHDR_DIR)
+# JASPERDEPLIBS is the libraries (-l options or file names) on which
+# The Jasper library depends -- i.e. what you have to link into any
+# executable that links in the Jasper library.
+JASPERDEPLIBS =
+#JASPERDEPLIBS = -ljpeg
+
+# And the Utah Raster Toolkit (aka URT aka RLE) library:
+
+URTLIB = $(BUILDDIR)/urt/librle.a
+URTHDR_DIR = $(SRCDIR)/urt
+
+# The X11 library has facilities for talking to an X Window System
+# server.  It is required by Pamx.
+
+X11LIB = NONE
+X11HDR_DIR =
+
+#X11LIB = /usr/lib/libX11.so
+#X11HDR_DIR =
+
+# The Linux SVGA library (Svgalib) is a facility for displaying graphics
+# on the Linux console.  It is required by Ppmsvgalib.
+
+LINUXSVGALIB = NONE
+LINUXSVGAHDR_DIR = 
+
+#LINUXSVGALIB = /usr/lib/libvga.so
+#LINUXSVGAHDR_DIR = /usr/include/vgalib
+
+# If you don't want any network functions, set OMIT_NETWORK to "y".
+# The only thing that requires network functions is the option in
+# ppmtompeg to run it on multiple computers simultaneously.  On some
+# systems network functions don't work or we haven't figured out how to 
+# make them work, or they just aren't worth the effort.  
+OMIT_NETWORK =
+#DJGPP/Windows, Tru64:
+#   (there's some minor header problem that prevents network functions from 
+#   building on Tru64 2000.10.06)
+#OMIT_NETWORK = y
+
+# These are -l options to link in the network libraries.  Often, these are
+# built into the standard C library, so this can be null.  This is irrelevant
+# if OMIT_NETWORK is "y".
+
+NETWORKLD = 
+# Solaris, SunOS:
+#NETWORKLD = -lsocket -lnsl
+# SCO:
+#NETWORKLD = -lsocket, -lresolv
+
+VMS = 
+#VMS:
+#VMS = yes
+
+# DONT_HAVE_PROCESS_MGMT is Y if this system doesn't have the usual
+# Unix process management stuff - fork, wait, etc.  N for a regular Unix
+# system.
+DONT_HAVE_PROCESS_MGMT = N
+
+# The following variables are used only by 'make install' (and the
+# variants of it).  Paths here don't, for example, get built into any
+# programs.
+
+# This is where everything goes when you do 'make package', unless you
+# override it by setting 'pkgdir' on the Make command line.
+PKGDIR_DEFAULT = /tmp/netpbm
+
+# Subdirectory of the package directory ($(pkgdir)) in which man pages
+# go.
+PKGMANDIR = man
+
+# File permissions for installed files.
+# Note that on some systems (e.g. Solaris), 'install' can't use the 
+# mnemonic permissions - you have to use octal.
+
+# binaries (pbmmake, etc)
+INSTALL_PERM_BIN =  755       # u=rwx,go=rx
+# shared libraries (libpbm.so, etc)
+INSTALL_PERM_LIBD = 755       # u=rwx,go=rx
+# static libraries (libpbm.a, etc)
+INSTALL_PERM_LIBS = 644       # u=rw,go=r
+# header files (pbm.h, etc)
+INSTALL_PERM_HDR =  644       # u=rw,go=r
+# man pages (pbmmake.1, etc)
+INSTALL_PERM_MAN =  644       # u=rw,go=r
+# data files (pnmtopalm color maps, etc)
+INSTALL_PERM_DATA = 644       # u=rw,go=r
+
+# Specify the suffix that want the man pages to have.
+
+SUFFIXMANUALS1 = 1
+SUFFIXMANUALS3 = 3
+SUFFIXMANUALS5 = 5
+
+#NETPBMLIBTYPE tells the kind of libraries that will get built to hold the
+#Netpbm library functions.  The value is used only in make file tests.
+# "unixshared" means a unix-style shared library, typically named like 
+# libxyz.so.2.3
+NETPBMLIBTYPE = unixshared
+# "unixstatic" means a unix-style static library, (like libxyz.a)
+#NETPBMLIBTYPE = unixstatic
+# "dll" means a Windows DLL shared library
+#NETPBMLIBTYPE = dll
+# "dylib" means a Darwin/Mac OS shared library
+#NETPBMLIBTYPE = dylib
+
+#NETPBMLIBSUFFIX is the suffix used on whatever kind of library is 
+#selected above.  All this is used for is to construct library names.
+#The make files never examine the actual value.
+NETPBMLIBSUFFIX = so
+
+# "a" is the suffix for unix-style static libraries.  It is also
+# traditionally used for shared libraries on AIX.  The Visual Age C
+# manual says sometimes .so works on AIX, and GNU software for AIX
+# 5.1.0 does indeed use it.  In our experiments, it works fine if you
+# name the library file explicitly on the link, but isn't in the -l
+# search order.  If you name the library explicitly on the link, the
+# library must live in exactly the same position at run time, so we
+# can't use that.  Therefore, you cannot build both static and shared
+# libraries with AIX.  You have to choose.
+#NETPBMLIBSUFFIX = a
+# For HP-UX shared libraries:
+#NETPBMLIBSUFFIX = sl
+# Darwin/Mac OS shared library:
+#NETPBMLIBSUFFIX = dylib
+# Windows shared library:
+#NETPBMLIBSUFFIX = dll
+
+#STATICLIB_TOO is "y" to signify that you want a static library built
+#and installed in addition to whatever library type you specified by
+#NETPBMLIBTYPE.  If NETPBMLIBTYPE specified a static library,
+#STATICLIB_TOO simply has no effect.
+STATICLIB_TOO = y
+#STATICLIB_TOO = n
+
+#STATICLIBSUFFIX is the suffix that static libraries have.  It's
+#meaningless if you aren't building static libraries.
+STATICLIBSUFFIX = a
+
+#SHLIBPREFIXLIST is a blank-delimited list of prefixes that a filename
+#of a shared library may have on this system.  Traditionally, it's
+#just "lib", as in libc or libnetpbm.  On Windows, though, varying
+#prefixes are used when multiple alternative forms of a library are
+#available.  The first prefix in this list is what we use to name the
+#Netpbm shared libraries.
+#
+# This variable controls how 'libopt' gets built.
+#
+SHLIBPREFIXLIST = lib
+#Cygwin:
+#SHLIBPREFIXLIST = cyg lib
+
+NETPBMSHLIBPREFIX = $(firstword $(SHLIBPREFIXLIST))
+
+#DLLVER is used to version the DLLs built on cygwin or other
+#windowsish platforms.  We can't add this to LIBROOT, or we'd
+#version the static libs (which is bad).  We can't add this
+#at the end of the name (like unix does with so numbers) because
+#windows will only load dlls whose name ends in "dll".  So,
+#we have this variable, which becomes the end of the library "root" name
+#for DLLs only.
+#
+# This variable controls how 'libopt' gets built.
+#
+DLLVER =
+#Cygwin
+#DLLVER = $(NETPBM_MAJOR_RELEASE)
+
+#NETPBM_DOCURL is the URL of the main documentation page for Netpbm.
+#This is a directory which contains a file for each Netpbm program,
+#library, and file type.  E.g. The documentation for jpegtopnm might be in
+#http://netpbm.sourceforge.net/doc/jpegtopnm.html .  This value gets
+#installed in the man pages (which say no more than to read the webpage)
+#and in the Webman netpbm.url file.
+NETPBM_DOCURL = http://netpbm.sourceforge.net/doc/
+#For a system with no web access, but a local copy of the doc:
+#NETPBM_DOCURL = file:/usr/doc/netpbm/
+
diff --git a/Makefile.srcdir b/Makefile.srcdir
new file mode 100644
index 00000000..6a21d9db
--- /dev/null
+++ b/Makefile.srcdir
@@ -0,0 +1 @@
+SRCDIR = $(CURDIR)
diff --git a/README b/README
new file mode 100644
index 00000000..ece3a32d
--- /dev/null
+++ b/README
@@ -0,0 +1,213 @@
+
+                              NETPBM
+
+
+THIS IS THE PRIMARY DOCUMENTATION DISTRIBUTED WITH NETPBM.  SEE THE doc
+DIRECTORY IN THE SOURCE TREE FOR OTHER INFORMATION, SUCH AS INSTALLATION
+INSTRUCTIONS, AND SEE <http://netpbm.sourceforge.net>.
+
+Netpbm is a toolkit for manipulation of graphic images, including
+conversion of images between a variety of different formats.  There
+are over 300 separate tools in the package including converters for
+about 100 graphics formats.  Examples of the sort of image
+manipulation we're talking about are: Shrinking an image by 10%;
+Cutting the top half off of an image; Making a mirror image; Creating
+a sequence of images that fade from one image to another;
+
+For more information on what the package does, see
+<http://netpbm.sourceforge.net/doc>.
+
+The package is intended to be portable to many platforms. It has, at
+least at one time, been tested under various Unix-based systems,
+Windows, Mac OS X, VMS and Amiga OS.  The maintainer uses and builds
+it on a platform that consists (in relevant part) mainly of GNU
+software (you probably know this kind of system by the name "Linux").
+
+The goal of Netpbm is to be a single source for all the primitive
+graphics utilities, especially converters, one might need.  So if you
+know of some freely redistributable software in this vein which is not
+in the package yet, you should bring it to the attention of the Netpbm
+maintainer so it can be included in the next release.
+
+Netpbm does not contain interactive tools and doesn't have a graphical
+interface.
+
+Netpbm replaces the widely spread Pbmplus package (last released
+December 10, 1991).  A lot of improvements and additions have been
+made.  After the latest release of Pbmplus, a lot of additional
+filters began circulating on the net, which was a fairly novel state
+of affairs at the time.  The aim of Netpbm was to collect these and to
+turn them into a package, hence the name "Netpbm."  This work has been
+performed by programmers all over the world.  If _you_ have some code
+to add, please contact the Netpbm maintainer.
+
+
+USING NETPBM IN A WEBSITE
+-------------------------
+
+Many people use Netpbm to perform graphics functions in a web site.  They
+have CGI scripts that invoke Netpbm programs to process images for display
+on a web page.  Gallery and 4Images are two web site software packages
+that rely on Netpbm for graphics manipulation.
+
+Installing Netpbm requires different skills and system access than 
+installing most other web site software.  You must be able to compile
+C code for the web server machine and have a basic understanding of 
+how files are organized and programs run on the web server.  Diagnosing
+inevitable problems usually requires shell access to the web server.
+
+Netpbm is basic graphics software that ought to be supplied by any
+web hosting service.  If it isn't on your web server already, you should
+request that the system administrator add it.
+
+The Gallery project provides an easy install package for the parts of
+Netpbm that Gallery needs, and provides technical support at:
+
+    http://gallery.sourceforge.net/forums.php
+
+
+DISTRIBUTION
+------------
+
+You'll find the latest release of Netpbm source code at
+<http://prdownloads.sourceforge.net/netpbm/>.
+
+The user manual is not in the source code package.  It is available online
+at <http://netpbm.sourceforge.net/doc> and you can download it from there.
+See the file doc/USERDOC for details.
+
+A list of prebuilt Netpbm distributions is on the Netpbm
+website, <http://netpbm.sourceforge.net>.
+
+
+PREREQUISITES
+-------------
+
+Don't sweat the prerequisites too much.  In most cases, if you're
+missing something, the build of the programs that depend on it will
+bomb, but the rest of the Netpbm programs will build just fine.  And
+you may not need the more demanding programs.
+
+If you have trouble getting, building, or installing the
+prerequisites, the Netpbm maintainer wants to know.  Since he uses
+them himself, he can help you.  And if there is a problem with a
+prerequisite package that its own maintainer cannot fix, it may be
+possible to ship a fix with Netpbm.
+
+To build and install Netpbm, you need GNU Make and a Perl interpreter.
+You can get GNU Make from http://www.gnu.org/software and Perl from 
+http://www.cpan.org.  It's possible to get around the Perl requirement
+by running some of the steps on a different machine that has Perl and
+doing others manually.  There is no practical substitute for GNU Make.
+
+To build pnmtotiff or tifftopnm or pnmtotiffcmyk, you need the Tiff
+library.  You can get it from http://www.libtiff.org.
+
+To build ppmtojpeg or jpegtopnm, you need the JPEG/JFIF library from
+the Independent JPEG Group (IJG).  You can get it at
+ftp://ftp.uu.net/graphics/jpeg.  See http://www.ijg.org for more
+information.  You need Release 6b or better.  With earlier releases, Netpbm
+build fails with undefined jpeg symbols.  The basic JPEG library installation
+procedure installs only the runtime part of the package -- you nee the
+development part as well, so run 'make install-lib'.  The JPEG library 
+documentation erroneously calls this installing "the library itself."
+This apparently was written before shared libraries.  With shared libraries,
+"the library itself" is part of the runtime installation, but install-lib
+still installs the compile-time stuff you need.
+
+You may also need the JPEG library to build the Tiff converters.  If
+your Tiff library references a shared JPEG library, then you do.  The
+Tiff library may also include a static copy of the JPEG library, in
+which case you won't need a separate JPEG library.  Or it may have
+been built without any JPEG compression capability, in which case you
+won't need a separate JPEG library, but the Tiff converters won't be
+able to handle Tiff with JPEG compression.
+
+The same goes for Ppmtompeg.  You need the jpeg library if you want to
+create MPEGs from JPEGs (without the loss of quality that comes with
+converting from JPEG to PPM first), and if you don't have the JPEG
+library and don't say so in Makefile.config, you won't be able to
+build Ppmtompeg at all.
+
+To build or use Pnmtopng and Pngtopnm, you need the Zlib compression
+library and the PNG library (libpng).  You can get Zlib from
+ftp://quest.jpl.nasa.gov/pub/zlib or
+ftp://metalab.unc.edu/pub/Linux/libs.  You can get libpng from
+http://www.libpng.org/pub/png/libpng.html or
+http://libpng.sourceforge.net.  Older libpng won't work -- you get
+unresolved external references to png_sig_cmp and png_set_sig_bytes.
+
+You may also need the Zlib library to build the Tiff converters, in
+the same way as the Tiff converters require the JPEG libraries, as
+explained above.
+
+Pstopnm (the Postscript to PNM image converter) requires Ghostscript
+(installed with the name 'gs' in your command search path).  And it 
+requires in particular that Ghostscript be built with the relevant 
+PNM device drivers.  See http://www.ghostscript.com/doc/GPL/ .
+
+The Utah Raster Toolkit is not a prerequisite because Netpbm includes
+a subset of it that meets the needs of Pnmtorle and Rletopnm.
+However, you can also substitute the real package by properly
+configuring Makefile.config.  You can get it from 
+ftp://ftp.cs.utah.edu/pub/dept/OLD/pub/urt-3.1b.tar.Z.  There's a 
+patch at ftp://ptolemy.berkeley.edu/pub/misc/urt/urt-3.1b-3.1b1.patch
+
+You generally need a compiler other than Gcc 2.96.  Gcc 2.96 has a bug
+in its inlining optimization.  It generates incorrect code.  Netpbm
+source code takes advantage of inlining and you normally build Netpbm
+with inlining enabled.  Therefore, if you use Gcc 2.96 you will get
+broken Netpbm programs.  The usual symptom is bogus syntax error
+messages when you run the program.  You can avoid this compiler bug by
+using a -O0 compile option instead of the usual -O3.  The automatic
+configuration program will usually detect that you need this and set
+it up for you.  This will make some programs noticeably slower,
+though.
+
+Netpbm requires about 3.5 MiB of disk space, not including documentation.
+The documentation is 1.1 MiB, but you don't necessarily have to install
+it; you can just access the public copy.
+
+
+INSTALLATION
+------------
+
+See doc/INSTALL.
+
+
+SUPPORT
+-------
+
+The maintainer of Netpbm, since September 1999, is Bryan Henderson:
+
+  bryanh@giraffe-data.com.  
+
+If for some reason that email address doesn't work, you can reach
+Bryan at giraffedata@yahoo.com as a backup.  Please don't mail to 
+both at the same time.  Bryan doesn't need two copies of your email.
+
+Bryan actively maintains the package and wants to know about any bugs
+or problems people have with Netpbm or suggestions for improvement.
+
+There is no bug reporting database or mailing list.  These would not
+be very useful with Netpbm because Bryan personally responds to all
+bug reports and requests for help immediately.  All known bugs in the
+"latest" release are listed in its release notes on Sourceforge
+(updated as the bugs are reported) and the "stable" release is
+generally maintained so as not to have known bugs for more than a few
+days.  The doc/HISTORY file in the package may be useful if you want
+to find out whether upgrading to the current release would solve your
+problem.  The information in that file, on a per-release basis, is 
+also in the change histories on Sourceforge.
+
+
+
+MORE INFORMATION, DOCUMENTATION
+-------------------------------
+
+For more information about Netpbm, see <http://netpbm.sourceforge.net/doc>.
+
+The 'doc' directory in the source tree has more information.
+
+A good place to start for information about the wide world of computer
+graphics is http://www.faqs.org/faqs/graphics/  .
diff --git a/analyzer/Makefile b/analyzer/Makefile
new file mode 100644
index 00000000..6a447ea7
--- /dev/null
+++ b/analyzer/Makefile
@@ -0,0 +1,48 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = analyzer
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+# We tend to separate out the build targets so that we don't have
+# any more dependencies for a given target than it really needs.
+# That way, if there is a problem with a dependency, we can still
+# successfully build all the stuff that doesn't depend upon it.
+# This package is so big, it's useful even when some parts won't 
+# build.
+
+PORTBINARIES = pamfile pamslice pamsumm pamtilt \
+               pgmhist pnmhistmap ppmhist pgmminkowski
+MATHBINARIES = pamsharpmap pamsharpness pgmtexture pnmpsnr 
+
+BINARIES = $(PORTBINARIES) $(MATHBINARIES)
+SCRIPT =
+
+OBJECTS = $(BINARIES:%=%.o)
+
+# We don't include programs that have special library dependencies in the
+# merge scheme, because we don't want those dependencies to prevent us
+# from building all the other programs.
+
+MERGEBINARIES = $(BINARIES)
+MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2)
+
+.PHONY: all
+all: $(BINARIES)
+
+include $(SRCDIR)/Makefile.common
+
+install.bin: install.bin.local
+.PHONY: install.bin.local
+install.bin.local: $(PKGDIR)/bin
+# Remember that $(SYMLINK) might just be a copy command.
+	cd $(PKGDIR)/bin ; \
+	$(SYMLINK) pamslice$(EXE) pgmslice
+	cd $(PKGDIR)/bin ; \
+	$(SYMLINK) pamfile$(EXE) pnmfile
+
+FORCE:
+
diff --git a/analyzer/pamfile.c b/analyzer/pamfile.c
new file mode 100644
index 00000000..efb2cbee
--- /dev/null
+++ b/analyzer/pamfile.c
@@ -0,0 +1,243 @@
+/* pamfile.c - describe a portable anymap
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "pam.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    int inputFileCount;  /* Number of input files */
+    const char ** inputFilespec;  /* Filespecs of input files */
+    unsigned int allimages;  /* -allimages or -count */
+    unsigned int count;      /* -count */
+    unsigned int comments;   /* -comments */
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to as as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+    
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "allimages", OPT_FLAG,  NULL, &cmdlineP->allimages,   0);
+    OPTENT3(0,   "count",     OPT_FLAG,  NULL, &cmdlineP->count,       0);
+    OPTENT3(0,   "comments",  OPT_FLAG,  NULL, &cmdlineP->comments,    0);
+
+    opt.opt_table     = option_def;
+    opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */
+    opt.allowNegNum   = FALSE; /* We have no parms that are negative numbers */
+    
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others */
+
+    cmdlineP->inputFilespec = (const char **)&argv[1];
+    cmdlineP->inputFileCount = argc - 1;
+}
+
+
+
+static void
+dumpHeader(struct pam const pam) {
+
+    switch (pam.format) {
+    case PAM_FORMAT:
+        printf("PAM, %d by %d by %d maxval %ld\n", 
+               pam.width, pam.height, pam.depth, pam.maxval);
+        printf("    Tuple type: %s\n", pam.tuple_type);
+        break;
+
+	case PBM_FORMAT:
+        printf("PBM plain, %d by %d\n", pam.width, pam.height );
+        break;
+
+	case RPBM_FORMAT:
+        printf("PBM raw, %d by %d\n", pam.width, pam.height);
+        break;
+
+	case PGM_FORMAT:
+        printf("PGM plain, %d by %d  maxval %ld\n", 
+               pam.width, pam.height, pam.maxval);
+        break;
+
+	case RPGM_FORMAT:
+        printf("PGM raw, %d by %d  maxval %ld\n", 
+               pam.width, pam.height, pam.maxval);
+        break;
+
+	case PPM_FORMAT:
+        printf("PPM plain, %d by %d  maxval %ld\n", 
+               pam.width, pam.height, pam.maxval);
+        break;
+
+	case RPPM_FORMAT:
+        printf("PPM raw, %d by %d  maxval %ld\n", 
+               pam.width, pam.height, pam.maxval);
+        break;
+    }
+}
+
+
+
+static void
+dumpComments(const char * const comments) {
+
+    const char * p;
+    bool startOfLine;
+    
+    printf("Comments:\n");
+
+    for (p = &comments[0], startOfLine = TRUE; *p; ++p) {
+        if (startOfLine)
+            printf("  #");
+        
+        fputc(*p, stdout);
+        
+        if (*p == '\n')
+            startOfLine = TRUE;
+        else
+            startOfLine = FALSE;
+    }
+    if (!startOfLine)
+        fputc('\n', stdout);
+}
+
+
+
+static void
+doOneImage(const char * const name,
+           unsigned int const imageDoneCount,
+           FILE *       const fileP,
+           bool         const allimages,
+           bool         const justCount,
+           bool         const wantComments,
+           bool *       const eofP) {
+                    
+    struct pam pam;
+    const char * comments;
+    enum pm_check_code checkRetval;
+
+    pam.comment_p = &comments;
+
+    pnm_readpaminit(fileP, &pam, PAM_STRUCT_SIZE(comment_p));
+        
+    if (!justCount) {
+        if (allimages)
+            printf("%s:\tImage %d:\t", name, imageDoneCount);
+        else 
+            printf("%s:\t", name);
+            
+        dumpHeader(pam);
+        if (wantComments)
+            dumpComments(comments);
+    }
+    strfree(comments);
+
+    pnm_checkpam(&pam, PM_CHECK_BASIC, &checkRetval);
+    if (allimages) {
+        tuple * tuplerow;
+        unsigned int row;
+        
+        tuplerow = pnm_allocpamrow(&pam);
+        
+        for (row = 0; row < pam.height; ++row) 
+            pnm_readpamrow(&pam, tuplerow);
+        
+        pnm_freepamrow(tuplerow);
+        
+        pnm_nextimage(fileP, eofP);
+    }
+}
+
+
+
+static void
+describeOneFile(const char * const name,
+                FILE *       const fileP,
+                bool         const allimages,
+                bool         const justCount,
+                bool         const wantComments) {
+/*----------------------------------------------------------------------------
+   Describe one image stream (file).  Its name, for purposes of display,
+   is 'name'.  The file is open as *fileP and positioned to the beginning.
+
+   'allimages' means report on every image in the stream and read all of
+   every image from it, as opposed to reading just the header of the first
+   image and reporting just on that.
+
+   'justCount' means don't tell anything about the stream except how
+   many images are in it.  Pretty useless without 'allimages'.
+
+   'wantComments' means to show the comments from the image header.
+   Meaningless with 'justCount'.
+-----------------------------------------------------------------------------*/
+    unsigned int imageDoneCount;
+        /* Number of images we've processed so far */
+    bool eof;
+    
+    eof = FALSE;
+    imageDoneCount = 0;
+
+    while (!eof && (imageDoneCount < 1 || allimages)) {
+        doOneImage(name, imageDoneCount, fileP,
+                   allimages, justCount, wantComments,
+                   &eof);
+        ++imageDoneCount;
+    }
+    if (justCount)
+        printf("%s:\t%u images\n", name, imageDoneCount);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    if (cmdline.inputFileCount == 0)
+        describeOneFile("stdin", stdin, cmdline.allimages || cmdline.count,
+                        cmdline.count, cmdline.comments);
+    else {
+        unsigned int i;
+        for (i = 0; i < cmdline.inputFileCount; ++i) {
+            FILE * ifP;
+            ifP = pm_openr(cmdline.inputFilespec[i]);
+            describeOneFile(cmdline.inputFilespec[i], ifP, 
+                            cmdline.allimages || cmdline.count,
+                            cmdline.count, cmdline.comments);
+            pm_close(ifP);
+	    }
+	}
+    
+    return 0;
+}
diff --git a/analyzer/pamsharpmap.c b/analyzer/pamsharpmap.c
new file mode 100644
index 00000000..73923ab9
--- /dev/null
+++ b/analyzer/pamsharpmap.c
@@ -0,0 +1,188 @@
+/*----------------------------------------------------------------------------
+                                Pnmsharpness
+------------------------------------------------------------------------------
+
+  Bryan Henderson derived this (January 2004) from the program Pnmsharp
+  by B.W. van Schooten and distributed to Bryan under the Perl
+  Artistic License, as part of the Photopnmtools package.  Bryan placed
+  his modifications in the public domain.
+
+  This is the copyright/license notice from the original:
+
+   Copyright (c) 2002, 2003 by B.W. van Schooten. All rights reserved.
+   This software is distributed under the Perl Artistic License.
+   No warranty. See file 'artistic.license' for more details.
+
+   boris@13thmonkey.org
+   www.13thmonkey.org/~boris/photopnmtools/ 
+-----------------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <math.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* '-' if stdin */
+    unsigned int context;
+};
+
+
+
+static void
+parseCommandLine ( int argc, char ** argv,
+                   struct cmdlineInfo *cmdlineP ) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int contextSpec;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "context",       OPT_UINT,   &cmdlineP->context,       
+            &contextSpec,         0 );
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!contextSpec)
+        cmdlineP->context = 1;
+
+    if (cmdlineP->context < 1)
+        pm_error("-context must be at least 1");
+
+
+    if (argc-1 > 1)
+        pm_error("The only argument is the input file name");
+    else if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+static void
+makeSharpnessPixel(struct pam * const pamP,
+                   float *      const sharpness,
+                   tuple        const sharpnessTuple) {
+
+    unsigned int plane;
+    for (plane = 0; plane < pamP->depth; ++plane)
+        sharpnessTuple[plane] = 
+            (sample)(sharpness[plane] * pamP->maxval + 0.5);
+}
+
+
+
+static void
+makeBlackTuplen(struct pam * const pamP,
+                tuplen       const tuplen) {
+
+    unsigned int plane;
+    for (plane = 0; plane < pamP->depth; ++plane)
+        tuplen[plane] = 0.0;
+}
+
+
+
+static void
+makeBlackRown(struct pam * const pamP,
+              tuplen *     const tuplenrow) {
+
+    unsigned int col;
+    for (col = 0; col < pamP->width; ++col)
+        makeBlackTuplen(pamP, tuplenrow[col]);
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    tuplen ** tuplenarray;
+    struct pam inpam;
+    struct pam mappam;
+    tuple ** map;
+    int row;
+    float * sharpness;
+
+	pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+	tuplenarray = pnm_readpamn(ifP, &inpam, sizeof(inpam));
+
+    mappam = inpam;
+    mappam.file = stdout;
+    mappam.maxval = 255;
+
+    MALLOCARRAY_NOFAIL(sharpness, inpam.depth);
+            
+    map = pnm_allocpamarray(&mappam);
+    makeBlackRown(&inpam, tuplenarray[0]);
+    for (row = 1; row < inpam.height-1; ++row) {
+        int col;
+        makeBlackTuplen(&inpam, tuplenarray[row][0]);
+        for (col = 1; col < inpam.width-1; ++col) {
+            int dy;
+            unsigned int plane;
+            
+            for (plane = 0; plane < inpam.depth; ++plane)
+                sharpness[plane] = 0.0;
+
+            for (dy = -1; dy <= 1; ++dy) {
+                int dx;
+                for (dx = -1; dx <= 1; ++dx) {
+                    if (dx != 0 || dy != 0) {
+                        unsigned int plane;
+                        for (plane = 0; plane < inpam.depth; ++plane) {
+                            samplen const sampleval = 
+                                tuplenarray[row][col][plane];
+                            samplen const sampleval2 = 
+                                tuplenarray[row+dy][col+dx][plane];
+                            sharpness[plane] += fabs(sampleval - sampleval2);
+                        }
+                    }
+                }
+            }
+            makeSharpnessPixel(&mappam, sharpness, map[row][col]);
+        }
+        makeBlackTuplen(&inpam, tuplenarray[row][inpam.width-1]);
+    }
+    makeBlackRown(&inpam, tuplenarray[inpam.height-1]);
+    free(sharpness);
+    
+    pnm_writepam(&mappam, map);
+
+    pnm_freepamarray(map, &mappam);
+	pnm_freepamarrayn(tuplenarray, &inpam);
+
+	return 0;
+}
diff --git a/analyzer/pamsharpness.c b/analyzer/pamsharpness.c
new file mode 100644
index 00000000..7e52a9ba
--- /dev/null
+++ b/analyzer/pamsharpness.c
@@ -0,0 +1,153 @@
+/*----------------------------------------------------------------------------
+                                Pnmsharpness
+------------------------------------------------------------------------------
+
+  Bryan Henderson derived this (January 2004) from the program of the
+  same name by B.W. van Schooten and distributed to Bryan under the Perl
+  Artistic License, as part of the Photopnmtools package.  Bryan placed
+  his modifications in the public domain.
+
+  This is the copyright/license notice from the original:
+
+   Copyright (c) 2002, 2003 by B.W. van Schooten. All rights reserved.
+   This software is distributed under the Perl Artistic License.
+   No warranty. See file 'artistic.license' for more details.
+
+   boris@13thmonkey.org
+   www.13thmonkey.org/~boris/photopnmtools/ 
+-----------------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <math.h>
+
+#include "pam.h"
+#include "shhopt.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* '-' if stdin */
+    unsigned int context;
+};
+
+
+
+static void
+parseCommandLine ( int argc, char ** argv,
+                   struct cmdlineInfo *cmdlineP ) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int contextSpec;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "context",       OPT_UINT,   &cmdlineP->context,       
+            &contextSpec,         0 );
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!contextSpec)
+        cmdlineP->context = 1;
+
+    if (cmdlineP->context < 1)
+        pm_error("-context must be at least 1");
+
+
+    if (argc-1 > 1)
+        pm_error("The only argument is the input file name");
+    else if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+static void
+computeSharpness(struct pam * const inpamP,
+                 tuplen **    const tuplenarray,
+                 double *     const sharpnessP) {
+
+    int row;
+    double totsharp;
+    
+    totsharp = 0.0;
+
+    for (row = 1; row < inpamP->height-1; ++row) {
+        int col;
+        for (col = 1; col < inpamP->width-1; ++col) {
+            int dy;
+            for (dy = -1; dy <= 1; ++dy) {
+                int dx;
+                for (dx = -1; dx <= 1; ++dx) {
+                    if (dx != 0 || dy != 0) {
+                        unsigned int plane;
+                        for (plane = 0; plane < inpamP->depth; ++plane) {
+                            samplen const sampleval = 
+                                tuplenarray[row][col][plane];
+                            samplen const sampleval2 = 
+                                tuplenarray[row+dy][col+dx][plane];
+                            totsharp += fabs(sampleval - sampleval2);
+                        }
+                    }
+                }
+            }
+		}
+	}
+    *sharpnessP = 
+        totsharp / (inpamP->width * inpamP->height * inpamP->depth * 8);
+        /* The 8 above is for the 8 neighbors to which we compare each pixel */
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    tuplen ** tuplenarray;
+    struct pam inpam;
+    double sharpness;
+
+	pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+	tuplenarray = pnm_readpamn(ifP, &inpam, sizeof(inpam));
+
+    if (inpam.height < 3 || inpam.width < 3)
+        pm_error("sharpness is undefined for an image less than 3 pixels "
+                 "in all directions.  This image is %d x %d",
+                 inpam.width, inpam.height);
+
+    computeSharpness(&inpam, tuplenarray, &sharpness);
+
+    pm_message("Sharpness = %f\n", sharpness);
+
+	pnm_freepamarrayn(tuplenarray, &inpam);
+    pm_close(ifP);
+	return 0;
+}
diff --git a/analyzer/pamslice.c b/analyzer/pamslice.c
new file mode 100644
index 00000000..fc63a2cc
--- /dev/null
+++ b/analyzer/pamslice.c
@@ -0,0 +1,199 @@
+/* 
+ *
+ * This program (Pamslice) was derived by Bryan Henderson in July 2002
+ * from Pgmslice by Jos Dingjan.  Pgmslice did the same thing, but
+ * only on PGM images.  Pamslice is a total rewrite.
+ *
+ * Copyright (C) 2000 Jos Dingjan <jos@tuatha.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+enum orientation {ROW, COLUMN};
+
+#include "pam.h"
+#include "shhopt.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+    enum orientation orientation;
+    union {
+        unsigned int row;
+        unsigned int col;
+    } u;
+    unsigned int onePlane;
+    unsigned int plane;
+    unsigned int xmgr;
+};
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int rowSpec, colSpec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "row",    OPT_UINT,   &cmdlineP->u.row, &rowSpec,            0);
+    OPTENT3(0, "column", OPT_UINT,   &cmdlineP->u.col, &colSpec,            0);
+    OPTENT3(0, "plane",  OPT_UINT,   &cmdlineP->plane, &cmdlineP->onePlane, 0);
+    OPTENT3(0, "xmgr",   OPT_FLAG,   NULL,             &cmdlineP->xmgr, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (rowSpec && colSpec)
+        pm_error("You cannot specify both -row and -col");
+
+    if (argc-1 > 1)
+        pm_error("Too many arguments (%d).  Only argument is filename.",
+                 argc-1);
+    else if (argc-1 == 1) 
+        cmdlineP->inputFilespec = argv[1];
+    else
+        cmdlineP->inputFilespec = "-";
+
+    if (rowSpec) 
+        cmdlineP->orientation = ROW;
+    else if (colSpec)
+        cmdlineP->orientation = COLUMN;
+    else
+        pm_error("You must specify either -column or -row");
+}
+
+
+
+static void
+printSlice(FILE *       const outfile,
+           tuple **     const tuples,
+           unsigned int const rowstart,
+           unsigned int const rowend,
+           unsigned int const colstart,
+           unsigned int const colend,
+           unsigned int const planestart,
+           unsigned int const planeend,
+           bool         const xmgr) {
+
+    unsigned int count;
+    unsigned int row;
+
+
+    if (xmgr) {
+        fprintf(outfile,"@    title \"Graylevel\"\n");
+        fprintf(outfile,"@    subtitle \"from (%d,%d) to (%d,%d)\"\n",
+                colstart, rowstart, colend, rowend);
+        if (colend - colstart == 1)
+            fprintf(outfile,"@    xaxis label \"row\"\n");
+        else
+            fprintf(outfile,"@    xaxis label \"column\"\n");
+        fprintf(outfile,"@    yaxis label \"grayvalue\"\n");
+        if (planeend - planestart == 1)
+            fprintf(stdout,"@    type xy\n");
+        else
+            fprintf(stdout,"@    type  nxy\n");
+    }
+    
+    count = 0;
+    for (row = rowstart; row < rowend; ++row) {
+        unsigned int col;
+        for (col = colstart; col < colend; ++col) {
+            unsigned int plane;
+            fprintf(outfile, "%d ", count++);
+            for (plane = planestart; plane < planeend; ++plane)
+                fprintf(outfile, "%lu ", tuples[row][col][plane]);
+            fprintf(outfile, "\n");
+        }
+    }
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE *ifP;
+    struct pam inpam;
+    tuple **tuples;
+    unsigned int colstart, colend, rowstart, rowend, planestart, planeend;
+
+    pgm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    tuples = pnm_readpam(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    switch (cmdline.orientation) {
+    case COLUMN:
+        if (cmdline.u.col >= inpam.width)
+            pm_error("You specified column %u, but there are only %u columns "
+                     "in the image.", cmdline.u.col, inpam.width);
+
+        colstart = cmdline.u.col;
+        colend   = colstart + 1;
+        rowstart = 0;
+        rowend   = inpam.height;
+
+        break;
+    case ROW:
+        if (cmdline.u.row >= inpam.height)
+            pm_error("You specified row %u, but there are only %u rows "
+                     "in the image.", cmdline.u.row, inpam.height);
+        colstart = 0;
+        colend   = inpam.width;
+        rowstart = cmdline.u.row;
+        rowend   = rowstart + 1;
+
+        break;
+    }
+    
+    if (cmdline.onePlane) {
+        if (cmdline.plane >= inpam.depth)
+            pm_error("You specified plane %u, but there are only %u planes "
+                     "in the image.", cmdline.plane, inpam.depth);
+        planestart = cmdline.plane;
+        planeend = planestart + 1;
+    } else {
+        planestart = 0;
+        planeend = inpam.depth;
+    }
+
+    printSlice(stdout, tuples, 
+               rowstart, rowend, colstart, colend, planestart, planeend,
+               cmdline.xmgr);
+
+    pm_close(ifP);
+    pm_close(stdout);
+        
+    exit(0);
+}
diff --git a/analyzer/pamsumm.c b/analyzer/pamsumm.c
new file mode 100644
index 00000000..390b8ebb
--- /dev/null
+++ b/analyzer/pamsumm.c
@@ -0,0 +1,231 @@
+/******************************************************************************
+                               pamsumm
+*******************************************************************************
+  Summarize all the samples of a PAM image with various functions.
+
+  By Bryan Henderson, San Jose CA 2004.02.07.
+
+  Contributed to the public domain
+
+
+******************************************************************************/
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+enum function {FN_ADD, FN_MEAN, FN_MIN, FN_MAX};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+    enum function function;
+    unsigned int normalize;
+    unsigned int brief;
+    unsigned int verbose;
+};
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int sumSpec, meanSpec, minSpec, maxSpec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "sum",       OPT_FLAG,  NULL, &sumSpec,             0);
+    OPTENT3(0,   "mean",      OPT_FLAG,  NULL, &meanSpec,            0);
+    OPTENT3(0,   "min",       OPT_FLAG,  NULL, &minSpec,             0);
+    OPTENT3(0,   "max",       OPT_FLAG,  NULL, &maxSpec,             0);
+    OPTENT3(0,   "normalize", OPT_FLAG,  NULL, &cmdlineP->normalize, 0);
+    OPTENT3(0,   "brief",     OPT_FLAG,  NULL, &cmdlineP->brief,     0);
+    OPTENT3(0,   "verbose",   OPT_FLAG,  NULL, &cmdlineP->verbose,   0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (sumSpec + minSpec + maxSpec > 1)
+        pm_error("You may specify at most one of -sum, -min, and -max");
+
+    if (sumSpec) {
+        cmdlineP->function = FN_ADD;
+    } else if (meanSpec) {
+        cmdlineP->function = FN_MEAN;
+    } else if (minSpec) {
+        cmdlineP->function = FN_MIN;
+    } else if (maxSpec) {
+        cmdlineP->function = FN_MAX;
+    } else 
+        pm_error("You must specify one of -sum, -min, or -max");
+        
+    if (argc-1 > 1)
+        pm_error("Too many arguments (%d).  File spec is the only argument.",
+                 argc-1);
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else 
+        cmdlineP->inputFilespec = argv[1];
+    
+}
+
+
+struct accum {
+    union {
+        double sum;
+        unsigned int min;
+        unsigned int max;
+    } u;
+};
+
+
+
+static void
+initAccumulator(struct accum * const accumulatorP,
+                enum function  const function) {
+
+    switch(function) {
+    case FN_ADD:  accumulatorP->u.sum = 0.0;      break;
+    case FN_MEAN: accumulatorP->u.sum = 0.0;      break;
+    case FN_MIN:  accumulatorP->u.min = UINT_MAX; break;
+    case FN_MAX:  accumulatorP->u.max = 0;        break;
+    }
+}
+
+
+
+static void
+aggregate(struct pam *   const inpamP,
+          tuple *        const tupleRow,
+          enum function  const function,
+          struct accum * const accumulatorP) {
+
+    unsigned int col;
+
+    for (col = 0; col < inpamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < inpamP->depth; ++plane) {
+            switch(function) {
+            case FN_ADD:  
+            case FN_MEAN: 
+                accumulatorP->u.sum += tupleRow[col][plane];
+            break;
+            case FN_MIN:  
+                if (tupleRow[col][plane] < accumulatorP->u.min)
+                    accumulatorP->u.min = tupleRow[col][plane];
+                break;
+            case FN_MAX:
+                if (tupleRow[col][plane] > accumulatorP->u.min)
+                    accumulatorP->u.min = tupleRow[col][plane];
+                break;
+            } 
+        }
+    }
+}
+
+
+
+static void
+printSummary(struct accum  const accumulator,
+             unsigned int  const scale,
+             unsigned int  const count,
+             enum function const function,
+             bool          const normalize,
+             bool          const brief) {
+
+    switch(function) {
+    case FN_ADD: {  
+        const char * const intro = brief ? "" : "the sum of all samples is ";
+
+        if (normalize)
+            printf("%s%f\n", intro, accumulator.u.sum/scale);
+        else
+            printf("%s%u\n", intro, (unsigned int)accumulator.u.sum);
+    }
+    break;
+    case FN_MEAN: {
+        const char * const intro = brief ? "" : "the mean of all samples is ";
+
+        if (normalize)
+            printf("%s%f\n", intro, accumulator.u.sum/count/scale);
+        else
+            printf("%s%f\n", intro, accumulator.u.sum/count);
+    }
+    break;
+    case FN_MIN: {
+        const char * const intro = 
+            brief ? "" : "the minimum of all samples is ";
+
+        if (normalize)
+            printf("%s%f\n", intro, (double)accumulator.u.min/scale);
+        else
+            printf("%s%u\n", intro, accumulator.u.min);
+    }
+    break;
+    case FN_MAX: {
+        const char * const intro = 
+            brief ? "" : "the maximum of all samples is ";
+
+        if (normalize)
+            printf("%s%f\n", intro, (double)accumulator.u.max/scale);
+        else
+            printf("%s%u\n", intro, accumulator.u.max);
+    }
+    break;
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE* ifP;
+    tuple* inputRow;   /* Row from input image */
+    int row;
+    struct cmdlineInfo cmdline;
+    struct pam inpam;   /* Input PAM image */
+    struct accum accumulator;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    inputRow = pnm_allocpamrow(&inpam);
+
+    initAccumulator(&accumulator, cmdline.function);
+
+    for (row = 0; row < inpam.height; row++) {
+        pnm_readpamrow(&inpam, inputRow);
+
+        aggregate(&inpam, inputRow, cmdline.function, &accumulator);
+    }
+    printSummary(accumulator, (unsigned)inpam.maxval,
+                 inpam.height * inpam.width * inpam.depth, 
+                 cmdline.function, cmdline.normalize, cmdline.brief);
+
+    pnm_freepamrow(inputRow);
+    pm_close(inpam.file);
+    
+    return 0;
+}
diff --git a/analyzer/pamtilt.c b/analyzer/pamtilt.c
new file mode 100644
index 00000000..3753955b
--- /dev/null
+++ b/analyzer/pamtilt.c
@@ -0,0 +1,437 @@
+/*=============================================================================
+                             pgmtilt
+===============================================================================
+  Print the tilt angle of a PGM file
+
+  Based on pgmskew by Gregg Townsend, August 2005.
+
+  Adapted to Netpbm by Bryan Henderson, August 2005.
+
+  All work has been contributed to the public domain by its authors.
+=============================================================================*/
+
+#include <math.h>
+
+#include "pam.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+
+/* program constants */
+#define DIFFMAX 255                     /* maximum scaling for differences */
+#define BARLENGTH 60                    /* length of bars printed with -v */
+
+struct cmdlineInfo {
+    /* command parameters */
+    const char * inputFilename;
+    float maxangle;     /* maximum angle attempted */
+    float astep;        /* initial angle increment */
+    float qmin;         /* minimum quality of solution */
+    unsigned int hstep; /* horizontal step size */
+    unsigned int vstep; /* vertical step size */
+    unsigned int dstep; /* difference distance */
+    unsigned int fast;    /* skip third iteration */
+    unsigned int verbose; /* generate commentary */
+};
+
+static void
+abandon(void) {
+
+    printf("00.00\n");
+    exit(0);
+}
+
+
+
+static void
+parseCommandLine(int argc, char *argv[],
+                 struct cmdlineInfo * const cmdlineP) {
+
+    static optEntry option_def[50];
+    static optStruct3 opt;
+    unsigned int option_def_index;
+
+    /* set defaults */
+    cmdlineP->hstep = 11;        /* read only every 11th column */
+    cmdlineP->vstep = 5;         /* calc differences every 5th row */
+    cmdlineP->dstep = 2;         /* check for differences two rows down */
+    cmdlineP->maxangle = 10.0;   /* assume skew is less than +/- ten degrees */
+    cmdlineP->astep = 1.0;       /* initially check by one-degree increments */
+    cmdlineP->qmin = 1.0;        /* don't require S/N better than 1.0 */
+
+    /* initialize option table */
+    option_def_index = 0;       /* incremented by OPTENT3 */
+    OPTENT3(0, "fast",     OPT_FLAG,  NULL, &cmdlineP->fast,     0);
+    OPTENT3(0, "verbose",  OPT_FLAG,  NULL, &cmdlineP->verbose,  0);
+    OPTENT3(0, "angle",    OPT_FLOAT, &cmdlineP->maxangle, NULL, 0);
+    OPTENT3(0, "quality",  OPT_FLOAT, &cmdlineP->qmin,     NULL, 0);
+    OPTENT3(0, "astep",    OPT_FLOAT, &cmdlineP->astep,    NULL, 0);
+    OPTENT3(0, "hstep",    OPT_UINT,  &cmdlineP->hstep,    NULL, 0);
+    OPTENT3(0, "vstep",    OPT_UINT,  &cmdlineP->vstep,    NULL, 0);
+    OPTENT3(0, "dstep",    OPT_UINT,  &cmdlineP->dstep,    NULL, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;          /* no short options used */
+    opt.allowNegNum = FALSE;            /* don't allow negative values */
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+    if (cmdlineP->hstep < 1)
+        pm_error("-hstep must be at least 1 column.");
+    if (cmdlineP->vstep < 1)
+        pm_error("-vstep must be at least 1 row.");
+    if (cmdlineP->dstep < 1)
+        pm_error("-dstep must be at least 1 row.");
+    if (cmdlineP->maxangle < 1 || cmdlineP->maxangle > 45)
+        pm_error("-maxangle must be between 1 and 45 degrees.");
+
+    if (argc-1 < 1)                      /* if input file name given */
+        cmdlineP->inputFilename = "-";
+    else {
+        cmdlineP->inputFilename = argv[1];
+
+        if (argc-1 > 1)
+            pm_error("There is at most one argument.  You specified %d",
+                     argc-1);
+    }
+}
+
+
+
+static void
+computeSteps(const struct pam * const pamP,
+             unsigned int       const hstepReq,
+             unsigned int       const vstepReq,
+             unsigned int *     const hstepP,
+             unsigned int *     const vstepP) {
+/*----------------------------------------------------------------------------
+  Adjust parameters if necessary now that we have the image size
+-----------------------------------------------------------------------------*/
+    if (pamP->width < 10 || pamP->height < 10)
+        abandon();
+
+    if (pamP->width < 10 * hstepReq)
+        *hstepP = pamP->width / 10;
+    else
+        *hstepP = hstepReq;
+
+    if (pamP->height < 10 * vstepReq)
+        *vstepP = pamP->height / 10;
+    else
+        *vstepP = vstepReq;
+}
+
+
+
+static void
+load(const struct pam * const pamP,
+     unsigned int       const hstep,
+     sample ***         const pixelsP,
+     unsigned int *     const hsamplesP) {
+/*----------------------------------------------------------------------------
+  read file into memory, returning array of rows
+-----------------------------------------------------------------------------*/
+    unsigned int const hsamples = 1 + (pamP->width - 1) / hstep;
+        /* use only this many cols */
+
+    unsigned int row;
+    tuple *  tuplerow;
+    sample ** pixels;
+
+    tuplerow = pnm_allocpamrow(pamP);
+    
+    MALLOCARRAY(pixels, pamP->height);
+    if (pixels == NULL)
+        pm_error("Unable to allocate array of %u pixel rows",
+                 pamP->height);
+
+    if (pixels == NULL)
+        pm_error("Unable to allocate array of %u rows", pamP->height);
+
+    for (row = 0; row < pamP->height; ++row) {
+        unsigned int i;
+        unsigned int col;
+
+        pnm_readpamrow(pamP, tuplerow);
+
+        MALLOCARRAY(pixels[row], hsamples);
+        if (pixels[row] == NULL)
+            pm_error("Unable to allocate %u-sample array for Row %u",
+                     hsamples, row);
+
+        /* save every hstep'th column */
+        for (i = col = 0; i < hsamples; ++i, col += hstep)
+            pixels[row][i] = tuplerow[col][0];
+    }
+
+    pnm_freepamrow(tuplerow);
+
+    *hsamplesP = hsamples;
+    *pixelsP   = pixels;
+}
+
+
+
+static void
+freePixels(sample **    const samples,
+           unsigned int const rows) {
+
+    unsigned int row;
+
+    for (row = 0; row < rows; ++row)
+        free(samples[row]);
+
+    free(samples);
+}
+
+
+
+static void
+replacePixelValuesWithScaledDiffs(
+    const struct pam * const pamP,
+    sample **          const pixels,
+    unsigned int       const hsamples,
+    unsigned int       const dstep) {
+/*----------------------------------------------------------------------------
+  Replace pixel values with scaled differences used for all
+  calculations
+-----------------------------------------------------------------------------*/
+    float const m = DIFFMAX / (float) pamP->maxval;  /* scale multiplier */
+
+    unsigned int row;
+
+    for (row = dstep; row < pamP->height; ++row) {
+        unsigned int col;
+        for (col = 0; col < hsamples; ++col) {
+            int const d = pixels[row - dstep][col] - pixels[row][col];
+            unsigned int const absd = d < 0 ? -d : d;
+            pixels[row - dstep][col] = m * absd;  /* scale the difference */
+        }
+    }
+}
+
+
+
+static void
+scoreAngle(const struct pam * const pamP,
+           sample **          const pixels,
+           unsigned int       const hstep,
+           unsigned int       const vstep,
+           unsigned int       const hsamples,
+           float              const deg,
+           float *            const scoreP) {
+/*----------------------------------------------------------------------------
+  calculate score for a given angle
+-----------------------------------------------------------------------------*/
+    float  const radians = (float)deg/360 * 2 * M_PI;
+    float  const dy      = hstep * tan(radians);
+    int    const dtotal  = pamP->width * tan(radians);
+    double const tscale  = 1.0 / hsamples;
+
+    double total;
+    int first;
+    int last;
+    
+    unsigned int row;
+
+    total = 0.0; /* initial value */
+
+    if (dtotal > 0) {
+        first = 0;
+        last = pamP->height - 1 - (dtotal + 1);
+    } else {
+        first = -(dtotal - 1);
+        last = pamP->height - 1;
+    }
+    for (row = first; row < last; row += vstep) {
+        float o;
+        long t;
+        double dt;
+
+        unsigned int i;
+
+        for (i = 0, t = 0, o = 0.5;
+             i < hsamples;
+             ++i, t += pixels[(int)(row + o)][i], o += dy) {
+        }
+        dt = tscale * t;
+        total += dt * dt;
+    }
+    *scoreP = total / (last - first);
+}
+
+
+
+static void
+getBestAngleLocal(
+    const struct pam * const pamP,
+    sample **          const pixels,
+    unsigned int       const hstep,
+    unsigned int       const vstep,
+    unsigned int       const hsamples,
+    float              const minangle,
+    float              const maxangle,
+    float              const incr,
+    bool               const verbose,
+    float *            const bestAngleP,
+    float *            const qualityP) {
+/*----------------------------------------------------------------------------
+  find angle of highest score within a range
+-----------------------------------------------------------------------------*/
+    int const nsamples = ((maxangle - minangle) / incr + 1.5);
+
+    float score;
+    float quality;  /* signal/noise ratio of the best angle */
+    float angle;
+    float bestangle;
+    float bestscore;
+    float total;
+    float others;
+    float * results;
+    int i;
+
+    MALLOCARRAY_NOFAIL(results, nsamples);      /* allocate array of results */
+
+    /* try all angles within the given range, stepping by incr */
+    bestangle = minangle;
+    bestscore = 0;
+    total = 0;
+    for (i = 0; i < nsamples; i++) {
+        angle = minangle + i * incr;
+        scoreAngle(pamP, pixels, hstep, vstep, hsamples, angle, &score);
+        results[i] = score;
+        if (score > bestscore ||
+            (score == bestscore && fabs(angle) < fabs(bestangle))) {
+            bestscore = score;
+            bestangle = angle;
+        }
+        total += score;
+    }
+
+    others = (total-bestscore) / (nsamples-1);  /* get mean of other scores */
+    quality = bestscore / others;
+
+    if (verbose) {
+        fprintf(stderr,
+                "\n%2d angles from %6.2f to %5.2f by %4.2f:  "
+                "best = %6.2f, S:N = %4.2f\n",
+                nsamples, minangle, maxangle, incr, bestangle, quality);
+        for (i = 0; i < nsamples; ++i) {
+            float const angle = minangle + i * incr;
+            int const n = (int) (BARLENGTH * results[i] / bestscore + 0.5);
+            fprintf(stderr, "%6.2f: %8.2f %0*d\n", angle, results[i], n, 0);
+        }
+    }
+    free(results);
+
+    *qualityP   = quality;
+    *bestAngleP = bestangle;
+}
+
+
+
+static void
+readRelevantPixels(const char *   const inputFilename,
+                   unsigned int   const hstepReq,
+                   unsigned int   const vstepReq,
+                   unsigned int * const hstepP,
+                   unsigned int * const vstepP,
+                   sample ***     const pixelsP,
+                   struct pam *   const pamP,
+                   unsigned int * const hsamplesP) {
+/*----------------------------------------------------------------------------
+  load the image, saving only the pixels we might actually inspect
+-----------------------------------------------------------------------------*/
+    FILE * ifP;
+    unsigned int hstep;
+    unsigned int vstep;
+
+    ifP = pm_openr(inputFilename);
+    pnm_readpaminit(ifP, pamP, PAM_STRUCT_SIZE(tuple_type));
+    computeSteps(pamP, hstepReq, vstepReq, &hstep, &vstep);
+
+    load(pamP, hstep, pixelsP, hsamplesP);
+
+    *hstepP = hstep;
+    *vstepP = vstep;
+    
+    pm_close(ifP);
+}
+
+
+
+static void
+getAngle(const struct pam * const pamP,
+         sample **          const pixels,
+         unsigned int       const hstep,
+         unsigned int       const vstep,
+         unsigned int       const hsamples,
+         float              const maxangle,
+         float              const astep,
+         float              const qmin,
+         bool               const fast,
+         bool               const verbose,
+         float *            const angleP) {
+
+    float a;
+    float da;
+    float lastq;        /* quality (s/n ratio) of last measurement */
+    
+    getBestAngleLocal(pamP, pixels, hstep, vstep, hsamples,
+                      -maxangle, maxangle, astep, verbose,
+                      &a, &lastq);
+
+    if ((a < -maxangle + astep / 2) || (a > maxangle - astep / 2))
+        /* extreme val almost certainly wrong */
+        abandon();
+    if (lastq < qmin)
+        /* insufficient s/n ratio */
+        abandon();
+
+    /* make a finer search in the neighborhood */
+    da = astep / 10;
+    getBestAngleLocal(pamP, pixels, hstep, vstep, hsamples,
+                      a - 9 * da, a + 9 * da, da, verbose,
+                      &a, &lastq);
+
+    /* iterate once more unless we don't need that much accuracy */
+    if (!fast) {
+        da /= 10;
+        getBestAngleLocal(pamP, pixels, hstep, vstep, hsamples,
+                          a - 9 * da, a + 9 * da, da, verbose,
+                          &a, &lastq);
+    }
+    *angleP = a;
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    struct pam pam;
+    sample ** pixels;       /* pixel data */
+    unsigned int hsamples; /* horizontal samples used */
+    unsigned int hstep;    /* horizontal step size */
+    unsigned int vstep;    /* vertical step size */
+    float angle;
+
+    pgm_init(&argc, argv);              /* initialize netpbm system */
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    readRelevantPixels(cmdline.inputFilename, cmdline.hstep, cmdline.vstep,
+                       &hstep, &vstep, &pixels, &pam, &hsamples);
+
+    replacePixelValuesWithScaledDiffs(&pam, pixels, hsamples, cmdline.dstep);
+
+    getAngle(&pam, pixels, hstep, vstep, hsamples,
+             cmdline.maxangle, cmdline.astep, cmdline.qmin,
+             cmdline.fast, cmdline.verbose, &angle);
+
+    /* report the result on stdout */
+    printf("%.2f\n", angle);
+
+    freePixels(pixels, pam.height);
+
+    return 0;
+}
diff --git a/analyzer/pbmminkowski.c b/analyzer/pbmminkowski.c
new file mode 100644
index 00000000..5edce506
--- /dev/null
+++ b/analyzer/pbmminkowski.c
@@ -0,0 +1,168 @@
+/* pbmminkowsky.c - read a portable bitmap and calculate the Minkowski Integrals
+**
+** Copyright (C) 2000 by Luuk van Dijk/Mind over Matter
+**
+** Based on pbmlife.c,
+** Copyright (C) 1988,1 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+
+#define ISWHITE(x) ( (x) == PBM_WHITE )
+
+
+int main( int argc, char** argv ){
+
+  FILE* ifp;
+
+  bit* prevrow;
+  bit* thisrow;
+  bit* tmprow;
+  
+  int row;
+  int col; 
+
+  int countTile=0;
+  int countEdgeX=0;
+  int countEdgeY=0;
+  int countVertex=0;
+  
+  int rows;
+  int cols;
+  int format;
+
+  int area, perimeter, eulerchi;
+
+
+  /*
+   * parse arg and initialize
+   */ 
+
+  pbm_init( &argc, argv );
+  
+  if ( argc > 2 )
+    pm_usage( "[pbmfile]" );
+  
+  if ( argc == 2 )
+    ifp = pm_openr( argv[1] );
+  else
+    ifp = stdin;
+  
+  pbm_readpbminit( ifp, &cols, &rows, &format );
+
+  prevrow = pbm_allocrow( cols );
+  thisrow = pbm_allocrow( cols );
+
+
+  /* first row */
+
+  pbm_readpbmrow( ifp, thisrow, cols, format );
+
+  /* tiles */
+
+  for ( col = 0; col < cols; ++col ) 
+    if( ISWHITE(thisrow[col]) ) ++countTile;
+  
+  /* shortcut: for the first row, edgeY == countTile */
+  countEdgeY = countTile;
+
+  /* x-edges */
+
+  if( ISWHITE(thisrow[0]) ) ++countEdgeX;
+
+  for ( col = 0; col < cols-1; ++col ) 
+    if( ISWHITE(thisrow[col]) || ISWHITE(thisrow[col+1]) ) ++countEdgeX;
+
+  if( ISWHITE(thisrow[cols-1]) ) ++countEdgeX;
+
+  /* shortcut: for the first row, countVertex == countEdgeX */
+  
+  countVertex = countEdgeX;
+  
+
+  for ( row = 1; row < rows; ++row ){  
+    
+    tmprow = prevrow; 
+    prevrow = thisrow;
+    thisrow = tmprow;
+ 
+    pbm_readpbmrow( ifp, thisrow, cols, format );
+  
+    /* tiles */
+
+    for ( col = 0; col < cols; ++col ) 
+      if( ISWHITE(thisrow[col]) ) ++countTile;
+    
+    /* y-edges */
+
+    for ( col = 0; col < cols; ++col ) 
+      if( ISWHITE(thisrow[col]) || ISWHITE( prevrow[col] )) ++countEdgeY;
+    
+    /* x-edges */
+
+    if( ISWHITE(thisrow[0]) ) ++countEdgeX;
+
+    for ( col = 0; col < cols-1; ++col ) 
+      if( ISWHITE(thisrow[col]) || ISWHITE(thisrow[col+1]) ) ++countEdgeX;
+    
+    if( ISWHITE(thisrow[cols-1]) ) ++countEdgeX;
+    
+    /* vertices */
+
+    if( ISWHITE(thisrow[0]) || ISWHITE(prevrow[0]) ) ++countVertex;
+
+    for ( col = 0; col < cols-1; ++col ) 
+      if(    ISWHITE(thisrow[col]) || ISWHITE(thisrow[col+1]) 
+	  || ISWHITE(prevrow[col]) || ISWHITE(prevrow[col+1]) ) ++countVertex;
+
+    if( ISWHITE(thisrow[cols-1]) || ISWHITE(prevrow[cols-1]) ) ++countVertex;
+
+	  
+  } /* for row */
+
+  /* now thisrow contains the top row*/
+  /* tiles and x-edges have been counted, now y-edges and top vertices remain */
+
+  
+  /* y-edges */
+
+  for ( col = 0; col < cols; ++col ) 
+    if( ISWHITE(thisrow[col]) ) ++countEdgeY;
+
+  /* vertices */
+  
+  if( ISWHITE(thisrow[0]) ) ++countVertex;
+
+  for ( col = 0; col < cols-1; ++col ) 
+    if( ISWHITE(thisrow[col]) || ISWHITE(thisrow[col+1]) ) ++countVertex;
+
+  if( ISWHITE(thisrow[cols-1]) ) ++countVertex;
+
+
+  /* cleanup */
+
+  pm_close( ifp );
+
+  /* print results */
+
+  printf( "   tiles:\t%d\n x-edges:\t%d\n y-edges:\t%d\nvertices:\t%d\n",
+	  countTile, countEdgeX, countEdgeY,countVertex );
+
+  area      = countTile;
+  perimeter = 2*countEdgeX + 2*countEdgeY - 4*countTile;
+  eulerchi  = countTile - countEdgeX - countEdgeY + countVertex;
+
+  printf( "    area:\t%d\nperimeter:\t%d\n eulerchi:\t%d\n",
+	  area, perimeter, eulerchi );
+  
+  exit( 0 );
+
+} /* main */
+
diff --git a/analyzer/pgmhist.c b/analyzer/pgmhist.c
new file mode 100644
index 00000000..8f4e512e
--- /dev/null
+++ b/analyzer/pgmhist.c
@@ -0,0 +1,87 @@
+/* pgmhist.c - print a histogram of the values in a portable graymap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pgm.h"
+#include "mallocvar.h"
+
+int
+main( argc, argv )
+    int argc;
+    char *argv[];
+{
+    FILE *ifp;
+    gray maxval, *grayrow;
+    register gray *gP;
+    int argn, rows, cols, format, row;
+    int i, *hist, *rcount, count, size;
+    register int col;
+    const char * const usage = "[pgmfile]";
+
+    pgm_init( &argc, argv );
+
+    argn = 1;
+
+    if ( argn < argc )
+	{
+        ifp = pm_openr( argv[argn] );
+        argn++;
+	}
+    else
+        ifp = stdin;
+
+    if ( argn != argc )
+        pm_usage( usage );
+
+    pgm_readpgminit( ifp, &cols, &rows, &maxval, &format );
+    grayrow = pgm_allocrow( cols );
+
+    /* Build histogram. */
+    MALLOCARRAY(hist, maxval + 1);
+    MALLOCARRAY(rcount, maxval + 1);
+    if ( hist == NULL || rcount == NULL )
+        pm_error( "out of memory" );
+    for ( i = 0; i <= maxval; i++ )
+        hist[i] = 0;
+    for ( row = 0; row < rows; row++ )
+	{
+        pgm_readpgmrow( ifp, grayrow, cols, maxval, format );
+        for ( col = 0, gP = grayrow; col < cols; col++, gP++ )
+            hist[(int) *gP]++;
+	}
+
+    pm_close( ifp );
+
+    /* Compute count-down */
+    count = 0;
+    for ( i = maxval; i >= 0; i-- )
+	{
+        count += hist[i];
+        rcount[i] = count;
+	}
+
+    /* And print it. */
+    printf( "value\tcount\tb%%\tw%%\n" );
+    printf( "-----\t-----\t--\t--\n" );
+    count = 0;
+    size = rows * cols;
+    for ( i = 0; i <= maxval; i++ )
+        if ( hist[i] > 0 )
+	    {
+            count += hist[i];
+            printf(
+                "%d\t%d\t%5.3g%%\t%5.3g%%\n", i, hist[i],
+                (float) count * 100.0 / size, 
+                (float) rcount[i] * 100.0 / size );
+	    }
+
+    exit( 0 );
+}
diff --git a/analyzer/pgmminkowski.c b/analyzer/pgmminkowski.c
new file mode 100644
index 00000000..dfb08429
--- /dev/null
+++ b/analyzer/pgmminkowski.c
@@ -0,0 +1,222 @@
+/* pgmminkowsky.c - read a portable graymap and calculate the Minkowski 
+** Integrals as a function of the threshold.
+**
+** Copyright (C) 2000 by Luuk van Dijk/Mind over Matter
+**
+** Based on pgmhist.c, 
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pgm.h"
+#include "mallocvar.h"
+
+
+#define MAX2(a,b) ( ( (a)>(b) ) ? (a) : (b) )
+#define MAX4(a,b,c,d) MAX2( MAX2((a),(b)), MAX2((c),(d)) )
+
+int main( int argc, char** argv ){
+  
+  FILE *ifp;
+
+  gray maxval;
+  int cols, rows, format;
+
+  gray* prevrow;
+  gray* thisrow;
+  gray* tmprow;
+  
+  int* countTile;   
+  int* countEdgeX;  
+  int* countEdgeY; 
+  int* countVertex; 
+
+  int i, col, row;
+
+  int maxtiles, maxedgex, maxedgey, maxvertex;
+  int area, perimeter, eulerchi;
+
+  double l2inv, linv;
+
+  /*
+   * parse arg and initialize
+   */ 
+
+  pgm_init( &argc, argv );
+
+  if ( argc > 2 ) pm_usage( "[pgmfile]" );
+  
+  if ( argc == 2 )
+    ifp = pm_openr( argv[1] );
+  else
+    ifp = stdin;
+
+  /*
+   * initialize
+   */
+
+  pgm_readpgminit( ifp, &cols, &rows, &maxval, &format );
+  
+  prevrow = pgm_allocrow( cols );
+  thisrow = pgm_allocrow( cols );
+  
+  MALLOCARRAY(countTile   , maxval + 1 );
+  MALLOCARRAY(countEdgeX  , maxval + 1 );
+  MALLOCARRAY(countEdgeY  , maxval + 1 );
+  MALLOCARRAY(countVertex , maxval + 1 );
+ 
+  if (countTile == NULL || countEdgeX == NULL || countEdgeY == NULL ||
+      countVertex == NULL)
+      pm_error( "out of memory" );
+  
+  for ( i = 0; i <= maxval; i++ ) countTile[i]   = 0;
+  for ( i = 0; i <= maxval; i++ ) countEdgeX[i]  = 0;
+  for ( i = 0; i <= maxval; i++ ) countEdgeY[i]  = 0;
+  for ( i = 0; i <= maxval; i++ ) countVertex[i] = 0;
+
+
+
+
+  /* first row */
+
+  pgm_readpgmrow( ifp, thisrow, cols, maxval, format );
+
+  /* tiles */
+
+  for ( col = 0; col < cols; ++col ) ++countTile[thisrow[col]]; 
+  
+  /* y-edges */
+
+  for ( col = 0; col < cols; ++col ) ++countEdgeY[thisrow[col]]; 
+
+  /* x-edges */
+
+  ++countEdgeX[thisrow[0]];
+
+  for ( col = 0; col < cols-1; ++col ) 
+    ++countEdgeX[ MAX2(thisrow[col], thisrow[col+1]) ];
+  
+  ++countEdgeX[thisrow[cols-1]];
+  
+  /* shortcut: for the first row, countVertex == countEdgeX */
+  
+  ++countVertex[thisrow[0]];
+
+  for ( col = 0; col < cols-1; ++col ) 
+    ++countVertex[ MAX2(thisrow[col], thisrow[col+1]) ];
+
+  ++countVertex[thisrow[cols-1]];
+
+  
+
+  for ( row = 1; row < rows; ++row ){  
+    
+    tmprow = prevrow; 
+    prevrow = thisrow;
+    thisrow = tmprow;
+ 
+    pgm_readpgmrow( ifp, thisrow, cols, maxval, format );
+  
+    /* tiles */
+
+    for ( col = 0; col < cols; ++col ) ++countTile[thisrow[col]]; 
+    
+    /* y-edges */
+    
+    for ( col = 0; col < cols; ++col ) 
+      ++countEdgeY[ MAX2(thisrow[col], prevrow[col]) ];
+    /* x-edges */
+    
+    ++countEdgeX[thisrow[0]];
+    
+    for ( col = 0; col < cols-1; ++col ) 
+      ++countEdgeX[ MAX2(thisrow[col], thisrow[col+1]) ];
+    
+    ++countEdgeX[thisrow[cols-1]];
+    
+    /* vertices */
+
+    ++countVertex[ MAX2(thisrow[0],prevrow[0]) ];
+
+    for ( col = 0; col < cols-1; ++col ) 
+      ++countVertex[
+        MAX4(thisrow[col], thisrow[col+1], prevrow[col], prevrow[col+1])
+      ];
+    
+    ++countVertex[ MAX2(thisrow[cols-1],prevrow[cols-1]) ];
+    
+  } /* for row */
+  
+  /* now thisrow contains the top row*/
+
+  /* tiles and x-edges have been counted, now upper
+     y-edges and top vertices remain */
+  
+  /* y-edges */
+
+  for ( col = 0; col < cols; ++col ) ++countEdgeY[ thisrow[col] ];
+
+  /* vertices */
+  
+  ++countVertex[thisrow[0]];
+
+  for ( col = 0; col < cols-1; ++col ) 
+    ++countVertex[ MAX2(thisrow[col],thisrow[col+1]) ];
+
+  ++countVertex[ thisrow[cols-1] ];
+
+
+  /* cleanup */
+
+  maxtiles =  rows    * cols;
+  maxedgex =  rows    * (cols+1);
+  maxedgey = (rows+1) *  cols;
+  maxvertex= (rows+1) * (cols+1);
+  
+  l2inv = 1.0/maxtiles;
+  linv  = 0.5/(rows+cols);
+
+  /* And print it. */
+  printf( "#threshold\t tiles\tx-edges\ty-edges\tvertices\n" );
+  printf( "#---------\t -----\t-------\t-------\t--------\n" );
+  for ( i = 0; i <= maxval; i++ ){
+
+    if( !(countTile[i] || countEdgeX[i] || countEdgeY[i] || countVertex[i] ) ) 
+      continue; /* skip empty slots */
+
+    area      = maxtiles;
+    perimeter = 2*maxedgex + 2*maxedgey - 4*maxtiles;
+    eulerchi  = maxtiles - maxedgex - maxedgey + maxvertex;
+
+    printf( "%f\t%6d\t%7d\t%7d\t%8d\t%g\t%g\t%6d\n", (float) i/(1.0*maxval), 
+        maxtiles, maxedgex, maxedgey, maxvertex,
+        area*l2inv, perimeter*linv, eulerchi
+        );
+
+
+    maxtiles -= countTile[i];
+    maxedgex -= countEdgeX[i];
+    maxedgey -= countEdgeY[i];
+    maxvertex-= countVertex[i];
+
+    /*  i, countTile[i], countEdgeX[i], countEdgeY[i], countVertex[i] */
+
+  }
+
+  /* these should be zero: */
+  printf( "#  check:\t%6d\t%7d\t%7d\t%8d\n", 
+          maxtiles, maxedgex, maxedgey, maxvertex );
+
+  pm_close( ifp );
+  
+  exit( 0 );
+  
+} /*main*/
+
+
diff --git a/analyzer/pgmtexture.c b/analyzer/pgmtexture.c
new file mode 100644
index 00000000..38eab114
--- /dev/null
+++ b/analyzer/pgmtexture.c
@@ -0,0 +1,1047 @@
+/* pgmtexture.c - calculate textural features on a portable graymap
+**
+** Author: James Darrell McCauley
+**         Texas Agricultural Experiment Station
+**         Department of Agricultural Engineering
+**         Texas A&M University
+**         College Station, Texas 77843-2117 USA
+**
+** Code written partially taken from pgmtofs.c in the PBMPLUS package
+** by Jef Poskanzer.
+**
+** Algorithms for calculating features (and some explanatory comments) are
+** taken from:
+**
+**   Haralick, R.M., K. Shanmugam, and I. Dinstein. 1973. Textural features
+**   for image classification.  IEEE Transactions on Systems, Man, and
+**   Cybertinetics, SMC-3(6):610-621.
+**
+** Copyright (C) 1991 Texas Agricultural Experiment Station, employer for
+** hire of James Darrell McCauley
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** THE TEXAS AGRICULTURAL EXPERIMENT STATION (TAES) AND THE TEXAS A&M
+** UNIVERSITY SYSTEM (TAMUS) MAKE NO EXPRESS OR IMPLIED WARRANTIES
+** (INCLUDING BY WAY OF EXAMPLE, MERCHANTABILITY) WITH RESPECT TO ANY
+** ITEM, AND SHALL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL
+** OR CONSEQUENTAL DAMAGES ARISING OUT OF THE POSESSION OR USE OF
+** ANY SUCH ITEM. LICENSEE AND/OR USER AGREES TO INDEMNIFY AND HOLD
+** TAES AND TAMUS HARMLESS FROM ANY CLAIMS ARISING OUT OF THE USE OR
+** POSSESSION OF SUCH ITEMS.
+** 
+** Modification History:
+** 24 Jun 91 - J. Michael Carstensen <jmc@imsor.dth.dk> supplied fix for 
+**             correlation function.
+**
+** 05 Oct 05 - Marc Breithecker <Marc.Breithecker@informatik.uni-erlangen.de>
+**             Fix calculation or normalizing constants for d > 1.
+*/
+
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "pgm.h"
+#include "mallocvar.h"
+
+#define RADIX 2.0
+#define EPSILON 0.000000001
+#define BL  "Angle                 "
+#define F1  "Angular Second Moment "
+#define F2  "Contrast              "
+#define F3  "Correlation           "
+#define F4  "Variance              "
+#define F5  "Inverse Diff Moment   "
+#define F6  "Sum Average           "
+#define F7  "Sum Variance          "
+#define F8  "Sum Entropy           "
+#define F9  "Entropy               "
+#define F10 "Difference Variance   "
+#define F11 "Difference Entropy    "
+#define F12 "Meas of Correlation-1 "
+#define F13 "Meas of Correlation-2 "
+#define F14 "Max Correlation Coeff "
+
+#define SIGN(x,y) ((y)<0 ? -fabs(x) : fabs(x))
+#define DOT fprintf(stderr,".")
+#define SWAP(a,b) {y=(a);(a)=(b);(b)=y;}
+
+
+static bool sortit = FALSE;
+
+static float *
+vector (int nl, int nh)
+{
+    float *v;
+
+    MALLOCARRAY(v, (unsigned) (nh - nl + 1));
+    if (v == NULL)
+        pm_error("Unable to allocate memory for a vector.");
+    return v - nl;
+}
+
+
+static float **
+matrix (int nrl, int nrh, int ncl, int nch)
+
+/* Allocates a float matrix with range [nrl..nrh][ncl..nch] */
+{
+    int i;
+    float **m;
+
+    /* allocate pointers to rows */
+    MALLOCARRAY(m, (unsigned) (nrh - nrl + 1));
+    if (m == NULL)
+        pm_error("Unable to allocate memory for a matrix.");
+
+    m -= ncl;
+
+    /* allocate rows and set pointers to them */
+    for (i = nrl; i <= nrh; i++)
+    {
+        MALLOCARRAY(m[i], (unsigned) (nch - ncl + 1));
+        if (m[i] == NULL)
+            pm_error("Unable to allocate memory for a matrix row.");
+        m[i] -= ncl;
+    }
+    /* return pointer to array of pointers to rows */
+    return m;
+}
+
+static void 
+results (const char * const c, const float * const a)
+{
+    int i;
+
+    DOT;
+    fprintf (stdout, "%s", c);
+    for (i = 0; i < 4; ++i)
+        fprintf (stdout, "% 1.3e ", a[i]);
+    fprintf (stdout, "% 1.3e\n", (a[0] + a[1] + a[2] + a[3]) / 4);
+}
+
+static void 
+simplesrt (int n, float arr[])
+{
+    int i, j;
+    float a;
+
+    for (j = 2; j <= n; j++)
+    {
+        a = arr[j];
+        i = j - 1;
+        while (i > 0 && arr[i] > a)
+        {
+            arr[i + 1] = arr[i];
+            i--;
+        }
+        arr[i + 1] = a;
+    }
+}
+
+static void 
+mkbalanced (float **a, int n)
+{
+    int last, j, i;
+    float s, r, g, f, c, sqrdx;
+
+    sqrdx = RADIX * RADIX;
+    last = 0;
+    while (last == 0)
+    {
+        last = 1;
+        for (i = 1; i <= n; i++)
+        {
+            r = c = 0.0;
+            for (j = 1; j <= n; j++)
+                if (j != i)
+                {
+                    c += fabs (a[j][i]);
+                    r += fabs (a[i][j]);
+                }
+            if (c && r)
+            {
+                g = r / RADIX;
+                f = 1.0;
+                s = c + r;
+                while (c < g)
+                {
+                    f *= RADIX;
+                    c *= sqrdx;
+                }
+                g = r * RADIX;
+                while (c > g)
+                {
+                    f /= RADIX;
+                    c /= sqrdx;
+                }
+                if ((c + r) / f < 0.95 * s)
+                {
+                    last = 0;
+                    g = 1.0 / f;
+                    for (j = 1; j <= n; j++)
+                        a[i][j] *= g;
+                    for (j = 1; j <= n; j++)
+                        a[j][i] *= f;
+                }
+            }
+        }
+    }
+}
+
+
+static void 
+reduction (float **a, int n)
+{
+    int m, j, i;
+    float y, x;
+
+    for (m = 2; m < n; m++)
+    {
+        x = 0.0;
+        i = m;
+        for (j = m; j <= n; j++)
+        {
+            if (fabs (a[j][m - 1]) > fabs (x))
+            {
+                x = a[j][m - 1];
+                i = j;
+            }
+        }
+        if (i != m)
+        {
+            for (j = m - 1; j <= n; j++)
+                SWAP (a[i][j], a[m][j])  
+                    for (j = 1; j <= n; j++)
+                        SWAP (a[j][i], a[j][m]) 
+                            a[j][i] = a[j][i];
+        }
+        if (x)
+        {
+            for (i = m + 1; i <= n; i++)
+            {
+                if ((y = a[i][m - 1]))
+                {
+                    y /= x;
+                    a[i][m - 1] = y;
+                    for (j = m; j <= n; j++)
+                        a[i][j] -= y * a[m][j];
+                    for (j = 1; j <= n; j++)
+                        a[j][m] += y * a[j][i];
+                }
+            }
+        }
+    }
+}
+
+
+
+static void 
+hessenberg (float **a, int n, float wr[], float wi[])
+
+{
+    int nn, m, l, k, j, its, i, mmin;
+    float z, y, x, w, v, u, t, s, r, q, p, anorm;
+
+    anorm = fabs (a[1][1]);
+    for (i = 2; i <= n; i++)
+        for (j = (i - 1); j <= n; j++)
+            anorm += fabs (a[i][j]);
+    nn = n;
+    t = 0.0;
+    while (nn >= 1)
+    {
+        its = 0;
+        do
+        {
+            for (l = nn; l >= 2; l--)
+            {
+                s = fabs (a[l - 1][l - 1]) + fabs (a[l][l]);
+                if (s == 0.0)
+                    s = anorm;
+                if ((float) (fabs (a[l][l - 1]) + s) == s)
+                    break;
+            }
+            x = a[nn][nn];
+            if (l == nn)
+            {
+                wr[nn] = x + t;
+                wi[nn--] = 0.0;
+            }
+            else
+            {
+                y = a[nn - 1][nn - 1];
+                w = a[nn][nn - 1] * a[nn - 1][nn];
+                if (l == (nn - 1))
+                {
+                    p = 0.5 * (y - x);
+                    q = p * p + w;
+                    z = sqrt (fabs (q));
+                    x += t;
+                    if (q >= 0.0)
+                    {
+                        z = p + SIGN (z, p); 
+                        wr[nn - 1] = wr[nn] = x + z;
+                        if (z)
+                            wr[nn] = x - w / z;
+                        wi[nn - 1] = wi[nn] = 0.0;
+                    }
+                    else
+                    {
+                        wr[nn - 1] = wr[nn] = x + p;
+                        wi[nn - 1] = -(wi[nn] = z);
+                    }
+                    nn -= 2;
+                }
+                else
+                {
+                    if (its == 30)
+                        pm_error("Too many iterations to required "
+                                 "to find %s.  Giving up", F14);
+                    if (its == 10 || its == 20)
+                    {
+                        t += x;
+                        for (i = 1; i <= nn; i++)
+                            a[i][i] -= x;
+                        s = fabs (a[nn][nn - 1]) + fabs (a[nn - 1][nn - 2]);
+                        y = x = 0.75 * s;
+                        w = -0.4375 * s * s;
+                    }
+                    ++its;
+                    for (m = (nn - 2); m >= l; m--)
+                    {
+                        z = a[m][m];
+                        r = x - z;
+                        s = y - z;
+                        p = (r * s - w) / a[m + 1][m] + a[m][m + 1];
+                        q = a[m + 1][m + 1] - z - r - s;
+                        r = a[m + 2][m + 1];
+                        s = fabs (p) + fabs (q) + fabs (r);
+                        p /= s;
+                        q /= s;
+                        r /= s;
+                        if (m == l)
+                            break;
+                        u = fabs (a[m][m - 1]) * (fabs (q) + fabs (r));
+                        v = fabs (p) * (fabs (a[m - 1][m - 1]) + fabs (z) + 
+                                        fabs (a[m + 1][m + 1]));
+                        if ((float) (u + v) == v)
+                            break;
+                    }
+                    for (i = m + 2; i <= nn; i++)
+                    {
+                        a[i][i - 2] = 0.0;
+                        if (i != (m + 2))
+                            a[i][i - 3] = 0.0;
+                    }
+                    for (k = m; k <= nn - 1; k++)
+                    {
+                        if (k != m)
+                        {
+                            p = a[k][k - 1];
+                            q = a[k + 1][k - 1];
+                            r = 0.0;
+                            if (k != (nn - 1))
+                                r = a[k + 2][k - 1];
+                            if ((x = fabs (p) + fabs (q) + fabs (r)))
+                            {
+                                p /= x;
+                                q /= x;
+                                r /= x;
+                            }
+                        }
+                        if ((s = SIGN (sqrt (p * p + q * q + r * r), p))) 
+                        {
+                            if (k == m)
+                            {
+                                if (l != m)
+                                    a[k][k - 1] = -a[k][k - 1];
+                            }
+                            else
+                                a[k][k - 1] = -s * x;
+                            p += s;
+                            x = p / s;
+                            y = q / s;
+                            z = r / s;
+                            q /= p;
+                            r /= p;
+                            for (j = k; j <= nn; j++)
+                            {
+                                p = a[k][j] + q * a[k + 1][j];
+                                if (k != (nn - 1))
+                                {
+                                    p += r * a[k + 2][j];
+                                    a[k + 2][j] -= p * z;
+                                }
+                                a[k + 1][j] -= p * y;
+                                a[k][j] -= p * x;
+                            }
+                            mmin = nn < k + 3 ? nn : k + 3;
+                            for (i = l; i <= mmin; i++)
+                            {
+                                p = x * a[i][k] + y * a[i][k + 1];
+                                if (k != (nn - 1))
+                                {
+                                    p += z * a[i][k + 2];
+                                    a[i][k + 2] -= p * r;
+                                }
+                                a[i][k + 1] -= p * q;
+                                a[i][k] -= p;
+                            }
+                        }
+                    }
+                }
+            }
+        } while (l < nn - 1);
+    }
+}
+
+
+
+static float 
+f1_asm (float **P, int Ng)
+
+/* Angular Second Moment */
+{
+    int i, j;
+    float sum = 0;
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            sum += P[i][j] * P[i][j];
+
+    return sum;
+
+    /*
+     * The angular second-moment feature (ASM) f1 is a measure of homogeneity
+     * of the image. In a homogeneous image, there are very few dominant
+     * gray-tone transitions. Hence the P matrix for such an image will have
+     * fewer entries of large magnitude.
+     */
+}
+
+
+static float 
+f2_contrast (float **P, int Ng)
+
+/* Contrast */
+{
+    int i, j, n;
+    float sum = 0, bigsum = 0;
+
+    for (n = 0; n < Ng; ++n)
+    {
+        for (i = 0; i < Ng; ++i)
+            for (j = 0; j < Ng; ++j)
+                if ((i - j) == n || (j - i) == n)
+                    sum += P[i][j];
+        bigsum += n * n * sum;
+
+        sum = 0;
+    }
+    return bigsum;
+
+    /*
+     * The contrast feature is a difference moment of the P matrix and is a
+     * measure of the contrast or the amount of local variations present in an
+     * image.
+     */
+}
+
+static float 
+f3_corr (float **P, int Ng)
+
+/* Correlation */
+{
+    int i, j;
+    float sum_sqrx = 0, sum_sqry = 0, tmp, *px;
+    float meanx =0 , meany = 0 , stddevx, stddevy;
+
+    px = vector (0, Ng);
+    for (i = 0; i < Ng; ++i)
+        px[i] = 0;
+
+    /*
+     * px[i] is the (i-1)th entry in the marginal probability matrix obtained
+     * by summing the rows of p[i][j]
+     */
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            px[i] += P[i][j];
+
+
+    /* Now calculate the means and standard deviations of px and py */
+    /*- fix supplied by J. Michael Christensen, 21 Jun 1991 */
+    /*- further modified by James Darrell McCauley, 16 Aug 1991 
+     *     after realizing that meanx=meany and stddevx=stddevy
+     */
+    for (i = 0; i < Ng; ++i)
+    {
+        meanx += px[i]*i;
+        sum_sqrx += px[i]*i*i;
+    }
+    meany = meanx;
+    sum_sqry = sum_sqrx;
+    stddevx = sqrt (sum_sqrx - (meanx * meanx));
+    stddevy = stddevx;
+
+    /* Finally, the correlation ... */
+    for (tmp = 0, i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            tmp += i*j*P[i][j];
+
+    return (tmp - meanx * meany) / (stddevx * stddevy);
+    /*
+     * This correlation feature is a measure of gray-tone linear-dependencies
+     * in the image.
+     */
+}
+
+
+static float 
+f4_var (float **P, int Ng)
+
+/* Sum of Squares: Variance */
+{
+    int i, j;
+    float mean = 0, var = 0;
+
+    /*- Corrected by James Darrell McCauley, 16 Aug 1991
+     *  calculates the mean intensity level instead of the mean of
+     *  cooccurrence matrix elements 
+     */
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            mean += i * P[i][j];
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            var += (i + 1 - mean) * (i + 1 - mean) * P[i][j];
+
+    return var;
+}
+
+static float 
+f5_idm (float **P, int Ng)
+
+/* Inverse Difference Moment */
+{
+    int i, j;
+    float idm = 0;
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            idm += P[i][j] / (1 + (i - j) * (i - j));
+
+    return idm;
+}
+
+static float 
+Pxpy[2 * PGM_MAXMAXVAL];
+
+static float 
+f6_savg (float **P, int Ng)
+
+/* Sum Average */
+{
+    int i, j;
+    float savg = 0;
+
+    for (i = 0; i <= 2 * Ng; ++i)
+        Pxpy[i] = 0;
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            Pxpy[i + j + 2] += P[i][j];
+    for (i = 2; i <= 2 * Ng; ++i)
+        savg += i * Pxpy[i];
+
+    return savg;
+}
+
+
+static float 
+f7_svar (float **P, int Ng, float S) {
+/* Sum Variance */
+    int i, j;
+    float var = 0;
+
+    for (i = 0; i <= 2 * Ng; ++i)
+        Pxpy[i] = 0;
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            Pxpy[i + j + 2] += P[i][j];
+
+    for (i = 2; i <= 2 * Ng; ++i)
+        var += (i - S) * (i - S) * Pxpy[i];
+
+    return var;
+}
+
+static float 
+f8_sentropy (float **P, int Ng)
+
+/* Sum Entropy */
+{
+    int i, j;
+    float sentropy = 0;
+
+    for (i = 0; i <= 2 * Ng; ++i)
+        Pxpy[i] = 0;
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            Pxpy[i + j + 2] += P[i][j];
+
+    for (i = 2; i <= 2 * Ng; ++i)
+        sentropy -= Pxpy[i] * log10 (Pxpy[i] + EPSILON);
+
+    return sentropy;
+}
+
+
+static float 
+f9_entropy (float **P, int Ng)
+
+/* Entropy */
+{
+    int i, j;
+    float entropy = 0;
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            entropy += P[i][j] * log10 (P[i][j] + EPSILON);
+
+    return -entropy;
+}
+
+
+static float 
+f10_dvar (float **P, int Ng)
+
+/* Difference Variance */
+{
+    int i, j, tmp;
+    float sum = 0, sum_sqr = 0, var = 0;
+
+    for (i = 0; i <= 2 * Ng; ++i)
+        Pxpy[i] = 0;
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            Pxpy[abs (i - j)] += P[i][j];
+
+    /* Now calculate the variance of Pxpy (Px-y) */
+    for (i = 0; i < Ng; ++i)
+    {
+        sum += Pxpy[i];
+        sum_sqr += Pxpy[i] * Pxpy[i];
+    }
+    tmp = Ng * Ng;
+    var = ((tmp * sum_sqr) - (sum * sum)) / (tmp * tmp);
+
+    return var;
+}
+
+static float 
+f11_dentropy (float **P, int Ng)
+    
+/* Difference Entropy */
+{
+    int i, j;
+    float sum = 0;
+
+    for (i = 0; i <= 2 * Ng; ++i)
+        Pxpy[i] = 0;
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            Pxpy[abs (i - j)] += P[i][j];
+
+    for (i = 0; i < Ng; ++i)
+        sum += Pxpy[i] * log10 (Pxpy[i] + EPSILON);
+
+    return -sum;
+}
+
+static float 
+f12_icorr (float **P, int Ng)
+
+/* Information Measures of Correlation */
+{
+    int i, j;
+    float *px, *py;
+    float hx = 0, hy = 0, hxy = 0, hxy1 = 0, hxy2 = 0;
+
+    px = vector (0, Ng);
+    py = vector (0, Ng);
+
+    /*
+     * px[i] is the (i-1)th entry in the marginal probability matrix obtained
+     * by summing the rows of p[i][j]
+     */
+    for (i = 0; i < Ng; ++i)
+    {
+        for (j = 0; j < Ng; ++j)
+        {
+            px[i] += P[i][j];
+            py[j] += P[i][j];
+        }
+    }
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+        {
+            hxy1 -= P[i][j] * log10 (px[i] * py[j] + EPSILON);
+            hxy2 -= px[i] * py[j] * log10 (px[i] * py[j] + EPSILON);
+            hxy -= P[i][j] * log10 (P[i][j] + EPSILON);
+        }
+
+    /* Calculate entropies of px and py - is this right? */
+    for (i = 0; i < Ng; ++i)
+    {
+        hx -= px[i] * log10 (px[i] + EPSILON);
+        hy -= py[i] * log10 (py[i] + EPSILON);
+    }
+/*  fprintf(stderr,"hxy1=%f\thxy=%f\thx=%f\thy=%f\n",hxy1,hxy,hx,hy); */
+    return ((hxy - hxy1) / (hx > hy ? hx : hy));
+}
+
+static float 
+f13_icorr (float **P, int Ng)
+
+/* Information Measures of Correlation */
+{
+    int i, j;
+    float *px, *py;
+    float hx = 0, hy = 0, hxy = 0, hxy1 = 0, hxy2 = 0;
+
+    px = vector (0, Ng);
+    py = vector (0, Ng);
+
+    /*
+     * px[i] is the (i-1)th entry in the marginal probability matrix obtained
+     * by summing the rows of p[i][j]
+     */
+    for (i = 0; i < Ng; ++i)
+    {
+        for (j = 0; j < Ng; ++j)
+        {
+            px[i] += P[i][j];
+            py[j] += P[i][j];
+        }
+    }
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+        {
+            hxy1 -= P[i][j] * log10 (px[i] * py[j] + EPSILON);
+            hxy2 -= px[i] * py[j] * log10 (px[i] * py[j] + EPSILON);
+            hxy -= P[i][j] * log10 (P[i][j] + EPSILON);
+        }
+
+    /* Calculate entropies of px and py */
+    for (i = 0; i < Ng; ++i)
+    {
+        hx -= px[i] * log10 (px[i] + EPSILON);
+        hy -= py[i] * log10 (py[i] + EPSILON);
+    }
+    /* fprintf(stderr,"hx=%f\thxy2=%f\n",hx,hxy2); */
+    return (sqrt (fabs (1 - exp (-2.0 * (hxy2 - hxy)))));
+}
+
+static float 
+f14_maxcorr (float **P, int Ng)
+
+/* Returns the Maximal Correlation Coefficient */
+{
+    int i, j, k;
+    float *px, *py, **Q;
+    float *x, *iy, tmp;
+
+    px = vector (0, Ng);
+    py = vector (0, Ng);
+    Q = matrix (1, Ng + 1, 1, Ng + 1);
+    x = vector (1, Ng);
+    iy = vector (1, Ng);
+
+    /*
+     * px[i] is the (i-1)th entry in the marginal probability matrix obtained
+     * by summing the rows of p[i][j]
+     */
+    for (i = 0; i < Ng; ++i)
+    {
+        for (j = 0; j < Ng; ++j)
+        {
+            px[i] += P[i][j];
+            py[j] += P[i][j];
+        }
+    }
+
+    /* Find the Q matrix */
+    for (i = 0; i < Ng; ++i)
+    {
+        for (j = 0; j < Ng; ++j)
+        {
+            Q[i + 1][j + 1] = 0;
+            for (k = 0; k < Ng; ++k)
+                Q[i + 1][j + 1] += P[i][k] * P[j][k] / px[i] / py[k];
+        }
+    }
+
+    /* Balance the matrix */
+    mkbalanced (Q, Ng);
+    /* Reduction to Hessenberg Form */
+    reduction (Q, Ng);
+    /* Finding eigenvalue for nonsymetric matrix using QR algorithm */
+    hessenberg (Q, Ng, x, iy);
+    if (sortit)
+        simplesrt(Ng,x);
+    /* Returns the sqrt of the second largest eigenvalue of Q */
+    for (i = 2, tmp = x[1]; i <= Ng; ++i)
+        tmp = (tmp > x[i]) ? tmp : x[i];
+    return sqrt (x[Ng - 1]);
+}
+
+int
+main (int argc, char *argv[]) {
+    FILE *ifp;
+    register gray **grays;
+    int tone[PGM_MAXMAXVAL], R0, R45, R90, angle, d = 1, x, y;
+    int argn, rows, cols, row, col;
+    int itone, jtone, tones;
+    float **P_matrix0, **P_matrix45, **P_matrix90, **P_matrix135;
+    float ASM[4], contrast[4], corr[4], var[4], idm[4], savg[4];
+    float sentropy[4], svar[4], entropy[4], dvar[4], dentropy[4];
+    float icorr[4], maxcorr[4];
+    gray maxval;
+    const char * const usage = "[-d <d>] [pgmfile]";
+
+
+    pgm_init( &argc, argv );
+
+    argn = 1;
+
+    /* Check for flags. */
+    if ( argn < argc && argv[argn][0] == '-' )
+    {
+        if ( argv[argn][1] == 'd' )
+        {
+            ++argn;
+            if ( argn == argc || sscanf( argv[argn], "%d", &d ) != 1 )
+                pm_usage( usage );
+        }
+        else
+            pm_usage( usage );
+        ++argn;
+    }
+
+    if ( argn < argc )
+    {
+        ifp = pm_openr( argv[argn] );
+        ++argn;
+    }
+    else
+        ifp = stdin;
+
+    if ( argn != argc )
+        pm_usage( usage );
+
+    grays = pgm_readpgm (ifp, &cols, &rows, &maxval);
+    pm_close (ifp);
+
+    if (maxval > PGM_MAXMAXVAL) 
+        pm_error("The maxval of the image (%d) is too high.  \n"
+                 "This program's maximum is %d.", maxval, PGM_MAXMAXVAL);
+
+    /* Determine the number of different gray scales (not maxval) */
+    for (row = PGM_MAXMAXVAL; row >= 0; --row)
+        tone[row] = -1;
+    for (row = rows - 1; row >= 0; --row)
+        for (col = 0; col < cols; ++col)
+            tone[grays[row][col]] = grays[row][col];
+    for (row = PGM_MAXMAXVAL, tones = 0; row >= 0; --row)
+        if (tone[row] != -1)
+            tones++;
+    pm_message("(Image has %d graylevels.)", tones);
+
+    /* Collapse array, taking out all zero values */
+    for (row = 0, itone = 0; row <= PGM_MAXMAXVAL; row++)
+        if (tone[row] != -1)
+            tone[itone++] = tone[row];
+    /* Now array contains only the gray levels present (in ascending order) */
+
+    /* Allocate memory for gray-tone spatial dependence matrix */
+    P_matrix0 = matrix (0, tones, 0, tones);
+    P_matrix45 = matrix (0, tones, 0, tones);
+    P_matrix90 = matrix (0, tones, 0, tones);
+    P_matrix135 = matrix (0, tones, 0, tones);
+    for (row = 0; row < tones; ++row)
+        for (col = 0; col < tones; ++col)
+        {
+            P_matrix0[row][col] = P_matrix45[row][col] = 0;
+            P_matrix90[row][col] = P_matrix135[row][col] = 0;
+        }
+
+    /* Find gray-tone spatial dependence matrix */
+    fprintf (stderr, "(Computing spatial dependence matrix...");
+    for (row = 0; row < rows; ++row)
+        for (col = 0; col < cols; ++col)
+            for (x = 0, angle = 0; angle <= 135; angle += 45)
+            {
+                while (tone[x] != grays[row][col])
+                    x++;
+                if (angle == 0 && col + d < cols)
+                {
+                    y = 0;
+                    while (tone[y] != grays[row][col + d])
+                        y++;
+                    P_matrix0[x][y]++;
+                    P_matrix0[y][x]++;
+                }
+                if (angle == 90 && row + d < rows)
+                {
+                    y = 0;
+                    while (tone[y] != grays[row + d][col])
+                        y++;
+                    P_matrix90[x][y]++;
+                    P_matrix90[y][x]++;
+                }
+                if (angle == 45 && row + d < rows && col - d >= 0)
+                {
+                    y = 0;
+                    while (tone[y] != grays[row + d][col - d])
+                        y++;
+                    P_matrix45[x][y]++;
+                    P_matrix45[y][x]++;
+                }
+                if (angle == 135 && row + d < rows && col + d < cols)
+                {
+                    y = 0;
+                    while (tone[y] != grays[row + d][col + d])
+                        y++;
+                    P_matrix135[x][y]++;
+                    P_matrix135[y][x]++;
+                }
+            }
+    /* Gray-tone spatial dependence matrices are complete */
+
+    /* Find normalizing constants */
+    R0 = 2 * rows * (cols - d);
+    R45 = 2 * (rows - d) * (cols - d);
+    R90 = 2 * (rows - d) * cols;
+
+    /* Normalize gray-tone spatial dependence matrix */
+    for (itone = 0; itone < tones; ++itone)
+        for (jtone = 0; jtone < tones; ++jtone)
+        {
+            P_matrix0[itone][jtone] /= R0;
+            P_matrix45[itone][jtone] /= R45;
+            P_matrix90[itone][jtone] /= R90;
+            P_matrix135[itone][jtone] /= R45;
+        }
+
+    fprintf (stderr, " done.)\n");
+    fprintf (stderr, "(Computing textural features");
+    fprintf (stdout, "\n");
+    DOT;
+    fprintf (stdout,
+             "%s         0         45         90        135        Avg\n",
+             BL);
+
+    ASM[0] = f1_asm (P_matrix0, tones);
+    ASM[1] = f1_asm (P_matrix45, tones);
+    ASM[2] = f1_asm (P_matrix90, tones);
+    ASM[3] = f1_asm (P_matrix135, tones);
+    results (F1, ASM);
+
+    contrast[0] = f2_contrast (P_matrix0, tones);
+    contrast[1] = f2_contrast (P_matrix45, tones);
+    contrast[2] = f2_contrast (P_matrix90, tones);
+    contrast[3] = f2_contrast (P_matrix135, tones);
+    results (F2, contrast);
+
+
+    corr[0] = f3_corr (P_matrix0, tones);
+    corr[1] = f3_corr (P_matrix45, tones);
+    corr[2] = f3_corr (P_matrix90, tones);
+    corr[3] = f3_corr (P_matrix135, tones);
+    results (F3, corr);
+
+    var[0] = f4_var (P_matrix0, tones);
+    var[1] = f4_var (P_matrix45, tones);
+    var[2] = f4_var (P_matrix90, tones);
+    var[3] = f4_var (P_matrix135, tones);
+    results (F4, var);
+
+
+    idm[0] = f5_idm (P_matrix0, tones);
+    idm[1] = f5_idm (P_matrix45, tones);
+    idm[2] = f5_idm (P_matrix90, tones);
+    idm[3] = f5_idm (P_matrix135, tones);
+    results (F5, idm);
+
+    savg[0] = f6_savg (P_matrix0, tones);
+    savg[1] = f6_savg (P_matrix45, tones);
+    savg[2] = f6_savg (P_matrix90, tones);
+    savg[3] = f6_savg (P_matrix135, tones);
+    results (F6, savg);
+
+    sentropy[0] = f8_sentropy (P_matrix0, tones);
+    sentropy[1] = f8_sentropy (P_matrix45, tones);
+    sentropy[2] = f8_sentropy (P_matrix90, tones);
+    sentropy[3] = f8_sentropy (P_matrix135, tones);
+    svar[0] = f7_svar (P_matrix0, tones, sentropy[0]);
+    svar[1] = f7_svar (P_matrix45, tones, sentropy[1]);
+    svar[2] = f7_svar (P_matrix90, tones, sentropy[2]);
+    svar[3] = f7_svar (P_matrix135, tones, sentropy[3]);
+    results (F7, svar);
+    results (F8, sentropy);
+
+    entropy[0] = f9_entropy (P_matrix0, tones);
+    entropy[1] = f9_entropy (P_matrix45, tones);
+    entropy[2] = f9_entropy (P_matrix90, tones);
+    entropy[3] = f9_entropy (P_matrix135, tones);
+    results (F9, entropy);
+
+    dvar[0] = f10_dvar (P_matrix0, tones);
+    dvar[1] = f10_dvar (P_matrix45, tones);
+    dvar[2] = f10_dvar (P_matrix90, tones);
+    dvar[3] = f10_dvar (P_matrix135, tones);
+    results (F10, dvar);
+
+    dentropy[0] = f11_dentropy (P_matrix0, tones);
+    dentropy[1] = f11_dentropy (P_matrix45, tones);
+    dentropy[2] = f11_dentropy (P_matrix90, tones);
+    dentropy[3] = f11_dentropy (P_matrix135, tones);
+    results (F11, dentropy);
+
+    icorr[0] = f12_icorr (P_matrix0, tones);
+    icorr[1] = f12_icorr (P_matrix45, tones);
+    icorr[2] = f12_icorr (P_matrix90, tones);
+    icorr[3] = f12_icorr (P_matrix135, tones);
+    results (F12, icorr);
+
+    icorr[0] = f13_icorr (P_matrix0, tones);
+    icorr[1] = f13_icorr (P_matrix45, tones);
+    icorr[2] = f13_icorr (P_matrix90, tones);
+    icorr[3] = f13_icorr (P_matrix135, tones);
+    results (F13, icorr);
+
+    maxcorr[0] = f14_maxcorr (P_matrix0, tones);
+    maxcorr[1] = f14_maxcorr (P_matrix45, tones);
+    maxcorr[2] = f14_maxcorr (P_matrix90, tones);
+    maxcorr[3] = f14_maxcorr (P_matrix135, tones);
+    results (F14, maxcorr);
+
+
+    fprintf (stderr, " done.)\n");
+
+    return 0;
+}
diff --git a/analyzer/pnmhistmap.c b/analyzer/pnmhistmap.c
new file mode 100644
index 00000000..0b1eeb1f
--- /dev/null
+++ b/analyzer/pnmhistmap.c
@@ -0,0 +1,483 @@
+/* pnmhistmap.c -
+ *  Draw a histogram for a PGM or PPM file
+ *
+ * Options: -verbose: the usual
+ *      -max N: force scaling value to N
+ *      -black: ignore all-black count
+ *      -white: ignore all-white count
+ *
+ * - PGM histogram is a PBM file, PPM histogram is a PPM file
+ * - No conditional code - assumes all three: PBM, PGM, PPM
+ *
+ * Copyright (C) 1993 by Wilson H. Bent, Jr (whb@usc.edu)
+ *
+ * 2004-12-11 john h. dubois iii (john@armory.com)
+ * - Added options:
+ *   -dots, -nmax, -red, -green, -blue, -width, -height, -lval, -rval
+ * - Deal properly with maxvals other than 256
+ */
+
+#include <string.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+static double const epsilon = .00001;
+
+#define SCALE_H(value) (hscale_unity ? (value) : (int)((value) * hscale))
+
+enum wantedColor {WANT_RED=0, WANT_GRN=1, WANT_BLU=2};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespecs of input files */
+    unsigned int black;
+    unsigned int white;
+    unsigned int dots;
+    bool         colorWanted[3];
+        /* subscript is enum wantedColor */
+    unsigned int verbose;
+    unsigned int nmaxSpec;
+    float        nmax;
+    unsigned int lval;
+    unsigned int rval;
+    unsigned int widthSpec;
+    unsigned int width;
+    unsigned int height;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int lvalSpec, rvalSpec, heightSpec;
+    unsigned int redSpec, greenSpec, blueSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "black",     OPT_FLAG, NULL, &cmdlineP->black,   0);
+    OPTENT3(0, "white",     OPT_FLAG, NULL, &cmdlineP->white,   0);
+    OPTENT3(0, "dots",      OPT_FLAG, NULL, &cmdlineP->dots,    0);
+    OPTENT3(0, "red",       OPT_FLAG, NULL, &redSpec,           0);
+    OPTENT3(0, "green",     OPT_FLAG, NULL, &greenSpec,         0);
+    OPTENT3(0, "blue",      OPT_FLAG, NULL, &blueSpec,          0);
+    OPTENT3(0, "verbose",   OPT_FLAG, NULL, &cmdlineP->verbose, 0);
+    OPTENT3(0, "nmax",      OPT_FLOAT, &cmdlineP->nmax,
+            &cmdlineP->nmaxSpec,   0);
+    OPTENT3(0, "lval",      OPT_UINT, &cmdlineP->lval,
+            &lvalSpec,             0);
+    OPTENT3(0, "rval",      OPT_UINT, &cmdlineP->rval,
+            &rvalSpec,             0);
+    OPTENT3(0, "width",     OPT_UINT, &cmdlineP->width,
+            &cmdlineP->widthSpec,  0);
+    OPTENT3(0, "height",    OPT_UINT, &cmdlineP->height,
+            &heightSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!lvalSpec)
+        cmdlineP->lval = 0;
+    if (!rvalSpec)
+        cmdlineP->rval = PNM_OVERALLMAXVAL;
+
+    if (!redSpec && !greenSpec && !blueSpec) {
+        cmdlineP->colorWanted[WANT_RED] = TRUE;
+        cmdlineP->colorWanted[WANT_GRN] = TRUE;
+        cmdlineP->colorWanted[WANT_BLU] = TRUE;
+    } else {
+        cmdlineP->colorWanted[WANT_RED] = redSpec;
+        cmdlineP->colorWanted[WANT_GRN] = greenSpec;
+        cmdlineP->colorWanted[WANT_BLU] = blueSpec;
+    }
+
+    if (!heightSpec)
+        cmdlineP->height = 200;
+
+    if (argc-1 == 0) 
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+static unsigned int
+maxSlotCount(const unsigned int * const hist,
+             unsigned int         const hist_width,
+             bool                 const no_white,
+             bool                 const no_black) {
+/*----------------------------------------------------------------------------
+   Return the maximum count among all the slots in hist[], not counting
+   the first and last as suggested by 'no_white' and 'no_black'.
+-----------------------------------------------------------------------------*/
+    unsigned int hmax;
+    unsigned int i;
+
+    unsigned int const start = (no_black ? 1 : 0);
+    unsigned int const finish = (no_white ? hist_width - 1 : hist_width);
+    for (hmax = 0, i = start; i < finish; ++i)
+        if (hmax < hist[i])
+            hmax = hist[i];
+
+    return hmax;
+}
+
+
+
+static void
+clipHistogram(unsigned int * const hist,
+              unsigned int   const hist_width,
+              unsigned int   const hmax) {
+
+            unsigned int i;
+
+            for (i = 0; i < hist_width; ++i)
+                hist[i] = MIN(hmax, hist[i]);
+}
+
+
+
+static void
+pgm_hist(FILE *       const fp,
+         int          const cols,
+         int          const rows,
+         xelval       const maxval,
+         int          const format,
+         bool         const dots,
+         bool         const no_white,
+         bool         const no_black,
+         bool         const verbose,
+         xelval       const startval,
+         xelval       const endval,
+         unsigned int const hist_width,
+         unsigned int const hist_height,
+         bool         const clipSpec,
+         unsigned int const clipCount,
+         double       const hscale) {
+
+    bool const hscale_unity = hscale - 1 < epsilon;
+
+    gray * grayrow;
+    bit ** bits;
+    int i, j;
+    unsigned int * ghist;
+    double vscale;
+    unsigned int hmax;
+
+    if ((ghist = calloc(hist_width, sizeof(int))) == NULL)
+        pm_error ("Not enough memory for histogram array (%d bytes)",
+                  hist_width * sizeof(int));
+    if ((bits = pbm_allocarray (hist_width, hist_height)) == NULL)
+        pm_error ("no space for output array (%d bits)",
+                  hist_width * hist_height);
+    memset (ghist, 0, sizeof (ghist));
+
+    /* read the pixel values into the histogram arrays */
+    grayrow = pgm_allocrow (cols);
+    /*XX error-check! */
+    if (verbose) pm_message ("making histogram...");
+    for (i = rows; i > 0; --i) {
+        pgm_readpgmrow (fp, grayrow, cols, maxval, format);
+        for (j = cols-1; j >= 0; --j) {
+            int value;
+
+            if ((value = grayrow[j]) >= startval && value <= endval)
+                ghist[SCALE_H(value-startval)]++;
+        }
+    }
+    pgm_freerow (grayrow);
+    fclose (fp);
+
+    /* find the highest-valued slot and set the vertical scale value */
+    if (verbose)
+        pm_message ("finding max. slot height...");
+    if (clipSpec)
+        hmax = clipCount;
+    else 
+        hmax = maxSlotCount(ghist, hist_width, no_white, no_black);
+
+    if (verbose)
+        pm_message ("Done: height = %u", hmax);
+
+    clipHistogram(ghist, hist_width, hmax);
+
+    vscale = (double) hist_height / hmax;
+
+    for (i = 0; i < hist_width; ++i) {
+        int mark = hist_height - (int)(vscale * ghist[i]);
+        for (j = 0; j < mark; ++j)
+            bits[j][i] = PBM_BLACK;
+        if (j < hist_height)
+            bits[j++][i] = PBM_WHITE;
+        for ( ; j < hist_height; ++j)
+            bits[j][i] = dots ? PBM_BLACK : PBM_WHITE;
+    }
+
+    pbm_writepbm (stdout, bits, hist_width, hist_height, 0);
+}
+
+
+
+static unsigned int
+maxSlotCountAll(unsigned int *       const hist[3],
+                unsigned int         const hist_width,
+                bool                 const no_white,
+                bool                 const no_black) {
+/*----------------------------------------------------------------------------
+   Return the maximum count among all the slots in hist[x] not
+   counting the first and last as suggested by 'no_white' and
+   'no_black'.  hist[x] may be NULL to indicate none.
+-----------------------------------------------------------------------------*/
+    unsigned int hmax;
+    unsigned int color;
+
+    hmax = 0;
+
+    for (color = 0; color < 3; ++color)
+        if (hist[color])
+            hmax = MAX(hmax, 
+                       maxSlotCount(hist[color], 
+                                    hist_width, no_white, no_black));
+    
+    return hmax;
+}
+
+
+
+static void
+createHist(bool             const colorWanted[3],
+           unsigned int     const hist_width,
+           unsigned int * (* const histP)[3]) {
+/*----------------------------------------------------------------------------
+   Allocate the histogram arrays and set each slot count to zero.
+-----------------------------------------------------------------------------*/
+    unsigned int color;
+
+    for (color = 0; color < 3; ++color)
+        if (colorWanted[color]) {
+            unsigned int * hist;
+            unsigned int i;
+            MALLOCARRAY(hist, hist_width);
+            if (hist == NULL)
+                pm_error ("Not enough memory for histogram arrays (%u bytes)",
+                          hist_width * sizeof(int) * 3);
+
+            for (i = 0; i < hist_width; ++i)
+                hist[i] = 0;
+            (*histP)[color] = hist;
+        } else
+            (*histP)[color] = NULL;
+}
+
+
+
+static void
+clipHistogramAll(unsigned int * const hist[3],
+                 unsigned int   const hist_width,
+                 unsigned int   const hmax) {
+
+    unsigned int color;
+
+    for (color = 0; color < 3; ++color)
+        if (hist[color])
+            clipHistogram(hist[color], hist_width, hmax);
+}
+
+
+
+static void
+ppm_hist(FILE *       const fp,
+         int          const cols,
+         int          const rows,
+         xelval       const maxval,
+         int          const format,
+         bool         const dots,
+         bool         const no_white,
+         bool         const no_black,
+         bool         const colorWanted[3],
+         bool         const verbose,
+         xelval       const startval,
+         xelval       const endval,
+         unsigned int const hist_width,
+         unsigned int const hist_height,
+         bool         const clipSpec,
+         unsigned int const clipCount,
+         double       const hscale) {
+
+    bool const hscale_unity = hscale - 1 < epsilon;
+
+    pixel *pixrow;
+    pixel **pixels;
+    int i, j;
+    unsigned int * hist[3];  /* Subscript is enum wantedColor */
+    double vscale;
+    unsigned int hmax;
+
+    createHist(colorWanted, hist_width, &hist);
+
+    if ((pixels = ppm_allocarray (hist_width, hist_height)) == NULL)
+        pm_error ("no space for output array (%d pixels)",
+                  hist_width * hist_height);
+    for (i = 0; i < hist_height; ++i)
+        memset (pixels[i], 0, hist_width * sizeof (pixel));
+
+    /* read the pixel values into the histogram arrays */
+    pixrow = ppm_allocrow (cols);
+    /*XX error-check! */
+    if (verbose) pm_message ("making histogram...");
+    for (i = rows; i > 0; --i) {
+        ppm_readppmrow (fp, pixrow, cols, maxval, format);
+        for (j = cols-1; j >= 0; --j) {
+            int value;
+
+            if (colorWanted[WANT_RED] && 
+                (value = PPM_GETR(pixrow[j])) >= startval && 
+                value <= endval)
+                hist[WANT_RED][SCALE_H(value-startval)]++;
+            if (colorWanted[WANT_GRN] && 
+                (value = PPM_GETG(pixrow[j])) >= startval && 
+                value <= endval)
+                hist[WANT_GRN][SCALE_H(value-startval)]++;
+            if (colorWanted[WANT_BLU] && 
+                (value = PPM_GETB(pixrow[j])) >= startval && 
+                value <= endval)
+                hist[WANT_BLU][SCALE_H(value-startval)]++;
+        }
+    }
+    ppm_freerow (pixrow);
+    fclose (fp);
+
+    /* find the highest-valued slot and set the vertical scale value */
+    if (verbose)
+        pm_message ("finding max. slot height...");
+    if (clipSpec)
+        hmax = clipCount;
+    else 
+        hmax = maxSlotCountAll(hist, hist_width, no_white, no_black);
+
+    clipHistogramAll(hist, hist_width, hmax);
+
+    vscale = (double) hist_height / hmax;
+    if (verbose)
+        pm_message("Done: height = %d, vertical scale factor = %g", 
+                   hmax, vscale);
+
+    for (i = 0; i < hist_width; ++i) {
+        if (hist[WANT_RED]) {
+            unsigned int j;
+            bool plotted;
+            plotted = FALSE;
+            for (j = hist_height - (int)(vscale * hist[WANT_RED][i]); 
+                 j < hist_height && !plotted; 
+                 ++j) {
+                PPM_PUTR(pixels[j][i], maxval);
+                plotted = dots;
+            }
+        }
+        if (hist[WANT_GRN]) {
+            unsigned int j;
+            bool plotted;
+            plotted = FALSE;
+            for (j = hist_height - (int)(vscale * hist[WANT_GRN][i]); 
+                 j < hist_height && !plotted; 
+                 ++j) {
+                PPM_PUTG(pixels[j][i], maxval);
+                plotted = dots;
+            }
+        }
+        if (hist[WANT_BLU]) {
+            unsigned int j;
+            bool plotted;
+            plotted = FALSE;
+            for (j = hist_height - (int)(vscale * hist[WANT_BLU][i]); 
+                 j < hist_height && !plotted; 
+                 ++j) {
+                PPM_PUTB(pixels[j][i], maxval);
+                plotted = dots;
+            }
+        }
+    }
+    ppm_writeppm (stdout, pixels, hist_width, hist_height, maxval, 0);
+}
+
+
+
+int
+main (int argc, char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE* ifP;
+    int cols, rows;
+    xelval maxval;
+    int format;
+    unsigned int hist_width;
+    unsigned int range;
+    double hscale;
+    int hmax;
+
+    pnm_init (&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
+
+    range = MIN(maxval, cmdline.rval) - cmdline.lval + 1;
+
+    if (cmdline.widthSpec)
+        hist_width = cmdline.width;
+    else
+        hist_width = range;
+
+    hscale = (float)hist_width / range;
+    if (hscale - 1.0 < epsilon && cmdline.verbose)
+        pm_message("Horizontal scale factor: %g (maxval = %u)", 
+                   hscale, maxval);
+
+    if (cmdline.nmaxSpec)
+        hmax = cols * rows / hist_width * cmdline.nmax;
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PPM_TYPE:
+        ppm_hist(ifP, cols, rows, maxval, format,
+                 cmdline.dots, cmdline.white, cmdline.black,
+                 cmdline.colorWanted,
+                 cmdline.verbose, cmdline.lval, cmdline.rval, 
+                 hist_width, cmdline.height, cmdline.nmaxSpec, hmax, hscale);
+        break;
+    case PGM_TYPE:
+        pgm_hist(ifP, cols, rows, maxval, format,
+                 cmdline.dots, cmdline.white, cmdline.black,
+                 cmdline.verbose, cmdline.lval, cmdline.rval,
+                 hist_width, cmdline.height, cmdline.nmaxSpec, hmax, hscale);
+        break;
+    case PBM_TYPE:
+        pm_error("Cannot do a histogram of a a PBM file");
+        break;
+    }
+    return 0;
+}
diff --git a/analyzer/pnmpsnr.c b/analyzer/pnmpsnr.c
new file mode 100644
index 00000000..4a6bfe56
--- /dev/null
+++ b/analyzer/pnmpsnr.c
@@ -0,0 +1,209 @@
+/*
+ *  pnmpsnr.c: Compute error (RMSE, PSNR) between images
+ *
+ *
+ *  Derived from pnmpnsmr by Ulrich Hafner, part of his fiasco package,
+ *  On 2001.03.04.
+
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "pam.h"
+
+#define MAXFILES 16
+
+static int
+udiff(unsigned int const subtrahend, unsigned int const subtractor) {
+    return subtrahend-subtractor;
+}
+
+
+static double
+square(double const arg) {
+    return(arg*arg);
+}
+
+
+static void
+validate_input(const struct pam pam1, const struct pam pam2) {
+
+    if (pam1.width != pam2.width)
+        pm_error("images are not the same width, so can't be compared.  "
+                 "The first is %d columns wide, "
+                 "while the second is %d columns wide.",
+                 pam1.width, pam2.width);
+    if (pam1.height != pam2.height)
+        pm_error("images are not the same height, so can't be compared.  "
+                 "The first is %d rows high, "
+                 "while the second is %d rows high.",
+                 pam1.height, pam2.height);
+
+    if (pam1.maxval != pam2.maxval)
+        pm_error("images do not have the same maxval.  This programs works "
+                 "only on like maxvals.  "
+                 "The first image has maxval %u, "
+                 "while the second has %u.  Use Pnmdepth to change the "
+                 "maxval of one of them.",
+                 (unsigned int) pam1.maxval, (unsigned int) pam2.maxval);
+
+    if (strcmp(pam1.tuple_type, pam2.tuple_type) != 0)
+        pm_error("images are not of the same type.  The tuple types are "
+                 "'%s' and '%s', respectively.",
+                 pam1.tuple_type, pam2.tuple_type);
+
+    if (strcmp(pam1.tuple_type, PAM_PBM_TUPLETYPE) != 0 &&
+        strcmp(pam1.tuple_type, PAM_PGM_TUPLETYPE) != 0 &&
+        strcmp(pam1.tuple_type, PAM_PPM_TUPLETYPE) != 0)
+        pm_error("Images are not of a PNM type.  Tuple type is '%s'",
+                 pam1.tuple_type);
+}
+
+
+
+static void
+psnr_color(tuple const tuple1, tuple const tuple2,
+           double * const ySqDiffP, 
+           double * const cbSqDiffP, double * const crSqDiffP) {
+
+    double y1, y2, cb1, cb2, cr1, cr2;
+    
+    pnm_YCbCrtuple(tuple1, &y1, &cb1, &cr1);
+    pnm_YCbCrtuple(tuple2, &y2, &cb2, &cr2);
+    
+    *ySqDiffP =  square(y1  - y2);
+    *cbSqDiffP = square(cb1 - cb2);
+    *crSqDiffP = square(cr1 - cr2);
+}
+
+
+
+static void
+reportPsnr(struct pam const pam1, struct pam const pam2, 
+           double const ySumSqDiff, 
+           double const crSumSqDiff, double const cbSumSqDiff,
+           const char filespec1[], const char filespec2[]) {
+
+    bool const color = (strcmp(pam1.tuple_type, PAM_PPM_TUPLETYPE) == 0);
+
+    /* The PSNR is the mean of the sum of squares of the differences,
+       normalized to the range 0..1
+    */
+    double const yPsnr = ySumSqDiff 
+        / (pam1.width * pam1.height) 
+        / square(pam1.maxval);
+
+    if (color) {
+        double const cbPsnr = cbSumSqDiff 
+            / (pam1.width * pam1.height) 
+            / square(pam1.maxval);
+        double const crPsnr = crSumSqDiff 
+            / (pam1.width * pam1.height) 
+            / (pam1.maxval * pam2.maxval);
+
+        pm_message("PSNR between %s and %s:", filespec1, filespec2);
+        if (yPsnr > 1e-9)
+            pm_message("Y  color component: %.2f dB", 10 * log10(1/yPsnr));
+        else
+            pm_message("Y color component does not differ.");
+        if (cbPsnr > 1e-9)
+            pm_message("Cb color component: %.2f dB", 10 * log10(1/cbPsnr));
+        else
+        pm_message("Cb color component does not differ.");
+        if (crPsnr > 1e-9)
+            pm_message("Cr color component: %.2f dB", 10 * log10(1/crPsnr));
+        else
+            pm_message("Cr color component does not differ.");
+    } else {
+        if (yPsnr > 1e-9)
+            pm_message("PSNR between %s and %s: %.2f dB",
+                       filespec1, filespec2, 10 * log10(1/yPsnr));
+        else
+            pm_message("Images %s and %s don't differ.",
+                       filespec1, filespec2);
+    }
+}
+
+
+
+int
+main (int argc, char **argv) {
+    char *filespec1, *filespec2;  /* specs of two files to compare */
+    FILE *file1, *file2;
+    struct pam pam1, pam2;
+    bool color;
+        /* It's a color image */
+    double ySumSqDiff, crSumSqDiff, cbSumSqDiff;
+    tuple *tuplerow1, *tuplerow2;  /* malloc'ed */
+    int row;
+    
+    pnm_init(&argc, argv);
+
+    if (argc < 2) 
+        pm_error("Takes two arguments:  specifications of the two files.");
+    else {
+        filespec1 = argv[1];
+        filespec2 = argv[2];
+    }
+    
+    file1 = pm_openr(filespec1);
+    file2 = pm_openr(filespec2);
+
+    pnm_readpaminit(file1, &pam1, PAM_STRUCT_SIZE(tuple_type));
+    pnm_readpaminit(file2, &pam2, PAM_STRUCT_SIZE(tuple_type));
+
+    validate_input(pam1, pam2);
+
+    if (strcmp(pam1.tuple_type, PAM_PPM_TUPLETYPE) == 0) 
+        color = TRUE;
+    else
+        color = FALSE;
+
+    tuplerow1 = pnm_allocpamrow(&pam1);
+    tuplerow2 = pnm_allocpamrow(&pam2);
+    
+    ySumSqDiff = 0.0;
+    cbSumSqDiff = 0.0;
+    crSumSqDiff = 0.0;
+
+    for (row = 0; row < pam1.height; ++row) {
+        int col;
+        
+        pnm_readpamrow(&pam1, tuplerow1);
+        pnm_readpamrow(&pam2, tuplerow2);
+
+        for (col = 0; col < pam1.width; ++col) {
+            if (color) {
+                double ySqDiff, cbSqDiff, crSqDiff;
+                psnr_color(tuplerow1[col], tuplerow2[col], 
+                           &ySqDiff, &cbSqDiff, &crSqDiff);
+                ySumSqDiff += ySqDiff;
+                cbSumSqDiff += cbSqDiff;
+                crSumSqDiff += crSqDiff;
+                
+            } else {
+                unsigned int yDiffSq;
+                yDiffSq = square(udiff(tuplerow1[col][0], tuplerow2[col][0]));
+                ySumSqDiff += yDiffSq;
+            }
+        }
+    }
+
+    reportPsnr(pam1, pam2, ySumSqDiff, crSumSqDiff, cbSumSqDiff,
+               filespec1, filespec2);
+
+    pnm_freepamrow(tuplerow1);
+    pnm_freepamrow(tuplerow2);
+
+    return 0;
+}
+
+
+
+
+
+
diff --git a/analyzer/ppmhist.c b/analyzer/ppmhist.c
new file mode 100644
index 00000000..4c4d3c55
--- /dev/null
+++ b/analyzer/ppmhist.c
@@ -0,0 +1,290 @@
+/* ppmhist.c - read a PPM image and compute a color histogram
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <assert.h>
+
+#include "ppm.h"
+#include "shhopt.h"
+#include "nstring.h"
+
+enum sort {SORT_BY_FREQUENCY, SORT_BY_RGB};
+
+enum colorFmt {FMT_DECIMAL, FMT_HEX, FMT_FLOAT, FMT_PPMPLAIN};
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec;  /* Filespecs of input files */
+    unsigned int noheader;    /* -noheader option */
+    enum colorFmt colorFmt;
+    unsigned int colorname;   /* -colorname option */
+    enum sort sort;           /* -sort option */
+};
+
+
+
+static void
+parse_command_line(int argc, char ** argv,
+                   struct cmdline_info * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+    unsigned int option_def_index;
+    
+    unsigned int hexcolorOpt, floatOpt, mapOpt, nomapOpt;
+    const char * sort_type;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "map",       OPT_FLAG, NULL,  &mapOpt,                0);
+    OPTENT3(0,   "nomap",     OPT_FLAG, NULL,  &nomapOpt,              0);
+    OPTENT3(0,   "noheader",  OPT_FLAG, NULL,  &cmdlineP->noheader,    0);
+    OPTENT3(0,   "hexcolor",  OPT_FLAG, NULL,  &hexcolorOpt,           0);
+    OPTENT3(0,   "float",     OPT_FLAG, NULL,  &floatOpt,              0);
+    OPTENT3(0,   "colorname", OPT_FLAG, NULL,  &cmdlineP->colorname,   0);
+    OPTENT3(0,   "sort",      OPT_STRING, &sort_type, NULL,            0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    /* Set defaults */
+    sort_type = "frequency";
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 == 0) 
+        cmdlineP->input_filespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->input_filespec = argv[1];
+
+    if (hexcolorOpt + floatOpt + mapOpt > 1)
+        pm_error("You can specify only one of -hexcolor, -float, and -map");
+    if (hexcolorOpt)
+        cmdlineP->colorFmt = FMT_HEX;
+    else if (floatOpt)
+        cmdlineP->colorFmt = FMT_FLOAT;
+    else if (mapOpt)
+        cmdlineP->colorFmt = FMT_PPMPLAIN;
+    else 
+        cmdlineP->colorFmt = FMT_DECIMAL;
+
+    if (strcmp(sort_type, "frequency") == 0)
+        cmdlineP->sort = SORT_BY_FREQUENCY;
+    else if (strcmp(sort_type, "rgb") == 0)
+        cmdlineP->sort = SORT_BY_RGB;
+    else
+        pm_error("Invalid -sort value: '%s'.  The valid values are "
+                 "'frequency' and 'rgb'.", sort_type);
+}
+
+
+
+static int
+countcompare(const void *ch1, const void *ch2) {
+    return ((colorhist_vector)ch2)->value - ((colorhist_vector)ch1)->value;
+}
+
+
+static int
+rgbcompare(const void * arg1, const void * arg2) {
+
+    colorhist_vector const ch1 = (colorhist_vector) arg1;
+    colorhist_vector const ch2 = (colorhist_vector) arg2;
+
+    int retval;
+
+    retval = (PPM_GETR(ch1->color) - PPM_GETR(ch2->color));
+    if (retval == 0) {
+        retval = (PPM_GETG(ch1->color) - PPM_GETG(ch2->color));
+        if (retval == 0)
+            retval = (PPM_GETB(ch1->color) - PPM_GETB(ch2->color));
+    }
+    return retval;
+}
+
+
+
+static const char *
+colornameLabel(pixel        const color, 
+               pixval       const maxval,
+               unsigned int const nDictColor,
+               pixel        const dictColors[],
+               const char * const dictColornames[]) {
+/*----------------------------------------------------------------------------
+   Return the name of the color 'color' or the closest color in the
+   dictionary to it.  If the name returned is not the exact color,
+   prefix it with "*".  Otherwise, prefix it with " ".
+
+   'nDictColor', dictColors[], and dictColorNames[] are the color 
+   dictionary.
+
+   Return the name in static storage within this subroutine.
+-----------------------------------------------------------------------------*/
+    static char retval[32];
+    int colorIndex;
+    
+    pixel color255;  
+        /* The color, normalized to a maxval of 255: the maxval of a color
+           dictionary.
+        */
+
+    PPM_DEPTH(color255, color, maxval, 255);
+
+    colorIndex = ppm_findclosestcolor(dictColors, nDictColor, &color);
+
+    assert(colorIndex >= 0 && colorIndex < nDictColor);
+    
+    if (PPM_EQUAL(dictColors[colorIndex], color))
+        STRSCPY(retval, " ");
+    else
+        STRSCPY(retval, "*");
+    
+    STRSCAT(retval, dictColornames[colorIndex]);
+    
+    return retval;
+}
+                
+
+
+static void
+printColors(colorhist_vector const chv, 
+            int              const nColors,
+            pixval           const maxval,
+            enum colorFmt    const colorFmt,
+            unsigned int     const nKnown,
+            pixel            const knownColors[],
+            const char *     const colornames[]) {
+
+    int i;
+
+    for (i = 0; i < nColors; i++) {
+        pixval       const r          = PPM_GETR(chv[i].color);
+        pixval       const g          = PPM_GETG(chv[i].color);
+        pixval       const b          = PPM_GETB(chv[i].color);
+        double       const lum        = PPM_LUMIN(chv[i].color);
+        unsigned int const intLum     = lum + 0.5;
+        double       const floatLum   = lum / maxval;
+        unsigned int const count      = chv[i].value;
+
+        const char * colornameValue;
+
+        if (colornames)
+            colornameValue = colornameLabel(chv[i].color, maxval, 
+                                            nKnown, knownColors, colornames);
+        else
+            colornameValue = "";
+
+        switch(colorFmt) {
+        case FMT_FLOAT:
+            printf(" %1.3f %1.3f %1.3f\t%1.3f\t%7d %s\n",
+                   (double)r / maxval,
+                   (double)g / maxval,
+                   (double)b / maxval,
+                   floatLum, count, colornameValue);
+            break;
+        case FMT_HEX:
+            printf("  %04x  %04x  %04x\t%5d\t%7d %s\n",
+                   r, g, b, intLum, count, colornameValue);
+            break;
+        case FMT_DECIMAL:
+            printf(" %5d %5d %5d\t%5d\t%7d %s\n",
+                   r, g, b, intLum, count, colornameValue);
+            break;
+        case FMT_PPMPLAIN:
+            printf(" %5d %5d %5d#\t%5d\t%7d %s\n",
+                   r, g, b, intLum, count, colornameValue);
+            break;
+        }
+    }
+}
+
+
+
+int
+main(int argc, char *argv[] ) {
+    struct cmdline_info cmdline;
+    FILE* ifP;
+    colorhist_vector chv;
+    int rows, cols;
+    pixval maxval;
+    int format;
+    int nColors;
+    int (*compare_function)(const void *, const void *);
+        /* The compare function to be used with qsort() to sort the
+           histogram for output
+        */
+    unsigned int nDictColor;
+    const char ** dictColornames;
+    pixel * dictColors;
+
+    ppm_init( &argc, argv );
+
+    parse_command_line(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.input_filespec);
+
+    ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
+
+    chv = ppm_computecolorhist2(ifP, cols, rows, maxval, format, 0, &nColors);
+
+    pm_close(ifP);
+
+    switch (cmdline.sort) {
+    case SORT_BY_FREQUENCY:
+        compare_function = countcompare; break;
+    case SORT_BY_RGB:
+        compare_function = rgbcompare; break;
+    }
+
+    qsort((char*) chv, nColors, sizeof(struct colorhist_item), 
+          compare_function);
+
+    /* And print the histogram. */
+    if (cmdline.colorFmt == FMT_PPMPLAIN) 
+        printf("P3\n# color map\n%d 1\n%d\n", nColors, maxval);
+
+    if (!cmdline.noheader) {
+        const char commentDelim = cmdline.colorFmt == FMT_PPMPLAIN ? '#' : ' ';
+        printf("%c  r     g     b   \t lum \t count  %s\n",
+               commentDelim, cmdline.colorname ? "name" : "");
+        printf("%c----- ----- ----- \t-----\t------- %s\n",
+               commentDelim, cmdline.colorname ? "----" : "");
+    }
+    if (cmdline.colorname) {
+        bool mustOpenTrue = TRUE;
+        ppm_readcolordict(NULL, mustOpenTrue, 
+                          &nDictColor, &dictColornames, &dictColors, NULL);
+    } else {
+        dictColors = NULL;
+        dictColornames = NULL;
+    }
+        
+    printColors(chv, nColors, maxval,
+                cmdline.colorFmt, nDictColor, dictColors, dictColornames);
+
+    if (dictColors)
+        free(dictColors);
+    if (dictColornames)
+        free(dictColornames);
+
+    ppm_freecolorhist(chv);
+
+    return 0;
+}
diff --git a/build_complete b/build_complete
new file mode 100644
index 00000000..e7113111
--- /dev/null
+++ b/build_complete
@@ -0,0 +1 @@
+EXISTENCE OF THIS FILE MEANS NETPBM HAS BEEN BUILT.
diff --git a/buildtools/Makefile b/buildtools/Makefile
new file mode 100644
index 00000000..63da1adf
--- /dev/null
+++ b/buildtools/Makefile
@@ -0,0 +1,46 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = buildtools
+VPATH = .:$(SRCDIR)/$(SUBDIR)
+include $(BUILDDIR)/Makefile.config
+
+MERGE_OBJECTS =
+
+# These are programs that are used by the make files:
+PROGS = libopt typegen endiangen
+
+all: $(PROGS)
+
+BINARIES =
+SCRIPTS =
+
+OMIT_BUILDTOOL_RULE = 1
+include $(SRCDIR)/Makefile.common
+
+ifdef DLLVER
+STRIP_DLL_VERSION=-DDLLVERSTR="\"$(DLLVER)\""
+endif
+
+ifeq ($(LINKER_CAN_DO_EXPLICIT_LIBRARY),Y)
+EXPLICIT=-DEXPLICIT
+endif
+
+libopt.o: libopt.c
+	$(CC_FOR_BUILD) -c $(CFLAGS_FOR_BUILD) \
+	  -DSHLIBPREFIXLIST="\"$(SHLIBPREFIXLIST)\"" \
+	  $(STRIP_DLL_VERSION) $(EXPLICIT) $(CFLAGS_PERSONAL) $(CADD) \
+	  -o $@ $<
+
+typegen.o endiangen.o:%.o:%.c
+	$(CC_FOR_BUILD) -c $(CFLAGS_FOR_BUILD) -o $@ $<
+
+$(PROGS):%:%.o
+	$(LD_FOR_BUILD) -o $@ $<
+
+clean: cleanlocal
+.PHONY: cleanlocal
+cleanlocal:
+	rm -f $(PROGS)
+
diff --git a/buildtools/Makefile.manpage b/buildtools/Makefile.manpage
new file mode 100644
index 00000000..e8c17972
--- /dev/null
+++ b/buildtools/Makefile.manpage
@@ -0,0 +1,366 @@
+# -*-makefile-*-    <-- an Emacs control
+
+# Make Unix man pages from Netpbm HTML user manual
+
+MAKEMAN = makeman
+
+MANDIR = /usr/share/man/man1
+
+# These can convert to man pages cleanly
+MAN1 = \
+	411toppm.1 \
+	anytopnm.1 \
+	asciitopgm.1 \
+	atktopbm.1 \
+	bioradtopgm.1 \
+	bmptopnm.1 \
+	bmptoppm.1 \
+	brushtopbm.1 \
+	cmuwmtopbm.1 \
+	ddbugtopbm.1 \
+	escp2topbm.1 \
+	eyuvtoppm.1 \
+	fiascotopnm.1 \
+	fitstopnm.1 \
+	fstopgm.1 \
+	g3topbm.1 \
+	gemtopbm.1 \
+	gemtopnm.1 \
+	giftopnm.1 \
+	gouldtoppm.1 \
+	hdifftopam.1 \
+	hipstopgm.1 \
+	hpcdtoppm.1 \
+	icontopbm.1 \
+	ilbmtoppm.1 \
+	imgtoppm.1 \
+	infotopam.1 \
+	jbigtopnm.1 \
+	jpeg2ktopam.1 \
+	jpegtopnm.1 \
+	leaftoppm.1 \
+	lispmtopgm.1 \
+	macptopbm.1 \
+	mdatopbm.1 \
+	mgrtopbm.1 \
+	mrf.1 \
+	mrftopbm.1 \
+	mtvtoppm.1 \
+	neotoppm.1 \
+	palmtopnm.1 \
+	pamarith.1 \
+	pamchannel.1 \
+	pamcomp.1 \
+	pamcut.1 \
+	pamdeinterlace.1 \
+	pamdice.1 \
+	pamditherbw.1 \
+	pamedge.1 \
+	pamendian.1 \
+	pamfile.1 \
+	pamflip.1 \
+	pamfunc.1 \
+	pamgauss.1 \
+	pamlookup.1 \
+	pamoil.1 \
+	pamperspective.1 \
+	pampop9.1 \
+	pamscale.1 \
+	pamseq.1 \
+	pamsharpmap.1 \
+	pamsharpness.1 \
+	pamslice.1 \
+	pamstack.1 \
+	pamstereogram.1 \
+	pamstretch-gen.1 \
+	pamstretch.1 \
+	pamsummcol.1 \
+	pamsumm.1 \
+	pamtodjvurle.1 \
+	pamtohdiff.1 \
+	pamtohtmltbl.1 \
+	pamtojpeg2k.1 \
+	pamtopfm.1 \
+	pamtopnm.1 \
+	pamtotga.1 \
+	pamtouil.1 \
+	pbmclean.1 \
+	pbmlife.1 \
+	pbmmake.1 \
+	pbmmask.1 \
+	pbmpage.1 \
+	pbmpscale.1 \
+	pbmreduce.1 \
+	pbmtext.1 \
+	pbmtextps.1 \
+	pbmto10x.1 \
+	pbmto4425.1 \
+	pbmtoascii.1 \
+	pbmtoatk.1 \
+	pbmtobbnbg.1 \
+	pbmtocmuwm.1 \
+	pbmtodjvurle.1 \
+	pbmtoepsi.1 \
+	pbmtoepson.1 \
+	pbmtoescp2.1 \
+	pbmtog3.1 \
+	pbmtogem.1 \
+	pbmtogo.1 \
+	pbmtoicon.1 \
+	pbmtolj.1 \
+	pbmtoln03.1 \
+	pbmtolps.1 \
+	pbmtomacp.1 \
+	pbmtomda.1 \
+	pbmtomgr.1 \
+	pbmtomrf.1 \
+	pbmtonokia.1 \
+	pbmtopgm.1 \
+	pbmtopi3.1 \
+	pbmtopk.1 \
+	pbmtoplot.1 \
+	pbmtoppa.1 \
+	pbmtopsg3.1 \
+	pbmtoptx.1 \
+	pbmtowbmp.1 \
+	pbmtox10bm.1 \
+	pbmtoxbm.1 \
+	pbmtoybm.1 \
+	pbmtozinc.1 \
+	pbmupc.1 \
+	pc1toppm.1 \
+	pcdovtoppm.1 \
+	pcxtoppm.1 \
+	pfmtopam.1 \
+	pgmabel.1 \
+	pgmbentley.1 \
+	pgmcrater.1 \
+	pgmedge.1 \
+	pgmenhance.1 \
+	pgmhist.1 \
+	pgmkernel.1 \
+	pgmminkowski.1 \
+	pgmmorphconv.1 \
+	pgmnoise.1 \
+	pgmnorm.1 \
+	pgmoil.1 \
+	pgmramp.1 \
+	pgmslice.1 \
+	pgmtexture.1 \
+	pgmtofs.1 \
+	pgmtolispm.1 \
+	pgmtopbm.1 \
+	pgmtopgm.1 \
+	pgmtoppm.1 \
+	pi1toppm.1 \
+	pi3topbm.1 \
+	picttoppm.1 \
+	pjtoppm.1 \
+	pktopbm.1 \
+	pngtopnm.1 \
+	pnmalias.1 \
+	pnmarith.1 \
+	pnmcat.1 \
+	pnmcolormap.1 \
+	pnmcomp.1 \
+	pnmconvol.1 \
+	pnmcrop.1 \
+	pnmcut.1 \
+	pnmdepth.1 \
+	pnmenlarge.1 \
+	pnmfile.1 \
+	pnmgamma.1 \
+	pnmhisteq.1 \
+	pnmhistmap.1 \
+	pnmindex.1 \
+	pnminterp.1 \
+	pnminvert.1 \
+	pnmmargin.1 \
+	pnmmontage.1 \
+	pnmnlfilt.1 \
+	pnmnoraw.1 \
+	pnmnorm.1 \
+	pnmpad.1 \
+	pnmpaste.1 \
+	pnmpsnr.1 \
+	pnmquant.1 \
+	pnmremap.1 \
+	pnmrotate.1 \
+	pnmscalefixed.1 \
+	pnmscale.1 \
+	pnmshear.1 \
+	pnmsmooth.1 \
+	pnmsplit.1 \
+	pnmstitch.1 \
+	pnmtile.1 \
+	pnmtoddif.1 \
+	pnmtofiasco.1 \
+	pnmtofits.1 \
+	pnmtojbig.1 \
+	pnmtojpeg.1 \
+	pnmtopalm.1 \
+	pnmtopclxl.1 \
+	pnmtoplainpnm.1 \
+	pnmtopng.1 \
+	pnmtopnm.1 \
+	pnmtops.1 \
+	pnmtorast.1 \
+	pnmtorle.1 \
+	pnmtosgi.1 \
+	pnmtosir.1 \
+	pnmtotiffcmyk.1 \
+	pnmtotiff.1 \
+	pnmtoxwd.1 \
+	ppm3d.1 \
+	ppmbrighten.1 \
+	ppmchange.1 \
+	ppmcie.1 \
+	ppmcolormask.1 \
+	ppmdim.1 \
+	ppmdist.1 \
+	ppmdither.1 \
+	ppmfade.1 \
+	ppmflash.1 \
+	ppmforge.1 \
+	ppmglobe.1 \
+	ppmhist.1 \
+	ppmlabel.1 \
+	ppmmake.1 \
+	ppmmix.1 \
+	ppmnorm.1 \
+	ppmntsc.1 \
+	ppmpat.1 \
+	ppmquantall.1 \
+	ppmquant.1 \
+	ppmrainbow.1 \
+	ppmrelief.1 \
+	ppmrough.1 \
+	ppmshadow.1 \
+	ppmshift.1 \
+	ppmspread.1 \
+	ppmsvgalib.1 \
+	ppmtoacad.1 \
+	ppmtoarbtxt.1 \
+	ppmtobmp.1 \
+	ppmtoeyuv.1 \
+	ppmtogif.1 \
+	ppmtoicr.1 \
+	ppmtoilbm.1 \
+	ppmtojpeg.1 \
+	ppmtoleaf.1 \
+	ppmtolj.1 \
+	ppmtomitsu.1 \
+	ppmtompeg.1 \
+	ppmtoneo.1 \
+	ppmtopcx.1 \
+	ppmtopgm.1 \
+	ppmtopi1.1 \
+	ppmtopict.1 \
+	ppmtopj.1 \
+	ppmtopjxl.1 \
+	ppmtoppm.1 \
+	ppmtopuzz.1 \
+	ppmtorgb3.1 \
+	ppmtosixel.1 \
+	ppmtoterm.1 \
+	ppmtotga.1 \
+	ppmtouil.1 \
+	ppmtowinicon.1 \
+	ppmtoxpm.1 \
+	ppmtoyuv.1 \
+	ppmtoyuvsplit.1 \
+	ppmtv.1 \
+	ppmwheel.1 \
+	psidtopgm.1 \
+	pstopnm.1 \
+	qrttoppm.1 \
+	rasttopnm.1 \
+	rawtopgm.1 \
+	rawtoppm.1 \
+	rgb3toppm.1 \
+	rletopnm.1 \
+	sbigtopgm.1 \
+	sgitopnm.1 \
+	sirtopnm.1 \
+	sldtoppm.1 \
+	spctoppm.1 \
+	spottopgm.1 \
+	sputoppm.1 \
+	tgatoppm.1 \
+	thinkjettopbm.1 \
+	tifftopnm.1 \
+	vidtoppm.1 \
+	wbmptopbm.1 \
+	winicontoppm.1 \
+	xbmtopbm.1 \
+	ximtoppm.1 \
+	xpmtoppm.1 \
+	xvminitoppm.1 \
+	xwdtopnm.1 \
+	ybmtopbm.1 \
+	yuvsplittoppm.1 \
+	yuvtoppm.1 \
+	zeisstopnm.1 \
+
+MAN3 = \
+	libnetpbm.3 \
+	libnetpbm_image.3 \
+	libnetpbm_ug.3 \
+	libpbm.3 \
+	libpgm.3 \
+	libpm.3 \
+	libpnm.3 \
+	libppm.3 \
+	libsystem.3 \
+	libtmpfile.3 \
+
+MAN5 = \
+	extendedopacity.5 \
+	pam.5 \
+	pbm.5 \
+	pgm.5 \
+	pnm.5 \
+	ppm.5 \
+
+MANPAGES = $(MAN1) netpbm.1 $(MAN3) $(MAN5)
+HTMLMANUALS = $(MAN1:.1=.html) $(MAN3:.3=.html) $(MAN5:.5=.html)
+XML = $(HTMLMANUALS:.html=.xml) netpbm.xml
+
+# These things don't get converted to manual pages
+# They're basically link lists, not useful in the man hierarchy.
+EXCEPTIONS = directory.html libnetpbm_dir.html
+
+# Make man pages -- reports bad lines to standard error
+manpages:
+	@python $(MAKEMAN) index.html $(HTMLMANUALS) 
+	mv index.1 netpbm.1
+
+# Make XML pages, and validate them.
+xmlpages:
+	@for x in $(MANPAGES); do doclifter $$x; done
+	@for x in $(XML); do xmllint -xinclude --postvalid $$x >/dev/null; done
+
+# This will install the generated man pages
+installman: manpages
+	for f in $(MAN1); do \
+	  if [ -f $$f ]; then gzip <$$f >$(MANDIR)/man1/$$f.gz; fi; \
+	  done
+	for f in $(MAN3); do \
+	  if [ -f $$f ]; then gzip <$$f >$(MANDIR)/man3/$$f.gz; fi; \
+	  done
+	for f in $(MAN5); do \
+	  if [ -f $$f ]; then gzip <$$f >$(MANDIR)/man5/$$f.gz; fi; \
+	  done
+	# Clean up old locations on Fedora Core 2
+	rm -f $(MANDIR)/man1/extendedopacity.1.gz 
+	rm -f $(MANDIR)/man3/directory.3.gz
+	rm -f $(MANDIR)/man3/libnetpbm_dir.3.gz
+	# remove pointer man pages (that say, "The man page isn't here")
+	# which might have been installed previously
+	for f in $(MAN1); do rm -f $(MANDIR)/man1/$$f; done
+	for f in $(MAN3); do rm -f $(MANDIR)/man3/$$f; done
+	for f in $(MAN5); do rm -f $(MANDIR)/man5/$$f; done
+
+clean:
+	@rm -f *.[135] $(XML)
+
diff --git a/buildtools/README.pkg b/buildtools/README.pkg
new file mode 100644
index 00000000..b2a6b0ef
--- /dev/null
+++ b/buildtools/README.pkg
@@ -0,0 +1,144 @@
+THESE ARE THE MANUAL INSTALLATION INSTRUCTIONS FOR NETPBM
+
+Most people install using the interactive install program 'installnetpbm'
+that is in the top level directory of the Netpbm source tree.  But it 
+isn't right for everyone.  If it doesn't do what you need, you can use
+these instructions instead.
+
+
+Once you have built and packaged Netpbm, installing is pretty
+straightforward.  If you browse the package directory, you can
+probably figure it out without reading any further.
+
+
+The parts to be installed are:
+
+  Executables 
+
+    These are the basic Netpbm programs, such as 'jpegtopnm'.
+    You will find these in the 'bin' subdirectory of the package directory.
+
+    You normally want to copy all of these into a directory that is in your
+    default program search path (which is controlled by your PATH 
+    environment variable).  Typical directories for this are /bin, /usr/bin,
+    and /usr/local/bin.
+
+  Shared Library
+         
+    This is the library that all Netpbm programs need to load and link
+    to at run time.  It is in the 'lib' subdirectory of the package
+    directory.  Building a shared library is optional; if you didn't
+    do it (which means you built executables that don't require it),
+    you don't have a 'lib' subdirectory.  Shared libraries are also
+    known as dynamic libraries and DLLs.
+
+    You normally want to copy the shared library to a directory that
+    is in your system's default shared library search path.  On
+    systems that have an 'ldconfig' program, that program controls the
+    shared library search path, and you must run it after copying the
+    Netpbm shared library to its directory.  Often, simply rebooting
+    will cause it to run cleanly.
+
+    Typical directories for this are /lib, /usr/lib, and /usr/local/lib.
+
+    On Windows, the DLLs are treated like executables, so you should
+    find the 'lib' directory empty and you should find the Netpbm DLL
+    in the 'bin' directory.  You'll probably want to install the DLL
+    and the executables in the same directory, because the shared
+    library and executable search paths are the same.
+
+  Link Library
+
+    This is a static link library.  You don't need it to run Netpbm.  You
+    need it only if you want to build your own programs that use the Netpbm
+    library.  It is in the 'link' subdirectory of the package directory.
+
+    You normally want to copy the link library into a directory that is
+    in the default search path of your linker.  Typical directories for 
+    this are /usr/lib and /usr/local/lib.
+
+  Interface Headers
+
+    These are the files that declare the interface to the Netpbm 
+    programming library.  You don't need them to run Netpbm.  You need
+    them only if you want to build your own programs that use the
+    Netpbm library.  They are in the 'include' subdirectory of the 
+    package directory.
+
+    You normally want to copy the interface header files into a directory
+    that is in the default search path of your compiler.  Typical
+    directories for this are /usr/include and /usr/local/include.
+
+  Data Files
+
+    These are files that you can use for various purposes as input to
+    Netpbm programs.  People rarely have uses for them, actually.  They
+    are in the 'data' subdirectory of the package directory.
+   
+    Put these somewhere that users will be able to find them.
+    /usr/lib/netpbm and /usr/share/netpbm are typical choices.
+
+  Man Pages
+
+    Netpbm does not have the typical Unix form of documentation.  The
+    documentation is not in files that you can read with the common
+    'man' program.  Instead, it is in HTML form, and it is not in the
+    package directory at all.  You must install it separately.
+
+    But so that a user who is not familiar with Netpbm documentation
+    doesn't find himself out in the cold with no access to
+    documentation, the package directory contains traditional man
+    pages that do nothing but tell you where to get the real
+    documentation.  (Exactly where that is depends on the options with
+    which you built and packaged Netpbm).  We call these "pointer man
+    pages."  You will find these man pages in the 'man' subdirectory
+    of the package directory.
+
+    You should copy the contents of this directory wherever your 'man'
+    program looks for man pages.  Typically, this is /usr/man, which
+    has subdirectories equivalent to those in the package directory's
+    'man' subdirectory.
+
+
+    One of the Netpbm programs is Manweb, which is designed to be a
+    replacement for the classic Man program that can access both
+    traditional man pages and worldwide web documentation in the
+    Netpbm style with the familiar 'man jpegtopnm' kind of command.
+    To set up your system for this, you will have to be sure to create
+    the /usr/man/web directory, with 'netpbm.url' in it.  Also, If you
+    install Manweb as 'man', there is no point to installing the
+    pointer man pages -- they will never be used.
+    
+
+
+The instructions above suggest putting the Netpbm parts in common
+directories such as /usr/bin, mingled with other packages.  This is
+usually the easiest way to get Netpbm working.  But also consider
+putting all Netpbm parts in separate Netpbm directories, such as
+/usr/bin/netpbm/ and /usr/link/netpbm.  In fact, you can just copy the
+entire package directory in one piece to some place such as
+/usr/local/netpbm.  You'll have to take care to set up search paths
+and such to make this kind of configuration work.  The advantage of
+keeping Netpbm separate is that it makes it easy to wipe out the
+entire installation when you don't want it anymore, and to keep
+multiple versions around.
+
+
+netpbm.config
+-------------
+
+You should create a shell script named 'netpbm.config' out of the
+template file 'config_template' in the package directory, and install
+netpbm.config in your executable search path.  Programs that want to
+find out where you installed some part of Netpbm can invoke
+netpbm.config and it will tell them.  For example, a make file for a
+program that uses the Netpbm programming library might use
+netpbm.config to generate the necessary compiler and linker options to
+access that library.
+
+Using netpbm.config, it's possible to have a viable Netpbm
+installation where netpbm.config is the only file in any default
+search path.
+
+The xxx.config file concept is a relatively new but growing convention,
+seen mostly in software related to the X Window System.
diff --git a/buildtools/config_template b/buildtools/config_template
new file mode 100644
index 00000000..04a32079
--- /dev/null
+++ b/buildtools/config_template
@@ -0,0 +1,52 @@
+@ This is a template to be processed into a Bourne shell program.
+@
+#!/bin/sh
+
+# This program was generated by the Netpbm installer.  Its purpose is to
+# allow other programs to find out about how Netpbm is installed on this
+# system.  This program is supposed to be invoked via the PATH. 
+
+@ The following @xxx@ strings get replaced by Installnetpbm when it turns
+@ this template into an actual Bourne shell program.
+version='@VERSION@'
+datadir='@DATADIR@'
+linkdir='@LINKDIR@'
+includedir='@INCLUDEDIR@'
+bindir='@BINDIR@'
+
+    
+if test $# -eq 0; then
+    echo >&2 "You need to specify one of these options:"
+    echo >&2 "  --version"
+    echo >&2 "  --datadir"
+    echo >&2 "  --linkdir"
+    echo >&2 "  --includedir"
+    echo >&2 "  --bindir"
+    exit 100
+    fi
+
+case "$1" in
+    --version)
+      echo $version
+      exit 0
+      ;;
+    --datadir)
+      echo $datadir
+      exit 0
+      ;;
+    --linkdir)
+      echo $linkdir
+      exit 0
+      ;;
+    --includedir)
+      echo $includedir
+      exit 0
+      ;;
+    --bindir)
+      echo $bindir
+      exit 0
+      ;;
+    *)
+      echo >&2 "Unrecognized option to $0: $1"
+      exit 100
+    esac
diff --git a/buildtools/configure.pl b/buildtools/configure.pl
new file mode 100755
index 00000000..3a44a0da
--- /dev/null
+++ b/buildtools/configure.pl
@@ -0,0 +1,2111 @@
+#!/usr/bin/perl -w
+
+require 5.000;
+
+use strict;
+use English;
+use File::Basename;
+use Cwd 'abs_path';
+use Fcntl;
+use Config;
+#use File::Temp "tempfile";   Not available before Perl 5.6.1
+    
+
+my ($TRUE, $FALSE) = (1,0);
+
+# This program generates Makefile.config, which is included by all of the
+# Netpbm makefiles.  You run this program as the first step in building 
+# Netpbm.  (The second step is 'make').
+
+# This program is only a convenience.  It is supported to create 
+# Makefile.config any way you want.  In fact, an easy way is to copy
+# Makefile.config.in and follow the instructions in the comments therein
+# to uncomment certain lines and make other changes.
+
+# Note that if you invoke 'make' without having first run 'configure',
+# the make will call 'configure' itself when it finds
+# 'Makefile.config' missing.  That might look a little messy to the
+# user, but it isn't the normal build process.
+
+# The argument to this program is the filepath of the Makefile.config.in
+# file.  If unspecified, the default is 'Makefile.config.in' in the 
+# Netpbm source directory.
+
+# For explanations of the stuff we put in the make files, see the comments
+# in Makefile.config.in.
+
+
+# $testCc is the command we use to do test compiles.  Note that test
+# compiles are never more than heuristics, because we may be configuring
+# a build that will happen on a whole different system, which will build
+# programs to run on a third system.
+
+my $testCc;
+
+#******************************************************************************
+#
+#  SUBROUTINES
+#
+#*****************************************************************************
+
+sub autoFlushStdout() {
+    my $oldFh = select(STDOUT);
+    $OUTPUT_AUTOFLUSH = $TRUE;
+    select($oldFh);
+}
+
+
+
+sub prompt($$) {
+
+    my ($prompt, $default) = @_;
+
+    my $defaultPrompt = defined($default) ? $default : "?";
+
+    print("$prompt [$defaultPrompt] ==> ");
+
+    my $response = <STDIN>;
+
+    if (defined($response)) {
+        chomp($response);
+        if ($response eq "" && defined($default)) {
+            $response = $default;
+        }
+    } else {
+        print("\n");
+        die("End of file on Standard Input when expecting response to prompt");
+    }
+
+    return $response;
+}
+
+
+
+sub tmpdir() {
+# This is our approximation of File::Spec->tmpdir(), which became part of
+# basic Perl some time after Perl 5.005_03.
+
+    my $retval;
+    
+    if ($ENV{"TMPDIR"}) {
+        $retval = $ENV{"TMPDIR"};
+    } else {
+        if ($Config{'osvers'} eq "djgpp") {
+            $retval = "/dev/env/DJDIR/tmp";
+        } else {
+            $retval =  "/tmp";
+        }
+    }
+    return $retval;
+}
+
+
+
+sub tempFile($) {
+
+# Here's what we'd do if we could expect Perl 5.6.1 or later, instead
+# of calling this subroutine:
+#    my ($cFile, $cFileName) = tempfile("netpbmXXXX", 
+#                                       SUFFIX=>".c", 
+#                                       DIR=>File::Spec->tmpdir(),
+#                                       UNLINK=>0);
+    my ($suffix) = @_;
+    my $fileName;
+    local *file;  # For some inexplicable reason, must be local, not my
+    my $i;
+    $i = 0;
+    do {
+        $fileName = tmpdir() . "/netpbm" . $i++ . $suffix;
+    } until sysopen(*file, $fileName, O_RDWR|O_CREAT|O_EXCL);
+
+    return(*file, $fileName);
+}
+
+
+
+sub commandExists($) {
+    my ($command) = @_;
+#-----------------------------------------------------------------------------
+#  Return TRUE iff a shell command $command exists.
+#-----------------------------------------------------------------------------
+
+# Note that it's significant that the redirection on the following
+# causes it to be executed in a shell.  That makes the return code
+# from system() a lot different than if system() were to try to
+# execute the program directly.
+
+    return(system("$command 1</dev/null 1>/dev/null 2>/dev/null")/256 != 127); 
+}
+
+
+
+sub chooseTestCompiler($$) {
+
+    my ($compiler, $testCcR) = @_;
+
+    my $cc;
+
+    if (!defined($compiler)) {
+        if ($ENV{'CC'}) {
+            $cc = $ENV{'CC'};
+        } else {
+            if (commandExists('cc')) {
+                $cc = 'cc';
+            } elsif (commandExists("gcc")) {
+                $cc = 'gcc';
+            }
+        }
+    } elsif ($compiler eq 'cc') {
+        $cc = "cc";
+    } elsif ($compiler eq 'gcc') {
+        $cc = 'gcc';
+    } else {
+        die("Internal error: invalid value \"$compiler\" for \$compiler");
+    }
+    $$testCcR = $cc;
+}
+
+
+
+sub testCflags($) {
+    my ($needLocal) = @_;
+
+    my $cflags;
+
+    $cflags = "";  # initial value 
+    
+    if ($ENV{"CPPFLAGS"}) {
+        $cflags = $ENV{"CPPFLAGS"};
+    } else {
+        $cflags = "";
+    }
+    
+    if ($ENV{"CFLAGS"}) {
+        $cflags .= " " . $ENV{"CFLAGS"};
+    }
+    
+    if ($needLocal) {
+        $cflags .= " -I/usr/local/include";
+    }
+    return $cflags;
+}    
+
+
+
+sub testCompile($$$) {
+    my ($cflags, $cSourceCodeR, $successR) = @_;
+#-----------------------------------------------------------------------------
+#  Do a test compile of the program in @{$cSourceCodeR}.
+#  
+#  Return $$successR == $TRUE iff the compile succeeds (exit code 0).
+#-----------------------------------------------------------------------------
+    my ($cFile, $cFileName) = tempFile(".c");
+
+    print $cFile @{$cSourceCodeR};
+    
+    my ($oFile, $oFileName) = tempFile(".o");
+    # Note: we tried using /dev/null for the output file and got complaints
+    # from the Sun compiler that it has the wrong suffix.  2002.08.09.
+    
+    my $compileCommand = "$testCc -c -o $oFileName $cflags $cFileName";
+    print ("Doing test compile: $compileCommand\n");
+    my $rc = system($compileCommand);
+    
+    unlink($oFileName);
+    close($oFile);
+    unlink($cFileName);
+    close($cFile);
+
+    $$successR = ($rc == 0);
+}
+
+
+
+sub displayIntroduction() {
+    print("This is the Netpbm configurator.  It is an interactive dialog " .
+          "that\n");
+    print("helps you build the file 'Makefile.config' and prepare to build ");
+    print("Netpbm.\n");
+    print("\n");
+
+    print("Do not be put off by all the questions.  Configure gives you " .
+          "the \n");
+    print("opportunity to make a lot of choices, but you don't have to.  " .
+          "If \n");
+    print("you don't have reason to believe you're smarter than Configure,\n");
+    print("just take the defaults (hit ENTER) and don't sweat it.\n");
+    print("\n");
+
+    print("If you are considering having a program feed answers to the " .
+          "questions\n");
+    print("below, please read doc/INSTALL, because that's probably the " .
+          "wrong thing to do.\n");
+    print("\n");
+
+    print("Hit ENTER to begin.\n");
+    my $response = <STDIN>;
+}
+
+
+sub askAboutCygwin() {
+
+    print("Are you building in/for the Cygwin environment?\n");
+    print("\n");
+    
+    my $default;
+    if ($OSNAME eq "cygwin") {
+        $default = "y";
+    } else {
+        $default = "n";
+    }
+    
+    my $retval;
+
+    while (!defined($retval)) {
+        my $response = prompt("(y)es or (n)o", $default);
+    
+        if (uc($response) =~ /^(Y|YES)$/)  {
+            $retval = $TRUE;
+        } elsif (uc($response) =~ /^(N|NO)$/)  {
+            $retval = $FALSE;
+        } else {
+            print("'$response' isn't one of the choices.  \n" .
+                  "You must choose 'yes' or 'no' (or 'y' or 'n').\n");
+        }
+    }
+    return $retval;
+}
+
+
+
+sub askAboutDjgpp() {
+
+    print("Are you building in/for the DJGPP environment?\n");
+    print("\n");
+    
+    my $default;
+    if ($OSNAME eq "dos") {
+        $default = "y";
+    } else {
+        $default = "n";
+    }
+    
+    my $retval;
+
+    while (!defined($retval)) {
+        my $response = prompt("(y)es or (n)o", $default);
+    
+        if (uc($response) =~ /^(Y|YES)$/)  {
+            $retval = $TRUE;
+        } elsif (uc($response) =~ /^(N|NO)$/)  {
+            $retval = $FALSE;
+        } else {
+            print("'$response' isn't one of the choices.  \n" .
+                  "You must choose 'yes' or 'no' (or 'y' or 'n').\n");
+        }
+    }
+}
+
+
+
+sub computePlatformDefault($) {
+
+    my ($defaultP) = @_;
+
+    if ($OSNAME eq "linux") {
+        $$defaultP = "gnu";
+    } elsif ($OSNAME eq "cygwin") {
+        $$defaultP = "win";
+    } elsif ($OSNAME eq "dos") {
+        # DJGPP says "dos"
+        $$defaultP = "win";
+    } elsif ($OSNAME eq "aix" || $OSNAME eq "freebsd" || $OSNAME eq "darwin" ||
+             $OSNAME eq "amigaos") {
+        $$defaultP = $OSNAME;
+    } elsif ($OSNAME eq "solaris") {
+        $$defaultP = "sun";
+    } elsif ($OSNAME eq "dec_osf") {
+        $$defaultP = "tru64";
+    } else {
+        print("Unrecognized OSNAME='$OSNAME'.  No default possible\n");
+    }
+    # OK - if you know what $OSNAME is on any other platform, send me a patch!
+}
+
+
+
+sub getPlatform() {
+
+    my $platform;
+    my $default;
+
+    computePlatformDefault(\$default);
+
+    print("Which of the following best describes your platform?\n");
+ 
+    print("gnu      GNU/Linux\n");
+    print("sun      Solaris or SunOS\n");
+    print("hp       HP-UX\n");
+    print("aix      AIX\n");
+    print("win      Windows/DOS (Cygwin, DJGPP, Mingw32)\n");
+    print("tru64    Tru64\n");
+    print("irix     Irix\n");
+    print("bsd      NetBSD, BSD/OS\n");
+    print("openbsd  OpenBSD\n");
+    print("freebsd  FreeBSD\n");
+    print("darwin   Darwin or Mac OS X\n");
+    print("amigaos  Amiga\n");
+    print("unixware Unixware\n");
+    print("sco      SCO OpenServer\n");
+    print("beos     BeOS\n");
+    print("none     none of these are even close\n");
+    print("\n");
+
+    my $response = prompt("Platform", $default);
+
+    my %platform = ("gnu"      => "GNU",
+                    "sun"      => "SOLARIS",
+                    "hp"       => "HP-UX",
+                    "aix"      => "AIX",
+                    "tru64"    => "TRU64",
+                    "irix"     => "IRIX",
+                    "win"      => "WINDOWS",
+                    "beos"     => "BEOS",
+                    "bsd"      => "NETBSD",
+                    "openbsd"  => "OPENBSD",
+                    "freebsd"  => "FREEBSD",
+                    "unixware" => "UNIXWARE",
+                    "sco"      => "SCO",
+                    "darwin"   => "DARWIN",
+                    "amigaos"  => "AMIGA",
+                    "none"     => "NONE"
+                    );
+
+    $platform = $platform{$response};
+    if (!defined($platform)) {
+        print("'$response' isn't one of the choices.\n");
+        exit 8;
+    }
+
+    my $subplatform;
+
+    if ($platform eq "WINDOWS") {
+        my ($djgpp, $cygwin);
+
+        if ($OSNAME eq "dos") {
+            $djgpp = askAboutDjgpp();
+            if ($djgpp) {
+                $cygwin = $FALSE;
+            } else {
+                $cygwin = askAboutCygwin();
+            }
+        } else {
+            $cygwin = askAboutCygwin();
+            if ($cygwin) {
+                $djgpp = $FALSE;
+            } else {
+                $djgpp = askAboutDjgpp();
+            }
+        }
+
+        if ($cygwin) {
+            $subplatform = "cygwin";
+        } elsif ($djgpp) {
+            $subplatform = "djgpp";
+        } else {
+            $subplatform = "other";
+        }
+    }
+
+    return($platform, $subplatform);
+}
+
+
+
+sub getCompiler($$) {
+    my ($platform, $compilerR) = @_;
+#-----------------------------------------------------------------------------
+#  Here are some of the issues surrounding choosing a compiler:
+#
+#  - It's not just the name of the program we need -- different compilers
+#    need different options.
+#
+#  - There are basically two choices on any system:  native compiler or
+#    GNU compiler.  That's all this program recognizes, anyway.
+#
+#  - A user may well have various compilers.  Different releases, using
+#    different standard libraries, for different target machines, etc.
+#
+#  - The CC environment variable tells the default compiler.
+#
+#  - In the absence of a CC environment variable, 'cc' is the default
+#    compiler.
+#
+#  - The user must be able to specify the compiler by overriding the CC
+#    make variable (e.g. make CC=gcc2).
+#
+#  - Configure needs to do test compiles.  The test is best if it uses
+#    the same compiler that the build eventually will use, but it's 
+#    useful even if not.
+#
+# The value this subroutine returns is NOT the command name to invoke the
+# compiler.  It is simply "cc" to mean native compiler or "gcc" to mean
+# GNU compiler or undefined to express no preference.
+#-----------------------------------------------------------------------------
+    my %gccCapablePlatform = ("SOLARIS" => 1,
+                              "TRU64"   => 1,
+                              "SCO"     => 1,
+                              "AIX"     => 1,
+                              "HP"      => 1);
+
+    if ($gccCapablePlatform{$platform}) {
+        print("GNU compiler or native operating system compiler (cc)?\n");
+        print("\n");
+
+        my $default;
+
+        if ($platform eq "SOLARIS" || $platform eq "SCO" ) {
+            $default = "gcc";
+        } else {
+            $default = "cc";
+        }
+
+        my $response = prompt("gcc or cc", $default);
+
+        if ($response eq "gcc") {
+            $$compilerR = "gcc";
+        } elsif ($response eq "cc") {
+            $$compilerR = "cc";
+        } else {
+            print("'$response' isn't one of the choices.  \n" .
+                  "You must choose 'gcc' or 'cc'.\n");
+            exit 12;
+        }
+
+        if ($$compilerR eq 'gcc' && !commandExists('gcc')) {
+            print("WARNING: You selected the GNU compiler, " .
+                  "but do not appear to have a program " .
+                  "named 'gcc' in your PATH.  This may " .
+                  "cause trouble later.  You may need to " .
+                  "set the CC environment variable or CC " .
+                  "makefile variable or install 'gcc'\n");
+        }
+        print("\n");
+    }
+}
+
+
+
+sub gccLinker() {
+#-----------------------------------------------------------------------------
+#  Determine what linker Gcc on this system appears to use.
+#
+#  Return either "gnu" or "sun"
+#
+#  For now, we assume it must be either a GNU linker or Sun linker and
+#  that all Sun linkers are fungible.
+#
+#  If we can't tell, we assume it is the GNU linker.
+#-----------------------------------------------------------------------------
+    # First, we assume that the compiler calls 'collect2' as the linker
+    # front end.  The specs file might specify some other program, but
+    # it usually says 'collect2'.
+    
+    my $retval;
+
+    my $collect2 = qx{gcc --print-prog-name=collect2};
+    
+    if (defined($collect2)) {
+        chomp($collect2);
+        my $linker = qx{$collect2 -v 2>&1};
+        if (defined($linker) && $linker =~ m{GNU ld}) {
+            $retval = "gnu";
+        } else {
+            $retval = "sun";
+        }
+    } else {
+        $retval = "gnu";
+    }
+    return $retval;
+}
+
+
+
+sub getLinker($$$$) {
+
+    my ($platform, $compiler, $baseLinkerR, $viaCompilerR) = @_;
+
+    my $baseLinker;
+
+    if ($platform eq "SOLARIS") {
+        $$viaCompilerR = $TRUE;
+
+        while (!defined($$baseLinkerR)) {
+            print("GNU linker or SUN linker?\n");
+            print("\n");
+
+            my $default;
+            
+            if ($compiler eq "gcc") {
+                $default = gccLinker();
+            } else {
+                $default = "sun";
+            }
+            my $response = prompt("sun or gnu", $default);
+            
+            if ($response eq "gnu") {
+                $$baseLinkerR = "GNU";
+            } elsif ($response eq "sun") {
+                $$baseLinkerR = "SUN";
+            } else {
+                print("'$response' isn't one of the choices.  \n" .
+                      "You must choose 'sun' or 'gnu'.\n");
+            }
+            print("\n");
+        }
+    } else {
+        $$viaCompilerR = $TRUE;
+        $$baseLinkerR = "?";
+    }
+}
+
+
+
+sub libSuffix($) {
+    my ($platform) = @_;
+#-----------------------------------------------------------------------------
+#  Return the traditional suffix for the link-time library on platform
+#  type $platform.
+#
+#  Note that this information is used mainly for cosmetic purposes in this
+#  this program and the Netpbm build, because the build typically removes
+#  any suffix and uses link options such as "-ltiff" to link the library.
+#  This leaves it up to the linker to supply the actual suffix.
+#-----------------------------------------------------------------------------
+    my $suffix;
+
+    if ($platform eq "windows") {
+        $suffix = ".a";
+    } else {
+        $suffix = ".so";
+    }
+}
+
+
+
+sub getLibTypes($$$$$$$$) {
+    my ($platform, $subplatform, $default_target,
+        $netpbmlibtypeR, $netpbmlibsuffixR, $shlibprefixlistR,
+        $willBuildSharedR, $staticlib_tooR) = @_;
+
+    print("Do you want libnetpbm to be statically linked or shared?\n");
+    print("\n");
+
+    my $default = ($default_target eq "merge") ? "static" : "shared";
+
+    my ($netpbmlibtype, $netpbmlibsuffix, $shlibprefixlist, 
+        $willBuildShared, $staticlib_too);
+    
+    my $response = prompt("static or shared", $default);
+    
+    if ($response eq "shared") {
+        $willBuildShared = $TRUE;
+        if ($platform eq "WINDOWS") {
+            $netpbmlibtype = "dll";
+            $netpbmlibsuffix = "dll";
+            if ($subplatform eq "cygwin") {
+                $shlibprefixlist = "cyg lib";
+            } 
+        } elsif ($platform eq "DARWIN") {
+            $netpbmlibtype = "dylib";
+            $netpbmlibsuffix = "dylib";
+        } else {
+	    if ($platform eq "IRIX") {
+		$netpbmlibtype = "irixshared";
+	    } else {
+		$netpbmlibtype = "unixshared";
+	    }
+            if ($platform eq "AIX") {
+                $netpbmlibsuffix = "a";
+            } elsif ($platform eq "HP-UX") {
+                $netpbmlibsuffix = "sl";
+            } else {
+                $netpbmlibsuffix = "so";
+            }
+        }
+    } elsif ($response eq "static") {
+        $willBuildShared = $FALSE;
+        $netpbmlibtype = "unixstatic";
+        $netpbmlibsuffix = "a";
+        # targets, but needed for building
+        # libopt 
+    } else {
+        print("'$response' isn't one of the choices.  \n" .
+              "You must choose 'static' or 'shared'.\n");
+        exit 12;
+    }
+
+    print("\n");
+
+    # Note that we can't do both a static and shared library for AIX, because
+    # they both have the same name: libnetpbm.a.
+    
+    if (($netpbmlibtype eq "unixshared" or 
+         $netpbmlibtype eq "irixshared" or 
+         $netpbmlibtype eq "dll") and $netpbmlibsuffix ne "a") {
+        print("Do you want to build static libraries too (for linking to \n");
+        print("programs not in the Netpbm package?\n");
+        print("\n");
+        
+        my $default = "y";
+        
+        my $response = prompt("(y)es or (n)o", $default);
+        
+        if (uc($response) =~ /^(Y|YES)$/)  {
+            $staticlib_too = "y";
+        } elsif (uc($response) =~ /^(N|NO)$/)  {
+            $staticlib_too = "n";
+        } else {
+            print("'$response' isn't one of the choices.  \n" .
+              "You must choose 'yes' or 'no' (or 'y' or 'n').\n");
+            exit 12;
+        }
+    } else {
+        $staticlib_too = "n";
+    }
+    print("\n");
+
+    $$netpbmlibtypeR   = $netpbmlibtype;
+    $$netpbmlibsuffixR = $netpbmlibsuffix;
+    $$shlibprefixlistR = $shlibprefixlist;
+    $$willBuildSharedR = $willBuildShared;
+    $$staticlib_tooR   = $staticlib_too;
+}
+
+
+
+sub inttypesDefault() {
+
+    my $retval;
+
+    if (defined($testCc)) {
+
+        print("(Doing test compiles to choose a default for you -- " .
+              "ignore errors)\n");
+
+        my $cflags = testCflags($FALSE);
+
+        my $works;
+
+        # We saw a system (Irix 5.3 with native IDO, December 2005) on
+        # which sys/types.h defines uint32_t, but not int32_t and other
+        # similar types.  We saw a Mac OS X system (January 2006) on which
+        # sys/types sometimes defines uint32_t, but sometimes doesn't.
+        # So we make those last resorts.
+
+        # int_fastXXX_t are the least likely of all the types to be
+        # defined, so we look for that.
+
+        # Solaris 8, for example, has a <sys/inttypes.h> that defines
+        # int32_t and uint32_t, but nothing the defines int_fast32_t.
+
+        my @candidateList = ("<inttypes.h>", "<sys/inttypes.h>",
+                             "<types.h>", "<sys/types.h>");
+        
+        for (my $i = 0; $i < @candidateList && !$works; ++$i) {
+            my $candidate = $candidateList[$i];
+            my @cSourceCode = (
+                               "#include $candidate\n",
+                               "int_fast32_t testvar;\n"
+                               );
+            
+            testCompile($cflags, \@cSourceCode, \my $success);
+            
+            if ($success) {
+                $works = $candidate;
+            }
+        }
+        if ($works) {
+            $retval = $works;
+        } else {
+            testCompile($cflags, ["int_fast32_t testvar;"], \my $success);
+            if ($success) {
+                $retval = "NONE";
+            } else {
+                $retval = '"inttypes_netpbm.h"';
+            }
+        }
+        print("\n");
+    } else {
+        $retval = '<inttypes.h>';
+    }
+    return $retval;
+}
+
+
+
+sub getInttypes($) {
+    my ($inttypesHeaderFileR) = @_;
+
+    my $gotit;
+
+    print("What header file defines uint32_t, etc.?\n");
+    print("\n");
+
+    my $default = inttypesDefault();
+    
+    while (!$gotit) {
+        my $response = prompt("'#include' argument or NONE", $default);
+
+        if ($response eq "NONE") {
+            $$inttypesHeaderFileR = '';
+            $gotit = $TRUE;
+        } else {
+            if ($response !~ m{<.+>} &&
+                $response !~ m{".+"}) {
+                print("'$response' is not a legal argument of a C #include " .
+                      "statement.  It must be something in <> or \"\".\n");
+            } else {
+                $gotit = $TRUE;
+                $$inttypesHeaderFileR = $response;
+            }
+        }
+    }
+}
+
+
+sub getInt64($$) {
+
+    my ($inttypes_h, $haveInt64R) = @_;
+
+    if (defined($testCc)) {
+
+        print("(Doing test compiles to determine if you have int64 type -- " .
+              "ignore errors)\n");
+
+        my $cflags = testCflags($FALSE);
+
+        my $works;
+
+        my @cSourceCode = (
+                           "#include $inttypes_h\n",
+                           "int64_t testvar;\n"
+                           );
+            
+        testCompile($cflags, \@cSourceCode, \my $success);
+            
+        if ($success) {
+            print("You do.\n");
+            $$haveInt64R = 'Y';
+        } else {
+            print("You do not.  64-bit code won't be built.\n");
+            $$haveInt64R = 'N';
+        }
+        print("\n");
+    } else {
+        $$haveInt64R = "N";
+    }
+}
+
+
+
+# TODO: These should do test compiles to see if the headers are in the
+# default search path, both to create a default to offer and to issue a
+# warning after user has chosen.  Also test links to test the link library.
+
+# It looks like these should all be in the default search paths and were there
+# just to override defaults in Makefile.config.in.  Since Configure now
+# creates a default of "standard search path," I'm guessing we don't need
+# to set these anymore.
+
+sub getTiffLibrary($@) {
+
+    my ($platform, @suggestedHdrDir) = @_;
+
+    my $tifflib;
+    {
+        my $default = "libtiff" . libSuffix($platform);
+
+        print("What is your TIFF (graphics format) library?\n");
+        
+        my $response = prompt("library filename or 'none'", $default);
+        
+        if ($response ne "none") {
+            $tifflib = $response;
+        }
+        if (defined($tifflib) and $tifflib =~ m{/} and !-f($tifflib)) {
+            printf("WARNING: No regular file named '$tifflib' exists.\n");
+        }
+    }
+    my $tiffhdr_dir;
+    if (defined($tifflib)) {
+        my $default;
+
+        if (-d("/usr/include/tiff")) {
+            $default = "/usr/include/tiff";
+        } elsif (-d("/usr/include/libtiff")) {
+            $default = "/usr/include/libtiff";
+        } else {
+            $default = "default";
+        }
+        print("Where are the interface headers for it?\n");
+        
+        my $response = prompt("TIFF header directory", $default);
+        
+        if ($response ne "default") {
+            $tiffhdr_dir = $response;
+        }
+        if (defined($tiffhdr_dir) and !-d($tiffhdr_dir)) {
+            printf("WARNING: No directory named '$tiffhdr_dir' exists.\n");
+        }
+    }
+    return($tifflib, $tiffhdr_dir);
+}
+
+
+
+sub getJpegLibrary($@) {
+
+    my ($platform, @suggestedHdrDir) = @_;
+
+    my $jpeglib;
+    {
+        my $default = "libjpeg" . libSuffix($platform);
+
+        print("What is your JPEG (graphics format) library?\n");
+        
+        my $response = prompt("library filename or 'none'", $default);
+        
+        if ($response ne "none") {
+            $jpeglib = $response;
+        }
+    }
+    my $jpeghdr_dir;
+    if (defined($jpeglib)) {
+        my $default;
+
+        if (-d("/usr/include/jpeg")) {
+            $default = "/usr/include/jpeg";
+        } else {
+            $default = "default";
+        }
+        print("Where are the interface headers for it?\n");
+        
+        my $response = prompt("JPEG header directory", $default);
+        
+        if ($response ne "default") {
+            $jpeghdr_dir = $response;
+        }
+        if (defined($jpeghdr_dir) and !-d($jpeghdr_dir)) {
+            printf("WARNING: No directory named '$jpeghdr_dir' exists.\n");
+        }
+    }
+    return($jpeglib, $jpeghdr_dir);
+}
+
+
+
+sub getPngLibrary($@) {
+
+    my ($platform, @suggestedHdrDir) = @_;
+
+    my ($pnglib, $pnghdr_dir);
+
+    if (commandExists('libpng-config')) {
+        # We don't need to ask where Libpng is; there's a 'libpng-config'
+        # That tells exactly how to access it, and the make files will use
+        # that.
+    } else {
+        {
+            my $default = "libpng" . libSuffix($platform);
+
+            print("What is your PNG (graphics format) library?\n");
+            
+            my $response = prompt("library filename or 'none'", $default);
+            
+            if ($response ne "none") {
+                $pnglib = $response;
+            }
+        }
+        if (defined($pnglib)) {
+            my $default;
+
+            if (-d("/usr/include/png")) {
+                $default = "/usr/include/libpng";
+            } else {
+                $default = "default";
+            }
+            
+            print("Where are the interface headers for it?\n");
+            
+            my $response = prompt("PNG header directory", $default);
+
+            if ($response ne "default") {
+                $pnghdr_dir = $response;
+            }
+        }
+    }
+    return($pnglib, $pnghdr_dir);
+}
+
+
+
+sub getZLibrary($@) {
+
+    my ($platform, @suggestedHdrDir) = @_;
+
+    my ($zlib, $zhdr_dir);
+
+    {
+        my $default = "libz" . libSuffix($platform);
+
+        print("What is your Z (compression) library?\n");
+        
+        my $response = prompt("library filename or 'none'", $default);
+        
+        if ($response ne "none") {
+            $zlib = $response;
+        }
+    }
+    if (defined($zlib)) {
+        my $default;
+
+        if (-d("/usr/include/zlib")) {
+            $default = "/usr/include/zlib";
+        } else {
+            $default = "default";
+        }
+        
+        print("Where are the interface headers for it?\n");
+        
+        my $response = prompt("Z header directory", $default);
+        
+        if ($response ne "default") {
+            $zhdr_dir = $response;
+        }
+    }
+    return($zlib, $zhdr_dir);
+}
+
+
+
+sub getX11Library($@) {
+
+    my ($platform, @suggestedHdrDir) = @_;
+
+    my $x11lib;
+    {
+        my $default;
+
+        if (-d('/usr/link/X11')) {
+            $default = '/usr/link/X11/libX11' . libSuffix($platform);
+        } elsif (-d('/usr/lib/X11')) {
+            $default = '/usr/lib/libX11' . libSuffix($platform);
+        } else {
+            $default = "libX11" . libSuffix($platform);
+        }
+        print("What is your X11 (X client) library?\n");
+        
+        my $response = prompt("library filename or 'none'", $default);
+        
+        if ($response ne "none") {
+            $x11lib = $response;
+        }
+    }
+    my $x11hdr_dir;
+    if (defined($x11lib)) {
+        my $default;
+
+        $default = "default";
+
+        print("Where are the interface headers for it?\n");
+        
+        my $response = prompt("X11 header directory", $default);
+        
+        if ($response ne "default") {
+            $x11hdr_dir = $response;
+        }
+        if (defined($x11hdr_dir)) {
+            if (!-d($x11hdr_dir)) {
+                printf("WARNING: No directory named '$x11hdr_dir' exists.\n");
+            } elsif (!-d("$x11hdr_dir/X11")) {
+                printf("WARNING: Directory '$x11hdr_dir' does not contain " .
+                       "the requisite 'X11' subdirectory\n");
+            }
+        }
+    }
+    return($x11lib, $x11hdr_dir);
+}
+
+
+
+sub getLinuxsvgaLibrary($@) {
+
+    my ($platform, @suggestedHdrDir) = @_;
+
+    my ($svgalib, $svgalibhdr_dir);
+
+    if ($platform eq "GNU") {
+        {
+            my $default;
+
+            if (-d('/usr/link/svgalib')) {
+                $default = '/usr/link/svgalib/libvga.so';
+            } elsif (-d('/usr/lib/svgalib')) {
+                $default = '/usr/lib/svgalib/libvga.so';
+            } else {
+                $default = 'libvga.so';
+            }
+            
+            print("What is your Svgalib library?\n");
+            
+            my $response = prompt("library filename or 'none'", $default);
+            
+            if ($response ne "none") {
+                $svgalib = $response;
+            }
+        }
+    }
+    if (defined($svgalib)) {
+        my $default;
+        
+        if (-d('/usr/include/svgalib')) {
+            $default = '/usr/include/svgalib';
+        } else {
+            $default = "default";
+        }
+        print("Where are the interface headers for it?\n");
+        
+        my $response = prompt("Svgalib header directory", $default);
+        
+        if ($response ne "default") {
+            $svgalibhdr_dir = $response;
+        }
+        if (defined($svgalibhdr_dir)) {
+            if (!-d($svgalibhdr_dir)) {
+                printf("WARNING: No directory named " .
+                       "'$svgalibhdr_dir' exists.\n");
+            }
+        }
+    }
+    return($svgalib, $svgalibhdr_dir);
+}
+
+
+
+sub symlink_command() {
+
+    my $retval;
+
+    # Some Windows environments don't have symbolic links (or even a
+    # simulation via a "ln" command, but have a "cp" command which works
+    # in a pinch.  Some Windows environments have "ln", but it won't do
+    # symbolic links.
+    
+    if (commandExists("ln")) {
+        # We assume if Perl can do symbolic links, so can Ln, and vice
+        # versa.
+
+        my $symlink_exists = eval { symlink("",""); 1 };
+        
+        if ($symlink_exists) {
+            $retval = "ln -s";
+        } else {
+            $retval = "ln";
+        }
+    } elsif (commandExists("cp")) {
+        $retval = "cp";
+    } else {
+        # Well, maybe we just made a mistake.
+        $retval = "ln -s";
+    }
+    return $retval;
+}
+
+
+
+sub help() {
+
+    print("This is the Netpbm custom configuration program.  \n");
+    print("It is not GNU Configure.\n");
+    print("\n");
+    print("There is one optional argument to this program:  The " .
+          "name of the file to use as the basis for the Makefile.config " .
+          "file.  Default is 'Makefile.config.in'\n");
+    print("\n");
+    print("Otherwise, the program is interactive.\n");
+}
+
+
+
+sub gnuOptimizeOpt($) {
+    my ($gccCommandName) = @_;
+#-----------------------------------------------------------------------------
+#  Compute the -O compiler flag appropriate for a GNU system.  Ordinarily,
+#  this is just -O3.  But many popular GNU systems have a broken compiler
+#  that causes -O3 to generate incorrect code (symptom: pnmtojpeg --quality=95
+#  generates a syntax error message from shhopt).
+#-----------------------------------------------------------------------------
+# I don't know what are exactly the cases that Gcc is broken.  I know 
+# Red Hat 7.1 and 7.2 and Mandrake 8.2, running gcc 2.96.1, commonly have
+# the problem.  But it may be limited to a certain subrelease level or
+# CPU type or other environment.  People who have reported the problem have
+# reported that Gcc 3.0 doesn't have it.  Gcc 2.95.3 doesn't have it.
+
+# Note that automatic inlining happens at -O3 level, but there are some
+# subroutines in Netpbm marked for explicit inlining, and we need to disable
+# that inlining too, so we must go all the way down to -O0.
+
+    my @gccVerboseResp = `$gccCommandName --verbose 2>&1`;
+
+    my $brokenCompiler;
+    
+    if (@gccVerboseResp ==2) {
+        if ($gccVerboseResp[1] =~ m{gcc version 2.96}) {
+            $brokenCompiler = $TRUE;
+        } else {
+            $brokenCompiler = $FALSE;
+        }
+    } else {
+        $brokenCompiler = $FALSE;
+    }
+
+    my $oOpt;
+
+    if ($brokenCompiler) {
+        print("You appear to have a broken compiler which would produce \n");
+        print("incorrect code if requested to do inline optimization.\n");
+        print("Therefore, I am configuring the build to not do inline \n");
+        print("optimization.  This will make some Netpbm programs \n");
+        print("noticeably slower.  If I am wrong about your compiler, just\n");
+        print("edit Makefile.config and change -O0 to -O3 near the bottom.\n");
+        print("\n");
+        print("The problem is known to exist in the GNU Compiler \n");
+        print("release 2.96.  If you upgrade, you will not have this \n");
+        print("problem.\n");
+        print("---------------------------------------------\n");
+        print("\n");
+        $oOpt = "-O0";
+    } else {
+        $oOpt = "-O3";
+    }
+    return $oOpt;
+}
+
+
+
+sub needLocal($) {
+#-----------------------------------------------------------------------------
+#  Return wether or not /usr/local paths must be added to compiles and
+#  links.  In a properly configured system, those paths should be in
+#  the compiler and linker default search paths, e.g. the compiler
+#  should search /usr/local/include and then /usr/include without any
+#  -I options needed.  But we've seen some systems where it isn't.
+#
+#  Actually, I have doubts now as to whether these misconfigured systems
+#  really exist.  This subroutine was apparently always broken, because
+#  before 04.03.15, it had "netbsd", etc. in lower case.  So it always
+#  returned false.  I never had a complaint.  Plus, we had a bug in 
+#  Makefile.config.in wherein it wiped out the user's setting of the LDFLAGS
+#  environment variable.  This could explain /usr/local/lib not being in
+#  the path when it should have been.
+#
+#  So I've disabled this function; we'll see if we encounter any truly
+#  misconfigured systems.  04.03.15.
+#-----------------------------------------------------------------------------
+    my ($platform) = @_;
+
+    return $FALSE;  # See comments above.
+
+    my $needLocal;
+    
+    if ($platform eq "NETBSD" || $platform eq "OPENBSD" || 
+        $platform eq "FREEBSD") {
+        $needLocal = $TRUE;
+    } else {
+        $needLocal = $FALSE;
+    }
+    return $needLocal;
+}
+
+
+
+sub findProcessManagement($) {
+    my ($dontHaveProcessMgmtR) = @_;
+#-----------------------------------------------------------------------------
+#  Return $TRUE iff the system does not have <sys/wait.h> in its default
+#  search path.
+#-----------------------------------------------------------------------------
+    my $cflags = testCflags($FALSE);
+
+    my @cSourceCode = (
+                       "#include <sys/wait.h>\n",
+                       );
+    
+    testCompile($cflags, \@cSourceCode, \my $success);
+
+    if (!$success) {
+        print("Your system does not appear to have <sys/wait.h> in its " .
+              "standard compiler include path.  Therefore, we will build " .
+              "Netpbm with some function missing (e.g. the pm_system() " .
+              "function in libnetpbm and most of 'pamlookup'\n");
+        $$dontHaveProcessMgmtR = $TRUE;
+    } else {
+        $$dontHaveProcessMgmtR = $FALSE;
+    }
+}
+
+
+
+sub validateLibraries(@) {
+    my @libList = @_;
+#-----------------------------------------------------------------------------
+#  Check each library name in the list @libList for viability.
+#-----------------------------------------------------------------------------
+    foreach my $libname (@libList) {
+        if (defined($libname)) {
+            if ($libname =~ m{/} and !-f($libname)) {
+                print("WARNING: No regular file named '$libname' exists.\n");
+            } elsif (!($libname =~ m{ .* \. (so|a|sa|sl|dll|dylib) $ }x)) {
+                print("WARNING: The library name '$libname' does not have " .
+                      "a conventional suffix (.so, .a, .dll, etc.)\n");
+            }
+        }
+    }
+}
+
+
+
+sub warnJpegTiffDependency($$) {
+    my ($jpeglib, $tifflib) = @_;
+
+    if (defined($tifflib) && !defined($jpeglib)) {
+        print("WARNING: You say you have a Tiff library, " .
+              "but no Jpeg library.\n");
+        print("Sometimes the Tiff library prerequires the " .
+              "Jpeg library.  If \n");
+        print("that is the case on your system, you will " .
+              "have some links fail with\n");
+        print("missing 'jpeg...' symbols.  If so, rerun " .
+              "Configure and say you\n");
+        print("have no Tiff library either.\n");
+        print("\n");
+    }
+}
+
+
+
+sub testCompileJpeglibH($$) {
+    my ($cflags, $successR) = @_;
+#-----------------------------------------------------------------------------
+#  Do a test compile to see if we can see jpeglib.h.
+#-----------------------------------------------------------------------------
+    my @cSourceCode = (
+                       "#include <ctype.h>\n",
+                       "#include <stdio.h>\n",
+                       "#include <jpeglib.h>\n",
+                       );
+    
+    testCompile($cflags, \@cSourceCode, $successR);
+}
+
+
+
+sub testCompileJpegMarkerStruct($$) {
+    my ($cflags, $successR) = @_;
+#-----------------------------------------------------------------------------
+#  Do a test compile to see if struct jpeg_marker_struct is defined in 
+#  jpeglib.h.  Assume it is already established that the compiler works
+#  and can find jpeglib.h.
+#-----------------------------------------------------------------------------
+    my @cSourceCode = (
+                       "#include <ctype.h>\n",
+                       "#include <stdio.h>\n",
+                       "#include <jpeglib.h>\n",
+                       "struct jpeg_marker_struct test;\n",
+                       );
+
+    testCompile($cflags, \@cSourceCode, $successR);
+}
+
+
+
+sub printMissingHdrWarning($$) {
+
+    my ($name, $hdr_dir) = @_;
+
+    print("WARNING: You said the compile-time part of the $name library " .
+          "(the header\n");
+    print("files) is in ");
+
+    if (defined($hdr_dir)) {
+        print("directory '$hdr_dir', ");
+    } else {
+        print("the compiler's default search path, ");
+    }
+    print("but a test compile failed\n");
+    print("to confirm that.  If your configuration is exotic, the test " .
+          "compile might\n");
+    print("just be wrong, but otherwise the Netpbm build will fail.\n");
+    print("\n");
+    print("To fix this, either install the $name library there \n");
+    print("or re-run Configure and answer the question about the $name " .
+          "library\n");
+    print("differently.\n");
+    print("\n");
+}
+
+
+
+sub printOldJpegWarning() {
+    print("WARNING: Your JPEG library appears to be too old for Netpbm.\n");
+    print("We base this conclusion on the fact that jpeglib.h apparently\n");
+    print("does not define struct jpeg_marker_struct.\n");
+    print("If the JPEG library is not Independent Jpeg Group's Version 6b\n");
+    print("or better, the Netpbm build will fail when it attempts to build\n");
+    print("the parts that use the JPEG library.\n");
+    print("\n");
+    print("If your configuration is exotic, this test may just be wrong.\n");
+    print("Otherwise, either upgrade your JPEG library or re-run Configure\n");
+    print("and say you don't have a JPEG library.\n");
+    print("\n");
+}
+
+
+
+sub testJpegHdr($$) {
+
+    my ($needLocal, $jpeghdr_dir) = @_;
+
+    if (defined($testCc)) {
+
+        my $generalCflags = testCflags($needLocal);
+
+        my $jpegIOpt = $jpeghdr_dir ? "-I$jpeghdr_dir" : "";
+
+        testCompileJpeglibH("$generalCflags $jpegIOpt", \my $success);
+
+        if (!$success) {
+            print("\n");
+            printMissingHdrWarning("JPEG", $jpeghdr_dir);
+        } else {
+            # We can get to something named jpeglib.h, but maybe it's an old
+            # version we can't use.  Check it out.
+            testCompileJpegMarkerStruct("$generalCflags $jpegIOpt", 
+                                        \my $success);
+            if (!$success) {
+                print("\n");
+                printOldJpegWarning();
+            }
+        }
+    }
+}
+
+
+
+sub testCompileZlibH($$) {
+    my ($cflags, $successR) = @_;
+#-----------------------------------------------------------------------------
+#  Do a test compile to see if we can see zlib.h.
+#-----------------------------------------------------------------------------
+    my @cSourceCode = (
+                       "#include <zlib.h>\n",
+                       );
+    
+    testCompile($cflags, \@cSourceCode, $successR);
+}
+
+
+
+sub testCompilePngH($$) {
+    my ($cflags, $successR) = @_;
+#-----------------------------------------------------------------------------
+#  Do a test compile to see if we can see png.h, assuming we can see
+#  zlib.h, which png.h #includes.
+#-----------------------------------------------------------------------------
+    my @cSourceCode = (
+                       "#include <png.h>\n",
+                       );
+    
+    testCompile($cflags, \@cSourceCode, $successR);
+}
+
+
+
+sub testPngHdr($$$) {
+#-----------------------------------------------------------------------------
+#  Issue a warning if the compiler can't find png.h.
+#-----------------------------------------------------------------------------
+    my ($needLocal, $pnghdr_dir, $zhdr_dir) = @_;
+
+    if (defined($testCc)) {
+
+        my $generalCflags = testCflags($needLocal);
+
+        my $zlibIOpt = $zhdr_dir ? "-I$zhdr_dir" : "";
+
+        testCompileZlibH("$generalCflags $zlibIOpt", \my $success);
+        if (!$success) {
+            print("\n");
+            printMissingHdrWarning("Zlib", $zhdr_dir);
+        } else {
+            my $pngIOpt = $pnghdr_dir ? "-I$pnghdr_dir" : "";
+
+            testCompilePngH("$generalCflags $zlibIOpt $pngIOpt", 
+                            \my $success);
+
+            if (!$success) {
+                print("\n");
+                printMissingHdrWarning("PNG", $pnghdr_dir);
+            }
+        }
+    }
+}
+
+
+
+sub testLibpngConfig($) {
+    my ($needLocal) = @_;
+#-----------------------------------------------------------------------------
+#  Issue a warning if the instructions 'libpng-config' give for compiling
+#  with Libpng don't work.
+#-----------------------------------------------------------------------------
+    my $generalCflags = testCflags($needLocal);
+
+    my $pngCflags = qx{libpng-config --cflags};
+    chomp($pngCflags);
+
+    testCompilePngH("$generalCflags $pngCflags", \my $success);
+
+    if (!$success) {
+        print("\n");
+        print("Unable to compile a test PNG program using the compiler " .
+              "flags that libpng-config says to use: '$pngCflags'.\n");
+        print("This indicates that you have Libpng installed incorrectly.\n");
+        print("libpng-config gets installed as part of the Libpng package.\n");
+    } else {
+        
+
+
+    }
+}
+
+
+
+sub testConfiguration($$$$$$$) {
+
+    my ($needLocal, $jpeglib, $jpeghdr_dir,
+        $pnglib, $pnghdr_dir, $zlib, $zhdr_dir) = @_;
+
+    if (defined($jpeglib)) {
+        testJpegHdr($needLocal, $jpeghdr_dir);
+    }
+    if (defined($pnglib) && defined($zlib)) {
+        testPngHdr($needLocal, $pnghdr_dir, $zhdr_dir);
+    } elsif (commandExists('libpng-config')) {
+        testLibpngConfig($needLocal);
+    }
+
+    # TODO: We ought to validate other libraries too.  But it's not
+    # that important, because in the vast majority of cases where the
+    # user incorrectly identifies any library, it affects all the
+    # libraries and if the user can get a handle on the JPEG library
+    # problem, he will also solve problems with any other library.
+}
+
+
+
+#******************************************************************************
+#
+#  MAINLINE
+#
+#*****************************************************************************
+
+autoFlushStdout();
+
+my $configInPathArg;
+if (@ARGV > 0) {
+    if ($ARGV[0] =~ "^-") {
+        if ($ARGV[0] eq "--help") {
+            help();
+            exit(0);
+        } else {
+            die("Unrecognized option: $ARGV[0]");
+        }
+    } 
+    $configInPathArg = $ARGV[0];
+}
+
+if (stat("Makefile.config")) {
+    print("Discard existing Makefile.config?\n");
+    print("Y or N (N) ==> ");
+
+    my $answer = <STDIN>;
+    if (!defined($answer)) {
+        die("\nEnd of file on Standard Input");
+    }
+    chomp($answer);
+    if (uc($answer) ne "Y") {
+        print("Aborting at user request.\n");
+        exit(1);
+    }
+}
+
+print("\n");
+
+displayIntroduction();
+
+my ($platform, $subplatform) = getPlatform();
+
+print("\n");
+
+if ($platform eq "NONE") {
+    print("You will have to construct Makefile.config manually.  To do \n");
+    print("this, copy Makefile.config.in as Makefile.config, and then \n");
+    print("edit it.  Follow the instructions and examples in the file. \n");
+    print("Please report your results to the Netpbm maintainer so he \n");
+    print("can improve the configure program. \n");
+    exit;
+}
+
+getCompiler($platform, \my $compiler);
+getLinker($platform, $compiler, \my $baseLinker, \my $linkViaCompiler);
+
+chooseTestCompiler($compiler, \$testCc);
+
+my $netpbmlib_runtime_path;
+    # Undefined if the default from Makefile.config.in is acceptable.
+
+if ($platform eq "SOLARIS" or $platform eq "IRIX" or
+    $platform eq "DARWIN" or $platform eq "NETBSD" or
+    $platform eq "AMIGA") {
+    print("Where will the Netpbm shared library reside once installed?\n");
+    print("Enter 'default' if it will reside somewhere that the shared\n");
+    print("library loader will find it automatically.  Otherwise, \n");
+    print("this directory will get built into the Netpbm programs.\n");
+    print("\n");
+
+    my $default = "default";
+    my $response = prompt("Netpbm shared library directory", $default);
+
+    if ($response eq "default") {
+        $netpbmlib_runtime_path = "";
+    } else {
+        $netpbmlib_runtime_path = $response;
+    }
+}
+
+my $default_target;
+
+print("Do you want a regular build or a merge build?\n");
+print("If you don't know what this means, " .
+      "take the default or see doc/INSTALL\n");
+print("\n");
+
+{
+    my $default = "regular";
+    my $response = prompt("regular or merge", $default);
+    
+    if ($response eq "regular") {
+        $default_target = "nonmerge";
+    } elsif ($response eq "merge") {
+        $default_target = "merge";
+    } else {
+        print("'$response' isn't one of the choices.  \n" .
+              "You must choose 'regular' or 'merge'.\n");
+        exit 12;
+    }
+}
+
+print("\n");
+
+getLibTypes($platform, $subplatform, $default_target,
+            \my $netpbmlibtype, \my $netpbmlibsuffix, \my $shlibprefixlist,
+            \my $willBuildShared, \my $staticlib_too);
+
+
+getInttypes(\my $inttypesHeaderFile);
+
+getInt64($inttypesHeaderFile, \my $haveInt64);
+
+findProcessManagement(\my $dontHaveProcessMgmt);
+
+#******************************************************************************
+#
+#  FIND THE PREREQUISITE LIBRARIES
+#
+#*****************************************************************************
+
+print("\n\n");
+print("The following questions concern the subroutine libraries that are " .
+      "Netpbm\n");
+print("prerequisites.  Every library has a compile-time part (header " .
+      "files)\n");
+print("and a link-time part.  In the case of a shared library, these are " .
+      "both\n");
+print("part of the \"development\" component of the library, which may be " .
+      "separately\n");
+print("installable from the runtime shared library.  For each library, you " .
+      "must\n");
+print("give the filename of the link library.  If it is not in your " .
+      "linker's\n");
+print("default search path, give the absolute pathname of the file.  In " .
+      "addition,\n");
+print("you will be asked for the directory in which the library's interface " .
+      "headers\n");
+print("reside, and you can respond 'default' if they are in your compiler's " .
+      "default\n");
+print("search path.\n");
+print("\n");
+print("If you don't have the library on your system, you can enter 'none' " .
+      "as the\n");
+print("library filename and the builder will skip any part that requires " .
+      "that ");
+print("library.\n");
+print("\n");
+
+my ($jpeglib, $jpeghdr_dir) = getJpegLibrary($platform);
+print("\n");
+my ($tifflib, $tiffhdr_dir) = getTiffLibrary($platform, $jpeghdr_dir);
+print("\n");
+my ($pnglib, $pnghdr_dir)   = getPngLibrary($platform, 
+                                            $tiffhdr_dir, $jpeghdr_dir);
+print("\n");
+my ($zlib, $zhdr_dir)       = getZLibrary($platform, 
+                                          $pnghdr_dir,
+                                          $tiffhdr_dir,
+                                          $jpeghdr_dir);
+print("\n");
+my ($x11lib, $x11hdr_dir) = getX11Library($platform); 
+
+print("\n");
+my ($linuxsvgalib, $linuxsvgahdr_dir) = getLinuxsvgaLibrary($platform); 
+
+print("\n");
+
+# We should add the JBIG and URT libraries here too.  They're a little
+# more complicated because there are versions shipped with Netpbm.
+
+
+#******************************************************************************
+#
+#  CONFIGURE DOCUMENTATION
+#
+#*****************************************************************************
+
+print("What URL will you use for the main Netpbm documentation page?\n");
+print("This information does not get built into any programs or libraries.\n");
+print("It does not make anything actually install that web page.\n");
+print("It is just for including in legacy man pages.\n");
+print("\n");
+
+my $default = "http://netpbm.sourceforge.net/doc/";
+
+my $netpbm_docurl = prompt("Documentation URL", $default);
+
+print("\n");
+
+
+
+
+#******************************************************************************
+#
+#  VALIDATE THE CONFIGURATION USER HAS SELECTED
+#
+#*****************************************************************************
+
+validateLibraries($jpeglib, $tifflib, $pnglib, $zlib);
+
+warnJpegTiffDependency($jpeglib, $tifflib);
+
+testConfiguration(needLocal($platform), 
+                  $jpeglib, $jpeghdr_dir,
+                  $pnglib, $pnghdr_dir,
+                  $zlib, $zhdr_dir,
+                  );
+
+#******************************************************************************
+#
+#  FIND THE NETPBM SOURCE TREE AND INITIALIZE BUILD TREE
+#
+#*****************************************************************************
+
+my $defaultConfigInPath;
+
+if (-f("GNUmakefile")) {
+    # He's apparently running us in the source tree or an already set up
+    # build directory.
+    $defaultConfigInPath = "Makefile.config.in";
+} else {
+    my $srcdir;
+    my $done;
+
+    $done = $FALSE;
+    while (!$done) {
+        print("Where is the Netpbm source code?\n");
+
+        $srcdir = prompt("Netpbm source directory", 
+                         abs_path(dirname($0) . "/.."));
+
+        if (-f("$srcdir/GNUmakefile")) {
+            $done = $TRUE;
+        } else {
+            print("That doesn't appear to contain Netpbm source code.\n");
+            print("There is no file named 'GNUmakefile' in it.\n");
+            print("\n");
+        }    
+    }
+    unlink("GNUmakefile");
+    symlink("$srcdir/GNUmakefile", "GNUmakefile");
+    unlink("Makefile");
+    symlink("$srcdir/Makefile", "Makefile");
+
+    open(SRCDIR, ">Makefile.srcdir");
+    print(SRCDIR "SRCDIR = $srcdir\n");
+    close(SRCDIR);
+    
+    $defaultConfigInPath = "$srcdir/Makefile.config.in";
+}
+
+sub makeCompilerGcc($) {
+    my ($Makefile_configR) = @_;
+    my $compileCommand = 'gcc';
+    push(@{$Makefile_configR}, "CC = $compileCommand\n");
+    push(@{$Makefile_configR}, gnuCflags($compileCommand));
+}
+
+
+#******************************************************************************
+#
+#  BUILD Makefile.config
+#
+#*****************************************************************************
+
+sub gnuCflags($) {
+    my ($gccCommandName) = @_;
+
+    return("CFLAGS = " . gnuOptimizeOpt($gccCommandName) . " -ffast-math " .
+           " -pedantic -fno-common " . 
+           "-Wall -Wno-uninitialized -Wmissing-declarations -Wimplicit " .
+           "-Wwrite-strings -Wmissing-prototypes -Wundef\n");
+}
+
+my @Makefile_config;
+    # This is the complete Makefile.config contents.  We construct it here
+    # and ultimately write the whole thing out as Makefile.config.
+
+# First, we just read the 'Makefile.config.in' in
+
+my $configInPath;
+if (defined($configInPathArg)) {
+    $configInPath = $configInPathArg;
+} else {
+    $configInPath = $defaultConfigInPath;
+}
+open (CONFIG_IN,"<$configInPath") or
+    die("Unable to open file '$configInPath' for input.");
+
+@Makefile_config = <CONFIG_IN>;
+
+unshift(@Makefile_config, 
+        "####This file was automatically created by 'configure.'\n",
+        "####Many variables are set twice -- a generic setting, then \n",
+        "####a system-specific override at the bottom of the file.\n",
+        "####\n");
+
+close(CONFIG_IN);
+
+# Now, add the variable settings that override the default settings that are
+# done by the Makefile.config.in contents.
+
+push(@Makefile_config, "\n\n\n\n");
+push(@Makefile_config, "####Lines above were copied from Makefile.config.in " .
+     "by 'configure'.\n");
+push(@Makefile_config, "####Lines below were added by 'configure' based on " .
+     "the $platform platform.\n");
+if (defined($subplatform)) {
+    push(@Makefile_config, "####subplatform '$subplatform'\n");
+}
+
+push(@Makefile_config, "DEFAULT_TARGET = $default_target\n");
+
+push(@Makefile_config, "NETPBMLIBTYPE=$netpbmlibtype\n");
+push(@Makefile_config, "NETPBMLIBSUFFIX=$netpbmlibsuffix\n");
+if (defined($shlibprefixlist)) {
+    push(@Makefile_config, "SHLIBPREFIXLIST=$shlibprefixlist\n");
+}
+push(@Makefile_config, "STATICLIB_TOO=$staticlib_too\n");
+
+if (defined($netpbmlib_runtime_path)) {
+    push(@Makefile_config, "NETPBMLIB_RUNTIME_PATH=$netpbmlib_runtime_path\n");
+}
+
+if ($platform eq "GNU") {
+    my $compileCommand;
+    if (!commandExists("cc") && commandExists("gcc")) {
+        $compileCommand = "gcc";
+        push(@Makefile_config, "CC = $compileCommand\n");
+    } else {
+        $compileCommand = "cc";
+    }
+    push(@Makefile_config, gnuCflags($compileCommand));
+# The merged programs have a main_XXX subroutine instead of main(),
+# which would cause a warning with -Wmissing-declarations or 
+# -Wmissing-prototypes.
+    push(@Makefile_config, "CFLAGS_MERGE = " .
+         "-Wno-missing-declarations -Wno-missing-prototypes\n");
+    push(@Makefile_config, "LDRELOC = ld --reloc\n");
+    push(@Makefile_config, "LINKER_CAN_DO_EXPLICIT_LIBRARY=Y\n");
+} elsif ($platform eq "SOLARIS") {
+    push(@Makefile_config, 'LDSHLIB = -Wl,-Bdynamic,-G,-h,$(SONAME)', "\n");
+
+    push(@Makefile_config, 'NEED_RUNTIME_PATH = Y', "\n");
+    if ($compiler eq "cc") {
+        push(@Makefile_config, "CFLAGS = -O\n");
+        push(@Makefile_config, "CFLAGS_SHLIB = -Kpic\n");
+    } else {
+        makeCompilerGcc(\@Makefile_config);
+    }
+    # Before Netpbm 10.20 (January 2004), we set this to -R for 
+    # $compiler == cc and -rpath otherwise.  But now we know that the GNU
+    # compiler can also invoke a linker that needs -R, so we're more flexible.
+    if ($baseLinker eq "GNU") {
+        push(@Makefile_config, "RPATHOPTNAME = -rpath\n");
+    } else {
+        push(@Makefile_config, "RPATHOPTNAME = -R\n");
+    }
+    push(@Makefile_config, "NETWORKLD = -lsocket -lnsl\n");
+} elsif ($platform eq "HP-UX") {
+    if ($compiler eq "gcc") {
+        makeCompilerGcc(\@Makefile_config);
+        push(@Makefile_config, "CFLAGS += -fPIC\n");
+        push(@Makefile_config, "LDSHLIB = -shared -fPIC\n");
+        push(@Makefile_config, 'LDFLAGS += -Wl,+b,/usr/pubsw/lib', "\n");
+    } else {
+        # We don't know what to do here.  We used to (before 10.20) just
+        # just assume the compiler was gcc.  We know that the gcc stuff
+        # above does NOT work for HP native compiler.
+    }
+} elsif ($platform eq "AIX") {
+    push(@Makefile_config, 'LDFLAGS = -L /usr/pubsw/lib', "\n");
+    if ($compiler eq "cc") {
+        # Yes, the -L option implies the runtime as well as linktime library
+        # search path.  There's no way to specify runtime path independently.
+        push(@Makefile_config, "RPATHOPTNAME = -L\n");
+        push(@Makefile_config, "LDSHLIB = -qmkshrobj\n");
+    } else {
+        makeCompilerGcc(\@Makefile_config);
+        push(@Makefile_config, "LDSHLIB = -shared\n");
+    }
+} elsif ($platform eq "TRU64") {
+#    push(@Makefile_config, "INSTALL = installbsd\n");
+    if ($compiler eq "cc") {
+        push(@Makefile_config, 'CFLAGS = -O2 -std1', "\n");
+        push(@Makefile_config, "LDFLAGS = -call_shared -oldstyle_liblookup " .
+             "-L/usr/local/lib\n");
+        push(@Makefile_config, "LDSHLIB = -shared -expect_unresolved \"*\"\n");
+    } else {
+        # We've never tested this.  This is just here to give a user a 
+        # headstart on submitting to us the necessary information.  2002.07.04.
+        push(@Makefile_config, "CC = gcc\n");
+        push(@Makefile_config, 'CFLAGS = -O3', "\n");
+        push(@Makefile_config, "LDSHLIB = -shared\n");
+    }
+    # Between May 2000 and July 2003, we had -DLONG_32 in these options.
+    # We took it out because it generated bad code for a TRU64 user in
+    # July 2003 whose system has 64 bit long and 32 bit int.  It affects
+    # only Ppmtompeg and it isn't clear that using long instead of int is
+    # ever right anyway.
+
+    push(@Makefile_config, "OMIT_NETWORK = y\n");
+    push(@Makefile_config, "LINKER_CAN_DO_EXPLICIT_LIBRARY=Y\n");
+} elsif ($platform eq "IRIX") {
+#    push(@Makefile_config, "INSTALL = install\n");
+    push(@Makefile_config, "MANPAGE_FORMAT = cat\n");
+    push(@Makefile_config, "RANLIB = true\n");
+    push(@Makefile_config, "CFLAGS = -n32 -O3 -fullwarn\n");
+    push(@Makefile_config, "LDFLAGS = -n32\n");
+    push(@Makefile_config, "LDSHLIB = -shared -n32\n");
+} elsif ($platform eq "WINDOWS") {
+    if ($subplatform eq "cygwin") {
+        makeCompilerGcc(\@Makefile_config);
+    }
+    push(@Makefile_config, "EXE = .exe\n");
+    push(@Makefile_config, "OMIT_NETWORK = y\n");
+#    # Though it may not have the link as "ginstall", "install" in a Windows
+#    # Unix environment is usually GNU install.
+#    my $ginstall_result = `ginstall --version 2>/dev/null`;
+#    if (!$ginstall_result) {
+#        # System doesn't have 'ginstall', so use 'install' instead.
+#        push(@Makefile_config, "INSTALL = install\n");
+#    }
+    push(@Makefile_config, 'SYMLINK = ', symlink_command(), "\n");
+    push(@Makefile_config, 'DLLVER=$(NETPBM_MAJOR_RELEASE)', "\n");
+    push(@Makefile_config, "LDSHLIB = " . 
+         '-shared -Wl,--image-base=0x10000000 -Wl,--enable-auto-import', "\n");
+} elsif ($platform eq "BEOS") {
+    push(@Makefile_config, "LDSHLIB = -nostart\n");
+} elsif ($platform eq "NETBSD") {
+    push(@Makefile_config, 'CFLAGS_SHLIB = -fpic', "\n");
+} elsif ($platform eq "OPENBSD") {
+    # vedge@vedge.com.ar says on 2001.04.29 that there are a ton of 
+    # undefined symbols in the Fiasco stuff on OpenBSD.  So we'll just
+    # cut it out of the build until someone feels like fixing it.
+    push(@Makefile_config, "BUILD_FIASCO = N\n");
+} elsif ($platform eq "FREEBSD") {
+} elsif ($platform eq "AMIGA") {
+    push(@Makefile_config, "CFLAGS = -m68020-60 -ffast-math -mstackextend\n");
+} elsif ($platform eq "UNIXWARE") {
+    # Nothing to do.
+} elsif ($platform eq "SCO") {
+    # Got this from "John H. DuBois III" <spcecdt@armory.com> 2002.09.27:
+    push(@Makefile_config, "RANLIB = true\n");
+    if ($compiler eq "cc") {
+        push(@Makefile_config, "CFLAGS = -O\n");
+        push(@Makefile_config, "CFLAGS_SHLIB = -O -K pic\n");
+        push(@Makefile_config, "LD_SHLIB = -G\n");
+        push(@Makefile_config, "SHLIB_CLIB =\n");
+    } else {
+        makeCompilerGcc(\@Makefile_config);
+    }
+    push(@Makefile_config, "CFLAGS_SHLIB = -fPIC\n");
+    push(@Makefile_config, "LDSHLIB = -shared\n"); 
+    push(@Makefile_config, "NETWORKLD = -lsocket -lresolve\n");
+} elsif ($platform eq "DARWIN") {
+    push(@Makefile_config, "CC = cc -no-cpp-precomp\n");
+    push(@Makefile_config, 'CFLAGS_SHLIB = -fno-common', "\n");
+    push(@Makefile_config, "LDSHLIB = ",
+         "-dynamiclib ",
+         '-install_name $(NETPBM_RUNTIME_PATH)/libnetpbm.$(MAJ).dylib', 
+         "\n");
+#    push(@Makefile_config, "INSTALL = install\n");
+} else {
+    die ("Internal error: invalid value for \$platform: '$platform'\n");
+}
+
+if (needLocal($platform)) {
+    push(@Makefile_config, "CFLAGS += -I/usr/local/include\n");
+    push(@Makefile_config, "LDFLAGS += -L/usr/local/lib\n");
+}
+
+if ($linkViaCompiler) {
+    push(@Makefile_config, "LINKERISCOMPILER = Y\n");
+}
+
+my $flex_result = `flex --version`;
+if (!$flex_result) {
+    # System doesn't have 'flex'.  Maybe 'lex' will work.  See the
+    # make rules for Thinkjettopbm for information on our experiences
+    # with Lexes besides Flex.
+
+    my $systemRc = system('lex </dev/null &>/dev/null');
+
+    if ($systemRc >> 8 == 127) {
+        print("\n");
+        print("You do not appear to have the 'flex' or 'lex' pattern \n");
+        print("matcher generator on your system, so we will not build \n");
+        print("programs that need it (Thinkjettopbm)\n");
+        
+        print("\n");
+        print("Press ENTER to continue.\n");
+        my $key = <STDIN>;
+        print("\n");
+
+        push(@Makefile_config, "LEX=\n");
+    } else {
+        print("\n");
+        print("Using 'lex' as the pattern matcher generator, " .
+              "since we cannot\n");
+        print("find 'flex' on your system.\n");
+        print("\n");
+
+        push(@Makefile_config, "LEX = lex\n"); 
+    }
+}
+
+if (defined($tiffhdr_dir)) {
+    push(@Makefile_config, "TIFFHDR_DIR = $tiffhdr_dir\n");
+}
+if (defined($tifflib)) {
+    push(@Makefile_config, "TIFFLIB = $tifflib\n");
+}
+
+if (defined($jpeghdr_dir)) {
+    push(@Makefile_config, "JPEGHDR_DIR = $jpeghdr_dir\n");
+}
+if (defined($jpeglib)) {
+    push(@Makefile_config, "JPEGLIB = $jpeglib\n");
+}
+
+if (defined($pnghdr_dir)) {
+    push(@Makefile_config, "PNGHDR_DIR = $pnghdr_dir\n");
+}
+if (defined($pnglib)) {
+    push(@Makefile_config, "PNGLIB = $pnglib\n");
+}
+
+if (defined($zhdr_dir)) {
+    push(@Makefile_config, "ZHDR_DIR = $zhdr_dir\n");
+}
+if (defined($zlib)) {
+    push(@Makefile_config, "ZLIB = $zlib\n");
+}
+
+if (defined($x11hdr_dir)) {
+    push(@Makefile_config, "X11HDR_DIR = $x11hdr_dir\n");
+}
+if (defined($x11lib)) {
+    push(@Makefile_config, "X11LIB = $x11lib\n");
+}
+
+if (defined($linuxsvgahdr_dir)) {
+    push(@Makefile_config, "LINUXSVGAHDR_DIR = $linuxsvgahdr_dir\n");
+}
+if (defined($linuxsvgalib)) {
+    push(@Makefile_config, "LINUXSVGALIB = $linuxsvgalib\n");
+}
+
+if (defined($netpbm_docurl)) {
+    push(@Makefile_config, "NETPBM_DOCURL = $netpbm_docurl\n");
+}
+
+if ($inttypesHeaderFile ne '<inttypes.h>') {
+    push(@Makefile_config, "INTTYPES_H = $inttypesHeaderFile\n");
+}
+
+if ($haveInt64 ne 'Y') {
+    push(@Makefile_config, "HAVE_INT64 = $haveInt64\n");
+}
+
+if ($dontHaveProcessMgmt) {
+    push(@Makefile_config, "DONT_HAVE_PROCESS_MGMT = Y\n");
+}
+
+#******************************************************************************
+#
+#  WRITE OUT THE FILE
+#
+#*****************************************************************************
+
+open(MAKEFILE_CONFIG, ">Makefile.config") or
+    die("Unable to open Makefile.config for writing in the current " .
+        "directory.");
+
+print MAKEFILE_CONFIG @Makefile_config;
+
+close(MAKEFILE_CONFIG) or
+    die("Error:  Close of Makefile.config failed.\n");
+
+print("\n");
+print("We have created the file 'Makefile.config'.  Note, however, that \n");
+print("we guessed a lot at your configuration and you may want to look \n");
+print("at Makefile.config and edit it to your requirements and taste \n");
+print("before doing the make.\n");
+print("\n");
+
+
+print("Now you may proceed with 'make'\n");
+print("\n");
+
+
+exit 0;          
diff --git a/buildtools/empty_depend b/buildtools/empty_depend
new file mode 100755
index 00000000..a4ba372c
--- /dev/null
+++ b/buildtools/empty_depend
@@ -0,0 +1,19 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+# This program turns every Makefile.depend file into an empty file,
+# so that dependencies in there can't make trouble for users trying to
+# build Netpbm.  They don't need dependencies anyway, since they're 
+# building everything and not modifying anything.
+
+# Developers should do 'make dep' to create real Makefile.depend files
+# before building
+
+my $mf_list = `find . -name Makefile.depend`;
+my @mf_list = split(/\s/, $mf_list);
+foreach (@mf_list) {
+    print "Emptying $_\n";
+    open(MF, ">$_");
+    close(MF);
+}
diff --git a/buildtools/endiangen b/buildtools/endiangen
new file mode 100755
index 00000000..8783096e
--- /dev/null
+++ b/buildtools/endiangen
Binary files differdiff --git a/buildtools/endiangen.c b/buildtools/endiangen.c
new file mode 100644
index 00000000..07560c10
--- /dev/null
+++ b/buildtools/endiangen.c
@@ -0,0 +1,97 @@
+/*----------------------------------------------------------------------------
+                                 endiangen
+------------------------------------------------------------------------------
+  This is a program to create C code that declares the endianness of the
+  machine on which it is run.
+
+  It generates something like this:
+
+    #ifndef LITTLE_ENDIAN
+    #define LITTLE_ENDIAN 1234
+    #endif
+
+    #ifndef BIG_ENDIAN
+    #define BIG_ENDIAN 4321
+    #endif
+       
+    #ifndef BYTE_ORDER
+    #define BYTE_ORDER LITTLE_ENDIAN
+    #endif
+
+    #define BITS_PER_WORD 32
+    #endif
+
+    
+  Really good code usually is not sensitive to endianness.  But fast,
+  not-so-good code often is.  The best way for code to determine
+  endianness is for it to do a runtime cast of an integer to an array
+  of characters and see where the bytes land.  But if speed requires
+  even sleazier code than that, use these macros.
+
+-----------------------------------------------------------------------------*/
+#include <stdio.h>
+#include <unistd.h>
+
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+
+enum endianness {ENDIAN_LITTLE, ENDIAN_BIG};
+
+static enum endianness
+byteOrder(void) {
+    
+    enum endianness retval;
+
+    union {
+        unsigned char arrayval[2];
+        unsigned short numval;
+    } testunion;
+
+    testunion.numval = 3;
+
+    if (testunion.arrayval[0] == 3)
+        retval = ENDIAN_LITTLE;
+    else
+        retval = ENDIAN_BIG;
+
+    return retval;
+}
+
+
+
+static unsigned int
+bitsPerWord(void) {
+
+    return MAX(sizeof(long), sizeof(int)) * 8;
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    printf("/* This was generated by the program 'endiangen' */\n");
+    printf("\n");
+    printf("/* LITTLE_ENDIAN, BIG_ENDIAN, and BYTE_ORDER "
+           "may come from the C library\n");
+    printf("via ctype.h. */\n");
+    printf("#include <ctype.h>\n");
+    printf("#ifndef LITTLE_ENDIAN\n");
+    printf("#define LITTLE_ENDIAN 1234\n");
+    printf("#endif\n");
+    printf("#ifndef BIG_ENDIAN\n");
+    printf("#define BIG_ENDIAN 4321\n");
+    printf("#endif\n");
+    printf("\n");
+    printf("#ifndef BYTE_ORDER\n");
+    printf("#define BYTE_ORDER %s\n", 
+           byteOrder() == ENDIAN_LITTLE ? "LITTLE_ENDIAN" : "BIG_ENDIAN");
+    printf("#endif\n");
+    printf("\n");
+    printf("#define BITS_PER_WORD %u\n", bitsPerWord());
+
+    return 0;
+}
+
+
+
+
diff --git a/buildtools/install.sh b/buildtools/install.sh
new file mode 100755
index 00000000..cd324f3d
--- /dev/null
+++ b/buildtools/install.sh
@@ -0,0 +1,255 @@
+#! /bin/sh
+#
+# install - install a program, script, or datafile
+# This comes from X11R5 (mit/util/scripts/install.sh).
+#
+# Copyright 1991 by the Massachusetts Institute of Technology
+#
+# Permission to use, copy, modify, distribute, and sell this software and its
+# documentation for any purpose is hereby granted without fee, provided that
+# the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation, and that the name of M.I.T. not be used in advertising or
+# publicity pertaining to distribution of the software without specific,
+# written prior permission.  M.I.T. makes no representations about the
+# suitability of this software for any purpose.  It is provided "as is"
+# without express or implied warranty.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.
+#
+
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+transformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+    case $1 in
+	-c) instcmd="$cpprog"
+	    shift
+	    continue;;
+
+	-d) dir_arg=true
+	    shift
+	    continue;;
+
+	-m) chmodcmd="$chmodprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-o) chowncmd="$chownprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-g) chgrpcmd="$chgrpprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-s) stripcmd="$stripprog"
+	    shift
+	    continue;;
+
+	-t=*) transformarg=`echo $1 | sed 's/-t=//'`
+	    shift
+	    continue;;
+
+	-b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+	    shift
+	    continue;;
+
+	*)  if [ x"$src" = x ]
+	    then
+                if [ -f "$1.exe" ]
+                then
+		    src=$1.exe
+                else
+                    src=$1
+                fi
+	    else
+		# this colon is to work around a 386BSD /bin/sh bug
+		:
+		dst=$1
+	    fi
+	    shift
+	    continue;;
+    esac
+done
+
+if [ x"$src" = x ]
+then
+	echo "install:	no input file specified"
+	exit 1
+else
+	true
+fi
+
+if [ x"$dir_arg" != x ]; then
+	dst=$src
+	src=""
+	
+	if [ -d $dst ]; then
+		instcmd=:
+	else
+		instcmd=mkdir
+	fi
+else
+
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad 
+# if $src (and thus $dsttmp) contains '*'.
+
+	if [ -f $src -o -d $src ]
+	then
+		true
+	else
+		echo "install:  $src does not exist"
+		exit 1
+	fi
+	
+	if [ x"$dst" = x ]
+	then
+		echo "install:	no destination specified"
+		exit 1
+	else
+		true
+	fi
+
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
+
+	if [ -d $dst ]
+	then
+		dst="$dst"/`basename $src`
+	else
+		true
+	fi
+fi
+
+## this sed command emulates the dirname command
+dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+
+# Make sure that the destination directory exists.
+#  this part is taken from Noah Friedman's mkinstalldirs script
+
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS='	
+'
+IFS="${IFS-${defaultIFS}}"
+
+oIFS="${IFS}"
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS="${oIFS}"
+
+pathcomp=''
+
+while [ $# -ne 0 ] ; do
+	pathcomp="${pathcomp}${1}"
+	shift
+
+	if [ ! -d "${pathcomp}" ] ;
+        then
+		$mkdirprog "${pathcomp}"
+	else
+		true
+	fi
+
+	pathcomp="${pathcomp}/"
+done
+fi
+
+if [ x"$dir_arg" != x ]
+then
+	$doit $instcmd $dst &&
+
+	if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
+	if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
+	if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
+	if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
+else
+
+# If we're going to rename the final executable, determine the name now.
+
+	if [ x"$transformarg" = x ] 
+	then
+		dstfile=`basename $dst`
+	else
+		dstfile=`basename $dst $transformbasename | 
+			sed $transformarg`$transformbasename
+	fi
+
+# don't allow the sed command to completely eliminate the filename
+
+	if [ x"$dstfile" = x ] 
+	then
+		dstfile=`basename $dst`
+	else
+		true
+	fi
+
+# Make a temp file name in the proper directory.
+
+	dsttmp=$dstdir/#inst.$$#
+
+# Move or copy the file name to the temp name
+
+	$doit $instcmd $src $dsttmp &&
+
+	trap "rm -f ${dsttmp}" 0 &&
+
+# and set any options; do chmod last to preserve setuid bits
+
+# If any of these fail, we abort the whole thing.  If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
+
+	if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
+	if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
+	if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
+	if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
+
+# Now rename the file to the real destination.
+
+	$doit $rmcmd -f $dstdir/$dstfile &&
+	$doit $mvcmd $dsttmp $dstdir/$dstfile 
+
+fi &&
+
+
+exit 0
diff --git a/buildtools/installnetpbm.pl b/buildtools/installnetpbm.pl
new file mode 100755
index 00000000..db3f6200
--- /dev/null
+++ b/buildtools/installnetpbm.pl
@@ -0,0 +1,919 @@
+#!/usr/bin/perl -w
+
+require 5.000;
+
+#Note: mkdir() must have 2 arguments as late as 5.005.
+
+use strict;
+use English;
+use Fcntl;
+use File::Basename;
+
+my ($TRUE, $FALSE) = (1,0);
+
+my $cpCommand;
+#use vars qw($cpCommand);
+
+#******************************************************************************
+#
+#  SUBROUTINES
+#
+#*****************************************************************************
+
+sub autoFlushStdout() {
+    my $oldFh = select(STDOUT); 
+    $OUTPUT_AUTOFLUSH = $TRUE;
+    select($oldFh);
+}
+
+
+
+sub prompt($$) {
+
+    my ($prompt, $default) = @_;
+
+    print("$prompt ($default) ==> ");
+
+    my $response = <STDIN>;
+
+    chomp($response);
+    if ($response eq "") {
+        $response = $default;
+    }
+
+    return $response;
+}
+
+
+
+sub getPkgdir() {
+
+    my $pkgdir;
+
+    while (!$pkgdir) {
+    
+        print("Where is the install package you created with " .
+              "'make package'?\n");
+        my $default = "/tmp/netpbm";
+        
+        my $response = prompt("package directory", $default);
+
+        if (!-f("$response/pkginfo")) {
+            print("This does not appear to be a Netpbm install package. \n");
+            print("A file named $response/pkginfo does not exist.\n");
+            print("\n");
+        } else {
+            $pkgdir = $response;
+        }
+    }
+    print("\n");
+    return $pkgdir;
+}
+
+
+
+sub makePrefixDirectory($) {
+
+    my ($prefixDir) = @_;
+
+    if ($prefixDir ne "" and !-d($prefixDir)) {
+        print("No directory named '$prefixDir' exists.  Do you want " .
+              "to create it?\n");
+
+        my $done;
+        while (!$done) {
+            my $response = prompt("Y(es) or N(o)", "Y");
+            if (uc($response) eq "Y") {
+                my $success = mkdir($prefixDir, 0777);
+                if (!$success) {
+                print("Unable to create directory '$prefixDir'.  " .
+                      "Error is $ERRNO\n");
+            }
+                $done = $TRUE;
+            } elsif (uc($response) eq "N") {
+                $done = $TRUE;
+            } 
+        }
+    }
+}
+
+
+
+
+
+sub getPrefix() {
+
+    print("Enter the default prefix for installation locations.  " .
+          "I will use \n");
+    print("this in generating defaults for the following prompts to " .
+          "save you \n");
+    print("typing.  If you plan to spread Netpbm across your system, \n" .
+          "enter '/'.\n");
+    print("\n");
+
+    my $default;
+    if ($OSNAME eq "cygwin") {
+        $default = "/usr/local";
+    } elsif ($ENV{OSTYPE} && $ENV{OSTYPE} eq "msdosdjgpp") {
+        $default = "/dev/env/DJDIR";
+    } else {
+        $default = "/usr/local/netpbm";
+    }
+
+    my $response = prompt("install prefix", $default);
+
+    my $prefix;
+
+    # Remove possible trailing /
+    if (substr($response,-1,1) eq "/") {
+        $prefix = substr($response, 0, -1);
+    } else {
+        $prefix = $response;
+    }
+    print("\n");
+
+    makePrefixDirectory($prefix);
+
+    return $prefix;
+}
+
+
+
+sub getCpCommand() {
+#-----------------------------------------------------------------------------
+# compute the command + options need to do a recursive copy, preserving
+# symbolic links and file attributes.
+#-----------------------------------------------------------------------------
+    my $cpCommand;
+
+    # We definitely need more intelligence here, but we'll need input from
+    # users to do it.  Maybe we should just bundle GNU Cp with Netpbm as an
+    # install tool.  Maybe we should write a small recursive copy program
+    # that uses more invariant tools, like buildtools/install.sh does for
+    # single files.
+
+    if (`cp --version 2>/dev/null` =~ m/GNU/) {
+        # It's GNU Cp -- we have options galore, and they're readable.
+        $cpCommand = "cp --recursive --preserve --no-dereference";
+    } else {
+        # This works on Cp from "4th Berkeley Distribution", July 1994.
+        # Mac OSX has this.
+        # -R means recursive with no dereferencing of symlinks
+        # -p means preserve attributes
+        $cpCommand = "cp -R -p";
+    }
+    return($cpCommand);
+}
+
+
+
+sub getBinDir($) {
+
+    my ($prefix) = @_;
+
+    print("Where do you want the programs installed?\n");
+    print("\n");
+
+    my $binDir;
+
+    while (!$binDir) {
+        my $default = "$prefix/bin";
+
+        my $response = prompt("program directory", $default);
+        
+        if (-d($response)) {
+            $binDir = $response;
+        } else {
+            my $succeeded = mkdir($response, 0777);
+            
+            if (!$succeeded) {
+                print("Unable to create directory '$response'.  " .
+                      "Error=$ERRNO\n");
+            } else {
+                $binDir = $response;
+            }
+        }
+    }
+    print("\n");
+
+    return $binDir;
+}
+
+
+
+sub installProgram($$$) {
+
+    my ($pkgdir, $prefix, $bindirR) = @_;
+
+    my $binDir = getBinDir($prefix);
+
+    print("Installing programs...\n");
+
+    my $rc = system("$cpCommand $pkgdir/bin/* $binDir/");
+
+    if ($rc != 0) {
+        print("Copy of programs from $pkgdir/bin to $binDir failed.\n");
+        print("cp return code is $rc\n");
+    } else {
+        print("Done.\n");
+    }
+    $$bindirR = $binDir;
+}
+
+
+
+sub getLibDir($) {
+
+    my ($prefix) = @_;
+
+    print("Where do you want the shared library installed?\n");
+    print("\n");
+
+    my $libDir;
+
+    while (!$libDir) {
+        my $default = "$prefix/lib";
+
+        my $response = prompt("shared library directory", $default);
+        
+        if (-d($response)) {
+            $libDir = $response;
+        } else {
+            my $succeeded = mkdir($response, 0777);
+            
+            if (!$succeeded) {
+                print("Unable to create directory '$response'.  " .
+                      "Error=$ERRNO\n");
+            } else {
+                $libDir = $response;
+            }
+        }
+    }
+    print("\n");
+
+    return $libDir;
+}
+
+
+
+sub 
+execLdconfig() {
+#-----------------------------------------------------------------------------
+#  Run Ldconfig.  Try with the -X option first, and if that is an invalid
+#  option (which we have seen on an openBSD system), try it without -X.
+#
+#  -X means "don't create any symlinks."  Any symlinks required should be
+#  created as part of installing the library, so we don't need that function
+#  from Ldconfig.  And we want to tread as lightly as possible on the 
+#  system -- we don't want creating symlinks that have nothing to do with
+#  Netpbm to be a hidden side effect of installing Netpbm.
+#
+#  Note that this Ldconfig works only if the user installed the Netpbm
+#  library in a standard directory that Ldconfig searches.  Note that on
+#  OpenBSD, Ldconfig is hardcoded to search only /usr/lib ever.  We could
+#  also do 'ldconfig DIR' to scan the particular directory in which we
+#  installed the Netpbm library.  But 1) the effects of this would disappear
+#  the next time the user rebuilds the cache file; and 2) on OpenBSD, this
+#  causes the cache file to be rebuilt from ONLY that directory.  On OpenBSD,
+#  you can add the -m option to cause it to ADD the contents of DIR to the
+#  existing cache file.
+#  
+#-----------------------------------------------------------------------------
+# Implementation note:  We've seen varying completion codes and varying
+# error messages from different versions of Ldconfig when it fails.
+
+    my $ldconfigSucceeded;
+
+    my $ldconfigXResp = `ldconfig -X 2>&1`;
+
+    if (!defined($ldconfigXResp)) {
+        print("Unable to run Ldconfig.\n");
+        $ldconfigSucceeded = $FALSE;
+    } elsif ($ldconfigXResp eq "") {
+        $ldconfigSucceeded = $TRUE;
+    } elsif ($ldconfigXResp =~ m{usage}i) {
+        print("Trying Ldconfig again without the -X option...\n");
+
+        my $rc = system("ldconfig");
+        
+        $ldconfigSucceeded = ($rc == 0);
+    } else {
+        print($ldconfigXResp);
+        $ldconfigSucceeded = $FALSE;
+    }
+    
+    if ($ldconfigSucceeded) {
+        print("Ldconfig completed successfully.\n");
+    } else {
+        print("Ldconfig failed.  You will have to fix this later.\n");
+    }
+}
+
+
+
+sub
+doLdconfig() {
+#-----------------------------------------------------------------------------
+#  Run Ldconfig where appropriate.
+#-----------------------------------------------------------------------------
+    if ($OSNAME eq "linux" || system("ldconfig -? 2>/dev/null") != 127) {
+        # This is a system where Ldconfig makes sense
+
+        print("In order for the Netpbm shared library to be found when " .
+              "you invoke \n");
+        print("A Netpbm program, you must either set an environment " .
+              "variable to \n");
+        print("tell where to look for it, or you must put its location " .
+              "in the shared \n");
+        print("library location cache.  Do you want to run Ldconfig now " .
+              "to put the \n");
+        print("Netpbm shared library in the cache?  This works only if " .
+              "you have\n");
+        print("installed the library in a standard location.\n");
+        print("\n");
+        
+        my $done;
+
+        $done = $FALSE;
+
+        while (!$done) {
+            my $response = prompt("Y(es) or N(o)", "Y");
+
+            if (uc($response) eq "Y") {
+                execLdconfig();
+                $done = $TRUE;
+            } elsif (uc($response) eq "N") {
+                $done = $TRUE;
+            } else {
+                print("Invalid response.  Enter 'Y' or 'N'\n");
+            }
+        }
+    }
+}
+
+
+
+sub installSharedLib($$$) {
+
+    my ($pkgdir, $prefix, $libdirR) = @_;
+
+    if (-d("$pkgdir/lib")) {
+        my $libDir = getLibDir($prefix);
+
+        print("Installing shared libraries...\n");
+
+        my $rc = system("$cpCommand $pkgdir/lib/* $libDir/");
+
+        if ($rc != 0) {
+            print("Copy of libraries from $pkgdir/lib to $libDir failed.\n");
+            print("cp return code is $rc\n");
+        } else {
+            print("done.\n");
+            print("\n");
+            doLdconfig();
+        }
+        $$libdirR = $libDir;
+    } else {
+        print("You did not build a shared library, so I will not " .
+              "install one.\n");
+    }
+    print("\n");
+}
+
+
+
+sub getLinkDir($) {
+
+    my ($prefix) = @_;
+
+    print("Where do you want the static link library installed?\n");
+    print("\n");
+
+    my $linkDir;
+
+    while (!$linkDir) {
+        my $default = "$prefix/lib";
+
+        my $response = prompt("static library directory", $default);
+        
+        if (-d($response)) {
+            $linkDir = $response;
+        } else {
+            my $succeeded = mkdir($response, 0777);
+            
+            if (!$succeeded) {
+                print("Unable to create directory '$response'.  " .
+                      "Error=$ERRNO\n");
+            } else {
+                $linkDir = $response;
+            }
+        }
+    }
+    print("\n");
+
+    return $linkDir;
+}
+
+
+
+sub installStaticLib($$$) {
+
+    my ($pkgdir, $prefix, $linkdirR) = @_;
+
+    if (-d("$pkgdir/link")) {
+        my $linkDir = getLinkDir($prefix);
+
+        print("Installing link libraries.\n");
+
+        my $rc = system("$cpCommand $pkgdir/link/* $linkDir/");
+
+        if ($rc != 0) {
+            print("Copy of files from $pkgdir/link to $linkDir failed.\n");
+            print("cp return code is $rc\n");
+        } else {
+            print("done.\n");
+        }
+        $$linkdirR = $linkDir;
+    } else {
+        print("You did not build a static library, so I will not " .
+              "install one \n");
+    }
+}
+
+
+
+sub getDataDir($) {
+
+    my ($prefix) = @_;
+
+    print("Where do you want the data files installed?\n");
+    print("\n");
+
+    my $dataDir;
+
+    while (!$dataDir) {
+        my $default = "$prefix/lib";
+
+        my $response = prompt("data file directory", $default);
+        
+        if (-d($response)) {
+            $dataDir = $response;
+        } else {
+            my $succeeded = mkdir($response, 0777);
+            
+            if (!$succeeded) {
+                print("Unable to create directory '$response'.  " .
+                      "Error=$ERRNO\n");
+            } else {
+                $dataDir = $response;
+            }
+        }
+    }
+    print("\n");
+
+    return $dataDir;
+}
+
+
+
+sub getHdrDir($) {
+
+    my ($prefix) = @_;
+
+    print("Where do you want the library interface header files installed?\n");
+    print("\n");
+
+    my $hdrDir;
+
+    while (!$hdrDir) {
+        my $default = "$prefix/include";
+
+        my $response = prompt("header directory", $default);
+        
+        if (-d($response)) {
+            $hdrDir = $response;
+        } else {
+            my $succeeded = mkdir($response, 0777);
+            
+            if (!$succeeded) {
+                print("Unable to create directory '$response'.  " .
+                      "Error=$ERRNO\n");
+            } else {
+                $hdrDir = $response;
+            }
+        }
+    }
+    print("\n");
+
+    return $hdrDir;
+}
+
+
+
+sub installDataFile($$$) {
+
+    my ($pkgdir, $prefix, $datadirR) = @_;
+
+    my $dataDir = getDataDir($prefix);
+
+    print("Installing data files...\n");
+
+    my $rc = system("$cpCommand $pkgdir/misc/* $dataDir/");
+
+    if ($rc != 0) {
+        print("copy of data files from $pkgdir/misc to $dataDir " .
+              "failed.\n");
+        print("cp exit code is $rc\n");
+    } else {
+        $$datadirR = $dataDir;
+        print("done.\n");
+    }
+}
+
+
+
+sub installHeader($$$) {
+
+    my ($pkgdir, $prefix, $includedirR) = @_;
+
+    my $hdrDir = getHdrDir($prefix);
+
+    print("Installing interface header files...\n");
+
+    my $rc = system("$cpCommand $pkgdir/include/* $hdrDir/");
+
+    if ($rc != 0) {
+        print("copy of header files from $pkgdir/include to $hdrDir " .
+              "failed.\n");
+        print("cp exit code is $rc\n");
+    } else {
+        print("done.\n");
+    }
+    $$includedirR = $hdrDir;
+}
+
+
+
+sub getManDir($) {
+
+    my ($prefix) = @_;
+
+    print("Where do you want the man pages installed?\n");
+
+    print("\n");
+
+    my $manDir;
+
+    while (!$manDir) {
+        my $default = "$prefix/man";
+
+        my $response = prompt("man page directory", $default);
+
+        if (-d($response)) {
+            $manDir = $response;
+        } else {
+            my $succeeded = mkdir($response, 0777);
+            
+            if (!$succeeded) {
+                print("Unable to create directory '$response'.  " .
+                      "Error=$ERRNO\n");
+            } else {
+                $manDir = $response;
+            }
+        }
+    }
+    print("\n");
+
+    return $manDir;
+}
+
+
+
+sub removeObsoleteManPage($) {
+
+    my ($mandir) = @_;
+
+    unlink("$mandir/man1/pgmoil");
+    unlink("$mandir/man1/pgmnorm");
+    unlink("$mandir/man1/ppmtojpeg");
+    unlink("$mandir/man1/bmptoppm");
+    unlink("$mandir/man1/ppmtonorm");
+    unlink("$mandir/man1/ppmtouil");
+    unlink("$mandir/man1/pnmnoraw");
+    unlink("$mandir/man1/gemtopbm");
+    unlink("$mandir/man1/pnminterp");
+}
+
+
+
+sub tryToCreateManwebConf($) {
+
+    my ($manweb_conf_filename) = $@;
+
+    print("You don't have a /etc/manweb.conf, which is the " .
+          "configuration\n");
+    print("file for the 'manweb' program, which is a quick way to " .
+          "get to Netpbm\n");
+    print("documentation.  Would you like to create one now?\n");
+        
+    my $done;
+    
+    while (!$done) {
+        my $response = prompt("create /etc/manweb.conf", "Y");
+        
+        if (uc($response) eq "Y") {
+            my $successful = open(MANWEB_CONF, ">/etc/manweb.conf");
+            if (!$successful) {
+                print("Unable to create file /etc/manweb.conf.  " .
+                          "error = $ERRNO\n");
+            } else {
+                print(MANWEB_CONF "#Configuration file for Manweb\n");
+                print(MANWEB_CONF "webdir=/usr/man/web\n");
+                close(MANWEB_CONF);
+                $done = $TRUE;
+            }
+        } else {
+            $done = $TRUE;
+        }
+    }
+}
+
+
+
+sub getWebdir($) {
+    my ($manweb_conf_filename) = @_;
+#-----------------------------------------------------------------------------
+#  Return the value of the Manweb "web directory," as indicated by the
+#  Manweb configuration file $manweb_conf_filename.
+#
+#  If that file doesn't exist, or doesn't have a 'webdir' value, or
+#  the 'webdir' value is a chain of directories instead of just one,
+#  we return an undefined value.
+#-----------------------------------------------------------------------------
+    my $webdir;
+
+    my $success = open(MANWEB_CONF, "<$manweb_conf_filename");
+    if (!$success) {
+        print("Unable to open file '$manweb_conf_filename' for reading.  " .
+              "error is $ERRNO\n");
+    } else {
+        while (<MANWEB_CONF>) {
+            chomp();
+            if (/^\s*#/) {
+                #It's comment - ignore
+            } elsif (/^\s*$/) {
+                #It's a blank line - ignore
+            } elsif (/\s*(\S+)\s*=\s*(\S+)/) {
+                #It looks like "keyword=value"
+                my ($keyword, $value) = ($1, $2);
+                if ($keyword eq "webdir") {
+                    # We can't handle a multi-directory path; we're looking
+                    # only for a webdir statement naming a sole directory.
+                    if ($value !~ m{:}) {
+                        $webdir = $value;
+                    }
+                }
+            }
+        }
+        close(MANWEB_CONF);
+    }              
+
+    return $webdir
+}
+
+
+
+sub userWantsManwebSymlink($$) {
+
+    my ($webdir, $netpbmWebdir) = @_;
+
+    print("Your manweb.conf file says top level documentation " .
+          "is in $webdir, \n");
+    print("but you installed netpbm.url in $netpbmWebdir.\n");
+    print("Do you want to create a symlink in $webdir now?\n");
+
+    my $wants;
+    my $done;
+    
+    while (!$done) {
+        my $response = prompt("create symlink (Y/N)", "Y");
+        
+        if (uc($response) eq "Y") {
+            $wants = $TRUE;
+            $done = $TRUE;
+        } elsif (uc($response) eq "N") {
+            $wants = $FALSE;
+            $done = $TRUE;
+        }
+    }
+    return $wants;
+}
+
+
+
+sub makeInManwebPath($) {
+
+    my ($netpbmWebdir) = @_;
+
+    # Now we need /etc/manweb.conf to point to the directory in which we
+    # just installed netpbm.url.
+
+    if (!-f("/etc/manweb.conf")) {
+        tryToCreateManwebConf("/etc/manweb.conf");
+    }
+    if (-f("/etc/manweb.conf")) {
+        my $webdir = getWebdir("/etc/manweb.conf");
+        if (defined($webdir)) {
+            if ($webdir ne $netpbmWebdir) {
+                if (userWantsManwebSymlink($webdir, $netpbmWebdir)) {
+                    my $old = "$netpbmWebdir/netpbm.url";
+                    my $new = "$webdir/netpbm.url";
+                    mkdir($webdir, 0777);
+                    my $success = symlink($old, $new);
+                    if (!$success) {
+                        print("Failed to create symbolic link from $new to " .
+                              "$old.  Error is $ERRNO\n");
+                    }
+                }
+            }
+        }
+    }
+}
+
+
+
+sub installManPage($$$) {
+
+
+# Note: This installs the pointer man pages and the netpbm.url file for Manweb.
+
+    my ($pkgdir, $prefix, $mandirR) = @_;
+
+    my $manDir = getManDir($prefix);
+
+    print("Installing man pages...\n");
+
+    my $rc = system("$cpCommand $pkgdir/man/* $manDir/");
+
+    if ($rc != 0) {
+        print("copy of man pages from $pkgdir/man to $manDir failed.\n");
+        print("cp exit code is $rc\n");
+    } else {
+        print("done.\n");
+    }
+
+    print("\n");
+
+    removeObsoleteManPage($manDir);
+
+    makeInManwebPath("$manDir/web");
+    
+    $$mandirR = $manDir;
+}
+
+
+
+sub 
+processTemplate($$$$$$$$$) {
+    my ($templateR, $version, $bindir, $libdir, $linkdir, $datadir, 
+        $includedir, $mandir, $outputR) = @_;
+
+    my @output;
+
+    foreach (@{$templateR}) {
+        if (m{^@}) {
+            # Comment -- ignore it.
+        } else {
+            if (defined($version)) {
+                s/\@VERSION\@/$version/;
+            }
+            if (defined($bindir)) {
+                s/\@BINDIR@/$bindir/;
+            }
+            if (defined($libdir)) {
+                s/\@LIBDIR@/$libdir/;
+            }
+            if (defined($linkdir)) {
+                s/\@LINKDIR@/$linkdir/;
+            }
+            if (defined($datadir)) {
+                s/\@DATADIR@/$datadir/;
+            }
+            if (defined($includedir)) {
+                s/\@INCLUDEDIR@/$includedir/;
+            }
+            if (defined($mandir)) {
+                s/\@MANDIR@/$mandir/;
+            }
+            push(@output, $_);
+        }
+    }
+    $$outputR = \@output;
+}
+
+
+
+sub
+installConfig($$$$$$$) {
+    my ($pkgdir, 
+        $bindir, $libdir, $linkdir, $datadir, $includedir, $mandir) = @_;
+
+    my $error;
+
+    my $configTemplateFilename = dirname($0) . "/config_template";
+
+    my $templateOpened = open(TEMPLATE, "<", $configTemplateFilename);
+    if (!$templateOpened) {
+        $error = "Can't open template file '$configTemplateFilename'.\n";
+    } else {
+        my @template = <TEMPLATE>;
+
+        close(TEMPLATE);
+
+        my $versionOpened = open(VERSION, "<", "$pkgdir/VERSION");
+
+        my $version;
+        if (!$versionOpened) {
+            $error = "Unable to open $pkgdir/VERSION for reading.  " .
+                "Errno=$ERRNO\n";
+        } else {
+            $version = <VERSION>;
+            chomp($version);
+            close(VERSION);
+
+            processTemplate(\@template, $version, $bindir, $libdir,
+                            $linkdir, $datadir, $includedir, $mandir,
+                            \my $fileContentsR);
+
+            # TODO: Really, this ought to go in an independent directory,
+            # because you might want to have the Netpbm executables in
+            # some place not in the PATH and use this program, via the
+            # PATH, to find them.
+            
+            my $filename = "$bindir/netpbm-config";
+            
+            my $success = open(NETPBM_CONFIG, ">", $filename);
+            if ($success) {
+                chmod(0755, $filename);
+                foreach (@{$fileContentsR}) { print NETPBM_CONFIG; }
+                close(NETPBM_CONFIG);
+            } else {
+                $error = "Unable to open the file " .
+                    "'$filename' for writing.  Errno=$ERRNO\n";
+            }
+        }
+    }
+    if ($error) {
+        print(STDERR "Failed to create the Netpbm configuration program.  " .
+              "$error\n");
+    }
+}
+
+
+
+#******************************************************************************
+#
+#  MAINLINE
+#
+#*****************************************************************************
+
+autoFlushStdout();
+
+print("Welcome to the Netpbm install dialogue.  We will now proceed \n");
+print("to interactively install Netpbm on this system.\n");
+print("\n");
+print("You must have already built Netpbm and then packaged it for \n");
+print("installation by running 'make package'.  See the INSTALL file.\n");
+print("\n");
+
+my $pkgdir = getPkgdir();
+
+my $prefix = getPrefix();
+
+$cpCommand = getCpCommand();
+
+installProgram($pkgdir, $prefix, \my $bindir);
+print("\n");
+
+installSharedLib($pkgdir, $prefix, \my $libdir);
+print("\n");
+
+installStaticLib($pkgdir, $prefix, \my $linkdir);
+print("\n");
+
+installDataFile($pkgdir, $prefix, \my $datadir);
+print("\n");
+
+installHeader($pkgdir, $prefix, \my $includedir);
+print("\n");
+
+installManPage($pkgdir, $prefix, \my $mandir);
+print("\n");
+
+installConfig($pkgdir, 
+              $bindir, $libdir, $linkdir, $datadir, $includedir, $mandir);
+
+print("Installation is complete (except where previous error messages have\n");
+print("indicated otherwise).\n");
+
+exit(0);
diff --git a/buildtools/installosf b/buildtools/installosf
new file mode 100755
index 00000000..a4e5c262
--- /dev/null
+++ b/buildtools/installosf
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+# This program takes parameters as a Netpbm make file might pass to
+# its $(INSTALL) program and invokes OSF1 Install with the proper
+# parameters to effect what the make file wants.  If your system has
+# OSF1 Install on it, you can just set the INSTALL variable in
+# Makefile.config to "installosf" and 'make install' will work for
+# you.
+
+# Of course, you could also just install Ginstall and forget about this
+# program.
+
+PERMISSIONS=444
+while [ $# -gt 0 ]; do
+    if [ "$1" = "-c" ]; then x=x;
+    elif [ "$1" = "-s" ]; then x=x;
+    elif [ "$1" = "-m" ]; then 
+        shift
+        PERMISSIONS=$1
+    elif [ ${SOURCE_FILE}x == x ]; then
+        SOURCE_FILE=$1
+    else
+        TARGET_DIR=$1
+    fi
+    shift;
+done
+
+install -f $TARGET_DIR -m $PERMISSIONS $SOURCE_FILE
+
+
+
diff --git a/buildtools/libopt b/buildtools/libopt
new file mode 100755
index 00000000..6d84ce67
--- /dev/null
+++ b/buildtools/libopt
Binary files differdiff --git a/buildtools/libopt.c b/buildtools/libopt.c
new file mode 100644
index 00000000..132f05f2
--- /dev/null
+++ b/buildtools/libopt.c
@@ -0,0 +1,541 @@
+/*----------------------------------------------------------------------------
+                                 libopt
+------------------------------------------------------------------------------
+  This is a program to convert link library filepaths to linker options
+  that select them.  E.g. ../lib/libnetpbm.so becomes -L../lib -lnetpbm   .
+
+  Each argument is a library filepath.  The option string to identify
+  all of those library filepaths goes to Standard Output.
+
+  If there is no slash in the library filepath, we assume it is just a
+  filename to be searched for in the linker's default search path, and
+  generate a -l option, but no -L.
+
+  If an argument doesn't make sense as a library filespec, it is
+  copied verbatim, blank delimited, to the output string.
+
+  The "lib" part of the library name, which we call the prefix, may be
+  other than "lib".  The list of recognized values is compiled in as
+  the macro SHLIBPREFIXLIST (see below).
+  
+  There is no newline or null character or anything after the output.
+
+  If you specify the option "-runtime", the output includes a -R
+  option in addition, for every library after "-runtime" in the
+  arguments.  -R tells the buildtime linker to include the specified
+  library's directory in the search path that the runtime linker uses
+  to find dynamically linked libraries.
+
+  But if you compile this program with the EXPLICIT macro defined, and
+  a library filepath contains a slash, then it skips all that -L/-l
+  nonsense and just outputs the input library filepath verbatim (plus
+  any -R option).
+------------------------------------------------------------------------------
+  Why would you want to use this?
+
+  On some systems, the -L/-l output of this program has exactly the
+  same effect as the filepath input when used in the arguments to a
+  link command.  A GNU/Linux system, for example.  On others (Solaris,
+  for example), if you include /tmp/lib/libnetpbm.so in the link as a
+  link object, the executable gets built in such a way that the system
+  accesses the shared library /tmp/lib/libnetpbm.so at run time.  On the
+  other hand, if you instead put the options -L/tmp/lib -lnetpbm on the
+  link command, the executable gets built so that the system accesses
+  libnetpbm.so in its actual installed directory at runtime (that
+  location might be determined by a --rpath linker option or a
+  LD_LIBRARY_PATH environment variable at run time).
+
+  In a make file, it is nice to use the same variable as the
+  dependency of a rule that builds an executable and as the thing that
+  the rule's command uses to identify its input.  Here is an example
+  of using libopt for that:
+
+     NETPBMLIB=../lib/libnetpbm.so
+     ...
+     pbmmake: pbmmake.o $(PBMLIB)
+             ld -o pbmmake pbmmake.o `libopt $(PBMLIB)` --rpath=/lib/netpbm
+
+  Caveat: "-L../lib -lnetpbm" is NOT exactly the same as
+  "../pbm/libnetpbm.so" on any system.  All of the -l libraries are
+  searched for in all of the -L directories.  So you just might get a
+  different library with the -L/-l version than if you specify the
+  library file explicitly.
+
+  That's why you should compile with -DEXPLICIT if your linker can
+  handle explicit file names.
+
+-----------------------------------------------------------------------------*/
+#define _BSD_SOURCE 1      /* Make sure strdup() is in stdio.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#define MAX_PREFIXES 10
+
+/* Here's how to use SHLIBPREFIXLIST:  Use a -D compile option to pass in
+   a value appropriate for the platform on which you are linking libraries.
+
+   It's a blank-delimited list of prefixes that library names might
+   have.  "lib" is traditionally the only prefix, as in libc or
+   libnetpbm.  However, on Windows there is a convention of using
+   different prefixes to distinguish different co-existent versions of
+   the same library (kind of like a major number in some unices). 
+   E.g. the value "cyg lib" is appropriate for a Cygwin system.
+*/
+#ifndef SHLIBPREFIXLIST
+#  define SHLIBPREFIXLIST "lib"
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+typedef unsigned char bool;
+#ifndef TRUE
+#define TRUE (1)
+#endif
+#ifndef FALSE
+#define FALSE (0)
+#endif
+
+#ifdef DLLVERSTR
+static const char * dllverstr = DLLVERSTR;
+#else
+static const char * dllverstr = "";
+#endif
+
+bool const explicit = 
+#ifdef EXPLICIT
+TRUE
+#else
+FALSE
+#endif
+;
+
+static void
+strfree(const char * const arg) {
+    free((void*) arg);
+}
+
+
+static void
+parse_prefixlist(const char * const prefixlist, 
+                 char * parsed_prefixes[MAX_PREFIXES+1],
+                 bool * const errorP) {
+/*----------------------------------------------------------------------------
+   Given a space-separated list of tokens (suffixes), return an
+   argv-style array of pointers, each to a newly malloc'ed storage
+   area the prefix as a null-terminated string.  Return a null string
+   for the unused entries at the end of the array
+
+   We never return more than MAX_PREFIXES prefixes from the input, so
+   there is guaranteed always to be one null string at the end of the
+   array.
+
+   In case of error, return *errorP == TRUE and don't allocate any
+   storage.  Otherwise, return *errorP = FALSE.
+-----------------------------------------------------------------------------*/
+    char * prlist;
+
+    prlist = strdup(prefixlist);
+    if (prlist == NULL)
+        *errorP = TRUE;
+    else {
+        if (strlen(prlist) <= 0) 
+            *errorP = TRUE;
+        else {
+            /* NOTE: Mac OS X, at least, does not have strtok_r().
+               2001.09.24
+            */
+            char * token;
+            int num_tokens;
+            int i;
+            
+            for (i=0; i < MAX_PREFIXES + 1; i++) {
+                parsed_prefixes[i] = NULL;
+            }
+            num_tokens = 0;
+            token = strtok(prlist, " ");
+            *errorP = FALSE;  /* initial value */
+            while (token != NULL && num_tokens < MAX_PREFIXES && !*errorP) {
+                parsed_prefixes[num_tokens] = strdup (token);
+                if (parsed_prefixes[num_tokens] == NULL) 
+                    *errorP = TRUE;
+                num_tokens++;
+                token = strtok(NULL, " ");
+            }
+            for (i = num_tokens; i < MAX_PREFIXES + 1 && !*errorP;  i++) {
+                parsed_prefixes[i] = strdup("");
+                if (parsed_prefixes[i] == NULL) 
+                    *errorP = TRUE;
+            }
+        }
+        if (*errorP) {
+            /* Deallocate any array entries we successfully did */
+            int i;
+            for (i = 0; i < MAX_PREFIXES + 1; i++)
+                if (parsed_prefixes[i])
+                    free(parsed_prefixes[i]);
+        }
+        free(prlist);
+    }
+}
+
+
+
+static void
+parse_prefix(const char * const filename, 
+             bool * const prefix_good_p, unsigned int * const prefix_length_p,
+             bool * const error_p) {
+/*----------------------------------------------------------------------------
+   Find the library name prefix (e.g. "lib") in the library filename
+   'filename'.
+   
+   Return the length of the prefix, in characters, as *prefix_length_p.
+   (The prefix always starts at the beginning of the filename).
+
+   Iff we don't find a valid library name prefix, return *prefix_good_p
+   == FALSE.  
+
+   The list of valid prefixes is compiled in as the blank-delimited
+   string which is the value of the SHLIBPREFIXLIST macro.
+-----------------------------------------------------------------------------*/
+    char * shlibprefixlist[MAX_PREFIXES+1];
+        /* An argv-style array of prefix strings in the first entries, 
+           null strings in the later entries.  At most MAX_PREFIXES prefixes,
+           so at least one null string.
+        */
+    char * prefix;
+        /* The prefix that the filename actually
+           uses.  e.g. if shlibprefixlist = { "lib", "cyg", "", ... } and the
+           filename is "path/to/cyg<something>.<extension>", then 
+           prefix = "cyg".  String is in the same storage as pointed to by
+           shlibprefixlist (shlibprefixlist[1] in this example).
+        */
+    bool prefix_good;
+        /* The first part of the filename matched one of the prefixes
+           in shlibprefixlist[].
+        */
+    int prefix_length;
+    int i;
+
+    parse_prefixlist(SHLIBPREFIXLIST , shlibprefixlist, error_p);
+    if (!*error_p) {
+        if (strcmp(shlibprefixlist[0], "") == 0) {
+            fprintf(stderr, "libopt was compiled with an invalid value "
+                    "of the SHLIBPREFIX macro.  It seems to have no "
+                    "tokens.  SHLIBPREFIX = '%s'", 
+                    SHLIBPREFIXLIST);
+            exit(100);
+        }
+
+        i = 0;  /* start with the first entry in shlibprefixlist[] */
+        prefix_length = 0;  /* initial value */
+        prefix = shlibprefixlist[i];
+        prefix_good = FALSE;  /* initial value */
+        while ( (*prefix != '\0' ) && !prefix_good ) {
+            /* stop condition: shlibprefixlist has MAX_PREFIXES+1 entries.
+             * we only ever put tokens in the 0..MAX_PREFIXES-1 positions.
+             * Then, we fill DOWN from the MAX_PREFIXES position with '\0'
+             * so we insure that the shlibprefixlist array contains at 
+             * least one final '\0' string, but probably many '\0' 
+             * strings (depending on how many tokens there were).               
+             */
+            prefix_length = strlen(prefix);
+            if (strncmp(filename, prefix, prefix_length) == 0) {
+                prefix_good = TRUE;
+                /* at this point, prefix is pointing to the correct
+                 * entry, and prefix_length has the correct value.
+                 * When we bail out of the while loop because of the
+                 * !prefix_good clause, we can then use these 
+                 * vars (prefix, prefix_length) 
+                 */
+            } else {
+                prefix = shlibprefixlist[++i];
+            }
+        }
+        *prefix_length_p = prefix_length;
+        *prefix_good_p = prefix_good;
+        { 
+            int i;
+            for (i=0; i < MAX_PREFIXES + 1; i++) 
+                free (shlibprefixlist[i]);
+        }
+    }
+}
+
+
+
+static void
+parse_filename(const char *  const filename,
+               const char ** const libname_p,
+               bool *        const valid_library_p,
+               bool *        const static_p,
+               bool *        const error_p) {
+/*----------------------------------------------------------------------------
+   Extract the library name root component of the filename 'filename'.  This
+   is just a filename, not a whole pathname.
+
+   Return it in newly malloc'ed storage pointed to by '*libname_p'.
+   
+   E.g. for "libxyz.so", return "xyz".
+
+   return *valid_library_p == TRUE iff 'filename' validly names a library
+   that can be expressed in a -l linker option.
+
+   return *static_p == TRUE iff 'filename' indicates a static library.
+   (but undefined if *valid_library_p != TRUE).
+
+   return *error_p == TRUE iff some error such as out of memory prevents
+   parsing.
+
+   Do not allocate any memory if *error_p == TRUE or *valid_library_p == FALSE.
+-----------------------------------------------------------------------------*/
+    char *lastdot;  
+    /* Pointer to last period in 'filename'.  Null if none */
+    
+    /* We accept any period-delimited suffix as a library type suffix.
+       It's probably .so or .a, but is could be .kalamazoo for all we
+       care. (HOWEVER, the double-suffixed import lib used on 
+       cygwin (.dll.a) is NOT understood). 
+    */
+    char *p;
+
+    lastdot = strrchr(filename, '.');
+    if (lastdot == NULL) {
+        /* This filename doesn't have any suffix, so we don't understand
+           it as a library filename.
+        */
+        *valid_library_p = FALSE;
+        *error_p = FALSE;
+    } else {
+        unsigned int prefix_length;
+        bool prefix_good;
+
+        if (strcmp(lastdot, "a") == 0)
+            *static_p = TRUE;
+        else
+            *static_p = FALSE;
+
+        parse_prefix(filename, &prefix_good, &prefix_length, error_p);
+        if (!*error_p) {
+            if (!prefix_good) {
+                *valid_library_p = FALSE;
+            } else {
+                /* Extract everything between <prefix> and "." as 
+                   the library name root. 
+                */
+                char * libname;
+
+                libname = strdup(filename + prefix_length);
+                if (libname == NULL)
+                    *error_p = TRUE;
+                else {
+                    libname[lastdot - filename - prefix_length] = '\0';
+                    if (strlen(dllverstr) > 0) {
+                        p = strstr(libname, dllverstr);
+                        if (p) {
+                            if (libname + strlen(libname) 
+                                - strlen(dllverstr) == p) {
+                                *p = '\0';
+                            }
+                        }
+                    }
+                    if (strlen(libname) == 0) {
+                        *valid_library_p = FALSE;
+                        strfree(libname);
+                    } else
+                        *valid_library_p = TRUE;
+                }
+                *libname_p = libname;
+            }
+        }
+    }
+}   
+
+static void
+parse_filepath(const char *  const filepath,
+               const char ** const directory_p, 
+               const char ** const filename_p,
+               bool *        const error_p) {
+/*----------------------------------------------------------------------------
+   Extract the directory and filename components of the filepath 
+   'filepath' and return them in newly malloc'ed storage pointed to by
+   '*directory_p' and '*filename_p'.
+
+   If there is no directory component, return a null string for it.
+-----------------------------------------------------------------------------*/
+    char *directory;
+    char *lastslash; /* Pointer to last slash in 'filepath', or null if none */
+
+    lastslash = strrchr(filepath, '/');
+
+    if (lastslash == NULL) {
+        /* There's no directory component; the filename starts at the
+           beginning of the filepath 
+        */
+        *filename_p = strdup(filepath);
+        if (*filename_p == NULL)
+            *error_p = TRUE;
+        else {
+            directory = strdup("");
+            if (directory == NULL) {
+                *error_p = TRUE;
+                strfree(*filename_p);
+            } else
+                *error_p = FALSE;
+        }
+    } else {
+        /* Split the string at the slash we just found, into filename and 
+           directory 
+           */
+        *filename_p = strdup(lastslash+1);
+        if (*filename_p == NULL)
+            *error_p = TRUE;
+        else {
+            directory = strdup(filepath);
+            if (directory == NULL) {
+                *error_p = TRUE;
+                strfree(*filename_p);
+            } else {
+                *error_p = FALSE;
+                directory[lastslash - filepath] = '\0';
+            }
+        }
+    }
+    *directory_p = directory;
+}
+
+
+
+static void
+doOptions(const char *  const filepath, 
+          const char *  const directory,
+          const char *  const libname,
+          bool          const runtime,
+          bool          const explicit,
+          bool          const staticlib,
+          const char ** const optionsP) {
+
+    char * options;
+    char * linkopt;
+
+    if (strlen(directory) == 0) {
+        linkopt = malloc(strlen(libname) + 10);
+        sprintf(linkopt, "-l%s", libname);
+    } else {
+        if (explicit)
+            linkopt = strdup(filepath);
+        else {
+            linkopt = malloc(strlen(directory) + strlen(libname) + 10);
+            sprintf(linkopt, "-L%s -l%s", directory, libname);
+        }
+    }
+    if (runtime && !staticlib && strlen(directory) > 0) {
+        options = malloc(strlen(linkopt) + strlen(directory) + 10);
+        sprintf(options, "%s -R%s", linkopt, directory); 
+    } else
+        options = strdup(linkopt);
+    
+    strfree(linkopt);
+    
+    *optionsP = options;
+}
+
+
+
+static void
+processOneLibrary(const char *  const filepath, 
+                  bool          const runtime, 
+                  bool          const explicit,
+                  const char ** const optionsP,
+                  bool *        const errorP) {
+/*----------------------------------------------------------------------------
+   Process the library with filepath 'filepath'.  Return the resulting
+   linker option string as a newly malloced null-terminated string at
+   *optionsP.
+-----------------------------------------------------------------------------*/
+    const char *directory;
+        /* Directory component of 'filepath' */
+    const char *filename;
+        /* Filename component of 'filepath' */
+
+    parse_filepath(filepath, &directory, &filename, errorP);
+    if (!*errorP) {
+        const char *libname;
+            /* Library name component of 'filename'.  e.g. xyz in
+               libxyz.so 
+            */
+        bool valid_library;  
+            /* Our argument is a valid library filepath that can be
+               converted to -l/-L notation.  
+            */
+        bool staticlib;
+            /* Our argument appears to name a static library. */
+
+        parse_filename(filename, 
+                       &libname, &valid_library, &staticlib, errorP);
+        
+        if (!*errorP) {
+            if (valid_library) {
+                doOptions(filepath, directory, libname, 
+                          runtime, explicit, staticlib, optionsP);
+
+                strfree(libname);
+            } else
+                *optionsP = strdup(filepath);
+        }
+        strfree(directory); 
+        strfree(filename);
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    bool error;
+    bool runtime;  /* -runtime option has been seen */
+    bool quiet;    /* -quiet option has been seen */
+    int retval;
+    unsigned int arg;  /* Index into argv[] of argument we're processing */
+    char outputLine[1024];
+
+    strcpy(outputLine, "");  /* initial value */
+    runtime = FALSE;  /* initial value */
+    quiet = FALSE;   /* initial value */
+    error = FALSE;  /* no error yet */
+    for (arg = 1; arg < argc && !error; arg++) {
+        if (strcmp(argv[arg], "-runtime") == 0)
+            runtime = TRUE;
+        else if (strcmp(argv[arg], "-quiet") == 0)
+            quiet = TRUE;
+        else {
+            if (strlen(argv[arg]) > 200)
+                error = TRUE;
+            else {
+                const char * options;
+                processOneLibrary(argv[arg], runtime, explicit, 
+                                  &options, &error);
+                if (!error) {
+                    if (strlen(outputLine) + strlen(options) + 1 + 1 > 
+                        sizeof(outputLine))
+                        error = TRUE;
+                    else {
+                        strcat(outputLine, " ");
+                        strcat(outputLine, options);
+                    }
+                    strfree(options);
+                }
+            }
+        }
+    }
+    if (error) {
+        fprintf(stderr, "serious libopt error prevented parsing library "
+                "names.  Invalid input to libopt is NOT the problem.\n");
+        retval = 10;
+    } else {
+        fputs(outputLine, stdout);
+        retval = 0;
+    }
+    return retval;
+}
diff --git a/buildtools/make_merge.sh b/buildtools/make_merge.sh
new file mode 100755
index 00000000..9043ad05
--- /dev/null
+++ b/buildtools/make_merge.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+# This program is called by the make files.  It creates the merge.h
+# file which is included by netpbm.c.
+
+echo "/* File generated by make_merge.sh */"
+echo "/* in directory" `pwd` "*/"
+echo
+
+for MERGE_BINARY in $*; do
+    echo "TRY(\"${MERGE_BINARY}\", main_${MERGE_BINARY});"
+    done
diff --git a/buildtools/makecat b/buildtools/makecat
new file mode 100755
index 00000000..3a77a2a6
--- /dev/null
+++ b/buildtools/makecat
@@ -0,0 +1,18 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+my $TRUE=1; my $FALSE = 0;
+
+foreach my $htmlfilename (@ARGV) {
+    if ($htmlfilename =~ m{ (.*) \.html $ }x) {
+        my $basename = $1;
+
+        my $catfilename = "$basename.1";
+
+        print ("Converting $htmlfilename to $catfilename...\n");
+        system("lynx -dump $htmlfilename >$catfilename");
+    } else {
+        print ("Filename '$htmlfilename' doesn't end in 'html'.  Skipping.\n");
+    }
+}
diff --git a/buildtools/makeman b/buildtools/makeman
new file mode 100755
index 00000000..634a2c79
--- /dev/null
+++ b/buildtools/makeman
@@ -0,0 +1,333 @@
+#!/bin/env python
+#
+# makeman -- compile netpbm's stereotyped HTML to troff markup
+#
+# This approach works because we control the entire document universe 
+# this is going to convert and can reinforce useful stereotypes.
+#
+# The output of this tool uses cliches parseable by doclifter,
+# which should thus be able to recover all the semantic information
+# it looks like this thing is losing.
+#
+# Known bugs:
+#  * Ordered lists are smashed into unordered lists
+#
+# Limitations:
+#  * IMG tags are issued as .IMG preceded by a bolded caption containing
+#    the alt content.  This will only work if the page is formatted with
+#    mwww macros.
+#  * Loses summary information from tables.
+#  * Only permits one <HR> in the HTML, right before the index.
+#
+# Use the makeman: passthrough to insert format lines for tables.
+#
+# By Eric S. Raymond <esr@thyrsus.com>
+# Version 1.0, July 26 2004
+
+import os, sys, exceptions, re
+
+source = "netpbm documentation"
+section = 1
+
+warning = '''\
+.\" This man page was generated by the Netpbm tool 'makeman' from HTML source.
+.\" Do not hand-hack it!  If you have bug fixes or improvements, please find
+.\" the corresponding HTML page on the Netpbm website, generate a patch
+.\" against that, and send it to the Netpbm maintainer.
+'''
+
+class LiftException(exceptions.Exception):
+    def __init__(self, message, retval=1):
+        self.message = message
+        self.retval = retval
+
+def makeman(name, file, indoc):
+    "Transform a string representing an HTML document into man markup."
+    global section, sectmap
+    # Dot at left margin confuses troff.
+    # This program generates these,
+    indoc = indoc.replace("\n.", "\n@%@%@")
+    # Header-bashing
+    indoc = indoc.replace('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\n',"")
+    indoc = indoc.replace('<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">', "")
+    indoc = indoc.replace('<?xml version="1.1" encoding="iso-8859-1" ?>\n',"")
+    indoc = indoc.replace('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "DTD/xhtml11.dtd">', "")
+    indoc = indoc.replace('<html xmlns="http://www.w3.org/1999/xhtml">', "")
+    indoc = indoc.replace("<HEAD>", "").replace("</HEAD>", "")
+    indoc = indoc.replace("<head>", "").replace("</head>", "")
+    indoc = re.sub('(?i)<A HREF="#index">Table Of Contents</A>', "", indoc)
+    datematch = re.compile("Updated: (.*)\n")
+    match = datematch.search(indoc)
+    if match:
+        date = match.group(1)
+    else:
+        date = ""
+    indoc = datematch.sub("", indoc)
+    namematch = re.compile("<H1>(.*)</H1>", re.I)
+    match = namematch.search(indoc)
+    if match:
+        name = match.group(1)
+    else:
+        name = None
+    section = 1
+    meta = re.compile('(?i)<META NAME="manual_section" CONTENT="([0-9])">')
+    match = meta.search(indoc)
+    if match:
+        section = int(match.group(1))
+        indoc = meta.sub("", indoc)
+    else:
+        section = sectmap.get(name, 0)
+    indoc = namematch.sub("", indoc)
+    indoc = re.sub("(?i)<BODY[^>]*>", "", indoc)
+    indoc = re.sub("(?i)<HTML>", "", indoc)
+    # Remove more superfluous headers
+    titlematch = re.compile("<TITLE>(.*)</TITLE>\n+", re.I)
+    match = titlematch.search(indoc)
+    if match:
+        title = match.group(1)
+    else:
+        title = None
+    indoc = titlematch.sub("", indoc)
+    indoc = re.sub("(?i)\n*<BR>\n+", "\n", indoc)
+    indoc = ('.TH "%s" %d "%s" "%s"\n' % (title,section,date,source)) + indoc
+    # Literal layout
+    indoc = re.sub("(?i)\n *<PRE>", "\n.nf", indoc)
+    indoc = re.sub("(?i)\n *</PRE>", "\n.fi", indoc)
+    indoc = re.sub("(?i)\n *<BLOCKQUOTE>", "\n.nf", indoc)
+    indoc = re.sub("(?i)\n *</BLOCKQUOTE>", "\n.fi", indoc)
+    # Highlight processing
+    indoc = re.sub("(?i)<B>", r"\\fB", indoc)
+    indoc = re.sub("(?i)</B>", r"\\fP", indoc)
+    indoc = re.sub("(?i)<EM>", r"\\fI", indoc)
+    indoc = re.sub("(?i)</EM>", r"\\fP", indoc)
+    indoc = re.sub("(?i)<CITE>", r"\\fI", indoc)
+    indoc = re.sub("(?i)</CITE>", r"\\fP", indoc)
+    indoc = re.sub("(?i)<I>", r"\\fI", indoc)
+    indoc = re.sub("(?i)</I>", r"\\fP", indoc)
+    indoc = re.sub("(?i)<TT>", r"\\f(CW", indoc)
+    indoc = re.sub("(?i)</TT>", r"\\fP", indoc)
+    indoc = re.sub("(?i)<KBD>", r"\\f(CW", indoc)
+    indoc = re.sub("(?i)</KBD>", r"\\fP", indoc)
+    indoc = re.sub("(?i)<STRONG>", r"\\fB", indoc)
+    indoc = re.sub("(?i)</STRONG>", r"\\fP", indoc)
+    indoc = re.sub("(?i)<SUP>", r"\\u", indoc)
+    indoc = re.sub("(?i)</SUP>", r"\\d", indoc)
+    # Paragraph handling
+    indoc = re.sub("(?i)\n*<P>\n*", r"\n.PP\n", indoc)
+    indoc = re.sub("(?i)</P>", "", indoc)
+    lines = indoc.split("\n")
+    listdepth = 0
+    for i in range(len(lines)):
+        lowered = lines[i].lower()
+        if "<dl" in lowered or "<ol" in lowered or "<ul" in lowered:
+            listdepth += 1
+        if listdepth:
+            lines[i] = lines[i].replace(".PP", ".sp")
+        if "</dl>" in lowered or "</ol>" in lowered or "</ul>" in lowered:
+            listdepth -= 1
+    indoc = "\n".join(lines)
+    indoc = re.sub(r"\s*\.sp", "\n.sp", indoc)
+    # Format email addresses as italic
+    indoc = re.sub('(?i)<A[ \n]+HREF="mailto:[^>]+">([^<]+)</A>', r'\\fI\1\\fP', indoc)    
+    # Format manual crossreferences
+    def xrefmatch(match):
+        xrefto = match.group(1)
+        xrefsection = sectmap.get(xrefto, 1)
+        if xrefsection == 0:
+            return "\n.I " + xrefto
+        else:
+            return "\n.BR %s (%d)" % (xrefto, xrefsection)
+    indoc = re.sub(r'(?i)\n* *(?:\\fB)?<A[ \n]+HREF="[^>]+.html">([^<]+)</A>(?:\\fP)?',
+                   xrefmatch, indoc)
+    # Format URLs
+    def urlmatch(match):
+        url = match.group(1).replace('\n', ' ')
+        txt = match.group(2).replace('\n', ' ')
+        return "\n.UR %s\n%s\n.UE\n\\&" % (url, txt)
+    indoc = re.sub(r'(?i)\n*(?:&lt;)?<A[ \n]+HREF *= *"([^>]+)">([^<]+)</A>(?:&gt;)?',
+                  urlmatch, indoc)
+    # Turn some entities into harmless cookies
+    indoc = indoc.replace("&lt;", "@#!#@").replace("&gt;", "#@!@#").replace("&amp;", "#!@!@!#")
+    indoc = indoc.replace("&#215;", r"\(mu")
+    indoc = indoc.replace("&#174;", r"\*R")
+    # Turn anchors into .UN tags
+    indoc = re.sub('(?i)<A NAME *= *"#?([a-zA-Z][a-zA-Z0-9.-]+)">(?:&nbsp;)*</A>\s*', ".UN \\1\n", indoc)
+    # Strip off the index trailer
+    trailer = re.compile('<HR */*>.*', re.DOTALL | re.IGNORECASE)
+    indoc = re.sub(trailer, "", indoc)
+    # If there was no index trailer, we still need to strip these
+    indoc = indoc.replace("</BODY>", "").replace("</HTML>", "")
+    indoc = indoc.replace("</body>", "").replace("</html>", "")
+    # Recognize sections with IDs
+    indoc = re.sub('(?i)<H2><A (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</A></H2>',
+                   ".UN \\1\n.SH \\2", indoc)
+    indoc = re.sub('(?i)<H3><A (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</A></H3>',
+                   ".UN \\1\n.SS \\2", indoc)
+    indoc = re.sub('(?i)<H4><A (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</A></H4>',
+                   ".UN \\1\n.B \\2", indoc)
+    indoc = re.sub('(?i)<H2 (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</H2>',
+                   ".UN \\1\n.SH \\2", indoc)
+    indoc = re.sub('(?i)<H3 (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</H3>',
+                   ".UN \\1\n.SS \\2", indoc)
+    indoc = re.sub('(?i)<H4 (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</H4>',
+                   ".UN \\1\n.B \\2", indoc)
+    # Sections without IDs
+    indoc = re.sub('(?i)<H2>([^><]*)</H2>', ".SH \\1", indoc)
+    indoc = re.sub('(?i)<H3>([^><]*)</H3>', ".SS \\1", indoc)
+    indoc = re.sub('(?i)<H4>([^><]*)</H4>', ".B \\1", indoc)
+    # 
+    # Process definition lists -- just turn them into .TPs
+    indoc = re.sub("(?i) *<DL *(COMPACT)?>", "", indoc)
+    indoc = re.sub("(?i) *</DL>", "", indoc)
+    indoc = re.sub("(?i) *<DT>", ".TP\n", indoc)
+    indoc = re.sub("(?i) *</DT>", "", indoc)
+    indoc = re.sub("(?i)\n*<DD>\n*", "\n", indoc)
+    indoc = re.sub("(?i) *</DD>", "", indoc)
+    # Process unordered lists -- just turn them into .TPs
+    indoc = re.sub("(?i)</?[UO]L *(COMPACT)?>", "", indoc)
+    indoc = re.sub("(?i) *<LI>", ".IP \(bu\n", indoc)
+    indoc = re.sub("(?i) *</LI>", "", indoc)
+    # No-print tags
+    indoc = re.sub("<!--no_print-->.*", "", indoc)
+    # Passthrough
+    indoc = re.sub(r"<\?makeman (.*) \?>", r'\1', indoc)
+    # Comments
+    indoc = re.sub("<!--([^-])*-->", r'.\"\1', indoc)
+    # Image tags
+    indoc = re.sub(' *<img src="([^"]*)" alt="([^"]*)"( *[a-z]*="?[0-9]*"?)*>', ".B \\2\n.IMG -C \\1", indoc)
+    # Special characters
+    indoc = indoc.replace("&quot;", "'")
+    indoc = indoc.replace("&nbsp;", "\\ ")
+    # Tables
+    indoc = re.sub(' *<table[^>]*>.*', ".TS", indoc)
+    indoc = re.sub(" *</table>.*", ".TE", indoc)
+    # First the single-line case
+    indoc = re.sub("</td> *<td>", "\t", indoc)
+    indoc = re.sub("<tr> *<td>", "", indoc)
+    indoc = re.sub("</td> *</tr>", "", indoc)
+    # Then the multiline case
+    indoc = re.sub(r'\s*<t[hd][^>]*>([^<\n]*)</t[dh]>\s*', '\t\\1', indoc)
+    indoc = re.sub(r'\s*<t[hd][^>]*>([^<]*)</t[dh]>\s*', '\tT{\n\\1T}', indoc)
+    indoc = indoc.replace("\n\\&T}", "\nT}")
+    indoc = re.sub(" *</tr>", "", indoc)
+    indoc = re.sub(" *<tr[^>]*>\t*", "", indoc)
+    indoc = re.sub(r"\.TS\s+<caption>([^<]*)</caption>\s*", ".B \\1\n.TS\n", indoc)
+    # Debugging
+    #sys.stderr.write("Name: %s, Title: %s, Date: %s\n" % (name, title, date))
+    # Time for error checking now
+    badlines = []
+    for line in indoc.split("\n"):
+        if "<" in line or ">" in line or re.search("&.*;", line):
+            badlines.append(line)
+    if badlines:
+        sys.stderr.write(("Bad lines from %s:\n-----------------\n" % file) + "\n".join(badlines) + "\n-----------------\n")
+    # Goes after bad-line check so we don't misinterpret it as an error
+    indoc = indoc.replace("@#!#@", "<").replace("#@!@#", ">").replace("#!@!@!#", "&")
+    indoc = re.sub("\n+$", "\n", indoc)
+    # Single-quote at left margin confuses troff.
+    # This program never generates these.
+    indoc = indoc.replace("\n'", "\n\\&'")
+    # Finish guarding against leading dots.
+    indoc = indoc.replace("\n@%@%@", "\n\\&.")
+    # Mark these generated pages so people won't hand-hack them.
+    indoc = warning + indoc
+    return indoc
+
+def main(args, mainout=sys.stdout, mainerr=sys.stderr):
+    global sectmap
+    import getopt
+    (options, arguments) = getopt.getopt(args, "v")
+    verbosity = 0
+    for (switch, val) in options:
+        if switch == '-v':
+            verbosity += 1
+    try:
+        # First pass: gather locations for crossreferences:
+        sectmap = {}
+        for file in arguments:
+            try: 
+                infp = open(file)
+            except:
+                sys.stderr.write("can't open %s" % name)
+                continue
+            indoc = infp.read()
+            infp.close()
+            namere = re.compile("<H1>(.*)</H1>", re.I)
+            namematch = namere.search(indoc)
+            titlere = re.compile("<TITLE>(.*)</TITLE>", re.I)
+            titlematch = titlere.search(indoc)
+            if not namematch:
+                raise LiftException("name missing from %s" % file)
+            if not titlematch:
+                raise LiftException("title missing from %s" % file)
+            else:
+                title = titlematch.group(1)
+                name = titlematch.group(1)
+            meta = re.compile('(?i)<META NAME="manual_section" CONTENT="([0-9])">')
+            match = meta.search(indoc)
+            if match:
+                section = int(match.group(1))
+                sectmap[title] = sectmap[file] = sectmap[name] = section
+            else:
+                sectmap[title] = sectmap[file] = sectmap[name] = 1
+            hr = re.compile("(?i)<HR>")
+            firsthr = hr.search(indoc)
+            if firsthr and hr.search(indoc[firsthr.start(0)+4:]):
+                LiftException("%s has two <HR> tags!" % file)
+        # Second pass: do formatting
+        for file in arguments:
+            try: 
+                infp = open(file)
+            except:
+                sys.stderr.write("can't open %s" % name)
+                continue
+            indoc = infp.read()
+            infp.close()
+            tempfile = file + ".~%s-%d~" % (name, os.getpid())
+            try:
+                outfp = open(tempfile, "w")
+            except OSError:
+                sys.stderr.write("%s: can't open tempfile" % name)
+                return True
+            try:
+                if verbosity:
+                    sys.stderr.write("makeman: %s\n" % file)
+                outdoc = makeman(name, file, indoc)
+            except:
+                os.remove(tempfile)
+                # Pass the exception upwards
+                (exc_type, exc_value, exc_traceback) = sys.exc_info()
+                raise exc_type, exc_value, exc_traceback
+            if outdoc == indoc:
+                os.remove(tempfile)
+            if outdoc is None:
+                continue
+            else:
+                outfp.write(outdoc)
+                outfp.close()	# under Windows you can't rename an open file
+                stem = file[:file.find(".")]
+                os.rename(tempfile, stem + "." + `sectmap[file]`)
+    except LiftException, e:
+        mainerr.write("makeman: " + e.message + "\n")
+        return e.retval
+    except IOError, e:
+        mainerr.write("makeman: file I/O error: %s\n" % e)
+        return 3
+    except KeyboardInterrupt:
+        mainerr.write("makeman: bailing out...\n")
+        return 4
+    except:
+        if verbosity:
+            (exc_type, exc_value, exc_traceback) = sys.exc_info()
+            raise exc_type, exc_value, exc_traceback
+        else:
+            return 5
+
+if __name__ == "__main__":
+    # Run the main sequence
+    raise SystemExit, main(sys.argv[1:])
+
+# The following sets edit modes for GNU EMACS
+# Local Variables:
+# mode:python
+# End:
diff --git a/buildtools/makepointerman b/buildtools/makepointerman
new file mode 100755
index 00000000..8fbb0f49
--- /dev/null
+++ b/buildtools/makepointerman
@@ -0,0 +1,91 @@
+#!/usr/bin/perl -w
+
+#############################################################################
+#                               makepointerman
+#############################################################################
+#
+# This program creates a Netpbm man page that says nothing except to use a
+# web browser to look at a particular URL.
+#
+# In Netpbm, we believe that man pages, and the Nroff/Troff formats, are
+# obsolete; that HTML and web browsers and the world wide web long ago replaced
+# them as the best way to deliver documentation.  However, documentation is 
+# useless when people don't know where it is.  People are very accustomed to
+# typing "man" to get information on a Unix program or library or file type,
+# so in the standard Netpbm installation, we install a conventional man page
+# for every command, library, and file type, but all it says is to use your
+# web browser to look at the real documentation.
+
+# What would be ideal is if the user simply had Manweb (or something like
+# it) installed as the 'man' command and configured to find the Netpbm
+# web documentation.
+
+# But because of the high probability that Netpbm installers will not 
+# install Manweb, pointer man pages are necessary.
+
+# Besides making the web documentation accessible, pointer man pages serve
+# another important purpose:  Installing them causes obsolete man pages from
+# before web documentation existed to be discarded.
+
+use strict;
+use Time::gmtime;
+
+if (@ARGV < 5) {
+    die("Need 6 arguments: filename, directory URL, man page directory, " .
+        "man section, format, octal permissions");
+}
+
+my ($filename, $directoryUrl, $mandir, $section, $format, $permissions) = 
+    @ARGV;
+
+if ($format ne "nroff" && $format ne "cat") {
+    die("format argument must be 'nroff' or 'cat', not '$format'.");
+}
+my $manPageFileName = "$mandir/$filename.$section";
+
+unlink($manPageFileName);
+
+open(MANPAGE, ">$manPageFileName") or
+    die("Unable to create file '$manPageFileName'");
+
+my ($wday, $mon, $mday, $tod, $year) = split(/ /,gmctime());
+if ($format eq "nroff") {
+    print(MANPAGE ".TH $filename $section Netpbm \"$mday $mon $year\" " .
+          "\"Netpbm pointer man pages\"\n\n");
+    
+    # NAME and DESCRIPTION section headings help some automated processors,
+    # such as Doclifter.  From ESR 2004.11.14.
+
+    # avoid a double slash in the URL
+    (my $cleanedUpDirectoryUrl = $directoryUrl) =~ s{^(.*?)/*$}{$1};
+
+    print(MANPAGE ".SH NAME\n");
+    print(MANPAGE "$filename \\- see $cleanedUpDirectoryUrl/$filename.html\n");
+    print(MANPAGE ".SH DESCRIPTION\n");
+} else {
+    print(MANPAGE "        NETPBM POINTER MAN PAGES       \n\n");
+}
+print(MANPAGE "$filename is part of the Netpbm package.\n");
+print(MANPAGE "Netpbm documentation is kept in HTML format.\n");
+print(MANPAGE "\n");
+print(MANPAGE "Please refer to <$directoryUrl/$filename.html>.\n\n");
+print(MANPAGE "If that doesn't work, also try " .
+      "<http://netpbm.sourceforge.net> and\n");
+print(MANPAGE "emailing Bryan Henderson, bryanh\@giraffe-data.com.\n");
+
+print(MANPAGE "\n");
+print(MANPAGE "Note that making the documentation available this way was\n");
+print(MANPAGE "a choice of the person who installed Netpbm on this system.\n");
+print(MANPAGE "It is also possible to install Netpbm such that you would\n");
+print(MANPAGE "simply see the documentation instead of the message you are\n");
+print(MANPAGE "reading now.\n");
+print(MANPAGE "\n");
+
+if ($format eq "nroff") {
+    print(MANPAGE ".\\\" This file was generated by the program " .
+          "'makepointerman',\n");
+    print(MANPAGE ".\\\" as part of Netpbm installation\n");
+}
+
+chmod(oct("0$permissions"),$manPageFileName);
+close(MANPAGE);
diff --git a/buildtools/mkinstalldirs b/buildtools/mkinstalldirs
new file mode 100755
index 00000000..2c5ab7cb
--- /dev/null
+++ b/buildtools/mkinstalldirs
@@ -0,0 +1,41 @@
+#! /bin/sh
+# mkinstalldirs --- make directory hierarchy
+# Author: Noah Friedman <friedman@prep.ai.mit.edu>
+# Created: 1993-05-16
+# Public domain
+
+# This program is important because some systems don't have an 'install'
+# program that has the -d option to create directories.
+
+errstatus=0
+
+for file
+do
+   set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'`
+   shift
+
+   pathcomp=
+   for d
+   do
+     pathcomp="$pathcomp$d"
+     case "$pathcomp" in
+       -* ) pathcomp=./$pathcomp ;;
+     esac
+
+     if test ! -d "$pathcomp"; then
+        echo "mkdir $pathcomp"
+
+        mkdir "$pathcomp" || lasterr=$?
+
+        if test ! -d "$pathcomp"; then
+  	  errstatus=$lasterr
+        fi
+     fi
+
+     pathcomp="$pathcomp/"
+   done
+done
+
+exit $errstatus
+
+# mkinstalldirs ends here
diff --git a/buildtools/stamp-date b/buildtools/stamp-date
new file mode 100755
index 00000000..808bab79
--- /dev/null
+++ b/buildtools/stamp-date
@@ -0,0 +1,23 @@
+#!/bin/sh
+#
+# Copyright (C) 1993 by Oliver Trepte.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose and without fee is hereby granted, provided
+# that the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation.  This software is provided "as is" without express or
+# implied warranty.
+#
+DATE=`date`
+LOGNAME_OR_UNKNOWN=${LOGNAME:-UNKNOWN}
+USER=${USER:-$LOGNAME_OR_UNKNOWN}
+if [ $USER = "UNKNOWN" ]; then
+    USER=`whoami`
+fi
+
+echo "/* This file tells the package when it was compiled */"
+echo "/* DO NOT EDIT - THIS FILE IS MAINTAINED AUTOMATICALLY */"
+echo "/* Created by the program 'stamp-date'  */"
+echo "#define COMPILE_TIME \"$DATE\""
+echo "#define COMPILED_BY \"$USER\""
diff --git a/buildtools/typegen b/buildtools/typegen
new file mode 100755
index 00000000..b76191f3
--- /dev/null
+++ b/buildtools/typegen
Binary files differdiff --git a/buildtools/typegen.c b/buildtools/typegen.c
new file mode 100644
index 00000000..5b32af28
--- /dev/null
+++ b/buildtools/typegen.c
@@ -0,0 +1,113 @@
+/*----------------------------------------------------------------------------
+                                 typegen
+------------------------------------------------------------------------------
+  This is a program to create an inttypes.h file for use in building
+  Netpbm.  Ordinarily we use the inttypes.h that comes with the C library
+  or C compiler, specified by Single Unix Specification.  When that isn't
+  available there is usually some other system header file we should use.
+  But when there isn't any system header file to define the needed types,
+  the file we generate is better than nothing.
+
+  The main problem with the inttypes.h we generate is that when we
+  figure out which type is a 32 bit integer, we aren't necessarily using
+  the same compile environment as what will actually get used to build
+  Netpbm, and thus the one where our inttypes.h will be used.
+
+  Right now, it simply generates either 
+  
+    typedef int int32_t;
+
+  or
+
+    typedef long int32_t;
+
+  based on which type, int, or long, is 32 bits.
+
+  We also include the uint32_t and int_fast32_t typedefs.
+
+  We also include the multiple inclusion guard ifdef.
+-----------------------------------------------------------------------------*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+
+
+static void
+createTypedefForUint32_t(void) {
+
+    if (sizeof(unsigned int) == 4)
+        printf("typedef unsigned int uint32_t;\n");
+    else if (sizeof(unsigned long) == 4)
+        printf("typedef unsigned long uint32_t;\n");
+    else {
+        fprintf(stderr, "Cannot find a 32 bit unsigned integer type!\n");
+        exit(1);
+    }
+}
+
+
+
+static void
+createTypedefForUintFast32_t(void) {
+
+    if (sizeof(unsigned int) == 4)
+        printf("typedef unsigned int uint_fast32_t;\n");
+    else if (sizeof(unsigned long) == 4)
+        printf("typedef unsigned long uint_fast32_t;\n");
+    else {
+        fprintf(stderr, "Cannot find a 32 bit unsigned integer type!\n");
+        exit(1);
+    }
+}
+
+
+
+static void
+createTypedefForInt32_t(void) {
+
+    if (sizeof(signed int) == 4)
+        printf("typedef signed int int32_t;\n");
+    else if (sizeof(signed long) == 4)
+        printf("typedef signed long int32_t;\n");
+    else {
+        fprintf(stderr, "Cannot find a 32 bit signed integer type!\n");
+        exit(1);
+    }
+}
+
+
+
+static void
+createTypedefForIntFast32_t(void) {
+
+    if (sizeof(signed int) == 4)
+        printf("typedef signed int int_fast32_t;\n");
+    else if (sizeof(signed long) == 4)
+        printf("typedef signed long int_fast32_t;\n");
+    else {
+        fprintf(stderr, "Cannot find a 32 bit signed integer type!\n");
+        exit(1);
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    printf("/* This was generated by the program 'typegen' */\n");
+
+
+    printf("#ifndef INTTYPES_H_NETPBM\n");
+    printf("#define INTTYPES_H_NETPBM\n");
+
+    createTypedefForUint32_t();
+    createTypedefForInt32_t();
+    createTypedefForIntFast32_t();
+    createTypedefForUintFast32_t();
+
+    printf("#endif\n");
+
+    return 0;
+}
diff --git a/configure b/configure
new file mode 100755
index 00000000..b7ba662d
--- /dev/null
+++ b/configure
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# You may be wondering why we have this shell script instead of just
+# naming buildtools/configure.pl 'configure' and letting the user
+# invoke it with a 'configure' command directly.  The reason is that
+# that would depend upon the user having a suitable perl interpreter
+# in /usr/bin/perl, which is nowhere near as dependable as that he has
+# a suitable Bourne shell interpreter at /bin/sh.  (An alternative that
+# has been suggested is to code "#!/usr/bin/env perl" in configure.pl,
+# but even the /usr/bin/env assumption is shakier than the /bin/sh
+# assumption).
+
+# We have had reports of Netpbm builders who do not have a suitable 
+# Perl interpreter at /usr/bin/perl.  They have a downlevel version that
+# came with their operating system and can't run configure.pl at
+# /usr/bin/perl, and a good Perl interpreter in /usr/local/bin/perl.
+
+perl `dirname $0`/buildtools/configure.pl $*
diff --git a/converter/Makefile b/converter/Makefile
new file mode 100644
index 00000000..02cab3be
--- /dev/null
+++ b/converter/Makefile
@@ -0,0 +1,17 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+SUBDIRS = pbm pgm ppm other
+
+all: $(SUBDIRS:%=%/all)
+
+SCRIPTS = 
+BINARIES =
+
+include $(SRCDIR)/Makefile.common
diff --git a/converter/bmp.h b/converter/bmp.h
new file mode 100644
index 00000000..b22be82f
--- /dev/null
+++ b/converter/bmp.h
@@ -0,0 +1,256 @@
+/*\
+ * $Id: bmp.h,v 1.3 1992/11/24 19:39:56 dws Exp dws $
+ * 
+ * bmp.h - routines to calculate sizes of parts of BMP files
+ *
+ * Some fields in BMP files contain offsets to other parts
+ * of the file.  These routines allow us to calculate these
+ * offsets, so that we can read and write BMP files without
+ * the need to fseek().
+ * 
+ * Copyright (C) 1992 by David W. Sanderson.
+ * 
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and
+ * that both that copyright notice and this permission notice appear
+ * in supporting documentation.  This software is provided "as is"
+ * without express or implied warranty.
+ * 
+ * $Log: bmp.h,v $
+ * Revision 1.3  1992/11/24  19:39:56  dws
+ * Added copyright.
+ *
+ * Revision 1.2  1992/11/17  02:13:37  dws
+ * Adjusted a string's name.
+ *
+ * Revision 1.1  1992/11/16  19:54:44  dws
+ * Initial revision
+ *
+\*/
+
+/* 
+   There is a specification of BMP (2003.07.24) at:
+
+     http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_4v1h.asp
+
+   There is a better written specification of the Windows BMP format
+   in (2000.06.08) <http://www.daubnet.com/formats/BMP.html>.
+   However, the "Windows BMP" format used in practice is much closer
+   to the Microsoft definition.  The Microsoft spec was not known for
+   Netpbm development until 2003.07.24.
+
+   The ColorsImportant field is defined in the daubnet spec as "Number of
+   important colors.  0 = all"  That is the entire definition.  The
+   spec also says the number of entries in the color map is a function
+   of the BitCount field alone.
+
+   But Marc Moorcroft says (2000.07.23) that he found two BMP files
+   some time ago that had a color map whose number of entries was not
+   as specified and was in fact the value of ColorsImportant.
+
+   And Bill Janssen actually produced some BMPs in January 2001 that
+   appear to have the size of the colormap determined by ColorsUsed.
+   They have 8 bits per pixel in the raster, but ColorsUsed is 4 and
+   there are in fact 4 entries in the color map.  He got these from
+   the Palm emulator for Windows, using the "Save Screen" menu 
+   option.
+
+   Bmptoppm had, for a few releases in 2000, code by Marc to use
+   ColorsImportant as the color map size unless it was zero, in which
+   case it determined color map size as specified.  The current
+   thinking is that there are probably more BMPs that need to be
+   interpreted per the spec than that need to be interpreted Marc's
+   way.  
+
+   But in light of Janssen's discovery, we have made the assumption
+   since February 2001 that when ColorsUsed is zero, the colormap size
+   is as specified, and when it is nonzero, the colormap size is given
+   by ColorsUsed.
+
+   But we were also assuming that if ColorsUsed is nonzero, the image
+   is colormapped.  We heard from "Ron & Bes Vantreese"
+   <eaglesun@aggienetwork.com> in February 2003 that his understanding
+   of the format was that ColorsUsed == 2**24 is appropriate for a
+   non-colormapped (24 bit) BMP, and images he created that way caused
+   trouble for Bmptopnm.  So since then, we look at ColorsUsed only if
+   we know because bits per pixel <= 8 that it is a colormapped image.
+
+*/
+
+#ifndef BMP_H_INCLUDED
+#define BMP_H_INCLUDED
+
+#include "pm.h"  /* For pm_error() */
+
+enum bmpClass {C_WIN=1, C_OS2=2};
+
+static char const er_internal[] = "%s: internal error!";
+
+/* Values of the "compression" field of the BMP info header */
+#define COMP_RGB       0
+#define COMP_RLE8      1
+#define COMP_RLE4      2
+#define COMP_BITFIELDS 3
+#define COMP_JPEG      4
+#define COMP_PNG       5
+
+static __inline__ unsigned int
+BMPlenfileheader(enum bmpClass const class) {
+
+    unsigned int retval;
+
+    switch (class) {
+    case C_WIN: retval = 14; break;
+    case C_OS2: retval = 14; break;
+    }
+    return retval;
+}
+
+
+
+static __inline__ unsigned int
+BMPleninfoheader(enum bmpClass const class) {
+
+    unsigned int retval;
+
+    switch (class) {
+    case C_WIN: retval = 40; break;
+    case C_OS2: retval = 12; break;
+    }
+    return retval;
+}
+
+
+
+static __inline__ unsigned int
+BMPlencolormap(enum bmpClass const class,
+               unsigned int  const bitcount, 
+               int           const cmapsize) {
+
+    unsigned int lenrgb;
+    unsigned int lencolormap;
+
+    if (bitcount < 1)
+        pm_error(er_internal, "BMPlencolormap");
+    else if (bitcount > 8) 
+        lencolormap = 0;
+    else {
+        switch (class) {
+        case C_WIN: lenrgb = 4; break;
+        case C_OS2: lenrgb = 3; break;
+        }
+
+        if (cmapsize < 0) 
+            lencolormap = (1 << bitcount) * lenrgb;
+        else 
+            lencolormap = cmapsize * lenrgb;
+    }
+    return lencolormap;
+}
+
+
+
+static __inline__ unsigned int
+BMPlenline(enum bmpClass const class,
+           unsigned int  const bitcount, 
+           unsigned int  const x) {
+/*----------------------------------------------------------------------------
+  length, in bytes, of a line of the image
+
+  Each row is padded on the right as needed to make it a multiple of 4
+  bytes int.  This appears to be true of both OS/2 and Windows BMP
+  files.
+-----------------------------------------------------------------------------*/
+    unsigned int bitsperline;
+    unsigned int retval;
+
+    bitsperline = x * bitcount;  /* tentative */
+
+    /*
+     * if bitsperline is not a multiple of 32, then round
+     * bitsperline up to the next multiple of 32.
+     */
+    if ((bitsperline % 32) != 0)
+        bitsperline += (32 - (bitsperline % 32));
+
+    if ((bitsperline % 32) != 0) {
+        pm_error(er_internal, "BMPlenline");
+        retval = 0;
+    } else 
+        /* number of bytes per line == bitsperline/8 */
+        retval = bitsperline >> 3;
+
+    return retval;
+}
+
+
+
+static __inline__ unsigned int
+BMPlenbits(enum bmpClass const class,
+           unsigned int  const bitcount, 
+           unsigned int  const x,
+           unsigned int  const y) {
+/*----------------------------------------------------------------------------
+  Return the number of bytes used to store the image bits
+  for an uncompressed image.
+-----------------------------------------------------------------------------*/
+    return y * BMPlenline(class, bitcount, x);
+}
+
+
+
+static __inline__ unsigned int
+BMPoffbits(enum bmpClass const class,
+           unsigned int  const bitcount, 
+           unsigned int  const cmapsize) {
+/*----------------------------------------------------------------------------
+  return the offset to the BMP image bits.
+-----------------------------------------------------------------------------*/
+    return BMPlenfileheader(class)
+        + BMPleninfoheader(class)
+        + BMPlencolormap(class, bitcount, cmapsize);
+}
+
+
+static __inline__ unsigned int
+BMPlenfileGen(enum bmpClass     const class,
+              unsigned int      const bitcount, 
+              int               const cmapsize,
+              unsigned int      const x,
+              unsigned int      const y,
+              unsigned int      const imageSize,
+              unsigned long int const compression) {
+/*----------------------------------------------------------------------------
+  Return the size of the BMP file in bytes.
+-----------------------------------------------------------------------------*/
+    unsigned int retval;
+
+    switch (compression) {
+    case COMP_RGB:
+    case COMP_BITFIELDS:
+        retval =
+            BMPoffbits(class, bitcount, cmapsize) +
+            BMPlenbits(class, bitcount, x, y);
+        break;
+    default:
+        retval = BMPoffbits(class, bitcount, cmapsize) + imageSize;
+    }
+    return retval;
+}
+
+
+
+static __inline__ unsigned int
+BMPlenfile(enum bmpClass const class,
+           unsigned int  const bitcount, 
+           int           const cmapsize,
+           unsigned int  const x,
+           unsigned int  const y) {
+/*----------------------------------------------------------------------------
+  return the size of the BMP file in bytes; no compression
+-----------------------------------------------------------------------------*/
+    return BMPlenfileGen(class, bitcount, cmapsize, x, y, 0, COMP_RGB);
+}
+
+#endif
diff --git a/converter/other/Makefile b/converter/other/Makefile
new file mode 100644
index 00000000..4585c45d
--- /dev/null
+++ b/converter/other/Makefile
@@ -0,0 +1,238 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/other
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+ifeq ($(shell xml2-config --version),)
+  XML2_LIBS=NONE
+  XML2_CFLAGS=NONE
+else
+  XML2_LIBS=$(shell xml2-config --libs)
+  XML2_CFLAGS=$(shell xml2-config --cflags)
+endif
+
+SUBDIRS = jbig pnmtopalm jpeg2000 cameratopam pamtosvg
+ifneq ($(BUILD_FIASCO), N)
+  SUBDIRS += fiasco
+endif
+
+INCLUDES = -I$(SRCDIR)/util 
+ifneq ($(TIFFLIB),NONE)
+  ifneq ($(TIFFHDR_DIR)x,x)
+    INCLUDES += -I$(TIFFHDR_DIR)
+  endif
+endif
+
+ifeq ($(shell libpng-config --version),)
+  ifneq ($(PNGLIB),NONE)
+    HAVE_PNGLIB = Y
+    ifneq ($(PNGHDR_DIR)x,x)
+      INCLUDES += -I$(PNGHDR_DIR)
+    endif
+    ifneq ($(ZHDR_DIR)x,x)
+      INCLUDES += -I$(ZHDR_DIR)
+    endif
+  endif
+else
+  HAVE_PNGLIB = Y
+  INCLUDES += $(shell libpng-config --cflags)
+endif
+
+ifneq ($(JPEGLIB),NONE)
+  ifneq ($(JPEGHDR_DIR)x,x)
+    INCLUDES += -I$(JPEGHDR_DIR)
+  endif
+endif
+ifneq ($(URTLIB),NONE)
+  ifneq ($(URTHDR_DIR)x,x)
+    INCLUDES += -I$(URTHDR_DIR)
+  endif
+endif
+ifneq ($(XML2_LIBS),NONE)
+  ifneq ($(XML2_CFLAGS),NONE)
+    INCLUDES += $(XML2_CFLAGS)
+  endif
+endif
+
+ifeq ($(TIFFLIB),NONE)
+  TIFF_PREREQ_MISSING = Y
+endif
+
+TIFFLIB_EXTRALIBS =
+ifeq ($(TIFFLIB_NEEDS_JPEG),Y)
+  ifeq ($(JPEGLIB),NONE)
+    TIFF_PREREQ_MISSING = Y
+  else
+    TIFFLIB_EXTRALIBS += $(JPEGLIB)
+  endif
+endif
+ifeq ($(TIFFLIB_NEEDS_Z),Y)
+  ifeq ($(ZLIB),NONE)
+    TIFF_PREREQ_MISSING = Y
+  else
+    TIFFLIB_EXTRALIBS += $(ZLIB)
+  endif
+endif
+
+PORTBINARIES =  bmptopnm fitstopnm \
+		gemtopnm giftopnm hdifftopam infotopam \
+		pamtodjvurle pamtofits \
+		pamtohdiff pamtohtmltbl pamtopfm pamtopnm pamtouil \
+		pamtoxvmini \
+		pbmtopgm pfmtopam \
+	        pgmtopbm pgmtoppm ppmtopgm pnmtoddif \
+		pnmtopclxl \
+		pnmtosgi pnmtosir pamtotga pnmtoxwd pstopnm \
+		rlatopam sgitopnm sirtopnm xwdtopnm zeisstopnm
+
+BINARIES = $(PORTBINARIES) pnmtorast rasttopnm
+
+ifeq ($(HAVE_PNGLIB),Y)
+  BINARIES += pnmtopng pngtopnm pamrgbatopng
+endif
+ifneq ($(JPEGLIB),NONE)
+  BINARIES += jpegtopnm pnmtojpeg
+endif
+ifneq ($(TIFF_PREREQ_MISSING),Y)
+  BINARIES += tifftopnm pamtotiff pnmtotiffcmyk
+endif
+ifneq ($(URTLIB),NONE)
+  BINARIES += rletopnm pnmtorle
+endif
+ifneq ($(ZLIB),NONE)
+  BINARIES += pnmtops
+endif
+
+ifneq ($(XML2_LIBS),NONE)
+  BINARIES += svgtopam
+endif 
+
+MERGEBINARIES = $(BINARIES)
+
+EXTRA_OBJECTS = exif.o rast.o pngtxt.o bmepsoe.o
+ifneq ($(JPEGLIB),NONE)
+  EXTRA_OBJECTS += jpegdatasource.o
+endif
+ifneq (($TIFF_PREREQ_MISSING),Y)
+  EXTRA_OBJECTS += tiff.o
+endif
+
+OBJECTS = $(BINARIES:%=%.o) $(EXTRA_OBJECTS)
+MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2) $(EXTRA_OBJECTS)
+
+
+SCRIPTS = anytopnm pnmtoplainpnm
+
+.PHONY: all
+all:	$(BINARIES) $(SUBDIRS:%=%/all)
+
+include $(SRCDIR)/Makefile.common
+
+ifeq ($(NEED_RUNTIME_PATH),Y)
+  LIBOPTR = -runtime
+else
+  LIBOPTR =
+endif
+
+LIBOPTS_TIFF = $(shell $(LIBOPT) $(NETPBMLIB) \
+  $(LIBOPTR) $(TIFFLIB) $(TIFFLIB_EXTRALIBS))
+
+tifftopnm pamtotiff pnmtotiffcmyk: %: %.o tiff.o $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $@.o tiff.o \
+	  $(LIBOPTS_TIFF) $(MATHLIB) $(LDLIBS) $(RPATH) $(LADD)
+
+ifeq ($(shell libpng-config --version),)
+  PNGLIB_LIBOPTS = $(shell $(LIBOPT) $(LIBOPTR) $(PNGLIB) $(ZLIB))
+else
+  PNGLIB_LIBOPTS = $(shell libpng-config --ldflags)
+endif
+
+pngtopnm: %: %.o $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $@.o \
+	  $(shell $(LIBOPT) $(NETPBMLIB)) \
+	  $(PNGLIB_LIBOPTS) $(MATHLIB) $(LDLIBS) $(RPATH) $(LADD)
+
+pnmtopng: %: %.o pngtxt.o $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $@.o pngtxt.o \
+	  $(shell $(LIBOPT) $(NETPBMLIB)) \
+	  $(PNGLIB_LIBOPTS) $(MATHLIB) $(LDLIBS) $(RPATH) $(LADD)
+
+pamrgbatopng: %: %.o $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $@.o \
+	  $(shell $(LIBOPT) $(NETPBMLIB)) $(PNGLIB_LIBOPTS) \
+	  $(MATHLIB) $(LDLIBS) $(RPATH) $(LADD)
+
+jpegtopnm: %: %.o jpegdatasource.o exif.o $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $< jpegdatasource.o exif.o \
+	  $(shell $(LIBOPT) $(NETPBMLIB) $(LIBOPTR) $(JPEGLIB)) \
+	  $(MATHLIB) $(LDLIBS) $(RPATH) $(LADD) 
+
+pnmtojpeg: %: %.o $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $@.o \
+	  $(shell $(LIBOPT) $(NETPBMLIB) $(LIBOPTR) $(JPEGLIB)) \
+	  $(MATHLIB) $(LDLIBS) $(RPATH) $(LADD)
+
+svgtopam: %: %.o $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $@.o \
+	  $(shell $(LIBOPT) $(NETPBMLIB) $(LIBOPTR)) \
+	  $(XML2_LIBS) $(MATHLIB) $(LDLIBS) $(RPATH) $(LADD)
+
+# If URTLIB is BUNDLED_URTLIB, then we're responsible for building it, which
+# means it needs to be a dependency:
+ifeq ($(URTLIB), $(BUNDLED_URTLIB))
+  URTLIBDEP = $(URTLIB)
+endif
+
+rletopnm pnmtorle: %: %.o $(NETPBMLIB) $(URTLIBDEP) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $@.o \
+	  $(shell $(LIBOPT) $(URTLIB) $(NETPBMLIB)) \
+	  $(MATHLIB) $(LDLIBS) $(RPATH) $(LADD)
+
+pnmtops: %: %.o bmepsoe.o $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $@.o bmepsoe.o \
+	  $(shell $(LIBOPT) $(NETPBMLIB) $(ZLIB)) \
+	  $(MATHLIB) $(LDLIBS) $(RPATH) $(LADD)
+
+pnmtorast rasttopnm: %: %.o rast.o $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $@.o rast.o \
+	  $(shell $(LIBOPT) $(NETPBMLIB)) \
+	  $(MATHLIB) $(LDLIBS) $(RPATH) $(LADD)
+
+bmptopnm.o bmptopnm.o2: bmp.h
+
+pamtotga.o pamtotga.o2: tga.h
+
+install.bin: install.bin.local
+.PHONY: install.bin.local
+install.bin.local: $(PKGDIR)/bin
+# Remember that $(SYMLINK) might just be a copy command.
+# backward compatibility: program used to be named pnmnoraw
+	cd $(PKGDIR)/bin ; \
+	$(SYMLINK) pnmtoplainpnm$(EXE) pnmnoraw
+# backward compatibility: program used to be gemtopbm
+	cd $(PKGDIR)/bin ; \
+	$(SYMLINK) gemtopnm$(EXE) gemtopbm
+# In October 2001, pnmtojpeg replaced ppmtojpeg
+	cd $(PKGDIR)/bin ; \
+	$(SYMLINK) pnmtojpeg$(EXE) ppmtojpeg
+# In March 2002, bmptopnm replaced bmptoppm
+	cd $(PKGDIR)/bin ; \
+	$(SYMLINK) bmptopnm$(EXE) bmptoppm
+# In May 2002, pamtouil replaced ppmtouil
+	cd $(PKGDIR)/bin ; \
+	$(SYMLINK) pamtouil$(EXE) ppmtouil
+# In March 2005, we realized that pamtopnm obviates pnmtopnm
+	cd $(PKGDIR)/bin ; \
+	$(SYMLINK) pamtopnm$(EXE) pnmtopnm
+# In October 2005, pamtofits replaced pnmtofits
+	cd $(PKGDIR)/bin ; \
+	$(SYMLINK) pamtofits$(EXE) pnmtofits
+ifneq ($(TIFF_PREREQ_MISSING),Y)
+# In October 2005, pamtotiff replaced pnmtotiff
+	cd $(PKGDIR)/bin ; \
+	$(SYMLINK) pamtotiff$(EXE) pnmtotiff
+endif
\ No newline at end of file
diff --git a/converter/other/README.JPEG b/converter/other/README.JPEG
new file mode 100644
index 00000000..5d2d941c
--- /dev/null
+++ b/converter/other/README.JPEG
@@ -0,0 +1,397 @@
+THIS IS THE README FILE FROM THE JPEG LIBRARY, 2000.03.06.  IT IS INCLUDED
+VERBATIM, INCLUDING ITS COPYRIGHT AND LICENSE NOTICES, IN THE NETPBM 
+PACKAGE AS ONE OF THE TERMS UNDER WHICH THE INDEPENDENT JPEG GROUP GRANTED
+PERMISSION TO BRYAN HENDERSON TODAY TO DISTRIBUTE THE PPMTOJPEG AND 
+JPEGTOPPM COMPONENTS OF NETPBM, WHICH ARE DERIVED FROM THE CJPEG AND DJPEG
+PROGRAMS IN THE JPEG LIBRARY PACKAGE.
+
+AS PROVIDED BY THE LICENSE GIVEN TO BRYAN, BRYAN PROPAGATES THE SAME
+LICENSE TO YOU, WITH RESPECT TO PPMTOJPEG AND JPEGTOPPM.
+
+
+
+The Independent JPEG Group's JPEG software
+==========================================
+
+README for release 6b of 27-Mar-1998
+====================================
+
+This distribution contains the sixth public release of the Independent JPEG
+Group's free JPEG software.  You are welcome to redistribute this software and
+to use it for any purpose, subject to the conditions under LEGAL ISSUES, below.
+
+Serious users of this software (particularly those incorporating it into
+larger programs) should contact IJG at jpeg-info@uunet.uu.net to be added to
+our electronic mailing list.  Mailing list members are notified of updates
+and have a chance to participate in technical discussions, etc.
+
+This software is the work of Tom Lane, Philip Gladstone, Jim Boucher,
+Lee Crocker, Julian Minguillon, Luis Ortiz, George Phillips, Davide Rossi,
+Guido Vollbeding, Ge' Weijers, and other members of the Independent JPEG
+Group.
+
+IJG is not affiliated with the official ISO JPEG standards committee.
+
+
+DOCUMENTATION ROADMAP
+=====================
+
+This file contains the following sections:
+
+OVERVIEW            General description of JPEG and the IJG software.
+LEGAL ISSUES        Copyright, lack of warranty, terms of distribution.
+REFERENCES          Where to learn more about JPEG.
+ARCHIVE LOCATIONS   Where to find newer versions of this software.
+RELATED SOFTWARE    Other stuff you should get.
+FILE FORMAT WARS    Software *not* to get.
+TO DO               Plans for future IJG releases.
+
+Other documentation files in the distribution are:
+
+User documentation:
+  install.doc       How to configure and install the IJG software.
+  usage.doc         Usage instructions for cjpeg, djpeg, jpegtran,
+                    rdjpgcom, and wrjpgcom.
+  *.1               Unix-style man pages for programs (same info as usage.doc).
+  wizard.doc        Advanced usage instructions for JPEG wizards only.
+  change.log        Version-to-version change highlights.
+Programmer and internal documentation:
+  libjpeg.doc       How to use the JPEG library in your own programs.
+  example.c         Sample code for calling the JPEG library.
+  structure.doc     Overview of the JPEG library's internal structure.
+  filelist.doc      Road map of IJG files.
+  coderules.doc     Coding style rules --- please read if you contribute code.
+
+Please read at least the files install.doc and usage.doc.  Useful information
+can also be found in the JPEG FAQ (Frequently Asked Questions) article.  See
+ARCHIVE LOCATIONS below to find out where to obtain the FAQ article.
+
+If you want to understand how the JPEG code works, we suggest reading one or
+more of the REFERENCES, then looking at the documentation files (in roughly
+the order listed) before diving into the code.
+
+
+OVERVIEW
+========
+
+This package contains C software to implement JPEG image compression and
+decompression.  JPEG (pronounced "jay-peg") is a standardized compression
+method for full-color and gray-scale images.  JPEG is intended for compressing
+"real-world" scenes; line drawings, cartoons and other non-realistic images
+are not its strong suit.  JPEG is lossy, meaning that the output image is not
+exactly identical to the input image.  Hence you must not use JPEG if you
+have to have identical output bits.  However, on typical photographic images,
+very good compression levels can be obtained with no visible change, and
+remarkably high compression levels are possible if you can tolerate a
+low-quality image.  For more details, see the references, or just experiment
+with various compression settings.
+
+This software implements JPEG baseline, extended-sequential, and progressive
+compression processes.  Provision is made for supporting all variants of these
+processes, although some uncommon parameter settings aren't implemented yet.
+For legal reasons, we are not distributing code for the arithmetic-coding
+variants of JPEG; see LEGAL ISSUES.  We have made no provision for supporting
+the hierarchical or lossless processes defined in the standard.
+
+We provide a set of library routines for reading and writing JPEG image files,
+plus two sample applications "cjpeg" and "djpeg", which use the library to
+perform conversion between JPEG and some other popular image file formats.
+The library is intended to be reused in other applications.
+
+In order to support file conversion and viewing software, we have included
+considerable functionality beyond the bare JPEG coding/decoding capability;
+for example, the color quantization modules are not strictly part of JPEG
+decoding, but they are essential for output to colormapped file formats or
+colormapped displays.  These extra functions can be compiled out of the
+library if not required for a particular application.  We have also included
+"jpegtran", a utility for lossless transcoding between different JPEG
+processes, and "rdjpgcom" and "wrjpgcom", two simple applications for
+inserting and extracting textual comments in JFIF files.
+
+The emphasis in designing this software has been on achieving portability and
+flexibility, while also making it fast enough to be useful.  In particular,
+the software is not intended to be read as a tutorial on JPEG.  (See the
+REFERENCES section for introductory material.)  Rather, it is intended to
+be reliable, portable, industrial-strength code.  We do not claim to have
+achieved that goal in every aspect of the software, but we strive for it.
+
+We welcome the use of this software as a component of commercial products.
+No royalty is required, but we do ask for an acknowledgement in product
+documentation, as described under LEGAL ISSUES.
+
+
+LEGAL ISSUES
+============
+
+In plain English:
+
+1. We don't promise that this software works.  (But if you find any bugs,
+   please let us know!)
+2. You can use this software for whatever you want.  You don't have to pay us.
+3. You may not pretend that you wrote this software.  If you use it in a
+   program, you must acknowledge somewhere in your documentation that
+   you've used the IJG code.
+
+In legalese:
+
+The authors make NO WARRANTY or representation, either express or implied,
+with respect to this software, its quality, accuracy, merchantability, or
+fitness for a particular purpose.  This software is provided "AS IS", and you,
+its user, assume the entire risk as to its quality and accuracy.
+
+This software is copyright (C) 1991-1998, Thomas G. Lane.
+All Rights Reserved except as specified below.
+
+Permission is hereby granted to use, copy, modify, and distribute this
+software (or portions thereof) for any purpose, without fee, subject to these
+conditions:
+(1) If any part of the source code for this software is distributed, then this
+README file must be included, with this copyright and no-warranty notice
+unaltered; and any additions, deletions, or changes to the original files
+must be clearly indicated in accompanying documentation.
+(2) If only executable code is distributed, then the accompanying
+documentation must state that "this software is based in part on the work of
+the Independent JPEG Group".
+(3) Permission for use of this software is granted only if the user accepts
+full responsibility for any undesirable consequences; the authors accept
+NO LIABILITY for damages of any kind.
+
+These conditions apply to any software derived from or based on the IJG code,
+not just to the unmodified library.  If you use our work, you ought to
+acknowledge us.
+
+Permission is NOT granted for the use of any IJG author's name or company name
+in advertising or publicity relating to this software or products derived from
+it.  This software may be referred to only as "the Independent JPEG Group's
+software".
+
+We specifically permit and encourage the use of this software as the basis of
+commercial products, provided that all warranty or liability claims are
+assumed by the product vendor.
+
+
+ansi2knr.c is included in this distribution by permission of L. Peter Deutsch,
+sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA.
+ansi2knr.c is NOT covered by the above copyright and conditions, but instead
+by the usual distribution terms of the Free Software Foundation; principally,
+that you must include source code if you redistribute it.  (See the file
+ansi2knr.c for full details.)  However, since ansi2knr.c is not needed as part
+of any program generated from the IJG code, this does not limit you more than
+the foregoing paragraphs do.
+
+The Unix configuration script "configure" was produced with GNU Autoconf.
+It is copyright by the Free Software Foundation but is freely distributable.
+The same holds for its supporting scripts (config.guess, config.sub,
+ltconfig, ltmain.sh).  Another support script, install-sh, is copyright
+by M.I.T. but is also freely distributable.
+
+It appears that the arithmetic coding option of the JPEG spec is covered by
+patents owned by IBM, AT&T, and Mitsubishi.  Hence arithmetic coding cannot
+legally be used without obtaining one or more licenses.  For this reason,
+support for arithmetic coding has been removed from the free JPEG software.
+(Since arithmetic coding provides only a marginal gain over the unpatented
+Huffman mode, it is unlikely that very many implementations will support it.)
+So far as we are aware, there are no patent restrictions on the remaining
+code.
+
+The IJG distribution formerly included code to read and write GIF files.
+To avoid entanglement with the Unisys LZW patent, GIF reading support has
+been removed altogether, and the GIF writer has been simplified to produce
+"uncompressed GIFs".  This technique does not use the LZW algorithm; the
+resulting GIF files are larger than usual, but are readable by all standard
+GIF decoders.
+
+We are required to state that
+    "The Graphics Interchange Format(c) is the Copyright property of
+    CompuServe Incorporated.  GIF(sm) is a Service Mark property of
+    CompuServe Incorporated."
+
+
+REFERENCES
+==========
+
+We highly recommend reading one or more of these references before trying to
+understand the innards of the JPEG software.
+
+The best short technical introduction to the JPEG compression algorithm is
+	Wallace, Gregory K.  "The JPEG Still Picture Compression Standard",
+	Communications of the ACM, April 1991 (vol. 34 no. 4), pp. 30-44.
+(Adjacent articles in that issue discuss MPEG motion picture compression,
+applications of JPEG, and related topics.)  If you don't have the CACM issue
+handy, a PostScript file containing a revised version of Wallace's article is
+available at ftp://ftp.uu.net/graphics/jpeg/wallace.ps.gz.  The file (actually
+a preprint for an article that appeared in IEEE Trans. Consumer Electronics)
+omits the sample images that appeared in CACM, but it includes corrections
+and some added material.  Note: the Wallace article is copyright ACM and IEEE,
+and it may not be used for commercial purposes.
+
+A somewhat less technical, more leisurely introduction to JPEG can be found in
+"The Data Compression Book" by Mark Nelson and Jean-loup Gailly, published by
+M&T Books (New York), 2nd ed. 1996, ISBN 1-55851-434-1.  This book provides
+good explanations and example C code for a multitude of compression methods
+including JPEG.  It is an excellent source if you are comfortable reading C
+code but don't know much about data compression in general.  The book's JPEG
+sample code is far from industrial-strength, but when you are ready to look
+at a full implementation, you've got one here...
+
+The best full description of JPEG is the textbook "JPEG Still Image Data
+Compression Standard" by William B. Pennebaker and Joan L. Mitchell, published
+by Van Nostrand Reinhold, 1993, ISBN 0-442-01272-1.  Price US$59.95, 638 pp.
+The book includes the complete text of the ISO JPEG standards (DIS 10918-1
+and draft DIS 10918-2).  This is by far the most complete exposition of JPEG
+in existence, and we highly recommend it.
+
+The JPEG standard itself is not available electronically; you must order a
+paper copy through ISO or ITU.  (Unless you feel a need to own a certified
+official copy, we recommend buying the Pennebaker and Mitchell book instead;
+it's much cheaper and includes a great deal of useful explanatory material.)
+In the USA, copies of the standard may be ordered from ANSI Sales at (212)
+642-4900, or from Global Engineering Documents at (800) 854-7179.  (ANSI
+doesn't take credit card orders, but Global does.)  It's not cheap: as of
+1992, ANSI was charging $95 for Part 1 and $47 for Part 2, plus 7%
+shipping/handling.  The standard is divided into two parts, Part 1 being the
+actual specification, while Part 2 covers compliance testing methods.  Part 1
+is titled "Digital Compression and Coding of Continuous-tone Still Images,
+Part 1: Requirements and guidelines" and has document numbers ISO/IEC IS
+10918-1, ITU-T T.81.  Part 2 is titled "Digital Compression and Coding of
+Continuous-tone Still Images, Part 2: Compliance testing" and has document
+numbers ISO/IEC IS 10918-2, ITU-T T.83.
+
+Some extensions to the original JPEG standard are defined in JPEG Part 3,
+a newer ISO standard numbered ISO/IEC IS 10918-3 and ITU-T T.84.  IJG
+currently does not support any Part 3 extensions.
+
+The JPEG standard does not specify all details of an interchangeable file
+format.  For the omitted details we follow the "JFIF" conventions, revision
+1.02.  A copy of the JFIF spec is available from:
+	Literature Department
+	C-Cube Microsystems, Inc.
+	1778 McCarthy Blvd.
+	Milpitas, CA 95035
+	phone (408) 944-6300,  fax (408) 944-6314
+A PostScript version of this document is available by FTP at
+ftp://ftp.uu.net/graphics/jpeg/jfif.ps.gz.  There is also a plain text
+version at ftp://ftp.uu.net/graphics/jpeg/jfif.txt.gz, but it is missing
+the figures.
+
+The TIFF 6.0 file format specification can be obtained by FTP from
+ftp://ftp.sgi.com/graphics/tiff/TIFF6.ps.gz.  The JPEG incorporation scheme
+found in the TIFF 6.0 spec of 3-June-92 has a number of serious problems.
+IJG does not recommend use of the TIFF 6.0 design (TIFF Compression tag 6).
+Instead, we recommend the JPEG design proposed by TIFF Technical Note #2
+(Compression tag 7).  Copies of this Note can be obtained from ftp.sgi.com or
+from ftp://ftp.uu.net/graphics/jpeg/.  It is expected that the next revision
+of the TIFF spec will replace the 6.0 JPEG design with the Note's design.
+Although IJG's own code does not support TIFF/JPEG, the free libtiff library
+uses our library to implement TIFF/JPEG per the Note.  libtiff is available
+from ftp://ftp.sgi.com/graphics/tiff/.
+
+
+ARCHIVE LOCATIONS
+=================
+
+The "official" archive site for this software is ftp.uu.net (Internet
+address 192.48.96.9).  The most recent released version can always be found
+there in directory graphics/jpeg.  This particular version will be archived
+as ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz.  If you don't have
+direct Internet access, UUNET's archives are also available via UUCP; contact
+help@uunet.uu.net for information on retrieving files that way.
+
+Numerous Internet sites maintain copies of the UUNET files.  However, only
+ftp.uu.net is guaranteed to have the latest official version.
+
+You can also obtain this software in DOS-compatible "zip" archive format from
+the SimTel archives (ftp://ftp.simtel.net/pub/simtelnet/msdos/graphics/), or
+on CompuServe in the Graphics Support forum (GO CIS:GRAPHSUP), library 12
+"JPEG Tools".  Again, these versions may sometimes lag behind the ftp.uu.net
+release.
+
+The JPEG FAQ (Frequently Asked Questions) article is a useful source of
+general information about JPEG.  It is updated constantly and therefore is
+not included in this distribution.  The FAQ is posted every two weeks to
+Usenet newsgroups comp.graphics.misc, news.answers, and other groups.
+It is available on the World Wide Web at http://www.faqs.org/faqs/jpeg-faq/
+and other news.answers archive sites, including the official news.answers
+archive at rtfm.mit.edu: ftp://rtfm.mit.edu/pub/usenet/news.answers/jpeg-faq/.
+If you don't have Web or FTP access, send e-mail to mail-server@rtfm.mit.edu
+with body
+	send usenet/news.answers/jpeg-faq/part1
+	send usenet/news.answers/jpeg-faq/part2
+
+
+RELATED SOFTWARE
+================
+
+Numerous viewing and image manipulation programs now support JPEG.  (Quite a
+few of them use this library to do so.)  The JPEG FAQ described above lists
+some of the more popular free and shareware viewers, and tells where to
+obtain them on Internet.
+
+If you are on a Unix machine, we highly recommend Jef Poskanzer's free
+PBMPLUS software, which provides many useful operations on PPM-format image
+files.  In particular, it can convert PPM images to and from a wide range of
+other formats, thus making cjpeg/djpeg considerably more useful.  The latest
+version is distributed by the NetPBM group, and is available from numerous
+sites, notably ftp://wuarchive.wustl.edu/graphics/graphics/packages/NetPBM/.
+Unfortunately PBMPLUS/NETPBM is not nearly as portable as the IJG software is;
+you are likely to have difficulty making it work on any non-Unix machine.
+
+A different free JPEG implementation, written by the PVRG group at Stanford,
+is available from ftp://havefun.stanford.edu/pub/jpeg/.  This program
+is designed for research and experimentation rather than production use;
+it is slower, harder to use, and less portable than the IJG code, but it
+is easier to read and modify.  Also, the PVRG code supports lossless JPEG,
+which we do not.  (On the other hand, it doesn't do progressive JPEG.)
+
+
+FILE FORMAT WARS
+================
+
+Some JPEG programs produce files that are not compatible with our library.
+The root of the problem is that the ISO JPEG committee failed to specify a
+concrete file format.  Some vendors "filled in the blanks" on their own,
+creating proprietary formats that no one else could read.  (For example, none
+of the early commercial JPEG implementations for the Macintosh were able to
+exchange compressed files.)
+
+The file format we have adopted is called JFIF (see REFERENCES).  This format
+has been agreed to by a number of major commercial JPEG vendors, and it has
+become the de facto standard.  JFIF is a minimal or "low end" representation.
+We recommend the use of TIFF/JPEG (TIFF revision 6.0 as modified by TIFF
+Technical Note #2) for "high end" applications that need to record a lot of
+additional data about an image.  TIFF/JPEG is fairly new and not yet widely
+supported, unfortunately.
+
+The upcoming JPEG Part 3 standard defines a file format called SPIFF.
+SPIFF is interoperable with JFIF, in the sense that most JFIF decoders should
+be able to read the most common variant of SPIFF.  SPIFF has some technical
+advantages over JFIF, but its major claim to fame is simply that it is an
+official standard rather than an informal one.  At this point it is unclear
+whether SPIFF will supersede JFIF or whether JFIF will remain the de-facto
+standard.  IJG intends to support SPIFF once the standard is frozen, but we
+have not decided whether it should become our default output format or not.
+(In any case, our decoder will remain capable of reading JFIF indefinitely.)
+
+Various proprietary file formats incorporating JPEG compression also exist.
+We have little or no sympathy for the existence of these formats.  Indeed,
+one of the original reasons for developing this free software was to help
+force convergence on common, open format standards for JPEG files.  Don't
+use a proprietary file format!
+
+
+TO DO
+=====
+
+The major thrust for v7 will probably be improvement of visual quality.
+The current method for scaling the quantization tables is known not to be
+very good at low Q values.  We also intend to investigate block boundary
+smoothing, "poor man's variable quantization", and other means of improving
+quality-vs-file-size performance without sacrificing compatibility.
+
+In future versions, we are considering supporting some of the upcoming JPEG
+Part 3 extensions --- principally, variable quantization and the SPIFF file
+format.
+
+As always, speeding things up is of great interest.
+
+Please send bug reports, offers of help, etc. to jpeg-info@uunet.uu.net.
diff --git a/converter/other/anytopnm b/converter/other/anytopnm
new file mode 100755
index 00000000..6c56b5ef
--- /dev/null
+++ b/converter/other/anytopnm
@@ -0,0 +1,544 @@
+#!/bin/sh
+#
+# anytopnm - attempt to convert an unknown type of image file to a P?M file.
+#
+# Copyright (C) 1991 by Jef Poskanzer.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose and without fee is hereby granted, provided
+# that the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation.  This software is provided "as is" without express or
+# implied warranty.
+
+
+
+putInputIntoTempfile() {
+
+    # $1 is the input file specification
+    # $2 is the temporary file name
+    
+    rm -f "$2"
+    if [ "$1" = "-" ] ; then
+            cat > "$2"
+    else
+        # We'd like to do a -e here to test the existence of the filesystem
+        # object per se, and thus give a more specific error message.  But
+        # some systems don't have -e.
+        if [ ! -f "$1" ] ; then
+            echo "$progname: '$1' does not exist or is not a file" 1>&2
+            exit 1
+            fi
+        
+        if [ ! -r "$1" ] ; then
+            echo "$progname: '$1' is not readable" 1>&2
+            exit 1
+            fi
+        
+        if [ -z "$1" ] ; then
+            echo "$progname: '$1' is empty" 1>&2
+            exit 1
+            fi
+        
+        cat < "$1" > "$2"
+        fi
+}
+
+
+
+setMimeType() {
+    # $1 is the file name
+
+    # Christos Zoulas's current 'file' (see Freshmeat) has the --mime option.
+
+    file --mime /dev/null >/dev/null 2>/dev/null
+    if [ $? -eq 0 ]; then
+        # Now that we know the --mime option exists, use it.
+        mimeType=`file --mime "$1" | cut -d: -f2- | cut -c2-`
+    else
+        # file --mime failed; we don't know why, but we assume it's because it
+        # is a traditional 'file' program that doesn't have a --mime option.
+    mimeType="unknown"
+    fi
+}
+
+
+
+computeTypeFromMimeType () {
+
+# $1 is the mime type string
+    
+    case "$1" in
+
+        image/jpeg )
+            filetype="jfif"
+            ;;
+        image/gif )
+            filetype="gif"
+            ;;
+        image/tiff )
+            filetype="tiff"
+            ;;
+        image/bmp )
+            filetype="bmp"
+            ;;
+        image/x-ms-bmp )
+            filetype="bmp"
+            ;;
+        image/png )
+            filetype="png"
+            ;;
+        image/x-portable-bitmap | image/x-portable-pixmap | \
+                image/x-portable-greymap)
+            filetype="pnm"
+            ;;
+        image/x-xpm )
+            filetype="xpm"
+            ;;
+        * )
+            filetype=unknown
+            ;;
+        esac
+}
+
+
+
+computeTypeFromTypeDescription () {
+
+# $1 is the full description from 'file' of the file type
+
+    case "$1" in
+    
+        *PBM* | *PGM* | *PPM* )
+            filetype=pnm
+            ;;
+    
+        *JPEG* | *JFIF* )
+            filetype=jfif
+            ;;
+    
+        *PNG* )
+            filetype=png
+            ;;
+    
+        *GIF* )
+            filetype=gif
+            ;;
+    
+        *TIFF* )
+            filetype=tiff
+            ;;
+    
+        *PC*bitmap*data* )
+            filetype=bmp
+            ;;
+        
+        *uuencoded* )
+            filetype=uuencoded
+            ;;
+    
+        *bzip2*compressed*data* )
+            filetype=bzip2
+            ;;
+    
+        *bzip*compressed*data* )
+            filetype=bzip
+            ;;
+        
+        *gzip*compressed*data* )
+            filetype=gzip
+            ;;
+    
+        *compress* )
+            filetype=compress
+            ;;
+    
+        *btoa* )
+            filetype=btoa
+            ;;
+    
+        *Sun* | *rasterfile* )
+            filetype=rast
+            ;;
+    
+        *IFF*ILBM* )
+            filetype=ilbm
+            ;;
+    
+        *Lisp* )
+            filetype=lispm
+            ;;
+    
+        *PC*Paintbrush* )
+            filetype=pcx
+            ;;
+    
+        *Bennet* )
+            filetype=ybm
+            ;;
+    
+        *pixmap*image*text* )
+            filetype=xpm
+            ;;
+    
+        # This has to come after all other 'text' files, or you may be
+        # disappointed.
+        *text* )
+            filetype=text
+            ;;
+    
+        *MicroDesign* )
+            filetype=mda
+            ;;
+    
+        * )
+            filetype=unknown
+            ;;
+        esac
+}
+
+
+
+computeTypeFromFilenameExtension () {
+# $1 is the filename extension (".gif", etc.)
+
+    case "$1" in
+
+        *.pbm | *.pbm.* | *.pgm | *.pgm.* | *.ppm | *.ppm.* )
+            filetype=pnm
+            ;;
+        *.JPEG | *.jpeg | *.jpg | *.JPG )
+            filetype=jfif
+            ;;
+        *.gif | *.gif.* )
+            filetype=gif
+            ;;
+        *.png | *.PNG )
+            filetype=png
+            ;;
+        *.tif | *.tif.* | *.tiff | *.tiff.* )
+            filetype=tiff
+            ;;
+        *.bmp )
+            filetype=bmp
+            ;;
+        *.x | *.x.* | *.xbm | *.xbm.* | *.x10bm | *.x10bm.* | \
+            *.x11bm | *.x11bm.* | *.bitmap | *.bitmap.* )
+            filetype=xbm
+            ;;
+        *.r | *.r.* | *.rast | *.rast.* )
+            filetype=rast
+            ;;
+        *.mac | *.mac.* | *.macp | *.macp.* )
+            filetype=macp
+            ;;
+        *.g3 | *.g3.* | *.fax | *.fax.* )
+            filetype=g3
+            ;;
+        *.xwd | *.xwd.* | *.x10wd | *.x10wd.* \
+                | *.x11wd | *.x11wd.* )
+            filetype=xwd
+            ;;
+        *.brush | *.brush.* )
+            filetype=brush
+            ;;
+        *.img | *.img.* )
+            filetype=gem
+            ;;
+        *.pcx | *.pcx.* )
+            filetype=pcx
+            ;;
+        *.pic | *.pic.* | *.pict | *.pict.* | *.pict2 | *.pict2.* )
+            filetype=pict
+            ;;
+        *.fs | *.fs.* | *.face | *.face.* )
+            filetype=fs
+            ;;
+        *.hips | *.hips.* )
+            filetype=hips
+            ;;
+        *.fits | *.fits.* )
+            filetype=fits
+            ;;
+        *.iff | *.iff.* | *.ilbm | *.ilbm.* )
+            filetype=ilbm
+            ;;
+        *.lispm | *.lispm.* )
+            filetype=lispm
+            ;;
+        *.mtv | *.mtv.* )
+            filetype=mtv
+            ;;
+        *.qrt | *.qrt.* )
+            filetype=qrt
+            ;;
+        *.tga | *.tga.* | *.targa | *.targa.* )
+            filetype=tga
+            ;;
+        *.xim | *.xim.* )
+            filetype=xim
+            ;;
+        *.xpm | *.xpm.* | *.xpm2 | *.xpm2.* )
+            filetype=xpm
+            ;;
+        *.pi1 | *.pi1.* )
+            filetype=pi1
+            ;;
+        *.pi3 | *.pi3.* )
+            filetype=pi3
+            ;;
+        *.spu | *.spu.* )
+            filetype=spu
+            ;;
+        *.spc | *.spc.* )
+            filetype=spc
+            ;;
+        *.ybm | *.ybm.* | *.face | *.face.* )
+            filetype=ybm
+            ;;
+        *.mda | *.mdp )
+            filetype=mda
+            ;;
+        * )
+            filetype=unknown
+            ;;
+        esac
+}
+
+
+
+determineType () {
+
+# $1 is the name of the file that contains the subject file's contents
+# $2 is the mime type or "unknown"
+# $3 is the type description from 'file'
+
+    if [ "$2" = "unknown" ]; then
+        filetype="unknown"
+    else
+        computeTypeFromMimeType "$2"
+        fi
+
+    if [ "$filetype" = "unknown" ]; then
+        computeTypeFromTypeDescription "$3"
+        fi
+
+    if [ "$filetype" = "unknown" ]; then
+        computeTypeFromFilenameExtension "$4"
+        fi
+}
+
+
+
+convertIt () {
+# Based on the file type computed, do the conversion
+
+# $1 is the input file name
+# $2 is our file type code
+
+case "$2" in
+
+    pnm )
+        cat "$file"
+        ;;
+
+    uuencoded )
+        newfile="$tempdir/atn.decode.$$"
+        rm -f "$newfile"
+        (echo begin 600 $newfile; sed 1d < "$file") | uudecode
+        anytopnm "$newfile"
+        ;;
+
+    bzip2 )
+        bzip2 -dk < "$file" | anytopnm -
+        ;;
+
+    bzip )
+        bzip -dk < "$file" | anytopnm -
+        ;;
+
+    gzip )
+        gzip --decompress --to-stdout < "$file" | anytopnm -
+        ;;
+
+    compress )
+        uncompress -c < "$file" | anytopnm -
+        ;;
+
+    btoa )
+        atob < "$file" | anytopnm -
+        ;;
+
+    rast )
+        rasttopnm "$file"
+        ;;
+
+    gif )
+        giftopnm "$file"
+        ;;
+
+    tiff )
+        tifftopnm "$file"
+        ;;
+
+    ilbm )
+        ilbmtoppm "$file"
+        ;;
+
+    lispm )
+        lispmtopgm "$file"
+        ;;
+
+    pcx )
+        pcxtoppm "$file"
+        ;;
+
+    ybm )
+        ybmtopbm "$file"
+        ;;
+
+    xpm )
+        xpmtoppm < "$file"
+        ;;
+
+    # This has to come after all other 'text' files, or you may be
+    # disappointed.
+    text )
+        pbmtext -builtin fixed < "$file"
+        ;;
+
+    jfif )
+        jpegtopnm "$file"
+        ;;
+
+    png )
+        pngtopnm "$file"
+        ;;
+
+    mda )
+        mdatopbm -d -- "$file"
+        ;;
+
+    bmp )
+        bmptoppm "$file"
+        ;;
+    
+    xbm )
+        xbmtopbm "$file"
+        ;;
+    macp )
+        macptopbm "$file"
+        ;;
+    g3 )
+        g3topbm "$file"
+        ;;
+    xwd )
+        xwdtopnm "$file"
+        ;;
+    brush )
+        brushtopbm "$file"
+        ;;
+    img )
+        gemtopbm "$file"
+        ;;
+    pcx ) 
+        pcxtoppm "$file"
+        ;;
+    pict )
+        picttoppm "$file"
+        ;;
+    fs )
+        fstopgm "$file"
+        ;;
+    hips )
+        hipstopgm "$file"
+        ;;
+    fits )
+        fitstopnm "$file"
+        ;;
+    mtv )
+        mtvtoppm "$file"
+        ;;
+    qrt )
+        qrttoppm "$file"
+        ;;
+    tga )
+        tgatoppm "$file"
+        ;;
+    xim )
+        ximtoppm "$file"
+        ;;
+    pi1 )
+        pi1toppm "$file"
+        ;;
+    pi3 )
+        pi3topbm "$file"
+        ;;
+    spu )
+        sputoppm "$file"
+        ;;
+    spc )
+        spctoppm "$file"
+        ;;
+    * )
+        echo "$progname: INTERNAL ERROR.  Illegal value of filetype variable"
+        exit 1
+        ;;
+
+    esac
+}
+
+
+
+###############################################################################
+#                                MAINLINE
+###############################################################################
+
+progname=$0
+
+if [ $# -gt 1 ] ; then
+    echo "Too many arguments: $#.  The only valid argument is the" \
+         "input file name." 1>&2
+    exit 1
+elif [ $# -eq 1 ] ; then
+    inputFile="$1"
+else
+    inputFile="-"
+fi
+
+tempdir="${TMPDIR-/tmp}/anytopnm.$$"
+mkdir $tempdir || { echo "Could not create temporary file. Exiting."; exit 1;}
+chmod 700 $tempdir
+
+trap 'rm -rf $tempdir' 0
+
+# Take out all spaces
+# Find the filename extension for last-ditch efforts later
+
+# We used to do this, but it doesn't work with all Awks.  The sed below
+# is universal.  2005.11.02.
+#fileExtension=`echo "$inputFile" | \
+#               $AWK '{gsub(" ","");gsub(".*\\\\.",".");print}'`
+
+fileExtension=`echo "$inputFile" | sed 's/ //;s/.*\././g'`
+
+file="$tempdir/atn.stdin.$$"
+
+putInputIntoTempfile "$inputFile" "$file"
+
+setMimeType "$file"
+
+typeDescription=`file "$file" | cut -d: -f2- | cut -c2`
+
+determineType "$file" "$mimeType" "$typeDescription" "$fileExtension"
+
+if [ "$filetype" = "unknown" ]; then
+    echo "$progname: unknown file type.  " \
+        "'file' says mime type is '$mimeType', " 1>&2
+    echo "type description is '$typeDescription'" 1>&2
+    exit 1
+    fi
+
+convertIt $file $filetype
+
+exit 0
diff --git a/converter/other/bmepsoe.c b/converter/other/bmepsoe.c
new file mode 100644
index 00000000..cdb52779
--- /dev/null
+++ b/converter/other/bmepsoe.c
@@ -0,0 +1,539 @@
+/* 
+ * This was adapted for Netpbm from bmpesoe.c in Dirk Krause's Bmeps package
+ * by Bryan Henderson on 2005.01.05.
+ *
+ * Differences:
+ *   - doesn't require Bmeps configuration stuff (bmepsco.h)
+ *   - doesn't include pngeps.h
+ *   - doesn't have test scaffold code
+ *   - a few compiler warnings fixed
+ *
+ * Copyright (C) 2000 - Dirk Krause
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ * In this package the copy of the GNU Library General Public License
+ * is placed in file COPYING.
+ */
+
+#include "bmepsoe.h"
+
+#define RL_RUNLENGTH(i) (257 - (i))
+#define RL_STRINGLENGTH(i) ((i) - 1)
+#define RL_MAXLENGTH (127)
+
+#define FINALOUTPUT(c) fputc((c),o->out)
+
+void oe_init(Output_Encoder *o, FILE *out, int mode, int rate, int *buf,
+  Bytef *flib, size_t flis, Bytef *flob, size_t flos
+)
+{
+  
+  o->out = out;
+  o->mode = mode;
+  o->textpos = 0;
+  o->a85_value = 0UL; o->a85_consumed = 0;
+  o->a85_0 = 1UL;
+  o->a85_1 = 85UL;
+  o->a85_2 = 85UL * 85UL;
+  o->a85_3 = 85UL * o->a85_2;
+  o->a85_4 = 85UL * o->a85_3;
+  o->rl_lastbyte = 0;
+  o->rl_buffer = buf;
+  o->rl_bufused = 0;
+  o->rl_state = 0;
+  o->bit_value = 0;
+  o->bit_consumed = 0;
+  o->flate_rate = rate;
+  if(o->flate_rate < 0) {
+    o->mode &= (~OE_FLATE);
+  }
+  if(o->flate_rate > 9) {
+    o->flate_rate = 9;
+  }
+  if((o->mode & OE_FLATE) && flib && flis && flob && flos) {
+    (o->flate_stream).zfree = (free_func)0;
+    (o->flate_stream).zalloc = (alloc_func)0;
+    (o->flate_stream).opaque = (voidpf)0;
+    if(deflateInit((&(o->flate_stream)),o->flate_rate) != Z_OK) {
+      o->mode &= (~OE_FLATE);
+    }
+  }
+  o->fl_i_buffer = flib;
+  o->fl_o_buffer = flob;
+  o->fl_i_size   = flis;
+  o->fl_o_size   = flos;
+  o->fl_i_used   = 0;
+  
+}
+
+static char hexdigits[] = {
+  "0123456789ABCDEF"
+};
+
+static void asciihex_add(Output_Encoder *o, int b)
+{
+  
+  FINALOUTPUT(hexdigits[(b/16)]) ;
+  FINALOUTPUT(hexdigits[(b%16)]) ;
+  o->textpos = o->textpos + 2;
+  if(o->textpos >= 64) {
+    FINALOUTPUT('\n') ;
+    o->textpos = 0;
+  }
+  
+}
+
+static void asciihex_flush(Output_Encoder *o)
+{
+  
+  if(o->textpos > 0) {
+    FINALOUTPUT('\n') ;
+    o->textpos = 0;
+  }
+  
+}
+
+static char   ascii85_char(unsigned long x)
+{
+  unsigned u;
+  int      i;
+  char back;
+  back = (char)0;
+  u = (unsigned)x;
+  i = (int)u;
+  i += 33;
+  back = (char)i;
+  return back;
+}
+
+
+
+static void
+ascii85_output(Output_Encoder * const o) {
+    unsigned long value;
+
+    value = o->a85_value;  /* initial value */
+
+    if (value == 0 && o->a85_consumed == 4) {
+        FINALOUTPUT('z');
+        ++o->textpos;
+    } else {
+        unsigned int i;
+        unsigned int j;
+        char buffer[6];
+
+        buffer[0] = ascii85_char(value / (o->a85_4));
+        value = value % (o->a85_4);
+        buffer[1] = ascii85_char(value / (o->a85_3));
+        value = value % (o->a85_3);
+        buffer[2] = ascii85_char(value / (o->a85_2));
+        value = value % (o->a85_2);
+        buffer[3] = ascii85_char(value / (o->a85_1));
+        value = value % (o->a85_1);
+        buffer[4] = ascii85_char(value);
+        buffer[5] = '\0';
+
+        i = o->a85_consumed + 1;
+        o->textpos += i;
+
+        for (j = 0; j < i; ++j)
+            FINALOUTPUT(buffer[j]);
+    }
+    if (o->textpos >= 64) {
+        FINALOUTPUT('\n');
+        o->textpos = 0;
+    }
+}
+
+
+
+static void ascii85_add(Output_Encoder *o, int b)
+{
+  unsigned u;
+  unsigned long ul;
+  
+  u = (unsigned)b;
+  ul = (unsigned long)u;
+  o->a85_value = 256UL * (o->a85_value) + ul;
+  o->a85_consumed = o->a85_consumed + 1;
+  if(o->a85_consumed >= 4) {
+    ascii85_output(o);
+    o->a85_value = 0UL;
+    o->a85_consumed = 0;
+  }
+  
+}
+
+static void ascii85_flush(Output_Encoder *o)
+{
+  int i;
+  
+  if(o->a85_consumed > 0) {
+    i = o->a85_consumed;
+    while(i < 4) {
+      o->a85_value = 256UL * o->a85_value;
+      i++;
+    }
+    ascii85_output(o);
+    o->a85_value = 0UL;
+    o->a85_consumed = 0;
+  }
+  if(o->textpos > 0) {
+    FINALOUTPUT('\n') ;
+    o->textpos = 0;
+  }
+  
+}
+
+static void after_flate_add(Output_Encoder *o, int b)
+{
+  
+  if(o->mode & OE_ASC85) {
+    ascii85_add(o,b);
+  } else {
+    asciihex_add(o,b);
+  }
+  
+}
+
+static void do_flate_flush(Output_Encoder *o, int final)
+{
+  Bytef *iptr, *optr, *xptr;
+  uLong  is, os, xs;
+  int err, must_continue;
+  
+  iptr = o->fl_i_buffer; optr = o->fl_o_buffer;
+  is = o->fl_i_size; os = o->fl_o_size;
+  
+  if(iptr && optr && is && os) {
+    is = o->fl_i_used;
+    if(is) {
+      (o->flate_stream).next_in = iptr;
+      (o->flate_stream).avail_in = is;
+      if(final) { 
+    must_continue = 1;
+    while(must_continue) {
+      (o->flate_stream).next_out = optr;
+      (o->flate_stream).avail_out = os;
+      must_continue = 0;
+      err = deflate(&(o->flate_stream), Z_FINISH);
+      switch(err) {
+        case Z_STREAM_END: { 
+              xptr = optr;
+          
+          xs = os - ((o->flate_stream).avail_out);
+          while(xs--) {
+        after_flate_add(o, (*(xptr++)));
+          }
+        } break;
+        case Z_OK : {
+          must_continue = 1;
+              xptr = optr;
+          
+          xs = os - ((o->flate_stream).avail_out);
+          while(xs--) {
+        after_flate_add(o, (*(xptr++)));
+          }
+        } break;
+        default : { 
+        } break;
+      }
+    }
+      } else { 
+    must_continue = 1;
+    while(must_continue) {
+      must_continue = 0;
+      (o->flate_stream).avail_out = os; (o->flate_stream).next_out = optr;
+      err = deflate(&(o->flate_stream), 0);
+      switch(err) {
+        case Z_OK: {
+          if((o->flate_stream).avail_in) {
+        must_continue = 1;
+          }
+          
+          xptr = optr; xs = os - ((o->flate_stream).avail_out);
+          while(xs--) {
+        after_flate_add(o, (*(xptr++)));
+          }
+        } break;
+        default : { 
+        } break;
+      }
+    }
+      }
+    }
+  }
+  
+}
+
+static void flate_add(Output_Encoder *o, int b)
+{
+  Byte bt;
+  Bytef *btptr;
+  
+  btptr = o->fl_i_buffer;
+  if(btptr) { 
+    bt = (Byte)b; 
+    btptr[(o->fl_i_used)] = bt;
+    o->fl_i_used += 1UL;
+    if(o->fl_i_used >= o->fl_i_size) {
+      do_flate_flush(o, 0);
+      o->fl_i_used = 0UL;
+    }
+  }
+  
+}
+
+static void after_flate_flush(Output_Encoder *o)
+{
+  
+  if(o->mode & OE_ASC85) {
+    ascii85_flush(o);
+  } else {
+    asciihex_flush(o);
+  }
+  
+}
+
+static void flate_flush(Output_Encoder *o)
+{
+  do_flate_flush(o,1);
+  deflateEnd(&(o->flate_stream));
+  after_flate_flush(o);
+}
+
+
+static void after_rl_add(Output_Encoder *o, int b)
+{
+  
+  if(o->mode & OE_FLATE) {
+    flate_add(o,b);
+  } else {
+    after_flate_add(o,b);
+  }
+  
+}
+
+static void rl_add(Output_Encoder *o, int b)
+{
+  int lgt, i;
+  int *buffer;
+  /* ##### */
+  
+  buffer = o->rl_buffer;
+  lgt = o->rl_bufused;
+  if(buffer) {
+    
+    if(lgt > 0) {
+      if(o->rl_lastbyte == b) {
+    switch(o->rl_state) {
+      case 2: {
+        buffer[lgt++] = b;
+        o->rl_bufused = lgt;
+        o->rl_state = 2;
+        o->rl_lastbyte = b;
+        if(lgt >= RL_MAXLENGTH) {
+          after_rl_add(o, RL_RUNLENGTH(lgt));
+          after_rl_add(o, b);
+          o->rl_bufused = 0;
+          o->rl_state = 0;
+          o->rl_lastbyte = b;
+        }
+      } break;
+      case 1: {
+        buffer[lgt++] = b;
+        o->rl_bufused = lgt;
+        o->rl_state = 2;
+        o->rl_lastbyte = b;
+        lgt = lgt - 3;
+        if(lgt > 0) {
+          after_rl_add(o, RL_STRINGLENGTH(lgt));
+          for(i = 0; i < lgt; i++) {
+        after_rl_add(o, buffer[i]);
+          }
+          buffer[0] = buffer[1] = buffer[2] = b;
+          o->rl_bufused = 3;
+          o->rl_state = 2;
+          o->rl_lastbyte = b;
+        }
+      } break;
+      default: {
+        buffer[lgt++] = b;
+        o->rl_bufused = lgt;
+        o->rl_state = 1;
+        o->rl_lastbyte = b;
+        if(lgt >= RL_MAXLENGTH) {
+          lgt = lgt - 2;
+          after_rl_add(o, RL_STRINGLENGTH(lgt));
+          for(i = 0; i < lgt; i++) {
+        after_rl_add(o, buffer[i]);
+          }
+          buffer[0] = buffer[1] = b;
+          o->rl_bufused = 2;
+          o->rl_state = 1;
+          o->rl_lastbyte = b;
+        }
+      } break;
+    }
+      } else {
+    if(o->rl_state == 2) {
+      after_rl_add(o, RL_RUNLENGTH(lgt));
+      after_rl_add(o, (o->rl_lastbyte));
+      buffer[0] = b; o->rl_bufused = 1; o->rl_lastbyte = b;
+      o->rl_state = 0;
+    } else {
+      buffer[lgt++] = b;
+      o->rl_bufused = lgt;
+      o->rl_lastbyte = b;
+      if(lgt >= RL_MAXLENGTH) {
+        after_rl_add(o, RL_STRINGLENGTH(lgt));
+        for(i = 0; i < lgt; i++) {
+          after_rl_add(o, buffer[i]);
+        }
+        o->rl_bufused = 0;
+      }
+      o->rl_state = 0;
+    }
+      }
+    } else {
+      buffer[0] = b;
+      o->rl_bufused = 1;
+      o->rl_lastbyte = b;
+    }
+    o->rl_lastbyte = b;
+    
+  } else { 
+    after_rl_add(o,0);
+    after_rl_add(o,b);
+  }
+  
+}
+
+static void after_rl_flush(Output_Encoder *o)
+{
+  
+  if(o->mode & OE_FLATE) {
+    flate_flush(o);
+  } else {
+    after_flate_flush(o);
+  }
+  
+}
+
+static void rl_flush(Output_Encoder *o)
+{
+  int lgt;
+  int *buffer;
+  int i;
+  
+  buffer = o->rl_buffer;
+  lgt = o->rl_bufused;
+  if(lgt > 0) {
+    if(o->rl_state == 2) {
+      i = o->rl_lastbyte;
+      after_rl_add(o,RL_RUNLENGTH(lgt));
+      after_rl_add(o,i);
+    } else {
+      after_rl_add(o,RL_STRINGLENGTH(lgt));
+      for(i = 0; i < lgt; i++) {
+    after_rl_add(o,buffer[i]);
+      }
+    }
+  }
+  after_rl_flush(o);
+  
+}
+
+static void internal_byte_add(Output_Encoder *o, int b)
+{
+  
+  if((o->mode) & OE_RL) {
+    rl_add(o,b);
+  } else {
+    after_rl_add(o,b);
+  }
+  
+}
+
+static void internal_byte_flush(Output_Encoder *o)
+{
+  
+  if((o->mode) & OE_RL) {
+    rl_flush(o);
+  } else {
+    after_rl_flush(o);
+  }
+  
+}
+
+void oe_bit_add(Output_Encoder *o, int b)
+{
+  
+  o->bit_value = 2 * o->bit_value + (b ? 1 : 0);
+  o->bit_consumed = o->bit_consumed + 1;
+  if(o->bit_consumed >= 8) {
+    o->bit_consumed = 0;
+    internal_byte_add(o, (o->bit_value));
+    o->bit_value = 0;
+  }
+  
+}
+
+void oe_bit_flush(Output_Encoder *o)
+{
+  
+  if(o->bit_consumed) {
+    int v, i;
+    v = o->bit_value;
+    i = o->bit_consumed;
+    while(i < 8) {
+      i++;
+      v = v * 2;
+    }
+    internal_byte_add(o,v);
+    o->bit_value = 0;
+    o->bit_consumed = 0;
+  }
+  internal_byte_flush(o);
+  
+}
+
+void oe_byte_add(Output_Encoder *o, int b)
+{
+  
+  if(o->bit_consumed) {
+    int testval,i;
+    testval = 128;
+    for(i = 0; i < 8; i++) {
+      if(b & testval) {
+    oe_bit_add(o,1);
+      } else {
+    oe_bit_add(o,0);
+      }
+      testval = testval / 2;
+    }
+  } else {
+    internal_byte_add(o,b);
+  }
+  
+}
+
+void oe_byte_flush(Output_Encoder *o)
+{
+  
+  oe_bit_flush(o);
+  
+}
diff --git a/converter/other/bmepsoe.h b/converter/other/bmepsoe.h
new file mode 100644
index 00000000..99c59f77
--- /dev/null
+++ b/converter/other/bmepsoe.h
@@ -0,0 +1,79 @@
+/* 
+ * libbmeps - Bitmap to EPS conversion library
+ * Copyright (C) 2000 - Dirk Krause
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ * In this package the copy of the GNU Library General Public License
+ * is placed in file COPYING.
+ */
+
+#ifndef BMPESOE_H_INCLUDED
+#define BMPESOE_H_INCLUDED
+
+#include <stdio.h>
+
+#include <zlib.h>
+
+typedef struct {
+  int mode;
+  FILE *out;
+  int textpos;
+  unsigned long a85_value;
+  int      a85_consumed;
+  unsigned long a85_4;
+  unsigned long a85_3;
+  unsigned long a85_2;
+  unsigned long a85_1;
+  unsigned long a85_0;
+  int rl_lastbyte;
+  int *rl_buffer;
+  int rl_bufused;
+  int rl_state;
+  int bit_value;
+  int bit_consumed;
+  z_stream flate_stream;
+  Bytef *fl_i_buffer;
+  Bytef *fl_o_buffer;
+  uLong  fl_i_size;
+  uLong  fl_o_size;
+  uLong  fl_i_used;
+  int    flate_rate;
+} Output_Encoder;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void oe_init(Output_Encoder *o, FILE *out, int mode, int rate, int *buf,
+  Bytef *flib, size_t flis, Bytef *flob, size_t flos
+);
+void oe_byte_add(Output_Encoder *o, int b);
+void oe_byte_flush(Output_Encoder *o);
+void oe_bit_add(Output_Encoder *o, int b);
+void oe_bit_flush(Output_Encoder *o);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+
+#define OE_ASC85 1
+#define OE_FLATE 2
+#define OE_RL    4
+
+#endif
+
diff --git a/converter/other/bmptopnm.c b/converter/other/bmptopnm.c
new file mode 100644
index 00000000..3ba1d77c
--- /dev/null
+++ b/converter/other/bmptopnm.c
@@ -0,0 +1,1477 @@
+/*****************************************************************************
+                                    bmptopnm.c
+******************************************************************************
+ 
+ Bmptopnm - Converts from a Microsoft Windows or OS/2 .BMP file to a
+ PBM, PGM, or PPM file.
+
+ This program was formerly called Bmptoppm (and generated only PPM output).
+ The name was changed in March 2002.
+
+ Copyright (C) 1992 by David W. Sanderson.
+ 
+ Permission to use, copy, modify, and distribute this software and its
+ documentation for any purpose and without fee is hereby granted,
+ provided that the above copyright notice appear in all copies and
+ that both that copyright notice and this permission notice appear
+ in supporting documentation.  This software is provided "as is"
+ without express or implied warranty.
+
+*****************************************************************************/
+#include <string.h>
+#include <limits.h>
+#include <assert.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "bmp.h"
+
+/* MAXCOLORS is the maximum size of a color map in a BMP image */
+#define MAXCOLORS       256
+
+static xelval const bmpMaxval = 255;
+    /* The maxval for intensity values in a BMP image -- either in a
+       truecolor raster or in a colormap
+    */
+
+enum rowOrder {BOTTOMUP, TOPDOWN};
+
+struct bitPosition {
+    /* mask and shift count to describe a set of bits in a binary value.
+
+       Example: if 16 bits are laid out as XRRRRRGGGGGBBBBB then the shift
+       count for the R component is 10 and the mask is 0000000000011111.
+    */
+    unsigned int shift;
+        /* How many bits right you have to shift the value to get the subject
+           bits in the least significant bit positions.
+        */
+    unsigned int mask;
+        /* Has one bits in positions where the subject bits are after
+           shifting.
+        */
+};
+
+struct pixelformat {
+    /* The format of a pixel representation from the raster.  i.e. which 
+       bits apply to red, green, blue, and transparency 
+    */
+    struct bitPosition red;
+    struct bitPosition blu;
+    struct bitPosition grn;
+    struct bitPosition trn;
+    
+    bool conventionalBgr;
+        /* This means that the above bit positions are just the conventional
+           BGR format -- one byte Blue, one byte Green, one byte Red,
+           no alpha.  Though it's totally redundant with the members above,
+           this member speeds up computation:  We've never actually seen
+           a BMP file that doesn't use conventional BGR, and it doesn't
+           require any masking or shifting at all to interpret.
+        */
+};
+
+struct bmpInfoHeader {
+    enum rowOrder rowOrder;
+    int cols;
+    int rows;
+    unsigned int cBitCount;
+        /* Number of bits in the BMP file that each pixel occupies. */
+    enum bmpClass class;
+    bool bitFields;
+        /* The raster values are arranged in arbitrary bit fields as
+           described by the "mask" values in the header, rather than
+           fixed formats.
+        */
+    int cmapsize;
+        /* Size in bytes of the colormap (palette) in the BMP file */
+    unsigned int imageSize;
+        /* Size in bytes of the image data.  We only reference this 
+           when the image is compressed. */    
+    unsigned short cPlanes;
+    unsigned long int compression;
+    struct pixelformat pixelformat;
+};
+
+
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec;  /* Filespecs of input files */
+    int verbose;    /* -verbose option */
+};
+
+static const char *ifname;
+
+
+
+static void
+parse_command_line(int argc, char ** argv,
+                   struct cmdline_info *cmdline_p) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct *option_def = malloc(100*sizeof(optStruct));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct2 opt;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENTRY(0,   "verbose",     OPT_FLAG,   &cmdline_p->verbose,         0);
+ 
+    /* Set the defaults */
+    cmdline_p->verbose = FALSE;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions2(&argc, argv, opt, 0);
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (argc-1 == 0) 
+        cmdline_p->input_filespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdline_p->input_filespec = argv[1];
+
+}
+
+
+
+static const char er_read[] = "%s: read error";
+
+static int
+GetByte(FILE * const fp) {
+
+    int             v;
+
+    if ((v = getc(fp)) == EOF)
+        pm_error(er_read, ifname);
+
+    return v;
+}
+
+
+
+static short
+GetShort(FILE * const fp) {
+
+    short           v;
+
+    if (pm_readlittleshort(fp, &v) == -1)
+        pm_error(er_read, ifname);
+
+    return v;
+}
+
+static short
+GetBigShort(FILE * const fp) {
+
+    short           v;
+
+    if (pm_readbigshort(fp, &v) == -1)
+        pm_error(er_read, ifname);
+
+    return v;
+}
+
+
+
+static long
+GetLong(FILE * const fp) {
+
+    long v;
+
+    if (pm_readlittlelong(fp, &v) == -1)
+        pm_error(er_read, ifname);
+
+    return v;
+}
+
+
+
+typedef struct {
+    long dummy[12];
+} cieXyzTriple;
+
+static cieXyzTriple
+GetCieXyzTriple(FILE * const fp) {
+
+    cieXyzTriple v;
+    unsigned int i;
+
+    for (i = 0; i < 12; ++i) 
+        if (pm_readlittlelong(fp, &v.dummy[i]) == -1)
+            pm_error(er_read, ifname);
+
+    return v;
+}
+
+
+
+static void
+readOffBytes(FILE * const fp, unsigned int const nbytes) {
+/*----------------------------------------------------------------------------
+   Read 'nbytes' from file 'fp'.  Abort program if read error.
+-----------------------------------------------------------------------------*/
+    int i;
+    
+    for(i = 0; i < nbytes; ++i) {
+        int rc;
+        rc = getc(fp);
+        if (rc == EOF)
+            pm_error(er_read, ifname);
+    }
+}
+
+
+
+static void
+BMPreadfileheader(FILE *         const ifP, 
+                  unsigned int * const bytesReadP, 
+                  unsigned int * const offBitsP) {
+
+    unsigned short  xHotSpot;
+    unsigned short  yHotSpot;
+    unsigned long   offBits;
+    unsigned long int fileSize;
+
+
+    if (GetByte(ifP) != 'B')
+        pm_error("'%s' is not a BMP file.  (It doesn't start with 'BM')",
+                 ifname);
+    if (GetByte(ifP) != 'M')
+        pm_error("'%s' is not a BMP file.  (It doesn't start with 'BM')",
+                 ifname);
+
+
+    fileSize = GetLong(ifP);  /* This is not always reliable. */
+    xHotSpot  = GetShort(ifP);
+    yHotSpot  = GetShort(ifP);
+    offBits   = GetLong(ifP);
+
+    *offBitsP = offBits;
+
+    *bytesReadP = 14;
+}
+
+
+
+static void
+readOs2InfoHeader(FILE *                 const ifP,
+                  struct bmpInfoHeader * const headerP) {
+
+    headerP->class = C_OS2;
+
+    headerP->cols = GetShort(ifP);
+    headerP->rows = GetShort(ifP);
+    headerP->rowOrder = BOTTOMUP;
+    headerP->cPlanes = GetShort(ifP);
+    headerP->cBitCount = GetShort(ifP);
+    /* I actually don't know if the OS/2 BMP format allows
+       cBitCount > 8 or if it does, what it means, but ppmtobmp
+       creates such BMPs, more or less as a byproduct of creating
+       the same for Windows BMP, so we interpret cBitCount > 8 the
+       same as for Windows.
+    */
+    if (headerP->cBitCount <= 8)
+        headerP->cmapsize = 1 << headerP->cBitCount;
+    else if (headerP->cBitCount == 24)
+        headerP->cmapsize = 0;
+    /* There is a 16 bit truecolor format, but we don't know how the
+       bits are divided among red, green, and blue, so we can't handle it.
+    */
+    else
+        pm_error("Unrecognized bits per pixel in OS/2 BMP file header: %d",
+                 headerP->cBitCount);
+                 
+    headerP->compression = COMP_RGB;
+    
+    pm_message("OS/2 BMP, %dx%dx%d",
+               headerP->cols, headerP->rows, headerP->cBitCount);
+}
+
+
+
+static void
+readWindowsBasic40ByteInfoHeader(FILE *                 const ifP,
+                                 struct bmpInfoHeader * const headerP) {
+/*----------------------------------------------------------------------------
+   Read from the file stream 'ifP' the basic BMP Info header.  This does
+   not include any Info header extension.  The Info header is the data
+   that comes after the BMP file header.
+
+   Return the information from the info header as *headerP.
+-----------------------------------------------------------------------------*/
+    int colorsimportant;   /* ColorsImportant value from header */
+    int colorsused;        /* ColorsUsed value from header */
+
+    headerP->class = C_WIN;
+
+    headerP->cols = GetLong(ifP);
+    {
+        long const cy = GetLong(ifP);
+        if (cy < 0) {
+            headerP->rowOrder = TOPDOWN;
+            headerP->rows = - cy;
+        } else {
+            headerP->rowOrder = BOTTOMUP;
+            headerP->rows = cy;
+        }
+    }
+    headerP->cPlanes = GetShort(ifP);
+    headerP->cBitCount = GetShort(ifP);
+ 
+    {
+        unsigned long int const compression = GetLong(ifP);
+
+        headerP->bitFields = (compression == COMP_BITFIELDS);
+    
+        if (compression != COMP_RGB && compression != COMP_BITFIELDS &&
+            compression != COMP_RLE4 && compression != COMP_RLE8 ) 
+            pm_error("Input is compressed.  Unsupported encoding method.\n"
+                     "Compression type code = %ld", compression);
+                     
+        if ( (compression == COMP_RLE4 || compression == COMP_RLE8) &&
+              headerP->rowOrder == TOPDOWN )                        
+            pm_error("Invalid BMP header.  Top-down images cannot be compressed.");
+
+        if ( (compression == COMP_RLE4 && headerP->cBitCount !=4) ||
+             (compression == COMP_RLE8 && headerP->cBitCount !=8) )                        
+            pm_error("Invalid BMP header.\n" 
+                     "Compression type (%s) disagrees with number of bits per pixel (%u).",
+                      compression == COMP_RLE4 ? "RLE4" : "RLE8",
+                      headerP->cBitCount);
+
+        headerP->compression = compression;             
+    }
+    /* And read the rest of the junk in the 40 byte header */
+    headerP->imageSize = GetLong(ifP);   /* ImageSize */
+    GetLong(ifP);   /* XpixelsPerMeter */
+    GetLong(ifP);   /* YpixelsPerMeter */
+    colorsused = GetLong(ifP);   /* ColorsUsed */
+    /* See comments in bmp.h for info about the definition of the following
+       word and its relationship to the color map size (*pcmapsize).
+    */
+    colorsimportant = GetLong(ifP);  /* ColorsImportant */
+
+    if (headerP->cBitCount <= 8) {
+        if (colorsused != 0) {
+            if (colorsused > 1 << headerP->cBitCount)
+                pm_error("Invalid BMP header.  Says %u bits per pixel, "
+                         "but %d colors used", 
+                         headerP->cBitCount, colorsused);
+            else
+                headerP->cmapsize = colorsused;
+        } else 
+            headerP->cmapsize = 1 << headerP->cBitCount;
+    } else if (headerP->cBitCount == 24 || 
+               headerP->cBitCount == 16 || 
+               headerP->cBitCount == 32)
+        headerP->cmapsize = 0;
+    else
+        pm_error("Unrecognized bits per pixel in Windows BMP file header: %d",
+                 headerP->cBitCount);
+}
+
+
+
+static unsigned int
+lsbZeroCount(unsigned int const mask)
+/*----------------------------------------------------------------------------
+   Return the number of consecutive zeroes in the mask 'mask', starting with
+   the least significant bit and going up.  E.g. for 0x20, it would be 5.
+   
+   Use GCC built-in when available.
+-----------------------------------------------------------------------------*/
+
+#if ( defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 304) )
+{
+      return ( mask==0 ? sizeof(mask)*8 : __builtin_ctz(mask) );
+}
+#else
+{
+      unsigned int i=0;
+    
+      while (((mask >> i) & 0x1) == 0 && i < sizeof(mask)*8)
+        ++i;
+
+      return i;
+
+}
+#endif
+
+
+static struct bitPosition
+bitPositionFromMask(long const bmpMask) {
+    struct bitPosition retval;
+
+    retval.shift = lsbZeroCount(bmpMask);
+    retval.mask  = bmpMask >> retval.shift;
+
+    return retval;
+}
+
+
+
+static void
+computeConventionalBgr(struct pixelformat * const fP,
+                       unsigned int         const bitCount) {
+
+    switch (bitCount) {
+    case 24:
+        fP->conventionalBgr =
+            fP->red.shift ==  0 && fP->red.mask == 0xFF &&
+            fP->grn.shift ==  8 && fP->grn.mask == 0xFF &&
+            fP->blu.shift == 16 && fP->blu.mask == 0xFF &&
+            fP->trn.mask == 0
+            ;
+        break;
+    case 32:
+        fP->conventionalBgr =
+            fP->red.shift ==  8  && fP->red.mask == 0xFF &&
+            fP->grn.shift == 16  && fP->grn.mask == 0xFF &&
+            fP->blu.shift == 24  && fP->blu.mask == 0xFF &&
+            fP->trn.mask == 0
+            ;
+        break;
+    default:
+        fP->conventionalBgr = FALSE;
+    }
+}
+
+
+
+static struct pixelformat
+defaultPixelformat(unsigned int const bitCount) {
+
+    struct pixelformat retval;
+
+    switch (bitCount) {
+    case 16:
+        retval.conventionalBgr = FALSE;
+        retval.red.shift = 10;
+        retval.grn.shift = 5;
+        retval.blu.shift = 0;
+        retval.trn.shift = 0;
+        retval.red.mask = 0x1f;  /* 5 bits */
+        retval.grn.mask = 0x1f;  /* 5 bits */
+        retval.blu.mask = 0x1f;  /* 5 bits */
+        retval.trn.mask = 0;
+        break;
+    case 24:
+    case 32:
+        retval.conventionalBgr = TRUE;
+        retval.red.shift = 16;
+        retval.grn.shift = 8;
+        retval.blu.shift = 0;
+        retval.trn.shift = 0;
+        retval.red.mask = 0xff;  /* 8 bits */
+        retval.grn.mask = 0xff;  /* 8 bits */
+        retval.blu.mask = 0xff;  /* 8 bits */
+        retval.trn.mask = 0;
+        break;
+    default:
+        /* colormapped - masks are undefined */
+        break;
+    }
+
+    return retval;
+}
+
+
+
+static void
+readV4InfoHeaderExtension(FILE *                 const ifP, 
+                          struct bmpInfoHeader * const headerP) {
+
+    if (headerP->bitFields) {
+        headerP->pixelformat.red = bitPositionFromMask(GetLong(ifP));
+        headerP->pixelformat.grn = bitPositionFromMask(GetLong(ifP));
+        headerP->pixelformat.blu = bitPositionFromMask(GetLong(ifP));
+        headerP->pixelformat.trn = bitPositionFromMask(GetLong(ifP));
+
+        computeConventionalBgr(&headerP->pixelformat, headerP->cBitCount);
+    } else
+        headerP->pixelformat = defaultPixelformat(headerP->cBitCount);
+
+    GetLong(ifP);  /* Color space */
+    GetCieXyzTriple(ifP);  /* Endpoints */
+    GetLong(ifP);  /* GammaRed */
+    GetLong(ifP);  /* GammaGreen */
+    GetLong(ifP);  /* GammaBlue */
+} 
+
+
+
+static void
+defaultV4InfoHeaderExtension(struct bmpInfoHeader * const headerP) {
+
+    headerP->pixelformat = defaultPixelformat(headerP->cBitCount);
+
+}
+
+
+
+static void
+readWindowsInfoHeader(FILE *                 const ifP, 
+                      unsigned int           const cInfoHeaderSize,
+                      struct bmpInfoHeader * const headerP) {
+
+    /* There are 3 major formats of Windows
+       BMP, identified by the 3 info header lengths.  The original
+       one is 40 bytes.  The "V4 header" is 108 bytes and was
+       new with Windows 95 and NT 4.0.  The "V5 header" is 124 bytes
+       and was new with Windows 98 and Windows 2000.
+    */
+    readWindowsBasic40ByteInfoHeader(ifP, headerP);
+
+    if (cInfoHeaderSize >= 108) 
+        readV4InfoHeaderExtension(ifP, headerP);
+    else 
+        defaultV4InfoHeaderExtension(headerP);
+
+    if (cInfoHeaderSize >= 124) {
+        /* Read off the V5 info header extension. */
+        GetLong(ifP);  /* Intent */
+        GetLong(ifP);  /* ProfileData */
+        GetLong(ifP);  /* ProfileSize */
+        GetLong(ifP);  /* Reserved */
+    }
+
+    pm_message("Windows BMP, %dx%dx%d",
+               headerP->cols, headerP->rows, headerP->cBitCount);
+}
+
+
+
+static void
+BMPreadinfoheader(FILE *                 const ifP, 
+                  unsigned int *         const bytesReadP,
+                  struct bmpInfoHeader * const headerP) {
+
+    unsigned int const cInfoHeaderSize = GetLong(ifP);
+
+    switch (cInfoHeaderSize) {
+    case 12:
+        readOs2InfoHeader(ifP, headerP);
+        break;
+    case 40: 
+    case 108:
+    case 124:
+        readWindowsInfoHeader(ifP, cInfoHeaderSize, headerP);
+        break;
+    default:
+        pm_error("%s: unknown Info Header size: %u bytes", 
+                 ifname, cInfoHeaderSize);
+        break;
+    }
+    *bytesReadP = cInfoHeaderSize;
+}
+
+
+
+static void
+BMPreadcolormap(FILE *         const ifP, 
+                int            const class, 
+                xel **         const colormapP, 
+                int            const cmapsize,
+                unsigned int * const bytesReadP) {
+/*----------------------------------------------------------------------------
+   Read the color map from the present position in the input BMP file
+   *ifP.
+
+   The map has 'cmapsize' entries in it.  cmapsize == 0 means there is
+   no color map.
+
+   We return a color map as *colormapP.  If there is no color map in the
+   BMP, this is just an arbitrary color map.
+ 
+   'class' is the class of BMP image - Windows or OS/2.
+-----------------------------------------------------------------------------*/
+
+    int i;
+
+    xel * colormap;
+    unsigned int bytesRead;
+
+    colormap = pnm_allocrow(MAX(1,cmapsize));
+    
+    bytesRead = 0;  /* initial value */
+
+    for (i = 0; i < cmapsize; ++i) {
+        /* There is a document that says the bytes are ordered R,G,B,Z,
+           but in practice it appears to be the following instead:
+        */
+        unsigned int r, g, b;
+        
+        b = GetByte(ifP);
+        g = GetByte(ifP);
+        r = GetByte(ifP);
+
+        PNM_ASSIGN(colormap[i], r, g, b);
+
+        bytesRead += 3;
+
+        if (class == C_WIN) {
+            GetByte(ifP);
+            bytesRead += 1;
+        }
+    }
+    *colormapP = colormap;
+    *bytesReadP = bytesRead;
+}
+
+
+
+static void
+extractBitFields(unsigned int       const rasterval,
+                 struct pixelformat const pixelformat,
+                 pixval             const maxval,
+                 pixval *           const rP,
+                 pixval *           const gP,
+                 pixval *           const bP,
+                 pixval *           const aP) {
+
+    unsigned int const rbits = 
+        (rasterval >> pixelformat.red.shift) & pixelformat.red.mask;
+    unsigned int const gbits = 
+        (rasterval >> pixelformat.grn.shift) & pixelformat.grn.mask;
+    unsigned int const bbits = 
+        (rasterval >> pixelformat.blu.shift) & pixelformat.blu.mask;
+    unsigned int const abits = 
+        (rasterval >> pixelformat.trn.shift) & pixelformat.trn.mask;
+    
+    *rP = (unsigned int) rbits * maxval / pixelformat.red.mask;
+    *gP = (unsigned int) gbits * maxval / pixelformat.blu.mask;
+    *bP = (unsigned int) bbits * maxval / pixelformat.grn.mask;
+    *aP = (unsigned int) abits * maxval / pixelformat.trn.mask;
+}        
+
+
+
+static void
+convertRow16(unsigned char      const bmprow[],
+             xel                      xelrow[],
+             int                const cols,
+             struct pixelformat const pixelformat) {
+    /* It's truecolor.  */
+
+    unsigned int col;
+    unsigned int cursor;
+    cursor = 0;
+    for (col=0; col < cols; ++col) {
+        unsigned short const rasterval = (unsigned short)
+            bmprow[cursor+1] << 8 | bmprow[cursor+0];
+
+        pixval r, g, b, a;
+
+        extractBitFields(rasterval, pixelformat, 255, &r, &g, &b, &a);
+
+        PNM_ASSIGN(xelrow[col], r, g, b);
+        
+        cursor += 2;
+    }
+}
+
+
+
+static void
+convertRow24(unsigned char      const bmprow[],
+             xel                      xelrow[],
+             int                const cols,
+             struct pixelformat const pixelformat) {
+    
+    /* It's truecolor */
+    /* There is a document that gives a much different format for
+       24 bit BMPs.  But this seems to be the de facto standard, and is,
+       with a little ambiguity and contradiction resolved, defined in the
+       Microsoft BMP spec.
+    */
+
+    unsigned int col;
+    unsigned int cursor;
+    
+    cursor = 0;
+    for (col = 0; col < cols; ++col) {
+        pixval r, g, b, a;
+
+        if (pixelformat.conventionalBgr) {
+            r = bmprow[cursor+2];
+            g = bmprow[cursor+1];
+            b = bmprow[cursor+0];
+            a = 0;
+        } else {
+            unsigned int const rasterval = 
+                (bmprow[cursor+0] << 16) +
+                (bmprow[cursor+1] << 8) +
+                (bmprow[cursor+2] << 0);
+            
+            extractBitFields(rasterval, pixelformat, 255, &r, &g, &b, &a);
+        }
+        PNM_ASSIGN(xelrow[col], r, g, b);
+        cursor += 3;
+    }
+} 
+
+
+
+static void
+convertRow32(unsigned char      const bmprow[],
+             xel                      xelrow[],
+             int                const cols,
+             struct pixelformat const pixelformat) {
+    
+    /* It's truecolor */
+
+    unsigned int col;
+    unsigned int cursor;
+    cursor = 0;
+    for (col = 0; col < cols; ++col) {
+        pixval r, g, b, a;
+
+        if (pixelformat.conventionalBgr) {
+            /* bmprow[cursor+3] is just padding */
+            r = bmprow[cursor+2];
+            g = bmprow[cursor+1];
+            b = bmprow[cursor+0];
+            a = 0;
+        } else {
+            unsigned int const rasterval = 
+                (bmprow[cursor+0] << 24) +
+                (bmprow[cursor+1] << 16) +
+                (bmprow[cursor+2] << 8) +
+                (bmprow[cursor+3] << 0);
+            
+            extractBitFields(rasterval, pixelformat, 255, &r, &g, &b, &a);
+        }
+
+        PNM_ASSIGN(xelrow[col], 
+                   bmprow[cursor+2], bmprow[cursor+1], bmprow[cursor+0]);
+        cursor += 4;
+    }
+} 
+
+
+
+static void
+convertRow(unsigned char      const bmprow[], 
+           xel                      xelrow[],
+           int                const cols, 
+           unsigned int       const cBitCount, 
+           struct pixelformat const pixelformat,
+           xel                const colormap[]
+           ) {
+/*----------------------------------------------------------------------------
+   Convert a row in raw BMP raster format bmprow[] to a row of xels xelrow[].
+
+   Use maxval 255 for the output xels.
+
+   The BMP image has 'cBitCount' bits per pixel.
+
+   If the image is colormapped, colormap[] is the colormap
+   (colormap[i] is the color with color index i).
+-----------------------------------------------------------------------------*/
+    if (cBitCount == 24) 
+        convertRow24(bmprow, xelrow, cols, pixelformat);
+    else if (cBitCount == 16) 
+        convertRow16(bmprow, xelrow, cols, pixelformat);
+    else if (cBitCount == 32) 
+        convertRow32(bmprow, xelrow, cols, pixelformat);
+    else if (cBitCount == 8) {            
+        /* It's a whole byte colormap index */
+        unsigned int col;
+        for (col = 0; col < cols; ++col)
+            xelrow[col] = colormap[bmprow[col]];
+    } else if (cBitCount < 8) {
+        /* It's a bit field color index */
+        unsigned char const mask = ( 1 << cBitCount ) - 1;
+
+        unsigned int col;
+
+        for (col = 0; col < cols; ++col) {
+            unsigned int const cursor = (col*cBitCount)/8;
+            unsigned int const shift = 8 - ((col*cBitCount) % 8) - cBitCount;
+            unsigned int const index = 
+                (bmprow[cursor] & (mask << shift)) >> shift;
+            xelrow[col] = colormap[index];
+        }
+    } else
+        pm_error("Internal error: invalid cBitCount in convertRow()");
+}
+
+
+
+static unsigned char **
+allocBMPraster(unsigned int const rows, unsigned int const bytesPerRow) {
+
+    unsigned int const storageSize = 
+        rows * sizeof(unsigned char *) + rows * bytesPerRow;        
+    unsigned char ** BMPraster;
+    unsigned int row;
+    unsigned char * startOfRows;
+
+    /* The raster array consists of an array of pointers to the rows
+       followed by the rows of bytes, in a single allocated chunk of storage.
+    */
+
+    if (UINT_MAX / (bytesPerRow + sizeof(unsigned char *)) < rows)
+        pm_error("raster is ridiculously large.");
+
+    BMPraster = (unsigned char **) malloc(storageSize);
+
+    if (BMPraster == NULL)
+        pm_error("Unable to allocate %u bytes for the BMP raster\n",
+                 storageSize);
+
+    startOfRows = (unsigned char *)(BMPraster + rows);
+
+    for (row = 0; row < rows; ++row) 
+        BMPraster[row] = startOfRows + row * bytesPerRow;
+
+    return BMPraster;
+}
+
+
+
+static void
+readrow(FILE *           const ifP,
+        unsigned int     const row,
+        unsigned int     const bytesPerRow,
+        unsigned char ** const BMPraster,
+        unsigned int *   const bytesReadP) {
+
+    size_t bytesRead;
+
+    assert(bytesPerRow > 0);
+    
+    bytesRead = fread(BMPraster[row], 1, bytesPerRow, ifP);
+
+    if (bytesRead < bytesPerRow) {
+        if (feof(ifP))
+            pm_error("End of file reading row %u of BMP raster.", row);
+        else 
+            pm_error("Error reading BMP raster.  Errno=%d (%s)",
+                     errno, strerror(errno));
+    }
+    *bytesReadP += bytesRead;
+}
+ 
+
+
+static void
+nibbleAlign(unsigned char * const ptr,
+            unsigned int    const nibbles){
+/*----------------------------------------------------------------------------
+  Shift data pointed by ptr one half byte toward the MSB (to the left).
+ 
+  Example:
+ 
+  (Numbers in hex, 8 nibbles)
+            5F 13 7E 89 A1
+   becomes  51 37 E8 9A 10
+-----------------------------------------------------------------------------*/
+    unsigned int const fullByteCount = (nibbles-1) / 2;
+    unsigned int i;
+              
+    ptr[0] = ptr[0] & ptr[1] >> 4;
+                                    
+    for (i = 0; i < fullByteCount; ++i)
+        ptr[i+1] = ptr[i+1] << 4 & ptr[i+2] >> 4;
+    
+    if (nibbles % 2 == 1)   /* if there is a final odd nibble */
+        ptr[fullByteCount+1] <<= 4; /* shift it a half byte */
+}
+
+
+           
+enum rleStatus { ABS_MODE, ENC_MODE, END_OF_ROW, END_OF_BMP, DELTA };
+
+static enum rleStatus
+readRLEcode(FILE *          const ifP,
+            unsigned int *  const cntP,
+            unsigned char * const codeP) {
+
+    unsigned short s;
+    enum rleStatus retval;
+    
+    s = GetBigShort(ifP);
+    
+    if      (s == 0) retval = END_OF_ROW;
+    else if (s == 1) retval = END_OF_BMP;
+    else if (s == 2) retval = DELTA;
+    else if (s < 256) {
+        if (cntP)
+            *cntP = s & 0xff;
+        retval = ABS_MODE;
+    } else {
+        if (cntP && codeP) {
+            *cntP  = (s >> 8) & 0xff;
+            *codeP = s & 0xff;
+        }
+        retval = ENC_MODE;
+    }
+    return retval;
+}
+
+
+
+static void
+readrowRLE(FILE *           const ifP,
+           unsigned int     const row,
+           unsigned int     const cols,
+           bool             const lastrow,
+           unsigned long    const compression,
+           unsigned char ** const BMPraster,
+           unsigned int  *  const bytesReadP) {
+
+    bool const RLE4 = (compression == COMP_RLE4);
+    int  const pixelsPerRowMargin = RLE4 ? cols % 2 : 0;
+
+    char const err_decode[] = 
+        "Error while decoding compressed BMP image.  "
+        "%s.  Row: %u  Pixel: %u" ; 
+     
+    unsigned int totalBytesRead;
+    unsigned int pixelsRead;
+
+    /* There are RLE4 images with rows coded up the byte boundary, resulting
+       in each row one pixel larger than the column length stated in the
+       BMP info header (header.cols) when the column length is odd.
+       pixelsPerRowMargin is a "wart" to provide for this case.
+    */
+
+    totalBytesRead = 0;  /* Initial value */
+    pixelsRead = 0;      /* Initial value */
+
+    while (TRUE) {
+        unsigned int n;
+            /* decompressed bytes already read; current write point */ 
+        unsigned int cnt;
+        unsigned char code;
+
+        n = RLE4 ? (pixelsRead + 1) / 2 : pixelsRead;
+
+        switch (readRLEcode(ifP, &cnt, &code)) {
+        case ENC_MODE: {
+            unsigned int const byteCnt = RLE4 ? (cnt + 1) /2 : cnt;
+            unsigned int i; 
+
+            if (pixelsRead + cnt > cols + pixelsPerRowMargin)
+                pm_error(err_decode,  "Too many pixels in encoded mode",
+                         row, pixelsRead ); 
+                 
+            for (i = 0; i < byteCnt; ++i)
+                BMPraster[row][n+i] = code;
+                 
+            if (RLE4 && pixelsRead % 2 == 1)
+                /* previous read ended odd */
+                nibbleAlign(&BMPraster[row][n-1], cnt); 
+            
+            pixelsRead += cnt;
+            totalBytesRead += 2;
+        } break;
+        
+        case ABS_MODE: {
+            unsigned int cmpBytesRead; /* compressed bytes read */
+            /* align read-end to 16 bit boundary */
+            unsigned int const bytesToRead =
+                RLE4 ? (cnt + 3) / 4 * 2 : (cnt + 1) / 2 * 2;
+
+            if (pixelsRead + cnt > cols + pixelsPerRowMargin)
+                pm_error(err_decode,  "Too many pixels in absolute mode",
+                         row, pixelsRead); 
+
+            cmpBytesRead = fread(&BMPraster[row][n], 
+                                 sizeof(char), bytesToRead, ifP);
+
+            if (cmpBytesRead < bytesToRead) {
+                if (feof(ifP))
+                    pm_error("End of file reading row %u "
+                             "of compressed BMP raster.", row);
+                else 
+                    pm_error("Error reading BMP raster.  Errno=%d (%s)",
+                             errno, strerror(errno));
+            }
+            if (RLE4 && pixelsRead % 2 == 1) /* previous read ended odd */
+                nibbleAlign(&BMPraster[row][n-1], cnt); 
+    
+            pixelsRead += cnt;
+            totalBytesRead += cmpBytesRead + 2;
+        } break;
+            
+        case END_OF_ROW: {
+            if (cols == pixelsRead ||
+                cols + pixelsPerRowMargin == pixelsRead) {
+                if (!lastrow) {
+                    *bytesReadP += totalBytesRead + 2;
+                    return;
+                } else if (readRLEcode(ifP, NULL, NULL) == END_OF_BMP) { 
+                    *bytesReadP += totalBytesRead +4;
+                    return;
+                } else
+                    /* lastrow and END_OF_BITMAP not detected */
+                    pm_error(err_decode,  "End of bitmap not marked",
+                             row, pixelsRead ); 
+            } else
+                pm_error(err_decode,  "Premature end of row",
+                         row, pixelsRead);
+        } break;  
+
+        case END_OF_BMP: {
+            if (lastrow && (cols == pixelsRead ||
+                            cols + pixelsPerRowMargin == pixelsRead)){
+                *bytesReadP += totalBytesRead + 2;
+                return;
+            } else
+                pm_error(err_decode,  "Premature end of bitmap",
+                         row, pixelsRead );
+        } break;
+
+        case DELTA: {
+            pm_error(err_decode,
+                     "Delta code in compressed BMP image.  "
+                     "This program does not process deltas.",
+                     row, pixelsRead);
+        } break;
+         
+        default:
+            pm_error("Internal error processing RLE code in row %u", row);
+        }
+   }
+}
+
+
+
+static void
+BMPreadraster(FILE *            const ifP, 
+              unsigned int      const cols, 
+              unsigned int      const rows, 
+              enum rowOrder     const rowOrder,
+              unsigned int      const cBitCount, 
+              unsigned long int const compression,
+              unsigned char *** const BMPrasterP, 
+              unsigned int *    const bytesReadP) {
+
+    unsigned int const bytesPerRow =
+        (compression == COMP_RLE4) ? cols / 2 + 2 :
+        (compression == COMP_RLE8) ? cols + 1 :
+        ((cols * cBitCount + 31) / 32) * 4;
+        /* A BMP raster row is a multiple of 4 bytes, padded on the right
+           with don't cares.
+        */
+    unsigned char ** BMPraster;
+
+    BMPraster = allocBMPraster(rows, bytesPerRow);
+
+    *bytesReadP = 0;
+
+    /* row order BOTTOMUP is by far the most common case - the bottom 
+       line is first in the file, the top line last.
+       
+       We have never actually seen TOPDOWN, except in a Microsoft spec
+    */
+    
+    switch(compression){
+    case COMP_RGB:
+    case COMP_BITFIELDS: {
+        unsigned int i;
+        for (i = 0; i < rows; ++i)
+            readrow(ifP, rowOrder == TOPDOWN ? i : rows - i - 1, 
+                    bytesPerRow, BMPraster, bytesReadP);
+    } break;
+    case COMP_RLE4: 
+    case COMP_RLE8: {
+        unsigned int i;
+        /* Read all rows except last */
+        for (i = 0; i < rows - 1; ++i){
+            readrowRLE(ifP, rowOrder == TOPDOWN ? i : rows - i - 1, 
+                       cols, FALSE, compression, BMPraster, bytesReadP);
+        }
+        /* Read last row */
+        readrowRLE(ifP, rowOrder == TOPDOWN ? i : rows - i - 1, 
+                   cols, TRUE,  compression, BMPraster, bytesReadP);
+    } break;             
+    default:       
+        pm_error("The BMP specifies a compression scheme we don't "
+                 "recognize.  Code= %lu", compression);
+    }
+
+    *BMPrasterP = BMPraster;
+}
+
+
+
+static void
+reportHeader(struct bmpInfoHeader const header,
+             unsigned int         const offBits) {
+             
+    pm_message("BMP image header says:");
+    pm_message("  Class of BMP: %s", 
+               header.class == C_WIN ? "Windows" : 
+               header.class == C_OS2 ? "OS/2" :
+               "???");
+    pm_message("  Width: %d pixels", header.cols);
+    pm_message("  Height: %d pixels", header.rows);
+    pm_message("  Depth: %d planes", header.cPlanes);
+    pm_message("  Row order: %s", 
+               header.rowOrder == BOTTOMUP ? "bottom up" : "top down");
+    pm_message("  Byte offset of raster within file: %u", offBits);
+    pm_message("  Bits per pixel in raster: %u", header.cBitCount);
+    pm_message("  Compression: %s", 
+               header.compression == COMP_RGB ? "none" :
+               header.compression == COMP_RLE4 ? "4 bit run-length coding" :
+               header.compression == COMP_RLE8 ? "8 bit run-length coding" :
+               header.compression == COMP_BITFIELDS ? "none" :
+               header.compression == COMP_JPEG ? "JPEG (not supported)" :
+               header.compression == COMP_PNG ? "PNG (not supported)" :
+               "???");                
+    pm_message("  Colors in color map: %d", header.cmapsize);
+}        
+
+
+
+static void
+analyzeColors(xel          const colormap[],
+              unsigned int const cmapsize,
+              xelval       const maxval,
+              bool *       const grayPresentP,
+              bool *       const colorPresentP) {
+    
+    if (cmapsize == 0) {
+        /* No colormap, and we're not about to search the entire raster,
+           so we just assume it's full color 
+        */
+        *colorPresentP = TRUE;
+        *grayPresentP = TRUE;
+    } else {
+        unsigned int i;
+
+        *colorPresentP = FALSE;  /* initial assumption */
+        *grayPresentP = FALSE;   /* initial assumption */
+        for (i = 0; i < cmapsize; ++i) {
+            if (PPM_ISGRAY(colormap[i])) {
+                if (PPM_GETR(colormap[i]) != 0 &&
+                    PPM_GETR(colormap[i]) != maxval)
+                    *grayPresentP = TRUE;
+            } else
+                *colorPresentP = TRUE;
+        }
+    }
+}
+
+
+
+static void
+warnIfOffBitsWrong(struct bmpInfoHeader const BMPheader,
+                   unsigned int         const offBits) {
+
+    if (offBits != BMPoffbits(BMPheader.class, BMPheader.cBitCount, 
+                              BMPheader.cmapsize)) {
+
+        pm_message("warning: the BMP header says the raster starts "
+                   "at offset %u bytes into the file (offbits), "
+                   "but that there are %u bytes of information before "
+                   "the raster.  This inconsistency probably means the "
+                   "input file is not a legal BMP file and is unusable.",
+                   offBits,
+                   BMPoffbits(BMPheader.class, BMPheader.cBitCount, 
+                              BMPheader.cmapsize));
+    }
+}
+
+
+
+static void
+readColorMap(FILE *               const ifP,
+             struct bmpInfoHeader const BMPheader,
+             xel **               const colorMapP,
+             unsigned int *       const posP) {
+
+    unsigned int bytesRead;
+
+    BMPreadcolormap(ifP, BMPheader.class, 
+                    colorMapP, BMPheader.cmapsize, &bytesRead);
+
+    *posP += bytesRead;
+
+    if (bytesRead != BMPlencolormap(BMPheader.class, BMPheader.cBitCount, 
+                                    BMPheader.cmapsize)) {
+
+        pm_message("warning: %u-byte RGB table, expected %u bytes",
+                   bytesRead,
+                   BMPlencolormap(BMPheader.class, BMPheader.cBitCount, 
+                                  BMPheader.cmapsize));
+}
+}
+
+
+
+static void
+readRaster(FILE *               const ifP,
+           struct bmpInfoHeader const BMPheader,
+           unsigned char ***    const BMPrasterP, 
+           unsigned int *       const posP) {
+
+    unsigned int bytesRead;
+
+    BMPreadraster(ifP, BMPheader.cols, BMPheader.rows, BMPheader.rowOrder,
+                  BMPheader.cBitCount, BMPheader.compression,
+                  BMPrasterP, &bytesRead);
+
+    *posP += bytesRead;
+}
+
+
+
+static void
+warnIfBadFileSize(struct bmpInfoHeader const BMPheader,
+                  unsigned int         const pos) {
+
+    unsigned int const expectedSize =
+        BMPlenfileGen(BMPheader.class, BMPheader.cBitCount, 
+                      BMPheader.cmapsize, BMPheader.cols,
+                      BMPheader.rows, BMPheader.imageSize,
+                      BMPheader.compression);
+
+    if (pos != expectedSize)
+        pm_message("warning: read %u bytes, expected to read %u bytes",
+                   pos, expectedSize);
+}
+
+
+
+static void
+readBmp(FILE *               const ifP, 
+        unsigned char ***    const BMPrasterP, 
+        int *                const colsP, 
+        int *                const rowsP,
+        bool *               const grayPresentP, 
+        bool *               const colorPresentP,
+        unsigned int *       const cBitCountP, 
+        struct pixelformat * const pixelformatP,
+        xel **               const colormapP,
+        bool                 const verbose) {
+
+    xel * colormap;  /* malloc'ed */
+    unsigned int pos;
+        /* Current byte position in the BMP file */
+
+    /* The following are all information from the BMP headers */
+    
+    unsigned int offBits;
+        /* Byte offset into file of raster */
+    struct bmpInfoHeader BMPheader;
+
+    pos = 0;  /* Starting at the beginning ... */
+    { 
+        unsigned int bytesRead;
+        BMPreadfileheader(ifP, &bytesRead, &offBits);
+        pos += bytesRead;
+    }
+    {
+        unsigned int bytesRead;
+        BMPreadinfoheader(ifP, &bytesRead, &BMPheader);
+        if (verbose)
+            pm_message("Read %u bytes of header", bytesRead);
+        pos += bytesRead;
+    }
+
+    if (verbose) 
+        reportHeader(BMPheader, offBits);
+
+    warnIfOffBitsWrong(BMPheader, offBits);
+
+    readColorMap(ifP, BMPheader, &colormap, &pos);
+
+    analyzeColors(colormap, BMPheader.cmapsize, bmpMaxval, 
+                  grayPresentP, colorPresentP);
+
+    readOffBytes(ifP, offBits - pos);
+
+    pos = offBits;
+
+    readRaster(ifP, BMPheader, BMPrasterP, &pos);
+
+    warnIfBadFileSize(BMPheader, pos);
+    
+    if (fgetc(ifP) != EOF)
+        pm_message("warning: some image data remains unread.");
+    
+    *colsP        = BMPheader.cols;
+    *rowsP        = BMPheader.rows;
+    *cBitCountP   = BMPheader.cBitCount;
+    *pixelformatP = BMPheader.pixelformat;
+    *colormapP    = colormap;
+}
+
+
+
+static void
+writeRasterGen(unsigned char **   const BMPraster,
+               int                const cols, 
+               int                const rows, 
+               int                const format,
+               unsigned int       const cBitCount, 
+               struct pixelformat const pixelformat,
+               xel                const colormap[]) {
+/*----------------------------------------------------------------------------
+  Write the PNM raster to Standard Output, corresponding to the raw BMP
+  raster BMPraster.  Write the raster assuming the PNM image has 
+  dimensions 'cols' by 'rows' and format 'format', with maxval 255.
+
+  The BMP image has 'cBitCount' bits per pixel, arranged in format
+  'pixelformat'.
+
+  If the image is colormapped, colormap[] is the colormap
+  (colormap[i] is the color with color index i).
+  
+  writeRasterPbm() is faster for a PBM image.
+-----------------------------------------------------------------------------*/
+    xel * xelrow;
+    unsigned int row;
+
+    xelrow = pnm_allocrow(cols);
+
+    for (row = 0; row < rows; ++row) {
+        convertRow(BMPraster[row], xelrow, cols, cBitCount, pixelformat,
+                   colormap);
+        pnm_writepnmrow(stdout, xelrow, cols, bmpMaxval, format, FALSE);
+    }
+    pnm_freerow(xelrow);
+}
+
+
+
+static void
+writeRasterPbm(unsigned char ** const BMPraster,
+               int              const cols, 
+               int              const rows, 
+               xel              const colormap[]) {
+/*----------------------------------------------------------------------------
+  Write the PBM raster to Standard Output corresponding to the raw BMP
+  raster BMPraster.  Write the raster assuming the PBM image has 
+  dimensions 'cols' by 'rows'.
+
+  The BMP image has 'cBitCount' bits per pixel, arranged in format
+  'pixelformat'.
+
+  The image must be colormapped; colormap[] is the colormap
+  (colormap[i] is the color with color index i).  We cannot handle the
+  abnormal case in which colormap[0] and colormap[1] have the same
+  value (i.e. both white or both black.)
+  
+  We destroy *BMPraster as a side effect.
+-----------------------------------------------------------------------------*/
+    unsigned int const charBits = (sizeof(unsigned char) * 8);
+        /* Number of bits in a character */
+    unsigned int const colChars = pbm_packed_bytes(cols);
+    
+    int row;
+    enum colorFormat {BlackWhite, WhiteBlack};
+    enum colorFormat colorformat;
+                  
+    if (PPM_GETR(colormap[0]) > 0)
+        colorformat = WhiteBlack;
+    else                  
+        colorformat = BlackWhite;
+        
+    for (row=0; row < rows; ++row){
+        unsigned char * const bitrow = BMPraster[row]; 
+
+        if (colorformat == BlackWhite) {
+            unsigned int i;
+            for (i = 0; i < colChars; ++i) 
+                bitrow[i] = ~bitrow[i]; /* flip all pixels */ 
+        }   
+            
+        if (cols % 8 > 0) {
+            /* adjust final partial byte */
+            bitrow[colChars-1] >>= charBits - cols % charBits;
+            bitrow[colChars-1] <<= charBits - cols % charBits;
+        }
+        
+        pbm_writepbmrow_packed(stdout, bitrow, cols, FALSE);
+    }
+}
+
+
+
+int
+main(int argc, char ** argv) {
+
+    struct cmdline_info cmdline;
+    FILE * ifP;
+    int outputType;
+
+    bool grayPresent, colorPresent;
+        /* These tell whether the image contains shades of gray other than
+           black and white and whether it has colors other than black, white,
+           and gray.
+        */
+    int cols, rows;
+    unsigned char **BMPraster;
+        /* The raster part of the BMP image, as a row x column array, with
+           each element being a raw byte from the BMP raster.  Note that
+           BMPraster[0] is really Row 0 -- the top row of the image, even
+           though the bottom row comes first in the BMP format.
+        */
+    unsigned int cBitCount;
+        /* Number of bits in BMP raster for each pixel */
+    struct pixelformat pixelformat;
+        /* Format of the raster bits for a single pixel */
+    xel * colormap;
+        /* Malloc'ed colormap (palette) from the BMP.  Contents of map
+           undefined if not a colormapped BMP.
+         */
+
+    pnm_init(&argc, argv);
+
+    parse_command_line(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.input_filespec);
+    if (streq(cmdline.input_filespec, "-"))
+        ifname = "Standard Input";
+    else 
+        ifname = cmdline.input_filespec;
+
+    readBmp(ifP, &BMPraster, &cols, &rows, &grayPresent, &colorPresent, 
+            &cBitCount, &pixelformat, &colormap,
+            cmdline.verbose);
+    pm_close(ifP);
+
+    if (colorPresent) {
+        outputType = PPM_TYPE;
+        pm_message("WRITING PPM IMAGE");
+    } else if (grayPresent) {
+        outputType = PGM_TYPE;
+        pm_message("WRITING PGM IMAGE");
+    } else {
+        outputType = PBM_TYPE;
+        pm_message("WRITING PBM IMAGE");
+    }
+    
+    if (outputType == PBM_TYPE  && cBitCount == 1){
+        pbm_writepbminit(stdout, cols, rows, FALSE);
+        writeRasterPbm(BMPraster, cols, rows, colormap);
+    } else {
+        pnm_writepnminit(stdout, cols, rows, bmpMaxval, outputType, FALSE);
+        writeRasterGen(BMPraster, cols, rows, outputType, cBitCount,
+                       pixelformat, colormap); 
+    }
+    free(colormap);
+    free(BMPraster);
+
+    return 0;
+}
+
+
diff --git a/converter/other/cameratopam/COPYRIGHT b/converter/other/cameratopam/COPYRIGHT
new file mode 100644
index 00000000..efc77975
--- /dev/null
+++ b/converter/other/cameratopam/COPYRIGHT
@@ -0,0 +1,35 @@
+Cameratopam is derived from Dave Coffin's Dcraw program.
+
+Bryan Henderson derived it in April 2005.  Bryan mainly just split it up
+into manageable pieces and replaced the parts that generate Netpbm formats
+and the command line parser.
+
+Dcraw and therefore Cameratopam is Copyright 1997-2005 by Dave Coffin,
+dcoffin a cybercom o net, and Dave licenses it to the public freely,
+except that the Foveon code (in foveon.c in Netpbm) is copyrighted by
+someone else (I don't know who).
+
+Dave licenses his copyright to the public freely, with the words, "Any
+code not declared GPL is free for all uses."  Only the code in
+foveon.c in Netpbm was declared GPL in Dave's work.
+
+The foveon code is licensed to the public by its unknown copyright owner
+under GPL.
+
+The Netpbm maintainer distributes the entire Cameratopam package under
+GPL.
+
+Dave's distribution to Bryan may be a copyright violation, but
+strangely enough, Bryan's distribution to others is not.  I.e.  you DO
+have license from every copyright owner to do GPL-compatible things
+with this code.
+
+The problem with Dave's distribution is that he combined the Foveon
+code and his code into a single work and distributed it with GPL terms
+applying only to the Foveon code.  The combined work may be a
+derivative work of the Foveon code and therefore cannot be distributed
+with the permission of the Foveon copyright owner, who gives that
+permission only on the condition that the distributor license the
+entire derivative work under GPL.  Dave did not do that.  He did not,
+for example, prohibit Bryan from distributing Dave's part of the code
+in object code only format.
diff --git a/converter/other/cameratopam/Makefile b/converter/other/cameratopam/Makefile
new file mode 100644
index 00000000..c50c9176
--- /dev/null
+++ b/converter/other/cameratopam/Makefile
@@ -0,0 +1,37 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/other/cameratopam
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+ifneq ($(JPEGLIB),NONE)
+  ifneq ($(JPEGHDR_DIR)x,x)
+    INCLUDES += -I$(JPEGHDR_DIR)
+    CFLAGS += -DHAVE_JPEG
+  endif
+endif
+
+include $(BUILDDIR)/Makefile.config
+
+
+.PHONY: all
+all: cameratopam
+
+OBJECTS = util.o identify.o cameratopam.o camera.o foveon.o decode.o \
+	canon.o ljpeg.o dng.o
+
+MERGE_OBJECTS =
+
+BINARIES = cameratopam
+MERGEBINARIES = 
+SCRIPTS = 
+
+include $(SRCDIR)/Makefile.common
+
+cameratopam: $(OBJECTS) $(NETPBMLIB) $(LIBOPT)
+	$(LD) -o $@ $(LDFLAGS) \
+          $(OBJECTS) $(shell $(LIBOPT) $(NETPBMLIB) $(LIBOPTR)) \
+	  $(MATHLIB) $(LDLIBS) \
+	  $(RPATH) $(LADD)
+
diff --git a/converter/other/cameratopam/bayer.h b/converter/other/cameratopam/bayer.h
new file mode 100644
index 00000000..7ac06ec3
--- /dev/null
+++ b/converter/other/cameratopam/bayer.h
@@ -0,0 +1,43 @@
+/*
+   In order to inline this calculation, I make the risky
+   assumption that all filter patterns can be described
+   by a repeating pattern of eight rows and two columns
+
+   Return values are either 0/1/2/3 = G/M/C/Y or 0/1/2/3 = R/G1/B/G2
+ */
+#define FC(row,col) \
+    (filters >> ((((row) << 1 & 14) + ((col) & 1)) << 1) & 3)
+
+#define BAYER(row,col) \
+    image[((row) >> shrink)*iwidth + ((col) >> shrink)][FC(row,col)]
+
+/*
+    PowerShot 600   PowerShot A50   PowerShot Pro70 Pro90 & G1
+    0xe1e4e1e4: 0x1b4e4b1e: 0x1e4b4e1b: 0xb4b4b4b4:
+
+      0 1 2 3 4 5     0 1 2 3 4 5     0 1 2 3 4 5     0 1 2 3 4 5
+    0 G M G M G M   0 C Y C Y C Y   0 Y C Y C Y C   0 G M G M G M
+    1 C Y C Y C Y   1 M G M G M G   1 M G M G M G   1 Y C Y C Y C
+    2 M G M G M G   2 Y C Y C Y C   2 C Y C Y C Y
+    3 C Y C Y C Y   3 G M G M G M   3 G M G M G M
+            4 C Y C Y C Y   4 Y C Y C Y C
+    PowerShot A5    5 G M G M G M   5 G M G M G M
+    0x1e4e1e4e: 6 Y C Y C Y C   6 C Y C Y C Y
+            7 M G M G M G   7 M G M G M G
+      0 1 2 3 4 5
+    0 C Y C Y C Y
+    1 G M G M G M
+    2 C Y C Y C Y
+    3 M G M G M G
+
+   All RGB cameras use one of these Bayer grids:
+
+    0x16161616: 0x61616161: 0x49494949: 0x94949494:
+
+      0 1 2 3 4 5     0 1 2 3 4 5     0 1 2 3 4 5     0 1 2 3 4 5
+    0 B G B G B G   0 G R G R G R   0 G B G B G B   0 R G R G R G
+    1 G R G R G R   1 B G B G B G   1 R G R G R G   1 G B G B G B
+    2 B G B G B G   2 G R G R G R   2 G B G B G B   2 R G R G R G
+    3 G R G R G R   3 B G B G B G   3 R G R G R G   3 G B G B G B
+ */
+
diff --git a/converter/other/cameratopam/camera.c b/converter/other/cameratopam/camera.c
new file mode 100644
index 00000000..98d6d37a
--- /dev/null
+++ b/converter/other/cameratopam/camera.c
@@ -0,0 +1,1781 @@
+#define _BSD_SOURCE
+    /* Make sure strcasecmp is in string.h */
+#define _XOPEN_SOURCE
+    /* Make sure putenv is in stdlib.h */
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/types.h>
+
+#ifdef HAVE_JPEG
+#include <jpeglib.h>
+#endif
+
+#include "pm.h"
+#include "mallocvar.h"
+
+#include "global_variables.h"
+#include "util.h"
+#include "decode.h"
+#include "bayer.h"
+#include "ljpeg.h"
+#include "dng.h"
+
+#include "camera.h"
+
+#if HAVE_INT64
+   typedef int64_t INT64;
+   static bool const have64BitArithmetic = true;
+#else
+   /* We define INT64 to something that lets the code compile, but we
+      should not execute any INT64 code, because it will get the wrong
+      result.
+   */
+   typedef int INT64;
+   static bool const have64BitArithmetic = false;
+#endif
+
+#ifndef LONG_BIT
+#define LONG_BIT (8 * sizeof (long))
+#endif
+
+#define FORC3 for (c=0; c < 3; c++)
+#define FORC4 for (c=0; c < colors; c++)
+
+static void 
+merror (const void *ptr, const char *where) 
+{
+    if (ptr == NULL)
+        pm_error ("Out of memory in %s", where);
+}
+
+
+
+
+static void  
+adobe_copy_pixel (int row, int col, unsigned short **rp, bool use_secondary)
+{
+  unsigned r=row, c=col;
+
+  if (fuji_secondary && use_secondary) (*rp)++;
+  if (filters) {
+    if (fuji_width) {
+      r = row + fuji_width - 1 - (col >> 1);
+      c = row + ((col+1) >> 1);
+    }
+    if (r < height && c < width)
+      BAYER(r,c) = **rp < 0x1000 ? curve[**rp] : **rp;
+    *rp += 1 + fuji_secondary;
+  } else
+    for (c=0; c < tiff_samples; c++) {
+      image[row*width+col][c] = **rp < 0x1000 ? curve[**rp] : **rp;
+      (*rp)++;
+    }
+  if (fuji_secondary && use_secondary) (*rp)--;
+}
+
+void
+adobe_dng_load_raw_lj()
+{
+  int save, twide, trow=0, tcol=0, jrow, jcol;
+  struct jhead jh;
+  unsigned short *rp;
+
+  while (1) {
+    save = ftell(ifp);
+    fseek (ifp, get4(ifp), SEEK_SET);
+    if (!ljpeg_start (ifp, &jh)) break;
+    if (trow >= raw_height) break;
+    if (jh.high > raw_height-trow)
+    jh.high = raw_height-trow;
+    twide = jh.wide;
+    if (filters) twide *= jh.clrs;
+    else         colors = jh.clrs;
+    if (fuji_secondary) twide /= 2;
+    if (twide > raw_width-tcol)
+    twide = raw_width-tcol;
+
+    for (jrow=0; jrow < jh.high; jrow++) {
+      ljpeg_row (&jh);
+      for (rp=jh.row, jcol=0; jcol < twide; jcol++)
+    adobe_copy_pixel (trow+jrow, tcol+jcol, &rp, use_secondary);
+    }
+    fseek (ifp, save+4, SEEK_SET);
+    if ((tcol += twide) >= raw_width) {
+      tcol = 0;
+      trow += jh.high;
+    }
+    free (jh.row);
+  }
+}
+
+
+
+void
+adobe_dng_load_raw_nc()
+{
+    unsigned short *pixel, *rp;
+    int row, col;
+
+    pixel = calloc (raw_width * tiff_samples, sizeof *pixel);
+    merror (pixel, "adobe_dng_load_raw_nc()");
+    for (row=0; row < raw_height; row++) {
+        read_shorts (ifp, pixel, raw_width * tiff_samples);
+        for (rp=pixel, col=0; col < raw_width; col++)
+            adobe_copy_pixel (row, col, &rp, use_secondary);
+    }
+    free (pixel);
+}
+
+
+
+static int nikon_curve_offset;
+
+void
+nikon_compressed_load_raw(void)
+{
+    static const unsigned char nikon_tree[] = {
+        0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0,
+        5,4,3,6,2,7,1,0,8,9,11,10,12
+    };
+    int csize, row, col, i, diff;
+    unsigned short vpred[4], hpred[2], *curve;
+
+    init_decoder();
+    make_decoder (nikon_tree, 0);
+
+    fseek (ifp, nikon_curve_offset, SEEK_SET);
+    read_shorts (ifp, vpred, 4);
+    csize = get2(ifp);
+    curve = calloc (csize, sizeof *curve);
+    merror (curve, "nikon_compressed_load_raw()");
+    read_shorts (ifp, curve, csize);
+
+    fseek (ifp, data_offset, SEEK_SET);
+    getbits(ifp, -1);
+
+    for (row=0; row < height; row++)
+        for (col=0; col < raw_width; col++)
+        {
+            diff = ljpeg_diff (first_decode);
+            if (col < 2) {
+                i = 2*(row & 1) + (col & 1);
+                vpred[i] += diff;
+                hpred[col] = vpred[i];
+            } else
+                hpred[col & 1] += diff;
+            if ((unsigned) (col-left_margin) >= width) continue;
+            diff = hpred[col & 1];
+            if (diff >= csize) diff = csize-1;
+            BAYER(row,col-left_margin) = curve[diff];
+        }
+    maximum = curve[csize-1];
+    free (curve);
+}
+
+void
+nikon_load_raw()
+{
+  int irow, row, col, i;
+
+  getbits(ifp, -1);
+  for (irow=0; irow < height; irow++) {
+    row = irow;
+    if (model[0] == 'E') {
+      row = irow * 2 % height + irow / (height/2);
+      if (row == 1 && atoi(model+1) < 5000) {
+    fseek (ifp, 0, SEEK_END);
+    fseek (ifp, ftell(ifp)/2, SEEK_SET);
+    getbits(ifp, -1);
+      }
+    }
+    for (col=0; col < raw_width; col++) {
+      i = getbits(ifp, 12);
+      if ((unsigned) (col-left_margin) < width)
+    BAYER(row,col-left_margin) = i;
+      if (tiff_data_compression == 34713 && (col % 10) == 9)
+    getbits(ifp, 8);
+    }
+  }
+}
+
+/*
+   Figure out if a NEF file is compressed.  These fancy heuristics
+   are only needed for the D100, thanks to a bug in some cameras
+   that tags all images as "compressed".
+ */
+int
+nikon_is_compressed()
+{
+  unsigned char test[256];
+  int i;
+
+  if (tiff_data_compression != 34713)
+    return 0;
+  if (strcmp(model,"D100"))
+    return 1;
+  fseek (ifp, data_offset, SEEK_SET);
+  fread (test, 1, 256, ifp);
+  for (i=15; i < 256; i+=16)
+    if (test[i]) return 1;
+  return 0;
+}
+
+/*
+   Returns 1 for a Coolpix 990, 0 for a Coolpix 995.
+ */
+int
+nikon_e990()
+{
+  int i, histo[256];
+  const unsigned char often[] = { 0x00, 0x55, 0xaa, 0xff };
+
+  memset (histo, 0, sizeof histo);
+  fseek (ifp, 2064*1540*3/4, SEEK_SET);
+  for (i=0; i < 2000; i++)
+    histo[fgetc(ifp)]++;
+  for (i=0; i < 4; i++)
+    if (histo[often[i]] > 400)
+      return 1;
+  return 0;
+}
+
+/*
+   Returns 1 for a Coolpix 2100, 0 for anything else.
+ */
+int
+nikon_e2100()
+{
+  unsigned char t[12];
+  int i;
+
+  fseek (ifp, 0, SEEK_SET);
+  for (i=0; i < 1024; i++) {
+    fread (t, 1, 12, ifp);
+    if (((t[2] & t[4] & t[7] & t[9]) >> 4
+    & t[1] & t[6] & t[8] & t[11] & 3) != 3)
+      return 0;
+  }
+  return 1;
+}
+
+/*
+   Separates a Pentax Optio 33WR from a Nikon E3700.
+ */
+int
+pentax_optio33()
+{
+  int i, sum[] = { 0, 0 };
+  unsigned char tail[952];
+
+  fseek (ifp, -sizeof tail, SEEK_END);
+  fread (tail, 1, sizeof tail, ifp);
+  for (i=0; i < sizeof tail; i++)
+    sum[(i>>2) & 1] += tail[i];
+  return sum[0] < sum[1]*4;
+}
+
+/*
+   Separates a Minolta DiMAGE Z2 from a Nikon E4300.
+ */
+int
+minolta_z2()
+{
+  int i;
+  char tail[424];
+
+  fseek (ifp, -sizeof tail, SEEK_END);
+  fread (tail, 1, sizeof tail, ifp);
+  for (i=0; i < sizeof tail; i++)
+    if (tail[i]) return 1;
+  return 0;
+}
+
+void
+nikon_e2100_load_raw()
+{
+  unsigned char   data[3432], *dp;
+  unsigned short pixel[2288], *pix;
+  int row, col;
+
+  for (row=0; row <= height; row+=2) {
+    if (row == height) {
+      fseek (ifp, ((width==1616) << 13) - (-ftell(ifp) & -2048), SEEK_SET);
+      row = 1;
+    }
+    fread (data, 1, width*3/2, ifp);
+    for (dp=data, pix=pixel; pix < pixel+width; dp+=12, pix+=8) {
+      pix[0] = (dp[2] >> 4) + (dp[ 3] << 4);
+      pix[1] = (dp[2] << 8) +  dp[ 1];
+      pix[2] = (dp[7] >> 4) + (dp[ 0] << 4);
+      pix[3] = (dp[7] << 8) +  dp[ 6];
+      pix[4] = (dp[4] >> 4) + (dp[ 5] << 4);
+      pix[5] = (dp[4] << 8) +  dp[11];
+      pix[6] = (dp[9] >> 4) + (dp[10] << 4);
+      pix[7] = (dp[9] << 8) +  dp[ 8];
+    }
+    for (col=0; col < width; col++)
+      BAYER(row,col) = (pixel[col] & 0xfff);
+  }
+}
+
+void
+nikon_e950_load_raw()
+{
+  int irow, row, col;
+
+  getbits(ifp, -1);
+  for (irow=0; irow < height; irow++) {
+    row = irow * 2 % height;
+    for (col=0; col < width; col++)
+      BAYER(row,col) = getbits(ifp, 10);
+    for (col=28; col--; )
+      getbits(ifp, 8);
+  }
+  maximum = 0x3dd;
+}
+
+/*
+   The Fuji Super CCD is just a Bayer grid rotated 45 degrees.
+ */
+void
+fuji_s2_load_raw()
+{
+  unsigned short pixel[2944];
+  int row, col, r, c;
+
+  fseek (ifp, (2944*24+32)*2, SEEK_CUR);
+  for (row=0; row < 2144; row++) {
+    read_shorts(ifp, pixel, 2944);
+    for (col=0; col < 2880; col++) {
+      r = row + ((col+1) >> 1);
+      c = 2143 - row + (col >> 1);
+      BAYER(r,c) = pixel[col];
+    }
+  }
+}
+
+void
+fuji_s3_load_raw()
+{
+  unsigned short pixel[4352];
+  int row, col, r, c;
+
+  fseek (ifp, (4352*2+32)*2, SEEK_CUR);
+  for (row=0; row < 1440; row++) {
+    read_shorts(ifp, pixel, 4352);
+    for (col=0; col < 4288; col++) {
+      r = 2143 + row - (col >> 1);
+      c = row + ((col+1) >> 1);
+      BAYER(r,c) = pixel[col];
+    }
+  }
+}
+
+static void  fuji_common_load_raw (int ncol, int icol, int nrow)
+{
+  unsigned short pixel[2048];
+  int row, col, r, c;
+
+  for (row=0; row < nrow; row++) {
+    read_shorts(ifp, pixel, ncol);
+    for (col=0; col <= icol; col++) {
+      r = icol - col + (row >> 1);
+      c = col + ((row+1) >> 1);
+      BAYER(r,c) = pixel[col];
+    }
+  }
+}
+
+void
+fuji_s5000_load_raw()
+{
+  fseek (ifp, (1472*4+24)*2, SEEK_CUR);
+  fuji_common_load_raw (1472, 1423, 2152);
+}
+
+void
+fuji_s7000_load_raw()
+{
+  fuji_common_load_raw (2048, 2047, 3080);
+}
+
+/*
+   The Fuji Super CCD SR has two photodiodes for each pixel.
+   The secondary has about 1/16 the sensitivity of the primary,
+   but this ratio may vary.
+ */
+void
+fuji_f700_load_raw()
+{
+  unsigned short pixel[2944];
+  int row, col, r, c, val;
+
+  for (row=0; row < 2168; row++) {
+    read_shorts(ifp, pixel, 2944);
+    for (col=0; col < 1440; col++) {
+      r = 1439 - col + (row >> 1);
+      c = col + ((row+1) >> 1);
+      val = pixel[col+16 + use_secondary*1472];
+      BAYER(r,c) = val;
+    }
+  }
+}
+
+void
+rollei_load_raw()
+{
+  unsigned char pixel[10];
+  unsigned iten=0, isix, i, buffer=0, row, col, todo[16];
+
+  isix = raw_width * raw_height * 5 / 8;
+  while (fread (pixel, 1, 10, ifp) == 10) {
+    for (i=0; i < 10; i+=2) {
+      todo[i]   = iten++;
+      todo[i+1] = pixel[i] << 8 | pixel[i+1];
+      buffer    = pixel[i] >> 2 | buffer << 6;
+    }
+    for (   ; i < 16; i+=2) {
+      todo[i]   = isix++;
+      todo[i+1] = buffer >> (14-i)*5;
+    }
+    for (i=0; i < 16; i+=2) {
+      row = todo[i] / raw_width - top_margin;
+      col = todo[i] % raw_width - left_margin;
+      if (row < height && col < width)
+    BAYER(row,col) = (todo[i+1] & 0x3ff);
+    }
+  }
+  maximum = 0x3ff;
+}
+
+void
+phase_one_load_raw()
+{
+  int row, col, a, b;
+  unsigned short *pixel, akey, bkey;
+
+  fseek (ifp, 8, SEEK_CUR);
+  fseek (ifp, get4(ifp) + 296, SEEK_CUR);
+  akey = get2(ifp);
+  bkey = get2(ifp);
+  fseek (ifp, data_offset + 12 + top_margin*raw_width*2, SEEK_SET);
+  pixel = calloc (raw_width, sizeof *pixel);
+  merror (pixel, "phase_one_load_raw()");
+  for (row=0; row < height; row++) {
+    read_shorts(ifp, pixel, raw_width);
+    for (col=0; col < raw_width; col+=2) {
+      a = pixel[col+0] ^ akey;
+      b = pixel[col+1] ^ bkey;
+      pixel[col+0] = (b & 0xaaaa) | (a & 0x5555);
+      pixel[col+1] = (a & 0xaaaa) | (b & 0x5555);
+    }
+    for (col=0; col < width; col++)
+      BAYER(row,col) = pixel[col+left_margin];
+  }
+  free (pixel);
+}
+
+void
+ixpress_load_raw()
+{
+  unsigned short pixel[4090];
+  int row, col;
+
+  order = 0x4949;
+  fseek (ifp, 304 + 6*2*4090, SEEK_SET);
+  for (row=height; --row >= 0; ) {
+    read_shorts(ifp, pixel, 4090);
+    for (col=0; col < width; col++)
+      BAYER(row,col) = pixel[width-1-col];
+  }
+}
+
+void
+leaf_load_raw()
+{
+  unsigned short *pixel;
+  int r, c, row, col;
+
+  pixel = calloc (raw_width, sizeof *pixel);
+  merror (pixel, "leaf_load_raw()");
+  for (r=0; r < height-32; r+=32)
+    FORC3 for (row=r; row < r+32; row++) {
+      read_shorts(ifp, pixel, raw_width);
+      for (col=0; col < width; col++)
+    image[row*width+col][c] = pixel[col];
+    }
+  free (pixel);
+}
+
+/*
+   For this function only, raw_width is in bytes, not pixels!
+ */
+void
+packed_12_load_raw()
+{
+  int row, col;
+
+  getbits(ifp, -1);
+  for (row=0; row < height; row++) {
+    for (col=0; col < width; col++)
+      BAYER(row,col) = getbits(ifp, 12);
+    for (col = width*3/2; col < raw_width; col++)
+      getbits(ifp, 8);
+  }
+}
+
+void
+unpacked_load_raw()
+{
+  unsigned short *pixel;
+  int row, col;
+
+  pixel = calloc (raw_width, sizeof *pixel);
+  merror (pixel, "unpacked_load_raw()");
+  for (row=0; row < height; row++) {
+    read_shorts(ifp, pixel, raw_width);
+    for (col=0; col < width; col++)
+      BAYER(row,col) = pixel[col];
+  }
+  free (pixel);
+}
+
+void
+olympus_e300_load_raw()
+{
+  unsigned char  *data,  *dp;
+  unsigned short *pixel, *pix;
+  int dwide, row, col;
+
+  dwide = raw_width * 16 / 10;
+  data = malloc (dwide + raw_width*2);
+  merror (data, "olympus_e300_load_raw()");
+  pixel = (unsigned short *) (data + dwide);
+  for (row=0; row < height; row++) {
+    fread (data, 1, dwide, ifp);
+    for (dp=data, pix=pixel; pix < pixel+raw_width; dp+=3, pix+=2) {
+      if (((dp-data) & 15) == 15) dp++;
+      pix[0] = dp[1] << 8 | dp[0];
+      pix[1] = dp[2] << 4 | dp[1] >> 4;
+    }
+    for (col=0; col < width; col++)
+      BAYER(row,col) = (pixel[col] & 0xfff);
+  }
+  free (data);
+}
+
+void
+olympus_cseries_load_raw()
+{
+  int irow, row, col;
+
+  for (irow=0; irow < height; irow++) {
+    row = irow * 2 % height + irow / (height/2);
+    if (row < 2) {
+      fseek (ifp, data_offset - row*(-width*height*3/4 & -2048), SEEK_SET);
+      getbits(ifp, -1);
+    }
+    for (col=0; col < width; col++)
+      BAYER(row,col) = getbits(ifp, 12);
+  }
+}
+
+void
+eight_bit_load_raw()
+{
+  unsigned char *pixel;
+  int row, col;
+
+  pixel = calloc (raw_width, sizeof *pixel);
+  merror (pixel, "eight_bit_load_raw()");
+  for (row=0; row < height; row++) {
+    fread (pixel, 1, raw_width, ifp);
+    for (col=0; col < width; col++)
+      BAYER(row,col) = pixel[col];
+  }
+  free (pixel);
+  maximum = 0xff;
+}
+
+void
+casio_qv5700_load_raw()
+{
+  unsigned char  data[3232],  *dp;
+  unsigned short pixel[2576], *pix;
+  int row, col;
+
+  for (row=0; row < height; row++) {
+    fread (data, 1, 3232, ifp);
+    for (dp=data, pix=pixel; dp < data+3220; dp+=5, pix+=4) {
+      pix[0] = (dp[0] << 2) + (dp[1] >> 6);
+      pix[1] = (dp[1] << 4) + (dp[2] >> 4);
+      pix[2] = (dp[2] << 6) + (dp[3] >> 2);
+      pix[3] = (dp[3] << 8) + (dp[4]     );
+    }
+    for (col=0; col < width; col++)
+      BAYER(row,col) = (pixel[col] & 0x3ff);
+  }
+  maximum = 0x3fc;
+}
+
+void
+nucore_load_raw()
+{
+  unsigned short *pixel;
+  int irow, row, col;
+
+  pixel = calloc (width, 2);
+  merror (pixel, "nucore_load_raw()");
+  for (irow=0; irow < height; irow++) {
+    read_shorts(ifp, pixel, width);
+    row = irow/2 + height/2 * (irow & 1);
+    for (col=0; col < width; col++)
+      BAYER(row,col) = pixel[col];
+  }
+  free (pixel);
+}
+
+static int  radc_token (int tree)
+{
+  int t;
+  static struct decode *dstart[18], *dindex;
+  static const int *s, source[] = {
+    1,1, 2,3, 3,4, 4,2, 5,7, 6,5, 7,6, 7,8,
+    1,0, 2,1, 3,3, 4,4, 5,2, 6,7, 7,6, 8,5, 8,8,
+    2,1, 2,3, 3,0, 3,2, 3,4, 4,6, 5,5, 6,7, 6,8,
+    2,0, 2,1, 2,3, 3,2, 4,4, 5,6, 6,7, 7,5, 7,8,
+    2,1, 2,4, 3,0, 3,2, 3,3, 4,7, 5,5, 6,6, 6,8,
+    2,3, 3,1, 3,2, 3,4, 3,5, 3,6, 4,7, 5,0, 5,8,
+    2,3, 2,6, 3,0, 3,1, 4,4, 4,5, 4,7, 5,2, 5,8,
+    2,4, 2,7, 3,3, 3,6, 4,1, 4,2, 4,5, 5,0, 5,8,
+    2,6, 3,1, 3,3, 3,5, 3,7, 3,8, 4,0, 5,2, 5,4,
+    2,0, 2,1, 3,2, 3,3, 4,4, 4,5, 5,6, 5,7, 4,8,
+    1,0, 2,2, 2,-2,
+    1,-3, 1,3,
+    2,-17, 2,-5, 2,5, 2,17,
+    2,-7, 2,2, 2,9, 2,18,
+    2,-18, 2,-9, 2,-2, 2,7,
+    2,-28, 2,28, 3,-49, 3,-9, 3,9, 4,49, 5,-79, 5,79,
+    2,-1, 2,13, 2,26, 3,39, 4,-16, 5,55, 6,-37, 6,76,
+    2,-26, 2,-13, 2,1, 3,-39, 4,16, 5,-55, 6,-76, 6,37
+  };
+
+  if (free_decode == first_decode)
+    for (s=source, t=0; t < 18; t++) {
+      dstart[t] = free_decode;
+      s = make_decoder_int (s, 0);
+    }
+  if (tree == 18) {
+    if (model[2] == '4')
+      return (getbits(ifp, 5) << 3) + 4; /* DC40 */
+    else
+      return (getbits(ifp, 6) << 2) + 2; /* DC50 */
+  }
+  for (dindex = dstart[tree]; dindex->branch[0]; )
+    dindex = dindex->branch[getbits(ifp, 1)];
+  return dindex->leaf;
+}
+
+#define FORYX for (y=1; y < 3; y++) for (x=col+1; x >= col; x--)
+
+#define PREDICTOR (c ? (buf[c][y-1][x] + buf[c][y][x+1]) / 2 \
+: (buf[c][y-1][x+1] + 2*buf[c][y-1][x] + buf[c][y][x+1]) / 4)
+
+void
+kodak_radc_load_raw()
+{
+  int row, col, tree, nreps, rep, step, i, c, s, r, x, y, val;
+  short last[3] = { 16,16,16 }, mul[3], buf[3][3][386];
+
+  init_decoder();
+  getbits(ifp, -1);
+  for (i=0; i < sizeof(buf)/sizeof(short); i++)
+    buf[0][0][i] = 2048;
+  for (row=0; row < height; row+=4) {
+    for (i=0; i < 3; i++)
+      mul[i] = getbits(ifp, 6);
+    FORC3 {
+      val = ((0x1000000/last[c] + 0x7ff) >> 12) * mul[c];
+      s = val > 65564 ? 10:12;
+      x = ~(-1 << (s-1));
+      val <<= 12-s;
+      for (i=0; i < sizeof(buf[0])/sizeof(short); i++)
+    buf[c][0][i] = (buf[c][0][i] * val + x) >> s;
+      last[c] = mul[c];
+      for (r=0; r <= !c; r++) {
+    buf[c][1][width/2] = buf[c][2][width/2] = mul[c] << 7;
+    for (tree=1, col=width/2; col > 0; ) {
+      if ((tree = radc_token(tree))) {
+        col -= 2;
+        if (tree == 8)
+          FORYX buf[c][y][x] = radc_token(tree+10) * mul[c];
+        else
+          FORYX buf[c][y][x] = radc_token(tree+10) * 16 + PREDICTOR;
+      } else
+        do {
+          nreps = (col > 2) ? radc_token(9) + 1 : 1;
+          for (rep=0; rep < 8 && rep < nreps && col > 0; rep++) {
+        col -= 2;
+        FORYX buf[c][y][x] = PREDICTOR;
+        if (rep & 1) {
+          step = radc_token(10) << 4;
+          FORYX buf[c][y][x] += step;
+        }
+          }
+        } while (nreps == 9);
+    }
+    for (y=0; y < 2; y++)
+      for (x=0; x < width/2; x++) {
+        val = (buf[c][y+1][x] << 4) / mul[c];
+        if (val < 0) val = 0;
+        if (c)
+          BAYER(row+y*2+c-1,x*2+2-c) = val;
+        else
+          BAYER(row+r*2+y,x*2+y) = val;
+      }
+    memcpy (buf[c][0]+!c, buf[c][2], sizeof buf[c][0]-2*!c);
+      }
+    }
+    for (y=row; y < row+4; y++)
+      for (x=0; x < width; x++)
+    if ((x+y) & 1) {
+      val = (BAYER(y,x)-2048)*2 + (BAYER(y,x-1)+BAYER(y,x+1))/2;
+      if (val < 0) val = 0;
+      BAYER(y,x) = val;
+    }
+  }
+  maximum = 0x1fff;     /* wild guess */
+}
+
+#undef FORYX
+#undef PREDICTOR
+
+#ifndef HAVE_JPEG
+void kodak_jpeg_load_raw() {}
+#else
+
+static boolean
+fill_input_buffer (j_decompress_ptr cinfo)
+{
+  static char jpeg_buffer[4096];
+  size_t nbytes;
+
+  nbytes = fread (jpeg_buffer, 1, 4096, ifp);
+  swab (jpeg_buffer, jpeg_buffer, nbytes);
+  cinfo->src->next_input_byte = jpeg_buffer;
+  cinfo->src->bytes_in_buffer = nbytes;
+  return TRUE;
+}
+
+void
+kodak_jpeg_load_raw()
+{
+  struct jpeg_decompress_struct cinfo;
+  struct jpeg_error_mgr jerr;
+  JSAMPARRAY buf;
+  JSAMPLE (*pixel)[3];
+  int row, col;
+
+  cinfo.err = jpeg_std_error (&jerr);
+  jpeg_create_decompress (&cinfo);
+  jpeg_stdio_src (&cinfo, ifp);
+  cinfo.src->fill_input_buffer = fill_input_buffer;
+  jpeg_read_header (&cinfo, TRUE);
+  jpeg_start_decompress (&cinfo);
+  if ((cinfo.output_width      != width  ) ||
+      (cinfo.output_height*2   != height ) ||
+      (cinfo.output_components != 3      )) {
+    pm_error ("incorrect JPEG dimensions");
+  }
+  buf = (*cinfo.mem->alloc_sarray)
+        ((j_common_ptr) &cinfo, JPOOL_IMAGE, width*3, 1);
+
+  while (cinfo.output_scanline < cinfo.output_height) {
+    row = cinfo.output_scanline * 2;
+    jpeg_read_scanlines (&cinfo, buf, 1);
+    pixel = (void *) buf[0];
+    for (col=0; col < width; col+=2) {
+      BAYER(row+0,col+0) = pixel[col+0][1] << 1;
+      BAYER(row+1,col+1) = pixel[col+1][1] << 1;
+      BAYER(row+0,col+1) = pixel[col][0] + pixel[col+1][0];
+      BAYER(row+1,col+0) = pixel[col][2] + pixel[col+1][2];
+    }
+  }
+  jpeg_finish_decompress (&cinfo);
+  jpeg_destroy_decompress (&cinfo);
+  maximum = 0xff << 1;
+}
+
+#endif
+
+void
+kodak_dc120_load_raw()
+{
+  static const int mul[4] = { 162, 192, 187,  92 };
+  static const int add[4] = {   0, 636, 424, 212 };
+  unsigned char pixel[848];
+  int row, shift, col;
+
+  for (row=0; row < height; row++) {
+    fread (pixel, 848, 1, ifp);
+    shift = row * mul[row & 3] + add[row & 3];
+    for (col=0; col < width; col++)
+      BAYER(row,col) = (unsigned short) pixel[(col + shift) % 848];
+  }
+  maximum = 0xff;
+}
+
+void
+kodak_dc20_coeff (float const juice)
+{
+  static const float my_coeff[3][4] =
+  { {  2.25,  0.75, -1.75, -0.25 },
+    { -0.25,  0.75,  0.75, -0.25 },
+    { -0.25, -1.75,  0.75,  2.25 } };
+  static const float flat[3][4] =
+  { {  1, 0,   0,   0 },
+    {  0, 0.5, 0.5, 0 },
+    {  0, 0,   0,   1 } };
+  int r, g;
+
+  for (r=0; r < 3; r++)
+    for (g=0; g < 4; g++)
+      coeff[r][g] = my_coeff[r][g] * juice + flat[r][g] * (1-juice);
+  use_coeff = 1;
+}
+
+void
+kodak_easy_load_raw()
+{
+  unsigned char *pixel;
+  unsigned row, col, icol;
+
+  if (raw_width > width)
+    black = 0;
+  pixel = calloc (raw_width, sizeof *pixel);
+  merror (pixel, "kodak_easy_load_raw()");
+  for (row=0; row < height; row++) {
+    fread (pixel, 1, raw_width, ifp);
+    for (col=0; col < raw_width; col++) {
+      icol = col - left_margin;
+      if (icol < width)
+    BAYER(row,icol) = (unsigned short) curve[pixel[col]];
+      else
+    black += curve[pixel[col]];
+    }
+  }
+  free (pixel);
+  if (raw_width > width)
+    black /= (raw_width - width) * height;
+  if (!strncmp(model,"DC2",3))
+    black = 0;
+  maximum = curve[0xff];
+}
+
+void
+kodak_compressed_load_raw()
+{
+  unsigned char c, blen[256];
+  unsigned short raw[6];
+  unsigned row, col, len, save, i, israw=0, bits=0, pred[2];
+  INT64 bitbuf=0;
+  int diff;
+
+  assert(have64BitArithmetic);
+
+  for (row=0; row < height; row++)
+    for (col=0; col < width; col++)
+    {
+      if ((col & 255) == 0) {       /* Get the bit-lengths of the */
+    len = width - col;      /* next 256 pixel values      */
+    if (len > 256) len = 256;
+    save = ftell(ifp);
+    for (israw=i=0; i < len; i+=2) {
+      c = fgetc(ifp);
+      if ((blen[i+0] = c & 15) > 12 ||
+          (blen[i+1] = c >> 4) > 12 )
+        israw = 1;
+    }
+    bitbuf = bits = pred[0] = pred[1] = 0;
+    if (len % 8 == 4) {
+      bitbuf  = fgetc(ifp) << 8;
+      bitbuf += fgetc(ifp);
+      bits = 16;
+    }
+    if (israw)
+      fseek (ifp, save, SEEK_SET);
+      }
+      if (israw) {          /* If the data is not compressed */
+    switch (col & 7) {
+      case 0:
+        read_shorts(ifp, raw, 6);
+        diff = raw[0] >> 12 << 8 | raw[2] >> 12 << 4 | raw[4] >> 12;
+        break;
+      case 1:
+        diff = raw[1] >> 12 << 8 | raw[3] >> 12 << 4 | raw[5] >> 12;
+        break;
+      default:
+        diff = raw[(col & 7) - 2] & 0xfff;
+    }
+      } else {              /* If the data is compressed */
+    len = blen[col & 255];      /* Number of bits for this pixel */
+    if (bits < len) {       /* Got enough bits in the buffer? */
+      for (i=0; i < 32; i+=8)
+        bitbuf += (INT64) fgetc(ifp) << (bits+(i^8));
+      bits += 32;
+    }
+    diff = bitbuf & (0xffff >> (16-len));  /* Pull bits from buffer */
+    bitbuf >>= len;
+    bits -= len;
+    if ((diff & (1 << (len-1))) == 0)
+      diff -= (1 << len) - 1;
+    pred[col & 1] += diff;
+    diff = pred[col & 1];
+      }
+      BAYER(row,col) = curve[diff];
+    }
+}
+
+void
+kodak_yuv_load_raw()
+{
+  unsigned char c, blen[384];
+  unsigned row, col, len, bits=0;
+  INT64 bitbuf=0;
+  int i, li=0, si, diff, six[6], y[4], cb=0, cr=0, rgb[3];
+  unsigned short *ip;
+
+  assert(have64BitArithmetic);
+
+  for (row=0; row < height; row+=2)
+    for (col=0; col < width; col+=2) {
+      if ((col & 127) == 0) {
+    len = (width - col + 1) * 3 & -4;
+    if (len > 384) len = 384;
+    for (i=0; i < len; ) {
+      c = fgetc(ifp);
+      blen[i++] = c & 15;
+      blen[i++] = c >> 4;
+    }
+    li = bitbuf = bits = y[1] = y[3] = cb = cr = 0;
+    if (len % 8 == 4) {
+      bitbuf  = fgetc(ifp) << 8;
+      bitbuf += fgetc(ifp);
+      bits = 16;
+    }
+      }
+      for (si=0; si < 6; si++) {
+    len = blen[li++];
+    if (bits < len) {
+      for (i=0; i < 32; i+=8)
+        bitbuf += (INT64) fgetc(ifp) << (bits+(i^8));
+      bits += 32;
+    }
+    diff = bitbuf & (0xffff >> (16-len));
+    bitbuf >>= len;
+    bits -= len;
+    if ((diff & (1 << (len-1))) == 0)
+      diff -= (1 << len) - 1;
+    six[si] = diff;
+      }
+      y[0] = six[0] + y[1];
+      y[1] = six[1] + y[0];
+      y[2] = six[2] + y[3];
+      y[3] = six[3] + y[2];
+      cb  += six[4];
+      cr  += six[5];
+      for (i=0; i < 4; i++) {
+    ip = image[(row+(i >> 1))*width + col+(i & 1)];
+    rgb[0] = y[i] + cr;
+    rgb[1] = y[i];
+    rgb[2] = y[i] + cb;
+    FORC3 if (rgb[c] > 0) ip[c] = curve[rgb[c]];
+      }
+    }
+  maximum = 0xe74;
+}
+
+static void  sony_decrypt (unsigned *data, int len, int start, int key)
+{
+  static uint32_t pad[128];
+  unsigned int p;
+
+  if (start) {
+    for (p=0; p < 4; p++)
+      pad[p] = key = key * 48828125 + 1;
+    pad[3] = pad[3] << 1 | (pad[0]^pad[2]) >> 31;
+    for (p=4; p < 127; p++)
+      pad[p] = (pad[p-4]^pad[p-2]) << 1 | (pad[p-3]^pad[p-1]) >> 31;
+
+    /* Convert to big-endian */
+
+    for (p=0; p < 127; p++) {
+        union {
+            unsigned char bytes[4];
+            uint32_t word;
+        } u;
+
+        u.bytes[0] = pad[p] >> 24;
+        u.bytes[1] = pad[p] >> 16;
+        u.bytes[2] = pad[p] >>  8;
+        u.bytes[3] = pad[p] >>  0;
+        
+        pad[p] = u.word;
+    }
+  }
+  while (len--)
+    *data++ ^= pad[p++ & 0x7f] = pad[(p+1) & 0x7f] ^ pad[(p+65) & 0x7f];
+}
+
+void
+sony_load_raw()
+{
+  unsigned char head[40];
+  struct pixel {
+      unsigned char bytes[2];
+  };
+  struct pixel * pixelrow;
+  unsigned i, key, row, col;
+
+  fseek (ifp, 200896, SEEK_SET);
+  fseek (ifp, (unsigned) fgetc(ifp)*4 - 1, SEEK_CUR);
+  order = 0x4d4d;
+  key = get4(ifp);
+  fseek (ifp, 164600, SEEK_SET);
+  fread (head, 1, 40, ifp);
+  sony_decrypt ((void *) head, 10, 1, key);
+  for (i=26; i-- > 22; )
+    key = key << 8 | head[i];
+  fseek (ifp, data_offset, SEEK_SET);
+  MALLOCARRAY(pixelrow, raw_width);
+  merror (pixelrow, "sony_load_raw()");
+  for (row=0; row < height; row++) {
+    fread (pixelrow, 2, raw_width, ifp);
+    sony_decrypt ((void *) pixelrow, raw_width/2, !row, key);
+    for (col=9; col < left_margin; col++)
+      black += pixelrow[col].bytes[0] * 256 + pixelrow[col].bytes[1];
+    for (col=0; col < width; col++)
+      BAYER(row,col) =
+          pixelrow[col+left_margin].bytes[0] * 256 +
+          pixelrow[col+left_margin].bytes[1];
+  }
+  free (pixelrow);
+  if (left_margin > 9)
+    black /= (left_margin-9) * height;
+  maximum = 0x3ff0;
+}
+
+void
+parse_minolta(FILE * const ifp)
+{
+  int save, tag, len, offset, high=0, wide=0;
+
+  fseek (ifp, 4, SEEK_SET);
+  offset = get4(ifp) + 8;
+  while ((save=ftell(ifp)) < offset) {
+    tag = get4(ifp);
+    len = get4(ifp);
+    switch (tag) {
+      case 0x505244:                /* PRD */
+    fseek (ifp, 8, SEEK_CUR);
+    high = get2(ifp);
+    wide = get2(ifp);
+    break;
+      case 0x574247:                /* WBG */
+    get4(ifp);
+    camera_red  = get2(ifp);
+    camera_red /= get2(ifp);
+    camera_blue = get2(ifp);
+    camera_blue = get2(ifp) / camera_blue;
+    break;
+      case 0x545457:                /* TTW */
+    parse_tiff(ifp, ftell(ifp));
+    }
+    fseek (ifp, save+len+8, SEEK_SET);
+  }
+  raw_height = high;
+  raw_width  = wide;
+  data_offset = offset;
+}
+
+/*
+   CIFF block 0x1030 contains an 8x8 white sample.
+   Load this into white[][] for use in scale_colors().
+ */
+static void  ciff_block_1030()
+{
+  static const unsigned short key[] = { 0x410, 0x45f3 };
+  int i, bpp, row, col, vbits=0;
+  unsigned long bitbuf=0;
+
+  get2(ifp);
+  if (get4(ifp) != 0x80008) return;
+  if (get4(ifp) == 0) return;
+  bpp = get2(ifp);
+  if (bpp != 10 && bpp != 12) return;
+  for (i=row=0; row < 8; row++)
+    for (col=0; col < 8; col++) {
+      if (vbits < bpp) {
+    bitbuf = bitbuf << 16 | (get2(ifp) ^ key[i++ & 1]);
+    vbits += 16;
+      }
+      white[row][col] =
+    bitbuf << (LONG_BIT - vbits) >> (LONG_BIT - bpp);
+      vbits -= bpp;
+    }
+}
+
+/*
+   Parse a CIFF file, better known as Canon CRW format.
+ */
+void 
+parse_ciff(FILE * const ifp, 
+           int    const offset, 
+           int    const length)
+{
+  int tboff, nrecs, i, type, len, roff, aoff, save, wbi=-1;
+  static const int remap[] = { 1,2,3,4,5,1 };
+  static const int remap_10d[] = { 0,1,3,4,5,6,0,0,2,8 };
+  static const int remap_s70[] = { 0,1,2,9,4,3,6,7,8,9,10,0,0,0,7,0,0,8 };
+  unsigned short key[] = { 0x410, 0x45f3 };
+
+  if (strcmp(model,"Canon PowerShot G6") &&
+      strcmp(model,"Canon PowerShot S70") &&
+      strcmp(model,"Canon PowerShot Pro1"))
+    key[0] = key[1] = 0;
+  fseek (ifp, offset+length-4, SEEK_SET);
+  tboff = get4(ifp) + offset;
+  fseek (ifp, tboff, SEEK_SET);
+  nrecs = get2(ifp);
+  for (i = 0; i < nrecs; i++) {
+    type = get2(ifp);
+    len  = get4(ifp);
+    roff = get4(ifp);
+    aoff = offset + roff;
+    save = ftell(ifp);
+    if (type == 0x080a) {       /* Get the camera make and model */
+      fseek (ifp, aoff, SEEK_SET);
+      fread (make, 64, 1, ifp);
+      fseek (ifp, aoff+strlen(make)+1, SEEK_SET);
+      fread (model, 64, 1, ifp);
+    }
+    if (type == 0x102a) {       /* Find the White Balance index */
+      fseek (ifp, aoff+14, SEEK_SET);   /* 0=auto, 1=daylight, 2=cloudy ... */
+      wbi = get2(ifp);
+      if (((!strcmp(model,"Canon EOS DIGITAL REBEL") ||
+        !strcmp(model,"Canon EOS 300D DIGITAL"))) && wbi == 6)
+    wbi++;
+    }
+    if (type == 0x102c) {       /* Get white balance (G2) */
+      if (!strcmp(model,"Canon PowerShot G1") ||
+      !strcmp(model,"Canon PowerShot Pro90 IS")) {
+    fseek (ifp, aoff+120, SEEK_SET);
+    white[0][1] = get2(ifp);
+    white[0][0] = get2(ifp);
+    white[1][0] = get2(ifp);
+    white[1][1] = get2(ifp);
+      } else {
+    fseek (ifp, aoff+100, SEEK_SET);
+    goto common;
+      }
+    }
+    if (type == 0x0032) {       /* Get white balance (D30 & G3) */
+      if (!strcmp(model,"Canon EOS D30")) {
+    fseek (ifp, aoff+72, SEEK_SET);
+common:
+    camera_red   = get2(ifp) ^ key[0];
+    camera_red   =(get2(ifp) ^ key[1]) / camera_red;
+    camera_blue  = get2(ifp) ^ key[0];
+    camera_blue /= get2(ifp) ^ key[1];
+      } else if (!strcmp(model,"Canon PowerShot G6") ||
+         !strcmp(model,"Canon PowerShot S70")) {
+    fseek (ifp, aoff+96 + remap_s70[wbi]*8, SEEK_SET);
+    goto common;
+      } else if (!strcmp(model,"Canon PowerShot Pro1")) {
+    fseek (ifp, aoff+96 + wbi*8, SEEK_SET);
+    goto common;
+      } else {
+    fseek (ifp, aoff+80 + (wbi < 6 ? remap[wbi]*8 : 0), SEEK_SET);
+    if (!camera_red)
+      goto common;
+      }
+    }
+    if (type == 0x10a9) {       /* Get white balance (D60) */
+      if (!strcmp(model,"Canon EOS 10D"))
+    wbi = remap_10d[wbi];
+      fseek (ifp, aoff+2 + wbi*8, SEEK_SET);
+      camera_red  = get2(ifp);
+      camera_red /= get2(ifp);
+      camera_blue = get2(ifp);
+      camera_blue = get2(ifp) / camera_blue;
+    }
+    if (type == 0x1030 && (wbi == 6 || wbi == 15)) {
+      fseek (ifp, aoff, SEEK_SET);  /* Get white sample */
+      ciff_block_1030();
+    }
+    if (type == 0x1031) {       /* Get the raw width and height */
+      fseek (ifp, aoff+2, SEEK_SET);
+      raw_width  = get2(ifp);
+      raw_height = get2(ifp);
+    }
+    if (type == 0x180e) {       /* Get the timestamp */
+      fseek (ifp, aoff, SEEK_SET);
+      timestamp = get4(ifp);
+    }
+    if (type == 0x580e)
+      timestamp = len;
+    if (type == 0x1810) {       /* Get the rotation */
+      fseek (ifp, aoff+12, SEEK_SET);
+      flip = get4(ifp);
+    }
+    if (type == 0x1835) {       /* Get the decoder table */
+      fseek (ifp, aoff, SEEK_SET);
+      crw_init_tables (get4(ifp));
+    }
+    if (type >> 8 == 0x28 || type >> 8 == 0x30) /* Get sub-tables */
+      parse_ciff(ifp, aoff, len);
+    fseek (ifp, save, SEEK_SET);
+  }
+  if (wbi == 0 && !strcmp(model,"Canon EOS D30"))
+    camera_red = -1;            /* Use my auto WB for this photo */
+}
+
+void
+parse_rollei(FILE * const ifp)
+{
+  char line[128], *val;
+  int tx=0, ty=0;
+  struct tm t;
+  time_t ts;
+
+  fseek (ifp, 0, SEEK_SET);
+  do {
+    fgets (line, 128, ifp);
+    if ((val = strchr(line,'=')))
+      *val++ = 0;
+    else
+      val = line + strlen(line);
+    if (!strcmp(line,"DAT"))
+      sscanf (val, "%d.%d.%d", &t.tm_mday, &t.tm_mon, &t.tm_year);
+    if (!strcmp(line,"TIM"))
+      sscanf (val, "%d:%d:%d", &t.tm_hour, &t.tm_min, &t.tm_sec);
+    if (!strcmp(line,"HDR"))
+      data_offset = atoi(val);
+    if (!strcmp(line,"X  "))
+      raw_width = atoi(val);
+    if (!strcmp(line,"Y  "))
+      raw_height = atoi(val);
+    if (!strcmp(line,"TX "))
+      tx = atoi(val);
+    if (!strcmp(line,"TY "))
+      ty = atoi(val);
+  } while (strncmp(line,"EOHD",4));
+  t.tm_year -= 1900;
+  t.tm_mon -= 1;
+  putenv((char*)"TZ=");
+  if ((ts = mktime(&t)) > 0)
+    timestamp = ts;
+  data_offset += tx * ty * 2;
+  strcpy (make, "Rollei");
+  strcpy (model,"d530flex");
+}
+
+
+
+void
+parse_mos(FILE * const ifp, 
+          int    const offset)
+{
+    char data[40];
+    int skip, from, i, neut[4];
+
+    fseek (ifp, offset, SEEK_SET);
+    while (1) {
+        fread (data, 1, 8, ifp);
+        if (strcmp(data,"PKTS")) break;
+        fread (data, 1, 40, ifp);
+        skip = get4(ifp);
+        from = ftell(ifp);
+#ifdef USE_LCMS
+        if (!strcmp(data,"icc_camera_profile")) {
+            profile_length = skip;
+            profile_offset = from;
+        }
+#endif
+        if (!strcmp(data,"NeutObj_neutrals")) {
+            for (i=0; i < 4; i++)
+                fscanf (ifp, "%d", neut+i);
+            camera_red  = (float) neut[2] / neut[1];
+            camera_blue = (float) neut[2] / neut[3];
+        }
+        parse_mos(ifp, from);
+        fseek (ifp, skip+from, SEEK_SET);
+    }
+}
+
+void
+nikon_e950_coeff()
+{
+  int r, g;
+  static const float my_coeff[3][4] =
+  { { -1.936280,  1.800443, -1.448486,  2.584324 },
+    {  1.405365, -0.524955, -0.289090,  0.408680 },
+    { -1.204965,  1.082304,  2.941367, -1.818705 } };
+
+  for (r=0; r < 3; r++)
+    for (g=0; g < 4; g++)
+      coeff[r][g] = my_coeff[r][g];
+  use_coeff = 1;
+}
+
+
+
+static double getrat()
+{
+  double num = get4(ifp);
+  return num / get4(ifp);
+}
+
+
+
+static void 
+parse_makernote(FILE * const ifp)
+{
+  unsigned base=0, offset=0, entries, tag, type, len, save;
+  static const int size[] = { 1,1,1,2,4,8,1,1,2,4,8,4,8 };
+  short sorder;
+  char buf[10];
+/*
+   The MakerNote might have its own TIFF header (possibly with
+   its own byte-order!), or it might just be a table.
+ */
+  sorder = order;
+  fread (buf, 1, 10, ifp);
+  if (!strncmp (buf,"KC" ,2) ||     /* these aren't TIFF format */
+      !strncmp (buf,"MLY",3)) return;
+  if (!strcmp (buf,"Nikon")) {
+    base = ftell(ifp);
+    order = get2(ifp);
+    if (get2(ifp) != 42) goto quit;
+    offset = get4(ifp);
+    fseek (ifp, offset-8, SEEK_CUR);
+  } else if (!strncmp (buf,"FUJIFILM",8) ||
+         !strcmp  (buf,"Panasonic")) {
+    order = 0x4949;
+    fseek (ifp,  2, SEEK_CUR);
+  } else if (!strcmp (buf,"OLYMP") ||
+         !strcmp (buf,"LEICA") ||
+         !strcmp (buf,"EPSON"))
+    fseek (ifp, -2, SEEK_CUR);
+  else if (!strcmp (buf,"AOC") ||
+       !strcmp (buf,"QVC"))
+    fseek (ifp, -4, SEEK_CUR);
+  else fseek (ifp, -10, SEEK_CUR);
+
+  entries = get2(ifp);
+  while (entries--) {
+    tag  = get2(ifp);
+    type = get2(ifp);
+    len  = get4(ifp);
+    save = ftell(ifp);
+    if (len * size[type < 13 ? type:0] > 4)
+      fseek (ifp, get4(ifp)+base, SEEK_SET);
+
+    if (tag == 0xc && len == 4) {
+      camera_red  = getrat();
+      camera_blue = getrat();
+    }
+    if (tag == 0x14 && len == 2560 && type == 7) {
+      fseek (ifp, 1248, SEEK_CUR);
+      goto get2_256;
+    }
+    if (strstr(make,"PENTAX")) {
+      if (tag == 0x1b) tag = 0x1018;
+      if (tag == 0x1c) tag = 0x1017;
+    }
+    if (tag == 0x8c)
+      nikon_curve_offset = ftell(ifp) + 2112;
+    if (tag == 0x96)
+      nikon_curve_offset = ftell(ifp) + 2;
+    if (tag == 0x97) {
+      if (!strcmp(model,"NIKON D100 ")) {
+    fseek (ifp, 72, SEEK_CUR);
+    camera_red  = get2(ifp) / 256.0;
+    camera_blue = get2(ifp) / 256.0;
+      } else if (!strcmp(model,"NIKON D2H")) {
+    fseek (ifp, 10, SEEK_CUR);
+    goto get2_rggb;
+      } else if (!strcmp(model,"NIKON D70")) {
+    fseek (ifp, 20, SEEK_CUR);
+    camera_red  = get2(ifp);
+    camera_red /= get2(ifp);
+    camera_blue = get2(ifp);
+    camera_blue/= get2(ifp);
+      }
+    }
+    if (tag == 0xe0 && len == 17) {
+      get2(ifp);
+      raw_width  = get2(ifp);
+      raw_height = get2(ifp);
+    }
+    if (tag == 0x200 && len == 4)
+      black = (get2(ifp)+get2(ifp)+get2(ifp)+get2(ifp))/4;
+    if (tag == 0x201 && len == 4) {
+      camera_red  = get2(ifp);
+      camera_red /= get2(ifp);
+      camera_blue = get2(ifp);
+      camera_blue = get2(ifp) / camera_blue;
+    }
+    if (tag == 0x401 && len == 4) {
+      black = (get4(ifp)+get4(ifp)+get4(ifp)+get4(ifp))/4;
+    }
+    if (tag == 0xe80 && len == 256 && type == 7) {
+      fseek (ifp, 48, SEEK_CUR);
+      camera_red  = get2(ifp) * 508 * 1.078 / 0x10000;
+      camera_blue = get2(ifp) * 382 * 1.173 / 0x10000;
+    }
+    if (tag == 0xf00 && len == 614 && type == 7) {
+      fseek (ifp, 188, SEEK_CUR);
+      goto get2_256;
+    }
+    if (tag == 0x1017)
+      camera_red  = get2(ifp) / 256.0;
+    if (tag == 0x1018)
+      camera_blue = get2(ifp) / 256.0;
+    if (tag == 0x2011 && len == 2) {
+get2_256:
+      order = 0x4d4d;
+      camera_red  = get2(ifp) / 256.0;
+      camera_blue = get2(ifp) / 256.0;
+    }
+    if (tag == 0x4001) {
+      fseek (ifp, strstr(model,"EOS-1D") ? 68:50, SEEK_CUR);
+get2_rggb:
+      camera_red  = get2(ifp);
+      camera_red /= get2(ifp);
+      camera_blue = get2(ifp);
+      camera_blue = get2(ifp) / camera_blue;
+    }
+    fseek (ifp, save+4, SEEK_SET);
+  }
+quit:
+  order = sorder;
+}
+
+
+
+static void
+get_timestamp(FILE * const ifp)
+{
+/*
+   Since the TIFF DateTime string has no timezone information,
+   assume that the camera's clock was set to Universal Time.
+ */
+  struct tm t;
+  time_t ts;
+
+  if (fscanf (ifp, "%d:%d:%d %d:%d:%d", &t.tm_year, &t.tm_mon,
+    &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec) != 6)
+    return;
+  t.tm_year -= 1900;
+  t.tm_mon -= 1;
+  putenv((char*)"TZ=UTC");   /* Remove this to assume local time */
+  if ((ts = mktime(&t)) > 0)
+    timestamp = ts;
+}
+
+static void 
+parse_exif(FILE * const ifp, int base)
+{
+  int entries, tag, type, len, val, save;
+
+  entries = get2(ifp);
+  while (entries--) {
+    tag  = get2(ifp);
+    type = get2(ifp);
+    len  = get4(ifp);
+    val  = get4(ifp);
+    save = ftell(ifp);
+    fseek (ifp, base+val, SEEK_SET);
+    if (tag == 0x9003 || tag == 0x9004)
+      get_timestamp(ifp);
+    if (tag == 0x927c) {
+      if (!strncmp(make,"SONY",4))
+    data_offset = base+val+len;
+      else
+    parse_makernote(ifp);
+    }
+    fseek (ifp, save, SEEK_SET);
+  }
+}
+
+static int 
+parse_tiff_ifd(FILE * const ifp, int base, int level)
+{
+  unsigned entries, tag, type, len, plen=16, save;
+  int done=0, use_cm=0, cfa, i, j, c;
+  static const int size[] = { 1,1,1,2,4,8,1,1,2,4,8,4,8 };
+  char software[64];
+  static const int flip_map[] = { 0,1,3,2,4,6,7,5 };
+  unsigned char cfa_pat[16], cfa_pc[] = { 0,1,2,3 }, tab[256];
+  unsigned short scale[4];
+  double dblack, cc[4][4], cm[4][3];
+  double ab[]={ 1,1,1,1 }, asn[] = { 0,0,0,0 }, xyz[] = { 1,1,1 };
+
+  for (j=0; j < 4; j++)
+    for (i=0; i < 4; i++)
+      cc[j][i] = i == j;
+  entries = get2(ifp);
+  if (entries > 512) return 1;
+  while (entries--) {
+    tag  = get2(ifp);
+    type = get2(ifp);
+    len  = get4(ifp);
+    save = ftell(ifp);
+    if (tag > 50700 && tag < 50800) done = 1;
+    if (len * size[type < 13 ? type:0] > 4)
+      fseek (ifp, get4(ifp)+base, SEEK_SET);
+    switch (tag) {
+      case 0x11:
+    camera_red  = get4(ifp) / 256.0;
+    break;
+      case 0x12:
+    camera_blue = get4(ifp) / 256.0;
+    break;
+      case 0x100:           /* ImageWidth */
+    if (strcmp(make,"Canon") || level)
+      raw_width = type==3 ? get2(ifp) : get4(ifp);
+    break;
+      case 0x101:           /* ImageHeight */
+    if (strcmp(make,"Canon") || level)
+      raw_height = type==3 ? get2(ifp) : get4(ifp);
+    break;
+      case 0x102:           /* Bits per sample */
+    fuji_secondary = len == 2;
+    if (level) maximum = (1 << get2(ifp)) - 1;
+    break;
+      case 0x103:           /* Compression */
+    tiff_data_compression = get2(ifp);
+    break;
+      case 0x106:           /* Kodak color format */
+    kodak_data_compression = get2(ifp);
+    break;
+      case 0x10f:           /* Make */
+    fgets (make, 64, ifp);
+    break;
+      case 0x110:           /* Model */
+    fgets (model, 64, ifp);
+    break;
+      case 0x111:           /* StripOffset */
+    data_offset = get4(ifp);
+    break;
+      case 0x112:           /* Orientation */
+    flip = flip_map[(get2(ifp)-1) & 7];
+    break;
+      case 0x115:           /* SamplesPerPixel */
+    tiff_samples = get2(ifp);
+    break;
+      case 0x131:           /* Software tag */
+    fgets (software, 64, ifp);
+    if (!strncmp(software,"Adobe",5))
+      make[0] = 0;
+    break;
+      case 0x132:           /* DateTime tag */
+    get_timestamp(ifp);
+    break;
+      case 0x144:           /* TileOffsets */
+    if (level) {
+      data_offset = ftell(ifp);
+    } else {
+      strcpy (make, "Leaf");
+      data_offset = get4(ifp);
+    }
+    break;
+      case 0x14a:           /* SubIFD tag */
+    if (len > 2 && !is_dng && !strcmp(make,"Kodak"))
+        len = 2;
+    while (len--) {
+      i = ftell(ifp);
+      fseek (ifp, get4(ifp)+base, SEEK_SET);
+      if (parse_tiff_ifd(ifp, base, level+1)) break;
+      fseek (ifp, i+4, SEEK_SET);
+    }
+    break;
+      case 33405:           /* Model2 */
+    fgets (model2, 64, ifp);
+    break;
+      case 33422:           /* CFAPattern */
+    if ((plen=len) > 16) plen = 16;
+    fread (cfa_pat, 1, plen, ifp);
+    for (colors=cfa=i=0; i < plen; i++) {
+      colors += !(cfa & (1 << cfa_pat[i]));
+      cfa |= 1 << cfa_pat[i];
+    }
+    if (cfa == 070) memcpy (cfa_pc,"\003\004\005",3);   /* CMY */
+    if (cfa == 072) memcpy (cfa_pc,"\005\003\004\001",4);   /* GMCY */
+    goto guess_cfa_pc;
+      case 34665:           /* EXIF tag */
+    fseek (ifp, get4(ifp)+base, SEEK_SET);
+    parse_exif(ifp, base);
+    break;
+      case 50706:           /* DNGVersion */
+    is_dng = 1;
+    if (flip == 7) flip = 4;    /* Adobe didn't read the TIFF spec. */
+    break;
+      case 50710:           /* CFAPlaneColor */
+    if (len > 4) len = 4;
+    colors = len;
+    fread (cfa_pc, 1, colors, ifp);
+guess_cfa_pc:
+    FORC4 tab[cfa_pc[c]] = c;
+    for (i=16; i--; )
+      filters = filters << 2 | tab[cfa_pat[i % plen]];
+    break;
+      case 50711:           /* CFALayout */
+    if (get2(ifp) == 2) {
+      fuji_width = (raw_width+1)/2;
+      filters = 0x49494949;
+    }
+    break;
+      case 0x123:
+      case 0x90d:
+      case 50712:           /* LinearizationTable */
+    if (len > 0x1000)
+        len = 0x1000;
+    read_shorts(ifp, curve, len);
+    for (i=len; i < 0x1000; i++)
+      maximum = curve[i] = curve[i-1];
+    break;
+      case 50714:           /* BlackLevel */
+      case 50715:           /* BlackLevelDeltaH */
+      case 50716:           /* BlackLevelDeltaV */
+    for (dblack=i=0; i < len; i++)
+      dblack += getrat();
+    black += dblack/len + 0.5;
+    break;
+      case 50717:           /* WhiteLevel */
+    maximum = get2(ifp);
+    break;
+      case 50718:           /* DefaultScale */
+    for (i=0; i < 4; i++)
+      scale[i] = get4(ifp);
+    if (scale[1]*scale[2] == 2*scale[0]*scale[3]) ymag = 2;
+    break;
+      case 50721:           /* ColorMatrix1 */
+      case 50722:           /* ColorMatrix2 */
+    FORC4 for (j=0; j < 3; j++)
+      cm[c][j] = getrat();
+    use_cm = 1;
+    break;
+      case 50723:           /* CameraCalibration1 */
+      case 50724:           /* CameraCalibration2 */
+    for (i=0; i < colors; i++)
+      FORC4 cc[i][c] = getrat();    
+      case 50727:           /* AnalogBalance */
+    FORC4 ab[c] = getrat();
+    break;
+      case 50728:           /* AsShotNeutral */
+    FORC4 asn[c] = getrat();
+    break;
+      case 50729:           /* AsShotWhiteXY */
+    xyz[0] = getrat();
+    xyz[1] = getrat();
+    xyz[2] = 1 - xyz[0] - xyz[1];
+    }
+    fseek (ifp, save+4, SEEK_SET);
+  }
+  for (i=0; i < colors; i++)
+    FORC4 cc[i][c] *= ab[i];
+  if (use_cm)
+    dng_coeff (cc, cm, xyz);
+  if (asn[0])
+    FORC4 pre_mul[c] = 1 / asn[c];
+  if (!use_cm)
+    FORC4 pre_mul[c] /= cc[c][c];
+
+  if (is_dng || level) return done;
+
+  if ((raw_height & 1) && !strncmp (make,"OLYMPUS",7))
+       raw_height++;
+
+  if (make[0] == 0 && raw_width == 680 && raw_height == 680) {
+    strcpy (make, "Imacon");
+    strcpy (model,"Ixpress");
+  }
+  return done;
+}
+
+void
+parse_tiff(FILE * const ifp, int base)
+{
+  int doff;
+
+  fseek (ifp, base, SEEK_SET);
+  order = get2(ifp);
+  if (order != 0x4949 && order != 0x4d4d) return;
+  get2(ifp);
+  while ((doff = get4(ifp))) {
+    fseek (ifp, doff+base, SEEK_SET);
+    if (parse_tiff_ifd(ifp, base, 0)) break;
+  }
+  if (!is_dng && !strncmp(make,"Kodak",5)) {
+    fseek (ifp, 12+base, SEEK_SET);
+    parse_tiff_ifd(ifp, base, 2);
+  }
+}
+
+
+
+/*
+   Many cameras have a "debug mode" that writes JPEG and raw
+   at the same time.  The raw file has no header, so try to
+   to open the matching JPEG file and read its metadata.
+ */
+void
+parse_external_jpeg(const char * const ifname)
+{
+    const char *file, *ext;
+    char * jfile;
+    char * jext;
+    char * jname;
+
+    ext  = strrchr (ifname, '.');
+    file = strrchr (ifname, '/');
+    if (!file) file = strrchr (ifname, '\\');
+    if (!file) file = ifname-1;
+    file++;
+    if (strlen(ext) != 4 || ext-file != 8) return;
+    jname = malloc (strlen(ifname) + 1);
+    merror (jname, "parse_external()");
+    strcpy (jname, ifname);
+    jfile = jname + (file - ifname);
+    jext  = jname + (ext  - ifname);
+    if (strcasecmp (ext, ".jpg")) {
+        strcpy (jext, isupper(ext[1]) ? ".JPG":".jpg");
+        memcpy (jfile, file+4, 4);
+        memcpy (jfile+4, file, 4);
+    } else
+        while (isdigit(*--jext)) {
+            if (*jext != '9') {
+                (*jext)++;
+                break;
+            }
+            *jext = '0';
+        }
+    if (strcmp (jname, ifname)) {
+        FILE * ifP;
+        ifP = fopen (jname, "rb");
+        if (ifP) {
+            if (verbose)
+                pm_message ("Reading metadata from %s...", jname);
+            parse_tiff(ifP, 12);
+            fclose (ifP);
+        }
+    }
+    if (!timestamp)
+        pm_message ( "Failed to read metadata from %s", jname);
+    free (jname);
+}
+
diff --git a/converter/other/cameratopam/camera.h b/converter/other/cameratopam/camera.h
new file mode 100644
index 00000000..a1e884cf
--- /dev/null
+++ b/converter/other/cameratopam/camera.h
@@ -0,0 +1,131 @@
+#include <stdio.h>
+
+void 
+parse_ciff(FILE * const ifp,
+           int    const offset,
+           int    const length);
+
+void
+parse_external_jpeg(const char * const ifname);
+
+void
+parse_tiff(FILE * const ifp, int base);
+
+void
+parse_minolta(FILE * const ifp);
+
+void
+parse_rollei(FILE * const ifp);
+
+void
+parse_mos(FILE * const ifp,
+          int    const offset);
+
+void
+adobe_dng_load_raw_lj(void);
+
+void
+adobe_dng_load_raw_nc(void);
+
+int
+nikon_is_compressed(void);
+
+void
+nikon_compressed_load_raw(void);
+
+void
+nikon_e950_load_raw(void);
+
+void
+nikon_e950_coeff(void);
+
+int
+nikon_e990(void);
+
+int
+nikon_e2100(void);
+
+void
+nikon_e2100_load_raw(void);
+
+int
+minolta_z2(void);
+
+void
+fuji_s2_load_raw(void);
+
+void
+fuji_s3_load_raw(void);
+
+void
+fuji_s5000_load_raw(void);
+
+void
+unpacked_load_raw(void);
+
+void
+fuji_s7000_load_raw(void);
+
+void
+fuji_f700_load_raw(void);
+
+void
+packed_12_load_raw(void);
+
+void
+eight_bit_load_raw(void);
+
+void
+phase_one_load_raw(void);
+
+void
+ixpress_load_raw(void);
+
+void
+leaf_load_raw(void);
+
+void
+olympus_e300_load_raw(void);
+
+void
+olympus_cseries_load_raw(void);
+
+void
+sony_load_raw(void);
+
+void
+kodak_easy_load_raw(void);
+
+void
+kodak_compressed_load_raw(void);
+
+void
+kodak_yuv_load_raw(void);
+
+void
+kodak_dc20_coeff (float const juice);
+
+void
+kodak_radc_load_raw(void);
+
+void
+kodak_jpeg_load_raw(void);
+
+void
+kodak_dc120_load_raw(void);
+
+void
+rollei_load_raw(void);
+
+void
+casio_qv5700_load_raw(void);
+
+void
+nucore_load_raw(void);
+
+void
+nikon_load_raw(void);
+
+int
+pentax_optio33(void);
+
diff --git a/converter/other/cameratopam/cameratopam.c b/converter/other/cameratopam/cameratopam.c
new file mode 100644
index 00000000..92773c91
--- /dev/null
+++ b/converter/other/cameratopam/cameratopam.c
@@ -0,0 +1,906 @@
+/*
+   This is derived from Dave Coffin's raw photo decoder, dcraw.c,
+   Copyright 1997-2005 by Dave Coffin, dcoffin a cybercom o net.
+
+   See the COPYRIGHT file in the same directory as this file for
+   information on copyright and licensing.
+ */
+
+
+#define _BSD_SOURCE 1   /* Make sure string.h contains strcasecmp() */
+#define _XOPEN_SOURCE  /* Make sure unistd.h contains swab() */
+
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef __CYGWIN__
+#include <io.h>
+#endif
+#ifdef WIN32
+  #include <winsock2.h>
+  #pragma comment(lib, "ws2_32.lib")
+  #define strcasecmp stricmp
+  typedef __int64 INT64;
+  static bool const have64BitArithmetic = true;
+#else
+  #include <unistd.h>
+#endif
+
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "pam.h"
+
+#include "global_variables.h"
+#include "util.h"
+#include "decode.h"
+#include "identify.h"
+#include "bayer.h"
+#include "foveon.h"
+#include "dng.h"
+
+#if HAVE_INT64
+   typedef int64_t INT64;
+   static bool const have64BitArithmetic = true;
+#else
+   /* We define INT64 to something that lets the code compile, but we
+      should not execute any INT64 code, because it will get the wrong
+      result.  */
+   typedef int INT64;
+   static bool const have64BitArithmetic = false;
+#endif
+
+/*
+   All global variables are defined here, and all functions that
+   access them are prefixed with "CLASS".  Note that a thread-safe
+   C++ class cannot have non-const static local variables.
+ */
+FILE * ifp;
+short order;
+char make[64], model[70], model2[64], *meta_data;
+time_t timestamp;
+int data_offset, meta_offset, meta_length;
+int tiff_data_compression, kodak_data_compression;
+int raw_height, raw_width, top_margin, left_margin;
+int height, width, fuji_width, colors, tiff_samples;
+int black, maximum, clip_max;
+int iheight, iwidth, shrink;
+int is_dng, is_canon, is_foveon, use_coeff, use_gamma;
+int trim, flip, xmag, ymag;
+int zero_after_ff;
+unsigned filters;
+unsigned short (*image)[4], white[8][8], curve[0x1000];
+int fuji_secondary;
+float cam_mul[4], pre_mul[4], coeff[3][4];
+int histogram[3][0x2000];
+jmp_buf failure;
+bool use_secondary;
+bool verbose;
+
+#ifdef USE_LCMS
+#include <lcms.h>
+int profile_offset, profile_length;
+#endif
+
+#define CLASS
+
+#define FORC3 for (c=0; c < 3; c++)
+#define FORC4 for (c=0; c < colors; c++)
+
+static void CLASS merror (const void *ptr, const char *where)
+{
+    if (ptr == NULL)
+        pm_error ("Out of memory in %s", where);
+}
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  /* "-" means Standard Input */
+    float bright;
+    float red_scale;
+    float blue_scale;
+    const char * profile;
+    unsigned int identify_only;
+    unsigned int verbose;
+    unsigned int half_size;
+    unsigned int four_color_rgb;
+    unsigned int document_mode;
+    unsigned int quick_interpolate;
+    unsigned int use_auto_wb;
+    unsigned int use_camera_wb;
+    unsigned int use_camera_rgb;
+    unsigned int use_secondary;
+    unsigned int no_clip_color;
+    unsigned int linear;
+};
+
+
+static struct cmdlineInfo cmdline;
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that many of the strings that this function returns in the
+   *cmdlineP structure are actually in the supplied argv array.  And
+   sometimes, one of these strings is actually just a suffix of an entry
+   in argv!
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;
+    optEntry *option_def;
+    unsigned int option_def_index;
+    unsigned int brightSpec, red_scaleSpec, blue_scaleSpec,
+        profileSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;
+    opt.allowNegNum = FALSE;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "bright", 
+            OPT_FLOAT,   &cmdlineP->bright,     &brightSpec, 0);
+    OPTENT3(0, "red_scale", 
+            OPT_FLOAT,   &cmdlineP->red_scale,  &red_scaleSpec, 0);
+    OPTENT3(0, "blue_scale", 
+            OPT_FLOAT,   &cmdlineP->blue_scale, &blue_scaleSpec, 0);
+    OPTENT3(0, "profile", 
+            OPT_STRING,  &cmdlineP->profile,    &profileSpec, 0);
+    OPTENT3(0,   "identify_only",   
+            OPT_FLAG,    NULL, &cmdlineP->identify_only, 0);
+    OPTENT3(0,   "verbose",   
+            OPT_FLAG,    NULL, &cmdlineP->verbose, 0);
+    OPTENT3(0,   "half_size",   
+            OPT_FLAG,    NULL, &cmdlineP->half_size, 0);
+    OPTENT3(0,   "four_color_rgb",   
+            OPT_FLAG,    NULL, &cmdlineP->four_color_rgb, 0);
+    OPTENT3(0,   "document_mode",   
+            OPT_FLAG,    NULL, &cmdlineP->document_mode, 0);
+    OPTENT3(0,   "quick_interpolate",   
+            OPT_FLAG,    NULL, &cmdlineP->quick_interpolate, 0);
+    OPTENT3(0,   "balance_auto",   
+            OPT_FLAG,    NULL, &cmdlineP->use_auto_wb, 0);
+    OPTENT3(0,   "balance_camera",   
+            OPT_FLAG,    NULL, &cmdlineP->use_camera_wb, 0);
+    OPTENT3(0,   "use_secondary",   
+            OPT_FLAG,    NULL, &cmdlineP->use_secondary, 0);
+    OPTENT3(0,   "no_clip_color",   
+            OPT_FLAG,    NULL, &cmdlineP->no_clip_color, 0);
+    OPTENT3(0,   "rgb",   
+            OPT_FLAG,    NULL, &cmdlineP->use_camera_rgb, 0);
+    OPTENT3(0,   "linear",   
+            OPT_FLAG,    NULL, &cmdlineP->linear, 0);
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+    if (!brightSpec)
+        cmdlineP->bright = 1.0;
+    if (!red_scaleSpec)
+        cmdlineP->red_scale = 1.0;
+    if (!blue_scaleSpec)
+        cmdlineP->blue_scale = 1.0;
+    if (!profileSpec)
+        cmdlineP->profile = NULL;
+
+
+    if (argc - 1 == 0)
+        cmdlineP->inputFileName = strdup("-");  /* he wants stdin */
+    else if (argc - 1 == 1)
+        cmdlineP->inputFileName = strdup(argv[1]);
+    else 
+        pm_error("Too many arguments.  The only argument accepted "
+                 "is the input file name");
+}
+
+  
+/*
+   Seach from the current directory up to the root looking for
+   a ".badpixels" file, and fix those pixels now.
+ */
+static void CLASS bad_pixels()
+{
+  FILE *fp=NULL;
+  char *fname, *cp, line[128];
+  int len, time, row, col, r, c, rad, tot, n, fixed=0;
+
+  if (!filters) return;
+  for (len=16 ; ; len *= 2) {
+    fname = malloc (len);
+    if (!fname) return;
+    if (getcwd (fname, len-12)) break;
+    free (fname);
+    if (errno != ERANGE) return;
+  }
+#ifdef WIN32
+  if (fname[1] == ':')
+    memmove (fname, fname+2, len-2);
+  for (cp=fname; *cp; cp++)
+    if (*cp == '\\') *cp = '/';
+#endif
+  cp = fname + strlen(fname);
+  if (cp[-1] == '/') cp--;
+  while (*fname == '/') {
+    strcpy (cp, "/.badpixels");
+    if ((fp = fopen (fname, "r"))) break;
+    if (cp == fname) break;
+    while (*--cp != '/');
+  }
+  free (fname);
+  if (!fp) return;
+  while (fgets (line, 128, fp)) {
+    cp = strchr (line, '#');
+    if (cp) *cp = 0;
+    if (sscanf (line, "%d %d %d", &col, &row, &time) != 3) continue;
+    if ((unsigned) col >= width || (unsigned) row >= height) continue;
+    if (time > timestamp) continue;
+    for (tot=n=0, rad=1; rad < 3 && n==0; rad++)
+      for (r = row-rad; r <= row+rad; r++)
+    for (c = col-rad; c <= col+rad; c++)
+      if ((unsigned) r < height && (unsigned) c < width &&
+        (r != row || c != col) && FC(r,c) == FC(row,col)) {
+        tot += BAYER(r,c);
+        n++;
+      }
+    BAYER(row,col) = tot/n;
+    if (cmdline.verbose) {
+      if (!fixed++)
+          pm_message ("Fixed bad pixels at: %d,%d", col, row);
+    }
+  }
+  fclose (fp);
+}
+
+static void CLASS scale_colors()
+{
+  int row, col, c, val, shift=0;
+  int min[4], max[4], count[4];
+  double sum[4], dmin;
+
+  maximum -= black;
+  if (cmdline.use_auto_wb || (cmdline.use_camera_wb && camera_red == -1)) {
+    FORC4 min[c] = INT_MAX;
+    FORC4 max[c] = count[c] = sum[c] = 0;
+    for (row=0; row < height; row++)
+      for (col=0; col < width; col++)
+    FORC4 {
+      val = image[row*width+col][c];
+      if (!val) continue;
+      if (min[c] > val) min[c] = val;
+      if (max[c] < val) max[c] = val;
+      val -= black;
+      if (val > maximum-25) continue;
+      if (val < 0) val = 0;
+      sum[c] += val;
+      count[c]++;
+    }
+    FORC4 pre_mul[c] = count[c] / sum[c];
+  }
+  if (cmdline.use_camera_wb && camera_red != -1) {
+    FORC4 count[c] = sum[c] = 0;
+    for (row=0; row < 8; row++)
+      for (col=0; col < 8; col++) {
+    c = FC(row,col);
+    if ((val = white[row][col] - black) > 0)
+      sum[c] += val;
+    count[c]++;
+      }
+    val = 1;
+    FORC4 if (sum[c] == 0) val = 0;
+    if (val)
+      FORC4 pre_mul[c] = count[c] / sum[c];
+    else if (camera_red && camera_blue)
+      memcpy (pre_mul, cam_mul, sizeof pre_mul);
+    else
+      pm_message ("Cannot use camera white balance.");
+  }
+  if (!use_coeff) {
+    pre_mul[0] *= cmdline.red_scale;
+    pre_mul[2] *= cmdline.blue_scale;
+  }
+  dmin = DBL_MAX;
+  FORC4 if (dmin > pre_mul[c])
+        dmin = pre_mul[c];
+  FORC4 pre_mul[c] /= dmin;
+
+  while (maximum << shift < 0x8000) shift++;
+  FORC4 pre_mul[c] *= 1 << shift;
+  maximum <<= shift;
+
+  if (cmdline.linear || cmdline.bright < 1) {
+      maximum *= cmdline.bright;
+      if (maximum > 0xffff)
+          maximum = 0xffff;
+      FORC4 pre_mul[c] *= cmdline.bright;
+  }
+  if (cmdline.verbose) {
+    fprintf (stderr, "Scaling with black=%d, pre_mul[] =", black);
+    FORC4 fprintf (stderr, " %f", pre_mul[c]);
+    fputc ('\n', stderr);
+  }
+  clip_max = cmdline.no_clip_color ? 0xffff : maximum;
+  for (row=0; row < height; row++)
+    for (col=0; col < width; col++)
+      FORC4 {
+    val = image[row*width+col][c];
+    if (!val) continue;
+    val -= black;
+    val *= pre_mul[c];
+    if (val < 0) val = 0;
+    if (val > clip_max) val = clip_max;
+    image[row*width+col][c] = val;
+      }
+}
+
+/*
+   This algorithm is officially called:
+
+   "Interpolation using a Threshold-based variable number of gradients"
+
+   described in http://www-ise.stanford.edu/~tingchen/algodep/vargra.html
+
+   I've extended the basic idea to work with non-Bayer filter arrays.
+   Gradients are numbered clockwise from NW=0 to W=7.
+ */
+static void CLASS vng_interpolate()
+{
+  static const signed char *cp, terms[] = {
+    -2,-2,+0,-1,0,(char)0x01, -2,-2,+0,+0,1,(char)0x01, -2,-1,-1,+0,0,(char)0x01,
+    -2,-1,+0,-1,0,(char)0x02, -2,-1,+0,+0,0,(char)0x03, -2,-1,+0,+1,1,(char)0x01,
+    -2,+0,+0,-1,0,(char)0x06, -2,+0,+0,+0,1,(char)0x02, -2,+0,+0,+1,0,(char)0x03,
+    -2,+1,-1,+0,0,(char)0x04, -2,+1,+0,-1,1,(char)0x04, -2,+1,+0,+0,0,(char)0x06,
+    -2,+1,+0,+1,0,(char)0x02, -2,+2,+0,+0,1,(char)0x04, -2,+2,+0,+1,0,(char)0x04,
+    -1,-2,-1,+0,0,(char)0x80, -1,-2,+0,-1,0,(char)0x01, -1,-2,+1,-1,0,(char)0x01,
+    -1,-2,+1,+0,1,(char)0x01, -1,-1,-1,+1,0,(char)0x88, -1,-1,+1,-2,0,(char)0x40,
+    -1,-1,+1,-1,0,(char)0x22, -1,-1,+1,+0,0,(char)0x33, -1,-1,+1,+1,1,(char)0x11,
+    -1,+0,-1,+2,0,(char)0x08, -1,+0,+0,-1,0,(char)0x44, -1,+0,+0,+1,0,(char)0x11,
+    -1,+0,+1,-2,1,(char)0x40, -1,+0,+1,-1,0,(char)0x66, -1,+0,+1,+0,1,(char)0x22,
+    -1,+0,+1,+1,0,(char)0x33, -1,+0,+1,+2,1,(char)0x10, -1,+1,+1,-1,1,(char)0x44,
+    -1,+1,+1,+0,0,(char)0x66, -1,+1,+1,+1,0,(char)0x22, -1,+1,+1,+2,0,(char)0x10,
+    -1,+2,+0,+1,0,(char)0x04, -1,+2,+1,+0,1,(char)0x04, -1,+2,+1,+1,0,(char)0x04,
+    +0,-2,+0,+0,1,(char)0x80, +0,-1,+0,+1,1,(char)0x88, +0,-1,+1,-2,0,(char)0x40,
+    +0,-1,+1,+0,0,(char)0x11, +0,-1,+2,-2,0,(char)0x40, +0,-1,+2,-1,0,(char)0x20,
+    +0,-1,+2,+0,0,(char)0x30, +0,-1,+2,+1,1,(char)0x10, +0,+0,+0,+2,1,(char)0x08,
+    +0,+0,+2,-2,1,(char)0x40, +0,+0,+2,-1,0,(char)0x60, +0,+0,+2,+0,1,(char)0x20,
+    +0,+0,+2,+1,0,(char)0x30, +0,+0,+2,+2,1,(char)0x10, +0,+1,+1,+0,0,(char)0x44,
+    +0,+1,+1,+2,0,(char)0x10, +0,+1,+2,-1,1,(char)0x40, +0,+1,+2,+0,0,(char)0x60,
+    +0,+1,+2,+1,0,(char)0x20, +0,+1,+2,+2,0,(char)0x10, +1,-2,+1,+0,0,(char)0x80,
+    +1,-1,+1,+1,0,(char)0x88, +1,+0,+1,+2,0,(char)0x08, +1,+0,+2,-1,0,(char)0x40,
+    +1,+0,+2,+1,0,(char)0x10
+  }, chood[] = { -1,-1, -1,0, -1,+1, 0,+1, +1,+1, +1,0, +1,-1, 0,-1 };
+  unsigned short (*brow[5])[4], *pix;
+  int code[8][2][320], *ip, gval[8], gmin, gmax, sum[4];
+  int row, col, shift, x, y, x1, x2, y1, y2, t, weight, grads, color, diag;
+  int g, diff, thold, num, c;
+
+  for (row=0; row < 8; row++) {     /* Precalculate for bilinear */
+    for (col=1; col < 3; col++) {
+      ip = code[row][col & 1];
+      memset (sum, 0, sizeof sum);
+      for (y=-1; y <= 1; y++)
+    for (x=-1; x <= 1; x++) {
+      shift = (y==0) + (x==0);
+      if (shift == 2) continue;
+      color = FC(row+y,col+x);
+      *ip++ = (width*y + x)*4 + color;
+      *ip++ = shift;
+      *ip++ = color;
+      sum[color] += 1 << shift;
+    }
+      FORC4
+    if (c != FC(row,col)) {
+      *ip++ = c;
+      *ip++ = sum[c];
+    }
+    }
+  }
+  for (row=1; row < height-1; row++) {  /* Do bilinear interpolation */
+    for (col=1; col < width-1; col++) {
+      pix = image[row*width+col];
+      ip = code[row & 7][col & 1];
+      memset (sum, 0, sizeof sum);
+      for (g=8; g--; ) {
+    diff = pix[*ip++];
+    diff <<= *ip++;
+    sum[*ip++] += diff;
+      }
+      for (g=colors; --g; ) {
+    c = *ip++;
+    pix[c] = sum[c] / *ip++;
+      }
+    }
+  }
+  if (cmdline.quick_interpolate)
+    return;
+
+  for (row=0; row < 8; row++) {     /* Precalculate for VNG */
+    for (col=0; col < 2; col++) {
+      ip = code[row][col];
+      for (cp=terms, t=0; t < 64; t++) {
+    y1 = *cp++;  x1 = *cp++;
+    y2 = *cp++;  x2 = *cp++;
+    weight = *cp++;
+    grads = *cp++;
+    color = FC(row+y1,col+x1);
+    if (FC(row+y2,col+x2) != color) continue;
+    diag = (FC(row,col+1) == color && FC(row+1,col) == color) ? 2:1;
+    if (abs(y1-y2) == diag && abs(x1-x2) == diag) continue;
+    *ip++ = (y1*width + x1)*4 + color;
+    *ip++ = (y2*width + x2)*4 + color;
+    *ip++ = weight;
+    for (g=0; g < 8; g++)
+      if (grads & 1<<g) *ip++ = g;
+    *ip++ = -1;
+      }
+      *ip++ = INT_MAX;
+      for (cp=chood, g=0; g < 8; g++) {
+    y = *cp++;  x = *cp++;
+    *ip++ = (y*width + x) * 4;
+    color = FC(row,col);
+    if (FC(row+y,col+x) != color && FC(row+y*2,col+x*2) == color)
+      *ip++ = (y*width + x) * 8 + color;
+    else
+      *ip++ = 0;
+      }
+    }
+  }
+  brow[4] = calloc (width*3, sizeof **brow);
+  merror (brow[4], "vng_interpolate()");
+  for (row=0; row < 3; row++)
+    brow[row] = brow[4] + row*width;
+  for (row=2; row < height-2; row++) {      /* Do VNG interpolation */
+    for (col=2; col < width-2; col++) {
+      pix = image[row*width+col];
+      ip = code[row & 7][col & 1];
+      memset (gval, 0, sizeof gval);
+      while ((g = ip[0]) != INT_MAX) {      /* Calculate gradients */
+    num = (diff = pix[g] - pix[ip[1]]) >> 31;
+    gval[ip[3]] += (diff = ((diff ^ num) - num) << ip[2]);
+    ip += 5;
+    if ((g = ip[-1]) == -1) continue;
+    gval[g] += diff;
+    while ((g = *ip++) != -1)
+      gval[g] += diff;
+      }
+      ip++;
+      gmin = gmax = gval[0];            /* Choose a threshold */
+      for (g=1; g < 8; g++) {
+    if (gmin > gval[g]) gmin = gval[g];
+    if (gmax < gval[g]) gmax = gval[g];
+      }
+      if (gmax == 0) {
+    memcpy (brow[2][col], pix, sizeof *image);
+    continue;
+      }
+      thold = gmin + (gmax >> 1);
+      memset (sum, 0, sizeof sum);
+      color = FC(row,col);
+      for (num=g=0; g < 8; g++,ip+=2) {     /* Average the neighbors */
+    if (gval[g] <= thold) {
+      FORC4
+        if (c == color && ip[1])
+          sum[c] += (pix[c] + pix[ip[1]]) >> 1;
+        else
+          sum[c] += pix[ip[0] + c];
+      num++;
+    }
+      }
+      FORC4 {                   /* Save to buffer */
+    t = pix[color];
+    if (c != color) {
+      t += (sum[c] - sum[color])/num;
+      if (t < 0) t = 0;
+      if (t > clip_max) t = clip_max;
+    }
+    brow[2][col][c] = t;
+      }
+    }
+    if (row > 3)                /* Write buffer to image */
+      memcpy (image[(row-2)*width+2], brow[0]+2, (width-4)*sizeof *image);
+    for (g=0; g < 4; g++)
+      brow[(g-1) & 3] = brow[g];
+  }
+  memcpy (image[(row-2)*width+2], brow[0]+2, (width-4)*sizeof *image);
+  memcpy (image[(row-1)*width+2], brow[1]+2, (width-4)*sizeof *image);
+  free (brow[4]);
+}
+
+#ifdef USE_LCMS
+static void 
+apply_profile(FILE *       const ifP,
+              const char * const pfname)
+{
+  char *prof;
+  cmsHPROFILE hInProfile=NULL, hOutProfile;
+  cmsHTRANSFORM hTransform;
+
+  if (pfname)
+    hInProfile = cmsOpenProfileFromFile (pfname, "r");
+  else if (profile_length) {
+    prof = malloc (profile_length);
+    merror (prof, "apply_profile()");
+    fseek (ifP, profile_offset, SEEK_SET);
+    fread (prof, 1, profile_length, ifP);
+    hInProfile = cmsOpenProfileFromMem (prof, profile_length);
+    free (prof);
+  }
+  if (!hInProfile) return;
+  if (cmdline.verbose)
+      pm_message( "Applying color profile...");
+  maximum = 0xffff;
+  use_gamma = use_coeff = 0;
+
+  hOutProfile = cmsCreate_sRGBProfile();
+  hTransform = cmsCreateTransform (hInProfile, TYPE_RGBA_16,
+    hOutProfile, TYPE_RGBA_16, INTENT_PERCEPTUAL, 0);
+  cmsDoTransform (hTransform, image, image, width*height);
+
+  cmsDeleteTransform (hTransform);
+  cmsCloseProfile (hInProfile);
+  cmsCloseProfile (hOutProfile);
+}
+#else
+static void 
+apply_profile(FILE *       const ifP,
+              const char * const pfname)
+{
+}
+#endif
+
+/*
+   Convert the entire image to RGB colorspace and build a histogram.
+ */
+static void CLASS convert_to_rgb()
+{
+  int row, col, r, g, c=0;
+  unsigned short *img;
+  float rgb[3];
+
+  if (cmdline.document_mode)
+    colors = 1;
+  memset (histogram, 0, sizeof histogram);
+  for (row = trim; row < height-trim; row++)
+    for (col = trim; col < width-trim; col++) {
+      img = image[row*width+col];
+      if (cmdline.document_mode)
+    c = FC(row,col);
+      if (colors == 4 && !use_coeff)    /* Recombine the greens */
+    img[1] = (img[1] + img[3]) >> 1;
+      if (colors == 1)          /* RGB from grayscale */
+    for (r=0; r < 3; r++)
+      rgb[r] = img[c];
+      else if (use_coeff) {     /* RGB via coeff[][] */
+    for (r=0; r < 3; r++)
+      for (rgb[r]=g=0; g < colors; g++)
+        rgb[r] += img[g] * coeff[r][g];
+      } else                /* RGB from RGB (easy) */
+    goto norgb;
+      for (r=0; r < 3; r++) {
+    if (rgb[r] < 0)        rgb[r] = 0;
+    if (rgb[r] > clip_max) rgb[r] = clip_max;
+    img[r] = rgb[r];
+      }
+norgb:
+      for (r=0; r < 3; r++)
+    histogram[r][img[r] >> 3]++;
+    }
+}
+
+static void CLASS fuji_rotate()
+{
+  int i, wide, high, row, col;
+  double step;
+  float r, c, fr, fc;
+  unsigned ur, uc;
+  unsigned short (*img)[4], (*pix)[4];
+
+  if (!fuji_width) return;
+  if (cmdline.verbose)
+    pm_message ("Rotating image 45 degrees...");
+  fuji_width = (fuji_width + shrink) >> shrink;
+  step = sqrt(0.5);
+  wide = fuji_width / step;
+  high = (height - fuji_width) / step;
+  img = calloc (wide*high, sizeof *img);
+  merror (img, "fuji_rotate()");
+
+  for (row=0; row < high; row++)
+    for (col=0; col < wide; col++) {
+      ur = r = fuji_width + (row-col)*step;
+      uc = c = (row+col)*step;
+      if (ur > height-2 || uc > width-2) continue;
+      fr = r - ur;
+      fc = c - uc;
+      pix = image + ur*width + uc;
+      for (i=0; i < colors; i++)
+    img[row*wide+col][i] =
+      (pix[    0][i]*(1-fc) + pix[      1][i]*fc) * (1-fr) +
+      (pix[width][i]*(1-fc) + pix[width+1][i]*fc) * fr;
+    }
+  free (image);
+  width  = wide;
+  height = high;
+  image  = img;
+  fuji_width = 0;
+}
+
+static void CLASS flip_image()
+{
+    unsigned *flag;
+    int size, base, dest, next, row, col, temp;
+
+    struct imageCell {
+        unsigned char contents[8];
+    };
+    struct imageCell * img;
+    struct imageCell hold;
+
+    switch ((flip+3600) % 360) {
+    case 270:  flip = 5;  break;
+    case 180:  flip = 3;  break;
+    case  90:  flip = 6;
+    }
+    img = (struct imageCell *) image;
+    size = height * width;
+    flag = calloc ((size+31) >> 5, sizeof *flag);
+    merror (flag, "flip_image()");
+    for (base = 0; base < size; base++) {
+        if (flag[base >> 5] & (1 << (base & 31)))
+            continue;
+        dest = base;
+        hold = img[base];
+        while (1) {
+            if (flip & 4) {
+                row = dest % height;
+                col = dest / height;
+            } else {
+                row = dest / width;
+                col = dest % width;
+            }
+            if (flip & 2)
+                row = height - 1 - row;
+            if (flip & 1)
+                col = width - 1 - col;
+            next = row * width + col;
+            if (next == base) break;
+            flag[next >> 5] |= 1 << (next & 31);
+            img[dest] = img[next];
+            dest = next;
+        }
+        img[dest] = hold;
+    }
+    free (flag);
+    if (flip & 4) {
+        temp = height;
+        height = width;
+        width = temp;
+        temp = ymag;
+        ymag = xmag;
+        xmag = temp;
+    }
+}
+
+/*
+   Write the image as an RGB PAM image
+ */
+static void CLASS write_pam_nonlinear (FILE *ofp)
+{
+  unsigned char lut[0x10000];
+  int perc, c, val, total, i, row, col;
+  float white=0, r;
+  struct pam pam;
+  tuple * tuplerow;
+
+  pam.size   = sizeof(pam);
+  pam.len    = PAM_STRUCT_SIZE(tuple_type);
+  pam.file   = ofp;
+  pam.width  = xmag*(width-trim*2);
+  pam.height = ymag*(height-trim*2);
+  pam.depth  = 3;
+  pam.format = PAM_FORMAT;
+  pam.maxval = 255;
+  strcpy(pam.tuple_type, "RGB");
+
+  pnm_writepaminit(&pam);
+
+  tuplerow = pnm_allocpamrow(&pam);
+
+  perc = width * height * 0.01;     /* 99th percentile white point */
+  if (fuji_width) perc /= 2;
+  FORC3 {
+    for (val=0x2000, total=0; --val > 32; )
+      if ((total += histogram[c][val]) > perc) break;
+    if (white < val) white = val;
+  }
+  white *= 8 / cmdline.bright;
+  for (i=0; i < 0x10000; i++) {
+    r = i / white;
+    val = 256 * ( !use_gamma ? r :
+    r <= 0.018 ? r*4.5 : pow(r,0.45)*1.099-0.099 );
+    if (val > 255) val = 255;
+    lut[i] = val;
+  }
+  for (row=trim; row < height-trim; row++) {
+      for (col=trim; col < width-trim; col++) {
+          unsigned int plane;
+          for (plane=0; plane < pam.depth; ++plane) {
+              unsigned int copy;
+              for (copy=0; copy < xmag; ++copy) {
+                  unsigned int const pamcol = xmag*(col-trim)+copy;
+                  tuplerow[pamcol][plane] = lut[image[row*width+col][plane]];
+              }
+          }
+      }
+      {
+          unsigned int copy;
+          for (copy=0; copy < ymag; ++copy)
+              pnm_writepamrow(&pam, tuplerow);
+      }
+  }
+  pnm_freepamrow(tuplerow);
+}
+
+/*
+   Write the image to a 16-bit PAM file with linear color space
+ */
+static void CLASS write_pam_linear (FILE *ofp)
+{
+  int row;
+
+  struct pam pam;
+  tuple * tuplerow;
+
+  if (maximum < 256) maximum = 256;
+
+  pam.size   = sizeof(pam);
+  pam.len    = PAM_STRUCT_SIZE(tuple_type);
+  pam.file   = ofp;
+  pam.width  = width-trim*2;
+  pam.height = height-trim*2;
+  pam.depth  = 3;
+  pam.format = PAM_FORMAT;
+  pam.maxval = MAX(maximum, 256);
+  strcpy(pam.tuple_type, "RGB");
+
+  pnm_writepaminit(&pam);
+
+  tuplerow = pnm_allocpamrow(&pam);
+
+  for (row = trim; row < height-trim; row++) {
+      unsigned int col;
+      for (col = trim; col < width-trim; col++) {
+          unsigned int const pamCol = col - trim;
+          unsigned int plane;
+          for (plane = 0; plane < 3; ++plane)
+              tuplerow[pamCol][plane] = image[row*width+col][plane];
+      }
+      pnm_writepamrow(&pam, tuplerow);
+  }
+  pnm_freepamrow(tuplerow);
+}
+
+
+
+static void CLASS
+writePam(FILE * const ofP,
+         bool   const linear) {
+
+    if (linear)
+        write_pam_linear(ofP);
+    else
+        write_pam_nonlinear(ofP);
+}
+
+
+
+
+static void CLASS
+convertIt(FILE *    const ifP,
+          FILE *    const ofP,
+          loadRawFn const load_raw) {
+
+    shrink = cmdline.half_size && filters;
+    iheight = (height + shrink) >> shrink;
+    iwidth  = (width  + shrink) >> shrink;
+    image = calloc (iheight*iwidth*sizeof *image + meta_length, 1);
+    merror (image, "main()");
+    meta_data = (char *) (image + iheight*iwidth);
+    if (cmdline.verbose)
+        pm_message ("Loading %s %s image ...", make, model);
+
+    use_secondary = cmdline.use_secondary;  /* for load_raw() */
+
+    ifp = ifP;  /* Set global variable for (*load_raw)() */
+
+    load_raw();
+    bad_pixels();
+    height = iheight;
+    width  = iwidth;
+    if (is_foveon) {
+        if (cmdline.verbose)
+            pm_message ("Foveon interpolation...");
+        foveon_interpolate(coeff);
+    } else {
+        scale_colors();
+    }
+    if (shrink) filters = 0;
+    trim = 0;
+    if (filters && !cmdline.document_mode) {
+        trim = 1;
+        if (cmdline.verbose)
+            pm_message ("%s interpolation...",
+                        cmdline.quick_interpolate ? "Bilinear":"VNG");
+        vng_interpolate();
+    }
+    fuji_rotate();
+    apply_profile(ifP, cmdline.profile);
+    if (cmdline.verbose)
+        pm_message ("Converting to RGB colorspace...");
+    convert_to_rgb();
+
+    if (flip) {
+        if (cmdline.verbose)
+            pm_message ("Flipping image %c:%c:%c...",
+                        flip & 1 ? 'H':'0', flip & 2 ? 'V':'0', 
+                        flip & 4 ? 'T':'0');
+        flip_image();
+    }
+    writePam(ofP, cmdline.linear);
+}
+
+
+int 
+main (int argc, char **argv) {
+
+    FILE * const ofP = stdout;
+
+    FILE * ifP;
+    int rc;
+    loadRawFn load_raw;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    verbose = cmdline.verbose;
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    image = NULL;
+    
+    rc = identify(ifP,
+                  cmdline.use_secondary, cmdline.use_camera_rgb,
+                  cmdline.red_scale, cmdline.blue_scale,
+                  cmdline.four_color_rgb, cmdline.inputFileName,
+                  &load_raw);
+    if (rc != 0)
+        pm_error("Unable to identify the format of the input image");
+    else {
+        if (cmdline.identify_only) {
+            pm_message ("Input is a %s %s image.", make, model);
+        } else {
+            if (cmdline.verbose)
+                pm_message ("Input is a %s %s image.", make, model);
+            convertIt(ifP, ofP, load_raw);
+        }
+    }
+    pm_close(ifP);
+    pm_close(ofP);
+    free(image);
+
+    return 0;
+}
+
+
+
diff --git a/converter/other/cameratopam/canon.c b/converter/other/cameratopam/canon.c
new file mode 100644
index 00000000..a34771d0
--- /dev/null
+++ b/converter/other/cameratopam/canon.c
@@ -0,0 +1,172 @@
+#include <string.h>
+#include "mallocvar.h"
+#include "pm.h"
+#include "global_variables.h"
+#include "util.h"
+#include "decode.h"
+#include "bayer.h"
+#include "canon.h"
+
+
+void 
+canon_600_load_raw(void) {
+    unsigned char  data[1120], *dp;
+    unsigned short pixel[896], *pix;
+    int irow, orow, col;
+
+    for (irow=orow=0; irow < height; irow++)
+    {
+        fread (data, 1120, 1, ifp);
+        for (dp=data, pix=pixel; dp < data+1120; dp+=10, pix+=8)
+        {
+            pix[0] = (dp[0] << 2) + (dp[1] >> 6    );
+            pix[1] = (dp[2] << 2) + (dp[1] >> 4 & 3);
+            pix[2] = (dp[3] << 2) + (dp[1] >> 2 & 3);
+            pix[3] = (dp[4] << 2) + (dp[1]      & 3);
+            pix[4] = (dp[5] << 2) + (dp[9]      & 3);
+            pix[5] = (dp[6] << 2) + (dp[9] >> 2 & 3);
+            pix[6] = (dp[7] << 2) + (dp[9] >> 4 & 3);
+            pix[7] = (dp[8] << 2) + (dp[9] >> 6    );
+        }
+        for (col=0; col < width; col++)
+            BAYER(orow,col) = pixel[col];
+        for (col=width; col < 896; col++)
+            black += pixel[col];
+        if ((orow+=2) > height)
+            orow = 1;
+    }
+    black /= (896 - width) * height;
+    maximum = 0x3ff;
+}
+
+
+
+void
+canon_a5_load_raw(void) {
+    unsigned char  data[1940], *dp;
+    unsigned short pixel[1552], *pix;
+    int row, col;
+
+    for (row=0; row < height; row++) {
+        fread (data, raw_width * 10 / 8, 1, ifp);
+        for (dp=data, pix=pixel; pix < pixel+raw_width; dp+=10, pix+=8)
+        {
+            pix[0] = (dp[1] << 2) + (dp[0] >> 6);
+            pix[1] = (dp[0] << 4) + (dp[3] >> 4);
+            pix[2] = (dp[3] << 6) + (dp[2] >> 2);
+            pix[3] = (dp[2] << 8) + (dp[5]     );
+            pix[4] = (dp[4] << 2) + (dp[7] >> 6);
+            pix[5] = (dp[7] << 4) + (dp[6] >> 4);
+            pix[6] = (dp[6] << 6) + (dp[9] >> 2);
+            pix[7] = (dp[9] << 8) + (dp[8]     );
+        }
+        for (col=0; col < width; col++)
+            BAYER(row,col) = (pixel[col] & 0x3ff);
+        for (col=width; col < raw_width; col++)
+            black += pixel[col] & 0x3ff;
+    }
+    if (raw_width > width)
+        black /= (raw_width - width) * height;
+    maximum = 0x3ff;
+}
+
+
+
+/*
+   Return 0 if the image starts with compressed data,
+   1 if it starts with uncompressed low-order bits.
+
+   In Canon compressed data, 0xff is always followed by 0x00.
+ */
+static int
+canon_has_lowbits()
+{
+    unsigned char test[0x4000];
+    int ret=1, i;
+
+    fseek (ifp, 0, SEEK_SET);
+    fread (test, 1, sizeof test, ifp);
+    for (i=540; i < sizeof test - 1; i++)
+        if (test[i] == 0xff) {
+            if (test[i+1]) return 1;
+            ret=0;
+        }
+    return ret;
+}
+
+
+
+void 
+canon_compressed_load_raw(void) {
+    unsigned short *pixel, *prow;
+    int lowbits, i, row, r, col, save, val;
+    unsigned irow, icol;
+    struct decode *decode, *dindex;
+    int block, diffbuf[64], leaf, len, diff, carry=0, pnum=0, base[2];
+    unsigned char c;
+
+    MALLOCARRAY(pixel, raw_width*8);
+    if (pixel == NULL)
+        pm_error("Unable to allocate space for %u pixels", raw_width*8);
+    lowbits = canon_has_lowbits();
+    if (!lowbits) maximum = 0x3ff;
+    fseek (ifp, 540 + lowbits*raw_height*raw_width/4, SEEK_SET);
+    zero_after_ff = 1;
+    getbits(ifp, -1);
+    for (row = 0; row < raw_height; row += 8) {
+        for (block=0; block < raw_width >> 3; block++) {
+            memset (diffbuf, 0, sizeof diffbuf);
+            decode = first_decode;
+            for (i=0; i < 64; i++ ) {
+                for (dindex=decode; dindex->branch[0]; )
+                    dindex = dindex->branch[getbits(ifp, 1)];
+                leaf = dindex->leaf;
+                decode = second_decode;
+                if (leaf == 0 && i) break;
+                if (leaf == 0xff) continue;
+                i  += leaf >> 4;
+                len = leaf & 15;
+                if (len == 0) continue;
+                diff = getbits(ifp, len);
+                if ((diff & (1 << (len-1))) == 0)
+                    diff -= (1 << len) - 1;
+                if (i < 64) diffbuf[i] = diff;
+            }
+            diffbuf[0] += carry;
+            carry = diffbuf[0];
+            for (i=0; i < 64; i++ ) {
+                if (pnum++ % raw_width == 0)
+                    base[0] = base[1] = 512;
+                pixel[(block << 6) + i] = ( base[i & 1] += diffbuf[i] );
+            }
+        }
+        if (lowbits) {
+            save = ftell(ifp);
+            fseek (ifp, 26 + row*raw_width/4, SEEK_SET);
+            for (prow=pixel, i=0; i < raw_width*2; i++) {
+                c = fgetc(ifp);
+                for (r=0; r < 8; r+=2, prow++) {
+                    val = (*prow << 2) + ((c >> r) & 3);
+                    if (raw_width == 2672 && val < 512) val += 2;
+                    *prow = val;
+                }
+            }
+            fseek (ifp, save, SEEK_SET);
+        }
+        for (r=0; r < 8; r++) {
+            irow = row - top_margin + r;
+            if (irow >= height) continue;
+            for (col = 0; col < raw_width; col++) {
+                icol = col - left_margin;
+                if (icol < width)
+                    BAYER(irow,icol) = pixel[r*raw_width+col];
+                else
+                    black += pixel[r*raw_width+col];
+            }
+        }
+    }
+    free (pixel);
+    if (raw_width > width)
+        black /= (raw_width - width) * height;
+}
+
diff --git a/converter/other/cameratopam/canon.h b/converter/other/cameratopam/canon.h
new file mode 100644
index 00000000..041cdc4d
--- /dev/null
+++ b/converter/other/cameratopam/canon.h
@@ -0,0 +1,13 @@
+#ifndef CANON_H_INCLUDED
+#define CANON_H_INCLUDED
+
+void 
+canon_600_load_raw(void);
+
+void
+canon_a5_load_raw(void);
+
+void 
+canon_compressed_load_raw(void);
+
+#endif
diff --git a/converter/other/cameratopam/decode.c b/converter/other/cameratopam/decode.c
new file mode 100644
index 00000000..e3a62fab
--- /dev/null
+++ b/converter/other/cameratopam/decode.c
@@ -0,0 +1,172 @@
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "pm.h"
+
+#include "decode.h"
+
+struct decode first_decode[2048], *second_decode, *free_decode;
+
+
+
+void init_decoder() {
+  memset (first_decode, 0, sizeof first_decode);
+  free_decode = first_decode;
+}
+
+
+
+/*
+   Construct a decode tree according the specification in *source.
+   The first 16 bytes specify how many codes should be 1-bit, 2-bit
+   3-bit, etc.  Bytes after that are the leaf values.
+
+   For example, if the source is
+
+    { 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0,
+      0x04,0x03,0x05,0x06,0x02,0x07,0x01,0x08,0x09,0x00,0x0a,0x0b,0xff  },
+
+   then the code is
+
+    00      0x04
+    010     0x03
+    011     0x05
+    100     0x06
+    101     0x02
+    1100        0x07
+    1101        0x01
+    11100       0x08
+    11101       0x09
+    11110       0x00
+    111110      0x0a
+    1111110     0x0b
+    1111111     0xff
+ */
+
+
+unsigned char * 
+make_decoder(const unsigned char * const source, 
+             int                   const level) {
+
+    struct decode *cur;
+    static int leaf;
+    int i, next;
+
+    if (level==0) 
+        leaf=0;
+    cur = free_decode++;
+    if (free_decode > first_decode+2048) {
+        pm_error ("decoder table overflow");
+    }
+    for (i=next=0; i <= leaf && next < 16; )
+        i += source[next++];
+    if (i > leaf) {
+        if (level < next) {
+            cur->branch[0] = free_decode;
+            make_decoder (source, level+1);
+            cur->branch[1] = free_decode;
+            make_decoder (source, level+1);
+        } else
+            cur->leaf = source[16 + leaf++];
+    }
+    return (unsigned char *) source + 16 + leaf;
+}
+
+
+
+const int * 
+make_decoder_int(const int * const source,
+                 int         const level) {
+
+    const int * p;
+    struct decode *cur;
+
+    p = source;  /* initial value */
+
+    cur = free_decode++;
+    if (level < p[0]) {
+        cur->branch[0] = free_decode;
+        p = make_decoder_int (p, level+1);
+        cur->branch[1] = free_decode;
+        p = make_decoder_int (p, level+1);
+    } else {
+        cur->leaf = p[1];
+        p += 2;
+    }
+    return p;
+}
+
+
+
+void 
+crw_init_tables(unsigned int const table) {
+
+  unsigned int const clippedTableNum = MIN(2, table);
+
+  static const unsigned char first_tree[3][29] = {
+    { 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0,
+      0x04,0x03,0x05,0x06,0x02,0x07,0x01,0x08,0x09,0x00,0x0a,0x0b,0xff  },
+
+    { 0,2,2,3,1,1,1,1,2,0,0,0,0,0,0,0,
+      0x03,0x02,0x04,0x01,0x05,0x00,0x06,0x07,0x09,0x08,0x0a,0x0b,0xff  },
+
+    { 0,0,6,3,1,1,2,0,0,0,0,0,0,0,0,0,
+      0x06,0x05,0x07,0x04,0x08,0x03,0x09,0x02,0x00,0x0a,0x01,0x0b,0xff  },
+  };
+
+  static const unsigned char second_tree[3][180] = {
+    { 0,2,2,2,1,4,2,1,2,5,1,1,0,0,0,139,
+      0x03,0x04,0x02,0x05,0x01,0x06,0x07,0x08,
+      0x12,0x13,0x11,0x14,0x09,0x15,0x22,0x00,0x21,0x16,0x0a,0xf0,
+      0x23,0x17,0x24,0x31,0x32,0x18,0x19,0x33,0x25,0x41,0x34,0x42,
+      0x35,0x51,0x36,0x37,0x38,0x29,0x79,0x26,0x1a,0x39,0x56,0x57,
+      0x28,0x27,0x52,0x55,0x58,0x43,0x76,0x59,0x77,0x54,0x61,0xf9,
+      0x71,0x78,0x75,0x96,0x97,0x49,0xb7,0x53,0xd7,0x74,0xb6,0x98,
+      0x47,0x48,0x95,0x69,0x99,0x91,0xfa,0xb8,0x68,0xb5,0xb9,0xd6,
+      0xf7,0xd8,0x67,0x46,0x45,0x94,0x89,0xf8,0x81,0xd5,0xf6,0xb4,
+      0x88,0xb1,0x2a,0x44,0x72,0xd9,0x87,0x66,0xd4,0xf5,0x3a,0xa7,
+      0x73,0xa9,0xa8,0x86,0x62,0xc7,0x65,0xc8,0xc9,0xa1,0xf4,0xd1,
+      0xe9,0x5a,0x92,0x85,0xa6,0xe7,0x93,0xe8,0xc1,0xc6,0x7a,0x64,
+      0xe1,0x4a,0x6a,0xe6,0xb3,0xf1,0xd3,0xa5,0x8a,0xb2,0x9a,0xba,
+      0x84,0xa4,0x63,0xe5,0xc5,0xf3,0xd2,0xc4,0x82,0xaa,0xda,0xe4,
+      0xf2,0xca,0x83,0xa3,0xa2,0xc3,0xea,0xc2,0xe2,0xe3,0xff,0xff  },
+
+    { 0,2,2,1,4,1,4,1,3,3,1,0,0,0,0,140,
+      0x02,0x03,0x01,0x04,0x05,0x12,0x11,0x06,
+      0x13,0x07,0x08,0x14,0x22,0x09,0x21,0x00,0x23,0x15,0x31,0x32,
+      0x0a,0x16,0xf0,0x24,0x33,0x41,0x42,0x19,0x17,0x25,0x18,0x51,
+      0x34,0x43,0x52,0x29,0x35,0x61,0x39,0x71,0x62,0x36,0x53,0x26,
+      0x38,0x1a,0x37,0x81,0x27,0x91,0x79,0x55,0x45,0x28,0x72,0x59,
+      0xa1,0xb1,0x44,0x69,0x54,0x58,0xd1,0xfa,0x57,0xe1,0xf1,0xb9,
+      0x49,0x47,0x63,0x6a,0xf9,0x56,0x46,0xa8,0x2a,0x4a,0x78,0x99,
+      0x3a,0x75,0x74,0x86,0x65,0xc1,0x76,0xb6,0x96,0xd6,0x89,0x85,
+      0xc9,0xf5,0x95,0xb4,0xc7,0xf7,0x8a,0x97,0xb8,0x73,0xb7,0xd8,
+      0xd9,0x87,0xa7,0x7a,0x48,0x82,0x84,0xea,0xf4,0xa6,0xc5,0x5a,
+      0x94,0xa4,0xc6,0x92,0xc3,0x68,0xb5,0xc8,0xe4,0xe5,0xe6,0xe9,
+      0xa2,0xa3,0xe3,0xc2,0x66,0x67,0x93,0xaa,0xd4,0xd5,0xe7,0xf8,
+      0x88,0x9a,0xd7,0x77,0xc4,0x64,0xe2,0x98,0xa5,0xca,0xda,0xe8,
+      0xf3,0xf6,0xa9,0xb2,0xb3,0xf2,0xd2,0x83,0xba,0xd3,0xff,0xff  },
+
+    { 0,0,6,2,1,3,3,2,5,1,2,2,8,10,0,117,
+      0x04,0x05,0x03,0x06,0x02,0x07,0x01,0x08,
+      0x09,0x12,0x13,0x14,0x11,0x15,0x0a,0x16,0x17,0xf0,0x00,0x22,
+      0x21,0x18,0x23,0x19,0x24,0x32,0x31,0x25,0x33,0x38,0x37,0x34,
+      0x35,0x36,0x39,0x79,0x57,0x58,0x59,0x28,0x56,0x78,0x27,0x41,
+      0x29,0x77,0x26,0x42,0x76,0x99,0x1a,0x55,0x98,0x97,0xf9,0x48,
+      0x54,0x96,0x89,0x47,0xb7,0x49,0xfa,0x75,0x68,0xb6,0x67,0x69,
+      0xb9,0xb8,0xd8,0x52,0xd7,0x88,0xb5,0x74,0x51,0x46,0xd9,0xf8,
+      0x3a,0xd6,0x87,0x45,0x7a,0x95,0xd5,0xf6,0x86,0xb4,0xa9,0x94,
+      0x53,0x2a,0xa8,0x43,0xf5,0xf7,0xd4,0x66,0xa7,0x5a,0x44,0x8a,
+      0xc9,0xe8,0xc8,0xe7,0x9a,0x6a,0x73,0x4a,0x61,0xc7,0xf4,0xc6,
+      0x65,0xe9,0x72,0xe6,0x71,0x91,0x93,0xa6,0xda,0x92,0x85,0x62,
+      0xf3,0xc5,0xb2,0xa4,0x84,0xba,0x64,0xa5,0xb3,0xd2,0x81,0xe5,
+      0xd3,0xaa,0xc4,0xca,0xf2,0xb1,0xe4,0xd1,0x83,0x63,0xea,0xc3,
+      0xe2,0x82,0xf1,0xa3,0xc2,0xa1,0xc1,0xe3,0xa2,0xe1,0xff,0xff  }
+  };
+
+  init_decoder();
+  make_decoder ( first_tree[clippedTableNum], 0);
+  second_decode = free_decode;
+  make_decoder (second_tree[clippedTableNum], 0);
+}
+
diff --git a/converter/other/cameratopam/decode.h b/converter/other/cameratopam/decode.h
new file mode 100644
index 00000000..b0addc82
--- /dev/null
+++ b/converter/other/cameratopam/decode.h
@@ -0,0 +1,22 @@
+struct decode {
+  struct decode *branch[2];
+  int leaf;
+}; 
+
+extern struct decode * free_decode;
+extern struct decode first_decode[2048];
+extern struct decode * second_decode;
+
+void 
+init_decoder(void);
+
+void 
+crw_init_tables(unsigned int const table);
+
+const int * 
+make_decoder_int (const int * const source, 
+                  int         const level);
+
+unsigned char * 
+make_decoder(const unsigned char * const source, 
+             int                   const level);
diff --git a/converter/other/cameratopam/dng.c b/converter/other/cameratopam/dng.c
new file mode 100644
index 00000000..44d45b02
--- /dev/null
+++ b/converter/other/cameratopam/dng.c
@@ -0,0 +1,73 @@
+#include <string.h>
+#include "global_variables.h"
+
+#include "dng.h"
+
+void 
+dng_coeff (double cc[4][4], double cm[4][3], double xyz[3])
+{
+  static const double rgb_xyz[3][3] = {     /* RGB from XYZ */
+    {  3.240479, -1.537150, -0.498535 },
+    { -0.969256,  1.875992,  0.041556 },
+    {  0.055648, -0.204043,  1.057311 } };
+#if 0
+  static const double xyz_rgb[3][3] = {     /* XYZ from RGB */
+    { 0.412453, 0.357580, 0.180423 },
+    { 0.212671, 0.715160, 0.072169 },
+    { 0.019334, 0.119193, 0.950227 } };
+#endif
+  double cam_xyz[4][3], xyz_cam[3][4], invert[3][6], num;
+  int i, j, k;
+
+  memset (cam_xyz, 0, sizeof cam_xyz);
+  for (i=0; i < colors; i++)
+    for (j=0; j < 3; j++)
+      for (k=0; k < colors; k++)
+    cam_xyz[i][j] += cc[i][k] * cm[k][j] * xyz[j];
+
+  for (i=0; i < colors; i++) {
+    for (num=j=0; j < 3; j++)
+      num += cam_xyz[i][j];
+    for (j=0; j < 3; j++)
+      cam_xyz[i][j] /= num;
+    pre_mul[i] = 1 / num;
+  }
+  for (i=0; i < 3; i++) {
+    for (j=0; j < 6; j++)
+      invert[i][j] = j == i+3;
+    for (j=0; j < 3; j++)
+      for (k=0; k < colors; k++)
+    invert[i][j] += cam_xyz[k][i] * cam_xyz[k][j];
+  }
+  for (i=0; i < 3; i++) {
+    num = invert[i][i];
+    for (j=0; j < 6; j++)       /* Normalize row i */
+      invert[i][j] /= num;
+    for (k=0; k < 3; k++) {     /* Subtract it from other rows */
+      if (k==i) continue;
+      num = invert[k][i];
+      for (j=0; j < 6; j++)
+    invert[k][j] -= invert[i][j] * num;
+    }
+  }
+  memset (xyz_cam, 0, sizeof xyz_cam);
+  for (i=0; i < 3; i++)
+    for (j=0; j < colors; j++)
+      for (k=0; k < 3; k++)
+    xyz_cam[i][j] += invert[i][k+3] * cam_xyz[j][k];
+
+  memset (coeff, 0, sizeof coeff);
+  for (i=0; i < 3; i++)
+    for (j=0; j < colors; j++)
+      for (k=0; k < 3; k++)
+    coeff[i][j] += rgb_xyz[i][k] * xyz_cam[k][j];
+
+  for (num=j=0; j < colors; j++)
+    num += coeff[1][j];
+  for (i=0; i < 3; i++) {
+    for (j=0; j < colors; j++)
+      coeff[i][j] /= num;
+  }
+  use_coeff = 1;
+}
+
diff --git a/converter/other/cameratopam/dng.h b/converter/other/cameratopam/dng.h
new file mode 100644
index 00000000..01e7e0a5
--- /dev/null
+++ b/converter/other/cameratopam/dng.h
@@ -0,0 +1,2 @@
+void 
+dng_coeff (double cc[4][4], double cm[4][3], double xyz[3]);
diff --git a/converter/other/cameratopam/foveon.c b/converter/other/cameratopam/foveon.c
new file mode 100644
index 00000000..0198940c
--- /dev/null
+++ b/converter/other/cameratopam/foveon.c
@@ -0,0 +1,790 @@
+/* This code is licensed to the public by its copyright owners under GPL. */
+
+#define _XOPEN_SOURCE   /* get M_PI */
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <float.h>
+#include <math.h>
+
+#include "mallocvar.h"
+#include "pm.h"
+#include "global_variables.h"
+#include "decode.h"
+#include "foveon.h"
+
+#if HAVE_INT64
+   typedef int64_t INT64;
+   static bool const have64BitArithmetic = true;
+#else
+   /* We define INT64 to something that lets the code compile, but we
+      should not execute any INT64 code, because it will get the wrong
+      result.
+   */
+   typedef int INT64;
+   static bool const have64BitArithmetic = false;
+#endif
+
+
+#define FORC3 for (c=0; c < 3; c++)
+#define FORC4 for (c=0; c < colors; c++)
+
+
+
+static char *  
+foveon_gets(int    const offset, 
+            char * const str, 
+            int    const len) {
+
+    unsigned int i;
+    fseek (ifp, offset, SEEK_SET);
+    for (i=0; i < len-1; ++i) {
+        /* It certains seems wrong that we're reading a 16 bit integer
+           and assigning it to char, but that's what Dcraw does.
+        */
+        unsigned short val;
+        pm_readlittleshortu(ifp, &val);
+        str[i] = val;
+        if (str[i] == 0)
+            break;
+    }
+    str[i] = 0;
+    return str;
+}
+
+
+
+void 
+parse_foveon(FILE * const ifp) {
+    long fliplong;
+    long pos;
+    long magic;
+    long junk;
+    long entries;
+
+    fseek (ifp, 36, SEEK_SET);
+    pm_readlittlelong(ifp, &fliplong);
+    flip = fliplong;
+    fseek (ifp, -4, SEEK_END);
+    pm_readlittlelong(ifp, &pos);
+    fseek (ifp, pos, SEEK_SET);
+    pm_readlittlelong(ifp, &magic);
+    if (magic != 0x64434553) 
+        return; /* SECd */
+    pm_readlittlelong(ifp, &junk);
+    pm_readlittlelong(ifp, &entries);
+    while (entries--) {
+        long off, len, tag;
+        long save;
+        long pent;
+        int i, poff[256][2];
+        char name[64];
+        long sec_;
+
+        pm_readlittlelong(ifp, &off);
+        pm_readlittlelong(ifp, &len);
+        pm_readlittlelong(ifp, &tag);
+            
+        save = ftell(ifp);
+        fseek (ifp, off, SEEK_SET);
+        pm_readlittlelong(ifp, &sec_);
+        if (sec_ != (0x20434553 | (tag << 24))) return;
+        switch (tag) {
+        case 0x47414d49:          /* IMAG */
+            if (data_offset) 
+                break;
+            data_offset = off + 28;
+            fseek (ifp, 12, SEEK_CUR);
+            {
+                long wlong, hlong;
+                pm_readlittlelong(ifp, &wlong);
+                pm_readlittlelong(ifp, &hlong);
+                raw_width  = wlong;
+                raw_height = hlong;
+            }
+            break;
+        case 0x464d4143:          /* CAMF */
+            meta_offset = off + 24;
+            meta_length = len - 28;
+            if (meta_length > 0x20000)
+                meta_length = 0x20000;
+            break;
+        case 0x504f5250:          /* PROP */
+            pm_readlittlelong(ifp, &junk);
+            pm_readlittlelong(ifp, &pent);
+            fseek (ifp, 12, SEEK_CUR);
+            off += pent*8 + 24;
+            if (pent > 256) pent=256;
+            for (i=0; i < pent*2; i++) {
+                long x;
+                pm_readlittlelong(ifp, &x);
+                poff[0][i] = off + x*2;
+            }
+            for (i=0; i < pent; i++) {
+                foveon_gets (poff[i][0], name, 64);
+                if (!strcmp (name, "CAMMANUF"))
+                    foveon_gets (poff[i][1], make, 64);
+                if (!strcmp (name, "CAMMODEL"))
+                    foveon_gets (poff[i][1], model, 64);
+                if (!strcmp (name, "WB_DESC"))
+                    foveon_gets (poff[i][1], model2, 64);
+                if (!strcmp (name, "TIME"))
+                    timestamp = atoi (foveon_gets (poff[i][1], name, 64));
+            }
+        }
+        fseek (ifp, save, SEEK_SET);
+    }
+    is_foveon = 1;
+}
+
+
+
+void  
+foveon_coeff(bool * const useCoeffP,
+             float        coeff[3][4]) {
+
+    static const float foveon[3][3] =
+    { {  1.4032, -0.2231, -0.1016 },
+      { -0.5263,  1.4816,  0.0170 },
+      { -0.0112,  0.0183,  0.9113 } };
+
+    int i, j;
+
+    for (i=0; i < 3; ++i)
+        for (j=0; j < 3; ++j)
+            coeff[i][j] = foveon[i][j];
+    *useCoeffP = 1;
+}
+
+
+
+static void  
+foveon_decoder (unsigned int const huff[1024], 
+                unsigned int const code) {
+
+    struct decode *cur;
+    int len;
+    unsigned int code2;
+
+    cur = free_decode++;
+    if (free_decode > first_decode+2048) {
+        pm_error ("decoder table overflow");
+    }
+    if (code) {
+        unsigned int i;
+        for (i=0; i < 1024; i++) {
+            if (huff[i] == code) {
+                cur->leaf = i;
+                return;
+            }
+        }
+    }
+    if ((len = code >> 27) > 26) 
+        return;
+    code2 = (len+1) << 27 | (code & 0x3ffffff) << 1;
+
+    cur->branch[0] = free_decode;
+    foveon_decoder (huff, code2);
+    cur->branch[1] = free_decode;
+    foveon_decoder (huff, code2+1);
+}
+
+
+
+static void  
+foveon_load_camf() {
+    unsigned int i, val;
+    long key;
+
+    fseek (ifp, meta_offset, SEEK_SET);
+    pm_readlittlelong(ifp, &key);
+    fread (meta_data, 1, meta_length, ifp);
+    for (i=0; i < meta_length; i++) {
+        key = (key * 1597 + 51749) % 244944;
+        assert(have64BitArithmetic);
+        val = key * (INT64) 301593171 >> 24;
+        meta_data[i] ^= ((((key << 8) - val) >> 1) + val) >> 17;
+    }
+}
+
+
+
+void  
+foveon_load_raw() {
+
+    struct decode *dindex;
+    short diff[1024], pred[3];
+    unsigned int huff[1024], bitbuf=0;
+    int row, col, bit=-1, c, i;
+
+    assert(have64BitArithmetic);
+
+    for (i=0; i < 1024; ++i)
+        pm_readlittleshort(ifp, &diff[i]);
+
+    for (i=0; i < 1024; ++i) {
+        long x;
+        pm_readlittlelong(ifp, &x);
+        huff[i] = x;
+    }
+    init_decoder();
+    foveon_decoder (huff, 0);
+
+    for (row=0; row < height; row++) {
+        long junk;
+        memset (pred, 0, sizeof pred);
+        if (!bit) 
+            pm_readlittlelong(ifp, &junk);
+        for (col=bit=0; col < width; col++) {
+            FORC3 {
+                for (dindex=first_decode; dindex->branch[0]; ) {
+                    if ((bit = (bit-1) & 31) == 31)
+                        for (i=0; i < 4; i++)
+                            bitbuf = (bitbuf << 8) + fgetc(ifp);
+                    dindex = dindex->branch[bitbuf >> bit & 1];
+                }
+                pred[c] += diff[dindex->leaf];
+            }
+            FORC3 image[row*width+col][c] = pred[c];
+        }
+    }
+    foveon_load_camf();
+    maximum = clip_max = 0xffff;
+}
+
+
+
+static int
+sget4(char const s[]) {
+    return s[0] | s[1] << 8 | s[2] << 16 | s[3] << 24;
+}
+
+
+
+static char *  
+foveon_camf_param (const char * const block, 
+                   const char * const param) {
+    unsigned idx, num;
+    char *pos, *cp, *dp;
+
+    for (idx=0; idx < meta_length; idx += sget4(pos+8)) {
+        pos = meta_data + idx;
+        if (strncmp (pos, "CMb", 3)) break;
+        if (pos[3] != 'P') continue;
+        if (strcmp (block, pos+sget4(pos+12))) continue;
+        cp = pos + sget4(pos+16);
+        num = sget4(cp);
+        dp = pos + sget4(cp+4);
+        while (num--) {
+            cp += 8;
+            if (!strcmp (param, dp+sget4(cp)))
+                return dp+sget4(cp+4);
+        }
+    }
+    return NULL;
+}
+
+
+
+static void *  
+foveon_camf_matrix (int                dim[3], 
+                    const char * const name) {
+
+    unsigned i, idx, type, ndim, size, *mat;
+    char *pos, *cp, *dp;
+
+    for (idx=0; idx < meta_length; idx += sget4(pos+8)) {
+        pos = meta_data + idx;
+        if (strncmp (pos, "CMb", 3)) break;
+        if (pos[3] != 'M') continue;
+        if (strcmp (name, pos+sget4(pos+12))) continue;
+        dim[0] = dim[1] = dim[2] = 1;
+        cp = pos + sget4(pos+16);
+        type = sget4(cp);
+        if ((ndim = sget4(cp+4)) > 3) break;
+        dp = pos + sget4(cp+8);
+        for (i=ndim; i--; ) {
+            cp += 12;
+            dim[i] = sget4(cp);
+        }
+        if ((size = dim[0]*dim[1]*dim[2]) > meta_length/4) break;
+        MALLOCARRAY(mat, size);
+        if (mat == NULL)
+            pm_error("Unable to allocate memory for size=%d", size);
+        for (i=0; i < size; i++)
+            if (type && type != 6)
+                mat[i] = sget4(dp + i*4);
+            else
+                mat[i] = sget4(dp + i*2) & 0xffff;
+        return mat;
+    }
+    pm_message ("'%s' matrix not found!", name);
+    return NULL;
+}
+
+
+
+static int  
+foveon_fixed (void *       const ptr, 
+              int          const size, 
+              const char * const name) {
+    void *dp;
+    int dim[3];
+
+    dp = foveon_camf_matrix (dim, name);
+    if (!dp) return 0;
+    memcpy (ptr, dp, size*4);
+    free (dp);
+    return 1;
+}
+
+static float  foveon_avg (unsigned short *pix, int range[2], float cfilt)
+{
+    int i;
+    float val, min=FLT_MAX, max=-FLT_MAX, sum=0;
+
+    for (i=range[0]; i <= range[1]; i++) {
+        sum += val = 
+            (short)pix[i*4] + ((short)pix[i*4]-(short)pix[(i-1)*4]) * cfilt;
+        if (min > val) min = val;
+        if (max < val) max = val;
+    }
+    return (sum - min - max) / (range[1] - range[0] - 1);
+}
+
+static short *foveon_make_curve (double max, double mul, double filt)
+{
+    short *curve;
+    int size;
+    unsigned int i;
+    double x;
+
+    size = 4*M_PI*max / filt;
+    MALLOCARRAY(curve, size+1);
+    if (curve == NULL)
+        pm_error("Out of memory for %d-element curve array", size);
+
+    curve[0] = size;
+    for (i=0; i < size; ++i) {
+        x = i*filt/max/4;
+        curve[i+1] = (cos(x)+1)/2 * tanh(i*filt/mul) * mul + 0.5;
+    }
+    return curve;
+}
+
+static void foveon_make_curves
+(short **curvep, float dq[3], float div[3], float filt)
+{
+    double mul[3], max=0;
+    int c;
+
+    FORC3 mul[c] = dq[c]/div[c];
+    FORC3 if (max < mul[c]) max = mul[c];
+    FORC3 curvep[c] = foveon_make_curve (max, mul[c], filt);
+}
+
+static int  foveon_apply_curve (short *curve, int i)
+{
+    if (abs(i) >= (unsigned short)curve[0]) return 0;
+    return i < 0 ? -(unsigned short)curve[1-i] : (unsigned short)curve[1+i];
+}
+
+void  
+foveon_interpolate(float coeff[3][4]) {
+
+    static const short hood[] = { 
+        -1,-1, -1,0, -1,1, 0,-1, 0,1, 1,-1, 1,0, 1,1 };
+    short *pix, prev[3], *curve[8], (*shrink)[3];
+    float cfilt=0.8, ddft[3][3][2], ppm[3][3][3];
+    float cam_xyz[3][3], correct[3][3], last[3][3], trans[3][3];
+    float chroma_dq[3], color_dq[3], diag[3][3], div[3];
+    float (*black)[3], (*sgain)[3], (*sgrow)[3];
+    float fsum[3], val, frow, num;
+    int row, col, c, i, j, diff, sgx, irow, sum, min, max, limit;
+    int dim[3], dscr[2][2], (*smrow[7])[3], total[4], ipix[3];
+    int work[3][3], smlast, smred, smred_p=0, dev[3];
+    int satlev[3], keep[4], active[4];
+    unsigned *badpix;
+    double dsum=0, trsum[3];
+    char str[128], *cp;
+
+    foveon_fixed (dscr, 4, "DarkShieldColRange");
+    foveon_fixed (ppm[0][0], 27, "PostPolyMatrix");
+    foveon_fixed (ddft[1][0], 12, "DarkDrift");
+    foveon_fixed (&cfilt, 1, "ColumnFilter");
+    foveon_fixed (satlev, 3, "SaturationLevel");
+    foveon_fixed (keep, 4, "KeepImageArea");
+    foveon_fixed (active, 4, "ActiveImageArea");
+    foveon_fixed (chroma_dq, 3, "ChromaDQ");
+    foveon_fixed (color_dq, 3,
+                  foveon_camf_param ("IncludeBlocks", "ColorDQ") ?
+                  "ColorDQ" : "ColorDQCamRGB");
+
+    if (!(cp = foveon_camf_param ("WhiteBalanceIlluminants", model2)))
+    { pm_message ( "Invalid white balance \"%s\"", model2);
+    return; }
+    foveon_fixed (cam_xyz, 9, cp);
+    foveon_fixed (correct, 9,
+                  foveon_camf_param ("WhiteBalanceCorrections", model2));
+    memset (last, 0, sizeof last);
+    for (i=0; i < 3; i++)
+        for (j=0; j < 3; j++)
+            FORC3 last[i][j] += correct[i][c] * cam_xyz[c][j];
+
+    sprintf (str, "%sRGBNeutral", model2);
+    if (foveon_camf_param ("IncludeBlocks", str))
+        foveon_fixed (div, 3, str);
+    else {
+#define LAST(x,y) last[(i+x)%3][(c+y)%3]
+        for (i=0; i < 3; i++)
+            FORC3 diag[c][i] = LAST(1,1)*LAST(2,2) - LAST(1,2)*LAST(2,1);
+#undef LAST
+        FORC3 div[c] = 
+            diag[c][0]*0.3127 + diag[c][1]*0.329 + diag[c][2]*0.3583;
+    }
+    num = 0;
+    FORC3 if (num < div[c]) num = div[c];
+    FORC3 div[c] /= num;
+
+    memset (trans, 0, sizeof trans);
+    for (i=0; i < 3; i++)
+        for (j=0; j < 3; j++)
+            FORC3 trans[i][j] += coeff[i][c] * last[c][j] * div[j];
+    FORC3 trsum[c] = trans[c][0] + trans[c][1] + trans[c][2];
+    dsum = (6*trsum[0] + 11*trsum[1] + 3*trsum[2]) / 20;
+    for (i=0; i < 3; i++)
+        FORC3 last[i][c] = trans[i][c] * dsum / trsum[i];
+    memset (trans, 0, sizeof trans);
+    for (i=0; i < 3; i++)
+        for (j=0; j < 3; j++)
+            FORC3 trans[i][j] += (i==c ? 32 : -1) * last[c][j] / 30;
+
+    foveon_make_curves (curve, color_dq, div, cfilt);
+    FORC3 chroma_dq[c] /= 3;
+    foveon_make_curves (curve+3, chroma_dq, div, cfilt);
+    FORC3 dsum += chroma_dq[c] / div[c];
+    curve[6] = foveon_make_curve (dsum, dsum, cfilt);
+    curve[7] = foveon_make_curve (dsum*2, dsum*2, cfilt);
+
+    sgain = foveon_camf_matrix (dim, "SpatialGain");
+    if (!sgain) return;
+    sgrow = calloc (dim[1], sizeof *sgrow);
+    sgx = (width + dim[1]-2) / (dim[1]-1);
+
+    black = calloc (height, sizeof *black);
+    for (row=0; row < height; row++) {
+        for (i=0; i < 6; i++)
+            ddft[0][0][i] = ddft[1][0][i] +
+                row / (height-1.0) * (ddft[2][0][i] - ddft[1][0][i]);
+        FORC3 black[row][c] =
+            ( foveon_avg (image[row*width]+c, dscr[0], cfilt) +
+              foveon_avg (image[row*width]+c, dscr[1], cfilt) * 3
+              - ddft[0][c][0] ) / 4 - ddft[0][c][1];
+    }
+    memcpy (black, black+8, sizeof *black*8);
+    memcpy (black+height-11, black+height-22, 11*sizeof *black);
+    memcpy (last, black, sizeof last);
+
+    for (row=1; row < height-1; row++) {
+        FORC3 if (last[1][c] > last[0][c]) {
+            if (last[1][c] > last[2][c])
+                black[row][c] = 
+                    (last[0][c] > last[2][c]) ? last[0][c]:last[2][c];
+        } else
+            if (last[1][c] < last[2][c])
+                black[row][c] = 
+                    (last[0][c] < last[2][c]) ? last[0][c]:last[2][c];
+        memmove (last, last+1, 2*sizeof last[0]);
+        memcpy (last[2], black[row+1], sizeof last[2]);
+    }
+    FORC3 black[row][c] = (last[0][c] + last[1][c])/2;
+    FORC3 black[0][c] = (black[1][c] + black[3][c])/2;
+
+    val = 1 - exp(-1/24.0);
+    memcpy (fsum, black, sizeof fsum);
+    for (row=1; row < height; row++)
+        FORC3 fsum[c] += black[row][c] =
+            (black[row][c] - black[row-1][c])*val + black[row-1][c];
+    memcpy (last[0], black[height-1], sizeof last[0]);
+    FORC3 fsum[c] /= height;
+    for (row = height; row--; )
+        FORC3 last[0][c] = black[row][c] =
+            (black[row][c] - fsum[c] - last[0][c])*val + last[0][c];
+
+    memset (total, 0, sizeof total);
+    for (row=2; row < height; row+=4)
+        for (col=2; col < width; col+=4) {
+            FORC3 total[c] += (short) image[row*width+col][c];
+            total[3]++;
+        }
+    for (row=0; row < height; row++)
+        FORC3 black[row][c] += fsum[c]/2 + total[c]/(total[3]*100.0);
+
+    for (row=0; row < height; row++) {
+        for (i=0; i < 6; i++)
+            ddft[0][0][i] = ddft[1][0][i] +
+                row / (height-1.0) * (ddft[2][0][i] - ddft[1][0][i]);
+        pix = (short*)image[row*width];
+        memcpy (prev, pix, sizeof prev);
+        frow = row / (height-1.0) * (dim[2]-1);
+        if ((irow = frow) == dim[2]-1) irow--;
+        frow -= irow;
+        for (i=0; i < dim[1]; i++)
+            FORC3 sgrow[i][c] = sgain[ irow   *dim[1]+i][c] * (1-frow) +
+                sgain[(irow+1)*dim[1]+i][c] *    frow;
+        for (col=0; col < width; col++) {
+            FORC3 {
+                diff = pix[c] - prev[c];
+                prev[c] = pix[c];
+                ipix[c] = pix[c] + floor ((diff + (diff*diff >> 14)) * cfilt
+                                          - ddft[0][c][1] 
+                                          - ddft[0][c][0] 
+                                            * ((float) col/width - 0.5)
+                                          - black[row][c] );
+            }
+            FORC3 {
+                work[0][c] = ipix[c] * ipix[c] >> 14;
+                work[2][c] = ipix[c] * work[0][c] >> 14;
+                work[1][2-c] = ipix[(c+1) % 3] * ipix[(c+2) % 3] >> 14;
+            }
+            FORC3 {
+                for (val=i=0; i < 3; i++)
+                    for (  j=0; j < 3; j++)
+                        val += ppm[c][i][j] * work[i][j];
+                ipix[c] = floor ((ipix[c] + floor(val)) *
+                                 ( sgrow[col/sgx  ][c] * (sgx - col%sgx) +
+                                   sgrow[col/sgx+1][c] * (col%sgx) ) / sgx / 
+                                 div[c]);
+                if (ipix[c] > 32000) ipix[c] = 32000;
+                pix[c] = ipix[c];
+            }
+            pix += 4;
+        }
+    }
+    free (black);
+    free (sgrow);
+    free (sgain);
+
+    if ((badpix = foveon_camf_matrix (dim, "BadPixels"))) {
+        for (i=0; i < dim[0]; i++) {
+            col = (badpix[i] >> 8 & 0xfff) - keep[0];
+            row = (badpix[i] >> 20       ) - keep[1];
+            if ((unsigned)(row-1) > height-3 || (unsigned)(col-1) > width-3)
+                continue;
+            memset (fsum, 0, sizeof fsum);
+            for (sum=j=0; j < 8; j++)
+                if (badpix[i] & (1 << j)) {
+                    FORC3 fsum[c] += 
+                        image[(row+hood[j*2])*width+col+hood[j*2+1]][c];
+                    sum++;
+                }
+            if (sum) FORC3 image[row*width+col][c] = fsum[c]/sum;
+        }
+        free (badpix);
+    }
+
+    /* Array for 5x5 Gaussian averaging of red values */
+    smrow[6] = calloc (width*5, sizeof **smrow);
+    if (smrow[6] == NULL)
+        pm_error("Out of memory");
+    for (i=0; i < 5; i++)
+        smrow[i] = smrow[6] + i*width;
+
+    /* Sharpen the reds against these Gaussian averages */
+    for (smlast=-1, row=2; row < height-2; row++) {
+        while (smlast < row+2) {
+            for (i=0; i < 6; i++)
+                smrow[(i+5) % 6] = smrow[i];
+            pix = (short*)image[++smlast*width+2];
+            for (col=2; col < width-2; col++) {
+                smrow[4][col][0] =
+                    (pix[0]*6 + (pix[-4]+pix[4])*4 + pix[-8]+pix[8] + 8) >> 4;
+                pix += 4;
+            }
+        }
+        pix = (short*)image[row*width+2];
+        for (col=2; col < width-2; col++) {
+            smred = ( 6 *  smrow[2][col][0]
+                      + 4 * (smrow[1][col][0] + smrow[3][col][0])
+                      +      smrow[0][col][0] + smrow[4][col][0] + 8 ) >> 4;
+            if (col == 2)
+                smred_p = smred;
+            i = pix[0] + ((pix[0] - ((smred*7 + smred_p) >> 3)) >> 3);
+            if (i > 32000) i = 32000;
+            pix[0] = i;
+            smred_p = smred;
+            pix += 4;
+        }
+    }
+
+    /* Adjust the brighter pixels for better linearity */
+    FORC3 {
+        i = satlev[c] / div[c];
+        if (maximum > i) maximum = i;
+    }
+    clip_max = maximum;
+    limit = maximum * 9 >> 4;
+    for (pix=(short*)image[0]; pix < (short *) image[height*width]; pix+=4) {
+        if (pix[0] <= limit || pix[1] <= limit || pix[2] <= limit)
+            continue;
+        min = max = pix[0];
+        for (c=1; c < 3; c++) {
+            if (min > pix[c]) min = pix[c];
+            if (max < pix[c]) max = pix[c];
+        }
+        i = 0x4000 - ((min - limit) << 14) / limit;
+        i = 0x4000 - (i*i >> 14);
+        i = i*i >> 14;
+        FORC3 pix[c] += (max - pix[c]) * i >> 14;
+    }
+    /*
+      Because photons that miss one detector often hit another,
+      the sum R+G+B is much less noisy than the individual colors.
+      So smooth the hues without smoothing the total.
+    */
+    for (smlast=-1, row=2; row < height-2; row++) {
+        while (smlast < row+2) {
+            for (i=0; i < 6; i++)
+                smrow[(i+5) % 6] = smrow[i];
+            pix = (short*)image[++smlast*width+2];
+            for (col=2; col < width-2; col++) {
+                FORC3 smrow[4][col][c] = (pix[c-4]+2*pix[c]+pix[c+4]+2) >> 2;
+                pix += 4;
+            }
+        }
+        pix = (short*)image[row*width+2];
+        for (col=2; col < width-2; col++) {
+            FORC3 dev[c] = -foveon_apply_curve (curve[7], pix[c] -
+                                                ((smrow[1][col][c] + 
+                                                  2*smrow[2][col][c] + 
+                                                  smrow[3][col][c]) >> 2));
+            sum = (dev[0] + dev[1] + dev[2]) >> 3;
+            FORC3 pix[c] += dev[c] - sum;
+            pix += 4;
+        }
+    }
+    for (smlast=-1, row=2; row < height-2; row++) {
+        while (smlast < row+2) {
+            for (i=0; i < 6; i++)
+                smrow[(i+5) % 6] = smrow[i];
+            pix = (short*)image[++smlast*width+2];
+            for (col=2; col < width-2; col++) {
+                FORC3 smrow[4][col][c] =
+                    (pix[c-8]+pix[c-4]+pix[c]+pix[c+4]+pix[c+8]+2) >> 2;
+                pix += 4;
+            }
+        }
+        pix = (short*)image[row*width+2];
+        for (col=2; col < width-2; col++) {
+            for (total[3]=375, sum=60, c=0; c < 3; c++) {
+                for (total[c]=i=0; i < 5; i++)
+                    total[c] += smrow[i][col][c];
+                total[3] += total[c];
+                sum += pix[c];
+            }
+            if (sum < 0) sum = 0;
+            j = total[3] > 375 ? (sum << 16) / total[3] : sum * 174;
+            FORC3 pix[c] += foveon_apply_curve (curve[6],
+                                                ((j*total[c] + 0x8000) >> 16) 
+                                                - pix[c]);
+            pix += 4;
+        }
+    }
+
+    /* Transform the image to a different colorspace */
+    for (pix=(short*)image[0]; pix < (short *) image[height*width]; pix+=4) {
+        FORC3 pix[c] -= foveon_apply_curve (curve[c], pix[c]);
+        sum = (pix[0]+pix[1]+pix[1]+pix[2]) >> 2;
+        FORC3 pix[c] -= foveon_apply_curve (curve[c], pix[c]-sum);
+        FORC3 {
+            for (dsum=i=0; i < 3; i++)
+                dsum += trans[c][i] * pix[i];
+            if (dsum < 0)  dsum = 0;
+            if (dsum > 24000) dsum = 24000;
+            ipix[c] = dsum + 0.5;
+        }
+        FORC3 pix[c] = ipix[c];
+    }
+
+    /* Smooth the image bottom-to-top and save at 1/4 scale */
+    MALLOCARRAY(shrink, (width/4) * (height/4));
+    if (shrink == NULL)
+        pm_error("Out of memory allocating 1/4 scale array");
+
+    for (row = height/4; row > 0; --row) {
+        for (col=0; col < width/4; ++col) {
+            ipix[0] = ipix[1] = ipix[2] = 0;
+            for (i=0; i < 4; i++)
+                for (j=0; j < 4; j++)
+                    FORC3 ipix[c] += image[(row*4+i)*width+col*4+j][c];
+            FORC3
+                if (row+2 > height/4)
+                    shrink[row*(width/4)+col][c] = ipix[c] >> 4;
+                else
+                    shrink[row*(width/4)+col][c] =
+                        (shrink[(row+1)*(width/4)+col][c]*1840 + 
+                         ipix[c]*141 + 2048) >> 12;
+        }
+    }
+    /* From the 1/4-scale image, smooth right-to-left */
+    for (row=0; row < (height & ~3); ++row) {
+        ipix[0] = ipix[1] = ipix[2] = 0;
+        if ((row & 3) == 0)
+            for (col = width & ~3 ; col--; )
+                FORC3 smrow[0][col][c] = ipix[c] =
+                    (shrink[(row/4)*(width/4)+col/4][c]*1485 + 
+                     ipix[c]*6707 + 4096) >> 13;
+
+        /* Then smooth left-to-right */
+        ipix[0] = ipix[1] = ipix[2] = 0;
+        for (col=0; col < (width & ~3); col++)
+            FORC3 smrow[1][col][c] = ipix[c] =
+                (smrow[0][col][c]*1485 + ipix[c]*6707 + 4096) >> 13;
+
+        /* Smooth top-to-bottom */
+        if (row == 0)
+            memcpy (smrow[2], smrow[1], sizeof **smrow * width);
+        else
+            for (col=0; col < (width & ~3); col++)
+                FORC3 smrow[2][col][c] =
+                    (smrow[2][col][c]*6707 + smrow[1][col][c]*1485 + 4096) 
+                        >> 13;
+
+        /* Adjust the chroma toward the smooth values */
+        for (col=0; col < (width & ~3); col++) {
+            for (i=j=30, c=0; c < 3; c++) {
+                i += smrow[2][col][c];
+                j += image[row*width+col][c];
+            }
+            j = (j << 16) / i;
+            for (sum=c=0; c < 3; c++) {
+                ipix[c] = foveon_apply_curve(
+                    curve[c+3],
+                    ((smrow[2][col][c] * j + 0x8000) >> 16) - 
+                    image[row*width+col][c]);
+                sum += ipix[c];
+            }
+            sum >>= 3;
+            FORC3 {
+                i = image[row*width+col][c] + ipix[c] - sum;
+                if (i < 0) i = 0;
+                image[row*width+col][c] = i;
+            }
+        }
+    }
+    free (shrink);
+    free (smrow[6]);
+    for (i=0; i < 8; i++)
+        free (curve[i]);
+
+    /* Trim off the black border */
+    active[1] -= keep[1];
+    active[3] -= 2;
+    i = active[2] - active[0];
+    for (row = 0; row < active[3]-active[1]; row++)
+        memcpy (image[row*i], image[(row+active[1])*width+active[0]],
+                i * sizeof *image);
+    width = i;
+    height = row;
+}
diff --git a/converter/other/cameratopam/foveon.h b/converter/other/cameratopam/foveon.h
new file mode 100644
index 00000000..57be2244
--- /dev/null
+++ b/converter/other/cameratopam/foveon.h
@@ -0,0 +1,14 @@
+#include "pm.h"
+
+void 
+parse_foveon(FILE * const ifp);
+
+void  
+foveon_interpolate(float coeff[3][4]);
+
+void 
+foveon_load_raw(void);
+
+void  
+foveon_coeff(bool * const useCoeffP,
+             float        coeff[3][4]);
diff --git a/converter/other/cameratopam/global_variables.h b/converter/other/cameratopam/global_variables.h
new file mode 100644
index 00000000..c8732d5a
--- /dev/null
+++ b/converter/other/cameratopam/global_variables.h
@@ -0,0 +1,55 @@
+/* These are unfortunately global variables used by various modules.
+
+   It would be nice to clean up this code and get rid of all of these.
+*/
+#include <stdio.h>
+#include <time.h>
+#include "pm_c_util.h"
+
+extern FILE * ifp;
+extern int flip;
+extern int data_offset;
+extern int raw_width;
+extern int raw_height;
+extern int height;
+extern int width;
+extern char * meta_data;
+extern int meta_offset;
+extern int meta_length;
+extern char make[64];
+extern char model[70];
+extern char model2[64];
+extern time_t timestamp;
+extern int is_foveon;
+extern int is_dng;
+extern int is_canon;
+extern unsigned short (*image)[4];
+extern int maximum;
+extern int clip_max;
+extern short order;
+extern int zero_after_ff;
+extern int shrink;
+extern int iwidth;
+extern unsigned int filters;
+extern int black;
+extern unsigned short white[8][8];
+extern int top_margin;
+extern int left_margin;
+extern int fuji_width;
+extern int tiff_samples;
+extern int tiff_data_compression;
+extern int kodak_data_compression;
+extern int fuji_secondary;
+extern int use_coeff;
+extern int use_gamma;
+extern int xmag;
+extern int ymag;
+extern float cam_mul[4];
+#define camera_red  cam_mul[0]
+#define camera_blue cam_mul[2]
+extern float pre_mul[4];
+extern int colors;
+extern unsigned short curve[0x1000];
+extern float coeff[3][4];
+extern int use_secondary;
+extern bool verbose;
diff --git a/converter/other/cameratopam/identify.c b/converter/other/cameratopam/identify.c
new file mode 100644
index 00000000..a101c8ad
--- /dev/null
+++ b/converter/other/cameratopam/identify.c
@@ -0,0 +1,1183 @@
+#define _BSD_SOURCE   /* Make sure strcasecmp() is in string.h */
+#include <string.h>
+
+#include "pm.h"
+
+#include "global_variables.h"
+#include "util.h"
+#include "foveon.h"
+#include "canon.h"
+#include "dng.h"
+#include "ljpeg.h"
+#include "camera.h"
+
+#include "identify.h"
+
+
+#if HAVE_INT64
+   static bool const have64BitArithmetic = true;
+#else
+   static bool const have64BitArithmetic = false;
+#endif
+
+
+static loadRawFn load_raw;
+
+/* This does the same as the function of the same name in the GNU C library */
+static const char *memmem_internal (const char *haystack, size_t haystacklen,
+                     const char *needle, size_t needlelen)
+{
+  const char *c;
+  for (c = haystack; c <= haystack + haystacklen - needlelen; c++)
+    if (!memcmp (c, needle, needlelen))
+      return c;
+  return NULL;
+}
+
+/*
+   Thanks to Adobe for providing these excellent CAM -> XYZ matrices!
+ */
+static void 
+adobe_coeff()
+{
+  static const struct {
+    const char *prefix;
+    short trans[12];
+  } table[] = {
+    { "Canon EOS D2000C",
+    { 24542,-10860,-3401,-1490,11370,-297,2858,-605,3225 } },
+    { "Canon EOS D30",
+    { 9805,-2689,-1312,-5803,13064,3068,-2438,3075,8775 } },
+    { "Canon EOS D60",
+    { 6188,-1341,-890,-7168,14489,2937,-2640,3228,8483 } },
+    { "Canon EOS 10D",
+    { 8197,-2000,-1118,-6714,14335,2592,-2536,3178,8266 } },
+    { "Canon EOS 20D",
+    { 6599,-537,-891,-8071,15783,2424,-1983,2234,7462 } },
+    { "Canon EOS-1Ds Mark II",
+    { 6517,-602,-867,-8180,15926,2378,-1618,1771,7633 } },
+    { "Canon EOS-1D Mark II",
+    { 6264,-582,-724,-8312,15948,2504,-1744,1919,8664 } },
+    { "Canon EOS-1DS",
+    { 4374,3631,-1743,-7520,15212,2472,-2892,3632,8161 } },
+    { "Canon EOS-1D",
+    { 6906,-278,-1017,-6649,15074,1621,-2848,3897,7611 } },
+    { "Canon EOS",
+    { 8197,-2000,-1118,-6714,14335,2592,-2536,3178,8266 } },
+    { "Canon PowerShot 600",
+    { -3822,10019,1311,4085,-157,3386,-5341,10829,4812,-1969,10969,1126 } },
+    { "Canon PowerShot A50",
+    { -5300,9846,1776,3436,684,3939,-5540,9879,6200,-1404,11175,217 } },
+    { "Canon PowerShot A5",
+    { -4801,9475,1952,2926,1611,4094,-5259,10164,5947,-1554,10883,547 } },
+    { "Canon PowerShot G1",
+    { -4778,9467,2172,4743,-1141,4344,-5146,9908,6077,-1566,11051,557 } },
+    { "Canon PowerShot G2",
+    { 9087,-2693,-1049,-6715,14382,2537,-2291,2819,7790 } },
+    { "Canon PowerShot G3",
+    { 9212,-2781,-1073,-6573,14189,2605,-2300,2844,7664 } },
+    { "Canon PowerShot G5",
+    { 9757,-2872,-933,-5972,13861,2301,-1622,2328,7212 } },
+    { "Canon PowerShot G6",
+    { 9877,-3775,-871,-7613,14807,3072,-1448,1305,7485 } },
+    { "Canon PowerShot Pro1",
+    { 10062,-3522,-999,-7643,15117,2730,-765,817,7323 } },
+    { "Canon PowerShot Pro70",
+    { -4155,9818,1529,3939,-25,4522,-5521,9870,6610,-2238,10873,1342 } },
+    { "Canon PowerShot Pro90",
+    { -4963,9896,2235,4642,-987,4294,-5162,10011,5859,-1770,11230,577 } },
+    { "Canon PowerShot S30",
+    { 10566,-3652,-1129,-6552,14662,2006,-2197,2581,7670 } },
+    { "Canon PowerShot S40",
+    { 8510,-2487,-940,-6869,14231,2900,-2318,2829,9013 } },
+    { "Canon PowerShot S45",
+    { 8163,-2333,-955,-6682,14174,2751,-2077,2597,8041 } },
+    { "Canon PowerShot S50",
+    { 8882,-2571,-863,-6348,14234,2288,-1516,2172,6569 } },
+    { "Canon PowerShot S70",
+    { 9976,-3810,-832,-7115,14463,2906,-901,989,7889 } },
+    { "Contax N Digital",
+    { 7777,1285,-1053,-9280,16543,2916,-3677,5679,7060 } },
+    { "EPSON R-D1",
+    { 6827,-1878,-732,-8429,16012,2564,-704,592,7145 } },
+    { "FUJIFILM FinePix E550",
+    { 11044,-3888,-1120,-7248,15168,2208,-1531,2277,8069 } },
+    { "FUJIFILM FinePix F700",
+    { 10004,-3219,-1201,-7036,15047,2107,-1863,2565,7736 } },
+    { "FUJIFILM FinePix S20Pro",
+    { 10004,-3219,-1201,-7036,15047,2107,-1863,2565,7736 } },
+    { "FUJIFILM FinePix S2Pro",
+    { 12492,-4690,-1402,-7033,15423,1647,-1507,2111,7697 } },
+    { "FUJIFILM FinePix S3Pro",
+    { 11807,-4612,-1294,-8927,16968,1988,-2120,2741,8006 } },
+    { "FUJIFILM FinePix S5000",
+    { 8754,-2732,-1019,-7204,15069,2276,-1702,2334,6982 } },
+    { "FUJIFILM FinePix S5100",
+    { 11940,-4431,-1255,-6766,14428,2542,-993,1165,7421 } },
+    { "FUJIFILM FinePix S7000",
+    { 10190,-3506,-1312,-7153,15051,2238,-2003,2399,7505 } },
+    { "Kodak DCS315C",
+    { 17523,-4827,-2510,756,8546,-137,6113,1649,2250 } },
+    { "Kodak DCS330C",
+    { 20620,-7572,-2801,-103,10073,-396,3551,-233,2220 } },
+    { "KODAK DCS420",
+    { 10868,-1852,-644,-1537,11083,484,2343,628,2216 } },
+    { "KODAK DCS460",
+    { 10592,-2206,-967,-1944,11685,230,2206,670,1273 } },
+    { "KODAK EOSDCS1",
+    { 10592,-2206,-967,-1944,11685,230,2206,670,1273 } },
+    { "KODAK EOSDCS3B",
+    { 9898,-2700,-940,-2478,12219,206,1985,634,1031 } },
+    { "Kodak DCS520C",
+    { 24542,-10860,-3401,-1490,11370,-297,2858,-605,3225 } },
+    { "Kodak DCS560C",
+    { 20482,-7172,-3125,-1033,10410,-285,2542,226,3136 } },
+    { "Kodak DCS620C",
+    { 23617,-10175,-3149,-2054,11749,-272,2586,-489,3453 } },
+    { "Kodak DCS620X",
+    { 13095,-6231,154,12221,-21,-2137,895,4602,2258 } },
+    { "Kodak DCS660C",
+    { 18244,-6351,-2739,-791,11193,-521,3711,-129,2802 } },
+    { "Kodak DCS720X",
+    { 11775,-5884,950,9556,1846,-1286,-1019,6221,2728 } },
+    { "Kodak DCS760C",
+    { 16623,-6309,-1411,-4344,13923,323,2285,274,2926 } },
+    { "Kodak DCS Pro SLR",
+    { 5494,2393,-232,-6427,13850,2846,-1876,3997,5445 } },
+    { "Kodak DCS Pro 14nx",
+    { 5494,2393,-232,-6427,13850,2846,-1876,3997,5445 } },
+    { "Kodak DCS Pro 14",
+    { 7791,3128,-776,-8588,16458,2039,-2455,4006,6198 } },
+    { "Kodak ProBack645",
+    { 16414,-6060,-1470,-3555,13037,473,2545,122,4948 } },
+    { "Kodak ProBack",
+    { 21179,-8316,-2918,-915,11019,-165,3477,-180,4210 } },
+    { "LEICA DIGILUX 2",
+    { 11340,-4069,-1275,-7555,15266,2448,-2960,3426,7685 } },
+    { "Leaf Valeo",
+    { 8236,1746,-1314,-8251,15953,2428,-3673,5786,5771 } },
+    { "Minolta DiMAGE 5",
+    { 8983,-2942,-963,-6556,14476,2237,-2426,2887,8014 } },
+    { "Minolta DiMAGE 7",
+    { 9144,-2777,-998,-6676,14556,2281,-2470,3019,7744 } },
+    { "Minolta DiMAGE A1",
+    { 9274,-2547,-1167,-8220,16323,1943,-2273,2720,8340 } },
+    { "MINOLTA DiMAGE A200",
+    { 8560,-2487,-986,-8112,15535,2771,-1209,1324,7743 } },
+    { "Minolta DiMAGE A2",
+    { 9097,-2726,-1053,-8073,15506,2762,-966,981,7763 } },
+    { "MINOLTA DYNAX 7D",
+    { 10239,-3104,-1099,-8037,15727,2451,-927,925,6871 } },
+    { "NIKON D100",
+    { 5915,-949,-778,-7516,15364,2282,-1228,1337,6404 } },
+    { "NIKON D1H",
+    { 7577,-2166,-926,-7454,15592,1934,-2377,2808,8606 } },
+    { "NIKON D1X",
+    { 7620,-2173,-966,-7604,15843,1805,-2356,2811,8439 } },
+    { "NIKON D1",
+    { 7559,-2130,-965,-7611,15713,1972,-2478,3042,8290 } },
+    { "NIKON D2H",
+    { 5710,-901,-615,-8594,16617,2024,-2975,4120,6830 } },
+    { "NIKON D70",
+    { 7732,-2422,-789,-8238,15884,2498,-859,783,7330 } },
+    { "NIKON E995", /* copied from E5000 */
+    { -5547,11762,2189,5814,-558,3342,-4924,9840,5949,688,9083,96 } },
+    { "NIKON E2500",
+    { -5547,11762,2189,5814,-558,3342,-4924,9840,5949,688,9083,96 } },
+    { "NIKON E4500",
+    { -5547,11762,2189,5814,-558,3342,-4924,9840,5949,688,9083,96 } },
+    { "NIKON E5000",
+    { -5547,11762,2189,5814,-558,3342,-4924,9840,5949,688,9083,96 } },
+    { "NIKON E5400",
+    { 9349,-2987,-1001,-7919,15766,2266,-2098,2680,6839 } },
+    { "NIKON E5700",
+    { -5368,11478,2368,5537,-113,3148,-4969,10021,5782,778,9028,211 } },
+    { "NIKON E8400",
+    { 7842,-2320,-992,-8154,15718,2599,-1098,1342,7560 } },
+    { "NIKON E8700",
+    { 8489,-2583,-1036,-8051,15583,2643,-1307,1407,7354 } },
+    { "NIKON E8800",
+    { 7971,-2314,-913,-8451,15762,2894,-1442,1520,7610 } },
+    { "OLYMPUS C5050",
+    { 10508,-3124,-1273,-6079,14294,1901,-1653,2306,6237 } },
+    { "OLYMPUS C5060",
+    { 10445,-3362,-1307,-7662,15690,2058,-1135,1176,7602 } },
+    { "OLYMPUS C70",
+    { 10793,-3791,-1146,-7498,15177,2488,-1390,1577,7321 } },
+    { "OLYMPUS C80",
+    { 8606,-2509,-1014,-8238,15714,2703,-942,979,7760 } },
+    { "OLYMPUS E-10",
+    { 12745,-4500,-1416,-6062,14542,1580,-1934,2256,6603 } },
+    { "OLYMPUS E-1",
+    { 11846,-4767,-945,-7027,15878,1089,-2699,4122,8311 } },
+    { "OLYMPUS E-20",
+    { 13173,-4732,-1499,-5807,14036,1895,-2045,2452,7142 } },
+    { "OLYMPUS E-300",
+    { 7828,-1761,-348,-5788,14071,1830,-2853,4518,6557 } },
+    { "PENTAX *ist D",
+    { 9651,-2059,-1189,-8881,16512,2487,-1460,1345,10687 } },
+    { "Panasonic DMC-LC1",
+    { 11340,-4069,-1275,-7555,15266,2448,-2960,3426,7685 } },
+    { "SONY DSC-F828",
+    { 7924,-1910,-777,-8226,15459,2998,-1517,2199,6818,-7242,11401,3481 } },
+    { "SONY DSC-V3",
+    { 9877,-3775,-871,-7613,14807,3072,-1448,1305,7485 } }
+  };
+  double cc[4][4], cm[4][3], xyz[] = { 1,1,1 };
+  char name[130];
+  int i, j;
+
+  for (i=0; i < 4; i++)
+    for (j=0; j < 4; j++)
+      cc[i][j] = i == j;
+  sprintf (name, "%s %s", make, model);
+  for (i=0; i < sizeof table / sizeof *table; i++)
+    if (!strncmp (name, table[i].prefix, strlen(table[i].prefix))) {
+      for (j=0; j < 12; j++)
+    cm[0][j] = table[i].trans[j];
+      dng_coeff (cc, cm, xyz);
+      break;
+    }
+}
+
+/*
+   Identify which camera created this file, and set global variables
+   accordingly.  Return nonzero if the file cannot be decoded.
+ */
+int
+identify(FILE *       const ifp,
+         bool         const use_secondary,
+         bool         const use_camera_rgb,
+         float        const red_scale,
+         float        const blue_scale,
+         unsigned int const four_color_rgb,
+         const char * const inputFileName,
+         loadRawFn *  const loadRawFnP)
+{
+  char head[32];
+  char * c;
+  unsigned hlen, fsize, i;
+  static const struct {
+    int fsize;
+    char make[12], model[15], withjpeg;
+  } table[] = {
+    {    62464, "Kodak",    "DC20"       ,0 },
+    {   124928, "Kodak",    "DC20"       ,0 },
+    {   311696, "ST Micro", "STV680 VGA" ,0 },  /* SPYz */
+    {   787456, "Creative", "PC-CAM 600" ,0 },
+    {  2465792, "NIKON",    "E950"       ,1 },  /* or E800,E700 */
+    {  2940928, "NIKON",    "E2100"      ,1 },  /* or E2500 */
+    {  4771840, "NIKON",    "E990"       ,1 },  /* or E995 */
+    {  4775936, "NIKON",    "E3700"      ,1 },  /* or Optio 33WR */
+    {  5869568, "NIKON",    "E4300"      ,1 },  /* or DiMAGE Z2 */
+    {  5865472, "NIKON",    "E4500"      ,0 },
+    {  1976352, "CASIO",    "QV-2000UX"  ,0 },
+    {  3217760, "CASIO",    "QV-3*00EX"  ,0 },
+    {  6218368, "CASIO",    "QV-5700"    ,0 },
+    {  7530816, "CASIO",    "QV-R51"     ,1 },
+    {  7684000, "CASIO",    "QV-4000"    ,0 },
+    {  7753344, "CASIO",    "EX-Z55"     ,1 },
+    {  9313536, "CASIO",    "EX-P600"    ,1 },
+    { 10979200, "CASIO",    "EX-P700"    ,1 },
+    {  3178560, "PENTAX",   "Optio S"    ,1 },  /*  8-bit */
+    {  4841984, "PENTAX",   "Optio S"    ,1 },  /* 12-bit */
+    {  6114240, "PENTAX",   "Optio S4"   ,1 },  /* or S4i */
+    { 12582980, "Sinar",    ""           ,0 } };
+  static const char *corp[] =
+    { "Canon", "NIKON", "EPSON", "Kodak", "OLYMPUS", "PENTAX",
+      "MINOLTA", "Minolta", "Konica", "CASIO" };
+  float tmp;
+
+  /*  What format is this file?  Set make[] if we recognize it. */
+
+  raw_height = raw_width = fuji_width = flip = 0;
+  make[0] = model[0] = model2[0] = 0;
+  memset (white, 0, sizeof white);
+  timestamp = tiff_samples = 0;
+  data_offset = meta_length = tiff_data_compression = 0;
+  zero_after_ff = is_dng = fuji_secondary = filters = 0;
+  black = is_foveon = use_coeff = 0;
+  use_gamma = xmag = ymag = 1;
+  for (i=0; i < 4; i++) {
+    cam_mul[i] = 1 & i;
+    pre_mul[i] = 1;
+  }
+  colors = 3;
+  for (i=0; i < 0x1000; i++) curve[i] = i;
+  maximum = 0xfff;
+#ifdef USE_LCMS
+  profile_length = 0;
+#endif
+
+  order = get2(ifp);
+  hlen = get4(ifp);
+  fseek (ifp, 0, SEEK_SET);
+  fread (head, 1, 32, ifp);
+  fseek (ifp, 0, SEEK_END);
+  fsize = ftell(ifp);
+  if ((c = (char*)memmem_internal(head, 32, "MMMMRawT", 8))) {
+    strcpy (make, "Phase One");
+    data_offset = c - head;
+    fseek (ifp, data_offset + 8, SEEK_SET);
+    fseek (ifp, get4(ifp) + 136, SEEK_CUR);
+    raw_width = get4(ifp);
+    fseek (ifp, 12, SEEK_CUR);
+    raw_height = get4(ifp);
+  } else if (order == 0x4949 || order == 0x4d4d) {
+    if (!memcmp (head+6, "HEAPCCDR", 8)) {
+      data_offset = hlen;
+      parse_ciff(ifp, hlen, fsize - hlen);
+    } else {
+      parse_tiff(ifp, 0);
+      if (!strncmp(make,"NIKON",5) && filters == 0)
+    make[0] = 0;
+    }
+  } else if (!memcmp (head, "\0MRM", 4))
+    parse_minolta(ifp);
+    else if (!memcmp (head, "\xff\xd8\xff\xe1", 4) &&
+         !memcmp (head+6, "Exif", 4)) {
+    fseek (ifp, 4, SEEK_SET);
+    fseek (ifp, 4 + get2(ifp), SEEK_SET);
+    if (fgetc(ifp) != 0xff)
+      parse_tiff(ifp, 12);
+  } else if (!memcmp (head, "BM", 2)) {
+    data_offset = 0x1000;
+    order = 0x4949;
+    fseek (ifp, 38, SEEK_SET);
+    if (get4(ifp) == 2834 && get4(ifp) == 2834) {
+      strcpy (model, "BMQ");
+      flip = 3;
+      goto nucore;
+    }
+  } else if (!memcmp (head, "BR", 2)) {
+    strcpy (model, "RAW");
+nucore:
+    strcpy (make, "Nucore");
+    order = 0x4949;
+    fseek (ifp, 10, SEEK_SET);
+    data_offset += get4(ifp);
+    get4(ifp);
+    raw_width  = get4(ifp);
+    raw_height = get4(ifp);
+    if (model[0] == 'B' && raw_width == 2597) {
+      raw_width++;
+      data_offset -= 0x1000;
+    }
+  } else if (!memcmp (head+25, "ARECOYK", 7)) {
+    strcpy (make, "Contax");
+    strcpy (model,"N Digital");
+    fseek (ifp, 60, SEEK_SET);
+    camera_red  = get4(ifp);
+    camera_red /= get4(ifp);
+    camera_blue = get4(ifp);
+    camera_blue = get4(ifp) / camera_blue;
+  } else if (!memcmp (head, "FUJIFILM", 8)) {
+    long data_offset_long;
+    fseek (ifp, 84, SEEK_SET);
+    parse_tiff(ifp, get4(ifp)+12);
+    fseek (ifp, 100, SEEK_SET);
+    pm_readbiglong(ifp, &data_offset_long);
+    data_offset = data_offset_long;
+  } else if (!memcmp (head, "DSC-Image", 9))
+    parse_rollei(ifp);
+  else if (!memcmp (head, "FOVb", 4))
+    parse_foveon(ifp);
+  else
+      for (i=0; i < sizeof table / sizeof *table; i++)
+          if (fsize == table[i].fsize) {
+              strcpy (make,  table[i].make );
+              strcpy (model, table[i].model);
+              if (table[i].withjpeg)
+                  parse_external_jpeg(inputFileName);
+          }
+  parse_mos(ifp, 8);
+  parse_mos(ifp, 3472);
+
+  for (i=0; i < sizeof corp / sizeof *corp; i++)
+    if (strstr (make, corp[i]))     /* Simplify company names */
+    strcpy (make, corp[i]);
+  if (!strncmp (make, "KODAK", 5))
+    make[16] = model[16] = 0;
+  c = make + strlen(make);      /* Remove trailing spaces */
+  while (*--c == ' ') *c = 0;
+  c = model + strlen(model);
+  while (*--c == ' ') *c = 0;
+  i = strlen(make);         /* Remove make from model */
+  if (!strncmp (model, make, i++))
+    memmove (model, model+i, 64-i);
+  make[63] = model[63] = model2[63] = 0;
+
+  if (make[0] == 0) {
+    pm_message ("unrecognized file format.");
+    return 1;
+  }
+
+/*  File format is OK.  Do we know this camera? */
+/*  Start with some useful defaults:           */
+
+  top_margin = left_margin = 0;
+  if ((raw_height | raw_width) < 0)
+       raw_height = raw_width  = 0;
+  height = raw_height;
+  width  = raw_width;
+  if (fuji_width) {
+    width = height + fuji_width;
+    height = width - 1;
+    ymag = 1;
+  }
+  load_raw = NULL;
+  if (is_dng) {
+    strcat (model, " DNG");
+    if (!filters)
+      colors = tiff_samples;
+    if (tiff_data_compression == 1)
+      load_raw = adobe_dng_load_raw_nc;
+    if (tiff_data_compression == 7)
+      load_raw = adobe_dng_load_raw_lj;
+    goto dng_skip;
+  }
+
+/*  We'll try to decode anything from Canon or Nikon. */
+
+  if (!filters) filters = 0x94949494;
+  if ((is_canon = !strcmp(make,"Canon")))
+    load_raw = memcmp (head+6,"HEAPCCDR",8) ?
+        lossless_jpeg_load_raw : canon_compressed_load_raw;
+  if (!strcmp(make,"NIKON"))
+    load_raw = nikon_is_compressed() ?
+    nikon_compressed_load_raw : nikon_load_raw;
+
+/* Set parameters based on camera name (for non-DNG files). */
+
+  if (is_foveon) {
+    if (!have64BitArithmetic)
+      pm_error("This program was built without 64 bit arithmetic "
+               "capability and the Foveon format requires it.");
+    if (height*2 < width) ymag = 2;
+    if (width < height) xmag = 2;
+    filters = 0;
+    load_raw = foveon_load_raw;
+    foveon_coeff(&use_coeff, coeff);
+  } else if (!strcmp(model,"PowerShot 600")) {
+    height = 613;
+    width  = 854;
+    colors = 4;
+    filters = 0xe1e4e1e4;
+    load_raw = canon_600_load_raw;
+  } else if (!strcmp(model,"PowerShot A5") ||
+         !strcmp(model,"PowerShot A5 Zoom")) {
+    height = 773;
+    width  = 960;
+    raw_width = 992;
+    colors = 4;
+    filters = 0x1e4e1e4e;
+    load_raw = canon_a5_load_raw;
+  } else if (!strcmp(model,"PowerShot A50")) {
+    height =  968;
+    width  = 1290;
+    raw_width = 1320;
+    colors = 4;
+    filters = 0x1b4e4b1e;
+    load_raw = canon_a5_load_raw;
+  } else if (!strcmp(model,"PowerShot Pro70")) {
+    height = 1024;
+    width  = 1552;
+    colors = 4;
+    filters = 0x1e4b4e1b;
+    load_raw = canon_a5_load_raw;
+    black = 34;
+  } else if (!strcmp(model,"PowerShot Pro90 IS")) {
+    width  = 1896;
+    colors = 4;
+    filters = 0xb4b4b4b4;
+  } else if (is_canon && raw_width == 2144) {
+    height = 1550;
+    width  = 2088;
+    top_margin  = 8;
+    left_margin = 4;
+    if (!strcmp(model,"PowerShot G1")) {
+      colors = 4;
+      filters = 0xb4b4b4b4;
+    }
+  } else if (is_canon && raw_width == 2224) {
+    height = 1448;
+    width  = 2176;
+    top_margin  = 6;
+    left_margin = 48;
+  } else if (is_canon && raw_width == 2376) {
+    height = 1720;
+    width  = 2312;
+    top_margin  = 6;
+    left_margin = 12;
+  } else if (is_canon && raw_width == 2672) {
+    height = 1960;
+    width  = 2616;
+    top_margin  = 6;
+    left_margin = 12;
+  } else if (is_canon && raw_width == 3152) {
+    height = 2056;
+    width  = 3088;
+    top_margin  = 12;
+    left_margin = 64;
+    maximum = 0xfa0;
+  } else if (is_canon && raw_width == 3160) {
+    height = 2328;
+    width  = 3112;
+    top_margin  = 12;
+    left_margin = 44;
+  } else if (is_canon && raw_width == 3344) {
+    height = 2472;
+    width  = 3288;
+    top_margin  = 6;
+    left_margin = 4;
+  } else if (!strcmp(model,"EOS D2000C")) {
+    filters = 0x61616161;
+    black = curve[200];
+  } else if (!strcmp(model,"EOS-1D")) {
+    raw_height = height = 1662;
+    raw_width  = width  = 2496;
+    data_offset = 288912;
+    filters = 0x61616161;
+  } else if (!strcmp(model,"EOS-1DS")) {
+    raw_height = height = 2718;
+    raw_width  = width  = 4082;
+    data_offset = 289168;
+    filters = 0x61616161;
+  } else if (is_canon && raw_width == 3516) {
+    top_margin  = 14;
+    left_margin = 42;
+    goto canon_cr2;
+  } else if (is_canon && raw_width == 3596) {
+    top_margin  = 12;
+    left_margin = 74;
+    goto canon_cr2;
+  } else if (is_canon && raw_width == 5108) {
+    top_margin  = 13;
+    left_margin = 98;
+    maximum = 0xe80;
+canon_cr2:
+    height = raw_height - top_margin;
+    width  = raw_width - left_margin;
+  } else if (!strcmp(model,"D1")) {
+    camera_red  *= 256/527.0;
+    camera_blue *= 256/317.0;
+  } else if (!strcmp(model,"D1X")) {
+    width  = 4024;
+    ymag = 2;
+  } else if (!strcmp(model,"D100")) {
+    if (tiff_data_compression == 34713 && load_raw == nikon_load_raw)
+      raw_width = (width += 3) + 3;
+  } else if (!strcmp(model,"D2H")) {
+    width  = 2482;
+    left_margin = 6;
+  } else if (!strcmp(model,"D2X")) {
+    width  = 4312;
+    pre_mul[0] = 1.514;
+    pre_mul[2] = 1.727;
+  } else if (fsize == 2465792) {
+    height = 1203;
+    width  = 1616;
+    filters = 0x4b4b4b4b;
+    colors = 4;
+    load_raw = nikon_e950_load_raw;
+    nikon_e950_coeff();
+    pre_mul[0] = 1.18193;
+    pre_mul[2] = 1.16452;
+    pre_mul[3] = 1.17250;
+  } else if (!strcmp(model,"E990")) {
+    if (!timestamp && !nikon_e990()) goto cp_e995;
+    height = 1540;
+    width  = 2064;
+    colors = 4;
+    filters = 0xb4b4b4b4;
+    nikon_e950_coeff();
+    pre_mul[0] = 1.196;
+    pre_mul[1] = 1.246;
+    pre_mul[2] = 1.018;
+  } else if (!strcmp(model,"E995")) {
+cp_e995:
+    strcpy (model, "E995");
+    height = 1540;
+    width  = 2064;
+    colors = 4;
+    filters = 0xe1e1e1e1;
+  } else if (!strcmp(model,"E2100")) {
+    if (!timestamp && !nikon_e2100()) goto cp_e2500;
+    width = 1616;
+    height = 1206;
+    load_raw = nikon_e2100_load_raw;
+    pre_mul[0] = 1.945;
+    pre_mul[2] = 1.040;
+  } else if (!strcmp(model,"E2500")) {
+cp_e2500:
+    strcpy (model, "E2500");
+    width = 1616;
+    height = 1204;
+    colors = 4;
+    filters = 0x4b4b4b4b;
+  } else if (!strcmp(model,"E3700")) {
+    if (!timestamp && pentax_optio33()) goto optio_33wr;
+    height = 1542;
+    width  = 2064;
+    load_raw = nikon_e2100_load_raw;
+    pre_mul[0] = 1.818;
+    pre_mul[2] = 1.618;
+  } else if (!strcmp(model,"Optio 33WR")) {
+optio_33wr:
+    strcpy (make, "PENTAX");
+    strcpy (model,"Optio 33WR");
+    height = 1542;
+    width  = 2064;
+    load_raw = nikon_e2100_load_raw;
+    flip = 1;
+    filters = 0x16161616;
+    pre_mul[0] = 1.331;
+    pre_mul[2] = 1.820;
+  } else if (!strcmp(model,"E4300")) {
+    if (!timestamp && minolta_z2()) goto dimage_z2;
+    height = 1710;
+    width  = 2288;
+    filters = 0x16161616;
+    pre_mul[0] = 508;
+    pre_mul[1] = 256;
+    pre_mul[2] = 322;
+  } else if (!strcmp(model,"DiMAGE Z2")) {
+dimage_z2:
+    strcpy (make, "MINOLTA");
+    strcpy (model,"DiMAGE Z2");
+    height = 1710;
+    width  = 2288;
+    filters = 0x16161616;
+    load_raw = nikon_e2100_load_raw;
+    pre_mul[0] = 508;
+    pre_mul[1] = 256;
+    pre_mul[2] = 450;
+  } else if (!strcmp(model,"E4500")) {
+    height = 1708;
+    width  = 2288;
+    colors = 4;
+    filters = 0xb4b4b4b4;
+  } else if (!strcmp(model,"R-D1")) {
+    tiff_data_compression = 34713;
+    load_raw = nikon_load_raw;
+  } else if (!strcmp(model,"FinePixS2Pro")) {
+    height = 3584;
+    width  = 3583;
+    fuji_width = 2144;
+    filters = 0x61616161;
+    load_raw = fuji_s2_load_raw;
+    black = 128;
+    strcpy (model+7, " S2Pro");
+  } else if (!strcmp(model,"FinePix S3Pro")) {
+    height = 3583;
+    width  = 3584;
+    fuji_width = 2144;
+    if (fsize > 18000000 && use_secondary)
+      data_offset += 4352*2*1444;
+    filters = 0x49494949;
+    load_raw = fuji_s3_load_raw;
+    maximum = 0xffff;
+  } else if (!strcmp(model,"FinePix S5000")) {
+    height = 2499;
+    width  = 2500;
+    fuji_width = 1423;
+    filters = 0x49494949;
+    load_raw = fuji_s5000_load_raw;
+    maximum = 0x3e00;
+  } else if (!strcmp(model,"FinePix S5100") ||
+         !strcmp(model,"FinePix S5500")) {
+    height = 1735;
+    width  = 2304;
+    data_offset += width*10;
+    filters = 0x49494949;
+    load_raw = unpacked_load_raw;
+    maximum = 0xffff;
+  } else if (!strcmp(model,"FinePix E550") ||
+         !strcmp(model,"FinePix F810") ||
+         !strcmp(model,"FinePix S7000")) {
+    height = 3587;
+    width  = 3588;
+    fuji_width = 2047;
+    filters = 0x49494949;
+    load_raw = fuji_s7000_load_raw;
+    maximum = 0x3e00;
+  } else if (!strcmp(model,"FinePix F700") ||
+         !strcmp(model,"FinePix S20Pro")) {
+    height = 2523;
+    width  = 2524;
+    fuji_width = 1440;
+    filters = 0x49494949;
+    load_raw = fuji_f700_load_raw;
+    maximum = 0x3e00;
+  } else if (!strcmp(model,"Digital Camera KD-400Z")) {
+    height = 1712;
+    width  = 2312;
+    raw_width = 2336;
+    data_offset = 4034;
+    fseek (ifp, 2032, SEEK_SET);
+    goto konica_400z;
+  } else if (!strcmp(model,"Digital Camera KD-510Z")) {
+    data_offset = 4032;
+    pre_mul[0] = 1.297;
+    pre_mul[2] = 1.438;
+    fseek (ifp, 2032, SEEK_SET);
+    goto konica_510z;
+  } else if (!strcasecmp(make,"MINOLTA")) {
+    load_raw = unpacked_load_raw;
+    maximum = 0xf7d;
+    if (!strncmp(model,"DiMAGE A",8)) {
+      if (!strcmp(model,"DiMAGE A200")) {
+    filters = 0x49494949;
+    tmp = camera_red;
+    camera_red  = 1 / camera_blue;
+    camera_blue = 1 / tmp;
+      }
+      load_raw = packed_12_load_raw;
+      maximum = model[8] == '1' ? 0xf8b : 0xfff;
+    } else if (!strncmp(model,"ALPHA",5) ||
+           !strncmp(model,"DYNAX",5) ||
+           !strncmp(model,"MAXXUM",6)) {
+      load_raw = packed_12_load_raw;
+      maximum = 0xffb;
+    } else if (!strncmp(model,"DiMAGE G",8)) {
+      if (model[8] == '4') {
+    data_offset = 5056;
+    pre_mul[0] = 1.602;
+    pre_mul[2] = 1.441;
+    fseek (ifp, 2078, SEEK_SET);
+    height = 1716;
+    width  = 2304;
+      } else if (model[8] == '5') {
+    data_offset = 4016;
+    fseek (ifp, 1936, SEEK_SET);
+konica_510z:
+    height = 1956;
+    width  = 2607;
+    raw_width = 2624;
+      } else if (model[8] == '6') {
+    data_offset = 4032;
+    fseek (ifp, 2030, SEEK_SET);
+    height = 2136;
+    width  = 2848;
+      }
+      filters = 0x61616161;
+konica_400z:
+      load_raw = unpacked_load_raw;
+      maximum = 0x3df;
+      order = 0x4d4d;
+      camera_red   = get2(ifp);
+      camera_blue  = get2(ifp);
+      camera_red  /= get2(ifp);
+      camera_blue /= get2(ifp);
+    }
+    if (pre_mul[0] == 1 && pre_mul[2] == 1) {
+      pre_mul[0] = 1.42;
+      pre_mul[2] = 1.25;
+    }
+  } else if (!strcmp(model,"*ist D")) {
+    load_raw = unpacked_load_raw;
+  } else if (!strcmp(model,"*ist DS")) {
+    height--;
+    load_raw = packed_12_load_raw;
+  } else if (!strcmp(model,"Optio S")) {
+    if (fsize == 3178560) {
+      height = 1540;
+      width  = 2064;
+      load_raw = eight_bit_load_raw;
+      camera_red  *= 4;
+      camera_blue *= 4;
+      pre_mul[0] = 1.391;
+      pre_mul[2] = 1.188;
+    } else {
+      height = 1544;
+      width  = 2068;
+      raw_width = 3136;
+      load_raw = packed_12_load_raw;
+      maximum = 0xf7c;
+      pre_mul[0] = 1.137;
+      pre_mul[2] = 1.453;
+    }
+  } else if (!strncmp(model,"Optio S4",8)) {
+    height = 1737;
+    width  = 2324;
+    raw_width = 3520;
+    load_raw = packed_12_load_raw;
+    maximum = 0xf7a;
+    pre_mul[0] = 1.980;
+    pre_mul[2] = 1.570;
+  } else if (!strcmp(model,"STV680 VGA")) {
+    height = 484;
+    width  = 644;
+    load_raw = eight_bit_load_raw;
+    flip = 2;
+    filters = 0x16161616;
+    black = 16;
+    pre_mul[0] = 1.097;
+    pre_mul[2] = 1.128;
+  } else if (!strcmp(make,"Phase One")) {
+    switch (raw_height) {
+      case 2060:
+    strcpy (model, "LightPhase");
+    height = 2048;
+    width  = 3080;
+    top_margin  = 5;
+    left_margin = 22;
+    pre_mul[0] = 1.331;
+    pre_mul[2] = 1.154;
+    break;
+      case 2682:
+    strcpy (model, "H10");
+    height = 2672;
+    width  = 4012;
+    top_margin  = 5;
+    left_margin = 26;
+    break;
+      case 4128:
+    strcpy (model, "H20");
+    height = 4098;
+    width  = 4098;
+    top_margin  = 20;
+    left_margin = 26;
+    pre_mul[0] = 1.963;
+    pre_mul[2] = 1.430;
+    break;
+      case 5488:
+    strcpy (model, "H25");
+    height = 5458;
+    width  = 4098;
+    top_margin  = 20;
+    left_margin = 26;
+    pre_mul[0] = 2.80;
+    pre_mul[2] = 1.20;
+    }
+    filters = top_margin & 1 ? 0x94949494 : 0x49494949;
+    load_raw = phase_one_load_raw;
+    maximum = 0xffff;
+  } else if (!strcmp(model,"Ixpress")) {
+    height = 4084;
+    width  = 4080;
+    filters = 0x49494949;
+    load_raw = ixpress_load_raw;
+    maximum = 0xffff;
+    pre_mul[0] = 1.963;
+    pre_mul[2] = 1.430;
+  } else if (!strcmp(make,"Sinar") && !memcmp(head,"8BPS",4)) {
+    fseek (ifp, 14, SEEK_SET);
+    height = get4(ifp);
+    width  = get4(ifp);
+    filters = 0x61616161;
+    data_offset = 68;
+    load_raw = unpacked_load_raw;
+    maximum = 0xffff;
+  } else if (!strcmp(make,"Leaf")) {
+    if (height > width)
+      filters = 0x16161616;
+    load_raw = unpacked_load_raw;
+    maximum = 0x3fff;
+    strcpy (model, "Valeo");
+    if (raw_width == 2060) {
+      filters = 0;
+      load_raw = leaf_load_raw;
+      maximum = 0xffff;
+      strcpy (model, "Volare");
+    }
+  } else if (!strcmp(model,"DIGILUX 2") || !strcmp(model,"DMC-LC1")) {
+    height = 1928;
+    width  = 2568;
+    data_offset = 1024;
+    load_raw = unpacked_load_raw;
+    maximum = 0xfff0;
+  } else if (!strcmp(model,"E-1")) {
+    filters = 0x61616161;
+    load_raw = unpacked_load_raw;
+    maximum = 0xfff0;
+    black = 1024;
+  } else if (!strcmp(model,"E-10")) {
+    load_raw = unpacked_load_raw;
+    maximum = 0xfff0;
+    black = 2048;
+  } else if (!strncmp(model,"E-20",4)) {
+    load_raw = unpacked_load_raw;
+    maximum = 0xffc0;
+    black = 2560;
+  } else if (!strcmp(model,"E-300")) {
+    width -= 21;
+    load_raw = olympus_e300_load_raw;
+    if (fsize > 15728640) {
+      load_raw = unpacked_load_raw;
+      maximum = 0xfc30;
+    } else
+      black = 62;
+  } else if (!strcmp(make,"OLYMPUS")) {
+    load_raw = olympus_cseries_load_raw;
+    if (!strcmp(model,"C5050Z") ||
+    !strcmp(model,"C8080WZ"))
+      filters = 0x16161616;
+  } else if (!strcmp(model,"N Digital")) {
+    height = 2047;
+    width  = 3072;
+    filters = 0x61616161;
+    data_offset = 0x1a00;
+    load_raw = packed_12_load_raw;
+    maximum = 0xf1e;
+  } else if (!strcmp(model,"DSC-F828")) {
+    width = 3288;
+    left_margin = 5;
+    load_raw = sony_load_raw;
+    filters = 0x9c9c9c9c;
+    colors = 4;
+    black = 491;
+  } else if (!strcmp(model,"DSC-V3")) {
+    width = 3109;
+    left_margin = 59;
+    load_raw = sony_load_raw;
+  } else if (!strcasecmp(make,"KODAK")) {
+    filters = 0x61616161;
+    if (!strcmp(model,"NC2000F")) {
+      width -= 4;
+      left_margin = 1;
+      for (i=176; i < 0x1000; i++)
+    curve[i] = curve[i-1];
+      pre_mul[0] = 1.509;
+      pre_mul[2] = 2.686;
+    } else if (!strcmp(model,"EOSDCS3B")) {
+      width -= 4;
+      left_margin = 2;
+    } else if (!strcmp(model,"EOSDCS1")) {
+      width -= 4;
+      left_margin = 2;
+    } else if (!strcmp(model,"DCS315C")) {
+      black = 8;
+    } else if (!strcmp(model,"DCS330C")) {
+      black = 8;
+    } else if (!strcmp(model,"DCS420")) {
+      width -= 4;
+      left_margin = 2;
+    } else if (!strcmp(model,"DCS460")) {
+      width -= 4;
+      left_margin = 2;
+    } else if (!strcmp(model,"DCS460A")) {
+      width -= 4;
+      left_margin = 2;
+      colors = 1;
+      filters = 0;
+    } else if (!strcmp(model,"DCS520C")) {
+      black = 180;
+    } else if (!strcmp(model,"DCS560C")) {
+      black = 188;
+    } else if (!strcmp(model,"DCS620C")) {
+      black = 180;
+    } else if (!strcmp(model,"DCS620X")) {
+      black = 185;
+    } else if (!strcmp(model,"DCS660C")) {
+      black = 214;
+    } else if (!strcmp(model,"DCS660M")) {
+      black = 214;
+      colors = 1;
+      filters = 0;
+    } else if (!strcmp(model,"DCS760M")) {
+      colors = 1;
+      filters = 0;
+    }
+    switch (tiff_data_compression) {
+    case 0:               /* No compression */
+    case 1:
+        load_raw = kodak_easy_load_raw;
+        break;
+    case 7:               /* Lossless JPEG */
+        load_raw = lossless_jpeg_load_raw;
+    case 32867:
+        break;
+    case 65000:           /* Kodak DCR compression */
+        if (!have64BitArithmetic)
+            pm_error("This program was built without 64 bit arithmetic "
+                     "capability, and Kodak DCR compression requires it.");
+        if (kodak_data_compression == 32803)
+            load_raw = kodak_compressed_load_raw;
+        else {
+            load_raw = kodak_yuv_load_raw;
+            height = (height+1) & -2;
+            width  = (width +1) & -2;
+            filters = 0;
+        }
+        break;
+    default:
+        pm_message ("%s %s uses unrecognized compression method %d.",
+                    make, model, tiff_data_compression);
+        return 1;
+    }
+    if (!strcmp(model,"DC20")) {
+      height = 242;
+      if (fsize < 100000) {
+    width = 249;
+    raw_width = 256;
+      } else {
+    width = 501;
+    raw_width = 512;
+      }
+      data_offset = raw_width + 1;
+      colors = 4;
+      filters = 0x8d8d8d8d;
+      kodak_dc20_coeff (1.0);
+      pre_mul[1] = 1.179;
+      pre_mul[2] = 1.209;
+      pre_mul[3] = 1.036;
+      load_raw = kodak_easy_load_raw;
+    } else if (strstr(model,"DC25")) {
+      strcpy (model, "DC25");
+      height = 242;
+      if (fsize < 100000) {
+    width = 249;
+    raw_width = 256;
+    data_offset = 15681;
+      } else {
+    width = 501;
+    raw_width = 512;
+    data_offset = 15937;
+      }
+      colors = 4;
+      filters = 0xb4b4b4b4;
+      load_raw = kodak_easy_load_raw;
+    } else if (!strcmp(model,"Digital Camera 40")) {
+      strcpy (model, "DC40");
+      height = 512;
+      width = 768;
+      data_offset = 1152;
+      load_raw = kodak_radc_load_raw;
+    } else if (strstr(model,"DC50")) {
+      strcpy (model, "DC50");
+      height = 512;
+      width = 768;
+      data_offset = 19712;
+      load_raw = kodak_radc_load_raw;
+    } else if (strstr(model,"DC120")) {
+      strcpy (model, "DC120");
+      height = 976;
+      width = 848;
+      if (tiff_data_compression == 7)
+    load_raw = kodak_jpeg_load_raw;
+      else
+    load_raw = kodak_dc120_load_raw;
+    }
+  } else if (!strcmp(make,"Rollei")) {
+    switch (raw_width) {
+      case 1316:
+    height = 1030;
+    width  = 1300;
+    top_margin  = 1;
+    left_margin = 6;
+    break;
+      case 2568:
+    height = 1960;
+    width  = 2560;
+    top_margin  = 2;
+    left_margin = 8;
+    }
+    filters = 0x16161616;
+    load_raw = rollei_load_raw;
+    pre_mul[0] = 1.8;
+    pre_mul[2] = 1.3;
+  } else if (!strcmp(model,"PC-CAM 600")) {
+    height = 768;
+    data_offset = width = 1024;
+    filters = 0x49494949;
+    load_raw = eight_bit_load_raw;
+    pre_mul[0] = 1.14;
+    pre_mul[2] = 2.73;
+  } else if (!strcmp(model,"QV-2000UX")) {
+    height = 1208;
+    width  = 1632;
+    data_offset = width * 2;
+    load_raw = eight_bit_load_raw;
+  } else if (!strcmp(model,"QV-3*00EX")) {
+    height = 1546;
+    width  = 2070;
+    raw_width = 2080;
+    load_raw = eight_bit_load_raw;
+  } else if (!strcmp(model,"QV-4000")) {
+    height = 1700;
+    width  = 2260;
+    load_raw = unpacked_load_raw;
+    maximum = 0xffff;
+  } else if (!strcmp(model,"QV-5700")) {
+    height = 1924;
+    width  = 2576;
+    load_raw = casio_qv5700_load_raw;
+  } else if (!strcmp(model,"QV-R51")) {
+    height = 1926;
+    width  = 2576;
+    raw_width = 3904;
+    load_raw = packed_12_load_raw;
+    pre_mul[0] = 1.340;
+    pre_mul[2] = 1.672;
+  } else if (!strcmp(model,"EX-Z55")) {
+    height = 1960;
+    width  = 2570;
+    raw_width = 3904;
+    load_raw = packed_12_load_raw;
+    pre_mul[0] = 1.520;
+    pre_mul[2] = 1.316;
+  } else if (!strcmp(model,"EX-P600")) {
+    height = 2142;
+    width  = 2844;
+    raw_width = 4288;
+    load_raw = packed_12_load_raw;
+    pre_mul[0] = 1.797;
+    pre_mul[2] = 1.219;
+  } else if (!strcmp(model,"EX-P700")) {
+    height = 2318;
+    width  = 3082;
+    raw_width = 4672;
+    load_raw = packed_12_load_raw;
+    pre_mul[0] = 1.758;
+    pre_mul[2] = 1.504;
+  } else if (!strcmp(make,"Nucore")) {
+    filters = 0x61616161;
+    load_raw = unpacked_load_raw;
+    if (width == 2598) {
+      filters = 0x16161616;
+      load_raw = nucore_load_raw;
+      flip = 2;
+    }
+  }
+  if (!use_coeff) adobe_coeff();
+dng_skip:
+  if (!load_raw || !height) {
+    pm_message ("This program cannot handle data from %s %s.",
+                make, model);
+    return 1;
+  }
+#ifdef NO_JPEG
+  if (load_raw == kodak_jpeg_load_raw) {
+    pm_message ("decoder was not linked with libjpeg.");
+    return 1;
+  }
+#endif
+  if (!raw_height) raw_height = height;
+  if (!raw_width ) raw_width  = width;
+  if (use_camera_rgb && colors == 3)
+      use_coeff = 0;
+  if (use_coeff)         /* Apply user-selected color balance */
+    for (i=0; i < colors; i++) {
+      coeff[0][i] *= red_scale;
+      coeff[2][i] *= blue_scale;
+    }
+  if (four_color_rgb && filters && colors == 3) {
+    for (i=0; i < 32; i+=4) {
+      if ((filters >> i & 15) == 9)
+    filters |= 2 << i;
+      if ((filters >> i & 15) == 6)
+    filters |= 8 << i;
+    }
+    colors++;
+    pre_mul[3] = pre_mul[1];
+    if (use_coeff)
+      for (i=0; i < 3; i++)
+    coeff[i][3] = coeff[i][1] /= 2;
+  }
+  fseek (ifp, data_offset, SEEK_SET);
+
+  *loadRawFnP = load_raw;
+
+  return 0;
+}
diff --git a/converter/other/cameratopam/identify.h b/converter/other/cameratopam/identify.h
new file mode 100644
index 00000000..012b807c
--- /dev/null
+++ b/converter/other/cameratopam/identify.h
@@ -0,0 +1,11 @@
+typedef void (*loadRawFn)();
+
+int
+identify(FILE *       const ifp,
+         bool         const use_secondary,
+         bool         const use_camera_rgb,
+         float        const red_scale,
+         float        const blue_scale,
+         unsigned int const four_color_rgb,
+         const char * const inputFileName,
+         loadRawFn *  const loadRawFnP);
diff --git a/converter/other/cameratopam/ljpeg.c b/converter/other/cameratopam/ljpeg.c
new file mode 100644
index 00000000..18423f4f
--- /dev/null
+++ b/converter/other/cameratopam/ljpeg.c
@@ -0,0 +1,141 @@
+#define _BSD_SOURCE    /* Make sure string.h containst strcasecmp() */
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+
+#include "pm.h"
+
+#include "global_variables.h"
+#include "util.h"
+#include "decode.h"
+#include "bayer.h"
+
+#include "ljpeg.h"
+
+
+/*
+   Not a full implementation of Lossless JPEG, just
+   enough to decode Canon, Kodak and Adobe DNG images.
+ */
+
+int  
+ljpeg_start (FILE * ifp, struct jhead *jh)
+{
+  int i, tag, len;
+  unsigned char data[256], *dp;
+
+  init_decoder();
+  for (i=0; i < 4; i++)
+    jh->huff[i] = free_decode;
+  fread (data, 2, 1, ifp);
+  if (data[0] != 0xff || data[1] != 0xd8) return 0;
+  do {
+    fread (data, 2, 2, ifp);
+    tag =  data[0] << 8 | data[1];
+    len = (data[2] << 8 | data[3]) - 2;
+    if (tag <= 0xff00 || len > 255) return 0;
+    fread (data, 1, len, ifp);
+    switch (tag) {
+      case 0xffc3:
+    jh->bits = data[0];
+    jh->high = data[1] << 8 | data[2];
+    jh->wide = data[3] << 8 | data[4];
+    jh->clrs = data[5];
+    break;
+      case 0xffc4:
+    for (dp = data; dp < data+len && *dp < 4; ) {
+      jh->huff[*dp] = free_decode;
+      dp = make_decoder (++dp, 0);
+    }
+    }
+  } while (tag != 0xffda);
+  jh->row = calloc (jh->wide*jh->clrs, 2);
+  if (jh->row == NULL)
+      pm_error("Out of memory in ljpeg_start()");
+  for (i=0; i < 4; i++)
+    jh->vpred[i] = 1 << (jh->bits-1);
+  zero_after_ff = 1;
+  getbits(ifp, -1);
+  return 1;
+}
+
+int 
+ljpeg_diff (struct decode *dindex)
+{
+  int len, diff;
+
+  while (dindex->branch[0])
+    dindex = dindex->branch[getbits(ifp, 1)];
+  diff = getbits(ifp, len = dindex->leaf);
+  if ((diff & (1 << (len-1))) == 0)
+    diff -= (1 << len) - 1;
+  return diff;
+}
+
+void
+ljpeg_row (struct jhead *jh)
+{
+  int col, c, diff;
+  unsigned short *outp=jh->row;
+
+  for (col=0; col < jh->wide; col++)
+    for (c=0; c < jh->clrs; c++) {
+      diff = ljpeg_diff (jh->huff[c]);
+      *outp = col ? outp[-jh->clrs]+diff : (jh->vpred[c] += diff);
+      outp++;
+    }
+}
+
+void  
+lossless_jpeg_load_raw(void)
+{
+  int jwide, jrow, jcol, val, jidx, i, row, col;
+  struct jhead jh;
+  int min=INT_MAX;
+
+  if (!ljpeg_start (ifp, &jh)) return;
+  jwide = jh.wide * jh.clrs;
+
+  for (jrow=0; jrow < jh.high; jrow++) {
+    ljpeg_row (&jh);
+    for (jcol=0; jcol < jwide; jcol++) {
+      val = curve[jh.row[jcol]];
+      jidx = jrow*jwide + jcol;
+      if (raw_width == 5108) {
+    i = jidx / (1680*jh.high);
+    if (i < 2) {
+      row = jidx / 1680 % jh.high;
+      col = jidx % 1680 + i*1680;
+    } else {
+      jidx -= 2*1680*jh.high;
+      row = jidx / 1748;
+      col = jidx % 1748 + 2*1680;
+    }
+      } else if (raw_width == 3516) {
+    row = jidx / 1758;
+    col = jidx % 1758;
+    if (row >= raw_height) {
+      row -= raw_height;
+      col += 1758;
+    }
+      } else {
+    row = jidx / raw_width;
+    col = jidx % raw_width;
+      }
+      if ((unsigned) (row-top_margin) >= height) continue;
+      if ((unsigned) (col-left_margin) < width) {
+    BAYER(row-top_margin,col-left_margin) = val;
+    if (min > val) min = val;
+      } else
+    black += val;
+    }
+  }
+  free (jh.row);
+  if (raw_width > width)
+    black /= (raw_width - width) * height;
+  if (!strcasecmp(make,"KODAK"))
+    black = min;
+}
+
+
diff --git a/converter/other/cameratopam/ljpeg.h b/converter/other/cameratopam/ljpeg.h
new file mode 100644
index 00000000..60832a3d
--- /dev/null
+++ b/converter/other/cameratopam/ljpeg.h
@@ -0,0 +1,17 @@
+struct jhead {
+  int bits, high, wide, clrs, vpred[4];
+  struct decode *huff[4];
+  unsigned short *row;
+};
+
+void  
+lossless_jpeg_load_raw(void);
+
+int  
+ljpeg_start (FILE * ifp, struct jhead *jh);
+
+int 
+ljpeg_diff (struct decode *dindex);
+
+void
+ljpeg_row (struct jhead *jh);
diff --git a/converter/other/cameratopam/util.c b/converter/other/cameratopam/util.c
new file mode 100644
index 00000000..b3ccf7e9
--- /dev/null
+++ b/converter/other/cameratopam/util.c
@@ -0,0 +1,83 @@
+#define _XOPEN_SOURCE   /* Make sure unistd.h contains swab() */
+#include <unistd.h>
+#include <stdio.h>
+
+#include "pm.h"
+#include "global_variables.h"
+#include "util.h"
+
+#ifndef LONG_BITS
+#define LONG_BITS (8 * sizeof(long))
+#endif
+/*
+   Get a 2-byte integer, making no assumptions about CPU byte order.
+   Nor should we assume that the compiler evaluates left-to-right.
+ */
+unsigned short
+get2(FILE * const ifp)
+{
+    unsigned char a, b;
+
+    a = fgetc(ifp);  b = fgetc(ifp);
+
+    if (order == 0x4949)      /* "II" means little-endian */
+        return a | b << 8;
+    else              /* "MM" means big-endian */
+        return a << 8 | b;
+}
+
+/*
+   Same for a 4-byte integer.
+ */
+int
+get4(FILE * const ifp)
+{
+    unsigned char a, b, c, d;
+
+    a = fgetc(ifp);  b = fgetc(ifp);
+    c = fgetc(ifp);  d = fgetc(ifp);
+
+    if (order == 0x4949)
+        return a | b << 8 | c << 16 | d << 24;
+    else
+        return a << 24 | b << 16 | c << 8 | d;
+}
+
+/*
+   Faster than calling get2() multiple times.
+ */
+void
+read_shorts (FILE * const ifp, unsigned short *pixel, int count)
+{
+    fread (pixel, 2, count, ifp);
+    if ((order == 0x4949) == (BYTE_ORDER == BIG_ENDIAN))
+        swab (pixel, pixel, count*2);
+}
+
+/*
+   getbits(-1) initializes the buffer
+   getbits(n) where 0 <= n <= 25 returns an n-bit integer
+ */
+unsigned 
+getbits (FILE * const ifp, int nbits)
+{
+    static unsigned long bitbuf=0;
+    static int vbits=0;
+    unsigned c, ret;
+
+    if (nbits == 0) return 0;
+    if (nbits == -1)
+        ret = bitbuf = vbits = 0;
+    else {
+        ret = bitbuf << (LONG_BITS - vbits) >> (LONG_BITS - nbits);
+        vbits -= nbits;
+    }
+    while (vbits < LONG_BITS - 7) {
+        c = fgetc(ifp);
+        bitbuf = (bitbuf << 8) + c;
+        if (c == 0xff && zero_after_ff)
+            fgetc(ifp);
+        vbits += 8;
+    }
+    return ret;
+}
diff --git a/converter/other/cameratopam/util.h b/converter/other/cameratopam/util.h
new file mode 100644
index 00000000..bf9006d3
--- /dev/null
+++ b/converter/other/cameratopam/util.h
@@ -0,0 +1,16 @@
+#ifndef UTIL_H_INCLUDED
+#define UTIL_H_INCLUDED
+
+unsigned short
+get2(FILE * const ifp);
+
+int
+get4(FILE * const ifp);
+
+void
+read_shorts (FILE * const ifp, unsigned short *pixel, int count);
+
+unsigned int
+getbits (FILE * const ifp, int nbits);
+
+#endif
diff --git a/converter/other/dithers.h b/converter/other/dithers.h
new file mode 100644
index 00000000..1ced833d
--- /dev/null
+++ b/converter/other/dithers.h
@@ -0,0 +1,91 @@
+#ifndef DITHERS_H_INCLUDED
+#define DITHERS_H_INCLUDED
+
+/*
+** dithers.h
+**
+** Here are some dithering matrices.  They are all taken from "Digital
+** Halftoning" by Robert Ulichney, MIT Press, ISBN 0-262-21009-6.
+*/
+
+
+#if 0
+/*
+** Order-6 ordered dithering matrix.  Note that smaller ordered dithers
+** have no advantage over larger ones, so use dither8 instead.
+*/
+static int dither6[8][8] = {
+  {  1, 59, 15, 55,  2, 56, 12, 52 },
+  { 33, 17, 47, 31, 34, 18, 44, 28 },
+  {  9, 49,  5, 63, 10, 50,  6, 60 },
+  { 41, 25, 37, 21, 42, 26, 38, 22 },
+  {  3, 57, 13, 53,  0, 58, 14, 54 },
+  { 35, 19, 45, 29, 32, 16, 46, 30 },
+  { 11, 51,  7, 61,  8, 48,  4, 62 },
+  { 43, 27, 39, 23, 40, 24, 36, 20 }
+  };
+#endif
+
+/* Order-8 ordered dithering matrix. */
+static int dither8[16][16] = {
+  {   1,235, 59,219, 15,231, 55,215,  2,232, 56,216, 12,228, 52,212},
+  { 129, 65,187,123,143, 79,183,119,130, 66,184,120,140, 76,180,116},
+  {  33,193, 17,251, 47,207, 31,247, 34,194, 18,248, 44,204, 28,244},
+  { 161, 97,145, 81,175,111,159, 95,162, 98,146, 82,172,108,156, 92},
+  {   9,225, 49,209,  5,239, 63,223, 10,226, 50,210,  6,236, 60,220},
+  { 137, 73,177,113,133, 69,191,127,138, 74,178,114,134, 70,188,124},
+  {  41,201, 25,241, 37,197, 21,255, 42,202, 26,242, 38,198, 22,252},
+  { 169,105,153, 89,165,101,149, 85,170,106,154, 90,166,102,150, 86},
+  {   3,233, 57,217, 13,229, 53,213,  0,234, 58,218, 14,230, 54,214},
+  { 131, 67,185,121,141, 77,181,117,128, 64,186,122,142, 78,182,118},
+  {  35,195, 19,249, 45,205, 29,245, 32,192, 16,250, 46,206, 30,246},
+  { 163, 99,147, 83,173,109,157, 93,160, 96,144, 80,174,110,158, 94},
+  {  11,227, 51,211,  7,237, 61,221,  8,224, 48,208,  4,238, 62,222},
+  { 139, 75,179,115,135, 71,189,125,136, 72,176,112,132, 68,190,126},
+  {  43,203, 27,243, 39,199, 23,253, 40,200, 24,240, 36,196, 20,254},
+  { 171,107,155, 91,167,103,151, 87,168,104,152, 88,164,100,148, 84} 
+};
+
+/* Order-3 clustered dithering matrix. */
+static int cluster3[6][6] = {
+  {  9,11,10, 8, 6, 7},
+  { 12,17,16, 5, 0, 1},
+  { 13,14,15, 4, 3, 2},
+  {  8, 6, 7, 9,11,10},
+  {  5, 0, 1,12,17,16},
+  {  4, 3, 2,13,14,15}
+};
+
+/* Order-4 clustered dithering matrix. */
+static int cluster4[8][8] = {
+  { 18,20,19,16,13,11,12,15},
+  { 27,28,29,22, 4, 3, 2, 9},
+  { 26,31,30,21, 5, 0, 1,10},
+  { 23,25,24,17, 8, 6, 7,14},
+  { 13,11,12,15,18,20,19,16},
+  {  4, 3, 2, 9,27,28,29,22},
+  {  5, 0, 1,10,26,31,30,21},
+  {  8, 6, 7,14,23,25,24,17}
+};
+
+/* Order-8 clustered dithering matrix. */
+static int cluster8[16][16] = {
+   { 64, 69, 77, 87, 86, 76, 68, 67, 63, 58, 50, 40, 41, 51, 59, 60},
+   { 70, 94,100,109,108, 99, 93, 75, 57, 33, 27, 18, 19, 28, 34, 52},
+   { 78,101,114,116,115,112, 98, 83, 49, 26, 13, 11, 12, 15, 29, 44},
+   { 88,110,123,124,125,118,107, 85, 39, 17,  4,  3,  2,  9, 20, 42},
+   { 89,111,122,127,126,117,106, 84, 38, 16,  5,  0,  1, 10, 21, 43},
+   { 79,102,119,121,120,113, 97, 82, 48, 25,  8,  6,  7, 14, 30, 45},
+   { 71, 95,103,104,105, 96, 92, 74, 56, 32, 24, 23, 22, 31, 35, 53},
+   { 65, 72, 80, 90, 91, 81, 73, 66, 62, 55, 47, 37, 36, 46, 54, 61},
+   { 63, 58, 50, 40, 41, 51, 59, 60, 64, 69, 77, 87, 86, 76, 68, 67},
+   { 57, 33, 27, 18, 19, 28, 34, 52, 70, 94,100,109,108, 99, 93, 75},
+   { 49, 26, 13, 11, 12, 15, 29, 44, 78,101,114,116,115,112, 98, 83},
+   { 39, 17,  4,  3,  2,  9, 20, 42, 88,110,123,124,125,118,107, 85},
+   { 38, 16,  5,  0,  1, 10, 21, 43, 89,111,122,127,126,117,106, 84},
+   { 48, 25,  8,  6,  7, 14, 30, 45, 79,102,119,121,120,113, 97, 82},
+   { 56, 32, 24, 23, 22, 31, 35, 53, 71, 95,103,104,105, 96, 92, 74},
+   { 62, 55, 47, 37, 36, 46, 54, 61, 65, 72, 80, 90, 91, 81, 73, 66}
+};
+
+#endif
diff --git a/converter/other/exif.c b/converter/other/exif.c
new file mode 100644
index 00000000..19f108a7
--- /dev/null
+++ b/converter/other/exif.c
@@ -0,0 +1,1030 @@
+/*--------------------------------------------------------------------------
+  This file contains subroutines for use by Jpegtopnm to handle the
+  EXIF header.
+
+  The code is adapted from the program Jhead by Matthaias Wandel
+  December 1999 - August 2000, and contributed to the public domain.
+  Bryan Henderson adapted it to Netpbm in September 2001.  Bryan
+  added more of Wandel's code, from Jhead 1.9 dated December 2002 in
+  January 2003.
+
+  An EXIF header is a JFIF APP1 marker.  It is generated by some
+  digital cameras and contains information about the circumstances of
+  the creation of the image (camera settings, etc.).
+
+  The EXIF header uses the TIFF format, only it contains only tag
+  values and no actual image.
+
+  Note that the image format called EXIF is simply JFIF with an EXIF
+  header, i.e. a subformat of JFIF.
+
+  See the EXIF specs at http://exif.org (2001.09.01).
+
+--------------------------------------------------------------------------*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <limits.h>
+#include <ctype.h>
+
+#ifdef _WIN32
+    #include <sys/utime.h>
+#else
+    #include <utime.h>
+    #include <sys/types.h>
+    #include <unistd.h>
+    #include <errno.h>
+#endif
+
+#include "pm_c_util.h"
+#include "pm.h"
+#include "nstring.h"
+
+#include "exif.h"
+
+static unsigned char * LastExifRefd;
+static unsigned char * DirWithThumbnailPtrs;
+static double FocalplaneXRes;
+bool HaveXRes;
+static double FocalplaneUnits;
+static int ExifImageWidth;
+static int MotorolaOrder = 0;
+
+typedef struct {
+    unsigned short Tag;
+    const char * Desc;
+}TagTable_t;
+
+
+/* Describes format descriptor */
+static int BytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8};
+#define NUM_FORMATS 12
+
+#define FMT_BYTE       1 
+#define FMT_STRING     2
+#define FMT_USHORT     3
+#define FMT_ULONG      4
+#define FMT_URATIONAL  5
+#define FMT_SBYTE      6
+#define FMT_UNDEFINED  7
+#define FMT_SSHORT     8
+#define FMT_SLONG      9
+#define FMT_SRATIONAL 10
+#define FMT_SINGLE    11
+#define FMT_DOUBLE    12
+
+/* Describes tag values */
+
+#define TAG_EXIF_OFFSET       0x8769
+#define TAG_INTEROP_OFFSET    0xa005
+
+#define TAG_MAKE              0x010F
+#define TAG_MODEL             0x0110
+
+#define TAG_ORIENTATION       0x0112
+
+#define TAG_EXPOSURETIME      0x829A
+#define TAG_FNUMBER           0x829D
+
+#define TAG_SHUTTERSPEED      0x9201
+#define TAG_APERTURE          0x9202
+#define TAG_MAXAPERTURE       0x9205
+#define TAG_FOCALLENGTH       0x920A
+
+#define TAG_DATETIME_ORIGINAL 0x9003
+#define TAG_USERCOMMENT       0x9286
+
+#define TAG_SUBJECT_DISTANCE  0x9206
+#define TAG_FLASH             0x9209
+
+#define TAG_FOCALPLANEXRES    0xa20E
+#define TAG_FOCALPLANEUNITS   0xa210
+#define TAG_EXIF_IMAGEWIDTH   0xA002
+#define TAG_EXIF_IMAGELENGTH  0xA003
+
+/* the following is added 05-jan-2001 vcs */
+#define TAG_EXPOSURE_BIAS     0x9204
+#define TAG_WHITEBALANCE      0x9208
+#define TAG_METERING_MODE     0x9207
+#define TAG_EXPOSURE_PROGRAM  0x8822
+#define TAG_ISO_EQUIVALENT    0x8827
+#define TAG_COMPRESSION_LEVEL 0x9102
+
+#define TAG_THUMBNAIL_OFFSET  0x0201
+#define TAG_THUMBNAIL_LENGTH  0x0202
+
+static TagTable_t const TagTable[] = {
+  {   0x100,   "ImageWidth"},
+  {   0x101,   "ImageLength"},
+  {   0x102,   "BitsPerSample"},
+  {   0x103,   "Compression"},
+  {   0x106,   "PhotometricInterpretation"},
+  {   0x10A,   "FillOrder"},
+  {   0x10D,   "DocumentName"},
+  {   0x10E,   "ImageDescription"},
+  {   0x10F,   "Make"},
+  {   0x110,   "Model"},
+  {   0x111,   "StripOffsets"},
+  {   0x112,   "Orientation"},
+  {   0x115,   "SamplesPerPixel"},
+  {   0x116,   "RowsPerStrip"},
+  {   0x117,   "StripByteCounts"},
+  {   0x11A,   "XResolution"},
+  {   0x11B,   "YResolution"},
+  {   0x11C,   "PlanarConfiguration"},
+  {   0x128,   "ResolutionUnit"},
+  {   0x12D,   "TransferFunction"},
+  {   0x131,   "Software"},
+  {   0x132,   "DateTime"},
+  {   0x13B,   "Artist"},
+  {   0x13E,   "WhitePoint"},
+  {   0x13F,   "PrimaryChromaticities"},
+  {   0x156,   "TransferRange"},
+  {   0x200,   "JPEGProc"},
+  {   0x201,   "ThumbnailOffset"},
+  {   0x202,   "ThumbnailLength"},
+  {   0x211,   "YCbCrCoefficients"},
+  {   0x212,   "YCbCrSubSampling"},
+  {   0x213,   "YCbCrPositioning"},
+  {   0x214,   "ReferenceBlackWhite"},
+  {   0x828D,  "CFARepeatPatternDim"},
+  {   0x828E,  "CFAPattern"},
+  {   0x828F,  "BatteryLevel"},
+  {   0x8298,  "Copyright"},
+  {   0x829A,  "ExposureTime"},
+  {   0x829D,  "FNumber"},
+  {   0x83BB,  "IPTC/NAA"},
+  {   0x8769,  "ExifOffset"},
+  {   0x8773,  "InterColorProfile"},
+  {   0x8822,  "ExposureProgram"},
+  {   0x8824,  "SpectralSensitivity"},
+  {   0x8825,  "GPSInfo"},
+  {   0x8827,  "ISOSpeedRatings"},
+  {   0x8828,  "OECF"},
+  {   0x9000,  "ExifVersion"},
+  {   0x9003,  "DateTimeOriginal"},
+  {   0x9004,  "DateTimeDigitized"},
+  {   0x9101,  "ComponentsConfiguration"},
+  {   0x9102,  "CompressedBitsPerPixel"},
+  {   0x9201,  "ShutterSpeedValue"},
+  {   0x9202,  "ApertureValue"},
+  {   0x9203,  "BrightnessValue"},
+  {   0x9204,  "ExposureBiasValue"},
+  {   0x9205,  "MaxApertureValue"},
+  {   0x9206,  "SubjectDistance"},
+  {   0x9207,  "MeteringMode"},
+  {   0x9208,  "LightSource"},
+  {   0x9209,  "Flash"},
+  {   0x920A,  "FocalLength"},
+  {   0x927C,  "MakerNote"},
+  {   0x9286,  "UserComment"},
+  {   0x9290,  "SubSecTime"},
+  {   0x9291,  "SubSecTimeOriginal"},
+  {   0x9292,  "SubSecTimeDigitized"},
+  {   0xA000,  "FlashPixVersion"},
+  {   0xA001,  "ColorSpace"},
+  {   0xA002,  "ExifImageWidth"},
+  {   0xA003,  "ExifImageLength"},
+  {   0xA005,  "InteroperabilityOffset"},
+  {   0xA20B,  "FlashEnergy"},                 /* 0x920B in TIFF/EP */
+  {   0xA20C,  "SpatialFrequencyResponse"},  /* 0x920C    -  - */
+  {   0xA20E,  "FocalPlaneXResolution"},     /* 0x920E    -  - */
+  {   0xA20F,  "FocalPlaneYResolution"},      /* 0x920F    -  - */
+  {   0xA210,  "FocalPlaneResolutionUnit"},  /* 0x9210    -  - */
+  {   0xA214,  "SubjectLocation"},             /* 0x9214    -  - */
+  {   0xA215,  "ExposureIndex"},            /* 0x9215    -  - */
+  {   0xA217,  "SensingMethod"},            /* 0x9217    -  - */
+  {   0xA300,  "FileSource"},
+  {   0xA301,  "SceneType"},
+  {      0, NULL}
+} ;
+
+
+
+/*--------------------------------------------------------------------------
+   Convert a 16 bit unsigned value from file's native byte order
+--------------------------------------------------------------------------*/
+static int Get16u(void * Short)
+{
+    if (MotorolaOrder){
+        return (((unsigned char *)Short)[0] << 8) | 
+            ((unsigned char *)Short)[1];
+    }else{
+        return (((unsigned char *)Short)[1] << 8) | 
+            ((unsigned char *)Short)[0];
+    }
+}
+
+/*--------------------------------------------------------------------------
+   Convert a 32 bit signed value from file's native byte order
+--------------------------------------------------------------------------*/
+static int Get32s(void * Long)
+{
+    if (MotorolaOrder){
+        return  
+            ((( char *)Long)[0] << 24) | (((unsigned char *)Long)[1] << 16) |
+            (((unsigned char *)Long)[2] << 8 ) | 
+            (((unsigned char *)Long)[3] << 0 );
+    }else{
+        return  
+            ((( char *)Long)[3] << 24) | (((unsigned char *)Long)[2] << 16) |
+            (((unsigned char *)Long)[1] << 8 ) | 
+            (((unsigned char *)Long)[0] << 0 );
+    }
+}
+
+/*--------------------------------------------------------------------------
+   Convert a 32 bit unsigned value from file's native byte order
+--------------------------------------------------------------------------*/
+static unsigned Get32u(void * Long)
+{
+    return (unsigned)Get32s(Long) & 0xffffffff;
+}
+
+/*--------------------------------------------------------------------------
+   Display a number as one of its many formats
+--------------------------------------------------------------------------*/
+static void PrintFormatNumber(FILE * const file, 
+                              void * const ValuePtr, 
+                              int const Format, int const ByteCount)
+{
+    switch(Format){
+    case FMT_SBYTE:
+    case FMT_BYTE:      printf("%02x\n",*(unsigned char *)ValuePtr); break;
+    case FMT_USHORT:    fprintf(file, "%d\n",Get16u(ValuePtr));    break;
+    case FMT_ULONG:     
+    case FMT_SLONG:     fprintf(file, "%d\n",Get32s(ValuePtr));    break;
+    case FMT_SSHORT:    
+        fprintf(file, "%hd\n",(signed short)Get16u(ValuePtr));     break;
+    case FMT_URATIONAL:
+    case FMT_SRATIONAL: 
+        fprintf(file, "%d/%d\n",Get32s(ValuePtr), Get32s(4+(char *)ValuePtr));
+        break;
+    case FMT_SINGLE:    
+        fprintf(file, "%f\n",(double)*(float *)ValuePtr);          break;
+    case FMT_DOUBLE:    fprintf(file, "%f\n",*(double *)ValuePtr); break;
+    default: 
+        fprintf(file, "Unknown format %d:", Format);
+        {
+            int a;
+            for (a=0; a < ByteCount && a < 16; ++a)
+                printf("%02x", ((unsigned char *)ValuePtr)[a]);
+        }
+        fprintf(file, "\n");
+    }
+}
+
+
+/*--------------------------------------------------------------------------
+   Evaluate number, be it int, rational, or float from directory.
+--------------------------------------------------------------------------*/
+static double ConvertAnyFormat(void * ValuePtr, int Format)
+{
+    double Value;
+    Value = 0;
+
+    switch(Format){
+        case FMT_SBYTE:     Value = *(signed char *)ValuePtr;  break;
+        case FMT_BYTE:      Value = *(unsigned char *)ValuePtr;        break;
+
+        case FMT_USHORT:    Value = Get16u(ValuePtr);          break;
+        case FMT_ULONG:     Value = Get32u(ValuePtr);          break;
+
+        case FMT_URATIONAL:
+        case FMT_SRATIONAL: 
+            {
+                int Num,Den;
+                Num = Get32s(ValuePtr);
+                Den = Get32s(4+(char *)ValuePtr);
+                if (Den == 0){
+                    Value = 0;
+                }else{
+                    Value = (double)Num/Den;
+                }
+                break;
+            }
+
+        case FMT_SSHORT:    Value = (signed short)Get16u(ValuePtr);  break;
+        case FMT_SLONG:     Value = Get32s(ValuePtr);                break;
+
+        /* Not sure if this is correct (never seen float used in Exif format)
+         */
+        case FMT_SINGLE:    Value = (double)*(float *)ValuePtr;      break;
+        case FMT_DOUBLE:    Value = *(double *)ValuePtr;             break;
+    }
+    return Value;
+}
+
+/*--------------------------------------------------------------------------
+   Process one of the nested EXIF directories.
+--------------------------------------------------------------------------*/
+static void 
+ProcessExifDir(unsigned char *  const ExifData, 
+               unsigned int     const ExifLength,
+               unsigned int     const DirOffset,
+               ImageInfo_t *    const ImageInfoP, 
+               int              const ShowTags,
+               unsigned char ** const LastExifRefdP) {
+
+    unsigned char * const DirStart = ExifData + DirOffset;
+    int de;
+    int a;
+    int NumDirEntries;
+    unsigned ThumbnailOffset = 0;
+    unsigned ThumbnailSize = 0;
+
+    NumDirEntries = Get16u(DirStart);
+    #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry))
+
+    {
+        unsigned char * DirEnd;
+        DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries);
+        if (DirEnd+4 > (ExifData+ExifLength)){
+            if (DirEnd+2 == ExifData+ExifLength || 
+                DirEnd == ExifData+ExifLength){
+                /* Version 1.3 of jhead would truncate a bit too much.
+                   This also caught later on as well.
+                */
+            }else{
+                /* Note: Files that had thumbnails trimmed with jhead
+                   1.3 or earlier might trigger this.
+                */
+                pm_message("Illegal directory entry size");
+                return;
+            }
+        }
+        if (DirEnd > LastExifRefd) LastExifRefd = DirEnd;
+    }
+
+    if (ShowTags){
+        pm_message("Directory with %d entries",NumDirEntries);
+    }
+
+    for (de=0;de<NumDirEntries;de++){
+        int Tag, Format, Components;
+        unsigned char * ValuePtr;
+            /* This actually can point to a variety of things; it must
+               be cast to other types when used.  But we use it as a
+               byte-by-byte cursor, so we declare it as a pointer to a
+               generic byte here.  
+            */
+        int ByteCount;
+        unsigned char * DirEntry;
+        DirEntry = DIR_ENTRY_ADDR(DirStart, de);
+
+        Tag = Get16u(DirEntry);
+        Format = Get16u(DirEntry+2);
+        Components = Get32u(DirEntry+4);
+
+        if ((Format-1) >= NUM_FORMATS) {
+            /* (-1) catches illegal zero case as unsigned underflows
+               to positive large.  
+            */
+            pm_message("Illegal number format %d for tag %04x", Format, Tag);
+            continue;
+        }
+        
+        ByteCount = Components * BytesPerFormat[Format];
+
+        if (ByteCount > 4){
+            unsigned OffsetVal;
+            OffsetVal = Get32u(DirEntry+8);
+            /* If its bigger than 4 bytes, the dir entry contains an offset.*/
+            if (OffsetVal+ByteCount > ExifLength){
+                /* Bogus pointer offset and / or bytecount value */
+                pm_message("Illegal pointer offset value in EXIF "
+                           "for tag %04x.  "
+                           "Offset %d bytes %d ExifLen %d\n",
+                           Tag, OffsetVal, ByteCount, ExifLength);
+                continue;
+            }
+            ValuePtr = ExifData+OffsetVal;
+        }else{
+            /* 4 bytes or less and value is in the dir entry itself */
+            ValuePtr = DirEntry+8;
+        }
+
+        if (*LastExifRefdP < ValuePtr+ByteCount){
+            /* Keep track of last byte in the exif header that was
+               actually referenced.  That way, we know where the
+               discardable thumbnail data begins.
+            */
+            *LastExifRefdP = ValuePtr+ByteCount;
+        }
+
+        if (ShowTags){
+            /* Show tag name */
+            for (a=0;;a++){
+                if (TagTable[a].Tag == 0){
+                    fprintf(stderr, "  Unknown Tag %04x Value = ", Tag);
+                    break;
+                }
+                if (TagTable[a].Tag == Tag){
+                    fprintf(stderr, "    %s = ",TagTable[a].Desc);
+                    break;
+                }
+            }
+
+            /* Show tag value. */
+            switch(Format){
+
+                case FMT_UNDEFINED:
+                    /* Undefined is typically an ascii string. */
+
+                case FMT_STRING:
+                    /* String arrays printed without function call
+                       (different from int arrays)
+                    */
+                    {
+                        int NoPrint = 0;
+                        printf("\"");
+                        for (a=0;a<ByteCount;a++){
+                            if (ISPRINT((ValuePtr)[a])){
+                                fprintf(stderr, "%c", (ValuePtr)[a]);
+                                NoPrint = 0;
+                            }else{
+
+                                /* Avoiding indicating too many
+                                   unprintable characters of proprietary
+                                   bits of binary information this
+                                   program may not know how to parse.  
+                                */
+                                if (!NoPrint){
+                                    fprintf(stderr, "?");
+                                    NoPrint = 1;
+                                }
+                            }
+                        }
+                        fprintf(stderr, "\"\n");
+                    }
+                    break;
+
+                default:
+                    /* Handle arrays of numbers later (will there ever be?)*/
+                    PrintFormatNumber(stderr, ValuePtr, Format, ByteCount);
+            }
+        }
+
+        /* Extract useful components of tag */
+        switch(Tag){
+
+            case TAG_MAKE:
+                strncpy(ImageInfoP->CameraMake, (char*)ValuePtr, 31);
+                break;
+
+            case TAG_MODEL:
+                strncpy(ImageInfoP->CameraModel, (char*)ValuePtr, 39);
+                break;
+
+            case TAG_DATETIME_ORIGINAL:
+                strncpy(ImageInfoP->DateTime, (char*)ValuePtr, 19);
+                ImageInfoP->DatePointer = (char*)ValuePtr;
+                break;
+
+            case TAG_USERCOMMENT:
+                /* Olympus has this padded with trailing spaces.
+                   Remove these first. 
+                */
+                for (a=ByteCount;;){
+                    a--;
+                    if (((char*)ValuePtr)[a] == ' '){
+                        ((char*)ValuePtr)[a] = '\0';
+                    }else{
+                        break;
+                    }
+                    if (a == 0) break;
+                }
+
+                /* Copy the comment */
+                if (memcmp(ValuePtr, "ASCII",5) == 0){
+                    for (a=5;a<10;a++){
+                        char c;
+                        c = ((char*)ValuePtr)[a];
+                        if (c != '\0' && c != ' '){
+                            strncpy(ImageInfoP->Comments, (char*)ValuePtr+a, 
+                                    199);
+                            break;
+                        }
+                    }
+                    
+                }else{
+                    strncpy(ImageInfoP->Comments, (char*)ValuePtr, 199);
+                }
+                break;
+
+            case TAG_FNUMBER:
+                /* Simplest way of expressing aperture, so I trust it the most.
+                   (overwrite previously computd value if there is one)
+                   */
+                ImageInfoP->ApertureFNumber = 
+                    (float)ConvertAnyFormat(ValuePtr, Format);
+                break;
+
+            case TAG_APERTURE:
+            case TAG_MAXAPERTURE:
+                /* More relevant info always comes earlier, so only
+                 use this field if we don't have appropriate aperture
+                 information yet. 
+                */
+                if (ImageInfoP->ApertureFNumber == 0){
+                    ImageInfoP->ApertureFNumber = (float)
+                        exp(ConvertAnyFormat(ValuePtr, Format)*log(2)*0.5);
+                }
+                break;
+
+            case TAG_FOCALLENGTH:
+                /* Nice digital cameras actually save the focal length
+                   as a function of how farthey are zoomed in. 
+                */
+
+                ImageInfoP->FocalLength = 
+                    (float)ConvertAnyFormat(ValuePtr, Format);
+                break;
+
+            case TAG_SUBJECT_DISTANCE:
+                /* Inidcates the distacne the autofocus camera is focused to.
+                   Tends to be less accurate as distance increases.
+                */
+                ImageInfoP->Distance = 
+                    (float)ConvertAnyFormat(ValuePtr, Format);
+                break;
+
+            case TAG_EXPOSURETIME:
+                /* Simplest way of expressing exposure time, so I
+                   trust it most.  (overwrite previously computd value
+                   if there is one) 
+                */
+                ImageInfoP->ExposureTime = 
+                    (float)ConvertAnyFormat(ValuePtr, Format);
+                break;
+
+            case TAG_SHUTTERSPEED:
+                /* More complicated way of expressing exposure time,
+                   so only use this value if we don't already have it
+                   from somewhere else.  
+                */
+                if (ImageInfoP->ExposureTime == 0){
+                    ImageInfoP->ExposureTime = (float)
+                        (1/exp(ConvertAnyFormat(ValuePtr, Format)*log(2)));
+                }
+                break;
+
+            case TAG_FLASH:
+                if ((int)ConvertAnyFormat(ValuePtr, Format) & 7){
+                    ImageInfoP->FlashUsed = TRUE;
+                }else{
+                    ImageInfoP->FlashUsed = FALSE;
+                }
+                break;
+
+            case TAG_ORIENTATION:
+                ImageInfoP->Orientation = 
+                    (int)ConvertAnyFormat(ValuePtr, Format);
+                if (ImageInfoP->Orientation < 1 || 
+                    ImageInfoP->Orientation > 8){
+                    pm_message("Undefined rotation value %d", 
+                               ImageInfoP->Orientation);
+                    ImageInfoP->Orientation = 0;
+                }
+                break;
+
+            case TAG_EXIF_IMAGELENGTH:
+            case TAG_EXIF_IMAGEWIDTH:
+                /* Use largest of height and width to deal with images
+                   that have been rotated to portrait format.  
+                */
+                a = (int)ConvertAnyFormat(ValuePtr, Format);
+                if (ExifImageWidth < a) ExifImageWidth = a;
+                break;
+
+            case TAG_FOCALPLANEXRES:
+                HaveXRes = TRUE;
+                FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format);
+                break;
+
+            case TAG_FOCALPLANEUNITS:
+                switch((int)ConvertAnyFormat(ValuePtr, Format)){
+                    case 1: FocalplaneUnits = 25.4; break; /* 1 inch */
+                    case 2: 
+                        /* According to the information I was using, 2
+                           means meters.  But looking at the Cannon
+                           powershot's files, inches is the only
+                           sensible value.  
+                        */
+                        FocalplaneUnits = 25.4;
+                        break;
+
+                    case 3: FocalplaneUnits = 10;   break;  /* 1 centimeter*/
+                    case 4: FocalplaneUnits = 1;    break;  /* 1 millimeter*/
+                    case 5: FocalplaneUnits = .001; break;  /* 1 micrometer*/
+                }
+                break;
+
+                /* Remaining cases contributed by: Volker C. Schoech
+                   (schoech@gmx.de)
+                */
+
+            case TAG_EXPOSURE_BIAS:
+                ImageInfoP->ExposureBias = 
+                    (float) ConvertAnyFormat(ValuePtr, Format);
+                break;
+
+            case TAG_WHITEBALANCE:
+                ImageInfoP->Whitebalance = 
+                    (int)ConvertAnyFormat(ValuePtr, Format);
+                break;
+
+            case TAG_METERING_MODE:
+                ImageInfoP->MeteringMode = 
+                    (int)ConvertAnyFormat(ValuePtr, Format);
+                break;
+
+            case TAG_EXPOSURE_PROGRAM:
+                ImageInfoP->ExposureProgram = 
+                    (int)ConvertAnyFormat(ValuePtr, Format);
+                break;
+
+            case TAG_ISO_EQUIVALENT:
+                ImageInfoP->ISOequivalent = 
+                    (int)ConvertAnyFormat(ValuePtr, Format);
+                if ( ImageInfoP->ISOequivalent < 50 ) 
+                    ImageInfoP->ISOequivalent *= 200;
+                break;
+
+            case TAG_COMPRESSION_LEVEL:
+                ImageInfoP->CompressionLevel = 
+                    (int)ConvertAnyFormat(ValuePtr, Format);
+                break;
+
+            case TAG_THUMBNAIL_OFFSET:
+                ThumbnailOffset = (unsigned)ConvertAnyFormat(ValuePtr, Format);
+                DirWithThumbnailPtrs = DirStart;
+                break;
+
+            case TAG_THUMBNAIL_LENGTH:
+                ThumbnailSize = (unsigned)ConvertAnyFormat(ValuePtr, Format);
+                break;
+
+            case TAG_EXIF_OFFSET:
+            case TAG_INTEROP_OFFSET:
+                {
+                    unsigned int const SubdirOffset  = Get32u(ValuePtr);
+                    if (SubdirOffset >= ExifLength)
+                        pm_message("Illegal exif or interop offset "
+                                   "directory link.  Offset is %u, "
+                                   "but Exif data is only %u bytes.",
+                                   SubdirOffset, ExifLength);
+                    else
+                        ProcessExifDir(ExifData, ExifLength, SubdirOffset, 
+                                       ImageInfoP, ShowTags, LastExifRefdP);
+                    continue;
+                }
+        }
+
+    }
+
+
+    {
+        /* In addition to linking to subdirectories via exif tags,
+           there's also a potential link to another directory at the end
+           of each directory.  This has got to be the result of a
+           committee!  
+        */
+        if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <= 
+            ExifData+ExifLength){
+            unsigned int const SubdirOffset =
+                Get32u(DirStart+2+12*NumDirEntries);
+            if (SubdirOffset){
+                unsigned char * const SubdirStart = ExifData + SubdirOffset;
+                if (SubdirStart > ExifData+ExifLength){
+                    if (SubdirStart < ExifData+ExifLength+20){
+                        /* Jhead 1.3 or earlier would crop the whole directory!
+                           As Jhead produces this form of format incorrectness,
+                           I'll just let it pass silently.
+                        */
+                        if (ShowTags) 
+                            printf("Thumbnail removed with "
+                                   "Jhead 1.3 or earlier\n");
+                    }else{
+                        pm_message("Illegal subdirectory link");
+                    }
+                }else{
+                    if (SubdirOffset <= ExifLength)
+                        ProcessExifDir(ExifData, ExifLength, SubdirOffset,
+                                       ImageInfoP, ShowTags, LastExifRefdP);
+                }
+            }
+        }else{
+            /* The exif header ends before the last next directory pointer. */
+        }
+    }
+
+    if (ThumbnailSize && ThumbnailOffset){
+        if (ThumbnailSize + ThumbnailOffset <= ExifLength){
+            /* The thumbnail pointer appears to be valid.  Store it. */
+            ImageInfoP->ThumbnailPointer = ExifData + ThumbnailOffset;
+            ImageInfoP->ThumbnailSize = ThumbnailSize;
+
+            if (ShowTags){
+                fprintf(stderr, "Thumbnail size: %d bytes\n",ThumbnailSize);
+            }
+        }
+    }
+}
+
+
+
+void 
+process_EXIF(unsigned char * const ExifData,
+             unsigned int    const length,
+             ImageInfo_t *   const ImageInfoP, 
+             int             const ShowTags,
+             const char **   const errorP) {
+/*--------------------------------------------------------------------------
+  Interpret an EXIF APP1 marker
+
+  'ExifData' is the actual Exif data; it does not include the
+  "Exif" identifier and length field that often prefix Exif data.
+
+  'length' is the length of the Exif section.
+--------------------------------------------------------------------------*/
+    int FirstOffset;
+    unsigned char * LastExifRefd;
+
+    *errorP = NULL;  /* initial assumption */
+
+    if (ShowTags){
+        fprintf(stderr, "Exif header %d bytes long\n",length);
+    }
+
+    if (memcmp(ExifData+0,"II",2) == 0) {
+        if (ShowTags) 
+            fprintf(stderr, "Exif header in Intel order\n");
+        MotorolaOrder = 0;
+    } else {
+        if (memcmp(ExifData+0, "MM", 2) == 0) {
+            if (ShowTags) 
+                fprintf(stderr, "Exif header in Motorola order\n");
+            MotorolaOrder = 1;
+        } else {
+            asprintfN(errorP, "Invalid alignment marker in Exif "
+                      "data.  First two bytes are '%c%c' (0x%02x%02x) "
+                      "instead of 'II' or 'MM'.", 
+                      ExifData[0], ExifData[1], ExifData[0], ExifData[1]);
+        }
+    }
+    if (!*errorP) {
+        unsigned short const start = Get16u(ExifData + 2);
+        /* Check the next value for correctness. */
+        if (start != 0x002a){
+            asprintfN(errorP, "Invalid Exif header start.  "
+                      "two bytes after the alignment marker "
+                      "should be 0x002a, but is 0x%04x",
+                      start);
+        }
+    }
+    if (!*errorP) {
+        FirstOffset = Get32u(ExifData + 4);
+        if (FirstOffset < 8 || FirstOffset > 16){
+            /* I used to ensure this was set to 8 (website I used
+               indicated its 8) but PENTAX Optio 230 has it set
+               differently, and uses it as offset. (Sept 11 2002)
+                */
+            pm_message("Suspicious offset of first IFD value in Exif header");
+        }
+        
+        ImageInfoP->Comments[0] = '\0';  /* Initial value - null string */
+        
+        HaveXRes = FALSE;  /* Initial assumption */
+        FocalplaneUnits = 0;
+        ExifImageWidth = 0;
+        
+        LastExifRefd = ExifData;
+        DirWithThumbnailPtrs = NULL;
+        
+        ProcessExifDir(ExifData, length, FirstOffset, 
+                       ImageInfoP, ShowTags, &LastExifRefd);
+        
+        /* Compute the CCD width, in millimeters. */
+        if (HaveXRes){
+            ImageInfoP->HaveCCDWidth = 1;
+            ImageInfoP->CCDWidth = 
+                    (float)(ExifImageWidth * FocalplaneUnits / FocalplaneXRes);
+        } else
+            ImageInfoP->HaveCCDWidth = 0;
+            
+        if (ShowTags){
+            fprintf(stderr, 
+                    "Non-settings part of Exif header: %d bytes\n",
+                    ExifData+length-LastExifRefd);
+        }
+    }
+}
+
+/*--------------------------------------------------------------------------
+   Show the collected image info, displaying camera F-stop and shutter
+   speed in a consistent and legible fashion.
+--------------------------------------------------------------------------*/
+void 
+ShowImageInfo(ImageInfo_t * const ImageInfoP)
+{
+    if (ImageInfoP->CameraMake[0]){
+        fprintf(stderr, "Camera make  : %s\n",ImageInfoP->CameraMake);
+        fprintf(stderr, "Camera model : %s\n",ImageInfoP->CameraModel);
+    }
+    if (ImageInfoP->DateTime[0]){
+        fprintf(stderr, "Date/Time    : %s\n",ImageInfoP->DateTime);
+    }
+    fprintf(stderr, "Resolution   : %d x %d\n",
+            ImageInfoP->Width, ImageInfoP->Height);
+    if (ImageInfoP->Orientation > 1){
+
+        /* Only print orientation if one was supplied, and if its not
+           1 (normal orientation)
+
+           1 - The 0th row is at the visual top of the image
+               and the 0th column is the visual left-hand side.
+           2 - The 0th row is at the visual top of the image
+               and the 0th column is the visual right-hand side.
+           3 - The 0th row is at the visual bottom of the image
+               and the 0th column is the visual right-hand side.
+           4 - The 0th row is at the visual bottom of the image
+               and the 0th column is the visual left-hand side.
+           5 - The 0th row is the visual left-hand side of of the image
+               and the 0th column is the visual top.
+           6 - The 0th row is the visual right-hand side of of the image
+               and the 0th column is the visual top.
+           7 - The 0th row is the visual right-hand side of of the image
+               and the 0th column is the visual bottom.
+           8 - The 0th row is the visual left-hand side of of the image
+               and the 0th column is the visual bottom.
+
+           Note: The descriptions here are the same as the name of the
+           command line option to pass to jpegtran to right the image
+        */
+        static const char * OrientTab[9] = {
+            "Undefined",
+            "Normal",           /* 1 */
+            "flip horizontal",  /* left right reversed mirror */
+            "rotate 180",       /* 3 */
+            "flip vertical",    /* upside down mirror */
+            "transpose",    /* Flipped about top-left <--> bottom-right axis.*/
+            "rotate 90",        /* rotate 90 cw to right it. */
+            "transverse",   /* flipped about top-right <--> bottom-left axis */
+            "rotate 270",       /* rotate 270 to right it. */
+        };
+
+        fprintf(stderr, "Orientation  : %s\n", 
+                OrientTab[ImageInfoP->Orientation]);
+    }
+
+    if (ImageInfoP->IsColor == 0){
+        fprintf(stderr, "Color/bw     : Black and white\n");
+    }
+    if (ImageInfoP->FlashUsed >= 0){
+        fprintf(stderr, "Flash used   : %s\n",
+                ImageInfoP->FlashUsed ? "Yes" :"No");
+    }
+    if (ImageInfoP->FocalLength){
+        fprintf(stderr, "Focal length : %4.1fmm",
+                (double)ImageInfoP->FocalLength);
+        if (ImageInfoP->HaveCCDWidth){
+            fprintf(stderr, "  (35mm equivalent: %dmm)",
+                    (int)
+                    (ImageInfoP->FocalLength/ImageInfoP->CCDWidth*36 + 0.5));
+        }
+        fprintf(stderr, "\n");
+    }
+
+    if (ImageInfoP->HaveCCDWidth){
+        fprintf(stderr, "CCD width    : %2.4fmm\n",
+                (double)ImageInfoP->CCDWidth);
+    }
+
+    if (ImageInfoP->ExposureTime){ 
+        if (ImageInfoP->ExposureTime < 0.010){
+            fprintf(stderr, 
+                    "Exposure time: %6.4f s ",
+                    (double)ImageInfoP->ExposureTime);
+        }else{
+            fprintf(stderr, 
+                    "Exposure time: %5.3f s ",
+                    (double)ImageInfoP->ExposureTime);
+        }
+        if (ImageInfoP->ExposureTime <= 0.5){
+            fprintf(stderr, " (1/%d)",(int)(0.5 + 1/ImageInfoP->ExposureTime));
+        }
+        fprintf(stderr, "\n");
+    }
+    if (ImageInfoP->ApertureFNumber){
+        fprintf(stderr, "Aperture     : f/%3.1f\n",
+                (double)ImageInfoP->ApertureFNumber);
+    }
+    if (ImageInfoP->Distance){
+        if (ImageInfoP->Distance < 0){
+            fprintf(stderr, "Focus dist.  : Infinite\n");
+        }else{
+            fprintf(stderr, "Focus dist.  :%5.2fm\n",
+                    (double)ImageInfoP->Distance);
+        }
+    }
+
+
+
+
+
+    if (ImageInfoP->ISOequivalent){ /* 05-jan-2001 vcs */
+        fprintf(stderr, "ISO equiv.   : %2d\n",(int)ImageInfoP->ISOequivalent);
+    }
+    if (ImageInfoP->ExposureBias){ /* 05-jan-2001 vcs */
+        fprintf(stderr, "Exposure bias:%4.2f\n",
+                (double)ImageInfoP->ExposureBias);
+    }
+        
+    if (ImageInfoP->Whitebalance){ /* 05-jan-2001 vcs */
+        switch(ImageInfoP->Whitebalance) {
+        case 1:
+            fprintf(stderr, "Whitebalance : sunny\n");
+            break;
+        case 2:
+            fprintf(stderr, "Whitebalance : fluorescent\n");
+            break;
+        case 3:
+            fprintf(stderr, "Whitebalance : incandescent\n");
+            break;
+        default:
+            fprintf(stderr, "Whitebalance : cloudy\n");
+        }
+    }
+    if (ImageInfoP->MeteringMode){ /* 05-jan-2001 vcs */
+        switch(ImageInfoP->MeteringMode) {
+        case 2:
+            fprintf(stderr, "Metering Mode: center weight\n");
+            break;
+        case 3:
+            fprintf(stderr, "Metering Mode: spot\n");
+            break;
+        case 5:
+            fprintf(stderr, "Metering Mode: matrix\n");
+            break;
+        }
+    }
+    if (ImageInfoP->ExposureProgram){ /* 05-jan-2001 vcs */
+        switch(ImageInfoP->ExposureProgram) {
+        case 2:
+            fprintf(stderr, "Exposure     : program (auto)\n");
+            break;
+        case 3:
+            fprintf(stderr, "Exposure     : aperture priority (semi-auto)\n");
+            break;
+        case 4:
+            fprintf(stderr, "Exposure     : shutter priority (semi-auto)\n");
+            break;
+        }
+    }
+    if (ImageInfoP->CompressionLevel){ /* 05-jan-2001 vcs */
+        switch(ImageInfoP->CompressionLevel) {
+        case 1:
+            fprintf(stderr, "Jpeg Quality  : basic\n");
+            break;
+        case 2:
+            fprintf(stderr, "Jpeg Quality  : normal\n");
+            break;
+        case 4:
+            fprintf(stderr, "Jpeg Quality  : fine\n");
+            break;
+       }
+    }
+
+         
+
+    /* Print the comment. Print 'Comment:' for each new line of comment. */
+    if (ImageInfoP->Comments[0]){
+        int a,c;
+        fprintf(stderr, "Comment      : ");
+        for (a=0;a<MAX_COMMENT;a++){
+            c = ImageInfoP->Comments[a];
+            if (c == '\0') break;
+            if (c == '\n'){
+                /* Do not start a new line if the string ends with a cr */
+                if (ImageInfoP->Comments[a+1] != '\0'){
+                    fprintf(stderr, "\nComment      : ");
+                }else{
+                    fprintf(stderr, "\n");
+                }
+            }else{
+                putc(c, stderr);
+            }
+        }
+        fprintf(stderr, "\n");
+    }
+
+    fprintf(stderr, "\n");
+}
+
+
+
+
diff --git a/converter/other/exif.h b/converter/other/exif.h
new file mode 100644
index 00000000..e5825e12
--- /dev/null
+++ b/converter/other/exif.h
@@ -0,0 +1,56 @@
+#ifndef EXIF_H_INCLUDED
+#define EXIF_H_INCLUDED
+
+#define MAX_COMMENT 2000
+
+#ifdef _WIN32
+    #define PATH_MAX _MAX_PATH
+#endif
+
+/*--------------------------------------------------------------------------
+  This structure stores Exif header image elements in a simple manner
+  Used to store camera data as extracted from the various ways that it can be
+  stored in an exif header
+--------------------------------------------------------------------------*/
+typedef struct {
+    char  CameraMake   [32];
+    char  CameraModel  [40];
+    char  DateTime     [20];
+    int   Height, Width;
+    int   Orientation;
+    int   IsColor;
+    int   FlashUsed;
+    float FocalLength;
+    float ExposureTime;
+    float ApertureFNumber;
+    float Distance;
+    int   HaveCCDWidth;  /* boolean */
+    float CCDWidth;
+    float ExposureBias;
+    int   Whitebalance;
+    int   MeteringMode;
+    int   ExposureProgram;
+    int   ISOequivalent;
+    int   CompressionLevel;
+    char  Comments[MAX_COMMENT];
+
+    unsigned char * ThumbnailPointer;  /* Pointer at the thumbnail */
+    unsigned ThumbnailSize;     /* Size of thumbnail. */
+
+    char * DatePointer;
+}ImageInfo_t;
+
+
+/* Prototypes for exif.c functions. */
+
+void 
+process_EXIF(unsigned char * const ExifSection, 
+             unsigned int    const length,
+             ImageInfo_t *   const ImageInfoP, 
+             int             const ShowTags,
+             const char **   const errorP);
+
+void 
+ShowImageInfo(ImageInfo_t * const ImageInfoP);
+
+#endif
diff --git a/converter/other/fiasco/Makefile b/converter/other/fiasco/Makefile
new file mode 100644
index 00000000..0dd945ed
--- /dev/null
+++ b/converter/other/fiasco/Makefile
@@ -0,0 +1,59 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/other/fiasco
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+INCLUDES = \
+	-I$(SRCDIR)/$(SUBDIR)/codec -I$(SRCDIR)/$(SUBDIR)/input \
+	-I$(SRCDIR)/$(SUBDIR)/output -I$(SRCDIR)/$(SUBDIR)/lib \
+
+BINARIES = pnmtofiasco fiascotopnm
+
+MERGEBINARIES = $(BINARIES)
+
+SCRIPTS =
+
+all: $(BINARIES)
+
+FIASCOLIBS = codec/libfiasco_codec.a \
+	     input/libfiasco_input.a \
+	     output/libfiasco_output.a \
+	     lib/libfiasco_lib.a 
+
+COMMON_OBJECTS = binerror.o getopt.o getopt1.o params.o
+
+OBJECTS = $(BINARIES:%=%.o) $(COMMON_OBJECTS)
+
+MERGE_OBJECTS = $(BINARIES:%=%.o2) $(COMMON_OBJECTS)  $(FIASCOLIBS)
+
+SUBDIRS = codec input output lib
+
+include $(SRCDIR)/Makefile.common
+
+$(BINARIES):%:%.o $(COMMON_OBJECTS) $(FIASCOLIBS) $(NETPBMLIB) \
+   $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $< $(COMMON_OBJECTS) \
+	$(shell $(LIBOPT) $(FIASCOLIBS) $(NETPBMLIB)) $(MATHLIB) $(LDLIBS) \
+	$(RPATH) $(LADD)
+
+codec/libfiasco_codec.a: $(BUILDDIR)/$(SUBDIR)/codec FORCE
+	$(MAKE) -C codec -f $(SRCDIR)/$(SUBDIR)/codec/Makefile \
+		libfiasco_codec.a SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR)
+
+input/libfiasco_input.a: $(BUILDDIR)/$(SUBDIR)/input FORCE
+	$(MAKE) -C input -f $(SRCDIR)/$(SUBDIR)/input/Makefile \
+		libfiasco_input.a SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR)
+
+output/libfiasco_output.a: $(BUILDDIR)/$(SUBDIR)/output FORCE
+	$(MAKE) -C output -f $(SRCDIR)/$(SUBDIR)/output/Makefile \
+		libfiasco_output.a SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR)
+
+lib/libfiasco_lib.a: $(BUILDDIR)/$(SUBDIR)/lib FORCE
+	$(MAKE) -C lib -f $(SRCDIR)/$(SUBDIR)/lib/Makefile \
+		libfiasco_lib.a SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR)
+
+
diff --git a/converter/other/fiasco/README.netpbm b/converter/other/fiasco/README.netpbm
new file mode 100644
index 00000000..4f4019ad
--- /dev/null
+++ b/converter/other/fiasco/README.netpbm
@@ -0,0 +1,41 @@
+The 'fiasco' subdirectory of the Netpbm source tree is primarily a copy
+of the source distribution of the Fiasco package by Ullrich Hafner.
+Bryan Henderson adapted fiasco-1.0 on July 6, 2000 to netpbm, and then
+merged in all the updates from fiasco-1.3 on February 9, 2001.
+
+The changes are:
+
+- Uses Netpbm libraries for input and output of Netpbm format images.
+
+- Works with maxvals other than 255 in Netpbm input images.  This change
+  also makes a minor correction to the maxval 255 case.  Where the Fiasco
+  package multiplies by 16 to convert from 8 bit to 12 bit intensity,
+  the correct factor is 4095/255.
+
+- Does not issue warning when system configuration file not found.
+  The location of that file is a compile-time option in 'fiasco', but
+  fixed at /etc in Netpbm.  The expectation is that Netpbm users will
+  never have a system configuration file.
+
+- Does not fail if basis file small.fco is not found.  The Fiasco code
+  already contained facilities for defaulting to a built-in version of
+  small.fco, but it was disabled by an early check for existence of
+  the basis file as an actual file.  In Netpbm, that check for
+  existence is simply removed.
+
+- Remove WINDOWS config.h configuration macro, which determined whether
+  files would be open with "b" flag (binary).  Use "b" flag unconditionally.
+
+- Rename internal "log2" function to "Log2" to avoid conflict with existing
+  "log2" macro or function.  The original package has conditional compilation
+  to allow it to use the existing log2 when configured for a system that has
+  it.  In Netpbm, we always use the private version.
+
+- Compilation warnings fixed.
+
+- 'bin' subdirectory moved to top level directory.
+
+- man pages for programs moved from doc subdirectory to top level directory.
+
+- man page of pnmpsnr created.
+
diff --git a/converter/other/fiasco/binerror.c b/converter/other/fiasco/binerror.c
new file mode 100644
index 00000000..8a41a214
--- /dev/null
+++ b/converter/other/fiasco/binerror.c
@@ -0,0 +1,143 @@
+/*
+ *  error.c:		Error handling
+ *
+ *  Written by:		Stefan Frank
+ *			Ullrich Hafner
+ *  
+ *  Credits:	Modelled after variable argument routines from Jef
+ *		Poskanzer's pbmplus package. 
+ *
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/03/20 21:29:59 $
+ *  $Author: hafner $
+ *  $Revision: 4.3 $
+ *  $State: Exp $
+ */
+
+#define _BSD_SOURCE 1   /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+#define _ERROR_C
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#if STDC_HEADERS
+#	include <stdarg.h>
+#	define VA_START(args, lastarg) va_start(args, lastarg)
+#else  /* not STDC_HEADERS */
+#	include <varargs.h>
+#	define VA_START(args, lastarg) va_start(args)
+#endif /* not STDC_HEADERS */
+#include <string.h>
+
+#if HAVE_SETJMP_H
+#	include <setjmp.h>
+#endif /* HAVE_SETJMP_H */
+
+#include "fiasco.h"
+#include "binerror.h"
+
+/*****************************************************************************
+
+			     global variables
+  
+*****************************************************************************/
+
+int   error_line = 0;
+const char *error_file = NULL;
+
+/*****************************************************************************
+
+			     local variables
+  
+*****************************************************************************/
+
+static const char *executable = "(name not initialized)";
+
+/*****************************************************************************
+
+			       public code
+  
+*****************************************************************************/
+
+void
+init_error_handling (const char *name)
+/*
+ *  Initialize filename of executable.
+ *
+ *  No return value.
+ */
+{
+   if (name)
+      executable = strdup (name);
+}
+
+void
+_error (const char *format, ...)
+/*
+ *  Print error message and exit.
+ *
+ *  No return value.
+ */
+{
+   va_list	args;
+
+   VA_START (args, format);
+
+   fprintf (stderr, "%s: %s: line %d:\nError: ",
+	    executable, error_file, error_line);
+#if HAVE_VPRINTF
+   vfprintf (stderr, format, args);
+#elif HAVE_DOPRNT
+   _doprnt (format, args, stderr);
+#endif /* HAVE_DOPRNT */
+   fputc ('\n', stderr);
+   va_end(args);
+
+   exit (1);
+}
+
+void
+_file_error (const char *filename)
+/*
+ *  Print file error message and exit.
+ *
+ *  No return value.
+ */
+{
+   fprintf (stderr, "%s: %s: line %d:\nError: ",
+	    executable, error_file, error_line);
+   perror (filename);
+
+   exit (2);
+}
+
+void 
+_warning (const char *format, ...)
+/*
+ *  Issue a warning and continue execution.
+ *
+ *  No return value.
+ */
+{
+   va_list args;
+
+   VA_START (args, format);
+
+   fprintf (stderr, "%s: %s: line %d:\nWarning: ",
+	    executable, error_file, error_line);
+#if HAVE_VPRINTF
+   vfprintf (stderr, format, args);
+#elif HAVE_DOPRNT
+   _doprnt (format, args, stderr);
+#endif /* HAVE_DOPRNT */
+   fputc ('\n', stderr);
+
+   va_end (args);
+}
diff --git a/converter/other/fiasco/binerror.h b/converter/other/fiasco/binerror.h
new file mode 100644
index 00000000..e7ff43c9
--- /dev/null
+++ b/converter/other/fiasco/binerror.h
@@ -0,0 +1,50 @@
+/*
+ *  error.h
+ *  
+ *  Written by:		Stefan Frank
+ *			Ullrich Hafner
+ *
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/03/20 21:29:59 $
+ *  $Author: hafner $
+ *  $Revision: 4.3 $
+ *  $State: Exp $
+ */
+
+#ifndef _ERROR_H
+#define _ERROR_H
+
+#define error          error_line=__LINE__,error_file=__FILE__,_error
+#define warning        error_line=__LINE__,error_file=__FILE__,_warning
+#define file_error(fn) error_line=__LINE__,error_file=__FILE__,_file_error(fn)
+
+#ifdef _ERROR_C
+#define _EXTERN_TYPE
+#else
+#define _EXTERN_TYPE	extern
+#endif
+
+_EXTERN_TYPE int   error_line;
+_EXTERN_TYPE const char *error_file;
+
+void
+init_error_handling (const char *name);
+void
+_error (const char *format, ...);
+void
+_warning (const char *format, ...);
+void
+_file_error (const char *filename);
+
+#if HAVE_ASSERT_H
+#	include <assert.h>
+#else /* not HAVE_ASSERT_H */
+#	define assert(exp)	{if (!(exp)) error ("Assertion `" #exp " != NULL' failed.");}
+#endif /* not HAVE_ASSERT_H */
+
+#endif /* not _ERROR_H */
+
diff --git a/converter/other/fiasco/buttons.c b/converter/other/fiasco/buttons.c
new file mode 100644
index 00000000..82ed18cd
--- /dev/null
+++ b/converter/other/fiasco/buttons.c
@@ -0,0 +1,510 @@
+/*
+ *  buttons.c:		Draw MWFA player buttons in X11 window	
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/15 17:23:11 $
+ *  $Author: hafner $
+ *  $Revision: 5.2 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#ifndef X_DISPLAY_MISSING
+
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+#include <X11/StringDefs.h>
+
+#if STDC_HEADERS
+#	include <stdlib.h>
+#endif /* not STDC_HEADERS */
+
+#include "types.h"
+#include "macros.h"
+
+#include "display.h"
+#include "binerror.h"
+#include "buttons.h"
+
+/*****************************************************************************
+
+			     local variables
+  
+*****************************************************************************/
+
+static const int EVENT_MASK = (KeyPressMask | ButtonPressMask |
+			       ButtonReleaseMask | ExposureMask);
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static void
+draw_progress_bar (x11_info_t *xinfo, binfo_t *binfo, unsigned n,
+		   unsigned n_frames);
+static void
+draw_button (x11_info_t *xinfo, binfo_t *binfo,
+	     buttons_t button, bool_t pressed);
+static void
+draw_control_panel (x11_info_t *xinfo, binfo_t *binfo,
+		    unsigned n, unsigned n_frames);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+binfo_t * 
+init_buttons (x11_info_t *xinfo, unsigned n, unsigned n_frames,
+	      unsigned buttons_height, unsigned progbar_height)
+/*
+ *  Initialize a toolbar with the typical collection of video player
+ *  buttons (pause, play, record, next, etc.) in the window given by 'xinfo'.
+ *  'n' gives the current frame, 'whereas' n_frames is the total number of
+ *  frames of the video stream.
+ *  The size of the button toolbar is given by 'buttons_height',
+ *  the size of the progressbar is given by 'progbar_height'.
+ *
+ *  Return value:
+ *	struct managing the toolbar and progressbar information
+ */
+{
+   XGCValues  values;
+   XEvent     event;
+   Colormap   cmap;
+   XColor     gray, dgray, lgray, red;
+   XColor     graye, dgraye, lgraye, rede;
+   buttons_t  button;			/* counter */
+   binfo_t   *binfo = calloc (1, sizeof (binfo_t));
+
+   if (!binfo)
+      error ("Out of memory.");
+   
+   binfo->width            = xinfo->ximage->width;
+   binfo->height           = buttons_height;
+   binfo->progbar_height   = progbar_height;
+   binfo->record_is_rewind = NO;
+
+   /*
+    *  Generate sub-window for control panel
+    */
+   binfo->window = XCreateSimpleWindow (xinfo->display, xinfo->window,
+					0, xinfo->ximage->height,
+					binfo->width, binfo->height, 0,
+					BlackPixel (xinfo->display,
+						    xinfo->screen),
+					WhitePixel (xinfo->display,
+						    xinfo->screen));
+   XSelectInput(xinfo->display, binfo->window, StructureNotifyMask);
+   XMapWindow (xinfo->display, binfo->window);
+   do
+   {
+      XNextEvent (xinfo->display, &event);
+   }
+   while (event.type != MapNotify || event.xmap.event != binfo->window);
+   XSelectInput (xinfo->display, binfo->window, EVENT_MASK);
+
+   /*
+    *  Generate graphic contexts for different colors.
+    */
+   cmap = DefaultColormap (xinfo->display, xinfo->screen);
+   XAllocNamedColor (xinfo->display, cmap, "#404040", &dgray, &dgraye);
+   XAllocNamedColor (xinfo->display, cmap, "white", &lgray, &lgraye);
+   XAllocNamedColor (xinfo->display, cmap, "#a8a8a8", &gray, &graye);
+   XAllocNamedColor (xinfo->display, cmap, "red", &red, &rede);
+   
+   values.foreground = BlackPixel (xinfo->display, xinfo->screen);
+   values.background = WhitePixel (xinfo->display, xinfo->screen);
+   binfo->gc [BLACK] = XCreateGC (xinfo->display,
+				  RootWindow (xinfo->display, xinfo->screen),
+				  (GCForeground | GCBackground), &values);
+   values.foreground = BlackPixel (xinfo->display, xinfo->screen);
+   values.background = WhitePixel (xinfo->display, xinfo->screen);
+   values.line_width = 3;
+   values.join_style = JoinRound;
+   binfo->gc [THICKBLACK] = XCreateGC (xinfo->display,
+				       RootWindow (xinfo->display,
+						   xinfo->screen),
+				       (GCForeground | GCBackground
+					| GCLineWidth | GCJoinStyle), &values);
+   values.foreground = gray.pixel;
+   values.background = WhitePixel (xinfo->display, xinfo->screen);
+   binfo->gc [NGRAY] = XCreateGC (xinfo->display,
+				  RootWindow (xinfo->display, xinfo->screen),
+				  (GCForeground | GCBackground), &values);
+   values.foreground = lgray.pixel;
+   values.background = WhitePixel (xinfo->display, xinfo->screen);
+   binfo->gc [LGRAY] = XCreateGC (xinfo->display,
+				  RootWindow (xinfo->display, xinfo->screen),
+				  (GCForeground | GCBackground), &values);
+   values.foreground = dgray.pixel;
+   values.background = WhitePixel (xinfo->display, xinfo->screen);
+   binfo->gc [DGRAY] = XCreateGC (xinfo->display,
+				  RootWindow (xinfo->display, xinfo->screen),
+				  (GCForeground | GCBackground), &values);
+   values.foreground = red.pixel;
+   values.background = WhitePixel (xinfo->display, xinfo->screen);
+   binfo->gc [RED]   = XCreateGC (xinfo->display,
+				  RootWindow (xinfo->display, xinfo->screen),
+				  (GCForeground | GCBackground), &values);
+
+   for (button = 0; button < NO_BUTTON; button++)
+      binfo->pressed [button] = NO;
+
+   draw_control_panel (xinfo, binfo, n, n_frames); 
+   
+   return binfo;
+}
+
+void
+wait_for_input (x11_info_t *xinfo)
+/*
+ *  Wait for key press or mouse click in window 'xinfo'.
+ *  Redraw 'image' if event other then ButtonPress or KeyPress occurs.
+ *  Enlarge or reduce size of image by factor 2^'enlarge_factor'.
+ *
+ *  No return value.
+ *
+ *  Side effect:
+ *	program is terminated after key press or mouse click.
+ */
+{
+   bool_t leave_loop = NO;
+   
+   XSelectInput (xinfo->display, xinfo->window, EVENT_MASK);
+
+   while (!leave_loop)
+   {
+      XEvent event;
+
+      XMaskEvent (xinfo->display, EVENT_MASK, &event);
+      switch (event.type)
+      {
+	 case ButtonPress:
+	 case KeyPress:
+	    leave_loop = YES;
+	    break;
+	 default:
+	    display_image (0, 0, xinfo);
+	    break;
+      }
+   }
+}
+
+void
+check_events (x11_info_t *xinfo, binfo_t *binfo, unsigned n, unsigned n_frames)
+/*
+ *  Check the X11 event loop. If the PAUSE buttonin the of panel 'binfo'
+ *  is activated wait until next event occurs.
+ *  Redraw 'image' if event other then ButtonPress or ButtonRelease occurs.
+ *  Enlarge or reduce size of image by factor 2^'enlarge_factor'.
+ *  'n' gives the current frame, 'whereas' n_frames is the total number of
+ *  frames of the video stream.
+ *
+ *  No return values.
+ *
+ *  Side effects:
+ *	status of buttons (binfo->pressed [button]) is changed accordingly.
+ */
+{
+   bool_t leave_eventloop;
+
+   leave_eventloop = (!binfo->pressed [PAUSE_BUTTON]
+		      && binfo->pressed [PLAY_BUTTON])
+		     || (!binfo->pressed [PAUSE_BUTTON]
+			 && binfo->record_is_rewind
+			 && binfo->pressed [RECORD_BUTTON])
+		     || binfo->pressed [RECORD_BUTTON];
+   draw_progress_bar (xinfo, binfo, n, n_frames);
+
+   if (binfo->pressed [PAUSE_BUTTON] && binfo->pressed [PLAY_BUTTON])
+   {
+      XFlush (xinfo->display);
+      draw_button (xinfo, binfo, PLAY_BUTTON, NO); /* clear PLAY mode */
+      XFlush (xinfo->display);
+   }
+   if (binfo->pressed [PAUSE_BUTTON]
+       && binfo->record_is_rewind && binfo->pressed [RECORD_BUTTON])
+   {
+      XFlush (xinfo->display);
+      draw_button (xinfo, binfo, RECORD_BUTTON, NO); /* clear PLAY mode */
+      XFlush (xinfo->display);
+   }
+
+   if (binfo->pressed [STOP_BUTTON])
+   {
+      XFlush (xinfo->display);
+      draw_button (xinfo, binfo, STOP_BUTTON, NO); /* clear STOP button */
+      XFlush (xinfo->display);
+   }
+
+   do
+   {
+      XEvent event;
+      int    button;
+      bool_t wait_release = NO;
+	 
+      
+      if (XCheckMaskEvent (xinfo->display, EVENT_MASK, &event))
+      {
+	 switch (event.type)
+	 {
+	    case ButtonPress:
+	       wait_release = NO;
+	       if (!(binfo->pressed [RECORD_BUTTON] &&
+		     !binfo->record_is_rewind))
+		  for (button = 0; button < NO_BUTTON; button++)
+		  {
+		     int x0, y0, x1, y1; /* button coordinates */
+		  
+		     x0 = button * (binfo->width / NO_BUTTON);
+		     y0 = binfo->progbar_height;
+		     x1 = x0 + binfo->width / NO_BUTTON;
+		     y1 = y0 + binfo->height - binfo->progbar_height - 1;
+		     if (event.xbutton.x > x0 && event.xbutton.x < x1
+			 && event.xbutton.y > y0 && event.xbutton.y < y1) 
+		     {
+			draw_button (xinfo, binfo, button,
+				     !binfo->pressed [button]);
+			wait_release = YES;
+			break;
+		     }
+		  }
+	       break;
+	    case ButtonRelease:
+	       wait_release = NO;
+	       break;
+	    default:
+	       wait_release = NO;
+	       draw_control_panel (xinfo, binfo, n, n_frames);
+	       display_image (0, 0, xinfo);
+	       break;
+	 }
+	 leave_eventloop = !wait_release
+			   && (binfo->pressed [PLAY_BUTTON]
+			       || binfo->pressed [STOP_BUTTON]
+			       || binfo->pressed [RECORD_BUTTON]
+			       || binfo->pressed [QUIT_BUTTON]);
+      }
+   } while (!leave_eventloop);
+
+   if ((binfo->pressed [RECORD_BUTTON] && !binfo->record_is_rewind)
+       && n == n_frames - 1)
+   {
+      binfo->record_is_rewind = YES;
+      draw_button (xinfo, binfo, RECORD_BUTTON, NO);
+   }
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static void
+draw_control_panel (x11_info_t *xinfo, binfo_t *binfo,
+		    unsigned n, unsigned n_frames)
+/*
+ *  Draw control panel 'binfo' with all buttons and progressbar in
+ *  the given 'window'.
+ *  'n' gives the current frame, 'whereas' n_frames is the total number of
+ *  frames of the video stream.
+ *
+ *  No return value.
+ */
+{
+   buttons_t button;
+   
+   XFillRectangle (xinfo->display, binfo->window, binfo->gc [NGRAY],
+		   0, 0, binfo->width, binfo->height);
+   draw_progress_bar (xinfo, binfo, n, n_frames);
+   for (button = 0; button < NO_BUTTON; button++)
+      draw_button (xinfo, binfo, button, binfo->pressed [button]);
+}
+
+static void
+draw_progress_bar (x11_info_t *xinfo, binfo_t *binfo, unsigned n,
+		   unsigned n_frames)
+/*
+ *  Draw progressbar of control panel 'binfo' in the given 'window'.
+ *  'n' gives the current frame, whereas 'n_frames' is the total number of
+ *  frames of the video stream.
+ *
+ *  No return value.
+ */
+{
+   unsigned x, y, width, height;
+
+   x 	  = 2;
+   y 	  = 1;
+   width  = binfo->width - 5;
+   height = binfo->progbar_height - 3;
+   
+   XDrawLine (xinfo->display, binfo->window, binfo->gc [DGRAY],
+	      x, y, x + width, y);
+   XDrawLine (xinfo->display, binfo->window, binfo->gc [DGRAY],
+	      x, y, x, y + height - 1);
+   XDrawLine (xinfo->display, binfo->window, binfo->gc [LGRAY],
+	      x + width, y + 1, x + width, y + height);
+   XDrawLine (xinfo->display, binfo->window, binfo->gc [LGRAY],
+	      x, y + height, x + width, y + height);
+
+   x++; y++; width  -= 2; height -= 2;
+   XFillRectangle (xinfo->display, binfo->window, binfo->gc [NGRAY],
+		   x, y, width, height);
+
+   XFillRectangle (xinfo->display, binfo->window, binfo->gc [BLACK],
+		   x + n * max (1, width / n_frames), y,
+		   max (1, width / n_frames), height);
+}
+
+static void
+draw_button (x11_info_t *xinfo, binfo_t *binfo,
+	     buttons_t button, bool_t pressed)
+/*
+ *  Draw 'button' of control panel 'binfo' in the given 'window'.
+ *  'pressed' indicates whether the button is pressed or not.
+ *
+ *  No return value.
+ */
+{
+   grayscale_t top, bottom;		/* index of GC */
+   unsigned    x, y, width, height;	/* coordinates of button */
+   
+   x 	  = button * (binfo->width / NO_BUTTON);
+   y 	  = binfo->progbar_height;
+   width  = binfo->width / NO_BUTTON;
+   height = binfo->height - binfo->progbar_height - 1;
+   
+   if (width < 4 || height < 4)
+      return;
+   
+   if (pressed)
+   {
+      top    = DGRAY;
+      bottom = LGRAY;
+   }
+   else
+   {
+      top    = LGRAY;
+      bottom = DGRAY;
+   }
+
+   x 	 += 2;
+   width -= 4;
+   
+   XDrawLine (xinfo->display, binfo->window, binfo->gc [top],
+	      x, y, x + width, y);
+   XDrawLine (xinfo->display, binfo->window, binfo->gc [top],
+	      x, y, x, y + height - 1);
+   XDrawLine (xinfo->display, binfo->window, binfo->gc [bottom],
+	      x + width, y + 1, x + width, y + height);
+   XDrawLine (xinfo->display, binfo->window, binfo->gc [bottom],
+	      x, y + height, x + width, y + height);
+
+   x++; y++; width  -= 2; height -= 2;
+   XFillRectangle (xinfo->display, binfo->window, binfo->gc [NGRAY],
+		   x, y, width, height);
+
+   switch (button)
+   {
+      case STOP_BUTTON:
+	 XFillRectangle (xinfo->display, binfo->window, binfo->gc [BLACK],
+			 x + width / 2 - 6, y + height / 2 - 4, 11, 11);
+	 if (pressed && !binfo->pressed [STOP_BUTTON])
+	 {
+	    draw_button (xinfo, binfo, PLAY_BUTTON, NO);
+	    draw_button (xinfo, binfo, PAUSE_BUTTON, NO); 
+	    draw_button (xinfo, binfo, RECORD_BUTTON, NO); 
+	 }
+	 break;
+      case PAUSE_BUTTON:
+	 XFillRectangle (xinfo->display, binfo->window, binfo->gc [BLACK],
+			 x + width / 2 - 6, y + height / 2 - 4, 5, 11);
+	 XFillRectangle (xinfo->display, binfo->window, binfo->gc [BLACK],
+			 x + width / 2 + 1, y + height / 2 - 4, 5, 11);
+	 break;
+      case PLAY_BUTTON:
+	 {
+	    XPoint triangle [3];
+
+	    triangle [0].x = x + width / 2 - 5;
+	    triangle [0].y = y + height / 2 - 5;
+	    triangle [1].x = 10;
+	    triangle [1].y = 6;
+	    triangle [2].x = -10;
+	    triangle [2].y = 6;
+
+	    XFillPolygon (xinfo->display, binfo->window, binfo->gc [BLACK],
+			  triangle, 3, Convex, CoordModePrevious);
+	    if (pressed && !binfo->pressed [PLAY_BUTTON]
+		&& binfo->pressed [RECORD_BUTTON])
+	       draw_button (xinfo, binfo, RECORD_BUTTON, NO);
+	 }
+	 break;
+      case RECORD_BUTTON:
+	 if (!binfo->record_is_rewind)
+	 {
+	    XFillArc (xinfo->display, binfo->window, binfo->gc [RED],
+		      x + width / 2 - 5, y + height / 2 - 5, 11, 11, 0,
+		      360 * 64);
+	    if (pressed && !binfo->pressed [RECORD_BUTTON])
+	    {
+	       draw_button (xinfo, binfo, STOP_BUTTON, YES);
+	       draw_button (xinfo, binfo, PLAY_BUTTON, NO);
+	       draw_button (xinfo, binfo, PAUSE_BUTTON, NO); 
+	    }
+	 }
+	 else
+	 {
+	    XPoint triangle [3];
+
+	    triangle [0].x = x + width / 2 + 5;
+	    triangle [0].y = y + height / 2 - 5;
+	    triangle [1].x = -10;
+	    triangle [1].y = 6;
+	    triangle [2].x = 10;
+	    triangle [2].y = 6;
+
+	    XFillPolygon (xinfo->display, binfo->window, binfo->gc [BLACK],
+			  triangle, 3, Convex, CoordModePrevious);
+	    if (pressed && !binfo->pressed [RECORD_BUTTON]
+		&& binfo->pressed [PLAY_BUTTON])
+	       draw_button (xinfo, binfo, PLAY_BUTTON, NO);
+	 }
+	 break;
+      case QUIT_BUTTON:
+	 {
+	    XPoint triangle [3];
+
+	    triangle [0].x = x + width / 2 - 6;
+	    triangle [0].y = y + height / 2 + 2;
+	    triangle [1].x = 6;
+	    triangle [1].y = -7;
+	    triangle [2].x = 6;
+	    triangle [2].y = 7;
+
+	    XFillPolygon (xinfo->display, binfo->window, binfo->gc [BLACK],
+			  triangle, 3, Convex, CoordModePrevious);
+	    XFillRectangle (xinfo->display, binfo->window, binfo->gc [BLACK],
+			    x + width / 2 - 5, y + height / 2 + 4, 11, 3);
+	 }
+	 break;
+      default:
+	 break;
+   }
+   binfo->pressed [button] = pressed;
+}
+
+#endif /* not X_DISPLAY_MISSING */
diff --git a/converter/other/fiasco/buttons.h b/converter/other/fiasco/buttons.h
new file mode 100644
index 00000000..a09f3423
--- /dev/null
+++ b/converter/other/fiasco/buttons.h
@@ -0,0 +1,50 @@
+/*
+ *  buttons.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:51:17 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _BUTTONS_H
+#define _BUTTONS_H
+
+#ifndef X_DISPLAY_MISSING
+
+typedef enum grayscale_e {BLACK, NGRAY, LGRAY, DGRAY, RED,
+			  THICKBLACK, NO_GC} grayscale_t;
+typedef enum buttons_e {STOP_BUTTON, PLAY_BUTTON, PAUSE_BUTTON, RECORD_BUTTON,
+			QUIT_BUTTON, NO_BUTTON} buttons_t;
+
+typedef struct buttoninfo
+{
+   Window   window;
+   bool_t   pressed [NO_BUTTON];
+   GC	    gc [NO_GC];
+   unsigned width;
+   unsigned height;
+   unsigned progbar_height;
+   bool_t   record_is_rewind;
+} binfo_t;
+
+void
+check_events (x11_info_t *xinfo, binfo_t *binfo, unsigned n,
+	      unsigned n_frames);
+void
+wait_for_input (x11_info_t *xinfo);
+binfo_t * 
+init_buttons (x11_info_t *xinfo, unsigned n, unsigned n_frames,
+	      unsigned buttons_height, unsigned progbar_height);
+
+#endif /* not X_DISPLAY_MISSING */
+
+#endif /* not _BUTTONS_H */
+
diff --git a/converter/other/fiasco/codec/Makefile b/converter/other/fiasco/codec/Makefile
new file mode 100644
index 00000000..9a9d502a
--- /dev/null
+++ b/converter/other/fiasco/codec/Makefile
@@ -0,0 +1,27 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../../..
+  BUILDDIR = $(SRCDIR)
+endif
+FIASCOSUBDIR = converter/other/fiasco
+SUBDIR = $(FIASCOSUBDIR)/codec
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+INCLUDES = -I$(SRCDIR)/$(FIASCOSUBDIR) -I$(SRCDIR)/$(FIASCOSUBDIR)/lib \
+	 -I$(SRCDIR)/$(FIASCOSUBDIR)/input -I$(SRCDIR)/$(FIASCOSUBDIR)/output 
+
+OBJECTS =  approx.o bintree.o coder.o coeff.o \
+           control.o decoder.o dfiasco.o domain-pool.o ip.o motion.o mwfa.o \
+           options.o prediction.o subdivide.o tiling.o wfalib.o
+
+MERGE_OBJECTS = $(OBJECTS)
+
+all: libfiasco_codec.a
+
+include $(SRCDIR)/Makefile.common
+
+libfiasco_codec.a: $(OBJECTS)
+	$(AR) -rc $@ $(OBJECTS)
+	$(RANLIB) $@
+
diff --git a/converter/other/fiasco/codec/approx.c b/converter/other/fiasco/codec/approx.c
new file mode 100644
index 00000000..72e38cbf
--- /dev/null
+++ b/converter/other/fiasco/codec/approx.c
@@ -0,0 +1,702 @@
+/*
+ *  approx.c:		Approximation of range images with matching pursuit
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include <math.h>
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "cwfa.h"
+#include "ip.h"
+#include "rpf.h"
+#include "domain-pool.h"
+#include "misc.h"
+#include "list.h"
+#include "approx.h"
+#include "coeff.h"
+#include "wfalib.h"
+
+/*****************************************************************************
+
+			     local variables
+  
+*****************************************************************************/
+
+typedef struct mp
+{
+   word_t exclude [MAXEDGES];
+   word_t indices [MAXEDGES + 1];
+   word_t into [MAXEDGES + 1];
+   real_t weight [MAXEDGES];
+   real_t matrix_bits;
+   real_t weights_bits;
+   real_t err;
+   real_t costs;
+} mp_t;
+
+/*****************************************************************************
+
+			     prototypes
+  
+*****************************************************************************/
+
+static void
+orthogonalize (unsigned index, unsigned n, unsigned level, real_t min_norm,
+	       const word_t *domain_blocks, const coding_t *c);
+static void 
+matching_pursuit (mp_t *mp, bool_t full_search, real_t price,
+		  unsigned max_edges, int y_state, const range_t *range,
+		  const domain_pool_t *domain_pool, const coeff_t *coeff,
+		  const wfa_t *wfa, const coding_t *c);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+real_t 
+approximate_range (real_t max_costs, real_t price, int max_edges,
+		   int y_state, range_t *range, domain_pool_t *domain_pool,
+		   coeff_t *coeff, const wfa_t *wfa, const coding_t *c)
+/*
+ *  Approximate image block 'range' by matching pursuit. This functions
+ *  calls the matching pursuit algorithm several times (with different
+ *  parameters) in order to find the best approximation. Refer to function
+ *  'matching_pursuit()' for more details about parameters.
+ *
+ *  Return value:
+ *	approximation costs
+ */
+{
+   mp_t	  mp;
+   bool_t success = NO;
+
+   /*
+    *  First approximation attempt: default matching pursuit algorithm.
+    */
+   mp.exclude [0] = NO_EDGE;
+   matching_pursuit (&mp, c->options.full_search, price, max_edges,
+		     y_state, range, domain_pool, coeff, wfa, c);
+
+   /*
+    *  Next approximation attempt: remove domain block mp->indices [0]
+    *  from domain pool (vector with smallest costs) and run the
+    *  matching pursuit again.
+    */
+   if (c->options.second_domain_block)
+   {
+      mp_t tmp_mp = mp;
+      
+      tmp_mp.exclude [0] = tmp_mp.indices [0];
+      tmp_mp.exclude [1] = NO_EDGE;
+	    
+      matching_pursuit (&tmp_mp, c->options.full_search, price, max_edges,
+			y_state, range, domain_pool, coeff, wfa, c);
+      if (tmp_mp.costs < mp.costs)	/* success */
+      {
+	 success = YES;
+	 mp      = tmp_mp;
+      }
+   }
+
+   /*
+    *  Next approximation attempt: check whether some coefficients have
+    *  been quantized to zero. Vectors causing the underflow are
+    *  removed from the domain pool and then the matching pursuit
+    *  algorithm is run again (until underflow doesn't occur anymore).
+    */
+   if (c->options.check_for_underflow)
+   {
+      int  iteration = -1;
+      mp_t tmp_mp    = mp;
+      
+      do
+      {
+	 int i;
+ 
+	 iteration++;
+	 tmp_mp.exclude [iteration] = NO_EDGE;
+	 
+	 for (i = 0; isdomain (tmp_mp.indices [i]); i++)
+	    if (tmp_mp.weight [i] == 0)
+	    {
+	       tmp_mp.exclude [iteration] = tmp_mp.indices [i];
+	       break;
+	    }
+      
+	 if (isdomain (tmp_mp.exclude [iteration])) /* try again */
+	 {
+	    tmp_mp.exclude [iteration + 1] = NO_EDGE;
+	    
+	    matching_pursuit (&tmp_mp, c->options.full_search, price,
+			      max_edges, y_state, range, domain_pool,
+			      coeff, wfa, c);
+	    if (tmp_mp.costs < mp.costs)	/* success */
+	    {
+	       success = YES;
+	       mp      = tmp_mp;
+	    }
+	 }
+      } while (isdomain (tmp_mp.exclude [iteration])
+	       && iteration < MAXEDGES - 1);
+   }
+
+   /*
+    *  Next approximation attempt: check whether some coefficients have
+    *  been quantized to +/- max-value. Vectors causing the overflow are
+    *  removed from the domain pool and then the matching pursuit
+    *  algorithm is run again (until overflow doesn't occur anymore).
+    */
+   if (c->options.check_for_overflow)
+   {
+      int  iteration = -1;
+      mp_t tmp_mp    = mp;
+      
+      do
+      {
+	 int i;
+ 
+	 iteration++;
+	 tmp_mp.exclude [iteration] = NO_EDGE;
+	 
+	 for (i = 0; isdomain (tmp_mp.indices [i]); i++)
+	 {
+	    rpf_t *rpf = tmp_mp.indices [i] ? coeff->rpf : coeff->dc_rpf;
+	    
+	    if (tmp_mp.weight [i] == btor (rtob (200, rpf), rpf)
+		|| tmp_mp.weight [i] == btor (rtob (-200, rpf), rpf))
+	    {
+	       tmp_mp.exclude [iteration] = tmp_mp.indices [i];
+	       break;
+	    }
+	 }
+      
+	 if (isdomain (tmp_mp.exclude [iteration])) /* try again */
+	 {
+	    tmp_mp.exclude [iteration + 1] = NO_EDGE;
+	    
+	    matching_pursuit (&tmp_mp, c->options.full_search, price,
+			      max_edges, y_state, range, domain_pool,
+			      coeff, wfa, c);
+	    if (tmp_mp.costs < mp.costs)	/* success */
+	    {
+	       success = YES;
+	       mp      = tmp_mp;
+	    }
+	 }
+      } while (isdomain (tmp_mp.exclude [iteration])
+	       && iteration < MAXEDGES - 1);
+   }
+
+   /*
+    *  Finally, check whether the best approximation has costs
+    *  smaller than 'max_costs'.
+    */
+   if (mp.costs < max_costs) 
+   {
+      int    edge;
+      bool_t overflow  = NO;
+      bool_t underflow = NO;
+      int    new_index, old_index;
+
+      new_index = 0;
+      for (old_index = 0; isdomain (mp.indices [old_index]); old_index++)
+	 if (mp.weight [old_index] != 0)
+	 {
+	    rpf_t *rpf = mp.indices [old_index] ? coeff->rpf : coeff->dc_rpf;
+	    
+	    if (mp.weight [old_index] == btor (rtob (200, rpf), rpf)
+		|| mp.weight [old_index] == btor (rtob (-200, rpf), rpf))
+	       overflow = YES;
+	    
+	    mp.indices [new_index] = mp.indices [old_index];
+	    mp.into [new_index]    = mp.into [old_index];
+	    mp.weight [new_index]  = mp.weight [old_index];
+	    new_index++;
+	 }
+	 else
+	    underflow = YES;
+      
+      mp.indices [new_index] = NO_EDGE;
+      mp.into  [new_index]   = NO_EDGE;
+
+      /*
+       *  Update of probability models
+       */
+      {
+	 word_t *domain_blocks = domain_pool->generate (range->level, y_state,
+							wfa,
+							domain_pool->model);
+	 domain_pool->update (domain_blocks, mp.indices,
+			      range->level, y_state, wfa, domain_pool->model);
+	 coeff->update (mp.weight, mp.into, range->level, coeff);
+	 
+	 Free (domain_blocks);
+      }
+      
+      for (edge = 0; isedge (mp.indices [edge]); edge++)
+      {
+	 range->into [edge]   = mp.into [edge];
+	 range->weight [edge] = mp.weight [edge];
+      }
+      range->into [edge]  = NO_EDGE;
+      range->matrix_bits  = mp.matrix_bits;
+      range->weights_bits = mp.weights_bits;
+      range->err          = mp.err;
+   }
+   else
+   {
+      range->into [0] = NO_EDGE;
+      mp.costs	      = MAXCOSTS;
+   }
+   
+   return mp.costs;
+}
+
+/*****************************************************************************
+
+			     local variables
+  
+*****************************************************************************/
+
+static real_t norm_ortho_vector [MAXSTATES];     
+/*
+ *  Square-norm of the i-th vector of the orthogonal basis (OB)
+ *  ||o_i||^2; i = 0, ... ,n
+ */
+static real_t ip_image_ortho_vector [MAXEDGES];
+/* 
+ *  Inner product between the i-th vector of the OB and the given range: 
+ *  <b, o_i>; i = 0, ... ,n 
+ */
+static real_t ip_domain_ortho_vector [MAXSTATES][MAXEDGES];
+/* 
+ *  Inner product between the i-th vector of the OB and the image of domain j: 
+ *  <s_j, o_i>; j = 0, ... , wfa->states; i = 0, ... ,n, 
+ */
+static real_t rem_denominator [MAXSTATES];     
+static real_t rem_numerator [MAXSTATES];
+/*
+ *  At step n of the orthogonalization the comparitive value
+ *  (numerator_i / denominator_i):= <b, o_n>^2 / ||o_n|| ,
+ *  is computed for every domain i,
+ *  where o_n := s_i - \sum(k = 0, ... , n-1) {(<s_i, o_k> / ||o_k||^2) o_k}
+ *  To avoid computing the same values over and over again,
+ *  the constant (remaining) parts of every domain are
+ *  stored in 'rem_numerator' and 'rem_denominator' separately
+ */
+static bool_t used [MAXSTATES];    
+/*
+ *  Shows whether a domain image was already used in a
+ *  linear combination (YES) or not (NO)
+ */
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static void 
+matching_pursuit (mp_t *mp, bool_t full_search, real_t price,
+		  unsigned max_edges, int y_state, const range_t *range,
+		  const domain_pool_t *domain_pool, const coeff_t *coeff,
+		  const wfa_t *wfa, const coding_t *c)
+/*
+ *  Find an approximation of the current 'range' with a linear
+ *  combination of vectors of the 'domain_pool'. The linear
+ *  combination is generated step by step with the matching pursuit
+ *  algorithm.  If flag 'full_search' is set then compute complete set
+ *  of linear combinations with n = {0, ..., 'max_edges'} vectors and
+ *  return the best one. Otherwise abort the computation as soon as
+ *  costs (LC (n + 1)) exceed costs ( LC (n)) and return the
+ *  sub-optimal solution.  'price' is the langrange multiplier
+ *  weighting rate and distortion.  'band' is the current color band
+ *  and 'y_state' the corresponding state in the Y component at same
+ *  pixel position.  'domain_pool' gives the set of available vectors,
+ *  and 'coeff' the model for the linear factors. The number of
+ *  elements in the linear combination is limited by 'max_edges'. In
+ *  'mp', vectors may be specified which should be excluded during the
+ *  approximation.
+ *  
+ *  No return value.
+ *
+ *  Side effects:
+ *	vectors, factors, rate, distortion and costs are stored in 'mp'
+ */
+{
+   unsigned	 n;			/* current vector of the OB */
+   int		 index;			/* best fitting domain image */
+   unsigned	 domain;		/* counter */
+   real_t	 norm;			/* norm of range image */
+   real_t	 additional_bits;	/* bits for mc, nd, and tree */
+   word_t	*domain_blocks;		/* current set of domain images */
+   const real_t  min_norm = 2e-3;	/* lower bound of norm */
+   unsigned 	 best_n   = 0;
+   unsigned	 size 	  = size_of_level (range->level);
+ 
+   /*
+    *  Initialize domain pool and inner product arrays
+    */
+   domain_blocks = domain_pool->generate (range->level, y_state, wfa,
+					  domain_pool->model);
+   for (domain = 0; domain_blocks [domain] >= 0; domain++)
+   {
+      used [domain] = NO;
+      rem_denominator [domain]		/* norm of domain */
+	 = get_ip_state_state (domain_blocks [domain], domain_blocks [domain],
+			       range->level, c);
+      if (rem_denominator [domain] / size < min_norm)
+	 used [domain] = YES;		/* don't use domains with small norm */
+      else
+	 rem_numerator [domain]		/* inner product <s_domain, b> */
+	    = get_ip_image_state (range->image, range->address,
+				  range->level, domain_blocks [domain], c);
+      if (!used [domain] && fabs (rem_numerator [domain]) < min_norm)
+	 used [domain] = YES;
+   }
+
+   /*
+    *  Exclude all domain blocks given in array 'mp->exclude'
+    */
+   for (n = 0; isdomain (mp->exclude [n]); n++)
+      used [mp->exclude [n]] = YES;
+
+   /*
+    *  Compute the approximation costs if 'range' is approximated with
+    *  no linear combination, i.e. the error is equal to the square
+    *  of the image norm and the size of the automaton is determined by
+    *  storing only zero elements in the current matrix row
+    */
+   for (norm = 0, n = 0; n < size; n++)
+      norm += square (c->pixels [range->address * size + n]);
+
+   additional_bits = range->tree_bits + range->mv_tree_bits
+		     + range->mv_coord_bits + range->nd_tree_bits
+		     + range->nd_weights_bits;
+
+   mp->err          = norm;
+   mp->weights_bits = 0;
+   mp->matrix_bits  = domain_pool->bits (domain_blocks, NULL, range->level,
+					 y_state, wfa, domain_pool->model);
+   mp->costs        = (mp->matrix_bits + mp->weights_bits
+		       + additional_bits) * price + mp->err;
+
+   n = 0;
+   do 
+   {
+      /*
+       *  Current approximation is: b = d_0 o_0 + ... + d_(n-1) o_(n-1)
+       *  with corresponding costs 'range->err + range->bits * p'.
+       *  For all remaining state images s_i (used[s_i] == NO) set
+       *  o_n :	= s_i - \sum(k = 0, ... , n-1) {(<s_i, o_k> / ||o_k||^2) o_k}
+       *  and try to beat current costs.
+       *  Choose that vector for the next orthogonalization step,
+       *  which has minimal costs: s_index.
+       *  (No progress is indicated by index == -1)
+       */
+      
+      real_t min_matrix_bits  = 0;
+      real_t min_weights_bits = 0;
+      real_t min_error 	      = 0;
+      real_t min_weight [MAXEDGES];
+      real_t min_costs = full_search ? MAXCOSTS : mp->costs;
+      
+      for (index = -1, domain = 0; domain_blocks [domain] >= 0; domain++) 
+	 if (!used [domain]) 
+	 {
+	    real_t    matrix_bits, weights_bits;
+	    /*
+	     *  To speed up the search through the domain images,
+	     *  the costs of using domain image 'domain' as next vector
+	     *  can be approximated in a first step:
+	     *  improvement of image quality
+	     *	  <= square (rem_numerator[domain]) / rem_denominator[domain]
+	     */
+	    {
+		  word_t   vectors [MAXEDGES + 1];
+		  word_t   states [MAXEDGES + 1];
+		  real_t   weights [MAXEDGES + 1];
+		  unsigned i, k;
+		  
+		  for (i = 0, k = 0; k < n; k++)
+		     if (mp->weight [k] != 0)
+		     {
+			vectors [i] = mp->indices [k];
+			states [i]  = domain_blocks [vectors [i]];
+			weights [i] = mp->weight [k];
+			i++;
+		     }
+		  vectors [i] 	  = domain;
+		  states [i]  	  = domain_blocks [domain];
+		  weights [i] 	  = 0.5;
+		  vectors [i + 1] = -1;
+		  states [i + 1]  = -1;
+
+		  weights_bits = coeff->bits (weights, states, range->level,
+					      coeff);
+		  matrix_bits = domain_pool->bits (domain_blocks, vectors,
+						   range->level, y_state,
+						   wfa, domain_pool->model);
+	    }
+	    if (((matrix_bits + weights_bits + additional_bits) * price +
+		 mp->err -
+		 square (rem_numerator [domain]) / rem_denominator [domain])
+		< min_costs)
+	    {
+	       /*
+		*  1.) Compute the weights (linear factors) c_i of the
+		*  linear combination
+		*  b = c_0 v_0 + ... + c_(n-1) v_(n-1) + c_n v_'domain'
+		*  Use backward substitution to obtain c_i from the linear
+		*  factors of the lin. comb. b = d_0 o_0 + ... + d_n o_n
+		*  of the corresponding orthogonal vectors {o_0, ..., o_n}.
+		*  Vector o_n of the orthogonal basis is obtained by using
+		*  vector 'v_domain' in step n of the Gram Schmidt
+		*  orthogonalization (see above for definition of o_n).
+		*  Recursive formula for the coefficients c_i:
+		*  c_n := <b, o_n> / ||o_n||^2
+		*  for i = n - 1, ... , 0:
+		*  c_i := <b, o_i> / ||o_i||^2 +
+		*          \sum (k = i + 1, ... , n){ c_k <v_k, o_i>
+		*					/ ||o_i||^2 }
+		*  2.) Because linear factors are stored with reduced precision
+		*  factor c_i is rounded with the given precision in step i
+		*  of the recursive formula. 
+		*/
+
+	       unsigned k;		/* counter */
+	       int    	l;		/* counter */
+	       real_t 	m_bits;		/* number of matrix bits to store */
+	       real_t 	w_bits;		/* number of weights bits to store */
+	       real_t 	r [MAXEDGES];	/* rounded linear factors */
+	       real_t 	f [MAXEDGES];	/* linear factors */
+	       int    	v [MAXEDGES];	/* mapping of domains to vectors */
+	       real_t 	costs;		/* current approximation costs */
+	       real_t 	m_err;		/* current approximation error */
+
+	       f [n] = rem_numerator [domain] / rem_denominator [domain];
+	       v [n] = domain;		/* corresponding mapping */
+	       for (k = 0; k < n; k++)
+	       {
+		  f [k] = ip_image_ortho_vector [k] / norm_ortho_vector [k];
+		  v [k] = mp->indices [k];
+	       }
+	    
+	       for (l = n; l >= 0; l--) 
+	       {
+		  rpf_t *rpf = domain_blocks [v [l]]
+			       ? coeff->rpf : coeff->dc_rpf;
+
+		  r [l] = f [l] = btor (rtob (f [l], rpf), rpf);
+		     
+		  for (k = 0; k < (unsigned) l; k++)
+		     f [k] -= f [l] * ip_domain_ortho_vector [v [l]][k]
+			      / norm_ortho_vector [k] ;
+	       } 
+
+	       /*
+		*  Compute the number of output bits of the linear combination
+		*  and store the weights with reduced precision. The
+		*  resulting linear combination is
+		*  b = r_0 v_0 + ... + r_(n-1) v_(n-1) + r_n v_'domain'
+		*/
+	       {
+		  word_t vectors [MAXEDGES + 1];
+		  word_t states [MAXEDGES + 1];
+		  real_t weights [MAXEDGES + 1];
+		  int	 i;
+		  
+		  for (i = 0, k = 0; k <= n; k++)
+		     if (f [k] != 0)
+		     {
+			vectors [i] = v [k];
+			states [i]  = domain_blocks [v [k]];
+			weights [i] = f [k];
+			i++;
+		     }
+		  vectors [i] = -1;
+		  states [i]  = -1;
+
+		  w_bits = coeff->bits (weights, states, range->level, coeff);
+		  m_bits = domain_pool->bits (domain_blocks, vectors,
+					      range->level, y_state,
+					      wfa, domain_pool->model);
+	       }
+	       
+	       /*
+		*  To compute the approximation error, the corresponding
+		*  linear factors of the linear combination 
+		*  b = r_0 o_0 + ... + r_(n-1) o_(n-1) + r_n o_'domain'
+		*  with orthogonal vectors must be computed with following
+		*  formula:
+		*  r_i := r_i +
+		*          \sum (k = i + 1, ... , n) { r_k <v_k, o_i>
+		*					/ ||o_i||^2 }
+		*/
+	       for (l = 0; (unsigned) l <= n; l++)
+	       {
+		  /*
+		   *  compute <v_n, o_n>
+		   */
+		  real_t a;
+
+		  a = get_ip_state_state (domain_blocks [v [l]],
+					  domain_blocks [domain],
+					  range->level, c);
+		  for (k = 0; k < n; k++) 
+		     a -= ip_domain_ortho_vector [v [l]][k]
+			  / norm_ortho_vector [k]
+			  * ip_domain_ortho_vector [domain][k];
+		  ip_domain_ortho_vector [v [l]][n] = a;
+	       }
+	       norm_ortho_vector [n]     = rem_denominator [domain];
+	       ip_image_ortho_vector [n] = rem_numerator [domain];
+ 	    
+	       for (k = 0; k <= n; k++)
+		  for (l = k + 1; (unsigned) l <= n; l++)
+		     r [k] += ip_domain_ortho_vector [v [l]][k] * r [l]
+			      / norm_ortho_vector [k];
+	       /*
+		*  Compute approximation error:
+		*  error := ||b||^2 +
+		*  \sum (k = 0, ... , n){r_k^2 ||o_k||^2 - 2 r_k <b, o_k>}
+		*/
+	       m_err = norm;
+	       for (k = 0; k <= n; k++)
+		  m_err += square (r [k]) * norm_ortho_vector [k]
+			 - 2 * r [k] * ip_image_ortho_vector [k];
+	       if (m_err < 0)		/* TODO: return MAXCOSTS */
+		  warning ("Negative image norm: %f"
+			   " (current domain: %d, level = %d)",
+			   (double) m_err, domain, range->level);
+
+	       costs = (m_bits + w_bits + additional_bits) * price + m_err;
+	       if (costs < min_costs)	/* found a better approximation */
+	       {
+		  index            = domain;
+		  min_costs        = costs;
+		  min_matrix_bits  = m_bits;
+		  min_weights_bits = w_bits;
+		  min_error        = m_err;
+		  for (k = 0; k <= n; k++)
+		     min_weight [k] = f [k];
+	       }
+	    }
+	 }
+      
+      if (index >= 0)			/* found a better approximation */
+      {
+	 if (min_costs < mp->costs)
+	 {
+	    unsigned k;
+	    
+	    mp->costs        = min_costs;
+	    mp->err          = min_error;
+	    mp->matrix_bits  = min_matrix_bits;
+	    mp->weights_bits = min_weights_bits;
+	    
+	    for (k = 0; k <= n; k++)
+	       mp->weight [k] = min_weight [k];
+
+	    best_n = n + 1;
+	 }
+	 
+	 mp->indices [n] = index;
+	 mp->into [n]    = domain_blocks [index];
+
+	 used [index] = YES;
+
+	 /* 
+	  *  Gram-Schmidt orthogonalization step n 
+	  */
+	 orthogonalize (index, n, range->level, min_norm, domain_blocks, c);
+	 n++;
+      }	
+   } 
+   while (n < max_edges && index >= 0);
+
+   mp->indices [best_n] = NO_EDGE;
+   
+   mp->costs = (mp->matrix_bits + mp->weights_bits + additional_bits) * price
+	       + mp->err;
+
+   Free (domain_blocks);
+}
+
+static void
+orthogonalize (unsigned index, unsigned n, unsigned level, real_t min_norm,
+	       const word_t *domain_blocks, const coding_t *c)
+/*
+ *  Step 'n' of the Gram-Schmidt orthogonalization procedure:
+ *  vector 'index' is orthogonalized with respect to the set
+ *  {u_[0], ... , u_['n' - 1]}. The size of the image blocks is given by
+ *  'level'. If the denominator gets smaller than 'min_norm' then
+ *  the corresponding domain is excluded from the list of available
+ *  domain blocks.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	The remainder values (numerator and denominator) of
+ *	all 'domain_blocks' are updated. 
+ */
+{
+   unsigned domain;
+   
+   ip_image_ortho_vector [n] = rem_numerator [index];
+   norm_ortho_vector [n]     = rem_denominator [index];
+
+   /*
+    *  Compute inner products between all domain images and 
+    *  vector n of the orthogonal basis:
+    *  for (i = 0, ... , wfa->states)  
+    *  <s_i, o_n> := <s_i, v_n> -
+    *      \sum (k = 0, ... , n - 1){ <v_n, o_k> <s_i, o_k> / ||o_k||^2}
+    *  Moreover the denominator and numerator parts of the comparitive
+    *  value are updated.
+    */
+   for (domain = 0; domain_blocks [domain] >= 0; domain++) 
+      if (!used [domain]) 
+      {
+	 unsigned k;
+	 real_t   tmp = get_ip_state_state (domain_blocks [index],
+					    domain_blocks [domain], level, c);
+	 
+	 for (k = 0; k < n; k++) 
+	    tmp -= ip_domain_ortho_vector [domain][k] / norm_ortho_vector [k]
+		   * ip_domain_ortho_vector [index][k];
+	 ip_domain_ortho_vector [domain][n] = tmp;
+	 rem_denominator [domain] -= square (tmp) / norm_ortho_vector [n];
+	 rem_numerator [domain]   -= ip_image_ortho_vector [n]
+				     / norm_ortho_vector [n]
+				     * ip_domain_ortho_vector [domain][n] ;
+
+	 /*
+	  *  Exclude vectors with small denominator
+	  */
+	 if (!used [domain]) 
+	    if (rem_denominator [domain] / size_of_level (level) < min_norm) 
+	       used [domain] = YES;
+      }
+}
+
+
+
diff --git a/converter/other/fiasco/codec/approx.h b/converter/other/fiasco/codec/approx.h
new file mode 100644
index 00000000..c54b78c9
--- /dev/null
+++ b/converter/other/fiasco/codec/approx.h
@@ -0,0 +1,30 @@
+/*
+ *  approx.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _APPROX_H
+#define _APPROX_H
+
+#include "types.h"
+#include "cwfa.h"
+#include "domain-pool.h"
+
+real_t 
+approximate_range (real_t max_costs, real_t price, int max_edges,
+		   int y_state, range_t *range, domain_pool_t *domain_pool,
+		   coeff_t *coeff, const wfa_t *wfa, const coding_t *c);
+
+#endif /* not _APPROX_H */
+
diff --git a/converter/other/fiasco/codec/bintree.c b/converter/other/fiasco/codec/bintree.c
new file mode 100644
index 00000000..ddd74e15
--- /dev/null
+++ b/converter/other/fiasco/codec/bintree.c
@@ -0,0 +1,94 @@
+/*
+ *  bintree.c:		Bintree model of WFA tree	
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include <math.h>
+
+#include "bintree.h"
+#include "misc.h"
+#include "cwfa.h"
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+void
+tree_update (bool_t child, unsigned level, tree_t *model)
+/*
+ *  Update tree model of given 'level'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	tree model is changed.
+ */
+{
+   if (!child)
+      model->total [level]++;
+   else
+   {
+      model->counts [level]++;
+      model->total [level]++;
+   }
+}
+
+real_t
+tree_bits (bool_t child, unsigned level, const tree_t *model)
+/*
+ *  Compute number of bits needed for coding element 'child' of the bintree.
+ *  For each 'level' a different context is used.
+ *
+ *  Return value:
+ *	# bits
+ */
+{
+   real_t prob = model->counts [level] / (real_t) model->total [level];
+
+   return child ? - log2 (prob) : - log2 (1 - prob);
+}
+
+void
+init_tree_model (tree_t *tree_model)
+/*
+ *  Initialize the model for the tree.
+ *  Symbol RANGE is synonymous with the '0' symbol and
+ *  symbol NO_RANGE is synonymous with the '1' symbol of the binary coder.
+ *  The 'count' array counts the number of NO_RANGE symbols.
+ *
+ *  No return value.
+ */
+{
+   unsigned level;
+   unsigned counts_0 [MAXLEVEL] = {20, 17, 15, 10, 5,  4,  3,
+				   2,  1,  1,  1,  1,  1,  1,  1,
+				   1,  1,  1,  1 , 1,  1,  1};
+   unsigned counts_1 [MAXLEVEL] = {1 , 1,  1,  1,  1,  1,  1,
+				   1,  1,  2,  3,  5,  10, 15, 20,
+				   25, 30, 35, 60, 60, 60, 60};
+   
+   for (level = 0; level < MAXLEVEL ; level++) 
+   {
+      tree_model->counts [level] = counts_1 [level];
+      tree_model->total [level]  = counts_0 [level] + counts_1 [level];
+   }
+}
diff --git a/converter/other/fiasco/codec/bintree.h b/converter/other/fiasco/codec/bintree.h
new file mode 100644
index 00000000..cdb80c94
--- /dev/null
+++ b/converter/other/fiasco/codec/bintree.h
@@ -0,0 +1,43 @@
+/*
+ *  bintree.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _BINTREE_H
+#define _BINTREE_H
+
+#include "wfa.h"
+#include "types.h"
+
+typedef struct tree
+/*
+ *  Used for estimating the number of bits needed for storing the
+ *  tree array. For each level a different context is used.
+ *  The binary alphabet consists of the two symbols NO_RANGE and RANGE,
+ *  which indicate whether there exists a tree edge or not.
+ */
+{
+   unsigned counts [MAXLEVEL];		/* # NO_RANGE symbols at given level */
+   unsigned total [MAXLEVEL];		/* total number of symbols at  ''   */
+} tree_t;
+
+real_t
+tree_bits (bool_t child, unsigned level, const tree_t *model);
+void
+init_tree_model (tree_t *tree_model);
+void
+tree_update (bool_t child, unsigned level, tree_t *model);
+
+#endif /* not _BINTREE_H */
+
diff --git a/converter/other/fiasco/codec/coder.c b/converter/other/fiasco/codec/coder.c
new file mode 100644
index 00000000..df878d87
--- /dev/null
+++ b/converter/other/fiasco/codec/coder.c
@@ -0,0 +1,965 @@
+/*
+ *  coder.c:		WFA coder toplevel functions
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#define _BSD_SOURCE 1   /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include "config.h"
+#include "pnm.h"
+
+#include <math.h>
+#include <ctype.h>
+
+#include <string.h>
+
+#include "nstring.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "fiasco.h"
+
+#include "cwfa.h"
+#include "misc.h"
+#include "control.h"
+#include "bintree.h"
+#include "subdivide.h"
+#include "read.h"
+#include "write.h"
+#include "image.h"
+#include "mwfa.h"
+#include "list.h"
+#include "decoder.h"
+#include "motion.h"
+#include "wfalib.h"
+#include "domain-pool.h"
+#include "coeff.h"
+#include "coder.h"
+#include "rpf.h"
+
+/*****************************************************************************
+
+				global variables
+  
+*****************************************************************************/
+
+const real_t MAXCOSTS =	1e20;
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static coding_t *
+alloc_coder (char const * const *inputname, const c_options_t *options,
+	     wfa_info_t *wi);
+static void
+free_coder (coding_t *c);
+static char *
+get_input_image_name (char const * const *templptr, unsigned ith_image);
+static void
+video_coder (char const * const *image_template, bitfile_t *output,
+	     wfa_t *wfa, coding_t *c);
+static void 
+frame_coder (wfa_t *wfa, coding_t *c, bitfile_t *output);
+static void
+print_statistics (char c, real_t costs, const wfa_t *wfa, const image_t *image,
+		  const range_t *range);
+static frame_type_e
+pattern2type (unsigned frame, const char *pattern);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+int
+fiasco_coder (char const * const *inputname, const char *outputname,
+	      float quality, const fiasco_c_options_t *options)
+/*
+ *  FIASCO coder.
+ *  Encode image or video frames given by the array of filenames `inputname'
+ *  and write to the outputfile `outputname'.
+ *  If 'inputname' = NULL or
+ *     'inputname [0]' == NULL or
+ *     'inputname [0]' == "-", read standard input.
+ *  If 'outputname' == NULL or "-", write on standard output.
+ *  'quality' defines the approximation quality and is 1 (worst) to 100 (best).
+ *
+ *  Return value:
+ *	1 on success
+ *	0 otherwise
+ */
+{
+   try
+   {
+      char const * const default_input [] = {"-", NULL};
+      fiasco_c_options_t *default_options = NULL;
+      const c_options_t  *cop;
+      char const * const *template;
+      
+      /*
+       *  Check parameters
+       */
+      if (!inputname || !inputname [0] || streq (inputname [0], "-"))
+	 template = default_input;
+      else
+	 template = inputname;
+      
+      if (quality <= 0)
+      {
+	 set_error (_("Compression quality has to be positive."));
+	 return 0;
+      }
+      else if (quality >= 100)
+      {
+	 warning (_("Quality typically is 1 (worst) to 100 (best).\n"
+		    "Be prepared for a long running time."));
+      }
+
+      if (options)
+      {
+	 cop = cast_c_options ((fiasco_c_options_t *) options);
+	 if (!cop)
+	    return 0;
+      }
+      else
+      {
+	 default_options = fiasco_c_options_new ();
+	 cop 		 = cast_c_options (default_options);
+      }
+
+   /*
+    *  Open output stream and initialize WFA
+    */
+      {
+	 bitfile_t *output = open_bitfile (outputname, "FIASCO_DATA",
+					   WRITE_ACCESS);
+	 if (!output)
+	 {
+	    set_error (_("Can't write outputfile `%s'.\n%s"),
+		       outputname ? outputname : "<stdout>",
+		       get_system_error ());
+	    if (default_options)
+	       fiasco_c_options_delete (default_options);
+	    return 0;
+	 }
+	 else
+	 {
+	    wfa_t    *wfa = alloc_wfa (YES);
+	    coding_t *c   = alloc_coder (template, cop, wfa->wfainfo);
+	 
+	    read_basis (cop->basis_name, wfa);
+	    append_basis_states (wfa->basis_states, wfa, c);
+	 
+	    c->price = 128 * 64 / quality;
+	 
+	    video_coder (template, output, wfa, c);
+	 
+	    close_bitfile (output);
+	    free_wfa (wfa);
+	    free_coder (c);
+	 
+	    if (default_options)
+	       fiasco_c_options_delete (default_options);
+	 }
+      }
+      return 1;
+   }
+   catch
+   {
+      return 0;
+   }
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static coding_t *
+alloc_coder (char const * const *inputname, const c_options_t *options,
+	     wfa_info_t *wi)
+/*
+ *  Coder structure constructor.
+ *  Allocate memory for the FIASCO coder structure and
+ *  fill in default values specified by 'options'.
+ *
+ *  Return value:
+ *	pointer to the new coder structure or NULL on error
+ */
+{
+   coding_t *c = NULL;
+   
+   /*
+    *  Check whether all specified image frames are readable and of same type
+    */
+   {
+      char     *filename;
+      int   	width, w = 0, height, h = 0;
+      bool_t	color, c = NO;
+      unsigned 	n;
+      
+      for (n = 0; (filename = get_input_image_name (inputname, n)); n++)
+      {
+          FILE *file;
+          xelval maxval;
+          int format;
+          if (filename == NULL)
+              file = stdin;
+          else
+              file = pm_openr(filename);
+          pnm_readpnminit(file, &width, &height, &maxval, &format);
+          color = (PNM_FORMAT_TYPE(format) == PPM_FORMAT) ? TRUE: FALSE;
+
+          pm_close(file);
+	 if (n)
+	 {
+	    if (w != width || h != height || c != color)
+	    {
+	       set_error (_("Format of image frame `%s' doesn't match."),
+			  filename ? filename : "<stdin>");
+	       return NULL;
+	    }
+	 }
+	 else
+	 {
+	    w = width;
+	    h = height;
+	    c = color;
+	 }
+	 Free (filename);
+      }
+      wi->frames = n;
+      wi->width  = w;
+      wi->height = h;
+      wi->color  = c;
+   }
+
+   /*
+    *  Levels ...
+    */
+   {
+      unsigned lx, ly;
+      
+      lx = (unsigned) (log2 (wi->width - 1) + 1);
+      ly = (unsigned) (log2 (wi->height - 1) + 1);
+      
+      wi->level = max (lx, ly) * 2 - ((ly == lx + 1) ? 1 : 0);
+   }
+   
+   c = Calloc (1, sizeof (coding_t));
+
+   c->options 	      	   = *options;
+   c->options.lc_min_level = max (options->lc_min_level, 3);
+   c->options.lc_max_level = min (options->lc_max_level, wi->level - 1);
+
+   c->tiling = alloc_tiling (options->tiling_method,
+			     options->tiling_exponent, wi->level);
+
+   if (wi->frames > 1 && c->tiling->exponent > 0)
+   {
+      c->tiling->exponent = 0;
+      warning (_("Image tiling valid only with still image compression."));
+   }
+
+   if (c->options.lc_max_level >= wi->level - c->tiling->exponent)
+   {
+      message ("'max_level' changed from %d to %d due to image tiling level.",
+	       c->options.lc_max_level, wi->level - c->tiling->exponent - 1);
+      c->options.lc_max_level = wi->level - c->tiling->exponent - 1;
+   }
+   
+   if (c->options.lc_min_level > c->options.lc_max_level)
+      c->options.lc_min_level = c->options.lc_max_level;
+   
+   /*
+    *  p_min_level, p_max_level min and max level for ND/MC prediction
+    *  [p_min_level, p_max_level] must be a subset of [min_level, max_level] !
+    */
+   wi->p_min_level = max (options->p_min_level, c->options.lc_min_level);
+   wi->p_max_level = min (options->p_max_level, c->options.lc_max_level);
+   if (wi->p_min_level > wi->p_max_level)
+      wi->p_min_level = wi->p_max_level;
+
+   c->options.images_level = min (c->options.images_level,
+				  c->options.lc_max_level - 1);
+   
+   c->products_level  = max (0, ((signed int) c->options.lc_max_level
+				 - (signed int) c->options.images_level - 1));
+   c->pixels 	      = Calloc (size_of_level (c->options.lc_max_level),
+				sizeof (real_t));
+   c->images_of_state = Calloc (MAXSTATES, sizeof (real_t *));
+   c->ip_images_state = Calloc (MAXSTATES, sizeof (real_t *));
+   c->ip_states_state = Calloc (MAXSTATES * MAXLEVEL, sizeof (real_t *));
+   
+   debug_message ("Imageslevel :%d, Productslevel :%d",
+		  c->options.images_level, c->products_level);
+   debug_message ("Memory : (%d + %d + %d * 'states') * 'states' + %d",
+		  size_of_tree (c->options.images_level) * 4,
+		  size_of_tree (c->products_level) * 4,
+		  (c->options.lc_max_level - c->options.images_level),
+		  size_of_level (c->options.lc_max_level));
+   
+   /*
+    *  Domain pools ...
+    */
+   c->domain_pool   = NULL;
+   c->d_domain_pool = NULL;
+
+   /*
+    *  Coefficients model ...
+    */
+   c->coeff   = NULL;
+   c->d_coeff = NULL;
+
+   /*
+    *  Max. number of states and edges
+    */
+   wi->max_states   	   = max (min (options->max_states, MAXSTATES), 1);
+   c->options.max_elements = max (min (options->max_elements, MAXEDGES), 1);
+
+   /*
+    *  Title and comment strings
+    */
+   wi->title   = strdup (options->title);
+   wi->comment = strdup (options->comment);
+   
+   /*
+    *  Reduced precision format
+    */
+   wi->rpf
+      = alloc_rpf (options->rpf_mantissa, options->rpf_range);
+   wi->dc_rpf
+      = alloc_rpf (options->dc_rpf_mantissa, options->dc_rpf_range);
+   wi->d_rpf
+      = alloc_rpf (options->d_rpf_mantissa, options->d_rpf_range);
+   wi->d_dc_rpf
+      = alloc_rpf (options->d_dc_rpf_mantissa, options->d_dc_rpf_range);
+   
+   /*
+    *  Color image options ...
+    */
+   wi->chroma_max_states = max (1, options->chroma_max_states);
+
+   /*
+    *  Set up motion compensation struct.
+    *  p_min_level, p_max_level are also used for ND prediction
+    */
+   wi->search_range   = options->search_range;
+   wi->fps 	      = options->fps;
+   wi->half_pixel     = options->half_pixel_prediction;
+   wi->cross_B_search = options->half_pixel_prediction;
+   wi->B_as_past_ref  = options->B_as_past_ref;
+   wi->smoothing      = options->smoothing;
+   
+   c->mt = alloc_motion (wi);
+
+   return c;
+}
+
+static void
+free_coder (coding_t *c)
+/*
+ *  Coder struct destructor:
+ *  Free memory of 'coder' struct.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	structure 'coder' is discarded.
+ */
+{
+   free_tiling (c->tiling);
+   free_motion (c->mt);
+   
+   Free (c->pixels);
+   Free (c->images_of_state);
+   Free (c->ip_images_state);
+   Free (c->ip_states_state);
+   Free (c);
+}
+
+static char *
+get_input_image_name (char const * const *templptr, unsigned ith_image)
+/*
+ *  Construct the i-th image-name using templates.
+ *  If the template contains a '[' it must be of the form
+ *  "prefix[start-end{+,-}step]suffix"
+ *  where "{+,-}step" is optional.
+ *  Leading zeros of "start" are significant.
+ *
+ *  Example:
+ *   "image0[12-01-1].pgm" yields image012.pgm, image011.pgm, ..., image001.pgm
+ *
+ *  Return value:
+ *      ptr to name of image 'ith_image' or NULL if ith_image is out of range.
+ */
+{
+   while (*templptr)
+   {
+      const char *template = *templptr++;
+      char       *s;
+
+      if (!(s = strchr (template, '['))) /* no template, just a filename */
+      {
+	 if (ith_image == 0)
+	    return strdup (template);
+	 else
+	    ith_image--;
+      }
+      else				/* template parser */
+      {
+	 unsigned  n_digits;		/* # of digits in image name no. */
+	 char 	  *s2;
+	 char 	  *suffix;		/* characters after template end */
+	 char  	   prefix [MAXSTRLEN];	/* chars up to the template start */
+	 unsigned  first;		/* first image number */
+	 unsigned  last;		/* last image number */
+	 int  	   image_num;		/* current image number */
+	 int   	   increment = 1;
+	 int 	   dummy;
+
+	 strcpy (prefix, template);
+	 prefix [s - template] = '\0';
+   
+	 for (s2 = ++s, n_digits = 0; ISDIGIT (*s2); s2++, n_digits++)
+	    ;
+	 if (sscanf (s, "%d", &dummy) == 0 || dummy < 0)
+	    error ("Input name template conversion failure.\n"
+		   "Check spelling of template.");
+	 first = (unsigned) dummy;
+	 
+	 if (*s2++ != '-')
+	    error ("Input name template conversion failure.\n"
+		   "Check spelling of template.");
+   
+	 for (s = s2; ISDIGIT (*s2); s2++)
+	    ;
+	 if (sscanf (s, "%d", &dummy) == 0 || dummy < 0)
+	    error ("Input name template conversion failure.\n"
+		   "Check spelling of template.");
+	 last = (unsigned) dummy;
+	 
+	 if (*s2 == '+' || *s2 == '-') 
+	 {
+	    for (s = s2++; ISDIGIT (*s2); s2++)
+	       ;
+	    if (sscanf (s, "%d", &increment) == 0)
+	       error ("Input name template conversion failure.\n"
+		      "Check spelling of template.");
+	 } 	 
+	 if (*s2 != ']')
+	    error ("Input name template conversion failure.\n"
+		   "Check spelling of template.");
+	 suffix = s2 + 1;
+   
+	 image_num = first + increment * ith_image;
+	 if (image_num < 0)
+	    error ("Input name template conversion failure.\n"
+		   "Check spelling of template.");
+	 
+	 if ((increment >  0 && (unsigned) image_num > last) || 
+	     (increment <= 0 && (unsigned) image_num < last))
+	 {
+	    /* TODO: check this */
+	    ith_image -= (last - first) / increment + 1;
+	 }
+	 else
+	 {
+	    char formatstr [MAXSTRLEN]; /* format string for image filename */
+	    char image_name [MAXSTRLEN]; /* image file name to be composed */
+	    
+	    strcpy (formatstr, "%s%0?d%s");
+	    formatstr [4] = '0' + (char) n_digits;
+	    sprintf (image_name, formatstr, prefix, image_num, suffix);
+	    return strdup (image_name);
+	 }
+      }
+   }
+   return NULL;
+}	
+
+static void
+video_coder (char const * const *image_template, bitfile_t *output,
+	     wfa_t *wfa, coding_t *c)
+/*
+ *  Toplevel function to encode a sequence of video frames specified
+ *  by 'image_template'. The output is written to stream 'output'.
+ *  Coding options are given by 'c'.
+ *
+ *  No return value.
+ */
+{
+   unsigned  display;			/* picture number in display order */
+   int	     future_display;		/* number of future reference */
+   int	     frame;			/* current frame number */
+   char	    *image_name;		/* image name of current frame */
+   image_t  *reconst 	  = NULL; 	/* decoded reference image */
+   bool_t    future_frame = NO;		/* YES if last frame was in future */
+   
+   debug_message ("Generating %d WFA's ...", wfa->wfainfo->frames);
+
+   future_display = -1;
+   frame          = -1;
+   display        = 0;
+
+   while ((image_name = get_input_image_name (image_template, display)))
+   {
+      frame_type_e type;		/* current frame type: I, B, P */
+      
+      /*
+       *  Determine type of next frame.
+       *  Skip already coded frames (future reference!)
+       */
+      if (display == 0 && !c->options.reference_filename)
+	 type = I_FRAME;		/* Force first frame to be intra */
+      else
+	 type = pattern2type (display, c->options.pattern);
+      
+      if (type != I_FRAME && c->options.reference_filename)
+	 /* Load reference from disk */
+      {
+	 debug_message ("Reading reference frame `%s'.",
+			c->options.reference_filename);
+	 reconst 	 = read_image (c->options.reference_filename);
+	 c->options.reference_filename = NULL;
+      }
+      if ((int) display == future_display) /* Skip already coded future ref */
+      {				
+	 display++;
+	 continue;
+      }	  
+      else if (type == B_FRAME && (int) display > future_display) 
+      {
+	 unsigned i = display;
+	 /*
+	  *  Search for future reference
+	  */
+	 while (type == B_FRAME)
+	 {
+	    char *name;			/* image name of future frame */
+
+	    i++;
+	    name = get_input_image_name (image_template, i);
+	    
+	    if (!name)			/* Force last valid frame to be 'P' */
+	    {
+	       future_display = i - 1;
+	       type = P_FRAME;
+	    }
+	    else
+	    {
+	       future_display = i;    
+	       image_name     = name;
+	       type 	      = pattern2type (i, c->options.pattern);
+	    }
+	    frame = future_display;
+	 }
+      }
+      else
+      {
+	 frame = display;
+	 display++;
+      }
+
+      debug_message ("Coding \'%s\' [%c-frame].", image_name,
+		     type == I_FRAME ? 'I' : (type == P_FRAME ? 'P' : 'B'));
+       
+      /*
+       *  Depending on current frame type update past and future frames
+       *  which are needed as reference frames.
+       */
+      c->mt->frame_type = type;
+      if (type == I_FRAME)
+      {
+	 if (c->mt->past)		/* discard past frame */
+	    free_image (c->mt->past);
+	 c->mt->past = NULL;
+	 if (c->mt->future)		/* discard future frame */
+	    free_image (c->mt->future);
+	 c->mt->future = NULL;
+	 if (reconst)			/* discard current frame */
+	    free_image (reconst);
+	 reconst = NULL;
+      }
+      else if (type == P_FRAME)
+      {
+	 if (c->mt->past)		/* discard past frame */
+	    free_image (c->mt->past);
+	 c->mt->past = reconst;		/* past frame <- current frame */
+	 reconst    = NULL;
+	 if (c->mt->future)		/* discard future frame */
+	    free_image (c->mt->future);
+	 c->mt->future = NULL;
+      }
+      else				/* B_FRAME */
+      {
+	 if (future_frame)		/* last frame was future frame */
+	 {
+	    if (c->mt->future)		/* discard future frame */
+	       free_image (c->mt->future);
+	    c->mt->future = reconst;	/* future frame <- current frame */
+	    reconst      = NULL;
+	 }
+	 else
+	 {
+	    if (wfa->wfainfo->B_as_past_ref == YES)
+	    {
+	       if (c->mt->past)		/* discard past frame */
+		  free_image (c->mt->past);
+	       c->mt->past = reconst;	/* past frame <- current frame */
+	       reconst    = NULL;
+	    }
+	    else
+	    {
+	       if (reconst)		/* discard current frame */
+		  free_image (reconst);
+	       reconst = NULL;
+	    }
+	 }
+      }
+
+      /*
+       *  Start WFA coding of current frame
+       */
+      future_frame   = frame == future_display;
+      c->mt->number   = frame;
+      c->mt->original = read_image (image_name);
+      if (c->tiling->exponent && type == I_FRAME) 
+	 perform_tiling (c->mt->original, c->tiling);
+
+      frame_coder (wfa, c, output);
+
+      /*
+       *  Regenerate image:
+       *  1. Compute approximation of WFA ranges (real image bintree order)
+       *  2. Generate byte image in rasterscan order
+       *  3. Apply motion compensation
+       */
+      reconst = decode_image (wfa->wfainfo->width, wfa->wfainfo->height,
+			      FORMAT_4_4_4, NULL, wfa);
+
+      if (type != I_FRAME)
+	 restore_mc (0, reconst, c->mt->past, c->mt->future, wfa);
+
+      if (c->mt->original)
+	 free_image (c->mt->original);
+      c->mt->original = NULL;
+      
+      remove_states (wfa->basis_states, wfa); /* Clear WFA structure */
+   }
+
+   if (reconst)
+      free_image (reconst);
+   if (c->mt->future)
+      free_image (c->mt->future);
+   if (c->mt->past)
+      free_image (c->mt->past);
+   if (c->mt->original)
+      free_image (c->mt->original);
+}
+
+static frame_type_e
+pattern2type (unsigned frame, const char *pattern)
+{
+    int tmp = TOUPPER (pattern [frame % strlen (pattern)]);
+    frame_type_e retval;
+
+    switch (tmp)
+    {
+    case 'I':
+        retval = I_FRAME;
+        break;
+    case 'B':
+        retval = B_FRAME;
+        break;
+    case 'P':
+        retval = P_FRAME;
+        break;
+    default:
+        error ("Frame type %c not valid. Choose one of I,B or P.", tmp);
+    }
+    return retval;
+}
+
+static void 
+frame_coder (wfa_t *wfa, coding_t *c, bitfile_t *output)
+/*
+ * 
+ *  WFA Coding of next frame.  All important coding parameters are
+ *  stored in 'c'.  The generated 'wfa' is written to stream 'output'
+ *  immediately after coding.
+ *
+ *  No return value.
+ */
+{
+   unsigned state;
+   range_t  range;			/* first range == the entire image */
+   real_t   costs;			/* total costs (minimized quantity) */
+   unsigned bits;			/* number of bits written on disk */
+   clock_t  ptimer;
+   
+   prg_timer (&ptimer, START);
+
+   bits = bits_processed (output);
+   
+   init_tree_model (&c->tree);
+   init_tree_model (&c->p_tree);
+
+   c->domain_pool
+      = alloc_domain_pool (c->options.id_domain_pool,
+			   wfa->wfainfo->max_states,
+			   c->options.max_elements, wfa);
+   c->d_domain_pool
+      = alloc_domain_pool ((c->options.prediction
+			    || c->mt->frame_type != I_FRAME)
+			   ? c->options.id_d_domain_pool : "constant",
+			   wfa->wfainfo->max_states,
+			   c->options.max_elements, wfa);
+
+   c->coeff   = alloc_coeff_model (c->options.id_rpf_model,
+				   wfa->wfainfo->rpf,
+				   wfa->wfainfo->dc_rpf,
+				   c->options.lc_min_level,
+				   c->options.lc_max_level);
+   c->d_coeff = alloc_coeff_model (c->options.id_d_rpf_model,
+				   wfa->wfainfo->d_rpf,
+				   wfa->wfainfo->d_dc_rpf,
+				   c->options.lc_min_level,
+				   c->options.lc_max_level);
+
+   if (!c->mt->original->color)		/* grayscale image */
+   {
+      memset (&range, 0, sizeof (range_t));
+      range.level = wfa->wfainfo->level;
+
+      costs = subdivide (MAXCOSTS, GRAY, RANGE, &range, wfa, c,
+			 c->options.prediction || c->mt->frame_type != I_FRAME,
+			 NO);
+      if (c->options.progress_meter != FIASCO_PROGRESS_NONE)
+	 message ("");
+
+      if (isrange (range.tree))		/* entire image is approx. by lc? */
+	 error ("No root state generated!");
+      else
+	 wfa->root_state = range.tree;
+
+      print_statistics ('\0', costs, wfa, c->mt->original, &range);
+   }
+   else
+   {
+      int     YCb_node = -1;
+      int     tree [3];			/* 3 root states of each color comp. */
+      color_e band;
+      
+      /*
+       *  When compressing color images, the three color components (YCbCr) 
+       *  are copied into a large image:
+       *  [  Y  Cr ]
+       *  [  Cb 0  ]
+       *  I.e. the color components of an image are processed in a row.
+       *  After all components are compressed, virtual states are generated
+       *  to describe the large image.
+       */
+      for (band = first_band (YES); band <= last_band (YES) ; band++)
+      {
+	 debug_message ("Encoding color component %d", band);
+	 tree [band] = RANGE;
+	 if (band == Cb)
+	 {
+	    unsigned min_level;
+
+	    c->domain_pool->chroma (wfa->wfainfo->chroma_max_states, wfa,
+				    c->domain_pool->model);
+	    /*
+	     *  Don't use a finer partioning for the chrominancy bands than for
+	     *  the luminancy band.
+	     */
+	    for (min_level = MAXLEVEL, state = wfa->basis_states;
+		 state < wfa->states; state++)
+	    {
+	       unsigned lincomb, label;
+	       
+	       for (lincomb = 0, label = 0; label < MAXLABELS; label++)
+		  lincomb += isrange (wfa->tree [state][label]) ? 1 : 0;
+	       if (lincomb)
+		  min_level = min (min_level,
+				   (unsigned) (wfa->level_of_state [state]
+					       - 1));
+	    }
+	    c->options.lc_min_level = min_level;
+	    if (c->mt->frame_type != I_FRAME) /* subtract mc of luminance */
+	       subtract_mc (c->mt->original, c->mt->past, c->mt->future, wfa);
+	 }
+
+	 memset (&range, 0, sizeof (range_t));
+	 range.level = wfa->wfainfo->level;
+	 
+	 costs = subdivide (MAXCOSTS, band, tree [Y], &range, wfa, c,
+			    c->mt->frame_type != I_FRAME && band == Y, NO);
+	 if (c->options.progress_meter != FIASCO_PROGRESS_NONE)
+	    message ("");
+	 {
+	    char colors [] = {'Y', 'B', 'R'};
+	    
+	    print_statistics (colors [band], costs, wfa,
+			      c->mt->original, &range);
+	 }
+	 
+	 if (isrange (range.tree))	/* whole image is approx. by a l.c. */
+	    error ("No root state generated for color component %d!", band);
+	 else
+	    tree[band] = range.tree;
+	 
+	 if (band == Cb)
+	 {
+	    wfa->tree [wfa->states][0] = tree[Y];
+	    wfa->tree [wfa->states][1] = tree[Cb];
+	    YCb_node = wfa->states;
+	    append_state (YES, compute_final_distribution (wfa->states, wfa),
+			  wfa->wfainfo->level + 1, wfa, c);
+	 }
+      }
+      /*
+       *  generate two virtual states (*) 
+       *
+       *              *
+       *            /   \
+       *           +     *
+       *          / \   /  
+       *         Y   CbCr 
+       */
+      wfa->tree [wfa->states][0] = tree[Cr];
+      wfa->tree [wfa->states][1] = RANGE;
+      append_state (YES, compute_final_distribution (wfa->states, wfa),
+		    wfa->wfainfo->level + 1, wfa, c);
+      wfa->tree[wfa->states][0] = YCb_node;
+      wfa->tree[wfa->states][1] = wfa->states - 1;
+      append_state (YES, compute_final_distribution (wfa->states, wfa),
+		    wfa->wfainfo->level + 2, wfa, c);
+
+      wfa->root_state = wfa->states - 1;
+   }
+
+   for (state = wfa->basis_states; state < MAXSTATES; state++)
+   {
+      unsigned level;
+      
+      if (c->images_of_state [state])
+      {
+	 Free (c->images_of_state [state]);
+	 c->images_of_state [state] = NULL;
+      }
+      if (c->ip_images_state [state])
+      {
+	 Free (c->ip_images_state [state]);
+	 c->ip_images_state [state] = NULL;
+      }
+      for (level = c->options.images_level + 1;
+	   level <= c->options.lc_max_level;
+	   level++)
+	 if (c->ip_states_state [state][level])
+	 {
+	    Free (c->ip_states_state [state][level]);
+	    c->ip_states_state [state][level] = NULL;
+	 }
+      
+   }
+   
+   locate_delta_images (wfa);
+   write_next_wfa (wfa, c, output);
+   
+   bits = bits_processed (output) - bits;
+   debug_message ("Total number of bits written: %d (%d bytes, %5.3f bpp)",
+		  bits, bits >> 3,
+		  bits / (double) (c->mt->original->height
+				   * c->mt->original->width));
+   debug_message ("Total encoding time (real): %d sec",
+		  prg_timer (&ptimer, STOP) / 1000);
+
+   c->domain_pool->free (c->domain_pool);
+   c->d_domain_pool->free (c->d_domain_pool);
+
+   c->coeff->free (c->coeff);
+   c->d_coeff->free (c->d_coeff);
+}
+
+static void
+print_statistics (char c, real_t costs, const wfa_t *wfa, const image_t *image,
+		  const range_t *range)
+{
+   unsigned max_level, min_level, state, label, lincomb;
+   
+   for (max_level = 0, min_level = MAXLEVEL, state = wfa->basis_states;
+	state < wfa->states; state++)
+   {
+      for (lincomb = 0, label = 0; label < MAXLABELS; label++)
+	 lincomb += isrange(wfa->tree[state][label]) ? 1 : 0;
+	 
+      if (lincomb)
+      {
+	 max_level = max (max_level,
+			  (unsigned) (wfa->level_of_state [state] - 1));
+	 min_level = min (min_level,
+			  (unsigned) (wfa->level_of_state [state] - 1));
+      }
+   }
+   debug_message ("Image partitioning: maximum level %d , minimum level %d",
+		  max_level, min_level);
+   debug_message ("WFA contains %d states (%d basis states).",
+		  wfa->states, wfa->basis_states);
+   debug_message ("Estimated error: %.2f (RMSE: %.2f, PSNR: %.2f dB).",
+		  (double) range->err,
+		  sqrt (range->err / image->width / image->height),
+		  10 * log ( 255.0 * 255.0 /
+			     (range->err / image->width / image->height))
+		  / log (10.0));
+   debug_message ("Estimated filesize: %.0f bits (%.0f bytes).",
+		  (double) (range->tree_bits + range->matrix_bits
+			    + range->weights_bits
+			    + range->mv_tree_bits + range->mv_coord_bits
+			    + range->nd_tree_bits + range->nd_weights_bits),
+		  (double) (range->tree_bits + range->matrix_bits
+			    + range->weights_bits + range->mv_tree_bits
+			    + range->mv_coord_bits + range->nd_tree_bits
+			    + range->nd_weights_bits) / 8);
+   if (c)
+      debug_message ("(%cT: %.0f, %cM: %.0f, %cW: %.0f, %cMC: %.0f, "
+		     "%cMV: %.0f, %cNT: %.0f, %cNW: %.0f.)",
+		     c, (double) range->tree_bits,
+		     c, (double) range->matrix_bits,
+		     c, (double) range->weights_bits,
+		     c, (double) range->mv_tree_bits,
+		     c, (double) range->mv_coord_bits,
+		     c, (double) range->nd_tree_bits,
+		     c, (double) range->nd_weights_bits);
+   else
+      debug_message ("(T: %.0f, M: %.0f, W: %.0f, MC: %.0f, MV: %.0f, "
+		     "NT: %.0f, NW: %.0f.)",
+		     (double) range->tree_bits,
+		     (double) range->matrix_bits,
+		     (double) range->weights_bits,
+		     (double) range->mv_tree_bits,
+		     (double) range->mv_coord_bits,
+		     (double) range->nd_tree_bits,
+		     (double) range->nd_weights_bits);
+   debug_message ("Total costs : %.2f", (double) costs);
+}
diff --git a/converter/other/fiasco/codec/coder.h b/converter/other/fiasco/codec/coder.h
new file mode 100644
index 00000000..c6f4bb7c
--- /dev/null
+++ b/converter/other/fiasco/codec/coder.h
@@ -0,0 +1,24 @@
+/*
+ *  coder.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _CODER_H
+#define _CODER_H
+
+#include "types.h"
+#include "cwfa.h"
+
+#endif /* not _CODER_H */
+
diff --git a/converter/other/fiasco/codec/coeff.c b/converter/other/fiasco/codec/coeff.c
new file mode 100644
index 00000000..0cd64f17
--- /dev/null
+++ b/converter/other/fiasco/codec/coeff.c
@@ -0,0 +1,369 @@
+/*
+ *  coeff.c:		Matching pursuit coefficients probability model
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include <string.h>
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "rpf.h"
+#include "misc.h"
+#include "coeff.h"
+
+/*
+ *  Coefficient model interface:
+ *  Implementing the coefficients model interface requires the
+ *  following steps: 
+ *  - Add a constructor that initializes the coeff_t structure
+ *  - Allocate new model with default_alloc() 
+ *  - Fill the c_array_t coeff_models[] array with constructor and name
+ *  - Write code for methods bits() and update()
+ *  - Either use default functions for remaining methods or override them
+ *  The new model is automatically registered at the command line.
+ */
+
+/*****************************************************************************
+		  uniform distribution coefficients model
+*****************************************************************************/
+
+static coeff_t *
+alloc_uniform_coeff_model (rpf_t *rpf, rpf_t *dc_rpf,
+			   unsigned min_level, unsigned max_level);
+static void
+uniform_update (const real_t *used_coeff, const word_t *used_states,
+		unsigned level, coeff_t *coeff);
+static real_t
+uniform_bits (const real_t *used_coeff, const word_t *used_states,
+	      unsigned level, const coeff_t *coeff);
+
+/*****************************************************************************
+			  default functions
+*****************************************************************************/
+
+static void
+default_model_free (void *model);
+static void *
+default_model_duplicate (const coeff_t *coeff, const void *model);
+static void
+default_free (coeff_t *coeff);
+static coeff_t *
+default_alloc (rpf_t *rpf, rpf_t *dc_rpf,
+	       unsigned min_level, unsigned max_level);
+
+/*****************************************************************************
+		adaptive arithmetic coding model
+*****************************************************************************/
+
+static coeff_t *
+alloc_aac_coeff_model (rpf_t *rpf, rpf_t *dc_rpf,
+		       unsigned min_level, unsigned max_level);
+static void
+aac_model_free (void *model);
+static void *
+aac_model_alloc (const coeff_t *coeff);
+static void *
+aac_model_duplicate (const coeff_t *coeff, const void *model);
+static void
+aac_update (const real_t *used_coeff, const word_t *used_states,
+	    unsigned level, coeff_t *coeff);
+static real_t
+aac_bits (const real_t *used_coeff, const word_t *used_states,
+	  unsigned level, const coeff_t *coeff);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+typedef struct c_array
+{
+   const char *identifier;
+   coeff_t    *(*function) (rpf_t *rpf, rpf_t *dc_rpf,
+			    unsigned min_level, unsigned max_level);
+} c_array_t;
+
+c_array_t coeff_models[] = {{"adaptive", alloc_aac_coeff_model},
+			    {"uniform",	 alloc_uniform_coeff_model},
+			    {NULL,	 NULL}};
+
+coeff_t *
+alloc_coeff_model (const char *coeff_model_name, rpf_t *rpf, rpf_t *dc_rpf,
+		   unsigned min_level, unsigned max_level)
+/*
+ *  Allocate a new coefficients model which is identified by the string
+ *  'coeff_model_name'.  'rpf' and 'dc_rpf' define the reduced
+ *  precision formats the should be used to quantize normal and DC
+ *  components, respectively. 'min_level' and 'max_level' define the
+ *  range of range approximations.
+ * 
+ *  Return value:
+ *	pointer to the allocated coefficients model
+ *
+ *  Note:
+ *      Refer to 'coeff.h' for a short description of the member functions.  */
+{
+   unsigned n;
+   
+   for (n = 0; coeff_models [n].identifier; n++) /* step through all id's */
+      if (strcaseeq (coeff_models [n].identifier, coeff_model_name)) 
+	 return coeff_models [n].function (rpf, dc_rpf, min_level, max_level);
+
+   warning ("Can't initialize coefficients model '%s'. "
+	    "Using default value '%s'.",
+	    coeff_model_name, coeff_models [0].identifier);
+
+   return coeff_models [0].function (rpf, dc_rpf, min_level, max_level);
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+/*****************************************************************************
+		  uniform distribution coefficients model
+*****************************************************************************/
+
+static coeff_t *
+alloc_uniform_coeff_model (rpf_t *rpf, rpf_t *dc_rpf,
+			   unsigned min_level, unsigned max_level)
+/*
+ *  Underlying probability model: uniform distribution.
+ *  I.e. each coefficient is written as such with
+ *  (1 + exponent_bits + mantissa_bits) bits.
+ */
+{
+   coeff_t *coeff = default_alloc (rpf, dc_rpf, min_level, max_level);
+
+   coeff->bits   = uniform_bits;
+   coeff->update = uniform_update;
+   
+   return coeff;
+}
+
+static real_t
+uniform_bits (const real_t *used_coeff, const word_t *used_states,
+	      unsigned level, const coeff_t *coeff)
+{
+   unsigned edge;
+   real_t   bits = 0;			/* #bits to store coefficients */
+   
+   for (edge = 0; isedge (used_states [edge]); edge++)
+   {
+      rpf_t *rpf = used_states [edge] ? coeff->rpf : coeff->dc_rpf;
+      
+      bits += rpf->mantissa_bits + 1;
+   }
+
+   return bits;
+}
+
+static void
+uniform_update (const real_t *used_coeff, const word_t *used_states,
+		unsigned level, coeff_t *coeff)
+{
+   return;				/* nothing to do */
+}
+
+/*****************************************************************************
+		adaptive arithmetic coding  model
+*****************************************************************************/
+
+typedef struct aac_model
+{
+   word_t *counts;
+   word_t *totals;
+} aac_model_t;
+
+static coeff_t *
+alloc_aac_coeff_model (rpf_t *rpf, rpf_t *dc_rpf,
+		       unsigned min_level, unsigned max_level)
+/*
+ *  Underlying probability model: adaptive arithmetic coding using
+ *  the level of a range as context.
+ */
+{
+   coeff_t *coeff = default_alloc (rpf, dc_rpf, min_level, max_level);
+   
+   coeff->bits            = aac_bits;
+   coeff->update          = aac_update;
+   coeff->model_free      = aac_model_free;
+   coeff->model_duplicate = aac_model_duplicate;
+   coeff->model		  = aac_model_alloc (coeff);
+   
+   return coeff;
+}
+
+static real_t
+aac_bits (const real_t *used_coeff, const word_t *used_states,
+	  unsigned level, const coeff_t *coeff)
+{
+   real_t	bits  = 0;		/* # bits to store coefficients */
+   unsigned	edge;
+   int		state;
+   word_t      *counts;
+   aac_model_t *model = (aac_model_t *) coeff->model;
+
+   counts = model->counts
+	    + (1 << (1 + coeff->dc_rpf->mantissa_bits))
+	    + ((level - coeff->min_level)
+	       * (1 << (1 + coeff->rpf->mantissa_bits)));
+   
+   for (edge = 0; isedge (state = used_states [edge]); edge++)
+      if (state)
+	 bits -= log2 (counts [rtob (used_coeff [edge], coeff->rpf)]
+		       / (real_t) model->totals [level
+						- coeff->min_level + 1]);
+      else
+	 bits -= log2 (model->counts [rtob (used_coeff [edge], coeff->dc_rpf)]
+		       / (real_t) model->totals [0]);
+   
+   return bits;
+}
+
+static void
+aac_update (const real_t *used_coeff, const word_t *used_states,
+	    unsigned level, coeff_t *coeff)
+{
+   unsigned	edge;
+   int		state;
+   word_t      *counts;
+   aac_model_t *model = (aac_model_t *) coeff->model;
+
+   counts = model->counts
+	    + (1 << (1 + coeff->dc_rpf->mantissa_bits))
+	    + ((level - coeff->min_level)
+	       * (1 << (1 + coeff->rpf->mantissa_bits)));
+
+   for (edge = 0; isedge (state = used_states [edge]); edge++)
+      if (state)
+      {
+	 counts [rtob (used_coeff [edge], coeff->rpf)]++;
+	 model->totals [level - coeff->min_level + 1]++;
+      }
+      else
+      {
+	 model->counts [rtob (used_coeff [edge], coeff->dc_rpf)]++;
+	 model->totals [0]++;
+      }
+}
+
+static void *
+aac_model_duplicate (const coeff_t *coeff, const void *model)
+{
+   aac_model_t *src = (aac_model_t *) model;
+   aac_model_t *dst = aac_model_alloc (coeff);
+
+   memcpy (dst->counts, src->counts,
+	   sizeof (word_t) * ((coeff->max_level - coeff->min_level + 1)
+			      * (1 << (1 + coeff->rpf->mantissa_bits))
+			      + (1 << (1 + coeff->dc_rpf->mantissa_bits))));
+   memcpy (dst->totals, src->totals,
+	   sizeof (word_t) * (coeff->max_level - coeff->min_level + 1 + 1));
+   
+   return dst;
+}
+
+static void *
+aac_model_alloc (const coeff_t *coeff)
+{
+   aac_model_t *model;
+   unsigned	size = (coeff->max_level - coeff->min_level + 1)
+		       * (1 << (1 + coeff->rpf->mantissa_bits))
+		       + (1 << (1 + coeff->dc_rpf->mantissa_bits));
+   
+   model 	 = Calloc (1, sizeof (aac_model_t));
+   model->counts = Calloc (size, sizeof (word_t));
+   model->totals = Calloc (coeff->max_level - coeff->min_level + 1 + 1,
+			   sizeof (word_t));
+   /*
+    *  Initialize model
+    */
+   {
+      unsigned  n;
+      word_t   *ptr = model->counts;
+      
+      for (n = size; n; n--)
+	 *ptr++ = 1;
+      model->totals [0] = 1 << (1 + coeff->dc_rpf->mantissa_bits);
+      for (n = coeff->min_level; n <= coeff->max_level; n++)
+	 model->totals [n - coeff->min_level + 1]
+	    = 1 << (1 + coeff->rpf->mantissa_bits);
+   }
+   
+   return (void *) model;
+}
+
+static void
+aac_model_free (void *model)
+{
+   aac_model_t	*aac_model = (aac_model_t *) model;
+
+   if (aac_model)
+   {
+      Free (aac_model->counts);
+      Free (aac_model->totals);
+      Free (aac_model);
+   }
+}
+
+/*****************************************************************************
+				default functions
+*****************************************************************************/
+
+static coeff_t *
+default_alloc (rpf_t *rpf, rpf_t *dc_rpf,
+	       unsigned min_level, unsigned max_level)
+{
+   coeff_t *coeff = Calloc (1, sizeof (coeff_t));
+
+   coeff->rpf 	      	  = rpf;
+   coeff->dc_rpf       	  = dc_rpf;
+   coeff->min_level	  = min_level;
+   coeff->max_level	  = max_level;
+   coeff->model	      	  = NULL;
+   coeff->bits	      	  = NULL;
+   coeff->update      	  = NULL;
+   coeff->free	      	  = default_free;
+   coeff->model_free  	  = default_model_free;
+   coeff->model_duplicate = default_model_duplicate;
+   
+   return coeff;
+}
+
+static void
+default_free (coeff_t *coeff)
+{
+   coeff->model_free (coeff->model);
+   Free (coeff);
+}
+
+static void *
+default_model_duplicate (const coeff_t *coeff, const void *model)
+{
+   return NULL;
+}
+
+static void
+default_model_free (void *model)
+{
+   if (model)
+      Free (model);
+}
diff --git a/converter/other/fiasco/codec/coeff.h b/converter/other/fiasco/codec/coeff.h
new file mode 100644
index 00000000..118cd0fc
--- /dev/null
+++ b/converter/other/fiasco/codec/coeff.h
@@ -0,0 +1,61 @@
+/*
+ *  coeff.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _COEFF_H
+#define _COEFF_H
+
+#include "types.h"
+#include "rpf.h"
+#include "wfa.h"
+
+typedef struct coeff
+{
+   rpf_t    *rpf;			/* reduced precision format */
+   rpf_t    *dc_rpf;			/* RPF of DC (state 0) component */
+   unsigned min_level, max_level;	/* allocate memory for [min,..,max] */
+   void	    *model;			/* generic pointer to prob. model */
+   real_t (*bits) (const real_t *used_coeff, const word_t *used_domains,
+		   unsigned level, const struct coeff *coeff);
+   /*
+    *  Compute bit-rate of a range approximation with coefficients given by
+    *  -1 terminated list 'used_domains'.
+    */
+   void   (*update) (const real_t *used_coeff, const word_t *used_domains,
+		       unsigned level, struct coeff *coeff);
+   /*
+    *  Update the probability model according to the chosen approximation.
+    *  (given by the -1 terminated list 'used_domains').
+    */
+   void	  (*free) (struct coeff *coeff);
+   /*
+    *  Discard the given coefficients struct.
+    */
+   void   (*model_free) (void *model);
+   /*
+    *  Free given probability model.
+    */
+   void   *(*model_duplicate) (const struct coeff *coeff, const void *model);
+   /*
+    *  Duplicate the given probability model (i.e. alloc and copy).
+    */
+} coeff_t;
+
+coeff_t *
+alloc_coeff_model (const char *coeff_model_name, rpf_t *rpf, rpf_t *dc_rpf,
+		   unsigned min_level, unsigned max_level);
+
+#endif /* not _COEFF_H */
+
diff --git a/converter/other/fiasco/codec/control.c b/converter/other/fiasco/codec/control.c
new file mode 100644
index 00000000..9af9928b
--- /dev/null
+++ b/converter/other/fiasco/codec/control.c
@@ -0,0 +1,276 @@
+/*
+ *  control.c:		Control unit of WFA structure
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#if HAVE_STRING_H
+#	include <string.h>
+#else /* not HAVE_STRING_H */
+#	include <strings.h>
+#endif /* not HAVE_STRING_H */
+ 
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "cwfa.h"
+#include "ip.h"
+#include "misc.h"
+#include "wfalib.h"
+#include "control.h"
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static void 
+clear_or_alloc (real_t **ptr, size_t size);
+static void 
+compute_images (unsigned from, unsigned to, const wfa_t *wfa, coding_t *c);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+void    
+append_state (bool_t auxiliary_state, real_t final, unsigned level_of_state,
+	      wfa_t *wfa, coding_t *c)
+/*
+ *  Append a 'wfa' state. If 'auxiliary_state' == YES then
+ *  allocate memory for inner products and state images.  'final' is
+ *  the final distribution of the new state. 'level_of_state' is the
+ *  level of the subimage which is represented by this state
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	The WFA information are updated in structure 'wfa'
+ *	State images are computed and inner products are cleared (in 'c')
+ */
+{
+   wfa->final_distribution [wfa->states] = final;
+   wfa->level_of_state [wfa->states]     = level_of_state;
+   
+   if (!auxiliary_state)
+   {
+      unsigned level;
+
+      wfa->domain_type [wfa->states] = USE_DOMAIN_MASK;
+
+      /*
+       *  Allocate memory for inner products and for state images
+       */
+      clear_or_alloc (&c->images_of_state [wfa->states],
+		      size_of_tree (c->options.images_level));
+	 
+      for (level = c->options.images_level + 1;
+	   level <= c->options.lc_max_level; level++)
+	 clear_or_alloc (&c->ip_states_state [wfa->states][level],
+			 wfa->states + 1);
+
+      clear_or_alloc (&c->ip_images_state [wfa->states],
+		      size_of_tree (c->products_level));
+
+      /*
+       *  Compute the images of the current state at level 0,..,'imageslevel'
+       */
+      
+      c->images_of_state [wfa->states][0] = final;
+      compute_images (wfa->states, wfa->states, wfa, c);  
+
+      /*
+       *  Compute the inner products between the current state and the
+       *  old states 0,...,'states'-1
+       */ 
+      
+      compute_ip_states_state (wfa->states, wfa->states, wfa, c);
+   }
+   else
+   {
+      unsigned level;
+      
+      wfa->domain_type [wfa->states] = 0;
+	    
+      /*
+       *  Free the allocated memory
+       */
+      if (c->images_of_state [wfa->states] != NULL)
+      {
+	 Free (c->images_of_state [wfa->states]);
+	 c->images_of_state [wfa->states] = NULL;
+      }
+      for (level = 0; level <= c->options.lc_max_level; level++)
+	 if (c->ip_states_state [wfa->states][level])
+	 {
+	    Free (c->ip_states_state [wfa->states][level]);
+	    c->ip_states_state [wfa->states][level] = NULL;
+	 }
+      if (c->ip_images_state [wfa->states])
+      {
+	 Free (c->ip_images_state [wfa->states]);
+	 c->ip_images_state [wfa->states] = NULL;
+      }
+   }
+   
+   wfa->states++;
+   if (wfa->states >= MAXSTATES) 
+      error ("Maximum number of states reached!");
+}	
+ 
+void 
+append_basis_states (unsigned basis_states, wfa_t *wfa, coding_t *c)
+/*
+ *  Append the WFA basis states 0, ... , ('basis_states' - 1).
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	The WFA information are updated in structure 'wfa'
+ *	State images and inner products are computed (in 'c')
+ */
+{
+   unsigned level, state;
+
+   /*
+    *  Allocate memory
+    */
+
+   for (state = 0; state < basis_states; state++)
+   {
+      clear_or_alloc (&c->images_of_state [state],
+		      size_of_tree (c->options.images_level));
+
+      for (level = c->options.images_level + 1;
+	   level <= c->options.lc_max_level; level++)
+	 clear_or_alloc (&c->ip_states_state [state][level], state + 1);
+
+      clear_or_alloc (&c->ip_images_state [state],
+		      size_of_tree (c->products_level));
+
+      c->images_of_state [state][0] = wfa->final_distribution [state];
+      wfa->level_of_state [state]   = -1;
+   }
+   
+   compute_images (0, basis_states - 1, wfa, c);  
+   compute_ip_states_state (0, basis_states - 1, wfa, c);
+   wfa->states = basis_states;
+   
+   if (wfa->states >= MAXSTATES) 
+      error ("Maximum number of states reached!");
+}	
+ 
+void 
+append_transitions (unsigned state, unsigned label, const real_t *weight,
+		    const word_t *into, wfa_t *wfa)
+/*
+ *  Append the 'wfa' transitions (given by the arrays 'weight' and 'into')
+ *  of the range ('state','label').
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	new 'wfa' edges are appended
+ */
+{
+   unsigned edge;
+
+   wfa->y_column [state][label] = 0;
+   for (edge = 0; isedge (into [edge]); edge++)
+   {
+      append_edge (state, into [edge], weight [edge], label, wfa);
+      if (into [edge] == wfa->y_state [state][label])
+	 wfa->y_column [state][label] = 1;
+   }
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static void 
+compute_images (unsigned from, unsigned to, const wfa_t *wfa, coding_t *c)
+/*
+ *  Computes the images of the given states 'from', ... , 'to' 
+ *  at level 0,...,'c->imagelevel'.
+ *  Uses the fact that each state image is a linear combination of state
+ *  images, i.e. s_i := c_0 s_0 + ... + c_i s_i.
+ */
+{
+   unsigned label, level, state;
+   
+   /*
+    *  Compute the images Phi(state)
+    *  #		level = 0
+    *  ##		level = 1
+    *  ####		level = 2
+    *  ########    	level = 3
+    *  ...
+    *  ########...##    level = imageslevel
+    */
+   
+   for (level = 1; level <= c->options.images_level; level++)
+      for (state = from; state <= to; state++)
+	 for (label = 0; label < MAXLABELS; label++)
+	 {
+	    real_t   *dst, *src;
+	    unsigned  edge;
+	    int	      domain;		/* current domain */
+	    
+	    if (ischild (domain = wfa->tree[state][label]))
+	    {
+	       dst = c->images_of_state [state] + address_of_level (level) +
+		     label * size_of_level (level - 1);
+	       src = c->images_of_state [domain]
+		     + address_of_level (level - 1);
+	       memcpy (dst, src, size_of_level (level - 1) * sizeof (real_t));
+	    }
+	    for (edge = 0; isedge (domain = wfa->into[state][label][edge]);
+		 edge++)
+	    {
+	       unsigned n;
+	       real_t 	weight = wfa->weight [state][label][edge];
+	       
+	       dst = c->images_of_state [state] + address_of_level (level) +
+		     label * size_of_level (level - 1);
+	       src = c->images_of_state [domain]
+		     + address_of_level (level - 1);
+		  
+	       for (n = size_of_level (level - 1); n; n--)
+		  *dst++ += *src++ * weight;
+	    }
+	 }
+
+}
+
+static void 
+clear_or_alloc (real_t **ptr, size_t size)
+/*
+ *  if *ptr == NULL 	allocate memory with Calloc 
+ *  otherwise 		fill the real_t-array ptr[] with 0
+ */
+{
+   if (*ptr == NULL) 
+      *ptr = Calloc (size, sizeof (real_t));
+   else 
+      memset (*ptr, 0, size * sizeof (real_t));
+    
+}
diff --git a/converter/other/fiasco/codec/control.h b/converter/other/fiasco/codec/control.h
new file mode 100644
index 00000000..f601d8b8
--- /dev/null
+++ b/converter/other/fiasco/codec/control.h
@@ -0,0 +1,33 @@
+/*
+ *  control.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _CONTROL_H
+#define _CONTROL_H
+
+#include "cwfa.h"
+#include "types.h"
+
+void 
+append_transitions (unsigned state, unsigned label, const real_t *weight,
+		    const word_t *into, wfa_t *wfa);
+void 
+append_basis_states (unsigned basis_states, wfa_t *wfa, coding_t *c);
+void    
+append_state (bool_t auxiliary_state, real_t final, unsigned level_of_state,
+	      wfa_t *wfa, coding_t *c);
+
+#endif /* not _CONTROL_H */
+
diff --git a/converter/other/fiasco/codec/cwfa.h b/converter/other/fiasco/codec/cwfa.h
new file mode 100644
index 00000000..9c4e7fee
--- /dev/null
+++ b/converter/other/fiasco/codec/cwfa.h
@@ -0,0 +1,107 @@
+/*
+ *  cwfa.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/10/28 17:39:30 $
+ *  $Author: hafner $
+ *  $Revision: 5.3 $
+ *  $State: Exp $
+ */
+
+#ifndef _CWFA_H
+#define _CWFA_H
+
+#include "wfa.h"
+#include "types.h"
+#include "tiling.h"
+#include "rpf.h"
+#include "domain-pool.h"
+#include "bintree.h"
+#include "coeff.h"
+#include "list.h"
+#include "wfalib.h"
+#include "options.h"
+
+extern const real_t MAXCOSTS;
+
+typedef struct motion
+{
+   image_t	 *original;		/* Current image */
+   image_t	 *past;			/* Preceeding image */
+   image_t	 *future;		/* Succeeding image */
+   frame_type_e	  frame_type;		/* frame type: B_, P_ I_FRAME */
+   unsigned	  number;		/* display number of frame */
+   real_t        *xbits;		/* # bits for mv x-component */
+   real_t        *ybits;		/* # bits for mv y-component */
+   real_t       **mc_forward_norms; 	/* norms of mcpe */
+   real_t       **mc_backward_norms; 	/* norms of mcpe */
+} motion_t;
+
+typedef struct range
+/*
+ *  Information about current range in the original image.
+ *  Approximation data (error, encoding bits, approximation type and factors
+ *  of the linear combination) are also saved.
+ */ 
+{
+   unsigned global_address;             /* We need absolute image addresses
+				           for distance calculations. */
+   unsigned x, y;			/* Coordinates of upper left corner */
+   unsigned image;			/* Position in the tree */
+   unsigned address;			/* Address of the pixel data */
+   unsigned level;			/* Level of the range */
+   real_t   weight [MAXEDGES + 1];	/* coeff. of the approximation */
+   word_t   into [MAXEDGES + 1];	/* used domains of the approximation */
+   int	    tree;			/* == domain : range is approximated
+					   with new state 'domain'
+					   == RANGE  :
+					   with a linear comb. */
+   real_t   err;			/* approximation error */
+   real_t   tree_bits;			/* # bits to encode tree */
+   real_t   matrix_bits;		/* # bits to encode matrices */
+   real_t   weights_bits;		/* # bits to encode weights */
+   mv_t	    mv;				/* motion vector */
+   real_t   mv_tree_bits;		/* # bits to encode mv tree */
+   real_t   mv_coord_bits;		/* # bits to encode mv coordinates */
+   real_t   nd_tree_bits;		/* # bits to encode nd tree */
+   real_t   nd_weights_bits;		/* # bits to encode nd factors */
+   bool_t   prediction;			/* range is predicted? */
+} range_t;
+
+typedef struct coding
+/*
+ *  All parameters and variables that must be accessible through the coding
+ *  process.
+ */
+{
+   real_t     	   price;		/* determines quality of approx. */
+   real_t   	 **images_of_state;	/* image of state i at level
+					   0, ... , imageslevel */
+   real_t   *(*ip_states_state)[MAXLEVEL]; /* inner products between state i
+					      and states 0, ... , i
+					      at all image levels */
+   real_t   	 **ip_images_state;	/* inner products between all
+					   ranges and state i */
+   real_t    	  *pixels;		/* current image pixels stored in tree
+					   order (only leaves are stored) */
+   unsigned   	   products_level;	/* inner products are stored up to
+					   this level */
+   tiling_t   	  *tiling;		/* tiling of the entire image */
+   tree_t     	   tree;		/* probability model */
+   tree_t     	   p_tree;		/* prediction probability model */
+   motion_t   	  *mt;			/* motion compensation information */
+   coeff_t   	  *coeff;
+   coeff_t   	  *d_coeff;
+   domain_pool_t  *domain_pool;
+   domain_pool_t  *d_domain_pool;
+   c_options_t     options;		/* global options */
+} coding_t;
+
+#endif /* not _CWFA_H */
+
diff --git a/converter/other/fiasco/codec/decoder.c b/converter/other/fiasco/codec/decoder.c
new file mode 100644
index 00000000..9474c1e7
--- /dev/null
+++ b/converter/other/fiasco/codec/decoder.c
@@ -0,0 +1,1532 @@
+/*
+ *  decode.c:		Decoding of an image represented by a WFA
+ *
+ *  Written by:		Ullrich Hafner
+ *			Michael Unger
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+ 
+/*
+ *  $Date: 2000/10/22 10:44:48 $
+ *  $Author: hafner $
+ *  $Revision: 5.3 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#if HAVE_STRING_H
+#	include <string.h>
+#else /* not HAVE_STRING_H */
+#	include <strings.h>
+#endif /* not HAVE_STRING_H */
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "wfa.h"
+#include "image.h"
+#include "misc.h"
+#include "motion.h"
+#include "read.h"
+#include "wfalib.h"
+#include "decoder.h"
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static void
+compute_state_images (unsigned frame_level, word_t **simg,
+		      const u_word_t *offset, const wfa_t *wfa);
+static void
+free_state_images (unsigned max_level, bool_t color, word_t **state_image,
+		   u_word_t *offset, const unsigned *root_state,
+		   unsigned range_state, format_e format, const wfa_t *wfa);
+static void
+alloc_state_images (word_t ***images, u_word_t **offsets, const image_t *frame,
+		    const unsigned *root_state, unsigned range_state,
+		    unsigned max_level, format_e format, const wfa_t *wfa);
+static void
+compute_actual_size (unsigned luminance_root,
+		     unsigned *width, unsigned *height, const wfa_t *wfa);
+static void
+enlarge_image (int enlarge_factor, format_e format, unsigned y_root,
+	       wfa_t *wfa);
+static word_t *
+duplicate_state_image (const word_t *domain, unsigned offset, unsigned level);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+video_t *
+alloc_video (bool_t store_wfa)
+/*
+ *  Video struct constructor:
+ *  Initialize video structure and allocate memory for current, past
+ *  and future WFA if flag 'store_wfa' is TRUE.
+ *
+ *  Return value:
+ *	pointer to the new video structure
+ */
+{
+   video_t *video = Calloc (1, sizeof (video_t));
+   
+   video->future_display = -1;
+   video->display        = 0;
+
+   video->future = video->sfuture = video->past
+		 = video->frame   = video->sframe = NULL;
+
+   if (store_wfa)
+   {
+      video->wfa        = alloc_wfa (NO);
+      video->wfa_past   = alloc_wfa (NO);
+      video->wfa_future = alloc_wfa (NO);
+   }
+   else
+      video->wfa = video->wfa_past = video->wfa_future = NULL;
+
+   return video;
+}
+
+void
+free_video (video_t *video)
+/*
+ *  Video struct destructor:
+ *  Free memory of given 'video' struct.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'video' struct is discarded.
+ */
+{
+   if (video->past)
+      free_image (video->past);
+   if (video->future)
+      free_image (video->future);
+   if (video->sfuture)
+      free_image (video->sfuture);
+   if (video->frame)
+      free_image (video->frame);
+   if (video->sframe)
+      free_image (video->sframe);
+   if (video->wfa)
+      free_wfa (video->wfa);
+   if (video->wfa_past)
+      free_wfa (video->wfa_past);
+   if (video->wfa_future)
+      free_wfa (video->wfa_future);
+
+   Free (video);
+}
+
+image_t *
+get_next_frame (bool_t store_wfa, int enlarge_factor,
+		int smoothing, const char *reference_frame,
+		format_e format, video_t *video, dectimer_t *timer,
+		wfa_t *orig_wfa, bitfile_t *input)
+/*
+ *  Get next frame of the WFA 'video' from stream 'input'.
+ *  'orig_wfa' is the constant part of the WFA used by all frames.
+ *  Depending on values of 'enlarge_factor' and 'smoothing' enlarge and
+ *  smooth image, respectively. 
+ *  If 'store_wfa' is TRUE, then store WFA structure of reference frames
+ *  (used by analysis tool xwfa).
+ *  If 'reference_frame' is not NULL, then load image 'reference_frame'
+ *  from disk.
+ *  'format' gives the color format to be used (either 4:2:0 or 4:4:4).
+ *  If 'timer' is not NULL, then accumulate running time statistics. 
+ *
+ *  Return value:
+ *	pointer to decoded frame
+ *
+ *  Side effects:
+ *	'video' and 'timer' struct are modified.
+ */
+{
+   image_t *frame 			  = NULL; /* current frame */
+   image_t *sframe 			  = NULL; /* current smoothed frame */
+   bool_t   current_frame_is_future_frame = NO;
+
+   if (video->future_display == video->display)	 
+   {
+      /*
+       *  Future frame is already computed since it has been used
+       *  as reference frame. So just return the stored frame.
+       */
+      if (video->frame) /* discard current frame */
+	 free_image (video->frame);
+      video->frame  = video->future;
+      video->future = NULL;
+
+      if (video->sframe) /* discard current (smoothed) frame */
+	 free_image (video->sframe);
+      video->sframe  = video->sfuture;
+      video->sfuture = NULL;
+
+      if (store_wfa)
+	 copy_wfa (video->wfa, video->wfa_future);
+
+      video->display++;
+
+      if (!store_wfa)
+	 video->wfa = NULL;
+   }
+   else
+   {
+      do				/* compute next frame(s) */
+      {
+	 unsigned      frame_number;	/* current frame number */
+	 clock_t       ptimer;
+	 unsigned int  stop_timer [3];
+	 wfa_t	      *tmp_wfa = NULL;
+	 
+	 if (!store_wfa)
+	    video->wfa = orig_wfa;
+	 else
+	 {
+	    tmp_wfa = alloc_wfa (NO);
+	    copy_wfa (tmp_wfa, video->wfa);
+	    copy_wfa (video->wfa, orig_wfa);
+	 }
+   
+	 /*
+	  *  First step: read WFA from disk
+	  */
+	 prg_timer (&ptimer, START);
+	 frame_number = read_next_wfa (video->wfa, input);
+	 stop_timer [0] = prg_timer (&ptimer, STOP);
+	 if (timer)
+	 {
+	    timer->input [video->wfa->frame_type] += stop_timer [0];
+	    timer->frames [video->wfa->frame_type]++;
+	 }
+      
+	 /*
+	  *  Read reference frame from disk if required
+	  *  (i.e., 1st frame is of type B or P)
+	  */
+	 if (video->display == 0 && video->wfa->frame_type != I_FRAME)
+	 {
+	    if (!reference_frame)
+	       error ("First frame is %c-frame but no "
+		      "reference frame is given.",
+		      video->wfa->frame_type == B_FRAME ? 'B' : 'P');
+
+	    video->frame  = read_image (reference_frame);
+	    video->sframe = NULL;
+	 }
+   
+	 /*
+	  *  Depending on current frame type update past and future frames
+	  */
+	 if (video->wfa->frame_type == I_FRAME)
+	 {
+	    if (video->past)		/* discard past frame */
+	       free_image (video->past);
+	    video->past = NULL;
+	    if (video->future)		/* discard future frame */
+	       free_image (video->future);
+	    video->future = NULL;
+	    if (video->sfuture)		/* discard (smoothed) future frame */
+	       free_image (video->sfuture);
+	    video->sfuture = NULL;
+	    if (video->frame)		/* discard current frame */
+	       free_image (video->frame);
+	    video->frame = NULL;
+	    if (video->sframe)		/* discard current (smoothed) frame */
+	       free_image (video->sframe);
+	    video->sframe = NULL;
+	 }
+	 else if (video->wfa->frame_type == P_FRAME)
+	 {
+	    if (video->past)		/* discard past frame */
+	       free_image (video->past);
+	    video->past = video->frame;	/* past <- current frame */
+	    video->frame = NULL;
+	    if (video->sframe)		/* discard current (smoothed) frame */
+	       free_image (video->sframe);
+	    video->sframe = NULL;
+	    if (store_wfa)
+	       copy_wfa (video->wfa_past, tmp_wfa);
+	    if (video->future)		/* discard future frame */
+	       free_image (video->future);
+	    video->future = NULL;
+	    if (video->sfuture)		/* discard (smoothed) future frame */
+	       free_image (video->sfuture);
+	    video->sfuture = NULL;
+	 }
+	 else				/* B_FRAME */
+	 {
+	    if (current_frame_is_future_frame)
+	    {
+	       if (video->future)	/* discard future frame */
+		  free_image (video->future);
+	       video->future = frame;	/* future <- current frame */
+	       if (video->sfuture)	/* discard (smoothed) future frame */
+		  free_image (video->sfuture);
+	       video->sfuture = sframe;	/* future <- current (smoothed) */
+	       if (store_wfa)
+		  copy_wfa (video->wfa_future, tmp_wfa);
+	       if (video->frame)	/* discard current frame */
+		  free_image (video->frame);
+	       video->frame = NULL;
+	       if (video->sframe)	/* discard current (smoothed) frame */
+		  free_image (video->sframe);
+	       video->sframe = NULL;
+	       frame  = NULL;
+	       sframe = NULL;
+	    }
+	    else
+	    {
+	       if (video->wfa->wfainfo->B_as_past_ref == YES)
+	       {
+		  if (video->past)	/* discard past frame */
+		     free_image (video->past);
+		  video->past  = video->frame; /* past <- current frame */
+		  video->frame = NULL;
+		  if (video->sframe)	/* discard current (smoothed) frame */
+		     free_image (video->sframe);
+		  video->sframe = NULL;
+		  if (store_wfa)
+		     copy_wfa (video->wfa_past, tmp_wfa);
+	       }
+	       else
+	       {
+		  if (video->frame)	/* discard current */
+		     free_image (video->frame);
+		  video->frame = NULL;
+		  if (video->sframe)	/* discard current (smoothed) frame */
+		     free_image (video->sframe);
+		  video->sframe = NULL;
+	       }
+	    }
+	 }
+	 if (tmp_wfa)
+	    free_wfa (tmp_wfa);
+	 
+	 current_frame_is_future_frame = NO;
+	 /*
+	  *  Second step: decode image
+	  *  Optionally enlarge image if specified by option 'enlarge_factor'.
+	  */
+	 {
+	    unsigned orig_width, orig_height;
+
+	    stop_timer [0] = stop_timer [1] = stop_timer [2] = 0;
+	 
+	    enlarge_image (enlarge_factor, format,
+			   (video->wfa->wfainfo->color
+			    && format == FORMAT_4_2_0)
+			   ? video->wfa->tree [video->wfa->tree [video->wfa->root_state][0]][0] : -1, video->wfa);
+
+	    if (enlarge_factor > 0)
+	    {
+	       orig_width  = video->wfa->wfainfo->width  << enlarge_factor;
+	       orig_height = video->wfa->wfainfo->height << enlarge_factor; 
+	    }
+	    else
+	    { 
+	       orig_width  = video->wfa->wfainfo->width  >> - enlarge_factor;
+	       orig_height = video->wfa->wfainfo->height >> - enlarge_factor;
+	       if (orig_width & 1)
+		  orig_width++;
+	       if (orig_height & 1)
+		  orig_height++;
+	    }
+	 
+	    frame = decode_image (orig_width, orig_height, format,
+				  timer != NULL ? stop_timer : NULL,
+				  video->wfa);
+	    if (timer)
+	    {
+	       timer->preprocessing [video->wfa->frame_type] += stop_timer [0];
+	       timer->decoder [video->wfa->frame_type]       += stop_timer [1];
+	       timer->cleanup [video->wfa->frame_type]       += stop_timer [2];
+	    }
+	 }
+
+	 /*
+	  *  Third step: restore motion compensation
+	  */
+	 if (video->wfa->frame_type != I_FRAME)
+	 {
+	    prg_timer (&ptimer, START);
+	    restore_mc (enlarge_factor, frame, video->past, video->future,
+			video->wfa);
+	    stop_timer [0] = prg_timer (&ptimer, STOP);
+	    if (timer)
+	       timer->motion [video->wfa->frame_type] += stop_timer [0];
+	 }
+
+	 /*
+	  *  Fourth step: smooth image along partitioning borders
+	  */
+	 prg_timer (&ptimer, START);
+	 if (smoothing < 0)	/* smoothing not changed by user */
+	    smoothing = video->wfa->wfainfo->smoothing;
+	 if (smoothing > 0 && smoothing <= 100)
+	 {
+	    sframe = clone_image (frame);
+	    smooth_image (smoothing, video->wfa, sframe);
+	 }
+	 else
+	    sframe = NULL;
+	 
+	 stop_timer [0] = prg_timer (&ptimer, STOP);
+	 if (timer)
+	    timer->smooth [video->wfa->frame_type] += stop_timer [0];
+
+	 if (frame_number == video->display)
+	 {
+	    video->display++;
+	    video->frame  = frame;
+	    video->sframe = sframe;
+	    frame         = NULL;
+	    sframe        = NULL;
+	 }
+	 else if (frame_number > video->display)
+	 {
+	    video->future_display 	  = frame_number;
+	    current_frame_is_future_frame = YES;
+	 }
+      
+	 if (!store_wfa)
+	    remove_states (video->wfa->basis_states, video->wfa);
+      } while (!video->frame);
+
+      if (!store_wfa)
+	 video->wfa = NULL;
+   }
+   
+   return video->sframe ? video->sframe : video->frame;
+}
+
+image_t *
+decode_image (unsigned orig_width, unsigned orig_height, format_e format,
+	      unsigned *dec_timer, const wfa_t *wfa)
+/*
+ *  Compute image which is represented by the given 'wfa'.
+ *  'orig_width'x'orig_height' gives the resolution of the image at
+ *  coding time. Use 4:2:0 subsampling or 4:4:4 'format' for color images.
+ *  If 'dec_timer' is given, accumulate running time statistics. 
+ *  
+ *  Return value:
+ *	pointer to decoded image
+ *
+ *  Side effects:
+ *	'*dectimer' is changed if 'dectimer' != NULL.
+ */
+{
+   unsigned   root_state [3];		/* root of bintree for each band */
+   unsigned   width, height;		/* computed image size */
+   image_t   *frame;			/* regenerated frame */
+   word_t   **images;			/* pointer to array of pointers
+					   to state images */
+   u_word_t  *offsets;			/* pointer to array of state image
+					   offsets */
+   unsigned   max_level;		/* max. level of state with approx. */
+   unsigned   state;
+   clock_t    ptimer;
+
+   prg_timer (&ptimer, START);
+
+   /*
+    *  Compute root of bintree for each color band
+    */
+   if (wfa->wfainfo->color)
+   {
+      root_state [Y]  = wfa->tree [wfa->tree [wfa->root_state][0]][0];
+      root_state [Cb] = wfa->tree [wfa->tree [wfa->root_state][0]][1];
+      root_state [Cr] = wfa->tree [wfa->tree [wfa->root_state][1]][0];
+   }
+   else
+      root_state [GRAY] = wfa->root_state;
+
+   /*
+    *  Compute maximum level of a linear combination
+    */
+   for (max_level = 0, state = wfa->basis_states; state < wfa->states; state++)
+      if (isedge (wfa->into [state][0][0]) || isedge (wfa->into [state][1][0]))
+	 max_level = max (max_level, wfa->level_of_state [state]);
+   
+
+   /*
+    *  Allocate frame buffer for decoded image
+    */
+   compute_actual_size (format == FORMAT_4_2_0 ? root_state [Y] : MAXSTATES,
+			&width, &height, wfa);
+   width  = max (width, orig_width);
+   height = max (height, orig_height);
+   frame = alloc_image (width, height, wfa->wfainfo->color, format);
+   
+   /*
+    *  Allocate buffers for intermediate state images
+    */
+   if (wfa->wfainfo->color)
+   {
+      wfa->level_of_state [wfa->root_state]               = 128;
+      wfa->level_of_state [wfa->tree[wfa->root_state][0]] = 128;
+      wfa->level_of_state [wfa->tree[wfa->root_state][1]] = 128;
+   }
+   alloc_state_images (&images, &offsets, frame, root_state, 0, max_level, 
+		       format, wfa);
+
+   if (dec_timer)
+      dec_timer [0] += prg_timer (&ptimer, STOP);
+
+   /*
+    *  Decode all state images, forming the complete image.
+    */
+   prg_timer (&ptimer, START);
+   compute_state_images (max_level, images, offsets, wfa);
+   if (dec_timer)
+      dec_timer [1] += prg_timer (&ptimer, STOP);
+
+   /*
+    *  Cleanup buffers used for intermediate state images
+    */
+   prg_timer (&ptimer, START);
+   free_state_images (max_level, frame->color, images, offsets, root_state, 0,
+		      format, wfa);
+   
+   /*
+    *  Crop decoded image if the image size differs.
+    */
+   if (orig_width != width || orig_height != height)
+   {
+      frame->height = orig_height;	
+      frame->width  = orig_width;	
+      if (orig_width != width)		
+      {
+	 color_e   band;		/* current color band */
+	 word_t	  *src, *dst;		/* source and destination pointers */
+	 unsigned  y;			/* current row */
+	 
+	 for (band  = first_band (frame->color);
+	      band <= last_band (frame->color); band++)
+	 {
+	    src = dst = frame->pixels [band];
+	    for (y = orig_height; y; y--)
+	    {
+	       memmove (dst, src, orig_width * sizeof (word_t));
+	       dst += orig_width;
+	       src += width;
+	    }
+	    if (format == FORMAT_4_2_0 && band == Y)
+	    {
+	       orig_width  >>= 1;
+	       orig_height >>= 1;
+	       width       >>= 1;
+	    }
+	 }
+      }
+   }
+   if (dec_timer)
+      dec_timer [2] += prg_timer (&ptimer, STOP);
+
+   return frame;
+}
+
+image_t *
+decode_state (unsigned state, unsigned level, wfa_t *wfa)
+/*
+ *  Decode 'state' image of 'wfa' at given 'level'.
+ *
+ *  Return value.
+ *	pointer to decoded state image
+ *
+ *  Side effects:
+ *	'wfa' states > 'state' are removed.  
+ */
+{
+   word_t  *domains [2];
+   image_t *img = Calloc (1, sizeof (image_t));
+
+   /*
+    *  Generate a new state with a 1.0 transition to 'state'
+    */
+   remove_states (state + 1, wfa);
+   append_edge (state + 1, state, 1.0, 0, wfa);
+   wfa->states = state + 2;
+
+   img->color  = NO;
+   img->width  = width_of_level (level);
+   img->height = height_of_level (level);
+   img->format = FORMAT_4_4_4;
+   img->pixels [GRAY] = decode_range (state + 1, 0, level, domains, wfa);
+
+   /*
+    *  Copy decoded range to the frame buffer
+    */
+   {
+      word_t   *src, *dst;
+      unsigned	y;
+	    
+      src = domains [0];
+      dst = img->pixels [GRAY];
+      for (y = img->height; y; y--)
+      {
+	 memcpy (dst, src, width_of_level (level) * sizeof (word_t));
+	 src += width_of_level (level);
+	 dst += img->width;
+      }
+      Free (domains [0]);
+   }
+
+   return img;
+}
+
+word_t *
+decode_range (unsigned range_state, unsigned range_label, unsigned range_level,
+	      word_t **domain, wfa_t *wfa)
+/*
+ *  Compute 'wfa' image of range (identified by 'state' and 'label')
+ *  at 'range_level (works as function decode_image()).
+ *
+ *  Return value:
+ *	pointer to the pixels in SHORT format
+ *
+ *  Side effects:
+ *	if 'domain' != NULL then also the domain blocks
+ *	of the corresponding range blocks are generated
+ *      and returned in domain[]
+ *	'wfa->level_of_state []' is changed
+ */
+{
+   unsigned   root_state [3];		/* dummy (for alloc_state_images) */
+   image_t   *state_image;		/* regenerated state image */
+   word_t   **images;			/* pointer to array of pointers
+					   to state images */
+   u_word_t  *offsets;			/* pointer to array of state image
+					   offsets */
+   word_t    *range;
+
+   enlarge_image (range_level - (wfa->level_of_state [range_state] - 1),
+		  FORMAT_4_4_4, -1, wfa);
+   root_state [0] = range_state;
+   state_image    = alloc_image (width_of_level (range_level + 1),
+				 height_of_level (range_level + 1),
+				 NO, FORMAT_4_4_4);
+   alloc_state_images (&images, &offsets, state_image, NULL, range_state,
+		       range_level + 1, NO, wfa);
+   compute_state_images (range_level + 1, images, offsets, wfa);
+
+   range = Calloc (size_of_level (range_level), sizeof (word_t));
+
+   if ((range_level & 1) == 0)		/* square image */
+   {
+      memcpy (range,
+	      images [range_state + (range_level + 1) * wfa->states]
+	      + range_label * size_of_level (range_level),
+	      size_of_level (range_level) * sizeof (word_t));
+   }
+   else					/* rectangle */
+   {
+      word_t   *src, *dst;
+      unsigned  y;
+      
+      src = images [range_state + (range_level + 1) * wfa->states]
+	    + range_label * width_of_level (range_level);
+      dst = range;
+      for (y = height_of_level (range_level); y; y--)
+      {
+	 memcpy (dst, src, width_of_level (range_level) * sizeof (word_t));
+	 dst += width_of_level (range_level);
+	 src += width_of_level (range_level + 1);
+      }
+   }
+
+   if (domain != NULL)			/* copy domain images */
+   {
+      int      s;			/* domain state */
+      unsigned edge;			/* counter */
+		
+      if (ischild (s = wfa->tree [range_state][range_label]))
+	 *domain++ = duplicate_state_image (images [s + (range_level)
+						   * wfa->states],
+					    offsets [s + (range_level)
+						    * wfa->states],
+					    range_level);
+      for (edge = 0; isedge (s = wfa->into[range_state][range_label][edge]);
+	   edge++)
+	 *domain++ = duplicate_state_image (images [s + (range_level)
+						   * wfa->states],
+					    offsets [s + (range_level)
+						    * wfa->states],
+					    range_level);
+      *domain = NULL;
+   }
+   
+   free_state_images (range_level + 1, NO, images, offsets, NULL, range_state,
+		      NO, wfa);
+   free_image (state_image);
+   
+   return range;
+}
+
+void
+smooth_image (unsigned sf, const wfa_t *wfa, image_t *image)
+/*
+ *  Smooth 'image' along the partitioning boundaries of the 'wfa'
+ *  with factor 's'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	pixel values of the 'image' are modified with respect to 's'
+ */
+{
+   int	    is, inegs;			/* integer factors of s and 1 - s*/
+   unsigned state;			
+   unsigned img_width  = image->width;
+   unsigned img_height = image->height;
+   real_t   s 	       = 1.0 - sf / 200.0;
+
+   if (s < 0.5 || s >= 1)		/* value out of range */
+      return;
+
+   is 	 = s * 512 + .5;		/* integer representation of s */
+   inegs = (1 - s) * 512 + .5;		/* integer representation of 1 - s */
+   
+   for (state = wfa->basis_states;
+	state < (wfa->wfainfo->color
+		 ? wfa->tree [wfa->root_state][0]
+		 : wfa->states); state++)
+   {
+      word_t   *bptr   = image->pixels [Y]; /* pointer to right or
+					       lower line */
+      unsigned  level  = wfa->level_of_state[state]; /* level of state image */
+      unsigned  width  = width_of_level (level); /* size of state image */
+      unsigned  height = height_of_level (level); /* size of state image */
+      
+      if (wfa->y [state][1] >= img_height || wfa->x [state][1] >= img_width)
+	 continue;			/* outside visible area */
+	 
+      if (level % 2)			/* horizontal smoothing */
+      {
+	 unsigned  i;			/* line counter */
+	 word_t   *img1;		/* pointer to left or upper line */
+	 word_t   *img2;		/* pointer to right or lower line */
+
+	 img1 = bptr + (wfa->y [state][1] - 1) * img_width
+		+ wfa->x [state][1];
+	 img2 = bptr + wfa->y [state][1] * img_width + wfa->x [state][1];
+	 
+	 for (i = min (width, img_width - wfa->x [state][1]); i;
+	      i--, img1++, img2++)
+	 {
+	    int tmp = *img1;
+	    
+#ifdef HAVE_SIGNED_SHIFT
+	    *img1 = (((is * tmp) >> 10) << 1)
+		    + (((inegs * (int) *img2) >> 10) << 1);
+	    *img2 = (((is * (int) *img2) >> 10) << 1)
+		    + (((inegs * tmp) >> 10) << 1);
+#else /* not HAVE_SIGNED_SHIFT */
+	    *img1 = (((is * tmp) / 1024) * 2)
+		    + (((inegs * (int) *img2) / 1024) * 2);
+	    *img2 = (((is * (int) *img2) / 1024) * 2)
+		    + (((inegs * tmp) / 1024) *2);
+#endif /* not HAVE_SIGNED_SHIFT */
+	 }
+      }
+      else				/* vertical smoothing */
+      {
+	 unsigned  i;			/* line counter */
+	 word_t   *img1;		/* pointer to left or upper line */
+	 word_t   *img2;		/* pointer to right or lower line */
+
+	 img1 = bptr + wfa->y [state][1] * img_width + wfa->x [state][1] - 1;
+	 img2 = bptr + wfa->y [state][1] * img_width + wfa->x [state][1];
+	 
+	 for (i = min (height, img_height - wfa->y [state][1]); i;
+	      i--, img1 += img_width, img2 += img_width)
+	 {
+	    int tmp = *img1;
+	    
+#ifdef HAVE_SIGNED_SHIFT
+	    *img1 = (((is * tmp) >> 10) << 1)
+		    + (((inegs * (int) *img2) >> 10) << 1);
+	    *img2 = (((is * (int) *img2) >> 10) << 1)
+		    + (((inegs * tmp) >> 10) << 1);
+#else /* not HAVE_SIGNED_SHIFT */
+	    *img1 = (((is * tmp) / 1024) * 2)
+		    + (((inegs * (int) *img2) / 1024) * 2);
+	    *img2 = (((is * (int) *img2) / 1024) * 2)
+		    + (((inegs * tmp) / 1024) *2);
+#endif /* not HAVE_SIGNED_SHIFT */
+	 }
+      }
+   }
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static void
+enlarge_image (int enlarge_factor, format_e format, unsigned y_root,
+	       wfa_t *wfa)
+/*
+ *  Enlarge or reduce size of state images by factor 2^'enlarge_factor'.
+ *  Use 4:2:0 subsampling if specified by 'format', else use 4:4:4 format.
+ *  'wfa' root state of the first chroma band is given by 'y_root' + 1.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	coordinates of ranges and motion blocks in the WFA structure 'wfa'
+ *	are modified.
+ */
+{
+   
+   if (enlarge_factor != 0 || format == FORMAT_4_2_0)
+   {
+      unsigned state;
+
+      if (enlarge_factor == 0)
+      {
+	 state 		= y_root + 1;
+	 enlarge_factor = -1;
+      }
+      else
+	 state = wfa->basis_states;
+      
+      for (; state < wfa->states; state++)
+      {
+	 unsigned label, n;
+	 
+	 wfa->level_of_state [state]
+	    = max (wfa->level_of_state [state] + enlarge_factor * 2, 0);
+
+	 for (label = 0; label < MAXLABELS; label++)
+	    if (enlarge_factor > 0)
+	    {
+	       wfa->x [state][label] <<= enlarge_factor;
+	       wfa->y [state][label] <<= enlarge_factor;
+	       for (n = enlarge_factor; n; n--)
+	       {
+		  wfa->mv_tree [state][label].fx *= 2;
+		  wfa->mv_tree [state][label].fy *= 2;
+		  wfa->mv_tree [state][label].bx *= 2;
+		  wfa->mv_tree [state][label].by *= 2;
+	       }
+	    }
+	    else				/* enlarge_factor < 0 */
+	    {
+	       wfa->x [state][label] >>= - enlarge_factor;
+	       wfa->y [state][label] >>= - enlarge_factor;
+	       for (n = - enlarge_factor; n; n--)
+	       {
+		  wfa->mv_tree [state][label].fx /= 2;
+		  wfa->mv_tree [state][label].fy /= 2;
+		  wfa->mv_tree [state][label].bx /= 2;
+		  wfa->mv_tree [state][label].by /= 2;
+	       }
+	    }
+	 if (format == FORMAT_4_2_0 && state == y_root)
+	    enlarge_factor--;
+      }
+   }
+}
+
+static void
+compute_actual_size (unsigned luminance_root,
+		     unsigned *width, unsigned *height, const wfa_t *wfa)
+/*
+ *  Compute actual size of the frame represented by the given 'wfa'.
+ *  (The reconstructed frame may get larger than the original due
+ *   to the bintree partitioning.)
+ *  If 'luminance_root' < MAXSTATES then the size of chroma ranges (4:2:0).
+ *
+ *  Return values:
+ *	actual 'width' and 'height' of the decoded frame.
+ */
+{
+   unsigned x = 0, y = 0;		/* maximum coordinates */
+   unsigned state;			/* counter */
+   
+   for (state = wfa->basis_states; state < wfa->states; state++)
+      if (isedge (wfa->into [state][0][0]) || isedge (wfa->into [state][1][0]))
+      {
+	 unsigned mult = state > luminance_root ? 2 : 1;
+	 
+	 x = max ((wfa->x [state][0]
+		   + width_of_level (wfa->level_of_state [state])) * mult, x);
+	 y = max ((wfa->y [state][0]
+		   + height_of_level (wfa->level_of_state [state])) * mult, y);
+      }
+
+   if (x & 1)				/* ensure that image size is even */
+      x++;
+   if (y & 1)
+      y++;
+   *width  = x;
+   *height = y;
+}
+
+static void
+alloc_state_images (word_t ***images, u_word_t **offsets, const image_t *frame,
+		    const unsigned *root_state, unsigned range_state,
+		    unsigned max_level, format_e format, const wfa_t *wfa)
+/*
+ *  Generate list of 'wfa' state images which have to be computed for
+ *  each level to obtain the decoded 'frame'. 'root_state[]' denotes the
+ *  state images of the three color bands.
+ *  'max_level' fives the max. level of a linear combination.
+ *  Memory is allocated for every required state image.
+ *  Use 4:2:0 subsampling or 4:4:4 'format' for color images.
+ *  If 'range_state' > 0 then rather compute image of 'range_state' than 
+ *  image of 'wfa->root_state'.
+ *
+ *  Return values:
+ *	'*images'	Pointer to array of state image pointers
+ *	'*offsets'	Pointer to array of state image offsets.
+ *
+ *  Side effects:
+ *	The arrays given above are filled with useful values.
+ */
+{
+   word_t   **simg;			/* ptr to list of state image ptr's */
+   u_word_t  *offs;			/* ptr to list of offsets */
+   unsigned   level;			/* counter */
+   
+   simg	= Calloc (wfa->states * (max_level + 1), sizeof (word_t *));
+   offs	= Calloc (wfa->states * (max_level + 1), sizeof (u_word_t));
+
+   /*
+    *  Initialize buffers for those state images which are at 'max_level'.
+    */
+   if (range_state > 0)			/* a range is given */
+   {
+      simg [range_state + max_level * wfa->states] = frame->pixels [GRAY];
+      offs [range_state + max_level * wfa->states] = frame->width;
+   }
+   else
+   {
+      unsigned state;
+
+      for (state = wfa->basis_states; state <= root_state [Y]; state++)
+	 if (wfa->level_of_state [state] == max_level)
+	 {
+	    simg [state + max_level * wfa->states]
+	       = (frame->pixels [Y] + wfa->y [state][0] * frame->width
+		  + wfa->x [state][0]);
+	    offs [state + max_level * wfa->states] = frame->width;
+	 }
+      if (frame->color)
+      {
+	 unsigned width = format == FORMAT_4_2_0 ?
+			  (frame->width >> 1) : frame->width;
+	 for (; state < wfa->states; state++)
+	    if (wfa->level_of_state [state] == max_level)
+	    {
+	       simg [state + max_level * wfa->states]
+		  = (frame->pixels [state > root_state [Cb] ? Cr : Cb]
+		     + wfa->y [state][0] * width + wfa->x [state][0]);
+	       offs [state + max_level * wfa->states] = width;
+	    }
+      }
+   }
+   
+   /*
+    *  Generate list of state images which must be computed at each level
+    */
+   for (level = max_level; level > 0; level--)
+   {
+      int      child, domain;
+      unsigned state, label, edge;
+      
+      /*
+       *  Range approximation with child. 
+       */
+      for (state = 1; state < (range_state > 0 ?
+			       range_state + 1 : wfa->states); state++)
+	 if (simg [state + level * wfa->states])
+	    for (label = 0; label < MAXLABELS; label++)
+	       if (ischild (child = wfa->tree[state][label]))
+	       {
+		  if (isedge (wfa->into[state][label][0]))
+		  {
+		     /*
+		      *  Allocate new image block.
+		      */
+		     simg [child + (level - 1) * wfa->states]
+			= Calloc (size_of_level (level - 1), sizeof (word_t));
+		     offs [child + (level - 1) * wfa->states]
+			= width_of_level (level - 1);
+		  }
+		  else
+		  {
+		     /*
+		      *  Use image block and offset of parent.
+		      */
+		     if (level & 1)	/* split vertically */
+		     {
+			simg [child + (level - 1) * wfa->states]
+			   = (simg [state + level * wfa->states]
+			      + label * (height_of_level (level - 1)
+					 * offs [state
+						+ level * wfa->states]));
+		     }
+		     else		/* split horizontally */
+		     {
+			simg [child + (level - 1) * wfa->states]
+			   = (simg [state + level * wfa->states]
+			      + label * width_of_level (level - 1));
+		     }
+		     offs [child + (level - 1) * wfa->states]
+			= offs [state + level * wfa->states];
+		  }
+	       }
+      /*
+       *  Range approximation with linear combination 
+       */
+      for (state = 1; state < (range_state > 0 ?
+			       range_state + 1 : wfa->states); state++)
+	 if (simg [state + level * wfa->states])
+	    for (label = 0; label < MAXLABELS; label++)
+	       for (edge = 0; isedge (domain = wfa->into[state][label][edge]);
+		    edge++)
+	       {
+		  if (domain > 0	/* don't allocate memory for state 0 */
+		      && !simg [domain + (level - 1) * wfa->states])
+		  {
+		     simg [domain + (level - 1) * wfa->states]
+			= Calloc (size_of_level (level - 1), sizeof (word_t));
+		     offs [domain + (level - 1) * wfa->states]
+			= width_of_level (level - 1);
+		  }
+	       }
+      
+   }
+
+   *images  = simg;
+   *offsets = offs;
+}
+
+static void
+free_state_images (unsigned max_level, bool_t color, word_t **state_image,
+		   u_word_t *offset, const unsigned *root_state,
+		   unsigned range_state, format_e format, const wfa_t *wfa)
+/*
+ *  Free memory of state images.
+ *  For more details refer to the inverse function 'alloc_state_images()'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	arrays 'state_image' and 'offset' are discarded.
+ */
+{
+   word_t   marker;			/* ptr is required as a marker */
+   unsigned level;
+
+   if (range_state > 0)
+   {
+      state_image [range_state + max_level * wfa->states] = &marker;
+   }
+   else
+   {
+      unsigned state;
+      
+      /*
+       *  Initialize state image array with states at 'max_level'
+       */
+      for (state = wfa->basis_states; state <= root_state [Y]; state++)
+	 if (wfa->level_of_state [state] == max_level)
+	    state_image [state + max_level * wfa->states] = &marker;
+
+      if (color)
+      {
+	 if (format == FORMAT_4_2_0)
+	    level = max_level - 2;
+	 else
+	    level = max_level;
+      
+	 for (; state < wfa->states; state++)
+	    if (wfa->level_of_state [state] == level)
+	       state_image [state + level * wfa->states] = &marker;
+      }
+   }
+   
+   for (level = max_level; level > 0; level--)
+   {
+      int      domain, child;
+      unsigned state, label, edge;
+      /*
+       *  Range approximation with child. 
+       */
+      for (state = 1; state < (range_state > 0 ?
+			       range_state + 1 : wfa->states); state++)
+	 if (state_image [state + level * wfa->states])
+	    for (label = 0; label < MAXLABELS; label++)
+	       if (ischild (child = wfa->tree[state][label]))
+	       {
+		  if (isedge (wfa->into[state][label][0])
+		      && (state_image [child + (level - 1) * wfa->states]
+			  != &marker))
+		     Free (state_image [child + (level - 1) * wfa->states]);
+		  state_image [child + (level - 1) * wfa->states] = &marker;
+	       }
+      /*
+       *  Range approximation with linear combination 
+       */
+      for (state = 1; state < (range_state > 0 ?
+			       range_state + 1 : wfa->states);
+	   state++)
+	 if (state_image [state + level * wfa->states])
+	    for (label = 0; label < MAXLABELS; label++)
+	       for (edge = 0; isedge (domain = wfa->into[state][label][edge]);
+		    edge++)
+		  if (domain > 0	
+		      && (state_image [domain + (level - 1) * wfa->states]
+			  != NULL)
+		      && (state_image [domain + (level - 1) * wfa->states]
+			  != &marker))
+		  {
+		     Free (state_image [domain + (level - 1) * wfa->states]);
+		     state_image [domain + (level - 1) * wfa->states]
+			= &marker;
+		  }
+   }
+   Free (state_image);
+   Free (offset);
+}
+
+static void
+compute_state_images (unsigned max_level, word_t **simg,
+		      const u_word_t *offset, const wfa_t *wfa)
+/*
+ *  Compute all state images of the 'wfa' at level {1, ... , 'max_level'}
+ *  which are marked in the array 'simg' (offsets of state images
+ *  are given by 'offset').
+ *
+ *  Warning: Several optimizations are used in this function making 
+ *  it difficult to understand.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	state images (given by pointers in the array 'state_image')
+ *	are computed.
+ */
+{
+   unsigned level, state;
+     
+   /*
+    *  Copy one-pixel images in case state_image pointer != &final distr.
+    */
+
+   for (state = 1; state < wfa->states; state++)
+      if (simg [state] != NULL)		/* compute image at level 0 */
+	 *simg [state] = (int) (wfa->final_distribution[state] * 8 + .5) * 2;
+
+   /*
+    *  Compute images of states
+    *  Integer arithmetics are used rather than floating point operations.
+    *  'weight' gives the weight in integer notation
+    *  'src', 'dst', and 'idst' are pointers to the source and
+    *  destination pixels (short or integer format), respectively.
+    *  Short format : one operation per register (16 bit mode). 
+    *  Integer format : two operations per register (32 bit mode). 
+    *  'src_offset', 'dst_offset', and 'dst_offset' give the number of
+    *  pixels which have to be omitted when jumping to the next image row.
+    */
+   for (level = 1; level <= max_level; level++) 
+   {
+      unsigned label;
+      unsigned width  = width_of_level (level - 1);
+      unsigned height = height_of_level (level - 1);
+      
+      for (state = 1; state < wfa->states; state++)
+	 if (simg [state + level * wfa->states] != NULL)
+	    for (label = 0; label < MAXLABELS; label++)
+	       if (isedge (wfa->into [state][label][0]))
+	       {
+		  unsigned  edge;
+		  int       domain;
+		  word_t   *range;	/* address of current range */
+		  bool_t    prediction_used; /* ND prediction found ? */
+
+		  /*
+		   *  Compute address of range image
+		   */
+		  if (level & 1)	/* split vertically */
+		  {
+		     range = simg [state + level * wfa->states]
+			     + label * (height_of_level (level - 1)
+					* offset [state
+						 + level * wfa->states]);
+		  }
+		  else			/* split horizontally */
+		  {
+		     range = simg [state + level * wfa->states]
+			     + label * width_of_level (level - 1);
+		  }
+
+		  /*
+		   *  Generate the state images by adding the corresponding 
+		   *  weighted state images:
+		   *  subimage [label] =
+		   *       weight_1 * image_1 + ... + weight_n * image_n
+		   */
+		  if (!ischild (domain = wfa->tree[state][label]))
+		     prediction_used = NO;
+		  else
+		  {
+		     unsigned  y;
+		     word_t   *src;
+		     word_t   *dst;
+		     unsigned  src_offset;
+		     unsigned  dst_offset;
+
+		     prediction_used = YES;
+		     /*
+		      *  Copy child image
+		      */
+		     src        = simg [domain + (level - 1) * wfa->states];
+		     src_offset = offset [domain + (level - 1) * wfa->states] ;
+		     dst        = range;
+		     dst_offset	= offset [state + level * wfa->states];
+		     for (y = height; y; y--)
+		     {
+			memcpy (dst, src, width * sizeof (word_t));
+			src += src_offset;
+			dst += dst_offset;
+		     }
+		  }
+
+		  if (!prediction_used
+		      && isedge (domain = wfa->into[state][label][0]))
+		  {
+		     /*
+		      *  If prediction is not used then the range is
+		      *  filled with the first domain. No addition is needed.
+		      */
+		     edge = 0;
+		     if (domain != 0)
+		     {
+			int	  weight;
+			word_t 	 *src;
+			unsigned  src_offset;
+
+			src        = simg [domain + ((level - 1)
+						     * wfa->states)];
+			src_offset = offset [domain + ((level - 1)
+						       * wfa->states)] - width;
+			weight     = wfa->int_weight [state][label][edge];
+			
+			if (width == 1)	/* can't add two-pixels in a row */
+			{
+			   word_t   *dst;
+			   unsigned  dst_offset;
+			   
+			   dst        = range;
+			   dst_offset = offset [state + level * wfa->states]
+					- width;
+#ifdef HAVE_SIGNED_SHIFT
+			   *dst++ = ((weight * (int) *src++) >> 10) << 1;
+#else 					/* not HAVE_SIGNED_SHIFT */
+			   *dst++ = ((weight * (int) *src++) / 1024) * 2;
+#endif /* not HAVE_SIGNED_SHIFT */
+			   if (height == 2) 
+			   {
+			      src += src_offset;
+			      dst += dst_offset;
+#ifdef HAVE_SIGNED_SHIFT
+			      *dst++ = ((weight * (int) *src++) >> 10) << 1;
+#else /* not HAVE_SIGNED_SHIFT */
+			      *dst++ = ((weight * (int) *src++) / 1024) * 2;
+#endif /* not HAVE_SIGNED_SHIFT */
+			   }
+			}
+			else
+			{
+			   unsigned  y;
+			   int 	    *idst;
+			   unsigned  idst_offset;
+			   
+			   idst        = (int *) range;
+			   idst_offset = (offset [state + level * wfa->states]
+					  - width) / 2;
+			   for (y = height; y; y--)
+			   {
+			      int *comp_dst = idst + (width >> 1);
+			      
+			      for (; idst != comp_dst; )
+ 			      {
+				 int tmp; /* temp. value of adjacent pixels */
+#ifdef HAVE_SIGNED_SHIFT
+#	ifndef WORDS_BIGENDIAN
+                                 tmp = (((weight * (int) src [1]) >> 10) << 17)
+				       | (((weight * (int) src [0]) >> 9)
+					  & 0xfffe);
+#	else /* not WORDS_BIGENDIAN */
+                                 tmp = (((weight * (int) src [0]) >> 10) << 17)
+				       | (((weight * (int) src [1]) >> 9)
+					  & 0xfffe);
+#	endif /* not WORDS_BIGENDIAN */
+#else /* not HAVE_SIGNED_SHIFT */
+#	ifndef WORDS_BIGENDIAN
+                                 tmp = (((weight * (int) src [1]) / 1024)
+					* 131072)
+				       | (((weight * (int) src [0])/ 512)
+					  & 0xfffe);
+#	else /* not WORDS_BIGENDIAN */
+                                 tmp = (((weight * (int) src [0]) / 1024)
+					* 131072)
+				       | (((weight * (int) src [1]) / 512)
+					  & 0xfffe);
+#	endif /* not WORDS_BIGENDIAN */
+#endif /* not HAVE_SIGNED_SHIFT */
+				 src    +=  2;
+				 *idst++ = tmp & 0xfffefffe;
+			      }
+			      src  += src_offset;
+			      idst += idst_offset;
+			   }
+			}
+		     }
+		     else
+		     {
+			int weight = (int) (wfa->weight[state][label][edge]
+					    * wfa->final_distribution[0]
+					    * 8 + .5) * 2;
+			/*
+			 *  Range needs domain 0
+			 *  (the constant function f(x, y) = 1),
+			 *  hence a faster algorithm is used.
+			 */
+			if (width == 1)	/* can't add two-pixels in a row */
+			{
+			   word_t   *dst;
+			   unsigned  dst_offset;
+			   
+			   dst        = range;
+			   dst_offset = offset [state + level * wfa->states]
+					- width;
+			   
+			   *dst++ = weight;
+			   if (height == 2)
+			   {
+			      dst += dst_offset;
+			      *dst++ = weight;
+			   }
+			}
+			else
+			{
+			   unsigned  x, y;
+			   int 	    *idst;
+			   unsigned  idst_offset;
+			   
+			   weight      = (weight * 65536) | (weight & 0xffff);
+			   idst	       = (int *) range;
+			   idst_offset = offset [state + level * wfa->states]
+					 / 2;
+			   for (x = width >> 1; x; x--)
+			      *idst++ = weight & 0xfffefffe;
+			   idst += (offset [state + level * wfa->states]
+				    - width) / 2;
+
+			   for (y = height - 1; y; y--)
+			   {
+			      memcpy (idst, idst - idst_offset,
+				      width * sizeof (word_t));
+			      idst += idst_offset;
+			   }
+			}
+		     }
+		     edge = 1;
+		  }
+		  else
+		     edge = 0;
+		  
+		  /*
+		   *  Add remaining weighted domain images to current range
+		   */
+		  for (; isedge (domain = wfa->into[state][label][edge]);
+		       edge++)
+		  {
+		     if (domain != 0)
+		     {
+			word_t 	 *src;
+			unsigned  src_offset;
+			int	  weight;
+
+			src        = simg [domain + (level - 1) * wfa->states];
+			src_offset = offset [domain + ((level - 1)
+						       * wfa->states)] - width;
+			weight     = wfa->int_weight [state][label][edge];
+			
+			if (width == 1)	/* can't add two-pixels in a row */
+			{
+			   word_t   *dst;
+			   unsigned  dst_offset;
+			   
+			   dst        = range;
+			   dst_offset = offset [state + level * wfa->states]
+					- width;
+
+#ifdef HAVE_SIGNED_SHIFT
+			   *dst++ += ((weight * (int) *src++) >> 10) << 1;
+#else /* not HAVE_SIGNED_SHIFT */
+			   *dst++ += ((weight * (int) *src++) / 1024) * 2;
+#endif /* not HAVE_SIGNED_SHIFT */
+			   if (height == 2) 
+			   {
+			      src += src_offset;
+			      dst += dst_offset;
+#ifdef HAVE_SIGNED_SHIFT
+			      *dst++ += ((weight * (int) *src++) >> 10) << 1;
+#else /* not HAVE_SIGNED_SHIFT */
+			      *dst++ += ((weight * (int) *src++) / 1024) * 2;
+#endif /* not HAVE_SIGNED_SHIFT */
+			   }
+			}
+			else
+			{
+			   int 	    *idst;
+			   unsigned  idst_offset;
+			   unsigned  y;
+			   
+			   idst        = (int *) range;
+			   idst_offset = (offset [state + level * wfa->states]
+					  - width) / 2;
+			   
+			   for (y = height; y; y--)
+			   {
+			      int *comp_dst = idst + (width >> 1);
+			      
+			      for (; idst != comp_dst;)
+ 			      {
+				 int tmp; /* temp. value of adjacent pixels */
+#ifdef HAVE_SIGNED_SHIFT
+#	ifndef WORDS_BIGENDIAN
+                                 tmp = (((weight * (int) src [1]) >> 10) << 17)
+				       | (((weight * (int) src [0]) >> 9)
+					  & 0xfffe);
+#	else /* not WORDS_BIGENDIAN */
+                                 tmp = (((weight * (int)src [0]) >> 10) << 17)
+				       | (((weight * (int)src [1]) >> 9)
+					  & 0xfffe);
+#	endif /* not WORDS_BIGENDIAN */
+#else /* not HAVE_SIGNED_SHIFT */
+#	ifndef WORDS_BIGENDIAN
+                                 tmp = (((weight * (int) src [1]) / 1024)
+					* 131072)
+				       | (((weight * (int) src [0])/ 512)
+					  & 0xfffe);
+#	else /* not WORDS_BIGENDIAN */
+                                 tmp = (((weight * (int) src [0]) / 1024)
+					* 131072)
+				       | (((weight * (int) src [1])/ 512)
+					  & 0xfffe);
+#	endif /* not WORDS_BIGENDIAN */
+#endif /* not HAVE_SIGNED_SHIFT */
+				 src +=  2;
+				 *idst = (*idst + tmp) & 0xfffefffe;
+				 idst++;
+			      }
+			      src  += src_offset;
+			      idst += idst_offset;
+			   }
+			}
+		     }
+		     else
+		     {
+			int weight = (int) (wfa->weight[state][label][edge]
+					    * wfa->final_distribution[0]
+					    * 8 + .5) * 2;
+			/*
+			 *  Range needs domain 0
+			 *  (the constant function f(x, y) = 1),
+			 *  hence a faster algorithm is used.
+			 */
+			if (width == 1)	/* can't add two-pixels in a row */
+			{
+			   word_t   *dst;
+			   unsigned  dst_offset;
+			   
+			   dst        = range;
+			   dst_offset = offset [state + level * wfa->states]
+					- width;
+			   
+			   *dst++ += weight;
+			   if (height == 2)
+			   {
+			      dst    += dst_offset;
+			      *dst++ += weight;
+			   }
+			}
+			else
+			{
+			   int 	    *idst;
+			   unsigned  idst_offset;
+			   unsigned  y;
+			   
+			   weight      = (weight * 65536) | (weight & 0xffff);
+			   idst	       = (int *) range;
+			   idst_offset = (offset [state + level * wfa->states]
+					  - width) /2;
+			   
+			   for (y = height; y; y--)
+			   {
+			      int *comp_dst = idst + (width >> 1);
+			      
+			      for (; idst != comp_dst; )
+			      {
+				 *idst = (*idst + weight) & 0xfffefffe;
+                                 idst++;
+			      }
+			      idst += idst_offset;
+			   }
+			}
+		     }
+		  }
+	       } 
+   }
+}
+
+static word_t *
+duplicate_state_image (const word_t *domain, unsigned offset, unsigned level)
+/*
+ *  Allocate new memory block 'pixels' and copy pixel values of 'domain' 
+ *  (size and pixel offset are given by 'level' and 'offset')
+ *  to the lock 'pixels'.
+ *
+ *  Return value:
+ *	pointer to the new domain block
+ */
+{
+   word_t *dst, *pixels;
+   int	   y, n;
+
+   dst = pixels = Calloc (size_of_level (level), sizeof (word_t));
+
+   if (domain)
+      for (y = height_of_level (level); y; y--)
+      {
+	 memcpy (dst, domain, width_of_level (level) * sizeof (word_t));
+	 dst    += width_of_level (level);
+	 domain += offset;
+      }
+   else					/* state 0 */
+      for (n = size_of_level (level); n; n--)
+	 *dst++ = (int) (128 * 8 + .5) * 2;
+
+   return pixels;
+}
diff --git a/converter/other/fiasco/codec/decoder.h b/converter/other/fiasco/codec/decoder.h
new file mode 100644
index 00000000..8cd211e0
--- /dev/null
+++ b/converter/other/fiasco/codec/decoder.h
@@ -0,0 +1,70 @@
+/*
+ *  decode.h
+ *		
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/10/22 10:44:48 $
+ *  $Author: hafner $
+ *  $Revision: 5.3 $
+ *  $State: Exp $
+ */
+
+#ifndef _DECODE_H
+#define _DECODE_H
+
+#include "types.h"
+#include "image.h"
+#include "wfa.h"
+
+typedef struct video
+{
+   unsigned  future_display;		/* number of a future frame */
+   unsigned  display;			/* current display number */
+   image_t  *frame;			/* current frame */
+   image_t  *sframe;			/* current smoothed frame */
+   image_t  *future;			/* future reference */
+   image_t  *sfuture;			/* future (smmothed) reference */
+   image_t  *past ;			/* past reference */
+   wfa_t    *wfa;			/* current wfa */
+   wfa_t    *wfa_future;		/* future wfa */
+   wfa_t    *wfa_past;			/* past wfa */
+} video_t;
+
+typedef struct dectimer
+{
+   unsigned int	input [3];
+   unsigned int	preprocessing [3];
+   unsigned int	decoder [3];
+   unsigned int	cleanup [3];
+   unsigned int	motion [3];
+   unsigned int	smooth [3];
+   unsigned int	display [3];
+   unsigned int	frames [3];
+} dectimer_t;
+
+image_t *
+get_next_frame (bool_t store_wfa, int enlarge_factor,
+		int smoothing, const char *reference_frame,
+		format_e format, video_t *video, dectimer_t *timer,
+		wfa_t *orig_wfa, bitfile_t *input);
+image_t *
+decode_image (unsigned orig_width, unsigned orig_height, format_e format,
+	      unsigned *dec_timer, const wfa_t *wfa);
+word_t *
+decode_range (unsigned range_state, unsigned range_label, unsigned range_level,
+	      word_t **domain, wfa_t *wfa);
+image_t *
+decode_state (unsigned state, unsigned level, wfa_t *wfa);
+void
+smooth_image (unsigned sf, const wfa_t *wfa, image_t *image);
+video_t *
+alloc_video (bool_t store_wfa);
+void
+free_video (video_t *video);
+
+#endif /* not _DECODE_H */
diff --git a/converter/other/fiasco/codec/dfiasco.c b/converter/other/fiasco/codec/dfiasco.c
new file mode 100644
index 00000000..1cdfc672
--- /dev/null
+++ b/converter/other/fiasco/codec/dfiasco.c
@@ -0,0 +1,398 @@
+/*
+ *  dfiasco.c:		Decoder public interface
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+ 
+/*
+ *  $Date: 2000/10/28 17:39:30 $
+ *  $Author: hafner $
+ *  $Revision: 5.7 $
+ *  $State: Exp $
+ */
+
+#include <string.h>
+
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "dfiasco.h"
+#include "wfa.h"
+#include "read.h"
+#include "misc.h"
+#include "bit-io.h"
+#include "decoder.h"
+#include "options.h"
+#include "wfalib.h"
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static dfiasco_t *
+cast_dfiasco (fiasco_decoder_t *dfiasco);
+static void
+free_dfiasco (dfiasco_t *dfiasco);
+static dfiasco_t *
+alloc_dfiasco (wfa_t *wfa, video_t *video, bitfile_t *input,
+	       int enlarge_factor, int smoothing, format_e image_format);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+fiasco_decoder_t *
+fiasco_decoder_new (const char *filename, const fiasco_d_options_t *options)
+{
+   try
+   {
+      bitfile_t        	 *input;	/* pointer to WFA FIASCO stream */
+      wfa_t            	 *wfa;		/* wfa structure */
+      video_t          	 *video;	/* information about decoder state */
+      const d_options_t  *dop;		/* decoder additional options */
+      dfiasco_t	       	 *dfiasco;	/* decoder internal state */
+      fiasco_decoder_t 	 *decoder;	/* public interface to decoder */
+      fiasco_d_options_t *default_options = NULL;
+
+      if (options)
+      {
+	 dop = cast_d_options ((fiasco_d_options_t *) options);
+	 if (!dop)
+	    return NULL;
+      }
+      else
+      {
+	 default_options = fiasco_d_options_new ();
+	 dop 		 = cast_d_options (default_options);
+      }
+      
+      wfa   = alloc_wfa (NO);
+      video = alloc_video (NO);
+      input = open_wfa (filename, wfa->wfainfo);
+      read_basis (wfa->wfainfo->basis_name, wfa);
+
+      decoder 	       	   = Calloc (1, sizeof (fiasco_decoder_t));
+      decoder->delete  	   = fiasco_decoder_delete;
+      decoder->write_frame = fiasco_decoder_write_frame;
+      decoder->get_frame   = fiasco_decoder_get_frame;
+      decoder->get_length  = fiasco_decoder_get_length;
+      decoder->get_rate    = fiasco_decoder_get_rate;
+      decoder->get_width   = fiasco_decoder_get_width;
+      decoder->get_height  = fiasco_decoder_get_height;
+      decoder->get_title   = fiasco_decoder_get_title;
+      decoder->get_comment = fiasco_decoder_get_comment;
+      decoder->is_color    = fiasco_decoder_is_color;
+
+      decoder->private = dfiasco
+		       = alloc_dfiasco (wfa, video, input,
+					dop->magnification,
+					dop->smoothing,
+					dop->image_format);
+   
+      if (default_options)
+	 fiasco_d_options_delete (default_options);
+      if (dfiasco->enlarge_factor >= 0)
+      {
+	 int 	       n;
+	 unsigned long pixels = wfa->wfainfo->width * wfa->wfainfo->height;
+
+	 for (n = 1; n <= (int) dfiasco->enlarge_factor; n++)
+	 {
+	    if (pixels << (n << 1) > 2048 * 2048)
+	    {
+	       set_error (_("Magnifaction factor `%d' is too large. "
+			    "Maximium value is %d."),
+			  dfiasco->enlarge_factor, max (0, n - 1));
+	       fiasco_decoder_delete (decoder);
+	       return NULL;
+	    }
+	 }
+      }
+      else
+      {
+	 int n;
+
+	 for (n = 0; n <= (int) - dfiasco->enlarge_factor; n++)
+	 {
+	    if (wfa->wfainfo->width >> n < 32
+		|| wfa->wfainfo->height >> n < 32)
+	    {
+	       set_error (_("Magnifaction factor `%d' is too small. "
+			    "Minimum value is %d."),
+			  dfiasco->enlarge_factor, - max (0, n - 1));
+	       fiasco_decoder_delete (decoder);
+	       return NULL;
+	    }
+	 }
+      }
+      return (fiasco_decoder_t *) decoder;
+   }
+   catch
+   {
+      return NULL;
+   }
+}
+
+int
+fiasco_decoder_write_frame (fiasco_decoder_t *decoder,
+			    const char *filename)
+{
+   dfiasco_t *dfiasco = cast_dfiasco (decoder);
+   
+   if (!dfiasco)
+      return 0;
+   else
+   {
+      try
+      {
+	 image_t *frame = get_next_frame (NO, dfiasco->enlarge_factor,
+					  dfiasco->smoothing, NULL,
+					  FORMAT_4_4_4, dfiasco->video, NULL,
+					  dfiasco->wfa, dfiasco->input);
+	 write_image (filename, frame);
+      }
+      catch
+      {
+	 return 0;
+      }
+      return 1;
+   }
+}
+
+fiasco_image_t *
+fiasco_decoder_get_frame (fiasco_decoder_t *decoder)
+{
+   dfiasco_t *dfiasco = cast_dfiasco (decoder);
+   
+   if (!dfiasco)
+      return NULL;
+   else
+   {
+      try
+      {
+	 fiasco_image_t *image = Calloc (1, sizeof (fiasco_image_t));
+	 image_t 	*frame = get_next_frame (NO, dfiasco->enlarge_factor,
+						 dfiasco->smoothing, NULL,
+						 dfiasco->image_format,
+						 dfiasco->video, NULL,
+						 dfiasco->wfa, dfiasco->input);
+
+	 frame->reference_count++;	/* for motion compensation */
+	 image->private    = frame;
+	 image->delete     = fiasco_image_delete;
+	 image->get_width  = fiasco_image_get_width;
+	 image->get_height = fiasco_image_get_height;
+	 image->is_color   = fiasco_image_is_color;
+	 
+	 return image;
+      }
+      catch
+      {
+	 return NULL;
+      }
+   }
+}
+
+unsigned
+fiasco_decoder_get_length (fiasco_decoder_t *decoder)
+{
+   dfiasco_t *dfiasco = cast_dfiasco (decoder);
+   
+   if (!dfiasco)
+      return 0;
+   else
+      return dfiasco->wfa->wfainfo->frames;
+}
+
+unsigned
+fiasco_decoder_get_rate (fiasco_decoder_t *decoder)
+{
+   dfiasco_t *dfiasco = cast_dfiasco (decoder);
+   
+   if (!dfiasco)
+      return 0;
+   else
+      return dfiasco->wfa->wfainfo->fps;
+}
+
+unsigned
+fiasco_decoder_get_width (fiasco_decoder_t *decoder)
+{
+   dfiasco_t *dfiasco = cast_dfiasco (decoder);
+
+   if (!dfiasco)
+      return 0;
+   else
+   {
+      unsigned width;
+      
+      if (dfiasco->enlarge_factor >= 0)
+	 width = dfiasco->wfa->wfainfo->width << dfiasco->enlarge_factor;
+      else
+	 width = dfiasco->wfa->wfainfo->width >> - dfiasco->enlarge_factor;
+      
+      return width & 1 ? width + 1 : width;
+   }
+}
+
+unsigned
+fiasco_decoder_get_height (fiasco_decoder_t *decoder)
+{
+   dfiasco_t *dfiasco = cast_dfiasco (decoder);
+
+   if (!dfiasco)
+      return 0;
+   else
+   {
+      unsigned height;
+      
+      if (dfiasco->enlarge_factor >= 0)
+	 height = dfiasco->wfa->wfainfo->height << dfiasco->enlarge_factor;
+      else
+	 height = dfiasco->wfa->wfainfo->height >> - dfiasco->enlarge_factor;
+
+      return height & 1 ? height + 1 : height;
+   }
+}
+
+const char *
+fiasco_decoder_get_title (fiasco_decoder_t *decoder)
+{
+   dfiasco_t *dfiasco = cast_dfiasco (decoder);
+
+   if (!dfiasco)
+      return NULL;
+   else
+      return dfiasco->wfa->wfainfo->title;
+}
+
+const char *
+fiasco_decoder_get_comment (fiasco_decoder_t *decoder)
+{
+   dfiasco_t *dfiasco = cast_dfiasco (decoder);
+
+   if (!dfiasco)
+      return NULL;
+   else
+      return dfiasco->wfa->wfainfo->comment;
+}
+
+int
+fiasco_decoder_is_color (fiasco_decoder_t *decoder)
+{
+   dfiasco_t *dfiasco = cast_dfiasco (decoder);
+
+   if (!dfiasco)
+      return 0;
+   else
+      return dfiasco->wfa->wfainfo->color;
+}
+
+int
+fiasco_decoder_delete (fiasco_decoder_t *decoder)
+{
+   dfiasco_t *dfiasco = cast_dfiasco (decoder);
+   
+   if (!dfiasco)
+      return 1;
+   
+   try
+   {
+      free_wfa (dfiasco->wfa);
+      free_video (dfiasco->video);
+      close_bitfile (dfiasco->input);
+      strcpy (dfiasco->id, " ");
+      free_dfiasco(dfiasco);
+      Free (decoder);
+   }
+   catch
+   {
+      return 0;
+   }
+
+   return 1;
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static dfiasco_t *
+alloc_dfiasco (wfa_t *wfa, video_t *video, bitfile_t *input,
+	       int enlarge_factor, int smoothing, format_e image_format)
+/*
+ *  FIASCO decoder constructor:
+ *  Initialize decoder structure.
+ *
+ *  Return value:
+ *	pointer to the new decoder structure
+ */
+{
+   dfiasco_t *dfiasco = Calloc (1, sizeof (dfiasco_t));
+
+   strcpy (dfiasco->id, "DFIASCO");
+   
+   dfiasco->wfa 	   = wfa;
+   dfiasco->video 	   = video;
+   dfiasco->input 	   = input;
+   dfiasco->enlarge_factor = enlarge_factor;
+   dfiasco->smoothing  	   = smoothing;
+   dfiasco->image_format   = image_format;
+   
+   return dfiasco;
+}
+
+static void
+free_dfiasco (dfiasco_t *dfiasco)
+/*
+ *  FIASCO decoder destructor:
+ *  Free memory of given 'decoder' struct.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'video' struct is discarded.
+ */
+{
+   Free (dfiasco);
+}
+
+static dfiasco_t *
+cast_dfiasco (fiasco_decoder_t *dfiasco)
+/*
+ *  Cast pointer `dfiasco' to type dfiasco_t.
+ *  Check whether `dfiasco' is a valid object of type dfiasco_t.
+ *
+ *  Return value:
+ *	pointer to dfiasco_t struct on success
+ *      NULL otherwise
+ */
+{
+   dfiasco_t *this = (dfiasco_t *) dfiasco->private;
+   if (this)
+   {
+      if (!streq (this->id, "DFIASCO"))
+      {
+	 set_error (_("Parameter `dfiasco' doesn't match required type."));
+	 return NULL;
+      }
+   }
+   else
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "dfiasco");
+   }
+
+   return this;
+}
diff --git a/converter/other/fiasco/codec/dfiasco.h b/converter/other/fiasco/codec/dfiasco.h
new file mode 100644
index 00000000..bcc3c7f9
--- /dev/null
+++ b/converter/other/fiasco/codec/dfiasco.h
@@ -0,0 +1,38 @@
+/*
+ *  dfiasco.h
+ *		
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/07/15 18:00:53 $
+ *  $Author: hafner $
+ *  $Revision: 5.2 $
+ *  $State: Exp $
+ */
+
+#ifndef _DFIASCO_H
+#define _DFIASCO_H
+
+#include "types.h"
+#include "bit-io.h"
+#include "decoder.h"
+#include "image.h"
+#include "wfa.h"
+
+typedef struct dfiasco
+{
+   char       id [8];
+   wfa_t     *wfa;
+   video_t   *video;
+   bitfile_t *input;
+   int	      enlarge_factor;
+   int        smoothing;
+   format_e   image_format;
+} dfiasco_t;
+
+#endif /* not _DFIASCO_H */
+
diff --git a/converter/other/fiasco/codec/domain-pool.c b/converter/other/fiasco/codec/domain-pool.c
new file mode 100644
index 00000000..09f854a6
--- /dev/null
+++ b/converter/other/fiasco/codec/domain-pool.c
@@ -0,0 +1,986 @@
+/*
+ *  domain-pool.c:  Domain pool management (probability model)
+ *
+ *  Written by:     Ullrich Hafner
+ *      
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include <math.h>
+
+#if STDC_HEADERS
+#   include <stdlib.h>
+#endif /* not STDC_HEADERS */
+
+#if HAVE_STRING_H
+#   include <string.h>
+#else /* not HAVE_STRING_H */
+#   include <strings.h>
+#endif /* not HAVE_STRING_H */
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "misc.h"
+#include "cwfa.h"
+#include "wfalib.h"
+#include "domain-pool.h"
+
+/*
+ *  Domain pool model interface:
+ *  Implementing the domain pool model interface requires the
+ *  following steps: 
+ *  - Add a constructor that initializes the domain_pool_t structure
+ *  - Allocate new model with default_alloc() 
+ *  - Fill the dp_array_t domain_pools array with constructor and name
+ *  - Write code for methods bits() and generate()
+ *  - Either use default functions for remaining methods or override them
+ *  The new model is automatically registered at the command line.
+ */
+
+/*****************************************************************************
+                                          
+                  local variables
+                  
+*****************************************************************************/
+
+static real_t *matrix_0 = NULL;
+static real_t *matrix_1 = NULL;
+
+/*****************************************************************************
+                        non-adaptive domain pool
+*****************************************************************************/
+
+static void
+qac_chroma (unsigned max_domains, const wfa_t *wfa, void *model);
+static bool_t
+qac_append (unsigned new_state, unsigned level, const wfa_t *wfa, void *model);
+static void
+qac_update (const word_t *domains, const word_t *used_domains,
+            unsigned level, int y_state, const wfa_t *wfa, void *model);
+static real_t
+qac_bits (const word_t *domains, const word_t *used_domains,
+          unsigned level, int y_state, const wfa_t *wfa, const void *model);
+static word_t *
+qac_generate (unsigned level, int y_state, const wfa_t *wfa,
+              const void *model);
+static void *
+qac_model_duplicate (const void *src);
+static void
+qac_model_free (void *model);
+static void *
+qac_model_alloc (unsigned max_domains);
+static domain_pool_t *
+alloc_qac_domain_pool (unsigned max_domains, unsigned max_edges,
+                       const wfa_t *wfa);
+
+/*****************************************************************************
+              run length encoding pool
+*****************************************************************************/
+
+static domain_pool_t *
+alloc_rle_no_chroma_domain_pool (unsigned max_domains, unsigned max_edges,
+                                 const wfa_t *wfa);
+static void
+rle_chroma (unsigned max_domains, const wfa_t *wfa, void *model);
+static bool_t
+rle_append (unsigned new_state, unsigned level, const wfa_t *wfa, void *model);
+static void
+rle_update (const word_t *domains, const word_t *used_domains,
+            unsigned level, int y_state, const wfa_t *wfa, void *model);
+static real_t
+rle_bits (const word_t *domains, const word_t *used_domains,
+          unsigned level, int y_state, const wfa_t *wfa, const void *model);
+static word_t *
+rle_generate (unsigned level, int y_state, const wfa_t *wfa,
+              const void *model);
+static void *
+rle_model_duplicate (const void *src);
+static void
+rle_model_free (void *model);
+static void *
+rle_model_alloc (unsigned max_domains);
+static domain_pool_t *
+alloc_rle_domain_pool (unsigned max_domains, unsigned max_edges,
+                       const wfa_t *wfa);
+
+/*****************************************************************************
+              const domain pool
+*****************************************************************************/
+
+static real_t
+const_bits (const word_t *domains, const word_t *used_domains,
+            unsigned level, int y_state, const wfa_t *wfa, const void *model);
+static word_t *
+const_generate (unsigned level, int y_state, const wfa_t *wfa,
+                const void *model);
+static domain_pool_t *
+alloc_const_domain_pool (unsigned max_domains, unsigned max_edges,
+                         const wfa_t *wfa);
+
+/*****************************************************************************
+              basis domain pool
+*****************************************************************************/
+
+static domain_pool_t *
+alloc_basis_domain_pool (unsigned max_domains, unsigned max_edges,
+                         const wfa_t *wfa);
+
+/*****************************************************************************
+              uniform distribution pool
+*****************************************************************************/
+
+static real_t
+uniform_bits (const word_t *domains, const word_t *used_domains,
+              unsigned level, int y_state, const wfa_t *wfa,
+              const void *model);
+static word_t *
+uniform_generate (unsigned level, int y_state, const wfa_t *wfa,
+                  const void *model);
+static domain_pool_t *
+alloc_uniform_domain_pool (unsigned max_domains, unsigned max_edges,
+                           const wfa_t *wfa);
+
+/*****************************************************************************
+              default functions
+*****************************************************************************/
+
+static void
+init_matrix_probabilities (void);
+static void
+default_chroma (unsigned max_domains, const wfa_t *wfa, void *model);
+static bool_t
+default_append (unsigned new_state, unsigned level,
+                const wfa_t *wfa, void *model);
+static void
+default_update (const word_t *domains, const word_t *used_domains,
+                unsigned level, int y_state, const wfa_t *wfa, void *model);
+static void
+default_free (domain_pool_t *pool);
+static void
+default_model_free (void *model);
+static void *
+default_model_alloc (unsigned max_domains);
+static void *
+default_model_duplicate (const void *src);
+static domain_pool_t *
+default_alloc (void);
+
+/*****************************************************************************
+
+                public code
+  
+*****************************************************************************/
+
+typedef struct dp_array
+{
+    const char       *identifier;
+    domain_pool_t *(*function) (unsigned max_domains, unsigned max_edges,
+                                const wfa_t *wfa);
+} dp_array_t;
+
+dp_array_t const domain_pools[] = {{"adaptive", alloc_qac_domain_pool},
+                                   {"constant",   alloc_const_domain_pool}, 
+                                   {"basis",      alloc_basis_domain_pool},
+                                   {"uniform",    alloc_uniform_domain_pool},
+                                   {"rle",        alloc_rle_domain_pool},
+                                   {"rle-no-chroma",  alloc_rle_no_chroma_domain_pool},
+                                   {NULL,     NULL}};
+
+domain_pool_t *
+alloc_domain_pool (const char *domain_pool_name, unsigned max_domains,
+                   unsigned max_edges, const wfa_t *wfa)
+/*
+ *  Allocate a new domain pool identified by the string
+ *  'domain_pool_name'.  Maximum number of domain images (each one
+ *  represented by one state of the given 'wfa') is specified by
+ *  'max_domains'. 
+ * 
+ *  Return value:
+ *  pointer to the allocated domain pool
+ *
+ *  Note:
+ *      refer to 'domain-pool.h' for a short description of the member functions.
+ */
+{
+    unsigned n;
+   
+    if (!max_domains)
+    {
+        warning ("Can't generate empty domain pool. "
+                 "Using at least DC component.");
+        max_domains = 1;
+    }
+   
+    for (n = 0; domain_pools [n].identifier; n++) /* step through all id's */
+        if (strcaseeq (domain_pools [n].identifier, domain_pool_name)) 
+            return domain_pools [n].function (max_domains, max_edges, wfa);
+
+    warning ("Can't initialize domain pool '%s'. Using default value '%s'.",
+             domain_pool_name, domain_pools [0].identifier);
+
+    return domain_pools [0].function (max_domains, max_edges, wfa);
+}
+
+/*****************************************************************************
+
+                private code
+  
+*****************************************************************************/
+
+/*****************************************************************************
+              adaptive domain pool
+*****************************************************************************/
+
+typedef struct qac_model
+{
+    word_t   *index;         /* probability of domains */
+    word_t   *states;            /* mapping states -> domains */
+    u_word_t  y_index;           /* pointer to prob of Y domain */
+    u_word_t  n;             /* number of domains in the pool */
+    u_word_t  max_domains;       /* max. number of domains */
+} qac_model_t;
+
+static domain_pool_t *
+alloc_qac_domain_pool (unsigned max_domains, unsigned max_edges,
+                       const wfa_t *wfa)
+/*
+ *  Domain pool with state images {0, ..., 'max_domains').
+ *  Underlying probability model: quasi arithmetic coding of columns.
+ */
+{
+    domain_pool_t *pool;
+    unsigned   state;
+   
+    pool                  = default_alloc ();
+    pool->model           = qac_model_alloc (max_domains);
+    pool->generate        = qac_generate;
+    pool->bits            = qac_bits;
+    pool->update          = qac_update;
+    pool->append          = qac_append;
+    pool->chroma      = qac_chroma;
+    pool->model_free      = qac_model_free;
+    pool->model_duplicate = qac_model_duplicate;
+   
+    for (state = 0; state < wfa->basis_states; state++)
+        if (usedomain (state, wfa))
+            qac_append (state, -1, wfa, pool->model);
+
+    return pool;
+}
+
+static void *
+qac_model_alloc (unsigned max_domains)
+{
+    qac_model_t *model;
+
+    init_matrix_probabilities ();
+
+    model          = Calloc (1, sizeof (qac_model_t));
+    model->index       = Calloc (max_domains, sizeof (word_t));
+    model->states      = Calloc (max_domains, sizeof (word_t));
+    model->y_index     = 0;
+    model->n       = 0;
+    model->max_domains = max_domains;
+
+    return model;
+}
+
+static void
+qac_model_free (void *model)
+{
+    Free (((qac_model_t *) model)->index);
+    Free (((qac_model_t *) model)->states);
+    Free (model);
+}
+
+static void *
+qac_model_duplicate (const void *src)
+{
+    qac_model_t       *qdst;
+    const qac_model_t *qsrc = (qac_model_t *) src;
+
+    qdst      = qac_model_alloc (qsrc->max_domains);
+    qdst->y_index = qsrc->y_index;
+    qdst->n       = qsrc->n;
+   
+    memcpy (qdst->index, qsrc->index, qsrc->n * sizeof (word_t));
+    memcpy (qdst->states, qsrc->states, qsrc->n * sizeof (word_t));
+
+    return qdst;
+}
+
+static word_t *
+qac_generate (unsigned level, int y_state, const wfa_t *wfa, const void *model)
+{
+    word_t      *domains;
+    unsigned n;
+    qac_model_t *qac_model     = (qac_model_t *) model;
+    bool_t   y_state_is_domain = NO;
+
+    if (y_state >= 0 && !usedomain (y_state, wfa)) /* don't use y-state */
+        y_state = -1;
+   
+    domains = Calloc (qac_model->n + 2, sizeof (word_t));
+
+    memcpy (domains, qac_model->states, qac_model->n * sizeof (word_t));
+
+    for (n = 0; n < qac_model->n; n++)
+        if (domains [n] == y_state)   /* match */
+            y_state_is_domain = YES;       
+
+    if (y_state_is_domain)
+        domains [qac_model->n] = -1;  /* end marker */
+    else
+    {
+        domains [qac_model->n]     = y_state; /* additional y-state */
+        domains [qac_model->n + 1] = -1;  /* end marker */
+    }
+
+    return domains;
+}
+
+static real_t
+qac_bits (const word_t *domains, const word_t *used_domains,
+          unsigned level, int y_state, const wfa_t *wfa, const void *model)
+{
+    int      domain;         /* counter */
+    real_t   bits      = 0;      /* bit rate R */
+    qac_model_t *qac_model = (qac_model_t *) model; /* probability model */
+
+    if (y_state >= 0 && !usedomain (y_state, wfa)) /* don't use y-state */
+        y_state = -1;
+
+    for (domain = 0; domain < qac_model->n; domain++)
+        if (qac_model->states [domain] != y_state)
+            bits += matrix_0 [qac_model->index [domain]];
+    if (y_state >= 0)
+        bits += matrix_0 [qac_model->y_index];
+   
+    if (used_domains != NULL)
+    {
+        unsigned edge;
+      
+        for (edge = 0; isedge (domain = used_domains [edge]); edge++)
+            if (domains [domain] == y_state)
+            {
+                bits -= matrix_0 [qac_model->y_index];
+                bits += matrix_1 [qac_model->y_index];
+            }
+            else
+            {
+                bits -= matrix_0 [qac_model->index [domain]];
+                bits += matrix_1 [qac_model->index [domain]];
+            }
+    } 
+   
+    return bits;
+}
+
+static void
+qac_update (const word_t *domains, const word_t *used_domains,
+            unsigned level, int y_state, const wfa_t *wfa, void *model)
+{
+    int      domain;
+    unsigned edge;
+    bool_t   used_y_state      = NO;
+    qac_model_t *qac_model     = (qac_model_t *) model;
+    bool_t   y_state_is_domain = NO;
+   
+    if (y_state >= 0 && !usedomain (y_state, wfa)) /* don't use y-state */
+        y_state = -1;
+
+    for (domain = 0; domain < qac_model->n; domain++)
+    {
+        qac_model->index [domain]++;  /* mark domains unused. */
+        if (qac_model->states [domain] == y_state) /* match */
+            y_state_is_domain = YES;       
+    }
+   
+    for (edge = 0; isedge (domain = used_domains [edge]); edge++)
+        if (domains [domain] == y_state) /* chroma coding */
+        {
+            if (y_state_is_domain)
+                qac_model->index [domain]--; /* undo */
+            qac_model->y_index >>= 1;
+            used_y_state = YES;
+        }
+        else              /* luminance coding */
+        {
+            qac_model->index [used_domains [edge]]--; /* undo */
+            qac_model->index [used_domains [edge]] >>= 1;      
+        }
+
+    if (y_state >= 0 && !used_y_state)
+        qac_model->y_index++;     /* update y-state model */
+   
+    for (domain = 0; domain < qac_model->n; domain++)
+        if (qac_model->index [domain] > 1020) /* check for overflow */
+            qac_model->index [domain] = 1020; 
+    if (qac_model->y_index > 1020)   /* check for overflow */
+        qac_model->y_index = 1020; 
+}
+
+static bool_t
+qac_append (unsigned new_state, unsigned level, const wfa_t *wfa, void *model)
+{
+    qac_model_t  *qac_model = (qac_model_t *) model; /* probability model */
+   
+    if (qac_model->n >= qac_model->max_domains)
+        return NO;            /* don't use state in domain pool */
+    else
+    {
+        qac_model->index [qac_model->n]
+            = qac_model->n > 0 ? qac_model->index [qac_model->n - 1] : 0;
+        qac_model->states [qac_model->n] = new_state;
+        qac_model->n++;
+
+        return YES;           /* state will be used in domain pool */
+    }
+}
+
+static void
+qac_chroma (unsigned max_domains, const wfa_t *wfa, void *model)
+{
+    qac_model_t *qac_model = (qac_model_t *) model; /* probability model */
+   
+    if (max_domains < qac_model->n)  /* choose most probable domains */
+    {
+        word_t   *domains;
+        unsigned  n, new, old;
+        word_t   *states = Calloc (max_domains, sizeof (word_t));
+        word_t   *index  = Calloc (max_domains, sizeof (word_t));
+   
+        domains = compute_hits (wfa->basis_states, wfa->states - 1,
+                                max_domains, wfa);
+        for (n = 0; n < max_domains && domains [n] >= 0; n++)
+            states [n] = domains [n];
+        max_domains = min (max_domains, n);
+        Free (domains);
+
+        for (old = 0, new = 0; new < max_domains && old < qac_model->n; old++)
+            if (qac_model->states [old] == states [new])
+                index [new++] = qac_model->index [old];
+
+        Free (qac_model->states);
+        Free (qac_model->index);
+        qac_model->states      = states;
+        qac_model->index       = index;
+        qac_model->n           = max_domains;
+        qac_model->max_domains = max_domains;
+    }
+    qac_model->y_index     = 0;
+    qac_model->max_domains = qac_model->n;
+}
+
+/*****************************************************************************
+              const domain pool
+*****************************************************************************/
+
+static domain_pool_t *
+alloc_const_domain_pool (unsigned max_domains, unsigned max_edges,
+                         const wfa_t *wfa)
+/*
+ *  Domain pool with state image 0 (constant function f(x, y) = 1).
+ *  No probability model is used.
+ */
+{
+    domain_pool_t *pool;
+   
+    pool           = default_alloc ();   
+    pool->generate = const_generate;
+    pool->bits     = const_bits;
+   
+    return pool;
+}
+
+static word_t *
+const_generate (unsigned level, int y_state, const wfa_t *wfa,
+                const void *model)
+{
+    word_t *domains = Calloc (2, sizeof (word_t));
+   
+    domains [0] = 0;
+    domains [1] = -1;
+   
+    return domains;
+}
+
+static real_t
+const_bits (const word_t *domains, const word_t *used_domains,
+            unsigned level, int y_state, const wfa_t *wfa, const void *model)
+{
+    return 0;                /* 0 bits,
+                                either we have a lc or not */
+}
+
+/*****************************************************************************
+                basis domain pool
+*****************************************************************************/
+
+static domain_pool_t *
+alloc_basis_domain_pool (unsigned max_domains, unsigned max_edges,
+                         const wfa_t *wfa)
+/*
+ *  Domain pool with state images {0, ..., 'basis_states' - 1).
+ *  Underlying probability model: quasi arithmetic coding of columns.
+ *  I.e. domain pool = qac_domainpool ('max_domains' == wfa->basis_states)
+ */
+{
+    return alloc_qac_domain_pool (wfa->basis_states, max_edges, wfa);
+}
+
+/*****************************************************************************
+              uniform-distribution pool
+*****************************************************************************/
+
+static domain_pool_t *
+alloc_uniform_domain_pool (unsigned max_domains, unsigned max_edges,
+                           const wfa_t *wfa)
+/*
+ *  Domain pool with state images {0, ..., 'max_domains').
+ *  Underlying probability model: uniform distribution.
+ */
+{
+    domain_pool_t *pool;
+   
+    pool           = default_alloc ();   
+    pool->generate = uniform_generate;
+    pool->bits     = uniform_bits;
+   
+    return pool;
+}
+
+static word_t *
+uniform_generate (unsigned level, int y_state, const wfa_t *wfa,
+                  const void *model)
+{
+    unsigned  state, n;
+    word_t   *domains = Calloc (wfa->states + 1, sizeof (word_t));
+
+    for (state = 0, n = 0; state < wfa->states; state++)
+        if (usedomain (state, wfa))
+            domains [n++] = state;
+    domains [n] = -1;
+   
+    return domains;
+}
+ 
+static real_t
+uniform_bits (const word_t *domains, const word_t *used_domains,
+              unsigned level, int y_state, const wfa_t *wfa, const void *model)
+{
+    unsigned state, n;
+    real_t   bits = 0;
+   
+    for (state = 0, n = 0; state < wfa->states; state++)
+        if (usedomain (state, wfa))
+            n++;
+
+    bits = - n * log2 ((n - 1) / (real_t) n);
+
+    if (used_domains != NULL)
+    {
+        int edge;
+      
+        for (edge = 0; isedge (used_domains [edge]); edge++)
+            bits -= log2 (1.0 / n);
+    }
+
+    return bits;
+}
+
+/*****************************************************************************
+              run length encoding pool
+*****************************************************************************/
+
+typedef struct rle_model
+{
+    word_t   count [MAXEDGES + 1];
+    u_word_t total;
+    u_word_t n;
+    u_word_t max_domains;
+    u_word_t y_index;        /* pointer to prob of Y domain */
+    word_t      *states;         /* mapping states -> domains */
+    qac_model_t *domain_0;
+} rle_model_t;
+
+static domain_pool_t *
+alloc_rle_domain_pool (unsigned max_domains, unsigned max_edges,
+                       const wfa_t *wfa)
+/*
+ *  Domain pool with state images {0, ..., 'max_domains').
+ *  Underlying probability model: rle 
+ */
+{
+    domain_pool_t *pool;
+    unsigned   state;
+   
+    pool                  = default_alloc ();    
+    pool->model           = rle_model_alloc (max_domains);
+    pool->model_free      = rle_model_free;
+    pool->model_duplicate = rle_model_duplicate;
+    pool->generate        = rle_generate;
+    pool->update          = rle_update;
+    pool->bits            = rle_bits;
+    pool->append          = rle_append;
+    pool->chroma          = rle_chroma;
+
+    for (state = 0; state < wfa->basis_states; state++)
+        if (usedomain (state, wfa))
+            rle_append (state, -1, wfa, pool->model);
+
+    return pool;
+}
+
+static void *
+rle_model_alloc (unsigned max_domains)
+{
+    unsigned m;
+    rle_model_t *model = Calloc (1, sizeof (rle_model_t));
+   
+    for (m = model->total = 0; m < MAXEDGES + 1; m++, model->total++)
+        model->count [m] = 1;
+
+    model->domain_0    = qac_model_alloc (1);
+    model->states      = Calloc (max_domains, sizeof (word_t));
+    model->n       = 0;
+    model->y_index     = 0;
+    model->max_domains = max_domains;
+   
+    return model;
+}
+
+static void
+rle_model_free (void *model)
+{
+    qac_model_free (((rle_model_t *) model)->domain_0);
+    Free (((rle_model_t *) model)->states);
+    Free (model);
+}
+
+static void *
+rle_model_duplicate (const void *src)
+{
+    const rle_model_t *rle_src = (rle_model_t *) src;
+    rle_model_t       *model   = Calloc (1, sizeof (rle_model_t));
+
+    model->domain_0    = qac_model_duplicate (rle_src->domain_0);
+    model->n       = rle_src->n;
+    model->max_domains = rle_src->max_domains;
+    model->states      = Calloc (model->max_domains, sizeof (word_t));
+    model->total       = rle_src->total;
+    model->y_index     = rle_src->y_index;
+   
+    memcpy (model->states, rle_src->states,
+            model->max_domains * sizeof (word_t));
+    memcpy (model->count, rle_src->count,
+            (MAXEDGES + 1) * sizeof (word_t));
+   
+    return model;
+}
+
+static word_t *
+rle_generate (unsigned level, int y_state, const wfa_t *wfa, const void *model)
+{
+    word_t      *domains;
+    unsigned n;
+    rle_model_t *rle_model     = (rle_model_t *) model;
+    bool_t   y_state_is_domain = NO;
+   
+    if (y_state >= 0 && !usedomain (y_state, wfa)) /* don't use y-state */
+        y_state = -1;
+   
+    domains = Calloc (rle_model->n + 2, sizeof (word_t));
+
+    memcpy (domains, rle_model->states, rle_model->n * sizeof (word_t));
+
+    for (n = 0; n < rle_model->n; n++)
+        if (domains [n] == y_state)   /* match */
+            y_state_is_domain = YES;       
+
+    if (y_state_is_domain)
+        domains [rle_model->n] = -1;  /* end marker */
+    else
+    {
+        domains [rle_model->n]     = y_state; /* additional y-state */
+        domains [rle_model->n + 1] = -1;  /* end marker */
+    }
+
+    return domains;
+}
+
+static real_t
+rle_bits (const word_t *domains, const word_t *used_domains,
+          unsigned level, int y_state, const wfa_t *wfa, const void *model)
+{
+    unsigned edge;
+    unsigned n     = 0;
+    real_t   bits      = 0;
+    word_t   sorted [MAXEDGES + 1];
+    rle_model_t *rle_model = (rle_model_t *) model;
+    unsigned last;
+    int      into;
+   
+    if (y_state >= 0 && !usedomain (y_state, wfa)) /* don't use y-state */
+        y_state = -1;
+
+    if (used_domains)
+    {
+        word_t domain;
+      
+        if (y_state >= 0)
+            bits += matrix_0 [rle_model->y_index];
+      
+        for (edge = n = 0; isedge (domain = used_domains [edge]); edge++)
+            if (domains [domain] != y_state)
+                sorted [n++] = used_domains [edge];
+            else
+            {
+                bits -= matrix_0 [rle_model->y_index];
+                bits += matrix_1 [rle_model->y_index];
+            }
+      
+        if (n > 1)
+            qsort (sorted, n, sizeof (word_t), sort_asc_word);
+    }
+
+    bits = - log2 (rle_model->count [n] / (real_t) rle_model->total);
+    if (used_domains && n && sorted [0] == 0)
+    {
+        word_t array0 [2] = {0, NO_EDGE};
+        bits += qac_bits (array0, array0, level, y_state, wfa, rle_model->domain_0);
+    }
+    else
+    {
+        word_t array0 [2] = {NO_EDGE};
+        bits += qac_bits (array0, array0, level, y_state, wfa, rle_model->domain_0);
+    }
+   
+    last = 1;
+    for (edge = 0; edge < n; edge++)
+        if ((into = sorted [edge]) && rle_model->n - 1 - last)
+        {
+            bits += bits_bin_code (into - last, rle_model->n - 1 - last);
+            last  = into + 1;
+        }
+   
+    return bits;
+}
+
+static void
+rle_update (const word_t *domains, const word_t *used_domains,
+            unsigned level, int y_state, const wfa_t *wfa, void *model)
+{
+    rle_model_t *rle_model  = (rle_model_t *) model;
+    bool_t   state_0    = NO, state_y = NO;
+    word_t   array0 [2] = {0, NO_EDGE};
+    unsigned     edge = 0;
+   
+    if (y_state >= 0 && !usedomain (y_state, wfa)) /* don't use y-state */
+        y_state = -1;
+
+    if (used_domains)
+    {
+        word_t   domain;
+      
+        for (edge = 0; isedge (domain = used_domains [edge]); edge++)
+            if (domains [domain] == 0)
+                state_0 = YES;
+            else if (domains [domain] == y_state)
+                state_y = YES;
+    }
+   
+    rle_model->count [edge]++;
+    rle_model->total++;
+
+    qac_update (array0, array0 + (state_0 ? 0 : 1), level, y_state, wfa,
+                rle_model->domain_0);
+
+    if (state_y)
+        rle_model->y_index >>= 1;
+    else
+        rle_model->y_index++;
+    if (rle_model->y_index > 1020)   /* check for overflow */
+        rle_model->y_index = 1020; 
+}
+
+static bool_t
+rle_append (unsigned new_state, unsigned level, const wfa_t *wfa, void *model)
+{
+    rle_model_t *rle_model = (rle_model_t *) model; /* probability model */
+   
+    if (rle_model->n >= rle_model->max_domains)
+        return NO;            /* don't use state in domain pool */
+    else
+    {
+        rle_model->states [rle_model->n] = new_state;
+        rle_model->n++;
+
+        if (new_state == 0)
+        {
+            assert (rle_model->n == 1);
+            qac_append (0, -1, wfa, rle_model->domain_0);
+        }
+      
+        return YES;           /* state will be used in domain pool */
+    }
+}
+
+static void
+rle_chroma (unsigned max_domains, const wfa_t *wfa, void *model)
+{
+    rle_model_t *rle_model = (rle_model_t *) model; /* probability model */
+   
+    if (max_domains < rle_model->n)  /* choose most probable domains */
+    {
+        unsigned  n;
+        word_t   *states  = Calloc (max_domains, sizeof (word_t));
+        word_t   *domains = compute_hits (wfa->basis_states, wfa->states - 1,
+                                          max_domains, wfa);
+      
+        for (n = 0; n < max_domains && domains [n] >= 0; n++)
+            states [n] = domains [n];
+
+        assert (states [0] == 0);
+        max_domains = min (max_domains, n);
+        Free (domains);
+
+        Free (rle_model->states);
+        rle_model->states = states;
+        rle_model->n      = max_domains;
+    }
+    rle_model->y_index     = 0;
+    rle_model->max_domains = rle_model->n;
+}
+
+/*****************************************************************************
+         run length encoding pool no special chroma pool
+*****************************************************************************/
+
+static domain_pool_t *
+alloc_rle_no_chroma_domain_pool (unsigned max_domains, unsigned max_edges,
+                                 const wfa_t *wfa)
+/*
+ *  Domain pool with state images {0, ..., 'max_domains').
+ *  Underlying probability model: rle 
+ *  Domain pool is not changed for chroma bands
+ */
+{
+    domain_pool_t *pool = alloc_rle_domain_pool (max_domains, max_edges, wfa);
+   
+    pool->chroma = default_chroma;
+
+    return pool;
+}
+
+/*****************************************************************************
+              default functions (see domain-pool.h)
+*****************************************************************************/
+
+static domain_pool_t *
+default_alloc (void)
+{
+    domain_pool_t *pool;
+
+    pool                  = Calloc (1, sizeof (domain_pool_t));
+    pool->model           = default_model_alloc(0);
+    pool->generate        = NULL;
+    pool->bits            = NULL;
+    pool->update          = default_update;
+    pool->append          = default_append;
+    pool->chroma          = default_chroma;
+    pool->free            = default_free;
+    pool->model_free      = default_model_free;
+    pool->model_duplicate = default_model_duplicate;
+   
+    return pool;
+}
+
+static void *
+default_model_duplicate (const void *src)
+{
+    return NULL;
+}
+
+static void *
+default_model_alloc (unsigned max_domains)
+{
+    return NULL;
+}
+
+static void
+default_model_free (void *model)
+{
+    if (model)
+        Free (model);
+}
+
+static void
+default_free (domain_pool_t *pool)
+{
+    pool->model_free (pool->model);
+    Free (pool);
+}
+
+static void
+default_update (const word_t *domains, const word_t *used_domains,
+                unsigned level, int y_state, const wfa_t *wfa, void *model)
+{
+    return;              /* nothing to do */
+}
+
+static bool_t
+default_append (unsigned new_state, unsigned level,
+                const wfa_t *wfa, void *model)
+{
+    return YES;              /* use every state in lin comb */
+}
+
+static void
+default_chroma (unsigned max_domains, const wfa_t *wfa, void *model)
+{
+    return;              /* don't alter domain pool */
+}
+
+static void
+init_matrix_probabilities (void)
+/*
+ *  Compute the information contents of matrix element '0' and '1' for
+ *  each possible probability index 0, ... ,  1023. These values are
+ *  obtained from the probability model in the quasi arithmetic
+ *  coding module.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *  local arrays matrix_0 and matrix_1 are initialized if not already done.
+ */
+{
+    if (matrix_0 == NULL || matrix_1 == NULL)
+    {
+        unsigned index;           
+        unsigned n, exp;
+      
+        matrix_0 = Calloc (1 << (MAX_PROB + 1), sizeof (real_t));
+        matrix_1 = Calloc (1 << (MAX_PROB + 1), sizeof (real_t));
+   
+        for (index = 0, n = MIN_PROB; n <= MAX_PROB; n++)
+            for (exp = 0; exp < (unsigned) 1 << n; exp++, index++)
+            {
+                matrix_1 [index] = -log2 (1 / (real_t) (1 << n));
+                matrix_0 [index] = -log2 (1 - 1 / (real_t) (1 << n));
+            }
+    }
+}
diff --git a/converter/other/fiasco/codec/domain-pool.h b/converter/other/fiasco/codec/domain-pool.h
new file mode 100644
index 00000000..d1488779
--- /dev/null
+++ b/converter/other/fiasco/codec/domain-pool.h
@@ -0,0 +1,75 @@
+/*
+ *  domain-pool.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _DOMAIN_POOL_H
+#define _DOMAIN_POOL_H
+
+#include "cwfa.h"
+#include "types.h"
+
+typedef struct domain_pool
+{
+   void	  *model;			/* probability model */
+   word_t *(*generate) (unsigned level, int y_state, const wfa_t *wfa,
+			const void  *model);
+   /*
+    *  Generate set of domain images which may be used for an approximation.
+    *  Use parameters 'level', 'y_state' and 'wfa' to make the decision.
+    */
+   real_t (*bits) (const word_t *domains, const word_t *used_domains,
+		   unsigned level, int y_state, const wfa_t *wfa,
+		   const void *model);
+   /*
+    *  Compute bit-rate of a range approximation with domains given by
+    *  the -1 terminated list 'used_domains'.
+    */
+   void	  (*update) (const word_t *domains, const word_t *used_domains,
+		     unsigned level, int y_state, const wfa_t *wfa,
+		     void *model);
+   /*
+    *  Update the probability model according to the chosen approximation.
+    *  (given by the -1 terminated list 'used_domains').
+    */
+   bool_t (*append) (unsigned state, unsigned level, const wfa_t *wfa,
+		     void *model);
+   /*
+    *  Try to append a new state to the domain pool.
+    */
+   void	  (*chroma) (unsigned max_domains, const wfa_t *wfa, void *model);
+   /*
+    *  Derive a new domain pool that will be used for chroma channel
+    *  coding 
+    */
+   void   (*free) (struct domain_pool *pool);
+   /*
+    *  Discard the given domain pool struct.
+    */
+   void   (*model_free)	(void *model);
+   /*
+    *  Free given probability model.
+    */
+   void   *(*model_duplicate) (const void *src);
+   /*
+    *  Duplicate the given probability model (i.e. alloc and copy).
+    */
+} domain_pool_t;
+
+domain_pool_t *
+alloc_domain_pool (const char *domain_pool_name, unsigned max_domains,
+		   unsigned max_edges, const wfa_t *wfa);
+
+#endif /* not _DOMAIN_POOL_H */
+
diff --git a/converter/other/fiasco/codec/ip.c b/converter/other/fiasco/codec/ip.c
new file mode 100644
index 00000000..caa97baf
--- /dev/null
+++ b/converter/other/fiasco/codec/ip.c
@@ -0,0 +1,324 @@
+/*
+ *  ip.c:		Computation of inner products
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "cwfa.h"
+#include "control.h"
+#include "ip.h"
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static real_t 
+standard_ip_image_state (unsigned address, unsigned level, unsigned domain,
+			 const coding_t *c);
+static real_t 
+standard_ip_state_state (unsigned domain1, unsigned domain2, unsigned level,
+			 const coding_t *c);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+real_t 
+get_ip_image_state (unsigned image, unsigned address, unsigned level,
+		    unsigned domain, const coding_t *c)
+/*
+ *  Return value:
+ *	Inner product between 'image' ('address') and
+ *      'domain' at given 'level' 
+ */
+{
+   if (level <= c->options.images_level)
+   {
+      /*
+       *  Compute the inner product in the standard way by multiplying 
+       *  the pixel-values of the given domain and range image.
+       */ 
+      return standard_ip_image_state (address, level, domain, c);
+   }
+   else 
+   {
+      /*
+       *  Use the already computed inner products stored in 'ip_images_states'
+       */
+      return c->ip_images_state [domain][image];
+   }
+}
+
+void 
+compute_ip_images_state (unsigned image, unsigned address, unsigned level,
+			 unsigned n, unsigned from,
+			 const wfa_t *wfa, coding_t *c)
+/*
+ *  Compute the inner products between all states
+ *  'from', ... , 'wfa->max_states' and the range images 'image'
+ *  (and childs) up to given level.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	inner product tables 'c->ip_images_states' are updated
+ */ 
+{
+   if (level > c->options.images_level) 
+   {
+      unsigned state, label;
+
+      if (level > c->options.images_level + 1)	/* recursive computation */
+	 compute_ip_images_state (MAXLABELS * image + 1, address * MAXLABELS,
+				  level - 1, MAXLABELS * n, from, wfa, c);
+      
+      /*
+       *  Compute inner product <f, Phi_i>
+       */
+      for (label = 0; label < MAXLABELS; label++)
+	 for (state = from; state < wfa->states; state++)
+	    if (need_image (state, wfa))
+	    {
+	       unsigned  edge, count;
+	       int     	 domain;
+	       real_t 	*dst, *src;
+	       
+	       if (ischild (domain = wfa->tree [state][label]))
+	       {
+		  if (level > c->options.images_level + 1)
+		  {
+		     dst = c->ip_images_state [state] + image;
+		     src = c->ip_images_state [domain]
+			   + image * MAXLABELS + label + 1;
+		     for (count = n; count; count--, src += MAXLABELS)
+			*dst++ += *src;
+		  }
+		  else
+		  {
+		     unsigned newadr = address * MAXLABELS + label;
+		     
+		     dst = c->ip_images_state [state] + image;
+		     
+		     for (count = n; count; count--, newadr += MAXLABELS)
+			*dst++ += standard_ip_image_state (newadr, level - 1,
+							   domain, c);
+		  }
+	       }
+	       for (edge = 0; isedge (domain = wfa->into [state][label][edge]);
+		    edge++)
+	       {
+		  real_t weight = wfa->weight [state][label][edge];
+		  
+		  if (level > c->options.images_level + 1)
+		  {
+		     dst = c->ip_images_state [state] + image;
+		     src = c->ip_images_state [domain]
+			   + image * MAXLABELS + label + 1;
+		     for (count = n; count; count--, src += MAXLABELS)
+			*dst++ += *src * weight;
+		  }
+		  else
+		  {
+		     unsigned newadr = address * MAXLABELS + label;
+
+		     dst = c->ip_images_state [state] + image;
+		     
+		     for (count = n; count; count--, newadr += MAXLABELS)
+			*dst++ += weight *
+				  standard_ip_image_state (newadr, level - 1,
+							   domain, c);
+		  }
+	       }
+	    }
+   }
+}
+
+real_t 
+get_ip_state_state (unsigned domain1, unsigned domain2, unsigned level,
+		    const coding_t *c)
+/*
+ *  Return value:
+ *	Inner product between 'domain1' and 'domain2' at given 'level'.
+ */
+{
+   if (level <= c->options.images_level)
+   {
+      /*
+       *  Compute the inner product in the standard way by multiplying 
+       *  the pixel-values of both state-images
+       */ 
+      return standard_ip_state_state (domain1, domain2, level, c);
+   }
+   else 
+   {
+      /*
+       *  Use already computed inner products stored in 'ip_images_states'
+       */
+      if (domain2 < domain1)
+	 return c->ip_states_state [domain1][level][domain2];
+      else
+	 return c->ip_states_state [domain2][level][domain1];
+   }
+}
+
+void 
+compute_ip_states_state (unsigned from, unsigned to,
+			 const wfa_t *wfa, coding_t *c)
+/*
+ *  Computes the inner products between the current state 'state1' and the
+ *  old states 0,...,'state1'-1
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	inner product tables 'c->ip_states_state' are computed.
+ */ 
+{
+   unsigned level;
+   unsigned state1, state2;
+
+   /*
+    *  Compute inner product <Phi_state1, Phi_state2>
+    */
+
+   for (level = c->options.images_level + 1;
+	level <= c->options.lc_max_level; level++)
+      for (state1 = from; state1 <= to; state1++)
+	 for (state2 = 0; state2 <= state1; state2++) 
+	    if (need_image (state2, wfa))
+	    {
+	       unsigned	label;
+	       real_t	ip = 0;
+	       
+	       for (label = 0; label < MAXLABELS; label++)
+	       {
+		  int	   domain1, domain2;
+		  unsigned edge1, edge2;
+		  real_t   sum, weight2;
+		  
+		  if (ischild (domain1 = wfa->tree [state1][label]))
+		  {
+		     sum = 0;
+		     if (ischild (domain2 = wfa->tree [state2][label]))
+			sum = get_ip_state_state (domain1, domain2,
+						  level - 1, c);
+		     
+		     for (edge2 = 0;
+			  isedge (domain2 = wfa->into [state2][label][edge2]);
+			  edge2++)
+		     {
+			weight2 = wfa->weight [state2][label][edge2];
+			sum += weight2 * get_ip_state_state (domain1, domain2,
+							     level - 1, c);
+		     }
+		     ip += sum;
+		  }
+		  for (edge1 = 0;
+		       isedge (domain1 = wfa->into [state1][label][edge1]);
+		       edge1++)
+		  {
+		     real_t weight1 = wfa->weight [state1][label][edge1];
+		     
+		     sum = 0;
+		     if (ischild (domain2 = wfa->tree [state2][label]))
+			sum = get_ip_state_state (domain1, domain2,
+						  level - 1, c);
+		     
+		     for (edge2 = 0;
+			  isedge (domain2 = wfa->into [state2][label][edge2]);
+			  edge2++)
+		     {
+			weight2 = wfa->weight [state2][label][edge2];
+			sum += weight2 * get_ip_state_state (domain1, domain2,
+							     level - 1, c);
+		     }
+		     ip += weight1 * sum;
+		  }
+	       }
+	       c->ip_states_state [state1][level][state2] = ip;
+	    }
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static real_t 
+standard_ip_image_state (unsigned address, unsigned level, unsigned domain,
+			 const coding_t *c)
+/*
+ *  Returns the inner product between the subimage 'address' and the
+ *  state image 'domain' at given 'level'.  The stored state images
+ *  and the image tree are used to compute the inner product in the
+ *  standard way by multiplying the corresponding pixel values.
+ *
+ *  Return value:
+ *	computed inner product
+ */
+{
+   unsigned i;
+   real_t   ip = 0, *imageptr, *stateptr;
+
+   if (level > c->options.images_level)
+      error ("Level %d not supported.", level);
+   
+   imageptr = &c->pixels [address * size_of_level (level)];
+
+   stateptr = c->images_of_state [domain] + address_of_level (level);
+   
+   for (i = size_of_level (level); i; i--)
+      ip += *imageptr++ * *stateptr++;
+
+   return ip;
+}
+
+static real_t 
+standard_ip_state_state (unsigned domain1, unsigned domain2, unsigned level,
+			 const coding_t *c)
+/*
+ *  Returns the inner product between the subimage 'address' and the
+ *  state image 'state' at given 'level'.  The stored state images are
+ *  used to compute the inner product in the standard way by
+ *  multiplying the corresponding pixel values.
+ *
+ *  Return value:
+ *	computed inner product
+ */
+{
+   unsigned i;
+   real_t   ip = 0, *state1ptr, *state2ptr;
+
+   if (level > c->options.images_level)
+      error ("Level %d not supported.", level);
+
+   state1ptr = c->images_of_state [domain1] + address_of_level (level);
+   state2ptr = c->images_of_state [domain2] + address_of_level (level);
+   
+   for (i = size_of_level (level); i; i--)
+      ip += *state1ptr++ * *state2ptr++;
+
+   return ip;
+}
+
diff --git a/converter/other/fiasco/codec/ip.h b/converter/other/fiasco/codec/ip.h
new file mode 100644
index 00000000..e5e4dd65
--- /dev/null
+++ b/converter/other/fiasco/codec/ip.h
@@ -0,0 +1,37 @@
+/*
+ *  ip.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _IP_H
+#define _IP_H
+
+#include "cwfa.h"
+
+void 
+compute_ip_states_state (unsigned from, unsigned to,
+			 const wfa_t *wfa, coding_t *c);
+real_t 
+get_ip_state_state (unsigned domain1, unsigned domain2, unsigned level,
+		    const coding_t *c);
+void 
+compute_ip_images_state (unsigned image, unsigned address, unsigned level,
+			 unsigned n, unsigned from,
+			 const wfa_t *wfa, coding_t *c);
+real_t 
+get_ip_image_state (unsigned image, unsigned address, unsigned level,
+		    unsigned domain, const coding_t *c);
+
+#endif /* not _IP_H */
+
diff --git a/converter/other/fiasco/codec/motion.c b/converter/other/fiasco/codec/motion.c
new file mode 100644
index 00000000..92951281
--- /dev/null
+++ b/converter/other/fiasco/codec/motion.c
@@ -0,0 +1,338 @@
+/*
+ *  motion.c:		Motion compensation code	
+ *
+ *  Written by:		Ullrich Hafner
+ *			Michael Unger
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#if HAVE_STRING_H
+#	include <string.h>
+#else /* not HAVE_STRING_H */
+#	include <strings.h>
+#endif /* not HAVE_STRING_H */
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "image.h"
+#include "misc.h"
+#include "motion.h"
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+void
+restore_mc (int enlarge_factor, image_t *image, const image_t *past,
+	    const image_t *future, const wfa_t *wfa)
+/*
+ *  Restore motion compensated prediction of 'image' represented by 'wfa'.
+ *  If 'enlarge_factor' != 0 then enlarge image by given amount.
+ *  Reference frames are given by 'past' and 'future'.
+ *
+ *  No return values.
+ */
+{
+   unsigned  state, label;
+   unsigned  root_state;
+   word_t   *mcblock1, *mcblock2;	/* MC blocks */
+
+#define FX(v) ((image->format == FORMAT_4_2_0) && band != Y ? ((v) / 2) : v)
+   
+   mcblock1 = Calloc (size_of_level (max ((int) wfa->wfainfo->p_max_level
+					  + 2 * enlarge_factor, 0)),
+		      sizeof (word_t));
+   mcblock2 = Calloc (size_of_level (max ((int) wfa->wfainfo->p_max_level
+					  + 2 * enlarge_factor, 0)),
+		      sizeof (word_t));
+
+   if (!image->color)
+      root_state = wfa->root_state;
+   else
+      root_state  = wfa->tree [wfa->tree [wfa->root_state][0]][0];
+   
+   for (state = wfa->basis_states; state <= root_state; state++)
+      for (label = 0; label < MAXLABELS; label++)
+	 if (wfa->mv_tree[state][label].type != NONE)
+	 {
+	    color_e band;
+	    unsigned level  = wfa->level_of_state [state] - 1;
+	    unsigned width  = width_of_level (level);
+	    unsigned height = height_of_level (level);
+	    unsigned offset = image->width - width;
+	    
+	    switch (wfa->mv_tree [state][label].type)
+	    {
+	       case FORWARD:
+		  for (band  = first_band (image->color);
+		       band <= last_band (image->color); band++)
+		  {
+		     extract_mc_block (mcblock1, FX (width), FX (height),
+				       past->pixels [band], FX (past->width),
+				       wfa->wfainfo->half_pixel,
+				       FX (wfa->x [state][label]),
+				       FX (wfa->y [state][label]),
+				       FX (wfa->mv_tree [state][label].fx),
+				       FX (wfa->mv_tree [state][label].fy));
+		     {
+			word_t   *mc1;	/* current pixel in MC block */
+			word_t 	 *orig;	/* current pixel in original image */
+			unsigned  x, y;	/* pixel coordinates */
+			
+			mc1  = mcblock1;
+			orig = (word_t *) image->pixels [band]
+			       + FX (wfa->x[state][label])
+			       + FX (wfa->y[state][label]) * FX (image->width);
+		     
+			for (y = FX (height); y; y--)
+			{
+			   for (x = FX (width); x; x--)
+			      *orig++ += *mc1++;
+
+			   orig += FX (offset);
+			}
+		     }
+		  }
+		  break;
+	       case BACKWARD:
+		  for (band  = first_band (image->color);
+		       band <= last_band (image->color); band++)
+		  {
+		     extract_mc_block (mcblock1, FX (width), FX (height),
+				       future->pixels [band],
+				       FX (future->width),
+				       wfa->wfainfo->half_pixel,
+				       FX (wfa->x [state][label]),
+				       FX (wfa->y [state][label]),
+				       FX (wfa->mv_tree [state][label].bx),
+				       FX (wfa->mv_tree [state][label].by));
+		     {
+			word_t   *mc1;	/* current pixel in MC block 1 */
+			word_t   *orig;	/* current pixel in original image */
+			unsigned  x, y;	/* pixel coordinates */
+			
+			mc1  = mcblock1;
+			orig = (word_t *) image->pixels [band]
+			       + FX (wfa->x[state][label])
+			       + FX (wfa->y[state][label]) * FX (image->width);
+		     
+			for (y = FX (height); y; y--)
+			{
+			   for (x = FX (width); x; x--)
+			      *orig++ += *mc1++;
+
+			   orig += FX (offset);
+			}
+		     }
+		  }
+		  break;
+	       case INTERPOLATED:
+		  for (band  = first_band (image->color);
+		       band <= last_band (image->color); band++)
+		  {
+		     extract_mc_block (mcblock1, FX (width), FX (height),
+				       past->pixels [band], FX (past->width),
+				       wfa->wfainfo->half_pixel,
+				       FX (wfa->x[state][label]),
+				       FX (wfa->y[state][label]),
+				       FX (wfa->mv_tree[state][label].fx),
+				       FX (wfa->mv_tree[state][label].fy));
+		     extract_mc_block (mcblock2, FX (width), FX (height),
+				       future->pixels [band],
+				       FX (future->width),
+				       wfa->wfainfo->half_pixel,
+				       FX (wfa->x[state][label]),
+				       FX (wfa->y[state][label]),
+				       FX (wfa->mv_tree[state][label].bx),
+				       FX (wfa->mv_tree[state][label].by));
+		     {
+			word_t   *mc1;	/* current pixel in MC block 1 */
+			word_t   *mc2;	/* current pixel in MC block 1 */
+			word_t   *orig;	/* current pixel in original image */
+			unsigned  x, y;	/* pixel coordinates */
+			
+			mc1  = mcblock1;
+			mc2  = mcblock2;
+			orig = (word_t *) image->pixels [band]
+			       + FX (wfa->x[state][label])
+			       + FX (wfa->y[state][label]) * FX (image->width);
+			
+			for (y = FX (height); y; y--)
+			{
+			   for (x = FX (width); x; x--)
+#ifdef HAVE_SIGNED_SHIFT
+			      *orig++ += (*mc1++ + *mc2++) >> 1;
+#else /* not HAVE_SIGNED_SHIFT */
+			   *orig++ += (*mc1++ + *mc2++) / 2;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+			   orig += FX (offset);
+			}
+		     }
+		  }
+		  break;
+	       default:
+		  break;
+	    }
+	 }
+
+   if (image->color)
+   {
+      unsigned	  n;
+      word_t	 *ptr;
+      static int *clipping = NULL;
+      unsigned	  shift    = image->format == FORMAT_4_2_0 ? 2 : 0;
+
+      if (!clipping)			/* initialize clipping table */
+      {
+	 int i;
+	    
+	 clipping = Calloc (256 * 3, sizeof (int));
+	 for (i = -128; i < 128; i++)
+	    clipping [256 + i + 128] = i;
+	 for (i = 0; i < 256; i++)
+	    clipping [i] = clipping [256];
+	 for (i = 512; i < 512 + 256; i++)
+	    clipping [i] = clipping [511];
+	 clipping += 256 + 128;
+      }
+	 
+      ptr = image->pixels [Cb];
+      for (n = (image->width * image->height) >> shift; n; n--, ptr++)
+#ifdef HAVE_SIGNED_SHIFT
+	 *ptr = clipping [*ptr >> 4] << 4;
+#else /* not HAVE_SIGNED_SHIFT */
+	 *ptr = clipping [*ptr / 16] * 16;
+#endif /* not HAVE_SIGNED_SHIFT */
+      ptr = image->pixels [Cr];
+      for (n = (image->width * image->height) >> shift; n; n--, ptr++)
+#ifdef HAVE_SIGNED_SHIFT
+	*ptr = clipping [*ptr >> 4] << 4;
+#else /* not HAVE_SIGNED_SHIFT */
+        *ptr = clipping [*ptr / 16] * 16;
+#endif /* not HAVE_SIGNED_SHIFT */
+   }
+   
+   Free (mcblock1);
+   Free (mcblock2);
+}
+
+void
+extract_mc_block (word_t *mcblock, unsigned width, unsigned height,
+		  const word_t *reference, unsigned ref_width,
+		  bool_t half_pixel, unsigned xo, unsigned yo,
+		  unsigned mx, unsigned my)
+/*
+ *  Extract motion compensation image 'mcblock' of size 'width'x'height'
+ *  from 'reference' image (width is given by 'ref_width').
+ *  Coordinates of reference block are given by ('xo' + 'mx', 'yo' + 'my').
+ *  Use 'half_pixel' precision if specified.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'mcblock[]'	MCPE block is filled with reference pixels 
+ */
+{
+   if (!half_pixel)			/* Fullpixel precision */
+   {
+      const word_t *rblock;		/* pointer to reference image */
+      unsigned	    y;			/* current row */
+      
+      rblock  = reference + (yo + my) * ref_width + (xo + mx);
+      for (y = height; y; y--) 
+      {
+	 memcpy (mcblock, rblock, width * sizeof (word_t));
+
+	 mcblock += width;
+	 rblock  += ref_width;
+      }
+   }
+   else					/* Halfpixel precision */
+   {
+      unsigned	    x, y;		/* current coordinates */
+      unsigned	    offset;		/* remaining pixels in row */
+      const word_t *rblock;		/* pointer to reference image */
+      const word_t *ryblock;		/* pointer to next line */
+      const word_t *rxblock;		/* pointer to next column */
+      const word_t *rxyblock;		/* pointer to next column & row */
+   
+      rblock   = reference + (yo + my / 2) * ref_width + (xo + mx / 2);
+      ryblock  = rblock + ref_width;	/* pixel in next row */
+      rxblock  = rblock + 1;		/* pixel in next column */
+      rxyblock = ryblock + 1;		/* pixel in next row & column */
+      offset   = ref_width - width;
+      
+      if ((mx & 1) == 0)
+      {
+	 if ((my & 1) == 0)		/* Don't use halfpixel refinement */
+	    for (y = height; y; y--) 
+	    {
+	       memcpy (mcblock, rblock, width * sizeof (word_t));
+	       
+	       mcblock += width;
+	       rblock  += ref_width;
+	    }
+	 else				/* Halfpixel in y direction */
+	    for (y = height; y; y--) 
+	    {
+	       for (x = width; x; x--)
+#ifdef HAVE_SIGNED_SHIFT
+		  *mcblock++ = (*rblock++ + *ryblock++) >> 1;
+#else /* not HAVE_SIGNED_SHIFT */
+		  *mcblock++ = (*rblock++ + *ryblock++) / 2;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+	       rblock  += offset;
+	       ryblock += offset;
+	    }
+      }
+      else
+      {
+	 if ((my & 1) == 0)		/* Halfpixel in x direction */
+	    for (y = height; y; y--) 
+	    {
+	       for (x = width; x; x--)
+#ifdef HAVE_SIGNED_SHIFT
+		  *mcblock++ = (*rblock++ + *rxblock++) >> 1;
+#else /* not HAVE_SIGNED_SHIFT */
+		  *mcblock++ = (*rblock++ + *rxblock++) / 2;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+	       rblock  += offset;
+	       rxblock += offset;
+	    }
+	 else				/* Halfpixel in xy direction */
+	    for (y = height; y; y--) 
+	    {
+	       for (x = width; x; x--)
+#ifdef HAVE_SIGNED_SHIFT
+		  *mcblock++ = (*rblock++ + *rxblock++
+				+ *ryblock++ + *rxyblock++) >> 2;
+#else /* not HAVE_SIGNED_SHIFT */
+		  *mcblock++ = (*rblock++ + *rxblock++
+				+ *ryblock++ + *rxyblock++) / 4;
+#endif /* not HAVE_SIGNED_SHIFT */
+	       rblock   += offset;
+	       ryblock  += offset;
+	       rxblock  += offset;
+	       rxyblock += offset;
+	    }
+      }
+   }
+}
diff --git a/converter/other/fiasco/codec/motion.h b/converter/other/fiasco/codec/motion.h
new file mode 100644
index 00000000..2ea382f7
--- /dev/null
+++ b/converter/other/fiasco/codec/motion.h
@@ -0,0 +1,35 @@
+/*
+ *  motion.h
+ *
+ *  Written by:		Ullrich Hafner
+ *			Michael Unger
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _MOTION_H
+#define _MOTION_H
+
+#include "wfa.h"
+#include "types.h"
+#include "image.h"
+
+void
+restore_mc (int enlarge_factor, image_t *image, const image_t *past,
+	    const image_t *future, const wfa_t *wfa);
+void
+extract_mc_block (word_t *mcblock, unsigned width, unsigned height,
+		  const word_t *reference, unsigned ref_width,
+		  bool_t half_pixel, unsigned xo, unsigned yo,
+		  unsigned mx, unsigned my);
+
+#endif /* not _MOTION_H */
+
diff --git a/converter/other/fiasco/codec/mwfa.c b/converter/other/fiasco/codec/mwfa.c
new file mode 100644
index 00000000..6f0af8be
--- /dev/null
+++ b/converter/other/fiasco/codec/mwfa.c
@@ -0,0 +1,864 @@
+/*
+ *  mwfa.c:		Initialization of MWFA coder
+ *
+ *  Written by:		Michael Unger
+ *			Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include <ctype.h>
+
+#if HAVE_STRING_H
+#	include <string.h>
+#else /* not HAVE_STRING_H */
+#	include <strings.h>
+#endif /* not HAVE_STRING_H */
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "misc.h"
+#include "cwfa.h"
+#include "image.h"
+#include "mvcode.h"
+#include "motion.h"
+#include "mwfa.h"
+
+
+static const unsigned local_range = 6;
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static void
+get_mcpe (word_t *mcpe, const image_t *original,
+	  unsigned x0, unsigned y0, unsigned width, unsigned height,
+	  const word_t *mcblock1, const word_t *mcblock2);
+static real_t
+mcpe_norm (const image_t *original, unsigned x0, unsigned y0, unsigned width,
+	   unsigned height, const word_t *mcblock1, const word_t *mcblock2);
+static real_t 
+find_best_mv (real_t price, const image_t *original, const image_t *reference,
+	      unsigned x0, unsigned y0, unsigned width, unsigned height,
+	      real_t *bits, int *mx, int *my, const real_t *mc_norms,
+	      const wfa_info_t *wi, const motion_t *mt);
+static real_t
+find_second_mv (real_t price, const image_t *original,
+		const image_t *reference, const word_t *mcblock1,
+		unsigned xr, unsigned yr, unsigned width, unsigned height,
+		real_t *bits, int *mx, int *my, const wfa_info_t *wi,
+		const motion_t *mt);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+motion_t *
+alloc_motion (const wfa_info_t *wi)
+/*
+ *  Motion structure constructor.
+ *  Allocate memory for the motion structure and
+ *  fill in default values specified by 'wi'.
+ *
+ *  Return value:
+ *	pointer to the new option structure or NULL on error
+ */
+{
+   int	     dx;			/* motion vector coordinate */
+   unsigned  level;
+   unsigned range_size = wi->half_pixel
+			 ? square (wi->search_range)
+			 : square (2 * wi->search_range);
+   motion_t *mt        = Calloc (1, sizeof (motion_t));
+   
+   mt->original = NULL;
+   mt->past     = NULL;
+   mt->future   = NULL;
+   mt->xbits 	= Calloc (2 * wi->search_range, sizeof (real_t));
+   mt->ybits 	= Calloc (2 * wi->search_range, sizeof (real_t));
+
+   for (dx = -wi->search_range; dx < (int) wi->search_range; dx++)
+   {
+      mt->xbits [dx + wi->search_range]
+	 = mt->ybits [dx + wi->search_range]
+	 = mv_code_table [dx + wi->search_range][1];
+   }
+   
+   mt->mc_forward_norms = Calloc (MAXLEVEL, sizeof (real_t *));
+   mt->mc_backward_norms = Calloc (MAXLEVEL, sizeof (real_t *));
+   
+   for (level = wi->p_min_level; level <= wi->p_max_level; level++)
+   {
+      mt->mc_forward_norms  [level] = Calloc (range_size, sizeof (real_t));
+      mt->mc_backward_norms [level] = Calloc (range_size, sizeof (real_t));
+   }
+
+   return mt;
+}
+
+void
+free_motion (motion_t *mt)
+/*
+ *  Motion struct destructor:
+ *  Free memory of 'motion' struct.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	structure 'motion' is discarded.
+ */
+{
+   unsigned level;
+
+   Free (mt->xbits);
+   Free (mt->ybits);
+   for (level = 0; level < MAXLEVEL; level++)
+   {
+      if (mt->mc_forward_norms [level])
+	 Free (mt->mc_forward_norms [level]);
+      if (mt->mc_backward_norms [level])
+	 Free (mt->mc_backward_norms [level]);
+   }
+   Free (mt->mc_forward_norms);
+   Free (mt->mc_backward_norms);
+   Free (mt);
+}
+
+void
+subtract_mc (image_t *image, const image_t *past, const image_t *future,
+	     const wfa_t *wfa)
+/*
+ *  Subtract motion compensation from chrom channels of 'image'.
+ *  Reference frames are given by 'past' and 'future'.
+ *
+ *  No return values.
+ */
+{
+   unsigned  state, label;
+   word_t   *mcblock1 = Calloc (size_of_level (wfa->wfainfo->p_max_level),
+				sizeof (word_t));
+   word_t   *mcblock2 = Calloc (size_of_level (wfa->wfainfo->p_max_level),
+				sizeof (word_t));
+
+   for (state = wfa->basis_states; state < wfa->states; state++)
+      for (label = 0; label < MAXLABELS; label++)
+	 if (wfa->mv_tree [state][label].type != NONE)
+	 {
+	    color_e  band;		/* current color band */
+	    unsigned width, height;	/* size of mcblock */
+	    unsigned offset;		/* remaining pixels in original */
+	    
+	    width  = width_of_level (wfa->level_of_state [state] - 1);
+	    height = height_of_level (wfa->level_of_state [state] - 1);
+	    offset = image->width - width;
+	    
+	    switch (wfa->mv_tree [state][label].type)
+	    {
+	       case FORWARD:
+		  for (band  = first_band (image->color) + 1;
+		       band <= last_band (image->color); band++)
+		  {
+		     unsigned  y;	/* row of block */
+		     word_t   *mc1;	/* pixel in MC block 1 */
+		     word_t   *orig;	/* pixel in original image */
+		     
+		     extract_mc_block (mcblock1, width, height,
+				       past->pixels [band], past->width,
+				       wfa->wfainfo->half_pixel,
+				       wfa->x [state][label],
+				       wfa->y [state][label],
+				       (wfa->mv_tree [state][label].fx / 2)
+				       * 2,
+				       (wfa->mv_tree [state][label].fy / 2)
+				       * 2);
+		     mc1  = mcblock1;
+		     orig = image->pixels [band] + wfa->x [state][label]
+			    + wfa->y [state][label] * image->width;
+		     
+ 		     for (y = height; y; y--)
+		     {
+			unsigned x;	/* column of block */
+			
+			for (x = width; x; x--)
+			   *orig++ -= *mc1++;
+
+			orig += offset;
+		     }
+		  }
+		  break;
+	       case BACKWARD:
+		  for (band  = first_band (image->color) + 1;
+		       band <= last_band (image->color); band++)
+		  {
+		     unsigned  y;	/* row of block */
+		     word_t   *mc1;	/* pixel in MC block 1 */
+		     word_t   *orig;	/* pixel in original image */
+		     
+		     extract_mc_block (mcblock1, width, height,
+				       future->pixels [band], future->width,
+				       wfa->wfainfo->half_pixel,
+				       wfa->x [state][label],
+				       wfa->y [state][label],
+				       (wfa->mv_tree [state][label].bx / 2)
+				       * 2,
+				       (wfa->mv_tree [state][label].by / 2)
+				       * 2);
+		     mc1  = mcblock1;
+		     orig = image->pixels [band] + wfa->x [state][label]
+			    + wfa->y [state][label] * image->width;
+		     
+		     for (y = height; y; y--)
+		     {
+			unsigned x;	/* column of block */
+			
+			for (x = width; x; x--)
+			   *orig++ -= *mc1++;
+
+			orig += offset;
+		     }
+		  }
+		  break;
+	       case INTERPOLATED:
+		  for (band  = first_band (image->color) + 1;
+		       band <= last_band (image->color); band++)
+		  {
+		     unsigned  y;	/* row of block */
+		     word_t   *mc1;	/* pixel in MC block 1 */
+		     word_t   *mc2;	/* pixel in MC block 2 */
+		     word_t   *orig;	/* pixel in original image */
+		     
+		     extract_mc_block (mcblock1, width, height,
+				       past->pixels [band], past->width,
+				       wfa->wfainfo->half_pixel,
+				       wfa->x [state][label],
+				       wfa->y [state][label],
+				       (wfa->mv_tree[state][label].fx / 2)
+				       * 2,
+				       (wfa->mv_tree[state][label].fy / 2)
+				       * 2);
+		     extract_mc_block (mcblock2, width, height,
+				       future->pixels [band], future->width,
+				       wfa->wfainfo->half_pixel,
+				       wfa->x [state][label],
+				       wfa->y [state][label],
+				       (wfa->mv_tree[state][label].bx / 2)
+				       * 2,
+				       (wfa->mv_tree[state][label].by / 2)
+				       * 2);
+		     mc1  = mcblock1;
+		     mc2  = mcblock2;
+		     orig = image->pixels [band] + wfa->x [state][label]
+			    + wfa->y [state][label] * image->width;
+		     
+		     for (y = height; y; y--)
+		     {
+			unsigned x;	/* column of block */
+			
+			for (x = width; x; x--)
+			   *orig++ -= (*mc1++ + *mc2++) / 2;
+
+			orig += offset;
+		     }
+		  }
+		  break;
+	       default:
+		  break;
+	    }
+	 }
+
+   Free (mcblock1);
+   Free (mcblock2);
+}
+
+void
+find_P_frame_mc (word_t *mcpe, real_t price, range_t *range,
+		 const wfa_info_t *wi, const motion_t *mt)
+/*
+ *  Determine best motion vector for P-frame.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *            range->mvt_bits  (# of mv-tree bits)
+ *            range->mvxybits  (# of bits for vector components)
+ *            mt->mcpe         (MCPE in scan-order)
+ */
+{
+   unsigned  width   = width_of_level (range->level);
+   unsigned  height  = height_of_level (range->level);
+   word_t   *mcblock = Calloc (width * height, sizeof (word_t));
+   
+   range->mv_tree_bits = 1;
+   range->mv.type      = FORWARD;
+
+   /*
+    *  Find best matching forward prediction
+    */
+   find_best_mv (price, mt->original, mt->past, range->x, range->y,
+		 width, height, &range->mv_coord_bits, &range->mv.fx,
+		 &range->mv.fy, mt->mc_forward_norms [range->level], wi, mt);
+
+   /*
+    *  Compute MCPE
+    */
+   extract_mc_block (mcblock, width, height, mt->past->pixels [GRAY],
+		     mt->past->width, wi->half_pixel, range->x, range->y,
+		     range->mv.fx, range->mv.fy);
+   get_mcpe (mcpe, mt->original, range->x, range->y, width, height,
+	     mcblock, NULL);
+
+   Free (mcblock);
+}
+
+void
+find_B_frame_mc (word_t *mcpe, real_t price, range_t *range,
+		 const wfa_info_t *wi, const motion_t *mt)
+/*
+ *  Determines best motion compensation for B-frame.
+ *  Steps:
+ *         1)  find best forward motion vector
+ *         2)  find best backward motion vector
+ *         3)  try both motion vectors together (interpolation)
+ *         4)  choose best mode (FORWARD, BACKWARD or INTERPOLATED)
+ *  Bitcodes:
+ *    FORWARD      000
+ *    BACKWARD     001
+ *    INTERPOLATED  01
+ *  
+ *  Return values:
+ *            range->mvt_bits  (# of mv-tree bits)
+ *            range->mvxybits  (# of bits for vector components)
+ *            mt->mcpe         (MCPE in scan-order)
+ */
+{
+   mc_type_e  mctype;			/* type of motion compensation */
+   real_t     forward_costs;		/* costs of FORWARD mc */
+   real_t     backward_costs;		/* costs of BACKWARD mc */
+   real_t     interp_costs;		/* costs of INTERPOLATED mc */
+   real_t     forward_bits;		/* bits for FORWARD mc */
+   real_t     backward_bits;		/* bits for BACKWARD mc */
+   real_t     interp_bits;		/* bits for INTERPOLATED mc */
+   int	      fx,  fy;			/* coordinates FORWARD mc */
+   int	      bx,  by;			/* coordinates BACKWARD mc */
+   int	      ifx, ify;			/* coordinates forw. INTERPOLATED mc */
+   int	      ibx, iby;			/* coordinates back. INTERPOLATED mc */
+   unsigned   width    = width_of_level (range->level);
+   unsigned   height   = height_of_level (range->level);
+   word_t    *mcblock1 = Calloc (width * height, sizeof (word_t));
+   word_t    *mcblock2 = Calloc (width * height, sizeof (word_t));
+   
+   /*
+    *  Forward interpolation: use past frame as reference
+    */
+   forward_costs = find_best_mv (price, mt->original, mt->past,
+				 range->x, range->y, width, height,
+				 &forward_bits, &fx, &fy,
+				 mt->mc_forward_norms [range->level], wi, mt)
+		   + 3 * price; /* code 000 */
+
+   /*
+    *  Backward interpolation: use future frame as reference
+    */
+   backward_costs = find_best_mv (price, mt->original, mt->future,
+				  range->x, range->y, width, height,
+				  &backward_bits, &bx, &by,
+				  mt->mc_backward_norms [range->level], wi, mt)
+		    + 3 * price; /* code 001 */
+
+   /*
+    *  Bidirectional interpolation: use both past and future frame as reference
+    */
+   if (wi->cross_B_search) 
+   {
+      real_t icosts1;			/* costs interpolation alternative 1 */
+      real_t icosts2;			/* costs interpolation alternative 2 */
+      real_t ibackward_bits;		/* additional bits alternative 1 */
+      real_t iforward_bits;		/* additional bits alternative 1 */
+      
+      /*
+       *  Alternative 1: keep forward mv and vary backward mv locally
+       */
+      extract_mc_block (mcblock1, width, height, mt->past->pixels [GRAY],
+			mt->past->width, wi->half_pixel,
+			range->x, range->y, fx, fy);
+
+      ibx = bx;				/* start with backward coordinates */
+      iby = by;
+      icosts1 = find_second_mv (price, mt->original, mt->future,
+				mcblock1, range->x, range->y, width, height,
+				&ibackward_bits, &ibx, &iby, wi, mt)
+		+ (forward_bits + 2) * price; /* code 01 */
+
+      /*
+       *  Alternative 2: Keep backward mv and vary forward mv locally
+       */
+      extract_mc_block (mcblock1, width, height, mt->future->pixels [GRAY],
+			mt->future->width, wi->half_pixel,
+			range->x, range->y, bx, by);
+
+      ifx = fx;
+      ify = fy;
+      icosts2 = find_second_mv (price, mt->original, mt->past,
+				mcblock1, range->x, range->y, width, height,
+				&iforward_bits, &ifx, &ify, wi, mt)
+		+ (backward_bits + 2) * price; /* code 01 */
+      
+      /*
+       *  Choose best alternative
+       */
+      if (icosts1 < icosts2)
+      {
+	 ifx 	      = fx;
+	 ify 	      = fy;
+	 interp_bits  = forward_bits + ibackward_bits;
+	 interp_costs = icosts1;
+      }
+      else
+      {
+	 ibx 	      = bx;
+	 iby 	      = by;
+	 interp_bits  = iforward_bits + backward_bits;
+	 interp_costs = icosts2;
+      }
+   }
+   else					/* local exhaustive search */
+   {
+      /*
+       *  Keep forward and backward mv due to time constraints
+       */
+
+      ifx = fx;
+      ify = fy;
+      ibx = bx;
+      iby = by;
+      interp_bits = forward_bits + backward_bits;
+
+      extract_mc_block (mcblock1, width, height, mt->past->pixels [GRAY],
+			mt->past->width, wi->half_pixel,
+			range->x, range->y, fx, fy);
+      extract_mc_block (mcblock2, width, height, mt->future->pixels [GRAY],
+			mt->future->width, wi->half_pixel,
+			range->x, range->y, bx, by);
+      interp_costs = mcpe_norm (mt->original, range->x, range->y,
+				width, height, mcblock1, mcblock2)
+		     + (interp_bits + 2) * price; /* code 01 */
+   }
+
+   /*
+    *  Choose alternative with smallest costs
+    */
+   if (forward_costs <= interp_costs)
+   {
+      if (forward_costs <= backward_costs)
+	 mctype = FORWARD;
+      else
+	 mctype = BACKWARD;
+   }
+   else
+   {
+      if (backward_costs <= interp_costs)
+	 mctype = BACKWARD;
+      else
+	 mctype = INTERPOLATED;
+   }
+
+   switch (mctype)
+   {
+      case FORWARD:
+	 range->mv_tree_bits  = 3;
+	 range->mv_coord_bits = forward_bits;
+	 range->mv.type       = FORWARD;
+	 range->mv.fx         = fx;
+	 range->mv.fy         = fy;
+	 extract_mc_block (mcblock1, width, height, mt->past->pixels [GRAY],
+			   mt->past->width, wi->half_pixel,
+			   range->x, range->y, range->mv.fx, range->mv.fy);
+	 get_mcpe (mcpe, mt->original, range->x, range->y, width, height,
+		   mcblock1, NULL);
+	 break;
+      case BACKWARD:
+	 range->mv_tree_bits  = 3;
+	 range->mv_coord_bits = backward_bits;
+	 range->mv.type       = BACKWARD;
+	 range->mv.bx         = bx;
+	 range->mv.by         = by;
+	 extract_mc_block (mcblock1, width, height, mt->future->pixels [GRAY],
+			   mt->future->width, wi->half_pixel,
+			   range->x, range->y, range->mv.bx, range->mv.by);
+	 get_mcpe (mcpe, mt->original, range->x, range->y, width, height,
+		   mcblock1, NULL);
+	 break;
+      case INTERPOLATED:
+	 range->mv_tree_bits  = 2;
+	 range->mv_coord_bits = interp_bits;
+	 range->mv.type       = INTERPOLATED;
+	 range->mv.fx         = ifx;
+	 range->mv.fy         = ify;
+	 range->mv.bx         = ibx;
+	 range->mv.by         = iby;
+	 extract_mc_block (mcblock1, width, height, mt->past->pixels [GRAY],
+			   mt->past->width, wi->half_pixel,
+			   range->x, range->y, range->mv.fx, range->mv.fy);
+	 extract_mc_block (mcblock2, width, height, mt->future->pixels [GRAY],
+			   mt->future->width, wi->half_pixel,
+			   range->x, range->y, range->mv.bx, range->mv.by);
+	 get_mcpe (mcpe, mt->original, range->x, range->y, width, height,
+		   mcblock1, mcblock2);
+	 break;
+      default:
+	 break;
+   }
+
+   Free (mcblock1);
+   Free (mcblock2);
+}
+
+void
+fill_norms_table (unsigned x0, unsigned y0, unsigned level,
+		  const wfa_info_t *wi, motion_t *mt)
+/*
+ *  Compute norms of difference images for all possible displacements
+ *  in 'mc_forward_norm' and 'mc_backward_norm'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'mt->mc_backward_norms' are computed
+ *	'mt->mc_forward_norms' are computed 
+ */
+{
+   int	     mx, my;			/* coordinates of motion vector */
+   unsigned  sr;			/* mv search range +-'sr' pixels */
+   unsigned  index   = 0;		/* index of motion vector */
+   unsigned  width   = width_of_level (level);
+   unsigned  height  = height_of_level (level);
+   word_t   *mcblock = Calloc (width * height, sizeof (word_t));
+
+   sr = wi->half_pixel ? wi->search_range / 2 :  wi->search_range;
+   
+   for (my = -sr; my < (int) sr; my++)
+      for (mx = -sr; mx < (int) sr; mx++, index++)
+      {
+	  if ((int) x0 + mx < 0 ||	/* block outside visible area */
+	      x0 + mx + width > mt->original->width || 
+	      (int) y0 + my < 0 ||
+	      y0 + my + height > mt->original->height)
+	  {
+	     mt->mc_forward_norms [level][index]  = 0.0;
+	     mt->mc_backward_norms [level][index] = 0.0;
+	  }
+	  else
+	  {
+	     extract_mc_block (mcblock, width, height, mt->past->pixels [GRAY],
+			       mt->past->width, wi->half_pixel,
+			       x0, y0, mx, my);
+	     mt->mc_forward_norms [level][index]
+		= mcpe_norm (mt->original, x0, y0, width, height,
+			     mcblock, NULL);
+
+	     if (mt->frame_type == B_FRAME)
+	     {
+		extract_mc_block (mcblock, width, height,
+				  mt->future->pixels [GRAY],
+				  mt->future->width, wi->half_pixel,
+				  x0, y0, mx, my);
+		mt->mc_backward_norms[level][index]
+		   = mcpe_norm (mt->original, x0, y0, width, height,
+				mcblock, NULL); 
+	     }
+	  }
+       }
+
+   Free (mcblock);
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static void
+get_mcpe (word_t *mcpe, const image_t *original, unsigned x0, unsigned y0,
+	  unsigned width, unsigned height, const word_t *mcblock1,
+	  const word_t *mcblock2)
+/*
+ *  Compute MCPE image 'original' - reference. The reference is either
+ *  composed of 'mcblock1' or of ('mcblock1' + 'mcblock2') / 2 (if
+ *  'mcblock2' != NULL).  Coordinates of original block are given by
+ *  'x0', 'y0', 'width', and 'height'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'mcpe []' is filled with the delta image
+ */
+{
+   const word_t	*oblock;		/* pointer to original image */
+
+   assert (mcpe);
+   
+   oblock = original->pixels [GRAY] + y0 * original->width + x0;
+
+   if (mcblock2 != NULL)		/* interpolated prediction */
+   {
+      unsigned x, y;			/* current coordinates */
+      
+      for (y = height; y; y--) 
+      {
+	 for (x = width; x; x--)
+	    *mcpe++ = *oblock++ - (*mcblock1++ + *mcblock2++) / 2;
+
+	 oblock += original->width - width;
+      }
+   }
+   else					/* forward or backward prediction */
+   {
+      unsigned x, y;			/* current coordinates */
+      
+      for (y = height; y; y--) 
+      {
+	 for (x = width; x; x--)
+	    *mcpe++ = *oblock++ - *mcblock1++;
+      
+	 oblock += original->width - width;
+      }
+   }
+}
+
+static real_t
+mcpe_norm (const image_t *original, unsigned x0, unsigned y0, unsigned width,
+	   unsigned height, const word_t *mcblock1, const word_t *mcblock2)
+/*
+ *  Compute norm of motion compensation prediction error.
+ *  Coordinates of 'original' block are given by ('x0', 'y0')
+ *  and 'width', 'height'.
+ *  Reference blocks are stored in 'mcimage1' and 'mcimage2'.
+ *
+ *  Return value:
+ *	square of norm of difference image
+ */
+{
+   unsigned  n;
+   real_t    norm = 0;
+   word_t   *mcpe = Calloc (width * height, sizeof (word_t));
+   word_t   *ptr  = mcpe;
+   
+   get_mcpe (mcpe, original, x0, y0, width, height, mcblock1, mcblock2);
+
+   for (n = height * width; n; n--, ptr++) 
+      norm += square (*ptr / 16);
+   
+   Free (mcpe);
+   
+   return norm;
+}
+
+static real_t 
+find_best_mv (real_t price, const image_t *original, const image_t *reference,
+	      unsigned x0, unsigned y0, unsigned width, unsigned height,
+	      real_t *bits, int *mx, int *my, const real_t *mc_norms,
+	      const wfa_info_t *wi, const motion_t *mt)
+/*
+ *  Find best matching motion vector in image 'reference' to predict
+ *  the block ('x0', 'y0') of size 'width'x'height in image 'original'.
+ *
+ *  Return values:
+ *	prediction costs
+ *
+ *  Side effects:
+ *	'mx', 'my'		coordinates of motion vector
+ *	'bits'			number of bits to encode mv
+ */
+{
+   unsigned sr;				/* mv search range +/- 'sr' pixels */
+   unsigned index;			/* index of motion vector */
+   int 	    x, y;			/* coordinates of motion vector */
+   real_t   costs;			/* costs arising if mv is chosen */
+   real_t   mincosts = MAXCOSTS;	/* best costs so far  */
+   unsigned bitshift;			/* half_pixel coordinates multiplier */
+   
+   *mx = *my = 0;
+
+   /*
+    *  Find best fitting motion vector:
+    *  Use exhaustive search in the interval x,y +- sr (no halfpixel accuracy)
+    *					  or x,y +- sr/2  (halfpixel accuracy)
+    */
+   sr 	    = wi->half_pixel ? wi->search_range / 2 :  wi->search_range;
+   bitshift = (wi->half_pixel ? 2 : 1);	/* bit0 reserved for halfpixel pred. */
+   
+   for (index = 0, y = -sr; y < (int) sr; y++)
+      for (x = -sr; x < (int) sr; x++, index++)
+	 if ((int) x0 + x >= 0 && (int) y0 + y >= 0 &&	
+	     x0 + x + width  <= original->width && 
+	     y0 + y + height <= original->height)
+	 {
+	    /*
+	     *  Block is inside visible area.
+	     *  Compare current costs with 'mincosts'
+	     */
+	    costs = mc_norms [index]
+		    + (mt->xbits [(x + sr) * bitshift]
+		       + mt->ybits [(y + sr) * bitshift]) * price;
+
+	     if (costs < mincosts)
+	     {
+		mincosts = costs;
+		*mx      = x * bitshift;
+		*my      = y * bitshift;
+	     }
+	 }
+
+   /*
+    *  Halfpixel prediction:
+    *  Compare all nine combinations (-1, y), (0, y), (+1, y) for y = -1,0,+1
+    */
+   if (wi->half_pixel)
+   {
+      int	rx, ry;			/* halfpixel refinement */
+      unsigned	bestrx, bestry;		/* coordinates of best mv */
+      word_t   *mcblock = Calloc (width * height, sizeof (word_t));
+      
+      bestrx = bestry = 0;
+      for (rx = -1; rx <= 1; rx++)
+	 for (ry = -1; ry <= 1; ry++)
+	 {
+	    /*
+	     *  Check if the new motion vector is in allowed area
+	     */
+	    if (rx == 0 && ry == 0)	/* already tested */
+	       continue;
+	    if ((int) x0 + (*mx / 2) + rx < 0 || /* outside visible area */
+		x0 + (*mx / 2) + rx + width > original->width ||
+		(int) y0 + (*my / 2) + ry < 0 || 
+		y0 + (*my / 2) + ry + height > original->height)
+	       continue;
+	    if (*mx + rx < (int) -sr || *mx + rx >= (int) sr ||
+		*my + ry < (int) -sr || *my + ry >= (int) sr) 
+	       continue;		/* out of bounds */
+
+	    /*
+	     *  Compute costs of new motion compensation
+	     */
+	    extract_mc_block (mcblock, width, height,
+			      reference->pixels [GRAY],
+			      reference->width, wi->half_pixel,
+			      x0, y0, *mx + rx, *my + ry);
+	    costs = mcpe_norm (mt->original, x0, y0, width, height, mcblock,
+			       NULL)
+		    + (mt->xbits [*mx + rx + sr * bitshift]
+		       + mt->ybits [*my + ry + sr * bitshift]) * price;
+	    if (costs < mincosts)
+	    {
+	       bestrx   = rx;
+	       bestry   = ry;
+	       mincosts = costs;
+	    }
+	 }
+
+      *mx += bestrx;
+      *my += bestry;
+
+      Free (mcblock);
+   } /* halfpixel */
+	     
+   *bits = mt->xbits [*mx + sr * bitshift] + mt->ybits [*my + sr * bitshift];
+
+   return mincosts;
+}
+
+static real_t
+find_second_mv (real_t price, const image_t *original,
+		const image_t *reference, const word_t *mcblock1,
+		unsigned xr, unsigned yr, unsigned width, unsigned height,
+		real_t *bits, int *mx, int *my, const wfa_info_t *wi,
+		const motion_t *mt)
+/*
+ *  Search local area (*mx,*my) for best additional mv.
+ *  Overwrite mt->tmpblock.
+ *  TODO check sr = search_range
+ *
+ *  Return values:
+ *	prediction costs
+ *
+ *  Side effects:
+ *	'mx','my'	coordinates of mv
+ *      'bits'		number of bits to encode mv
+ */
+{
+   real_t    mincosts = MAXCOSTS;	/* best costs so far  */
+   unsigned  sr;			/* MV search range +/- 'sr' pixels */
+   int       x, y;			/* coordinates of motion vector */
+   int       y0, y1, x0, x1;		/* start/end coord. of search range */
+   unsigned  bitshift;			/* half_pixel coordinates multiplier */
+   word_t   *mcblock2 = Calloc (width * height, sizeof (word_t));
+
+   sr = wi->search_range;
+
+   y0 = max ((int) -sr, *my - (int) local_range);
+   y1 = min ((int) sr, *my + (int) local_range);
+   x0 = max ((int) -sr, *mx - (int) local_range);
+   x1 = min ((int) sr, *mx + (int) local_range);
+
+   *mx = *my = 0;
+
+   bitshift = (wi->half_pixel ? 2 : 1);	/* bit0 reserved for halfpixel pred. */
+
+   
+   for (y = y0; y < y1; y++)
+      for (x = x0; x < x1; x++)
+      {
+	 real_t costs;			/* costs arising if mv is chosen */
+	 
+	 /*
+	  *  Test each mv ('x', 'y') in the given search range:
+	  *  Get the new motion compensation image from 'reference' and compute
+	  *  the norm of the motion compensation prediction error
+	  *  'original' - 0.5 * ('firstmc' + 'reference')
+	  */
+	 if ((int) (xr * bitshift) + x < 0 ||	/* outside visible area */
+	     xr * bitshift + x > (original->width - width) * bitshift ||
+	     (int) (yr * bitshift) + y < 0 ||
+	     yr * bitshift + y > (original->height - height) * bitshift)
+	    continue;
+	 
+	 extract_mc_block (mcblock2, width, height,
+			   reference->pixels [GRAY], reference->width,
+			   wi->half_pixel, x0, y0, x, y);
+
+	 costs  = mcpe_norm (mt->original, x0, y0, width, height,
+			     mcblock1, mcblock2)
+		  + (mt->xbits [x + sr] + mt->ybits [y + sr]) * price;
+	 
+	 if (costs < mincosts)
+	 {
+	    mincosts = costs;
+	    *mx      = x;
+	    *my      = y;
+	 }
+      }
+
+   *bits = mt->xbits [*mx + sr] + mt->ybits [*my + sr];
+
+   Free (mcblock2);
+
+   return mincosts;
+}
diff --git a/converter/other/fiasco/codec/mwfa.h b/converter/other/fiasco/codec/mwfa.h
new file mode 100644
index 00000000..52f41866
--- /dev/null
+++ b/converter/other/fiasco/codec/mwfa.h
@@ -0,0 +1,44 @@
+/*
+ *  mwfa.h
+ *
+ *  Written by:		Michael Unger
+ *			Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _MWFA_H
+#define _MWFA_H
+
+#include "types.h"
+#include "image.h"
+#include "wfa.h"
+#include "cwfa.h"
+
+void
+fill_norms_table (unsigned x0, unsigned y0, unsigned level,
+		  const wfa_info_t *wi, motion_t *mt);
+void
+find_B_frame_mc (word_t *mcpe, real_t price, range_t *range,
+		 const wfa_info_t *wi, const motion_t *mt);
+void
+find_P_frame_mc (word_t *mcpe, real_t price, range_t *range,
+		 const wfa_info_t *wi, const motion_t *mt);
+void
+subtract_mc (image_t *image, const image_t *past, const image_t *future,
+	     const wfa_t *wfa);
+void
+free_motion (motion_t *mt);
+motion_t *
+alloc_motion (const wfa_info_t *wi);
+
+#endif /* not _MWFA_H */
+
diff --git a/converter/other/fiasco/codec/options.c b/converter/other/fiasco/codec/options.c
new file mode 100644
index 00000000..77dbaf00
--- /dev/null
+++ b/converter/other/fiasco/codec/options.c
@@ -0,0 +1,894 @@
+/*
+ *  options.c:		FIASCO options handling
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/10/28 17:39:31 $
+ *  $Author: hafner $
+ *  $Revision: 5.5 $
+ *  $State: Exp $
+ */
+
+#define _BSD_SOURCE 1   /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include "config.h"
+
+#include <string.h>
+#if STDC_HEADERS
+#	include <stdlib.h>
+#endif /* not STDC_HEADERS */
+
+#include <stdio.h>
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "wfa.h"
+#include "misc.h"
+#include "bit-io.h"
+#include "fiasco.h"
+#include "options.h"
+
+fiasco_c_options_t *
+fiasco_c_options_new (void)
+/*
+ *  FIASCO options constructor.
+ *  Allocate memory for the FIASCO coder options structure and
+ *  fill in default values.
+ *
+ *  Return value:
+ *	pointer to the new option structure
+ */
+{
+   c_options_t 	      *options = calloc (1, sizeof (c_options_t));
+   fiasco_c_options_t *public  = calloc (1, sizeof (fiasco_c_options_t));
+
+   if (!options || !public)
+   {
+      set_error (_("Out of memory."));
+      return NULL;
+   }
+   public->private 	      = options;
+   public->delete 	      = fiasco_c_options_delete;
+   public->set_tiling 	      = fiasco_c_options_set_tiling;
+   public->set_frame_pattern  = fiasco_c_options_set_frame_pattern;
+   public->set_basisfile      = fiasco_c_options_set_basisfile;
+   public->set_chroma_quality = fiasco_c_options_set_chroma_quality;
+   public->set_optimizations  = fiasco_c_options_set_optimizations;
+   public->set_video_param    = fiasco_c_options_set_video_param;
+   public->set_quantization   = fiasco_c_options_set_quantization;
+   public->set_progress_meter = fiasco_c_options_set_progress_meter;
+   public->set_smoothing      = fiasco_c_options_set_smoothing;
+   public->set_title   	      = fiasco_c_options_set_title;
+   public->set_comment        = fiasco_c_options_set_comment;
+   
+   strcpy (options->id, "COFIASCO");
+
+   /*
+    *  Set default value of fiasco options
+    */
+   options->basis_name 		  = strdup ("small.fco");
+   options->lc_min_level 	  = 4;
+   options->lc_max_level 	  = 12;
+   options->p_min_level 	  = 8;
+   options->p_max_level 	  = 10;
+   options->images_level 	  = 5;
+   options->max_states 		  = MAXSTATES;
+   options->chroma_max_states 	  = 40;
+   options->max_elements 	  = MAXEDGES;
+   options->tiling_exponent 	  = 4;
+   options->tiling_method 	  = FIASCO_TILING_VARIANCE_DSC;
+   options->id_domain_pool 	  = strdup ("rle");
+   options->id_d_domain_pool 	  = strdup ("rle");
+   options->id_rpf_model 	  = strdup ("adaptive");
+   options->id_d_rpf_model 	  = strdup ("adaptive");
+   options->rpf_mantissa 	  = 3;
+   options->rpf_range 		  = FIASCO_RPF_RANGE_1_50;
+   options->dc_rpf_mantissa 	  = 5;
+   options->dc_rpf_range 	  = FIASCO_RPF_RANGE_1_00;
+   options->d_rpf_mantissa 	  = 3;
+   options->d_rpf_range 	  = FIASCO_RPF_RANGE_1_50;
+   options->d_dc_rpf_mantissa 	  = 5;
+   options->d_dc_rpf_range 	  = FIASCO_RPF_RANGE_1_00;
+   options->chroma_decrease 	  = 2.0;
+   options->prediction 		  = NO;
+   options->delta_domains 	  = YES;
+   options->normal_domains 	  = YES;
+   options->search_range 	  = 16;
+   options->fps 		  = 25;
+   options->pattern 		  = strdup ("IPPPPPPPPP");
+   options->reference_filename 	  = NULL;
+   options->half_pixel_prediction = NO;
+   options->cross_B_search 	  = YES;
+   options->B_as_past_ref 	  = YES;
+   options->check_for_underflow   = NO;
+   options->check_for_overflow 	  = NO;
+   options->second_domain_block   = NO;
+   options->full_search 	  = NO;
+   options->progress_meter 	  = FIASCO_PROGRESS_NONE;
+   options->smoothing 	 	  = 70;
+   options->comment 		  = strdup ("");
+   options->title 		  = strdup ("");
+   
+   return public;
+}
+
+void
+fiasco_c_options_delete (fiasco_c_options_t *options)
+/*
+ *  FIASCO options destructor.
+ *  Free memory of FIASCO options struct.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	structure 'options' is discarded.
+ */
+{
+   c_options_t *this = cast_c_options (options);
+
+   if (!this)
+      return;
+   
+   Free (this->id_domain_pool);
+   Free (this->id_d_domain_pool);
+   Free (this->id_rpf_model);
+   Free (this->id_d_rpf_model);
+   Free (this->pattern);
+   Free (this->comment);
+   Free (this->title);
+   
+   Free (this);
+
+   return;
+}
+
+int
+fiasco_c_options_set_tiling (fiasco_c_options_t *options,
+			     fiasco_tiling_e method, unsigned exponent)
+/*
+ *  Set tiling `method' and `exponent'.
+ *  See type `fiasco_tiling_e' for a list of valid  tiling `methods'.
+ *  The image is subdivied into 2^`exponent' tiles
+ *
+ *  Return value:
+ *	1 on success
+ *	0 otherwise
+ */
+{
+   c_options_t *this = (c_options_t *) cast_c_options (options);
+
+   if (!this)
+   {
+      return 0;
+   }
+   switch (method)
+   {
+      case FIASCO_TILING_SPIRAL_ASC:
+      case FIASCO_TILING_SPIRAL_DSC:
+      case FIASCO_TILING_VARIANCE_ASC:
+      case FIASCO_TILING_VARIANCE_DSC:
+	 this->tiling_method = method;
+	 break;
+      default:
+	 set_error (_("Invalid tiling method `%d' specified "
+		      "(valid methods are 0, 1, 2, or 3)."), method);
+	 return 0;
+   }
+   this->tiling_exponent = exponent;
+   
+   return 1;
+}
+
+int
+fiasco_c_options_set_frame_pattern (fiasco_c_options_t *options,
+				    const char *pattern)
+/*
+ *  Set `pattern' of input frames.
+ *  `pattern' has to be a sequence of the following
+ *  characters (case insensitive):
+ *  'i' intra frame
+ *  'p' predicted frame
+ *  'b' bidirectional predicted frame
+ *  E.g. pattern = 'IBBPBBPBB'
+ *
+ *  When coding video frames the prediction type of input frame N is determined
+ *  by reading `pattern' [N] (`pattern' is periodically extended).
+ *  
+ *  Return value:
+ *	1 on success
+ *	0 otherwise
+ */
+{
+   c_options_t *this = (c_options_t *) cast_c_options (options);
+
+   if (!this)
+   {
+      return 0;
+   }
+   else if (!pattern)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "pattern");
+      return 0;
+   }
+   else if (strlen (pattern) < 1)
+   {
+      set_error (_("Frame type pattern doesn't contain any character."));
+      return 0;
+   }
+   else
+   {
+      const char *str;
+      bool_t 	  parse_error = NO;
+      int	  c 	      = 0;
+      
+      for (str = pattern; *str && !parse_error; str++)
+	 switch (*str)
+	 {
+	    case 'i':
+	    case 'I':
+	    case 'b':
+	    case 'B':
+	    case 'p':
+	    case 'P':
+	       break;
+	    default:
+	       c = *str;
+	       parse_error = YES;
+	 }
+
+      if (parse_error)
+      {
+	 set_error (_("Frame type pattern contains invalid character `%c' "
+		      "(choose I, B or P)."), c);
+	 return 0;
+      }
+      else
+      {
+	 Free (this->pattern);
+	 this->pattern = strdup (pattern);
+
+	 return 1;
+      }
+   }
+}
+
+int
+fiasco_c_options_set_basisfile (fiasco_c_options_t *options,
+				const char *filename)
+/*
+ *  Set `filename' of FIASCO initial basis.
+ *  
+ *  Return value:
+ *	1 on success (if the file is readable)
+ *	0 otherwise
+ */
+{
+   c_options_t *this = (c_options_t *) cast_c_options (options);
+
+   if (!this)
+   {
+      return 0;
+   }
+   else if (!filename)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "filename");
+      return 0;
+   }
+   else
+   {
+       /* Skip this because basis file may be linked with program, not
+          in a separate file.  See get_linked_basis().  NETPBM
+      FILE *file = open_file (filename, "FIASCO_DATA", READ_ACCESS);
+      if (file)
+      {
+	 fclose (file);
+	 return 1;
+      }
+      else
+      {
+	 set_error (_("Can't read basis file `%s'.\n%s."), filename,
+		    get_system_error ());
+	 return 0;
+      }
+      */ return 1;
+   }
+}
+
+int
+fiasco_c_options_set_chroma_quality (fiasco_c_options_t *options,
+				     float quality_factor,
+				     unsigned dictionary_size)
+/*
+ *  Set color compression parameters.
+ *  When coding chroma channels (Cb and Cr)
+ *  - approximation quality is given by `quality_factor' * `Y quality' and
+ *  - `dictionary_size' gives the number of dictionary elements.
+ *  
+ *  If 'quality' <= 0 then the luminancy coding quality is also during
+ *  chroma channel coding.
+ *  
+ *  Return value:
+ *	1 on success
+ *	0 otherwise
+ */
+{
+   c_options_t *this = (c_options_t *) cast_c_options (options);
+
+   if (!this)
+   {
+      return 0;
+   }
+   else if (!dictionary_size)
+   {
+      set_error (_("Size of chroma compression dictionary has to be "
+		   "a positive number."));
+      return 0;
+   }
+   else if (quality_factor <= 0)
+   {
+      set_error (_("Quality of chroma channel compression has to be "
+		   "positive value."));
+      return 0;
+   }
+   else
+   {
+      this->chroma_decrease   = quality_factor;
+      this->chroma_max_states = dictionary_size;
+
+      return 1;
+   }
+}
+
+int
+fiasco_c_options_set_optimizations (fiasco_c_options_t *options,
+				    unsigned min_block_level,
+				    unsigned max_block_level,
+				    unsigned max_elements,
+				    unsigned dictionary_size,
+				    unsigned optimization_level)
+/*
+ *  Set various optimization parameters.
+ *  - During compression only image blocks of size
+ *    {`min_block_level', ... ,`max_block_level'} are considered.
+ *    The smaller this set of blocks is the faster the coder runs
+ *    and the worse the image quality will be.  
+ *  - An individual approximation may use at most `max_elements'
+ *    elements of the dictionary which itself contains at most
+ *    `dictionary_size' elements. The smaller these values are
+ *    the faster the coder runs and the worse the image quality will be. 
+ *  - `optimization_level' enables some additional low level optimizations.
+ *    0: standard approximation method
+ *    1: significantly increases the approximation quality,
+ *       running time is twice as high as with the standard method
+ *    2: hardly increases the approximation quality of method 1, 
+ *       running time is twice as high as with method 1
+ *       (this method just remains for completeness)
+ *
+ *  Return value:
+ *	1 on success
+ *	0 otherwise
+ */
+{
+   c_options_t *this = (c_options_t *) cast_c_options (options);
+
+   if (!this)
+   {
+      return 0;
+   }
+   else if (!dictionary_size)
+   {
+      set_error (_("Size of dictionary has to be a positive number."));
+      return 0;
+   }
+   else if (!max_elements)
+   {
+      set_error (_("At least one dictionary element has to be used "
+		   "in an approximation."));
+      return 0;
+   }
+   else if (max_block_level < 4)
+   {
+      set_error (_("Maximum image block size has to be at least level 4."));
+      return 0;
+   }
+   else if (min_block_level < 4)
+   {
+      set_error (_("Minimum image block size has to be at least level 4."));
+      return 0;
+   }
+   else if (max_block_level < min_block_level)
+   {
+      set_error (_("Maximum block size has to be larger or "
+		   "equal minimum block size."));
+      return 0;
+   }
+   else
+   {
+      this->lc_min_level 	= min_block_level;
+      this->lc_max_level 	= max_block_level;
+      this->max_states 	 	= dictionary_size;
+      this->max_elements 	= max_elements;
+      this->second_domain_block = optimization_level > 0 ? YES : NO;
+      this->check_for_overflow  = optimization_level > 1 ? YES : NO;
+      this->check_for_underflow = optimization_level > 1 ? YES : NO;
+      this->full_search 	= optimization_level > 1 ? YES : NO;
+
+      return 1;
+   }
+}
+
+int
+fiasco_c_options_set_prediction (fiasco_c_options_t *options,
+				 int intra_prediction,
+				 unsigned min_block_level,
+				 unsigned max_block_level)
+/*
+ *  Set minimum and maximum size of image block prediction to
+ *  `min_block_level' and `max_block_level'.
+ *  (For either motion compensated prediction of inter frames
+ *   or DC based prediction of intra frames)
+ *  Prediction of intra frames is only used if `intra_prediction' != 0.
+ *
+ *  Return value:
+ *	1 on success
+ *	0 otherwise
+ */
+{
+   c_options_t *this = (c_options_t *) cast_c_options (options);
+
+   if (!this)
+   {
+      return 0;
+   }
+   else if (max_block_level < 6)
+   {
+      set_error (_("Maximum prediction block size has to be "
+		   "at least level 6"));
+      return 0;
+   }
+   else if (min_block_level < 6)
+   {
+      set_error (_("Minimum prediction block size has to be "
+		   "at least level 6"));
+      return 0;
+   }
+   else if (max_block_level < min_block_level)
+   {
+      set_error (_("Maximum prediction block size has to be larger or "
+		   "equal minimum block size."));
+      return 0;
+   }
+   else
+   {
+      this->p_min_level = min_block_level;
+      this->p_max_level = max_block_level;
+      this->prediction  = intra_prediction;
+      
+      return 1;
+   }
+}
+
+int
+fiasco_c_options_set_video_param (fiasco_c_options_t *options,
+				  unsigned frames_per_second,
+				  int half_pixel_prediction,
+				  int cross_B_search,
+				  int B_as_past_ref)
+/*
+ *  Set various parameters used for video compensation.
+ *  'frames_per_second' defines the frame rate which should be
+ *  used when the video is decoded. This value has no effect during coding,
+ *  it is just passed to the FIASCO output file.
+ *  If 'half_pixel_prediction' is not 0 then half pixel precise
+ *  motion compensated prediction is used.
+ *  If 'cross_B_search' is not 0 then the fast Cross-B-Search algorithm is
+ *  used to determine the motion vectors of interpolated prediction. Otherwise
+ *  exhaustive search (in the given search range) is used.
+ *  If 'B_as_past_ref' is not 0 then B frames are allowed to be used
+ *  for B frame predicion.
+ *  
+ *  Return value:
+ *	1 on success
+ *	0 otherwise
+ */
+{
+   c_options_t *this = (c_options_t *) cast_c_options (options);
+
+   if (!this)
+   {
+      return 0;
+   }
+   else
+   {
+      this->fps 	  	  = frames_per_second;
+      this->half_pixel_prediction = half_pixel_prediction;
+      this->cross_B_search 	  = cross_B_search;
+      this->B_as_past_ref 	  = B_as_past_ref;
+
+      return 1;
+   }
+}
+
+int
+fiasco_c_options_set_quantization (fiasco_c_options_t *options,
+				   unsigned mantissa,
+				   fiasco_rpf_range_e range,
+				   unsigned dc_mantissa,
+				   fiasco_rpf_range_e dc_range)
+/*
+ *  Set accuracy of coefficients quantization.
+ *  DC coefficients (of the constant dictionary vector f(x,y) = 1)
+ *  are quantized to values of the interval [-`dc_range', `dc_range'] using
+ *  #`dc_mantissa' bits. All other quantized coefficients are quantized in
+ *  an analogous way using the parameters `range' and `mantissa'.
+ *  
+ *  Return value:
+ *	1 on success
+ *	0 otherwise
+ */
+{
+   c_options_t *this = (c_options_t *) cast_c_options (options);
+
+   if (!this)
+   {
+      return 0;
+   }
+   else if (mantissa < 2 || mantissa > 8 || dc_mantissa < 2 || dc_mantissa > 8)
+   {
+      set_error (_("Number of RPF mantissa bits `%d', `%d' have to be in "
+		   "the interval [2,8]."), mantissa, dc_mantissa);
+      return 0;
+   }
+   else
+   {
+      if ((range == FIASCO_RPF_RANGE_0_75
+	  || range == FIASCO_RPF_RANGE_1_00
+	  || range == FIASCO_RPF_RANGE_1_50
+	   || range == FIASCO_RPF_RANGE_2_00)
+	  &&
+	  (dc_range == FIASCO_RPF_RANGE_0_75
+	   || dc_range == FIASCO_RPF_RANGE_1_00
+	   || dc_range == FIASCO_RPF_RANGE_1_50
+	   || dc_range == FIASCO_RPF_RANGE_2_00))
+      {
+	 this->rpf_range       = range;
+	 this->dc_rpf_range    = dc_range;
+	 this->rpf_mantissa    = mantissa;
+	 this->dc_rpf_mantissa = dc_mantissa;
+
+	 return 1;
+      }
+      else
+      {
+	 set_error (_("Invalid RPF ranges `%d', `%d' specified."),
+		    range, dc_range);
+	 return 0;
+      }
+   }
+}
+
+int
+fiasco_c_options_set_progress_meter (fiasco_c_options_t *options,
+				     fiasco_progress_e type)
+/*
+ *  Set type of progress meter.
+ *
+ *  Return value:
+ *	1 on success
+ *	0 otherwise
+ */
+{
+   c_options_t *this = (c_options_t *) cast_c_options (options);
+
+   if (!this)
+   {
+      return 0;
+   }
+   switch (type)
+   {
+      case FIASCO_PROGRESS_BAR:
+      case FIASCO_PROGRESS_PERCENT:
+      case FIASCO_PROGRESS_NONE:
+	 this->progress_meter = type;
+	 break;
+      default:
+	 set_error (_("Invalid progress meter `%d' specified "
+		      "(valid values are 0, 1, or 2)."), type);
+	 return 0;
+   }
+   return 1;
+}
+
+int
+fiasco_c_options_set_smoothing (fiasco_c_options_t *options, int smoothing)
+/*
+ *  Define `smoothing'-percentage along partitioning borders.
+ *  
+ *  Return value:
+ *	1 on success
+ *	0 otherwise
+ */
+{
+   c_options_t *this = (c_options_t *) cast_c_options (options);
+
+   if (!this)
+   {
+      return 0;
+   }
+   else if (smoothing < -1 || smoothing > 100)
+   {
+      set_error (_("Smoothing percentage must be in the range [-1, 100]."));
+      return 0;
+   }
+   else
+   {
+      this->smoothing = smoothing;
+      return 1;
+   }
+}
+
+int
+fiasco_c_options_set_comment (fiasco_c_options_t *options, const char *comment)
+/*
+ *  Define `comment' of FIASCO stream.
+ *  
+ *  Return value:
+ *	1 on success
+ *	0 otherwise
+ */
+{
+   c_options_t *this = (c_options_t *) cast_c_options (options);
+
+   if (!this)
+   {
+      return 0;
+   }
+   else if (!comment)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "title");
+      return 0;
+   }
+   else
+   {
+      this->comment = strdup (comment);
+      return 1;
+   }
+}
+
+int
+fiasco_c_options_set_title (fiasco_c_options_t *options, const char *title)
+/*
+ *  Define `title' of FIASCO stream.
+ *  
+ *  Return value:
+ *	1 on success
+ *	0 otherwise
+ */
+{
+   c_options_t *this = (c_options_t *) cast_c_options (options);
+
+   if (!this)
+   {
+      return 0;
+   }
+   else if (!title)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "title");
+      return 0;
+   }
+   else
+   {
+      this->title = strdup (title);
+      return 1;
+   }
+}
+
+c_options_t *
+cast_c_options (fiasco_c_options_t *options)
+/*
+ *  Cast generic pointer `options' to type c_options_t.
+ *  Check whether `options' is a valid object of type c_options_t.
+ *
+ *  Return value:
+ *	pointer to options struct on success
+ *      NULL otherwise
+ */
+{
+   c_options_t *this = (c_options_t *) options->private;
+   if (this)
+   {
+      if (!streq (this->id, "COFIASCO"))
+      {
+	 set_error (_("Parameter `options' doesn't match required type."));
+	 return NULL;
+      }
+   }
+   else
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "options");
+   }
+
+   return this;
+}
+
+/**************************************************************************
+***************************************************************************
+			       DECODER
+***************************************************************************
+**************************************************************************/
+
+fiasco_d_options_t *
+fiasco_d_options_new (void)
+/*
+ *  FIASCO options constructor.
+ *  Allocate memory for the FIASCO coder options structure and
+ *  fill in default values.
+ *
+ *  Return value:
+ *	pointer to the new option structure
+ */
+{
+   d_options_t 	      *options = calloc (1, sizeof (d_options_t));
+   fiasco_d_options_t *public  = calloc (1, sizeof (fiasco_d_options_t));
+
+   if (!options || !public)
+   {
+      set_error (_("Out of memory."));
+      return NULL;
+   }
+   public->private 	      = options;
+   public->delete 	      = fiasco_d_options_delete;
+   public->set_smoothing      = fiasco_d_options_set_smoothing;
+   public->set_magnification  = fiasco_d_options_set_magnification;
+   public->set_4_2_0_format   = fiasco_d_options_set_4_2_0_format;
+   
+   strcpy (options->id, "DOFIASCO");
+
+   /*
+    *  Set default value of fiasco decoder options
+    */
+   options->smoothing 	  = 70;
+   options->magnification = 0;
+   options->image_format  = FORMAT_4_4_4;
+   
+   return public;
+}
+
+void
+fiasco_d_options_delete (fiasco_d_options_t *options)
+/*
+ *  FIASCO options destructor.
+ *  Free memory of FIASCO options struct.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	structure 'options' is discarded.
+ */
+{
+   d_options_t *this = cast_d_options (options);
+
+   if (!this)
+      return;
+   
+   Free (this);
+
+   return;
+}
+
+int
+fiasco_d_options_set_smoothing (fiasco_d_options_t *options, int smoothing)
+/*
+ *  Define `smoothing'-percentage along partitioning borders.
+ *  
+ *  Return value:
+ *	1 on success
+ *	0 otherwise
+ */
+{
+   d_options_t *this = (d_options_t *) cast_d_options (options);
+
+   if (!this)
+   {
+      return 0;
+   }
+   else if (smoothing < -1 || smoothing > 100)
+   {
+      set_error (_("Smoothing percentage must be in the range [-1, 100]."));
+      return 0;
+   }
+   else
+   {
+      this->smoothing = smoothing;
+      return 1;
+   }
+}
+
+int
+fiasco_d_options_set_magnification (fiasco_d_options_t *options, int level)
+/*
+ *  Set magnification-'level' of decoded image.
+ *  0: width x height of original image
+ *  1: (2 * width) x (2 * height) of original image
+ *  -1: (width / 2 ) x (height / 2) of original image
+ *  etc.
+ *
+ *  Return value:
+ *	1 on success
+ *	0 otherwise
+ */
+{
+   d_options_t *this = (d_options_t *) cast_d_options (options);
+
+   if (!this)
+   {
+      return 0;
+   }
+   else
+   {
+      this->magnification = level;
+      return 1;
+   }
+}
+
+int
+fiasco_d_options_set_4_2_0_format (fiasco_d_options_t *options, int format)
+/*
+ *  Set image format to 4:2:0 or 4:4:4.
+ *  
+ *  Return value:
+ *	1 on success
+ *	0 otherwise
+ */
+{
+   d_options_t *this = (d_options_t *) cast_d_options (options);
+
+   if (!this)
+   {
+      return 0;
+   }
+   else
+   {
+      this->image_format = format ? FORMAT_4_2_0 : FORMAT_4_4_4;
+      return 1;
+   }
+}
+
+d_options_t *
+cast_d_options (fiasco_d_options_t *options)
+/*
+ *  Cast generic pointer `options' to type d_options_t.
+ *  Check whether `options' is a valid object of type d_options_t.
+ *
+ *  Return value:
+ *	pointer to options struct on success
+ *      NULL otherwise
+ */
+{
+   d_options_t *this = (d_options_t *) options->private;
+   
+   if (this)
+   {
+      if (!streq (this->id, "DOFIASCO"))
+      {
+	 set_error (_("Parameter `options' doesn't match required type."));
+	 return NULL;
+      }
+   }
+   else
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "options");
+   }
+
+   return this;
+}
+
+
diff --git a/converter/other/fiasco/codec/options.h b/converter/other/fiasco/codec/options.h
new file mode 100644
index 00000000..3af6be01
--- /dev/null
+++ b/converter/other/fiasco/codec/options.h
@@ -0,0 +1,80 @@
+/*
+ *  options.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/10/28 17:39:31 $
+ *  $Author: hafner $
+ *  $Revision: 5.4 $
+ *  $State: Exp $
+ */
+
+#ifndef _OPTIONS_H
+#define _OPTIONS_H
+
+typedef struct c_options
+{
+   char      	       id [9];
+   char  	      *basis_name;
+   unsigned  	       lc_min_level;
+   unsigned  	       lc_max_level;
+   unsigned  	       p_min_level;
+   unsigned  	       p_max_level;
+   unsigned  	       images_level;
+   unsigned  	       max_states;
+   unsigned  	       chroma_max_states;
+   unsigned  	       max_elements;
+   unsigned  	       tiling_exponent;
+   fiasco_tiling_e     tiling_method;
+   char        	      *id_domain_pool;
+   char        	      *id_d_domain_pool;
+   char        	      *id_rpf_model;
+   char        	      *id_d_rpf_model;
+   unsigned  	       rpf_mantissa;
+   real_t    	       rpf_range;
+   unsigned  	       dc_rpf_mantissa;
+   fiasco_rpf_range_e  dc_rpf_range;
+   unsigned  	       d_rpf_mantissa;
+   fiasco_rpf_range_e  d_rpf_range;
+   unsigned  	       d_dc_rpf_mantissa;
+   fiasco_rpf_range_e  d_dc_rpf_range;
+   real_t    	       chroma_decrease;
+   bool_t    	       prediction;
+   bool_t    	       delta_domains;
+   bool_t    	       normal_domains;
+   unsigned  	       search_range;
+   unsigned  	       fps;
+   char        	      *pattern;
+   char        	      *reference_filename;
+   bool_t    	       half_pixel_prediction;
+   bool_t    	       cross_B_search;
+   bool_t    	       B_as_past_ref;
+   bool_t    	       check_for_underflow;
+   bool_t    	       check_for_overflow;
+   bool_t    	       second_domain_block;
+   bool_t    	       full_search;
+   fiasco_progress_e   progress_meter;
+   char 	      *title;
+   char 	      *comment;
+   unsigned    	       smoothing;
+} c_options_t;
+
+typedef struct d_options
+{
+   char     id [9];
+   unsigned smoothing;
+   unsigned magnification;
+   format_e image_format;
+} d_options_t;
+
+c_options_t *
+cast_c_options (fiasco_c_options_t *options);
+d_options_t *
+cast_d_options (fiasco_d_options_t *options);
+
+#endif /* not _OPTIONS_H */
diff --git a/converter/other/fiasco/codec/prediction.c b/converter/other/fiasco/codec/prediction.c
new file mode 100644
index 00000000..351ba9df
--- /dev/null
+++ b/converter/other/fiasco/codec/prediction.c
@@ -0,0 +1,629 @@
+/*
+ *  prediction.c:	Range image prediction with MC or ND	
+ *
+ *  Written by:		Ullrich Hafner
+ *			Michael Unger
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#if HAVE_STRING_H
+#	include <string.h>
+#else /* not HAVE_STRING_H */
+#	include <strings.h>
+#endif /* not HAVE_STRING_H */
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "cwfa.h"
+#include "ip.h"
+#include "control.h"
+#include "misc.h"
+#include "subdivide.h"
+#include "bintree.h"
+#include "domain-pool.h"
+#include "approx.h"
+#include "wfalib.h"
+#include "mwfa.h"
+#include "prediction.h"
+
+#include "decoder.h"
+
+
+/*****************************************************************************
+
+			     local variables
+  
+*****************************************************************************/
+
+typedef struct state_data
+{
+   real_t final_distribution;
+   byte_t level_of_state;
+   byte_t domain_type;
+
+   real_t *images_of_state;
+   real_t *inner_products;
+   real_t *ip_states_state [MAXLEVEL];
+
+   word_t tree [MAXLABELS];
+   mv_t	  mv_tree [MAXLABELS];
+   word_t y_state [MAXLABELS];
+   byte_t y_column [MAXLABELS];
+   byte_t prediction [MAXLABELS];
+
+   u_word_t x [MAXLABELS];
+   u_word_t y [MAXLABELS];
+
+   real_t weight [MAXLABELS][MAXEDGES + 1];
+   word_t int_weight [MAXLABELS][MAXEDGES + 1];
+   word_t into [MAXLABELS][MAXEDGES + 1];
+} state_data_t;
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static real_t
+nd_prediction (real_t max_costs, real_t price, unsigned band, int y_state,
+	       range_t *range, wfa_t *wfa, coding_t *c);
+static real_t
+mc_prediction (real_t max_costs, real_t price, unsigned band, int y_state,
+	       range_t *range, wfa_t *wfa, coding_t *c);
+static state_data_t *
+store_state_data (unsigned from, unsigned to, unsigned max_level,
+		  wfa_t *wfa, coding_t *c);
+static void
+restore_state_data (unsigned from, unsigned to, unsigned max_level,
+		    state_data_t *data, wfa_t *wfa, coding_t *c);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+ 
+real_t
+predict_range (real_t max_costs, real_t price, range_t *range, wfa_t *wfa,
+	       coding_t *c, unsigned band, int y_state, unsigned states,
+	       const tree_t *tree_model, const tree_t *p_tree_model,
+	       const void *domain_model, const void *d_domain_model,
+	       const void *coeff_model, const void *d_coeff_model)
+{
+   unsigned	 state;		     	/* counter */
+   void		*rec_domain_model;   	/* domain model after recursion */
+   void		*rec_d_domain_model; 	/* p domain model after recursion */
+   void		*rec_coeff_model;    	/* coeff model after recursion */
+   void		*rec_d_coeff_model;  	/* d coeff model after recursion */
+   tree_t	 rec_tree_model;	/* tree_model after '' */
+   tree_t	 rec_p_tree_model;    	/* p_tree_model after '' */
+   unsigned	 rec_states;	     	/* wfa->states after '' */
+   real_t	*rec_pixels;	     	/* c->pixels after '' */
+   state_data_t	*rec_state_data;     	/* state_data struct after '' */
+   real_t	 costs;		     	/* current approximation costs */
+   unsigned	 level;		     	/* counter */
+   state_data_t	*sd;		     	/* pointer to state_data field */
+
+   /*
+    *  Store WFA data from state 'lc_states' to 'wfa->states' - 1 and
+    *  current state of probability models.
+    */
+   rec_domain_model   = c->domain_pool->model;
+   rec_d_domain_model = c->d_domain_pool->model;
+   rec_coeff_model    = c->coeff->model;
+   rec_d_coeff_model  = c->d_coeff->model;
+   rec_tree_model     = c->tree;
+   rec_p_tree_model   = c->p_tree;
+   rec_states         = wfa->states;	
+   rec_pixels         = c->pixels;
+   rec_state_data     = store_state_data (states, rec_states - 1,
+					  c->options.lc_max_level, wfa, c);
+   
+   /*
+    *  Restore probability models to the state before the recursive subdivision
+    *  has been started.
+    */
+   wfa->states             = states;
+   c->tree                 = *tree_model;
+   c->p_tree               = *p_tree_model;
+   c->domain_pool->model   = c->domain_pool->model_duplicate (domain_model);
+   c->d_domain_pool->model = c->d_domain_pool->model_duplicate (d_domain_model);
+   c->coeff->model   	   = c->coeff->model_duplicate (c->coeff, coeff_model);
+   c->d_coeff->model   	   = c->d_coeff->model_duplicate (c->d_coeff,
+							  d_coeff_model);
+   
+   if (c->mt->frame_type == I_FRAME)
+      costs = nd_prediction (max_costs, price, band, y_state, range, wfa, c); 
+   else
+      costs = mc_prediction (max_costs, price, band, y_state, range, wfa, c);
+   
+   c->pixels = rec_pixels;
+   
+   if (costs < MAXCOSTS)
+   {
+      /*
+       *  Free the memory used by the state_data struct
+       */
+      for (state = states; state < rec_states; state++)
+      {
+	 sd = &rec_state_data [state - states];
+	 for (level = c->options.images_level + 1;
+	      level <= c->options.lc_max_level; level++)
+	    if (sd->ip_states_state [level] != NULL)
+	       Free (sd->ip_states_state [level]);
+	 if (sd->images_of_state != NULL)
+	    Free (sd->images_of_state);
+	 if (sd->inner_products != NULL)
+	    Free (sd->inner_products);
+      }
+      if (states < rec_states)
+	 Free (rec_state_data);
+      c->domain_pool->model_free (rec_domain_model);
+      c->d_domain_pool->model_free (rec_d_domain_model);
+      c->coeff->model_free (rec_coeff_model);
+      c->d_coeff->model_free (rec_d_coeff_model);
+
+      costs = (range->tree_bits + range->matrix_bits + range->weights_bits
+	       + range->mv_tree_bits + range->mv_coord_bits
+	       + range->nd_tree_bits + range->nd_weights_bits) * price
+	      + range->err;
+   }
+   else
+   {
+      /*
+       *  Restore WFA to state before function was called
+       */
+      c->domain_pool->model_free (c->domain_pool->model);
+      c->d_domain_pool->model_free (c->d_domain_pool->model);
+      c->coeff->model_free (c->coeff->model);
+      c->d_coeff->model_free (c->d_coeff->model);
+      
+      c->domain_pool->model   = rec_domain_model;
+      c->d_domain_pool->model = rec_d_domain_model;
+      c->coeff->model         = rec_coeff_model;
+      c->d_coeff->model       = rec_d_coeff_model;
+      c->tree                 = rec_tree_model;
+      c->p_tree               = rec_p_tree_model;
+      
+      range->prediction = NO;
+      
+      if (wfa->states != states)
+	 remove_states (states, wfa);
+      restore_state_data (states, rec_states - 1, c->options.lc_max_level,
+			  rec_state_data, wfa, c);
+      costs = MAXCOSTS;
+   }
+ 
+   return costs;
+} 
+
+void
+clear_norms_table (unsigned level, const wfa_info_t *wi, motion_t *mt)
+/*
+ *  Clear norms arrays.
+ *
+ *  No return value.
+ */
+{
+   unsigned  range_size = wi->half_pixel
+			  ? square (wi->search_range)
+			  : square (2 * wi->search_range);
+
+   if (level > wi->p_min_level)
+   {
+      memset (mt->mc_forward_norms [level], 0, range_size * sizeof(real_t));
+      memset (mt->mc_backward_norms [level], 0, range_size * sizeof(real_t));
+   }
+}
+
+void
+update_norms_table (unsigned level, const wfa_info_t *wi, motion_t *mt)
+/*
+ *  Norms table of levels larger than the bottom level are computed
+ *  by summing up previously calculated displacement costs of lower levels.
+ *
+ *  No return value.
+ */
+{
+   unsigned  range_size = wi->half_pixel
+			  ? square (wi->search_range)
+			  : square (2 * wi->search_range);
+   
+   if (level > wi->p_min_level)
+   {
+      unsigned index;			/* index of motion vector */
+      
+      for (index = 0; index < range_size; index++)
+	 mt->mc_forward_norms [level][index]
+	    += mt->mc_forward_norms [level - 1][index];
+      if (mt->frame_type == B_FRAME)
+	 for (index = 0; index < range_size; index++)
+	    mt->mc_backward_norms [level][index]
+	       += mt->mc_backward_norms [level - 1][index];
+   }
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static real_t
+mc_prediction (real_t max_costs, real_t price, unsigned band, int y_state,
+	       range_t *range, wfa_t *wfa, coding_t *c)
+{
+   real_t    costs;		   	/* current approximation costs */
+   range_t   prange = *range;
+   unsigned  width  = width_of_level (range->level);
+   unsigned  height = height_of_level (range->level);
+   word_t   *mcpe   = Calloc (width * height, sizeof (word_t));
+
+   /*
+    *  If we are at the bottom level of the mc tree:
+    *  Fill in the norms table
+    */
+   if (prange.level == wfa->wfainfo->p_min_level) 
+      fill_norms_table (prange.x, prange.y, prange.level, wfa->wfainfo, c->mt);
+   /*
+    *  Predict 'range' with motion compensation according to frame type.
+    *  MCPE is returned in 'c->mcpe'
+    */
+   if (c->mt->frame_type == P_FRAME)
+      find_P_frame_mc (mcpe, price, &prange, wfa->wfainfo, c->mt);
+   else
+      find_B_frame_mc (mcpe, price, &prange, wfa->wfainfo, c->mt);
+   
+   costs = (prange.mv_tree_bits + prange.mv_coord_bits) * price;
+   
+   if (costs < max_costs)		/* motion vector not too expensive */
+   {
+      unsigned  last_state;		/* last WFA state before recursion */
+      real_t   *ipi [MAXSTATES];	/* inner products pointers */
+      unsigned  state;
+      real_t  	mvt, mvc;
+      
+      c->pixels = Calloc (width * height, sizeof (real_t));
+      cut_to_bintree (c->pixels, mcpe, width, height, 0, 0, width, height);
+   
+      /*
+       *  Approximate MCPE recursively.
+       */
+      last_state = wfa->states - 1;
+      for (state = 0; state <= last_state; state++)
+	 if (need_image (state, wfa))
+	 {
+	    ipi [state] = c->ip_images_state[state];
+	    c->ip_images_state[state]
+	       = Calloc (size_of_tree (c->products_level), sizeof (real_t));
+	 }
+
+      mvc = prange.mv_coord_bits;
+      mvt = prange.mv_tree_bits;
+      
+      prange.image           = 0;
+      prange.address         = 0;
+      prange.tree_bits       = 0;
+      prange.matrix_bits     = 0;
+      prange.weights_bits    = 0;
+      prange.mv_coord_bits   = 0;
+      prange.mv_tree_bits    = 0;
+      prange.nd_weights_bits = 0;
+      prange.nd_tree_bits    = 0;
+
+      compute_ip_images_state (prange.image, prange.address, prange.level,
+			       1, 0, wfa, c);
+      costs += subdivide (max_costs - costs, band, y_state, &prange,
+			  wfa, c, NO, YES);
+
+      if (costs < max_costs)		/* use motion compensation */
+      {
+	 unsigned img, adr;		/* temp. values */
+	 
+	 img                  = range->image;
+	 adr                  = range->address;
+	 *range               = prange;
+	 range->image         = img;
+	 range->address       = adr;
+	 range->mv_coord_bits = mvc;
+	 range->mv_tree_bits  = mvt;
+	 range->prediction    = YES;
+
+	 for (state = last_state + 1; state < wfa->states; state++)
+	    if (need_image (state, wfa))
+	       memset (c->ip_images_state [state], 0,
+		       size_of_tree (c->products_level) * sizeof (real_t));
+
+	 costs = (range->tree_bits + range->matrix_bits + range->weights_bits
+		  + range->mv_tree_bits + range->mv_coord_bits
+		  + range->nd_tree_bits + range->nd_weights_bits) * price
+		 + range->err;
+      }
+      else
+	 costs = MAXCOSTS;
+
+      for (state = 0; state <= last_state; state++)
+	 if (need_image (state, wfa))
+	 {
+	    Free (c->ip_images_state[state]);
+	    c->ip_images_state[state] = ipi [state];
+	 }
+      Free (c->pixels);
+   }
+   else
+      costs = MAXCOSTS;
+   
+   Free (mcpe);
+
+   return costs;
+}
+
+static real_t
+nd_prediction (real_t max_costs, real_t price, unsigned band, int y_state,
+	       range_t *range, wfa_t *wfa, coding_t *c)
+{
+   real_t  costs;			/* current approximation costs */
+   range_t lrange = *range;
+   
+   /*
+    *  Predict 'range' with DC component approximation
+    */
+   {
+      real_t x = get_ip_image_state (range->image, range->address,
+				     range->level, 0, c);
+      real_t y = get_ip_state_state (0, 0, range->level, c);
+      real_t w = btor (rtob (x / y, c->coeff->dc_rpf), c->coeff->dc_rpf);
+      word_t s [2] = {0, -1};
+
+      lrange.into [0] 	     = 0;
+      lrange.into [1] 	     = NO_EDGE;
+      lrange.weight [0]      = w;
+      lrange.mv_coord_bits   = 0;
+      lrange.mv_tree_bits    = 0;
+      lrange.nd_tree_bits    = tree_bits (LEAF, lrange.level, &c->p_tree);
+      lrange.nd_weights_bits = 0;
+      lrange.tree_bits       = 0;
+      lrange.matrix_bits     = 0;
+      lrange.weights_bits    = c->coeff->bits (&w, s, range->level, c->coeff);
+   }
+   costs = price * (lrange.weights_bits + lrange.nd_tree_bits);
+   
+   /*
+    *  Recursive aproximation of difference image
+    */
+   if (costs < max_costs)		
+   {
+      unsigned  state;
+      range_t  	rrange;			/* range: recursive subdivision */
+      unsigned  last_state;		/* last WFA state before recursion */
+      real_t   *ipi [MAXSTATES];	/* inner products pointers */
+      unsigned 	width  = width_of_level (range->level);
+      unsigned  height = height_of_level (range->level);
+      real_t   *pixels;
+
+      /*
+       *  Generate difference image original - approximation
+       */
+      {
+	 unsigned  n;
+	 real_t *src, *dst;		/* pointers to image data */
+	 real_t w = - lrange.weight [0] * c->images_of_state [0][0];
+		     
+	 src = c->pixels + range->address * size_of_level (range->level); 
+	 dst = c->pixels = pixels = Calloc (width * height, sizeof (real_t));
+
+	 for (n = width * height; n; n--)
+	    *dst++ = *src++ + w;
+      }
+      
+      /*
+       *  Approximate difference recursively.
+       */
+      rrange                 = *range;
+      rrange.tree_bits       = 0;
+      rrange.matrix_bits     = 0;
+      rrange.weights_bits    = 0;
+      rrange.mv_coord_bits   = 0;
+      rrange.mv_tree_bits    = 0;
+      rrange.nd_tree_bits    = 0;
+      rrange.nd_weights_bits = 0;
+      rrange.image           = 0;
+      rrange.address         = 0;
+
+      last_state = wfa->states - 1;
+      for (state = 0; state <= last_state; state++)
+	 if (need_image (state, wfa))
+	 {
+	    ipi [state] = c->ip_images_state[state];
+	    c->ip_images_state[state]
+	       = Calloc (size_of_tree (c->products_level), sizeof (real_t));
+	 }
+      
+      compute_ip_images_state (rrange.image, rrange.address, rrange.level,
+			       1, 0, wfa, c);
+      
+      costs += subdivide (max_costs - costs, band, y_state, &rrange, wfa, c,
+			  NO, YES);
+      
+      Free (pixels);
+
+      if (costs < max_costs && ischild (rrange.tree)) /* use prediction */
+      {
+	 unsigned img, adr;
+	 unsigned edge;
+
+	 img                     = range->image;
+	 adr                     = range->address;
+	 *range                  = rrange;
+	 range->image            = img;
+	 range->address          = adr;
+	 range->nd_tree_bits    += lrange.nd_tree_bits;
+	 range->nd_weights_bits += lrange.weights_bits;
+	 
+	 for (edge = 0; isedge (lrange.into [edge]); edge++)
+	 {
+	    range->into [edge]   = lrange.into [edge];
+	    range->weight [edge] = lrange.weight [edge];
+	 }
+	 range->into [edge] = NO_EDGE;
+	 range->prediction  = edge;
+
+	 for (state = last_state + 1; state < wfa->states; state++)
+	    if (need_image (state, wfa))
+	       memset (c->ip_images_state [state], 0,
+		       size_of_tree (c->products_level) * sizeof (real_t));
+      }
+      else
+	 costs = MAXCOSTS;
+      
+      for (state = 0; state <= last_state; state++)
+	 if (need_image (state, wfa))
+	 {
+	    Free (c->ip_images_state [state]);
+	    c->ip_images_state [state] = ipi [state];
+	 }
+   }
+   else
+      costs = MAXCOSTS;
+
+   return costs;
+}
+
+static state_data_t *
+store_state_data (unsigned from, unsigned to, unsigned max_level,
+		  wfa_t *wfa, coding_t *c)
+/*
+ *  Save and remove all states starting from state 'from'.
+ *
+ *  Return value:
+ *	pointer to array of state_data structs
+ */
+{
+   state_data_t *data;			/* array of savestates */
+   state_data_t *sd;			/* pointer to current savestates */
+   unsigned	 state, label, level;
+
+   if (to < from)
+      return NULL;			/* nothing to do */
+   
+   data = Calloc (to - from + 1, sizeof (state_data_t));
+   
+   for (state = from; state <= to; state++)
+   {
+      sd = &data [state - from];
+
+      sd->final_distribution = wfa->final_distribution [state];
+      sd->level_of_state     = wfa->level_of_state [state];
+      sd->domain_type        = wfa->domain_type [state];
+      sd->images_of_state    = c->images_of_state [state];
+      sd->inner_products     = c->ip_images_state [state];
+      
+      wfa->domain_type [state]   = 0;
+      c->images_of_state [state] = NULL;
+      c->ip_images_state [state] = NULL;
+				   
+      for (label = 0; label < MAXLABELS; label++) 
+      {
+	 sd->tree [label]     	= wfa->tree [state][label];
+	 sd->y_state [label]  	= wfa->y_state [state][label];
+	 sd->y_column [label] 	= wfa->y_column [state][label];
+	 sd->mv_tree [label]  	= wfa->mv_tree [state][label];
+	 sd->x [label]        	= wfa->x [state][label];
+	 sd->y [label]        	= wfa->y [state][label];
+	 sd->prediction [label] = wfa->prediction [state][label];
+
+	 memcpy (sd->weight [label], wfa->weight [state][label], 
+		 sizeof (real_t) * (MAXEDGES + 1));
+	 memcpy (sd->int_weight [label], wfa->int_weight [state][label], 
+		 sizeof (word_t) * (MAXEDGES + 1));
+	 memcpy (sd->into [label], wfa->into [state][label], 
+		 sizeof (word_t) * (MAXEDGES + 1));
+
+	 wfa->into [state][label][0] = NO_EDGE;
+	 wfa->tree [state][label]    = RANGE;
+	 wfa->y_state [state][label] = RANGE;
+      }
+      for (level = c->options.images_level + 1; level <= max_level;
+	   level++)
+      {
+	 sd->ip_states_state [level]       = c->ip_states_state [state][level];
+	 c->ip_states_state [state][level] = NULL;
+      }
+   }
+
+   return data;
+}
+
+static void
+restore_state_data (unsigned from, unsigned to, unsigned max_level,
+		    state_data_t *data, wfa_t *wfa, coding_t *c)
+/*
+ *  Restore all state data starting from state 'from'.
+ *  
+ *  No return value.
+ */
+{
+   state_data_t *sd;			/* pointer to state_data item */
+   unsigned	 state, label, level;
+
+   if (to < from)
+      return;				/* nothing to do */
+   
+   for (state = from; state <= to; state++)
+   {
+      sd = &data [state - from];
+      
+      wfa->final_distribution [state] = sd->final_distribution;
+      wfa->level_of_state [state]     = sd->level_of_state;
+      wfa->domain_type [state]        = sd->domain_type;
+      
+      if (c->images_of_state [state] != NULL)
+	 Free (c->images_of_state [state]);
+      c->images_of_state [state] = sd->images_of_state;
+      if (c->ip_images_state [state] != NULL)
+	 Free (c->ip_images_state [state]);
+      c->ip_images_state [state] = sd->inner_products;
+
+      for (label = 0; label < MAXLABELS; label++)
+      {
+	 wfa->tree [state][label]     	= sd->tree [label];
+	 wfa->y_state [state][label]  	= sd->y_state [label];
+	 wfa->y_column [state][label] 	= sd->y_column [label];
+	 wfa->mv_tree [state][label]  	= sd->mv_tree [label];
+	 wfa->x [state][label]        	= sd->x [label];
+	 wfa->y [state][label]        	= sd->y [label];
+	 wfa->prediction [state][label] = sd->prediction [label];
+	 
+	 memcpy (wfa->weight [state][label], sd->weight [label], 
+		 sizeof(real_t) * (MAXEDGES + 1));
+	 memcpy (wfa->int_weight [state][label], sd->int_weight [label], 
+		 sizeof(word_t) * (MAXEDGES + 1));
+	 memcpy (wfa->into [state][label], sd->into [label],  
+		 sizeof(word_t) * (MAXEDGES + 1));
+      }	 
+      for (level = c->options.images_level + 1; level <= max_level;
+	   level++)
+      {
+	 if (c->ip_states_state [state][level] != NULL)
+	    Free (c->ip_states_state [state][level]);
+	 c->ip_states_state [state][level] = sd->ip_states_state [level];
+      }
+   }
+
+   Free (data);
+   wfa->states = to + 1;
+}
diff --git a/converter/other/fiasco/codec/prediction.h b/converter/other/fiasco/codec/prediction.h
new file mode 100644
index 00000000..1068501a
--- /dev/null
+++ b/converter/other/fiasco/codec/prediction.h
@@ -0,0 +1,36 @@
+/*
+ *  prediction.h
+ *
+ *  Written by:		Ullrich Hafner
+ *			Michael Unger
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _PREDICTION_H
+#define _PREDICTION_H
+
+#include "types.h"
+#include "cwfa.h"
+
+real_t
+predict_range (real_t max_costs, real_t price, range_t *range, wfa_t *wfa,
+	       coding_t *c, unsigned band, int y_state, unsigned states,
+	       const tree_t *tree_model, const tree_t *p_tree_model,
+	       const void *domain_model, const void *d_domain_model,
+	       const void *coeff_model, const void *d_coeff_model);
+void
+update_norms_table (unsigned level, const wfa_info_t *wi, motion_t *mt);
+void
+clear_norms_table (unsigned level, const wfa_info_t *wi, motion_t *mt);
+
+#endif /* not _PREDICTION_H */
+
diff --git a/converter/other/fiasco/codec/subdivide.c b/converter/other/fiasco/codec/subdivide.c
new file mode 100644
index 00000000..b7982716
--- /dev/null
+++ b/converter/other/fiasco/codec/subdivide.c
@@ -0,0 +1,650 @@
+/*
+ *  subdivide.c:	Recursive subdivision of range images
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/07/15 17:59:31 $
+ *  $Author: hafner $
+ *  $Revision: 5.4 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#if HAVE_STRING_H
+#	include <string.h>
+#else /* not HAVE_STRING_H */
+#	include <strings.h>
+#endif /* not HAVE_STRING_H */
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "image.h"
+#include "cwfa.h"
+#include "approx.h"
+#include "ip.h"
+#include "bintree.h"
+#include "control.h"
+#include "prediction.h"
+#include "domain-pool.h"
+#include "mwfa.h"
+#include "misc.h"
+#include "subdivide.h"
+#include "list.h"
+#include "coeff.h"
+#include "wfalib.h"
+
+/*****************************************************************************
+
+				prototypes
+
+*****************************************************************************/
+
+static void
+init_new_state (bool_t auxiliary_state, bool_t delta, range_t *range,
+		const range_t *child, const int *y_state,
+		wfa_t *wfa, coding_t *c);
+static void
+init_range (range_t *range, const image_t *image, unsigned band,
+	    const wfa_t *wfa, coding_t *c);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+real_t 
+subdivide (real_t max_costs, unsigned band, int y_state, range_t *range,
+	   wfa_t *wfa, coding_t *c, bool_t prediction, bool_t delta)
+/*
+ *  Subdivide the current 'range' recursively and decide whether
+ *  a linear combination, a recursive subdivision, or a prediction is
+ *  the best choice of approximation.
+ *  'band' is the current color band, 'y_state' is the corresponding
+ *  state of the Y color component (color image compression only).
+ *  If 'prediction' is TRUE then also test motion compensation or
+ *  nondeterministic approximation.
+ *  If 'delta' is TRUE then current range is already predicted.
+ *  
+ *  Return value:
+ *	costs of the best approximation or MAXCOSTS if costs exceed 'max_costs'
+ *
+ *  Side effects:
+ *	'range'	factors and costs of linear combination are modified
+ *      'wfa'	new transitions and prediction coefficients are added
+ *	'c'	pixels and inner products are updated
+ */
+{
+   real_t    subdivide_costs;        /* Costs arising from approx. the current
+				       range with two childs */
+   real_t    lincomb_costs;          /* Costs arising from approx. the current
+				       range with a linear combination */
+   int	     new_y_state [MAXLABELS];	/* Corresponding state of Y */
+   real_t    price;			/* Approximation costs multiplier */
+   bool_t    try_mc;			/* YES: try MC prediction */
+   bool_t    try_nd;			/* YES: try ND prediction */
+   unsigned  states;			/* Number of states before the
+					   recursive subdivision starts */
+   void     *domain_model;		/* copy of domain pool model */      
+   void     *d_domain_model;		/* copy of delta domain pool model */
+   void     *lc_domain_model;		/* copy of domain pool model */
+   void     *lc_d_domain_model;		/* copy of delta domain pool model */
+   void	    *coeff_model;	        /* copy of coefficients model */
+   void	    *d_coeff_model;		/* copy of delta coefficients model */
+   void	    *lc_coeff_model;	        /* copy of coefficients model */
+   void	    *lc_d_coeff_model;		/* copy of delta coefficients model */
+   tree_t    tree_model;		/* copy of tree model */
+   tree_t    p_tree_model;		/* copy of pred. tree model */
+   range_t   lrange;			/* range of lin. comb. approx. */
+   range_t   rrange;			/* range of recursive approx. */
+   range_t   child [MAXLABELS];		/* new childs of the current range */
+   static unsigned percent = 0;		/* status of progress meter */
+
+   if (wfa->wfainfo->level == range->level)
+      percent = 0;
+   
+   range->into [0] = NO_EDGE;		/* default approximation: empty */
+   range->tree     = RANGE;
+
+   if (range->level < 3)		/* Don't process small ranges */
+      return MAXCOSTS;	
+
+   /*
+    *  If image permutation (tiling) is performed and the tiling level
+    *  is reached then get coordinates of the new block.
+    */
+   if (c->tiling->exponent
+       && range->level == wfa->wfainfo->level - c->tiling->exponent)
+   {
+      unsigned width, height;		/* size of range (dummies)*/
+      
+      if (c->tiling->vorder [range->global_address] < 0)
+	 return 0;			/* nothing to do */
+      else
+	 locate_subimage (wfa->wfainfo->level, range->level,
+			  c->tiling->vorder [range->global_address],
+			  &range->x, &range->y, &width, &height);
+   }
+
+   if (range->x >= c->mt->original->width ||
+       range->y >= c->mt->original->height)
+      return 0;				/* range is not visible */
+
+   /*
+    *  Check whether prediction is allowed or not
+    *  mc == motion compensation, nd == nondeterminism
+    */
+   try_mc = (prediction && c->mt->frame_type != I_FRAME			
+	     && range->level >= wfa->wfainfo->p_min_level
+	     && range->level <= wfa->wfainfo->p_max_level
+	     && (range->x + width_of_level (range->level)
+		 <= c->mt->original->width)
+	     && (range->y + height_of_level (range->level)
+		 <= c->mt->original->height));
+
+   try_nd = (prediction && c->mt->frame_type == I_FRAME
+	     && range->level >= wfa->wfainfo->p_min_level
+	     && range->level <= wfa->wfainfo->p_max_level);
+
+   if (try_mc)
+      clear_norms_table (range->level, wfa->wfainfo, c->mt);
+
+   
+   /*
+    *  Check if current range must be initialized. I.e. range pixels must
+    *  be copied from entire image to bintree pixel buffer. Moreover,
+    *  all inner products tables must be initialized.
+    */
+   if (range->level == c->options.lc_max_level)	
+      init_range (range, c->mt->original, band, wfa, c);
+   
+   price = c->price;
+   if (band != Y)			
+      price *= c->options.chroma_decrease; /* less quality for chroma bands */
+
+   /*
+    *  Compute childs of corresponding state in Y band
+    */
+   if (band != Y)			/* Cb and Cr bands only */
+   {
+      unsigned label;
+
+      for (label = 0; label < MAXLABELS; label++)
+	 if (ischild (y_state))
+	    new_y_state [label] = wfa->tree [y_state][label];
+	 else
+	    new_y_state [label] = RANGE;
+   }
+   else
+      new_y_state [0] = new_y_state [1] = RANGE;
+   
+   /*
+    *  Store contents of all models that may get modified during recursion
+    */
+   domain_model   = c->domain_pool->model_duplicate (c->domain_pool->model);
+   d_domain_model = c->d_domain_pool->model_duplicate (c->d_domain_pool->model);
+   coeff_model    = c->coeff->model_duplicate (c->coeff, c->coeff->model);
+   d_coeff_model  = c->d_coeff->model_duplicate (c->d_coeff, c->d_coeff->model);
+   tree_model     = c->tree;
+   p_tree_model   = c->p_tree;
+   states         = wfa->states;	
+   
+   /*
+    *  First alternative of range approximation:
+    *  Compute costs of linear combination.
+    */
+   if (range->level <= c->options.lc_max_level) /* range is small enough */
+   {
+      lrange                 = *range;
+      lrange.tree            = RANGE;
+      lrange.tree_bits       = tree_bits (LEAF, lrange.level, &c->tree);
+      lrange.matrix_bits     = 0;
+      lrange.weights_bits    = 0;
+      lrange.mv_tree_bits    = try_mc ? 1 : 0; /* mc allowed but not used */
+      lrange.mv_coord_bits   = 0;
+      lrange.nd_tree_bits    = 0;	
+      lrange.nd_weights_bits = 0;	
+      lrange.prediction	     = NO;
+      
+      lincomb_costs
+	 = approximate_range (max_costs, price, c->options.max_elements,
+			      y_state, &lrange,
+			      (delta ? c->d_domain_pool : c->domain_pool),
+			      (delta ? c->d_coeff : c->coeff), wfa, c);
+   }
+   else
+      lincomb_costs = MAXCOSTS;		
+
+   /*
+    *  Store contents of models that have been modified
+    *  by approximate_range () above ...
+    */
+   lc_domain_model   = c->domain_pool->model;
+   lc_d_domain_model = c->d_domain_pool->model;
+   lc_coeff_model    = c->coeff->model;
+   lc_d_coeff_model  = c->d_coeff->model;
+   /*
+    *  ... and restore them with values before lc
+    */
+   c->domain_pool->model   = c->domain_pool->model_duplicate (domain_model);
+   c->d_domain_pool->model = c->d_domain_pool->model_duplicate (d_domain_model);
+   c->coeff->model         = c->coeff->model_duplicate (c->coeff, coeff_model);
+   c->d_coeff->model       = c->d_coeff->model_duplicate (c->d_coeff,
+							  d_coeff_model);
+   
+   /*
+    *  Second alternative of range approximation:
+    *  Compute costs of recursive subdivision.
+    */
+   if (range->level > c->options.lc_min_level) /* range is large enough */
+   {
+      unsigned label;
+      
+      memset (&child [0], 0, 2 * sizeof (range_t)); /* initialize childs */
+
+      /*
+       *  Initialize a new range for recursive approximation
+       */
+      rrange                 = *range;
+      rrange.tree_bits       = tree_bits (CHILD, rrange.level, &c->tree);
+      rrange.matrix_bits     = 0;
+      rrange.weights_bits    = 0;
+      rrange.err             = 0;
+      rrange.mv_tree_bits    = try_mc ? 1 : 0;	/* mc allowed but not used */
+      rrange.mv_coord_bits   = 0;
+      rrange.nd_tree_bits    = try_nd ?
+			       tree_bits (CHILD, lrange.level, &c->p_tree): 0;
+      rrange.nd_weights_bits = 0;	
+      rrange.prediction	     = NO;
+
+      /*
+       *  Initialize the cost function and subdivide the current range.
+       *  Every child is approximated by a recursive call of subdivide()
+       */
+      subdivide_costs = (rrange.tree_bits + rrange.weights_bits
+			 + rrange.matrix_bits + rrange.mv_tree_bits
+			 + rrange.mv_coord_bits + rrange.nd_tree_bits
+			 + rrange.nd_weights_bits) * price;
+      
+      for (label = 0; label < MAXLABELS; label++) 
+      {
+	 real_t remaining_costs;	/* upper limit for next recursion */
+	 
+	 child[label].image          = rrange.image * MAXLABELS + label + 1;
+	 child[label].address        = rrange.address * MAXLABELS + label;
+	 child[label].global_address = rrange.global_address * MAXLABELS
+				       + label;
+	 child[label].level          = rrange.level - 1;
+	 child[label].x	= rrange.level & 1
+			  ? rrange.x
+			  : (rrange.x
+			     + label * width_of_level (rrange.level - 1));
+	 child[label].y = rrange.level & 1
+			  ? (rrange.y
+			     + label * height_of_level (rrange.level - 1))
+			  : rrange.y;
+	 
+	 /* 
+	  *  If neccessary compute the inner products of the new states
+	  *  (generated during the recursive approximation of child [0])
+	  */
+	 if (label && rrange.level <= c->options.lc_max_level)
+	    compute_ip_images_state (child[label].image, child[label].address,
+				     child[label].level, 1, states, wfa, c);
+	 /*
+	  *  Call subdivide() for both childs. 
+	  *  Abort the recursion if 'subdivide_costs' exceed 'lincomb_costs'
+	  *  or 'max_costs'.
+	  */
+	 remaining_costs = min (lincomb_costs, max_costs) - subdivide_costs;
+
+	 if (remaining_costs > 0)	/* still a way for improvement */
+	 {
+	    subdivide_costs += subdivide (remaining_costs, band,
+					  new_y_state [label], &child [label],
+					  wfa, c, prediction, delta);
+	 }
+	 else if (try_mc && child[label].level >= wfa->wfainfo->p_min_level)
+	 {
+	    fill_norms_table (child[label].x, child[label].y,
+			      child[label].level, wfa->wfainfo, c->mt);
+	 }
+	 
+	 if (try_mc)
+	    update_norms_table (rrange.level, wfa->wfainfo, c->mt);
+	 
+	 /*
+	  *  Update of progress meter
+	  */
+	 if (c->options.progress_meter != FIASCO_PROGRESS_NONE)
+	 {
+	    if (c->options.progress_meter == FIASCO_PROGRESS_PERCENT)
+	    {
+	       unsigned	new_percent; 	/* new status of progress meter */
+	 
+	       new_percent = (child[label].global_address + 1) * 100.0
+			     / (1 << (wfa->wfainfo->level - child[label].level));
+	       if (new_percent > percent)
+	       {
+		  percent = new_percent;
+		  info ("%3d%%  \r", percent);
+	       }
+	    }
+	    else if (c->options.progress_meter == FIASCO_PROGRESS_BAR)
+	    {
+	       unsigned	new_percent;	/* new status of progress meter */
+	 
+	       new_percent = (child[label].global_address + 1) * 50.0
+			     / (1 << (wfa->wfainfo->level
+				      - child[label].level));
+	       for (; new_percent > percent; percent++)
+	       {
+		  info ("#");
+	       }
+	    }
+	 }
+   
+	 /*
+	  *  If costs of subdivision exceed costs of linear combination 
+	  *  then abort recursion.
+	  */
+	 if (subdivide_costs >= min (lincomb_costs, max_costs)) 
+	 {
+	    subdivide_costs = MAXCOSTS;
+	    break; 
+	 }
+	 rrange.err             += child [label].err;
+	 rrange.tree_bits       += child [label].tree_bits;
+	 rrange.matrix_bits     += child [label].matrix_bits;
+	 rrange.weights_bits    += child [label].weights_bits;
+	 rrange.mv_tree_bits    += child [label].mv_tree_bits;
+	 rrange.mv_coord_bits   += child [label].mv_coord_bits;
+	 rrange.nd_weights_bits += child [label].nd_weights_bits;
+	 rrange.nd_tree_bits    += child [label].nd_tree_bits;
+
+	 tree_update (ischild (child [label].tree) ? CHILD : LEAF,
+		      child [label].level, &c->tree);
+	 tree_update (child [label].prediction ? LEAF : CHILD,
+		      child [label].level, &c->p_tree);
+      }
+   }
+   else
+      subdivide_costs = MAXCOSTS;
+
+   /*
+    *  Third alternative of range approximation:
+    *  Predict range via motion compensation or nondeterminism and 
+    *  approximate delta image. 
+    */
+   if (try_mc || try_nd)		/* try prediction */
+   {
+      real_t prediction_costs;	/* Costs arising from approx. the current
+				   range with prediction */
+
+      prediction_costs
+	 = predict_range (min (min (lincomb_costs, subdivide_costs),
+			       max_costs),
+			  price, range, wfa, c, band, y_state, states,
+			  &tree_model, &p_tree_model, domain_model,
+			  d_domain_model, coeff_model, d_coeff_model);
+      if (prediction_costs < MAXCOSTS)	/* prediction has smallest costs */
+      {
+	 c->domain_pool->model_free (domain_model);
+	 c->d_domain_pool->model_free (d_domain_model);
+	 c->domain_pool->model_free (lc_domain_model);
+	 c->d_domain_pool->model_free (lc_d_domain_model);
+	 c->coeff->model_free (coeff_model);
+	 c->d_coeff->model_free (d_coeff_model);
+	 c->coeff->model_free (lc_coeff_model);
+	 c->d_coeff->model_free (lc_d_coeff_model);
+	 
+	 return prediction_costs;
+      }
+   }
+
+   if (lincomb_costs >= MAXCOSTS && subdivide_costs >= MAXCOSTS)
+   {
+      /*
+       *  Return MAXCOSTS if neither a linear combination nor a recursive
+       *  subdivision yield costs less than 'max_costs'
+       */
+      c->domain_pool->model_free (c->domain_pool->model);
+      c->d_domain_pool->model_free (c->d_domain_pool->model);
+      c->domain_pool->model_free (lc_domain_model);
+      c->d_domain_pool->model_free (lc_d_domain_model);
+
+      c->coeff->model_free (c->coeff->model);
+      c->d_coeff->model_free (c->d_coeff->model);
+      c->coeff->model_free (lc_coeff_model);
+      c->d_coeff->model_free (lc_d_coeff_model);
+	 
+      c->domain_pool->model   = domain_model;
+      c->d_domain_pool->model = d_domain_model;
+      c->coeff->model	      = coeff_model;
+      c->d_coeff->model	      = d_coeff_model;
+      c->tree                 = tree_model;
+      c->p_tree               = p_tree_model;
+      
+      if (wfa->states != states)
+	 remove_states (states, wfa);
+
+      return MAXCOSTS;
+   }
+   else if (lincomb_costs < subdivide_costs) 
+   {
+      /*
+       *  Use the linear combination: The factors of the linear combination
+       *  are stored already in 'range', so revert the probability models
+       *  only. 
+       */
+      c->domain_pool->model_free (c->domain_pool->model);
+      c->d_domain_pool->model_free (c->d_domain_pool->model);
+      c->domain_pool->model_free (domain_model);
+      c->d_domain_pool->model_free (d_domain_model);
+
+      c->coeff->model_free (c->coeff->model);
+      c->d_coeff->model_free (c->d_coeff->model);
+      c->coeff->model_free (coeff_model);
+      c->d_coeff->model_free (d_coeff_model);
+      
+      c->domain_pool->model   = lc_domain_model;
+      c->d_domain_pool->model = lc_d_domain_model;
+      c->coeff->model	      = lc_coeff_model;
+      c->d_coeff->model	      = lc_d_coeff_model;
+      c->tree                 = tree_model;
+      c->p_tree               = p_tree_model;
+
+      *range = lrange;
+      
+      if (wfa->states != states)
+	 remove_states (states, wfa);
+
+      return lincomb_costs;
+   }
+   else
+   {
+      /*
+       *  Use the recursive subdivision: Generate a new state with transitions
+       *  given in child[].
+       *  Don't use state in linear combinations in any of the following cases:
+       *  - if color component is Cb or Cr
+       *  - if level of state > tiling level 
+       *  - if state is (partially) outside image geometry 
+       */
+      if (band > Y
+	  || (c->tiling->exponent
+	      && rrange.level > wfa->wfainfo->level - c->tiling->exponent)
+	  || (range->x + width_of_level (range->level)
+	      > c->mt->original->width)
+	  || (range->y + height_of_level (range->level)
+	      > c->mt->original->height))
+	 init_new_state (YES, delta, &rrange, child, new_y_state, wfa, c);
+      else
+	 init_new_state (NO, delta, &rrange, child, new_y_state, wfa, c);
+
+      *range = rrange;
+
+      c->domain_pool->model_free (domain_model);
+      c->d_domain_pool->model_free (d_domain_model);
+      c->domain_pool->model_free (lc_domain_model);
+      c->d_domain_pool->model_free (lc_d_domain_model);
+      c->coeff->model_free (coeff_model);
+      c->d_coeff->model_free (d_coeff_model);
+      c->coeff->model_free (lc_coeff_model);
+      c->d_coeff->model_free (lc_d_coeff_model);
+
+      return subdivide_costs;
+   }
+}
+
+void
+cut_to_bintree (real_t *dst, const word_t *src,
+		unsigned src_width, unsigned src_height,
+		unsigned x0, unsigned y0, unsigned width, unsigned height)
+/*
+ *  Cut region ('x0', 'y0', 'width', 'height') of the pixel array 'src'.
+ *  Size of image is given by 'src_width' x 'src_height'.
+ *  'dst' pixels are converted to bintree order and real format.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'dst []' is filled with corresponding region.
+ */
+{
+   const unsigned mask01      = 0x555555; /* binary ...010101010101 */
+   const unsigned mask10      = 0xaaaaaa; /* binary ...101010101010 */
+   const unsigned mask01plus1 = mask01 + 1; /* binary ...010101010110 */
+   const unsigned mask10plus1 = mask10 + 1; /* binary ...101010101011 */
+   unsigned  	  x, y;			/* pixel coordinates */
+   unsigned  	  xmask, ymask;		/* address conversion */
+
+   if (width != height && width != (height >> 1))
+      error ("Bintree cutting requires special type of images.");
+
+   ymask = 0;
+   for (y = y0; y < y0 + height; y++, ymask = (ymask + mask10plus1) & mask01)
+   {
+      xmask = 0;
+      for (x = x0; x < x0 + width; x++, xmask = (xmask + mask01plus1) & mask10)
+      {
+	 if (y >= src_height || x >= src_width)
+	    dst [xmask | ymask] = 0;
+	 else
+	    dst [xmask | ymask] = src [y * src_width + x] / 16;
+      }
+   }
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static void
+init_new_state (bool_t auxiliary_state, bool_t delta, range_t *range,
+		const range_t *child, const int *y_state,
+		wfa_t *wfa, coding_t *c)
+/*
+ *  Initializes a new state with all parameters needed for the encoding step.
+ *  If flag 'auxiliary_state' is set then don't insert state into domain pools.
+ *  If flag 'delta' is set then state represents a delta image (prediction via
+ *  nondeterminism or motion compensation).
+ *  'range' the current range image,
+ *   'child []' the left and right childs of 'range'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	New state is appended to 'wfa' (and also its inner products and images
+ *      are computed and stored in 'c')
+ */
+{
+   unsigned label;
+   bool_t   state_is_domain = NO;
+
+   if (!auxiliary_state)
+   {
+      if (!delta || c->options.delta_domains)
+	 state_is_domain = c->domain_pool->append (wfa->states, range->level,
+						   wfa, c->domain_pool->model);
+      if (delta || c->options.normal_domains)
+	 state_is_domain = c->d_domain_pool->append (wfa->states, range->level,
+						     wfa,
+						     c->d_domain_pool->model)
+			   || state_is_domain;
+   }
+   else
+      state_is_domain = NO;
+   
+   range->into [0] = NO_EDGE;
+   range->tree     = wfa->states;
+   
+   for (label = 0; label < MAXLABELS; label++) 
+   {
+      wfa->tree [wfa->states][label]       = child [label].tree;
+      wfa->y_state [wfa->states][label]    = y_state [label];
+      wfa->mv_tree [wfa->states][label]    = child [label].mv;
+      wfa->x [wfa->states][label]          = child [label].x;
+      wfa->y [wfa->states][label]          = child [label].y;
+      wfa->prediction [wfa->states][label] = child [label].prediction;
+
+      append_transitions (wfa->states, label, child [label].weight,
+			  child [label].into, wfa);
+   }
+   wfa->delta_state [wfa->states] = delta;
+
+   if (range->err < 0)
+      warning ("Negative image norm: %f, %f", child [0].err, child [1].err);
+
+/*    state_is_domain = YES; */
+   
+   append_state (!state_is_domain,
+		 compute_final_distribution (wfa->states, wfa),
+		 range->level, wfa, c);
+}
+
+static void
+init_range (range_t *range, const image_t *image, unsigned band,
+	    const wfa_t *wfa, coding_t *c)
+/*
+ *  Read a new 'range' of the image 'image_name' (current color component
+ *  is 'band') and compute the new inner product arrays.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'c->pixels' are filled with pixel values of image block 
+ *	'c->ip_images_state' are computed with respect to new image block 
+ *	'range->address' and 'range->image' are initialized with zero
+ */
+{
+   unsigned state;
+   
+   /*
+    *  Clear already computed products
+    */
+   for (state = 0; state < wfa->states; state++)
+      if (need_image (state, wfa))
+	 memset (c->ip_images_state[state], 0,
+		 size_of_tree (c->products_level) * sizeof(real_t));
+
+   cut_to_bintree (c->pixels, image->pixels [band],
+		   image->width, image->height,
+		   range->x, range->y, width_of_level (range->level),
+		   height_of_level (range->level));
+   
+   range->address = range->image = 0;
+   compute_ip_images_state (0, 0, range->level, 1, 0, wfa, c);
+}
+
+
diff --git a/converter/other/fiasco/codec/subdivide.h b/converter/other/fiasco/codec/subdivide.h
new file mode 100644
index 00000000..b6840e58
--- /dev/null
+++ b/converter/other/fiasco/codec/subdivide.h
@@ -0,0 +1,33 @@
+/*
+ *  subdivide.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _SUBDIVIDE_H
+#define _SUBDIVIDE_H
+
+#include "types.h"
+#include "cwfa.h"
+
+real_t 
+subdivide (real_t max_costs, unsigned band, int y_state, range_t *range,
+	   wfa_t *wfa, coding_t *c, bool_t prediction, bool_t delta);
+void
+cut_to_bintree (real_t *dst, const word_t *src,
+		unsigned src_width, unsigned src_height,
+		unsigned x0, unsigned y0, unsigned width, unsigned height);
+
+#endif /* not _SUBDIVIDE_H */
+
+
diff --git a/converter/other/fiasco/codec/tiling.c b/converter/other/fiasco/codec/tiling.c
new file mode 100644
index 00000000..e820f7fb
--- /dev/null
+++ b/converter/other/fiasco/codec/tiling.c
@@ -0,0 +1,239 @@
+/*
+ *  tiling.c:		Subimage permutation
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#if STDC_HEADERS
+#	include <stdlib.h>
+#endif /* not STDC_HEADERS */
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "image.h"
+#include "misc.h"
+#include "wfalib.h"
+#include "tiling.h"
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static int
+cmpdecvar (const void *value1, const void *value2);
+static int
+cmpincvar (const void *value1, const void *value2);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+typedef struct var_list
+{
+   int	  address;			/* bintree address */
+   real_t variance;			/* variance of tile */
+} var_list_t;
+
+tiling_t *
+alloc_tiling (fiasco_tiling_e method, unsigned tiling_exponent,
+	      unsigned image_level)
+/*
+ *  Image tiling constructor.
+ *  Allocate memory for the tiling_t structure.
+ *  `method' defines the tiling method (spiral or variance,
+ *  ascending or descending).
+ *  In case of invalid parameters, a structure with tiling.exponent == 0 is
+ *  returned. 
+ *
+ *  Return value
+ *	pointer to the new tiling structure on success
+ */
+{
+   tiling_t *tiling = Calloc (1, sizeof (tiling_t));
+
+   if ((int) image_level - (int) tiling_exponent < 6)
+   {
+      tiling_exponent = 6;
+      warning (_("Image tiles must be at least 8x8 pixels large.\n"
+		 "Setting tiling size to 8x8 pixels."));
+   }
+   
+   switch (method)
+   {
+      case FIASCO_TILING_SPIRAL_ASC:
+      case FIASCO_TILING_SPIRAL_DSC:
+      case FIASCO_TILING_VARIANCE_ASC:
+      case FIASCO_TILING_VARIANCE_DSC:
+	 tiling_exponent = tiling_exponent;
+	 break;
+      default:
+	 warning (_("Invalid tiling method specified. Disabling tiling."));
+	 tiling_exponent = 0;
+	 break;
+   }
+
+   return tiling;
+}
+
+void
+free_tiling (tiling_t *tiling)
+/*
+ *  Tiling struct destructor:
+ *  Free memory of 'tiling' struct.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	structure 'tiling' is discarded.
+ */
+{
+   if (tiling->vorder)
+      Free (tiling->vorder);
+   Free (tiling);
+}
+
+void
+perform_tiling (const image_t *image, tiling_t *tiling)
+/*
+ *  Compute image tiling permutation.
+ *  The image is split into 2**'tiling->exponent' tiles.
+ *  Depending on 'tiling->method', the following algorithms are used:
+ *  "VARIANCE_ASC" :  Tiles are sorted by variance.
+ *                    The first tile has the lowest variance
+ *  "VARIANCE_DSC" :  Tiles are sorted by variance.
+ *                    The first tile has the largest variance
+ *  "SPIRAL_ASC" :    Tiles are sorted like a spiral starting
+ *                    in the middle of the image.
+ *  "SPIRAL_DSC" :    Tiles are sorted like a spiral starting
+ *                    in the upper left corner.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	The tiling permutation is stored in 'tiling->vorder'.
+ */
+{
+   if (tiling->exponent)
+   {
+      unsigned 	tiles = 1 << tiling->exponent; /* number of image tiles */
+      bool_t   *tile_valid;		/* tile i is in valid range ? */
+      
+      tiling->vorder = Calloc (tiles, sizeof (int));
+      tile_valid     = Calloc (tiles, sizeof (bool_t));
+
+      if (tiling->method == FIASCO_TILING_VARIANCE_ASC
+	  || tiling->method == FIASCO_TILING_VARIANCE_DSC)
+      {
+	 unsigned    address;		/* bintree address of tile */
+	 unsigned    number;		/* number of image tiles */
+	 unsigned    lx       = log2 (image->width - 1) + 1; /* x level */
+	 unsigned    ly       = log2 (image->height - 1) + 1; /* y level */
+	 unsigned    level    = max (lx, ly) * 2 - ((ly == lx + 1) ? 1 : 0);
+	 var_list_t *var_list = Calloc (tiles, sizeof (var_list_t));
+	 
+	 /*
+	  *  Compute variances of image tiles
+	  */
+	 for (number = 0, address = 0; address < tiles; address++)
+	 {
+	    unsigned width, height;	/* size of image tile */
+	    unsigned x0, y0;		/* NW corner of image tile */
+      
+	    locate_subimage (level, level - tiling->exponent, address,
+			     &x0, &y0, &width, &height);
+	    if (x0 < image->width && y0 < image->height) /* valid range */
+	    {
+	       if (x0 + width > image->width)	/* outside image area */
+		  width = image->width - x0;
+	       if (y0 + height > image->height) /* outside image area */
+		  height = image->height - y0;
+
+	       var_list [number].variance
+		  = variance (image->pixels [GRAY], x0, y0,
+			      width, height, image->width);
+	       var_list [number].address  = address;
+	       number++;
+	       tile_valid [address] = YES;
+	    }
+	    else
+	       tile_valid [address] = NO;
+	 }
+
+	 /*
+	  *  Sort image tiles according to sign of 'tiling->exp'
+	  */
+	 if (tiling->method == FIASCO_TILING_VARIANCE_DSC)
+	    qsort (var_list, number, sizeof (var_list_t), cmpdecvar);
+	 else
+	    qsort (var_list, number, sizeof (var_list_t), cmpincvar);
+
+	 for (number = 0, address = 0; address < tiles; address++)
+	    if (tile_valid [address])
+	    {
+	       tiling->vorder [address] = var_list [number].address;
+	       number++;
+	       debug_message ("tile number %d has original address %d",
+			      number, tiling->vorder [address]);
+	    }
+	    else
+	       tiling->vorder [address] = -1;
+
+	 Free (var_list);
+      }
+      else if (tiling->method == FIASCO_TILING_SPIRAL_DSC
+	       || tiling->method == FIASCO_TILING_SPIRAL_ASC)
+      {
+	 compute_spiral (tiling->vorder, image->width, image->height,
+			 tiling->exponent,
+			 tiling->method == FIASCO_TILING_SPIRAL_ASC);
+      }
+      else
+      {
+	 warning ("Unsupported image tiling method.\n"
+		  "Skipping image tiling step.");
+	 tiling->exponent = 0;
+      }
+   }
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static int
+cmpincvar (const void *value1, const void *value2)
+/*
+ *  Sorts by increasing variances (quicksort sorting function).
+ */
+{
+  return ((var_list_t *) value1)->variance - ((var_list_t *) value2)->variance;
+}
+
+static int
+cmpdecvar (const void *value1, const void *value2)
+/*
+ *  Sorts by decreasing variances (quicksort sorting function).
+ */
+{
+  return ((var_list_t *) value2)->variance - ((var_list_t *) value1)->variance;
+}
diff --git a/converter/other/fiasco/codec/tiling.h b/converter/other/fiasco/codec/tiling.h
new file mode 100644
index 00000000..2eb04fe0
--- /dev/null
+++ b/converter/other/fiasco/codec/tiling.h
@@ -0,0 +1,40 @@
+/*
+ *  tiling.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _TILING_H
+#define _TILING_H
+
+#include "image.h"
+#include "fiasco.h"
+
+typedef struct tiling
+{
+   unsigned    	    exponent;		/* Image is split in 2^exp tiles */
+   fiasco_tiling_e  method;		/* Method of Image tiling */
+   int	      	   *vorder;		/* Block permutation (size = 2^exp)
+					   -1 indicates empty block */
+} tiling_t;
+
+void
+perform_tiling (const image_t *image, tiling_t *tiling);
+tiling_t *
+alloc_tiling (fiasco_tiling_e method, unsigned tiling_exponent,
+	      unsigned image_level);
+void
+free_tiling (tiling_t *tiling);
+
+#endif /* not _TILING_H */
+
diff --git a/converter/other/fiasco/codec/wfa.h b/converter/other/fiasco/codec/wfa.h
new file mode 100644
index 00000000..8b9793f2
--- /dev/null
+++ b/converter/other/fiasco/codec/wfa.h
@@ -0,0 +1,141 @@
+/*
+ *  wfa.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/07/18 15:44:57 $
+ *  $Author: hafner $
+ *  $Revision: 5.3 $
+ *  $State: Exp $
+ */
+
+#ifndef _WFA_H
+#define _WFA_H
+
+#define MAXEDGES  5
+#define MAXSTATES 6000
+#define MAXLABELS 2			/* only bintree supported anymore */
+#define MAXLEVEL  22 
+
+#define FIASCO_BINFILE_RELEASE   2
+#define FIASCO_MAGIC	         "FIASCO" /* FIASCO magic number */
+#define FIASCO_BASIS_MAGIC       "Fiasco" /* FIASCO initial basis */
+
+#define NO_EDGE		-1
+#define RANGE		-1
+#define NO_RANGE	 0
+
+#define CHILD		 1
+#define LEAF		 0
+
+#define MAX_PROB	 9
+#define MIN_PROB	 1
+
+/*
+ *  WFA state types:
+ *	0:		 state is not allowed to be used in an
+ *			 approximation and it's image is not needed
+ *			 for ip computations.
+ *	AUXILIARY_MASK:  state is required for computation of ip's but is not
+ *			 allowed to be used in an approximation.
+ *	USE_DOMAIN_MASK: state is allowed to be used in an approximation.
+ */
+enum state_types {AUXILIARY_MASK = 1 << 0, USE_DOMAIN_MASK = 1 << 1};
+
+#define isedge(x)	   ((x) != NO_EDGE)
+#define isdomain(x)	   ((x) != NO_EDGE)
+#define isrange(x)	   ((x) == RANGE)
+#define ischild(x)	   ((x) != RANGE)
+#define isauxiliary(d,wfa) ((wfa)->domain_type[d] & AUXILIARY_MASK)
+#define usedomain(d, wfa)  ((wfa)->domain_type[d] & USE_DOMAIN_MASK)
+#define need_image(d,wfa)  (isauxiliary ((d), (wfa)) || usedomain ((d), (wfa)))
+
+typedef enum mc_type {NONE, FORWARD, BACKWARD, INTERPOLATED} mc_type_e;
+typedef enum frame_type {I_FRAME, P_FRAME, B_FRAME} frame_type_e;
+typedef enum header {HEADER_END, HEADER_TITLE, HEADER_COMMENT} header_type_e;
+
+typedef struct mv
+/*
+ *  Motion vector components
+ */
+{
+   mc_type_e type;			/* motion compensation type */
+   int       fx, fy;			/* forward vector coordinates */
+   int       bx, by;			/* backward vector coordinates */
+} mv_t;
+
+typedef struct range_info
+{
+   unsigned x, y;			/* coordinates of upper left corner */
+   unsigned level;			/* bintree level of range */
+} range_info_t;
+
+#include "image.h"
+#include "rpf.h"
+#include "bit-io.h"
+
+typedef struct wfa_info
+{
+   char	    *wfa_name;			/* filename of the WFA */
+   char	    *basis_name;		/* filename of the initial basis */
+   char     *title;			/* title of FIASCO stream */
+   char     *comment;			/* comment for FIASCO stream */
+   
+   unsigned  max_states;		/* max. cardinality of domain pool */
+   unsigned  chroma_max_states;		/* max. cardinality of domain pool for
+					   chroma band coding */
+   bool_t    color;			/* color image */
+   unsigned  width;			/* image width */
+   unsigned  height;			/* image height */
+   unsigned  level;			/* image level */
+   rpf_t    *rpf;			/* Standard reduced precision format */
+   rpf_t    *dc_rpf;			/* DC reduced precision format */
+   rpf_t    *d_rpf;			/* Delta reduced precision format */
+   rpf_t    *d_dc_rpf;			/* Delta DC reduced precision format */
+   unsigned  frames;			/* number of frames in the video */
+   unsigned  fps;			/* number of frames per second */
+   unsigned  p_min_level;		/* min. level of prediction */
+   unsigned  p_max_level;		/* max. level of prediction */
+   unsigned  search_range;		/* motion vector interval */
+   bool_t    half_pixel;		/* usage of half pixel precision */
+   bool_t    cross_B_search;		/* usage of Cross-B-Search */
+   bool_t    B_as_past_ref;		/* usage of B frames as ref's */
+   unsigned  smoothing;			/* smoothing of image along borders */
+   unsigned  release;			/* FIASCO file format release */
+} wfa_info_t;
+
+typedef struct wfa
+/*
+ *  Used to store all informations and data structures of a WFA
+ */
+{
+   wfa_info_t	*wfainfo;		/* misc. information about the WFA */
+   frame_type_e frame_type;		/* intra, predicted, bi-directional */
+   unsigned	states;			/* number of states */
+   unsigned	basis_states;		/* number of states in the basis */
+   unsigned	root_state;		/* root of the tree */
+   real_t	*final_distribution;    /* one pixel images */
+   byte_t	*level_of_state;	/* level of the image part which is
+					   represented by the current state */
+   byte_t	*domain_type;		/* Bit_0==1: auxilliary state
+					   Bit_1==1: used for Y compr */
+   mv_t		(*mv_tree)[MAXLABELS];	/* motion vectors */
+   word_t	(*tree)[MAXLABELS];	/* bintree partitioning */
+   u_word_t	(*x)[MAXLABELS];	/* range coordinate */
+   u_word_t	(*y)[MAXLABELS];	/* range coordinate */
+   word_t	(*into)[MAXLABELS][MAXEDGES + 1];   /* domain references */
+   real_t	(*weight)[MAXLABELS][MAXEDGES + 1]; /* lin.comb. coefficients */
+   word_t	(*int_weight)[MAXLABELS][MAXEDGES + 1]; /* bin. representation */
+   word_t	(*y_state)[MAXLABELS];	/* bintree of Y component */
+   byte_t	(*y_column)[MAXLABELS];	/* array for Y component references */
+   byte_t	(*prediction)[MAXLABELS]; /* DC prediction */
+   bool_t	(*delta_state);		/* delta state */
+} wfa_t;
+
+#endif /* not _WFA_H */
+
diff --git a/converter/other/fiasco/codec/wfalib.c b/converter/other/fiasco/codec/wfalib.c
new file mode 100644
index 00000000..a3acb975
--- /dev/null
+++ b/converter/other/fiasco/codec/wfalib.c
@@ -0,0 +1,774 @@
+/*
+ *  wfalib.c:		Library functions both for encoding and decoding
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/07/18 15:57:28 $
+ *  $Author: hafner $
+ *  $Revision: 5.5 $
+ *  $State: Exp $
+ */
+
+#define _BSD_SOURCE 1   /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include "config.h"
+
+#if STDC_HEADERS
+#	include <stdlib.h>
+#endif /* not STDC_HEADERS */
+
+#include <string.h>
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "wfa.h"
+#include "misc.h"
+#include "wfalib.h"
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static unsigned
+xy_to_address (unsigned x, unsigned y, unsigned level, unsigned n);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+wfa_t *
+alloc_wfa (bool_t coding)
+/*
+ *  WFA constructor:
+ *  Initialize the WFA structure 'wfa' and allocate memory.
+ *  Flag 'coding' indicates whether WFA is used for coding or decoding.
+ *
+ *  Return value:
+ *	pointer to the new WFA structure
+ */
+{
+   wfa_t *wfa = Calloc (1, sizeof (wfa_t));
+		 
+   /*
+    *  Allocate memory
+    */
+   wfa->final_distribution = Calloc (MAXSTATES, sizeof (real_t));
+   wfa->level_of_state     = Calloc (MAXSTATES, sizeof (byte_t));
+   wfa->domain_type        = Calloc (MAXSTATES, sizeof (byte_t));
+   wfa->delta_state        = Calloc (MAXSTATES, sizeof (bool_t));
+   wfa->tree               = Calloc (MAXSTATES * MAXLABELS, sizeof (word_t));
+   wfa->x                  = Calloc (MAXSTATES * MAXLABELS, sizeof (word_t));
+   wfa->y                  = Calloc (MAXSTATES * MAXLABELS, sizeof (word_t));
+   wfa->mv_tree            = Calloc (MAXSTATES * MAXLABELS, sizeof (mv_t));
+   wfa->y_state            = Calloc (MAXSTATES * MAXLABELS, sizeof (word_t));
+   wfa->into               = Calloc (MAXSTATES * MAXLABELS * (MAXEDGES + 1),
+				     sizeof (word_t));
+   wfa->weight             = Calloc (MAXSTATES * MAXLABELS * (MAXEDGES + 1),
+				     sizeof (real_t));
+   wfa->int_weight         = Calloc (MAXSTATES * MAXLABELS * (MAXEDGES + 1),
+				     sizeof (word_t));
+   wfa->wfainfo            = Calloc (1, sizeof (wfa_info_t));;
+   wfa->prediction         = Calloc (MAXSTATES * MAXLABELS, sizeof (byte_t));
+
+   wfa->wfainfo->wfa_name   = NULL;
+   wfa->wfainfo->basis_name = NULL;
+   wfa->wfainfo->title 	    = strdup ("");
+   wfa->wfainfo->comment    = strdup ("");
+
+   /*
+    *  Initialize structure
+    */
+   {
+      unsigned  state, label;
+
+      wfa->states       = 0;
+      wfa->basis_states = 0;
+      wfa->root_state   = 0;
+      for (state = 0; state < MAXSTATES; state++) 
+      {
+	 wfa->final_distribution [state] = 0;
+	 wfa->domain_type [state]        = 0;
+	 for (label = 0; label < MAXLABELS; label++)
+	 {
+	    wfa->into [state][label][0] = NO_EDGE;
+	    wfa->tree [state][label]    = RANGE;
+	    wfa->y_state [state][label] = RANGE;
+	 }
+      }
+   }
+
+   if (coding)				/* initialize additional variables */
+      wfa->y_column = Calloc (MAXSTATES * MAXLABELS, sizeof (byte_t));
+   else
+      wfa->y_column = NULL;
+   
+   return wfa;
+}
+
+void
+free_wfa (wfa_t *wfa)
+/*
+ *  WFA destructor:
+ *  Free memory of given 'wfa'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'wfa' struct is discarded.
+ */
+{
+   if (wfa->wfainfo->wfa_name)
+      Free (wfa->wfainfo->wfa_name);
+   if (wfa->wfainfo->basis_name)
+      Free (wfa->wfainfo->basis_name);
+   if (wfa->wfainfo->title)
+      Free (wfa->wfainfo->title);
+   if (wfa->wfainfo->comment)
+      Free (wfa->wfainfo->comment);
+
+   Free (wfa->final_distribution);
+   Free (wfa->level_of_state);
+   Free (wfa->domain_type);
+   Free (wfa->tree);
+   Free (wfa->x);
+   Free (wfa->y);
+   Free (wfa->mv_tree);
+   Free (wfa->y_state);
+   Free (wfa->into);
+   Free (wfa->weight);
+   Free (wfa->int_weight);
+   Free (wfa->wfainfo);
+   Free (wfa->prediction);
+   Free (wfa->delta_state);
+   if (wfa->y_column)
+      Free (wfa->y_column);
+   Free (wfa);
+}
+
+real_t 
+compute_final_distribution (unsigned state, const wfa_t *wfa)
+/*
+ *  Compute the final distribution of the given 'state'.
+ *  Uses the fact that the generated 'wfa' is average preserving.
+ *
+ *  Return value:
+ *	final distribution
+ */
+{
+   unsigned label;
+   real_t   final = 0;
+
+   for (label = 0; label < MAXLABELS; label++)
+   {
+      unsigned edge;
+      int      domain;
+      
+      if (ischild (domain = wfa->tree [state][label]))
+	 final += wfa->final_distribution [domain];
+      for (edge = 0; isedge (domain = wfa->into [state][label][edge]); edge++)
+	 final += wfa->weight [state][label][edge]
+		  * wfa->final_distribution [domain];
+   }
+   
+   return final / MAXLABELS;
+}
+
+word_t *
+compute_hits (unsigned from, unsigned to, unsigned n, const wfa_t *wfa)
+/*
+ *  Selects the 'n' most popular domain images of the given 'wfa'.
+ *  Consider only linear combinations of state images
+ *  {i | 'from' <= i <= 'to'}. I.e. domains are in {i | from <= i < 'to'}
+ *  Always ensure that state 0 is among selected states even though from
+ *  may be > 0.
+ *  
+ *  Return value:
+ *	pointer to array of the most popular state images
+ *	sorted by increasing state numbers and terminated by -1
+ */
+{
+   word_t   *domains;
+   unsigned  state, label, edge;
+   int       domain;
+   pair_t   *hits = Calloc (to, sizeof (pair_t));
+
+   for (domain = 0; domain < (int) to; domain++)
+   {
+      hits [domain].value = domain;
+      hits [domain].key   = 0;
+   }
+   
+   for (state = from; state <= to; state++)
+      for (label = 0; label < MAXLABELS; label++)
+	 for (edge = 0; isedge (domain = wfa->into [state][label][edge]);
+	      edge++)
+	    hits [domain].key++;
+
+   qsort (hits + 1, to - 1, sizeof (pair_t), sort_desc_pair);
+
+   n       = min (to, n);
+   domains = Calloc (n + 1, sizeof (word_t));
+
+   for (domain = 0; domain < (int) n && (!domain || hits [domain].key);
+	domain++)
+      domains [domain] = hits [domain].value;
+   if (n != domain)
+      debug_message ("Only %d domains have been used in the luminance.",
+		     domain);
+   n = domain;
+   qsort (domains, n, sizeof (word_t), sort_asc_word);
+   domains [n] = -1;
+   
+   Free (hits);
+   
+   return domains;
+}
+
+void
+append_edge (unsigned from, unsigned into, real_t weight,
+	     unsigned label, wfa_t *wfa)
+/*
+ *  Append an edge from state 'from' to state 'into' with
+ *  the given 'label' and 'weight' to the 'wfa'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'wfa' structure is changed.
+ */
+{
+   unsigned new;			/* position of the new edge */
+   unsigned edge;
+
+   /*
+    *  First look where to insert the new edge:
+    *  edges are sorted by increasing 'into' values
+    */
+   for (new = 0; (isedge (wfa->into [from][label][new])
+		  && wfa->into [from][label][new] < (int) into); new++)
+      ;
+   /*
+    *  Move the edges 'n' to position 'n+1', for n = max, ..., 'new'
+    */
+   for (edge = new; isedge (wfa->into [from][label][edge]); edge++)
+      ;
+   for (edge++; edge != new; edge--)
+   {
+      wfa->into [from][label][edge]    = wfa->into [from][label][edge - 1];
+      wfa->weight [from][label][edge]  = wfa->weight [from][label][edge - 1];
+      wfa->int_weight [from][label][edge]
+	 = wfa->int_weight [from][label][edge - 1];
+   }
+   /*
+    *  Insert the new edge
+    */
+   wfa->into [from][label][edge]       = into;
+   wfa->weight [from][label][edge]     = weight;
+   wfa->int_weight [from][label][edge] = weight * 512 + 0.5;
+}
+
+void 
+remove_states (unsigned from, wfa_t *wfa)
+/* 
+ *  Remove 'wfa' states 'wfa->basis_states',...,'wfa->states' - 1.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'wfa' structure is cleared for the given states.
+ */
+{
+   unsigned state;
+
+   for (state = from; state < wfa->states; state++)
+   {
+      unsigned label;
+      
+      for (label = 0; label < MAXLABELS; label++) 
+      {
+	 wfa->into [state][label][0]      = NO_EDGE;
+	 wfa->tree [state][label]         = RANGE;
+	 wfa->prediction [state][label]   = FALSE;
+	 wfa->y_state [state][label]      = RANGE;
+	 wfa->mv_tree [state][label].type = NONE;
+	 wfa->mv_tree [state][label].fx   = 0;
+	 wfa->mv_tree [state][label].fy   = 0;
+	 wfa->mv_tree [state][label].bx   = 0;
+	 wfa->mv_tree [state][label].by   = 0;
+      }
+      wfa->domain_type [state] = 0;
+      wfa->delta_state [state] = FALSE;
+   }
+
+   wfa->states = from;
+}
+
+void
+copy_wfa (wfa_t *dst, const wfa_t *src)
+/*
+ *  Copy WFA struct 'src' to WFA struct 'dst'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'dst' is filled with same data as 'src'
+ *
+ *  NOTE: size of WFA 'dst' must be at least size of WFA 'src'
+ */
+{
+   unsigned state;
+
+   memset (dst->final_distribution, 0, MAXSTATES * sizeof (real_t));
+   memset (dst->level_of_state, 0, MAXSTATES * sizeof (byte_t));
+   memset (dst->domain_type, 0, MAXSTATES * sizeof (byte_t));
+   memset (dst->mv_tree, 0, MAXSTATES * MAXLABELS * sizeof (mv_t));
+   memset (dst->tree, 0, MAXSTATES * MAXLABELS * sizeof (word_t));
+   memset (dst->x, 0, MAXSTATES * MAXLABELS * sizeof (word_t));
+   memset (dst->y, 0, MAXSTATES * MAXLABELS * sizeof (word_t));
+   memset (dst->y_state, 0, MAXSTATES * MAXLABELS * sizeof (word_t));
+   memset (dst->into, NO_EDGE,
+	   MAXSTATES * MAXLABELS * (MAXEDGES + 1) * sizeof (word_t));
+   memset (dst->weight, 0,
+	   MAXSTATES * MAXLABELS * (MAXEDGES + 1) * sizeof (real_t));
+   memset (dst->int_weight, 0,
+	   MAXSTATES * MAXLABELS * (MAXEDGES + 1) * sizeof (word_t));
+   memset (dst->prediction, 0, MAXSTATES * MAXLABELS * sizeof (byte_t));
+   memset (dst->delta_state, 0, MAXSTATES * sizeof (bool_t));
+   if (dst->y_column)
+      memset (dst->y_column, 0, MAXSTATES * MAXLABELS * sizeof (byte_t));
+
+   for (state = 0; state < MAXSTATES; state++) /* clear WFA struct */
+   {
+      unsigned label;
+      
+      for (label = 0; label < MAXLABELS; label++)
+      {
+	 dst->into [state][label][0]      = NO_EDGE;
+	 dst->tree [state][label]         = RANGE;
+	 dst->mv_tree [state][label].type = NONE;
+	 dst->y_state[state][label]       = RANGE;
+      }
+      dst->delta_state [state] = NO;
+      dst->domain_type [state] = 0;
+   }
+   
+   dst->frame_type   = src->frame_type;
+   dst->states 	     = src->states;
+   dst->basis_states = src->basis_states;
+   dst->root_state   = src->root_state;
+
+   memcpy (dst->wfainfo, src->wfainfo, sizeof (wfa_info_t));
+
+   if (dst->states == 0)		/* nothing to do */
+      return;
+
+   memcpy (dst->final_distribution, src->final_distribution,
+	   src->states * sizeof (real_t));
+   memcpy (dst->level_of_state, src->level_of_state,
+	   src->states * sizeof (byte_t));
+   memcpy (dst->domain_type, src->domain_type,
+	   src->states * sizeof (byte_t));
+   memcpy (dst->delta_state, src->delta_state,
+	   src->states * sizeof (bool_t));
+   memcpy (dst->mv_tree, src->mv_tree,
+	   src->states * MAXLABELS * sizeof (mv_t));
+   memcpy (dst->tree, src->tree,
+	   src->states * MAXLABELS * sizeof (word_t));
+   memcpy (dst->x, src->x,
+	   src->states * MAXLABELS * sizeof (word_t));
+   memcpy (dst->y, src->y,
+	   src->states * MAXLABELS * sizeof (word_t));
+   memcpy (dst->y_state, src->y_state,
+	   src->states * MAXLABELS * sizeof (word_t));
+   memcpy (dst->into, src->into,
+	   src->states * MAXLABELS * (MAXEDGES + 1) * sizeof (word_t));
+   memcpy (dst->weight, src->weight,
+	   src->states * MAXLABELS * (MAXEDGES + 1) * sizeof (real_t));
+   memcpy (dst->int_weight, src->int_weight,
+	   src->states * MAXLABELS * (MAXEDGES + 1) * sizeof (word_t));
+   memcpy (dst->prediction, src->prediction,
+	   src->states * MAXLABELS * sizeof (byte_t));
+   if (dst->y_column)
+      memcpy (dst->y_column, src->y_column,
+	      src->states * MAXLABELS * sizeof (byte_t));
+}
+
+void
+locate_subimage (unsigned orig_level, unsigned level, unsigned bintree,
+		 unsigned *x, unsigned *y, unsigned *width, unsigned *height)
+/*
+ *  Compute pixel coordinates of the subimage which 'bintree' address is given.
+ *  The level of the original image is 'orig_level' and the level of the
+ *  subimage is 'level'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'*x', '*y'		coordinates of the upper left corner
+ *      '*width', '*height'	size of image
+ */
+{
+   /*
+    *  Compute coordinates of the subimage
+    */
+   *x = *y = 0;				/* start at NW corner */
+   *width  = width_of_level (level);
+   *height = height_of_level (level);
+
+   if (level > orig_level)
+   {
+      error ("size of tile must be less or equal than image size.");
+      return;
+   }
+   else if (bintree >= (unsigned) (1 << (orig_level - level)))
+   {
+      error ("address out of bounds.");
+      return;
+   }
+   else if (level < orig_level)
+   {
+      unsigned mask;			/* mask for bintree -> xy conversion */
+      bool_t   hor;			/* 1 next subdivision is horizontal
+					   0 next subdivision is vertical */
+      unsigned l = orig_level - 1;	/* current level */
+      
+      hor = orig_level % 2;		/* start with vertival subdivision
+					   for square image and vice versa */
+   
+      for (mask = 1 << (orig_level - level - 1); mask; mask >>= 1, hor = !hor)
+      {
+	 if (bintree & mask)		/* change coordinates */
+	 {
+	    if (hor)			/* horizontal subdivision */
+	       *y += height_of_level (l);
+	    else			/* vertical subdivision */
+	       *x += width_of_level (l);
+	 }
+	 l--;
+      }
+   }
+}
+
+void
+compute_spiral (int *vorder, unsigned image_width, unsigned image_height,
+		unsigned tiling_exp, bool_t inc_spiral)
+/*
+ *  Compute image tiling with spiral order.
+ *  'inc_spiral' specifies whether the spiral starts in the middle
+ *  of the image (TRUE) or at the border (FALSE).
+ *  'image_width'x'image_height' define the size of the image.
+ *  The image is split into 'tiling->exp' tiles.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	vorder[] is filled with tiling permutation
+ */
+{
+   unsigned x, y;			/* current position */
+   unsigned xmin, xmax, ymin, ymax;	/* boundaries for current line */
+   unsigned width, height;		/* offset for each tile */
+   unsigned lx, ly, level;		/* level x and y */
+   unsigned tiles;			/* total number of tiles */
+   unsigned address;			/* bintree address */
+   
+   lx     = log2 (image_width - 1) + 1;
+   ly     = log2 (image_height - 1) + 1;
+   level  = max (lx, ly) * 2 - ((ly == lx + 1) ? 1 : 0);
+   tiles  = 1 << tiling_exp;		/* Number of image tiles */
+   width  = width_of_level (level - tiling_exp);
+   height = height_of_level (level - tiling_exp);
+   for (address = 0; address < tiles; address++)
+   {
+      unsigned x0, y0, width, height;
+      
+      locate_subimage (level, level - tiling_exp, address,
+		       &x0, &y0, &width, &height);
+      vorder [address] = (x0 < image_width && y0 < image_height) ? 0 : -1;
+   }
+
+   xmin    = 0;
+   xmax    = width_of_level (level);
+   ymin    = 0;
+   ymax    = height_of_level (level);
+   address = 0;
+
+   /*
+    *  1234
+    *  CDE5  Traverse image in spiral order 
+    *  BGF6  starting at the top left corner
+    *  A987
+    */
+   while (TRUE)
+   {
+      for (x = xmin, y = ymin; x < xmax; x += width) /* W>E */
+      {
+	 while (vorder [address] == -1)
+	    address++;
+	 if (x < image_width && y < image_height) /* valid range */
+	    vorder [address++] = xy_to_address (x, y, level, tiling_exp);
+	 while (address < tiles && vorder [address] == -1)
+	    address++;
+      }
+      ymin += height;
+
+      if (address >= tiles)
+	 break;
+      
+      for (x = xmax - width, y = ymin; y < ymax; y += height) /* N>S  */
+      {
+	 while (vorder [address] == -1)
+	    address++;
+	 if (x <= image_width && y <= image_height) /* valid range */
+	    vorder [address++] = xy_to_address (x, y, level, tiling_exp);
+	 while (address < tiles && vorder [address] == -1)
+	    address++;
+      }
+      xmax -= width;
+
+      if (address >= tiles)
+	 break;
+
+      for (x = xmax - width, y = ymax - width; x >= xmin; x -= width) /* E<W */
+      {
+	 while (vorder [address] == -1)
+	    address++;
+	 if (x <= image_width && y <= image_height) /* valid range */
+	    vorder [address++] = xy_to_address (x, y, level, tiling_exp);
+	 while (address < tiles && vorder [address] == -1)
+	    address++;
+      }
+      ymax -= height;
+
+      if (address >= tiles)
+	 break;
+
+      for (x = xmin, y = ymax - height; y >= ymin; y -= height)	/* S>N */
+      {
+	 while (vorder [address] == -1)
+	    address++;
+	 if (x <= image_width && y <= image_height) /* valid range */
+	    vorder [address++] = xy_to_address (x, y, level, tiling_exp);
+	 while (address < tiles && vorder [address] == -1)
+	    address++;
+      }
+      xmin += width;
+	 
+      if (address >= tiles)
+	 break;
+   }
+
+   if (inc_spiral)
+   {
+      int i = 0, j = tiles - 1;
+
+      while (i < j)
+      {
+	 int tmp;
+	    
+	 while (vorder [i] == -1)
+	    i++;
+	 while (vorder [j] == -1)
+	    j--;
+	    
+	 tmp 	       = vorder [i];
+	 vorder [i] = vorder [j];
+	 vorder [j] = tmp;
+	 i++;
+	 j--;
+      }
+   }
+   /*
+    *  Print tiling info
+    */
+   {
+      unsigned number;
+      
+      for (number = 0, address = 0; address < tiles; address++)
+	 if (vorder [address] != -1)
+	    debug_message ("number %d: address %d",
+			   number++, vorder [address]);
+   }
+}
+
+bool_t
+find_range (unsigned x, unsigned y, unsigned band,
+	    const wfa_t *wfa, unsigned *range_state, unsigned *range_label)
+/*
+ *  Find a range ('*range_state', '*range_label') that contains
+ *  pixel ('x', 'y') in the iven color 'band'.
+ *
+ *  Return value:
+ *	TRUE on success, or FALSE if there is no such range
+ *
+ *  Side effects:
+ *	'*range_state' and '*range_label' are modified on success.
+ */
+{
+   unsigned state, label;
+   unsigned first_state, last_state;
+   bool_t   success = NO;
+   
+   first_state = wfa->basis_states;
+   last_state  = wfa->states;
+   if (wfa->wfainfo->color)
+      switch (band)
+      {
+	 case Y:
+	    first_state = wfa->basis_states;
+	    last_state  = wfa->tree [wfa->tree [wfa->root_state][0]][0];
+	    break;
+	 case Cb:
+	    first_state = wfa->tree [wfa->tree [wfa->root_state][0]][0] + 1;
+	    last_state  = wfa->tree [wfa->tree [wfa->root_state][0]][1];
+	    break;
+	 case Cr:
+	    first_state = wfa->tree [wfa->tree [wfa->root_state][0]][1] + 1;
+	    last_state  = wfa->states;
+	    break;
+	 default:
+	    error ("unknown color component.");
+      }
+
+   for (state = first_state; state < last_state; state++)
+      for (label = 0; label < MAXLABELS; label++)
+	 if (isrange (wfa->tree [state][label]))
+	    if (x >= wfa->x [state][label] && y >= wfa->y [state][label]
+		&& x < (unsigned) (wfa->x [state][label]
+			+ width_of_level (wfa->level_of_state [state] - 1))
+		&& y < (unsigned) (wfa->y [state][label]
+			+ height_of_level (wfa->level_of_state [state] - 1))) 
+	    {
+	       success      = YES;
+	       *range_state = state;
+	       *range_label = label;
+
+	       return success;
+	    }
+
+   return success;
+}
+
+void
+sort_ranges (unsigned state, unsigned *domain,
+	     range_sort_t *rs, const wfa_t *wfa)
+/*
+ *  Generate list of ranges in coder order.
+ *  'state' is the current state of the call tree while 'domain' is the
+ *  index of the last added WFA state.
+ *
+ *  Side effects:
+ *	'domain' is incremented after recursion returns
+ *	'rs'	 is filled accordingly
+ *
+ *  No return value.
+ */
+{
+   unsigned label;
+   
+   for (label = 0; label < MAXLABELS; label++)
+   {
+      if (isrange (wfa->tree [state][label]))
+	 rs->range_subdivided [rs->range_no] = NO;
+      else
+      {
+	 sort_ranges (wfa->tree [state][label], domain, rs, wfa);
+	 rs->range_subdivided [rs->range_no] = YES;
+      }
+
+      rs->range_state [rs->range_no]      = state;
+      rs->range_label [rs->range_no]      = label;
+      rs->range_max_domain [rs->range_no] = *domain;
+      while (!usedomain (rs->range_max_domain [rs->range_no], wfa))
+	 rs->range_max_domain [rs->range_no]--;
+
+      if (label == 1 || !rs->range_subdivided [rs->range_no])
+	 rs->range_no++;
+   }
+   
+   (*domain)++;
+}
+
+bool_t
+locate_delta_images (wfa_t *wfa)
+/*
+ *  Locate all WFA states that are part of a delta approximation.
+ *  I.e., these states are assigned to ranges that have been predicted
+ *  via MC or ND.
+ *
+ *  Return value:
+ *	TRUE	at least one state is part of a delta approximation
+ *	FALSE	no delta approximations in this WFA
+ *
+ *  Side effects:
+ *	'wfa->delta [state][label]' is set accordingly.
+ */
+{
+   unsigned state, label;
+   bool_t   delta = NO;
+
+   for (state = wfa->root_state; state >= wfa->basis_states; state--)
+      wfa->delta_state [state] = NO;
+
+   for (state = wfa->root_state; state >= wfa->basis_states; state--)
+      for (label = 0; label < MAXLABELS; label++)
+	 if (ischild (wfa->tree [state][label]))
+	    if (wfa->mv_tree [state][label].type != NONE
+		|| isedge (wfa->into [state][label][0])
+		|| wfa->delta_state [state])
+	    {
+	       delta = YES;
+	       wfa->delta_state [wfa->tree [state][label]] = YES;
+	    }
+
+   return delta;
+}
+
+/*****************************************************************************
+
+				private code
+  
+******************************************************************************/
+
+static unsigned
+xy_to_address (unsigned x, unsigned y, unsigned level, unsigned n)
+/*
+ *  Compute bintree address of subimage at coordinates ('x', 'y').
+ *  Size of original image is determined by 'level'.
+ *  'n' specifies number of iterations.
+ *
+ *  Return value:
+ *	address of subimage
+ */ 
+{ 
+   unsigned address = 0;
+
+   while (n--)
+   {
+      address <<= 1;
+      if (--level % 2) 
+      {
+	 if (x & width_of_level (level))
+	    address++;
+      }
+      else
+      {
+	 if (y & height_of_level (level))
+	    address++;
+      }
+   }
+   
+   return address;
+}
diff --git a/converter/other/fiasco/codec/wfalib.h b/converter/other/fiasco/codec/wfalib.h
new file mode 100644
index 00000000..4622fcd2
--- /dev/null
+++ b/converter/other/fiasco/codec/wfalib.h
@@ -0,0 +1,66 @@
+/*
+ *  wfalib.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _WFALIB_H
+#define _WFALIB_H
+
+#include "types.h"
+#include "wfa.h"
+#include "list.h"
+
+typedef struct range_sort
+{
+   u_word_t *range_state;
+   byte_t   *range_label;
+   u_word_t *range_max_domain;
+   bool_t   *range_subdivided;
+   unsigned  range_no;
+} range_sort_t;
+
+bool_t
+locate_delta_images (wfa_t *wfa);
+void
+sort_ranges (unsigned state, unsigned *domain,
+	     range_sort_t *rs, const wfa_t *wfa);
+bool_t
+find_range (unsigned x, unsigned y, unsigned band,
+	    const wfa_t *wfa, unsigned *range_state, unsigned *range_label);
+void
+compute_spiral (int *vorder, unsigned image_width, unsigned image_height,
+		unsigned tiling_exp, bool_t inc_spiral);
+void
+locate_subimage (unsigned orig_level, unsigned level, unsigned bintree,
+		 unsigned *x, unsigned *y, unsigned *width, unsigned *height);
+void
+copy_wfa (wfa_t *dst, const wfa_t *src);
+void 
+remove_states (unsigned from, wfa_t *wfa);
+void
+append_edge (unsigned from, unsigned into, real_t weight,
+	     unsigned label, wfa_t *wfa);
+word_t *
+compute_hits (unsigned from, unsigned to, unsigned n, const wfa_t *wfa);
+real_t 
+compute_final_distribution (unsigned state, const wfa_t *wfa);
+wfa_t *
+alloc_wfa (bool_t coding);
+void
+free_wfa (wfa_t *wfa);
+bool_t
+locate_delta_images (wfa_t *wfa);
+
+#endif /* not _WFALIB_H */
+
diff --git a/converter/other/fiasco/config.h b/converter/other/fiasco/config.h
new file mode 100644
index 00000000..d6b15a84
--- /dev/null
+++ b/converter/other/fiasco/config.h
@@ -0,0 +1,96 @@
+/* config.h.  Generated automatically by configure.  */
+/* But then manually maintained as part of Netpbm! */
+
+/* config.h.in.  Generated automatically from configure.in by autoheader.  */
+
+/* Define to empty if the keyword does not work.  */
+/* #undef const */
+
+/* Define if you don't have vprintf but do have _doprnt.  */
+/* #undef HAVE_DOPRNT */
+
+/* Define if you have the vprintf function.  */
+#define HAVE_VPRINTF 1
+
+/* Define to `unsigned' if <sys/types.h> doesn't define.  */
+/* #undef size_t */
+
+/* Define if you have the ANSI C header files.  */
+#define STDC_HEADERS 1
+
+/* Define if you can safely include both <sys/time.h> and <time.h>.  */
+#define TIME_WITH_SYS_TIME 1
+
+/* Define if your processor stores words with the most significant
+   byte first (like Motorola and SPARC, unlike Intel and VAX).  */
+/* #undef WORDS_BIGENDIAN */
+
+/* Define if the X Window System is missing or not being used.  */
+#define X_DISPLAY_MISSING 1
+
+/* Define if shifting of signed integers works */
+#define HAVE_SIGNED_SHIFT 1
+
+/* Define if you don't have the CLOCKS_PER_SEC define in <time.h>. */
+/* #undef CLOCKS_PER_SEC */
+
+/* The number of bytes in a char.  */
+#define SIZEOF_CHAR 1
+
+/* The number of bytes in a int.  */
+#define SIZEOF_INT 4
+
+/* The number of bytes in a short.  */
+#define SIZEOF_SHORT 2
+
+/* Define if you have the log2 function.  */
+/* #undef HAVE_LOG2 */
+/* For Netpbm, we won't use log2() because it might not exist.  In
+   Netpbm, misc.c contains Log2(), so we just define log2 as Log2 here.
+   But first, we include <math.h> because if we don't it may get included
+   after config.h, and then there could be a redefinition issue with log2.
+*/
+#include <math.h>
+#undef log2
+#define log2 Log2
+
+/* Define if you have the memmove function.  */
+#define HAVE_MEMMOVE 1
+
+/* Define if you have the strcasecmp function.  */
+#define HAVE_STRCASECMP 1
+
+/* Define if you have the strdup function.  */
+#define HAVE_STRDUP 1
+
+/* Define if you have the <X11/extensions/XShm.h> header file.  */
+/* #undef HAVE_X11_EXTENSIONS_XSHM_H */
+
+/* Define if you have the <assert.h> header file.  */
+#define HAVE_ASSERT_H 1
+
+/* Define if you have the <features.h> header file.  */
+#define HAVE_FEATURES_H 1
+
+/* Define if you have the <setjmp.h> header file.  */
+#define HAVE_SETJMP_H 1
+
+/* Define if you have the <string.h> header file.  */
+#define HAVE_STRING_H 1
+
+/* Define if you have the <sys/time.h> header file.  */
+#define HAVE_SYS_TIME_H 1
+
+/* Define if you have the <unistd.h> header file.  */
+#define HAVE_UNISTD_H 1
+
+/* Define if you have the m library (-lm).  */
+#define HAVE_LIBM 1
+
+/* Name of package */
+#define PACKAGE "fiasco"
+
+/* Version number of package */
+#define VERSION "1.0"
+
+#define FIASCO_SHARE "/etc/"
diff --git a/converter/other/fiasco/display.c b/converter/other/fiasco/display.c
new file mode 100644
index 00000000..9e531149
--- /dev/null
+++ b/converter/other/fiasco/display.c
@@ -0,0 +1,422 @@
+/*
+ *  display.c:		X11 display of frames
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ *		
+ *  Based on mpeg2decode, (C) 1994, MPEG Software Simulation Group
+ *  and      mpeg2play,   (C) 1994 Stefan Eckart
+ *                                 <stefan@lis.e-technik.tu-muenchen.de>
+ *  and      tmndec       (C) 1995, 1996  Telenor R&D, Norway
+ */
+
+/*
+ *  $Date: 2000/07/03 19:35:59 $
+ *  $Author: hafner $
+ *  $Revision: 5.2 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#ifndef X_DISPLAY_MISSING
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+
+#if STDC_HEADERS
+#	include <stdlib.h>
+#	include <string.h>
+#else /* not STDC_HEADERS */
+#	if HAVE_STRING_H
+#		include <string.h>
+#	else /* not HAVE_STRING_H */
+#		include <strings.h>
+#	endif /* not HAVE_STRING_H */
+#endif /* not STDC_HEADERS */
+
+#include "types.h"
+#include "macros.h"
+#include "display.h"
+#include "binerror.h"
+
+/*****************************************************************************
+
+	       shared memory functions (if USE_SHM is defined)
+  
+*****************************************************************************/
+
+#ifdef USE_SHM
+
+#ifdef HAVE_FEATURES_H
+#include <features.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <X11/extensions/XShm.h>
+
+int
+XShmQueryExtension (Display *dpy);
+int
+XShmGetEventBase (Display *dpy);
+
+static int
+HandleXError (Display *dpy, XErrorEvent *event);
+static void
+InstallXErrorHandler (x11_info_t *xinfo);
+static void
+DeInstallXErrorHandler (x11_info_t *xinfo);
+
+static int		shmem_flag;
+static XShmSegmentInfo	shminfo1, shminfo2;
+static int		gXErrorFlag;
+static int		CompletionType = -1;
+
+static int
+HandleXError (Display *dpy, XErrorEvent *event)
+{
+   gXErrorFlag = 1;
+   
+   return 0;
+}
+
+static void
+InstallXErrorHandler (x11_info_t *xinfo)
+{
+   XSetErrorHandler (HandleXError);
+   XFlush (xinfo->display);
+}
+
+static void
+DeInstallXErrorHandler (x11_info_t *xinfo)
+{
+   XSetErrorHandler (NULL);
+   XFlush (xinfo->display);
+}
+
+#endif /* USE_SHM */
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+void
+display_image (unsigned x0, unsigned y0, x11_info_t *xinfo)
+/*
+ *  Display 'image' at pos ('x0', 'y0') in the current window
+ *  (given by 'xinfo->window'). 
+ *
+ *  No return value.
+ */
+{
+   int byte_order_check = 1;
+   
+   /*
+    *  Always work in native bit and byte order. This tells Xlib to
+    *  reverse bit and byte order if necessary when crossing a
+    *  network. Frankly, this part of XImages is somewhat
+    *  underdocumented, so this may not be exactly correct.
+    */
+   if (*(char *) & byte_order_check == 1)
+   {
+      xinfo->ximage->byte_order       = LSBFirst;
+      xinfo->ximage->bitmap_bit_order = LSBFirst;
+   }
+   else
+   {
+      xinfo->ximage->byte_order       = MSBFirst;
+      xinfo->ximage->bitmap_bit_order = MSBFirst;
+   }    
+
+   /* Display dithered image */
+#ifdef USE_SHM
+   if (shmem_flag)
+   {
+      XEvent xev;
+      
+      XShmPutImage (xinfo->display, xinfo->window, xinfo->gc, xinfo->ximage,
+		    0, 0, x0, y0, xinfo->ximage->width, xinfo->ximage->height,
+		    True);
+      XFlush (xinfo->display);
+      
+      while (!XCheckTypedEvent (xinfo->display, CompletionType, &xev))
+	 ;
+   }
+   else 
+#endif /* USE_SHM */
+   {
+      xinfo->ximage->data = (char *) xinfo->pixels; 
+      XPutImage (xinfo->display, xinfo->window, xinfo->gc, xinfo->ximage, 0, 0,
+		 x0, y0, xinfo->ximage->width, xinfo->ximage->height);
+   }
+}
+
+void
+close_window (x11_info_t *xinfo)
+{
+#ifdef USE_SHM
+   if (shmem_flag && xinfo->ximage)
+   {
+      XShmDetach (xinfo->display, &shminfo1);
+      XDestroyImage (xinfo->ximage);
+      xinfo->ximage = NULL;
+      shmdt (shminfo1.shmaddr);
+   }
+   else
+#endif /* USE_SHM */
+   if (xinfo->ximage)
+   {
+      XDestroyImage (xinfo->ximage);
+      xinfo->ximage = NULL;
+   }
+   if (xinfo->display)
+   {
+      XCloseDisplay (xinfo->display);
+      xinfo->display = NULL;
+   }
+}
+
+x11_info_t *
+open_window (const char *titlename, const char *iconname,
+	     unsigned width, unsigned height)
+/*
+ *  Open a X11 window of size 'width'x'height'.
+ *  If 'color' is false then allocate a colormap with grayscales.
+ *  Window and icon titles are given by 'titlename' and 'iconname',
+ *  respectively.
+ *
+ *  Return value:
+ *	X11 info struct containing display, gc, window ID and colormap.
+ */
+{
+   XVisualInfo		visual_template; /* template for XGetVisualInfo() */
+   XVisualInfo		visual_info;	/* return value of XGetVisualInfo() */
+   int			visual_n;	/* # of matches of XGetVisualInfo */
+   XEvent		xev;		
+   XSizeHints		hint;		
+   XSetWindowAttributes xswa;		
+   unsigned int		fg, bg;		/* foreground and background color */
+   unsigned int		mask;		/* event mask */
+   x11_info_t		*xinfo = calloc (1, sizeof (x11_info_t));
+   long                 visual_mask;
+   
+   if (!xinfo)
+      error ("Out of memory");
+   /*
+    *  Initialization of display
+    */
+   xinfo->display = XOpenDisplay (NULL);
+   if (xinfo->display == NULL)
+      error ("Can't open display.\n"
+	     "Make sure that your environment variable DISPLAY "
+	     "is set correctly.");
+
+   xinfo->screen = DefaultScreen (xinfo->display);
+   xinfo->gc     = DefaultGC (xinfo->display, xinfo->screen);
+
+   {
+      unsigned   depth [] 	= {32, 24, 16};
+      int        class [] 	= {TrueColor, PseudoColor};
+      const char *class_text [] = {"TrueColor", "PseudoColor"};
+      Status     found 		= 0;
+      unsigned   d, c;
+
+      for (d = 0; !found && d < sizeof (depth) / sizeof (unsigned); d++)
+	 for (c = 0; !found && c < sizeof (class) / sizeof (int); c++)
+	 {
+	    found = XMatchVisualInfo (xinfo->display, xinfo->screen,
+				      depth [d], class [c], &visual_info);
+	    if (found)
+	       fprintf (stderr, "%s : %d bit colordepth.\n",
+			class_text [c], depth [d]);
+	 }
+      if (!found && fiasco_get_verbosity ())
+	 error ("Can't find a 16/24/32 bit TrueColor/DirectColor display");
+   }
+   
+   /* Width and height of the display window */
+   hint.x = hint.y = 0;
+   hint.min_width  = hint.max_width  = hint.width  = width;
+   hint.min_height = hint.max_height = hint.height = height;
+   hint.flags = PSize | PMinSize | PMaxSize;
+
+   /* Get some colors */
+   bg = WhitePixel (xinfo->display, xinfo->screen);
+   fg = BlackPixel (xinfo->display, xinfo->screen);
+
+   /* Make the window */
+   mask = CWBackPixel | CWBorderPixel;
+   if (visual_info.depth >= 16)
+   {
+      mask |= CWColormap;
+      xswa.colormap = XCreateColormap (xinfo->display,
+				       DefaultRootWindow (xinfo->display),
+				       visual_info.visual, AllocNone);
+   }
+   xswa.background_pixel = bg;
+   xswa.border_pixel     = fg;
+   xinfo->window = XCreateWindow (xinfo->display,
+				  DefaultRootWindow (xinfo->display), 0, 0,
+				  width, height, 1, visual_info.depth,
+				  InputOutput, visual_info.visual,
+				  mask, &xswa);
+
+   XSelectInput (xinfo->display, xinfo->window, StructureNotifyMask);
+
+   /* Tell other applications about this window */
+   XSetStandardProperties (xinfo->display, xinfo->window, titlename, iconname,
+			   None, NULL, 0, &hint);
+
+   /* Map window. */
+   XMapWindow (xinfo->display, xinfo->window);
+
+   /* Wait for map. */
+   do
+   {
+      XNextEvent (xinfo->display, &xev);
+   }
+   while (xev.type != MapNotify || xev.xmap.event != xinfo->window);
+
+   /* Allocate colors */
+
+   return xinfo;
+}
+
+void
+alloc_ximage (x11_info_t *xinfo, unsigned width, unsigned height)
+/*
+ *  Allocate ximage of size 'width'x'height'.
+ *  If USE_SHM is defined then use shared memory extensions.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'ximage->ximage' and 'ximage->pixels' are set to useful values.
+ */
+{
+   char dummy;
+   
+#ifdef USE_SHM
+   if (XShmQueryExtension(xinfo->display))
+   {
+      if (fiasco_get_verbosity ())
+	 fprintf (stderr, "Trying shared memory.\n");
+      shmem_flag = 1;
+   }
+   else
+   {
+      shmem_flag = 0;
+      if (fiasco_get_verbosity ())
+	 fprintf (stderr,
+		  "Shared memory not supported\nReverting to normal Xlib.\n");
+   }
+
+   if (shmem_flag)
+      CompletionType = XShmGetEventBase (xinfo->display) + ShmCompletion;
+
+   InstallXErrorHandler (xinfo);
+
+   if (shmem_flag)
+   {
+      xinfo->ximage = XShmCreateImage (xinfo->display,
+				       DefaultVisual (xinfo->display,
+						      xinfo->screen),
+				       DefaultDepth (xinfo->display,
+						     xinfo->screen), ZPixmap,
+				       NULL, &shminfo1, width, height);
+
+      /* If no go, then revert to normal Xlib calls. */
+
+      if (xinfo->ximage == NULL)
+      {
+	 if (fiasco_get_verbosity ())
+	    fprintf (stderr,
+		     "Shared memory error, disabling (Ximage error).\n");
+	 goto shmemerror;
+      }
+
+      /* Success here, continue. */
+
+      shminfo1.shmid = shmget (IPC_PRIVATE, xinfo->ximage->bytes_per_line
+			       * xinfo->ximage->height, IPC_CREAT | 0777);
+
+      if (shminfo1.shmid < 0)
+      {
+	 XDestroyImage (xinfo->ximage);
+	 if (fiasco_get_verbosity ())
+	    fprintf (stderr,
+		     "Shared memory error, disabling (seg id error).\n");
+	 goto shmemerror;
+      }
+
+      shminfo1.shmaddr = (char *) shmat (shminfo1.shmid, 0, 0);
+      shminfo2.shmaddr = (char *) shmat (shminfo2.shmid, 0, 0);
+
+      if (shminfo1.shmaddr == ((char *) -1))
+      {
+	 XDestroyImage (xinfo->ximage);
+	 if (shminfo1.shmaddr != ((char *) -1))
+	    shmdt (shminfo1.shmaddr);
+	 if (fiasco_get_verbosity ())
+	    fprintf (stderr,
+		     "Shared memory error, disabling (address error).\n");
+	 goto shmemerror;
+      }
+
+      xinfo->ximage->data = shminfo1.shmaddr;
+      xinfo->pixels       = (byte_t *) xinfo->ximage->data;
+      shminfo1.readOnly   = False;
+
+      XShmAttach (xinfo->display, &shminfo1);
+      XSync (xinfo->display, False);
+
+      if (gXErrorFlag)
+      {
+	 /* Ultimate failure here. */
+	 XDestroyImage (xinfo->ximage);
+	 shmdt (shminfo1.shmaddr);
+	 if (fiasco_get_verbosity ())
+	    fprintf (stderr, "Shared memory error, disabling.\n");
+	 gXErrorFlag = 0;
+	 goto shmemerror;
+      }
+      else
+	 shmctl (shminfo1.shmid, IPC_RMID, 0);
+      if (fiasco_get_verbosity ())
+	 fprintf (stderr, "Sharing memory.\n");
+   }
+   else
+   {
+     shmemerror:
+      shmem_flag = 0;
+#endif /* USE_SHM */
+
+      xinfo->ximage = XCreateImage (xinfo->display,
+				    DefaultVisual (xinfo->display,
+						   xinfo->screen),
+				    DefaultDepth (xinfo->display,
+						  xinfo->screen),
+				    ZPixmap, 0, &dummy, width, height, 8, 0);
+      xinfo->pixels = calloc (width * height,
+			      xinfo->ximage->depth <= 8
+			      ? sizeof (byte_t)
+			      : (xinfo->ximage->depth <= 16
+				 ? sizeof (u_word_t) : sizeof (unsigned int)));
+      if (!xinfo->pixels)
+	 error ("Out of memory.");
+    
+#ifdef USE_SHM
+   }
+
+   DeInstallXErrorHandler (xinfo);
+#endif /* USE_SHM */
+}
+
+#endif /* not X_DISPLAY_MISSING */
diff --git a/converter/other/fiasco/display.h b/converter/other/fiasco/display.h
new file mode 100644
index 00000000..5f30b117
--- /dev/null
+++ b/converter/other/fiasco/display.h
@@ -0,0 +1,49 @@
+/*
+ *  display.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:51:17 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _DISPLAY_H
+#define _DISPLAY_H
+
+#ifndef X_DISPLAY_MISSING
+
+#include <X11/Xlib.h>
+
+#include "types.h"
+#include "image.h"
+
+typedef struct x11_info
+{
+   Display *display;
+   int	    screen;			/* default screen number */
+   Window   window;			
+   XImage  *ximage;
+   GC	    gc;
+   byte_t  *pixels;
+} x11_info_t;
+
+void
+display_image (unsigned x0, unsigned y0, x11_info_t *xinfo);
+void
+close_window (x11_info_t *xinfo);
+x11_info_t *
+open_window (const char *titlename, const char *iconname,
+	     unsigned width, unsigned height);
+void
+alloc_ximage (x11_info_t *xinfo, unsigned width, unsigned height);
+
+#endif /* X_DISPLAY_MISSING */
+
+#endif /* not _DISPLAY_H */
diff --git a/converter/other/fiasco/doc/README.LIB b/converter/other/fiasco/doc/README.LIB
new file mode 100644
index 00000000..4bf8c382
--- /dev/null
+++ b/converter/other/fiasco/doc/README.LIB
@@ -0,0 +1,51 @@
+---------------------------------------------------------------------------
+	 FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+		       Copyright (C) 1994-2000
+     Ullrich Hafner <hafner@bigfoot.de>, http://ulli.linuxave.net
+			     Version 1.0
+---------------------------------------------------------------------------
+
+FIASCO is an image and video compression system based on fractal
+coding which outperforms the well known JPEG and MPEG
+standards. FIASCO has been developed during my Ph.D. thesis "Low
+Bit-Rate Image and Video Coding with Weighted Finite Automata", Mensch
+& Buch Verlag, ISBN 3-89820-002-7.
+
+Some information about the FIASCO compression library:
+The library consists of the five "classes"
+
+	- fiasco_coder: used to encode a still image or a sequence of
+	  frames to a FIASCO stream, see fiasco_coder(3) or the file
+	  bin/cwfa.c for details.
+
+	- fiasco_decoder: used to decode the individual frames step by
+	  step, see fiasco_decoder(3) or the file bin/dwfa.c for
+	  details. 
+
+	- fiasco_image: internal representation of an decoded FIASCO
+	  image, see fiasco_image(3) or the file bin/dwfa.c for
+	  details. 
+
+	- fiasco_renderer: used to render the generated image object
+	  to one of the supported X11 output formats, see
+	  fiasco_render(3) or the files bin/dwfa.c or bin/pnmpsnr.c for
+	  details.
+
+	- fiasco_options: used to control various decoder and encoder
+	  options, see fiasco_options(3) or the files bin/cwfa.c,
+	  bin/dwfa.c or bin/pnmpsnr.c for details.
+
+
+Since the coder doesn't store any internal information, the only
+method of this class is the function fiasco_coder ().
+
+For all other classes, a new object is created with the
+fiasco_[object]_new () function, e.g., fiasco_decoder_new () creates a
+new fiasco_decoder_t object. Each object has to be deleted manually by
+calling the destructor fiasco_[object]_delete () (or by calling the
+method object->delete (object)). If you prefer C++ calls: every
+function of the type fiasco_[object]_[method] can be called via
+[object]->[method] ([object], args), too.
+
+Note: PLEASE use only functions, which are noted in the fiasco.h file
+(i.e., all functions and types with the prefix fiasco_)!
diff --git a/converter/other/fiasco/doc/fiasco_c_options.3 b/converter/other/fiasco/doc/fiasco_c_options.3
new file mode 100644
index 00000000..58b2da44
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_c_options.3
@@ -0,0 +1 @@
+.so man3/fiasco_c_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_c_options_delete.3 b/converter/other/fiasco/doc/fiasco_c_options_delete.3
new file mode 100644
index 00000000..58b2da44
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_c_options_delete.3
@@ -0,0 +1 @@
+.so man3/fiasco_c_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_c_options_new.3 b/converter/other/fiasco/doc/fiasco_c_options_new.3
new file mode 100644
index 00000000..52efb86c
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_c_options_new.3
@@ -0,0 +1,432 @@
+.\" $Id: fiasco_c_options_new.3,v 1.1 2000/10/28 17:35:06 hafner Exp $
+.TH fiasco 3 "April, 2000" "FIASCO" "Fractal Image And Sequence COdec"
+
+.SH NAME
+.B fiasco_c_options_new, fiasco_c_options_delete,
+.B fiasco_c_options_set_progress_meter, fiasco_c_options_set_basisfile,
+.B fiasco_c_options_set_smoothing, fiasco_c_options_set_tiling, 
+.B fiasco_c_options_set_chroma_quality, fiasco_c_options_set_optimizations,
+.B fiasco_c_options_set_prediction, fiasco_c_options_set_video_param,
+.B fiasco_c_options_set_quantization, fiasco_c_options_set_frame_pattern
+.B fiasco_c_options_set_title, fiasco_c_options_set_comment
+\- define additional options of FIASCO coder and decoder 
+
+.SH SYNOPSIS
+.B #include <fiasco.h>
+.sp
+.BI "fiasco_c_options_t *"
+.fi
+.BI "fiasco_c_options_new"
+.fi
+.BI "   (void);"
+.sp
+.BI "void"
+.fi
+.BI "fiasco_c_options_delete"
+.fi
+.BI "   (fiasco_c_options_t * "options );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_c_options_set_basisfile"
+.fi
+.BI "   (fiasco_c_options_t * "options ,
+.fi
+.BI "    const char * "filename );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_c_options_set_chroma_quality"
+.fi
+.BI "   (fiasco_c_options_t * "options ,
+.fi
+.BI "    float "quality_factor ,
+.fi
+.BI "    unsigned "dictionary_size );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_c_options_set_comment"
+.fi
+.BI "   (fiasco_c_options_t * "options ,
+.fi
+.BI "    const char * "comment );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_c_options_set_frame_pattern"
+.fi
+.BI "   (fiasco_c_options_t * "options ,
+.fi
+.BI "    const char * "pattern );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_c_options_set_optimizations"
+.fi
+.BI "   (fiasco_c_options_t * "options ,
+.fi
+.BI "    unsigned "min_block_level ,
+.fi
+.BI "    unsigned "max_block_level ,
+.fi
+.BI "    unsigned "max_elements ,
+.fi
+.BI "    unsigned "dictionary_size ,
+.fi
+.BI "    unsigned "optimization_level );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_c_options_set_quantization"
+.fi
+.BI "   (fiasco_c_options_t * "options ,
+.fi
+.BI "    unsigned "mantissa ,
+.fi
+.BI "    fiasco_rpf_range_e "range ,
+.fi
+.BI "    unsigned "dc_mantissa ,
+.fi
+.BI "    fiasco_rpf_range_e "dc_range );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_c_options_set_prediction"
+.fi
+.BI "   (fiasco_c_options_t * "options ,
+.fi
+.BI "    int "intra_prediction ,
+.fi
+.BI "    unsigned "min_block_level ,
+.fi
+.BI "    unsigned "max_block_level );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_c_options_set_progress_meter"
+.fi
+.BI "   (fiasco_c_options_t * "options ,
+.fi
+.BI "    fiasco_progress_e "type );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_c_options_set_smoothing"
+.fi
+.BI "   (fiasco_c_options_t * "options ,
+.fi
+.BI "    unsigned "smoothing );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_c_options_set_tiling"
+.fi
+.BI "   (fiasco_c_options_t * "options ,
+.fi
+.BI "    fiasco_tiling_e "method ,
+.fi
+.BI "    unsigned "exponent );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_c_options_set_title"
+.fi
+.BI "   (fiasco_c_options_t * "options ,
+.fi
+.BI "    const char * "title );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_c_options_set_video_param"
+.fi
+.BI "   (fiasco_c_options_t * "options ,
+.fi
+.BI "    unsigned "frames_per_second ,
+.fi
+.BI "    int "half_pixel_prediction ,
+.fi
+.BI "    int "cross_B_search ,
+.fi
+.BI "    int "B_as_past_ref );
+.fi
+
+.SH DESCRIPTION
+The \fBfiasco_c_options_new()\fP function allocates and initializes a
+FIASCO options object which is used to control additional compression
+ parameters.
+
+Conversely, the function \fBfiasco_c_options_delete()\fP discards the
+given FIASCO coder options object.
+
+Several member functions are available to modify the default behavior
+of the FIASCO coder.
+
+\fBfiasco_c_options_set_smoothing()\fP sets the
+\fIsmoothing\fP-percentage along partitioning borders when the image
+is regenerated; default is 70. This value is stored in the FIASCO file
+and is used as default smoothing percentage in the decoder.
+
+\fBfiasco_c_options_set_frame_pattern()\fP sets the type of inter frame
+compression which should be applied to individual frames of a video
+stream; default is "IPPPPPPPPP". 
+
+\fBfiasco_c_options_set_tiling()\fP sets \fImethod\fP and \fIexponent\fP
+of the image tiling algorithm which runs as initial step of the
+encoder; by default the image is subdivided into 16 tiles which
+are sorted by decreasing variance. 
+
+\fBfiasco_c_options_set_basisfile()\fP sets the \fIfilename\fP of
+the FIASCO initial basis (codebook of dictionary vectors); default is
+"small.fco". 
+
+\fBfiasco_c_options_set_chroma_quality()\fP sets the quality used when
+coding the chroma channels of a color image to the term "\fIquality\fP
+of luminance / \fIquality_factor\fP"; default is 2. Moreover, the size
+of the codebook is limited by \fIdictionary_size\fP; default is 40
+elements. 
+
+\fBfiasco_c_options_set_comment()\fP sets a \fIcomment\fP string to be
+stored in the FIASCO file; default is the empty string. 
+
+\fBfiasco_c_options_set_title()\fP sets a \fItitle\fP string to be
+stored in the FIASCO file; default is the empty string. 
+
+\fBfiasco_c_options_set_optimizations()\fP toggles various coding
+optimizations. E.g., the size of the dictionary (default is 10000),
+the subset of dictionary elements to use for an individual
+approximation (default is 5), the size of the image blocks to consider
+(4x4, ..., 64x64), and some additional low level
+optimizations (default level is 1). 
+
+\fBfiasco_c_options_set_prediction()\fP enables an additional intra
+block prediction by using a DC component approximation. By giving
+levels \fImin_block_level\fP and \fImax_block_level\fP the prediction
+can be limited to a small range of blocks only. By default, this
+method is disabled. 
+
+\fBfiasco_c_options_set_video_param()\fP defines the framerate (default
+is 25) and toggles whether to use half pixel precise motion
+compensated prediction (disabled by default), whether to determine
+motion vectors of interpolated prediction with the Cross-B-Search
+algorithm (disabled by default), and whether to allow B frames to be
+used for B frame predicion (disabled by default).
+
+\fBfiasco_c_options_set_quantization()\fP defines the quantization
+parameters of the approximation coefficients. By default the range of
+DC coefficients is [-1,+1] using a mantissa of 5 bits (and one sign
+bit). By default, all other coefficients are quantized with 3 mantissa
+bits in the interval [-1.5,+1.5].
+
+\fBfiasco_c_options_set_progress_meter()\fP sets the type of progress
+meter to be used during coding. By default, an RPM style progress bar
+using 50 hash marks (####) is used.
+
+.SH ARGUMENTS
+.TP
+options
+This object encapsulates various coding parameters.  
+
+.TP
+smoothing
+This percentage (range is 0 - i.e., no smoothing - to 100) defines how
+much the regenerated image is smoothed along the partitioning borders.
+
+.TP
+method
+Defines the algorithm which should be used to sort the image tiles
+which are generated in the initial coding step. If \fImethod\fP is
+\fBFIASCO_VARIANCE_ASC\fP then the tiles are sorted by variance - the
+first tile has the lowest variance. Conversely, when using
+\fBFIASCO_VARIANCE_DSC\fP the first tile has the largest variance. If
+\fImethod\fP is \fBFIASCO_SPIRAL_ASC\fP then the tiles are sorted like
+a spiral starting in the middle of the image. Conversely, when using
+\fBFIASCO_SPIRAL_DSC\fP the tiles are sorted like a spiral starting in
+the upper left corner.
+
+.TP
+exponent
+This value sets the number of image tiles - which are generated in the
+initial step of the encoder - to 2^\fIexponent\fP.
+
+.TP
+title
+This value is the title string of the FIASCO file. It is displayed, e.g.,
+in the window title of the decoder.
+
+.TP
+comment
+This value defines an optional comment to be stored in the FIASCO file.
+
+.TP
+pattern
+This string defines the sequence of frame types. Character \fIn\fP of
+the string defines the type of frame \fIn\fP (\fIpattern\fP is
+periodically extended). Three different frame types are available
+(case insensitive): choose 'i' for intra-frames (no inter frame
+prediction is used), 'p' for predicted frames (a frame of the
+past is used for prediction), or 'b' for bi-directional predicted
+frames (both a frame of the past and the future is used for
+prediction).
+
+.TP
+filename
+The initial basis (codebook) of the coder is loaded from this
+(ASCII) file. Files that already come with FIASCO are "small.fco" (3 elements),
+"medium.fco" (132 elements), and "large.fco" (219 elements). 
+
+.TP
+quality_factor
+When coding chroma channels (Cb and Cr band) the approximation quality
+is determined by the term `quality of Y component' / \fIquality_factor\fP.
+
+.TP
+dictionary_size
+FIASCO uses a dictionary (codebook) of variable size to approximate
+individual image blocks. The size of the codebook can be limited by
+\fIdictionary_size\fP to reduce the coding time, however, at the cost
+of decreasing quality. 
+
+.TP
+min_block_level
+During coding only those image blocks are considered for approximation
+(or prediction) which binary tree level is larger than
+\fImin_block_level\fP (minimum value is 3). (Since FIASCO internally
+works with binary trees, the size of an image block is determined by
+the \fIlevel\fP of the corresponding binary tree). Refer to following
+table to convert these values:
+
+.ce
+level | width | height
+.fi
+------+-------+--------
+.fi
+  0   |    1  |    1  
+.fi
+  1   |    1  |    2  
+.fi
+  2   |    2  |    2  
+.fi
+  3   |    2  |    4  
+.fi
+  4   |    4  |    4  
+.fi
+  5   |    4  |    8  
+.fi
+  6   |    8  |    8  
+.fi
+  7   |    8  |   16
+.fi
+------+-------+--------
+.fi
+The larger this value is the faster the coder runs but the worse the
+image quality will be.
+
+.TP
+max_block_level
+During coding only those image blocks are considered for approximation
+(or prediction) which binary tree level is smaller than
+\fImax_block_level\fP. The smaller this value is the faster the coder
+runs but the worse the image quality will be.
+
+.TP
+max_elements
+This value defines how many dictionary elements can be
+used to approximate an individual image block. The smaller this positive
+value (range is 1 to 5) is the faster the coder runs but the worse the
+image quality will be. 
+
+.TP
+optimization_level
+Additional low level optimizations are available by setting
+\fIoptimization_level\fP to one of the following values:
+.fi
+0 standard approximation method
+.fi
+1 slightly increases the approximation quality, running time is
+twice as high as with the standard method 
+.fi
+2 hardly increases the approximation quality of method 1, running time
+is twice as high as with method 1 (this method just remains for
+completeness) 
+.fi
+
+.TP
+intra_prediction
+If \fIintra_prediction\fP is set to a non-zero value then an
+additional block prediction of intra-frames is enabled. For some
+images, the image quality is slightly improved, however, at the cost of
+a significantly increased running time of the coder. 
+
+.TP
+frames_per_second
+This value defines the frame rate, i.e., how many frames per second
+should be displayed. This value has no effect during coding, it is just
+passed to the FIASCO output file where it is read and used by the
+decoder.
+
+.TP
+half_pixel_prediction
+A non-zero value enables half pixel precise motion compensated
+prediction. 
+
+.TP
+cross_B_search
+A non-zero value enables the fast Cross-B-Search algorithm to determine
+the motion vectors of an interpolated prediction. Otherwise,
+exhaustive search (in the given search range) is used. 
+
+.TP
+B_as_past_ref
+A non-zero value allows not only I- and P-frames but also B-frames to be
+used for a forward or bi-directional predicion.
+
+.TP
+mantissa, range
+Approximation coefficients are quantized to a small number of
+values (in fixed point format) in the interval [-\fIrange\fP,
++\fIrange\fP]. The number of \fImantissa\fP bits defines the accuracy of
+quantization.
+
+.TP
+dc_mantissa, dc_range
+Approximation coefficients of the DC component are quantized in a
+different way: the number of mantissa bits is given by
+\fIdc_mantissa\fP whereas the quantization interval is given by
+[-\fIdc_range\fP, +\fBdc_range\fP].
+
+.TP
+type
+This value sets the \fItype\fP of progress meter which should be used
+during coding. The following types are available:
+.fi
+\fBFIASCO_PROGRESS_NONE\fP:  no output at all
+.fi
+\fBFIASCO_PROGRESS_BAR\fP: print hash marks (###)
+\fBFIASCO_PROGRESS_PERCENT\fP: percentage meter (50%)
+
+.SH RETURN VALUES
+The function \fBfiasco_c_options_new()\fP returns a pointer to the
+newly allocated coder option object. If an error has been catched, a
+NULL pointer is returned.
+
+All set functions return 1 on success and 0 if an error has been
+catched.  
+
+In case of an error, use the function fiasco_get_error_message(3) to
+get a string with the last error message of FIASCO.
+
+.SH "SEE ALSO"
+.br
+.BR fiasco_decoder "(3), " fiasco_coder (3)
+
+Ullrich Hafner, Juergen Albert, Stefan Frank, and Michael Unger.
+\fBWeighted Finite Automata for Video Compression\fP, IEEE Journal on
+Selected Areas In Communications, January 1998
+.br
+Ullrich Hafner. \fBLow Bit-Rate Image and Video Coding with Weighted
+Finite Automata\fP, Ph.D. thesis, Mensch & Buch Verlag, ISBN
+3-89820-002-7, October 1999.
+
+.SH AUTHOR
+Ullrich Hafner <hafner@bigfoot.de>
diff --git a/converter/other/fiasco/doc/fiasco_c_options_set_basisfile.3 b/converter/other/fiasco/doc/fiasco_c_options_set_basisfile.3
new file mode 100644
index 00000000..58b2da44
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_c_options_set_basisfile.3
@@ -0,0 +1 @@
+.so man3/fiasco_c_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_c_options_set_chroma_quality.3 b/converter/other/fiasco/doc/fiasco_c_options_set_chroma_quality.3
new file mode 100644
index 00000000..58b2da44
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_c_options_set_chroma_quality.3
@@ -0,0 +1 @@
+.so man3/fiasco_c_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_c_options_set_comment.3 b/converter/other/fiasco/doc/fiasco_c_options_set_comment.3
new file mode 100644
index 00000000..58b2da44
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_c_options_set_comment.3
@@ -0,0 +1 @@
+.so man3/fiasco_c_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_c_options_set_frame_pattern.3 b/converter/other/fiasco/doc/fiasco_c_options_set_frame_pattern.3
new file mode 100644
index 00000000..58b2da44
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_c_options_set_frame_pattern.3
@@ -0,0 +1 @@
+.so man3/fiasco_c_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_c_options_set_optimizations.3 b/converter/other/fiasco/doc/fiasco_c_options_set_optimizations.3
new file mode 100644
index 00000000..58b2da44
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_c_options_set_optimizations.3
@@ -0,0 +1 @@
+.so man3/fiasco_c_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_c_options_set_prediction.3 b/converter/other/fiasco/doc/fiasco_c_options_set_prediction.3
new file mode 100644
index 00000000..58b2da44
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_c_options_set_prediction.3
@@ -0,0 +1 @@
+.so man3/fiasco_c_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_c_options_set_progress_meter.3 b/converter/other/fiasco/doc/fiasco_c_options_set_progress_meter.3
new file mode 100644
index 00000000..58b2da44
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_c_options_set_progress_meter.3
@@ -0,0 +1 @@
+.so man3/fiasco_c_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_c_options_set_quantization.3 b/converter/other/fiasco/doc/fiasco_c_options_set_quantization.3
new file mode 100644
index 00000000..58b2da44
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_c_options_set_quantization.3
@@ -0,0 +1 @@
+.so man3/fiasco_c_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_c_options_set_smoothing.3 b/converter/other/fiasco/doc/fiasco_c_options_set_smoothing.3
new file mode 100644
index 00000000..58b2da44
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_c_options_set_smoothing.3
@@ -0,0 +1 @@
+.so man3/fiasco_c_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_c_options_set_tiling.3 b/converter/other/fiasco/doc/fiasco_c_options_set_tiling.3
new file mode 100644
index 00000000..58b2da44
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_c_options_set_tiling.3
@@ -0,0 +1 @@
+.so man3/fiasco_c_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_c_options_set_title.3 b/converter/other/fiasco/doc/fiasco_c_options_set_title.3
new file mode 100644
index 00000000..58b2da44
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_c_options_set_title.3
@@ -0,0 +1 @@
+.so man3/fiasco_c_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_c_options_set_video_param.3 b/converter/other/fiasco/doc/fiasco_c_options_set_video_param.3
new file mode 100644
index 00000000..58b2da44
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_c_options_set_video_param.3
@@ -0,0 +1 @@
+.so man3/fiasco_c_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_coder.3 b/converter/other/fiasco/doc/fiasco_coder.3
new file mode 100644
index 00000000..3d1c6b87
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_coder.3
@@ -0,0 +1,106 @@
+.\" $Id: fiasco_coder.3,v 1.2 2000/10/28 17:39:32 hafner Exp $
+.TH fiasco 3 "April, 2000" "FIASCO" "Fractal Image And Sequence COdec"
+
+.SH NAME
+.B  fiasco_coder
+\- compress image files to a FIASCO file
+
+.SH SYNOPSIS
+.B #include <fiasco.h>
+.sp
+.BI "int "
+.fi
+.BI "fiasco_coder (char const * const * "image_names ,
+.fi
+.BI "              const char * "fiasco_name ,
+.fi
+.BI "              float "quality ,
+.fi
+.BI "              const fiasco_c_options_t * "options );
+.fi
+
+.SH DESCRIPTION
+The \fBfiasco-coder()\fP function compresses the image file(s) given
+by the list of \fIimage_names\fP and creates the new FIASCO output file
+\fIfiasco_name\fP. Besides the approximation \fIquality\fP, several
+compression parameters can be adjusted by the class \fBoptions\fP (see
+fiasco_c_options_new(3)).
+
+.SH ARGUMENTS
+
+.TP
+image_names
+NULL terminated array of image filenames to process. If the first
+array element is "-" or a NULL pointer then FIASCO reads the image
+from standard input. Each array element either has to be an image
+filename or a template of the form:
+
+.ce
+prefix[start-end{+,-}step]suffix
+
+Templates are useful when compressing video streams: e.g., if the template
+"img0[12-01-2].pgm" is given as array element, then FIASCO compresses the
+images img012.pgm, img010.pgm, ..., img002.pgm (in this order).
+
+If a filename is a relative path then the images are searched for in
+the current directory and in the (colon-separated) list of directories
+given by the environment variable \fBFIASCO_IMAGES\fP.
+
+.TP
+fiasco_name
+Name of the FIASCO output file. If the name is "-" or NULL then the
+file is produced on standard output.
+
+If \fIfiasco_name\fP is a relative path and the environment variable
+\fBFIASCO_DATA\fP is a (colon-separated) list of directories, then the
+output file is written to the first (writable) directory of this
+list. Otherwise, the current directory is used to store the output
+file.
+
+.TP
+quality
+Defines the quality of compression. Quality has to be a positive
+value, its typical range is 1.0 (worst) to 100.0 (best). Larger values
+are also allowed - at the cost of exploding encoding times.
+
+.TP
+options
+This "class" encapsulates the various coding and decoding
+parameters. Use the functions fiasco_c_options_new(3) and
+fiasco_c_options_delete(3) to create and delete an object of this
+class. Several member functions (see fiasco_c_options(3)) are
+available to change the default values.
+
+.SH RETURN VALUE
+The function \fBfiasco_coder()\fP returns 1 if the FIASCO file has
+been successfully written. If an error has been catched during
+compression, 0 is returned - use the function
+fiasco_get_error_message(3) to get the last error message of FIASCO.
+
+.SH ENVIRONMENT
+.PD 0
+.TP
+.B FIASCO_IMAGES
+Search path for image files. Default is "./".
+.TP
+.B FIASCO_DATA
+Search and save path for FIASCO files. Default is "./".
+.PD 
+
+.SH "SEE ALSO"
+.br
+.BR fiasco_c_options_new "(3), " fiasco_c_options_delete (3), 
+.br
+.BR fiasco_c_options "(3), " fiasco_get_error_message (3)
+.br
+
+Ullrich Hafner, Juergen Albert, Stefan Frank, and Michael Unger.
+\fBWeighted Finite Automata for Video Compression\fP, IEEE Journal on
+Selected Areas In Communications, January 1998
+.br
+Ullrich Hafner. \fBLow Bit-Rate Image and Video Coding with Weighted
+Finite Automata\fP, Ph.D. thesis, Mensch & Buch Verlag, ISBN
+3-89820-002-7, October 1999.
+
+.SH AUTHOR
+Ullrich Hafner <hafner@bigfoot.de>
diff --git a/converter/other/fiasco/doc/fiasco_d_options.3 b/converter/other/fiasco/doc/fiasco_d_options.3
new file mode 100644
index 00000000..21f1db63
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_d_options.3
@@ -0,0 +1 @@
+.so man3/fiasco_d_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_d_options_delete.3 b/converter/other/fiasco/doc/fiasco_d_options_delete.3
new file mode 100644
index 00000000..21f1db63
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_d_options_delete.3
@@ -0,0 +1 @@
+.so man3/fiasco_d_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_d_options_new.3 b/converter/other/fiasco/doc/fiasco_d_options_new.3
new file mode 100644
index 00000000..4294330a
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_d_options_new.3
@@ -0,0 +1,122 @@
+.\" $Id: fiasco_d_options_new.3,v 1.1 2000/10/28 17:35:12 hafner Exp $
+.TH fiasco 3 "April, 2000" "FIASCO" "Fractal Image And Sequence COdec"
+
+.SH NAME
+.B  fiasco_d_options_new, fiasco_d_options_set_magnification,
+.B fiasco_d_options_delete, fiasco_d_options_set_smoothing
+.B fiasco_d_options_set_4_2_0_format
+\- define additional options of FIASCO decoder 
+
+.SH SYNOPSIS
+.B #include <fiasco.h>
+.sp
+.BI "fiasco_d_options_t *"
+.fi
+.BI "fiasco_d_options_new"
+.fi
+.BI "   (void);"
+.sp
+.BI "void"
+.fi
+.BI "fiasco_d_options_delete"
+.fi
+.BI "   (fiasco_d_options_t * "options );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_d_options_set_4_2_0_format"
+.fi
+.BI "   (fiasco_d_options_t * "options ,
+.fi
+.BI "    int "format );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_d_options_set_magnification"
+.fi
+.BI "   (fiasco_d_options_t * "options ,
+.fi
+.BI "    int "level );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_d_options_set_smoothing"
+.fi
+.BI "   (fiasco_d_options_t * "options ,
+.fi
+.BI "    unsigned "smoothing );
+.fi
+
+.SH DESCRIPTION
+The \fBfiasco_d_options_new()\fP function allocates and initializes a
+FIASCO options object which is used to control additional
+decompression parameters.
+
+Conversely, the function \fBfiasco_d_options_delete()\fP discards the
+given FIASCO decoder options object.
+
+Several member functions are available to modify the default behavior
+of the FIASCO decoder.
+
+\fBfiasco_d_options_set_smoothing()\fP sets the
+\fIsmoothing\fP-percentage along partitioning borders when the images
+are regenerated; default is 70.
+
+\fBfiasco_d_options_set_magnification()\fP sets the \fImagnification\fP
+of the regenerated image; default is 0, i.e., the image geometry is
+not changed.
+
+\fBfiasco_d_options_set_4_2_0_format()\fP defines whether the decoder
+should use the default 4:4:4 format or the 4:2:0 format. The latter
+one significantly reduces the decoding time at the cost of some
+additional blocking artefacts.
+
+.SH ARGUMENTS
+.TP
+options
+This object encapsulates various decoding parameters.  
+
+.TP
+smoothing
+This percentage (range is 0 - i.e., no smoothing - to 100) defines how
+much the regenerated image is smoothed along the partitioning borders.
+
+.TP
+level
+This value gives the magnification of the decoded image with respect
+to the original size. Positive values increase and negative values
+decrease the width and height of the image by a factor of
+2^abs(\fIlevel\fP).
+
+.TP
+format
+If \fIformat\fP is 0 then the 4:4:4 color image format is used, i.e.,
+the chroma channel are of the same size as the luminance. Otherwise,
+the 4:2:0 format is used. Then, width and height of each chroma
+channel is only one half of the width and height of the luminance.
+
+.SH RETURN VALUES
+The function \fBfiasco_d_options_new()\fP returns a pointer to the
+newly allocated decoder option object. If an error has been catched, a
+NULL pointer is returned.
+
+All set functions return 1 on success and 0 if an error has been
+catched.  
+
+In case of an error, use the function fiasco_get_error_message(3) to
+get a string with the last error message of FIASCO.
+
+.SH "SEE ALSO"
+.br
+.BR fiasco_decoder "(3), " fiasco_coder (3)
+
+Ullrich Hafner, Juergen Albert, Stefan Frank, and Michael Unger.
+\fBWeighted Finite Automata for Video Compression\fP, IEEE Journal on
+Selected Areas In Communications, January 1998
+.br
+Ullrich Hafner. \fBLow Bit-Rate Image and Video Coding with Weighted
+Finite Automata\fP, Ph.D. thesis, Mensch & Buch Verlag, ISBN
+3-89820-002-7, October 1999.
+
+.SH AUTHOR
+Ullrich Hafner <hafner@bigfoot.de>
diff --git a/converter/other/fiasco/doc/fiasco_d_options_set_4_2_0_format.3 b/converter/other/fiasco/doc/fiasco_d_options_set_4_2_0_format.3
new file mode 100644
index 00000000..21f1db63
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_d_options_set_4_2_0_format.3
@@ -0,0 +1 @@
+.so man3/fiasco_d_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_d_options_set_magnification.3 b/converter/other/fiasco/doc/fiasco_d_options_set_magnification.3
new file mode 100644
index 00000000..21f1db63
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_d_options_set_magnification.3
@@ -0,0 +1 @@
+.so man3/fiasco_d_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_d_options_set_smoothing.3 b/converter/other/fiasco/doc/fiasco_d_options_set_smoothing.3
new file mode 100644
index 00000000..21f1db63
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_d_options_set_smoothing.3
@@ -0,0 +1 @@
+.so man3/fiasco_d_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_decoder.3 b/converter/other/fiasco/doc/fiasco_decoder.3
new file mode 100644
index 00000000..33c0d21b
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_decoder.3
@@ -0,0 +1 @@
+.so man3/fiasco_decoder_new.3
diff --git a/converter/other/fiasco/doc/fiasco_decoder_delete.3 b/converter/other/fiasco/doc/fiasco_decoder_delete.3
new file mode 100644
index 00000000..33c0d21b
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_decoder_delete.3
@@ -0,0 +1 @@
+.so man3/fiasco_decoder_new.3
diff --git a/converter/other/fiasco/doc/fiasco_decoder_get_comment.3 b/converter/other/fiasco/doc/fiasco_decoder_get_comment.3
new file mode 100644
index 00000000..33c0d21b
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_decoder_get_comment.3
@@ -0,0 +1 @@
+.so man3/fiasco_decoder_new.3
diff --git a/converter/other/fiasco/doc/fiasco_decoder_get_frame.3 b/converter/other/fiasco/doc/fiasco_decoder_get_frame.3
new file mode 100644
index 00000000..33c0d21b
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_decoder_get_frame.3
@@ -0,0 +1 @@
+.so man3/fiasco_decoder_new.3
diff --git a/converter/other/fiasco/doc/fiasco_decoder_get_framerate.3 b/converter/other/fiasco/doc/fiasco_decoder_get_framerate.3
new file mode 100644
index 00000000..33c0d21b
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_decoder_get_framerate.3
@@ -0,0 +1 @@
+.so man3/fiasco_decoder_new.3
diff --git a/converter/other/fiasco/doc/fiasco_decoder_get_height.3 b/converter/other/fiasco/doc/fiasco_decoder_get_height.3
new file mode 100644
index 00000000..33c0d21b
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_decoder_get_height.3
@@ -0,0 +1 @@
+.so man3/fiasco_decoder_new.3
diff --git a/converter/other/fiasco/doc/fiasco_decoder_get_length.3 b/converter/other/fiasco/doc/fiasco_decoder_get_length.3
new file mode 100644
index 00000000..33c0d21b
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_decoder_get_length.3
@@ -0,0 +1 @@
+.so man3/fiasco_decoder_new.3
diff --git a/converter/other/fiasco/doc/fiasco_decoder_get_title.3 b/converter/other/fiasco/doc/fiasco_decoder_get_title.3
new file mode 100644
index 00000000..33c0d21b
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_decoder_get_title.3
@@ -0,0 +1 @@
+.so man3/fiasco_decoder_new.3
diff --git a/converter/other/fiasco/doc/fiasco_decoder_get_width.3 b/converter/other/fiasco/doc/fiasco_decoder_get_width.3
new file mode 100644
index 00000000..33c0d21b
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_decoder_get_width.3
@@ -0,0 +1 @@
+.so man3/fiasco_decoder_new.3
diff --git a/converter/other/fiasco/doc/fiasco_decoder_is_color.3 b/converter/other/fiasco/doc/fiasco_decoder_is_color.3
new file mode 100644
index 00000000..33c0d21b
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_decoder_is_color.3
@@ -0,0 +1 @@
+.so man3/fiasco_decoder_new.3
diff --git a/converter/other/fiasco/doc/fiasco_decoder_new.3 b/converter/other/fiasco/doc/fiasco_decoder_new.3
new file mode 100644
index 00000000..05e981a9
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_decoder_new.3
@@ -0,0 +1,194 @@
+.\" $Id: fiasco_decoder_new.3,v 1.5 2000/10/28 17:39:32 hafner Exp $
+.TH fiasco 3 "April, 2000" "FIASCO" "Fractal Image And Sequence COdec"
+
+.SH NAME
+.B  fiasco_decoder_new, fiasco_decoder_delete,
+.B fiasco_decoder_write_frame, fiasco_decoder_get_frame,
+.B fiasco_decoder_get_length, fiasco_decoder_get_rate,
+.B fiasco_decoder_get_width, fiasco_decoder_get_height
+.B fiasco_decoder_get_title, fiasco_decoder_get_comment
+.B fiasco_decoder_is_color
+\- decompress a FIASCO file
+
+.SH SYNOPSIS
+.B #include <fiasco.h>
+.sp
+.BI "fiasco_decoder_t *"
+.fi
+.BI "fiasco_decoder_new (const char * "fiasco_name ,
+.fi
+.BI "                    const fiasco_d_options_t * "options );
+.sp
+.BI "void"
+.fi
+.BI "fiasco_decoder_delete (fiasco_decoder_t * "decoder );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_decoder_write_frame (fiasco_decoder_t * "decoder ,
+.fi
+.BI "                            const char * "image_name );
+.sp
+.BI "fiasco_image_t *"
+.fi
+.BI "fiasco_decoder_get_frame (fiasco_decoder_t * "decoder );
+.sp
+.BI "unsigned"
+.fi
+.BI "fiasco_decoder_get_length (fiasco_decoder_t * "decoder );
+.sp
+.BI "unsigned"
+.fi
+.BI "fiasco_decoder_get_rate (fiasco_decoder_t * "decoder );
+.sp
+.BI "unsigned"
+.fi
+.BI "fiasco_decoder_get_width (fiasco_decoder_t * "decoder );
+.sp
+.BI "unsigned"
+.fi
+.BI "fiasco_decoder_get_height (fiasco_decoder_t * "decoder );
+.sp
+.BI "const char *"
+.fi
+.BI "fiasco_decoder_get_title (fiasco_decoder_t * "decoder );
+.sp
+.BI "const char *"
+.fi
+.BI "fiasco_decoder_get_comment (fiasco_decoder_t * "decoder );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_decoder_is_color (fiasco_decoder_t * "decoder );
+.fi
+
+.SH DESCRIPTION
+The \fBfiasco_decoder_new()\fP function initializes the decompression
+of FIASCO file \fIfiasco_name\fP. Several decompression parameters
+can be adjusted by the class \fIoptions\fP (see
+fiasco_d_options_new(3)).
+
+The individual frames of a FIASCO video can be decompressed by calling
+successively either function \fBfiasco_decoder_write_frame()\fP or
+\fBfiasco_decoder_get_frame()\fP.
+
+The function \fBfiasco_decoder_write_frame()\fP decompresses the
+current frame and writes it in raw pgm(5) or ppm(5) format to the file
+\fIimage_name\fP. If \fIimage_name\fP=- or a NULL pointer then the
+image file is produced on the standard output. If \fIimage_name\fP is a
+relative path and the environment variable \fBFIASCO_IMAGES\fP is a
+(colon-separated) list of directories, then the output file is
+written to the first (writable) directory of this list. Otherwise, the
+current directory is used to store the file.
+
+The function \fBfiasco_decoder_get_frame()\fP decompresses the
+current frame and returns the computed image object. Use the function
+fiasco_renderer_new(3) to create a renderer object that converts the
+FIASCO image to the desired format. 
+
+After all frames have been decompressed, the function
+\fBfiasco_decoder_delete()\fP should be called to close the input file
+and to free temporarily allocated memory.
+
+Number of available frames, frame rate and frames geometry, type of the
+FIASCO file are accessible through member functions
+\fBfiasco_decoder_get_length()\fP,
+\fBfiasco_decoder_get_rate()\fP,  
+\fBfiasco_decoder_get_width()\fP,
+\fBfiasco_decoder_get_height()\fP,
+and \fBfiasco_decoder_is_color()\fP. Use \fBfiasco_decoder_get_title()\fP,
+\fBfiasco_decoder_get_comment()\fP to read title and comment strings of the
+FIASCO file. 
+
+.SH ARGUMENTS
+
+.TP
+fiasco_name
+Filename of the FIASCO input file. If \fIfiasco_name\fP is a NULL pointer
+or "-" then the decoder reads from standard input. If the file is not
+found in the current directory and the environment variable
+\fBFIASCO_DATA\fP is a (colon-separated) list of directories, then the
+input file is searched for in these directories, too.
+
+.TP
+options
+This "class" encapsulates the various coding and decoding
+parameters. Use the functions fiasco_d_options_new(3) and
+fiasco_d_options_delete(3) to create and delete an object of this
+class. Several member functions (see fiasco_d_options(3)) are
+available to change the default values.
+
+.TP
+decoder
+The decoder "class" encapsulates the FIASCO decoder. It is used to
+store the internal state of the decoder.
+
+.SH RETURN VALUES
+The function \fBfiasco_decoder_new()\fP returns a pointer to the newly
+allocated decoder object. If an error has been catched, a NULL pointer
+is returned.
+
+The function \fBfiasco_decoder_write_frame()\fP returns 1 if the file
+has been successfully written. Otherwise, the function returns 0.
+
+The function \fBfiasco_decoder_get_frame()\fP returns a pointer to the
+newly allocated FIASCO image object. If an error has been catched, a NULL
+pointer is returned.
+
+The function \fBfiasco_decoder_get_length()\fP returns the number of
+frames of the FIASCO file. If an error has been catched, 0 is
+returned. 
+
+The function \fBfiasco_decoder_get_rate()\fP returns the
+framerate (number of frames per second) of the FIASCO file. If an
+error has been catched, 0 is returned.
+
+The function \fBfiasco_decoder_get_width()\fP returns the width of the
+decoded frames of the FIASCO file. If an error has been catched, 0 is
+returned.
+
+The function \fBfiasco_decoder_get_height()\fP returns the height of the
+decoded frames of the FIASCO file. If an error has been catched, 0 is
+returned.
+
+The function \fBfiasco_decoder_get_title()\fP returns an optional
+title of the FIASCO file. If an error has been catched, 0 is returned.
+
+The function \fBfiasco_decoder_get_comment()\fP returns an optional
+comment of the FIASCO file. If an error has been catched, 0 is returned.
+
+The function \fBfiasco_decoder_is_color()\fP returns 0 if the decoded
+frames are grayscale images, otherwise a non-zero value is
+returned.
+
+In case of an error in one of the above functions, use the function
+fiasco_get_error_message(3) to get a string describing the last error
+message of FIASCO.
+
+.SH ENVIRONMENT
+.PD 0
+.TP
+.B FIASCO_IMAGES
+Search path for image files. Default is "./".
+.TP
+.B FIASCO_DATA
+Search and save path for FIASCO files. Default is "./".
+.PD 
+
+.SH "SEE ALSO"
+.br
+.BR fiasco_d_options_new "(3), " fiasco_d_options_delete (3), 
+.br
+.BR fiasco_d_options "(3), " fiasco_get_error_message (3)
+.br
+
+Ullrich Hafner, Juergen Albert, Stefan Frank, and Michael Unger.
+\fBWeighted Finite Automata for Video Compression\fP, IEEE Journal on
+Selected Areas In Communications, January 1998
+.br
+Ullrich Hafner. \fBLow Bit-Rate Image and Video Coding with Weighted
+Finite Automata\fP, Ph.D. thesis, Mensch & Buch Verlag, ISBN
+3-89820-002-7, October 1999.
+
+.SH AUTHOR
+Ullrich Hafner <hafner@bigfoot.de>
diff --git a/converter/other/fiasco/doc/fiasco_decoder_write_frame.3 b/converter/other/fiasco/doc/fiasco_decoder_write_frame.3
new file mode 100644
index 00000000..33c0d21b
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_decoder_write_frame.3
@@ -0,0 +1 @@
+.so man3/fiasco_decoder_new.3
diff --git a/converter/other/fiasco/doc/fiasco_get_error_message.3 b/converter/other/fiasco/doc/fiasco_get_error_message.3
new file mode 100644
index 00000000..09d593fb
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_get_error_message.3
@@ -0,0 +1,41 @@
+.\" $Id: fiasco_get_error_message.3,v 1.1 2000/06/14 19:07:02 hafner Exp $
+.TH fiasco 3 "April, 2000" "FIASCO" "Fractal Image And Sequence COdec"
+
+.SH NAME
+.B  fiasco_get_error_message
+\- return string describing last error catched in FIASCO library
+
+.SH SYNOPSIS
+.B #include <fiasco.h>
+.sp
+.BI "const char * "
+.fi
+.BI "fiasco_get_error_message (void);"
+.fi
+
+.SH DESCRIPTION
+The \fBfiasco_get_error_message()\fP function returns a string
+describing the last error that has been catched in the FIASCO library.
+
+.SH RETURN VALUE
+The function \fBfiasco_get_error_message()\fP returns the appropriate
+description string, or an empty string if no error has been catched so
+far.
+
+.SH "SEE ALSO"
+.br
+.BR fiasco_options "(3), " fiasco_coder (3), 
+.br
+.BR fiasco_decoder "(3), " fiasco_renderer (3)
+.br
+
+Ullrich Hafner, Juergen Albert, Stefan Frank, and Michael Unger.
+\fBWeighted Finite Automata for Video Compression\fP, IEEE Journal on
+Selected Areas In Communications, January 1998
+.br
+Ullrich Hafner. \fBLow Bit-Rate Image and Video Coding with Weighted
+Finite Automata\fP, Ph.D. thesis, Mensch & Buch Verlag, ISBN
+3-89820-002-7, October 1999.
+
+.SH AUTHOR
+Ullrich Hafner <hafner@bigfoot.de>
diff --git a/converter/other/fiasco/doc/fiasco_get_verbosity.3 b/converter/other/fiasco/doc/fiasco_get_verbosity.3
new file mode 100644
index 00000000..884cd19e
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_get_verbosity.3
@@ -0,0 +1 @@
+.so man3/fiasco_set_verbosity.3
diff --git a/converter/other/fiasco/doc/fiasco_image.3 b/converter/other/fiasco/doc/fiasco_image.3
new file mode 100644
index 00000000..f8df38d5
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_image.3
@@ -0,0 +1 @@
+.so man3/fiasco_image_new.3
diff --git a/converter/other/fiasco/doc/fiasco_image_delete.3 b/converter/other/fiasco/doc/fiasco_image_delete.3
new file mode 100644
index 00000000..f8df38d5
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_image_delete.3
@@ -0,0 +1 @@
+.so man3/fiasco_image_new.3
diff --git a/converter/other/fiasco/doc/fiasco_image_get_height.3 b/converter/other/fiasco/doc/fiasco_image_get_height.3
new file mode 100644
index 00000000..f8df38d5
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_image_get_height.3
@@ -0,0 +1 @@
+.so man3/fiasco_image_new.3
diff --git a/converter/other/fiasco/doc/fiasco_image_get_width.3 b/converter/other/fiasco/doc/fiasco_image_get_width.3
new file mode 100644
index 00000000..f8df38d5
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_image_get_width.3
@@ -0,0 +1 @@
+.so man3/fiasco_image_new.3
diff --git a/converter/other/fiasco/doc/fiasco_image_is_color.3 b/converter/other/fiasco/doc/fiasco_image_is_color.3
new file mode 100644
index 00000000..f8df38d5
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_image_is_color.3
@@ -0,0 +1 @@
+.so man3/fiasco_image_new.3
diff --git a/converter/other/fiasco/doc/fiasco_image_new.3 b/converter/other/fiasco/doc/fiasco_image_new.3
new file mode 100644
index 00000000..10625b63
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_image_new.3
@@ -0,0 +1,95 @@
+.\" $Id: fiasco_image_new.3,v 1.2 2000/06/14 19:26:06 hafner Exp $
+.TH fiasco 3 "April, 2000" "FIASCO" "Fractal Image And Sequence COdec"
+
+.SH NAME
+.B  fiasco_image_new, fiasco_image_delete, fiasco_image_get_width,
+.B  fiasco_image_get_height,  fiasco_image_is_color
+\- handle FIASCO image objects
+
+.SH SYNOPSIS
+.B #include <fiasco.h>
+.sp
+.BI "fiasco_image_t *"
+.fi
+.BI "fiasco_image_new (const char * "filename );
+.sp
+.BI "void"
+.fi
+.BI "fiasco_image_delete (fiasco_image_t * "image );
+.sp
+.BI "unsigned"
+.fi
+.BI "fiasco_image_get_width (const fiasco_image_t * "image );
+.sp
+.BI "unsigned"
+.fi
+.BI "fiasco_image_get_height (const fiasco_image_t * "image );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_image_is_color (const fiasco_image_t * "image );
+.fi
+
+.SH DESCRIPTION
+The \fBfiasco_image_new()\fP function reads the given image file and
+allocates and initializes a FIASCO image object. Use the function
+fiasco_renderer_new(3) to create a renderer object that converts the
+FIASCO image to the desired image format.
+
+The function \fBfiasco_image_delete()\fP deletes the image object and
+frees the image buffer. 
+
+Image geometry and type are accessible through member functions
+\fBfiasco_image_get_width()\fP,
+\fBfiasco_image_get_height()\fP,
+and \fBfiasco_image_is_color()\fP. 
+
+.SH ARGUMENTS
+
+.TP
+image
+The image "class" encapsulates the FIASCO image object. It is used to
+store the pixel values of the decoded or read image.
+
+.TP
+filename
+Image filename to process. If \fIfilename\fP is "-" or a NULL pointer
+then the image is read from standard input. If a filename is a
+relative path then the images are searched for in the current
+directory and in the (colon-separated) list of directories given by
+the environment variable \fBFIASCO_IMAGES\fP.
+
+.SH RETURN VALUE
+The function \fBfiasco_image_new()\fP returns a pointer to the newly
+allocated image object. If an error has been catched, a NULL pointer
+is returned.
+
+The function \fBfiasco_image_get_width()\fP returns the width of the
+image. If an error has been catched, 0 is returned.
+
+The function \fBfiasco_image_get_height()\fP returns the height of the
+image. If an error has been catched, 0 is returned.
+
+The function \fBfiasco_image_is_color()\fP returns 0 if the image
+object is a grayscale image, otherwise a non-zero value is returned.
+
+In case of an error in one of the above functions, use the function
+fiasco_get_error_message(3) to get a string with the last error
+message of FIASCO.
+
+.SH "SEE ALSO"
+.br
+.BR fiasco_decoder_get_frame "(3), " fiasco_get_error_message (3)
+.BR fiasco_renderer_new (3)
+.br
+
+Ullrich Hafner, Juergen Albert, Stefan Frank, and Michael Unger.
+\fBWeighted Finite Automata for Video Compression\fP, IEEE Journal on
+Selected Areas In Communications, January 1998
+.br
+Ullrich Hafner. \fBLow Bit-Rate Image and Video Coding with Weighted
+Finite Automata\fP, Ph.D. thesis, Mensch & Buch Verlag, ISBN
+3-89820-002-7, October 1999.
+
+.SH AUTHOR
+Ullrich Hafner <hafner@bigfoot.de>
diff --git a/converter/other/fiasco/doc/fiasco_options.3 b/converter/other/fiasco/doc/fiasco_options.3
new file mode 100644
index 00000000..493fcce9
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_options.3
@@ -0,0 +1 @@
+.so man3/fiasco_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_options_delete.3 b/converter/other/fiasco/doc/fiasco_options_delete.3
new file mode 100644
index 00000000..493fcce9
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_options_delete.3
@@ -0,0 +1 @@
+.so man3/fiasco_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_options_new.3 b/converter/other/fiasco/doc/fiasco_options_new.3
new file mode 100644
index 00000000..26e070ca
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_options_new.3
@@ -0,0 +1,441 @@
+.\" $Id: fiasco_options_new.3,v 1.2 2000/06/25 16:38:06 hafner Exp $
+.TH fiasco 3 "April, 2000" "FIASCO" "Fractal Image And Sequence COdec"
+
+.SH NAME
+.B  fiasco_options_new, fiasco_options_set_magnification,
+.B fiasco_options_delete, fiasco_options_set_progress_meter,
+.B fiasco_options_set_smoothing, fiasco_options_set_tiling, 
+.B fiasco_options_set_4_2_0_format, fiasco_options_set_basisfile,
+.B fiasco_options_set_chroma_quality, fiasco_options_set_optimizations,
+.B fiasco_options_set_prediction, fiasco_options_set_video_param,
+.B fiasco_options_set_quantization, fiasco_options_set_frame_pattern
+\- define additional options of FIASCO coder and decoder 
+
+.SH SYNOPSIS
+.B #include <fiasco.h>
+.sp
+.BI "fiasco_options_t *"
+.fi
+.BI "fiasco_options_new"
+.fi
+.BI "   (void);"
+.sp
+.BI "void"
+.fi
+.BI "fiasco_options_delete"
+.fi
+.BI "   (fiasco_options_t * "options );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_options_set_4_2_0_format"
+.fi
+.BI "   (fiasco_options_t * "options ,
+.fi
+.BI "    int "format );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_options_set_basisfile"
+.fi
+.BI "   (fiasco_options_t * "options ,
+.fi
+.BI "    const char * "filename );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_options_set_chroma_quality"
+.fi
+.BI "   (fiasco_options_t * "options ,
+.fi
+.BI "    float "quality_factor ,
+.fi
+.BI "    unsigned "dictionary_size );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_options_set_frame_pattern"
+.fi
+.BI "   (fiasco_options_t * "options ,
+.fi
+.BI "    const char * "pattern );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_options_set_magnification"
+.fi
+.BI "   (fiasco_options_t * "options ,
+.fi
+.BI "    int "level );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_options_set_optimizations"
+.fi
+.BI "   (fiasco_options_t * "options ,
+.fi
+.BI "    unsigned "min_block_level ,
+.fi
+.BI "    unsigned "max_block_level ,
+.fi
+.BI "    unsigned "max_elements ,
+.fi
+.BI "    unsigned "dictionary_size ,
+.fi
+.BI "    unsigned "optimization_level );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_options_set_quantization"
+.fi
+.BI "   (fiasco_options_t * "options ,
+.fi
+.BI "    unsigned "mantissa ,
+.fi
+.BI "    fiasco_rpf_range_e "range ,
+.fi
+.BI "    unsigned "dc_mantissa ,
+.fi
+.BI "    fiasco_rpf_range_e "dc_range );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_options_set_prediction"
+.fi
+.BI "   (fiasco_options_t * "options ,
+.fi
+.BI "    int "intra_prediction ,
+.fi
+.BI "    unsigned "min_block_level ,
+.fi
+.BI "    unsigned "max_block_level );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_options_set_progress_meter"
+.fi
+.BI "   (fiasco_options_t * "options ,
+.fi
+.BI "    fiasco_progress_e "type );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_options_set_smoothing"
+.fi
+.BI "   (fiasco_options_t * "options ,
+.fi
+.BI "    unsigned "smoothing );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_options_set_tiling"
+.fi
+.BI "   (fiasco_options_t * "options ,
+.fi
+.BI "    fiasco_tiling_e "method ,
+.fi
+.BI "    unsigned "exponent );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_options_set_video_param"
+.fi
+.BI "   (fiasco_options_t * "options ,
+.fi
+.BI "    unsigned "frames_per_second ,
+.fi
+.BI "    int "half_pixel_prediction ,
+.fi
+.BI "    int "cross_B_search ,
+.fi
+.BI "    int "B_as_past_ref );
+.fi
+
+.SH DESCRIPTION
+The \fBfiasco_options_new()\fP function allocates and initializes a
+FIASCO options object which is used to control additional compression and
+decompression parameters.
+
+Conversely, the function \fBfiasco_options_delete()\fP discards the
+given FIASCO options object.
+
+Several member functions are available to modify the default behavior
+of the FIASCO coder and decoder. 
+
+\fBfiasco_options_set_smoothing()\fP sets the
+\fIsmoothing\fP-percentage along partitioning borders when the image
+is regenerated; default is 70. This option is used both by the decoder
+and encoder. You should use the \fIsmoothing\fP value specified in the
+FIASCO file when you are decoding video frames.
+
+\fBfiasco_options_set_magnification()\fP sets the \fImagnification\fP
+of the regenerated image; default is 0, i.e., the image geometry is
+not changed. This option is used by the decoder only.
+
+\fBfiasco_options_set_4_2_0_format()\fP defines whether the decoder
+should use the default 4:4:4 format or the 4:2:0 format. The latter
+one significantly reduces the decoding time at the cost of some
+additional blocking artefacts. This option is used by the decoder only.
+
+\fBfiasco_options_set_frame_pattern()\fP sets the type of inter frame
+compression which should be applied to individual frames of a video
+stream; default is "IPPPPPPPPP". 
+
+\fBfiasco_options_set_tiling()\fP sets \fImethod\fP and \fIexponent\fP
+of the image tiling algorithm which runs as initial step of the
+encoder; by default the image is subdivided into 16 tiles which
+are sorted by decreasing variance. 
+
+\fBfiasco_options_set_basisfile()\fP sets the \fIfilename\fP of
+the FIASCO initial basis (codebook of dictionary vectors); default is
+"small.fco". 
+
+\fBfiasco_options_set_chroma_quality()\fP sets the quality used when
+coding the chroma channels of a color image to the term "\fIquality\fP
+of luminance / \fIquality_factor\fP"; default is 2. Moreover, the size
+of the codebook is limited by \fIdictionary_size\fP; default is 40
+elements. 
+
+\fBfiasco_options_set_optimizations()\fP toggles various coding
+optimizations. E.g., the size of the dictionary (default is 10000),
+the subset of dictionary elements to use for an individual
+approximation (default is 5), the size of the image blocks to consider
+(4x4, ..., 64x64), and some additional low level
+optimizations (default level is 1). 
+
+\fBfiasco_options_set_prediction()\fP enables an additional intra
+block prediction by using a DC component approximation. By giving
+levels \fImin_block_level\fP and \fImax_block_level\fP the prediction
+can be limited to a small range of blocks only. By default, this
+method is disabled. 
+
+\fBfiasco_options_set_video_param()\fP defines the framerate (default
+is 25) and toggles whether to use half pixel precise motion
+compensated prediction (disabled by default), whether to determine
+motion vectors of interpolated prediction with the Cross-B-Search
+algorithm (disabled by default), and whether to allow B frames to be
+used for B frame predicion (disabled by default).
+
+\fBfiasco_options_set_quantization()\fP defines the quantization
+parameters of the approximation coefficients. By default the range of
+DC coefficients is [-1,+1] using a mantissa of 5 bits (and one sign
+bit). By default, all other coefficients are quantized with 3 mantissa
+bits in the interval [-1.5,+1.5].
+
+\fBfiasco_options_set_progress_meter()\fP sets the type of progress
+meter to be used during coding. By default, an RPM style progress bar
+using 50 hash marks (####) is used.
+
+.SH ARGUMENTS
+.TP
+options
+This object encapsulates the various coding and decoding parameters.  
+
+.TP
+smoothing
+This percentage (range is 0 - i.e., no smoothing - to 100) defines how
+much the regenerated image is smoothed along the partitioning borders.
+
+.TP
+level
+This value gives the magnification of the decoded image with respect
+to the original size. Positive values increase and negative values
+decrease the width and height of the image by a factor of
+2^abs(\fIlevel\fP).
+
+.TP
+format
+If \fIformat\fP is 0 then the 4:4:4 color image format is used, i.e.,
+the chroma channel are of the same size as the luminance. Otherwise,
+the 4:2:0 format is used. Then, width and height of each chroma
+channel is only one half of the width and height of the luminance.
+
+.TP
+method
+Defines the algorithm which should be used to sort the image tiles
+which are generated in the initial coding step. If \fImethod\fP is
+\fBFIASCO_VARIANCE_ASC\fP then the tiles are sorted by variance - the
+first tile has the lowest variance. Conversely, when using
+\fBFIASCO_VARIANCE_DSC\fP the first tile has the largest variance. If
+\fImethod\fP is \fBFIASCO_SPIRAL_ASC\fP then the tiles are sorted like
+a spiral starting in the middle of the image. Conversely, when using
+\fBFIASCO_SPIRAL_DSC\fP the tiles are sorted like a spiral starting in
+the upper left corner.
+
+.TP
+exponent
+This value sets the number of image tiles - which are generated in the
+initial step of the encoder - to 2^\fIexponent\fP.
+
+.TP
+pattern
+This string defines the sequence of frame types. Character \fIn\fP of
+the string defines the type of frame \fIn\fP (\fIpattern\fP is
+periodically extended). Three different frame types are available
+(case insensitive): choose 'i' for intra-frames (no inter frame
+prediction is used), 'p' for predicted frames (a frame of the
+past is used for prediction), or 'b' for bi-directional predicted
+frames (both a frame of the past and the future is used for
+prediction).
+
+.TP
+filename
+The initial basis (codebook) of the coder is loaded from this
+(ASCII) file. Files that already come with FIASCO are "small.fco" (3 elements),
+"medium.fco" (132 elements), and "large.fco" (219 elements). 
+
+.TP
+quality_factor
+When coding chroma channels (Cb and Cr band) the approximation quality
+is determined by the term `quality of Y component' / \fIquality_factor\fP.
+
+.TP
+dictionary_size
+FIASCO uses a dictionary (codebook) of variable size to approximate
+individual image blocks. The size of the codebook can be limited by
+\fIdictionary_size\fP to reduce the coding time, however, at the cost
+of decreasing quality. 
+
+.TP
+min_block_level
+During coding only those image blocks are considered for approximation
+(or prediction) which binary tree level is larger than
+\fImin_block_level\fP (minimum value is 3). (Since FIASCO internally
+works with binary trees, the size of an image block is determined by
+the \fIlevel\fP of the corresponding binary tree). Refer to following
+table to convert these values:
+
+.ce
+level | width | height
+.fi
+------+-------+--------
+.fi
+  0   |    1  |    1  
+.fi
+  1   |    1  |    2  
+.fi
+  2   |    2  |    2  
+.fi
+  3   |    2  |    4  
+.fi
+  4   |    4  |    4  
+.fi
+  5   |    4  |    8  
+.fi
+  6   |    8  |    8  
+.fi
+  7   |    8  |   16
+.fi
+------+-------+--------
+.fi
+The larger this value is the faster the coder runs but the worse the
+image quality will be.
+
+.TP
+max_block_level
+During coding only those image blocks are considered for approximation
+(or prediction) which binary tree level is smaller than
+\fImax_block_level\fP. The smaller this value is the faster the coder
+runs but the worse the image quality will be.
+
+.TP
+max_elements
+This value defines how many dictionary elements can be
+used to approximate an individual image block. The smaller this positive
+value (range is 1 to 5) is the faster the coder runs but the worse the
+image quality will be. 
+
+.TP
+optimization_level
+Additional low level optimizations are available by setting
+\fIoptimization_level\fP to one of the following values:
+.fi
+0 standard approximation method
+.fi
+1 slightly increases the approximation quality, running time is
+twice as high as with the standard method 
+.fi
+2 hardly increases the approximation quality of method 1, running time
+is twice as high as with method 1 (this method just remains for
+completeness) 
+.fi
+
+.TP
+intra_prediction
+If \fIintra_prediction\fP is set to a non-zero value then an
+additional block prediction of intra-frames is enabled. For some
+images, the image quality is slightly improved, however, at the cost of
+a significantly increased running time of the coder. 
+
+.TP
+frames_per_second
+This value defines the frame rate, i.e., how many frames per second
+should be displayed. This value has no effect during coding, it is just
+passed to the FIASCO output file where it is read and used by the
+decoder.
+
+.TP
+half_pixel_prediction
+A non-zero value enables half pixel precise motion compensated
+prediction. 
+
+.TP
+cross_B_search
+A non-zero value enables the fast Cross-B-Search algorithm to determine
+the motion vectors of an interpolated prediction. Otherwise,
+exhaustive search (in the given search range) is used. 
+
+.TP
+B_as_past_ref
+A non-zero value allows not only I- and P-frames but also B-frames to be
+used for a forward or bi-directional predicion.
+
+.TP
+mantissa, range
+Approximation coefficients are quantized to a small number of
+values (in fixed point format) in the interval [-\fIrange\fP,
++\fIrange\fP]. The number of \fImantissa\fP bits defines the accuracy of
+quantization.
+
+.TP
+dc_mantissa, dc_range
+Approximation coefficients of the DC component are quantized in a
+different way: the number of mantissa bits is given by
+\fIdc_mantissa\fP whereas the quantization interval is given by
+[-\fIdc_range\fP, +\fBdc_range\fP].
+
+.TP
+type
+This value sets the \fItype\fP of progress meter which should be used
+during coding. The following types are available:
+.fi
+\fBFIASCO_PROGRESS_NONE\fP:  no output at all
+.fi
+\fBFIASCO_PROGRESS_BAR\fP: print hash marks (###)
+\fBFIASCO_PROGRESS_PERCENT\fP: percentage meter (50%)
+
+.SH RETURN VALUES
+The function \fBfiasco_decoder_new()\fP returns a pointer to the newly
+allocated option object. If an error has been catched, a NULL pointer
+is returned.
+
+All set functions return 1 on success and 0 if an error has been
+catched.  
+
+In case of an error, use the function fiasco_get_error_message(3) to
+get a string with the last error message of FIASCO.
+
+.SH "SEE ALSO"
+.br
+.BR fiasco_decoder "(3), " fiasco_coder (3)
+
+Ullrich Hafner, Juergen Albert, Stefan Frank, and Michael Unger.
+\fBWeighted Finite Automata for Video Compression\fP, IEEE Journal on
+Selected Areas In Communications, January 1998
+.br
+Ullrich Hafner. \fBLow Bit-Rate Image and Video Coding with Weighted
+Finite Automata\fP, Ph.D. thesis, Mensch & Buch Verlag, ISBN
+3-89820-002-7, October 1999.
+
+.SH AUTHOR
+Ullrich Hafner <hafner@bigfoot.de>
diff --git a/converter/other/fiasco/doc/fiasco_options_set_4_2_0_format.3 b/converter/other/fiasco/doc/fiasco_options_set_4_2_0_format.3
new file mode 100644
index 00000000..493fcce9
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_options_set_4_2_0_format.3
@@ -0,0 +1 @@
+.so man3/fiasco_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_options_set_basisfile.3 b/converter/other/fiasco/doc/fiasco_options_set_basisfile.3
new file mode 100644
index 00000000..493fcce9
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_options_set_basisfile.3
@@ -0,0 +1 @@
+.so man3/fiasco_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_options_set_chroma_quality.3 b/converter/other/fiasco/doc/fiasco_options_set_chroma_quality.3
new file mode 100644
index 00000000..493fcce9
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_options_set_chroma_quality.3
@@ -0,0 +1 @@
+.so man3/fiasco_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_options_set_frame_pattern.3 b/converter/other/fiasco/doc/fiasco_options_set_frame_pattern.3
new file mode 100644
index 00000000..493fcce9
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_options_set_frame_pattern.3
@@ -0,0 +1 @@
+.so man3/fiasco_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_options_set_magnification.3 b/converter/other/fiasco/doc/fiasco_options_set_magnification.3
new file mode 100644
index 00000000..493fcce9
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_options_set_magnification.3
@@ -0,0 +1 @@
+.so man3/fiasco_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_options_set_optimizations.3 b/converter/other/fiasco/doc/fiasco_options_set_optimizations.3
new file mode 100644
index 00000000..493fcce9
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_options_set_optimizations.3
@@ -0,0 +1 @@
+.so man3/fiasco_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_options_set_prediction.3 b/converter/other/fiasco/doc/fiasco_options_set_prediction.3
new file mode 100644
index 00000000..493fcce9
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_options_set_prediction.3
@@ -0,0 +1 @@
+.so man3/fiasco_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_options_set_progress_meter.3 b/converter/other/fiasco/doc/fiasco_options_set_progress_meter.3
new file mode 100644
index 00000000..493fcce9
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_options_set_progress_meter.3
@@ -0,0 +1 @@
+.so man3/fiasco_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_options_set_quantization.3 b/converter/other/fiasco/doc/fiasco_options_set_quantization.3
new file mode 100644
index 00000000..493fcce9
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_options_set_quantization.3
@@ -0,0 +1 @@
+.so man3/fiasco_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_options_set_smoothing.3 b/converter/other/fiasco/doc/fiasco_options_set_smoothing.3
new file mode 100644
index 00000000..493fcce9
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_options_set_smoothing.3
@@ -0,0 +1 @@
+.so man3/fiasco_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_options_set_tiling.3 b/converter/other/fiasco/doc/fiasco_options_set_tiling.3
new file mode 100644
index 00000000..493fcce9
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_options_set_tiling.3
@@ -0,0 +1 @@
+.so man3/fiasco_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_options_set_video_param.3 b/converter/other/fiasco/doc/fiasco_options_set_video_param.3
new file mode 100644
index 00000000..493fcce9
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_options_set_video_param.3
@@ -0,0 +1 @@
+.so man3/fiasco_options_new.3
diff --git a/converter/other/fiasco/doc/fiasco_renderer.3 b/converter/other/fiasco/doc/fiasco_renderer.3
new file mode 100644
index 00000000..0aec996e
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_renderer.3
@@ -0,0 +1 @@
+.so man3/fiasco_renderer_new.3
diff --git a/converter/other/fiasco/doc/fiasco_renderer_delete.3 b/converter/other/fiasco/doc/fiasco_renderer_delete.3
new file mode 100644
index 00000000..0aec996e
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_renderer_delete.3
@@ -0,0 +1 @@
+.so man3/fiasco_renderer_new.3
diff --git a/converter/other/fiasco/doc/fiasco_renderer_new.3 b/converter/other/fiasco/doc/fiasco_renderer_new.3
new file mode 100644
index 00000000..b24d8462
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_renderer_new.3
@@ -0,0 +1,125 @@
+.\" $Id: fiasco_renderer_new.3,v 1.2 2000/06/14 18:58:35 hafner Exp $
+.TH fiasco 3 "April, 2000" "FIASCO" "Fractal Image And Sequence COdec"
+
+.SH NAME
+.B  fiasco_renderer_new, fiasco_renderer_delete
+\- convert a FIASCO image object to an X11 XImage
+
+.SH SYNOPSIS
+.B #include <fiasco.h>
+.sp
+.BI "fiasco_renderer_t *"
+.fi
+.BI "fiasco_renderer_new (unsigned long "red_mask ,
+.fi
+.BI "                     unsigned long "green_mask ,
+.fi
+.BI "                     unsigned long "blue_mask ,
+.fi
+.BI "                     unsigned "bpp ,
+.fi
+.BI "                     int "double_resolution );
+.sp
+.BI "void"
+.fi
+.BI "fiasco_renderer_delete (fiasco_renderer_t * "renderer );
+.sp
+.BI "int"
+.fi
+.BI "fiasco_renderer_render (const fiasco_renderer_t * "renderer ,
+.fi
+.BI "                        unsigned char * "data );
+.fi
+.BI "                        const fiasco_image_t * "fiasco_image );
+.fi
+
+.SH DESCRIPTION
+The \fBfiasco_renderer_new()\fP function allocates and initializes a
+renderer object which has to be used to convert an internal FIASCO
+image object to one of the supported X11 formats. Currently, the FIASCO
+image can be rendered to an X11 XImage of either 16, 24, or 32 bits
+per pixel. Additional formats will be supported upon request.  
+
+Function \fBfiasco_renderer_render()\fP is used to convert the given
+FIASCO image object to the specified format. 
+
+After all frames are rendered, the function
+\fBfiasco_renderer_delete()\fP should be called to free temporarily
+allocated memory and to discard the renderer object.
+
+Note that the FIASCO renderer class is not restricted to X11 images: a
+FIASCO image object can be converted to an image data array of the
+form RGBRGB... by setting \fIred_mask\fP=0xff0000,
+\fIgreen_mask\fP=0xff00, \fIblue_mask\fP=0xff, and \fIbpp\fP=24.
+
+.SH ARGUMENTS
+
+.TP
+bpp
+Determines the number of bits of a single pixel of the X11 XImage
+structure (see XCreateImage(3)). If the XImage is already allocated
+then the value XImage->bits_per_pixel should be used. Currently, 16,
+24, and 32 bits per pixel are supported.
+
+.TP
+red_mask
+Determines which bits of a pixel should be used for the red
+component. If the XImage is already allocated then the value
+XImage->red_mask should be used. E.g., if \fIbpp=16\fP and
+\fIred_mask=0xf800\fP then each pixel is stored with two bytes. The
+red component uses bits 11-15, the remaining green and blue components
+use bits 0-10.
+
+.TP
+green_mask
+Determines which bits of a pixel should be used for the green
+component. If the XImage is already allocated then the value
+XImage->green_mask should be used.
+
+.TP
+blue_mask
+Determines which bits of a pixel should be used for the blue
+component. If the XImage is already allocated then the value
+XImage->blue_mask should be used.
+
+.TP
+data
+A pointer to the image data. If the XImage is already allocated then
+the value XImage->data should be used. This array has to be large
+enough to hold the decoded image at the given size (geometry and bits
+per pixel).
+
+.TP
+fiasco_image
+This object represents the decoded image which has been
+created by the FIASCO functions fiasco_decoder_get_frame(3) or
+fiasco_image_new(3).
+
+.SH RETURN VALUE
+The function \fBfiasco_renderer_new()\fP returns a pointer to the newly
+allocated renderer object. If an error has been catched, a NULL pointer
+is returned.
+
+The function \fBfiasco_renderer_render()\fP returns 1 if the image
+has been successfully converted. Otherwise, the function returns 0.
+
+In case of an error in one of the above functions, use the function
+fiasco_get_error_message(3) to get a string with the last error
+message of FIASCO.
+
+.SH "SEE ALSO"
+.br
+.BR fiasco_decoder_get_frame "(3), " fiasco_get_error_message (3)
+.BR fiasco_image_new (3)
+.br
+
+Ullrich Hafner, Juergen Albert, Stefan Frank, and Michael Unger.
+\fBWeighted Finite Automata for Video Compression\fP, IEEE Journal on
+Selected Areas In Communications, January 1998
+.br
+Ullrich Hafner. \fBLow Bit-Rate Image and Video Coding with Weighted
+Finite Automata\fP, Ph.D. thesis, Mensch & Buch Verlag, ISBN
+3-89820-002-7, October 1999.
+
+.SH AUTHOR
+Ullrich Hafner <hafner@bigfoot.de>
diff --git a/converter/other/fiasco/doc/fiasco_renderer_render.3 b/converter/other/fiasco/doc/fiasco_renderer_render.3
new file mode 100644
index 00000000..0aec996e
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_renderer_render.3
@@ -0,0 +1 @@
+.so man3/fiasco_renderer_new.3
diff --git a/converter/other/fiasco/doc/fiasco_set_verbosity.3 b/converter/other/fiasco/doc/fiasco_set_verbosity.3
new file mode 100644
index 00000000..746854b1
--- /dev/null
+++ b/converter/other/fiasco/doc/fiasco_set_verbosity.3
@@ -0,0 +1,46 @@
+.\" $Id: fiasco_set_verbosity.3,v 1.1 2000/06/06 20:55:05 hafner Exp $
+.TH fiasco 3 "April, 2000" "FIASCO" "Fractal Image And Sequence COdec"
+
+.SH NAME
+.B  fiasco_get_verbosity, fiasco_set_verbosity
+\- get or set verbosity of FIASCO library
+
+.SH SYNOPSIS
+.B #include <fiasco.h>
+.sp
+.BI "fiasco_verbosity_e"
+.fi
+.BI "fiasco_get_verbosity (void);"
+.sp
+.BI "void"
+.fi
+.BI "fiasco_set_verbosity (fiasco_verbosity_e "level );
+.fi
+
+.SH DESCRIPTION
+The \fBfiasco_get_verbosity()\fP function returns the current
+verbosity level of the FIASCO library. Conversely, the function
+\fBfiasco_set_verbosity()\fP sets the verbosity of the FIASCO library
+to the given \fIlevel\fP.
+
+.SH RETURN VALUE
+The function \fBfiasco_get_verbosity()\fP returns the current
+verbosity level. Level either is \fBFIASCO_NO_VERBOSITY\fP (no output at
+all), \fBFIASCO_SOME_VERBOSITY\fP (show progress meter) or
+\fBFIASCO_ULTIMATE_VERBOSITY\fP (show debugging output).
+
+.SH "SEE ALSO"
+.br
+.BR fiasco_coder (3), fiasco_decoder (3)
+.br
+
+Ullrich Hafner, Juergen Albert, Stefan Frank, and Michael Unger.
+\fBWeighted Finite Automata for Video Compression\fP, IEEE Journal on
+Selected Areas In Communications, January 1998
+.br
+Ullrich Hafner. \fBLow Bit-Rate Image and Video Coding with Weighted
+Finite Automata\fP, Ph.D. thesis, Mensch & Buch Verlag, ISBN
+3-89820-002-7, October 1999.
+
+.SH AUTHOR
+Ullrich Hafner <hafner@bigfoot.de>
diff --git a/converter/other/fiasco/fiasco.h b/converter/other/fiasco/fiasco.h
new file mode 100644
index 00000000..235b1279
--- /dev/null
+++ b/converter/other/fiasco/fiasco.h
@@ -0,0 +1,425 @@
+/*
+ *  fiasco.h		
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ */
+
+/*
+ *  $Date: 2000/10/28 17:39:28 $
+ *  $Author: hafner $
+ *  $Revision: 5.6 $
+ *  $State: Exp $
+ */
+
+#undef __BEGIN_DECLS
+#undef __END_DECLS
+#ifdef __cplusplus
+# define __BEGIN_DECLS extern "C" {
+# define __END_DECLS }
+#else
+# define __BEGIN_DECLS /* empty */
+# define __END_DECLS /* empty */
+#endif
+
+#ifndef _FIASCO_H
+#define _FIASCO_H 1
+
+__BEGIN_DECLS
+
+/****************************************************************************
+			  FIASCO data types
+****************************************************************************/
+
+/*
+ *  Verbosity level:
+ *  FIASCO_NO_VERBOSITY:       No output at all.
+ *  FIASCO_SOME_VERBOSITY:     Show progress meter during coding
+ *  FIASCO_ULTIMATE_VERBOSITY: Show debugging output
+ */
+typedef enum {FIASCO_NO_VERBOSITY,
+	      FIASCO_SOME_VERBOSITY,
+	      FIASCO_ULTIMATE_VERBOSITY} fiasco_verbosity_e;
+  
+/*
+ *  Image tiling methods:
+ *  VARIANCE_ASC:  Tiles are sorted by variance.
+ *                 The first tile has the lowest variance.
+ *  VARIANCE_DSC:  Tiles are sorted by variance.
+ *                 The first tile has the largest variance.
+ *  SPIRAL_ASC:    Tiles are sorted like a spiral starting
+ *                 in the middle of the image.
+ *  SPIRAL_DSC:    Tiles are sorted like a spiral starting
+ *                 in the upper left corner.
+ */
+typedef enum {FIASCO_TILING_SPIRAL_ASC,
+	      FIASCO_TILING_SPIRAL_DSC,
+	      FIASCO_TILING_VARIANCE_ASC,
+	      FIASCO_TILING_VARIANCE_DSC} fiasco_tiling_e;
+
+/*
+ *  Range of reduced precision format:
+ *  FIASCO_RPF_RANGE_0_75: use interval [-0.75,0.75]
+ *  FIASCO_RPF_RANGE_1_00: use interval [-1.00,1.00]
+ *  FIASCO_RPF_RANGE_1_50: use interval [-1.50,0.75]
+ *  FIASCO_RPF_RANGE_2_00: use interval [-2.00,2.00]
+ */
+typedef enum {FIASCO_RPF_RANGE_0_75,
+	      FIASCO_RPF_RANGE_1_00,
+	      FIASCO_RPF_RANGE_1_50,
+	      FIASCO_RPF_RANGE_2_00} fiasco_rpf_range_e;
+
+/*
+ *  Type of progress meter to be used during coding
+ *  FIASCO_PROGRESS_NONE:    no output at all
+ *  FIASCO_PROGRESS_BAR:     RPM style progress bar using 50 hash marks ###### 
+ *  FIASCO_PROGRESS_PERCENT: percentage meter 50% 
+ */
+typedef enum {FIASCO_PROGRESS_NONE,
+	      FIASCO_PROGRESS_BAR,
+	      FIASCO_PROGRESS_PERCENT} fiasco_progress_e;
+
+/*
+ * Class to encapsulate FIASCO images.
+ */
+typedef struct fiasco_image
+{
+   void		(*delete)	(struct fiasco_image *image);
+   unsigned	(*get_width)	(struct fiasco_image *image);
+   unsigned	(*get_height)	(struct fiasco_image *image);
+   int		(*is_color)	(struct fiasco_image *image);
+   void *private;
+} fiasco_image_t;
+
+/*
+ * Class to store internal state of decoder.
+ */
+typedef struct fiasco_decoder
+{
+   int			(*delete)	 (struct fiasco_decoder *decoder);
+   int			(*write_frame)   (struct fiasco_decoder *decoder,
+					  const char *filename);
+   fiasco_image_t *	(*get_frame)     (struct fiasco_decoder *decoder);
+   unsigned		(*get_length)    (struct fiasco_decoder *decoder);
+   unsigned		(*get_rate)	 (struct fiasco_decoder *decoder);
+   unsigned		(*get_width)	 (struct fiasco_decoder *decoder);
+   unsigned		(*get_height)	 (struct fiasco_decoder *decoder);
+   const char *		(*get_title)	 (struct fiasco_decoder *decoder);
+   const char *		(*get_comment)	 (struct fiasco_decoder *decoder);
+   int			(*is_color)	 (struct fiasco_decoder *decoder);
+   void *private;
+} fiasco_decoder_t;
+
+/*
+ * Class to encapsulate advanced coder options.
+ */
+typedef struct fiasco_c_options
+{
+   void (*delete)            (struct fiasco_c_options *options);
+   int (*set_tiling)         (struct fiasco_c_options *options,
+			      fiasco_tiling_e method,
+			      unsigned exponent);
+   int (*set_frame_pattern)  (struct fiasco_c_options *options,
+			      const char *pattern);
+   int (*set_basisfile)      (struct fiasco_c_options *options,
+			      const char *filename);
+   int (*set_chroma_quality) (struct fiasco_c_options *options,
+			      float quality_factor,
+			      unsigned dictionary_size);
+   int (*set_optimizations)  (struct fiasco_c_options *options,
+			      unsigned min_block_level,
+			      unsigned max_block_level,
+			      unsigned max_elements,
+			      unsigned dictionary_size,
+			      unsigned optimization_level);
+   int (*set_prediction)     (struct fiasco_c_options *options,
+			      int intra_prediction,
+			      unsigned min_block_level,
+			      unsigned max_block_level);
+   int (*set_video_param)    (struct fiasco_c_options *options,
+			      unsigned frames_per_second,
+			      int half_pixel_prediction,
+			      int cross_B_search,
+			      int B_as_past_ref);
+   int (*set_quantization)   (struct fiasco_c_options *options,
+			      unsigned mantissa,
+			      fiasco_rpf_range_e range,
+			      unsigned dc_mantissa,
+			      fiasco_rpf_range_e dc_range);
+   int (*set_progress_meter) (struct fiasco_c_options *options,
+			      fiasco_progress_e type);
+   int (*set_smoothing)      (struct fiasco_c_options *options,
+			      int smoothing);
+   int (*set_comment)        (struct fiasco_c_options *options,
+			      const char *comment);
+   int (*set_title)          (struct fiasco_c_options *options,
+			      const char *title);
+   void *private;
+} fiasco_c_options_t;
+
+/*
+ * Class to encapsulate advanced decoder options.
+ */
+typedef struct fiasco_d_options
+{
+   void (*delete)            (struct fiasco_d_options *options);
+   int (*set_smoothing)      (struct fiasco_d_options *options,
+			      int smoothing);
+   int (*set_magnification)  (struct fiasco_d_options *options,
+			      int level);
+   int (*set_4_2_0_format)   (struct fiasco_d_options *options,
+			      int format);
+   void *private;
+} fiasco_d_options_t;
+
+/*
+ * Class to convert internal FIASCO image structure to a XImage structure.
+ * Method `renderer()' is used to convert internal image to XImage. 
+ * Method `delete()' is used to delete and free internal image. 
+ */
+typedef struct fiasco_renderer
+{
+   int  (*render) (const struct fiasco_renderer *this,
+		   unsigned char *data,
+		   const fiasco_image_t *fiasco_image);
+   void (*delete) (struct fiasco_renderer *this);
+   void *private;
+} fiasco_renderer_t;
+
+/****************************************************************************
+		       miscellaneous functions
+****************************************************************************/
+  
+/* Get last error message of FIASCO library */
+const char *fiasco_get_error_message (void);
+
+/* Set verbosity of FIASCO library */
+void fiasco_set_verbosity (fiasco_verbosity_e level);
+
+/* Get verbosity of FIASCO library */
+fiasco_verbosity_e fiasco_get_verbosity (void);
+
+/****************************************************************************
+			  decoder functions
+****************************************************************************/
+
+/* Decode FIASCO image or sequence */
+fiasco_decoder_t *fiasco_decoder_new (const char *filename,
+				      const fiasco_d_options_t *options);
+
+/* Flush and discard FIASCO decoder */
+int fiasco_decoder_delete (fiasco_decoder_t *decoder);
+
+/* Decode next FIASCO frame and write to PNM image 'filename' */
+int fiasco_decoder_write_frame (fiasco_decoder_t *decoder,
+				const char *filename);
+
+/* Decode next FIASCO frame to FIASCO image structure */
+fiasco_image_t *fiasco_decoder_get_frame (fiasco_decoder_t *decoder);
+
+/* Get width of FIASCO image or sequence */
+unsigned fiasco_decoder_get_width (fiasco_decoder_t *decoder);
+
+/* Get height of FIASCO image or sequence */
+unsigned fiasco_decoder_get_height (fiasco_decoder_t *decoder);
+
+/* Get width of FIASCO image or sequence */
+int fiasco_decoder_is_color (fiasco_decoder_t *decoder);
+
+/* Get frame rate of FIASCO sequence */
+unsigned fiasco_decoder_get_rate (fiasco_decoder_t *decoder);
+
+/* Get number of frames of FIASCO file */
+unsigned fiasco_decoder_get_length (fiasco_decoder_t *decoder);
+
+/* Get title of FIASCO file */
+const char *
+fiasco_decoder_get_title (fiasco_decoder_t *decoder);
+
+/* Get comment of FIASCO file */
+const char *
+fiasco_decoder_get_comment (fiasco_decoder_t *decoder);
+
+/****************************************************************************
+			  image functions
+****************************************************************************/
+
+/* Read FIASCO image (raw ppm or pgm format) */
+fiasco_image_t * fiasco_image_new (const char *filename);
+
+/* Discard FIASCO image */
+void fiasco_image_delete (fiasco_image_t *image); 
+
+/* Get width of FIASCO image or sequence */
+unsigned fiasco_image_get_width (fiasco_image_t *image);
+
+/* Get height of FIASCO image or sequence */
+unsigned fiasco_image_get_height (fiasco_image_t *image);
+
+/* Get width of FIASCO image or sequence */
+int fiasco_image_is_color (fiasco_image_t *image);
+
+/****************************************************************************
+			  renderer functions
+****************************************************************************/
+
+/* Constructor of FIASCO image structure to a XImage renderer */
+fiasco_renderer_t *
+fiasco_renderer_new (unsigned long red_mask, unsigned long green_mask,
+		     unsigned long blue_mask, unsigned bpp,
+		     int double_resolution);
+
+/* Destructor of FIASCO image structure to a XImage renderer */
+void
+fiasco_renderer_delete (fiasco_renderer_t *renderer);
+
+/* FIASCO image structure to a XImage renderer */
+int
+fiasco_renderer_render (const fiasco_renderer_t *renderer,
+			unsigned char *ximage,
+			const fiasco_image_t *fiasco_image);
+
+/****************************************************************************
+			   coder functions
+****************************************************************************/
+
+/* Encode image or sequence by FIASCO */
+int fiasco_coder (char const * const *inputname,
+		  const char *outputname,
+		  float quality,
+		  const fiasco_c_options_t *options);
+
+/****************************************************************************
+		 coder options functions
+****************************************************************************/
+
+/* FIASCO additional options constructor */
+fiasco_c_options_t *fiasco_c_options_new (void);
+
+/* FIASCO additional options destructor */
+void fiasco_c_options_delete (fiasco_c_options_t *options);
+
+/* Define `smoothing'-percentage along partitioning borders.*/
+int fiasco_c_options_set_smoothing (fiasco_c_options_t *options,
+				    int smoothing);
+
+/* Set type of frame prediction for sequence of frames */
+int fiasco_c_options_set_frame_pattern (fiasco_c_options_t *options,
+					const char *pattern);
+
+/* Set method and number of tiles for image tiling */
+int fiasco_c_options_set_tiling (fiasco_c_options_t *options,
+				 fiasco_tiling_e method,
+				 unsigned exponent);
+
+/* Set FIASCO initial basis file */
+int fiasco_c_options_set_basisfile (fiasco_c_options_t *options,
+				    const char *filename);
+
+/* Set quality and dictionary size of chroma compression */
+int fiasco_c_options_set_chroma_quality (fiasco_c_options_t *options,
+					 float quality_factor,
+					 unsigned dictionary_size);
+
+/*
+ *  Since FIASCO internally works with binary trees, all functions
+ *  (which are handling image geometry) rather expect the `level' of
+ *  the corresponding binary tree than the traditional `width' and
+ *  `height' arguments.  Refer to following table to convert these
+ *  values:
+ *  
+ *  level | width | height
+ *  ------+-------+--------
+ *    0   |    1  |    1  
+ *    1   |    1  |    2  
+ *    2   |    2  |    2  
+ *    3   |    2  |    4  
+ *    4   |    4  |    4  
+ *    5   |    4  |    8  
+ *    6   |    8  |    8  
+ *    7   |    8  |   16
+ *
+ */
+   
+/* Set various optimization parameters. */
+int fiasco_c_options_set_optimizations (fiasco_c_options_t *options,
+					unsigned min_block_level,
+					unsigned max_block_level,
+					unsigned max_elements,
+					unsigned dictionary_size,
+					unsigned optimization_level);
+
+/* Set minimum and maximum size of image block prediction */
+int fiasco_c_options_set_prediction (fiasco_c_options_t *options,
+				     int intra_prediction,
+				     unsigned min_block_level,
+				     unsigned max_block_level);
+
+/*  Set various parameters used for video compensation */
+int fiasco_c_options_set_video_param (fiasco_c_options_t *options,
+				      unsigned frames_per_second,
+				      int half_pixel_prediction,
+				      int cross_B_search,
+				      int B_as_past_ref);
+
+/* Set accuracy of coefficients quantization */
+int fiasco_c_options_set_quantization (fiasco_c_options_t *options,
+				       unsigned mantissa,
+				       fiasco_rpf_range_e range,
+				       unsigned dc_mantissa,
+				       fiasco_rpf_range_e dc_range);
+
+/* Set type of progress meter */
+int fiasco_c_options_set_progress_meter (fiasco_c_options_t *options,
+					 fiasco_progress_e type);
+
+/*  Set comment of FIASCO stream */
+int fiasco_c_options_set_comment (fiasco_c_options_t *options,
+				  const char *comment);
+
+/*  Set title of FIASCO stream */
+int fiasco_c_options_set_title (fiasco_c_options_t *options,
+				const char *title);
+
+/****************************************************************************
+		 decoder options functions
+****************************************************************************/
+
+/* FIASCO additional options constructor */
+fiasco_d_options_t *fiasco_d_options_new (void);
+
+/* FIASCO additional options destructor */
+void fiasco_d_options_delete (fiasco_d_options_t *options);
+
+
+/* Define `smoothing'-percentage along partitioning borders.*/
+int fiasco_d_options_set_smoothing (fiasco_d_options_t *options,
+				  int smoothing);
+
+/* Set magnification-'level' of decoded image */
+int fiasco_d_options_set_magnification (fiasco_d_options_t *options,
+				      int level);
+
+/* Set image format to 4:2:0 or 4:4:4 */
+int fiasco_d_options_set_4_2_0_format (fiasco_d_options_t *options,
+				     int format);
+
+__END_DECLS
+
+#endif /* not _FIASCO_H */
diff --git a/converter/other/fiasco/fiascotopnm.c b/converter/other/fiasco/fiascotopnm.c
new file mode 100644
index 00000000..6d8b6f7f
--- /dev/null
+++ b/converter/other/fiasco/fiascotopnm.c
@@ -0,0 +1,477 @@
+/*
+ *  dwfa.c:     Decoding of WFA-files
+ *
+ *  Written by:     Ullrich Hafner
+ *          Michael Unger
+ *      
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+ 
+/*
+ *  $Date: 2000/10/28 17:39:29 $
+ *  $Author: hafner $
+ *  $Revision: 5.7 $
+ *  $State: Exp $
+ */
+
+#define _BSD_SOURCE 1   /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include "config.h"
+#include "pnm.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "types.h"
+#include "macros.h"
+
+#include <getopt.h>
+
+#include "binerror.h"
+#include "misc.h"
+#include "params.h"
+#include "fiasco.h"
+
+#ifndef X_DISPLAY_MISSING
+
+#   include "display.h"
+#   include "buttons.h"
+
+static x11_info_t *xinfo = NULL;
+
+#endif /* not X_DISPLAY_MISSING */
+
+/*****************************************************************************
+
+                prototypes
+  
+*****************************************************************************/
+
+static int 
+checkargs (int argc, char **argv, bool_t *double_resolution, bool_t *panel,
+           int *fps, char **image_name, fiasco_d_options_t **options);
+static void
+video_decoder (const char *wfa_name, const char *image_name, bool_t panel,
+               bool_t double_resolution, int fps, fiasco_d_options_t *options);
+static void
+get_output_template (const char *image_name, const char *wfa_name,
+                     bool_t color, char **basename, char **suffix);
+
+#ifndef X_DISPLAY_MISSING
+
+static void
+show_stored_frames (unsigned char * const *frame_buffer, int last_frame,
+                    x11_info_t *xinfo, binfo_t *binfo, size_t size,
+                    unsigned frame_time);
+
+#endif /* not X_DISPLAY_MISSING */
+
+/*****************************************************************************
+
+                public code
+  
+*****************************************************************************/
+
+int 
+main (int argc, char **argv)
+{
+    char               *image_name        = NULL; /* output filename */
+    bool_t              double_resolution = NO; /* double resolution of image */
+    bool_t              panel             = NO; /* control panel */
+    int             fps               = -1; /* frame display rate */
+    fiasco_d_options_t *options           = NULL; /* additional coder options */
+    int                 last_arg;    /* last processed cmdline parameter */
+
+    init_error_handling (argv[0]);
+
+    last_arg = checkargs (argc, argv, &double_resolution, &panel, &fps,
+                          &image_name, &options);
+   
+    if (last_arg >= argc)
+        video_decoder ("-", image_name, panel, double_resolution, fps, options);
+    else
+        while (last_arg++ < argc)
+            video_decoder (argv [last_arg - 1], image_name, panel,
+                           double_resolution, fps, options);
+
+    return 0;
+}
+
+/*****************************************************************************
+
+                private code
+  
+*****************************************************************************/
+
+static param_t params [] =
+{
+#ifdef X_DISPLAY_MISSING
+    {"output", "FILE", 'o', PSTR, {0}, "-",
+     "Write raw PNM frame(s) to `%s'."},
+#else  /* not X_DISPLAY_MISSING */
+    {"output", "FILE", 'o', POSTR, {0}, NULL,
+     "Write raw PNM frame(s) to INPUT.ppm/pgm [or `%s']."},
+#endif /* not X_DISPLAY_MISSING */
+    {"double", NULL, 'd', PFLAG, {0}, "FALSE",
+     "Interpolate images to double size before display."},
+    {"fast", NULL, 'r', PFLAG, {0}, "FALSE",
+     "Use 4:2:0 format for fast, low quality output."},
+    {"panel", NULL, 'p', PFLAG, {0}, "FALSE",
+     "Display control panel."},
+    {"magnify", "NUM", 'm', PINT, {0}, "0",
+     "Magnify/reduce image size by a factor of 4^`%s'."},
+    {"framerate", "NUM", 'F', PINT, {0}, "-1",
+     "Set display rate to `%s' frames per second."},
+    {"smoothing", "NUM", 's', PINT, {0}, "-1",
+     "Smooth image(s) by factor `%s' (0-100)"},
+    {NULL, NULL, 0, 0, {0}, NULL, NULL }
+};
+
+static int 
+checkargs (int argc, char **argv, bool_t *double_resolution, bool_t *panel,
+           int *fps, char **image_name, fiasco_d_options_t **options)
+/*
+ *  Check validness of command line parameters and of the parameter files.
+ *
+ *  Return value.
+ *  index in argv of the first argv-element that is not an option.
+ *
+ *  Side effects:
+ *  'double_resolution', 'panel', 'fps', 'image_name' and 'options'
+ *      are modified.
+ */
+{
+    int optind;              /* last processed commandline param */
+
+    optind = parseargs (params, argc, argv,
+#ifdef X_DISPLAY_MISSING
+                        "Decode FIASCO-FILEs and write frame(s) to disk.",
+#else  /* not X_DISPLAY_MISSING */
+                        "Decode and display FIASCO-FILEs using X11.",
+#endif /* not X_DISPLAY_MISSING */
+                        "With no FIASCO-FILE, or if FIASCO-FILE is -, "
+                        "read standard input.\n"
+#ifndef X_DISPLAY_MISSING
+                        "With --output=[FILE] specified, "
+                        "write frames without displaying them.\n\n"
+#endif  /* not X_DISPLAY_MISSING */
+                        "Environment:\n"
+                        "FIASCO_DATA   Search path for automata files. "
+                        "Default: ./\n"
+                        "FIASCO_IMAGES Save path for image files. "
+                        "Default: ./", " [FIASCO-FILE]...",
+                        FIASCO_SHARE, "system.fiascorc", ".fiascorc");
+
+    *image_name        =   (char *)   parameter_value (params, "output");
+    *double_resolution = *((bool_t *) parameter_value (params, "double"));
+    *panel             = *((bool_t *) parameter_value (params, "panel"));
+    *fps           = *((int *)    parameter_value (params, "framerate"));
+
+    /*
+     *  Additional options ... (have to be set with the fiasco_set_... methods)
+     */
+    *options = fiasco_d_options_new ();
+
+    {
+        int n = *((int *) parameter_value (params, "smoothing"));
+      
+        if (!fiasco_d_options_set_smoothing (*options, max (-1, n)))
+            error (fiasco_get_error_message ());
+    }
+
+    {
+        int n = *((int *) parameter_value (params, "magnify"));
+      
+        if (!fiasco_d_options_set_magnification (*options, n))
+            error (fiasco_get_error_message ());
+    }
+   
+    {
+        bool_t n = *((bool_t *) parameter_value (params, "fast"));
+      
+        if (!fiasco_d_options_set_4_2_0_format (*options, n > 0 ? YES : NO))
+            error (fiasco_get_error_message ());
+    }
+
+    return optind;
+}
+
+static void
+video_decoder (const char *wfa_name, const char *image_name, bool_t panel,
+               bool_t double_resolution, int fps, fiasco_d_options_t *options)
+{
+#ifndef X_DISPLAY_MISSING
+    fiasco_renderer_t  *renderer     = NULL;
+    unsigned char     **frame_buffer = NULL;
+    binfo_t            *binfo        = NULL; /* buttons info */
+#endif /* not X_DISPLAY_MISSING */
+   
+    do
+    {
+        unsigned      width, height, frames, n;
+        fiasco_decoder_t *decoder_state;
+        char             *filename;
+        char             *basename;   /* basename of decoded frame */
+        char             *suffix;     /* suffix of decoded frame */
+        unsigned      frame_time;
+      
+        if (!(decoder_state = fiasco_decoder_new (wfa_name, options)))
+            error (fiasco_get_error_message ());
+   
+        if (fps <= 0)         /* then use value of FIASCO file */ 
+            fps = fiasco_decoder_get_rate (decoder_state);
+        frame_time = fps ? (1000 / fps) : (1000 / 25);
+
+        if (!(width = fiasco_decoder_get_width (decoder_state)))
+            error (fiasco_get_error_message ());
+      
+        if (!(height = fiasco_decoder_get_height (decoder_state)))
+            error (fiasco_get_error_message ());
+
+        if (!(frames = fiasco_decoder_get_length (decoder_state)))
+            error (fiasco_get_error_message ());
+      
+        get_output_template (image_name, wfa_name,
+                             fiasco_decoder_is_color (decoder_state),
+                             &basename, &suffix);
+
+        filename = calloc (strlen (basename) + strlen (suffix) + 2
+                           + 10 + (int) (log10 (frames) + 1), sizeof (char));
+        if (!filename)
+            error ("Out of memory.");
+
+        for (n = 0; n < frames; n++)
+        {
+            clock_t fps_timer;     /* frames per second timer struct */
+     
+            prg_timer (&fps_timer, START);
+     
+            if (image_name)        /* just write frame to disk */
+            {
+                if (frames == 1)        /* just one image */
+                {
+                    if (streq (image_name, "-"))
+                        strcpy (filename, "-");
+                    else
+                        sprintf (filename, "%s.%s", basename, suffix);
+                }
+                else
+                {
+                    fprintf (stderr, "Decoding frame %d to file `%s.%0*d.%s\n",
+                             n, basename, (int) (log10 (frames - 1) + 1),
+                             n, suffix);
+                    sprintf (filename, "%s.%0*d.%s", basename,
+                             (int) (log10 (frames - 1) + 1), n, suffix);
+                }
+
+                if (!fiasco_decoder_write_frame (decoder_state, filename))
+                    error (fiasco_get_error_message ());
+            }
+#ifndef X_DISPLAY_MISSING
+            else
+            {
+                fiasco_image_t *frame;
+        
+                if (!(frame = fiasco_decoder_get_frame (decoder_state)))
+                    error (fiasco_get_error_message ());
+        
+                if (frames == 1)
+                    panel = NO;
+
+                if (xinfo == NULL)      /* initialize X11 window */
+                {
+                    const char * const title = 
+                        fiasco_decoder_get_title (decoder_state);
+                    char        titlename [MAXSTRLEN];
+
+           
+                    sprintf (titlename, "dfiasco " VERSION ": %s",
+                             strlen (title) > 0 ? title : wfa_name);
+                    xinfo = 
+                        open_window (titlename, "dfiasco",
+                                     (width  << (double_resolution ? 1 : 0)),
+                                     (height << (double_resolution ? 1 : 0))
+                                     + (panel ? 30 : 0));
+                    alloc_ximage (xinfo, width  << (double_resolution ? 1 : 0),
+                                  height << (double_resolution ? 1 : 0));
+                    if (panel)       /* initialize button panel */
+                        binfo = init_buttons (xinfo, n, frames, 30, 10);
+                    renderer = 
+                        fiasco_renderer_new (xinfo->ximage->red_mask,
+                                             xinfo->ximage->green_mask,
+                                             xinfo->ximage->blue_mask,
+                                             xinfo->ximage->bits_per_pixel,
+                                             double_resolution);
+                    if (!renderer)
+                        error (fiasco_get_error_message ());
+                }
+                renderer->render (renderer, xinfo->pixels, frame);
+                frame->delete (frame);
+        
+                if (frame_buffer != NULL) /* store next frame */
+                {
+                    size_t size = (width  << (double_resolution ? 1 : 0))
+                        * (height << (double_resolution ? 1 : 0))
+                        * (xinfo->ximage->depth <= 8
+                           ? sizeof (byte_t)
+                           : (xinfo->ximage->depth <= 16
+                              ? sizeof (u_word_t)
+                              : sizeof (unsigned int)));
+
+                    frame_buffer [n] = malloc (size);
+                    if (!frame_buffer [n])
+                        error ("Out of memory.");
+                    memcpy (frame_buffer [n], xinfo->pixels, size);
+
+                    if (n == frames - 1)
+                    {
+                        show_stored_frames (frame_buffer, frames - 1,
+                                            xinfo, binfo, size, frame_time);
+                        break;
+                    }
+                }
+
+                display_image (0, 0, xinfo);
+                if (frames == 1)
+                    wait_for_input (xinfo);
+                else if (panel)
+                {
+                    check_events (xinfo, binfo, n, frames);
+                    if (binfo->pressed [QUIT_BUTTON]) 
+                        /* start from beginning */
+                        break;
+                    if (binfo->pressed [STOP_BUTTON]) 
+                        /* start from beginning */
+                        n = frames;
+           
+                    if (binfo->pressed [RECORD_BUTTON] && frame_buffer == NULL)
+                    {
+                        n = frames;
+                        frame_buffer = 
+                            calloc (frames, sizeof (unsigned char *));
+                        if (!frame_buffer)
+                            error ("Out of memory.");
+                    }
+                }
+                while (prg_timer (&fps_timer, STOP) < frame_time) /* wait */
+                    ;
+            }
+#endif /* not X_DISPLAY_MISSING */   
+        }
+        free (filename);
+   
+        fiasco_decoder_delete (decoder_state);
+    } while (panel
+
+#ifndef X_DISPLAY_MISSING
+             && !binfo->pressed [QUIT_BUTTON]
+#endif /* not X_DISPLAY_MISSING */
+        
+        );
+
+#ifndef X_DISPLAY_MISSING
+    if (renderer)
+        renderer->delete (renderer);
+
+    if (!image_name)
+    {
+        close_window (xinfo);
+        free (xinfo);
+        xinfo = NULL;
+        if (binfo)
+            free (binfo);
+    }
+#endif /* not X_DISPLAY_MISSING */
+}
+
+static void
+get_output_template (const char *image_name, const char *wfa_name,
+                     bool_t color, char **basename, char **suffix)
+/*
+ *  Generate image filename template for output of image sequences.
+ *  'wfa_name' is the filename of the WFA stream.
+ *  Images are either saved with filename 'basename'.'suffix' (still images)
+ *  or 'basename'.%03d.'suffix' (videos).
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *  '*basename' and '*suffix' is set.
+ */
+{
+    if (!wfa_name || streq (wfa_name, "-"))
+        wfa_name = "stdin";       
+    /*
+     *  Generate filename template
+     */
+    if (!image_name || streq (image_name, "") || streq (image_name, "-"))
+    {
+        *basename = strdup (wfa_name);
+        *suffix   = NULL;
+    }
+    else
+    {
+        *basename = strdup (image_name);
+        *suffix   = strrchr (*basename, '.');
+    }
+    
+    if (*suffix)         /* found name 'basename.suffix' */
+    {
+        **suffix = 0;         /* remove dot */
+        (*suffix)++;
+        if (**suffix == 0)
+            *suffix = strdup (color ? "ppm" : "pgm");
+    }
+    else             /* no suffix found, generate one */
+        *suffix = strdup (color ? "ppm" : "pgm");
+}
+
+#ifndef X_DISPLAY_MISSING
+
+static void
+show_stored_frames (unsigned char * const *frame_buffer, int last_frame,
+                    x11_info_t *xinfo, binfo_t *binfo, size_t size,
+                    unsigned frame_time)
+/*
+ *  After a WFA video stream has been saved, all frames have been
+ *  decoded and stored in memory. These frames are then displayed
+ *  in an endless loop.
+ *
+ *  This function never returns, the program is terminated if the
+ *  STOP button is pressed.
+ */
+{
+    int n = last_frame;          /* frame number */
+   
+    while (1)
+    {
+        clock_t fps_timer;        /* frames per second timer struct */
+      
+        prg_timer (&fps_timer, START);
+      
+        display_image (0, 0, xinfo);
+        check_events (xinfo, binfo, n, last_frame + 1);
+
+        if (binfo->pressed [STOP_BUTTON])
+            n = 0;
+        else if (binfo->pressed [QUIT_BUTTON])
+            break;
+        else if (binfo->pressed [PLAY_BUTTON])
+            n++;
+        else if (binfo->pressed [RECORD_BUTTON]) /* REWIND is mapped RECORD */
+            n--;
+        if (n < 0)
+            n = last_frame;
+        if (n > last_frame)
+            n = 0;
+
+        memcpy (xinfo->pixels, frame_buffer [n], size);
+        while (prg_timer (&fps_timer, STOP) < frame_time) /* wait */
+            ;
+    };
+}
+
+#endif /* not X_DISPLAY_MISSING */
diff --git a/converter/other/fiasco/getopt.c b/converter/other/fiasco/getopt.c
new file mode 100644
index 00000000..0b2d1b75
--- /dev/null
+++ b/converter/other/fiasco/getopt.c
@@ -0,0 +1,1002 @@
+/* Getopt for GNU.
+   NOTE: getopt is now part of the C library, so if you don't know what
+   "Keep this file name-space clean" means, talk to roland@gnu.ai.mit.edu
+   before changing it!
+
+   Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97
+   Free Software Foundation, Inc.
+
+   This file is part of the GNU C Library.  Its master source is NOT part of
+   the C library, however.  The master source lives in /gd/gnu/lib.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>.
+   Ditto for AIX 3.2 and <stdlib.h>.  */
+#ifndef _NO_PROTO
+#define _NO_PROTO
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#if !defined (__STDC__) || !__STDC__
+/* This is a separate conditional since some stdc systems
+   reject `defined (const)'.  */
+#ifndef const
+#define const
+#endif
+#endif
+
+#include <string.h>
+#include <stdio.h>
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+   actually compiling the library itself.  This code is part of the GNU C
+   Library, but also included in many other GNU distributions.  Compiling
+   and linking in this code is a waste when using the GNU C library
+   (especially if it is a shared library).  Rather than having every GNU
+   program understand `configure --with-gnu-libc' and omit the object files,
+   it is simpler to just do this in the source for each such file.  */
+
+#define GETOPT_INTERFACE_VERSION 2
+#if !defined (_LIBC) && defined (__GLIBC__) && __GLIBC__ >= 2
+#include <gnu-versions.h>
+#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION
+#define ELIDE_CODE
+#endif
+#endif
+
+#ifndef ELIDE_CODE
+
+
+/* This needs to come after some library #include
+   to get __GNU_LIBRARY__ defined.  */
+#ifdef	__GNU_LIBRARY__
+/* Don't include stdlib.h for non-GNU C libraries because some of them
+   contain conflicting prototypes for getopt.  */
+#include <stdlib.h>
+#include <unistd.h>
+#endif	/* GNU C library.  */
+
+#ifdef VMS
+#include <unixlib.h>
+#if HAVE_STRING_H - 0
+#include <string.h>
+#endif
+#endif
+
+#if defined (WIN32) && !defined (__CYGWIN32__)
+/* It's not Unix, really.  See?  Capital letters.  */
+#include <windows.h>
+#define getpid() GetCurrentProcessId()
+#endif
+
+#ifndef _
+/* This is for other GNU distributions with internationalized messages.
+   When compiling libc, the _ macro is predefined.  */
+#ifdef HAVE_LIBINTL_H
+# include <libintl.h>
+# define _(msgid)	gettext (msgid)
+#else
+# define _(msgid)	(msgid)
+#endif
+#endif
+
+/* This version of `getopt' appears to the caller like standard Unix `getopt'
+   but it behaves differently for the user, since it allows the user
+   to intersperse the options with the other arguments.
+
+   As `getopt' works, it permutes the elements of ARGV so that,
+   when it is done, all the options precede everything else.  Thus
+   all application programs are extended to handle flexible argument order.
+
+   Setting the environment variable POSIXLY_CORRECT disables permutation.
+   Then the behavior is completely standard.
+
+   GNU application programs can use a third alternative mode in which
+   they can distinguish the relative order of options and other arguments.  */
+
+#include "getopt.h"
+
+/* For communication from `getopt' to the caller.
+   When `getopt' finds an option that takes an argument,
+   the argument value is returned here.
+   Also, when `ordering' is RETURN_IN_ORDER,
+   each non-option ARGV-element is returned here.  */
+
+char *optarg = NULL;
+
+/* Index in ARGV of the next element to be scanned.
+   This is used for communication to and from the caller
+   and for communication between successive calls to `getopt'.
+
+   On entry to `getopt', zero means this is the first call; initialize.
+
+   When `getopt' returns -1, this is the index of the first of the
+   non-option elements that the caller should itself scan.
+
+   Otherwise, `optind' communicates from one call to the next
+   how much of ARGV has been scanned so far.  */
+
+/* 1003.2 says this must be 1 before any call.  */
+int optind = 1;
+
+/* Formerly, initialization of getopt depended on optind==0, which
+   causes problems with re-calling getopt as programs generally don't
+   know that. */
+
+int __getopt_initialized = 0;
+
+/* The next char to be scanned in the option-element
+   in which the last option character we returned was found.
+   This allows us to pick up the scan where we left off.
+
+   If this is zero, or a null string, it means resume the scan
+   by advancing to the next ARGV-element.  */
+
+static char *nextchar;
+
+/* Callers store zero here to inhibit the error message
+   for unrecognized options.  */
+
+int opterr = 1;
+
+/* Set to an option character which was unrecognized.
+   This must be initialized on some systems to avoid linking in the
+   system's own getopt implementation.  */
+
+int optopt = '?';
+
+/* Describe how to deal with options that follow non-option ARGV-elements.
+
+   If the caller did not specify anything,
+   the default is REQUIRE_ORDER if the environment variable
+   POSIXLY_CORRECT is defined, PERMUTE otherwise.
+
+   REQUIRE_ORDER means don't recognize them as options;
+   stop option processing when the first non-option is seen.
+   This is what Unix does.
+   This mode of operation is selected by either setting the environment
+   variable POSIXLY_CORRECT, or using `+' as the first character
+   of the list of option characters.
+
+   PERMUTE is the default.  We permute the contents of ARGV as we scan,
+   so that eventually all the non-options are at the end.  This allows options
+   to be given in any order, even with programs that were not written to
+   expect this.
+
+   RETURN_IN_ORDER is an option available to programs that were written
+   to expect options and other ARGV-elements in any order and that care about
+   the ordering of the two.  We describe each non-option ARGV-element
+   as if it were the argument of an option with character code 1.
+   Using `-' as the first character of the list of option characters
+   selects this mode of operation.
+
+   The special argument `--' forces an end of option-scanning regardless
+   of the value of `ordering'.  In the case of RETURN_IN_ORDER, only
+   `--' can cause `getopt' to return -1 with `optind' != ARGC.  */
+
+static enum
+{
+  REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
+} ordering;
+
+/* Value of POSIXLY_CORRECT environment variable.  */
+static char *posixly_correct;
+
+#ifdef	__GNU_LIBRARY__
+/* We want to avoid inclusion of string.h with non-GNU libraries
+   because there are many ways it can cause trouble.
+   On some systems, it contains special magic macros that don't work
+   in GCC.  */
+#include <string.h>
+#define	my_index	strchr
+#else
+
+/* Avoid depending on library functions or files
+   whose names are inconsistent.  */
+
+char *getenv ();
+
+static char *
+my_index (str, chr)
+     const char *str;
+     int chr;
+{
+  while (*str)
+    {
+      if (*str == chr)
+	return (char *) str;
+      str++;
+    }
+  return 0;
+}
+
+/* If using GCC, we can safely declare strlen this way.
+   If not using GCC, it is ok not to declare it.  */
+#ifdef __GNUC__
+/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h.
+   That was relevant to code that was here before.  */
+#if !defined (__STDC__) || !__STDC__
+/* gcc with -traditional declares the built-in strlen to return int,
+   and has done so at least since version 2.4.5. -- rms.  */
+extern int strlen (const char *);
+#endif /* not __STDC__ */
+#endif /* __GNUC__ */
+
+#endif /* not __GNU_LIBRARY__ */
+
+/* Handle permutation of arguments.  */
+
+/* Describe the part of ARGV that contains non-options that have
+   been skipped.  `first_nonopt' is the index in ARGV of the first of them;
+   `last_nonopt' is the index after the last of them.  */
+
+static int first_nonopt;
+static int last_nonopt;
+
+#ifdef _LIBC
+/* Bash 2.0 gives us an environment variable containing flags
+   indicating ARGV elements that should not be considered arguments.  */
+
+static const char *nonoption_flags;
+static int nonoption_flags_len;
+
+static int original_argc;
+static char *const *original_argv;
+
+/* Make sure the environment variable bash 2.0 puts in the environment
+   is valid for the getopt call we must make sure that the ARGV passed
+   to getopt is that one passed to the process.  */
+static void store_args (int argc, char *const *argv) __attribute__ ((unused));
+static void
+store_args (int argc, char *const *argv)
+{
+  /* XXX This is no good solution.  We should rather copy the args so
+     that we can compare them later.  But we must not use malloc(3).  */
+  original_argc = argc;
+  original_argv = argv;
+}
+text_set_element (__libc_subinit, store_args);
+#endif
+
+/* Exchange two adjacent subsequences of ARGV.
+   One subsequence is elements [first_nonopt,last_nonopt)
+   which contains all the non-options that have been skipped so far.
+   The other is elements [last_nonopt,optind), which contains all
+   the options processed since those non-options were skipped.
+
+   `first_nonopt' and `last_nonopt' are relocated so that they describe
+   the new indices of the non-options in ARGV after they are moved.  */
+
+#if defined (__STDC__) && __STDC__
+static void exchange (char **);
+#endif
+
+static void
+exchange (argv)
+     char **argv;
+{
+  int bottom = first_nonopt;
+  int middle = last_nonopt;
+  int top = optind;
+  char *tem;
+
+  /* Exchange the shorter segment with the far end of the longer segment.
+     That puts the shorter segment into the right place.
+     It leaves the longer segment in the right place overall,
+     but it consists of two parts that need to be swapped next.  */
+
+  while (top > middle && middle > bottom)
+    {
+      if (top - middle > middle - bottom)
+	{
+	  /* Bottom segment is the short one.  */
+	  int len = middle - bottom;
+	  register int i;
+
+	  /* Swap it with the top part of the top segment.  */
+	  for (i = 0; i < len; i++)
+	    {
+	      tem = argv[bottom + i];
+	      argv[bottom + i] = argv[top - (middle - bottom) + i];
+	      argv[top - (middle - bottom) + i] = tem;
+	    }
+	  /* Exclude the moved bottom segment from further swapping.  */
+	  top -= len;
+	}
+      else
+	{
+	  /* Top segment is the short one.  */
+	  int len = top - middle;
+	  register int i;
+
+	  /* Swap it with the bottom part of the bottom segment.  */
+	  for (i = 0; i < len; i++)
+	    {
+	      tem = argv[bottom + i];
+	      argv[bottom + i] = argv[middle + i];
+	      argv[middle + i] = tem;
+	    }
+	  /* Exclude the moved top segment from further swapping.  */
+	  bottom += len;
+	}
+    }
+
+  /* Update records for the slots the non-options now occupy.  */
+
+  first_nonopt += (optind - last_nonopt);
+  last_nonopt = optind;
+}
+
+/* Initialize the internal data when the first call is made.  */
+
+#if defined (__STDC__) && __STDC__
+static const char *_getopt_initialize (int, char *const *, const char *);
+#endif
+static const char *
+_getopt_initialize (argc, argv, optstring)
+     int argc;
+     char *const *argv;
+     const char *optstring;
+{
+  /* Start processing options with ARGV-element 1 (since ARGV-element 0
+     is the program name); the sequence of previously skipped
+     non-option ARGV-elements is empty.  */
+
+  first_nonopt = last_nonopt = optind = 1;
+
+  nextchar = NULL;
+
+  posixly_correct = getenv ("POSIXLY_CORRECT");
+
+  /* Determine how to handle the ordering of options and nonoptions.  */
+
+  if (optstring[0] == '-')
+    {
+      ordering = RETURN_IN_ORDER;
+      ++optstring;
+    }
+  else if (optstring[0] == '+')
+    {
+      ordering = REQUIRE_ORDER;
+      ++optstring;
+    }
+  else if (posixly_correct != NULL)
+    ordering = REQUIRE_ORDER;
+  else
+    ordering = PERMUTE;
+
+#ifdef _LIBC
+  if (posixly_correct == NULL
+      && argc == original_argc && argv == original_argv)
+    {
+      /* Bash 2.0 puts a special variable in the environment for each
+	 command it runs, specifying which ARGV elements are the results of
+	 file name wildcard expansion and therefore should not be
+	 considered as options.  */
+      char var[100];
+      sprintf (var, "_%d_GNU_nonoption_argv_flags_", getpid ());
+      nonoption_flags = getenv (var);
+      if (nonoption_flags == NULL)
+	nonoption_flags_len = 0;
+      else
+	nonoption_flags_len = strlen (nonoption_flags);
+    }
+  else
+    nonoption_flags_len = 0;
+#endif
+
+  return optstring;
+}
+
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+   given in OPTSTRING.
+
+   If an element of ARGV starts with '-', and is not exactly "-" or "--",
+   then it is an option element.  The characters of this element
+   (aside from the initial '-') are option characters.  If `getopt'
+   is called repeatedly, it returns successively each of the option characters
+   from each of the option elements.
+
+   If `getopt' finds another option character, it returns that character,
+   updating `optind' and `nextchar' so that the next call to `getopt' can
+   resume the scan with the following option character or ARGV-element.
+
+   If there are no more option characters, `getopt' returns -1.
+   Then `optind' is the index in ARGV of the first ARGV-element
+   that is not an option.  (The ARGV-elements have been permuted
+   so that those that are not options now come last.)
+
+   OPTSTRING is a string containing the legitimate option characters.
+   If an option character is seen that is not listed in OPTSTRING,
+   return '?' after printing an error message.  If you set `opterr' to
+   zero, the error message is suppressed but we still return '?'.
+
+   If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+   so the following text in the same ARGV-element, or the text of the following
+   ARGV-element, is returned in `optarg'.  Two colons mean an option that
+   wants an optional arg; if there is text in the current ARGV-element,
+   it is returned in `optarg', otherwise `optarg' is set to zero.
+
+   If OPTSTRING starts with `-' or `+', it requests different methods of
+   handling the non-option ARGV-elements.
+   See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+
+   Long-named options begin with `--' instead of `-'.
+   Their names may be abbreviated as long as the abbreviation is unique
+   or is an exact match for some defined option.  If they have an
+   argument, it follows the option name in the same ARGV-element, separated
+   from the option name by a `=', or else the in next ARGV-element.
+   When `getopt' finds a long-named option, it returns 0 if that option's
+   `flag' field is nonzero, the value of the option's `val' field
+   if the `flag' field is zero.
+
+   The elements of ARGV aren't really const, because we permute them.
+   But we pretend they're const in the prototype to be compatible
+   with other systems.
+
+   LONGOPTS is a vector of `struct option' terminated by an
+   element containing a name which is zero.
+
+   LONGIND returns the index in LONGOPT of the long-named option found.
+   It is only valid when a long-named option has been found by the most
+   recent call.
+
+   If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+   long-named options.  */
+
+int
+_getopt_internal (argc, argv, optstring, longopts, longind, long_only)
+     int argc;
+     char *const *argv;
+     const char *optstring;
+     const struct option *longopts;
+     int *longind;
+     int long_only;
+{
+  optarg = NULL;
+
+  if (!__getopt_initialized || optind == 0)
+    {
+      optstring = _getopt_initialize (argc, argv, optstring);
+      optind = 1;		/* Don't scan ARGV[0], the program name.  */
+      __getopt_initialized = 1;
+    }
+
+  /* Test whether ARGV[optind] points to a non-option argument.
+     Either it does not have option syntax, or there is an environment flag
+     from the shell indicating it is not an option.  The later information
+     is only used when the used in the GNU libc.  */
+#ifdef _LIBC
+#define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0'	      \
+		     || (optind < nonoption_flags_len			      \
+			 && nonoption_flags[optind] == '1'))
+#else
+#define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0')
+#endif
+
+  if (nextchar == NULL || *nextchar == '\0')
+    {
+      /* Advance to the next ARGV-element.  */
+
+      /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been
+	 moved back by the user (who may also have changed the arguments).  */
+      if (last_nonopt > optind)
+	last_nonopt = optind;
+      if (first_nonopt > optind)
+	first_nonopt = optind;
+
+      if (ordering == PERMUTE)
+	{
+	  /* If we have just processed some options following some non-options,
+	     exchange them so that the options come first.  */
+
+	  if (first_nonopt != last_nonopt && last_nonopt != optind)
+	    exchange ((char **) argv);
+	  else if (last_nonopt != optind)
+	    first_nonopt = optind;
+
+	  /* Skip any additional non-options
+	     and extend the range of non-options previously skipped.  */
+
+	  while (optind < argc && NONOPTION_P)
+	    optind++;
+	  last_nonopt = optind;
+	}
+
+      /* The special ARGV-element `--' means premature end of options.
+	 Skip it like a null option,
+	 then exchange with previous non-options as if it were an option,
+	 then skip everything else like a non-option.  */
+
+      if (optind != argc && !strcmp (argv[optind], "--"))
+	{
+	  optind++;
+
+	  if (first_nonopt != last_nonopt && last_nonopt != optind)
+	    exchange ((char **) argv);
+	  else if (first_nonopt == last_nonopt)
+	    first_nonopt = optind;
+	  last_nonopt = argc;
+
+	  optind = argc;
+	}
+
+      /* If we have done all the ARGV-elements, stop the scan
+	 and back over any non-options that we skipped and permuted.  */
+
+      if (optind == argc)
+	{
+	  /* Set the next-arg-index to point at the non-options
+	     that we previously skipped, so the caller will digest them.  */
+	  if (first_nonopt != last_nonopt)
+	    optind = first_nonopt;
+	  return -1;
+	}
+
+      /* If we have come to a non-option and did not permute it,
+	 either stop the scan or describe it to the caller and pass it by.  */
+
+      if (NONOPTION_P)
+	{
+	  if (ordering == REQUIRE_ORDER)
+	    return -1;
+	  optarg = argv[optind++];
+	  return 1;
+	}
+
+      /* We have found another option-ARGV-element.
+	 Skip the initial punctuation.  */
+
+      nextchar = (argv[optind] + 1
+		  + (longopts != NULL && argv[optind][1] == '-'));
+    }
+
+  /* Decode the current option-ARGV-element.  */
+
+  /* Check whether the ARGV-element is a long option.
+
+     If long_only and the ARGV-element has the form "-f", where f is
+     a valid short option, don't consider it an abbreviated form of
+     a long option that starts with f.  Otherwise there would be no
+     way to give the -f short option.
+
+     On the other hand, if there's a long option "fubar" and
+     the ARGV-element is "-fu", do consider that an abbreviation of
+     the long option, just like "--fu", and not "-f" with arg "u".
+
+     This distinction seems to be the most useful approach.  */
+
+  if (longopts != NULL
+      && (argv[optind][1] == '-'
+	  || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1])))))
+    {
+      char *nameend;
+      const struct option *p;
+      const struct option *pfound = NULL;
+      int exact = 0;
+      int ambig = 0;
+      int indfound = -1;
+      int option_index;
+
+      for (nameend = nextchar; *nameend && *nameend != '='; nameend++)
+	/* Do nothing.  */ ;
+
+      /* Test all long options for either exact match
+	 or abbreviated matches.  */
+      for (p = longopts, option_index = 0; p->name; p++, option_index++)
+	if (!strncmp (p->name, nextchar, nameend - nextchar))
+	  {
+	    if ((unsigned int) (nameend - nextchar)
+		== (unsigned int) strlen (p->name))
+	      {
+		/* Exact match found.  */
+		pfound = p;
+		indfound = option_index;
+		exact = 1;
+		break;
+	      }
+	    else if (pfound == NULL)
+	      {
+		/* First nonexact match found.  */
+		pfound = p;
+		indfound = option_index;
+	      }
+	    else
+	      /* Second or later nonexact match found.  */
+	      ambig = 1;
+	  }
+
+      if (ambig && !exact)
+	{
+	  if (opterr)
+	    fprintf (stderr, _("%s: option `%s' is ambiguous\n"),
+		     argv[0], argv[optind]);
+	  nextchar += strlen (nextchar);
+	  optind++;
+	  optopt = 0;
+	  return '?';
+	}
+
+      if (pfound != NULL)
+	{
+	  option_index = indfound;
+	  optind++;
+	  if (*nameend)
+	    {
+	      /* Don't test has_arg with >, because some C compilers don't
+		 allow it to be used on enums.  */
+	      if (pfound->has_arg)
+		optarg = nameend + 1;
+	      else
+		{
+            if (opterr) {
+		   if (argv[optind - 1][1] == '-')
+		    /* --option */
+		    fprintf (stderr,
+		     _("%s: option `--%s' doesn't allow an argument\n"),
+		     argv[0], pfound->name);
+		   else
+		    /* +option or -option */
+		    fprintf (stderr,
+		     _("%s: option `%c%s' doesn't allow an argument\n"),
+		     argv[0], argv[optind - 1][0], pfound->name);
+            }
+
+		  nextchar += strlen (nextchar);
+
+		  optopt = pfound->val;
+		  return '?';
+		}
+	    }
+	  else if (pfound->has_arg == 1)
+	    {
+	      if (optind < argc)
+		optarg = argv[optind++];
+	      else
+		{
+		  if (opterr)
+		    fprintf (stderr,
+			   _("%s: option `%s' requires an argument\n"),
+			   argv[0], argv[optind - 1]);
+		  nextchar += strlen (nextchar);
+		  optopt = pfound->val;
+		  return optstring[0] == ':' ? ':' : '?';
+		}
+	    }
+	  nextchar += strlen (nextchar);
+	  if (longind != NULL)
+	    *longind = option_index;
+	  if (pfound->flag)
+	    {
+	      *(pfound->flag) = pfound->val;
+	      return 0;
+	    }
+	  return pfound->val;
+	}
+
+      /* Can't find it as a long option.  If this is not getopt_long_only,
+	 or the option starts with '--' or is not a valid short
+	 option, then it's an error.
+	 Otherwise interpret it as a short option.  */
+      if (!long_only || argv[optind][1] == '-'
+	  || my_index (optstring, *nextchar) == NULL)
+	{
+	  if (opterr)
+	    {
+	      if (argv[optind][1] == '-')
+		/* --option */
+		fprintf (stderr, _("%s: unrecognized option `--%s'\n"),
+			 argv[0], nextchar);
+	      else
+		/* +option or -option */
+		fprintf (stderr, _("%s: unrecognized option `%c%s'\n"),
+			 argv[0], argv[optind][0], nextchar);
+	    }
+	  nextchar = (char *) "";
+	  optind++;
+	  optopt = 0;
+	  return '?';
+	}
+    }
+
+  /* Look at and handle the next short option-character.  */
+
+  {
+    char c = *nextchar++;
+    char *temp = my_index (optstring, c);
+
+    /* Increment `optind' when we start to process its last character.  */
+    if (*nextchar == '\0')
+      ++optind;
+
+    if (temp == NULL || c == ':')
+      {
+	if (opterr)
+	  {
+	    if (posixly_correct)
+	      /* 1003.2 specifies the format of this message.  */
+	      fprintf (stderr, _("%s: illegal option -- %c\n"),
+		       argv[0], c);
+	    else
+	      fprintf (stderr, _("%s: invalid option -- %c\n"),
+		       argv[0], c);
+	  }
+	optopt = c;
+	return '?';
+      }
+    /* Convenience. Treat POSIX -W foo same as long option --foo */
+    if (temp[0] == 'W' && temp[1] == ';')
+      {
+	char *nameend;
+	const struct option *p;
+	const struct option *pfound = NULL;
+	int exact = 0;
+	int ambig = 0;
+	int indfound = 0;
+	int option_index;
+
+	/* This is an option that requires an argument.  */
+	if (*nextchar != '\0')
+	  {
+	    optarg = nextchar;
+	    /* If we end this ARGV-element by taking the rest as an arg,
+	       we must advance to the next element now.  */
+	    optind++;
+	  }
+	else if (optind == argc)
+	  {
+	    if (opterr)
+	      {
+		/* 1003.2 specifies the format of this message.  */
+		fprintf (stderr, _("%s: option requires an argument -- %c\n"),
+			 argv[0], c);
+	      }
+	    optopt = c;
+	    if (optstring[0] == ':')
+	      c = ':';
+	    else
+	      c = '?';
+	    return c;
+	  }
+	else
+	  /* We already incremented `optind' once;
+	     increment it again when taking next ARGV-elt as argument.  */
+	  optarg = argv[optind++];
+
+	/* optarg is now the argument, see if it's in the
+	   table of longopts.  */
+
+	for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++)
+	  /* Do nothing.  */ ;
+
+	/* Test all long options for either exact match
+	   or abbreviated matches.  */
+	for (p = longopts, option_index = 0; p->name; p++, option_index++)
+	  if (!strncmp (p->name, nextchar, nameend - nextchar))
+	    {
+	      if ((unsigned int) (nameend - nextchar) == strlen (p->name))
+		{
+		  /* Exact match found.  */
+		  pfound = p;
+		  indfound = option_index;
+		  exact = 1;
+		  break;
+		}
+	      else if (pfound == NULL)
+		{
+		  /* First nonexact match found.  */
+		  pfound = p;
+		  indfound = option_index;
+		}
+	      else
+		/* Second or later nonexact match found.  */
+		ambig = 1;
+	    }
+	if (ambig && !exact)
+	  {
+	    if (opterr)
+	      fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"),
+		       argv[0], argv[optind]);
+	    nextchar += strlen (nextchar);
+	    optind++;
+	    return '?';
+	  }
+	if (pfound != NULL)
+	  {
+	    option_index = indfound;
+	    if (*nameend)
+	      {
+		/* Don't test has_arg with >, because some C compilers don't
+		   allow it to be used on enums.  */
+		if (pfound->has_arg)
+		  optarg = nameend + 1;
+		else
+		  {
+		    if (opterr)
+		      fprintf (stderr, _("\
+%s: option `-W %s' doesn't allow an argument\n"),
+			       argv[0], pfound->name);
+
+		    nextchar += strlen (nextchar);
+		    return '?';
+		  }
+	      }
+	    else if (pfound->has_arg == 1)
+	      {
+		if (optind < argc)
+		  optarg = argv[optind++];
+		else
+		  {
+		    if (opterr)
+		      fprintf (stderr,
+			       _("%s: option `%s' requires an argument\n"),
+			       argv[0], argv[optind - 1]);
+		    nextchar += strlen (nextchar);
+		    return optstring[0] == ':' ? ':' : '?';
+		  }
+	      }
+	    nextchar += strlen (nextchar);
+	    if (longind != NULL)
+	      *longind = option_index;
+	    if (pfound->flag)
+	      {
+		*(pfound->flag) = pfound->val;
+		return 0;
+	      }
+	    return pfound->val;
+	  }
+	  nextchar = NULL;
+	  return 'W';	/* Let the application handle it.   */
+      }
+    if (temp[1] == ':')
+      {
+	if (temp[2] == ':')
+	  {
+	    /* This is an option that accepts an argument optionally.  */
+	    if (*nextchar != '\0')
+	      {
+		optarg = nextchar;
+		optind++;
+	      }
+	    else
+	      optarg = NULL;
+	    nextchar = NULL;
+	  }
+	else
+	  {
+	    /* This is an option that requires an argument.  */
+	    if (*nextchar != '\0')
+	      {
+		optarg = nextchar;
+		/* If we end this ARGV-element by taking the rest as an arg,
+		   we must advance to the next element now.  */
+		optind++;
+	      }
+	    else if (optind == argc)
+	      {
+		if (opterr)
+		  {
+		    /* 1003.2 specifies the format of this message.  */
+		    fprintf (stderr,
+			   _("%s: option requires an argument -- %c\n"),
+			   argv[0], c);
+		  }
+		optopt = c;
+		if (optstring[0] == ':')
+		  c = ':';
+		else
+		  c = '?';
+	      }
+	    else
+	      /* We already incremented `optind' once;
+		 increment it again when taking next ARGV-elt as argument.  */
+	      optarg = argv[optind++];
+	    nextchar = NULL;
+	  }
+      }
+    return c;
+  }
+}
+
+int
+getopt (argc, argv, optstring)
+     int argc;
+     char *const *argv;
+     const char *optstring;
+{
+  return _getopt_internal (argc, argv, optstring,
+			   (const struct option *) 0,
+			   (int *) 0,
+			   0);
+}
+
+#endif	/* Not ELIDE_CODE.  */
+
+#ifdef TEST
+
+/* Compile with -DTEST to make an executable for use in testing
+   the above definition of `getopt'.  */
+
+int
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int c;
+  int digit_optind = 0;
+
+  while (1)
+    {
+      int this_option_optind = optind ? optind : 1;
+
+      c = getopt (argc, argv, "abc:d:0123456789");
+      if (c == -1)
+	break;
+
+      switch (c)
+	{
+	case '0':
+	case '1':
+	case '2':
+	case '3':
+	case '4':
+	case '5':
+	case '6':
+	case '7':
+	case '8':
+	case '9':
+	  if (digit_optind != 0 && digit_optind != this_option_optind)
+	    printf ("digits occur in two different argv-elements.\n");
+	  digit_optind = this_option_optind;
+	  printf ("option %c\n", c);
+	  break;
+
+	case 'a':
+	  printf ("option a\n");
+	  break;
+
+	case 'b':
+	  printf ("option b\n");
+	  break;
+
+	case 'c':
+	  printf ("option c with value `%s'\n", optarg);
+	  break;
+
+	case '?':
+	  break;
+
+	default:
+	  printf ("?? getopt returned character code 0%o ??\n", c);
+	}
+    }
+
+  if (optind < argc)
+    {
+      printf ("non-option ARGV-elements: ");
+      while (optind < argc)
+	printf ("%s ", argv[optind++]);
+      printf ("\n");
+    }
+
+  exit (0);
+}
+
+#endif /* TEST */
diff --git a/converter/other/fiasco/getopt.h b/converter/other/fiasco/getopt.h
new file mode 100644
index 00000000..9acca708
--- /dev/null
+++ b/converter/other/fiasco/getopt.h
@@ -0,0 +1,129 @@
+/* Declarations for getopt.
+   Copyright (C) 1989,90,91,92,93,94,96,97 Free Software Foundation, Inc.
+
+   This file is part of the GNU C Library.  Its master source is NOT part of
+   the C library, however.  The master source lives in /gd/gnu/lib.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#ifndef _GETOPT_H
+#define _GETOPT_H 1
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/* For communication from `getopt' to the caller.
+   When `getopt' finds an option that takes an argument,
+   the argument value is returned here.
+   Also, when `ordering' is RETURN_IN_ORDER,
+   each non-option ARGV-element is returned here.  */
+
+extern char *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+   This is used for communication to and from the caller
+   and for communication between successive calls to `getopt'.
+
+   On entry to `getopt', zero means this is the first call; initialize.
+
+   When `getopt' returns -1, this is the index of the first of the
+   non-option elements that the caller should itself scan.
+
+   Otherwise, `optind' communicates from one call to the next
+   how much of ARGV has been scanned so far.  */
+
+extern int optind;
+
+/* Callers store zero here to inhibit the error message `getopt' prints
+   for unrecognized options.  */
+
+extern int opterr;
+
+/* Set to an option character which was unrecognized.  */
+
+extern int optopt;
+
+/* Describe the long-named options requested by the application.
+   The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector
+   of `struct option' terminated by an element containing a name which is
+   zero.
+
+   The field `has_arg' is:
+   no_argument		(or 0) if the option does not take an argument,
+   required_argument	(or 1) if the option requires an argument,
+   optional_argument 	(or 2) if the option takes an optional argument.
+
+   If the field `flag' is not NULL, it points to a variable that is set
+   to the value given in the field `val' when the option is found, but
+   left unchanged if the option is not found.
+
+   To have a long-named option do something other than set an `int' to
+   a compiled-in constant, such as set a value from `optarg', set the
+   option's `flag' field to zero and its `val' field to a nonzero
+   value (the equivalent single-letter option character, if there is
+   one).  For long options that have a zero `flag' field, `getopt'
+   returns the contents of the `val' field.  */
+
+struct option
+{
+  const char *name;
+  /* has_arg can't be an enum because some compilers complain about
+     type mismatches in all the code that assumes it is an int.  */
+  int has_arg;
+  int *flag;
+  int val;
+};
+
+/* Names for the values of the `has_arg' field of `struct option'.  */
+
+#define	no_argument		0
+#define required_argument	1
+#define optional_argument	2
+
+#if defined (__STDC__) && __STDC__
+#ifdef __GNU_LIBRARY__
+/* Many other libraries have conflicting prototypes for getopt, with
+   differences in the consts, in stdlib.h.  To avoid compilation
+   errors, only prototype getopt for the GNU C library.  */
+extern int getopt (int argc, char *const *argv, const char *shortopts);
+#else /* not __GNU_LIBRARY__ */
+extern int getopt ();
+#endif /* __GNU_LIBRARY__ */
+extern int getopt_long (int argc, char *const *argv, const char *shortopts,
+		        const struct option *longopts, int *longind);
+extern int getopt_long_only (int argc, char *const *argv,
+			     const char *shortopts,
+		             const struct option *longopts, int *longind);
+
+/* Internal only.  Users should not call this directly.  */
+extern int _getopt_internal (int argc, char *const *argv,
+			     const char *shortopts,
+		             const struct option *longopts, int *longind,
+			     int long_only);
+#else /* not __STDC__ */
+extern int getopt ();
+extern int getopt_long ();
+extern int getopt_long_only ();
+
+extern int _getopt_internal ();
+#endif /* __STDC__ */
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif /* _GETOPT_H */
diff --git a/converter/other/fiasco/getopt1.c b/converter/other/fiasco/getopt1.c
new file mode 100644
index 00000000..8347bb13
--- /dev/null
+++ b/converter/other/fiasco/getopt1.c
@@ -0,0 +1,189 @@
+/* getopt_long and getopt_long_only entry points for GNU getopt.
+   Copyright (C) 1987,88,89,90,91,92,93,94,96,97 Free Software Foundation, Inc.
+
+   This file is part of the GNU C Library.  Its master source is NOT part of
+   the C library, however.  The master source lives in /gd/gnu/lib.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "getopt.h"
+
+#if !defined (__STDC__) || !__STDC__
+/* This is a separate conditional since some stdc systems
+   reject `defined (const)'.  */
+#ifndef const
+#define const
+#endif
+#endif
+
+#include <stdio.h>
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+   actually compiling the library itself.  This code is part of the GNU C
+   Library, but also included in many other GNU distributions.  Compiling
+   and linking in this code is a waste when using the GNU C library
+   (especially if it is a shared library).  Rather than having every GNU
+   program understand `configure --with-gnu-libc' and omit the object files,
+   it is simpler to just do this in the source for each such file.  */
+
+#define GETOPT_INTERFACE_VERSION 2
+#if !defined (_LIBC) && defined (__GLIBC__) && __GLIBC__ >= 2
+#include <gnu-versions.h>
+#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION
+#define ELIDE_CODE
+#endif
+#endif
+
+#ifndef ELIDE_CODE
+
+
+/* This needs to come after some library #include
+   to get __GNU_LIBRARY__ defined.  */
+#ifdef __GNU_LIBRARY__
+#include <stdlib.h>
+#endif
+
+#ifndef	NULL
+#define NULL 0
+#endif
+
+int
+getopt_long (argc, argv, options, long_options, opt_index)
+     int argc;
+     char *const *argv;
+     const char *options;
+     const struct option *long_options;
+     int *opt_index;
+{
+  return _getopt_internal (argc, argv, options, long_options, opt_index, 0);
+}
+
+/* Like getopt_long, but '-' as well as '--' can indicate a long option.
+   If an option that starts with '-' (not '--') doesn't match a long option,
+   but does match a short option, it is parsed as a short option
+   instead.  */
+
+int
+getopt_long_only (argc, argv, options, long_options, opt_index)
+     int argc;
+     char *const *argv;
+     const char *options;
+     const struct option *long_options;
+     int *opt_index;
+{
+  return _getopt_internal (argc, argv, options, long_options, opt_index, 1);
+}
+
+
+#endif	/* Not ELIDE_CODE.  */
+
+#ifdef TEST
+
+#include <stdio.h>
+
+int
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int c;
+  int digit_optind = 0;
+
+  while (1)
+    {
+      int this_option_optind = optind ? optind : 1;
+      int option_index = 0;
+      static struct option long_options[] =
+      {
+	{"add", 1, 0, 0},
+	{"append", 0, 0, 0},
+	{"delete", 1, 0, 0},
+	{"verbose", 0, 0, 0},
+	{"create", 0, 0, 0},
+	{"file", 1, 0, 0},
+	{0, 0, 0, 0}
+      };
+
+      c = getopt_long (argc, argv, "abc:d:0123456789",
+		       long_options, &option_index);
+      if (c == -1)
+	break;
+
+      switch (c)
+	{
+	case 0:
+	  printf ("option %s", long_options[option_index].name);
+	  if (optarg)
+	    printf (" with arg %s", optarg);
+	  printf ("\n");
+	  break;
+
+	case '0':
+	case '1':
+	case '2':
+	case '3':
+	case '4':
+	case '5':
+	case '6':
+	case '7':
+	case '8':
+	case '9':
+	  if (digit_optind != 0 && digit_optind != this_option_optind)
+	    printf ("digits occur in two different argv-elements.\n");
+	  digit_optind = this_option_optind;
+	  printf ("option %c\n", c);
+	  break;
+
+	case 'a':
+	  printf ("option a\n");
+	  break;
+
+	case 'b':
+	  printf ("option b\n");
+	  break;
+
+	case 'c':
+	  printf ("option c with value `%s'\n", optarg);
+	  break;
+
+	case 'd':
+	  printf ("option d with value `%s'\n", optarg);
+	  break;
+
+	case '?':
+	  break;
+
+	default:
+	  printf ("?? getopt returned character code 0%o ??\n", c);
+	}
+    }
+
+  if (optind < argc)
+    {
+      printf ("non-option ARGV-elements: ");
+      while (optind < argc)
+	printf ("%s ", argv[optind++]);
+      printf ("\n");
+    }
+
+  exit (0);
+}
+
+#endif /* TEST */
diff --git a/converter/other/fiasco/input/Makefile b/converter/other/fiasco/input/Makefile
new file mode 100644
index 00000000..c01af772
--- /dev/null
+++ b/converter/other/fiasco/input/Makefile
@@ -0,0 +1,26 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../../..
+  BUILDDIR = $(SRCDIR)
+endif
+FIASCOSUBDIR = converter/other/fiasco
+SUBDIR = $(FIASCOSUBDIR)/input
+BUILDDIR = ../../../..
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+OBJECTS =  basis.o matrices.o mc.o nd.o read.o tree.o weights.o
+
+MERGE_OBJECTS = $(OBJECTS)
+
+INCLUDES = -I$(SRCDIR)/$(FIASCOSUBDIR) -I$(SRCDIR)/$(FIASCOSUBDIR)/lib \
+	   -I$(SRCDIR)/$(FIASCOSUBDIR)/codec
+
+all: libfiasco_input.a
+
+include $(SRCDIR)/Makefile.common
+
+libfiasco_input.a: $(OBJECTS)
+	$(AR) -rc $@ $(OBJECTS)
+	$(RANLIB) $@
+
diff --git a/converter/other/fiasco/input/basis.c b/converter/other/fiasco/input/basis.c
new file mode 100644
index 00000000..cef075e6
--- /dev/null
+++ b/converter/other/fiasco/input/basis.c
@@ -0,0 +1,141 @@
+/*
+ *  basis.c:		WFA initial basis files	
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/25 16:38:06 $
+ *  $Author: hafner $
+ *  $Revision: 5.3 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "wfa.h"
+#include "wfalib.h"
+
+#include "basis.h"
+
+typedef struct basis_values
+{
+   unsigned  states;
+   real_t   *final;
+   bool_t   *use_domain;
+   real_t (*transitions)[4];
+} basis_values_t;
+
+typedef struct
+{
+    const char *filename;
+    void (*function)(basis_values_t *bv);
+} basis_file_t;
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static void
+small_init (basis_values_t *bv);
+
+static basis_file_t const basis_files[] = { 
+    {"small.fco", small_init},
+    {"small.wfa", small_init},
+    {NULL, NULL} 
+};
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+bool_t
+get_linked_basis (const char *basis_name, wfa_t *wfa)
+/*
+ *  Check wether given WFA initial basis 'basis_name' is already linked
+ *  with the excecutable. If the basis is available then fill the 'wfa' struct
+ *  according to the stored data, otherwise print a warning message.
+ *
+ *  Return value:
+ *	true on success, false if basis is not available yet.
+ *
+ *  Side effects:
+ *	'wfa' struct is filled on success.
+ */
+{
+   bool_t	  success = NO;		/* indicates if basis is found */
+   unsigned	  n;			/* counter */
+   basis_values_t bv;			/* basis values */
+   
+   for (n = 0; basis_files [n].filename != NULL; n++)
+      if (streq (basis_files [n].filename, basis_name))	/* basis is stored */
+      {
+	 unsigned state, edge;		
+	 
+	 (*basis_files [n].function) (&bv); /* initialize local variables */
+	 /*
+	  *  Generate WFA
+	  */
+	 wfa->basis_states = wfa->states = bv.states + 1;
+	 wfa->domain_type[0]             = USE_DOMAIN_MASK; 
+	 wfa->final_distribution[0]      = 128;
+	 append_edge (0, 0, 1.0, 0, wfa);
+	 append_edge (0, 0, 1.0, 1, wfa);
+	 for (state = 1; state < wfa->basis_states; state++)
+	 {
+	    wfa->final_distribution [state] = bv.final [state - 1];
+	    wfa->domain_type [state]        = bv.use_domain [state - 1]
+					      ? USE_DOMAIN_MASK
+					      : AUXILIARY_MASK;
+	 }
+	 for (edge = 0; isedge (bv.transitions [edge][0]); edge++)
+	    append_edge (bv.transitions [edge][0], bv.transitions [edge][1],
+			 bv.transitions [edge][2], bv.transitions [edge][3],
+			 wfa);
+	 
+	 success = YES;
+	 break;
+      }
+
+   if (!success)
+      warning ("WFA initial basis '%s' isn't linked with the excecutable yet."
+	       "\nLoading basis from disk instead.", basis_name);
+
+   return success;
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+/*****************************************************************************
+				basis "small.wfa"
+*****************************************************************************/
+
+static unsigned	states_small           = 2;
+static bool_t	use_domain_small[]     = {YES, YES};
+static real_t 	final_small[]          = {64, 64};
+static real_t 	transitions_small[][4] = {{1, 2, 0.5, 0}, {1, 2, 0.5, 1},
+				      	 {1, 0, 0.5, 1}, {2, 1, 1.0, 0},
+				      	 {2, 1, 1.0, 1}, {-1, 0, 0, 0}};
+static void
+small_init (basis_values_t *bv)
+{
+   bv->states      = states_small;
+   bv->final       = final_small;
+   bv->use_domain  = use_domain_small;
+   bv->transitions = transitions_small;
+}
diff --git a/converter/other/fiasco/input/basis.h b/converter/other/fiasco/input/basis.h
new file mode 100644
index 00000000..fa26bca2
--- /dev/null
+++ b/converter/other/fiasco/input/basis.h
@@ -0,0 +1,26 @@
+/*
+ *  basis.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:13 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _BASIS_H
+#define _BASIS_H
+
+#include "wfa.h"
+
+bool_t
+get_linked_basis (const char *basis_name, wfa_t *wfa);
+
+#endif /* not _BASIS_H */
+
diff --git a/converter/other/fiasco/input/matrices.c b/converter/other/fiasco/input/matrices.c
new file mode 100644
index 00000000..47cde1aa
--- /dev/null
+++ b/converter/other/fiasco/input/matrices.c
@@ -0,0 +1,644 @@
+/*
+ *  matrices.c:		Input of transition matrices
+ *
+ *  Written by:		Ullrich Hafner
+ *  
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:13 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "bit-io.h"
+#include "arith.h"
+#include "misc.h"
+#include "wfalib.h"
+
+#include "matrices.h"
+
+#if STDC_HEADERS
+#	include <stdlib.h>
+#endif /* not STDC_HEADERS */
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static unsigned
+delta_decoding (wfa_t *wfa, unsigned last_domain, bitfile_t *input);
+static unsigned
+column_0_decoding (wfa_t *wfa, unsigned last_row, bitfile_t *input);
+static unsigned
+chroma_decoding (wfa_t *wfa, bitfile_t *input);
+static void
+compute_y_state (int state, int y_state, wfa_t *wfa);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+unsigned
+read_matrices (wfa_t *wfa, bitfile_t *input)
+/* 
+ *  Read transitions of WFA given from the stream 'input'.
+ *
+ *  Return value:
+ *	number of edges
+ *
+ *  Side effects:
+ *	'wfa->into' is filled with decoded values 
+ */
+{
+   unsigned total;			/* total number of edges in the WFA */
+   unsigned root_state = wfa->wfainfo->color
+			 ? wfa->tree [wfa->tree [wfa->root_state][0]][0]
+			 : wfa->root_state;
+
+   total  = column_0_decoding (wfa, root_state, input);
+   total += delta_decoding (wfa, root_state, input);
+   if (wfa->wfainfo->color)
+      total += chroma_decoding (wfa, input);
+       
+   return total;
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static unsigned
+delta_decoding (wfa_t *wfa, unsigned last_domain, bitfile_t *input)
+/*
+ *  Read transition matrices which are encoded with delta coding
+ *  from stream 'input'.
+ *  'last_domain' is the maximum state number used as domain image.
+ *
+ *  Return value:
+ *	number of non-zero matrix elements (WFA edges)
+ *
+ *  Side effects:
+ *	'wfa->into' is filled with decoded values 
+ */
+{
+   range_sort_t	 rs;			/* ranges are sorted as in the coder */
+   unsigned	 max_domain;		/* dummy used for recursion */
+   unsigned	 range;
+   unsigned	 count [MAXEDGES + 1];
+   unsigned 	 state, label;
+   unsigned	*n_edges;		/* number of elements per row */
+   unsigned	 total = 0;		/* total number of decoded edges */
+
+   /*
+    *  Generate a list of range blocks.
+    *  The order is the same as in the coder.
+    */
+   rs.range_state      = Calloc ((last_domain + 1) * MAXLABELS,
+				 sizeof (u_word_t));
+   rs.range_label      = Calloc ((last_domain + 1) * MAXLABELS,
+				 sizeof (byte_t));
+   rs.range_max_domain = Calloc ((last_domain + 1) * MAXLABELS,
+				 sizeof (u_word_t));
+   rs.range_subdivided = Calloc ((last_domain + 1) * MAXLABELS,
+				 sizeof (bool_t));
+   rs.range_no	       = 0;
+   max_domain 	       = wfa->basis_states - 1;
+   sort_ranges (last_domain, &max_domain, &rs, wfa);
+
+   /*
+    *  Get row statistics
+    */
+   {
+      arith_t  *decoder;
+      model_t  *elements;
+      unsigned 	max_edges = read_rice_code (3, input);
+      
+      /*
+       *  Get the probability array of the number of edges distribution
+       *  and allocate the corresponding model.
+       */
+      {
+	 unsigned edge;
+	 
+	 for (edge = 0; edge <= max_edges; edge++)
+	    count [edge] = read_rice_code ((int) log2 (last_domain) - 2,
+					   input);
+	 elements = alloc_model (max_edges + 1, 0, 0, count);
+      }
+      
+      /*
+       *  Get number of elements per matrix row
+       */
+      {
+	 unsigned row;
+      
+	 n_edges = Calloc (wfa->states, sizeof (unsigned));
+	 decoder = alloc_decoder (input);
+	 for (row = range = 0; range < rs.range_no; range++)
+	    if (!rs.range_subdivided [range])
+	    {
+	       state = rs.range_state [range];
+	       label = rs.range_label [range];
+	       
+	       n_edges [row++]
+		  = decode_symbol (decoder, elements)
+		  - (isedge (wfa->into [state][label][0]) ? 1 : 0);
+	    }
+	 
+	 free_decoder (decoder);
+	 free_model (elements);
+      }
+   }
+   
+   /*
+    *  Get matrix elements
+    */
+   {
+      unsigned row;
+      u_word_t *mapping1           = Calloc (wfa->states, sizeof (word_t));
+      u_word_t *mapping_coder1     = Calloc (wfa->states, sizeof (word_t));
+      u_word_t *mapping2           = Calloc (wfa->states, sizeof (word_t));
+      u_word_t *mapping_coder2     = Calloc (wfa->states, sizeof (word_t));
+      bool_t	use_normal_domains = get_bit (input);
+      bool_t	use_delta_domains  = get_bit (input);
+	  
+      /*
+       *  Generate array of states which are admitted domains.
+       *  When coding intra frames 'mapping1' == 'mapping2' otherwise
+       *  'mapping1' is a list of 'normal' domains which are admitted for 
+       *             coding intra blocks
+       *  'mapping2' is a list of 'delta' domains which are admitted for
+       *             coding the motion compensated prediction error 
+       */
+      {
+	 unsigned n1, n2, state;
+	    
+	 for (n1 = n2 = state = 0; state < wfa->states; state++)
+	 {
+	    mapping1 [n1] = state;
+	    mapping_coder1 [state] = n1;
+	    if (usedomain (state, wfa)
+		&& (state < wfa->basis_states
+		    || use_delta_domains || !wfa->delta_state [state]))
+	       n1++;
+	    
+	    mapping2 [n2] = state;
+	    mapping_coder2 [state] = n2;
+	    if (usedomain (state, wfa)
+		&& (state < wfa->basis_states || use_normal_domains
+		    || wfa->delta_state [state]))
+	       n2++;
+	 }
+      }
+	 
+      for (row = 0, range = 0; range < rs.range_no; range++)
+	 if (!rs.range_subdivided [range])
+	 {
+	    u_word_t *mapping;
+	    u_word_t *mapping_coder;
+	    unsigned  max_value;
+	    unsigned  edge;
+	    unsigned  state = rs.range_state [range];
+	    unsigned  label = rs.range_label [range];
+	    unsigned  last  = 1;
+
+	    if (wfa->delta_state [state] ||
+		wfa->mv_tree [state][label].type != NONE)
+	    {
+	       mapping 	     = mapping2;
+	       mapping_coder = mapping_coder2;
+	    }
+	    else
+	    {
+	       mapping 	     = mapping1;
+	       mapping_coder = mapping_coder1;
+	    }
+	    max_value = mapping_coder [rs.range_max_domain [range]];
+	    for (edge = n_edges [row]; edge; edge--)
+	    {
+	       unsigned domain;
+
+	       if (max_value - last)
+		  domain = read_bin_code (max_value - last, input) + last;
+	       else
+		  domain = max_value;
+	       append_edge (state, mapping [domain], -1, label, wfa);
+	       last = domain + 1;
+	       total++;
+	    }
+	    row++;
+	 }
+      Free (mapping1);
+      Free (mapping_coder1);
+      Free (mapping2);
+      Free (mapping_coder2);
+   }
+      
+   Free (n_edges);
+   Free (rs.range_state);
+   Free (rs.range_label);
+   Free (rs.range_max_domain);
+   Free (rs.range_subdivided);
+
+   return total;
+}
+
+static unsigned
+column_0_decoding (wfa_t *wfa, unsigned last_row, bitfile_t *input)
+/*
+ *  Read column 0 of the transition matrices of the 'wfa' which are coded
+ *  with quasi arithmetic coding from stream 'input'.
+ *  All rows from 'wfa->basis_states' up to 'last_row' are decoded.
+ * 
+ *  Return value:
+ *	number of non-zero matrix elements (WFA edges)
+ *
+ *  Side effects:
+ *	'wfa->into' is filled with decoded values 
+ */
+{
+   unsigned  row;			/* current matrix row */
+   unsigned  total = 0;			/* total number of edges in col 0 */
+   unsigned *prob_ptr;			/* pointer to current probability */
+   unsigned *last;			/* pointer to minimum probability */
+   unsigned *first;			/* pointer to maximum probability */
+   unsigned *new_prob_ptr;		/* ptr to probability of last domain */
+   unsigned *prob;			/* probability array */
+   u_word_t  high;			/* Start of the current code range */
+   u_word_t  low;			/* End of the current code range */
+   u_word_t  code;			/* The present input code value */
+   word_t   *is_leaf;			/* pointer to the tree structure */
+
+   /*
+    *  Compute the asymmetric probability array
+    *  prob[] = { 1/2, 1/2, 1/4, 1/4, 1/4, 1/4,
+    *             1/8, ... , 1/16, ..., 1/(MAXPROB+1)}
+    */
+   {
+      unsigned n;
+      unsigned index;			/* probability index */
+      unsigned exp;			/* current exponent */
+      
+      prob = Calloc (1 << (MAX_PROB + 1), sizeof (unsigned));
+   
+      for (index = 0, n = MIN_PROB; n <= MAX_PROB; n++)
+	 for (exp = 0; exp < 1U << n; exp++, index++)
+	    prob [index] = n;
+   }
+
+   first = prob_ptr = new_prob_ptr = prob;
+   last  = first + 1020;
+   
+   is_leaf = wfa->tree [wfa->basis_states]; /* use pointer arithmetics ... */
+
+   high = HIGH;				/* 1.0 */
+   low  = LOW;				/* 0.0 */
+   code = get_bits (input, 16);		
+
+   /*
+    *  Decode column 0 with a quasi arithmetic coder (QAC).
+    *  Advantage of this QAC with respect to a binary AC:
+    *  Instead of using time consuming multiplications and divisions
+    *  to compute the probability of the most probable symbol (MPS) and
+    *  the range of the interval, a table look up procedure linked
+    *  with a shift operation is used for both computations.
+    *
+    *  Loops and array accesses have been removed
+    *  to make real time decoding possible.
+    */
+   for (row = wfa->basis_states; row <= last_row; row++)
+   {
+      unsigned count;			/* value in the current interval */
+      
+      /*
+       *  Read label 0 element
+       */
+      if (isrange (*is_leaf++))		/* valid matrix index */
+      {
+	 count = high - ((high - low) >> *prob_ptr);
+	 if (code < count)
+	 {
+	    if (prob_ptr < last)	/* model update */
+	       prob_ptr++;
+	    /*
+	     *  Decode the MPS '0'
+	     */
+	    high = count - 1;
+
+	    RESCALE_INPUT_INTERVAL;
+	 }
+	 else
+	 {
+	    prob_ptr = ((prob_ptr - first) >> 1) + first; /* model update */
+	    /*
+	     *  Decode the LPS '1'
+	     */
+	    low = count;
+
+	    RESCALE_INPUT_INTERVAL;
+	    /*
+	     *  Restore the transition (weight = -1)
+	     */
+	    append_edge (row, 0, -1, 0, wfa);
+	    total++;
+	 }
+      }
+      /*
+       *  Read label 1 element
+       */
+      if (isrange (*is_leaf++)) /* valid matrix index */
+      {
+	 count = high - ((high - low) >> *prob_ptr);
+	 if (code < count)
+	 {
+	    if (prob_ptr < last)
+	       prob_ptr++;		/* model update */
+	    /*
+	     *  Decode the MPS '0'
+	     */
+	    high = count - 1;
+
+	    RESCALE_INPUT_INTERVAL;
+	 }
+	 else
+	 {
+	    prob_ptr = ((prob_ptr - first) >> 1) + first; /* model update */
+	    /*
+	     *  Decode the LPS '1'
+	     */
+	    low = count;
+
+	    RESCALE_INPUT_INTERVAL;
+	    /*
+	     *  Restore the transition (weight = -1)
+	     */
+	    append_edge (row, 0, -1, 1, wfa);
+	    total++;
+	 }
+      }
+   }
+
+   INPUT_BYTE_ALIGN (input);
+
+   Free (prob);
+   
+   return total;
+}
+
+static unsigned
+chroma_decoding (wfa_t *wfa, bitfile_t *input)
+/*
+ *  Read transition matrices of 'wfa' states which are part of the
+ *  chroma channels Cb and Cr from stream 'input'.
+ *
+ *  Return value:
+ *	number of non-zero matrix elements (WFA edges)
+ *
+ *  Side effects:
+ *	'wfa->into' is filled with decoded values 
+ */
+{
+   unsigned  domain;			/* current domain, counter */
+   unsigned  total = 0;			/* total number of chroma edges */
+   unsigned *prob_ptr;			/* pointer to current probability */
+   unsigned *last;			/* pointer to minimum probability */
+   unsigned *first;			/* pointer to maximum probability */
+   unsigned *new_prob_ptr;		/* ptr to probability of last domain */
+   unsigned *prob;			/* probability array */
+   u_word_t  high;			/* Start of the current code range */
+   u_word_t  low;			/* End of the current code range */
+   u_word_t  code;			/* The present input code value */
+   word_t   *y_domains;			/* domain images corresponding to Y */
+   int	     save_index;		/* YES: store current probabilty */
+
+   /*
+    *  Compute the asymmetric probability array
+    *  prob[] = { 1/2, 1/2, 1/4, 1/4, 1/4, 1/4,
+    *                     1/8, ... , 1/16, ..., 1/(MAXPROB+1)}
+    */
+   {
+      unsigned n;
+      unsigned index;			/* probability index */
+      unsigned exp;			/* current exponent */
+      
+      prob = Calloc (1 << (MAX_PROB + 1), sizeof (unsigned));
+   
+      for (index = 0, n = MIN_PROB; n <= MAX_PROB; n++)
+	 for (exp = 0; exp < 1U << n; exp++, index++)
+	    prob [index] = n;
+   }
+
+   high = HIGH;				/* 1.0 */
+   low  = LOW;				/* 0.0 */
+   code = get_bits (input, 16);
+
+   /*
+    *  Compute list of admitted domains
+    */
+   y_domains = compute_hits (wfa->basis_states,
+			     wfa->tree [wfa->tree [wfa->root_state][0]][0],
+			     wfa->wfainfo->chroma_max_states, wfa);
+   
+   first = prob_ptr = new_prob_ptr = prob;
+   last  = first + 1020;
+
+   /*
+    *  First of all, read all matrix columns given in the list 'y_domains'
+    *  which note all admitted domains.
+    *  These matrix elements are stored with QAC (see column_0_decoding ()).
+    */
+   for (domain = 0; y_domains [domain] != -1; domain++)
+   {
+      unsigned 	row	= wfa->tree [wfa->tree [wfa->root_state][0]][0] + 1;
+      word_t   *is_leaf = wfa->tree [row];
+
+      prob_ptr   = new_prob_ptr;
+      save_index = YES;
+
+      for (; row < wfa->states; row++)
+      {
+	 unsigned count;		/* value in the current interval */
+	 /*
+	  *  Read label 0 element
+	  */
+	 if (isrange (*is_leaf++)) 	/* valid matrix index */
+	 {
+	    count = high - ((high - low) >> *prob_ptr);
+	    if (code < count)
+	    {
+	       if (prob_ptr < last)
+		  prob_ptr++;
+	       /*
+		*  Decode the MPS '0'
+		*/
+	       high = count - 1;
+
+	       RESCALE_INPUT_INTERVAL;
+	    }
+	    else
+	    {
+	       prob_ptr = ((prob_ptr - first) >> 1) + first;
+	       /*
+		*  Decode the LPS '1'
+		*/
+	       low = count;
+
+	       RESCALE_INPUT_INTERVAL;
+	       /*
+		*  Restore the transition (weight = -1)
+		*/
+	       append_edge (row, y_domains [domain], -1, 0, wfa);
+	       total++;
+	    }
+	 }
+	 /*
+	  *  Read label 1 element
+	  */
+	 if (isrange (*is_leaf++)) /* valid matrix index */
+	 {
+	    count = high - ((high - low) >> *prob_ptr);
+	    if (code < count)
+	    {
+	       if (prob_ptr < last)
+		  prob_ptr++;
+	       /*
+		*  Decode the MPS '0'
+		*/
+	       high = count - 1;
+
+	       RESCALE_INPUT_INTERVAL;
+	    }
+	    else
+	    {
+	       prob_ptr = ((prob_ptr - first) >> 1) + first;
+	       /*
+		*  Decode the LPS '1'
+		*/
+	       low = count;
+
+	       RESCALE_INPUT_INTERVAL;
+	       /*
+		*  Restore the transition (weight = -1)
+		*/
+	       append_edge (row, y_domains [domain], -1, 1, wfa);
+	       total++;
+	    }
+	 }
+	 if (save_index)
+	 {
+	    save_index 	 = NO;
+	    new_prob_ptr = prob_ptr;
+	 }
+      }
+   }
+
+   Free (y_domains);
+
+   compute_y_state (wfa->tree [wfa->tree [wfa->root_state][0]][1],
+		    wfa->tree [wfa->tree [wfa->root_state][0]][0], wfa);
+   compute_y_state (wfa->tree [wfa->tree [wfa->root_state][1]][0],
+		    wfa->tree [wfa->tree [wfa->root_state][0]][0], wfa);
+   
+   first = prob_ptr = new_prob_ptr = prob;
+
+   /*
+    *  Decode the additional column which indicates whether there
+    *  are transitions to a state with same spatial coordinates
+    *  in the Y component.
+    *
+    *  Again, quasi arithmetic decoding is used for this task.
+    */
+   {
+      unsigned 	row;
+      
+      for (row = wfa->tree [wfa->tree [wfa->root_state][0]][0] + 1;
+	   row < wfa->states; row++)
+      {
+	 int label;			/* current label */
+
+	 for (label = 0; label < MAXLABELS; label++)
+	 {
+	    u_word_t count = high - ((high - low) >> *prob_ptr);
+
+	    if (code < count)
+	    {
+	       if (prob_ptr < last)
+		  prob_ptr++;
+	       /*
+		*  Decode the MPS '0'
+		*/
+	       high = count - 1;
+
+	       RESCALE_INPUT_INTERVAL;
+	    }
+	    else
+	    {
+	       prob_ptr = ((prob_ptr - first) >> 1) + first;
+	       /*
+		*  Decode the LPS '1'
+		*/
+	       low = count;
+
+	       RESCALE_INPUT_INTERVAL;
+	       /*
+		*  Restore the transition (weight = -1)
+		*/
+	       append_edge (row, wfa->y_state [row][label], -1, label, wfa);
+	       total++;
+	    }
+	 }
+      }
+   }
+
+   INPUT_BYTE_ALIGN (input);
+
+   Free (prob);
+
+   return total;
+}
+
+static void
+compute_y_state (int state, int y_state, wfa_t *wfa)
+/*
+ *  Compute the 'wfa->y_state' array which denotes those states of
+ *  the Y band that have the same spatial coordinates as the corresponding
+ *  states of the Cb and Cr bands.
+ *  The current root of the Y tree is given by 'y_state'.
+ *  The current root of the tree of the chroma channel is given by 'state'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'wfa->y_state' is filled with the generated tree structure.
+ */
+{
+   unsigned label;
+   
+   for (label = 0; label < MAXLABELS; label++)
+      if (isrange (y_state))
+	 wfa->y_state [state][label] = RANGE;
+      else
+      {
+	 wfa->y_state [state][label] = wfa->tree [y_state][label];
+	 if (!isrange (wfa->tree [state][label]))
+	    compute_y_state (wfa->tree [state][label],
+			     wfa->y_state [state][label], wfa);
+      }
+      
+}
diff --git a/converter/other/fiasco/input/matrices.h b/converter/other/fiasco/input/matrices.h
new file mode 100644
index 00000000..ba8fd9bf
--- /dev/null
+++ b/converter/other/fiasco/input/matrices.h
@@ -0,0 +1,27 @@
+/*
+ *  matrices.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:13 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _MATRICES_H
+#define _MATRICES_H
+
+#include "wfa.h"
+#include "bit-io.h"
+
+unsigned
+read_matrices (wfa_t *wfa, bitfile_t *input);
+
+#endif /* not _MATRICES_H */
+
diff --git a/converter/other/fiasco/input/mc.c b/converter/other/fiasco/input/mc.c
new file mode 100644
index 00000000..070d839e
--- /dev/null
+++ b/converter/other/fiasco/input/mc.c
@@ -0,0 +1,334 @@
+/*
+ *  mc.c:	Input of motion compensation	
+ *
+ *  written by: Michael Unger
+ *		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:13 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include <stdlib.h>
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "wfa.h"
+#include "bit-io.h"
+#include "misc.h"
+#include "mvcode.h"
+
+#include "mc.h"
+
+/*****************************************************************************
+
+			     local variables
+  
+*****************************************************************************/
+
+typedef struct huff_node 
+{
+   int		     code_index;	/* leaf if index >= 0 */
+   struct huff_node *left;		/* follow if '0' bit read */
+   struct huff_node *right;		/* follow if '1' bit read */
+   int		     index_set [34];
+} huff_node_t;
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static void
+decode_mc_tree (frame_type_e frame_type, unsigned max_state,
+		wfa_t *wfa, bitfile_t *input);
+static void
+decode_mc_coords (unsigned max_state, wfa_t *wfa, bitfile_t *input);
+static int
+get_mv (int f_code, huff_node_t *hn, bitfile_t *input);
+static huff_node_t *
+create_huff_tree (void);
+static void
+create_huff_node (huff_node_t *hn, int bits_processed);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+void
+read_mc (frame_type_e frame_type, wfa_t *wfa, bitfile_t *input)
+/*
+ *  Read motion compensation information of the 'input' stream.
+ *  Depending on 'frame_type' different decoding methods are used.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'wfa->mv_tree' is filled with the decoded values.
+ */
+{
+   unsigned max_state = wfa->wfainfo->color
+			? wfa->tree [wfa->tree [wfa->root_state][0]][0]
+			: wfa->states;
+
+   decode_mc_tree (frame_type, max_state, wfa, input);
+   decode_mc_coords (max_state, wfa, input);
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static void
+decode_mc_tree (frame_type_e frame_type, unsigned max_state,
+		wfa_t *wfa, bitfile_t *input)
+/*
+ *  Read tree of motion compensation decisions of the 'input' stream.
+ *  Depending on 'frame_type' different decoding methods are used.
+ *  'max_state' is the last state with motion compensation infos.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'wfa->mv_tree' is filled with decoded values.
+ */
+{
+   unsigned  state;			/* current state */
+   unsigned *queue;			/* states in breadth first order */
+   unsigned  last;			/* last node
+					   (update for each new node) */
+
+   /*
+    *  Traverse tree in breadth first order (starting at level
+    *  'wfa->wfainfo->p_max_level'). Use a queue to store the childs
+    *  of each node ('last' is the next free queue element).  
+    */
+   queue = Calloc (MAXSTATES, sizeof (unsigned));
+   for (last = 0, state = wfa->basis_states; state < max_state; state++)
+      if (wfa->level_of_state [state] - 1 == (int) wfa->wfainfo->p_max_level)
+	 queue [last++] = state;	/* init level 'p_max_level' */
+
+   if (frame_type == P_FRAME)
+   {
+      unsigned label;			/* current label */
+      unsigned current;			/* current node to process */
+      
+      for (current = 0; current < last; current++)
+	 for (label = 0; label < MAXLABELS; label++)
+	 {
+	    state = queue[current];
+	    if (wfa->x [state][label]	/* process visible states only */
+		+  width_of_level (wfa->level_of_state [state] - 1)
+		<= wfa->wfainfo->width
+		&&
+		wfa->y [state][label]
+		+  height_of_level (wfa->level_of_state [state] - 1)
+		<= wfa->wfainfo->height)
+	    {
+	       wfa->mv_tree [state][label].type
+		  = get_bit (input) ? NONE : FORWARD;
+	    }
+	    else
+	       wfa->mv_tree [state][label].type = NONE;
+	    if (wfa->mv_tree [state][label].type == NONE &&
+		!isrange (wfa->tree [state][label]) &&
+		wfa->level_of_state [state] - 1 >=
+		(int) wfa->wfainfo->p_min_level) 
+	       queue [last++] = wfa->tree [state][label]; /* append child  */
+	 }
+   }
+   else
+   {
+      unsigned label;			/* current label */
+      unsigned current;			/* current node to process */
+      
+      for (current = 0; current < last; current++)
+	 for (label = 0; label < MAXLABELS; label++)
+	 {
+	    state = queue[current];
+	    if (wfa->x [state][label]	/* process visible states only */
+		+ width_of_level (wfa->level_of_state [state] - 1)
+		> wfa->wfainfo->width
+		||
+		wfa->y [state][label]
+		+ height_of_level (wfa->level_of_state [state] - 1)
+		> wfa->wfainfo->height)
+	       wfa->mv_tree[state][label].type = NONE;
+	    else if (get_bit (input))	/* 1   */
+	       wfa->mv_tree[state][label].type = NONE;
+	    else if (get_bit (input))	/* 01  */
+	       wfa->mv_tree[state][label].type = INTERPOLATED;
+	    else if (get_bit (input))	/* 001 */ 
+	       wfa->mv_tree[state][label].type = BACKWARD;
+	    else			/* 000 */ 
+	       wfa->mv_tree[state][label].type = FORWARD;
+	    if (wfa->mv_tree[state][label].type == NONE &&
+		!isrange (wfa->tree[state][label]) &&
+		wfa->level_of_state[state] - 1
+		>= (int) wfa->wfainfo->p_min_level) 
+	       queue[last++] = wfa->tree[state][label]; /* append child  */
+	 }
+   }
+   
+   INPUT_BYTE_ALIGN (input);
+   Free (queue);
+}
+
+static void
+decode_mc_coords (unsigned max_state, wfa_t *wfa, bitfile_t *input)
+/*
+ *  Read motion vector coordinates of the 'input' stream. They are stored
+ *  with the static Huffman code of the MPEG and H.263 standards.
+ *  'max_state' is the last state with motion compensation infos.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'wfa->mv_tree' is filled with decoded values.
+ */
+{
+   unsigned	       label;		/* current label */
+   unsigned	       state;		/* current state */
+   mv_t		      *mv;		/* current motion vector */
+   static huff_node_t *huff_mv_root = NULL; /* root of huffman tree */
+ 
+   if (huff_mv_root == NULL)
+      huff_mv_root = create_huff_tree ();
+   
+   for (state = wfa->basis_states; state < max_state; state++)
+      for (label = 0; label < MAXLABELS; label++)
+      {
+	 mv = &wfa->mv_tree[state][label];
+	 switch (mv->type)
+	 {
+	    case NONE:
+	       break;
+	    case FORWARD:
+	       mv->fx = get_mv (1, huff_mv_root, input);
+	       mv->fy = get_mv (1, huff_mv_root, input);
+	       break;	    
+	    case BACKWARD:	    
+	       mv->bx = get_mv (1, huff_mv_root, input);
+	       mv->by = get_mv (1, huff_mv_root, input);
+	       break;	    
+	    case INTERPOLATED:   
+	       mv->fx = get_mv (1, huff_mv_root, input);
+	       mv->fy = get_mv (1, huff_mv_root, input);
+	       mv->bx = get_mv (1, huff_mv_root, input);
+	       mv->by = get_mv (1, huff_mv_root, input);
+	       break;
+	 }
+      }
+
+   INPUT_BYTE_ALIGN (input);
+}
+ 
+static int
+get_mv (int f_code, huff_node_t *hn, bitfile_t *input)
+/* 
+ *  Decode next motion vector component in bitstream 
+ *  by traversing the huffman tree.
+ */
+{
+   int vlc_code, vlc_code_magnitude, residual, diffvec;
+
+   while (hn->code_index < 0)
+   {
+      if (hn->code_index == -2)
+	 error ("wrong huffman code !");
+      if (get_bit (input))
+	 hn = hn->right;
+      else
+	 hn = hn->left;
+   }
+   vlc_code = hn->code_index - 16;
+   if (vlc_code == 0 || f_code == 1) 
+      return vlc_code;
+
+   vlc_code_magnitude = abs (vlc_code) - 1;
+   if (f_code <= 1)
+      residual = 0;
+   else
+      residual = get_bits (input, f_code - 1);
+   diffvec = (vlc_code_magnitude << (f_code - 1)) + residual + 1;
+   
+   return vlc_code > 0 ? diffvec : - diffvec;
+}
+
+static huff_node_t *
+create_huff_tree (void)
+/*
+ *  Construct huffman tree from code table
+ */
+{
+   unsigned	i;
+   huff_node_t *huff_root = Calloc (1, sizeof (huff_node_t));
+   
+   /*
+    *  The nodes' index set contains indices of all codewords that are
+    *  still decodable by traversing further down from the node.
+    *  (The root node has the full index set.)
+    */
+
+   for (i = 0; i < 33; i++)
+      huff_root->index_set [i] = i;
+   huff_root->index_set [i] = -1;	/* end marker */
+
+   create_huff_node (huff_root, 0);
+
+   return huff_root;
+}
+
+static void
+create_huff_node (huff_node_t *hn, int bits_processed)
+/*
+ *  Create one node in the huffman tree
+ */
+{
+   int lind = 0;			/* next index of left huff_node */
+   int rind = 0;			/* next index of right huff_node */
+   int code_len, i, ind;
+
+   hn->code_index = -1;
+   if (hn->index_set [0] < 0)		/* empty index set ? */
+   {
+      hn->code_index = -2;		/* error */
+      return;
+   }
+   hn->left  = Calloc (1, sizeof (huff_node_t));
+   hn->right = Calloc (1, sizeof (huff_node_t));
+
+   for (i = 0; (ind = hn->index_set[i]) >= 0; i++)
+   {
+      code_len = mv_code_table[ind][1];
+      if (code_len == bits_processed)	/* generate leaf */
+      {
+	 hn->code_index = ind;
+	 Free (hn->left); 
+	 Free (hn->right);
+	 return;
+      }
+      if (mv_code_table[ind][0] & (1 << (code_len - 1 - bits_processed)))
+	 hn->right->index_set[rind++] = ind;
+      else
+	 hn->left->index_set[lind++] = ind;
+   }
+   hn->right->index_set[rind] = -1;	/* set end markers */
+   hn->left->index_set[lind]  = -1;
+   create_huff_node (hn->left, bits_processed + 1);
+   create_huff_node (hn->right, bits_processed + 1);
+}
diff --git a/converter/other/fiasco/input/mc.h b/converter/other/fiasco/input/mc.h
new file mode 100644
index 00000000..1e14d287
--- /dev/null
+++ b/converter/other/fiasco/input/mc.h
@@ -0,0 +1,28 @@
+/*
+ *  mc.h
+ *
+ *  written by: Michael Unger
+ *		Ullrich Hafner
+ 
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:13 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _MC_H
+#define _MC_H
+
+#include "wfa.h"
+#include "bit-io.h"
+
+void
+read_mc (frame_type_e frame_type, wfa_t *wfa, bitfile_t *input);
+
+#endif /* not _MC_H */
+
diff --git a/converter/other/fiasco/input/nd.c b/converter/other/fiasco/input/nd.c
new file mode 100644
index 00000000..1a68bfbf
--- /dev/null
+++ b/converter/other/fiasco/input/nd.c
@@ -0,0 +1,237 @@
+/*
+ *  nd.c:		Input of prediction tree	
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:13 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "bit-io.h"
+#include "arith.h"
+#include "misc.h"
+#include "list.h"
+#include "wfalib.h"
+
+#include "nd.h"
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static void
+decode_nd_coefficients (unsigned total, wfa_t *wfa, bitfile_t *input);
+static unsigned
+decode_nd_tree (wfa_t *wfa, bitfile_t *input);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+void
+read_nd (wfa_t *wfa, bitfile_t *input)
+/*
+ *  Read transitions of the nondetermistic 'wfa' part from 'input' stream.
+ *  ND is used only at levels {'wfa->p_min_level', ... , 'wfa->p_max_level'}.
+ *
+ *  Side effects:
+ *	'wfa->into' and 'wfa->weights' are filled with the decoded values
+ */
+{
+   unsigned total = decode_nd_tree (wfa, input);
+   
+   if (total > 0)
+      decode_nd_coefficients (total, wfa, input);
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static unsigned
+decode_nd_tree (wfa_t *wfa, bitfile_t *input)
+/*
+ *  Read 'wfa' prediction tree of given 'input' stream.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'wfa->into' is filled with the decoded values
+ */
+{
+   lqueue_t *queue;			/* queue of states */
+   int       next, state;		/* state and its current child */
+   unsigned  total = 0;			/* total number of predicted states */
+   u_word_t  sum0, sum1;		/* Probability model */
+   u_word_t  code;			/* The present input code value */
+   u_word_t  low;			/* Start of the current code range */
+   u_word_t  high;			/* End of the current code range */
+
+   /*
+    *  Initialize arithmetic decoder
+    */
+   code = get_bits (input, 16);
+   low  = 0;
+   high = 0xffff;
+   sum0 = 1;
+   sum1 = 11;
+
+   queue = alloc_queue (sizeof (int));
+   state = wfa->root_state;
+   queue_append (queue, &state);
+
+   /*
+    *  Traverse the WFA tree in breadth first order (using a queue).
+    */
+   while (queue_remove (queue, &next))
+   {
+      unsigned label;
+
+      if (wfa->level_of_state [next] > wfa->wfainfo->p_max_level + 1) 
+      {
+	 /*
+	  *  Nondetermismn is not allowed at levels larger than
+	  *  'wfa->wfainfo->p_max_level'.
+	  */
+	 for (label = 0; label < MAXLABELS; label++)
+	    if (ischild (state = wfa->tree [next][label]))
+	       queue_append (queue, &state); /* continue with childs */
+      }
+      else if (wfa->level_of_state [next] > wfa->wfainfo->p_min_level)
+      {
+	 for (label = 0; label < MAXLABELS; label++)
+	    if (ischild (state = wfa->tree [next][label]))
+	    {
+	       unsigned count;		/* Current interval count */
+	       unsigned range;		/* Current interval range */
+	       
+	       count = (((code - low) + 1) * sum1 - 1) / ((high - low) + 1);
+	       if (count < sum0)
+	       {
+		  /*
+		   *  Decode a '0' symbol
+		   *  First, the range is expanded to account for the
+		   *  symbol removal.
+		   */
+		  range = (high - low) + 1;
+		  high = low + (u_word_t) ((range * sum0) / sum1 - 1 );
+		  RESCALE_INPUT_INTERVAL;
+		  /*
+		   *  Update the frequency counts
+		   */
+		  sum0++;
+		  sum1++;
+		  if (sum1 > 50) /* scale the symbol frequencies */
+		  {
+		     sum0 >>= 1;
+		     sum1 >>= 1;
+		     if (!sum0)
+			sum0 = 1;
+		     if (sum0 >= sum1)
+			sum1 = sum0 + 1;
+		  }
+		  if (wfa->level_of_state [state] > wfa->wfainfo->p_min_level)
+		     queue_append (queue, &state);
+	       }
+	       else
+	       {
+		  /*
+		   *  Decode a '1' symbol
+		   *  First, the range is expanded to account for the
+		   *  symbol removal.
+		   */
+		  range = (high - low) + 1;
+		  high = low + (u_word_t) ((range * sum1) / sum1 - 1);
+		  low  = low + (u_word_t) ((range * sum0) / sum1);
+		  RESCALE_INPUT_INTERVAL;
+		  /*
+		   *  Update the frequency counts
+		   */
+		  sum1++;
+		  if (sum1 > 50) /* scale the symbol frequencies */
+		  {
+		     sum0 >>= 1;
+		     sum1 >>= 1;
+		     if (!sum0)
+			sum0 = 1;
+		     if (sum0 >= sum1)
+			sum1 = sum0 + 1;
+		  }
+		  append_edge (next, 0, -1, label, wfa);
+		  total++;
+	       }
+	    }
+      }
+   }
+   free_queue (queue);
+
+   INPUT_BYTE_ALIGN (input);
+
+   return total;
+}
+
+static void
+decode_nd_coefficients (unsigned total, wfa_t *wfa, bitfile_t *input)
+/*
+ *  Read #'total' weights of nondeterministic part of 'wfa'  
+ *  of given 'input' stream.
+ *  'frame' gives the current frame number.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'wfa->weights' is filled with the decoded values.
+ */
+{
+   unsigned *coefficients;		/* array of factors to encode */
+   unsigned *ptr;			/* pointer to current factor */
+   
+   /*
+    *  Decode array of coefficients stored with arithmetic coding
+    */
+   {
+      const int	scaling  = 50;		/* scaling factor of prob. model */
+      unsigned  c_symbols = 1 << (wfa->wfainfo->dc_rpf->mantissa_bits + 1);
+      
+      ptr = coefficients = decode_array (input, NULL, &c_symbols, 1,
+					 total, scaling);
+   }
+   
+   /*
+    *  Fill 'wfa->weights' with decoded coefficients
+    */
+   {
+      unsigned state, label;
+      
+      for (state = wfa->basis_states; state < wfa->states; state++)
+	 for (label = 0; label < MAXLABELS; label++)
+	    if (ischild (wfa->tree [state][label])
+		&& isedge (wfa->into [state][label][0]))
+	    {
+	       wfa->weight [state][label][0] = btor (*ptr++,
+						     wfa->wfainfo->dc_rpf);
+	       wfa->int_weight [state][label][0]
+		  = wfa->weight [state][label][0] * 512 + 0.5;
+	    }
+   }
+   Free (coefficients);
+}
diff --git a/converter/other/fiasco/input/nd.h b/converter/other/fiasco/input/nd.h
new file mode 100644
index 00000000..2c2fff4b
--- /dev/null
+++ b/converter/other/fiasco/input/nd.h
@@ -0,0 +1,28 @@
+/*
+ *  nd.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:13 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _ND_H
+#define _ND_H
+
+#include "wfa.h"
+#include "rpf.h"
+#include "bit-io.h"
+
+void
+read_nd (wfa_t *wfa, bitfile_t *input);
+
+#endif /* not _ND_H */
+
diff --git a/converter/other/fiasco/input/read.c b/converter/other/fiasco/input/read.c
new file mode 100644
index 00000000..26bae7e4
--- /dev/null
+++ b/converter/other/fiasco/input/read.c
@@ -0,0 +1,499 @@
+/*
+ *  read.c:		Input of WFA files
+ *
+ *  Written by:		Ullrich Hafner
+ *  
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/07/18 15:44:58 $
+ *  $Author: hafner $
+ *  $Revision: 5.4 $
+ *  $State: Exp $
+ */
+
+#define _BSD_SOURCE 1   /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include "config.h"
+
+#include <stdio.h>
+
+#include <string.h>
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "wfa.h"
+#include "misc.h"
+#include "rpf.h"
+#include "bit-io.h"
+#include "wfalib.h"
+
+#include "tree.h"
+#include "matrices.h"
+#include "weights.h"
+#include "nd.h"
+#include "mc.h"
+#include "basis.h"
+#include "read.h"
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static void
+read_tiling (tiling_t *tiling, unsigned image_width, unsigned image_height,
+	     unsigned image_level, bitfile_t *input);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+bitfile_t *
+open_wfa (const char *filename, wfa_info_t *wi)
+/*
+ *  Open WFA file 'filename' and read header information.
+ *
+ *  Return value:
+ *	Pointer to input stream (fileposition: first WFA frame)
+ *
+ *  Side effects:
+ *	The values of the header of 'filename' are copied to 'wfainfo'. 
+ *
+ */
+{
+   bitfile_t *input;			/* pointer to WFA bitfile */
+   
+   assert (filename && wi);
+   
+   wi->wfa_name = strdup (filename);
+
+   /*
+    *  Check whether 'filename' is a regular WFA file
+    */
+   {
+      unsigned 	n;
+      const char     *str;
+      
+      if (!(input = open_bitfile (filename, "FIASCO_DATA", READ_ACCESS)))
+          file_error (filename);
+   
+      for (str = FIASCO_MAGIC, n = strlen (FIASCO_MAGIC); n; n--)
+          if (get_bits (input, 8) != (unsigned) *str++)
+              error ("Input file %s is not a valid FIASCO file!", filename);
+      get_bits (input, 8);		/* fetch newline */
+   }
+   
+   /*
+    *  Read WFA header information
+    */
+   {
+      char	      basis_name [MAXSTRLEN]; /* temp. buffer */
+      const unsigned  rice_k = 8; 	/* parameter of Rice Code */
+      char     	     *str    = basis_name;
+      
+      while ((*str++ = get_bits (input, 8)) != 0
+	     && str < basis_name + MAXSTRLEN)
+	 ;
+      if (str == basis_name + MAXSTRLEN)
+	 error ("Input file %s is not a valid FIASCO file!", filename);
+      
+      {
+	 wi->release = read_rice_code (rice_k, input);
+
+	 if (wi->release > FIASCO_BINFILE_RELEASE)
+	    error ("Can't decode FIASCO files of file format release `%d'."
+		   "\nCurrent file format release is `%d'.", wi->release,
+		   FIASCO_BINFILE_RELEASE);
+      }
+
+      if (wi->release > 1)
+      {
+	 header_type_e type;
+	 
+	 while ((type = read_rice_code (rice_k, input)) != HEADER_END)
+	 {
+	    char     buffer [MAXSTRLEN];
+	    unsigned n = 0;
+	    
+	    switch (type)
+	    {
+	       case HEADER_TITLE:
+		  while ((buffer [n++] = get_bits (input, 8)))
+		     ;
+		  wi->title = strdup (buffer);
+		  break;
+	       case HEADER_COMMENT:
+		  while ((buffer [n++] = get_bits (input, 8)))
+		     ;
+		  wi->comment = strdup (buffer);
+		  break;
+	       default:			/* should not happen */
+		  break;
+	    }
+	 }
+      }
+
+      wi->basis_name = strdup (basis_name);
+      wi->max_states = read_rice_code (rice_k, input);
+      wi->color      = get_bit (input) ? YES : NO;
+      wi->width      = read_rice_code (rice_k, input);
+      wi->height     = read_rice_code (rice_k, input);
+
+      /*
+       *  Compute bintree level
+       */
+      {
+	 unsigned lx = log2 (wi->width - 1) + 1;
+	 unsigned ly = log2 (wi->height - 1) + 1;
+      
+	 wi->level = max (lx, ly) * 2 - ((ly == lx + 1) ? 1 : 0);
+      }
+      wi->chroma_max_states = wi->color ? read_rice_code (rice_k, input) : -1;
+      wi->p_min_level       = read_rice_code (rice_k, input);
+      wi->p_max_level       = read_rice_code (rice_k, input);
+      wi->frames            = read_rice_code (rice_k, input);
+      wi->smoothing	    = read_rice_code (rice_k, input);
+
+      /*
+       *  Read RPF models from disk
+       */
+      {
+	 unsigned 	    mantissa;
+	 fiasco_rpf_range_e range;
+
+	 mantissa = get_bits (input, 3) + 2;
+	 range    = get_bits (input, 2);
+	 wi->rpf  = alloc_rpf (mantissa, range);
+	 
+	 if (get_bit (input))		/* different DC model */
+	 {
+	    mantissa   = get_bits (input, 3) + 2;
+	    range      = get_bits (input, 2);
+	    wi->dc_rpf = alloc_rpf (mantissa, range);
+	 }
+	 else				/* use same model for DC coefficents */
+	    wi->dc_rpf = alloc_rpf (wi->rpf->mantissa_bits,
+				    wi->rpf->range_e);
+
+	 if (get_bit (input))		/* different delta model */
+	 {
+	    mantissa  = get_bits (input, 3) + 2;
+	    range     = get_bits (input, 2);
+	    wi->d_rpf = alloc_rpf (mantissa, range);
+	 }
+	 else
+	    wi->d_rpf = alloc_rpf (wi->rpf->mantissa_bits,
+				   wi->rpf->range_e);
+	 
+	 if (get_bit (input))		/* different DC delta model */
+	 {
+	    mantissa  	 = get_bits (input, 3) + 2;
+	    range     	 = get_bits (input, 2);
+	    wi->d_dc_rpf = alloc_rpf (mantissa, range);
+	 }
+	 else
+	    wi->d_dc_rpf = alloc_rpf (wi->dc_rpf->mantissa_bits,
+				      wi->dc_rpf->range_e);
+      }
+
+      if (wi->frames > 1)		/* motion compensation stuff */
+      {
+	 wi->fps           = read_rice_code (rice_k, input);
+	 wi->search_range  = read_rice_code (rice_k, input);
+	 wi->half_pixel    = get_bit (input) ? YES : NO;
+	 wi->B_as_past_ref = get_bit (input) ? YES : NO;
+      }
+   }
+   
+   INPUT_BYTE_ALIGN (input);
+
+   return input;
+}
+
+void
+read_basis (const char *filename, wfa_t *wfa)
+/*
+ *  Read WFA initial basis 'filename' and fill 'wfa' struct.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	wfa->into, wfa->weights, wfa->final_distribution, wfa->basis_states
+ *	wfa->domain_type wfa->wfainfo->basis_name, are filled with the
+ *	values of the WFA basis.
+ */
+{
+   FILE	*input;				/* ASCII WFA initial basis file */
+
+   assert (filename && wfa);
+
+   if (!wfa->wfainfo->basis_name ||
+       !streq (wfa->wfainfo->basis_name, filename))
+   {
+      if (wfa->wfainfo->basis_name)
+	 Free (wfa->wfainfo->basis_name);
+      wfa->wfainfo->basis_name = strdup (filename);
+   }
+   
+   if (get_linked_basis (filename, wfa))
+      return;				/* basis is linked with excecutable */
+   
+   /*
+    *  Check whether 'wfa_name' is a regular ASCII WFA initial basis file
+    */
+   {
+      char magic [MAXSTRLEN];		/* WFA magic number */
+
+      if (!(input = open_file (filename, "FIASCO_DATA", READ_ACCESS)))
+	 file_error(filename);
+      
+      if (fscanf (input, MAXSTRLEN_SCANF, magic) != 1)
+	 error ("Format error: ASCII FIASCO initial basis file %s", filename);
+      else if (strneq (FIASCO_BASIS_MAGIC, magic))
+	 error ("Input file %s is not an ASCII FIASCO initial basis!",
+		filename);
+   }
+   
+   /*
+    *  WFA ASCII format:
+    *
+    *  Note: State 0 is assumed to be the constant function f(x, y) = 128.
+    *        Don't define any transitions of state 0 in an initial basis. 
+    *
+    *  Header:
+    *   type		|description
+    *	----------------+-----------
+    *   string		|MAGIC Number "Wfa"
+    *	int		|Number of basis states 'N'
+    *	bool_t-array[N]	|use vector in linear combinations,
+    *			|0: don't use vector (auxilliary state)
+    *			|1: use vector in linear combinations
+    *	float-array[N]	|final distribution of every state
+    *
+    *  Transitions:
+    *
+    *      <state 1>			current state
+    *      <label> <into> <weight>	transition 1 of current state
+    *      <label> <into> <weight>	transition 2 of current state
+    *      ...
+    *      <-1>				last transition marker
+    *      <state 2>
+    *      ...
+    *      <-1>				last transition marker
+    *      <state N>
+    *      ...
+    *
+    *      <-1>				last transition marker
+    *      <-1>				last state marker
+    */
+   {
+      unsigned state;
+
+      if (fscanf (input ,"%u", &wfa->basis_states) != 1)
+	 error ("Format error: ASCII FIASCO initial basis file %s", filename);
+
+      /*
+       *  State 0 is assumed to be the constant function f(x, y) = 128.
+       */
+      wfa->domain_type [0]        = USE_DOMAIN_MASK; 
+      wfa->final_distribution [0] = 128;
+      wfa->states 		  = wfa->basis_states;
+      wfa->basis_states++;
+
+      append_edge (0, 0, 1.0, 0, wfa);
+      append_edge (0, 0, 1.0, 1, wfa);
+   
+      for (state = 1; state < wfa->basis_states; state++)
+	 wfa->domain_type [state]
+	    = read_int (input) ? USE_DOMAIN_MASK : AUXILIARY_MASK;
+
+      for (state = 1; state < wfa->basis_states; state++)
+	 wfa->final_distribution[state] = read_real (input);
+
+      /*
+       *  Read transitions
+       */
+      for (state = 1; state < wfa->basis_states; state++)
+      {
+	 unsigned domain;
+	 int      label;
+	 real_t   weight;
+
+	 if (read_int (input) != (int) state)
+	    error ("Format error: ASCII FIASCO initial basis file %s",
+		   filename);
+
+	 while((label = read_int (input)) != -1)
+	 {
+	    domain = read_int (input);
+	    weight = read_real (input);
+	    append_edge (state, domain, weight, label, wfa);
+	 }
+      }
+   }
+   
+   fclose (input);
+}
+
+unsigned
+read_next_wfa (wfa_t *wfa, bitfile_t *input)
+/*
+ *  Read next WFA frame of the WFA stream 'input'.
+ *  WFA header information has to be already present in the 'wfainfo' struct.
+ *  (i.e. open_wfa must be called first!)
+ *  
+ *  No return value.
+ *
+ *  Side effects:
+ *	wfa->into, wfa->weights, wfa->final_distribution, wfa->states
+ *	wfa->x, wfa->y, wfa->level_of_state, wfa->domain_type
+ *      mt->type, mt->number are filled with the values of the WFA file.
+ */
+{
+   tiling_t tiling;			/* tiling information */
+   unsigned frame_number;		/* current frame number */
+   
+   assert (wfa && input);
+   
+   /*
+    *  Frame header information
+    */
+   {
+      const unsigned rice_k = 8;	/* parameter of Rice Code */
+
+      wfa->states     = read_rice_code (rice_k, input);
+      wfa->frame_type = read_rice_code (rice_k, input);
+      frame_number    = read_rice_code (rice_k, input);
+   }
+
+   if (wfa->wfainfo->release > 1)	/* no alignment in version 1 */
+   {
+      INPUT_BYTE_ALIGN (input);
+   }
+   
+   /*
+    *  Read image tiling info 
+    */
+   if (get_bit (input))			/* tiling performed ? */
+      read_tiling (&tiling, wfa->wfainfo->width, wfa->wfainfo->height,
+		   wfa->wfainfo->level, input);
+   else
+      tiling.exponent = 0;
+   
+   INPUT_BYTE_ALIGN (input);
+
+   read_tree (wfa, &tiling, input);
+
+   /*
+    *  Compute domain pool.
+    *  Large images have not been used due to image tiling.
+    */
+   {
+      unsigned state;
+   
+      for (state = wfa->basis_states; state < wfa->states; state++)
+	 if ((!wfa->wfainfo->color
+	      || (int) state <= wfa->tree [wfa->tree [wfa->root_state][0]][0])
+	     &&
+	     (!tiling.exponent ||
+	      wfa->level_of_state [state] <= (wfa->wfainfo->level
+					      - tiling.exponent))
+	     && ((wfa->x [state][0]
+		 + width_of_level (wfa->level_of_state [state]))
+		 <= wfa->wfainfo->width)
+	     && ((wfa->y [state][0]
+		 + height_of_level (wfa->level_of_state [state]))
+		 <= wfa->wfainfo->height))
+	    wfa->domain_type [state] = USE_DOMAIN_MASK;
+	 else
+	    wfa->domain_type [state] = 0;
+   }
+   
+   if (tiling.exponent)
+      Free (tiling.vorder);
+
+   if (get_bit (input))			/* nondeterministic prediction used */
+      read_nd (wfa, input);
+
+   if (wfa->frame_type != I_FRAME)	/* motion compensation used */
+      read_mc (wfa->frame_type, wfa, input);
+
+   locate_delta_images (wfa);
+   
+   /*
+    *  Read linear combinations (coefficients and indices)
+    */
+   {
+      unsigned edges = read_matrices (wfa, input); 
+
+      if (edges)
+	 read_weights (edges, wfa, input);
+   }
+
+   /*
+    *  Compute final distribution of all states
+    */
+   {
+      unsigned state;
+   
+      for (state = wfa->basis_states; state <= wfa->states; state++)
+	 wfa->final_distribution[state]
+	    = compute_final_distribution (state, wfa);
+   }
+
+   return frame_number;
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static void
+read_tiling (tiling_t *tiling, unsigned image_width, unsigned image_height,
+	     unsigned image_level, bitfile_t *input)
+/*
+ *  Read image tiling information from the given file 'input'
+ *  and store parameters in struct 'tiling'.
+ *  
+ *  No return value.
+ */
+{
+   const unsigned rice_k = 8;		/* parameter of Rice Code */
+   
+   tiling->exponent = read_rice_code (rice_k, input);
+   
+   if (get_bit (input))			/* variance order */
+   {
+      unsigned tile;			/* current image tile */
+      unsigned x0, y0;			/* NW corner of image tile */
+      unsigned width, height;		/* size of image tile */
+
+      tiling->vorder = Calloc (1 << tiling->exponent, sizeof (int));
+      for (tile = 0; tile <  1U << tiling->exponent; tile++)
+      {
+	 locate_subimage (image_level, image_level - tiling->exponent, tile,
+			  &x0, &y0, &width, &height);
+	 if (x0 < image_width && y0 < image_height) 
+	    tiling->vorder [tile] = get_bits (input, tiling->exponent);
+	 else
+	    tiling->vorder [tile] = -1;
+      }
+   }
+   else					/* spiral order */
+   {
+      tiling->vorder = Calloc (1 << tiling->exponent, sizeof (int));
+      compute_spiral (tiling->vorder, image_width, image_height,
+		      tiling->exponent, get_bit (input) ? YES : NO);
+   }
+}
diff --git a/converter/other/fiasco/input/read.h b/converter/other/fiasco/input/read.h
new file mode 100644
index 00000000..d0d0ee13
--- /dev/null
+++ b/converter/other/fiasco/input/read.h
@@ -0,0 +1,31 @@
+/*
+ *  read.h
+ *
+ *  Written by:		Ullrich Hafner
+ *  
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:13 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _READ_H
+#define _READ_H
+
+#include "wfa.h"
+#include "bit-io.h"
+
+bitfile_t *
+open_wfa (const char *filename, wfa_info_t *wfainfo);
+void
+read_basis (const char *filename, wfa_t *wfa);
+unsigned
+read_next_wfa (wfa_t *wfa, bitfile_t *input);
+
+#endif /* not _READ_H */
+
diff --git a/converter/other/fiasco/input/tree.c b/converter/other/fiasco/input/tree.c
new file mode 100644
index 00000000..e3e7117e
--- /dev/null
+++ b/converter/other/fiasco/input/tree.c
@@ -0,0 +1,303 @@
+/*
+ *  tree.c:		Input of bintree partitioning
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:13 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "bit-io.h"
+#include "arith.h"
+#include "misc.h"
+#include "wfalib.h"
+#include "tiling.h"
+
+#include "tree.h"
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static unsigned
+restore_depth_first_order (unsigned src_state, unsigned level, unsigned x,
+			   unsigned y, unsigned *dst_state,
+			   word_t (*bfo_tree)[MAXLABELS],
+			   wfa_t *wfa, tiling_t *tiling);
+static void 
+decode_tree (bitfile_t *input, byte_t *data, unsigned n_data, unsigned scaling,
+	     u_word_t sum0, u_word_t sum1);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+void
+read_tree (wfa_t *wfa, tiling_t *tiling, bitfile_t *input)
+/*
+ *  Read bintree partitioning of WFA from the 'input' stream.
+ *  'tiling' provides the information about image tiling, if applied.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'wfa->tree', 'wfa->x', 'wfa->y', 'wfa->level_of_state'
+ *      are filled with decoded values.
+ */
+{
+   byte_t *bitstring;			/* the encoded data */
+   word_t (*bfo_tree)[MAXLABELS];	/* node numbers in BFO */
+      
+   /*
+    *  Read WFA tree stored in breadth first order
+    */
+   {
+      unsigned total = (wfa->states - wfa->basis_states) * MAXLABELS;
+      unsigned scale = total / 20;
+
+      bitstring = Calloc (total, sizeof (byte_t));
+      decode_tree (input, bitstring, total, scale, 1, 11);
+   }
+   
+   /*
+    *  Generate tree using a breadth first traversal
+    */
+   {
+      unsigned 	next;			/* next free node number of the tree */
+      unsigned 	state;
+      unsigned 	label;
+      byte_t   *buffer = bitstring;	/* pointer to decoded data */
+      
+      bfo_tree = Calloc (wfa->states * MAXLABELS, sizeof (word_t));
+      for (state = 0, next = 1; state < next; state++)
+	 for (label = 0; label < MAXLABELS; label++)
+	    bfo_tree [state][label] = *buffer++ ? next++ : RANGE;
+   }
+
+   /*
+    *  Traverse tree and restore depth first order
+    */
+   {
+      unsigned dst_state = wfa->basis_states;
+
+      wfa->root_state
+	 = restore_depth_first_order (0, (wfa->wfainfo->level
+					  + (wfa->wfainfo->color ? 2 : 0)),
+				      0, 0, &dst_state, bfo_tree, wfa, tiling);
+   }
+
+   Free (bitstring);
+   Free (bfo_tree);
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static unsigned
+restore_depth_first_order (unsigned src_state, unsigned level, unsigned x,
+			   unsigned y, unsigned *dst_state,
+			   word_t (*bfo_tree)[MAXLABELS],
+			   wfa_t *wfa, tiling_t *tiling)
+/*
+ *  Map state 'src_state' (breadth first order) 
+ *  to state '*dst_state' (depth first order)
+ *  Add a tree edge 'state' --> 'child' with label and weight 1.0
+ *  if required.
+ *  'x', 'y' give the coordinates of the current state in the 'color' image
+ *  of size 'image_level'. 'tiling' defines the image partitioning. 
+ *  
+ *  Return value:
+ *	new node number in depth first order
+ *
+ *  Side effects:
+ *	'wfa->tree', 'wfa->x', 'wfa->y', 'wfa->level_of_state'
+ *      are filled with decoded values.
+ */
+{
+   unsigned newx [MAXLABELS];		/* x coordinate of childs */
+   unsigned newy [MAXLABELS];		/* y coordinate of childs */
+   unsigned x0, y0;			/* NW corner of image tile */
+   unsigned width, height;		/* size of image tile */
+
+   /*
+    *  If tiling is performed then replace current coordinates
+    */
+   if (tiling->exponent && level == wfa->wfainfo->level - tiling->exponent)
+   {
+      unsigned tile;
+      
+      for (tile = 0; tile < 1U << tiling->exponent; tile++)
+      {
+	 locate_subimage (wfa->wfainfo->level, level, tile,
+			  &x0, &y0, &width, &height);
+	 if (x0 == x && y0 == y) /* matched ! */
+	 {
+	    locate_subimage (wfa->wfainfo->level, level, tiling->vorder[tile],
+			     &x, &y, &width, &height);
+	    break;
+	 }
+      }
+   }
+   /*
+    *  Coordinates of childs 0 and 1
+    */
+   if (wfa->wfainfo->color && level == wfa->wfainfo->level + 1)
+      newx[0] = newy[0] = newx[1] = newy[1] = 0;
+   else
+   {
+      newx[0] = x;
+      newy[0] = y;
+      newx[1] = level & 1 ? x : x + width_of_level (level - 1);
+      newy[1] = level & 1 ? y + height_of_level (level - 1) : y;
+   }
+   
+   /*
+    *  Remap node numbers
+    */
+   {
+      int      child [MAXLABELS];	/* childs of current node (state) */
+      int      domain;			/* current domain */
+      unsigned label;
+
+      for (label = 0; label < MAXLABELS; label++)
+	 if (!isrange (domain = bfo_tree [src_state][label]))
+	    child [label] = restore_depth_first_order (domain, level - 1,
+						       newx [label],
+						       newy [label], dst_state,
+						       bfo_tree, wfa, tiling);
+	 else
+	    child [label] = RANGE;
+
+      for (label = 0; label < MAXLABELS; label++)
+      {
+	 wfa->tree [*dst_state][label] = child [label];
+	 wfa->x [*dst_state][label]    = newx [label];
+	 wfa->y [*dst_state][label]    = newy [label];
+      }
+      wfa->level_of_state [*dst_state] = level;
+   }
+   
+   return (*dst_state)++;
+}	
+
+/****************************************************************************
+
+                 Binary adaptive arithmetic compression
+ 
+****************************************************************************/
+
+static void 
+decode_tree (bitfile_t *input, byte_t *data, unsigned n_data, unsigned scaling,
+	     u_word_t sum0, u_word_t sum1)
+/*
+ *  Decode bintree partitioning using adaptive binary arithmetic decoding.
+ *  'input'	input stream,
+ *  'data'	buffer for decoded szmbols,
+ *  'n_data'	number of symbols to decode,
+ *  'scaling'	rescale probability models if range > 'scaling'
+ *  'sum0'	initial totals of symbol '0'
+ *  'sum1'	initial totals of symbol '1'
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'data []' is filled with the decoded bitstring
+ */
+{
+   u_word_t code;			/* The present input code value */
+   u_word_t low;			/* Start of the current code range */
+   u_word_t high;			/* End of the current code range */
+   unsigned n;				/* Data counter */
+
+   assert (data);
+
+   code = get_bits (input, 16);
+   low  = 0;
+   high = 0xffff;
+
+   for (n = n_data; n; n--) 
+   {
+      unsigned count;			/* Current interval count */
+      unsigned range;			/* Current interval range */
+      
+      count = (((code - low) + 1) * sum1 - 1) / ((high - low) + 1);
+      if (count < sum0)
+      {
+	 /*
+	  *  Decode a '0' symbol
+	  *  First, the range is expanded to account for the symbol removal.
+	  */
+	 range = (high - low) + 1;
+	 high = low + (u_word_t) ((range * sum0) / sum1 - 1 );
+
+	 RESCALE_INPUT_INTERVAL;
+
+	 *data++ = 0;
+	 /*
+	  *  Update the frequency counts
+	  */
+	 sum0++;
+	 sum1++;
+	 if (sum1 > scaling) /* scale the symbol frequencies */
+	 {
+	    sum0 >>= 1;
+	    sum1 >>= 1;
+	    if (!sum0)
+	       sum0 = 1;
+	    if (sum0 >= sum1)
+	       sum1 = sum0 + 1;
+	 }
+
+      }
+      else
+      {
+	 /*
+	  *  Decode a '1' symbol
+	  *  First, the range is expanded to account for the symbol removal.
+	  */
+	 range = (high - low) + 1;
+	 high = low + (u_word_t) ((range * sum1) / sum1 - 1);
+	 low  = low + (u_word_t) ((range * sum0) / sum1);
+
+	 RESCALE_INPUT_INTERVAL;
+
+	 *data++ = 1;
+	 /*
+	  *  Update the frequency counts
+	  */
+	 sum1++;
+	 if (sum1 > scaling) /* scale the symbol frequencies */
+	 {
+	    sum0 >>= 1;
+	    sum1 >>= 1;
+	    if (!sum0)
+	       sum0 = 1;
+	    if (sum0 >= sum1)
+	       sum1 = sum0 + 1;
+	 }
+      }
+   }
+   INPUT_BYTE_ALIGN (input);
+}
+
+
diff --git a/converter/other/fiasco/input/tree.h b/converter/other/fiasco/input/tree.h
new file mode 100644
index 00000000..e4b5f2d8
--- /dev/null
+++ b/converter/other/fiasco/input/tree.h
@@ -0,0 +1,28 @@
+/*
+ *  tree.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:13 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _TREE_H
+#define _TREE_H
+
+#include "wfa.h"
+#include "bit-io.h"
+#include "tiling.h"
+
+void
+read_tree (wfa_t *wfa, tiling_t *tiling, bitfile_t *input);
+
+#endif /* not _TREE_H */
+
diff --git a/converter/other/fiasco/input/weights.c b/converter/other/fiasco/input/weights.c
new file mode 100644
index 00000000..55339980
--- /dev/null
+++ b/converter/other/fiasco/input/weights.c
@@ -0,0 +1,200 @@
+/*
+ *  weights.c:		Input of weights
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:13 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "bit-io.h"
+#include "arith.h"
+#include "rpf.h"
+#include "misc.h"
+
+#include "weights.h"
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+void
+read_weights (unsigned total, wfa_t *wfa, bitfile_t *input)
+/*
+ *  Read #'total' weights from input stream 'input' and
+ *  update transitions of the WFA states with corresponding weights.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'wfa->weights' are filled with the decoded values
+ */
+{
+   unsigned	    state;
+   unsigned	    label;
+   unsigned	    edge;		/* current edge */
+   unsigned	   *weights_array;	/* array of weights to encode */
+   unsigned	   *level_array;	/* array of corresponding levels */
+   unsigned	    offset1, offset2;	/* prob. model offsets. */
+   unsigned	    offset3, offset4;	/* prob. model offsets. */
+   bool_t	    delta_approx = NO; 	/* true if delta has been used */
+   
+   /*
+    *  Check whether delta approximation has been used
+    */
+   for (state = wfa->basis_states; state < wfa->states; state++)
+      if (wfa->delta_state [state])
+      {
+	 delta_approx = YES;
+	 break;
+      }
+  
+   /*
+    *  Generate array of corresponding levels (context of probability model)
+    */
+   {
+      int 	min_level, max_level; 	/* min and max range level */
+      int 	d_min_level, d_max_level; /* min and max range level (delta) */
+      unsigned *lptr;			/* pointer to current corresp. level */
+      int	domain;			/* current domain */
+      bool_t	dc, d_dc;		/* indicates whether DC is used */
+
+      /*
+       *  Compute minimum and maximum level of delta and normal approximations
+       */
+      min_level = d_min_level = MAXLEVEL;
+      max_level = d_max_level = 0;
+      dc 	= d_dc	   = NO;
+   
+      for (state = wfa->basis_states; state < wfa->states; state++)
+	 for (label = 0; label < MAXLABELS; label++)
+	    if (isrange (wfa->tree [state][label]))
+	    {
+	       if (delta_approx && wfa->delta_state [state])
+	       {
+		  d_min_level = min (d_min_level,
+				     wfa->level_of_state [state] - 1);
+		  d_max_level = max (d_max_level,
+				     wfa->level_of_state [state] - 1);
+		  if (wfa->into [state][label][0] == 0)
+		     d_dc = YES;
+	       }
+	       else
+	       {
+		  min_level = min (min_level, wfa->level_of_state [state] - 1);
+		  max_level = max (max_level, wfa->level_of_state [state] - 1);
+		  if (wfa->into [state][label][0] == 0)
+		     dc = YES;
+	       }
+	    }
+      if (min_level > max_level)		/* no lc found */
+	 max_level = min_level - 1;
+      if (d_min_level > d_max_level)
+	 d_max_level = d_min_level - 1;
+
+      offset1 = dc ? 1 : 0;
+      offset2 = offset1 + (d_dc ? 1 : 0);
+      offset3 = offset2 + (max_level - min_level + 1);
+      offset4 = offset3 + (d_max_level - d_min_level + 1);
+
+      lptr = level_array = Calloc (total, sizeof (int));
+      for (state = wfa->basis_states; state < wfa->states; state++)
+	 for (label = 0; label < MAXLABELS; label++)
+	    if (isrange (wfa->tree[state][label]))
+	       for (edge = 0; isedge (domain = wfa->into[state][label][edge]);
+		    edge++)
+	       {
+		  if ((unsigned) (lptr - level_array) >= total)
+		     error ("Can't read more than %d weights.", total);
+		  if (domain)
+		  {
+		     if (delta_approx && wfa->delta_state [state])
+			*lptr++ = offset3 + wfa->level_of_state [state]
+				  - 1 - d_min_level;
+		     else
+			*lptr++ = offset2 + wfa->level_of_state [state]
+				  - 1 - min_level;
+		  }
+		  else
+		     *lptr++ = delta_approx && wfa->delta_state [state]
+			       ? offset1 : 0;
+	       }
+   }
+
+   /*
+    *  Decode the list of weights with an arithmetic decoder
+    */
+   {
+      unsigned	      i;
+      unsigned	     *c_symbols = Calloc (offset4, sizeof (unsigned));
+      const unsigned  scale 	= 500; 	/* scaling of probability model */
+
+      c_symbols [0] = 1 << (wfa->wfainfo->dc_rpf->mantissa_bits + 1);
+      if (offset1 != offset2)
+	 c_symbols [offset1] = 1 << (wfa->wfainfo->d_dc_rpf->mantissa_bits
+				     + 1);
+      for (i = offset2; i < offset3; i++)
+	 c_symbols [i] = 1 << (wfa->wfainfo->rpf->mantissa_bits + 1);
+      for (; i < offset4; i++)
+	 c_symbols [i] = 1 << (wfa->wfainfo->d_rpf->mantissa_bits + 1);
+      
+      weights_array = decode_array (input, level_array, c_symbols,
+				    offset4, total, scale);
+      Free (c_symbols);
+   }
+   Free (level_array);
+
+   /*
+    *  Update transitions with decoded weights
+    */
+   {
+      unsigned *wptr = weights_array;	/* pointer to current weight */
+      int	domain;			/* current domain */
+
+      for (state = wfa->basis_states; state < wfa->states; state++)
+	 for (label = 0; label < MAXLABELS; label++)
+	    if (isrange (wfa->tree[state][label]))
+	       for (edge = 0; isedge (domain = wfa->into[state][label][edge]);
+		    edge++)
+	       {
+		  if (domain)		/* not DC component */
+		  {
+		     if (delta_approx && wfa->delta_state [state])
+			wfa->weight [state][label][edge]
+			   = btor (*wptr++, wfa->wfainfo->d_rpf);
+		     else
+			wfa->weight [state][label][edge]
+			   = btor (*wptr++, wfa->wfainfo->rpf);
+		  }
+		  else
+		  {
+		     if (delta_approx && wfa->delta_state [state])
+			wfa->weight [state][label][edge]
+			   = btor (*wptr++, wfa->wfainfo->d_dc_rpf);
+		     else
+			wfa->weight [state][label][edge]
+			   = btor (*wptr++, wfa->wfainfo->dc_rpf);
+		  }
+		  wfa->int_weight [state][label][edge]
+		     = wfa->weight [state][label][edge] * 512 + 0.5;
+	       }
+   }
+   
+   Free (weights_array);
+}
+ 
diff --git a/converter/other/fiasco/input/weights.h b/converter/other/fiasco/input/weights.h
new file mode 100644
index 00000000..1e2285a9
--- /dev/null
+++ b/converter/other/fiasco/input/weights.h
@@ -0,0 +1,27 @@
+/*
+ *  weights.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:13 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _WEIGHTS_H
+#define _WEIGHTS_H
+
+#include "wfa.h"
+#include "bit-io.h"
+
+void
+read_weights (unsigned total, wfa_t *wfa, bitfile_t *input);
+
+#endif /* not _WEIGHTS_H */
+
diff --git a/converter/other/fiasco/lib/Makefile b/converter/other/fiasco/lib/Makefile
new file mode 100644
index 00000000..99d7c1d7
--- /dev/null
+++ b/converter/other/fiasco/lib/Makefile
@@ -0,0 +1,33 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../../..
+  BUILDDIR = $(SRCDIR)
+endif
+FIASCOSUBDIR = converter/other/fiasco
+SUBDIR = $(FIASCOSUBDIR)/lib
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+OBJECTS = \
+  arith.o \
+  bit-io.o \
+  dither.o \
+  error.o \
+  image.o \
+  list.o \
+  misc.o \
+  rpf.o \
+  mvcode.o \
+
+MERGE_OBJECTS = $(OBJECTS)
+
+INCLUDES = -I$(SRCDIR)/$(FIASCOSUBDIR)
+
+all: libfiasco_lib.a
+
+include $(SRCDIR)/Makefile.common
+
+libfiasco_lib.a: $(OBJECTS)
+	$(AR) -rc $@ $(OBJECTS)
+	$(RANLIB) $@
+
diff --git a/converter/other/fiasco/lib/arith.c b/converter/other/fiasco/lib/arith.c
new file mode 100644
index 00000000..e3745bf7
--- /dev/null
+++ b/converter/other/fiasco/lib/arith.c
@@ -0,0 +1,708 @@
+/*
+ *  arith.c:		Adaptive arithmetic coding and decoding
+ *
+ *  Written by:		Ullrich Hafner
+ *  
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:49:37 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "bit-io.h"
+#include "misc.h"
+#include "arith.h"
+
+/******************************************************************************
+
+				public code
+  
+******************************************************************************/
+
+arith_t *
+alloc_encoder (bitfile_t *output)
+/*
+ *  Arithmetic coder constructor:
+ *  Initialize the arithmetic coder.
+ *  
+ *  Return value:
+ *	A pointer to the new coder structure
+ */
+{
+   arith_t *arith = Calloc (1, sizeof (arith_t));
+
+   assert (output);
+   
+   arith->low       = LOW;
+   arith->high      = HIGH;
+   arith->underflow = 0;
+   arith->file 	    = output;
+
+   return arith;
+}
+
+void
+free_encoder (arith_t *arith)
+/*
+ *  Arithmetic encoder destructor.
+ *  Flush the arithmetic coder. Append all remaining bits to the
+ *  output stream. Append zero bits to get the output file byte aligned.
+ *  
+ *  No return value.
+ */
+{
+   u_word_t   low;			/* start of the current code range  */
+   u_word_t   high;			/* end of the current code range    */
+   u_word_t   underflow;		/* number of underflow bits pending */
+   bitfile_t *output;
+   
+   assert (arith);
+
+   low       = arith->low;
+   high      = arith->high;
+   underflow = arith->underflow;
+   output    = arith->file;
+   
+   low = high;
+
+   RESCALE_OUTPUT_INTERVAL;
+
+   OUTPUT_BYTE_ALIGN (output);
+
+   Free (arith);
+}
+
+real_t
+encode_symbol (unsigned symbol, arith_t *arith, model_t *model)
+/*
+ *  Encode the given 'symbol' using the given probability 'model'.
+ *  The current state of the arithmetic coder is given by 'arith'.
+ *  Output bits are appended to the stream 'output'.
+ *
+ *  The model is updated after encoding the symbol (if neccessary the
+ *  symbol counts are rescaled).
+ *  
+ *  Return value:
+ *	information content of the encoded symbol.
+ *
+ *  Side effects:
+ *	'model' is updated (probability distribution)
+ *	'arith' is updated (coder state)
+ */
+{
+   u_word_t   low_count;		/* lower bound of 'symbol' interval */
+   u_word_t   high_count;		/* upper bound of 'symbol' interval */
+   u_word_t   scale;			/* range of all 'm' symbol intervals */
+   unsigned   range;			/* range of current interval */
+   unsigned   index;			/* index of probability model */
+   u_word_t   low;			/* start of the current code range  */
+   u_word_t   high;			/* end of the current code range    */
+   u_word_t   underflow;		/* number of underflow bits pending */
+   bitfile_t *output;			/* output file */
+   
+   assert (model && arith);
+
+   /*
+    * Get interval values
+    */
+   low       = arith->low;
+   high      = arith->high;
+   underflow = arith->underflow;
+   output    = arith->file;
+
+   assert (high > low);
+   
+   if (model->order > 0)		/* order-'n' model*/
+   {
+      unsigned power;			/* multiplicator */
+      unsigned i;
+
+      /*
+       *  Compute index of the probability model to use.
+       *  See init_model() for more details.
+       */
+      power = 1;			/* multiplicator */
+      index = 0;			/* address of prob. model */
+	 
+      for (i = 0; i < model->order; i++) /* genarate a M-nary number */
+      {
+	 index += model->context [i] * power;	
+	 power *= model->symbols;
+      }
+
+      index *= model->symbols + 1;	/* we need space for M + 1 elements */
+
+      for (i = 0; i < model->order - 1; i++)
+	 model->context [i] = model->context [i + 1];
+      model->context [i] = symbol;
+   }
+   else
+      index = 0;
+
+   scale      = model->totals [index + model->symbols];
+   low_count  = model->totals [index + symbol];
+   high_count = model->totals [index + symbol + 1];
+
+   /*
+    *  Compute the new interval depending on the input 'symbol'.
+    */
+   range = (high - low) + 1;
+   high  = low + (u_word_t) ((range * high_count) / scale - 1);
+   low   = low + (u_word_t) ((range * low_count) / scale);
+   
+   RESCALE_OUTPUT_INTERVAL;
+   
+   if (model->scale > 0)		/* adaptive model */
+   {
+      unsigned i;
+
+      /*
+       *  Update probability model
+       */
+      for (i = symbol + 1; i <= model->symbols; i++)
+	 model->totals [index + i]++;
+      if (model->totals [index + model->symbols] > model->scale) /* scaling */
+      {
+	 for (i = 1; i <= model->symbols; i++)
+	 {
+	    model->totals [index + i] >>= 1;
+	    if (model->totals [index + i] <= model->totals [index + i - 1])
+	       model->totals [index + i] = model->totals [index + i - 1] + 1;
+	 }
+      }
+   }
+
+   /*
+    *  Store interval values
+    */
+   arith->low  	    = low;
+   arith->high 	    = high;
+   arith->underflow = underflow;
+   
+   return - log2 ((high_count - low_count) / (real_t) scale);
+}
+
+void
+encode_array (bitfile_t *output, const unsigned *data, const unsigned *context,
+	      const unsigned *c_symbols, unsigned n_context, unsigned n_data,
+	      unsigned scaling)
+/*
+ *  Arithmetic coding of #'n_data' symbols given in the array 'data'.
+ *  If 'n_context' > 1 then a number (context [n]) is assigned to every
+ *  data element n, specifying which context (i.e. number of symbols given by
+ *  c_symbols [context [n]] and adaptive probability model) must be used.
+ *  Rescale probability models if range > 'scaling'.
+ *
+ *  No return value.
+ */
+{
+   u_word_t **totals;			/* probability model */
+
+   if (!n_context)
+      n_context = 1;			/* always use one context */
+
+   assert (output && c_symbols && data);
+   assert (n_context == 1 || context);
+   
+   /*
+    *  Allocate probability models, start with uniform distribution
+    */
+   totals = Calloc (n_context, sizeof (u_word_t *));
+   {
+      unsigned c;
+      
+      for (c = 0; c < n_context; c++)
+      {
+	 unsigned i;
+      
+	 totals [c]    = Calloc (c_symbols [c] + 1, sizeof (u_word_t));
+	 totals [c][0] = 0;
+      
+	 for (i = 0; i < c_symbols [c]; i++)
+	    totals [c][i + 1] = totals [c][i] + 1;
+      }
+   }
+
+   /*
+    *  Encode array elements
+    */
+   {
+      u_word_t low  	 = 0;		/* Start of the current code range */
+      u_word_t high 	 = 0xffff;	/* End of the current code range */
+      u_word_t underflow = 0;		/* Number of underflow bits pending */
+      unsigned n;
+      
+      for (n = 0; n < n_data; n++)
+      {
+	 u_word_t low_count;		/* lower bound of 'symbol' interval */
+	 u_word_t high_count;		/* upper bound of 'symbol' interval */
+	 u_word_t scale;		/* range of all 'm' symbol intervals */
+	 unsigned range;		/* current range */
+	 int	  d;			/* current data symbol */
+	 int	  c;			/* context of current data symbol */
+
+	 d = data [n];
+	 c = n_context > 1 ? context [n] : 0; 
+      
+	 scale	    = totals [c][c_symbols [c]];
+	 low_count  = totals [c][d];
+	 high_count = totals [c][d + 1];
+
+	 /*
+	  * Rescale high and low for the new symbol.
+	  */
+	 range = (high - low) + 1;
+	 high  = low + (u_word_t) ((range * high_count) / scale - 1);
+	 low   = low + (u_word_t) ((range * low_count) / scale);
+	 RESCALE_OUTPUT_INTERVAL;
+      
+	 /*
+	  *  Update probability models
+	  */
+	 {
+	    unsigned i;
+
+	    for (i = d + 1; i < c_symbols [c] + 1; i++)
+	       totals [c][i]++;
+	 
+	    if (totals [c][c_symbols [c]] > scaling) /* scaling */
+	       for (i = 1; i < c_symbols [c] + 1; i++)
+	       {
+		  totals [c][i] >>= 1;
+		  if (totals [c][i] <= totals [c][i - 1])
+		     totals [c][i] = totals [c][i - 1] + 1;
+	       }
+	 }
+      }
+      /*
+       *  Flush arithmetic encoder
+       */
+      low = high;
+      RESCALE_OUTPUT_INTERVAL;
+      OUTPUT_BYTE_ALIGN (output);
+   }
+   
+   /*
+    *  Cleanup ...
+    */
+   {
+      unsigned c;
+      for (c = 0; c < n_context; c++)
+	 Free (totals [c]);
+      Free (totals);
+   }
+}
+
+arith_t *
+alloc_decoder (bitfile_t *input)
+/*
+ *  Arithmetic decoder constructor:
+ *  Initialize the arithmetic decoder with the first
+ *  16 input bits from the stream 'input'.
+ *  
+ *  Return value:
+ *	A pointer to the new decoder structure
+ */
+
+{
+   arith_t *arith = Calloc (1, sizeof (arith_t));
+   
+   assert (input);
+   
+   arith->low  = LOW;
+   arith->high = HIGH;
+   arith->code = get_bits (input, 16);
+   arith->file = input;
+
+   return arith;
+}
+
+void
+free_decoder (arith_t *arith)
+/*
+ *  Arithmetic decoder destructor:
+ *  Flush the arithmetic decoder, i.e., read bits to get the input
+ *  file byte aligned. 
+ *  
+ *  No return value.
+ *
+ *  Side effects:
+ *	structure 'arith' is discarded.
+ */
+{
+   assert (arith);
+
+   INPUT_BYTE_ALIGN (arith->file);
+
+   Free (arith);
+}
+
+unsigned
+decode_symbol (arith_t *arith, model_t *model)
+/*
+ *  Decode the next symbol - the state of the arithmetic decoder
+ *  is given in 'arith'. Read refinement bits from the stream 'input'
+ *  and use the given probability 'model'. Update the probability model after
+ *  deconding the symbol (if neccessary also rescale the symbol counts).
+ *  
+ *  Return value:
+ *	decoded symbol
+ *
+ *  Side effects:
+ *	'model' is updated (probability distribution)
+ *	'arith' is updated (decoder state)
+ */
+{
+   unsigned   range;			/* range of current interval */
+   unsigned   count;			/* value in the current interval */
+   unsigned   index;			/* index of probability model */
+   unsigned   symbol;			/* decoded symbol */
+   u_word_t   scale;			/* range of all 'm' symbol intervals */
+   u_word_t   low;			/* start of the current code range  */
+   u_word_t   high;			/* end of the current code range    */
+   u_word_t   code;			/* the present input code value */
+   bitfile_t *input;			/* input file */
+
+   assert (arith && model);
+   
+   /*
+    * Get interval values
+    */
+   low   = arith->low;
+   high  = arith->high;
+   code  = arith->code;
+   input = arith->file;
+
+   assert (high > low);
+   
+   if (model->order > 0)		/* order-'n' model */
+   {
+      unsigned power;			/* multiplicator */
+      unsigned i;
+      
+      /*
+       *  Compute index of the probability model to use.
+       *  See init_model() for more details.
+       */
+      power = 1;			/* multiplicator */
+      index = 0;			/* address of prob. model */
+	 
+      for (i = 0; i < model->order; i++) /* genarate a m-nary number */
+      {
+	 index += model->context[i] * power;	
+	 power *= model->symbols;
+      }
+
+      index *= model->symbols + 1;	/* we need space for m + 1 elements */
+   }
+   else
+      index = 0;
+
+   scale = model->totals [index + model->symbols];
+   range = (high - low) + 1;
+   count = ((code - low + 1) * scale - 1) / range;
+
+   for (symbol = model->symbols; count < model->totals [index + symbol];
+	symbol--)
+      ;
+
+   if (model->order > 0)		/* order-'n' model */
+   {
+      unsigned i;
+      
+      for (i = 0; i < model->order - 1; i++)
+	 model->context [i] = model->context [i + 1];
+      model->context [i] = symbol;
+   }
+
+   /*
+    *  Compute interval boundaries
+    */
+   {
+      u_word_t low_count;		/* lower bound of 'symbol' interval */
+      u_word_t high_count;		/* upper bound of 'symbol' interval */
+      
+      low_count  = model->totals [index + symbol];
+      high_count = model->totals [index + symbol + 1];
+      high       = low + (u_word_t) ((range * high_count) / scale - 1 );
+      low        = low + (u_word_t) ((range * low_count) / scale );
+   }
+   
+   RESCALE_INPUT_INTERVAL;
+   
+   if (model->scale > 0)		/* adaptive model */
+   {
+      unsigned i;
+
+      /*
+       *  Update probability model
+       */
+      for (i = symbol + 1; i <= model->symbols; i++)
+	 model->totals [index + i]++;
+      if (model->totals [index + model->symbols] > model->scale) /* scaling */
+      {
+	 for (i = 1; i <= model->symbols; i++)
+	 {
+	    model->totals [index + i] >>= 1;
+	    if (model->totals [index + i] <= model->totals [index + i - 1])
+	       model->totals [index + i] = model->totals [index + i - 1] + 1;
+	 }
+      }
+   }
+   
+   /*
+    *  Store interval values
+    */
+   arith->low  = low;
+   arith->high = high;
+   arith->code = code;
+
+   return symbol;
+}
+
+unsigned *
+decode_array (bitfile_t *input, const unsigned *context,
+	      const unsigned *c_symbols, unsigned n_context,
+	      unsigned n_data, unsigned scaling)
+/*
+ *  Arithmetic decoding of #'n_data' symbols.
+ *  If 'n_context' > 1 then a number (context [n]) is assigned to every
+ *  data element n, specifying which context (i.e. number of symbols given by
+ *  c_symbols [context [n]] and adaptive probability model) must be used.
+ *  Rescale probability models if range > 'scaling'.
+ *
+ *  Return value:
+ *	pointer to array containing the decoded symbols
+ */
+{
+   unsigned  *data;			/* array to store decoded symbols */
+   u_word_t **totals;			/* probability model */
+   
+   if (n_context < 1)
+      n_context = 1;			/* always use one context */
+   assert (input && c_symbols);
+   assert (n_context == 1 || context);
+
+   data = Calloc (n_data, sizeof (unsigned));
+   
+   /*
+    *  Allocate probability models, start with uniform distribution
+    */
+   totals = Calloc (n_context, sizeof (u_word_t *));
+   {
+      unsigned c;
+      
+      for (c = 0; c < n_context; c++)
+      {
+	 unsigned i;
+      
+	 totals [c]    = Calloc (c_symbols [c] + 1, sizeof (u_word_t));
+	 totals [c][0] = 0;
+      
+	 for (i = 0; i < c_symbols [c]; i++)
+	    totals [c][i + 1] = totals [c][i] + 1;
+      }
+   }
+
+   /*
+    *  Fill array 'data' with decoded values
+    */
+   {
+      u_word_t code = get_bits (input, 16); /* The present input code value */
+      u_word_t low  = 0;		/* Start of the current code range */
+      u_word_t high = 0xffff;		/* End of the current code range */
+      unsigned n;
+      
+      for (n = 0; n < n_data; n++) 
+      {
+	 u_word_t scale;		/* range of all 'm' symbol intervals */
+	 u_word_t low_count;		/* lower bound of 'symbol' interval */
+	 u_word_t high_count;		/* upper bound of 'symbol' interval */
+	 unsigned count;		/* value in the current interval */
+	 unsigned range;		/* current interval range */
+	 unsigned d;			/* current data symbol */
+	 unsigned c;			/* context of current data symbol */
+
+	 c = n_context > 1 ? context [n] : 0; 
+
+	 assert (high > low);
+	 scale = totals [c][c_symbols [c]];
+	 range = (high - low) + 1;
+	 count = (((code - low) + 1 ) * scale - 1) / range;
+      
+	 for (d = c_symbols [c]; count < totals [c][d]; d--) /* next symbol */
+	    ;
+	 low_count  = totals [c][d];
+	 high_count = totals [c][d + 1];
+
+	 high = low + (u_word_t) ((range * high_count) / scale - 1 );
+	 low  = low + (u_word_t) ((range * low_count) / scale );
+	 RESCALE_INPUT_INTERVAL;
+
+	 /*
+	  *  Updata probability models
+	  */
+	 {
+	    unsigned i;
+
+	    for (i = d + 1; i < c_symbols [c] + 1; i++)
+	       totals [c][i]++;
+	 
+	    if (totals [c][c_symbols [c]] > scaling) /* scaling */
+	       for (i = 1; i < c_symbols [c] + 1; i++)
+	       {
+		  totals [c][i] >>= 1;
+		  if (totals [c][i] <= totals [c][i - 1])
+		     totals [c][i] = totals [c][i - 1] + 1;
+	       }
+	 }
+	 data [n] = d;
+      }
+      INPUT_BYTE_ALIGN (input);
+   }
+
+   /*
+    *  Cleanup ...
+    */
+   {
+      unsigned c;
+      
+      for (c = 0; c < n_context; c++)
+	 Free (totals [c]);
+      Free (totals);
+   }
+   
+   return data;
+}
+
+model_t *
+alloc_model (unsigned m, unsigned scale, unsigned n, unsigned *totals)
+/*
+ *  Model constructor:
+ *  allocate and initialize an order-'n' probability model.
+ *  The size of the source alphabet is 'm'. Rescale the symbol counts after
+ *  'scale' symbols are encoded/decoded. The initial probability of every
+ *  symbol is 1/m.
+ *  If 'scale' = 0 then use static modeling (p = 1/n).
+ *  If 'totals' is not NULL then use this array of 'm' values to set
+ *  the initial counts.
+ *
+ *  Return value:
+ *	a pointer to the new probability model structure.
+ *  
+ *  Note: We recommend a small size of the alphabet because no escape codes
+ *  are used to encode/decode previously unseen symbols.
+ *  
+ */
+{
+   model_t  *model;			/* new probability model */
+   unsigned  num;			/* number of contexts to allocate */
+   bool_t    cont;			/* continue flag */
+   bool_t    dec;			/* next order flag */
+   unsigned  i;
+
+   /*
+    *  Allocate memory for the structure
+    */
+   model          = Calloc (1, sizeof (model_t));
+   model->symbols = m;
+   model->scale   = scale;
+   model->order   = n;
+   model->context = n > 0 ? Calloc (n, sizeof (unsigned)) : NULL;
+   /*
+    *  Allocate memory for the probabilty model.
+    *  Each of the m^n different contexts requires its own probability model.
+    */
+   for (num = 1, i = 0; i < model->order; i++)
+      num *= model->symbols;
+
+   model->totals = Calloc (num * (model->symbols + 1), sizeof (unsigned));
+
+   for (i = 0; i < model->order; i++)
+      model->context[i] = 0;		/* start with context 0,0, .. ,0 */
+   cont = YES;
+   while (cont)				/* repeat while context != M ... M */
+   {
+      int	power;			/* multiplicator */
+      int	index;			/* index of probability model */
+      /*
+       *  There are m^n different contexts:
+       *  Let "context_1 context_2 ... context_n symbol" be the current input
+       *  stream then the index of the probability model is given by:
+       *  index = context_1 * M^0 + context_2 * M^1 + ... + context_n * M^(n-1)
+       */
+      power = 1;			/* multiplicator */
+      index = 0;			/* address of prob. model */
+	 
+      for (i = 0; i < model->order; i++)	/* genarate a m-nary number */
+      {
+	 index += model->context[i] * power;	
+	 power *= model->symbols;
+      }
+
+      index *= model->symbols + 1;	/* size of each model is m + 1 */
+
+      model->totals [index + 0] = 0;	/* always zero */
+	 
+      for (i = 1; i <= model->symbols; i++) /* prob of each symbol is 1/m or
+					       as given in totals */
+	 model->totals[index + i] = model->totals [index + i - 1]
+				    + (totals ? totals [i - 1] : 1);
+
+      if (model->order == 0)		/* order-0 model */
+	 cont = NO;
+      else				/* try next context */
+	 for (i = model->order - 1, dec = YES; dec; i--)
+	 {
+	    dec = NO;
+	    model->context[i]++;
+	    if (model->context[i] >= model->symbols) 
+	    {
+	       /* change previous context */
+	       model->context[i] = 0;
+	       if (i > 0)		/* there's still a context remaining */
+		  dec = YES;
+	       else
+		  cont = NO;		/* all context models initilized */
+	    }
+	 }
+   }
+   for (i = 0; i < model->order; i++)
+      model->context[i] = 0;		/* start with context 0,0, .. ,0 */
+
+   return model;
+}
+
+void
+free_model (model_t *model)
+/*
+ *  Model destructor:
+ *  Free memory allocated by the arithmetic 'model'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	struct 'model' is discarded
+ */
+{
+   if (model != NULL)
+   {
+      if (model->context != NULL)
+	 Free (model->context);
+      Free (model->totals);
+      Free (model);
+   }
+   else
+      warning ("Can't free model <NULL>.");
+}
diff --git a/converter/other/fiasco/lib/arith.h b/converter/other/fiasco/lib/arith.h
new file mode 100644
index 00000000..744eb9d7
--- /dev/null
+++ b/converter/other/fiasco/lib/arith.h
@@ -0,0 +1,122 @@
+/*
+ *  arith.h
+ *
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:49:37 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _ARITH_H
+#define _ARITH_H
+
+#include "types.h"
+#include "bit-io.h"
+
+typedef struct model
+{
+   unsigned  symbols;			/* number of symbols in the alphabet */
+   unsigned  scale;			/* if totals > scale rescale totals */
+   unsigned  order;			/* order of the probability model */
+   unsigned *context;			/* context of the model */
+   unsigned *totals;			/* the totals */
+} model_t;
+
+typedef struct arith
+{
+   u_word_t   low;			/* start of the current code range */
+   u_word_t   high;			/* end of the current code range */
+   u_word_t   underflow;		/* number of underflow bits pending */
+   u_word_t   code;			/* the present input code value */
+   bitfile_t *file;			/* I/O stream */
+} arith_t;
+
+enum interval {LOW = 0x0000, FIRST_QUARTER = 0x4000, HALF = 0x8000,
+	       THIRD_QUARTER = 0xc000, HIGH = 0xffff};
+
+arith_t *
+alloc_encoder (bitfile_t *file);
+void
+free_encoder (arith_t *arith);
+real_t
+encode_symbol (unsigned symbol, arith_t *arith, model_t *model);
+void
+encode_array (bitfile_t *output, const unsigned *data, const unsigned *context,
+	      const unsigned *c_symbols, unsigned n_context, unsigned n_data,
+	      unsigned scaling);
+arith_t *
+alloc_decoder (bitfile_t *input);
+void
+free_decoder (arith_t *arith);
+unsigned
+decode_symbol (arith_t *arith, model_t *model);
+unsigned *
+decode_array (bitfile_t *input, const unsigned *context,
+	      const unsigned *c_symbols, unsigned n_context,
+	      unsigned n_data, unsigned scaling);
+model_t *
+alloc_model (unsigned m, unsigned scale, unsigned n, unsigned *totals);
+void
+free_model (model_t *model);
+
+#define RESCALE_INPUT_INTERVAL  for (;;)                                      \
+                                   if ((high >= HALF) && (low < HALF) &&      \
+                                      ((low & FIRST_QUARTER) != FIRST_QUARTER \
+				       || (high & FIRST_QUARTER) != 0))       \
+                                   {                                          \
+                                      break;                                  \
+                                   }                                          \
+                                   else if ((high < HALF) || (low >= HALF))   \
+                                   {                                          \
+                                      low  <<= 1;                             \
+                                      high <<= 1;                             \
+                                      high  |= 1;                             \
+                                      code <<= 1;                             \
+                                      code  += get_bit (input);               \
+                                   }                                          \
+                                   else                                       \
+                                   {                                          \
+                                      code  ^= FIRST_QUARTER;                 \
+                                      low   &= FIRST_QUARTER - 1;             \
+                                      low  <<= 1;                             \
+                                      high <<= 1;                             \
+                                      high  |= HALF + 1;                      \
+                                      code <<= 1;                             \
+                                      code  += get_bit (input);               \
+                                   }                                          
+        								   
+#define RESCALE_OUTPUT_INTERVAL  for (;;)                                     \
+                                 {                                            \
+                                    if (high < HALF)                          \
+                                    {                                         \
+                                       put_bit (output, 0);                   \
+                                       for (; underflow; underflow--)         \
+                                          put_bit (output, 1);                \
+                                    }                                         \
+                                    else if (low >= HALF)                     \
+                                    {                                         \
+                                       put_bit (output, 1);                   \
+                                       for (; underflow; underflow--)         \
+                                          put_bit (output, 0);                \
+                                    }                                         \
+                                    else if (high < THIRD_QUARTER &&          \
+                                             low >= FIRST_QUARTER)            \
+                                    {                                         \
+                                       underflow++;                           \
+                                       high |= FIRST_QUARTER;                 \
+                                       low  &= FIRST_QUARTER - 1;             \
+                                    }                                         \
+                                    else                                      \
+                                       break;                                 \
+                                    high <<= 1;                               \
+                                    high  |= 1;                               \
+                                    low  <<= 1;                               \
+                                 }                                             
+					 
+#endif /* not _ARITH_H */
+
diff --git a/converter/other/fiasco/lib/bit-io.c b/converter/other/fiasco/lib/bit-io.c
new file mode 100644
index 00000000..364a1c05
--- /dev/null
+++ b/converter/other/fiasco/lib/bit-io.c
@@ -0,0 +1,327 @@
+/*
+ *  bit-io.c:       Buffered and bit oriented file I/O 
+ *
+ *  Written by:     Ullrich Hafner
+ *  
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+ 
+/*
+ *  $Date: 2000/06/14 20:49:37 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#define _BSD_SOURCE 1   /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include "config.h"
+
+#include <string.h>
+#if STDC_HEADERS
+#   include <stdlib.h>
+#endif /* not STDC_HEADERS */
+
+#include "macros.h"
+#include "types.h"
+#include "error.h"
+
+#include "misc.h"
+#include "bit-io.h"
+
+/*****************************************************************************
+
+                 local constants
+  
+*****************************************************************************/
+
+static const unsigned BUFFER_SIZE = 16350;
+
+static const unsigned mask[] = {0x0001, 0x0002, 0x0004, 0x0008,
+                                0x0010, 0x0020, 0x0040, 0x0080,
+                                0x0100, 0x0200, 0x0400, 0x0800,
+                                0x1000, 0x2000, 0x4000, 0x8000};
+
+/*****************************************************************************
+
+                public code
+  
+*****************************************************************************/
+
+FILE *
+open_file (const char *filename, const char *env_var, openmode_e mode)
+/*
+ *  Try to open file 'filename' with mode 'mode' (READ_ACCESS, WRITE_ACCESS).
+ *  Scan the current directory first and then cycle through the
+ *  path given in the environment variable 'env_var', if set.
+ * 
+ *  Return value:
+ *  Pointer to open file on success, else NULL.
+ */
+{
+    char       *path;        /* current path */
+    FILE       *fp;      /* file pointer of I/O stream */
+    char       *ext_filename = NULL; /* full path of file */
+    char       *env_path     = NULL; /* path given by 'env_var' */
+    char const * const PATH_SEP     = " ;:,"; /* path separation characters */
+    char const * const DEFAULT_PATH = "."; /* default for output files */
+    const char * const read_mode  = "rb";
+    const char * const write_mode = "wb";
+
+
+    assert (mode == READ_ACCESS || mode == WRITE_ACCESS);
+
+    /*
+     *  First check for stdin or stdout
+     */
+    if (filename == NULL || streq (filename, "-"))
+    {
+        if (mode == READ_ACCESS)
+            return stdin;
+        else 
+            return stdout;
+    }
+   
+    /*
+     *  Try to open 'readonly' file in the current directory
+     */
+    if (mode == READ_ACCESS && (fp = fopen (filename, read_mode)))
+        return fp; 
+
+    if (mode == WRITE_ACCESS && strchr (filename, '/')) /* contains path */
+        return fopen (filename, write_mode);
+   
+    /*
+     *  Get value of environment variable 'env_var', if set
+     *  else use DEFAULT_PATH ("./")
+     */
+    if (env_var != NULL)
+        env_path = getenv (env_var);
+    if (env_path == NULL) 
+        env_path = strdup (DEFAULT_PATH);
+    else
+        env_path = strdup (env_path);
+   
+    /*
+     *  Try to open file in the directory given by the environment
+     *  variable env_var - individual path components are separated by PATH_SEP 
+     */
+    path = strtok (env_path, PATH_SEP);
+    do 
+    {
+        if (ext_filename) 
+            Free (ext_filename);
+        ext_filename =  Calloc (strlen (path) + strlen (filename) + 2,
+                                sizeof (char));
+        strcpy (ext_filename, path); 
+        if (*(ext_filename + strlen (ext_filename) - 1) != '/')
+            strcat (ext_filename, "/");
+        strcat (ext_filename, filename);
+        fp = fopen (ext_filename, mode == READ_ACCESS ? read_mode : write_mode);
+    }
+    while (fp == NULL && (path = strtok (NULL, PATH_SEP)) != NULL);
+
+    Free (env_path);
+   
+    return fp;
+}
+
+bitfile_t *
+open_bitfile (const char *filename, const char *env_var, openmode_e mode)
+/*
+ *  Bitfile constructor:
+ *  Try to open file 'filename' for buffered bit oriented access with mode
+ *  'mode'. Scan the current directory first and then cycle through the path
+ *  given in the environment variable 'env_var', if set.
+ *
+ *  Return value:
+ *  Pointer to open bitfile on success,
+ *      otherwise the program is terminated.
+ */
+{
+    bitfile_t *bitfile = Calloc (1, sizeof (bitfile_t));
+   
+    bitfile->file = open_file (filename, env_var, mode);
+
+    if (bitfile->file == NULL)
+        file_error (filename);
+
+    if (mode == READ_ACCESS)
+    {
+        bitfile->bytepos  = 0;
+        bitfile->bitpos   = 0;
+        bitfile->mode     = mode;
+        bitfile->filename = filename ? strdup (filename) : strdup ("(stdin)");
+    }
+    else if (mode == WRITE_ACCESS)
+    {
+        bitfile->bytepos  = BUFFER_SIZE - 1;
+        bitfile->bitpos   = 8;
+        bitfile->mode     = mode;
+        bitfile->filename = filename ? strdup (filename) : strdup ("(stdout)");
+    }
+    else
+        error ("Unknow file access mode '%d'.", mode);
+   
+    bitfile->bits_processed = 0;
+    bitfile->buffer         = Calloc (BUFFER_SIZE, sizeof (byte_t));
+    bitfile->ptr            = bitfile->buffer;
+
+    return bitfile;
+}
+
+bool_t
+get_bit (bitfile_t *bitfile)
+/*
+ *  Get one bit from the given stream 'bitfile'.
+ *
+ *  Return value:
+ *   1  H bit
+ *   0  L bit
+ *
+ *  Side effects:
+ *  Buffer of 'bitfile' is modified accordingly.
+ */
+{
+    assert (bitfile);
+   
+    if (!bitfile->bitpos--)      /* use next byte ? */
+    {
+        bitfile->ptr++;
+        if (!bitfile->bytepos--)      /* no more bytes left in the buffer? */
+        {
+            /*
+             *  Fill buffer with new data
+             */
+            int bytes = fread (bitfile->buffer, sizeof (byte_t),
+                               BUFFER_SIZE, bitfile->file) - 1;
+            if (bytes < 0)         /* Error or EOF */
+                error ("Can't read next bit from bitfile %s.", bitfile->filename);
+            else
+                bitfile->bytepos = bytes;
+
+            bitfile->ptr = bitfile->buffer;
+        }
+        bitfile->bitpos = 7;
+    }
+
+    bitfile->bits_processed++;
+
+    return *bitfile->ptr & mask [bitfile->bitpos] ? 1 : 0;
+}
+
+unsigned int
+get_bits (bitfile_t *bitfile, unsigned bits)
+/*
+ *  Get #'bits' bits from the given stream 'bitfile'.
+ *
+ *  Return value:
+ *  composed integer value
+ *
+ *  Side effects:
+ *  Buffer of 'bitfile' is modified.
+ */
+{
+    unsigned value = 0;          /* input value */
+   
+    while (bits--)
+        value = (unsigned) (value << 1) | get_bit (bitfile);
+
+    return value;
+}
+
+void
+put_bit (bitfile_t *bitfile, unsigned value)
+/*     
+ *  Put the bit 'value' to the bitfile buffer.
+ *  The buffer is written to the file 'bitfile->file' if the number of
+ *  buffer bytes exceeds 'BUFFER_SIZE'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *  Buffer of 'bitfile' is modified.
+ */
+{
+    assert (bitfile);
+   
+    if (!bitfile->bitpos--)      /* use next byte ? */
+    {
+        bitfile->ptr++;
+        if (!bitfile->bytepos--)      /* no more bytes left ? */
+        {
+            /*
+             *  Write buffer to disk and fill buffer with zeros
+             */
+            if (fwrite (bitfile->buffer, sizeof (byte_t),
+                        BUFFER_SIZE, bitfile->file) != BUFFER_SIZE)
+                error ("Can't write next bit of bitfile %s!", bitfile->filename);
+            memset (bitfile->buffer, 0, BUFFER_SIZE);
+            bitfile->bytepos = BUFFER_SIZE - 1;
+            bitfile->ptr     = bitfile->buffer;
+        }
+        bitfile->bitpos = 7;
+    }
+   
+    if (value)
+        *bitfile->ptr |= mask [bitfile->bitpos];
+
+    bitfile->bits_processed++;
+}
+
+void
+put_bits (bitfile_t *bitfile, unsigned value, unsigned bits)
+/*     
+ *  Put #'bits' bits of integer 'value' to the bitfile buffer 'bitfile'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *  Buffer of 'bitfile' is modified.
+ */
+{
+    while (bits--)
+        put_bit (bitfile, value & mask [bits]);
+}
+
+void
+close_bitfile (bitfile_t *bitfile)
+/*
+ *  Bitfile destructor:
+ *  Close 'bitfile', if 'bitfile->mode' == WRITE_ACCESS write bit buffer
+ *  to disk. 
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *  Structure 'bitfile' is discarded.
+ */
+{
+    assert (bitfile);
+   
+    if (bitfile->mode == WRITE_ACCESS)
+    {
+        unsigned bytes = fwrite (bitfile->buffer, sizeof (byte_t),
+                                 BUFFER_SIZE - bitfile->bytepos, bitfile->file);
+        if (bytes != BUFFER_SIZE - bitfile->bytepos)
+            error ("Can't write remaining %d bytes of bitfile "
+                   "(only %d bytes written)!",
+                   BUFFER_SIZE - bitfile->bytepos, bytes);
+    }
+    fclose (bitfile->file);
+    Free (bitfile->buffer);
+    Free (bitfile->filename);
+    Free (bitfile);
+}
+
+unsigned
+bits_processed (const bitfile_t *bitfile)
+/*
+ *  Return value:
+ *  Number of bits processed up to now
+ */
+{
+    return bitfile->bits_processed;
+}
diff --git a/converter/other/fiasco/lib/bit-io.h b/converter/other/fiasco/lib/bit-io.h
new file mode 100644
index 00000000..d37cc47c
--- /dev/null
+++ b/converter/other/fiasco/lib/bit-io.h
@@ -0,0 +1,58 @@
+/*
+ *  bit-io.h
+ *
+ *  Written by:		Ullrich Hafner
+ *
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:49:37 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _BIT_IO_H
+#define _BIT_IO_H
+
+#include <stdio.h>
+#include "types.h"
+
+#define OUTPUT_BYTE_ALIGN(bfile) while ((bfile)->bitpos) put_bit (bfile, 0);
+#define INPUT_BYTE_ALIGN(bfile)  while ((bfile)->bitpos) get_bit (bfile);
+
+typedef enum {READ_ACCESS, WRITE_ACCESS} openmode_e;
+
+typedef struct bitfile
+{
+   FILE	      *file;			/* associated filepointer */
+   char	      *filename;		/* corresponding filename */
+   byte_t     *buffer;			/* stream buffer */
+   byte_t     *ptr;			/* pointer to current buffer pos */
+   unsigned    bytepos;			/* current I/O byte */
+   unsigned    bitpos;			/* current I/O bit */
+   unsigned    bits_processed;		/* number of bits already processed */
+   openmode_e  mode;			/* access mode */
+} bitfile_t;
+
+FILE *
+open_file (const char *filename, const char *env_var, openmode_e mode);
+bitfile_t *
+open_bitfile (const char *filename, const char *env_var, openmode_e mode);
+void
+put_bit (bitfile_t *bitfile, unsigned value);
+void
+put_bits (bitfile_t *bitfile, unsigned value, unsigned bits);
+bool_t
+get_bit (bitfile_t *bitfile);
+unsigned 
+get_bits (bitfile_t *bitfile, unsigned bits);
+void
+close_bitfile (bitfile_t *bitfile);
+unsigned
+bits_processed (const bitfile_t *bitfile);
+
+#endif /* not _BIT_IO_H */
+
diff --git a/converter/other/fiasco/lib/dither.c b/converter/other/fiasco/lib/dither.c
new file mode 100644
index 00000000..c7f9ebab
--- /dev/null
+++ b/converter/other/fiasco/lib/dither.c
@@ -0,0 +1,1892 @@
+/*
+ *  dither.c:		Various dithering routines 	
+ *
+ *  Adapted by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ * Copyright (c) 1995 Erik Corry
+ * All rights reserved.
+ * 
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ * 
+ * IN NO EVENT SHALL ERIK CORRY BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
+ * SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
+ * THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF ERIK CORRY HAS BEEN ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * ERIK CORRY SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
+ * BASIS, AND ERIK CORRY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT,
+ * UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*
+ *  $Date: 2000/11/27 20:22:51 $
+ *  $Author: hafner $
+ *  $Revision: 5.3 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#if HAVE_STRING_H
+#	include <string.h>
+#else /* not HAVE_STRING_H */
+#	include <strings.h>
+#endif /* not HAVE_STRING_H */
+#if STDC_HEADERS
+#	include <stdlib.h>
+#endif /* not STDC_HEADERS */
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "fiasco.h"
+#include "image.h"
+#include "misc.h"
+#include "dither.h"
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static int 
+display_16_bit (const struct fiasco_renderer *this, unsigned char *ximage,
+		const fiasco_image_t *fiasco_image);
+static int 
+display_24_bit_bgr (const struct fiasco_renderer *this, unsigned char *ximage,
+		    const fiasco_image_t *fiasco_image);
+static int 
+display_24_bit_rgb (const struct fiasco_renderer *this, unsigned char *ximage,
+		    const fiasco_image_t *fiasco_image);
+static int 
+display_32_bit (const struct fiasco_renderer *this, unsigned char *ximage,
+		const fiasco_image_t *fiasco_image);
+static int
+free_bits_at_bottom (unsigned long a);
+static int
+free_bits_at_top (unsigned long a);
+static int
+number_of_bits_set (unsigned long a);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+fiasco_renderer_t *
+fiasco_renderer_new (unsigned long red_mask, unsigned long green_mask,
+		     unsigned long blue_mask, unsigned bpp,
+		     int double_resolution)
+/*
+ *  FIASCO renderer constructor.
+ *  Allocate memory for the FIASCO renderer structure and
+ *  initialize values.
+ *  `red_mask', `green_mask', and `blue_mask' are the corresponding masks
+ *  of the X11R6 XImage structure. 
+ *  `bpp' gives the depth of the image in bits per pixel (16, 24, or 32).
+ *  If `double_resolution' is not 0 the the image width and height is doubled.
+ *  (fast pixel doubling, no interpolation!)
+ *
+ *  Return value:
+ *	pointer to the new structure or NULL on error
+ */
+{
+   if (bpp != 16 && bpp != 24 && bpp !=32)
+   {
+      set_error (_("Rendering depth of XImage must be 16, 24, or 32 bpp."));
+      return NULL;
+   }
+   else
+   {
+      fiasco_renderer_t  *render    = calloc (1, sizeof (fiasco_renderer_t));
+      renderer_private_t *private   = calloc (1, sizeof (renderer_private_t));
+      bool_t 	       	  twopixels = (bpp == 16 && double_resolution);
+      int 		  crval, cbval, i; /* counter */
+
+      if (!render || !private)
+      {
+	 set_error (_("Out of memory."));
+	 return NULL;
+      }
+      switch (bpp)
+      {
+	 case 16:
+	    render->render = display_16_bit;
+	    break;
+	 case 24:
+	    if (red_mask > green_mask)
+	       render->render = display_24_bit_rgb;
+	    else
+	       render->render = display_24_bit_bgr;
+	    break;
+	 case 32:
+	    render->render = display_32_bit;
+	    break;
+	 default:
+	    break;			/* does not happen */
+      }
+      render->private = private;
+      render->delete  = fiasco_renderer_delete;
+
+      private->double_resolution = double_resolution;
+      private->Cr_r_tab = calloc (256 + 2 * 1024, sizeof (int));
+      private->Cr_g_tab = calloc (256 + 2 * 1024, sizeof (int));
+      private->Cb_g_tab = calloc (256 + 2 * 1024, sizeof (int));
+      private->Cb_b_tab = calloc (256 + 2 * 1024, sizeof (int));
+
+      if (!private->Cr_r_tab || !private->Cr_g_tab
+	  || !private->Cb_b_tab || !private->Cb_g_tab)
+      {
+	 set_error (_("Out of memory."));
+	 return NULL;
+      }
+      
+      for (i = 1024; i < 1024 + 256; i++)
+      {
+	 cbval = crval  = i - 128 - 1024;
+
+	 private->Cr_r_tab [i] =  1.4022 * crval + 0.5;
+	 private->Cr_g_tab [i] = -0.7145 * crval + 0.5;
+	 private->Cb_g_tab [i] = -0.3456 * cbval + 0.5; 
+	 private->Cb_b_tab [i] =  1.7710 * cbval + 0.5;
+      }
+      for (i = 0; i < 1024; i++)
+      {
+	 private->Cr_r_tab [i] = private->Cr_r_tab [1024];
+	 private->Cr_g_tab [i] = private->Cr_g_tab [1024];
+	 private->Cb_g_tab [i] = private->Cb_g_tab [1024]; 
+	 private->Cb_b_tab [i] = private->Cb_b_tab [1024];
+      }
+      for (i = 1024 + 256; i < 2048 + 256; i++)
+      {
+	 private->Cr_r_tab [i] = private->Cr_r_tab [1024 + 255];
+	 private->Cr_g_tab [i] = private->Cr_g_tab [1024 + 255];
+	 private->Cb_g_tab [i] = private->Cb_g_tab [1024 + 255]; 
+	 private->Cb_b_tab [i] = private->Cb_b_tab [1024 + 255];
+      }
+
+      private->Cr_r_tab += 1024 + 128;
+      private->Cr_g_tab += 1024 + 128;
+      private->Cb_g_tab += 1024 + 128;
+      private->Cb_b_tab += 1024 + 128;
+   
+      /* 
+       *  Set up entries 0-255 in rgb-to-pixel value tables.
+       */
+      private->r_table = calloc (256 + 2 * 1024, sizeof (unsigned int));
+      private->g_table = calloc (256 + 2 * 1024, sizeof (unsigned int));
+      private->b_table = calloc (256 + 2 * 1024, sizeof (unsigned int));
+      private->y_table = calloc (256 + 2 * 1024, sizeof (unsigned int));
+
+      if (!private->r_table || !private->g_table
+	  || !private->b_table || !private->y_table)
+      {
+	 set_error (_("Out of memory."));
+	 return NULL;
+      }
+      
+      for (i = 0; i < 256; i++)
+      {
+	 private->r_table [i + 1024]
+	    = i >> (8 - number_of_bits_set(red_mask));
+	 private->r_table [i + 1024]
+	    <<= free_bits_at_bottom (red_mask);
+	 private->g_table [i + 1024]
+	    = i >> (8 - number_of_bits_set (green_mask));
+	 private->g_table [i + 1024]
+	    <<= free_bits_at_bottom (green_mask);
+	 private->b_table [i + 1024]
+	    <<= free_bits_at_bottom (blue_mask);
+	 private->b_table [i + 1024]
+	    = i >> (8 - number_of_bits_set (blue_mask));
+	 if (twopixels)
+	 {
+	    private->r_table [i + 1024] = ((private->r_table [i + 1024] << 16)
+					   | private->r_table [i + 1024]);
+	    private->g_table [i + 1024] = ((private->g_table [i + 1024] << 16)
+					   | private->g_table [i + 1024]);
+	    private->b_table [i + 1024] = ((private->b_table [i + 1024] << 16)
+					   | private->b_table [i + 1024]);
+	 }
+	 private->y_table [i + 1024] = (private->r_table [i + 1024]
+					| private->g_table [i + 1024]
+					| private->b_table [i + 1024]);
+      }
+
+      /*
+       * Spread out the values we have to the rest of the array so that
+       * we do not need to check for overflow.
+       */
+      for (i = 0; i < 1024; i++)
+      {
+	 private->r_table [i]              = private->r_table [1024];
+	 private->r_table [i + 1024 + 256] = private->r_table [1024 + 255];
+	 private->g_table [i]              = private->g_table [1024];
+	 private->g_table [i + 1024 + 256] = private->g_table [1024 + 255];
+	 private->b_table [i]              = private->b_table [1024];
+	 private->b_table [i + 1024 + 256] = private->b_table [1024 + 255];
+	 private->y_table [i]              = private->y_table [1024];
+	 private->y_table [i + 1024 + 256] = private->y_table [1024 + 255];
+      }
+
+      private->r_table += 1024;
+      private->g_table += 1024;
+      private->b_table += 1024;
+      private->y_table += 1024 + 128;
+
+      return render;
+   }
+   
+}
+
+void
+fiasco_renderer_delete (fiasco_renderer_t *renderer)
+/*
+ *  FIASCO renderer destructor:
+ *  Free memory of 'renderer' structure.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	structure 'renderer' is discarded.
+ */
+{
+   if (!renderer)
+      return;
+   else
+   {
+      renderer_private_t *private = (renderer_private_t *) renderer->private;
+
+      Free (private->Cr_g_tab - (1024 + 128));
+      Free (private->Cr_r_tab - (1024 + 128));
+      Free (private->Cb_g_tab - (1024 + 128));
+      Free (private->Cb_b_tab - (1024 + 128));
+      Free (private->r_table - 1024);
+      Free (private->g_table - 1024);
+      Free (private->b_table - 1024);
+      Free (private->y_table - (1024 + 128));
+
+      Free (private);
+      Free (renderer);
+   }
+}
+
+int
+fiasco_renderer_render (const fiasco_renderer_t *renderer,
+			unsigned char *ximage,
+			const fiasco_image_t *fiasco_image)
+{
+   if (!renderer)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "renderer");
+      return 0;
+   }
+   else
+      return renderer->render (renderer, ximage, fiasco_image);
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+/*
+ *  Erik Corry's multi-byte dither routines.
+ *
+ *  The basic idea is that the Init generates all the necessary
+ *  tables.  The tables incorporate the information about the layout
+ *  of pixels in the XImage, so that it should be able to cope with
+ *  16-bit 24-bit (non-packed) and 32-bit (10-11 bits per
+ *  color!) screens.  At present it cannot cope with 24-bit packed
+ *  mode, since this involves getting down to byte level again. It is
+ *  assumed that the bits for each color are contiguous in the
+ *  longword.
+ * 
+ *  Writing to memory is done in shorts or ints. (Unfortunately, short
+ *  is not very fast on Alpha, so there is room for improvement
+ *  here). There is no dither time check for overflow - instead the
+ *  tables have slack at each end. This is likely to be faster than an
+ *  'if' test as many modern architectures are really bad at
+ *  ifs. Potentially, each '&&' causes a pipeline flush!
+ *
+ *  There is no shifting and fixed point arithmetic, as I really doubt
+ *  you can see the difference, and it costs. This may be just my
+ *  bias, since I heard that Intel is really bad at shifting.
+ */
+
+static int
+number_of_bits_set (unsigned long a)
+/*
+ *  How many 1 bits are there in the longword.
+ *  Low performance, do not call often.
+ */
+{
+   if (!a)
+      return 0;
+   if (a & 1)
+      return 1 + number_of_bits_set (a >> 1);
+   else
+      return (number_of_bits_set (a >> 1));
+}
+
+static int
+free_bits_at_top (unsigned long a)
+/*
+ *  How many 0 bits are there at most significant end of longword.
+ *  Low performance, do not call often.
+ */
+{
+   if(!a)				/* assume char is 8 bits */
+      return sizeof (unsigned long) * 8;
+   else if (((long) a) < 0l)		/* assume twos complement */
+      return 0;
+   else
+      return 1 + free_bits_at_top ( a << 1);
+}
+
+static int
+free_bits_at_bottom (unsigned long a)
+/*
+ *  How many 0 bits are there at least significant end of longword.
+ *  Low performance, do not call often.
+ */
+{
+   /* assume char is 8 bits */
+   if (!a)
+      return sizeof (unsigned long) * 8;
+   else if(((long) a) & 1l)
+      return 0;
+   else
+      return 1 + free_bits_at_bottom ( a >> 1);
+}
+
+static int 
+display_16_bit (const struct fiasco_renderer *this, unsigned char *ximage,
+		const fiasco_image_t *fiasco_image)
+/*
+ *  Convert 'image' to 16 bit color bitmap.
+ *  If 'double_resolution' is true then double image size in both directions.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'out[]'	is filled with dithered image
+ */
+{
+   const image_t      *image;
+   renderer_private_t *private;
+   byte_t	      *out;
+   
+   if (!this)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "this");
+      return 0;
+   }
+   if (!ximage)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "ximage");
+      return 0;
+   }
+   if (!fiasco_image)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "fiasco_image");
+      return 0;
+   }
+
+   out 	   = (byte_t *) ximage;
+   image   = cast_image ((fiasco_image_t *) fiasco_image);
+   if (!image)
+      return 0;
+   private = (renderer_private_t *) this->private;
+   
+   if (image->color)
+   {
+      word_t 	   *cbptr, *crptr;	/* pointer to chroma bands */
+      word_t 	   *yptr;		/* pointers to lumincance band */
+      int     	    yval, crval, cbval;	/* pixel value in YCbCr color space */
+      int     	    R, G, B;		/* pixel value in RGB color space */
+      int     	    n;			/* pixel counter */
+      int     	    x, y;		/* pixel coordinates */
+      int 	   *Cr_r_tab, *Cr_g_tab, *Cb_g_tab, *Cb_b_tab;
+      unsigned int *r_table, *g_table, *b_table;
+
+      Cr_g_tab = private->Cr_g_tab;
+      Cr_r_tab = private->Cr_r_tab;
+      Cb_b_tab = private->Cb_b_tab;
+      Cb_g_tab = private->Cb_g_tab;
+      r_table  = private->r_table;
+      g_table  = private->g_table;
+      b_table  = private->b_table;
+      yptr     = image->pixels [Y];
+      cbptr    = image->pixels [Cb];
+      crptr    = image->pixels [Cr];
+
+      if (image->format == FORMAT_4_2_0)
+      {
+	 u_word_t *dst, *dst2;		/* pointers to dithered pixels */
+	 word_t	  *yptr2;		/* pointers to lumincance band */
+
+	 if (private->double_resolution)
+	 {
+	    yptr2 = yptr + image->width;
+	    dst   = (u_word_t *) out;
+	    dst2  = dst + 4 * image->width;
+	    for (y = image->height / 2; y; y--)
+	    {
+	       for (x = image->width / 2; x; x--)
+	       {
+#ifdef HAVE_SIGNED_SHIFT
+		  crval = *crptr++ >> 4;
+		  cbval = *cbptr++ >> 4;
+		  yval  = (*yptr++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  crval = *crptr++ / 16;
+		  cbval = *cbptr++ / 16;
+		  yval  = *yptr++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  *dst++ = r_table [R] | g_table [G] | b_table [B];
+		  *dst++ = r_table [R] | g_table [G] | b_table [B];
+	       
+#ifdef HAVE_SIGNED_SHIFT
+		  yval  = (*yptr++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval  = *yptr++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  *dst++ = r_table [R] | g_table [G] | b_table [B];
+		  *dst++ = r_table [R] | g_table [G] | b_table [B];
+
+#ifdef HAVE_SIGNED_SHIFT
+		  yval  = (*yptr2++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval  = *yptr2++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  *dst2++ = r_table [R] | g_table [G] | b_table [B];
+		  *dst2++ = r_table [R] | g_table [G] | b_table [B];
+
+#ifdef HAVE_SIGNED_SHIFT
+		  yval  = (*yptr2++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval  = *yptr2++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  *dst2++ = r_table [R] | g_table [G] | b_table [B];
+		  *dst2++ = r_table [R] | g_table [G] | b_table [B];
+	       }
+	       memcpy (dst, dst - 2 * image->width,
+		       2 * image->width * sizeof (u_word_t));
+	       memcpy (dst2, dst2 - 2 * image->width,
+		       2 * image->width * sizeof (u_word_t));
+	       yptr  += image->width;
+	       yptr2 += image->width;
+	       dst   += 3 * image->width * 2;
+	       dst2  += 3 * image->width * 2;
+	    }
+	 }
+	 else
+	 {
+	    yptr2 = yptr + image->width;
+	    dst  = (u_word_t *) out;
+	    dst2 = dst + image->width;
+	    
+	    for (y = image->height / 2; y; y--)
+	    {
+	       for (x = image->width / 2; x; x--)
+	       {
+#ifdef HAVE_SIGNED_SHIFT
+		  crval = *crptr++ >> 4;
+		  cbval = *cbptr++ >> 4;
+		  yval  = (*yptr++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  crval = *crptr++ / 16;
+		  cbval = *cbptr++ / 16;
+		  yval  = *yptr++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  *dst++ = r_table [R] | g_table [G] | b_table [B];
+	       
+#ifdef HAVE_SIGNED_SHIFT
+		  yval  = (*yptr++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval  = *yptr++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  *dst++ = r_table [R] | g_table [G] | b_table [B];
+
+#ifdef HAVE_SIGNED_SHIFT
+		  yval  = (*yptr2++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval  = *yptr2++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  *dst2++ = r_table [R] | g_table [G] | b_table [B];
+
+#ifdef HAVE_SIGNED_SHIFT
+		  yval  = (*yptr2++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval  = *yptr2++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  *dst2++ = r_table [R] | g_table [G] | b_table [B];
+	       }
+	       yptr  += image->width;
+	       yptr2 += image->width;
+	       dst   += image->width;
+	       dst2  += image->width;
+	    }
+	 }
+      }
+      else				/* 4:4:4 format */
+      {
+	 if (private->double_resolution)
+	 {
+	    unsigned int *dst;		/* pointer to dithered pixels */
+	    
+	    dst  = (unsigned int *) out;
+	    
+	    for (y = image->height; y; y--)
+	    {
+	       for (x = image->width; x; x--)
+	       {
+#ifdef HAVE_SIGNED_SHIFT
+		  crval = *crptr++ >> 4;
+		  cbval = *cbptr++ >> 4;
+		  yval  = (*yptr++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  crval = *crptr++ / 16;
+		  cbval = *cbptr++ / 16;
+		  yval  = *yptr++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  
+		  *dst++ = r_table [R] | g_table [G] | b_table [B];
+	       }
+	       memcpy (dst, dst - image->width,
+		       image->width * sizeof (unsigned int));
+	       dst += image->width;
+	    }
+	 }
+	 else
+	 {
+	    u_word_t *dst;		/* pointer to dithered pixels */
+
+	    dst  = (u_word_t *) out;
+	    
+	    for (n = image->width * image->height; n; n--)
+	    {
+#ifdef HAVE_SIGNED_SHIFT
+	       crval = *crptr++ >> 4;
+	       cbval = *cbptr++ >> 4;
+	       yval  = (*yptr++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+	       crval = *crptr++ / 16;
+	       cbval = *cbptr++ / 16;
+	       yval  = *yptr++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+	       R = yval + Cr_r_tab [crval];
+	       G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+	       B = yval + Cb_b_tab [cbval];
+
+	       *dst++ = r_table [R] | g_table [G] | b_table [B];
+	    }
+	 }
+      }
+   }
+   else
+   {
+      unsigned int *dst;		/* pointer to dithered pixels */
+      word_t	   *src;		/* current pixel of frame */
+      unsigned int *y_table;
+
+      y_table = private->y_table;
+      dst     = (unsigned int *) out;
+      src     = image->pixels [GRAY];
+      
+      if (private->double_resolution)
+      {
+	 int x, y;			/* pixel coordinates */
+  	    
+	 for (y = image->height; y; y--)
+	 {
+	    for (x = image->width; x; x--)
+	    {
+	       int value;
+	       
+#ifdef HAVE_SIGNED_SHIFT
+	       value = y_table [*src++ >> 4];
+#else /* not HAVE_SIGNED_SHIFT */
+	       value = y_table [*src++ / 16];
+#endif /* not HAVE_SIGNED_SHIFT */
+	       *dst++ = (value << 16) | value;
+	    }
+	    
+	    memcpy (dst, dst - image->width,
+		    image->width * sizeof (unsigned int));
+	    dst += image->width;
+	 }
+      }
+      else
+      {
+	 int n;				/* pixel counter */
+	 
+	 for (n = image->width * image->height / 2; n; n--, src += 2)
+#ifdef HAVE_SIGNED_SHIFT
+#	ifndef WORDS_BIGENDIAN
+	    *dst++ = (y_table [src [1] >> 4] << 16) | y_table [src [0] >> 4];
+#	else /* not WORDS_BIGENDIAN  */
+	    *dst++ = (y_table [src [0] >> 4] << 16) | y_table [src [1] >> 4];
+#	endif
+#else /* not HAVE_SIGNED_SHIFT */
+#	ifndef WORDS_BIGENDIAN
+	    *dst++ = (y_table [src [1] / 16] << 16) | y_table [src [0] / 16];
+#	else /* not WORDS_BIGENDIAN  */
+	    *dst++ = (y_table [src [0] / 16] << 16) | y_table [src [1] / 16];
+#	endif
+#endif /* not HAVE_SIGNED_SHIFT */
+      }
+   }
+
+   return 1;
+}
+
+static int 
+display_24_bit_bgr (const struct fiasco_renderer *this, unsigned char *ximage,
+		    const fiasco_image_t *fiasco_image)
+/*
+ *  Convert 'image' to 16 bit color bitmap.
+ *  If 'double_resolution' is true then double image size in both directions.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'out[]'	is filled with dithered image
+ */
+{
+   unsigned 	      *gray_clip = init_clipping ();
+   const image_t      *image;
+   renderer_private_t *private;
+   byte_t	      *out;
+
+   if (!gray_clip)
+      return 0;
+   if (!this)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "this");
+      return 0;
+   }
+   if (!ximage)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "ximage");
+      return 0;
+   }
+   if (!fiasco_image)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "fiasco_image");
+      return 0;
+   }
+
+   out 	   = (byte_t *) ximage;
+   image   = cast_image ((fiasco_image_t *) fiasco_image);
+   if (!image)
+      return 0;
+   private = (renderer_private_t *) this->private;
+   
+   if (image->color)
+   {
+      word_t 	   *cbptr, *crptr;	/* pointer to chroma bands */
+      word_t 	   *yptr;		/* pointers to lumincance band */
+      int 	   *Cr_r_tab, *Cr_g_tab, *Cb_g_tab, *Cb_b_tab;
+      unsigned int *r_table, *g_table, *b_table;
+
+      Cr_g_tab = private->Cr_g_tab;
+      Cr_r_tab = private->Cr_r_tab;
+      Cb_b_tab = private->Cb_b_tab;
+      Cb_g_tab = private->Cb_g_tab;
+      r_table  = private->r_table;
+      g_table  = private->g_table;
+      b_table  = private->b_table;
+      yptr     = image->pixels [Y];
+      cbptr    = image->pixels [Cb];
+      crptr    = image->pixels [Cr];
+
+      if (image->format == FORMAT_4_2_0)
+      {
+	 if (private->double_resolution)
+	 {
+	    int		  yval1;	/* lumincance pixel */
+	    int 	  crval1, cbval1; /* chroma pixels */
+	    int		  yval2;	/* pixel in YCbCr color space */
+	    unsigned int  R1, G1, B1;	/* pixel in RGB color space */
+	    unsigned int  R2, G2, B2;	/* pixel in RGB color space */
+	    int		  x, y;		/* pixel counter */
+	    unsigned int *dst;		/* pointer to dithered pixels */
+	    unsigned int *dst2;		/* pointers to dithered pixels */
+	    word_t	 *yptr2;	/* pointers to lumincance band */
+	    
+	    dst   = (unsigned int *) out;
+	    dst2  = dst + (image->width >> 1) * 3 * 2;
+	    yptr2 = yptr + image->width;
+	    
+	    for (y = image->height >> 1; y; y--)
+	    {
+	       for (x = image->width >> 1; x; x--)
+	       {
+#ifdef HAVE_SIGNED_SHIFT
+		  yval1  = (*yptr++ >> 4) + 128;
+		  yval2  = (*yptr++ >> 4) + 128;
+		  crval1 = *crptr++ >> 4;
+		  cbval1 = *cbptr++ >> 4;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval1  = *yptr++  / 16 + 128;
+		  yval2  = *yptr++  / 16 + 128;
+		  crval1 = *crptr++ / 16;
+		  cbval1 = *cbptr++ / 16;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+		  R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+		  G1 = gray_clip [yval1 + Cr_g_tab [crval1]
+				 + Cb_g_tab [cbval1]];
+		  B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+		  R2 = gray_clip [yval2 + Cr_r_tab [crval1]];
+		  G2 = gray_clip [yval2 + Cr_g_tab [crval1]
+				 + Cb_g_tab [cbval1]];
+		  B2 = gray_clip [yval2 + Cb_b_tab [cbval1]];
+
+		  *dst++ = B1 | (G1 << 8) | (R1 << 16) | (B1 << 24);
+		  *dst++ = G1 | (R1 << 8) | (B2 << 16) | (G2 << 24);
+		  *dst++ = R2 | (B2 << 8) | (G2 << 16) | (R2 << 24);
+
+#ifdef HAVE_SIGNED_SHIFT
+		  yval1  = (*yptr2++ >> 4) + 128;
+		  yval2  = (*yptr2++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval1  = *yptr2++  / 16 + 128;
+		  yval2  = *yptr2++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+		  R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+		  G1 = gray_clip [yval1 + Cr_g_tab [crval1]
+				 + Cb_g_tab [cbval1]];
+		  B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+		  R2 = gray_clip [yval2 + Cr_r_tab [crval1]];
+		  G2 = gray_clip [yval2 + Cr_g_tab [crval1]
+				 + Cb_g_tab [cbval1]];
+		  B2 = gray_clip [yval2 + Cb_b_tab [cbval1]];
+
+		  *dst2++ = B1 | (G1 << 8) | (R1 << 16) | (B1 << 24);
+		  *dst2++ = G1 | (R1 << 8) | (B2 << 16) | (G2 << 24);
+		  *dst2++ = R2 | (B2 << 8) | (G2 << 16) | (R2 << 24);
+	       }
+	       memcpy (dst, dst - (image->width >> 1) * 3,
+		       (image->width >> 1) * 3 * sizeof (unsigned int));
+	       memcpy (dst2, dst2 - (image->width >> 1) * 3, 
+		       (image->width >> 1) * 3 * sizeof (unsigned int));
+	       dst   += (image->width >> 1) * 3 * 3;
+	       dst2  += (image->width >> 1) * 3 * 3;
+	       yptr  += image->width;
+	       yptr2 += image->width;
+	    }
+	 }
+	 else
+	 {
+	    int		  yval1;	/* lumincance pixel */
+	    int 	  crval1, cbval1; /* chroma pixels */
+	    int		  yval2;	/* pixel in YCbCr color space */
+	    unsigned int  R1, G1, B1;	/* pixel in RGB color space */
+	    unsigned int  R2, G2, B2;	/* pixel in RGB color space */
+	    int		  x, y;		/* pixel counter */
+	    unsigned int *dst;		/* pointer to dithered pixels */
+	    unsigned int *dst2;		/* pointers to dithered pixels */
+	    word_t	 *yptr2;	/* pointers to lumincance band */
+	    
+	    dst   = (unsigned int *) out;
+	    dst2  = dst + (image->width >> 2) * 3;
+	    yptr2 = yptr + image->width;
+	    
+	    for (y = image->height >> 1; y; y--)
+	    {
+	       for (x = image->width >> 2; x; x--)
+	       {
+#ifdef HAVE_SIGNED_SHIFT
+		  yval1  = (*yptr++ >> 4) + 128;
+		  yval2  = (*yptr++ >> 4) + 128;
+		  crval1 = *crptr++ >> 4;
+		  cbval1 = *cbptr++ >> 4;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval1  = *yptr++  / 16 + 128;
+		  yval2  = *yptr++  / 16 + 128;
+		  crval1 = *crptr++ / 16;
+		  cbval1 = *cbptr++ / 16;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+		  R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+		  G1 = gray_clip [yval1 + Cr_g_tab [crval1]
+				 + Cb_g_tab [cbval1]];
+		  B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+		  R2 = gray_clip [yval2 + Cr_r_tab [crval1]];
+		  G2 = gray_clip [yval2 + Cr_g_tab [crval1]
+				 + Cb_g_tab [cbval1]];
+		  B2 = gray_clip [yval2 + Cb_b_tab [cbval1]];
+
+		  *dst++ = B1 | (G1 << 8) | (R1 << 16) | (B2 << 24);
+		  *dst   = G2 | (R2 << 8);
+
+#ifdef HAVE_SIGNED_SHIFT
+		  yval1  = (*yptr2++ >> 4) + 128;
+		  yval2  = (*yptr2++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval1  = *yptr2++  / 16 + 128;
+		  yval2  = *yptr2++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+		  R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+		  G1 = gray_clip [yval1 + Cr_g_tab [crval1]
+				 + Cb_g_tab [cbval1]];
+		  B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+		  R2 = gray_clip [yval2 + Cr_r_tab [crval1]];
+		  G2 = gray_clip [yval2 + Cr_g_tab [crval1]
+				 + Cb_g_tab [cbval1]];
+		  B2 = gray_clip [yval2 + Cb_b_tab [cbval1]];
+
+		  *dst2++ = B1 | (G1 << 8) | (R1 << 16) | (B2 << 24);
+		  *dst2   = G2 | (R2 << 8);
+	       
+#ifdef HAVE_SIGNED_SHIFT
+		  yval1  = (*yptr++ >> 4) + 128;
+		  yval2  = (*yptr++ >> 4) + 128;
+		  crval1 = *crptr++ >> 4;
+		  cbval1 = *cbptr++ >> 4;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval1  = *yptr++  / 16 + 128;
+		  yval2  = *yptr++  / 16 + 128;
+		  crval1 = *crptr++ / 16;
+		  crval2 = *crptr++ / 16;
+		  cbval1 = *cbptr++ / 16;
+		  cbval2 = *cbptr++ / 16;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+		  R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+		  G1 = gray_clip [yval1 + Cr_g_tab [crval1]
+				 + Cb_g_tab [cbval1]];
+		  B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+		  R2 = gray_clip [yval2 + Cr_r_tab [crval1]];
+		  G2 = gray_clip [yval2 + Cr_g_tab [crval1]
+				 + Cb_g_tab [cbval1]];
+		  B2 = gray_clip [yval2 + Cb_b_tab [cbval1]];
+
+		  *dst++ |= (B1 << 16) | (G1 << 24);
+		  *dst++ = R1 | (B2 << 8) | (G2 << 16) | (R2 << 24);
+
+#ifdef HAVE_SIGNED_SHIFT
+		  yval1  = (*yptr2++ >> 4) + 128;
+		  yval2  = (*yptr2++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval1  = *yptr2++  / 16 + 128;
+		  yval2  = *yptr2++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+		  R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+		  G1 = gray_clip [yval1 + Cr_g_tab [crval1]
+				 + Cb_g_tab [cbval1]];
+		  B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+		  R2 = gray_clip [yval2 + Cr_r_tab [crval1]];
+		  G2 = gray_clip [yval2 + Cr_g_tab [crval1]
+				 + Cb_g_tab [cbval1]];
+		  B2 = gray_clip [yval2 + Cb_b_tab [cbval1]];
+
+		  *dst2++ |= (B1 << 16) | (G1 << 24);
+		  *dst2++ = R1 | (B2 << 8) | (G2 << 16) | (R2 << 24);
+	       }
+	       dst   += (image->width >> 2) * 3;
+	       dst2  += (image->width >> 2) * 3;
+	       yptr  += image->width;
+	       yptr2 += image->width;
+	    }
+	 }
+      }
+      else				/* 4:4:4 format */
+      {
+	 if (private->double_resolution)
+	 {
+	    unsigned int R1, G1, B1;		/* pixel1 in RGB color space */
+	    unsigned int R2, G2, B2;		/* pixel2 in RGB color space */
+	    int		 yval1, crval1, cbval1;	/* pixel1 in YCbCr space */
+	    int		 yval2, crval2, cbval2;	/* pixel2 in YCbCr space */
+	    int		 x, y;			/* pixel counter */
+	    unsigned int *dst;		        /* dithered pixel pointer */
+	    
+	    dst = (unsigned int *) out;
+	    
+	    for (y = image->height; y; y--)
+	    {
+	       for (x = image->width >> 1; x; x--)
+	       {
+#ifdef HAVE_SIGNED_SHIFT
+		  yval1  = (*yptr++ >> 4) + 128;
+		  yval2  = (*yptr++ >> 4) + 128;
+		  crval1 = *crptr++ >> 4;
+		  crval2 = *crptr++ >> 4;
+		  cbval1 = *cbptr++ >> 4;
+		  cbval2 = *cbptr++ >> 4;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval1  = *yptr++  / 16 + 128;
+		  yval2  = *yptr++  / 16 + 128;
+		  crval1 = *crptr++ / 16;
+		  crval2 = *crptr++ / 16;
+		  cbval1 = *cbptr++ / 16;
+		  cbval2 = *cbptr++ / 16;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  
+		  R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+		  G1 = gray_clip [yval1 + Cr_g_tab [crval1]
+				 + Cb_g_tab [cbval1]];
+		  B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+		  R2 = gray_clip [yval2 + Cr_r_tab [crval2]];
+		  G2 = gray_clip [yval2 + Cr_g_tab [crval2]
+				 + Cb_g_tab [cbval2]];
+		  B2 = gray_clip [yval2 + Cb_b_tab [cbval2]];
+
+		  *dst++ = B1 | (G1 << 8) | (R1 << 16) | (B1 << 24);
+		  *dst++ = G1 | (R1 << 8) | (B2 << 16) | (G2 << 24);
+		  *dst++ = R2 | (B2 << 8) | (G2 << 16) | (R2 << 24);
+	       }
+	       memcpy (dst, dst - 3 * (image->width >> 1),
+		       3 * (image->width >> 1) * sizeof (unsigned int));
+	       dst += 3 * (image->width >> 1);
+	    }
+	 }
+	 else
+	 {
+	    unsigned int R1, G1, B1;		/* pixel in RGB color space */
+	    unsigned int R2, G2, B2;		/* pixel in RGB color space */
+	    int		 yval1, crval1, cbval1;	/* pixel1 in YCbCr space */
+	    int		 yval2, crval2, cbval2;	/* pixel2 in YCbCr space */
+	    int		 n;			/* pixel counter */
+	    unsigned int *dst;		        /* dithered pixel pointer */
+	    
+	    dst = (unsigned int *) out;
+	    
+	    for (n = (image->width * image->height) >> 2; n; n--)
+	    {
+#ifdef HAVE_SIGNED_SHIFT
+	       yval1  = (*yptr++ >> 4) + 128;
+	       yval2  = (*yptr++ >> 4) + 128;
+	       crval1 = *crptr++ >> 4;
+	       crval2 = *crptr++ >> 4;
+	       cbval1 = *cbptr++ >> 4;
+	       cbval2 = *cbptr++ >> 4;
+#else /* not HAVE_SIGNED_SHIFT */
+	       yval1  = *yptr++  / 16 + 128;
+	       yval2  = *yptr++  / 16 + 128;
+	       crval1 = *crptr++ / 16;
+	       crval2 = *crptr++ / 16;
+	       cbval1 = *cbptr++ / 16;
+	       cbval2 = *cbptr++ / 16;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+	       R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+	       G1 = gray_clip [yval1 + Cr_g_tab [crval1] + Cb_g_tab [cbval1]];
+	       B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+	       R2 = gray_clip [yval2 + Cr_r_tab [crval2]];
+	       G2 = gray_clip [yval2 + Cr_g_tab [crval2] + Cb_g_tab [cbval2]];
+	       B2 = gray_clip [yval2 + Cb_b_tab [cbval2]];
+
+	       *dst++ = B1 | (G1 << 8) | (R1 << 16) | (B2 << 24);
+	       *dst   = G2 | (R2 << 8);
+
+#ifdef HAVE_SIGNED_SHIFT
+	       yval1  = (*yptr++ >> 4) + 128;
+	       yval2  = (*yptr++ >> 4) + 128;
+	       crval1 = *crptr++ >> 4;
+	       crval2 = *crptr++ >> 4;
+	       cbval1 = *cbptr++ >> 4;
+	       cbval2 = *cbptr++ >> 4;
+#else /* not HAVE_SIGNED_SHIFT */
+	       yval1  = *yptr++  / 16 + 128;
+	       yval2  = *yptr++  / 16 + 128;
+	       crval1 = *crptr++ / 16;
+	       crval2 = *crptr++ / 16;
+	       cbval1 = *cbptr++ / 16;
+	       cbval2 = *cbptr++ / 16;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+	       R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+	       G1 = gray_clip [yval1 + Cr_g_tab [crval1] + Cb_g_tab [cbval1]];
+	       B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+	       R2 = gray_clip [yval2 + Cr_r_tab [crval2]];
+	       G2 = gray_clip [yval2 + Cr_g_tab [crval2] + Cb_g_tab [cbval2]];
+	       B2 = gray_clip [yval2 + Cb_b_tab [cbval2]];
+
+	       *dst++ |= (B1 << 16) | (G1 << 24);
+	       *dst++ = R1 | (B2 << 8) | (G2 << 16) | (R2 << 24);
+	    }
+	 }
+      }
+   }
+   else
+   {
+      unsigned int *dst;		/* pointer to dithered pixels */
+      word_t	   *src;		/* current pixel of frame */
+      unsigned int *y_table;
+
+      y_table = private->y_table;
+      dst     = (unsigned int *) out;
+      src     = image->pixels [GRAY];
+
+      if (private->double_resolution)
+      {
+	 int	   x, y;		/* pixel counter */
+	 unsigned *shift_clipping = gray_clip + 128;
+
+	 for (y = image->height; y; y--)
+	 {
+	    for (x = image->width >> 1; x; x--)
+	    {
+	       unsigned int val1, val2;
+#ifdef HAVE_SIGNED_SHIFT
+	       val1 = shift_clipping [*src++ >> 4];
+	       val2 = shift_clipping [*src++ >> 4];
+#else /* not HAVE_SIGNED_SHIFT */
+	       val1 = shift_clipping [*src++ / 16];
+	       val2 = shift_clipping [*src++ / 16];
+#endif /* not HAVE_SIGNED_SHIFT */
+
+	       *dst++ = val1 | (val1 << 8) | (val1 << 16) | (val1 << 24);
+	       *dst++ = val1 | (val1 << 8) | (val2 << 16) | (val2 << 24); 
+	       *dst++ = val2 | (val2 << 8) | (val2 << 16) | (val2 << 24);
+	    }
+
+	    memcpy (dst, dst - 3 * (image->width >> 1),
+		    3 * (image->width >> 1) * sizeof (unsigned int));
+	    dst += 3 * (image->width >> 1);
+	 }
+      }
+      else
+      {
+	 int	   n;			/* pixel counter */
+	 unsigned *shift_clipping = gray_clip + 128;
+
+	 for (n = (image->width * image->height) >> 2; n; n--)
+	 {
+	    unsigned int val1, val2;
+
+#ifdef HAVE_SIGNED_SHIFT
+	    val1 = shift_clipping [*src++ >> 4];
+	    val2 = shift_clipping [*src++ >> 4];
+#else /* not HAVE_SIGNED_SHIFT */
+	    val1 = shift_clipping [*src++ / 16];
+	    val2 = shift_clipping [*src++ / 16];
+#endif /* not HAVE_SIGNED_SHIFT */
+
+	    *dst++ = val1 | (val1 << 8)
+		     | (val1 << 16) | (val2 << 24);  /* RGBR */
+	    *dst   = val2 | (val2 << 8);             /* GB-- */
+
+#ifdef HAVE_SIGNED_SHIFT
+	    val1 = shift_clipping [*src++ >> 4];
+	    val2 = shift_clipping [*src++ >> 4];
+#else /* not HAVE_SIGNED_SHIFT */
+	    val1 = shift_clipping [*src++ / 16];
+	    val2 = shift_clipping [*src++ / 16];
+#endif /* not HAVE_SIGNED_SHIFT */
+	    
+	    *dst++ |= (val1 << 16) | (val1 << 24);   /* --RG */
+	    *dst++  = val1 | (val2 << 8)
+		      | (val2 << 16) | (val2 << 24); /* BRGB */
+	 }
+      }
+   }
+   
+   return 1;
+}
+
+static int 
+display_24_bit_rgb (const struct fiasco_renderer *this, unsigned char *ximage,
+		    const fiasco_image_t *fiasco_image)
+/*
+ *  Convert 'image' to 16 bit color bitmap.
+ *  If 'double_resolution' is true then double image size in both directions.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'out[]'	is filled with dithered image
+ */
+{
+   unsigned 	      *gray_clip = init_clipping ();
+   const image_t      *image;
+   renderer_private_t *private;
+   byte_t	      *out;
+
+   if (!gray_clip)
+      return 0;
+   if (!this)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "this");
+      return 0;
+   }
+   if (!ximage)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "ximage");
+      return 0;
+   }
+   if (!fiasco_image)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "fiasco_image");
+      return 0;
+   }
+
+   out 	   = (byte_t *) ximage;
+   image   = cast_image ((fiasco_image_t *) fiasco_image);
+   if (!image)
+      return 0;
+   private = (renderer_private_t *) this->private;
+   
+   if (image->color)
+   {
+      word_t 	   *cbptr, *crptr;	/* pointer to chroma bands */
+      word_t 	   *yptr;		/* pointers to lumincance band */
+      int 	   *Cr_r_tab, *Cr_g_tab, *Cb_g_tab, *Cb_b_tab;
+      unsigned int *r_table, *g_table, *b_table;
+
+      Cr_g_tab = private->Cr_g_tab;
+      Cr_r_tab = private->Cr_r_tab;
+      Cb_b_tab = private->Cb_b_tab;
+      Cb_g_tab = private->Cb_g_tab;
+      r_table  = private->r_table;
+      g_table  = private->g_table;
+      b_table  = private->b_table;
+      yptr     = image->pixels [Y];
+      cbptr    = image->pixels [Cb];
+      crptr    = image->pixels [Cr];
+
+      if (image->format == FORMAT_4_2_0)
+      {
+	 if (private->double_resolution)
+	 {
+	    int		  yval1;	/* lumincance pixel */
+	    int 	  crval1, cbval1; /* chroma pixels */
+	    int		  yval2;	/* pixel in YCbCr color space */
+	    unsigned int  R1, G1, B1;	/* pixel in RGB color space */
+	    unsigned int  R2, G2, B2;	/* pixel in RGB color space */
+	    int		  x, y;		/* pixel counter */
+	    unsigned int *dst;		/* pointer to dithered pixels */
+	    unsigned int *dst2;		/* pointers to dithered pixels */
+	    word_t	 *yptr2;	/* pointers to lumincance band */
+	    
+	    dst   = (unsigned int *) out;
+	    dst2  = dst + (image->width >> 1) * 3 * 2;
+	    yptr2 = yptr + image->width;
+	    
+	    for (y = image->height >> 1; y; y--)
+	    {
+	       for (x = image->width >> 1; x; x--)
+	       {
+#ifdef HAVE_SIGNED_SHIFT
+		  yval1  = (*yptr++ >> 4) + 128;
+		  yval2  = (*yptr++ >> 4) + 128;
+		  crval1 = *crptr++ >> 4;
+		  cbval1 = *cbptr++ >> 4;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval1  = *yptr++  / 16 + 128;
+		  yval2  = *yptr++  / 16 + 128;
+		  crval1 = *crptr++ / 16;
+		  cbval1 = *cbptr++ / 16;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+		  R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+		  G1 = gray_clip [yval1 + Cr_g_tab [crval1]
+				  + Cb_g_tab [cbval1]];
+		  B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+		  R2 = gray_clip [yval2 + Cr_r_tab [crval1]];
+		  G2 = gray_clip [yval2 + Cr_g_tab [crval1]
+				  + Cb_g_tab [cbval1]];
+		  B2 = gray_clip [yval2 + Cb_b_tab [cbval1]];
+
+		  *dst++ = R1 | (G1 << 8) | (B1 << 16) | (R1 << 24);
+		  *dst++ = G1 | (B1 << 8) | (R2 << 16) | (G2 << 24);
+		  *dst++ = B2 | (R2 << 8) | (G2 << 16) | (B2 << 24);
+
+#ifdef HAVE_SIGNED_SHIFT
+		  yval1  = (*yptr2++ >> 4) + 128;
+		  yval2  = (*yptr2++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval1  = *yptr2++  / 16 + 128;
+		  yval2  = *yptr2++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+		  R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+		  G1 = gray_clip [yval1 + Cr_g_tab [crval1]
+				  + Cb_g_tab [cbval1]];
+		  B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+		  R2 = gray_clip [yval2 + Cr_r_tab [crval1]];
+		  G2 = gray_clip [yval2 + Cr_g_tab [crval1]
+				  + Cb_g_tab [cbval1]];
+		  B2 = gray_clip [yval2 + Cb_b_tab [cbval1]];
+
+		  *dst2++ = R1 | (G1 << 8) | (B1 << 16) | (R1 << 24);
+		  *dst2++ = G1 | (B1 << 8) | (R2 << 16) | (G2 << 24);
+		  *dst2++ = B2 | (R2 << 8) | (G2 << 16) | (B2 << 24);
+	       }
+	       memcpy (dst, dst - (image->width >> 1) * 3,
+		       (image->width >> 1) * 3 * sizeof (unsigned int));
+	       memcpy (dst2, dst2 - (image->width >> 1) * 3, 
+		       (image->width >> 1) * 3 * sizeof (unsigned int));
+	       dst   += (image->width >> 1) * 3 * 3;
+	       dst2  += (image->width >> 1) * 3 * 3;
+	       yptr  += image->width;
+	       yptr2 += image->width;
+	    }
+	 }
+	 else
+	 {
+	    int		  yval1;	/* lumincance pixel */
+	    int 	  crval1, cbval1; /* chroma pixels */
+	    int		  yval2;	/* pixel in YCbCr color space */
+	    unsigned int  R1, G1, B1;	/* pixel in RGB color space */
+	    unsigned int  R2, G2, B2;	/* pixel in RGB color space */
+	    int		  x, y;		/* pixel counter */
+	    unsigned int *dst;		/* pointer to dithered pixels */
+	    unsigned int *dst2;		/* pointers to dithered pixels */
+	    word_t	 *yptr2;	/* pointers to lumincance band */
+	    
+	    dst   = (unsigned int *) out;
+	    dst2  = dst + (image->width >> 2) * 3;
+	    yptr2 = yptr + image->width;
+	    
+	    for (y = image->height >> 1; y; y--)
+	    {
+	       for (x = image->width >> 2; x; x--)
+	       {
+#ifdef HAVE_SIGNED_SHIFT
+		  yval1  = (*yptr++ >> 4) + 128;
+		  yval2  = (*yptr++ >> 4) + 128;
+		  crval1 = *crptr++ >> 4;
+		  cbval1 = *cbptr++ >> 4;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval1  = *yptr++  / 16 + 128;
+		  yval2  = *yptr++  / 16 + 128;
+		  crval1 = *crptr++ / 16;
+		  cbval1 = *cbptr++ / 16;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+		  R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+		  G1 = gray_clip [yval1 + Cr_g_tab [crval1]
+				  + Cb_g_tab [cbval1]];
+		  B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+		  R2 = gray_clip [yval2 + Cr_r_tab [crval1]];
+		  G2 = gray_clip [yval2 + Cr_g_tab [crval1]
+				  + Cb_g_tab [cbval1]];
+		  B2 = gray_clip [yval2 + Cb_b_tab [cbval1]];
+
+		  *dst++ = R1 | (G1 << 8) | (B1 << 16) | (R2 << 24);
+		  *dst   = G2 | (B2 << 8);
+
+#ifdef HAVE_SIGNED_SHIFT
+		  yval1  = (*yptr2++ >> 4) + 128;
+		  yval2  = (*yptr2++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval1  = *yptr2++  / 16 + 128;
+		  yval2  = *yptr2++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+		  R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+		  G1 = gray_clip [yval1 + Cr_g_tab [crval1]
+				  + Cb_g_tab [cbval1]];
+		  B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+		  R2 = gray_clip [yval2 + Cr_r_tab [crval1]];
+		  G2 = gray_clip [yval2 + Cr_g_tab [crval1]
+				  + Cb_g_tab [cbval1]];
+		  B2 = gray_clip [yval2 + Cb_b_tab [cbval1]];
+
+		  *dst2++ = R1 | (G1 << 8) | (B1 << 16) | (R2 << 24);
+		  *dst2   = G2 | (B2 << 8);
+	       
+#ifdef HAVE_SIGNED_SHIFT
+		  yval1  = (*yptr++ >> 4) + 128;
+		  yval2  = (*yptr++ >> 4) + 128;
+		  crval1 = *crptr++ >> 4;
+		  cbval1 = *cbptr++ >> 4;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval1  = *yptr++  / 16 + 128;
+		  yval2  = *yptr++  / 16 + 128;
+		  crval1 = *crptr++ / 16;
+		  crval2 = *crptr++ / 16;
+		  cbval1 = *cbptr++ / 16;
+		  cbval2 = *cbptr++ / 16;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+		  R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+		  G1 = gray_clip [yval1 + Cr_g_tab [crval1]
+				  + Cb_g_tab [cbval1]];
+		  B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+		  R2 = gray_clip [yval2 + Cr_r_tab [crval1]];
+		  G2 = gray_clip [yval2 + Cr_g_tab [crval1]
+				  + Cb_g_tab [cbval1]];
+		  B2 = gray_clip [yval2 + Cb_b_tab [cbval1]];
+
+		  *dst++ |= (R1 << 16) | (G1 << 24);
+		  *dst++ = B1 | (R2 << 8) | (G2 << 16) | (B2 << 24);
+
+#ifdef HAVE_SIGNED_SHIFT
+		  yval1  = (*yptr2++ >> 4) + 128;
+		  yval2  = (*yptr2++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval1  = *yptr2++  / 16 + 128;
+		  yval2  = *yptr2++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+		  R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+		  G1 = gray_clip [yval1 + Cr_g_tab [crval1]
+				  + Cb_g_tab [cbval1]];
+		  B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+		  R2 = gray_clip [yval2 + Cr_r_tab [crval1]];
+		  G2 = gray_clip [yval2 + Cr_g_tab [crval1]
+				  + Cb_g_tab [cbval1]];
+		  B2 = gray_clip [yval2 + Cb_b_tab [cbval1]];
+
+		  *dst2++ |= (R1 << 16) | (G1 << 24);
+		  *dst2++ = B1 | (R2 << 8) | (G2 << 16) | (B2 << 24);
+	       }
+	       dst   += (image->width >> 2) * 3;
+	       dst2  += (image->width >> 2) * 3;
+	       yptr  += image->width;
+	       yptr2 += image->width;
+	    }
+	 }
+      }
+      else				/* 4:4:4 format */
+      {
+	 if (private->double_resolution)
+	 {
+	    unsigned int R1, G1, B1;		/* pixel1 in RGB color space */
+	    unsigned int R2, G2, B2;		/* pixel2 in RGB color space */
+	    int		 yval1, crval1, cbval1;	/* pixel1 in YCbCr space */
+	    int		 yval2, crval2, cbval2;	/* pixel2 in YCbCr space */
+	    int		 x, y;			/* pixel counter */
+	    unsigned int *dst;		        /* dithered pixel pointer */
+	    
+	    dst = (unsigned int *) out;
+	    
+	    for (y = image->height; y; y--)
+	    {
+	       for (x = image->width >> 1; x; x--)
+	       {
+#ifdef HAVE_SIGNED_SHIFT
+		  yval1  = (*yptr++ >> 4) + 128;
+		  yval2  = (*yptr++ >> 4) + 128;
+		  crval1 = *crptr++ >> 4;
+		  crval2 = *crptr++ >> 4;
+		  cbval1 = *cbptr++ >> 4;
+		  cbval2 = *cbptr++ >> 4;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval1  = *yptr++  / 16 + 128;
+		  yval2  = *yptr++  / 16 + 128;
+		  crval1 = *crptr++ / 16;
+		  crval2 = *crptr++ / 16;
+		  cbval1 = *cbptr++ / 16;
+		  cbval2 = *cbptr++ / 16;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  
+		  R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+		  G1 = gray_clip [yval1 + Cr_g_tab [crval1]
+				  + Cb_g_tab [cbval1]];
+		  B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+		  R2 = gray_clip [yval2 + Cr_r_tab [crval2]];
+		  G2 = gray_clip [yval2 + Cr_g_tab [crval2]
+				  + Cb_g_tab [cbval2]];
+		  B2 = gray_clip [yval2 + Cb_b_tab [cbval2]];
+
+		  *dst++ = R1 | (G1 << 8) | (B1 << 16) | (R1 << 24);
+		  *dst++ = G1 | (B1 << 8) | (R2 << 16) | (G2 << 24);
+		  *dst++ = B2 | (R2 << 8) | (G2 << 16) | (B2 << 24);
+	       }
+	       memcpy (dst, dst - 3 * (image->width >> 1),
+		       3 * (image->width >> 1) * sizeof (unsigned int));
+	       dst += 3 * (image->width >> 1);
+	    }
+	 }
+	 else
+	 {
+	    unsigned int R1, G1, B1;		/* pixel in RGB color space */
+	    unsigned int R2, G2, B2;		/* pixel in RGB color space */
+	    int		 yval1, crval1, cbval1;	/* pixel1 in YCbCr space */
+	    int		 yval2, crval2, cbval2;	/* pixel2 in YCbCr space */
+	    int		 n;			/* pixel counter */
+	    unsigned int *dst;		        /* dithered pixel pointer */
+	    
+	    dst = (unsigned int *) out;
+	    
+	    for (n = (image->width * image->height) >> 2; n; n--)
+	    {
+#ifdef HAVE_SIGNED_SHIFT
+	       yval1  = (*yptr++ >> 4) + 128;
+	       yval2  = (*yptr++ >> 4) + 128;
+	       crval1 = *crptr++ >> 4;
+	       crval2 = *crptr++ >> 4;
+	       cbval1 = *cbptr++ >> 4;
+	       cbval2 = *cbptr++ >> 4;
+#else /* not HAVE_SIGNED_SHIFT */
+	       yval1  = *yptr++  / 16 + 128;
+	       yval2  = *yptr++  / 16 + 128;
+	       crval1 = *crptr++ / 16;
+	       crval2 = *crptr++ / 16;
+	       cbval1 = *cbptr++ / 16;
+	       cbval2 = *cbptr++ / 16;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+	       R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+	       G1 = gray_clip [yval1 + Cr_g_tab [crval1] + Cb_g_tab [cbval1]];
+	       B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+	       R2 = gray_clip [yval2 + Cr_r_tab [crval2]];
+	       G2 = gray_clip [yval2 + Cr_g_tab [crval2] + Cb_g_tab [cbval2]];
+	       B2 = gray_clip [yval2 + Cb_b_tab [cbval2]];
+
+	       *dst++ = R1 | (G1 << 8) | (B1 << 16) | (R2 << 24);
+	       *dst   = G2 | (B2 << 8);
+
+#ifdef HAVE_SIGNED_SHIFT
+	       yval1  = (*yptr++ >> 4) + 128;
+	       yval2  = (*yptr++ >> 4) + 128;
+	       crval1 = *crptr++ >> 4;
+	       crval2 = *crptr++ >> 4;
+	       cbval1 = *cbptr++ >> 4;
+	       cbval2 = *cbptr++ >> 4;
+#else /* not HAVE_SIGNED_SHIFT */
+	       yval1  = *yptr++  / 16 + 128;
+	       yval2  = *yptr++  / 16 + 128;
+	       crval1 = *crptr++ / 16;
+	       crval2 = *crptr++ / 16;
+	       cbval1 = *cbptr++ / 16;
+	       cbval2 = *cbptr++ / 16;
+#endif /* not HAVE_SIGNED_SHIFT */
+
+	       R1 = gray_clip [yval1 + Cr_r_tab [crval1]];
+	       G1 = gray_clip [yval1 + Cr_g_tab [crval1] + Cb_g_tab [cbval1]];
+	       B1 = gray_clip [yval1 + Cb_b_tab [cbval1]];
+	       R2 = gray_clip [yval2 + Cr_r_tab [crval2]];
+	       G2 = gray_clip [yval2 + Cr_g_tab [crval2] + Cb_g_tab [cbval2]];
+	       B2 = gray_clip [yval2 + Cb_b_tab [cbval2]];
+
+	       *dst++ |= (R1 << 16) | (G1 << 24);
+	       *dst++ = B1 | (R2 << 8) | (G2 << 16) | (B2 << 24);
+	    }
+	 }
+      }
+   }
+   else
+   {
+      unsigned int *dst;		/* pointer to dithered pixels */
+      word_t	   *src;		/* current pixel of frame */
+      unsigned int *y_table;
+
+      y_table = private->y_table;
+      dst     = (unsigned int *) out;
+      src     = image->pixels [GRAY];
+
+      if (private->double_resolution)
+      {
+	 int	   x, y;		/* pixel counter */
+	 unsigned *shift_clipping = gray_clip + 128;
+
+	 for (y = image->height; y; y--)
+	 {
+	    for (x = image->width >> 1; x; x--)
+	    {
+	       unsigned int val1, val2;
+#ifdef HAVE_SIGNED_SHIFT
+	       val1 = shift_clipping [*src++ >> 4];
+	       val2 = shift_clipping [*src++ >> 4];
+#else /* not HAVE_SIGNED_SHIFT */
+	       val1 = shift_clipping [*src++ / 16];
+	       val2 = shift_clipping [*src++ / 16];
+#endif /* not HAVE_SIGNED_SHIFT */
+
+	       *dst++ = val1 | (val1 << 8) | (val1 << 16) | (val1 << 24);
+	       *dst++ = val1 | (val1 << 8) | (val2 << 16) | (val2 << 24); 
+	       *dst++ = val2 | (val2 << 8) | (val2 << 16) | (val2 << 24);
+	    }
+
+	    memcpy (dst, dst - 3 * (image->width >> 1),
+		    3 * (image->width >> 1) * sizeof (unsigned int));
+	    dst += 3 * (image->width >> 1);
+	 }
+      }
+      else
+      {
+	 int	   n;			/* pixel counter */
+	 unsigned *shift_clipping = gray_clip + 128;
+
+	 for (n = (image->width * image->height) >> 2; n; n--)
+	 {
+	    unsigned int val1, val2;
+
+#ifdef HAVE_SIGNED_SHIFT
+	    val1 = shift_clipping [*src++ >> 4];
+	    val2 = shift_clipping [*src++ >> 4];
+#else /* not HAVE_SIGNED_SHIFT */
+	    val1 = shift_clipping [*src++ / 16];
+	    val2 = shift_clipping [*src++ / 16];
+#endif /* not HAVE_SIGNED_SHIFT */
+
+	    *dst++ = val1 | (val1 << 8)
+		     | (val1 << 16) | (val2 << 24);  /* RGBR */
+	    *dst   = val2 | (val2 << 8);             /* GB-- */
+
+#ifdef HAVE_SIGNED_SHIFT
+	    val1 = shift_clipping [*src++ >> 4];
+	    val2 = shift_clipping [*src++ >> 4];
+#else /* not HAVE_SIGNED_SHIFT */
+	    val1 = shift_clipping [*src++ / 16];
+	    val2 = shift_clipping [*src++ / 16];
+#endif /* not HAVE_SIGNED_SHIFT */
+	    
+	    *dst++ |= (val1 << 16) | (val1 << 24);   /* --RG */
+	    *dst++  = val1 | (val2 << 8)
+		      | (val2 << 16) | (val2 << 24); /* BRGB */
+	 }
+      }
+   }
+   
+   return 1;
+}
+
+static int 
+display_32_bit (const struct fiasco_renderer *this, unsigned char *ximage,
+		const fiasco_image_t *fiasco_image)
+/*
+ *  Convert 'image' to 16 bit color bitmap.
+ *  If 'double_resolution' is true then double image size in both directions.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'out[]'	is filled with dithered image
+ */
+{
+   const image_t      *image;
+   renderer_private_t *private;
+   byte_t	      *out;
+   
+   if (!this)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "this");
+      return 0;
+   }
+   if (!ximage)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "ximage");
+      return 0;
+   }
+   if (!fiasco_image)
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "fiasco_image");
+      return 0;
+   }
+
+   out 	   = (byte_t *) ximage;
+   private = (renderer_private_t *) this->private;
+   image   = cast_image ((fiasco_image_t *) fiasco_image);
+   if (!image)
+      return 0;
+   
+   if (image->color)
+   {
+      word_t 	   *cbptr, *crptr;	/* pointer to chroma bands */
+      word_t 	   *yptr;		/* pointers to lumincance band */
+      int     	    yval, crval, cbval;	/* pixel value in YCbCr color space */
+      int     	    R, G, B;		/* pixel value in RGB color space */
+      int     	    n;			/* pixel counter */
+      int     	    x, y;		/* pixel coordinates */
+      int 	   *Cr_r_tab, *Cr_g_tab, *Cb_g_tab, *Cb_b_tab;
+      unsigned int *r_table, *g_table, *b_table;
+
+      Cr_g_tab = private->Cr_g_tab;
+      Cr_r_tab = private->Cr_r_tab;
+      Cb_b_tab = private->Cb_b_tab;
+      Cb_g_tab = private->Cb_g_tab;
+      r_table  = private->r_table;
+      g_table  = private->g_table;
+      b_table  = private->b_table;
+      yptr  = image->pixels [Y];
+      cbptr = image->pixels [Cb];
+      crptr = image->pixels [Cr];
+
+      if (image->format == FORMAT_4_2_0)
+      {
+	 unsigned int	*dst, *dst2;	/* pointers to dithered pixels */
+	 word_t		*yptr2;		/* pointers to lumincance band */
+
+	 if (private->double_resolution)
+	 {
+	    yptr2 = yptr + image->width;
+	    dst  = (unsigned int *) out;
+	    dst2 = dst + 4 * image->width;
+	    for (y = image->height / 2; y; y--)
+	    {
+	       for (x = image->width / 2; x; x--)
+	       {
+#ifdef HAVE_SIGNED_SHIFT
+		  crval = *crptr++ >> 4;
+		  cbval = *cbptr++ >> 4;
+		  yval  = (*yptr++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  crval = *crptr++ / 16;
+		  cbval = *cbptr++ / 16;
+		  yval  = *yptr++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  *dst++ = r_table [R] | g_table [G] | b_table [B];
+		  *dst++ = r_table [R] | g_table [G] | b_table [B];
+	       
+#ifdef HAVE_SIGNED_SHIFT
+		  yval  = (*yptr++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval  = *yptr++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  *dst++ = r_table [R] | g_table [G] | b_table [B];
+		  *dst++ = r_table [R] | g_table [G] | b_table [B];
+
+#ifdef HAVE_SIGNED_SHIFT
+		  yval  = (*yptr2++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval  = *yptr2++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  *dst2++ = r_table [R] | g_table [G] | b_table [B];
+		  *dst2++ = r_table [R] | g_table [G] | b_table [B];
+
+#ifdef HAVE_SIGNED_SHIFT
+		  yval  = (*yptr2++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval  = *yptr2++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  *dst2++ = r_table [R] | g_table [G] | b_table [B];
+		  *dst2++ = r_table [R] | g_table [G] | b_table [B];
+	       }
+	       memcpy (dst, dst - 2 * image->width,
+		       2 * image->width * sizeof (unsigned int));
+	       memcpy (dst2, dst2 - 2 * image->width,
+		       2 * image->width * sizeof (unsigned int));
+	       yptr  += image->width;
+	       yptr2 += image->width;
+	       dst   += 3 * image->width * 2;
+	       dst2  += 3 * image->width * 2;
+	    }
+	 }
+	 else
+	 {
+	    yptr2 = yptr + image->width;
+	    dst   = (unsigned int *) out;
+	    dst2  = dst + image->width;
+	    
+	    for (y = image->height / 2; y; y--)
+	    {
+	       for (x = image->width / 2; x; x--)
+	       {
+#ifdef HAVE_SIGNED_SHIFT
+		  crval = *crptr++ >> 4;
+		  cbval = *cbptr++ >> 4;
+		  yval  = (*yptr++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  crval = *crptr++ / 16;
+		  cbval = *cbptr++ / 16;
+		  yval  = *yptr++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  *dst++ = r_table [R] | g_table [G] | b_table [B];
+	       
+#ifdef HAVE_SIGNED_SHIFT
+		  yval  = (*yptr++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval  = *yptr++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  *dst++ = r_table [R] | g_table [G] | b_table [B];
+
+#ifdef HAVE_SIGNED_SHIFT
+		  yval  = (*yptr2++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval  = *yptr2++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  *dst2++ = r_table [R] | g_table [G] | b_table [B];
+
+#ifdef HAVE_SIGNED_SHIFT
+		  yval  = (*yptr2++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  yval  = *yptr2++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  *dst2++ = r_table [R] | g_table [G] | b_table [B];
+	       }
+	       yptr  += image->width;
+	       yptr2 += image->width;
+	       dst   += image->width;
+	       dst2  += image->width;
+	    }
+	 }
+      }
+      else				/* 4:4:4 format */
+      {
+	 if (private->double_resolution)
+	 {
+	    unsigned int *dst;		/* pointer to dithered pixels */
+	    
+	    dst = (unsigned int *) out;
+	    
+	    for (y = image->height; y; y--)
+	    {
+	       for (x = image->width; x; x--)
+	       {
+#ifdef HAVE_SIGNED_SHIFT
+		  crval = *crptr++ >> 4;
+		  cbval = *cbptr++ >> 4;
+		  yval  = (*yptr++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+		  crval = *crptr++ / 16;
+		  cbval = *cbptr++ / 16;
+		  yval  = *yptr++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+		  R = yval + Cr_r_tab [crval];
+		  G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+		  B = yval + Cb_b_tab [cbval];
+		  
+		  *dst++ = r_table [R] | g_table [G] | b_table [B];
+		  *dst++ = r_table [R] | g_table [G] | b_table [B];
+	       }
+	       memcpy (dst, dst - 2 * image->width,
+		       2 * image->width * sizeof (unsigned int));
+	       dst += image->width * 2;
+	    }
+	 }
+	 else
+	 {
+	    unsigned int *dst;		/* pointer to dithered pixels */
+
+	    dst  = (unsigned int *) out;
+	    
+	    for (n = image->width * image->height; n; n--)
+	    {
+#ifdef HAVE_SIGNED_SHIFT
+	       crval = *crptr++ >> 4;
+	       cbval = *cbptr++ >> 4;
+	       yval  = (*yptr++ >> 4) + 128;
+#else /* not HAVE_SIGNED_SHIFT */
+	       crval = *crptr++ / 16;
+	       cbval = *cbptr++ / 16;
+	       yval  = *yptr++  / 16 + 128;
+#endif /* not HAVE_SIGNED_SHIFT */
+	       R = yval + Cr_r_tab [crval];
+	       G = yval + Cr_g_tab [crval] + Cb_g_tab [cbval];
+	       B = yval + Cb_b_tab [cbval];
+
+	       *dst++ = r_table [R] | g_table [G] | b_table [B];
+	    }
+	 }
+      }
+   }
+   else
+   {
+      unsigned int *dst;		/* pointer to dithered pixels */
+      word_t	   *src;		/* current pixel of frame */
+      unsigned int *y_table;
+
+      y_table = private->y_table;
+      dst     = (unsigned int *) out;
+      src     = image->pixels [GRAY];
+      
+      if (private->double_resolution)
+      {
+	 int x, y;			/* pixel coordinates */
+	 
+	 for (y = image->height; y; y--)
+	 {
+	    for (x = image->width; x; x--)
+	    {
+	       int value;
+
+#ifdef HAVE_SIGNED_SHIFT
+	       value = y_table [*src++ >> 4];
+#else /* not HAVE_SIGNED_SHIFT */
+	       value = y_table [*src++ / 16];
+#endif /* not HAVE_SIGNED_SHIFT */
+	       *dst++ = value;
+	       *dst++ = value;
+	    }
+
+	    memcpy (dst, dst - 2 * image->width,
+		    2 * image->width * sizeof (unsigned int));
+	    dst += 2 * image->width;
+	 }
+      }
+      else
+      {
+	 int n;				/* pixel counter */
+	 
+	 for (n = image->width * image->height; n; n--)
+#ifdef HAVE_SIGNED_SHIFT
+	    *dst++ = y_table [*src++ >> 4];
+#else /* not HAVE_SIGNED_SHIFT */
+	    *dst++ = y_table [*src++ / 16];
+#endif /* not HAVE_SIGNED_SHIFT */
+      }
+   }
+   
+   return 1;
+}
+
+
+ 
diff --git a/converter/other/fiasco/lib/dither.h b/converter/other/fiasco/lib/dither.h
new file mode 100644
index 00000000..71f9d3c3
--- /dev/null
+++ b/converter/other/fiasco/lib/dither.h
@@ -0,0 +1,27 @@
+/*
+ *  dither.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:49:37 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _DITHER_H
+#define _DITHER_H
+
+typedef struct renderer_private
+{
+   int 	       	*Cr_r_tab, *Cr_g_tab, *Cb_g_tab, *Cb_b_tab;
+   unsigned int *r_table, *g_table, *b_table, *y_table;
+   bool_t	double_resolution;
+} renderer_private_t;
+
+#endif /* _DITHER_H */
diff --git a/converter/other/fiasco/lib/error.c b/converter/other/fiasco/lib/error.c
new file mode 100644
index 00000000..b858badf
--- /dev/null
+++ b/converter/other/fiasco/lib/error.c
@@ -0,0 +1,326 @@
+/*
+ *  error.c:		Error handling
+ *
+ *  Written by:		Stefan Frank
+ *			Ullrich Hafner
+ *  
+ *  Credits:	Modelled after variable argument routines from Jef
+ *		Poskanzer's pbmplus package. 
+ *
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+
+    "int dummy = " change to int dummy; dummy =" for Netpbm to avoid 
+    unused variable warning.
+
+ */
+
+/*
+ *  $Date: 2000/06/14 20:49:37 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#define _ERROR_C
+
+#include "config.h"
+
+#include <stdio.h>
+#include <errno.h>
+
+#if STDC_HEADERS
+#	include <stdarg.h>
+#	define VA_START(args, lastarg) va_start(args, lastarg)
+#else  /* not STDC_HEADERS */
+#	include <varargs.h>
+#	define VA_START(args, lastarg) va_start(args)
+#endif /* not STDC_HEADERS */
+#if HAVE_STRING_H
+#	include <string.h>
+#else /* not HAVE_STRING_H */
+#	include <strings.h>
+#endif /* not HAVE_STRING_H */
+
+#if HAVE_SETJMP_H
+#	include <setjmp.h>
+#endif /* HAVE_SETJMP_H */
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "misc.h"
+#include "fiasco.h"
+
+/*****************************************************************************
+
+			     local variables
+  
+*****************************************************************************/
+
+static fiasco_verbosity_e  verboselevel  = FIASCO_SOME_VERBOSITY;
+static char 	      	  *error_message = NULL;
+
+#if HAVE_SETJMP_H
+jmp_buf env;
+#endif /* HAVE_SETJMP_H */
+
+/*****************************************************************************
+
+			       public code
+  
+*****************************************************************************/
+
+void
+set_error (const char *format, ...)
+/*
+ *  Set error text to given string.
+ */
+{
+   va_list     args;
+   unsigned    len = 0;
+   const char *str = format;
+   
+   VA_START (args, format);
+
+   len = strlen (format);
+   while ((str = strchr (str, '%')))
+   {
+      str++;
+      if (*str == 's')
+      {
+	 char *vstring = va_arg (args, char *);
+	 len += strlen (vstring);
+      }
+      else if (*str == 'd')
+      {
+	 int dummy;
+     dummy = va_arg (args, int);
+	 len += 10;
+      }
+      else if (*str == 'c')
+      {
+	 int dummy;
+     dummy = va_arg (args, int);
+	 len += 1;
+      }
+      else
+	 return;
+      str++;
+   }
+   va_end(args);
+
+   VA_START (args, format);
+
+   if (error_message)
+      Free (error_message);
+   error_message = Calloc (len, sizeof (char));
+   
+#if HAVE_VPRINTF
+   vsprintf (error_message, format, args);
+#elif HAVE_DOPRNT
+   _doprnt (format, args, stderr);
+#endif /* HAVE_DOPRNT */
+
+   va_end (args);
+}
+
+void
+error (const char *format, ...)
+/*
+ *  Set error text to given string.
+ */
+{
+   va_list     args;
+   unsigned    len = 0;
+   const char *str = format;
+   
+   VA_START (args, format);
+
+   len = strlen (format);
+   while ((str = strchr (str, '%')))
+   {
+      str++;
+      if (*str == 's')
+      {
+	 char *vstring = va_arg (args, char *);
+	 len += strlen (vstring);
+      }
+      else if (*str == 'd')
+      {
+	 int dummy;
+     dummy = va_arg (args, int);
+	 len += 10;
+      }
+      else if (*str == 'c')
+      {
+	 int dummy;
+     dummy = va_arg (args, int);
+	 len += 1;
+      }
+      else
+      {
+#if HAVE_SETJMP_H
+	 longjmp (env, 1);
+#else /* not HAVE_SETJMP_H */
+	 exit (1);
+#endif /* HAVE_SETJMP_H */
+      };
+      
+      str++;
+   }
+   va_end(args);
+
+   VA_START (args, format);
+
+   if (error_message)
+      Free (error_message);
+   error_message = Calloc (len, sizeof (char));
+   
+#if HAVE_VPRINTF
+   vsprintf (error_message, format, args);
+#elif HAVE_DOPRNT
+   _doprnt (format, args, stderr);
+#endif /* HAVE_DOPRNT */
+
+   va_end (args);
+   
+#if HAVE_SETJMP_H
+   longjmp (env, 1);
+#else /* not HAVE_SETJMP_H */
+   exit (1);
+#endif /* HAVE_SETJMP_H */
+}
+
+const char *
+fiasco_get_error_message (void)
+/*
+ *  Return value:
+ *	Last error message of FIASCO library.
+ */
+{
+   return error_message ? error_message : "";
+}
+
+const char *
+get_system_error (void)
+{
+   return strerror (errno);
+}
+
+void
+file_error (const char *filename)
+/*
+ *  Print file error message and exit.
+ *
+ *  No return value.
+ */
+{
+   error ("File `%s': I/O Error - %s.", filename, get_system_error ());
+}
+
+void 
+warning (const char *format, ...)
+/*
+ *  Issue a warning and continue execution.
+ *
+ *  No return value.
+ */
+{
+   va_list	args;
+
+   VA_START (args, format);
+
+   if (verboselevel == FIASCO_NO_VERBOSITY)
+      return;
+	
+   fprintf (stderr, "Warning: ");
+#if HAVE_VPRINTF
+   vfprintf (stderr, format, args);
+#elif HAVE_DOPRNT
+   _doprnt (format, args, stderr);
+#endif /* HAVE_DOPRNT */
+   fputc ('\n', stderr);
+
+   va_end (args);
+}
+
+void 
+message (const char *format, ...)
+/*
+ *  Print a message to stderr.
+ */
+{
+   va_list args;
+
+   VA_START (args, format);
+
+   if (verboselevel == FIASCO_NO_VERBOSITY)
+      return;
+
+#if HAVE_VPRINTF
+   vfprintf (stderr, format, args);
+#elif HAVE_DOPRNT
+   _doprnt (format, args, stderr);
+#endif /* HAVE_DOPRNT */
+   fputc ('\n', stderr);
+   va_end (args);
+}
+
+void 
+debug_message (const char *format, ...)
+/*
+ *  Print a message to stderr.
+ */
+{
+   va_list args;
+
+   VA_START (args, format);
+
+   if (verboselevel < FIASCO_ULTIMATE_VERBOSITY)
+      return;
+
+   fprintf (stderr, "*** ");
+#if HAVE_VPRINTF
+   vfprintf (stderr, format, args);
+#elif HAVE_DOPRNT
+   _doprnt (format, args, stderr);
+#endif /* HAVE_DOPRNT */
+   fputc ('\n', stderr);
+   va_end (args);
+}
+
+void
+info (const char *format, ...)
+/*
+ *  Print a message to stderr. Do not append a newline.
+ */
+{
+   va_list args;
+
+   VA_START (args, format);
+
+   if (verboselevel == FIASCO_NO_VERBOSITY)
+      return;
+
+#if HAVE_VPRINTF
+   vfprintf (stderr, format, args);
+#elif HAVE_DOPRNT
+   _doprnt (format, args, stderr);
+#endif /* HAVE_DOPRNT */
+   fflush (stderr);
+   va_end (args);
+}
+
+void
+fiasco_set_verbosity (fiasco_verbosity_e level)
+{
+   verboselevel = level;
+}
+
+fiasco_verbosity_e
+fiasco_get_verbosity (void)
+{
+   return verboselevel;
+}
diff --git a/converter/other/fiasco/lib/error.h b/converter/other/fiasco/lib/error.h
new file mode 100644
index 00000000..288b25f4
--- /dev/null
+++ b/converter/other/fiasco/lib/error.h
@@ -0,0 +1,39 @@
+/*
+ *  error.h
+ *  
+ *  Written by:     Stefan Frank
+ *          Ullrich Hafner
+ *
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+#ifndef ERROR_H_INCLUDED
+#define ERROR_H_INCLUDED
+
+void
+set_error (const char *format, ...);
+void
+error (const char *format, ...);
+void
+file_error (const char *filename);
+void
+message (const char *format, ...);
+void 
+debug_message (const char *format, ...);
+void
+warning (const char *format, ...);
+void 
+info (const char *format, ...);
+const char *
+get_system_error (void);
+
+#include <setjmp.h>
+extern jmp_buf env;
+
+#define try         if (setjmp (env) == 0)
+#define catch           else
+
+#include <assert.h>
+
+#endif
diff --git a/converter/other/fiasco/lib/image.c b/converter/other/fiasco/lib/image.c
new file mode 100644
index 00000000..019ba03c
--- /dev/null
+++ b/converter/other/fiasco/lib/image.c
@@ -0,0 +1,512 @@
+/*
+ *  image.c:		Input and output of PNM images.
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/15 17:21:30 $
+ *  $Author: hafner $
+ *  $Revision: 5.2 $
+ *  $State: Exp $
+ */
+
+#include "pnm.h"
+
+#include <string.h>
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "fiasco.h"
+#include "misc.h"
+#include "image.h"
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static void
+init_chroma_tables (void);
+
+/*****************************************************************************
+
+				local variables
+  
+*****************************************************************************/
+static int *Cr_r_tab = NULL;
+static int *Cr_g_tab = NULL;
+static int *Cb_g_tab = NULL;
+static int *Cb_b_tab = NULL;
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+fiasco_image_t *
+fiasco_image_new (const char *filename)
+/*
+ *  FIASCO image constructor.
+ *  Allocate memory for the FIASCO image structure and
+ *  load the specified image `filename'. The image has to be in
+ *  raw pgm or ppm format.
+ *
+ *  Return value:
+ *	pointer to the new image structure
+ *	or NULL in case of an error
+ */
+{
+   try
+   {
+      fiasco_image_t *image = Calloc (1, sizeof (fiasco_image_t));
+
+      image->private 	= read_image (filename);
+      image->delete  	= fiasco_image_delete;
+      image->get_width  = fiasco_image_get_width;
+      image->get_height = fiasco_image_get_height;
+      image->is_color  	= fiasco_image_is_color;
+
+      return image;
+   }
+   catch
+   {
+      return NULL;
+   }
+}
+
+void
+fiasco_image_delete (fiasco_image_t *image)
+/*
+ *  FIASCO image destructor.
+ *  Free memory of FIASCO image struct.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	structure 'image' is discarded.
+ */
+{
+   image_t *this = cast_image (image);
+
+   if (!this)
+      return;
+
+   try
+   {
+      free_image (this);
+   }
+   catch
+   {
+      return;
+   }
+}
+
+unsigned
+fiasco_image_get_width (fiasco_image_t *image)
+{
+   image_t *this = cast_image (image);
+
+   if (!this)
+      return 0;
+   else
+      return this->width;
+}
+
+unsigned
+fiasco_image_get_height (fiasco_image_t *image)
+{
+   image_t *this = cast_image (image);
+
+   if (!this)
+      return 0;
+   else
+      return this->width;
+}
+
+int
+fiasco_image_is_color (fiasco_image_t *image)
+{
+   image_t *this = cast_image (image);
+
+   if (!this)
+      return 0;
+   else
+      return this->color;
+}
+
+image_t *
+cast_image (fiasco_image_t *image)
+/*
+ *  Cast pointer `image' to type image_t.
+ *  Check whether `image' is a valid object of type image_t.
+ *
+ *  Return value:
+ *	pointer to dfiasco_t struct on success
+ *      NULL otherwise
+ */
+{
+   image_t *this = (image_t *) image->private;
+   if (this)
+   {
+      if (!streq (this->id, "IFIASCO"))
+      {
+	 set_error (_("Parameter `image' doesn't match required type."));
+	 return NULL;
+      }
+   }
+   else
+   {
+      set_error (_("Parameter `%s' not defined (NULL)."), "image");
+   }
+
+   return this;
+}
+
+image_t *
+alloc_image (unsigned width, unsigned height, bool_t color, format_e format)
+/*
+ *  Image constructor:
+ *  Allocate memory for the image_t structure.
+ *  Image size is given by 'width' and 'height'.
+ *  If 'color' == YES then allocate memory for three color bands (Y, Cb, Cr).
+ *  otherwise just allocate memory for a grayscale image.
+ *  'format' specifies whether image pixels of color images
+ *  are stored in 4:4:4 or 4:2:0 format.
+ *
+ *  Return value:
+ *	pointer to the new image structure.
+ */
+{
+   image_t *image;
+   color_e band;
+
+   if ((width & 1) || (height & 1))
+      error ("Width and height of images must be even numbers.");
+   if (!color)
+      format = FORMAT_4_4_4;
+
+   image         	  = Calloc (1, sizeof (image_t));
+   image->width  	  = width;
+   image->height 	  = height;
+   image->color  	  = color;
+   image->format 	  = format;
+   image->reference_count = 1;
+   
+   strcpy (image->id, "IFIASCO");
+
+   for (band = first_band (color); band <= last_band (color); band++)
+      if (format == FORMAT_4_2_0 && band != Y)
+	 image->pixels [band] = Calloc ((width * height) >> 2,
+					sizeof (word_t));
+      else
+	 image->pixels [band] = Calloc (width * height, sizeof (word_t));
+   
+   return image;
+}
+
+image_t *
+clone_image (image_t *image)
+/*
+ *  Copy constructor:
+ *  Construct new image by copying the given `image'.
+ *
+ *  Return value:
+ *	pointer to the new image structure.
+ */
+{
+   image_t *new = alloc_image (image->width, image->height, image->color,
+			       image->format);
+   color_e band;
+   
+   for (band = first_band (new->color); band <= last_band (new->color); band++)
+      if (new->format == FORMAT_4_2_0 && band != Y)
+      {
+	 memcpy (new->pixels [band], image->pixels [band],
+		 ((new->width * new->height) >> 2) * sizeof (word_t));
+      }
+      else
+      {
+	 memcpy (new->pixels [band], image->pixels [band],
+		 new->width * new->height * sizeof (word_t));
+      }
+
+   return new;
+}
+
+void
+free_image (image_t *image)
+/*
+ *  Image destructor:
+ *  Free memory of 'image' struct and pixel data.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	structure 'image' is discarded.
+ */
+{
+   if (image != NULL)
+   {
+      if (--image->reference_count)
+	 return;			/* image is still referenced */
+      else
+      {
+	 color_e band;
+
+	 for (band  = first_band (image->color);
+	      band <= last_band (image->color); band++)
+	    if (image->pixels [band])
+	       Free (image->pixels [band]);
+	 Free (image);
+      }
+   }
+   else
+      warning ("Can't free image <NULL>.");
+}
+
+
+static void 
+read_image_data(image_t * const image, FILE *input, const bool_t color,
+                const int width, const int height, const xelval maxval,
+                const int format) {
+   int row;
+   int i;      /* Cursor into image->pixels arrays */
+   xel * xelrow;
+   /* The following are just the normal rgb -> YCbCr conversion matrix,
+      except normalization to maxval 4095 (12 bit color) is built in
+      */
+   const double coeff_lu_r = +0.2989 / maxval * 4095;
+   const double coeff_lu_g = +0.5866 / maxval * 4095;
+   const double coeff_lu_b = +0.1145 / maxval * 4095;
+   const double coeff_cb_r = -0.1687 / maxval * 4095;
+   const double coeff_cb_g = -0.3312 / maxval * 4095;
+   const double coeff_cb_b = +0.5000 / maxval * 4095;
+   const double coeff_cr_r = +0.5000 / maxval * 4095;
+   const double coeff_cr_g = -0.4183 / maxval * 4095;
+   const double coeff_cr_b = -0.0816 / maxval * 4095;
+
+   xelrow = pnm_allocrow(width);
+
+   i = 0; 
+   for (row = 0; row < height; row++) {
+       int col;
+       pnm_readpnmrow(input, xelrow, width, maxval, format);
+       for (col = 0; col < width; col++) {
+           if (color) {
+               image->pixels[Y][i] = 
+                   coeff_lu_r * PPM_GETR(xelrow[col]) 
+                   + coeff_lu_g * PPM_GETG(xelrow[col])
+                   + coeff_lu_b * PPM_GETB(xelrow[col]) - 2048;
+               image->pixels[Cb][i] = 
+                   coeff_cb_r * PPM_GETR(xelrow[col]) 
+                   + coeff_cb_g * PPM_GETG(xelrow[col])
+                   + coeff_cb_b * PPM_GETB(xelrow[col]);
+               image->pixels[Cr][i] = 
+                   coeff_cr_r * PPM_GETR(xelrow[col]) 
+                   + coeff_cr_g * PPM_GETG(xelrow[col])
+                   + coeff_cr_b * PPM_GETB(xelrow[col]);
+
+               i++;
+           } else 
+               image->pixels[GRAY][i++] =
+                   PNM_GET1(xelrow[col]) * 4095 / maxval - 2048;
+       }
+   }
+
+   free(xelrow);
+}
+
+
+
+image_t *
+read_image (const char *image_name)
+/*
+ *  Read image 'image_name'.
+ *  
+ *  Return value:
+ *	pointer to the image structure.
+ */
+{
+   FILE	    *input;			/* input stream */
+   image_t  *image;			/* pointer to new image structure */
+   int  width, height;		/* image size */
+   xelval   maxval;         /* Maxval of image */
+   int format;              /* Image's format code */
+   bool_t    color;			/* color image ? (YES/NO) */
+
+   if (image_name == NULL)
+       input = stdin;
+   else
+       input = pm_openr((char*)image_name);
+
+   pnm_readpnminit(input, &width, &height, &maxval, &format);
+
+   if (PNM_FORMAT_TYPE(format) == PPM_FORMAT)
+       color = YES;
+   else
+       color = NO;
+
+   if (width < 32)
+       pm_error("Image must have a width of at least 32 pixels.");
+
+   if (height < 32)
+       pm_error("Image must have a height of at least 32 pixels.");
+
+   image = alloc_image (width, height, color, FORMAT_4_4_4);
+
+   read_image_data(image, input, color, width, height, maxval, format);
+
+   pm_close(input);
+   
+   return image;
+}   
+
+void
+write_image (const char *image_name, const image_t *image)
+/*
+ *  Write given 'image' data to the file 'image_name'.
+ *  
+ *  No return value.
+ */
+{
+   FILE	*output;			/* output stream */
+   int format;
+   int row;
+   int i;     /* Cursor into image->pixel arrays */
+   xel * xelrow;
+   unsigned *gray_clip;			/* clipping table */
+
+   assert (image && image_name);
+   
+   if (image->format == FORMAT_4_2_0)
+   {
+      warning ("Writing of images in 4:2:0 format not supported.");
+      return;
+   }
+   
+   if (image_name == NULL)
+       output = stdout;
+   else if (strcmp(image_name, "-") == 0)
+       output = stdout;
+   else
+       output = pm_openw((char*)image_name);
+
+   gray_clip  = init_clipping ();	/* mapping of int -> unsigned */
+   if (!gray_clip)
+      error (fiasco_get_error_message ());
+   init_chroma_tables ();
+
+   format = image->color ? PPM_TYPE : PGM_TYPE;
+   
+   pnm_writepnminit(output, image->width, image->height, 255, format, 0);
+
+   xelrow = pnm_allocrow(image->width);
+   i = 0;
+   for (row = 0; row < image->height; row++) {
+       int col;
+       for (col = 0; col < image->width; col++) {
+           if (image->color) {
+               word_t yval, cbval, crval;
+
+               yval  = image->pixels[Y][i]  / 16 + 128;
+               cbval = image->pixels[Cb][i] / 16;
+               crval = image->pixels[Cr][i] / 16;
+
+               PPM_ASSIGN(xelrow[col], 
+                          gray_clip[yval + Cr_r_tab[crval]],
+                          gray_clip[yval + Cr_g_tab[crval] + Cb_g_tab [cbval]],
+                          gray_clip[yval + Cb_b_tab[cbval]]);
+
+           } else
+               /* The 16 below should be 4095/255 = 16.0588 */
+               PNM_ASSIGN1(xelrow[col], 
+                           gray_clip[image->pixels[GRAY][i]/16+128]);
+           i++;
+       }
+       pnm_writepnmrow(output, xelrow, 
+                       image->width, 255, format, 0);
+   }
+   pnm_freerow(xelrow);
+
+   pm_close(output);
+}
+
+bool_t
+same_image_type (const image_t *img1, const image_t *img2)
+/*
+ *  Check whether the given images 'img1' and `img2' are of the same type.
+ *
+ *  Return value:
+ *	YES	if images 'img1' and `img2' are of the same type
+ *	NO	otherwise.
+ */
+{
+   assert (img1 && img2);
+   
+   return ((img1->width == img2->width)
+	   && (img1->height == img2->height)
+	   && (img1->color == img2->color)
+	   && (img1->format == img2->format));
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static void
+init_chroma_tables (void)
+/*
+ *  Chroma tables are used to perform fast YCbCr->RGB color space conversion.
+ */
+{
+   int crval, cbval, i;
+
+   if (Cr_r_tab != NULL || Cr_g_tab != NULL ||
+       Cb_g_tab != NULL || Cb_b_tab != NULL)
+      return;
+
+   Cr_r_tab = Calloc (768, sizeof (int));
+   Cr_g_tab = Calloc (768, sizeof (int));
+   Cb_g_tab = Calloc (768, sizeof (int));
+   Cb_b_tab = Calloc (768, sizeof (int));
+
+   for (i = 256; i < 512; i++)
+   {
+      cbval = crval  = i - 128 - 256;
+
+      Cr_r_tab[i] =  1.4022 * crval + 0.5;
+      Cr_g_tab[i] = -0.7145 * crval + 0.5;
+      Cb_g_tab[i] = -0.3456 * cbval + 0.5; 
+      Cb_b_tab[i] =  1.7710 * cbval + 0.5;
+   }
+   for (i = 0; i < 256; i++)
+   {
+      Cr_r_tab[i] = Cr_r_tab[256];
+      Cr_g_tab[i] = Cr_g_tab[256];
+      Cb_g_tab[i] = Cb_g_tab[256]; 
+      Cb_b_tab[i] = Cb_b_tab[256];
+   }
+   for (i = 512; i < 768; i++)
+   {
+      Cr_r_tab[i] = Cr_r_tab[511];
+      Cr_g_tab[i] = Cr_g_tab[511];
+      Cb_g_tab[i] = Cb_g_tab[511]; 
+      Cb_b_tab[i] = Cb_b_tab[511];
+   }
+
+   Cr_r_tab += 256 + 128;
+   Cr_g_tab += 256 + 128;
+   Cb_g_tab += 256 + 128;
+   Cb_b_tab += 256 + 128;
+}
+
diff --git a/converter/other/fiasco/lib/image.h b/converter/other/fiasco/lib/image.h
new file mode 100644
index 00000000..958049f6
--- /dev/null
+++ b/converter/other/fiasco/lib/image.h
@@ -0,0 +1,59 @@
+/*
+ *  image.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/10/22 10:43:56 $
+ *  $Author: hafner $
+ *  $Revision: 5.3 $
+ *  $State: Exp $
+ */
+
+#ifndef _IMAGE_H
+#define _IMAGE_H
+
+#include <stdio.h>
+#include "types.h"
+#include "fiasco.h"
+
+typedef enum {FORMAT_4_4_4, FORMAT_4_2_0} format_e;
+
+typedef struct image
+/*
+ *  Image data
+ */
+{
+   char      id [7];
+   unsigned  reference_count;
+   unsigned  width;			/* Width of the image */
+   unsigned  height;			/* Height of the image */
+   bool_t    color;			/* Color or grayscale image */
+   format_e  format;			/* Pixel format 4:4:4 or 4:2:0 */
+   word_t   *pixels [3];		/* Pixels in short format */
+} image_t;
+
+image_t *
+cast_image (fiasco_image_t *image);
+image_t *
+alloc_image (unsigned width, unsigned height, bool_t color, format_e format);
+image_t *
+clone_image (image_t *image);
+void
+free_image (image_t *image);
+FILE *
+read_pnmheader (const char *image_name, unsigned *width, unsigned *height,
+		bool_t *color);
+image_t *
+read_image (const char *image_name);
+void
+write_image (const char *image_name, const image_t *image);
+bool_t
+same_image_type (const image_t *img1, const image_t *img2);
+
+#endif /* not _IMAGE_H */
+
diff --git a/converter/other/fiasco/lib/list.c b/converter/other/fiasco/lib/list.c
new file mode 100644
index 00000000..9f516c2e
--- /dev/null
+++ b/converter/other/fiasco/lib/list.c
@@ -0,0 +1,258 @@
+/*
+ *  list.c:		List operations	
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:49:37 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#if HAVE_STRING_H
+#	include <string.h>
+#else /* not HAVE_STRING_H */
+#	include <strings.h>
+#endif /* not HAVE_STRING_H */
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "misc.h"
+#include "list.h"
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+list_t *
+alloc_list (size_t size_of_element)
+/*
+ *  List constructor:
+ *  Allocate a new list.
+ *  Size of list element values is given by 'size_of_element'.
+ *
+ *  Return value:
+ *	pointer to an empty list
+ */
+{
+   list_t *new_list = Calloc (1, sizeof (list_t));
+
+   assert (size_of_element > 0);
+
+   new_list->head 	     = NULL;
+   new_list->tail 	     = NULL;
+   new_list->size_of_element = size_of_element;
+
+   return new_list;
+}
+
+void
+free_list (list_t *list)
+/*
+ *  List destructor:
+ *  Discard list and its elements.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	struct 'list' is discarded
+ */
+{
+   assert (list);
+   
+   while (list_remove (list, HEAD, NULL))
+      ;
+   Free (list);
+}
+
+void
+list_insert (list_t *list, pos_e pos, const void *data)
+/*
+ *  Insert a new 'list' element at head ('pos' = HEAD) or
+ *  tail ('pos' = TAIL) of 'list'. 
+ *  'data' is a pointer to a memory segment of size
+ *  'list'->size_of_element containing the value to store.
+ *  The value is directly copied - no references are stored.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	lists current tail or head is replaced by the new element
+ */
+{
+   node_t *element;
+
+   assert (list && data);
+
+   element 	  = Calloc (1, sizeof (node_t));
+   element->value = Calloc (1, list->size_of_element);
+   memcpy (element->value, data, list->size_of_element);
+
+   if (pos == TAIL)
+   {
+      element->next = NULL;
+      element->prev = list->tail;
+      if (list->tail)
+	 list->tail->next = element;
+      list->tail = element;
+      if (!list->head)
+	 list->head = element;
+   }
+   else					/* pos == HEAD */
+   {
+      element->prev = NULL;
+      element->next = list->head;
+      if (list->head)
+	 list->head->prev = element;
+      list->head = element;
+      if (!list->tail)
+	 list->tail = element;
+   }
+}
+
+bool_t
+list_remove (list_t *list, pos_e pos, void *data)
+/*
+ *  Remove 'list' element from head or tail of 'list'.
+ *
+ *  Return value:
+ *	TRUE on success,
+ *	FALSE if list is empty or
+ *	      if list value data is NULL
+ *
+ *  Side effects:
+ *	lists current head or tail is removed
+ *	value of the removed list element (if not NULL) is copied to
+ *      'data' (if 'data' is not NULL)
+ */
+{
+   node_t *element;
+   void	  *valueptr;
+
+   assert (list);
+   
+   if (pos == TAIL)
+   {
+      element = list->tail;
+      if (element)
+      {
+	 list->tail = element->prev;
+	 valueptr   = element->value;
+	 Free (element);
+      }
+      else
+	 valueptr = NULL;
+      if (!list->tail)			/* 'element' was last node */
+	 list->head = NULL;
+   }
+   else					/* pos == HEAD */
+   {
+      element = list->head;
+      if (element)
+      {
+	 list->head = element->next;
+	 valueptr   = element->value;
+	 Free (element);
+      }
+      else
+	 valueptr = NULL;
+      if (!list->head)			/* 'element' was last node */
+	 list->tail = NULL;
+   }
+
+   if (valueptr)			/* copy value of node */
+   {
+      if (data)				
+	 memcpy (data, valueptr, list->size_of_element);
+      Free (valueptr);
+   }
+   
+   return valueptr ? TRUE : FALSE;
+}
+
+bool_t
+list_element_n (const list_t *list, pos_e pos, unsigned n, void *data)
+/*
+ *  Get value of 'list' element number 'n'.
+ *  (First element is list head if 'pos' == HEAD
+ *                 or list tail if 'pos' == TAIL.
+ *   Accordingly, traverse the list in ascending or descending order).
+ *  
+ *  Return value:
+ *	TRUE on success, FALSE if there is no element 'n'
+ *
+ *  Side effects:
+ *	value of list element 'n' is copied to 'data' 
+ */
+{
+   node_t *element;
+
+   assert (list && data);
+   
+   if (pos == HEAD)
+      for (element = list->head; element != NULL && n;
+	   element = element->next, n--)
+	 ;
+   else
+      for (element = list->tail; element != NULL && n;
+	   element = element->prev, n--)
+	 ;
+      
+   if (element)
+   {
+      memcpy (data, element->value, list->size_of_element);
+      return TRUE;
+   }
+   else
+      return FALSE;
+}
+
+unsigned
+list_sizeof (const list_t *list)
+/*
+ *  Count number of 'list' elements.
+ *
+ *  Return value:
+ *	number of 'list' elements.
+ */
+{
+   node_t   *element;
+   unsigned  n = 0;
+
+   assert (list);
+   
+   for (element = list->head; element != NULL; element = element->next)
+      n++;
+
+   return n;
+}
+
+void
+list_foreach (const list_t *list, void (*function)(void *, void *), void *data)
+/*
+ *  Call 'function' for each element of the 'list'.
+ *  Parameters given to 'function' are a pointer to the value of the
+ *  current 'list' element and the user pointer 'data'.
+ *
+ *  No return value.
+ */
+{
+   node_t *element;
+
+   assert (list && function && data);
+   
+   for (element = list->head; element; element = element->next)
+      function (element->value, data);
+}
+
diff --git a/converter/other/fiasco/lib/list.h b/converter/other/fiasco/lib/list.h
new file mode 100644
index 00000000..db7c08b2
--- /dev/null
+++ b/converter/other/fiasco/lib/list.h
@@ -0,0 +1,72 @@
+/*
+ *  list.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:49:37 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _LIST_H
+#define _LIST_H
+
+#include <stdio.h>
+#include <stddef.h>
+
+typedef struct node
+{
+   struct node *prev;			/* pointer to prev list element */
+   struct node *next;			/* pointer to next list element */
+   void	       *value;			/* pointer to value of node */
+} node_t;
+
+typedef struct list
+{
+   node_t *head;
+   node_t *tail;
+   size_t  size_of_element;		/* number of bytes to store value */
+} list_t;
+
+typedef enum {TAIL, HEAD} pos_e;
+
+/*
+ *  Alias definitions for queue and stack
+ */
+
+typedef list_t lqueue_t ;
+#define alloc_queue		alloc_list
+#define free_queue		free_list		
+#define queue_append(q, d)	(list_insert ((q), TAIL, (d)))
+#define queue_remove(q, d)	(list_remove ((q), HEAD, (d)))
+
+typedef list_t lstack_t ;
+#define alloc_stack		alloc_list
+#define free_stack		free_list
+#define stack_push(q, d)	(list_insert ((q), TAIL, (d)))
+#define stack_pop(q, d)		(list_remove ((q), TAIL, (d)))
+
+list_t *
+alloc_list (size_t size_of_element);
+void 
+free_list (list_t *list);
+bool_t
+list_element_n (const list_t *list, pos_e pos, unsigned n, void *data);
+void
+list_foreach (const list_t *list, void (*function)(void *, void *),
+	      void *data);
+void
+list_insert (list_t *list, pos_e pos, const void *data);
+bool_t
+list_remove (list_t *list, pos_e pos, void *data);
+unsigned
+list_sizeof (const list_t *list);
+
+#endif /* not _LIST_H */
+
diff --git a/converter/other/fiasco/lib/macros.h b/converter/other/fiasco/lib/macros.h
new file mode 100644
index 00000000..877abeea
--- /dev/null
+++ b/converter/other/fiasco/lib/macros.h
@@ -0,0 +1,70 @@
+/*
+ *  macros.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:49:37 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _MACROS_H
+#define _MACROS_H
+
+#include <string.h>
+/*******************************************************************************
+
+			  System configuration section
+  
+*******************************************************************************/
+
+#ifndef SEEK_CUR
+#   define SEEK_CUR	1
+#endif /* not SEEK_CUR */
+
+#ifdef WIN32
+#undef max
+#undef min
+#endif /* not WIN32 */
+
+/*****************************************************************************
+
+				Various macros
+  
+*****************************************************************************/
+
+#define streq(str1, str2)	(strcmp ((str1), (str2)) == 0)
+#define strneq(str1, str2)	(strcmp ((str1), (str2)) != 0)
+#define square(x)		((x) * (x))
+#define first_band(color)	((unsigned) ((color) ? Y  : GRAY))
+#define last_band(color)        ((unsigned) ((color) ? Cr : GRAY))
+#define width_of_level(l)	((unsigned) (1 << ((l) >> 1)))
+#define height_of_level(l)	((unsigned) (1 << (((l) + 1) >> 1)))
+#define size_of_level(l)	((unsigned) (1 << (l)))
+#define address_of_level(l)	((unsigned) (size_of_level (l) - 1))
+#define size_of_tree(l)		((unsigned) (address_of_level ((l) + 1)))
+#define is_odd(n)		(abs (n) % 2)
+#ifndef max
+#define max(a,b)		((a) < (b) ? (b) : (a))
+#endif
+#ifndef min
+#define min(a,b)	        ((a) > (b) ? (b) : (a))
+#endif
+#define _(x) (x) 
+
+
+#define	MAXSTRLEN 1024
+#define	MAXSTRLEN_SCANF "%1024s"
+
+typedef enum color {GRAY = 0, Y = 0, Cb = 1, Cr = 2} color_e;
+
+#endif /* _MACROS_H */
+
+
+
diff --git a/converter/other/fiasco/lib/misc.c b/converter/other/fiasco/lib/misc.c
new file mode 100644
index 00000000..02a1314f
--- /dev/null
+++ b/converter/other/fiasco/lib/misc.c
@@ -0,0 +1,563 @@
+/*
+ *  misc.c:		Some usefull functions, that don't fit in one of 
+ *			the other files and that are needed by at least
+ *			two modules. 
+ *
+ *  Written by:		Stefan Frank
+ *			Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:49:37 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include <math.h>
+#include <ctype.h>
+
+#ifdef TIME_WITH_SYS_TIME
+#	include <sys/time.h>
+#	include <time.h>
+#else  /* not TIME_WITH_SYS_TIME */
+#	if HAVE_SYS_TIME_H
+#		include <sys/time.h>
+#	else /* not HAVE_SYS_TIME_H */
+#		include <time.h>
+#	endif /* not HAVE_SYS_TIME_H */
+#endif /* not TIME_WITH_SYS_TIME */
+
+#if STDC_HEADERS
+#	include <stdlib.h>
+#endif /* not STDC_HEADERS */
+
+#if HAVE_STRING_H
+#	include <string.h>
+#else /* not HAVE_STRING_H */
+#	include <strings.h>
+#endif /* not HAVE_STRING_H */
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "bit-io.h"
+#include "misc.h"
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static void
+remove_comments (FILE *file);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+void *
+Calloc (size_t n, size_t size)
+/*
+ *  Allocate memory like calloc ().
+ *
+ *  Return value: Pointer to the new block of memory on success,
+ *		  otherwise the program is terminated.
+ */
+{
+   void	*ptr;				/* pointer to the new memory block */
+
+   if (n <= 0 || size <= 0)
+      error ("Can't allocate memory for %d items of size %d",
+	     (int) n, (int) size);
+
+   ptr = calloc (n, size);
+   if (!ptr)
+      error ("Out of memory!");
+
+   return ptr;
+}
+
+void
+Free (void *memory)
+/*
+ *  Free memory given by the pointer 'memory'
+ *
+ *  No return value.
+ */
+{
+   if (memory != NULL)
+      free (memory);
+   else
+      warning ("Can't free memory block <NULL>.");
+}
+
+unsigned
+prg_timer (clock_t *last_timer, enum action_e action)
+/*
+ *  If 'action' == START then store current value of system timer.
+ *  If 'action' == STOP	 then compute number of elapsed micro seconds since
+ *			 the last time 'prg_timer' was called
+ *			 with 'action' == START.
+ *
+ *  Return value:
+ *	Number of elapsed micro seconds if 'action' == STOP
+ *	0				if 'action' == START
+ *
+ *  Side effects:
+ *	last_timer is set to current timer if action == START
+ */
+{
+   assert (last_timer);
+   
+   if (action == START)
+   {
+      *last_timer = clock ();
+      return 0;
+   }
+   else
+      return (clock () - *last_timer) / (CLOCKS_PER_SEC / 1000.0);
+}
+
+real_t 
+read_real (FILE *infile)
+/* 
+ *  Read one real value from the given input stream 'infile'.
+ *  
+ *  Return value:
+ *	real value on success
+ */
+{
+   float input;
+
+   assert (infile);
+   
+   remove_comments (infile);
+   if (fscanf(infile, "%f", &input) != 1)
+      error("Can't read float value!");
+
+   return (real_t) input;
+}
+
+int 
+read_int (FILE *infile)
+/* 
+ *  Read one integer value from the given input stream 'infile'.
+ *
+ *  Return value:
+ *	integer value on success
+ */
+{
+   int input;				/* integer */
+
+   assert (infile);
+   
+   remove_comments (infile);
+   if (fscanf(infile, "%d", &input) != 1)
+      error("Can't read integer value!");
+
+   return input;
+}
+   
+static void
+remove_comments (FILE *file)
+/*
+ *  Remove shell/pgm style comments (#) from the input 'file'
+ *
+ *  No return value.
+ */
+{
+   int c;				/* current character */
+   
+   assert (file);
+   
+   do
+   {
+      while (isspace(c = getc (file)))
+	 ;
+      if (c == EOF)
+	 error ("EOF reached, input seems to be truncated!");
+      if (c == '#')
+      {
+	 int dummy;
+	 
+	 while (((dummy = getc (file)) != '\n') && dummy != EOF)
+	    ;
+	 if (dummy == EOF)
+	    error ("EOF reached, input seems to be truncated!");
+      }
+      else 
+	 ungetc (c, file);
+   } while (c == '#');
+}
+
+void
+write_rice_code (unsigned value, unsigned rice_k, bitfile_t *output)
+/*
+ *  Write 'value' to the stream 'output' using Rice coding with base 'rice_k'.
+ *
+ *  No return value.
+ */
+{
+   unsigned unary;			/* unary part of Rice Code */
+
+   assert (output);
+   
+   for (unary = value >> rice_k; unary; unary--)
+      put_bit (output, 1);
+   put_bit (output, 0);
+   put_bits (output, value & ((1 << rice_k) - 1), rice_k);
+}
+
+unsigned
+read_rice_code (unsigned rice_k, bitfile_t *input)
+/*
+ *  Read a Rice encoded integer (base 'rice_k') from the stream 'input'.
+ *
+ *  Return value:
+ *	decoded integer
+ */
+{
+   unsigned unary;			/* unary part of Rice code */
+   
+   assert (input);
+   
+   for (unary = 0; get_bit (input); unary++) /* unary part */
+      ;
+
+   return (unary << rice_k) | get_bits (input, rice_k);
+}
+
+void
+write_bin_code (unsigned value, unsigned maxval, bitfile_t *output)
+/*
+ *  Write 'value' to the stream 'output' using an adjusted binary code
+ *  based on given 'maxval'.
+ *
+ *  No return value.
+ */
+{
+   unsigned k;
+   unsigned r;
+   
+   assert (output && maxval && value <= maxval);
+
+   k = log2 (maxval + 1);
+   r = (maxval + 1) % (1 << k);
+
+   if (value < maxval + 1 - 2 * r)	/* 0, ... , maxval - 2r */
+      put_bits (output, value, k);
+   else					/* maxval - 2r + 1, ..., maxval */
+      put_bits (output, value + maxval + 1 - 2 * r, k + 1);
+}
+
+unsigned
+read_bin_code (unsigned maxval, bitfile_t *input)
+/*
+ *  Read a bincode encoded integer from the stream 'input'.
+ *
+ *  Return value:
+ *	decoded integer
+ */
+{
+   unsigned k;
+   unsigned r;
+   unsigned value;
+   
+   assert (input);
+
+   k = log2 (maxval + 1);
+   r = (maxval + 1) % (1 << k);
+
+   value = get_bits (input, k);
+   if (value < maxval + 1 - 2 * r)
+      return value;
+   else
+   {
+      value <<= 1;
+      if (get_bit (input))
+	 value++;
+      return value - maxval - 1 + 2 * r;
+   }
+}
+
+unsigned
+bits_rice_code (unsigned value, unsigned rice_k)
+/*
+ *  Compute number of bits needed for coding integer 'value'
+ *  with given Rice code 'rice_k'.
+ *
+ *  Return value:
+ *	number of bits
+ */
+{
+   unsigned unary;
+   unsigned bits = 0;
+   
+   for (unary = value >> rice_k; unary; unary--)
+      bits++;
+   bits += rice_k + 1;
+
+   return bits;
+}
+
+unsigned
+bits_bin_code (unsigned value, unsigned maxval)
+/*
+ *  Compute number of bits needed for coding integer 'value'
+ *  with adjusted binary code of given maximum value 'maxval'.
+ *
+ *  Return value:
+ *	number of bits
+ */
+{
+   unsigned k;
+   unsigned r;
+
+   assert (maxval && value <= maxval);
+
+   k = log2 (maxval + 1);
+   r = (maxval + 1) % (1 << k);
+
+   return value < maxval + 1 - 2 * r ? k : k + 1;
+}
+
+unsigned *
+init_clipping (void)
+/*
+ *  Initialize the clipping tables
+ *
+ *  Return value:
+ *	pointer to clipping table
+ */
+{
+   static unsigned *gray_clip = NULL;	/* clipping array */
+
+   if (gray_clip == NULL)		/* initialize clipping table */
+   {
+      int i;				/* counter */
+
+      gray_clip  = calloc (256 * 3, sizeof (unsigned));
+      if (!gray_clip)
+      {
+	 set_error (_("Out of memory."));
+	 return NULL;
+      }
+      gray_clip += 256;
+
+      for (i = -256; i < 512; i++)
+	 if (i < 0)
+	    gray_clip [i] = 0;
+	 else if (i > 255)
+	    gray_clip [i] = 255;
+	 else
+	    gray_clip [i] = i;
+   }
+
+   return gray_clip;
+}
+
+#ifndef HAVE_MEMMOVE
+void *
+memmove (void *v_dst, const void *v_src, size_t n)
+/*
+ *  Copy 'n' bytes from memory area 'src' to memory area 'dest'.
+ *  The memory areas may overlap.
+ *
+ *  Return value:
+ *	pointer 'dest'
+ */
+{
+   byte_t	*to, *dst = (byte_t *) v_dst;
+   const byte_t	*from, *src = (byte_t *) v_src;
+   
+   assert (v_dst && v_src);
+   
+   if (dst <= src)
+   {
+      from = src;
+      to   = dst;
+      for (; n; n--)
+	 *to++ = *from++;
+   }
+   else
+   { 
+      from = src + (n - 1);
+      to   = dst + (n - 1);
+      for (; n; n--)
+	 *to-- = *from--;
+   }
+   
+   return v_dst;
+}
+#endif /* not HAVE_MEMMOVE */
+
+#ifndef HAVE_STRDUP
+char *
+strdup (const char *s)
+/*
+ *  Duplicate given string 's'.
+ *
+ *  Return value:
+ *	pointer to new string value
+ */
+{
+   assert (s);
+   
+   return strcpy (Calloc (strlen (s) + 1, sizeof (char)), s);
+}
+#endif /* not HAVE_STRDUP */
+
+/* Note that some systems have a "log2()" in the math library and some
+   have a "log2" macro.  So we name ours Log2.  But to avoid lots of
+   differences from the original fiasco source code, we define a
+   macro log2 in config.h to expand to Log2.
+   */
+double
+Log2 (double x)
+/*
+ *  Return value:
+ *	base-2 logarithm of 'x'
+ */
+{
+   return log (x) / 0.69314718;
+}
+
+#ifndef HAVE_STRCASECMP
+bool_t
+strcaseeq (const char *s1, const char *s2)
+/*
+ *  Compare strings 's1' and 's2', ignoring  the  case of the characters.
+ *
+ *  Return value:
+ *	TRUE if strings match, else FALSE
+ */
+{
+   bool_t  matched;
+   char	  *ls1, *ls2, *ptr;
+
+   assert (s1 && s2);
+   
+   ls1 = strdup (s1);
+   ls2 = strdup (s2);
+   
+   for (ptr = ls1; *ptr; ptr++)
+      *ptr = tolower (*ptr);
+   for (ptr = ls2; *ptr; ptr++)
+      *ptr = tolower (*ptr);
+
+   matched = streq (ls1, ls2) ? YES : NO;
+
+   Free (ls1);
+   Free (ls2);
+   
+   return matched;
+}
+#endif /* not HAVE_STRCASECMP */
+
+real_t
+variance (const word_t *pixels, unsigned x0, unsigned y0,
+	  unsigned width, unsigned height, unsigned cols)
+/*
+ *  Compute variance of subimage ('x0', y0', 'width', 'height') of 
+ *  the image data given by 'pixels' ('cols' is the number of pixels
+ *  in one row of the image).
+ *
+ *  Return value:
+ *	variance
+ */
+{
+   real_t   average;			/* average of pixel values */
+   real_t   variance;			/* variance of pixel values */
+   unsigned x, y;			/* pixel counter */
+   unsigned n;				/* number of pixels */
+
+   assert (pixels);
+   
+   for (average = 0, n = 0, y = y0; y < y0 + height; y++)
+      for (x = x0; x < min (x0 + width, cols); x++, n++)
+	 average += pixels [y * cols + x] / 16;
+
+   average /= n;
+
+   for (variance = 0, y = y0; y < y0 + height; y++)
+      for (x = x0; x < min (x0 + width, cols); x++)
+	 variance += square ((pixels [y * cols + x] / 16) - average);
+
+   return variance;
+}
+
+int
+sort_asc_word (const void *value1, const void *value2)
+/*
+ *  Sorting function for quicksort.
+ *  Smallest values come first.
+ */
+{
+   if (* (word_t *) value1 < * (word_t *) value2)
+      return -1;
+   else if (* (word_t *) value1 > * (word_t *) value2)
+      return +1;
+   else
+      return 0;
+}
+
+int
+sort_desc_word (const void *value1, const void *value2)
+/*
+ *  Sorting function for quicksort.
+ *  Largest values come first.
+ */
+{
+   if (* (word_t *) value1 > * (word_t *) value2)
+      return -1;
+   else if (* (word_t *) value1 < * (word_t *) value2)
+      return +1;
+   else
+      return 0;
+}
+
+int
+sort_asc_pair (const void *value1, const void *value2)
+/*
+ *  Sorting function for quicksort.
+ *  Smallest values come first.
+ */
+{
+   word_t v1 = ((pair_t *) value1)->key;
+   word_t v2 = ((pair_t *) value2)->key;
+   
+   if (v1 < v2)
+      return -1;
+   else if (v1 > v2)
+      return +1;
+   else
+      return 0;
+}
+
+int
+sort_desc_pair (const void *value1, const void *value2)
+/*
+ *  Sorting function for quicksort.
+ *  Largest values come first.
+ */
+{
+   word_t v1 = ((pair_t *) value1)->key;
+   word_t v2 = ((pair_t *) value2)->key;
+
+   if (v1 > v2)
+      return -1;
+   else if (v1 < v2)
+      return +1;
+   else
+      return 0;
+}
diff --git a/converter/other/fiasco/lib/misc.h b/converter/other/fiasco/lib/misc.h
new file mode 100644
index 00000000..29456590
--- /dev/null
+++ b/converter/other/fiasco/lib/misc.h
@@ -0,0 +1,98 @@
+/*
+ *  misc.h
+ *
+ *  Written by:		Stefan Frank
+ *			Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:49:37 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _MISC_H
+#define _MISC_H
+
+#include "config.h"
+
+#if TIME_WITH_SYS_TIME
+#	include <sys/time.h>
+#	include <time.h>
+#else  /* not TIME_WITH_SYS_TIME */
+#	if HAVE_SYS_TIME_H
+#		include <sys/time.h>
+#	else /* not HAVE_SYS_TIME_H */
+#		include <time.h>
+#	endif /* not HAVE_SYS_TIME_H */
+#endif /* not TIME_WITH_SYS_TIME */
+
+#include <stdio.h>
+#include "types.h"
+#include "bit-io.h"
+
+enum action_e {START, STOP};
+
+void *
+Calloc (size_t n, size_t size);
+void
+Free (void *memory);
+unsigned
+prg_timer (clock_t *ptimer, enum action_e action);
+int 
+read_int(FILE *infile);
+real_t 
+read_real(FILE *infile);
+unsigned
+read_rice_code (unsigned rice_k, bitfile_t *input);
+void
+write_rice_code (unsigned value, unsigned rice_k, bitfile_t *output);
+void
+write_bin_code (unsigned value, unsigned maxval, bitfile_t *output);
+unsigned
+bits_bin_code (unsigned value, unsigned maxval);
+unsigned
+bits_rice_code (unsigned value, unsigned rice_k);
+unsigned
+read_bin_code (unsigned maxval, bitfile_t *input);
+unsigned *
+init_clipping (void);
+real_t
+variance (const word_t *pixels, unsigned x0, unsigned y0,
+	  unsigned width, unsigned height, unsigned cols);
+
+#ifndef HAVE_MEMMOVE
+void *
+memmove(void *dest, const void *src, size_t n);
+#endif /* not HAVE_MEMMOVE */
+
+double
+Log2 (double x);
+#ifndef HAVE_STRDUP
+char *
+strdup (const char *s);
+#endif
+#ifndef HAVE_STRCASECMP
+bool_t
+strcaseeq (const char *s1, const char *s2);
+#else  /* HAVE_STRCASECMP */
+int
+strcasecmp (const char *s1, const char *s2);
+#define strcaseeq(s1, s2) (strcasecmp ((s1), (s2)) == 0)
+#endif /* HAVE_STRCASECMP */
+
+int
+sort_asc_word (const void *value1, const void *value2);
+int
+sort_desc_word (const void *value1, const void *value2);
+int
+sort_asc_pair (const void *value1, const void *value2);
+int
+sort_desc_pair (const void *value1, const void *value2);
+
+#endif /* not _MISC_H */
+
diff --git a/converter/other/fiasco/lib/mvcode.c b/converter/other/fiasco/lib/mvcode.c
new file mode 100644
index 00000000..d9ce91e2
--- /dev/null
+++ b/converter/other/fiasco/lib/mvcode.c
@@ -0,0 +1,14 @@
+#include "mvcode.h"
+
+unsigned mv_code_table [33][2] =
+/*
+ *  MPEG's huffman code for vector components. Format: code_value, length
+ */
+{
+   {0x19, 11}, {0x1b, 11}, {0x1d, 11}, {0x1f, 11}, {0x21, 11}, {0x23, 11},
+   {0x13, 10}, {0x15, 10}, {0x17, 10}, {0x7, 8}, {0x9, 8}, {0xb, 8}, {0x7, 7},
+   {0x3, 5}, {0x3, 4}, {0x3, 3}, {0x1, 1}, {0x2, 3}, {0x2, 4}, {0x2, 5},
+   {0x6, 7}, {0xa, 8}, {0x8, 8}, {0x6, 8}, {0x16, 10}, {0x14, 10}, {0x12, 10}, 
+   {0x22, 11}, {0x20, 11}, {0x1e, 11}, {0x1c, 11}, {0x1a, 11}, {0x18, 11}
+};
+
diff --git a/converter/other/fiasco/lib/mvcode.h b/converter/other/fiasco/lib/mvcode.h
new file mode 100644
index 00000000..f43f2081
--- /dev/null
+++ b/converter/other/fiasco/lib/mvcode.h
@@ -0,0 +1,6 @@
+#ifndef MVCODE_H_INCLUDED
+#define MVCODE_H_INCLUDED
+
+extern unsigned mv_code_table [33][2];
+
+#endif
diff --git a/converter/other/fiasco/lib/rpf.c b/converter/other/fiasco/lib/rpf.c
new file mode 100644
index 00000000..ac7d48ca
--- /dev/null
+++ b/converter/other/fiasco/lib/rpf.c
@@ -0,0 +1,223 @@
+/*
+ *  rpf.c:		Conversion of float to reduced precision format values
+ *
+ *  Written by:		Stefan Frank
+ *			Richard Krampfl
+ *			Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:49:37 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "misc.h"
+#include "rpf.h"
+
+/* 
+ * CAUTION: The IEEE float format must be used by your compiler,
+ *          or all following code is void!
+ */
+
+#ifdef WORDS_BIGENDIAN
+/*
+ *  Big-Endian Architecture (e.g. SUN, Motorola)
+ *  Memory representation of integer 0x00112233 is 00,11,22,33
+ */
+
+enum real_bytes {BYTE_0, BYTE_1, BYTE_2, BYTE_3};
+
+#else  /* not WORDS_BIGENDIAN */
+/*
+ *  Little-Endian Architecture (e.g. Intel, VAX, Alpha)
+ *  Memory representation of integer 0x00112233 is 33,22,11,00
+ */
+
+enum real_bytes {BYTE_3, BYTE_2, BYTE_1, BYTE_0};
+
+#endif /* not WORDS_BIGENDIAN */
+
+const int RPF_ZERO = -1;
+
+/*****************************************************************************
+
+			       private code
+  
+*****************************************************************************/
+
+int
+rtob (real_t f, const rpf_t *rpf)
+/*
+ *  Convert real number 'f' into fixed point format.
+ *  The real number in [-'range'; +'range'] is scaled to [-1 ; +1].
+ *  Sign and the first 'precision' - 1 bits of the mantissa are
+ *  packed into one integer.  
+ *
+ *  Return value:
+ *	real value in reduced precision format
+ */
+{  
+   unsigned int	mantissa;
+   int		exponent, sign;
+   union
+   {
+      float f;
+      unsigned char c[4];
+   } v;					/* conversion dummy */
+
+   f  /= rpf->range;			/* scale f to [-1,+1] */	
+   v.f = f;
+
+   /*
+    *  Extract mantissa (23 Bits), exponent (8 Bits) and sign (1 Bit)
+    */
+
+   mantissa = ((((v.c[BYTE_1] & 127) << 8 ) | v.c[BYTE_2]) << 8) | v.c[BYTE_3];
+   exponent = (((v.c[BYTE_0] & 127) << 1) | (v.c[BYTE_1] & 128 ? 1 : 0)) - 126;
+   sign     = v.c[BYTE_0] & 128 ? 1 : 0;
+		
+   /*
+    *  Generate reduced precision mantissa.
+    */
+   mantissa >>= 1;				/* shift 1 into from left */
+   mantissa  |= (1 << 22);
+   if (exponent > 0) 
+      mantissa <<= exponent;
+   else
+      mantissa >>= -exponent;  
+   
+   mantissa >>= (23 - rpf->mantissa_bits - 1);
+
+   mantissa +=  1;			/* Round last bit. */
+   mantissa >>= 1;
+   
+   if (mantissa == 0)			/* close to zero */
+      return RPF_ZERO;
+   else if (mantissa >= (1U << rpf->mantissa_bits)) /* overflow */
+      return sign;
+   else
+      return ((mantissa & ((1U << rpf->mantissa_bits) - 1)) << 1) | sign;
+}
+
+float
+btor (int binary, const rpf_t *rpf)
+/*
+ *  Convert value 'binary' in reduced precision format to a real value.
+ *  For more information refer to function lin_rtob() above.
+ *
+ *  Return value:
+ *	converted value
+ */
+{
+   unsigned int	mantissa;
+   int		sign, exponent;
+   union
+   {
+      float f;
+      unsigned char c[4];
+   } value;
+
+   if (binary == RPF_ZERO)
+      return 0;
+
+   if (binary < 0 || binary >= 1 << (rpf->mantissa_bits + 1))
+      error ("Reduced precision format: value %d out of range.", binary);
+
+   /*
+    *  Restore IEEE float format:
+    *  mantissa (23 Bits), exponent (8 Bits) and sign (1 Bit)
+    */
+   
+   sign       = binary & 1;
+   mantissa   = (binary & ((1 << (rpf->mantissa_bits + 1)) - 1)) >> 1; 
+   mantissa <<= (23 - rpf->mantissa_bits);
+   exponent   = 0;
+
+   if (mantissa == 0)
+   {
+      value.f = (sign ? -1.0 : 1.0);
+   }
+   else
+   {
+      while (!(mantissa & (1 << 22)))	/* normalize mantissa */
+      {
+	 exponent--;
+	 mantissa <<= 1;
+      }
+      mantissa <<= 1;
+
+      value.c[BYTE_0] = (sign << 7) | ((exponent + 126) >> 1);
+      value.c[BYTE_1] = (((exponent + 126) & 1) << 7)
+			| ((mantissa  >> 16) & 127);
+      value.c[BYTE_2] = (mantissa >> 8) & 255;
+      value.c[BYTE_3] = mantissa & 255;
+   }
+   
+   return value.f * rpf->range;		/* expand [ -1 ; +1 ] to
+					   [ -range ; +range ] */
+}
+
+rpf_t *
+alloc_rpf (unsigned mantissa, fiasco_rpf_range_e range)
+/*
+ *  Reduced precision format constructor.
+ *  Allocate memory for the rpf_t structure.
+ *  Number of mantissa bits is given by `mantissa'.
+ *  The range of the real values is in the interval [-`range', +`range'].
+ *  In case of invalid parameters, a structure with default values is
+ *  returned. 
+ *
+ *  Return value
+ *	pointer to the new rpf structure
+ */
+{
+   rpf_t *rpf = Calloc (1, sizeof (rpf_t));
+   
+   if (mantissa < 2)
+   {
+      warning (_("Size of RPF mantissa has to be in the interval [2,8]. "
+		 "Using minimum value 2.\n"));
+      mantissa = 2;
+   }
+   else if (mantissa > 8)
+   {
+      warning (_("Size of RPF mantissa has to be in the interval [2,8]. "
+		 "Using maximum value 8.\n"));
+      mantissa = 2;
+   }
+
+   rpf->mantissa_bits = mantissa;
+   rpf->range_e       = range;
+   switch (range)
+   {
+      case FIASCO_RPF_RANGE_0_75:
+	 rpf->range = 0.75;
+	 break;
+      case FIASCO_RPF_RANGE_1_50:
+	 rpf->range = 1.50;
+	 break;
+      case FIASCO_RPF_RANGE_2_00:
+	 rpf->range = 2.00;
+	 break;
+      case FIASCO_RPF_RANGE_1_00:
+	 rpf->range = 1.00;
+	 break;
+      default:
+	 warning (_("Invalid RPF range specified. Using default value 1.0."));
+	 rpf->range   = 1.00;
+	 rpf->range_e = FIASCO_RPF_RANGE_1_00;
+	 break;
+   }
+   return rpf;
+}
diff --git a/converter/other/fiasco/lib/rpf.h b/converter/other/fiasco/lib/rpf.h
new file mode 100644
index 00000000..ba3ff6be
--- /dev/null
+++ b/converter/other/fiasco/lib/rpf.h
@@ -0,0 +1,47 @@
+/*
+ *  rpf.h
+ *
+ *  Written by:		Stefan Frank
+ *			Richard Krampfl
+ *			Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:49:37 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _RPF_H
+#define _RPF_H
+
+#include "types.h"
+#include "fiasco.h"
+
+typedef struct rpf
+{
+   unsigned    	      mantissa_bits;	/* number of bits used for mantissa */
+   real_t      	      range;		/* scale value to [-range, +range] */
+   fiasco_rpf_range_e range_e;
+} rpf_t;
+
+int
+rtob (real_t real, const rpf_t *rpf);
+real_t 
+btor (int b, const rpf_t *rpf);
+rpf_t *
+alloc_rpf (unsigned mantissa, fiasco_rpf_range_e range);
+
+extern const int RPF_ZERO;
+
+#endif /* not _RPF_H */
+
+
+
+
+
+
diff --git a/converter/other/fiasco/lib/types.h b/converter/other/fiasco/lib/types.h
new file mode 100644
index 00000000..16d8028c
--- /dev/null
+++ b/converter/other/fiasco/lib/types.h
@@ -0,0 +1,38 @@
+/*
+ *  types.h
+ *
+ *  Written by:     Ullrich Hafner
+ *      
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:49:37 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _FIASCO_TYPES_H
+#define _FIASCO_TYPES_H
+
+#undef FALSE
+#undef NO
+#undef TRUE
+#undef YES
+
+enum fiasco_boolean { NO = 0, FALSE = 0, YES = 1, TRUE = 1};
+
+typedef float                real_t;
+typedef enum fiasco_boolean  bool_t;
+typedef unsigned char        byte_t;
+typedef short                word_t;
+typedef unsigned short       u_word_t;
+typedef struct pair
+{
+   word_t key;
+   word_t value;
+} pair_t;
+
+#endif
diff --git a/converter/other/fiasco/output/Makefile b/converter/other/fiasco/output/Makefile
new file mode 100644
index 00000000..3bdc4635
--- /dev/null
+++ b/converter/other/fiasco/output/Makefile
@@ -0,0 +1,26 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../../..
+  BUILDDIR = $(SRCDIR)
+endif
+FIASCOSUBDIR = converter/other/fiasco
+SUBDIR = $(FIASCOSUBDIR)/output
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+OBJECTS =  matrices.o mc.o nd.o tree.o weights.o write.o
+
+MERGE_OBJECTS = $(OBJECTS)
+
+INCLUDES = -I$(SRCDIR)/$(FIASCOSUBDIR) \
+	   -I$(SRCDIR)/$(FIASCOSUBDIR)/lib \
+	   -I$(SRCDIR)/$(FIASCOSUBDIR)/codec 
+
+all: libfiasco_output.a
+
+include $(SRCDIR)/Makefile.common
+
+libfiasco_output.a: $(OBJECTS)
+	$(AR) -rc $@ $(OBJECTS)
+	$(RANLIB) $@
+
diff --git a/converter/other/fiasco/output/matrices.c b/converter/other/fiasco/output/matrices.c
new file mode 100644
index 00000000..fd8d31e2
--- /dev/null
+++ b/converter/other/fiasco/output/matrices.c
@@ -0,0 +1,547 @@
+/*
+ *  matrices.c:		Output of transitions matrices
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/* NETPBM: When you call delta_encoding() with last_domain < 4, it
+   crashes.  And we have seen it happen.
+*/
+
+/*
+ *  $Date: 2000/06/14 20:50:31 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#if STDC_HEADERS
+#	include <stdlib.h>
+#endif /* not STDC_HEADERS */
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "wfa.h"
+#include "bit-io.h"
+#include "arith.h"
+#include "misc.h"
+#include "wfalib.h"
+
+#include "matrices.h"
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static unsigned
+delta_encoding (bool_t use_normal_domains, bool_t use_delta_domains,
+		const wfa_t *wfa, unsigned last_domain, bitfile_t *output);
+static unsigned
+column_0_encoding (const wfa_t *wfa, unsigned last_row, bitfile_t *output);
+static unsigned
+chroma_encoding (const wfa_t *wfa, bitfile_t *output);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+unsigned
+write_matrices (bool_t use_normal_domains, bool_t use_delta_domains,
+		const wfa_t *wfa, bitfile_t *output)
+/*
+ *  Write transition matrices of 'wfa' to stream 'output'.
+ *
+ *  Return value:
+ *	number of transitions encoded
+ */
+{
+   unsigned root_state;			/* root of luminance */
+   unsigned total = 0;			/* number of transitions */
+   
+   root_state = wfa->wfainfo->color
+		? wfa->tree [wfa->tree [wfa->root_state][0]][0]
+		: wfa->root_state;
+   
+   total  = column_0_encoding (wfa, root_state, output);
+
+   total += delta_encoding (use_normal_domains, use_delta_domains,
+			    wfa, root_state, output);
+   
+   if (wfa->wfainfo->color)
+      total += chroma_encoding (wfa, output);
+   
+   return total;
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static unsigned
+delta_encoding (bool_t use_normal_domains, bool_t use_delta_domains,
+		const wfa_t *wfa, unsigned last_domain, bitfile_t *output)
+/*
+ *  Write transition matrices with delta coding to stream 'input'.
+ *  'last_domain' is the maximum state number used as domain image.
+ *
+ *  Return value:
+ *	number of non-zero matrix elements (WFA edges)
+ */
+{
+   range_sort_t	rs;			/* ranges are sorted as in the coder */
+   unsigned	max_domain;		/* dummy used for recursion */
+   unsigned	total = 0;
+      
+   /*
+    *  Generate a list of range blocks.
+    *  The order is the same as in the coder.
+    */
+   rs.range_state      = Calloc ((last_domain + 1) * MAXLABELS,
+				 sizeof (u_word_t));
+   rs.range_label      = Calloc ((last_domain + 1) * MAXLABELS,
+				 sizeof (byte_t));
+   rs.range_max_domain = Calloc ((last_domain + 1) * MAXLABELS,
+				 sizeof (u_word_t));
+   rs.range_subdivided = Calloc ((last_domain + 1) * MAXLABELS,
+				 sizeof (bool_t));
+   rs.range_no	       = 0;
+   max_domain 	       = wfa->basis_states - 1;
+   sort_ranges (last_domain, &max_domain, &rs, wfa);
+   
+   /*
+    *  Compute and write distribution of #edges
+    */
+   {
+      unsigned state, label;
+      unsigned edge;
+      unsigned count [MAXEDGES + 1];
+      unsigned n;
+      unsigned edges = 0;
+      unsigned M     = 0;
+      unsigned bits  = bits_processed (output);
+      
+      for (n = 0; n < MAXEDGES + 1; n++)
+	 count [n] = 0;
+      
+      for (state = wfa->basis_states; state <= last_domain; state++)
+	 for (label = 0; label < MAXLABELS; label++)
+	    if (isrange (wfa->tree [state][label]))
+	    {
+	       for (edge = 0; isedge (wfa->into [state][label][edge]); edge++)
+		  ;
+	       count [edge]++;
+	       edges++;
+	       M = max (edge, M);
+	    }
+      write_rice_code (M, 3, output);
+      for (n = 0; n <= M; n++)
+/* NETPBM: The following causes a crash when last_domain < 4, because
+   it requests writing of a negative number of bits.  And we have seen
+   last_domain = 3.  But we have no clue what last_domain means, or 
+   even what a rice code is, so we don't know where the error lies.
+   -Bryan 2001.02.09 
+*/
+	 write_rice_code (count [n], (int) log2 (last_domain) - 2, output);
+
+      /*
+       * Arithmetic coding of values */
+      {
+	 unsigned  range;
+	 model_t  *elements = alloc_model (M + 1, 0, 0, count);
+	 arith_t  *encoder  = alloc_encoder (output);
+	       
+	 for (range = 0; range < rs.range_no; range++)
+	    if (!rs.range_subdivided [range])
+	    {
+	       state = rs.range_state [range];
+	       label = rs.range_label [range];
+	       for (edge = 0; isedge (wfa->into [state][label][edge]); edge++)
+		  ;
+	       
+	       encode_symbol (edge, encoder, elements);
+	    }
+	 free_encoder (encoder);
+	 free_model (elements);
+      }
+      debug_message ("delta-#edges: %5d bits. (%5d symbols => %5.2f bps)",
+		     bits_processed (output) - bits, edges,
+		     edges > 0 ? ((bits_processed (output) - bits) /
+				  (double) edges) : 0);
+   }
+
+   /*
+    *  Write matrix elements
+    */
+   {
+      unsigned	bits  	 = bits_processed (output);
+      u_word_t *mapping1 = Calloc (wfa->states, sizeof (u_word_t));
+      u_word_t *mapping2 = Calloc (wfa->states, sizeof (u_word_t));
+      unsigned	range;
+
+      put_bit (output, use_normal_domains);
+      put_bit (output, use_delta_domains);
+      
+      /*
+       *  Generate array of states which are admitted domains.
+       *  When coding intra frames 'mapping1' == 'mapping2' otherwise
+       *  'mapping1' is a list of 'normal' domains which are admitted for 
+       *             coding intra blocks
+       *  'mapping2' is a list of 'delta' domains which are admitted for
+       *             coding the motion compensated prediction error 
+       */
+      {
+	 unsigned n1, n2, state;
+	 
+	 for (n1 = n2 = state = 0; state < wfa->states; state++)
+	 {
+	    mapping1 [state] = n1;
+	    if (usedomain (state, wfa)
+		&& (state < wfa->basis_states || use_delta_domains
+		    || !wfa->delta_state [state]))
+	       n1++;
+	    
+	    mapping2 [state] = n2;
+	    if (usedomain (state, wfa)
+		&& (state < wfa->basis_states || use_normal_domains
+		    || wfa->delta_state [state]))
+	       n2++;
+	 }
+	 debug_message ("# normal states = %d, # delta states = %d,"
+			" # WFA states = %d", n1, n2, wfa->states);
+      }
+      
+      for (range = 0; range < rs.range_no; range++)
+	 if (!rs.range_subdivided [range])
+	 {
+	    unsigned  state = rs.range_state [range];
+	    unsigned  label = rs.range_label [range];
+	    unsigned  last  = 1;
+	    u_word_t *mapping;
+	    unsigned  max_value;
+	    unsigned  edge;
+	    word_t    domain;
+
+	    if (wfa->delta_state [state] ||
+		wfa->mv_tree [state][label].type != NONE)
+	       mapping = mapping2;
+	    else
+	       mapping = mapping1;
+	    
+	    max_value = mapping [rs.range_max_domain [range]];
+	    
+	    for (edge = 0; isedge (domain = wfa->into [state][label][edge]);
+		 edge++)
+	       if (domain > 0)
+	       {
+		  total++;
+		  if (max_value - last)
+		  {
+		     write_bin_code (mapping [domain] - last,
+				     max_value - last, output);
+		     last = mapping [domain] + 1;
+		  }
+	       }
+	 }
+
+      debug_message ("delta-index:  %5d bits. (%5d symbols => %5.2f bps)",
+		     bits_processed (output) - bits, total,
+		     total > 0 ? ((bits_processed (output) - bits) /
+				  (double) total) : 0);
+      Free (mapping1);
+      Free (mapping2);
+   }
+   
+   Free (rs.range_state);
+   Free (rs.range_label);
+   Free (rs.range_max_domain);
+   Free (rs.range_subdivided);
+
+   return total;
+}
+
+static unsigned
+column_0_encoding (const wfa_t *wfa, unsigned last_row, bitfile_t *output)
+/*
+ *  Write column 0 of the transition matrices of the 'wfa' to stream 'output'
+ *  with quasi arithmetic coding.
+ *  All rows from 'wfa->basis_states' up to 'last_row' are decoded.
+ *
+ *  Return value:
+ *	number of non-zero matrix elements (WFA edges)
+ */
+{
+   u_word_t  high;			/* Start of the current code range */
+   u_word_t  low;			/* End of the current code range */
+   unsigned *prob;			/* probability array */
+   unsigned  row;			/* current matrix row */
+   unsigned  label;			/* current matrix label */
+   unsigned  underflow;			/* Underflow bits */
+   unsigned  index;			/* probability index */
+   unsigned  total = 0;			/* Number of '1' elements */
+   unsigned  bits  = bits_processed (output);
+
+   /*
+    *  Compute the probability array:
+    *  prob[] = { 1/2, 1/2, 1/4, 1/4, 1/4, 1/4,
+    *             1/8, ... , 1/16, ..., 1/(MAXPROB+1)}
+    */
+   {
+      unsigned n;
+      unsigned exp;			/* current exponent */
+      
+      prob = Calloc (1 << (MAX_PROB + 1), sizeof (unsigned));
+   
+      for (index = 0, n = MIN_PROB; n <= MAX_PROB; n++)
+	 for (exp = 0; exp < 1U << n; exp++, index++)
+	    prob [index] = n;
+   }
+   
+   high      = HIGH;			/* 1.0 */
+   low       = LOW;			/* 0.0 */
+   underflow = 0;			/* no underflow bits */
+
+   index = 0;
+
+   /*
+    *  Encode column 0 with a quasi arithmetic coder (QAC).
+    *  Advantage of this QAC with respect to a binary AC:
+    *  Instead of using time consuming multiplications and divisions
+    *  to compute the probability of the most probable symbol (MPS) and
+    *  the range of the interval, a table look up procedure linked
+    *  with a shift operation is used for both computations.
+    */
+   for (row = wfa->basis_states; row <= last_row; row++)
+      for (label = 0; label < MAXLABELS; label++)
+	 if (isrange (wfa->tree [row][label]))
+	 {
+	    if (wfa->into [row][label][0] != 0)
+	    {
+	       /*
+		*  encode the MPS '0'
+		*/
+	       high = high - ((high - low) >> prob [index]) - 1;
+	       RESCALE_OUTPUT_INTERVAL;
+	       
+	       if (index < 1020)
+		  index++;
+	    }
+	    else
+	    {
+	       /*
+		*  encode the LPS '1'
+		*/
+	       low = high - ((high - low) >> prob [index]);
+
+	       RESCALE_OUTPUT_INTERVAL;
+	       
+	       total++;
+	       index >>= 1;
+	    }
+	 }
+   /*
+    *  Flush the quasi-arithmetic encoder
+    */
+   low = high;
+
+   RESCALE_OUTPUT_INTERVAL;
+   
+   OUTPUT_BYTE_ALIGN (output);
+
+   Free (prob);
+
+   debug_message ("delta-state0: %5d bits. (%5d symbols => %5.2f bps)",
+		  bits_processed (output) - bits, total,
+		  total > 0 ? ((bits_processed (output) - bits) /
+			       (double) total) : 0);
+
+   return total;
+}   
+
+static unsigned
+chroma_encoding (const wfa_t *wfa, bitfile_t *output)
+/*
+ *  Write transition matrices of 'wfa' states which are part of the
+ *  chroma channels Cb and Cr to stream 'output'.
+ *
+ *  Return value:
+ *	number of non-zero matrix elements (WFA edges)
+ */
+{
+
+   unsigned  domain;			/* current domain, counter */
+   unsigned  label;			/* current label */
+   unsigned  total = 0;			/* number of '1' elements */
+   u_word_t  high;			/* Start of the current code range */
+   u_word_t  low;			/* End of the current code range */
+   unsigned  underflow;			/* underflow bits */
+   unsigned *prob;			/* probability array */
+   unsigned  index;			/* probability index, counter */
+   unsigned  next_index;		/* probability of last domain */
+   unsigned  row;			/* current matrix row */
+   word_t   *y_domains;
+   unsigned  count = 0;			/* number of transitions for part 1 */
+   unsigned  bits  = bits_processed (output);
+   
+   /*
+    *  Compute the asymmetric probability array
+    *  prob[] = { 1/2, 1/2, 1/4, 1/4, 1/4, 1/4,
+    *                     1/8, ... , 1/16, ..., 1/(MAXPROB+1)}
+    */
+   {
+      unsigned n;
+      unsigned exp;			/* current exponent */
+      
+      prob = Calloc (1 << (MAX_PROB + 1), sizeof (unsigned));
+   
+      for (index = 0, n = MIN_PROB; n <= MAX_PROB; n++)
+	 for (exp = 0; exp < 1U << n; exp++, index++)
+	    prob [index] = n;
+   }
+   
+   high      = HIGH;			/* 1.0 */
+   low       = LOW;			/* 0.0 */
+   underflow = 0;			/* no underflow bits */
+
+   next_index = index = 0;
+
+   y_domains = compute_hits (wfa->basis_states,
+			     wfa->tree [wfa->tree [wfa->root_state][0]][0],
+			     wfa->wfainfo->chroma_max_states, wfa);
+
+   /*
+    *  First of all, read all matrix columns given in the list 'y_domains'
+    *  which note all admitted domains.
+    *  These matrix elements are stored with QAC (see column_0_encoding ()).
+    */
+   for (domain = 0; y_domains [domain] != -1; domain++)
+   {
+      bool_t save_index = YES;		/* YES: store current prob. index */
+      
+      row   = wfa->tree [wfa->tree [wfa->root_state][0]][0] + 1;
+      index = next_index;
+	 
+      for (; row < wfa->states; row++)
+      {
+	 for (label = 0; label < MAXLABELS; label++)
+	    if (isrange (wfa->tree [row][label]))
+	    {
+	       unsigned	edge;
+	       int	into;
+	       bool_t    match;		/* approx with current domain found */
+	       
+	       for (match = NO, edge = 0;
+		    isedge (into = wfa->into [row][label][edge])
+			    && (unsigned) into < row;
+		    edge++)
+		  if (into == y_domains [domain]
+		      && into != wfa->y_state [row][label])
+		     match = YES;
+	       if (!match)
+	       {
+		  /*
+		   *  encode the MPS '0'
+		   */
+		  high = high - ((high - low) >> prob [index]) - 1;
+
+		  RESCALE_OUTPUT_INTERVAL;
+		     
+		  if (index < 1020)
+		     index++;
+	       }
+	       else
+	       {
+		  /*
+		   *  encode the LPS '1'
+		   */
+		  low = high - ((high - low) >> prob [index]);
+
+		  RESCALE_OUTPUT_INTERVAL;
+		     
+		  total++;
+		  index >>= 1;
+	       }
+	    }
+	 if (save_index)
+	 {
+	    next_index = index;
+	    save_index = NO;
+	 }
+      }
+   }
+
+   debug_message ("CbCr_matrix:  %5d bits. (%5d symbols => %5.2f bps)",
+		  bits_processed (output) - bits, total,
+		  total > 0 ? ((bits_processed (output) - bits) /
+			       (double) total) : 0);
+   count = total;
+   bits  = bits_processed (output);
+   
+   /*
+    *  Encode the additional column which indicates whether there
+    *  are transitions to a state with same spatial coordinates
+    *  in the Y component.
+    *
+    *  Again, quasi arithmetic coding is used for this task.
+    */
+
+   next_index = index = 0;
+
+   for (row = wfa->tree [wfa->tree [wfa->root_state][0]][0] + 1;
+	row < wfa->states; row++)
+      for (label = 0; label < MAXLABELS; label++)
+	 if (!wfa->y_column [row][label])
+	 {
+	    /*
+	     *  encode the MPS '0'
+	     */
+	    high = high - ((high - low) >> prob [index]) - 1;
+
+	    RESCALE_OUTPUT_INTERVAL;
+	    
+	    if (index < 1020)
+	       index++;
+	 }
+	 else
+	 {
+	    /*
+	     *  encode the LPS '1'
+	     */
+	    low = high - ((high - low) >> prob [index]);
+
+	    RESCALE_OUTPUT_INTERVAL;
+
+	    index >>= 1;
+	    total++;
+	 }
+
+   /*
+    *  Flush the quasi-arithmetic encoder
+    */
+   low = high;
+
+   RESCALE_OUTPUT_INTERVAL;
+   OUTPUT_BYTE_ALIGN (output);
+
+   debug_message ("Yreferences:  %5d bits. (%5d symbols => %5.2f bps)",
+		  bits_processed (output) - bits, total - count,
+		  total - count > 0 ? ((bits_processed (output) - bits) /
+				       (double) (total - count)) : 0);
+
+   Free (prob);
+   Free (y_domains);
+   
+   return total;
+}
diff --git a/converter/other/fiasco/output/matrices.h b/converter/other/fiasco/output/matrices.h
new file mode 100644
index 00000000..f880fef8
--- /dev/null
+++ b/converter/other/fiasco/output/matrices.h
@@ -0,0 +1,28 @@
+/*
+ *  matrices.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:31 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _MATRICES_H
+#define _MATRICES_H
+
+#include "wfa.h"
+#include "bit-io.h"
+
+unsigned
+write_matrices (bool_t use_normal_domains, bool_t use_delta_domains,
+		const wfa_t *wfa, bitfile_t *output);
+
+#endif /* _MATRICES_H */
+
diff --git a/converter/other/fiasco/output/mc.c b/converter/other/fiasco/output/mc.c
new file mode 100644
index 00000000..afff586b
--- /dev/null
+++ b/converter/other/fiasco/output/mc.c
@@ -0,0 +1,250 @@
+/*
+ *  mc.c:		Output of motion compensation	
+ *
+ *  Written by:		Michael Unger
+ *			Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:31 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "wfa.h"
+#include "bit-io.h"
+#include "mvcode.h"
+
+#include "mc.h"
+
+/*****************************************************************************
+
+			     local variables
+  
+*****************************************************************************/
+
+static unsigned p_frame_codes [4][2] =
+/*
+ *  Code values and bits for P-frame prediction
+ *  NONE,  FORWARD
+ */
+{
+   {1, 1}, {0, 1}, {0, 0}, {0, 0} 
+};
+
+static unsigned b_frame_codes [4][2] =
+/*
+ *  Code values and bits for B-frame prediction
+ *  NONE,  FORWARD,  BACKWARD, INTERPOLATED
+ */
+{
+   {1, 1}, {000, 3}, {001, 3}, {01, 2} 
+};
+
+enum vlc_e {CODE = 0, BITS = 1};
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static void
+encode_mc_tree (unsigned max_state, frame_type_e frame_type, const wfa_t *wfa,
+	       bitfile_t *output);
+static void
+encode_mc_coords (unsigned max_state, const wfa_t *wfa, bitfile_t *output);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+void
+write_mc (frame_type_e frame_type, const wfa_t *wfa, bitfile_t *output)
+{
+   unsigned max_state = wfa->wfainfo->color
+			? wfa->tree[wfa->tree[wfa->root_state][0]][0]
+			: wfa->states;
+
+   encode_mc_tree (max_state, frame_type, wfa, output);
+   encode_mc_coords (max_state, wfa, output);
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static void
+encode_mc_tree (unsigned max_state, frame_type_e frame_type, const wfa_t *wfa,
+		bitfile_t *output)
+/*
+ *  Write tree of motion compensation decisions to the 'output' stream.
+ *  Depending on 'frame_type' different decoding methods are used.
+ *  'max_state' is the last state with motion compensation infos.
+ *
+ *  No return value.
+ */
+{
+   unsigned  label;			/* current label */
+   unsigned  state;			/* current state */
+   unsigned  total = 0;			/* number of motion tree decisions */
+   unsigned  queue [MAXSTATES];		/* state numbers in BFO */
+   unsigned  current;			/* current node to process */
+   unsigned  last;			/* last node (update every new node) */
+   mc_type_e type;			/* type of motion compensation */
+   unsigned	     (*mc_tree_codes)[2]; /* pointer to VLC table */
+   unsigned  bits  = bits_processed (output); /* number of bits used */
+   
+   if (frame_type == P_FRAME)
+      mc_tree_codes = p_frame_codes;	/* binary code */
+   else 
+      mc_tree_codes = b_frame_codes;	/* variable length code */
+   
+   /*
+    *  Traverse tree in breadth first order (starting at
+    *  level 'wfa->p_max_level'). Use a queue to store the childs
+    *  of each node ('last' is the next free queue element).  
+    */
+
+   for (last = 0, state = wfa->basis_states; state < max_state; state++)
+      if (wfa->level_of_state [state] - 1 == (int) wfa->wfainfo->p_max_level)
+	 queue [last++] = state;	/* init level = 'mc_max_level' */
+   
+   for (current = 0; current < last; current++)
+      for (label = 0; label < MAXLABELS; label++)
+      {
+	 state = queue [current];
+	 type  = wfa->mv_tree [state][label].type;
+	 if (wfa->x [state][label]
+	     + width_of_level (wfa->level_of_state [state] - 1)
+	     <= wfa->wfainfo->width
+	     &&
+	     wfa->y [state][label]
+	     + height_of_level (wfa->level_of_state [state] - 1)
+	     <= wfa->wfainfo->height)
+	 {
+	    put_bits (output, mc_tree_codes [type][CODE],
+		      mc_tree_codes [type][BITS]);
+	    total++;
+	 }
+	 if (type == NONE && !isrange (wfa->tree [state][label]) &&
+	     wfa->level_of_state [state] - 1 >=
+	     (int) wfa->wfainfo->p_min_level)
+	    queue [last++] = wfa->tree [state][label]; /* append child */
+	 
+      }
+
+   OUTPUT_BYTE_ALIGN (output);
+   debug_message ("mc-tree:      %5d bits. (%5d symbols => %5.2f bps)",
+		  bits_processed (output) - bits, total,
+		  total > 0 ? ((bits_processed (output) - bits) /
+			       (double) total) : 0);
+}
+
+static void
+encode_mc_coords (unsigned max_state, const wfa_t *wfa, bitfile_t *output)
+/*
+ *  Write motion vector coordinates to the 'output' stream. They are stored
+ *  with the static Huffman code of the MPEG and H.263 standards.
+ *  'max_state' is the last state with motion compensation infos.
+ *
+ *  No return value.
+ */
+{
+   unsigned  state;			/* current state */
+   unsigned  label;			/* current label */
+   unsigned  level_count [MAXLEVEL];	/* number of mv per level */
+   unsigned  level;			/* counter */
+   unsigned  ftotal = 0;		/* #forward motion tree decisions */
+   unsigned  btotal = 0;		/* #backward decisions */
+   unsigned  itotal = 0;		/* #interpolated decisions */
+   unsigned  bits   = bits_processed (output); /* number of bits used */
+   unsigned  sr     = wfa->wfainfo->search_range; /* search range */
+   
+   for (level = wfa->wfainfo->p_max_level;
+	level >= wfa->wfainfo->p_min_level; level--)
+      level_count [level] = 0;
+   
+   for (state = wfa->basis_states; state < max_state; state++)
+      for (label = 0; label < MAXLABELS; label++)
+      {
+	 mv_t *mv = &wfa->mv_tree[state][label]; /* motion vector info */
+	 
+	 if (mv->type != NONE)
+	 {
+	    level_count [wfa->level_of_state [state] - 1]++;
+	    switch (mv->type)
+	    {
+	       case FORWARD:
+		  put_bits (output,
+			    mv_code_table[(mv->fx + sr)][CODE],
+			    mv_code_table[(mv->fx + sr)][BITS]);
+		  put_bits (output,
+			    mv_code_table[(mv->fy + sr)][CODE],
+			    mv_code_table[(mv->fy + sr)][BITS]);
+		  ftotal++;
+		  break;
+	       case BACKWARD:
+		  put_bits (output,
+			    mv_code_table[(mv->bx + sr)][CODE],
+			    mv_code_table[(mv->bx + sr)][BITS]);
+		  put_bits (output,
+			    mv_code_table[(mv->by + sr)][CODE],
+			    mv_code_table[(mv->by + sr)][BITS]);
+		  btotal++;
+		  break;
+	       case INTERPOLATED:
+		  put_bits (output,
+			    mv_code_table[(mv->fx + sr)][CODE],
+			    mv_code_table[(mv->fx + sr)][BITS]);
+		  put_bits (output,
+			    mv_code_table[(mv->fy + sr)][CODE],
+			    mv_code_table[(mv->fy + sr)][BITS]);
+		  put_bits (output,
+			    mv_code_table[(mv->bx + sr)][CODE],
+			    mv_code_table[(mv->bx + sr)][BITS]);
+		  put_bits (output,
+			    mv_code_table[(mv->by + sr)][CODE],
+			    mv_code_table[(mv->by + sr)][BITS]);
+		  itotal++;
+		  break;
+	       default:
+		  break;
+	    }
+	 }
+      }
+
+   OUTPUT_BYTE_ALIGN (output);
+   
+   debug_message ("Motion compensation: %d forward, %d backward, "
+		  "%d interpolated", ftotal, btotal, itotal);
+
+   for (level = wfa->wfainfo->p_max_level;
+	level >= wfa->wfainfo->p_min_level; level--)
+      debug_message ("Level %d: %d motion vectors", level, level_count[level]);
+   
+   {
+      unsigned  total = ftotal * 2 + btotal * 2 + itotal * 4;
+
+      debug_message ("mv-coord:     %5d bits. (%5d symbols => %5.2f bps)",
+		     bits_processed (output) - bits, total,
+		     total > 0 ? ((bits_processed (output) - bits) /
+				  (double) total) : 0);
+   }
+
+   return;
+}
diff --git a/converter/other/fiasco/output/mc.h b/converter/other/fiasco/output/mc.h
new file mode 100644
index 00000000..b7843fd8
--- /dev/null
+++ b/converter/other/fiasco/output/mc.h
@@ -0,0 +1,28 @@
+/*
+ *  mc.h
+ *
+ *  Written by:		Michael Unger
+ *			Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:31 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _MC_H
+#define _MC_H
+
+#include "wfa.h"
+#include "bit-io.h"
+
+void
+write_mc (frame_type_e frame_type, const wfa_t *wfa, bitfile_t *output);
+
+#endif /* not _MC_H */
+
diff --git a/converter/other/fiasco/output/nd.c b/converter/other/fiasco/output/nd.c
new file mode 100644
index 00000000..a09ff762
--- /dev/null
+++ b/converter/other/fiasco/output/nd.c
@@ -0,0 +1,244 @@
+/*
+ *  nd.c:		Output of prediction tree	
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:31 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "wfa.h"
+#include "arith.h"
+#include "misc.h"
+#include "bit-io.h"
+#include "rpf.h"
+#include "list.h"
+
+#include "nd.h"
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static unsigned
+encode_nd_tree (const wfa_t *wfa, bitfile_t *output);
+static void
+encode_nd_coefficients (unsigned total, const wfa_t *wfa, bitfile_t *output);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+void
+write_nd (const wfa_t *wfa, bitfile_t *output)
+/*
+ *  Write prediction information of 'wfa' to given stream 'output'.
+ *  Coefficients are quantized with model 'p_rpf'.
+ *
+ *  No return value.
+ */
+{
+   unsigned total = encode_nd_tree (wfa, output);
+   
+   if (total > 0)
+      encode_nd_coefficients (total, wfa, output);
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static unsigned
+encode_nd_tree (const wfa_t *wfa, bitfile_t *output)
+/*
+ *  Write prediction tree of 'wfa' to given stream 'output'. 
+ *
+ *  No return value.
+ */
+{
+   lqueue_t *queue;			/* queue of states */
+   int	     state, next;		/* state and its current child */
+   unsigned  used, not_used;		/* counter ND used/not used */
+   u_word_t  low;			/* Start of the current code range */
+   u_word_t  high;			/* End of the current code range */
+   u_word_t  underflow;			/* Number of underflow bits pending */
+   u_word_t  sum0, sum1;		/* Probability model */
+   unsigned  bits = bits_processed (output);
+
+   used = not_used = 0;
+   
+   /*
+    *  Initialize arithmetic coder
+    */
+   low       = 0;
+   high      = 0xffff;
+   underflow = 0;
+   sum0      = 1;
+   sum1      = 11;
+   
+   queue = alloc_queue (sizeof (int));
+   state = wfa->root_state;
+   queue_append (queue, &state);
+   
+   /*
+    *  Traverse the WFA tree in breadth first order (using a queue).
+    */
+   while (queue_remove (queue, &next))
+   {
+      unsigned label;
+      
+      if (wfa->level_of_state [next] > wfa->wfainfo->p_max_level + 1)
+      {
+	 /*
+	  *  Nondetermismn is not allowed at levels larger than
+	  *  'wfa->wfainfo->p_max_level'.
+	  */
+	 for (label = 0; label < MAXLABELS; label++)
+	    if (ischild (state = wfa->tree [next][label]))
+	       queue_append (queue, &state); /* continue with childs */
+      }
+      else if (wfa->level_of_state [next] > wfa->wfainfo->p_min_level)
+      {
+	 for (label = 0; label < MAXLABELS; label++)
+	    if (ischild (state = wfa->tree [next][label]))
+	    {
+	       unsigned range;		/* Current interval range */
+
+	       if (isedge (wfa->into [next][label][0])) /* prediction used */
+	       {
+		  used++;
+		  
+		  /*
+		   *  Encode a '1' symbol
+		   */
+		  range =  (high - low) + 1;
+		  low   = low + (u_word_t) ((range * sum0) / sum1);
+		  RESCALE_OUTPUT_INTERVAL;
+	       }
+	       else			/* no predict., continue with childs */
+	       {
+		  not_used++;
+		  if (wfa->level_of_state [state] > wfa->wfainfo->p_min_level)
+		     queue_append (queue, &state);
+		  
+		  /*
+		   *  Encode a '0' symbol
+		   */
+		  range =  (high - low) + 1;
+		  high  = low + (u_word_t) ((range * sum0) / sum1 - 1);
+		  RESCALE_OUTPUT_INTERVAL;
+		  sum0++;
+	       }
+	       /*
+		*  Update the frequency counts
+		*/
+	       sum1++;
+	       if (sum1 > 50)		/* Scale the symbol frequencies */
+	       {
+		  sum0 >>= 1;
+		  sum1 >>= 1;
+		  if (!sum0)
+		     sum0 = 1;
+		  if (sum0 >= sum1)
+		     sum1 = sum0 + 1;
+	       }
+	    }
+	 
+      }
+   }
+   free_queue (queue);
+
+   /*
+    *  Flush the quasi-arithmetic encoder
+    */
+   low = high;
+   RESCALE_OUTPUT_INTERVAL;
+   OUTPUT_BYTE_ALIGN (output);
+
+   debug_message ("%d nd fields: %d used nd, %d used not nd", used + not_used,
+		  used, not_used);
+   {
+      unsigned total = used + not_used;
+      
+      debug_message ("nd-tree:      %5d bits. (%5d symbols => %5.2f bps)",
+		     bits_processed (output) - bits, total,
+		     total > 0 ? ((bits_processed (output) - bits) /
+				  (double) total) : 0);
+   }
+
+   return used;
+}
+
+static void
+encode_nd_coefficients (unsigned total, const wfa_t *wfa, bitfile_t *output)
+/*
+ *  Write #'total' weights of nondeterministic part of 'wfa' to given 'output'
+ *  stream. Coefficients are stored with arithmetic coding (the model is
+ *  given by 'p_rpf').
+ *
+ *  No return value.
+ */
+{
+   unsigned bits = bits_processed (output);
+
+   {
+      unsigned *coefficients;		/* array of factors to encode */
+      unsigned *ptr;			/* pointer to current factor */
+      unsigned	state, label, edge;
+      word_t	domain;
+      
+      ptr = coefficients  = Calloc (total, sizeof (unsigned));
+
+      for (state = wfa->basis_states; state < wfa->states; state++)
+	 for (label = 0; label < MAXLABELS; label++)
+	    if (ischild (wfa->tree [state][label])
+		&& isedge (wfa->into [state][label][0]))
+	       for (edge = 0; isedge (domain = wfa->into [state][label][edge]);
+		    edge++)
+	       {
+		  if (ptr - coefficients >= (int) total)
+		     error ("Can't write more than %d coefficients.", total);
+
+		  *ptr++ = rtob (wfa->weight [state][label][edge],
+				 wfa->wfainfo->dc_rpf);
+	       }
+
+      /*
+       *  Encode array of coefficients with arithmetic coding
+       */
+      {
+	 const int scaling = 50;	/* scaling factor of prob. model */
+	 unsigned  c_symbols = 1 << (wfa->wfainfo->dc_rpf->mantissa_bits + 1);
+
+	 encode_array (output, coefficients, NULL, &c_symbols, 1,
+		       total, scaling);
+      }
+      
+      debug_message ("nd-factors:   %5d bits. (%5d symbols => %5.2f bps)",
+		     bits_processed (output) - bits, total,
+		     total ? ((bits_processed (output) - bits)
+			      / (double) total) : 0);
+      Free (coefficients);
+   }
+}
+
+
diff --git a/converter/other/fiasco/output/nd.h b/converter/other/fiasco/output/nd.h
new file mode 100644
index 00000000..600b3d73
--- /dev/null
+++ b/converter/other/fiasco/output/nd.h
@@ -0,0 +1,27 @@
+/*
+ *  nd.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:31 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _ND_H
+#define _ND_H
+
+#include "wfa.h"
+#include "bit-io.h"
+
+void
+write_nd (const wfa_t *wfa, bitfile_t *output);
+
+#endif /* not _ND_H */
+
diff --git a/converter/other/fiasco/output/tree.c b/converter/other/fiasco/output/tree.c
new file mode 100644
index 00000000..0056d7dd
--- /dev/null
+++ b/converter/other/fiasco/output/tree.c
@@ -0,0 +1,176 @@
+/*
+ *  tree.c:		Output of bintree partitioning
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:31 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "wfa.h"
+#include "bit-io.h"
+#include "arith.h"
+#include "misc.h"
+
+#include "tree.h"
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static void
+encode_tree (bitfile_t *output, const byte_t *data, unsigned n_data,
+	     unsigned scaling, u_word_t sum0, u_word_t sum1);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+void
+write_tree (const wfa_t *wfa, bitfile_t *output)
+/*
+ *  Write bintree to stream 'output'.
+ *  Traverse tree in breadth first order and save a '1' for each child
+ *  and a '0' for each range image.
+ *
+ *  No return value.
+ */
+{
+   unsigned  queue [MAXSTATES];		/* state numbers in BFO */
+   unsigned  current;			/* current node to process */
+   unsigned  last;			/* last node (update every new node) */
+   unsigned  label;			/* current label */
+   int	     into;			/* next child */
+   byte_t   *tree_string;		/* bitstring to encode */
+   unsigned  total = 0;			/* number of ranges */
+   unsigned  bits  = bits_processed (output); /* number of bits */
+
+   /*
+    *  Traverse tree in breadth first order. Use a queue to store
+    *  the childs of each node ('last' is the next free queue element).
+    *  The first element ('current') of this queue will get the new parent
+    *  node. 
+    */
+   tree_string = Calloc (MAXSTATES * MAXLABELS, sizeof (byte_t));
+   queue [0] = wfa->root_state;
+   for (last = 1, current = 0; current < last; current++)
+      for (label = 0; label < MAXLABELS; label++)
+	 if (!isrange (into = wfa->tree [queue[current]][label])) /* child ? */
+	 {
+	    queue [last++]        = into;
+	    tree_string [total++] = 1;
+	 }
+	 else				/* or range ? */
+	    tree_string [total++] = 0;
+
+   if (total != (wfa->states - wfa->basis_states) * MAXLABELS)
+      error ("total [%d] != (states - basis_states) * 2 [%d]", total,
+	     (wfa->states - wfa->basis_states) * MAXLABELS);
+   
+   {
+      unsigned scale = total / 20 ;
+
+      encode_tree (output, tree_string, total, scale, 1, 11);
+   }
+
+   Free (tree_string);
+   
+   debug_message ("tree:         %5d bits. (%5d symbols => %5.2f bps)",
+		  bits_processed (output) - bits, total,
+		  total > 0 ? ((bits_processed (output) - bits)
+			       / (double) total) : 0);
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static void
+encode_tree (bitfile_t *output, const byte_t *data, unsigned n_data,
+	     unsigned scaling, u_word_t sum0, u_word_t sum1)
+/*
+ *  Encode bintree data with adaptive binary arithmetic coding.
+ *  Write 'n_data' output symbols stored in 'data' to stream 'output'.
+ *  Rescale probability model after every 'scaling' symbols.
+ *  Initial counts are given by 'sum0' and 'sum1'.
+ *
+ *  No return value.
+ */
+{
+   u_word_t low;			/* Start of the current code range */
+   u_word_t high;			/* End of the current code range */
+   u_word_t underflow;			/* Number of underflow bits pending */
+   unsigned n;				/* Data counter */
+
+   low       = 0;
+   high      = 0xffff;
+   underflow = 0;
+
+   for (n = n_data; n; n--)
+   {
+      unsigned range;			/* Current interval range */
+      
+      if (!*data++)
+      {
+	 /*
+	  *  encode a '0'
+	  */
+	 range =  (high - low) + 1;
+	 high  = low + (u_word_t) ((range * sum0) / sum1 - 1);
+
+	 RESCALE_OUTPUT_INTERVAL;
+
+	 sum0++;
+      }
+      else
+      {
+	 /*
+	  *  encode a '1'
+	  */
+	 range =  (high - low) + 1;
+	 low   = low + (u_word_t) ((range * sum0) / sum1);
+
+	 RESCALE_OUTPUT_INTERVAL;
+      }
+      /*
+       *  Update the frequency counts
+       */
+      sum1++;
+      if (sum1 > scaling) /* Scale the symbol frequencies */
+      {
+	 sum0 >>= 1;
+	 sum1 >>= 1;
+	 if (!sum0)
+	    sum0 = 1;
+	 if (sum0 >= sum1)
+	    sum1 = sum0 + 1;
+      }
+   }
+   /*
+    *  Flush the quasi-arithmetic encoder
+    */
+   low = high;
+
+   RESCALE_OUTPUT_INTERVAL;
+
+   OUTPUT_BYTE_ALIGN (output);
+}
diff --git a/converter/other/fiasco/output/tree.h b/converter/other/fiasco/output/tree.h
new file mode 100644
index 00000000..6f8a3800
--- /dev/null
+++ b/converter/other/fiasco/output/tree.h
@@ -0,0 +1,27 @@
+/*
+ *  tree.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:31 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _TREE_H
+#define _TREE_H
+
+#include "wfa.h"
+#include "bit-io.h"
+
+void
+write_tree (const wfa_t *wfa, bitfile_t *output);
+
+#endif /* not _TREE_H */
+
diff --git a/converter/other/fiasco/output/weights.c b/converter/other/fiasco/output/weights.c
new file mode 100644
index 00000000..085a1f00
--- /dev/null
+++ b/converter/other/fiasco/output/weights.c
@@ -0,0 +1,200 @@
+/*
+ *  weights.c:		Output of weights
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:31 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "wfa.h"
+#include "misc.h"
+#include "bit-io.h"
+#include "arith.h"
+#include "wfalib.h"
+
+#include "weights.h"
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+void
+write_weights (unsigned total, const wfa_t *wfa, bitfile_t *output)
+/*
+ *  Traverse the transition matrices of the 'wfa' and write #'total'
+ *  weights != 0 to stream 'output'.
+ *
+ *  No return value.
+ */
+{
+   unsigned  state, label;		/* current label */
+   unsigned  offset1, offset2;		/* model offsets. */
+   unsigned  offset3, offset4;		/* model offsets. */
+   unsigned *weights_array;		/* array of weights to encode */
+   unsigned *wptr;			/* pointer to current weight */
+   unsigned *level_array;		/* array of corresponding levels */
+   unsigned *lptr;			/* pointer to current corr. level */
+   int	     min_level, max_level;	/* min and max range level */
+   int	     d_min_level, d_max_level; 	/* min and max delta range level */
+   bool_t    dc, d_dc;			/* true if dc or delta dc are used */
+   bool_t    delta_approx = NO;		/* true if delta has been used */
+   unsigned  delta_count  = 0;		/* number of delta ranges */
+   unsigned  bits 	  = bits_processed (output);
+   
+   /*
+    *  Check whether delta approximation has been used
+    */
+   for (state = wfa->basis_states; state < wfa->states; state++)
+      if (wfa->delta_state [state])
+      {
+	 delta_approx = YES;
+	 break;
+      }
+   
+   /*
+    *  Generate array of corresponding levels (context of probability model)
+    */
+   min_level = d_min_level = MAXLEVEL;
+   max_level = d_max_level = 0;
+   dc 	     = d_dc	   = NO;
+   
+   for (state = wfa->basis_states; state < wfa->states; state++)
+      for (label = 0; label < MAXLABELS; label++)
+         if (isrange (wfa->tree [state][label]))
+	 {
+	    if (delta_approx && wfa->delta_state [state]) /* delta approx. */
+	    {
+	       d_min_level = min (d_min_level,
+				  wfa->level_of_state [state] - 1);
+	       d_max_level = max (d_max_level,
+				  wfa->level_of_state [state] - 1);
+	       if (wfa->into [state][label][0] == 0)
+		  d_dc = YES;
+	    }
+	    else
+	    {
+	       min_level = min (min_level, wfa->level_of_state [state] - 1);
+	       max_level = max (max_level, wfa->level_of_state [state] - 1);
+	       if (wfa->into [state][label][0] == 0)
+		  dc = YES;
+	    }
+	 }
+   if (min_level > max_level)		/* no lc found */
+      max_level = min_level - 1;
+   if (d_min_level > d_max_level)
+      d_max_level = d_min_level - 1;
+
+   /*
+    *  Context model:
+    *		0		DC weight
+    *		1		Delta DC weight
+    *		2-k		normal weights per level
+    *		k+1 - m		Delta weights per level
+    */
+
+   offset1 = dc ? 1 : 0;
+   offset2 = offset1 + (d_dc ? 1 : 0);
+   offset3 = offset2 + (max_level - min_level + 1);
+   offset4 = offset3 + (d_max_level - d_min_level + 1);
+   
+   /*
+    *  Weights are encoded as follows:
+    *  all weights of state n
+    *     sorted by label
+    *        sorted by domain number
+    */
+
+   wptr = weights_array = Calloc (total, sizeof (unsigned));
+   lptr = level_array   = Calloc (total, sizeof (unsigned));
+
+   for (state = wfa->basis_states; state < wfa->states; state++)
+      for (label = 0; label < MAXLABELS; label++)
+         if (isrange (wfa->tree [state][label]))
+	 {
+	    int	edge;			/* current edge */
+	    int	domain;			/* current domain (context of model) */
+	    
+            for (edge = 0; isedge (domain = wfa->into [state][label][edge]);
+		 edge++)
+            {
+	       if (wptr - weights_array >= (int) total)
+		  error ("Can't write more than %d weights.", total);
+	       if (domain)		/* not DC component */
+	       {
+		  if (delta_approx && wfa->delta_state [state]) /* delta */
+		  {
+		     *wptr++ = rtob (wfa->weight [state][label][edge],
+				     wfa->wfainfo->d_rpf);
+		     *lptr++ = offset3
+			       + wfa->level_of_state [state] - 1 - d_min_level;
+		     delta_count++;
+		  }
+		  else
+		  {
+		     *wptr++ = rtob (wfa->weight [state][label][edge],
+				     wfa->wfainfo->rpf);
+		     *lptr++ = offset2
+			       + wfa->level_of_state [state] - 1 - min_level;
+		  }
+	       }
+	       else			/* DC component */
+	       {
+		  if (delta_approx && wfa->delta_state [state]) /* delta */
+		  {
+		     *wptr++ = rtob (wfa->weight [state][label][edge],
+				     wfa->wfainfo->d_dc_rpf);
+		     *lptr++ = offset1;
+		  }
+		  else
+		  {
+		     *wptr++ = rtob (wfa->weight [state][label][edge],
+				     wfa->wfainfo->dc_rpf);
+		     *lptr++ = 0;
+		  }
+	       }
+            }
+	 }
+
+   {
+      unsigned	 i;
+      unsigned	*c_symbols = Calloc (offset4, sizeof (int));
+      const int	 scale 	   = 500;	/* scaling of probability model */
+
+      c_symbols [0] = 1 << (wfa->wfainfo->dc_rpf->mantissa_bits + 1);
+      if (offset1 != offset2)
+	 c_symbols [offset1] = 1 << (wfa->wfainfo->d_dc_rpf->mantissa_bits
+				     + 1);
+      for (i = offset2; i < offset3; i++)
+	 c_symbols [i] = 1 << (wfa->wfainfo->rpf->mantissa_bits + 1);
+      for (; i < offset4; i++)
+	 c_symbols [i] = 1 << (wfa->wfainfo->d_rpf->mantissa_bits + 1);
+      
+      encode_array (output, weights_array, level_array, c_symbols, offset4,
+		    total, scale);
+      Free (c_symbols);
+   }
+   
+   debug_message ("%d delta weights out of %d.", delta_count, total);
+   debug_message ("weights:      %5d bits. (%5d symbols => %5.2f bps)",
+		  bits_processed (output) - bits, total,
+		  (bits_processed (output) - bits) / (double) total);
+
+   Free (weights_array);
+   Free (level_array);
+}
diff --git a/converter/other/fiasco/output/weights.h b/converter/other/fiasco/output/weights.h
new file mode 100644
index 00000000..271203ad
--- /dev/null
+++ b/converter/other/fiasco/output/weights.h
@@ -0,0 +1,27 @@
+/*
+ *  weights.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:50:31 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef _WEIGHTS_H
+#define _WEIGHTS_H
+
+#include "wfa.h"
+#include "bit-io.h"
+
+void
+write_weights (unsigned total, const wfa_t *wfa, bitfile_t *output);
+
+#endif /* not _WEIGHTS_H */
+
diff --git a/converter/other/fiasco/output/write.c b/converter/other/fiasco/output/write.c
new file mode 100644
index 00000000..e6185ad3
--- /dev/null
+++ b/converter/other/fiasco/output/write.c
@@ -0,0 +1,250 @@
+/*
+ *  write.c:        Output of WFA files
+ *
+ *  Written by:     Ullrich Hafner
+ *      
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/07/18 15:44:59 $
+ *  $Author: hafner $
+ *  $Revision: 5.3 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+
+#include "types.h"
+#include "macros.h"
+#include "error.h"
+
+#include "cwfa.h"
+#include "image.h"
+#include "misc.h"
+#include "bit-io.h"
+#include "rpf.h"
+
+#include "tree.h"
+#include "matrices.h"
+#include "weights.h"
+#include "mc.h"
+#include "nd.h"
+#include "write.h"
+ 
+/*****************************************************************************
+
+                prototypes
+  
+*****************************************************************************/
+
+static void
+write_tiling (const tiling_t *tiling, bitfile_t *output);
+
+/*****************************************************************************
+
+                public code
+
+                
+*****************************************************************************/
+
+
+void
+write_next_wfa (const wfa_t *wfa, const coding_t *c, bitfile_t *output)
+/*
+ *  Write 'wfa' to stream 'output'. If the first frame should be written
+ *  then also store the header information of the coding struct 'c'.
+ *
+ *  No return value.
+ */
+{
+   unsigned edges = 0;          /* number of transitions */
+   unsigned bits;
+   
+   debug_message ("--------------------------------------"
+          "--------------------------------------");
+
+   if (c->mt->number == 0)              /* first WFA */
+      write_header (wfa->wfainfo, output);
+  
+   bits = bits_processed (output);
+   
+   /*
+    *  Frame header information
+    */
+   {
+      const int rice_k = 8;     /* parameter of Rice Code */
+
+      write_rice_code (wfa->states, rice_k, output);      
+      write_rice_code (c->mt->frame_type, rice_k, output); 
+      write_rice_code (c->mt->number, rice_k, output);     
+   }
+   
+   OUTPUT_BYTE_ALIGN (output);
+
+   debug_message ("frame-header: %5d bits.", bits_processed (output) - bits);
+
+   if (c->tiling->exponent)     /* write tiling permutation */
+   {
+      put_bit (output, 1);
+      write_tiling (c->tiling, output);
+   }
+   else
+      put_bit (output, 0);
+
+   OUTPUT_BYTE_ALIGN (output);
+
+   write_tree (wfa, output);
+
+   if (c->options.prediction)       /* write nondeterministic approx. */
+   {
+      put_bit (output, 1); 
+      write_nd (wfa, output);
+   }
+   else
+      put_bit (output, 0);
+
+   if (c->mt->frame_type != I_FRAME)    /* write motion compensation info */
+      write_mc (c->mt->frame_type, wfa, output);
+   
+   edges = write_matrices (c->options.normal_domains,
+               c->options.delta_domains, wfa, output);
+
+   if (edges)               /* found at least one approximation */
+      write_weights (edges, wfa, output);
+
+   debug_message ("--------------------------------------"
+          "--------------------------------------");
+}
+
+void
+write_header (const wfa_info_t *wi, bitfile_t *output)
+/*
+ *  Write the header information describing the type of 'wfa'
+ *  to stream 'output'.
+ *
+ *  No return value.
+ */
+{
+   const unsigned rice_k = 8;       /* parameter of Rice Code */
+   unsigned   bits   = bits_processed (output);
+   const char *text;            /* next character to write */
+
+   /*
+    *  Write magic number and name of initial basis
+    */
+   for (text = FIASCO_MAGIC; *text; text++)
+      put_bits (output, *text, 8);
+   put_bits (output, '\n', 8);
+   for (text = wi->basis_name; *text; text++)
+      put_bits (output, *text, 8);
+   put_bits (output, *text, 8);
+   
+   write_rice_code (FIASCO_BINFILE_RELEASE, rice_k, output);
+
+   write_rice_code (HEADER_TITLE, rice_k, output);
+   for (text = wi->title;
+    text && *text && text - wi->title < MAXSTRLEN - 2;
+    text++)
+      put_bits (output, *text, 8);
+   put_bits (output, 0, 8);
+   
+   write_rice_code (HEADER_COMMENT, rice_k, output);
+   for (text = wi->comment;
+    text && *text && text - wi->comment < MAXSTRLEN - 2;
+    text++)
+      put_bits (output, *text, 8);
+   put_bits (output, 0, 8);
+   
+   write_rice_code (HEADER_END, rice_k, output);
+   
+   write_rice_code (wi->max_states, rice_k, output); 
+   put_bit (output, wi->color ? 1 : 0); 
+   write_rice_code (wi->width, rice_k, output);
+   write_rice_code (wi->height, rice_k, output);
+   if (wi->color)
+      write_rice_code (wi->chroma_max_states, rice_k, output); 
+   write_rice_code (wi->p_min_level, rice_k, output); 
+   write_rice_code (wi->p_max_level, rice_k, output); 
+   write_rice_code (wi->frames, rice_k, output);
+   write_rice_code (wi->smoothing, rice_k, output);
+
+   put_bits (output, wi->rpf->mantissa_bits - 2, 3);
+   put_bits (output, wi->rpf->range_e, 2);
+   if (wi->rpf->mantissa_bits != wi->dc_rpf->mantissa_bits ||
+       wi->rpf->range != wi->dc_rpf->range)
+   {
+      put_bit (output, YES);
+      put_bits (output, wi->dc_rpf->mantissa_bits - 2, 3);
+      put_bits (output, wi->dc_rpf->range_e, 2);
+   }
+   else
+      put_bit (output, NO);
+   if (wi->rpf->mantissa_bits != wi->d_rpf->mantissa_bits ||
+       wi->rpf->range != wi->d_rpf->range)
+   {
+      put_bit (output, YES);
+      put_bits (output, wi->d_rpf->mantissa_bits - 2, 3);
+      put_bits (output, wi->d_rpf->range_e, 2);
+   }
+   else
+      put_bit (output, NO);
+   if (wi->dc_rpf->mantissa_bits != wi->d_dc_rpf->mantissa_bits ||
+       wi->dc_rpf->range != wi->d_dc_rpf->range)
+   {
+      put_bit (output, YES);
+      put_bits (output, wi->d_dc_rpf->mantissa_bits - 2, 3);
+      put_bits (output, wi->d_dc_rpf->range_e, 2);
+   }
+   else
+      put_bit (output, NO);
+
+   if (wi->frames > 1)          /* motion compensation stuff */
+   {
+      write_rice_code (wi->fps, rice_k, output); 
+      write_rice_code (wi->search_range, rice_k, output); 
+      put_bit (output, wi->half_pixel ? 1 : 0); 
+      put_bit (output, wi->B_as_past_ref ? 1 : 0);
+   }
+
+   OUTPUT_BYTE_ALIGN (output);
+   debug_message ("header:         %d bits.", bits_processed (output) - bits);
+}
+
+/*****************************************************************************
+
+                private code
+  
+*****************************************************************************/
+
+static void
+write_tiling (const tiling_t *tiling, bitfile_t *output)
+/*
+ *  Write image tiling information given by 'tiling' to stream 'output'.
+ *
+ *  No return value.
+ */
+{
+   const unsigned rice_k = 8;       /* parameter of Rice Code */
+   unsigned       bits   = bits_processed (output);
+   
+   write_rice_code (tiling->exponent, rice_k, output);
+   if (tiling->method == FIASCO_TILING_VARIANCE_ASC
+       || tiling->method == FIASCO_TILING_VARIANCE_DSC)
+   {
+      unsigned tile;            /* current image tile */
+
+      put_bit (output, 1);      
+      for (tile = 0; tile < 1U << tiling->exponent; tile++)
+     if (tiling->vorder [tile] != -1) /* image tile is visible */
+        put_bits (output, tiling->vorder [tile], tiling->exponent);
+   }
+   else
+   {
+      put_bit (output, 0);      
+      put_bit (output, tiling->method == FIASCO_TILING_SPIRAL_ASC);
+   }
+
+   debug_message ("tiling:        %4d bits.", bits_processed (output) - bits);
+}
diff --git a/converter/other/fiasco/output/write.h b/converter/other/fiasco/output/write.h
new file mode 100644
index 00000000..a3ede1f4
--- /dev/null
+++ b/converter/other/fiasco/output/write.h
@@ -0,0 +1,28 @@
+/*
+ *  write.h
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/07/15 17:27:30 $
+ *  $Author: hafner $
+ *  $Revision: 5.2 $
+ *  $State: Exp $
+ */
+
+#ifndef _WRITE_H
+#define _WRITE_H
+
+#include "cwfa.h"
+#include "bit-io.h"
+
+void
+write_next_wfa (const wfa_t *wfa, const coding_t *c, bitfile_t *output);
+void
+write_header (const wfa_info_t *wi, bitfile_t *output);
+
+#endif /* not _WRITE_H */
diff --git a/converter/other/fiasco/params.c b/converter/other/fiasco/params.c
new file mode 100644
index 00000000..3d0a0252
--- /dev/null
+++ b/converter/other/fiasco/params.c
@@ -0,0 +1,727 @@
+/*
+ *  params.c:		Parameter file and command line parsing
+ *
+ *  Written by:		Stefan Frank
+ *			Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/07/15 17:24:21 $
+ *  $Author: hafner $
+ *  $Revision: 5.2 $
+ *  $State: Exp $
+ */
+
+#define _BSD_SOURCE 1   /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <ctype.h>
+#include <math.h>			/* strtod() on SUN sparc */
+
+#include <stdlib.h>
+#include <string.h>
+ 
+#include <getopt.h>			/* system or ../lib */
+
+#include "nstring.h"
+
+#include "types.h"
+#include "macros.h"
+#include "bit-io.h"
+#include "misc.h"
+#include "fiasco.h"
+
+#include "binerror.h"
+
+#include "params.h"
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static void
+read_parameter_file (param_t *params, FILE *file);
+static int
+get_parameter_index (const param_t *params, const char *search_string);
+static void
+set_parameter (param_t *parameter, const char *value);
+static void 
+usage (const param_t *params, const char *progname, const char *synopsis,
+       const char *comment, const char *non_opt_string,
+       bool_t show_all_options, const char *sys_file_name,
+       const char *usr_file_name);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+
+int
+parseargs (param_t *usr_params, 
+           int argc, char **argv, 
+           const char *synopsis,
+           const char *comment, 
+           const char *non_opt_string, 
+           const char *path,
+           const char *sys_file_name, 
+           const char *usr_file_name)
+/*
+ *  Perform the command line parsing.
+ *  List of allowed parameters is given by 'usr_params'.
+ *  Command line and number of parameters are given by 'argv' and 'argc'.
+ *  'synopsis' contains a brief description of the program and
+ *  'comment' may contain some additional advice.
+ *  Initialization order of parameters:
+ *	1.) Default values given by the param_t struct
+ *	2.) System parameter-file ('path'/'sys_file_name')
+ *	3.) User parameter-file ($HOME/'usr_file_name')
+ *	4.) Command line parameters
+ *	5.) Parameter-file forced by option -f (--config-file)
+ *
+ *  Return value:
+ *	index in ARGV of the first ARGV-element that is not an option.
+ *
+ *  Side effects:
+ *	the elements of ARGV are permuted
+ *      usr_params [].value is modified 
+ */
+{
+   extern int optind;			/* index in ARGV of the 1st element
+					   that is not an option */
+   bool_t     detailed_help = NO;	/* NO if all parameters can be modified
+					   with short options too */
+   unsigned   n1;			/* number of user parameters */
+   unsigned   n2;			/* number of system parameters */
+   bool_t     read_config_file = NO;	/* will override command line */
+   param_t    *params;			/* array of user and system params */
+   param_t    *sys_params;		/* array of system parameters */
+   param_t    detailed_sys_params [] =  /* detailed system parameters */
+   {
+      {"version", NULL, 'v', PFLAG, {0}, NULL,
+       "Print program version number, then exit."},
+      {"verbose", "NUM", 'V', PINT, {0}, "1",
+       "Set level of verbosity to `%s'."},
+      {"config", "FILE", 'f', PSTR, {0}, NULL,
+       "Load `%s' to initialize parameters."},
+      {"info", NULL, 'h', PFLAG, {0}, NULL,
+       "Print brief help, then exit."},
+      {"help", NULL, 'H', PFLAG, {0}, NULL,
+       "Print detailed help, then exit."},
+      {NULL, NULL, 0, PSTR, {0}, NULL, NULL }
+   };
+   param_t    short_sys_params [] =	/* short system parameters */
+   {
+      {"version", NULL, 'v', PFLAG, {0}, NULL,
+       "Print program version number, then exit."},
+      {"verbose", "NUM", 'V', PINT, {0}, "1",
+       "Set level of verbosity to `%s'."},
+      {"config", "FILE", 'f', PSTR, {0}, NULL,
+       "Load `%s' to initialize parameters."},
+      {"help", NULL, 'h', PFLAG, {0}, NULL,
+       "Print this help, then exit."},
+      {NULL, NULL, 0, PSTR, {0}, NULL, NULL }
+   };
+   char *sys_path;			/* path to system config file */
+
+   sys_path = calloc (strlen (path) + strlen (sys_file_name) + 2,
+		      sizeof (char));
+   if (!sys_path)
+      error ("Out of memory.");
+   sprintf (sys_path, "%s/%s", path, sys_file_name);
+
+   /*
+    *  Set parameters defaults
+    */
+   {
+       param_t *p;
+      
+       for (p = usr_params; p->name != NULL; p++)
+       {
+           set_parameter (p, p->default_value);
+           if (p->optchar == '\0')
+               detailed_help = YES;
+       }
+
+      sys_params = detailed_help ? detailed_sys_params : short_sys_params;
+      
+      for (p = sys_params; p->name != NULL; p++)
+          set_parameter (p, p->default_value);
+   }
+   /*
+    *  Append system command line option to user parameters
+    */
+   for (n1 = 0; usr_params [n1].name != NULL; n1++)
+      ;
+   for (n2 = 0; sys_params [n2].name != NULL; n2++)
+      ;
+   params = calloc (n1 + n2 + 1, sizeof (param_t));
+   if (!params)
+      error ("Out of memory.");
+
+   memcpy (params, usr_params, n1 * sizeof (param_t));
+   memcpy (params + n1, sys_params, (n2 + 1) * sizeof (param_t));
+   /*
+    *  Try to open the system resource file 'path'/'sys_file_name'
+    */
+   {
+      FILE *parameter_file = open_file (sys_path, NULL, READ_ACCESS);
+      if (parameter_file == NULL)
+/*
+	 warning ("No system resource file found.");
+*/ {}
+      else
+      {
+	 read_parameter_file (params, parameter_file);
+	 fclose (parameter_file);
+      }
+   }
+   /*
+    *  Try to read user resource file $HOME/'usr_file_name'
+    */
+   {
+      FILE *parameter_file = open_file (usr_file_name, "HOME", READ_ACCESS);
+      if (parameter_file != NULL)
+      {
+	 read_parameter_file (params, parameter_file);
+	 fclose (parameter_file);
+      }
+   }
+   /*
+    *  Parse command line options
+    */
+   {
+      extern char   *optarg;		/* argument of current option */
+      struct option *long_options;	/* array of long options */
+      int	     option_index = 0;
+      char	     optstr [MAXSTRLEN]; /* string containing the legitimate
+					    option characters */
+      int	     optchar;		/* found option character */
+
+      /*
+       *  Build short option string for getopt_long (). 
+       */
+      {
+	 param_t *p;			/* counter */
+	 char	 *ptr_optstr;		/* pointer to position in string */
+	 
+	 ptr_optstr = optstr;
+	 for (p = params; p->name != NULL; p++)
+	    if (p->optchar != '\0')
+	    {
+	       *ptr_optstr++ = p->optchar;
+	       if (p->type == POSTR)
+	       {
+		  *ptr_optstr++ = ':';
+		  *ptr_optstr++ = ':';
+	       }
+	       else if (p->type != PFLAG)
+		  *ptr_optstr++ = ':';
+	    }
+	 *ptr_optstr = '\0';
+      }
+      
+      /*
+       *  Build long option string for getopt_long (). 
+       */
+      {
+	 int i;
+	 
+	 long_options = calloc (n1 + n2 + 1, sizeof (struct option));
+	 if (!long_options)
+	    error ("Out of memory.");
+	 for (i = 0; params [i].name != NULL; i++)
+	 {
+	    long_options [i].name    = params [i].name;
+	    switch (params [i].type)
+	    {
+	       case PFLAG:
+		  long_options [i].has_arg = 0;
+		  break;
+	       case POSTR:
+		  long_options [i].has_arg = 2;
+		  break;
+	       case PINT:
+	       case PSTR:
+	       case PFLOAT:
+	       default:
+		  long_options [i].has_arg = 1;
+		  break;
+	    }
+	    long_options [i].has_arg = params [i].type != PFLAG;
+	    long_options [i].flag    = NULL;
+	    long_options [i].val     = 0;
+	 }
+      }
+      
+      /*
+       *  Parse comand line
+       */
+      while ((optchar = getopt_long (argc, argv, optstr, long_options,
+				     &option_index)) != EOF)
+      {
+	 int param_index = -1;
+	 
+	 switch (optchar)
+	 {
+	    case 0:
+	       param_index = option_index;
+	       break;
+	    case ':':
+	       if (detailed_help)
+		  fprintf (stderr,
+			   "Try `%s -h' or `%s --help' for "
+			   "more information.\n",
+			   argv [0], argv [0]);
+	       else
+		  fprintf (stderr, "Try `%s --help' for more information.\n",
+			   argv [0]);
+	       exit (2);
+	       break;
+	    case '?':
+	       if (detailed_help)
+		  fprintf (stderr,
+			   "Try `%s -h' or `%s --help' "
+			   "for more information.\n",
+			   argv [0], argv [0]);
+	       else
+		  fprintf (stderr, "Try `%s --help' for more information.\n",
+			   argv [0]);
+	       exit (2);
+	       break;
+	    default:
+	       {
+		  int i;
+		  
+		  for (i = 0; params [i].name != NULL; i++)
+		     if (params [i].optchar == optchar)
+		     {
+			param_index = i;
+			break;
+		     }
+	       }
+	 }
+	 /*
+	  *  Check for system options
+	  */
+	 if (param_index >= 0)
+	 {
+	    set_parameter (params + param_index, optarg ? optarg : "");
+	    if (streq (params [param_index].name, "help"))
+	       usage (params, argv [0], synopsis, comment, non_opt_string,
+		      YES, sys_path, usr_file_name);
+	    else if (streq (params [param_index].name, "info"))
+	       usage (params, argv [0], synopsis, comment, non_opt_string,
+		      NO, sys_path, usr_file_name);
+	    else if (streq (params [param_index].name, "version"))
+	    {
+	       fprintf (stderr, "%s " VERSION "\n", argv [0]);
+	       exit (2);
+	    }
+	    else if (streq (params [param_index].name, "verbose"))
+	       fiasco_set_verbosity (
+               * (fiasco_verbosity_e *) parameter_value (params,
+                                                         "verbose"));
+	    else if (streq (params [param_index].name, "config"))
+	       read_config_file = YES;
+	    param_index = -1;		/* clear index flag */
+	 }
+      }
+
+      free (long_options);
+   }
+   
+   /*
+    *  Read config-file if specified by option -f
+    */
+   if (read_config_file)
+   {
+      char *filename;
+
+      if ((filename = (char *) parameter_value (params, "config")) != NULL)
+      {
+	 FILE *parameter_file;		/* input file */
+	 
+	 warning ("Options set in file `%s' will override"
+		  " command line options.", filename);
+	 parameter_file = open_file (filename, NULL, READ_ACCESS);
+	 if (parameter_file != NULL)
+	 {
+	    read_parameter_file (params, parameter_file);
+	    fclose (parameter_file);
+	 }
+	 else
+	    file_error (filename);
+      }
+      else
+	 error ("Invalid config filename.");
+   }
+
+   memcpy (usr_params, params, n1 * sizeof (param_t)); /* fill user struct */
+   free (sys_path);
+   
+   return optind;
+}
+ 
+void *
+parameter_value (const param_t *params, const char *name)
+/*
+ *  Extract value of parameter 'name.' of the given parameters 'params'.
+ *
+ *  Return value:
+ *	value of given parameter
+ */
+{
+   int pind = get_parameter_index (params, name);
+
+   if (pind < 0)
+      error ("Invalid parameter `%s'.", name);
+
+   if (params [pind].type == PSTR || params [pind].type == POSTR)
+      return (void *) params [pind].value.s;
+      
+   return (void *) &(params [pind].value);
+}
+
+void
+ask_and_set (param_t *params, const char *name, const char *msg)
+/*
+ *  Ask user (print given message 'msg') for missing mandatory
+ *  parameter 'name' of the given parameters 'params'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'params ['name'].value' is changed
+ */
+{ 
+   char answer [MAXSTRLEN];
+   int  index = get_parameter_index (params, name);
+
+   if (index < 0)
+      error ("Invalid parameter %s.", name);
+
+   if (msg)
+      fprintf (stderr, "%s\n", msg);
+  
+   switch (params [index].type)
+   {
+      case PFLAG:			/* Unusual, at least. */
+	 warning ("Flags should be initialized and set on demand, "
+		  "not request");
+      case PINT:
+      case PSTR:
+      case POSTR:
+      case PFLOAT:
+	 scanf (MAXSTRLEN_SCANF, answer);
+	 set_parameter (&params [index], answer);
+	 break;
+      default:
+	 error ("Invalid parameter type for %s", name);
+   }
+} 
+
+void
+write_parameters (const param_t *params, FILE *output)
+/*
+ *  Write all parameter settings to 'output'.
+ *
+ *  No return value.
+ */
+{
+   int pind;
+
+   if (!params || !output)
+      error ("Parameters must be not NULL.");
+
+   for (pind = 0; params [pind].name != NULL; pind++)
+   {
+      fprintf (output, "# %s = ", params [pind].name);
+      switch (params [pind].type)
+      {
+	 case PFLAG:
+	    fprintf (output, "%s\n", params [pind].value.b ? "TRUE" : "FALSE");
+	    break;
+	 case PINT:
+	    fprintf (output, "%d\n", params [pind].value.i);
+	    break;
+	 case PFLOAT:
+	    fprintf (output, "%.4f\n", (double) params [pind].value.f);
+	    break;
+	 case PSTR:
+	 case POSTR:
+	    fprintf (output, "%s\n", params [pind].value.s);
+	    break;
+	 default:
+	    error ("Invalid type %d for parameter %s",
+		   params [pind].type, params [pind].name);
+      }
+   }
+   fputc ('\n', output);
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static void
+set_parameter (param_t *parameter, const char *value)
+/*
+ *  Set value of 'parameter' to 'value'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'parameter.value' is changed accordingly
+ */
+{
+   assert (parameter);
+   
+   switch (parameter->type)
+   {
+      case PFLAG:
+	 if (value != NULL && *value != '\0')
+	 {
+	    if (strcaseeq (value, "TRUE"))
+	       parameter->value.b = YES;
+	    else if (strcaseeq (value, "FALSE"))
+	       parameter->value.b = NO;
+	    else if (strcaseeq (value, "YES"))
+	       parameter->value.b = YES;
+	    else if (strcaseeq (value, "NO"))
+	       parameter->value.b = NO;
+	    else
+	    {
+	       long int	data;
+	       char	*endptr;
+	    
+	       data = strtol (value, &endptr, 0);
+	       if (*endptr != '\0' || endptr == value)
+		  warning ("Invalid value `%s' converted to %d",
+			   value, (int) data);
+	       parameter->value.b = data ? YES : NO;
+	    }
+	 }
+	 else
+	    parameter->value.b = !parameter->value.b;
+	 break;
+      case PINT:
+	 {
+	    long int  data;
+	    char     *endptr;
+	    
+	    data = strtol (value, &endptr, 0);
+	    if (*endptr != '\0' || endptr == value)
+	       warning ("Invalid value `%s' converted to %d",
+			value, (int) data);
+	    parameter->value.i = data;
+	 }
+	 break;
+      case PFLOAT:
+	 {
+	    double	data;
+	    char	*endptr;
+	    
+	    data = strtod (value, &endptr);
+	    if (*endptr != '\0' || endptr == value)
+	       warning ("Invalid value `%s' converted to %f",
+			value, (double) data);
+	    parameter->value.f = data;
+	 }
+	 break;
+      case PSTR:
+      case POSTR:
+	 parameter->value.s = value ? strdup (value) : NULL;
+	 break;
+      default:				
+	 error ("Invalid parameter type for %s", parameter->name);
+   }
+}
+
+static int
+get_parameter_index (const param_t *params, const char *search_string)
+/*
+ *  Search for parameter with name 'search_string' in parameter struct.
+ *
+ *  Return value:
+ *	index of parameter or -1 if no matching parameter has been found
+ */
+{
+   int n;
+   int index = -1;
+
+   assert (params && search_string);
+
+   for (n = 0; params [n].name != NULL; n++)
+      if (strcaseeq (params [n].name, search_string))
+      {
+	 index = n;
+	 break;
+      }
+
+   return index;
+}
+
+static void
+read_parameter_file (param_t *params, FILE *file)
+/*
+ *  Read parameter settings from 'file'.
+ *
+ *  No return value.
+ *
+ *  Side effects:
+ *	'params [].value' are changed if specified in 'file'
+ */
+{
+   char buffer [MAXSTRLEN];
+   int  n = 0;
+
+   assert (params && file);
+
+   while (fgets (buffer, MAXSTRLEN, file) != NULL)
+   {
+      char *b;				/* temporary variable */
+      char *name;			/* parameter name */
+      char *value;			/* parameter value */
+      int   pind;			/* current argument number */
+      
+      b = strchr (buffer, '#');
+      if (b != NULL)			/* Strip comments. */
+	 *b = '\0';
+
+      b = strchr (buffer, '=');
+      if (b == NULL)			/* Strip lines that contain no '=' */
+	 continue;
+      *b = '\0';			/* Replace '=' by string terminator */
+
+      /*
+       *  Extract value of parameter
+       */
+      for (value = b + 1; ISSPACE (*value); value++) 
+	 ;				/* Delete leading spaces */
+
+      for (b = value + strlen (value) - 1; b >= value && ISSPACE (*b); b--)
+	 *b = '\0';			/* Delete trailing spaces. */
+
+      /*
+       *  Extract parameter name
+       */
+      for (name = buffer; ISSPACE (*name); name++) 
+	 ;				/* Delete leading spaces */
+
+      for (b = name + strlen (name) - 1; b >= name && ISSPACE (*b); b--)
+	 *b = '\0';			/* Delete trailing spaces. */
+
+      pind = get_parameter_index (params, name);
+      if (pind >= 0)
+	 set_parameter (&params [pind], value);
+      
+      n++;
+   }
+}   
+
+static void 
+usage (const param_t *params, const char *progname, const char *synopsis,
+       const char *comment, const char *non_opt_string,
+       bool_t show_all_options, const char *sys_file_name,
+       const char *usr_file_name)
+/*
+ *  Generates and prints command line description from param_t struct 'params'.
+ *  'progname' is the name of the excecutable, 'synopsis' a short program
+ *  description, and 'comment' some more advice.
+ *  If flag 'show_all_options' is set then print also options that are not
+ *  associated with a short option character.
+ *  'sys_file_name' and 'usr_file_name' are filenames to parameter files.
+ *
+ *  No return value.
+ */
+{
+   int	  i;
+   size_t width = 0;
+   
+   fprintf (stderr, "Usage: %s [OPTION]...%s\n", progname,
+	    non_opt_string ? non_opt_string : " ");
+   if (synopsis != NULL)
+      fprintf (stderr, synopsis);
+   fprintf (stderr, "\n\n");
+   fprintf (stderr, "Mandatory or optional arguments to long options "
+	    "are mandatory or optional\nfor short options too. "
+	    "Default values are surrounded by {}.\n");
+   for (i = 0; params [i].name != NULL; i++)
+      if (params [i].optchar != '\0' || show_all_options)
+      {
+	 if (params [i].type == POSTR)
+	    width = max (width, (strlen (params [i].name)
+				 + strlen (params [i].argument_name) + 2));
+	 else if (params [i].type != PFLAG)
+	    width = max (width, (strlen (params [i].name)
+				 + strlen (params [i].argument_name)));
+	 else
+	    width = max (width, (strlen (params [i].name)) - 1);
+      }
+   
+   for (i = 0; params [i].name != NULL; i++)
+      if (params [i].optchar != '\0' || show_all_options)
+      {
+	 if (params [i].optchar != '\0')
+	    fprintf (stderr, "  -%c, --", params [i].optchar);
+	 else
+	    fprintf (stderr, "      --");
+	 
+	 if (params [i].type == POSTR)
+	    fprintf (stderr, "%s=[%s]%-*s  ", params [i].name,
+		     params [i].argument_name,
+		     max (0, (width - 2 - strlen (params [i].name)
+			   - strlen (params [i].argument_name))), "");
+	 else if (params [i].type != PFLAG)
+	    fprintf (stderr, "%s=%-*s  ", params [i].name,
+		  width - strlen (params [i].name),
+		  params [i].argument_name);
+	 else
+	    fprintf (stderr, "%-*s  ", width + 1, params [i].name);
+
+	 fprintf (stderr, params [i].use, params [i].argument_name);
+	 
+	 switch (params [i].type)
+	 {
+	    case PFLAG:
+	       break;
+	    case PINT:
+	       fprintf (stderr, "{%d}", params [i].value.i);
+	       break;
+	    case PFLOAT:
+	       fprintf (stderr, "{%.2f}", (double) params [i].value.f);
+	       break;
+	    case PSTR:
+	    case POSTR:
+	       if (params [i].value.s)
+		  fprintf (stderr, "{%s}", params [i].value.s);
+	       break;
+	    default:
+	       error ("type %d for %s invalid",
+		      params [i].type, params [i].name);
+	 }
+	 fprintf (stderr, "\n");
+      }
+   fprintf (stderr, "\n");
+   fprintf (stderr, "Parameter initialization order:\n");
+   fprintf (stderr,
+	    "1.) %s\n2.) $HOME/%s\t 3.) command line\t 4.) --config=file",
+	    sys_file_name, usr_file_name);
+   fprintf (stderr, "\n\n");
+   if (comment != NULL)
+      fprintf (stderr, "%s\n", comment);
+
+   exit (1);
+}
+
diff --git a/converter/other/fiasco/params.h b/converter/other/fiasco/params.h
new file mode 100644
index 00000000..810a9ff0
--- /dev/null
+++ b/converter/other/fiasco/params.h
@@ -0,0 +1,61 @@
+/*
+ *  params.h
+ *
+ *  Written by:     Stefan Frank
+ *          Ullrich Hafner
+ *      
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+/*
+ *  $Date: 2000/06/14 20:51:17 $
+ *  $Author: hafner $
+ *  $Revision: 5.1 $
+ *  $State: Exp $
+ */
+
+#ifndef PARAMS_H
+#define PARAMS_H
+
+#include <stdio.h>
+
+typedef union pdata_t           /* Allow for different */
+{                   /* parameter types. */
+    int    b;
+    int    i;
+    float  f;
+    char  *s;
+} pdata_t;
+
+typedef enum {PFLAG = 1, PINT, PFLOAT, PSTR, POSTR} param_e;
+
+typedef struct param_t
+{
+    const char  *name;           /* Parameter name */
+    const char  *argument_name;      /* Argument name */
+    char         optchar;            /* Corresponding command line switch */
+    param_e      type;           /* Parameter type */
+    pdata_t      value;          /* Parameter value */
+    const char  *default_value;      /* Parameters default value */
+    const char  *use;            /* One line usage. Must contain %s,
+                                    which will be replaced by 'name'. */
+} param_t;
+
+int
+parseargs (param_t *usr_params, 
+           int argc, char **argv, 
+           const char *synopsis,
+           const char *comment, 
+           const char *non_opt_string, 
+           const char *path,
+           const char *sys_file_name, 
+           const char *usr_file_name);
+void
+write_parameters (const param_t *params, FILE *output);
+void
+ask_and_set (param_t *params, const char *name, const char *msg);
+void *
+parameter_value (const param_t *params, const char *name);
+
+#endif /* not PARAMS_H */
diff --git a/converter/other/fiasco/pnmtofiasco.c b/converter/other/fiasco/pnmtofiasco.c
new file mode 100644
index 00000000..2218256d
--- /dev/null
+++ b/converter/other/fiasco/pnmtofiasco.c
@@ -0,0 +1,411 @@
+/*
+ *  cwfa.c:		FIASCO coder
+ *
+ *  Written by:		Ullrich Hafner
+ *		
+ *  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+ 
+/*
+ *  $Date: 2000/10/28 17:39:29 $
+ *  $Author: hafner $
+ *  $Revision: 5.4 $
+ *  $State: Exp $
+ */
+
+#include "config.h"
+#include "pnm.h"
+
+#if STDC_HEADERS
+#	include <stdlib.h>
+#	include <string.h>
+#else /* not STDC_HEADERS */
+#	if HAVE_STRING_H
+#		include <string.h>
+#	else /* not HAVE_STRING_H */
+#		include <strings.h>
+#	endif /* not HAVE_STRING_H */
+#endif /* not STDC_HEADERS */
+
+#include "types.h"
+#include "macros.h"
+
+#include "binerror.h"
+#include "misc.h"
+#include "params.h"
+#include "fiasco.h"
+
+/*****************************************************************************
+
+			     local variables
+  
+*****************************************************************************/
+
+static param_t params [] =
+{
+  /*
+   *  Options for standard user
+   */
+  {"image-name", "FILE", 'i', PSTR, {0}, NULL,
+   "Compress raw PPM/PGM image(s) `%s'."},
+  {"output-name", "FILE", 'o', PSTR, {0}, "-",
+   "Write automaton to `%s' (`-' means stdout)."},
+  {"quality", "REAL", 'q', PFLOAT, {0}, "20.0",
+   "Set quality of compression to `%s'."},
+  {"title", "NAME", 't', PSTR, {0}, "",
+   "Set title of FIASCO stream to `%s'."},
+  {"comment", "NAME", 'c', PSTR, {0}, "",
+   "Set comment of FIASCO stream to `%s'."},
+  {"chroma-qfactor", "REAL", '\0', PFLOAT, {0}, "2",
+   "Decrease chroma band quality `%s' times."},
+  {"basis-name", "FILE", '\0', PSTR, {0}, "small.fco",
+   "Preload basis `%s' into FIASCO."},
+  {"optimize", "NUM", 'z', PINT, {0}, "0",
+   "Set optimization level to `%s'."},
+  {"dictionary-size", "NUM", '\0', PINT, {0}, "10000",
+   "Set max# size of dictionary to `%s'."},
+  {"chroma-dictionary", "NUM", '\0', PINT, {0}, "40",
+   "Set max# size of chroma dictionary to `%s'.."},
+  {"min-level", "NUM", '\0', PINT, {0}, "6",
+   "Start prediction on block level `%s'."},
+  {"max-level", "NUM", '\0', PINT, {0}, "10",
+   "Stop prediction on block level `%s'."},
+  {"tiling-exponent", "NUM", '\0', PINT, {0}, "4",
+   "Set exponent of image permutation to `%s'."},
+  {"tiling-method", "NAME", '\0', PSTR, {0}, "desc-variance",
+   "Set type of permutation to `%s'."},
+  {"rpf-range", "REAL", '\0', PFLOAT, {0}, "1.5",
+   "Set quantization range to `%s'."},
+  {"rpf-mantissa", "NUM", '\0', PINT, {0}, "3",
+   "Set quantization mantissa to `%s' bits."},
+  {"dc-rpf-range", "REAL", '\0', PFLOAT, {0}, "1",
+   "Set quant. range (DC part) to `%s'."},
+  {"dc-rpf-mantissa", "NUM", '\0', PINT, {0}, "5",
+   "Set quant. mantissa (DC part) to `%s' bits."},
+  {"pattern", "NAME", '\0', PSTR, {0}, "ippppppppp",
+   "Set frame type sequence to `%s'."},
+  {"fps", "NUM", '\0', PINT, {0}, "25",
+   "Set display rate to `%s' frames per second."},
+  {"half-pixel", NULL, '\0', PFLAG, {0}, "FALSE",
+   "Use half-pixel precision for mc."},
+  {"cross-B-search", NULL, '\0', PFLAG, {0}, "FALSE",
+   "Use cross-B-search for interpolated mc."},
+  {"B-as-past-ref", NULL, '\0', PFLAG, {0}, "FALSE",
+   "Use B-frames as reference images." },
+  {"prediction", NULL, '\0', PFLAG, {0}, "FALSE",
+   "Use additional predictive coding."},
+  {"progress-meter", "NUM", '\0', PINT, {0}, "2",
+   "Set type of progress meter to `%s'."},
+  {"smooth", "NUM", '\0', PINT, {0}, "70",
+   "Smooth image(s) by factor `%s' (0-100)"},
+#if 0
+  /*
+   *  Options currently not activated (maybe in future versions of FIASCO)
+   */
+  {"min-level", "NUM", 'm', PINT, {0}, "4",
+   "Start compression on block level `%s'."},
+  {"max-level", "NUM", 'M', PINT, {0}, "12",
+   "Stop compression on block level `%s'."},
+  {"max-elements", "NUM", 'N', PINT, {0}, "8",
+   "Set max# of elements in an approx. to `%s'." },
+  {"domain-pool", "NAME", '\0', PSTR, {0}, "rle",
+   "Set domain pool of r-lc to `%s'."},
+  {"coeff", "NAME", '\0', PSTR, {0}, "adaptive",
+   "Set coefficients model to `%s'."},
+  /*  DELTA APPROXIATION  */
+  {"d-domain-pool", "NAME", '\0', PSTR, {0}, "rle",
+   "Set domain pool of d-lc to `%s'."},
+  {"d-coeff", "NAME", '\0', PSTR, {0}, "adaptive",
+   "Set d coefficients model to `%s'."},
+  {"d-range", "REAL", '\0', PFLOAT, {0}, "1.5",
+   "Set range of RPF for delta lc to `%s'."},
+  {"d-mantissa", "NUM", '\0', PINT, {0}, "3",
+   "Set #m-bits of RPF for delta lc to `%s'."},
+  {"d-dc-range", "REAL", '\0', PFLOAT, {0}, "1",
+   "Set DC range of RPF of delta lc to `%s'."},
+  {"d-dc-mantissa", "NUM", '\0', PINT, {0}, "5",
+   "Set #m-bits of delta RPF for DC domain to `%s'."},
+  /*  ADVANCED  */
+  {"images-level", "NUM", '\0', PINT, {0}, "5",
+   "Compute state images up to level `%s'."},
+  {"delta-domains", NULL, '\0', PFLAG, {0}, "FALSE",
+   "Use delta domains every time."},
+  {"normal-domains", NULL, '\0', PFLAG, {0}, "FALSE",
+   "Use normal domains every time."},
+  /*  VIDEO COMPRESSION  */
+  {"smooth", "REAL", 's', PFLOAT, {0}, "1.0",
+   "Smooth frames by factor `%s' (0.5 - 1.0)"},
+  {"reference-frame", "FILE", '\0', PSTR, {0}, NULL,
+   "Use PPM/PGM image `%s' as reference frame."},
+#endif  
+  {NULL, NULL, 0, PSTR, {0}, NULL, NULL }
+};
+
+/*****************************************************************************
+
+				prototypes
+  
+*****************************************************************************/
+
+static void 
+checkargs (int argc, char **argv, char const ***image_template,
+	   char **wfa_name, float *quality, fiasco_c_options_t **options);
+
+/*****************************************************************************
+
+				public code
+  
+*****************************************************************************/
+ 
+int 
+main (int argc, char **argv)
+{
+   char const 	      **image_template;	/* template for input image files */
+   char	       	       *wfa_name;	/* filename of output WFA */
+   float	      	quality;	/* approximation quality */
+   fiasco_c_options_t  *options;	/* additional coder options */
+   
+   pnm_init(&argc, argv);
+   
+   init_error_handling (argv [0]);
+
+   checkargs (argc, argv, &image_template, &wfa_name, &quality, &options);
+
+   if (fiasco_coder (image_template, wfa_name, quality, options))
+      return 0;
+   else
+   {
+      fprintf (stderr, fiasco_get_error_message ());
+      fprintf (stderr, "\n");
+      return 1;
+   }
+}
+
+/*****************************************************************************
+
+				private code
+  
+*****************************************************************************/
+
+static void 
+checkargs (int argc, char **argv, char const ***image_template,
+	   char **wfa_name, float *quality, fiasco_c_options_t **options)
+/*
+ *  Check validness of command line parameters and of the parameter files.
+ *
+ *  Return value:
+ *	1 on success
+ *	0 otherwise
+ *  
+ *
+ *  Side effects:
+ *	'image_template', 'wfa_name', 'quality' and 'options' are set.
+ */
+{
+   int	 optind;			/* last processed commandline param */
+   char	*image_name;			/* filename given by option '-i' */
+   int	 i;				/* counter */
+   
+   optind = parseargs (params, argc, argv,
+		       "Compress raw PPM/PGM image FILEs to a FIASCO file.",
+		       "With no image FILE, or if FILE is -, "
+		       "read standard input.\n"
+		       "FILE must be either a filename"
+		       " or an image template of the form:\n"
+		       "`prefix[start-end{+,-}step]suffix'\n"
+		       "e.g., img0[12-01-1].pgm is substituted by"
+		       " img012.pgm ... img001.pgm\n\n"
+		       "Environment:\n"
+		       "FIASCO_DATA   Search and save path for FIASCO files. "
+		       "Default: ./\n"
+		       "FIASCO_IMAGES Search path for image files. "
+		       "Default: ./", " [FILE]...",
+		       FIASCO_SHARE, "system.fiascorc", ".fiascorc");
+
+   /*
+    *  Default options ...
+    */
+   image_name = (char *) parameter_value (params, "image-name"); 
+   *wfa_name  = (char *) parameter_value (params, "output-name");
+   for (;;)
+   {
+      *quality = * (float *) parameter_value (params, "quality");
+      if (*quality > 100)
+	 fprintf (stderr, "Typical range of quality: (0,100].\n"
+		  "Expect some trouble on slow machines.\n");
+      if (*quality > 0)
+	 break;
+      ask_and_set (params, "quality",
+		   "Please enter coding quality 'q' ('q' > 0): ");
+   }
+   
+   if (optind < argc)			/* Additional command line param */
+   {
+      if (image_name)
+	 error ("Multiple image_template arguments."
+		"\nOption -i %s already specified!", image_name);
+
+      *image_template = calloc (argc - optind + 1, sizeof (char *));
+      if (!*image_template)
+	 error ("Out of memory.");
+      for (i = 0; optind < argc; i++, optind++)
+	 (*image_template) [i] = argv [optind];
+      (*image_template) [i] = NULL;
+   }
+   else					/* option -i image_name */
+   {
+      *image_template = calloc (2, sizeof (char *));
+      if (!*image_template)
+	 error ("Out of memory.");
+      (*image_template) [0] = image_name;
+      (*image_template) [1] = NULL;
+   }
+   /*
+    *  Additional options ... (have to be set with the fiasco_set_... methods)
+    */
+   {
+      *options = fiasco_c_options_new ();
+      
+      {
+	 char *pattern = (char *) parameter_value (params, "pattern");
+
+	 if (!fiasco_c_options_set_frame_pattern (*options, pattern))
+	    error (fiasco_get_error_message ());
+      }
+
+      {
+	 char *basis = (char *) parameter_value (params, "basis-name");
+	 
+	 if (!fiasco_c_options_set_basisfile (*options, basis))
+	    error (fiasco_get_error_message ());
+      }
+
+      {
+	 int   n = * (int *) parameter_value (params, "chroma-dictionary");
+	 float q = * (float *) parameter_value (params, "chroma-qfactor");
+      
+	 if (!fiasco_c_options_set_chroma_quality (*options, q, max (0, n)))
+	    error (fiasco_get_error_message ());
+      }
+      
+      {
+	 int n = *((int *) parameter_value (params, "smooth"));
+	 
+	 if (!fiasco_c_options_set_smoothing (*options, max (0, n)))
+	    error (fiasco_get_error_message ());
+      }
+      
+      {
+          int n = * (int *) parameter_value (params, "progress-meter");
+          fiasco_progress_e type = (n < 0) ? 
+              FIASCO_PROGRESS_NONE : (fiasco_progress_e) n;
+      
+          if (!fiasco_c_options_set_progress_meter (*options, type))
+              error (fiasco_get_error_message ());
+      }
+      
+      {
+	 char *t = (char *) parameter_value (params, "title");
+	 
+	 if (strlen (t) > 0 && !fiasco_c_options_set_title (*options, t))
+	    error (fiasco_get_error_message ());
+      }
+      
+      {
+	 char *c = (char *) parameter_value (params, "comment");
+
+	 if (strlen (c) > 0 && !fiasco_c_options_set_comment (*options, c))
+	    error (fiasco_get_error_message ());
+      }
+      
+      {
+	 fiasco_tiling_e method = FIASCO_TILING_VARIANCE_DSC;
+	 int   e  = * (int *) parameter_value (params, "tiling-exponent");
+	 char *m  = (char *) parameter_value (params, "tiling-method");
+
+	 if (strcaseeq (m, "desc-variance"))
+	    method = FIASCO_TILING_VARIANCE_DSC;
+	 else if (strcaseeq (m, "asc-variance"))
+	    method = FIASCO_TILING_VARIANCE_ASC;
+	 else if (strcaseeq (m, "asc-spiral"))
+	    method = FIASCO_TILING_SPIRAL_ASC;
+	 else if (strcaseeq (m, "dsc-spiral"))
+	    method = FIASCO_TILING_SPIRAL_DSC;
+	 else
+	    error (_("Invalid tiling method `%s' specified."), m);
+
+	 if (!fiasco_c_options_set_tiling (*options, method, max (0, e)))
+	    error (fiasco_get_error_message ());
+      }
+      
+      {
+	 int M/*  = * (int *) parameter_value (params, "max-level") */;
+	 int m/*  = * (int *) parameter_value (params, "min-level") */;
+	 int N/*  = * (int *) parameter_value (params, "max-elements") */;
+	 int D = * (int *) parameter_value (params, "dictionary-size");
+	 int o = * (int *) parameter_value (params, "optimize");
+
+	 if (o <= 0)
+	 {
+	    o = 0;
+	    M = 10;
+	    m = 6;
+	    N = 3;
+	 }
+	 else
+	 {
+	    o -= 1;
+	    M = 12;
+	    m = 4;
+	    N = 5;
+	 }
+	 
+	 if (!fiasco_c_options_set_optimizations (*options, m, M, N,
+						  max (0, D), o))
+	    error (fiasco_get_error_message ());
+      }
+      {
+	 int M = * (int *) parameter_value (params, "max-level");
+	 int m = * (int *) parameter_value (params, "min-level");
+	 int p = * (int *) parameter_value (params, "prediction");
+	 
+	 if (!fiasco_c_options_set_prediction (*options,
+					       p, max (0, m), max (0, M)))
+	    error (fiasco_get_error_message ());
+      }
+      {
+	 float r    = * (float *) parameter_value (params, "rpf-range");
+	 float dc_r = * (float *) parameter_value (params, "dc-rpf-range");
+	 int   m    = * (int *)   parameter_value (params, "rpf-mantissa");
+	 int   dc_m = * (int *)   parameter_value (params, "dc-rpf-mantissa");
+	 fiasco_rpf_range_e range, dc_range;
+	 
+	 if (r < 1)
+	    range = FIASCO_RPF_RANGE_0_75;
+	 else if (r < 1.5)
+	    range = FIASCO_RPF_RANGE_1_00;
+	 else if (r < 2.0)
+	    range = FIASCO_RPF_RANGE_1_50;
+	 else
+	    range = FIASCO_RPF_RANGE_2_00;
+	    
+	 if (dc_r < 1)
+	    dc_range = FIASCO_RPF_RANGE_0_75;
+	 else if (dc_r < 1.5)
+	    dc_range = FIASCO_RPF_RANGE_1_00;
+	 else if (dc_r < 2.0)
+	    dc_range = FIASCO_RPF_RANGE_1_50;
+	 else
+	    dc_range = FIASCO_RPF_RANGE_2_00;
+	    
+	 if (!fiasco_c_options_set_quantization (*options,
+						 max (0, m), range,
+						 max (0, dc_m), dc_range))
+	    error (fiasco_get_error_message ());
+      }
+
+      if (fiasco_get_verbosity () == FIASCO_ULTIMATE_VERBOSITY)
+	 write_parameters (params, stderr);
+   }
+}	
diff --git a/converter/other/fiasco/system.fiascorc b/converter/other/fiasco/system.fiascorc
new file mode 100644
index 00000000..86ff2da2
--- /dev/null
+++ b/converter/other/fiasco/system.fiascorc
@@ -0,0 +1,120 @@
+#
+#  system.wfarc:	Resource file WFA coder
+# 
+#  Written by:		Ullrich Hafner
+#		
+#  This file is part of FIASCO («F»ractal «I»mage «A»nd «S»equence «CO»dec)
+#  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+
+#
+#  $Date: 2000/06/25 16:38:01 $
+#  $Author: hafner $
+#  $Revision: 5.2 $
+#  $State: Exp $
+
+#
+# Options for FIASCO coder `cfiasco':
+#
+
+# Initial basis to start with
+# small.fco:  domains 1, x, y
+# medium.fco: domains 1, x, y and 129 additional images
+# large.fco: domains 1, x, y and 226 additional images
+basis-name = small.fco
+
+# Limit the number of elements in the dictionary by `dictionary-size' 
+# The smaller the value is the faster the coder runs and the worse
+# the image quality will be.  
+dictionary-size = 10000
+
+# Optimization level
+#  0: standard approximation method, process blocks of level [6, 10],
+#     use up to 3 dictionary vectors
+#  1: standard approximation method, process blocks of level [4, 12],
+#     use up to 5 dictionary vectors
+#  2: significantly increases the approximation quality,
+#     running time is twice as high as with the standard method
+#  3: hardly increases the approximation quality of method 2, 
+#     running time is twice as high as with method 21
+#     (this method just remains for completeness)
+optimize = 0
+
+# Approximation quality (typical range = [1-100]) 
+quality = 20.0                    
+
+# For compression of chroma bands the dictionary can be reduced to
+# the best #`chroma-dictionary' elements.
+# Furthermore, the quality of the approximation is decreased
+# 'chroma-qfactor' times.
+chroma-dictionary = 40
+chroma-qfactor    = 2
+
+# Verbosity level
+# 0 no debug output
+# 1 some verbosity and statistics of the output size
+# 2 lots of debug information and log-files are written
+verbose = 1
+
+# Set exponent and method of image tiling
+# 0   image is processed in normal bintree order
+# >0  image is subdivided into 2^`tiling-exponent' tiles. 
+# Set type of image tiling
+# asc-variance  Tiles with large variances are processed first
+# desc-variance Tiles with small variances are processed first
+# asc-spiral    Tiles are process in spiral order starting in the middle
+# desc-spiral   Tiles are process in spiral order starting at the border
+tiling-exponent = 4
+tiling-method   = desc-variance
+
+# Quantization parameters define the accuracy of coefficients quantization.
+# DC coefficients (of the constant dictionary vector f(x,y) = 1) are quantized
+# to values of the interval [-`dc-rpf-range', `dc-rpf-range'] using
+# #`dc-rpf-mantissa' bits. All other quantized coefficients are quantized in
+# an analogous way using the parameters `rpf-range' and `rpf-mantissa'.
+rpf-mantissa    = 3
+rpf-range       = 1.5
+dc-rpf-mantissa = 5
+dc-rpf-range    = 1
+
+# Search for prediction (coarse approximation or motion compensation)
+# on all levels between minlevel and maxlevel
+min-level = 6
+max-level = 10
+
+# Set various parameters used for video compensation.
+# 'fps' defines the frame rate which should be
+# used when the video is decoded. This value has no effect during coding,
+# it is just passed to the FIASCO output file.
+# If 'half-pixel' is set then half pixel precise
+# motion compensated prediction is used.
+# If 'cross-B-search' is set then the fast Cross-B-Search algorithm is
+# used to determine the motion vectors of interpolated prediction. Otherwise
+# exhaustive search (in the given search range) is used.
+# If 'B-as-past-ref' is set then B frames are allowed to be used
+# for B frame predicion.
+fps            = 25
+half-pixel     = NO
+cross-B-search = NO
+B-as-past-ref  = NO
+
+# Set `pattern' of input frames.
+# `pattern' has to be a sequence of the following
+# characters (case insensitive):
+# 'i' intra frame
+# 'p' predicted frame
+# 'b' bidirectional predicted frame
+# E.g. pattern = 'IBBPBBPBB'
+#
+# When coding video frames the prediction type of input frame N is determined
+# by reading `pattern' [N] (`pattern' is periodically extended).
+
+pattern = ippppppppp
+
+#
+# Options for FIASCO decoder `dfiasco':
+#
+
+double  = NO
+reduced = NO
+panel   = NO
+enlarge = 0
diff --git a/converter/other/fitstopnm.c b/converter/other/fitstopnm.c
new file mode 100644
index 00000000..796ca489
--- /dev/null
+++ b/converter/other/fitstopnm.c
@@ -0,0 +1,494 @@
+ /* fitstopnm.c - read a FITS file and produce a portable anymap
+ **
+ ** Copyright (C) 1989 by Jef Poskanzer.
+ **
+ ** Permission to use, copy, modify, and distribute this software and its
+ ** documentation for any purpose and without fee is hereby granted, provided
+ ** that the above copyright notice appear in all copies and that both that
+ ** copyright notice and this permission notice appear in supporting
+ ** documentation.  This software is provided "as is" without express or
+ ** implied warranty.
+ **
+ ** Hacked up version by Daniel Briggs  (dbriggs@nrao.edu)  20-Oct-92
+ **
+ ** Include floating point formats, more or less.  Will only work on
+ ** machines that understand IEEE-754.  Added -scanmax -printmax
+ ** -min -max and -noraw.  Ignore axes past 3, instead of error (many packages
+ ** use pseudo axes).  Use a finite scale when max=min.  NB: Min and max
+ ** are the real world FITS values (scaled), so watch out when bzer & bscale
+ ** are not 0 & 1.  Datamin & datamax interpreted correctly in scaled case,
+ ** and initialization changed to less likely values.  If datamin & max are
+ ** not present in the header, the a first pass is made to determine them
+ ** from the array values.
+ **
+ ** Modified by Alberto Accomazzi (alberto@cfa.harvard.edu), Dec 1, 1992.
+ **
+ ** Added understanding of 3-plane FITS files, the program is renamed
+ ** fitstopnm.  Fixed some non-ansi declarations (DBL_MAX and FLT_MAX
+ ** replace MAXDOUBLE and MAXFLOAT), fixed some scaling parameters to
+ ** map the full FITS data resolution to the maximum PNM resolution,
+ ** disabled min max scanning when reading from stdin.
+ */
+
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "pnm.h"
+
+/* Do what you have to, to get a sensible value here.  This may not be so */
+/* portable as the rest of the program.  We want to use MAXFLOAT and */
+/* MAXDOUBLE, so you could define them manually if you have to. */
+/* #include <values.h> */
+#include <float.h>
+
+struct FITS_Header {
+  int simple;       /* basic format or not */
+  int bitpix;       /* number of bits per pixel */
+  int naxis;        /* number of axes */
+  int naxis1;       /* number of points on axis 1 */
+  int naxis2;       /* number of points on axis 2 */
+  int naxis3;       /* number of points on axis 3 */
+  double datamin;   /* min # (Physical value!) */
+  double datamax;   /* max #     "       "     */
+  double bzer;      /* Physical value = Array value*bscale + bzero */
+  double bscale;
+};
+
+static void read_fits_header ARGS(( FILE* fp, struct FITS_Header* hP ));
+static void read_card ARGS(( FILE* fp, char* buf ));
+static void read_val ARGS(( FILE* fp, int bitpix, double* vp ));
+     
+
+
+static void
+scanImageForMinMax(FILE *       const ifP,
+                   unsigned int const images,
+                   int          const cols,
+                   int          const rows,
+                   unsigned int const bitpix,
+                   double       const bscale,
+                   double       const bzer,
+                   unsigned int const imagenum,
+                   bool         const multiplane,
+                   double *     const dataminP,
+                   double *     const datamaxP) {
+
+    double dmax, dmin;
+    unsigned int image;
+    pm_filepos rasterPos;
+    double fmaxval;
+    
+    pm_tell2(ifP, &rasterPos, sizeof(rasterPos));
+
+    pm_message("Scanning file for scaling parameters");
+
+    switch (bitpix) {
+    case   8: fmaxval = 255.0;        break;
+    case  16: fmaxval = 65535.0;      break;
+    case  32: fmaxval = 4294967295.0; break;
+    case -32: fmaxval = FLT_MAX;      break;
+    case -64: fmaxval = DBL_MAX;      break;
+    default:
+        pm_error("unusual bits per pixel (%u), can't read", bitpix);
+    }
+
+    dmax = -fmaxval;
+    dmin = fmaxval;
+    for (image = 1; image <= images; ++image) {
+        unsigned int row;
+        for (row = 0; row < rows; ++row) {
+            unsigned int col;
+            for (col = 0; col < cols; ++col) {
+                double val;
+                read_val(ifP, bitpix, &val);
+                if (image == imagenum || multiplane ) {
+                    dmax = MAX(dmax, val);
+                    dmin = MIN(dmin, val);
+                }
+            }
+        }
+        if (bscale < 0.0) {
+            double const origDmax = dmax;
+            dmax = dmin;
+            dmin = origDmax;
+        }
+    }
+    *dataminP = dmin * bscale + bzer;
+    *datamaxP = dmax * bscale + bzer;
+
+    pm_message("Scan results: min=%f max=%f", *dataminP, *datamaxP);
+
+    pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
+}
+
+
+
+static void
+computeMinMax(FILE *             const ifP,
+              unsigned int       const images,
+              int                const cols,
+              int                const rows,
+              struct FITS_Header const h,
+              unsigned int       const imagenum,
+              bool               const multiplane,
+              bool               const forcemin,
+              bool               const forcemax,
+              double             const frmin,
+              double             const frmax,
+              double *           const dataminP,
+              double *           const datamaxP) {
+
+    double datamin, datamax;
+
+    datamin = -DBL_MAX;  /* initial assumption */
+    datamax = DBL_MAX;   /* initial assumption */
+
+    if (forcemin)
+        datamin = frmin;
+    if (forcemax)
+        datamax = frmax;
+
+    if (datamin == -DBL_MAX)
+        datamin = h.datamin;
+    if (datamax == DBL_MAX)
+        datamax = h.datamax;
+
+    if (datamin == -DBL_MAX || datamax == DBL_MAX) {
+        double scannedDatamin, scannedDatamax;
+        scanImageForMinMax(ifP, images, cols, rows, h.bitpix, h.bscale, h.bzer,
+                           imagenum, multiplane,
+                           &scannedDatamin, &scannedDatamax);
+
+        if (datamin == -DBL_MAX)
+            datamin = scannedDatamin;
+        if (datamax == DBL_MAX)
+            datamax = scannedDatamax;
+    }
+    *dataminP = datamin;
+    *datamaxP = datamax;
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    FILE* ifp;
+    int argn, imagenum, image, row;
+    register int col;
+    xelval maxval;
+    double val, frmin, frmax, scale, t;
+    double datamin, datamax;
+    int rows, cols, images, format;
+    struct FITS_Header h;
+    xel** pnmarray;
+    xelval tx, txv[4];
+    const char* fits_name;
+    const char* const usage = "[-image N] [-scanmax] [-printmax] [-min f] [-max f] [FITSfile]";
+
+    int doscan = 0;
+    int forceplain = 0;
+    int forcemin = 0;
+    int forcemax = 0;
+    int printmax = 0;
+    bool multiplane;
+  
+    pnm_init( &argc, argv );
+  
+    argn = 1;
+    imagenum = 0;
+  
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+    {
+        if ( pm_keymatch( argv[argn], "-image", 2 ) )
+        {
+            ++argn;
+            if ( argn == argc || sscanf( argv[argn], "%d", &imagenum ) != 1 )
+                pm_usage( usage );
+        }
+        else if ( pm_keymatch( argv[argn], "-max", 3 ) )
+        {
+            ++argn;
+            forcemax = 1;
+            if ( argn == argc || sscanf( argv[argn], "%lf", &frmax ) != 1 )
+                pm_usage( usage );
+        }
+        else if ( pm_keymatch( argv[argn], "-min", 3 ) )
+        {
+            ++argn;
+            forcemin = 1;
+            if ( argn == argc || sscanf( argv[argn], "%lf", &frmin ) != 1 )
+                pm_usage( usage );
+        }
+        else if ( pm_keymatch( argv[argn], "-scanmax", 2 ) )
+            doscan = 1;
+        else if ( pm_keymatch( argv[argn], "-noraw", 2 ) )
+            /* This is for backward compatibility only.  Use the common option
+               -plain now.  (pnm_init() processes -plain).
+            */
+            forceplain = 1;
+        else if ( pm_keymatch( argv[argn], "-printmax", 2 ) )
+            printmax = 1;
+        else
+            pm_usage( usage );
+        ++argn;
+    }
+  
+    if ( argn < argc )
+    {
+        fits_name = argv[argn];
+        ++argn;
+    }
+    else
+        fits_name = "-";
+
+    if ( argn != argc )
+        pm_usage( usage );
+
+    ifp = pm_openr_seekable(fits_name);
+  
+    read_fits_header( ifp, &h );
+  
+    if ( ! h.simple )
+        pm_error( "FITS file is not in simple format, can't read" );
+    if ( h.naxis != 2 && h.naxis != 3 )
+        pm_message( "Warning: FITS file has %d axes", h.naxis );
+    cols = h.naxis1;
+    rows = h.naxis2;
+    if ( h.naxis == 2 )
+        images = imagenum = 1;
+    else
+        images = h.naxis3;
+    if ( imagenum > images )
+        pm_error( "only %d plane%s in this file", 
+                  images, images > 1 ? "s" : "" );
+    if ( images != 3 && images > 1 && imagenum == 0 )
+        pm_error( "need to specify a plane using the -imagenum flag" );
+
+    multiplane = ( images == 3 && imagenum == 0 );
+
+    computeMinMax(ifp, images, cols, rows, h, imagenum, multiplane,
+                  forcemin, forcemax, frmin, frmax,
+                  &datamin, &datamax);
+
+    if (h.bitpix < 0) {
+        /* samples are floating point, which means the resolution could be
+           anything.  So we just pick a convenient maxval of 255.  We should
+           have a program option to choose the maxval.  Before Netpbm 10.20
+           (January 2004), we did maxval = max - min for floating point as
+           well as integer samples.
+        */
+        maxval = 255;
+    } else
+        maxval = MAX(1, MIN(PNM_OVERALLMAXVAL, datamax - datamin));
+
+    if ( datamax - datamin == 0 )
+        scale = 1.0;
+    else
+        scale = maxval / ( datamax - datamin );
+
+    /* If printmax option is true, just print and exit. */
+    /* For use in shellscripts.  Ex:                    */
+    /* eval `fitstopnm -printmax $filename | \          */
+    /*   awk '{min = $1; max = $2}\                     */
+    /*         END {print "min=" min; " max=" max}'`    */
+    if (printmax) {
+        printf( "%f %f\n", datamin, datamax);
+        pm_close( ifp );
+        pm_close( stdout );
+        exit( 0 );
+    }
+
+    if (multiplane)
+        format = PPM_FORMAT;
+    else
+        format = PGM_FORMAT;
+
+    pnmarray = pnm_allocarray( cols, rows );
+
+    switch( PNM_FORMAT_TYPE( format ))
+    {
+    case PGM_TYPE:
+        pm_message( "writing PGM file" );
+        for ( image = 1; image <= imagenum; ++image )
+        {
+            if ( image != imagenum )
+                pm_message( "skipping image plane %d of %d", image, images );
+            else if ( images > 1 )
+                pm_message( "reading image plane %d of %d", image, images );
+            for ( row = 0; row < rows; ++row)
+                for ( col = 0; col < cols; ++col )
+                {
+                    read_val (ifp, h.bitpix, &val);
+                    t = scale * ( val * h.bscale + h.bzer - datamin );
+                    tx = MAX( 0, MIN( t, maxval ) );
+                    if ( image == imagenum )
+                        PNM_ASSIGN1( pnmarray[row][col], tx );
+                }
+        }
+        break;
+    case PPM_TYPE:
+        pm_message( "writing PPM file" );
+        for ( image = 1; image <= images; image++ )
+        {
+            pm_message( "reading image plane %d of %d", image, images );
+            for ( row = 0; row < rows; row++ )
+                for ( col = 0; col < cols; col++ )
+                {
+                    read_val (ifp, h.bitpix, &val);
+                    txv[1] = PPM_GETR( pnmarray[row][col] );
+                    txv[2] = PPM_GETG( pnmarray[row][col] );
+                    txv[3] = PPM_GETB( pnmarray[row][col] );
+                    t = scale * ( val * h.bscale + h.bzer - datamin );
+                    txv[image] = MAX( 0, MIN( t, maxval ));
+                    PPM_ASSIGN( pnmarray[row][col], txv[1], txv[2], txv[3] );
+                }
+        }
+        break;
+    default:
+        pm_error( "can't happen" );
+        break;
+    }
+
+    pnm_writepnm( stdout, pnmarray, cols, rows, maxval, format, forceplain );
+    pnm_freearray( pnmarray, rows );
+
+    pm_close( ifp );
+    pm_close( stdout );
+  
+    exit( 0 );
+}
+
+/*
+ ** This code will deal properly with integers, no matter what the byte order
+ ** or integer size of the host machine.  Sign extension is handled manually
+ ** to prevent problems with signed/unsigned characters.  Floating point
+ ** values will only be read properly when the host architecture is IEEE-754
+ ** conformant.  If you need to tweak this code for other machines, you might
+ ** want to snag a copy of the FITS documentation from nssdca.gsfc.nasa.gov
+ */
+
+static void
+read_val (fp, bitpix, vp)
+    FILE *fp;
+    int bitpix;
+    double *vp;
+
+{
+    int i, ich, ival;
+    long int lval;
+    unsigned char c[8];
+  
+    switch ( bitpix )
+    {
+        /* 8 bit FITS integers are unsigned */
+    case 8:
+        ich = getc( fp );
+        if ( ich == EOF )
+            pm_error( "EOF / read error" );
+        *vp = ich;
+        break;
+      
+    case 16:
+        ich = getc( fp );
+        if ( ich == EOF )
+            pm_error( "EOF / read error" );
+        c[0] = ich;
+        ich = getc( fp );
+        if ( ich == EOF )
+            pm_error( "EOF / read error" );
+        c[1] = ich;
+        if (c[0] & 0x80)
+            ival = ~0xFFFF | c[0]<<8 | c[1];
+        else
+            ival = c[0]<<8 | c[1];
+        *vp = ival;
+        break;
+      
+    case 32:
+        for (i=0; i<4; i++) {
+            ich = getc( fp );
+            if ( ich == EOF )
+                pm_error( "EOF / read error" );
+            c[i] = ich;
+        }
+        if (c[0] & 0x80)
+            lval = ~0xFFFFFFFF |
+                c[0]<<24 | c[1]<<16 | c[2]<<8 | c[3];
+        else
+            lval = c[0]<<24 | c[1]<<16 | c[2]<<8 | c[3];
+        *vp = lval;
+      
+    case -32:
+        for (i=0; i<4; i++) {
+            ich = getc( fp );
+            if ( ich == EOF )
+                pm_error( "EOF / read error" );
+            c[i] = ich;
+        }
+        *vp = *( (float *) c);
+        break;
+      
+    case -64:
+        for (i=0; i<8; i++) {
+            ich = getc( fp );
+            if ( ich == EOF )
+                pm_error( "EOF / read error" );
+            c[i] = ich;
+        }
+        *vp = *( (double *) c);
+        break;
+      
+    default:
+        pm_error( "Strange bitpix in read_value" );
+    }
+}
+
+static void
+read_fits_header( fp, hP )
+    FILE* fp;
+    struct FITS_Header* hP;
+{
+    char buf[80];
+    int seen_end;
+    int i;
+    char c;
+  
+    seen_end = 0;
+    hP->simple = 0;
+    hP->bzer = 0.0;
+    hP->bscale = 1.0;
+    hP->datamin = - DBL_MAX;
+    hP->datamax = DBL_MAX;
+  
+    while ( ! seen_end )
+        for ( i = 0; i < 36; ++i )
+        {
+            read_card( fp, buf );
+    
+            if ( sscanf( buf, "SIMPLE = %c", &c ) == 1 )
+            {
+                if ( c == 'T' || c == 't' )
+                    hP->simple = 1;
+            }
+            else if ( sscanf( buf, "BITPIX = %d", &(hP->bitpix) ) == 1 );
+            else if ( sscanf( buf, "NAXIS = %d", &(hP->naxis) ) == 1 );
+            else if ( sscanf( buf, "NAXIS1 = %d", &(hP->naxis1) ) == 1 );
+            else if ( sscanf( buf, "NAXIS2 = %d", &(hP->naxis2) ) == 1 );
+            else if ( sscanf( buf, "NAXIS3 = %d", &(hP->naxis3) ) == 1 );
+            else if ( sscanf( buf, "DATAMIN = %lf", &(hP->datamin) ) == 1 );
+            else if ( sscanf( buf, "DATAMAX = %lf", &(hP->datamax) ) == 1 );
+            else if ( sscanf( buf, "BZERO = %lf", &(hP->bzer) ) == 1 );
+            else if ( sscanf( buf, "BSCALE = %lf", &(hP->bscale) ) == 1 );
+            else if ( strncmp( buf, "END ", 4 ) == 0 ) seen_end = 1;
+        }
+}
+
+static void
+read_card( fp, buf )
+    FILE* fp;
+    char* buf;
+{
+    if ( fread( buf, 1, 80, fp ) == 0 )
+        pm_error( "error reading header" );
+}
diff --git a/converter/other/gemtopnm.c b/converter/other/gemtopnm.c
new file mode 100644
index 00000000..aac74793
--- /dev/null
+++ b/converter/other/gemtopnm.c
@@ -0,0 +1,305 @@
+/*
+ * Convert a GEM .img file to PBM
+ *
+ * Author: Diomidis D. Spinellis
+ * (C) Copyright 1988 Diomidis D. Spinellis.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and that
+ * both that copyright notice and this permission notice appear in
+ * supporting documentation.
+ *
+ * This file is provided AS IS with no warranties of any kind.  The author
+ * shall have no liability with respect to the infringement of copyrights,
+ * trade secrets or any patents by this file or any part thereof.  In no
+ * event will the author be liable for any lost revenue or profits or
+ * other special, indirect and consequential damages.
+ *
+ * Comments and additions should be sent to the author:
+ *
+ *                     Diomidis D. Spinellis
+ *                     1 Myrsinis Str.
+ *                     GR-145 62 Kifissia
+ *                     GREECE
+ *
+ * 92/07/11 Johann Haider
+ * Changed to read from stdin if file is omitted
+ * Changed to handle line length not a multipe of 8
+ *
+ * 94/01/31 Andreas Schwab (schwab@ls5.informatik.uni-dortmund.de)
+ * Changed to remove architecture dependency and conform to
+ * PBM coding standard.
+ * Added more tests for garbage.
+ *
+ * 2000/04/30 John Elliott <jce@seasip.demon.co.uk> Added ability to
+ * read 4-plane color IMG files.  Therefore changed from PBM to PPM.
+ * Bryan changed it further to use the PNM facilities so it outputs
+ * both PBM and PPM in the Netpbm tradition.  Name changed from
+ * gemtopbm to gemtopnm.  
+ */
+
+#include <assert.h>
+#include "pnm.h"
+
+#define MAXVAL 3
+#define LIGHT  2
+#define DARK   1
+#define BLACK  0
+
+char pattern[8];
+
+static void getinit ARGS ((FILE *file, int *colsP, int *rowsP, int *padrightP,
+			   int *patlenP, int *planesP));
+
+int
+main(argc, argv)
+	int             argc;
+	char           *argv[];
+{
+	int     debug = 0;
+	FILE    *f;
+    int     row;
+	int     rows, cols, padright, patlen, planes;
+      /* attributes of input image */
+    int type;  /* The format type (PBM/PPM) of the output image */
+	bit	*bitrow[4];
+      /* One row of input, one or four planes.  (If one, only [0] is defined)*/
+    xel * xelrow;  /* One row of output */
+	const char * const usage = "[-debug] [gem IMG file]";
+	int argn;
+
+/* Process multiple planes by maintaining a separate row of bits for each
+ * plane. In a single-plane image, all we have to do is write out the 
+ * first plane; in a multiple-plane image, we combine them just before writing
+ * out the row.
+ */
+	pnm_init( &argc, argv );
+    
+    argn = 1;
+
+	while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0')
+	  {
+	    if (pm_keymatch(argv[1], "-debug", 2))
+	      debug = 1;
+	    else
+	      pm_usage (usage);
+	    ++argn;
+	  }
+
+	if (argc == argn)
+		f = stdin;
+	else {
+		f = pm_openr (argv[argn]);
+		++argn;
+	}
+
+	if (argn != argc)
+	  pm_usage (usage);
+
+	getinit (f, &cols, &rows, &padright, &patlen, &planes);
+
+    if (planes == 1) 
+        type = PBM_TYPE;
+    else 
+        type = PPM_TYPE;
+
+	pnm_writepnminit( stdout, cols, rows, MAXVAL, type, 0 );
+
+    { 
+        /* allocate input row data structure */
+        int plane;
+        for (plane = 0; plane < planes; plane++) 
+            bitrow[plane] = malloc (cols + padright);
+    }
+    xelrow = pnm_allocrow(cols+padright);   /* Output row */
+
+	for (row = 0; row < rows; ) {
+      int linerep;
+      int plane;
+
+	  linerep = 1;
+	  for (plane = 0; plane < planes; plane++) {
+        int col;
+		col = 0;
+		while (col < cols) {
+            int c;
+			switch (c = getc(f)) {
+			case 0x80:	/* Bit String */
+            {
+                int j;
+				c = getc(f);	/* Byte count */
+				if (debug)
+                  pm_message("bit string of %d bytes", c);
+				
+				if (col + c * 8 > cols + padright)
+				  pm_error ("bad byte count");
+				for (j = 0; j < c; ++j) {
+                    int cc, k;
+					cc = getc(f);
+					for (k = 0x80; k; k >>= 1) {
+						bitrow[plane][col] = (k & cc) ? 0 : 1;
+						++col;
+					}
+				}
+            }
+            break;
+			case 0:		/* Pattern run */
+            {
+                int j, l;
+				c = getc(f);	/* Repeat count */
+				if (debug)
+					pm_message("pattern run of %d repetitions",	c);
+                /* line repeat */
+                if (c == 0) {
+                    c = getc(f);
+                    if (c != 0x00ff)
+                        pm_error( "badly formed line repeat" );
+                    linerep = getc(f);
+                    break;
+                }
+				fread (pattern, 1, patlen, f);
+				if (col + c * patlen * 8 > cols + padright)
+				  pm_error ("bad pattern repeat count");
+				for (j = 0; j < c; ++j)
+					for (l = 0; l < patlen; ++l) {
+                        int k;
+						for (k = 0x80; k; k >>= 1) {
+							bitrow[plane][col] = (k & pattern[l]) ? 0 : 1;
+							++col;
+						}
+                    }
+            }
+            break;
+
+			default:	/* Solid run */
+            {
+                int l, j;
+				if (debug)
+					pm_message("solid run of %d bytes %s", c & 0x7f,
+                               c & 0x80 ? "on" : "off" );
+                /* each byte had eight bits DSB */
+                l = (c & 0x80) ? 0: 1;
+                c = (c & 0x7f) * 8;
+                if (col + c > cols + padright)
+                    pm_error ("bad solid run repeat count");
+                for (j = 0; j < c; ++j) {
+                    bitrow[plane][col] = l;
+					++col;
+                }
+            }
+				break;
+
+			case EOF:	/* End of file */
+				pm_error( "end of file reached" );
+
+			}
+		}
+                if ( debug )
+                        pm_message( "EOL plane %d row %d", plane, row );
+                if (col != cols + padright)
+                        pm_error( "EOL beyond edge" );
+	  }
+
+	  if (planes == 4) {
+          /* Construct a pixel from the 4 planes of bits for this row */
+          int col;
+          for (col = 0; col < cols; col++) {
+            int r, g, b, i;
+
+            const int r_bit = !bitrow[0][col];
+            const int g_bit = !bitrow[1][col];
+            const int b_bit = !bitrow[2][col];
+            i = bitrow[3][col];
+
+			/* Deal with weird GEM palette - white/black/gray are
+               encoded oddly 
+            */
+			if (r_bit == g_bit && g_bit == b_bit) {
+                /* It's black, white, or gray */
+				if (r_bit && i) r = LIGHT;
+				else if (r_bit) r = BLACK;
+				else if (i) r = MAXVAL;
+				else r = DARK;
+				g = b = r;	
+			} else {
+                /* It's one of the twelve colored colors */
+                if (!i) {
+                    /* Low intensity */
+                    r = r_bit * LIGHT; 
+                    g = g_bit * LIGHT;
+                    b = b_bit * LIGHT;
+                } else {
+                    /* Normal intensity */
+                    r = r_bit * MAXVAL;
+                    g = g_bit * MAXVAL;
+                    b = b_bit * MAXVAL;
+                }
+            }
+            PPM_ASSIGN(xelrow[col], r, g, b);
+		}
+	  } else {
+          int col;
+          for (col = 0; col < cols; col++) 
+              PNM_ASSIGN1(xelrow[col], bitrow[0][col]);
+      }
+	  while (linerep--) {
+		pnm_writepnmrow( stdout, xelrow, cols, MAXVAL, type, 0 );
+		++row;
+	  }
+	}
+    pnm_freerow(xelrow);
+	pm_close( f );
+	pm_close( stdout );
+	exit(0);
+}
+
+
+static void
+getinit (file, colsP, rowsP, padrightP, patlenP, planesP)
+     FILE *file;
+     int *colsP;
+     int *rowsP;
+     int *padrightP;
+     int *patlenP;
+     int *planesP;
+{
+  short s;
+  short headlen;
+
+  if (pm_readbigshort (file, &s) == -1) /* Image file version */
+    pm_error ("EOF / read error");
+  if (s != 1)
+    pm_error ("unknown version number (%d)", (int) s);
+  if (pm_readbigshort (file, &headlen) == -1) /* Header length in words */
+    pm_error ("EOF / read error");
+  if (headlen < 8)
+    pm_error ("short header (%d)", (int) headlen);
+  if (pm_readbigshort (file, &s) == -1) /* Number of planes */
+    pm_error ("EOF / read error");
+  if (s != 4 && s != 1)
+    pm_error ("This program can interpret IMGs with only 1 or 4 planes");
+  *planesP = s;
+  if (pm_readbigshort (file, &s) == -1) /* Pattern definition length (bytes) */
+    pm_error ("EOF / read error");
+  if (s < 1 || s > 8)
+    pm_error ("illegal pattern length (%d)", (int) s);
+  *patlenP = (int) s;
+  if (pm_readbigshort (file, &s) == -1 /* Pixel height (microns) */
+      || pm_readbigshort (file, &s) == -1 /* Pixel height (microns) */
+      || pm_readbigshort (file, &s) == -1) /* Scan line width */
+    pm_error ("EOF / read error");
+  *colsP = (int) s;
+  if (pm_readbigshort (file, &s) == -1) /* Number of scan line items */
+    pm_error ("EOF / read error");
+  *rowsP = (int) s;
+  *padrightP = 7 - ((*colsP + 7) & 7);
+
+  headlen -= 8;
+  while (headlen-- > 0)
+    {
+      (void) getc (file);
+      (void) getc (file);
+    }
+}
+
diff --git a/converter/other/giftopnm.c b/converter/other/giftopnm.c
new file mode 100644
index 00000000..d3d02fde
--- /dev/null
+++ b/converter/other/giftopnm.c
@@ -0,0 +1,1460 @@
+/* +-------------------------------------------------------------------+ */
+/* | Copyright 1990, 1991, 1993, David Koblas.  (koblas@netcom.com)    | */
+/* |   Permission to use, copy, modify, and distribute this software   | */
+/* |   and its documentation for any purpose and without fee is hereby | */
+/* |   granted, provided that the above copyright notice appear in all | */
+/* |   copies and that both that copyright notice and this permission  | */
+/* |   notice appear in supporting documentation.  This software is    | */
+/* |   provided "as is" without express or implied warranty.           | */
+/* +-------------------------------------------------------------------+ */
+
+/* There is a copy of the GIF89 specification, as defined by its
+   inventor, Compuserve, in 1989, at http://members.aol.com/royalef/gif89a.txt
+
+   This covers the high level format, but does not cover how the "data"
+   contents of a GIF image represent the raster of color table indices.
+   An appendix describes extensions to Lempel-Ziv that GIF makes (variable
+   length compression codes and the clear and end codes), but does not
+   describe the Lempel-Ziv base.
+*/
+
+#define _BSD_SOURCE   /* Make sure strcasecmp() is in string.h */
+
+#include <string.h>
+#include <assert.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+#define GIFMAXVAL 255
+#define MAXCOLORMAPSIZE 256
+
+#define CM_RED 0
+#define CM_GRN 1
+#define CM_BLU 2
+
+#define MAX_LZW_BITS  12
+
+#define INTERLACE      0x40
+#define LOCALCOLORMAP  0x80
+#define BitSet(byte, bit)      (((byte) & (bit)) == (bit))
+
+static __inline__ bool
+ReadOK(FILE *          const fileP,
+       unsigned char * const buffer,
+       size_t          const len) {
+
+    size_t bytesRead;
+
+    bytesRead = fread(buffer, len, 1, fileP);
+
+    return (bytesRead != 0);
+}
+
+
+#define LM_to_uint(a,b)                        (((b)<<8)|(a))
+
+static int const maxnum_lzwCode = (1<<MAX_LZW_BITS);
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * input_filespec;  /* Filespecs of input files */
+    unsigned int verbose;    /* -verbose option */
+    unsigned int comments;   /* -comments option */
+    bool all_images;  /* He wants all the images */
+    unsigned int image_no;
+        /* image number he wants from input, starting at 0.  Undefined
+           if all_images is TRUE
+        */
+    const char * alpha_filename;
+    unsigned int quitearly;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    
+    optStruct3 opt;
+
+    unsigned int alphaSpec, imageSpec;
+    const char * image;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+    
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "verbose",     OPT_FLAG, NULL, 
+            &cmdlineP->verbose,         0);
+    OPTENT3(0, "comments",    OPT_FLAG, NULL,
+            &cmdlineP->comments,        0);
+    OPTENT3(0, "quitearly",    OPT_FLAG, NULL,
+            &cmdlineP->quitearly,       0);
+    OPTENT3(0, "image",       OPT_STRING, &image,
+            &imageSpec,                 0);
+    OPTENT3(0, "alphaout",    OPT_STRING, &cmdlineP->alpha_filename, 
+            &alphaSpec,                 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!imageSpec) {
+        cmdlineP->image_no = 0;
+        cmdlineP->all_images = FALSE;
+    } else {
+        if (strcasecmp(image, "all") == 0) 
+            cmdlineP->all_images = TRUE;
+        else {
+            char * tailptr;
+
+            long const imageNo = strtol(image, &tailptr, 10);
+
+            if (*tailptr != '\0')
+                pm_error("Invalid value for '-image' option.  Must be either "
+                         "a number or 'all'.  You specified '%s'", image);
+            else if (imageNo < 0)
+                pm_error("Invalid value for '-image' option.  Must be "
+                         "positive.  You specified %ld", imageNo);
+            else if (imageNo == 0)
+                pm_error("Invalid value for 'image' option.  You specified "
+                         "zero.  The first image is 1.");
+
+            cmdlineP->all_images = FALSE;
+            cmdlineP->image_no = (unsigned int) imageNo - 1;
+        }
+    }
+    
+    if (argc-1 == 0) 
+        cmdlineP->input_filespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->input_filespec = argv[1];
+
+    if (!alphaSpec) 
+        cmdlineP->alpha_filename = NULL;
+}
+
+
+typedef unsigned char gifColorMap[3][MAXCOLORMAPSIZE];
+
+struct gifScreen {
+    unsigned int    Width;
+    unsigned int    Height;
+    gifColorMap     ColorMap;
+    unsigned int    ColorMapSize;
+        /* Number of colors in the color map. */
+    unsigned int    ColorResolution;
+    unsigned int    Background;
+    unsigned int    AspectRatio;
+        /* Aspect ratio of each pixel, times 64, minus 15.  (i.e. 1 => 1:4).
+           But Zero means 1:1.
+        */
+    int      hasGray;  
+        /* Boolean: global colormap has at least one gray color
+           (not counting black and white) 
+        */
+    int      hasColor;
+        /* Boolean: global colormap has at least one non-gray,
+           non-black, non-white color 
+        */
+};
+
+struct gif89 {
+       int     transparent;
+       int     delayTime;
+       int     inputFlag;
+       int     disposal;
+};
+
+static void
+initGif89(struct gif89 * const gif89P) {
+    gif89P->transparent = -1;
+    gif89P->delayTime = -1;
+    gif89P->inputFlag = -1;
+    gif89P->disposal = -1;
+}       
+
+
+static int verbose;
+int    showComment;
+
+
+
+static void
+readColorMap(FILE *ifP, const int colormapsize, 
+             unsigned char colormap[3][MAXCOLORMAPSIZE],
+             int *hasGrayP, int * const hasColorP) {
+
+    int             i;
+    unsigned char   rgb[3];
+
+    assert(colormapsize <= MAXCOLORMAPSIZE);
+
+    *hasGrayP = FALSE;  /* initial assumption */
+    *hasColorP = FALSE;  /* initial assumption */
+
+    for (i = 0; i < colormapsize; ++i) {
+        if (! ReadOK(ifP, rgb, sizeof(rgb)))
+            pm_error("Unable to read Color %d from colormap", i);
+
+        colormap[CM_RED][i] = rgb[0] ;
+        colormap[CM_GRN][i] = rgb[1] ;
+        colormap[CM_BLU][i] = rgb[2] ;
+
+        if (rgb[0] == rgb[1] && rgb[1] == rgb[2]) {
+            if (rgb[0] != 0 && rgb[0] != GIFMAXVAL)
+                *hasGrayP = TRUE;
+        } else
+            *hasColorP = TRUE;
+    }
+}
+
+
+
+static bool zeroDataBlock = FALSE;
+    /* the most recently read DataBlock was an EOD marker, i.e. had
+       zero length */
+
+static void
+getDataBlock(FILE *          const ifP, 
+             unsigned char * const buf, 
+             bool *          const eofP,
+             unsigned int *  const lengthP) {
+/*----------------------------------------------------------------------------
+   Read a DataBlock from file 'ifP', return it at 'buf'.
+
+   The first byte of the datablock is the length, in pure binary, of the
+   rest of the datablock.  We return the data portion (not the length byte)
+   of the datablock at 'buf', and its length as *lengthP.
+
+   Except that if we hit EOF or have an I/O error reading the first
+   byte (size field) of the DataBlock, we return *eofP == TRUE and
+   *lengthP == 0.
+
+   We return *eofP == FALSE if we don't hit EOF or have an I/O error.
+
+   If we hit EOF or have an I/O error reading the data portion of the
+   DataBlock, we exit the program with pm_error().
+-----------------------------------------------------------------------------*/
+    unsigned char count;
+    bool successfulRead;
+    
+    long const pos=ftell(ifP);
+    successfulRead = ReadOK(ifP, &count, 1);
+    if (!successfulRead) {
+        pm_message("EOF or error in reading DataBlock size from file" );
+        *eofP = TRUE;
+        *lengthP = 0;
+    } else {
+        if (verbose)
+            pm_message("%d byte block at Position %ld", count, pos);
+        *eofP = FALSE;
+        *lengthP = count;
+
+        if (count == 0) 
+            zeroDataBlock = TRUE;
+        else {
+            bool successfulRead;
+
+            zeroDataBlock = FALSE;
+            successfulRead = ReadOK(ifP, buf, count); 
+            
+            if (!successfulRead) 
+                pm_error("EOF or error reading data portion of %d byte "
+                         "DataBlock from file", count);
+        }
+    }
+}
+
+
+
+static void
+readThroughEod(FILE * const ifP) {
+/*----------------------------------------------------------------------------
+  Read the file 'ifP' through the next EOD marker.  An EOD marker is a
+  a zero length data block.
+
+  If there is no EOD marker between the present file position and EOF,
+  we read to EOF and issue warning message about a missing EOD marker.
+-----------------------------------------------------------------------------*/
+    unsigned char buf[260];
+    bool eod;
+
+    eod = FALSE;  /* initial value */
+    while (!eod) {
+        bool eof;
+        unsigned int count;
+
+        getDataBlock(ifP, buf, &eof, &count);
+        if (eof)
+            pm_message("EOF encountered before EOD marker.  The GIF "
+                       "file is malformed, but we are proceeding "
+                       "anyway as if an EOD marker were at the end "
+                       "of the file.");
+        if (eof || count == 0)
+            eod = TRUE;
+    }
+}
+
+
+
+static void
+doCommentExtension(FILE * const ifP) {
+/*----------------------------------------------------------------------------
+   Read the rest of a comment extension from the input file 'ifP' and handle
+   it.
+   
+   We ought to deal with the possibility that the comment is not text.  I.e.
+   it could have nonprintable characters or embedded nulls.  I don't know if
+   the GIF spec requires regular text or not.
+-----------------------------------------------------------------------------*/
+    char buf[255+1];
+    unsigned int blocklen;  
+    bool done;
+
+    done = FALSE;
+    while (!done) {
+        bool eof;
+        getDataBlock(ifP, (unsigned char*) buf, &eof, &blocklen); 
+        if (blocklen == 0 || eof)
+            done = TRUE;
+        else {
+            buf[blocklen] = '\0';
+            if (showComment) {
+                pm_message("gif comment: %s", buf);
+            }
+        }
+    }
+}
+
+
+
+static void 
+doGraphicControlExtension(FILE *         const ifP,
+                          struct gif89 * const gif89P) {
+
+    bool eof;
+    unsigned int length;
+    static unsigned char buf[256];
+
+    getDataBlock(ifP, buf, &eof, &length);
+    if (eof)
+        pm_error("EOF/error encountered reading "
+                 "1st DataBlock of Graphic Control Extension.");
+    else if (length < 4) 
+        pm_error("graphic control extension 1st DataBlock too short.  "
+                 "It must be at least 4 bytes; it is %d bytes.",
+                 length);
+    else {
+        gif89P->disposal = (buf[0] >> 2) & 0x7;
+        gif89P->inputFlag = (buf[0] >> 1) & 0x1;
+        gif89P->delayTime = LM_to_uint(buf[1],buf[2]);
+        if ((buf[0] & 0x1) != 0)
+            gif89P->transparent = buf[3];
+        readThroughEod(ifP);
+    }
+}
+
+
+
+static void
+doExtension(FILE * const ifP, int const label, struct gif89 * const gif89P) {
+    const char * str;
+    
+    switch (label) {
+    case 0x01:              /* Plain Text Extension */
+        str = "Plain Text";
+#ifdef notdef
+        GetDataBlock(ifP, (unsigned char*) buf, &eof, &length);
+        
+        lpos   = LM_to_uint(buf[0], buf[1]);
+        tpos   = LM_to_uint(buf[2], buf[3]);
+        width  = LM_to_uint(buf[4], buf[5]);
+        height = LM_to_uint(buf[6], buf[7]);
+        cellw  = buf[8];
+        cellh  = buf[9];
+        foreground = buf[10];
+        background = buf[11];
+        
+        while (GetDataBlock(ifP, (unsigned char*) buf) != 0) {
+            PPM_ASSIGN(xels[ypos][xpos],
+                       cmap[CM_RED][v],
+                       cmap[CM_GRN][v],
+                       cmap[CM_BLU][v]);
+            ++index;
+        }
+#else
+        readThroughEod(ifP);
+#endif
+        break;
+    case 0xff:              /* Application Extension */
+        str = "Application";
+        readThroughEod(ifP);
+        break;
+    case 0xfe:              /* Comment Extension */
+        str = "Comment";
+        doCommentExtension(ifP);
+        break;
+    case 0xf9:              /* Graphic Control Extension */
+        str = "Graphic Control";
+        doGraphicControlExtension(ifP, gif89P);
+        break;
+    default: {
+        static char buf[256];
+        str = buf;
+        sprintf(buf, "UNKNOWN (0x%02x)", label);
+        pm_message("Ignoring unrecognized extension (type 0x%02x)", label);
+        readThroughEod(ifP);
+        }
+        break;
+    }
+    if (verbose)
+        pm_message(" got a '%s' extension", str );
+}
+
+
+
+struct getCodeState {
+    unsigned char buf[280];
+        /* This is the buffer through which we read the data from the 
+           stream.  We must buffer it because we have to read whole data
+           blocks at a time, but our client wants one code at a time.
+           The buffer typically contains the contents of one data block
+           plus two bytes from the previous data block.
+        */
+    int bufCount;
+        /* This is the number of bytes of contents in buf[]. */
+    int curbit;
+        /* The bit number (starting at 0) within buf[] of the next bit
+           to be returned.  If the next bit to be returned is not yet in
+           buf[] (we've already returned everything in there), this points
+           one beyond the end of the buffer contents.
+        */
+    bool streamExhausted;
+        /* The last time we read from the input stream, we got an EOD marker
+           or EOF
+        */
+};
+
+
+
+static void
+initGetCode(struct getCodeState * const getCodeStateP) {
+    
+    /* Fake a previous data block */
+    getCodeStateP->buf[0] = 0;
+    getCodeStateP->buf[1] = 0;
+    getCodeStateP->bufCount = 2;
+    getCodeStateP->curbit = getCodeStateP->bufCount * 8;
+    getCodeStateP->streamExhausted = FALSE;
+}
+
+
+
+static void
+getAnotherBlock(FILE * const ifP, 
+                struct getCodeState * const gsP) {
+
+    unsigned int count;
+    unsigned int assumed_count;
+    bool eof;
+
+    /* Shift buffer down so last two bytes are now the
+       first two bytes.  Shift 'curbit' cursor, which must
+       be somewhere in or immediately after those two
+       bytes, accordingly.
+    */
+    gsP->buf[0] = gsP->buf[gsP->bufCount-2];
+    gsP->buf[1] = gsP->buf[gsP->bufCount-1];
+
+    gsP->curbit -= (gsP->bufCount-2)*8;
+    gsP->bufCount = 2;
+        
+    /* Add the next block to the buffer */
+    getDataBlock(ifP, &gsP->buf[gsP->bufCount], &eof, &count);
+    if (eof) {
+        pm_message("EOF encountered in image "
+                   "before EOD marker.  The GIF "
+                   "file is malformed, but we are proceeding "
+                   "anyway as if an EOD marker were at the end "
+                   "of the file.");
+        assumed_count = 0;
+    } else
+        assumed_count = count;
+
+    gsP->streamExhausted = (assumed_count == 0);
+
+    gsP->bufCount += assumed_count;
+}
+
+
+
+static void
+doGetCode(FILE *                const ifP, 
+          int                   const codeSize,
+          struct getCodeState * const gsP,
+          int *                 const retvalP) {
+
+    if ((gsP->curbit+codeSize) > gsP->bufCount*8 && !gsP->streamExhausted) 
+        /* Not enough left in buffer to satisfy request.  Get the next
+           data block into the buffer.
+        */
+        getAnotherBlock(ifP, gsP);
+
+    if ((gsP->curbit+codeSize) > gsP->bufCount*8) {
+        /* If the buffer still doesn't have enough bits in it, that means
+           there were no data blocks left to read.
+        */
+        *retvalP = -1;  /* EOF */
+
+        {
+            int const bitsUnused = gsP->bufCount*8 - gsP->curbit;
+            if (bitsUnused > 0)
+                pm_message("Stream ends with a partial code "
+                           "(%d bits left in file; "
+                           "expected a %d bit code).  Ignoring.",
+                           bitsUnused, codeSize);
+        }
+    } else {
+        int i, j;
+        int code;
+        unsigned char * const buf = gsP->buf;
+
+        code = 0;  /* initial value */
+        for (i = gsP->curbit, j = 0; j < codeSize; ++i, ++j)
+            code |= ((buf[ i / 8 ] & (1 << (i % 8))) != 0) << j;
+        gsP->curbit += codeSize;
+        *retvalP = code;
+    }
+}
+
+
+
+static int
+getCode(FILE * const ifP, 
+        int    const codeSize, 
+        bool   const init)
+{
+/*----------------------------------------------------------------------------
+   If 'init', initialize the code getter.
+
+   Otherwise, read and return the next lzw code from the file *ifP.
+
+   'codeSize' is the number of bits in the code we are to get.
+
+   Return -1 instead of a code if we encounter the end of the file.
+-----------------------------------------------------------------------------*/
+    static struct getCodeState getCodeState;
+
+    int retval;
+
+    if (init) {
+        initGetCode(&getCodeState);
+        retval = 0;
+    } else 
+        doGetCode(ifP, codeSize, &getCodeState, &retval);
+
+    return retval;
+}
+
+
+struct stack {
+    /* Stack grows from low addresses to high addresses */
+    int * stack;  /* malloc'ed array */
+    int * sp;     /* stack pointer */
+    int * top;    /* next word above top of stack */
+};
+
+
+
+static void 
+initStack(struct stack * const stackP, unsigned int const size) {
+
+    MALLOCARRAY(stackP->stack, size);
+    if (stackP->stack == NULL)
+        pm_error("Unable to allocate %d-word stack.", size);
+    stackP->sp = stackP->stack;
+    stackP->top = stackP->stack + size;
+}
+
+
+
+static void
+pushStack(struct stack * const stackP, int const value) {
+
+    if (stackP->sp >= stackP->top)
+        pm_error("stack overflow");
+
+    *(stackP->sp++) = value;
+}
+
+
+
+static bool
+stackIsEmpty(const struct stack * const stackP) {
+    return stackP->sp == stackP->stack;
+}
+
+
+
+static int
+popStack(struct stack * const stackP) {
+
+    if (stackP->sp <= stackP->stack)
+        pm_error("stack underflow");
+    
+    return *(--stackP->sp);
+}
+
+
+
+static void
+termStack(struct stack * const stackP) {
+    free(stackP->stack);
+    stackP->stack = NULL;
+}
+
+
+/*----------------------------------------------------------------------------
+   Some notes on LZW.
+
+   LZW is an extension of Limpel-Ziv.  The two extensions are:
+
+     1) in Limpel-Ziv, codes are all the same number of bits.  In
+        LZW, they start out small and increase as the stream progresses.
+
+     2) LZW has a clear code that resets the string table and code
+        size.
+
+   The LZW code space is allocated as follows:
+
+   The true data elements are dataWidth bits wide, so the maximum
+   value of a true data element is 2**dataWidth-1.  We call that
+   max_dataVal.  The first byte in the stream tells you what dataWidth
+   is.
+
+   LZW codes 0 - max_dataVal are direct codes.  Each on represents
+   the true data element whose value is that of the LZW code itself.
+   No decompression is required.
+
+   max_dataVal + 1 and up are compression codes.  They encode
+   true data elements:
+
+   max_dataVal + 1 is the clear code.
+         
+   max_dataVal + 2 is the end code.
+
+   max_dataVal + 3 and up are string codes.  Each string code 
+   represents a string of true data elements.  The translation from a
+   string code to the string of true data elements varies as the stream
+   progresses.  In the beginning and after every clear code, the
+   translation table is empty, so no string codes are valid.  As the
+   stream progresses, the table gets filled and more string codes 
+   become valid.
+
+-----------------------------------------------------------------------------*/
+
+
+struct decompressor {
+    struct stack stack;
+    int      fresh;
+        /* The stream is right after a clear code or at the very beginning */
+    int      codeSize;
+        /* The current code size -- each LZW code in this part of the image
+           is this many bits.  Ergo, we read this many bits at a time from
+           the stream.
+        */
+    int      maxnum_code;
+        /* The maximum number of LZW codes that can be represented with the 
+           current code size.  (1 << codeSize)
+        */
+    int      next_tableSlot;
+        /* Index in the code translation table of the next free entry */
+    int      firstcode;
+        /* This is always a true data element code */
+    int      prevcode;
+        /* The code just before, in the image, the one we're processing now */
+    int      table[2][(1 << MAX_LZW_BITS)];
+
+    /* The following are constant for the life of the decompressor */
+    FILE * ifP;
+    int init_codeSize;
+    int max_dataVal;
+    int clear_code;
+    int end_code; 
+};
+
+
+
+static void
+resetDecompressor(struct decompressor * const decompP) {
+
+    decompP->codeSize = decompP->init_codeSize+1;
+    decompP->maxnum_code = 1 << decompP->codeSize;
+    decompP->next_tableSlot = decompP->max_dataVal + 3;
+    decompP->fresh = 1;
+}
+
+
+
+static void
+lzwInit(struct decompressor * const decompP, 
+        FILE *                const ifP,
+        int                   const init_codeSize) {
+
+    if (verbose)
+        pm_message("Image says the initial compression code size is "
+                   "%d bits", 
+                   init_codeSize);
+    
+    decompP->ifP = ifP;
+    decompP->init_codeSize = init_codeSize;
+
+    assert(decompP->init_codeSize < sizeof(decompP->max_dataVal) * 8);
+
+    decompP->max_dataVal = (1 << init_codeSize) - 1;
+    decompP->clear_code = decompP->max_dataVal + 1;
+    decompP->end_code = decompP->max_dataVal + 2;
+
+    if (verbose)
+        pm_message("Initial code size is %u bits; clear code = 0x%x, "
+                   "end code = 0x%x",
+                   decompP->init_codeSize, 
+                   decompP->clear_code, decompP->end_code);
+    
+    /* The entries in the translation table for true data codes are
+       constant throughout the stream.  We set them now and they never
+       change.
+    */
+    {
+        unsigned int i;
+        for (i = 0; i <= decompP->max_dataVal; ++i) {
+            decompP->table[0][i] = 0;
+            decompP->table[1][i] = i;
+        }
+    }
+    resetDecompressor(decompP);
+
+    getCode(decompP->ifP, 0, TRUE);
+    
+    decompP->fresh = TRUE;
+    
+    initStack(&decompP->stack, maxnum_lzwCode * 2);
+}
+
+
+
+static void
+lzwTerm(struct decompressor * const decompP) {
+
+    termStack(&decompP->stack);
+}
+
+
+
+static void
+expandCodeOntoStack(struct decompressor * const decompP,
+                    int                   const incode,
+                    bool *                const errorP) {
+/*----------------------------------------------------------------------------
+   'incode' is an LZW string code.  It represents a string of true data
+   elements, as defined by the string translation table in *decompP.
+
+   Expand the code to a string of LZW direct codes and push them onto the
+   stack such that the leftmost code is on top.
+
+   Also add to the translation table where appropriate.
+
+   Iff the translation table contains a cycle (which means the LZW stream
+   from which it was built is invalid), return *errorP == TRUE.
+-----------------------------------------------------------------------------*/
+    int code;
+    bool error;
+
+    error = FALSE;
+
+    if (incode < decompP->next_tableSlot) 
+        code = incode;
+    else {
+        /* It's a code that isn't in our translation table yet */
+        pushStack(&decompP->stack, decompP->firstcode);
+        code = decompP->prevcode;
+    }
+
+    {
+        /* Get the whole string that this compression code
+           represents and push it onto the code stack so the
+           leftmost code is on top.  Set decompP->firstcode to the
+           first (leftmost) code in that string.
+            */
+
+        unsigned int stringCount;
+        stringCount = 0;
+
+        while (code > decompP->max_dataVal && !error) {
+            if (stringCount > maxnum_lzwCode) {
+                pm_message("Error in GIF image: contains LZW string loop");
+                error = TRUE;
+            } else {
+                ++stringCount;
+                pushStack(&decompP->stack, decompP->table[1][code]);
+                code = decompP->table[0][code];
+            }
+        }
+        decompP->firstcode = decompP->table[1][code];
+        pushStack(&decompP->stack, decompP->firstcode);
+    }
+
+    if (decompP->next_tableSlot < maxnum_lzwCode) {
+        decompP->table[0][decompP->next_tableSlot] = decompP->prevcode;
+        decompP->table[1][decompP->next_tableSlot] = decompP->firstcode;
+        ++decompP->next_tableSlot;
+        if (decompP->next_tableSlot >= decompP->maxnum_code) {
+            /* We've used up all the codes of the current code size.
+               Future codes in the stream will have codes one bit longer.
+               But there's an exception if we're already at the LZW
+               maximum, in which case the codes will simply continue
+               the same size.
+            */
+            if (decompP->codeSize < MAX_LZW_BITS) {
+                ++decompP->codeSize;
+                decompP->maxnum_code = 1 << decompP->codeSize;
+            }
+        }
+    }
+
+    decompP->prevcode = incode;
+    *errorP = error;
+}
+
+
+
+static int
+lzwReadByte(struct decompressor * const decompP) {
+/*----------------------------------------------------------------------------
+  Return the next data element of the decompressed image.  In the context
+  of a GIF, a data element is the color table index of one pixel.
+
+  We read and return the next byte of the decompressed image, or:
+
+    Return -1 if we hit EOF prematurely (i.e. before an "end" code.  We
+    forgive the case that the "end" code is followed by EOF instead of
+    an EOD marker (zero length DataBlock)).
+
+    Return -2 if there are no more bytes in the image.  In that case,
+    make sure the file is positioned immediately after the image (i.e.
+    after the EOD marker that marks the end of the image or EOF).
+
+    Return -3 if we encounter errors in the LZW stream.
+-----------------------------------------------------------------------------*/
+    int retval;
+
+    if (!stackIsEmpty(&decompP->stack))
+        retval = popStack(&decompP->stack);
+    else if (decompP->fresh) {
+        decompP->fresh = FALSE;
+        /* Read off all initial clear codes, read the first non-clear code,
+           and return it.  There are no strings in the table yet, so the next
+           code must be a direct true data code.
+        */
+        do {
+            decompP->firstcode =
+                getCode(decompP->ifP, decompP->codeSize, FALSE);
+            decompP->prevcode = decompP->firstcode;
+        } while (decompP->firstcode == decompP->clear_code);
+        if (decompP->firstcode == decompP->end_code) {
+            if (!zeroDataBlock)
+                readThroughEod(decompP->ifP);
+            retval = -2;
+        } else
+            retval = decompP->firstcode;
+    } else {
+        int code;
+        code = getCode(decompP->ifP, decompP->codeSize, FALSE);
+        if (code == -1)
+            retval = -1;
+        else {
+            assert(code >= 0);  /* -1 is only possible error return */
+            if (code == decompP->clear_code) {
+                resetDecompressor(decompP);
+                retval = lzwReadByte(decompP);
+            } else {
+                if (code == decompP->end_code) {
+                    if (!zeroDataBlock)
+                        readThroughEod(decompP->ifP);
+                    retval = -2;
+                } else {
+                    bool error;
+                    expandCodeOntoStack(decompP, code, &error);
+                    if (error)
+                        retval = -3;
+                    else
+                        retval = popStack(&decompP->stack);
+                }
+            }
+        }
+    }
+    return retval;
+}
+
+
+
+enum pass {MULT8PLUS0, MULT8PLUS4, MULT4PLUS2, MULT2PLUS1};
+
+static void
+bumpRowInterlace(unsigned int * const rowP,
+                 unsigned int   const rows,
+                 enum pass *    const passP) {
+/*----------------------------------------------------------------------------
+   Move *pixelCursorP to the next row in the interlace pattern.
+-----------------------------------------------------------------------------*/
+    /* There are 4 passes:
+       MULT8PLUS0: Rows 8, 16, 24, 32, etc.
+       MULT8PLUS4: Rows 4, 12, 20, 28, etc.
+       MULT4PLUS2: Rows 2, 6, 10, 14, etc.
+       MULT2PLUS1: Rows 1, 3, 5, 7, 9, etc.
+    */
+    
+    switch (*passP) {
+    case MULT8PLUS0:
+        *rowP += 8;
+        break;
+    case MULT8PLUS4:
+        *rowP += 8;
+        break;
+    case MULT4PLUS2:
+        *rowP += 4;
+        break;
+    case MULT2PLUS1:
+        *rowP += 2;
+        break;
+    }
+    /* Set the proper pass for the next read.  Note that if there are
+       more than 4 rows, the sequence of passes is sequential, but
+       when there are fewer than 4, we may skip e.g. from MULT8PLUS0
+       to MULT4PLUS2.
+    */
+    while (*rowP >= rows && *passP != MULT2PLUS1) {
+        switch (*passP) {
+        case MULT8PLUS0:
+            *passP = MULT8PLUS4;
+            *rowP = 4;
+            break;
+        case MULT8PLUS4:
+            *passP = MULT4PLUS2;
+            *rowP = 2;
+            break;
+        case MULT4PLUS2:
+            *passP = MULT2PLUS1;
+            *rowP = 1;
+            break;
+        case MULT2PLUS1:
+            /* We've read the entire image */
+            break;
+        }
+    }
+}
+
+
+
+struct pnmBuffer {
+    xel ** xels;
+    unsigned int col;
+    unsigned int row;
+};
+
+static void
+addPixelToRaster(unsigned int       const cmapIndex,
+                 struct pnmBuffer * const pnmBufferP,
+                 unsigned int       const cols,
+                 unsigned int       const rows,
+                 gifColorMap              cmap, 
+                 unsigned int       const cmapSize,
+                 bool               const interlace,
+                 int                const transparentIndex,
+                 bit **             const alphabits,
+                 enum pass *        const passP) {
+
+    if (cmapIndex >= cmapSize)
+        pm_error("Invalid color index %u in an image that has only "
+                 "%u colors in the color map.", cmapIndex, cmapSize);
+    
+    assert(cmapIndex < MAXCOLORMAPSIZE);
+    
+    PPM_ASSIGN(pnmBufferP->xels[pnmBufferP->row][pnmBufferP->col], 
+               cmap[CM_RED][cmapIndex],
+               cmap[CM_GRN][cmapIndex],
+               cmap[CM_BLU][cmapIndex]);
+    
+    if (alphabits) 
+        alphabits[pnmBufferP->row][pnmBufferP->col] =
+            (cmapIndex == transparentIndex) ? PBM_BLACK : PBM_WHITE;
+    
+    ++pnmBufferP->col;
+    if (pnmBufferP->col == cols) {
+        pnmBufferP->col = 0;
+        if (interlace)
+            bumpRowInterlace(&pnmBufferP->row, rows, passP);
+        else
+            ++pnmBufferP->row;
+    }
+}
+
+
+
+static void
+readImageData(FILE *       const ifP, 
+              xel **       const xels, 
+              unsigned int const cols,
+              unsigned int const rows,
+              gifColorMap        cmap, 
+              unsigned int const cmapSize,
+              bool         const interlace,
+              int          const transparentIndex,
+              bit **       const alphabits) {
+
+    unsigned char lzwMinCodeSize;      
+    enum pass pass;
+    struct decompressor decomp;
+    struct pnmBuffer pnmBuffer;
+    bool gotMinCodeSize;
+
+    pass = MULT8PLUS0;
+
+    pnmBuffer.xels = xels;
+    pnmBuffer.col  = 0;
+    pnmBuffer.row  = 0;
+
+    gotMinCodeSize =  ReadOK(ifP, &lzwMinCodeSize, 1);
+    if (!gotMinCodeSize)
+        pm_error("GIF stream ends (or read error) "
+                 "right after an image separator; no "
+                 "image data follows.");
+
+    if (lzwMinCodeSize > MAX_LZW_BITS)
+        pm_error("Invalid minimum code size value in image data: %u.  "
+                 "Maximum allowable code size in GIF is %u", 
+                 lzwMinCodeSize, MAX_LZW_BITS);
+
+    lzwInit(&decomp, ifP, lzwMinCodeSize);
+
+    while (pnmBuffer.row < rows) {
+        int const rc = lzwReadByte(&decomp);
+
+        switch (rc) {
+        case -3:
+            pm_error("Error in GIF input stream");
+            break;
+        case -2:
+            pm_error("Error in GIF image: Not enough raster data to fill "
+                     "%u x %u dimensions.  Ran out of raster data in "
+                     "row %u", cols, rows, pnmBuffer.row);
+            break;
+        case -1:
+            pm_error("Premature end of file; no proper GIF closing");
+            break;
+        default:
+            addPixelToRaster(rc, &pnmBuffer, cols, rows, cmap, cmapSize,
+                             interlace, transparentIndex, alphabits, &pass);
+        }
+    }
+    if (lzwReadByte(&decomp) >= 0)
+        pm_message("Extraneous data at end of image.  "
+                   "Skipped to end of image");
+
+    lzwTerm(&decomp);
+}
+
+
+
+static void
+writePnm(FILE *outfile, xel ** const xels, 
+         const int cols, const int rows,
+         const int hasGray, const int hasColor) {
+/*----------------------------------------------------------------------------
+   Write a PNM image to the current position of file 'outfile' with
+   dimensions 'cols' x 'rows' and raster 'xels'.
+   
+   Make it PBM, PGM, or PBM according to 'hasGray' and 'hasColor'.
+-----------------------------------------------------------------------------*/
+    int format;
+    const char *format_name;
+           
+    if (hasColor) {
+        format = PPM_FORMAT;
+        format_name = "PPM";
+    } else if (hasGray) {
+        format = PGM_FORMAT;
+        format_name = "PGM";
+    } else {
+        format = PBM_FORMAT;
+        format_name = "PBM";
+    }
+    if (verbose) 
+        pm_message("writing a %s file", format_name);
+    
+    if (outfile) 
+        pnm_writepnm(outfile, xels, cols, rows,
+                     (xelval) GIFMAXVAL, format, FALSE);
+}
+
+
+
+static void
+transparencyMessage(int const transparentIndex, 
+                    gifColorMap cmap) {
+/*----------------------------------------------------------------------------
+   If user wants verbose output, tell him that the color with index
+   'transparentIndex' is supposed to be a transparent background color.
+   
+   If transparentIndex == -1, tell him there is no transparent background
+   color.
+-----------------------------------------------------------------------------*/
+    if (verbose) {
+        if (transparentIndex == -1)
+            pm_message("no transparency");
+        else
+            pm_message("transparent background color: rgb:%02x/%02x/%02x "
+                       "Index %d",
+                       cmap[CM_RED][transparentIndex],
+                       cmap[CM_GRN][transparentIndex],
+                       cmap[CM_BLU][transparentIndex],
+                       transparentIndex
+                );
+    }
+}
+
+static void
+readGifHeader(FILE * const gifFile, struct gifScreen * const gifScreenP) {
+/*----------------------------------------------------------------------------
+   Read the GIF stream header off the file gifFile, which is present
+   positioned to the beginning of a GIF stream.  Return the info from it
+   as *gifScreenP.
+-----------------------------------------------------------------------------*/
+    unsigned char   buf[16];
+    char     version[4];
+
+
+    if (! ReadOK(gifFile,buf,6))
+        pm_error("error reading magic number" );
+    
+    if (strncmp((char *)buf,"GIF",3) != 0)
+        pm_error("File does not contain a GIF stream.  It does not start "
+                 "with 'GIF'.");
+    
+    strncpy(version, (char *)buf + 3, 3);
+    version[3] = '\0';
+    
+    if (verbose)
+        pm_message("GIF format version is '%s'", version);
+    
+    if ((!STREQ(version, "87a")) && (!STREQ(version, "89a")))
+        pm_error("bad version number, not '87a' or '89a'" );
+    
+    if (! ReadOK(gifFile,buf,7))
+        pm_error("failed to read screen descriptor" );
+    
+    gifScreenP->Width           = LM_to_uint(buf[0],buf[1]);
+    gifScreenP->Height          = LM_to_uint(buf[2],buf[3]);
+    gifScreenP->ColorMapSize    = 1 << ((buf[4] & 0x07) + 1);
+    gifScreenP->ColorResolution = (buf[4] & 0x70 >> 3) + 1;
+    gifScreenP->Background      = buf[5];
+    gifScreenP->AspectRatio     = buf[6];
+
+    if (verbose) {
+        pm_message("GIF Width = %d GIF Height = %d "
+                   "Pixel aspect ratio = %d (%f:1)",
+                   gifScreenP->Width, gifScreenP->Height, 
+                   gifScreenP->AspectRatio, 
+                   gifScreenP->AspectRatio == 0 ? 
+                   1 : (gifScreenP->AspectRatio + 15) / 64.0);
+        pm_message("Colors = %d   Color Resolution = %d",
+                   gifScreenP->ColorMapSize, gifScreenP->ColorResolution);
+    }           
+    if (BitSet(buf[4], LOCALCOLORMAP)) {    /* Global Colormap */
+        readColorMap(gifFile, gifScreenP->ColorMapSize, gifScreenP->ColorMap,
+                     &gifScreenP->hasGray, &gifScreenP->hasColor);
+        if (verbose) {
+            pm_message("Color map %s grays, %s colors", 
+                       gifScreenP->hasGray ? "contains" : "doesn't contain",
+                       gifScreenP->hasColor ? "contains" : "doesn't contain");
+        }
+    }
+    
+    if (gifScreenP->AspectRatio != 0 && gifScreenP->AspectRatio != 49) {
+        float   r;
+        r = ( (float) gifScreenP->AspectRatio + 15.0 ) / 64.0;
+        pm_message("warning - input pixels are not square, "
+                   "but we are rendering them as square pixels "
+                   "in the output.  "
+                   "To fix the output, run it through "
+                   "'pnmscale -%cscale %g'",
+                   r < 1.0 ? 'x' : 'y',
+                   r < 1.0 ? 1.0 / r : r );
+    }
+}
+
+
+
+static void
+readExtensions(FILE*          const ifP, 
+               struct gif89 * const gif89P,
+               bool *         const eodP) {
+/*----------------------------------------------------------------------------
+   Read extension blocks from the GIF stream to which the file *ifP is
+   positioned.  Read up through the image separator that begins the
+   next image or GIF stream terminator.
+
+   If we encounter EOD (end of GIF stream) before we find an image 
+   separator, we return *eodP == TRUE.  Else *eodP == FALSE.
+
+   If we hit end of file before an EOD marker, we abort the program with
+   an error message.
+-----------------------------------------------------------------------------*/
+    bool imageStart;
+    bool eod;
+
+    eod = FALSE;
+    imageStart = FALSE;
+
+    /* Read the image descriptor */
+    while (!imageStart && !eod) {
+        unsigned char c;
+
+        if (! ReadOK(ifP,&c,1))
+            pm_error("EOF / read error on image data" );
+
+        if (c == ';') {         /* GIF terminator */
+            eod = TRUE;
+        } else if (c == '!') {         /* Extension */
+            if (! ReadOK(ifP,&c,1))
+                pm_error("EOF / "
+                         "read error on extension function code");
+            doExtension(ifP, c, gif89P);
+        } else if (c == ',') 
+            imageStart = TRUE;
+        else 
+            pm_message("bogus character 0x%02x, ignoring", (int) c );
+    }
+    *eodP = eod;
+}
+
+
+
+static void
+reportImageInfo(unsigned int const cols,
+                unsigned int const rows,
+                bool         const useGlobalColormap,
+                unsigned int const localColorMapSize,
+                bool         const interlaced) {
+
+
+    pm_message("reading %u by %u%s GIF image",
+               cols, rows, interlaced ? " interlaced" : "" );
+    if (useGlobalColormap)
+        pm_message("  Uses global colormap");
+    else
+        pm_message("  Uses local colormap of %u colors", localColorMapSize);
+}
+
+
+
+static void
+convertImage(FILE *           const ifP, 
+             bool             const skipIt, 
+             FILE *           const imageout_file, 
+             FILE *           const alphafile, 
+             struct gifScreen       gifScreen,
+             struct gif89     const gif89) {
+/*----------------------------------------------------------------------------
+   Read a single GIF image from the current position of file 'ifP'.
+
+   If 'skipIt' is TRUE, don't do anything else.  Otherwise, write the
+   image to the current position of files 'imageout_file' and 'alphafile'.
+   If 'alphafile' is NULL, though, don't write any alpha information.
+-----------------------------------------------------------------------------*/
+    unsigned char buf[16];
+    bool useGlobalColormap;
+    xel **xels;  /* The image raster, in libpnm format */
+    bit **alphabits;  
+        /* The image alpha mask, in libpbm format.  NULL if we aren't computing
+           an alpha mask.
+        */
+    unsigned int cols, rows;  /* Dimensions of the image */
+    gifColorMap localColorMap;
+    unsigned int localColorMapSize;
+    bool interlaced;
+
+    if (! ReadOK(ifP,buf,9))
+        pm_error("couldn't read left/top/width/height");
+
+    useGlobalColormap = ! BitSet(buf[8], LOCALCOLORMAP);
+    localColorMapSize = 1u << ((buf[8] & 0x07) + 1);
+    cols = LM_to_uint(buf[4], buf[5]);
+    rows = LM_to_uint(buf[6], buf[7]);
+    interlaced = !!BitSet(buf[8], INTERLACE);
+
+    if (verbose)
+        reportImageInfo(cols, rows, useGlobalColormap, localColorMapSize,
+                        interlaced);
+        
+    xels = pnm_allocarray(cols, rows);
+    if (!xels)
+        pm_error("couldn't alloc space for image" );
+
+    if (alphafile) {
+        alphabits = pbm_allocarray(cols, rows);
+        if (!alphabits)
+            pm_error("couldn't alloc space for alpha image" );
+    } else
+        alphabits = NULL;
+    
+    if (!useGlobalColormap) {
+        int hasGray, hasColor;
+
+        readColorMap(ifP, localColorMapSize, localColorMap, 
+                     &hasGray, &hasColor);
+        transparencyMessage(gif89.transparent, localColorMap);
+        readImageData(ifP, xels, cols, rows, localColorMap, localColorMapSize,
+                      interlaced, gif89.transparent, alphabits);
+        if (!skipIt) {
+            writePnm(imageout_file, xels, cols, rows,
+                     hasGray, hasColor);
+        }
+    } else {
+        transparencyMessage(gif89.transparent, gifScreen.ColorMap);
+        readImageData(ifP, xels, cols, rows, 
+                      gifScreen.ColorMap, gifScreen.ColorMapSize,
+                      interlaced, gif89.transparent, alphabits);
+        if (!skipIt) {
+            writePnm(imageout_file, xels, cols, rows,
+                     gifScreen.hasGray, gifScreen.hasColor);
+        }
+    }
+
+    if (!skipIt && alphafile && alphabits)
+        pbm_writepbm(alphafile, alphabits, cols, rows, FALSE);
+
+    pnm_freearray(xels, rows);
+    if (alphabits)
+        pbm_freearray(alphabits, rows);
+}
+
+
+
+static void
+convertImages(FILE * const ifP, 
+              bool   const allImages,
+              int    const requestedImageSeq, 
+              bool   const drainStream,
+              FILE * const imageout_file, 
+              FILE * const alphafile) {
+/*----------------------------------------------------------------------------
+   Read a GIF stream from file 'ifP' and write one or more images from
+   it as PNM images to file 'imageout_file'.  If the images have transparency
+   and 'alphafile' is non-NULL, write PGM alpha masks to file 'alphafile'.
+
+   'allImages' means Caller wants all the images in the stream.  
+
+   'requestedImageSeq' is meaningful only when 'allImages' is FALSE.  It 
+   is the sequence number of the one image Caller wants from the stream,
+   with the first image being 0.
+
+   'drainInput' means to read the entire GIF stream, even after
+   reading the image Caller asked for.  We read the stream, not just
+   the file it's in, so we still recognize certain errors in the GIF
+   format in the tail of the stream and there may yet be more stuff in
+   the file when we return.
+-----------------------------------------------------------------------------*/
+    int imageSeq;
+        /* Sequence within GIF stream of image we are currently processing.
+           First is 0.
+        */
+    struct gifScreen gifScreen;
+    struct gif89 gif89;
+    bool eod;
+        /* We've read through the GIF terminator character */
+
+    initGif89(&gif89);
+
+    readGifHeader(ifP, &gifScreen);
+
+    for (imageSeq = 0, eod = FALSE;
+         !eod && (imageSeq <= requestedImageSeq || allImages || drainStream);
+         ++imageSeq) {
+
+        readExtensions(ifP, &gif89, &eod);
+
+        if (eod) {
+            /* GIF stream ends before image with sequence imageSeq */
+            if (!allImages && (imageSeq <= requestedImageSeq))
+                pm_error("You requested Image %d, but "
+                         "only %d image%s found in GIF stream",
+                         requestedImageSeq+1,
+                         imageSeq, imageSeq>1?"s":"" );
+        } else {
+            if (verbose)
+                pm_message("Reading Image Sequence %d", imageSeq);
+            convertImage(ifP, !allImages && (imageSeq != requestedImageSeq), 
+                         imageout_file, alphafile, gifScreen, gif89);
+        }
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE *ifP;
+    FILE *alpha_file, *imageout_file;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    verbose = cmdline.verbose;
+    showComment = cmdline.comments;
+   
+    ifP = pm_openr(cmdline.input_filespec);
+
+    if (cmdline.alpha_filename == NULL)
+        alpha_file = NULL;
+    else
+        alpha_file = pm_openw(cmdline.alpha_filename);
+
+    if (alpha_file && STREQ(cmdline.alpha_filename, "-"))
+        imageout_file = NULL;
+    else
+        imageout_file = stdout;
+
+    convertImages(ifP, cmdline.all_images, cmdline.image_no, 
+                  !cmdline.quitearly, imageout_file, alpha_file);
+
+    pm_close(ifP);
+    if (imageout_file != NULL) 
+        pm_close( imageout_file );
+    if (alpha_file != NULL)
+        pm_close( alpha_file );
+
+    return 0;
+}
diff --git a/converter/other/hdifftopam.c b/converter/other/hdifftopam.c
new file mode 100644
index 00000000..444d6068
--- /dev/null
+++ b/converter/other/hdifftopam.c
@@ -0,0 +1,156 @@
+/******************************************************************************
+                                hdifftopam
+*******************************************************************************
+  This program recovers a PAM image from a horizontal difference images 
+  such as created by Pamtohdiff.
+
+  By Bryan Henderson, San Jose, CA 2002.04.15.
+******************************************************************************/
+#include <string.h>
+#include <stdio.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "nstring.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespecs of input files */
+    unsigned int pnm;
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc( 100*sizeof( optEntry ) );
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "pnm",       OPT_FLAG,    NULL, &cmdlineP->pnm,      0);
+    OPTENT3(0, "verbose",   OPT_FLAG,    NULL, &cmdlineP->verbose,  0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        pm_error("Too many arguments.");
+}
+
+
+
+static void
+makePnm(struct pam * const pamP) {
+
+    switch (pamP->depth) {
+    case 1: 
+        pamP->format = PGM_FORMAT;
+        break;
+    case 3:
+        pamP->format = PPM_FORMAT;
+        break;
+    default:
+        pm_error("Input depth (%d) does not correspond to a PNM format.",
+                 pamP->depth);
+    }
+}
+
+
+
+static void
+    describeOutput(struct pam const pam) {
+
+    pm_message("Output is %d x %d x %d, maxval %u",
+               pam.width, pam.height, pam.depth, (unsigned int) pam.maxval);
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+    FILE *ifP;
+    struct cmdlineInfo cmdline;
+    struct pam diffpam, outpam;
+    unsigned int row;
+    tuple * diffrow;
+    tuple * outrow;
+    tuple * prevrow;
+
+    pnm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    pnm_readpaminit(ifP, &diffpam, PAM_STRUCT_SIZE(tuple_type));
+
+    if (diffpam.format != PAM_FORMAT) 
+        pm_error("Input must be a PAM file, not PNM");
+    else if (!STREQ(diffpam.tuple_type, "hdiff")) 
+        pm_error("Input tuple type is '%s'.  Must be 'hdiff'",
+                 diffpam.tuple_type);
+
+    outpam = diffpam;
+    outpam.file = stdout;
+    strcpy(outpam.tuple_type, "unhdiff");
+
+    if (cmdline.verbose)
+        describeOutput(outpam);
+    if (cmdline.pnm)
+        makePnm(&outpam);
+
+    pnm_writepaminit(&outpam);
+
+    diffrow = pnm_allocpamrow(&diffpam);
+    outrow =  pnm_allocpamrow(&outpam);
+    prevrow = pnm_allocpamrow(&diffpam);
+
+    pnm_setpamrow(&diffpam, prevrow, 0);
+
+    {
+        unsigned int const bias = diffpam.maxval/2;
+        
+        for (row = 0; row < diffpam.height; ++row) {
+            unsigned int col;
+            pnm_readpamrow(&diffpam, diffrow);
+            for (col = 0; col < diffpam.width; ++col) {
+                unsigned int plane;
+                for (plane = 0; plane < diffpam.depth; ++plane) {
+                    sample const prevSample = prevrow[col][plane];
+                    sample const diffSample = diffrow[col][plane];
+                    
+                    outrow[col][plane] = 
+                        (-bias + prevSample + diffSample) % (outpam.maxval+1);
+                    prevrow[col][plane] = outrow[col][plane];
+                }
+            }
+            pnm_writepamrow(&outpam, outrow);
+        }
+    }
+    pnm_freepamrow(prevrow);
+    pnm_freepamrow(outrow);
+    pnm_freepamrow(diffrow);
+
+    exit(0);
+}
+
diff --git a/converter/other/infotopam.c b/converter/other/infotopam.c
new file mode 100644
index 00000000..4f29eb07
--- /dev/null
+++ b/converter/other/infotopam.c
@@ -0,0 +1,543 @@
+/* infotopam:  A program to convert Amiga Info icon files to PAM files
+ * Copyright (C) 2004  Richard Griswold - griswold@acm.org
+ *
+ * Thanks to the following people on comp.sys.amiga.programmer for tips
+ * and pointers on decoding the info file format:
+ *
+ *   Ben Hutchings
+ *   Thomas Richter
+ *   Kjetil Svalastog Matheussen
+ *   Anders Melchiorsen
+ *   Dirk Stoecker
+ *   Ronald V.D.
+ *
+ * The format of the Amiga info file is as follows:
+ *
+ *   DiskObject header            (78 bytes)
+ *   Optional DrawerData header   (56 bytes)
+ *   First icon header            (20 bytes)
+ *   First icon data
+ *   Second icon header           (20 bytes)
+ *   Second icon data
+ *
+ * The DiskObject header contains, among other things, the magic number
+ * (0xE310), the object width and height (inside the embedded Gadget header),
+ * and the version.
+ *
+ * Each icon header contains the icon width and height, which can be smaller
+ * than the object width and height, and the number of bit-planes.
+ *
+ * The icon data has the following format:
+ *
+ *   BIT-PLANE planes, each with HEIGHT rows of (WIDTH +15) / 16 * 2 bytes
+ *   length.
+ *
+ * So if you have a 9x3x2 icon, the icon data will look like this:
+ *
+ *   aaaa aaaa a000 0000
+ *   aaaa aaaa a000 0000
+ *   aaaa aaaa a000 0000
+ *   bbbb bbbb b000 0000
+ *   bbbb bbbb b000 0000
+ *   bbbb bbbb b000 0000
+ *
+ * Where 'a' is a bit for the first bit-plane, 'b' is a bit for the second
+ * bit-plane, and '0' is padding.  Thanks again to Ben Hutchings for his
+ * very helpful post!
+ *
+ * This program uses code from "sidplay" and an older "infotoxpm" program I
+ * wrote, both of which are released under GPL.
+ *
+ *-------------------------------------------------------------------------
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/* Struct to hold miscellaneous icon information */
+typedef struct IconInfo_ {
+    const char  *name;        /* Icon file name */
+    FILE        *fp;          /* Input file pointer */
+
+    bool         forceColor;  /* Convert 1 bitplane icon to color icon */
+    unsigned int numColors;   /* Number of colors to override */
+    bool         selected;    /* Converting selected (second) icon */
+
+    bool         drawerData;  /* Icon has drawer data */
+    unsigned int version;     /* Icon version */
+    unsigned int width;       /* Width in pixels */
+    unsigned int height;      /* Height in pixels */
+    unsigned int depth;       /* Bits of color per pixel */
+    pixel        colors[4];   /* Colors to use for converted icons */
+    unsigned char *icon;      /* Completed icon */
+
+} IconInfo;
+
+/* Header for each icon image */
+typedef struct IconHeader_ { /* 20 bytes */
+    unsigned char pad0[4];        /* Padding (always seems to be zero) */
+    unsigned char iconWidth[2];   /* Width (usually equal to Gadget width) */
+    unsigned char iconHeight[2];  
+    /* Height (usually equal to Gadget height -1) */
+    unsigned char bpp[2];         /* Bits per pixel */
+    unsigned char pad1[10];       /* ??? */
+} IconHeader;
+
+/*
+ * Gadget and DiskObject structs come from the libsidplay 1.36.57 info_.h file
+ * http://www.geocities.com/SiliconValley/Lakes/5147/sidplay/linux.html
+ */
+typedef struct DiskObject_ { /* 78 bytes (including Gadget struct) */
+    unsigned char magic[2];         /* Magic number at the start of the file */
+    unsigned char version[2];       /* Object version number */
+    unsigned char gadget[44];       /* Copy of in memory gadget (44 by */
+    unsigned char type;             /* ??? */
+    unsigned char pad;              /* Pad it out to the next word boundry */
+    unsigned char pDefaultTool[4];  /* Pointer  to default tool */
+    unsigned char ppToolTypes[4];   /* Pointer pointer to tool types */
+    unsigned char currentX[4];      /* Current X position (?) */
+    unsigned char currentY[4];      /* Current Y position (?) */
+    unsigned char pDrawerData[4];   /* Pointer to drawer data */
+    unsigned char pToolWindow[4];   /* Ptr to tool window - only for tools */
+    unsigned char stackSize[4];     /* Stack size - only for tools */
+} DiskObject;
+
+
+
+static void
+parseCommandLine( int              argc,
+                  char *           argv[],
+                  IconInfo * const infoP ) {
+
+    unsigned int numColorArgs,  /* Number of arguments for overriding colors */
+        colorIdx,      /* Color index */
+        i;             /* Argument index */
+    const char  * const colors[4] = { 
+        /* Pixel colors based on original Amiga colors */
+        "#0055AA",    /*   Blue      0,  85, 170 */
+        "#FFFFFF",    /*   White   255, 255, 255 */
+        "#000020",    /*   Black     0,   0,  32 */
+        "#FF8A00"     /*   Orange  255, 138,   0 */
+    };
+
+    /* Option entry variables */
+    optEntry     *option_def;
+    optStruct3    opt;
+    unsigned int  option_def_index;
+    unsigned int numColorsSpec, forceColorSpec, selectedSpec;
+  
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    /* Set command line options */
+    option_def_index = 0;   /* Incremented by OPTENT3 */
+    OPTENT3(0, "forcecolor", OPT_FLAG, NULL, &forceColorSpec, 0);
+    OPTENT3(0, "numcolors",  OPT_UINT, &infoP->numColors, &numColorsSpec, 0);
+    OPTENT3(0, "selected",   OPT_FLAG, NULL, &selectedSpec,   0);
+
+    /* Initialize the iconInfo struct */
+    infoP->name = NULL;
+    infoP->fp = NULL;
+    infoP->drawerData = FALSE;
+    infoP->version = 0;
+    infoP->width = 0;
+    infoP->height = 0;
+    infoP->depth = 0;
+    infoP->icon = NULL;
+    for ( colorIdx = 0; colorIdx < 4; colorIdx++ )
+        infoP->colors[colorIdx] = 
+            ppm_parsecolor( (char*) colors[colorIdx], 0xFF );
+
+    /* Initialize option structure */
+    opt.opt_table     = option_def;
+    opt.short_allowed = FALSE;  /* No short (old-fashioned) options */
+    opt.allowNegNum   = FALSE;  /* No negative number parameters */
+
+    /* Parse the command line */
+    optParseOptions3( &argc, argv, opt, sizeof( opt ), 0 );
+
+    infoP->forceColor = forceColorSpec;
+    infoP->selected = selectedSpec;
+    if (!numColorsSpec)
+        infoP->numColors = 0;
+
+    /* Get colors and file name */
+    numColorArgs = infoP->numColors * 2;
+    if ( ( argc - 1 != numColorArgs ) && ( argc - 1 != numColorArgs + 1 ) ) {
+        pm_error( "Wrong number of arguments for number of colors.  "
+                  "For %u colors, you need %u color arguments, "
+                  "with possibly one more argument for the input file name.",
+                  infoP->numColors, numColorArgs );
+    }
+
+    /* Convert color arguments */
+    for ( i = 1; i < numColorArgs; i += 2 ) {
+        char *       endptr;        /* End pointer for strtol() */
+        unsigned int colorIdx;
+
+        /* Get color index from argument */
+        colorIdx = strtoul( argv[i], &endptr, 0 );
+
+        if ( *endptr != '\0' ) {
+            pm_error( "'%s' is not a valid color index", argv[i] );
+        }
+
+        /* Check color index range (current 0 to 3) */
+        if ( ( colorIdx < 0 ) || ( colorIdx > 3 ) ) {
+            pm_error( "%u is not a valid color index (minimum 0, maximum 3)",
+                      colorIdx );
+        }
+
+        /* Convert the color for this color index */
+        infoP->colors[colorIdx] = ppm_parsecolor( argv[i+1], 0xFF );
+    }
+
+    /* Set file name */
+    if ( i > argc-1 )
+        infoP->name = "-";  /* Read from standard input */
+    else
+        infoP->name = argv[i];
+}
+
+
+
+static void
+getDiskObject( IconInfo * const infoP ) {
+/*-------------------------------------------------------------------------
+ * Get fields from disk object portion of info file
+ *-------------------------------------------------------------------------*/
+    DiskObject  dobj;      /* Disk object structure */
+    size_t      bytesRead;
+
+    /* Read the disk object header */
+    bytesRead = fread( &dobj, 1, sizeof(dobj), infoP->fp );
+    if (ferror(infoP->fp))
+        pm_error( "Cannot read disk object header for file '%s'.  "
+                  "fread() errno = %d (%s)",
+                  infoP->name, errno, strerror( errno ) );
+    else if ( bytesRead != sizeof(dobj) )
+        pm_error( "Cannot read entire disk object header for file '%s'.  "
+                  "Only read 0x%X of 0x%X bytes",
+                  infoP->name, bytesRead, sizeof(dobj) );
+
+    /* Check magic number */
+    if ( ( dobj.magic[0] != 0xE3 ) && ( dobj.magic[1] != 0x10 ) )
+        pm_error( "Wrong magic number for file '%s'.  "
+                  "Expected 0xE310, but got 0x%X%X",
+                  infoP->name, dobj.magic[0], dobj.magic[1] );
+
+    /* Set version info and have drawer data flag */
+    infoP->version     = ( dobj.version[0]     <<  8 ) +
+        ( dobj.version[1]           );
+    infoP->drawerData  = ( dobj.pDrawerData[0] << 24 ) +
+        ( dobj.pDrawerData[1] << 16 ) +
+        ( dobj.pDrawerData[2] <<  8 ) +
+        ( dobj.pDrawerData[3]       ) ? TRUE : FALSE;
+}
+
+
+
+static void 
+getIconHeader( IconInfo * const infoP ) {
+/*-------------------------------------------------------------------------
+ * Get fields from icon header portion of info file
+ *-------------------------------------------------------------------------*/
+    IconHeader  ihead;      /* Icon header structure */
+    size_t      bytesRead;
+
+    /* Read icon header */
+    bytesRead = fread( &ihead, 1, sizeof(ihead), infoP->fp );
+    if ( ferror(infoP->fp ) )
+         pm_error( "Cannot read icon header for file '%s'.  "
+                   "fread() errno = %d (%s)",
+                   infoP->name, errno, strerror( errno ) );
+    else if ( bytesRead != sizeof(ihead) )
+        pm_error( "Cannot read the entire icon header for file '%s'.  "
+                  "Only read 0x%X of 0x%X bytes",
+                  infoP->name, bytesRead, sizeof(ihead) );
+
+    /* Get icon width, heigh, and bitplanes */
+    infoP->width  = ( ihead.iconWidth[0]  << 8 ) + ihead.iconWidth[1];
+    infoP->height = ( ihead.iconHeight[0] << 8 ) + ihead.iconHeight[1];
+    infoP->depth  = ( ihead.bpp[0]        << 8 ) + ihead.bpp[1];
+
+    /* Check number of bit planes */
+    if ( ( infoP->depth > 2 ) || ( infoP->depth < 1 ) )
+        pm_error( "We don't know how to interpret %u bitplanes file '%s'.  ",
+                  infoP->depth, infoP->name );
+}
+
+
+
+static void
+addBitplane(unsigned char * const icon,
+            unsigned int    const bpsize,
+            unsigned char * const buff) {
+/*----------------------------------------------------------------------------
+   Add bitplane to existing icon image
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+    unsigned int j;
+    
+    for (i = j = 0; i < bpsize; ++i, j += 8) {
+        icon[j+0] = (icon[j+0] << 1) | ((buff[i] >> 0) & 0x01);
+        icon[j+1] = (icon[j+1] << 1) | ((buff[i] >> 1) & 0x01);
+        icon[j+2] = (icon[j+2] << 1) | ((buff[i] >> 2) & 0x01);
+        icon[j+3] = (icon[j+3] << 1) | ((buff[i] >> 3) & 0x01);
+        icon[j+4] = (icon[j+4] << 1) | ((buff[i] >> 4) & 0x01);
+        icon[j+5] = (icon[j+5] << 1) | ((buff[i] >> 5) & 0x01);
+        icon[j+6] = (icon[j+6] << 1) | ((buff[i] >> 6) & 0x01);
+        icon[j+7] = (icon[j+7] << 1) | ((buff[i] >> 7) & 0x01);
+    }
+}
+
+
+
+static void
+readIconData(FILE *           const fileP,
+             unsigned int     const width,
+             unsigned int     const height, 
+             unsigned int     const depth,
+             unsigned char ** const iconP) {
+/*-------------------------------------------------------------------------
+ * Read icon data from file
+ *-------------------------------------------------------------------------*/
+    int             bitplane; /* Bitplane index */
+    unsigned char * buff;     /* Buffer to hold bits for 1 bitplane */
+    unsigned char * icon;
+
+    unsigned int const bpsize = height * (((width + 15) / 16) * 2);
+        /* Bitplane size in bytes, with padding */
+
+  
+    MALLOCARRAY(buff, bpsize);
+    if ( buff == NULL )
+        pm_error( "Cannot allocate memory to hold icon pixels" );
+
+    MALLOCARRAY(icon, bpsize * 8);
+    if (icon == NULL)
+        pm_error( "Cannot allocate memory to hold icon" );
+
+    /* Initialize to zero */
+    memset(buff, 0, bpsize);
+    memset(icon, 0, bpsize * 8);
+
+    /* Each bitplane is stored independently in the icon file.  This
+     * loop reads one bitplane at a time into buff.  Since fread() may
+     * not read all of the bitplane on the first call, the inner loop
+     * continues until all bytes are read.  The buffer pointer, bp,
+     * points to the next byte in buff to fill in.  When the inner
+     * loop is done, bp points to the end of buff.
+     *
+     * After reading in the entire bitplane, the second inner loop splits the 
+     * eight pixels in each byte of the bitplane into eight separate bytes in 
+     * the icon buffer.  The existing contents of each byte in icon are left 
+     * shifted by one to make room for the next bit. 
+     *
+     * Each byte in the completed icon contains a value from 0 to
+     * 2^depth (0 to 1 for depth of 1 and 0 to 3 for a depth of 3).
+     * This is an index into the colors array in the info struct.  */
+
+    for (bitplane = 0; bitplane < depth; ++bitplane) {
+        /* Read bitplane into buffer */
+        int toread;   /* Number of bytes left to read */
+        unsigned char * buffp;    /* Buffer point for reading data */
+       
+        toread = bpsize; buffp = &buff[0];
+
+        while (toread > 0) {
+            size_t bytesRead;
+
+            bytesRead = fread(buffp, 1, toread, fileP);
+            if (ferror(fileP))
+                pm_error("Cannot read from file info file.  "
+                         "fread() errno = %d (%s)",
+                         errno, strerror(errno));
+            else if (bytesRead == 0)
+                pm_error("Premature end-of-file.  "
+                         "Still have 0x%X bytes to read",
+                         toread );
+            
+            toread -= bytesRead;
+            buffp  += bytesRead;
+        }
+        addBitplane(icon, bpsize, buff);
+    }
+    *iconP = icon;
+
+    free(buff);
+}
+
+
+
+static void
+writeIconData( IconInfo *   const infoP,
+               struct pam * const pamP ) {
+/*-------------------------------------------------------------------------
+ * Write icon data to file
+ *-------------------------------------------------------------------------*/
+    unsigned int const bpwidth = ( ( infoP->width + 15 ) / 16 ) * 16;
+        /* Bitplane width; Width of each row in icon, including padding */
+
+    tuple * row;      /* Output row */
+
+    /* Allocate row */
+    row = pnm_allocpamrow( pamP );
+
+    /* Write icon image to output file */
+    /* Put if check outside for loop to reduce number of times check is made */
+    if ( infoP->depth == 1 ) {
+        if ( infoP->forceColor ) {
+            /* Convert 1 bitplane icon into color PAM */
+            unsigned int i;
+            for ( i = 0; i < infoP->height; ++i ) {
+                unsigned int j;
+                for ( j = 0; j < infoP->width; ++j ) {
+                    /* 1 is black and 0 is white */
+                    unsigned int colorIdx =
+                        infoP->icon[ i * bpwidth + j ] ? 2 : 1;
+                    row[j][PAM_RED_PLANE] =
+                        PPM_GETR( infoP->colors[colorIdx] );
+                    row[j][PAM_GRN_PLANE] =
+                        PPM_GETG( infoP->colors[colorIdx] );
+                    row[j][PAM_BLU_PLANE] =
+                        PPM_GETB( infoP->colors[colorIdx] );
+                }
+                pnm_writepamrow( pamP, row );
+            }
+        } else {
+            /* Convert 1 bitplane icon into bitmap PAM */
+            unsigned int i;
+            for ( i = 0; i < infoP->height; ++i ) {
+                unsigned int j;
+                for ( j = 0; j < infoP->width; j++ ) {
+                    /* 1 is black and 0 is white */
+                    row[j][0] = infoP->icon[ i * bpwidth + j ] ? 0 : 1;
+                }
+                pnm_writepamrow( pamP, row );
+            }
+        }
+    } else {
+        /* Convert color icon into color PAM */
+        unsigned int i;
+        for ( i = 0; i < infoP->height; ++i ) {
+            unsigned int j;
+            for ( j = 0; j < infoP->width; ++j ) {
+                unsigned int const colorIdx = infoP->icon[ i * bpwidth + j ];
+                row[j][PAM_RED_PLANE] = PPM_GETR( infoP->colors[colorIdx] );
+                row[j][PAM_GRN_PLANE] = PPM_GETG( infoP->colors[colorIdx] );
+                row[j][PAM_BLU_PLANE] = PPM_GETB( infoP->colors[colorIdx] );
+            }
+            pnm_writepamrow( pamP, row );
+        }
+    }
+
+    /* Clean up allocated memory */
+    pnm_freepamrow( row );
+}
+
+
+
+int
+main( int argc,
+      char *argv[] ) {
+
+    IconInfo    info;    /* Miscellaneous icon information */
+    struct pam  pam;     /* PAM header */
+    int         skip;    /* Bytes to skip to read next icon header */
+
+    /* Init PNM library */
+    pnm_init( &argc, argv );
+
+    /* Parse command line arguments */
+    parseCommandLine( argc, argv, &info );
+
+    /* Open input file */
+    info.fp = pm_openr( info.name );
+
+    /* Read disk object header */
+    getDiskObject( &info );
+
+    /* Skip drawer data, if any */
+    if ( info.drawerData ) {
+        skip = 56;   /* Draw data size */
+        if ( fseek( info.fp, skip, SEEK_CUR ) < 0 )
+            pm_error( "Cannot skip header information in file '%s'.  "
+                      "fseek() errno = %d (%s)",
+                      info.name, errno, strerror( errno ) );
+    }
+
+    /* Get dimensions for first icon */
+    getIconHeader( &info );
+
+    /* Skip ahead to next header if converting second icon */
+    if ( info.selected ) {
+        skip = info.height * ( ( ( info.width + 15 ) / 16 ) * 2 ) * info.depth;
+
+        if ( fseek( info.fp, skip, SEEK_CUR ) < 0 )
+            pm_error( "Cannot skip to next icon in file '%s'.  "
+                      "fseek() errno = %d (%s)",
+                      info.name, errno, strerror( errno ) );
+
+        /* Get dimensions for second icon */
+        getIconHeader( &info );
+    }
+
+    /* Read icon data */
+    readIconData( info.fp, info.width, info.height, info.depth, &info.icon );
+
+    /* Print icon info */
+    pm_message( "converting %s, version %d, %s icon: %d X %d X %d",
+                info.name, info.version, info.selected ? "second" : "first",
+                info.width, info.height, info.depth );
+
+    /* Write PAM header */
+    pam.size   = sizeof( pam );
+    pam.len    = PAM_STRUCT_SIZE( tuple_type );
+    pam.file   = stdout;
+    pam.height = info.height;
+    pam.width  = info.width;
+    pam.format = PAM_FORMAT;
+
+    if ( ( info.depth == 1 ) && ( info.forceColor == FALSE ) ) {
+        pam.depth  = 1;
+        pam.maxval = 1;
+        strcpy( pam.tuple_type, "BLACKANDWHITE" );
+    } else {
+        pam.depth  = 3;
+        pam.maxval = 0xFF;
+        strcpy( pam.tuple_type, "RGB" );
+    }
+    pnm_writepaminit( &pam );
+
+    /* Write icon data */
+    writeIconData( &info, &pam );
+
+    free( info.icon );
+
+    /* Close input file and return */
+    pm_close( pam.file );
+    pm_close( info.fp );
+
+    return 0;
+}
diff --git a/converter/other/jbig/ANNOUNCE b/converter/other/jbig/ANNOUNCE
new file mode 100644
index 00000000..edbcc3f8
--- /dev/null
+++ b/converter/other/jbig/ANNOUNCE
@@ -0,0 +1,243 @@
+
+Version 1.2 of the JBIG-KIT lossless image compression library available
+------------------------------------------------------------------------
+
+Markus Kuhn -- 2000-04-08
+
+
+The latest release of JBIG-KIT can be downloaded over the Internet
+with anonymous ftp from
+
+  ftp://ftp.informatik.uni-erlangen.de/pub/doc/ISO/JBIG/jbigkit-1.2.tar.gz
+  http://www.cl.cam.ac.uk/~mgk25/download/jbigkit-1.2.tar.gz
+
+and from a number of other servers.
+
+JBIG-KIT implements a highly effective data compression algorithm for
+bi-level high-resolution images such as fax pages or scanned
+documents.
+
+JBIG-KIT provides a portable library of compression and decompression
+functions with a documented interface that you can very easily include
+into your image or document processing software. In addition, JBIG-KIT
+provides ready-to-use compression and decompression programs with a
+simple command line interface (similar to the converters found in Jef
+Poskanzer's PBM graphics file conversion package).
+
+JBIG-KIT implements the specification
+
+  International Standard ISO/IEC 11544:1993 and ITU-T Recommendation
+  T.82(1993), "Information technology - Coded representation of picture
+  and audio information - progressive bi-level image compression",
+  <http://www.itu.ch/itudoc/itu-t/rec/t/t82_23822.html>,
+
+which is commonly referred to as the "JBIG standard". JBIG (Joint
+Bi-level Image experts Group) is the committee which developed this
+international standard for the lossless compression of images using
+arithmetic coding. Like the well-known compression algorithms JPEG and
+MPEG, also JBIG has been developed and published by the International
+Organization for Standardization (ISO) and the International
+Telecommunication Union (ITU). See also
+
+  http://www.jpeg.org/public/jbighomepage.htm
+  http://www.iso.ch/
+  http://www.itu.ch/
+
+The JBIG compression algorithm offers the following features:
+
+  - Close to state-of-the-art lossless compression ratio for high
+    resolution bi-level images.
+
+  - Around 1.1 to 1.5 times better compression ratio on typical
+    scanned documents compared to G4 fax compression (ITU-T T.6),
+    which has been the best compression algorithm for scanned
+    documents available prior to JBIG.
+
+  - Up to 30 times better compression of scanned images with dithered
+    images compared to G4 fax compression.
+
+  - Around 2 times better compression on typical 300 dpi documents
+    compared to 'gzip -9' on raw bitmaps.
+    
+  - Around 3-4 times better compression than GIF on typical 300 dpi
+    documents.
+
+  - Even much better competitive compression results on computer
+    generated images which are free of scanning distortions.
+
+  - JBIG supports hierarchical "progressive" encoding, that means it is
+    possible to encode a low resolution image first, followed by
+    resolution enhancement data. This allows for instance a document
+    browser to display already a good 75 dpi low resolution version of
+    an image, while the data necessary to reconstruct the full 300 dpi
+    version for laser printer reproduction is still arriving (say
+    over a slow network link or mass storage medium).
+
+  - The various resolution layers of a JBIG image in progressive
+    encoding mode together require not much more space than a
+    normal non-progressive mode encoded image (which JBIG also
+    supports).
+
+  - The progressive encoding mode utilizes a very sophisticated
+    resolution reduction algorithm which offers highest quality
+    low resolution versions that preserve the shape of characters as
+    well as the integrity of thin lines and dithered images.
+
+  - JBIG supports multiple bit planes and can this way also be used
+    for greyscale and color images, although the main field of
+    application is compression of bi-level images, i.e. images with
+    only two different pixel values. For greyscale images with up to
+    6 bit per pixel, JBIG performs superior to JPEG's lossless
+    mode.
+
+JBIG-KIT is free software under the GNU General Public License. For
+other license arrangements contact the author. JBIG-KIT provides a
+portable library implemented in ANSI/ISO C for encoding and decoding
+JBIG data streams together with documentation. The library is not
+intended for 8-bit or 16-bit machine architectures (e.g., old MS-DOS C
+compilers) on which a number of very efficient optimization techniques
+used in this software are not possible. For maximum performance, a
+32-bit processor is required (64-bit systems work too, of course). On
+architectures with 16-bit pointer arithmetic, only very small images
+can be processed.
+
+Special features of the JBIG-KIT implementation are:
+
+  - Fully reentrant multithread-capable design (no global or static
+    variables, isolated malloc()/free() calls, etc.).
+
+  - Capable of handling incomplete and growing JBIG data streams in
+    order to allow earliest display of low resolution versions.
+
+  - Capable of handling several incoming data streams simultaneously
+    in one single process and task.
+
+  - Especially designed with applications in mind that want to display
+    incoming data as early as possible (e.g., similar to the way in
+    which Netscape Navigator handles incoming GIF images).
+
+  - Implements all JBIG features and options including progressive and
+    sequential encoding, multiple bit planes, user specified
+    resolution reduction and deterministic prediction tables, adaptive
+    template changes for optimal performance on half-tone images,
+    deterministic prediction, typical prediction in lowest and
+    differential layers, various stripe orderings, etc. Only the SEQ
+    and HITOLO options are currently not supported by the decoder
+    (they are normally never required, but could be added later in
+    case of user requirements).
+
+  - Efficient code, optimized utilization of 32-bit processor
+    registers.
+
+  - Very easy to use documented C library interface.
+
+  - Included Gray code conversion routines for efficient encoding
+    of greyscale images.
+
+  - Ready-to-use pbmtojbg and jbgtopbm converters.
+
+
+Changes in version 1.2 (2000-04-08):
+
+  - bug in the decoder fixed, which caused the rest of the input file
+    to be skipped whenever a comment marker was encountered (special
+    thanks to Ben Rudiak-Gould <benrg@math.berkeley.edu> for
+    reporting this one)
+
+Changes in version 1.1 (1999-11-16):
+
+  - serious bug in the encoder fixed, which for a very small
+    percentage of images has caused an unterminated linked list to be
+    created internally that could have been responsible for
+    segmentation violations or non-terminating encoders
+    (special thanks to Hisashi Saiga <saiga@itl.tnr.sharp.co.jp> for
+    tracking that one down)
+
+  - minor bug in the "jbgtopbm -d" diagnostic output fixed
+
+Changes in version 1.0 (1998-04-11):
+
+  - two bugs fixed that caused the encoder and decoder to fail
+    under certain modes of operation with several bit planes
+
+  - added new functions jbg_split_planes(), jbg_dec_merge_planes(),
+    and jbg_dec_getsize_merged() for easy handling of greyscale
+    images
+
+  - added support for compressing greyscale PGM files to pbmtojbg
+    and jbgtopbm
+
+  - more changes to avoid paranoid compiler warnings
+
+Changes in version 0.9 (1996-01-09):
+
+  - encoder won't break any more on input bitmap data with incorrect
+    zero padding
+
+  - pbmtojbg displays a warning if input file has incorrect zero
+    padding
+
+  - various minor improvements suggested by Stefan Willer
+    <Stefan.Willer@unnet.wupper.DE>
+
+  - many minor changes in order to avoid warnings from paranoid
+    compilers
+
+Changes in version 0.8 (1995-09-20):
+
+  - namespace cleared up, all names externally visible from the library
+    start now with jbg_ or JBG_
+
+  - minor non-critical bug fixed which caused library to fail compatibility
+    test and showed up especially on DEC Alpha systems
+
+  - jbg_dec_gethight() is now called jbg_dec_getheight()
+
+  - filenames conform now to MS-DOS limits
+
+  - Bug in pbmtojbg fixed (handling of ASCII PBM files)
+
+Changes in version 0.7 (1995-06-10):
+
+  - more problems on 16-bit int systems and on Macintosh systems fixed
+    (special thanks to Jean-Pierre Gachen <jpg11@calvanet.calvacom.fr>)
+
+  - global Makefile
+
+Changes in version 0.6 (1995-06-08):
+
+  - memory leak fixed
+
+  - should now also work on systems where int is only 16-bit large
+
+  - changes of the JBIG "Technical Corrigendum 1" included (special
+    thanks to Dr. Sebestyen from Siemens AG for sending me a copy
+    of the draft)
+
+First release: version 0.5 (1995-05-28)
+
+
+Please send all questions, problem reports, patches, suggestions,
+success stories, comments, etc. to
+
+  mkuhn at acm.org
+
+I will try to provide free support and maintenance for this software
+at least for the next few months depending on my available time.
+
+Y2K statement: JBIG-KIT does not handle any date and time related
+data, therefore if JBIG-KIT causes you any problems related to date
+and time overflows, this would indeed be most surprising.
+
+This library has been published in the hope that it will encourage the
+development of good freely available scanned document handling and
+transmission systems for the Internet so that large amounts of scanned
+text can be made available to the global village easily.
+
+Happy compressing ...
+
+Markus Kuhn
+
+--
+Markus G. Kuhn, Security Group, Computer Lab, Cambridge University, UK
+email: mkuhn at acm.org,  home page: <http://www.cl.cam.ac.uk/~mgk25/>
diff --git a/converter/other/jbig/Makefile b/converter/other/jbig/Makefile
new file mode 100644
index 00000000..ca98ef29
--- /dev/null
+++ b/converter/other/jbig/Makefile
@@ -0,0 +1,47 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/other/jbig
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+LIBJBIG_OBJECTS = jbig.o jbig_tab.o
+
+INCLUDES =
+ifneq ($(JBIGHDR_DIR),NONE)
+  INCLUDES += -I$(JBIGHDR_DIR)
+endif
+
+ifneq ($(JBIGHDR_DIR),NONE)
+  ifneq ($(JBIGLIB),NONE)
+    BINARIES = jbigtopnm pnmtojbig
+  endif
+endif
+
+SCRIPTS =
+
+ifeq ($(JBIGLIB),$(BUILDDIR)/$(SUBDIR)/libjbig.a)
+  JBIGLIB_DEP = $(JBIGLIB)
+else
+  # It's not our internal version; user's on his own to make sure it's built
+endif
+
+OBJECTS = $(BINARIES:%=%.o) $(LIBJBIG_OBJECTS)
+MERGE_OBJECTS = $(BINARIES:%=%.o2) $(LIBJBIG_OBJECTS)
+
+all: $(BINARIES)
+
+include $(SRCDIR)/Makefile.common
+
+$(BINARIES): %: %.o $(JBIGLIB_DEP) $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $< \
+	  $(shell $(LIBOPT) $(NETPBMLIB) $(JBIGLIB)) $(MATHLIB) $(LDLIBS) \
+	  $(RPATH) $(LADD)
+
+
+$(BUILDDIR)/$(SUBDIR)/libjbig.a: $(LIBJBIG_OBJECTS)
+	$(AR) -rc $@ $^
+	$(RANLIB) $@
+
diff --git a/converter/other/jbig/README.Netpbm b/converter/other/jbig/README.Netpbm
new file mode 100644
index 00000000..3d593b92
--- /dev/null
+++ b/converter/other/jbig/README.Netpbm
@@ -0,0 +1,12 @@
+The jbig tools are derived from the JBIG-KIT package by Marcus Kuhn,
+by Bryan Henderson on 2000.05.11.
+
+The file ANNOUNCE in this directory is from that package and gives
+details.
+
+The Netpbm tools jbigtopbm and pbmtojbig were adapted from JBIG-KIT's
+jbgtopbm and pbmtojbg.  The main difference is that the Netpbm
+versions use the Netpbm libraries.
+
+The jbig.c and jbig_table.c modules are straight from the JBIG_KIT 
+package.  They are what normally are packaged as libjbig.a.
diff --git a/converter/other/jbig/jbig.c b/converter/other/jbig/jbig.c
new file mode 100644
index 00000000..ebd7c08f
--- /dev/null
+++ b/converter/other/jbig/jbig.c
@@ -0,0 +1,2905 @@
+/*
+ *  Portable Free JBIG image compression library
+ *
+ *  Markus Kuhn -- mkuhn@acm.org
+ *
+ *  $Id: jbig.c,v 1.12 2000-04-08 11:42:18+01 mgk25 Rel $
+ *
+ *  This module implements a portable standard C encoder and decoder
+ *  using the JBIG lossless bi-level image compression algorithm as
+ *  specified in International Standard ISO 11544:1993 or equivalently
+ *  as specified in ITU-T Recommendation T.82. See the file jbig.doc
+ *  for usage instructions and application examples.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * 
+ *  If you want to use this program under different license conditions,
+ *  then contact the author for an arrangement.
+ *
+ *  It is possible that certain products which can be built using this
+ *  software module might form inventions protected by patent rights in
+ *  some countries (e.g., by patents about arithmetic coding algorithms
+ *  owned by IBM and AT&T in the USA). Provision of this software by the
+ *  author does NOT include any licences for any patents. In those
+ *  countries where a patent licence is required for certain applications
+ *  of this software module, you will have to obtain such a licence
+ *  yourself.
+ */
+
+#ifdef DEBUG
+#include <stdio.h>
+#endif
+
+#include <stdlib.h>
+#include <assert.h>
+
+#include "jbig.h"
+
+
+/* optional export of arithmetic coder functions for test purposes */
+#ifdef TEST_CODEC
+#define ARITH
+#define ARITH_INL
+#else
+#define ARITH      static
+#ifdef __GNUC__
+#define ARITH_INL  static __inline__
+#else
+#define ARITH_INL  static
+#endif
+#endif
+
+#define MX_MAX  23     /* maximal supported mx offset for
+			* adaptive template in the encoder */
+
+#define TPB2CX  0x195  /* contexts for TP special pixels */
+#define TPB3CX  0x0e5
+#define TPDCX   0xc3f
+
+/* marker codes */
+#define MARKER_STUFF    0x00
+#define MARKER_RESERVE  0x01
+#define MARKER_SDNORM   0x02
+#define MARKER_SDRST    0x03
+#define MARKER_ABORT    0x04
+#define MARKER_NEWLEN   0x05
+#define MARKER_ATMOVE   0x06
+#define MARKER_COMMENT  0x07
+#define MARKER_ESC      0xff
+
+/* loop array indices */
+#define STRIPE  0
+#define LAYER   1
+#define PLANE   2
+
+/* special jbg_buf pointers (instead of NULL) */
+#define SDE_DONE ((struct jbg_buf *) -1)
+#define SDE_TODO ((struct jbg_buf *) 0)
+
+/* object code version id */
+
+const char jbg_version[] = 
+" JBIG-KIT " JBG_VERSION " -- Markus Kuhn -- "
+"$Id: jbig.c,v 1.12 2000-04-08 11:42:18+01 mgk25 Rel $ ";
+
+/*
+ * the following array specifies for each combination of the 3
+ * ordering bits, which ii[] variable represents which dimension
+ * of s->sde.
+ */
+static const int index[8][3] = {
+  { 2, 1, 0 },    /* no ordering bit set */
+  { -1, -1, -1},  /* SMID -> illegal combination */
+  { 2, 0, 1 },    /* ILEAVE */
+  { 1, 0, 2 },    /* SMID + ILEAVE */
+  { 0, 2, 1 },    /* SEQ */
+  { 1, 2, 0 },    /* SEQ + SMID */
+  { 0, 1, 2 },    /* SEQ + ILEAVE */
+  { -1, -1, -1 }  /* SEQ + SMID + ILEAVE -> illegal combination */
+};
+
+
+/*
+ * Array [language][message] with text string error messages that correspond
+ * to return values from public functions in this library.
+ */
+#define NEMSG         9  /* number of error codes */
+#define NEMSG_LANG    3  /* number of supported languages */
+static const char *errmsg[NEMSG_LANG][NEMSG] = {
+  /* English (JBG_EN) */
+  {
+    "Everything is ok",                                     /* JBG_EOK */
+    "Reached specified maximum size",                       /* JBG_EOK_INTR */
+    "Unexpected end of data",                               /* JBG_EAGAIN */
+    "Not enough memory available",                          /* JBG_ENOMEM */
+    "ABORT marker found",                                   /* JBG_EABORT */
+    "Unknown marker segment encountered",                   /* JBG_EMARKER */
+    "Incremental BIE does not fit to previous one",         /* JBG_ENOCONT */
+    "Invalid data encountered",                             /* JBG_EINVAL */
+    "Unimplemented features used"                           /* JBG_EIMPL */
+  },
+  /* German (JBG_DE_8859_1) */
+  {
+    "Kein Problem aufgetreten",                             /* JBG_EOK */
+    "Angegebene maximale Bildgr\366\337e erreicht",         /* JBG_EOK_INTR */
+    "Unerwartetes Ende der Daten",                          /* JBG_EAGAIN */
+    "Nicht gen\374gend Speicher vorhanden",                 /* JBG_ENOMEM */
+    "Es wurde eine Abbruch-Sequenz gefunden",               /* JBG_EABORT */
+    "Eine unbekannte Markierungssequenz wurde gefunden",    /* JBG_EMARKER */
+    "Neue Daten passen nicht zu vorangegangenen Daten",     /* JBG_ENOCONT */
+    "Es wurden ung\374ltige Daten gefunden",                /* JBG_EINVAL */
+    "Noch nicht implementierte Optionen wurden benutzt"     /* JBG_EIMPL */
+  },
+  /* German (JBG_DE_UTF_8) */
+  {
+    "Kein Problem aufgetreten",                             /* JBG_EOK */
+    "Angegebene maximale Bildgr\303\266\303\237e erreicht", /* JBG_EOK_INTR */
+    "Unerwartetes Ende der Daten",                          /* JBG_EAGAIN */
+    "Nicht gen\303\274gend Speicher vorhanden",             /* JBG_ENOMEM */
+    "Es wurde eine Abbruch-Sequenz gefunden",               /* JBG_EABORT */
+    "Eine unbekannte Markierungssequenz wurde gefunden",    /* JBG_EMARKER */
+    "Neue Daten passen nicht zu vorangegangenen Daten",     /* JBG_ENOCONT */
+    "Es wurden ung\303\274ltige Daten gefunden",            /* JBG_EINVAL */
+    "Noch nicht implementierte Optionen wurden benutzt"     /* JBG_EIMPL */
+  }
+};
+
+
+
+/*
+ * The following three functions are the only places in this code, were
+ * C library memory management functions are called. The whole JBIG
+ * library has been designed in order to allow multi-threaded
+ * execution. no static or global variables are used, so all fuctions
+ * are fully reentrant. However if you want to use this multi-thread
+ * capability and your malloc, realloc and free are not reentrant,
+ * then simply add the necessary semaphores or mutex primitives below.
+ */
+
+static void *checked_malloc(size_t size)
+{
+  void *p;
+  
+  p = malloc(size);
+  /* Full manual exception handling is ugly here for performance
+   * reasons. If an adequate handling of lack of memory is required,
+   * then use C++ and throw a C++ exception here. */
+  if (!p)
+    abort();
+
+#if 0
+  fprintf(stderr, "%p = malloc(%ld)\n", p, (long) size);
+#endif
+
+  return p;
+}
+
+
+static void *checked_realloc(void *ptr, size_t size)
+{
+  void *p;
+
+  p = realloc(ptr, size);
+  /* Full manual exception handling is ugly here for performance
+   * reasons. If an adequate handling of lack of memory is required,
+   * then use C++ and throw a C++ exception here. */
+  if (!p)
+    abort();
+
+#if 0
+  fprintf(stderr, "%p = realloc(%p, %ld)\n", p, ptr, (long) size);
+#endif
+
+  return p;
+}
+
+
+static void checked_free(void *ptr)
+{
+  free(ptr);
+
+#if 0
+  fprintf(stderr, "free(%p)\n", ptr);
+#endif
+
+}
+
+
+
+/*
+ * The next functions implement the arithmedic encoder and decoder
+ * required for JBIG. The same algorithm is also used in the arithmetic
+ * variant of JPEG.
+ */
+
+#ifdef DEBUG
+static long encoded_pixels = 0;
+#endif
+
+ARITH void arith_encode_init(struct jbg_arenc_state *s, int reuse_st)
+{
+  int i;
+  
+  if (!reuse_st)
+    for (i = 0; i < 4096; s->st[i++] = 0);
+  s->c = 0;
+  s->a = 0x10000L;
+  s->sc = 0;
+  s->ct = 11;
+  s->buffer = -1;    /* empty */
+  
+  return;
+}
+
+
+ARITH void arith_encode_flush(struct jbg_arenc_state *s)
+{
+  unsigned long temp;
+
+#ifdef DEBUG
+  fprintf(stderr, "  encoded pixels = %ld, a = %05lx, c = %08lx\n",
+	  encoded_pixels, s->a, s->c);
+#endif
+
+  /* find the s->c in the coding interval with the largest
+   * number of trailing zero bits */
+  if ((temp = (s->a - 1 + s->c) & 0xffff0000L) < s->c)
+    s->c = temp + 0x8000;
+  else
+    s->c = temp;
+  /* send remaining bytes to output */
+  s->c <<= s->ct;
+  if (s->c & 0xf8000000L) {
+    /* one final overflow has to be handled */
+    if (s->buffer >= 0) {
+      s->byte_out(s->buffer + 1, s->file);
+      if (s->buffer + 1 == MARKER_ESC)
+	s->byte_out(MARKER_STUFF, s->file);
+    }
+    /* output 0x00 bytes only when more non-0x00 will follow */
+    if (s->c & 0x7fff800L)
+      for (; s->sc; --s->sc)
+	s->byte_out(0x00, s->file);
+  } else {
+    if (s->buffer >= 0)
+      s->byte_out(s->buffer, s->file); 
+    /* T.82 figure 30 says buffer+1 for the above line! Typo? */
+    for (; s->sc; --s->sc) {
+      s->byte_out(0xff, s->file);
+      s->byte_out(MARKER_STUFF, s->file);
+    }
+  }
+  /* output final bytes only if they are not 0x00 */
+  if (s->c & 0x7fff800L) {
+    s->byte_out((s->c >> 19) & 0xff, s->file);
+    if (((s->c >> 19) & 0xff) == MARKER_ESC)
+      s->byte_out(MARKER_STUFF, s->file);
+    if (s->c & 0x7f800L) {
+      s->byte_out((s->c >> 11) & 0xff, s->file);
+      if (((s->c >> 11) & 0xff) == MARKER_ESC)
+	s->byte_out(MARKER_STUFF, s->file);
+    }
+  }
+
+  return;
+}
+
+
+ARITH_INL void arith_encode(struct jbg_arenc_state *s, int cx, int pix) 
+{
+  extern short jbg_lsz[];
+  extern unsigned char jbg_nmps[], jbg_nlps[];
+  register unsigned lsz, ss;
+  register unsigned char *st;
+  long temp;
+
+#ifdef DEBUG
+  ++encoded_pixels;
+#endif
+
+  assert(cx >= 0 && cx < 4096);
+  st = s->st + cx;
+  ss = *st & 0x7f;
+  assert(ss < 113);
+  lsz = jbg_lsz[ss];
+
+#if 0
+  fprintf(stderr, "pix = %d, cx = %d, mps = %d, st = %3d, lsz = 0x%04x, "
+	  "a = 0x%05lx, c = 0x%08lx, ct = %2d, buf = 0x%02x\n",
+	  pix, cx, !!(s->st[cx] & 0x80), ss, lsz, s->a, s->c, s->ct,
+	  s->buffer);
+#endif
+
+  if (((pix << 7) ^ s->st[cx]) & 0x80) {
+    /* encode the less probable symbol */
+    if ((s->a -= lsz) >= lsz) {
+      /* If the interval size (lsz) for the less probable symbol (LPS)
+       * is larger than the interval size for the MPS, then exchange
+       * the two symbols for coding efficiency, otherwise code the LPS
+       * as usual: */
+      s->c += s->a;
+      s->a = lsz;
+    }
+    /* Check whether MPS/LPS exchange is necessary
+     * and chose next probability estimator status */
+    *st &= 0x80;
+    *st ^= jbg_nlps[ss];
+  } else {
+    /* encode the more probable symbol */
+    if ((s->a -= lsz) & 0xffff8000L)
+      return;   /* A >= 0x8000 -> ready, no renormalization required */
+    if (s->a < lsz) {
+      /* If the interval size (lsz) for the less probable symbol (LPS)
+       * is larger than the interval size for the MPS, then exchange
+       * the two symbols for coding efficiency: */
+      s->c += s->a;
+      s->a = lsz;
+    }
+    /* chose next probability estimator status */
+    *st &= 0x80;
+    *st |= jbg_nmps[ss];
+  }
+
+  /* renormalization of coding interval */
+  do {
+    s->a <<= 1;
+    s->c <<= 1;
+    --s->ct;
+    if (s->ct == 0) {
+      /* another byte is ready for output */
+      temp = s->c >> 19;
+      if (temp & 0xffffff00L) {
+	/* handle overflow over all buffered 0xff bytes */
+	if (s->buffer >= 0) {
+	  ++s->buffer;
+	  s->byte_out(s->buffer, s->file);
+	  if (s->buffer == MARKER_ESC)
+	    s->byte_out(MARKER_STUFF, s->file);
+	}
+	for (; s->sc; --s->sc)
+	  s->byte_out(0x00, s->file);
+	s->buffer = temp & 0xff;  /* new output byte, might overflow later */
+	assert(s->buffer != 0xff);
+	/* can s->buffer really never become 0xff here? */
+      } else if (temp == 0xff) {
+	/* buffer 0xff byte (which might overflow later) */
+	++s->sc;
+      } else {
+	/* output all buffered 0xff bytes, they will not overflow any more */
+	if (s->buffer >= 0)
+	  s->byte_out(s->buffer, s->file);
+	for (; s->sc; --s->sc) {
+	  s->byte_out(0xff, s->file);
+	  s->byte_out(MARKER_STUFF, s->file);
+	}
+	s->buffer = temp;   /* buffer new output byte (can still overflow) */
+      }
+      s->c &= 0x7ffffL;
+      s->ct = 8;
+    }
+  } while (s->a < 0x8000);
+ 
+  return;
+}
+
+
+ARITH void arith_decode_init(struct jbg_ardec_state *s, int reuse_st)
+{
+  int i;
+  
+  if (!reuse_st)
+    for (i = 0; i < 4096; s->st[i++] = 0);
+  s->c = 0;
+  s->a = 1;
+  s->ct = 0;
+  s->result = JBG_OK;
+  s->startup = 1;
+  return;
+}
+
+
+ARITH_INL int arith_decode(struct jbg_ardec_state *s, int cx)
+{
+  extern short jbg_lsz[];
+  extern unsigned char jbg_nmps[], jbg_nlps[];
+  register unsigned lsz, ss;
+  register unsigned char *st;
+  int pix;
+
+  /* renormalization */
+  while (s->a < 0x8000 || s->startup) {
+    if (s->ct < 1 && s->result != JBG_READY) {
+      /* first we have to move a new byte into s->c */
+      if (s->pscd_ptr >= s->pscd_end) {
+	s->result = JBG_MORE;
+	return -1;
+      }
+      if (*s->pscd_ptr == 0xff) 
+	if (s->pscd_ptr + 1 >= s->pscd_end) {
+	  s->result = JBG_MARKER;
+	  return -1;
+	} else {
+	  if (*(s->pscd_ptr + 1) == MARKER_STUFF) {
+	    s->c |= 0xffL << (8 - s->ct);
+	    s->ct += 8;
+	    s->pscd_ptr += 2;
+	    s->result = JBG_OK;
+	  } else
+	    s->result = JBG_READY;
+	}
+      else {
+	s->c |= (long)*(s->pscd_ptr++) << (8 - s->ct);
+	s->ct += 8;
+	s->result = JBG_OK;
+      }
+    }
+    s->c <<= 1;
+    s->a <<= 1;
+    --s->ct;
+    if (s->a == 0x10000L)
+      s->startup = 0;
+  }
+
+  st = s->st + cx;
+  ss = *st & 0x7f;
+  assert(ss < 113);
+  lsz = jbg_lsz[ss];
+
+#if 0
+  fprintf(stderr, "cx = %d, mps = %d, st = %3d, lsz = 0x%04x, a = 0x%05lx, "
+	  "c = 0x%08lx, ct = %2d\n",
+	  cx, !!(s->st[cx] & 0x80), ss, lsz, s->a, s->c, s->ct);
+#endif
+
+  if ((s->c >> 16) < (s->a -= lsz))
+    if (s->a & 0xffff8000L)
+      return *st >> 7;
+    else {
+      /* MPS_EXCHANGE */
+      if (s->a < lsz) {
+	pix = 1 - (*st >> 7);
+	/* Check whether MPS/LPS exchange is necessary
+	 * and chose next probability estimator status */
+	*st &= 0x80;
+	*st ^= jbg_nlps[ss];
+      } else {
+	pix = *st >> 7;
+	*st &= 0x80;
+	*st |= jbg_nmps[ss];
+      }
+    }
+  else {
+    /* LPS_EXCHANGE */
+    if (s->a < lsz) {
+      s->c -= s->a << 16;
+      s->a = lsz;
+      pix = *st >> 7;
+      *st &= 0x80;
+      *st |= jbg_nmps[ss];
+    } else {
+      s->c -= s->a << 16;
+      s->a = lsz;
+      pix = 1 - (*st >> 7);
+      /* Check whether MPS/LPS exchange is necessary
+       * and chose next probability estimator status */
+      *st &= 0x80;
+      *st ^= jbg_nlps[ss];
+    }
+  }
+
+  return pix;
+}
+
+
+
+/*
+ * Memory management for buffers which are used for temporarily
+ * storing SDEs by the encoder.
+ *
+ * The following functions manage a set of struct jbg_buf storage
+ * containers were each can keep JBG_BUFSIZE bytes. The jbg_buf
+ * containers can be linked to form linear double-chained lists for
+ * which a number of operations are provided. Blocks which are
+ * tempoarily not used any more are returned to a freelist which each
+ * encoder keeps. Only the destructor of the encoder actually returns
+ * the block via checked_free() to the stdlib memory management.
+ */
+
+
+/*
+ * Allocate a new buffer block and initialize it. Try to get it from
+ * the free_list, and if it is empty, call checked_malloc().
+ */
+static struct jbg_buf *jbg_buf_init(struct jbg_buf **free_list)
+{
+  struct jbg_buf *new_block;
+  
+  /* Test whether a block from the free list is available */
+  if (*free_list) {
+    new_block = *free_list;
+    *free_list = new_block->next;
+  } else {
+    /* request a new memory block */
+    new_block = (struct jbg_buf *) checked_malloc(sizeof(struct jbg_buf));
+  }
+  new_block->len = 0;
+  new_block->next = NULL;
+  new_block->previous = NULL;
+  new_block->last = new_block;
+  new_block->free_list = free_list;
+
+  return new_block;
+}
+
+
+/*
+ * Return an entire free_list to the memory management of stdlib.
+ * This is only done by jbg_enc_free().
+ */
+static void jbg_buf_free(struct jbg_buf **free_list)
+{
+  struct jbg_buf *tmp;
+  
+  while (*free_list) {
+    tmp = (*free_list)->next;
+    checked_free(*free_list);
+    *free_list = tmp;
+  }
+  
+  return;
+}
+
+
+/*
+ * Append a single byte to a single list that starts with the block
+ * *(struct jbg_buf *) head. The type of *head is void here in order to
+ * keep the interface of the arithmetic encoder gereric, which uses this
+ * function as a call-back function in order to deliver single bytes
+ * for a PSCD.
+ */
+static void jbg_buf_write(int b, void *head)
+{
+  struct jbg_buf *now;
+
+  now = ((struct jbg_buf *) head)->last;
+  if (now->len < JBG_BUFSIZE - 1) {
+    now->d[now->len++] = b;
+    return;
+  }
+  now->next = jbg_buf_init(((struct jbg_buf *) head)->free_list);
+  now->next->previous = now;
+  now->next->d[now->next->len++] = b;
+  ((struct jbg_buf *) head)->last = now->next;
+
+  return;
+}
+
+
+/*
+ * Remove any trailing zero bytes from the end of a linked jbg_buf list,
+ * however make sure that no zero byte is removed which directly
+ * follows a 0xff byte (i.e., keep MARKER_ESC MARKER_STUFF sequences
+ * intact). This function is used to remove any redundant final zero
+ * bytes from a PSCD.
+ */
+static void jbg_buf_remove_zeros(struct jbg_buf *head)
+{
+  struct jbg_buf *last;
+
+  while (1) {
+    /* remove trailing 0x00 in last block of list until this block is empty */
+    last = head->last;
+    while (last->len && last->d[last->len - 1] == 0)
+      last->len--;
+    /* if block became really empty, remove it in case it is not the
+     * only remaining block and then loop to next block */
+    if (last->previous && !last->len) {
+      head->last->next = *head->free_list;
+      *head->free_list = head->last;
+      head->last = last->previous;
+      head->last->next = NULL;
+    } else
+      break;
+  }
+
+  /*
+   * If the final non-zero byte is 0xff (MARKER_ESC), then we just have
+   * removed a MARKER_STUFF and we will append it again now in order
+   * to preserve PSCD status of byte stream.
+   */
+  if (head->last->len && head->last->d[head->last->len - 1] == MARKER_ESC)
+    jbg_buf_write(MARKER_STUFF, head);
+ 
+  return;
+}
+
+
+/*
+ * The jbg_buf list which starts with block *new_prefix is concatenated
+ * with the list which starts with block **start and *start will then point
+ * to the first block of the new list.
+ */
+static void jbg_buf_prefix(struct jbg_buf *new_prefix, struct jbg_buf **start)
+{
+  new_prefix->last->next = *start;
+  new_prefix->last->next->previous = new_prefix->last;
+  new_prefix->last = new_prefix->last->next->last;
+  *start = new_prefix;
+  
+  return;
+}
+
+
+/*
+ * Send the contents of a jbg_buf list that starts with block **head to
+ * the call back function data_out and return the blocks of the jbg_buf
+ * list to the freelist from which these jbg_buf blocks have been taken.
+ * After the call, *head == NULL.
+ */
+static void jbg_buf_output(struct jbg_buf **head,
+			void (*data_out)(unsigned char *start,
+					 size_t len, void *file),
+			void *file)
+{
+  struct jbg_buf *tmp;
+  
+  while (*head) {
+    data_out((*head)->d, (*head)->len, file);
+    tmp = (*head)->next;
+    (*head)->next = *(*head)->free_list;
+    *(*head)->free_list = *head;
+    *head = tmp;
+  }
+  
+  return;
+}
+
+
+/*
+ * Calculate y = ceil(x/2) applied n times. This function is used to
+ * determine the number of pixels per row or column after n resolution
+ * reductions. E.g. X[d-1] = jbg_ceil_half(X[d], 1) and X[0] =
+ * jbg_ceil_half(X[d], d) as defined in clause 6.2.3 of T.82.
+ */
+unsigned long jbg_ceil_half(unsigned long x, int n)
+{
+  unsigned long mask;
+  
+  mask = (1UL << n) - 1;     /* the lowest n bits are 1 here */
+  return (x >> n) + ((mask & x) != 0);
+}
+
+
+/*
+ * Initialize the status struct for the encoder.
+ */
+void jbg_enc_init(struct jbg_enc_state *s, unsigned long x, unsigned long y,
+                  int planes, unsigned char **p,
+                  void (*data_out)(unsigned char *start, size_t len,
+				   void *file),
+		  void *file)
+{
+  unsigned long l, lx;
+  int i;
+  size_t bufsize;
+
+  extern char jbg_resred[], jbg_dptable[];
+
+  s->xd = x;
+  s->yd = y;
+  s->planes = planes;
+  s->data_out = data_out;
+  s->file = file;
+
+  s->d = 0;
+  s->dl = 0;
+  s->dh = s->d;
+  s->l0 = jbg_ceil_half(s->yd, s->d) / 35;   /* 35 stripes/image */
+  while ((s->l0 << s->d) > 128)              /* but <= 128 lines/stripe */
+    --s->l0;
+  if (s->l0 < 2) s->l0 = 2;
+  s->mx = 8;
+  s->my = 0;
+  s->order = JBG_ILEAVE | JBG_SMID;
+  s->options = JBG_TPBON | JBG_TPDON | JBG_DPON;
+  s->dppriv = jbg_dptable;
+  s->res_tab = jbg_resred;
+  
+  s->highres = checked_malloc(planes * sizeof(int));
+  s->lhp[0] = p;
+  s->lhp[1] = checked_malloc(planes * sizeof(unsigned char *));
+  bufsize = ((jbg_ceil_half(x, 1) + 7) / 8) * jbg_ceil_half(y, 1);
+  for (i = 0; i < planes; i++) {
+    s->highres[i] = 0;
+    s->lhp[1][i] = checked_malloc(sizeof(unsigned char) * bufsize);
+  }
+  
+  s->free_list = NULL;
+  s->s = (struct jbg_arenc_state *) 
+    checked_malloc(s->planes * sizeof(struct jbg_arenc_state));
+  s->tx = (int *) checked_malloc(s->planes * sizeof(int));
+  lx = jbg_ceil_half(x, 1);
+  s->tp = (char *) checked_malloc(lx * sizeof(char));
+  for (l = 0; l < lx; s->tp[l++] = 2);
+  s->sde = NULL;
+
+  return;
+}
+
+
+/*
+ * This function selects the number of differential layers based on
+ * the maximum size requested for the lowest resolution layer. If
+ * possible, a number of differential layers is selected, which will
+ * keep the size of the lowest resolution layer below or equal to the
+ * given width x and height y. However not more than 6 differential
+ * resolution layers will be used. In addition, a reasonable value for
+ * l0 (height of one stripe in the lowest resolution layer) is
+ * selected, which obeys the recommended limitations for l0 in annex A
+ * and C of the JBIG standard. The selected number of resolution layers
+ * is returned. 
+ */
+int jbg_enc_lrlmax(struct jbg_enc_state *s, unsigned long x, 
+		   unsigned long y)
+{
+  for (s->d = 0; s->d < 6; s->d++)
+    if (jbg_ceil_half(s->xd, s->d) <= x && jbg_ceil_half(s->yd, s->d) <= y)
+      break;
+  s->dl = 0;
+  s->dh = s->d;
+
+  s->l0 = jbg_ceil_half(s->yd, s->d) / 35;  /* 35 stripes/image */
+  while ((s->l0 << s->d) > 128)             /* but <= 128 lines/stripe */
+    --s->l0;
+  if (s->l0 < 2) s->l0 = 2;
+
+  return s->d;
+}
+
+
+/*
+ * As an alternative to jbg_enc_lrlmax(), the following function allows
+ * to specify the number of layers directly. The stripe height and layer
+ * range is also adjusted automatically here.
+ */
+void jbg_enc_layers(struct jbg_enc_state *s, int d)
+{
+  if (d < 0 || d > 255)
+    return;
+  s->d  = d;
+  s->dl = 0;
+  s->dh = s->d;
+
+  s->l0 = jbg_ceil_half(s->yd, s->d) / 35;  /* 35 stripes/image */
+  while ((s->l0 << s->d) > 128)             /* but <= 128 lines/stripe */
+    --s->l0;
+  if (s->l0 < 2) s->l0 = 2;
+
+  return;
+}
+
+
+/*
+ * Specify the highest and lowest resolution layers which will be
+ * written to the output file. Call this function not before
+ * jbg_enc_layers() or jbg_enc_lrlmax(), because these two functions
+ * reset the lowest and highest resolution layer to default values.
+ * Negative values are ignored. The total number of layers is returned.
+ */
+int jbg_enc_lrange(struct jbg_enc_state *s, int dl, int dh)
+{
+  if (dl >= 0     && dl <= s->d) s->dl = dl;
+  if (dh >= s->dl && dh <= s->d) s->dh = dh;
+
+  return s->d;
+}
+
+
+/*
+ * The following function allows to specify the bits describing the
+ * options of the format as well as the maximum AT movement window and
+ * the number of layer 0 lines per stripes.
+ */
+void jbg_enc_options(struct jbg_enc_state *s, int order, int options,
+		     long l0, int mx, int my)
+{
+  if (order >= 0 && order <= 0x0f) s->order = order;
+  if (options >= 0) s->options = options;
+  if (l0 >= 0) s->l0 = l0;
+  if (mx >= 0 && my < 128) s->mx = mx;
+  if (my >= 0 && my < 256) s->my = my;
+
+  return;
+}
+
+
+/*
+ * This function actually does all the tricky work involved in producing
+ * a SDE, which is stored in the appropriate s->sde[][][] element
+ * for later output in the correct order.
+ */
+static void encode_sde(struct jbg_enc_state *s,
+		       long stripe, int layer, int plane)
+{
+  unsigned char *hp, *lp1, *lp2, *p0, *p1, *q1, *q2;
+  unsigned long hl, ll, hx, hy, lx, ly, hbpl, lbpl;
+  unsigned long line_h0 = 0, line_h1 = 0;
+  unsigned long line_h2, line_h3, line_l1, line_l2, line_l3;
+  struct jbg_arenc_state *se;
+  unsigned long i, j, y;
+  unsigned t;
+  int ltp, ltp_old, cx;
+  unsigned long c_all, c[MX_MAX + 1], cmin, cmax, clmin, clmax;
+  int tmax, at_determined;
+  int new_tx;
+  long new_tx_line = -1;
+  struct jbg_buf *new_jbg_buf;
+
+#ifdef DEBUG
+  static long tp_lines, tp_exceptions, tp_pixels, dp_pixels;
+  static long encoded_pixels;
+#endif
+
+  /* return immediately if this stripe has already been encoded */
+  if (s->sde[stripe][layer][plane] != SDE_TODO)
+    return;
+
+#ifdef DEBUG
+  if (stripe == 0)
+    tp_lines = tp_exceptions = tp_pixels = dp_pixels = encoded_pixels = 0;
+  fprintf(stderr, "encode_sde: s/d/p = %2ld/%2d/%2d\n",
+	  stripe, layer, plane);
+#endif
+
+  /* number of lines per stripe in highres image */
+  hl = s->l0 << layer;
+  /* number of lines per stripe in lowres image */
+  ll = hl >> 1;
+  /* current line number in highres image */
+  y = stripe * hl;
+  /* number of pixels in highres image */
+  hx = jbg_ceil_half(s->xd, s->d - layer);
+  hy = jbg_ceil_half(s->yd, s->d - layer);
+  /* number of pixels in lowres image */
+  lx = jbg_ceil_half(hx, 1);
+  ly = jbg_ceil_half(hy, 1);
+  /* bytes per line in highres and lowres image */
+  hbpl = (hx + 7) / 8;
+  lbpl = (lx + 7) / 8;
+  /* pointer to first image byte of highres stripe */
+  hp = s->lhp[s->highres[plane]][plane] + stripe * hl * hbpl;
+  lp2 = s->lhp[1 - s->highres[plane]][plane] + stripe * ll * lbpl;
+  lp1 = lp2 + lbpl;
+  
+  /* initialize arithmetic encoder */
+  se = s->s + plane;
+  arith_encode_init(se, stripe != 0);
+  s->sde[stripe][layer][plane] = jbg_buf_init(&s->free_list);
+  se->byte_out = jbg_buf_write;
+  se->file = s->sde[stripe][layer][plane];
+
+  /* initialize adaptive template movement algorithm */
+  c_all = 0;
+  for (t = 0; t <= s->mx; t++)
+    c[t] = 0;
+  if (stripe == 0)
+    s->tx[plane] = 0;
+  new_tx = -1;
+  at_determined = 0;  /* we haven't yet decided the template move */
+  if (s->mx == 0)
+    at_determined = 1;
+
+  /* initialize typical prediction */
+  ltp = 0;
+  if (stripe == 0)
+    ltp_old = 0;
+  else {
+    ltp_old = 1;
+    p1 = hp - hbpl;
+    if (y > 1) {
+      q1 = p1 - hbpl;
+      while (p1 < hp && (ltp_old = (*p1++ == *q1++)) != 0);
+    } else
+      while (p1 < hp && (ltp_old = (*p1++ == 0)) != 0);
+  }
+
+  if (layer == 0) {
+
+    /*
+     *  Encode lowest resolution layer
+     */
+
+    for (i = 0; i < hl && y < hy; i++, y++) {
+
+      /* check whether it is worth to perform an ATMOVE */
+      if (!at_determined && c_all > 2048) {
+	cmin = clmin = 0xffffffffL;
+	cmax = clmax = 0;
+	tmax = 0;
+	for (t = (s->options & JBG_LRLTWO) ? 5 : 3; t <= s->mx; t++) {
+	  if (c[t] > cmax) cmax = c[t];
+	  if (c[t] < cmin) cmin = c[t];
+	  if (c[t] > c[tmax]) tmax = t;
+	}
+	clmin = (c[0] < cmin) ? c[0] : cmin;
+	clmax = (c[0] > cmax) ? c[0] : cmax;
+	if (c_all - cmax < (c_all >> 3) &&
+	    cmax - c[s->tx[plane]] > c_all - cmax &&
+	    cmax - c[s->tx[plane]] > (c_all >> 4) &&
+	    /*                     ^ T.82 says here < !!! Typo ? */
+	    cmax - (c_all - c[s->tx[plane]]) > c_all - cmax &&
+	    cmax - (c_all - c[s->tx[plane]]) > (c_all >> 4) &&
+	    cmax - cmin > (c_all >> 2) &&
+	    (s->tx[plane] || clmax - clmin > (c_all >> 3))) {
+	  /* we have decided to perform an ATMOVE */
+	  new_tx = tmax;
+	  if (!(s->options & JBG_DELAY_AT)) {
+	    new_tx_line = i;
+	    s->tx[plane] = new_tx;
+	  }
+	}
+	at_determined = 1;
+      }
+      
+      /* typical prediction */
+      if (s->options & JBG_TPBON) {
+	ltp = 1;
+	p1 = hp;
+	if (y > 0) {
+	  q1 = hp - hbpl;
+	  while (q1 < hp && (ltp = (*p1++ == *q1++)) != 0);
+	} else
+	  while (p1 < hp + hbpl && (ltp = (*p1++ == 0)) != 0);
+	arith_encode(se, (s->options & JBG_LRLTWO) ? TPB2CX : TPB3CX,
+		     ltp == ltp_old);
+#ifdef DEBUG
+	tp_lines += ltp;
+#endif
+	ltp_old = ltp;
+	if (ltp) {
+	  /* skip next line */
+	  hp += hbpl;
+	  continue;
+	}
+      }
+
+      /*
+       * Layout of the variables line_h1, line_h2, line_h3, which contain
+       * as bits the neighbour pixels of the currently coded pixel X:
+       *
+       *          76543210765432107654321076543210     line_h3
+       *          76543210765432107654321076543210     line_h2
+       *  76543210765432107654321X76543210             line_h1
+       */
+      
+      line_h1 = line_h2 = line_h3 = 0;
+      if (y > 0) line_h2 = (long)*(hp - hbpl) << 8;
+      if (y > 1) line_h3 = (long)*(hp - hbpl - hbpl) << 8;
+      
+      /* encode line */
+      for (j = 0; j < hx; hp++) {
+	line_h1 |= *hp;
+	if (j < hbpl * 8 - 8 && y > 0) {
+	  line_h2 |= *(hp - hbpl + 1);
+	  if (y > 1)
+	    line_h3 |= *(hp - hbpl - hbpl + 1);
+	}
+	if (s->options & JBG_LRLTWO) {
+	  /* two line template */
+	  do {
+	    line_h1 <<= 1;  line_h2 <<= 1;  line_h3 <<= 1;
+	    if (s->tx[plane])
+	      arith_encode(se, (((line_h2 >> 10) & 0x3e0) |
+				((line_h1 >> (4 + s->tx[plane])) & 0x010) |
+				((line_h1 >>  9) & 0x00f)),
+			   (line_h1 >> 8) & 1);
+	    else
+	      arith_encode(se, (((line_h2 >> 10) & 0x3f0) |
+				((line_h1 >>  9) & 0x00f)),
+			   (line_h1 >> 8) & 1);
+#ifdef DEBUG
+	    encoded_pixels++;
+#endif
+	    /* statistics for adaptive template changes */
+	    if (!at_determined && j >= s->mx && j < hx-2) {
+	      c[0] += !(((line_h2 >> 6) ^ line_h1) & 0x100);
+	      for (t = 5; t <= s->mx; t++)
+		c[t] += !(((line_h1 >> t) ^ line_h1) & 0x100);
+	      ++c_all;
+	    }
+	  } while (++j & 7 && j < hx);
+	} else {
+	  /* three line template */
+	  do {
+	    line_h1 <<= 1;  line_h2 <<= 1;  line_h3 <<= 1;
+	    if (s->tx[plane]) 
+	      arith_encode(se, (((line_h3 >>  8) & 0x380) |
+				((line_h2 >> 12) & 0x078) |
+				((line_h1 >> (6 + s->tx[plane])) & 0x004) |
+				((line_h1 >>  9) & 0x003)),
+			   (line_h1 >> 8) & 1);
+	    else
+	      arith_encode(se, (((line_h3 >>  8) & 0x380) |
+				((line_h2 >> 12) & 0x07c) |
+				((line_h1 >>  9) & 0x003)),
+			   (line_h1 >> 8) & 1);
+#ifdef DEBUG
+	    encoded_pixels++;
+#endif
+	    /* statistics for adaptive template changes */
+	    if (!at_determined && j >= s->mx && j < hx-2) {
+	      c[0] += !(((line_h2 >> 6) ^ line_h1) & 0x100);
+	      for (t = 3; t <= s->mx; t++)
+		c[t] += !(((line_h1 >> t) ^ line_h1) & 0x100);
+	      ++c_all;
+	    }
+	  } while (++j & 7 && j < hx);
+	} /* if (s->options & JBG_LRLTWO) */
+      } /* for (j = ...) */
+    } /* for (i = ...) */
+
+  } else {
+
+    /*
+     *  Encode differential layer
+     */
+    
+    for (i = 0; i < hl && y < hy; i++, y++) {
+
+      /* check whether it is worth to perform an ATMOVE */
+      if (!at_determined && c_all > 2048) {
+	cmin = clmin = 0xffffffffL;
+	cmax = clmax = 0;
+	tmax = 0;
+	for (t = 3; t <= s->mx; t++) {
+	  if (c[t] > cmax) cmax = c[t];
+	  if (c[t] < cmin) cmin = c[t];
+	  if (c[t] > c[tmax]) tmax = t;
+	}
+	clmin = (c[0] < cmin) ? c[0] : cmin;
+	clmax = (c[0] > cmax) ? c[0] : cmax;
+	if (c_all - cmax < (c_all >> 3) &&
+	    cmax - c[s->tx[plane]] > c_all - cmax &&
+	    cmax - c[s->tx[plane]] > (c_all >> 4) &&
+	    /*                     ^ T.82 says here < !!! Typo ? */
+	    cmax - (c_all - c[s->tx[plane]]) > c_all - cmax &&
+	    cmax - (c_all - c[s->tx[plane]]) > (c_all >> 4) &&
+	    cmax - cmin > (c_all >> 2) &&
+	    (s->tx[plane] || clmax - clmin > (c_all >> 3))) {
+	  /* we have decided to perform an ATMOVE */
+	  new_tx = tmax;
+	  if (!(s->options & JBG_DELAY_AT)) {
+	    new_tx_line = i;
+	    s->tx[plane] = new_tx;
+	  }
+#ifdef DEBUG
+	  fprintf(stderr, "ATMOVE: line=%ld, tx=%d, c_all=%ld\n",
+		  i, new_tx, c_all);
+#endif
+	}
+	at_determined = 1;
+      }
+      
+      if ((i >> 1) >= ll - 1 || (y >> 1) >= ly - 1)
+	lp1 = lp2;
+
+      /* typical prediction */
+      if (s->options & JBG_TPDON && (i & 1) == 0) {
+	q1 = lp1; q2 = lp2;
+	p0 = p1 = hp;
+	if (i < hl - 1 && y < hy - 1)
+	  p0 = hp + hbpl;
+	if (y > 1)
+	  line_l3 = (long)*(q2 - lbpl) << 8;
+	else
+	  line_l3 = 0;
+	line_l2 = (long)*q2 << 8;
+	line_l1 = (long)*q1 << 8;
+	ltp = 1;
+	for (j = 0; j < lx && ltp; q1++, q2++) {
+	  if (j < lbpl * 8 - 8) {
+	    if (y > 1)
+	      line_l3 |= *(q2 - lbpl + 1);
+	    line_l2 |= *(q2 + 1);
+	    line_l1 |= *(q1 + 1);
+	  }
+	  do {
+	    if ((j >> 2) < hbpl) {
+	      line_h1 = *(p1++);
+	      line_h0 = *(p0++);
+	    }
+	    do {
+	      line_l3 <<= 1;
+	      line_l2 <<= 1;
+	      line_l1 <<= 1;
+	      line_h1 <<= 2;
+	      line_h0 <<= 2;
+	      cx = (((line_l3 >> 15) & 0x007) |
+		    ((line_l2 >> 12) & 0x038) |
+		    ((line_l1 >> 9)  & 0x1c0));
+	      if (cx == 0x000)
+		if ((line_h1 & 0x300) == 0 && (line_h0 & 0x300) == 0)
+		  s->tp[j] = 0;
+		else {
+		  ltp = 0;
+#ifdef DEBUG
+		  tp_exceptions++;
+#endif
+		}
+	      else if (cx == 0x1ff)
+		if ((line_h1 & 0x300) == 0x300 && (line_h0 & 0x300) == 0x300)
+		  s->tp[j] = 1;
+		else {
+		  ltp = 0;
+#ifdef DEBUG
+		  tp_exceptions++;
+#endif
+		}
+	      else
+		s->tp[j] = 2;
+	    } while (++j & 3 && j < lx);
+	  } while (j & 7 && j < lx);
+	} /* for (j = ...) */
+	arith_encode(se, TPDCX, !ltp);
+#ifdef DEBUG
+	tp_lines += ltp;
+#endif
+      }
+
+
+      /*
+       * Layout of the variables line_h1, line_h2, line_h3, which contain
+       * as bits the high resolution neighbour pixels of the currently coded
+       * highres pixel X:
+       *
+       *            76543210 76543210 76543210 76543210     line_h3
+       *            76543210 76543210 76543210 76543210     line_h2
+       *   76543210 76543210 7654321X 76543210              line_h1
+       *
+       * Layout of the variables line_l1, line_l2, line_l3, which contain
+       * the low resolution pixels near the currently coded pixel as bits.
+       * The lowres pixel in which the currently coded highres pixel is
+       * located is marked as Y:
+       *
+       *            76543210 76543210 76543210 76543210     line_l3
+       *            76543210 7654321Y 76543210 76543210     line_l2
+       *            76543210 76543210 76543210 76543210     line_l1
+       */
+      
+
+      line_h1 = line_h2 = line_h3 = line_l1 = line_l2 = line_l3 = 0;
+      if (y > 0) line_h2 = (long)*(hp - hbpl) << 8;
+      if (y > 1) {
+	line_h3 = (long)*(hp - hbpl - hbpl) << 8;
+	line_l3 = (long)*(lp2 - lbpl) << 8;
+      }
+      line_l2 = (long)*lp2 << 8;
+      line_l1 = (long)*lp1 << 8;
+      
+      /* encode line */
+      for (j = 0; j < hx; lp1++, lp2++) {
+	if ((j >> 1) < lbpl * 8 - 8) {
+	  if (y > 1)
+	    line_l3 |= *(lp2 - lbpl + 1);
+	  line_l2 |= *(lp2 + 1);
+	  line_l1 |= *(lp1 + 1);
+	}
+	do {
+
+	  assert(hp - (s->lhp[s->highres[plane]][plane] +
+		       (stripe * hl + i) * hbpl)
+		 == (ptrdiff_t) j >> 3);
+
+	  assert(lp2 - (s->lhp[1-s->highres[plane]][plane] +
+			(stripe * ll + (i>>1)) * lbpl)
+		 == (ptrdiff_t) j >> 4);
+
+	  line_h1 |= *(hp++);
+	  if (j < hbpl * 8 - 8) {
+	    if (y > 0) {
+	      line_h2 |= *(hp - hbpl);
+	      if (y > 1)
+		line_h3 |= *(hp - hbpl - hbpl);
+	    }
+	  }
+	  do {
+	    line_l1 <<= 1;  line_l2 <<= 1;  line_l3 <<= 1;
+	    if (ltp && s->tp[j >> 1] < 2) {
+	      /* pixel are typical and have not to be encoded */
+	      line_h1 <<= 2;  line_h2 <<= 2;  line_h3 <<= 2;
+#ifdef DEBUG
+	      do {
+		++tp_pixels;
+	      } while (++j & 1 && j < hx);
+#else
+	      j += 2;
+#endif
+	    } else
+	      do {
+		line_h1 <<= 1;  line_h2 <<= 1;  line_h3 <<= 1;
+
+		/* deterministic prediction */
+		if (s->options & JBG_DPON) {
+		  if ((y & 1) == 0) {
+		    if ((j & 1) == 0) {
+		      /* phase 0 */
+		      if (s->dppriv[((line_l3 >> 16) & 0x003) |
+				    ((line_l2 >> 14) & 0x00c) |
+				    ((line_h1 >> 5)  & 0x010) |
+				    ((line_h2 >> 10) & 0x0e0)] < 2) {
+#ifdef DEBUG
+			++dp_pixels;
+#endif
+			continue;
+		      }
+		    } else {
+		      /* phase 1 */
+		      if (s->dppriv[(((line_l3 >> 16) & 0x003) |
+				     ((line_l2 >> 14) & 0x00c) |
+				     ((line_h1 >> 5)  & 0x030) |
+				     ((line_h2 >> 10) & 0x1c0)) + 256] < 2) {
+#ifdef DEBUG
+			++dp_pixels;
+#endif
+			continue;
+		      }
+		    }
+		  } else {
+		    if ((j & 1) == 0) {
+		      /* phase 2 */
+		      if (s->dppriv[(((line_l3 >> 16) & 0x003) |
+				     ((line_l2 >> 14) & 0x00c) |
+				     ((line_h1 >> 5)  & 0x010) |
+				     ((line_h2 >> 10) & 0x0e0) |
+				     ((line_h3 >> 7) & 0x700)) + 768] < 2) {
+#ifdef DEBUG
+			++dp_pixels;
+#endif
+			continue;
+		      }
+		    } else {
+		      /* phase 3 */
+		      if (s->dppriv[(((line_l3 >> 16) & 0x003) |
+				     ((line_l2 >> 14) & 0x00c) |
+				     ((line_h1 >> 5)  & 0x030) |
+				     ((line_h2 >> 10) & 0x1c0) |
+				     ((line_h3 >> 7)  & 0xe00)) + 2816] < 2) {
+#ifdef DEBUG
+			++dp_pixels;
+#endif
+			continue;
+		      }
+		    }	
+		  }	
+		}
+
+		/* determine context */
+		if (s->tx[plane])
+		  cx = (((line_h1 >> 9)  & 0x003) |
+			((line_h1 >> (4 + s->tx[plane])) & 0x010) |
+			((line_h2 >> 13) & 0x00c) |
+			((line_h3 >> 11) & 0x020));
+		else
+		  cx = (((line_h1 >> 9)  & 0x003) |
+			((line_h2 >> 13) & 0x01c) |
+			((line_h3 >> 11) & 0x020));
+		if (j & 1)
+		  cx |= (((line_l2 >> 9)  & 0x0c0) |
+			 ((line_l1 >> 7)  & 0x300)) | (1UL << 10);
+		else
+		  cx |= (((line_l2 >> 10) & 0x0c0) |
+			 ((line_l1 >> 8)  & 0x300));
+		cx |= (y & 1) << 11;
+
+		arith_encode(se, cx, (line_h1 >> 8) & 1);
+#ifdef DEBUG
+		encoded_pixels++;
+#endif
+		
+		/* statistics for adaptive template changes */
+		if (!at_determined && j >= s->mx) {
+		  c[0] += !(((line_h2 >> 6) ^ line_h1) & 0x100);
+		  for (t = 3; t <= s->mx; t++)
+		    c[t] += !(((line_h1 >> t) ^ line_h1) & 0x100);
+		  ++c_all;
+		}
+		
+	      } while (++j & 1 && j < hx);
+	  } while (j & 7 && j < hx);
+	} while (j & 15 && j < hx);
+      } /* for (j = ...) */
+
+      /* low resolution pixels are used twice */
+      if ((i & 1) == 0) {
+	lp1 -= lbpl;
+	lp2 -= lbpl;
+      }
+      
+    } /* for (i = ...) */
+  }
+  
+  arith_encode_flush(se);
+  jbg_buf_remove_zeros(s->sde[stripe][layer][plane]);
+  jbg_buf_write(MARKER_ESC, s->sde[stripe][layer][plane]);
+  jbg_buf_write(MARKER_SDNORM, s->sde[stripe][layer][plane]);
+
+  /* add ATMOVE */
+  if (new_tx != -1) {
+    if (s->options & JBG_DELAY_AT) {
+      /* ATMOVE will become active at the first line of the next stripe */
+      s->tx[plane] = new_tx;
+      jbg_buf_write(MARKER_ESC, s->sde[stripe][layer][plane]);
+      jbg_buf_write(MARKER_ATMOVE, s->sde[stripe][layer][plane]);
+      jbg_buf_write(0, s->sde[stripe][layer][plane]);
+      jbg_buf_write(0, s->sde[stripe][layer][plane]);
+      jbg_buf_write(0, s->sde[stripe][layer][plane]);
+      jbg_buf_write(0, s->sde[stripe][layer][plane]);
+      jbg_buf_write(s->tx[plane], s->sde[stripe][layer][plane]);
+      jbg_buf_write(0, s->sde[stripe][layer][plane]);
+    } else {
+      /* ATMOVE has already become active during this stripe
+       * => we have to prefix the SDE data with an ATMOVE marker */
+      new_jbg_buf = jbg_buf_init(&s->free_list);
+      jbg_buf_write(MARKER_ESC, new_jbg_buf);
+      jbg_buf_write(MARKER_ATMOVE, new_jbg_buf);
+      jbg_buf_write((new_tx_line >> 24) & 0xff, new_jbg_buf);
+      jbg_buf_write((new_tx_line >> 16) & 0xff, new_jbg_buf);
+      jbg_buf_write((new_tx_line >> 8) & 0xff, new_jbg_buf);
+      jbg_buf_write(new_tx_line & 0xff, new_jbg_buf);
+      jbg_buf_write(new_tx, new_jbg_buf);
+      jbg_buf_write(0, new_jbg_buf);
+      jbg_buf_prefix(new_jbg_buf, &s->sde[stripe][layer][plane]);
+    }
+  }
+
+#if 0
+  if (stripe == s->stripes - 1)
+    fprintf(stderr, "tp_lines = %ld, tp_exceptions = %ld, tp_pixels = %ld, "
+	    "dp_pixels = %ld, encoded_pixels = %ld\n",
+	    tp_lines, tp_exceptions, tp_pixels, dp_pixels, encoded_pixels);
+#endif
+
+  return;
+}
+
+
+/*
+ * Create the next lower resolution version of an image
+ */
+static void resolution_reduction(struct jbg_enc_state *s, int plane,
+				 int higher_layer)
+{
+  unsigned long hx, hy, lx, ly, hbpl, lbpl;
+  unsigned char *hp1, *hp2, *hp3, *lp;
+  unsigned long line_h1, line_h2, line_h3, line_l2;
+  unsigned long i, j;
+  int pix, k, l;
+
+  /* number of pixels in highres image */
+  hx = jbg_ceil_half(s->xd, s->d - higher_layer);
+  hy = jbg_ceil_half(s->yd, s->d - higher_layer);
+  /* number of pixels in lowres image */
+  lx = jbg_ceil_half(hx, 1);
+  ly = jbg_ceil_half(hy, 1);
+  /* bytes per line in highres and lowres image */
+  hbpl = (hx + 7) / 8;
+  lbpl = (lx + 7) / 8;
+  /* pointers to first image bytes */
+  hp2 = s->lhp[s->highres[plane]][plane];
+  hp1 = hp2 + hbpl;
+  hp3 = hp2 - hbpl;
+  lp = s->lhp[1 - s->highres[plane]][plane];
+  
+#ifdef DEBUG
+  fprintf(stderr, "resolution_reduction: plane = %d, higher_layer = %d\n",
+	  plane, higher_layer);
+#endif
+
+  /*
+   * Layout of the variables line_h1, line_h2, line_h3, which contain
+   * as bits the high resolution neighbour pixels of the currently coded
+   * lowres pixel /\:
+   *              \/
+   *
+   *   76543210 76543210 76543210 76543210     line_h3
+   *   76543210 76543210 765432/\ 76543210     line_h2
+   *   76543210 76543210 765432\/ 76543210     line_h1
+   *
+   * Layout of the variable line_l2, which contains the low resolution
+   * pixels near the currently coded pixel as bits. The lowres pixel
+   * which is currently coded is marked as X:
+   *
+   *   76543210 76543210 76543210 76543210     line_l2
+   *                            X
+   */
+      
+  for (i = 0; i < ly; i++) {
+    if (2*i + 1 >= hy)
+      hp1 = hp2;
+    pix = 0;
+    line_h1 = line_h2 = line_h3 = line_l2 = 0;
+    for (j = 0; j < lbpl * 8; j += 8) {
+      *lp = 0;
+      line_l2 |= i ? lp[-lbpl] : 0;
+      for (k = 0; k < 8 && j + k < lx; k += 4) {
+	if (((j + k) >> 2) < hbpl) {
+	  line_h3 |= i ? *hp3 : 0;
+	  ++hp3;
+	  line_h2 |= *(hp2++);
+	  line_h1 |= *(hp1++);
+	}
+	for (l = 0; l < 4 && j + k + l < lx; l++) {
+	  line_h3 <<= 2;
+	  line_h2 <<= 2;
+	  line_h1 <<= 2;
+	  line_l2 <<= 1;
+	  pix = s->res_tab[((line_h1 >> 8) & 0x007) |
+			   ((line_h2 >> 5) & 0x038) |
+			   ((line_h3 >> 2) & 0x1c0) |
+			   (pix << 9) | ((line_l2 << 2) & 0xc00)];
+	  *lp = (*lp << 1) | pix;
+	}
+      }
+      ++lp;
+    }
+    *(lp - 1) <<= lbpl * 8 - lx;
+    hp1 += hbpl;
+    hp2 += hbpl;
+    hp3 += hbpl;
+  }
+
+#ifdef DEBUG
+  {
+    FILE *f;
+    char fn[50];
+    
+    sprintf(fn, "dbg_d=%02d.pbm", higher_layer - 1);
+    f = fopen(fn, "wb");
+    fprintf(f, "P4\n%lu %lu\n", lx, ly);
+    fwrite(s->lhp[1 - s->highres[plane]][plane], 1, lbpl * ly, f);
+    fclose(f);
+  }
+#endif
+
+  return;
+}
+
+
+/* 
+ * This function is called inside the three loops of jbg_enc_out() in
+ * order to write the next SDE. It has first to generate the required
+ * SDE and all SDEs which have to be encoded before this SDE can be
+ * created. The problem here is that if we want to output a lower
+ * resolution layer, we have to allpy the resolution reduction
+ * algorithm in order to get it. As we try to safe as much memory as
+ * possible, the resolution reduction will overwrite previous higher
+ * resolution bitmaps. Consequently, we have to encode and buffer SDEs
+ * which depend on higher resolution layers before we can start the
+ * resolution reduction. All this logic about which SDE has to be
+ * encoded before resolution reduction is allowed is handled here.
+ * This approach might be a little bit more complex than alternative
+ * ways to do it, but it allows us to do the encoding with the minimal
+ * possible amount of temporary memory.
+ */
+static void output_sde(struct jbg_enc_state *s,
+		       unsigned long stripe, int layer, int plane)
+{
+  int lfcl;     /* lowest fully coded layer */
+  long i;
+  unsigned long u;
+  
+  assert(s->sde[stripe][layer][plane] != SDE_DONE);
+
+  if (s->sde[stripe][layer][plane] != SDE_TODO) {
+#ifdef DEBUG
+    fprintf(stderr, "writing SDE: s/d/p = %2lu/%2d/%2d\n",
+	    stripe, layer, plane);
+#endif
+    jbg_buf_output(&s->sde[stripe][layer][plane], s->data_out, s->file);
+    s->sde[stripe][layer][plane] = SDE_DONE;
+    return;
+  }
+
+  /* Determine the smallest resolution layer in this plane for which
+   * not yet all stripes have been encoded into SDEs. This layer will
+   * have to be completely coded, before we can apply the next
+   * resolution reduction step. */
+  lfcl = 0;
+  for (i = s->d; i >= 0; i--)
+    if (s->sde[s->stripes - 1][i][plane] == SDE_TODO) {
+      lfcl = i + 1;
+      break;
+    }
+  if (lfcl > s->d && s->d > 0 && stripe == 0) {
+    /* perform the first resolution reduction */
+    resolution_reduction(s, plane, s->d);
+  }
+  /* In case HITOLO is not used, we have to encode and store the higher
+   * resolution layers first, although we do not need them right now. */
+  while (lfcl - 1 > layer) {
+    for (u = 0; u < s->stripes; u++)
+      encode_sde(s, u, lfcl - 1, plane);
+    --lfcl;
+    s->highres[plane] ^= 1;
+    if (lfcl > 1)
+      resolution_reduction(s, plane, lfcl - 1);
+  }
+  
+  encode_sde(s, stripe, layer, plane);
+
+#ifdef DEBUG
+  fprintf(stderr, "writing SDE: s/d/p = %2lu/%2d/%2d\n", stripe, layer, plane);
+#endif
+  jbg_buf_output(&s->sde[stripe][layer][plane], s->data_out, s->file);
+  s->sde[stripe][layer][plane] = SDE_DONE;
+  
+  if (stripe == s->stripes - 1 && layer > 0 &&
+      s->sde[0][layer-1][plane] == SDE_TODO) {
+    s->highres[plane] ^= 1;
+    if (layer > 1)
+      resolution_reduction(s, plane, layer - 1);
+  }
+  
+  return;
+}
+
+
+/*
+ * Convert the table which controls the deterministic prediction
+ * process from the internal format into the representation required
+ * for the 1728 byte long DPTABLE element of a BIH.
+ *
+ * The bit order of the DPTABLE format (see also ITU-T T.82 figure 13) is
+ *
+ * high res:   4  5  6     low res:  0  1
+ *             7  8  9               2  3
+ *            10 11 12
+ *
+ * were 4 table entries are packed into one byte, while we here use
+ * internally an unpacked 6912 byte long table indexed by the following
+ * bit order:
+ *
+ * high res:   7  6  5     high res:   8  7  6     low res:  1  0
+ * (phase 0)   4  .  .     (phase 1)   5  4  .               3  2
+ *             .  .  .                 .  .  .
+ *
+ * high res:  10  9  8     high res:  11 10  9
+ * (phase 2)   7  6  5     (phase 3)   8  7  6
+ *             4  .  .                 5  4  .
+ */
+void jbg_int2dppriv(unsigned char *dptable, const char *internal)
+{
+  int i, j, k;
+  int trans0[ 8] = { 1, 0, 3, 2, 7, 6, 5, 4 };
+  int trans1[ 9] = { 1, 0, 3, 2, 8, 7, 6, 5, 4 };
+  int trans2[11] = { 1, 0, 3, 2, 10, 9, 8, 7, 6, 5, 4 };
+  int trans3[12] = { 1, 0, 3, 2, 11, 10, 9, 8, 7, 6, 5, 4 };
+  
+  for (i = 0; i < 1728; dptable[i++] = 0);
+
+#define FILL_TABLE1(offset, len, trans) \
+  for (i = 0; i < len; i++) { \
+    k = 0; \
+    for (j = 0; j < 8; j++) \
+      k |= ((i >> j) & 1) << trans[j]; \
+    dptable[(i + offset) >> 2] |= \
+      (internal[k + offset] & 3) << ((3 - (i&3)) << 1); \
+  }
+
+  FILL_TABLE1(   0,  256, trans0);
+  FILL_TABLE1( 256,  512, trans1);
+  FILL_TABLE1( 768, 2048, trans2);
+  FILL_TABLE1(2816, 4096, trans3);
+
+  return;
+}
+
+
+/*
+ * Convert the table which controls the deterministic prediction
+ * process from the 1728 byte long DPTABLE format into the 6912 byte long
+ * internal format.
+ */
+void jbg_dppriv2int(char *internal, const unsigned char *dptable)
+{
+  int i, j, k;
+  int trans0[ 8] = { 1, 0, 3, 2, 7, 6, 5, 4 };
+  int trans1[ 9] = { 1, 0, 3, 2, 8, 7, 6, 5, 4 };
+  int trans2[11] = { 1, 0, 3, 2, 10, 9, 8, 7, 6, 5, 4 };
+  int trans3[12] = { 1, 0, 3, 2, 11, 10, 9, 8, 7, 6, 5, 4 };
+  
+#define FILL_TABLE2(offset, len, trans) \
+  for (i = 0; i < len; i++) { \
+    k = 0; \
+    for (j = 0; j < 8; j++) \
+      k |= ((i >> j) & 1) << trans[j]; \
+    internal[k + offset] = \
+      (dptable[(i + offset) >> 2] >> ((3 - (i & 3)) << 1)) & 3; \
+  }
+
+  FILL_TABLE2(   0,  256, trans0);
+  FILL_TABLE2( 256,  512, trans1);
+  FILL_TABLE2( 768, 2048, trans2);
+  FILL_TABLE2(2816, 4096, trans3);
+
+  return;
+}
+
+
+/*
+ * Encode one full BIE and pass the generated data to the specified
+ * call-back function
+ */
+void jbg_enc_out(struct jbg_enc_state *s)
+{
+  long bpl;
+  unsigned char bih[20];
+  unsigned long xd, yd, y;
+  long ii[3], is[3], ie[3];    /* generic variables for the 3 nested loops */ 
+  unsigned long stripe;
+  int layer, plane;
+  int order;
+  unsigned char dpbuf[1728];
+  extern char jbg_dptable[];
+
+  /* some sanity checks */
+  s->order &= JBG_HITOLO | JBG_SEQ | JBG_ILEAVE | JBG_SMID;
+  order = s->order & (JBG_SEQ | JBG_ILEAVE | JBG_SMID);
+  if (index[order][0] < 0)
+    s->order = order = JBG_SMID | JBG_ILEAVE;
+  if (s->options & JBG_DPON && s->dppriv != jbg_dptable)
+    s->options |= JBG_DPPRIV;
+  if (s->mx > MX_MAX)
+    s->mx = MX_MAX;
+  s->my = 0;
+  if (s->mx && s->mx < ((s->options & JBG_LRLTWO) ? 5U : 3U))
+    s->mx = 0;
+  if (s->d > 255 || s->d < 0 || s->dh > s->d || s->dh < 0 ||
+      s->dl < 0 || s->dl > s->dh || s->planes < 0 || s->planes > 255)
+    return;
+
+  /* ensure correct zero padding of bitmap at the final byte of each line */
+  if (s->xd & 7) {
+    bpl = (s->xd + 7) / 8;     /* bytes per line */
+    for (plane = 0; plane < s->planes; plane++)
+      for (y = 0; y < s->yd; y++)
+	s->lhp[0][plane][y * bpl + bpl - 1] &= ~((1 << (8 - (s->xd & 7))) - 1);
+  }
+
+  /* calculate number of stripes that will be required */
+  s->stripes = ((s->yd >> s->d) + 
+		((((1UL << s->d) - 1) & s->xd) != 0) + s->l0 - 1) / s->l0;
+
+  /* allocate buffers for SDE pointers */
+  if (s->sde == NULL) {
+    s->sde = (struct jbg_buf ****)
+      checked_malloc(s->stripes * sizeof(struct jbg_buf ***));
+    for (stripe = 0; stripe < s->stripes; stripe++) {
+      s->sde[stripe] = (struct jbg_buf ***)
+	checked_malloc((s->d + 1) * sizeof(struct jbg_buf **));
+      for (layer = 0; layer < s->d + 1; layer++) {
+	s->sde[stripe][layer] = (struct jbg_buf **)
+	  checked_malloc(s->planes * sizeof(struct jbg_buf *));
+	for (plane = 0; plane < s->planes; plane++)
+	  s->sde[stripe][layer][plane] = SDE_TODO;
+      }
+    }
+  }
+
+  /* output BIH */
+  bih[0] = s->dl;
+  bih[1] = s->dh;
+  bih[2] = s->planes;
+  bih[3] = 0;
+  xd = jbg_ceil_half(s->xd, s->d - s->dh);
+  yd = jbg_ceil_half(s->yd, s->d - s->dh);
+  bih[4] = xd >> 24;
+  bih[5] = (xd >> 16) & 0xff;
+  bih[6] = (xd >> 8) & 0xff;
+  bih[7] = xd & 0xff;
+  bih[8] = yd >> 24;
+  bih[9] = (yd >> 16) & 0xff;
+  bih[10] = (yd >> 8) & 0xff;
+  bih[11] = yd & 0xff;
+  bih[12] = s->l0 >> 24;
+  bih[13] = (s->l0 >> 16) & 0xff;
+  bih[14] = (s->l0 >> 8) & 0xff;
+  bih[15] = s->l0 & 0xff;
+  bih[16] = s->mx;
+  bih[17] = s->my;
+  bih[18] = s->order;
+  bih[19] = s->options & 0x7f;
+  s->data_out(bih, 20, s->file);
+  if ((s->options & (JBG_DPON | JBG_DPPRIV | JBG_DPLAST)) ==
+      (JBG_DPON | JBG_DPPRIV)) {
+    /* write private table */
+    jbg_int2dppriv(dpbuf, s->dppriv);
+    s->data_out(dpbuf, 1728, s->file);
+  }
+
+#if 0
+  /*
+   * Encode everything first. This is a simple-minded alternative to
+   * all the tricky on-demand encoding logic in output_sde() for
+   * debugging purposes.
+   */
+  for (layer = s->dh; layer >= s->dl; layer--) {
+    for (plane = 0; plane < s->planes; plane++) {
+      if (layer > 0)
+	resolution_reduction(s, plane, layer);
+      for (stripe = 0; stripe < s->stripes; stripe++)
+	encode_sde(s, stripe, layer, plane);
+      s->highres[plane] ^= 1;
+    }
+  }
+#endif
+
+  /*
+   * Generic loops over all SDEs. Which loop represents layer, plane and
+   * stripe depends on the option flags.
+   */
+
+  /* start and end value vor each loop */
+  is[index[order][STRIPE]] = 0;
+  ie[index[order][STRIPE]] = s->stripes - 1;
+  is[index[order][LAYER]] = s->dl;
+  ie[index[order][LAYER]] = s->dh;
+  is[index[order][PLANE]] = 0;
+  ie[index[order][PLANE]] = s->planes - 1;
+
+  for (ii[0] = is[0]; ii[0] <= ie[0]; ii[0]++)
+    for (ii[1] = is[1]; ii[1] <= ie[1]; ii[1]++)
+      for (ii[2] = is[2]; ii[2] <= ie[2]; ii[2]++) {
+	
+	stripe = ii[index[order][STRIPE]];
+	if (s->order & JBG_HITOLO)
+	  layer = s->dh - (ii[index[order][LAYER]] - s->dl);
+	else
+	  layer = ii[index[order][LAYER]];
+	plane = ii[index[order][PLANE]];
+
+	output_sde(s, stripe, layer, plane);
+
+      }
+
+  return;
+}
+
+
+void jbg_enc_free(struct jbg_enc_state *s)
+{
+  unsigned long stripe;
+  int layer, plane;
+
+#ifdef DEBUG
+  fprintf(stderr, "jbg_enc_free(%p)\n", s);
+#endif
+
+  /* clear buffers for SDEs */
+  if (s->sde) {
+    for (stripe = 0; stripe < s->stripes; stripe++) {
+      for (layer = 0; layer < s->d + 1; layer++) {
+	for (plane = 0; plane < s->planes; plane++)
+	  if (s->sde[stripe][layer][plane] != SDE_DONE &&
+	      s->sde[stripe][layer][plane] != SDE_TODO)
+	    jbg_buf_free(&s->sde[stripe][layer][plane]);
+	checked_free(s->sde[stripe][layer]);
+      }
+      checked_free(s->sde[stripe]);
+    }
+    checked_free(s->sde);
+  }
+
+  /* clear free_list */
+  jbg_buf_free(&s->free_list);
+
+  /* clear memory for arithmetic encoder states */
+  checked_free(s->s);
+
+  /* clear memory for differential-layer typical prediction buffer */
+  checked_free(s->tp);
+
+  /* clear memory for adaptive template pixel offsets */
+  checked_free(s->tx);
+
+  /* clear lowres image buffers */
+  if (s->lhp[1]) {
+    for (plane = 0; plane < s->planes; plane++)
+      checked_free(s->lhp[1][plane]);
+    checked_free(s->lhp[1]);
+  }
+
+  return;
+}
+
+
+/*
+ * Convert the error codes used by jbg_dec_in() into a string
+ * written in the selected language and character set.
+ */
+const char *jbg_strerror(int errnum, int language)
+{
+  if (errnum < 0 || errnum >= NEMSG)
+    return "Unknown error code passed to jbg_strerror()";
+  if (language < 0 || language >= NEMSG_LANG)
+    return "Unknown language code passed to jbg_strerror()";
+
+  return errmsg[language][errnum];
+}
+
+
+/*
+ * The constructor for a decoder 
+ */
+void jbg_dec_init(struct jbg_dec_state *s)
+{
+  s->order = 0;
+  s->d = -1;
+  s->bie_len = 0;
+  s->buf_len = 0;
+  s->dppriv = NULL;
+  s->xmax = 4294967295UL;
+  s->ymax = 4294967295UL;
+  s->dmax = 256;
+  s->s = NULL;
+
+  return;
+}
+
+
+/*
+ * Specify a maximum image size for the decoder. If the JBIG file has
+ * the order bit ILEAVE, but not the bit SEQ set, then the decoder
+ * will abort to decode after the image has reached the maximal
+ * resolution layer which is still not wider than xmax or higher than
+ * ymax.
+ */
+void jbg_dec_maxsize(struct jbg_dec_state *s, unsigned long xmax,
+		     unsigned long ymax)
+{
+  if (xmax > 0) s->xmax = xmax;
+  if (ymax > 0) s->ymax = ymax;
+
+  return;
+}
+
+
+/*
+ * Decode the new len PSDC bytes to which data points and add them to
+ * the current stripe. Return the number of bytes which have actually
+ * been read (this will be less than len if a marker segment was 
+ * part of the data or if the final byte was 0xff were this code
+ * can not determine, whether we have a marker segment.
+ */
+static size_t decode_pscd(struct jbg_dec_state *s, unsigned char *data,
+			  size_t len)
+{
+  unsigned long stripe;
+  unsigned int layer, plane;
+  unsigned long hl, ll, y, hx, hy, lx, ly, hbpl, lbpl;
+  unsigned char *hp, *lp1, *lp2, *p1, *q1;
+  register unsigned long line_h1, line_h2, line_h3;
+  register unsigned long line_l1, line_l2, line_l3;
+  struct jbg_ardec_state *se;
+  unsigned long x;
+  int n;
+  int pix, cx = 0, slntp, shift, tx;
+
+  /* SDE loop variables */
+  stripe = s->ii[index[s->order & 7][STRIPE]];
+  layer = s->ii[index[s->order & 7][LAYER]];
+  plane = s->ii[index[s->order & 7][PLANE]];
+
+  /* forward data to arithmetic decoder */
+  se = s->s[plane] + layer - s->dl;
+  se->pscd_ptr = data;
+  se->pscd_end = data + len;
+  
+  /* number of lines per stripe in highres image */
+  hl = s->l0 << layer;
+  /* number of lines per stripe in lowres image */
+  ll = hl >> 1;
+  /* current line number in highres image */
+  y = stripe * hl + s->i;
+  /* number of pixels in highres image */
+  hx = jbg_ceil_half(s->xd, s->d - layer);
+  hy = jbg_ceil_half(s->yd, s->d - layer);
+  /* number of pixels in lowres image */
+  lx = jbg_ceil_half(hx, 1);
+  ly = jbg_ceil_half(hy, 1);
+  /* bytes per line in highres and lowres image */
+  hbpl = (hx + 7) / 8;
+  lbpl = (lx + 7) / 8;
+  /* pointer to highres and lowres image bytes */
+  hp  = s->lhp[ layer    & 1][plane] + (stripe * hl + s->i) * hbpl +
+    (s->x >> 3);
+  lp2 = s->lhp[(layer-1) & 1][plane] + (stripe * ll + (s->i >> 1)) * lbpl +
+    (s->x >> 4);
+  lp1 = lp2 + lbpl;
+
+  /* restore a few local variables */
+  line_h1 = s->line_h1;
+  line_h2 = s->line_h2;
+  line_h3 = s->line_h3;
+  line_l1 = s->line_l1;
+  line_l2 = s->line_l2;
+  line_l3 = s->line_l3;
+  x = s->x;
+
+  if (s->x == 0 && s->i == 0 &&
+      (stripe == 0 || s->reset[plane][layer - s->dl])) {
+    s->tx[plane][layer - s->dl] = s->ty[plane][layer - s->dl] = 0;
+    if (s->pseudo)
+      s->lntp[plane][layer - s->dl] = 1;
+  }
+
+#ifdef DEBUG
+  if (s->x == 0 && s->i == 0 && s->pseudo)
+    fprintf(stderr, "decode_pscd(%p, %p, %ld): s/d/p = %2lu/%2u/%2u\n",
+	    s, data, (long) len, stripe, layer, plane);
+#endif
+
+  if (layer == 0) {
+
+    /*
+     *  Decode lowest resolution layer
+     */
+
+    for (; s->i < hl && y < hy; s->i++, y++) {
+
+      /* adaptive template changes */
+      if (x == 0)
+	for (n = 0; n < s->at_moves; n++)
+	  if (s->at_line[n] == s->i) {
+	    s->tx[plane][layer - s->dl] = s->at_tx[n];
+	    s->ty[plane][layer - s->dl] = s->at_ty[n];
+#ifdef DEBUG
+	    fprintf(stderr, "ATMOVE: line=%lu, tx=%d, ty=%d.\n", s->i,
+		    s->tx[plane][layer - s->dl], s->ty[plane][layer - s->dl]);
+#endif
+	  }
+      tx = s->tx[plane][layer - s->dl];
+      shift =  tx - ((s->options & JBG_LRLTWO) ? 5 : 3);
+
+      /* typical prediction */
+      if (s->options & JBG_TPBON && s->pseudo) {
+	slntp = arith_decode(se, (s->options & JBG_LRLTWO) ? TPB2CX : TPB3CX);
+	if (se->result == JBG_MORE || se->result == JBG_MARKER)
+	  goto leave;
+	s->lntp[plane][layer - s->dl] =
+	  !(slntp ^ s->lntp[plane][layer - s->dl]);
+	if (s->lntp[plane][layer - s->dl]) {
+	  /* this line is 'not typical' and has to be coded completely */
+	  s->pseudo = 0;
+	} else {
+	  /* this line is 'typical' (i.e. identical to the previous one) */
+	  p1 = hp;
+	  if (s->i == 0 && (stripe == 0 || s->reset[plane][layer - s->dl]))
+	    while (p1 < hp + hbpl) *p1++ = 0;
+	  else {
+	    q1 = hp - hbpl;
+	    while (q1 < hp) *p1++ = *q1++;
+	  }
+	  hp += hbpl;
+	  continue;
+	}
+      }
+      
+      /*
+       * Layout of the variables line_h1, line_h2, line_h3, which contain
+       * as bits the neighbour pixels of the currently decoded pixel X:
+       *
+       *                     76543210 76543210 76543210 76543210     line_h3
+       *                     76543210 76543210 76543210 76543210     line_h2
+       *   76543210 76543210 76543210 76543210 X                     line_h1
+       */
+      
+      if (x == 0) {
+	line_h1 = line_h2 = line_h3 = 0;
+	if (s->i > 0 || (y > 0 && !s->reset[plane][layer - s->dl]))
+	  line_h2 = (long)*(hp - hbpl) << 8;
+	if (s->i > 1 || (y > 1 && !s->reset[plane][layer - s->dl]))
+	  line_h3 = (long)*(hp - hbpl - hbpl) << 8;
+      }
+      
+      /*
+       * Another tiny JBIG standard bug:
+       *
+       * While implementing the line_h3 handling here, I discovered
+       * another problem with the ITU-T T.82(1993 E) specification.
+       * This might be a somewhat pathological case, however. The
+       * standard is unclear about how a decoder should behave in the
+       * following situation:
+       *
+       * Assume we are in layer 0 and all stripes are single lines
+       * (L0=1 allowed by table 9). We are now decoding the first (and
+       * only) line of the third stripe. Assume, the first stripe was
+       * terminated by SDRST and the second stripe was terminated by
+       * SDNORM. While decoding the only line of the third stripe with
+       * the three-line template, we need access to pixels from the
+       * previous two stripes. We know that the previous stripe
+       * terminated with SDNROM, so we access the pixel from the
+       * second stripe. But do we have to replace the pixels from the
+       * first stripe by background pixels, because this stripe ended
+       * with SDRST? The standard, especially clause 6.2.5 does never
+       * mention this case, so the behaviour is undefined here. My
+       * current implementation remembers only the marker used to
+       * terminate the previous stripe. In the above example, the
+       * pixels of the first stripe are accessed despite the fact that
+       * this stripe ended with SDRST. An alternative (only slightly
+       * more complicated) implementation would be to remember the end
+       * marker (SDNORM or SDRST) of the previous two stripes in a
+       * plane/layer and to act accordingly when accessing the two
+       * previous lines. What am I supposed to do here?
+       *
+       * As the standard is unclear about the correct behaviour in the
+       * situation of the above example, I strongly suggest to avoid
+       * the following situation while encoding data with JBIG:
+       *
+       *   LRLTWO = 0, L0=1 and both SDNORM and SDRST appear in layer 0.
+       *
+       * I guess that only a very few if any encoders will switch
+       * between SDNORM and SDRST, so let us hope that this ambiguity
+       * in the standard will never cause any interoperability
+       * problems.
+       *
+       * Markus Kuhn -- 1995-04-30
+       */
+
+      /* decode line */
+      while (x < hx) {
+	if ((x & 7) == 0) {
+	  if (x < hbpl * 8 - 8 &&
+	      (s->i > 0 || (y > 0 && !s->reset[plane][layer - s->dl]))) {
+	    line_h2 |= *(hp - hbpl + 1);
+	    if (s->i > 1 || (y > 1 && !s->reset[plane][layer - s->dl]))
+	      line_h3 |= *(hp - hbpl - hbpl + 1);
+	  }
+	}
+	if (s->options & JBG_LRLTWO) {
+	  /* two line template */
+	  do {
+	    if (tx)
+	      pix = arith_decode(se, (((line_h2 >> 9) & 0x3e0) |
+				      ((line_h1 >> shift) & 0x010) |
+				      (line_h1 & 0x00f)));
+	    else
+	      pix = arith_decode(se, (((line_h2 >> 9) & 0x3f0) |
+				      (line_h1 & 0x00f)));
+	    if (se->result == JBG_MORE || se->result == JBG_MARKER)
+	      goto leave;
+	    line_h1 = (line_h1 << 1) | pix;
+	    line_h2 <<= 1;
+	  } while ((++x & 7) && x < hx);
+	} else {
+	  /* three line template */
+	  do {
+	    if (tx) 
+	      pix = arith_decode(se, (((line_h3 >>  7) & 0x380) |
+				      ((line_h2 >> 11) & 0x078) |
+				      ((line_h1 >> shift) & 0x004) |
+				      (line_h1 & 0x003)));
+	    else
+	      pix = arith_decode(se, (((line_h3 >>  7) & 0x380) |
+				      ((line_h2 >> 11) & 0x07c) |
+				      (line_h1 & 0x003)));
+	    if (se->result == JBG_MORE || se->result == JBG_MARKER)
+	      goto leave;
+	    
+	    line_h1 = (line_h1 << 1) | pix;
+	    line_h2 <<= 1;
+	    line_h3 <<= 1;
+	  } while ((++x & 7) && x < hx);
+	} /* if (s->options & JBG_LRLTWO) */
+	*hp++ = line_h1;
+      } /* while */
+      *(hp - 1) <<= hbpl * 8 - hx;
+      x = 0;
+      s->pseudo = 1;
+    } /* for (i = ...) */
+    
+  } else {
+
+    /*
+     *  Decode differential layer
+     */
+
+    for (; s->i < hl && y < hy; s->i++, y++) {
+
+      /* adaptive template changes */
+      if (x == 0)
+	for (n = 0; n < s->at_moves; n++)
+	  if (s->at_line[n] == s->i) {
+	    s->tx[plane][layer - s->dl] = s->at_tx[n];
+	    s->ty[plane][layer - s->dl] = s->at_ty[n];
+#ifdef DEBUG
+	    fprintf(stderr, "ATMOVE: line=%lu, tx=%d, ty=%d.\n", s->i,
+		    s->tx[plane][layer - s->dl], s->ty[plane][layer - s->dl]);
+#endif
+	  }
+      tx = s->tx[plane][layer - s->dl];
+      shift = tx - 3;
+
+      /* handle lower border of low-resolution image */
+      if ((s->i >> 1) >= ll - 1 || (y >> 1) >= ly - 1)
+	lp1 = lp2;
+
+      /* typical prediction */
+      if (s->options & JBG_TPDON && s->pseudo) {
+	s->lntp[plane][layer - s->dl] = arith_decode(se, TPDCX);
+	if (se->result == JBG_MORE || se->result == JBG_MARKER)
+	  goto leave;
+	s->pseudo = 0;
+      }
+
+
+      /*
+       * Layout of the variables line_h1, line_h2, line_h3, which contain
+       * as bits the high resolution neighbour pixels of the currently
+       * decoded highres pixel X:
+       *
+       *                     76543210 76543210 76543210 76543210     line_h3
+       *                     76543210 76543210 76543210 76543210     line_h2
+       *   76543210 76543210 76543210 76543210 X                     line_h1
+       *
+       * Layout of the variables line_l1, line_l2, line_l3, which contain
+       * the low resolution pixels near the currently decoded pixel as bits.
+       * The lowres pixel in which the currently coded highres pixel is
+       * located is marked as Y:
+       *
+       *                     76543210 76543210 76543210 76543210     line_l3
+       *                     76543210 76543210 Y6543210 76543210     line_l2
+       *                     76543210 76543210 76543210 76543210     line_l1
+       */
+      
+
+      if (x == 0) {
+	line_h1 = line_h2 = line_h3 = line_l1 = line_l2 = line_l3 = 0;
+	if (s->i > 0 || (y > 0 && !s->reset[plane][layer - s->dl])) {
+	  line_h2 = (long)*(hp - hbpl) << 8;
+	  if (s->i > 1 || (y > 1 && !s->reset[plane][layer - s->dl]))
+	    line_h3 = (long)*(hp - hbpl - hbpl) << 8;
+	}
+	if (s->i > 1 || (y > 1 && !s->reset[plane][layer-s->dl]))
+	  line_l3 = (long)*(lp2 - lbpl) << 8;
+	line_l2 = (long)*lp2 << 8;
+	line_l1 = (long)*lp1 << 8;
+      }
+      
+      /* decode line */
+      while (x < hx) {
+	if ((x & 15) == 0)
+	  if ((x >> 1) < lbpl * 8 - 8) {
+	    line_l1 |= *(lp1 + 1);
+	    line_l2 |= *(lp2 + 1);
+	    if (s->i > 1 || 
+		(y > 1 && !s->reset[plane][layer - s->dl]))
+	      line_l3 |= *(lp2 - lbpl + 1);
+	  }
+	do {
+
+	  assert(hp  - (s->lhp[ layer     &1][plane] + (stripe * hl + s->i)
+			* hbpl) == (ptrdiff_t) x >> 3);
+	  assert(lp2 - (s->lhp[(layer-1) &1][plane] + (stripe * ll + (s->i>>1))
+			* lbpl) == (ptrdiff_t) x >> 4);
+
+	  if ((x & 7) == 0)
+	    if (x < hbpl * 8 - 8) {
+	      if (s->i > 0 || (y > 0 && !s->reset[plane][layer - s->dl])) {
+		line_h2 |= *(hp + 1 - hbpl);
+		if (s->i > 1 || (y > 1 && !s->reset[plane][layer - s->dl]))
+		  line_h3 |= *(hp + 1 - hbpl - hbpl);
+	      }
+	    }
+	  do {
+	    if (!s->lntp[plane][layer - s->dl])
+              cx = (((line_l3 >> 14) & 0x007) |
+                    ((line_l2 >> 11) & 0x038) |
+                    ((line_l1 >> 8)  & 0x1c0));
+	    if (!s->lntp[plane][layer - s->dl] &&
+		(cx == 0x000 || cx == 0x1ff)) {
+	      /* pixels are typical and have not to be decoded */
+	      do {
+		line_h1 = (line_h1 << 1) | (cx & 1);
+	      } while ((++x & 1) && x < hx);
+	      line_h2 <<= 2;  line_h3 <<= 2;
+	    } else 
+	      do {
+		
+		/* deterministic prediction */
+		if (s->options & JBG_DPON)
+		  if ((y & 1) == 0)
+		    if ((x & 1) == 0) 
+		      /* phase 0 */
+		      pix = s->dppriv[((line_l3 >> 15) & 0x003) |
+				      ((line_l2 >> 13) & 0x00c) |
+				      ((line_h1 <<  4) & 0x010) |
+				      ((line_h2 >>  9) & 0x0e0)];
+		    else
+		      /* phase 1 */
+		      pix = s->dppriv[(((line_l3 >> 15) & 0x003) |
+				       ((line_l2 >> 13) & 0x00c) |
+				       ((line_h1 <<  4) & 0x030) |
+				       ((line_h2 >>  9) & 0x1c0)) + 256];
+		  else
+		    if ((x & 1) == 0)
+		      /* phase 2 */
+		      pix = s->dppriv[(((line_l3 >> 15) & 0x003) |
+				       ((line_l2 >> 13) & 0x00c) |
+				       ((line_h1 <<  4) & 0x010) |
+				       ((line_h2 >>  9) & 0x0e0) |
+				       ((line_h3 >>  6) & 0x700)) + 768];
+		    else
+		      /* phase 3 */
+		      pix = s->dppriv[(((line_l3 >> 15) & 0x003) |
+				       ((line_l2 >> 13) & 0x00c) |
+				       ((line_h1 <<  4) & 0x030) |
+				       ((line_h2 >>  9) & 0x1c0) |
+				       ((line_h3 >>  6) & 0xe00)) + 2816];
+		else
+		  pix = 2;
+
+		if (pix & 2) {
+		  if (tx)
+		    cx = ((line_h1         & 0x003) |
+			  (((line_h1 << 2) >> shift) & 0x010) |
+			  ((line_h2 >> 12) & 0x00c) |
+			  ((line_h3 >> 10) & 0x020));
+		  else
+		    cx = ((line_h1         & 0x003) |
+			  ((line_h2 >> 12) & 0x01c) |
+			  ((line_h3 >> 10) & 0x020));
+		  if (x & 1)
+		    cx |= (((line_l2 >> 8) & 0x0c0) |
+			   ((line_l1 >> 6) & 0x300)) | (1UL << 10);
+		  else
+		    cx |= (((line_l2 >> 9) & 0x0c0) |
+			   ((line_l1 >> 7) & 0x300));
+		  cx |= (y & 1) << 11;
+
+		  pix = arith_decode(se, cx);
+		  if (se->result == JBG_MORE || se->result == JBG_MARKER)
+		    goto leave;
+		}
+
+		line_h1 = (line_h1 << 1) | pix;
+		line_h2 <<= 1;
+		line_h3 <<= 1;
+		
+	      } while ((++x & 1) && x < hx);
+	    line_l1 <<= 1; line_l2 <<= 1;  line_l3 <<= 1;
+	  } while ((x & 7) && x < hx);
+	  *hp++ = line_h1;
+	} while ((x & 15) && x < hx);
+	++lp1;
+	++lp2;
+      } /* while */
+      x = 0;
+      
+      *(hp - 1) <<= hbpl * 8 - hx;
+      if ((s->i & 1) == 0) {
+	/* low resolution pixels are used twice */
+	lp1 -= lbpl;
+	lp2 -= lbpl;
+      } else
+	s->pseudo = 1;
+      
+    } /* for (i = ...) */
+    
+  }
+
+ leave:
+
+  /* save a few local variables */
+  s->line_h1 = line_h1;
+  s->line_h2 = line_h2;
+  s->line_h3 = line_h3;
+  s->line_l1 = line_l1;
+  s->line_l2 = line_l2;
+  s->line_l3 = line_l3;
+  s->x = x;
+
+  return se->pscd_ptr - data;
+}
+
+
+/*
+ * Provide a new BIE fragment to the decoder.
+ *
+ * If cnt is not NULL, then *cnt will contain after the call the
+ * number of actually read bytes. If the data was not complete, then
+ * the return value will be JBG_EAGAIN and *cnt == len. In case this
+ * function has returned with JBG_EOK, then it has reached the end of
+ * a BIE but it can be called again with data from the next BIE if
+ * there exists one in order to get to a higher resolution layer. In
+ * case the return value was JBG_EOK_INTR then this function can be
+ * called again with the rest of the BIE, because parsing the BIE has
+ * been interrupted by a jbg_dec_maxsize() specification. In both
+ * cases the remaining len - *cnt bytes of the previous block will
+ * have to passed to this function again (if len > *cnt). In case of
+ * any other return value than JBG_EOK, JBG_EOK_INTR or JBG_EAGAIN, a
+ * serious problem has occured and the only function you should call
+ * is jbg_dec_free() in order to remove the mess (and probably
+ * jbg_strerror() in order to find out what to tell the user).
+ */
+int jbg_dec_in(struct jbg_dec_state *s, unsigned char *data, size_t len,
+	       size_t *cnt)
+{
+  int i, j, required_length;
+  unsigned long x, y;
+  unsigned long is[3], ie[3];
+  long hsize, lsize;
+  extern char jbg_dptable[];
+  size_t dummy_cnt;
+
+  if (!cnt) cnt = &dummy_cnt;
+  *cnt = 0;
+  if (len < 1) return JBG_EAGAIN;
+
+  /* read in 20-byte BIH */
+  if (s->bie_len < 20) {
+    while (s->bie_len < 20 && *cnt < len)
+      s->buffer[s->bie_len++] = data[(*cnt)++];
+    if (s->bie_len < 20) 
+      return JBG_EAGAIN;
+    if (s->buffer[1] < s->buffer[0])
+      return JBG_EINVAL;
+    /* test whether this looks like a valid JBIG header at all */
+    if (s->buffer[3] != 0 || (s->buffer[18] & 0xf0) != 0 ||
+	(s->buffer[19] & 0x80) != 0)
+      return JBG_EINVAL;
+    if (s->buffer[0] != s->d + 1)
+      return JBG_ENOCONT;
+    s->dl = s->buffer[0];
+    s->d = s->buffer[1];
+    if (s->dl == 0)
+      s->planes = s->buffer[2];
+    else
+      if (s->planes != s->buffer[2])
+	return JBG_ENOCONT;
+    x = (((long) s->buffer[ 4] << 24) | ((long) s->buffer[ 5] << 16) |
+	 ((long) s->buffer[ 6] <<  8) | (long) s->buffer[ 7]);
+    y = (((long) s->buffer[ 8] << 24) | ((long) s->buffer[ 9] << 16) |
+	 ((long) s->buffer[10] <<  8) | (long) s->buffer[11]);
+    if (s->dl != 0 && ((s->xd << (s->d - s->dl + 1)) != x &&
+		       (s->yd << (s->d - s->dl + 1)) != y))
+      return JBG_ENOCONT;
+    s->xd = x;
+    s->yd = y;
+    s->l0 = (((long) s->buffer[12] << 24) | ((long) s->buffer[13] << 16) |
+	     ((long) s->buffer[14] <<  8) | (long) s->buffer[15]);
+    if (!s->planes || !s->xd || !s->yd || !s->l0)
+      return JBG_EINVAL;
+    s->mx = s->buffer[16];
+    if (s->mx > 127)
+      return JBG_EINVAL;
+    s->my = s->buffer[17];
+    if (s->mx > 32 || s->my > 0) 
+      return JBG_EIMPL;
+    s->order = s->buffer[18];
+    if (index[s->order & 7][0] < 0)
+      return JBG_EINVAL;
+    /* HITOLO and SEQ currently not yet implemented */
+    if (s->dl != s->d && (s->order & JBG_HITOLO || s->order & JBG_SEQ))
+      return JBG_EIMPL;
+    s->options = s->buffer[19];
+
+    /* calculate number of stripes that will be required */
+    s->stripes = ((s->yd >> s->d) +
+		  ((((1UL << s->d) - 1) & s->xd) != 0) + s->l0 - 1) / s->l0;
+
+    /* some initialization */
+    s->ii[index[s->order & 7][STRIPE]] = 0;
+    s->ii[index[s->order & 7][LAYER]] = s->dl;
+    s->ii[index[s->order & 7][PLANE]] = 0;
+    /* bytes required for resolution layer D and D-1 */
+    hsize = ((s->xd + 7) / 8) * s->yd;
+    lsize = ((jbg_ceil_half(s->xd, 1) + 7) / 8) *
+      jbg_ceil_half(s->yd, 1);
+    if (s->dl == 0) {
+      s->s = checked_malloc(s->planes * sizeof(struct jbg_ardec_state *));
+      s->tx = checked_malloc(s->planes * sizeof(int *));
+      s->ty = checked_malloc(s->planes * sizeof(int *));
+      s->reset = checked_malloc(s->planes * sizeof(int *));
+      s->lntp = checked_malloc(s->planes * sizeof(int *));
+      s->lhp[0] = checked_malloc(s->planes * sizeof(unsigned char *));
+      s->lhp[1] = checked_malloc(s->planes * sizeof(unsigned char *));
+      for (i = 0; i < s->planes; i++) {
+	s->s[i] = checked_malloc((s->d - s->dl + 1) *
+				 sizeof(struct jbg_ardec_state));
+	s->tx[i] = checked_malloc((s->d - s->dl + 1) * sizeof(int));
+	s->ty[i] = checked_malloc((s->d - s->dl + 1) * sizeof(int));
+	s->reset[i] = checked_malloc((s->d - s->dl + 1) * sizeof(int));
+	s->lntp[i] = checked_malloc((s->d - s->dl + 1) * sizeof(int));
+	s->lhp[s->d    &1][i] = checked_malloc(sizeof(unsigned char) * hsize);
+	s->lhp[(s->d-1)&1][i] = checked_malloc(sizeof(unsigned char) * lsize);
+      }
+    } else {
+      for (i = 0; i < s->planes; i++) {
+	s->s[i] = checked_realloc(s->s[i], (s->d - s->dl + 1) *
+				  sizeof(struct jbg_ardec_state));
+	s->tx[i] = checked_realloc(s->tx[i], (s->d - s->dl + 1) * sizeof(int));
+	s->ty[i] = checked_realloc(s->ty[i], (s->d - s->dl + 1) * sizeof(int));
+	s->reset[i] = checked_realloc(s->reset[i],
+				      (s->d - s->dl +1) * sizeof(int));
+	s->lntp[i] = checked_realloc(s->lntp[i],
+				     (s->d - s->dl +1) * sizeof(int));
+	s->lhp[s->d    &1][i] = checked_realloc(s->lhp[s->d    & 1][i],
+						sizeof(unsigned char) * hsize);
+	s->lhp[(s->d-1)&1][i] = checked_realloc(s->lhp[(s->d-1)&1][i],
+						sizeof(unsigned char) * lsize);
+      }
+    }
+    for (i = 0; i < s->planes; i++)
+      for (j = 0; j <= s->d - s->dl; j++)
+	arith_decode_init(s->s[i] + j, 0);
+    if (s->dl == 0 || (s->options & JBG_DPON && !(s->options & JBG_DPPRIV)))
+      s->dppriv = jbg_dptable;
+    s->comment_skip = 0;
+    s->buf_len = 0;
+    s->x = 0;
+    s->i = 0;
+    s->pseudo = 1;
+    s->at_moves = 0;
+  }
+
+  /* read in DPTABLE */
+  if (s->bie_len < 20 + 1728 && 
+      (s->options & (JBG_DPON | JBG_DPPRIV | JBG_DPLAST)) ==
+      (JBG_DPON | JBG_DPPRIV)) {
+    assert(s->bie_len >= 20);
+    while (s->bie_len < 20 + 1728 && *cnt < len)
+      s->buffer[s->bie_len++ - 20] = data[(*cnt)++];
+    if (s->bie_len < 20 + 1728) 
+      return JBG_EAGAIN;
+    if (!s->dppriv || s->dppriv == jbg_dptable)
+      s->dppriv = checked_malloc(sizeof(char) * 1728);
+    jbg_dppriv2int(s->dppriv, s->buffer);
+  }
+
+  /*
+   * BID processing loop
+   */
+  
+  while (*cnt < len) {
+
+    /* process floating marker segments */
+
+    /* skip COMMENT contents */
+    if (s->comment_skip) {
+      if (s->comment_skip <= len - *cnt) {
+	*cnt += s->comment_skip;
+	s->comment_skip = 0;
+      } else {
+	s->comment_skip -= len - *cnt;
+	*cnt = len;
+      }
+      continue;
+    }
+
+    /* load complete marker segments into s->buffer for processing */
+    if (s->buf_len > 0) {
+      assert(s->buffer[0] == MARKER_ESC);
+      while (s->buf_len < 2 && *cnt < len)
+	s->buffer[s->buf_len++] = data[(*cnt)++];
+      if (s->buf_len < 2) continue;
+      switch (s->buffer[1]) {
+      case MARKER_COMMENT: required_length = 6; break;
+      case MARKER_ATMOVE:  required_length = 8; break;
+      case MARKER_NEWLEN:  required_length = 6; break;
+      case MARKER_ABORT:
+      case MARKER_SDNORM:
+      case MARKER_SDRST:   required_length = 2; break;
+      case MARKER_STUFF:
+	/* forward stuffed 0xff to arithmetic decoder */
+	s->buf_len = 0;
+	decode_pscd(s, s->buffer, 2);
+	continue;
+      default:
+	return JBG_EMARKER;
+      }
+      while (s->buf_len < required_length && *cnt < len)
+	s->buffer[s->buf_len++] = data[(*cnt)++];
+      if (s->buf_len < required_length) continue;
+      /* now the buffer is filled with exactly one marker segment */
+      switch (s->buffer[1]) {
+      case MARKER_COMMENT:
+	s->comment_skip = 
+	  (((long) s->buffer[2] << 24) | ((long) s->buffer[3] << 16) |
+	   ((long) s->buffer[4] <<  8) | (long) s->buffer[5]);
+	break;
+      case MARKER_ATMOVE:
+	if (s->at_moves < JBG_ATMOVES_MAX) {
+	  s->at_line[s->at_moves] =
+	    (((long) s->buffer[2] << 24) | ((long) s->buffer[3] << 16) |
+	     ((long) s->buffer[4] <<  8) | (long) s->buffer[5]);
+	  s->at_tx[s->at_moves] = (signed char) s->buffer[6];
+	  s->at_ty[s->at_moves] = s->buffer[7];
+	  if (s->at_tx[s->at_moves] < - (int) s->mx ||
+	      s->at_tx[s->at_moves] >   (int) s->mx ||
+	      s->at_ty[s->at_moves] >   (int) s->my ||
+	      (s->at_ty[s->at_moves] == 0 && s->at_tx[s->at_moves] < 0))
+	    return JBG_EINVAL;
+	  s->at_moves++;
+	} else
+	  return JBG_EINVAL;
+	break;
+      case MARKER_NEWLEN:
+	y = (((long) s->buffer[2] << 24) | ((long) s->buffer[3] << 16) |
+	     ((long) s->buffer[4] <<  8) | (long) s->buffer[5]);
+	if (y > s->yd || !(s->options & JBG_VLENGTH))
+	  return JBG_EINVAL;
+	s->yd = y;
+	/* calculate again number of stripes that will be required */
+	s->stripes = 
+	  ((s->yd >> s->d) +
+	   ((((1UL << s->d) - 1) & s->xd) != 0) + s->l0 - 1) / s->l0;
+	break;
+      case MARKER_ABORT:
+	return JBG_EABORT;
+	
+      case MARKER_SDNORM:
+      case MARKER_SDRST:
+	/* decode final pixels based on trailing zero bytes */
+	decode_pscd(s, s->buffer, 2);
+
+	arith_decode_init(s->s[s->ii[index[s->order & 7][PLANE]]] + 
+			  s->ii[index[s->order & 7][LAYER]] - s->dl,
+			  s->ii[index[s->order & 7][STRIPE]] != s->stripes - 1
+			  && s->buffer[1] != MARKER_SDRST);
+	
+	s->reset[s->ii[index[s->order & 7][PLANE]]]
+	  [s->ii[index[s->order & 7][LAYER]] - s->dl] =
+	    (s->buffer[1] == MARKER_SDRST);
+	
+	/* prepare for next SDE */
+	s->x = 0;
+	s->i = 0;
+	s->pseudo = 1;
+	s->at_moves = 0;
+	
+	/* increment layer/stripe/plane loop variables */
+	/* start and end value for each loop: */
+	is[index[s->order & 7][STRIPE]] = 0;
+	ie[index[s->order & 7][STRIPE]] = s->stripes - 1;
+	is[index[s->order & 7][LAYER]] = s->dl;
+	ie[index[s->order & 7][LAYER]] = s->d;
+	is[index[s->order & 7][PLANE]] = 0;
+	ie[index[s->order & 7][PLANE]] = s->planes - 1;
+	i = 2;  /* index to innermost loop */
+	do {
+	  j = 0;  /* carry flag */
+	  if (++s->ii[i] > ie[i]) {
+	    /* handling overflow of loop variable */
+	    j = 1;
+	    if (i > 0)
+	      s->ii[i] = is[i];
+	  }
+	} while (--i >= 0 && j);
+
+	s->buf_len = 0;
+	
+	/* check whether this have been all SDEs */
+	if (j) {
+	  s->bie_len = 0;
+	  return JBG_EOK;
+	}
+
+	/* check whether we have to abort because of xmax/ymax */
+	if (index[s->order & 7][LAYER] == 0 && i < 0) {
+	  /* LAYER is the outermost loop and we have just gone to next layer */
+	  if (jbg_ceil_half(s->xd, s->d - s->ii[0]) > s->xmax ||
+	      jbg_ceil_half(s->yd, s->d - s->ii[0]) > s->ymax) {
+	    s->xmax = 4294967295UL;
+	    s->ymax = 4294967295UL;
+	    return JBG_EOK_INTR;
+	  }
+	  if (s->ii[0] > (unsigned long) s->dmax) {
+	    s->dmax = 256;
+	    return JBG_EOK_INTR;
+	  }
+	}
+
+	break;
+      }
+      s->buf_len = 0;
+
+    } else if (data[*cnt] == MARKER_ESC)
+      s->buffer[s->buf_len++] = data[(*cnt)++];
+
+    else {
+
+      /* we have found PSCD bytes */
+      *cnt += decode_pscd(s, data + *cnt, len - *cnt);
+      if (*cnt < len && data[*cnt] != 0xff) {
+#ifdef DEBUG
+	fprintf(stderr, "PSCD was longer than expected, unread bytes "
+		"%02x %02x %02x %02x ...\n", data[*cnt], data[*cnt+1],
+		data[*cnt+2], data[*cnt+3]);
+#endif
+	return JBG_EINVAL;
+      }
+      
+    }
+  }  /* of BID processing loop 'while (*cnt < len) ...' */
+
+  return JBG_EAGAIN;
+}
+
+
+/*
+ * After jbg_dec_in() returned JBG_EOK or JBG_EOK_INTR, you can call this
+ * function in order to find out the width of the image.
+ */
+long jbg_dec_getwidth(const struct jbg_dec_state *s)
+{
+  if (s->d < 0)
+    return -1;
+  if (index[s->order & 7][LAYER] == 0) {
+    if (s->ii[0] < 1)
+      return -1;
+    else
+      return jbg_ceil_half(s->xd, s->d - (s->ii[0] - 1));
+  }
+
+  return s->xd;
+}
+
+
+/*
+ * After jbg_dec_in() returned JBG_EOK or JBG_EOK_INTR, you can call this
+ * function in order to find out the height of the image.
+ */
+long jbg_dec_getheight(const struct jbg_dec_state *s)
+{
+  if (s->d < 0)
+    return -1;
+  if (index[s->order & 7][LAYER] == 0) {
+    if (s->ii[0] < 1)
+      return -1;
+    else
+      return jbg_ceil_half(s->yd, s->d - (s->ii[0] - 1));
+  }
+  
+  return s->yd;
+}
+
+
+/*
+ * After jbg_dec_in() returned JBG_EOK or JBG_EOK_INTR, you can call this
+ * function in order to get a pointer to the image.
+ */
+unsigned char *jbg_dec_getimage(const struct jbg_dec_state *s, int plane)
+{
+  if (s->d < 0)
+    return NULL;
+  if (index[s->order & 7][LAYER] == 0) {
+    if (s->ii[0] < 1)
+      return NULL;
+    else
+      return s->lhp[(s->ii[0] - 1) & 1][plane];
+  }
+  
+  return s->lhp[s->d & 1][plane];
+}
+
+
+/*
+ * After jbg_dec_in() returned JBG_EOK or JBG_EOK_INTR, you can call
+ * this function in order to find out the size in bytes of one
+ * bitplane of the image.
+ */
+long jbg_dec_getsize(const struct jbg_dec_state *s)
+{
+  if (s->d < 0)
+    return -1;
+  if (index[s->order & 7][LAYER] == 0) {
+    if (s->ii[0] < 1)
+      return -1;
+    else
+      return 
+	((jbg_ceil_half(s->xd, s->d - (s->ii[0] - 1)) + 7) / 8) *
+	jbg_ceil_half(s->yd, s->d - (s->ii[0] - 1));
+  }
+  
+  return ((s->xd + 7) / 8) * s->yd;
+}
+
+
+/*
+ * After jbg_dec_in() returned JBG_EOK or JBG_EOK_INTR, you can call
+ * this function in order to find out the size of the image that you
+ * can retrieve with jbg_merge_planes().
+ */
+long jbg_dec_getsize_merged(const struct jbg_dec_state *s)
+{
+  if (s->d < 0)
+    return -1;
+  if (index[s->order & 7][LAYER] == 0) {
+    if (s->ii[0] < 1)
+      return -1;
+    else
+      return 
+	jbg_ceil_half(s->xd, s->d - (s->ii[0] - 1)) *
+	jbg_ceil_half(s->yd, s->d - (s->ii[0] - 1)) *
+	((s->planes + 7) / 8);
+  }
+  
+  return s->xd * s->yd * ((s->planes + 7) / 8);
+}
+
+
+/* 
+ * The destructor function which releases any resources obtained by the
+ * other decoder functions.
+ */
+void jbg_dec_free(struct jbg_dec_state *s)
+{
+  int i;
+
+  if (s->d < 0 || s->s == NULL)
+    return;
+  s->d = -2;
+
+  for (i = 0; i < s->planes; i++) {
+    checked_free(s->s[i]);
+    checked_free(s->tx[i]);
+    checked_free(s->ty[i]);
+    checked_free(s->reset[i]);
+    checked_free(s->lntp[i]);
+    checked_free(s->lhp[0][i]);
+    checked_free(s->lhp[1][i]);
+  }
+  
+  checked_free(s->s);
+  checked_free(s->tx);
+  checked_free(s->ty);
+  checked_free(s->reset);
+  checked_free(s->lntp);
+  checked_free(s->lhp[0]);
+  checked_free(s->lhp[1]);
+
+  s->s = NULL;
+
+  return;
+}
+
+
+/*
+ * Split bigendian integer pixel field into separate bit planes. In the
+ * src array, every pixel is represented by a ((has_planes + 7) / 8) byte
+ * long word, most significant byte first. While has_planes describes
+ * the number of used bits per pixel in the source image, encode_plane
+ * is the number of most significant bits among those that we
+ * actually transfer to dest.
+ */
+void jbg_split_planes(unsigned long x, unsigned long y, int has_planes,
+		      int encode_planes,
+		      const unsigned char *src, unsigned char **dest,
+		      int use_graycode)
+{
+  unsigned bpl = (x + 7) / 8;           /* bytes per line in dest plane */
+  unsigned i, k = 8;
+  int p;
+  unsigned long line;
+  extern void *memset(void *s, int c, size_t n);
+  unsigned prev;     /* previous *src byte shifted by 8 bit to the left */
+  register int bits, msb = has_planes - 1;
+  int bitno;
+
+  /* sanity checks */
+  if (encode_planes > has_planes)
+    encode_planes = has_planes;
+  use_graycode = use_graycode != 0 && encode_planes > 1;
+  
+  for (p = 0; p < encode_planes; p++)
+    memset(dest[p], 0, bpl * y);
+  
+  for (line = 0; line < y; line++) {                 /* lines loop */
+    for (i = 0; i * 8 < x; i++) {                    /* dest bytes loop */
+      for (k = 0; k < 8 && i * 8 + k < x; k++) {     /* pixel loop */
+	prev = 0;
+	for (p = 0; p < encode_planes; p++) {        /* bit planes loop */
+	  /* calculate which bit in *src do we want */
+	  bitno = (msb - p) & 7;
+	  /* put this bit with its left neighbor right adjusted into bits */
+	  bits = (prev | *src) >> bitno;
+	  /* go to next *src byte, but keep old */
+	  if (bitno == 0)
+	    prev = *src++;
+	  /* make space for inserting new bit */
+	  dest[p][bpl * line + i] <<= 1;
+	  /* insert bit, if requested apply Gray encoding */
+	  dest[p][bpl * line + i] |= (bits ^ (use_graycode & (bits>>1))) & 1;
+	  /*
+	   * Theorem: Let b(n),...,b(1),b(0) be the digits of a
+	   * binary word and let g(n),...,g(1),g(0) be the digits of the
+	   * corresponding Gray code word, then g(i) = b(i) xor b(i+1).
+	   */
+	}
+	/* skip unused *src bytes */
+	for (;p < has_planes; p++)
+	  if (((has_planes - 1 - p) & 7) == 0)
+	    src++;
+      }
+    }
+    for (p = 0; p < encode_planes; p++)              /* right padding loop */
+      dest[p][bpl * (line + 1) - 1] <<= 8 - k;
+  }
+  
+  return;
+}
+
+/* 
+ * Merge the separate bit planes decoded by the JBIG decoder into an
+ * integer pixel field. This is essentially the counterpart to
+ * jbg_split_planes(). */
+void jbg_dec_merge_planes(const struct jbg_dec_state *s, int use_graycode,
+			  void (*data_out)(unsigned char *start, size_t len,
+					   void *file), void *file)
+{
+#define BUFLEN 4096
+  int bpp, bpl;
+  unsigned long line;
+  unsigned i, k = 8;
+  int p, q;
+  unsigned char buf[BUFLEN];
+  unsigned char *bp = buf;
+  unsigned char **src;
+  unsigned long x, y;
+  unsigned v;
+
+  /* sanity check */
+  use_graycode = use_graycode != 0;
+  
+  x = jbg_dec_getwidth(s);
+  y = jbg_dec_getheight(s);
+  if (x <= 0 || y <= 0)
+    return;
+  bpp = (s->planes + 7) / 8;   /* bytes per pixel in dest image */
+  bpl = (x + 7) / 8;           /* bytes per line in src plane */
+
+  if (index[s->order & 7][LAYER] == 0)
+    if (s->ii[0] < 1)
+      return;
+    else
+      src = s->lhp[(s->ii[0] - 1) & 1];
+  else
+    src = s->lhp[s->d & 1];
+  
+  for (line = 0; line < y; line++) {                    /* lines loop */
+    for (i = 0; i * 8 < x; i++) {                       /* src bytes loop */
+      for (k = 0; k < 8 && i * 8 + k < x; k++) {        /* pixel loop */
+	for (p = (s->planes-1) & ~7; p >= 0; p -= 8) {  /* dest bytes loop */
+	  v = 0;
+	  for (q = 0; q < 8 && p+q < s->planes; q++)    /* pixel bit loop */
+	    v = (v << 1) |
+	      (((src[p+q][bpl * line + i] >> (7 - k)) & 1) ^
+	       (use_graycode & v));
+	  *bp++ = v;
+	  if (bp - buf == BUFLEN) {
+	    data_out(buf, BUFLEN, file);
+	    bp = buf;
+	  }
+	}
+      }
+    }
+  }
+  
+  if (bp - buf > 0)
+    data_out(buf, bp - buf, file);
+  
+  return;
+}
diff --git a/converter/other/jbig/jbig.doc b/converter/other/jbig/jbig.doc
new file mode 100644
index 00000000..10eeda80
--- /dev/null
+++ b/converter/other/jbig/jbig.doc
@@ -0,0 +1,721 @@
+
+Using the JBIG-KIT library
+--------------------------
+
+Markus Kuhn -- 1998-04-10
+
+
+This text explains how to include the functions provided by the
+JBIG-KIT portable image compression library into your application
+software.
+
+
+1  Introduction to JBIG
+
+Below follows a short introduction into some technical aspects of the
+JBIG standard. More detailed information is provided in the
+"Introduction and overview" section of the JBIG standard. Information
+about how to obtain a copy of the standard is available on the
+Internet from <http://www.itu.ch/> or <http://www.iso.ch/>.
+
+Image data encoded with the JBIG algorithm is separated into planes,
+layers, and stripes. Each plane contains one bit per pixel. The number
+of planes stored in a JBIG data stream is the number of bits per
+pixel. Resolution layers are numbered from 0 to D with 0 being the
+layer with the lowest resolution and D the layer with the highest one.
+Each next higher resolution layer has exactly twice the number of rows
+and columns than the previous one. Layer 0 is encoded independently of
+any other data, all other resolution layers are encoded as only the
+difference between the next lower and the current layer. For
+applications that require very quick access to parts of an image it is
+possible to divide an image into several horizontal stripes. All
+stripes of one resolution layer have equal size except perhaps the
+final one. The number of stripes of an image is equal in all
+resolution layers and in all bit planes.
+
+The compressed data stream specified by the JBIG standard is called a
+bi-level image entity (BIE). A BIE consists of a 20-byte header,
+followed by an optional 1728-byte table (usually not present, except
+in special applications) followed by a sequence of stripe data
+entities (SDE). SDEs are the data blocks of which each encodes the
+content of one single stripe in one plane and resolution layer.
+Between the SDEs, other information blocks (called floating marker
+segments) can also be present, which change certain parameters of the
+algorithm in the middle of an image or contain additional application
+specific information. A BIE looks like this:
+
+
+          +------------------------------------------------+
+          |                                                |
+          |  20-byte header (with image size, #planes,     |
+          |  #layers, stripe size, first layer, options,   |
+          |  SDE ordering, ...)                            |
+          |                                                |
+          +------------------------------------------------+
+          |                                                |
+          |           optional 1728-byte table             |
+          |                                                |
+          +------------------------------------------------+
+          |                                                |
+          |              stripe data entity                |
+          |                                                |
+          +------------------------------------------------+
+          |                                                |
+          |       optional floating marker segments        |
+          |                                                |
+          +------------------------------------------------+
+          |                                                |
+          |              stripe data entity                |
+          |                                                |
+          +------------------------------------------------+
+            ...
+          +------------------------------------------------+
+          |                                                |
+          |              stripe data entity                |
+          |                                                |
+          +------------------------------------------------+
+
+
+One BIE can contain all resolution layers of an image, but it is also
+possible to store various resolution layers in several BIEs. The BIE
+header contains the number of the first and the last resolution layer
+stored in this BIE, as well as the size of the highest resolution
+layer stored in this BIE. Progressive coding is deactivated by simply
+storing the image in one single resolution layer.
+
+Different applications might have different requirements for the order
+in which the SDEs for stripes of various planes and layers are stored
+in the BIE, so all possible sensible orderings are allowed and
+indicated by four bits in the header.
+
+It is possible to use the raw BIE data stream as specified by the JBIG
+standard directly as the format of a file used for storing images.
+This is what the JBIG<->PBM conversion tools that are provided in this
+package as demonstration applications do. However as the BIE format
+has been designed for a large number of very different applications
+and also in order to allow efficient direct processing by special JBIG
+hardware chip implementations, the BIE header contains only the
+minimum amount of information absolutely required by the decompression
+algorithm. A large number of features expected from a good file format
+are missing in the BIE data stream:
+
+  - no "magic code" in the first few bytes to allow identification
+    of the file on a typeless file system as JBIG encoded and to allow
+    automatic distinction from other compression algorithms
+
+  - no standardized way to encode additional information like a textual
+    description, information about the meaning of various bit planes,
+    the physical size and resolution of the document, etc.
+
+  - a checksum that ensures image integrity
+
+  - encryption and signature mechanisms
+
+  - many things more
+
+Raw BIE data streams alone are consequently no suitable format for
+document archiving and exchange. A standard format for this purpose
+would typically combine a BIE representing the image data together
+with an additional header providing auxiliary information into one
+file. Existing established multi-purpose file formats with a rich set
+of auxiliary information attributes like TIFF could be extended easily
+so that they can also contain JBIG compressed data.
+
+On the other hand, in database applications for instance, a BIE might
+be directly stored in a variable length field. Auxiliary information
+on which efficient search operations are required would then be stored
+in other fields of the same record.
+
+
+2  Compressing an image
+
+2.1  Format of the source image
+
+For processing by the library, the image has to be present in memory
+as separate bitmap planes. Each byte of a bitmap contains eight
+pixels, the most significant bit represents the leftmost of these
+pixels. Each line of a bitmap has to be stored in an integral number
+of bytes. If the image width is not an integral multiple of eight,
+then the final byte has to be padded with zero bits.
+
+For example the 23x5 pixels large single plane image:
+
+   .XXXXX..XXX...X...XXX..
+   .....X..X..X..X..X.....
+   .....X..XXX...X..X.XXX.
+   .X...X..X..X..X..X...X.
+   ..XXX...XXX...X...XXX..
+
+is represented by the 15 bytes
+
+   01111100 11100010 00111000
+   00000100 10010010 01000000
+   00000100 11100010 01011100
+   01000100 10010010 01000100
+   00111000 11100010 00111000
+
+or in hexadecimal notation
+
+   7c e2 38 04 92 40 04 e2 5c 44 92 44 38 e2 38
+
+This is the format used in binary PBM files and it can also be be
+handled directly by the Xlib library of the X Window System.
+
+As JBIG can also handle images with several bit planes, the JBIG-KIT
+library functions accept and return always arrays of pointers to
+bitmaps with one pointer per plane.
+
+For single plane images, the standard recommends that a 0 pixel
+represents the background and a 1 pixel represents the foreground
+color of an image, i.e. 0 is white and 1 is black for scanned paper
+documents. For images with several bits per pixel, the JBIG standard
+makes no recommendations about how various colors should be encoded.
+
+For greyscale images, by using a Gray code instead of a simple binary
+weighted representation of the pixel intensity, some increase in
+coding efficiency can be reached.
+
+A Gray code is also a binary representation of integer numbers, but
+has the property that the representations of the integer numbers i and
+(i+1) differ always in exactly one single bit. For example, the
+numbers 0 to 7 can be represented in normal binary code and Gray code
+as in the following table:
+
+                           normal
+              number    binary code     Gray code
+            ---------------------------------------
+                0           000            000
+                1           001            001
+                2           010            011
+                3           011            010
+                4           100            110
+                5           101            111
+                6           110            101
+                7           111            100
+
+The form of Gray code shown above has the property that the second
+half of the code (numbers 4 - 7) is simply the mirrored first half
+(numbers 3 - 0) with the first bit set to one. This way, arbitrarily
+large Gray codes can be generated quickly by mirroring the above
+example and prefixing the first half with zeros and the second half
+with ones as often as required. In greyscale images, it is common
+practise to use the all-0 code for black and the all-1 code for white.
+
+No matter whether a Gray code or a binary code is used for encoding a
+pixel intensity in several bit planes, it always makes sense to store
+the most significant (leftmost) bit in plane 0, which is transmitted
+first. This way, a decoder could increase the precision of the
+displayed pixel intensities while data is still being received and the
+basic structure of the image will become visible as early as possible
+during the transmission.
+
+
+2.2  A simple compression application
+
+In order to use JBIG-KIT in your application, just link libjbig.a to
+your executable (on Unix systems just add -ljbig and -L. to the
+command line options of your compiler, on other systems you will have
+to write a new Makefile anyway), copy the file jbig.h into your source
+directory and put the line
+
+  #include "jbig.h"
+
+into your source code.
+
+The library interface follows the concepts of object-oriented
+programming. You have to declare a variable (object)
+
+  struct jbg_enc_state se;
+
+which contains the current status of an encoder. Then you initialize
+the encoder by calling the constructor function
+
+  void jbg_enc_init(struct jbg_enc_state *s, unsigned long x, unsigned long y,
+                    int pl, unsigned char **p,
+                    void (*data_out)(unsigned char *start, size_t len,
+                                     void *file),
+                    void *file);
+
+The parameters have the following meaning:
+
+  s             A pointer to the jbg_enc_state structure which you want
+                to initialize.
+
+  x             The width of your image.
+
+  y             The height of your image.
+
+  pl            the number of bitmap planes you want to encode.
+
+  p             A pointer to an array of pl pointers, where each is again
+                pointing to the first byte of a bitmap as described in
+                section 2.1.
+
+  data_out      This is a call-back function which will be called during
+                the compression process by libjbig in order to deliver
+                the BIE data to the application. The parameters of the
+                function data_out are a pointer start to the new block of
+                data to be delivered as well as the number len of delivered
+                bytes. The pointer file is transparently delivered to
+                data_out as specified in jbg_enc_init(). Usually, data_out
+                will write the BIE portion to a file, send it to a
+                network connection or append it to some memory buffer.
+
+  file          A pointer parameter which is transparently passed to
+                data_out() and allows data_out() to distinguish by which
+                compression task it has been called in multi-threaded
+                applications.
+
+In the simplest case, the compression is then started by calling the
+function
+
+  void jbg_enc_out(struct jbg_enc_state *s);
+
+which will deliver the complete BIE to data_out(). After this, a call
+to the destructor function
+
+  void jbg_enc_free(struct jbg_enc_state *s);
+
+will release any memory allocated by the previous functions.
+
+
+A minimal example application which sends the BIE of the above example
+bitmap to stdout looks like this:
+
+---------------------------------------------------------------------------
+/* A sample JBIG encoding application */
+
+#include <stdio.h>
+#include "jbig.h"
+
+void output_bie(unsigned char *start, size_t len, void *file)
+{
+  fwrite(start, 1, len, (FILE *) file);
+  
+  return;
+}
+
+int main()
+{
+  unsigned char bitmap[15] = {
+    /* 23 x 5 pixels, "JBIG" */
+    0x7c, 0xe2, 0x38, 0x04, 0x92, 0x40, 0x04, 0xe2,
+    0x5c, 0x44, 0x92, 0x44, 0x38, 0xe2, 0x38
+  };
+  unsigned char *bitmaps[1] = { bitmap };
+  struct jbg_enc_state se;
+  
+  jbg_enc_init(&se, 23, 5, 1, bitmaps, 
+	       output_bie, stdout);              /* initialize encoder */
+  jbg_enc_out(&se);                                    /* encode image */
+  jbg_enc_free(&se);                    /* release allocated resources */
+  
+  return 0;
+}
+---------------------------------------------------------------------------
+
+This software produces a 42 byte long BIE. (JBIG is not very good at
+compressing extremely small images like in this example, because the
+arithmetic encoder requires some startup data in order to generate
+reasonable statistics which influence the compression process and
+because there is some header overhead.)
+
+
+2.3  More about compression
+
+If jbg_enc_out() is called directly after jbg_enc_init(), the
+following default values are used for various compression parameters:
+
+  - Only one single resolution layer is used, i.e. no progressive
+    mode.
+
+  - The number of lines per stripe is selected so that approximately
+    35 stripes per image are used (as recommended in annex C of the
+    standard together with the suggested adaptive template change
+    algorithm). However not less than 2 and not more than 128 lines
+    are used in order to stay within the suggested minimum parameter
+    support range specified in annex A of the standard).
+
+  - All optional parts of the JBIG algorithm are activated (TPBON,
+    TPDON and DPON).
+
+  - The default resolution reduction table and the default deterministic
+    prediction tables are used
+
+  - The maximal vertical offset of the adaptive template pixel is 0
+    and the maximal horizontal offset is 8 (mx = 8, my = 0).
+
+In order to change any of these default parameters, additional
+functions have to be called between jbg_enc_init() and jbg_enc_out().
+
+In order to activate progressive encoding, it is possible to specify
+with 
+
+  void jbg_enc_layers(struct jbg_enc_state *s, int d);
+
+the number d of differential resolution layers which shall be encoded
+in addition to the lowest resolution layer 0. For example, if a 300
+dpi document has to be stored and the lowest resolution layer shall
+have 75 dpi so that a screen previewer can directly decompress only
+the required resolution, then a call
+
+  jbg_enc_layers(&se, 2);
+
+will cause three resolution layers with 75, 150 and 300 dots per inch.
+
+If the application does not know what typical resolutions are used and
+simply wants to ensure that the lowest resolution layer will fit into
+a given maximal window size, then as an alternative, a call to
+
+  int jbg_enc_lrlmax(struct jbg_enc_state *s, unsigned long mwidth,
+                     unsigned long mheight);
+
+will cause the library to automatically determine the suitable number
+of resolutions so that the lowest resolution layer 0 will not be
+larger than mwidth x mheight pixels. E.g. if one wants to ensure that
+systems with a 640 x 480 pixel large screen can decode the required
+resolution directly, then call
+
+  jbg_enc_lrlmax(&se, 640, 480);
+
+The return value is the number of differential layers selected.
+
+After the number of resolution layers has been specified by calls to
+jbg_enc_layers() or jbg_enc_lrlmax(), by default all these layers will
+be written into the BIE. This can be changed with a call to
+
+  int  jbg_enc_lrange(struct jbg_enc_state *s, int dl, int dh);
+
+Parameter dl specifies the lowest resolution layer and dh the highest
+resolution layer that will appear in the BIE. If e.g. only layer 0
+shall be written to the first BIE and layer 1 and 2 shall be written
+to a second one, then before writing the first BIE, one calls
+
+  jbg_enc_lrange(&se, 0, 0);
+
+and before writing the second BIE with jbg_enc_out(), one calls
+
+  jbg_enc_lrange(&se, 1, 2);
+
+If any of the parameters is negative, it will be ignored. The return
+value is the total number of differential layers which will represent
+the input image. This way, jbg_enc_lrange(&se, -1, -1) can be used to
+query the layer of the full image.
+
+A number of other more exotic options of the JBIG algorithm can be
+modified by calling
+
+  void jbg_enc_options(struct jbg_enc_state *s, int order, int options,
+                       long l0, int mx, int my);
+
+before calling jbg_enc_out().
+
+The order parameter can be a combination of the bits JBG_HITOLO,
+JBG_SEQ, JBG_ILEAVE and JBG_SMID and it determines in which order
+the SDEs are stored in the BIE. The bits have the following meaning:
+
+  JBG_HITOLO   Usually, the lower resolution layers are stored before
+               the higher resolution layers, so that a decoder can
+               already start to display a low resolution version of
+               the full image once a prefix of the BIE has been
+               received. When this bit is set however, the BIE will
+               contain the higher layers before the lower layers. This
+               avoids additional buffer memory in the encoder and is
+               intended for applications where the encoder is connected
+               to a database which can easily reorder the SDEs before
+               sending them to a decoder. Warning: JBIG decoders are
+               not expected to support the HITOLO option (e.g. the
+               JBIG-KIT decoder does currently not) so you should
+               normally not use it.
+
+  JBG_SEQ      Usually, at first all stripes of one resolution layer
+               are written to the BIE and then all stripes of the next
+               layer, and so on. When the SEQ bit is set however, then
+               all layers of the first stripe will be written,
+               followed by all layers of the second stripe, etc. This
+               option also should normally never be required and is
+               not supported by the current JBIG-KIT decoder.
+
+  JBG_SMID     In case there exist several bit planes, then the order of
+               the stripes is determined by 3 loops over all stripes,
+               all planes and all layers. When SMID is set, the loop
+               over all stripes is the middle loop.
+
+  JBG_ILEAVE   If this bit is set, then at first all layers of one
+               plane are written before the encoder starts with the next
+               plane.
+
+The above description might be somewhat confusing, but the following
+table (see also Table 11 in ITU-T T.82) makes clear how the three bits
+JBG_SEQ, JBIG_ILEAVE and JBG_SMID influence the ordering of the loops
+over all stripes, planes and layers:
+
+
+                                                 Loops:
+    JBG_SEQ   JBG_ILEAVE   JBG_SMID   |  Outer   Middle    Inner
+  ------------------------------------+---------------------------
+       0           0           0      |    p        d        s
+       0           1           0      |    d        p        s
+       0           1           1      |    d        s        p
+       1           0           0      |    s        p        d
+       1           0           1      |    p        s        d
+       1           1           0      |    s        d        p
+
+                                       p: plane, s: stripe, d: layer
+
+
+By default, the order combination JBG_ILEAVE | JBG_SMID is used.
+
+The options value can contain the following bits, which activate
+some of the optional algorithms defined by JBIG:
+
+  JBG_LRLTWO     Normally, in the lowest resolution layer, pixels
+                 from three lines around the next pixel are used
+                 in order to determine the context in which the next
+                 pixel is encoded. Some people in the JBIG committee
+                 seem to have argued that using only 2 lines will
+                 make software implementations a little bit faster,
+                 however others have argued that using only two lines
+                 will decrease compression efficiency by around 5%.
+                 As you might expect from a committee, now both
+                 alternatives are allowed and if JBG_LRLTWO is set,
+                 the slightly faster but 5% less well compressing two
+                 line alternative is selected. God bless the committees.
+                 Although probably nobody will ever need this option,
+                 it has been implemented in JBIG-KIT and is off by
+                 default.
+
+  JBG_TPDON      This activates the "typical prediction" algorithm
+                 for differential layers which avoids that large
+                 areas of equal color have to be encoded at all.
+                 This is on by default and there is no good reason to
+                 switch it off except for debugging or preparing data
+                 for cheap JBIG hardware which does not support this
+                 option.
+
+  JBG_TPBON      Like JBG_TPDON this activates the "typical prediction"
+                 algorithm in the lowest resolution layer. Also activated
+                 by default.
+
+  JBG_DPON       This bit activates for the differential resolution
+                 layers the "deterministic prediction" algorithm,
+                 which avoids that higher resolution layer pixels are
+                 encoded when their value can already be determined
+                 with the knowledge of the neighbor pixels, the
+                 corresponding lower resolution pixels and the
+                 resolution reduction algorithm. This is also
+                 activated by default and one only might perhaps want
+                 to deactivate it if the default resolution reduction
+                 algorithm is replaced by a new one.
+
+  JBG_DELAY_AT   Use a slightly less efficient algorithm to determine
+                 when an adaptive template change is necessary. With
+                 this bit set, the encoder output is compatible to the
+                 conformance test examples in cause 7.2 of ITU-T T.82.
+                 Then all adaptive template changes are delayed until
+                 the first line of the next stripe. This option is by
+                 default deactivated and only required for passing a
+                 special compatibility test suite.
+
+In addition, parameter l0 in jbg_enc_options() allows you to specify
+the number of lines per stripe in resolution layer 0. The parameters
+mx and my change the maximal offset allowed for the adaptive template
+pixel. The JBIG-KIT implementation allows currently a maximal mx value
+of 23 in the encoder and 32 in the decoder. Parameter my is at the
+moment ignored and always set to 0. As the standard requires of all
+decoder implementations only a maximum supported mx = 16 and my = 0,
+higher values should normally be avoided in order to guarantee
+interoperability. Default is mx = 8 and my = 0. If any of the
+parameters order, options, l0, mx or my is negative, then this value
+is ignored and the current value stays unmodified.
+
+The resolution reduction and deterministic prediction tables can also
+be replaced. However as these options are anyway only for experts,
+please have a look at the source code of jbg_enc_out() and the struct
+members dppriv and res_tab of struct jbg_enc_state for the details of
+how to do this in case you really need it. The functions
+jbg_int2dppriv and jbg_dppriv2int are provided in order to convert the
+DPTABLE data from the format used in the standard into the more
+efficient format used internally by JBIG-KIT.
+
+If you want to encode a greyscale image, you can use the library
+function
+
+  void jbg_split_planes(unsigned long x, unsigned long y, int has_planes,
+                        int encode_planes,
+                        const unsigned char *src, unsigned char **dest,
+                        int use_graycode);
+
+It separates an image in which each pixel is represented by one or
+more bytes into separate bitplanes. The dest array of pointers to
+these bitplanes can then be handed over to jbg_enc_init(). The
+variables x and y specify the width and height of the image in pixels,
+and has_planes specifies how many bits per pixel are used. As each
+pixel is represented by an integral number of consecutive bytes, of
+which each contains up to eight bits, the total length of the input
+image array src[] will therefore be x * y * ((has_planes + 7) / 8)
+bytes. The pixels are stored as usually in English reading order, and
+for each pixel the integer value is stored with the most significant
+byte coming first (Bigendian). This is exactly the format used in raw
+PGM files. In encode_planes, the number of bitplanes that shall be
+extracted can be specified. This allows for instance to extract only
+the most significant 8 bits of a 12-bit image, where each pixel is
+represented by two bytes, by specifying has_planes = 12 and
+encode_planes = 8. If use_graycode is zero, then the binary code of
+the pixel integer values will be used instead of the Gray code. Plane
+0 contains always the most significant bit.
+
+
+3  Decompressing an image
+
+Like with the compression functions, if you want to use the JBIG-KIT
+library, you have to put the line
+
+  #include "jbig.h"
+
+into your source code and link your executable with libjbig.a.
+
+The state of a JBIG decoder is stored completely in a struct and you
+will have to define a variable like
+
+  struct jbg_dec_state sd;
+
+which is initialized by a call to
+
+  void jbg_dec_init(struct jbg_dec_state *s);
+
+After this, you can directly start to pass data from the BIE to the decoder
+by calling the function
+
+  int jbg_dec_in(struct jbg_dec_state *s, unsigned char *data, size_t len,
+                 size_t *cnt);
+
+The pointer data points to the first byte of a data block with length
+len, which contains bytes from a BIE. It is not necessary to pass a
+whole BIE at once to jbg_dec_in(), it can arrive fragmented in any way
+by calling jbg_dec_in() several times. It is also possible to send
+several BIEs concatenated to jbg_dec_in(), however these then have to
+fit together. If you send several BIEs to the decoder, the lowest
+resolution layer in each following BIE has to be the highest
+resolution layer in the previous BIE plus one and the image sizes and
+number of planes also have to fit together, otherwise jbg_dec_in()
+will return the error JBG_ENOCONT after the header of the new BIE has
+been received completely.
+
+If pointer cnt is not NULL, then the number of bytes actually read
+from the data block is stored there. In case the data block did not
+contain the end of the BIE, then the value JBG_EAGAIN will be returned
+and *cnt equals len.
+
+Once the end of a BIE has been reached, the return value of
+jbg_dec_in() will be JBG_EOK. After this has happened, the functions
+and macros
+
+  long jbg_dec_getwidth(struct jbg_dec_state *s);
+  long jbg_dec_getheight(struct jbg_dec_state *s);
+  int jbg_dec_getplanes(struct jbg_dec_state *s);
+  unsigned char *jbg_dec_getimage(struct jbg_dec_state *s, int plane);
+  long jbg_dec_getsize(struct jbg_dec_state *s);
+
+can be used to query the dimensions of the now completely decoded
+image and to get a pointer to all bitmap planes. The bitmaps are
+stored as described in section 2.1. The function jbg_dec_getsize()
+calculates the number of bytes which one bitmap requires.
+
+The function
+
+  void jbg_dec_merge_planes(const struct jbg_dec_state *s, int use_graycode,
+                            void (*data_out)(unsigned char *start, size_t len,
+                                             void *file), void *file);
+
+allows you to merge the bitplanes that can be accessed individually
+with jbg_dec_getimage() into an array with one or more bytes per pixel
+(i.e., the format provided to jbg_split_planes()). If use_graycode is
+zero, then a binary encoding will be used. The output array will be
+delivered via the callback function data_out, exactly in the same way
+in which the encoder provides the BIE. The function
+
+  long jbg_dec_getsize_merged(const struct jbg_dec_state *s);
+
+determines how long the data array delivered by jbg_dec_merge_planes()
+is going to be.
+
+Before calling jbg_dec_in() the first time, it is possible to specify with
+a call to
+
+  void jbg_dec_maxsize(struct jbg_dec_state *s, unsigned long xmax,
+                       unsigned long ymax);
+
+an abort criterion for progressively encoded images. For instance if an
+application will display a whole document on a screen which is 1024 x
+768 pixels large, then this application should call
+
+  jbg_dec_maxsize(&sd, 1024, 768);
+
+before the decoding process starts. If the image has been encoded in
+progressive mode (i.e. with several resolution layers), then the
+decoder will stop with a return value JBG_EOK_INTR after the largest
+resolution layer that is still smaller than 1024 x 768. However this
+is no guarantee that the image which can then be read out using
+jbg_dec_getimage(), etc. is really not larger than the specified
+maximal size. The application will have to check the size of the
+image, because the decoder does not automatically apply a resolution
+reduction if no suitable resolution layer is available in the BIE.
+
+If jbg_dec_in() returned JBG_EOK_INTR or JBG_EOK, then it is possible
+to continue calling jbg_dec_in() with the remaining data in order to
+either decode the remaining resolution layers of the current BIE or in
+order to add another BIE with additional resolution layers. In both
+cases, after jbg_dec_in() returned JBG_EOK_INTR or JBG_EOK, *cnt is
+probably not equal to len and the remainder of the data block which
+has not yet been processed by the decoder has to be delivered to
+jbg_dec_in() again.
+
+If any other return value than JBG_EOK, JBG_EOK_INTR or JBG_EAGAIN
+has been returned by jbg_dec_in(), then an error has occurred and
+
+  void jbg_dec_free(struct jbg_dec_state *s);
+
+should be called in order to release any allocated memory. The
+destructor jbg_dec_free() should of course also be called, once the
+decoded bitmap returned by jbg_dec_getimage() is no longer required
+and the memory can be released.
+
+The function
+
+  const char *jbg_strerror(int errnum, int language);
+
+returns a pointer to a short single line test message which explains
+the return value of jbg_dec_in(). This message can be used in order to
+provide the user a brief informative message about what when wrong
+while decompressing the JBIG image. The error messages are available
+in several languages and in several character sets. Currently
+supported are the following values for the language parameter:
+
+  JBG_EN              English messages in ASCII
+  JBG_DE_8859_1       German messages in ISO 8859-1 Latin 1 character set
+  JBG_DE_UTF_8        German messages in ISO 10646/Unicode UTF-8 encoding
+
+
+The current implementation of the JBIG-KIT decoder has the following
+limitations:
+
+  - The maximal horizontal offset mx of the adaptive template pixel
+    must not be larger than 32 and the maximal vertical offset must
+    be zero.
+
+  - HITOLO and SEQ bits must not be set in the order value.
+
+A more detailed description of the JBIG-KIT implementation is
+
+  Markus Kuhn: Effiziente Kompression von bi-level Bilddaten durch
+  kontextsensitive arithmetische Codierung. Studienarbeit, Lehrstuhl
+  für Betriebssysteme, IMMD IV, Universität Erlangen-Nürnberg,
+  Erlangen, July 1995. (German, 62 pages)
+  <http://www.cl.cam.ac.uk/~mgk25/kuhn-sta.pdf>
+
+Please quote the above if you use JBIG-KIT in your research project.
+
+*** Happy compressing ***
+
+[end]
diff --git a/converter/other/jbig/jbig.h b/converter/other/jbig/jbig.h
new file mode 100644
index 00000000..dd9a76f3
--- /dev/null
+++ b/converter/other/jbig/jbig.h
@@ -0,0 +1,267 @@
+/*
+ *  Header file for the portable free JBIG compression library
+ *
+ *  Markus Kuhn -- mkuhn@acm.org
+ *
+ *  $Id: jbig.h,v 1.9 1999-11-16 15:58:45+00 mgk25 Rel $
+ */
+
+#ifndef JBG_H
+#define JBG_H
+
+#include <stddef.h>
+
+/*
+ * JBIG-KIT version number
+ */
+
+#define JBG_VERSION    "1.1"
+
+/*
+ * Buffer block for SDEs which are temporarily stored by encoder
+ */
+
+#define JBG_BUFSIZE 4000
+
+struct jbg_buf {
+  unsigned char d[JBG_BUFSIZE];              /* one block of a buffer list */
+  int len;                             /* length of the data in this block */
+  struct jbg_buf *next;                           /* pointer to next block */
+  struct jbg_buf *previous;                   /* pointer to previous block *
+					       * (unused in freelist)      */
+  struct jbg_buf *last;     /* only used in list head: final block of list */
+  struct jbg_buf **free_list;   /* pointer to pointer to head of free list */
+};
+
+/*
+ * Maximum number of allowed ATMOVEs per stripe
+ */
+
+#define JBG_ATMOVES_MAX  64
+
+/*
+ * Option and order flags
+ */
+
+#define JBG_HITOLO     0x08
+#define JBG_SEQ        0x04
+#define JBG_ILEAVE     0x02
+#define JBG_SMID       0x01
+
+#define JBG_LRLTWO     0x40
+#define JBG_VLENGTH    0x20
+#define JBG_TPDON      0x10
+#define JBG_TPBON      0x08
+#define JBG_DPON       0x04
+#define JBG_DPPRIV     0x02
+#define JBG_DPLAST     0x01
+
+#define JBG_DELAY_AT   0x100  /* delay ATMOVE until the first line of the next
+			       * stripe. Option available for compatibility
+			       * with conformance test example in clause 7.2.*/
+
+
+/*
+ * Possible error code return values
+ */
+
+#define JBG_EOK        0
+#define JBG_EOK_INTR   1
+#define JBG_EAGAIN     2
+#define JBG_ENOMEM     3
+#define JBG_EABORT     4
+#define JBG_EMARKER    5
+#define JBG_ENOCONT    6
+#define JBG_EINVAL     7
+#define JBG_EIMPL      8
+
+/*
+ * Language code for error message strings (based on ISO 639 2-letter
+ * standard language name abbreviations).
+ */ 
+
+#define JBG_EN         0        /* English */
+#define JBG_DE_8859_1  1        /* German in ISO Latin 1 character set */
+#define JBG_DE_UTF_8   2        /* German in Unicode UTF-8 encoding */
+
+/*
+ * Status description of an arithmetic encoder
+ */
+
+struct jbg_arenc_state {
+  unsigned char st[4096];    /* probability status for contexts, MSB = MPS */
+  unsigned long c;                /* C register, base of coding intervall, *
+                                   * layout as in Table 23                 */
+  unsigned long a;      /* A register, normalized size of coding intervall */
+  long sc;        /* counter for buffered 0xff values which might overflow */
+  int ct;  /* bit shift counter, determines when next byte will be written */
+  int buffer;                /* buffer for most recent output byte != 0xff */
+  void (*byte_out)(int, void *); /* function which receives all PSCD bytes */
+  void *file;                              /* parameter passed to byte_out */
+};
+
+
+/*
+ * Status description of an arithmetic decoder
+ */
+
+struct jbg_ardec_state {
+  unsigned char st[4096];    /* probability status for contexts, MSB = MPS */
+  unsigned long c;                /* C register, base of coding intervall, *
+                                   * layout as in Table 25                 */
+  unsigned long a;      /* A register, normalized size of coding intervall */
+  int ct;     /* bit shift counter, determines when next byte will be read */
+  unsigned char *pscd_ptr;               /* pointer to next PSCD data byte */
+  unsigned char *pscd_end;                   /* pointer to byte after PSCD */
+  enum {
+    JBG_OK,                        /* symbol has been successfully decoded */
+    JBG_READY,             /* no more bytes of this PSCD required, marker  *
+		            * encountered, probably more symbols available */
+    JBG_MORE,          /* more PSCD data bytes required to decode a symbol */
+    JBG_MARKER   /* more PSCD data bytes required, ignored final 0xff byte */
+  } result;                              /* result of previous decode call */
+  int startup;                            /* controls initial fill of s->c */
+};
+
+#ifdef TEST_CODEC
+void arith_encode_init(struct jbg_arenc_state *s, int reuse_st);
+void arith_encode_flush(struct jbg_arenc_state *s);
+void arith_encode(struct jbg_arenc_state *s, int cx, int pix);
+void arith_decode_init(struct jbg_ardec_state *s, int reuse_st);
+int arith_decode(struct jbg_ardec_state *s, int cx);
+#endif
+
+ 
+/*
+ * Status of a JBIG encoder
+ */
+
+struct jbg_enc_state {
+  int d;                            /* resolution layer of the input image */
+  unsigned long xd, yd;    /* size of the input image (resolution layer d) */
+  int planes;                         /* number of different bitmap planes */
+  int dl;                       /* lowest resolution layer in the next BIE */
+  int dh;                      /* highest resolution layer in the next BIE */
+  unsigned long l0;                /* number of lines per stripe at lowest *
+                                    * resolution layer 0                   */
+  unsigned long stripes;    /* number of stripes required  (determ. by l0) */
+  unsigned char **lhp[2];    /* pointers to lower/higher resolution images */
+  int *highres;                 /* index [plane] of highres image in lhp[] */
+  int order;                                    /* SDE ordering parameters */
+  int options;                                      /* encoding parameters */
+  unsigned mx, my;                           /* maximum ATMOVE window size */
+  int *tx;       /* array [plane] with x-offset of adaptive template pixel */
+  char *dppriv;         /* optional private deterministic prediction table */
+  char *res_tab;           /* table for the resolution reduction algorithm */
+  struct jbg_buf ****sde;      /* array [stripe][layer][plane] pointers to *
+				* buffers for stored SDEs                  */
+  struct jbg_arenc_state *s;  /* array [planes] for arithm. encoder status */
+  struct jbg_buf *free_list; /* list of currently unused SDE block buffers */
+  void (*data_out)(unsigned char *start, size_t len, void *file);
+                                                    /* data write callback */
+  void *file;                            /* parameter passed to data_out() */
+  char *tp;    /* buffer for temp. values used by diff. typical prediction */
+};
+
+
+/*
+ * Status of a JBIG decoder
+ */
+
+struct jbg_dec_state {
+  /* data from BIH */
+  int d;                             /* resolution layer of the full image */
+  int dl;                            /* first resolution layer in this BIE */
+  unsigned long xd, yd;     /* size of the full image (resolution layer d) */
+  int planes;                         /* number of different bitmap planes */
+  unsigned long l0;                /* number of lines per stripe at lowest *
+				    * resolution layer 0                   */
+  unsigned long stripes;    /* number of stripes required  (determ. by l0) */
+  int order;                                    /* SDE ordering parameters */
+  int options;                                      /* encoding parameters */
+  int mx, my;                                /* maximum ATMOVE window size */
+  char *dppriv;         /* optional private deterministic prediction table */
+
+  /* loop variables */
+  unsigned long ii[3];  /* current stripe, layer, plane (outer loop first) */
+
+  /*
+   * Pointers to array [planes] of lower/higher resolution images.
+   * lhp[d & 1] contains image of layer d. 
+   */
+  unsigned char **lhp[2];
+
+  /* status information */
+  int **tx, **ty;   /* array [plane][layer-dl] with x,y-offset of AT pixel */
+  struct jbg_ardec_state **s;    /* array [plane][layer-dl] for arithmetic *
+				  * decoder status */
+  int **reset;     /* array [plane][layer-dl] remembers if previous stripe *
+		    * in that plane/resolution ended with SDRST.           */
+  unsigned long bie_len;                    /* number of bytes read so far */
+  unsigned char buffer[20]; /* used to store BIH or marker segments fragm. */
+  int buf_len;                                /* number of bytes in buffer */
+  unsigned long comment_skip;      /* remaining bytes of a COMMENT segment */
+  unsigned long x;              /* x position of next pixel in current SDE */
+  unsigned long i; /* line in current SDE (first line of each stripe is 0) */ 
+  int at_moves;                /* number of AT moves in the current stripe */
+  unsigned long at_line[JBG_ATMOVES_MAX];           /* lines at which an   *
+					             * AT move will happen */
+  int at_tx[JBG_ATMOVES_MAX], at_ty[JBG_ATMOVES_MAX]; /* ATMOVE offsets in *
+						       * current stripe    */
+  unsigned long line_h1, line_h2, line_h3;     /* variables of decode_pscd */
+  unsigned long line_l1, line_l2, line_l3;
+  int pseudo;         /* flag for TPBON/TPDON:  next pixel is pseudo pixel */
+  int **lntp;        /* flag [plane][layer-dl] for TP: line is not typical */
+
+  unsigned long xmax, ymax;         /* if possible abort before image gets *
+				     * larger than this size */
+  int dmax;                                      /* abort after this layer */
+};
+
+
+/* some macros (too trivial for a function) */
+
+#define jbg_dec_getplanes(s)     ((s)->planes)
+
+
+/* function prototypes */
+
+void jbg_enc_init(struct jbg_enc_state *s, unsigned long x, unsigned long y,
+		  int planes, unsigned char **p,
+		  void (*data_out)(unsigned char *start, size_t len,
+				   void *file),
+		  void *file);
+int jbg_enc_lrlmax(struct jbg_enc_state *s, unsigned long mwidth,
+		   unsigned long mheight);
+void jbg_enc_layers(struct jbg_enc_state *s, int d);
+int  jbg_enc_lrange(struct jbg_enc_state *s, int dl, int dh);
+void jbg_enc_options(struct jbg_enc_state *s, int order, int options,
+		     long l0, int mx, int my);
+void jbg_enc_out(struct jbg_enc_state *s);
+void jbg_enc_free(struct jbg_enc_state *s);
+
+void jbg_dec_init(struct jbg_dec_state *s);
+void jbg_dec_maxsize(struct jbg_dec_state *s, unsigned long xmax,
+		     unsigned long ymax);
+int  jbg_dec_in(struct jbg_dec_state *s, unsigned char *data, size_t len,
+		size_t *cnt);
+long jbg_dec_getwidth(const struct jbg_dec_state *s);
+long jbg_dec_getheight(const struct jbg_dec_state *s);
+unsigned char *jbg_dec_getimage(const struct jbg_dec_state *s, int plane);
+long jbg_dec_getsize(const struct jbg_dec_state *s);
+void jbg_dec_merge_planes(const struct jbg_dec_state *s, int use_graycode,
+			  void (*data_out)(unsigned char *start, size_t len,
+					   void *file), void *file);
+long jbg_dec_getsize_merged(const struct jbg_dec_state *s);
+void jbg_dec_free(struct jbg_dec_state *s);
+
+const char *jbg_strerror(int errnum, int language);
+void jbg_int2dppriv(unsigned char *dptable, const char *internal);
+void jbg_dppriv2int(char *internal, const unsigned char *dptable);
+unsigned long jbg_ceil_half(unsigned long x, int n);
+void jbg_split_planes(unsigned long x, unsigned long y, int has_planes,
+		      int encode_planes,
+		      const unsigned char *src, unsigned char **dest,
+		      int use_graycode);
+
+#endif /* JBG_H */
diff --git a/converter/other/jbig/jbig_tab.c b/converter/other/jbig/jbig_tab.c
new file mode 100644
index 00000000..55183503
--- /dev/null
+++ b/converter/other/jbig/jbig_tab.c
@@ -0,0 +1,428 @@
+/*
+ *  Probability estimation tables for the arithmetic encoder/decoder
+ *  given by ITU T.82 Table 24.
+ *
+ *  $Id: jbig_tab.c,v 1.6 1998-04-05 18:36:19+01 mgk25 Rel $
+ */
+
+short jbg_lsz[113] = {
+  0x5a1d, 0x2586, 0x1114, 0x080b, 0x03d8, 0x01da, 0x00e5, 0x006f,
+  0x0036, 0x001a, 0x000d, 0x0006, 0x0003, 0x0001, 0x5a7f, 0x3f25,
+  0x2cf2, 0x207c, 0x17b9, 0x1182, 0x0cef, 0x09a1, 0x072f, 0x055c,
+  0x0406, 0x0303, 0x0240, 0x01b1, 0x0144, 0x00f5, 0x00b7, 0x008a,
+  0x0068, 0x004e, 0x003b, 0x002c, 0x5ae1, 0x484c, 0x3a0d, 0x2ef1,
+  0x261f, 0x1f33, 0x19a8, 0x1518, 0x1177, 0x0e74, 0x0bfb, 0x09f8,
+  0x0861, 0x0706, 0x05cd, 0x04de, 0x040f, 0x0363, 0x02d4, 0x025c,
+  0x01f8, 0x01a4, 0x0160, 0x0125, 0x00f6, 0x00cb, 0x00ab, 0x008f,
+  0x5b12, 0x4d04, 0x412c, 0x37d8, 0x2fe8, 0x293c, 0x2379, 0x1edf,
+  0x1aa9, 0x174e, 0x1424, 0x119c, 0x0f6b, 0x0d51, 0x0bb6, 0x0a40,
+  0x5832, 0x4d1c, 0x438e, 0x3bdd, 0x34ee, 0x2eae, 0x299a, 0x2516,
+  0x5570, 0x4ca9, 0x44d9, 0x3e22, 0x3824, 0x32b4, 0x2e17, 0x56a8,
+  0x4f46, 0x47e5, 0x41cf, 0x3c3d, 0x375e, 0x5231, 0x4c0f, 0x4639,
+  0x415e, 0x5627, 0x50e7, 0x4b85, 0x5597, 0x504f, 0x5a10, 0x5522,
+  0x59eb
+};
+
+unsigned char jbg_nmps[113] = {
+    1,   2,   3,   4,   5,   6,   7,   8,
+    9,  10,  11,  12,  13,  13,  15,  16,
+   17,  18,  19,  20,  21,  22,  23,  24,
+   25,  26,  27,  28,  29,  30,  31,  32,
+   33,  34,  35,   9,  37,  38,  39,  40,
+   41,  42,  43,  44,  45,  46,  47,  48,
+   49,  50,  51,  52,  53,  54,  55,  56,
+   57,  58,  59,  60,  61,  62,  63,  32,
+   65,  66,  67,  68,  69,  70,  71,  72,
+   73,  74,  75,  76,  77,  78,  79,  48,
+   81,  82,  83,  84,  85,  86,  87,  71,
+   89,  90,  91,  92,  93,  94,  86,  96,
+   97,  98,  99, 100,  93, 102, 103, 104,
+   99, 106, 107, 103, 109, 107, 111, 109,
+  111
+};
+
+/*
+ * least significant 7 bits (mask 0x7f) of jbg_nlps[] contain NLPS value,
+ * most significant bit (mask 0x80) contains SWTCH bit
+ */
+unsigned char jbg_nlps[113] = {
+  129,  14,  16,  18,  20,  23,  25,  28,
+   30,  33,  35,   9,  10,  12, 143,  36,
+   38,  39,  40,  42,  43,  45,  46,  48,
+   49,  51,  52,  54,  56,  57,  59,  60,
+   62,  63,  32,  33, 165,  64,  65,  67,
+   68,  69,  70,  72,  73,  74,  75,  77,
+   78,  79,  48,  50,  50,  51,  52,  53,
+   54,  55,  56,  57,  58,  59,  61,  61,
+  193,  80,  81,  82,  83,  84,  86,  87,
+   87,  72,  72,  74,  74,  75,  77,  77,
+  208,  88,  89,  90,  91,  92,  93,  86,
+  216,  95,  96,  97,  99,  99,  93, 223,
+  101, 102, 103, 104,  99, 105, 106, 107,
+  103, 233, 108, 109, 110, 111, 238, 112,
+  240
+};
+
+/*
+ * Resolution reduction table given by ITU-T T.82 Table 17
+ */
+
+char jbg_resred[4096] = {
+  0,0,0,1,0,0,0,1,0,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,0,1,1,0,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,
+  0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,1,1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,
+  0,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,1,0,1,0,0,1,1,1,0,1,1,
+  0,0,0,1,0,0,0,1,0,0,1,0,0,0,1,1,0,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,1,1,1,0,1,0,1,0,0,1,1,1,0,1,1,0,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,0,1,0,0,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,1,0,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,0,1,0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,
+  1,0,0,1,0,0,1,1,0,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,
+  0,0,1,1,0,0,0,1,0,0,0,1,0,0,1,1,0,1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0,1,1,0,1,1,1,1,1,1,0,1,1,1,0,
+  0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,1,0,0,1,0,0,1,1,0,1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,
+  0,0,0,1,0,0,0,1,0,1,1,0,1,0,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,
+  1,1,1,0,1,0,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,
+  1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1,1,1,1,1,1,
+  0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,1,1,1,0,1,0,1,0,1,1,0,1,0,1,1,0,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,0,1,0,0,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,1,1,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,1,0,1,0,0,1,0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,1,0,0,1,1,1,1,1,1,0,1,1,1,1,0,1,1,
+  1,0,0,1,0,0,1,0,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,1,0,1,1,1,1,1,1,1,1,1,0,
+  0,0,1,0,1,1,1,1,0,0,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,
+  0,0,0,0,1,0,0,1,0,0,1,1,0,1,1,1,0,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,1,1,0,1,0,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,1,1,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,1,0,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,
+  0,0,1,0,0,1,1,1,0,1,1,1,1,1,1,1,0,1,1,1,1,0,1,1,0,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,1,
+  0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,1,1,0,0,1,0,0,1,1,
+  0,0,0,0,0,0,0,1,0,1,1,1,0,1,0,1,0,0,1,0,0,0,0,1,0,1,0,1,0,1,0,1,
+  0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,1,0,0,0,0,0,0,0,1,1,1,1,0,1,1,1,
+  0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,1,1,0,1,0,1,1,0,0,0,1,0,0,1,1,
+  0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,1,1,1,0,0,1,1,
+  0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,0,0,1,
+  0,0,1,0,0,1,1,1,0,0,0,0,1,0,0,1,0,0,0,1,1,1,1,0,1,0,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,1,1,0,
+  0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,1,1,1,0,1,1,1,
+  0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,1,1,0,1,0,0,0,1,1,0,1,0,0,0,0,1,1,1,1,0,0,1,1,1,0,1,1,0,0,1,1,
+  0,0,0,0,0,0,0,0,1,1,0,1,0,0,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,1,1,0,0,1,1,0,0,1,1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,1,1,1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,1,0,0,0,1,0,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,
+  0,0,0,1,0,0,1,0,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,0,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,
+  0,0,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,0,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,
+  0,0,1,1,1,1,1,1,0,0,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,
+  0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,1,1,0,1,1,
+  0,0,0,1,0,0,0,1,0,0,1,0,0,0,1,1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,1,1,1,0,1,0,1,0,0,1,0,1,0,1,1,0,1,1,1,0,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,1,0,1,0,1,1,0,1,1,0,1,1,1,1,1,1,1,
+  0,0,0,0,1,0,0,1,0,0,1,1,0,0,1,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,1,1,0,1,1,1,1,1,1,1,1,0,1,1,
+  1,0,1,0,1,0,0,1,1,0,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1,1,0,1,1,0,1,1,1,
+  0,0,1,0,0,0,0,1,0,0,0,0,0,0,1,1,0,1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1,1,1,0,1,1,0,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,1,0,1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,1,0,1,0,1,0,1,1,0,1,0,1,0,0,0,1,1,1,1,1,1,1,1,1,
+  1,1,1,0,1,0,0,0,1,1,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,
+  1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,1,1,0,1,1,
+  0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,1,0,1,0,1,0,0,1,1,0,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,1,1,1,0,1,0,1,0,0,1,0,1,0,0,1,0,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,1,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,1,0,1,0,0,0,1,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,
+  1,0,0,0,1,0,0,0,0,1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,1,1,1,0,1,1,0,
+  0,0,1,1,1,1,1,1,0,0,0,0,1,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1,1,1,0,1,1,1,1,1,1,1,
+  0,0,0,0,1,0,0,0,0,0,0,1,0,0,1,1,0,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,
+  0,0,1,0,1,0,1,1,0,0,1,0,1,1,1,1,0,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1,0,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,
+  0,0,1,0,1,0,1,1,0,1,1,1,1,1,1,1,0,0,1,1,1,0,1,1,0,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,1,
+  0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,
+  0,0,0,0,0,0,0,1,0,1,1,1,0,1,0,1,0,0,1,0,0,0,0,1,0,1,0,1,0,1,0,1,
+  0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,1,0,1,0,0,1,1,
+  0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,1,
+  0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,1,
+  0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,1,1,
+  1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,1,1,0,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,
+  0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,1,
+  0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,
+  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,
+  0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,
+  0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,1,0,1,1,1,1,1,1,1,
+  0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,1,
+  0,0,0,0,0,0,0,1,0,1,0,1,0,1,0,0,0,0,1,1,0,0,0,1,0,1,1,1,0,1,1,1
+};
+
+/*
+ * Deterministic prediction tables given by ITU-T T.82 tables
+ * 19 to 22. The table below is organized differently, the
+ * index bits are permutated for higher efficiency.
+ */
+
+char jbg_dptable[256 + 512 + 2048 + 4096] = {
+  /* phase 0: offset=0 */
+  0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,2,2,2,2,2,2,0,2,2,2,2,2,2,2,
+  0,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,2,2,2,2,2,2,0,2,0,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  /* phase 1: offset=256 */
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,2,2,2,2,2,0,2,0,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  0,2,2,2,2,1,2,1,2,2,2,2,1,1,1,1,2,0,2,0,2,2,2,2,0,2,0,2,2,2,2,2,
+  0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,2,2,2,2,0,2,2,2,2,2,2,2,
+  0,2,0,2,2,2,2,2,2,2,2,2,2,0,2,0,2,2,0,0,2,2,2,2,2,0,0,2,2,2,2,2,
+  0,2,2,2,2,1,2,1,2,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
+  1,2,1,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,1,1,2,2,2,2,2,0,2,2,2,2,2,2,
+  2,2,2,2,2,0,2,0,2,2,2,2,0,0,0,0,0,2,0,2,2,2,2,2,0,2,2,2,2,2,2,2,
+  0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,0,2,2,2,2,0,2,0,2,2,2,2,2,
+  2,2,2,2,2,1,1,1,2,2,2,2,1,1,1,1,1,2,1,2,2,2,2,2,2,2,2,2,2,2,2,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,0,1,2,0,2,0,2,2,2,2,2,0,2,0,2,2,2,2,1,
+  0,2,0,2,2,1,2,1,2,2,2,2,1,1,1,1,0,0,0,0,2,2,2,2,0,2,0,2,2,2,2,1,
+  2,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,2,2,0,0,0,2,2,2,2,2,
+  2,2,2,2,2,1,2,1,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1,2,1,2,2,2,2,1,
+  2,2,2,2,2,2,2,2,0,2,0,2,2,1,2,2,2,2,2,2,2,2,2,2,0,0,0,2,2,2,2,2,
+  /* phase 2: offset=768 */
+  2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,
+  0,2,2,2,2,1,2,1,2,2,2,2,1,2,1,2,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,
+  2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,1,2,1,2,2,2,2,2,1,1,1,
+  2,0,2,2,2,1,2,1,0,2,2,2,1,2,1,2,2,2,2,0,2,2,2,2,0,2,0,2,2,2,2,2,
+  0,2,0,0,1,1,1,1,2,2,2,2,1,1,1,1,0,2,0,2,1,1,1,1,2,2,2,2,1,1,1,1,
+  2,2,0,2,2,2,1,2,2,2,2,2,1,2,1,2,2,2,0,2,2,1,2,1,0,2,0,2,1,1,1,1,
+  2,0,0,2,2,2,2,2,0,2,0,2,2,0,2,0,2,0,2,0,2,2,2,1,2,2,0,2,1,1,2,1,
+  2,2,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,
+  0,0,0,0,2,2,2,2,0,0,0,0,2,2,2,2,0,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,0,2,2,2,2,1,0,2,2,2,1,1,1,1,2,0,2,2,2,2,2,2,0,2,0,2,2,1,2,1,
+  2,0,2,0,2,2,2,2,0,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,2,2,
+  0,2,2,2,1,2,1,2,2,2,2,2,2,2,2,2,0,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,
+  2,2,0,2,2,2,2,2,2,2,2,2,2,2,0,2,2,0,0,2,2,1,2,1,0,2,2,2,1,1,1,1,
+  2,2,2,0,2,2,2,2,2,2,0,2,2,0,2,0,2,1,2,2,2,2,2,2,1,2,1,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,0,2,2,2,1,
+  0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,2,1,1,1,2,2,2,2,1,1,1,1,
+  2,2,2,1,2,2,2,2,2,2,1,2,0,0,0,0,2,2,0,2,2,1,2,2,2,2,2,2,1,1,1,1,
+  2,0,0,0,2,2,2,2,0,2,2,2,2,2,2,0,2,2,2,0,2,2,2,2,2,0,0,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,0,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,1,
+  0,2,0,2,2,1,1,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,
+  2,0,2,0,2,1,2,1,0,2,0,2,2,2,1,2,2,0,2,0,2,2,2,2,0,2,0,2,2,2,1,2,
+  2,2,2,0,2,2,2,2,2,2,0,2,2,2,2,2,2,2,1,2,2,2,2,2,2,0,1,2,2,2,2,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,
+  0,2,2,2,1,2,1,2,2,2,2,2,2,2,2,2,0,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,
+  2,0,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,2,2,1,2,1,0,2,2,2,1,1,1,1,
+  2,0,2,0,2,1,2,2,0,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,1,2,2,
+  2,0,2,2,2,2,2,2,2,0,2,2,2,2,2,2,2,0,2,0,2,2,2,2,0,0,0,0,2,1,2,1,
+  2,2,2,2,2,2,2,0,2,2,2,2,2,2,2,0,2,2,2,2,2,1,2,0,0,2,2,2,1,2,2,2,
+  0,0,2,0,2,2,2,2,0,2,0,2,2,0,2,0,1,1,1,2,2,2,2,2,2,2,2,2,2,1,1,1,
+  2,2,2,2,2,2,2,2,2,0,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,0,2,0,2,2,2,1,
+  2,2,0,0,2,2,2,2,2,2,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,1,1,1,1,
+  0,2,2,2,1,2,1,2,2,2,2,2,2,2,2,2,0,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,
+  2,0,0,2,2,2,2,2,0,2,0,2,2,2,2,2,1,0,1,2,2,2,2,1,0,2,2,2,1,1,1,1,
+  2,2,2,2,2,2,2,2,2,2,0,2,2,0,2,0,2,1,2,2,2,2,2,2,2,2,0,2,2,1,2,2,
+  0,2,0,0,1,1,1,1,0,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,2,0,2,2,1,2,1,1,
+  2,2,0,2,2,1,2,2,2,2,2,2,1,2,2,2,2,0,2,2,2,2,2,2,0,2,0,2,1,2,1,1,
+  2,0,2,0,2,2,2,2,0,2,0,2,2,1,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,1,
+  2,2,2,2,2,2,2,2,2,0,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,
+  0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  0,2,2,2,2,0,2,0,2,2,2,2,0,0,0,0,2,2,2,2,2,1,1,2,2,2,2,2,1,2,2,2,
+  2,0,2,2,2,1,2,1,0,2,2,2,2,2,1,2,2,0,2,0,2,2,2,2,0,2,0,2,2,1,2,2,
+  0,2,0,0,2,2,2,2,1,2,2,2,2,2,2,0,2,1,2,2,2,2,2,2,1,2,2,2,2,2,2,2,
+  0,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,2,1,0,2,2,
+  0,0,0,2,2,1,1,1,2,2,2,2,1,2,2,2,2,0,2,0,2,2,2,1,2,2,2,2,1,2,1,2,
+  0,0,0,0,2,2,2,2,2,2,0,2,2,1,2,2,2,1,2,1,2,2,2,2,1,2,1,2,0,2,2,2,
+  2,0,2,0,2,2,2,2,2,0,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,
+  0,2,2,2,1,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,1,2,2,2,2,2,0,2,2,1,2,2,0,0,0,2,2,2,2,2,1,2,2,0,2,2,2,1,2,1,2,
+  2,0,2,0,2,2,2,2,0,2,0,2,2,1,2,2,0,2,0,0,2,2,2,2,2,2,2,2,2,1,2,2,
+  2,2,2,2,2,2,2,2,2,0,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,0,2,0,2,2,2,1,
+  1,2,0,2,2,1,2,1,2,2,2,2,1,2,2,2,2,0,2,0,2,2,2,2,2,0,2,2,1,1,1,1,
+  0,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,0,2,2,1,2,1,
+  2,2,0,0,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,
+  2,2,2,0,2,2,2,2,2,2,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,1,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,2,
+  2,0,2,0,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,1,0,2,0,2,2,2,1,2,
+  2,0,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,
+  2,0,2,0,2,2,2,2,2,0,2,0,2,2,2,2,2,0,2,0,2,2,2,2,0,0,0,0,2,1,2,1,
+  2,2,2,2,2,1,2,1,0,2,0,2,2,2,2,2,2,0,2,0,2,2,2,2,0,2,0,2,2,2,2,1,
+  2,0,2,0,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,0,
+  2,0,2,0,2,2,2,1,2,2,2,0,2,2,2,1,2,0,2,0,2,2,2,2,0,0,0,2,2,2,2,1,
+  2,0,2,0,2,2,2,2,2,2,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,2,
+  /* phase 3: offset=2816 */
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,
+  0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,1,2,1,2,0,2,0,1,2,1,2,0,2,0,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,0,2,2,2,1,2,0,2,2,2,1,2,2,2,2,0,2,0,2,1,2,1,0,0,0,0,1,1,1,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,0,2,2,2,1,2,
+  2,2,2,1,2,2,2,0,1,1,1,1,0,0,0,0,2,2,2,2,2,2,2,2,2,0,2,0,2,1,2,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,0,0,0,0,1,1,1,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,0,2,1,2,1,0,0,0,0,1,1,1,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,1,2,0,2,0,2,
+  2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,0,2,0,2,1,2,1,2,0,2,0,2,1,2,1,
+  2,0,0,0,2,1,1,1,0,0,0,0,1,1,1,1,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,
+  2,0,2,2,2,1,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,0,0,2,0,1,1,2,1,
+  2,2,2,0,2,2,2,1,2,1,2,2,2,0,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,
+  0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,2,0,2,0,2,1,2,1,0,0,0,0,1,1,1,1,
+  2,0,0,2,2,1,1,2,2,2,2,2,2,2,2,2,2,1,2,1,2,0,2,0,2,1,1,1,2,0,0,0,
+  2,1,2,1,2,0,2,0,1,2,1,2,0,2,0,2,2,2,2,0,2,2,2,1,2,0,2,0,2,1,2,1,
+  2,0,2,0,2,1,2,1,0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2,2,0,2,0,2,1,2,1,
+  2,2,2,2,2,2,2,2,2,0,0,0,2,1,1,1,2,2,2,2,2,2,2,2,2,0,2,0,2,1,2,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,
+  0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,
+  2,0,0,0,2,1,1,1,0,0,0,0,1,1,1,1,2,0,2,0,2,1,2,1,0,0,2,0,1,1,2,1,
+  2,2,1,2,2,2,0,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,0,0,0,2,1,1,1,
+  2,2,2,1,2,2,2,0,2,1,1,1,2,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,1,2,1,2,0,2,0,1,2,1,2,0,2,0,2,
+  2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,
+  2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,2,1,1,1,0,0,0,0,1,1,1,1,
+  2,0,2,2,2,1,2,2,0,0,2,0,1,1,2,1,2,1,2,1,2,0,2,0,2,2,2,2,2,2,2,2,
+  2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,0,0,0,0,1,1,1,1,
+  2,0,0,0,2,1,1,1,0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2,2,1,0,2,2,0,1,2,
+  2,2,2,1,2,2,2,0,2,1,1,1,2,0,0,0,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,
+  2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,
+  0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,0,2,2,2,2,2,2,2,2,2,2,
+  2,1,2,1,2,0,2,0,1,2,1,1,0,2,0,0,0,0,2,1,1,1,2,0,0,0,0,0,1,1,1,1,
+  2,2,2,2,2,2,2,2,1,2,2,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,0,2,1,2,1,2,0,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,0,2,2,2,1,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,1,2,2,2,0,2,2,2,2,0,2,2,2,1,2,2,2,0,0,2,2,1,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,
+  0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,0,2,2,2,2,2,2,2,2,2,
+  2,0,2,0,2,1,2,1,0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2,0,0,0,0,1,1,1,1,
+  2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,0,0,0,2,1,1,1,
+  2,2,2,0,2,2,2,1,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,
+  2,0,2,2,2,1,2,2,2,0,2,0,2,1,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,
+  2,1,2,1,2,0,2,0,1,2,1,2,0,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,1,2,1,2,0,2,0,1,2,1,1,0,2,0,0,2,0,2,2,2,1,2,2,0,2,1,2,1,2,0,2,
+  2,2,2,1,2,2,2,0,2,2,1,2,2,2,0,2,2,1,2,2,2,0,2,2,2,2,0,2,2,2,1,2,
+  0,0,2,0,1,1,2,1,0,0,1,0,1,1,0,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,0,2,2,2,1,1,2,2,2,0,2,2,2,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,
+  2,2,2,2,2,2,2,2,2,1,2,2,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,
+  2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,0,0,2,2,1,1,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,
+  0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,0,2,2,2,2,2,2,2,2,2,
+  2,0,0,0,2,1,1,1,0,0,0,0,1,1,1,1,2,2,2,1,2,2,2,0,2,1,2,1,2,0,2,0,
+  2,1,2,2,2,0,2,2,2,2,2,2,2,2,2,2,0,2,0,0,1,2,1,1,2,0,0,0,2,1,1,1,
+  2,2,2,2,2,2,2,2,2,1,1,1,2,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,0,2,2,2,1,2,2,0,2,2,2,1,2,2,1,2,1,2,0,2,0,2,0,2,2,2,1,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,0,2,2,2,1,1,1,2,0,0,0,
+  2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,0,2,0,0,1,2,1,1,
+  2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,0,2,2,2,1,2,2,2,
+  2,1,2,1,2,0,2,0,2,1,2,2,2,0,2,2,2,2,2,0,2,2,2,1,2,0,2,0,2,1,2,1,
+  2,0,2,0,2,1,2,1,0,2,0,2,1,2,1,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,
+  2,2,2,2,2,2,2,2,0,2,0,2,1,2,1,2,2,2,2,2,2,2,2,2,0,1,0,0,1,0,1,1,
+  2,2,2,2,2,2,2,2,2,1,2,2,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,2,2,1,2,2,2,0,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,1,2,2,1,0,2,0,2,2,2,1,2,2,2,
+  2,2,2,2,2,2,2,2,1,2,2,2,0,2,2,2,2,2,2,0,2,2,2,1,2,2,0,2,2,2,1,2,
+  2,0,2,0,2,1,2,1,0,2,0,2,1,2,1,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,
+  0,2,0,0,1,2,1,1,2,0,0,0,2,1,1,1,2,2,2,2,2,2,2,2,1,0,1,2,0,1,0,2,
+  2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,1,2,2,2,0,2,2,1,1,2,2,0,0,2,2,
+  0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,1,2,1,2,0,2,0,2,1,2,2,2,0,2,2,2,0,2,2,2,1,2,2,0,2,2,2,1,2,2,2,
+  0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,1,2,2,2,0,2,2,2,
+  2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,0,2,1,2,1,2,
+  0,0,0,0,1,1,1,1,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,1,2,0,2,0,2,2,0,2,2,2,1,2,
+  2,0,2,0,2,1,2,1,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,
+  0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,1,2,2,2,0,1,1,2,1,0,0,2,0,2,0,2,2,2,1,2,2,0,2,2,2,1,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,0,2,2,2,2,0,2,2,2,1,2,
+  2,0,2,0,2,1,2,1,0,2,0,2,1,2,1,2,2,2,2,2,2,2,2,2,2,1,2,2,2,0,2,2,
+  0,2,0,0,1,2,1,1,0,2,0,2,1,2,1,2,2,2,2,2,2,2,2,2,0,0,0,2,1,1,1,2,
+  2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,
+  2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,0,0,2,1,1,1,2,0,0,2,2,2,1,2,2,2,
+  2,1,2,1,2,0,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,0,0,1,2,1,1,
+  0,0,2,2,1,1,2,2,0,2,1,2,1,2,0,2,2,1,2,1,2,0,2,0,1,2,1,2,0,2,0,2,
+  2,2,2,2,2,2,2,2,1,2,1,2,0,2,0,2,2,2,2,2,2,2,2,2,2,0,2,0,2,1,2,1,
+  2,2,0,0,2,2,1,1,2,2,0,0,2,2,1,1,2,2,2,2,2,2,2,2,2,2,0,0,2,2,1,1,
+  2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,0,0,1,2,1,1,
+  2,2,2,0,2,2,2,1,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,1,1,1,2,0,0,0,2,
+  2,2,2,2,2,2,2,2,1,1,1,2,0,0,0,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,
+  2,0,2,0,2,1,2,1,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,0,0,0,2,1,1,1,
+  2,0,2,2,2,1,2,2,0,2,2,2,1,2,2,2,2,0,2,0,2,1,2,1,2,2,2,2,2,2,2,2,
+  2,0,2,0,2,1,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,0,2,0,2,1,2,1,2,1,2,0,2,0,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,0,2,0,2,1,2,1,1,2,1,2,0,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,1,2,1,2,0,2,0,2,2,1,2,1,2,0,2,0,2,2,2,2,2,2,2,2,
+  2,0,2,1,2,1,2,0,0,2,1,2,1,2,0,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,
+  2,0,2,0,2,1,2,1,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,0,2,0,2,1,2,1,
+  2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,1,2,1,2,0,2,0,1,1,1,2,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,
+  2,0,2,0,2,1,2,1,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+};
diff --git a/converter/other/jbig/jbigtopnm.c b/converter/other/jbig/jbigtopnm.c
new file mode 100644
index 00000000..7a6e95c1
--- /dev/null
+++ b/converter/other/jbig/jbigtopnm.c
@@ -0,0 +1,286 @@
+/*
+    jbigtopnm - JBIG to PNM converter
+  
+    This program was derived from jbgtopbm.c in Markus Kuhn's
+    JBIG-KIT package by Bryan Henderson on 2000.05.11
+
+    The main difference is that this version uses the Netpbm libraries.
+  
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <jbig.h>
+#include "pnm.h"
+
+#define BUFSIZE 8192
+
+
+static void
+collect_image (unsigned char *data, size_t len, void *image) {
+    static int cursor = 0;
+    int i;
+
+    for (i = 0; i < len; i++) {
+        ((unsigned char *)image)[cursor++] = data[i];
+    }
+}
+
+
+
+static void 
+write_pnm (FILE *fout, const unsigned char * const image, const int bpp,
+           const int rows, const int cols, const int maxval,
+           const int format) {
+
+    int row;
+    xel *pnm_row;
+
+    pnm_writepnminit(fout, cols, rows, maxval, format, 0);
+
+    pnm_row = pnm_allocrow(cols);
+
+    for (row = 0; row < rows; row++) {
+        int col;
+        for (col = 0; col < cols; col++) {
+            int j;
+            for (j = 0; j < bpp; j++)
+                PNM_ASSIGN1(pnm_row[col], 
+                            image[(((row*cols)+col) * bpp) + j]);
+        }
+        pnm_writepnmrow(fout, pnm_row, cols, maxval, format, 0);
+    }
+    
+    pnm_freerow(pnm_row);
+}
+
+
+
+static void
+write_raw_pbm(FILE * const fout, 
+              const unsigned char * const binary_image,
+              int                   const cols,
+              int                   const rows) { 
+
+    unsigned int const bytes_per_row = pbm_packed_bytes(cols);
+
+    int row;
+
+    pbm_writepbminit(fout, cols, rows, 0);
+
+    for (row = 0; row < rows; ++row)
+        pbm_writepbmrow_packed(fout, &binary_image[row*bytes_per_row], cols, 
+                               0);
+}
+
+
+
+/*
+ *
+ */
+static void 
+diagnose_bie(FILE *f)
+{
+  unsigned char bih[20];
+  int len;
+  unsigned long xd, yd, l0;
+
+  len = fread(bih, 1, 20, f);
+  if (len < 20) {
+    printf("Input file is %d < 20 bytes long and does therefore not "
+	   "contain an intact BIE header!\n", len);
+    return;
+  }
+
+  printf("Decomposition of BIH:\n\n  DL = %d\n  D  = %d\n  P  = %d\n"
+	 "  -  = %d\n  XD = %lu\n  YD = %lu\n  L0 = %lu\n  MX = %d\n"
+	 "  MY = %d\n",
+	 bih[0], bih[1], bih[2], bih[3],
+	 xd = ((unsigned long) bih[ 4] << 24) | ((unsigned long)bih[ 5] << 16)|
+	 ((unsigned long) bih[ 6] <<  8) | ((unsigned long) bih[ 7]),
+	 yd = ((unsigned long) bih[ 8] << 24) | ((unsigned long)bih[ 9] << 16)|
+	 ((unsigned long) bih[10] <<  8) | ((unsigned long) bih[11]),
+	 l0 = ((unsigned long) bih[12] << 24) | ((unsigned long)bih[13] << 16)|
+	 ((unsigned long) bih[14] <<  8) | ((unsigned long) bih[15]),
+	 bih[16], bih[17]);
+  printf("  order   = %d %s%s%s%s%s\n", bih[18],
+	 bih[18] & JBG_HITOLO ? " HITOLO" : "",
+	 bih[18] & JBG_SEQ ? " SEQ" : "",
+	 bih[18] & JBG_ILEAVE ? " ILEAVE" : "",
+	 bih[18] & JBG_SMID ? " SMID" : "",
+	 bih[18] & 0xf0 ? " other" : "");
+  printf("  options = %d %s%s%s%s%s%s%s%s\n", bih[19],
+	 bih[19] & JBG_LRLTWO ? " LRLTWO" : "",
+	 bih[19] & JBG_VLENGTH ? " VLENGTH" : "",
+	 bih[19] & JBG_TPDON ? " TPDON" : "",
+	 bih[19] & JBG_TPBON ? " TPBON" : "",
+	 bih[19] & JBG_DPON ? " DPON" : "",
+	 bih[19] & JBG_DPPRIV ? " DPPRIV" : "",
+	 bih[19] & JBG_DPLAST ? " DPLAST" : "",
+	 bih[19] & 0x80 ? " other" : "");
+  printf("\n  %lu stripes, %d layers, %d planes\n\n",
+	 ((yd >> bih[1]) +  ((((1UL << bih[1]) - 1) & xd) != 0) + l0 - 1) / l0,
+	 bih[1] - bih[0], bih[2]);
+
+  return;
+}
+
+
+int main (int argc, char **argv)
+{
+    FILE *fin = stdin, *fout = stdout;
+    const char *fnin = "<stdin>", *fnout = "<stdout>";
+    int i, j, result;
+    int all_args = 0, files = 0;
+    struct jbg_dec_state s;
+    char *buffer;
+    unsigned char *p;
+    size_t len, cnt;
+    unsigned long xmax = 4294967295UL, ymax = 4294967295UL;
+    int plane = -1, use_graycode = 1, diagnose = 0;
+
+    pnm_init(&argc, argv);
+
+    buffer = malloc(BUFSIZE);
+    if (!buffer)
+        pm_error("Sorry, not enough memory available!");
+
+    /* parse command line arguments */
+    for (i = 1; i < argc; i++) {
+        if (!all_args && argv[i][0] == '-') {
+            if (argv[i][1] == '\0' && files == 0)
+                ++files;
+            else {
+                for (j = 1; j > 0 && argv[i][j]; j++) {
+                    switch(tolower(argv[i][j])) {
+                    case '-' :
+                        all_args = 1;
+                        break;
+                    case 'b':
+                        use_graycode = 0;
+                        break;
+                    case 'd':
+                        diagnose = 1;
+                        break;
+                    case 'x':
+                        if (++i >= argc)
+                            pm_error("-x needs a value");
+                        xmax = atol(argv[i]);
+                        j = -1;
+                        break;
+                    case 'y':
+                        if (++i >= argc)
+                            pm_error("-y needs a value");
+                        ymax = atol(argv[i]);
+                        j = -1;
+                        break;
+                    case 'p':
+                        if (++i >= argc)
+                            pm_error("-p needs a value");
+                        plane = atoi(argv[i]);
+                        j = -1;
+                        break;
+                    default:
+                        pm_error("Unrecognized option: %c", argv[i][j]);
+                    }
+                }
+            }
+        } else {
+            switch (files++) {
+            case 0:
+                if (argv[i][0] != '-' || argv[i][1] != '\0') {
+                    fnin = argv[i];
+                    fin = fopen(fnin, "rb");
+                    if (!fin)
+                        pm_error("Can't open input file '%s'", fnin);
+                }
+                if (diagnose) {
+                    diagnose_bie(fin);
+                    exit(0);
+                }
+                break;
+            case 1:
+                fnout = argv[i];
+                fout = fopen(fnout, "wb");
+                if (!fout)
+                    pm_error("Can't open output file '%s'", fnout);
+                break;
+            default:
+                pm_error("Too many non-option arguments");
+            }
+        }
+    }
+
+    /* send input file to decoder */
+    jbg_dec_init(&s);
+    jbg_dec_maxsize(&s, xmax, ymax);
+    result = JBG_EAGAIN;
+    do {
+        len = fread(buffer, 1, BUFSIZE, fin);
+        if (!len) break;
+        cnt = 0;
+        p = (unsigned char *) buffer;
+        while (len > 0 && (result == JBG_EAGAIN || result == JBG_EOK)) {
+            result = jbg_dec_in(&s, p, len, &cnt);
+            p += cnt;
+            len -= cnt;
+        }
+    } while (result == JBG_EAGAIN || result == JBG_EOK);
+    if (ferror(fin)) 
+        pm_error("Problem while reading input file '%s", fnin);
+    if (result != JBG_EOK && result != JBG_EOK_INTR) 
+        pm_error("Problem with input file '%s': %s\n", 
+                 fnin, jbg_strerror(result, JBG_EN));
+    if (plane >= 0 && jbg_dec_getplanes(&s) <= plane) 
+        pm_error("Image has only %d planes!\n", jbg_dec_getplanes(&s));
+
+    {
+        /* Write it out */
+
+        int rows, cols;
+        int maxval;
+        int bpp;
+        int plane_to_write;
+
+        cols = jbg_dec_getwidth(&s);
+        rows = jbg_dec_getheight(&s);
+        maxval = pm_bitstomaxval(jbg_dec_getplanes(&s));
+        bpp = (jbg_dec_getplanes(&s)+7)/8;
+
+        if (jbg_dec_getplanes(&s) == 1) 
+            plane_to_write = 0;
+        else 
+            plane_to_write = plane;
+
+        if (plane_to_write >= 0) {
+            /* Write just one plane */
+            unsigned char * binary_image;
+
+            pm_message("WRITING PBM FILE");
+
+            binary_image=jbg_dec_getimage(&s, plane_to_write);
+            write_raw_pbm(fout, binary_image, cols, rows);
+        } else {
+            unsigned char *image;
+            pm_message("WRITING PGM FILE");
+
+            /* Write out all the planes */
+            /* What jbig.doc doesn't tell you is that jbg_dec_merge_planes
+               delivers the image in chunks, in consecutive calls to 
+               the data-out callback function.  And a row can span two
+               chunks.
+            */
+            image = malloc(cols*rows*bpp);
+            jbg_dec_merge_planes(&s, use_graycode, collect_image, image);
+            write_pnm(fout, image, bpp, rows, cols, maxval, PGM_TYPE);
+            free(image);
+        }
+    }
+  
+    pm_close(fout);
+
+    jbg_dec_free(&s);
+
+    return 0;
+}
diff --git a/converter/other/jbig/pnmtojbig.c b/converter/other/jbig/pnmtojbig.c
new file mode 100644
index 00000000..9dbef3fa
--- /dev/null
+++ b/converter/other/jbig/pnmtojbig.c
@@ -0,0 +1,463 @@
+/*
+    pnmtojbig - PNM to JBIG converter
+  
+    This program was derived from pbmtojbg.c in Markus Kuhn's
+    JBIG-KIT package by Bryan Henderson on 2000.05.11
+
+    The main difference is that this version uses the Netpbm libraries.
+
+ */
+
+/*
+     The JBIG standard doesn't say which end of the scale is white and
+     which end is black in a BIE.  It has a recommendation in terms of
+     foreground and background (a concept which does not exist in the
+     Netpbm formats) for single-plane images, and is silent for
+     multi-plane images.
+
+     Kuhn's implementation of the JBIG standard says if the BIE has a
+     single plane, then in that plane a zero bit means white and a one
+     bit means black.  But if it has multiple planes, a composite zero
+     value means black and a composite maximal value means white.
+
+     Actually, Kuhn's pbmtojbg doesn't even implement this, but rather
+     bases the distinction on whether the input file was PBM or PGM.
+     This means that if you convert a PGM file with maxval 1 to a JBIG
+     file and then back, the result (which is a PBM file) is the
+     inverse of what you started with.  Same if the PGM file has
+     maxval > 1 but you use a -t option to write only one plane.  We
+     assume this is just a bug in pbmtojpg and that hardly anybody does
+     this.  So we adopt the implementation described above.
+
+     This means that after jbg_split_planes() hands us a set of bitmap
+     planes, if there is only one of them, we have to invert all the
+     bits in it.
+
+*/
+  
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <jbig.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "pnm.h"
+
+static unsigned long total_length = 0;  
+  /* used for determining output file length */
+
+/*
+ * malloc() with exception handler
+ */
+static void
+*checkedmalloc(size_t n)
+{
+  void *p;
+  
+  if ((p = malloc(n)) == NULL) {
+    fprintf(stderr, "Sorry, not enough memory available!\n");
+    exit(1);
+  }
+  
+  return p;
+}
+
+
+/*
+ * Callback procedure which is used by JBIG encoder to deliver the
+ * encoded data. It simply sends the bytes to the output file.
+ */
+static void data_out(unsigned char *start, size_t len, void *file)
+{
+  fwrite(start, len, 1, (FILE *) file);
+  total_length += len;
+  return;
+}
+
+
+
+static void
+readPbm(FILE *            const fin,
+        unsigned int      const cols,
+        unsigned int      const rows,
+        unsigned char *** const bitmapP) {
+
+    unsigned int const bytes_per_line = pbm_packed_bytes(cols);
+
+    unsigned char ** bitmap;
+
+    /* Read the input image into bitmap[] */
+    /* Shortcut for PBM */
+    int row;
+    bitmap = (unsigned char **) checkedmalloc(sizeof(unsigned char *));
+    bitmap[0] = (unsigned char *) checkedmalloc(bytes_per_line * rows);
+    
+    for (row = 0; row < rows; row++)
+        pbm_readpbmrow_packed(fin, &bitmap[0][row*bytes_per_line],
+                              cols, RPBM_FORMAT);
+
+    *bitmapP = bitmap;
+} 
+
+
+
+static void
+readImage(FILE * const fin,
+          unsigned int     const cols,
+          unsigned int     const rows,
+          xelval           const maxval,
+          int              const format,
+          unsigned int     const bpp,
+          unsigned char ** const imageP) {
+/*----------------------------------------------------------------------------
+  Read the input image and put it into *imageP;
+
+  Although the PBM case is separated, this logic works also for
+  PBM, bpp=1.
+-----------------------------------------------------------------------------*/
+    unsigned char *image;
+        /* This is a representation of the entire image with 'bpp' bytes per
+           pixel.  The 'bpp' bytes for each pixel are arranged MSB first
+           and its numerical value is the value from the PNM input.
+           The pixels are laid out in row-major format in this rectangle.
+           
+           The point of this data structure is it is what jbg_split_planes()
+           wants for input.
+        */
+    xel* pnm_row;
+    unsigned int row;
+
+    pnm_row = pnm_allocrow(cols);  /* row buffer */
+    MALLOCARRAY_NOFAIL(image, cols * rows * bpp);
+    
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        pnm_readpnmrow(fin, pnm_row, cols, maxval, format);
+        for (col = 0; col < cols; col++) {
+            unsigned int j;
+            /* Move each byte of the sample into image[], MSB first */
+            for (j = 0; j < bpp; ++j)
+                image[(((row*cols)+col) * bpp) + j] = (unsigned char)
+                    PNM_GET1(pnm_row[col]) >> ((bpp-1-j) * 8);
+        }
+    }
+    pnm_freerow(pnm_row);
+    *imageP = image;
+}
+      
+
+
+static void
+convertImageToBitmap(unsigned char *   const image,
+                     unsigned char *** const bitmapP,
+                     unsigned int      const encode_planes,
+                     unsigned int      const bytes_per_line,
+                     unsigned int      const lines) {
+    
+    /* Convert image[] into bitmap[]  */
+    
+    unsigned char ** bitmap;
+    unsigned int i;
+    
+    MALLOCARRAY_NOFAIL(bitmap, encode_planes);
+    for (i = 0; i < encode_planes; ++i)
+        MALLOCARRAY_NOFAIL(bitmap[i], bytes_per_line * lines);
+
+    *bitmapP = bitmap;
+}
+
+
+
+static void
+readPnm(FILE *            const fin,
+        unsigned int      const cols,
+        unsigned int      const rows,
+        xelval            const maxval,
+        int               const format,
+        unsigned int      const bpp,
+        unsigned int      const planes,
+        unsigned int      const encode_planes,
+        bool              const use_graycode,
+        unsigned char *** const bitmapP) {
+
+    unsigned int const bytes_per_line = pbm_packed_bytes(cols);
+
+    unsigned char * image;
+    unsigned char ** bitmap;
+
+    readImage(fin, cols, rows, maxval, format, bpp, &image);
+
+    convertImageToBitmap(image, &bitmap, encode_planes, bytes_per_line, rows);
+
+    jbg_split_planes(cols, rows, planes, encode_planes, image, bitmap,
+                     use_graycode);
+    free(image);
+    
+    /* Invert the image if it is just one plane.  See top of this file
+       for an explanation why.  Due to the separate handling of PBM,
+       this is for exceptional PGM files.  
+    */
+
+    if (encode_planes == 1) {
+        unsigned int row;
+        for (row = 0; row < rows; ++row) {
+            unsigned int i;
+            for (i = 0; i < bytes_per_line; i++)
+                bitmap[0][(row*bytes_per_line) + i] ^= 0xff;
+
+            if (cols % 8 > 0) {   
+                bitmap[0][ (row+1)*bytes_per_line  -1] >>= 8-cols%8;
+                bitmap[0][ (row+1)*bytes_per_line  -1] <<= 8-cols%8;
+            }
+        }
+    }
+    *bitmapP = bitmap;
+}
+
+
+
+int
+main(int argc, char **argv) {
+    FILE *fin = stdin, *fout = stdout;
+    const char *fnin = "<stdin>", *fnout = "<stdout>";
+    int i;
+    int all_args = 0, files = 0;
+    int bpp, planes, encode_planes = -1;
+    int cols, rows;
+    xelval maxval;
+    int format;
+    unsigned char **bitmap;
+    /* This is an array of the planes of the image.  Each plane is a
+       two-dimensional array of pixels laid out in row-major format.
+       format with each pixel being one bit.  A byte in the array 
+       contains 8 pixels left to right, msb to lsb.
+    */
+
+    struct jbg_enc_state s;
+    int verbose = 0, delay_at = 0, use_graycode = 1;
+    long mwidth = 640, mheight = 480;
+    int dl = -1, dh = -1, d = -1, l0 = -1, mx = -1;
+    int options = JBG_TPDON | JBG_TPBON | JBG_DPON;
+    int order = JBG_ILEAVE | JBG_SMID;
+
+    pbm_init(&argc, argv);
+
+    /* parse command line arguments */
+    for (i = 1; i < argc; ++i) {
+        int j;
+        if (!all_args && argv[i][0] == '-') {
+            if (argv[i][1] == '\0' && files == 0)
+                ++files;
+            else {
+                for (j = 1; j > 0 && argv[i][j]; j++) {
+                    switch(tolower(argv[i][j])) {
+                    case '-' :
+                        all_args = 1;
+                        break;
+                    case 'v':
+                        verbose = 1;
+                        break;
+                    case 'b':
+                        use_graycode = 0;
+                        break;
+                    case 'c':
+                        delay_at = 1;
+                        break;
+                    case 'x':
+                        if (++i >= argc)
+                            pm_error("-x needs a value");
+                        j = -1;
+                        mwidth = atol(argv[i]);
+                        break;
+                    case 'y':
+                        if (++i >= argc)
+                            pm_error("-y needsa  value");
+                        j = -1;
+                        mheight = atol(argv[i]);
+                        break;
+                    case 'o':
+                        if (++i >= argc)
+                            pm_error("-o needs a value");
+                        j = -1;
+                        order = atoi(argv[i]);
+                        break;
+                    case 'p':
+                        if (++i >= argc)
+                            pm_error("-p needs a value");
+                        j = -1;
+                        options = atoi(argv[i]);
+                        break;
+                    case 'l':
+                        if (++i >= argc)
+                            pm_error("-l needs a value");
+                        j = -1;
+                        dl = atoi(argv[i]);
+                        break;
+                    case 'h':
+                        if (++i >= argc)
+                            pm_error("-h needs a value");
+                        j = -1;
+                        dh = atoi(argv[i]);
+                        break;
+                    case 'q':
+                        d = 0;
+                        break;
+                    case 'd':
+                        if (++i >= argc)
+                            pm_error("-d needs a value");
+                        j = -1;
+                        d = atoi(argv[i]);
+                        break;
+                    case 's':
+                        if (++i >= argc)
+                            pm_error("-s needs a value");
+                        j = -1;
+                        l0 = atoi(argv[i]);
+                        break;
+                    case 't':
+                        if (++i >= argc)
+                            pm_error("-t needs a value");
+                        j = -1;
+                        encode_planes = atoi(argv[i]);
+                        break;
+                    case 'm':
+                        if (++i >= argc)
+                            pm_error("-m needs a value");
+                        j = -1;
+                        mx = atoi(argv[i]);
+                        break;
+                    default:
+                        pm_error("Unrecognized option: %c", argv[i][j]);
+                    }
+                }
+            }
+        } else {
+            switch (files++) {
+            case 0:
+                if (argv[i][0] != '-' || argv[i][1] != '\0') {
+                    fnin = argv[i];
+                    fin = fopen(fnin, "rb");
+                    if (!fin) {
+                        fprintf(stderr, "Can't open input file '%s", fnin);
+                        perror("'");
+                        exit(1);
+                    }
+                }
+                break;
+            case 1:
+                fnout = argv[i];
+                fout = fopen(fnout, "wb");
+                if (!fout) {
+                    fprintf(stderr, "Can't open input file '%s", fnout);
+                    perror("'");
+                    exit(1);
+                }
+                break;
+            default:
+                pm_error("too many non-option arguments");
+            }
+        }
+    }
+
+    pnm_readpnminit(fin, &cols, &rows, &maxval, &format);
+
+    if (PNM_FORMAT_TYPE(format) != PGM_TYPE &&
+        PNM_FORMAT_TYPE(format) != PBM_TYPE)
+        pm_error("This program accepts PBM and PGM input only.  "
+                 "Try Ppmtopgm.");
+
+    planes = pm_maxvaltobits(maxval);
+
+    /* In a JBIG file, maxvals are determined only by the number of planes,
+       so must be a power of 2 minus 1
+    */
+  
+    if ((1UL << planes)-1 != maxval) 
+        pm_error("Input image has unacceptable maxval: %d.  JBIG files must "
+                 "have a maxval which is a power of 2 minus 1.  Use "
+                 "Ppmdepth to adjust the image's maxval", maxval);
+
+    bpp = (planes + 7) / 8;
+
+    if (encode_planes < 0 || encode_planes > planes)
+        encode_planes = planes;
+
+    if (bpp == 1 && PNM_FORMAT_TYPE(format) == PBM_TYPE)
+        readPbm(fin, cols, rows, &bitmap);
+    else
+        readPnm(fin, cols, rows, maxval, format, bpp, 
+                planes, encode_planes, use_graycode, 
+                &bitmap);
+
+    /* Apply JBIG algorithm and write BIE to output file */
+
+  /* initialize parameter struct for JBIG encoder*/
+    jbg_enc_init(&s, cols, rows, encode_planes, bitmap, data_out, fout);
+
+    /* Select number of resolution layers either directly or based
+   * on a given maximum size for the lowest resolution layer */
+    if (d >= 0)
+        jbg_enc_layers(&s, d);
+    else
+        jbg_enc_lrlmax(&s, mwidth, mheight);
+
+  /* Specify a few other options (each is ignored if negative) */
+    if (delay_at)
+        options |= JBG_DELAY_AT;
+    jbg_enc_lrange(&s, dl, dh);
+    jbg_enc_options(&s, order, options, l0, mx, -1);
+
+  /* now encode everything and send it to data_out() */
+    jbg_enc_out(&s);
+
+    /* give encoder a chance to free its temporary data structures */
+    jbg_enc_free(&s);
+
+    /* check for file errors and close fout */
+    if (ferror(fout) || fclose(fout)) {
+        fprintf(stderr, "Problem while writing output file '%s", fnout);
+        perror("'");
+        exit(1);
+    }
+
+    /* In case the user wants to know all the gory details ... */
+    if (verbose) {
+        fprintf(stderr, "Information about the created JBIG bi-level image entity "
+                "(BIE):\n\n");
+        fprintf(stderr, "              input image size: %ld x %ld pixel\n",
+                s.xd, s.yd);
+        fprintf(stderr, "                    bit planes: %d\n", s.planes);
+        if (s.planes > 1)
+            fprintf(stderr, "                      encoding: %s code, MSB first\n",
+                    use_graycode ? "Gray" : "binary");
+        fprintf(stderr, "                       stripes: %ld\n", s.stripes);
+        fprintf(stderr, "   lines per stripe in layer 0: %ld\n", s.l0);
+        fprintf(stderr, "  total number of diff. layers: %d\n", s.d);
+        fprintf(stderr, "           lowest layer in BIE: %d\n", s.dl);
+        fprintf(stderr, "          highest layer in BIE: %d\n", s.dh);
+        fprintf(stderr, "             lowest layer size: %lu x %lu pixel\n",
+                jbg_ceil_half(s.xd, s.d - s.dl), jbg_ceil_half(s.yd, s.d - s.dl));
+        fprintf(stderr, "            highest layer size: %lu x %lu pixel\n",
+                jbg_ceil_half(s.xd, s.d - s.dh), jbg_ceil_half(s.yd, s.d - s.dh));
+        fprintf(stderr, "                   option bits:%s%s%s%s%s%s%s\n",
+                s.options & JBG_LRLTWO  ? " LRLTWO" : "",
+                s.options & JBG_VLENGTH ? " VLENGTH" : "",
+                s.options & JBG_TPDON   ? " TPDON" : "",
+                s.options & JBG_TPBON   ? " TPBON" : "",
+                s.options & JBG_DPON    ? " DPON" : "",
+                s.options & JBG_DPPRIV  ? " DPPRIV" : "",
+                s.options & JBG_DPLAST  ? " DPLAST" : "");
+        fprintf(stderr, "                    order bits:%s%s%s%s\n",
+                s.order & JBG_HITOLO ? " HITOLO" : "",
+                s.order & JBG_SEQ    ? " SEQ" : "",
+                s.order & JBG_ILEAVE ? " ILEAVE" : "",
+                s.order & JBG_SMID   ? " SMID" : "");
+        fprintf(stderr, "           AT maximum x-offset: %d\n"
+                "           AT maximum y-offset: %d\n", s.mx, s.my);
+        fprintf(stderr, "         length of output file: %lu byte\n\n",
+                total_length);
+    }
+
+    return 0;
+}
diff --git a/converter/other/jpeg2000/Makefile b/converter/other/jpeg2000/Makefile
new file mode 100644
index 00000000..70cfafb7
--- /dev/null
+++ b/converter/other/jpeg2000/Makefile
@@ -0,0 +1,77 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/other/jpeg2000
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+SUBDIRS = libjasper
+
+include $(BUILDDIR)/Makefile.config
+
+INCLUDES =
+ifneq ($(JASPERHDR_DIR),NONE)
+  INCLUDES += -I$(JASPERHDR_DIR)
+endif
+
+
+# INTERNAL_JASPERLIB must be relative to the current directory, because it
+# may end up in MERGE_OBJECTS, which must be relative.
+INTERNAL_JASPERLIB = libjasper/libjasper.a
+INTERNAL_JASPERHDR_DIR = libjasper/include
+
+ifeq ($(JASPERLIB),$(INTERNAL_JASPERLIB))
+  ifeq ($(HAVE_INT64),Y)
+    JASPERLIB_DEP = $(JASPERLIB)
+    JASPERLIB_USE = $(JASPERLIB)
+  else
+    # He wants the internal library, but doesn't have a 64 bit compiler,
+    # so we have no way to build it.  Ergo, he doesn't have a Jasper
+    # library.
+    JASPERLIB_USE = NONE
+  endif
+else
+  # It's not our internal version; user's on his own to make sure it's built
+  JASPERLIB_USE = $(JASPERLIB)
+endif
+
+
+ifneq ($(JASPERHDR_DIR),NONE)
+  ifneq ($(JASPERLIB_USE),NONE)
+    BINARIES = pamtojpeg2k jpeg2ktopam
+  endif
+endif
+
+
+OBJECTS = $(BINARIES:%=%.o)
+MERGE_OBJECTS = $(BINARIES:%=%.o2) 
+ifeq ($(JASPERLIB),$(INTERNAL_JASPERLIB))
+  # MERGE_OBJECTS contains relative paths, so $(INTERNAL_JASPERLIB) had better
+  # be relative to the current relative to the current directory.
+  MERGE_OBJECTS += $(JASPERLIB)
+endif
+MERGEBINARIES = $(BINARIES)
+
+.PHONY: all
+all: $(BINARIES)
+
+include $(SRCDIR)/Makefile.common
+
+LIBOPTS = $(shell $(LIBOPT) $(NETPBMLIB) $(JASPERLIB_USE))
+
+$(BINARIES): %: %.o $(JASPERLIB_DEP) $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) $(MATHLIB) $(LDLIBS) -o $@ $< \
+	  $(LIBOPTS) $(JASPERDEPLIBS) -lm $(RPATH) $(LADD)
+
+$(INTERNAL_JASPERLIB): FORCE
+	$(MAKE) -f $(SRCDIR)/$(SUBDIR)/libjasper/Makefile \
+	   -C $(dir $@) $(notdir $@)
+
+clean: localclean
+
+.PHONY: localclean
+localclean:
+	$(MAKE) -f $(SRCDIR)/$(SUBDIR)/libjasper/Makefile -C libjasper clean
+
+.PHONY: FORCE
+FORCE:
diff --git a/converter/other/jpeg2000/jpeg2ktopam.c b/converter/other/jpeg2000/jpeg2ktopam.c
new file mode 100644
index 00000000..d6ea580c
--- /dev/null
+++ b/converter/other/jpeg2000/jpeg2ktopam.c
@@ -0,0 +1,498 @@
+/*****************************************************************************
+                              jpeg2kopam
+******************************************************************************
+
+  Convert a JPEG-2000 code stream image to a PNM or PAM
+
+  By Bryan Henderson, San Jose CA  2002.10.26
+
+*****************************************************************************/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+#include <string.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "mallocvar.h"
+
+#include <jasper/jasper.h>
+#include "libjasper_compat.h"
+
+enum compmode {COMPMODE_INTEGER, COMPMODE_REAL};
+
+enum progression {PROG_LRCP, PROG_RLCP, PROG_RPCL, PROG_PCRL, PROG_CPRL};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    char * inputFilename;
+    unsigned int debuglevel;  /* Jasper library debug level */
+    unsigned int verbose;
+};
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that many of the strings that this function returns in the
+   *cmdline_p structure are actually in the supplied argv array.  And
+   sometimes, one of these strings is actually just a suffix of an entry
+   in argv!
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+    optStruct3 opt;
+
+    unsigned int debuglevelSpec;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "verbose",      OPT_FLAG,   NULL, 
+            &cmdlineP->verbose,   0);
+    OPTENT3(0, "debuglevel",   OPT_UINT,   &cmdlineP->debuglevel,
+            &debuglevelSpec,      0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+    if (!debuglevelSpec)
+        cmdlineP->debuglevel = 0;
+
+    if (argc - 1 == 0)
+        cmdlineP->inputFilename = strdup("-");  /* he wants stdin */
+    else if (argc - 1 == 1)
+        cmdlineP->inputFilename = strdup(argv[1]);
+    else 
+        pm_error("Too many arguments.  The only argument accepted\n"
+                 "is the input file specification");
+
+}
+
+
+
+static void
+readJpc(const char *   const inputFilename, 
+        jas_image_t ** const jasperPP) {
+
+    jas_image_t * jasperP;
+    jas_stream_t *instream;
+    const char * options;
+
+    if ( strcmp(inputFilename, "-") == 0) {
+        /* The input image is to be read from standard input. */
+        instream = jas_stream_fdopen(fileno(stdin), "rb");
+        if (instream == NULL)
+            pm_error("error: cannot reopen standard input");
+    } else {
+        instream = jas_stream_fopen(inputFilename, "rb");
+        if (instream == NULL )
+            pm_error("cannot open input image file '%s'", inputFilename);
+    } 
+
+    if (jas_image_getfmt(instream) != jas_image_strtofmt((char*)"jpc"))
+        pm_error("Input is not JPEG-2000 code stream");
+
+    options = "";
+
+    jasperP = jas_image_decode(instream, jas_image_strtofmt((char*)"jpc"), 
+                               (char*)options);
+    if (jasperP == NULL)
+        pm_error("Unable to interpret JPEG-2000 input.  "
+                 "The Jasper library jas_image_decode() subroutine failed.");
+
+	jas_stream_close(instream);
+
+    *jasperPP = jasperP;
+}
+
+
+
+static void
+getRgbComponents(int jasperCmpnt[], jas_image_t * const jasperP) {
+
+    {
+        int const rc = 
+            jas_image_getcmptbytype(jasperP,
+                                    JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_R));
+        if (rc < 0)
+            pm_error("Input says it has RGB color space, but contains "
+                     "no red component");
+        else
+            jasperCmpnt[PAM_RED_PLANE] = rc;
+
+        if (jas_image_cmptsgnd(jasperP, rc)) 
+            pm_error("Input image says it is RGB, but has signed values "
+                     "for what should be the red intensities.");
+    }
+    {
+        int const rc = 
+            jas_image_getcmptbytype(jasperP,
+                                    JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_G));
+        if (rc < 0)
+            pm_error("Input says it has RGB color space, but contains "
+                     "no green component");
+        else
+            jasperCmpnt[PAM_GRN_PLANE] = rc;
+
+        if (jas_image_cmptsgnd(jasperP, rc)) 
+            pm_error("Input image says it is RGB, but has signed values "
+                     "for what should be the green intensities.");
+    }
+    {
+        int const rc = 
+            jas_image_getcmptbytype(jasperP,
+                                    JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_B));
+        if (rc < 0)
+            pm_error("Input says it has RGB color space, but contains "
+                     "no blue component");
+        else
+            jasperCmpnt[PAM_BLU_PLANE] = rc;
+
+        if (jas_image_cmptsgnd(jasperP, rc)) 
+            pm_error("Input image says it is RGB, but has signed values "
+                     "for what should be the blue intensities.");
+    }
+}            
+
+
+
+static void
+getGrayComponent(int * jasperCmptP, jas_image_t * const jasperP) {
+
+    int const rc = 
+        jas_image_getcmptbytype(jasperP,
+                                JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_GRAY_Y));
+    if (rc < 0)
+        pm_error("Input says it has Grayscale color space, but contains "
+                 "no gray intensity component");
+    else
+        *jasperCmptP = rc;
+    if (jas_image_cmptsgnd(jasperP, 0)) 
+        pm_error("Input image says it is grayscale, but has signed values "
+                 "for what should be the gray levels.");
+}
+
+
+
+static void
+validateComponentsAlike(jas_image_t * const jasperP) {
+/*----------------------------------------------------------------------------
+   JPC allows each component to have its own width and height.  But
+   PAM requires all planes to the same shape.  So we validate now that
+   all the channels are the same, and abort the program if not.
+-----------------------------------------------------------------------------*/
+    int cmptNo;
+    
+    for (cmptNo = 0; cmptNo < jas_image_numcmpts(jasperP); ++cmptNo) {
+        if (jas_image_cmptwidth(jasperP, cmptNo) != 
+            jas_image_cmptwidth(jasperP, 0))
+            pm_message("Input image does not have components all the same "
+                       "width.");
+        if (jas_image_cmptheight(jasperP, cmptNo) != 
+            jas_image_cmptheight(jasperP, 0))
+            pm_message("Input image does not have components all the same "
+                       "height.");
+    }
+}
+
+
+
+static unsigned int
+maxJasperComponentPrecision(jas_image_t * const jasperP) {
+
+    int cmptNo;
+    unsigned int max;
+    
+    max = 1;
+
+    for (cmptNo = 0; cmptNo < jas_image_numcmpts(jasperP); ++cmptNo)
+        max = MAX(max, jas_image_cmptprec(jasperP, cmptNo));
+
+    return max;
+}
+
+
+
+static void
+computeOutputParm(jas_image_t * const jasperP,
+                  struct pam *  const outpamP,
+                  int **        const jasperCmptNoP) {
+
+    int * jasperCmptNo;
+       /* Malloc'ed array.  jaspercmptNo[P] is the component number for use
+          with the Jasper library that corresponds to Plane P of the PAM.
+       */
+
+	switch (jas_clrspc_fam(jas_image_clrspc(jasperP))) {
+	case JAS_CLRSPC_FAM_GRAY:
+        outpamP->depth = 1;
+        MALLOCARRAY_NOFAIL(jasperCmptNo, 1);
+        getGrayComponent(&jasperCmptNo[0], jasperP);
+        if (jas_image_cmptprec(jasperP, jasperCmptNo[0]) == 1) {
+            outpamP->format = RPBM_FORMAT;
+            strcpy(outpamP->tuple_type, PAM_PBM_TUPLETYPE);
+        } else {
+            outpamP->format = RPGM_FORMAT;
+            strcpy(outpamP->tuple_type, PAM_PGM_TUPLETYPE);
+        }
+        break;
+	case JAS_CLRSPC_FAM_RGB:
+        outpamP->depth = 3;
+        MALLOCARRAY_NOFAIL(jasperCmptNo, 3);
+        getRgbComponents(jasperCmptNo, jasperP);
+        outpamP->format = RPPM_FORMAT;
+        strcpy(outpamP->tuple_type, PAM_PPM_TUPLETYPE);
+        break;
+    default:
+        outpamP->format = PAM_FORMAT;
+        outpamP->depth = jas_image_numcmpts(jasperP);
+        {
+            unsigned int plane;
+
+            MALLOCARRAY_NOFAIL(jasperCmptNo, outpamP->depth);
+            for (plane = 0; plane < outpamP->depth; ++plane)
+                jasperCmptNo[plane] = plane;
+        }
+        strcpy(outpamP->tuple_type, "");
+        if (jas_image_cmptsgnd(jasperP, 0)) 
+            pm_message("Warning: Input image has signed sample values.  "
+                       "They will be represented in the PAM output in "
+                       "two's complement.");
+    }
+    outpamP->plainformat = FALSE;
+
+	outpamP->width = jas_image_cmptwidth(jasperP, 0);
+	outpamP->height = jas_image_cmptheight(jasperP, 0);
+
+    validateComponentsAlike(jasperP);
+
+    {
+        unsigned int const maxPrecision = maxJasperComponentPrecision(jasperP);
+
+        outpamP->maxval = pm_bitstomaxval(maxPrecision);
+        
+        outpamP->bytes_per_sample = (maxPrecision + 7)/8;
+    }
+    *jasperCmptNoP = jasperCmptNo;
+}
+
+
+
+static void
+createMatrices(struct pam * const outpamP, jas_matrix_t *** matrixP) {
+
+    jas_matrix_t ** matrix; 
+    unsigned int plane;
+
+    MALLOCARRAY_NOFAIL(matrix, outpamP->depth);
+
+    for (plane = 0; plane < outpamP->depth; ++plane) {
+        matrix[plane] = jas_matrix_create(1, outpamP->width);
+
+        if (matrix[plane] == NULL)
+            pm_error("Unable to create matrix for plane %u.  "
+                     "jas_matrix_create() failed.", plane);
+    }   
+    *matrixP = matrix;
+}
+
+
+
+static void
+destroyMatrices(struct pam *    const outpamP, 
+                jas_matrix_t ** const matrix ) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < outpamP->depth; ++plane)
+        jas_matrix_destroy(matrix[plane]);
+    free(matrix);
+}    
+
+
+
+static void
+computeComponentMaxval(struct pam *  const outpamP,
+                       jas_image_t * const jasperP,
+                       int           const jasperCmpt[],
+                       sample **     const jasperMaxvalP,
+                       bool *        const singleMaxvalP) {
+    
+    sample * jasperMaxval;
+    unsigned int plane;
+
+    MALLOCARRAY(jasperMaxval, outpamP->depth);
+
+    *singleMaxvalP = TRUE;  /* initial assumption */
+    for (plane = 0; plane < outpamP->depth; ++plane) {
+        jasperMaxval[plane] = 
+            pm_bitstomaxval(jas_image_cmptprec(jasperP, jasperCmpt[plane]));
+        if (jasperMaxval[plane] != jasperMaxval[0])
+            *singleMaxvalP = FALSE;
+    }
+    *jasperMaxvalP = jasperMaxval;
+}
+
+                       
+
+static void
+copyRowSingleMaxval(jas_seqent_t ** const jasperRow,
+                    tuple *         const tuplerow,
+                    struct pam *    const outpamP) {
+/*----------------------------------------------------------------------------
+   Copy row from Jasper library representation to Netpbm library
+   representation, assuming all Jasper components have the same precision,
+   which corresponds to the maxval of the output PAM, which means we don't
+   have to do any maxval scaling.
+
+   This is significantly faster than copyRowAnyMaxval().
+-----------------------------------------------------------------------------*/
+    unsigned int col;
+    
+    for (col = 0; col < outpamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < outpamP->depth; ++plane) 
+            tuplerow[col][plane] = jasperRow[plane][col];
+    }
+}
+
+
+
+static void
+copyRowAnyMaxval(jas_seqent_t ** const jasperRow,
+                 tuple *         const tuplerow,
+                 struct pam *    const outpamP,
+                 sample          const jasperMaxval[]) {
+/*----------------------------------------------------------------------------
+   Copy row from Jasper library representation to Netpbm library
+   representation, allowing for each Jasper library component to have a
+   different precision (number of bits) and for those precisions to be
+   unrelated to the PAM maxval.
+
+   This is significantly slower than copyRowSingleMaxval().
+-----------------------------------------------------------------------------*/
+    unsigned int col;
+            
+    for (col = 0; col < outpamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < outpamP->depth; ++plane) 
+            tuplerow[col][plane] = 
+                jasperRow[plane][col] * 
+                outpamP->maxval / jasperMaxval[plane];
+    }
+}
+
+
+
+static void
+convertToPamPnm(struct pam *  const outpamP,
+                jas_image_t * const jasperP,
+                int           const jasperCmptNo[]) {
+
+    jas_matrix_t ** matrix;  /* malloc'ed */
+        /* matrix[X] is the data for Plane X of the current row */
+    sample * jasperMaxval;
+    unsigned int row;
+    tuple * tuplerow;
+    jas_seqent_t ** jasperRow;   /* malloc'ed */
+       /* A row of a plane of the raster from the Jasper library 
+          This is an array of pointers into the 'matrix' data structures.
+       */
+    bool singleMaxval;
+
+    createMatrices(outpamP, &matrix);
+
+    computeComponentMaxval(outpamP, jasperP, jasperCmptNo,
+                           &jasperMaxval, &singleMaxval);
+
+    MALLOCARRAY(jasperRow, outpamP->depth);
+    if (jasperRow == NULL)
+        pm_error("Out of memory");
+
+    tuplerow = pnm_allocpamrow(outpamP);
+
+    for (row = 0; row < outpamP->height; ++row) {
+        unsigned int plane;
+
+        for (plane = 0; plane < outpamP->depth; ++plane) {
+            int rc;
+            rc = jas_image_readcmpt(jasperP, jasperCmptNo[plane], 0, row,
+                                    outpamP->width, 1,
+                                    matrix[plane]);
+            if (rc != 0)
+                pm_error("jas_image_readcmpt() of row %u plane %u "
+                         "failed.", 
+                         row, plane);
+            jasperRow[plane] = jas_matrix_getref(matrix[plane], 0, 0);
+        }
+        if (singleMaxval) 
+            copyRowSingleMaxval(jasperRow, tuplerow, outpamP);
+        else
+            copyRowAnyMaxval(jasperRow, tuplerow, outpamP, jasperMaxval);
+
+        pnm_writepamrow(outpamP, tuplerow);
+    }
+    pnm_freepamrow(tuplerow);
+
+    destroyMatrices(outpamP, matrix);
+
+    free(jasperRow);
+    free(jasperMaxval);
+}
+
+
+
+int
+main(int argc, char **argv)
+{
+    struct cmdlineInfo cmdline;
+    struct pam outpam;
+    int * jasperCmpt;  /* malloc'ed */
+       /* jaspercmpt[P] is the component number for use with the
+          Jasper library that corresponds to Plane P of the PAM.  
+       */
+    jas_image_t * jasperP;
+
+    pnm_init(&argc, argv);
+    
+    parseCommandLine(argc, argv, &cmdline);
+    
+    { 
+        int rc;
+        
+        rc = jas_init();
+        if ( rc != 0 )
+            pm_error("Failed to initialize Jasper library.  "
+                     "jas_init() returns rc %d", rc );
+    }
+    
+    jas_setdbglevel(cmdline.debuglevel);
+    
+    readJpc(cmdline.inputFilename, &jasperP);
+
+    outpam.file = stdout;
+    outpam.size = sizeof(outpam);
+    outpam.len  = PAM_STRUCT_SIZE(tuple_type);
+
+    computeOutputParm(jasperP, &outpam, &jasperCmpt);
+
+    pnm_writepaminit(&outpam);
+    
+    convertToPamPnm(&outpam, jasperP, jasperCmpt);
+    
+    free(jasperCmpt);
+	jas_image_destroy(jasperP);
+
+    pm_close(stdout);
+    
+    return 0;
+}
diff --git a/converter/other/jpeg2000/libjasper/Makefile b/converter/other/jpeg2000/libjasper/Makefile
new file mode 100644
index 00000000..73d263ec
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/Makefile
@@ -0,0 +1,28 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/other/jpeg2000/libjasper
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+SUBDIRS = base jp2 jpc
+
+# NOTE: The library source code uses 64 bit types, so you cannot
+# build it (compile will fail) if you don't have 64 bit types defined
+# by pm_config.h (normally by including <inttypes.h>)
+
+all: libjasper.a
+
+include $(SRCDIR)/$(SUBDIR)/Makefile.common
+
+# We cheat a bit here -- the real dependencies are all the .o files listed
+# in the part list, but since we don't know what those are, we just do a
+# 'make all' in each subdirectory to get them built.  That means it always
+# looks like dependencies were rebuilt and libjasper.a gets rebuilt every
+# time.
+libjasper.a: $(SUBDIRS:%=%/all) partlist 
+	ar rc $@ $(shell cat partlist)
+	$(RANLIB) $@
+
diff --git a/converter/other/jpeg2000/libjasper/Makefile.common b/converter/other/jpeg2000/libjasper/Makefile.common
new file mode 100644
index 00000000..71a11832
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/Makefile.common
@@ -0,0 +1,41 @@
+# -*-makefile-*-    <-- an Emacs control
+# This is common rules for the pnmtojpc subdirectories.
+#
+# Set the following variables before including this:
+#
+#  LIB_OBJECTS: List of object files from this directory that go into 
+#               libjasper.
+
+all: $(LIB_OBJECTS) partlist
+
+partlist: $(SUBDIRS:%=%/partlist)
+	cat /dev/null $(SUBDIRS:%=%/partlist) >$@
+	echo $(LIB_OBJECTS:%=$(CURDIR)/%) >>$@
+
+.PHONY: $(SUBDIRS:%=%/partlist)
+$(SUBDIRS:%=%/partlist): %/partlist: $(CURDIR)/%
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
+	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+
+INCLUDES = -I$(JASPERSRCDIR)/include -I$(JASPERSRCDIR)/importinc
+
+include $(SRCDIR)/Makefile.common
+
+DEFS = -DHAVE_LIBM=1 -DSTDC_HEADERS=1 -DHAVE_FCNTL_H=1 -DHAVE_LIMITS_H=1 -DHAVE_UNISTD_H=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STDDEF_H=1 -DEXCLUDE_BMP_SUPPORT -DEXCLUDE_RAS_SUPPORT -DEXCLUDE_MIF_SUPPORT -DEXCLUDE_JPG_SUPPORT -DEXCLUDE_PGX_SUPPORT -DEXCLUDE_PNM_SUPPORT
+
+$(LIB_OBJECTS):%.o:%.c
+	$(CC) -c $(CFLAGS) $(INCLUDES) $(DEFS) $(CADD) $<
+
+$(LIB_OBJECTS): $(JASPERSRCDIR)/importinc
+
+$(JASPERSRCDIR)/importinc:
+	$(MAKE) -C $(dir $@) $(notdir $@)
+
+clean: localclean
+
+.PHONY: localclean
+localclean:
+	rm -f partlist
+
+.PHONY: FORCE
+FORCE:
diff --git a/converter/other/jpeg2000/libjasper/README b/converter/other/jpeg2000/libjasper/README
new file mode 100644
index 00000000..ad3e019b
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/README
@@ -0,0 +1,17 @@
+The code in the directory 'libjasper' is taken from the Jasper package.
+In particular, from the 'src/libjasper' directory of Jasper Version
+1.600.0.
+
+The adaptation was done by Bryan Henderson on 2002.10.26.
+
+The adaptation involved:
+
+  - remove stuff for formats other than PNM.
+
+  - Replace build stuff (Jasper uses GNU Autoconf/Automake/Libtool).
+
+See <http://www.ece.uvic.ca/~mdadams/jasper/>.
+
+
+You cannot build this library without a 64 bit compiler (and 64 bit
+types such as int64_t defined).  It does a lot of 64 bit arithmetic.
diff --git a/converter/other/jpeg2000/libjasper/base/Makefile b/converter/other/jpeg2000/libjasper/base/Makefile
new file mode 100644
index 00000000..0ee65b5e
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/base/Makefile
@@ -0,0 +1,21 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../../../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/other/jpeg2000/libjasper/base
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+JASPERSRCDIR=$(SRCDIR)/$(SUBDIR)/..
+
+include $(BUILDDIR)/Makefile.config
+
+LIB_OBJECTS = jas_debug.o jas_getopt.o jas_image.o jas_init.o \
+	      jas_malloc.o jas_seq.o jas_stream.o jas_string.o \
+	      jas_tvp.o jas_version.o
+
+MERGE_OBJECTS =
+
+all: partlist $(LIB_OBJECTS)
+
+include $(JASPERSRCDIR)/Makefile.common
+
diff --git a/converter/other/jpeg2000/libjasper/base/jas_debug.c b/converter/other/jpeg2000/libjasper/base/jas_debug.c
new file mode 100644
index 00000000..4248178f
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/base/jas_debug.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "jasper/jas_types.h"
+#include "jasper/jas_debug.h"
+
+/******************************************************************************\
+* Local data.
+\******************************************************************************/
+
+static int jas_dbglevel = 0;
+/* The debug level. */
+
+/******************************************************************************\
+* Code for getting/setting the debug level.
+\******************************************************************************/
+
+/* Set the library debug level. */
+int jas_setdbglevel(int dbglevel)
+{
+	int olddbglevel;
+
+	/* Save the old debug level. */
+	olddbglevel = jas_dbglevel;
+
+	/* Change the debug level. */
+	jas_dbglevel = dbglevel;
+
+	/* Return the old debug level. */
+	return olddbglevel;
+}
+
+/* Get the library debug level. */
+int jas_getdbglevel()
+{
+	return jas_dbglevel;
+}
+
+/******************************************************************************\
+* Code.
+\******************************************************************************/
+
+/* Perform formatted output to standard error. */
+int jas_eprintf(const char *fmt, ...)
+{
+	int ret;
+	va_list ap;
+
+	va_start(ap, fmt);
+	ret = vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	return ret;
+}
+
+/* Dump memory to a stream. */
+int jas_memdump(FILE *out, void *data, size_t len)
+{
+	int i;
+	int j;
+	unsigned char *dp;
+	dp = data;
+	for (i = 0; i < len; i += 16) {
+		fprintf(out, "%04x:", i);
+		for (j = 0; j < 16; ++j) {
+			if (i + j < len) {
+				fprintf(out, " %02x", dp[i + j]);
+			}
+		}
+		fprintf(out, "\n");
+	}
+	return 0;
+}
diff --git a/converter/other/jpeg2000/libjasper/base/jas_getopt.c b/converter/other/jpeg2000/libjasper/base/jas_getopt.c
new file mode 100644
index 00000000..7f579b3e
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/base/jas_getopt.c
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 1999-2000, Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved. 
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Command Line Option Parsing Library
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdio.h>
+#include <string.h>
+
+#include "jasper/jas_getopt.h"
+#include "jasper/jas_math.h"
+
+/******************************************************************************\
+* Global data.
+\******************************************************************************/
+
+int jas_optind = 0;
+int jas_opterr = 1;
+char *jas_optarg = 0;
+
+/******************************************************************************\
+* Code.
+\******************************************************************************/
+
+static jas_opt_t *jas_optlookup(jas_opt_t *opts, char *name)
+{
+	jas_opt_t *opt;
+
+	for (opt = opts; opt->id >= 0 && opt->name; ++opt) {
+		if (!strcmp(opt->name, name)) {
+			return opt;
+		}
+	}
+	return 0;
+}
+
+int jas_getopt(int argc, char **argv, jas_opt_t *opts)
+{
+	char *cp;
+	int id;
+	int hasarg;
+	jas_opt_t *opt;
+	char *s;
+
+	if (!jas_optind) {
+		jas_optind = JAS_MIN(1, argc);
+	}
+	while (jas_optind < argc) {
+		s = cp = argv[jas_optind];
+		if (*cp == '-') {
+			/* We are processing an option. */
+			++jas_optind;
+			if (*++cp == '-') {
+				/* We are processing a long option. */
+				++cp;
+				if (*cp == '\0') {
+					/* This is the end of the options. */
+					return JAS_GETOPT_EOF;
+				}
+				if (!(opt = jas_optlookup(opts, cp))) {
+					if (jas_opterr) {
+						fprintf(stderr, "unknown long option %s\n", s);
+					}
+					return JAS_GETOPT_ERR;
+				}
+				hasarg = (opt->flags & JAS_OPT_HASARG) != 0;
+				id = opt->id;
+			} else {
+				/* We are processing a short option. */
+				if (strlen(cp) != 1 ||
+				  !(opt = jas_optlookup(opts, cp))) {
+					if (jas_opterr) {
+						fprintf(stderr, "unknown short option %s\n", s);
+					}
+					return JAS_GETOPT_ERR;
+				}
+				hasarg = (opt->flags & JAS_OPT_HASARG) != 0;
+				id = opt->id;
+			}
+			if (hasarg) {
+				/* The option has an argument. */
+				if (jas_optind >= argc) {
+					if (jas_opterr) {
+						fprintf(stderr, "missing argument for option %s\n", s);
+					}
+					return JAS_GETOPT_ERR;
+				}
+				jas_optarg = argv[jas_optind];
+				++jas_optind;
+			} else {
+				/* The option does not have an argument. */
+				jas_optarg = 0;
+			}
+			return id;
+		} else {
+			/* We are not processing an option. */
+			return JAS_GETOPT_EOF;
+		}
+	}
+	return JAS_GETOPT_EOF;
+}
diff --git a/converter/other/jpeg2000/libjasper/base/jas_image.c b/converter/other/jpeg2000/libjasper/base/jas_image.c
new file mode 100644
index 00000000..e6439fcd
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/base/jas_image.c
@@ -0,0 +1,968 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Image Library
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "jasper/jas_math.h"
+#include "jasper/jas_image.h"
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_string.h"
+
+/******************************************************************************\
+* Types.
+\******************************************************************************/
+
+/******************************************************************************\
+* Local prototypes.
+\******************************************************************************/
+
+static void jas_image_cmpt_destroy(jas_image_cmpt_t *cmpt);
+static jas_image_cmpt_t *jas_image_cmpt_create(uint_fast32_t tlx, uint_fast32_t tly,
+  uint_fast32_t hstep, uint_fast32_t vstep, uint_fast32_t width, uint_fast32_t
+  height, uint_fast16_t depth, bool sgnd, uint_fast32_t inmem);
+static void jas_image_setbbox(jas_image_t *image);
+static jas_image_cmpt_t *jas_image_cmpt_copy(jas_image_cmpt_t *cmpt);
+static jas_image_cmpt_t *jas_image_cmpt_create0();
+static int jas_image_growcmpts(jas_image_t *image, int maxcmpts);
+static uint_fast32_t inttobits(jas_seqent_t v, int prec, bool sgnd);
+static jas_seqent_t bitstoint(uint_fast32_t v, int prec, bool sgnd);
+
+/******************************************************************************\
+* Global data.
+\******************************************************************************/
+
+static int jas_image_numfmts = 0;
+static jas_image_fmtinfo_t jas_image_fmtinfos[JAS_IMAGE_MAXFMTS];
+
+/******************************************************************************\
+* Create and destroy operations.
+\******************************************************************************/
+
+jas_image_t *jas_image_create(uint_fast16_t numcmpts, jas_image_cmptparm_t *cmptparms,
+  int colorspace)
+{
+	jas_image_t *image;
+	uint_fast32_t rawsize;
+	uint_fast32_t inmem;
+	uint_fast16_t cmptno;
+	jas_image_cmptparm_t *cmptparm;
+
+	if (!(image = jas_image_create0())) {
+		return 0;
+	}
+
+	image->colorspace_ = colorspace;
+	image->maxcmpts_ = numcmpts;
+	image->inmem_ = true;
+
+	/* Allocate memory for the per-component information. */
+	if (!(image->cmpts_ = jas_malloc(image->maxcmpts_ *
+	  sizeof(jas_image_cmpt_t *)))) {
+		jas_image_destroy(image);
+		return 0;
+	}
+	/* Initialize in case of failure. */
+	for (cmptno = 0; cmptno < image->maxcmpts_; ++cmptno) {
+		image->cmpts_[cmptno] = 0;
+	}
+
+	/* Compute the approximate raw size of the image. */
+	rawsize = 0;
+	for (cmptno = 0, cmptparm = cmptparms; cmptno < numcmpts; ++cmptno,
+	  ++cmptparm) {
+		rawsize += cmptparm->width * cmptparm->height *
+		  (cmptparm->prec + 7) / 8;
+	}
+	/* Decide whether to buffer the image data in memory, based on the
+	  raw size of the image. */
+	inmem = (rawsize < JAS_IMAGE_INMEMTHRESH);
+
+	/* Create the individual image components. */
+	for (cmptno = 0, cmptparm = cmptparms; cmptno < numcmpts; ++cmptno,
+	  ++cmptparm) {
+		if (!(image->cmpts_[cmptno] = jas_image_cmpt_create(cmptparm->tlx,
+		  cmptparm->tly, cmptparm->hstep, cmptparm->vstep,
+		  cmptparm->width, cmptparm->height, cmptparm->prec,
+		  cmptparm->sgnd, inmem))) {
+			jas_image_destroy(image);
+			return 0;
+		}
+		++image->numcmpts_;
+	}
+
+	/* Determine the bounding box for all of the components on the
+	  reference grid (i.e., the image area) */
+	jas_image_setbbox(image);
+
+	return image;
+}
+
+jas_image_t *jas_image_create0()
+{
+	jas_image_t *image;
+
+	if (!(image = jas_malloc(sizeof(jas_image_t)))) {
+		return 0;
+	}
+
+	image->tlx_ = 0;
+	image->tly_ = 0;
+	image->brx_ = 0;
+	image->bry_ = 0;
+	image->colorspace_ = JAS_IMAGE_CS_UNKNOWN;
+	image->numcmpts_ = 0;
+	image->maxcmpts_ = 0;
+	image->cmpts_ = 0;
+	image->inmem_ = true;
+	image->iccp_ = 0;
+	image->iccplen_ = 0;
+
+	return image;
+}
+
+jas_image_t *jas_image_copy(jas_image_t *image)
+{
+	jas_image_t *newimage;
+	int cmptno;
+
+	newimage = jas_image_create0();
+	if (jas_image_growcmpts(newimage, image->numcmpts_)) {
+		goto error;
+	}
+	for (cmptno = 0; cmptno < image->numcmpts_; ++cmptno) {
+		if (!(newimage->cmpts_[cmptno] = jas_image_cmpt_copy(image->cmpts_[cmptno]))) {
+			goto error;
+		}
+		++newimage->numcmpts_;
+	}
+
+	jas_image_setbbox(newimage);
+
+	return newimage;
+error:
+	if (newimage) {
+		jas_image_destroy(newimage);
+	}
+	return 0;
+}
+
+static jas_image_cmpt_t *jas_image_cmpt_create0()
+{
+	jas_image_cmpt_t *cmpt;
+	if (!(cmpt = jas_malloc(sizeof(jas_image_cmpt_t)))) {
+		return 0;
+	}
+	memset(cmpt, 0, sizeof(jas_image_cmpt_t));
+	cmpt->type_ = JAS_IMAGE_CT_UNKNOWN;
+	return cmpt;
+}
+
+static jas_image_cmpt_t *jas_image_cmpt_copy(jas_image_cmpt_t *cmpt)
+{
+	jas_image_cmpt_t *newcmpt;
+
+	if (!(newcmpt = jas_image_cmpt_create0())) {
+		return 0;
+	}
+	newcmpt->tlx_ = cmpt->tlx_;
+	newcmpt->tly_ = cmpt->tly_;
+	newcmpt->hstep_ = cmpt->hstep_;
+	newcmpt->vstep_ = cmpt->vstep_;
+	newcmpt->width_ = cmpt->width_;
+	newcmpt->height_ = cmpt->height_;
+	newcmpt->prec_ = cmpt->prec_;
+	newcmpt->sgnd_ = cmpt->sgnd_;
+	newcmpt->cps_ = cmpt->cps_;
+	newcmpt->type_ = cmpt->type_;
+	if (!(newcmpt->stream_ = jas_stream_memopen(0, 0))) {
+		return 0;
+	}
+	if (jas_stream_seek(cmpt->stream_, 0, SEEK_SET)) {
+		return 0;
+	}
+	if (jas_stream_copy(newcmpt->stream_, cmpt->stream_, -1)) {
+		return 0;
+	}
+	if (jas_stream_seek(newcmpt->stream_, 0, SEEK_SET)) {
+		return 0;
+	}
+	return newcmpt;
+}
+
+void jas_image_destroy(jas_image_t *image)
+{
+	int i;
+
+	if (image->cmpts_) {
+		for (i = 0; i < image->numcmpts_; ++i) {
+			jas_image_cmpt_destroy(image->cmpts_[i]);
+			image->cmpts_[i] = 0;
+		}
+		jas_free(image->cmpts_);
+	}
+	jas_free(image);
+}
+
+static jas_image_cmpt_t *jas_image_cmpt_create(uint_fast32_t tlx, uint_fast32_t tly,
+  uint_fast32_t hstep, uint_fast32_t vstep, uint_fast32_t width, uint_fast32_t
+  height, uint_fast16_t depth, bool sgnd, uint_fast32_t inmem)
+{
+	jas_image_cmpt_t *cmpt;
+	long size;
+
+	if (!(cmpt = jas_malloc(sizeof(jas_image_cmpt_t)))) {
+		return 0;
+	}
+
+	cmpt->tlx_ = tlx;
+	cmpt->tly_ = tly;
+	cmpt->hstep_ = hstep;
+	cmpt->vstep_ = vstep;
+	cmpt->width_ = width;
+	cmpt->height_ = height;
+	cmpt->prec_ = depth;
+	cmpt->sgnd_ = sgnd;
+	cmpt->stream_ = 0;
+	cmpt->cps_ = (depth + 7) / 8;
+
+	size = cmpt->width_ * cmpt->height_ * cmpt->cps_;
+	cmpt->stream_ = (inmem) ? jas_stream_memopen(0, size) : jas_stream_tmpfile();
+	if (!cmpt->stream_) {
+		jas_image_cmpt_destroy(cmpt);
+		return 0;
+	}
+
+	/* Zero the component data.  This isn't necessary, but it is
+	convenient for debugging purposes. */
+	if (jas_stream_seek(cmpt->stream_, size - 1, SEEK_SET) < 0 ||
+	  jas_stream_putc(cmpt->stream_, 0) == EOF ||
+	  jas_stream_seek(cmpt->stream_, 0, SEEK_SET) < 0) {
+		jas_image_cmpt_destroy(cmpt);
+		return 0;
+	}
+
+	return cmpt;
+}
+
+static void jas_image_cmpt_destroy(jas_image_cmpt_t *cmpt)
+{
+	if (cmpt->stream_) {
+		jas_stream_close(cmpt->stream_);
+	}
+	jas_free(cmpt);
+}
+
+/******************************************************************************\
+* Load and save operations.
+\******************************************************************************/
+
+jas_image_t *jas_image_decode(jas_stream_t *in, int fmt, char *optstr)
+{
+	jas_image_fmtinfo_t *fmtinfo;
+
+	/* If possible, try to determine the format of the input data. */
+	if (fmt < 0) {
+		if ((fmt = jas_image_getfmt(in)) < 0) {
+			return 0;
+		}
+	}
+	if (!(fmtinfo = jas_image_lookupfmtbyid(fmt))) {
+		return 0;
+	}
+	return (fmtinfo->ops.decode) ? (*fmtinfo->ops.decode)(in, optstr) : 0;
+}
+
+int jas_image_encode(jas_image_t *image, jas_stream_t *out, int fmt, char *optstr)
+{
+	jas_image_fmtinfo_t *fmtinfo;
+	if (!(fmtinfo = jas_image_lookupfmtbyid(fmt))) {
+		return -1;
+	}
+	return (fmtinfo->ops.encode) ? (*fmtinfo->ops.encode)(image, out,
+	  optstr) : (-1);
+}
+
+/******************************************************************************\
+* Component read and write operations.
+\******************************************************************************/
+
+int jas_image_readcmpt(jas_image_t *image, uint_fast16_t cmptno, uint_fast32_t x, uint_fast32_t y, uint_fast32_t width,
+  uint_fast32_t height, jas_matrix_t *data)
+{
+	jas_image_cmpt_t *cmpt;
+	uint_fast32_t i;
+	uint_fast32_t j;
+	int k;
+	jas_seqent_t v;
+	int c;
+	jas_seqent_t *dr;
+	jas_seqent_t *d;
+	int drs;
+
+	if (cmptno >= image->numcmpts_) {
+		return -1;
+	}
+
+	cmpt = image->cmpts_[cmptno];
+	if (x >= cmpt->width_ || y >= cmpt->height_ ||
+	  x + width > cmpt->width_ ||
+	  y + height > cmpt->height_) {
+		return -1;
+	}
+
+	if (jas_matrix_numrows(data) != height || jas_matrix_numcols(data) != width) {
+		if (jas_matrix_resize(data, height, width)) {
+			return -1;
+		}
+	}
+
+	dr = jas_matrix_getref(data, 0, 0);
+	drs = jas_matrix_rowstep(data);
+	for (i = 0; i < height; ++i, dr += drs) {
+		d = dr;
+		if (jas_stream_seek(cmpt->stream_, (cmpt->width_ * (y + i) + x)
+		  * cmpt->cps_, SEEK_SET) < 0) {
+			return -1;
+		}
+		for (j = width; j > 0; --j, ++d) {
+			v = 0;
+			for (k = cmpt->cps_; k > 0; --k) {
+				if ((c = jas_stream_getc(cmpt->stream_)) == EOF) {
+					return -1;
+				}
+				v = (v << 8) | (c & 0xff);
+			}
+			*d = bitstoint(v, cmpt->prec_, cmpt->sgnd_);
+		}
+	}
+
+	return 0;
+}
+
+#if 0
+int_fast64_t jas_image_readcmpt1(jas_image_t *image, uint_fast16_t cmptno,
+  uint_fast32_t x, uint_fast32_t y)
+{
+	jas_image_cmpt_t *cmpt;
+	int k;
+	int c;
+	int_fast64_t v;
+	cmpt = image->cmpts_[cmptno];
+	if (jas_stream_seek(cmpt->stream_, (cmpt->width_ * y + x) * cmpt->cps_,
+	  SEEK_SET) < 0) {
+		goto error;
+	}
+	v = 0;
+	for (k = cmpt->cps_; k > 0; --k) {
+		if ((c = jas_stream_getc(cmpt->stream_)) == EOF) {
+			goto error;
+		}
+		v = (v << 8) | (c & 0xff);
+	}
+if (cmpt->sgnd_) {
+	abort();
+}
+
+	return v;
+
+error:
+	return 0;
+}
+#endif
+
+int jas_image_writecmpt(jas_image_t *image, uint_fast16_t cmptno, uint_fast32_t x, uint_fast32_t y, uint_fast32_t width,
+  uint_fast32_t height, jas_matrix_t *data)
+{
+	jas_image_cmpt_t *cmpt;
+	uint_fast32_t i;
+	uint_fast32_t j;
+	jas_seqent_t *d;
+	jas_seqent_t *dr;
+	int drs;
+	jas_seqent_t v;
+	int k;
+	int c;
+
+	if (cmptno >= image->numcmpts_) {
+		return -1;
+	}
+
+	cmpt = image->cmpts_[cmptno];
+	if (x >= cmpt->width_ || y >= cmpt->height_ ||
+	  x + width > cmpt->width_ ||
+	  y + height > cmpt->height_) {
+		return -1;
+	}
+
+	if (jas_matrix_numrows(data) != height || jas_matrix_numcols(data) != width) {
+		return -1;
+	}
+
+	dr = jas_matrix_getref(data, 0, 0);
+	drs = jas_matrix_rowstep(data);
+	for (i = 0; i < height; ++i, dr += drs) {
+		d = dr;
+		if (jas_stream_seek(cmpt->stream_, (cmpt->width_ * (y + i) + x)
+		  * cmpt->cps_, SEEK_SET) < 0) {
+			return -1;
+		}
+		for (j = width; j > 0; --j, ++d) {
+			v = inttobits(*d, cmpt->prec_, cmpt->sgnd_);
+			for (k = cmpt->cps_; k > 0; --k) {
+				c = (v >> (8 * (cmpt->cps_ - 1))) & 0xff;
+				if (jas_stream_putc(cmpt->stream_,
+				  (unsigned char) c) == EOF) {
+					return -1;
+				}
+				v <<= 8;
+			}
+		}
+	}
+
+	return 0;
+}
+
+/******************************************************************************\
+* File format operations.
+\******************************************************************************/
+
+void jas_image_clearfmts()
+{
+	int i;
+	jas_image_fmtinfo_t *fmtinfo;
+	for (i = 0; i < jas_image_numfmts; ++i) {
+		fmtinfo = &jas_image_fmtinfos[i];
+		if (fmtinfo->name) {
+			jas_free(fmtinfo->name);
+			fmtinfo->name = 0;
+		}
+		if (fmtinfo->ext) {
+			jas_free(fmtinfo->ext);
+			fmtinfo->ext = 0;
+		}
+		if (fmtinfo->desc) {
+			jas_free(fmtinfo->desc);
+			fmtinfo->desc = 0;
+		}
+	}
+	jas_image_numfmts = 0;
+}
+
+int jas_image_addfmt(int id, const char *name, const char *ext, 
+  const char *desc,
+  jas_image_fmtops_t *ops)
+{
+	jas_image_fmtinfo_t *fmtinfo;
+	assert(id >= 0 && name && ext && ops);
+	if (jas_image_numfmts >= JAS_IMAGE_MAXFMTS) {
+		return -1;
+	}
+	fmtinfo = &jas_image_fmtinfos[jas_image_numfmts];
+	fmtinfo->id = id;
+	if (!(fmtinfo->name = jas_strdup(name))) {
+		return -1;
+	}
+	if (!(fmtinfo->ext = jas_strdup(ext))) {
+		jas_free(fmtinfo->name);
+		return -1;
+	}
+	if (!(fmtinfo->desc = jas_strdup(desc))) {
+		jas_free(fmtinfo->name);
+		jas_free(fmtinfo->ext);
+		return -1;
+	}
+	fmtinfo->ops = *ops;
+	++jas_image_numfmts;
+	return 0;
+}
+
+int jas_image_strtofmt(char *name)
+{
+	jas_image_fmtinfo_t *fmtinfo;
+	if (!(fmtinfo = jas_image_lookupfmtbyname(name))) {
+		return -1;
+	}
+	return fmtinfo->id;
+}
+
+char *jas_image_fmttostr(int fmt)
+{
+	jas_image_fmtinfo_t *fmtinfo;
+	if (!(fmtinfo = jas_image_lookupfmtbyid(fmt))) {
+		return 0;
+	}
+	return fmtinfo->name;
+}
+
+int jas_image_getfmt(jas_stream_t *in)
+{
+	jas_image_fmtinfo_t *fmtinfo;
+	int found;
+	int i;
+
+	/* Check for data in each of the supported formats. */
+	found = 0;
+	for (i = 0, fmtinfo = jas_image_fmtinfos; i < jas_image_numfmts; ++i,
+	  ++fmtinfo) {
+		if (fmtinfo->ops.validate) {
+			/* Is the input data valid for this format? */
+			if (!(*fmtinfo->ops.validate)(in)) {
+				found = 1;
+				break;
+			}
+		}
+	}
+	return found ? fmtinfo->id : (-1);
+}
+
+int jas_image_fmtfromname(char *name)
+{
+	int i;
+	char *ext;
+	jas_image_fmtinfo_t *fmtinfo;
+	/* Get the file name extension. */
+	if (!(ext = strrchr(name, '.'))) {
+		return -1;
+	}
+	++ext;
+	/* Try to find a format that uses this extension. */	
+	for (i = 0, fmtinfo = jas_image_fmtinfos; i < jas_image_numfmts; ++i,
+	  ++fmtinfo) {
+		/* Do we have a match? */
+		if (!strcmp(ext, fmtinfo->ext)) {
+			return fmtinfo->id;
+		}
+	}
+	return -1;
+}
+
+/******************************************************************************\
+* Miscellaneous operations.
+\******************************************************************************/
+
+uint_fast32_t jas_image_rawsize(jas_image_t *image)
+{
+	uint_fast32_t rawsize;
+	uint_fast32_t cmptno;
+	jas_image_cmpt_t *cmpt;
+
+	rawsize = 0;
+	for (cmptno = 0; cmptno < image->numcmpts_; ++cmptno) {
+		cmpt = image->cmpts_[cmptno];
+		rawsize += (cmpt->width_ * cmpt->height_ * cmpt->prec_ +
+		  7) / 8;
+	}
+	return rawsize;
+}
+
+void jas_image_delcmpt(jas_image_t *image, uint_fast16_t cmptno)
+{
+	if (cmptno >= image->numcmpts_) {
+		return;
+	}
+	jas_image_cmpt_destroy(image->cmpts_[cmptno]);
+	if (cmptno < image->numcmpts_) {
+		memmove(&image->cmpts_[cmptno], &image->cmpts_[cmptno + 1],
+		  (image->numcmpts_ - 1 - cmptno) * sizeof(jas_image_cmpt_t *));
+	}
+	--image->numcmpts_;
+
+	jas_image_setbbox(image);
+}
+
+int jas_image_addcmpt(jas_image_t *image, uint_fast16_t cmptno,
+  jas_image_cmptparm_t *cmptparm)
+{
+	jas_image_cmpt_t *newcmpt;
+	assert(cmptno <= image->numcmpts_);
+	if (image->numcmpts_ >= image->maxcmpts_) {
+		if (jas_image_growcmpts(image, image->maxcmpts_ + 128)) {
+			return -1;
+		}
+	}
+	if (!(newcmpt = jas_image_cmpt_create(cmptparm->tlx,
+	  cmptparm->tly, cmptparm->hstep, cmptparm->vstep,
+	  cmptparm->width, cmptparm->height, cmptparm->prec,
+	  cmptparm->sgnd, 1))) {
+		return -1;
+	}
+	if (cmptno < image->numcmpts_) {
+		memmove(&image->cmpts_[cmptno + 1], &image->cmpts_[cmptno],
+		  (image->numcmpts_ - cmptno) * sizeof(jas_image_cmpt_t *));
+	}
+	image->cmpts_[cmptno] = newcmpt;
+	++image->numcmpts_;
+
+	jas_image_setbbox(image);
+
+	return 0;
+}
+
+jas_image_fmtinfo_t *jas_image_lookupfmtbyid(int id)
+{
+	int i;
+	jas_image_fmtinfo_t *fmtinfo;
+
+	for (i = 0, fmtinfo = jas_image_fmtinfos; i < jas_image_numfmts; ++i, ++fmtinfo) {
+		if (fmtinfo->id == id) {
+			return fmtinfo;
+		}
+	}
+	return 0;
+}
+
+jas_image_fmtinfo_t *jas_image_lookupfmtbyname(const char *name)
+{
+	int i;
+	jas_image_fmtinfo_t *fmtinfo;
+
+	for (i = 0, fmtinfo = jas_image_fmtinfos; i < jas_image_numfmts; ++i, ++fmtinfo) {
+		if (!strcmp(fmtinfo->name, name)) {
+			return fmtinfo;
+		}
+	}
+	return 0;
+}
+
+
+
+
+
+static uint_fast32_t inttobits(jas_seqent_t v, int prec, bool sgnd)
+{
+	uint_fast32_t ret;
+	ret = ((sgnd && v < 0) ? ((1 << prec) + v) : v) & JAS_ONES(prec);
+	return ret;
+}
+
+static jas_seqent_t bitstoint(uint_fast32_t v, int prec, bool sgnd)
+{
+	jas_seqent_t ret;
+	v &= JAS_ONES(prec);
+	ret = (sgnd && (v & (1 << (prec - 1)))) ? (v - (1 << prec)) : v;
+	return ret;
+}
+
+static void jas_image_setbbox(jas_image_t *image)
+{
+	jas_image_cmpt_t *cmpt;
+	int cmptno;
+	int_fast32_t x;
+	int_fast32_t y;
+
+	if (image->numcmpts_ > 0) {
+		/* Determine the bounding box for all of the components on the
+		  reference grid (i.e., the image area) */
+		cmpt = image->cmpts_[0];
+		image->tlx_ = cmpt->tlx_;
+		image->tly_ = cmpt->tly_;
+		image->brx_ = cmpt->tlx_ + cmpt->hstep_ * (cmpt->width_ - 1) + 1;
+		image->bry_ = cmpt->tly_ + cmpt->vstep_ * (cmpt->height_ - 1) + 1;
+		for (cmptno = 1; cmptno < image->numcmpts_; ++cmptno) {
+			cmpt = image->cmpts_[cmptno];
+			if (image->tlx_ > cmpt->tlx_) {
+				image->tlx_ = cmpt->tlx_;
+			}
+			if (image->tly_ > cmpt->tly_) {
+				image->tly_ = cmpt->tly_;
+			}
+			x = cmpt->tlx_ + cmpt->hstep_ * (cmpt->width_ - 1) + 1;
+			if (image->brx_ < x) {
+				image->brx_ = x;
+			}
+			y = cmpt->tly_ + cmpt->vstep_ * (cmpt->height_ - 1) + 1;
+			if (image->bry_ < y) {
+				image->bry_ = y;
+			}
+		}
+	} else {
+		image->tlx_ = 0;
+		image->tly_ = 0;
+		image->brx_ = 0;
+		image->bry_ = 0;
+	}
+}
+
+static int jas_image_growcmpts(jas_image_t *image, int maxcmpts)
+{
+	jas_image_cmpt_t **newcmpts;
+	int cmptno;
+
+	newcmpts = (!image->cmpts_) ? jas_malloc(maxcmpts * sizeof(jas_image_cmpt_t *)) :
+	  jas_realloc(image->cmpts_, maxcmpts * sizeof(jas_image_cmpt_t *));
+	if (!newcmpts) {
+		return -1;
+	}
+	image->cmpts_ = newcmpts;
+	image->maxcmpts_ = maxcmpts;
+	for (cmptno = image->numcmpts_; cmptno < image->maxcmpts_; ++cmptno) {
+		image->cmpts_[cmptno] = 0;
+	}
+	return 0;
+}
+
+int jas_image_copycmpt(jas_image_t *dstimage, int dstcmptno, jas_image_t *srcimage,
+  int srccmptno)
+{
+	jas_image_cmpt_t *newcmpt;
+	if (dstimage->numcmpts_ >= dstimage->maxcmpts_) {
+		if (jas_image_growcmpts(dstimage, dstimage->maxcmpts_ + 128)) {
+			return -1;
+		}
+	}
+	if (!(newcmpt = jas_image_cmpt_copy(srcimage->cmpts_[srccmptno]))) {
+		return -1;
+	}
+	if (dstcmptno < dstimage->numcmpts_) {
+		memmove(&dstimage->cmpts_[dstcmptno + 1], &dstimage->cmpts_[dstcmptno],
+		  (dstimage->numcmpts_ - dstcmptno) * sizeof(jas_image_cmpt_t *));
+	}
+	dstimage->cmpts_[dstcmptno] = newcmpt;
+	++dstimage->numcmpts_;
+
+	jas_image_setbbox(dstimage);
+	return 0;
+}
+
+void jas_image_dump(jas_image_t *image, FILE *out)
+{
+	int cmptno;
+	jas_seq2d_t *data;
+	jas_image_cmpt_t *cmpt;
+	if (!(data = jas_seq2d_create(0, 0, 1, 1))) {
+		abort();
+	}
+	for (cmptno = 0; cmptno < image->numcmpts_; ++cmptno) {
+		cmpt = image->cmpts_[cmptno];
+		fprintf(out, "prec=%d sgnd=%d\n", cmpt->prec_, cmpt->sgnd_);
+		if (jas_image_readcmpt(image, cmptno, 0, 0, 1, 1, data)) {
+			abort();
+		}
+		fprintf(out, "tlsample %ld\n", (long) jas_seq2d_get(data, 0, 0));
+	}
+	jas_seq2d_destroy(data);
+}
+
+int jas_image_depalettize(jas_image_t *image, int cmptno, int numlutents,
+  int_fast32_t *lutents, int dtype, int newcmptno)
+{
+	jas_image_cmptparm_t cmptparms;
+	int_fast32_t v;
+	int i;
+	int j;
+	jas_image_cmpt_t *cmpt;
+
+	cmpt = image->cmpts_[cmptno];
+	cmptparms.tlx = cmpt->tlx_;
+	cmptparms.tly = cmpt->tly_;
+	cmptparms.hstep = cmpt->hstep_;
+	cmptparms.vstep = cmpt->vstep_;
+	cmptparms.width = cmpt->width_;
+	cmptparms.height = cmpt->height_;
+	cmptparms.prec = JAS_IMAGE_CDT_GETPREC(dtype);
+	cmptparms.sgnd = JAS_IMAGE_CDT_GETSGND(dtype);
+
+	if (jas_image_addcmpt(image, newcmptno, &cmptparms)) {
+		return -1;
+	}
+	if (newcmptno <= cmptno) {
+		++cmptno;
+		cmpt = image->cmpts_[cmptno];
+	}
+
+	for (j = 0; j < cmpt->height_; ++j) {
+		for (i = 0; i < cmpt->width_; ++i) {
+			v = jas_image_readcmptsample(image, cmptno, i, j);
+			if (v < 0) {
+				v = 0;
+			} else if (v >= numlutents) {
+				v = numlutents - 1;
+			}
+			jas_image_writecmptsample(image, newcmptno, i, j,
+			  lutents[v]);
+		}
+	}
+	return 0;
+}
+
+int jas_image_readcmptsample(jas_image_t *image, int cmptno, int x, int y)
+{
+	jas_image_cmpt_t *cmpt;
+	uint_fast32_t v;
+	int k;
+	int c;
+
+	cmpt = image->cmpts_[cmptno];
+
+	if (jas_stream_seek(cmpt->stream_, (cmpt->width_ * y + x) * cmpt->cps_,
+	  SEEK_SET) < 0) {
+		return -1;
+	}
+	v = 0;
+	for (k = cmpt->cps_; k > 0; --k) {
+		if ((c = jas_stream_getc(cmpt->stream_)) == EOF) {
+			return -1;
+		}
+		v = (v << 8) | (c & 0xff);
+	}
+	return bitstoint(v, cmpt->prec_, cmpt->sgnd_);
+}
+
+void jas_image_writecmptsample(jas_image_t *image, int cmptno, int x, int y,
+  int_fast32_t v)
+{
+	jas_image_cmpt_t *cmpt;
+	uint_fast32_t t;
+	int k;
+	int c;
+
+	cmpt = image->cmpts_[cmptno];
+
+	if (jas_stream_seek(cmpt->stream_, (cmpt->width_ * y + x) * cmpt->cps_,
+	  SEEK_SET) < 0) {
+		return;
+	}
+	t = inttobits(v, cmpt->prec_, cmpt->sgnd_);
+	for (k = cmpt->cps_; k > 0; --k) {
+		c = (t >> (8 * (cmpt->cps_ - 1))) & 0xff;
+		if (jas_stream_putc(cmpt->stream_, (unsigned char) c) == EOF) {
+			return;
+		}
+		t <<= 8;
+	}
+}
+
+int jas_image_getcmptbytype(jas_image_t *image, int ctype)
+{
+	int cmptno;
+
+	for (cmptno = 0; cmptno < image->numcmpts_; ++cmptno) {
+		if (image->cmpts_[cmptno]->type_ == ctype) {
+			return cmptno;
+		}
+	}
+    return -1;
+}
diff --git a/converter/other/jpeg2000/libjasper/base/jas_init.c b/converter/other/jpeg2000/libjasper/base/jas_init.c
new file mode 100644
index 00000000..c1114bf7
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/base/jas_init.c
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_types.h"
+#include "jasper/jas_image.h"
+#include "jasper/jas_init.h"
+
+/******************************************************************************\
+* Code.
+\******************************************************************************/
+
+/* Initialize the image format table. */
+int jas_init()
+{
+	jas_image_fmtops_t fmtops;
+	int fmtid;
+
+	fmtid = 0;
+
+#if !defined(EXCLUDE_MIF_SUPPORT)
+	fmtops.decode = mif_decode;
+	fmtops.encode = mif_encode;
+	fmtops.validate = mif_validate;
+	jas_image_addfmt(fmtid, "mif", "mif", "My Image Format (MIF)", &fmtops);
+	++fmtid;
+#endif
+
+#if !defined(EXCLUDE_PNM_SUPPORT)
+	fmtops.decode = pnm_decode;
+	fmtops.encode = pnm_encode;
+	fmtops.validate = pnm_validate;
+	jas_image_addfmt(fmtid, "pnm", "pnm", "Portable Graymap/Pixmap (PNM)",
+	  &fmtops);
+	jas_image_addfmt(fmtid, "pnm", "pgm", "Portable Graymap/Pixmap (PNM)",
+	  &fmtops);
+	jas_image_addfmt(fmtid, "pnm", "ppm", "Portable Graymap/Pixmap (PNM)",
+	  &fmtops);
+	++fmtid;
+#endif
+
+#if !defined(EXCLUDE_BMP_SUPPORT)
+	fmtops.decode = bmp_decode;
+	fmtops.encode = bmp_encode;
+	fmtops.validate = bmp_validate;
+	jas_image_addfmt(fmtid, "bmp", "bmp", "Microsoft Bitmap (BMP)", &fmtops);
+	++fmtid;
+#endif
+
+#if !defined(EXCLUDE_RAS_SUPPORT)
+	fmtops.decode = ras_decode;
+	fmtops.encode = ras_encode;
+	fmtops.validate = ras_validate;
+	jas_image_addfmt(fmtid, "ras", "ras", "Sun Rasterfile (RAS)", &fmtops);
+	++fmtid;
+#endif
+
+#if !defined(EXCLUDE_JP2_SUPPORT)
+	fmtops.decode = jp2_decode;
+	fmtops.encode = jp2_encode;
+	fmtops.validate = jp2_validate;
+	jas_image_addfmt(fmtid, "jp2", "jp2",
+	  "JPEG-2000 JP2 File Format Syntax (ISO/IEC 15444-1)", &fmtops);
+	++fmtid;
+	fmtops.decode = jpc_decode;
+	fmtops.encode = jpc_encode;
+	fmtops.validate = jpc_validate;
+	jas_image_addfmt(fmtid, "jpc", "jpc",
+	  "JPEG-2000 Code Stream Syntax (ISO/IEC 15444-1)", &fmtops);
+	++fmtid;
+#endif
+
+#if !defined(EXCLUDE_JPG_SUPPORT)
+	fmtops.decode = jpg_decode;
+	fmtops.encode = jpg_encode;
+	fmtops.validate = jpg_validate;
+	jas_image_addfmt(fmtid, "jpg", "jpg", "JPEG (ISO/IEC 10918-1)", &fmtops);
+	++fmtid;
+#endif
+
+#if !defined(EXCLUDE_PGX_SUPPORT)
+	fmtops.decode = pgx_decode;
+	fmtops.encode = pgx_encode;
+	fmtops.validate = pgx_validate;
+	jas_image_addfmt(fmtid, "pgx", "pgx", "JPEG-2000 VM Format (PGX)", &fmtops);
+	++fmtid;
+#endif
+
+	return 0;
+}
diff --git a/converter/other/jpeg2000/libjasper/base/jas_malloc.c b/converter/other/jpeg2000/libjasper/base/jas_malloc.c
new file mode 100644
index 00000000..43c4d3cd
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/base/jas_malloc.c
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Memory Allocator
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdio.h>
+
+/* We need the prototype for memset. */
+#include <string.h>
+
+#include "jasper/jas_malloc.h"
+
+/******************************************************************************\
+* Code.
+\******************************************************************************/
+
+#if defined(DEBUG_MEMALLOC)
+#include "../../../local/src/memalloc.c"
+#endif
+
+#if !defined(DEBUG_MEMALLOC)
+
+void *jas_malloc(size_t size)
+{
+	return malloc(size);
+}
+
+void jas_free(void *ptr)
+{
+	free(ptr);
+}
+
+void *jas_realloc(void *ptr, size_t size)
+{
+	return realloc(ptr, size);
+}
+
+void *jas_calloc(size_t nmemb, size_t size)
+{
+	void *ptr;
+	size_t n;
+	n = nmemb * size;
+	if (!(ptr = jas_malloc(n * sizeof(char)))) {
+		return 0;
+	}
+	memset(ptr, 0, n);
+	return ptr;
+}
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/base/jas_seq.c b/converter/other/jpeg2000/libjasper/base/jas_seq.c
new file mode 100644
index 00000000..b8e3c94b
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/base/jas_seq.c
@@ -0,0 +1,475 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Sequence/Matrix Library
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdlib.h>
+#include <assert.h>
+#include <math.h>
+
+#include "jasper/jas_seq.h"
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_math.h"
+
+/******************************************************************************\
+* Constructors and destructors.
+\******************************************************************************/
+
+jas_matrix_t *jas_seq2d_create(int xstart, int ystart, int xend, int yend)
+{
+	jas_matrix_t *matrix;
+	assert(xstart <= xend && ystart <= yend);
+	if (!(matrix = jas_matrix_create(yend - ystart, xend - xstart))) {
+		return 0;
+	}
+	matrix->xstart_ = xstart;
+	matrix->ystart_ = ystart;
+	matrix->xend_ = xend;
+	matrix->yend_ = yend;
+	return matrix;
+}
+
+jas_matrix_t *jas_matrix_create(int numrows, int numcols)
+{
+	jas_matrix_t *matrix;
+	int i;
+
+	if (!(matrix = jas_malloc(sizeof(jas_matrix_t)))) {
+		return 0;
+	}
+	matrix->flags_ = 0;
+	matrix->numrows_ = numrows;
+	matrix->numcols_ = numcols;
+	matrix->rows_ = 0;
+	matrix->maxrows_ = numrows;
+	matrix->data_ = 0;
+	matrix->datasize_ = numrows * numcols;
+
+	if (matrix->maxrows_ > 0) {
+		if (!(matrix->rows_ = jas_malloc(matrix->maxrows_ *
+		  sizeof(jas_seqent_t *)))) {
+			jas_matrix_destroy(matrix);
+			return 0;
+		}
+	}
+
+	if (matrix->datasize_ > 0) {
+		if (!(matrix->data_ = jas_malloc(matrix->datasize_ *
+		  sizeof(jas_seqent_t)))) {
+			jas_matrix_destroy(matrix);
+			return 0;
+		}
+	}
+
+	for (i = 0; i < numrows; ++i) {
+		matrix->rows_[i] = &matrix->data_[i * matrix->numcols_];
+	}
+
+	for (i = 0; i < matrix->datasize_; ++i) {
+		matrix->data_[i] = 0;
+	}
+
+	matrix->xstart_ = 0;
+	matrix->ystart_ = 0;
+	matrix->xend_ = matrix->numcols_;
+	matrix->yend_ = matrix->numrows_;
+
+	return matrix;
+}
+
+void jas_matrix_destroy(jas_matrix_t *matrix)
+{
+	if (matrix->data_) {
+		assert(!(matrix->flags_ & JAS_MATRIX_REF));
+		jas_free(matrix->data_);
+		matrix->data_ = 0;
+	}
+	if (matrix->rows_) {
+		jas_free(matrix->rows_);
+		matrix->rows_ = 0;
+	}
+	jas_free(matrix);
+}
+
+jas_seq2d_t *jas_seq2d_copy(jas_seq2d_t *x)
+{
+	jas_matrix_t *y;
+	int i;
+	int j;
+	y = jas_seq2d_create(jas_seq2d_xstart(x), jas_seq2d_ystart(x), jas_seq2d_xend(x),
+	  jas_seq2d_yend(x));
+	assert(y);
+	for (i = 0; i < x->numrows_; ++i) {
+		for (j = 0; j < x->numcols_; ++j) {
+			*jas_matrix_getref(y, i, j) = jas_matrix_get(x, i, j);
+		}
+	}
+	return y;
+}
+
+jas_matrix_t *jas_matrix_copy(jas_matrix_t *x)
+{
+	jas_matrix_t *y;
+	int i;
+	int j;
+	y = jas_matrix_create(x->numrows_, x->numcols_);
+	for (i = 0; i < x->numrows_; ++i) {
+		for (j = 0; j < x->numcols_; ++j) {
+			*jas_matrix_getref(y, i, j) = jas_matrix_get(x, i, j);
+		}
+	}
+	return y;
+}
+
+/******************************************************************************\
+* Bind operations.
+\******************************************************************************/
+
+void jas_seq2d_bindsub(jas_matrix_t *s, jas_matrix_t *s1, int xstart, int ystart,
+  int xend, int yend)
+{
+	jas_matrix_bindsub(s, s1, ystart - s1->ystart_, xstart - s1->xstart_,
+	  yend - s1->ystart_ - 1, xend - s1->xstart_ - 1);
+}
+
+void jas_matrix_bindsub(jas_matrix_t *mat0, jas_matrix_t *mat1, int r0, int c0,
+  int r1, int c1)
+{
+	int i;
+
+	if (mat0->data_) {
+		if (!(mat0->flags_ & JAS_MATRIX_REF)) {
+			jas_free(mat0->data_);
+		}
+		mat0->data_ = 0;
+		mat0->datasize_ = 0;
+	}
+	if (mat0->rows_) {
+		jas_free(mat0->rows_);
+		mat0->rows_ = 0;
+	}
+	mat0->flags_ |= JAS_MATRIX_REF;
+	mat0->numrows_ = r1 - r0 + 1;
+	mat0->numcols_ = c1 - c0 + 1;
+	mat0->maxrows_ = mat0->numrows_;
+	mat0->rows_ = jas_malloc(mat0->maxrows_ * sizeof(jas_seqent_t *));
+	for (i = 0; i < mat0->numrows_; ++i) {
+		mat0->rows_[i] = mat1->rows_[r0 + i] + c0;
+	}
+
+	mat0->xstart_ = mat1->xstart_ + c0;
+	mat0->ystart_ = mat1->ystart_ + r0;
+	mat0->xend_ = mat0->xstart_ + mat0->numcols_;
+	mat0->yend_ = mat0->ystart_ + mat0->numrows_;
+}
+
+/******************************************************************************\
+* Arithmetic operations.
+\******************************************************************************/
+
+int jas_matrix_cmp(jas_matrix_t *mat0, jas_matrix_t *mat1)
+{
+	int i;
+	int j;
+
+	if (mat0->numrows_ != mat1->numrows_ || mat0->numcols_ !=
+	  mat1->numcols_) {
+		return 1;
+	}
+	for (i = 0; i < mat0->numrows_; i++) {
+		for (j = 0; j < mat0->numcols_; j++) {
+			if (jas_matrix_get(mat0, i, j) != jas_matrix_get(mat1, i, j)) {
+				return 1;
+			}
+		}
+	}
+	return 0;
+}
+
+void jas_matrix_divpow2(jas_matrix_t *matrix, int n)
+{
+	int i;
+	int j;
+	jas_seqent_t *rowstart;
+	int rowstep;
+	jas_seqent_t *data;
+
+	rowstep = jas_matrix_rowstep(matrix);
+	for (i = matrix->numrows_, rowstart = matrix->rows_[0]; i > 0; --i,
+	  rowstart += rowstep) {
+		for (j = matrix->numcols_, data = rowstart; j > 0; --j,
+		  ++data) {
+			*data = (*data >= 0) ? ((*data) >> n) :
+			  (-((-(*data)) >> n));
+		}
+	}
+}
+
+void jas_matrix_clip(jas_matrix_t *matrix, jas_seqent_t minval, jas_seqent_t maxval)
+{
+	int i;
+	int j;
+	jas_seqent_t v;
+	jas_seqent_t *rowstart;
+	jas_seqent_t *data;
+	int rowstep;
+
+	rowstep = jas_matrix_rowstep(matrix);
+	for (i = matrix->numrows_, rowstart = matrix->rows_[0]; i > 0; --i,
+	  rowstart += rowstep) {
+		data = rowstart;
+		for (j = matrix->numcols_, data = rowstart; j > 0; --j,
+		  ++data) {
+			v = *data;
+			if (v < minval) {
+				*data = minval;
+			} else if (v > maxval) {
+				*data = maxval;
+			}
+		}
+	}
+}
+
+void jas_matrix_asr(jas_matrix_t *matrix, int n)
+{
+	int i;
+	int j;
+	jas_seqent_t *rowstart;
+	int rowstep;
+	jas_seqent_t *data;
+
+	assert(n >= 0);
+	rowstep = jas_matrix_rowstep(matrix);
+	for (i = matrix->numrows_, rowstart = matrix->rows_[0]; i > 0; --i,
+	  rowstart += rowstep) {
+		for (j = matrix->numcols_, data = rowstart; j > 0; --j,
+		  ++data) {
+			*data >>= n;
+		}
+	}
+}
+
+void jas_matrix_asl(jas_matrix_t *matrix, int n)
+{
+	int i;
+	int j;
+	jas_seqent_t *rowstart;
+	int rowstep;
+	jas_seqent_t *data;
+
+	rowstep = jas_matrix_rowstep(matrix);
+	for (i = matrix->numrows_, rowstart = matrix->rows_[0]; i > 0; --i,
+	  rowstart += rowstep) {
+		for (j = matrix->numcols_, data = rowstart; j > 0; --j,
+		  ++data) {
+			*data <<= n;
+		}
+	}
+}
+
+/******************************************************************************\
+* Code.
+\******************************************************************************/
+
+int jas_matrix_resize(jas_matrix_t *matrix, int numrows, int numcols)
+{
+	int size;
+	int i;
+
+	size = numrows * numcols;
+	if (size > matrix->datasize_ || numrows > matrix->maxrows_) {
+		return -1;
+	}
+
+	matrix->numrows_ = numrows;
+	matrix->numcols_ = numcols;
+
+	for (i = 0; i < numrows; ++i) {
+		matrix->rows_[i] = &matrix->data_[numcols * i];
+	}
+
+	return 0;
+}
+
+int jas_matrix_output(jas_matrix_t *matrix, FILE *out)
+{
+	int i;
+	int j;
+	jas_seqent_t x;
+
+	fprintf(out, "%d %d\n", jas_matrix_numrows(matrix), jas_matrix_numcols(matrix));
+	for (i = 0; i < jas_matrix_numrows(matrix); ++i) {
+		for (j = 0; j < jas_matrix_numcols(matrix); ++j) {
+			x = jas_matrix_get(matrix, i, j);
+			fprintf(out, "%ld", JAS_CAST(long, x));
+			if (j < jas_matrix_numcols(matrix) - 1) {
+				fprintf(out, " ");
+			}
+		}
+		fprintf(out, "\n");
+	}
+	return 0;
+}
+
+void jas_matrix_setall(jas_matrix_t *matrix, jas_seqent_t val)
+{
+	int i;
+	int j;
+	jas_seqent_t *rowstart;
+	int rowstep;
+	jas_seqent_t *data;
+
+	rowstep = jas_matrix_rowstep(matrix);
+	for (i = matrix->numrows_, rowstart = matrix->rows_[0]; i > 0; --i,
+	  rowstart += rowstep) {
+		for (j = matrix->numcols_, data = rowstart; j > 0; --j,
+		  ++data) {
+			*data = val;
+		}
+	}
+}
+
+jas_matrix_t *jas_matrix_input(FILE *in)
+{
+	jas_matrix_t *matrix;
+	int i;
+	int j;
+	long x;
+	int numrows;
+	int numcols;
+
+	if (fscanf(in, "%d %d", &numrows, &numcols) != 2)
+		return 0;
+	if (!(matrix = jas_matrix_create(numrows, numcols)))
+		return 0;
+
+	/* Get matrix data. */
+	for (i = 0; i < jas_matrix_numrows(matrix); i++) {
+		for (j = 0; j < jas_matrix_numcols(matrix); j++) {
+			if (fscanf(in, "%ld", &x) != 1) {
+				jas_matrix_destroy(matrix);
+				return 0;
+			}
+			jas_matrix_set(matrix, i, j, JAS_CAST(jas_seqent_t, x));
+		}
+	}
+
+	return matrix;
+}
diff --git a/converter/other/jpeg2000/libjasper/base/jas_stream.c b/converter/other/jpeg2000/libjasper/base/jas_stream.c
new file mode 100644
index 00000000..f450aabf
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/base/jas_stream.c
@@ -0,0 +1,1204 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+#define _SVID_SOURCE
+    /* Make sure P_tmpdir is defined in GNU libc 2.0.7 (_XOPEN_SOURCE 500
+       does it in other libc's).  pm_config.h defines TMPDIR as P_tmpdir
+       in some environments.
+    */
+#define _XOPEN_SOURCE 500    /* Make sure P_tmpdir is defined */
+
+#include <assert.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <ctype.h>
+#if defined(HAVE_UNISTD_H)
+#include <unistd.h>
+#endif
+#if defined(WIN32) || defined(HAVE_IO_H)
+#include <io.h>
+#endif
+
+#include "jasper/jas_types.h"
+#include "jasper/jas_stream.h"
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_math.h"
+
+/****************************************************************************\
+* Local function prototypes.
+\****************************************************************************/
+
+static int jas_strtoopenmode(const char *s);
+static void jas_stream_destroy(jas_stream_t *stream);
+static jas_stream_t *jas_stream_create(void);
+static void jas_stream_initbuf(jas_stream_t *stream, int bufmode, char *buf,
+  int bufsize);
+
+static int mem_read(jas_stream_obj_t *obj, char *buf, int cnt);
+static int mem_write(jas_stream_obj_t *obj, char *buf, int cnt);
+static long mem_seek(jas_stream_obj_t *obj, long offset, int origin);
+static int mem_close(jas_stream_obj_t *obj);
+
+static int sfile_read(jas_stream_obj_t *obj, char *buf, int cnt);
+static int sfile_write(jas_stream_obj_t *obj, char *buf, int cnt);
+static long sfile_seek(jas_stream_obj_t *obj, long offset, int origin);
+static int sfile_close(jas_stream_obj_t *obj);
+
+static int file_read(jas_stream_obj_t *obj, char *buf, int cnt);
+static int file_write(jas_stream_obj_t *obj, char *buf, int cnt);
+static long file_seek(jas_stream_obj_t *obj, long offset, int origin);
+static int file_close(jas_stream_obj_t *obj);
+
+/******************************************************************************\
+* Local data.
+\******************************************************************************/
+
+static jas_stream_ops_t jas_stream_fileops = {
+	file_read,
+	file_write,
+	file_seek,
+	file_close
+};
+
+static jas_stream_ops_t jas_stream_sfileops = {
+	sfile_read,
+	sfile_write,
+	sfile_seek,
+	sfile_close
+};
+
+static jas_stream_ops_t jas_stream_memops = {
+	mem_read,
+	mem_write,
+	mem_seek,
+	mem_close
+};
+
+/******************************************************************************\
+* Code for opening and closing streams.
+\******************************************************************************/
+
+static jas_stream_t *jas_stream_create()
+{
+	jas_stream_t *stream;
+
+	if (!(stream = jas_malloc(sizeof(jas_stream_t)))) {
+		return 0;
+	}
+	stream->openmode_ = 0;
+	stream->bufmode_ = 0;
+	stream->flags_ = 0;
+	stream->bufbase_ = 0;
+	stream->bufstart_ = 0;
+	stream->bufsize_ = 0;
+	stream->ptr_ = 0;
+	stream->cnt_ = 0;
+	stream->ops_ = 0;
+	stream->obj_ = 0;
+	stream->rwcnt_ = 0;
+	stream->rwlimit_ = -1;
+
+	return stream;
+}
+
+jas_stream_t *jas_stream_memopen(char *buf, int bufsize)
+{
+	jas_stream_t *stream;
+	jas_stream_memobj_t *obj;
+
+	if (!(stream = jas_stream_create())) {
+		return 0;
+	}
+
+	/* A stream associated with a memory buffer is always opened
+	for both reading and writing in binary mode. */
+	stream->openmode_ = JAS_STREAM_READ | JAS_STREAM_WRITE | JAS_STREAM_BINARY;
+
+	/* Since the stream data is already resident in memory, buffering
+	is not necessary. */
+	/* But... It still may be faster to use buffering anyways. */
+	jas_stream_initbuf(stream, JAS_STREAM_FULLBUF, 0, 0);
+
+	/* Select the operations for a memory stream. */
+	stream->ops_ = &jas_stream_memops;
+
+	/* Allocate memory for the underlying memory stream object. */
+	if (!(obj = jas_malloc(sizeof(jas_stream_memobj_t)))) {
+		jas_stream_destroy(stream);
+		return 0;
+	}
+	stream->obj_ = (void *) obj;
+
+	/* Initialize a few important members of the memory stream object. */
+	obj->myalloc_ = 0;
+	obj->buf_ = 0;
+
+	/* If the buffer size specified is nonpositive, then the buffer
+	is allocated internally and automatically grown as needed. */
+	if (bufsize <= 0) {
+		obj->bufsize_ = 1024;
+		obj->growable_ = 1;
+	} else {
+		obj->bufsize_ = bufsize;
+		obj->growable_ = 0;
+	}
+	if (buf) {
+		obj->buf_ = (unsigned char *) buf;
+	} else {
+		obj->buf_ = jas_malloc(obj->bufsize_ * sizeof(char));
+		obj->myalloc_ = 1;
+	}
+	if (!obj->buf_) {
+		jas_stream_close(stream);
+		return 0;
+	}
+
+	if (bufsize > 0 && buf) {
+		/* If a buffer was supplied by the caller and its length is positive,
+		  make the associated buffer data appear in the stream initially. */
+		obj->len_ = bufsize;
+	} else {
+		/* The stream is initially empty. */
+		obj->len_ = 0;
+	}
+	obj->pos_ = 0;
+	
+	return stream;
+}
+
+jas_stream_t *jas_stream_fopen(const char *filename, const char *mode)
+{
+	jas_stream_t *stream;
+	int *obj;
+	int openflags;
+
+	/* Allocate a stream object. */
+	if (!(stream = jas_stream_create())) {
+		return 0;
+	}
+
+	/* Parse the mode string. */
+	stream->openmode_ = jas_strtoopenmode(mode);
+
+	/* Determine the correct flags to use for opening the file. */
+	if ((stream->openmode_ & JAS_STREAM_READ) &&
+	  (stream->openmode_ & JAS_STREAM_WRITE)) {
+		openflags = O_RDWR;
+	} else if (stream->openmode_ & JAS_STREAM_READ) {
+		openflags = O_RDONLY;
+	} else if (stream->openmode_ & JAS_STREAM_WRITE) {
+		openflags = O_WRONLY;
+	} else {
+		openflags = 0;
+	}
+	if (stream->openmode_ & JAS_STREAM_APPEND) {
+		openflags |= O_APPEND;
+	}
+	if (stream->openmode_ & JAS_STREAM_BINARY) {
+		openflags |= O_BINARY;
+	}
+	if (stream->openmode_ & JAS_STREAM_CREATE) {
+		openflags |= O_CREAT | O_TRUNC;
+	}
+
+	/* Allocate space for the underlying file stream object. */
+	if (!(obj = jas_malloc(sizeof(int)))) {
+		jas_stream_destroy(stream);
+		return 0;
+	}
+	stream->obj_ = (void *) obj;
+
+	/* Select the operations for a file stream object. */
+	stream->ops_ = &jas_stream_fileops;
+
+	/* Open the underlying file. */
+	if ((*obj = open(filename, openflags, JAS_STREAM_PERMS)) < 0) {
+		jas_stream_destroy(stream);
+		return 0;
+	}
+
+	/* By default, use full buffering for this type of stream. */
+	jas_stream_initbuf(stream, JAS_STREAM_FULLBUF, 0, 0);
+
+	return stream;
+}
+
+jas_stream_t *jas_stream_freopen(const char *path, const char *mode, FILE *fp)
+{
+	jas_stream_t *stream;
+	int openflags;
+
+	/* Allocate a stream object. */
+	if (!(stream = jas_stream_create())) {
+		return 0;
+	}
+
+	/* Parse the mode string. */
+	stream->openmode_ = jas_strtoopenmode(mode);
+
+	/* Determine the correct flags to use for opening the file. */
+	if ((stream->openmode_ & JAS_STREAM_READ) &&
+	  (stream->openmode_ & JAS_STREAM_WRITE)) {
+		openflags = O_RDWR;
+	} else if (stream->openmode_ & JAS_STREAM_READ) {
+		openflags = O_RDONLY;
+	} else if (stream->openmode_ & JAS_STREAM_WRITE) {
+		openflags = O_WRONLY;
+	} else {
+		openflags = 0;
+	}
+	if (stream->openmode_ & JAS_STREAM_APPEND) {
+		openflags |= O_APPEND;
+	}
+	if (stream->openmode_ & JAS_STREAM_BINARY) {
+		openflags |= O_BINARY;
+	}
+	if (stream->openmode_ & JAS_STREAM_CREATE) {
+		openflags |= O_CREAT | O_TRUNC;
+	}
+
+	stream->obj_ = JAS_CAST(void *, fp);
+
+	/* Select the operations for a file stream object. */
+	stream->ops_ = &jas_stream_sfileops;
+
+	/* By default, use full buffering for this type of stream. */
+	jas_stream_initbuf(stream, JAS_STREAM_FULLBUF, 0, 0);
+
+	return stream;
+}
+
+
+static int
+tmpfilex(void) {
+/*----------------------------------------------------------------------------
+   This is a copy of pm_tmpfile() without the libnetpbm dependencies
+   and returning a file descriptor instead of stream.
+-----------------------------------------------------------------------------*/
+    const char libname[] = "jasper";
+
+    int fd;
+    char buf[FILENAME_MAX];
+    const char * tbuf;
+    unsigned int fnamelen;
+
+    fnamelen = strlen(libname) + 10; /* "/" + "_XXXXXX\0" */
+
+    tbuf = getenv("TMPDIR");
+
+    if ((tbuf != NULL) && (strlen(tbuf) > FILENAME_MAX - fnamelen))
+        /* length of TMPDIR value too big for buf */
+        tbuf = NULL;
+
+    buf[FILENAME_MAX - fnamelen -1] = 0;
+
+    if ((tbuf == NULL) || (strlen(tbuf) == 0))
+        /* environment variable not suitable to construct file name.
+           Use default.
+        */
+        strncpy(buf, TMPDIR, sizeof(buf) - fnamelen);
+    else
+        strncpy(buf, tbuf, sizeof(buf) - fnamelen);
+
+    if (buf[strlen(buf) - 1] != '/')
+        strcat (buf, "/");
+    
+    strcat(buf, libname);
+    strcat(buf, "_XXXXXX");
+
+    fd = mkstemp(buf);
+
+    if (fd >= 0)
+        unlink(buf);
+
+    return fd;
+}
+
+jas_stream_t *jas_stream_tmpfile()
+{
+	jas_stream_t *stream;
+	int *obj;
+
+	if (!(stream = jas_stream_create())) {
+		return 0;
+	}
+
+	/* A temporary file stream is always opened for both reading and
+	writing in binary mode. */
+	stream->openmode_ = JAS_STREAM_READ | JAS_STREAM_WRITE | JAS_STREAM_BINARY;
+
+	/* Allocate memory for the underlying temporary file object. */
+	if (!(obj = jas_malloc(sizeof(int)))) {
+		jas_stream_destroy(stream);
+		return 0;
+	}
+	stream->obj_ = obj;
+    
+    /* This is a Netpbm enhancement.  Original Jasper library uses
+       tmpnam(), which is unsafe.
+    */
+    if ((*obj = tmpfilex()) < 0) {
+		jas_stream_destroy(stream);
+		return 0;
+    }
+	/* Use full buffering. */
+	jas_stream_initbuf(stream, JAS_STREAM_FULLBUF, 0, 0);
+
+	stream->ops_ = &jas_stream_fileops;
+
+	return stream;
+}
+
+jas_stream_t *jas_stream_fdopen(int fd, const char *mode)
+{
+	jas_stream_t *stream;
+	int *obj;
+
+	/* Allocate a stream object. */
+	if (!(stream = jas_stream_create())) {
+		return 0;
+	}
+
+	/* Parse the mode string. */
+	stream->openmode_ = jas_strtoopenmode(mode);
+
+#if defined(WIN32)
+	/* Argh!!!  Someone ought to banish text mode (i.e., O_TEXT) to the
+	  greatest depths of purgatory! */
+	/* Ensure that the file descriptor is in binary mode, if the caller
+	  has specified the binary mode flag.  Arguably, the caller ought to
+	  take care of this, but text mode is a ugly wart anyways, so we save
+	  the caller some grief by handling this within the stream library. */
+	/* This ugliness is mainly for the benefit of those who run the
+	  JasPer software under Windows from shells that insist on opening
+	  files in text mode.  For example, in the Cygwin environment,
+	  shells often open files in text mode when I/O redirection is
+	  used.  Grr... */
+	if (stream->openmode_ & JAS_STREAM_BINARY) {
+		setmode(fd, O_BINARY);
+	}
+#endif
+
+	/* Allocate space for the underlying file stream object. */
+	if (!(obj = jas_malloc(sizeof(int)))) {
+		jas_stream_destroy(stream);
+		return 0;
+	}
+	stream->obj_ = (void *) obj;
+	*obj = fd;
+
+	/* By default, use full buffering for this type of stream. */
+	jas_stream_initbuf(stream, JAS_STREAM_FULLBUF, 0, 0);
+
+	/* Select the operations for a file stream object. */
+	stream->ops_ = &jas_stream_fileops;
+
+/* Do not close the underlying file descriptor when the stream is closed. */
+	stream->openmode_ |= JAS_STREAM_NOCLOSE;
+
+	return stream;
+}
+
+static void jas_stream_destroy(jas_stream_t *stream)
+{
+	/* If the memory for the buffer was allocated with malloc, free
+	this memory. */
+	if ((stream->bufmode_ & JAS_STREAM_FREEBUF) && stream->bufbase_) {
+		jas_free(stream->bufbase_);
+		stream->bufbase_ = 0;
+	}
+	jas_free(stream);
+}
+
+int jas_stream_close(jas_stream_t *stream)
+{
+	/* Flush buffer if necessary. */
+	jas_stream_flush(stream);
+
+	/* Close the underlying stream object. */
+	if (!(stream->openmode_ & JAS_STREAM_NOCLOSE)) {
+		(*stream->ops_->close_)(stream->obj_);
+	}
+
+	jas_stream_destroy(stream);
+
+	return 0;
+}
+
+/******************************************************************************\
+* Code for reading and writing streams.
+\******************************************************************************/
+
+int jas_stream_getc_func(jas_stream_t *stream)
+{
+	assert(stream->ptr_ - stream->bufbase_ <= stream->bufsize_ +
+	  JAS_STREAM_MAXPUTBACK);
+	return jas_stream_getc_macro(stream);
+}
+
+int jas_stream_putc_func(jas_stream_t *stream, int c)
+{
+	assert(stream->ptr_ - stream->bufstart_ <= stream->bufsize_);
+	return jas_stream_putc_macro(stream, c);
+}
+
+int jas_stream_ungetc(jas_stream_t *stream, int c)
+{
+	if (!stream->ptr_ || stream->ptr_ == stream->bufbase_) {
+		return -1;
+	}
+
+	/* Reset the EOF indicator (since we now have at least one character
+	  to read). */
+	stream->flags_ &= ~JAS_STREAM_EOF;
+
+	--stream->rwcnt_;
+	--stream->ptr_;
+	++stream->cnt_;
+	*stream->ptr_ = c;
+	return 0;
+}
+
+int jas_stream_read(jas_stream_t *stream, void *buf, int cnt)
+{
+	int n;
+	int c;
+	char *bufptr;
+
+	bufptr = buf;
+
+	n = 0;
+	while (n < cnt) {
+		if ((c = jas_stream_getc(stream)) == EOF) {
+			return n;
+		}
+		*bufptr++ = c;
+		++n;
+	}
+
+	return n;
+}
+
+int jas_stream_write(jas_stream_t *stream, const void *buf, int cnt)
+{
+	int n;
+	const char *bufptr;
+
+	bufptr = buf;
+
+	n = 0;
+	while (n < cnt) {
+		if (jas_stream_putc(stream, *bufptr) == EOF) {
+			return n;
+		}
+		++bufptr;
+		++n;
+	}
+
+	return n;
+}
+
+/* Note: This function uses a fixed size buffer.  Therefore, it cannot
+  handle invocations that will produce more output than can be held
+  by the buffer. */
+int jas_stream_printf(jas_stream_t *stream, const char *fmt, ...)
+{
+	va_list ap;
+	char buf[4096];
+	int ret;
+
+	va_start(ap, fmt);
+	ret = vsprintf(buf, fmt, ap);
+	jas_stream_puts(stream, buf);
+	va_end(ap);
+	return ret;
+}
+
+int jas_stream_puts(jas_stream_t *stream, const char *s)
+{
+	while (*s != '\0') {
+		if (jas_stream_putc_macro(stream, *s) == EOF) {
+			return -1;
+		}
+		++s;
+	}
+	return 0;
+}
+
+char *jas_stream_gets(jas_stream_t *stream, char *buf, int bufsize)
+{
+	int c;
+	char *bufptr;
+	assert(bufsize > 0);
+
+	bufptr = buf;
+	while (bufsize > 1) {
+		if ((c = jas_stream_getc(stream)) == EOF) {
+			break;
+		}
+		*bufptr++ = c;
+		--bufsize;
+		if (c == '\n') {
+			break;
+		}
+	}
+	*bufptr = '\0';
+	return buf;
+}
+
+int jas_stream_gobble(jas_stream_t *stream, int n)
+{
+	int m;
+	m = n;
+	for (m = n; m > 0; --m) {
+		if (jas_stream_getc(stream) == EOF) {
+			return n - m;
+		}
+	}
+	return n;
+}
+
+/******************************************************************************\
+* Code for getting and setting the stream position.
+\******************************************************************************/
+
+int jas_stream_isseekable(jas_stream_t *stream)
+{
+	if (stream->ops_ == &jas_stream_memops) {
+		return 1;
+	} else if (stream->ops_ == &jas_stream_fileops) {
+		if ((*stream->ops_->seek_)(stream->obj_, 0, SEEK_CUR) < 0) {
+			return 0;
+		}
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+int jas_stream_rewind(jas_stream_t *stream)
+{
+	return jas_stream_seek(stream, 0, SEEK_SET);
+}
+
+long jas_stream_seek(jas_stream_t *stream, long offset, int origin)
+{
+	long newpos;
+
+	/* The buffer cannot be in use for both reading and writing. */
+	assert(!((stream->bufmode_ & JAS_STREAM_RDBUF) && (stream->bufmode_ &
+	  JAS_STREAM_WRBUF)));
+
+	/* Reset the EOF indicator (since we may not be at the EOF anymore). */
+	stream->flags_ &= ~JAS_STREAM_EOF;
+
+	if (stream->bufmode_ & JAS_STREAM_RDBUF) {
+		if (origin == SEEK_CUR) {
+			offset -= stream->cnt_;
+		}
+	} else if (stream->bufmode_ & JAS_STREAM_WRBUF) {
+		if (jas_stream_flush(stream)) {
+			return -1;
+		}
+	}
+	stream->cnt_ = 0;
+	stream->ptr_ = stream->bufstart_;
+	stream->bufmode_ &= ~(JAS_STREAM_RDBUF | JAS_STREAM_WRBUF);
+
+	if ((newpos = (*stream->ops_->seek_)(stream->obj_, offset, origin))
+	  < 0) {
+		return -1;
+	}
+
+	return newpos;
+}
+
+long jas_stream_tell(jas_stream_t *stream)
+{
+	int adjust;
+	int offset;
+
+	if (stream->bufmode_ & JAS_STREAM_RDBUF) {
+		adjust = -stream->cnt_;
+	} else if (stream->bufmode_ & JAS_STREAM_WRBUF) {
+		adjust = stream->ptr_ - stream->bufstart_;
+	} else {
+		adjust = 0;
+	}
+
+	if ((offset = (*stream->ops_->seek_)(stream->obj_, 0, SEEK_CUR)) < 0) {
+		return -1;
+	}
+
+	return offset + adjust;
+}
+
+/******************************************************************************\
+* Buffer initialization code.
+\******************************************************************************/
+
+static void jas_stream_initbuf(jas_stream_t *stream, int bufmode, char *buf,
+  int bufsize)
+{
+	/* If this function is being called, the buffer should not have been
+	  initialized yet. */
+	assert(!stream->bufbase_);
+
+	if (bufmode != JAS_STREAM_UNBUF) {
+		/* The full- or line-buffered mode is being employed. */
+		if (!buf) {
+			/* The caller has not specified a buffer to employ, so allocate
+			  one. */
+			if ((stream->bufbase_ = jas_malloc(JAS_STREAM_BUFSIZE +
+			  JAS_STREAM_MAXPUTBACK))) {
+				stream->bufmode_ |= JAS_STREAM_FREEBUF;
+				stream->bufsize_ = JAS_STREAM_BUFSIZE;
+			} else {
+				/* The buffer allocation has failed.  Resort to unbuffered
+				  operation. */
+				stream->bufbase_ = stream->tinybuf_;
+				stream->bufsize_ = 1;
+			}
+		} else {
+			/* The caller has specified a buffer to employ. */
+			/* The buffer must be large enough to accommodate maximum
+			  putback. */
+			assert(bufsize > JAS_STREAM_MAXPUTBACK);
+			stream->bufbase_ = JAS_CAST(unsigned char *, buf);
+			stream->bufsize_ = bufsize - JAS_STREAM_MAXPUTBACK;
+		}
+	} else {
+		/* The unbuffered mode is being employed. */
+		/* A buffer should not have been supplied by the caller. */
+		assert(!buf);
+		/* Use a trivial one-character buffer. */
+		stream->bufbase_ = stream->tinybuf_;
+		stream->bufsize_ = 1;
+	}
+	stream->bufstart_ = &stream->bufbase_[JAS_STREAM_MAXPUTBACK];
+	stream->ptr_ = stream->bufstart_;
+	stream->cnt_ = 0;
+	stream->bufmode_ |= bufmode & JAS_STREAM_BUFMODEMASK;
+}
+
+/******************************************************************************\
+* Buffer filling and flushing code.
+\******************************************************************************/
+
+int jas_stream_flush(jas_stream_t *stream)
+{
+	if (stream->bufmode_ & JAS_STREAM_RDBUF) {
+		return 0;
+	}
+	return jas_stream_flushbuf(stream, EOF);
+}
+
+int jas_stream_fillbuf(jas_stream_t *stream, int getflag)
+{
+	int c;
+
+	/* The stream must not be in an error or EOF state. */
+	if ((stream->flags_ & (JAS_STREAM_ERRMASK)) != 0) {
+		return EOF;
+	}
+
+	/* The stream must be open for reading. */
+	if ((stream->openmode_ & JAS_STREAM_READ) == 0) {
+		return EOF;
+	}
+
+	/* Make a half-hearted attempt to confirm that the buffer is not
+	currently being used for writing.  This check is not intended
+	to be foolproof! */
+	assert((stream->bufmode_ & JAS_STREAM_WRBUF) == 0);
+
+	assert(stream->ptr_ - stream->bufstart_ <= stream->bufsize_);
+
+	/* Mark the buffer as being used for reading. */
+	stream->bufmode_ |= JAS_STREAM_RDBUF;
+
+	/* Read new data into the buffer. */
+	stream->ptr_ = stream->bufstart_;
+	if ((stream->cnt_ = (*stream->ops_->read_)(stream->obj_,
+	  (char *) stream->bufstart_, stream->bufsize_)) <= 0) {
+		if (stream->cnt_ < 0) {
+			stream->flags_ |= JAS_STREAM_ERR;
+		} else {
+			stream->flags_ |= JAS_STREAM_EOF;
+		}
+		stream->cnt_ = 0;
+		return EOF;
+	}
+
+	assert(stream->cnt_ > 0);
+	/* Get or peek at the first character in the buffer. */
+	c = (getflag) ? jas_stream_getc2(stream) : (*stream->ptr_);
+
+	return c;
+}
+
+int jas_stream_flushbuf(jas_stream_t *stream, int c)
+{
+	int len;
+	int n;
+
+	/* The stream should not be in an error or EOF state. */
+	if ((stream->flags_ & (JAS_STREAM_ERRMASK)) != 0) {
+		return EOF;
+	}
+
+	/* The stream must be open for writing. */
+	if ((stream->openmode_ & (JAS_STREAM_WRITE | JAS_STREAM_APPEND)) == 0) {
+		return EOF;
+	}
+
+	/* The buffer should not currently be in use for reading. */
+	assert(!(stream->bufmode_ & JAS_STREAM_RDBUF));
+
+	/* Note: Do not use the quantity stream->cnt to determine the number
+	of characters in the buffer!  Depending on how this function was
+	called, the stream->cnt value may be "off-by-one". */
+	len = stream->ptr_ - stream->bufstart_;
+	if (len > 0) {
+		n = (*stream->ops_->write_)(stream->obj_, (char *)
+		  stream->bufstart_, len);
+		if (n != len) {
+			stream->flags_ |= JAS_STREAM_ERR;
+			return EOF;
+		}
+	}
+	stream->cnt_ = stream->bufsize_;
+	stream->ptr_ = stream->bufstart_;
+
+	stream->bufmode_ |= JAS_STREAM_WRBUF;
+
+	if (c != EOF) {
+		assert(stream->cnt_ > 0);
+		jas_stream_putc2(stream, c);
+	}
+
+	return 0;
+}
+
+/******************************************************************************\
+* Miscellaneous code.
+\******************************************************************************/
+
+static int jas_strtoopenmode(const char *s)
+{
+	int openmode = 0;
+	while (*s != '\0') {
+		switch (*s) {
+		case 'r':
+			openmode |= JAS_STREAM_READ;
+			break;
+		case 'w':
+			openmode |= JAS_STREAM_WRITE | JAS_STREAM_CREATE;
+			break;
+		case 'b':
+			openmode |= JAS_STREAM_BINARY;
+			break;
+		case 'a':
+			openmode |= JAS_STREAM_APPEND;
+			break;
+		case '+':
+			openmode |= JAS_STREAM_READ | JAS_STREAM_WRITE;
+			break;
+		default:
+			break;
+		}
+		++s;
+	}
+	return openmode;
+}
+
+int jas_stream_copy(jas_stream_t *out, jas_stream_t *in, int n)
+{
+	int all;
+	int c;
+	int m;
+
+	all = (n < 0) ? 1 : 0;
+
+	m = n;
+	while (all || m > 0) {
+		if ((c = jas_stream_getc_macro(in)) == EOF) {
+			/* The next character of input could not be read. */
+			/* Return with an error if an I/O error occured
+			  (not including EOF) or if an explicit copy count
+			  was specified. */
+			return (!all || jas_stream_error(in)) ? (-1) : 0;
+		}
+		if (jas_stream_putc_macro(out, c) == EOF) {
+			return -1;
+		}
+		--m;
+	}
+	return 0;
+}
+
+long jas_stream_setrwcount(jas_stream_t *stream, long rwcnt)
+{
+	int old;
+
+	old = stream->rwcnt_;
+	stream->rwcnt_ = rwcnt;
+	return old;
+}
+
+int jas_stream_display(jas_stream_t *stream, FILE *fp, int n)
+{
+	unsigned char buf[16];
+	int i;
+	int j;
+	int m;
+	int c;
+	int display;
+	int cnt;
+
+	cnt = n - (n % 16);
+	display = 1;
+
+	for (i = 0; i < n; i += 16) {
+		if (n > 16 && i > 0) {
+			display = (i >= cnt) ? 1 : 0;
+		}
+		if (display) {
+			fprintf(fp, "%08x:", i);
+		}
+		m = JAS_MIN(n - i, 16);
+		for (j = 0; j < m; ++j) {
+			if ((c = jas_stream_getc(stream)) == EOF) {
+				abort();
+				return -1;
+			}
+			buf[j] = c;
+		}
+		if (display) {
+			for (j = 0; j < m; ++j) {
+				fprintf(fp, " %02x", buf[j]);
+			}
+			fputc(' ', fp);
+			for (; j < 16; ++j) {
+				fprintf(fp, "   ");
+			}
+			for (j = 0; j < m; ++j) {
+				if (isprint(buf[j])) {
+					fputc(buf[j], fp);
+				} else {
+					fputc(' ', fp);
+				}
+			}
+			fprintf(fp, "\n");
+		}
+
+
+	}
+	return 0;
+}
+
+long jas_stream_length(jas_stream_t *stream)
+{
+	long oldpos;
+	long pos;
+	if ((oldpos = jas_stream_tell(stream)) < 0) {
+		return -1;
+	}
+	if (jas_stream_seek(stream, 0, SEEK_END) < 0) {
+		return -1;
+	}
+	if ((pos = jas_stream_tell(stream)) < 0) {
+		return -1;
+	}
+	if (jas_stream_seek(stream, oldpos, SEEK_SET) < 0) {
+		return -1;
+	}
+	return pos;
+}
+
+/******************************************************************************\
+* Memory stream object.
+\******************************************************************************/
+
+static int mem_read(jas_stream_obj_t *obj, char *buf, int cnt)
+{
+	int n;
+	jas_stream_memobj_t *m = (jas_stream_memobj_t *)obj;
+	n = m->len_ - m->pos_;
+	cnt = JAS_MIN(n, cnt);
+	memcpy(buf, &m->buf_[m->pos_], cnt);
+	m->pos_ += cnt;
+	return cnt;
+}
+
+static int mem_resize(jas_stream_memobj_t *m, int bufsize)
+{
+	unsigned char *buf;
+
+	assert(m->buf_);
+	if (!(buf = jas_realloc(m->buf_, bufsize * sizeof(unsigned char)))) {
+		return -1;
+	}
+	m->buf_ = buf;
+	m->bufsize_ = bufsize;
+	return 0;
+}
+
+static int mem_write(jas_stream_obj_t *obj, char *buf, int cnt)
+{
+	int n;
+	int ret;
+	jas_stream_memobj_t *m = (jas_stream_memobj_t *)obj;
+	long newbufsize;
+	long newpos;
+
+	newpos = m->pos_ + cnt;
+	if (newpos > m->bufsize_ && m->growable_) {
+		newbufsize = m->bufsize_;
+		while (newbufsize < newpos) {
+			newbufsize <<= 1;
+			assert(newbufsize >= 0);
+		}
+		if (mem_resize(m, newbufsize)) {
+			return -1;
+		}
+	}
+	if (m->pos_ > m->len_) {
+		/* The current position is beyond the end of the file, so
+		  pad the file to the current position with zeros. */
+		n = JAS_MIN(m->pos_, m->bufsize_) - m->len_;
+		if (n > 0) {
+			memset(&m->buf_[m->len_], 0, n);
+			m->len_ += n;
+		}
+		if (m->pos_ != m->len_) {
+			/* The buffer is not big enough. */
+			return 0;
+		}
+	}
+	n = m->bufsize_ - m->pos_;
+	ret = JAS_MIN(n, cnt);
+	if (ret > 0) {
+		memcpy(&m->buf_[m->pos_], buf, ret);
+		m->pos_ += ret;
+	}
+	if (m->pos_ > m->len_) {
+		m->len_ = m->pos_;
+	}
+assert(ret == cnt);
+	return ret;
+}
+
+static long mem_seek(jas_stream_obj_t *obj, long offset, int origin)
+{
+	jas_stream_memobj_t *m = (jas_stream_memobj_t *)obj;
+	long newpos;
+
+	switch (origin) {
+	case SEEK_SET:
+		newpos = offset;
+		break;
+	case SEEK_END:
+		newpos = m->len_ - offset;
+		break;
+	case SEEK_CUR:
+		newpos = m->pos_ + offset;
+		break;
+	default:
+		abort();
+		break;
+	}
+	if (newpos < 0) {
+		return -1;
+	}
+	m->pos_ = newpos;
+
+	return m->pos_;
+}
+
+static int mem_close(jas_stream_obj_t *obj)
+{
+	jas_stream_memobj_t *m = (jas_stream_memobj_t *)obj;
+	if (m->myalloc_ && m->buf_) {
+		jas_free(m->buf_);
+		m->buf_ = 0;
+	}
+	jas_free(obj);
+	return 0;
+}
+
+/******************************************************************************\
+* File stream object.
+\******************************************************************************/
+
+static int file_read(jas_stream_obj_t *obj, char *buf, int cnt)
+{
+	int fd;
+	fd = *((int *)obj);
+	return read(fd, buf, cnt);
+}
+
+static int file_write(jas_stream_obj_t *obj, char *buf, int cnt)
+{
+	int fd;
+	fd = *((int *)obj);
+	return write(fd, buf, cnt);
+}
+
+static long file_seek(jas_stream_obj_t *obj, long offset, int origin)
+{
+	int fd;
+	fd = *((int *)obj);
+	return lseek(fd, offset, origin);
+}
+
+static int file_close(jas_stream_obj_t *obj)
+{
+	int fd;
+	fd = *((int *)obj);
+	jas_free(obj);
+	return close(fd);
+}
+
+/******************************************************************************\
+* Stdio file stream object.
+\******************************************************************************/
+
+static int sfile_read(jas_stream_obj_t *obj, char *buf, int cnt)
+{
+	FILE *fp;
+	fp = JAS_CAST(FILE *, obj);
+	return fread(buf, 1, cnt, fp);
+}
+
+static int sfile_write(jas_stream_obj_t *obj, char *buf, int cnt)
+{
+	FILE *fp;
+	fp = JAS_CAST(FILE *, obj);
+	return fwrite(buf, 1, cnt, fp);
+}
+
+static long sfile_seek(jas_stream_obj_t *obj, long offset, int origin)
+{
+	FILE *fp;
+	fp = JAS_CAST(FILE *, obj);
+	return fseek(fp, offset, origin);
+}
+
+static int sfile_close(jas_stream_obj_t *obj)
+{
+	FILE *fp;
+	fp = JAS_CAST(FILE *, obj);
+	return fclose(fp);
+}
diff --git a/converter/other/jpeg2000/libjasper/base/jas_string.c b/converter/other/jpeg2000/libjasper/base/jas_string.c
new file mode 100644
index 00000000..945731a1
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/base/jas_string.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * String Library
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes
+\******************************************************************************/
+
+#include <string.h>
+
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_string.h"
+
+/******************************************************************************\
+* Miscellaneous Functions
+\******************************************************************************/
+
+/* This function is equivalent to the popular but non-standard (and
+  not-always-available) strdup function. */
+
+char *jas_strdup(const char *s)
+{
+	int n;
+	char *p;
+	n = strlen(s) + 1;
+	if (!(p = jas_malloc(n * sizeof(char)))) {
+		return 0;
+	}
+	strcpy(p, s);
+	return p;
+}
diff --git a/converter/other/jpeg2000/libjasper/base/jas_tvp.c b/converter/other/jpeg2000/libjasper/base/jas_tvp.c
new file mode 100644
index 00000000..aab12703
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/base/jas_tvp.c
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Tag-Value Parser Library
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <assert.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_string.h"
+#include "jasper/jas_tvp.h"
+
+/******************************************************************************\
+* Macros.
+\******************************************************************************/
+
+/* Is the specified character valid for a tag name? */
+#define	JAS_TVP_ISTAG(x) \
+	(isalpha(x) || (x) == '_' || isdigit(x))
+
+/******************************************************************************\
+* Code for creating and destroying a tag-value parser.
+\******************************************************************************/
+
+jas_tvparser_t *jas_tvparser_create(const char *s)
+{
+	jas_tvparser_t *tvp;
+	if (!(tvp = jas_malloc(sizeof(jas_tvparser_t)))) {
+		return 0;
+	}
+	if (!(tvp->buf = jas_strdup(s))) {
+		jas_tvparser_destroy(tvp);
+		return 0;
+	}
+	tvp->pos = tvp->buf;
+	tvp->tag = 0;
+	tvp->val = 0;
+	return tvp;
+}
+
+void jas_tvparser_destroy(jas_tvparser_t *tvp)
+{
+	if (tvp->buf) {
+		jas_free(tvp->buf);
+	}
+	jas_free(tvp);
+}
+
+/******************************************************************************\
+* Main parsing code.
+\******************************************************************************/
+
+/* Get the next tag-value pair. */
+int jas_tvparser_next(jas_tvparser_t *tvp)
+{
+	char *p;
+	char *tag;
+	const char *val;
+
+	/* Skip any leading whitespace. */
+	p = tvp->pos;
+	while (*p != '\0' && isspace(*p)) {
+		++p;
+	}
+
+	/* Has the end of the input data been reached? */
+	if (*p == '\0') {
+		/* No more tags are present. */
+		tvp->pos = p;
+		return 1;
+	}
+
+	/* Does the tag name begin with a valid character? */
+	if (!JAS_TVP_ISTAG(*p)) {
+		return -1;
+	}
+
+	/* Remember where the tag name begins. */
+	tag = p;
+
+	/* Find the end of the tag name. */
+	while (*p != '\0' && JAS_TVP_ISTAG(*p)) {
+		++p;
+	}
+
+	/* Has the end of the input data been reached? */
+	if (*p == '\0') {
+		/* The value field is empty. */
+		tvp->tag = tag;
+		tvp->val = "";
+		tvp->pos = p;
+		return 0;
+	}
+
+	/* Is a value field not present? */
+	if (*p != '=') {
+		if (*p != '\0' && !isspace(*p)) {
+			return -1;
+		}
+		*p++ = '\0';
+		tvp->tag = tag;
+		tvp->val = "";
+		tvp->pos = p;
+		return 0;
+	}
+
+	*p++ = '\0';
+
+	val = p;
+	while (*p != '\0' && !isspace(*p)) {
+		++p;
+	}
+
+	if (*p != '\0') {
+		*p++ = '\0';
+	}
+
+	tvp->pos = p;
+	tvp->tag = tag;
+	tvp->val = val;
+
+	return 0;
+}
+
+/******************************************************************************\
+* Code for querying the current tag/value.
+\******************************************************************************/
+
+/* Get the current tag. */
+char *jas_tvparser_gettag(jas_tvparser_t *tvp)
+{
+	return tvp->tag;
+}
+
+/* Get the current value. */
+const char *jas_tvparser_getval(jas_tvparser_t *tvp)
+{
+	return tvp->val;
+}
+
+/******************************************************************************\
+* Miscellaneous code.
+\******************************************************************************/
+
+/* Lookup a tag by name. */
+jas_taginfo_t *jas_taginfos_lookup(jas_taginfo_t *taginfos, const char *name)
+{
+	jas_taginfo_t *taginfo;
+	taginfo = taginfos;
+	while (taginfo->id >= 0) {
+		if (!strcmp(taginfo->name, name)) {
+			return taginfo;
+		}
+		++taginfo;
+	}
+	return 0;
+}
+
+/* This function is simply for convenience. */
+/* One can avoid testing for the special case of a null pointer, by
+  using this function.   This function never returns a null pointer.  */
+jas_taginfo_t *jas_taginfo_nonull(jas_taginfo_t *taginfo)
+{
+	static jas_taginfo_t invalidtaginfo = {
+		-1, 0
+	};
+	
+	return taginfo ? taginfo : &invalidtaginfo;
+}
diff --git a/converter/other/jpeg2000/libjasper/base/jas_version.c b/converter/other/jpeg2000/libjasper/base/jas_version.c
new file mode 100644
index 00000000..5608c4d4
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/base/jas_version.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+#include "jasper/jas_version.h"
+
+const char *jas_getversion()
+{
+	return JAS_VERSION;
+}
diff --git a/converter/other/jpeg2000/libjasper/base/partlist b/converter/other/jpeg2000/libjasper/base/partlist
new file mode 100644
index 00000000..cd244275
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/base/partlist
@@ -0,0 +1 @@
+/mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_debug.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_getopt.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_image.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_init.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_malloc.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_seq.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_stream.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_string.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_tvp.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_version.o
diff --git a/converter/other/jpeg2000/libjasper/include/jasper/jas_debug.h b/converter/other/jpeg2000/libjasper/include/jasper/jas_debug.h
new file mode 100644
index 00000000..6c597c59
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/include/jasper/jas_debug.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Debugging-Related Code
+ *
+ * $Id$
+ */
+
+#ifndef JAS_DEBUG_H
+#define JAS_DEBUG_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdio.h>
+
+#include "jasper/jas_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/******************************************************************************\
+* Macros and functions.
+\******************************************************************************/
+
+/* Output debugging information to standard error provided that the debug
+  level is set sufficiently high. */
+#if defined(DEBUG)
+#define	JAS_DBGLOG(n, x) \
+	((jas_getdbglevel() >= (n)) ? (jas_eprintf x) : 0)
+#else
+#define	JAS_DBGLOG(n, x)
+#endif
+
+/* Get the library debug level. */
+int jas_getdbglevel(void);
+
+/* Set the library debug level. */
+int jas_setdbglevel(int dbglevel);
+
+/* Perform formatted output to standard error. */
+int jas_eprintf(const char *fmt, ...);
+
+/* Dump memory to a stream. */
+int jas_memdump(FILE *out, void *data, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/include/jasper/jas_fix.h b/converter/other/jpeg2000/libjasper/include/jasper/jas_fix.h
new file mode 100644
index 00000000..9599eb1c
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/include/jasper/jas_fix.h
@@ -0,0 +1,406 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Fixed-Point Number Class
+ *
+ * $Id$
+ */
+
+#ifndef JAS_FIX_H
+#define JAS_FIX_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <jasper/jas_types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/******************************************************************************\
+* Constants.
+\******************************************************************************/
+
+/* The representation of the value zero. */
+#define	JAS_FIX_ZERO(fix_t, fracbits) \
+	JAS_CAST(fix_t, 0)
+
+/* The representation of the value one. */
+#define	JAS_FIX_ONE(fix_t, fracbits) \
+	(JAS_CAST(fix_t, 1) << (fracbits))
+
+/* The representation of the value one half. */
+#define	JAS_FIX_HALF(fix_t, fracbits) \
+	(JAS_CAST(fix_t, 1) << ((fracbits) - 1))
+
+/******************************************************************************\
+* Conversion operations.
+\******************************************************************************/
+
+/* Convert an int to a fixed-point number. */
+#define JAS_INTTOFIX(fix_t, fracbits, x) \
+	JAS_CAST(fix_t, (x) << (fracbits))
+
+/* Convert a fixed-point number to an int. */
+#define JAS_FIXTOINT(fix_t, fracbits, x) \
+	JAS_CAST(int, (x) >> (fracbits))
+
+/* Convert a fixed-point number to a double. */
+#define JAS_FIXTODBL(fix_t, fracbits, x) \
+	(JAS_CAST(double, x) / (JAS_CAST(fix_t, 1) << (fracbits)))
+
+/* Convert a double to a fixed-point number. */
+#define JAS_DBLTOFIX(fix_t, fracbits, x) \
+	JAS_CAST(fix_t, ((x) * JAS_CAST(double, JAS_CAST(fix_t, 1) << (fracbits))))
+
+/******************************************************************************\
+* Basic arithmetic operations.
+* All other arithmetic operations are synthesized from these basic operations.
+* There are three macros for each type of arithmetic operation.
+* One macro always performs overflow/underflow checking, one never performs
+* overflow/underflow checking, and one is generic with its behavior
+* depending on compile-time flags.
+* Only the generic macros should be invoked directly by application code.
+\******************************************************************************/
+
+/* Calculate the sum of two fixed-point numbers. */
+#if !defined(DEBUG_OVERFLOW)
+#define JAS_FIX_ADD			JAS_FIX_ADD_FAST
+#else
+#define JAS_FIX_ADD			JAS_FIX_ADD_OFLOW
+#endif
+
+/* Calculate the sum of two fixed-point numbers without overflow checking. */
+#define	JAS_FIX_ADD_FAST(fix_t, fracbits, x, y)	((x) + (y))
+
+/* Calculate the sum of two fixed-point numbers with overflow checking. */
+#define	JAS_FIX_ADD_OFLOW(fix_t, fracbits, x, y) \
+	((x) >= 0) ? \
+	  (((y) >= 0) ? ((x) + (y) >= 0 || JAS_FIX_OFLOW(), (x) + (y)) : \
+	  ((x) + (y))) : \
+	  (((y) >= 0) ? ((x) + (y)) : ((x) + (y) < 0 || JAS_FIX_OFLOW(), \
+	  (x) + (y)))
+
+/* Calculate the product of two fixed-point numbers. */
+#if !defined(DEBUG_OVERFLOW)
+#define JAS_FIX_MUL			JAS_FIX_MUL_FAST
+#else
+#define JAS_FIX_MUL			JAS_FIX_MUL_OFLOW
+#endif
+
+/* Calculate the product of two fixed-point numbers without overflow
+  checking. */
+#define	JAS_FIX_MUL_FAST(fix_t, fracbits, bigfix_t, x, y) \
+	JAS_CAST(fix_t, (JAS_CAST(bigfix_t, x) * JAS_CAST(bigfix_t, y)) >> \
+	  (fracbits))
+
+/* Calculate the product of two fixed-point numbers with overflow
+  checking. */
+#define JAS_FIX_MUL_OFLOW(fix_t, fracbits, bigfix_t, x, y) \
+	((JAS_CAST(bigfix_t, x) * JAS_CAST(bigfix_t, y) >> (fracbits)) == \
+	  JAS_CAST(fix_t, (JAS_CAST(bigfix_t, x) * JAS_CAST(bigfix_t, y) >> \
+	  (fracbits))) ? \
+	  JAS_CAST(fix_t, (JAS_CAST(bigfix_t, x) * JAS_CAST(bigfix_t, y) >> \
+	  (fracbits))) : JAS_FIX_OFLOW())
+
+/* Calculate the product of a fixed-point number and an int. */
+#if !defined(DEBUG_OVERFLOW)
+#define	JAS_FIX_MULBYINT	JAS_FIX_MULBYINT_FAST
+#else
+#define	JAS_FIX_MULBYINT	JAS_FIX_MULBYINT_OFLOW
+#endif
+
+/* Calculate the product of a fixed-point number and an int without overflow
+  checking. */
+#define	JAS_FIX_MULBYINT_FAST(fix_t, fracbits, x, y) \
+	JAS_CAST(fix_t, ((x) * (y)))
+
+/* Calculate the product of a fixed-point number and an int with overflow
+  checking. */
+#define	JAS_FIX_MULBYINT_OFLOW(fix_t, fracbits, x, y) \
+	JAS_FIX_MULBYINT_FAST(fix_t, fracbits, x, y)
+
+/* Calculate the quotient of two fixed-point numbers. */
+#if !defined(DEBUG_OVERFLOW)
+#define JAS_FIX_DIV			JAS_FIX_DIV_FAST
+#else
+#define JAS_FIX_DIV			JAS_FIX_DIV_UFLOW
+#endif
+
+/* Calculate the quotient of two fixed-point numbers without underflow
+  checking. */
+#define	JAS_FIX_DIV_FAST(fix_t, fracbits, bigfix_t, x, y) \
+	JAS_CAST(fix_t, (JAS_CAST(bigfix_t, x) << (fracbits)) / (y))
+
+/* Calculate the quotient of two fixed-point numbers with underflow
+  checking. */
+#define JAS_FIX_DIV_UFLOW(fix_t, fracbits, bigfix_t, x, y) \
+	JAS_FIX_DIV_FAST(fix_t, fracbits, bigfix_t, x, y)
+
+/* Negate a fixed-point number. */
+#if !defined(DEBUG_OVERFLOW)
+#define	JAS_FIX_NEG			JAS_FIX_NEG_FAST
+#else
+#define	JAS_FIX_NEG			JAS_FIX_NEG_OFLOW
+#endif
+
+/* Negate a fixed-point number without overflow checking. */
+#define	JAS_FIX_NEG_FAST(fix_t, fracbits, x) \
+	(-(x))
+
+/* Negate a fixed-point number with overflow checking. */
+/* Yes, overflow is actually possible for two's complement representations,
+  although highly unlikely to occur. */
+#define	JAS_FIX_NEG_OFLOW(fix_t, fracbits, x) \
+	(((x) < 0) ? (-(x) > 0 || JAS_FIX_OFLOW(), -(x)) : (-(x)))
+
+/* Perform an arithmetic shift left of a fixed-point number. */
+#if !defined(DEBUG_OVERFLOW)
+#define	JAS_FIX_ASL			JAS_FIX_ASL_FAST
+#else
+#define	JAS_FIX_ASL			JAS_FIX_ASL_OFLOW
+#endif
+
+/* Perform an arithmetic shift left of a fixed-point number without overflow
+  checking. */
+#define	JAS_FIX_ASL_FAST(fix_t, fracbits, x, n) \
+	((x) << (n))
+
+/* Perform an arithmetic shift left of a fixed-point number with overflow
+  checking. */
+#define	JAS_FIX_ASL_OFLOW(fix_t, fracbits, x, n) \
+	((((x) << (n)) >> (n)) == (x) || JAS_FIX_OFLOW(), (x) << (n))
+
+/* Perform an arithmetic shift right of a fixed-point number. */
+#if !defined(DEBUG_OVERFLOW)
+#define	JAS_FIX_ASR			JAS_FIX_ASR_FAST
+#else
+#define	JAS_FIX_ASR			JAS_FIX_ASR_UFLOW
+#endif
+
+/* Perform an arithmetic shift right of a fixed-point number without underflow
+  checking. */
+#define	JAS_FIX_ASR_FAST(fix_t, fracbits, x, n) \
+	((x) >> (n))
+
+/* Perform an arithmetic shift right of a fixed-point number with underflow
+  checking. */
+#define	JAS_FIX_ASR_UFLOW(fix_t, fracbits, x, n) \
+	JAS_FIX_ASR_FAST(fix_t, fracbits, x, n)
+
+/******************************************************************************\
+* Other basic arithmetic operations.
+\******************************************************************************/
+
+/* Calculate the difference between two fixed-point numbers. */
+#define JAS_FIX_SUB(fix_t, fracbits, x, y) \
+	JAS_FIX_ADD(fix_t, fracbits, x, JAS_FIX_NEG(fix_t, fracbits, y))
+
+/* Add one fixed-point number to another. */
+#define JAS_FIX_PLUSEQ(fix_t, fracbits, x, y) \
+	((x) = JAS_FIX_ADD(fix_t, fracbits, x, y))
+
+/* Subtract one fixed-point number from another. */
+#define JAS_FIX_MINUSEQ(fix_t, fracbits, x, y) \
+	((x) = JAS_FIX_SUB(fix_t, fracbits, x, y))
+
+/* Multiply one fixed-point number by another. */
+#define	JAS_FIX_MULEQ(fix_t, fracbits, bigfix_t, x, y) \
+	((x) = JAS_FIX_MUL(fix_t, fracbits, bigfix_t, x, y))
+
+/******************************************************************************\
+* Miscellaneous operations.
+\******************************************************************************/
+
+/* Calculate the absolute value of a fixed-point number. */
+#define	JAS_FIX_ABS(fix_t, fracbits, x) \
+	(((x) >= 0) ? (x) : (JAS_FIX_NEG(fix_t, fracbits, x)))
+
+/* Is a fixed-point number an integer? */
+#define	JAS_FIX_ISINT(fix_t, fracbits, x) \
+	(JAS_FIX_FLOOR(fix_t, fracbits, x) == (x))
+
+/* Get the sign of a fixed-point number. */
+#define JAS_FIX_SGN(fix_t, fracbits, x) \
+	((x) >= 0 ? 1 : (-1))
+
+/******************************************************************************\
+* Relational operations.
+\******************************************************************************/
+
+/* Compare two fixed-point numbers. */
+#define JAS_FIX_CMP(fix_t, fracbits, x, y) \
+	((x) > (y) ? 1 : (((x) == (y)) ? 0 : (-1)))
+
+/* Less than. */
+#define	JAS_FIX_LT(fix_t, fracbits, x, y) \
+	((x) < (y))
+
+/* Less than or equal. */
+#define	JAS_FIX_LTE(fix_t, fracbits, x, y) \
+	((x) <= (y))
+
+/* Greater than. */
+#define	JAS_FIX_GT(fix_t, fracbits, x, y) \
+	((x) > (y))
+
+/* Greater than or equal. */
+#define	JAS_FIX_GTE(fix_t, fracbits, x, y) \
+	((x) >= (y))
+
+/******************************************************************************\
+* Rounding functions.
+\******************************************************************************/
+
+/* Round a fixed-point number to the nearest integer. */
+#define	JAS_FIX_ROUND(fix_t, fracbits, x) \
+	(((x) < 0) ? JAS_FIX_FLOOR(fix_t, fracbits, JAS_FIX_ADD(fix_t, fracbits, \
+	  (x), JAS_FIX_HALF(fix_t, fracbits))) : \
+	  JAS_FIX_NEG(fix_t, fracbits, JAS_FIX_FLOOR(fix_t, fracbits, \
+	  JAS_FIX_ADD(fix_t, fracbits, (-(x)), JAS_FIX_HALF(fix_t, fracbits)))))
+
+/* Round a fixed-point number to the nearest integer in the direction of
+  negative infinity (i.e., the floor function). */
+#define	JAS_FIX_FLOOR(fix_t, fracbits, x) \
+	((x) & (~((JAS_CAST(fix_t, 1) << (fracbits)) - 1)))
+
+/* Round a fixed-point number to the nearest integer in the direction
+  of zero. */
+#define JAS_FIX_TRUNC(fix_t, fracbits, x) \
+	(((x) >= 0) ? JAS_FIX_FLOOR(fix_t, fracbits, x) : \
+	  JAS_FIX_CEIL(fix_t, fracbits, x))
+
+/******************************************************************************\
+* The below macros are for internal library use only.  Do not invoke them
+* directly in application code.
+\******************************************************************************/
+
+/* Handle overflow. */
+#define	JAS_FIX_OFLOW() \
+	fprintf(stderr, "overflow error: file %s, line %d\n", __FILE__, __LINE__)
+
+/* Handle underflow. */
+#define	JAS_FIX_UFLOW() \
+	fprintf(stderr, "underflow error: file %s, line %d\n", __FILE__, __LINE__)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/include/jasper/jas_getopt.h b/converter/other/jpeg2000/libjasper/include/jasper/jas_getopt.h
new file mode 100644
index 00000000..4f272dc9
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/include/jasper/jas_getopt.h
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Command Line Option Parsing Code
+ *
+ * $Id$
+ */
+
+#ifndef JAS_GETOPT_H
+#define JAS_GETOPT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/******************************************************************************\
+* Constants.
+\******************************************************************************/
+
+#define	JAS_GETOPT_EOF	(-1)
+#define	JAS_GETOPT_ERR	'?'
+
+/* option flags. */
+#define	JAS_OPT_HASARG	0x01	/* option has argument */
+
+/******************************************************************************\
+* Types.
+\******************************************************************************/
+
+/* Command line option type. */
+typedef struct {
+
+	int id;
+	/* The unique identifier for this option. */
+
+	char *name;
+	/* The name of this option. */
+
+	int flags;
+	/* option flags. */
+
+} jas_opt_t;
+
+/******************************************************************************\
+* External data.
+\******************************************************************************/
+
+/* The current option index. */
+extern int jas_optind;
+
+/* The current option argument. */
+extern char *jas_optarg;
+
+/* The debug level. */
+extern int jas_opterr;
+
+/******************************************************************************\
+* Prototypes.
+\******************************************************************************/
+
+/* Get the next option. */
+int jas_getopt(int argc, char **argv, jas_opt_t *opts);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/include/jasper/jas_image.h b/converter/other/jpeg2000/libjasper/include/jasper/jas_image.h
new file mode 100644
index 00000000..d6456f7c
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/include/jasper/jas_image.h
@@ -0,0 +1,593 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Image Class
+ *
+ * $Id$
+ */
+
+#ifndef JAS_IMAGE_H
+#define JAS_IMAGE_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "pm_c_util.h"
+#include <jasper/jas_stream.h>
+#include <jasper/jas_seq.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/******************************************************************************\
+* Constants.
+\******************************************************************************/
+
+/*
+ * Miscellaneous constants.
+ */
+
+/* The threshold at which image data is no longer stored in memory. */
+#define JAS_IMAGE_INMEMTHRESH	(16 * 1024 * 1024)
+
+/*
+ * Color models.
+ */
+
+#define JAS_IMAGE_CS_UNKNOWN	0	/* Unknown */
+#define	JAS_IMAGE_CS_GRAY	1	/* Standard Gray */
+#define	JAS_IMAGE_CS_RGB	2	/* Standard RGB */
+#define	JAS_IMAGE_CS_YCBCR	3	/* Standard YCC */
+#if 0
+#define	JAS_IMAGE_CM_ICC	4	/* ICC Profile */
+#endif
+/*
+ * Component types
+ */
+
+#define	JAS_IMAGE_CT_UNKNOWN	0x10000
+#define	JAS_IMAGE_CT_COLOR(n)	((n) & 0x7fff)
+#define	JAS_IMAGE_CT_OPACITY	0x08000
+
+#define	JAS_IMAGE_CT_RGB_R	0
+#define	JAS_IMAGE_CT_RGB_G	1
+#define	JAS_IMAGE_CT_RGB_B	2
+
+#define	JAS_IMAGE_CT_YCBCR_Y	0
+#define	JAS_IMAGE_CT_YCBCR_CB	1
+#define	JAS_IMAGE_CT_YCBCR_CR	2
+
+#define	JAS_IMAGE_CT_GRAY_Y	0
+
+/******************************************************************************\
+* Image class and supporting classes.
+\******************************************************************************/
+
+/* Image component class. */
+
+typedef struct {
+
+	uint_fast32_t tlx_;
+	/* The x-coordinate of the top-left corner of the component. */
+
+	uint_fast32_t tly_;
+	/* The y-coordinate of the top-left corner of the component. */
+
+	uint_fast32_t hstep_;
+	/* The horizontal sampling period in units of the reference grid. */
+
+	uint_fast32_t vstep_;
+	/* The vertical sampling period in units of the reference grid. */
+
+	uint_fast32_t width_;
+	/* The component width in samples. */
+
+	uint_fast32_t height_;
+	/* The component height in samples. */
+
+	uint_fast16_t prec_;
+	/* The precision of the sample data (i.e., the number of bits per
+	sample).  If the samples are signed values, this quantity
+	includes the sign bit. */
+
+	uint_fast8_t sgnd_;
+	/* The signedness of the sample data. */
+
+	jas_stream_t *stream_;
+	/* The stream containing the component data. */
+
+	int cps_;
+	/* The number of characters per sample in the stream. */
+
+	uint_fast32_t type_;
+	/* The type of component (e.g., opacity, red, green, blue, luma). */
+
+} jas_image_cmpt_t;
+
+/* Image class. */
+
+typedef struct {
+
+	uint_fast32_t tlx_;
+	/* The x-coordinate of the top-left corner of the image bounding box. */
+
+	uint_fast32_t tly_;
+	/* The y-coordinate of the top-left corner of the image bounding box. */
+
+	uint_fast32_t brx_;
+	/* The x-coordinate of the bottom-right corner of the image bounding
+	  box (plus one). */
+
+	uint_fast32_t bry_;
+	/* The y-coordinate of the bottom-right corner of the image bounding
+	  box (plus one). */
+
+	uint_fast16_t numcmpts_;
+	/* The number of components. */
+
+	uint_fast16_t maxcmpts_;
+	/* The maximum number of components that this image can have (i.e., the
+	  allocated size of the components array). */
+
+	jas_image_cmpt_t **cmpts_;
+	/* Per-component information. */
+
+	int colorspace_;
+	/* The color space used (e.g., RGB, YCbCr, gray).  This field is only
+	of particular relevance in the case of a multi-component image. */
+
+	unsigned char *iccp_;
+	/* ICC profile information. */
+
+	int iccplen_;
+
+	bool inmem_;
+
+} jas_image_t;
+
+/* Component parameters class. */
+/* This data type exists solely/mainly for the purposes of the
+  jas_image_create function. */
+
+typedef struct {
+
+	uint_fast32_t tlx;
+	/* The x-coordinate of the top-left corner of the component. */
+
+	uint_fast32_t tly;
+	/* The y-coordinate of the top-left corner of the component. */
+
+	uint_fast32_t hstep;
+	/* The horizontal sampling period in units of the reference grid. */
+
+	uint_fast32_t vstep;
+	/* The vertical sampling period in units of the reference grid. */
+
+	uint_fast32_t width;
+	/* The width of the component in samples. */
+
+	uint_fast32_t height;
+	/* The height of the component in samples. */
+
+	uint_fast16_t prec;
+	/* The precision of the component sample data. */
+
+	bool sgnd;
+	/* The signedness of the component sample data. */
+
+} jas_image_cmptparm_t;
+
+/******************************************************************************\
+* File format related classes.
+\******************************************************************************/
+
+#define	JAS_IMAGE_MAXFMTS	32
+/* The maximum number of image data formats supported. */
+
+/* Image format-dependent operations. */
+
+typedef struct {
+
+	jas_image_t *(*decode)(jas_stream_t *in, char *opts);
+	/* Decode image data from a stream. */
+
+	int (*encode)(jas_image_t *image, jas_stream_t *out, char *opts);
+	/* Encode image data to a stream. */
+
+	int (*validate)(jas_stream_t *in);
+	/* Determine if stream data is in a particular format. */
+
+} jas_image_fmtops_t;
+
+/* Image format information. */
+
+typedef struct {
+
+	int id;
+	/* The ID for this format. */
+
+	char *name;
+	/* The name by which this format is identified. */
+
+	char *ext;
+	/* The file name extension associated with this format. */
+
+	char *desc;
+	/* A brief description of the format. */
+
+	jas_image_fmtops_t ops;
+	/* The operations for this format. */
+
+} jas_image_fmtinfo_t;
+
+/******************************************************************************\
+* Image operations.
+\******************************************************************************/
+
+/* Create an image. */
+jas_image_t *jas_image_create(uint_fast16_t numcmpts,
+  jas_image_cmptparm_t *cmptparms, int colorspace);
+
+/* Create an "empty" image. */
+jas_image_t *jas_image_create0(void);
+
+/* Clone an image. */
+jas_image_t *jas_image_copy(jas_image_t *image);
+
+/* Deallocate any resources associated with an image. */
+void jas_image_destroy(jas_image_t *image);
+
+/* Get the width of the image in units of the image reference grid. */
+#define jas_image_width(image) \
+	((image)->brx_ - (image)->tlx_)
+
+/* Get the height of the image in units of the image reference grid. */
+#define	jas_image_height(image) \
+	((image)->bry_ - (image)->tly_)
+
+/* Get the x-coordinate of the top-left corner of the image bounding box
+  on the reference grid. */
+#define jas_image_tlx(image) \
+	((image)->tlx_)
+
+/* Get the y-coordinate of the top-left corner of the image bounding box
+  on the reference grid. */
+#define jas_image_tly(image) \
+	((image)->tly_)
+
+/* Get the x-coordinate of the bottom-right corner of the image bounding box
+  on the reference grid (plus one). */
+#define jas_image_brx(image) \
+	((image)->brx_)
+
+/* Get the y-coordinate of the bottom-right corner of the image bounding box
+  on the reference grid (plus one). */
+#define jas_image_bry(image) \
+	((image)->bry_)
+
+/* Get the number of image components. */
+#define	jas_image_numcmpts(image) \
+	((image)->numcmpts_)
+
+/* Get the color model used by the image. */
+#define	jas_image_colorspace(image) \
+	((image)->colorspace_)
+
+/* Set the color model for an image. */
+#define jas_image_setcolorspace(image, colorspace) \
+	((image)->colorspace_ = (colorspace))
+
+#define jas_image_cmpttype(image, cmptno) \
+	((image)->cmpts_[(cmptno)]->type_)
+#define jas_image_setcmpttype(image, cmptno, type) \
+	((image)->cmpts_[(cmptno)]->type_ = (type))
+
+/* Get the width of a component. */
+#define	jas_image_cmptwidth(image, cmptno) \
+	((image)->cmpts_[cmptno]->width_)
+
+/* Get the height of a component. */
+#define	jas_image_cmptheight(image, cmptno) \
+	((image)->cmpts_[cmptno]->height_)
+
+/* Get the signedness of the sample data for a component. */
+#define	jas_image_cmptsgnd(image, cmptno) \
+	((image)->cmpts_[cmptno]->sgnd_)
+
+/* Get the precision of the sample data for a component. */
+#define	jas_image_cmptprec(image, cmptno) \
+	((image)->cmpts_[cmptno]->prec_)
+
+/* Get the horizontal subsampling factor for a component. */
+#define	jas_image_cmpthstep(image, cmptno) \
+	((image)->cmpts_[cmptno]->hstep_)
+
+/* Get the vertical subsampling factor for a component. */
+#define	jas_image_cmptvstep(image, cmptno) \
+	((image)->cmpts_[cmptno]->vstep_)
+
+/* Get the x-coordinate of the top-left corner of a component. */
+#define	jas_image_cmpttlx(image, cmptno) \
+	((image)->cmpts_[cmptno]->tlx_)
+
+/* Get the y-coordinate of the top-left corner of a component. */
+#define	jas_image_cmpttly(image, cmptno) \
+	((image)->cmpts_[cmptno]->tly_)
+
+/* Get the x-coordinate of the bottom-right corner of a component
+  (plus "one"). */
+#define	jas_image_cmptbrx(image, cmptno) \
+	((image)->cmpts_[cmptno]->tlx_ + (image)->cmpts_[cmptno]->width_ * \
+	  (image)->cmpts_[cmptno]->hstep_)
+
+/* Get the y-coordinate of the bottom-right corner of a component
+  (plus "one"). */
+#define	jas_image_cmptbry(image, cmptno) \
+	((image)->cmpts_[cmptno]->tly_ + (image)->cmpts_[cmptno]->height_ * \
+	  (image)->cmpts_[cmptno]->vstep_)
+
+/* Get the raw size of an image (i.e., the nominal size of the image without
+  any compression. */
+uint_fast32_t jas_image_rawsize(jas_image_t *image);
+
+/* Create an image from a stream in some specified format. */
+jas_image_t *jas_image_decode(jas_stream_t *in, int fmt, char *optstr);
+
+/* Write an image to a stream in a specified format. */
+int jas_image_encode(jas_image_t *image, jas_stream_t *out, int fmt,
+  char *optstr);
+
+/* Read a rectangular region of an image component. */
+/* The position and size of the rectangular region to be read is specified
+relative to the component's coordinate system. */
+int jas_image_readcmpt(jas_image_t *image, uint_fast16_t cmptno,
+  uint_fast32_t x, uint_fast32_t y, uint_fast32_t width, uint_fast32_t height,
+  jas_matrix_t *data);
+
+/* Write a rectangular region of an image component. */
+int jas_image_writecmpt(jas_image_t *image, uint_fast16_t cmptno,
+  uint_fast32_t x, uint_fast32_t y, uint_fast32_t width, uint_fast32_t height,
+  jas_matrix_t *data);
+
+/* Delete a component from an image. */
+void jas_image_delcmpt(jas_image_t *image, uint_fast16_t cmptno);
+
+/* Add a component to an image. */
+int jas_image_addcmpt(jas_image_t *image, uint_fast16_t cmptno,
+  jas_image_cmptparm_t *cmptparm);
+
+/* Copy a component from one image to another. */
+int jas_image_copycmpt(jas_image_t *dstimage, int dstcmptno,
+  jas_image_t *srcimage, int srccmptno);
+
+#if 0
+int_fast64_t jas_image_readcmpt1(jas_image_t *image, uint_fast16_t cmptno,
+  uint_fast32_t x, uint_fast32_t y);
+#endif
+
+#define	JAS_IMAGE_CDT_GETSGND(dtype) (((dtype) >> 7) & 1)
+#define	JAS_IMAGE_CDT_SETSGND(dtype) (((dtype) & 1) << 7)
+#define	JAS_IMAGE_CDT_GETPREC(dtype) ((dtype) & 0x7f)
+#define	JAS_IMAGE_CDT_SETPREC(dtype) ((dtype) & 0x7f)
+
+#define	jas_image_cmptdtype(image, cmptno) \
+	(JAS_IMAGE_CDT_SETSGND((image)->cmpts_[cmptno]->sgnd_) | JAS_IMAGE_CDT_SETPREC((image)->cmpts_[cmptno]->prec_))
+
+void jas_image_dump(jas_image_t *image, FILE *out);
+
+int jas_image_depalettize(jas_image_t *image, int cmptno, int numlutents,
+  int_fast32_t *lutents, int dtype, int newcmptno);
+
+int jas_image_readcmptsample(jas_image_t *image, int cmptno, int x, int y);
+void jas_image_writecmptsample(jas_image_t *image, int cmptno, int x, int y,
+  int_fast32_t v);
+
+int jas_image_getcmptbytype(jas_image_t *image, int ctype);
+
+/******************************************************************************\
+* Image format-related operations.
+\******************************************************************************/
+
+/* Clear the table of image formats. */
+void jas_image_clearfmts(void);
+
+/* Add entry to table of image formats. */
+int jas_image_addfmt(int id, const char *name, const char *ext, 
+  const char *desc,
+  jas_image_fmtops_t *ops);
+
+/* Get the ID for the image format with the specified name. */
+int jas_image_strtofmt(char *s);
+
+/* Get the name of the image format with the specified ID. */
+char *jas_image_fmttostr(int fmt);
+
+/* Lookup image format information by the format ID. */
+jas_image_fmtinfo_t *jas_image_lookupfmtbyid(int id);
+
+/* Lookup image format information by the format name. */
+jas_image_fmtinfo_t *jas_image_lookupfmtbyname(const char *name);
+
+/* Guess the format of an image file based on its name. */
+int jas_image_fmtfromname(char *filename);
+
+/* Get the format of image data in a stream. */
+int jas_image_getfmt(jas_stream_t *in);
+
+/******************************************************************************\
+* Image format-dependent operations.
+\******************************************************************************/
+
+#if !defined(EXCLUDE_JPG_SUPPORT)
+/* Format-dependent operations for JPG support. */
+jas_image_t *jpg_decode(jas_stream_t *in, char *optstr);
+int jpg_encode(jas_image_t *image, jas_stream_t *out, char *optstr);
+int jpg_validate(jas_stream_t *in);
+#endif
+
+#if !defined(EXCLUDE_MIF_SUPPORT)
+/* Format-dependent operations for MIF support. */
+jas_image_t *mif_decode(jas_stream_t *in, char *optstr);
+int mif_encode(jas_image_t *image, jas_stream_t *out, char *optstr);
+int mif_validate(jas_stream_t *in);
+#endif
+
+#if !defined(EXCLUDE_PNM_SUPPORT)
+/* Format-dependent operations for PNM support. */
+jas_image_t *pnm_decode(jas_stream_t *in, char *optstr);
+int pnm_encode(jas_image_t *image, jas_stream_t *out, char *optstr);
+int pnm_validate(jas_stream_t *in);
+#endif
+
+#if !defined(EXCLUDE_RAS_SUPPORT)
+/* Format-dependent operations for Sun Rasterfile support. */
+jas_image_t *ras_decode(jas_stream_t *in, char *optstr);
+int ras_encode(jas_image_t *image, jas_stream_t *out, char *optstr);
+int ras_validate(jas_stream_t *in);
+#endif
+
+#if !defined(EXCLUDE_BMP_SUPPORT)
+/* Format-dependent operations for BMP support. */
+jas_image_t *bmp_decode(jas_stream_t *in, char *optstr);
+int bmp_encode(jas_image_t *image, jas_stream_t *out, char *optstr);
+int bmp_validate(jas_stream_t *in);
+#endif
+
+#if !defined(EXCLUDE_JP2_SUPPORT)
+/* Format-dependent operations for JP2 support. */
+jas_image_t *jp2_decode(jas_stream_t *in, char *optstr);
+int jp2_encode(jas_image_t *image, jas_stream_t *out, char *optstr);
+int jp2_validate(jas_stream_t *in);
+#endif
+
+#if !defined(EXCLUDE_JPC_SUPPORT)
+/* Format-dependent operations for JPEG-2000 code stream support. */
+jas_image_t *jpc_decode(jas_stream_t *in, char *optstr);
+int jpc_encode(jas_image_t *image, jas_stream_t *out, char *optstr);
+int jpc_validate(jas_stream_t *in);
+#endif
+
+#if !defined(EXCLUDE_PGX_SUPPORT)
+/* Format-dependent operations for PGX support. */
+jas_image_t *pgx_decode(jas_stream_t *in, char *optstr);
+int pgx_encode(jas_image_t *image, jas_stream_t *out, char *optstr);
+int pgx_validate(jas_stream_t *in);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/include/jasper/jas_init.h b/converter/other/jpeg2000/libjasper/include/jasper/jas_init.h
new file mode 100644
index 00000000..4ebeaa87
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/include/jasper/jas_init.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+#ifndef JAS_INIT_H
+#define JAS_INIT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/******************************************************************************\
+* Functions.
+\******************************************************************************/
+
+int jas_init(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/include/jasper/jas_malloc.h b/converter/other/jpeg2000/libjasper/include/jasper/jas_malloc.h
new file mode 100644
index 00000000..fe5b6172
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/include/jasper/jas_malloc.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Memory Allocator
+ *
+ * $Id$
+ */
+
+#ifndef JAS_MALLOC_H
+#define JAS_MALLOC_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/******************************************************************************\
+* Hack follows...
+\******************************************************************************/
+
+#if defined(DEBUG_MEMALLOC)
+/* This is somewhat of a hack, but it's a useful hack. :-) */
+/* Use my own custom memory allocator for debugging. */
+#include "../../../../local/src/memalloc.h"
+#define jas_malloc	MEMALLOC
+#define	jas_free	MEMFREE
+#define	jas_realloc	MEMREALLOC
+#define	jas_calloc	MEMCALLOC
+#endif
+
+/******************************************************************************\
+* Functions.
+\******************************************************************************/
+
+#if !defined(DEBUG_MEMALLOC)
+
+/* Allocate memory. */
+void *jas_malloc(size_t size);
+
+/* Free memory. */
+void jas_free(void *ptr);
+
+/* Resize a block of allocated memory. */
+void *jas_realloc(void *ptr, size_t size);
+
+/* Allocate a block of memory and initialize the contents to zero. */
+void *jas_calloc(size_t nmemb, size_t size);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/include/jasper/jas_math.h b/converter/other/jpeg2000/libjasper/include/jasper/jas_math.h
new file mode 100644
index 00000000..a43051e5
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/include/jasper/jas_math.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Math-Related Code
+ *
+ * $Id$
+ */
+
+#ifndef	JAS_MATH_H
+#define	JAS_MATH_H
+
+/******************************************************************************\
+* Includes
+\******************************************************************************/
+
+#include	<assert.h>
+#include	<stdio.h>
+#include	<string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/******************************************************************************\
+* Macros
+\******************************************************************************/
+
+/* Compute the absolute value. */
+#define	JAS_ABS(x) \
+	(((x) >= 0) ? (x) : (-(x)))
+
+/* Compute the minimum of two values. */
+#define	JAS_MIN(x, y) \
+	(((x) < (y)) ? (x) : (y))
+
+/* Compute the maximum of two values. */
+#define	JAS_MAX(x, y) \
+	(((x) > (y)) ? (x) : (y))
+
+/* Compute the remainder from division (where division is defined such
+  that the remainder is always nonnegative). */
+#define	JAS_MOD(x, y) \
+	(((x) < 0) ? (((-x) % (y)) ? ((y) - ((-(x)) % (y))) : (0)) : ((x) % (y)))
+
+/* Compute the integer with the specified number of least significant bits
+  set to one. */
+#define	JAS_ONES(n) \
+  ((1 << (n)) - 1)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/include/jasper/jas_seq.h b/converter/other/jpeg2000/libjasper/include/jasper/jas_seq.h
new file mode 100644
index 00000000..4599f50c
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/include/jasper/jas_seq.h
@@ -0,0 +1,348 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Sequence/Matrix Library
+ *
+ * $Id$
+ */
+
+#ifndef JAS_SEQ_H
+#define JAS_SEQ_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <jasper/jas_stream.h>
+#include <jasper/jas_types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/******************************************************************************\
+* Constants.
+\******************************************************************************/
+
+/* This matrix is a reference to another matrix. */
+#define JAS_MATRIX_REF	0x0001
+
+/******************************************************************************\
+* Types.
+\******************************************************************************/
+
+/* An element in a sequence. */
+typedef int_fast32_t jas_seqent_t;
+
+/* An element in a matrix. */
+typedef int_fast32_t jas_matent_t;
+
+/* Matrix. */
+
+typedef struct {
+
+	/* Additional state information. */
+	int flags_;
+
+	/* The starting horizontal index. */
+	int_fast32_t xstart_;
+
+	/* The starting vertical index. */
+	int_fast32_t ystart_;
+
+	/* The ending horizontal index. */
+	int_fast32_t xend_;
+
+	/* The ending vertical index. */
+	int_fast32_t yend_;
+
+	/* The number of rows in the matrix. */
+	int_fast32_t numrows_;
+
+	/* The number of columns in the matrix. */
+	int_fast32_t numcols_;
+
+	/* Pointers to the start of each row. */
+	jas_seqent_t **rows_;
+
+	/* The allocated size of the rows array. */
+	int_fast32_t maxrows_;
+
+	/* The matrix data buffer. */
+	jas_seqent_t *data_;
+
+	/* The allocated size of the data array. */
+	int_fast32_t datasize_;
+
+} jas_matrix_t;
+
+typedef jas_matrix_t jas_seq2d_t;
+typedef jas_matrix_t jas_seq_t;
+
+/******************************************************************************\
+* Functions/macros for matrix class.
+\******************************************************************************/
+
+/* Get the number of rows. */
+#define jas_matrix_numrows(matrix) \
+	((matrix)->numrows_)
+
+/* Get the number of columns. */
+#define jas_matrix_numcols(matrix) \
+	((matrix)->numcols_)
+
+/* Get a matrix element. */
+#define jas_matrix_get(matrix, i, j) \
+	((matrix)->rows_[i][j])
+
+/* Set a matrix element. */
+#define jas_matrix_set(matrix, i, j, v) \
+	((matrix)->rows_[i][j] = (v))
+
+/* Get an element from a matrix that is known to be a row or column vector. */
+#define jas_matrix_getv(matrix, i) \
+	(((matrix)->numrows_ == 1) ? ((matrix)->rows_[0][i]) : \
+	  ((matrix)->rows_[i][0]))
+
+/* Set an element in a matrix that is known to be a row or column vector. */
+#define jas_matrix_setv(matrix, i, v) \
+	(((matrix)->numrows_ == 1) ? ((matrix)->rows_[0][i] = (v)) : \
+	  ((matrix)->rows_[i][0] = (v)))
+
+/* Get the address of an element in a matrix. */
+#define	jas_matrix_getref(matrix, i, j) \
+	(&(matrix)->rows_[i][j])
+
+#define	jas_matrix_getvref(matrix, i) \
+	(((matrix)->numrows_ > 1) ? jas_matrix_getref(matrix, i, 0) : jas_matrix_getref(matrix, 0, i))
+
+#define jas_matrix_length(matrix) \
+	(max((matrix)->numrows_, (matrix)->numcols_))
+
+/* Create a matrix with the specified dimensions. */
+jas_matrix_t *jas_matrix_create(int numrows, int numcols);
+
+/* Destroy a matrix. */
+void jas_matrix_destroy(jas_matrix_t *matrix);
+
+jas_matrix_t *jas_matrix_copy(jas_matrix_t *x);
+
+/* Resize a matrix.  The previous contents of the matrix are lost. */
+int jas_matrix_resize(jas_matrix_t *matrix, int numrows, int numcols);
+
+int jas_matrix_output(jas_matrix_t *matrix, FILE *out);
+
+jas_matrix_t *jas_matrix_input(FILE *in);
+
+/* Create a matrix that references part of another matrix. */
+void jas_matrix_bindsub(jas_matrix_t *mat0, jas_matrix_t *mat1, int r0, int c0,
+  int r1, int c1);
+
+/* Create a matrix that is a reference to a row of another matrix. */
+#define jas_matrix_bindrow(mat0, mat1, r) \
+  (jas_matrix_bindsub((mat0), (mat1), (r), 0, (r), (mat1)->numcols_ - 1))
+
+/* Create a matrix that is a reference to a column of another matrix. */
+#define jas_matrix_bindcol(mat0, mat1, c) \
+  (jas_matrix_bindsub((mat0), (mat1), 0, (c), (mat1)->numrows_ - 1, (c)))
+
+/* Clip the values of matrix elements to the specified range. */
+void jas_matrix_clip(jas_matrix_t *matrix, jas_seqent_t minval,
+  jas_seqent_t maxval);
+
+/* Arithmetic shift left of all elements in a matrix. */
+void jas_matrix_asl(jas_matrix_t *matrix, int n);
+
+/* Arithmetic shift right of all elements in a matrix. */
+void jas_matrix_asr(jas_matrix_t *matrix, int n);
+
+/* Almost-but-not-quite arithmetic shift right of all elements in a matrix. */
+void jas_matrix_divpow2(jas_matrix_t *matrix, int n);
+
+/* Set all elements of a matrix to the specified value. */
+void jas_matrix_setall(jas_matrix_t *matrix, jas_seqent_t val);
+
+/* The spacing between rows of a matrix. */
+#define	jas_matrix_rowstep(matrix) \
+	(((matrix)->numrows_ > 1) ? ((matrix)->rows_[1] - (matrix)->rows_[0]) : (0))
+
+/* The spacing between columns of a matrix. */
+#define	jas_matrix_step(matrix) \
+	(((matrix)->numrows_ > 1) ? (jas_matrix_rowstep(matrix)) : (1))
+
+/* Compare two matrices for equality. */
+int jas_matrix_cmp(jas_matrix_t *mat0, jas_matrix_t *mat1);
+
+/******************************************************************************\
+* Functions/macros for 2-D sequence class.
+\******************************************************************************/
+
+jas_seq2d_t *jas_seq2d_copy(jas_seq2d_t *x);
+
+jas_matrix_t *jas_seq2d_create(int xstart, int ystart, int xend, int yend);
+
+#define	jas_seq2d_destroy(s) \
+	jas_matrix_destroy(s)
+
+#define	jas_seq2d_xstart(s) \
+	((s)->xstart_)
+#define	jas_seq2d_ystart(s) \
+	((s)->ystart_)
+#define	jas_seq2d_xend(s) \
+	((s)->xend_)
+#define	jas_seq2d_yend(s) \
+	((s)->yend_)
+#define	jas_seq2d_getref(s, x, y) \
+	(jas_matrix_getref(s, (y) - (s)->ystart_, (x) - (s)->xstart_))
+#define	jas_seq2d_get(s, x, y) \
+	(jas_matrix_get(s, (y) - (s)->ystart_, (x) - (s)->xstart_))
+#define	jas_seq2d_rowstep(s) \
+	jas_matrix_rowstep(s)
+#define	jas_seq2d_width(s) \
+	((s)->xend_ - (s)->xstart_)
+#define	jas_seq2d_height(s) \
+	((s)->yend_ - (s)->ystart_)
+#define	jas_seq2d_setshift(s, x, y) \
+	((s)->xstart_ = (x), (s)->ystart_ = (y), \
+	  (s)->xend_ = (s)->xstart_ + (s)->numcols_, \
+	  (s)->yend_ = (s)->ystart_ + (s)->numrows_)
+
+void jas_seq2d_bindsub(jas_matrix_t *s, jas_matrix_t *s1, int xstart,
+  int ystart, int xend, int yend);
+
+/******************************************************************************\
+* Functions/macros for 1-D sequence class.
+\******************************************************************************/
+
+#define	jas_seq_create(start, end) \
+	(jas_seq2d_create(start, 0, end, 1))
+
+#define	jas_seq_destroy(seq) \
+	(jas_seq2d_destroy(seq))
+
+#define jas_seq_set(seq, i, v) \
+	((seq)->rows_[0][(i) - (seq)->xstart_] = (v))
+#define	jas_seq_getref(seq, i) \
+	(&(seq)->rows_[0][(i) - (seq)->xstart_])
+#define	jas_seq_get(seq, i) \
+	((seq)->rows_[0][(i) - (seq)->xstart_])
+#define	jas_seq_start(seq) \
+	((seq)->xstart_)
+#define	jas_seq_end(seq) \
+	((seq)->xend_)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/include/jasper/jas_stream.h b/converter/other/jpeg2000/libjasper/include/jasper/jas_stream.h
new file mode 100644
index 00000000..1062fe1d
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/include/jasper/jas_stream.h
@@ -0,0 +1,498 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * I/O Stream Class
+ *
+ * $Id$
+ */
+
+#ifndef JAS_STREAM_H
+#define JAS_STREAM_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#if defined(HAVE_UNISTD_H)
+#include <unistd.h>
+#endif
+#include <jasper/jas_types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/******************************************************************************\
+* Constants.
+\******************************************************************************/
+
+/* On most UNIX systems, we probably need to define O_BINARY ourselves. */
+#ifndef O_BINARY
+#define O_BINARY	0
+#endif
+
+/*
+ * Stream open flags.
+ */
+
+/* The stream was opened for reading. */
+#define JAS_STREAM_READ	0x0001
+/* The stream was opened for writing. */
+#define JAS_STREAM_WRITE	0x0002
+/* The stream was opened for appending. */
+#define JAS_STREAM_APPEND	0x0004
+/* The stream was opened in binary mode. */
+#define JAS_STREAM_BINARY	0x0008
+/* The stream should be created/truncated. */
+#define JAS_STREAM_CREATE	0x0010
+
+#define JAS_STREAM_NOCLOSE	0x0020
+
+/*
+ * Stream buffering flags.
+ */
+
+/* The stream is unbuffered. */
+#define JAS_STREAM_UNBUF	0x0000
+/* The stream is line buffered. */
+#define JAS_STREAM_LINEBUF	0x0001
+/* The stream is fully buffered. */
+#define JAS_STREAM_FULLBUF	0x0002
+/* The buffering mode mask. */
+#define	JAS_STREAM_BUFMODEMASK	0x000f
+
+/* The memory associated with the buffer needs to be deallocated when the
+  stream is destroyed. */
+#define JAS_STREAM_FREEBUF	0x0008
+/* The buffer is currently being used for reading. */
+#define JAS_STREAM_RDBUF	0x0010
+/* The buffer is currently being used for writing. */
+#define JAS_STREAM_WRBUF	0x0020
+
+/*
+ * Stream error flags.
+ */
+
+/* The end-of-file has been encountered (on reading). */
+#define JAS_STREAM_EOF	0x0001
+/* An I/O error has been encountered on the stream. */
+#define JAS_STREAM_ERR	0x0002
+/* The read/write limit has been exceeded. */
+#define	JAS_STREAM_RWLIMIT	0x0004
+/* The error mask. */
+#define JAS_STREAM_ERRMASK \
+	(JAS_STREAM_EOF | JAS_STREAM_ERR | JAS_STREAM_RWLIMIT)
+
+/*
+ * Other miscellaneous constants.
+ */
+
+/* The default buffer size (for fully-buffered operation). */
+#define JAS_STREAM_BUFSIZE	8192
+/* The default permission mask for file creation. */
+#define JAS_STREAM_PERMS	0666
+
+/* The maximum number of characters that can always be put back on a stream. */
+#define	JAS_STREAM_MAXPUTBACK	16
+
+/******************************************************************************\
+* Types.
+\******************************************************************************/
+
+/*
+ * Generic file object.
+ */
+
+typedef void jas_stream_obj_t;
+
+/*
+ * Generic file object operations.
+ */
+
+typedef struct {
+
+	/* Read characters from a file object. */
+	int (*read_)(jas_stream_obj_t *obj, char *buf, int cnt);
+
+	/* Write characters to a file object. */
+	int (*write_)(jas_stream_obj_t *obj, char *buf, int cnt);
+
+	/* Set the position for a file object. */
+	long (*seek_)(jas_stream_obj_t *obj, long offset, int origin);
+
+	/* Close a file object. */
+	int (*close_)(jas_stream_obj_t *obj);
+
+} jas_stream_ops_t;
+
+/*
+ * Stream object.
+ */
+
+typedef struct {
+
+	/* The mode in which the stream was opened. */
+	int openmode_;
+
+	/* The buffering mode. */
+	int bufmode_;
+
+	/* The stream status. */
+	int flags_;
+
+	/* The start of the buffer area to use for reading/writing. */
+	unsigned char *bufbase_;
+
+	/* The start of the buffer area excluding the extra initial space for
+	  character putback. */
+	unsigned char *bufstart_;
+
+	/* The buffer size. */
+	int bufsize_;
+
+	/* The current position in the buffer. */
+	unsigned char *ptr_;
+
+	/* The number of characters that must be read/written before
+	the buffer needs to be filled/flushed. */
+	int cnt_;
+
+	/* A trivial buffer to be used for unbuffered operation. */
+	unsigned char tinybuf_[JAS_STREAM_MAXPUTBACK + 1];
+
+	/* The operations for the underlying stream file object. */
+	jas_stream_ops_t *ops_;
+
+	/* The underlying stream file object. */
+	jas_stream_obj_t *obj_;
+
+	/* The number of characters read/written. */
+	long rwcnt_;
+
+	/* The maximum number of characters that may be read/written. */
+	long rwlimit_;
+
+} jas_stream_t;
+
+/*
+ * Regular file object.
+ */
+
+/* Note: This is simply a file descriptor. */
+typedef int jas_stream_fileobj_t;
+
+/*
+ * Memory file object.
+ */
+
+typedef struct {
+
+	/* The data associated with this file. */
+	unsigned char *buf_;
+
+	/* The allocated size of the buffer for holding file data. */
+	int bufsize_;
+
+	/* The length of the file. */
+	int_fast32_t len_;
+
+	/* The seek position. */
+	int_fast32_t pos_;
+
+	/* Is the buffer growable? */
+	int growable_;
+
+	/* Was the buffer allocated internally? */
+	int myalloc_;
+
+} jas_stream_memobj_t;
+
+/******************************************************************************\
+* Macros/functions for opening and closing streams.
+\******************************************************************************/
+
+/* Open a file as a stream. */
+jas_stream_t *jas_stream_fopen(const char *filename, const char *mode);
+
+/* Open a memory buffer as a stream. */
+jas_stream_t *jas_stream_memopen(char *buf, int bufsize);
+
+/* Open a file descriptor as a stream. */
+jas_stream_t *jas_stream_fdopen(int fd, const char *mode);
+
+/* Open a stdio stream as a stream. */
+jas_stream_t *jas_stream_freopen(const char *path, const char *mode, FILE *fp);
+
+/* Open a temporary file as a stream. */
+jas_stream_t *jas_stream_tmpfile(void);
+
+/* Close a stream. */
+int jas_stream_close(jas_stream_t *stream);
+
+/******************************************************************************\
+* Macros/functions for getting/setting the stream state.
+\******************************************************************************/
+
+/* Get the EOF indicator for a stream. */
+#define jas_stream_eof(stream) \
+	(((stream)->flags_ & JAS_STREAM_EOF) != 0)
+
+/* Get the error indicator for a stream. */
+#define jas_stream_error(stream) \
+	(((stream)->flags_ & JAS_STREAM_ERR) != 0)
+
+/* Clear the error indicator for a stream. */
+#define jas_stream_clearerr(stream) \
+	((stream)->flags_ &= ~(JAS_STREAM_ERR | JAS_STREAM_EOF))
+
+/* Get the read/write limit for a stream. */
+#define	jas_stream_getrwlimit(stream) \
+	(((const jas_stream_t *)(stream))->rwlimit_)
+
+/* Set the read/write limit for a stream. */
+int jas_stream_setrwlimit(jas_stream_t *stream, long rwlimit);
+
+/* Get the read/write count for a stream. */
+#define	jas_stream_getrwcount(stream) \
+	(((const jas_stream_t *)(stream))->rwcnt_)
+
+/* Set the read/write count for a stream. */
+long jas_stream_setrwcount(jas_stream_t *stream, long rwcnt);
+
+/******************************************************************************\
+* Macros/functions for I/O.
+\******************************************************************************/
+
+/* Read a character from a stream. */
+#if defined(DEBUG)
+#define	jas_stream_getc(stream)	jas_stream_getc_func(stream)
+#else
+#define jas_stream_getc(stream)	jas_stream_getc_macro(stream)
+#endif
+
+/* Write a character to a stream. */
+#if defined(DEBUG)
+#define jas_stream_putc(stream, c)	jas_stream_putc_func(stream, c)
+#else
+#define jas_stream_putc(stream, c)	jas_stream_putc_macro(stream, c)
+#endif
+
+/* Read characters from a stream into a buffer. */
+int jas_stream_read(jas_stream_t *stream, void *buf, int cnt);
+
+/* Write characters from a buffer to a stream. */
+int jas_stream_write(jas_stream_t *stream, const void *buf, int cnt);
+
+/* Write formatted output to a stream. */
+int jas_stream_printf(jas_stream_t *stream, const char *fmt, ...);
+
+/* Write a string to a stream. */
+int jas_stream_puts(jas_stream_t *stream, const char *s);
+
+/* Read a line of input from a stream. */
+char *jas_stream_gets(jas_stream_t *stream, char *buf, int bufsize);
+
+/* Look at the next character to be read from a stream without actually
+  removing it from the stream. */
+#define	jas_stream_peekc(stream) \
+	(((stream)->cnt_ <= 0) ? jas_stream_fillbuf(stream, 0) : \
+	  ((int)(*(stream)->ptr_)))
+
+/* Put a character back on a stream. */
+int jas_stream_ungetc(jas_stream_t *stream, int c);
+
+/******************************************************************************\
+* Macros/functions for getting/setting the stream position.
+\******************************************************************************/
+
+/* Is it possible to seek on this stream? */
+int jas_stream_isseekable(jas_stream_t *stream);
+
+/* Set the current position within the stream. */
+long jas_stream_seek(jas_stream_t *stream, long offset, int origin);
+
+/* Get the current position within the stream. */
+long jas_stream_tell(jas_stream_t *stream);
+
+/* Seek to the beginning of a stream. */
+int jas_stream_rewind(jas_stream_t *stream);
+
+/******************************************************************************\
+* Macros/functions for flushing.
+\******************************************************************************/
+
+/* Flush any pending output to a stream. */
+int jas_stream_flush(jas_stream_t *stream);
+
+/******************************************************************************\
+* Miscellaneous macros/functions.
+\******************************************************************************/
+
+/* Copy data from one stream to another. */
+int jas_stream_copy(jas_stream_t *dst, jas_stream_t *src, int n);
+
+/* Display stream contents (for debugging purposes). */
+int jas_stream_display(jas_stream_t *stream, FILE *fp, int n);
+
+/* Consume (i.e., discard) characters from stream. */
+int jas_stream_gobble(jas_stream_t *stream, int n);
+
+/* Get the size of the file associated with the specified stream.
+  The specified stream must be seekable. */
+long jas_stream_length(jas_stream_t *stream);
+
+/******************************************************************************\
+* Internal functions.
+\******************************************************************************/
+
+/* The following functions are for internal use only!  If you call them
+directly, you will die a horrible, miserable, and painful death! */
+
+/* Read a character from a stream. */
+#define jas_stream_getc_macro(stream) \
+	((!((stream)->flags_ & (JAS_STREAM_ERR | JAS_STREAM_EOF | \
+	  JAS_STREAM_RWLIMIT))) ? \
+	  (((stream)->rwlimit_ >= 0 && (stream)->rwcnt_ >= (stream)->rwlimit_) ? \
+	  (stream->flags_ |= JAS_STREAM_RWLIMIT, EOF) : \
+	  jas_stream_getc2(stream)) : EOF)
+#define jas_stream_getc2(stream) \
+	((--(stream)->cnt_ < 0) ? jas_stream_fillbuf(stream, 1) : \
+	  (++(stream)->rwcnt_, (int)(*(stream)->ptr_++)))
+
+/* Write a character to a stream. */
+#define jas_stream_putc_macro(stream, c) \
+	((!((stream)->flags_ & (JAS_STREAM_ERR | JAS_STREAM_EOF | \
+	  JAS_STREAM_RWLIMIT))) ? \
+	  (((stream)->rwlimit_ >= 0 && (stream)->rwcnt_ >= (stream)->rwlimit_) ? \
+	  (stream->flags_ |= JAS_STREAM_RWLIMIT, EOF) : \
+	  jas_stream_putc2(stream, c)) : EOF)
+#define jas_stream_putc2(stream, c) \
+	(((stream)->bufmode_ |= JAS_STREAM_WRBUF, --(stream)->cnt_ < 0) ? \
+	  jas_stream_flushbuf((stream), (unsigned char)(c)) : \
+	  (++(stream)->rwcnt_, (int)(*(stream)->ptr_++ = (c))))
+
+/* These prototypes need to be here for the sake of the stream_getc and
+stream_putc macros. */
+int jas_stream_fillbuf(jas_stream_t *stream, int getflag);
+int jas_stream_flushbuf(jas_stream_t *stream, int c);
+int jas_stream_getc_func(jas_stream_t *stream);
+int jas_stream_putc_func(jas_stream_t *stream, int c);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/include/jasper/jas_string.h b/converter/other/jpeg2000/libjasper/include/jasper/jas_string.h
new file mode 100644
index 00000000..55b611ba
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/include/jasper/jas_string.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * String Library
+ *
+ * $Id$
+ */
+
+#ifndef	JAS_STRING_H
+#define	JAS_STRING_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/******************************************************************************\
+* Functions.
+\******************************************************************************/
+
+/* Copy a string (a la strdup). */
+char *jas_strdup(const char *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/include/jasper/jas_tvp.h b/converter/other/jpeg2000/libjasper/include/jasper/jas_tvp.h
new file mode 100644
index 00000000..9ac50dbd
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/include/jasper/jas_tvp.h
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Tag/Value Parser
+ *
+ * $Id$
+ */
+
+#ifndef JAS_TVP_H
+#define JAS_TVP_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/******************************************************************************\
+* Types.
+\******************************************************************************/
+
+/* Tag information type. */
+
+typedef struct {
+
+	int id;
+	/* The ID for the tag. */
+
+	const char *name;
+	/* The name of the tag. */
+
+} jas_taginfo_t;
+
+/* Tag-value parser type. */
+
+typedef struct {
+
+	char *buf;
+	/* The parsing buffer. */
+
+	char *tag;
+	/* The current tag name. */
+
+	const char *val;
+	/* The current value. */
+
+	char *pos;
+	/* The current position in the parsing buffer. */
+
+} jas_tvparser_t;
+
+/******************************************************************************\
+* Tag information functions.
+\******************************************************************************/
+
+/* Lookup a tag by name. */
+jas_taginfo_t *jas_taginfos_lookup(jas_taginfo_t *taginfos, const char *name);
+
+/* This function returns a pointer to the specified taginfo object if it
+  exists (i.e., the pointer is nonnull); otherwise, a pointer to a dummy
+  object is returned.  This is useful in some situations to avoid checking
+  for a null pointer. */
+jas_taginfo_t *jas_taginfo_nonull(jas_taginfo_t *taginfo);
+
+/******************************************************************************\
+* Tag-value parser functions.
+\******************************************************************************/
+
+/* Create a tag-value parser for the specified string. */
+jas_tvparser_t *jas_tvparser_create(const char *s);
+
+/* Destroy a tag-value parser. */
+void jas_tvparser_destroy(jas_tvparser_t *tvparser);
+
+/* Get the next tag-value pair. */
+int jas_tvparser_next(jas_tvparser_t *tvparser);
+
+/* Get the tag name for the current tag-value pair. */
+char *jas_tvparser_gettag(jas_tvparser_t *tvparser);
+
+/* Get the value for the current tag-value pair. */
+const char *jas_tvparser_getval(jas_tvparser_t *tvparser);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/include/jasper/jas_types.h b/converter/other/jpeg2000/libjasper/include/jasper/jas_types.h
new file mode 100644
index 00000000..4d3a4988
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/include/jasper/jas_types.h
@@ -0,0 +1,14 @@
+/* In the original Jasper library, this contains definitions of the int_fast32,
+   etc. types and bool.
+
+   In Netpbm, we do that with pm_config.h, and the original Jasper
+   method doesn't work.
+*/
+#include "pm_config.h"
+
+
+/* The below macro is intended to be used for type casts.  By using this
+  macro, type casts can be easily located in the source code with
+  tools like "grep". */
+#define	JAS_CAST(t, e) \
+	((t) (e))
diff --git a/converter/other/jpeg2000/libjasper/include/jasper/jas_types.h.orig b/converter/other/jpeg2000/libjasper/include/jasper/jas_types.h.orig
new file mode 100644
index 00000000..10c1152d
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/include/jasper/jas_types.h.orig
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Primitive Types
+ *
+ * $Id$
+ */
+
+#ifndef JAS_TYPES_H
+#define JAS_TYPES_H
+
+#if defined(HAVE_STDLIB_H)
+#include <stdlib.h>
+#endif
+#if defined(HAVE_STDDEF_H)
+#include <stddef.h>
+#endif
+#if defined(HAVE_SYS_TYPES_H)
+#include <sys/types.h>
+#endif
+
+#if defined(HAVE_STDBOOL_H)
+/*
+ * The C language implementation does correctly provide the standard header
+ * file "stdbool.h".
+ */
+#include <stdbool.h>
+#else
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * The C language implementation does not provide the standard header file
+ * "stdbool.h" as required by ISO/IEC 9899:1999.  Try to compensate for this
+ * braindamage below.
+ */
+#if !defined(bool)
+#define	bool	int
+#endif
+#if !defined(true)
+#define true	1
+#endif
+#if !defined(false)
+#define	false	0
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+/* pm_config.h defines the FAST integer types if possible (typically by
+   including <inttypes.h>.  If not, the following try to take care
+   of it.
+*/
+/**********/
+#if !defined(INT_FAST8_MAX)
+typedef signed char int_fast8_t;
+#define INT_FAST8_MIN	(-127)
+#define INT_FAST8_MAX	128
+#endif
+/**********/
+#if !defined(UINT_FAST8_MAX)
+typedef unsigned char uint_fast8_t;
+#define UINT_FAST8_MAX	255
+#endif
+/**********/
+#if !defined(INT_FAST16_MAX)
+typedef short int_fast16_t;
+#define INT_FAST16_MIN	SHRT_MIN
+#define INT_FAST16_MAX	SHRT_MAX
+#endif
+/**********/
+#if !defined(UINT_FAST16_MAX)
+typedef unsigned short uint_fast16_t;
+#define UINT_FAST16_MAX	USHRT_MAX
+#endif
+/**********/
+#if !defined(INT_FAST32_MAX)
+typedef int int_fast32_t;
+#define INT_FAST32_MIN	INT_MIN
+#define INT_FAST32_MAX	INT_MAX
+#endif
+/**********/
+#if !defined(UINT_FAST32_MAX)
+typedef unsigned int uint_fast32_t;
+#define UINT_FAST32_MAX	UINT_MAX
+#endif
+/**********/
+#if !defined(INT_FAST64_MAX)
+typedef int int_fast64_t;
+#define INT_FAST64_MIN	LLONG_MIN
+#define INT_FAST64_MAX	LLONG_MAX
+#endif
+/**********/
+#if !defined(UINT_FAST64_MAX)
+typedef unsigned int uint_fast64_t;
+#define UINT_FAST64_MAX	ULLONG_MAX
+#endif
+/**********/
+#endif
+
+/* The below macro is intended to be used for type casts.  By using this
+  macro, type casts can be easily located in the source code with
+  tools like "grep". */
+#define	JAS_CAST(t, e) \
+	((t) (e))
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/include/jasper/jas_version.h b/converter/other/jpeg2000/libjasper/include/jasper/jas_version.h
new file mode 100644
index 00000000..2178257c
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/include/jasper/jas_version.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef JAS_VERSION_H
+#define JAS_VERSION_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/******************************************************************************\
+* Constants and types.
+\******************************************************************************/
+
+#if !defined(JAS_VERSION)
+/* The version information below should match that specified in
+  the "configure.in" file! */
+#define	JAS_VERSION		"1.600.0"
+#endif
+
+#define	JAS_COPYRIGHT \
+	"Copyright (c) 1999-2000 Image Power, Inc. and the University of\n" \
+	"  British Columbia.\n" \
+	"Copyright (c) 2001-2002 Michael David Adams.\n" \
+	"All rights reserved.\n"
+
+#define	JAS_NOTES \
+	"For more information about this software, please visit the following\n" \
+	"web sites/pages:\n" \
+	"    http://www.ece.ubc.ca/~mdadams/jasper\n" \
+	"    http://www.jpeg.org/software\n" \
+	"To be added to the (moderated) JasPer software announcements\n" \
+	"mailing list, send an email to:\n" \
+	"    jasper-announce-subscribe@yahoogroups.com\n" \
+	"To be added to the (unmoderated) JasPer software discussion\n" \
+	"mailing list, send an email to:\n" \
+	"    jasper-discussion-subscribe@yahoogroups.com\n" \
+	"Please send any bug reports to:\n" \
+	"    mdadams@ieee.org\n"
+
+/******************************************************************************\
+* Functions.
+\******************************************************************************/
+
+const char *jas_getversion(void);
+/* Get the version information for the JasPer library. */
+/* Note:  Since libjasper can be built as a shared library, the version
+  returned by this function may not necessarily correspond to JAS_VERSION. */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/include/jasper/jasper.h b/converter/other/jpeg2000/libjasper/include/jasper/jasper.h
new file mode 100644
index 00000000..b003acfa
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/include/jasper/jasper.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+#ifndef JAS_JASPER_H
+#define JAS_JASPER_H
+
+#include <jasper/jas_types.h>
+#include <jasper/jas_version.h>
+
+#include <jasper/jas_init.h>
+#include <jasper/jas_fix.h>
+#include <jasper/jas_debug.h>
+#include <jasper/jas_getopt.h>
+#include <jasper/jas_image.h>
+#include <jasper/jas_math.h>
+#include <jasper/jas_malloc.h>
+#include <jasper/jas_seq.h>
+#include <jasper/jas_stream.h>
+#include <jasper/jas_string.h>
+#include <jasper/jas_tvp.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jp2/Makefile b/converter/other/jpeg2000/libjasper/jp2/Makefile
new file mode 100644
index 00000000..254b7f56
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jp2/Makefile
@@ -0,0 +1,19 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../../../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/other/jpeg2000/libjasper/jp2
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+JASPERSRCDIR=$(SRCDIR)/$(SUBDIR)/..
+
+include $(BUILDDIR)/Makefile.config
+
+LIB_OBJECTS = jp2_cod.o jp2_dec.o jp2_enc.o
+
+MERGE_OBJECTS =
+
+all: partlist $(LIB_OBJECTS)
+
+include $(JASPERSRCDIR)/Makefile.common
+
diff --git a/converter/other/jpeg2000/libjasper/jp2/jp2_cod.c b/converter/other/jpeg2000/libjasper/jp2/jp2_cod.c
new file mode 100644
index 00000000..4769c408
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jp2/jp2_cod.c
@@ -0,0 +1,928 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * JP2 Library
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "pm_c_util.h"
+
+#include "jasper/jas_stream.h"
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_debug.h"
+
+#include "jp2_cod.h"
+
+/******************************************************************************\
+* Function prototypes.
+\******************************************************************************/
+
+#define	ONES(n)	((1 << (n)) - 1)
+
+jp2_boxinfo_t *jp2_boxinfolookup(int type);
+
+static int jp2_getuint8(jas_stream_t *in, uint_fast8_t *val);
+static int jp2_getuint16(jas_stream_t *in, uint_fast16_t *val);
+static int jp2_getuint32(jas_stream_t *in, uint_fast32_t *val);
+static int jp2_getuint64(jas_stream_t *in, uint_fast64_t *val);
+static int jp2_putuint8(jas_stream_t *out, uint_fast8_t val);
+static int jp2_putuint16(jas_stream_t *out, uint_fast16_t val);
+static int jp2_putuint32(jas_stream_t *out, uint_fast32_t val);
+
+static int jp2_getint(jas_stream_t *in, int s, int n, int_fast32_t *val);
+
+jp2_box_t *jp2_box_get(jas_stream_t *in);
+void jp2_box_dump(jp2_box_t *box, FILE *out);
+
+static int jp2_jp_getdata(jp2_box_t *box, jas_stream_t *in);
+static int jp2_jp_putdata(jp2_box_t *box, jas_stream_t *out);
+static int jp2_ftyp_getdata(jp2_box_t *box, jas_stream_t *in);
+static int jp2_ftyp_putdata(jp2_box_t *box, jas_stream_t *out);
+static int jp2_ihdr_getdata(jp2_box_t *box, jas_stream_t *in);
+static int jp2_ihdr_putdata(jp2_box_t *box, jas_stream_t *out);
+static void jp2_bpcc_destroy(jp2_box_t *box);
+static int jp2_bpcc_getdata(jp2_box_t *box, jas_stream_t *in);
+static int jp2_bpcc_putdata(jp2_box_t *box, jas_stream_t *out);
+static int jp2_colr_getdata(jp2_box_t *box, jas_stream_t *in);
+static int jp2_colr_putdata(jp2_box_t *box, jas_stream_t *out);
+static void jp2_colr_dumpdata(jp2_box_t *box, FILE *out);
+static void jp2_colr_destroy(jp2_box_t *box);
+static void jp2_cdef_destroy(jp2_box_t *box);
+static int jp2_cdef_getdata(jp2_box_t *box, jas_stream_t *in);
+static int jp2_cdef_putdata(jp2_box_t *box, jas_stream_t *out);
+static void jp2_cdef_dumpdata(jp2_box_t *box, FILE *out);
+static void jp2_cmap_destroy(jp2_box_t *box);
+static int jp2_cmap_getdata(jp2_box_t *box, jas_stream_t *in);
+static int jp2_cmap_putdata(jp2_box_t *box, jas_stream_t *out);
+static void jp2_cmap_dumpdata(jp2_box_t *box, FILE *out);
+static void jp2_pclr_destroy(jp2_box_t *box);
+static int jp2_pclr_getdata(jp2_box_t *box, jas_stream_t *in);
+static int jp2_pclr_putdata(jp2_box_t *box, jas_stream_t *out);
+static void jp2_pclr_dumpdata(jp2_box_t *box, FILE *out);
+
+/******************************************************************************\
+* Local data.
+\******************************************************************************/
+
+jp2_boxinfo_t jp2_boxinfos[] = {
+	{JP2_BOX_JP, "JP", 0,
+	  {0, 0, jp2_jp_getdata, jp2_jp_putdata, 0}},
+	{JP2_BOX_FTYP, "FTYP", 0,
+	  {0, 0, jp2_ftyp_getdata, jp2_ftyp_putdata, 0}},
+	{JP2_BOX_JP2H, "JP2H", JP2_BOX_SUPER,
+	  {0, 0, 0, 0, 0}},
+	{JP2_BOX_IHDR, "IHDR", 0,
+	  {0, 0, jp2_ihdr_getdata, jp2_ihdr_putdata, 0}},
+	{JP2_BOX_BPCC, "BPCC", 0,
+	  {0, jp2_bpcc_destroy, jp2_bpcc_getdata, jp2_bpcc_putdata, 0}},
+	{JP2_BOX_COLR, "COLR", 0,
+	  {0, jp2_colr_destroy, jp2_colr_getdata, jp2_colr_putdata, jp2_colr_dumpdata}},
+	{JP2_BOX_PCLR, "PCLR", 0,
+	  {0, jp2_pclr_destroy, jp2_pclr_getdata, jp2_pclr_putdata, jp2_pclr_dumpdata}},
+	{JP2_BOX_CMAP, "CMAP", 0,
+	  {0, jp2_cmap_destroy, jp2_cmap_getdata, jp2_cmap_putdata, jp2_cmap_dumpdata}},
+	{JP2_BOX_CDEF, "CDEF", 0,
+	  {0, jp2_cdef_destroy, jp2_cdef_getdata, jp2_cdef_putdata, jp2_cdef_dumpdata}},
+	{JP2_BOX_RES, "RES", JP2_BOX_SUPER,
+	  {0, 0, 0, 0, 0}},
+	{JP2_BOX_RESC, "RESC", 0,
+	  {0, 0, 0, 0, 0}},
+	{JP2_BOX_RESD, "RESD", 0,
+	  {0, 0, 0, 0, 0}},
+	{JP2_BOX_JP2C, "JP2C", JP2_BOX_NODATA,
+	  {0, 0, 0, 0, 0}},
+	{JP2_BOX_JP2I, "JP2I", 0,
+	  {0, 0, 0, 0, 0}},
+	{JP2_BOX_XML, "XML", 0,
+	  {0, 0, 0, 0, 0}},
+	{JP2_BOX_UUID, "UUID", 0,
+	  {0, 0, 0, 0, 0}},
+	{JP2_BOX_UINF, "UINF", JP2_BOX_SUPER,
+	  {0, 0, 0, 0, 0}},
+	{JP2_BOX_ULST, "ULST", 0,
+	  {0, 0, 0, 0, 0}},
+	{JP2_BOX_URL, "URL", 0,
+	  {0, 0, 0, 0, 0}},
+	{0, 0, 0, {0, 0, 0, 0, 0}},
+};
+
+jp2_boxinfo_t jp2_boxinfo_unk = {
+	0, "Unknown", 0, {0, 0, 0, 0}
+};
+
+/******************************************************************************\
+* Box constructor.
+\******************************************************************************/
+
+jp2_box_t *jp2_box_create(int type)
+{
+	jp2_box_t *box;
+	jp2_boxinfo_t *boxinfo;
+
+	if (!(box = jas_malloc(sizeof(jp2_box_t)))) {
+		return 0;
+	}
+	memset(box, 0, sizeof(jp2_box_t));
+	box->type = type;
+	box->len = 0;
+	if (!(boxinfo = jp2_boxinfolookup(type))) {
+		return 0;
+	}
+	box->info = boxinfo;
+	box->ops = &boxinfo->ops;
+	return box;
+}
+
+/******************************************************************************\
+* Box destructor.
+\******************************************************************************/
+
+void jp2_box_destroy(jp2_box_t *box)
+{
+	if (box->ops->destroy) {
+		(*box->ops->destroy)(box);
+	}
+	jas_free(box);
+}
+
+static void jp2_bpcc_destroy(jp2_box_t *box)
+{
+	jp2_bpcc_t *bpcc = &box->data.bpcc;
+	if (bpcc->bpcs) {
+		jas_free(bpcc->bpcs);
+		bpcc->bpcs = 0;
+	}
+}
+
+static void jp2_cdef_destroy(jp2_box_t *box)
+{
+	jp2_cdef_t *cdef = &box->data.cdef;
+	if (cdef->ents) {
+		jas_free(cdef->ents);
+		cdef->ents = 0;
+	}
+}
+
+/******************************************************************************\
+* Box input.
+\******************************************************************************/
+
+jp2_box_t *jp2_box_get(jas_stream_t *in)
+{
+	jp2_box_t *box;
+	jp2_boxinfo_t *boxinfo;
+	jas_stream_t *tmpstream;
+	uint_fast32_t len;
+    uint_fast64_t extlen;
+	bool dataflag;
+
+	box = 0;
+	tmpstream = 0;
+
+	if (!(box = jas_malloc(sizeof(jp2_box_t)))) {
+		goto error;
+	}
+	box->ops = &jp2_boxinfo_unk.ops;
+	if (jp2_getuint32(in, &len) || jp2_getuint32(in, &box->type)) {
+		goto error;
+	}
+	boxinfo = jp2_boxinfolookup(box->type);
+	box->info = boxinfo;
+	box->ops = &boxinfo->ops;
+	box->len = len;
+	if (box->len == 1) {
+		if (jp2_getuint64(in, &extlen)) {
+			goto error;
+		}
+		box->len = extlen;
+	}
+	if (box->len != 0 && box->len < 8) {
+		goto error;
+	}
+
+	dataflag = !(box->info->flags & (JP2_BOX_SUPER | JP2_BOX_NODATA));
+
+	if (dataflag) {
+		if (!(tmpstream = jas_stream_memopen(0, 0))) {
+			goto error;
+		}
+		if (jas_stream_copy(tmpstream, in, box->len - JP2_BOX_HDRLEN)) {
+			goto error;
+		}
+		jas_stream_rewind(tmpstream);
+
+		if (box->ops->getdata) {
+			if ((*box->ops->getdata)(box, tmpstream)) {
+				goto error;
+			}
+		}
+		jas_stream_close(tmpstream);
+	}
+
+	jp2_box_dump(box, stderr);
+
+	return box;
+	abort();
+
+error:
+	if (box) {
+		jp2_box_destroy(box);
+	}
+	if (tmpstream) {
+		jas_stream_close(tmpstream);
+	}
+	return 0;
+}
+
+void jp2_box_dump(jp2_box_t *box, FILE *out)
+{
+	jp2_boxinfo_t *boxinfo;
+	boxinfo = jp2_boxinfolookup(box->type);
+	assert(boxinfo);
+
+	fprintf(out, "JP2 box: ");
+	fprintf(out, "type=%c%s%c (0x%08x); length=%d\n", '"', boxinfo->name,
+	  '"', box->type, box->len);
+	if (box->ops->dumpdata) {
+		(*box->ops->dumpdata)(box, out);
+	}
+}
+
+static int jp2_jp_getdata(jp2_box_t *box, jas_stream_t *in)
+{
+	jp2_jp_t *jp = &box->data.jp;
+	if (jp2_getuint32(in, &jp->magic)) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jp2_ftyp_getdata(jp2_box_t *box, jas_stream_t *in)
+{
+	jp2_ftyp_t *ftyp = &box->data.ftyp;
+	int i;
+	if (jp2_getuint32(in, &ftyp->majver) || jp2_getuint32(in, &ftyp->minver)) {
+		return -1;
+	}
+	ftyp->numcompatcodes = ((box->len - JP2_BOX_HDRLEN) - 8) / 4;
+	if (ftyp->numcompatcodes > JP2_FTYP_MAXCOMPATCODES) {
+		return -1;
+	}
+	for (i = 0; i < ftyp->numcompatcodes; ++i) {
+		if (jp2_getuint32(in, &ftyp->compatcodes[i])) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static int jp2_ihdr_getdata(jp2_box_t *box, jas_stream_t *in)
+{
+	jp2_ihdr_t *ihdr = &box->data.ihdr;
+	if (jp2_getuint32(in, &ihdr->height) || jp2_getuint32(in, &ihdr->width) ||
+	  jp2_getuint16(in, &ihdr->numcmpts) || jp2_getuint8(in, &ihdr->bpc) ||
+	  jp2_getuint8(in, &ihdr->comptype) || jp2_getuint8(in, &ihdr->csunk) ||
+	  jp2_getuint8(in, &ihdr->ipr)) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jp2_bpcc_getdata(jp2_box_t *box, jas_stream_t *in)
+{
+	jp2_bpcc_t *bpcc = &box->data.bpcc;
+	int i;
+	bpcc->numcmpts = box->len - JP2_BOX_HDRLEN;
+	if (!(bpcc->bpcs = jas_malloc(bpcc->numcmpts * sizeof(uint_fast8_t)))) {
+		return -1;
+	}
+	for (i = 0; i < bpcc->numcmpts; ++i) {
+		if (jp2_getuint8(in, &bpcc->bpcs[i])) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static void jp2_colr_dumpdata(jp2_box_t *box, FILE *out)
+{
+	jp2_colr_t *colr = &box->data.colr;
+	fprintf(out, "method=%d; pri=%d; approx=%d\n", (int)colr->method, (int)colr->pri, (int)colr->approx);
+	switch (colr->method) {
+	case JP2_COLR_ENUM:
+		fprintf(out, "csid=%d\n", (int)colr->csid);
+		break;
+	case JP2_COLR_ICC:
+		jas_memdump(out, colr->iccp, colr->iccplen);
+		break;
+	}
+}
+
+static int jp2_colr_getdata(jp2_box_t *box, jas_stream_t *in)
+{
+	jp2_colr_t *colr = &box->data.colr;
+	colr->csid = 0;
+	colr->iccp = 0;
+	colr->iccplen = 0;
+
+	if (jp2_getuint8(in, &colr->method) || jp2_getuint8(in, &colr->pri) ||
+	  jp2_getuint8(in, &colr->approx)) {
+		return -1;
+	}
+	switch (colr->method) {
+	case JP2_COLR_ENUM:
+		if (jp2_getuint32(in, &colr->csid)) {
+			return -1;
+		}
+		break;
+	case JP2_COLR_ICC:
+		colr->iccplen = box->len - JP2_BOX_HDRLEN - 3;
+		if (!(colr->iccp = jas_malloc(colr->iccplen * sizeof(uint_fast8_t)))) {
+			return -1;
+		}
+		if (jas_stream_read(in, colr->iccp, colr->iccplen) != colr->iccplen) {
+			return -1;
+		}
+		break;
+	}
+	return 0;
+}
+
+static void jp2_cdef_dumpdata(jp2_box_t *box, FILE *out)
+{
+	jp2_cdef_t *cdef = &box->data.cdef;
+	int i;
+	for (i = 0; i < cdef->numchans; ++i) {
+		fprintf(out, "channo=%d; type=%d; assoc=%d\n",
+		  cdef->ents[i].channo, cdef->ents[i].type, cdef->ents[i].assoc);
+	}
+}
+
+static void jp2_colr_destroy(jp2_box_t *box)
+{
+	jp2_colr_t *colr = &box->data.colr;
+	if (colr->iccp) {
+		free(colr->iccp);
+	}
+}
+
+static int jp2_cdef_getdata(jp2_box_t *box, jas_stream_t *in)
+{
+	jp2_cdef_t *cdef = &box->data.cdef;
+	jp2_cdefchan_t *chan;
+	int channo;
+	if (jp2_getuint16(in, &cdef->numchans)) {
+		return -1;
+	}
+	if (!(cdef->ents = jas_malloc(cdef->numchans * sizeof(jp2_cdefchan_t)))) {
+		return -1;
+	}
+	for (channo = 0; channo < cdef->numchans; ++channo) {
+		chan = &cdef->ents[channo];
+		if (jp2_getuint16(in, &chan->channo) || jp2_getuint16(in, &chan->type) ||
+		  jp2_getuint16(in, &chan->assoc)) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* Box output.
+\******************************************************************************/
+
+int jp2_box_put(jp2_box_t *box, jas_stream_t *out)
+{
+	jas_stream_t *tmpstream;
+	bool dataflag;
+
+	tmpstream = 0;
+
+	dataflag = !(box->info->flags & (JP2_BOX_SUPER | JP2_BOX_NODATA));
+
+	if (dataflag) {
+		tmpstream = jas_stream_memopen(0, 0);
+		if (box->ops->putdata) {
+			if ((*box->ops->putdata)(box, tmpstream)) {
+				goto error;
+			}
+		}
+		box->len = jas_stream_tell(tmpstream) + JP2_BOX_HDRLEN;
+		jas_stream_rewind(tmpstream);
+	}
+    /* There was code here in official Jasper to handle 64 bit lengths,
+       but it was based on determining whether box->len fits in 32 bits.
+       But box->len is a 32 bit data type, so it fits in 32 bits by
+       definition.  Either this encoder is incapable of making boxes with
+       lengths that don't fit in 32 bits, or it passes invalid values of
+       box->len to us.  We assume the former because it's easier.
+    */
+	if (jp2_putuint32(out, box->len)) {
+		goto error;
+	}
+	if (jp2_putuint32(out, box->type)) {
+		goto error;
+	}
+
+	if (dataflag) {
+		if (jas_stream_copy(out, tmpstream, box->len - JP2_BOX_HDRLEN)) {
+			goto error;
+		}
+		jas_stream_close(tmpstream);
+	}
+
+	return 0;
+	abort();
+
+error:
+
+	if (tmpstream) {
+		jas_stream_close(tmpstream);
+	}
+	return -1;
+}
+
+static int jp2_jp_putdata(jp2_box_t *box, jas_stream_t *out)
+{
+	jp2_jp_t *jp = &box->data.jp;
+	if (jp2_putuint32(out, jp->magic)) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jp2_ftyp_putdata(jp2_box_t *box, jas_stream_t *out)
+{
+	jp2_ftyp_t *ftyp = &box->data.ftyp;
+	int i;
+	if (jp2_putuint32(out, ftyp->majver) || jp2_putuint32(out, ftyp->minver)) {
+		return -1;
+	}
+	for (i = 0; i < ftyp->numcompatcodes; ++i) {
+		if (jp2_putuint32(out, ftyp->compatcodes[i])) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static int jp2_ihdr_putdata(jp2_box_t *box, jas_stream_t *out)
+{
+	jp2_ihdr_t *ihdr = &box->data.ihdr;
+	if (jp2_putuint32(out, ihdr->height) || jp2_putuint32(out, ihdr->width) ||
+	  jp2_putuint16(out, ihdr->numcmpts) || jp2_putuint8(out, ihdr->bpc) ||
+	  jp2_putuint8(out, ihdr->comptype) || jp2_putuint8(out, ihdr->csunk) ||
+	  jp2_putuint8(out, ihdr->ipr)) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jp2_bpcc_putdata(jp2_box_t *box, jas_stream_t *out)
+{
+	jp2_bpcc_t *bpcc = &box->data.bpcc;
+	int i;
+	for (i = 0; i < bpcc->numcmpts; ++i) {
+		if (jp2_putuint8(out, bpcc->bpcs[i])) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static int jp2_colr_putdata(jp2_box_t *box, jas_stream_t *out)
+{
+	jp2_colr_t *colr = &box->data.colr;
+	if (jp2_putuint8(out, colr->method) || jp2_putuint8(out, colr->pri) ||
+	  jp2_putuint8(out, colr->approx)) {
+		return -1;
+	}
+	switch (colr->method) {
+	case JP2_COLR_ENUM:
+		if (jp2_putuint32(out, colr->csid)) {
+			return -1;
+		}
+		break;
+	case JP2_COLR_ICC:
+		/* XXX - not implemented */
+		abort();
+		break;
+	}
+	return 0;
+}
+
+static int jp2_cdef_putdata(jp2_box_t *box, jas_stream_t *out)
+{
+	jp2_cdef_t *cdef = &box->data.cdef;
+	int i;
+	jp2_cdefchan_t *ent;
+
+	if (jp2_putuint16(out, cdef->numchans)) {
+		return -1;
+	}
+
+	for (i = 0; i < cdef->numchans; ++i) {
+		ent = &cdef->ents[i];
+		if (jp2_putuint16(out, ent->channo) ||
+		  jp2_putuint16(out, ent->type) ||
+		  jp2_putuint16(out, ent->assoc)) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* Input operations for primitive types.
+\******************************************************************************/
+
+static int jp2_getuint8(jas_stream_t *in, uint_fast8_t *val)
+{
+	int c;
+	if ((c = jas_stream_getc(in)) == EOF) {
+		return -1;
+	}
+	if (val) {
+		*val = c;
+	}
+	return 0;
+}
+
+static int jp2_getuint16(jas_stream_t *in, uint_fast16_t *val)
+{
+	uint_fast16_t v;
+	int c;
+	if ((c = jas_stream_getc(in)) == EOF) {
+		return -1;
+	}
+	v = c;
+	if ((c = jas_stream_getc(in)) == EOF) {
+		return -1;
+	}
+	v = (v << 8) | c;
+	if (val) {
+		*val = v;
+	}
+	return 0;
+}
+
+static int jp2_getuint32(jas_stream_t *in, uint_fast32_t *val)
+{
+	uint_fast32_t v;
+	int c;
+	if ((c = jas_stream_getc(in)) == EOF) {
+		return -1;
+	}
+	v = c;
+	if ((c = jas_stream_getc(in)) == EOF) {
+		return -1;
+	}
+	v = (v << 8) | c;
+	if ((c = jas_stream_getc(in)) == EOF) {
+		return -1;
+	}
+	v = (v << 8) | c;
+	if ((c = jas_stream_getc(in)) == EOF) {
+		return -1;
+	}
+	v = (v << 8) | c;
+	if (val) {
+		*val = v;
+	}
+	return 0;
+}
+
+static int jp2_getuint64(jas_stream_t *in, uint_fast64_t *val)
+{
+	abort();
+}
+
+/******************************************************************************\
+* Output operations for primitive types.
+\******************************************************************************/
+
+static int jp2_putuint8(jas_stream_t *out, uint_fast8_t val)
+{
+	if (jas_stream_putc(out, val & 0xff) == EOF) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jp2_putuint16(jas_stream_t *out, uint_fast16_t val)
+{
+	if (jas_stream_putc(out, (val >> 8) & 0xff) == EOF ||
+	  jas_stream_putc(out, val & 0xff) == EOF) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jp2_putuint32(jas_stream_t *out, uint_fast32_t val)
+{
+	if (jas_stream_putc(out, (val >> 24) & 0xff) == EOF ||
+	  jas_stream_putc(out, (val >> 16) & 0xff) == EOF ||
+	  jas_stream_putc(out, (val >> 8) & 0xff) == EOF ||
+	  jas_stream_putc(out, val & 0xff) == EOF) {
+		return -1;
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* Miscellaneous code.
+\******************************************************************************/
+
+jp2_boxinfo_t *jp2_boxinfolookup(int type)
+{
+	jp2_boxinfo_t *boxinfo;
+	for (boxinfo = jp2_boxinfos; boxinfo->name; ++boxinfo) {
+		if (boxinfo->type == type) {
+			return boxinfo;
+		}
+	}
+	return &jp2_boxinfo_unk;
+}
+
+
+
+
+
+static void jp2_cmap_destroy(jp2_box_t *box)
+{
+	jp2_cmap_t *cmap = &box->data.cmap;
+	if (cmap->ents) {
+		jas_free(cmap->ents);
+	}
+}
+
+static int jp2_cmap_getdata(jp2_box_t *box, jas_stream_t *in)
+{
+	jp2_cmap_t *cmap = &box->data.cmap;
+	jp2_cmapent_t *ent;
+	int i;
+
+	cmap->numchans = (box->len - JP2_BOX_HDRLEN) / 4;
+	if (!(cmap->ents = jas_malloc(cmap->numchans * sizeof(jp2_cmapent_t)))) {
+		return -1;
+	}
+	for (i = 0; i < cmap->numchans; ++i) {
+		ent = &cmap->ents[i];
+		if (jp2_getuint16(in, &ent->cmptno) ||
+		  jp2_getuint8(in, &ent->map) ||
+		  jp2_getuint8(in, &ent->pcol)) {
+			return -1;
+		}
+	}
+	
+	return 0;
+}
+
+static int jp2_cmap_putdata(jp2_box_t *box, jas_stream_t *out)
+{
+	return -1;
+}
+
+static void jp2_cmap_dumpdata(jp2_box_t *box, FILE *out)
+{
+	jp2_cmap_t *cmap = &box->data.cmap;
+	int i;
+	jp2_cmapent_t *ent;
+	fprintf(stderr, "numchans = %d\n", (int) cmap->numchans);
+	for (i = 0; i < cmap->numchans; ++i) {
+		ent = &cmap->ents[i];
+		fprintf(stderr, "cmptno=%d; map=%d; pcol=%d\n",
+		  (int) ent->cmptno, (int) ent->map, (int) ent->pcol);
+	}
+}
+
+static void jp2_pclr_destroy(jp2_box_t *box)
+{
+	jp2_pclr_t *pclr = &box->data.pclr;
+	if (pclr->lutdata) {
+		jas_free(pclr->lutdata);
+	}
+}
+
+static int jp2_pclr_getdata(jp2_box_t *box, jas_stream_t *in)
+{
+	jp2_pclr_t *pclr = &box->data.pclr;
+	int lutsize;
+	int i;
+	int j;
+	int_fast32_t x;
+
+	pclr->lutdata = 0;
+
+	if (jp2_getuint16(in, &pclr->numlutents) ||
+	  jp2_getuint8(in, &pclr->numchans)) {
+		return -1;
+	}
+	lutsize = pclr->numlutents * pclr->numchans;
+	if (!(pclr->lutdata = jas_malloc(lutsize * sizeof(int_fast32_t)))) {
+		return -1;
+	}
+	if (!(pclr->bpc = jas_malloc(pclr->numchans * sizeof(uint_fast8_t)))) {
+		return -1;
+	}
+	for (i = 0; i < pclr->numchans; ++i) {
+		if (jp2_getuint8(in, &pclr->bpc[i])) {
+			return -1;
+		}
+	}
+	for (i = 0; i < pclr->numlutents; ++i) {
+		for (j = 0; j < pclr->numchans; ++j) {
+			if (jp2_getint(in, (pclr->bpc[j] & 0x80) != 0,
+			  (pclr->bpc[j] & 0x7f) + 1, &x)) {
+				return -1;
+			}
+			pclr->lutdata[i * pclr->numchans + j] = x;
+		}
+	}
+	return 0;
+}
+
+static int jp2_pclr_putdata(jp2_box_t *box, jas_stream_t *out)
+{
+    /* This code from official Jasper must be part of unfinished work.
+       It generates an unused variable warning.
+	jp2_pclr_t *pclr = &box->data.pclr;
+    */
+	return -1;
+}
+
+static void jp2_pclr_dumpdata(jp2_box_t *box, FILE *out)
+{
+	jp2_pclr_t *pclr = &box->data.pclr;
+	int i;
+	int j;
+	fprintf(out, "numents=%d; numchans=%d\n", (int) pclr->numlutents,
+	  (int) pclr->numchans);
+	for (i = 0; i < pclr->numlutents; ++i) {
+		for (j = 0; j < pclr->numchans; ++j) {
+			fprintf(out, "LUT[%d][%d]=%d\n", i, j, pclr->lutdata[i * pclr->numchans + j]);
+		}
+	}
+}
+
+static int jp2_getint(jas_stream_t *in, int s, int n, int_fast32_t *val)
+{
+	int c;
+	int i;
+	uint_fast32_t v;
+	int m;
+
+	m = (n + 7) / 8;
+
+	v = 0;
+	for (i = 0; i < m; ++i) {
+		if ((c = jas_stream_getc(in)) == EOF) {
+			return -1;
+		}
+		v = (v << 8) | c;
+	}
+	v &= ONES(n);
+	if (s) {
+		int sb;
+		sb = v & (1 << (8 * m - 1));
+		*val = ((~v) + 1) & ONES(8 * m);
+		if (sb) {
+			*val = -*val;
+		}
+	} else {
+		*val = v;
+	}
+
+	return 0;
+}
+
+jp2_cdefchan_t *jp2_cdef_lookup(jp2_cdef_t *cdef, int channo)
+{
+	int i;
+	jp2_cdefchan_t *cdefent;
+	for (i = 0; i < cdef->numchans; ++i) {
+		cdefent = &cdef->ents[i];
+		if (cdefent->channo == channo) {
+			return cdefent;
+		}
+	}
+	return 0;
+}
diff --git a/converter/other/jpeg2000/libjasper/jp2/jp2_cod.h b/converter/other/jpeg2000/libjasper/jp2/jp2_cod.h
new file mode 100644
index 00000000..96b48d12
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jp2/jp2_cod.h
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * JP2 Library
+ *
+ * $Id$
+ */
+
+#ifndef JP2_COD_H
+#define JP2_COD_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_types.h"
+
+/******************************************************************************\
+* Macros.
+\******************************************************************************/
+
+#define	JP2_SPTOBPC(s, p) \
+	((((p) - 1) & 0x7f) | (((s) & 1) << 7))
+
+/******************************************************************************\
+* Box class.
+\******************************************************************************/
+
+#define	JP2_BOX_HDRLEN	8
+
+/* Box types. */
+#define	JP2_BOX_JP		0x6a502020	/* Signature */
+#define JP2_BOX_FTYP	0x66747970	/* File Type */
+#define	JP2_BOX_JP2H	0x6a703268	/* JP2 Header */
+#define	JP2_BOX_IHDR	0x69686472	/* Image Header */
+#define	JP2_BOX_BPCC	0x62706363	/* Bits Per Component */
+#define	JP2_BOX_COLR	0x636f6c72	/* Color Specification */
+#define	JP2_BOX_PCLR	0x70636c72	/* Palette */
+#define	JP2_BOX_CMAP	0x636d6170	/* Component Mapping */
+#define	JP2_BOX_CDEF	0x63646566	/* Channel Definition */
+#define	JP2_BOX_RES		0x72657320	/* Resolution */
+#define	JP2_BOX_RESC	0x72657363	/* Capture Resolution */
+#define	JP2_BOX_RESD	0x72657364	/* Default Display Resolution */
+#define	JP2_BOX_JP2C	0x6a703263	/* Contiguous Code Stream */
+#define	JP2_BOX_JP2I	0x6a703269	/* Intellectual Property */
+#define	JP2_BOX_XML		0x786d6c20	/* XML */
+#define	JP2_BOX_UUID	0x75756964	/* UUID */
+#define	JP2_BOX_UINF	0x75696e66	/* UUID Info */
+#define	JP2_BOX_ULST	0x75637374	/* UUID List */
+#define	JP2_BOX_URL		0x75726c20	/* URL */
+
+#define	JP2_BOX_SUPER	0x01
+#define	JP2_BOX_NODATA	0x02
+
+/* JP box data. */
+
+#define	JP2_JP_MAGIC	0x0d0a870a
+#define	JP2_JP_LEN		12
+
+typedef struct {
+	uint_fast32_t magic;
+} jp2_jp_t;
+
+/* FTYP box data. */
+
+#define	JP2_FTYP_MAXCOMPATCODES	32
+#define	JP2_FTYP_MAJVER		0x6a703220
+#define	JP2_FTYP_MINVER		0
+#define	JP2_FTYP_COMPATCODE		JP2_FTYP_MAJVER
+
+typedef struct {
+	uint_fast32_t majver;
+	uint_fast32_t minver;
+	uint_fast32_t numcompatcodes;
+	uint_fast32_t compatcodes[JP2_FTYP_MAXCOMPATCODES];
+} jp2_ftyp_t;
+
+/* IHDR box data. */
+
+#define	JP2_IHDR_COMPTYPE	7
+#define	JP2_IHDR_BPCNULL	255
+
+typedef struct {
+	uint_fast32_t width;
+	uint_fast32_t height;
+	uint_fast16_t numcmpts;
+	uint_fast8_t bpc;
+	uint_fast8_t comptype;
+	uint_fast8_t csunk;
+	uint_fast8_t ipr;
+} jp2_ihdr_t;
+
+/* BPCC box data. */
+
+typedef struct {
+	uint_fast16_t numcmpts;
+	uint_fast8_t *bpcs;
+} jp2_bpcc_t;
+
+/* COLR box data. */
+
+#define	JP2_COLR_ENUM	1
+#define	JP2_COLR_ICC	2
+#define	JP2_COLR_PRI	0
+
+#define	JP2_COLR_SRGB	16
+#define	JP2_COLR_SGRAY	17
+#define	JP2_COLR_SYCC	18
+
+typedef struct {
+	uint_fast8_t method;
+	uint_fast8_t pri;
+	uint_fast8_t approx;
+	uint_fast32_t csid;
+	uint_fast8_t *iccp;
+	int iccplen;
+	/* XXX - Someday we ought to add ICC profile data here. */
+} jp2_colr_t;
+
+/* PCLR box data. */
+
+typedef struct {
+	uint_fast16_t numlutents;
+	uint_fast8_t numchans;
+	int_fast32_t *lutdata;
+	uint_fast8_t *bpc;
+} jp2_pclr_t;
+
+/* CDEF box per-channel data. */
+
+#define JP2_CDEF_RGB_R	1
+#define JP2_CDEF_RGB_G	2
+#define JP2_CDEF_RGB_B	3
+
+#define JP2_CDEF_YCBCR_Y	1
+#define JP2_CDEF_YCBCR_CB	2
+#define JP2_CDEF_YCBCR_CR	3
+
+#define	JP2_CDEF_GRAY_Y	1
+
+#define	JP2_CDEF_TYPE_COLOR	0
+#define	JP2_CDEF_TYPE_OPACITY	1
+#define	JP2_CDEF_TYPE_UNSPEC	65535
+#define	JP2_CDEF_ASOC_ALL	0
+#define	JP2_CDEF_ASOC_NONE	65535
+
+typedef struct {
+	uint_fast16_t channo;
+	uint_fast16_t type;
+	uint_fast16_t assoc;
+} jp2_cdefchan_t;
+
+/* CDEF box data. */
+
+typedef struct {
+	uint_fast16_t numchans;
+	jp2_cdefchan_t *ents;
+} jp2_cdef_t;
+
+typedef struct {
+	uint_fast16_t cmptno;
+	uint_fast8_t map;
+	uint_fast8_t pcol;
+} jp2_cmapent_t;
+
+typedef struct {
+	uint_fast16_t numchans;
+	jp2_cmapent_t *ents;
+} jp2_cmap_t;
+
+#define	JP2_CMAP_DIRECT		0
+#define	JP2_CMAP_PALETTE	1
+
+/* Generic box. */
+
+struct jp2_boxops_s;
+typedef struct {
+
+	struct jp2_boxops_s *ops;
+	struct jp2_boxinfo_s *info;
+
+	uint_fast32_t type;
+	uint_fast32_t len;
+
+	union {
+		jp2_jp_t jp;
+		jp2_ftyp_t ftyp;
+		jp2_ihdr_t ihdr;
+		jp2_bpcc_t bpcc;
+		jp2_colr_t colr;
+		jp2_pclr_t pclr;
+		jp2_cdef_t cdef;
+		jp2_cmap_t cmap;
+	} data;
+
+} jp2_box_t;
+
+typedef struct jp2_boxops_s {
+	void (*init)(jp2_box_t *box);
+	void (*destroy)(jp2_box_t *box);
+	int (*getdata)(jp2_box_t *box, jas_stream_t *in);
+	int (*putdata)(jp2_box_t *box, jas_stream_t *out);
+	void (*dumpdata)(jp2_box_t *box, FILE *out);
+} jp2_boxops_t;
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+typedef struct jp2_boxinfo_s {
+	int type;
+	const char *name;
+	int flags;
+	jp2_boxops_t ops;
+} jp2_boxinfo_t;
+
+/******************************************************************************\
+* Box class.
+\******************************************************************************/
+
+jp2_box_t *jp2_box_create(int type);
+void jp2_box_destroy(jp2_box_t *box);
+jp2_box_t *jp2_box_get(jas_stream_t *in);
+int jp2_box_put(jp2_box_t *box, jas_stream_t *out);
+
+#define JP2_DTYPETOBPC(dtype) \
+  ((JAS_IMAGE_CDT_GETSGND(dtype) << 7) | (JAS_IMAGE_CDT_GETPREC(dtype) - 1))
+#define	JP2_BPCTODTYPE(bpc) \
+  (JAS_IMAGE_CDT_SETSGND(bpc >> 7) | JAS_IMAGE_CDT_SETPREC((bpc & 0x7f) + 1))
+
+#define ICC_CS_RGB	0x52474220
+#define ICC_CS_YCBCR	0x59436272
+#define ICC_CS_GRAY	0x47524159
+
+jp2_cdefchan_t *jp2_cdef_lookup(jp2_cdef_t *cdef, int channo);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jp2/jp2_dec.c b/converter/other/jpeg2000/libjasper/jp2/jp2_dec.c
new file mode 100644
index 00000000..3cce9278
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jp2/jp2_dec.c
@@ -0,0 +1,649 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * JP2 Library
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_image.h"
+#include "jasper/jas_stream.h"
+#include "jasper/jas_math.h"
+#include "jasper/jas_debug.h"
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_version.h"
+
+#include "jp2_cod.h"
+#include "jp2_dec.h"
+
+#define	JP2_VALIDATELEN	(JAS_MIN(JP2_JP_LEN + 16, JAS_STREAM_MAXPUTBACK))
+
+static jp2_dec_t *jp2_dec_create(void);
+static void jp2_dec_destroy(jp2_dec_t *dec);
+static int jp2_getcs(jp2_colr_t *colr);
+static int jp2_getct(int colorspace, int type, int assoc);
+static int fromiccpcs(int cs);
+
+/******************************************************************************\
+* Functions.
+\******************************************************************************/
+
+jas_image_t *jp2_decode(jas_stream_t *in, char *optstr)
+{
+	jp2_box_t *box;
+	int found;
+	jas_image_t *image;
+	jp2_dec_t *dec;
+	bool samedtype;
+	int dtype;
+	int i;
+	jp2_cmap_t *cmapd;
+	jp2_pclr_t *pclrd;
+	jp2_cdef_t *cdefd;
+	int channo;
+	int newcmptno;
+	int_fast32_t *lutents;
+	jp2_cmapent_t *cmapent;
+	unsigned char *iccp;
+	int cs;
+
+	dec = 0;
+	box = 0;
+	image = 0;
+
+	if (!(dec = jp2_dec_create())) {
+		goto error;
+	}
+
+	/* Get the first box.  This should be a JP box. */
+	if (!(box = jp2_box_get(in))) {
+		jas_eprintf("error: cannot get box\n");
+		goto error;
+	}
+	if (box->type != JP2_BOX_JP) {
+		jas_eprintf("error: expecting signature box\n");
+		goto error;
+	}
+	if (box->data.jp.magic != JP2_JP_MAGIC) {
+		jas_eprintf("incorrect magic number\n");
+		goto error;
+	}
+	jp2_box_destroy(box);
+	box = 0;
+
+	/* Get the second box.  This should be a FTYP box. */
+	if (!(box = jp2_box_get(in))) {
+		goto error;
+	}
+	if (box->type != JP2_BOX_FTYP) {
+		jas_eprintf("expecting file type box\n");
+		goto error;
+	}
+	jp2_box_destroy(box);
+	box = 0;
+
+	/* Get more boxes... */
+	found = 0;
+	while ((box = jp2_box_get(in))) {
+		if (jas_getdbglevel() >= 1) {
+			fprintf(stderr, "box type %s\n", box->info->name);
+		}
+		switch (box->type) {
+		case JP2_BOX_JP2C:
+			found = 1;
+			break;
+		case JP2_BOX_IHDR:
+			if (!dec->ihdr) {
+				dec->ihdr = box;
+				box = 0;
+			}
+			break;
+		case JP2_BOX_BPCC:
+			if (!dec->bpcc) {
+				dec->bpcc = box;
+				box = 0;
+			}
+			break;
+		case JP2_BOX_CDEF:
+			if (!dec->cdef) {
+				dec->cdef = box;
+				box = 0;
+			}
+			break;
+		case JP2_BOX_PCLR:
+			if (!dec->pclr) {
+				dec->pclr = box;
+				box = 0;
+			}
+			break;
+		case JP2_BOX_CMAP:
+			if (!dec->cmap) {
+				dec->cmap = box;
+				box = 0;
+			}
+			break;
+		case JP2_BOX_COLR:
+			if (!dec->colr) {
+				dec->colr = box;
+				box = 0;
+			}
+			break;
+		}
+		if (box) {
+			jp2_box_destroy(box);
+			box = 0;
+		}
+		if (found) {
+			break;
+		}
+	}
+
+	if (!found) {
+		jas_eprintf("error: no code stream found\n");
+		goto error;
+	}
+
+	if (!(dec->image = jpc_decode(in, optstr))) {
+		jas_eprintf("error: cannot decode code stream\n");
+		goto error;
+	}
+
+	/* An IHDR box must be present. */
+	if (!dec->ihdr) {
+		jas_eprintf("error: missing IHDR box\n");
+		goto error;
+	}
+
+	/* Does the number of components indicated in the IHDR box match
+	  the value specified in the code stream? */
+	if (dec->ihdr->data.ihdr.numcmpts != jas_image_numcmpts(dec->image)) {
+		jas_eprintf("warning: number of components mismatch\n");
+	}
+
+	/* At least one component must be present. */
+	if (!jas_image_numcmpts(dec->image)) {
+		jas_eprintf("error: no components\n");
+		goto error;
+	}
+
+	/* Determine if all components have the same data type. */
+	samedtype = true;
+	dtype = jas_image_cmptdtype(dec->image, 0);
+	for (i = 1; i < jas_image_numcmpts(dec->image); ++i) {
+		if (jas_image_cmptdtype(dec->image, i) != dtype) {
+			samedtype = false;
+			break;
+		}
+	}
+
+	/* Is the component data type indicated in the IHDR box consistent
+	  with the data in the code stream? */
+	if ((samedtype && dec->ihdr->data.ihdr.bpc != JP2_DTYPETOBPC(dtype)) ||
+	  (!samedtype && dec->ihdr->data.ihdr.bpc != JP2_IHDR_BPCNULL)) {
+		jas_eprintf("warning: component data type mismatch\n");
+	}
+
+	/* Is the compression type supported? */
+	if (dec->ihdr->data.ihdr.comptype != JP2_IHDR_COMPTYPE) {
+		jas_eprintf("error: unsupported compression type\n");
+		goto error;
+	}
+
+	if (dec->bpcc) {
+		/* Is the number of components indicated in the BPCC box
+		  consistent with the code stream data? */
+		if (dec->bpcc->data.bpcc.numcmpts != jas_image_numcmpts(
+		  dec->image)) {
+			jas_eprintf("warning: number of components mismatch\n");
+		}
+		/* Is the component data type information indicated in the BPCC
+		  box consistent with the code stream data? */
+		if (!samedtype) {
+			for (i = 0; i < jas_image_numcmpts(dec->image); ++i) {
+				if (jas_image_cmptdtype(dec->image, i) != JP2_BPCTODTYPE(dec->bpcc->data.bpcc.bpcs[i])) {
+					jas_eprintf("warning: component data type mismatch\n");
+				}
+			}
+		} else {
+			jas_eprintf("warning: superfluous BPCC box\n");
+		}
+	}
+
+	/* A COLR box must be present. */
+	if (!dec->colr) {
+		jas_eprintf("error: no COLR box\n");
+		goto error;
+	}
+
+	switch (dec->colr->data.colr.method) {
+	case JP2_COLR_ENUM:
+		jas_image_setcolorspace(dec->image, jp2_getcs(&dec->colr->data.colr));
+		break;
+	case JP2_COLR_ICC:
+		if (dec->colr->data.colr.iccplen < 128) {
+			abort();
+		}
+		iccp = dec->colr->data.colr.iccp;
+		cs = (iccp[16] << 24) | (iccp[17] << 16) | (iccp[18] << 8) |
+		  iccp[19];
+		jas_eprintf("ICC Profile CS %08x\n", cs);
+		jas_image_setcolorspace(dec->image, fromiccpcs(cs));
+		break;
+	}
+
+	/* If a CMAP box is present, a PCLR box must also be present. */
+	if (dec->cmap && !dec->pclr) {
+		jas_eprintf("warning: missing PCLR box or superfluous CMAP box\n");
+		jp2_box_destroy(dec->cmap);
+		dec->cmap = 0;
+	}
+
+	/* If a CMAP box is not present, a PCLR box must not be present. */
+	if (!dec->cmap && dec->pclr) {
+		jas_eprintf("warning: missing CMAP box or superfluous PCLR box\n");
+		jp2_box_destroy(dec->pclr);
+		dec->pclr = 0;
+	}
+
+	/* Determine the number of channels (which is essentially the number
+	  of components after any palette mappings have been applied). */
+	dec->numchans = dec->cmap ? dec->cmap->data.cmap.numchans : jas_image_numcmpts(dec->image);
+
+	/* Perform a basic sanity check on the CMAP box if present. */
+	if (dec->cmap) {
+		for (i = 0; i < dec->numchans; ++i) {
+			/* Is the component number reasonable? */
+			if (dec->cmap->data.cmap.ents[i].cmptno >= jas_image_numcmpts(dec->image)) {
+				jas_eprintf("error: invalid component number in CMAP box\n");
+				goto error;
+			}
+			/* Is the LUT index reasonable? */
+			if (dec->cmap->data.cmap.ents[i].pcol >= dec->pclr->data.pclr.numchans) {
+				jas_eprintf("error: invalid CMAP LUT index\n");
+				goto error;
+			}
+		}
+	}
+
+	/* Allocate space for the channel-number to component-number LUT. */
+	if (!(dec->chantocmptlut = jas_malloc(dec->numchans * sizeof(uint_fast16_t)))) {
+		jas_eprintf("error: no memory\n");
+		goto error;
+	}
+
+	if (!dec->cmap) {
+		for (i = 0; i < dec->numchans; ++i) {
+			dec->chantocmptlut[i] = i;
+		}
+	} else {
+		cmapd = &dec->cmap->data.cmap;
+		pclrd = &dec->pclr->data.pclr;
+		cdefd = &dec->cdef->data.cdef;
+		for (channo = 0; channo < cmapd->numchans; ++channo) {
+			cmapent = &cmapd->ents[channo];
+			if (cmapent->map == JP2_CMAP_DIRECT) {
+				dec->chantocmptlut[channo] = channo;
+			} else if (cmapent->map == JP2_CMAP_PALETTE) {
+				lutents = jas_malloc(pclrd->numlutents * sizeof(int_fast32_t));
+				for (i = 0; i < pclrd->numlutents; ++i) {
+					lutents[i] = pclrd->lutdata[cmapent->pcol + i * pclrd->numchans];
+				}
+				newcmptno = jas_image_numcmpts(dec->image);
+				jas_image_depalettize(dec->image, cmapent->cmptno, pclrd->numlutents, lutents, JP2_BPCTODTYPE(pclrd->bpc[cmapent->pcol]), newcmptno);
+				dec->chantocmptlut[channo] = newcmptno;
+				jas_free(lutents);
+#if 0
+				if (dec->cdef) {
+					cdefent = jp2_cdef_lookup(cdefd, channo);
+					if (!cdefent) {
+						abort();
+					}
+				jas_image_setcmpttype(dec->image, newcmptno, jp2_getct(jas_image_colorspace(dec->image), cdefent->type, cdefent->assoc));
+				} else {
+				jas_image_setcmpttype(dec->image, newcmptno, jp2_getct(jas_image_colorspace(dec->image), 0, channo + 1));
+				}
+#endif
+			}
+		}
+	}
+
+	/* Mark all components as being of unknown type. */
+
+	for (i = 0; i < jas_image_numcmpts(dec->image); ++i) {
+		jas_image_setcmpttype(dec->image, i, JAS_IMAGE_CT_UNKNOWN);
+	}
+
+	/* Determine the type of each component. */
+	if (dec->cdef) {
+		for (i = 0; i < dec->numchans; ++i) {
+			jas_image_setcmpttype(dec->image,
+			  dec->chantocmptlut[dec->cdef->data.cdef.ents[i].channo],
+			  jp2_getct(jas_image_colorspace(dec->image),
+			  dec->cdef->data.cdef.ents[i].type, dec->cdef->data.cdef.ents[i].assoc));
+		}
+	} else {
+		for (i = 0; i < dec->numchans; ++i) {
+			jas_image_setcmpttype(dec->image, dec->chantocmptlut[i],
+			  jp2_getct(jas_image_colorspace(dec->image), 0, i + 1));
+		}
+	}
+
+	/* Delete any components that are not of interest. */
+	for (i = jas_image_numcmpts(dec->image) - 1; i >= 0; --i) {
+		if (jas_image_cmpttype(dec->image, i) == JAS_IMAGE_CT_UNKNOWN) {
+			jas_image_delcmpt(dec->image, i);
+		}
+	}
+
+	/* Ensure that some components survived. */
+	if (!jas_image_numcmpts(dec->image)) {
+		jas_eprintf("error: no components\n");
+		goto error;
+	}
+fprintf(stderr, "no of components is %d\n", jas_image_numcmpts(dec->image));
+
+	/* Prevent the image from being destroyed later. */
+	image = dec->image;
+	dec->image = 0;
+
+	jp2_dec_destroy(dec);
+
+	return image;
+
+error:
+	if (box) {
+		jp2_box_destroy(box);
+	}
+	if (dec) {
+		jp2_dec_destroy(dec);
+	}
+	return 0;
+}
+
+int jp2_validate(jas_stream_t *in)
+{
+	char buf[JP2_VALIDATELEN];
+	int i;
+	int n;
+
+	assert(JAS_STREAM_MAXPUTBACK >= JP2_VALIDATELEN);
+
+	/* Read the validation data (i.e., the data used for detecting
+	  the format). */
+	if ((n = jas_stream_read(in, buf, JP2_VALIDATELEN)) < 0) {
+		return -1;
+	}
+
+	/* Put the validation data back onto the stream, so that the
+	  stream position will not be changed. */
+	for (i = n - 1; i >= 0; --i) {
+		if (jas_stream_ungetc(in, buf[i]) == EOF) {
+			return -1;
+		}
+	}
+
+	/* Did we read enough data? */
+	if (n < JP2_VALIDATELEN) {
+		return -1;
+	}
+
+	/* Is the box type correct? */
+	if (((buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]) !=
+	  JP2_BOX_JP)
+	{
+		return -1;
+	}
+
+	return 0;
+}
+
+static jp2_dec_t *jp2_dec_create(void)
+{
+	jp2_dec_t *dec;
+
+	if (!(dec = jas_malloc(sizeof(jp2_dec_t)))) {
+		return 0;
+	}
+	dec->ihdr = 0;
+	dec->bpcc = 0;
+	dec->cdef = 0;
+	dec->pclr = 0;
+	dec->image = 0;
+	dec->chantocmptlut = 0;
+	dec->cmap = 0;
+	dec->colr = 0;
+	return dec;
+}
+
+static void jp2_dec_destroy(jp2_dec_t *dec)
+{
+	if (dec->ihdr) {
+		jp2_box_destroy(dec->ihdr);
+	}
+	if (dec->bpcc) {
+		jp2_box_destroy(dec->bpcc);
+	}
+	if (dec->cdef) {
+		jp2_box_destroy(dec->cdef);
+	}
+	if (dec->pclr) {
+		jp2_box_destroy(dec->pclr);
+	}
+	if (dec->image) {
+		jas_image_destroy(dec->image);
+	}
+	if (dec->cmap) {
+		jp2_box_destroy(dec->cmap);
+	}
+	if (dec->colr) {
+		jp2_box_destroy(dec->colr);
+	}
+	if (dec->chantocmptlut) {
+		jas_free(dec->chantocmptlut);
+	}
+	jas_free(dec);
+}
+
+
+
+
+
+
+static int jp2_getct(int colorspace, int type, int assoc)
+{
+	if (type == 1 && assoc == 0) {
+		return JAS_IMAGE_CT_OPACITY;
+	}
+	if (type == 0 && assoc >= 1 && assoc <= 65534) {
+		switch (colorspace) {
+		case JAS_IMAGE_CS_RGB:
+			switch (assoc) {
+			case JP2_CDEF_RGB_R:
+				return JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_R);
+				break;
+			case JP2_CDEF_RGB_G:
+				return JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_G);
+				break;
+			case JP2_CDEF_RGB_B:
+				return JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_B);
+				break;
+			}
+			break;
+		case JAS_IMAGE_CS_YCBCR:
+			switch (assoc) {
+			case JP2_CDEF_YCBCR_Y:
+				return JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_YCBCR_Y);
+				break;
+			case JP2_CDEF_YCBCR_CB:
+				return JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_YCBCR_CB);
+				break;
+			case JP2_CDEF_YCBCR_CR:
+				return JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_YCBCR_CR);
+				break;
+			}
+			break;
+		case JAS_IMAGE_CS_GRAY:
+			switch (assoc) {
+			case JP2_CDEF_GRAY_Y:
+				return JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_GRAY_Y);
+				break;
+			}
+			break;
+#if 0
+		case JAS_IMAGE_CS_ICC:
+#endif
+		default:
+			return JAS_IMAGE_CT_COLOR(assoc - 1);
+			break;
+		}
+	}
+	return JAS_IMAGE_CT_UNKNOWN;
+}
+
+static int jp2_getcs(jp2_colr_t *colr)
+{
+	if (colr->method == JP2_COLR_ENUM) {
+		switch (colr->csid) {
+		case JP2_COLR_SRGB:
+			return JAS_IMAGE_CS_RGB;
+			break;
+		case JP2_COLR_SYCC:
+			return JAS_IMAGE_CS_YCBCR;
+			break;
+		case JP2_COLR_SGRAY:
+			return JAS_IMAGE_CS_GRAY;
+			break;
+		}
+	}
+	return JAS_IMAGE_CS_UNKNOWN;
+}
+
+static int fromiccpcs(int cs)
+{
+	switch (cs) {
+	case ICC_CS_RGB:
+		return JAS_IMAGE_CS_RGB;
+		break;
+	case ICC_CS_YCBCR:
+		return JAS_IMAGE_CS_YCBCR;
+		break;
+	case ICC_CS_GRAY:
+		return JAS_IMAGE_CS_GRAY;
+		break;
+	}
+	return JAS_IMAGE_CS_UNKNOWN;
+}
diff --git a/converter/other/jpeg2000/libjasper/jp2/jp2_dec.h b/converter/other/jpeg2000/libjasper/jp2/jp2_dec.h
new file mode 100644
index 00000000..300be107
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jp2/jp2_dec.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+#ifndef JP2_DEC_H
+#define JP2_DEC_H
+
+#include "jasper/jas_image.h"
+#include "jasper/jas_stream.h"
+#include "jp2_cod.h"
+
+typedef struct {
+
+	jp2_box_t *pclr;
+	jp2_box_t *cdef;
+	jp2_box_t *ihdr;
+	jp2_box_t *bpcc;
+	jp2_box_t *cmap;
+	jp2_box_t *colr;
+	jas_image_t *image;
+	uint_fast16_t numchans;
+	uint_fast16_t *chantocmptlut;
+
+} jp2_dec_t;
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jp2/jp2_enc.c b/converter/other/jpeg2000/libjasper/jp2/jp2_enc.c
new file mode 100644
index 00000000..12538248
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jp2/jp2_enc.c
@@ -0,0 +1,414 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * JP2 Library
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_image.h"
+#include "jasper/jas_stream.h"
+
+#include "jp2_cod.h"
+
+static uint_fast32_t jp2_gettypeasoc(int colorspace, int ctype);
+
+/******************************************************************************\
+* Functions.
+\******************************************************************************/
+
+int jp2_encode(jas_image_t *image, jas_stream_t *out, char *optstr)
+{
+	jp2_box_t *box;
+	jp2_ftyp_t *ftyp;
+	jp2_ihdr_t *ihdr;
+	jas_stream_t *tmpstream;
+	int allcmptssame;
+	jp2_bpcc_t *bpcc;
+	long len;
+	uint_fast16_t cmptno;
+	jp2_colr_t *colr;
+	char buf[4096];
+	uint_fast32_t overhead;
+	jp2_cdefchan_t *cdefchanent;
+	jp2_cdef_t *cdef;
+	int i;
+	uint_fast32_t typeasoc;
+
+	box = 0;
+	tmpstream = 0;
+
+	/* Output the signature box. */
+
+	if (!(box = jp2_box_create(JP2_BOX_JP))) {
+		goto error;
+	}
+	box->data.jp.magic = JP2_JP_MAGIC;
+	if (jp2_box_put(box, out)) {
+		goto error;
+	}
+	jp2_box_destroy(box);
+	box = 0;
+
+	/* Output the file type box. */
+
+	if (!(box = jp2_box_create(JP2_BOX_FTYP))) {
+		goto error;
+	}
+	ftyp = &box->data.ftyp;
+	ftyp->majver = JP2_FTYP_MAJVER;
+	ftyp->minver = JP2_FTYP_MINVER;
+	ftyp->numcompatcodes = 1;
+	ftyp->compatcodes[0] = JP2_FTYP_COMPATCODE;
+	if (jp2_box_put(box, out)) {
+		goto error;
+	}
+	jp2_box_destroy(box);
+	box = 0;
+
+	/*
+	 * Generate the data portion of the JP2 header box.
+	 * We cannot simply output the header for this box
+	 * since we do not yet know the correct value for the length
+	 * field.
+	 */
+
+	if (!(tmpstream = jas_stream_memopen(0, 0))) {
+		goto error;
+	}
+
+	/* Generate image header box. */
+
+	if (!(box = jp2_box_create(JP2_BOX_IHDR))) {
+		goto error;
+	}
+	ihdr = &box->data.ihdr;
+	ihdr->width = jas_image_width(image);
+	ihdr->height = jas_image_height(image);
+	ihdr->numcmpts = jas_image_numcmpts(image);
+	allcmptssame = 0;
+	ihdr->bpc = allcmptssame ? JP2_SPTOBPC(jas_image_cmptsgnd(image, 0),
+	  jas_image_cmptprec(image, 0)) : JP2_IHDR_BPCNULL;
+	ihdr->comptype = JP2_IHDR_COMPTYPE;
+	ihdr->csunk = 0;
+	ihdr->ipr = 0;
+	if (jp2_box_put(box, tmpstream)) {
+		goto error;
+	}
+	jp2_box_destroy(box);
+	box = 0;
+
+	/* Generate bits per component box. */
+
+	if (!allcmptssame) {
+		if (!(box = jp2_box_create(JP2_BOX_BPCC))) {
+			goto error;
+		}
+		bpcc = &box->data.bpcc;
+		bpcc->numcmpts = jas_image_numcmpts(image);
+		if (!(bpcc->bpcs = jas_malloc(bpcc->numcmpts *
+		  sizeof(uint_fast8_t)))) {
+			goto error;
+		}
+		for (cmptno = 0; cmptno < bpcc->numcmpts; ++cmptno) {
+			bpcc->bpcs[cmptno] = JP2_SPTOBPC(jas_image_cmptsgnd(image,
+			  cmptno), jas_image_cmptprec(image, cmptno));
+		}
+		if (jp2_box_put(box, tmpstream)) {
+			goto error;
+		}
+		jp2_box_destroy(box);
+		box = 0;
+	}
+
+	/* Generate color specification box. */
+
+	if (!(box = jp2_box_create(JP2_BOX_COLR))) {
+		goto error;
+	}
+	colr = &box->data.colr;
+	colr->method = JP2_COLR_ENUM;
+	colr->pri = JP2_COLR_PRI;
+	colr->approx = 0;
+	colr->csid = (jas_image_colorspace(image) == JAS_IMAGE_CS_RGB) ? JP2_COLR_SRGB :
+	  JP2_COLR_SGRAY;
+	if (jp2_box_put(box, tmpstream)) {
+		goto error;
+	}
+	jp2_box_destroy(box);
+	box = 0;
+
+	if (!(jas_image_colorspace(image) == JAS_IMAGE_CS_RGB &&
+	  jas_image_numcmpts(image) == 3 &&
+	  jas_image_getcmptbytype(image, 0) ==
+	  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_R) &&
+	  jas_image_getcmptbytype(image, 1) ==
+	  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_G) &&
+	  jas_image_getcmptbytype(image, 2) ==
+	  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_B)) &&
+	  !(jas_image_colorspace(image) == JAS_IMAGE_CS_YCBCR &&
+	  jas_image_numcmpts(image) != 3 &&
+	  jas_image_getcmptbytype(image, 0) ==
+	  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_YCBCR_Y) &&
+	  jas_image_getcmptbytype(image, 1) ==
+	  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_YCBCR_CB) &&
+	  jas_image_getcmptbytype(image, 2) ==
+	  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_YCBCR_CR)) &&
+	  !(jas_image_colorspace(image) == JAS_IMAGE_CS_GRAY &&
+	  jas_image_numcmpts(image) == 1 &&
+	  jas_image_getcmptbytype(image, 0) ==
+	  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_GRAY_Y))) {
+
+		if (!(box = jp2_box_create(JP2_BOX_CDEF))) {
+			goto error;
+		}
+		cdef = &box->data.cdef;
+		cdef->numchans = jas_image_numcmpts(image);
+		cdef->ents = jas_malloc(cdef->numchans * sizeof(jp2_cdefchan_t));
+		for (i = 0; i < jas_image_numcmpts(image); ++i) {
+			cdefchanent = &cdef->ents[i];
+			cdefchanent->channo = i;
+			typeasoc = jp2_gettypeasoc(jas_image_colorspace(image), jas_image_cmpttype(image, i));
+			cdefchanent->type = typeasoc >> 16;
+			cdefchanent->assoc = typeasoc & 0x7fff;
+		}
+		jp2_box_destroy(box);
+		box = 0;
+	}
+
+	/* Determine the total length of the JP2 header box. */
+
+	len = jas_stream_tell(tmpstream);
+	jas_stream_rewind(tmpstream);
+
+	/*
+	 * Output the JP2 header box and all of the boxes which it contains.
+	 */
+
+	if (!(box = jp2_box_create(JP2_BOX_JP2H))) {
+		goto error;
+	}
+	box->len = len + JP2_BOX_HDRLEN;
+	if (jp2_box_put(box, out)) {
+		goto error;
+	}
+	jp2_box_destroy(box);
+	box = 0;
+
+	if (jas_stream_copy(out, tmpstream, len)) {
+		goto error;
+	}
+
+	jas_stream_close(tmpstream);
+	tmpstream = 0;
+
+	/*
+	 * Output the contiguous code stream box.
+	 */
+
+	if (!(box = jp2_box_create(JP2_BOX_JP2C))) {
+		goto error;
+	}
+	box->len = 0;
+	if (jp2_box_put(box, out)) {
+		goto error;
+	}
+	jp2_box_destroy(box);
+	box = 0;
+
+	/* Output the JPEG-2000 code stream. */
+
+	overhead = jas_stream_getrwcount(out);
+	sprintf(buf, "%s\n_jp2overhead=%lu\n", (optstr ? optstr : ""),
+	  (unsigned long) overhead);
+
+	if (jpc_encode(image, out, buf)) {
+		goto error;
+	}
+
+	return 0;
+	abort();
+
+error:
+
+	if (box) {
+		jp2_box_destroy(box);
+	}
+	if (tmpstream) {
+		jas_stream_close(tmpstream);
+	}
+	return -1;
+}
+
+
+static uint_fast32_t jp2_gettypeasoc(int colorspace, int ctype)
+{
+	int type;
+	int asoc;
+
+	if (ctype & JAS_IMAGE_CT_OPACITY) {
+		type = JP2_CDEF_TYPE_OPACITY;
+		asoc = JP2_CDEF_ASOC_ALL;
+		goto done;
+	}
+
+	type = JP2_CDEF_TYPE_UNSPEC;
+	asoc = JP2_CDEF_ASOC_NONE;
+	switch (colorspace) {
+	case JAS_IMAGE_CS_RGB:
+		switch (JAS_IMAGE_CT_COLOR(ctype)) {
+		case JAS_IMAGE_CT_RGB_R:
+			type = JP2_CDEF_TYPE_COLOR;
+			asoc = JP2_CDEF_RGB_R;
+			break;
+		case JAS_IMAGE_CT_RGB_G:
+			type = JP2_CDEF_TYPE_COLOR;
+			asoc = JP2_CDEF_RGB_G;
+			break;
+		case JAS_IMAGE_CT_RGB_B:
+			type = JP2_CDEF_TYPE_COLOR;
+			asoc = JP2_CDEF_RGB_B;
+			break;
+		}
+		break;
+	case JAS_IMAGE_CS_YCBCR:
+		switch (JAS_IMAGE_CT_COLOR(ctype)) {
+		case JAS_IMAGE_CT_YCBCR_Y:
+			type = JP2_CDEF_TYPE_COLOR;
+			asoc = JP2_CDEF_YCBCR_Y;
+			break;
+		case JAS_IMAGE_CT_YCBCR_CB:
+			type = JP2_CDEF_TYPE_COLOR;
+			asoc = JP2_CDEF_YCBCR_CB;
+			break;
+		case JAS_IMAGE_CT_YCBCR_CR:
+			type = JP2_CDEF_TYPE_COLOR;
+			asoc = JP2_CDEF_YCBCR_CR;
+			break;
+		}
+		break;
+	case JAS_IMAGE_CS_GRAY:
+		type = JP2_CDEF_TYPE_COLOR;
+		asoc = JP2_CDEF_GRAY_Y;
+		break;
+	}
+
+done:
+	return (type << 16) | asoc;
+}
+
diff --git a/converter/other/jpeg2000/libjasper/jp2/partlist b/converter/other/jpeg2000/libjasper/jp2/partlist
new file mode 100644
index 00000000..26ec4c8a
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jp2/partlist
@@ -0,0 +1 @@
+/mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jp2/jp2_cod.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jp2/jp2_dec.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jp2/jp2_enc.o
diff --git a/converter/other/jpeg2000/libjasper/jpc/Makefile b/converter/other/jpeg2000/libjasper/jpc/Makefile
new file mode 100644
index 00000000..e176bd48
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/Makefile
@@ -0,0 +1,22 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../../../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/other/jpeg2000/libjasper/jpc
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+JASPERSRCDIR=$(SRCDIR)/$(SUBDIR)/..
+
+include $(BUILDDIR)/Makefile.config
+
+LIB_OBJECTS = jpc_bs.o jpc_cs.o jpc_dec.o jpc_enc.o \
+	jpc_math.o jpc_mct.o jpc_mqcod.o jpc_mqdec.o jpc_mqenc.o \
+	jpc_qmfb.o jpc_tagtree.o jpc_t1cod.o jpc_t1dec.o jpc_t1enc.o \
+	jpc_tsfb.o jpc_t2cod.o jpc_t2dec.o jpc_t2enc.o jpc_util.o
+
+MERGE_OBJECTS =
+
+all: partlist $(LIB_OBJECTS)
+
+include $(JASPERSRCDIR)/Makefile.common
+
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_bs.c b/converter/other/jpeg2000/libjasper/jpc/jpc_bs.c
new file mode 100644
index 00000000..54f0a819
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_bs.c
@@ -0,0 +1,478 @@
+/*
+ * Copyright (c) 1999-2000, Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Bit Stream Class
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_math.h"
+#include "jasper/jas_debug.h"
+
+#include "jpc_bs.h"
+
+/******************************************************************************\
+* Code for opening and closing bit streams.
+\******************************************************************************/
+
+/* Allocate a new bit stream. */
+static jpc_bitstream_t *jpc_bitstream_alloc()
+{
+	jpc_bitstream_t *bitstream;
+
+	/* Allocate memory for the new bit stream object. */
+	if (!(bitstream = jas_malloc(sizeof(jpc_bitstream_t)))) {
+		return 0;
+	}
+	/* Initialize all of the data members. */
+	bitstream->stream_ = 0;
+	bitstream->cnt_ = 0;
+	bitstream->flags_ = 0;
+	bitstream->openmode_ = 0;
+
+	return bitstream;
+}
+
+/* Open a bit stream from a stream. */
+jpc_bitstream_t *jpc_bitstream_sopen(jas_stream_t *stream, const char *mode)
+{
+	jpc_bitstream_t *bitstream;
+
+	/* Ensure that the open mode is valid. */
+	assert(!strcmp(mode, "r") || !strcmp(mode, "w") || !strcmp(mode, "r+")
+	  || !strcmp(mode, "w+"));
+
+	if (!(bitstream = jpc_bitstream_alloc())) {
+		return 0;
+	}
+
+	/* By default, do not close the underlying (character) stream, upon
+	  the close of the bit stream. */
+	bitstream->flags_ = JPC_BITSTREAM_NOCLOSE;
+
+	bitstream->stream_ = stream;
+	bitstream->openmode_ = (mode[0] == 'w') ? JPC_BITSTREAM_WRITE :
+	  JPC_BITSTREAM_READ;
+
+	/* Mark the data buffer as empty. */
+	bitstream->cnt_ = (bitstream->openmode_ == JPC_BITSTREAM_READ) ? 0 : 8;
+	bitstream->buf_ = 0;
+
+	return bitstream;
+}
+
+/* Close a bit stream. */
+int jpc_bitstream_close(jpc_bitstream_t *bitstream)
+{
+	int ret = 0;
+
+	/* Align to the next byte boundary while considering the effects of
+	  bit stuffing. */
+	if (jpc_bitstream_align(bitstream)) {
+		ret = -1;
+	}
+
+	/* If necessary, close the underlying (character) stream. */
+	if (!(bitstream->flags_ & JPC_BITSTREAM_NOCLOSE) && bitstream->stream_) {
+		if (jas_stream_close(bitstream->stream_)) {
+			ret = -1;
+		}
+		bitstream->stream_ = 0;
+	}
+
+	jas_free(bitstream);
+	return ret;
+}
+
+/******************************************************************************\
+* Code for reading/writing from/to bit streams.
+\******************************************************************************/
+
+/* Get a bit from a bit stream. */
+int jpc_bitstream_getbit_func(jpc_bitstream_t *bitstream)
+{
+	int ret;
+	JAS_DBGLOG(1000, ("jpc_bitstream_getbit_func(%p)\n", bitstream));
+	ret = jpc_bitstream_getbit_macro(bitstream);
+	JAS_DBGLOG(1000, ("jpc_bitstream_getbit_func -> %d\n", ret));
+	return ret;
+}
+
+/* Put a bit to a bit stream. */
+int jpc_bitstream_putbit_func(jpc_bitstream_t *bitstream, int b)
+{
+	int ret;
+	JAS_DBGLOG(1000, ("jpc_bitstream_putbit_func(%p, %d)\n", bitstream, b));
+	ret = jpc_bitstream_putbit_macro(bitstream, b);
+	JAS_DBGLOG(1000, ("jpc_bitstream_putbit_func() -> %d\n", ret));
+	return ret;
+}
+
+/* Get one or more bits from a bit stream. */
+long jpc_bitstream_getbits(jpc_bitstream_t *bitstream, int n)
+{
+	long v;
+	int u;
+
+	/* We can reliably get at most 31 bits since ISO/IEC 9899 only
+	  guarantees that a long can represent values up to 2^31-1. */
+	assert(n >= 0 && n < 32);
+
+	/* Get the number of bits requested from the specified bit stream. */
+	v = 0;
+	while (--n >= 0) {
+		if ((u = jpc_bitstream_getbit(bitstream)) < 0) {
+			return -1;
+		}
+		v = (v << 1) | u;
+	}
+	return v;
+}
+
+/* Put one or more bits to a bit stream. */
+int jpc_bitstream_putbits(jpc_bitstream_t *bitstream, int n, long v)
+{
+	int m;
+
+	/* We can reliably put at most 31 bits since ISO/IEC 9899 only
+	  guarantees that a long can represent values up to 2^31-1. */
+	assert(n >= 0 && n < 32);
+	/* Ensure that only the bits to be output are nonzero. */
+	assert(!(v & (~JAS_ONES(n))));
+
+	/* Put the desired number of bits to the specified bit stream. */
+	m = n - 1;
+	while (--n >= 0) {
+		if (jpc_bitstream_putbit(bitstream, (v >> m) & 1) == EOF) {
+			return EOF;
+		}
+		v <<= 1;
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* Code for buffer filling and flushing.
+\******************************************************************************/
+
+/* Fill the buffer for a bit stream. */
+int jpc_bitstream_fillbuf(jpc_bitstream_t *bitstream)
+{
+	int c;
+	/* Note: The count has already been decremented by the caller. */
+	assert(bitstream->openmode_ & JPC_BITSTREAM_READ);
+	assert(bitstream->cnt_ <= 0);
+
+	if (bitstream->flags_ & JPC_BITSTREAM_ERR) {
+		bitstream->cnt_ = 0;
+		return -1;
+	}
+
+	if (bitstream->flags_ & JPC_BITSTREAM_EOF) {
+		bitstream->buf_ = 0x7f;
+		bitstream->cnt_ = 7;
+		return 1;
+	}
+
+	bitstream->buf_ = (bitstream->buf_ << 8) & 0xffff;
+	if ((c = jas_stream_getc((bitstream)->stream_)) == EOF) {
+		bitstream->flags_ |= JPC_BITSTREAM_EOF;
+		return 1;
+	}
+	bitstream->cnt_ = (bitstream->buf_ == 0xff00) ? 6 : 7;
+	bitstream->buf_ |= c & ((1 << (bitstream->cnt_ + 1)) - 1);
+	return (bitstream->buf_ >> bitstream->cnt_) & 1;
+}
+
+
+/******************************************************************************\
+* Code related to flushing.
+\******************************************************************************/
+
+/* Does the bit stream need to be aligned to a byte boundary (considering
+  the effects of bit stuffing)? */
+int jpc_bitstream_needalign(jpc_bitstream_t *bitstream)
+{
+	if (bitstream->openmode_ & JPC_BITSTREAM_READ) {
+		/* The bit stream is open for reading. */
+		/* If there are any bits buffered for reading, or the
+		  previous byte forced a stuffed bit, alignment is
+		  required. */
+		if ((bitstream->cnt_ < 8 && bitstream->cnt_ > 0) ||
+		  ((bitstream->buf_ >> 8) & 0xff) == 0xff) {
+			return 1;
+		}
+	} else if (bitstream->openmode_ & JPC_BITSTREAM_WRITE) {
+		/* The bit stream is open for writing. */
+		/* If there are any bits buffered for writing, or the
+		  previous byte forced a stuffed bit, alignment is
+		  required. */
+		if ((bitstream->cnt_ < 8 && bitstream->cnt_ >= 0) ||
+		  ((bitstream->buf_ >> 8) & 0xff) == 0xff) {
+			return 1;
+		}
+	} else {
+		/* This should not happen.  Famous last words, eh? :-) */
+		assert(0);
+		return -1;
+	}
+	return 0;
+}
+
+/* How many additional bytes would be output if we align the bit stream? */
+int jpc_bitstream_pending(jpc_bitstream_t *bitstream)
+{
+	if (bitstream->openmode_ & JPC_BITSTREAM_WRITE) {
+		/* The bit stream is being used for writing. */
+#if 1
+		/* XXX - Is this really correct?  Check someday... */
+		if (bitstream->cnt_ < 8) {
+			return 1;
+		}
+#else
+		if (bitstream->cnt_ < 8) {
+			if (((bitstream->buf_ >> 8) & 0xff) == 0xff) {
+				return 2;
+			}
+			return 1;
+		}
+#endif
+		return 0;
+	} else {
+		/* This operation should not be invoked on a bit stream that
+		  is being used for reading. */
+		return -1;
+	}
+}
+
+/* Align the bit stream to a byte boundary. */
+int jpc_bitstream_align(jpc_bitstream_t *bitstream)
+{
+	int ret;
+	if (bitstream->openmode_ & JPC_BITSTREAM_READ) {
+		ret = jpc_bitstream_inalign(bitstream, 0, 0);
+	} else if (bitstream->openmode_ & JPC_BITSTREAM_WRITE) {
+		ret = jpc_bitstream_outalign(bitstream, 0);
+	}
+	return ret;
+}
+
+/* Align a bit stream in the input case. */
+int jpc_bitstream_inalign(jpc_bitstream_t *bitstream, int fillmask,
+  int filldata)
+{
+	int n;
+	int v;
+	int u;
+	int numfill;
+	int m;
+
+	numfill = 7;
+	m = 0;
+	v = 0;
+	if (bitstream->cnt_ > 0) {
+		n = bitstream->cnt_;
+	} else if (!bitstream->cnt_) {
+		n = ((bitstream->buf_ & 0xff) == 0xff) ? 7 : 0;
+	} else {
+		n = 0;
+	}
+	if (n > 0) {
+		if ((u = jpc_bitstream_getbits(bitstream, n)) < 0) {
+			return -1;
+		}
+		m += n;
+		v = (v << n) | u;
+	}
+	if ((bitstream->buf_ & 0xff) == 0xff) {
+		if ((u = jpc_bitstream_getbits(bitstream, 7)) < 0) {
+			return -1;
+		}
+		v = (v << 7) | u;
+		m += 7;
+	}
+	if (m > numfill) {
+		v >>= m - numfill;
+	} else {
+		filldata >>= numfill - m;
+		fillmask >>= numfill - m;
+	}
+	if (((~(v ^ filldata)) & fillmask) != fillmask) {
+		/* The actual fill pattern does not match the expected one. */
+		return 1;
+	}
+
+	return 0;
+}
+
+/* Align a bit stream in the output case. */
+int jpc_bitstream_outalign(jpc_bitstream_t *bitstream, int filldata)
+{
+	int n;
+	int v;
+
+	/* Ensure that this bit stream is open for writing. */
+	assert(bitstream->openmode_ & JPC_BITSTREAM_WRITE);
+
+	/* Ensure that the first bit of fill data is zero. */
+	/* Note: The first bit of fill data must be zero.  If this were not
+	  the case, the fill data itself could cause further bit stuffing to
+	  be required (which would cause numerous complications). */
+	assert(!(filldata & (~0x3f)));
+
+	if (!bitstream->cnt_) {
+		if ((bitstream->buf_ & 0xff) == 0xff) {
+			n = 7;
+			v = filldata;
+		} else {
+			n = 0;
+			v = 0;
+		}
+	} else if (bitstream->cnt_ > 0 && bitstream->cnt_ < 8) {
+		n = bitstream->cnt_;
+		v = filldata >> (7 - n);
+	} else {
+		n = 0;
+		v = 0;
+		return 0;
+	}
+
+	/* Write the appropriate fill data to the bit stream. */
+	if (n > 0) {
+		if (jpc_bitstream_putbits(bitstream, n, v)) {
+			return -1;
+		}
+	}
+	if (bitstream->cnt_ < 8) {
+		assert(bitstream->cnt_ >= 0 && bitstream->cnt_ < 8);
+		assert((bitstream->buf_ & 0xff) != 0xff);
+		/* Force the pending byte of output to be written to the
+		  underlying (character) stream. */
+		if (jas_stream_putc(bitstream->stream_, bitstream->buf_ & 0xff) == EOF) {
+			return -1;
+		}
+		bitstream->cnt_ = 8;
+		bitstream->buf_ = (bitstream->buf_ << 8) & 0xffff;
+	}
+
+	return 0;
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_bs.h b/converter/other/jpeg2000/libjasper/jpc/jpc_bs.h
new file mode 100644
index 00000000..f515972b
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_bs.h
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Bit Stream Class
+ *
+ * $Id$
+ */
+
+#ifndef JPC_BS_H
+#define JPC_BS_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdio.h>
+
+#include "jasper/jas_types.h"
+#include "jasper/jas_stream.h"
+
+/******************************************************************************\
+* Constants.
+\******************************************************************************/
+
+/*
+ * Bit stream open mode flags.
+ */
+
+/* Bit stream open for reading. */
+#define	JPC_BITSTREAM_READ	0x01
+/* Bit stream open for writing. */
+#define	JPC_BITSTREAM_WRITE	0x02
+
+/*
+ * Bit stream flags.
+ */
+
+/* Do not close underlying character stream. */
+#define	JPC_BITSTREAM_NOCLOSE	0x01
+/* End of file has been reached while reading. */
+#define	JPC_BITSTREAM_EOF	0x02
+/* An I/O error has occured. */
+#define	JPC_BITSTREAM_ERR	0x04
+
+/******************************************************************************\
+* Types.
+\******************************************************************************/
+
+/* Bit stream class. */
+
+typedef struct {
+
+	/* Some miscellaneous flags. */
+	int flags_;
+
+	/* The input/output buffer. */
+	uint_fast16_t buf_;
+
+	/* The number of bits remaining in the byte being read/written. */
+	int cnt_;
+
+	/* The underlying stream associated with this bit stream. */
+	jas_stream_t *stream_;
+
+	/* The mode in which this bit stream was opened. */
+	int openmode_;
+
+} jpc_bitstream_t;
+
+/******************************************************************************\
+* Functions/macros for opening and closing bit streams..
+\******************************************************************************/
+
+/* Open a stream as a bit stream. */
+jpc_bitstream_t *jpc_bitstream_sopen(jas_stream_t *stream, const char *mode);
+
+/* Close a bit stream. */
+int jpc_bitstream_close(jpc_bitstream_t *bitstream);
+
+/******************************************************************************\
+* Functions/macros for reading from and writing to bit streams..
+\******************************************************************************/
+
+/* Read a bit from a bit stream. */
+#if defined(DEBUG)
+#define	jpc_bitstream_getbit(bitstream) \
+	jpc_bitstream_getbit_func(bitstream)
+#else
+#define jpc_bitstream_getbit(bitstream) \
+	jpc_bitstream_getbit_macro(bitstream)
+#endif
+
+/* Write a bit to a bit stream. */
+#if defined(DEBUG)
+#define	jpc_bitstream_putbit(bitstream, v) \
+	jpc_bitstream_putbit_func(bitstream, v)
+#else
+#define	jpc_bitstream_putbit(bitstream, v) \
+	jpc_bitstream_putbit_macro(bitstream, v)
+#endif
+
+/* Read one or more bits from a bit stream. */
+long jpc_bitstream_getbits(jpc_bitstream_t *bitstream, int n);
+
+/* Write one or more bits to a bit stream. */
+int jpc_bitstream_putbits(jpc_bitstream_t *bitstream, int n, long v);
+
+/******************************************************************************\
+* Functions/macros for flushing and aligning bit streams.
+\******************************************************************************/
+
+/* Align the current position within the bit stream to the next byte
+  boundary. */
+int jpc_bitstream_align(jpc_bitstream_t *bitstream);
+
+/* Align the current position in the bit stream with the next byte boundary,
+  ensuring that certain bits consumed in the process match a particular
+  pattern. */
+int jpc_bitstream_inalign(jpc_bitstream_t *bitstream, int fillmask,
+  int filldata);
+
+/* Align the current position in the bit stream with the next byte boundary,
+  writing bits from the specified pattern (if necessary) in the process. */
+int jpc_bitstream_outalign(jpc_bitstream_t *bitstream, int filldata);
+
+/* Check if a bit stream needs alignment. */
+int jpc_bitstream_needalign(jpc_bitstream_t *bitstream);
+
+/* How many additional bytes would be output if the bit stream was aligned? */
+int jpc_bitstream_pending(jpc_bitstream_t *bitstream);
+
+/******************************************************************************\
+* Functions/macros for querying state information for bit streams.
+\******************************************************************************/
+
+/* Has EOF been encountered on a bit stream? */
+#define jpc_bitstream_eof(bitstream) \
+	((bitstream)->flags_ & JPC_BITSTREAM_EOF)
+
+/******************************************************************************\
+* Internals.
+\******************************************************************************/
+
+/* DO NOT DIRECTLY INVOKE ANY OF THE MACROS OR FUNCTIONS BELOW.  THEY ARE
+  FOR INTERNAL USE ONLY. */
+
+int jpc_bitstream_getbit_func(jpc_bitstream_t *bitstream);
+
+int jpc_bitstream_putbit_func(jpc_bitstream_t *bitstream, int v);
+
+int jpc_bitstream_fillbuf(jpc_bitstream_t *bitstream);
+
+#define	jpc_bitstream_getbit_macro(bitstream) \
+	(assert((bitstream)->openmode_ & JPC_BITSTREAM_READ), \
+	  (--(bitstream)->cnt_ >= 0) ? \
+	  (((bitstream)->buf_ >> (bitstream)->cnt_) & 1) : \
+	  jpc_bitstream_fillbuf(bitstream))
+
+#define jpc_bitstream_putbit_macro(bitstream, bit) \
+	(assert((bitstream)->openmode_ & JPC_BITSTREAM_WRITE), \
+	  (--(bitstream)->cnt_ < 0) ? \
+	  ((bitstream)->buf_ = ((bitstream)->buf_ << 8) & 0xffff, \
+	  (bitstream)->cnt_ = ((bitstream)->buf_ == 0xff00) ? 6 : 7, \
+	  (bitstream)->buf_ |= ((bit) & 1) << (bitstream)->cnt_, \
+	  (jas_stream_putc((bitstream)->stream_, (bitstream)->buf_ >> 8) == EOF) \
+	  ? (EOF) : ((bit) & 1)) : \
+	  ((bitstream)->buf_ |= ((bit) & 1) << (bitstream)->cnt_, \
+	  (bit) & 1))
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_cod.h b/converter/other/jpeg2000/libjasper/jpc/jpc_cod.h
new file mode 100644
index 00000000..9b3ddbee
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_cod.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef JPC_COD_H
+#define JPC_COD_H
+
+/******************************************************************************\
+* Constants.
+\******************************************************************************/
+
+/* The nominal word size used by this implementation. */
+#define	JPC_PREC	32
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_cs.c b/converter/other/jpeg2000/libjasper/jpc/jpc_cs.c
new file mode 100644
index 00000000..63cf8ba7
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_cs.c
@@ -0,0 +1,1614 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * JPEG-2000 Code Stream Library
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdlib.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_debug.h"
+
+#include "jpc_cs.h"
+
+/******************************************************************************\
+* Types.
+\******************************************************************************/
+
+/* Marker segment table entry. */
+typedef struct {
+	int id;
+	const char *name;
+	jpc_msops_t ops;
+} jpc_mstabent_t;
+
+/******************************************************************************\
+* Local prototypes.
+\******************************************************************************/
+
+static jpc_mstabent_t *jpc_mstab_lookup(int id);
+
+static int jpc_poc_dumpparms(jpc_ms_t *ms, FILE *out);
+static int jpc_poc_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out);
+static int jpc_poc_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in);
+static void jpc_poc_destroyparms(jpc_ms_t *ms);
+
+static int jpc_unk_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in);
+static int jpc_sot_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in);
+static int jpc_siz_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in);
+static int jpc_cod_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in);
+static int jpc_coc_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in);
+static int jpc_qcd_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in);
+static int jpc_qcc_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in);
+static int jpc_rgn_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in);
+static int jpc_sop_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in);
+static int jpc_ppm_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in);
+static int jpc_ppt_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in);
+static int jpc_crg_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in);
+static int jpc_com_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in);
+
+static int jpc_sot_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out);
+static int jpc_siz_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out);
+static int jpc_cod_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out);
+static int jpc_coc_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out);
+static int jpc_qcd_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out);
+static int jpc_qcc_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out);
+static int jpc_rgn_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out);
+static int jpc_unk_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out);
+static int jpc_sop_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out);
+static int jpc_ppm_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out);
+static int jpc_ppt_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out);
+static int jpc_crg_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out);
+static int jpc_com_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out);
+
+static int jpc_sot_dumpparms(jpc_ms_t *ms, FILE *out);
+static int jpc_siz_dumpparms(jpc_ms_t *ms, FILE *out);
+static int jpc_cod_dumpparms(jpc_ms_t *ms, FILE *out);
+static int jpc_coc_dumpparms(jpc_ms_t *ms, FILE *out);
+static int jpc_qcd_dumpparms(jpc_ms_t *ms, FILE *out);
+static int jpc_qcc_dumpparms(jpc_ms_t *ms, FILE *out);
+static int jpc_rgn_dumpparms(jpc_ms_t *ms, FILE *out);
+static int jpc_unk_dumpparms(jpc_ms_t *ms, FILE *out);
+static int jpc_sop_dumpparms(jpc_ms_t *ms, FILE *out);
+static int jpc_ppm_dumpparms(jpc_ms_t *ms, FILE *out);
+static int jpc_ppt_dumpparms(jpc_ms_t *ms, FILE *out);
+static int jpc_crg_dumpparms(jpc_ms_t *ms, FILE *out);
+static int jpc_com_dumpparms(jpc_ms_t *ms, FILE *out);
+
+static void jpc_siz_destroyparms(jpc_ms_t *ms);
+static void jpc_qcd_destroyparms(jpc_ms_t *ms);
+static void jpc_qcc_destroyparms(jpc_ms_t *ms);
+static void jpc_cod_destroyparms(jpc_ms_t *ms);
+static void jpc_coc_destroyparms(jpc_ms_t *ms);
+static void jpc_unk_destroyparms(jpc_ms_t *ms);
+static void jpc_ppm_destroyparms(jpc_ms_t *ms);
+static void jpc_ppt_destroyparms(jpc_ms_t *ms);
+static void jpc_crg_destroyparms(jpc_ms_t *ms);
+static void jpc_com_destroyparms(jpc_ms_t *ms);
+
+static void jpc_qcx_destroycompparms(jpc_qcxcp_t *compparms);
+static int jpc_qcx_getcompparms(jpc_qcxcp_t *compparms, jpc_cstate_t *cstate,
+  jas_stream_t *in, uint_fast16_t len);
+static int jpc_qcx_putcompparms(jpc_qcxcp_t *compparms, jpc_cstate_t *cstate,
+  jas_stream_t *out);
+static void jpc_cox_destroycompparms(jpc_coxcp_t *compparms);
+static int jpc_cox_getcompparms(jpc_ms_t *ms, jpc_cstate_t *cstate,
+  jas_stream_t *in, int prtflag, jpc_coxcp_t *compparms);
+static int jpc_cox_putcompparms(jpc_ms_t *ms, jpc_cstate_t *cstate,
+  jas_stream_t *out, int prtflag, jpc_coxcp_t *compparms);
+
+/******************************************************************************\
+* Global data.
+\******************************************************************************/
+
+static jpc_mstabent_t jpc_mstab[] = {
+	{JPC_MS_SOC, "SOC", {0, 0, 0, 0}},
+	{JPC_MS_SOT, "SOT", {0, jpc_sot_getparms, jpc_sot_putparms,
+	  jpc_sot_dumpparms}},
+	{JPC_MS_SOD, "SOD", {0, 0, 0, 0}},
+	{JPC_MS_EOC, "EOC", {0, 0, 0, 0}},
+	{JPC_MS_SIZ, "SIZ", {jpc_siz_destroyparms, jpc_siz_getparms,
+	  jpc_siz_putparms, jpc_siz_dumpparms}},
+	{JPC_MS_COD, "COD", {jpc_cod_destroyparms, jpc_cod_getparms,
+	  jpc_cod_putparms, jpc_cod_dumpparms}},
+	{JPC_MS_COC, "COC", {jpc_coc_destroyparms, jpc_coc_getparms,
+	  jpc_coc_putparms, jpc_coc_dumpparms}},
+	{JPC_MS_RGN, "RGN", {0, jpc_rgn_getparms, jpc_rgn_putparms,
+	  jpc_rgn_dumpparms}},
+	{JPC_MS_QCD, "QCD", {jpc_qcd_destroyparms, jpc_qcd_getparms,
+	  jpc_qcd_putparms, jpc_qcd_dumpparms}},
+	{JPC_MS_QCC, "QCC", {jpc_qcc_destroyparms, jpc_qcc_getparms,
+	  jpc_qcc_putparms, jpc_qcc_dumpparms}},
+	{JPC_MS_POC, "POC", {jpc_poc_destroyparms, jpc_poc_getparms,
+	  jpc_poc_putparms, jpc_poc_dumpparms}},
+	{JPC_MS_TLM, "TLM", {0, jpc_unk_getparms, jpc_unk_putparms, 0}},
+	{JPC_MS_PLM, "PLM", {0, jpc_unk_getparms, jpc_unk_putparms, 0}},
+	{JPC_MS_PPM, "PPM", {jpc_ppm_destroyparms, jpc_ppm_getparms,
+	  jpc_ppm_putparms, jpc_ppm_dumpparms}},
+	{JPC_MS_PPT, "PPT", {jpc_ppt_destroyparms, jpc_ppt_getparms,
+	  jpc_ppt_putparms, jpc_ppt_dumpparms}},
+	{JPC_MS_SOP, "SOP", {0, jpc_sop_getparms, jpc_sop_putparms,
+	  jpc_sop_dumpparms}},
+	{JPC_MS_EPH, "EPH", {0, 0, 0, 0}},
+	{JPC_MS_CRG, "CRG", {0, jpc_crg_getparms, jpc_crg_putparms,
+	  jpc_crg_dumpparms}},
+	{JPC_MS_COM, "COM", {jpc_com_destroyparms, jpc_com_getparms,
+	  jpc_com_putparms, jpc_com_dumpparms}},
+	{-1, "UNKNOWN",  {jpc_unk_destroyparms, jpc_unk_getparms,
+	  jpc_unk_putparms, jpc_unk_dumpparms}}
+};
+
+/******************************************************************************\
+* Code stream manipulation functions.
+\******************************************************************************/
+
+/* Create a code stream state object. */
+jpc_cstate_t *jpc_cstate_create()
+{
+	jpc_cstate_t *cstate;
+	if (!(cstate = jas_malloc(sizeof(jpc_cstate_t)))) {
+		return 0;
+	}
+	cstate->numcomps = 0;
+	return cstate;
+}
+
+/* Destroy a code stream state object. */
+void jpc_cstate_destroy(jpc_cstate_t *cstate)
+{
+	jas_free(cstate);
+}
+
+/* Read a marker segment from a stream. */
+jpc_ms_t *jpc_getms(jas_stream_t *in, jpc_cstate_t *cstate)
+{
+	jpc_ms_t *ms;
+	jpc_mstabent_t *mstabent;
+	jas_stream_t *tmpstream;
+
+	if (!(ms = jpc_ms_create(0))) {
+		return 0;
+	}
+
+	/* Get the marker type. */
+	if (jpc_getuint16(in, &ms->id) || ms->id < JPC_MS_MIN ||
+	  ms->id > JPC_MS_MAX) {
+		jpc_ms_destroy(ms);
+		return 0;
+	}
+
+	mstabent = jpc_mstab_lookup(ms->id);
+	ms->ops = &mstabent->ops;
+
+	/* Get the marker segment length and parameters if present. */
+	/* Note: It is tacitly assumed that a marker segment cannot have
+	  parameters unless it has a length field.  That is, there cannot
+	  be a parameters field without a length field and vice versa. */
+	if (JPC_MS_HASPARMS(ms->id)) {
+		/* Get the length of the marker segment. */
+		if (jpc_getuint16(in, &ms->len) || ms->len < 3) {
+			jpc_ms_destroy(ms);
+			return 0;
+		}
+		/* Calculate the length of the marker segment parameters. */
+		ms->len -= 2;
+		/* Create and prepare a temporary memory stream from which to
+		  read the marker segment parameters. */
+		/* Note: This approach provides a simple way of ensuring that
+		  we never read beyond the end of the marker segment (even if
+		  the marker segment length is errantly set too small). */
+		if (!(tmpstream = jas_stream_memopen(0, 0))) {
+			jpc_ms_destroy(ms);
+			return 0;
+		}
+		if (jas_stream_copy(tmpstream, in, ms->len) ||
+		  jas_stream_seek(tmpstream, 0, SEEK_SET) < 0) {
+			jas_stream_close(tmpstream);
+			jpc_ms_destroy(ms);
+			return 0;
+		}
+		/* Get the marker segment parameters. */
+		if ((*ms->ops->getparms)(ms, cstate, tmpstream)) {
+			ms->ops = 0;
+			jpc_ms_destroy(ms);
+			jas_stream_close(tmpstream);
+			return 0;
+		}
+
+		if (jas_getdbglevel() > 0) {
+			jpc_ms_dump(ms, stderr);
+		}
+
+		if (jas_stream_tell(tmpstream) != ms->len) {
+			fprintf(stderr,
+			  "warning: trailing garbage in marker segment (%ld bytes)\n",
+			  ms->len - jas_stream_tell(tmpstream));
+		}
+
+		/* Close the temporary stream. */
+		jas_stream_close(tmpstream);
+
+	} else {
+		/* There are no marker segment parameters. */
+		ms->len = 0;
+
+		if (jas_getdbglevel() > 0) {
+			jpc_ms_dump(ms, stderr);
+		}
+	}
+
+	/* Update the code stream state information based on the type of
+	  marker segment read. */
+	/* Note: This is a bit of a hack, but I'm not going to define another
+	  type of virtual function for this one special case. */
+	if (ms->id == JPC_MS_SIZ) {
+		cstate->numcomps = ms->parms.siz.numcomps;
+	}
+
+	return ms;
+}
+
+/* Write a marker segment to a stream. */
+int jpc_putms(jas_stream_t *out, jpc_cstate_t *cstate, jpc_ms_t *ms)
+{
+	jas_stream_t *tmpstream;
+	int len;
+
+	/* Output the marker segment type. */
+	if (jpc_putuint16(out, ms->id)) {
+		return -1;
+	}
+
+	/* Output the marker segment length and parameters if necessary. */
+	if (ms->ops->putparms) {
+		/* Create a temporary stream in which to buffer the
+		  parameter data. */
+		if (!(tmpstream = jas_stream_memopen(0, 0))) {
+			return -1;
+		}
+		if ((*ms->ops->putparms)(ms, cstate, tmpstream)) {
+			jas_stream_close(tmpstream);
+			return -1;
+		}
+		/* Get the number of bytes of parameter data written. */
+		if ((len = jas_stream_tell(tmpstream)) < 0) {
+			jas_stream_close(tmpstream);
+			return -1;
+		}
+		ms->len = len;
+		/* Write the marker segment length and parameter data to
+		  the output stream. */
+		if (jas_stream_seek(tmpstream, 0, SEEK_SET) < 0 ||
+		  jpc_putuint16(out, ms->len + 2) ||
+		  jas_stream_copy(out, tmpstream, ms->len) < 0) {
+			jas_stream_close(tmpstream);
+			return -1;
+		}
+		/* Close the temporary stream. */
+		jas_stream_close(tmpstream);
+	}
+
+	/* This is a bit of a hack, but I'm not going to define another
+	  type of virtual function for this one special case. */
+	if (ms->id == JPC_MS_SIZ) {
+		cstate->numcomps = ms->parms.siz.numcomps;
+	}
+
+	if (jas_getdbglevel() > 0) {
+		jpc_ms_dump(ms, stderr);
+	}
+
+	return 0;
+}
+
+/******************************************************************************\
+* Marker segment operations.
+\******************************************************************************/
+
+/* Create a marker segment of the specified type. */
+jpc_ms_t *jpc_ms_create(int type)
+{
+	jpc_ms_t *ms;
+	jpc_mstabent_t *mstabent;
+
+	if (!(ms = jas_malloc(sizeof(jpc_ms_t)))) {
+		return 0;
+	}
+	ms->id = type;
+	ms->len = 0;
+	mstabent = jpc_mstab_lookup(ms->id);
+	ms->ops = &mstabent->ops;
+	memset(&ms->parms, 0, sizeof(jpc_msparms_t));
+	return ms;
+}
+
+/* Destroy a marker segment. */
+void jpc_ms_destroy(jpc_ms_t *ms)
+{
+	if (ms->ops && ms->ops->destroyparms) {
+		(*ms->ops->destroyparms)(ms);
+	}
+	jas_free(ms);
+}
+
+/* Dump a marker segment to a stream for debugging. */
+void jpc_ms_dump(jpc_ms_t *ms, FILE *out)
+{
+	jpc_mstabent_t *mstabent;
+	mstabent = jpc_mstab_lookup(ms->id);
+	fprintf(out, "type = 0x%04x (%s);", ms->id, mstabent->name);
+	if (JPC_MS_HASPARMS(ms->id)) {
+		fprintf(out, " len = %d;", ms->len + 2);
+		if (ms->ops->dumpparms) {
+			(*ms->ops->dumpparms)(ms, out);
+		} else {
+			fprintf(out, "\n");
+		}
+	} else {
+		fprintf(out, "\n");
+	}
+}
+
+/******************************************************************************\
+* SOT marker segment operations.
+\******************************************************************************/
+
+static int jpc_sot_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in)
+{
+	jpc_sot_t *sot = &ms->parms.sot;
+	if (jpc_getuint16(in, &sot->tileno) ||
+	  jpc_getuint32(in, &sot->len) ||
+	  jpc_getuint8(in, &sot->partno) ||
+	  jpc_getuint8(in, &sot->numparts)) {
+		return -1;
+	}
+	if (jas_stream_eof(in)) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_sot_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out)
+{
+	jpc_sot_t *sot = &ms->parms.sot;
+	if (jpc_putuint16(out, sot->tileno) ||
+	  jpc_putuint32(out, sot->len) ||
+	  jpc_putuint8(out, sot->partno) ||
+	  jpc_putuint8(out, sot->numparts)) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_sot_dumpparms(jpc_ms_t *ms, FILE *out)
+{
+	jpc_sot_t *sot = &ms->parms.sot;
+	fprintf(out, "tileno = %d; len = %d; partno = %d; numparts = %d\n",
+	  sot->tileno, sot->len, sot->partno, sot->numparts);
+	return 0;
+}
+
+/******************************************************************************\
+* SIZ marker segment operations.
+\******************************************************************************/
+
+static void jpc_siz_destroyparms(jpc_ms_t *ms)
+{
+	jpc_siz_t *siz = &ms->parms.siz;
+	if (siz->comps) {
+		jas_free(siz->comps);
+	}
+}
+
+static int jpc_siz_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in)
+{
+	jpc_siz_t *siz = &ms->parms.siz;
+	int i;
+	uint_fast8_t tmp;
+	if (jpc_getuint16(in, &siz->caps) ||
+	  jpc_getuint32(in, &siz->width) ||
+	  jpc_getuint32(in, &siz->height) ||
+	  jpc_getuint32(in, &siz->xoff) ||
+	  jpc_getuint32(in, &siz->yoff) ||
+	  jpc_getuint32(in, &siz->tilewidth) ||
+	  jpc_getuint32(in, &siz->tileheight) ||
+	  jpc_getuint32(in, &siz->tilexoff) ||
+	  jpc_getuint32(in, &siz->tileyoff) ||
+	  jpc_getuint16(in, &siz->numcomps)) {
+		return -1;
+	}
+	if (!siz->width || !siz->height || !siz->tilewidth ||
+	  !siz->tileheight || !siz->numcomps) {
+		return -1;
+	}
+	if (!(siz->comps = jas_malloc(siz->numcomps * sizeof(jpc_sizcomp_t)))) {
+		return -1;
+	}
+	for (i = 0; i < siz->numcomps; ++i) {
+		if (jpc_getuint8(in, &tmp) ||
+		  jpc_getuint8(in, &siz->comps[i].hsamp) ||
+		  jpc_getuint8(in, &siz->comps[i].vsamp)) {
+			jas_free(siz->comps);
+			return -1;
+		}
+		siz->comps[i].sgnd = (tmp >> 7) & 1;
+		siz->comps[i].prec = (tmp & 0x7f) + 1;
+	}
+	if (jas_stream_eof(in)) {
+		jas_free(siz->comps);
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_siz_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out)
+{
+	jpc_siz_t *siz = &ms->parms.siz;
+	int i;
+	assert(siz->width && siz->height && siz->tilewidth &&
+	  siz->tileheight && siz->numcomps);
+	if (jpc_putuint16(out, siz->caps) ||
+	  jpc_putuint32(out, siz->width) ||
+	  jpc_putuint32(out, siz->height) ||
+	  jpc_putuint32(out, siz->xoff) ||
+	  jpc_putuint32(out, siz->yoff) ||
+	  jpc_putuint32(out, siz->tilewidth) ||
+	  jpc_putuint32(out, siz->tileheight) ||
+	  jpc_putuint32(out, siz->tilexoff) ||
+	  jpc_putuint32(out, siz->tileyoff) ||
+	  jpc_putuint16(out, siz->numcomps)) {
+		return -1;
+	}
+	for (i = 0; i < siz->numcomps; ++i) {
+		if (jpc_putuint8(out, ((siz->comps[i].sgnd & 1) << 7) |
+		  ((siz->comps[i].prec - 1) & 0x7f)) ||
+		  jpc_putuint8(out, siz->comps[i].hsamp) ||
+		  jpc_putuint8(out, siz->comps[i].vsamp)) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static int jpc_siz_dumpparms(jpc_ms_t *ms, FILE *out)
+{
+	jpc_siz_t *siz = &ms->parms.siz;
+	int i;
+	fprintf(out, "caps = 0x%02x;\n", siz->caps);
+	fprintf(out, "width = %d; height = %d; xoff = %d; yoff = %d;\n",
+	  siz->width, siz->height, siz->xoff, siz->yoff);
+	fprintf(out, "tilewidth = %d; tileheight = %d; tilexoff = %d; "
+	  "tileyoff = %d;\n", siz->tilewidth, siz->tileheight, siz->tilexoff,
+	  siz->tileyoff);
+	for (i = 0; i < siz->numcomps; ++i) {
+		fprintf(out, "prec[%d] = %d; sgnd[%d] = %d; hsamp[%d] = %d; "
+		  "vsamp[%d] = %d\n", i, siz->comps[i].prec, i,
+		  siz->comps[i].sgnd, i, siz->comps[i].hsamp, i,
+		  siz->comps[i].vsamp);
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* COD marker segment operations.
+\******************************************************************************/
+
+static void jpc_cod_destroyparms(jpc_ms_t *ms)
+{
+	jpc_cod_t *cod = &ms->parms.cod;
+	jpc_cox_destroycompparms(&cod->compparms);
+}
+
+static int jpc_cod_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in)
+{
+	jpc_cod_t *cod = &ms->parms.cod;
+	if (jpc_getuint8(in, &cod->csty)) {
+		return -1;
+	}
+	if (jpc_getuint8(in, &cod->prg) ||
+	  jpc_getuint16(in, &cod->numlyrs) ||
+	  jpc_getuint8(in, &cod->mctrans)) {
+		return -1;
+	}
+	if (jpc_cox_getcompparms(ms, cstate, in,
+	  (cod->csty & JPC_COX_PRT) != 0, &cod->compparms)) {
+		return -1;
+	}
+	if (jas_stream_eof(in)) {
+		jpc_cod_destroyparms(ms);
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_cod_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out)
+{
+	jpc_cod_t *cod = &ms->parms.cod;
+	assert(cod->numlyrs > 0 && cod->compparms.numdlvls <= 32);
+	assert(cod->compparms.numdlvls == cod->compparms.numrlvls - 1);
+	if (jpc_putuint8(out, cod->compparms.csty) ||
+	  jpc_putuint8(out, cod->prg) ||
+	  jpc_putuint16(out, cod->numlyrs) ||
+	  jpc_putuint8(out, cod->mctrans)) {
+		return -1;
+	}
+	if (jpc_cox_putcompparms(ms, cstate, out,
+	  (cod->csty & JPC_COX_PRT) != 0, &cod->compparms)) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_cod_dumpparms(jpc_ms_t *ms, FILE *out)
+{
+	jpc_cod_t *cod = &ms->parms.cod;
+	int i;
+	fprintf(out, "csty = 0x%02x;\n", cod->compparms.csty);
+	fprintf(out, "numdlvls = %d; qmfbid = %d; mctrans = %d\n",
+	  cod->compparms.numdlvls, cod->compparms.qmfbid, cod->mctrans);
+	fprintf(out, "prg = %d; numlyrs = %d;\n",
+	  cod->prg, cod->numlyrs);
+	fprintf(out, "cblkwidthval = %d; cblkheightval = %d; "
+	  "cblksty = 0x%02x;\n", cod->compparms.cblkwidthval, cod->compparms.cblkheightval,
+	  cod->compparms.cblksty);
+	if (cod->csty & JPC_COX_PRT) {
+		for (i = 0; i < cod->compparms.numrlvls; ++i) {
+			fprintf(stderr, "prcwidth[%d] = %d, prcheight[%d] = %d\n",
+			  i, cod->compparms.rlvls[i].parwidthval,
+			  i, cod->compparms.rlvls[i].parheightval);
+		}
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* COC marker segment operations.
+\******************************************************************************/
+
+static void jpc_coc_destroyparms(jpc_ms_t *ms)
+{
+	jpc_coc_t *coc = &ms->parms.coc;
+	jpc_cox_destroycompparms(&coc->compparms);
+}
+
+static int jpc_coc_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in)
+{
+	jpc_coc_t *coc = &ms->parms.coc;
+	uint_fast8_t tmp;
+	if (cstate->numcomps <= 256) {
+		if (jpc_getuint8(in, &tmp)) {
+			return -1;
+		}
+		coc->compno = tmp;
+	} else {
+		if (jpc_getuint16(in, &coc->compno)) {
+			return -1;
+		}
+	}
+	if (jpc_getuint8(in, &coc->compparms.csty)) {
+		return -1;
+	}
+	if (jpc_cox_getcompparms(ms, cstate, in,
+	  (coc->compparms.csty & JPC_COX_PRT) != 0, &coc->compparms)) {
+		return -1;
+	}
+	if (jas_stream_eof(in)) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_coc_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out)
+{
+	jpc_coc_t *coc = &ms->parms.coc;
+	assert(coc->compparms.numdlvls <= 32);
+	if (cstate->numcomps <= 256) {
+		if (jpc_putuint8(out, coc->compno)) {
+			return -1;
+		}
+	} else {
+		if (jpc_putuint16(out, coc->compno)) {
+			return -1;
+		}
+	}
+	if (jpc_putuint8(out, coc->compparms.csty)) {
+		return -1;
+	}
+	if (jpc_cox_putcompparms(ms, cstate, out,
+	  (coc->compparms.csty & JPC_COX_PRT) != 0, &coc->compparms)) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_coc_dumpparms(jpc_ms_t *ms, FILE *out)
+{
+	jpc_coc_t *coc = &ms->parms.coc;
+	fprintf(out, "compno = %d; csty = 0x%02x; numdlvls = %d;\n",
+	  coc->compno, coc->compparms.csty, coc->compparms.numdlvls);
+	fprintf(out, "cblkwidthval = %d; cblkheightval = %d; "
+	  "cblksty = 0x%02x; qmfbid = %d;\n", coc->compparms.cblkwidthval,
+	  coc->compparms.cblkheightval, coc->compparms.cblksty, coc->compparms.qmfbid);
+	return 0;
+}
+/******************************************************************************\
+* COD/COC marker segment operation helper functions.
+\******************************************************************************/
+
+static void jpc_cox_destroycompparms(jpc_coxcp_t *compparms)
+{
+}
+
+static int jpc_cox_getcompparms(jpc_ms_t *ms, jpc_cstate_t *cstate,
+  jas_stream_t *in, int prtflag, jpc_coxcp_t *compparms)
+{
+	uint_fast8_t tmp;
+	int i;
+	if (jpc_getuint8(in, &compparms->numdlvls) ||
+	  jpc_getuint8(in, &compparms->cblkwidthval) ||
+	  jpc_getuint8(in, &compparms->cblkheightval) ||
+	  jpc_getuint8(in, &compparms->cblksty) ||
+	  jpc_getuint8(in, &compparms->qmfbid)) {
+		return -1;
+	}
+	compparms->numrlvls = compparms->numdlvls + 1;
+	if (prtflag) {
+		for (i = 0; i < compparms->numrlvls; ++i) {
+			if (jpc_getuint8(in, &tmp)) {
+				jpc_cox_destroycompparms(compparms);
+				return -1;
+			}
+			compparms->rlvls[i].parwidthval = tmp & 0xf;
+			compparms->rlvls[i].parheightval = (tmp >> 4) & 0xf;
+		}
+/* Sigh.  This bit should be in the same field in both COC and COD mrk segs. */
+compparms->csty |= JPC_COX_PRT;
+	} else {
+	}
+	if (jas_stream_eof(in)) {
+		jpc_cox_destroycompparms(compparms);
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_cox_putcompparms(jpc_ms_t *ms, jpc_cstate_t *cstate,
+  jas_stream_t *out, int prtflag, jpc_coxcp_t *compparms)
+{
+	int i;
+	assert(compparms->numdlvls <= 32);
+	if (jpc_putuint8(out, compparms->numdlvls) ||
+	  jpc_putuint8(out, compparms->cblkwidthval) ||
+	  jpc_putuint8(out, compparms->cblkheightval) ||
+	  jpc_putuint8(out, compparms->cblksty) ||
+	  jpc_putuint8(out, compparms->qmfbid)) {
+		return -1;
+	}
+	if (prtflag) {
+		for (i = 0; i < compparms->numrlvls; ++i) {
+			if (jpc_putuint8(out,
+			  ((compparms->rlvls[i].parheightval & 0xf) << 4) |
+			  (compparms->rlvls[i].parwidthval & 0xf))) {
+				return -1;
+			}
+		}
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* RGN marker segment operations.
+\******************************************************************************/
+
+static int jpc_rgn_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in)
+{
+	jpc_rgn_t *rgn = &ms->parms.rgn;
+	uint_fast8_t tmp;
+	if (cstate->numcomps <= 256) {
+		if (jpc_getuint8(in, &tmp)) {
+			return -1;
+		}
+		rgn->compno = tmp;
+	} else {
+		if (jpc_getuint16(in, &rgn->compno)) {
+			return -1;
+		}
+	}
+	if (jpc_getuint8(in, &rgn->roisty) ||
+	  jpc_getuint8(in, &rgn->roishift)) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_rgn_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out)
+{
+	jpc_rgn_t *rgn = &ms->parms.rgn;
+	if (cstate->numcomps <= 256) {
+		if (jpc_putuint8(out, rgn->compno)) {
+			return -1;
+		}
+	} else {
+		if (jpc_putuint16(out, rgn->compno)) {
+			return -1;
+		}
+	}
+	if (jpc_putuint8(out, rgn->roisty) ||
+	  jpc_putuint8(out, rgn->roishift)) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_rgn_dumpparms(jpc_ms_t *ms, FILE *out)
+{
+	jpc_rgn_t *rgn = &ms->parms.rgn;
+	fprintf(out, "compno = %d; roisty = %d; roishift = %d\n",
+	  rgn->compno, rgn->roisty, rgn->roishift);
+	return 0;
+}
+
+/******************************************************************************\
+* QCD marker segment operations.
+\******************************************************************************/
+
+static void jpc_qcd_destroyparms(jpc_ms_t *ms)
+{
+	jpc_qcd_t *qcd = &ms->parms.qcd;
+	jpc_qcx_destroycompparms(&qcd->compparms);
+}
+
+static int jpc_qcd_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in)
+{
+	jpc_qcxcp_t *compparms = &ms->parms.qcd.compparms;
+	return jpc_qcx_getcompparms(compparms, cstate, in, ms->len);
+}
+
+static int jpc_qcd_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out)
+{
+	jpc_qcxcp_t *compparms = &ms->parms.qcd.compparms;
+	return jpc_qcx_putcompparms(compparms, cstate, out);
+}
+
+static int jpc_qcd_dumpparms(jpc_ms_t *ms, FILE *out)
+{
+	jpc_qcd_t *qcd = &ms->parms.qcd;
+	int i;
+	fprintf(out, "qntsty = %d; numguard = %d; numstepsizes = %d\n",
+	  (int) qcd->compparms.qntsty, qcd->compparms.numguard, qcd->compparms.numstepsizes);
+	for (i = 0; i < qcd->compparms.numstepsizes; ++i) {
+		fprintf(out, "expn[%d] = 0x%04x; mant[%d] = 0x%04x;\n",
+		  i, (unsigned) JPC_QCX_GETEXPN(qcd->compparms.stepsizes[i]),
+		  i, (unsigned) JPC_QCX_GETMANT(qcd->compparms.stepsizes[i]));
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* QCC marker segment operations.
+\******************************************************************************/
+
+static void jpc_qcc_destroyparms(jpc_ms_t *ms)
+{
+	jpc_qcc_t *qcc = &ms->parms.qcc;
+	jpc_qcx_destroycompparms(&qcc->compparms);
+}
+
+static int jpc_qcc_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in)
+{
+	jpc_qcc_t *qcc = &ms->parms.qcc;
+	uint_fast8_t tmp;
+	int len;
+	len = ms->len;
+	if (cstate->numcomps <= 256) {
+		jpc_getuint8(in, &tmp);
+		qcc->compno = tmp;
+		--len;
+	} else {
+		jpc_getuint16(in, &qcc->compno);
+		len -= 2;
+	}
+	if (jpc_qcx_getcompparms(&qcc->compparms, cstate, in, len)) {
+		return -1;
+	}
+	if (jas_stream_eof(in)) {
+		jpc_qcc_destroyparms(ms);
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_qcc_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out)
+{
+	jpc_qcc_t *qcc = &ms->parms.qcc;
+	if (cstate->numcomps <= 256) {
+		jpc_putuint8(out, qcc->compno);
+	} else {
+		jpc_putuint16(out, qcc->compno);
+	}
+	if (jpc_qcx_putcompparms(&qcc->compparms, cstate, out)) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_qcc_dumpparms(jpc_ms_t *ms, FILE *out)
+{
+	jpc_qcc_t *qcc = &ms->parms.qcc;
+	int i;
+	fprintf(out, "compno = %d; qntsty = %d; numguard = %d; "
+	  "numstepsizes = %d\n", qcc->compno, qcc->compparms.qntsty, qcc->compparms.numguard,
+	  qcc->compparms.numstepsizes);
+	for (i = 0; i < qcc->compparms.numstepsizes; ++i) {
+		fprintf(out, "expn[%d] = 0x%04x; mant[%d] = 0x%04x;\n",
+		  i, (unsigned) JPC_QCX_GETEXPN(qcc->compparms.stepsizes[i]),
+		  i, (unsigned) JPC_QCX_GETMANT(qcc->compparms.stepsizes[i]));
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* QCD/QCC marker segment helper functions.
+\******************************************************************************/
+
+static void jpc_qcx_destroycompparms(jpc_qcxcp_t *compparms)
+{
+	if (compparms->stepsizes) {
+		jas_free(compparms->stepsizes);
+	}
+}
+
+static int jpc_qcx_getcompparms(jpc_qcxcp_t *compparms, jpc_cstate_t *cstate,
+  jas_stream_t *in, uint_fast16_t len)
+{
+	uint_fast8_t tmp;
+	int n;
+	int i;
+	n = 0;
+	jpc_getuint8(in, &tmp);
+	++n;
+	compparms->qntsty = tmp & 0x1f;
+	compparms->numguard = (tmp >> 5) & 7;
+	switch (compparms->qntsty) {
+	case JPC_QCX_SIQNT:
+		compparms->numstepsizes = 1;
+		break;
+	case JPC_QCX_NOQNT:
+		compparms->numstepsizes = (len - n);
+		break;
+	case JPC_QCX_SEQNT:
+		/* XXX - this is a hack */
+		compparms->numstepsizes = (len - n) / 2;
+		break;
+	}
+if (compparms->numstepsizes > 0) {
+	compparms->stepsizes = jas_malloc(compparms->numstepsizes *
+	  sizeof(uint_fast32_t));
+	assert(compparms->stepsizes);
+	for (i = 0; i < compparms->numstepsizes; ++i) {
+		if (compparms->qntsty == JPC_QCX_NOQNT) {
+			jpc_getuint8(in, &tmp);
+			compparms->stepsizes[i] = JPC_QCX_EXPN(tmp >> 3);
+		} else {
+			jpc_getuint16(in, &compparms->stepsizes[i]);
+		}
+	}
+} else {
+	compparms->stepsizes = 0;
+}
+	if (jas_stream_error(in) || jas_stream_eof(in)) {
+		jpc_qcx_destroycompparms(compparms);
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_qcx_putcompparms(jpc_qcxcp_t *compparms, jpc_cstate_t *cstate,
+  jas_stream_t *out)
+{
+	int i;
+	jpc_putuint8(out, ((compparms->numguard & 7) << 5) | compparms->qntsty);
+	for (i = 0; i < compparms->numstepsizes; ++i) {
+		if (compparms->qntsty == JPC_QCX_NOQNT) {
+			jpc_putuint8(out, JPC_QCX_GETEXPN(
+			  compparms->stepsizes[i]) << 3);
+		} else {
+			jpc_putuint16(out, compparms->stepsizes[i]);
+		}
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* SOP marker segment operations.
+\******************************************************************************/
+
+static int jpc_sop_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in)
+{
+	jpc_sop_t *sop = &ms->parms.sop;
+	if (jpc_getuint16(in, &sop->seqno)) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_sop_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out)
+{
+	jpc_sop_t *sop = &ms->parms.sop;
+	if (jpc_putuint16(out, sop->seqno)) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_sop_dumpparms(jpc_ms_t *ms, FILE *out)
+{
+	jpc_sop_t *sop = &ms->parms.sop;
+	fprintf(out, "seqno = %d;\n", sop->seqno);
+	return 0;
+}
+
+/******************************************************************************\
+* PPM marker segment operations.
+\******************************************************************************/
+
+static void jpc_ppm_destroyparms(jpc_ms_t *ms)
+{
+	jpc_ppm_t *ppm = &ms->parms.ppm;
+	if (ppm->data) {
+		jas_free(ppm->data);
+	}
+}
+
+static int jpc_ppm_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in)
+{
+	jpc_ppm_t *ppm = &ms->parms.ppm;
+
+	ppm->data = 0;
+
+	if (ms->len < 1) {
+		goto error;
+	}
+	if (jpc_getuint8(in, &ppm->ind)) {
+		goto error;
+	}
+
+	ppm->len = ms->len - 1;
+	if (ppm->len > 0) {
+		if (!(ppm->data = jas_malloc(ppm->len * sizeof(unsigned char)))) {
+			goto error;
+		}
+		if (jas_stream_read(in, ppm->data, ppm->len) != ppm->len) {
+			goto error;
+		}
+	} else {
+		ppm->data = 0;
+	}
+	return 0;
+
+error:
+	jpc_ppm_destroyparms(ms);
+	return -1;
+}
+
+static int jpc_ppm_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out)
+{
+	jpc_ppm_t *ppm = &ms->parms.ppm;
+	if (jas_stream_write(out, (char *) ppm->data, ppm->len) != ppm->len) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_ppm_dumpparms(jpc_ms_t *ms, FILE *out)
+{
+	jpc_ppm_t *ppm = &ms->parms.ppm;
+	fprintf(out, "ind=%d; len = %d;\n", ppm->ind, ppm->len);
+	if (ppm->len > 0) {
+		fprintf(out, "data =\n");
+		jas_memdump(out, ppm->data, ppm->len);
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* PPT marker segment operations.
+\******************************************************************************/
+
+static void jpc_ppt_destroyparms(jpc_ms_t *ms)
+{
+	jpc_ppt_t *ppt = &ms->parms.ppt;
+	if (ppt->data) {
+		jas_free(ppt->data);
+	}
+}
+
+static int jpc_ppt_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in)
+{
+	jpc_ppt_t *ppt = &ms->parms.ppt;
+	ppt->data = 0;
+
+	if (ms->len < 1) {
+		goto error;
+	}
+	if (jpc_getuint8(in, &ppt->ind)) {
+		goto error;
+	}
+	ppt->len = ms->len - 1;
+	if (ppt->len > 0) {
+		if (!(ppt->data = jas_malloc(ppt->len * sizeof(unsigned char)))) {
+			goto error;
+		}
+		if (jas_stream_read(in, (char *) ppt->data, ppt->len) != ppt->len) {
+			goto error;
+		}
+	} else {
+		ppt->data = 0;
+	}
+	return 0;
+
+error:
+	jpc_ppt_destroyparms(ms);
+	return -1;
+}
+
+static int jpc_ppt_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out)
+{
+	jpc_ppt_t *ppt = &ms->parms.ppt;
+	if (jpc_putuint8(out, ppt->ind)) {
+		return -1;
+	}
+	if (jas_stream_write(out, (char *) ppt->data, ppt->len) != ppt->len) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_ppt_dumpparms(jpc_ms_t *ms, FILE *out)
+{
+	jpc_ppt_t *ppt = &ms->parms.ppt;
+	fprintf(out, "ind=%d; len = %d;\n", ppt->ind, ppt->len);
+	if (ppt->len > 0) {
+		fprintf(out, "data =\n");
+		jas_memdump(out, ppt->data, ppt->len);
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* POC marker segment operations.
+\******************************************************************************/
+
+static void jpc_poc_destroyparms(jpc_ms_t *ms)
+{
+	jpc_poc_t *poc = &ms->parms.poc;
+	if (poc->pchgs) {
+		jas_free(poc->pchgs);
+	}
+}
+
+static int jpc_poc_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in)
+{
+	jpc_poc_t *poc = &ms->parms.poc;
+	jpc_pocpchg_t *pchg;
+	int pchgno;
+	uint_fast8_t tmp;
+	poc->numpchgs = (cstate->numcomps > 256) ? (ms->len / 9) :
+	  (ms->len / 7);
+	if (!(poc->pchgs = jas_malloc(poc->numpchgs * sizeof(jpc_pocpchg_t)))) {
+		goto error;
+	}
+	for (pchgno = 0, pchg = poc->pchgs; pchgno < poc->numpchgs; ++pchgno,
+	  ++pchg) {
+		if (jpc_getuint8(in, &pchg->rlvlnostart)) {
+			goto error;
+		}
+		if (cstate->numcomps > 256) {
+			if (jpc_getuint16(in, &pchg->compnostart)) {
+				goto error;
+			}
+		} else {
+			if (jpc_getuint8(in, &tmp)) {
+				goto error;
+			};
+			pchg->compnostart = tmp;
+		}
+		if (jpc_getuint16(in, &pchg->lyrnoend) ||
+		  jpc_getuint8(in, &pchg->rlvlnoend)) {
+			goto error;
+		}
+		if (cstate->numcomps > 256) {
+			if (jpc_getuint16(in, &pchg->compnoend)) {
+				goto error;
+			}
+		} else {
+			if (jpc_getuint8(in, &tmp)) {
+				goto error;
+			}
+			pchg->compnoend = tmp;
+		}
+		if (jpc_getuint8(in, &pchg->prgord)) {
+			goto error;
+		}
+		if (pchg->rlvlnostart > pchg->rlvlnoend ||
+		  pchg->compnostart > pchg->compnoend) {
+			goto error;
+		}
+	}
+	return 0;
+
+error:
+	jpc_poc_destroyparms(ms);
+	return -1;
+}
+
+static int jpc_poc_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out)
+{
+	jpc_poc_t *poc = &ms->parms.poc;
+	jpc_pocpchg_t *pchg;
+	int pchgno;
+	for (pchgno = 0, pchg = poc->pchgs; pchgno < poc->numpchgs; ++pchgno,
+	  ++pchg) {
+		if (jpc_putuint8(out, pchg->rlvlnostart) ||
+		  ((cstate->numcomps > 256) ?
+		  jpc_putuint16(out, pchg->compnostart) :
+		  jpc_putuint8(out, pchg->compnostart)) ||
+		  jpc_putuint16(out, pchg->lyrnoend) ||
+		  jpc_putuint8(out, pchg->rlvlnoend) ||
+		  ((cstate->numcomps > 256) ?
+		  jpc_putuint16(out, pchg->compnoend) :
+		  jpc_putuint8(out, pchg->compnoend)) ||
+		  jpc_putuint8(out, pchg->prgord)) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static int jpc_poc_dumpparms(jpc_ms_t *ms, FILE *out)
+{
+	jpc_poc_t *poc = &ms->parms.poc;
+	jpc_pocpchg_t *pchg;
+	int pchgno;
+	for (pchgno = 0, pchg = poc->pchgs; pchgno < poc->numpchgs;
+	  ++pchgno, ++pchg) {
+		fprintf(out, "po[%d] = %d; ", pchgno, pchg->prgord);
+		fprintf(out, "cs[%d] = %d; ce[%d] = %d; ",
+		  pchgno, pchg->compnostart, pchgno, pchg->compnoend);
+		fprintf(out, "rs[%d] = %d; re[%d] = %d; ",
+		  pchgno, pchg->rlvlnostart, pchgno, pchg->rlvlnoend);
+		fprintf(out, "le[%d] = %d\n", pchgno, pchg->lyrnoend);
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* CRG marker segment operations.
+\******************************************************************************/
+
+static void jpc_crg_destroyparms(jpc_ms_t *ms)
+{
+	jpc_crg_t *crg = &ms->parms.crg;
+	if (crg->comps) {
+		jas_free(crg->comps);
+	}
+}
+
+static int jpc_crg_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in)
+{
+	jpc_crg_t *crg = &ms->parms.crg;
+	jpc_crgcomp_t *comp;
+	uint_fast16_t compno;
+	crg->numcomps = cstate->numcomps;
+	if (!(crg->comps = jas_malloc(cstate->numcomps * sizeof(uint_fast16_t)))) {
+		return -1;
+	}
+	for (compno = 0, comp = crg->comps; compno < cstate->numcomps;
+	  ++compno, ++comp) {
+		if (jpc_getuint16(in, &comp->hoff) ||
+		  jpc_getuint16(in, &comp->voff)) {
+			jpc_crg_destroyparms(ms);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static int jpc_crg_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out)
+{
+	jpc_crg_t *crg = &ms->parms.crg;
+	int compno;
+	jpc_crgcomp_t *comp;
+	for (compno = 0, comp = crg->comps; compno < crg->numcomps; ++compno,
+	  ++comp) {
+		if (jpc_putuint16(out, comp->hoff) ||
+		  jpc_putuint16(out, comp->voff)) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static int jpc_crg_dumpparms(jpc_ms_t *ms, FILE *out)
+{
+	jpc_crg_t *crg = &ms->parms.crg;
+	int compno;
+	jpc_crgcomp_t *comp;
+	for (compno = 0, comp = crg->comps; compno < crg->numcomps; ++compno,
+	  ++comp) {
+		fprintf(out, "hoff[%d] = %d; voff[%d] = %d\n", compno,
+		  comp->hoff, compno, comp->voff);
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* Operations for COM marker segment.
+\******************************************************************************/
+
+static void jpc_com_destroyparms(jpc_ms_t *ms)
+{
+	jpc_com_t *com = &ms->parms.com;
+	if (com->data) {
+		jas_free(com->data);
+	}
+}
+
+static int jpc_com_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in)
+{
+	jpc_com_t *com = &ms->parms.com;
+	if (jpc_getuint16(in, &com->regid)) {
+		return -1;
+	}
+	com->len = ms->len - 2;
+	if (com->len > 0) {
+		if (!(com->data = jas_malloc(com->len))) {
+			return -1;
+		}
+		if (jas_stream_read(in, com->data, com->len) != com->len) {
+			return -1;
+		}
+	} else {
+		com->data = 0;
+	}
+	return 0;
+}
+
+static int jpc_com_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out)
+{
+	jpc_com_t *com = &ms->parms.com;
+	if (jpc_putuint16(out, com->regid)) {
+		return -1;
+	}
+	if (jas_stream_write(out, com->data, com->len) != com->len) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_com_dumpparms(jpc_ms_t *ms, FILE *out)
+{
+	jpc_com_t *com = &ms->parms.com;
+	int i;
+	int printable;
+	fprintf(out, "regid = %d;\n", com->regid);
+	printable = 1;
+	for (i = 0; i < com->len; ++i) {
+		if (!isprint(com->data[i])) {
+			printable = 0;
+			break;
+		}
+	}
+	if (printable) {
+		fprintf(out, "data = ");
+		fwrite(com->data, sizeof(char), com->len, out);
+		fprintf(out, "\n");
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* Operations for unknown types of marker segments.
+\******************************************************************************/
+
+static void jpc_unk_destroyparms(jpc_ms_t *ms)
+{
+	jpc_unk_t *unk = &ms->parms.unk;
+	if (unk->data) {
+		jas_free(unk->data);
+	}
+}
+
+static int jpc_unk_getparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in)
+{
+	jpc_unk_t *unk = &ms->parms.unk;
+
+	if (ms->len > 0) {
+		if (!(unk->data = jas_malloc(ms->len * sizeof(unsigned char)))) {
+			return -1;
+		}
+		if (jas_stream_read(in, (char *) unk->data, ms->len) != ms->len) {
+			jas_free(unk->data);
+			return -1;
+		}
+		unk->len = ms->len;
+	} else {
+		unk->data = 0;
+		unk->len = 0;
+	}
+	return 0;
+}
+
+static int jpc_unk_putparms(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out)
+{
+	/* If this function is called, we are trying to write an unsupported
+	  type of marker segment.  Return with an error indication.  */
+	return -1;
+}
+
+static int jpc_unk_dumpparms(jpc_ms_t *ms, FILE *out)
+{
+	int i;
+	jpc_unk_t *unk = &ms->parms.unk;
+	for (i = 0; i < unk->len; ++i) {
+		fprintf(out, "%02x ", unk->data[i]);
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* Primitive I/O operations.
+\******************************************************************************/
+
+int jpc_getuint8(jas_stream_t *in, uint_fast8_t *val)
+{
+	int c;
+	if ((c = jas_stream_getc(in)) == EOF) {
+		return -1;
+	}
+	if (val) {
+		*val = c;
+	}
+	return 0;
+}
+
+int jpc_putuint8(jas_stream_t *out, uint_fast8_t val)
+{
+	if (jas_stream_putc(out, val & 0xff) == EOF) {
+		return -1;
+	}
+	return 0;
+}
+
+int jpc_getuint16(jas_stream_t *in, uint_fast16_t *val)
+{
+	uint_fast16_t v;
+	int c;
+	if ((c = jas_stream_getc(in)) == EOF) {
+		return -1;
+	}
+	v = c;
+	if ((c = jas_stream_getc(in)) == EOF) {
+		return -1;
+	}
+	v = (v << 8) | c;
+	if (val) {
+		*val = v;
+	}
+	return 0;
+}
+
+int jpc_putuint16(jas_stream_t *out, uint_fast16_t val)
+{
+	if (jas_stream_putc(out, (val >> 8) & 0xff) == EOF ||
+	  jas_stream_putc(out, val & 0xff) == EOF) {
+		return -1;
+	}
+	return 0;
+}
+
+int jpc_getuint32(jas_stream_t *in, uint_fast32_t *val)
+{
+	uint_fast32_t v;
+	int c;
+	if ((c = jas_stream_getc(in)) == EOF) {
+		return -1;
+	}
+	v = c;
+	if ((c = jas_stream_getc(in)) == EOF) {
+		return -1;
+	}
+	v = (v << 8) | c;
+	if ((c = jas_stream_getc(in)) == EOF) {
+		return -1;
+	}
+	v = (v << 8) | c;
+	if ((c = jas_stream_getc(in)) == EOF) {
+		return -1;
+	}
+	v = (v << 8) | c;
+	if (val) {
+		*val = v;
+	}
+	return 0;
+}
+
+int jpc_putuint32(jas_stream_t *out, uint_fast32_t val)
+{
+	if (jas_stream_putc(out, (val >> 24) & 0xff) == EOF ||
+	  jas_stream_putc(out, (val >> 16) & 0xff) == EOF ||
+	  jas_stream_putc(out, (val >> 8) & 0xff) == EOF ||
+	  jas_stream_putc(out, val & 0xff) == EOF) {
+		return -1;
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* Miscellany
+\******************************************************************************/
+
+static jpc_mstabent_t *jpc_mstab_lookup(int id)
+{
+	jpc_mstabent_t *mstabent;
+	for (mstabent = jpc_mstab;; ++mstabent) {
+		if (mstabent->id == id || mstabent->id < 0) {
+			return mstabent;
+		}
+	}
+	assert(0);
+	return 0;
+}
+
+int jpc_validate(jas_stream_t *in)
+{
+	int n;
+	int i;
+	unsigned char buf[2];
+
+	assert(JAS_STREAM_MAXPUTBACK >= 2);
+
+	if ((n = jas_stream_read(in, (char *) buf, 2)) < 0) {
+		return -1;
+	}
+	for (i = n - 1; i >= 0; --i) {
+		if (jas_stream_ungetc(in, buf[i]) == EOF) {
+			return -1;
+		}
+	}
+	if (n < 2) {
+		return -1;
+	}
+	if (buf[0] == (JPC_MS_SOC >> 8) && buf[1] == (JPC_MS_SOC & 0xff)) {
+		return 0;
+	}
+	return -1;
+}
+
+int jpc_getdata(jas_stream_t *in, jas_stream_t *out, long len)
+{
+	return jas_stream_copy(out, in, len);
+}
+
+int jpc_putdata(jas_stream_t *out, jas_stream_t *in, long len)
+{
+	return jas_stream_copy(out, in, len);
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_cs.h b/converter/other/jpeg2000/libjasper/jpc/jpc_cs.h
new file mode 100644
index 00000000..07a046d1
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_cs.h
@@ -0,0 +1,812 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * JPEG-2000 Code Stream Library
+ *
+ * $Id$
+ */
+
+#ifndef JPC_CS_H
+#define JPC_CS_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_image.h"
+#include "jasper/jas_stream.h"
+
+#include "jpc_cod.h"
+
+/******************************************************************************\
+* Constants and Types.
+\******************************************************************************/
+
+/* The maximum number of resolution levels. */
+#define	JPC_MAXRLVLS	33
+
+/* The maximum number of bands. */
+#define	JPC_MAXBANDS	(3 * JPC_MAXRLVLS + 1)
+
+/* The maximum number of layers. */
+#define JPC_MAXLYRS	16384
+
+/**************************************\
+* Code stream.
+\**************************************/
+
+/*
+ * Code stream states.
+ */
+
+/* Initial. */
+#define	JPC_CS_INIT	0
+/* Main header. */
+#define	JPC_CS_MHDR	1
+/* Tile-part header. */
+#define	JPC_CS_THDR	2
+/* Main trailer. */
+#define	JPC_CS_MTLR	3
+/* Tile-part data. */
+#define	JPC_CS_TDATA	4
+
+/*
+ * Unfortunately, the code stream syntax was not designed in such a way that
+ * any given marker segment can be correctly decoded without additional state
+ * derived from previously decoded marker segments.
+ * For example, a RGN/COC/QCC marker segment cannot be decoded unless the
+ * number of components is known.
+ */
+
+/*
+ * Code stream state information.
+ */
+
+typedef struct {
+
+	/* The number of components. */
+	int numcomps;
+
+} jpc_cstate_t;
+
+/**************************************\
+* SOT marker segment parameters.
+\**************************************/
+
+typedef struct {
+
+	/* The tile number. */
+	uint_fast16_t tileno;
+
+	/* The combined length of the marker segment and its auxilary data
+	  (i.e., packet data). */
+	uint_fast32_t len;
+
+	/* The tile-part instance. */
+	uint_fast8_t partno;
+
+	/* The number of tile-parts. */
+	uint_fast8_t numparts;
+
+} jpc_sot_t;
+
+/**************************************\
+* SIZ marker segment parameters.
+\**************************************/
+
+/* Per component information. */
+
+typedef struct {
+
+	/* The precision of the samples. */
+	uint_fast8_t prec;
+
+	/* The signedness of the samples. */
+	uint_fast8_t sgnd;
+
+	/* The horizontal separation of samples with respect to the reference
+	  grid. */
+	uint_fast8_t hsamp;
+
+	/* The vertical separation of samples with respect to the reference
+	  grid. */
+	uint_fast8_t vsamp;
+
+} jpc_sizcomp_t;
+
+/* SIZ marker segment parameters. */
+
+typedef struct {
+
+	/* The code stream capabilities. */
+	uint_fast16_t caps;
+
+	/* The width of the image in units of the reference grid. */
+	uint_fast32_t width;
+
+	/* The height of the image in units of the reference grid. */
+	uint_fast32_t height;
+
+	/* The horizontal offset from the origin of the reference grid to the
+	  left side of the image area. */
+	uint_fast32_t xoff;
+
+	/* The vertical offset from the origin of the reference grid to the
+	  top side of the image area. */
+	uint_fast32_t yoff;
+
+	/* The nominal width of a tile in units of the reference grid. */
+	uint_fast32_t tilewidth;
+
+	/* The nominal height of a tile in units of the reference grid. */
+	uint_fast32_t tileheight;
+
+	/* The horizontal offset from the origin of the reference grid to the
+	  left side of the first tile. */
+	uint_fast32_t tilexoff;
+
+	/* The vertical offset from the origin of the reference grid to the
+	  top side of the first tile. */
+	uint_fast32_t tileyoff;
+
+	/* The number of components. */
+	uint_fast16_t numcomps;
+
+	/* The per-component information. */
+	jpc_sizcomp_t *comps;
+
+} jpc_siz_t;
+
+/**************************************\
+* COD marker segment parameters.
+\**************************************/
+
+/*
+ * Coding style constants.
+ */
+
+/* Precincts may be used. */
+#define	JPC_COX_PRT	0x01
+/* SOP marker segments may be used. */
+#define	JPC_COD_SOP	0x02
+/* EPH marker segments may be used. */
+#define	JPC_COD_EPH	0x04
+
+/*
+ * Progression order constants.
+ */
+
+/* Layer-resolution-component-precinct progressive
+  (i.e., progressive by fidelity). */
+#define	JPC_COD_LRCPPRG	0
+/* Resolution-layer-component-precinct progressive
+  (i.e., progressive by resolution). */
+#define	JPC_COD_RLCPPRG	1
+/* Resolution-precinct-component-layer progressive. */
+#define	JPC_COD_RPCLPRG	2
+/* Precinct-component-resolution-layer progressive. */
+#define	JPC_COD_PCRLPRG	3
+/* Component-position-resolution-layer progressive. */
+#define	JPC_COD_CPRLPRG	4
+
+/*
+ * Code block style constants.
+ */
+
+#define	JPC_COX_LAZY	0x01 /* Selective arithmetic coding bypass. */
+#define	JPC_COX_RESET	0x02 /* Reset context probabilities. */
+#define	JPC_COX_TERMALL	0x04 /* Terminate all coding passes. */
+#define	JPC_COX_VSC		0x08 /* Vertical stripe causal context formation. */
+#define	JPC_COX_PTERM	0x10 /* Predictable termination. */
+#define	JPC_COX_SEGSYM	0x20 /* Use segmentation symbols. */
+
+/* Transform constants. */
+#define	JPC_COX_INS	0x00 /* Irreversible 9/7. */
+#define	JPC_COX_RFT	0x01 /* Reversible 5/3. */
+
+/* Multicomponent transform constants. */
+#define	JPC_COD_NOMCT	0x00 /* No multicomponent transform. */
+#define	JPC_COD_MCT		0x01 /* Multicomponent transform. */
+
+/* Get the code block size value from the code block size exponent. */
+#define	JPC_COX_CBLKSIZEEXPN(x)		((x) - 2)
+/* Get the code block size exponent from the code block size value. */
+#define	JPC_COX_GETCBLKSIZEEXPN(x)	((x) + 2)
+
+/* Per resolution-level information. */
+
+typedef struct {
+
+	/* The packet partition width. */
+	uint_fast8_t parwidthval;
+
+	/* The packet partition height. */
+	uint_fast8_t parheightval;
+
+} jpc_coxrlvl_t;
+
+/* Per component information. */
+
+typedef struct {
+
+	/* The coding style. */
+	uint_fast8_t csty;
+
+	/* The number of decomposition levels. */
+	uint_fast8_t numdlvls;
+
+	/* The nominal code block width specifier. */
+	uint_fast8_t cblkwidthval;
+
+	/* The nominal code block height specifier. */
+	uint_fast8_t cblkheightval;
+
+	/* The style of coding passes. */
+	uint_fast8_t cblksty;
+
+	/* The QMFB employed. */
+	uint_fast8_t qmfbid;
+
+	/* The number of resolution levels. */
+	int numrlvls;
+
+	/* The per-resolution-level information. */
+	jpc_coxrlvl_t rlvls[JPC_MAXRLVLS];
+
+} jpc_coxcp_t;
+
+/* COD marker segment parameters. */
+
+typedef struct {
+
+	/* The general coding style. */
+	uint_fast8_t csty;
+
+	/* The progression order. */
+	uint_fast8_t prg;
+
+	/* The number of layers. */
+	uint_fast16_t numlyrs;
+
+	/* The multicomponent transform. */
+	uint_fast8_t mctrans;
+
+	/* Component-related parameters. */
+	jpc_coxcp_t compparms;
+
+} jpc_cod_t;
+
+/* COC marker segment parameters. */
+
+typedef struct {
+
+	/* The component number. */
+	uint_fast16_t compno;
+
+	/* Component-related parameters. */
+	jpc_coxcp_t compparms;
+
+} jpc_coc_t;
+
+/**************************************\
+* RGN marker segment parameters.
+\**************************************/
+
+/* The maxshift ROI style. */
+#define	JPC_RGN_MAXSHIFT	0x00
+
+typedef struct {
+
+	/* The component to which the marker applies. */
+	uint_fast16_t compno;
+
+	/* The ROI style. */
+	uint_fast8_t roisty;
+
+	/* The ROI shift value. */
+	uint_fast8_t roishift;
+
+} jpc_rgn_t;
+
+/**************************************\
+* QCD/QCC marker segment parameters.
+\**************************************/
+
+/*
+ * Quantization style constants.
+ */
+
+#define	JPC_QCX_NOQNT	0 /* No quantization. */
+#define	JPC_QCX_SIQNT	1 /* Scalar quantization, implicit. */
+#define	JPC_QCX_SEQNT	2 /* Scalar quantization, explicit. */
+
+/*
+ * Stepsize manipulation macros.
+ */
+
+#define	JPC_QCX_GETEXPN(x)	((x) >> 11)
+#define	JPC_QCX_GETMANT(x)	((x) & 0x07ff)
+#define	JPC_QCX_EXPN(x)		(assert(!((x) & (~0x1f))), (((x) & 0x1f) << 11))
+#define	JPC_QCX_MANT(x)		(assert(!((x) & (~0x7ff))), ((x) & 0x7ff))
+
+/* Per component information. */
+
+typedef struct {
+
+	/* The quantization style. */
+	uint_fast8_t qntsty;
+
+	/* The number of step sizes. */
+	int numstepsizes;
+
+	/* The step sizes. */
+	uint_fast16_t *stepsizes;
+
+	/* The number of guard bits. */
+	uint_fast8_t numguard;
+
+} jpc_qcxcp_t;
+
+/* QCC marker segment parameters. */
+
+typedef struct {
+
+	/* The component associated with this marker segment. */
+	uint_fast16_t compno;
+
+	/* The parameters. */
+	jpc_qcxcp_t compparms;
+
+} jpc_qcc_t;
+
+/* QCD marker segment parameters. */
+
+typedef struct {
+
+	/* The parameters. */
+	jpc_qcxcp_t compparms;
+
+} jpc_qcd_t;
+
+/**************************************\
+* POD marker segment parameters.
+\**************************************/
+
+typedef struct {
+
+	/* The progression order. */
+	uint_fast8_t prgord;
+
+	/* The lower bound (inclusive) on the resolution level for the
+	  progression order volume. */
+	uint_fast8_t rlvlnostart;
+
+	/* The upper bound (exclusive) on the resolution level for the
+	  progression order volume. */
+	uint_fast8_t rlvlnoend;
+
+	/* The lower bound (inclusive) on the component for the progression
+	  order volume. */
+	uint_fast16_t compnostart;
+
+	/* The upper bound (exclusive) on the component for the progression
+	  order volume. */
+	uint_fast16_t compnoend;
+
+	/* The upper bound (exclusive) on the layer for the progression
+	  order volume. */
+	uint_fast16_t lyrnoend;
+
+} jpc_pocpchg_t;
+
+/* An alias for the above type. */
+typedef jpc_pocpchg_t jpc_pchg_t;
+
+/* POC marker segment parameters. */
+
+typedef struct {
+
+	/* The number of progression order changes. */
+	int numpchgs;
+
+	/* The per-progression-order-change information. */
+	jpc_pocpchg_t *pchgs;
+
+} jpc_poc_t;
+
+/**************************************\
+* PPM/PPT marker segment parameters.
+\**************************************/
+
+/* PPM marker segment parameters. */
+
+typedef struct {
+
+	/* The index. */
+	uint_fast8_t ind;
+
+	/* The length. */
+	uint_fast16_t len;
+
+	/* The data. */
+	unsigned char *data;
+
+} jpc_ppm_t;
+
+/* PPT marker segment parameters. */
+
+typedef struct {
+
+	/* The index. */
+	uint_fast8_t ind;
+
+	/* The length. */
+	uint_fast32_t len;
+
+	/* The data. */
+	unsigned char *data;
+
+} jpc_ppt_t;
+
+/**************************************\
+* COM marker segment parameters.
+\**************************************/
+
+/*
+ * Registration IDs.
+ */
+
+#define	JPC_COM_BIN		0x00
+#define	JPC_COM_LATIN	0x01
+
+typedef struct {
+
+	/* The registration ID. */
+	uint_fast16_t regid;
+
+	/* The length of the data in bytes. */
+	uint_fast16_t len;
+
+	/* The data. */
+	unsigned char *data;
+
+} jpc_com_t;
+
+/**************************************\
+* SOP marker segment parameters.
+\**************************************/
+
+typedef struct {
+
+	/* The sequence number. */
+	uint_fast16_t seqno;
+
+} jpc_sop_t;
+
+/**************************************\
+* CRG marker segment parameters.
+\**************************************/
+
+/* Per component information. */
+
+typedef struct {
+
+	/* The horizontal offset. */
+	uint_fast16_t hoff;
+
+	/* The vertical offset. */
+	uint_fast16_t voff;
+
+} jpc_crgcomp_t;
+
+typedef struct {
+
+	/* The number of components. */
+	int numcomps;
+
+	/* Per component information. */
+	jpc_crgcomp_t *comps;
+
+} jpc_crg_t;
+
+/**************************************\
+* Marker segment parameters for unknown marker type.
+\**************************************/
+
+typedef struct {
+
+	/* The data. */
+	unsigned char *data;
+
+	/* The length. */
+	uint_fast16_t len;
+
+} jpc_unk_t;
+
+/**************************************\
+* Generic marker segment parameters.
+\**************************************/
+
+typedef union {
+	int soc;	/* unused */
+	jpc_sot_t sot;
+	int sod;	/* unused */
+	int eoc;	/* unused */
+	jpc_siz_t siz;
+	jpc_cod_t cod;
+	jpc_coc_t coc;
+	jpc_rgn_t rgn;
+	jpc_qcd_t qcd;
+	jpc_qcc_t qcc;
+	jpc_poc_t poc;
+	/* jpc_plm_t plm; */
+	/* jpc_plt_t plt; */
+	jpc_ppm_t ppm;
+	jpc_ppt_t ppt;
+	jpc_sop_t sop;
+	int eph;	/* unused */
+	jpc_com_t com;
+	jpc_crg_t crg;
+	jpc_unk_t unk;
+} jpc_msparms_t;
+
+/**************************************\
+* Marker segment.
+\**************************************/
+
+/* Marker segment IDs. */
+
+/* The smallest valid marker value. */
+#define	JPC_MS_MIN	0xff00
+
+/* The largest valid marker value. */
+#define	JPC_MS_MAX	0xffff
+
+/* The minimum marker value that cannot occur within packet data. */
+#define	JPC_MS_INMIN	0xff80
+/* The maximum marker value that cannot occur within packet data. */
+#define	JPC_MS_INMAX	0xffff
+
+/* Delimiting marker segments. */
+#define	JPC_MS_SOC	0xff4f /* Start of code stream (SOC). */
+#define	JPC_MS_SOT	0xff90 /* Start of tile-part (SOT). */
+#define	JPC_MS_SOD	0xff93 /* Start of data (SOD). */
+#define	JPC_MS_EOC	0xffd9 /* End of code stream (EOC). */
+
+/* Fixed information marker segments. */
+#define	JPC_MS_SIZ	0xff51 /* Image and tile size (SIZ). */
+
+/* Functional marker segments. */
+#define	JPC_MS_COD	0xff52 /* Coding style default (COD). */
+#define JPC_MS_COC	0xff53 /* Coding style component (COC). */
+#define	JPC_MS_RGN	0xff5e /* Region of interest (RGN). */
+#define JPC_MS_QCD	0xff5c /* Quantization default (QCD). */
+#define JPC_MS_QCC	0xff5d /* Quantization component (QCC). */
+#define JPC_MS_POC	0xff5f /* Progression order default (POC). */
+
+/* Pointer marker segments. */
+#define	JPC_MS_TLM	0xff55 /* Tile-part lengths, main header (TLM). */
+#define	JPC_MS_PLM	0xff57 /* Packet length, main header (PLM). */
+#define	JPC_MS_PLT	0xff58 /* Packet length, tile-part header (PLT). */
+#define	JPC_MS_PPM	0xff60 /* Packed packet headers, main header (PPM). */
+#define	JPC_MS_PPT	0xff61 /* Packet packet headers, tile-part header (PPT). */
+
+/* In bit stream marker segments. */
+#define	JPC_MS_SOP	0xff91	/* Start of packet (SOP). */
+#define	JPC_MS_EPH	0xff92	/* End of packet header (EPH). */
+
+/* Informational marker segments. */
+#define	JPC_MS_CRG	0xff63 /* Component registration (CRG). */
+#define JPC_MS_COM	0xff64 /* Comment (COM). */
+
+/* Forward declaration. */
+struct jpc_msops_s;
+
+/* Generic marker segment class. */
+
+typedef struct {
+
+	/* The type of marker segment. */
+	uint_fast16_t id;
+
+	/* The length of the marker segment. */
+	uint_fast16_t len;
+
+	/* The starting offset within the stream. */
+	uint_fast32_t off;
+
+	/* The parameters of the marker segment. */
+	jpc_msparms_t parms;
+
+	/* The marker segment operations. */
+	struct jpc_msops_s *ops;
+
+} jpc_ms_t;
+
+/* Marker segment operations (which depend on the marker segment type). */
+
+typedef struct jpc_msops_s {
+
+	/* Destroy the marker segment parameters. */
+	void (*destroyparms)(jpc_ms_t *ms);
+
+	/* Get the marker segment parameters from a stream. */
+	int (*getparms)(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *in);
+
+	/* Put the marker segment parameters to a stream. */
+	int (*putparms)(jpc_ms_t *ms, jpc_cstate_t *cstate, jas_stream_t *out);
+
+	/* Dump the marker segment parameters (for debugging). */
+	int (*dumpparms)(jpc_ms_t *ms, FILE *out);
+
+} jpc_msops_t;
+
+/******************************************************************************\
+* Macros/Functions.
+\******************************************************************************/
+
+/* Create a code-stream state object. */
+jpc_cstate_t *jpc_cstate_create(void);
+
+/* Destroy a code-stream state object. */
+void jpc_cstate_destroy(jpc_cstate_t *cstate);
+
+/* Create a marker segment. */
+jpc_ms_t *jpc_ms_create(int type);
+
+/* Destroy a marker segment. */
+void jpc_ms_destroy(jpc_ms_t *ms);
+
+/* Does a marker segment have parameters? */
+#define	JPC_MS_HASPARMS(x) \
+	(!((x) == JPC_MS_SOC || (x) == JPC_MS_SOD || (x) == JPC_MS_EOC || \
+	  (x) == JPC_MS_EPH || ((x) >= 0xff30 && (x) <= 0xff3f)))
+
+/* Get the marker segment type. */
+#define	jpc_ms_gettype(ms) \
+	((ms)->id)
+
+/* Read a marker segment from a stream. */
+jpc_ms_t *jpc_getms(jas_stream_t *in, jpc_cstate_t *cstate);
+
+/* Write a marker segment to a stream. */
+int jpc_putms(jas_stream_t *out, jpc_cstate_t *cstate, jpc_ms_t *ms);
+
+/* Copy code stream data from one stream to another. */
+int jpc_getdata(jas_stream_t *in, jas_stream_t *out, long n);
+
+/* Copy code stream data from one stream to another. */
+int jpc_putdata(jas_stream_t *out, jas_stream_t *in, long n);
+
+/* Dump a marker segment (for debugging). */
+void jpc_ms_dump(jpc_ms_t *ms, FILE *out);
+
+/* Read a 8-bit unsigned integer from a stream. */
+int jpc_getuint8(jas_stream_t *in, uint_fast8_t *val);
+
+/* Read a 16-bit unsigned integer from a stream. */
+int jpc_getuint16(jas_stream_t *in, uint_fast16_t *val);
+
+/* Read a 32-bit unsigned integer from a stream. */
+int jpc_getuint32(jas_stream_t *in, uint_fast32_t *val);
+
+/* Write a 8-bit unsigned integer to a stream. */
+int jpc_putuint8(jas_stream_t *out, uint_fast8_t val);
+
+/* Write a 16-bit unsigned integer to a stream. */
+int jpc_putuint16(jas_stream_t *out, uint_fast16_t val);
+
+/* Write a 32-bit unsigned integer to a stream. */
+int jpc_putuint32(jas_stream_t *out, uint_fast32_t val);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_dec.c b/converter/other/jpeg2000/libjasper/jpc/jpc_dec.c
new file mode 100644
index 00000000..42980225
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_dec.c
@@ -0,0 +1,2334 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "jasper/jas_types.h"
+#include "jasper/jas_math.h"
+#include "jasper/jas_tvp.h"
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_debug.h"
+
+#include "jpc_fix.h"
+#include "jpc_dec.h"
+#include "jpc_cs.h"
+#include "jpc_mct.h"
+#include "jpc_t2dec.h"
+#include "jpc_t1dec.h"
+#include "jpc_math.h"
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+#define	JPC_MHSOC	0x0001
+  /* In the main header, expecting a SOC marker segment. */
+#define	JPC_MHSIZ	0x0002
+  /* In the main header, expecting a SIZ marker segment. */
+#define	JPC_MH		0x0004
+  /* In the main header, expecting "other" marker segments. */
+#define	JPC_TPHSOT	0x0008
+  /* In a tile-part header, expecting a SOT marker segment. */
+#define	JPC_TPH		0x0010
+  /* In a tile-part header, expecting "other" marker segments. */
+#define	JPC_MT		0x0020
+  /* In the main trailer. */
+
+typedef struct {
+
+	uint_fast16_t id;
+	/* The marker segment type. */
+
+	int validstates;
+	/* The states in which this type of marker segment can be
+	  validly encountered. */
+
+	int (*action)(jpc_dec_t *dec, jpc_ms_t *ms);
+	/* The action to take upon encountering this type of marker segment. */
+
+} jpc_dec_mstabent_t;
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+/* COD/COC parameters have been specified. */
+#define	JPC_CSET	0x0001
+/* QCD/QCC parameters have been specified. */
+#define	JPC_QSET	0x0002
+/* COD/COC parameters set from a COC marker segment. */
+#define	JPC_COC	0x0004
+/* QCD/QCC parameters set from a QCC marker segment. */
+#define	JPC_QCC	0x0008
+
+/******************************************************************************\
+* Local function prototypes.
+\******************************************************************************/
+
+static int jpc_dec_dump(jpc_dec_t *dec, FILE *out);
+
+jpc_ppxstab_t *jpc_ppxstab_create(void);
+void jpc_ppxstab_destroy(jpc_ppxstab_t *tab);
+int jpc_ppxstab_grow(jpc_ppxstab_t *tab, int maxents);
+int jpc_ppxstab_insert(jpc_ppxstab_t *tab, jpc_ppxstabent_t *ent);
+jpc_streamlist_t *jpc_ppmstabtostreams(jpc_ppxstab_t *tab);
+int jpc_pptstabwrite(jas_stream_t *out, jpc_ppxstab_t *tab);
+jpc_ppxstabent_t *jpc_ppxstabent_create(void);
+void jpc_ppxstabent_destroy(jpc_ppxstabent_t *ent);
+
+int jpc_streamlist_numstreams(jpc_streamlist_t *streamlist);
+jpc_streamlist_t *jpc_streamlist_create(void);
+int jpc_streamlist_insert(jpc_streamlist_t *streamlist, int streamno,
+  jas_stream_t *stream);
+jas_stream_t *jpc_streamlist_remove(jpc_streamlist_t *streamlist, int streamno);
+void jpc_streamlist_destroy(jpc_streamlist_t *streamlist);
+jas_stream_t *jpc_streamlist_get(jpc_streamlist_t *streamlist, int streamno);
+
+static void jpc_dec_cp_resetflags(jpc_dec_cp_t *cp);
+static jpc_dec_cp_t *jpc_dec_cp_create(uint_fast16_t numcomps);
+static int jpc_dec_cp_isvalid(jpc_dec_cp_t *cp);
+static jpc_dec_cp_t *jpc_dec_cp_copy(jpc_dec_cp_t *cp);
+static int jpc_dec_cp_setfromcod(jpc_dec_cp_t *cp, jpc_cod_t *cod);
+static int jpc_dec_cp_setfromcoc(jpc_dec_cp_t *cp, jpc_coc_t *coc);
+static int jpc_dec_cp_setfromcox(jpc_dec_cp_t *cp, jpc_dec_ccp_t *ccp,
+  jpc_coxcp_t *compparms, int flags);
+static int jpc_dec_cp_setfromqcd(jpc_dec_cp_t *cp, jpc_qcd_t *qcd);
+static int jpc_dec_cp_setfromqcc(jpc_dec_cp_t *cp, jpc_qcc_t *qcc);
+static int jpc_dec_cp_setfromqcx(jpc_dec_cp_t *cp, jpc_dec_ccp_t *ccp,
+  jpc_qcxcp_t *compparms, int flags);
+static int jpc_dec_cp_setfromrgn(jpc_dec_cp_t *cp, jpc_rgn_t *rgn);
+static int jpc_dec_cp_prepare(jpc_dec_cp_t *cp);
+static void jpc_dec_cp_destroy(jpc_dec_cp_t *cp);
+static int jpc_dec_cp_setfrompoc(jpc_dec_cp_t *cp, jpc_poc_t *poc, int reset);
+static int jpc_pi_addpchgfrompoc(jpc_pi_t *pi, jpc_poc_t *poc);
+
+static int jpc_dec_decode(jpc_dec_t *dec);
+static jpc_dec_t *jpc_dec_create(jpc_dec_importopts_t *impopts, jas_stream_t *in);
+static void jpc_dec_destroy(jpc_dec_t *dec);
+static void jpc_dequantize(jas_matrix_t *x, jpc_fix_t absstepsize);
+static void jpc_undo_roi(jas_matrix_t *x, int roishift, int bgshift, int numbps);
+static jpc_fix_t jpc_calcabsstepsize(int stepsize, int numbits);
+static int jpc_dec_tiledecode(jpc_dec_t *dec, jpc_dec_tile_t *tile);
+static int jpc_dec_tileinit(jpc_dec_t *dec, jpc_dec_tile_t *tile);
+static int jpc_dec_tilefini(jpc_dec_t *dec, jpc_dec_tile_t *tile);
+static int jpc_dec_process_soc(jpc_dec_t *dec, jpc_ms_t *ms);
+static int jpc_dec_process_sot(jpc_dec_t *dec, jpc_ms_t *ms);
+static int jpc_dec_process_sod(jpc_dec_t *dec, jpc_ms_t *ms);
+static int jpc_dec_process_eoc(jpc_dec_t *dec, jpc_ms_t *ms);
+static int jpc_dec_process_siz(jpc_dec_t *dec, jpc_ms_t *ms);
+static int jpc_dec_process_cod(jpc_dec_t *dec, jpc_ms_t *ms);
+static int jpc_dec_process_coc(jpc_dec_t *dec, jpc_ms_t *ms);
+static int jpc_dec_process_rgn(jpc_dec_t *dec, jpc_ms_t *ms);
+static int jpc_dec_process_qcd(jpc_dec_t *dec, jpc_ms_t *ms);
+static int jpc_dec_process_qcc(jpc_dec_t *dec, jpc_ms_t *ms);
+static int jpc_dec_process_poc(jpc_dec_t *dec, jpc_ms_t *ms);
+static int jpc_dec_process_ppm(jpc_dec_t *dec, jpc_ms_t *ms);
+static int jpc_dec_process_ppt(jpc_dec_t *dec, jpc_ms_t *ms);
+static int jpc_dec_process_com(jpc_dec_t *dec, jpc_ms_t *ms);
+static int jpc_dec_process_unk(jpc_dec_t *dec, jpc_ms_t *ms);
+static int jpc_dec_process_crg(jpc_dec_t *dec, jpc_ms_t *ms);
+static int jpc_dec_parseopts(char *optstr, jpc_dec_importopts_t *opts);
+
+/******************************************************************************\
+* Global data.
+\******************************************************************************/
+
+jpc_dec_mstabent_t jpc_dec_mstab[] = {
+	{JPC_MS_SOC, JPC_MHSOC, jpc_dec_process_soc},
+	{JPC_MS_SOT, JPC_MH | JPC_TPHSOT, jpc_dec_process_sot},
+	{JPC_MS_SOD, JPC_TPH, jpc_dec_process_sod},
+	{JPC_MS_EOC, JPC_TPHSOT, jpc_dec_process_eoc},
+	{JPC_MS_SIZ, JPC_MHSIZ, jpc_dec_process_siz},
+	{JPC_MS_COD, JPC_MH | JPC_TPH, jpc_dec_process_cod},
+	{JPC_MS_COC, JPC_MH | JPC_TPH, jpc_dec_process_coc},
+	{JPC_MS_RGN, JPC_MH | JPC_TPH, jpc_dec_process_rgn},
+	{JPC_MS_QCD, JPC_MH | JPC_TPH, jpc_dec_process_qcd},
+	{JPC_MS_QCC, JPC_MH | JPC_TPH, jpc_dec_process_qcc},
+	{JPC_MS_POC, JPC_MH | JPC_TPH, jpc_dec_process_poc},
+	{JPC_MS_TLM, JPC_MH, 0},
+	{JPC_MS_PLM, JPC_MH, 0},
+	{JPC_MS_PLT, JPC_TPH, 0},
+	{JPC_MS_PPM, JPC_MH, jpc_dec_process_ppm},
+	{JPC_MS_PPT, JPC_TPH, jpc_dec_process_ppt},
+	{JPC_MS_SOP, 0, 0},
+	{JPC_MS_CRG, JPC_MH, jpc_dec_process_crg},
+	{JPC_MS_COM, JPC_MH | JPC_TPH, jpc_dec_process_com},
+	{0, JPC_MH | JPC_TPH, jpc_dec_process_unk}
+};
+
+/******************************************************************************\
+* The main entry point for the JPEG-2000 decoder.
+\******************************************************************************/
+
+jas_image_t *jpc_decode(jas_stream_t *in, char *optstr)
+{
+	jpc_dec_importopts_t opts;
+	jpc_dec_t *dec;
+	jas_image_t *image;
+
+	dec = 0;
+
+	if (jpc_dec_parseopts(optstr, &opts)) {
+		goto error;
+	}
+
+	jpc_initluts();
+
+	if (!(dec = jpc_dec_create(&opts, in))) {
+		goto error;
+	}
+
+	/* Do most of the work. */
+	if (jpc_dec_decode(dec)) {
+		goto error;
+	}
+
+	if (jas_image_numcmpts(dec->image) >= 3) {
+		jas_image_setcolorspace(dec->image, JAS_IMAGE_CS_RGB);
+		jas_image_setcmpttype(dec->image, 0,
+		  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_R));
+		jas_image_setcmpttype(dec->image, 1,
+		  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_G));
+		jas_image_setcmpttype(dec->image, 2,
+		  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_B));
+	} else {
+		jas_image_setcolorspace(dec->image, JAS_IMAGE_CS_GRAY);
+		jas_image_setcmpttype(dec->image, 0,
+		  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_GRAY_Y));
+	}
+
+	/* Save the return value. */
+	image = dec->image;
+
+	/* Stop the image from being discarded. */
+	dec->image = 0;
+
+	/* Destroy decoder. */
+	jpc_dec_destroy(dec);
+
+	return image;
+
+error:
+	if (dec) {
+		jpc_dec_destroy(dec);
+	}
+	return 0;
+}
+
+typedef enum {
+	OPT_MAXLYRS,
+	OPT_MAXPKTS,
+	OPT_DEBUG
+} optid_t;
+
+jas_taginfo_t decopts[] = {
+	{OPT_MAXLYRS, "maxlyrs"},
+	{OPT_MAXPKTS, "maxpkts"},
+	{OPT_DEBUG, "debug"},
+	{-1, 0}
+};
+
+static int jpc_dec_parseopts(char *optstr, jpc_dec_importopts_t *opts)
+{
+	jas_tvparser_t *tvp;
+
+	opts->debug = 0;
+	opts->maxlyrs = JPC_MAXLYRS;
+	opts->maxpkts = -1;
+
+	if (!(tvp = jas_tvparser_create(optstr ? optstr : ""))) {
+		return -1;
+	}
+
+	while (!jas_tvparser_next(tvp)) {
+		switch (jas_taginfo_nonull(jas_taginfos_lookup(decopts,
+		  jas_tvparser_gettag(tvp)))->id) {
+		case OPT_MAXLYRS:
+			opts->maxlyrs = atoi(jas_tvparser_getval(tvp));
+			break;
+		case OPT_DEBUG:
+			opts->debug = atoi(jas_tvparser_getval(tvp));
+			break;
+		case OPT_MAXPKTS:
+			opts->maxpkts = atoi(jas_tvparser_getval(tvp));
+			break;
+		default:
+			fprintf(stderr, "warning: ignoring invalid option %s\n",
+			  jas_tvparser_gettag(tvp));
+			break;
+		}
+	}
+
+	jas_tvparser_destroy(tvp);
+
+	return 0;
+}
+
+/******************************************************************************\
+* Code for table-driven code stream decoder.
+\******************************************************************************/
+
+static jpc_dec_mstabent_t *jpc_dec_mstab_lookup(uint_fast16_t id)
+{
+	jpc_dec_mstabent_t *mstabent;
+	for (mstabent = jpc_dec_mstab; mstabent->id != 0; ++mstabent) {
+		if (mstabent->id == id) {
+			break;
+		}
+	}
+	return mstabent;
+}
+
+static int jpc_dec_decode(jpc_dec_t *dec)
+{
+	jpc_ms_t *ms;
+	jpc_dec_mstabent_t *mstabent;
+	int ret;
+	jpc_cstate_t *cstate;
+
+	if (!(cstate = jpc_cstate_create())) {
+		return -1;
+	}
+	dec->cstate = cstate;
+
+	/* Initially, we should expect to encounter a SOC marker segment. */
+	dec->state = JPC_MHSOC;
+
+	for (;;) {
+
+		/* Get the next marker segment in the code stream. */
+		if (!(ms = jpc_getms(dec->in, cstate))) {
+			fprintf(stderr, "cannot get marker segment\n");
+			return -1;
+		}
+
+		mstabent = jpc_dec_mstab_lookup(ms->id);
+		assert(mstabent);
+
+		/* Ensure that this type of marker segment is permitted
+		  at this point in the code stream. */
+		if (!(dec->state & mstabent->validstates)) {
+			fprintf(stderr, "unexpected marker segment type\n");
+			jpc_ms_destroy(ms);
+			return -1;
+		}
+
+		/* Process the marker segment. */
+		if (mstabent->action) {
+			ret = (*mstabent->action)(dec, ms);
+		} else {
+			/* No explicit action is required. */
+			ret = 0;
+		}
+
+		/* Destroy the marker segment. */
+		jpc_ms_destroy(ms);
+
+		if (ret < 0) {
+			return -1;
+		} else if (ret > 0) {
+			break;
+		}
+
+	}
+
+	return 0;
+}
+
+static int jpc_dec_process_crg(jpc_dec_t *dec, jpc_ms_t *ms)
+{
+	uint_fast16_t cmptno;
+	jpc_dec_cmpt_t *cmpt;
+	jpc_crg_t *crg;
+
+	crg = &ms->parms.crg;
+	for (cmptno = 0, cmpt = dec->cmpts; cmptno < dec->numcomps; ++cmptno,
+	  ++cmpt) {
+		/* Ignore the information in the CRG marker segment for now.
+		  This information serves no useful purpose for decoding anyhow.
+		  Some other parts of the code need to be changed if these lines
+		  are uncommented.
+		cmpt->hsubstep = crg->comps[cmptno].hoff;
+		cmpt->vsubstep = crg->comps[cmptno].voff;
+		*/
+	}
+	return 0;
+}
+
+static int jpc_dec_process_soc(jpc_dec_t *dec, jpc_ms_t *ms)
+{
+	/* We should expect to encounter a SIZ marker segment next. */
+	dec->state = JPC_MHSIZ;
+
+	return 0;
+}
+
+static int jpc_dec_process_sot(jpc_dec_t *dec, jpc_ms_t *ms)
+{
+	jpc_dec_tile_t *tile;
+	jpc_sot_t *sot = &ms->parms.sot;
+	jas_image_cmptparm_t *compinfos;
+	jas_image_cmptparm_t *compinfo;
+	jpc_dec_cmpt_t *cmpt;
+	uint_fast16_t cmptno;
+
+	if (dec->state == JPC_MH) {
+
+		compinfos = jas_malloc(dec->numcomps * sizeof(jas_image_cmptparm_t));
+		assert(compinfos);
+		for (cmptno = 0, cmpt = dec->cmpts, compinfo = compinfos;
+		  cmptno < dec->numcomps; ++cmptno, ++cmpt, ++compinfo) {
+			compinfo->tlx = 0;
+			compinfo->tly = 0;
+			compinfo->prec = cmpt->prec;
+			compinfo->sgnd = cmpt->sgnd;
+			compinfo->width = cmpt->width;
+			compinfo->height = cmpt->height;
+			compinfo->hstep = cmpt->hstep;
+			compinfo->vstep = cmpt->vstep;
+		}
+
+		if (!(dec->image = jas_image_create(dec->numcomps, compinfos,
+		  JAS_IMAGE_CS_UNKNOWN))) {
+			return -1;
+		}
+		jas_free(compinfos);
+
+		/* Is the packet header information stored in PPM marker segments in
+		  the main header? */
+		if (dec->ppmstab) {
+			/* Convert the PPM marker segment data into a collection of streams
+			  (one stream per tile-part). */
+			if (!(dec->pkthdrstreams = jpc_ppmstabtostreams(dec->ppmstab))) {
+				abort();
+			}
+			jpc_ppxstab_destroy(dec->ppmstab);
+			dec->ppmstab = 0;
+		}
+	}
+
+	if (sot->len > 0) {
+		dec->curtileendoff = jas_stream_getrwcount(dec->in) - ms->len -
+		  4 + sot->len;
+	} else {
+		dec->curtileendoff = 0;
+	}
+
+	if (sot->tileno > dec->numtiles) {
+		fprintf(stderr, "invalid tile number in SOT marker segment\n");
+		return -1;
+	}
+	/* Set the current tile. */
+	dec->curtile = &dec->tiles[sot->tileno];
+	tile = dec->curtile;
+	/* Ensure that this is the expected part number. */
+	if (sot->partno != tile->partno) {
+		return -1;
+	}
+	if (tile->numparts > 0 && sot->partno >= tile->numparts) {
+		return -1;
+	}
+	if (!tile->numparts && sot->numparts > 0) {
+		tile->numparts = sot->numparts;
+	}
+
+	tile->pptstab = 0;
+
+	switch (tile->state) {
+	case JPC_TILE_INIT:
+		/* This is the first tile-part for this tile. */
+		tile->state = JPC_TILE_ACTIVE;
+		assert(!tile->cp);
+		if (!(tile->cp = jpc_dec_cp_copy(dec->cp))) {
+			return -1;
+		}
+		jpc_dec_cp_resetflags(dec->cp);
+		break;
+	default:
+		if (sot->numparts == sot->partno - 1) {
+			tile->state = JPC_TILE_ACTIVELAST;
+		}
+		break;
+	}
+
+	/* Note: We do not increment the expected tile-part number until
+	  all processing for this tile-part is complete. */
+
+	/* We should expect to encounter other tile-part header marker
+	  segments next. */
+	dec->state = JPC_TPH;
+
+	return 0;
+}
+
+static int jpc_dec_process_sod(jpc_dec_t *dec, jpc_ms_t *ms)
+{
+	jpc_dec_tile_t *tile;
+	int pos;
+
+	if (!(tile = dec->curtile)) {
+		return -1;
+	}
+
+	if (!tile->partno) {
+		if (!jpc_dec_cp_isvalid(tile->cp)) {
+			return -1;
+		}
+		jpc_dec_cp_prepare(tile->cp);
+		if (jpc_dec_tileinit(dec, tile)) {
+			return -1;
+		}
+	}
+
+	/* Are packet headers stored in the main header or tile-part header? */
+	if (dec->pkthdrstreams) {
+		/* Get the stream containing the packet header data for this
+		  tile-part. */
+		if (!(tile->pkthdrstream = jpc_streamlist_remove(dec->pkthdrstreams, 0))) {
+			return -1;
+		}
+	}
+
+	if (tile->pptstab) {
+		if (!tile->pkthdrstream) {
+			if (!(tile->pkthdrstream = jas_stream_memopen(0, 0))) {
+				return -1;
+			}
+		}
+		pos = jas_stream_tell(tile->pkthdrstream);
+		jas_stream_seek(tile->pkthdrstream, 0, SEEK_END);
+		if (jpc_pptstabwrite(tile->pkthdrstream, tile->pptstab)) {
+			return -1;
+		}
+		jas_stream_seek(tile->pkthdrstream, pos, SEEK_SET);
+		jpc_ppxstab_destroy(tile->pptstab);
+		tile->pptstab = 0;
+	}
+
+	if (jas_getdbglevel() >= 10) {
+		jpc_dec_dump(dec, stderr);
+	}
+
+	if (jpc_dec_decodepkts(dec, (tile->pkthdrstream) ? tile->pkthdrstream :
+	  dec->in, dec->in)) {
+		fprintf(stderr, "jpc_dec_decodepkts failed\n");
+		return -1;
+	}
+
+	/* Gobble any unconsumed tile data. */
+	if (dec->curtileendoff > 0) {
+		uint_fast32_t curoff;
+		uint_fast32_t n;
+		curoff = jas_stream_getrwcount(dec->in);
+		if (curoff < dec->curtileendoff) {
+			n = dec->curtileendoff - curoff;
+			fprintf(stderr,
+			  "warning: ignoring trailing garbage (%lu bytes)\n",
+			  (unsigned long) n);
+
+			while (n-- > 0) {
+				if (jas_stream_getc(dec->in) == EOF) {
+					fprintf(stderr, "read error\n");
+					return -1;
+				}
+			}
+		} else if (curoff > dec->curtileendoff) {
+			fprintf(stderr,
+			  "warning: not enough tile data (%lu bytes)\n",
+			  (unsigned long) curoff - dec->curtileendoff);
+		}
+
+	}
+
+	if (tile->numparts > 0 && tile->partno == tile->numparts - 1) {
+		if (jpc_dec_tiledecode(dec, tile)) {
+			return -1;
+		}
+		jpc_dec_tilefini(dec, tile);
+	}
+
+	dec->curtile = 0;
+
+	/* Increment the expected tile-part number. */
+	++tile->partno;
+
+	/* We should expect to encounter a SOT marker segment next. */
+	dec->state = JPC_TPHSOT;
+
+	return 0;
+}
+
+static int jpc_dec_tileinit(jpc_dec_t *dec, jpc_dec_tile_t *tile)
+{
+	jpc_dec_tcomp_t *tcomp;
+	uint_fast16_t compno;
+	int rlvlno;
+	jpc_dec_rlvl_t *rlvl;
+	jpc_dec_band_t *band;
+	jpc_dec_prc_t *prc;
+	int bndno;
+	jpc_tsfb_band_t *bnd;
+	int bandno;
+	jpc_dec_ccp_t *ccp;
+	int prccnt;
+	jpc_dec_cblk_t *cblk;
+	int cblkcnt;
+	uint_fast32_t tlprcxstart;
+	uint_fast32_t tlprcystart;
+	uint_fast32_t brprcxend;
+	uint_fast32_t brprcyend;
+	uint_fast32_t tlcbgxstart;
+	uint_fast32_t tlcbgystart;
+	uint_fast32_t brcbgxend;
+	uint_fast32_t brcbgyend;
+	uint_fast32_t cbgxstart;
+	uint_fast32_t cbgystart;
+	uint_fast32_t cbgxend;
+	uint_fast32_t cbgyend;
+	uint_fast32_t tlcblkxstart;
+	uint_fast32_t tlcblkystart;
+	uint_fast32_t brcblkxend;
+	uint_fast32_t brcblkyend;
+	uint_fast32_t cblkxstart;
+	uint_fast32_t cblkystart;
+	uint_fast32_t cblkxend;
+	uint_fast32_t cblkyend;
+	uint_fast32_t tmpxstart;
+	uint_fast32_t tmpystart;
+	uint_fast32_t tmpxend;
+	uint_fast32_t tmpyend;
+	jpc_dec_cp_t *cp;
+	jpc_tsfb_band_t bnds[64];
+	jpc_pchg_t *pchg;
+	int pchgno;
+	jpc_dec_cmpt_t *cmpt;
+
+	cp = tile->cp;
+	tile->realmode = 0;
+	if (cp->mctid == JPC_MCT_ICT) {
+		tile->realmode = 1;
+	}
+
+	for (compno = 0, tcomp = tile->tcomps, cmpt = dec->cmpts; compno <
+	  dec->numcomps; ++compno, ++tcomp, ++cmpt) {
+		ccp = &tile->cp->ccps[compno];
+		if (ccp->qmfbid == JPC_COX_INS) {
+			tile->realmode = 1;
+		}
+		tcomp->numrlvls = ccp->numrlvls;
+		if (!(tcomp->rlvls = jas_malloc(tcomp->numrlvls *
+		  sizeof(jpc_dec_rlvl_t)))) {
+			return -1;
+		}
+		if (!(tcomp->data = jas_seq2d_create(JPC_CEILDIV(tile->xstart,
+		  cmpt->hstep), JPC_CEILDIV(tile->ystart, cmpt->vstep),
+		  JPC_CEILDIV(tile->xend, cmpt->hstep), JPC_CEILDIV(tile->yend,
+		  cmpt->vstep)))) {
+			return -1;
+		}
+		if (!(tcomp->tsfb = jpc_cod_gettsfb(ccp->qmfbid,
+		  tcomp->numrlvls - 1))) {
+			return -1;
+		}
+{
+	jpc_tsfb_getbands(tcomp->tsfb, jas_seq2d_xstart(tcomp->data), jas_seq2d_ystart(tcomp->data), jas_seq2d_xend(tcomp->data), jas_seq2d_yend(tcomp->data), bnds);
+}
+		for (rlvlno = 0, rlvl = tcomp->rlvls; rlvlno < tcomp->numrlvls;
+		  ++rlvlno, ++rlvl) {
+rlvl->bands = 0;
+			rlvl->xstart = JPC_CEILDIVPOW2(tcomp->xstart,
+			  tcomp->numrlvls - 1 - rlvlno);
+			rlvl->ystart = JPC_CEILDIVPOW2(tcomp->ystart,
+			  tcomp->numrlvls - 1 - rlvlno);
+			rlvl->xend = JPC_CEILDIVPOW2(tcomp->xend,
+			  tcomp->numrlvls - 1 - rlvlno);
+			rlvl->yend = JPC_CEILDIVPOW2(tcomp->yend,
+			  tcomp->numrlvls - 1 - rlvlno);
+			rlvl->prcwidthexpn = ccp->prcwidthexpns[rlvlno];
+			rlvl->prcheightexpn = ccp->prcheightexpns[rlvlno];
+			tlprcxstart = JPC_FLOORDIVPOW2(rlvl->xstart,
+			  rlvl->prcwidthexpn) << rlvl->prcwidthexpn;
+			tlprcystart = JPC_FLOORDIVPOW2(rlvl->ystart,
+			  rlvl->prcheightexpn) << rlvl->prcheightexpn;
+			brprcxend = JPC_CEILDIVPOW2(rlvl->xend,
+			  rlvl->prcwidthexpn) << rlvl->prcwidthexpn;
+			brprcyend = JPC_CEILDIVPOW2(rlvl->yend,
+			  rlvl->prcheightexpn) << rlvl->prcheightexpn;
+			rlvl->numhprcs = (brprcxend - tlprcxstart) >>
+			  rlvl->prcwidthexpn;
+			rlvl->numvprcs = (brprcyend - tlprcystart) >>
+			  rlvl->prcheightexpn;
+			rlvl->numprcs = rlvl->numhprcs * rlvl->numvprcs;
+
+			if (rlvl->xstart >= rlvl->xend || rlvl->ystart >= rlvl->yend) {
+				rlvl->bands = 0;
+				rlvl->numprcs = 0;
+				rlvl->numhprcs = 0;
+				rlvl->numvprcs = 0;
+				continue;
+			}	
+			if (!rlvlno) {
+				tlcbgxstart = tlprcxstart;
+				tlcbgystart = tlprcystart;
+				brcbgxend = brprcxend;
+				brcbgyend = brprcyend;
+				rlvl->cbgwidthexpn = rlvl->prcwidthexpn;
+				rlvl->cbgheightexpn = rlvl->prcheightexpn;
+			} else {
+				tlcbgxstart = JPC_CEILDIVPOW2(tlprcxstart, 1);
+				tlcbgystart = JPC_CEILDIVPOW2(tlprcystart, 1);
+				brcbgxend = JPC_CEILDIVPOW2(brprcxend, 1);
+				brcbgyend = JPC_CEILDIVPOW2(brprcyend, 1);
+				rlvl->cbgwidthexpn = rlvl->prcwidthexpn - 1;
+				rlvl->cbgheightexpn = rlvl->prcheightexpn - 1;
+			}
+			rlvl->cblkwidthexpn = JAS_MIN(ccp->cblkwidthexpn,
+			  rlvl->cbgwidthexpn);
+			rlvl->cblkheightexpn = JAS_MIN(ccp->cblkheightexpn,
+			  rlvl->cbgheightexpn);
+
+			rlvl->numbands = (!rlvlno) ? 1 : 3;
+			if (!(rlvl->bands = jas_malloc(rlvl->numbands *
+			  sizeof(jpc_dec_band_t)))) {
+				return -1;
+			}
+			for (bandno = 0, band = rlvl->bands;
+			  bandno < rlvl->numbands; ++bandno, ++band) {
+				bndno = (!rlvlno) ? 0 : (3 * (rlvlno - 1) +
+				  bandno + 1);
+				bnd = &bnds[bndno];
+
+				band->orient = bnd->orient;
+				band->stepsize = ccp->stepsizes[bndno];
+				band->analgain = JPC_NOMINALGAIN(ccp->qmfbid,
+				  tcomp->numrlvls - 1, rlvlno, band->orient);
+				band->absstepsize = jpc_calcabsstepsize(band->stepsize,
+				  cmpt->prec + band->analgain);
+				band->numbps = ccp->numguardbits +
+				  JPC_QCX_GETEXPN(band->stepsize) - 1;
+				band->roishift = (ccp->roishift + band->numbps >= JPC_PREC) ?
+				  (JPC_PREC - 1 - band->numbps) : ccp->roishift;
+				band->data = 0;
+				band->prcs = 0;
+				if (bnd->xstart == bnd->xend || bnd->ystart == bnd->yend) {
+					continue;
+				}
+				if (!(band->data = jas_seq2d_create(0, 0, 0, 0))) {
+					return -1;
+				}
+				jas_seq2d_bindsub(band->data, tcomp->data, bnd->locxstart, bnd->locystart, bnd->locxend, bnd->locyend);
+				jas_seq2d_setshift(band->data, bnd->xstart, bnd->ystart);
+
+				assert(rlvl->numprcs);
+
+				if (!(band->prcs = jas_malloc(rlvl->numprcs * sizeof(jpc_dec_prc_t)))) {
+					return -1;
+				}
+
+/************************************************/
+	cbgxstart = tlcbgxstart;
+	cbgystart = tlcbgystart;
+	for (prccnt = rlvl->numprcs, prc = band->prcs;
+	  prccnt > 0; --prccnt, ++prc) {
+		cbgxend = cbgxstart + (1 << rlvl->cbgwidthexpn);
+		cbgyend = cbgystart + (1 << rlvl->cbgheightexpn);
+		prc->xstart = JAS_MAX(cbgxstart, jas_seq2d_xstart(band->data));
+		prc->ystart = JAS_MAX(cbgystart, jas_seq2d_ystart(band->data));
+		prc->xend = JAS_MIN(cbgxend, jas_seq2d_xend(band->data));
+		prc->yend = JAS_MIN(cbgyend, jas_seq2d_yend(band->data));
+		if (prc->xend > prc->xstart && prc->yend > prc->ystart) {
+			tlcblkxstart = JPC_FLOORDIVPOW2(prc->xstart,
+			  rlvl->cblkwidthexpn) << rlvl->cblkwidthexpn;
+			tlcblkystart = JPC_FLOORDIVPOW2(prc->ystart,
+			  rlvl->cblkheightexpn) << rlvl->cblkheightexpn;
+			brcblkxend = JPC_CEILDIVPOW2U(prc->xend,
+			  rlvl->cblkwidthexpn) << rlvl->cblkwidthexpn;
+			brcblkyend = JPC_CEILDIVPOW2U(prc->yend,
+			  rlvl->cblkheightexpn) << rlvl->cblkheightexpn;
+			prc->numhcblks = (brcblkxend - tlcblkxstart) >>
+			  rlvl->cblkwidthexpn;
+			prc->numvcblks = (brcblkyend - tlcblkystart) >>
+			  rlvl->cblkheightexpn;
+			prc->numcblks = prc->numhcblks * prc->numvcblks;
+			assert(prc->numcblks > 0);
+
+			if (!(prc->incltagtree = jpc_tagtree_create(prc->numhcblks, prc->numvcblks))) {
+				return -1;
+			}
+			if (!(prc->numimsbstagtree = jpc_tagtree_create(prc->numhcblks, prc->numvcblks))) {
+				return -1;
+			}
+			if (!(prc->cblks = jas_malloc(prc->numcblks * sizeof(jpc_dec_cblk_t)))) {
+				return -1;
+			}
+
+			cblkxstart = cbgxstart;
+			cblkystart = cbgystart;
+			for (cblkcnt = prc->numcblks, cblk = prc->cblks; cblkcnt > 0;) {
+				cblkxend = cblkxstart + (1 << rlvl->cblkwidthexpn);
+				cblkyend = cblkystart + (1 << rlvl->cblkheightexpn);
+				tmpxstart = JAS_MAX(cblkxstart, prc->xstart);
+				tmpystart = JAS_MAX(cblkystart, prc->ystart);
+				tmpxend = JAS_MIN(cblkxend, prc->xend);
+				tmpyend = JAS_MIN(cblkyend, prc->yend);
+				if (tmpxend > tmpxstart && tmpyend > tmpystart) {
+					cblk->firstpassno = -1;
+					cblk->mqdec = 0;
+					cblk->nulldec = 0;
+					cblk->flags = 0;
+					cblk->numpasses = 0;
+					cblk->segs.head = 0;
+					cblk->segs.tail = 0;
+					cblk->curseg = 0;
+					cblk->numimsbs = 0;
+					cblk->numlenbits = 3;
+					cblk->flags = 0;
+					if (!(cblk->data = jas_seq2d_create(0, 0, 0, 0))) {
+						return -1;
+					}
+					jas_seq2d_bindsub(cblk->data, band->data, tmpxstart, tmpystart, tmpxend, tmpyend);
+					++cblk;
+					--cblkcnt;
+				}
+				cblkxstart += 1 << rlvl->cblkwidthexpn;
+				if (cblkxstart >= cbgxend) {
+					cblkxstart = cbgxstart;
+					cblkystart += 1 << rlvl->cblkheightexpn;
+				}
+			}
+
+		} else {
+			prc->cblks = 0;
+			prc->incltagtree = 0;
+			prc->numimsbstagtree = 0;
+		}
+		cbgxstart += 1 << rlvl->cbgwidthexpn;
+		if (cbgxstart >= brcbgxend) {
+			cbgxstart = tlcbgxstart;
+			cbgystart += 1 << rlvl->cbgheightexpn;
+		}
+
+	}
+/********************************************/
+			}
+		}
+	}
+
+if (!(tile->pi = jpc_dec_pi_create(dec, tile)))
+{
+	return -1;
+}
+
+	for (pchgno = 0; pchgno < jpc_pchglist_numpchgs(tile->cp->pchglist);
+	  ++pchgno) {
+		pchg = jpc_pchg_copy(jpc_pchglist_get(tile->cp->pchglist, pchgno));
+		assert(pchg);
+		jpc_pi_addpchg(tile->pi, pchg);
+	}
+	jpc_pi_init(tile->pi);
+
+	return 0;
+}
+
+static int jpc_dec_tilefini(jpc_dec_t *dec, jpc_dec_tile_t *tile)
+{
+	jpc_dec_tcomp_t *tcomp;
+	int compno;
+	int bandno;
+	int rlvlno;
+	jpc_dec_band_t *band;
+	jpc_dec_rlvl_t *rlvl;
+	int prcno;
+	jpc_dec_prc_t *prc;
+	jpc_dec_seg_t *seg;
+	jpc_dec_cblk_t *cblk;
+	int cblkno;
+
+if (tile->tcomps) {
+
+	for (compno = 0, tcomp = tile->tcomps; compno < dec->numcomps;
+	  ++compno, ++tcomp) {
+		for (rlvlno = 0, rlvl = tcomp->rlvls; rlvlno < tcomp->numrlvls;
+		  ++rlvlno, ++rlvl) {
+if (!rlvl->bands) {
+	continue;
+}
+			for (bandno = 0, band = rlvl->bands; bandno < rlvl->numbands; ++bandno, ++band) {
+if (band->prcs) {
+				for (prcno = 0, prc = band->prcs; prcno <
+				  rlvl->numprcs; ++prcno, ++prc) {
+if (!prc->cblks) {
+	continue;
+}
+					for (cblkno = 0, cblk = prc->cblks; cblkno < prc->numcblks; ++cblkno, ++cblk) {
+
+	while (cblk->segs.head) {
+		seg = cblk->segs.head;
+		jpc_seglist_remove(&cblk->segs, seg);
+		jpc_seg_destroy(seg);
+	}
+	jas_matrix_destroy(cblk->data);
+	if (cblk->mqdec) {
+		jpc_mqdec_destroy(cblk->mqdec);
+	}
+	if (cblk->nulldec) {
+		jpc_bitstream_close(cblk->nulldec);
+	}
+	if (cblk->flags) {
+		jas_matrix_destroy(cblk->flags);
+	}
+					}
+					if (prc->incltagtree) {
+						jpc_tagtree_destroy(prc->incltagtree);
+					}
+					if (prc->numimsbstagtree) {
+						jpc_tagtree_destroy(prc->numimsbstagtree);
+					}
+					if (prc->cblks) {
+						jas_free(prc->cblks);
+					}
+				}
+}
+				if (band->data) {
+					jas_matrix_destroy(band->data);
+				}
+				if (band->prcs) {
+					jas_free(band->prcs);
+				}
+			}
+			if (rlvl->bands) {
+				jas_free(rlvl->bands);
+			}
+		}
+		if (tcomp->rlvls) {
+			jas_free(tcomp->rlvls);
+		}
+		if (tcomp->data) {
+			jas_matrix_destroy(tcomp->data);
+		}
+		if (tcomp->tsfb) {
+			jpc_tsfb_destroy(tcomp->tsfb);
+		}
+	}
+}
+	if (tile->cp) {
+		jpc_dec_cp_destroy(tile->cp);
+		tile->cp = 0;
+	}
+	if (tile->tcomps) {
+		jas_free(tile->tcomps);
+		tile->tcomps = 0;
+	}
+	if (tile->pi) {
+		jpc_pi_destroy(tile->pi);
+		tile->pi = 0;
+	}
+	if (tile->pkthdrstream) {
+		jas_stream_close(tile->pkthdrstream);
+		tile->pkthdrstream = 0;
+	}
+	if (tile->pptstab) {
+		jpc_ppxstab_destroy(tile->pptstab);
+		tile->pptstab = 0;
+	}
+
+	tile->state = JPC_TILE_DONE;
+
+	return 0;
+}
+
+static int jpc_dec_tiledecode(jpc_dec_t *dec, jpc_dec_tile_t *tile)
+{
+	int i;
+	int j;
+	jpc_dec_tcomp_t *tcomp;
+	jpc_dec_rlvl_t *rlvl;
+	jpc_dec_band_t *band;
+	int compno;
+	int rlvlno;
+	int bandno;
+	int adjust;
+	int v;
+	jpc_dec_ccp_t *ccp;
+	jpc_dec_cmpt_t *cmpt;
+
+	if (jpc_dec_decodecblks(dec, tile)) {
+		fprintf(stderr, "jpc_dec_decodecblks failed\n");
+		return -1;
+	}
+
+	/* Perform dequantization. */
+	for (compno = 0, tcomp = tile->tcomps; compno < dec->numcomps;
+	  ++compno, ++tcomp) {
+		ccp = &tile->cp->ccps[compno];
+		for (rlvlno = 0, rlvl = tcomp->rlvls; rlvlno < tcomp->numrlvls;
+		  ++rlvlno, ++rlvl) {
+			if (!rlvl->bands) {
+				continue;
+			}
+			for (bandno = 0, band = rlvl->bands;
+			  bandno < rlvl->numbands; ++bandno, ++band) {
+				if (!band->data) {
+					continue;
+				}
+				jpc_undo_roi(band->data, band->roishift, ccp->roishift -
+				  band->roishift, band->numbps);
+				if (tile->realmode) {
+					jas_matrix_asl(band->data, JPC_FIX_FRACBITS);
+					jpc_dequantize(band->data, band->absstepsize);
+				}
+
+			}
+		}
+	}
+
+	/* Apply an inverse wavelet transform if necessary. */
+	for (compno = 0, tcomp = tile->tcomps; compno < dec->numcomps;
+	  ++compno, ++tcomp) {
+		ccp = &tile->cp->ccps[compno];
+		jpc_tsfb_synthesize(tcomp->tsfb, ((ccp->qmfbid ==
+		  JPC_COX_RFT) ? JPC_TSFB_RITIMODE : 0), tcomp->data);
+	}
+
+
+	/* Apply an inverse intercomponent transform if necessary. */
+	switch (tile->cp->mctid) {
+	case JPC_MCT_RCT:
+		assert(dec->numcomps == 3);
+		jpc_irct(tile->tcomps[0].data, tile->tcomps[1].data,
+		  tile->tcomps[2].data);
+		break;
+	case JPC_MCT_ICT:
+		assert(dec->numcomps == 3);
+		jpc_iict(tile->tcomps[0].data, tile->tcomps[1].data,
+		  tile->tcomps[2].data);
+		break;
+	}
+
+	/* Perform rounding and convert to integer values. */
+	if (tile->realmode) {
+		for (compno = 0, tcomp = tile->tcomps; compno < dec->numcomps;
+		  ++compno, ++tcomp) {
+			for (i = 0; i < jas_matrix_numrows(tcomp->data); ++i) {
+				for (j = 0; j < jas_matrix_numcols(tcomp->data); ++j) {
+					v = jas_matrix_get(tcomp->data, i, j);
+					v = jpc_fix_round(v);
+					jas_matrix_set(tcomp->data, i, j, jpc_fixtoint(v));
+				}
+			}
+		}
+	}
+
+	/* Perform level shift. */
+	for (compno = 0, tcomp = tile->tcomps, cmpt = dec->cmpts; compno <
+	  dec->numcomps; ++compno, ++tcomp, ++cmpt) {
+		adjust = cmpt->sgnd ? 0 : (1 << (cmpt->prec - 1));
+		for (i = 0; i < jas_matrix_numrows(tcomp->data); ++i) {
+			for (j = 0; j < jas_matrix_numcols(tcomp->data); ++j) {
+				*jas_matrix_getref(tcomp->data, i, j) += adjust;
+			}
+		}
+	}
+
+	/* Perform clipping. */
+	for (compno = 0, tcomp = tile->tcomps, cmpt = dec->cmpts; compno <
+	  dec->numcomps; ++compno, ++tcomp, ++cmpt) {
+		jpc_fix_t mn;
+		jpc_fix_t mx;
+		mn = cmpt->sgnd ? (-(1 << (cmpt->prec - 1))) : (0);
+		mx = cmpt->sgnd ? ((1 << (cmpt->prec - 1)) - 1) : ((1 <<
+		  cmpt->prec) - 1);
+		jas_matrix_clip(tcomp->data, mn, mx);
+	}
+
+	/* XXX need to free tsfb struct */
+
+	/* Write the data for each component of the image. */
+	for (compno = 0, tcomp = tile->tcomps, cmpt = dec->cmpts; compno <
+	  dec->numcomps; ++compno, ++tcomp, ++cmpt) {
+		if (jas_image_writecmpt(dec->image, compno, tcomp->xstart -
+		  JPC_CEILDIV(dec->xstart, cmpt->hstep), tcomp->ystart -
+		  JPC_CEILDIV(dec->ystart, cmpt->vstep), jas_matrix_numcols(
+		  tcomp->data), jas_matrix_numrows(tcomp->data), tcomp->data)) {
+			fprintf(stderr, "write component failed\n");
+			return -4;
+		}
+	}
+
+	return 0;
+}
+
+static int jpc_dec_process_eoc(jpc_dec_t *dec, jpc_ms_t *ms)
+{
+	int tileno;
+	jpc_dec_tile_t *tile;
+	for (tileno = 0, tile = dec->tiles; tileno < dec->numtiles; ++tileno,
+	  ++tile) {
+		if (tile->state == JPC_TILE_ACTIVE) {
+			if (jpc_dec_tiledecode(dec, tile)) {
+				return -1;
+			}
+		}
+		jpc_dec_tilefini(dec, tile);
+	}
+
+	/* We are done processing the code stream. */
+	dec->state = JPC_MT;
+
+	return 1;
+}
+
+static int jpc_dec_process_siz(jpc_dec_t *dec, jpc_ms_t *ms)
+{
+	jpc_siz_t *siz = &ms->parms.siz;
+	uint_fast16_t compno;
+	uint_fast32_t tileno;
+	jpc_dec_tile_t *tile;
+	jpc_dec_tcomp_t *tcomp;
+	uint_fast32_t htileno;
+	uint_fast32_t vtileno;
+	jpc_dec_cmpt_t *cmpt;
+
+	dec->xstart = siz->xoff;
+	dec->ystart = siz->yoff;
+	dec->xend = siz->width;
+	dec->yend = siz->height;
+	dec->tilewidth = siz->tilewidth;
+	dec->tileheight = siz->tileheight;
+	dec->tilexoff = siz->tilexoff;
+	dec->tileyoff = siz->tileyoff;
+	dec->numcomps = siz->numcomps;
+	if (!(dec->cp = jpc_dec_cp_create(dec->numcomps))) {
+		return -1;
+	}
+
+	if (!(dec->cmpts = jas_malloc(dec->numcomps * sizeof(jpc_dec_cmpt_t)))) {
+		return -1;
+	}
+
+	for (compno = 0, cmpt = dec->cmpts; compno < dec->numcomps; ++compno,
+	  ++cmpt) {
+		cmpt->prec = siz->comps[compno].prec;
+		cmpt->sgnd = siz->comps[compno].sgnd;
+		cmpt->hstep = siz->comps[compno].hsamp;
+		cmpt->vstep = siz->comps[compno].vsamp;
+		cmpt->width = JPC_CEILDIV(dec->xend, cmpt->hstep) -
+		  JPC_CEILDIV(dec->xstart, cmpt->hstep);
+		cmpt->height = JPC_CEILDIV(dec->yend, cmpt->vstep) -
+		  JPC_CEILDIV(dec->ystart, cmpt->vstep);
+		cmpt->hsubstep = 0;
+		cmpt->vsubstep = 0;
+	}
+
+	dec->image = 0;
+
+	dec->numhtiles = JPC_CEILDIV(dec->xend - dec->tilexoff, dec->tilewidth);
+	dec->numvtiles = JPC_CEILDIV(dec->yend - dec->tileyoff, dec->tileheight);
+	dec->numtiles = dec->numhtiles * dec->numvtiles;
+	if (!(dec->tiles = jas_malloc(dec->numtiles * sizeof(jpc_dec_tile_t)))) {
+		return -1;
+	}
+
+	for (tileno = 0, tile = dec->tiles; tileno < dec->numtiles; ++tileno,
+	  ++tile) {
+		htileno = tileno % dec->numhtiles;
+		vtileno = tileno / dec->numhtiles;
+		tile->realmode = 0;
+		tile->state = JPC_TILE_INIT;
+		tile->xstart = JAS_MAX(dec->tilexoff + htileno * dec->tilewidth,
+		  dec->xstart);
+		tile->ystart = JAS_MAX(dec->tileyoff + vtileno * dec->tileheight,
+		  dec->ystart);
+		tile->xend = JAS_MIN(dec->tilexoff + (htileno + 1) *
+		  dec->tilewidth, dec->xend);
+		tile->yend = JAS_MIN(dec->tileyoff + (vtileno + 1) *
+		  dec->tileheight, dec->yend);
+		tile->numparts = 0;
+		tile->partno = 0;
+		tile->pkthdrstream = 0;
+		tile->pkthdrstreampos = 0;
+		tile->pptstab = 0;
+		tile->cp = 0;
+		if (!(tile->tcomps = jas_malloc(dec->numcomps *
+		  sizeof(jpc_dec_tcomp_t)))) {
+			return -1;
+		}
+		for (compno = 0, cmpt = dec->cmpts, tcomp = tile->tcomps;
+		  compno < dec->numcomps; ++compno, ++cmpt, ++tcomp) {
+			tcomp->rlvls = 0;
+			tcomp->data = 0;
+			tcomp->xstart = JPC_CEILDIV(tile->xstart, cmpt->hstep);
+			tcomp->ystart = JPC_CEILDIV(tile->ystart, cmpt->vstep);
+			tcomp->xend = JPC_CEILDIV(tile->xend, cmpt->hstep);
+			tcomp->yend = JPC_CEILDIV(tile->yend, cmpt->vstep);
+			tcomp->tsfb = 0;
+		}
+	}
+
+	dec->pkthdrstreams = 0;
+
+	/* We should expect to encounter other main header marker segments
+	  or an SOT marker segment next. */
+	dec->state = JPC_MH;
+
+	return 0;
+}
+
+static int jpc_dec_process_cod(jpc_dec_t *dec, jpc_ms_t *ms)
+{
+	jpc_cod_t *cod = &ms->parms.cod;
+	jpc_dec_tile_t *tile;
+
+	switch (dec->state) {
+	case JPC_MH:
+		jpc_dec_cp_setfromcod(dec->cp, cod);
+		break;
+	case JPC_TPH:
+		if (!(tile = dec->curtile)) {
+			return -1;
+		}
+		if (tile->partno != 0) {
+			return -1;
+		}
+		jpc_dec_cp_setfromcod(tile->cp, cod);
+		break;
+	}
+	return 0;
+}
+
+static int jpc_dec_process_coc(jpc_dec_t *dec, jpc_ms_t *ms)
+{
+	jpc_coc_t *coc = &ms->parms.coc;
+	jpc_dec_tile_t *tile;
+
+	if (coc->compno > dec->numcomps) {
+		fprintf(stderr,
+		  "invalid component number in COC marker segment\n");
+		return -1;
+	}
+	switch (dec->state) {
+	case JPC_MH:
+		jpc_dec_cp_setfromcoc(dec->cp, coc);
+		break;
+	case JPC_TPH:
+		if (!(tile = dec->curtile)) {
+			return -1;
+		}
+		if (tile->partno > 0) {
+			return -1;
+		}
+		jpc_dec_cp_setfromcoc(tile->cp, coc);
+		break;
+	}
+	return 0;
+}
+
+static int jpc_dec_process_rgn(jpc_dec_t *dec, jpc_ms_t *ms)
+{
+	jpc_rgn_t *rgn = &ms->parms.rgn;
+	jpc_dec_tile_t *tile;
+
+	if (rgn->compno > dec->numcomps) {
+		fprintf(stderr,
+		  "invalid component number in RGN marker segment\n");
+		return -1;
+	}
+	switch (dec->state) {
+	case JPC_MH:
+		jpc_dec_cp_setfromrgn(dec->cp, rgn);
+		break;
+	case JPC_TPH:
+		if (!(tile = dec->curtile)) {
+			return -1;
+		}
+		if (tile->partno > 0) {
+			return -1;
+		}
+		jpc_dec_cp_setfromrgn(tile->cp, rgn);
+		break;
+	}
+
+	return 0;
+}
+
+static int jpc_dec_process_qcd(jpc_dec_t *dec, jpc_ms_t *ms)
+{
+	jpc_qcd_t *qcd = &ms->parms.qcd;
+	jpc_dec_tile_t *tile;
+
+	switch (dec->state) {
+	case JPC_MH:
+		jpc_dec_cp_setfromqcd(dec->cp, qcd);
+		break;
+	case JPC_TPH:
+		if (!(tile = dec->curtile)) {
+			return -1;
+		}
+		if (tile->partno > 0) {
+			return -1;
+		}
+		jpc_dec_cp_setfromqcd(tile->cp, qcd);
+		break;
+	}
+	return 0;
+}
+
+static int jpc_dec_process_qcc(jpc_dec_t *dec, jpc_ms_t *ms)
+{
+	jpc_qcc_t *qcc = &ms->parms.qcc;
+	jpc_dec_tile_t *tile;
+
+	if (qcc->compno > dec->numcomps) {
+		fprintf(stderr,
+		  "invalid component number in QCC marker segment\n");
+		return -1;
+	}
+	switch (dec->state) {
+	case JPC_MH:
+		jpc_dec_cp_setfromqcc(dec->cp, qcc);
+		break;
+	case JPC_TPH:
+		if (!(tile = dec->curtile)) {
+			return -1;
+		}
+		if (tile->partno > 0) {
+			return -1;
+		}
+		jpc_dec_cp_setfromqcc(tile->cp, qcc);
+		break;
+	}
+	return 0;
+}
+
+static int jpc_dec_process_poc(jpc_dec_t *dec, jpc_ms_t *ms)
+{
+	jpc_poc_t *poc = &ms->parms.poc;
+	jpc_dec_tile_t *tile;
+	switch (dec->state) {
+	case JPC_MH:
+		if (jpc_dec_cp_setfrompoc(dec->cp, poc, 1)) {
+			return -1;
+		}
+		break;
+	case JPC_TPH:
+		if (!(tile = dec->curtile)) {
+			return -1;
+		}
+		if (!tile->partno) {
+			if (jpc_dec_cp_setfrompoc(tile->cp, poc, (!tile->partno))) {
+				return -1;
+			}
+		} else {
+			jpc_pi_addpchgfrompoc(tile->pi, poc);
+		}
+		break;
+	}
+	return 0;
+}
+
+static int jpc_dec_process_ppm(jpc_dec_t *dec, jpc_ms_t *ms)
+{
+	jpc_ppm_t *ppm = &ms->parms.ppm;
+	jpc_ppxstabent_t *ppmstabent;
+
+	if (!dec->ppmstab) {
+		if (!(dec->ppmstab = jpc_ppxstab_create())) {
+			return -1;
+		}
+	}
+
+	if (!(ppmstabent = jpc_ppxstabent_create())) {
+		return -1;
+	}
+	ppmstabent->ind = ppm->ind;
+	ppmstabent->data = ppm->data;
+	ppm->data = 0;
+	ppmstabent->len = ppm->len;
+	if (jpc_ppxstab_insert(dec->ppmstab, ppmstabent)) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_dec_process_ppt(jpc_dec_t *dec, jpc_ms_t *ms)
+{
+	jpc_ppt_t *ppt = &ms->parms.ppt;
+	jpc_dec_tile_t *tile;
+	jpc_ppxstabent_t *pptstabent;
+
+	tile = dec->curtile;
+	if (!tile->pptstab) {
+		if (!(tile->pptstab = jpc_ppxstab_create())) {
+			return -1;
+		}
+	}
+	if (!(pptstabent = jpc_ppxstabent_create())) {
+		return -1;
+	}
+	pptstabent->ind = ppt->ind;
+	pptstabent->data = ppt->data;
+	ppt->data = 0;
+	pptstabent->len = ppt->len;
+	if (jpc_ppxstab_insert(tile->pptstab, pptstabent)) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_dec_process_com(jpc_dec_t *dec, jpc_ms_t *ms)
+{
+	return 0;
+}
+
+static int jpc_dec_process_unk(jpc_dec_t *dec, jpc_ms_t *ms)
+{
+	fprintf(stderr, "warning: ignoring unknown marker segment\n");
+	jpc_ms_dump(ms, stderr);
+	return 0;
+}
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+static jpc_dec_cp_t *jpc_dec_cp_create(uint_fast16_t numcomps)
+{
+	jpc_dec_cp_t *cp;
+	jpc_dec_ccp_t *ccp;
+	int compno;
+
+	if (!(cp = jas_malloc(sizeof(jpc_dec_cp_t)))) {
+		return 0;
+	}
+	cp->flags = 0;
+	cp->numcomps = numcomps;
+	cp->prgord = 0;
+	cp->numlyrs = 0;
+	cp->mctid = 0;
+	cp->csty = 0;
+	if (!(cp->ccps = jas_malloc(cp->numcomps * sizeof(jpc_dec_ccp_t)))) {
+		return 0;
+	}
+	if (!(cp->pchglist = jpc_pchglist_create())) {
+		jas_free(cp->ccps);
+		return 0;
+	}
+	for (compno = 0, ccp = cp->ccps; compno < cp->numcomps;
+	  ++compno, ++ccp) {
+		ccp->flags = 0;
+		ccp->numrlvls = 0;
+		ccp->cblkwidthexpn = 0;
+		ccp->cblkheightexpn = 0;
+		ccp->qmfbid = 0;
+		ccp->numstepsizes = 0;
+		ccp->numguardbits = 0;
+		ccp->roishift = 0;
+		ccp->cblkctx = 0;
+	}
+	return cp;
+}
+
+static jpc_dec_cp_t *jpc_dec_cp_copy(jpc_dec_cp_t *cp)
+{
+	jpc_dec_cp_t *newcp;
+	jpc_dec_ccp_t *newccp;
+	jpc_dec_ccp_t *ccp;
+	int compno;
+
+	if (!(newcp = jpc_dec_cp_create(cp->numcomps))) {
+		return 0;
+	}
+	newcp->flags = cp->flags;
+	newcp->prgord = cp->prgord;
+	newcp->numlyrs = cp->numlyrs;
+	newcp->mctid = cp->mctid;
+	newcp->csty = cp->csty;
+	jpc_pchglist_destroy(newcp->pchglist);
+	newcp->pchglist = 0;
+	if (!(newcp->pchglist = jpc_pchglist_copy(cp->pchglist))) {
+		jas_free(newcp);
+		return 0;
+	}
+	for (compno = 0, newccp = newcp->ccps, ccp = cp->ccps;
+	  compno < cp->numcomps;
+	  ++compno, ++newccp, ++ccp) {
+		*newccp = *ccp;
+	}
+	return newcp;
+}
+
+static void jpc_dec_cp_resetflags(jpc_dec_cp_t *cp)
+{
+	int compno;
+	jpc_dec_ccp_t *ccp;
+	cp->flags &= (JPC_CSET | JPC_QSET);
+	for (compno = 0, ccp = cp->ccps; compno < cp->numcomps;
+	  ++compno, ++ccp) {
+		ccp->flags = 0;
+	}
+}
+
+static void jpc_dec_cp_destroy(jpc_dec_cp_t *cp)
+{
+	if (cp->ccps) {
+		jas_free(cp->ccps);
+	}
+	if (cp->pchglist) {
+		jpc_pchglist_destroy(cp->pchglist);
+	}
+	jas_free(cp);
+}
+
+static int jpc_dec_cp_isvalid(jpc_dec_cp_t *cp)
+{
+	uint_fast16_t compcnt;
+	jpc_dec_ccp_t *ccp;
+
+	if (!(cp->flags & JPC_CSET) || !(cp->flags & JPC_QSET)) {
+		return 0;
+	}
+	for (compcnt = cp->numcomps, ccp = cp->ccps; compcnt > 0; --compcnt,
+	  ++ccp) {
+		/* Is there enough step sizes for the number of bands? */
+		if ((ccp->qsty != JPC_QCX_SIQNT && ccp->numstepsizes < 3 *
+		  ccp->numrlvls - 2) || (ccp->qsty == JPC_QCX_SIQNT &&
+		  ccp->numstepsizes != 1)) {
+			return 0;
+		}
+	}
+	return 1;
+}
+
+static void calcstepsizes(uint_fast16_t refstepsize, int numrlvls,
+  uint_fast16_t *stepsizes)
+{
+	int bandno;
+	int numbands;
+	uint_fast16_t expn;
+	uint_fast16_t mant;
+	expn = JPC_QCX_GETEXPN(refstepsize);
+	mant = JPC_QCX_GETMANT(refstepsize);
+	numbands = 3 * numrlvls - 2;
+	for (bandno = 0; bandno < numbands; ++bandno) {
+		stepsizes[bandno] = JPC_QCX_MANT(mant) | JPC_QCX_EXPN(expn +
+		  (numrlvls - 1) - (numrlvls - 1 - ((bandno > 0) ? ((bandno + 2) / 3) : (0))));
+	}
+}
+
+static int jpc_dec_cp_prepare(jpc_dec_cp_t *cp)
+{
+	jpc_dec_ccp_t *ccp;
+	int compno;
+	int i;
+	for (compno = 0, ccp = cp->ccps; compno < cp->numcomps;
+	  ++compno, ++ccp) {
+		if (!(ccp->csty & JPC_COX_PRT)) {
+			for (i = 0; i < JPC_MAXRLVLS; ++i) {
+				ccp->prcwidthexpns[i] = 15;
+				ccp->prcheightexpns[i] = 15;
+			}
+		}
+		if (ccp->qsty == JPC_QCX_SIQNT) {
+			calcstepsizes(ccp->stepsizes[0], ccp->numrlvls, ccp->stepsizes);
+		}
+	}
+	return 0;
+}
+
+static int jpc_dec_cp_setfromcod(jpc_dec_cp_t *cp, jpc_cod_t *cod)
+{
+	jpc_dec_ccp_t *ccp;
+	int compno;
+	cp->flags |= JPC_CSET;
+	cp->prgord = cod->prg;
+	if (cod->mctrans) {
+		cp->mctid = (cod->compparms.qmfbid == JPC_COX_INS) ? (JPC_MCT_ICT) : (JPC_MCT_RCT);
+	} else {
+		cp->mctid = JPC_MCT_NONE;
+	}
+	cp->numlyrs = cod->numlyrs;
+	cp->csty = cod->csty & (JPC_COD_SOP | JPC_COD_EPH);
+	for (compno = 0, ccp = cp->ccps; compno < cp->numcomps;
+	  ++compno, ++ccp) {
+		jpc_dec_cp_setfromcox(cp, ccp, &cod->compparms, 0);
+	}
+	cp->flags |= JPC_CSET;
+	return 0;
+}
+
+static int jpc_dec_cp_setfromcoc(jpc_dec_cp_t *cp, jpc_coc_t *coc)
+{
+	jpc_dec_cp_setfromcox(cp, &cp->ccps[coc->compno], &coc->compparms, JPC_COC);
+	return 0;
+}
+
+static int jpc_dec_cp_setfromcox(jpc_dec_cp_t *cp, jpc_dec_ccp_t *ccp,
+  jpc_coxcp_t *compparms, int flags)
+{
+	int rlvlno;
+	if ((flags & JPC_COC) || !(ccp->flags & JPC_COC)) {
+		ccp->numrlvls = compparms->numdlvls + 1;
+		ccp->cblkwidthexpn = JPC_COX_GETCBLKSIZEEXPN(
+		  compparms->cblkwidthval);
+		ccp->cblkheightexpn = JPC_COX_GETCBLKSIZEEXPN(
+		  compparms->cblkheightval);
+		ccp->qmfbid = compparms->qmfbid;
+		ccp->cblkctx = compparms->cblksty;
+		ccp->csty = compparms->csty & JPC_COX_PRT;
+		for (rlvlno = 0; rlvlno < compparms->numrlvls; ++rlvlno) {
+			ccp->prcwidthexpns[rlvlno] =
+			  compparms->rlvls[rlvlno].parwidthval;
+			ccp->prcheightexpns[rlvlno] =
+			  compparms->rlvls[rlvlno].parheightval;
+		}
+		ccp->flags |= flags | JPC_CSET;
+	}
+	return 0;
+}
+
+static int jpc_dec_cp_setfromqcd(jpc_dec_cp_t *cp, jpc_qcd_t *qcd)
+{
+	int compno;
+	jpc_dec_ccp_t *ccp;
+	for (compno = 0, ccp = cp->ccps; compno < cp->numcomps;
+	  ++compno, ++ccp) {
+		jpc_dec_cp_setfromqcx(cp, ccp, &qcd->compparms, 0);
+	}
+	cp->flags |= JPC_QSET;
+	return 0;
+}
+
+static int jpc_dec_cp_setfromqcc(jpc_dec_cp_t *cp, jpc_qcc_t *qcc)
+{
+	return jpc_dec_cp_setfromqcx(cp, &cp->ccps[qcc->compno], &qcc->compparms, JPC_QCC);
+}
+
+static int jpc_dec_cp_setfromqcx(jpc_dec_cp_t *cp, jpc_dec_ccp_t *ccp,
+  jpc_qcxcp_t *compparms, int flags)
+{
+	int bandno;
+	if ((flags & JPC_QCC) || !(ccp->flags & JPC_QCC)) {
+		ccp->flags |= flags | JPC_QSET;
+		for (bandno = 0; bandno < compparms->numstepsizes; ++bandno) {
+			ccp->stepsizes[bandno] = compparms->stepsizes[bandno];
+		}
+		ccp->numstepsizes = compparms->numstepsizes;
+		ccp->numguardbits = compparms->numguard;
+		ccp->qsty = compparms->qntsty;
+	}
+	return 0;
+}
+
+static int jpc_dec_cp_setfromrgn(jpc_dec_cp_t *cp, jpc_rgn_t *rgn)
+{
+	jpc_dec_ccp_t *ccp;
+	ccp = &cp->ccps[rgn->compno];
+	ccp->roishift = rgn->roishift;
+	return 0;
+}
+
+static int jpc_pi_addpchgfrompoc(jpc_pi_t *pi, jpc_poc_t *poc)
+{
+	int pchgno;
+	jpc_pchg_t *pchg;
+	for (pchgno = 0; pchgno < poc->numpchgs; ++pchgno) {
+		if (!(pchg = jpc_pchg_copy(&poc->pchgs[pchgno]))) {
+			return -1;
+		}
+		if (jpc_pchglist_insert(pi->pchglist, -1, pchg)) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static int jpc_dec_cp_setfrompoc(jpc_dec_cp_t *cp, jpc_poc_t *poc, int reset)
+{
+	int pchgno;
+	jpc_pchg_t *pchg;
+	if (reset) {
+		while (jpc_pchglist_numpchgs(cp->pchglist) > 0) {
+			pchg = jpc_pchglist_remove(cp->pchglist, 0);
+			jpc_pchg_destroy(pchg);
+		}
+	}
+	for (pchgno = 0; pchgno < poc->numpchgs; ++pchgno) {
+		if (!(pchg = jpc_pchg_copy(&poc->pchgs[pchgno]))) {
+			return -1;
+		}
+		if (jpc_pchglist_insert(cp->pchglist, -1, pchg)) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static jpc_fix_t jpc_calcabsstepsize(int stepsize, int numbits)
+{
+	jpc_fix_t absstepsize;
+	int n;
+
+	absstepsize = jpc_inttofix(1);
+	n = JPC_FIX_FRACBITS - 11;
+	absstepsize |= (n >= 0) ? (JPC_QCX_GETMANT(stepsize) << n) :
+	  (JPC_QCX_GETMANT(stepsize) >> (-n));
+	n = numbits - JPC_QCX_GETEXPN(stepsize);
+	absstepsize = (n >= 0) ? (absstepsize << n) : (absstepsize >> (-n));
+	return absstepsize;
+}
+
+static void jpc_dequantize(jas_matrix_t *x, jpc_fix_t absstepsize)
+{
+	int i;
+	int j;
+	int t;
+
+	assert(absstepsize >= 0);
+	if (absstepsize == jpc_inttofix(1)) {
+		return;
+	}
+
+	for (i = 0; i < jas_matrix_numrows(x); ++i) {
+		for (j = 0; j < jas_matrix_numcols(x); ++j) {
+			t = jas_matrix_get(x, i, j);
+			if (t) {
+				t = jpc_fix_mul(t, absstepsize);
+			} else {
+				t = 0;
+			}
+			jas_matrix_set(x, i, j, t);
+		}
+	}
+
+}
+
+static void jpc_undo_roi(jas_matrix_t *x, int roishift, int bgshift, int numbps)
+{
+	int i;
+	int j;
+	int thresh;
+	jpc_fix_t val;
+	jpc_fix_t mag;
+	bool warn;
+	uint_fast32_t mask;
+
+	if (roishift == 0 && bgshift == 0) {
+		return;
+	}
+	thresh = 1 << roishift;
+
+	warn = false;
+	for (i = 0; i < jas_matrix_numrows(x); ++i) {
+		for (j = 0; j < jas_matrix_numcols(x); ++j) {
+			val = jas_matrix_get(x, i, j);
+			mag = JAS_ABS(val);
+			if (mag >= thresh) {
+				/* We are dealing with ROI data. */
+				mag >>= roishift;
+				val = (val < 0) ? (-mag) : mag;
+				jas_matrix_set(x, i, j, val);
+			} else {
+				/* We are dealing with non-ROI (i.e., background) data. */
+				mag <<= bgshift;
+				mask = (1 << numbps) - 1;
+				/* Perform a basic sanity check on the sample value. */
+				/* Some implementations write garbage in the unused
+				  most-significant bit planes introduced by ROI shifting.
+				  Here we ensure that any such bits are masked off. */
+				if (mag & (~mask)) {
+					if (!warn) {
+						fprintf(stderr,
+						  "warning: possibly corrupt code stream\n");
+						warn = true;
+					}
+					mag &= mask;
+				}
+				val = (val < 0) ? (-mag) : mag;
+				jas_matrix_set(x, i, j, val);
+			}
+		}
+	}
+}
+
+static jpc_dec_t *jpc_dec_create(jpc_dec_importopts_t *impopts, jas_stream_t *in)
+{
+	jpc_dec_t *dec;
+
+	if (!(dec = jas_malloc(sizeof(jpc_dec_t)))) {
+		return 0;
+	}
+
+	dec->image = 0;
+	dec->xstart = 0;
+	dec->ystart = 0;
+	dec->xend = 0;
+	dec->yend = 0;
+	dec->tilewidth = 0;
+	dec->tileheight = 0;
+	dec->tilexoff = 0;
+	dec->tileyoff = 0;
+	dec->numhtiles = 0;
+	dec->numvtiles = 0;
+	dec->numtiles = 0;
+	dec->tiles = 0;
+	dec->curtile = 0;
+	dec->numcomps = 0;
+	dec->in = in;
+	dec->cp = 0;
+	dec->maxlyrs = impopts->maxlyrs;
+	dec->maxpkts = impopts->maxpkts;
+dec->numpkts = 0;
+	dec->ppmseqno = 0;
+	dec->state = 0;
+	dec->cmpts = 0;
+	dec->pkthdrstreams = 0;
+	dec->ppmstab = 0;
+	dec->curtileendoff = 0;
+
+	return dec;
+}
+
+static void jpc_dec_destroy(jpc_dec_t *dec)
+{
+	if (dec->cstate) {
+		jpc_cstate_destroy(dec->cstate);
+	}
+	if (dec->pkthdrstreams) {
+		jpc_streamlist_destroy(dec->pkthdrstreams);
+	}
+	if (dec->image) {
+		jas_image_destroy(dec->image);
+	}
+
+	if (dec->cp) {
+		jpc_dec_cp_destroy(dec->cp);
+	}
+
+	if (dec->cmpts) {
+		jas_free(dec->cmpts);
+	}
+
+	if (dec->tiles) {
+		jas_free(dec->tiles);
+	}
+
+	jas_free(dec);
+}
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+void jpc_seglist_insert(jpc_dec_seglist_t *list, jpc_dec_seg_t *ins, jpc_dec_seg_t *node)
+{
+	jpc_dec_seg_t *prev;
+	jpc_dec_seg_t *next;
+
+	prev = ins;
+	node->prev = prev;
+	next = prev ? (prev->next) : 0;
+	node->prev = prev;
+	node->next = next;
+	if (prev) {
+		prev->next = node;
+	} else {
+		list->head = node;
+	}
+	if (next) {
+		next->prev = node;
+	} else {
+		list->tail = node;
+	}
+}
+
+void jpc_seglist_remove(jpc_dec_seglist_t *list, jpc_dec_seg_t *seg)
+{
+	jpc_dec_seg_t *prev;
+	jpc_dec_seg_t *next;
+
+	prev = seg->prev;
+	next = seg->next;
+	if (prev) {
+		prev->next = next;
+	} else {
+		list->head = next;
+	}
+	if (next) {
+		next->prev = prev;
+	} else {
+		list->tail = prev;
+	}
+	seg->prev = 0;
+	seg->next = 0;
+}
+
+jpc_dec_seg_t *jpc_seg_alloc(void)
+{
+	jpc_dec_seg_t *seg;
+
+	if (!(seg = jas_malloc(sizeof(jpc_dec_seg_t)))) {
+		return 0;
+	}
+	seg->prev = 0;
+	seg->next = 0;
+	seg->passno = -1;
+	seg->numpasses = 0;
+	seg->maxpasses = 0;
+	seg->type = JPC_SEG_INVALID;
+	seg->stream = 0;
+	seg->cnt = 0;
+	seg->complete = 0;
+	seg->lyrno = -1;
+	return seg;
+}
+
+void jpc_seg_destroy(jpc_dec_seg_t *seg)
+{
+	if (seg->stream) {
+		jas_stream_close(seg->stream);
+	}
+	jas_free(seg);
+}
+
+static int jpc_dec_dump(jpc_dec_t *dec, FILE *out)
+{
+	jpc_dec_tile_t *tile;
+	int tileno;
+	jpc_dec_tcomp_t *tcomp;
+	uint_fast16_t compno;
+	jpc_dec_rlvl_t *rlvl;
+	int rlvlno;
+	jpc_dec_band_t *band;
+	int bandno;
+	jpc_dec_prc_t *prc;
+	int prcno;
+	jpc_dec_cblk_t *cblk;
+	int cblkno;
+
+	for (tileno = 0, tile = dec->tiles; tileno < dec->numtiles;
+	  ++tileno, ++tile) {
+		for (compno = 0, tcomp = tile->tcomps; compno < dec->numcomps;
+		  ++compno, ++tcomp) {
+			for (rlvlno = 0, rlvl = tcomp->rlvls; rlvlno <
+			  tcomp->numrlvls; ++rlvlno, ++rlvl) {
+fprintf(out, "RESOLUTION LEVEL %d\n", rlvlno);
+fprintf(out, "xs =%d, ys = %d, xe = %d, ye = %d, w = %d, h = %d\n",
+  rlvl->xstart, rlvl->ystart, rlvl->xend, rlvl->yend, rlvl->xend -
+  rlvl->xstart, rlvl->yend - rlvl->ystart);
+				for (bandno = 0, band = rlvl->bands;
+				  bandno < rlvl->numbands; ++bandno, ++band) {
+fprintf(out, "BAND %d\n", bandno);
+fprintf(out, "xs =%d, ys = %d, xe = %d, ye = %d, w = %d, h = %d\n",
+  jas_seq2d_xstart(band->data), jas_seq2d_ystart(band->data), jas_seq2d_xend(band->data),
+  jas_seq2d_yend(band->data), jas_seq2d_xend(band->data) - jas_seq2d_xstart(band->data),
+  jas_seq2d_yend(band->data) - jas_seq2d_ystart(band->data));
+					for (prcno = 0, prc = band->prcs;
+					  prcno < rlvl->numprcs; ++prcno,
+					  ++prc) {
+fprintf(out, "CODE BLOCK GROUP %d\n", prcno);
+fprintf(out, "xs =%d, ys = %d, xe = %d, ye = %d, w = %d, h = %d\n",
+  prc->xstart, prc->ystart, prc->xend, prc->yend, prc->xend -
+  prc->xstart, prc->yend - prc->ystart);
+						for (cblkno = 0, cblk =
+						  prc->cblks; cblkno <
+						  prc->numcblks; ++cblkno,
+						  ++cblk) {
+fprintf(out, "CODE BLOCK %d\n", cblkno);
+fprintf(out, "xs =%d, ys = %d, xe = %d, ye = %d, w = %d, h = %d\n",
+  jas_seq2d_xstart(cblk->data), jas_seq2d_ystart(cblk->data), jas_seq2d_xend(cblk->data),
+  jas_seq2d_yend(cblk->data), jas_seq2d_xend(cblk->data) - jas_seq2d_xstart(cblk->data),
+  jas_seq2d_yend(cblk->data) - jas_seq2d_ystart(cblk->data));
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return 0;
+}
+
+jpc_streamlist_t *jpc_streamlist_create()
+{
+	jpc_streamlist_t *streamlist;
+	int i;
+
+	if (!(streamlist = jas_malloc(sizeof(jpc_streamlist_t)))) {
+		return 0;
+	}
+	streamlist->numstreams = 0;
+	streamlist->maxstreams = 100;
+	if (!(streamlist->streams = jas_malloc(streamlist->maxstreams *
+	  sizeof(jas_stream_t *)))) {
+		jas_free(streamlist);
+		return 0;
+	}
+	for (i = 0; i < streamlist->maxstreams; ++i) {
+		streamlist->streams[i] = 0;
+	}
+	return streamlist;
+}
+
+int jpc_streamlist_insert(jpc_streamlist_t *streamlist, int streamno,
+  jas_stream_t *stream)
+{
+	jas_stream_t **newstreams;
+	int newmaxstreams;
+	int i;
+	/* Grow the array of streams if necessary. */
+	if (streamlist->numstreams >= streamlist->maxstreams) {
+		newmaxstreams = streamlist->maxstreams + 1024;
+		if (!(newstreams = jas_realloc(streamlist->streams,
+		  (newmaxstreams + 1024) * sizeof(jas_stream_t *)))) {
+			return -1;
+		}
+		for (i = streamlist->numstreams; i < streamlist->maxstreams; ++i) {
+			streamlist->streams[i] = 0;
+		}
+		streamlist->maxstreams = newmaxstreams;
+		streamlist->streams = newstreams;
+	}
+	if (streamno != streamlist->numstreams) {
+		/* Can only handle insertion at start of list. */
+		return -1;
+	}
+	streamlist->streams[streamno] = stream;
+	++streamlist->numstreams;
+	return 0;
+}
+
+jas_stream_t *jpc_streamlist_remove(jpc_streamlist_t *streamlist, int streamno)
+{
+	jas_stream_t *stream;
+	int i;
+	if (streamno >= streamlist->numstreams) {
+		abort();
+	}
+	stream = streamlist->streams[streamno];
+	for (i = streamno + 1; i < streamlist->numstreams; ++i) {
+		streamlist->streams[i - 1] = streamlist->streams[i];
+	}
+	--streamlist->numstreams;
+	return stream;
+}
+
+void jpc_streamlist_destroy(jpc_streamlist_t *streamlist)
+{
+	int streamno;
+	if (streamlist->streams) {
+		for (streamno = 0; streamno < streamlist->numstreams;
+		  ++streamno) {
+			jas_stream_close(streamlist->streams[streamno]);
+		}
+		jas_free(streamlist->streams);
+	}
+	jas_free(streamlist);
+}
+
+jas_stream_t *jpc_streamlist_get(jpc_streamlist_t *streamlist, int streamno)
+{
+	assert(streamno < streamlist->numstreams);
+	return streamlist->streams[streamno];
+}
+
+int jpc_streamlist_numstreams(jpc_streamlist_t *streamlist)
+{
+	return streamlist->numstreams;
+}
+
+jpc_ppxstab_t *jpc_ppxstab_create()
+{
+	jpc_ppxstab_t *tab;
+
+	if (!(tab = jas_malloc(sizeof(jpc_ppxstab_t)))) {
+		return 0;
+	}
+	tab->numents = 0;
+	tab->maxents = 0;
+	tab->ents = 0;
+	return tab;
+}
+
+void jpc_ppxstab_destroy(jpc_ppxstab_t *tab)
+{
+	int i;
+	for (i = 0; i < tab->numents; ++i) {
+		jpc_ppxstabent_destroy(tab->ents[i]);
+	}
+	if (tab->ents) {
+		jas_free(tab->ents);
+	}
+	jas_free(tab);
+}
+
+int jpc_ppxstab_grow(jpc_ppxstab_t *tab, int maxents)
+{
+	jpc_ppxstabent_t **newents;
+	if (tab->maxents < maxents) {
+		newents = (tab->ents) ? jas_realloc(tab->ents, maxents *
+		  sizeof(jpc_ppxstabent_t *)) : jas_malloc(maxents * sizeof(jpc_ppxstabent_t *));
+		if (!newents) {
+			return -1;
+		}
+		tab->ents = newents;
+		tab->maxents = maxents;
+	}
+	return 0;
+}
+
+int jpc_ppxstab_insert(jpc_ppxstab_t *tab, jpc_ppxstabent_t *ent)
+{
+	int inspt;
+	int i;
+
+	for (i = 0; i < tab->numents; ++i) {
+		if (tab->ents[i]->ind > ent->ind) {
+			break;
+		}
+	}
+	inspt = i;
+
+	if (tab->numents >= tab->maxents) {
+		if (jpc_ppxstab_grow(tab, tab->maxents + 128)) {
+			return -1;
+		}
+	}
+
+	for (i = tab->numents; i > inspt; --i) {
+		tab->ents[i] = tab->ents[i - 1];
+	}
+	tab->ents[i] = ent;
+	++tab->numents;
+
+	return 0;
+}
+
+jpc_streamlist_t *jpc_ppmstabtostreams(jpc_ppxstab_t *tab)
+{
+	jpc_streamlist_t *streams;
+	unsigned char *dataptr;
+	uint_fast32_t datacnt;
+	uint_fast32_t tpcnt;
+	jpc_ppxstabent_t *ent;
+	int entno;
+	jas_stream_t *stream;
+	int n;
+
+	if (!(streams = jpc_streamlist_create())) {
+		goto error;
+	}
+
+	if (!tab->numents) {
+		return streams;
+	}
+
+	entno = 0;
+	ent = tab->ents[entno];
+	dataptr = ent->data;
+	datacnt = ent->len;
+	for (;;) {
+
+		/* Get the length of the packet header data for the current
+		  tile-part. */
+		if (datacnt < 4) {
+			goto error;
+		}
+		if (!(stream = jas_stream_memopen(0, 0))) {
+			goto error;
+		}
+		if (jpc_streamlist_insert(streams, jpc_streamlist_numstreams(streams),
+		  stream)) {
+			goto error;
+		}
+		tpcnt = (dataptr[0] << 24) | (dataptr[1] << 16) | (dataptr[2] << 8)
+		  | dataptr[3];
+		datacnt -= 4;
+		dataptr += 4;
+
+		/* Get the packet header data for the current tile-part. */
+		while (tpcnt) {
+			if (!datacnt) {
+				if (++entno >= tab->numents) {
+					goto error;
+				}
+				ent = tab->ents[entno];
+				dataptr = ent->data;
+				datacnt = ent->len;
+			}
+			n = JAS_MIN(tpcnt, datacnt);
+			if (jas_stream_write(stream, dataptr, n) != n) {
+				goto error;
+			}
+			tpcnt -= n;
+			dataptr += n;
+			datacnt -= n;
+		}
+		jas_stream_rewind(stream);
+		if (!datacnt) {
+			if (++entno >= tab->numents) {
+				break;
+			}
+			ent = tab->ents[entno];
+			dataptr = ent->data;
+			datacnt = ent->len;
+		}
+	}
+
+	return streams;
+
+error:
+	jpc_streamlist_destroy(streams);
+	return 0;
+}
+
+int jpc_pptstabwrite(jas_stream_t *out, jpc_ppxstab_t *tab)
+{
+	int i;
+	jpc_ppxstabent_t *ent;
+	for (i = 0; i < tab->numents; ++i) {
+		ent = tab->ents[i];
+		if (jas_stream_write(out, ent->data, ent->len) != ent->len) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+jpc_ppxstabent_t *jpc_ppxstabent_create()
+{
+	jpc_ppxstabent_t *ent;
+	if (!(ent = jas_malloc(sizeof(jpc_ppxstabent_t)))) {
+		return 0;
+	}
+	ent->data = 0;
+	ent->len = 0;
+	ent->ind = 0;
+	return ent;
+}
+
+void jpc_ppxstabent_destroy(jpc_ppxstabent_t *ent)
+{
+	if (ent->data) {
+		jas_free(ent->data);
+	}
+	jas_free(ent);
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_dec.h b/converter/other/jpeg2000/libjasper/jpc/jpc_dec.h
new file mode 100644
index 00000000..5231048d
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_dec.h
@@ -0,0 +1,745 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * JPEG-2000 Decoder
+ *
+ * $Id$
+ */
+
+#ifndef JPC_DEC_H
+#define JPC_DEC_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_stream.h"
+
+#include "jpc_tsfb.h"
+#include "jpc_bs.h"
+#include "jpc_tagtree.h"
+#include "jpc_cs.h"
+#include "jpc_cod.h"
+#include "jpc_mqdec.h"
+#include "jpc_t2cod.h"
+
+/******************************************************************************\
+* Below are some ugly warts necessary to support packed packet headers.
+\******************************************************************************/
+
+/* PPM/PPT marker segment table entry. */
+
+typedef struct {
+
+	/* The index for this entry. */
+	uint_fast16_t ind;
+
+	/* The data length. */
+	uint_fast32_t len;
+
+	/* The data. */
+	unsigned char *data;
+
+} jpc_ppxstabent_t;
+
+/* PPM/PPT marker segment table. */
+
+typedef struct {
+
+	/* The number of entries. */
+	int numents;
+
+	/* The maximum number of entries (i.e., the allocated size of the array
+	  below). */
+	int maxents;
+
+	/* The table entries. */
+	jpc_ppxstabent_t **ents;
+
+} jpc_ppxstab_t;
+
+/* Stream list class. */
+
+typedef struct {
+
+	/* The number of streams in this list. */
+	int numstreams;
+
+	/* The maximum number of streams that can be accomodated without
+	  growing the streams array. */
+	int maxstreams;
+
+	/* The streams. */
+	jas_stream_t **streams;
+
+} jpc_streamlist_t;
+
+/******************************************************************************\
+* Coding parameters class.
+\******************************************************************************/
+
+/* Per-component coding parameters. */
+
+typedef struct {
+
+	/* How were various coding parameters set? */
+	int flags;
+
+	/* Per-component coding style parameters (e.g., explicit precinct sizes) */
+	uint_fast8_t csty;
+
+	/* The number of resolution levels. */
+	uint_fast8_t numrlvls;
+
+	/* The code block width exponent. */
+	uint_fast8_t cblkwidthexpn;
+
+	/* The code block height exponent. */
+	uint_fast8_t cblkheightexpn;
+
+	/* The QMFB ID. */
+	uint_fast8_t qmfbid;
+
+	/* The quantization style. */
+	uint_fast8_t qsty;
+
+	/* The number of quantizer step sizes. */
+	uint_fast16_t numstepsizes;
+
+	/* The step sizes. */
+	uint_fast16_t stepsizes[3 * JPC_MAXRLVLS + 1];
+
+	/* The number of guard bits. */
+	uint_fast8_t numguardbits;
+
+	/* The ROI shift value. */
+	uint_fast8_t roishift;
+
+	/* The code block parameters. */
+	uint_fast8_t cblkctx;
+
+	/* The precinct width exponents. */
+	uint_fast8_t prcwidthexpns[JPC_MAXRLVLS];
+
+	/* The precinct height exponents. */
+	uint_fast8_t prcheightexpns[JPC_MAXRLVLS];
+
+} jpc_dec_ccp_t;
+
+/* Coding paramters. */
+
+typedef struct {
+
+	/* How were these coding parameters set? */
+	int flags;
+
+	/* Progression change list. */
+	jpc_pchglist_t *pchglist;
+
+	/* Progression order. */
+	uint_fast8_t prgord;
+
+	/* The number of layers. */
+	uint_fast16_t numlyrs;
+
+	/* The MCT ID. */
+	uint_fast8_t mctid;
+
+	/* The coding style parameters (e.g., SOP, EPH). */
+	uint_fast8_t csty;
+
+	/* The number of components. */
+	uint_fast16_t numcomps;
+
+	/* The per-component coding parameters. */
+	jpc_dec_ccp_t *ccps;
+
+} jpc_dec_cp_t;
+
+/******************************************************************************\
+* Decoder class.
+\******************************************************************************/
+
+/* Decoder per-segment state information. */
+
+typedef struct jpc_dec_seg_s {
+
+	/* The next segment in the list. */
+	struct jpc_dec_seg_s *next;
+
+	/* The previous segment in the list. */
+	struct jpc_dec_seg_s *prev;
+
+	/* The starting pass number for this segment. */
+	int passno;
+
+	/* The number of passes in this segment. */
+	int numpasses;
+
+	/* The maximum number of passes in this segment. */
+	int maxpasses;
+
+	/* The type of data in this segment (i.e., MQ or raw). */
+	int type;
+
+	/* A stream containing the data for this segment. */
+	jas_stream_t *stream;
+
+	/* The number of bytes destined for this segment from the packet
+	  currently being decoded. */
+	int cnt;
+
+	/* A flag indicating if this segment has been terminated. */
+	int complete;
+
+	/* The layer number to which this segment belongs. */
+	/* If the segment spans multiple layers, then the largest layer number
+	  spanned by the segment is used. */
+	int lyrno;
+
+} jpc_dec_seg_t;
+
+/* Decoder segment list. */
+
+typedef struct {
+
+	/* The first entry in the list. */
+	jpc_dec_seg_t *head;
+
+	/* The last entry in the list. */
+	jpc_dec_seg_t *tail;
+
+} jpc_dec_seglist_t;
+
+/* Decoder per-code-block state information. */
+
+typedef struct {
+
+	/* The number of passes. */
+	int numpasses;
+
+	/* A list of segments that still need to be decoded. */
+	jpc_dec_seglist_t segs;
+
+	/* The first incomplete/partial segment. */
+	jpc_dec_seg_t *curseg;
+
+	/* The number of leading insignificant bit planes for this code block. */
+	int numimsbs;
+
+	/* The number of bits used to encode pass data lengths. */
+	int numlenbits;
+
+	/* The first pass number containing data for this code block. */
+	int firstpassno;
+
+	/* The MQ decoder. */
+	jpc_mqdec_t *mqdec;
+
+	/* The raw bit stream decoder. */
+	jpc_bitstream_t *nulldec;
+
+	/* The per-sample state information for this code block. */
+	jas_matrix_t *flags;
+
+	/* The sample data associated with this code block. */
+	jas_matrix_t *data;
+
+} jpc_dec_cblk_t;
+
+/* Decoder per-code-block-group state information. */
+
+typedef struct {
+
+	/* The x-coordinate of the top-left corner of the precinct. */
+	uint_fast32_t xstart;
+
+	/* The y-coordinate of the top-left corner of the precinct. */
+	uint_fast32_t ystart;
+
+	/* The x-coordinate of the bottom-right corner of the precinct
+	  (plus one). */
+	uint_fast32_t xend;
+
+	/* The y-coordinate of the bottom-right corner of the precinct
+	  (plus one). */
+	uint_fast32_t yend;
+
+	/* The number of code blocks spanning this precinct in the horizontal
+	  direction. */
+	int numhcblks;
+
+	/* The number of code blocks spanning this precinct in the vertical
+	  direction. */
+	int numvcblks;
+
+	/* The total number of code blocks in this precinct. */
+	int numcblks;
+
+	/* The per code block information. */
+	jpc_dec_cblk_t *cblks;
+
+	/* The inclusion tag tree. */
+	jpc_tagtree_t *incltagtree;
+
+	/* The insignificant MSBs tag tree. */
+	jpc_tagtree_t *numimsbstagtree;
+
+} jpc_dec_prc_t;
+
+/* Decoder per-band state information. */
+
+typedef struct {
+
+	/* The per-code-block-group state information. */
+	jpc_dec_prc_t *prcs;
+
+	/* The sample data associated with this band. */
+	jas_matrix_t *data;
+
+	/* The orientation of this band (i.e., LL, LH, HL, or HH). */
+	int orient;
+
+	/* The encoded quantizer step size. */
+	int stepsize;
+
+	/* The absolute quantizer step size. */
+	jpc_fix_t absstepsize;
+
+	/* The number of bit planes for this band. */
+	int numbps;
+
+	/* The analysis gain associated with this band. */
+	int analgain;
+
+	/* The ROI shift value for this band. */
+	int roishift;
+
+} jpc_dec_band_t;
+
+/* Decoder per-resolution-level state information. */
+
+typedef struct {
+
+	/* The number of bands associated with this resolution level. */
+	int numbands;
+
+	/* The per-band information. */
+	jpc_dec_band_t *bands;
+
+	/* The x-coordinate of the top-left corner of the tile-component
+	  at this resolution. */
+	uint_fast32_t xstart;
+
+	/* The y-coordinate of the top-left corner of the tile-component
+	  at this resolution. */
+	uint_fast32_t ystart;
+
+	/* The x-coordinate of the bottom-right corner of the tile-component
+	  at this resolution (plus one). */
+	uint_fast32_t xend;
+
+	/* The y-coordinate of the bottom-right corner of the tile-component
+	  at this resolution (plus one). */
+	uint_fast32_t yend;
+
+	/* The exponent value for the nominal precinct width measured
+	  relative to the associated LL band. */
+	int prcwidthexpn;
+
+	/* The exponent value for the nominal precinct height measured
+	  relative to the associated LL band. */
+	int prcheightexpn;
+
+	/* The number of precincts in the horizontal direction. */
+	int numhprcs;
+
+	/* The number of precincts in the vertical direction. */
+	int numvprcs;
+
+	/* The total number of precincts. */
+	int numprcs;
+
+	/* The exponent value for the nominal code block group width.
+	  This quantity is associated with the next lower resolution level
+	  (assuming that there is one). */
+	int cbgwidthexpn;
+
+	/* The exponent value for the nominal code block group height
+	  This quantity is associated with the next lower resolution level
+	  (assuming that there is one). */
+	int cbgheightexpn;
+
+	/* The exponent value for the code block width. */
+	uint_fast16_t cblkwidthexpn;
+
+	/* The exponent value for the code block height. */
+	uint_fast16_t cblkheightexpn;
+
+} jpc_dec_rlvl_t;
+
+/* Decoder per-tile-component state information. */
+
+typedef struct {
+
+	/* The x-coordinate of the top-left corner of the tile-component
+	  in the coordinate system of the tile-component. */
+	uint_fast32_t xstart;
+
+	/* The y-coordinate of the top-left corner of the tile-component
+	  in the coordinate system of the tile-component. */
+	uint_fast32_t ystart;
+
+	/* The x-coordinate of the bottom-right corner of the tile-component
+	  in the coordinate system of the tile-component (plus one). */
+	uint_fast32_t xend;
+
+	/* The y-coordinate of the bottom-right corner of the tile-component
+	  in the coordinate system of the tile-component (plus one). */
+	uint_fast32_t yend;
+
+	/* The component data for the current tile. */
+	jas_matrix_t *data;
+
+	/* The number of resolution levels. */
+	uint_fast16_t numrlvls;
+
+	/* The per resolution level information. */
+	jpc_dec_rlvl_t *rlvls;
+
+	/* The TSFB. */
+	jpc_tsfb_t *tsfb;
+
+} jpc_dec_tcomp_t;
+
+/*
+ * Tile states.
+ */
+
+#define	JPC_TILE_INIT	0
+#define	JPC_TILE_ACTIVE	1
+#define	JPC_TILE_ACTIVELAST	2
+#define	JPC_TILE_DONE	3
+
+/* Decoder per-tile state information. */
+
+typedef struct {
+
+	/* The processing state for this tile. */
+	int state;
+
+	/* The x-coordinate of the top-left corner of the tile on the reference
+	  grid. */
+	uint_fast32_t xstart;
+
+	/* The y-coordinate of the top-left corner of the tile on the reference
+	  grid. */
+	uint_fast32_t ystart;
+
+	/* The x-coordinate of the bottom-right corner of the tile on the
+	  reference grid (plus one). */
+	uint_fast32_t xend;
+
+	/* The y-coordinate of the bottom-right corner of the tile on the
+	  reference grid (plus one). */
+	uint_fast32_t yend;
+
+	/* The packed packet header data for this tile. */
+	jpc_ppxstab_t *pptstab;
+
+	/* A stream containing the packed packet header data for this tile. */
+	jas_stream_t *pkthdrstream;
+
+	/* The current position within the packed packet header stream. */
+	long pkthdrstreampos;
+
+	/* The coding parameters for this tile. */
+	jpc_dec_cp_t *cp;
+
+	/* The per tile-component information. */
+	jpc_dec_tcomp_t *tcomps;
+
+	/* The next expected tile-part number. */
+	int partno;
+
+	/* The number of tile-parts. */
+	int numparts;
+
+	/* The coding mode. */
+	int realmode;
+
+	/* The packet iterator for this tile. */
+	jpc_pi_t *pi;
+
+} jpc_dec_tile_t;
+
+/* Decoder per-component state information. */
+
+typedef struct {
+
+	/* The horizontal sampling period. */
+	uint_fast32_t hstep;
+
+	/* The vertical sampling period. */
+	uint_fast32_t vstep;
+
+	/* The number of samples in the horizontal direction. */
+	uint_fast32_t width;
+
+	/* The number of samples in the vertical direction. */
+	uint_fast32_t height;
+
+	/* The precision of the sample data. */
+	uint_fast16_t prec;
+
+	/* The signedness of the sample data. */
+	bool sgnd;
+
+	/* The sample alignment horizontal offset. */
+	uint_fast32_t hsubstep;
+	
+	/* The sample alignment vertical offset. */
+	uint_fast32_t vsubstep;
+
+} jpc_dec_cmpt_t;
+
+/* Decoder state information. */
+
+typedef struct {
+
+	/* The decoded image. */
+	jas_image_t *image;
+
+	/* The x-coordinate of the top-left corner of the image area on
+	  the reference grid. */
+	uint_fast32_t xstart;
+
+	/* The y-coordinate of the top-left corner of the image area on
+	  the reference grid. */
+	uint_fast32_t ystart;
+
+	/* The x-coordinate of the bottom-right corner of the image area on
+	  the reference grid (plus one). */
+	uint_fast32_t xend;
+
+	/* The y-coordinate of the bottom-right corner of the image area on
+	  the reference grid (plus one). */
+	uint_fast32_t yend;
+
+	/* The nominal tile width in units of the image reference grid. */
+	uint_fast32_t tilewidth;
+
+	/* The nominal tile height in units of the image reference grid. */
+	uint_fast32_t tileheight;
+
+	/* The horizontal offset from the origin of the reference grid to the
+	  left side of the first tile. */
+	uint_fast32_t tilexoff;
+
+	/* The vertical offset from the origin of the reference grid to the
+	  top side of the first tile. */
+	uint_fast32_t tileyoff;
+
+	/* The number of tiles spanning the image area in the vertical
+	  direction. */
+	int numhtiles;
+
+	/* The number of tiles spanning the image area in the horizontal
+	  direction. */
+	int numvtiles;
+
+	/* The total number of tiles. */
+	int numtiles;
+
+	/* The per-tile information. */
+	jpc_dec_tile_t *tiles;
+
+	/* The tile currently being processed. */
+	jpc_dec_tile_t *curtile;
+
+	/* The number of components. */
+	int numcomps;
+
+	/* The stream containing the input JPEG-2000 code stream data. */
+	jas_stream_t *in;
+
+	/* The default coding parameters for all tiles. */
+	jpc_dec_cp_t *cp;
+
+	/* The maximum number of layers that may be decoded. */
+	int maxlyrs;
+
+	/* The maximum number of packets that may be decoded. */
+	int maxpkts;
+
+	/* The number of packets decoded so far in the processing of the entire
+	  code stream. */
+	int numpkts;
+
+	/* The next expected PPM marker segment sequence number. */
+	int ppmseqno;
+
+	/* The current state for code stream processing. */
+	int state;
+
+	/* The per-component information. */
+	jpc_dec_cmpt_t *cmpts;
+
+	/* The information from PPM marker segments. */
+	jpc_ppxstab_t *ppmstab;
+
+	/* A list of streams containing packet header data from PPM marker
+	  segments. */
+	jpc_streamlist_t *pkthdrstreams;
+
+	/* The expected ending offset for a tile-part. */
+	long curtileendoff;
+
+	/* This is required by the tier-2 decoder. */
+	jpc_cstate_t *cstate;
+
+} jpc_dec_t;
+
+/* Decoder options. */
+
+typedef struct {
+
+	/* The debug level for the decoder. */
+	int debug;
+
+	/* The maximum number of layers to decode. */
+	int maxlyrs;
+
+	/* The maximum number of packets to decode. */
+	int maxpkts;
+
+} jpc_dec_importopts_t;
+
+/******************************************************************************\
+* Functions.
+\******************************************************************************/
+
+/* Create a decoder segment object. */
+jpc_dec_seg_t *jpc_seg_alloc(void);
+
+/* Destroy a decoder segment object. */
+void jpc_seg_destroy(jpc_dec_seg_t *seg);
+
+/* Remove a segment from a segment list. */
+void jpc_seglist_remove(jpc_dec_seglist_t *list, jpc_dec_seg_t *node);
+
+/* Insert a segment into a segment list. */
+void jpc_seglist_insert(jpc_dec_seglist_t *list, jpc_dec_seg_t *ins,
+  jpc_dec_seg_t *node);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_enc.c b/converter/other/jpeg2000/libjasper/jpc/jpc_enc.c
new file mode 100644
index 00000000..3284dfeb
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_enc.c
@@ -0,0 +1,2624 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <math.h>
+#include <float.h>
+
+#include "jasper/jas_string.h"
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_image.h"
+#include "jasper/jas_fix.h"
+#include "jasper/jas_tvp.h"
+#include "jasper/jas_version.h"
+#include "jasper/jas_math.h"
+#include "jasper/jas_debug.h"
+
+#include "jpc_flt.h"
+#include "jpc_fix.h"
+#include "jpc_tagtree.h"
+#include "jpc_enc.h"
+#include "jpc_cs.h"
+#include "jpc_mct.h"
+#include "jpc_tsfb.h"
+#include "jpc_qmfb.h"
+#include "jpc_t1enc.h"
+#include "jpc_t2enc.h"
+#include "jpc_cod.h"
+#include "jpc_math.h"
+#include "jpc_util.h"
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+#define JPC_POW2(n) \
+	(1 << (n))
+
+#define JPC_FLOORTOMULTPOW2(x, n) \
+  (((n) > 0) ? ((x) & (~((1 << n) - 1))) : (x))
+/* Round to the nearest multiple of the specified power of two in the
+  direction of negative infinity. */
+
+#define	JPC_CEILTOMULTPOW2(x, n) \
+  (((n) > 0) ? JPC_FLOORTOMULTPOW2(((x) + (1 << (n)) - 1), n) : (x))
+/* Round to the nearest multiple of the specified power of two in the
+  direction of positive infinity. */
+
+#define	JPC_POW2(n)	\
+  (1 << (n))
+
+jpc_enc_tile_t *jpc_enc_tile_create(jpc_enc_cp_t *cp, jas_image_t *image, int tileno);
+void jpc_enc_tile_destroy(jpc_enc_tile_t *tile);
+
+static jpc_enc_tcmpt_t *tcmpt_create(jpc_enc_tcmpt_t *tcmpt, jpc_enc_cp_t *cp,
+  jas_image_t *image, jpc_enc_tile_t *tile);
+static void tcmpt_destroy(jpc_enc_tcmpt_t *tcmpt);
+static jpc_enc_rlvl_t *rlvl_create(jpc_enc_rlvl_t *rlvl, jpc_enc_cp_t *cp,
+  jpc_enc_tcmpt_t *tcmpt, jpc_tsfb_band_t *bandinfos);
+static void rlvl_destroy(jpc_enc_rlvl_t *rlvl);
+static jpc_enc_band_t *band_create(jpc_enc_band_t *band, jpc_enc_cp_t *cp,
+  jpc_enc_rlvl_t *rlvl, jpc_tsfb_band_t *bandinfos);
+static void band_destroy(jpc_enc_band_t *bands);
+static jpc_enc_prc_t *prc_create(jpc_enc_prc_t *prc, jpc_enc_cp_t *cp,
+  jpc_enc_band_t *band);
+static void prc_destroy(jpc_enc_prc_t *prcs);
+static jpc_enc_cblk_t *cblk_create(jpc_enc_cblk_t *cblk, jpc_enc_cp_t *cp,
+  jpc_enc_prc_t *prc);
+static void cblk_destroy(jpc_enc_cblk_t *cblks);
+int ratestrtosize(const char *s, uint_fast32_t rawsize, uint_fast32_t *size);
+static void pass_destroy(jpc_enc_pass_t *pass);
+void jpc_enc_dump(jpc_enc_t *enc);
+
+/******************************************************************************\
+* Local prototypes.
+\******************************************************************************/
+
+void quantize(jas_matrix_t *data, jpc_fix_t stepsize);
+static int jpc_enc_encodemainhdr(jpc_enc_t *enc);
+static int jpc_enc_encodemainbody(jpc_enc_t *enc);
+int jpc_enc_encodetiledata(jpc_enc_t *enc);
+jpc_enc_t *jpc_enc_create(jpc_enc_cp_t *cp, jas_stream_t *out, jas_image_t *image);
+void jpc_enc_destroy(jpc_enc_t *enc);
+static int jpc_enc_encodemainhdr(jpc_enc_t *enc);
+static int jpc_enc_encodemainbody(jpc_enc_t *enc);
+int jpc_enc_encodetiledata(jpc_enc_t *enc);
+int rateallocate(jpc_enc_t *enc, int numlyrs, uint_fast32_t *cumlens);
+int setins(int numvalues, jpc_flt_t *values, jpc_flt_t value);
+static jpc_enc_cp_t *cp_create(char *optstr, jas_image_t *image);
+void jpc_enc_cp_destroy(jpc_enc_cp_t *cp);
+
+static uint_fast32_t jpc_abstorelstepsize(jpc_fix_t absdelta, int scaleexpn)
+{
+	int p;
+	uint_fast32_t mant;
+	uint_fast32_t expn;
+	int n;
+
+	if (absdelta < 0) {
+		abort();
+	}
+
+	p = jpc_firstone(absdelta) - JPC_FIX_FRACBITS;
+	n = 11 - jpc_firstone(absdelta);
+	mant = ((n < 0) ? (absdelta >> (-n)) : (absdelta << n)) & 0x7ff;
+	expn = scaleexpn - p;
+	if (scaleexpn < p) {
+		abort();
+	}
+	return JPC_QCX_EXPN(expn) | JPC_QCX_MANT(mant);
+}
+
+typedef enum {
+	OPT_DEBUG,
+	OPT_IMGAREAOFFX,
+	OPT_IMGAREAOFFY,
+	OPT_TILEGRDOFFX,
+	OPT_TILEGRDOFFY,
+	OPT_TILEWIDTH,
+	OPT_TILEHEIGHT,
+	OPT_PRCWIDTH,
+	OPT_PRCHEIGHT,
+	OPT_CBLKWIDTH,
+	OPT_CBLKHEIGHT,
+	OPT_MODE,
+	OPT_PRG,
+	OPT_NOMCT,
+	OPT_MAXRLVLS,
+	OPT_SOP,
+	OPT_EPH,
+	OPT_LAZY,
+	OPT_TERMALL,
+	OPT_SEGSYM,
+	OPT_VCAUSAL,
+	OPT_RESET,
+	OPT_PTERM,
+	OPT_NUMGBITS,
+	OPT_RATE,
+	OPT_ILYRRATES,
+	OPT_JP2OVERHEAD
+} optid_t;
+
+jas_taginfo_t encopts[] = {
+	{OPT_DEBUG, "debug"},
+	{OPT_IMGAREAOFFX, "imgareatlx"},
+	{OPT_IMGAREAOFFY, "imgareatly"},
+	{OPT_TILEGRDOFFX, "tilegrdtlx"},
+	{OPT_TILEGRDOFFY, "tilegrdtly"},
+	{OPT_TILEWIDTH, "tilewidth"},
+	{OPT_TILEHEIGHT, "tileheight"},
+	{OPT_PRCWIDTH, "prcwidth"},
+	{OPT_PRCHEIGHT, "prcheight"},
+	{OPT_CBLKWIDTH, "cblkwidth"},
+	{OPT_CBLKHEIGHT, "cblkheight"},
+	{OPT_MODE, "mode"},
+	{OPT_PRG, "prg"},
+	{OPT_NOMCT, "nomct"},
+	{OPT_MAXRLVLS, "numrlvls"},
+	{OPT_SOP, "sop"},
+	{OPT_EPH, "eph"},
+	{OPT_LAZY, "lazy"},
+	{OPT_TERMALL, "termall"},
+	{OPT_SEGSYM, "segsym"},
+	{OPT_VCAUSAL, "vcausal"},
+	{OPT_PTERM, "pterm"},
+	{OPT_RESET, "resetprob"},
+	{OPT_NUMGBITS, "numgbits"},
+	{OPT_RATE, "rate"},
+	{OPT_ILYRRATES, "ilyrrates"},
+	{OPT_JP2OVERHEAD, "_jp2overhead"},
+	{-1, 0}
+};
+
+typedef enum {
+	PO_L = 0,
+	PO_R
+} poid_t;
+
+
+jas_taginfo_t prgordtab[] = {
+	{JPC_COD_LRCPPRG, "lrcp"},
+	{JPC_COD_RLCPPRG, "rlcp"},
+	{JPC_COD_RPCLPRG, "rpcl"},
+	{JPC_COD_PCRLPRG, "pcrl"},
+	{JPC_COD_CPRLPRG, "cprl"},
+	{-1, 0}
+};
+
+typedef enum {
+	MODE_INT,
+	MODE_REAL
+} modeid_t;
+
+jas_taginfo_t modetab[] = {
+	{MODE_INT, "int"},
+	{MODE_REAL, "real"},
+	{-1, 0}
+};
+
+/******************************************************************************\
+* The main encoder entry point.
+\******************************************************************************/
+
+int jpc_encode(jas_image_t *image, jas_stream_t *out, char *optstr)
+{
+	jpc_enc_t *enc;
+	jpc_enc_cp_t *cp;
+
+	enc = 0;
+	cp = 0;
+
+	jpc_initluts();
+
+	if (!(cp = cp_create(optstr, image))) {
+		fprintf(stderr, "invalid JP encoder options\n");
+		goto error;
+	}
+
+	if (!(enc = jpc_enc_create(cp, out, image))) {
+		goto error;
+	}
+	cp = 0;
+
+	/* Encode the main header. */
+	if (jpc_enc_encodemainhdr(enc)) {
+		goto error;
+	}
+
+	/* Encode the main body.  This constitutes most of the encoding work. */
+	if (jpc_enc_encodemainbody(enc)) {
+		goto error;
+	}
+
+	/* Write EOC marker segment. */
+	if (!(enc->mrk = jpc_ms_create(JPC_MS_EOC))) {
+		goto error;
+	}
+	if (jpc_putms(enc->out, enc->cstate, enc->mrk)) {
+		fprintf(stderr, "cannot write EOI marker\n");
+		goto error;
+	}
+	jpc_ms_destroy(enc->mrk);
+	enc->mrk = 0;
+
+	if (jas_stream_flush(enc->out)) {
+		goto error;
+	}
+
+	jpc_enc_destroy(enc);
+
+	return 0;
+
+error:
+	if (cp) {
+		jpc_enc_cp_destroy(cp);
+	}
+	if (enc) {
+		jpc_enc_destroy(enc);
+	}
+	return -1;
+}
+
+/******************************************************************************\
+* Option parsing code.
+\******************************************************************************/
+
+static jpc_enc_cp_t *cp_create(char *optstr, jas_image_t *image)
+{
+	jpc_enc_cp_t *cp;
+	jas_tvparser_t *tvp;
+	int ret;
+	int numilyrrates;
+	double *ilyrrates;
+	int i;
+	int tagid;
+	jpc_enc_tcp_t *tcp;
+	jpc_enc_tccp_t *tccp;
+	jpc_enc_ccp_t *ccp;
+	uint_fast16_t cmptno;
+	uint_fast16_t rlvlno;
+	uint_fast16_t prcwidthexpn;
+	uint_fast16_t prcheightexpn;
+	bool enablemct;
+	uint_fast32_t jp2overhead;
+	uint_fast16_t lyrno;
+	uint_fast32_t hsteplcm;
+	uint_fast32_t vsteplcm;
+	bool mctvalid;
+
+	tvp = 0;
+	cp = 0;
+	ilyrrates = 0;
+	numilyrrates = 0;
+
+	if (!(cp = jas_malloc(sizeof(jpc_enc_cp_t)))) {
+		goto error;
+	}
+
+	prcwidthexpn = 15;
+	prcheightexpn = 15;
+	enablemct = true;
+	jp2overhead = 0;
+
+	cp->ccps = 0;
+	cp->debug = 0;
+	cp->imgareatlx = UINT_FAST32_MAX;
+	cp->imgareatly = UINT_FAST32_MAX;
+	cp->refgrdwidth = 0;
+	cp->refgrdheight = 0;
+	cp->tilegrdoffx = UINT_FAST32_MAX;
+	cp->tilegrdoffy = UINT_FAST32_MAX;
+	cp->tilewidth = 0;
+	cp->tileheight = 0;
+	cp->numcmpts = jas_image_numcmpts(image);
+
+	hsteplcm = 1;
+	vsteplcm = 1;
+	for (cmptno = 0; cmptno < jas_image_numcmpts(image); ++cmptno) {
+		if (jas_image_cmptbrx(image, cmptno) + jas_image_cmpthstep(image, cmptno) <=
+		  jas_image_brx(image) || jas_image_cmptbry(image, cmptno) +
+		  jas_image_cmptvstep(image, cmptno) <= jas_image_bry(image)) {
+			fprintf(stderr, "unsupported image type\n");
+			goto error;
+		}
+		/* Note: We ought to be calculating the LCMs here.  Fix some day. */
+		hsteplcm *= jas_image_cmpthstep(image, cmptno);
+		vsteplcm *= jas_image_cmptvstep(image, cmptno);
+	}
+
+	if (!(cp->ccps = jas_malloc(cp->numcmpts * sizeof(jpc_enc_ccp_t)))) {
+		goto error;
+	}
+	for (cmptno = 0, ccp = cp->ccps; cmptno < cp->numcmpts; ++cmptno,
+	  ++ccp) {
+		ccp->sampgrdstepx = jas_image_cmpthstep(image, cmptno);
+		ccp->sampgrdstepy = jas_image_cmptvstep(image, cmptno);
+		/* XXX - this isn't quite correct for more general image */
+		ccp->sampgrdsubstepx = 0;
+		ccp->sampgrdsubstepx = 0;
+		ccp->prec = jas_image_cmptprec(image, cmptno);
+		ccp->sgnd = jas_image_cmptsgnd(image, cmptno);
+		ccp->numstepsizes = 0;
+		memset(ccp->stepsizes, 0, sizeof(ccp->stepsizes));
+	}
+
+	cp->rawsize = jas_image_rawsize(image);
+	cp->totalsize = UINT_FAST32_MAX;
+
+	tcp = &cp->tcp;
+	tcp->csty = 0;
+	tcp->intmode = true;
+	tcp->prg = JPC_COD_LRCPPRG;
+	tcp->numlyrs = 1;
+	tcp->ilyrrates = 0;
+
+	tccp = &cp->tccp;
+	tccp->csty = 0;
+	tccp->maxrlvls = 6;
+	tccp->cblkwidthexpn = 6;
+	tccp->cblkheightexpn = 6;
+	tccp->cblksty = 0;
+	tccp->numgbits = 2;
+
+	if (!(tvp = jas_tvparser_create(optstr ? optstr : ""))) {
+		goto error;
+	}
+
+	while (!(ret = jas_tvparser_next(tvp))) {
+		switch (jas_taginfo_nonull(jas_taginfos_lookup(encopts,
+		  jas_tvparser_gettag(tvp)))->id) {
+		case OPT_DEBUG:
+			cp->debug = atoi(jas_tvparser_getval(tvp));
+			break;
+		case OPT_IMGAREAOFFX:
+			cp->imgareatlx = atoi(jas_tvparser_getval(tvp));
+			break;
+		case OPT_IMGAREAOFFY:
+			cp->imgareatly = atoi(jas_tvparser_getval(tvp));
+			break;
+		case OPT_TILEGRDOFFX:
+			cp->tilegrdoffx = atoi(jas_tvparser_getval(tvp));
+			break;
+		case OPT_TILEGRDOFFY:
+			cp->tilegrdoffy = atoi(jas_tvparser_getval(tvp));
+			break;
+		case OPT_TILEWIDTH:
+			cp->tilewidth = atoi(jas_tvparser_getval(tvp));
+			break;
+		case OPT_TILEHEIGHT:
+			cp->tileheight = atoi(jas_tvparser_getval(tvp));
+			break;
+		case OPT_PRCWIDTH:
+			prcwidthexpn = jpc_floorlog2(atoi(jas_tvparser_getval(tvp)));
+			break;
+		case OPT_PRCHEIGHT:
+			prcheightexpn = jpc_floorlog2(atoi(jas_tvparser_getval(tvp)));
+			break;
+		case OPT_CBLKWIDTH:
+			tccp->cblkwidthexpn =
+			  jpc_floorlog2(atoi(jas_tvparser_getval(tvp)));
+			break;
+		case OPT_CBLKHEIGHT:
+			tccp->cblkheightexpn =
+			  jpc_floorlog2(atoi(jas_tvparser_getval(tvp)));
+			break;
+		case OPT_MODE:
+			if ((tagid = jas_taginfo_nonull(jas_taginfos_lookup(modetab,
+			  jas_tvparser_getval(tvp)))->id) < 0) {
+				fprintf(stderr,
+				  "ignoring invalid mode %s\n",
+				  jas_tvparser_getval(tvp));
+			} else {
+				tcp->intmode = (tagid == MODE_INT);
+			}
+			break;
+		case OPT_PRG:
+			if ((tagid = jas_taginfo_nonull(jas_taginfos_lookup(prgordtab,
+			  jas_tvparser_getval(tvp)))->id) < 0) {
+				fprintf(stderr,
+				  "ignoring invalid progression order %s\n",
+				  jas_tvparser_getval(tvp));
+			} else {
+				tcp->prg = tagid;
+			}
+			break;
+		case OPT_NOMCT:
+			enablemct = false;
+			break;
+		case OPT_MAXRLVLS:
+			tccp->maxrlvls = atoi(jas_tvparser_getval(tvp));
+			break;
+		case OPT_SOP:
+			cp->tcp.csty |= JPC_COD_SOP;
+			break;
+		case OPT_EPH:
+			cp->tcp.csty |= JPC_COD_EPH;
+			break;
+		case OPT_LAZY:
+			tccp->cblksty |= JPC_COX_LAZY;
+			break;
+		case OPT_TERMALL:
+			tccp->cblksty |= JPC_COX_TERMALL;
+			break;
+		case OPT_SEGSYM:
+			tccp->cblksty |= JPC_COX_SEGSYM;
+			break;
+		case OPT_VCAUSAL:
+			tccp->cblksty |= JPC_COX_VSC;
+			break;
+		case OPT_RESET:
+			tccp->cblksty |= JPC_COX_RESET;
+			break;
+		case OPT_PTERM:
+			tccp->cblksty |= JPC_COX_PTERM;
+			break;
+		case OPT_NUMGBITS:
+			cp->tccp.numgbits = atoi(jas_tvparser_getval(tvp));
+			break;
+		case OPT_RATE:
+			if (ratestrtosize(jas_tvparser_getval(tvp), cp->rawsize,
+			  &cp->totalsize)) {
+				fprintf(stderr,
+				  "ignoring bad rate specifier %s\n",
+				  jas_tvparser_getval(tvp));
+			}
+			break;
+		case OPT_ILYRRATES:
+			if (jpc_atoaf(jas_tvparser_getval(tvp), &numilyrrates,
+			  &ilyrrates)) {
+				fprintf(stderr,
+				  "warning: invalid intermediate layer rates specifier ignored (%s)\n",
+				  jas_tvparser_getval(tvp));
+			}
+			break;
+
+		case OPT_JP2OVERHEAD:
+			jp2overhead = atoi(jas_tvparser_getval(tvp));
+			break;
+		default:
+			fprintf(stderr, "warning: ignoring invalid option %s\n",
+			 jas_tvparser_gettag(tvp));
+			break;
+		}
+	}
+
+	jas_tvparser_destroy(tvp);
+	tvp = 0;
+
+	if (cp->totalsize != UINT_FAST32_MAX) {
+		cp->totalsize = (cp->totalsize > jp2overhead) ?
+		  (cp->totalsize - jp2overhead) : 0;
+	}
+
+	if (cp->imgareatlx == UINT_FAST32_MAX) {
+		cp->imgareatlx = 0;
+	} else {
+		if (hsteplcm != 1) {
+			fprintf(stderr, "warning: overriding imgareatlx value\n");
+		}
+		cp->imgareatlx *= hsteplcm;
+	}
+	if (cp->imgareatly == UINT_FAST32_MAX) {
+		cp->imgareatly = 0;
+	} else {
+		if (vsteplcm != 1) {
+			fprintf(stderr, "warning: overriding imgareatly value\n");
+		}
+		cp->imgareatly *= vsteplcm;
+	}
+	cp->refgrdwidth = cp->imgareatlx + jas_image_width(image);
+	cp->refgrdheight = cp->imgareatly + jas_image_height(image);
+	if (cp->tilegrdoffx == UINT_FAST32_MAX) {
+		cp->tilegrdoffx = cp->imgareatlx;
+	}
+	if (cp->tilegrdoffy == UINT_FAST32_MAX) {
+		cp->tilegrdoffy = cp->imgareatly;
+	}
+	if (!cp->tilewidth) {
+		cp->tilewidth = cp->refgrdwidth - cp->tilegrdoffx;
+	}
+	if (!cp->tileheight) {
+		cp->tileheight = cp->refgrdheight - cp->tilegrdoffy;
+	}
+
+	if (cp->numcmpts == 3) {
+		mctvalid = true;
+		for (cmptno = 0; cmptno < jas_image_numcmpts(image); ++cmptno) {
+			if (jas_image_cmptprec(image, cmptno) != jas_image_cmptprec(image, 0) ||
+			  jas_image_cmptsgnd(image, cmptno) != jas_image_cmptsgnd(image, 0) ||
+			  jas_image_cmptwidth(image, cmptno) != jas_image_cmptwidth(image, 0) ||
+			  jas_image_cmptheight(image, cmptno) != jas_image_cmptheight(image, 0)) {
+				mctvalid = false;
+			}
+		}
+	} else {
+		mctvalid = false;
+	}
+	if (mctvalid && enablemct && jas_image_colorspace(image) != JAS_IMAGE_CS_RGB) {
+		fprintf(stderr, "warning: color model apparently not RGB\n");
+	}
+	if (mctvalid && enablemct && jas_image_colorspace(image) == JAS_IMAGE_CS_RGB) {
+		tcp->mctid = (tcp->intmode) ? (JPC_MCT_RCT) : (JPC_MCT_ICT);
+	} else {
+		tcp->mctid = JPC_MCT_NONE;
+	}
+	tccp->qmfbid = (tcp->intmode) ? (JPC_COX_RFT) : (JPC_COX_INS);
+
+	for (rlvlno = 0; rlvlno < tccp->maxrlvls; ++rlvlno) {
+		tccp->prcwidthexpns[rlvlno] = prcwidthexpn;
+		tccp->prcheightexpns[rlvlno] = prcheightexpn;
+	}
+	if (prcwidthexpn != 15 || prcheightexpn != 15) {
+		tccp->csty |= JPC_COX_PRT;
+	}
+
+	/* Ensure that the tile width and height is valid. */
+	if (!cp->tilewidth) {
+		fprintf(stderr, "invalid tile width %lu\n", (unsigned long)
+		  cp->tilewidth);
+		goto error;
+	}
+	if (!cp->tileheight) {
+		fprintf(stderr, "invalid tile height %lu\n", (unsigned long)
+		  cp->tileheight);
+		goto error;
+	}
+
+	/* Ensure that the tile grid offset is valid. */
+	if (cp->tilegrdoffx > cp->imgareatlx ||
+	  cp->tilegrdoffy > cp->imgareatly ||
+	  cp->tilegrdoffx + cp->tilewidth < cp->imgareatlx ||
+	  cp->tilegrdoffy + cp->tileheight < cp->imgareatly) {
+		fprintf(stderr, "invalid tile grid offset (%lu, %lu)\n",
+		  (unsigned long) cp->tilegrdoffx, (unsigned long)
+		  cp->tilegrdoffy);
+		goto error;
+	}
+
+	cp->numhtiles = JPC_CEILDIV(cp->refgrdwidth - cp->tilegrdoffx,
+	  cp->tilewidth);
+	cp->numvtiles = JPC_CEILDIV(cp->refgrdheight - cp->tilegrdoffy,
+	  cp->tileheight);
+	cp->numtiles = cp->numhtiles * cp->numvtiles;
+
+	if (ilyrrates && numilyrrates > 0) {
+		tcp->numlyrs = numilyrrates + 1;
+		if (!(tcp->ilyrrates = jas_malloc((tcp->numlyrs - 1) *
+		  sizeof(jpc_fix_t)))) {
+			goto error;
+		}
+		for (i = 0; i < tcp->numlyrs - 1; ++i) {
+			tcp->ilyrrates[i] = jpc_dbltofix(ilyrrates[i]);
+		}
+	}
+
+	/* Ensure that the integer mode is used in the case of lossless
+	  coding. */
+	if (cp->totalsize == UINT_FAST32_MAX && (!cp->tcp.intmode)) {
+		fprintf(stderr, "cannot use real mode for lossless coding\n");
+		goto error;
+	}
+
+	/* Ensure that the precinct width is valid. */
+	if (prcwidthexpn > 15) {
+		fprintf(stderr, "invalid precinct width\n");
+		goto error;
+	}
+
+	/* Ensure that the precinct height is valid. */
+	if (prcheightexpn > 15) {
+		fprintf(stderr, "invalid precinct height\n");
+		goto error;
+	}
+
+	/* Ensure that the code block width is valid. */
+	if (cp->tccp.cblkwidthexpn < 2 || cp->tccp.cblkwidthexpn > 12) {
+		fprintf(stderr, "invalid code block width %d\n",
+		  JPC_POW2(cp->tccp.cblkwidthexpn));
+		goto error;
+	}
+
+	/* Ensure that the code block height is valid. */
+	if (cp->tccp.cblkheightexpn < 2 || cp->tccp.cblkheightexpn > 12) {
+		fprintf(stderr, "invalid code block height %d\n",
+		  JPC_POW2(cp->tccp.cblkheightexpn));
+		goto error;
+	}
+
+	/* Ensure that the code block size is not too large. */
+	if (cp->tccp.cblkwidthexpn + cp->tccp.cblkheightexpn > 12) {
+		fprintf(stderr, "code block size too large\n");
+		goto error;
+	}
+
+	/* Ensure that the number of layers is valid. */
+	if (cp->tcp.numlyrs > 16384) {
+		fprintf(stderr, "too many layers\n");
+		goto error;
+	}
+
+	/* There must be at least one resolution level. */
+	if (cp->tccp.maxrlvls < 1) {
+		fprintf(stderr, "must be at least one resolution level\n");
+		goto error;
+	}
+
+	/* Ensure that the number of guard bits is valid. */
+	if (cp->tccp.numgbits > 8) {
+		fprintf(stderr, "invalid number of guard bits\n");
+		goto error;
+	}
+
+	/* Ensure that the rate is within the legal range. */
+	if (cp->totalsize != UINT_FAST32_MAX && cp->totalsize > cp->rawsize) {
+		fprintf(stderr, "warning: specified rate is unreasonably large (%lu > %lu)\n", (unsigned long) cp->totalsize, (unsigned long) cp->rawsize);
+	}
+
+	/* Ensure that the intermediate layer rates are valid. */
+	if (tcp->numlyrs > 1) {
+		/* The intermediate layers rates must increase monotonically. */
+		for (lyrno = 0; lyrno + 2 < tcp->numlyrs; ++lyrno) {
+			if (tcp->ilyrrates[lyrno] >= tcp->ilyrrates[lyrno + 1]) {
+				fprintf(stderr, "intermediate layer rates must increase monotonically\n");
+				goto error;
+			}
+		}
+		/* The intermediate layer rates must be less than the overall rate. */
+		if (cp->totalsize != UINT_FAST32_MAX) {
+			for (lyrno = 0; lyrno < tcp->numlyrs - 1; ++lyrno) {
+				if (jpc_fixtodbl(tcp->ilyrrates[lyrno]) > ((double) cp->totalsize)
+				  / cp->rawsize) {
+					fprintf(stderr, "warning: intermediate layer rates must be less than overall rate\n");
+					goto error;
+				}
+			}
+		}
+	}
+
+	if (ilyrrates) {
+		jas_free(ilyrrates);
+	}
+
+	return cp;
+
+error:
+
+	if (ilyrrates) {
+		jas_free(ilyrrates);
+	}
+	if (tvp) {
+		jas_tvparser_destroy(tvp);
+	}
+	if (cp) {
+		jpc_enc_cp_destroy(cp);
+	}
+	return 0;
+}
+
+void jpc_enc_cp_destroy(jpc_enc_cp_t *cp)
+{
+	if (cp->ccps) {
+		if (cp->tcp.ilyrrates) {
+			jas_free(cp->tcp.ilyrrates);
+		}
+		jas_free(cp->ccps);
+	}
+	jas_free(cp);
+}
+
+int ratestrtosize(const char *s, uint_fast32_t rawsize, uint_fast32_t *size)
+{
+	char *cp;
+	jpc_flt_t f;
+
+	/* Note: This function must not modify output size on failure. */
+	if ((cp = strchr(s, 'B'))) {
+		*size = atoi(s);
+	} else {
+		f = atof(s);
+		if (f < 0) {
+			*size = 0;
+		} else if (f > 1.0) {
+			*size = rawsize + 1;
+		} else {
+			*size = f * rawsize;
+		}
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* Encoder constructor and destructor.
+\******************************************************************************/
+
+jpc_enc_t *jpc_enc_create(jpc_enc_cp_t *cp, jas_stream_t *out, jas_image_t *image)
+{
+	jpc_enc_t *enc;
+
+	enc = 0;
+
+	if (!(enc = jas_malloc(sizeof(jpc_enc_t)))) {
+		goto error;
+	}
+
+	enc->image = image;
+	enc->out = out;
+	enc->cp = cp;
+	enc->cstate = 0;
+	enc->tmpstream = 0;
+	enc->mrk = 0;
+	enc->curtile = 0;
+
+	if (!(enc->cstate = jpc_cstate_create())) {
+		goto error;
+	}
+	enc->len = 0;
+	enc->mainbodysize = 0;
+
+	return enc;
+
+error:
+
+	if (enc) {
+		jpc_enc_destroy(enc);
+	}
+	return 0;
+}
+
+void jpc_enc_destroy(jpc_enc_t *enc)
+{
+	/* The image object (i.e., enc->image) and output stream object
+	(i.e., enc->out) are created outside of the encoder.
+	Therefore, they must not be destroyed here. */
+
+	if (enc->curtile) {
+		jpc_enc_tile_destroy(enc->curtile);
+	}
+	if (enc->cp) {
+		jpc_enc_cp_destroy(enc->cp);
+	}
+	if (enc->cstate) {
+		jpc_cstate_destroy(enc->cstate);
+	}
+	if (enc->tmpstream) {
+		jas_stream_close(enc->tmpstream);
+	}
+
+	jas_free(enc);
+}
+
+/******************************************************************************\
+* Code.
+\******************************************************************************/
+
+static int jpc_enc_encodemainhdr(jpc_enc_t *enc)
+{
+	jpc_siz_t *siz;
+	jpc_cod_t *cod;
+	jpc_qcd_t *qcd;
+	int i;
+long startoff;
+long mainhdrlen;
+	jpc_enc_cp_t *cp;
+	jpc_qcc_t *qcc;
+	jpc_enc_tccp_t *tccp;
+	uint_fast16_t cmptno;
+	jpc_tsfb_band_t bandinfos[JPC_MAXBANDS];
+	jpc_fix_t mctsynweight;
+	jpc_enc_tcp_t *tcp;
+	jpc_tsfb_t *tsfb;
+	jpc_tsfb_band_t *bandinfo;
+	uint_fast16_t numbands;
+	uint_fast16_t bandno;
+	uint_fast16_t rlvlno;
+	uint_fast16_t analgain;
+	jpc_fix_t absstepsize;
+	char buf[1024];
+	jpc_com_t *com;
+
+	cp = enc->cp;
+
+startoff = jas_stream_getrwcount(enc->out);
+
+	/* Write SOC marker segment. */
+	if (!(enc->mrk = jpc_ms_create(JPC_MS_SOC))) {
+		return -1;
+	}
+	if (jpc_putms(enc->out, enc->cstate, enc->mrk)) {
+		fprintf(stderr, "cannot write SOC marker\n");
+		return -1;
+	}
+	jpc_ms_destroy(enc->mrk);
+	enc->mrk = 0;
+
+	/* Write SIZ marker segment. */
+	if (!(enc->mrk = jpc_ms_create(JPC_MS_SIZ))) {
+		return -1;
+	}
+	siz = &enc->mrk->parms.siz;
+	siz->caps = 0;
+	siz->xoff = cp->imgareatlx;
+	siz->yoff = cp->imgareatly;
+	siz->width = cp->refgrdwidth;
+	siz->height = cp->refgrdheight;
+	siz->tilexoff = cp->tilegrdoffx;
+	siz->tileyoff = cp->tilegrdoffy;
+	siz->tilewidth = cp->tilewidth;
+	siz->tileheight = cp->tileheight;
+	siz->numcomps = cp->numcmpts;
+	siz->comps = jas_malloc(siz->numcomps * sizeof(jpc_sizcomp_t));
+	assert(siz->comps);
+	for (i = 0; i < cp->numcmpts; ++i) {
+		siz->comps[i].prec = cp->ccps[i].prec;
+		siz->comps[i].sgnd = cp->ccps[i].sgnd;
+		siz->comps[i].hsamp = cp->ccps[i].sampgrdstepx;
+		siz->comps[i].vsamp = cp->ccps[i].sampgrdstepy;
+	}
+	if (jpc_putms(enc->out, enc->cstate, enc->mrk)) {
+		fprintf(stderr, "cannot write SIZ marker\n");
+		return -1;
+	}
+	jpc_ms_destroy(enc->mrk);
+	enc->mrk = 0;
+
+	if (!(enc->mrk = jpc_ms_create(JPC_MS_COM))) {
+		return -1;
+	}
+	sprintf(buf, "Creator: JasPer Version %s", jas_getversion());
+	com = &enc->mrk->parms.com;
+	com->len = strlen(buf);
+	com->regid = JPC_COM_LATIN;
+	if (!(com->data = JAS_CAST(unsigned char *, jas_strdup(buf)))) {
+		abort();
+	}
+	if (jpc_putms(enc->out, enc->cstate, enc->mrk)) {
+		fprintf(stderr, "cannot write COM marker\n");
+		return -1;
+	}
+	jpc_ms_destroy(enc->mrk);
+	enc->mrk = 0;
+
+#if 0
+	if (!(enc->mrk = jpc_ms_create(JPC_MS_CRG))) {
+		return -1;
+	}
+	crg = &enc->mrk->parms.crg;
+	crg->comps = jas_malloc(crg->numcomps * sizeof(jpc_crgcomp_t));
+	if (jpc_putms(enc->out, enc->cstate, enc->mrk)) {
+		fprintf(stderr, "cannot write CRG marker\n");
+		return -1;
+	}
+	jpc_ms_destroy(enc->mrk);
+	enc->mrk = 0;
+#endif
+
+	tcp = &cp->tcp;
+	tccp = &cp->tccp;
+	for (cmptno = 0; cmptno < cp->numcmpts; ++cmptno) {
+		tsfb = jpc_cod_gettsfb(tccp->qmfbid, tccp->maxrlvls - 1);
+		jpc_tsfb_getbands(tsfb, 0, 0, 1 << tccp->maxrlvls, 1 << tccp->maxrlvls,
+		  bandinfos);
+		jpc_tsfb_destroy(tsfb);
+		mctsynweight = jpc_mct_getsynweight(tcp->mctid, cmptno);
+		numbands = 3 * tccp->maxrlvls - 2;
+		for (bandno = 0, bandinfo = bandinfos; bandno < numbands;
+		  ++bandno, ++bandinfo) {
+			rlvlno = (bandno) ? ((bandno - 1) / 3 + 1) : 0;
+			analgain = JPC_NOMINALGAIN(tccp->qmfbid, tccp->maxrlvls,
+			  rlvlno, bandinfo->orient);
+			if (!tcp->intmode) {
+				absstepsize = jpc_fix_div(jpc_inttofix(1 <<
+				  (analgain + 1)), bandinfo->synenergywt);
+			} else {
+				absstepsize = jpc_inttofix(1);
+			}	
+			cp->ccps[cmptno].stepsizes[bandno] =
+			  jpc_abstorelstepsize(absstepsize,
+			  cp->ccps[cmptno].prec + analgain);
+		}
+		cp->ccps[cmptno].numstepsizes = numbands;
+	}
+
+	if (!(enc->mrk = jpc_ms_create(JPC_MS_COD))) {
+		return -1;
+	}
+	cod = &enc->mrk->parms.cod;
+	cod->csty = cp->tccp.csty | cp->tcp.csty;
+	cod->compparms.csty = cp->tccp.csty | cp->tcp.csty;
+	cod->compparms.numdlvls = cp->tccp.maxrlvls - 1;
+	cod->compparms.numrlvls = cp->tccp.maxrlvls;
+	cod->prg = cp->tcp.prg;
+	cod->numlyrs = cp->tcp.numlyrs;
+	cod->compparms.cblkwidthval = JPC_COX_CBLKSIZEEXPN(cp->tccp.cblkwidthexpn);
+	cod->compparms.cblkheightval = JPC_COX_CBLKSIZEEXPN(cp->tccp.cblkheightexpn);
+	cod->compparms.cblksty = cp->tccp.cblksty;
+	cod->compparms.qmfbid = cp->tccp.qmfbid;
+	cod->mctrans = (cp->tcp.mctid != JPC_MCT_NONE);
+	if (tccp->csty & JPC_COX_PRT) {
+		for (rlvlno = 0; rlvlno < tccp->maxrlvls; ++rlvlno) {
+			cod->compparms.rlvls[rlvlno].parwidthval = tccp->prcwidthexpns[rlvlno];
+			cod->compparms.rlvls[rlvlno].parheightval = tccp->prcheightexpns[rlvlno];
+		}
+	}
+	if (jpc_putms(enc->out, enc->cstate, enc->mrk)) {
+		fprintf(stderr, "cannot write COD marker\n");
+		return -1;
+	}
+	jpc_ms_destroy(enc->mrk);
+	enc->mrk = 0;
+
+	if (!(enc->mrk = jpc_ms_create(JPC_MS_QCD))) {
+		return -1;
+	}
+	qcd = &enc->mrk->parms.qcd;
+	qcd->compparms.qntsty = (tccp->qmfbid == JPC_COX_INS) ?
+	  JPC_QCX_SEQNT : JPC_QCX_NOQNT;
+	qcd->compparms.numstepsizes = cp->ccps[0].numstepsizes;
+	qcd->compparms.numguard = cp->tccp.numgbits;
+	qcd->compparms.stepsizes = cp->ccps[0].stepsizes;
+	if (jpc_putms(enc->out, enc->cstate, enc->mrk)) {
+		return -1;
+	}
+	/* We do not want the step size array to be freed! */
+	qcd->compparms.stepsizes = 0;
+	jpc_ms_destroy(enc->mrk);
+	enc->mrk = 0;
+
+	tccp = &cp->tccp;
+	for (cmptno = 1; cmptno < cp->numcmpts; ++cmptno) {
+		if (!(enc->mrk = jpc_ms_create(JPC_MS_QCC))) {
+			return -1;
+		}
+		qcc = &enc->mrk->parms.qcc;
+		qcc->compno = cmptno;
+		qcc->compparms.qntsty = (tccp->qmfbid == JPC_COX_INS) ?
+		  JPC_QCX_SEQNT : JPC_QCX_NOQNT;
+		qcc->compparms.numstepsizes = cp->ccps[cmptno].numstepsizes;
+		qcc->compparms.numguard = cp->tccp.numgbits;
+		qcc->compparms.stepsizes = cp->ccps[cmptno].stepsizes;
+		if (jpc_putms(enc->out, enc->cstate, enc->mrk)) {
+			return -1;
+		}
+		/* We do not want the step size array to be freed! */
+		qcc->compparms.stepsizes = 0;
+		jpc_ms_destroy(enc->mrk);
+		enc->mrk = 0;
+	}
+
+#define MAINTLRLEN	2
+	mainhdrlen = jas_stream_getrwcount(enc->out) - startoff;
+	enc->len += mainhdrlen;
+	if (enc->cp->totalsize != UINT_FAST32_MAX) {
+		uint_fast32_t overhead;
+		overhead = mainhdrlen + MAINTLRLEN;
+		enc->mainbodysize = (enc->cp->totalsize >= overhead) ?
+		  (enc->cp->totalsize - overhead) : 0;
+	} else {
+		enc->mainbodysize = UINT_FAST32_MAX;
+	}
+
+	return 0;
+}
+
+static int jpc_enc_encodemainbody(jpc_enc_t *enc)
+{
+	int tileno;
+	int tilex;
+	int tiley;
+	int i;
+	jpc_sot_t *sot;
+	jpc_enc_tcmpt_t *comp;
+	jpc_enc_tcmpt_t *endcomps;
+	jpc_enc_band_t *band;
+	jpc_enc_band_t *endbands;
+	jpc_enc_rlvl_t *lvl;
+	int rlvlno;
+	jpc_qcc_t *qcc;
+	jpc_cod_t *cod;
+	int adjust;
+	int j;
+	int absbandno;
+	long numbytes;
+	long tilehdrlen;
+	long tilelen;
+	jpc_enc_tile_t *tile;
+	jpc_enc_cp_t *cp;
+	double rho;
+	uint_fast16_t lyrno;
+	uint_fast16_t cmptno;
+	int samestepsizes;
+	jpc_enc_ccp_t *ccps;
+	jpc_enc_tccp_t *tccp;
+int bandno;
+uint_fast32_t x;
+uint_fast32_t y;
+int mingbits;
+int actualnumbps;
+jpc_fix_t mxmag;
+jpc_fix_t mag;
+int numgbits;
+
+	cp = enc->cp;
+
+	/* Avoid compile warnings. */
+	numbytes = 0;
+
+	for (tileno = 0; tileno < cp->numtiles; ++tileno) {
+		tilex = tileno % cp->numhtiles;
+		tiley = tileno / cp->numhtiles;
+
+		if (!(enc->curtile = jpc_enc_tile_create(enc->cp, enc->image, tileno))) {
+			abort();
+		}
+
+		tile = enc->curtile;
+
+		if (jas_getdbglevel() >= 10) {
+			jpc_enc_dump(enc);
+		}
+
+		endcomps = &tile->tcmpts[tile->numtcmpts];
+		for (cmptno = 0, comp = tile->tcmpts; cmptno < tile->numtcmpts; ++cmptno, ++comp) {
+			if (!cp->ccps[cmptno].sgnd) {
+				adjust = 1 << (cp->ccps[cmptno].prec - 1);
+				for (i = 0; i < jas_matrix_numrows(comp->data); ++i) {
+					for (j = 0; j < jas_matrix_numcols(comp->data); ++j) {
+						*jas_matrix_getref(comp->data, i, j) -= adjust;
+					}
+				}
+			}
+		}
+
+		if (!tile->intmode) {
+				endcomps = &tile->tcmpts[tile->numtcmpts];
+				for (comp = tile->tcmpts; comp != endcomps; ++comp) {
+					jas_matrix_asl(comp->data, JPC_FIX_FRACBITS);
+				}
+		}
+
+		switch (tile->mctid) {
+		case JPC_MCT_RCT:
+assert(jas_image_numcmpts(enc->image) == 3);
+			jpc_rct(tile->tcmpts[0].data, tile->tcmpts[1].data,
+			  tile->tcmpts[2].data);
+			break;
+		case JPC_MCT_ICT:
+assert(jas_image_numcmpts(enc->image) == 3);
+			jpc_ict(tile->tcmpts[0].data, tile->tcmpts[1].data,
+			  tile->tcmpts[2].data);
+			break;
+		default:
+			break;
+		}
+
+		for (i = 0; i < jas_image_numcmpts(enc->image); ++i) {
+			comp = &tile->tcmpts[i];
+			jpc_tsfb_analyze(comp->tsfb, ((comp->qmfbid == JPC_COX_RFT) ? JPC_TSFB_RITIMODE : 0), comp->data);
+
+		}
+
+
+		endcomps = &tile->tcmpts[tile->numtcmpts];
+		for (cmptno = 0, comp = tile->tcmpts; comp != endcomps; ++cmptno, ++comp) {
+			mingbits = 0;
+			absbandno = 0;
+			/* All bands must have a corresponding quantizer step size,
+			  even if they contain no samples and are never coded. */
+			/* Some bands may not be hit by the loop below, so we must
+			  initialize all of the step sizes to a sane value. */
+			memset(comp->stepsizes, 0, sizeof(comp->stepsizes));
+			for (rlvlno = 0, lvl = comp->rlvls; rlvlno < comp->numrlvls; ++rlvlno, ++lvl) {
+				if (!lvl->bands) {
+					absbandno += rlvlno ? 3 : 1;
+					continue;
+				}
+				endbands = &lvl->bands[lvl->numbands];
+				for (band = lvl->bands; band != endbands; ++band) {
+					if (!band->data) {
+						++absbandno;
+						continue;
+					}
+					actualnumbps = 0;
+					mxmag = 0;
+					for (y = 0; y < jas_matrix_numrows(band->data); ++y) {
+						for (x = 0; x < jas_matrix_numcols(band->data); ++x) {
+							mag = abs(jas_matrix_get(band->data, y, x));
+							if (mag > mxmag) {
+								mxmag = mag;
+							}
+						}
+					}
+					if (tile->intmode) {
+						actualnumbps = jpc_firstone(mxmag) + 1;
+					} else {
+						actualnumbps = jpc_firstone(mxmag) + 1 - JPC_FIX_FRACBITS;
+					}
+					numgbits = actualnumbps - (cp->ccps[cmptno].prec - 1 +
+					  band->analgain);
+#if 0
+fprintf(stderr, "%d %d mag=%d actual=%d numgbits=%d\n", cp->ccps[cmptno].prec, band->analgain, mxmag, actualnumbps, numgbits);
+#endif
+					if (numgbits > mingbits) {
+						mingbits = numgbits;
+					}
+					if (!tile->intmode) {
+						band->absstepsize = jpc_fix_div(jpc_inttofix(1
+						  << (band->analgain + 1)),
+						  band->synweight);
+					} else {
+						band->absstepsize = jpc_inttofix(1);
+					}
+					band->stepsize = jpc_abstorelstepsize(
+					  band->absstepsize, cp->ccps[cmptno].prec +
+					  band->analgain);
+					band->numbps = cp->tccp.numgbits +
+					  JPC_QCX_GETEXPN(band->stepsize) - 1;
+
+					if ((!tile->intmode) && band->data) {
+						quantize(band->data, band->absstepsize);
+					}
+
+					comp->stepsizes[absbandno] = band->stepsize;
+					++absbandno;
+				}
+			}
+
+			assert(JPC_FIX_FRACBITS >= JPC_NUMEXTRABITS);
+			if (!tile->intmode) {
+				jas_matrix_divpow2(comp->data, JPC_FIX_FRACBITS - JPC_NUMEXTRABITS);
+			} else {
+				jas_matrix_asl(comp->data, JPC_NUMEXTRABITS);
+			}
+		}
+#if 0
+fprintf(stderr, "mingbits %d\n", mingbits);
+#endif
+
+		if (mingbits > cp->tccp.numgbits) {
+			fprintf(stderr, "error: too few guard bits (need at least %d)\n",
+			  mingbits);
+			return -1;
+		}
+
+		if (!(enc->tmpstream = jas_stream_memopen(0, 0))) {
+			fprintf(stderr, "cannot open tmp file\n");
+			return -1;
+		}
+
+		/* Write the tile header. */
+		if (!(enc->mrk = jpc_ms_create(JPC_MS_SOT))) {
+			return -1;
+		}
+		sot = &enc->mrk->parms.sot;
+		sot->len = 0;
+		sot->tileno = tileno;
+		sot->partno = 0;
+		sot->numparts = 1;
+		if (jpc_putms(enc->tmpstream, enc->cstate, enc->mrk)) {
+			fprintf(stderr, "cannot write SOT marker\n");
+			return -1;
+		}
+		jpc_ms_destroy(enc->mrk);
+		enc->mrk = 0;
+
+/************************************************************************/
+/************************************************************************/
+/************************************************************************/
+
+		tccp = &cp->tccp;
+		for (cmptno = 0; cmptno < cp->numcmpts; ++cmptno) {
+			comp = &tile->tcmpts[cmptno];
+			if (comp->numrlvls != tccp->maxrlvls) {
+				if (!(enc->mrk = jpc_ms_create(JPC_MS_COD))) {
+					return -1;
+				}
+/* XXX = this is not really correct. we are using comp #0's precint sizes
+and other characteristics */
+				comp = &tile->tcmpts[0];
+				cod = &enc->mrk->parms.cod;
+				cod->compparms.csty = 0;
+				cod->compparms.numdlvls = comp->numrlvls - 1;
+				cod->prg = tile->prg;
+				cod->numlyrs = tile->numlyrs;
+				cod->compparms.cblkwidthval = JPC_COX_CBLKSIZEEXPN(comp->cblkwidthexpn);
+				cod->compparms.cblkheightval = JPC_COX_CBLKSIZEEXPN(comp->cblkheightexpn);
+				cod->compparms.cblksty = comp->cblksty;
+				cod->compparms.qmfbid = comp->qmfbid;
+				cod->mctrans = (tile->mctid != JPC_MCT_NONE);
+				for (i = 0; i < comp->numrlvls; ++i) {
+					cod->compparms.rlvls[i].parwidthval = comp->rlvls[i].prcwidthexpn;
+					cod->compparms.rlvls[i].parheightval = comp->rlvls[i].prcheightexpn;
+				}
+				if (jpc_putms(enc->tmpstream, enc->cstate, enc->mrk)) {
+					return -1;
+				}
+				jpc_ms_destroy(enc->mrk);
+				enc->mrk = 0;
+			}
+		}
+
+		for (cmptno = 0, comp = tile->tcmpts; cmptno < cp->numcmpts; ++cmptno, ++comp) {
+			ccps = &cp->ccps[cmptno];
+			if (ccps->numstepsizes == comp->numstepsizes) {
+				samestepsizes = 1;
+				for (bandno = 0; bandno < ccps->numstepsizes; ++bandno) {
+					if (ccps->stepsizes[bandno] != comp->stepsizes[bandno]) {
+						samestepsizes = 0;
+						break;
+					}
+				}
+			} else {
+				samestepsizes = 0;
+			}
+			if (!samestepsizes) {
+				if (!(enc->mrk = jpc_ms_create(JPC_MS_QCC))) {
+					return -1;
+				}
+				qcc = &enc->mrk->parms.qcc;
+				qcc->compno = cmptno;
+				qcc->compparms.numguard = cp->tccp.numgbits;
+				qcc->compparms.qntsty = (comp->qmfbid == JPC_COX_INS) ?
+				  JPC_QCX_SEQNT : JPC_QCX_NOQNT;
+				qcc->compparms.numstepsizes = comp->numstepsizes;
+				qcc->compparms.stepsizes = comp->stepsizes;
+				if (jpc_putms(enc->tmpstream, enc->cstate, enc->mrk)) {
+					return -1;
+				}
+				qcc->compparms.stepsizes = 0;
+				jpc_ms_destroy(enc->mrk);
+				enc->mrk = 0;
+			}
+		}
+
+		/* Write a SOD marker to indicate the end of the tile header. */
+		if (!(enc->mrk = jpc_ms_create(JPC_MS_SOD))) {
+			return -1;
+		}
+		if (jpc_putms(enc->tmpstream, enc->cstate, enc->mrk)) {
+			fprintf(stderr, "cannot write SOD marker\n");
+			return -1;
+		}
+		jpc_ms_destroy(enc->mrk);
+		enc->mrk = 0;
+tilehdrlen = jas_stream_getrwcount(enc->tmpstream);
+
+/************************************************************************/
+/************************************************************************/
+/************************************************************************/
+
+if (jpc_enc_enccblks(enc)) {
+	abort();
+	return -1;
+}
+
+		cp = enc->cp;
+		rho = (double) (tile->brx - tile->tlx) * (tile->bry - tile->tly) /
+		  ((cp->refgrdwidth - cp->imgareatlx) * (cp->refgrdheight -
+		  cp->imgareatly));
+		tile->rawsize = cp->rawsize * rho;
+
+		for (lyrno = 0; lyrno < tile->numlyrs - 1; ++lyrno) {
+			tile->lyrsizes[lyrno] = tile->rawsize * jpc_fixtodbl(
+			  cp->tcp.ilyrrates[lyrno]);
+		}
+		tile->lyrsizes[tile->numlyrs - 1] = (cp->totalsize != UINT_FAST32_MAX) ?
+		  (rho * enc->mainbodysize) : UINT_FAST32_MAX;
+		for (lyrno = 0; lyrno < tile->numlyrs; ++lyrno) {
+			if (tile->lyrsizes[lyrno] != UINT_FAST32_MAX) {
+				if (tilehdrlen <= tile->lyrsizes[lyrno]) {
+					tile->lyrsizes[lyrno] -= tilehdrlen;
+				} else {
+					tile->lyrsizes[lyrno] = 0;
+				}
+			}
+		}
+
+		if (rateallocate(enc, tile->numlyrs, tile->lyrsizes)) {
+			return -1;
+		}
+
+#if 0
+fprintf(stderr, "ENCODE TILE DATA\n");
+#endif
+		if (jpc_enc_encodetiledata(enc)) {
+			fprintf(stderr, "dotile failed\n");
+			return -1;
+		}
+
+/************************************************************************/
+/************************************************************************/
+/************************************************************************/
+
+/************************************************************************/
+/************************************************************************/
+/************************************************************************/
+
+		tilelen = jas_stream_tell(enc->tmpstream);
+
+		if (jas_stream_seek(enc->tmpstream, 6, SEEK_SET) < 0) {
+			return -1;
+		}
+		jpc_putuint32(enc->tmpstream, tilelen);
+
+		if (jas_stream_seek(enc->tmpstream, 0, SEEK_SET) < 0) {
+			return -1;
+		}
+		if (jpc_putdata(enc->out, enc->tmpstream, -1)) {
+			return -1;
+		}
+		enc->len += tilelen;
+
+		jas_stream_close(enc->tmpstream);
+		enc->tmpstream = 0;
+
+		jpc_enc_tile_destroy(enc->curtile);
+		enc->curtile = 0;
+
+	}
+
+	return 0;
+}
+
+int jpc_enc_encodetiledata(jpc_enc_t *enc)
+{
+assert(enc->tmpstream);
+	if (jpc_enc_encpkts(enc, enc->tmpstream)) {
+		return -1;
+	}
+	return 0;
+}
+
+void quantize(jas_matrix_t *data, jpc_fix_t stepsize)
+{
+	int i;
+	int j;
+	jpc_fix_t t;
+
+	if (stepsize == jpc_inttofix(1)) {
+		return;
+	}
+
+	for (i = 0; i < jas_matrix_numrows(data); ++i) {
+		for (j = 0; j < jas_matrix_numcols(data); ++j) {
+			t = jas_matrix_get(data, i, j);
+
+{
+	if (t < 0) {
+		t = jpc_fix_neg(jpc_fix_div(jpc_fix_neg(t), stepsize));
+	} else {
+		t = jpc_fix_div(t, stepsize);
+	}
+}
+
+			jas_matrix_set(data, i, j, t);
+		}
+	}
+}
+
+static void calcrdslopes(jpc_enc_cblk_t *cblk)
+{
+	jpc_enc_pass_t *endpasses;
+	jpc_enc_pass_t *pass0;
+	jpc_enc_pass_t *pass1;
+	jpc_enc_pass_t *pass2;
+	jpc_flt_t slope0;
+	jpc_flt_t slope;
+	jpc_flt_t dd;
+	long dr;
+
+	endpasses = &cblk->passes[cblk->numpasses];
+	pass2 = cblk->passes;
+	slope0 = 0;
+	while (pass2 != endpasses) {
+		pass0 = 0;
+		for (pass1 = cblk->passes; pass1 != endpasses; ++pass1) {
+			dd = pass1->cumwmsedec;
+			dr = pass1->end;
+			if (pass0) {
+				dd -= pass0->cumwmsedec;
+				dr -= pass0->end;
+			}
+			if (dd <= 0) {
+				pass1->rdslope = JPC_BADRDSLOPE;
+				if (pass1 >= pass2) {
+					pass2 = &pass1[1];
+				}
+				continue;
+			}
+			if (pass1 < pass2 && pass1->rdslope <= 0) {
+				continue;
+			}
+			if (!dr) {
+				assert(pass0);
+				pass0->rdslope = 0;
+				break;
+			}
+			slope = dd / dr;
+			if (pass0 && slope >= slope0) {
+				pass0->rdslope = 0;
+				break;
+			}
+			pass1->rdslope = slope;
+			if (pass1 >= pass2) {
+				pass2 = &pass1[1];
+			}
+			pass0 = pass1;
+			slope0 = slope;
+		}
+	}
+
+#if 0
+	for (pass0 = cblk->passes; pass0 != endpasses; ++pass0) {
+if (pass0->rdslope > 0.0) {
+		fprintf(stderr, "pass %02d nmsedec=%lf dec=%lf end=%d %lf\n", pass0 - cblk->passes,
+		  fixtodbl(pass0->nmsedec), pass0->wmsedec, pass0->end, pass0->rdslope);
+}
+	}
+#endif
+}
+
+static void dump_layeringinfo(jpc_enc_t *enc)
+{
+
+	jpc_enc_tcmpt_t *tcmpt;
+	uint_fast16_t tcmptno;
+	jpc_enc_rlvl_t *rlvl;
+	uint_fast16_t rlvlno;
+	jpc_enc_band_t *band;
+	uint_fast16_t bandno;
+	jpc_enc_prc_t *prc;
+	uint_fast32_t prcno;
+	jpc_enc_cblk_t *cblk;
+	uint_fast16_t cblkno;
+	jpc_enc_pass_t *pass;
+	uint_fast16_t passno;
+	int lyrno;
+	jpc_enc_tile_t *tile;
+
+	tile = enc->curtile;
+
+	for (lyrno = 0; lyrno < tile->numlyrs; ++lyrno) {
+		fprintf(stderr, "lyrno = %02d\n", lyrno);
+		for (tcmptno = 0, tcmpt = tile->tcmpts; tcmptno < tile->numtcmpts;
+		  ++tcmptno, ++tcmpt) {
+			for (rlvlno = 0, rlvl = tcmpt->rlvls; rlvlno < tcmpt->numrlvls;
+			  ++rlvlno, ++rlvl) {
+				if (!rlvl->bands) {
+					continue;
+				}
+				for (bandno = 0, band = rlvl->bands; bandno < rlvl->numbands;
+				  ++bandno, ++band) {
+					if (!band->data) {
+						continue;
+					}
+					for (prcno = 0, prc = band->prcs; prcno < rlvl->numprcs;
+					  ++prcno, ++prc) {
+						if (!prc->cblks) {
+							continue;
+						}
+						for (cblkno = 0, cblk = prc->cblks; cblkno <
+						  prc->numcblks; ++cblkno, ++cblk) {
+							for (passno = 0, pass = cblk->passes; passno <
+							  cblk->numpasses && pass->lyrno == lyrno;
+							  ++passno, ++pass) {
+								fprintf(stderr, "lyrno=%02d cmptno=%02d rlvlno=%02d bandno=%02d prcno=%02d cblkno=%03d passno=%03d\n", lyrno, tcmptno, rlvlno, bandno, prcno, cblkno, passno);
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
+int rateallocate(jpc_enc_t *enc, int numlyrs, uint_fast32_t *cumlens)
+{
+	jpc_flt_t lo;
+	jpc_flt_t hi;
+	jas_stream_t *out;
+	long cumlen;
+	int lyrno;
+	jpc_flt_t thresh;
+	jpc_flt_t goodthresh;
+	int success;
+	long pos;
+	long oldpos;
+	int numiters;
+
+	jpc_enc_tcmpt_t *comp;
+	jpc_enc_tcmpt_t *endcomps;
+	jpc_enc_rlvl_t *lvl;
+	jpc_enc_rlvl_t *endlvls;
+	jpc_enc_band_t *band;
+	jpc_enc_band_t *endbands;
+	jpc_enc_cblk_t *cblk;
+	jpc_enc_cblk_t *endcblks;
+	jpc_enc_pass_t *pass;
+	jpc_enc_pass_t *endpasses;
+	jpc_enc_pass_t *pass1;
+	jpc_flt_t mxrdslope;
+	jpc_flt_t mnrdslope;
+	jpc_enc_tile_t *tile;
+	jpc_enc_prc_t *prc;
+	uint_fast32_t prcno;
+
+	tile = enc->curtile;
+
+	for (lyrno = 1; lyrno < numlyrs - 1; ++lyrno) {
+		if (cumlens[lyrno - 1] > cumlens[lyrno]) {
+			abort();
+		}
+	}
+
+	if (!(out = jas_stream_memopen(0, 0))) {
+		return -1;
+	}
+
+
+	/* Find minimum and maximum R-D slope values. */
+	mnrdslope = DBL_MAX;
+	mxrdslope = 0;
+	endcomps = &tile->tcmpts[tile->numtcmpts];
+	for (comp = tile->tcmpts; comp != endcomps; ++comp) {
+		endlvls = &comp->rlvls[comp->numrlvls];
+		for (lvl = comp->rlvls; lvl != endlvls; ++lvl) {
+			if (!lvl->bands) {
+				continue;
+			}
+			endbands = &lvl->bands[lvl->numbands];
+			for (band = lvl->bands; band != endbands; ++band) {
+				if (!band->data) {
+					continue;
+				}
+				for (prcno = 0, prc = band->prcs; prcno < lvl->numprcs; ++prcno, ++prc) {
+					if (!prc->cblks) {
+						continue;
+					}
+					endcblks = &prc->cblks[prc->numcblks];
+					for (cblk = prc->cblks; cblk != endcblks; ++cblk) {
+						calcrdslopes(cblk);
+						endpasses = &cblk->passes[cblk->numpasses];
+						for (pass = cblk->passes; pass != endpasses; ++pass) {
+							if (pass->rdslope > 0) {
+								if (pass->rdslope < mnrdslope) {
+									mnrdslope = pass->rdslope;
+								}
+								if (pass->rdslope > mxrdslope) {
+									mxrdslope = pass->rdslope;
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+if (jas_getdbglevel()) {
+	fprintf(stderr, "min rdslope = %f max rdslope = %f\n", mnrdslope, mxrdslope);
+}
+
+	jpc_init_t2state(enc, 1);
+
+	for (lyrno = 0; lyrno < numlyrs; ++lyrno) {
+
+		lo = mnrdslope;
+		hi = mxrdslope;
+
+		success = 0;
+		goodthresh = 0;
+		numiters = 0;
+
+		do {
+
+			cumlen = cumlens[lyrno];
+			if (cumlen == UINT_FAST32_MAX) {
+				/* Only the last layer can be free of a rate
+				  constraint (e.g., for lossless coding). */
+				assert(lyrno == numlyrs - 1);
+				goodthresh = -1;
+				success = 1;
+				break;
+			}
+
+			thresh = (lo + hi) / 2;
+
+			/* Save the tier 2 coding state. */
+			jpc_save_t2state(enc);
+			oldpos = jas_stream_tell(out);
+			assert(oldpos >= 0);
+
+			/* Assign all passes with R-D slopes greater than or
+			  equal to the current threshold to this layer. */
+			endcomps = &tile->tcmpts[tile->numtcmpts];
+			for (comp = tile->tcmpts; comp != endcomps; ++comp) {
+				endlvls = &comp->rlvls[comp->numrlvls];
+				for (lvl = comp->rlvls; lvl != endlvls; ++lvl) {
+					if (!lvl->bands) {
+						continue;
+					}
+					endbands = &lvl->bands[lvl->numbands];
+					for (band = lvl->bands; band != endbands; ++band) {
+						if (!band->data) {
+							continue;
+						}
+						for (prcno = 0, prc = band->prcs; prcno < lvl->numprcs; ++prcno, ++prc) {
+							if (!prc->cblks) {
+								continue;
+							}
+							endcblks = &prc->cblks[prc->numcblks];
+							for (cblk = prc->cblks; cblk != endcblks; ++cblk) {
+								if (cblk->curpass) {
+									endpasses = &cblk->passes[cblk->numpasses];
+									pass1 = cblk->curpass;
+									for (pass = cblk->curpass; pass != endpasses; ++pass) {
+										if (pass->rdslope >= thresh) {
+											pass1 = &pass[1];
+										}
+									}
+									for (pass = cblk->curpass; pass != pass1; ++pass) {
+										pass->lyrno = lyrno;
+									}
+									for (; pass != endpasses; ++pass) {
+										pass->lyrno = -1;
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+
+			/* Perform tier 2 coding. */
+			endcomps = &tile->tcmpts[tile->numtcmpts];
+			for (comp = tile->tcmpts; comp != endcomps; ++comp) {
+				endlvls = &comp->rlvls[comp->numrlvls];
+				for (lvl = comp->rlvls; lvl != endlvls; ++lvl) {
+					if (!lvl->bands) {
+						continue;
+					}
+					for (prcno = 0; prcno < lvl->numprcs; ++prcno) {
+						if (jpc_enc_encpkt(enc, out, comp - tile->tcmpts, lvl - comp->rlvls, prcno, lyrno)) {
+							return -1;
+						}
+					}
+				}
+			}
+
+			pos = jas_stream_tell(out);
+
+			/* Check the rate constraint. */
+			assert(pos >= 0);
+			if (pos > cumlen) {
+				/* The rate is too high. */
+				lo = thresh;
+			} else if (pos <= cumlen) {
+				/* The rate is low enough, so try higher. */
+				hi = thresh;
+				if (!success || thresh < goodthresh) {
+					goodthresh = thresh;
+					success = 1;
+				}
+			}
+
+			/* Save the tier 2 coding state. */
+			jpc_restore_t2state(enc);
+			if (jas_stream_seek(out, oldpos, SEEK_SET) < 0) {
+				abort();
+			}
+
+if (jas_getdbglevel()) {
+fprintf(stderr, "maxlen=%08ld actuallen=%08ld thresh=%f\n", cumlen, pos, thresh);
+}
+
+			++numiters;
+		} while (lo < hi - 1e-3 && numiters < 32);
+
+		if (!success) {
+			fprintf(stderr, "warning: empty layer generated\n");
+		}
+
+if (jas_getdbglevel()) {
+fprintf(stderr, "success %d goodthresh %f\n", success, goodthresh);
+}
+
+		/* Assign all passes with R-D slopes greater than or
+		  equal to the selected threshold to this layer. */
+		endcomps = &tile->tcmpts[tile->numtcmpts];
+		for (comp = tile->tcmpts; comp != endcomps; ++comp) {
+			endlvls = &comp->rlvls[comp->numrlvls];
+			for (lvl = comp->rlvls; lvl != endlvls; ++lvl) {
+if (!lvl->bands) {
+	continue;
+}
+				endbands = &lvl->bands[lvl->numbands];
+				for (band = lvl->bands; band != endbands; ++band) {
+					if (!band->data) {
+						continue;
+					}
+					for (prcno = 0, prc = band->prcs; prcno < lvl->numprcs; ++prcno, ++prc) {
+						if (!prc->cblks) {
+							continue;
+						}
+						endcblks = &prc->cblks[prc->numcblks];
+						for (cblk = prc->cblks; cblk != endcblks; ++cblk) {
+							if (cblk->curpass) {
+								endpasses = &cblk->passes[cblk->numpasses];
+								pass1 = cblk->curpass;
+								if (success) {
+									for (pass = cblk->curpass; pass != endpasses; ++pass) {
+										if (pass->rdslope >= goodthresh) {
+											pass1 = &pass[1];
+										}
+									}
+								}
+								for (pass = cblk->curpass; pass != pass1; ++pass) {
+									pass->lyrno = lyrno;
+								}
+								for (; pass != endpasses; ++pass) {
+									pass->lyrno = -1;
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+
+		/* Perform tier 2 coding. */
+		endcomps = &tile->tcmpts[tile->numtcmpts];
+		for (comp = tile->tcmpts; comp != endcomps; ++comp) {
+			endlvls = &comp->rlvls[comp->numrlvls];
+			for (lvl = comp->rlvls; lvl != endlvls; ++lvl) {
+				if (!lvl->bands) {
+					continue;
+				}
+				for (prcno = 0; prcno < lvl->numprcs; ++prcno) {
+					if (jpc_enc_encpkt(enc, out, comp - tile->tcmpts, lvl - comp->rlvls, prcno, lyrno)) {
+						return -1;
+					}
+				}
+			}
+		}
+	}
+
+	if (jas_getdbglevel() >= 5) {
+		dump_layeringinfo(enc);
+	}
+
+	jas_stream_close(out);
+
+	JAS_DBGLOG(10, ("done doing rateallocation\n"));
+#if 0
+fprintf(stderr, "DONE RATE ALLOCATE\n");
+#endif
+
+	return 0;
+}
+
+/******************************************************************************\
+* Tile constructors and destructors.
+\******************************************************************************/
+
+jpc_enc_tile_t *jpc_enc_tile_create(jpc_enc_cp_t *cp, jas_image_t *image, int tileno)
+{
+	jpc_enc_tile_t *tile;
+	uint_fast32_t htileno;
+	uint_fast32_t vtileno;
+	uint_fast16_t lyrno;
+	uint_fast16_t cmptno;
+	jpc_enc_tcmpt_t *tcmpt;
+
+	if (!(tile = jas_malloc(sizeof(jpc_enc_tile_t)))) {
+		goto error;
+	}
+
+	/* Initialize a few members used in error recovery. */
+	tile->tcmpts = 0;
+	tile->lyrsizes = 0;
+	tile->numtcmpts = cp->numcmpts;
+	tile->pi = 0;
+
+	tile->tileno = tileno;
+	htileno = tileno % cp->numhtiles;
+	vtileno = tileno / cp->numhtiles;
+
+	/* Calculate the coordinates of the top-left and bottom-right
+	  corners of the tile. */
+	tile->tlx = JAS_MAX(cp->tilegrdoffx + htileno * cp->tilewidth,
+	  cp->imgareatlx);
+	tile->tly = JAS_MAX(cp->tilegrdoffy + vtileno * cp->tileheight,
+	  cp->imgareatly);
+	tile->brx = JAS_MIN(cp->tilegrdoffx + (htileno + 1) * cp->tilewidth,
+	  cp->refgrdwidth);
+	tile->bry = JAS_MIN(cp->tilegrdoffy + (vtileno + 1) * cp->tileheight,
+	  cp->refgrdheight);
+
+	/* Initialize some tile coding parameters. */
+	tile->intmode = cp->tcp.intmode;
+	tile->csty = cp->tcp.csty;
+	tile->prg = cp->tcp.prg;
+	tile->mctid = cp->tcp.mctid;
+
+	tile->numlyrs = cp->tcp.numlyrs;
+	if (!(tile->lyrsizes = jas_malloc(tile->numlyrs *
+	  sizeof(uint_fast32_t)))) {
+		goto error;
+	}
+	for (lyrno = 0; lyrno < tile->numlyrs; ++lyrno) {
+		tile->lyrsizes[lyrno] = 0;
+	}
+
+	/* Allocate an array for the per-tile-component information. */
+	if (!(tile->tcmpts = jas_malloc(cp->numcmpts * sizeof(jpc_enc_tcmpt_t)))) {
+		goto error;
+	}
+	/* Initialize a few members critical for error recovery. */
+	for (cmptno = 0, tcmpt = tile->tcmpts; cmptno < cp->numcmpts;
+	  ++cmptno, ++tcmpt) {
+		tcmpt->rlvls = 0;
+		tcmpt->tsfb = 0;
+		tcmpt->data = 0;
+	}
+	/* Initialize the per-tile-component information. */
+	for (cmptno = 0, tcmpt = tile->tcmpts; cmptno < cp->numcmpts;
+	  ++cmptno, ++tcmpt) {
+		if (!tcmpt_create(tcmpt, cp, image, tile)) {
+			goto error;
+		}
+	}
+
+	/* Initialize the synthesis weights for the MCT. */
+	switch (tile->mctid) {
+	case JPC_MCT_RCT:
+		tile->tcmpts[0].synweight = jpc_dbltofix(sqrt(3.0));
+		tile->tcmpts[1].synweight = jpc_dbltofix(sqrt(0.6875));
+		tile->tcmpts[2].synweight = jpc_dbltofix(sqrt(0.6875));
+		break;
+	case JPC_MCT_ICT:
+		tile->tcmpts[0].synweight = jpc_dbltofix(sqrt(3.0000));
+		tile->tcmpts[1].synweight = jpc_dbltofix(sqrt(3.2584));
+		tile->tcmpts[2].synweight = jpc_dbltofix(sqrt(2.4755));
+		break;
+	default:
+	case JPC_MCT_NONE:
+		for (cmptno = 0, tcmpt = tile->tcmpts; cmptno < cp->numcmpts;
+		  ++cmptno, ++tcmpt) {
+			tcmpt->synweight = JPC_FIX_ONE;
+		}
+		break;
+	}
+
+	if (!(tile->pi = jpc_enc_pi_create(cp, tile))) {
+		goto error;
+	}
+
+	return tile;
+
+error:
+
+	if (tile) {
+		jpc_enc_tile_destroy(tile);
+	}
+	return 0;
+}
+
+void jpc_enc_tile_destroy(jpc_enc_tile_t *tile)
+{
+	jpc_enc_tcmpt_t *tcmpt;
+	uint_fast16_t cmptno;
+
+	if (tile->tcmpts) {
+		for (cmptno = 0, tcmpt = tile->tcmpts; cmptno <
+		  tile->numtcmpts; ++cmptno, ++tcmpt) {
+			tcmpt_destroy(tcmpt);
+		}
+		jas_free(tile->tcmpts);
+	}
+	if (tile->lyrsizes) {
+		jas_free(tile->lyrsizes);
+	}
+	if (tile->pi) {
+		jpc_pi_destroy(tile->pi);
+	}
+	jas_free(tile);
+}
+
+static jpc_enc_tcmpt_t *tcmpt_create(jpc_enc_tcmpt_t *tcmpt, jpc_enc_cp_t *cp,
+  jas_image_t *image, jpc_enc_tile_t *tile)
+{
+	uint_fast16_t cmptno;
+	uint_fast16_t rlvlno;
+	jpc_enc_rlvl_t *rlvl;
+	uint_fast32_t tlx;
+	uint_fast32_t tly;
+	uint_fast32_t brx;
+	uint_fast32_t bry;
+	uint_fast32_t cmpttlx;
+	uint_fast32_t cmpttly;
+	jpc_enc_ccp_t *ccp;
+	jpc_tsfb_band_t bandinfos[JPC_MAXBANDS];
+
+	tcmpt->tile = tile;
+	tcmpt->tsfb = 0;
+	tcmpt->data = 0;
+	tcmpt->rlvls = 0;
+
+	/* Deduce the component number. */
+	cmptno = tcmpt - tile->tcmpts;
+
+	ccp = &cp->ccps[cmptno];
+
+	/* Compute the coordinates of the top-left and bottom-right
+	  corners of this tile-component. */
+	tlx = JPC_CEILDIV(tile->tlx, ccp->sampgrdstepx);
+	tly = JPC_CEILDIV(tile->tly, ccp->sampgrdstepy);
+	brx = JPC_CEILDIV(tile->brx, ccp->sampgrdstepx);
+	bry = JPC_CEILDIV(tile->bry, ccp->sampgrdstepy);
+
+	/* Create a sequence to hold the tile-component sample data. */
+	if (!(tcmpt->data = jas_seq2d_create(tlx, tly, brx, bry))) {
+		goto error;
+	}
+
+	/* Get the image data associated with this tile-component. */
+	cmpttlx = JPC_CEILDIV(cp->imgareatlx, ccp->sampgrdstepx);
+	cmpttly = JPC_CEILDIV(cp->imgareatly, ccp->sampgrdstepy);
+	if (jas_image_readcmpt(image, cmptno, tlx - cmpttlx, tly - cmpttly,
+	  brx - tlx, bry - tly, tcmpt->data)) {
+		goto error;
+	}
+
+	tcmpt->synweight = 0;
+	tcmpt->qmfbid = cp->tccp.qmfbid;
+	tcmpt->numrlvls = cp->tccp.maxrlvls;
+	tcmpt->numbands = 3 * tcmpt->numrlvls - 2;
+	if (!(tcmpt->tsfb = jpc_cod_gettsfb(tcmpt->qmfbid, tcmpt->numrlvls - 1))) {
+		goto error;
+	}
+
+	for (rlvlno = 0; rlvlno < tcmpt->numrlvls; ++rlvlno) {
+		tcmpt->prcwidthexpns[rlvlno] = cp->tccp.prcwidthexpns[rlvlno];
+		tcmpt->prcheightexpns[rlvlno] = cp->tccp.prcheightexpns[rlvlno];
+	}
+	tcmpt->cblkwidthexpn = cp->tccp.cblkwidthexpn;
+	tcmpt->cblkheightexpn = cp->tccp.cblkheightexpn;
+	tcmpt->cblksty = cp->tccp.cblksty;
+	tcmpt->csty = cp->tccp.csty;
+
+	tcmpt->numstepsizes = tcmpt->numbands;
+	assert(tcmpt->numstepsizes <= JPC_MAXBANDS);
+	memset(tcmpt->stepsizes, 0, sizeof(tcmpt->numstepsizes *
+	  sizeof(uint_fast16_t)));
+
+	/* Retrieve information about the various bands. */
+	jpc_tsfb_getbands(tcmpt->tsfb, jas_seq2d_xstart(tcmpt->data),
+	  jas_seq2d_ystart(tcmpt->data), jas_seq2d_xend(tcmpt->data),
+	  jas_seq2d_yend(tcmpt->data), bandinfos);
+
+	if (!(tcmpt->rlvls = jas_malloc(tcmpt->numrlvls * sizeof(jpc_enc_rlvl_t)))) {
+		goto error;
+	}
+	for (rlvlno = 0, rlvl = tcmpt->rlvls; rlvlno < tcmpt->numrlvls;
+	  ++rlvlno, ++rlvl) {
+		rlvl->bands = 0;
+		rlvl->tcmpt = tcmpt;
+	}
+	for (rlvlno = 0, rlvl = tcmpt->rlvls; rlvlno < tcmpt->numrlvls;
+	  ++rlvlno, ++rlvl) {
+		if (!rlvl_create(rlvl, cp, tcmpt, bandinfos)) {
+			goto error;
+		}
+	}
+
+	return tcmpt;
+
+error:
+
+	tcmpt_destroy(tcmpt);
+	return 0;
+
+}
+
+static void tcmpt_destroy(jpc_enc_tcmpt_t *tcmpt)
+{
+	jpc_enc_rlvl_t *rlvl;
+	uint_fast16_t rlvlno;
+
+	if (tcmpt->rlvls) {
+		for (rlvlno = 0, rlvl = tcmpt->rlvls; rlvlno < tcmpt->numrlvls;
+		  ++rlvlno, ++rlvl) {
+			rlvl_destroy(rlvl);
+		}
+		jas_free(tcmpt->rlvls);
+	}
+
+	if (tcmpt->data) {
+		jas_seq2d_destroy(tcmpt->data);
+	}
+	if (tcmpt->tsfb) {
+		jpc_tsfb_destroy(tcmpt->tsfb);
+	}
+}
+
+static jpc_enc_rlvl_t *rlvl_create(jpc_enc_rlvl_t *rlvl, jpc_enc_cp_t *cp,
+  jpc_enc_tcmpt_t *tcmpt, jpc_tsfb_band_t *bandinfos)
+{
+	uint_fast16_t rlvlno;
+	uint_fast32_t tlprctlx;
+	uint_fast32_t tlprctly;
+	uint_fast32_t brprcbrx;
+	uint_fast32_t brprcbry;
+	uint_fast16_t bandno;
+	jpc_enc_band_t *band;
+
+	/* Deduce the resolution level. */
+	rlvlno = rlvl - tcmpt->rlvls;
+
+	/* Initialize members required for error recovery. */
+	rlvl->bands = 0;
+	rlvl->tcmpt = tcmpt;
+
+	/* Compute the coordinates of the top-left and bottom-right
+	  corners of the tile-component at this resolution. */
+	rlvl->tlx = JPC_CEILDIVPOW2(jas_seq2d_xstart(tcmpt->data), tcmpt->numrlvls -
+	  1 - rlvlno);
+	rlvl->tly = JPC_CEILDIVPOW2(jas_seq2d_ystart(tcmpt->data), tcmpt->numrlvls -
+	  1 - rlvlno);
+	rlvl->brx = JPC_CEILDIVPOW2(jas_seq2d_xend(tcmpt->data), tcmpt->numrlvls -
+	  1 - rlvlno);
+	rlvl->bry = JPC_CEILDIVPOW2(jas_seq2d_yend(tcmpt->data), tcmpt->numrlvls -
+	  1 - rlvlno);
+
+	if (rlvl->tlx >= rlvl->brx || rlvl->tly >= rlvl->bry) {
+		rlvl->numhprcs = 0;
+		rlvl->numvprcs = 0;
+		rlvl->numprcs = 0;
+		return rlvl;
+	}
+
+	rlvl->numbands = (!rlvlno) ? 1 : 3;
+	rlvl->prcwidthexpn = cp->tccp.prcwidthexpns[rlvlno];
+	rlvl->prcheightexpn = cp->tccp.prcheightexpns[rlvlno];
+	if (!rlvlno) {
+		rlvl->cbgwidthexpn = rlvl->prcwidthexpn;
+		rlvl->cbgheightexpn = rlvl->prcheightexpn;
+	} else {
+		rlvl->cbgwidthexpn = rlvl->prcwidthexpn - 1;
+		rlvl->cbgheightexpn = rlvl->prcheightexpn - 1;
+	}
+	rlvl->cblkwidthexpn = JAS_MIN(cp->tccp.cblkwidthexpn, rlvl->cbgwidthexpn);
+	rlvl->cblkheightexpn = JAS_MIN(cp->tccp.cblkheightexpn, rlvl->cbgheightexpn);
+
+	/* Compute the number of precincts. */
+	tlprctlx = JPC_FLOORTOMULTPOW2(rlvl->tlx, rlvl->prcwidthexpn);
+	tlprctly = JPC_FLOORTOMULTPOW2(rlvl->tly, rlvl->prcheightexpn);
+	brprcbrx = JPC_CEILTOMULTPOW2(rlvl->brx, rlvl->prcwidthexpn);
+	brprcbry = JPC_CEILTOMULTPOW2(rlvl->bry, rlvl->prcheightexpn);
+	rlvl->numhprcs = JPC_FLOORDIVPOW2(brprcbrx - tlprctlx, rlvl->prcwidthexpn);
+	rlvl->numvprcs = JPC_FLOORDIVPOW2(brprcbry - tlprctly, rlvl->prcheightexpn);
+	rlvl->numprcs = rlvl->numhprcs * rlvl->numvprcs;
+
+	if (!(rlvl->bands = jas_malloc(rlvl->numbands * sizeof(jpc_enc_band_t)))) {
+		goto error;
+	}
+	for (bandno = 0, band = rlvl->bands; bandno < rlvl->numbands;
+	  ++bandno, ++band) {
+		band->prcs = 0;
+		band->data = 0;
+		band->rlvl = rlvl;
+	}
+	for (bandno = 0, band = rlvl->bands; bandno < rlvl->numbands;
+	  ++bandno, ++band) {
+		if (!band_create(band, cp, rlvl, bandinfos)) {
+			goto error;
+		}
+	}
+
+	return rlvl;
+error:
+
+	rlvl_destroy(rlvl);
+	return 0;
+}
+
+static void rlvl_destroy(jpc_enc_rlvl_t *rlvl)
+{
+	jpc_enc_band_t *band;
+	uint_fast16_t bandno;
+
+	if (rlvl->bands) {
+		for (bandno = 0, band = rlvl->bands; bandno < rlvl->numbands;
+		  ++bandno, ++band) {
+			band_destroy(band);
+		}
+		jas_free(rlvl->bands);
+	}
+}
+
+static jpc_enc_band_t *band_create(jpc_enc_band_t *band, jpc_enc_cp_t *cp,
+  jpc_enc_rlvl_t *rlvl, jpc_tsfb_band_t *bandinfos)
+{
+	uint_fast16_t bandno;
+	uint_fast16_t gblbandno;
+	uint_fast16_t rlvlno;
+	jpc_tsfb_band_t *bandinfo;
+	jpc_enc_tcmpt_t *tcmpt;
+	uint_fast32_t prcno;
+	jpc_enc_prc_t *prc;
+
+	tcmpt = rlvl->tcmpt;
+	band->data = 0;
+	band->prcs = 0;
+	band->rlvl = rlvl;
+
+	/* Deduce the resolution level and band number. */
+	rlvlno = rlvl - rlvl->tcmpt->rlvls;
+	bandno = band - rlvl->bands;
+	gblbandno = (!rlvlno) ? 0 : (3 * (rlvlno - 1) + bandno + 1);
+
+	bandinfo = &bandinfos[gblbandno];
+
+if (bandinfo->xstart != bandinfo->xend && bandinfo->ystart != bandinfo->yend) {
+	if (!(band->data = jas_seq2d_create(0, 0, 0, 0))) {
+		goto error;
+	}
+	jas_seq2d_bindsub(band->data, tcmpt->data, bandinfo->locxstart,
+	  bandinfo->locystart, bandinfo->locxend, bandinfo->locyend);
+	jas_seq2d_setshift(band->data, bandinfo->xstart, bandinfo->ystart);
+}
+	band->orient = bandinfo->orient;
+	band->analgain = JPC_NOMINALGAIN(cp->tccp.qmfbid, tcmpt->numrlvls, rlvlno,
+	  band->orient);
+	band->numbps = 0;
+	band->absstepsize = 0;
+	band->stepsize = 0;
+	band->synweight = bandinfo->synenergywt;
+
+if (band->data) {
+	if (!(band->prcs = jas_malloc(rlvl->numprcs * sizeof(jpc_enc_prc_t)))) {
+		goto error;
+	}
+	for (prcno = 0, prc = band->prcs; prcno < rlvl->numprcs; ++prcno,
+	  ++prc) {
+		prc->cblks = 0;
+		prc->incltree = 0;
+		prc->nlibtree = 0;
+		prc->savincltree = 0;
+		prc->savnlibtree = 0;
+		prc->band = band;
+	}
+	for (prcno = 0, prc = band->prcs; prcno < rlvl->numprcs; ++prcno,
+	  ++prc) {
+		if (!prc_create(prc, cp, band)) {
+			goto error;
+		}
+	}
+}
+
+	return band;
+
+error:
+	band_destroy(band);
+	return 0;
+}
+
+static void band_destroy(jpc_enc_band_t *band)
+{
+	jpc_enc_prc_t *prc;
+	jpc_enc_rlvl_t *rlvl;
+	uint_fast32_t prcno;
+
+	if (band->prcs) {
+		rlvl = band->rlvl;
+		for (prcno = 0, prc = band->prcs; prcno < rlvl->numprcs;
+		  ++prcno, ++prc) {
+			prc_destroy(prc);
+		}
+		jas_free(band->prcs);
+	}
+	if (band->data) {
+		jas_seq2d_destroy(band->data);
+	}
+}
+
+static jpc_enc_prc_t *prc_create(jpc_enc_prc_t *prc, jpc_enc_cp_t *cp, jpc_enc_band_t *band)
+{
+	uint_fast32_t prcno;
+	uint_fast32_t prcxind;
+	uint_fast32_t prcyind;
+	uint_fast32_t cbgtlx;
+	uint_fast32_t cbgtly;
+	uint_fast32_t tlprctlx;
+	uint_fast32_t tlprctly;
+	uint_fast32_t tlcbgtlx;
+	uint_fast32_t tlcbgtly;
+	uint_fast16_t rlvlno;
+	jpc_enc_rlvl_t *rlvl;
+	uint_fast32_t tlcblktlx;
+	uint_fast32_t tlcblktly;
+	uint_fast32_t brcblkbrx;
+	uint_fast32_t brcblkbry;
+	uint_fast32_t cblkno;
+	jpc_enc_cblk_t *cblk;
+	jpc_enc_tcmpt_t *tcmpt;
+
+	prc->cblks = 0;
+	prc->incltree = 0;
+	prc->savincltree = 0;
+	prc->nlibtree = 0;
+	prc->savnlibtree = 0;
+
+	rlvl = band->rlvl;
+	tcmpt = rlvl->tcmpt;
+rlvlno = rlvl - tcmpt->rlvls;
+	prcno = prc - band->prcs;
+	prcxind = prcno % rlvl->numhprcs;
+	prcyind = prcno / rlvl->numhprcs;
+	prc->band = band;
+
+tlprctlx = JPC_FLOORTOMULTPOW2(rlvl->tlx, rlvl->prcwidthexpn);
+tlprctly = JPC_FLOORTOMULTPOW2(rlvl->tly, rlvl->prcheightexpn);
+if (!rlvlno) {
+	tlcbgtlx = tlprctlx;
+	tlcbgtly = tlprctly;
+} else {
+	tlcbgtlx = JPC_CEILDIVPOW2(tlprctlx, 1);
+	tlcbgtly = JPC_CEILDIVPOW2(tlprctly, 1);
+}
+
+	/* Compute the coordinates of the top-left and bottom-right
+	  corners of the precinct. */
+	cbgtlx = tlcbgtlx + (prcxind << rlvl->cbgwidthexpn);
+	cbgtly = tlcbgtly + (prcyind << rlvl->cbgheightexpn);
+	prc->tlx = JAS_MAX(jas_seq2d_xstart(band->data), cbgtlx);
+	prc->tly = JAS_MAX(jas_seq2d_ystart(band->data), cbgtly);
+	prc->brx = JAS_MIN(jas_seq2d_xend(band->data), cbgtlx +
+	  (1 << rlvl->cbgwidthexpn));
+	prc->bry = JAS_MIN(jas_seq2d_yend(band->data), cbgtly +
+	  (1 << rlvl->cbgheightexpn));
+
+	if (prc->tlx < prc->brx && prc->tly < prc->bry) {
+		/* The precinct contains at least one code block. */
+
+		tlcblktlx = JPC_FLOORTOMULTPOW2(prc->tlx, rlvl->cblkwidthexpn);
+		tlcblktly = JPC_FLOORTOMULTPOW2(prc->tly, rlvl->cblkheightexpn);
+		brcblkbrx = JPC_CEILTOMULTPOW2(prc->brx, rlvl->cblkwidthexpn);
+		brcblkbry = JPC_CEILTOMULTPOW2(prc->bry, rlvl->cblkheightexpn);
+		prc->numhcblks = JPC_FLOORDIVPOW2(brcblkbrx - tlcblktlx,
+		  rlvl->cblkwidthexpn);
+		prc->numvcblks = JPC_FLOORDIVPOW2(brcblkbry - tlcblktly,
+		  rlvl->cblkheightexpn);
+		prc->numcblks = prc->numhcblks * prc->numvcblks;
+
+		if (!(prc->incltree = jpc_tagtree_create(prc->numhcblks,
+		  prc->numvcblks))) {
+			goto error;
+		}
+		if (!(prc->nlibtree = jpc_tagtree_create(prc->numhcblks,
+		  prc->numvcblks))) {
+			goto error;
+		}
+		if (!(prc->savincltree = jpc_tagtree_create(prc->numhcblks,
+		  prc->numvcblks))) {
+			goto error;
+		}
+		if (!(prc->savnlibtree = jpc_tagtree_create(prc->numhcblks,
+		  prc->numvcblks))) {
+			goto error;
+		}
+
+		if (!(prc->cblks = jas_malloc(prc->numcblks * sizeof(jpc_enc_cblk_t)))) {
+			goto error;
+		}
+		for (cblkno = 0, cblk = prc->cblks; cblkno < prc->numcblks;
+		  ++cblkno, ++cblk) {
+			cblk->passes = 0;
+			cblk->stream = 0;
+			cblk->mqenc = 0;
+			cblk->data = 0;
+			cblk->flags = 0;
+			cblk->prc = prc;
+		}
+		for (cblkno = 0, cblk = prc->cblks; cblkno < prc->numcblks;
+		  ++cblkno, ++cblk) {
+			if (!cblk_create(cblk, cp, prc)) {
+				goto error;
+			}
+		}
+	} else {
+		/* The precinct does not contain any code blocks. */
+		prc->tlx = prc->brx;
+		prc->tly = prc->bry;
+		prc->numcblks = 0;
+		prc->numhcblks = 0;
+		prc->numvcblks = 0;
+		prc->cblks = 0;
+		prc->incltree = 0;
+		prc->nlibtree = 0;
+		prc->savincltree = 0;
+		prc->savnlibtree = 0;
+	}
+
+	return prc;
+
+error:
+	prc_destroy(prc);
+	return 0;
+}
+
+static void prc_destroy(jpc_enc_prc_t *prc)
+{
+	jpc_enc_cblk_t *cblk;
+	uint_fast32_t cblkno;
+
+	if (prc->cblks) {
+		for (cblkno = 0, cblk = prc->cblks; cblkno < prc->numcblks;
+		  ++cblkno, ++cblk) {
+			cblk_destroy(cblk);
+		}
+		jas_free(prc->cblks);
+	}
+	if (prc->incltree) {
+		jpc_tagtree_destroy(prc->incltree);
+	}
+	if (prc->nlibtree) {
+		jpc_tagtree_destroy(prc->nlibtree);
+	}
+	if (prc->savincltree) {
+		jpc_tagtree_destroy(prc->savincltree);
+	}
+	if (prc->savnlibtree) {
+		jpc_tagtree_destroy(prc->savnlibtree);
+	}
+}
+
+static jpc_enc_cblk_t *cblk_create(jpc_enc_cblk_t *cblk, jpc_enc_cp_t *cp, jpc_enc_prc_t *prc)
+{
+	jpc_enc_band_t *band;
+	uint_fast32_t cblktlx;
+	uint_fast32_t cblktly;
+	uint_fast32_t cblkbrx;
+	uint_fast32_t cblkbry;
+	jpc_enc_rlvl_t *rlvl;
+	uint_fast32_t cblkxind;
+	uint_fast32_t cblkyind;
+	uint_fast32_t cblkno;
+	uint_fast32_t tlcblktlx;
+	uint_fast32_t tlcblktly;
+
+	cblkno = cblk - prc->cblks;
+	cblkxind = cblkno % prc->numhcblks;
+	cblkyind = cblkno / prc->numhcblks;
+	rlvl = prc->band->rlvl;
+	cblk->prc = prc;
+
+	cblk->numpasses = 0;
+	cblk->passes = 0;
+	cblk->numencpasses = 0;
+	cblk->numimsbs = 0;
+	cblk->numlenbits = 0;
+	cblk->stream = 0;
+	cblk->mqenc = 0;
+	cblk->flags = 0;
+	cblk->numbps = 0;
+	cblk->curpass = 0;
+	cblk->data = 0;
+	cblk->savedcurpass = 0;
+	cblk->savednumlenbits = 0;
+	cblk->savednumencpasses = 0;
+
+	band = prc->band;
+	tlcblktlx = JPC_FLOORTOMULTPOW2(prc->tlx, rlvl->cblkwidthexpn);
+	tlcblktly = JPC_FLOORTOMULTPOW2(prc->tly, rlvl->cblkheightexpn);
+	cblktlx = JAS_MAX(tlcblktlx + (cblkxind << rlvl->cblkwidthexpn), prc->tlx);
+	cblktly = JAS_MAX(tlcblktly + (cblkyind << rlvl->cblkheightexpn), prc->tly);
+	cblkbrx = JAS_MIN(tlcblktlx + ((cblkxind + 1) << rlvl->cblkwidthexpn),
+	  prc->brx);
+	cblkbry = JAS_MIN(tlcblktly + ((cblkyind + 1) << rlvl->cblkheightexpn),
+	  prc->bry);
+
+	assert(cblktlx < cblkbrx && cblktly < cblkbry);
+	if (!(cblk->data = jas_seq2d_create(0, 0, 0, 0))) {
+		goto error;
+	}
+	jas_seq2d_bindsub(cblk->data, band->data, cblktlx, cblktly, cblkbrx, cblkbry);
+
+	return cblk;
+
+error:
+	cblk_destroy(cblk);
+	return 0;
+}
+
+static void cblk_destroy(jpc_enc_cblk_t *cblk)
+{
+	uint_fast16_t passno;
+	jpc_enc_pass_t *pass;
+	if (cblk->passes) {
+		for (passno = 0, pass = cblk->passes; passno < cblk->numpasses;
+		  ++passno, ++pass) {
+			pass_destroy(pass);
+		}
+		jas_free(cblk->passes);
+	}
+	if (cblk->stream) {
+		jas_stream_close(cblk->stream);
+	}
+	if (cblk->mqenc) {
+		jpc_mqenc_destroy(cblk->mqenc);
+	}
+	if (cblk->data) {
+		jas_seq2d_destroy(cblk->data);
+	}
+	if (cblk->flags) {
+		jas_seq2d_destroy(cblk->flags);
+	}
+}
+
+static void pass_destroy(jpc_enc_pass_t *pass)
+{
+	/* XXX - need to free resources here */
+}
+
+void jpc_enc_dump(jpc_enc_t *enc)
+{
+	jpc_enc_tile_t *tile;
+	jpc_enc_tcmpt_t *tcmpt;
+	jpc_enc_rlvl_t *rlvl;
+	jpc_enc_band_t *band;
+	jpc_enc_prc_t *prc;
+	jpc_enc_cblk_t *cblk;
+	uint_fast16_t cmptno;
+	uint_fast16_t rlvlno;
+	uint_fast16_t bandno;
+	uint_fast32_t prcno;
+	uint_fast32_t cblkno;
+
+	tile = enc->curtile;
+
+	for (cmptno = 0, tcmpt = tile->tcmpts; cmptno < tile->numtcmpts; ++cmptno,
+	  ++tcmpt) {
+		fprintf(stderr, "  tcmpt %5d %5d %5d %5d\n", jas_seq2d_xstart(tcmpt->data), jas_seq2d_ystart(tcmpt->data), jas_seq2d_xend(tcmpt->data), jas_seq2d_yend(tcmpt->data));
+		for (rlvlno = 0, rlvl = tcmpt->rlvls; rlvlno < tcmpt->numrlvls;
+		  ++rlvlno, ++rlvl) {
+			fprintf(stderr, "    rlvl %5d %5d %5d %5d\n", rlvl->tlx, rlvl->tly, rlvl->brx, rlvl->bry);
+			for (bandno = 0, band = rlvl->bands; bandno < rlvl->numbands;
+			  ++bandno, ++band) {
+				if (!band->data) {
+					continue;
+				}
+				fprintf(stderr, "      band %5d %5d %5d %5d\n", jas_seq2d_xstart(band->data), jas_seq2d_ystart(band->data), jas_seq2d_xend(band->data), jas_seq2d_yend(band->data));
+				for (prcno = 0, prc = band->prcs; prcno < rlvl->numprcs;
+				  ++prcno, ++prc) {
+					fprintf(stderr, "        prc %5d %5d %5d %5d (%5d %5d)\n", prc->tlx, prc->tly, prc->brx, prc->bry, prc->brx - prc->tlx, prc->bry - prc->tly);
+					if (!prc->cblks) {
+						continue;
+					}
+					for (cblkno = 0, cblk = prc->cblks; cblkno < prc->numcblks;
+					  ++cblkno, ++cblk) {
+						fprintf(stderr, "         cblk %5d %5d %5d %5d\n", jas_seq2d_xstart(cblk->data), jas_seq2d_ystart(cblk->data), jas_seq2d_xend(cblk->data), jas_seq2d_yend(cblk->data));
+					}
+				}
+			}
+		}
+	}
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_enc.h b/converter/other/jpeg2000/libjasper/jpc/jpc_enc.h
new file mode 100644
index 00000000..cfd754c9
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_enc.h
@@ -0,0 +1,695 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef JPC_ENC_H
+#define JPC_ENC_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_seq.h"
+
+#include "jpc_t2cod.h"
+#include "jpc_mqenc.h"
+#include "jpc_cod.h"
+#include "jpc_tagtree.h"
+#include "jpc_cs.h"
+#include "jpc_flt.h"
+#include "jpc_tsfb.h"
+
+/******************************************************************************\
+* Constants.
+\******************************************************************************/
+
+/* The number of bits used in various lookup tables. */
+#define	JPC_NUMEXTRABITS	JPC_NMSEDEC_FRACBITS
+
+/* An invalid R-D slope value. */
+#define	JPC_BADRDSLOPE	(-1)
+
+/******************************************************************************\
+* Coding parameters types.
+\******************************************************************************/
+
+/* Per-component coding paramters. */
+
+typedef struct {
+
+	/* The horizontal sampling period. */
+	uint_fast8_t sampgrdstepx;
+
+	/* The vertical sampling period. */
+	uint_fast8_t sampgrdstepy;
+
+	/* The sample alignment horizontal offset. */
+	uint_fast8_t sampgrdsubstepx;
+
+	/* The sample alignment vertical offset. */
+	uint_fast8_t sampgrdsubstepy;
+
+	/* The precision of the samples. */
+	uint_fast8_t prec;
+
+	/* The signedness of the samples. */
+	bool sgnd;
+
+	/* The number of step sizes. */
+	uint_fast16_t numstepsizes;
+
+	/* The quantizer step sizes. */
+	uint_fast16_t stepsizes[JPC_MAXBANDS];
+
+} jpc_enc_ccp_t;
+
+/* Per-tile coding parameters. */
+
+typedef struct {
+
+	/* The coding mode. */
+	bool intmode;
+
+	/* The coding style (i.e., SOP, EPH). */
+	uint_fast8_t csty;
+
+	/* The progression order. */
+	uint_fast8_t prg;
+
+	/* The multicomponent transform. */
+	uint_fast8_t mctid;
+
+	/* The number of layers. */
+	uint_fast16_t numlyrs;
+
+	/* The normalized bit rates associated with the various
+	  intermediate layers. */
+	jpc_fix_t *ilyrrates;
+
+} jpc_enc_tcp_t;
+
+/* Per tile-component coding parameters. */
+
+typedef struct {
+
+	/* The coding style (i.e., explicit precinct sizes). */
+	uint_fast8_t csty;
+
+	/* The maximum number of resolution levels allowed. */
+	uint_fast8_t maxrlvls;
+
+	/* The exponent for the nominal code block width. */
+	uint_fast16_t cblkwidthexpn;
+
+	/* The exponent for the nominal code block height. */
+	uint_fast16_t cblkheightexpn;
+
+	/* The code block style parameters (e.g., lazy, terminate all,
+	  segmentation symbols, causal, reset probability models). */
+	uint_fast8_t cblksty;
+
+	/* The QMFB. */
+	uint_fast8_t qmfbid;
+
+	/* The precinct width values. */
+	uint_fast16_t prcwidthexpns[JPC_MAXRLVLS];
+
+	/* The precinct height values. */
+	uint_fast16_t prcheightexpns[JPC_MAXRLVLS];
+
+	/* The number of guard bits. */
+	uint_fast8_t numgbits;
+
+} jpc_enc_tccp_t;
+
+/* Coding parameters. */
+
+typedef struct {
+
+	/* The debug level. */
+	int debug;
+
+	/* The horizontal offset from the origin of the reference grid to the
+	  left edge of the image area. */
+	uint_fast32_t imgareatlx;
+
+	/* The vertical offset from the origin of the reference grid to the
+	  top edge of the image area. */
+	uint_fast32_t imgareatly;
+
+	/* The horizontal offset from the origin of the reference grid to the
+	  right edge of the image area (plus one). */
+	uint_fast32_t refgrdwidth;
+
+	/* The vertical offset from the origin of the reference grid to the
+	  bottom edge of the image area (plus one). */
+	uint_fast32_t refgrdheight;
+
+	/* The horizontal offset from the origin of the tile grid to the
+	  origin of the reference grid. */
+	uint_fast32_t tilegrdoffx;
+
+	/* The vertical offset from the origin of the tile grid to the
+	  origin of the reference grid. */
+	uint_fast32_t tilegrdoffy;
+
+	/* The nominal tile width in units of the image reference grid. */
+	uint_fast32_t tilewidth;
+
+	/* The nominal tile height in units of the image reference grid. */
+	uint_fast32_t tileheight;
+
+	/* The number of tiles spanning the image area in the horizontal
+	  direction. */
+	uint_fast32_t numhtiles;
+
+	/* The number of tiles spanning the image area in the vertical
+	  direction. */
+	uint_fast32_t numvtiles;
+
+	/* The number of tiles. */
+	uint_fast32_t numtiles;
+
+	/* The number of components. */
+	uint_fast16_t numcmpts;
+
+	/* The per-component coding parameters. */
+	jpc_enc_ccp_t *ccps;
+
+	/* The per-tile coding parameters. */
+	jpc_enc_tcp_t tcp;
+
+	/* The per-tile-component coding parameters. */
+	jpc_enc_tccp_t tccp;
+
+	/* The target code stream length in bytes. */
+	uint_fast32_t totalsize;
+
+	/* The raw (i.e., uncompressed) size of the image in bytes. */
+	uint_fast32_t rawsize;
+
+} jpc_enc_cp_t;
+
+/******************************************************************************\
+* Encoder class.
+\******************************************************************************/
+
+/* Encoder per-coding-pass state information. */
+
+typedef struct {
+
+	/* The starting offset for this pass. */
+	int start;
+
+	/* The ending offset for this pass. */
+	int end;
+
+	/* The type of data in this pass (i.e., MQ or raw). */
+	int type;
+
+	/* Flag indicating that this pass is terminated. */
+	int term;
+
+	/* The entropy coder state after coding this pass. */
+	jpc_mqencstate_t mqencstate;
+
+	/* The layer to which this pass has been assigned. */
+	int lyrno;
+
+	/* The R-D slope for this pass. */
+	jpc_flt_t rdslope;
+
+	/* The weighted MSE reduction associated with this pass. */
+	jpc_flt_t wmsedec;
+
+	/* The cumulative weighted MSE reduction. */
+	jpc_flt_t cumwmsedec;
+
+	/* The normalized MSE reduction. */
+	long nmsedec;
+
+} jpc_enc_pass_t;
+
+/* Encoder per-code-block state information. */
+
+typedef struct {
+
+	/* The number of passes. */
+	int numpasses;
+
+	/* The per-pass information. */
+	jpc_enc_pass_t *passes;
+
+	/* The number of passes encoded so far. */
+	int numencpasses;
+
+	/* The number of insignificant MSBs. */
+	int numimsbs;
+
+	/* The number of bits used to encode pass data lengths. */
+	int numlenbits;
+
+	/* The byte stream for this code block. */
+	jas_stream_t *stream;
+
+	/* The entropy encoder. */
+	jpc_mqenc_t *mqenc;
+
+	/* The data for this code block. */
+	jas_matrix_t *data;
+
+	/* The state for this code block. */
+	jas_matrix_t *flags;
+
+	/* The number of bit planes required for this code block. */
+	int numbps;
+
+	/* The next pass to be encoded. */
+	jpc_enc_pass_t *curpass;
+
+	/* The per-code-block-group state information. */
+	struct jpc_enc_prc_s *prc;
+
+	/* The saved current pass. */
+	/* This is used by the rate control code. */
+	jpc_enc_pass_t *savedcurpass;
+
+	/* The saved length indicator size. */
+	/* This is used by the rate control code. */
+	int savednumlenbits;
+
+	/* The saved number of encoded passes. */
+	/* This is used by the rate control code. */
+	int savednumencpasses;
+
+} jpc_enc_cblk_t;
+
+/* Encoder per-code-block-group state information. */
+
+typedef struct jpc_enc_prc_s {
+
+	/* The x-coordinate of the top-left corner of the precinct. */
+	uint_fast32_t tlx;
+
+	/* The y-coordinate of the top-left corner of the precinct. */
+	uint_fast32_t tly;
+
+	/* The x-coordinate of the bottom-right corner of the precinct
+	  (plus one). */
+	uint_fast32_t brx;
+
+	/* The y-coordinate of the bottom-right corner of the precinct
+	  (plus one). */
+	uint_fast32_t bry;
+
+	/* The number of code blocks spanning the precinct in the horizontal
+	direction. */
+	int numhcblks;
+
+	/* The number of code blocks spanning the precinct in the vertical
+	direction. */
+	int numvcblks;
+
+	/* The total number of code blocks. */
+	int numcblks;
+
+	/* The per-code-block information. */
+	jpc_enc_cblk_t *cblks;
+
+	/* The inclusion tag tree. */
+	jpc_tagtree_t *incltree;
+
+	/* The insignifcant MSBs tag tree. */
+	jpc_tagtree_t *nlibtree;
+
+	/* The per-band information. */
+	struct jpc_enc_band_s *band;
+
+	/* The saved inclusion tag tree. */
+	/* This is used by rate control. */
+	jpc_tagtree_t *savincltree;
+
+	/* The saved leading-insignificant-bit-planes tag tree. */
+	/* This is used by rate control. */
+	jpc_tagtree_t *savnlibtree;
+
+} jpc_enc_prc_t;
+
+/* Encoder per-band state information. */
+
+typedef struct jpc_enc_band_s {
+
+	/* The per precinct information. */
+	jpc_enc_prc_t *prcs;
+
+	/* The coefficient data for this band. */
+	jas_matrix_t *data;
+
+	/* The orientation of this band (i.e., LL, LH, HL, or HH). */
+	int orient;
+
+	/* The number of bit planes associated with this band. */
+	int numbps;
+
+	/* The quantizer step size. */
+	jpc_fix_t absstepsize;
+
+	/* The encoded quantizer step size. */
+	int stepsize;
+
+	/* The L2 norm of the synthesis basis functions associated with
+	  this band.  (The MCT is not considered in this value.) */
+	jpc_fix_t synweight;
+
+	/* The analysis gain for this band. */
+	int analgain;
+
+	/* The per-resolution-level information. */
+	struct jpc_enc_rlvl_s *rlvl;
+
+} jpc_enc_band_t;
+
+/* Encoder per-resolution-level state information. */
+
+typedef struct jpc_enc_rlvl_s {
+
+	/* The x-coordinate of the top-left corner of the tile-component
+	  at this resolution. */
+	uint_fast32_t tlx;
+
+	/* The y-coordinate of the top-left corner of the tile-component
+	  at this resolution. */
+	uint_fast32_t tly;
+
+	/* The x-coordinate of the bottom-right corner of the tile-component
+	  at this resolution (plus one). */
+	uint_fast32_t brx;
+
+	/* The y-coordinate of the bottom-right corner of the tile-component
+	  at this resolution (plus one). */
+	uint_fast32_t bry;
+
+	/* The exponent value for the nominal precinct width measured
+	  relative to the associated LL band. */
+	int prcwidthexpn;
+
+	/* The exponent value for the nominal precinct height measured
+	  relative to the associated LL band. */
+	int prcheightexpn;
+
+	/* The number of precincts spanning the resolution level in the
+	  horizontal direction. */
+	int numhprcs;
+
+	/* The number of precincts spanning the resolution level in the
+	  vertical direction. */
+	int numvprcs;
+
+	/* The total number of precincts. */
+	int numprcs;
+
+	/* The exponent value for the nominal code block group width.
+	  This quantity is associated with the next lower resolution level
+	  (assuming that there is one). */
+	int cbgwidthexpn;
+
+	/* The exponent value for the nominal code block group height.
+	  This quantity is associated with the next lower resolution level
+	  (assuming that there is one). */
+	int cbgheightexpn;
+
+	/* The exponent value for the code block width. */
+	uint_fast16_t cblkwidthexpn;
+
+	/* The exponent value for the code block height. */
+	uint_fast16_t cblkheightexpn;
+
+	/* The number of bands associated with this resolution level. */
+	int numbands;
+
+	/* The per-band information. */
+	jpc_enc_band_t *bands;
+
+	/* The parent tile-component. */
+	struct jpc_enc_tcmpt_s *tcmpt;
+
+} jpc_enc_rlvl_t;
+
+/* Encoder per-tile-component state information. */
+
+typedef struct jpc_enc_tcmpt_s {
+
+	/* The number of resolution levels. */
+	uint_fast16_t numrlvls;
+
+	/* The per-resolution-level information. */
+	jpc_enc_rlvl_t *rlvls;
+
+	/* The tile-component data. */
+	jas_matrix_t *data;
+
+	/* The QMFB. */
+	int qmfbid;
+
+	/* The number of bands. */
+	int numbands;
+
+	/* The TSFB. */
+	jpc_tsfb_t *tsfb;
+
+	/* The synthesis energy weight (for the MCT). */
+	jpc_fix_t synweight;
+
+	/* The precinct width exponents. */
+	int prcwidthexpns[JPC_MAXRLVLS];
+
+	/* The precinct height exponents. */
+	int prcheightexpns[JPC_MAXRLVLS];
+
+	/* The code block width exponent. */
+	int cblkwidthexpn;
+
+	/* The code block height exponent. */
+	int cblkheightexpn;
+
+	/* Coding style (i.e., explicit precinct sizes). */
+	int csty;
+
+	/* Code block style. */
+	int cblksty;
+
+	/* The number of quantizer step sizes. */
+	uint_fast16_t numstepsizes;
+
+	/* The encoded quantizer step sizes. */
+	uint_fast16_t stepsizes[JPC_MAXBANDS];
+
+	/* The parent tile. */
+	struct jpc_enc_tile_s *tile;
+
+} jpc_enc_tcmpt_t;
+
+/* Encoder per-tile state information. */
+
+typedef struct jpc_enc_tile_s {
+
+	/* The tile number. */
+	uint_fast32_t tileno;
+
+	/* The x-coordinate of the top-left corner of the tile measured with
+	  respect to the reference grid. */
+	uint_fast32_t tlx;
+
+	/* The y-coordinate of the top-left corner of the tile measured with
+	  respect to the reference grid. */
+	uint_fast32_t tly;
+
+	/* The x-coordinate of the bottom-right corner of the tile measured
+	  with respect to the reference grid (plus one). */
+	uint_fast32_t brx;
+
+	/* The y-coordinate of the bottom-right corner of the tile measured
+	  with respect to the reference grid (plus one). */
+	uint_fast32_t bry;
+
+	/* The coding style. */
+	uint_fast8_t csty;
+
+	/* The progression order. */
+	uint_fast8_t prg;
+
+	/* The number of layers. */
+	uint_fast16_t numlyrs;
+
+	/* The MCT to employ (if any). */
+	uint_fast8_t mctid;
+
+	/* The packet iterator (used to determine the order of packet
+	  generation). */
+	jpc_pi_t *pi;
+
+	/* The coding mode (i.e., integer or real). */
+	bool intmode;
+
+	/* The number of bytes to allocate to the various layers. */
+	uint_fast32_t *lyrsizes;
+
+	/* The number of tile-components. */
+	int numtcmpts;
+
+	/* The per tile-component information. */
+	jpc_enc_tcmpt_t *tcmpts;
+
+	/* The raw (i.e., uncompressed) size of this tile. */
+	uint_fast32_t rawsize;
+
+} jpc_enc_tile_t;
+
+/* Encoder class. */
+
+typedef struct jpc_enc_s {
+
+	/* The image being encoded. */
+	jas_image_t *image;
+
+	/* The output stream. */
+	jas_stream_t *out;
+
+	/* The coding parameters. */
+	jpc_enc_cp_t *cp;
+
+	/* The tile currently being processed. */
+	jpc_enc_tile_t *curtile;
+
+	/* The code stream state. */
+	jpc_cstate_t *cstate;
+
+	/* The number of bytes output so far. */
+	uint_fast32_t len;
+
+	/* The number of bytes available for the main body of the code stream. */
+	/* This is used for rate allocation purposes. */
+	uint_fast32_t mainbodysize;
+
+	/* The marker segment currently being processed. */
+	/* This member is a convenience for making cleanup easier. */
+	jpc_ms_t *mrk;
+
+	/* The stream used to temporarily hold tile-part data. */
+	jas_stream_t *tmpstream;
+
+} jpc_enc_t;
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_fix.h b/converter/other/jpeg2000/libjasper/jpc/jpc_fix.h
new file mode 100644
index 00000000..5ff0c9f8
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_fix.h
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Fixed-Point Number Class
+ *
+ * $Id$
+ */
+
+#ifndef JPC_FIX_H
+#define JPC_FIX_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_types.h"
+#include "jasper/jas_fix.h"
+
+/******************************************************************************\
+* Basic parameters of the fixed-point type.
+\******************************************************************************/
+
+/* The integral type used to represent a fixed-point number.  This
+  type must be capable of representing values from -(2^31) to 2^31-1
+  (inclusive). */
+typedef int_fast32_t jpc_fix_t;
+
+/* The integral type used to respresent higher-precision intermediate results.
+  This type should be capable of representing values from -(2^63) to 2^63-1
+  (inclusive). */
+typedef int_fast64_t jpc_fix_big_t;
+
+/* The number of bits used for the fractional part of a fixed-point number. */
+#define JPC_FIX_FRACBITS	13
+
+/******************************************************************************\
+* Instantiations of the generic fixed-point number macros for the
+* parameters given above.  (Too bad C does not support templates, eh?)
+* The purpose of these macros is self-evident if one examines the
+* corresponding macros in the jasper/jas_fix.h header file.
+\******************************************************************************/
+
+#define	JPC_FIX_ZERO	JAS_FIX_ZERO(jpc_fix_t, JPC_FIX_FRACBITS)
+#define	JPC_FIX_ONE		JAS_FIX_ONE(jpc_fix_t, JPC_FIX_FRACBITS)
+#define	JPC_FIX_HALF	JAS_FIX_HALF(jpc_fix_t, JPC_FIX_FRACBITS)
+
+#define jpc_inttofix(x)	JAS_INTTOFIX(jpc_fix_t, JPC_FIX_FRACBITS, x)
+#define jpc_fixtoint(x)	JAS_FIXTOINT(jpc_fix_t, JPC_FIX_FRACBITS, x)
+#define jpc_fixtodbl(x)	JAS_FIXTODBL(jpc_fix_t, JPC_FIX_FRACBITS, x)
+#define jpc_dbltofix(x)	JAS_DBLTOFIX(jpc_fix_t, JPC_FIX_FRACBITS, x)
+
+#define	jpc_fix_add(x, y)	JAS_FIX_ADD(jpc_fix_t, JPC_FIX_FRACBITS, x, y)
+#define	jpc_fix_sub(x, y)	JAS_FIX_SUB(jpc_fix_t, JPC_FIX_FRACBITS, x, y)
+#define	jpc_fix_mul(x, y) \
+	JAS_FIX_MUL(jpc_fix_t, JPC_FIX_FRACBITS, jpc_fix_big_t, x, y)
+#define	jpc_fix_mulbyint(x, y) \
+	JAS_FIX_MULBYINT(jpc_fix_t, JPC_FIX_FRACBITS, x, y)
+#define	jpc_fix_div(x, y) \
+	JAS_FIX_DIV(jpc_fix_t, JPC_FIX_FRACBITS, jpc_fix_big_t, x, y)
+#define	jpc_fix_neg(x)		JAS_FIX_NEG(jpc_fix_t, JPC_FIX_FRACBITS, x)
+#define	jpc_fix_asl(x, n)	JAS_FIX_ASL(jpc_fix_t, JPC_FIX_FRACBITS, x, n)
+#define	jpc_fix_asr(x, n)	JAS_FIX_ASR(jpc_fix_t, JPC_FIX_FRACBITS, x, n)
+
+#define jpc_fix_pluseq(x, y)	JAS_FIX_PLUSEQ(jpc_fix_t, JPC_FIX_FRACBITS, x, y)
+#define jpc_fix_minuseq(x, y)	JAS_FIX_MINUSEQ(jpc_fix_t, JPC_FIX_FRACBITS, x, y)
+#define	jpc_fix_muleq(x, y)	\
+	JAS_FIX_MULEQ(jpc_fix_t, JPC_FIX_FRACBITS, jpc_fix_big_t, x, y)
+
+#define	jpc_fix_abs(x)		JAS_FIX_ABS(jpc_fix_t, JPC_FIX_FRACBITS, x)
+#define	jpc_fix_isint(x)	JAS_FIX_ISINT(jpc_fix_t, JPC_FIX_FRACBITS, x)
+#define jpc_fix_sgn(x)		JAS_FIX_SGN(jpc_fix_t, JPC_FIX_FRACBITS, x)
+#define	jpc_fix_round(x)	JAS_FIX_ROUND(jpc_fix_t, JPC_FIX_FRACBITS, x)
+#define	jpc_fix_floor(x)	JAS_FIX_FLOOR(jpc_fix_t, JPC_FIX_FRACBITS, x)
+#define jpc_fix_trunc(x)	JAS_FIX_TRUNC(jpc_fix_t, JPC_FIX_FRACBITS, x)
+
+/******************************************************************************\
+* Extra macros for convenience.
+\******************************************************************************/
+
+/* Compute the sum of three fixed-point numbers. */
+#define jpc_fix_add3(x, y, z)	jpc_fix_add(jpc_fix_add(x, y), z)
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_flt.h b/converter/other/jpeg2000/libjasper/jpc/jpc_flt.h
new file mode 100644
index 00000000..088eb00c
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_flt.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Floating-Point Class
+ *
+ * $Id$
+ */
+
+#ifndef JPC_FLT_H
+#define JPC_FLT_H
+
+#include <float.h>
+
+/* The code ought to be modified so this type is not used at all. */
+/* Very few places in the code rely on floating-point arithmetic, aside
+  from conversions in printf's. */
+typedef double jpc_flt_t;
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_math.c b/converter/other/jpeg2000/libjasper/jpc/jpc_math.c
new file mode 100644
index 00000000..d860847d
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_math.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Math Library
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes
+\******************************************************************************/
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "jpc_math.h"
+
+/******************************************************************************\
+* Miscellaneous Functions
+\******************************************************************************/
+
+/* Calculate the integer quantity floor(log2(x)), where x is a positive
+  integer. */
+int jpc_floorlog2(int x)
+{
+	int y;
+
+	/* The argument must be positive. */
+	assert(x > 0);
+
+	y = 0;
+	while (x > 1) {
+		x >>= 1;
+		++y;
+	}
+	return y;
+}
+
+/* Calculate the bit position of the first leading one in a nonnegative
+  integer. */
+/* This function is the basically the same as ceillog2(x), except that the
+  allowable range for x is slightly different. */
+int jpc_firstone(int x)
+{
+	int n;
+
+	/* The argument must be nonnegative. */
+	assert(x >= 0);
+
+	n = -1;
+	while (x > 0) {
+		x >>= 1;
+		++n;
+	}
+	return n;
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_math.h b/converter/other/jpeg2000/libjasper/jpc/jpc_math.h
new file mode 100644
index 00000000..e343bab1
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_math.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+#ifndef	JPC_MATH_H
+#define	JPC_MATH_H
+
+/******************************************************************************\
+* Includes
+\******************************************************************************/
+
+#include	<assert.h>
+
+/******************************************************************************\
+* Macros
+\******************************************************************************/
+
+/* Compute the floor of the quotient of two integers. */
+#define	JPC_FLOORDIV(x, y)	(assert(x >= 0 && y > 0), (x) / (y))
+
+/* Compute the ceiling of the quotient of two integers. */
+#define	JPC_CEILDIV(x, y)	(assert(x >= 0 && y > 0), ((x) + (y) - 1) / (y))
+
+/* Compute the floor of (x / 2^y). */
+#define	JPC_FLOORDIVPOW2(x, y) \
+	(assert(x >= 0 && y > 0), (x) >> (y))
+
+/* Compute the ceiling of (x / 2^y). */
+#define	JPC_CEILDIVPOW2(x, y) \
+	(assert(x >= 0 && y >= 0), ((x) + (1 << (y)) - 1) >> (y))
+/* JPC_CEILDIVPOW2U is for unsigned arguments (JPC_CEILDIVPOW2 would generate
+   compiler warnings due to its superfluous >= 0 check)
+*/
+#define	JPC_CEILDIVPOW2U(x, y) \
+	(((x) + (1 << (y)) - 1) >> (y))
+
+/******************************************************************************\
+* Functions.
+\******************************************************************************/
+
+/* Calculate the bit position of the first leading one in a nonnegative
+  integer. */
+int jpc_firstone(int x);
+
+/* Calculate the integer quantity floor(log2(x)), where x is a positive
+  integer. */
+int jpc_floorlog2(int x);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_mct.c b/converter/other/jpeg2000/libjasper/jpc/jpc_mct.c
new file mode 100644
index 00000000..7c16c3e5
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_mct.c
@@ -0,0 +1,337 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Multicomponent Transform Code
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <assert.h>
+
+#include "jasper/jas_seq.h"
+
+#include "jpc_fix.h"
+#include "jpc_mct.h"
+
+/******************************************************************************\
+* Code.
+\******************************************************************************/
+
+/* Compute the forward RCT. */
+
+void jpc_rct(jas_matrix_t *c0, jas_matrix_t *c1, jas_matrix_t *c2)
+{
+	int numrows;
+	int numcols;
+	int i;
+	int j;
+	jpc_fix_t *c0p;
+	jpc_fix_t *c1p;
+	jpc_fix_t *c2p;
+
+	numrows = jas_matrix_numrows(c0);
+	numcols = jas_matrix_numcols(c0);
+
+	/* All three matrices must have the same dimensions. */
+	assert(jas_matrix_numrows(c1) == numrows && jas_matrix_numcols(c1) == numcols
+	  && jas_matrix_numrows(c2) == numrows && jas_matrix_numcols(c2) == numcols);
+
+	for (i = 0; i < numrows; i++) {
+		c0p = jas_matrix_getref(c0, i, 0);
+		c1p = jas_matrix_getref(c1, i, 0);
+		c2p = jas_matrix_getref(c2, i, 0);
+		for (j = numcols; j > 0; --j) {
+			int r;
+			int g;
+			int b;
+			int y;
+			int u;
+			int v;
+			r = *c0p;
+			g = *c1p;
+			b = *c2p;
+			y = (r + (g << 1) + b) >> 2;
+			u = b - g;
+			v = r - g;
+			*c0p++ = y;
+			*c1p++ = u;
+			*c2p++ = v;
+		}
+	}
+}
+
+/* Compute the inverse RCT. */
+
+void jpc_irct(jas_matrix_t *c0, jas_matrix_t *c1, jas_matrix_t *c2)
+{
+	int numrows;
+	int numcols;
+	int i;
+	int j;
+	jpc_fix_t *c0p;
+	jpc_fix_t *c1p;
+	jpc_fix_t *c2p;
+
+	numrows = jas_matrix_numrows(c0);
+	numcols = jas_matrix_numcols(c0);
+
+	/* All three matrices must have the same dimensions. */
+	assert(jas_matrix_numrows(c1) == numrows && jas_matrix_numcols(c1) == numcols
+	  && jas_matrix_numrows(c2) == numrows && jas_matrix_numcols(c2) == numcols);
+
+	for (i = 0; i < numrows; i++) {
+		c0p = jas_matrix_getref(c0, i, 0);
+		c1p = jas_matrix_getref(c1, i, 0);
+		c2p = jas_matrix_getref(c2, i, 0);
+		for (j = numcols; j > 0; --j) {
+			int r;
+			int g;
+			int b;
+			int y;
+			int u;
+			int v;
+			y = *c0p;
+			u = *c1p;
+			v = *c2p;
+			g = y - ((u + v) >> 2);
+			r = v + g;
+			b = u + g;
+			*c0p++ = r;
+			*c1p++ = g;
+			*c2p++ = b;
+		}
+	}
+}
+
+void jpc_ict(jas_matrix_t *c0, jas_matrix_t *c1, jas_matrix_t *c2)
+{
+	int numrows;
+	int numcols;
+	int i;
+	int j;
+	jpc_fix_t r;
+	jpc_fix_t g;
+	jpc_fix_t b;
+	jpc_fix_t y;
+	jpc_fix_t u;
+	jpc_fix_t v;
+	jpc_fix_t *c0p;
+	jpc_fix_t *c1p;
+	jpc_fix_t *c2p;
+
+	numrows = jas_matrix_numrows(c0);
+	assert(jas_matrix_numrows(c1) == numrows && jas_matrix_numrows(c2) == numrows);
+	numcols = jas_matrix_numcols(c0);
+	assert(jas_matrix_numcols(c1) == numcols && jas_matrix_numcols(c2) == numcols);
+	for (i = 0; i < numrows; ++i) {
+		c0p = jas_matrix_getref(c0, i, 0);
+		c1p = jas_matrix_getref(c1, i, 0);
+		c2p = jas_matrix_getref(c2, i, 0);
+		for (j = numcols; j > 0; --j) {
+			r = *c0p;
+			g = *c1p;
+			b = *c2p;
+			y = jpc_fix_add3(jpc_fix_mul(jpc_dbltofix(0.299), r), jpc_fix_mul(jpc_dbltofix(0.587), g),
+			  jpc_fix_mul(jpc_dbltofix(0.114), b));
+			u = jpc_fix_add3(jpc_fix_mul(jpc_dbltofix(-0.16875), r), jpc_fix_mul(jpc_dbltofix(-0.33126), g),
+			  jpc_fix_mul(jpc_dbltofix(0.5), b));
+			v = jpc_fix_add3(jpc_fix_mul(jpc_dbltofix(0.5), r), jpc_fix_mul(jpc_dbltofix(-0.41869), g),
+			  jpc_fix_mul(jpc_dbltofix(-0.08131), b));
+			*c0p++ = y;
+			*c1p++ = u;
+			*c2p++ = v;
+		}
+	}
+}
+
+void jpc_iict(jas_matrix_t *c0, jas_matrix_t *c1, jas_matrix_t *c2)
+{
+	int numrows;
+	int numcols;
+	int i;
+	int j;
+	jpc_fix_t r;
+	jpc_fix_t g;
+	jpc_fix_t b;
+	jpc_fix_t y;
+	jpc_fix_t u;
+	jpc_fix_t v;
+	jpc_fix_t *c0p;
+	jpc_fix_t *c1p;
+	jpc_fix_t *c2p;
+
+	numrows = jas_matrix_numrows(c0);
+	assert(jas_matrix_numrows(c1) == numrows && jas_matrix_numrows(c2) == numrows);
+	numcols = jas_matrix_numcols(c0);
+	assert(jas_matrix_numcols(c1) == numcols && jas_matrix_numcols(c2) == numcols);
+	for (i = 0; i < numrows; ++i) {
+		c0p = jas_matrix_getref(c0, i, 0);
+		c1p = jas_matrix_getref(c1, i, 0);
+		c2p = jas_matrix_getref(c2, i, 0);
+		for (j = numcols; j > 0; --j) {
+			y = *c0p;
+			u = *c1p;
+			v = *c2p;
+			r = jpc_fix_add(y, jpc_fix_mul(jpc_dbltofix(1.402), v));
+			g = jpc_fix_add3(y, jpc_fix_mul(jpc_dbltofix(-0.34413), u),
+			  jpc_fix_mul(jpc_dbltofix(-0.71414), v));
+			b = jpc_fix_add(y, jpc_fix_mul(jpc_dbltofix(1.772), u));
+			*c0p++ = r;
+			*c1p++ = g;
+			*c2p++ = b;
+		}
+	}
+}
+
+jpc_fix_t jpc_mct_getsynweight(int mctid, int cmptno)
+{
+	jpc_fix_t synweight;
+
+	switch (mctid) {
+	case JPC_MCT_RCT:
+		switch (cmptno) {
+		case 0:
+			synweight = jpc_dbltofix(sqrt(3.0));
+			break;
+		case 1:
+			synweight = jpc_dbltofix(sqrt(0.6875));
+			break;
+		case 2:
+			synweight = jpc_dbltofix(sqrt(0.6875));
+			break;
+		}
+		break;
+	case JPC_MCT_ICT:
+		switch (cmptno) {
+		case 0:
+			synweight = jpc_dbltofix(sqrt(3.0000));
+			break;
+		case 1:
+			synweight = jpc_dbltofix(sqrt(3.2584));
+			break;
+		case 2:
+			synweight = jpc_dbltofix(sqrt(2.4755));
+			break;
+		}
+		break;
+	default:
+		synweight = JPC_FIX_ONE;
+		break;
+	}
+
+	return synweight;
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_mct.h b/converter/other/jpeg2000/libjasper/jpc/jpc_mct.h
new file mode 100644
index 00000000..d03d6ce1
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_mct.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Multicomponent Transform Code
+ *
+ * $Id$
+ */
+
+#ifndef JPC_MCT_H
+#define JPC_MCT_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_seq.h"
+#include "jasper/jas_fix.h"
+
+/******************************************************************************\
+* Constants.
+\******************************************************************************/
+
+/*
+ * Multicomponent transform IDs.
+ */
+
+#define JPC_MCT_NONE	0
+#define JPC_MCT_ICT		1
+#define JPC_MCT_RCT		2
+
+/******************************************************************************\
+* Functions.
+\******************************************************************************/
+
+/* Calculate the forward RCT. */
+void jpc_rct(jas_matrix_t *c0, jas_matrix_t *c1, jas_matrix_t *c2);
+
+/* Calculate the inverse RCT. */
+void jpc_irct(jas_matrix_t *c0, jas_matrix_t *c1, jas_matrix_t *c2);
+
+/* Calculate the forward ICT. */
+void jpc_ict(jas_matrix_t *c0, jas_matrix_t *c1, jas_matrix_t *c2);
+
+/* Calculate the inverse ICT. */
+void jpc_iict(jas_matrix_t *c0, jas_matrix_t *c1, jas_matrix_t *c2);
+
+/* Get the synthesis weight associated with a particular component. */
+jpc_fix_t jpc_mct_getsynweight(int mctid, int cmptno);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_mqcod.c b/converter/other/jpeg2000/libjasper/jpc/jpc_mqcod.c
new file mode 100644
index 00000000..535eaa2d
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_mqcod.c
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * MQ Arithmetic Coder
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_malloc.h"
+
+#include "jpc_mqcod.h"
+
+/******************************************************************************\
+* Data.
+\******************************************************************************/
+
+/* MQ coder per-state information. */
+
+jpc_mqstate_t jpc_mqstates[47 * 2] = {
+	{0x5601, 0, &jpc_mqstates[ 2], &jpc_mqstates[ 3]},
+	{0x5601, 1, &jpc_mqstates[ 3], &jpc_mqstates[ 2]},
+	{0x3401, 0, &jpc_mqstates[ 4], &jpc_mqstates[12]},
+	{0x3401, 1, &jpc_mqstates[ 5], &jpc_mqstates[13]},
+	{0x1801, 0, &jpc_mqstates[ 6], &jpc_mqstates[18]},
+	{0x1801, 1, &jpc_mqstates[ 7], &jpc_mqstates[19]},
+	{0x0ac1, 0, &jpc_mqstates[ 8], &jpc_mqstates[24]},
+	{0x0ac1, 1, &jpc_mqstates[ 9], &jpc_mqstates[25]},
+	{0x0521, 0, &jpc_mqstates[10], &jpc_mqstates[58]},
+	{0x0521, 1, &jpc_mqstates[11], &jpc_mqstates[59]},
+	{0x0221, 0, &jpc_mqstates[76], &jpc_mqstates[66]},
+	{0x0221, 1, &jpc_mqstates[77], &jpc_mqstates[67]},
+	{0x5601, 0, &jpc_mqstates[14], &jpc_mqstates[13]},
+	{0x5601, 1, &jpc_mqstates[15], &jpc_mqstates[12]},
+	{0x5401, 0, &jpc_mqstates[16], &jpc_mqstates[28]},
+	{0x5401, 1, &jpc_mqstates[17], &jpc_mqstates[29]},
+	{0x4801, 0, &jpc_mqstates[18], &jpc_mqstates[28]},
+	{0x4801, 1, &jpc_mqstates[19], &jpc_mqstates[29]},
+	{0x3801, 0, &jpc_mqstates[20], &jpc_mqstates[28]},
+	{0x3801, 1, &jpc_mqstates[21], &jpc_mqstates[29]},
+	{0x3001, 0, &jpc_mqstates[22], &jpc_mqstates[34]},
+	{0x3001, 1, &jpc_mqstates[23], &jpc_mqstates[35]},
+	{0x2401, 0, &jpc_mqstates[24], &jpc_mqstates[36]},
+	{0x2401, 1, &jpc_mqstates[25], &jpc_mqstates[37]},
+	{0x1c01, 0, &jpc_mqstates[26], &jpc_mqstates[40]},
+	{0x1c01, 1, &jpc_mqstates[27], &jpc_mqstates[41]},
+	{0x1601, 0, &jpc_mqstates[58], &jpc_mqstates[42]},
+	{0x1601, 1, &jpc_mqstates[59], &jpc_mqstates[43]},
+	{0x5601, 0, &jpc_mqstates[30], &jpc_mqstates[29]},
+	{0x5601, 1, &jpc_mqstates[31], &jpc_mqstates[28]},
+	{0x5401, 0, &jpc_mqstates[32], &jpc_mqstates[28]},
+	{0x5401, 1, &jpc_mqstates[33], &jpc_mqstates[29]},
+	{0x5101, 0, &jpc_mqstates[34], &jpc_mqstates[30]},
+	{0x5101, 1, &jpc_mqstates[35], &jpc_mqstates[31]},
+	{0x4801, 0, &jpc_mqstates[36], &jpc_mqstates[32]},
+	{0x4801, 1, &jpc_mqstates[37], &jpc_mqstates[33]},
+	{0x3801, 0, &jpc_mqstates[38], &jpc_mqstates[34]},
+	{0x3801, 1, &jpc_mqstates[39], &jpc_mqstates[35]},
+	{0x3401, 0, &jpc_mqstates[40], &jpc_mqstates[36]},
+	{0x3401, 1, &jpc_mqstates[41], &jpc_mqstates[37]},
+	{0x3001, 0, &jpc_mqstates[42], &jpc_mqstates[38]},
+	{0x3001, 1, &jpc_mqstates[43], &jpc_mqstates[39]},
+	{0x2801, 0, &jpc_mqstates[44], &jpc_mqstates[38]},
+	{0x2801, 1, &jpc_mqstates[45], &jpc_mqstates[39]},
+	{0x2401, 0, &jpc_mqstates[46], &jpc_mqstates[40]},
+	{0x2401, 1, &jpc_mqstates[47], &jpc_mqstates[41]},
+	{0x2201, 0, &jpc_mqstates[48], &jpc_mqstates[42]},
+	{0x2201, 1, &jpc_mqstates[49], &jpc_mqstates[43]},
+	{0x1c01, 0, &jpc_mqstates[50], &jpc_mqstates[44]},
+	{0x1c01, 1, &jpc_mqstates[51], &jpc_mqstates[45]},
+	{0x1801, 0, &jpc_mqstates[52], &jpc_mqstates[46]},
+	{0x1801, 1, &jpc_mqstates[53], &jpc_mqstates[47]},
+	{0x1601, 0, &jpc_mqstates[54], &jpc_mqstates[48]},
+	{0x1601, 1, &jpc_mqstates[55], &jpc_mqstates[49]},
+	{0x1401, 0, &jpc_mqstates[56], &jpc_mqstates[50]},
+	{0x1401, 1, &jpc_mqstates[57], &jpc_mqstates[51]},
+	{0x1201, 0, &jpc_mqstates[58], &jpc_mqstates[52]},
+	{0x1201, 1, &jpc_mqstates[59], &jpc_mqstates[53]},
+	{0x1101, 0, &jpc_mqstates[60], &jpc_mqstates[54]},
+	{0x1101, 1, &jpc_mqstates[61], &jpc_mqstates[55]},
+	{0x0ac1, 0, &jpc_mqstates[62], &jpc_mqstates[56]},
+	{0x0ac1, 1, &jpc_mqstates[63], &jpc_mqstates[57]},
+	{0x09c1, 0, &jpc_mqstates[64], &jpc_mqstates[58]},
+	{0x09c1, 1, &jpc_mqstates[65], &jpc_mqstates[59]},
+	{0x08a1, 0, &jpc_mqstates[66], &jpc_mqstates[60]},
+	{0x08a1, 1, &jpc_mqstates[67], &jpc_mqstates[61]},
+	{0x0521, 0, &jpc_mqstates[68], &jpc_mqstates[62]},
+	{0x0521, 1, &jpc_mqstates[69], &jpc_mqstates[63]},
+	{0x0441, 0, &jpc_mqstates[70], &jpc_mqstates[64]},
+	{0x0441, 1, &jpc_mqstates[71], &jpc_mqstates[65]},
+	{0x02a1, 0, &jpc_mqstates[72], &jpc_mqstates[66]},
+	{0x02a1, 1, &jpc_mqstates[73], &jpc_mqstates[67]},
+	{0x0221, 0, &jpc_mqstates[74], &jpc_mqstates[68]},
+	{0x0221, 1, &jpc_mqstates[75], &jpc_mqstates[69]},
+	{0x0141, 0, &jpc_mqstates[76], &jpc_mqstates[70]},
+	{0x0141, 1, &jpc_mqstates[77], &jpc_mqstates[71]},
+	{0x0111, 0, &jpc_mqstates[78], &jpc_mqstates[72]},
+	{0x0111, 1, &jpc_mqstates[79], &jpc_mqstates[73]},
+	{0x0085, 0, &jpc_mqstates[80], &jpc_mqstates[74]},
+	{0x0085, 1, &jpc_mqstates[81], &jpc_mqstates[75]},
+	{0x0049, 0, &jpc_mqstates[82], &jpc_mqstates[76]},
+	{0x0049, 1, &jpc_mqstates[83], &jpc_mqstates[77]},
+	{0x0025, 0, &jpc_mqstates[84], &jpc_mqstates[78]},
+	{0x0025, 1, &jpc_mqstates[85], &jpc_mqstates[79]},
+	{0x0015, 0, &jpc_mqstates[86], &jpc_mqstates[80]},
+	{0x0015, 1, &jpc_mqstates[87], &jpc_mqstates[81]},
+	{0x0009, 0, &jpc_mqstates[88], &jpc_mqstates[82]},
+	{0x0009, 1, &jpc_mqstates[89], &jpc_mqstates[83]},
+	{0x0005, 0, &jpc_mqstates[90], &jpc_mqstates[84]},
+	{0x0005, 1, &jpc_mqstates[91], &jpc_mqstates[85]},
+	{0x0001, 0, &jpc_mqstates[90], &jpc_mqstates[86]},
+	{0x0001, 1, &jpc_mqstates[91], &jpc_mqstates[87]},
+	{0x5601, 0, &jpc_mqstates[92], &jpc_mqstates[92]},
+	{0x5601, 1, &jpc_mqstates[93], &jpc_mqstates[93]},
+};
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_mqcod.h b/converter/other/jpeg2000/libjasper/jpc/jpc_mqcod.h
new file mode 100644
index 00000000..914f8e8a
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_mqcod.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * MQ Arithmetic Coder
+ *
+ * $Id$
+ */
+
+#ifndef JPC_MQCOD_H
+#define JPC_MQCOD_H
+
+/*****************************************************************************\
+* Includes.
+\*****************************************************************************/
+
+#include "pm_c_util.h"
+#include "jasper/jas_types.h"
+
+/*****************************************************************************\
+* Types.
+\*****************************************************************************/
+
+/*
+ * MQ coder context information.
+ */
+
+typedef struct {
+
+	/* The most probable symbol (MPS). */
+	bool mps;
+
+	/* The state index. */
+	int_fast16_t ind;
+
+} jpc_mqctx_t;
+
+/*
+ * MQ coder state table entry.
+ */
+
+typedef struct jpc_mqstate_s {
+
+	/* The Qe value. */
+	uint_fast16_t qeval;
+
+	/* The MPS. */
+	uint_fast16_t mps;
+
+	/* The NMPS state. */
+	struct jpc_mqstate_s *nmps;
+
+	/* The NLPS state. */
+	struct jpc_mqstate_s *nlps;
+
+} jpc_mqstate_t;
+
+/******************************************************************************\
+* Data.
+\******************************************************************************/
+
+/* The state table for the MQ coder. */
+extern jpc_mqstate_t jpc_mqstates[];
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_mqdec.c b/converter/other/jpeg2000/libjasper/jpc/jpc_mqdec.c
new file mode 100644
index 00000000..f58a1c7d
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_mqdec.c
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * MQ Arithmetic Decoder
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "jasper/jas_types.h"
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_math.h"
+#include "jasper/jas_debug.h"
+
+#include "jpc_mqdec.h"
+
+/******************************************************************************\
+* Local macros.
+\******************************************************************************/
+
+#if defined(DEBUG)
+#define	MQDEC_CALL(n, x) \
+	((jas_getdbglevel() >= (n)) ? ((void)(x)) : ((void)0))
+#else
+#define	MQDEC_CALL(n, x)
+#endif
+
+/******************************************************************************\
+* Local function prototypes.
+\******************************************************************************/
+
+static void jpc_mqdec_bytein(jpc_mqdec_t *mqdec);
+
+/******************************************************************************\
+* Code for creation and destruction of a MQ decoder.
+\******************************************************************************/
+
+/* Create a MQ decoder. */
+jpc_mqdec_t *jpc_mqdec_create(int maxctxs, jas_stream_t *in)
+{
+	jpc_mqdec_t *mqdec;
+
+	/* There must be at least one context. */
+	assert(maxctxs > 0);
+
+	/* Allocate memory for the MQ decoder. */
+	if (!(mqdec = jas_malloc(sizeof(jpc_mqdec_t)))) {
+		goto error;
+	}
+	mqdec->in = in;
+	mqdec->maxctxs = maxctxs;
+	/* Allocate memory for the per-context state information. */
+	if (!(mqdec->ctxs = jas_malloc(mqdec->maxctxs * sizeof(jpc_mqstate_t *)))) {
+		goto error;
+	}
+	/* Set the current context to the first context. */
+	mqdec->curctx = mqdec->ctxs;
+
+	/* If an input stream has been associated with the MQ decoder,
+	  initialize the decoder state from the stream. */
+	if (mqdec->in) {
+		jpc_mqdec_init(mqdec);
+	}
+	/* Initialize the per-context state information. */
+	jpc_mqdec_setctxs(mqdec, 0, 0);
+
+	return mqdec;
+
+error:
+	/* Oops...  Something has gone wrong. */
+	if (mqdec) {
+		jpc_mqdec_destroy(mqdec);
+	}
+	return 0;
+}
+
+/* Destroy a MQ decoder. */
+void jpc_mqdec_destroy(jpc_mqdec_t *mqdec)
+{
+	if (mqdec->ctxs) {
+		jas_free(mqdec->ctxs);
+	}
+	jas_free(mqdec);
+}
+
+/******************************************************************************\
+* Code for initialization of a MQ decoder.
+\******************************************************************************/
+
+/* Initialize the state of a MQ decoder. */
+
+void jpc_mqdec_init(jpc_mqdec_t *mqdec)
+{
+	int c;
+
+	mqdec->eof = 0;
+	mqdec->creg = 0;
+	/* Get the next byte from the input stream. */
+	if ((c = jas_stream_getc(mqdec->in)) == EOF) {
+		/* We have encountered an I/O error or EOF. */
+		c = 0xff;
+		mqdec->eof = 1;
+	}
+	mqdec->inbuffer = c;
+	mqdec->creg += mqdec->inbuffer << 16;
+	jpc_mqdec_bytein(mqdec);
+	mqdec->creg <<= 7;
+	mqdec->ctreg -= 7;
+	mqdec->areg = 0x8000;
+}
+
+/* Set the input stream for a MQ decoder. */
+
+void jpc_mqdec_setinput(jpc_mqdec_t *mqdec, jas_stream_t *in)
+{
+	mqdec->in = in;
+}
+
+/* Initialize one or more contexts. */
+
+void jpc_mqdec_setctxs(jpc_mqdec_t *mqdec, int numctxs, jpc_mqctx_t *ctxs)
+{
+	jpc_mqstate_t **ctx;
+	int n;
+
+	ctx = mqdec->ctxs;
+	n = JAS_MIN(mqdec->maxctxs, numctxs);
+	while (--n >= 0) {
+		*ctx = &jpc_mqstates[2 * ctxs->ind + ctxs->mps];
+		++ctx;
+		++ctxs;
+	}
+	n = mqdec->maxctxs - numctxs;
+	while (--n >= 0) {
+		*ctx = &jpc_mqstates[0];
+		++ctx;
+	}
+}
+
+/* Initialize a context. */
+
+void jpc_mqdec_setctx(jpc_mqdec_t *mqdec, int ctxno, jpc_mqctx_t *ctx)
+{
+	jpc_mqstate_t **ctxi;
+	ctxi = &mqdec->ctxs[ctxno];
+	*ctxi = &jpc_mqstates[2 * ctx->ind + ctx->mps];
+}
+
+/******************************************************************************\
+* Code for decoding a bit.
+\******************************************************************************/
+
+/* Decode a bit. */
+
+int jpc_mqdec_getbit_func(register jpc_mqdec_t *mqdec)
+{
+	int bit;
+	JAS_DBGLOG(100, ("jpc_mqdec_getbit_func(%p)\n", mqdec));
+	MQDEC_CALL(100, jpc_mqdec_dump(mqdec, stderr));
+	bit = jpc_mqdec_getbit_macro(mqdec);
+	MQDEC_CALL(100, jpc_mqdec_dump(mqdec, stderr));
+	JAS_DBGLOG(100, ("ctx = %d, decoded %d\n", mqdec->curctx -
+	  mqdec->ctxs, bit));
+	return bit;
+}
+
+/* Apply MPS_EXCHANGE algorithm (with RENORMD). */
+int jpc_mqdec_mpsexchrenormd(register jpc_mqdec_t *mqdec)
+{
+	int ret;
+	register jpc_mqstate_t *state = *mqdec->curctx;
+	jpc_mqdec_mpsexchange(mqdec->areg, state->qeval, mqdec->curctx, ret);
+	jpc_mqdec_renormd(mqdec->areg, mqdec->creg, mqdec->ctreg, mqdec->in,
+	  mqdec->eof, mqdec->inbuffer);
+	return ret;
+}
+
+/* Apply LPS_EXCHANGE algorithm (with RENORMD). */
+int jpc_mqdec_lpsexchrenormd(register jpc_mqdec_t *mqdec)
+{
+	int ret;
+	register jpc_mqstate_t *state = *mqdec->curctx;
+	jpc_mqdec_lpsexchange(mqdec->areg, state->qeval, mqdec->curctx, ret);
+	jpc_mqdec_renormd(mqdec->areg, mqdec->creg, mqdec->ctreg, mqdec->in,
+	  mqdec->eof, mqdec->inbuffer);
+	return ret;
+}
+
+/******************************************************************************\
+* Support code.
+\******************************************************************************/
+
+/* Apply the BYTEIN algorithm. */
+static void jpc_mqdec_bytein(jpc_mqdec_t *mqdec)
+{
+	int c;
+	unsigned char prevbuf;
+
+	if (!mqdec->eof) {
+		if ((c = jas_stream_getc(mqdec->in)) == EOF) {
+			mqdec->eof = 1;
+			c = 0xff;
+		}
+		prevbuf = mqdec->inbuffer;
+		mqdec->inbuffer = c;
+		if (prevbuf == 0xff) {
+			if (c > 0x8f) {
+				mqdec->creg += 0xff00;
+				mqdec->ctreg = 8;
+			} else {
+				mqdec->creg += c << 9;
+				mqdec->ctreg = 7;
+			}
+		} else {
+			mqdec->creg += c << 8;
+			mqdec->ctreg = 8;
+		}
+	} else {
+		mqdec->creg += 0xff00;
+		mqdec->ctreg = 8;
+	}
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_mqdec.h b/converter/other/jpeg2000/libjasper/jpc/jpc_mqdec.h
new file mode 100644
index 00000000..30185506
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_mqdec.h
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * MQ Arithmetic Decoder
+ *
+ * $Id$
+ */
+
+#ifndef JPC_MQDEC_H
+#define JPC_MQDEC_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_types.h"
+#include "jasper/jas_stream.h"
+
+#include "jpc_mqcod.h"
+
+/******************************************************************************\
+* Types.
+\******************************************************************************/
+
+/* MQ arithmetic decoder. */
+
+typedef struct {
+
+	/* The C register. */
+	uint_fast32_t creg;
+
+	/* The A register. */
+	uint_fast32_t areg;
+
+	/* The CT register. */
+	uint_fast32_t ctreg;
+
+	/* The current context. */
+	jpc_mqstate_t **curctx;
+
+	/* The per-context information. */
+	jpc_mqstate_t **ctxs;
+
+	/* The maximum number of contexts. */
+	int maxctxs;
+
+	/* The stream from which to read data. */
+	jas_stream_t *in;
+
+	/* The last character read. */
+	unsigned char inbuffer;
+
+	/* The EOF indicator. */
+	int eof;
+
+} jpc_mqdec_t;
+
+/******************************************************************************\
+* Functions/macros for construction and destruction.
+\******************************************************************************/
+
+/* Create a MQ decoder. */
+jpc_mqdec_t *jpc_mqdec_create(int maxctxs, jas_stream_t *in);
+
+/* Destroy a MQ decoder. */
+void jpc_mqdec_destroy(jpc_mqdec_t *dec);
+
+/******************************************************************************\
+* Functions/macros for initialization.
+\******************************************************************************/
+
+/* Set the input stream associated with a MQ decoder. */
+void jpc_mqdec_setinput(jpc_mqdec_t *dec, jas_stream_t *in);
+
+/* Initialize a MQ decoder. */
+void jpc_mqdec_init(jpc_mqdec_t *dec);
+
+/******************************************************************************\
+* Functions/macros for manipulating contexts.
+\******************************************************************************/
+
+/* Set the current context for a MQ decoder. */
+#define	jpc_mqdec_setcurctx(dec, ctxno) \
+	((mqdec)->curctx = &(mqdec)->ctxs[ctxno]);
+
+/* Set the state information for a particular context of a MQ decoder. */
+void jpc_mqdec_setctx(jpc_mqdec_t *dec, int ctxno, jpc_mqctx_t *ctx);
+
+/* Set the state information for all contexts of a MQ decoder. */
+void jpc_mqdec_setctxs(jpc_mqdec_t *dec, int numctxs, jpc_mqctx_t *ctxs);
+
+/******************************************************************************\
+* Functions/macros for decoding bits.
+\******************************************************************************/
+
+/* Decode a symbol. */
+#if !defined(DEBUG)
+#define	jpc_mqdec_getbit(dec) \
+	jpc_mqdec_getbit_macro(dec)
+#else
+#define	jpc_mqdec_getbit(dec) \
+	jpc_mqdec_getbit_func(dec)
+#endif
+
+/* Decode a symbol (assuming an unskewed probability distribution). */
+#if !defined(DEBUG)
+#define	jpc_mqdec_getbitnoskew(dec) \
+	jpc_mqdec_getbit_macro(dec)
+#else
+#define	jpc_mqdec_getbitnoskew(dec) \
+	jpc_mqdec_getbit_func(dec)
+#endif
+
+/******************************************************************************\
+* Functions/macros for debugging.
+\******************************************************************************/
+
+/* Dump the MQ decoder state for debugging. */
+void mqdec_dump(jpc_mqdec_t *dec, FILE *out);
+
+/******************************************************************************\
+* EVERYTHING BELOW THIS POINT IS IMPLEMENTATION SPECIFIC AND NOT PART OF THE
+* APPLICATION INTERFACE.  DO NOT RELY ON ANY OF THE INTERNAL FUNCTIONS/MACROS
+* GIVEN BELOW.
+\******************************************************************************/
+
+#define	jpc_mqdec_getbit_macro(dec) \
+	((((dec)->areg -= (*(dec)->curctx)->qeval), \
+	  (dec)->creg >> 16 >= (*(dec)->curctx)->qeval) ? \
+	  ((((dec)->creg -= (*(dec)->curctx)->qeval << 16), \
+	  (dec)->areg & 0x8000) ?  (*(dec)->curctx)->mps : \
+	  jpc_mqdec_mpsexchrenormd(dec)) : \
+	  jpc_mqdec_lpsexchrenormd(dec))
+
+#define	jpc_mqdec_mpsexchange(areg, delta, curctx, bit) \
+{ \
+	if ((areg) < (delta)) { \
+		register jpc_mqstate_t *state = *(curctx); \
+		/* LPS decoded. */ \
+		(bit) = state->mps ^ 1; \
+		*(curctx) = state->nlps; \
+	} else { \
+		register jpc_mqstate_t *state = *(curctx); \
+		/* MPS decoded. */ \
+		(bit) = state->mps; \
+		*(curctx) = state->nmps; \
+	} \
+}
+
+#define	jpc_mqdec_lpsexchange(areg, delta, curctx, bit) \
+{ \
+	if ((areg) >= (delta)) { \
+		register jpc_mqstate_t *state = *(curctx); \
+		(areg) = (delta); \
+		(bit) = state->mps ^ 1; \
+		*(curctx) = state->nlps; \
+	} else { \
+		register jpc_mqstate_t *state = *(curctx); \
+		(areg) = (delta); \
+		(bit) = state->mps; \
+		*(curctx) = state->nmps; \
+	} \
+}
+
+#define	jpc_mqdec_renormd(areg, creg, ctreg, in, eof, inbuf) \
+{ \
+	do { \
+		if (!(ctreg)) { \
+			jpc_mqdec_bytein2(creg, ctreg, in, eof, inbuf); \
+		} \
+		(areg) <<= 1; \
+		(creg) <<= 1; \
+		--(ctreg); \
+	} while (!((areg) & 0x8000)); \
+}
+
+#define	jpc_mqdec_bytein2(creg, ctreg, in, eof, inbuf) \
+{ \
+	int c; \
+	unsigned char prevbuf; \
+	if (!(eof)) { \
+		if ((c = jas_stream_getc(in)) == EOF) { \
+			(eof) = 1; \
+			c = 0xff; \
+		} \
+		prevbuf = (inbuf); \
+		(inbuf) = c; \
+		if (prevbuf == 0xff) { \
+			if (c > 0x8f) { \
+				(creg) += 0xff00; \
+				(ctreg) = 8; \
+			} else { \
+				(creg) += c << 9; \
+				(ctreg) = 7; \
+			} \
+		} else { \
+			(creg) += c << 8; \
+			(ctreg) = 8; \
+		} \
+	} else { \
+		(creg) += 0xff00; \
+		(ctreg) = 8; \
+	} \
+}
+
+int jpc_mqdec_getbit_func(jpc_mqdec_t *dec);
+int jpc_mqdec_mpsexchrenormd(jpc_mqdec_t *dec);
+int jpc_mqdec_lpsexchrenormd(jpc_mqdec_t *dec);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_mqenc.c b/converter/other/jpeg2000/libjasper/jpc/jpc_mqenc.c
new file mode 100644
index 00000000..0219a000
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_mqenc.c
@@ -0,0 +1,441 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * MQ Arithmetic Encoder
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "jasper/jas_stream.h"
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_math.h"
+#include "jasper/jas_debug.h"
+
+#include "jpc_mqenc.h"
+
+/******************************************************************************\
+* Macros
+\******************************************************************************/
+
+#if defined(DEBUG)
+#define	JPC_MQENC_CALL(n, x) \
+	((jas_getdbglevel() >= (n)) ? ((void)(x)) : ((void)0))
+#else
+#define	JPC_MQENC_CALL(n, x)
+#endif
+
+#define	jpc_mqenc_codemps9(areg, creg, ctreg, curctx, enc) \
+{ \
+	jpc_mqstate_t *state = *(curctx); \
+	(areg) -= state->qeval; \
+	if (!((areg) & 0x8000)) { \
+		if ((areg) < state->qeval) { \
+			(areg) = state->qeval; \
+		} else { \
+			(creg) += state->qeval; \
+		} \
+		*(curctx) = state->nmps; \
+		jpc_mqenc_renorme((areg), (creg), (ctreg), (enc)); \
+	} else { \
+		(creg) += state->qeval; \
+	} \
+}
+
+#define	jpc_mqenc_codelps2(areg, creg, ctreg, curctx, enc) \
+{ \
+	jpc_mqstate_t *state = *(curctx); \
+	(areg) -= state->qeval; \
+	if ((areg) < state->qeval) { \
+		(creg) += state->qeval; \
+	} else { \
+		(areg) = state->qeval; \
+	} \
+	*(curctx) = state->nlps; \
+	jpc_mqenc_renorme((areg), (creg), (ctreg), (enc)); \
+}
+
+#define	jpc_mqenc_renorme(areg, creg, ctreg, enc) \
+{ \
+	do { \
+		(areg) <<= 1; \
+		(creg) <<= 1; \
+		if (!--(ctreg)) { \
+			jpc_mqenc_byteout((areg), (creg), (ctreg), (enc)); \
+		} \
+	} while (!((areg) & 0x8000)); \
+}
+
+#define	jpc_mqenc_byteout(areg, creg, ctreg, enc) \
+{ \
+	if ((enc)->outbuf != 0xff) { \
+		if ((creg) & 0x8000000) { \
+			if (++((enc)->outbuf) == 0xff) { \
+				(creg) &= 0x7ffffff; \
+				jpc_mqenc_byteout2(enc); \
+				enc->outbuf = ((creg) >> 20) & 0xff; \
+				(creg) &= 0xfffff; \
+				(ctreg) = 7; \
+			} else { \
+				jpc_mqenc_byteout2(enc); \
+				enc->outbuf = ((creg) >> 19) & 0xff; \
+				(creg) &= 0x7ffff; \
+				(ctreg) = 8; \
+			} \
+		} else { \
+			jpc_mqenc_byteout2(enc); \
+			(enc)->outbuf = ((creg) >> 19) & 0xff; \
+			(creg) &= 0x7ffff; \
+			(ctreg) = 8; \
+		} \
+	} else { \
+		jpc_mqenc_byteout2(enc); \
+		(enc)->outbuf = ((creg) >> 20) & 0xff; \
+		(creg) &= 0xfffff; \
+		(ctreg) = 7; \
+	} \
+}
+
+#define	jpc_mqenc_byteout2(enc) \
+{ \
+	if (enc->outbuf >= 0) { \
+		if (jas_stream_putc(enc->out, (unsigned char)enc->outbuf) == EOF) { \
+			enc->err |= 1; \
+		} \
+	} \
+	enc->lastbyte = enc->outbuf; \
+}
+
+/******************************************************************************\
+* Local function protoypes.
+\******************************************************************************/
+
+static void jpc_mqenc_setbits(jpc_mqenc_t *mqenc);
+
+/******************************************************************************\
+* Code for creation and destruction of encoder.
+\******************************************************************************/
+
+/* Create a MQ encoder. */
+
+jpc_mqenc_t *jpc_mqenc_create(int maxctxs, jas_stream_t *out)
+{
+	jpc_mqenc_t *mqenc;
+
+	/* Allocate memory for the MQ encoder. */
+	if (!(mqenc = jas_malloc(sizeof(jpc_mqenc_t)))) {
+		goto error;
+	}
+	mqenc->out = out;
+	mqenc->maxctxs = maxctxs;
+
+	/* Allocate memory for the per-context state information. */
+	if (!(mqenc->ctxs = jas_malloc(mqenc->maxctxs * sizeof(jpc_mqstate_t *)))) {
+		goto error;
+	}
+
+	/* Set the current context to the first one. */
+	mqenc->curctx = mqenc->ctxs;
+
+	jpc_mqenc_init(mqenc);
+
+	/* Initialize the per-context state information to something sane. */
+	jpc_mqenc_setctxs(mqenc, 0, 0);
+
+	return mqenc;
+
+error:
+	if (mqenc) {
+		jpc_mqenc_destroy(mqenc);
+	}
+	return 0;
+}
+
+/* Destroy a MQ encoder. */
+
+void jpc_mqenc_destroy(jpc_mqenc_t *mqenc)
+{
+	if (mqenc->ctxs) {
+		jas_free(mqenc->ctxs);
+	}
+	jas_free(mqenc);
+}
+
+/******************************************************************************\
+* State initialization code.
+\******************************************************************************/
+
+/* Initialize the coding state of a MQ encoder. */
+
+void jpc_mqenc_init(jpc_mqenc_t *mqenc)
+{
+	mqenc->areg = 0x8000;
+	mqenc->outbuf = -1;
+	mqenc->creg = 0;
+	mqenc->ctreg = 12;
+	mqenc->lastbyte = -1;
+	mqenc->err = 0;
+}
+
+/* Initialize one or more contexts. */
+
+void jpc_mqenc_setctxs(jpc_mqenc_t *mqenc, int numctxs, jpc_mqctx_t *ctxs)
+{
+	jpc_mqstate_t **ctx;
+	int n;
+
+	ctx = mqenc->ctxs;
+	n = JAS_MIN(mqenc->maxctxs, numctxs);
+	while (--n >= 0) {
+		*ctx = &jpc_mqstates[2 * ctxs->ind + ctxs->mps];
+		++ctx;
+		++ctxs;
+	}
+	n = mqenc->maxctxs - numctxs;
+	while (--n >= 0) {
+		*ctx = &jpc_mqstates[0];
+		++ctx;
+	}
+
+}
+
+/* Get the coding state for a MQ encoder. */
+
+void jpc_mqenc_getstate(jpc_mqenc_t *mqenc, jpc_mqencstate_t *state)
+{
+	state->areg = mqenc->areg;
+	state->creg = mqenc->creg;
+	state->ctreg = mqenc->ctreg;
+	state->lastbyte = mqenc->lastbyte;
+}
+
+/******************************************************************************\
+* Code for coding symbols.
+\******************************************************************************/
+
+/* Encode a bit. */
+
+int jpc_mqenc_putbit_func(jpc_mqenc_t *mqenc, int bit)
+{
+	const jpc_mqstate_t *state;
+	JAS_DBGLOG(100, ("jpc_mqenc_putbit(%p, %d)\n", mqenc, bit));
+	JPC_MQENC_CALL(100, jpc_mqenc_dump(mqenc, stderr));
+
+	state = *(mqenc->curctx);
+
+	if (state->mps == bit) {
+		/* Apply the CODEMPS algorithm as defined in the standard. */
+		mqenc->areg -= state->qeval;
+		if (!(mqenc->areg & 0x8000)) {
+			jpc_mqenc_codemps2(mqenc);
+		} else {
+			mqenc->creg += state->qeval;
+		}
+	} else {
+		/* Apply the CODELPS algorithm as defined in the standard. */
+		jpc_mqenc_codelps2(mqenc->areg, mqenc->creg, mqenc->ctreg, mqenc->curctx, mqenc);
+	}
+
+	return jpc_mqenc_error(mqenc) ? (-1) : 0;
+}
+
+int jpc_mqenc_codemps2(jpc_mqenc_t *mqenc)
+{
+	/* Note: This function only performs part of the work associated with
+	the CODEMPS algorithm from the standard.  Some of the work is also
+	performed by the caller. */
+
+	jpc_mqstate_t *state = *(mqenc->curctx);
+	if (mqenc->areg < state->qeval) {
+		mqenc->areg = state->qeval;
+	} else {
+		mqenc->creg += state->qeval;
+	}
+	*mqenc->curctx = state->nmps;
+	jpc_mqenc_renorme(mqenc->areg, mqenc->creg, mqenc->ctreg, mqenc);
+	return jpc_mqenc_error(mqenc) ? (-1) : 0;
+}
+
+int jpc_mqenc_codelps(jpc_mqenc_t *mqenc)
+{
+	jpc_mqenc_codelps2(mqenc->areg, mqenc->creg, mqenc->ctreg, mqenc->curctx, mqenc);
+	return jpc_mqenc_error(mqenc) ? (-1) : 0;
+}
+
+/******************************************************************************\
+* Miscellaneous code.
+\******************************************************************************/
+
+/* Terminate the code word. */
+
+int jpc_mqenc_flush(jpc_mqenc_t *mqenc, int termmode)
+{
+	int_fast16_t k;
+
+	switch (termmode) {
+	case JPC_MQENC_PTERM:
+		k = 11 - mqenc->ctreg + 1;
+		while (k > 0) {
+			mqenc->creg <<= mqenc->ctreg;
+			mqenc->ctreg = 0;
+			jpc_mqenc_byteout(mqenc->areg, mqenc->creg, mqenc->ctreg,
+			  mqenc);
+			k -= mqenc->ctreg;
+		}
+		if (mqenc->outbuf != 0xff) {
+			jpc_mqenc_byteout(mqenc->areg, mqenc->creg, mqenc->ctreg, mqenc);
+		}
+		break;
+	case JPC_MQENC_DEFTERM:
+		jpc_mqenc_setbits(mqenc);
+		mqenc->creg <<= mqenc->ctreg;
+		jpc_mqenc_byteout(mqenc->areg, mqenc->creg, mqenc->ctreg, mqenc);
+		mqenc->creg <<= mqenc->ctreg;
+		jpc_mqenc_byteout(mqenc->areg, mqenc->creg, mqenc->ctreg, mqenc);
+		if (mqenc->outbuf != 0xff) {
+			jpc_mqenc_byteout(mqenc->areg, mqenc->creg, mqenc->ctreg, mqenc);
+		}
+		break;
+	default:
+		abort();
+		break;
+	}
+	return 0;
+}
+
+static void jpc_mqenc_setbits(jpc_mqenc_t *mqenc)
+{
+	uint_fast32_t tmp = mqenc->creg + mqenc->areg;
+	mqenc->creg |= 0xffff;
+	if (mqenc->creg >= tmp) {
+		mqenc->creg -= 0x8000;
+	}
+}
+
+/* Dump a MQ encoder to a stream for debugging. */
+
+int jpc_mqenc_dump(jpc_mqenc_t *mqenc, FILE *out)
+{
+	fprintf(out, "AREG = %08x, CREG = %08x, CTREG = %d\n",
+	  mqenc->areg, mqenc->creg, mqenc->ctreg);
+	fprintf(out, "IND = %02d, MPS = %d, QEVAL = %04x\n",
+	  *mqenc->curctx - jpc_mqstates, (*mqenc->curctx)->mps,
+	  (*mqenc->curctx)->qeval);
+	return 0;
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_mqenc.h b/converter/other/jpeg2000/libjasper/jpc/jpc_mqenc.h
new file mode 100644
index 00000000..1421ae4d
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_mqenc.h
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * MQ Arithmetic Encoder
+ *
+ * $Id$
+ */
+
+#ifndef JPC_MQENC_H
+#define JPC_MQENC_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_types.h"
+#include "jasper/jas_stream.h"
+
+#include "jpc_mqcod.h"
+
+/******************************************************************************\
+* Constants.
+\******************************************************************************/
+
+/*
+ * Termination modes.
+ */
+
+#define	JPC_MQENC_DEFTERM	0	/* default termination */
+#define	JPC_MQENC_PTERM		1	/* predictable termination */
+
+/******************************************************************************\
+* Types.
+\******************************************************************************/
+
+/* MQ arithmetic encoder class. */
+
+typedef struct {
+
+	/* The C register. */
+	uint_fast32_t creg;
+
+	/* The A register. */
+	uint_fast32_t areg;
+
+	/* The CT register. */
+	uint_fast32_t ctreg;
+
+	/* The maximum number of contexts. */
+	int maxctxs;
+
+	/* The per-context information. */
+	jpc_mqstate_t **ctxs;
+
+	/* The current context. */
+	jpc_mqstate_t **curctx;
+
+	/* The stream for encoder output. */
+	jas_stream_t *out;
+
+	/* The byte buffer (i.e., the B variable in the standard). */
+	int_fast16_t outbuf;
+
+	/* The last byte output. */
+	int_fast16_t lastbyte;
+
+	/* The error indicator. */
+	int err;
+	
+} jpc_mqenc_t;
+
+/* MQ arithmetic encoder state information. */
+
+typedef struct {
+
+	/* The A register. */
+	unsigned areg;
+
+	/* The C register. */
+	unsigned creg;
+
+	/* The CT register. */
+	unsigned ctreg;
+
+	/* The last byte output by the encoder. */
+	int lastbyte;
+
+} jpc_mqencstate_t;
+
+/******************************************************************************\
+* Functions/macros for construction and destruction.
+\******************************************************************************/
+
+/* Create a MQ encoder. */
+jpc_mqenc_t *jpc_mqenc_create(int maxctxs, jas_stream_t *out);
+
+/* Destroy a MQ encoder. */
+void jpc_mqenc_destroy(jpc_mqenc_t *enc);
+
+/******************************************************************************\
+* Functions/macros for initialization.
+\******************************************************************************/
+
+/* Initialize a MQ encoder. */
+void jpc_mqenc_init(jpc_mqenc_t *enc);
+
+/******************************************************************************\
+* Functions/macros for context manipulation.
+\******************************************************************************/
+
+/* Set the current context. */
+#define	jpc_mqenc_setcurctx(enc, ctxno) \
+        ((enc)->curctx = &(enc)->ctxs[ctxno]);
+
+/* Set the state information for a particular context. */
+void jpc_mqenc_setctx(jpc_mqenc_t *enc, int ctxno, jpc_mqctx_t *ctx);
+
+/* Set the state information for multiple contexts. */
+void jpc_mqenc_setctxs(jpc_mqenc_t *enc, int numctxs, jpc_mqctx_t *ctxs);
+
+/******************************************************************************\
+* Miscellaneous functions/macros.
+\******************************************************************************/
+
+/* Get the error state of a MQ encoder. */
+#define	jpc_mqenc_error(enc) \
+	((enc)->err)
+
+/* Get the current encoder state. */
+void jpc_mqenc_getstate(jpc_mqenc_t *enc, jpc_mqencstate_t *state);
+
+/* Terminate the code. */
+int jpc_mqenc_flush(jpc_mqenc_t *enc, int termmode);
+
+/******************************************************************************\
+* Functions/macros for encoding bits.
+\******************************************************************************/
+
+/* Encode a bit. */
+#if !defined(DEBUG)
+#define	jpc_mqenc_putbit(enc, bit)	jpc_mqenc_putbit_macro(enc, bit)
+#else
+#define	jpc_mqenc_putbit(enc, bit)	jpc_mqenc_putbit_func(enc, bit)
+#endif
+
+/******************************************************************************\
+* Functions/macros for debugging.
+\******************************************************************************/
+
+int jpc_mqenc_dump(jpc_mqenc_t *mqenc, FILE *out);
+
+/******************************************************************************\
+* Implementation-specific details.
+\******************************************************************************/
+
+/* Note: This macro is included only to satisfy the needs of
+  the mqenc_putbit macro. */
+#define	jpc_mqenc_putbit_macro(enc, bit) \
+	(((*((enc)->curctx))->mps == (bit)) ? \
+	  (((enc)->areg -= (*(enc)->curctx)->qeval), \
+	  ((!((enc)->areg & 0x8000)) ? (jpc_mqenc_codemps2(enc)) : \
+	  ((enc)->creg += (*(enc)->curctx)->qeval))) : \
+	  jpc_mqenc_codelps(enc))
+
+/* Note: These function prototypes are included only to satisfy the
+  needs of the mqenc_putbit_macro macro.  Do not call any of these
+  functions directly. */
+int jpc_mqenc_codemps2(jpc_mqenc_t *enc);
+int jpc_mqenc_codelps(jpc_mqenc_t *enc);
+
+/* Note: This function prototype is included only to satisfy the needs of
+  the mqenc_putbit macro. */
+int jpc_mqenc_putbit_func(jpc_mqenc_t *enc, int bit);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_qmfb.c b/converter/other/jpeg2000/libjasper/jpc/jpc_qmfb.c
new file mode 100644
index 00000000..1ed0dd90
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_qmfb.c
@@ -0,0 +1,1125 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Quadrature Mirror-Image Filter Bank (QMFB) Library
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <assert.h>
+
+#include "jasper/jas_fix.h"
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_math.h"
+
+#include "jpc_qmfb.h"
+#include "jpc_tsfb.h"
+#include "jpc_math.h"
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+static jpc_qmfb1d_t *jpc_qmfb1d_create(void);
+
+static int jpc_ft_getnumchans(jpc_qmfb1d_t *qmfb);
+static int jpc_ft_getanalfilters(jpc_qmfb1d_t *qmfb, int len, jas_seq2d_t **filters);
+static int jpc_ft_getsynfilters(jpc_qmfb1d_t *qmfb, int len, jas_seq2d_t **filters);
+static void jpc_ft_analyze(jpc_qmfb1d_t *qmfb, int flags, jas_seq2d_t *x);
+static void jpc_ft_synthesize(jpc_qmfb1d_t *qmfb, int flags, jas_seq2d_t *x);
+
+static int jpc_ns_getnumchans(jpc_qmfb1d_t *qmfb);
+static int jpc_ns_getanalfilters(jpc_qmfb1d_t *qmfb, int len, jas_seq2d_t **filters);
+static int jpc_ns_getsynfilters(jpc_qmfb1d_t *qmfb, int len, jas_seq2d_t **filters);
+static void jpc_ns_analyze(jpc_qmfb1d_t *qmfb, int flags, jas_seq2d_t *x);
+static void jpc_ns_synthesize(jpc_qmfb1d_t *qmfb, int flags, jas_seq2d_t *x);
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+jpc_qmfb1dops_t jpc_ft_ops = {
+	jpc_ft_getnumchans,
+	jpc_ft_getanalfilters,
+	jpc_ft_getsynfilters,
+	jpc_ft_analyze,
+	jpc_ft_synthesize
+};
+
+jpc_qmfb1dops_t jpc_ns_ops = {
+	jpc_ns_getnumchans,
+	jpc_ns_getanalfilters,
+	jpc_ns_getsynfilters,
+	jpc_ns_analyze,
+	jpc_ns_synthesize
+};
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+static void jpc_qmfb1d_setup(jpc_fix_t *startptr, int startind, int endind,
+  int intrastep, jpc_fix_t **lstartptr, int *lstartind, int *lendind,
+  jpc_fix_t **hstartptr, int *hstartind, int *hendind)
+{
+	*lstartind = JPC_CEILDIVPOW2(startind, 1);
+	*lendind = JPC_CEILDIVPOW2(endind, 1);
+	*hstartind = JPC_FLOORDIVPOW2(startind, 1);
+	*hendind = JPC_FLOORDIVPOW2(endind, 1);
+	*lstartptr = startptr;
+	*hstartptr = &startptr[(*lendind - *lstartind) * intrastep];
+}
+
+static void jpc_qmfb1d_split(jpc_fix_t *startptr, int startind, int endind,
+  register int step, jpc_fix_t *lstartptr, int lstartind, int lendind,
+  jpc_fix_t *hstartptr, int hstartind, int hendind)
+{
+#define QMFB_SPLITBUFSIZE 4096
+	jpc_fix_t splitbuf[QMFB_SPLITBUFSIZE];
+	jpc_fix_t *buf = splitbuf;
+	int llen;
+	int hlen;
+	int twostep;
+	jpc_fix_t *tmpptr;
+	register jpc_fix_t *ptr;
+	register jpc_fix_t *hptr;
+	register jpc_fix_t *lptr;
+	register int n;
+	int state;
+
+	twostep = step << 1;
+	llen = lendind - lstartind;
+	hlen = hendind - hstartind;
+
+#if defined(WIN32)
+	/* Get a buffer. */
+	if (bufsize > QMFB_SPLITBUFSIZE) {
+		if (!(buf = jas_malloc(bufsize * sizeof(jpc_fix_t)))) {
+			/* We have no choice but to commit suicide in this case. */
+			abort();
+		}
+	}
+#endif
+
+	if (hstartind < lstartind) {
+		/* The first sample in the input signal is to appear
+		  in the highpass subband signal. */
+		/* Copy the appropriate samples into the lowpass subband
+		  signal, saving any samples destined for the highpass subband
+		  signal as they are overwritten. */
+		tmpptr = buf;
+		ptr = &startptr[step];
+		lptr = lstartptr;
+		n = llen;
+		state = 1;
+		while (n-- > 0) {
+			if (state) {
+				*tmpptr = *lptr;
+				++tmpptr;
+			}
+			*lptr = *ptr;
+			ptr += twostep;
+			lptr += step;
+			state ^= 1;
+		}
+		/* Copy the appropriate samples into the highpass subband
+		  signal. */
+		/* Handle the nonoverwritten samples. */
+		hptr = &hstartptr[(hlen - 1) * step];
+		ptr = &startptr[(((llen + hlen - 1) >> 1) << 1) * step];
+		n = hlen - (tmpptr - buf);
+		while (n-- > 0) {
+			*hptr = *ptr;
+			hptr -= step;
+			ptr -= twostep;
+		}
+		/* Handle the overwritten samples. */
+		n = tmpptr - buf;
+		while (n-- > 0) {
+			--tmpptr;
+			*hptr = *tmpptr;
+			hptr -= step;
+		}
+	} else {
+		/* The first sample in the input signal is to appear
+		  in the lowpass subband signal. */
+		/* Copy the appropriate samples into the lowpass subband
+		  signal, saving any samples for the highpass subband
+		  signal as they are overwritten. */
+		state = 0;
+		ptr = startptr;
+		lptr = lstartptr;
+		tmpptr = buf;
+		n = llen;
+		while (n-- > 0) {
+			if (state) {
+				*tmpptr = *lptr;
+				++tmpptr;
+			}
+			*lptr = *ptr;
+			ptr += twostep;
+			lptr += step;
+			state ^= 1;
+		}
+		/* Copy the appropriate samples into the highpass subband
+		  signal. */
+		/* Handle the nonoverwritten samples. */
+		ptr = &startptr[((((llen + hlen) >> 1) << 1) - 1) * step];
+		hptr = &hstartptr[(hlen - 1) * step];
+		n = hlen - (tmpptr - buf);
+		while (n-- > 0) {
+			*hptr = *ptr;
+			ptr -= twostep;
+			hptr -= step;
+		}
+		/* Handle the overwritten samples. */
+		n = tmpptr - buf;
+		while (n-- > 0) {
+			--tmpptr;
+			*hptr = *tmpptr;
+			hptr -= step;
+		}
+	}
+
+#if defined(WIN32)
+	/* If the split buffer was allocated on the heap, free this memory. */
+	if (buf != splitbuf) {
+		jas_free(buf);
+	}
+#endif
+}
+
+static void jpc_qmfb1d_join(jpc_fix_t *startptr, int startind, int endind,
+  register int step, jpc_fix_t *lstartptr, int lstartind, int lendind,
+  jpc_fix_t *hstartptr, int hstartind, int hendind)
+{
+#define	QMFB_JOINBUFSIZE	4096
+	jpc_fix_t joinbuf[QMFB_JOINBUFSIZE];
+	jpc_fix_t *buf = joinbuf;
+	int llen;
+	int hlen;
+	int twostep;
+	jpc_fix_t *tmpptr;
+	register jpc_fix_t *ptr;
+	register jpc_fix_t *hptr;
+	register jpc_fix_t *lptr;
+	register int n;
+	int state;
+
+#if defined(WIN32)
+	/* Allocate memory for the join buffer from the heap. */
+	if (bufsize > QMFB_JOINBUFSIZE) {
+		if (!(buf = jas_malloc(bufsize * sizeof(jpc_fix_t)))) {
+			/* We have no choice but to commit suicide. */
+			abort();
+		}
+	}
+#endif
+
+	twostep = step << 1;
+	llen = lendind - lstartind;
+	hlen = hendind - hstartind;
+
+	if (hstartind < lstartind) {
+		/* The first sample in the highpass subband signal is to
+		  appear first in the output signal. */
+		/* Copy the appropriate samples into the first phase of the
+		  output signal. */
+		tmpptr = buf;
+		hptr = hstartptr;
+		ptr = startptr;
+		n = (llen + 1) >> 1;
+		while (n-- > 0) {
+			*tmpptr = *ptr;
+			*ptr = *hptr;
+			++tmpptr;
+			ptr += twostep;
+			hptr += step;
+		}
+		n = hlen - ((llen + 1) >> 1);
+		while (n-- > 0) {
+			*ptr = *hptr;
+			ptr += twostep;
+			hptr += step;
+		}
+		/* Copy the appropriate samples into the second phase of
+		  the output signal. */
+		ptr -= (lendind > hendind) ? (step) : (step + twostep);
+		state = !((llen - 1) & 1);
+		lptr = &lstartptr[(llen - 1) * step];
+		n = llen;
+		while (n-- > 0) {
+			if (state) {
+				--tmpptr;
+				*ptr = *tmpptr;
+			} else {
+				*ptr = *lptr;
+			}
+			lptr -= step;
+			ptr -= twostep;
+			state ^= 1;
+		}
+	} else {
+		/* The first sample in the lowpass subband signal is to
+		  appear first in the output signal. */
+		/* Copy the appropriate samples into the first phase of the
+		  output signal (corresponding to even indexed samples). */
+		lptr = &lstartptr[(llen - 1) * step];
+		ptr = &startptr[((llen - 1) << 1) * step];
+		n = llen >> 1;
+		tmpptr = buf;
+		while (n-- > 0) {
+			*tmpptr = *ptr;
+			*ptr = *lptr;
+			++tmpptr;
+			ptr -= twostep;
+			lptr -= step;
+		}
+		n = llen - (llen >> 1);
+		while (n-- > 0) {
+			*ptr = *lptr;
+			ptr -= twostep;
+			lptr -= step;
+		}
+		/* Copy the appropriate samples into the second phase of
+		  the output signal (corresponding to odd indexed
+		  samples). */
+		ptr = &startptr[step];
+		hptr = hstartptr;
+		state = !(llen & 1);
+		n = hlen;
+		while (n-- > 0) {
+			if (state) {
+				--tmpptr;
+				*ptr = *tmpptr;
+			} else {
+				*ptr = *hptr;
+			}
+			hptr += step;
+			ptr += twostep;
+			state ^= 1;
+		}
+	}
+
+#if defined(WIN32)
+	/* If the join buffer was allocated on the heap, free this memory. */
+	if (buf != joinbuf) {
+		jas_free(buf);
+	}
+#endif
+}
+
+/******************************************************************************\
+* Code for 5/3 transform.
+\******************************************************************************/
+
+static int jpc_ft_getnumchans(jpc_qmfb1d_t *qmfb)
+{
+	return 2;
+}
+
+static int jpc_ft_getanalfilters(jpc_qmfb1d_t *qmfb, int len, jas_seq2d_t **filters)
+{
+	abort();
+	return -1;
+}
+
+static int jpc_ft_getsynfilters(jpc_qmfb1d_t *qmfb, int len, jas_seq2d_t **filters)
+{
+	jas_seq_t *lf;
+	jas_seq_t *hf;
+
+	lf = 0;
+	hf = 0;
+
+	if (len > 1 || (!len)) {
+		if (!(lf = jas_seq_create(-1, 2))) {
+			goto error;
+		}
+		jas_seq_set(lf, -1, jpc_dbltofix(0.5));
+		jas_seq_set(lf, 0, jpc_dbltofix(1.0));
+		jas_seq_set(lf, 1, jpc_dbltofix(0.5));
+		if (!(hf = jas_seq_create(-1, 4))) {
+			goto error;
+		}
+		jas_seq_set(hf, -1, jpc_dbltofix(-0.125));
+		jas_seq_set(hf, 0, jpc_dbltofix(-0.25));
+		jas_seq_set(hf, 1, jpc_dbltofix(0.75));
+		jas_seq_set(hf, 2, jpc_dbltofix(-0.25));
+		jas_seq_set(hf, 3, jpc_dbltofix(-0.125));
+	} else if (len == 1) {
+		if (!(lf = jas_seq_create(0, 1))) {
+			goto error;
+		}
+		jas_seq_set(lf, 0, jpc_dbltofix(1.0));
+		if (!(hf = jas_seq_create(0, 1))) {
+			goto error;
+		}
+		jas_seq_set(hf, 0, jpc_dbltofix(2.0));
+	} else {
+		abort();
+	}
+
+	filters[0] = lf;
+	filters[1] = hf;
+
+	return 0;
+
+error:
+	if (lf) {
+		jas_seq_destroy(lf);
+	}
+	if (hf) {
+		jas_seq_destroy(hf);
+	}
+	return -1;
+}
+
+#define	NFT_LIFT0(lstartptr, lstartind, lendind, hstartptr, hstartind, hendind, step, pluseq) \
+{ \
+	register jpc_fix_t *lptr = (lstartptr); \
+	register jpc_fix_t *hptr = (hstartptr); \
+	register int n = (hendind) - (hstartind); \
+	if ((hstartind) < (lstartind)) { \
+		pluseq(*hptr, *lptr); \
+		hptr += (step); \
+		--n; \
+	} \
+	if ((hendind) >= (lendind)) { \
+		--n; \
+	} \
+	while (n-- > 0) { \
+		pluseq(*hptr, jpc_fix_asr(jpc_fix_add(*lptr, lptr[(step)]), 1)); \
+		hptr += (step); \
+		lptr += (step); \
+	} \
+	if ((hendind) >= (lendind)) { \
+		pluseq(*hptr, *lptr); \
+	} \
+}
+
+#define	NFT_LIFT1(lstartptr, lstartind, lendind, hstartptr, hstartind, hendind, step, pluseq) \
+{ \
+	register jpc_fix_t *lptr = (lstartptr); \
+	register jpc_fix_t *hptr = (hstartptr); \
+	register int n = (lendind) - (lstartind); \
+	if ((hstartind) >= (lstartind)) { \
+		pluseq(*lptr, *hptr); \
+		lptr += (step); \
+		--n; \
+	} \
+	if ((lendind) > (hendind)) { \
+		--n; \
+	} \
+	while (n-- > 0) { \
+		pluseq(*lptr, jpc_fix_asr(jpc_fix_add(*hptr, hptr[(step)]), 2)); \
+		lptr += (step); \
+		hptr += (step); \
+	} \
+	if ((lendind) > (hendind)) { \
+		pluseq(*lptr, *hptr); \
+	} \
+}
+
+#define	RFT_LIFT0(lstartptr, lstartind, lendind, hstartptr, hstartind, hendind, step, pmeqop) \
+{ \
+	register jpc_fix_t *lptr = (lstartptr); \
+	register jpc_fix_t *hptr = (hstartptr); \
+	register int n = (hendind) - (hstartind); \
+	if ((hstartind) < (lstartind)) { \
+		*hptr pmeqop *lptr; \
+		hptr += (step); \
+		--n; \
+	} \
+	if ((hendind) >= (lendind)) { \
+		--n; \
+	} \
+	while (n-- > 0) { \
+		*hptr pmeqop (*lptr + lptr[(step)]) >> 1; \
+		hptr += (step); \
+		lptr += (step); \
+	} \
+	if ((hendind) >= (lendind)) { \
+		*hptr pmeqop *lptr; \
+	} \
+}
+
+#define	RFT_LIFT1(lstartptr, lstartind, lendind, hstartptr, hstartind, hendind, step, pmeqop) \
+{ \
+	register jpc_fix_t *lptr = (lstartptr); \
+	register jpc_fix_t *hptr = (hstartptr); \
+	register int n = (lendind) - (lstartind); \
+	if ((hstartind) >= (lstartind)) { \
+		*lptr pmeqop ((*hptr << 1) + 2) >> 2; \
+		lptr += (step); \
+		--n; \
+	} \
+	if ((lendind) > (hendind)) { \
+		--n; \
+	} \
+	while (n-- > 0) { \
+		*lptr pmeqop ((*hptr + hptr[(step)]) + 2) >> 2; \
+		lptr += (step); \
+		hptr += (step); \
+	} \
+	if ((lendind) > (hendind)) { \
+		*lptr pmeqop ((*hptr << 1) + 2) >> 2; \
+	} \
+}
+
+static void jpc_ft_analyze(jpc_qmfb1d_t *qmfb, int flags, jas_seq2d_t *x)
+{
+	jpc_fix_t *startptr;
+	int startind;
+	int endind;
+	jpc_fix_t *  lstartptr;
+	int   lstartind;
+	int   lendind;
+	jpc_fix_t *  hstartptr;
+	int   hstartind;
+	int   hendind;
+	int interstep;
+	int intrastep;
+	int numseq;
+
+	if (flags & JPC_QMFB1D_VERT) {
+		interstep = 1;
+		intrastep = jas_seq2d_rowstep(x);
+		numseq = jas_seq2d_width(x);
+		startind = jas_seq2d_ystart(x);
+		endind = jas_seq2d_yend(x);
+	} else {
+		interstep = jas_seq2d_rowstep(x);
+		intrastep = 1;
+		numseq = jas_seq2d_height(x);
+		startind = jas_seq2d_xstart(x);
+		endind = jas_seq2d_xend(x);
+	}
+
+	assert(startind < endind);
+
+	startptr = jas_seq2d_getref(x, jas_seq2d_xstart(x), jas_seq2d_ystart(x));
+	if (flags & JPC_QMFB1D_RITIMODE) {
+		while (numseq-- > 0) {
+			jpc_qmfb1d_setup(startptr, startind, endind, intrastep,
+			  &lstartptr, &lstartind, &lendind, &hstartptr,
+			  &hstartind, &hendind);
+			if (endind - startind > 1) {
+				jpc_qmfb1d_split(startptr, startind, endind,
+				  intrastep, lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind);
+				RFT_LIFT0(lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind, intrastep, -=);
+				RFT_LIFT1(lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind, intrastep, +=);
+			} else {
+				if (lstartind == lendind) {
+					*startptr <<= 1;
+				}
+			}
+			startptr += interstep;
+		}
+	} else {
+		while (numseq-- > 0) {
+			jpc_qmfb1d_setup(startptr, startind, endind, intrastep,
+			  &lstartptr, &lstartind, &lendind, &hstartptr,
+			  &hstartind, &hendind);
+			if (endind - startind > 1) {
+				jpc_qmfb1d_split(startptr, startind, endind,
+				  intrastep, lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind);
+				NFT_LIFT0(lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind, intrastep,
+				  jpc_fix_minuseq);
+				NFT_LIFT1(lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind, intrastep,
+				  jpc_fix_pluseq);
+			} else {
+				if (lstartind == lendind) {
+					*startptr = jpc_fix_asl(*startptr, 1);
+				}
+			}
+			startptr += interstep;
+		}
+	}
+}
+
+static void jpc_ft_synthesize(jpc_qmfb1d_t *qmfb, int flags, jas_seq2d_t *x)
+{
+	jpc_fix_t *startptr;
+	int startind;
+	int endind;
+	jpc_fix_t *lstartptr;
+	int lstartind;
+	int lendind;
+	jpc_fix_t *hstartptr;
+	int hstartind;
+	int hendind;
+	int interstep;
+	int intrastep;
+	int numseq;
+
+	if (flags & JPC_QMFB1D_VERT) {
+		interstep = 1;
+		intrastep = jas_seq2d_rowstep(x);
+		numseq = jas_seq2d_width(x);
+		startind = jas_seq2d_ystart(x);
+		endind = jas_seq2d_yend(x);
+	} else {
+		interstep = jas_seq2d_rowstep(x);
+		intrastep = 1;
+		numseq = jas_seq2d_height(x);
+		startind = jas_seq2d_xstart(x);
+		endind = jas_seq2d_xend(x);
+	}
+
+	assert(startind < endind);
+
+	startptr = jas_seq2d_getref(x, jas_seq2d_xstart(x), jas_seq2d_ystart(x));
+	if (flags & JPC_QMFB1D_RITIMODE) {
+		while (numseq-- > 0) {
+			jpc_qmfb1d_setup(startptr, startind, endind, intrastep,
+			  &lstartptr, &lstartind, &lendind, &hstartptr,
+			  &hstartind, &hendind);
+			if (endind - startind > 1) {
+				RFT_LIFT1(lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind, intrastep, -=);
+				RFT_LIFT0(lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind, intrastep, +=);
+				jpc_qmfb1d_join(startptr, startind, endind,
+				  intrastep, lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind);
+			} else {
+				if (lstartind == lendind) {
+					*startptr >>= 1;
+				}
+			}
+			startptr += interstep;
+		}
+	} else {
+		while (numseq-- > 0) {
+			jpc_qmfb1d_setup(startptr, startind, endind, intrastep,
+			  &lstartptr, &lstartind, &lendind, &hstartptr,
+			  &hstartind, &hendind);
+			if (endind - startind > 1) {
+				NFT_LIFT1(lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind, intrastep,
+				  jpc_fix_minuseq);
+				NFT_LIFT0(lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind, intrastep,
+				  jpc_fix_pluseq);
+				jpc_qmfb1d_join(startptr, startind, endind,
+				  intrastep, lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind);
+			} else {
+				if (lstartind == lendind) {
+					*startptr = jpc_fix_asr(*startptr, 1);
+				}
+			}
+			startptr += interstep;
+		}
+	}
+}
+
+/******************************************************************************\
+* Code for 9/7 transform.
+\******************************************************************************/
+
+static int jpc_ns_getnumchans(jpc_qmfb1d_t *qmfb)
+{
+	return 2;
+}
+
+static int jpc_ns_getanalfilters(jpc_qmfb1d_t *qmfb, int len, jas_seq2d_t **filters)
+{
+	abort();
+	return -1;
+}
+
+static int jpc_ns_getsynfilters(jpc_qmfb1d_t *qmfb, int len, jas_seq2d_t **filters)
+{
+	jas_seq_t *lf;
+	jas_seq_t *hf;
+
+	lf = 0;
+	hf = 0;
+
+	if (len > 1 || (!len)) {
+		if (!(lf = jas_seq_create(-3, 4))) {
+			goto error;
+		}
+		jas_seq_set(lf, -3, jpc_dbltofix(-0.09127176311424948));
+		jas_seq_set(lf, -2, jpc_dbltofix(-0.05754352622849957));
+		jas_seq_set(lf, -1, jpc_dbltofix(0.5912717631142470));
+		jas_seq_set(lf, 0, jpc_dbltofix(1.115087052456994));
+		jas_seq_set(lf, 1, jpc_dbltofix(0.5912717631142470));
+		jas_seq_set(lf, 2, jpc_dbltofix(-0.05754352622849957));
+		jas_seq_set(lf, 3, jpc_dbltofix(-0.09127176311424948));
+		if (!(hf = jas_seq_create(-3, 6))) {
+			goto error;
+		}
+		jas_seq_set(hf, -3, jpc_dbltofix(-0.02674875741080976 * 2.0));
+		jas_seq_set(hf, -2, jpc_dbltofix(-0.01686411844287495 * 2.0));
+		jas_seq_set(hf, -1, jpc_dbltofix(0.07822326652898785 * 2.0));
+		jas_seq_set(hf, 0, jpc_dbltofix(0.2668641184428723 * 2.0));
+		jas_seq_set(hf, 1, jpc_dbltofix(-0.6029490182363579 * 2.0));
+		jas_seq_set(hf, 2, jpc_dbltofix(0.2668641184428723 * 2.0));
+		jas_seq_set(hf, 3, jpc_dbltofix(0.07822326652898785 * 2.0));
+		jas_seq_set(hf, 4, jpc_dbltofix(-0.01686411844287495 * 2.0));
+		jas_seq_set(hf, 5, jpc_dbltofix(-0.02674875741080976 * 2.0));
+	} else if (len == 1) {
+		if (!(lf = jas_seq_create(0, 1))) {
+			goto error;
+		}
+		jas_seq_set(lf, 0, jpc_dbltofix(1.0));
+		if (!(hf = jas_seq_create(0, 1))) {
+			goto error;
+		}
+		jas_seq_set(hf, 0, jpc_dbltofix(2.0));
+	} else {
+		abort();
+	}
+
+	filters[0] = lf;
+	filters[1] = hf;
+
+	return 0;
+
+error:
+	if (lf) {
+		jas_seq_destroy(lf);
+	}
+	if (hf) {
+		jas_seq_destroy(hf);
+	}
+	return -1;
+}
+
+#define	NNS_LIFT0(lstartptr, lstartind, lendind, hstartptr, hstartind, hendind, step, alpha) \
+{ \
+	register jpc_fix_t *lptr = (lstartptr); \
+	register jpc_fix_t *hptr = (hstartptr); \
+	register int n = (hendind) - (hstartind); \
+	jpc_fix_t twoalpha = jpc_fix_mulbyint(alpha, 2); \
+	if ((hstartind) < (lstartind)) { \
+		jpc_fix_pluseq(*hptr, jpc_fix_mul(*lptr, (twoalpha))); \
+		hptr += (step); \
+		--n; \
+	} \
+	if ((hendind) >= (lendind)) { \
+		--n; \
+	} \
+	while (n-- > 0) { \
+		jpc_fix_pluseq(*hptr, jpc_fix_mul(jpc_fix_add(*lptr, lptr[(step)]), (alpha))); \
+		hptr += (step); \
+		lptr += (step); \
+	} \
+	if ((hendind) >= (lendind)) { \
+		jpc_fix_pluseq(*hptr, jpc_fix_mul(*lptr, (twoalpha))); \
+	} \
+}
+
+#define	NNS_LIFT1(lstartptr, lstartind, lendind, hstartptr, hstartind, hendind, step, alpha) \
+{ \
+	register jpc_fix_t *lptr = (lstartptr); \
+	register jpc_fix_t *hptr = (hstartptr); \
+	register int n = (lendind) - (lstartind); \
+	int twoalpha = jpc_fix_mulbyint(alpha, 2); \
+	if ((hstartind) >= (lstartind)) { \
+		jpc_fix_pluseq(*lptr, jpc_fix_mul(*hptr, (twoalpha))); \
+		lptr += (step); \
+		--n; \
+	} \
+	if ((lendind) > (hendind)) { \
+		--n; \
+	} \
+	while (n-- > 0) { \
+		jpc_fix_pluseq(*lptr, jpc_fix_mul(jpc_fix_add(*hptr, hptr[(step)]), (alpha))); \
+		lptr += (step); \
+		hptr += (step); \
+	} \
+	if ((lendind) > (hendind)) { \
+		jpc_fix_pluseq(*lptr, jpc_fix_mul(*hptr, (twoalpha))); \
+	} \
+}
+
+#define	NNS_SCALE(startptr, startind, endind, step, alpha) \
+{ \
+	register jpc_fix_t *ptr = (startptr); \
+	register int n = (endind) - (startind); \
+	while (n-- > 0) { \
+		jpc_fix_muleq(*ptr, alpha); \
+		ptr += (step); \
+	} \
+}
+
+static void jpc_ns_analyze(jpc_qmfb1d_t *qmfb, int flags, jas_seq2d_t *x)
+{
+	jpc_fix_t *startptr;
+	int startind;
+	int endind;
+	jpc_fix_t *lstartptr;
+	int lstartind;
+	int lendind;
+	jpc_fix_t *hstartptr;
+	int hstartind;
+	int hendind;
+	int interstep;
+	int intrastep;
+	int numseq;
+
+	if (flags & JPC_QMFB1D_VERT) {
+		interstep = 1;
+		intrastep = jas_seq2d_rowstep(x);
+		numseq = jas_seq2d_width(x);
+		startind = jas_seq2d_ystart(x);
+		endind = jas_seq2d_yend(x);
+	} else {
+		interstep = jas_seq2d_rowstep(x);
+		intrastep = 1;
+		numseq = jas_seq2d_height(x);
+		startind = jas_seq2d_xstart(x);
+		endind = jas_seq2d_xend(x);
+	}
+
+	assert(startind < endind);
+
+	startptr = jas_seq2d_getref(x, jas_seq2d_xstart(x), jas_seq2d_ystart(x));
+	if (!(flags & JPC_QMFB1D_RITIMODE)) {
+		while (numseq-- > 0) {
+			jpc_qmfb1d_setup(startptr, startind, endind, intrastep,
+			  &lstartptr, &lstartind, &lendind, &hstartptr,
+			  &hstartind, &hendind);
+			if (endind - startind > 1) {
+				jpc_qmfb1d_split(startptr, startind, endind,
+				  intrastep, lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind);
+				NNS_LIFT0(lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind, intrastep,
+				  jpc_dbltofix(-1.586134342));
+				NNS_LIFT1(lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind, intrastep,
+				  jpc_dbltofix(-0.052980118));
+				NNS_LIFT0(lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind, intrastep,
+				  jpc_dbltofix(0.882911075));
+				NNS_LIFT1(lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind, intrastep,
+				  jpc_dbltofix(0.443506852));
+				NNS_SCALE(lstartptr, lstartind, lendind,
+				  intrastep, jpc_dbltofix(1.0/1.23017410558578));
+				NNS_SCALE(hstartptr, hstartind, hendind,
+				  intrastep, jpc_dbltofix(1.0/1.62578613134411));
+			} else {
+#if 0
+				if (lstartind == lendind) {
+					*startptr = jpc_fix_asl(*startptr, 1);
+				}
+#endif
+			}
+			startptr += interstep;
+		}
+	} else {
+		/* The reversible integer-to-integer mode is not supported
+		  for this transform. */
+		abort();
+	}
+}
+
+static void jpc_ns_synthesize(jpc_qmfb1d_t *qmfb, int flags, jas_seq2d_t *x)
+{
+	jpc_fix_t *startptr;
+	int startind;
+	int endind;
+	jpc_fix_t *lstartptr;
+	int lstartind;
+	int lendind;
+	jpc_fix_t *hstartptr;
+	int hstartind;
+	int hendind;
+	int interstep;
+	int intrastep;
+	int numseq;
+
+	if (flags & JPC_QMFB1D_VERT) {
+		interstep = 1;
+		intrastep = jas_seq2d_rowstep(x);
+		numseq = jas_seq2d_width(x);
+		startind = jas_seq2d_ystart(x);
+		endind = jas_seq2d_yend(x);
+	} else {
+		interstep = jas_seq2d_rowstep(x);
+		intrastep = 1;
+		numseq = jas_seq2d_height(x);
+		startind = jas_seq2d_xstart(x);
+		endind = jas_seq2d_xend(x);
+	}
+
+	assert(startind < endind);
+
+	startptr = jas_seq2d_getref(x, jas_seq2d_xstart(x), jas_seq2d_ystart(x));
+	if (!(flags & JPC_QMFB1D_RITIMODE)) {
+		while (numseq-- > 0) {
+			jpc_qmfb1d_setup(startptr, startind, endind, intrastep,
+			  &lstartptr, &lstartind, &lendind, &hstartptr,
+			  &hstartind, &hendind);
+			if (endind - startind > 1) {
+				NNS_SCALE(lstartptr, lstartind, lendind,
+				  intrastep, jpc_dbltofix(1.23017410558578));
+				NNS_SCALE(hstartptr, hstartind, hendind,
+				  intrastep, jpc_dbltofix(1.62578613134411));
+				NNS_LIFT1(lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind, intrastep,
+				  jpc_dbltofix(-0.443506852));
+				NNS_LIFT0(lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind, intrastep,
+				  jpc_dbltofix(-0.882911075));
+				NNS_LIFT1(lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind, intrastep,
+				  jpc_dbltofix(0.052980118));
+				NNS_LIFT0(lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind, intrastep,
+				  jpc_dbltofix(1.586134342));
+				jpc_qmfb1d_join(startptr, startind, endind,
+				  intrastep, lstartptr, lstartind, lendind,
+				  hstartptr, hstartind, hendind);
+			} else {
+#if 0
+				if (lstartind == lendind) {
+					*startptr = jpc_fix_asr(*startptr, 1);
+				}
+#endif
+			}
+			startptr += interstep;
+		}
+	} else {
+		/* The reversible integer-to-integer mode is not supported
+		  for this transform. */
+		abort();
+	}
+}
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+jpc_qmfb1d_t *jpc_qmfb1d_make(int qmfbid)
+{
+	jpc_qmfb1d_t *qmfb;
+	if (!(qmfb = jpc_qmfb1d_create())) {
+		return 0;
+	}
+	switch (qmfbid) {
+	case JPC_QMFB1D_FT:
+		qmfb->ops = &jpc_ft_ops;
+		break;
+	case JPC_QMFB1D_NS:
+		qmfb->ops = &jpc_ns_ops;
+		break;
+	default:
+		jpc_qmfb1d_destroy(qmfb);
+		return 0;
+		break;
+	}
+	return qmfb;
+}
+
+static jpc_qmfb1d_t *jpc_qmfb1d_create()
+{
+	jpc_qmfb1d_t *qmfb;
+	if (!(qmfb = jas_malloc(sizeof(jpc_qmfb1d_t)))) {
+		return 0;
+	}
+	qmfb->ops = 0;
+	return qmfb;
+}
+
+jpc_qmfb1d_t *jpc_qmfb1d_copy(jpc_qmfb1d_t *qmfb)
+{
+	jpc_qmfb1d_t *newqmfb;
+
+	if (!(newqmfb = jpc_qmfb1d_create())) {
+		return 0;
+	}
+	newqmfb->ops = qmfb->ops;
+	return newqmfb;
+}
+
+void jpc_qmfb1d_destroy(jpc_qmfb1d_t *qmfb)
+{
+	jas_free(qmfb);
+}
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+void jpc_qmfb1d_getbands(jpc_qmfb1d_t *qmfb, int flags, uint_fast32_t xstart,
+  uint_fast32_t ystart, uint_fast32_t xend, uint_fast32_t yend, int maxbands,
+  int *numbandsptr, jpc_qmfb1dband_t *bands)
+{
+	int start;
+	int end;
+	if (flags & JPC_QMFB1D_VERT) {
+		start = ystart;
+		end = yend;
+	} else {
+		start = xstart;
+		end = xend;
+	}
+	assert(jpc_qmfb1d_getnumchans(qmfb) == 2);
+	assert(start <= end);
+	bands[0].start = JPC_CEILDIVPOW2(start, 1);
+	bands[0].end = JPC_CEILDIVPOW2(end, 1);
+	bands[0].locstart = start;
+	bands[0].locend = start + bands[0].end - bands[0].start;
+	bands[1].start = JPC_FLOORDIVPOW2(start, 1);
+	bands[1].end = JPC_FLOORDIVPOW2(end, 1);
+	bands[1].locstart = bands[0].locend;
+	bands[1].locend = bands[1].locstart + bands[1].end - bands[1].start;
+	assert(bands[1].locend == end);
+	*numbandsptr = 2;
+}
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+int jpc_qmfb1d_getnumchans(jpc_qmfb1d_t *qmfb)
+{
+	return (*qmfb->ops->getnumchans)(qmfb);
+}
+
+int jpc_qmfb1d_getanalfilters(jpc_qmfb1d_t *qmfb, int len, jas_seq2d_t **filters)
+{
+	return (*qmfb->ops->getanalfilters)(qmfb, len, filters);
+}
+
+int jpc_qmfb1d_getsynfilters(jpc_qmfb1d_t *qmfb, int len, jas_seq2d_t **filters)
+{
+	return (*qmfb->ops->getsynfilters)(qmfb, len, filters);
+}
+
+void jpc_qmfb1d_analyze(jpc_qmfb1d_t *qmfb, int flags, jas_seq2d_t *x)
+{
+	(*qmfb->ops->analyze)(qmfb, flags, x);
+}
+
+void jpc_qmfb1d_synthesize(jpc_qmfb1d_t *qmfb, int flags, jas_seq2d_t *x)
+{
+	(*qmfb->ops->synthesize)(qmfb, flags, x);
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_qmfb.h b/converter/other/jpeg2000/libjasper/jpc/jpc_qmfb.h
new file mode 100644
index 00000000..e10b6c13
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_qmfb.h
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Quadrature Mirror-Image Filter Bank (QMFB) Routines
+ *
+ * $Id$
+ */
+
+#ifndef JPC_QMFB_H
+#define JPC_QMFB_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_seq.h"
+
+/******************************************************************************\
+* Constants.
+\******************************************************************************/
+
+/* The maximum number of channels for a QMF bank. */
+#define	JPC_QMFB1D_MAXCHANS	2
+
+/* Select reversible integer-to-integer mode. */
+#define	JPC_QMFB1D_RITIMODE	1
+
+/* Vertical filtering. */
+#define	JPC_QMFB1D_VERT	0x10000
+
+/* QMFB IDs. */
+#define	JPC_QMFB1D_FT	1	/* 5/3 */
+#define	JPC_QMFB1D_NS	2	/* 9/7 */
+
+/******************************************************************************\
+* Types.
+\******************************************************************************/
+
+/* Forward declaration. */
+struct jpc_qmfb1dops_s;
+
+/* Band information. */
+
+typedef struct {
+
+	/* The starting index for the band in the downsampled domain. */
+	int start;
+
+	/* The ending index for the band in the downsampled domain. */
+	int end;
+
+	/* The location of the start of the band. */
+	int locstart;
+
+	/* The location of the end of the band. */
+	int locend;
+
+} jpc_qmfb1dband_t;
+
+/* QMF bank */
+
+typedef struct {
+
+	/* The operations for this QMFB. */
+	struct jpc_qmfb1dops_s *ops;
+
+} jpc_qmfb1d_t;
+
+/* QMFB operations. */
+
+typedef struct jpc_qmfb1dops_s {
+
+	/* The number of channels in the QMFB. */
+	int (*getnumchans)(jpc_qmfb1d_t *qmfb);
+
+	/* Get the analysis filters for this QMFB. */
+	int (*getanalfilters)(jpc_qmfb1d_t *qmfb, int len, jas_seq2d_t **filters);
+
+	/* Get the synthesis filters for this QMFB. */
+	int (*getsynfilters)(jpc_qmfb1d_t *qmfb, int len, jas_seq2d_t **filters);
+
+	/* Do analysis. */
+	void (*analyze)(jpc_qmfb1d_t *qmfb, int flags, jas_seq2d_t *x);
+
+	/* Do synthesis. */
+	void (*synthesize)(jpc_qmfb1d_t *qmfb, int flags, jas_seq2d_t *x);
+
+} jpc_qmfb1dops_t;
+
+/******************************************************************************\
+* Functions.
+\******************************************************************************/
+
+/* Create a QMFB from a QMFB ID. */
+jpc_qmfb1d_t *jpc_qmfb1d_make(int qmfbid);
+
+/* Create a copy of a QMFB. */
+jpc_qmfb1d_t *jpc_qmfb1d_copy(jpc_qmfb1d_t *qmfb);
+
+/* Destroy a QMFB. */
+void jpc_qmfb1d_destroy(jpc_qmfb1d_t *qmfb);
+
+/* Get the number of channels for a QMFB. */
+int jpc_qmfb1d_getnumchans(jpc_qmfb1d_t *qmfb);
+
+/* Get the analysis filters for a QMFB. */
+int jpc_qmfb1d_getanalfilters(jpc_qmfb1d_t *qmfb, int len,
+  jas_seq2d_t **filters);
+
+/* Get the synthesis filters for a QMFB. */
+int jpc_qmfb1d_getsynfilters(jpc_qmfb1d_t *qmfb, int len,
+  jas_seq2d_t **filters);
+
+/* Get the bands for a QMFB. */
+void jpc_qmfb1d_getbands(jpc_qmfb1d_t *qmfb, int flags, uint_fast32_t xstart,
+  uint_fast32_t ystart, uint_fast32_t xend, uint_fast32_t yend, int maxbands,
+  int *numbandsptr, jpc_qmfb1dband_t *bands);
+
+/* Perform analysis. */
+void jpc_qmfb1d_analyze(jpc_qmfb1d_t *qmfb, int flags, jas_seq2d_t *x);
+
+/* Perform synthesis. */
+void jpc_qmfb1d_synthesize(jpc_qmfb1d_t *qmfb, int flags, jas_seq2d_t *x);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_t1cod.c b/converter/other/jpeg2000/libjasper/jpc/jpc_t1cod.c
new file mode 100644
index 00000000..650cb854
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_t1cod.c
@@ -0,0 +1,537 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <math.h>
+
+#include "jasper/jas_types.h"
+#include "jasper/jas_math.h"
+
+#include "jpc_bs.h"
+#include "jpc_dec.h"
+#include "jpc_cs.h"
+#include "jpc_mqcod.h"
+#include "jpc_t1cod.h"
+#include "jpc_tsfb.h"
+
+double jpc_pow2i(int n);
+
+/******************************************************************************\
+* Global data.
+\******************************************************************************/
+
+int jpc_zcctxnolut[4 * 256];
+int jpc_spblut[256];
+int jpc_scctxnolut[256];
+int jpc_magctxnolut[4096];
+
+jpc_fix_t jpc_signmsedec[1 << JPC_NMSEDEC_BITS];
+jpc_fix_t jpc_refnmsedec[1 << JPC_NMSEDEC_BITS];
+jpc_fix_t jpc_signmsedec0[1 << JPC_NMSEDEC_BITS];
+jpc_fix_t jpc_refnmsedec0[1 << JPC_NMSEDEC_BITS];
+
+jpc_mqctx_t jpc_mqctxs[JPC_NUMCTXS];
+
+/******************************************************************************\
+* Code.
+\******************************************************************************/
+
+int JPC_PASSTYPE(int passno)
+{
+	int passtype;
+	switch (passno % 3) {
+	case 0:
+		passtype = JPC_CLNPASS;
+		break;
+	case 1:
+		passtype = JPC_SIGPASS;
+		break;
+	case 2:
+		passtype = JPC_REFPASS;
+		break;
+	default:
+		passtype = -1;
+		assert(0);
+		break;
+	}
+	return passtype;
+}
+
+int JPC_NOMINALGAIN(int qmfbid, int numlvls, int lvlno, int orient)
+{
+if (qmfbid == JPC_COX_INS) {
+	return 0;
+}
+	assert(qmfbid == JPC_COX_RFT);
+	if (lvlno == 0) {
+		assert(orient == JPC_TSFB_LL);
+		return 0;
+	} else {
+		switch (orient) {
+		case JPC_TSFB_LH:
+		case JPC_TSFB_HL:
+			return 1;
+			break;
+		case JPC_TSFB_HH:
+			return 2;
+			break;
+		}
+	}
+	abort();
+}
+
+/******************************************************************************\
+* Coding pass related functions.
+\******************************************************************************/
+
+int JPC_SEGTYPE(int passno, int firstpassno, int bypass)
+{
+	int passtype;
+	if (bypass) {
+		passtype = JPC_PASSTYPE(passno);
+		if (passtype == JPC_CLNPASS) {
+			return JPC_SEG_MQ;
+		}
+		return ((passno < firstpassno + 10) ? JPC_SEG_MQ : JPC_SEG_RAW);
+	} else {
+		return JPC_SEG_MQ;
+	}
+}
+
+int JPC_SEGPASSCNT(int passno, int firstpassno, int numpasses, int bypass, int termall)
+{
+	int ret;
+	int passtype;
+
+	if (termall) {
+		ret = 1;
+	} else if (bypass) {
+		if (passno < firstpassno + 10) {
+			ret = 10 - (passno - firstpassno);
+		} else {
+			passtype = JPC_PASSTYPE(passno);
+			switch (passtype) {
+			case JPC_SIGPASS:
+				ret = 2;
+				break;
+			case JPC_REFPASS:
+				ret = 1;
+				break;
+			case JPC_CLNPASS:
+				ret = 1;
+				break;
+			default:
+				ret = -1;
+				assert(0);
+				break;
+			}
+		}
+	} else {
+		ret = JPC_PREC * 3 - 2;
+	}
+	ret = JAS_MIN(ret, numpasses - passno);
+	return ret;
+}
+
+int JPC_ISTERMINATED(int passno, int firstpassno, int numpasses, int termall,
+  int lazy)
+{
+	int ret;
+	int n;
+	if (passno - firstpassno == numpasses - 1) {
+		ret = 1;
+	} else {
+		n = JPC_SEGPASSCNT(passno, firstpassno, numpasses, lazy, termall);
+		ret = (n <= 1) ? 1 : 0;
+	}
+
+	return ret;
+}
+
+/******************************************************************************\
+* Lookup table code.
+\******************************************************************************/
+
+static void jpc_initmqctxs(void)
+{
+	jpc_initctxs(jpc_mqctxs);
+}
+
+void jpc_initluts()
+{
+	int i;
+	int orient;
+	int refine;
+	float u;
+	float v;
+	float t;
+
+/* XXX - hack */
+jpc_initmqctxs();
+
+	for (orient = 0; orient < 4; ++orient) {
+		for (i = 0; i < 256; ++i) {
+			jpc_zcctxnolut[(orient << 8) | i] = jpc_getzcctxno(i, orient);
+		}
+	}
+
+	for (i = 0; i < 256; ++i) {
+		jpc_spblut[i] = jpc_getspb(i << 4);
+	}
+
+	for (i = 0; i < 256; ++i) {
+		jpc_scctxnolut[i] = jpc_getscctxno(i << 4);
+	}
+
+	for (refine = 0; refine < 2; ++refine) {
+		for (i = 0; i < 2048; ++i) {
+			jpc_magctxnolut[(refine << 11) + i] = jpc_getmagctxno((refine ? JPC_REFINE : 0) | i);
+		}
+	}
+
+	for (i = 0; i < (1 << JPC_NMSEDEC_BITS); ++i) {
+		t = i * jpc_pow2i(-JPC_NMSEDEC_FRACBITS);
+		u = t;
+		v = t - 1.5;
+		jpc_signmsedec[i] = jpc_dbltofix(floor((u * u - v * v) * jpc_pow2i(JPC_NMSEDEC_FRACBITS) + 0.5) / jpc_pow2i(JPC_NMSEDEC_FRACBITS));
+/* XXX - this calc is not correct */
+		jpc_signmsedec0[i] = jpc_dbltofix(floor((u * u) * jpc_pow2i(JPC_NMSEDEC_FRACBITS) + 0.5) / jpc_pow2i(JPC_NMSEDEC_FRACBITS));
+		u = t - 1.0;
+		if (i & (1 << (JPC_NMSEDEC_BITS - 1))) {
+			v = t - 1.5;
+		} else {
+			v = t - 0.5;
+		}
+		jpc_refnmsedec[i] = jpc_dbltofix(floor((u * u - v * v) * jpc_pow2i(JPC_NMSEDEC_FRACBITS) + 0.5) / jpc_pow2i(JPC_NMSEDEC_FRACBITS));
+/* XXX - this calc is not correct */
+		jpc_refnmsedec0[i] = jpc_dbltofix(floor((u * u) * jpc_pow2i(JPC_NMSEDEC_FRACBITS) + 0.5) / jpc_pow2i(JPC_NMSEDEC_FRACBITS));
+	}
+}
+
+jpc_fix_t jpc_getsignmsedec_func(jpc_fix_t x, int bitpos)
+{
+	jpc_fix_t y;
+	assert(!(x & (~JAS_ONES(bitpos + 1))));
+	y = jpc_getsignmsedec_macro(x, bitpos);
+	return y;
+}
+
+int jpc_getzcctxno(int f, int orient)
+{
+	int h;
+	int v;
+	int d;
+	int n;
+	int t;
+	int hv;
+
+	/* Avoid compiler warning. */
+	n = 0;
+
+	h = ((f & JPC_WSIG) != 0) + ((f & JPC_ESIG) != 0);
+	v = ((f & JPC_NSIG) != 0) + ((f & JPC_SSIG) != 0);
+	d = ((f & JPC_NWSIG) != 0) + ((f & JPC_NESIG) != 0) + ((f & JPC_SESIG) != 0) + ((f & JPC_SWSIG) != 0);
+	switch (orient) {
+	case JPC_TSFB_HL:
+		t = h;
+		h = v;
+		v = t;
+	case JPC_TSFB_LL:
+	case JPC_TSFB_LH:
+		if (!h) {
+			if (!v) {
+				if (!d) {
+					n = 0;
+				} else if (d == 1) {
+					n = 1;
+				} else {
+					n = 2;
+				}
+			} else if (v == 1) {
+				n = 3;
+			} else {
+				n = 4;
+			}
+		} else if (h == 1) {
+			if (!v) {
+				if (!d) {
+					n = 5;
+				} else {
+					n = 6;
+				}
+			} else {
+				n = 7;
+			}
+		} else {
+			n = 8;
+		}
+		break;
+	case JPC_TSFB_HH:
+		hv = h + v;
+		if (!d) {
+			if (!hv) {
+				n = 0;
+			} else if (hv == 1) {
+				n = 1;
+			} else {
+				n = 2;
+			}
+		} else if (d == 1) {
+			if (!hv) {
+				n = 3;
+			} else if (hv == 1) {
+				n = 4;
+			} else {
+				n = 5;
+			}
+		} else if (d == 2) {
+			if (!hv) {
+				n = 6;
+			} else {
+				n = 7;
+			}
+		} else {
+			n = 8;
+		}
+		break;
+	}
+	assert(n < JPC_NUMZCCTXS);
+	return JPC_ZCCTXNO + n;
+}
+
+int jpc_getspb(int f)
+{
+	int hc;
+	int vc;
+	int n;
+
+	hc = JAS_MIN(((f & (JPC_ESIG | JPC_ESGN)) == JPC_ESIG) + ((f & (JPC_WSIG | JPC_WSGN)) == JPC_WSIG), 1) -
+	  JAS_MIN(((f & (JPC_ESIG | JPC_ESGN)) == (JPC_ESIG | JPC_ESGN)) + ((f & (JPC_WSIG | JPC_WSGN)) == (JPC_WSIG | JPC_WSGN)), 1);
+	vc = JAS_MIN(((f & (JPC_NSIG | JPC_NSGN)) == JPC_NSIG) + ((f & (JPC_SSIG | JPC_SSGN)) == JPC_SSIG), 1) -
+	  JAS_MIN(((f & (JPC_NSIG | JPC_NSGN)) == (JPC_NSIG | JPC_NSGN)) + ((f & (JPC_SSIG | JPC_SSGN)) == (JPC_SSIG | JPC_SSGN)), 1);
+	if (!hc && !vc) {
+		n = 0;
+	} else {
+		n = (!(hc > 0 || (!hc && vc > 0)));
+	}
+	return n;
+}
+
+int jpc_getscctxno(int f)
+{
+	int hc;
+	int vc;
+	int n;
+
+	/* Avoid compiler warning. */
+	n = 0;
+
+	hc = JAS_MIN(((f & (JPC_ESIG | JPC_ESGN)) == JPC_ESIG) + ((f & (JPC_WSIG | JPC_WSGN)) == JPC_WSIG),
+	  1) - JAS_MIN(((f & (JPC_ESIG | JPC_ESGN)) == (JPC_ESIG | JPC_ESGN)) +
+	  ((f & (JPC_WSIG | JPC_WSGN)) == (JPC_WSIG | JPC_WSGN)), 1);
+	vc = JAS_MIN(((f & (JPC_NSIG | JPC_NSGN)) == JPC_NSIG) + ((f & (JPC_SSIG | JPC_SSGN)) == JPC_SSIG),
+	  1) - JAS_MIN(((f & (JPC_NSIG | JPC_NSGN)) == (JPC_NSIG | JPC_NSGN)) +
+	  ((f & (JPC_SSIG | JPC_SSGN)) == (JPC_SSIG | JPC_SSGN)), 1);
+	assert(hc >= -1 && hc <= 1 && vc >= -1 && vc <= 1);
+	if (hc < 0) {
+		hc = -hc;
+		vc = -vc;
+	}
+	if (!hc) {
+		if (vc == -1) {
+			n = 1;
+		} else if (!vc) {
+			n = 0;
+		} else {
+			n = 1;
+		}
+	} else if (hc == 1) {
+		if (vc == -1) {
+			n = 2;
+		} else if (!vc) {
+			n = 3;
+		} else {
+			n = 4;
+		}
+	}
+	assert(n < JPC_NUMSCCTXS);
+	return JPC_SCCTXNO + n;
+}
+
+int jpc_getmagctxno(int f)
+{
+	int n;
+
+	if (!(f & JPC_REFINE)) {
+		n = (f & (JPC_OTHSIGMSK)) ? 1 : 0;
+	} else {
+		n = 2;
+	}
+
+	assert(n < JPC_NUMMAGCTXS);
+	return JPC_MAGCTXNO + n;
+}
+
+void jpc_initctxs(jpc_mqctx_t *ctxs)
+{
+	jpc_mqctx_t *ctx;
+	int i;
+
+	ctx = ctxs;
+	for (i = 0; i < JPC_NUMCTXS; ++i) {
+		ctx->mps = 0;
+		switch (i) {
+		case JPC_UCTXNO:
+			ctx->ind = 46;
+			break;
+		case JPC_ZCCTXNO:
+			ctx->ind = 4;
+			break;
+		case JPC_AGGCTXNO:
+			ctx->ind = 3;
+			break;
+		default:
+			ctx->ind = 0;
+			break;
+		}
+		++ctx;
+	}
+}
+
+/* Calculate the real quantity exp2(n), where x is an integer. */
+double jpc_pow2i(int n)
+{
+	double x;
+	double a;
+
+	x = 1.0;
+	if (n < 0) {
+		a = 0.5;
+		n = -n;
+	} else {
+		a = 2.0;
+	}
+	while (--n >= 0) {
+		x *= a;
+	}
+	return x;
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_t1cod.h b/converter/other/jpeg2000/libjasper/jpc/jpc_t1cod.h
new file mode 100644
index 00000000..3e061d20
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_t1cod.h
@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef JPC_T1COD_H
+#define JPC_T1COD_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_fix.h"
+#include "jasper/jas_math.h"
+
+#include "jpc_mqcod.h"
+#include "jpc_tsfb.h"
+
+/******************************************************************************\
+* Constants.
+\******************************************************************************/
+
+/* The number of bits used to index into various lookup tables. */
+#define JPC_NMSEDEC_BITS	7
+#define JPC_NMSEDEC_FRACBITS	(JPC_NMSEDEC_BITS - 1)
+
+/*
+ * Segment types.
+ */
+
+/* Invalid. */
+#define JPC_SEG_INVALID	0
+/* MQ. */
+#define JPC_SEG_MQ		1
+/* Raw. */
+#define JPC_SEG_RAW		2
+
+/* The nominal word size. */
+#define	JPC_PREC	32
+
+/* Tier-1 coding pass types. */
+#define	JPC_SIGPASS	0	/* significance */
+#define	JPC_REFPASS	1	/* refinement */
+#define	JPC_CLNPASS	2	/* cleanup */
+
+/*
+ * Per-sample state information for tier-1 coding.
+ */
+
+/* The northeast neighbour has been found to be significant. */
+#define	JPC_NESIG	0x0001
+/* The southeast neighbour has been found to be significant. */
+#define	JPC_SESIG	0x0002
+/* The southwest neighbour has been found to be significant. */
+#define	JPC_SWSIG	0x0004
+/* The northwest neighbour has been found to be significant. */
+#define	JPC_NWSIG	0x0008
+/* The north neighbour has been found to be significant. */
+#define	JPC_NSIG	0x0010
+/* The east neighbour has been found to be significant. */
+#define	JPC_ESIG	0x0020
+/* The south neighbour has been found to be significant. */
+#define	JPC_SSIG	0x0040
+/* The west neighbour has been found to be significant. */
+#define	JPC_WSIG	0x0080
+/* The significance mask for 8-connected neighbours. */
+#define	JPC_OTHSIGMSK \
+	(JPC_NSIG | JPC_NESIG | JPC_ESIG | JPC_SESIG | JPC_SSIG | JPC_SWSIG | JPC_WSIG | JPC_NWSIG)
+/* The significance mask for 4-connected neighbours. */
+#define	JPC_PRIMSIGMSK	(JPC_NSIG | JPC_ESIG | JPC_SSIG | JPC_WSIG)
+
+/* The north neighbour is negative in value. */
+#define	JPC_NSGN	0x0100
+/* The east neighbour is negative in value. */
+#define	JPC_ESGN	0x0200
+/* The south neighbour is negative in value. */
+#define	JPC_SSGN	0x0400
+/* The west neighbour is negative in value. */
+#define	JPC_WSGN	0x0800
+/* The sign mask for 4-connected neighbours. */
+#define	JPC_SGNMSK	(JPC_NSGN | JPC_ESGN | JPC_SSGN | JPC_WSGN)
+
+/* This sample has been found to be significant. */
+#define JPC_SIG		0x1000
+/* The sample has been refined. */
+#define	JPC_REFINE	0x2000
+/* This sample has been processed during the significance pass. */
+#define	JPC_VISIT	0x4000
+
+/* The number of aggregation contexts. */
+#define	JPC_NUMAGGCTXS	1
+/* The number of zero coding contexts. */
+#define	JPC_NUMZCCTXS	9
+/* The number of magnitude contexts. */
+#define	JPC_NUMMAGCTXS	3
+/* The number of sign coding contexts. */
+#define	JPC_NUMSCCTXS	5
+/* The number of uniform contexts. */
+#define	JPC_NUMUCTXS	1
+
+/* The context ID for the first aggregation context. */
+#define	JPC_AGGCTXNO	0
+/* The context ID for the first zero coding context. */
+#define	JPC_ZCCTXNO		(JPC_AGGCTXNO + JPC_NUMAGGCTXS)
+/* The context ID for the first magnitude context. */
+#define	JPC_MAGCTXNO	(JPC_ZCCTXNO + JPC_NUMZCCTXS)
+/* The context ID for the first sign coding context. */
+#define	JPC_SCCTXNO		(JPC_MAGCTXNO + JPC_NUMMAGCTXS)
+/* The context ID for the first uniform context. */
+#define	JPC_UCTXNO		(JPC_SCCTXNO + JPC_NUMSCCTXS)
+/* The total number of contexts. */
+#define	JPC_NUMCTXS		(JPC_UCTXNO + JPC_NUMUCTXS)
+
+/******************************************************************************\
+* External data.
+\******************************************************************************/
+
+/* These lookup tables are used by various macros/functions. */
+/* Do not access these lookup tables directly. */
+extern int jpc_zcctxnolut[];
+extern int jpc_spblut[];
+extern int jpc_scctxnolut[];
+extern int jpc_magctxnolut[];
+extern jpc_fix_t jpc_refnmsedec[];
+extern jpc_fix_t jpc_signmsedec[];
+extern jpc_fix_t jpc_refnmsedec0[];
+extern jpc_fix_t jpc_signmsedec0[];
+
+/* The initial settings for the MQ contexts. */
+extern jpc_mqctx_t jpc_mqctxs[];
+
+/******************************************************************************\
+* Functions and macros.
+\******************************************************************************/
+
+/* Initialize the MQ contexts. */
+void jpc_initctxs(jpc_mqctx_t *ctxs);
+
+/* Get the zero coding context. */
+int jpc_getzcctxno(int f, int orient);
+#define	JPC_GETZCCTXNO(f, orient) \
+	(jpc_zcctxnolut[((orient) << 8) | ((f) & JPC_OTHSIGMSK)])
+
+/* Get the sign prediction bit. */
+int jpc_getspb(int f);
+#define	JPC_GETSPB(f) \
+	(jpc_spblut[((f) & (JPC_PRIMSIGMSK | JPC_SGNMSK)) >> 4])
+
+/* Get the sign coding context. */
+int jpc_getscctxno(int f);
+#define	JPC_GETSCCTXNO(f) \
+	(jpc_scctxnolut[((f) & (JPC_PRIMSIGMSK | JPC_SGNMSK)) >> 4])
+
+/* Get the magnitude context. */
+int jpc_getmagctxno(int f);
+#define	JPC_GETMAGCTXNO(f) \
+	(jpc_magctxnolut[((f) & JPC_OTHSIGMSK) | ((((f) & JPC_REFINE) != 0) << 11)])
+
+/* Get the normalized MSE reduction for significance passes. */
+#define	JPC_GETSIGNMSEDEC(x, bitpos)	jpc_getsignmsedec_macro(x, bitpos)
+jpc_fix_t jpc_getsignmsedec_func(jpc_fix_t x, int bitpos);
+#define	jpc_getsignmsedec_macro(x, bitpos) \
+	((bitpos > JPC_NMSEDEC_FRACBITS) ? jpc_signmsedec[JPC_ASR(x, bitpos - JPC_NMSEDEC_FRACBITS) & JAS_ONES(JPC_NMSEDEC_BITS)] : \
+	  (jpc_signmsedec0[JPC_ASR(x, bitpos - JPC_NMSEDEC_FRACBITS) & JAS_ONES(JPC_NMSEDEC_BITS)]))
+
+/* Get the normalized MSE reduction for refinement passes. */
+#define	JPC_GETREFNMSEDEC(x, bitpos)	jpc_getrefnmsedec_macro(x, bitpos)
+jpc_fix_t jpc_refsignmsedec_func(jpc_fix_t x, int bitpos);
+#define	jpc_getrefnmsedec_macro(x, bitpos) \
+	((bitpos > JPC_NMSEDEC_FRACBITS) ? jpc_refnmsedec[JPC_ASR(x, bitpos - JPC_NMSEDEC_FRACBITS) & JAS_ONES(JPC_NMSEDEC_BITS)] : \
+	  (jpc_refnmsedec0[JPC_ASR(x, bitpos - JPC_NMSEDEC_FRACBITS) & JAS_ONES(JPC_NMSEDEC_BITS)]))
+
+/* Arithmetic shift right (with ability to shift left also). */
+#define	JPC_ASR(x, n) \
+	(((n) >= 0) ? ((x) >> (n)) : ((x) << (-(n))))
+
+/* Update the per-sample state information. */
+#define	JPC_UPDATEFLAGS4(fp, rowstep, s, vcausalflag) \
+{ \
+	register jpc_fix_t *np = (fp) - (rowstep); \
+	register jpc_fix_t *sp = (fp) + (rowstep); \
+	if ((vcausalflag)) { \
+		sp[-1] |= JPC_NESIG; \
+		sp[1] |= JPC_NWSIG; \
+		if (s) { \
+			*sp |= JPC_NSIG | JPC_NSGN; \
+			(fp)[-1] |= JPC_ESIG | JPC_ESGN; \
+			(fp)[1] |= JPC_WSIG | JPC_WSGN; \
+		} else { \
+			*sp |= JPC_NSIG; \
+			(fp)[-1] |= JPC_ESIG; \
+			(fp)[1] |= JPC_WSIG; \
+		} \
+	} else { \
+		np[-1] |= JPC_SESIG; \
+		np[1] |= JPC_SWSIG; \
+		sp[-1] |= JPC_NESIG; \
+		sp[1] |= JPC_NWSIG; \
+		if (s) { \
+			*np |= JPC_SSIG | JPC_SSGN; \
+			*sp |= JPC_NSIG | JPC_NSGN; \
+			(fp)[-1] |= JPC_ESIG | JPC_ESGN; \
+			(fp)[1] |= JPC_WSIG | JPC_WSGN; \
+		} else { \
+			*np |= JPC_SSIG; \
+			*sp |= JPC_NSIG; \
+			(fp)[-1] |= JPC_ESIG; \
+			(fp)[1] |= JPC_WSIG; \
+		} \
+	} \
+}
+
+/* Initialize the lookup tables used by the codec. */
+void jpc_initluts(void);
+
+/* Get the nominal gain associated with a particular band. */
+int JPC_NOMINALGAIN(int qmfbid, int numlvls, int lvlno, int orient);
+
+/* Get the coding pass type. */
+int JPC_PASSTYPE(int passno);
+
+/* Get the segment type. */
+int JPC_SEGTYPE(int passno, int firstpassno, int bypass);
+
+/* Get the number of coding passess in the segment. */
+int JPC_SEGPASSCNT(int passno, int firstpassno, int numpasses, int bypass,
+  int termall);
+
+/* Is the coding pass terminated? */
+int JPC_ISTERMINATED(int passno, int firstpassno, int numpasses, int termall,
+  int lazy);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_t1dec.c b/converter/other/jpeg2000/libjasper/jpc/jpc_t1dec.c
new file mode 100644
index 00000000..01a54ea8
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_t1dec.c
@@ -0,0 +1,954 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Tier 1 Decoder
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "jasper/jas_fix.h"
+#include "jasper/jas_stream.h"
+#include "jasper/jas_math.h"
+
+#include "jpc_bs.h"
+#include "jpc_mqdec.h"
+#include "jpc_t1dec.h"
+#include "jpc_t1cod.h"
+#include "jpc_dec.h"
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+static int jpc_dec_decodecblk(jpc_dec_t *dec, jpc_dec_tile_t *tile, jpc_dec_tcomp_t *tcomp, jpc_dec_band_t *band,
+  jpc_dec_cblk_t *cblk, int dopartial, int maxlyrs);
+static int dec_sigpass(jpc_dec_t *dec, jpc_mqdec_t *mqdec, int bitpos, int orient,
+  int vcausalflag, jas_matrix_t *flags, jas_matrix_t *data);
+static int dec_rawsigpass(jpc_dec_t *dec, jpc_bitstream_t *in, int bitpos,
+  int vcausalflag, jas_matrix_t *flags, jas_matrix_t *data);
+static int dec_refpass(jpc_dec_t *dec, jpc_mqdec_t *mqdec, int bitpos, int vcausalflag,
+  jas_matrix_t *flags, jas_matrix_t *data);
+static int dec_rawrefpass(jpc_dec_t *dec, jpc_bitstream_t *in, int bitpos,
+  int vcausalflag, jas_matrix_t *flags, jas_matrix_t *data);
+static int dec_clnpass(jpc_dec_t *dec, jpc_mqdec_t *mqdec, int bitpos, int orient,
+  int vcausalflag, int segsymflag, jas_matrix_t *flags, jas_matrix_t *data);
+
+#if defined(DEBUG)
+static long t1dec_cnt = 0;
+#endif
+
+#if !defined(DEBUG)
+#define	JPC_T1D_GETBIT(mqdec, v, passtypename, symtypename) \
+	((v) = jpc_mqdec_getbit(mqdec))
+#else
+#define	JPC_T1D_GETBIT(mqdec, v, passtypename, symtypename) \
+{ \
+	(v) = jpc_mqdec_getbit(mqdec); \
+	if (jas_getdbglevel() >= 100) { \
+		fprintf(stderr, "index = %ld; passtype = %s; symtype = %s; sym = %d\n", t1dec_cnt, passtypename, symtypename, v); \
+		++t1dec_cnt; \
+	} \
+}
+#endif
+#define	JPC_T1D_GETBITNOSKEW(mqdec, v, passtypename, symtypename) \
+	JPC_T1D_GETBIT(mqdec, v, passtypename, symtypename)
+
+#if !defined(DEBUG)
+#define	JPC_T1D_RAWGETBIT(bitstream, v, passtypename, symtypename) \
+	((v) = jpc_bitstream_getbit(bitstream))
+#else
+#define	JPC_T1D_RAWGETBIT(bitstream, v, passtypename, symtypename) \
+{ \
+	(v) = jpc_bitstream_getbit(bitstream); \
+	if (jas_getdbglevel() >= 100) { \
+		fprintf(stderr, "index = %ld; passtype = %s; symtype = %s; sym = %d\n", t1dec_cnt, passtypename, symtypename, v); \
+		++t1dec_cnt; \
+	} \
+}
+#endif
+
+/******************************************************************************\
+* Code.
+\******************************************************************************/
+
+int jpc_dec_decodecblks(jpc_dec_t *dec, jpc_dec_tile_t *tile)
+{
+	jpc_dec_tcomp_t *tcomp;
+	int compcnt;
+	jpc_dec_rlvl_t *rlvl;
+	int rlvlcnt;
+	jpc_dec_band_t *band;
+	int bandcnt;
+	jpc_dec_prc_t *prc;
+	int prccnt;
+	jpc_dec_cblk_t *cblk;
+	int cblkcnt;
+
+	for (compcnt = dec->numcomps, tcomp = tile->tcomps; compcnt > 0;
+	  --compcnt, ++tcomp) {
+		for (rlvlcnt = tcomp->numrlvls, rlvl = tcomp->rlvls;
+		  rlvlcnt > 0; --rlvlcnt, ++rlvl) {
+			if (!rlvl->bands) {
+				continue;
+			}
+			for (bandcnt = rlvl->numbands, band = rlvl->bands;
+			  bandcnt > 0; --bandcnt, ++band) {
+				if (!band->data) {
+					continue;
+				}
+				for (prccnt = rlvl->numprcs, prc = band->prcs;
+				  prccnt > 0; --prccnt, ++prc) {
+					if (!prc->cblks) {
+						continue;
+					}
+					for (cblkcnt = prc->numcblks,
+					  cblk = prc->cblks; cblkcnt > 0;
+					  --cblkcnt, ++cblk) {
+						if (jpc_dec_decodecblk(dec, tile, tcomp,
+						  band, cblk, 1, JPC_MAXLYRS)) {
+							return -1;
+						}
+					}
+				}
+
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int jpc_dec_decodecblk(jpc_dec_t *dec, jpc_dec_tile_t *tile, jpc_dec_tcomp_t *tcomp, jpc_dec_band_t *band,
+  jpc_dec_cblk_t *cblk, int dopartial, int maxlyrs)
+{
+	jpc_dec_seg_t *seg;
+	int i;
+	int bpno;
+	int passtype;
+	int ret;
+	int compno;
+	int filldata;
+	int fillmask;
+	jpc_dec_ccp_t *ccp;
+
+	compno = tcomp - tile->tcomps;
+
+	if (!cblk->flags) {
+		/* Note: matrix is assumed to be zeroed */
+		if (!(cblk->flags = jas_matrix_create(jas_matrix_numrows(cblk->data) +
+		  2, jas_matrix_numcols(cblk->data) + 2))) {
+			return -1;
+		}
+	}
+
+	seg = cblk->segs.head;
+	while (seg && (seg != cblk->curseg || dopartial) && (maxlyrs < 0 ||
+	  seg->lyrno < maxlyrs)) {
+		assert(seg->numpasses >= seg->maxpasses || dopartial);
+		assert(seg->stream);
+		jas_stream_rewind(seg->stream);
+		jas_stream_setrwcount(seg->stream, 0);
+		if (seg->type == JPC_SEG_MQ) {
+			if (!cblk->mqdec) {
+				if (!(cblk->mqdec = jpc_mqdec_create(JPC_NUMCTXS, 0))) {
+					return -1;
+				}
+				jpc_mqdec_setctxs(cblk->mqdec, JPC_NUMCTXS, jpc_mqctxs);
+			}
+			jpc_mqdec_setinput(cblk->mqdec, seg->stream);
+			jpc_mqdec_init(cblk->mqdec);
+		} else {
+			assert(seg->type == JPC_SEG_RAW);
+			if (!cblk->nulldec) {
+				if (!(cblk->nulldec = jpc_bitstream_sopen(seg->stream, "r"))) {
+					assert(0);
+				}
+			}
+		}
+
+
+		for (i = 0; i < seg->numpasses; ++i) {
+			if (cblk->numimsbs > band->numbps) {
+				ccp = &tile->cp->ccps[compno];
+				if (ccp->roishift <= 0) {
+					fprintf(stderr, "warning: corrupt code stream\n");
+				} else {
+					if (cblk->numimsbs < ccp->roishift - band->numbps) {
+						fprintf(stderr, "warning: corrupt code stream\n");
+					}
+				}
+			}
+			bpno = band->roishift + band->numbps - 1 - (cblk->numimsbs +
+			  (seg->passno + i - cblk->firstpassno + 2) / 3);
+if (bpno < 0) {
+	goto premature_exit;
+}
+#if 1
+			passtype = (seg->passno + i + 2) % 3;
+#else
+			passtype = JPC_PASSTYPE(seg->passno + i + 2);
+#endif
+			assert(bpno >= 0 && bpno < 31);
+			switch (passtype) {
+			case JPC_SIGPASS:
+				ret = (seg->type == JPC_SEG_MQ) ? dec_sigpass(dec,
+				  cblk->mqdec, bpno, band->orient,
+				  (tile->cp->ccps[compno].cblkctx & JPC_COX_VSC) != 0,
+				  cblk->flags, cblk->data) :
+				  dec_rawsigpass(dec, cblk->nulldec, bpno,
+				  (tile->cp->ccps[compno].cblkctx & JPC_COX_VSC) != 0,
+				  cblk->flags, cblk->data);
+				break;
+			case JPC_REFPASS:
+				ret = (seg->type == JPC_SEG_MQ) ?
+				  dec_refpass(dec, cblk->mqdec, bpno,
+				  (tile->cp->ccps[compno].cblkctx & JPC_COX_VSC) != 0,
+				  cblk->flags, cblk->data) :
+				  dec_rawrefpass(dec, cblk->nulldec, bpno,
+				  (tile->cp->ccps[compno].cblkctx & JPC_COX_VSC) != 0,
+				  cblk->flags, cblk->data);
+				break;
+			case JPC_CLNPASS:
+				assert(seg->type == JPC_SEG_MQ);
+				ret = dec_clnpass(dec, cblk->mqdec, bpno,
+				  band->orient, (tile->cp->ccps[compno].cblkctx &
+				  JPC_COX_VSC) != 0, (tile->cp->ccps[compno].cblkctx &
+				  JPC_COX_SEGSYM) != 0, cblk->flags,
+				  cblk->data);
+				break;
+			default:
+				ret = -1;
+				break;
+			}
+			/* Do we need to reset after each coding pass? */
+			if (tile->cp->ccps[compno].cblkctx & JPC_COX_RESET) {
+				jpc_mqdec_setctxs(cblk->mqdec, JPC_NUMCTXS, jpc_mqctxs);
+			}
+
+			if (ret) {
+				fprintf(stderr, "coding pass failed passtype=%d segtype=%d\n", passtype, seg->type);
+				return -1;
+			}
+
+		}
+
+		if (seg->type == JPC_SEG_MQ) {
+/* Note: dont destroy mq decoder because context info will be lost */
+		} else {
+			assert(seg->type == JPC_SEG_RAW);
+			if (tile->cp->ccps[compno].cblkctx & JPC_COX_PTERM) {
+				fillmask = 0x7f;
+				filldata = 0x2a;
+			} else {
+				fillmask = 0;
+				filldata = 0;
+			}
+			if ((ret = jpc_bitstream_inalign(cblk->nulldec, fillmask,
+			  filldata)) < 0) {
+				return -1;
+			} else if (ret > 0) {
+				fprintf(stderr, "warning: bad termination pattern detected\n");
+			}
+			jpc_bitstream_close(cblk->nulldec);
+			cblk->nulldec = 0;
+		}
+
+		cblk->curseg = seg->next;
+		jpc_seglist_remove(&cblk->segs, seg);
+		jpc_seg_destroy(seg);
+		seg = cblk->curseg;
+	}
+
+	assert(dopartial ? (!cblk->curseg) : 1);
+
+premature_exit:
+	return 0;
+}
+
+/******************************************************************************\
+* Code for significance pass.
+\******************************************************************************/
+
+#define	jpc_sigpass_step(fp, frowstep, dp, bitpos, oneplushalf, orient, mqdec, vcausalflag) \
+{ \
+	int f; \
+	int v; \
+	f = *(fp); \
+	if ((f & JPC_OTHSIGMSK) && !(f & (JPC_SIG | JPC_VISIT))) { \
+		jpc_mqdec_setcurctx((mqdec), JPC_GETZCCTXNO(f, (orient))); \
+		JPC_T1D_GETBIT((mqdec), v, "SIG", "ZC"); \
+		if (v) { \
+			jpc_mqdec_setcurctx((mqdec), JPC_GETSCCTXNO(f)); \
+			JPC_T1D_GETBIT((mqdec), v, "SIG", "SC"); \
+			v ^= JPC_GETSPB(f); \
+			JPC_UPDATEFLAGS4((fp), (frowstep), v, (vcausalflag)); \
+			*(fp) |= JPC_SIG; \
+			*(dp) = (v) ? (-(oneplushalf)) : (oneplushalf); \
+		} \
+		*(fp) |= JPC_VISIT; \
+	} \
+}
+
+static int dec_sigpass(jpc_dec_t *dec, register jpc_mqdec_t *mqdec, int bitpos, int orient,
+  int vcausalflag, jas_matrix_t *flags, jas_matrix_t *data)
+{
+	int i;
+	int j;
+	int one;
+	int half;
+	int oneplushalf;
+	int vscanlen;
+	int width;
+	int height;
+	jpc_fix_t *fp;
+	int frowstep;
+	int fstripestep;
+	jpc_fix_t *fstripestart;
+	jpc_fix_t *fvscanstart;
+	jpc_fix_t *dp;
+	int drowstep;
+	int dstripestep;
+	jpc_fix_t *dstripestart;
+	jpc_fix_t *dvscanstart;
+	int k;
+
+	width = jas_matrix_numcols(data);
+	height = jas_matrix_numrows(data);
+	frowstep = jas_matrix_rowstep(flags);
+	drowstep = jas_matrix_rowstep(data);
+	fstripestep = frowstep << 2;
+	dstripestep = drowstep << 2;
+
+	one = 1 << bitpos;
+	half = one >> 1;
+	oneplushalf = one | half;
+
+	fstripestart = jas_matrix_getref(flags, 1, 1);
+	dstripestart = jas_matrix_getref(data, 0, 0);
+	for (i = height; i > 0; i -= 4, fstripestart += fstripestep,
+	  dstripestart += dstripestep) {
+		fvscanstart = fstripestart;
+		dvscanstart = dstripestart;
+		vscanlen = JAS_MIN(i, 4);
+		for (j = width; j > 0; --j, ++fvscanstart, ++dvscanstart) {
+			fp = fvscanstart;
+			dp = dvscanstart;
+			k = vscanlen;
+
+			/* Process first sample in vertical scan. */
+			jpc_sigpass_step(fp, frowstep, dp, bitpos, oneplushalf,
+			  orient, mqdec, vcausalflag);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			/* Process second sample in vertical scan. */
+			jpc_sigpass_step(fp, frowstep, dp, bitpos, oneplushalf,
+			  orient, mqdec, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			/* Process third sample in vertical scan. */
+			jpc_sigpass_step(fp, frowstep, dp, bitpos, oneplushalf,
+			  orient, mqdec, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			/* Process fourth sample in vertical scan. */
+			jpc_sigpass_step(fp, frowstep, dp, bitpos, oneplushalf,
+			  orient, mqdec, 0);
+		}
+	}
+	return 0;
+}
+
+#define	jpc_rawsigpass_step(fp, frowstep, dp, oneplushalf, in, vcausalflag) \
+{ \
+	jpc_fix_t f = *(fp); \
+	jpc_fix_t v; \
+	if ((f & JPC_OTHSIGMSK) && !(f & (JPC_SIG | JPC_VISIT))) { \
+		JPC_T1D_RAWGETBIT(in, v, "SIG", "ZC"); \
+		if (v < 0) { \
+			return -1; \
+		} \
+		if (v) { \
+			JPC_T1D_RAWGETBIT(in, v, "SIG", "SC"); \
+			if (v < 0) { \
+				return -1; \
+			} \
+			JPC_UPDATEFLAGS4((fp), (frowstep), v, (vcausalflag)); \
+			*(fp) |= JPC_SIG; \
+			*(dp) = v ? (-oneplushalf) : (oneplushalf); \
+		} \
+		*(fp) |= JPC_VISIT; \
+	} \
+}
+
+static int dec_rawsigpass(jpc_dec_t *dec, jpc_bitstream_t *in, int bitpos, int vcausalflag,
+  jas_matrix_t *flags, jas_matrix_t *data)
+{
+	int i;
+	int j;
+	int k;
+	int one;
+	int half;
+	int oneplushalf;
+	int vscanlen;
+	int width;
+	int height;
+	jpc_fix_t *fp;
+	int frowstep;
+	int fstripestep;
+	jpc_fix_t *fstripestart;
+	jpc_fix_t *fvscanstart;
+	jpc_fix_t *dp;
+	int drowstep;
+	int dstripestep;
+	jpc_fix_t *dstripestart;
+	jpc_fix_t *dvscanstart;
+
+	width = jas_matrix_numcols(data);
+	height = jas_matrix_numrows(data);
+	frowstep = jas_matrix_rowstep(flags);
+	drowstep = jas_matrix_rowstep(data);
+	fstripestep = frowstep << 2;
+	dstripestep = drowstep << 2;
+
+	one = 1 << bitpos;
+	half = one >> 1;
+	oneplushalf = one | half;
+
+	fstripestart = jas_matrix_getref(flags, 1, 1);
+	dstripestart = jas_matrix_getref(data, 0, 0);
+	for (i = height; i > 0; i -= 4, fstripestart += fstripestep,
+	  dstripestart += dstripestep) {
+		fvscanstart = fstripestart;
+		dvscanstart = dstripestart;
+		vscanlen = JAS_MIN(i, 4);
+		for (j = width; j > 0; --j, ++fvscanstart, ++dvscanstart) {
+			fp = fvscanstart;
+			dp = dvscanstart;
+			k = vscanlen;
+
+			/* Process first sample in vertical scan. */
+			jpc_rawsigpass_step(fp, frowstep, dp, oneplushalf,
+			  in, vcausalflag);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			/* Process second sample in vertical scan. */
+			jpc_rawsigpass_step(fp, frowstep, dp, oneplushalf,
+			  in, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			/* Process third sample in vertical scan. */
+			jpc_rawsigpass_step(fp, frowstep, dp, oneplushalf,
+			  in, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			/* Process fourth sample in vertical scan. */
+			jpc_rawsigpass_step(fp, frowstep, dp, oneplushalf,
+			  in, 0);
+
+		}
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* Code for refinement pass.
+\******************************************************************************/
+
+#define	jpc_refpass_step(fp, dp, poshalf, neghalf, mqdec, vcausalflag) \
+{ \
+	int v; \
+	int t; \
+	if (((*(fp)) & (JPC_SIG | JPC_VISIT)) == JPC_SIG) { \
+		jpc_mqdec_setcurctx((mqdec), JPC_GETMAGCTXNO(*(fp))); \
+		JPC_T1D_GETBITNOSKEW((mqdec), v, "REF", "MR"); \
+		t = (v ? (poshalf) : (neghalf)); \
+		*(dp) += (*(dp) < 0) ? (-t) : t; \
+		*(fp) |= JPC_REFINE; \
+	} \
+}
+
+static int dec_refpass(jpc_dec_t *dec, register jpc_mqdec_t *mqdec, int bitpos,
+  int vcausalflag, jas_matrix_t *flags, jas_matrix_t *data)
+{
+	int i;
+	int j;
+	int vscanlen;
+	int width;
+	int height;
+	int one;
+	int poshalf;
+	int neghalf;
+	jpc_fix_t *fp;
+	int frowstep;
+	int fstripestep;
+	jpc_fix_t *fstripestart;
+	jpc_fix_t *fvscanstart;
+	jpc_fix_t *dp;
+	int drowstep;
+	int dstripestep;
+	jpc_fix_t *dstripestart;
+	jpc_fix_t *dvscanstart;
+	int k;
+
+	width = jas_matrix_numcols(data);
+	height = jas_matrix_numrows(data);
+	frowstep = jas_matrix_rowstep(flags);
+	drowstep = jas_matrix_rowstep(data);
+	fstripestep = frowstep << 2;
+	dstripestep = drowstep << 2;
+
+	one = 1 << bitpos;
+	poshalf = one >> 1;
+	neghalf = (bitpos > 0) ? (-poshalf) : (-1);
+
+	fstripestart = jas_matrix_getref(flags, 1, 1);
+	dstripestart = jas_matrix_getref(data, 0, 0);
+	for (i = height; i > 0; i -= 4, fstripestart += fstripestep,
+	  dstripestart += dstripestep) {
+		fvscanstart = fstripestart;
+		dvscanstart = dstripestart;
+		vscanlen = JAS_MIN(i, 4);
+		for (j = width; j > 0; --j, ++fvscanstart, ++dvscanstart) {
+			fp = fvscanstart;
+			dp = dvscanstart;
+			k = vscanlen;
+
+			/* Process first sample in vertical scan. */
+			jpc_refpass_step(fp, dp, poshalf, neghalf, mqdec,
+			  vcausalflag);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			/* Process second sample in vertical scan. */
+			jpc_refpass_step(fp, dp, poshalf, neghalf, mqdec, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			/* Process third sample in vertical scan. */
+			jpc_refpass_step(fp, dp, poshalf, neghalf, mqdec, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			/* Process fourth sample in vertical scan. */
+			jpc_refpass_step(fp, dp, poshalf, neghalf, mqdec, 0);
+		}
+	}
+
+	return 0;
+}
+
+#define	jpc_rawrefpass_step(fp, dp, poshalf, neghalf, in, vcausalflag) \
+{ \
+	jpc_fix_t v; \
+	jpc_fix_t t; \
+	if (((*(fp)) & (JPC_SIG | JPC_VISIT)) == JPC_SIG) { \
+		JPC_T1D_RAWGETBIT(in, v, "REF", "MAGREF"); \
+		if (v < 0) { \
+			return -1; \
+		} \
+		t = (v ? poshalf : neghalf); \
+		*(dp) += (*(dp) < 0) ? (-t) : t; \
+		*(fp) |= JPC_REFINE; \
+	} \
+}
+
+static int dec_rawrefpass(jpc_dec_t *dec, jpc_bitstream_t *in, int bitpos, int vcausalflag,
+  jas_matrix_t *flags, jas_matrix_t *data)
+{
+	int i;
+	int j;
+	int k;
+	int vscanlen;
+	int width;
+	int height;
+	int one;
+	int poshalf;
+	int neghalf;
+	jpc_fix_t *fp;
+	int frowstep;
+	int fstripestep;
+	jpc_fix_t *fstripestart;
+	jpc_fix_t *fvscanstart;
+	jpc_fix_t *dp;
+	int drowstep;
+	int dstripestep;
+	jpc_fix_t *dstripestart;
+	jpc_fix_t *dvscanstart;
+
+	width = jas_matrix_numcols(data);
+	height = jas_matrix_numrows(data);
+	frowstep = jas_matrix_rowstep(flags);
+	drowstep = jas_matrix_rowstep(data);
+	fstripestep = frowstep << 2;
+	dstripestep = drowstep << 2;
+
+	one = 1 << bitpos;
+	poshalf = one >> 1;
+	neghalf = (bitpos > 0) ? (-poshalf) : (-1);
+
+	fstripestart = jas_matrix_getref(flags, 1, 1);
+	dstripestart = jas_matrix_getref(data, 0, 0);
+	for (i = height; i > 0; i -= 4, fstripestart += fstripestep,
+	  dstripestart += dstripestep) {
+		fvscanstart = fstripestart;
+		dvscanstart = dstripestart;
+		vscanlen = JAS_MIN(i, 4);
+		for (j = width; j > 0; --j, ++fvscanstart, ++dvscanstart) {
+			fp = fvscanstart;
+			dp = dvscanstart;
+			k = vscanlen;
+
+			/* Process first sample in vertical scan. */
+			jpc_rawrefpass_step(fp, dp, poshalf, neghalf, in,
+			  vcausalflag);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			/* Process second sample in vertical scan. */
+			jpc_rawrefpass_step(fp, dp, poshalf, neghalf, in, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			/* Process third sample in vertical scan. */
+			jpc_rawrefpass_step(fp, dp, poshalf, neghalf, in, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			/* Process fourth sample in vertical scan. */
+			jpc_rawrefpass_step(fp, dp, poshalf, neghalf, in, 0);
+		}
+	}
+	return 0;
+}
+
+/******************************************************************************\
+* Code for cleanup pass.
+\******************************************************************************/
+
+#define	jpc_clnpass_step(f, fp, frowstep, dp, oneplushalf, orient, mqdec, flabel, plabel, vcausalflag) \
+{ \
+	int v; \
+flabel \
+	if (!((f) & (JPC_SIG | JPC_VISIT))) { \
+		jpc_mqdec_setcurctx((mqdec), JPC_GETZCCTXNO((f), (orient))); \
+		JPC_T1D_GETBIT((mqdec), v, "CLN", "ZC"); \
+		if (v) { \
+plabel \
+			/* Coefficient is significant. */ \
+			jpc_mqdec_setcurctx((mqdec), JPC_GETSCCTXNO(f)); \
+			JPC_T1D_GETBIT((mqdec), v, "CLN", "SC"); \
+			v ^= JPC_GETSPB(f); \
+			*(dp) = (v) ? (-(oneplushalf)) : (oneplushalf); \
+			JPC_UPDATEFLAGS4((fp), (frowstep), v, (vcausalflag)); \
+			*(fp) |= JPC_SIG; \
+		} \
+	} \
+	/* XXX - Is this correct?  Can aggregation cause some VISIT bits not to be reset?  Check. */ \
+	*(fp) &= ~JPC_VISIT; \
+}
+
+static int dec_clnpass(jpc_dec_t *dec, register jpc_mqdec_t *mqdec, int bitpos, int orient,
+  int vcausalflag, int segsymflag, jas_matrix_t *flags, jas_matrix_t *data)
+{
+	int i;
+	int j;
+	int k;
+	int vscanlen;
+	int v;
+	int half;
+	int runlen;
+	int f;
+	int width;
+	int height;
+	int one;
+	int oneplushalf;
+
+	jpc_fix_t *fp;
+	int frowstep;
+	int fstripestep;
+	jpc_fix_t *fstripestart;
+	jpc_fix_t *fvscanstart;
+
+	jpc_fix_t *dp;
+	int drowstep;
+	int dstripestep;
+	jpc_fix_t *dstripestart;
+	jpc_fix_t *dvscanstart;
+
+	one = 1 << bitpos;
+	half = one >> 1;
+	oneplushalf = one | half;
+
+	width = jas_matrix_numcols(data);
+	height = jas_matrix_numrows(data);
+
+	frowstep = jas_matrix_rowstep(flags);
+	drowstep = jas_matrix_rowstep(data);
+	fstripestep = frowstep << 2;
+	dstripestep = drowstep << 2;
+
+	fstripestart = jas_matrix_getref(flags, 1, 1);
+	dstripestart = jas_matrix_getref(data, 0, 0);
+	for (i = 0; i < height; i += 4, fstripestart += fstripestep,
+	  dstripestart += dstripestep) {
+		fvscanstart = fstripestart;
+		dvscanstart = dstripestart;
+		vscanlen = JAS_MIN(4, height - i);
+		for (j = width; j > 0; --j, ++fvscanstart, ++dvscanstart) {
+			fp = fvscanstart;
+			if (vscanlen >= 4 && (!((*fp) & (JPC_SIG | JPC_VISIT |
+			  JPC_OTHSIGMSK))) && (fp += frowstep, !((*fp) & (JPC_SIG |
+			  JPC_VISIT | JPC_OTHSIGMSK))) && (fp += frowstep, !((*fp) &
+			  (JPC_SIG | JPC_VISIT | JPC_OTHSIGMSK))) && (fp += frowstep,
+			  !((*fp) & (JPC_SIG | JPC_VISIT | JPC_OTHSIGMSK)))) {
+
+				jpc_mqdec_setcurctx(mqdec, JPC_AGGCTXNO);
+				JPC_T1D_GETBIT(mqdec, v, "CLN", "AGG");
+				if (!v) {
+					continue;
+				}
+				jpc_mqdec_setcurctx(mqdec, JPC_UCTXNO);
+				JPC_T1D_GETBITNOSKEW(mqdec, v, "CLN", "RL");
+				runlen = v;
+				JPC_T1D_GETBITNOSKEW(mqdec, v, "CLN", "RL");
+				runlen = (runlen << 1) | v;
+				f = *(fp = fvscanstart + frowstep * runlen);
+				dp = dvscanstart + drowstep * runlen;
+				k = vscanlen - runlen;
+				switch (runlen) {
+				case 0:
+					goto clnpass_partial0;
+					break;
+				case 1:
+					goto clnpass_partial1;
+					break;
+				case 2:
+					goto clnpass_partial2;
+					break;
+				case 3:
+					goto clnpass_partial3;
+					break;
+				}
+			} else {
+				f = *(fp = fvscanstart);
+				dp = dvscanstart;
+				k = vscanlen;
+				goto clnpass_full0;
+			}
+
+			/* Process first sample in vertical scan. */
+			jpc_clnpass_step(f, fp, frowstep, dp, oneplushalf, orient,
+			  mqdec, clnpass_full0:, clnpass_partial0:,
+			  vcausalflag);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			/* Process second sample in vertical scan. */
+			f = *fp;
+			jpc_clnpass_step(f, fp, frowstep, dp, oneplushalf, orient,
+				mqdec, ;, clnpass_partial1:, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			/* Process third sample in vertical scan. */
+			f = *fp;
+			jpc_clnpass_step(f, fp, frowstep, dp, oneplushalf, orient,
+				mqdec, ;, clnpass_partial2:, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			/* Process fourth sample in vertical scan. */
+			f = *fp;
+			jpc_clnpass_step(f, fp, frowstep, dp, oneplushalf, orient,
+				mqdec, ;, clnpass_partial3:, 0);
+		}
+	}
+
+	if (segsymflag) {
+		int segsymval;
+		segsymval = 0;
+		jpc_mqdec_setcurctx(mqdec, JPC_UCTXNO);
+		JPC_T1D_GETBITNOSKEW(mqdec, v, "CLN", "SEGSYM");
+		segsymval = (segsymval << 1) | (v & 1);
+		JPC_T1D_GETBITNOSKEW(mqdec, v, "CLN", "SEGSYM");
+		segsymval = (segsymval << 1) | (v & 1);
+		JPC_T1D_GETBITNOSKEW(mqdec, v, "CLN", "SEGSYM");
+		segsymval = (segsymval << 1) | (v & 1);
+		JPC_T1D_GETBITNOSKEW(mqdec, v, "CLN", "SEGSYM");
+		segsymval = (segsymval << 1) | (v & 1);
+		if (segsymval != 0xa) {
+			fprintf(stderr, "warning: bad segmentation symbol\n");
+		}
+	}
+
+	return 0;
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_t1dec.h b/converter/other/jpeg2000/libjasper/jpc/jpc_t1dec.h
new file mode 100644
index 00000000..e28a1f57
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_t1dec.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Tier 1 Decoder
+ *
+ * $Id$
+ */
+
+#ifndef JPC_T1DEC_H
+#define JPC_T1DEC_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jpc_dec.h"
+#include "jpc_mqdec.h"
+#include "jpc_t1cod.h"
+
+/******************************************************************************\
+* Functions.
+\******************************************************************************/
+
+/* Decode all of the code blocks for a particular tile. */
+int jpc_dec_decodecblks(jpc_dec_t *dec, jpc_dec_tile_t *tile);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_t1enc.c b/converter/other/jpeg2000/libjasper/jpc/jpc_t1enc.c
new file mode 100644
index 00000000..8aa024a9
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_t1enc.c
@@ -0,0 +1,1008 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Tier 1 Encoder
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "jasper/jas_fix.h"
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_math.h"
+
+#include "jpc_t1enc.h"
+#include "jpc_t1cod.h"
+#include "jpc_enc.h"
+#include "jpc_cod.h"
+#include "jpc_math.h"
+
+static int jpc_encsigpass(jpc_mqenc_t *mqenc, int bitpos, int orient, int,
+  jas_matrix_t *flags, jas_matrix_t *data, int term, long *nmsedec);
+
+static int jpc_encrefpass(jpc_mqenc_t *mqenc, int bitpos, int, jas_matrix_t *flags,
+  jas_matrix_t *data, int term, long *nmsedec);
+
+static int jpc_encclnpass(jpc_mqenc_t *mqenc, int bitpos, int orient, int,
+  int, jas_matrix_t *flags, jas_matrix_t *data, int term, long *nmsedec);
+
+static int jpc_encrawsigpass(jpc_bitstream_t *out, int bitpos, int,
+  jas_matrix_t *flags, jas_matrix_t *data, int term, long *nmsedec);
+
+static int jpc_encrawrefpass(jpc_bitstream_t *out, int bitpos, int,
+  jas_matrix_t *flags, jas_matrix_t *data, int term, long *nmsedec);
+
+/******************************************************************************\
+* Code for encoding code blocks.
+\******************************************************************************/
+
+/* Encode all of the code blocks associated with the current tile. */
+int jpc_enc_enccblks(jpc_enc_t *enc)
+{
+	jpc_enc_tcmpt_t *tcmpt;
+	jpc_enc_tcmpt_t *endcomps;
+	jpc_enc_rlvl_t *lvl;
+	jpc_enc_rlvl_t *endlvls;
+	jpc_enc_band_t *band;
+	jpc_enc_band_t *endbands;
+	jpc_enc_cblk_t *cblk;
+	jpc_enc_cblk_t *endcblks;
+	int i;
+	int j;
+	int mx;
+	int bmx;
+	int v;
+	jpc_enc_tile_t *tile;
+	uint_fast32_t prcno;
+	jpc_enc_prc_t *prc;
+
+	tile = enc->curtile;
+
+	endcomps = &tile->tcmpts[tile->numtcmpts];
+	for (tcmpt = tile->tcmpts; tcmpt != endcomps; ++tcmpt) {
+		endlvls = &tcmpt->rlvls[tcmpt->numrlvls];
+		for (lvl = tcmpt->rlvls; lvl != endlvls; ++lvl) {
+			if (!lvl->bands) {
+				continue;
+			}
+			endbands = &lvl->bands[lvl->numbands];
+			for (band = lvl->bands; band != endbands; ++band) {
+				if (!band->data) {
+					continue;
+				}
+				for (prcno = 0, prc = band->prcs; prcno < lvl->numprcs; ++prcno, ++prc) {
+					if (!prc->cblks) {
+						continue;
+					}
+					bmx = 0;
+					endcblks = &prc->cblks[prc->numcblks];
+					for (cblk = prc->cblks; cblk != endcblks; ++cblk) {
+						mx = 0;
+						for (i = 0; i < jas_matrix_numrows(cblk->data); ++i) {
+							for (j = 0; j < jas_matrix_numcols(cblk->data); ++j) {
+								v = abs(jas_matrix_get(cblk->data, i, j));
+								if (v > mx) {
+									mx = v;
+								}
+							}
+						}
+						if (mx > bmx) {
+							bmx = mx;
+						}
+						cblk->numbps = JAS_MAX(jpc_firstone(mx) + 1 - JPC_NUMEXTRABITS, 0);
+					}
+
+					for (cblk = prc->cblks; cblk != endcblks; ++cblk) {
+						cblk->numimsbs = band->numbps - cblk->numbps;
+						assert(cblk->numimsbs >= 0);
+					}
+
+					for (cblk = prc->cblks; cblk != endcblks; ++cblk) {
+						if (jpc_enc_enccblk(enc, cblk->stream, tcmpt, band, cblk)) {
+							return -1;
+						}
+					}
+				}
+			}
+		}
+	}
+	return 0;
+}
+
+static int getthebyte(jas_stream_t *in, long off)
+{
+	int c;
+	long oldpos;
+	oldpos = jas_stream_tell(in);
+	assert(oldpos >= 0);
+	jas_stream_seek(in, off, SEEK_SET);
+	c = jas_stream_peekc(in);
+	jas_stream_seek(in, oldpos, SEEK_SET);
+	return c;
+}
+
+/* Encode a single code block. */
+int jpc_enc_enccblk(jpc_enc_t *enc, jas_stream_t *out, jpc_enc_tcmpt_t *tcmpt, jpc_enc_band_t *band, jpc_enc_cblk_t *cblk)
+{
+	jpc_enc_pass_t *pass;
+	jpc_enc_pass_t *endpasses;
+	int bitpos;
+	int n;
+	int adjust;
+	int ret;
+	int passtype;
+	int t;
+	jpc_bitstream_t *bout;
+	jpc_enc_pass_t *termpass;
+	jpc_enc_rlvl_t *rlvl;
+	int vcausal;
+	int segsym;
+	int termmode;
+	int c;
+
+	bout = 0;
+	rlvl = band->rlvl;
+
+	cblk->stream = jas_stream_memopen(0, 0);
+	assert(cblk->stream);
+	cblk->mqenc = jpc_mqenc_create(JPC_NUMCTXS, cblk->stream);
+	assert(cblk->mqenc);
+	jpc_mqenc_setctxs(cblk->mqenc, JPC_NUMCTXS, jpc_mqctxs);
+
+	cblk->numpasses = (cblk->numbps > 0) ? (3 * cblk->numbps - 2) : 0;
+	if (cblk->numpasses > 0) {
+		cblk->passes = jas_malloc(cblk->numpasses * sizeof(jpc_enc_pass_t));
+		assert(cblk->passes);
+	} else {
+		cblk->passes = 0;
+	}
+	endpasses = &cblk->passes[cblk->numpasses];
+	for (pass = cblk->passes; pass != endpasses; ++pass) {
+		pass->start = 0;
+		pass->end = 0;
+		pass->term = JPC_ISTERMINATED(pass - cblk->passes, 0, cblk->numpasses, (tcmpt->cblksty & JPC_COX_TERMALL) != 0, (tcmpt->cblksty & JPC_COX_LAZY) != 0);
+		pass->type = JPC_SEGTYPE(pass - cblk->passes, 0, (tcmpt->cblksty & JPC_COX_LAZY) != 0);
+		pass->lyrno = -1;
+if (pass == endpasses - 1) {
+assert(pass->term == 1);
+	pass->term = 1;
+}
+	}
+
+	cblk->flags = jas_matrix_create(jas_matrix_numrows(cblk->data) + 2,
+	  jas_matrix_numcols(cblk->data) + 2);
+	assert(cblk->flags);
+
+
+	bitpos = cblk->numbps - 1;
+	pass = cblk->passes;
+	n = cblk->numpasses;
+	while (--n >= 0) {
+
+		if (pass->type == JPC_SEG_MQ) {
+			/* NOP */
+		} else {
+			assert(pass->type == JPC_SEG_RAW);
+			if (!bout) {
+				bout = jpc_bitstream_sopen(cblk->stream, "w");
+				assert(bout);
+			}
+		}
+
+#if 1
+		passtype = (pass - cblk->passes + 2) % 3;
+#else
+		passtype = JPC_PASSTYPE(pass - cblk->passes + 2);
+#endif
+		pass->start = jas_stream_tell(cblk->stream);
+#if 0
+assert(jas_stream_tell(cblk->stream) == jas_stream_getrwcount(cblk->stream));
+#endif
+		assert(bitpos >= 0);
+		vcausal = (tcmpt->cblksty & JPC_COX_VSC) != 0;
+		segsym = (tcmpt->cblksty & JPC_COX_SEGSYM) != 0;
+		if (pass->term) {
+			termmode = ((tcmpt->cblksty & JPC_COX_PTERM) ?
+			  JPC_MQENC_PTERM : JPC_MQENC_DEFTERM) + 1;
+		} else {
+			termmode = 0;
+		}
+		switch (passtype) {
+		case JPC_SIGPASS:
+			ret = (pass->type == JPC_SEG_MQ) ? jpc_encsigpass(cblk->mqenc,
+			  bitpos, band->orient, vcausal, cblk->flags,
+			  cblk->data, termmode, &pass->nmsedec) :
+			  jpc_encrawsigpass(bout, bitpos, vcausal, cblk->flags,
+			  cblk->data, termmode, &pass->nmsedec);
+			break;
+		case JPC_REFPASS:
+			ret = (pass->type == JPC_SEG_MQ) ? jpc_encrefpass(cblk->mqenc,
+			  bitpos, vcausal, cblk->flags, cblk->data, termmode,
+			  &pass->nmsedec) : jpc_encrawrefpass(bout, bitpos,
+			  vcausal, cblk->flags, cblk->data, termmode,
+			  &pass->nmsedec);
+			break;
+		case JPC_CLNPASS:
+			assert(pass->type == JPC_SEG_MQ);
+			ret = jpc_encclnpass(cblk->mqenc, bitpos, band->orient,
+			  vcausal, segsym, cblk->flags, cblk->data, termmode,
+			  &pass->nmsedec);
+			break;
+		default:
+			assert(0);
+			break;
+		}
+
+		if (pass->type == JPC_SEG_MQ) {
+			if (pass->term) {
+				jpc_mqenc_init(cblk->mqenc);
+			}
+			jpc_mqenc_getstate(cblk->mqenc, &pass->mqencstate);
+			pass->end = jas_stream_tell(cblk->stream);
+			if (tcmpt->cblksty & JPC_COX_RESET) {
+				jpc_mqenc_setctxs(cblk->mqenc, JPC_NUMCTXS, jpc_mqctxs);
+			}
+		} else {
+			if (pass->term) {
+				if (jpc_bitstream_pending(bout)) {
+					jpc_bitstream_outalign(bout, 0x2a);
+				}
+				jpc_bitstream_close(bout);
+				bout = 0;
+				pass->end = jas_stream_tell(cblk->stream);
+			} else {
+				pass->end = jas_stream_tell(cblk->stream) +
+				  jpc_bitstream_pending(bout);
+/* NOTE - This will not work.  need to adjust by # of pending output bytes */
+			}
+		}
+#if 0
+/* XXX - This assertion fails sometimes when various coding modes are used.
+This seems to be harmless, but why does it happen at all? */
+assert(jas_stream_tell(cblk->stream) == jas_stream_getrwcount(cblk->stream));
+#endif
+
+		pass->wmsedec = jpc_fixtodbl(band->rlvl->tcmpt->synweight) *
+		  jpc_fixtodbl(band->rlvl->tcmpt->synweight) *
+		  jpc_fixtodbl(band->synweight) *
+		  jpc_fixtodbl(band->synweight) *
+		  jpc_fixtodbl(band->absstepsize) * jpc_fixtodbl(band->absstepsize) *
+		  ((double) (1 << bitpos)) * ((double)(1 << bitpos)) *
+		  jpc_fixtodbl(pass->nmsedec);
+		pass->cumwmsedec = pass->wmsedec;
+		if (pass != cblk->passes) {
+			pass->cumwmsedec += pass[-1].cumwmsedec;
+		}
+		if (passtype == JPC_CLNPASS) {
+			--bitpos;
+		}
+		++pass;
+	}
+
+#if 0
+dump_passes(cblk->passes, cblk->numpasses, cblk);
+#endif
+
+	n = 0;
+	endpasses = &cblk->passes[cblk->numpasses];
+	for (pass = cblk->passes; pass != endpasses; ++pass) {
+		if (pass->start < n) {
+			pass->start = n;
+		}
+		if (pass->end < n) {
+			pass->end = n;
+		}
+		if (!pass->term) {
+			termpass = pass;
+			while (termpass - pass < cblk->numpasses &&
+			  !termpass->term) {
+				++termpass;
+			}
+			if (pass->type == JPC_SEG_MQ) {
+				t = (pass->mqencstate.lastbyte == 0xff) ? 1 : 0;
+				if (pass->mqencstate.ctreg >= 5) {
+					adjust = 4 + t;
+				} else {
+					adjust = 5 + t;
+				}
+				pass->end += adjust;
+			}
+			if (pass->end > termpass->end) {
+				pass->end = termpass->end;
+			}
+			if ((c = getthebyte(cblk->stream, pass->end - 1)) == EOF) {
+				abort();
+			}
+			if (c == 0xff) {
+				++pass->end;
+			}
+			n = JAS_MAX(n, pass->end);
+		} else {
+			n = JAS_MAX(n, pass->end);
+		}
+	}
+
+#if 0
+dump_passes(cblk->passes, cblk->numpasses, cblk);
+#endif
+
+	if (bout) {
+		jpc_bitstream_close(bout);
+	}
+
+	return 0;
+}
+
+/******************************************************************************\
+* Code for significance pass.
+\******************************************************************************/
+
+#define	sigpass_step(fp, frowstep, dp, bitpos, one, nmsedec, orient, mqenc, vcausalflag) \
+{ \
+	int f; \
+	int v; \
+	f = *(fp); \
+	if ((f & JPC_OTHSIGMSK) && !(f & (JPC_SIG | JPC_VISIT))) { \
+		v = (abs(*(dp)) & (one)) ? 1 : 0; \
+		jpc_mqenc_setcurctx(mqenc, JPC_GETZCCTXNO(f, (orient))); \
+		jpc_mqenc_putbit(mqenc, v); \
+		if (v) { \
+			*(nmsedec) += JPC_GETSIGNMSEDEC(abs(*(dp)), (bitpos) + JPC_NUMEXTRABITS); \
+			v = ((*(dp) < 0) ? 1 : 0); \
+			jpc_mqenc_setcurctx(mqenc, JPC_GETSCCTXNO(f)); \
+			jpc_mqenc_putbit(mqenc, v ^ JPC_GETSPB(f)); \
+			JPC_UPDATEFLAGS4(fp, frowstep, v, vcausalflag); \
+			*(fp) |= JPC_SIG; \
+		} \
+		*(fp) |= JPC_VISIT; \
+	} \
+}
+
+static int jpc_encsigpass(jpc_mqenc_t *mqenc, int bitpos, int orient, int vcausalflag,
+  jas_matrix_t *flags, jas_matrix_t *data, int term, long *nmsedec)
+{
+	int i;
+	int j;
+	int one;
+	int vscanlen;
+	int width;
+	int height;
+	int frowstep;
+	int drowstep;
+	int fstripestep;
+	int dstripestep;
+	jpc_fix_t *fstripestart;
+	jpc_fix_t *dstripestart;
+	jpc_fix_t *fp;
+	jpc_fix_t *dp;
+	jpc_fix_t *fvscanstart;
+	jpc_fix_t *dvscanstart;
+	int k;
+
+	*nmsedec = 0;
+	width = jas_matrix_numcols(data);
+	height = jas_matrix_numrows(data);
+	frowstep = jas_matrix_rowstep(flags);
+	drowstep = jas_matrix_rowstep(data);
+	fstripestep = frowstep << 2;
+	dstripestep = drowstep << 2;
+
+	one = 1 << (bitpos + JPC_NUMEXTRABITS);
+
+	fstripestart = jas_matrix_getref(flags, 1, 1);
+	dstripestart = jas_matrix_getref(data, 0, 0);
+	for (i = height; i > 0; i -= 4, fstripestart += fstripestep,
+	  dstripestart += dstripestep) {
+		fvscanstart = fstripestart;
+		dvscanstart = dstripestart;
+		vscanlen = JAS_MIN(i, 4);
+		for (j = width; j > 0; --j, ++fvscanstart, ++dvscanstart) {
+			fp = fvscanstart;
+			dp = dvscanstart;
+			k = vscanlen;
+
+			sigpass_step(fp, frowstep, dp, bitpos, one,
+			  nmsedec, orient, mqenc, vcausalflag);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+			sigpass_step(fp, frowstep, dp, bitpos, one,
+			  nmsedec, orient, mqenc, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+			sigpass_step(fp, frowstep, dp, bitpos, one,
+			  nmsedec, orient, mqenc, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+			sigpass_step(fp, frowstep, dp, bitpos, one,
+			  nmsedec, orient, mqenc, 0);
+
+		}
+	}
+
+	if (term) {
+		jpc_mqenc_flush(mqenc, term - 1);
+	}
+
+	return jpc_mqenc_error(mqenc) ? (-1) : 0;
+}
+
+#define	rawsigpass_step(fp, frowstep, dp, bitpos, one, nmsedec, out, vcausalflag) \
+{ \
+	jpc_fix_t f = *(fp); \
+	jpc_fix_t v; \
+	if ((f & JPC_OTHSIGMSK) && !(f & (JPC_SIG | JPC_VISIT))) { \
+		v = (abs(*(dp)) & (one)) ? 1 : 0; \
+		if ((jpc_bitstream_putbit((out), v)) == EOF) { \
+			return -1; \
+		} \
+		if (v) { \
+			*(nmsedec) += JPC_GETSIGNMSEDEC(abs(*(dp)), (bitpos) + JPC_NUMEXTRABITS); \
+			v = ((*(dp) < 0) ? 1 : 0); \
+			if (jpc_bitstream_putbit(out, v) == EOF) { \
+				return -1; \
+			} \
+			JPC_UPDATEFLAGS4(fp, frowstep, v, vcausalflag); \
+			*(fp) |= JPC_SIG; \
+		} \
+		*(fp) |= JPC_VISIT; \
+	} \
+}
+
+static int jpc_encrawsigpass(jpc_bitstream_t *out, int bitpos, int vcausalflag, jas_matrix_t *flags,
+  jas_matrix_t *data, int term, long *nmsedec)
+{
+	int i;
+	int j;
+	int k;
+	int one;
+	int vscanlen;
+	int width;
+	int height;
+	int frowstep;
+	int drowstep;
+	int fstripestep;
+	int dstripestep;
+	jpc_fix_t *fstripestart;
+	jpc_fix_t *dstripestart;
+	jpc_fix_t *fp;
+	jpc_fix_t *dp;
+	jpc_fix_t *fvscanstart;
+	jpc_fix_t *dvscanstart;
+
+	*nmsedec = 0;
+	width = jas_matrix_numcols(data);
+	height = jas_matrix_numrows(data);
+	frowstep = jas_matrix_rowstep(flags);
+	drowstep = jas_matrix_rowstep(data);
+	fstripestep = frowstep << 2;
+	dstripestep = drowstep << 2;
+
+	one = 1 << (bitpos + JPC_NUMEXTRABITS);
+
+	fstripestart = jas_matrix_getref(flags, 1, 1);
+	dstripestart = jas_matrix_getref(data, 0, 0);
+	for (i = height; i > 0; i -= 4, fstripestart += fstripestep,
+	  dstripestart += dstripestep) {
+		fvscanstart = fstripestart;
+		dvscanstart = dstripestart;
+		vscanlen = JAS_MIN(i, 4);
+		for (j = width; j > 0; --j, ++fvscanstart, ++dvscanstart) {
+			fp = fvscanstart;
+			dp = dvscanstart;
+			k = vscanlen;
+
+			rawsigpass_step(fp, frowstep, dp, bitpos, one,
+			  nmsedec, out, vcausalflag);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			rawsigpass_step(fp, frowstep, dp, bitpos, one,
+			  nmsedec, out, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			rawsigpass_step(fp, frowstep, dp, bitpos, one,
+			  nmsedec, out, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+			rawsigpass_step(fp, frowstep, dp, bitpos, one,
+			  nmsedec, out, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+
+		}
+	}
+
+	if (term) {
+		jpc_bitstream_outalign(out, 0x2a);
+	}
+
+	return 0;
+}
+
+/******************************************************************************\
+* Code for refinement pass.
+\******************************************************************************/
+
+#define	refpass_step(fp, dp, bitpos, one, nmsedec, mqenc, vcausalflag) \
+{ \
+	int v; \
+	if (((*(fp)) & (JPC_SIG | JPC_VISIT)) == JPC_SIG) { \
+		(d) = *(dp); \
+		*(nmsedec) += JPC_GETREFNMSEDEC(abs(d), (bitpos) + JPC_NUMEXTRABITS); \
+		jpc_mqenc_setcurctx((mqenc), JPC_GETMAGCTXNO(*(fp))); \
+		v = (abs(d) & (one)) ? 1 : 0; \
+		jpc_mqenc_putbit((mqenc), v); \
+		*(fp) |= JPC_REFINE; \
+	} \
+}
+
+static int jpc_encrefpass(jpc_mqenc_t *mqenc, int bitpos, int vcausalflag, jas_matrix_t *flags, jas_matrix_t *data,
+  int term, long *nmsedec)
+{
+	int i;
+	int j;
+	int one;
+	int vscanlen;
+	int d;
+	int width;
+	int height;
+	int frowstep;
+	int drowstep;
+	int fstripestep;
+	int dstripestep;
+	jpc_fix_t *fstripestart;
+	jpc_fix_t *dstripestart;
+	jpc_fix_t *fvscanstart;
+	jpc_fix_t *dvscanstart;
+	jpc_fix_t *dp;
+	jpc_fix_t *fp;
+int k;
+
+	*nmsedec = 0;
+	width = jas_matrix_numcols(data);
+	height = jas_matrix_numrows(data);
+	frowstep = jas_matrix_rowstep(flags);
+	drowstep = jas_matrix_rowstep(data);
+	fstripestep = frowstep << 2;
+	dstripestep = drowstep << 2;
+
+	one = 1 << (bitpos + JPC_NUMEXTRABITS);
+
+	fstripestart = jas_matrix_getref(flags, 1, 1);
+	dstripestart = jas_matrix_getref(data, 0, 0);
+	for (i = height; i > 0; i -= 4, fstripestart += fstripestep,
+	  dstripestart += dstripestep) {
+		fvscanstart = fstripestart;
+		dvscanstart = dstripestart;
+		vscanlen = JAS_MIN(i, 4);
+		for (j = width; j > 0; --j, ++fvscanstart, ++dvscanstart) {
+			fp = fvscanstart;
+			dp = dvscanstart;
+			k = vscanlen;
+
+			refpass_step(fp, dp, bitpos, one, nmsedec,
+			  mqenc, vcausalflag);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+			refpass_step(fp, dp, bitpos, one, nmsedec,
+			  mqenc, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+			refpass_step(fp, dp, bitpos, one, nmsedec,
+			  mqenc, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+			refpass_step(fp, dp, bitpos, one, nmsedec,
+			  mqenc, 0);
+
+		}
+	}
+
+	if (term) {
+		jpc_mqenc_flush(mqenc, term - 1);
+	}
+
+	return jpc_mqenc_error(mqenc) ? (-1) : 0;
+}
+
+#define	rawrefpass_step(fp, dp, bitpos, one, nmsedec, out, vcausalflag) \
+{ \
+	jpc_fix_t d; \
+	jpc_fix_t v; \
+	if (((*(fp)) & (JPC_SIG | JPC_VISIT)) == JPC_SIG) { \
+		d = *(dp); \
+		*(nmsedec) += JPC_GETREFNMSEDEC(abs(d), (bitpos) + JPC_NUMEXTRABITS); \
+		v = (abs(d) & (one)) ? 1 : 0; \
+		if (jpc_bitstream_putbit((out), v) == EOF) { \
+			return -1; \
+		} \
+		*(fp) |= JPC_REFINE; \
+	} \
+}
+
+static int jpc_encrawrefpass(jpc_bitstream_t *out, int bitpos, int vcausalflag, jas_matrix_t *flags,
+  jas_matrix_t *data, int term, long *nmsedec)
+{
+	int i;
+	int j;
+	int k;
+	int one;
+	int vscanlen;
+	int width;
+	int height;
+	int frowstep;
+	int drowstep;
+	int fstripestep;
+	int dstripestep;
+	jpc_fix_t *fstripestart;
+	jpc_fix_t *dstripestart;
+	jpc_fix_t *fvscanstart;
+	jpc_fix_t *dvscanstart;
+	jpc_fix_t *dp;
+	jpc_fix_t *fp;
+
+	*nmsedec = 0;
+	width = jas_matrix_numcols(data);
+	height = jas_matrix_numrows(data);
+	frowstep = jas_matrix_rowstep(flags);
+	drowstep = jas_matrix_rowstep(data);
+	fstripestep = frowstep << 2;
+	dstripestep = drowstep << 2;
+
+	one = 1 << (bitpos + JPC_NUMEXTRABITS);
+
+	fstripestart = jas_matrix_getref(flags, 1, 1);
+	dstripestart = jas_matrix_getref(data, 0, 0);
+	for (i = height; i > 0; i -= 4, fstripestart += fstripestep,
+	  dstripestart += dstripestep) {
+		fvscanstart = fstripestart;
+		dvscanstart = dstripestart;
+		vscanlen = JAS_MIN(i, 4);
+		for (j = width; j > 0; --j, ++fvscanstart, ++dvscanstart) {
+			fp = fvscanstart;
+			dp = dvscanstart;
+			k = vscanlen;
+
+			rawrefpass_step(fp, dp, bitpos, one, nmsedec,
+			  out, vcausalflag);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+			rawrefpass_step(fp, dp, bitpos, one, nmsedec,
+			  out, vcausalflag);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+			rawrefpass_step(fp, dp, bitpos, one, nmsedec,
+			  out, vcausalflag);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+			rawrefpass_step(fp, dp, bitpos, one, nmsedec,
+			  out, vcausalflag);
+
+		}
+	}
+
+	if (term) {
+		jpc_bitstream_outalign(out, 0x2a);
+	}
+
+	return 0;
+}
+
+/******************************************************************************\
+* Code for cleanup pass.
+\******************************************************************************/
+
+#define	clnpass_step(fp, frowstep, dp, bitpos, one, orient, nmsedec, mqenc, label1, label2, vcausalflag) \
+{ \
+	int f; \
+	int v; \
+label1 \
+	f = *(fp); \
+	if (!(f & (JPC_SIG | JPC_VISIT))) { \
+		jpc_mqenc_setcurctx(mqenc, JPC_GETZCCTXNO(f, (orient))); \
+		v = (abs(*(dp)) & (one)) ? 1 : 0; \
+		jpc_mqenc_putbit((mqenc), v); \
+		if (v) { \
+label2 \
+			f = *(fp); \
+			/* Coefficient is significant. */ \
+			*(nmsedec) += JPC_GETSIGNMSEDEC(abs(*(dp)), (bitpos) + JPC_NUMEXTRABITS); \
+			jpc_mqenc_setcurctx((mqenc), JPC_GETSCCTXNO(f)); \
+			v = ((*(dp) < 0) ? 1 : 0); \
+			jpc_mqenc_putbit((mqenc), v ^ JPC_GETSPB(f)); \
+			JPC_UPDATEFLAGS4((fp), (frowstep), v, vcausalflag); \
+			*(fp) |= JPC_SIG; \
+		} \
+	} \
+	*(fp) &= ~JPC_VISIT; \
+}
+
+static int jpc_encclnpass(jpc_mqenc_t *mqenc, int bitpos, int orient, int vcausalflag, int segsymflag, jas_matrix_t *flags,
+  jas_matrix_t *data, int term, long *nmsedec)
+{
+	int i;
+	int j;
+	int k;
+	int vscanlen;
+	int v;
+	int runlen;
+	jpc_fix_t *fp;
+	int width;
+	int height;
+	jpc_fix_t *dp;
+	int one;
+	int frowstep;
+	int drowstep;
+	int fstripestep;
+	int dstripestep;
+	jpc_fix_t *fstripestart;
+	jpc_fix_t *dstripestart;
+	jpc_fix_t *fvscanstart;
+	jpc_fix_t *dvscanstart;
+
+	*nmsedec = 0;
+	width = jas_matrix_numcols(data);
+	height = jas_matrix_numrows(data);
+	frowstep = jas_matrix_rowstep(flags);
+	drowstep = jas_matrix_rowstep(data);
+	fstripestep = frowstep << 2;
+	dstripestep = drowstep << 2;
+
+	one = 1 << (bitpos + JPC_NUMEXTRABITS);
+
+	fstripestart = jas_matrix_getref(flags, 1, 1);
+	dstripestart = jas_matrix_getref(data, 0, 0);
+	for (i = height; i > 0; i -= 4, fstripestart += fstripestep,
+	  dstripestart += dstripestep) {
+		fvscanstart = fstripestart;
+		dvscanstart = dstripestart;
+		vscanlen = JAS_MIN(i, 4);
+		for (j = width; j > 0; --j, ++fvscanstart, ++dvscanstart) {
+
+			fp = fvscanstart;
+			if (vscanlen >= 4 && !((*fp) & (JPC_SIG | JPC_VISIT |
+			  JPC_OTHSIGMSK)) && (fp += frowstep, !((*fp) & (JPC_SIG |
+			  JPC_VISIT | JPC_OTHSIGMSK))) && (fp += frowstep, !((*fp) &
+			  (JPC_SIG | JPC_VISIT | JPC_OTHSIGMSK))) && (fp += frowstep,
+			  !((*fp) & (JPC_SIG | JPC_VISIT | JPC_OTHSIGMSK)))) {
+				dp = dvscanstart;
+				for (k = 0; k < vscanlen; ++k) {
+					v = (abs(*dp) & one) ? 1 : 0;
+					if (v) {
+						break;
+					}
+					dp += drowstep;
+				}
+				runlen = k;
+				if (runlen >= 4) {
+					jpc_mqenc_setcurctx(mqenc, JPC_AGGCTXNO);
+					jpc_mqenc_putbit(mqenc, 0);
+					continue;
+				}
+				jpc_mqenc_setcurctx(mqenc, JPC_AGGCTXNO);
+				jpc_mqenc_putbit(mqenc, 1);
+				jpc_mqenc_setcurctx(mqenc, JPC_UCTXNO);
+				jpc_mqenc_putbit(mqenc, runlen >> 1);
+				jpc_mqenc_putbit(mqenc, runlen & 1);
+				fp = fvscanstart + frowstep * runlen;
+				dp = dvscanstart + drowstep * runlen;
+				k = vscanlen - runlen;
+				switch (runlen) {
+				case 0:
+					goto clnpass_partial0;
+					break;
+				case 1:
+					goto clnpass_partial1;
+					break;
+				case 2:
+					goto clnpass_partial2;
+					break;
+				case 3:
+					goto clnpass_partial3;
+					break;
+				}
+			} else {
+				runlen = 0;
+				fp = fvscanstart;
+				dp = dvscanstart;
+				k = vscanlen;
+				goto clnpass_full0;
+			}
+			clnpass_step(fp, frowstep, dp, bitpos, one,
+			  orient, nmsedec, mqenc, clnpass_full0:, clnpass_partial0:, vcausalflag);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+			clnpass_step(fp, frowstep, dp, bitpos, one,
+				orient, nmsedec, mqenc, ;, clnpass_partial1:, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+			clnpass_step(fp, frowstep, dp, bitpos, one,
+				orient, nmsedec, mqenc, ;, clnpass_partial2:, 0);
+			if (--k <= 0) {
+				continue;
+			}
+			fp += frowstep;
+			dp += drowstep;
+			clnpass_step(fp, frowstep, dp, bitpos, one,
+				orient, nmsedec, mqenc, ;, clnpass_partial3:, 0);
+		}
+	}
+
+	if (segsymflag) {
+		jpc_mqenc_setcurctx(mqenc, JPC_UCTXNO);
+		jpc_mqenc_putbit(mqenc, 1);
+		jpc_mqenc_putbit(mqenc, 0);
+		jpc_mqenc_putbit(mqenc, 1);
+		jpc_mqenc_putbit(mqenc, 0);
+	}
+
+	if (term) {
+		jpc_mqenc_flush(mqenc, term - 1);
+	}
+
+	return jpc_mqenc_error(mqenc) ? (-1) : 0;
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_t1enc.h b/converter/other/jpeg2000/libjasper/jpc/jpc_t1enc.h
new file mode 100644
index 00000000..9ce5ed27
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_t1enc.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Tier 1 Encoder
+ *
+ * $Id$
+ */
+
+#ifndef JPC_T1ENC_H
+#define JPC_T1ENC_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_seq.h"
+
+#include "jpc_enc.h"
+#include "jpc_t1cod.h"
+
+/******************************************************************************\
+* Functions.
+\******************************************************************************/
+
+/* Encode all of the code blocks. */
+int jpc_enc_enccblks(jpc_enc_t *enc);
+
+/* Encode a single code block. */
+int jpc_enc_enccblk(jpc_enc_t *enc, jas_stream_t *out, jpc_enc_tcmpt_t *comp,
+  jpc_enc_band_t *band, jpc_enc_cblk_t *cblk);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_t2cod.c b/converter/other/jpeg2000/libjasper/jpc/jpc_t2cod.c
new file mode 100644
index 00000000..a9ca0dc1
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_t2cod.c
@@ -0,0 +1,733 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Tier-2 Coding Library
+ *
+ * $Id$
+ */
+
+#include "jasper/jas_math.h"
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_math.h"
+
+#include "jpc_cs.h"
+#include "jpc_t2cod.h"
+#include "jpc_math.h"
+
+static int jpc_pi_nextlrcp(jpc_pi_t *pi);
+static int jpc_pi_nextrlcp(jpc_pi_t *pi);
+static int jpc_pi_nextrpcl(jpc_pi_t *pi);
+static int jpc_pi_nextpcrl(jpc_pi_t *pi);
+static int jpc_pi_nextcprl(jpc_pi_t *pi);
+
+int jpc_pi_next(jpc_pi_t *pi)
+{
+	jpc_pchg_t *pchg;
+	int ret;
+
+
+	for (;;) {
+
+		pi->valid = false;
+
+		if (!pi->pchg) {
+			++pi->pchgno;
+			pi->compno = 0;
+			pi->rlvlno = 0;
+			pi->prcno = 0;
+			pi->lyrno = 0;
+			pi->prgvolfirst = true;
+			if (pi->pchgno < jpc_pchglist_numpchgs(pi->pchglist)) {
+				pi->pchg = jpc_pchglist_get(pi->pchglist, pi->pchgno);
+			} else if (pi->pchgno == jpc_pchglist_numpchgs(pi->pchglist)) {
+				pi->pchg = &pi->defaultpchg;
+			} else {
+				return 1;
+			}
+		}
+
+		pchg = pi->pchg;
+		switch (pchg->prgord) {
+		case JPC_COD_LRCPPRG:
+			ret = jpc_pi_nextlrcp(pi);
+			break;
+		case JPC_COD_RLCPPRG:
+			ret = jpc_pi_nextrlcp(pi);
+			break;
+		case JPC_COD_RPCLPRG:
+			ret = jpc_pi_nextrpcl(pi);
+			break;
+		case JPC_COD_PCRLPRG:
+			ret = jpc_pi_nextpcrl(pi);
+			break;
+		case JPC_COD_CPRLPRG:
+			ret = jpc_pi_nextcprl(pi);
+			break;
+		default:
+			ret = -1;
+			break;
+		}
+		if (!ret) {
+			pi->valid = true;
+			++pi->pktno;
+			return 0;
+		}
+		pi->pchg = 0;
+	}
+}
+
+static int jpc_pi_nextlrcp(register jpc_pi_t *pi)
+{
+	jpc_pchg_t *pchg;
+	int *prclyrno;
+
+	pchg = pi->pchg;
+	if (!pi->prgvolfirst) {
+		prclyrno = &pi->pirlvl->prclyrnos[pi->prcno];
+		goto skip;
+	} else {
+		pi->prgvolfirst = false;
+	}
+
+	for (pi->lyrno = 0; pi->lyrno < pi->numlyrs && pi->lyrno <
+	  pchg->lyrnoend; ++pi->lyrno) {
+		for (pi->rlvlno = pchg->rlvlnostart; pi->rlvlno < pi->maxrlvls &&
+		  pi->rlvlno < pchg->rlvlnoend; ++pi->rlvlno) {
+			for (pi->compno = pchg->compnostart, pi->picomp =
+			  &pi->picomps[pi->compno]; pi->compno < pi->numcomps
+			  && pi->compno < pchg->compnoend; ++pi->compno,
+			  ++pi->picomp) {
+				if (pi->rlvlno >= pi->picomp->numrlvls) {
+					continue;
+				}
+				pi->pirlvl = &pi->picomp->pirlvls[pi->rlvlno];
+				for (pi->prcno = 0, prclyrno =
+				  pi->pirlvl->prclyrnos; pi->prcno <
+				  pi->pirlvl->numprcs; ++pi->prcno,
+				  ++prclyrno) {
+					if (pi->lyrno >= *prclyrno) {
+						*prclyrno = pi->lyrno;
+						++(*prclyrno);
+						return 0;
+					}
+skip:
+					;
+				}
+			}
+		}
+	}
+	return 1;
+}
+
+static int jpc_pi_nextrlcp(register jpc_pi_t *pi)
+{
+	jpc_pchg_t *pchg;
+	int *prclyrno;
+
+	pchg = pi->pchg;
+	if (!pi->prgvolfirst) {
+		assert(pi->prcno < pi->pirlvl->numprcs);
+		prclyrno = &pi->pirlvl->prclyrnos[pi->prcno];
+		goto skip;
+	} else {
+		pi->prgvolfirst = 0;
+	}
+
+	for (pi->rlvlno = pchg->rlvlnostart; pi->rlvlno < pi->maxrlvls &&
+	  pi->rlvlno < pchg->rlvlnoend; ++pi->rlvlno) {
+		for (pi->lyrno = 0; pi->lyrno < pi->numlyrs && pi->lyrno <
+		  pchg->lyrnoend; ++pi->lyrno) {
+			for (pi->compno = pchg->compnostart, pi->picomp =
+			  &pi->picomps[pi->compno]; pi->compno < pi->numcomps &&
+			  pi->compno < pchg->compnoend; ++pi->compno, ++pi->picomp) {
+				if (pi->rlvlno >= pi->picomp->numrlvls) {
+					continue;
+				}
+				pi->pirlvl = &pi->picomp->pirlvls[pi->rlvlno];
+				for (pi->prcno = 0, prclyrno = pi->pirlvl->prclyrnos;
+				  pi->prcno < pi->pirlvl->numprcs; ++pi->prcno, ++prclyrno) {
+					if (pi->lyrno >= *prclyrno) {
+						*prclyrno = pi->lyrno;
+						++(*prclyrno);
+						return 0;
+					}
+skip:
+					;
+				}
+			}
+		}
+	}
+	return 1;
+}
+
+static int jpc_pi_nextrpcl(register jpc_pi_t *pi)
+{
+	int rlvlno;
+	jpc_pirlvl_t *pirlvl;
+	jpc_pchg_t *pchg;
+	int prchind;
+	int prcvind;
+	int *prclyrno;
+	int compno;
+	jpc_picomp_t *picomp;
+	int xstep;
+	int ystep;
+	uint_fast32_t r;
+	uint_fast32_t rpx;
+	uint_fast32_t rpy;
+	uint_fast32_t trx0;
+	uint_fast32_t try0;
+
+	pchg = pi->pchg;
+	if (!pi->prgvolfirst) {
+		goto skip;
+	} else {
+		pi->xstep = 0;
+		pi->ystep = 0;
+		for (compno = 0, picomp = pi->picomps; compno < pi->numcomps;
+		  ++compno, ++picomp) {
+			for (rlvlno = 0, pirlvl = picomp->pirlvls; rlvlno <
+			  picomp->numrlvls; ++rlvlno, ++pirlvl) {
+				xstep = picomp->hsamp * (1 << (pirlvl->prcwidthexpn +
+				  picomp->numrlvls - rlvlno - 1));
+				ystep = picomp->vsamp * (1 << (pirlvl->prcheightexpn +
+				  picomp->numrlvls - rlvlno - 1));
+				pi->xstep = (!pi->xstep) ? xstep : JAS_MIN(pi->xstep, xstep);
+				pi->ystep = (!pi->ystep) ? ystep : JAS_MIN(pi->ystep, ystep);
+			}
+		}
+		pi->prgvolfirst = 0;
+	}
+
+	for (pi->rlvlno = pchg->rlvlnostart; pi->rlvlno < pchg->rlvlnoend &&
+	  pi->rlvlno < pi->maxrlvls; ++pi->rlvlno) {
+		for (pi->y = pi->ystart; pi->y < pi->yend; pi->y +=
+		  pi->ystep - (pi->y % pi->ystep)) {
+			for (pi->x = pi->xstart; pi->x < pi->xend; pi->x +=
+			  pi->xstep - (pi->x % pi->xstep)) {
+				for (pi->compno = pchg->compnostart,
+				  pi->picomp = &pi->picomps[pi->compno];
+				  pi->compno < pchg->compnoend && pi->compno <
+				  pi->numcomps; ++pi->compno, ++pi->picomp) {
+					if (pi->rlvlno >= pi->picomp->numrlvls) {
+						continue;
+					}
+					pi->pirlvl = &pi->picomp->pirlvls[pi->rlvlno];
+					if (pi->pirlvl->numprcs == 0) {
+						continue;
+					}
+					r = pi->picomp->numrlvls - 1 - pi->rlvlno;
+					rpx = r + pi->pirlvl->prcwidthexpn;
+					rpy = r + pi->pirlvl->prcheightexpn;
+					trx0 = JPC_CEILDIV(pi->xstart, pi->picomp->hsamp << r);
+					try0 = JPC_CEILDIV(pi->ystart, pi->picomp->vsamp << r);
+					if (((pi->x == pi->xstart && ((trx0 << r) % (1 << rpx)))
+					  || !(pi->x % (1 << rpx))) &&
+					  ((pi->y == pi->ystart && ((try0 << r) % (1 << rpy)))
+					  || !(pi->y % (1 << rpy)))) {
+						prchind = JPC_FLOORDIVPOW2(JPC_CEILDIV(pi->x, pi->picomp->hsamp
+						  << r), pi->pirlvl->prcwidthexpn) - JPC_FLOORDIVPOW2(trx0,
+						  pi->pirlvl->prcwidthexpn);
+						prcvind = JPC_FLOORDIVPOW2(JPC_CEILDIV(pi->y, pi->picomp->vsamp
+						  << r), pi->pirlvl->prcheightexpn) - JPC_FLOORDIVPOW2(try0,
+						  pi->pirlvl->prcheightexpn);
+						pi->prcno = prcvind * pi->pirlvl->numhprcs + prchind;
+
+						assert(pi->prcno < pi->pirlvl->numprcs);
+						for (pi->lyrno = 0; pi->lyrno <
+						  pi->numlyrs && pi->lyrno < pchg->lyrnoend; ++pi->lyrno) {
+							prclyrno = &pi->pirlvl->prclyrnos[pi->prcno];
+							if (pi->lyrno >= *prclyrno) {
+								++(*prclyrno);
+								return 0;
+							}
+skip:
+							;
+						}
+					}
+				}
+			}
+		}
+	}
+	return 1;
+}
+
+static int jpc_pi_nextpcrl(register jpc_pi_t *pi)
+{
+	int rlvlno;
+	jpc_pirlvl_t *pirlvl;
+	jpc_pchg_t *pchg;
+	int prchind;
+	int prcvind;
+	int *prclyrno;
+	int compno;
+	jpc_picomp_t *picomp;
+	int xstep;
+	int ystep;
+	uint_fast32_t trx0;
+	uint_fast32_t try0;
+	uint_fast32_t r;
+	uint_fast32_t rpx;
+	uint_fast32_t rpy;
+
+	pchg = pi->pchg;
+	if (!pi->prgvolfirst) {
+		goto skip;
+	} else {
+		pi->xstep = 0;
+		pi->ystep = 0;
+		for (compno = 0, picomp = pi->picomps; compno < pi->numcomps;
+		  ++compno, ++picomp) {
+			for (rlvlno = 0, pirlvl = picomp->pirlvls; rlvlno <
+			  picomp->numrlvls; ++rlvlno, ++pirlvl) {
+				xstep = picomp->hsamp * (1 <<
+				  (pirlvl->prcwidthexpn + picomp->numrlvls -
+				  rlvlno - 1));
+				ystep = picomp->vsamp * (1 <<
+				  (pirlvl->prcheightexpn + picomp->numrlvls -
+				  rlvlno - 1));
+				pi->xstep = (!pi->xstep) ? xstep :
+				  JAS_MIN(pi->xstep, xstep);
+				pi->ystep = (!pi->ystep) ? ystep :
+				  JAS_MIN(pi->ystep, ystep);
+			}
+		}
+		pi->prgvolfirst = 0;
+	}
+
+	for (pi->y = pi->ystart; pi->y < pi->yend; pi->y += pi->ystep -
+	  (pi->y % pi->ystep)) {
+		for (pi->x = pi->xstart; pi->x < pi->xend; pi->x += pi->xstep -
+		  (pi->x % pi->xstep)) {
+			for (pi->compno = pchg->compnostart, pi->picomp =
+			  &pi->picomps[pi->compno]; pi->compno < pi->numcomps
+			  && pi->compno < pchg->compnoend; ++pi->compno,
+			  ++pi->picomp) {
+				for (pi->rlvlno = pchg->rlvlnostart,
+				  pi->pirlvl = &pi->picomp->pirlvls[pi->rlvlno];
+				  pi->rlvlno < pi->picomp->numrlvls &&
+				  pi->rlvlno < pchg->rlvlnoend; ++pi->rlvlno,
+				  ++pi->pirlvl) {
+					if (pi->pirlvl->numprcs == 0) {
+						continue;
+					}
+					r = pi->picomp->numrlvls - 1 - pi->rlvlno;
+					trx0 = JPC_CEILDIV(pi->xstart, pi->picomp->hsamp << r);
+					try0 = JPC_CEILDIV(pi->ystart, pi->picomp->vsamp << r);
+					rpx = r + pi->pirlvl->prcwidthexpn;
+					rpy = r + pi->pirlvl->prcheightexpn;
+					if (((pi->x == pi->xstart && ((trx0 << r) % (1 << rpx))) ||
+					  !(pi->x % (pi->picomp->hsamp << rpx))) &&
+					  ((pi->y == pi->ystart && ((try0 << r) % (1 << rpy))) ||
+					  !(pi->y % (pi->picomp->vsamp << rpy)))) {
+						prchind = JPC_FLOORDIVPOW2(JPC_CEILDIV(pi->x, pi->picomp->hsamp
+						  << r), pi->pirlvl->prcwidthexpn) - JPC_FLOORDIVPOW2(trx0,
+						  pi->pirlvl->prcwidthexpn);
+						prcvind = JPC_FLOORDIVPOW2(JPC_CEILDIV(pi->y, pi->picomp->vsamp
+						  << r), pi->pirlvl->prcheightexpn) - JPC_FLOORDIVPOW2(try0,
+						  pi->pirlvl->prcheightexpn);
+						pi->prcno = prcvind * pi->pirlvl->numhprcs + prchind;
+						assert(pi->prcno < pi->pirlvl->numprcs);
+						for (pi->lyrno = 0; pi->lyrno < pi->numlyrs &&
+						  pi->lyrno < pchg->lyrnoend; ++pi->lyrno) {
+							prclyrno = &pi->pirlvl->prclyrnos[pi->prcno];
+							if (pi->lyrno >= *prclyrno) {
+								++(*prclyrno);
+								return 0;
+							}
+skip:
+							;
+						}
+					}
+				}
+			}
+		}
+	}
+	return 1;
+}
+
+static int jpc_pi_nextcprl(register jpc_pi_t *pi)
+{
+	int rlvlno;
+	jpc_pirlvl_t *pirlvl;
+	jpc_pchg_t *pchg;
+	int prchind;
+	int prcvind;
+	int *prclyrno;
+	uint_fast32_t trx0;
+	uint_fast32_t try0;
+	uint_fast32_t r;
+	uint_fast32_t rpx;
+	uint_fast32_t rpy;
+
+	pchg = pi->pchg;
+	if (!pi->prgvolfirst) {
+		goto skip;
+	} else {
+		pi->prgvolfirst = 0;
+	}
+
+	for (pi->compno = pchg->compnostart, pi->picomp =
+	  &pi->picomps[pi->compno]; pi->compno < pchg->compnoend; ++pi->compno,
+	  ++pi->picomp) {
+		pirlvl = pi->picomp->pirlvls;
+		pi->xstep = pi->picomp->hsamp * (1 << (pirlvl->prcwidthexpn +
+		  pi->picomp->numrlvls - 1));
+		pi->ystep = pi->picomp->vsamp * (1 << (pirlvl->prcheightexpn +
+		  pi->picomp->numrlvls - 1));
+		for (rlvlno = 1, pirlvl = &pi->picomp->pirlvls[1];
+		  rlvlno < pi->picomp->numrlvls; ++rlvlno, ++pirlvl) {
+			pi->xstep = JAS_MIN(pi->xstep, pi->picomp->hsamp * (1 <<
+			  (pirlvl->prcwidthexpn + pi->picomp->numrlvls -
+			  rlvlno - 1)));
+			pi->ystep = JAS_MIN(pi->ystep, pi->picomp->vsamp * (1 <<
+			  (pirlvl->prcheightexpn + pi->picomp->numrlvls -
+			  rlvlno - 1)));
+		}
+		for (pi->y = pi->ystart; pi->y < pi->yend;
+		  pi->y += pi->ystep - (pi->y % pi->ystep)) {
+			for (pi->x = pi->xstart; pi->x < pi->xend;
+			  pi->x += pi->xstep - (pi->x % pi->xstep)) {
+				for (pi->rlvlno = pchg->rlvlnostart,
+				  pi->pirlvl = &pi->picomp->pirlvls[pi->rlvlno];
+				  pi->rlvlno < pi->picomp->numrlvls && pi->rlvlno <
+				  pchg->rlvlnoend; ++pi->rlvlno, ++pi->pirlvl) {
+					if (pi->pirlvl->numprcs == 0) {
+						continue;
+					}
+					r = pi->picomp->numrlvls - 1 - pi->rlvlno;
+					trx0 = JPC_CEILDIV(pi->xstart, pi->picomp->hsamp << r);
+					try0 = JPC_CEILDIV(pi->ystart, pi->picomp->vsamp << r);
+					rpx = r + pi->pirlvl->prcwidthexpn;
+					rpy = r + pi->pirlvl->prcheightexpn;
+					if (((pi->x == pi->xstart && ((trx0 << r) % (1 << rpx))) ||
+					  !(pi->x % (pi->picomp->hsamp << rpx))) &&
+					  ((pi->y == pi->ystart && ((try0 << r) % (1 << rpy))) ||
+					  !(pi->y % (pi->picomp->vsamp << rpy)))) {
+						prchind = JPC_FLOORDIVPOW2(JPC_CEILDIV(pi->x, pi->picomp->hsamp
+						  << r), pi->pirlvl->prcwidthexpn) - JPC_FLOORDIVPOW2(trx0,
+						  pi->pirlvl->prcwidthexpn);
+						prcvind = JPC_FLOORDIVPOW2(JPC_CEILDIV(pi->y, pi->picomp->vsamp
+						  << r), pi->pirlvl->prcheightexpn) - JPC_FLOORDIVPOW2(try0,
+						  pi->pirlvl->prcheightexpn);
+						pi->prcno = prcvind *
+						  pi->pirlvl->numhprcs +
+						  prchind;
+						assert(pi->prcno <
+						  pi->pirlvl->numprcs);
+						for (pi->lyrno = 0; pi->lyrno <
+						  pi->numlyrs && pi->lyrno < pchg->lyrnoend; ++pi->lyrno) {
+							prclyrno = &pi->pirlvl->prclyrnos[pi->prcno];
+							if (pi->lyrno >= *prclyrno) {
+								++(*prclyrno);
+								return 0;
+							}
+skip:
+							;
+						}
+					}
+				}
+			}
+		}
+	}
+	return 1;
+}
+
+static void pirlvl_destroy(jpc_pirlvl_t *rlvl)
+{
+	if (rlvl->prclyrnos) {
+		jas_free(rlvl->prclyrnos);
+	}
+}
+
+static void jpc_picomp_destroy(jpc_picomp_t *picomp)
+{
+	int rlvlno;
+	jpc_pirlvl_t *pirlvl;
+	if (picomp->pirlvls) {
+		for (rlvlno = 0, pirlvl = picomp->pirlvls; rlvlno <
+		  picomp->numrlvls; ++rlvlno, ++pirlvl) {
+			pirlvl_destroy(pirlvl);
+		}
+		jas_free(picomp->pirlvls);
+	}
+}
+
+void jpc_pi_destroy(jpc_pi_t *pi)
+{
+	jpc_picomp_t *picomp;
+	int compno;
+	if (pi->picomps) {
+		for (compno = 0, picomp = pi->picomps; compno < pi->numcomps;
+		  ++compno, ++picomp) {
+			jpc_picomp_destroy(picomp);
+		}
+		jas_free(pi->picomps);
+	}
+	if (pi->pchglist) {
+		jpc_pchglist_destroy(pi->pchglist);
+	}
+	jas_free(pi);
+}
+
+jpc_pi_t *jpc_pi_create0(void)
+{
+	jpc_pi_t *pi;
+	if (!(pi = jas_malloc(sizeof(jpc_pi_t)))) {
+		return 0;
+	}
+	pi->picomps = 0;
+	pi->pchgno = 0;
+	if (!(pi->pchglist = jpc_pchglist_create())) {
+		jas_free(pi);
+		return 0;
+	}
+	return pi;
+}
+
+int jpc_pi_addpchg(jpc_pi_t *pi, jpc_pocpchg_t *pchg)
+{
+	return jpc_pchglist_insert(pi->pchglist, -1, pchg);
+}
+
+jpc_pchglist_t *jpc_pchglist_create(void)
+{
+	jpc_pchglist_t *pchglist;
+	if (!(pchglist = jas_malloc(sizeof(jpc_pchglist_t)))) {
+		return 0;
+	}
+	pchglist->numpchgs = 0;
+	pchglist->maxpchgs = 0;
+	pchglist->pchgs = 0;
+	return pchglist;
+}
+
+int jpc_pchglist_insert(jpc_pchglist_t *pchglist, int pchgno, jpc_pchg_t *pchg)
+{
+	int i;
+	int newmaxpchgs;
+	jpc_pchg_t **newpchgs;
+	if (pchgno < 0) {
+		pchgno = pchglist->numpchgs;
+	}
+	if (pchglist->numpchgs >= pchglist->maxpchgs) {
+		newmaxpchgs = pchglist->maxpchgs + 128;
+		if (!(newpchgs = jas_realloc(pchglist->pchgs, newmaxpchgs * sizeof(jpc_pchg_t *)))) {
+			return -1;
+		}
+		pchglist->maxpchgs = newmaxpchgs;
+		pchglist->pchgs = newpchgs;
+	}
+	for (i = pchglist->numpchgs; i > pchgno; --i) {
+		pchglist->pchgs[i] = pchglist->pchgs[i - 1];
+	}
+	pchglist->pchgs[pchgno] = pchg;
+	++pchglist->numpchgs;
+	return 0;
+}
+
+jpc_pchg_t *jpc_pchglist_remove(jpc_pchglist_t *pchglist, int pchgno)
+{
+	int i;
+	jpc_pchg_t *pchg;
+	assert(pchgno < pchglist->numpchgs);
+	pchg = pchglist->pchgs[pchgno];
+	for (i = pchgno + 1; i < pchglist->numpchgs; ++i) {
+		pchglist->pchgs[i - 1] = pchglist->pchgs[i];
+	}
+	--pchglist->numpchgs;
+	return pchg;
+}
+
+jpc_pchg_t *jpc_pchg_copy(jpc_pchg_t *pchg)
+{
+	jpc_pchg_t *newpchg;
+	if (!(newpchg = jas_malloc(sizeof(jpc_pchg_t)))) {
+		return 0;
+	}
+	*newpchg = *pchg;
+	return newpchg;
+}
+
+jpc_pchglist_t *jpc_pchglist_copy(jpc_pchglist_t *pchglist)
+{
+	jpc_pchglist_t *newpchglist;
+	jpc_pchg_t *newpchg;
+	int pchgno;
+	if (!(newpchglist = jpc_pchglist_create())) {
+		return 0;
+	}
+	for (pchgno = 0; pchgno < pchglist->numpchgs; ++pchgno) {
+		if (!(newpchg = jpc_pchg_copy(pchglist->pchgs[pchgno])) ||
+		  jpc_pchglist_insert(newpchglist, -1, newpchg)) {
+			jpc_pchglist_destroy(newpchglist);
+			return 0;
+		}
+	}
+	return newpchglist;
+}
+
+void jpc_pchglist_destroy(jpc_pchglist_t *pchglist)
+{
+	int pchgno;
+	if (pchglist->pchgs) {
+		for (pchgno = 0; pchgno < pchglist->numpchgs; ++pchgno) {
+			jpc_pchg_destroy(pchglist->pchgs[pchgno]);
+		}
+		jas_free(pchglist->pchgs);
+	}
+	jas_free(pchglist);
+}
+
+void jpc_pchg_destroy(jpc_pchg_t *pchg)
+{
+	jas_free(pchg);
+}
+
+jpc_pchg_t *jpc_pchglist_get(jpc_pchglist_t *pchglist, int pchgno)
+{
+	return pchglist->pchgs[pchgno];
+}
+
+int jpc_pchglist_numpchgs(jpc_pchglist_t *pchglist)
+{
+	return pchglist->numpchgs;
+}
+
+int jpc_pi_init(jpc_pi_t *pi)
+{
+	int compno;
+	int rlvlno;
+	int prcno;
+	jpc_picomp_t *picomp;
+	jpc_pirlvl_t *pirlvl;
+	int *prclyrno;
+
+	pi->prgvolfirst = 0;
+	pi->valid = 0;
+	pi->pktno = -1;
+	pi->pchgno = -1;
+	pi->pchg = 0;
+
+	for (compno = 0, picomp = pi->picomps; compno < pi->numcomps;
+	  ++compno, ++picomp) {
+		for (rlvlno = 0, pirlvl = picomp->pirlvls; rlvlno <
+		  picomp->numrlvls; ++rlvlno, ++pirlvl) {
+			for (prcno = 0, prclyrno = pirlvl->prclyrnos;
+			  prcno < pirlvl->numprcs; ++prcno, ++prclyrno) {
+				*prclyrno = 0;
+			}
+		}
+	}
+	return 0;
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_t2cod.h b/converter/other/jpeg2000/libjasper/jpc/jpc_t2cod.h
new file mode 100644
index 00000000..05f41b9e
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_t2cod.h
@@ -0,0 +1,348 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Tier-2 Coding Library
+ *
+ * $Id$
+ */
+
+#ifndef JPC_T2COD_H
+#define	JPC_T2COD_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jpc_cs.h"
+
+/******************************************************************************\
+* Types.
+\******************************************************************************/
+
+/* Progression change list. */
+
+typedef struct {
+
+	/* The number of progression changes. */
+	int numpchgs;
+
+	/* The maximum number of progression changes that can be accomodated
+	  without growing the progression change array. */
+	int maxpchgs;
+
+	/* The progression changes. */
+	jpc_pchg_t **pchgs;
+
+} jpc_pchglist_t;
+
+/* Packet iterator per-resolution-level information. */
+
+typedef struct {
+
+	/* The number of precincts. */
+	int numprcs;
+
+	/* The last layer processed for each precinct. */
+	int *prclyrnos;
+
+	/* The precinct width exponent. */
+	int prcwidthexpn;
+
+	/* The precinct height exponent. */
+	int prcheightexpn;
+
+	/* The number of precincts spanning the resolution level in the horizontal
+	  direction. */
+	int numhprcs;
+
+} jpc_pirlvl_t;
+
+/* Packet iterator per-component information. */
+
+typedef struct {
+
+	/* The number of resolution levels. */
+	int numrlvls;
+
+	/* The per-resolution-level information. */
+	jpc_pirlvl_t *pirlvls;
+
+	/* The horizontal sampling period. */
+	int hsamp;
+
+	/* The vertical sampling period. */
+	int vsamp;
+
+} jpc_picomp_t;
+
+/* Packet iterator class. */
+
+typedef struct {
+
+	/* The number of layers. */
+	int numlyrs;
+
+	/* The number of resolution levels. */
+	int maxrlvls;
+
+	/* The number of components. */
+	int numcomps;
+
+	/* The per-component information. */
+	jpc_picomp_t *picomps;
+
+	/* The current component. */
+	jpc_picomp_t *picomp;
+
+	/* The current resolution level. */
+	jpc_pirlvl_t *pirlvl;
+
+	/* The number of the current component. */
+	int compno;
+
+	/* The number of the current resolution level. */
+	int rlvlno;
+
+	/* The number of the current precinct. */
+	int prcno;
+
+	/* The number of the current layer. */
+	int lyrno;
+
+	/* The x-coordinate of the current position. */
+	int x;
+
+	/* The y-coordinate of the current position. */
+	int y;
+
+	/* The horizontal step size. */
+	int xstep;
+
+	/* The vertical step size. */
+	int ystep;
+
+	/* The x-coordinate of the top-left corner of the tile on the reference
+	  grid. */
+	int xstart;
+
+	/* The y-coordinate of the top-left corner of the tile on the reference
+	  grid. */
+	int ystart;
+
+	/* The x-coordinate of the bottom-right corner of the tile on the
+	  reference grid (plus one). */
+	int xend;
+
+	/* The y-coordinate of the bottom-right corner of the tile on the
+	  reference grid (plus one). */
+	int yend;
+
+	/* The current progression change. */
+	jpc_pchg_t *pchg;
+
+	/* The progression change list. */
+	jpc_pchglist_t *pchglist;
+
+	/* The progression to use in the absense of explicit specification. */
+	jpc_pchg_t defaultpchg;
+
+	/* The current progression change number. */
+	int pchgno;
+
+	/* Is this the first time in the current progression volume? */
+	bool prgvolfirst;
+
+	/* Is the current iterator value valid? */
+	bool valid;
+
+	/* The current packet number. */
+	int pktno;
+
+} jpc_pi_t;
+
+/******************************************************************************\
+* Functions/macros for packet iterators.
+\******************************************************************************/
+
+/* Create a packet iterator. */
+jpc_pi_t *jpc_pi_create0(void);
+
+/* Destroy a packet iterator. */
+void jpc_pi_destroy(jpc_pi_t *pi);
+
+/* Add a progression change to a packet iterator. */
+int jpc_pi_addpchg(jpc_pi_t *pi, jpc_pocpchg_t *pchg);
+
+/* Prepare a packet iterator for iteration. */
+int jpc_pi_init(jpc_pi_t *pi);
+
+/* Set the iterator to the first packet. */
+int jpc_pi_begin(jpc_pi_t *pi);
+
+/* Proceed to the next packet in sequence. */
+int jpc_pi_next(jpc_pi_t *pi);
+
+/* Get the index of the current packet. */
+#define	jpc_pi_getind(pi)	((pi)->pktno)
+
+/* Get the component number of the current packet. */
+#define jpc_pi_cmptno(pi)	(assert(pi->valid), (pi)->compno)
+
+/* Get the resolution level of the current packet. */
+#define jpc_pi_rlvlno(pi)	(assert(pi->valid), (pi)->rlvlno)
+
+/* Get the layer number of the current packet. */
+#define jpc_pi_lyrno(pi)	(assert(pi->valid), (pi)->lyrno)
+
+/* Get the precinct number of the current packet. */
+#define jpc_pi_prcno(pi)	(assert(pi->valid), (pi)->prcno)
+
+/* Get the progression order for the current packet. */
+#define jpc_pi_prg(pi)	(assert(pi->valid), (pi)->pchg->prgord)
+
+/******************************************************************************\
+* Functions/macros for progression change lists.
+\******************************************************************************/
+
+/* Create a progression change list. */
+jpc_pchglist_t *jpc_pchglist_create(void);
+
+/* Destroy a progression change list. */
+void jpc_pchglist_destroy(jpc_pchglist_t *pchglist);
+
+/* Insert a new element into a progression change list. */
+int jpc_pchglist_insert(jpc_pchglist_t *pchglist, int pchgno, jpc_pchg_t *pchg);
+
+/* Remove an element from a progression change list. */
+jpc_pchg_t *jpc_pchglist_remove(jpc_pchglist_t *pchglist, int pchgno);
+
+/* Get an element from a progression change list. */
+jpc_pchg_t *jpc_pchglist_get(jpc_pchglist_t *pchglist, int pchgno);
+
+/* Copy a progression change list. */
+jpc_pchglist_t *jpc_pchglist_copy(jpc_pchglist_t *pchglist);
+
+/* Get the number of elements in a progression change list. */
+int jpc_pchglist_numpchgs(jpc_pchglist_t *pchglist);
+
+/******************************************************************************\
+* Functions/macros for progression changes.
+\******************************************************************************/
+
+/* Destroy a progression change. */
+void jpc_pchg_destroy(jpc_pchg_t *pchg);
+
+/* Copy a progression change. */
+jpc_pchg_t *jpc_pchg_copy(jpc_pchg_t *pchg);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_t2dec.c b/converter/other/jpeg2000/libjasper/jpc/jpc_t2dec.c
new file mode 100644
index 00000000..9aaaa55c
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_t2dec.c
@@ -0,0 +1,626 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Tier 2 Decoder
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "jasper/jas_types.h"
+#include "jasper/jas_fix.h"
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_math.h"
+#include "jasper/jas_stream.h"
+#include "jasper/jas_debug.h"
+
+#include "jpc_bs.h"
+#include "jpc_dec.h"
+#include "jpc_cs.h"
+#include "jpc_mqdec.h"
+#include "jpc_t2dec.h"
+#include "jpc_t1cod.h"
+#include "jpc_math.h"
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+long jpc_dec_lookahead(jas_stream_t *in);
+static int jpc_getcommacode(jpc_bitstream_t *in);
+static int jpc_getnumnewpasses(jpc_bitstream_t *in);
+static int jpc_dec_decodepkt(jpc_dec_t *dec, jas_stream_t *pkthdrstream, jas_stream_t *in, int compno, int lvlno,
+  int prcno, int lyrno);
+
+/******************************************************************************\
+* Code.
+\******************************************************************************/
+
+static int jpc_getcommacode(jpc_bitstream_t *in)
+{
+	int n;
+	int v;
+
+	n = 0;
+	for (;;) {
+		if ((v = jpc_bitstream_getbit(in)) < 0) {
+			return -1;
+		}
+		if (jpc_bitstream_eof(in)) {
+			return -1;
+		}
+		if (!v) {
+			break;
+		}
+		++n;
+	}
+
+	return n;
+}
+
+static int jpc_getnumnewpasses(jpc_bitstream_t *in)
+{
+	int n;
+
+	if ((n = jpc_bitstream_getbit(in)) > 0) {
+		if ((n = jpc_bitstream_getbit(in)) > 0) {
+			if ((n = jpc_bitstream_getbits(in, 2)) == 3) {
+				if ((n = jpc_bitstream_getbits(in, 5)) == 31) {
+					if ((n = jpc_bitstream_getbits(in, 7)) >= 0) {
+						n += 36 + 1;
+					}
+				} else if (n >= 0) {
+					n += 5 + 1;
+				}
+			} else if (n >= 0) {
+				n += 2 + 1;
+			}
+		} else if (!n) {
+			n += 2;
+		}
+	} else if (!n) {
+		++n;
+	}
+
+	return n;
+}
+
+static int jpc_dec_decodepkt(jpc_dec_t *dec, jas_stream_t *pkthdrstream, jas_stream_t *in, int compno, int rlvlno,
+  int prcno, int lyrno)
+{
+	jpc_bitstream_t *inb;
+	jpc_dec_tcomp_t *tcomp;
+	jpc_dec_rlvl_t *rlvl;
+	jpc_dec_band_t *band;
+	jpc_dec_cblk_t *cblk;
+	int n;
+	int m;
+	int i;
+	jpc_tagtreenode_t *leaf;
+	int included;
+	int ret;
+	int numnewpasses;
+	jpc_dec_seg_t *seg;
+	int len;
+	int present;
+	int savenumnewpasses;
+	int mycounter;
+	jpc_ms_t *ms;
+	jpc_dec_tile_t *tile;
+	jpc_dec_ccp_t *ccp;
+	jpc_dec_cp_t *cp;
+	int bandno;
+	jpc_dec_prc_t *prc;
+	int usedcblkcnt;
+	int cblkno;
+	uint_fast32_t bodylen;
+	bool discard;
+	int passno;
+	int maxpasses;
+	int hdrlen;
+	int hdroffstart;
+	int hdroffend;
+
+	discard = (lyrno >= dec->maxlyrs);
+
+	tile = dec->curtile;
+	cp = tile->cp;
+	ccp = &cp->ccps[compno];
+
+	/*
+	 * Decode the packet header.
+	 */
+
+	/* Decode the SOP marker segment if present. */
+	if (cp->csty & JPC_COD_SOP) {
+		if (jpc_dec_lookahead(in) == JPC_MS_SOP) {
+			if (!(ms = jpc_getms(in, dec->cstate))) {
+				return -1;
+			}
+			if (jpc_ms_gettype(ms) != JPC_MS_SOP) {
+				jpc_ms_destroy(ms);
+				fprintf(stderr, "missing SOP marker segment\n");
+				return -1;
+			}
+			jpc_ms_destroy(ms);
+		}
+	}
+
+hdroffstart = jas_stream_getrwcount(pkthdrstream);
+
+	if (!(inb = jpc_bitstream_sopen(pkthdrstream, "r"))) {
+		return -1;
+	}
+
+	if ((present = jpc_bitstream_getbit(inb)) < 0) {
+		return 1;
+	}
+	JAS_DBGLOG(10, ("\n", present));
+	JAS_DBGLOG(10, ("present=%d ", present));
+
+	/* Is the packet non-empty? */
+	if (present) {
+		/* The packet is non-empty. */
+		tcomp = &tile->tcomps[compno];
+		rlvl = &tcomp->rlvls[rlvlno];
+		bodylen = 0;
+		for (bandno = 0, band = rlvl->bands; bandno < rlvl->numbands;
+		  ++bandno, ++band) {
+			if (!band->data) {
+				continue;
+			}
+			prc = &band->prcs[prcno];
+			if (!prc->cblks) {
+				continue;
+			}
+			usedcblkcnt = 0;
+			for (cblkno = 0, cblk = prc->cblks; cblkno < prc->numcblks;
+			  ++cblkno, ++cblk) {
+				++usedcblkcnt;
+				if (!cblk->numpasses) {
+					leaf = jpc_tagtree_getleaf(prc->incltagtree, usedcblkcnt - 1);
+					if ((included = jpc_tagtree_decode(prc->incltagtree, leaf, lyrno + 1, inb)) < 0) {
+						return -1;
+					}
+				} else {
+					if ((included = jpc_bitstream_getbit(inb)) < 0) {
+						return -1;
+					}
+				}
+				JAS_DBGLOG(10, ("\n"));
+				JAS_DBGLOG(10, ("included=%d ", included));
+				if (!included) {
+					continue;
+				}
+				if (!cblk->numpasses) {
+					i = 1;
+					leaf = jpc_tagtree_getleaf(prc->numimsbstagtree, usedcblkcnt - 1);
+					for (;;) {
+						if ((ret = jpc_tagtree_decode(prc->numimsbstagtree, leaf, i, inb)) < 0) {
+							return -1;
+						}
+						if (ret) {
+							break;
+						}
+						++i;
+					}
+					cblk->numimsbs = i - 1;
+					cblk->firstpassno = cblk->numimsbs * 3;
+				}
+				if ((numnewpasses = jpc_getnumnewpasses(inb)) < 0) {
+					return -1;
+				}
+				JAS_DBGLOG(10, ("numnewpasses=%d ", numnewpasses));
+				seg = cblk->curseg;
+				savenumnewpasses = numnewpasses;
+				mycounter = 0;
+				if (numnewpasses > 0) {
+					if ((m = jpc_getcommacode(inb)) < 0) {
+						return -1;
+					}
+					cblk->numlenbits += m;
+					JAS_DBGLOG(10, ("increment=%d ", m));
+					while (numnewpasses > 0) {
+						passno = cblk->firstpassno + cblk->numpasses + mycounter;
+	/* XXX - the maxpasses is not set precisely but this doesn't matter... */
+						maxpasses = JPC_SEGPASSCNT(passno, cblk->firstpassno, 10000, (ccp->cblkctx & JPC_COX_LAZY) != 0, (ccp->cblkctx & JPC_COX_TERMALL) != 0);
+						if (!discard && !seg) {
+							if (!(seg = jpc_seg_alloc())) {
+								return -1;
+							}
+							jpc_seglist_insert(&cblk->segs, cblk->segs.tail, seg);
+							if (!cblk->curseg) {
+								cblk->curseg = seg;
+							}
+							seg->passno = passno;
+							seg->type = JPC_SEGTYPE(seg->passno, cblk->firstpassno, (ccp->cblkctx & JPC_COX_LAZY) != 0);
+							seg->maxpasses = maxpasses;
+						}
+						n = JAS_MIN(numnewpasses, maxpasses);
+						mycounter += n;
+						numnewpasses -= n;
+						if ((len = jpc_bitstream_getbits(inb, cblk->numlenbits + jpc_floorlog2(n))) < 0) {
+							return -1;
+						}
+						JAS_DBGLOG(10, ("len=%d ", len));
+						if (!discard) {
+							seg->lyrno = lyrno;
+							seg->numpasses += n;
+							seg->cnt = len;
+							seg = seg->next;
+						}
+						bodylen += len;
+					}
+				}
+				cblk->numpasses += savenumnewpasses;
+			}
+		}
+
+		jpc_bitstream_inalign(inb, 0, 0);
+
+	} else {
+		if (jpc_bitstream_inalign(inb, 0x7f, 0)) {
+			fprintf(stderr, "alignment failed\n");
+			return -1;
+		}
+	}
+	jpc_bitstream_close(inb);
+
+	hdroffend = jas_stream_getrwcount(pkthdrstream);
+	hdrlen = hdroffend - hdroffstart;
+	if (jas_getdbglevel() >= 5) {
+		fprintf(stderr, "hdrlen=%lu bodylen=%lu \n", (unsigned long) hdrlen,
+		  (unsigned long) bodylen);
+	}
+
+	if (cp->csty & JPC_COD_EPH) {
+		if (jpc_dec_lookahead(pkthdrstream) == JPC_MS_EPH) {
+			if (!(ms = jpc_getms(pkthdrstream, dec->cstate))) {
+				fprintf(stderr, "cannot get (EPH) marker segment\n");
+				return -1;
+			}
+			if (jpc_ms_gettype(ms) != JPC_MS_EPH) {
+				jpc_ms_destroy(ms);
+				fprintf(stderr, "missing EPH marker segment\n");
+				return -1;
+			}
+			jpc_ms_destroy(ms);
+		}
+	}
+
+	/* decode the packet body. */
+
+	if (jas_getdbglevel() >= 1) {
+		fprintf(stderr, "packet body offset=%06ld\n", (long) jas_stream_getrwcount(in));
+	}
+
+	if (!discard) {
+		tcomp = &tile->tcomps[compno];
+		rlvl = &tcomp->rlvls[rlvlno];
+		for (bandno = 0, band = rlvl->bands; bandno < rlvl->numbands;
+		  ++bandno, ++band) {
+			if (!band->data) {
+				continue;
+			}
+			prc = &band->prcs[prcno];
+			if (!prc->cblks) {
+				continue;
+			}
+			for (cblkno = 0, cblk = prc->cblks; cblkno < prc->numcblks;
+			  ++cblkno, ++cblk) {
+				seg = cblk->curseg;
+				while (seg) {
+					if (!seg->stream) {
+						if (!(seg->stream = jas_stream_memopen(0, 0))) {
+							return -1;
+						}
+					}
+#if 0
+fprintf(stderr, "lyrno=%02d, compno=%02d, lvlno=%02d, prcno=%02d, bandno=%02d, cblkno=%02d, passno=%02d numpasses=%02d cnt=%d numbps=%d, numimsbs=%d\n", lyrno, compno, rlvlno, prcno, band - rlvl->bands, cblk - prc->cblks, seg->passno, seg->numpasses, seg->cnt, band->numbps, cblk->numimsbs);
+#endif
+					if (seg->cnt > 0) {
+						if (jpc_getdata(in, seg->stream, seg->cnt) < 0) {
+							return -1;
+						}
+						seg->cnt = 0;
+					}
+					if (seg->numpasses >= seg->maxpasses) {
+						cblk->curseg = seg->next;
+					}
+					seg = seg->next;
+				}
+			}
+		}
+	} else {
+		if (jas_stream_gobble(in, bodylen) != bodylen) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+/********************************************************************************************/
+/********************************************************************************************/
+
+int jpc_dec_decodepkts(jpc_dec_t *dec, jas_stream_t *pkthdrstream, jas_stream_t *in)
+{
+	jpc_dec_tile_t *tile;
+	jpc_pi_t *pi;
+	int ret;
+
+	tile = dec->curtile;
+	pi = tile->pi;
+	for (;;) {
+if (!tile->pkthdrstream || jas_stream_peekc(tile->pkthdrstream) == EOF) {
+		switch (jpc_dec_lookahead(in)) {
+		case JPC_MS_EOC:
+		case JPC_MS_SOT:
+			return 0;
+			break;
+		case JPC_MS_SOP:
+		case JPC_MS_EPH:
+		case 0:
+			break;
+		default:
+			return -1;
+			break;
+		}
+}
+		if ((ret = jpc_pi_next(pi))) {
+			return ret;
+		}
+if (dec->maxpkts >= 0 && dec->numpkts >= dec->maxpkts) {
+	fprintf(stderr, "warning: stopping decode prematurely as requested\n");
+	return 0;
+}
+		if (jas_getdbglevel() >= 1) {
+			fprintf(stderr, "packet offset=%08ld prg=%d cmptno=%02d "
+			  "rlvlno=%02d prcno=%03d lyrno=%02d\n", (long)
+			  jas_stream_getrwcount(in), jpc_pi_prg(pi), jpc_pi_cmptno(pi),
+			  jpc_pi_rlvlno(pi), jpc_pi_prcno(pi), jpc_pi_lyrno(pi));
+		}
+		if (jpc_dec_decodepkt(dec, pkthdrstream, in, jpc_pi_cmptno(pi), jpc_pi_rlvlno(pi),
+		  jpc_pi_prcno(pi), jpc_pi_lyrno(pi))) {
+			return -1;
+		}
+++dec->numpkts;
+	}
+
+	return 0;
+}
+
+jpc_pi_t *jpc_dec_pi_create(jpc_dec_t *dec, jpc_dec_tile_t *tile)
+{
+	jpc_pi_t *pi;
+	int compno;
+	jpc_picomp_t *picomp;
+	jpc_pirlvl_t *pirlvl;
+	jpc_dec_tcomp_t *tcomp;
+	int rlvlno;
+	jpc_dec_rlvl_t *rlvl;
+	int prcno;
+	int *prclyrno;
+	jpc_dec_cmpt_t *cmpt;
+
+	if (!(pi = jpc_pi_create0())) {
+		return 0;
+	}
+	pi->numcomps = dec->numcomps;
+	if (!(pi->picomps = jas_malloc(pi->numcomps * sizeof(jpc_picomp_t)))) {
+		jpc_pi_destroy(pi);
+		return 0;
+	}
+	for (compno = 0, picomp = pi->picomps; compno < pi->numcomps; ++compno,
+	  ++picomp) {
+		picomp->pirlvls = 0;
+	}
+
+	for (compno = 0, tcomp = tile->tcomps, picomp = pi->picomps;
+	  compno < pi->numcomps; ++compno, ++tcomp, ++picomp) {
+		picomp->numrlvls = tcomp->numrlvls;
+		if (!(picomp->pirlvls = jas_malloc(picomp->numrlvls *
+		  sizeof(jpc_pirlvl_t)))) {
+			jpc_pi_destroy(pi);
+			return 0;
+		}
+		for (rlvlno = 0, pirlvl = picomp->pirlvls; rlvlno <
+		  picomp->numrlvls; ++rlvlno, ++pirlvl) {
+			pirlvl->prclyrnos = 0;
+		}
+		for (rlvlno = 0, pirlvl = picomp->pirlvls, rlvl = tcomp->rlvls;
+		  rlvlno < picomp->numrlvls; ++rlvlno, ++pirlvl, ++rlvl) {
+/* XXX sizeof(long) should be sizeof different type */
+			pirlvl->numprcs = rlvl->numprcs;
+			if (!(pirlvl->prclyrnos = jas_malloc(pirlvl->numprcs *
+			  sizeof(long)))) {
+				jpc_pi_destroy(pi);
+				return 0;
+			}
+		}
+	}
+
+	pi->maxrlvls = 0;
+	for (compno = 0, tcomp = tile->tcomps, picomp = pi->picomps, cmpt =
+	  dec->cmpts; compno < pi->numcomps; ++compno, ++tcomp, ++picomp,
+	  ++cmpt) {
+		picomp->hsamp = cmpt->hstep;
+		picomp->vsamp = cmpt->vstep;
+		for (rlvlno = 0, pirlvl = picomp->pirlvls, rlvl = tcomp->rlvls;
+		  rlvlno < picomp->numrlvls; ++rlvlno, ++pirlvl, ++rlvl) {
+			pirlvl->prcwidthexpn = rlvl->prcwidthexpn;
+			pirlvl->prcheightexpn = rlvl->prcheightexpn;
+			for (prcno = 0, prclyrno = pirlvl->prclyrnos;
+			  prcno < pirlvl->numprcs; ++prcno, ++prclyrno) {
+				*prclyrno = 0;
+			}
+			pirlvl->numhprcs = rlvl->numhprcs;
+		}
+		if (pi->maxrlvls < tcomp->numrlvls) {
+			pi->maxrlvls = tcomp->numrlvls;
+		}
+	}
+
+	pi->numlyrs = tile->cp->numlyrs;
+	pi->xstart = tile->xstart;
+	pi->ystart = tile->ystart;
+	pi->xend = tile->xend;
+	pi->yend = tile->yend;
+
+	pi->picomp = 0;
+	pi->pirlvl = 0;
+	pi->x = 0;
+	pi->y = 0;
+	pi->compno = 0;
+	pi->rlvlno = 0;
+	pi->prcno = 0;
+	pi->lyrno = 0;
+	pi->xstep = 0;
+	pi->ystep = 0;
+
+	pi->pchgno = -1;
+
+	pi->defaultpchg.prgord = tile->cp->prgord;
+	pi->defaultpchg.compnostart = 0;
+	pi->defaultpchg.compnoend = pi->numcomps;
+	pi->defaultpchg.rlvlnostart = 0;
+	pi->defaultpchg.rlvlnoend = pi->maxrlvls;
+	pi->defaultpchg.lyrnoend = pi->numlyrs;
+	pi->pchg = 0;
+
+	pi->valid = 0;
+
+	return pi;
+}
+
+long jpc_dec_lookahead(jas_stream_t *in)
+{
+	uint_fast16_t x;
+	if (jpc_getuint16(in, &x)) {
+		return -1;
+	}
+	if (jas_stream_ungetc(in, x & 0xff) == EOF ||
+	  jas_stream_ungetc(in, x >> 8) == EOF) {
+		return -1;
+	}
+	if (x >= JPC_MS_INMIN && x <= JPC_MS_INMAX) {
+		return x;
+	}
+	return 0;
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_t2dec.h b/converter/other/jpeg2000/libjasper/jpc/jpc_t2dec.h
new file mode 100644
index 00000000..923d7c1a
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_t2dec.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Tier 2 Decoder
+ *
+ * $Id$
+ */
+
+#ifndef JPC_T2DEC_H
+#define JPC_T2DEC_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_fix.h"
+#include "jasper/jas_stream.h"
+
+#include "jpc_bs.h"
+#include "jpc_dec.h"
+#include "jpc_mqdec.h"
+
+/******************************************************************************\
+* Functions.
+\******************************************************************************/
+
+/* Decode the packets for a tile-part. */
+int jpc_dec_decodepkts(jpc_dec_t *dec, jas_stream_t *pkthdrstream,
+  jas_stream_t *in);
+
+/* Create a packet iterator for the decoder. */
+jpc_pi_t *jpc_dec_pi_create(jpc_dec_t *dec, jpc_dec_tile_t *tile);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_t2enc.c b/converter/other/jpeg2000/libjasper/jpc/jpc_t2enc.c
new file mode 100644
index 00000000..1444dc7b
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_t2enc.c
@@ -0,0 +1,704 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Tier 2 Encoder
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "jasper/jas_fix.h"
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_math.h"
+#include "jasper/jas_debug.h"
+
+#include "jpc_flt.h"
+#include "jpc_t2enc.h"
+#include "jpc_t2cod.h"
+#include "jpc_tagtree.h"
+#include "jpc_enc.h"
+#include "jpc_math.h"
+
+/******************************************************************************\
+* Code.
+\******************************************************************************/
+
+static int jpc_putcommacode(jpc_bitstream_t *out, int n)
+{
+	assert(n >= 0);
+
+	while (--n >= 0) {
+		if (jpc_bitstream_putbit(out, 1) == EOF) {
+			return -1;
+		}
+	}
+	if (jpc_bitstream_putbit(out, 0) == EOF) {
+		return -1;
+	}
+	return 0;
+}
+
+static int jpc_putnumnewpasses(jpc_bitstream_t *out, int n)
+{
+	int ret;
+
+	if (n <= 0) {
+		return -1;
+	} else if (n == 1) {
+		ret = jpc_bitstream_putbit(out, 0);
+	} else if (n == 2) {
+		ret = jpc_bitstream_putbits(out, 2, 2);
+	} else if (n <= 5) {
+		ret = jpc_bitstream_putbits(out, 4, 0xc | (n - 3));
+	} else if (n <= 36) {
+		ret = jpc_bitstream_putbits(out, 9, 0x1e0 | (n - 6));
+	} else if (n <= 164) {
+		ret = jpc_bitstream_putbits(out, 16, 0xff80 | (n - 37));
+	} else {
+		/* The standard has no provision for encoding a larger value.
+		In practice, however, it is highly unlikely that this
+		limitation will ever be encountered. */
+		return -1;
+	}
+
+	return (ret != EOF) ? 0 : (-1);
+}
+
+int jpc_enc_encpkts(jpc_enc_t *enc, jas_stream_t *out)
+{
+	jpc_enc_tile_t *tile;
+	jpc_pi_t *pi;
+
+	tile = enc->curtile;
+
+	jpc_init_t2state(enc, 0);
+	pi = tile->pi;
+	jpc_pi_init(pi);
+
+	if (!jpc_pi_next(pi)) {
+		for (;;) {
+			if (jpc_enc_encpkt(enc, out, jpc_pi_cmptno(pi), jpc_pi_rlvlno(pi),
+			  jpc_pi_prcno(pi), jpc_pi_lyrno(pi))) {
+				return -1;
+			}
+			if (jpc_pi_next(pi)) {
+				break;
+			}
+		}
+	}
+	
+	return 0;
+}
+
+int jpc_enc_encpkt(jpc_enc_t *enc, jas_stream_t *out, int compno, int lvlno, int prcno, int lyrno)
+{
+	jpc_enc_tcmpt_t *comp;
+	jpc_enc_rlvl_t *lvl;
+	jpc_enc_band_t *band;
+	jpc_enc_band_t *endbands;
+	jpc_enc_cblk_t *cblk;
+	jpc_enc_cblk_t *endcblks;
+	jpc_bitstream_t *outb;
+	jpc_enc_pass_t *pass;
+	jpc_enc_pass_t *startpass;
+	jpc_enc_pass_t *lastpass;
+	jpc_enc_pass_t *endpass;
+	jpc_enc_pass_t *endpasses;
+	int i;
+	int included;
+	int ret;
+	jpc_tagtreenode_t *leaf;
+	int n;
+	int t1;
+	int t2;
+	int adjust;
+	int maxadjust;
+	int datalen;
+	int numnewpasses;
+	int passcount;
+	jpc_enc_tile_t *tile;
+	jpc_enc_prc_t *prc;
+	jpc_enc_cp_t *cp;
+	jpc_ms_t *ms;
+
+	tile = enc->curtile;
+	cp = enc->cp;
+
+	if (cp->tcp.csty & JPC_COD_SOP) {
+		if (!(ms = jpc_ms_create(JPC_MS_SOP))) {
+			return -1;
+		}
+		ms->parms.sop.seqno = jpc_pi_getind(tile->pi);
+		if (jpc_putms(out, enc->cstate, ms)) {
+			return -1;
+		}
+		jpc_ms_destroy(ms);
+	}
+
+	outb = jpc_bitstream_sopen(out, "w+");
+	assert(outb);
+
+	if (jpc_bitstream_putbit(outb, 1) == EOF) {
+		return -1;
+	}
+	JAS_DBGLOG(10, ("\n"));
+	JAS_DBGLOG(10, ("present. "));
+
+	comp = &tile->tcmpts[compno];
+	lvl = &comp->rlvls[lvlno];
+	endbands = &lvl->bands[lvl->numbands];
+	for (band = lvl->bands; band != endbands; ++band) {
+		if (!band->data) {
+			continue;
+		}
+		prc = &band->prcs[prcno];
+		if (!prc->cblks) {
+			continue;
+		}
+
+		endcblks = &prc->cblks[prc->numcblks];
+		for (cblk = prc->cblks; cblk != endcblks; ++cblk) {
+			if (!lyrno) {
+				leaf = jpc_tagtree_getleaf(prc->nlibtree, cblk - prc->cblks);
+				jpc_tagtree_setvalue(prc->nlibtree, leaf, cblk->numimsbs);
+			}
+			pass = cblk->curpass;
+			included = (pass && pass->lyrno == lyrno);
+			if (included && (!cblk->numencpasses)) {
+				assert(pass->lyrno == lyrno);
+				leaf = jpc_tagtree_getleaf(prc->incltree,
+				  cblk - prc->cblks);
+				jpc_tagtree_setvalue(prc->incltree, leaf, pass->lyrno);
+			}
+		}
+
+		endcblks = &prc->cblks[prc->numcblks];
+		for (cblk = prc->cblks; cblk != endcblks; ++cblk) {
+			pass = cblk->curpass;
+			included = (pass && pass->lyrno == lyrno);
+			if (!cblk->numencpasses) {
+				leaf = jpc_tagtree_getleaf(prc->incltree,
+				  cblk - prc->cblks);
+				if (jpc_tagtree_encode(prc->incltree, leaf, lyrno
+				  + 1, outb) < 0) {
+					return -1;
+				}
+			} else {
+				if (jpc_bitstream_putbit(outb, included) == EOF) {
+					return -1;
+				}
+			}
+			JAS_DBGLOG(10, ("included=%d ", included));
+			if (!included) {
+				continue;
+			}
+			if (!cblk->numencpasses) {
+				i = 1;
+				leaf = jpc_tagtree_getleaf(prc->nlibtree, cblk - prc->cblks);
+				for (;;) {
+					if ((ret = jpc_tagtree_encode(prc->nlibtree, leaf, i, outb)) < 0) {
+						return -1;
+					}
+					if (ret) {
+						break;
+					}
+					++i;
+				}
+				assert(leaf->known_ && i == leaf->value_ + 1);
+			}
+
+			endpasses = &cblk->passes[cblk->numpasses];
+			startpass = pass;
+			endpass = startpass;
+			while (endpass != endpasses && endpass->lyrno == lyrno){
+				++endpass;
+			}
+			numnewpasses = endpass - startpass;
+			if (jpc_putnumnewpasses(outb, numnewpasses)) {
+				return -1;
+			}
+			JAS_DBGLOG(10, ("numnewpasses=%d ", numnewpasses));
+
+			lastpass = endpass - 1;
+			n = startpass->start;
+			passcount = 1;
+			maxadjust = 0;
+			for (pass = startpass; pass != endpass; ++pass) {
+				if (pass->term || pass == lastpass) {
+					datalen = pass->end - n;
+					t1 = jpc_firstone(datalen) + 1;
+					t2 = cblk->numlenbits + jpc_floorlog2(passcount);
+					adjust = JAS_MAX(t1 - t2, 0);
+					maxadjust = JAS_MAX(adjust, maxadjust);
+					n += datalen;
+					passcount = 1;
+				} else {
+					++passcount;
+				}
+			}
+			if (jpc_putcommacode(outb, maxadjust)) {
+				return -1;
+			}
+			cblk->numlenbits += maxadjust;
+
+			lastpass = endpass - 1;
+			n = startpass->start;
+			passcount = 1;
+			for (pass = startpass; pass != endpass; ++pass) {
+				if (pass->term || pass == lastpass) {
+					datalen = pass->end - n;
+assert(jpc_firstone(datalen) < cblk->numlenbits + jpc_floorlog2(passcount));
+					if (jpc_bitstream_putbits(outb, cblk->numlenbits + jpc_floorlog2(passcount), datalen) == EOF) {
+						return -1;
+					}
+					n += datalen;
+					passcount = 1;
+				} else {
+					++passcount;
+				}
+			}
+		}
+	}
+
+	jpc_bitstream_outalign(outb, 0);
+	jpc_bitstream_close(outb);
+
+	if (cp->tcp.csty & JPC_COD_EPH) {
+		if (!(ms = jpc_ms_create(JPC_MS_EPH))) {
+			return -1;
+		}
+		jpc_putms(out, enc->cstate, ms);
+		jpc_ms_destroy(ms);
+	}
+
+	comp = &tile->tcmpts[compno];
+	lvl = &comp->rlvls[lvlno];
+	endbands = &lvl->bands[lvl->numbands];
+	for (band = lvl->bands; band != endbands; ++band) {
+		if (!band->data) {
+			continue;
+		}
+		prc = &band->prcs[prcno];
+		if (!prc->cblks) {
+			continue;
+		}
+		endcblks = &prc->cblks[prc->numcblks];
+		for (cblk = prc->cblks; cblk != endcblks; ++cblk) {
+			pass = cblk->curpass;
+
+			if (!pass) {
+				continue;
+			}
+			if (pass->lyrno != lyrno) {
+				assert(pass->lyrno < 0 || pass->lyrno > lyrno);
+				continue;
+			}
+
+			endpasses = &cblk->passes[cblk->numpasses];
+			startpass = pass;
+			endpass = startpass;
+			while (endpass != endpasses && endpass->lyrno == lyrno){
+				++endpass;
+			}
+			lastpass = endpass - 1;
+			numnewpasses = endpass - startpass;
+
+			jas_stream_seek(cblk->stream, startpass->start, SEEK_SET);
+			assert(jas_stream_tell(cblk->stream) == startpass->start);
+			if (jas_stream_copy(out, cblk->stream, lastpass->end - startpass->start)) {
+				return -1;
+			}
+			cblk->curpass = (endpass != endpasses) ? endpass : 0;
+			cblk->numencpasses += numnewpasses;
+
+		}
+	}
+
+	return 0;
+}
+
+void jpc_save_t2state(jpc_enc_t *enc)
+{
+/* stream pos in embedded T1 stream may be wrong since not saved/restored! */
+
+	jpc_enc_tcmpt_t *comp;
+	jpc_enc_tcmpt_t *endcomps;
+	jpc_enc_rlvl_t *lvl;
+	jpc_enc_rlvl_t *endlvls;
+	jpc_enc_band_t *band;
+	jpc_enc_band_t *endbands;
+	jpc_enc_cblk_t *cblk;
+	jpc_enc_cblk_t *endcblks;
+	jpc_enc_tile_t *tile;
+	uint_fast32_t prcno;
+	jpc_enc_prc_t *prc;
+
+	tile = enc->curtile;
+
+	endcomps = &tile->tcmpts[tile->numtcmpts];
+	for (comp = tile->tcmpts; comp != endcomps; ++comp) {
+		endlvls = &comp->rlvls[comp->numrlvls];
+		for (lvl = comp->rlvls; lvl != endlvls; ++lvl) {
+			if (!lvl->bands) {
+				continue;
+			}
+			endbands = &lvl->bands[lvl->numbands];
+			for (band = lvl->bands; band != endbands; ++band) {
+				if (!band->data) {
+					continue;
+				}
+				for (prcno = 0, prc = band->prcs; prcno < lvl->numprcs; ++prcno, ++prc) {
+					if (!prc->cblks) {
+						continue;
+					}
+					jpc_tagtree_copy(prc->savincltree, prc->incltree);
+					jpc_tagtree_copy(prc->savnlibtree, prc->nlibtree);
+					endcblks = &prc->cblks[prc->numcblks];
+					for (cblk = prc->cblks; cblk != endcblks; ++cblk) {
+						cblk->savedcurpass = cblk->curpass;
+						cblk->savednumencpasses = cblk->numencpasses;
+						cblk->savednumlenbits = cblk->numlenbits;
+					}
+				}
+			}
+		}
+	}
+
+}
+
+void jpc_restore_t2state(jpc_enc_t *enc)
+{
+
+	jpc_enc_tcmpt_t *comp;
+	jpc_enc_tcmpt_t *endcomps;
+	jpc_enc_rlvl_t *lvl;
+	jpc_enc_rlvl_t *endlvls;
+	jpc_enc_band_t *band;
+	jpc_enc_band_t *endbands;
+	jpc_enc_cblk_t *cblk;
+	jpc_enc_cblk_t *endcblks;
+	jpc_enc_tile_t *tile;
+	uint_fast32_t prcno;
+	jpc_enc_prc_t *prc;
+
+	tile = enc->curtile;
+
+	endcomps = &tile->tcmpts[tile->numtcmpts];
+	for (comp = tile->tcmpts; comp != endcomps; ++comp) {
+		endlvls = &comp->rlvls[comp->numrlvls];
+		for (lvl = comp->rlvls; lvl != endlvls; ++lvl) {
+			if (!lvl->bands) {
+				continue;
+			}
+			endbands = &lvl->bands[lvl->numbands];
+			for (band = lvl->bands; band != endbands; ++band) {
+				if (!band->data) {
+					continue;
+				}
+				for (prcno = 0, prc = band->prcs; prcno < lvl->numprcs; ++prcno, ++prc) {
+					if (!prc->cblks) {
+						continue;
+					}
+					jpc_tagtree_copy(prc->incltree, prc->savincltree);
+					jpc_tagtree_copy(prc->nlibtree, prc->savnlibtree);
+					endcblks = &prc->cblks[prc->numcblks];
+					for (cblk = prc->cblks; cblk != endcblks; ++cblk) {
+						cblk->curpass = cblk->savedcurpass;
+						cblk->numencpasses = cblk->savednumencpasses;
+						cblk->numlenbits = cblk->savednumlenbits;
+					}
+				}
+			}
+		}
+	}
+}
+
+void jpc_init_t2state(jpc_enc_t *enc, int raflag)
+{
+/* It is assumed that band->numbps and cblk->numbps precomputed */
+
+	jpc_enc_tcmpt_t *comp;
+	jpc_enc_tcmpt_t *endcomps;
+	jpc_enc_rlvl_t *lvl;
+	jpc_enc_rlvl_t *endlvls;
+	jpc_enc_band_t *band;
+	jpc_enc_band_t *endbands;
+	jpc_enc_cblk_t *cblk;
+	jpc_enc_cblk_t *endcblks;
+	jpc_enc_pass_t *pass;
+	jpc_enc_pass_t *endpasses;
+	jpc_tagtreenode_t *leaf;
+	jpc_enc_tile_t *tile;
+	uint_fast32_t prcno;
+	jpc_enc_prc_t *prc;
+
+	tile = enc->curtile;
+
+	endcomps = &tile->tcmpts[tile->numtcmpts];
+	for (comp = tile->tcmpts; comp != endcomps; ++comp) {
+		endlvls = &comp->rlvls[comp->numrlvls];
+		for (lvl = comp->rlvls; lvl != endlvls; ++lvl) {
+			if (!lvl->bands) {
+				continue;
+			}
+			endbands = &lvl->bands[lvl->numbands];
+			for (band = lvl->bands; band != endbands; ++band) {
+				if (!band->data) {
+					continue;
+				}
+				for (prcno = 0, prc = band->prcs; prcno < lvl->numprcs; ++prcno, ++prc) {
+					if (!prc->cblks) {
+						continue;
+					}
+					jpc_tagtree_reset(prc->incltree);
+					jpc_tagtree_reset(prc->nlibtree);
+					endcblks = &prc->cblks[prc->numcblks];
+					for (cblk = prc->cblks; cblk != endcblks; ++cblk) {
+						if (jas_stream_rewind(cblk->stream)) {
+							assert(0);
+						}
+						cblk->curpass = (cblk->numpasses > 0) ? cblk->passes : 0;
+						cblk->numencpasses = 0;
+						cblk->numlenbits = 3;
+						cblk->numimsbs = band->numbps - cblk->numbps;
+						assert(cblk->numimsbs >= 0);
+						leaf = jpc_tagtree_getleaf(prc->nlibtree, cblk - prc->cblks);
+						jpc_tagtree_setvalue(prc->nlibtree, leaf, cblk->numimsbs);
+
+						if (raflag) {
+							endpasses = &cblk->passes[cblk->numpasses];
+							for (pass = cblk->passes; pass != endpasses; ++pass) {
+								pass->lyrno = -1;
+								pass->lyrno = 0;
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+
+}
+
+jpc_pi_t *jpc_enc_pi_create(jpc_enc_cp_t *cp, jpc_enc_tile_t *tile)
+{
+	jpc_pi_t *pi;
+	int compno;
+	jpc_picomp_t *picomp;
+	jpc_pirlvl_t *pirlvl;
+	jpc_enc_tcmpt_t *tcomp;
+	int rlvlno;
+	jpc_enc_rlvl_t *rlvl;
+	int prcno;
+	int *prclyrno;
+
+	if (!(pi = jpc_pi_create0())) {
+		return 0;
+	}
+	pi->pktno = -1;
+	pi->numcomps = cp->numcmpts;
+	if (!(pi->picomps = jas_malloc(pi->numcomps * sizeof(jpc_picomp_t)))) {
+		jpc_pi_destroy(pi);
+		return 0;
+	}
+	for (compno = 0, picomp = pi->picomps; compno < pi->numcomps; ++compno,
+	  ++picomp) {
+		picomp->pirlvls = 0;
+	}
+
+	for (compno = 0, tcomp = tile->tcmpts, picomp = pi->picomps;
+	  compno < pi->numcomps; ++compno, ++tcomp, ++picomp) {
+		picomp->numrlvls = tcomp->numrlvls;
+		if (!(picomp->pirlvls = jas_malloc(picomp->numrlvls *
+		  sizeof(jpc_pirlvl_t)))) {
+			jpc_pi_destroy(pi);
+			return 0;
+		}
+		for (rlvlno = 0, pirlvl = picomp->pirlvls; rlvlno <
+		  picomp->numrlvls; ++rlvlno, ++pirlvl) {
+			pirlvl->prclyrnos = 0;
+		}
+		for (rlvlno = 0, pirlvl = picomp->pirlvls, rlvl = tcomp->rlvls;
+		  rlvlno < picomp->numrlvls; ++rlvlno, ++pirlvl, ++rlvl) {
+/* XXX sizeof(long) should be sizeof different type */
+			pirlvl->numprcs = rlvl->numprcs;
+			if (rlvl->numprcs) {
+				if (!(pirlvl->prclyrnos = jas_malloc(pirlvl->numprcs *
+				  sizeof(long)))) {
+					jpc_pi_destroy(pi);
+					return 0;
+				}
+			} else {
+				pirlvl->prclyrnos = 0;
+			}
+		}
+	}
+
+	pi->maxrlvls = 0;
+	for (compno = 0, tcomp = tile->tcmpts, picomp = pi->picomps;
+	  compno < pi->numcomps; ++compno, ++tcomp, ++picomp) {
+		picomp->hsamp = cp->ccps[compno].sampgrdstepx;
+		picomp->vsamp = cp->ccps[compno].sampgrdstepy;
+		for (rlvlno = 0, pirlvl = picomp->pirlvls, rlvl = tcomp->rlvls;
+		  rlvlno < picomp->numrlvls; ++rlvlno, ++pirlvl, ++rlvl) {
+			pirlvl->prcwidthexpn = rlvl->prcwidthexpn;
+			pirlvl->prcheightexpn = rlvl->prcheightexpn;
+			for (prcno = 0, prclyrno = pirlvl->prclyrnos;
+			  prcno < pirlvl->numprcs; ++prcno, ++prclyrno) {
+				*prclyrno = 0;
+			}
+			pirlvl->numhprcs = rlvl->numhprcs;
+		}
+		if (pi->maxrlvls < tcomp->numrlvls) {
+			pi->maxrlvls = tcomp->numrlvls;
+		}
+	}
+
+	pi->numlyrs = tile->numlyrs;
+	pi->xstart = tile->tlx;
+	pi->ystart = tile->tly;
+	pi->xend = tile->brx;
+	pi->yend = tile->bry;
+
+	pi->picomp = 0;
+	pi->pirlvl = 0;
+	pi->x = 0;
+	pi->y = 0;
+	pi->compno = 0;
+	pi->rlvlno = 0;
+	pi->prcno = 0;
+	pi->lyrno = 0;
+	pi->xstep = 0;
+	pi->ystep = 0;
+
+	pi->pchgno = -1;
+
+	pi->defaultpchg.prgord = tile->prg;
+	pi->defaultpchg.compnostart = 0;
+	pi->defaultpchg.compnoend = pi->numcomps;
+	pi->defaultpchg.rlvlnostart = 0;
+	pi->defaultpchg.rlvlnoend = pi->maxrlvls;
+	pi->defaultpchg.lyrnoend = pi->numlyrs;
+	pi->pchg = 0;
+
+	pi->valid = 0;
+
+	return pi;
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_t2enc.h b/converter/other/jpeg2000/libjasper/jpc/jpc_t2enc.h
new file mode 100644
index 00000000..e97f8c06
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_t2enc.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Tier 2 Encoder
+ *
+ * $Id$
+ */
+
+#ifndef JPC_T2ENC_H
+#define JPC_T2ENC_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "jpc_enc.h"
+
+/******************************************************************************\
+* Functions.
+\******************************************************************************/
+
+/* Encode the packets for a tile. */
+int jpc_enc_encpkts(jpc_enc_t *enc, jas_stream_t *out);
+
+/* Encode the specified packet. */
+int jpc_enc_encpkt(jpc_enc_t *enc, jas_stream_t *out, int compno, int lvlno,
+  int prcno, int lyrno);
+
+/* Save the tier-2 coding state. */
+void jpc_save_t2state(jpc_enc_t *enc);
+
+/* Restore the tier-2 coding state. */
+void jpc_restore_t2state(jpc_enc_t *enc);
+
+/* Initialize the tier-2 coding state. */
+void jpc_init_t2state(jpc_enc_t *enc, int raflag);
+
+/* Create a packet iterator for the encoder. */
+jpc_pi_t *jpc_enc_pi_create(jpc_enc_cp_t *cp, jpc_enc_tile_t *tile);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_tagtree.c b/converter/other/jpeg2000/libjasper/jpc/jpc_tagtree.c
new file mode 100644
index 00000000..2b13fcf1
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_tagtree.c
@@ -0,0 +1,426 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Tag Tree Library
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <limits.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+
+#include "jasper/jas_malloc.h"
+
+#include "jpc_tagtree.h"
+
+/******************************************************************************\
+* Code for creating and destroying tag trees.
+\******************************************************************************/
+
+static jpc_tagtree_t *jpc_tagtree_alloc(void)
+{
+	jpc_tagtree_t *tree;
+
+	if (!(tree = jas_malloc(sizeof(jpc_tagtree_t)))) {
+		return 0;
+	}
+	tree->numleafsh_ = 0;
+	tree->numleafsv_ = 0;
+	tree->numnodes_ = 0;
+	tree->nodes_ = 0;
+
+	return tree;
+}
+
+/* Create a tag tree. */
+
+jpc_tagtree_t *jpc_tagtree_create(int numleafsh, int numleafsv)
+{
+	int nplh[JPC_TAGTREE_MAXDEPTH];
+	int nplv[JPC_TAGTREE_MAXDEPTH];
+	jpc_tagtreenode_t *node;
+	jpc_tagtreenode_t *parentnode;
+	jpc_tagtreenode_t *parentnode0;
+	jpc_tagtree_t *tree;
+	int i;
+	int j;
+	int k;
+	int numlvls;
+	int n;
+
+	assert(numleafsh > 0 && numleafsv > 0);
+
+	if (!(tree = jpc_tagtree_alloc())) {
+		return 0;
+	}
+	tree->numleafsh_ = numleafsh;
+	tree->numleafsv_ = numleafsv;
+
+	numlvls = 0;
+	nplh[0] = numleafsh;
+	nplv[0] = numleafsv;
+	do {
+		n = nplh[numlvls] * nplv[numlvls];
+		nplh[numlvls + 1] = (nplh[numlvls] + 1) / 2;
+		nplv[numlvls + 1] = (nplv[numlvls] + 1) / 2;
+		tree->numnodes_ += n;
+		++numlvls;
+	} while (n > 1);
+
+	if (!(tree->nodes_ = jas_malloc(tree->numnodes_ * sizeof(jpc_tagtreenode_t)))) {
+		return 0;
+	}
+
+	/* Initialize the parent links for all nodes in the tree. */
+
+	node = tree->nodes_;
+	parentnode = &tree->nodes_[tree->numleafsh_ * tree->numleafsv_];
+	parentnode0 = parentnode;
+
+	for (i = 0; i < numlvls - 1; ++i) {
+		for (j = 0; j < nplv[i]; ++j) {
+			k = nplh[i];
+			while (--k >= 0) {
+				node->parent_ = parentnode;
+				++node;
+				if (--k >= 0) {
+					node->parent_ = parentnode;
+					++node;
+				}
+				++parentnode;
+			}
+			if ((j & 1) || j == nplv[i] - 1) {
+				parentnode0 = parentnode;
+			} else {
+				parentnode = parentnode0;
+				parentnode0 += nplh[i];
+			}
+		}
+	}
+	node->parent_ = 0;
+
+	/* Initialize the data values to something sane. */
+
+	jpc_tagtree_reset(tree);
+
+	return tree;
+}
+
+/* Destroy a tag tree. */
+
+void jpc_tagtree_destroy(jpc_tagtree_t *tree)
+{
+	if (tree->nodes_) {
+		jas_free(tree->nodes_);
+	}
+	jas_free(tree);
+}
+
+/******************************************************************************\
+* Code.
+\******************************************************************************/
+
+/* Copy state information from one tag tree to another. */
+
+void jpc_tagtree_copy(jpc_tagtree_t *dsttree, jpc_tagtree_t *srctree)
+{
+	int n;
+	jpc_tagtreenode_t *srcnode;
+	jpc_tagtreenode_t *dstnode;
+
+	/* The two tag trees must have similar sizes. */
+	assert(srctree->numleafsh_ == dsttree->numleafsh_ &&
+	  srctree->numleafsv_ == dsttree->numleafsv_);
+
+	n = srctree->numnodes_;
+	srcnode = srctree->nodes_;
+	dstnode = dsttree->nodes_;
+	while (--n >= 0) {
+		dstnode->value_ = srcnode->value_;
+		dstnode->low_ = srcnode->low_;
+		dstnode->known_ = srcnode->known_;
+		++dstnode;
+		++srcnode;
+	}
+}
+
+/* Reset all of the state information associated with a tag tree. */
+
+void jpc_tagtree_reset(jpc_tagtree_t *tree)
+{
+	int n;
+	jpc_tagtreenode_t *node;
+
+	n = tree->numnodes_;
+	node = tree->nodes_;
+
+	while (--n >= 0) {
+		node->value_ = INT_MAX;
+		node->low_ = 0;
+		node->known_ = 0;
+		++node;
+	}
+}
+
+/* Set the value associated with the specified leaf node, updating
+the other nodes as necessary. */
+
+void jpc_tagtree_setvalue(jpc_tagtree_t *tree, jpc_tagtreenode_t *leaf, int value)
+{
+	jpc_tagtreenode_t *node;
+
+	assert(value >= 0);
+
+	node = leaf;
+	while (node && node->value_ > value) {
+		node->value_ = value;
+		node = node->parent_;
+	}
+}
+
+/* Get a particular leaf node. */
+
+jpc_tagtreenode_t *jpc_tagtree_getleaf(jpc_tagtree_t *tree, int n)
+{
+	return &tree->nodes_[n];
+}
+
+/* Invoke the tag tree encoding procedure. */
+
+int jpc_tagtree_encode(jpc_tagtree_t *tree, jpc_tagtreenode_t *leaf, int threshold,
+  jpc_bitstream_t *out)
+{
+	jpc_tagtreenode_t *stk[JPC_TAGTREE_MAXDEPTH - 1];
+	jpc_tagtreenode_t **stkptr;
+	jpc_tagtreenode_t *node;
+	int low;
+
+	assert(leaf);
+	assert(threshold >= 0);
+
+	/* Traverse to the root of the tree, recording the path taken. */
+	stkptr = stk;
+	node = leaf;
+	while (node->parent_) {
+		*stkptr++ = node;
+		node = node->parent_;
+	}
+
+	low = 0;
+	for (;;) {
+		if (low > node->low_) {
+			/* Deferred propagation of the lower bound downward in
+			  the tree. */
+			node->low_ = low;
+		} else {
+			low = node->low_;
+		}
+
+		while (low < threshold) {
+			if (low >= node->value_) {
+				if (!node->known_) {
+					if (jpc_bitstream_putbit(out, 1) == EOF) {
+						return -1;
+					}
+					node->known_ = 1;
+				}
+				break;
+			}
+			if (jpc_bitstream_putbit(out, 0) == EOF) {
+				return -1;
+			}
+			++low;
+		}
+		node->low_ = low;
+		if (stkptr == stk) {
+			break;
+		}
+		node = *--stkptr;
+
+	}
+	return (leaf->low_ < threshold) ? 1 : 0;
+
+}
+
+/* Invoke the tag tree decoding procedure. */
+
+int jpc_tagtree_decode(jpc_tagtree_t *tree, jpc_tagtreenode_t *leaf, int threshold,
+  jpc_bitstream_t *in)
+{
+	jpc_tagtreenode_t *stk[JPC_TAGTREE_MAXDEPTH - 1];
+	jpc_tagtreenode_t **stkptr;
+	jpc_tagtreenode_t *node;
+	int low;
+	int ret;
+
+	assert(threshold >= 0);
+
+	/* Traverse to the root of the tree, recording the path taken. */
+	stkptr = stk;
+	node = leaf;
+	while (node->parent_) {
+		*stkptr++ = node;
+		node = node->parent_;
+	}
+
+	low = 0;
+	for (;;) {
+		if (low > node->low_) {
+			node->low_ = low;
+		} else {
+			low = node->low_;
+		}
+		while (low < threshold && low < node->value_) {
+			if ((ret = jpc_bitstream_getbit(in)) < 0) {
+				return -1;
+			}
+			if (ret) {
+				node->value_ = low;
+			} else {
+				++low;
+			}
+		}
+		node->low_ = low;
+		if (stkptr == stk) {
+			break;
+		}
+		node = *--stkptr;
+	}
+
+	return (node->value_ < threshold) ? 1 : 0;
+}
+
+/******************************************************************************\
+* Code for debugging.
+\******************************************************************************/
+
+void jpc_tagtree_dump(jpc_tagtree_t *tree, FILE *out)
+{
+	jpc_tagtreenode_t *node;
+	int n;
+
+	node = tree->nodes_;
+	n = tree->numnodes_;
+	while (--n >= 0) {
+		fprintf(out, "node %p, parent %p, value %d, lower %d, known %d\n",
+		  (void *) node, (void *) node->parent_, node->value_, node->low_,
+		  node->known_);
+		++node;
+	}
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_tagtree.h b/converter/other/jpeg2000/libjasper/jpc/jpc_tagtree.h
new file mode 100644
index 00000000..1892b13f
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_tagtree.h
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Tag Tree Library
+ *
+ * $Id$
+ */
+
+#ifndef JPC_TAGTREE_H
+#define JPC_TAGTREE_H
+
+/******************************************************************************\
+* Includes
+\******************************************************************************/
+
+#include <limits.h>
+#include <stdio.h>
+
+#include "jpc_bs.h"
+
+/******************************************************************************\
+* Constants
+\******************************************************************************/
+
+/* The maximum allowable depth for a tag tree. */
+#define JPC_TAGTREE_MAXDEPTH	32
+
+/******************************************************************************\
+* Types
+\******************************************************************************/
+
+/*
+ * Tag tree node.
+ */
+
+typedef struct jpc_tagtreenode_ {
+
+	/* The parent of this node. */
+	struct jpc_tagtreenode_ *parent_;
+
+	/* The value associated with this node. */
+	int value_;
+
+	/* The lower bound on the value associated with this node. */
+	int low_;
+
+	/* A flag indicating if the value is known exactly. */
+	int known_;
+
+} jpc_tagtreenode_t;
+
+/*
+ * Tag tree.
+ */
+
+typedef struct {
+
+	/* The number of leaves in the horizontal direction. */
+	int numleafsh_;
+
+	/* The number of leaves in the vertical direction. */
+	int numleafsv_;
+
+	/* The total number of nodes in the tree. */
+	int numnodes_;
+
+	/* The nodes. */
+	jpc_tagtreenode_t *nodes_;
+
+} jpc_tagtree_t;
+
+/******************************************************************************\
+* Functions.
+\******************************************************************************/
+
+/* Create a tag tree. */
+jpc_tagtree_t *jpc_tagtree_create(int numleafsh, int numleafsv);
+
+/* Destroy a tag tree. */
+void jpc_tagtree_destroy(jpc_tagtree_t *tree);
+
+/* Copy data from one tag tree to another. */
+void jpc_tagtree_copy(jpc_tagtree_t *dsttree, jpc_tagtree_t *srctree);
+
+/* Reset the tag tree state. */
+void jpc_tagtree_reset(jpc_tagtree_t *tree);
+
+/* Set the value associated with a particular leaf node of a tag tree. */
+void jpc_tagtree_setvalue(jpc_tagtree_t *tree, jpc_tagtreenode_t *leaf,
+  int value);
+
+/* Get a pointer to a particular leaf node. */
+jpc_tagtreenode_t *jpc_tagtree_getleaf(jpc_tagtree_t *tree, int n);
+
+/* Invoke the tag tree decoding procedure. */
+int jpc_tagtree_decode(jpc_tagtree_t *tree, jpc_tagtreenode_t *leaf,
+  int threshold, jpc_bitstream_t *in);
+
+/* Invoke the tag tree encoding procedure. */
+int jpc_tagtree_encode(jpc_tagtree_t *tree, jpc_tagtreenode_t *leaf,
+  int threshold, jpc_bitstream_t *out);
+
+/* Dump a tag tree (for debugging purposes). */
+void jpc_tagtree_dump(jpc_tagtree_t *tree, FILE *out);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_tsfb.c b/converter/other/jpeg2000/libjasper/jpc/jpc_tsfb.c
new file mode 100644
index 00000000..f7d66b57
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_tsfb.c
@@ -0,0 +1,662 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Tree-Structured Filter Bank (TSFB) Library
+ *
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include <assert.h>
+
+#include "jasper/jas_malloc.h"
+#include "jasper/jas_seq.h"
+
+#include "jpc_tsfb.h"
+#include "jpc_cod.h"
+#include "jpc_cs.h"
+#include "jpc_util.h"
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+#define	bandnotovind(tsfbnode, x)	((x) / (tsfbnode)->numhchans)
+#define	bandnotohind(tsfbnode, x)	((x) % (tsfbnode)->numhchans)
+
+static jpc_tsfb_t *jpc_tsfb_create(void);
+static jpc_tsfbnode_t *jpc_tsfbnode_create(void);
+static void jpc_tsfbnode_destroy(jpc_tsfbnode_t *node);
+static void jpc_tsfbnode_synthesize(jpc_tsfbnode_t *node, int flags, jas_seq2d_t *x);
+static void jpc_tsfbnode_analyze(jpc_tsfbnode_t *node, int flags, jas_seq2d_t *x);
+static void qmfb2d_getbands(jpc_qmfb1d_t *hqmfb, jpc_qmfb1d_t *vqmfb,
+  uint_fast32_t xstart, uint_fast32_t ystart, uint_fast32_t xend,
+  uint_fast32_t yend, int maxbands, int *numbandsptr, jpc_tsfbnodeband_t *bands);
+static void jpc_tsfbnode_getbandstree(jpc_tsfbnode_t *node, uint_fast32_t posxstart,
+  uint_fast32_t posystart, uint_fast32_t xstart, uint_fast32_t ystart,
+  uint_fast32_t xend, uint_fast32_t yend, jpc_tsfb_band_t **bands);
+static int jpc_tsfbnode_findchild(jpc_tsfbnode_t *parnode, jpc_tsfbnode_t *cldnode);
+static int jpc_tsfbnode_getequivfilters(jpc_tsfbnode_t *tsfbnode, int cldind,
+  int width, int height, jas_seq_t **vfilter, jas_seq_t **hfilter);
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+jpc_tsfb_t *jpc_tsfb_wavelet(jpc_qmfb1d_t *hqmfb, jpc_qmfb1d_t *vqmfb, int numdlvls)
+{
+	jpc_tsfb_t *tsfb;
+	int dlvlno;
+	jpc_tsfbnode_t *curnode;
+	jpc_tsfbnode_t *prevnode;
+	int childno;
+	if (!(tsfb = jpc_tsfb_create())) {
+		return 0;
+	}
+	prevnode = 0;
+	for (dlvlno = 0; dlvlno < numdlvls; ++dlvlno) {
+		if (!(curnode = jpc_tsfbnode_create())) {
+			jpc_tsfb_destroy(tsfb);
+			return 0;
+		}
+		if (prevnode) {
+			prevnode->children[0] = curnode;
+			++prevnode->numchildren;
+			curnode->parent = prevnode;
+		} else {
+			tsfb->root = curnode;
+			curnode->parent = 0;
+		}
+		if (hqmfb) {
+			curnode->numhchans = jpc_qmfb1d_getnumchans(hqmfb);
+			if (!(curnode->hqmfb = jpc_qmfb1d_copy(hqmfb))) {
+				jpc_tsfb_destroy(tsfb);
+				return 0;
+			}
+		} else {
+			curnode->hqmfb = 0;
+			curnode->numhchans = 1;
+		}
+		if (vqmfb) {
+			curnode->numvchans = jpc_qmfb1d_getnumchans(vqmfb);
+			if (!(curnode->vqmfb = jpc_qmfb1d_copy(vqmfb))) {
+				jpc_tsfb_destroy(tsfb);
+				return 0;
+			}
+		} else {
+			curnode->vqmfb = 0;
+			curnode->numvchans = 1;
+		}
+		curnode->maxchildren = curnode->numhchans * curnode->numvchans;
+		for (childno = 0; childno < curnode->maxchildren;
+		  ++childno) {
+			curnode->children[childno] = 0;
+		}
+		prevnode = curnode;
+	}
+	return tsfb;
+}
+
+static jpc_tsfb_t *jpc_tsfb_create()
+{
+	jpc_tsfb_t *tsfb;
+	if (!(tsfb = jas_malloc(sizeof(jpc_tsfb_t)))) {
+		return 0;
+	}
+	tsfb->root = 0;
+	return tsfb;
+}
+
+void jpc_tsfb_destroy(jpc_tsfb_t *tsfb)
+{
+	if (tsfb->root) {
+		jpc_tsfbnode_destroy(tsfb->root);
+	}
+	jas_free(tsfb);
+}
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+void jpc_tsfb_analyze(jpc_tsfb_t *tsfb, int flags, jas_seq2d_t *x)
+{
+	if (tsfb->root) {
+		jpc_tsfbnode_analyze(tsfb->root, flags, x);
+	}
+}
+
+static void jpc_tsfbnode_analyze(jpc_tsfbnode_t *node, int flags, jas_seq2d_t *x)
+{
+	jpc_tsfbnodeband_t nodebands[JPC_TSFB_MAXBANDSPERNODE];
+	int numbands;
+	jas_seq2d_t *y;
+	int bandno;
+	jpc_tsfbnodeband_t *band;
+
+	if (node->vqmfb) {
+		jpc_qmfb1d_analyze(node->vqmfb, flags | JPC_QMFB1D_VERT, x);
+	}
+	if (node->hqmfb) {
+		jpc_qmfb1d_analyze(node->hqmfb, flags, x);
+	}
+	if (node->numchildren > 0) {
+		qmfb2d_getbands(node->hqmfb, node->vqmfb, jas_seq2d_xstart(x),
+		  jas_seq2d_ystart(x), jas_seq2d_xend(x), jas_seq2d_yend(x),
+		  JPC_TSFB_MAXBANDSPERNODE, &numbands, nodebands);
+		y = jas_seq2d_create(0, 0, 0, 0);
+		assert(y);
+		for (bandno = 0, band = nodebands; bandno < numbands; ++bandno, ++band) {
+			if (node->children[bandno]) {
+				if (band->xstart != band->xend && band->ystart != band->yend) {
+					jas_seq2d_bindsub(y, x, band->locxstart, band->locystart,
+					  band->locxend, band->locyend);
+					jas_seq2d_setshift(y, band->xstart, band->ystart);
+					jpc_tsfbnode_analyze(node->children[bandno], flags, y);
+				}
+			}
+		}
+		jas_matrix_destroy(y);
+	}
+}
+
+void jpc_tsfb_synthesize(jpc_tsfb_t *tsfb, int flags, jas_seq2d_t *x)
+{
+	if (tsfb->root) {
+		jpc_tsfbnode_synthesize(tsfb->root, flags, x);
+	}
+}
+
+static void jpc_tsfbnode_synthesize(jpc_tsfbnode_t *node, int flags, jas_seq2d_t *x)
+{
+	jpc_tsfbnodeband_t nodebands[JPC_TSFB_MAXBANDSPERNODE];
+	int numbands;
+	jas_seq2d_t *y;
+	int bandno;
+	jpc_tsfbnodeband_t *band;
+
+	if (node->numchildren > 0) {
+		qmfb2d_getbands(node->hqmfb, node->vqmfb, jas_seq2d_xstart(x),
+		  jas_seq2d_ystart(x), jas_seq2d_xend(x), jas_seq2d_yend(x),
+		  JPC_TSFB_MAXBANDSPERNODE, &numbands, nodebands);
+		y = jas_seq2d_create(0, 0, 0, 0);
+		for (bandno = 0, band = nodebands; bandno < numbands; ++bandno, ++band) {
+			if (node->children[bandno]) {
+				if (band->xstart != band->xend && band->ystart != band->yend) {
+					jas_seq2d_bindsub(y, x, band->locxstart, band->locystart,
+					  band->locxend, band->locyend);
+					jas_seq2d_setshift(y, band->xstart, band->ystart);
+					jpc_tsfbnode_synthesize(node->children[bandno], flags, y);
+				}
+			}
+		}
+		jas_seq2d_destroy(y);
+	}
+	if (node->hqmfb) {
+		jpc_qmfb1d_synthesize(node->hqmfb, flags, x);
+	}
+	if (node->vqmfb) {
+		jpc_qmfb1d_synthesize(node->vqmfb, flags | JPC_QMFB1D_VERT, x);
+	}
+}
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+
+int jpc_tsfb_getbands(jpc_tsfb_t *tsfb, uint_fast32_t xstart, uint_fast32_t ystart,
+  uint_fast32_t xend, uint_fast32_t yend, jpc_tsfb_band_t *bands)
+{
+	jpc_tsfb_band_t *savbands;
+	savbands = bands;
+	if (!tsfb->root) {
+		bands[0].xstart = xstart;
+		bands[0].ystart = ystart;
+		bands[0].xend = xend;
+		bands[0].yend = yend;
+		bands[0].locxstart = xstart;
+		bands[0].locystart = ystart;
+		bands[0].locxend = xend;
+		bands[0].locyend = yend;
+		bands[0].orient = JPC_TSFB_LL;
+		bands[0].synenergywt = JPC_FIX_ONE;
+		++bands;
+	} else {
+		jpc_tsfbnode_getbandstree(tsfb->root, xstart, ystart,
+		  xstart, ystart, xend, yend, &bands);
+	}
+	return bands - savbands;
+}
+
+static void jpc_tsfbnode_getbandstree(jpc_tsfbnode_t *node, uint_fast32_t posxstart,
+  uint_fast32_t posystart, uint_fast32_t xstart, uint_fast32_t ystart,
+  uint_fast32_t xend, uint_fast32_t yend, jpc_tsfb_band_t **bands)
+{
+	jpc_tsfbnodeband_t nodebands[JPC_TSFB_MAXBANDSPERNODE];
+	jpc_tsfbnodeband_t *nodeband;
+	int nodebandno;
+	int numnodebands;
+	jpc_tsfb_band_t *band;
+	jas_seq_t *hfilter;
+	jas_seq_t *vfilter;
+
+	qmfb2d_getbands(node->hqmfb, node->vqmfb, xstart, ystart, xend, yend,
+	  JPC_TSFB_MAXBANDSPERNODE, &numnodebands, nodebands);
+	if (node->numchildren > 0) {
+		for (nodebandno = 0, nodeband = nodebands;
+		  nodebandno < numnodebands; ++nodebandno, ++nodeband) {
+			if (node->children[nodebandno]) {
+				jpc_tsfbnode_getbandstree(node->children[
+				  nodebandno], posxstart +
+				  nodeband->locxstart - xstart, posystart +
+				  nodeband->locystart - ystart, nodeband->xstart,
+				  nodeband->ystart, nodeband->xend,
+				  nodeband->yend, bands);
+
+			}
+		}
+	}
+assert(numnodebands == 4 || numnodebands == 3);
+	for (nodebandno = 0, nodeband = nodebands; nodebandno < numnodebands;
+	  ++nodebandno, ++nodeband) {
+		if (!node->children[nodebandno]) {
+			band = *bands;
+			band->xstart = nodeband->xstart;
+			band->ystart = nodeband->ystart;
+			band->xend = nodeband->xend;
+			band->yend = nodeband->yend;
+			band->locxstart = posxstart + nodeband->locxstart -
+			  xstart;
+			band->locystart = posystart + nodeband->locystart -
+			  ystart;
+			band->locxend = band->locxstart + band->xend -
+			  band->xstart;
+			band->locyend = band->locystart + band->yend -
+			  band->ystart;
+			if (numnodebands == 4) {
+				switch (nodebandno) {
+				case 0:
+					band->orient = JPC_TSFB_LL;
+					break;
+				case 1:
+					band->orient = JPC_TSFB_HL;
+					break;
+				case 2:
+					band->orient = JPC_TSFB_LH;
+					break;
+				case 3:
+					band->orient = JPC_TSFB_HH;
+					break;
+				default:
+					abort();
+					break;
+				}
+			} else {
+				switch (nodebandno) {
+				case 0:
+					band->orient = JPC_TSFB_HL;
+					break;
+				case 1:
+					band->orient = JPC_TSFB_LH;
+					break;
+				case 2:
+					band->orient = JPC_TSFB_HH;
+					break;
+				default:
+					abort();
+					break;
+				}
+			}
+			jpc_tsfbnode_getequivfilters(node, nodebandno, band->xend - band->xstart, band->yend - band->ystart, &hfilter, &vfilter);
+			band->synenergywt = jpc_fix_mul(jpc_seq_norm(hfilter),
+			  jpc_seq_norm(vfilter));
+			jas_seq_destroy(hfilter);
+			jas_seq_destroy(vfilter);
+			++(*bands);
+		}
+	}
+}
+
+/******************************************************************************\
+*
+\******************************************************************************/
+
+static jpc_tsfbnode_t *jpc_tsfbnode_create()
+{
+	jpc_tsfbnode_t *node;
+	if (!(node = jas_malloc(sizeof(jpc_tsfbnode_t)))) {
+		return 0;
+	}
+	node->numhchans = 0;
+	node->numvchans = 0;
+	node->numchildren = 0;
+	node->maxchildren = 0;
+	node->hqmfb = 0;
+	node->vqmfb = 0;
+	node->parent = 0;
+	return node;
+}
+
+static void jpc_tsfbnode_destroy(jpc_tsfbnode_t *node)
+{
+	jpc_tsfbnode_t **child;
+	int childno;
+	for (childno = 0, child = node->children; childno < node->maxchildren;
+	  ++childno, ++child) {
+		if (*child) {
+			jpc_tsfbnode_destroy(*child);
+		}
+	}
+	if (node->hqmfb) {
+		jpc_qmfb1d_destroy(node->hqmfb);
+	}
+	if (node->vqmfb) {
+		jpc_qmfb1d_destroy(node->vqmfb);
+	}
+	jas_free(node);
+}
+
+
+
+
+
+
+
+
+static void qmfb2d_getbands(jpc_qmfb1d_t *hqmfb, jpc_qmfb1d_t *vqmfb,
+  uint_fast32_t xstart, uint_fast32_t ystart, uint_fast32_t xend,
+  uint_fast32_t yend, int maxbands, int *numbandsptr, jpc_tsfbnodeband_t *bands)
+{
+	jpc_qmfb1dband_t hbands[JPC_QMFB1D_MAXCHANS];
+	jpc_qmfb1dband_t vbands[JPC_QMFB1D_MAXCHANS];
+	int numhbands;
+	int numvbands;
+	int numbands;
+	int bandno;
+	int hbandno;
+	int vbandno;
+	jpc_tsfbnodeband_t *band;
+
+	if (hqmfb) {
+		jpc_qmfb1d_getbands(hqmfb, 0, xstart, ystart, xend, yend,
+		  JPC_QMFB1D_MAXCHANS, &numhbands, hbands);
+	} else {
+		numhbands = 1;
+		hbands[0].start = xstart;
+		hbands[0].end = xend;
+		hbands[0].locstart = xstart;
+		hbands[0].locend = xend;
+	}
+	if (vqmfb) {
+		jpc_qmfb1d_getbands(vqmfb, JPC_QMFB1D_VERT, xstart, ystart, xend,
+		  yend, JPC_QMFB1D_MAXCHANS, &numvbands, vbands);
+	} else {
+		numvbands = 1;
+		vbands[0].start = ystart;
+		vbands[0].end = yend;
+		vbands[0].locstart = ystart;
+		vbands[0].locend = yend;
+	}
+	numbands = numhbands * numvbands;
+	*numbandsptr = numbands;
+	for (bandno = 0, band = bands; bandno < numbands; ++bandno, ++band) {
+		hbandno = bandno % numhbands;
+		vbandno = bandno / numhbands;
+		band->xstart = hbands[hbandno].start;
+		band->ystart = vbands[vbandno].start;
+		band->xend = hbands[hbandno].end;
+		band->yend = vbands[vbandno].end;
+		band->locxstart = hbands[hbandno].locstart;
+		band->locystart = vbands[vbandno].locstart;
+		band->locxend = hbands[hbandno].locend;
+		band->locyend = vbands[vbandno].locend;
+		assert(band->xstart <= band->xend &&
+		  band->ystart <= band->yend);
+		if (band->xstart == band->xend) {
+			band->yend = band->ystart;
+			band->locyend = band->locystart;
+		} else if (band->ystart == band->yend) {
+			band->xend = band->xstart;
+			band->locxend = band->locxstart;
+		}
+	}
+}
+
+static int jpc_tsfbnode_getequivfilters(jpc_tsfbnode_t *tsfbnode, int cldind,
+  int width, int height, jas_seq_t **hfilter, jas_seq_t **vfilter)
+{
+	jas_seq_t *hseq;
+	jas_seq_t *vseq;
+	jpc_tsfbnode_t *node;
+	jas_seq2d_t *hfilters[JPC_QMFB1D_MAXCHANS];
+	jas_seq2d_t *vfilters[JPC_QMFB1D_MAXCHANS];
+	int numhchans;
+	int numvchans;
+	jas_seq_t *tmpseq;
+
+	hseq = 0;
+	vseq = 0;
+
+	if (!(hseq = jas_seq_create(0, 1))) {
+		goto error;
+	}
+	jas_seq_set(hseq, 0, jpc_inttofix(1));
+	if (!(vseq = jas_seq_create(0, 1))) {
+		goto error;
+	}
+	jas_seq_set(vseq, 0, jpc_inttofix(1));
+
+	node = tsfbnode;
+	while (node) {
+		if (node->hqmfb) {
+			numhchans = jpc_qmfb1d_getnumchans(node->hqmfb);
+			if (jpc_qmfb1d_getsynfilters(node->hqmfb, width, hfilters)) {
+				goto error;
+			}
+			if (!(tmpseq = jpc_seq_upsample(hseq, numhchans))) {
+				goto error;
+			}
+			jas_seq_destroy(hseq);
+			hseq = tmpseq;
+			if (!(tmpseq = jpc_seq_conv(hseq, hfilters[bandnotohind(node, cldind)]))) {
+				goto error;
+			}
+			jas_seq_destroy(hfilters[0]);
+			jas_seq_destroy(hfilters[1]);
+			jas_seq_destroy(hseq);
+			hseq = tmpseq;
+		}
+		if (node->vqmfb) {
+			numvchans = jpc_qmfb1d_getnumchans(node->vqmfb);
+			if (jpc_qmfb1d_getsynfilters(node->vqmfb, height, vfilters)) {
+				abort();
+			}
+			if (!(tmpseq = jpc_seq_upsample(vseq, numvchans))) {
+				goto error;
+			}
+			jas_seq_destroy(vseq);
+			vseq = tmpseq;
+			if (!(tmpseq = jpc_seq_conv(vseq, vfilters[bandnotovind(node, cldind)]))) {
+				goto error;
+			}
+			jas_seq_destroy(vfilters[0]);
+			jas_seq_destroy(vfilters[1]);
+			jas_seq_destroy(vseq);
+			vseq = tmpseq;
+		}
+		if (node->parent) {
+			cldind = jpc_tsfbnode_findchild(node->parent, node);
+		}
+		node = node->parent;
+	}
+
+	*hfilter = hseq;
+	*vfilter = vseq;
+
+	return 0;
+
+error:
+	if (hseq) {
+		jas_seq_destroy(hseq);
+	}
+	if (vseq) {
+		jas_seq_destroy(vseq);
+	}
+	return -1;
+
+}
+
+static int jpc_tsfbnode_findchild(jpc_tsfbnode_t *parnode, jpc_tsfbnode_t *cldnode)
+{
+	int i;
+
+	for (i = 0; i < parnode->maxchildren; i++) {
+		if (parnode->children[i] == cldnode)
+			return i;
+	}
+	assert(0);
+	return -1;
+}
+
+jpc_tsfb_t *jpc_cod_gettsfb(int qmfbid, int numlevels)
+{
+	jpc_tsfb_t *tsfb;
+
+	switch (qmfbid) {
+	case JPC_COX_RFT:
+		qmfbid = JPC_QMFB1D_FT;
+		break;
+	case JPC_COX_INS:
+		qmfbid = JPC_QMFB1D_NS;
+		break;
+	default:
+		assert(0);
+		qmfbid = 10;
+		break;
+	}
+
+{
+	jpc_qmfb1d_t *hqmfb;
+	hqmfb = jpc_qmfb1d_make(qmfbid);
+	assert(hqmfb);
+	tsfb = jpc_tsfb_wavelet(hqmfb, hqmfb, numlevels);
+	assert(tsfb);
+	jpc_qmfb1d_destroy(hqmfb);
+}
+
+	return tsfb;
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_tsfb.h b/converter/other/jpeg2000/libjasper/jpc/jpc_tsfb.h
new file mode 100644
index 00000000..8670c22e
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_tsfb.h
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * Tree-Structured Filter Bank (TSFB) Library
+ *
+ * $Id$
+ */
+
+#ifndef JPC_TSFB_H
+#define JPC_TSFB_H
+
+/******************************************************************************\
+* Includes.
+\******************************************************************************/
+
+#include "jasper/jas_seq.h"
+
+#include "jpc_fix.h"
+#include "jpc_qmfb.h"
+
+/******************************************************************************\
+* Constants.
+\******************************************************************************/
+
+#define	JPC_TSFB_MAXBANDS	(JPC_TSFB_MAXDEPTH * 3 + 1)
+#define	JPC_TSFB_MAXDEPTH	32
+#define	JPC_TSFB_RITIMODE	JPC_QMFB1D_RITIMODE
+
+#define	JPC_TSFB_MAXBANDSPERNODE	(JPC_QMFB1D_MAXCHANS * JPC_QMFB1D_MAXCHANS)
+
+#define	JPC_TSFB_PRUNEVERT	0x01
+#define	JPC_TSFB_PRUNEHORZ	0x02
+
+#define JPC_TSFB_LL	0
+#define JPC_TSFB_LH	1
+#define JPC_TSFB_HL	2
+#define JPC_TSFB_HH	3
+
+/******************************************************************************\
+* Types.
+\******************************************************************************/
+
+typedef struct {
+
+	int xstart;
+	int ystart;
+	int xend;
+	int yend;
+	int locxstart;
+	int locystart;
+	int locxend;
+	int locyend;
+
+} jpc_tsfbnodeband_t;
+
+typedef struct jpc_tsfbnode_s {
+
+	int numhchans;
+	int numvchans;
+	jpc_qmfb1d_t *hqmfb;
+	jpc_qmfb1d_t *vqmfb;
+	int maxchildren;
+	int numchildren;
+	struct jpc_tsfbnode_s *children[JPC_TSFB_MAXBANDSPERNODE];
+	struct jpc_tsfbnode_s *parent;
+
+} jpc_tsfbnode_t;
+
+typedef struct {
+	jpc_tsfbnode_t *root;
+} jpc_tsfb_t;
+
+typedef struct {
+	int xstart;
+	int ystart;
+	int xend;
+	int yend;
+	int orient;
+	int locxstart;
+	int locystart;
+	int locxend;
+	int locyend;
+	jpc_fix_t synenergywt;
+} jpc_tsfb_band_t;
+
+/******************************************************************************\
+* Functions.
+\******************************************************************************/
+
+/* Create a TSFB. */
+jpc_tsfb_t *jpc_cod_gettsfb(int qmfbid, int numlevels);
+
+/* Create a wavelet-type TSFB with the specified horizontal and vertical
+  QMFBs. */
+jpc_tsfb_t *jpc_tsfb_wavelet(jpc_qmfb1d_t *hqmfb, jpc_qmfb1d_t *vqmfb,
+  int numdlvls);
+
+/* Destroy a TSFB. */
+void jpc_tsfb_destroy(jpc_tsfb_t *tsfb);
+
+/* Perform analysis. */
+void jpc_tsfb_analyze(jpc_tsfb_t *tsfb, int flags, jas_seq2d_t *x);
+
+/* Perform synthesis. */
+void jpc_tsfb_synthesize(jpc_tsfb_t *tsfb, int flags, jas_seq2d_t *x);
+
+/* Get band information for a TSFB. */
+int jpc_tsfb_getbands(jpc_tsfb_t *tsfb, uint_fast32_t xstart,
+  uint_fast32_t ystart, uint_fast32_t xend, uint_fast32_t yend,
+  jpc_tsfb_band_t *bands);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_util.c b/converter/other/jpeg2000/libjasper/jpc/jpc_util.c
new file mode 100644
index 00000000..82f4b285
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_util.c
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 1999-2000 Image Power, Inc. and the University of
+ *   British Columbia.
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+/*
+ * $Id$
+ */
+
+/******************************************************************************\
+* Includes
+\******************************************************************************/
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "jasper/jas_math.h"
+#include "jasper/jas_malloc.h"
+
+#include "jpc_fix.h"
+#include "jpc_cs.h"
+#include "jpc_flt.h"
+#include "jpc_util.h"
+
+/******************************************************************************\
+* Miscellaneous Functions
+\******************************************************************************/
+
+int jpc_atoaf(const char *s, int *numvalues, double **values)
+{
+	static char delim[] = ", \t\n";
+	char buf[4096];
+	int n;
+	double *vs;
+	char *cp;
+
+	strncpy(buf, s, sizeof(buf));
+	buf[sizeof(buf) - 1] = '\0';
+	n = 0;
+	if ((cp = strtok(buf, delim))) {
+		++n;
+		while ((cp = strtok(0, delim))) {
+			if (cp != '\0') {
+				++n;
+			}
+		}
+	}
+
+	if (n) {
+		if (!(vs = jas_malloc(n * sizeof(double)))) {
+			return -1;
+		}
+
+		strncpy(buf, s, sizeof(buf));
+		buf[sizeof(buf) - 1] = '\0';
+		n = 0;
+		if ((cp = strtok(buf, delim))) {
+			vs[n] = atof(cp);
+			++n;
+			while ((cp = strtok(0, delim))) {
+				if (cp != '\0') {
+					vs[n] = atof(cp);
+					++n;
+				}
+			}
+		}
+	} else {
+		vs = 0;
+	}
+
+	*numvalues = n;
+	*values = vs;
+
+	return 0;
+}
+
+jas_seq_t *jpc_seq_upsample(jas_seq_t *x, int m)
+{
+	jas_seq_t *z;
+	int i;
+
+	if (!(z = jas_seq_create(jas_seq_start(x) * m, (jas_seq_end(x) - 1) * m + 1)))
+		return 0;
+	for (i = jas_seq_start(z); i < jas_seq_end(z); i++) {
+		*jas_seq_getref(z, i) = (!JAS_MOD(i, m)) ? jas_seq_get(x, i / m) :
+		  jpc_inttofix(0);
+	}
+
+	return z;
+}
+
+jpc_fix_t jpc_seq_norm(jas_seq_t *x)
+{
+	jpc_fix_t s;
+	int i;
+
+	s = jpc_inttofix(0);
+	for (i = jas_seq_start(x); i < jas_seq_end(x); i++) {
+		s = jpc_fix_add(s, jpc_fix_mul(jas_seq_get(x, i), jas_seq_get(x, i)));
+	}
+
+	return jpc_dbltofix(sqrt(jpc_fixtodbl(s)));
+}
+
+jas_seq_t *jpc_seq_conv(jas_seq_t *x, jas_seq_t *y)
+{
+	int i;
+	int j;
+	int k;
+	jas_seq_t *z;
+	jpc_fix_t s;
+	jpc_fix_t v;
+
+	z = jas_seq_create(jas_seq_start(x) + jas_seq_start(y),
+	  jas_seq_end(x) + jas_seq_end(y) - 1);
+	assert(z);
+	for (i = jas_seq_start(z); i < jas_seq_end(z); i++) {
+		s = jpc_inttofix(0);
+		for (j = jas_seq_start(y); j < jas_seq_end(y); j++) {
+			k = i - j;
+			if (k < jas_seq_start(x) || k >= jas_seq_end(x)) {
+				v = JPC_FIX_ZERO;
+			} else {
+				v = jas_seq_get(x, k);
+			}
+			s = jpc_fix_add(s, jpc_fix_mul(jas_seq_get(y, j), v));
+		}
+		*jas_seq_getref(z, i) = s;
+	}
+
+	return z;
+}
diff --git a/converter/other/jpeg2000/libjasper/jpc/jpc_util.h b/converter/other/jpeg2000/libjasper/jpc/jpc_util.h
new file mode 100644
index 00000000..c23fc33b
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/jpc_util.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2001-2002 Michael David Adams.
+ * All rights reserved.
+ */
+
+/* __START_OF_JASPER_LICENSE__
+ * 
+ * JasPer Software License
+ * 
+ * IMAGE POWER JPEG-2000 PUBLIC LICENSE
+ * ************************************
+ * 
+ * GRANT:
+ * 
+ * Permission is hereby granted, free of charge, to any person (the "User")
+ * obtaining a copy of this software and associated documentation, to deal
+ * in the JasPer Software without restriction, including without limitation
+ * the right to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the JasPer Software (in source and binary forms),
+ * and to permit persons to whom the JasPer Software is furnished to do so,
+ * provided further that the License Conditions below are met.
+ * 
+ * License Conditions
+ * ******************
+ * 
+ * A.  Redistributions of source code must retain the above copyright notice,
+ * and this list of conditions, and the following disclaimer.
+ * 
+ * B.  Redistributions in binary form must reproduce the above copyright
+ * notice, and this list of conditions, and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * 
+ * C.  Neither the name of Image Power, Inc. nor any other contributor
+ * (including, but not limited to, the University of British Columbia and
+ * Michael David Adams) may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * 
+ * D.  User agrees that it shall not commence any action against Image Power,
+ * Inc., the University of British Columbia, Michael David Adams, or any
+ * other contributors (collectively "Licensors") for infringement of any
+ * intellectual property rights ("IPR") held by the User in respect of any
+ * technology that User owns or has a right to license or sublicense and
+ * which is an element required in order to claim compliance with ISO/IEC
+ * 15444-1 (i.e., JPEG-2000 Part 1).  "IPR" means all intellectual property
+ * rights worldwide arising under statutory or common law, and whether
+ * or not perfected, including, without limitation, all (i) patents and
+ * patent applications owned or licensable by User; (ii) rights associated
+ * with works of authorship including copyrights, copyright applications,
+ * copyright registrations, mask work rights, mask work applications,
+ * mask work registrations; (iii) rights relating to the protection of
+ * trade secrets and confidential information; (iv) any right analogous
+ * to those set forth in subsections (i), (ii), or (iii) and any other
+ * proprietary rights relating to intangible property (other than trademark,
+ * trade dress, or service mark rights); and (v) divisions, continuations,
+ * renewals, reissues and extensions of the foregoing (as and to the extent
+ * applicable) now existing, hereafter filed, issued or acquired.
+ * 
+ * E.  If User commences an infringement action against any Licensor(s) then
+ * such Licensor(s) shall have the right to terminate User's license and
+ * all sublicenses that have been granted hereunder by User to other parties.
+ * 
+ * F.  This software is for use only in hardware or software products that
+ * are compliant with ISO/IEC 15444-1 (i.e., JPEG-2000 Part 1).  No license
+ * or right to this Software is granted for products that do not comply
+ * with ISO/IEC 15444-1.  The JPEG-2000 Part 1 standard can be purchased
+ * from the ISO.
+ * 
+ * THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
+ * NO USE OF THE JASPER SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+ * THIS DISCLAIMER.  THE JASPER SOFTWARE IS PROVIDED BY THE LICENSORS AND
+ * CONTRIBUTORS UNDER THIS LICENSE ON AN ``AS-IS'' BASIS, WITHOUT WARRANTY
+ * OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+ * WARRANTIES THAT THE JASPER SOFTWARE IS FREE OF DEFECTS, IS MERCHANTABLE,
+ * IS FIT FOR A PARTICULAR PURPOSE OR IS NON-INFRINGING.  THOSE INTENDING
+ * TO USE THE JASPER SOFTWARE OR MODIFICATIONS THEREOF FOR USE IN HARDWARE
+ * OR SOFTWARE PRODUCTS ARE ADVISED THAT THEIR USE MAY INFRINGE EXISTING
+ * PATENTS, COPYRIGHTS, TRADEMARKS, OR OTHER INTELLECTUAL PROPERTY RIGHTS.
+ * THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE JASPER SOFTWARE
+ * IS WITH THE USER.  SHOULD ANY PART OF THE JASPER SOFTWARE PROVE DEFECTIVE
+ * IN ANY RESPECT, THE USER (AND NOT THE INITIAL DEVELOPERS, THE UNIVERSITY
+ * OF BRITISH COLUMBIA, IMAGE POWER, INC., MICHAEL DAVID ADAMS, OR ANY
+ * OTHER CONTRIBUTOR) SHALL ASSUME THE COST OF ANY NECESSARY SERVICING,
+ * REPAIR OR CORRECTION.  UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+ * WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+ * INITIAL DEVELOPER, THE UNIVERSITY OF BRITISH COLUMBIA, IMAGE POWER, INC.,
+ * MICHAEL DAVID ADAMS, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE
+ * JASPER SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO
+ * THE USER OR ANY OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION,
+ * DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ * MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF
+ * SUCH PARTY HAD BEEN INFORMED, OR OUGHT TO HAVE KNOWN, OF THE POSSIBILITY
+ * OF SUCH DAMAGES.  THE JASPER SOFTWARE AND UNDERLYING TECHNOLOGY ARE NOT
+ * FAULT-TOLERANT AND ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE OR
+ * RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING
+ * FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES,
+ * AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT
+ * LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * JASPER SOFTWARE OR UNDERLYING TECHNOLOGY OR PRODUCT COULD LEAD DIRECTLY
+ * TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+ * ("HIGH RISK ACTIVITIES").  LICENSOR SPECIFICALLY DISCLAIMS ANY EXPRESS
+ * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.  USER WILL NOT
+ * KNOWINGLY USE, DISTRIBUTE OR RESELL THE JASPER SOFTWARE OR UNDERLYING
+ * TECHNOLOGY OR PRODUCTS FOR HIGH RISK ACTIVITIES AND WILL ENSURE THAT ITS
+ * CUSTOMERS AND END-USERS OF ITS PRODUCTS ARE PROVIDED WITH A COPY OF THE
+ * NOTICE SPECIFIED IN THIS SECTION.
+ * 
+ * __END_OF_JASPER_LICENSE__
+ */
+
+#ifndef JPC_UTIL_H
+#define JPC_UTIL_H
+
+/* Parse a comma separated list of real numbers into an array of doubles. */
+int jpc_atoaf(const char *s, int *numvalues, double **values);
+
+/* Upsample a sequence. */
+jas_seq_t *jpc_seq_upsample(jas_seq_t *seq, int n);
+
+/* Convolve two sequences. */
+jas_seq_t *jpc_seq_conv(jas_seq_t *seq0, jas_seq_t *seq1);
+
+/* Compute the norm of a sequence. */
+jpc_fix_t jpc_seq_norm(jas_seq_t *x);
+
+#endif
diff --git a/converter/other/jpeg2000/libjasper/jpc/partlist b/converter/other/jpeg2000/libjasper/jpc/partlist
new file mode 100644
index 00000000..05c36ff9
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/jpc/partlist
@@ -0,0 +1 @@
+/mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_bs.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_cs.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_dec.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_enc.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_math.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_mct.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_mqcod.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_mqdec.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_mqenc.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_qmfb.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_tagtree.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_t1cod.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_t1dec.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_t1enc.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_tsfb.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_t2cod.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_t2dec.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_t2enc.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_util.o
diff --git a/converter/other/jpeg2000/libjasper/partlist b/converter/other/jpeg2000/libjasper/partlist
new file mode 100644
index 00000000..4a088cee
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper/partlist
@@ -0,0 +1,4 @@
+/mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_debug.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_getopt.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_image.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_init.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_malloc.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_seq.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_stream.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_string.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_tvp.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/base/jas_version.o
+/mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jp2/jp2_cod.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jp2/jp2_dec.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jp2/jp2_enc.o
+/mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_bs.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_cs.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_dec.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_enc.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_math.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_mct.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_mqcod.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_mqdec.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_mqenc.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_qmfb.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_tagtree.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_t1cod.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_t1dec.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_t1enc.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_tsfb.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_t2cod.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_t2dec.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_t2enc.o /mp/hdb4d/home/bryanh/netpbm/converter/other/jpeg2000/libjasper/jpc/jpc_util.o
+
diff --git a/converter/other/jpeg2000/libjasper_compat.h b/converter/other/jpeg2000/libjasper_compat.h
new file mode 100644
index 00000000..401144a3
--- /dev/null
+++ b/converter/other/jpeg2000/libjasper_compat.h
@@ -0,0 +1,24 @@
+/* Here's some stuff to create backward compatibility with older Jasper
+   libraries.  Unfortunately, new versions of the Jasper library are not
+   backward compatible with old applications.
+*/
+/* The color space thing got more complex between Version 1.600 and
+   1.701.  For example, it now allows for multiple kinds of RGB, whereas
+   in 1.600 RGB meant SRGB.  As part of that change, names changed
+   from "colorspace" to "clrspc".
+*/
+#if defined(jas_image_setcolorspace)
+/* Old style color space */
+#define jas_image_setclrspc jas_image_setcolorspace
+#define JAS_CLRSPC_GENRGB JAS_IMAGE_CS_RGB
+#define JAS_CLRSPC_GENGRAY JAS_IMAGE_CS_GRAY
+#define JAS_CLRSPC_UNKNOWN JAS_IMAGE_CS_UNKNOWN
+
+#define jas_clrspc_fam(clrspc) (clrspc)
+#define jas_image_clrspc jas_image_colorspace
+
+#define JAS_CLRSPC_FAM_RGB JAS_IMAGE_CS_RGB
+#define JAS_CLRSPC_FAM_GRAY JAS_IMAGE_CS_GRAY
+#define JAS_CLRSPC_FAM_UNKNOWN JAS_IMAGE_CS_UNKNOWN
+
+#endif
diff --git a/converter/other/jpeg2000/pamtojpeg2k.c b/converter/other/jpeg2000/pamtojpeg2k.c
new file mode 100644
index 00000000..76da46d2
--- /dev/null
+++ b/converter/other/jpeg2000/pamtojpeg2k.c
@@ -0,0 +1,522 @@
+/*****************************************************************************
+                              pamtojpeg2k
+******************************************************************************
+
+  Convert a PNM image to JPEG-2000 code stream image
+
+  By Bryan Henderson, San Jose CA  2002.10.26
+
+*****************************************************************************/
+
+#define _BSD_SOURCE 1    /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+#include <string.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "mallocvar.h"
+
+#include <jasper/jasper.h>
+#include "libjasper_compat.h"
+
+
+enum compmode {COMPMODE_INTEGER, COMPMODE_REAL};
+
+enum progression {PROG_LRCP, PROG_RLCP, PROG_RPCL, PROG_PCRL, PROG_CPRL};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    char * inputFilename;
+    unsigned int imgareatlx;
+    unsigned int imgareatly;
+    unsigned int tilegrdtlx;
+    unsigned int tilegrdtly;
+    unsigned int tilewidth;
+    unsigned int tileheight;
+    unsigned int prcwidth;
+    unsigned int prcheight;
+    unsigned int cblkwidth;
+    unsigned int cblkheight;
+    enum compmode compmode;
+    float        compressionRatio;
+    char *       ilyrrates;
+    enum progression progression;
+    unsigned int numrlvls;
+    unsigned int numgbits;
+    unsigned int nomct;
+    unsigned int sop;
+    unsigned int eph;
+    unsigned int lazy;
+    unsigned int termall;
+    unsigned int segsym;
+    unsigned int vcausal;
+    unsigned int pterm;
+    unsigned int resetprob;
+    unsigned int debuglevel;  /* Jasper library debug level */
+    unsigned int verbose;
+};
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that many of the strings that this function returns in the
+   *cmdline_p structure are actually in the supplied argv array.  And
+   sometimes, one of these strings is actually just a suffix of an entry
+   in argv!
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int imgareatlxSpec, imgareatlySpec;
+    unsigned int tilegrdtlxSpec, tilegrdtlySpec;
+    unsigned int tilewidthSpec, tileheightSpec;
+    unsigned int prcwidthSpec, prcheightSpec;
+    unsigned int cblkwidthSpec, cblkheightSpec;
+    unsigned int modeSpec, compressionSpec, ilyrratesSpec;
+    unsigned int progressionSpec, numrlvlsSpec, numgbitsSpec;
+    unsigned int debuglevelSpec;
+
+    char * progressionOpt;
+    char * modeOpt;
+
+    unsigned int option_def_index;
+    
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "imgareatlx",   OPT_UINT,   &cmdlineP->imgareatlx,
+            &imgareatlxSpec,       0);
+    OPTENT3(0, "imgareatly",   OPT_UINT,   &cmdlineP->imgareatly,
+            &imgareatlySpec,       0);
+    OPTENT3(0, "tilegrdtlx",   OPT_UINT,   &cmdlineP->tilegrdtlx,
+            &tilegrdtlxSpec,       0);
+    OPTENT3(0, "tilegrdtly",   OPT_UINT,   &cmdlineP->tilegrdtly,
+            &tilegrdtlySpec,       0);
+    OPTENT3(0, "tilewidth",    OPT_UINT,   &cmdlineP->tilewidth,
+            &tilewidthSpec,        0);
+    OPTENT3(0, "tileheight",   OPT_UINT,   &cmdlineP->tileheight,
+            &tileheightSpec,       0);
+    OPTENT3(0, "prcwidth",     OPT_UINT,   &cmdlineP->prcwidth,
+            &prcwidthSpec,       0);
+    OPTENT3(0, "prcheight",    OPT_UINT,   &cmdlineP->prcheight,
+            &prcheightSpec,      0);
+    OPTENT3(0, "cblkwidth",    OPT_UINT,   &cmdlineP->cblkwidth,
+            &cblkwidthSpec,      0);
+    OPTENT3(0, "cblkheight",   OPT_UINT,   &cmdlineP->cblkheight,
+            &cblkheightSpec,     0);
+    OPTENT3(0, "mode",         OPT_STRING, &modeOpt,
+            &modeSpec,           0);
+    OPTENT3(0, "compression",  OPT_FLOAT,  &cmdlineP->compressionRatio,
+            &compressionSpec,    0);
+    OPTENT3(0, "ilyrrates",    OPT_STRING, &cmdlineP->ilyrrates,
+            &ilyrratesSpec,      0);
+    OPTENT3(0, "progression",  OPT_STRING, &progressionOpt,
+            &progressionSpec,    0);
+    OPTENT3(0, "numrlvls",     OPT_UINT,   &cmdlineP->numrlvls,
+            &numrlvlsSpec,       0);
+    OPTENT3(0, "numgbits",     OPT_UINT,   &cmdlineP->numgbits,
+            &numgbitsSpec,       0);
+    OPTENT3(0, "nomct",        OPT_FLAG,   NULL, 
+            &cmdlineP->nomct,    0);
+    OPTENT3(0, "sop",          OPT_FLAG,   NULL, 
+            &cmdlineP->sop,      0);
+    OPTENT3(0, "eph",          OPT_FLAG,   NULL, 
+            &cmdlineP->eph,      0);
+    OPTENT3(0, "lazy",         OPT_FLAG,   NULL, 
+            &cmdlineP->lazy,     0);
+    OPTENT3(0, "termall",      OPT_FLAG,   NULL, 
+            &cmdlineP->termall,  0);
+    OPTENT3(0, "segsym",       OPT_FLAG,   NULL, 
+            &cmdlineP->segsym,    0);
+    OPTENT3(0, "vcausal",      OPT_FLAG,   NULL, 
+            &cmdlineP->vcausal,   0);
+    OPTENT3(0, "pterm",        OPT_FLAG,   NULL, 
+            &cmdlineP->pterm,     0);
+    OPTENT3(0, "resetprob",    OPT_FLAG,   NULL, 
+            &cmdlineP->resetprob, 0);
+    OPTENT3(0, "verbose",      OPT_FLAG,   NULL, 
+            &cmdlineP->verbose,   0);
+    OPTENT3(0, "debuglevel",   OPT_UINT,   &cmdlineP->debuglevel,
+            &debuglevelSpec,      0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+    if (!imgareatlxSpec)
+        cmdlineP->imgareatlx = 0;
+    if (!imgareatlySpec)
+        cmdlineP->imgareatly = 0;
+    if (!tilegrdtlxSpec)
+        cmdlineP->tilegrdtlx = 0;
+    if (!tilegrdtlySpec)
+        cmdlineP->tilegrdtly = 0;
+    if (!tilewidthSpec)
+        cmdlineP->tilewidth = 0;
+    if (!tileheightSpec)
+        cmdlineP->tileheight = 0;
+    if (!prcwidthSpec)
+        cmdlineP->prcwidth = 32768;
+    if (!prcheightSpec)
+        cmdlineP->prcheight = 32768;
+    if (!cblkwidthSpec)
+        cmdlineP->cblkwidth = 64;
+    if (!cblkheightSpec)
+        cmdlineP->cblkheight = 64;
+    if (modeSpec) {
+        if (strcmp(modeOpt, "integer") == 0 || strcmp(modeOpt, "int") == 0)
+            cmdlineP->compmode = COMPMODE_INTEGER;
+        else if (strcmp(modeOpt, "real") == 0)
+            cmdlineP->compmode = COMPMODE_REAL;
+        else
+            pm_error("Invalid value for 'mode' option: '%s'.  "
+                     "valid values are 'INTEGER' and 'REAL'", modeOpt);
+    } else
+        cmdlineP->compmode = COMPMODE_INTEGER;
+    if (compressionSpec) {
+        if (cmdlineP->compressionRatio < 1.0)
+            pm_error("Compression ratio less than 1 does not make sense.");
+    } else
+        cmdlineP->compressionRatio = 1.0;
+    if (!ilyrratesSpec)
+        cmdlineP->ilyrrates = (char*) "";
+    if (progressionSpec) {
+        if (strcmp(progressionOpt, "lrcp") == 0)
+            cmdlineP->progression = PROG_LRCP;
+        if (strcmp(progressionOpt, "rlcp") == 0)
+            cmdlineP->progression = PROG_RLCP;
+        if (strcmp(progressionOpt, "rpcl") == 0)
+            cmdlineP->progression = PROG_RPCL;
+        if (strcmp(progressionOpt, "pcrl") == 0)
+            cmdlineP->progression = PROG_PCRL;
+        if (strcmp(progressionOpt, "cprl") == 0)
+            cmdlineP->progression = PROG_CPRL;
+        else
+            pm_error("Invalid value for -progression: '%s'.  "
+                     "Valid values are lrcp, rlcp, rpcl, pcrl, and cprl.",
+                     progressionOpt);
+    } else
+        cmdlineP->progression = PROG_LRCP;
+    if (!numrlvlsSpec)
+        cmdlineP->numrlvls = 6;
+    if (!numgbitsSpec)
+        cmdlineP->numgbits = 2;
+    if (!debuglevelSpec)
+        cmdlineP->debuglevel = 0;
+
+    if (argc - 1 == 0)
+        cmdlineP->inputFilename = strdup("-");  /* he wants stdin */
+    else if (argc - 1 == 1)
+        cmdlineP->inputFilename = strdup(argv[1]);
+    else 
+        pm_error("Too many arguments.  The only argument accepted\n"
+                 "is the input file specification");
+
+}
+
+
+
+static void
+createJasperRaster(struct pam *  const inpamP, 
+                   jas_image_t * const jasperP) {
+
+    jas_matrix_t ** matrix;  /* malloc'ed */
+        /* matrix[X] is the data for Plane X of the current row */
+    unsigned int plane;
+    unsigned int row;
+    tuple * tuplerow;
+    bool oddMaxval;
+    sample jasperMaxval;
+
+    MALLOCARRAY_NOFAIL(matrix, inpamP->depth);
+
+    for (plane = 0; plane < inpamP->depth; ++plane) {
+        matrix[plane] = jas_matrix_create(1, inpamP->width);
+
+        if (matrix[plane] == NULL)
+            pm_error("Unable to create matrix for plane %u.  "
+                     "jas_matrix_create() failed.", plane);
+    }   
+    tuplerow = pnm_allocpamrow(inpamP);
+
+    jasperMaxval = pm_bitstomaxval(pm_maxvaltobits(inpamP->maxval));
+    oddMaxval = jasperMaxval != inpamP->maxval;
+
+    for (row = 0; row < inpamP->height; ++row) {
+        unsigned int col;
+
+        pnm_readpamrow(inpamP, tuplerow);
+
+        for (col = 0; col < inpamP->width; ++col) {
+            unsigned int plane;
+            for (plane = 0; plane < inpamP->depth; ++plane) {
+                unsigned int jasperSample;
+
+                if (oddMaxval)
+                    jasperSample = tuplerow[col][plane] * 
+                        jasperMaxval / inpamP->maxval;
+                else
+                    jasperSample = tuplerow[col][plane];
+
+                jas_matrix_set(matrix[plane], 0, col, jasperSample);
+            }
+        }
+        { 
+            unsigned int plane;
+
+            for (plane = 0; plane < inpamP->depth; ++plane) {
+                int rc;
+                rc = jas_image_writecmpt(jasperP, plane, 0, row, 
+                                         inpamP->width, 1,
+                                         matrix[plane]);
+                if (rc != 0)
+                    pm_error("jas_image_writecmpt() of plane %u failed.", 
+                             plane);
+            }
+        }
+    }
+
+    pnm_freepamrow(tuplerow);
+    for (plane = 0; plane < inpamP->depth; ++plane)
+        jas_matrix_destroy(matrix[plane]);
+    
+    free(matrix);
+}
+
+
+
+static void
+createJasperImage(struct pam *   const inpamP, 
+                  jas_image_t ** const jasperPP) {
+
+	jas_image_cmptparm_t * cmptparms;
+    unsigned int plane;
+
+    MALLOCARRAY_NOFAIL(cmptparms, inpamP->depth);
+
+    for (plane = 0; plane < inpamP->depth; ++plane) {
+        cmptparms[plane].tlx = 0;
+        cmptparms[plane].tly = 0;
+        cmptparms[plane].hstep = 1;
+        cmptparms[plane].vstep = 1;
+        cmptparms[plane].width = inpamP->width;
+        cmptparms[plane].height = inpamP->height;
+        cmptparms[plane].prec = pm_maxvaltobits(inpamP->maxval);
+        cmptparms[plane].sgnd = 0;
+    }
+    *jasperPP = 
+        jas_image_create(inpamP->depth, cmptparms, JAS_CLRSPC_UNKNOWN);
+    if (*jasperPP == NULL)
+        pm_error("Unable to create jasper image structure.  "
+                 "jas_image_create() failed.");
+
+    free(cmptparms);
+}
+
+
+
+static void
+convertToJasperImage(struct pam *   const inpamP,
+                     jas_image_t ** const jasperPP) {
+
+    jas_image_t * jasperP;
+
+    createJasperImage(inpamP, &jasperP);
+
+    if (strncmp(inpamP->tuple_type, "RGB", 3) == 0) {
+        if (inpamP->depth < 3)
+            pm_error("Input tuple type is RGB*, but depth is only %d.  "
+                     "It should be at least 3.", inpamP->depth);
+        else {
+            jas_image_setclrspc(jasperP, JAS_CLRSPC_GENRGB);
+            jas_image_setcmpttype(jasperP, 0,
+                                  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_R));
+            jas_image_setcmpttype(jasperP, 1,
+                                  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_G));
+            jas_image_setcmpttype(jasperP, 2,
+                                  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_B));
+        }
+    } else {
+        if (strncmp(inpamP->tuple_type, "GRAYSCALE", 9 == 0) ||
+            strncmp(inpamP->tuple_type, "BLACKANDWHITE", 13) == 0) {
+            jas_image_setclrspc(jasperP, JAS_CLRSPC_GENGRAY);
+            jas_image_setcmpttype(jasperP, 0,
+                                  JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_GRAY_Y));
+        }
+    }
+
+    createJasperRaster(inpamP, jasperP);
+
+    *jasperPP = jasperP;
+}
+
+
+
+static void
+writeJpc(jas_image_t *      const jasperP, 
+         struct cmdlineInfo const cmdline,
+         FILE *             const ofP) {
+
+    jas_stream_t * outStreamP;
+    const char * options;
+    const char * ilyrratesOpt;
+    const char * prgValue;
+    char rateOpt[20+1];
+
+    /* Note: ilyrrates is a hack because we're too lazy to properly parse
+       command line options to get the information and then compose
+       a proper input to Jasper.  So the user can screw things up by 
+       specifying garbage for the -ilyrrates option 
+    */
+    if (strlen(cmdline.ilyrrates) > 0)
+        asprintfN(&ilyrratesOpt, "ilyrrates=%s", cmdline.ilyrrates);
+    else
+        ilyrratesOpt = strdup("");
+
+    switch(cmdline.progression) {
+    case PROG_LRCP: prgValue = "lrcp"; break;
+    case PROG_RLCP: prgValue = "rlcp"; break;
+    case PROG_RPCL: prgValue = "rcpc"; break;
+    case PROG_PCRL: prgValue = "pcrl"; break;
+    case PROG_CPRL: prgValue = "cprl"; break;
+    }
+
+    /* Note that asprintfN() doesn't understand %f, but sprintf() does */
+
+    sprintf(rateOpt, "%1.9f", 1.0/cmdline.compressionRatio);
+
+    asprintfN(&options, 
+              "imgareatlx=%u "
+              "imgareatly=%u "
+              "tilegrdtlx=%u "
+              "tilegrdtly=%u "
+              "tilewidth=%u "
+              "tileheight=%u "
+              "prcwidth=%u "
+              "prcheight=%u "
+              "cblkwidth=%u "
+              "cblkheight=%u "
+              "mode=%s "
+              "rate=%s "
+              "%s "
+              "prg=%s "
+              "numrlvls=%u "
+              "numgbits=%u "
+              "%s %s %s %s %s %s %s %s %s",
+
+              cmdline.imgareatlx,
+              cmdline.imgareatly,
+              cmdline.tilegrdtlx,
+              cmdline.tilegrdtlx,
+              cmdline.tilewidth,
+              cmdline.tileheight,
+              cmdline.prcwidth,
+              cmdline.prcheight,
+              cmdline.cblkwidth,
+              cmdline.cblkheight,
+              cmdline.compmode == COMPMODE_INTEGER ? "int" : "real",
+              rateOpt,
+              ilyrratesOpt,
+              prgValue,
+              cmdline.numrlvls,
+              cmdline.numgbits,
+              cmdline.nomct     ? "nomct"     : "",
+              cmdline.sop       ? "sop"       : "",
+              cmdline.eph       ? "eph"       : "",
+              cmdline.lazy      ? "lazy"      : "",
+              cmdline.termall   ? "termall"   : "",
+              cmdline.segsym    ? "segsym"    : "",
+              cmdline.vcausal   ? "vcausal"   : "",
+              cmdline.pterm     ? "pterm"     : "",
+              cmdline.resetprob ? "resetprob" : ""
+        );
+    strfree(ilyrratesOpt);
+
+
+    /* Open the output image file (Standard Output) */
+    outStreamP = jas_stream_fdopen(fileno(ofP), "w+b");
+    if (outStreamP == NULL)
+        pm_error("Unable to open output stream.  jas_stream_fdopen() "
+                 "failed");
+
+    {
+        int rc;
+
+        if (cmdline.verbose)
+            pm_message("Using Jasper to encode to 'jpc' format with options "
+                       "'%s'", options);
+
+        rc = jas_image_encode(jasperP, outStreamP, 
+                              jas_image_strtofmt((char*)"jpc"), 
+                              (char*)options);
+        if (rc != 0)
+            pm_error("jas_image_encode() failed to encode the JPEG 2000 "
+                     "image.  Rc=%d", rc);
+    }
+	jas_stream_flush(outStreamP);
+
+    {
+        int rc;
+
+        rc = jas_stream_close(outStreamP);
+            
+        if (rc != 0)
+            pm_error("Failed to close output stream, "
+                     "jas_stream_close() rc = %d", rc);
+    }                     
+
+	jas_image_clearfmts();
+
+    strfree(options);
+}
+
+
+
+int
+main(int argc, char **argv)
+{
+    struct cmdlineInfo cmdline;
+    FILE *ifP;
+    struct pam inpam;
+    jas_image_t * jasperP;
+
+    pnm_init(&argc, argv);
+    
+    parseCommandLine(argc, argv, &cmdline);
+    
+    { 
+        int rc;
+        
+        rc = jas_init();
+        if ( rc != 0 )
+            pm_error("Failed to initialize Jasper library.  "
+                     "jas_init() returns rc %d", rc );
+    }
+    
+    jas_setdbglevel(cmdline.debuglevel);
+    
+    ifP = pm_openr(cmdline.inputFilename);
+    
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+    
+    convertToJasperImage(&inpam, &jasperP);
+    
+    writeJpc(jasperP, cmdline, stdout);
+    
+	jas_image_destroy(jasperP);
+
+    pm_close(ifP);
+
+    pm_close(stdout);
+    
+    return 0;
+}
diff --git a/converter/other/jpegdatasource.c b/converter/other/jpegdatasource.c
new file mode 100644
index 00000000..5c1070e4
--- /dev/null
+++ b/converter/other/jpegdatasource.c
@@ -0,0 +1,183 @@
+/*****************************************************************************
+                           jpegdatasource.c
+******************************************************************************
+   This is the data source manager that Jpegtopnm supplies to the JPEG
+   library.  The Jpeg library uses this object to get JPEG input.
+
+   This data source manager is the same as the Jpeg library's built in
+   "stdio" one, except that it looks ahead and one can query it to see
+   if there is any data in the stream that the Jpeg library hasn't seen
+   yet.  Thus, you can use it in a program that reads multiple JPEG 
+   images and know when to stop.  You can also nicely handle completely
+   empty input files more gracefully than just crying input error.
+
+   This data source manager does 4K fread() reads and passes 4K buffers 
+   to the Jpeg library.  It reads one 4K block ahead, so there is up to
+   8K of image buffered at any time.
+
+   By Bryan Henderson, San Jose CA 2002.10.13
+*****************************************************************************/
+
+#include <ctype.h>	   
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+/* Note: jpeglib.h prerequires stdlib.h and ctype.h.  It should include them
+   itself, but doesn't.
+*/
+#include <jpeglib.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "pnm.h"
+
+#include "jpegdatasource.h"
+
+#define BUFFER_SIZE 4096
+
+struct sourceManager {
+    /* The following structure _must_ be the first thing in this structure,
+       because the methods below assume the address of the struct
+       jpeg_source_mgr is the same as the address of the struct sourceManager.
+       The former address is the only information the Jpeg library gives
+       these methods to tell them on what source manager object they are
+       supposed to operate.
+    */
+    struct jpeg_source_mgr jpegSourceMgr;
+    FILE * ifP;
+    JOCTET * currentBuffer;
+    JOCTET * nextBuffer;
+    unsigned int bytesInNextBuffer;
+    JOCTET buffer1[BUFFER_SIZE];
+    JOCTET buffer2[BUFFER_SIZE];
+};
+
+
+static void
+dsInitSource(j_decompress_ptr const cinfoP) {
+/*----------------------------------------------------------------------------
+   This is the init_source method for the data source manager the Jpeg
+   library uses.
+-----------------------------------------------------------------------------*/
+    /* The object was created ready to go and remains ready from one image
+       to the next.  No per-image initialization is necessary.
+    */
+}
+
+
+
+static boolean
+dsFillInputBuffer(j_decompress_ptr const cinfoP) {
+/*----------------------------------------------------------------------------
+   This is the fill_input_buffer method for the data source manager the Jpeg
+   library uses.
+-----------------------------------------------------------------------------*/
+    struct sourceManager * const srcP = (struct sourceManager *) cinfoP->src;
+
+    if (srcP->bytesInNextBuffer == 0) 
+        pm_error("End-of-file encountered in the middle of JPEG image.");
+    else {
+        /* Rotate the buffers */
+        srcP->jpegSourceMgr.next_input_byte = srcP->nextBuffer;
+        srcP->jpegSourceMgr.bytes_in_buffer = srcP->bytesInNextBuffer;
+        {
+            JOCTET * tmp;
+            tmp = srcP->nextBuffer;
+            srcP->nextBuffer = srcP->currentBuffer;
+            srcP->currentBuffer = tmp;
+        }
+
+        /* Fill the new 'next' buffer */
+        srcP->bytesInNextBuffer = 
+            fread(srcP->nextBuffer, 1, BUFFER_SIZE, srcP->ifP);
+    }
+    return TRUE;
+}
+
+
+
+static void
+dsSkipInputData(j_decompress_ptr const cinfoP, long const num_bytes) {
+/*----------------------------------------------------------------------------
+   This is the skip_input_data method for the data source manager the Jpeg
+   library uses.
+-----------------------------------------------------------------------------*/
+    struct jpeg_source_mgr * const jpegSourceMgrP = cinfoP->src;
+
+    long i;
+
+    for (i = 0; i < num_bytes; ++i) {
+        if (jpegSourceMgrP->bytes_in_buffer == 0)
+            dsFillInputBuffer(cinfoP);
+        ++jpegSourceMgrP->next_input_byte;
+        --jpegSourceMgrP->bytes_in_buffer;
+    }
+}
+
+
+
+static void
+dsTermSource(j_decompress_ptr const cinfoP) {
+/*----------------------------------------------------------------------------
+   This is the term_source method for the data source manager the Jpeg
+   library uses.
+-----------------------------------------------------------------------------*/
+    /* We couldn't care less that the Jpeg library is done reading an
+       image.  The source manager object remains active and ready for the
+       Jpeg library to read the next image.
+    */
+}
+
+
+
+bool
+dsDataLeft(struct sourceManager * const srcP) {
+
+    return((srcP->jpegSourceMgr.bytes_in_buffer > 0 ||
+            srcP->bytesInNextBuffer > 0));
+}
+
+
+
+struct sourceManager * 
+dsCreateSource(const char * const fileName) {
+
+    struct sourceManager * srcP;
+
+    MALLOCVAR(srcP);
+    if (srcP == NULL)
+        pm_error("Unable to get memory for the Jpeg library source manager.");
+
+    srcP->ifP = pm_openr(fileName);
+
+    srcP->jpegSourceMgr.init_source = dsInitSource;
+    srcP->jpegSourceMgr.fill_input_buffer = dsFillInputBuffer;
+    srcP->jpegSourceMgr.skip_input_data = dsSkipInputData;
+    srcP->jpegSourceMgr.resync_to_restart = jpeg_resync_to_restart;
+    srcP->jpegSourceMgr.term_source = dsTermSource;
+    
+    srcP->currentBuffer = srcP->buffer1;
+    srcP->nextBuffer = srcP->buffer2;
+    srcP->jpegSourceMgr.bytes_in_buffer = 
+        fread(srcP->currentBuffer, 1, BUFFER_SIZE, srcP->ifP);
+    srcP->jpegSourceMgr.next_input_byte = srcP->currentBuffer;
+    srcP->bytesInNextBuffer = 
+        fread(srcP->nextBuffer, 1, BUFFER_SIZE, srcP->ifP);
+
+    return srcP;
+}
+
+void
+dsDestroySource(struct sourceManager * const srcP) {
+    
+    pm_close(srcP->ifP);
+    free(srcP);
+
+}
+
+
+
+struct jpeg_source_mgr *
+dsJpegSourceMgr(struct sourceManager * const srcP) {
+    return &srcP->jpegSourceMgr;
+}
diff --git a/converter/other/jpegdatasource.h b/converter/other/jpegdatasource.h
new file mode 100644
index 00000000..07f17389
--- /dev/null
+++ b/converter/other/jpegdatasource.h
@@ -0,0 +1,18 @@
+#ifndef JPEGDATASOURCE_H_INCLUDED
+#define JPEGDATASOURCE_H_INCLUDED
+
+#include "pm_c_util.h"
+
+struct sourceManager * 
+dsCreateSource(const char * const fileName);
+
+void
+dsDestroySource(struct sourceManager * const srcP);
+
+bool
+dsDataLeft(struct sourceManager * const srcP);
+
+struct jpeg_source_mgr *
+dsJpegSourceMgr(struct sourceManager * const srcP);
+
+#endif
diff --git a/converter/other/jpegtopnm.c b/converter/other/jpegtopnm.c
new file mode 100644
index 00000000..60ae7e42
--- /dev/null
+++ b/converter/other/jpegtopnm.c
@@ -0,0 +1,970 @@
+/*****************************************************************************
+                                jpegtopnm
+******************************************************************************
+  This program is part of the Netpbm package.
+
+  This program converts from the JFIF format, which is based on JPEG, to
+  the fundamental ppm or pgm format (depending on whether the JFIF 
+  image is gray scale or color).
+
+  This program is by Bryan Henderson on 2000.03.20, but is derived
+  with permission from the program djpeg, which is in the Independent
+  Jpeg Group's JPEG library package.  Under the terms of that permission,
+  redistribution of this software is restricted as described in the 
+  file README.JPEG.
+
+  Copyright (C) 1991-1998, Thomas G. Lane.
+
+  TODO:
+
+    For CMYK and YCCK JPEG input, optionally produce a 4-deep PAM
+    output containing CMYK values.  Define a tupletype for this.
+    Extend pamtoppm to convert this to ppm using the standard
+    transformation.
+
+    See if additional decompressor options effects signficant speedup.
+    grayscale output of color image, downscaling, color quantization, and
+    dithering are possibilities.  Djpeg's man page says they make it faster.
+
+  IMPLEMENTATION NOTE - PRECISION
+
+    There are two versions of the JPEG library.  One handles only JPEG
+    files with 8 bit samples; the other handles only 12 bit files.
+    This program may be compiled and linked against either, and run
+    dynamically linked to either at runtime independently.  It uses
+    the precision information from the file header.  Note that when
+    the input has 12 bit precision, this program generates PPM files
+    with two-byte samples, but when the input has 8 bit precision, it
+    generates PPM files with one-byte samples.  One should use
+    Pnmdepth to reduce precision to 8 bits if one-byte-sample output
+    is essential.
+
+  IMPLEMENTATION NOTE - EXIF
+
+    See http://exif.org.  See the programs Exifdump
+    (http://topo.math.u-psud.fr/~bousch/exifdump.py) and Jhead
+    (http://www.sentex.net/~mwandel/jhead).
+
+    
+*****************************************************************************/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <ctype.h>		/* to declare isprint() */
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+/* Note: jpeglib.h prerequires stdlib.h and ctype.h.  It should include them
+   itself, but doesn't.
+*/
+#include <jpeglib.h>
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "exif.h"
+#include "jpegdatasource.h"
+
+#define EXIT_WARNING 2  /* Goes with EXIT_FAILURE, EXIT_SUCCESS in stdlib.h */
+
+enum inklevel {NORMAL, ADOBE, GUESS};
+   /* This describes image samples that represent ink levels.  NORMAL
+      means 0 is no ink; ADOBE means 0 is maximum ink.  GUESS means we
+      don't know what 0 means, so we have to guess from information in 
+      the image.
+      */
+
+enum colorspace {
+    /* These are the color spaces in which we can get pixels from the
+       JPEG decompressor.  We include only those that are possible
+       given our particular inputs to the decompressor.  The
+       decompressor is theoretically capable of other, e.g. YCCK.
+       Unlike the JPEG library, this type distinguishes between the
+       Adobe and non-Adobe style of CMYK samples.  
+    */
+    GRAYSCALE_COLORSPACE,
+    RGB_COLORSPACE, 
+    CMYK_NORMAL_COLORSPACE, 
+    CMYK_ADOBE_COLORSPACE
+    };
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    char *input_filespec;
+    char *exif_filespec;
+        /* Filespec in which to save EXIF information.  NULL means don't
+           save.  "-" means standard output
+        */
+    unsigned int verbose;
+    unsigned int nosmooth;
+    J_DCT_METHOD dct_method;
+    long int max_memory_to_use;
+    unsigned int trace_level;
+    enum inklevel inklevel;
+    unsigned int comments;
+    unsigned int dumpexif;
+    unsigned int multiple;
+};
+
+
+static bool displayComments;
+    /* User wants comments from the JPEG to be displayed */
+
+static void 
+interpret_maxmemory (bool         const maxmemorySpec,
+                     const char * const maxmemory, 
+                     long int *   const max_memory_to_use_p) { 
+/*----------------------------------------------------------------------------
+   Interpret the "maxmemory" command line option.
+-----------------------------------------------------------------------------*/
+    long int lval;
+    char ch;
+    
+    if (!maxmemorySpec) {
+        *max_memory_to_use_p = -1;  /* unspecified */
+    } else if (sscanf(maxmemory, "%ld%c", &lval, &ch) < 1) {
+        pm_error("Invalid value for --maxmemory option: '%s'.", maxmemory);
+    } else {
+        if (ch == 'm' || ch == 'M') lval *= 1000L;
+        *max_memory_to_use_p = lval * 1000L;
+    }
+}
+
+
+static void
+interpret_adobe(const int adobe, const int notadobe, 
+                enum inklevel * const inklevel_p) {
+/*----------------------------------------------------------------------------
+   Interpret the adobe/notadobe command line options
+-----------------------------------------------------------------------------*/
+    if (adobe && notadobe)
+        pm_error("You cannot specify both -adobe and -notadobe options.");
+    else {
+        if (adobe)
+            *inklevel_p = ADOBE;
+        else if (notadobe)
+            *inklevel_p = NORMAL;
+        else 
+            *inklevel_p = GUESS;
+    }
+}
+
+
+
+static void
+parse_command_line(const int argc, char ** argv,
+                   struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that many of the strings that this function returns in the
+   *cmdlineP structure are actually in the supplied argv array.  And
+   sometimes, one of these strings is actually just a suffix of an entry
+   in argv!
+
+   On the other hand, unlike other option processing functions, we do
+   not change argv at all.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    int i;  /* local loop variable */
+
+    char *maxmemory;
+    char *dctval;
+    unsigned int adobe, notadobe;
+
+    unsigned int tracelevelSpec, exifSpec, dctvalSpec, maxmemorySpec;
+    unsigned int option_def_index;
+
+    int argc_parse;       /* argc, except we modify it as we parse */
+    char ** argv_parse;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+    MALLOCARRAY_NOFAIL(argv_parse, argc);
+    
+    /* argv, except we modify it as we parse */
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "verbose",     OPT_FLAG,   NULL, &cmdlineP->verbose,       0);
+    OPTENT3(0, "dct",         OPT_STRING, &dctval,
+            &dctvalSpec, 0);
+    OPTENT3(0, "maxmemory",   OPT_STRING, &maxmemory,
+            &maxmemorySpec, 0); 
+    OPTENT3(0, "nosmooth",    OPT_FLAG,   NULL, &cmdlineP->nosmooth,      0);
+    OPTENT3(0, "tracelevel",  OPT_UINT,   &cmdlineP->trace_level,   
+            &tracelevelSpec, 0);
+    OPTENT3(0, "adobe",       OPT_FLAG,   NULL, &adobe,                   0);
+    OPTENT3(0, "notadobe",    OPT_FLAG,   NULL, &notadobe,                0);
+    OPTENT3(0, "comments",    OPT_FLAG,   NULL, &cmdlineP->comments,      0);
+    OPTENT3(0, "exif",        OPT_STRING, &cmdlineP->exif_filespec, 
+            &exifSpec, 0);
+    OPTENT3(0, "dumpexif",    OPT_FLAG,   NULL, &cmdlineP->dumpexif,      0);
+    OPTENT3(0, "multiple",    OPT_FLAG,   NULL, &cmdlineP->multiple,      0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    /* Make private copy of arguments for optParseOptions to corrupt */
+    argc_parse = argc;
+    for (i=0; i < argc; i++) argv_parse[i] = argv[i];
+
+    optParseOptions3( &argc_parse, argv_parse, opt, sizeof(opt), 0);
+        /* Uses and sets argc_parse, argv_parse, 
+           and some of *cmdlineP and others. */
+
+    if (!tracelevelSpec)
+        cmdlineP->trace_level = 0;
+
+    if (!exifSpec)
+        cmdlineP->exif_filespec = NULL;
+
+    if (argc_parse - 1 == 0)
+        cmdlineP->input_filespec = strdup("-");  /* he wants stdin */
+    else if (argc_parse - 1 == 1)
+        cmdlineP->input_filespec = strdup(argv_parse[1]);
+    else 
+        pm_error("Too many arguments.  The only argument accepted "
+                 "is the input file specification");
+
+    if (!dctvalSpec)
+        cmdlineP->dct_method = JDCT_DEFAULT;
+    else {
+        if (STREQ(dctval, "int"))
+            cmdlineP->dct_method = JDCT_ISLOW;
+        else if (STREQ(dctval, "fast"))
+            cmdlineP->dct_method = JDCT_IFAST;
+        else if (STREQ(dctval, "float"))
+            cmdlineP->dct_method = JDCT_FLOAT;
+        else pm_error("Invalid value for the --dct option: '%s'.", dctval);
+    }
+
+    interpret_maxmemory(maxmemorySpec, maxmemory, 
+                        &cmdlineP->max_memory_to_use);
+
+    interpret_adobe(adobe, notadobe, &cmdlineP->inklevel);
+
+    free(argv_parse);
+}
+
+
+/*
+ * Marker processor for COM and interesting APPn markers.
+ * This replaces the library's built-in processor, which just skips the marker.
+ * We want to print out the marker as text, to the extent possible.
+ * Note this code relies on a non-suspending data source.
+ */
+
+#if 0
+static unsigned int
+jpeg_getc (j_decompress_ptr cinfo)
+/* Read next byte */
+{
+  struct jpeg_source_mgr * datasrc = cinfo->src;
+
+  if (datasrc->bytes_in_buffer == 0) {
+      if (! (*datasrc->fill_input_buffer) (cinfo)) 
+          pm_error("Can't suspend here.");
+  }
+  datasrc->bytes_in_buffer--;
+  return GETJOCTET(*datasrc->next_input_byte++);
+}
+
+
+static boolean
+print_text_marker (j_decompress_ptr cinfo) {
+/*----------------------------------------------------------------------------
+   This is a routine that you can register with the Jpeg decompressor
+   with e.g.
+
+     jpeg_set_marker_processor(cinfoP, JPEG_APP0 + app_type, 
+                               print_text_marker);
+
+  The decompressor then calls it when it encounters a miscellaneous marker
+  of the specified type (e.g. APP1).  At that time, the jpeg input stream
+  is positioned to the marker contents -- first 2 bytes of length information,
+  MSB first, where the length includes those two bytes, then the data.
+  
+  We just get and print the contents of the marker.
+
+  This routine is no longer used; it is kept as an example in case we want
+  to use it in the future.  Instead, we use jpeg_save_markers() and have
+  the Jpeg library store all the markers in memory for our later access.
+-----------------------------------------------------------------------------*/
+    const boolean traceit = (cinfo->err->trace_level >= 1);
+    const boolean display_value = 
+        traceit || (cinfo->unread_marker == JPEG_COM && displayComments);
+    
+    INT32 length;
+    unsigned int ch;
+    unsigned int lastch = 0;
+    
+    length = jpeg_getc(cinfo) << 8;
+    length += jpeg_getc(cinfo);
+    length -= 2;			/* discount the length word itself */
+
+    if (traceit) {
+        if (cinfo->unread_marker == JPEG_COM)
+            fprintf(stderr, "Comment, length %ld:\n", (long) length);
+        else			/* assume it is an APPn otherwise */
+            fprintf(stderr, "APP%d, length %ld:\n",
+                    cinfo->unread_marker - JPEG_APP0, (long) length);
+    }
+    
+    if (cinfo->unread_marker == JPEG_COM && displayComments)
+        fprintf(stderr, "COMMENT: ");
+    
+    while (--length >= 0) {
+        ch = jpeg_getc(cinfo);
+        if (display_value) {
+            /* Emit the character in a readable form.
+             * Nonprintables are converted to \nnn form,
+             * while \ is converted to \\.
+             * Newlines in CR, CR/LF, or LF form will be printed as one 
+             * newline.
+             */
+            if (ch == '\r') {
+              fprintf(stderr, "\n");
+            } else if (ch == '\n') {
+                if (lastch != '\r')
+                    fprintf(stderr, "\n");
+            } else if (ch == '\\') {
+                fprintf(stderr, "\\\\");
+            } else if (isprint(ch)) {
+                putc(ch, stderr);
+            } else {
+                fprintf(stderr, "\\%03o", ch);
+            }
+          lastch = ch;
+        }
+    }
+    
+    if (display_value)
+        fprintf(stderr, "\n");
+    
+    return TRUE;
+}
+#endif
+
+
+
+static void
+print_marker(struct jpeg_marker_struct const marker) {
+
+    if (marker.original_length != marker.data_length) {
+        /* This should be impossible, because we asked for up to 65535
+           bytes, and the jpeg spec doesn't allow anything bigger than that.
+        */
+        pm_message("INTERNAL ERROR: %d of %d bytes of marker were "
+                   "saved.", marker.data_length, marker.original_length);
+    }
+
+    {
+        int i;
+        JOCTET lastch;
+
+        lastch = 0;
+        for (i = 0; i < marker.data_length; i++) {
+            /* Emit the character in a readable form.
+             * Nonprintables are converted to \nnn form,
+             * while \ is converted to \\.
+             * Newlines in CR, CR/LF, or LF form will be printed as one 
+             * newline.
+             */
+            if (marker.data[i] == '\r') {
+                fprintf(stderr, "\n");
+            } else if (marker.data[i] == '\n') {
+                if (lastch != '\r')
+                    fprintf(stderr, "\n");
+            } else if (marker.data[i] == '\\') {
+                fprintf(stderr, "\\\\");
+            } else if (isprint(marker.data[i])) {
+                putc(marker.data[i], stderr);
+            } else {
+                fprintf(stderr, "\\%03o", marker.data[i]);
+            }
+            lastch = marker.data[i];
+        }
+        fprintf(stderr, "\n");
+    }
+}
+
+
+typedef struct rgb {unsigned int r; unsigned int g; unsigned int b;} rgb_type;
+
+
+static rgb_type *
+read_rgb(JSAMPLE *ptr, const enum colorspace color_space, 
+         const unsigned int maxval) {
+/*----------------------------------------------------------------------------
+  Return the RGB triple corresponding to the color of the JPEG pixel at
+  'ptr', which is in color space 'color_space'.  
+
+  Assume 'maxval' is the maximum sample value in the input pixel, and also
+  use it for the maximum sample value in the return value.
+-----------------------------------------------------------------------------*/
+    static rgb_type rgb;  /* Our return value */
+
+    switch (color_space) {
+    case RGB_COLORSPACE: {
+        rgb.r = GETJSAMPLE(*(ptr+0));
+        rgb.g = GETJSAMPLE(*(ptr+1)); 
+        rgb.b = GETJSAMPLE(*(ptr+2)); 
+    }
+        break;
+    case CMYK_NORMAL_COLORSPACE: {
+        const int c = GETJSAMPLE(*(ptr+0));
+        const int m = GETJSAMPLE(*(ptr+1));
+        const int y = GETJSAMPLE(*(ptr+2));
+        const int k = GETJSAMPLE(*(ptr+3));
+
+        /* I swapped m and y below, because they looked wrong.
+           -Bryan 2000.08.20
+           */
+        rgb.r = ((maxval-k)*(maxval-c))/maxval;
+        rgb.g = ((maxval-k)*(maxval-m))/maxval;
+        rgb.b = ((maxval-k)*(maxval-y))/maxval;
+    }
+        break;
+    case CMYK_ADOBE_COLORSPACE: {
+        const int c = GETJSAMPLE(*(ptr+0));
+        const int m = GETJSAMPLE(*(ptr+1));
+        const int y = GETJSAMPLE(*(ptr+2));
+        const int k = GETJSAMPLE(*(ptr+3));
+
+        rgb.r = (k*c)/maxval;
+        rgb.g = (k*m)/maxval;
+        rgb.b = (k*y)/maxval;
+    }
+        break;
+    default:
+        pm_error("Internal error: unknown color space %d passed to "
+                 "read_rgb().", (int) color_space);
+    }
+    return(&rgb);
+}
+
+
+
+/* pnmbuffer is declared global because it would be improper to pass a
+   pointer to it as input to copy_pixel_row(), since it isn't
+   logically a parameter of the operation, but rather is private to
+   copy_pixel_row().  But it would be impractical to allocate and free
+   the storage with every call to copy_pixel_row().
+*/
+static xel *pnmbuffer;      /* Output buffer.  Input to pnm_writepnmrow() */
+
+static void
+copy_pixel_row(const JSAMPROW jpegbuffer, const int width, 
+               const unsigned int samples_per_pixel, 
+               const enum colorspace color_space,
+               const unsigned int maxval,
+               FILE * const output_file, const int output_type) {
+  JSAMPLE *ptr;
+  unsigned int output_cursor;     /* Cursor into output buffer 'pnmbuffer' */
+
+  ptr = jpegbuffer;  /* Start at beginning of input row */
+
+  for (output_cursor = 0; output_cursor < width; output_cursor++) {
+      xel current_pixel;
+      if (samples_per_pixel >= 3) {
+          const rgb_type * const rgb_p = read_rgb(ptr, color_space, maxval);
+          PPM_ASSIGN(current_pixel, rgb_p->r, rgb_p->g, rgb_p->b);
+      } else {
+          PNM_ASSIGN1(current_pixel, GETJSAMPLE(*ptr));
+      }
+      ptr += samples_per_pixel;  /* move to next pixel of input */
+      pnmbuffer[output_cursor] = current_pixel;
+  }
+  pnm_writepnmrow(output_file, pnmbuffer, width,
+                  maxval, output_type, FALSE);
+}
+
+
+
+static void
+set_color_spaces(const J_COLOR_SPACE jpeg_color_space,
+                 int * const output_type_p,
+                 J_COLOR_SPACE * const out_color_space_p) {
+/*----------------------------------------------------------------------------
+   Decide what type of output (PPM or PGM) we shall generate and what 
+   color space we must request from the JPEG decompressor, based on the
+   color space of the input JPEG image, 'jpeg_color_space'.
+
+   Write to stderr a message telling which type we picked.
+
+   Exit the process with EXIT_FAILURE completion code and a message to
+   stderr if the input color space is beyond our capability.
+-----------------------------------------------------------------------------*/
+    /* Note that the JPEG decompressor is not capable of translating
+       CMYK or YCCK to RGB, but can translate YCCK to CMYK.
+    */
+
+    switch (jpeg_color_space) {
+    case JCS_UNKNOWN:
+        pm_error("Input JPEG image has 'unknown' color space "
+                 "(JCS_UNKNOWN).\n"
+                 "We cannot interpret this image.");
+        break;
+    case JCS_GRAYSCALE:
+        *output_type_p = PGM_TYPE;
+        *out_color_space_p = JCS_GRAYSCALE;
+        break;
+    case JCS_RGB:
+        *output_type_p = PPM_TYPE;
+        *out_color_space_p = JCS_RGB;
+        break;
+    case JCS_YCbCr:
+        *output_type_p = PPM_TYPE;
+        *out_color_space_p = JCS_RGB;
+        /* Design note:  We found this YCbCr->RGB conversion increases
+           user mode CPU time by 2.5%.  2002.10.12
+        */
+        break;
+    case JCS_CMYK:
+        *output_type_p = PPM_TYPE;
+        *out_color_space_p = JCS_CMYK;
+        break;
+    case JCS_YCCK:
+        *output_type_p = PPM_TYPE;
+        *out_color_space_p = JCS_CMYK;
+        break;
+    default:
+        pm_error("Internal error: unknown color space code %d passed "
+                 "to set_color_spaces().", jpeg_color_space);
+    }
+    pm_message("WRITING %s FILE", 
+               *output_type_p == PPM_TYPE ? "PPM" : "PGM");
+}
+
+
+
+static const char *
+colorspace_name(const J_COLOR_SPACE jpeg_color_space) {
+
+    const char *retval;
+
+    switch(jpeg_color_space) {
+    case JCS_UNKNOWN: retval = "JCS_UNKNOWN"; break;
+    case JCS_GRAYSCALE: retval= "JCS_GRAYSCALE"; break;
+    case JCS_RGB: retval = "JCS_RGB"; break;
+    case JCS_YCbCr: retval = "JCS_YCbCr"; break;
+    case JCS_CMYK: retval = "JCS_CMYK"; break;
+    case JCS_YCCK: retval = "JCS_YCCK"; break;
+    default: retval = "invalid"; break;
+    };
+    return(retval);
+}
+
+
+
+static void
+print_verbose_info_about_header(struct jpeg_decompress_struct const cinfo){
+
+    struct jpeg_marker_struct * markerP;
+
+    pm_message("input color space is %d (%s)\n", 
+               cinfo.jpeg_color_space, 
+               colorspace_name(cinfo.jpeg_color_space));
+
+    /* Note that raw information about marker, including marker type code,
+       was already printed by the jpeg library, due to the jpeg library
+       trace level >= 1.  Our job is to interpret it a little bit.
+    */
+    if (cinfo.marker_list)
+        pm_message("Miscellaneous markers (excluding APP0, APP12) "
+                   "in header:");
+    else
+        pm_message("No miscellaneous markers (excluding APP0, APP12) "
+                   "in header");
+    for (markerP = cinfo.marker_list; markerP; markerP = markerP->next) {
+        if (markerP->marker == JPEG_COM)
+            pm_message("Comment marker (COM):");
+        else if (markerP->marker >= JPEG_APP0 && 
+                 markerP->marker <= JPEG_APP0+15)
+            pm_message("Miscellaneous marker type APP%d:", 
+                       markerP->marker - JPEG_APP0);
+        else
+            pm_message("Miscellaneous marker of unknown type (0x%X):",
+                       markerP->marker);
+        
+        print_marker(*markerP);
+    }
+}
+
+
+
+static void
+beginJpegInput(struct jpeg_decompress_struct * const cinfoP,
+               const boolean verbose, 
+               const J_DCT_METHOD dct_method, 
+               const int max_memory_to_use, 
+               const boolean nosmooth) {
+/*----------------------------------------------------------------------------
+   Read the JPEG header, create decompressor object (and
+   allocate memory for it), set up decompressor.
+-----------------------------------------------------------------------------*/
+    /* Read file header, set default decompression parameters */
+    jpeg_read_header(cinfoP, TRUE);
+
+    cinfoP->dct_method = dct_method;
+    if (max_memory_to_use != -1)
+        cinfoP->mem->max_memory_to_use = max_memory_to_use;
+    if (nosmooth)
+        cinfoP->do_fancy_upsampling = FALSE;
+    
+}
+
+
+
+static void
+print_comments(struct jpeg_decompress_struct const cinfo) {
+    
+    struct jpeg_marker_struct * markerP;
+
+    for (markerP = cinfo.marker_list;
+         markerP; markerP = markerP->next) 
+        if (markerP->marker == JPEG_COM) {
+            pm_message("COMMENT:");
+            print_marker(*markerP);
+        }
+}
+
+
+
+static void
+print_exif_info(struct jpeg_marker_struct const marker) {
+/*----------------------------------------------------------------------------
+   Dump as informational messages the contents of the Jpeg miscellaneous
+   marker 'marker', assuming it is an Exif header.
+-----------------------------------------------------------------------------*/
+    ImageInfo_t imageInfo;
+    const char * error;
+
+    assert(marker.data_length >= 6);
+
+    process_EXIF(marker.data+6, marker.data_length-6, 
+                 &imageInfo, FALSE, &error);
+
+    if (error) {
+        pm_message("EXIF header is invalid.  %s", error);
+        strfree(error);
+    } else
+        ShowImageInfo(&imageInfo);
+}
+
+
+
+static boolean
+is_exif(struct jpeg_marker_struct const marker) {
+/*----------------------------------------------------------------------------
+   Return true iff the JPEG miscellaneous marker 'marker' is an Exif 
+   header.
+-----------------------------------------------------------------------------*/
+    boolean retval;
+    
+    if (marker.marker == JPEG_APP0+1) {
+        if (marker.data_length >=6 && memcmp(marker.data, "Exif", 4) == 0)
+            retval = TRUE;
+        else retval = FALSE;
+    }
+    else retval = FALSE;
+
+    return retval;
+}
+
+
+
+static void
+dump_exif(struct jpeg_decompress_struct const cinfo) {
+/*----------------------------------------------------------------------------
+   Dump as informational messages the contents of all EXIF headers in
+   the image, interpreted.  An EXIF header is an APP1 marker.
+-----------------------------------------------------------------------------*/
+    struct jpeg_marker_struct * markerP;
+    boolean found_one;
+
+    found_one = FALSE;  /* initial value */
+
+    for (markerP = cinfo.marker_list;
+         markerP; markerP = markerP->next) 
+        if (is_exif(*markerP)) {
+            pm_message("EXIF INFO:");
+            print_exif_info(*markerP);
+            found_one = TRUE;
+        }
+    if (!found_one)
+        pm_message("No EXIF info in image.");
+}
+
+
+
+static void
+save_exif(struct jpeg_decompress_struct const cinfo, 
+          const char *                  const exif_filespec) {
+/*----------------------------------------------------------------------------
+  Write the contents of the first Exif header in the image into the
+  file with filespec 'exif_filespec'.  Start with the two byte length
+  field.  If 'exif_filespec' is "-", write to standard output.
+
+  If there is no Exif header in the image, write just zero, as a two
+  byte pure binary integer.
+-----------------------------------------------------------------------------*/
+    FILE * exif_file;
+    struct jpeg_marker_struct * markerP;
+
+    exif_file = pm_openw(exif_filespec);
+
+    for (markerP = cinfo.marker_list; 
+         markerP && !is_exif(*markerP);
+         markerP = markerP->next);
+
+    if (markerP) {
+        pm_writebigshort(exif_file, markerP->data_length+2);
+        if (ferror(exif_file))
+            pm_error("Write of Exif header to %s failed on first byte.",
+                     exif_filespec);
+        else {
+            int rc;
+
+            rc = fwrite(markerP->data, 1, markerP->data_length, exif_file);
+            if (rc != markerP->data_length)
+                pm_error("Write of Exif header to '%s' failed.  Wrote "
+                         "length successfully, but then failed after "
+                         "%d characters of data.", exif_filespec, rc);
+        }
+    } else {
+        /* There is no Exif header in the image */
+        pm_writebigshort(exif_file, 0);
+        if (ferror(exif_file))
+            pm_error("Write of Exif header file '%s' failed.", exif_filespec);
+    }
+    pm_close(exif_file);
+}
+
+
+
+static void
+tellDetails(struct jpeg_decompress_struct const cinfo,
+            xelval                        const maxval,
+            int                           const output_type) {
+
+    print_verbose_info_about_header(cinfo);
+
+    pm_message("Input image data precision = %d bits", 
+               cinfo.data_precision);
+    pm_message("Output file will have format %c%c "
+               "with max sample value of %d.", 
+               (char) (output_type/256), (char) (output_type % 256),
+               maxval);
+}  
+
+
+
+static enum colorspace
+computeColorSpace(struct jpeg_decompress_struct * const cinfoP,
+                  enum inklevel                   const inklevel) {
+    
+    enum colorspace colorSpace;
+
+    if (cinfoP->out_color_space == JCS_GRAYSCALE)
+        colorSpace = GRAYSCALE_COLORSPACE;
+    else if (cinfoP->out_color_space == JCS_RGB)
+        colorSpace = RGB_COLORSPACE;
+    else if (cinfoP->out_color_space == JCS_CMYK) {
+        switch (inklevel) {
+        case ADOBE:
+            colorSpace = CMYK_ADOBE_COLORSPACE; break;
+        case NORMAL:
+            colorSpace = CMYK_NORMAL_COLORSPACE; break;
+        case GUESS:
+            colorSpace = CMYK_ADOBE_COLORSPACE; break;
+        }
+    } else
+        pm_error("Internal error: unacceptable output color space from "
+                 "JPEG decompressor.");
+
+    return colorSpace;
+}
+
+
+
+static void
+convertImage(FILE *                          const ofP, 
+             struct cmdlineInfo              const cmdline,
+             struct jpeg_decompress_struct * const cinfoP) {
+
+    int output_type;
+        /* The type of output file, PGM or PPM.  Value is either PPM_TYPE
+           or PGM_TYPE, which conveniently also pass as format values
+           PPM_FORMAT and PGM_FORMAT.
+        */
+    JSAMPROW jpegbuffer;  /* Input buffer.  Filled by jpeg_scanlines() */
+    unsigned int maxval;  
+        /* The maximum value of a sample (color component), both in the input
+           and the output.
+        */
+    enum colorspace color_space;
+        /* The color space of the pixels coming out of the JPEG decompressor */
+
+    beginJpegInput(cinfoP, cmdline.verbose, 
+                   cmdline.dct_method, 
+                   cmdline.max_memory_to_use, cmdline.nosmooth);
+                   
+    set_color_spaces(cinfoP->jpeg_color_space, &output_type, 
+                     &cinfoP->out_color_space);
+
+    maxval = (1 << cinfoP->data_precision) - 1;
+
+    if (cmdline.verbose) 
+        tellDetails(*cinfoP, maxval, output_type);
+
+    /* Calculate output image dimensions so we can allocate space */
+    jpeg_calc_output_dimensions(cinfoP);
+
+    jpegbuffer = ((*cinfoP->mem->alloc_sarray)
+                  ((j_common_ptr) cinfoP, JPOOL_IMAGE,
+                   cinfoP->output_width * cinfoP->output_components, 
+                   (JDIMENSION) 1)
+        )[0];
+
+    /* Start decompressor */
+    jpeg_start_decompress(cinfoP);
+
+    if (ofP)
+        /* Write pnm output header */
+        pnm_writepnminit(ofP, cinfoP->output_width, cinfoP->output_height,
+                         maxval, output_type, FALSE);
+
+    pnmbuffer = pnm_allocrow(cinfoP->output_width);
+    
+    color_space = computeColorSpace(cinfoP, cmdline.inklevel);
+
+    /* Process data */
+    while (cinfoP->output_scanline < cinfoP->output_height) {
+        jpeg_read_scanlines(cinfoP, &jpegbuffer, 1);
+        if (ofP)
+            copy_pixel_row(jpegbuffer, cinfoP->output_width, 
+                           cinfoP->out_color_components,
+                           color_space, maxval, ofP, output_type);
+    }
+
+    if (cmdline.comments)
+        print_comments(*cinfoP);
+    if (cmdline.dumpexif)
+        dump_exif(*cinfoP);
+    if (cmdline.exif_filespec)
+        save_exif(*cinfoP, cmdline.exif_filespec);
+
+    pnm_freerow(pnmbuffer);
+
+    /* Finish decompression and release decompressor memory. */
+    jpeg_finish_decompress(cinfoP);
+}
+
+
+
+
+static void
+saveMarkers(struct jpeg_decompress_struct * const cinfoP) {
+
+    unsigned int app_type;
+    /* Get all the miscellaneous markers (COM and APPn) saved for our
+       later access.
+    */
+    jpeg_save_markers(cinfoP, JPEG_COM, 65535);
+    for (app_type = 0; app_type <= 15; ++app_type) {
+        if (app_type == 0 || app_type == 14) {
+            /* The jpeg library uses APP0 and APP14 internally (see
+               libjpeg.doc), so we don't mess with those.
+            */
+        } else
+            jpeg_save_markers(cinfoP, JPEG_APP0 + app_type, 65535);
+    }
+}
+
+
+
+static void
+convertImages(FILE *                          const ofP,
+              struct cmdlineInfo              const cmdline,
+              struct jpeg_decompress_struct * const cinfoP,
+              struct sourceManager *          const sourceManagerP) {
+              
+    if (cmdline.multiple) {
+        unsigned int imageSequence;
+        for (imageSequence = 0; dsDataLeft(sourceManagerP); ++imageSequence) {
+            if (cmdline.verbose)
+                pm_message("Reading Image %u", imageSequence);
+            convertImage(ofP, cmdline, cinfoP);
+        }
+    } else {
+        if (dsDataLeft(sourceManagerP))
+            convertImage(ofP, cmdline, cinfoP);
+        else
+            pm_error("Input stream is empty");
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+    FILE * ofP;
+    struct cmdlineInfo cmdline;
+    struct jpeg_decompress_struct cinfo;
+    struct jpeg_error_mgr jerr;
+    struct sourceManager * sourceManagerP;
+
+    pnm_init(&argc, argv);
+
+    parse_command_line(argc, argv, &cmdline);
+
+    if (cmdline.exif_filespec && STREQ(cmdline.exif_filespec, "-"))
+        /* He's got exif going to stdout, so there can be no image output */
+        ofP = NULL;
+    else
+        ofP = stdout;
+
+    displayComments = cmdline.comments;
+
+    /* Initialize the JPEG decompression object with default error handling. */
+    cinfo.err = jpeg_std_error(&jerr);
+    jpeg_create_decompress(&cinfo);
+
+    if (cmdline.trace_level == 0 && cmdline.verbose) 
+        cinfo.err->trace_level = 1;
+    else 
+        cinfo.err->trace_level = cmdline.trace_level;
+    
+    saveMarkers(&cinfo);
+
+    sourceManagerP = dsCreateSource(cmdline.input_filespec);
+
+    cinfo.src = dsJpegSourceMgr(sourceManagerP);
+
+    convertImages(ofP, cmdline, &cinfo, sourceManagerP);
+
+    jpeg_destroy_decompress(&cinfo);
+
+    if (ofP) {
+        int rc;
+        rc = fclose(ofP);
+        if (rc == EOF) 
+            pm_error("Error writing output file.  Errno = %s (%d).",
+                     strerror(errno), errno);
+    }
+
+    dsDestroySource(sourceManagerP);
+
+    free(cmdline.input_filespec);
+  
+    exit(jerr.num_warnings > 0 ? EXIT_WARNING : EXIT_SUCCESS);
+}
+
diff --git a/converter/other/pamrgbatopng.c b/converter/other/pamrgbatopng.c
new file mode 100644
index 00000000..7babf9f9
--- /dev/null
+++ b/converter/other/pamrgbatopng.c
@@ -0,0 +1,150 @@
+#include <png.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <setjmp.h>
+
+#include "pam.h"
+#include "mallocvar.h"
+
+struct cmdlineInfo {
+    const char * inputFileName;
+};
+
+
+
+static void
+processCommandLine(int                  const argc,
+                   char *               const argv[],
+                   struct cmdlineInfo * const cmdlineP) {
+        
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else {
+        cmdlineP->inputFileName = argv[1];
+
+        if (argc-1 > 1)
+            pm_error("Too many arguments.  "
+                     "The only argument is the input file name.");
+    }
+}
+
+
+
+static void
+convertPamToPng(const struct pam * const pamP,
+                const tuple *      const tuplerow,
+                png_byte *         const pngRow) {
+    
+    unsigned int col;
+    
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        
+        for (plane = 0; plane < 4; ++plane)
+            pngRow[4 * col + plane] = tuplerow[col][plane];
+    }
+}
+
+
+
+static void
+writeRaster(const struct pam * const pamP,
+            png_struct *       const pngP) {
+    
+    tuple * tupleRow;
+    png_byte * pngRow;
+    
+    tupleRow = pnm_allocpamrow(pamP);
+    MALLOCARRAY(pngRow, pamP->width * 4);
+
+    if (pngRow == NULL)
+        pm_error("Unable to allocate space for PNG pixel row.");
+    else {
+        unsigned int row;
+        for (row = 0; row < pamP->height; ++row) {
+            pnm_readpamrow(pamP, tupleRow);
+            
+            convertPamToPng(pamP, tupleRow, pngRow);
+            
+            png_write_row(pngP, pngRow);
+        }
+        free(pngRow);
+    }
+    pnm_freepamrow(tupleRow);
+}
+
+
+
+static void
+pngErrorHandler(png_struct * const pngP,
+                const char * const message) {
+
+    pm_error("Error generating PNG image.  libpng says: %s", message);
+}
+
+
+
+static void
+writePng(const struct pam * const pamP,
+         FILE *             const ofP) {
+
+    png_struct * pngP;
+    png_info * infoP;
+
+    pngP = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (!pngP)
+        pm_error("Could not allocate png struct.");
+
+    png_set_error_fn(pngP, NULL, &pngErrorHandler, NULL);
+
+    infoP = png_create_info_struct(pngP);
+    if (!infoP)
+        pm_error("Could not allocate PNG info structure");
+    else {
+        infoP->width      = pamP->width;
+        infoP->height     = pamP->height;
+        infoP->bit_depth  = 8;
+        infoP->color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+        
+        png_init_io(pngP, ofP);
+
+        png_write_info(pngP, infoP);
+        
+        writeRaster(pamP, pngP);
+
+        png_write_end(pngP, infoP);
+        
+        png_destroy_write_struct(&pngP, &infoP);
+    }
+}
+    
+
+
+int
+main(int argc, char * argv[]) {
+
+    FILE * ifP;
+    struct cmdlineInfo cmdline;
+    struct pam pam;
+
+    pnm_init(&argc, argv);
+
+    processCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pnm_readpaminit(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
+    
+    if (pam.depth < 4)
+        pm_error("PAM must have depth at least 4 (red, green, blue, alpha).  "
+                 "This one has depth %u", pam.depth);
+        
+    if (pam.maxval != 255)
+        pm_error("PAM must have maxval 255.  This one has %lu", pam.maxval);
+
+    writePng(&pam, stdout);
+
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/converter/other/pamtodjvurle.c b/converter/other/pamtodjvurle.c
new file mode 100644
index 00000000..ae35e81d
--- /dev/null
+++ b/converter/other/pamtodjvurle.c
@@ -0,0 +1,293 @@
+/*****************************************************************************
+                               pamtodjvurle
+******************************************************************************
+  This program converts a PAM image to DjVu Color RLE format.
+  
+  By Bryan Henderson, San Jose, CA April 2004.
+
+  Contributed to the public domain by its author.
+
+  This work is inspired by Ppmtodjvurle, written by Scott Pakin
+  <scott+pbm@pakin.org> in March 2004.  Bryan took the requirements of
+  the DjVu Color RLE format and the technique for generating the format
+  (but not code) from that program.
+
+*****************************************************************************/
+#include <stdio.h>
+#include <assert.h>
+
+#include "pam.h"
+#include "pammap.h"
+#include "shhopt.h"
+
+
+struct cmdlineInfo {
+    const char * inputFilespec;
+    const char * transparent;
+    unsigned int showcolormap;
+};
+
+
+
+static void 
+parseCommandLine(int argc, 
+                 char ** argv, 
+                 struct cmdlineInfo  * const cmdlineP) {
+/* --------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+--------------------------------------------------------------------------*/
+    optEntry *option_def = malloc( 100*sizeof( optEntry ) );
+    /* Instructions to optParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+  
+    unsigned int option_def_index;
+    unsigned int transparentSpec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "transparent",   OPT_STRING, &cmdlineP->transparent, 
+            &transparentSpec,        0);
+    OPTENT3(0, "showcolormap",  OPT_FLAG, NULL,
+            &cmdlineP->showcolormap,        0);
+  
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;   /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+    /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (!transparentSpec)
+        cmdlineP->transparent = "white";
+
+    /* Get the program parameters */
+
+    if (argc-1 >= 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        cmdlineP->inputFilespec = "-";
+    
+    if (argc-1 > 1)
+        pm_error("Program takes at most one argument:  the file name.  "
+                 "You specified %d", argc-1);
+}
+
+
+
+static void
+computeColorMap(struct pam *   const pamP,
+                tuple **       const tupleArray,
+                unsigned int * const numColorsP,
+                tupletable *   const colormapP,
+                tuplehash *    const colorhashP,
+                bool           const show) {
+
+    unsigned int numColors;
+    tupletable colormap;
+
+    colormap = pnm_computetuplefreqtable(pamP, tupleArray, 0, &numColors);
+    if (numColors > 0xFF0)
+        pm_error("too many colors; "
+                 "use pnmquant to reduce to no more than %u colors", 0xFF0);
+    
+    if (show) {
+        unsigned int colorIndex;
+        fprintf(stderr, "Color map:\n");
+        fprintf(stderr, "    Index Color\n");
+        for (colorIndex = 0; colorIndex < numColors; ++colorIndex) {
+            unsigned int plane;
+            fprintf(stderr, "    %5u   ", colorIndex);
+            for (plane = 0; plane < pamP->depth; ++plane)
+                fprintf(stderr, "%3lu ", colormap[colorIndex]->tuple[plane]);
+            fprintf(stderr, "\n");
+        }
+    }
+
+    *colorhashP = pnm_computetupletablehash(pamP, colormap, numColors);
+
+    *numColorsP = numColors;
+    *colormapP  = colormap;
+}
+
+
+
+static void
+makeDjvurleHeader(FILE *       const ofP,
+                  struct pam * const pamP,
+                  unsigned int const numColors,
+                  tupletable   const colormap) {
+    
+    unsigned int colorIndex;
+
+    fprintf(ofP, "R6\n");
+    fprintf(ofP, "%d %d %d\n", pamP->width, pamP->height, numColors);
+
+    for (colorIndex = 0; colorIndex < numColors; ++colorIndex) {
+        sample red, grn, blu;
+
+        if (pamP->depth >= 3) {
+            red = colormap[colorIndex]->tuple[PAM_RED_PLANE];
+            grn = colormap[colorIndex]->tuple[PAM_GRN_PLANE];
+            blu = colormap[colorIndex]->tuple[PAM_BLU_PLANE];
+        } else
+            red = grn = blu = colormap[colorIndex]->tuple[0];
+        
+        fputc(pnm_scalesample(red, pamP->maxval, 255), ofP);
+        fputc(pnm_scalesample(grn, pamP->maxval, 255), ofP);
+        fputc(pnm_scalesample(blu, pamP->maxval, 255), ofP);
+    }
+}
+
+
+
+static bool
+colorEqual(tuple        comparand,
+           unsigned int comparandDepth,
+           tuple        comparator) {
+
+    /* comparator has depth 3 */
+
+    if (comparandDepth >= 3)
+        return (comparand[0] == comparator[0] &&
+                comparand[1] == comparator[1] &&
+                comparand[2] == comparator[2]);
+    else
+        return (comparand[0] == comparator[0] &&
+                comparand[0] == comparator[1] &&
+                comparand[0] == comparator[2]);
+}
+
+
+
+static void 
+writeRleRun(FILE *       const ofP, 
+            struct pam * const pamP,
+            tuple        const color, 
+            int          const count,
+            tuplehash    const colorhash,
+            tuple        const transcolor) {
+/*----------------------------------------------------------------------------
+  Write one DjVu Color RLE run to the file 'ofP'.  The run is 
+  'count' pixels of color 'color', using the color index given by
+  'colorhash' and assuming 'transcolor' is the transparent color.
+  
+  'transcolor' is a 3-deep tuple with the same maxval as the image.
+-----------------------------------------------------------------------------*/
+    uint32n rlevalue;         /* RLE-encoded color/valuex */
+    int index;
+
+    if (count > 0) {
+        if (colorEqual(color, pamP->depth, transcolor))
+            index = 0xFFF;
+        else {
+            int found;
+            pnm_lookuptuple(pamP, colorhash, color, &found, &index);
+            assert(found);
+        }
+        rlevalue = (index << 20) | count;
+      
+        pm_writebiglong(ofP, rlevalue);
+    }
+}
+
+
+
+static void
+writeDjvurleRow(FILE *       const ofP,
+                struct pam * const pamP,
+                tuple *      const tupleRow,
+                tuplehash    const colorhash,
+                tuple        const transcolor) {
+
+    unsigned int col;
+    unsigned int runlength;
+    tuple prevpixel;        /* Previous pixel seen */
+
+    prevpixel = tupleRow[0];
+    runlength = 0;
+    
+    for (col = 0; col < pamP->width; ++col) {
+        tuple const newpixel = tupleRow[col];      /* Current pixel color */
+        
+        if (pnm_tupleequal(pamP, newpixel, prevpixel))
+            /* This is a continuation of the current run */
+            ++runlength;
+          else {
+              /* The run is over.  Write it out and start a run of the next
+                 color.
+              */
+              writeRleRun(ofP, pamP, prevpixel, runlength, 
+                          colorhash, transcolor);
+              runlength = 1;
+              prevpixel = newpixel;
+          }
+        if (runlength >= (1<<20)-1) {
+            /* Can't make the run any longer.  Write it out and start a
+               new run.
+            */
+            writeRleRun(ofP, pamP, prevpixel, runlength, 
+                        colorhash, transcolor);
+            runlength = 1;
+        }
+    }
+    /* Write the last run we started */
+    writeRleRun(ofP, pamP, prevpixel, runlength, colorhash, transcolor);
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+
+    FILE * const rlefile = stdout;
+
+    struct cmdlineInfo cmdline;
+    FILE *ifP;                 /* Input (Netpbm) file */
+    struct pam pam;            /* Description of the image */
+    tuple ** tupleArray;       /* The image raster */
+    tupletable colormap;       /* List of all of the colors used */
+    unsigned int numColors;    /* Number of unique colors in the color map */
+    tuplehash colorhash; 
+        /* Mapping from color to index into colormap[] */
+    tuple transcolor;
+        /* Color that should be considered transparent */
+
+    pnm_init (&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    tupleArray = pnm_readpam(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
+
+    transcolor = pnm_parsecolor(cmdline.transparent, pam.maxval);
+    
+    computeColorMap(&pam, tupleArray, &numColors, &colormap, &colorhash,
+                    cmdline.showcolormap);
+    
+    makeDjvurleHeader(rlefile, &pam, numColors, colormap);
+
+    /* Write the raster */
+
+    {
+        unsigned int row;
+        for (row = 0; row < pam.height; ++row)
+            writeDjvurleRow(rlefile, &pam, tupleArray[row], colorhash, 
+                            transcolor);
+    }
+    /* Clean up */
+    
+    pnm_freepamarray(tupleArray, &pam);
+    pnm_freetupletable(&pam, colormap);
+    pnm_destroytuplehash(colorhash);
+    pnm_freepamtuple(transcolor);
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/converter/other/pamtofits.c b/converter/other/pamtofits.c
new file mode 100644
index 00000000..d0552a5c
--- /dev/null
+++ b/converter/other/pamtofits.c
@@ -0,0 +1,304 @@
+/* pnmtofits.c - read a PNM image and produce a FITS file
+**
+** Copyright (C) 1989 by Wilson H. Bent (whb@hoh-2.att.com).
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** Modified by Alberto Accomazzi (alberto@cfa.harvard.edu), Dec 1, 1992.
+**
+** Added PPM input capability; the program is renamed pnmtofits.
+** This program produces files with NAXIS = 2 if input file is in PBM
+** or PGM format, and NAXIS = 3, NAXIS3 = 3 if input file is a PPM file.
+** Data is written out as either 8 bits/pixel or 16 bits/pixel integers,
+** depending on the value of maxval in the input file.
+** Flags -max, -min can be used to set DATAMAX, DATAMIN, BSCALE and BZERO
+** in the FITS header, but do not cause the data to be rescaled.
+*/
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "pam.h"
+
+struct cmdlineInfo {
+    const char * inputFileName;
+    unsigned int maxSpec;
+    double max;
+    double min;
+};
+
+
+
+static void 
+parseCommandLine(int argc, 
+                 char ** argv, 
+                 struct cmdlineInfo  * const cmdlineP) {
+/* --------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+--------------------------------------------------------------------------*/
+    optEntry *option_def;
+    /* Instructions to optParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+
+    unsigned int minSpec;
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "min",     OPT_FLOAT,
+            &cmdlineP->min,  &minSpec,                              0);
+    OPTENT3(0, "max",     OPT_FLOAT,
+            &cmdlineP->max,  &cmdlineP->maxSpec,                    0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;   /* We have no parms that are negative numbers */
+
+    /* Set some defaults the lazy way (using multiple setting of variables) */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+    /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!minSpec)
+        cmdlineP->min = 0.0;
+
+    if (cmdlineP->maxSpec) {
+        if (cmdlineP->max <= cmdlineP->min)
+            pm_error("-max must be greater than min (%f).  You specified %f",
+                     cmdlineP->min, cmdlineP->max);
+    }
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else {
+        cmdlineP->inputFileName = argv[1];
+        
+        if (argc-1 > 1)
+            pm_error("Too many arguments (%u).  The only non-option argument "
+                     "is the input file name.", argc-1);
+    }
+}
+
+
+
+
+static void
+writeHeaderCard(const char * const s) {
+/*----------------------------------------------------------------------------
+   Write the string 's', padded with spaces to 80 characters.
+-----------------------------------------------------------------------------*/
+    const char * card;
+
+    asprintfN(&card, "%-80.80s", s);
+
+    fwrite(card, sizeof(card[0]), 80, stdout);
+
+    strfree(card);
+}
+
+
+
+static void
+padToMultipleOf36Cards(unsigned int const nCardsAlreadyWritten) {
+
+    /* pad with blanks cards to multiple of 36 cards */
+
+    unsigned int const npadCard = 36 - (nCardsAlreadyWritten % 36);
+    unsigned int i;
+    
+    for (i = 0; i < npadCard; ++i)
+        writeHeaderCard("");
+}
+
+
+
+static void
+writeFitsHeader(int    const bitpix,
+                int    const planes,
+                int    const cols,
+                int    const rows,
+                double const bscale,
+                double const fitsBzero,
+                double const datamax,
+                double const datamin) {
+
+    char buffer[80+1];
+    unsigned int cardsWritten;
+                
+    cardsWritten = 0;  /* initial value */
+
+    sprintf(buffer, "%-20.20s%10.10s", "SIMPLE  =", "T");
+    writeHeaderCard(buffer);
+    ++cardsWritten;
+
+    sprintf(buffer, "%-20.20s%10d", "BITPIX  =", bitpix);
+    writeHeaderCard(buffer);
+    ++cardsWritten;
+
+    sprintf(buffer, "%-20.20s%10d", "NAXIS   =", (planes == 3) ? 3 : 2);
+    writeHeaderCard(buffer);
+    ++cardsWritten;
+
+    sprintf(buffer, "%-20.20s%10d", "NAXIS1  =", cols);
+    writeHeaderCard(buffer);
+    ++cardsWritten;
+
+    sprintf(buffer, "%-20.20s%10d", "NAXIS2  =", rows);
+    writeHeaderCard(buffer);
+    ++cardsWritten;
+
+    if (planes == 3) {
+        sprintf(buffer, "%-20.20s%10d", "NAXIS3  =", 3);
+        writeHeaderCard(buffer);
+        ++cardsWritten;
+    }
+
+    sprintf(buffer, "%-18.18s%12.5E", "BSCALE  =", bscale);
+    writeHeaderCard(buffer);
+    ++cardsWritten;
+    
+    sprintf(buffer, "%-18.18s%12.5E", "BZERO   =", fitsBzero);
+    writeHeaderCard(buffer);
+    ++cardsWritten;
+
+    sprintf(buffer, "%-18.18s%12.5E", "DATAMAX =", datamax);
+    writeHeaderCard(buffer);
+    ++cardsWritten;
+
+    sprintf(buffer, "%-18.18s%12.5E", "DATAMIN =", datamin);
+    writeHeaderCard(buffer);
+    ++cardsWritten;
+
+    writeHeaderCard("HISTORY Created by pnmtofits.");
+    ++cardsWritten;
+
+    writeHeaderCard("END");
+    ++cardsWritten;
+
+    padToMultipleOf36Cards(cardsWritten);
+}
+
+
+
+static void
+writeRaster(struct pam * const pamP,
+            tuple **     const tuples,
+            unsigned int const bitpix,
+            int          const offset) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        unsigned int row;
+        for (row = 0; row < pamP->height; ++row) {
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col) {
+                if (bitpix == 16) {
+                    /* 16 bit FITS samples are signed integers */
+                    int const fitsSample =
+                        (int)tuples[row][col][plane] - offset;
+                    pm_writebigshort(stdout, (short)fitsSample);
+                } else
+                    /* 8 bit FITS samples are unsigned integers */
+                    putchar(tuples[row][col][plane] - offset);
+            }
+        }
+    }
+    {
+        /* pad raster to 36 cards with nulls */
+        unsigned int const bytesWritten =
+            pamP->height * pamP->width * pamP->depth * bitpix/8;
+        unsigned int const npad = (36*80) - (bytesWritten % (36*80));
+        unsigned int i;
+
+        for (i = 0; i < npad; ++i)
+            putchar('\0');
+    }
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    tuple ** tuples;
+    struct pam pam;
+    unsigned int bitpix;
+    double datamin, datamax, bscale, fitsBzero;
+    int pnmSampleOffsetFromFits;
+        /* This is what you add to a FITS sample (raster) value in order
+           to get the PNM sample value which it represents.  Note that in
+           the default case, that PNM sample value is also the FITS "physical"
+           value, but user options can change that.
+        */
+    
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    tuples = pnm_readpam(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
+
+    datamin = cmdline.min;
+
+    if (cmdline.maxSpec)
+        datamax = cmdline.max;
+    else {
+        if (pam.maxval <= cmdline.min)
+            pm_error("You must specify -max greater than -min (%f).  "
+                     "max defaults to the maxval, which is %u",
+                     cmdline.min, (unsigned)pam.maxval);
+        datamax = pam.maxval;
+    }
+
+    assert(datamax > datamin);
+
+    bscale = (datamax - datamin) / pam.maxval;
+    
+    if (pam.maxval > 255) {
+        bitpix = 16;
+        /* Because 16 bit FITS samples are signed, we have to do a 2**15
+           offset to get any possible unsigned 16 bit PNM sample into a FITS
+           sample.
+        */
+        fitsBzero = 1 << 15;
+        pnmSampleOffsetFromFits = 1 << 15;
+    } else {
+        bitpix = 8;
+        fitsBzero = datamin;
+        /* Both 8 bit FITS samples and PNM samples are unsigned 8 bits, so
+           we make them identical.
+        */
+        pnmSampleOffsetFromFits = 0;
+    }
+
+    fitsBzero = datamin + pnmSampleOffsetFromFits;
+    pm_close(ifP);
+
+    writeFitsHeader(bitpix, pam.depth, pam.width, pam.height,
+                    bscale, fitsBzero, datamax, datamin);
+
+    writeRaster(&pam, tuples, bitpix, pnmSampleOffsetFromFits);
+
+    return 0;
+}
diff --git a/converter/other/pamtohdiff.c b/converter/other/pamtohdiff.c
new file mode 100644
index 00000000..0e1ff00f
--- /dev/null
+++ b/converter/other/pamtohdiff.c
@@ -0,0 +1,142 @@
+/******************************************************************************
+                                 pamtohdiff
+*******************************************************************************
+  This program creates a PAM output which a horizontal difference image of
+  the input PAM.  The samples in each row are a number to be added to to
+  the previous output row to create the next output row (and the first
+  output row is simply the same as the input row).
+
+  Because these samples must be positive and negative and PAM samples are
+  unsigned, we bias each PAM sample by the input maxval and make the output
+  maxval twice the input maxval.
+
+  By Bryan Henderson, San Jose, CA 2002.04.15.
+
+******************************************************************************/
+#include <string.h>
+#include <stdio.h>
+
+#include "pam.h"
+#include "shhopt.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespecs of input files */
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc( 100*sizeof( optEntry ) );
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "verbose",   OPT_FLAG,    NULL, &cmdlineP->verbose,  0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        pm_error("Too many arguments.");
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+    FILE *ifP;
+    struct cmdlineInfo cmdline;
+    struct pam inpam, outpam;
+    unsigned int row;
+    tuple * inrow;
+    tuple * outrow;
+    tuple * prevrow;
+
+    pnm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    outpam = inpam;
+    outpam.file = stdout;
+    outpam.format = PAM_FORMAT;
+    strcpy(outpam.tuple_type, "hdiff");
+
+    pnm_writepaminit(&outpam);
+
+    inrow = pnm_allocpamrow(&inpam);
+    outrow = pnm_allocpamrow(&outpam);
+    prevrow = pnm_allocpamrow(&inpam);
+
+    pnm_setpamrow(&inpam, prevrow, 0);
+
+    /* All arithmetic in this operation and in the reverse operation
+       (to recover the image) is done modulus the maxval+1 (the hdiff
+       PAM and the image have the same maxval) in order to produce
+       legal PAM samples (which must be in the range 0..maxval).  This
+       might seem to throw away information, but it doesn't.  Example:
+       maxval is 99.  Intensity goes from 90 in Row 0 to 10 in Row 1.
+       The difference is -80.  -80 mod 100 is 20, so 20 goes in the
+       hdiff output.  On reconstructing the image, the interpreter
+       knows the "20" can't be +20, because that would create the
+       sample 90 + 20 = 110, and violate maxval.  So it must be -80.
+       Modulus arithmetic by the interpreter effectively makes that
+       decision.  
+    */
+
+
+    /* The bias is just to make it easier to look at the output visually.
+       If you display the values as intensities, and your differences are
+       all +/- half of maxval, you can see positive transitions as bright
+       spots and negative transitions as dark spots.
+    */
+    
+    {
+        unsigned int const bias = outpam.maxval/2;
+        for (row = 0; row < inpam.height; ++row) {
+            unsigned int col;
+            pnm_readpamrow(&inpam, inrow);
+            for (col = 0; col < inpam.width; ++col) {
+            unsigned int plane;
+            for (plane = 0; plane < inpam.depth; ++plane) {
+                
+                sample const sampleValue = inrow[col][plane];
+                int const difference = sampleValue - prevrow[col][plane];
+                outrow[col][plane] = (difference + bias) % (outpam.maxval+1);
+                prevrow[col][plane] = sampleValue;
+            }
+        }
+            pnm_writepamrow(&outpam, outrow);
+        }
+    }
+    pnm_freepamrow(prevrow);
+    pnm_freepamrow(outrow);
+    pnm_freepamrow(inrow);
+
+    exit(0);
+}
+
diff --git a/converter/other/pamtohtmltbl.c b/converter/other/pamtohtmltbl.c
new file mode 100644
index 00000000..293badcb
--- /dev/null
+++ b/converter/other/pamtohtmltbl.c
@@ -0,0 +1,284 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* '-' if stdin */
+    const char *transparent;  /* NULL if none */
+    unsigned int verbose;
+};
+
+
+
+
+static void
+parseCommandLine ( int argc, char ** argv,
+                   struct cmdlineInfo *cmdlineP ) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int transparentSpec;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "verbose",     OPT_FLAG,   NULL,                  
+            &cmdlineP->verbose,       0 );
+    OPTENT3(0, "transparent", OPT_STRING, &cmdlineP->transparent,
+            &transparentSpec,  0 );
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+
+    if (!transparentSpec)
+        cmdlineP->transparent = NULL;
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        pm_error("Too many arguments.  Program takes at most one argument: "
+                 "input file name");
+}
+
+
+
+
+static void
+pripix(struct pam * const pamP,
+       tuple        const color, 
+       unsigned int const rectWidth, 
+       unsigned int const rectHeight, 
+       tuple        const transparentColor) {
+
+    if (rectWidth > 0 && rectHeight > 0) {
+        printf("<TD VALIGN=CENTER ALIGN=CENTER");
+
+        if (rectWidth != 1) 
+            printf(" COLSPAN=%d", rectWidth);
+        if (rectHeight != 1) 
+            printf(" ROWSPAN=%d", rectHeight);
+
+        if (transparentColor && 
+            !pnm_tupleequal(pamP, color,transparentColor)) {
+            /* No BGCOLOR attribute */
+        } else {
+            tuple const colorff = pnm_allocpamtuple(pamP);
+
+            unsigned int r, g, b;
+
+            pnm_scaletuple(pamP, colorff, color, 0xff);
+
+            if (pamP->depth < 3)
+                r = g = b = colorff[0];
+            else {
+                r = color[PAM_RED_PLANE];
+                g = color[PAM_GRN_PLANE];
+                b = color[PAM_BLU_PLANE];
+            }
+            printf(" BGCOLOR=#%02X%02X%02X", r, g, b);
+        }
+        printf(">");
+        printf("<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>"
+               "<TR><TD></TD></TR></TABLE>");
+        printf("</TD>\n");
+    }
+}
+
+
+
+static void
+findSameColorRectangle(struct pam *   const pamP,
+                       tuple **       const tuples,
+                       unsigned int   const row,
+                       unsigned int   const col,
+                       unsigned int * const rectWidthP,
+                       unsigned int * const rectHeightP) {
+/*----------------------------------------------------------------------------
+   Find the largest rectangle, in the image described by 'pam' and 
+   'tuples', of uniform color, whose upper left corner is at (row, col).
+
+   Return the width and height of that rectangle as *rectWidthP
+   and *rectHeightP.
+-----------------------------------------------------------------------------*/
+    tuple const rectangleColor = tuples[row][col];
+
+    unsigned int i;
+    unsigned int mx, my;
+    unsigned int cnx, cny;
+
+    mx=0; my=0;
+    cnx = pamP->width - col; cny = pamP->height - row;
+
+    for (i=0; (!mx)||(!my); i++) {
+        int j;
+        /*fprintf(stderr,"\n[%d]",i);*/
+        for (j=0; j<=i; j++) {
+            if (!my) {
+                if (i>=cny) 
+                    my=cny;
+                else
+                    if (((!mx) || (j<mx)) && (j < cnx)) {
+                        /*fprintf(stderr,"%d/%d ",j,i);*/
+                        if (!pnm_tupleequal(pamP, tuples[row+i][col+j],
+                                            rectangleColor)) 
+                            my = i;
+                    }
+            }
+            if (!mx) {
+                if (i>=cnx) 
+                    mx=cnx;
+                else
+                    if (((!my) || (j<my)) && (j < cny)) {
+                        /*fprintf(stderr,"%d/%d ",i,j);*/
+                        if (!pnm_tupleequal(pamP, tuples[row+j][col+i],
+                                            rectangleColor)) 
+                            mx = i;
+                    }
+            }
+        }
+    }
+    *rectWidthP  = mx;
+    *rectHeightP = my;
+}
+
+
+
+static bool **
+allocOutputtedArray(unsigned int const width, unsigned int const height) {
+
+    bool ** outputted;
+    unsigned int row;
+
+    MALLOCARRAY(outputted, height);
+    if (outputted == NULL)
+        pm_error("Unable to allocate space for 'outputted' array");
+
+    for (row = 0; row < height; ++row) {
+        MALLOCARRAY(outputted[row], width);
+        if (outputted[row] == NULL)
+            pm_error("Unable to allocate space for 'outputted' array");
+    }
+    return outputted;
+}
+
+
+
+static void
+freeOutputtedArray(bool ** const outputted, unsigned int const height) {
+
+    unsigned int row;
+
+    for (row = 0; row < height; ++row)
+        free(outputted[row]);
+}
+
+
+
+static void
+markOutputted(bool ** const outputted,
+              unsigned int const upperLeftCol,
+              unsigned int const upperLeftRow,
+              unsigned int const width,
+              unsigned int const height) {
+
+    unsigned int const lowerRightCol = upperLeftCol + width;
+    unsigned int const lowerRightRow = upperLeftRow + height;
+    unsigned int row;
+    
+    for (row = upperLeftRow; row < lowerRightRow; ++row) {
+        unsigned int col;
+        for (col = upperLeftCol; col < lowerRightCol; ++col) 
+            outputted[row][col] = TRUE;
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    struct pam inpam;
+    tuple ** tuples;
+    int row;
+    unsigned int rectWidth, rectHeight;
+    bool ** outputted;
+    tuple transparentColor;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    tuples = pnm_readpam(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    if (cmdline.transparent) {
+        pixel transcolor;
+        transcolor = ppm_parsecolor(cmdline.transparent, inpam.maxval);
+        transparentColor = pnm_allocpamtuple(&inpam);
+        transparentColor[PAM_RED_PLANE] = PPM_GETR(transcolor);
+        transparentColor[PAM_GRN_PLANE] = PPM_GETG(transcolor);
+        transparentColor[PAM_BLU_PLANE] = PPM_GETB(transcolor);
+    } else
+        transparentColor = NULL;
+
+    outputted = allocOutputtedArray(inpam.width, inpam.height);
+
+    printf("<TABLE WIDTH=%d HEIGHT=%d BORDER=0 CELLSPACING=0 CELLPADDING=0>\n",
+           inpam.width, inpam.height);
+    for (row = 0; row < inpam.height; ++row) {
+        int col;
+        printf("<TR>\n");
+        pripix(&inpam, tuples[row][0], 1, 1, transparentColor); 
+        markOutputted(outputted, 0, row, 1, 1);
+
+        for (col = 1; col < inpam.width; ++col) {
+            if (!outputted[row][col]) {
+                findSameColorRectangle(&inpam, tuples, row, col, 
+                                       &rectWidth, &rectHeight);
+                if (cmdline.verbose)
+                    pm_message("[%d/%d] [%d/%d]",
+                               col, row, rectWidth, rectHeight);
+                pripix(&inpam, tuples[row][col], rectWidth, rectHeight, 
+                       transparentColor);
+                markOutputted(outputted, col, row, rectWidth, rectHeight);
+            }
+        }
+        printf("</TR>\n");
+    }
+    printf("</TABLE>\n");
+
+    if (transparentColor)
+        pnm_freepamtuple(transparentColor);
+    pnm_freepamarray(tuples, &inpam);
+    freeOutputtedArray(outputted, inpam.height);
+
+    exit(0);
+}
diff --git a/converter/other/pamtopfm.c b/converter/other/pamtopfm.c
new file mode 100644
index 00000000..f8fdc96b
--- /dev/null
+++ b/converter/other/pamtopfm.c
@@ -0,0 +1,304 @@
+/*****************************************************************************
+                                  pamtopfm
+******************************************************************************
+  This program converts a PAM image to PFM (Portable Float Map).
+  
+  By Bryan Henderson, San Jose, CA April 2004.
+
+  Contributed to the public domain by its author.
+
+*****************************************************************************/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <assert.h>
+
+#include "pam.h"
+#include "pm_gamma.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+enum endian {ENDIAN_BIG, ENDIAN_LITTLE};
+
+struct cmdlineInfo {
+    const char * inputFilespec;
+    unsigned int verbose;
+    enum endian endian;
+    float scale;
+};
+
+
+
+static enum endian machineEndianness;
+
+
+
+static void 
+parseCommandLine(int argc, 
+                 char ** argv, 
+                 struct cmdlineInfo  * const cmdlineP) {
+/* --------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+--------------------------------------------------------------------------*/
+    optEntry *option_def = malloc( 100*sizeof( optEntry ) );
+    /* Instructions to optParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+  
+    unsigned int option_def_index;
+    char * endianOpt;
+    unsigned int endianSpec, scaleSpec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "endian",   OPT_STRING, &endianOpt, &endianSpec,        0);
+    OPTENT3(0, "scale",    OPT_FLOAT,  &cmdlineP->scale, &scaleSpec,   0);
+  
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;   /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+    /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (endianSpec) {
+        if (streq(endianOpt, "big"))
+            cmdlineP->endian = ENDIAN_BIG;
+        else if (streq(endianOpt, "little"))
+            cmdlineP->endian = ENDIAN_LITTLE;
+        else
+            pm_error("Invalid value '%s' for -endian.  "
+                     "Must be 'big' or 'little'.", endianOpt);
+    } else
+        cmdlineP->endian = machineEndianness;
+
+    if (!scaleSpec) {
+        cmdlineP->scale = 1.0;
+    }
+    if (cmdlineP->scale == 0.0)
+        pm_error("Scale factor cannot be zero");
+
+    /* Get the program parameters */
+
+    if (argc-1 >= 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        cmdlineP->inputFilespec = "-";
+    
+    if (argc-1 > 1)
+        pm_error("Program takes at most one argument:  the file name.  "
+                 "You specified %d", argc-1);
+}
+
+
+
+static enum endian
+thisMachineEndianness(void) {
+/*----------------------------------------------------------------------------
+   Endianness is a component of the format in which a machine represents
+   a number in memory or a register.  It is the only component of the format
+   that varies among typical machines.
+
+   Big endianness is the natural format.  In this format, if an integer is
+   4 bytes, to be stored at memory address 100-103, the most significant 
+   byte goes at 100, the next most significant at 101, and the least
+   significant byte at 103.  This is natural because it matches the way
+   humans read and write numbers.  I.e. 258 is stored as 0x00000102.
+
+   Little endian is extremely common because it is used by IA32.  In the
+   example above, the least significant byte goes first, so 258 would be
+   stored as 0x02010000.
+
+   You can extend this concept to floating point numbers, even though the
+   bytes of a floating point number differ by more than significance.
+-----------------------------------------------------------------------------*/
+    short const testNumber = 0x0001;
+
+    unsigned char * const storedNumber = (unsigned char *)&testNumber;
+    enum endian endianness;
+    
+    if (storedNumber[0] == 0x01)
+        endianness = ENDIAN_LITTLE;
+    else
+        endianness = ENDIAN_BIG;
+
+    return endianness;
+}
+
+
+
+typedef struct {
+    unsigned char bytes[4];
+} pfmSample;
+
+
+
+static void
+floatToPfmSample(float       const input,
+                 pfmSample *       outputP,
+                 enum endian const pfmEndianness) {
+/*----------------------------------------------------------------------------
+   Type converter
+-----------------------------------------------------------------------------*/
+    if (machineEndianness == pfmEndianness) {
+        *(float *)outputP->bytes = input;
+    } else {
+        unsigned char reversed[sizeof(pfmSample)];
+        unsigned int i, j;
+
+        *(float *)reversed = input;
+        
+        for (i = 0, j = sizeof(pfmSample)-1; 
+             i < sizeof(pfmSample); 
+             ++i, --j)
+            
+            outputP->bytes[i] = reversed[j];
+    }
+}
+
+
+
+struct pfmHeader {
+    unsigned int width;
+    unsigned int height;
+    bool color;
+    float scaleFactor;
+    enum endian endian;
+};
+
+
+static void
+writePfmHeader(FILE *           const ofP,
+               struct pfmHeader const pfmHeader) {
+
+    const char * const magic = pfmHeader.color ? "PF" : "Pf";
+    float const scaleFactorEndian = 
+        pfmHeader.endian == ENDIAN_BIG ? 
+            pfmHeader.scaleFactor :
+            - pfmHeader.scaleFactor;
+
+    fprintf(ofP, "%s\n",    magic);
+    fprintf(ofP, "%u %u\n", pfmHeader.width, pfmHeader.height);
+    fprintf(ofP, "%f\n",    scaleFactorEndian);
+}
+
+
+
+static void
+writePfmRow(struct pam * const pamP,
+            FILE *       const ofP,
+            unsigned int const pfmRow,
+            unsigned int const pfmSamplesPerRow,
+            tuplen **    const tuplenArray,
+            enum endian  const endian,
+            float        const scaleFactor,
+            pfmSample *  const pfmRowBuffer) {
+
+    int const row = pamP->height - pfmRow - 1;
+    tuplen * const tuplenRow = tuplenArray[row];
+
+    int col;
+    int pfmCursor;
+    int rc;
+
+    pfmCursor = 0;  /* initial value */
+
+    for (col = 0; col < pamP->width; ++col) {
+        /* The order of planes (R, G, B) is the same in PFM as in PAM. */
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane) {
+            pfmSample val;
+            floatToPfmSample(tuplenRow[col][plane] * scaleFactor, 
+                             &val, endian);
+            pfmRowBuffer[pfmCursor++] = val;
+        }
+    }
+    assert(pfmCursor == pfmSamplesPerRow);
+
+    rc = fwrite(pfmRowBuffer, sizeof(pfmSample), pfmSamplesPerRow, ofP);
+    if (rc != pfmSamplesPerRow)
+        pm_error("Unable to write to output file in the middle of row %d", 
+                 pfmRow);
+
+
+}
+
+
+
+static struct pfmHeader
+makePfmHeader(const struct pam * const pamP,
+              float              const scaleFactor,
+              enum endian        const endian) {
+    
+    struct pfmHeader pfmHeader;
+    
+    pfmHeader.width  = pamP->width;
+    pfmHeader.height = pamP->height;
+
+    if (strncmp(pamP->tuple_type, "RGB", 3) == 0)
+        pfmHeader.color = TRUE;
+    else if (strncmp(pamP->tuple_type, "GRAYSCALE", 9) == 0)
+        pfmHeader.color = FALSE;
+    else if (strncmp(pamP->tuple_type, "BLACKANDWHITE", 13) == 0)
+        pfmHeader.color = FALSE;
+    else
+        pm_error("Invalid PAM input.  Tuple type is '%s'.  "
+                 "We understand only RGB* and GRAYSCALE*", pamP->tuple_type);
+
+    pfmHeader.scaleFactor = scaleFactor;
+    pfmHeader.endian = endian;
+        
+    return pfmHeader;
+}
+
+
+int
+main(int argc, char **argv ) {
+
+    struct cmdlineInfo cmdline;
+    FILE* ifP;
+    struct pam pam;
+    pfmSample * pfmRowBuffer;
+    unsigned int pfmSamplesPerRow;
+    unsigned int pfmRow;
+    tuplen ** tuplenArray;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    machineEndianness = thisMachineEndianness();
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    tuplenArray = pnm_readpamn(ifP, &pam, sizeof(pam));
+
+    writePfmHeader(stdout, 
+                   makePfmHeader(&pam, cmdline.scale, cmdline.endian));
+
+    pfmSamplesPerRow = pam.width * pam.depth;
+    
+    MALLOCARRAY_NOFAIL(pfmRowBuffer, pfmSamplesPerRow);
+
+    /* PFMs are upside down like BMPs */
+    for (pfmRow = 0; pfmRow < pam.height; ++pfmRow)
+        writePfmRow(&pam, stdout, pfmRow, pfmSamplesPerRow,
+                    tuplenArray, cmdline.endian, cmdline.scale,
+                    pfmRowBuffer);
+
+    pnm_freepamarrayn(tuplenArray, &pam);
+    free(pfmRowBuffer);
+    
+    pm_close(stdout);
+    pm_close(pam.file);
+
+    return 0;
+}
diff --git a/converter/other/pamtopnm.c b/converter/other/pamtopnm.c
new file mode 100644
index 00000000..cc1164da
--- /dev/null
+++ b/converter/other/pamtopnm.c
@@ -0,0 +1,155 @@
+/*----------------------------------------------------------------------------
+                               pamtopnm
+------------------------------------------------------------------------------
+  Part of the Netpbm package.
+
+  Convert PAM images to PBM, PGM, or PPM (i.e. PNM)
+
+  By Bryan Henderson, San Jose CA 2000.08.05
+
+  Contributed to the public domain by its author 2000.08.05.
+-----------------------------------------------------------------------------*/
+
+#include <string.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespecs of input files */
+    unsigned int assume;    /* -assume option */
+};
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "assume",     OPT_FLAG,   NULL, &cmdlineP->assume,         0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 == 0) 
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+static void
+validateTupleType(struct pam const inpam, 
+                  int        const assumeTupleType) {
+/*----------------------------------------------------------------------------
+   Make sure the image has a tuple type we know how to convert to PNM.
+
+   We're quite liberal, trying to accomodate all sorts of future
+   twists on the formats.  If the tuple type _starts with_
+   BLACKANDWHITE, GRAYSCALE, or RGB, and has at least as many planes
+   as we'd need to convert to PBM, PGM, or PPM, respectively, we
+   accept it.  We thus accomodate variations on these formats that add
+   planes and add to the right end of the tuple type to explain them.
+
+   If Callers specified 'assumeTupleType', we're even more liberal.
+-----------------------------------------------------------------------------*/
+    if (assumeTupleType) {
+        /* User says tuple type is appropriate regardless of tuple_type. */
+    } else {
+        if (inpam.depth >= 1 && 
+            strncmp(inpam.tuple_type, "BLACKANDWHITE", 
+                    sizeof("BLACKANDWHITE")-1) == 0) {
+            /* It's a PBMable image */
+        } else if (inpam.depth >= 1 && 
+                   strncmp(inpam.tuple_type, "GRAYSCALE",
+                           sizeof("GRAYSCALE")-1) == 0) {
+            /* It's a PGMable image */
+        } else if (inpam.depth >= 3 &&
+                   strncmp(inpam.tuple_type, "RGB", sizeof("RGB")-1) == 0) {
+            /* It's a PPMable image */
+        } else 
+            pm_error("PAM image does not have a depth and tuple_type "
+                     "consistent with a PNM image."
+                     "According to its "
+                     "header, depth is %d and tuple_type is '%s'.  "
+                     "Use the -assume option to convert anyway.",
+                     inpam.depth, inpam.tuple_type);
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE* ifP;
+    struct pam inpam;   /* Input PAM image */
+    struct pam outpam;  /* Output PNM image */
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    validateTupleType(inpam, cmdline.assume);
+
+    outpam = inpam;
+    outpam.file = stdout;
+    
+    if (inpam.depth < 3) {
+        outpam.depth = 1;
+        if (inpam.maxval == 1)
+            outpam.format = PBM_FORMAT;
+        else 
+            outpam.format = PGM_FORMAT;
+    } else {
+        outpam.depth = 3;
+        outpam.format = PPM_FORMAT;
+    }
+
+    pnm_writepaminit(&outpam);
+
+    {
+        tuple *tuplerow;
+        
+        tuplerow = pnm_allocpamrow(&inpam);      
+        { 
+            int row;
+            
+            for (row = 0; row < inpam.height; row++) {
+                pnm_readpamrow(&inpam, tuplerow);
+                pnm_writepamrow(&outpam, tuplerow);
+            }
+        }
+        pnm_freepamrow(tuplerow);        
+    }
+    return 0;
+}
diff --git a/converter/other/pamtosvg/Makefile b/converter/other/pamtosvg/Makefile
new file mode 100644
index 00000000..7f9c3e30
--- /dev/null
+++ b/converter/other/pamtosvg/Makefile
@@ -0,0 +1,55 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/other/pamtosvg
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+BINARIES = pamtosvg
+
+PAMTOSVG_OBJECTS = \
+	pamtosvg.o \
+	output-svg.o \
+	fit.o \
+	spline.o \
+	curve.o \
+	vector.o \
+	epsilon-equal.o \
+	autotrace.o \
+	pxl-outline.o \
+	bitmap.o \
+	thin-image.o \
+	logreport.o \
+	exception.o \
+	image-proc.o \
+
+MERGE_OBJECTS = \
+	pamtosvg.o2 \
+	output-svg.o \
+	fit.o \
+	spline.o \
+	curve.o \
+	vector.o \
+	epsilon-equal.o \
+	autotrace.o \
+	pxl-outline.o \
+	bitmap.o \
+	thin-image.o \
+	logreport.o \
+	exception.o \
+	image-proc.o \
+
+OBJECTS = $(PAMTOSVG_OBJECTS)
+
+MERGEBINARIES = $(BINARIES)
+
+all: $(BINARIES)
+
+include $(SRCDIR)/Makefile.common
+
+pamtosvg: $(PAMTOSVG_OBJECTS) $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $(PAMTOSVG_OBJECTS) \
+	  $(shell $(LIBOPT) $(NETPBMLIB)) \
+	  $(MATHLIB) $(LDLIBS) $(RPATH) $(LADD)
diff --git a/converter/other/pamtosvg/README b/converter/other/pamtosvg/README
new file mode 100644
index 00000000..a06b71a5
--- /dev/null
+++ b/converter/other/pamtosvg/README
@@ -0,0 +1,109 @@
+The core of this program is derived from Martin Weber's
+(martweb@gmx.net) Autotrace.  Bryan Henderson adapted it to Netpbm in 
+February 2006.
+
+Much of the Autotrace code has been rewritten to be easier to read and match
+Netpbm coding style.
+
+Pieces of Autotrace that duplicate other Netpbm programs or just don't fit
+the Netpbm philosophy have been removed.  In particular, Autotrace has the
+ability to take formats other than Netpbm formats for input, to despeckle
+the image before tracing, and to quantize colors before tracing.  Pamtosvg
+has none of that.
+
+Pamtosvg uses libnetpbm to read the input image, process the command line,
+manage memory, and do several other minor things.  Autotrace has its own
+code for those things.
+
+Autotrace uses a shmaltzy trick to deal with open outlines.  There is a
+piece of the program that divides an outline at its corners and then creates
+a curve for the pieces between every two corners.  For an open outline,
+there are segments of the outline at each end that are not between two
+corners.  One piece of code finds the corners, and another computes the
+curves between them.  
+
+In order to use the closed outline code for the open outline case,
+Autotrace includes a dummy corner at the start of the outline in the
+list of corners.  The curve-finding code then has a special case for
+the segment after the last corner.  Pamtosvg uses a cleaner approach.
+The corner list is an actual list of corners -- there is no dummy
+corner.  The curve-finding code has two special cases for open
+outlines -- one for the segment before the first corner, and one for
+the segment after the last corner.
+
+
+STRATEGY
+--------
+
+Autotrace is capable of many more vector graphics output formats
+besides SVG.  The basic curve tracing is the same for all; it just has
+output formatting modules.  Netpbm ought to be able to generate those
+formats as well.  SVG is just the beginning.
+
+Of course, having a program that generates multiple output formats based
+on command line options is not the Netpbm way to approach it.  A pure
+traditional Netpbm approach would be to have a converter between PAM
+and each of the vector graphics formats.
+
+That isn't practical because of the information that gets lost when you
+convert from vector to raster form, and also because 99% of the logic
+in converting between PAM and a vector graphics format is the same for
+all the vector graphics formats.
+
+Therefore, the strategy is to adopt a single vector graphics format
+and use it as the common intermediate format.  Right now, it looks
+like SVG would be a good choice for that.  However, it may turn out to
+be better to create a vector graphics format specially for Netpbm.
+The reason for that is that an intermediate Netpbm format has a rather
+different requirement from all these other formats -- it isn't meant
+to be transmitted or stored, but it is meant to be easy for a
+programmer to work with.
+
+We need a program to go the other way -- to convert from SVG to PAM.
+I.e. a curve drawing program.  The Ppmdraw program does this kind of
+curve drawing (in fact, a Ppmdraw script can be considered a vector
+graphics format).  Ppmdraw itself might not be adaptable, but the
+library routines it uses are probably all we need to convert SVG to
+PAM.
+
+The Pamtosvg code should be reworked to use a libnetpbm tuple array
+instead of its at_bitmap_type for the raster.
+
+Nobody has any plans to do any of this work.  I document the strategy
+only so that if someone decides to do some work, he can go in the
+right direction.
+
+
+COPYRIGHT CONSIDERATIONS
+------------------------
+
+Bryan took the code and has distributed it under a copyright license
+granted to him, as a member of the public, by the authors.  That
+license is the GNU Lesser Public License Version 2.1.  All the authors
+offer that same license for Pamtosvg to the public.  A copy of it is
+in the Netpbm doc/ directory.
+
+
+CREDITS
+-------
+
+Autotrace's source code listed the following as contributors to it:
+
+Martin Weber <martweb@gmx.net>
+Bernhard Herzog (Postscript, svg and sk export filter) 
+Ian MacPhedran (xfig export filter) 
+Martin Kroeker (bugfixes) 
+Tobias Polzin (bugfixes) 
+Kevin O'Gorman (Shockwave support) 
+MenTaLguY (png import filter)
+Peter Cucka (bugfixes)
+Enrico Persiani (emf export) 
+Johannes Schindelin (Magick import filter)
+Masatake YAMATO (library, help with cvs)
+Steffen Politzky (dxf export)
+David A. Bartold (part of despeckle)
+Han-Wen Nienhuys (rpm-spec file)
+R. P. C. Rodgers (man page)
+Allen Barnett (improved emf export)
+Andrew Elia (dr2d export filter)
+Ralf Stubner (bugfixes about pstoedit usage)
diff --git a/converter/other/pamtosvg/autotrace.c b/converter/other/pamtosvg/autotrace.c
new file mode 100644
index 00000000..e9902669
--- /dev/null
+++ b/converter/other/pamtosvg/autotrace.c
@@ -0,0 +1,220 @@
+/* autotrace.c --- Autotrace API
+
+  Copyright (C) 2000, 2001, 2002 Martin Weber
+
+  The author can be contacted at <martweb@gmx.net>
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include "mallocvar.h"
+
+#include "autotrace.h"
+#include "exception.h"
+
+#include "fit.h"
+#include "bitmap.h"
+#include "spline.h"
+
+#include "image-header.h"
+#include "image-proc.h"
+#include "thin-image.h"
+
+
+#define AT_DEFAULT_DPI 72
+
+at_fitting_opts_type *
+at_fitting_opts_new(void)
+{
+  at_fitting_opts_type * opts;
+  MALLOCVAR_NOFAIL(opts);
+  return opts;
+}
+
+at_fitting_opts_type *
+at_fitting_opts_copy (at_fitting_opts_type * original)
+{
+  at_fitting_opts_type * new_opts;
+  if (original == NULL)
+    return NULL;
+
+  new_opts = at_fitting_opts_new ();
+  *new_opts = *original;
+  new_opts->backgroundSpec = original->backgroundSpec;
+  new_opts->background_color = original->background_color;
+  return new_opts;
+}
+
+void 
+at_fitting_opts_free(at_fitting_opts_type * opts)
+{
+  free(opts);
+}
+
+at_output_opts_type *
+at_output_opts_new(void)
+{
+  at_output_opts_type * opts;
+  MALLOCVAR_NOFAIL(opts);
+  opts->dpi          = AT_DEFAULT_DPI;
+  return opts;
+}
+
+at_output_opts_type *
+at_output_opts_copy(at_output_opts_type * original)
+{
+  at_output_opts_type * opts =  at_output_opts_new();
+  *opts = *original;
+  return opts;
+}
+
+void
+at_output_opts_free(at_output_opts_type * opts)
+{
+  free(opts);
+}
+
+/* at_splines_new_full modifies its 'bitmap' argument
+   when it does the thin_image thing.
+*/
+at_spline_list_array_type * 
+at_splines_new_full(at_bitmap_type *       const bitmap,
+                    at_fitting_opts_type * const opts,
+                    at_msg_func                  msg_func, 
+                    void *                 const msg_data,
+                    at_progress_func             notify_progress,
+                    void *                 const progress_data,
+                    at_testcancel_func           test_cancel,
+                    void *                 const testcancel_data) {
+
+    at_spline_list_array_type * retval;
+    image_header_type image_header;
+    pixel_outline_list_type pixelOutlineList;
+    at_exception_type exp;
+    distance_map_type distanceMap;
+    bool haveDistMap;
+
+    exp = at_exception_new(msg_func, msg_data);
+
+    image_header.width  = at_bitmap_get_width(bitmap);
+    image_header.height = at_bitmap_get_height(bitmap);
+
+    if (opts->centerline) {
+        if (opts->preserve_width) {
+            /* Preserve line width prior to thinning. */
+            bool const paddedTrue = true;
+            distanceMap = new_distance_map(*bitmap, 255, paddedTrue, &exp);
+            haveDistMap = true;
+        } else
+            haveDistMap = false;
+        thin_image(bitmap, opts->backgroundSpec, opts->background_color, &exp);
+    } else
+        haveDistMap = false;
+
+    if (at_exception_got_fatal(&exp))
+        retval = NULL;
+    else {
+        if (opts->centerline) {
+            pixel background_color;
+
+            if (opts->backgroundSpec) 
+                background_color = opts->background_color;
+            else
+                PPM_ASSIGN(background_color, 255, 255, 255);
+            
+            pixelOutlineList =
+                find_centerline_pixels(*bitmap, background_color, 
+                                       notify_progress, progress_data,
+                                       test_cancel, testcancel_data, &exp);
+        } else
+            pixelOutlineList =
+                find_outline_pixels(*bitmap,
+                                    opts->backgroundSpec,
+                                    opts->background_color, 
+                                    notify_progress, progress_data,
+                                    test_cancel, testcancel_data, &exp);
+
+        if (at_exception_got_fatal(&exp) ||
+            (test_cancel && test_cancel(testcancel_data)))
+            retval = NULL;
+        else {
+            at_spline_list_array_type * splinesP;
+        
+            MALLOCVAR_NOFAIL(splinesP); 
+            fit_outlines_to_splines(pixelOutlineList, opts,
+                                    haveDistMap ? &distanceMap : NULL,
+                                    image_header.width,
+                                    image_header.height,
+                                    &exp,
+                                    notify_progress, progress_data,
+                                    test_cancel, testcancel_data,
+                                    splinesP);
+
+            if (at_exception_got_fatal(&exp) ||
+                (test_cancel && test_cancel(testcancel_data)))
+                retval = NULL;
+            else {
+                if (notify_progress)
+                    notify_progress(1.0, progress_data);
+
+                retval = splinesP;
+            }
+            free_pixel_outline_list(&pixelOutlineList);
+        }
+        if (haveDistMap)
+            free_distance_map(&distanceMap);
+    }
+    return retval;
+}
+
+
+
+void 
+at_splines_write(at_output_write_func                  outputWriter,
+                 FILE *                          const writeto,
+                 at_output_opts_type *           const optsArg,
+                 at_spline_list_array_type *     const splinesP,
+                 at_msg_func                           msgFunc,
+                 void *                          const msgData) {
+
+    at_output_opts_type * optsP;
+    bool newOpts;
+    int llx, lly, urx, ury;
+    llx = 0;
+    lly = 0;
+    urx = splinesP->width;
+    ury = splinesP->height;
+    
+    if (optsArg == NULL) {
+        newOpts = true;
+        optsP   = at_output_opts_new();
+    } else {
+        newOpts = false;
+        optsP   = optsArg;
+    }
+    (*outputWriter)(writeto, "DUMMYFILENAME",
+                    llx, lly, urx, ury, optsP, *splinesP,
+                    msgFunc, msgData);
+    if (newOpts)
+        at_output_opts_free(optsP);
+}
+
+
+
+void 
+at_splines_free(at_spline_list_array_type * const splines) {
+
+    free_spline_list_array(splines);
+    free(splines);
+}
diff --git a/converter/other/pamtosvg/autotrace.h b/converter/other/pamtosvg/autotrace.h
new file mode 100644
index 00000000..2ac81a08
--- /dev/null
+++ b/converter/other/pamtosvg/autotrace.h
@@ -0,0 +1,258 @@
+/* autotrace.h --- Autotrace API
+
+  Copyright (C) 2000, 2001, 2002 Martin Weber
+
+  The author can be contacted at <martweb@gmx.net>
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifndef AUTOTRACE_H
+#define AUTOTRACE_H
+
+#include <stdio.h>
+
+#include "point.h"
+#include "pm_c_util.h"
+#include "ppm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* ===================================================================== *
+ * Typedefs
+ * ===================================================================== */
+
+typedef struct _at_fitting_opts_type at_fitting_opts_type;
+typedef struct _at_input_opts_type   at_input_opts_type;
+typedef struct _at_output_opts_type  at_output_opts_type;
+typedef struct _at_bitmap_type at_bitmap_type;
+typedef struct _at_spline_type at_spline_type;
+typedef struct _at_spline_list_type at_spline_list_type;
+typedef struct _at_spline_list_array_type at_spline_list_array_type;
+
+/* Third degree is the highest we deal with.  */
+typedef enum _at_polynomial_degree
+{
+  AT_LINEARTYPE = 1, 
+  AT_QUADRATICTYPE = 2, 
+  AT_CUBICTYPE = 3, 
+  AT_PARALLELELLIPSETYPE = 4,
+  AT_ELLIPSETYPE = 5, 
+  AT_CIRCLETYPE = 6 
+  /* not the real number of points to define a
+     circle but to distinguish between a cubic spline */
+} at_polynomial_degree;
+
+/* A Bezier spline can be represented as four points in the real plane:
+   a starting point, ending point, and two control points.  The
+   curve always lies in the convex hull defined by the four points.  It
+   is also convenient to save the divergence of the spline from the
+   straight line defined by the endpoints.  */
+struct _at_spline_type
+{
+  float_coord v[4];	/* The control points.  */
+  at_polynomial_degree degree;
+  float linearity;
+};
+
+/* Each outline in a character is typically represented by many
+   splines.  So, here is a list structure for that:  */
+struct _at_spline_list_type
+{
+  at_spline_type *data;
+  unsigned length;
+  bool clockwise;
+  pixel color;
+  bool open;
+};
+
+/* Each character is in general made up of many outlines. So here is one
+   more list structure.  */
+struct _at_spline_list_array_type
+{
+  at_spline_list_type *data;
+  unsigned length;
+
+  /* splines bbox */
+  unsigned short height, width;
+  
+  /* the values for following members are inherited from 
+     at_fitting_opts_type */
+  bool backgroundSpec;
+  pixel background_color;
+  bool centerline;
+  bool preserve_width;
+  float width_weight_factor;
+
+};
+
+
+/* Fitting option.
+   With using at_fitting_opts_doc macro, the description of 
+   each option could be get. e.g. at_fitting_opts_doc(background_color) */
+struct _at_fitting_opts_type {
+    bool backgroundSpec;
+    pixel background_color;
+    float corner_always_threshold;
+    unsigned corner_surround;
+    float corner_threshold;
+    float error_threshold;
+    unsigned filter_iterations;
+    float line_reversion_threshold;
+    float line_threshold;
+    bool remove_adjacent_corners;
+    unsigned tangent_surround;
+    bool centerline;
+    bool preserve_width;
+    float width_weight_factor;
+};
+
+struct _at_output_opts_type
+{
+  int dpi;			/* DPI is used only in MIF output.*/
+};
+
+struct _at_bitmap_type
+{
+  unsigned short height;
+  unsigned short width;
+  unsigned char *bitmap;
+  unsigned int np;
+};
+
+typedef enum _at_msg_type
+{
+  AT_MSG_FATAL = 1,
+  AT_MSG_WARNING
+} at_msg_type;
+
+typedef
+void (* at_msg_func) (const char * const msg,
+                      at_msg_type  const msg_type,
+                      void *       const client_data);
+
+typedef 
+int (*at_output_write_func) (FILE *                          const file,
+                             const char *                    const name,
+                             int                             const llx,
+                             int                             const lly, 
+                             int                             const urx,
+                             int                             const ury,
+                             at_output_opts_type *           const opts,
+                             at_spline_list_array_type       const shape,
+                             at_msg_func                           msg_func, 
+                             void *                          const msg_data);
+
+/*
+ * Progress handler typedefs
+ * 0.0 <= percentage <= 1.0
+ */
+typedef
+void (*at_progress_func) (float const percentage,
+                           void *  const client_data);
+
+/*
+ * Test cancel
+ * Return true if auto-tracing should be stopped.
+ */
+typedef
+bool (*at_testcancel_func) (void * const client_data);
+
+/* ===================================================================== *
+ * Functions
+ * ===================================================================== */
+
+/* --------------------------------------------------------------------- *
+ * Fitting option related
+ *
+ * TODO: internal data access, copy
+ * --------------------------------------------------------------------- */
+at_fitting_opts_type * at_fitting_opts_new(void);
+at_fitting_opts_type * at_fitting_opts_copy (at_fitting_opts_type * original); 
+void at_fitting_opts_free(at_fitting_opts_type * opts);
+
+/* TODO: Gettextize */
+#define at_fitting_opts_doc(opt) _(at_doc__##opt)
+
+/* --------------------------------------------------------------------- *
+ * Output option related
+ *
+ * TODO: internal data access
+ * --------------------------------------------------------------------- */
+at_output_opts_type * at_output_opts_new(void);
+at_output_opts_type * at_output_opts_copy(at_output_opts_type * original);
+void at_output_opts_free(at_output_opts_type * opts);
+
+/* --------------------------------------------------------------------- *
+ * Spline related
+ *
+ * TODO: internal data access
+ * --------------------------------------------------------------------- */
+/* at_splines_new_full
+
+   args:
+
+   NOTIFY_PROGRESS is called repeatedly inside at_splines_new_full
+   to notify the progress of the execution. This might be useful for 
+   interactive applications. NOTIFY_PROGRESS is called following 
+   format:
+
+   NOTIFY_PROGRESS (percentage, progress_data);
+
+   test_cancel is called repeatedly inside at_splines_new_full
+   to test whether the execution is canceled or not.
+   If test_cancel returns TRUE, execution of at_splines_new_full
+   is stopped as soon as possible and returns NULL. If test_cancel 
+   returns FALSE, nothing happens. test_cancel  is called following
+   format:
+
+   TEST_CANCEL (testcancel_data);
+   
+   NULL is valid value for notify_progress and/or test_cancel if 
+   you don't need to know the progress of the execution and/or 
+   cancel the execution */ 
+
+at_spline_list_array_type * 
+at_splines_new_full(at_bitmap_type *       const bitmap,
+                    at_fitting_opts_type * const opts,
+                    at_msg_func                  msg_func, 
+                    void *                 const msg_data,
+                    at_progress_func             notify_progress,
+                    void *                 const progress_data,
+                    at_testcancel_func           test_cancel,
+                    void *                 const testcancel_data);
+
+void 
+at_splines_write(at_output_write_func                  output_writer,
+                 FILE *                          const writeto,
+                 at_output_opts_type *           const opts,
+                 at_spline_list_array_type *     const splines,
+                 at_msg_func                           msg_func,
+                 void *                          const msg_data);
+
+void
+at_splines_free(at_spline_list_array_type * const splines);
+
+
+/* --------------------------------------------------------------------- *
+ * Misc
+ * --------------------------------------------------------------------- */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
diff --git a/converter/other/pamtosvg/bitmap.c b/converter/other/pamtosvg/bitmap.c
new file mode 100644
index 00000000..1a00e748
--- /dev/null
+++ b/converter/other/pamtosvg/bitmap.c
@@ -0,0 +1,116 @@
+/* bitmap.c: operations on bitmaps. */
+
+#include <string.h>
+
+#include "mallocvar.h"
+
+#include "bitmap.h"
+
+at_bitmap_type *
+at_bitmap_new(unsigned short width,
+              unsigned short height,
+              unsigned int planes) {
+
+    at_bitmap_type * bitmap;
+
+    MALLOCVAR_NOFAIL(bitmap); 
+
+    *bitmap = at_bitmap_init(NULL, width, height, planes);
+
+    return bitmap;
+}
+
+
+
+at_bitmap_type *
+at_bitmap_copy(at_bitmap_type * src)
+{
+    at_bitmap_type * dist;
+    unsigned short width, height, planes;
+
+    width  = at_bitmap_get_width(src);
+    height = at_bitmap_get_height(src);
+    planes = at_bitmap_get_planes(src);
+    
+    dist = at_bitmap_new(width, height, planes);
+    memcpy(dist->bitmap, 
+           src->bitmap, 
+           width * height * planes * sizeof(unsigned char));
+    return dist;
+}
+
+
+
+at_bitmap_type
+at_bitmap_init(unsigned char * area,
+               unsigned short width,
+               unsigned short height,
+               unsigned int planes) {
+
+    at_bitmap_type bitmap;
+    
+    if (area)
+        bitmap.bitmap = area;
+    else {
+        if (width * height == 0)
+            bitmap.bitmap = NULL;
+        else {
+            MALLOCARRAY(bitmap.bitmap, width * height * planes);
+            if (bitmap.bitmap == NULL)
+                pm_error("Unable to allocate %u x %u x %u bitmap array",
+                         width, height, planes);
+            bzero(bitmap.bitmap,
+                  width * height * planes * sizeof(unsigned char));
+        }
+    }
+    
+    bitmap.width  = width;
+    bitmap.height = height;
+    bitmap.np     =  planes;
+
+    return bitmap;  
+}
+
+void 
+at_bitmap_free (at_bitmap_type * bitmap)
+{
+    free_bitmap (bitmap);
+    free(bitmap);
+}
+
+unsigned short
+at_bitmap_get_width (at_bitmap_type * bitmap)
+{
+    return bitmap->width;
+}
+
+unsigned short
+at_bitmap_get_height (at_bitmap_type * bitmap)
+{
+    return bitmap->height;
+}
+
+unsigned short
+at_bitmap_get_planes (at_bitmap_type * bitmap)
+{
+    return bitmap->np;
+}
+
+
+
+bitmap_type
+new_bitmap (unsigned short width, unsigned short height)
+{
+    return at_bitmap_init(NULL,width,height,1);
+}
+
+/* Free the storage that is allocated for a bitmap.  On the other hand,
+   the bitmap might not have any storage allocated for it if it is zero
+   in either dimension; in that case, don't free it.  */
+
+void
+free_bitmap (bitmap_type *b)
+{
+    if (b->bitmap != NULL)
+        free (b->bitmap);
+}
diff --git a/converter/other/pamtosvg/bitmap.h b/converter/other/pamtosvg/bitmap.h
new file mode 100644
index 00000000..7334f138
--- /dev/null
+++ b/converter/other/pamtosvg/bitmap.h
@@ -0,0 +1,53 @@
+/* bitmap.h: definition for a bitmap type.  No packing is done by
+   default; each pixel is represented by an entire byte.  Among other
+   things, this means the type can be used for both grayscale and binary
+   images. */
+
+#ifndef BITMAP_H
+#define BITMAP_H
+
+#include "autotrace.h"
+#include <stdio.h>
+
+/* at_ prefix removed version */
+typedef at_bitmap_type bitmap_type;
+#define BITMAP_PLANES(b)          AT_BITMAP_PLANES(b)
+#define BITMAP_BITS(b)            AT_BITMAP_BITS(b)  
+#define BITMAP_WIDTH(b)           AT_BITMAP_WIDTH(b)  
+#define BITMAP_HEIGHT(b)          AT_BITMAP_HEIGHT(b) 
+
+/* This is the pixel at [ROW,COL].  */
+#define BITMAP_PIXEL(b, row, col)					\
+  ((b).bitmap + ((row) * (b).width + (col)) * (b).np)
+
+#define BITMAP_VALID_PIXEL(b, row, col)					\
+   	((row) < (b).height && (col) < (b).width)
+
+/* Allocate storage for the bits, set them all to white, and return an
+   initialized structure.  */
+extern bitmap_type new_bitmap (unsigned short width, unsigned short height);
+
+/* Free that storage.  */
+extern void free_bitmap (bitmap_type *);
+
+
+at_bitmap_type * at_bitmap_new(unsigned short width,
+			       unsigned short height,
+			       unsigned int planes);
+at_bitmap_type * at_bitmap_copy(at_bitmap_type * src);
+
+/* We have to export functions that supports internal datum 
+   access. Such functions might be useful for 
+   at_bitmap_new user. */
+unsigned short at_bitmap_get_width (at_bitmap_type * bitmap);
+unsigned short at_bitmap_get_height (at_bitmap_type * bitmap);
+unsigned short at_bitmap_get_planes (at_bitmap_type * bitmap);
+void at_bitmap_free (at_bitmap_type * bitmap);
+
+at_bitmap_type
+at_bitmap_init(unsigned char * area,
+	       unsigned short width,
+	       unsigned short height,
+	       unsigned int planes);
+
+#endif /* not BITMAP_H */
diff --git a/converter/other/pamtosvg/curve.c b/converter/other/pamtosvg/curve.c
new file mode 100644
index 00000000..cc8aeb59
--- /dev/null
+++ b/converter/other/pamtosvg/curve.c
@@ -0,0 +1,314 @@
+/* curve.c: operations on the lists of pixels and lists of curves.
+
+   The code was partially derived from limn.
+
+   Copyright (C) 1992 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+#include "mallocvar.h"
+
+#include "logreport.h"
+#include "curve.h"
+
+
+static float_coord
+int_to_real_coord(pm_pixelcoord const int_coord) {
+/*----------------------------------------------------------------------------
+  Turn an integer point into a real one.
+-----------------------------------------------------------------------------*/
+    float_coord real_coord;
+
+    real_coord.x = int_coord.col;
+    real_coord.y = int_coord.row;
+    real_coord.z = 0.0;
+    
+    return real_coord;
+}
+
+
+
+/* Return an entirely empty curve.  */
+
+curve_type
+new_curve (void)
+{
+  curve_type curve;
+  MALLOCVAR_NOFAIL(curve);
+  curve->point_list = NULL;
+  CURVE_LENGTH (curve) = 0;
+  CURVE_CYCLIC (curve) = false;
+  CURVE_START_TANGENT (curve) = CURVE_END_TANGENT (curve) = NULL;
+  PREVIOUS_CURVE (curve) = NEXT_CURVE (curve) = NULL;
+
+  return curve;
+}
+
+
+/* Don't copy the points or tangents, but copy everything else.  */
+
+curve_type
+copy_most_of_curve (curve_type old_curve)
+{
+  curve_type curve = new_curve ();
+
+  CURVE_CYCLIC (curve) = CURVE_CYCLIC (old_curve);
+  PREVIOUS_CURVE (curve) = PREVIOUS_CURVE (old_curve);
+  NEXT_CURVE (curve) = NEXT_CURVE (old_curve);
+
+  return curve;
+}
+
+
+/* The length of CURVE will be zero if we ended up not being able to fit
+   it (which in turn implies a problem elsewhere in the program, but at
+   any rate, we shouldn't try here to free the nonexistent curve).  */
+
+void
+free_curve (curve_type curve)
+{
+  if (CURVE_LENGTH (curve) > 0)
+    free (curve->point_list);
+  if (CURVE_START_TANGENT (curve))
+    free (CURVE_START_TANGENT (curve));
+  if (CURVE_END_TANGENT (curve))
+    free (CURVE_END_TANGENT (curve));
+}
+
+
+void
+append_point(curve_type  const curve,
+             float_coord const coord) {
+
+    CURVE_LENGTH(curve)++;
+    REALLOCARRAY_NOFAIL(curve->point_list, CURVE_LENGTH(curve));
+    LAST_CURVE_POINT(curve) = coord;
+    /* The t value does not need to be set.  */
+}
+
+
+void
+append_pixel(curve_type    const curve,
+             pm_pixelcoord const coord) {
+
+    append_point(curve, int_to_real_coord(coord));
+}
+
+
+/* Print a curve in human-readable form.  It turns out we never care
+   about most of the points on the curve, and so it is pointless to
+   print them all out umpteen times.  What matters is that we have some
+   from the end and some from the beginning.  */
+
+#define NUM_TO_PRINT 3
+
+#define LOG_CURVE_POINT(c, p, print_t)					\
+  do									\
+    {									\
+      LOG2 ("(%.3f,%.3f)", CURVE_POINT (c, p).x, CURVE_POINT (c, p).y);	\
+      if (print_t)							\
+        LOG1 ("/%.2f", CURVE_T (c, p));					\
+    }									\
+  while (0)
+
+void
+log_curve (curve_type curve, bool print_t)
+{
+  unsigned this_point;
+
+  if (!log_file) return;
+
+  LOG1 ("curve id = %lx:\n", (unsigned long) curve);
+  LOG1 ("  length = %u.\n", CURVE_LENGTH (curve));
+  if (CURVE_CYCLIC (curve))
+    LOG ("  cyclic.\n");
+
+  /* It should suffice to check just one of the tangents for being null
+     -- either they both should be, or neither should be.  */
+  if (CURVE_START_TANGENT (curve) != NULL)
+    LOG4 ("  tangents = (%.3f,%.3f) & (%.3f,%.3f).\n",
+          CURVE_START_TANGENT (curve)->dx, CURVE_START_TANGENT (curve)->dy,
+          CURVE_END_TANGENT (curve)->dx, CURVE_END_TANGENT (curve)->dy);
+
+  LOG ("  ");
+
+  /* If the curve is short enough, don't use ellipses.  */
+  if (CURVE_LENGTH (curve) <= NUM_TO_PRINT * 2)
+    {
+      for (this_point = 0; this_point < CURVE_LENGTH (curve); this_point++)
+        {
+          LOG_CURVE_POINT (curve, this_point, print_t);
+          LOG (" ");
+
+          if (this_point != CURVE_LENGTH (curve) - 1
+              && (this_point + 1) % NUM_TO_PRINT == 0)
+            LOG ("\n  ");
+        }
+    }
+  else
+    {
+      for (this_point = 0;
+           this_point < NUM_TO_PRINT && this_point < CURVE_LENGTH (curve);
+           this_point++)
+        {
+          LOG_CURVE_POINT (curve, this_point, print_t);
+          LOG (" ");
+        }
+
+      LOG ("...\n   ...");
+
+      for (this_point = CURVE_LENGTH (curve) - NUM_TO_PRINT;
+           this_point < CURVE_LENGTH (curve);
+           this_point++)
+        {
+          LOG (" ");
+          LOG_CURVE_POINT (curve, this_point, print_t);
+        }
+    }
+
+  LOG (".\n");
+}
+
+
+/* Like `log_curve', but write the whole thing.  */
+
+void
+log_entire_curve (curve_type curve)
+{
+  unsigned this_point;
+
+  if (!log_file) return;
+
+  LOG1 ("curve id = %lx:\n", (unsigned long) curve);
+  LOG1 ("  length = %u.\n", CURVE_LENGTH (curve));
+  if (CURVE_CYCLIC (curve))
+    LOG ("  cyclic.\n");
+
+  /* It should suffice to check just one of the tangents for being null
+     -- either they both should be, or neither should be.  */
+  if (CURVE_START_TANGENT (curve) != NULL)
+    LOG4 ("  tangents = (%.3f,%.3f) & (%.3f,%.3f).\n",
+          CURVE_START_TANGENT (curve)->dx, CURVE_START_TANGENT (curve)->dy,
+          CURVE_END_TANGENT (curve)->dx, CURVE_END_TANGENT (curve)->dy);
+
+  LOG (" ");
+
+  for (this_point = 0; this_point < CURVE_LENGTH (curve); this_point++)
+    {
+      LOG (" ");
+      LOG_CURVE_POINT (curve, this_point, true);
+      /* Compiler warning `Condition is always true' can be ignored */
+    }
+
+  LOG (".\n");
+}
+
+
+/* Return an initialized but empty curve list.  */
+
+curve_list_type
+new_curve_list (void)
+{
+  curve_list_type curve_list;
+
+  curve_list.length = 0;
+  curve_list.data = NULL;
+
+  return curve_list;
+}
+
+
+/* Free a curve list and all the curves it contains.  */
+
+void
+free_curve_list(curve_list_type * const curve_list) {
+
+  unsigned this_curve;
+
+  for (this_curve = 0; this_curve < curve_list->length; this_curve++)
+    {
+      free_curve (curve_list->data[this_curve]);
+      free (curve_list->data[this_curve]);
+    }
+
+  /* If the character was empty, it won't have any curves.  */
+  if (curve_list->data != NULL)
+    free (curve_list->data);
+}
+
+
+/* Add an element to a curve list.  */
+
+void
+append_curve (curve_list_type *curve_list, curve_type curve)
+{
+  curve_list->length++;
+  REALLOCARRAY_NOFAIL(curve_list->data, curve_list->length);
+  curve_list->data[curve_list->length - 1] = curve; }
+
+
+/* Return an initialized but empty curve list array.  */
+
+curve_list_array_type
+new_curve_list_array (void)
+{
+  curve_list_array_type curve_list_array;
+
+  CURVE_LIST_ARRAY_LENGTH (curve_list_array) = 0;
+  curve_list_array.data = NULL;
+
+  return curve_list_array;
+}
+
+
+/* Free a curve list array and all the curve lists it contains.  */
+
+void
+free_curve_list_array(const curve_list_array_type * const curve_list_array,
+                      at_progress_func                    notify_progress, 
+                      void *                        const client_data) {
+
+  unsigned this_list;
+
+  for (this_list = 0; this_list < CURVE_LIST_ARRAY_LENGTH(*curve_list_array);
+       this_list++) {
+      if (notify_progress)
+          notify_progress(((float)this_list)/
+                          (CURVE_LIST_ARRAY_LENGTH(*curve_list_array) *
+                           (float)3.0)+(float)0.666 ,
+                          client_data);
+      free_curve_list(&CURVE_LIST_ARRAY_ELT (*curve_list_array, this_list));
+  }
+  
+  /* If the character was empty, it won't have any curves.  */
+  if (curve_list_array->data != NULL)
+      free(curve_list_array->data);
+}
+
+
+/* Add an element to a curve list array.  */
+
+void
+append_curve_list(curve_list_array_type * const curve_list_array,
+                  curve_list_type         const curve_list) {
+
+  CURVE_LIST_ARRAY_LENGTH (*curve_list_array)++;
+  REALLOCARRAY_NOFAIL(curve_list_array->data,
+                      CURVE_LIST_ARRAY_LENGTH(*curve_list_array));
+  LAST_CURVE_LIST_ARRAY_ELT (*curve_list_array) = curve_list;
+}
+
+
+
diff --git a/converter/other/pamtosvg/curve.h b/converter/other/pamtosvg/curve.h
new file mode 100644
index 00000000..db3cc682
--- /dev/null
+++ b/converter/other/pamtosvg/curve.h
@@ -0,0 +1,160 @@
+/* curve.h: data structures for the conversion from pixels to splines. */
+
+#ifndef CURVE_H
+#define CURVE_H
+
+#include "autotrace.h"
+#include "point.h"
+#include "vector.h"
+
+/* We are simultaneously manipulating two different representations of
+   the same outline: one based on (x,y) positions in the plane, and one
+   based on parametric splines.  (We are trying to match the latter to
+   the former.)  Although the original (x,y)'s are pixel positions,
+   i.e., integers, after filtering they are reals.  */
+
+typedef struct {
+    float_coord coord;
+    float       t;
+} point_type;
+
+
+
+struct curve {
+/*----------------------------------------------------------------------------
+  An ordered list of contiguous points in the raster, with no corners
+  in it.  I.e. something that could reasonably be fit to a spline.
+-----------------------------------------------------------------------------*/
+    point_type *   point_list;
+    unsigned       length;
+    bool           cyclic;
+    vector_type *  start_tangent;
+    vector_type *  end_tangent;
+    struct curve * previous;
+    struct curve * next;
+};
+
+typedef struct curve * curve_type;
+
+/* Get at the coordinates and the t values.  */
+#define CURVE_POINT(c, n) ((c)->point_list[n].coord)
+#define LAST_CURVE_POINT(c) ((c)->point_list[(c)->length-1].coord)
+#define CURVE_T(c, n) ((c)->point_list[n].t)
+#define LAST_CURVE_T(c) ((c)->point_list[(c)->length-1].t)
+
+/* This is the length of `point_list'.  */
+#define CURVE_LENGTH(c)  ((c)->length)
+
+/* A curve is ``cyclic'' if it didn't have any corners, after all, so
+   the last point is adjacent to the first.  */
+#define CURVE_CYCLIC(c)  ((c)->cyclic)
+
+/* If the curve is cyclic, the next and previous points should wrap
+   around; otherwise, if we get to the end, we return CURVE_LENGTH and
+   -1, respectively.  */
+#define CURVE_NEXT(c, n)						\
+  ((n) + 1 >= CURVE_LENGTH (c)						\
+  ? CURVE_CYCLIC (c) ? ((n) + 1) % CURVE_LENGTH (c) : CURVE_LENGTH (c)	\
+  : (n) + 1)
+#define CURVE_PREV(c, n)						\
+  ((signed int) (n) - 1 < 0							\
+  ? CURVE_CYCLIC (c) ? (signed int) CURVE_LENGTH (c) + (signed int) (n) - 1 : -1\
+  : (signed int) (n) - 1)
+
+/* The tangents at the endpoints are computed using the neighboring curves.  */
+#define CURVE_START_TANGENT(c) ((c)->start_tangent)
+#define CURVE_END_TANGENT(c) ((c)->end_tangent)
+#define PREVIOUS_CURVE(c) ((c)->previous)
+#define NEXT_CURVE(c) ((c)->next)
+
+
+/* Return an entirely empty curve.  */
+extern curve_type new_curve (void);
+
+/* Return a curve the same as C, except without any points.  */
+extern curve_type copy_most_of_curve (curve_type c);
+
+/* Free the memory C uses.  */
+extern void free_curve (curve_type c);
+
+/* Append the point P to the end of C's list.  */
+void
+append_pixel(curve_type    const c,
+             pm_pixelcoord const p);
+
+/* Like `append_pixel', for a point in real coordinates.  */
+extern void append_point (curve_type c, float_coord p);
+
+/* Write some or all, respectively, of the curve C in human-readable
+   form to the log file, if logging is enabled.  */
+extern void log_curve (curve_type c, bool print_t);
+extern void log_entire_curve (curve_type c);
+
+/* Display the curve C online, if displaying is enabled.  */
+extern void display_curve (curve_type);
+
+
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   An ordered list of contiguous curves of a particular color.
+-----------------------------------------------------------------------------*/
+    curve_type * data;
+    unsigned     length;
+    bool         clockwise;
+    pixel        color;
+    bool         open;
+        /* The curve list does not form a closed shape;  i.e. the last
+           curve doesn't end where the first one starts.
+        */
+} curve_list_type;
+
+/* Number of curves in the list.  */
+#define CURVE_LIST_LENGTH(c_l)  ((c_l).length)
+#define CURVE_LIST_EMPTY(c_l) ((c_l).length == 0)
+
+/* Access the individual curves.  */
+#define CURVE_LIST_ELT(c_l, n) ((c_l).data[n])
+#define LAST_CURVE_LIST_ELT(c_l) ((c_l).data[CURVE_LIST_LENGTH (c_l) - 1])
+
+/* Says whether the outline that this curve list represents moves
+   clockwise or counterclockwise.  */
+#define CURVE_LIST_CLOCKWISE(c_l) ((c_l).clockwise)
+
+
+extern curve_list_type new_curve_list (void);
+
+void
+free_curve_list(curve_list_type * const curve_list);
+
+extern void append_curve (curve_list_type *, curve_type);
+
+/* And a character is a list of outlines.  I named this
+   `curve_list_array_type' because `curve_list_list_type' seemed pretty
+   monstrous.  */
+typedef struct
+{
+  curve_list_type *data;
+  unsigned length;
+} curve_list_array_type;
+
+/* Turns out we can use the same definitions for lists of lists as for
+   just lists.  But we define the usual names, just in case.  */
+#define CURVE_LIST_ARRAY_LENGTH CURVE_LIST_LENGTH
+#define CURVE_LIST_ARRAY_ELT CURVE_LIST_ELT
+#define LAST_CURVE_LIST_ARRAY_ELT LAST_CURVE_LIST_ELT
+
+curve_list_array_type
+new_curve_list_array(void);
+
+void
+free_curve_list_array(const curve_list_array_type * const curve_list_array,
+                      at_progress_func                    notify_progress, 
+                      void *                        const client_data);
+
+void
+append_curve_list(curve_list_array_type * const curve_list_array,
+                  curve_list_type         const curve_list);
+
+#endif
+
diff --git a/converter/other/pamtosvg/epsilon-equal.c b/converter/other/pamtosvg/epsilon-equal.c
new file mode 100644
index 00000000..46d630c5
--- /dev/null
+++ b/converter/other/pamtosvg/epsilon-equal.c
@@ -0,0 +1,19 @@
+/* epsilon-equal.c: define a error resist compare. */
+
+#include <math.h>
+
+#include "epsilon-equal.h"
+
+/* Numerical errors sometimes make a floating point number just slightly
+   larger or smaller than its true value.  When it matters, we need to
+   compare with some tolerance, REAL_EPSILON, defined in kbase.h.  */
+
+bool
+epsilon_equal(float const v1,
+              float const v2) {
+
+    return
+        v1 == v2		       /* Usually they'll be exactly equal, anyway.  */
+        || fabs(v1 - v2) <= REAL_EPSILON;
+}
+
diff --git a/converter/other/pamtosvg/epsilon-equal.h b/converter/other/pamtosvg/epsilon-equal.h
new file mode 100644
index 00000000..fa0437cd
--- /dev/null
+++ b/converter/other/pamtosvg/epsilon-equal.h
@@ -0,0 +1,20 @@
+/* epsilon-equal.h: define an error resist compare. */
+
+#ifndef EPSILON_EQUAL_H
+#define EPSILON_EQUAL_H
+
+#include "pm_c_util.h"
+
+/* Says whether V1 and V2 are within REAL_EPSILON of each other.
+   Fixed-point arithmetic would be better, to guarantee machine
+   independence, but it's so much more painful to work with.  The value
+   here is smaller than can be represented in either a `fix_word' or a
+   `scaled_num', so more precision than this will be lost when we
+   output, anyway.  */
+bool epsilon_equal(float const v1,
+                   float const v2);
+
+#define REAL_EPSILON 0.00001
+
+#endif /* not EPSILON_EQUAL_H */
+
diff --git a/converter/other/pamtosvg/exception.c b/converter/other/pamtosvg/exception.c
new file mode 100644
index 00000000..43761936
--- /dev/null
+++ b/converter/other/pamtosvg/exception.c
@@ -0,0 +1,55 @@
+
+#include "exception.h"
+
+at_exception_type
+at_exception_new(at_msg_func       client_func,
+                 void *      const client_data) {
+
+    at_exception_type e;
+
+    e.msg_type = 0;
+    e.client_func = client_func;
+    e.client_data = client_data;
+    
+    return e;
+}
+
+
+
+bool
+at_exception_got_fatal(at_exception_type * const exception) {
+
+    return (exception->msg_type == AT_MSG_FATAL);
+}
+
+
+
+void
+at_exception_fatal(at_exception_type * const exception,
+                   const char *        const message) {
+
+    if (exception) {
+        exception->msg_type = AT_MSG_FATAL;
+        if (exception->client_func) {
+            exception->client_func(message, 
+                                   AT_MSG_FATAL,
+                                   exception->client_data);
+        }
+    }
+}
+
+
+
+void
+at_exception_warning(at_exception_type * const exception,
+                     const char *        const message) {
+
+    if (exception) {
+        exception->msg_type = AT_MSG_WARNING;
+        if (exception->client_func) {
+            exception->client_func(message, 
+                                   AT_MSG_WARNING,
+                                   exception->client_data);
+        }
+    }
+}
diff --git a/converter/other/pamtosvg/exception.h b/converter/other/pamtosvg/exception.h
new file mode 100644
index 00000000..113f65e6
--- /dev/null
+++ b/converter/other/pamtosvg/exception.h
@@ -0,0 +1,43 @@
+/* exception.h: facility to handle error in autotrace */
+
+#ifndef AT_EXCEPTION_H
+#define AT_EXCEPTION_H 
+
+#include "autotrace.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Protocol:
+   If a function raises a FATAL(including propagation), 
+   the function must release resources allocated by the 
+   function itself.
+*/
+typedef struct _at_exception_type at_exception_type;
+struct _at_exception_type {
+    at_msg_type msg_type;
+    at_msg_func client_func;
+    void **     client_data;
+};
+
+at_exception_type
+at_exception_new(at_msg_func       client_func,
+                 void *      const client_data);
+
+bool
+at_exception_got_fatal(at_exception_type * const exception);
+
+void
+at_exception_fatal(at_exception_type * const exception,
+                   const char *        const message);
+
+void
+at_exception_warning(at_exception_type * const exception,
+                     const char *        const message);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/converter/other/pamtosvg/fit.c b/converter/other/pamtosvg/fit.c
new file mode 100644
index 00000000..bcda033f
--- /dev/null
+++ b/converter/other/pamtosvg/fit.c
@@ -0,0 +1,1923 @@
+/* fit.c: turn a bitmap representation of a curve into a list of splines.
+    Some of the ideas, but not the code, comes from the Phoenix thesis.
+   See README for the reference.
+
+   The code was partially derived from limn.
+
+   Copyright (C) 1992 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+#include <math.h>
+#include <limits.h>
+#include <float.h>
+#include <string.h>
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+
+#include "autotrace.h"
+#include "fit.h"
+#include "message.h"
+#include "logreport.h"
+#include "spline.h"
+#include "vector.h"
+#include "curve.h"
+#include "pxl-outline.h"
+#include "epsilon-equal.h"
+
+#define CUBE(x) ((x) * (x) * (x))
+
+/* We need to manipulate lists of array indices.  */
+
+typedef struct index_list
+{
+  unsigned *data;
+  unsigned length;
+} index_list_type;
+
+/* The usual accessor macros.  */
+#define GET_INDEX(i_l, n)  ((i_l).data[n])
+#define INDEX_LIST_LENGTH(i_l)  ((i_l).length)
+#define GET_LAST_INDEX(i_l)  ((i_l).data[INDEX_LIST_LENGTH (i_l) - 1])
+
+static void append_index (index_list_type *, unsigned);
+static void free_index_list (index_list_type *);
+static index_list_type new_index_list (void);
+static void remove_adjacent_corners (index_list_type *, unsigned, bool,
+                     at_exception_type * exception);
+static void filter (curve_type, fitting_opts_type *);
+static void find_vectors
+  (unsigned, pixel_outline_type, vector_type *, vector_type *, unsigned);
+static float find_error (curve_type, spline_type, unsigned *,
+               at_exception_type * exception);
+static vector_type find_half_tangent (curve_type, bool start, unsigned *, unsigned);
+static void find_tangent (curve_type, bool, bool, unsigned);
+static void remove_knee_points (curve_type, bool);
+static void set_initial_parameter_values (curve_type);
+static float distance (float_coord, float_coord);
+
+
+static pm_pixelcoord
+real_to_int_coord(float_coord const real_coord) {
+/*----------------------------------------------------------------------------
+  Turn an real point into a integer one.
+-----------------------------------------------------------------------------*/
+
+    pm_pixelcoord int_coord;
+
+    int_coord.col = ROUND(real_coord.x);
+    int_coord.row = ROUND(real_coord.y);
+    
+    return int_coord;
+}
+
+
+static void
+appendCorner(index_list_type *  const cornerListP,
+             unsigned int       const pixelSeq,
+             pixel_outline_type const outline,
+             float              const angle,
+             char               const logType) {
+
+    pm_pixelcoord const coord = O_COORDINATE(outline, pixelSeq);
+
+    append_index(cornerListP, pixelSeq);
+    LOG4(" (%d,%d)%c%.3f", coord.col, coord.row, logType, angle);
+}
+
+
+
+static void
+lookAheadForBetterCorner(pixel_outline_type  const outline,
+                         unsigned int        const basePixelSeq,
+                         float               const baseCornerAngle,
+                         unsigned int        const cornerSurround,
+                         unsigned int        const cornerAlwaysThreshold,
+                         unsigned int *      const highestExaminedP,
+                         float *             const bestCornerAngleP,
+                         unsigned int *      const bestCornerIndexP,
+                         index_list_type *   const equallyGoodListP,
+                         index_list_type *   const cornerListP,
+                         at_exception_type * const exceptionP) {
+/*----------------------------------------------------------------------------
+   'basePixelSeq' is the sequence position within 'outline' of a pixel
+   that has a sufficiently small angle (to wit 'baseCornerAngle') to
+   be a corner.  We look ahead in 'outline' for an even better one.
+   We'll look up to 'cornerSurround' pixels ahead.
+
+   We return the pixel sequence of the best corner we find (which could
+   be the base) as *bestCornerIndexP.  Its angle is *bestCornerAngleP.
+
+   We return as *highestExaminedP the pixel sequence of the last pixel
+   we examined in our search (Caller can use this information to avoid
+   examining them again).
+
+   And we have this really dirty side effect: If we encounter any
+   corner whose angle is less than 'cornerAlwaysThreshold', we add
+   that to the list *cornerListP along the way.
+-----------------------------------------------------------------------------*/
+    float bestCornerAngle;
+    unsigned bestCornerIndex;
+    index_list_type equallyGoodList;
+    unsigned int q;
+    unsigned int i;
+
+    bestCornerIndex = basePixelSeq;     /* initial assumption */
+    bestCornerAngle = baseCornerAngle;    /* initial assumption */
+    
+    equallyGoodList = new_index_list();
+    
+    q = basePixelSeq;
+    i = basePixelSeq + 1;  /* Start with the next pixel */
+    
+    while (i < bestCornerIndex + cornerSurround &&
+           i < O_LENGTH(outline) &&
+           !at_exception_got_fatal(exceptionP)) {
+
+        vector_type inVector, outVector;
+        float cornerAngle;
+        
+        /* Check the angle.  */
+
+        q = i % O_LENGTH(outline);
+        find_vectors(q, outline, &inVector, &outVector, cornerSurround);
+        cornerAngle = Vangle(inVector, outVector, exceptionP);
+        if (!at_exception_got_fatal(exceptionP)) {
+            /* Perhaps the angle is sufficiently small that we want to
+               consider this a corner, even if it's not the best
+               (unless we've already wrapped around in the search, in
+               which case we have already added the corner, and we
+               don't want to add it again).
+            */
+            if (cornerAngle <= cornerAlwaysThreshold && q >= basePixelSeq)
+                appendCorner(cornerListP, q, outline, cornerAngle, '\\');
+
+            if (epsilon_equal(cornerAngle, bestCornerAngle))
+                append_index(&equallyGoodList, q);
+            else if (cornerAngle < bestCornerAngle) {
+                bestCornerAngle = cornerAngle;
+                /* We want to check `cornerSurround' pixels beyond the
+                   new best corner.
+                */
+                i = bestCornerIndex = q;
+                free_index_list(&equallyGoodList);
+                equallyGoodList = new_index_list();
+            }
+            ++i;
+        }
+    }
+    *bestCornerAngleP = bestCornerAngle;
+    *bestCornerIndexP = bestCornerIndex;
+    *equallyGoodListP = equallyGoodList;
+    *highestExaminedP = q;
+}
+
+
+
+static void
+establishCornerSearchLimits(pixel_outline_type  const outline,
+                            fitting_opts_type * const fittingOptsP,
+                            unsigned int *      const firstP,
+                            unsigned int *      const lastP) {
+/*----------------------------------------------------------------------------
+   Determine where in the outline 'outline' we should look for corners.
+-----------------------------------------------------------------------------*/
+    assert(O_LENGTH(outline) >= 1);
+    assert(O_LENGTH(outline) - 1 >= fittingOptsP->corner_surround);
+
+    *firstP = 0;
+    *lastP  = O_LENGTH(outline) - 1;
+    if (outline.open) {
+        *firstP += fittingOptsP->corner_surround;
+        *lastP  -= fittingOptsP->corner_surround;
+    }
+}
+
+
+
+static void
+removeAdjacent(index_list_type *   const cornerListP,
+               pixel_outline_type  const outline,
+               fitting_opts_type * const fittingOptsP,
+               at_exception_type * const exception) {
+               
+    /* We never want two corners next to each other, since the
+       only way to fit such a ``curve'' would be with a straight
+       line, which usually interrupts the continuity dreadfully.
+    */
+
+    if (INDEX_LIST_LENGTH(*cornerListP) > 0)
+        remove_adjacent_corners(
+            cornerListP,
+            O_LENGTH(outline) - (outline.open ? 2 : 1),
+            fittingOptsP->remove_adjacent_corners,
+            exception);
+}
+
+
+
+static index_list_type
+find_corners(pixel_outline_type  const outline,
+             fitting_opts_type * const fittingOptsP,
+             at_exception_type * const exceptionP) {
+
+    /* We consider a point to be a corner if (1) the angle defined by
+       the `corner_surround' points coming into it and going out from
+       it is less than `corner_threshold' degrees, and no point within
+       `corner_surround' points has a smaller angle; or (2) the angle
+       is less than `corner_always_threshold' degrees.
+    */
+    unsigned int p;
+    unsigned int firstPixelSeq, lastPixelSeq;
+    index_list_type cornerList;
+
+    cornerList = new_index_list();
+
+    if (O_LENGTH(outline) <= fittingOptsP->corner_surround * 2 + 1)
+        return cornerList;
+
+    establishCornerSearchLimits(outline, fittingOptsP,
+                                &firstPixelSeq, &lastPixelSeq);
+    
+    /* Consider each pixel on the outline in turn.  */
+    for (p = firstPixelSeq; p <= lastPixelSeq;) {
+        vector_type inVector, outVector;
+        float cornerAngle;
+
+        /* Check if the angle is small enough.  */
+        find_vectors(p, outline, &inVector, &outVector,
+                     fittingOptsP->corner_surround);
+        cornerAngle = Vangle(inVector, outVector, exceptionP);
+        if (at_exception_got_fatal(exceptionP))
+            goto cleanup;
+
+        if (fabs(cornerAngle) <= fittingOptsP->corner_threshold) {
+            /* We want to keep looking, instead of just appending the
+               first pixel we find with a small enough angle, since there
+               might be another corner within `corner_surround' pixels, with
+               a smaller angle.  If that is the case, we want that one.
+
+               If we come across a corner that is just as good as the
+               best one, we should make it a corner, too.  This
+               happens, for example, at the points on the `W' in some
+               typefaces, where the "points" are flat.
+            */
+            float bestCornerAngle;
+            unsigned bestCornerIndex;
+            index_list_type equallyGoodList;
+            unsigned int q;
+
+            if (cornerAngle <= fittingOptsP->corner_always_threshold)
+                /* The angle is sufficiently small that we want to
+                   consider this a corner, even if it's not the best.
+                */
+                appendCorner(&cornerList, p, outline, cornerAngle, '\\');
+
+            lookAheadForBetterCorner(outline, p, cornerAngle,
+                                     fittingOptsP->corner_surround,
+                                     fittingOptsP->corner_always_threshold,
+                                     &q,
+                                     &bestCornerAngle, &bestCornerIndex,
+                                     &equallyGoodList,
+                                     &cornerList,
+                                     exceptionP);
+
+            if (at_exception_got_fatal(exceptionP))
+                goto cleanup;
+
+            /* `q' is the index of the last point lookAhead checked.
+               He added the corner if `bestCornerAngle' is less than
+               `corner_always_threshold'.  If we've wrapped around, we
+               added the corner on the first pass.  Otherwise, we add
+               the corner now.
+            */
+            if (bestCornerAngle > fittingOptsP->corner_always_threshold
+                && bestCornerIndex >= p) {
+
+                unsigned int j;
+                
+                appendCorner(&cornerList, bestCornerIndex,
+                             outline, bestCornerAngle, '/');
+                
+                for (j = 0; j < INDEX_LIST_LENGTH (equallyGoodList); ++j)
+                    appendCorner(&cornerList, GET_INDEX(equallyGoodList, j),
+                                 outline, bestCornerAngle, '@');
+            }
+            free_index_list(&equallyGoodList);
+
+            /* If we wrapped around in our search, we're done;
+               otherwise, we move on to the pixel after the highest
+               one we just checked.
+            */
+            p = (q < p) ? O_LENGTH(outline) : q + 1;
+        } else
+            ++p;
+    }
+    removeAdjacent(&cornerList, outline, fittingOptsP, exceptionP);
+
+cleanup:  
+    return cornerList;
+}
+
+
+
+static void
+makeOutlineOneCurve(pixel_outline_type const outline,
+                    curve_list_type *  const curveListP) {
+/*----------------------------------------------------------------------------
+   Add to *curveListP a single curve that represents the outline 'outline'.
+-----------------------------------------------------------------------------*/
+    curve_type curve;
+    unsigned int pixelSeq;
+
+    curve = new_curve();
+    
+    for (pixelSeq = 0; pixelSeq < O_LENGTH(outline); ++pixelSeq)
+        append_pixel(curve, O_COORDINATE(outline, pixelSeq));
+    
+    if (curveListP->open)
+        CURVE_CYCLIC(curve) = false;
+    else
+        CURVE_CYCLIC(curve) = true;
+    
+    /* Make it a one-curve cycle */
+    NEXT_CURVE(curve)     = curve;
+    PREVIOUS_CURVE(curve) = curve;
+
+    append_curve(curveListP, curve);
+}
+
+
+
+static void
+addCurveStartingAtCorner(pixel_outline_type const outline,
+                         index_list_type    const cornerList,
+                         unsigned int       const cornerSeq,
+                         curve_list_type *  const curveListP) {
+
+    unsigned int const cornerPixelSeq = GET_INDEX(cornerList, cornerSeq);
+    
+    unsigned int lastPixelSeq;
+    curve_type curve;
+    unsigned int pixelSeq;
+    
+    if (cornerSeq + 1 >= cornerList.length)
+        /* No more corners, so we go through the end of the outline. */
+        lastPixelSeq = O_LENGTH(outline) - 1;
+    else
+        /* Go through the next corner */
+        lastPixelSeq = GET_INDEX(cornerList, cornerSeq + 1);
+    
+    curve = new_curve();
+
+    for (pixelSeq = cornerPixelSeq; pixelSeq <= lastPixelSeq; ++pixelSeq)
+        append_pixel(curve, O_COORDINATE(outline, pixelSeq));
+    
+    {
+        /* Add curve to end of chain */
+        if (!CURVE_LIST_EMPTY(*curveListP)) {
+            curve_type const previousCurve = LAST_CURVE_LIST_ELT(*curveListP);
+            NEXT_CURVE(previousCurve) = curve;
+            PREVIOUS_CURVE(curve)     = previousCurve;
+        }
+    }
+    append_curve(curveListP, curve);
+}
+
+
+
+static void
+divideOutlineWithCorners(pixel_outline_type const outline,
+                         index_list_type    const cornerList,
+                         curve_list_type *  const curveListP) {
+/*----------------------------------------------------------------------------
+   Divide the outline 'outline' into curves at the corner points
+   'cornerList' and add each curve to *curveListP.
+
+   Each curve contains the corners at each end.
+
+   The last curve is special.  It consists of the pixels (inclusive)
+   between the last corner and the end of the outline, and the
+   beginning of the outline and the first corner.
+
+   We link the curves in a chain.  If the outline (and therefore the
+   curve list) is closed, the chain is a cycle of all the curves.  If
+   it is open, the chain is a linear chain of all the curves except
+   the last one (the one that goes from the last corner to the first
+   corner).
+
+   Assume there is at least one corner.
+-----------------------------------------------------------------------------*/
+    unsigned int const firstCurveSeq = CURVE_LIST_LENGTH(*curveListP);
+        /* Index in curve list of the first curve we add */
+    unsigned int cornerSeq;
+
+    assert(cornerList.length > 0);
+
+    if (outline.open) {
+        /* Start with a curve that contains the point up to the first
+           corner
+        */
+        curve_type curve;
+        unsigned int pixelSeq;
+        
+        curve = new_curve();
+
+        for (pixelSeq = 0; pixelSeq <= GET_INDEX(cornerList, 0); ++pixelSeq)
+            append_pixel(curve, O_COORDINATE(outline, pixelSeq));
+
+        append_curve(curveListP, curve);
+    } else
+        /* We'll pick up the pixels before the first corner at the end */
+
+    /* Add to the list a curve that starts at each corner and goes
+       through the following corner, or the end of the outline if
+       there is no following corner.  Do it in order of the corners.
+    */
+    for (cornerSeq = 0; cornerSeq < cornerList.length; ++cornerSeq)
+        addCurveStartingAtCorner(outline, cornerList, cornerSeq, curveListP);
+
+    if (!outline.open) {
+        /* Come around to the start of the curve list -- add the pixels
+           before the first corner to the last curve, and chain the last
+           curve to the first one.
+        */
+        curve_type const firstCurve =
+            CURVE_LIST_ELT(*curveListP, firstCurveSeq);
+        curve_type const lastCurve  =
+            LAST_CURVE_LIST_ELT(*curveListP);
+
+        unsigned int pixelSeq;
+
+        for (pixelSeq = 0; pixelSeq <= GET_INDEX(cornerList, 0); ++pixelSeq)
+            append_pixel(lastCurve, O_COORDINATE(outline, pixelSeq));
+
+        NEXT_CURVE(lastCurve)      = firstCurve;
+        PREVIOUS_CURVE(firstCurve) = lastCurve;
+    }
+}
+
+
+
+static curve_list_array_type
+split_at_corners(pixel_outline_list_type const pixel_list,
+                 fitting_opts_type *     const fitting_opts,
+                 at_exception_type *     const exception) {
+/*----------------------------------------------------------------------------
+   Find the corners in PIXEL_LIST, the list of points.  (Presumably we
+   can't fit a single spline around a corner.)  The general strategy
+   is to look through all the points, remembering which we want to
+   consider corners.  Then go through that list, producing the
+   curve_list.  This is dictated by the fact that PIXEL_LIST does not
+   necessarily start on a corner---it just starts at the character's
+   first outline pixel, going left-to-right, top-to-bottom.  But we
+   want all our splines to start and end on real corners.
+
+   For example, consider the top of a capital `C' (this is in cmss20):
+                     x
+                     ***********
+                  ******************
+
+   PIXEL_LIST will start at the pixel below the `x'.  If we considered
+   this pixel a corner, we would wind up matching a very small segment
+   from there to the end of the line, probably as a straight line, which
+   is certainly not what we want.
+
+   PIXEL_LIST has one element for each closed outline on the character.
+   To preserve this information, we return an array of curve_lists, one
+   element (which in turn consists of several curves, one between each
+   pair of corners) for each element in PIXEL_LIST.
+-----------------------------------------------------------------------------*/
+    unsigned outlineSeq;
+    curve_list_array_type curve_array;
+
+    curve_array = new_curve_list_array();
+
+    LOG("\nFinding corners:\n");
+
+    for (outlineSeq = 0;
+         outlineSeq < O_LIST_LENGTH(pixel_list);
+         ++outlineSeq) {
+
+        pixel_outline_type const outline =
+            O_LIST_OUTLINE(pixel_list, outlineSeq);
+
+        index_list_type corner_list;
+        curve_list_type curve_list;
+
+        curve_list = new_curve_list();
+
+        CURVE_LIST_CLOCKWISE(curve_list) = O_CLOCKWISE(outline);
+        curve_list.color = outline.color;
+        curve_list.open  = outline.open;
+
+        LOG1("#%u:", outlineSeq);
+        
+        /* If the outline does not have enough points, we can't do
+           anything.  The endpoints of the outlines are automatically
+           corners.  We need at least `corner_surround' more pixels on
+           either side of a point before it is conceivable that we might
+           want another corner.
+        */
+        if (O_LENGTH(outline) > fitting_opts->corner_surround * 2 + 2)
+            corner_list = find_corners(outline, fitting_opts, exception);
+
+        else {
+            int const surround = (O_LENGTH(outline) - 3) / 2;
+            if (surround >= 2) {
+                unsigned int const save_corner_surround =
+                    fitting_opts->corner_surround;
+                fitting_opts->corner_surround = surround;
+                corner_list = find_corners(outline, fitting_opts, exception);
+                fitting_opts->corner_surround = save_corner_surround;
+            } else {
+                corner_list.length = 0;
+                corner_list.data = NULL;
+            }
+        }
+
+        if (corner_list.length == 0)
+            /* No corners.  Use all of the pixel outline as the one curve. */
+            makeOutlineOneCurve(outline, &curve_list);
+        else
+            divideOutlineWithCorners(outline, corner_list, &curve_list);
+        
+        LOG1(" [%u].\n", corner_list.length);
+        free_index_list(&corner_list);
+
+        /* And now add the just-completed curve list to the array.  */
+        append_curve_list(&curve_array, curve_list);
+    }
+
+    return curve_array;
+}
+
+
+
+static void
+removeKnees(curve_list_type const curveList) {
+/*----------------------------------------------------------------------------
+  Remove the extraneous ``knee'' points before filtering.  Since the
+  corners have already been found, we don't need to worry about
+  removing a point that should be a corner.
+-----------------------------------------------------------------------------*/
+    unsigned int curveSeq;
+    
+    LOG("\nRemoving knees:\n");
+    for (curveSeq = 0; curveSeq < curveList.length; ++curveSeq) {
+        LOG1("#%u:", curveSeq);
+        remove_knee_points(CURVE_LIST_ELT(curveList, curveSeq),
+                           CURVE_LIST_CLOCKWISE(curveList));
+    }
+}
+    
+
+
+static void
+computePointWeights(curve_list_type     const curveList,
+                    fitting_opts_type * const fittingOptsP,
+                    distance_map_type * const distP) {
+
+    unsigned int const height = distP->height;
+
+    unsigned int curveSeq;
+    
+    for (curveSeq = 0; curveSeq < curveList.length; ++curveSeq) {
+        unsigned pointSeq;
+        curve_type const curve = CURVE_LIST_ELT(curveList, curveSeq);
+        for (pointSeq = 0; pointSeq < CURVE_LENGTH(curve); ++pointSeq) {
+            float_coord * const coordP = &CURVE_POINT(curve, pointSeq);
+            unsigned int x = coordP->x;
+            unsigned int y = height - (unsigned int)coordP->y - 1;
+            
+            float width, w;
+
+            /* Each (x, y) is a point on the skeleton of the curve, which
+               might be offset from the true centerline, where the width
+               is maximal.  Therefore, use as the local line width the
+               maximum distance over the neighborhood of (x, y). 
+            */
+            width = distP->d[y][x];  /* initial value */
+            if (y - 1 >= 0) {
+                if ((w = distP->d[y-1][x]) > width)
+                    width = w;
+                if (x - 1 >= 0) {
+                    if ((w = distP->d[y][x-1]) > width)
+                        width = w;
+                    if ((w = distP->d[y-1][x-1]) > width)
+                        width = w;
+                }
+                if (x + 1 < distP->width) {
+                    if ((w = distP->d[y][x+1]) > width)
+                        width = w;
+                    if ((w = distP->d[y-1][x+1]) > width)
+                        width = w;
+                }
+            }
+            if (y + 1 < height) {
+                if ((w = distP->d[y+1][x]) > width)
+                    width = w;
+                if (x - 1 >= 0 && (w = distP->d[y+1][x-1]) > width)
+                    width = w;
+                if (x + 1 < distP->width && (w = distP->d[y+1][x+1]) > width)
+                    width = w;
+            }
+            coordP->z = width * (fittingOptsP->width_weight_factor);
+        }
+    }
+}
+
+
+
+static void
+filterCurves(curve_list_type     const curveList,
+             fitting_opts_type * const fittingOptsP) {
+             
+    unsigned int curveSeq;
+
+    LOG("\nFiltering curves:\n");
+
+    for (curveSeq = 0; curveSeq < curveList.length; ++curveSeq) {
+        LOG1("#%u: ", curveSeq);
+        filter(CURVE_LIST_ELT(curveList, curveSeq), fittingOptsP);
+    }
+}
+
+
+
+static void
+logSplinesForCurve(unsigned int     const curveSeq,
+                   spline_list_type const curveSplines) {
+
+    unsigned int splineSeq;
+
+    LOG1("Fitted splines for curve #%u:\n", curveSeq);
+    for (splineSeq = 0;
+         splineSeq < SPLINE_LIST_LENGTH(curveSplines);
+         ++splineSeq) {
+        LOG1("  %u: ", splineSeq);
+        if (log_file)
+            print_spline(log_file, SPLINE_LIST_ELT(curveSplines, splineSeq));
+    }
+}     
+       
+
+
+static void
+change_bad_lines(spline_list_type *        const spline_list,
+                 const fitting_opts_type * const fitting_opts) {
+
+/* Unfortunately, we cannot tell in isolation whether a given spline
+   should be changed to a line or not.  That can only be known after the
+   entire curve has been fit to a list of splines.  (The curve is the
+   pixel outline between two corners.)  After subdividing the curve, a
+   line may very well fit a portion of the curve just as well as the
+   spline---but unless a spline is truly close to being a line, it
+   should not be combined with other lines.  */
+
+  unsigned this_spline;
+  bool found_cubic = false;
+  unsigned length = SPLINE_LIST_LENGTH (*spline_list);
+
+  LOG1 ("\nChecking for bad lines (length %u):\n", length);
+
+  /* First see if there are any splines in the fitted shape.  */
+  for (this_spline = 0; this_spline < length; this_spline++)
+    {
+      if (SPLINE_DEGREE (SPLINE_LIST_ELT (*spline_list, this_spline)) ==
+       CUBICTYPE)
+        {
+          found_cubic = true;
+          break;
+        }
+    }
+
+  /* If so, change lines back to splines (we haven't done anything to
+     their control points, so we only have to change the degree) unless
+     the spline is close enough to being a line.  */
+  if (found_cubic)
+    for (this_spline = 0; this_spline < length; this_spline++)
+      {
+        spline_type s = SPLINE_LIST_ELT (*spline_list, this_spline);
+
+        if (SPLINE_DEGREE (s) == LINEARTYPE)
+          {
+            LOG1 ("  #%u: ", this_spline);
+            if (SPLINE_LINEARITY (s) > fitting_opts->line_reversion_threshold)
+              {
+                LOG ("reverted, ");
+                SPLINE_DEGREE (SPLINE_LIST_ELT (*spline_list, this_spline))
+                  = CUBICTYPE;
+              }
+            LOG1 ("linearity %.3f.\n", SPLINE_LINEARITY (s));
+          }
+      }
+    else
+      LOG ("  No lines.\n");
+}
+
+
+
+static bool
+spline_linear_enough(spline_type *             const spline,
+                     curve_type                const curve,
+                     const fitting_opts_type * const fitting_opts) {
+
+/* Supposing that we have accepted the error, another question arises:
+   would we be better off just using a straight line?  */
+
+  float A, B, C;
+  unsigned this_point;
+  float dist = 0.0, start_end_dist, threshold;
+
+  LOG ("Checking linearity:\n");
+
+  A = END_POINT(*spline).x - START_POINT(*spline).x;
+  B = END_POINT(*spline).y - START_POINT(*spline).y;
+  C = END_POINT(*spline).z - START_POINT(*spline).z;
+
+  start_end_dist = (float) (SQR(A) + SQR(B) + SQR(C));
+  LOG1 ("start_end_distance is %.3f.\n", sqrt(start_end_dist));
+
+  LOG3 ("  Line endpoints are (%.3f, %.3f, %.3f) and ", START_POINT(*spline).x, START_POINT(*spline).y, START_POINT(*spline).z);
+  LOG3 ("(%.3f, %.3f, %.3f)\n", END_POINT(*spline).x, END_POINT(*spline).y, END_POINT(*spline).z);
+
+  /* LOG3 ("  Line is %.3fx + %.3fy + %.3f = 0.\n", A, B, C); */
+
+  for (this_point = 0; this_point < CURVE_LENGTH (curve); this_point++)
+    {
+      float a, b, c, w;
+      float t = CURVE_T (curve, this_point);
+      float_coord spline_point = evaluate_spline (*spline, t);
+
+      a = spline_point.x - START_POINT(*spline).x;
+      b = spline_point.y - START_POINT(*spline).y;
+      c = spline_point.z - START_POINT(*spline).z;
+      w = (A*a + B*b + C*c) / start_end_dist;
+
+      dist += (float)sqrt(SQR(a-A*w) + SQR(b-B*w) + SQR(c-C*w));
+    }
+  LOG1 ("  Total distance is %.3f, ", dist);
+
+  dist /= (CURVE_LENGTH (curve) - 1);
+  LOG1 ("which is %.3f normalized.\n", dist);
+
+  /* We want reversion of short curves to splines to be more likely than
+     reversion of long curves, hence the second division by the curve
+     length, for use in `change_bad_lines'.  */
+  SPLINE_LINEARITY (*spline) = dist;
+  LOG1 ("  Final linearity: %.3f.\n", SPLINE_LINEARITY (*spline));
+  if (start_end_dist * (float) 0.5 > fitting_opts->line_threshold)
+    threshold = fitting_opts->line_threshold;
+  else
+    threshold = start_end_dist * (float) 0.5;
+  LOG1 ("threshold is %.3f .\n", threshold);
+  if (dist < threshold)
+    return true;
+  else
+    return false;
+}
+
+
+
+/* Forward declaration for recursion */
+
+static spline_list_type *
+fitCurve(curve_type                const curve,
+         const fitting_opts_type * const fitting_opts,
+         at_exception_type *       const exception);
+
+
+
+static spline_list_type *
+fit_with_line(curve_type const curve) {
+/*----------------------------------------------------------------------------
+  This routine returns the curve fitted to a straight line in a very
+  simple way: make the first and last points on the curve be the
+  endpoints of the line.  This simplicity is justified because we are
+  called only on very short curves.
+-----------------------------------------------------------------------------*/
+    spline_type line;
+
+    LOG("Fitting with straight line:\n");
+
+    SPLINE_DEGREE(line) = LINEARTYPE;
+    START_POINT(line) = CONTROL1(line) = CURVE_POINT(curve, 0);
+    END_POINT(line) = CONTROL2(line) = LAST_CURVE_POINT(curve);
+
+    /* Make sure that this line is never changed to a cubic.  */
+    SPLINE_LINEARITY(line) = 0;
+
+    if (log_file) {
+        LOG("  ");
+        print_spline(log_file, line);
+    }
+
+    return new_spline_list_with_spline(line);
+}
+
+
+
+#define B0(t) CUBE ((float) 1.0 - (t))
+#define B1(t) ((float) 3.0 * (t) * SQR ((float) 1.0 - (t)))
+#define B2(t) ((float) 3.0 * SQR (t) * ((float) 1.0 - (t)))
+#define B3(t) CUBE (t)
+
+static spline_type
+fit_one_spline(curve_type          const curve, 
+               at_exception_type * const exception) {
+/*----------------------------------------------------------------------------
+   Our job here is to find alpha1 (and alpha2), where t1_hat (t2_hat) is
+   the tangent to CURVE at the starting (ending) point, such that:
+
+   control1 = alpha1 * t1_hat + starting point
+   control2 = alpha2 * t2_hat + ending_point
+
+   and the resulting spline (starting_point .. control1 and control2 ..
+   ending_point) minimizes the least-square error from CURVE.
+
+   See pp.57--59 of the Phoenix thesis.
+
+   The B?(t) here corresponds to B_i^3(U_i) there.
+   The Bernshte\u in polynomials of degree n are defined by
+   B_i^n(t) = { n \choose i } t^i (1-t)^{n-i}, i = 0..n
+-----------------------------------------------------------------------------*/
+    /* Since our arrays are zero-based, the `C0' and `C1' here correspond
+       to `C1' and `C2' in the paper. 
+    */
+    float X_C1_det, C0_X_det, C0_C1_det;
+    float alpha1, alpha2;
+    spline_type spline;
+    vector_type start_vector, end_vector;
+    unsigned i;
+    vector_type * A;
+    vector_type t1_hat;
+    vector_type t2_hat;
+    float C[2][2] = { { 0.0, 0.0 }, { 0.0, 0.0 } };
+    float X[2] = { 0.0, 0.0 };
+
+    t1_hat = *CURVE_START_TANGENT(curve);  /* initial value */
+    t2_hat = *CURVE_END_TANGENT(curve);    /* initial value */
+
+    MALLOCARRAY_NOFAIL(A, CURVE_LENGTH(curve) * 2);
+
+    START_POINT(spline) = CURVE_POINT(curve, 0);
+    END_POINT(spline)   = LAST_CURVE_POINT(curve);
+    start_vector = make_vector(START_POINT(spline));
+    end_vector   = make_vector(END_POINT(spline));
+
+    for (i = 0; i < CURVE_LENGTH(curve); ++i) {
+        A[(i << 1) + 0] = Vmult_scalar(t1_hat, B1(CURVE_T(curve, i)));
+        A[(i << 1) + 1] = Vmult_scalar(t2_hat, B2(CURVE_T(curve, i)));
+    }
+
+    for (i = 0; i < CURVE_LENGTH(curve); ++i) {
+        vector_type temp, temp0, temp1, temp2, temp3;
+        vector_type * Ai = A + (i << 1);
+
+        C[0][0] += Vdot(Ai[0], Ai[0]);
+        C[0][1] += Vdot(Ai[0], Ai[1]);
+        /* C[1][0] = C[0][1] (this is assigned outside the loop)  */
+        C[1][1] += Vdot(Ai[1], Ai[1]);
+
+        /* Now the right-hand side of the equation in the paper.  */
+        temp0 = Vmult_scalar(start_vector, B0(CURVE_T(curve, i)));
+        temp1 = Vmult_scalar(start_vector, B1(CURVE_T(curve, i)));
+        temp2 = Vmult_scalar(end_vector, B2(CURVE_T(curve, i)));
+        temp3 = Vmult_scalar(end_vector, B3(CURVE_T(curve, i)));
+
+        temp = make_vector(
+            Vsubtract_point(CURVE_POINT(curve, i),
+                            Vadd(temp0, Vadd(temp1, Vadd(temp2, temp3)))));
+
+        X[0] += Vdot(temp, Ai[0]);
+        X[1] += Vdot(temp, Ai[1]);
+    }
+    free(A);
+
+    C[1][0] = C[0][1];
+    
+    X_C1_det = X[0] * C[1][1] - X[1] * C[0][1];
+    C0_X_det = C[0][0] * X[1] - C[0][1] * X[0];
+    C0_C1_det = C[0][0] * C[1][1] - C[1][0] * C[0][1];
+    if (C0_C1_det == 0.0) {
+        LOG ("zero determinant of C0*C1");
+        at_exception_fatal(exception, "zero determinant of C0*C1");
+        goto cleanup;
+    }
+
+    alpha1 = X_C1_det / C0_C1_det;
+    alpha2 = C0_X_det / C0_C1_det;
+
+    CONTROL1(spline) = Vadd_point(START_POINT(spline),
+                                  Vmult_scalar(t1_hat, alpha1));
+    CONTROL2(spline) = Vadd_point(END_POINT(spline),
+                                  Vmult_scalar(t2_hat, alpha2));
+    SPLINE_DEGREE(spline) = CUBICTYPE;
+
+cleanup:
+    return spline;
+}
+
+
+
+static void
+logSplineFit(spline_type const spline) {
+
+    if (SPLINE_DEGREE(spline) == LINEARTYPE)
+        LOG("  fitted to line:\n");
+    else
+        LOG("  fitted to spline:\n");
+    
+    if (log_file) {
+        LOG ("    ");
+        print_spline (log_file, spline);
+    }
+}
+
+
+
+static spline_list_type *
+fit_with_least_squares(curve_type                const curve,
+                       const fitting_opts_type * const fitting_opts,
+                       at_exception_type *       const exception) {
+/*----------------------------------------------------------------------------
+  The least squares method is well described in Schneider's thesis.
+  Briefly, we try to fit the entire curve with one spline.  If that
+  fails, we subdivide the curve. 
+-----------------------------------------------------------------------------*/
+    float error;
+    float best_error;
+    spline_type spline;
+    spline_type best_spline;
+    spline_list_type * spline_list;
+    unsigned int worst_point;
+    float previous_error;
+    
+    best_error = FLT_MAX;  /* initial value */
+    previous_error = FLT_MAX;  /* initial value */
+    spline_list = NULL;  /* initial value */
+    worst_point = 0;  /* initial value */
+
+    LOG ("\nFitting with least squares:\n");
+    
+    /* Phoenix reduces the number of points with a ``linear spline
+       technique''.  But for fitting letterforms, that is
+       inappropriate.  We want all the points we can get.
+    */
+    
+    /* It makes no difference whether we first set the `t' values or
+       find the tangents.  This order makes the documentation a little
+       more coherent.
+    */
+
+    LOG("Finding tangents:\n");
+    find_tangent(curve, /* to_start */ true,  /* cross_curve */ false,
+                 fitting_opts->tangent_surround);
+    find_tangent(curve, /* to_start */ false, /* cross_curve */ false,
+                 fitting_opts->tangent_surround);
+
+    set_initial_parameter_values(curve);
+
+    /* Now we loop, subdividing, until CURVE has been fit.  */
+    while (true) {
+        float error;
+
+        spline = fit_one_spline(curve, exception);
+        best_spline = spline;
+        if (at_exception_got_fatal(exception))
+            goto cleanup;
+
+        logSplineFit(spline);
+        
+        if (SPLINE_DEGREE(spline) == LINEARTYPE)
+            break;
+
+        error = find_error(curve, spline, &worst_point, exception);
+        if (error <= previous_error) {
+            best_error  = error;
+            best_spline = spline;
+        }
+        break;
+    }
+
+    if (SPLINE_DEGREE(spline) == LINEARTYPE) {
+        spline_list = new_spline_list_with_spline(spline);
+        LOG1("Accepted error of %.3f.\n", error);
+        return spline_list;
+    }
+
+    /* Go back to the best fit.  */
+    spline = best_spline;
+    error = best_error;
+
+    if (error < fitting_opts->error_threshold && !CURVE_CYCLIC(curve)) {
+        /* The points were fitted with a spline.  We end up here
+           whenever a fit is accepted.  We have one more job: see if
+           the ``curve'' that was fit should really be a straight
+           line.
+        */
+        if (spline_linear_enough(&spline, curve, fitting_opts)) {
+            SPLINE_DEGREE(spline) = LINEARTYPE;
+            LOG("Changed to line.\n");
+        }
+        spline_list = new_spline_list_with_spline(spline);
+        LOG1("Accepted error of %.3f.\n", error);
+    } else {
+        /* We couldn't fit the curve acceptably, so subdivide.  */
+        unsigned subdivision_index;
+        spline_list_type * left_spline_list;
+        spline_list_type * right_spline_list;
+        curve_type left_curve, right_curve;
+
+        left_curve  = new_curve();
+        right_curve = new_curve();
+
+        /* Insert 'left_curve', then 'right_curve' after 'curve' in the list */
+        NEXT_CURVE(right_curve) = NEXT_CURVE(curve);
+        PREVIOUS_CURVE(right_curve) = left_curve;
+        NEXT_CURVE(left_curve) = right_curve;
+        PREVIOUS_CURVE(left_curve) = curve;
+        NEXT_CURVE(curve) = left_curve;
+
+        LOG1("\nSubdividing (error %.3f):\n", error);
+        LOG3("  Original point: (%.3f,%.3f), #%u.\n",
+             CURVE_POINT (curve, worst_point).x,
+             CURVE_POINT (curve, worst_point).y, worst_point);
+        subdivision_index = worst_point;
+        LOG3 ("  Final point: (%.3f,%.3f), #%u.\n",
+              CURVE_POINT (curve, subdivision_index).x,
+              CURVE_POINT (curve, subdivision_index).y, subdivision_index);
+
+        /* The last point of the left-hand curve will also be the first
+           point of the right-hand curve.  */
+        CURVE_LENGTH(left_curve)  = subdivision_index + 1;
+        CURVE_LENGTH(right_curve) = CURVE_LENGTH(curve) - subdivision_index;
+        left_curve->point_list = curve->point_list;
+        right_curve->point_list = curve->point_list + subdivision_index;
+
+        /* We want to use the tangents of the curve which we are
+           subdividing for the start tangent for left_curve and the
+           end tangent for right_curve.
+        */
+        CURVE_START_TANGENT(left_curve) = CURVE_START_TANGENT(curve);
+        CURVE_END_TANGENT(right_curve)  = CURVE_END_TANGENT(curve);
+
+        /* We have to set up the two curves before finding the tangent at
+           the subdivision point.  The tangent at that point must be the
+           same for both curves, or noticeable bumps will occur in the
+           character.  But we want to use information on both sides of the
+           point to compute the tangent, hence cross_curve = true.
+        */
+        find_tangent(left_curve, /* to_start_point: */ false,
+                     /* cross_curve: */ true, fitting_opts->tangent_surround);
+        CURVE_START_TANGENT(right_curve) = CURVE_END_TANGENT(left_curve);
+
+        /* Now that we've set up the curves, we can fit them.  */
+        left_spline_list = fitCurve(left_curve, fitting_opts, exception);
+        if (at_exception_got_fatal(exception))
+            /* TODO: Memory allocated for left_curve and right_curve
+               will leak.*/
+            goto cleanup;
+
+        right_spline_list = fitCurve(right_curve, fitting_opts, exception);
+        /* TODO: Memory allocated for left_curve and right_curve
+           will leak.*/
+        if (at_exception_got_fatal(exception))
+            goto cleanup;
+        
+        /* Neither of the subdivided curves could be fit, so fail.  */
+        if (left_spline_list == NULL && right_spline_list == NULL)
+            return NULL;
+
+        /* Put the two together (or whichever of them exist).  */
+        spline_list = new_spline_list();
+
+        if (left_spline_list == NULL) {
+            LOG1("Could not fit spline to left curve (%lx).\n",
+                 (unsigned long) left_curve);
+            at_exception_warning(exception, "Could not fit left spline list");
+        } else {
+            concat_spline_lists(spline_list, *left_spline_list);
+            free_spline_list(*left_spline_list);
+            free(left_spline_list);
+        }
+        
+        if (right_spline_list == NULL) {
+            LOG1("Could not fit spline to right curve (%lx).\n",
+                 (unsigned long) right_curve);
+            at_exception_warning(exception, "Could not fit right spline list");
+        } else {
+            concat_spline_lists(spline_list, *right_spline_list);
+            free_spline_list(*right_spline_list);
+            free(right_spline_list);
+        }
+        if (CURVE_END_TANGENT(left_curve))
+            free(CURVE_END_TANGENT(left_curve));
+        free(left_curve);
+        free(right_curve);
+    }
+cleanup:
+
+    return spline_list;
+}
+
+
+
+static spline_list_type *
+fitCurve(curve_type                const curve,
+         const fitting_opts_type * const fittingOptsP,
+         at_exception_type *       const exception) {
+/*----------------------------------------------------------------------------
+  Transform a set of locations to a list of splines (the fewer the
+  better).  We are guaranteed that CURVE does not contain any corners.
+  We return NULL if we cannot fit the points at all.
+-----------------------------------------------------------------------------*/
+    spline_list_type * fittedSplinesP;
+
+    if (CURVE_LENGTH(curve) < 2) {
+        LOG("Tried to fit curve with less than two points");
+        at_exception_warning(exception, 
+                             "Tried to fit curve with less than two points");
+        fittedSplinesP = NULL;
+    } else if (CURVE_LENGTH(curve) < 4)
+        fittedSplinesP = fit_with_line(curve);
+    else
+        fittedSplinesP =
+            fit_with_least_squares(curve, fittingOptsP, exception);
+
+    return fittedSplinesP;
+}
+
+
+
+static void
+fitCurves(curve_list_type           const curveList,
+          pixel                     const color,
+          const fitting_opts_type * const fittingOptsP,
+          spline_list_type *        const splinesP,
+          at_exception_type *       const exceptionP) {
+          
+    spline_list_type curveListSplines;
+    unsigned int curveSeq;
+
+    curveListSplines = empty_spline_list();
+    
+    curveListSplines.open      = curveList.open;
+    curveListSplines.clockwise = curveList.clockwise;
+    curveListSplines.color     = color;
+
+    for (curveSeq = 0;
+         curveSeq < curveList.length && !at_exception_got_fatal(exceptionP);
+         ++curveSeq) {
+
+        curve_type const currentCurve = CURVE_LIST_ELT(curveList, curveSeq);
+
+        spline_list_type * curveSplinesP;
+
+        LOG1("\nFitting curve #%u:\n", curveSeq);
+
+        curveSplinesP = fitCurve(currentCurve, fittingOptsP, exceptionP);
+        if (!at_exception_got_fatal(exceptionP)) {
+            if (curveSplinesP == NULL) {
+                LOG1("Could not fit curve #%u", curveSeq);
+                at_exception_warning(exceptionP, "Could not fit curve");
+            } else {
+                logSplinesForCurve(curveSeq, *curveSplinesP);
+                
+                /* After fitting, we may need to change some would-be lines
+                   back to curves, because they are in a list with other
+                   curves.
+                */
+                change_bad_lines(curveSplinesP, fittingOptsP);
+                
+                concat_spline_lists(&curveListSplines, *curveSplinesP);
+                free_spline_list(*curveSplinesP);
+                free(curveSplinesP);
+            }
+        }
+    }
+    if (at_exception_got_fatal(exceptionP))
+        free_spline_list(curveListSplines);
+    else
+        *splinesP = curveListSplines;
+}
+    
+
+
+static void
+logFittedSplines(spline_list_type const curve_list_splines) {
+
+    unsigned int splineSeq;
+
+    LOG("\nFitted splines are:\n");
+    for (splineSeq = 0;
+         splineSeq < SPLINE_LIST_LENGTH(curve_list_splines);
+         ++splineSeq) {
+        LOG1("  %u: ", splineSeq);
+        print_spline(log_file,
+                     SPLINE_LIST_ELT(curve_list_splines, splineSeq));
+    }
+}
+
+
+
+static void
+fitCurveList(curve_list_type     const curveList,
+             fitting_opts_type * const fittingOptsP,
+             distance_map_type * const dist,
+             pixel               const color,
+             spline_list_type *  const splineListP,
+             at_exception_type * const exception) {
+/*----------------------------------------------------------------------------
+  Fit the list of curves CURVE_LIST to a list of splines, and return
+  it.  CURVE_LIST represents a single closed paths, e.g., either the
+  inside or outside outline of an `o'.
+-----------------------------------------------------------------------------*/
+    curve_type curve;
+    spline_list_type curveListSplines;
+
+    removeKnees(curveList);
+
+    if (dist != NULL)
+        computePointWeights(curveList, fittingOptsP, dist);
+    
+    /* We filter all the curves in 'curveList' at once; otherwise, we
+       would look at an unfiltered curve when computing tangents.
+    */
+    filterCurves(curveList, fittingOptsP);
+
+    /* Make the first point in the first curve also be the last point in
+       the last curve, so the fit to the whole curve list will begin and
+       end at the same point.  This may cause slight errors in computing
+       the tangents and t values, but it's worth it for the continuity.
+       Of course we don't want to do this if the two points are already
+       the same, as they are if the curve is cyclic.  (We don't append it
+       earlier, in `split_at_corners', because that confuses the
+       filtering.)  Finally, we can't append the point if the curve is
+       exactly three points long, because we aren't adding any more data,
+       and three points isn't enough to determine a spline.  Therefore,
+       the fitting will fail.
+    */
+    curve = CURVE_LIST_ELT(curveList, 0);
+    if (CURVE_CYCLIC(curve))
+        append_point(curve, CURVE_POINT(curve, 0));
+
+    /* Finally, fit each curve in the list to a list of splines.  */
+
+    fitCurves(curveList, color, fittingOptsP, &curveListSplines, exception);
+    if (!at_exception_got_fatal(exception)) {
+        if (log_file)
+            logFittedSplines(curveListSplines);
+        *splineListP = curveListSplines;
+    }
+}
+
+
+
+static void
+fitCurvesToSplines(curve_list_array_type    const curveArray,
+                   fitting_opts_type *      const fittingOptsP,
+                   distance_map_type *      const dist,
+                   unsigned short           const width,
+                   unsigned short           const height,
+                   at_exception_type *      const exception,
+                   at_progress_func               notifyProgress, 
+                   void *                   const progressData,
+                   at_testcancel_func             testCancel,
+                   void *                   const testcancelData,
+                   spline_list_array_type * const splineListArrayP) {
+    
+    unsigned splineListSeq;
+    bool cancelled;
+    spline_list_array_type splineListArray;
+
+    splineListArray = new_spline_list_array();
+    splineListArray.centerline          = fittingOptsP->centerline;
+    splineListArray.preserve_width      = fittingOptsP->preserve_width;
+    splineListArray.width_weight_factor = fittingOptsP->width_weight_factor;
+    splineListArray.backgroundSpec      = fittingOptsP->backgroundSpec;
+    splineListArray.background_color    = fittingOptsP->background_color;
+    /* Set dummy values. Real value is set in upper context. */
+    splineListArray.width  = width;
+    splineListArray.height = height;
+    
+    for (splineListSeq = 0, cancelled = false;
+         splineListSeq < CURVE_LIST_ARRAY_LENGTH(curveArray) &&
+             !at_exception_got_fatal(exception) && !cancelled;
+         ++splineListSeq) {
+
+        curve_list_type const curveList =
+            CURVE_LIST_ARRAY_ELT(curveArray, splineListSeq);
+      
+        spline_list_type curveSplineList;
+
+        if (notifyProgress)
+            notifyProgress((((float)splineListSeq)/
+                            ((float)CURVE_LIST_ARRAY_LENGTH(curveArray) *
+                             (float)3.0) + (float)0.333),
+                           progressData);
+        if (testCancel && testCancel(testcancelData))
+            cancelled = true;
+
+        LOG1("\nFitting curve list #%u:\n", splineListSeq);
+
+        fitCurveList(curveList, fittingOptsP, dist, curveList.color,
+                     &curveSplineList, exception);
+        if (!at_exception_got_fatal(exception))
+            append_spline_list(&splineListArray, curveSplineList);
+    }
+    *splineListArrayP = splineListArray;
+}
+
+
+
+void
+fit_outlines_to_splines(pixel_outline_list_type  const pixelOutlineList,
+                        fitting_opts_type *      const fittingOptsP,
+                        distance_map_type *      const dist,
+                        unsigned short           const width,
+                        unsigned short           const height,
+                        at_exception_type *      const exception,
+                        at_progress_func               notifyProgress, 
+                        void *                   const progressData,
+                        at_testcancel_func             testCancel,
+                        void *                   const testcancelData,
+                        spline_list_array_type * const splineListArrayP) {
+/*----------------------------------------------------------------------------
+   Transform a list of pixels in the outlines of the original character to
+   a list of spline lists fitted to those pixels.
+-----------------------------------------------------------------------------*/
+    curve_list_array_type const curveListArray =
+        split_at_corners(pixelOutlineList, fittingOptsP, exception);
+
+    fitCurvesToSplines(curveListArray, fittingOptsP, dist, width, height,
+                       exception, notifyProgress, progressData,
+                       testCancel, testcancelData, splineListArrayP);
+
+
+    free_curve_list_array(&curveListArray, notifyProgress, progressData);
+    
+    flush_log_output();
+}
+
+
+
+
+static void
+find_vectors(unsigned int       const test_index,
+             pixel_outline_type const outline,
+             vector_type *      const in,
+             vector_type *      const out,
+             unsigned int       const corner_surround) {
+/*----------------------------------------------------------------------------
+  Return the difference vectors coming in and going out of the outline
+  OUTLINE at the point whose index is TEST_INDEX.  In Phoenix,
+  Schneider looks at a single point on either side of the point we're
+  considering.  That works for him because his points are not touching.
+  But our points *are* touching, and so we have to look at
+  `corner_surround' points on either side, to get a better picture of
+  the outline's shape.
+-----------------------------------------------------------------------------*/
+    int i;
+    unsigned n_done;
+    pm_pixelcoord const candidate = O_COORDINATE(outline, test_index);
+
+    in->dx  = in->dy  = in->dz  = 0.0;
+    out->dx = out->dy = out->dz = 0.0;
+    
+    /* Add up the differences from p of the `corner_surround' points
+       before p.
+    */
+    for (i = O_PREV(outline, test_index), n_done = 0;
+         n_done < corner_surround;
+         i = O_PREV(outline, i), ++n_done)
+        *in = Vadd(*in, IPsubtract(O_COORDINATE(outline, i), candidate));
+    
+    /* And the points after p. */
+    for (i = O_NEXT (outline, test_index), n_done = 0;
+         n_done < corner_surround;
+         i = O_NEXT(outline, i), ++n_done)
+        *out = Vadd(*out, IPsubtract(O_COORDINATE(outline, i), candidate));
+}
+
+
+
+/* Remove adjacent points from the index list LIST.  We do this by first
+   sorting the list and then running through it.  Since these lists are
+   quite short, a straight selection sort (e.g., p.139 of the Art of
+   Computer Programming, vol.3) is good enough.  LAST_INDEX is the index
+   of the last pixel on the outline, i.e., the next one is the first
+   pixel. We need this for checking the adjacency of the last corner.
+
+   We need to do this because the adjacent corners turn into
+   two-pixel-long curves, which can only be fit by straight lines.  */
+
+static void
+remove_adjacent_corners (index_list_type *list, unsigned last_index,
+             bool remove_adj_corners,
+             at_exception_type * exception)
+             
+{
+  unsigned j;
+  unsigned last;
+  index_list_type new_list = new_index_list ();
+
+  for (j = INDEX_LIST_LENGTH (*list) - 1; j > 0; j--)
+    {
+      unsigned search;
+      unsigned temp;
+      /* Find maximal element below `j'.  */
+      unsigned max_index = j;
+
+      for (search = 0; search < j; search++)
+        if (GET_INDEX (*list, search) > GET_INDEX (*list, max_index))
+          max_index = search;
+
+      if (max_index != j)
+        {
+          temp = GET_INDEX (*list, j);
+          GET_INDEX (*list, j) = GET_INDEX (*list, max_index);
+          GET_INDEX (*list, max_index) = temp;
+      
+      /* xx -- really have to sort?  */
+      LOG ("needed exchange");
+      at_exception_warning(exception, "needed exchange");
+        }
+    }
+
+  /* The list is sorted.  Now look for adjacent entries.  Each time
+     through the loop we insert the current entry and, if appropriate,
+     the next entry.  */
+  for (j = 0; j < INDEX_LIST_LENGTH (*list) - 1; j++)
+    {
+      unsigned current = GET_INDEX (*list, j);
+      unsigned next = GET_INDEX (*list, j + 1);
+
+      /* We should never have inserted the same element twice.  */
+      /* assert (current != next); */
+
+      if ((remove_adj_corners) && ((next == current + 1) || (next == current)))
+        j++;
+
+      append_index (&new_list, current);
+    }
+
+  /* Don't append the last element if it is 1) adjacent to the previous
+     one; or 2) adjacent to the very first one.  */
+  last = GET_LAST_INDEX (*list);
+  if (INDEX_LIST_LENGTH (new_list) == 0
+      || !(last == GET_LAST_INDEX (new_list) + 1
+           || (last == last_index && GET_INDEX (*list, 0) == 0)))
+    append_index (&new_list, last);
+
+  free_index_list (list);
+  *list = new_list;
+}
+
+/* A ``knee'' is a point which forms a ``right angle'' with its
+   predecessor and successor.  See the documentation (the `Removing
+   knees' section) for an example and more details.
+
+   The argument CLOCKWISE tells us which direction we're moving.  (We
+   can't figure that information out from just the single segment with
+   which we are given to work.)
+
+   We should never find two consecutive knees.
+
+   Since the first and last points are corners (unless the curve is
+   cyclic), it doesn't make sense to remove those.  */
+
+/* This evaluates to true if the vector V is zero in one direction and
+   nonzero in the other.  */
+#define ONLY_ONE_ZERO(v)                                                \
+  (((v).dx == 0.0 && (v).dy != 0.0) || ((v).dy == 0.0 && (v).dx != 0.0))
+
+/* There are four possible cases for knees, one for each of the four
+   corners of a rectangle; and then the cases differ depending on which
+   direction we are going around the curve.  The tests are listed here
+   in the order of upper left, upper right, lower right, lower left.
+   Perhaps there is some simple pattern to the
+   clockwise/counterclockwise differences, but I don't see one.  */
+#define CLOCKWISE_KNEE(prev_delta, next_delta)                                                  \
+  ((prev_delta.dx == -1.0 && next_delta.dy == 1.0)                                              \
+   || (prev_delta.dy == 1.0 && next_delta.dx == 1.0)                                    \
+   || (prev_delta.dx == 1.0 && next_delta.dy == -1.0)                                   \
+   || (prev_delta.dy == -1.0 && next_delta.dx == -1.0))
+
+#define COUNTERCLOCKWISE_KNEE(prev_delta, next_delta)                                   \
+  ((prev_delta.dy == 1.0 && next_delta.dx == -1.0)                                              \
+   || (prev_delta.dx == 1.0 && next_delta.dy == 1.0)                                    \
+   || (prev_delta.dy == -1.0 && next_delta.dx == 1.0)                                   \
+   || (prev_delta.dx == -1.0 && next_delta.dy == -1.0))
+
+
+
+static void
+remove_knee_points(curve_type const curve,
+                   bool       const clockwise) {
+
+      unsigned const offset = (CURVE_CYCLIC(curve) == true) ? 0 : 1;
+      curve_type const trimmed_curve = copy_most_of_curve(curve);
+
+      pm_pixelcoord previous;
+      unsigned i;
+
+      if (!CURVE_CYCLIC(curve))
+          append_pixel(trimmed_curve,
+                       real_to_int_coord(CURVE_POINT(curve, 0)));
+
+      previous = real_to_int_coord(CURVE_POINT(curve,
+                                               CURVE_PREV(curve, offset)));
+
+      for (i = offset; i < CURVE_LENGTH (curve) - offset; ++i) {
+          pm_pixelcoord const current =
+              real_to_int_coord(CURVE_POINT(curve, i));
+          pm_pixelcoord const next =
+              real_to_int_coord(CURVE_POINT(curve, CURVE_NEXT(curve, i)));
+          vector_type const prev_delta = IPsubtract(previous, current);
+          vector_type const next_delta = IPsubtract(next, current);
+
+          if (ONLY_ONE_ZERO(prev_delta) && ONLY_ONE_ZERO(next_delta)
+              && ((clockwise && CLOCKWISE_KNEE(prev_delta, next_delta))
+                  || (!clockwise
+                      && COUNTERCLOCKWISE_KNEE(prev_delta, next_delta))))
+              LOG2(" (%d,%d)", current.col, current.row);
+          else {
+              previous = current;
+              append_pixel(trimmed_curve, current);
+          }
+      }
+
+      if (!CURVE_CYCLIC(curve))
+          append_pixel(trimmed_curve,
+                       real_to_int_coord(LAST_CURVE_POINT(curve)));
+
+      if (CURVE_LENGTH(trimmed_curve) == CURVE_LENGTH(curve))
+          LOG(" (none)");
+
+      LOG(".\n");
+
+      free_curve(curve);
+      *curve = *trimmed_curve;
+      free(trimmed_curve);      /* free_curve? --- Masatake */
+}
+
+
+
+/* Smooth the curve by adding in neighboring points.  Do this
+   `filter_iterations' times.  But don't change the corners.  */
+
+static void
+filter (curve_type curve, fitting_opts_type *fitting_opts)
+{
+  unsigned iteration, this_point;
+  unsigned offset = (CURVE_CYCLIC (curve) == true) ? 0 : 1;
+  float_coord prev_new_point;
+
+  /* We must have at least three points---the previous one, the current
+     one, and the next one.  But if we don't have at least five, we will
+     probably collapse the curve down onto a single point, which means
+     we won't be able to fit it with a spline.  */
+  if (CURVE_LENGTH (curve) < 5)
+    {
+      LOG1 ("Length is %u, not enough to filter.\n", CURVE_LENGTH (curve));
+      return;
+    }
+
+  prev_new_point.x = FLT_MAX;
+  prev_new_point.y = FLT_MAX;
+  prev_new_point.z = FLT_MAX;
+
+  for (iteration = 0; iteration < fitting_opts->filter_iterations;
+   iteration++)
+    {
+      curve_type newcurve = copy_most_of_curve (curve);
+      bool collapsed = false;
+
+      /* Keep the first point on the curve.  */
+      if (offset)
+        append_point (newcurve, CURVE_POINT (curve, 0));
+
+      for (this_point = offset; this_point < CURVE_LENGTH (curve) - offset;
+           this_point++)
+        {
+          vector_type in, out, sum;
+          float_coord new_point;
+
+          /* Calculate the vectors in and out, computed by looking at n points
+             on either side of this_point. Experimental it was found that 2 is
+             optimal. */
+
+          signed int prev, prevprev; /* have to be signed */
+          unsigned int next, nextnext;
+          float_coord candidate = CURVE_POINT (curve, this_point);
+
+          prev = CURVE_PREV (curve, this_point);
+          prevprev = CURVE_PREV (curve, prev);
+          next = CURVE_NEXT (curve, this_point);
+          nextnext = CURVE_NEXT (curve, next);
+
+          /* Add up the differences from p of the `surround' points
+             before p.  */
+          in.dx = in.dy = in.dz = 0.0;
+
+          in = Vadd (in, Psubtract (CURVE_POINT (curve, prev), candidate));
+          if (prevprev >= 0)
+              in = Vadd (in, Psubtract (CURVE_POINT (curve, prevprev), candidate));
+
+          /* And the points after p.  Don't use more points after p than we
+             ended up with before it.  */
+          out.dx = out.dy = out.dz = 0.0;
+
+          out = Vadd (out, Psubtract (CURVE_POINT (curve, next), candidate));
+          if (nextnext < CURVE_LENGTH (curve))
+              out = Vadd (out, Psubtract (CURVE_POINT (curve, nextnext), candidate));
+
+          /* Start with the old point.  */
+          new_point = candidate;
+          sum = Vadd (in, out);
+          /* We added 2*n+2 points, so we have to divide the sum by 2*n+2 */
+          new_point.x += sum.dx / 6;
+          new_point.y += sum.dy / 6;
+          new_point.z += sum.dz / 6;
+          if (fabs (prev_new_point.x - new_point.x) < 0.3
+              && fabs (prev_new_point.y - new_point.y) < 0.3
+              && fabs (prev_new_point.z - new_point.z) < 0.3)
+            {
+              collapsed = true;
+              break;
+            }
+
+
+          /* Put the newly computed point into a separate curve, so it
+             doesn't affect future computation (on this iteration).  */
+          append_point (newcurve, prev_new_point = new_point);
+        }
+
+      if (collapsed)
+    free_curve (newcurve);
+      else
+    {
+          /* Just as with the first point, we have to keep the last point.  */
+          if (offset)
+        append_point (newcurve, LAST_CURVE_POINT (curve));
+      
+          /* Set the original curve to the newly filtered one, and go again.  */
+          free_curve (curve);
+          *curve = *newcurve;
+    }
+      free (newcurve);
+    }
+
+  log_curve (curve, false);
+}
+
+
+
+/* Find reasonable values for t for each point on CURVE.  The method is
+   called chord-length parameterization, which is described in Plass &
+   Stone.  The basic idea is just to use the distance from one point to
+   the next as the t value, normalized to produce values that increase
+   from zero for the first point to one for the last point.  */
+
+static void
+set_initial_parameter_values (curve_type curve)
+{
+  unsigned p;
+
+  LOG ("\nAssigning initial t values:\n  ");
+
+  CURVE_T (curve, 0) = 0.0;
+
+  for (p = 1; p < CURVE_LENGTH (curve); p++)
+    {
+      float_coord point = CURVE_POINT (curve, p),
+                           previous_p = CURVE_POINT (curve, p - 1);
+      float d = distance (point, previous_p);
+      CURVE_T (curve, p) = CURVE_T (curve, p - 1) + d;
+    }
+
+  assert (LAST_CURVE_T (curve) != 0.0);
+
+  for (p = 1; p < CURVE_LENGTH (curve); p++)
+    CURVE_T (curve, p) = CURVE_T (curve, p) / LAST_CURVE_T (curve);
+
+  log_entire_curve (curve);
+}
+
+/* Find an approximation to the tangent to an endpoint of CURVE (to the
+   first point if TO_START_POINT is true, else the last).  If
+   CROSS_CURVE is true, consider points on the adjacent curve to CURVE.
+
+   It is important to compute an accurate approximation, because the
+   control points that we eventually decide upon to fit the curve will
+   be placed on the half-lines defined by the tangents and
+   endpoints...and we never recompute the tangent after this.  */
+
+static void
+find_tangent (curve_type curve, bool to_start_point, bool cross_curve,
+  unsigned tangent_surround)
+{
+  vector_type tangent;
+  vector_type **curve_tangent = (to_start_point == true) ? &(CURVE_START_TANGENT (curve))
+                                               : &(CURVE_END_TANGENT (curve));
+  unsigned n_points = 0;
+
+  LOG1 ("  tangent to %s: ", (to_start_point == true) ? "start" : "end");
+
+  if (*curve_tangent == NULL)
+    {
+        MALLOCVAR_NOFAIL(*curve_tangent);
+      do
+        {
+          tangent = find_half_tangent (curve, to_start_point, &n_points,
+            tangent_surround);
+
+          if ((cross_curve == true) || (CURVE_CYCLIC (curve) == true))
+            {
+              curve_type adjacent_curve
+                = (to_start_point == true) ? PREVIOUS_CURVE (curve) : NEXT_CURVE (curve);
+              vector_type tangent2
+                = (to_start_point == false) ? find_half_tangent (adjacent_curve, true, &n_points,
+                tangent_surround) : find_half_tangent (adjacent_curve, true, &n_points,
+                tangent_surround);
+
+              LOG3 ("(adjacent curve half tangent (%.3f,%.3f,%.3f)) ",
+                tangent2.dx, tangent2.dy, tangent2.dz);
+              tangent = Vadd (tangent, tangent2);
+            }
+          tangent_surround--;
+
+        }
+      while (tangent.dx == 0.0 && tangent.dy == 0.0);
+
+      assert (n_points > 0);
+      **curve_tangent = Vmult_scalar (tangent, (float)(1.0 / n_points));
+      if ((CURVE_CYCLIC (curve) == true) && CURVE_START_TANGENT (curve))
+          *CURVE_START_TANGENT (curve) = **curve_tangent;
+      if  ((CURVE_CYCLIC (curve) == true) && CURVE_END_TANGENT (curve))
+          *CURVE_END_TANGENT (curve) = **curve_tangent;
+    }
+  else
+    LOG ("(already computed) ");
+
+  LOG3 ("(%.3f,%.3f,%.3f).\n", (*curve_tangent)->dx, (*curve_tangent)->dy, (*curve_tangent)->dz);
+}
+
+/* Find the change in y and change in x for `tangent_surround' (a global)
+   points along CURVE.  Increment N_POINTS by the number of points we
+   actually look at.  */
+
+static vector_type
+find_half_tangent (curve_type c, bool to_start_point, unsigned *n_points,
+  unsigned tangent_surround)
+{
+  unsigned p;
+  int factor = to_start_point ? 1 : -1;
+  unsigned tangent_index = to_start_point ? 0 : c->length - 1;
+  float_coord tangent_point = CURVE_POINT (c, tangent_index);
+  vector_type tangent = { 0.0, 0.0 };
+  unsigned int surround;
+
+  if ((surround = CURVE_LENGTH (c) / 2) > tangent_surround)
+    surround = tangent_surround;
+
+  for (p = 1; p <= surround; p++)
+    {
+      int this_index = p * factor + tangent_index;
+      float_coord this_point;
+
+      if (this_index < 0 || this_index >= (int) c->length)
+        break;
+
+      this_point = CURVE_POINT (c, p * factor + tangent_index);
+
+      /* Perhaps we should weight the tangent from `this_point' by some
+         factor dependent on the distance from the tangent point.  */
+      tangent = Vadd (tangent,
+                      Vmult_scalar (Psubtract (this_point, tangent_point),
+                                    (float) factor));
+      (*n_points)++;
+    }
+
+  return tangent;
+}
+
+/* When this routine is called, we have computed a spline representation
+   for the digitized curve.  The question is, how good is it?  If the
+   fit is very good indeed, we might have an error of zero on each
+   point, and then WORST_POINT becomes irrelevant.  But normally, we
+   return the error at the worst point, and the index of that point in
+   WORST_POINT.  The error computation itself is the Euclidean distance
+   from the original curve CURVE to the fitted spline SPLINE.  */
+
+static float
+find_error (curve_type curve, spline_type spline, unsigned *worst_point,
+        at_exception_type * exception)
+{
+  unsigned this_point;
+  float total_error = 0.0;
+  float worst_error = FLT_MIN;
+
+  *worst_point = CURVE_LENGTH (curve) + 1;   /* A sentinel value.  */
+
+  for (this_point = 0; this_point < CURVE_LENGTH (curve); this_point++)
+    {
+      float_coord curve_point = CURVE_POINT (curve, this_point);
+      float t = CURVE_T (curve, this_point);
+      float_coord spline_point = evaluate_spline (spline, t);
+      float this_error = distance (curve_point, spline_point);
+      if (this_error >= worst_error)
+        {
+         *worst_point = this_point;
+          worst_error = this_error;
+        }
+      total_error += this_error;
+    }
+
+  if (*worst_point == CURVE_LENGTH (curve) + 1)
+    { /* Didn't have any ``worst point''; the error should be zero.  */
+      if (epsilon_equal (total_error, 0.0))
+        LOG ("  Every point fit perfectly.\n");
+      else
+    {
+      LOG("No worst point found; something is wrong");
+      at_exception_warning(exception, "No worst point found; something is wrong");
+    }
+    }
+  else
+    {
+      if (epsilon_equal (total_error, 0.0))
+        LOG ("  Every point fit perfectly.\n");
+      else
+        {
+          LOG5 ("  Worst error (at (%.3f,%.3f,%.3f), point #%u) was %.3f.\n",
+              CURVE_POINT (curve, *worst_point).x,
+              CURVE_POINT (curve, *worst_point).y,
+              CURVE_POINT (curve, *worst_point).z, *worst_point, worst_error);
+          LOG1 ("  Total error was %.3f.\n", total_error);
+          LOG2 ("  Average error (over %u points) was %.3f.\n",
+              CURVE_LENGTH (curve), total_error / CURVE_LENGTH (curve));
+        }
+    }
+
+  return worst_error;
+}
+
+
+/* Lists of array indices (well, that is what we use it for).  */
+
+static index_list_type
+new_index_list (void)
+{
+  index_list_type index_list;
+
+  index_list.data = NULL;
+  INDEX_LIST_LENGTH (index_list) = 0;
+
+  return index_list;
+}
+
+static void
+free_index_list (index_list_type *index_list)
+{
+  if (INDEX_LIST_LENGTH (*index_list) > 0)
+    {
+      free (index_list->data);
+      index_list->data = NULL;
+      INDEX_LIST_LENGTH (*index_list) = 0;
+    }
+}
+
+static void
+append_index (index_list_type *list, unsigned new_index)
+{
+  INDEX_LIST_LENGTH (*list)++;
+  REALLOCARRAY_NOFAIL(list->data, INDEX_LIST_LENGTH(*list));
+  list->data[INDEX_LIST_LENGTH (*list) - 1] = new_index;
+}
+
+
+/* Return the Euclidean distance between P1 and P2.  */
+
+static float
+distance (float_coord p1, float_coord p2)
+{
+  float x = p1.x - p2.x, y = p1.y - p2.y, z = p1.z - p2.z;
+  return (float) sqrt (SQR(x) + SQR(y) + SQR(z));
+}
diff --git a/converter/other/pamtosvg/fit.h b/converter/other/pamtosvg/fit.h
new file mode 100644
index 00000000..529da5c7
--- /dev/null
+++ b/converter/other/pamtosvg/fit.h
@@ -0,0 +1,34 @@
+/* fit.h: convert the pixel representation to splines. */
+
+#ifndef FIT_H_INCLUDED
+#define FIT_H_INCLUDED
+
+#include "autotrace.h"
+#include "image-proc.h"
+#include "pxl-outline.h"
+#include "spline.h"
+#include "exception.h"
+
+/* See fit.c for descriptions of these variables, all of which can be
+   set using options. 
+*/
+typedef at_fitting_opts_type fitting_opts_type;
+
+void
+fit_outlines_to_splines(pixel_outline_list_type  const pixelOutlineList,
+                        fitting_opts_type *      const fittingOptsP,
+                        distance_map_type *      const dist,
+                        unsigned short           const width,
+                        unsigned short           const height,
+                        at_exception_type *      const exception,
+                        at_progress_func               notifyProgress, 
+                        void *                   const progressData,
+                        at_testcancel_func             testCancel,
+                        void *                   const testcancelData,
+                        spline_list_array_type * const splineListArrayP);
+
+/* Get a new set of fitting options */
+fitting_opts_type
+new_fitting_opts(void);
+
+#endif
diff --git a/converter/other/pamtosvg/image-header.h b/converter/other/pamtosvg/image-header.h
new file mode 100644
index 00000000..adcf4771
--- /dev/null
+++ b/converter/other/pamtosvg/image-header.h
@@ -0,0 +1,19 @@
+/* image-header.h: declarations for a generic image header. */
+
+#ifndef IMAGE_HEADER_H
+#define IMAGE_HEADER_H
+
+
+/* The important general information about the image data.
+   See `get_{img,pbm}_header' for the full details of the headers for
+   the particular formats.  */
+typedef struct
+{
+  unsigned short hres, vres;	/* In pixels per inch.  */
+  unsigned short width, height;	/* In bits.  */
+  unsigned short depth;		/* Perhaps the depth?  */
+  unsigned format;		/* (for pbm) Whether packed or not.  */
+} image_header_type;
+
+#endif /* not IMAGE_HEADER_H */
+
diff --git a/converter/other/pamtosvg/image-proc.c b/converter/other/pamtosvg/image-proc.c
new file mode 100644
index 00000000..287e6384
--- /dev/null
+++ b/converter/other/pamtosvg/image-proc.c
@@ -0,0 +1,516 @@
+/* image-proc.c: image processing routines */
+
+#include <assert.h>
+#include <math.h>
+#include <string.h>
+
+#include "mallocvar.h"
+
+#include "message.h"
+
+#include "image-proc.h"
+
+#define BLACK 0
+#define WHITE 0xff
+#ifndef M_SQRT2
+#define M_SQRT2 1.41421356237
+#endif
+
+#if 0
+struct etyp { int t00, t11, t01, t01s; };
+
+
+static bool get_edge(bitmap_type, int y, int x, struct etyp *t);
+static void check(int v1, int v2, int v3, struct etyp *t);
+#endif
+
+
+/* Allocate storage for a new distance map with the same dimensions
+   as BITMAP and initialize it so that pixels in BITMAP with value
+   TARGET_VALUE are at distance zero and all other pixels are at
+   distance infinity.  Then compute the gray-weighted distance from
+   every non-target point to the nearest target point. */
+
+distance_map_type
+new_distance_map(bitmap_type bitmap, unsigned char target_value, bool padded, at_exception_type * exp)
+{
+    signed x, y;
+    float d, min;
+    distance_map_type dist;
+    unsigned char *b = bitmap.bitmap;
+    unsigned w = bitmap.width;
+    unsigned h = bitmap.height;
+    unsigned spp = bitmap.np;
+
+    dist.height = h; dist.width = w;
+    MALLOCARRAY(dist.d, h);
+    MALLOCARRAY(dist.weight, h);
+    if (dist.d == NULL || dist.weight == NULL)
+        pm_error("Unable to get memory for distance map");
+    for (y = 0; y < (signed) h; y++)
+    {
+        MALLOCARRAY(dist.d[y], w);
+        if (dist.d[y] == NULL)
+            pm_error("Unable to get memory for distance map");
+        bzero(dist.d[y], w * sizeof(float));
+        
+        MALLOCARRAY(dist.weight[y], w);
+        if (dist.weight[y] == NULL)
+            pm_error("Unable to get memory for distance map");
+    }
+
+    if (spp == 3)
+    {
+      for (y = 0; y < (signed) h; y++)
+      {
+        for (x = 0; x < (signed) w; x++, b += spp)
+        {
+          int gray; float fgray;
+          gray = (int)LUMINANCE(b[0], b[1], b[2]);
+          dist.d[y][x] = (gray == target_value ? 0.0F : 1.0e10F);
+          fgray = gray * 0.0039215686F;  /* = gray / 255.0F */
+          dist.weight[y][x] = 1.0F - fgray;
+/*        dist.weight[y][x] = 1.0F - (fgray * fgray);*/
+/*        dist.weight[y][x] = (fgray < 0.5F ? 1.0F - fgray : -2.0F * fgray * (fgray - 1.0F));*/
+	    }
+      }
+    }
+    else
+    {
+      for (y = 0; y < (signed) h; y++)
+      {
+        for (x = 0; x < (signed) w; x++, b += spp)
+        {
+          int gray; float fgray;
+          gray = b[0];
+          dist.d[y][x] = (gray == target_value ? 0.0F : 1.0e10F);
+          fgray = gray * 0.0039215686F;  /* = gray / 255.0F */
+          dist.weight[y][x] = 1.0F - fgray;
+/*        dist.weight[y][x] = 1.0F - (fgray * fgray);*/
+/*        dist.weight[y][x] = (fgray < 0.5F ? 1.0F - fgray : -2.0F * fgray * (fgray - 1.0F)); */
+        }
+      }
+    }
+
+    /* If the image is padded then border points are all at most
+       one unit away from the nearest target point. */
+    if (padded)
+    {
+        for (y = 0; y < (signed) h; y++)
+        {
+            if (dist.d[y][0] > dist.weight[y][0])
+                dist.d[y][0] = dist.weight[y][0];
+            if (dist.d[y][w - 1] > dist.weight[y][w - 1])
+                dist.d[y][w - 1] = dist.weight[y][w - 1];
+        }
+        for (x = 0; x < (signed) w; x++)
+        {
+            if (dist.d[0][x] > dist.weight[0][x])
+                dist.d[0][x] = dist.weight[0][x];
+            if (dist.d[h - 1][x] > dist.weight[h - 1][x])
+                dist.d[h - 1][x] = dist.weight[h - 1][x];
+        }
+    }
+
+    /* Scan the image from left to right, top to bottom.
+       Examine the already-visited neighbors of each point (those
+       situated above or to the left of it).  Each neighbor knows
+       the distance to its nearest target point; add to this distance
+       the distance from the central point to the neighbor (either
+       sqrt(2) or one) multiplied by the central point's weight
+       (derived from its gray level).  Replace the distance already
+       stored at the central point if the new distance is smaller. */
+    for (y = 1; y < (signed) h; y++)
+    {
+        for (x = 1; x < (signed) w; x++)
+        {
+            if (dist.d[y][x] == 0.0F) continue;
+
+            min = dist.d[y][x];
+
+            /* upper-left neighbor */
+            d = dist.d[y - 1][x - 1] + (float) M_SQRT2 * dist.weight[y][x];
+            if (d < min) min = dist.d[y][x] = d;
+
+            /* upper neighbor */
+            d = dist.d[y - 1][x] + dist.weight[y][x];
+            if (d < min) min = dist.d[y][x] = d;
+
+            /* left neighbor */
+            d = dist.d[y][x - 1] + dist.weight[y][x];
+            if (d < min) min = dist.d[y][x] = d;
+
+            /* upper-right neighbor (except at the last column) */
+            if (x + 1 < (signed) w)
+            {
+                d = dist.d[y - 1][x + 1] + (float) M_SQRT2 * dist.weight[y][x];
+                if (d < min) min = dist.d[y][x] = d;
+            }
+        }
+    }
+
+    /* Same as above, but now scanning right to left, bottom to top. */
+    for (y = h - 2; y >= 0; y--)
+    {
+        for (x = w - 2; x >= 0; x--)
+        {
+            min = dist.d[y][x];
+
+            /* lower-right neighbor */
+            d = dist.d[y + 1][x + 1] + (float) M_SQRT2 * dist.weight[y][x];
+	        if (d < min) min = dist.d[y][x] = d;
+
+            /* lower neighbor */
+            d = dist.d[y + 1][x] + dist.weight[y][x];
+	        if (d < min) min = dist.d[y][x] = d;
+
+            /* right neighbor */
+            d = dist.d[y][x + 1] + dist.weight[y][x];
+	        if (d < min) min = dist.d[y][x] = d;
+
+            /* lower-left neighbor (except at the first column) */
+            if (x - 1 >= 0)
+            {
+                d = dist.d[y + 1][x - 1] + (float) M_SQRT2 * dist.weight[y][x];
+                if (d < min) min = dist.d[y][x] = d;
+            }
+        }
+    }
+    return dist;
+}
+
+
+/* Free the dynamically-allocated storage associated with a distance map. */
+
+void
+free_distance_map(distance_map_type *dist)
+{
+    unsigned y, h;
+
+    if (!dist) return;
+
+    h = dist->height;
+
+    if (dist->d != NULL)
+    {
+	for (y = 0; y < h; y++)
+	    free(dist->d[y]);
+        free(dist->d);
+    }
+    if (dist->weight != NULL)
+    {
+	for (y = 0; y < h; y++)
+	    free(dist->weight[y]);
+        free(dist->weight);
+    }
+}
+
+
+#if 0
+void
+medial_axis(bitmap_type *bitmap, distance_map_type *dist,
+            bool bgSpec, pixel bg_color)
+{
+    unsigned x, y, test;
+    unsigned w, h;
+    unsigned char *b;
+    float **d, f;
+    pixel bg;
+
+    assert(bitmap != NULL);
+
+    assert(BITMAP_PLANES(*bitmap) == 1);
+
+    b = BITMAP_BITS(*bitmap);
+    assert(b != NULL);
+    assert(dist != NULL);
+    d = dist->d;
+    assert(d != NULL);
+
+    h = BITMAP_HEIGHT(*dist);
+    w = BITMAP_WIDTH(*dist);
+    assert(BITMAP_WIDTH(*bitmap) == w && BITMAP_HEIGHT(*bitmap) == h);
+
+    if (bgSpec)
+        bg = bg_color;
+    else 
+        PPM_ASSIGN(bg, 255, 255, 255);
+
+    f = d[0][0] + 0.5;
+    test = (f < d[1][0]) + (f < d[1][1]) + (f < d[0][1]);
+    if (test > 1) b[0] = PPM_GETR(bg);
+
+    f = d[0][w-1] + 0.5;
+    test = (f < d[1][w-1]) + (f < d[1][w-2]) + (f < d[0][w-2]);
+    if (test > 1) b[w-1] = PPM_GETR(bg);
+
+    for (x = 1; x < w - 1; x++)
+    {
+	    f = d[0][x] + 0.5;
+	    test = (f < d[0][x-1]) + (f < d[0][x+1]) + (f < d[1][x-1])
+	        + (f < d[1][x]) + (f < d[1][x+1]);
+	    if (test > 1) b[x] = PPM_GETR(bg);
+    }
+    b += w;
+
+    for (y = 1; y < h - 1; y++)
+    {
+	    f = d[y][0] + 0.5;
+	    test = (f < d[y-1][0]) + (f < d[y-1][1]) + (f < d[y][1])
+	        + (f < d[y+1][0]) + (f < d[y+1][1]);
+	    if (test > 1) b[0] = PPM_GETR(bg);
+
+	    for (x = 1; x < w - 1; x++)
+		{
+	        f = d[y][x] + 0.5;
+	        test = (f < d[y-1][x-1]) + (f < d[y-1][x]) + (f < d[y-1][x+1])
+		    + (f < d[y][x-1]) + (f < d[y][x+1])
+		    + (f < d[y+1][x-1]) + (f < d[y+1][x]) + (f < d[y+1][x+1]);
+	        if (test > 1) b[x] = PPM_GETR(bg)
+		}
+
+	    f = d[y][w-1] + 0.5;
+	    test = (f < d[y-1][w-1]) + (f < d[y-1][w-2]) + (f < d[y][w-2])
+	        + (f < d[y+1][w-1]) + (f < d[y+1][w-2]);
+	    if (test > 1)
+	        b[w-1] = PPM_GETR(bg);
+
+        b += w;
+    }
+
+    for (x = 1; x < w - 1; x++)
+    {
+	    f = d[h-1][x] + 0.5;
+	    test = (f < d[h-1][x-1]) + (f < d[h-1][x+1])
+	        + (f < d[h-2][x-1]) + (f < d[h-2][x]) + (f < d[h-2][x+1]);
+	    if (test > 1) b[x] = PPM_GETR(bg);
+    }
+
+    f = d[h-1][0] + 0.5;
+    test = (f < d[h-2][0]) + (f < d[h-2][1]) + (f < d[h-1][1]);
+    if (test > 1) b[0] = PPM_GETR(bg);
+
+    f = d[h-1][w-1] + 0.5;
+    test = (f < d[h-2][w-1]) + (f < d[h-2][w-2]) + (f < d[h-1][w-2]);
+    if (test > 1) b[w-1] = PPM_GETR(bg);
+}
+#endif
+
+
+/* Binarize a grayscale or color image. */
+
+void
+binarize(bitmap_type *bitmap)
+{
+    unsigned i, npixels, spp;
+    unsigned char *b;
+
+    assert(bitmap != NULL);
+    assert(bitmap->bitmap != NULL);
+
+    b = bitmap->bitmap;
+    spp = bitmap->np;
+    npixels = bitmap->width * bitmap->height;
+
+    if (spp == 1)
+    {
+	    for (i = 0; i < npixels; i++)
+	        b[i] = (b[i] > GRAY_THRESHOLD ? WHITE : BLACK);
+    }
+    else if (spp == 3)
+    {
+	    unsigned char *rgb = b;
+	    for (i = 0; i < npixels; i++, rgb += 3)
+		{
+	        b[i] = (LUMINANCE(rgb[0], rgb[1], rgb[2]) > GRAY_THRESHOLD
+		        ? WHITE : BLACK);
+		}
+	    REALLOCARRAY_NOFAIL(bitmap->bitmap, npixels);
+	    bitmap->np = 1;
+    }
+    else
+    {
+	    WARNING1("binarize: %u-plane images are not supported", spp);
+    }
+}
+
+
+#if 0
+/* Thin a binary image, replacing the original image with the thinned one. */
+
+bitmap_type
+ip_thin(bitmap_type input_b)
+{
+    unsigned y, x, i;
+    bool k, again;
+    struct etyp t;
+    unsigned w = BITMAP_WIDTH(input_b);
+    unsigned h = BITMAP_HEIGHT(input_b);
+    size_t num_bytes = w * h;
+    bitmap_type b = input_b;
+
+    if (BITMAP_PLANES(input_b) != 1)
+    {
+	    FATAL1("thin: single-plane image required; "
+	        "%u-plane images cannot be thinned", BITMAP_PLANES(input_b));
+	    return b;
+    }
+
+    /* Process and return a copy of the input image. */
+    MALLOCARRAY(b.bitmap, num_bytes);
+    if (b.bitmap == NULL)
+        pm_error("Unable to get memory for bitmap");
+    memcpy(b.bitmap, input_b.bitmap, num_bytes);
+
+    /* Set background pixels to zero, foreground pixels to one. */
+    for (i = 0; i < num_bytes; i++)
+	b.bitmap[i] = (b.bitmap[i] == BLACK ? 1 : 0);
+
+    again = true;
+    while (again)
+    {
+	again = false;
+
+	for (y = 1; y < h - 1; y++)
+	{
+	    for (x = 1; x < w - 1; x++)
+	    {
+		    /* During processing, pixels are used to store edge
+		       type codes, so we can't just test for WHITE or BLACK. */
+		    if (*BITMAP_PIXEL(b, y, x) == 0) continue;
+
+		    k = (!get_edge(b, y, x, &t)
+		        || (get_edge(b, y, x+1, &t) && *BITMAP_PIXEL(b, y-1, x)
+			    && *BITMAP_PIXEL(b, y+1, x))
+		        || (get_edge(b, y+1, x, &t) && *BITMAP_PIXEL(b, y, x-1)
+			    && *BITMAP_PIXEL(b, y, x+1))
+		        || (get_edge(b, y, x+1, &t) && get_edge(b, y+1, x+1, &t)
+			    && get_edge(b, y+1, x, &t)));
+		    if (k) continue;
+
+		    get_edge(b, y, x, &t);
+		    if (t.t01) *BITMAP_PIXEL(b, y, x) |= 4;
+		    *BITMAP_PIXEL(b, y, x) |= 2;
+		    again = true;
+	    }
+	}
+
+	for (y = 0; y < h; y++)
+	    for (x = 0; x < w; x++)
+		    if (*BITMAP_PIXEL(b, y, x) & 02) *BITMAP_PIXEL(b, y, x) = 0;
+
+	for (y = 1; y < h - 1; y++)
+	{
+	    for (x = 1; x < w - 1; x++)
+	    {
+		    if (*BITMAP_PIXEL(b, y, x) == 0) continue;
+
+		    k = (!get_edge(b, y, x, &t)
+		        || ((*BITMAP_PIXEL(b, y, x) & 04) == 0)
+		        || (get_edge(b, y+1, x, &t) && (*BITMAP_PIXEL(b, y, x-1))
+			    && *BITMAP_PIXEL(b, y, x+1))
+		        || (get_edge(b, y, x+1, &t) && *BITMAP_PIXEL(b, y-1, x)
+			    && *BITMAP_PIXEL(b, y+1, x))
+		        || (get_edge(b, y+1, x, &t) && get_edge(b, y, x+1, &t)
+			    && get_edge(b, y+1, x+1, &t)));
+		    if (k) continue;
+
+		    *BITMAP_PIXEL(b, y, x) |= 02;
+		    again = true;
+	    }
+	}
+
+	for (y = 0; y < h; y++)
+	{
+	    for (x = 0; x < w; x++)
+	    {
+		    if (*BITMAP_PIXEL(b, y, x) & 02) *BITMAP_PIXEL(b, y, x) = 0;
+		    else if (*BITMAP_PIXEL(b, y, x) > 0) *BITMAP_PIXEL(b, y, x) = 1;
+	    }
+	}
+    }
+
+    /* Staircase removal; northward bias. */
+    for (y = 1; y < h - 1; y++)
+    {
+	    for (x = 1; x < w - 1; x++)
+		{
+	        if (*BITMAP_PIXEL(b, y, x) == 0) continue;
+
+	        k = !(*BITMAP_PIXEL(b, y-1, x)
+		        && ((*BITMAP_PIXEL(b, y, x+1) && !*BITMAP_PIXEL(b, y-1, x+1)
+		        && !*BITMAP_PIXEL(b, y+1, x-1)
+		        && (!*BITMAP_PIXEL(b, y, x-1) || !*BITMAP_PIXEL(b, y+1, x)))
+		        || (*BITMAP_PIXEL(b, y, x-1) && !*BITMAP_PIXEL(b, y-1, x-1)
+		        && !*BITMAP_PIXEL(b, y+1, x+1) &&
+		        (!*BITMAP_PIXEL(b, y, x+1) || !*BITMAP_PIXEL(b, y+1, x)))));
+	        if (k) continue;
+
+	        *BITMAP_PIXEL(b, y, x) |= 02;
+		}
+    }
+    for (y = 0; y < h; y++)
+    {
+	    for (x = 0; x < w; x++)
+		{
+	        if (*BITMAP_PIXEL(b, y, x) & 02) *BITMAP_PIXEL(b, y, x) = 0;
+	        else if (*BITMAP_PIXEL(b, y, x) > 0) *BITMAP_PIXEL(b, y, x) = 1;
+		}
+    }
+
+    /* Southward bias */
+    for (y = 1; y < h - 1; y++)
+    {
+	    for (x = 1; x < w - 1; x++)
+		{
+	        if (*BITMAP_PIXEL(b, y, x) == 0) continue;
+
+	        k = !(*BITMAP_PIXEL(b, y+1, x)
+		    && ((*BITMAP_PIXEL(b, y, x+1) && !*BITMAP_PIXEL(b, y+1, x+1)
+		    && !*BITMAP_PIXEL(b, y-1, x-1) && (!*BITMAP_PIXEL(b, y, x-1)
+		    || !*BITMAP_PIXEL(b, y-1, x))) || (*BITMAP_PIXEL(b, y, x-1)
+		    && !*BITMAP_PIXEL(b, y+1, x-1) && !*BITMAP_PIXEL(b, y-1, x+1)
+		    && (!*BITMAP_PIXEL(b, y, x+1) || !*BITMAP_PIXEL(b, y-1, x)) )));
+	        if (k) continue;
+
+	        *BITMAP_PIXEL(b, y, x) |= 02;
+		}
+    }
+    for (y = 0; y < h; y++)
+    {
+	    for (x = 0; x < w; x++)
+		{
+	        if (*BITMAP_PIXEL(b, y, x) & 02) *BITMAP_PIXEL(b, y, x) = 0;
+	        else if (*BITMAP_PIXEL(b, y, x) > 0) *BITMAP_PIXEL(b, y, x) = 1;
+		}
+    }
+
+    /* Set background pixels to WHITE, foreground pixels to BLACK. */
+    for (i = 0; i < num_bytes; i++)
+	b.bitmap[i] = (b.bitmap[i] == 0 ? WHITE : BLACK);
+    return b;
+}
+
+
+bool get_edge(bitmap_type b, int y, int x, struct etyp *t)
+{
+    t->t00 = 0; t->t01 = 0; t->t01s = 0; t->t11 = 0;
+    check(*BITMAP_PIXEL(b, y - 1, x - 1), *BITMAP_PIXEL(b, y - 1, x),
+	*BITMAP_PIXEL(b, y - 1, x + 1), t);
+    check(*BITMAP_PIXEL(b, y - 1, x + 1), *BITMAP_PIXEL(b, y, x + 1),
+	*BITMAP_PIXEL(b, y + 1, x + 1), t);
+    check(*BITMAP_PIXEL(b, y + 1, x + 1), *BITMAP_PIXEL(b, y + 1, x),
+	*BITMAP_PIXEL(b, y + 1, x - 1), t);
+    check(*BITMAP_PIXEL(b, y + 1, x - 1), *BITMAP_PIXEL(b, y, x - 1),
+	*BITMAP_PIXEL(b, y - 1, x - 1), t);
+    return *BITMAP_PIXEL(b, y, x) && t->t00 && t->t11 && !t->t01s;
+}
+
+
+void check(int v1, int v2, int v3, struct etyp *t)
+{
+    if (!v2 && (!v1 || !v3)) t->t00 = 1;
+    if (v2 && (v1 || v3)) t->t11 = 1;
+    if ((!v1 && v2) || (!v2 && v3)) { t->t01s = t->t01; t->t01 = 1; }
+}
+#endif
diff --git a/converter/other/pamtosvg/image-proc.h b/converter/other/pamtosvg/image-proc.h
new file mode 100644
index 00000000..a5b86ec1
--- /dev/null
+++ b/converter/other/pamtosvg/image-proc.h
@@ -0,0 +1,42 @@
+/* image-proc.h: image processing routines */
+
+#ifndef IMAGE_PROC_H
+#define IMAGE_PROC_H
+
+#include "bitmap.h"
+#include "exception.h"
+
+
+/* Threshold for binarizing a monochrome image */
+#define GRAY_THRESHOLD 225
+
+/* RGB to grayscale */
+#define LUMINANCE(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11 + 0.5)
+
+
+typedef struct
+{
+  unsigned height, width;
+  float **weight;
+  float **d;
+} distance_map_type;
+
+
+/* Allocate and compute a new distance map. */
+extern distance_map_type new_distance_map(bitmap_type,
+    unsigned char target_value, bool padded,
+					  at_exception_type * exp);
+
+/* Free the dynamically-allocated storage associated with a distance map. */
+extern void free_distance_map(distance_map_type*);
+
+
+/* Binarize a grayscale or color image. */
+extern void binarize(bitmap_type*);
+
+
+/* Thin a binary image, replacing the original image with the thinned one. */
+extern bitmap_type ip_thin(bitmap_type);
+
+#endif /* not IMAGE_PROC_H */
+
diff --git a/converter/other/pamtosvg/logreport.c b/converter/other/pamtosvg/logreport.c
new file mode 100644
index 00000000..7d726584
--- /dev/null
+++ b/converter/other/pamtosvg/logreport.c
@@ -0,0 +1,17 @@
+/* logreport.c: showing information to the user. */
+
+#include "logreport.h"
+#include "message.h"
+
+/* Says whether to output detailed progress reports, i.e., all the data
+   on the fitting, as we run.  (-log)  */
+FILE *log_file = NULL;
+
+
+void
+flush_log_output (void)
+{
+  if (log_file)
+    fflush (log_file);
+}
+
diff --git a/converter/other/pamtosvg/logreport.h b/converter/other/pamtosvg/logreport.h
new file mode 100644
index 00000000..577da8df
--- /dev/null
+++ b/converter/other/pamtosvg/logreport.h
@@ -0,0 +1,28 @@
+/* logreport.h: status reporting routines. */
+
+#ifndef LOGREPORT_H
+#define LOGREPORT_H
+
+#include <stdio.h>
+
+/* The file we write information to.  */
+extern FILE *at_log_file;
+#define log_file at_log_file
+
+extern void flush_log_output (void);
+
+#define LOG(s)								\
+  do { if (log_file) fputs (s, log_file); } while (0)
+#define LOG1(s, e)							\
+  do { if (log_file) fprintf (log_file, s, e); } while (0)
+#define LOG2(s, e1, e2)							\
+  do { if (log_file) fprintf (log_file, s, e1, e2); } while (0)
+#define LOG3(s, e1, e2, e3)						\
+  do { if (log_file) fprintf (log_file, s, e1, e2, e3); } while (0)
+#define LOG4(s, e1, e2, e3, e4)						\
+  do { if (log_file) fprintf (log_file, s, e1, e2, e3, e4); } while (0)
+#define LOG5(s, e1, e2, e3, e4, e5)					\
+  do { if (log_file) fprintf (log_file, s, e1, e2, e3, e4, e5); } while (0)
+
+#endif /* not LOGREPORT_H */
+
diff --git a/converter/other/pamtosvg/message.h b/converter/other/pamtosvg/message.h
new file mode 100644
index 00000000..0a226206
--- /dev/null
+++ b/converter/other/pamtosvg/message.h
@@ -0,0 +1,47 @@
+/* message.h: extend the standard programming environment a little. */
+
+#ifndef MESSAGE_H
+#define MESSAGE_H
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "logreport.h"
+
+/* Define common sorts of messages.  */
+
+/* This should be called only after a system call fails.  */
+#define FATAL_PERROR(s) do { perror (s); exit (errno); } while (0)
+
+
+#define START_FATAL() do { fputs ("fatal: ", stderr); LOG("fatal: ")
+#define END_FATAL() fputs (".\n", stderr); exit (1); } while (0)
+
+#define FATAL(s)							\
+  START_FATAL (); fprintf (stderr, "%s", s); LOG (s); END_FATAL ()
+#define FATAL1(s, e1)							\
+  START_FATAL (); fprintf (stderr, s, e1); LOG1 (s, e1); END_FATAL ()
+#define FATAL2(s, e1, e2)						\
+  START_FATAL (); fprintf (stderr, s, e1, e2); LOG2 (s, e1, e2); END_FATAL ()
+#define FATAL3(s, e1, e2, e3)						\
+  START_FATAL (); fprintf (stderr, s, e1, e2, e3); LOG3 (s, e1, e2, e3); END_FATAL ()
+#define FATAL4(s, e1, e2, e3, e4)					\
+  START_FATAL (); fprintf (stderr, s, e1, e2, e3, e4); LOG4 (s, e1, e2, e3, e4); END_FATAL ()
+
+
+#define START_WARNING() do { fputs ("warning: ", stderr); LOG ("warning: ")
+#define END_WARNING() fputs (".\n", stderr); } while (0)
+
+#define WARNING(s)							\
+  START_WARNING (); fprintf (stderr, "%s", s); LOG (s); END_WARNING ()
+#define WARNING1(s, e1)							\
+  START_WARNING (); fprintf (stderr, s, e1); LOG1 (s, e1); END_WARNING ()
+#define WARNING2(s, e1, e2)						\
+  START_WARNING (); fprintf (stderr, s, e1, e2); LOG2 (s, e1, e2); END_WARNING ()
+#define WARNING3(s, e1, e2, e3)						\
+  START_WARNING (); fprintf (stderr, s, e1, e2, e3); LOG3 (s, e1, e2, e3); END_WARNING ()
+#define WARNING4(s, e1, e2, e3, e4)					\
+  START_WARNING (); fprintf (stderr, s, e1, e2, e3, e4); LOG4 (s, e1, e2, e3, e4); END_WARNING ()
+
+#endif /* not MESSAGE_H */
+
diff --git a/converter/other/pamtosvg/output-svg.c b/converter/other/pamtosvg/output-svg.c
new file mode 100644
index 00000000..53a8d4fd
--- /dev/null
+++ b/converter/other/pamtosvg/output-svg.c
@@ -0,0 +1,130 @@
+/* output-svg.h - output in SVG format
+
+   Copyright (C) 1999, 2000, 2001 Bernhard Herzog
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public License
+   as published by the Free Software Foundation; either version 2.1 of
+   the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+   USA. */
+
+#include "spline.h"
+#include "output-svg.h"
+
+
+
+static void
+outSplineList(FILE *           const fileP,
+              spline_list_type const splineList,
+              unsigned int     const height) {
+              
+    unsigned splineSeq;
+        
+    for (splineSeq = 0;
+         splineSeq < SPLINE_LIST_LENGTH(splineList);
+         ++splineSeq) {
+        
+        spline_type const spline = SPLINE_LIST_ELT(splineList, splineSeq);
+        
+        if (SPLINE_DEGREE(spline) == LINEARTYPE) {
+            fprintf(fileP, "L%g %g",
+                    END_POINT(spline).x, height - END_POINT(spline).y);
+        } else
+            fprintf(fileP, "C%g %g %g %g %g %g",
+                    CONTROL1(spline).x, height  - CONTROL1(spline).y,
+                    CONTROL2(spline).x, height  - CONTROL2(spline).y,
+                    END_POINT(spline).x, height - END_POINT(spline).y);
+    }
+}
+
+
+
+static void
+out_splines(FILE *                 const fileP,
+            spline_list_array_type const shape,
+            unsigned int           const height) {
+
+    unsigned listSeq;
+    pixel lastColor;
+    
+    PPM_ASSIGN(lastColor, 0, 0, 0);
+    
+    for (listSeq = 0;
+         listSeq < SPLINE_LIST_ARRAY_LENGTH(shape);
+         ++listSeq) {
+        
+        spline_list_type const splineList =
+            SPLINE_LIST_ARRAY_ELT(shape, listSeq);
+        spline_type const first = SPLINE_LIST_ELT(splineList, 0);
+
+        if (listSeq == 0 || !PPM_EQUAL(splineList.color, lastColor)) {
+            if (listSeq > 0) {
+                /* Close previous <path> element */
+                if (!(shape.centerline || splineList.open))
+                    fputs("z", fileP);
+                fputs("\"/>\n", fileP);
+            }
+            /* Open new <path> element */
+            fprintf(fileP, "<path style=\"%s:#%02x%02x%02x; %s:none;\" d=\"",
+                    (shape.centerline || splineList.open) ? "stroke" : "fill",
+                    PPM_GETR(splineList.color),
+                    PPM_GETG(splineList.color),
+                    PPM_GETB(splineList.color),
+                    (shape.centerline || splineList.open) ? "fill" : "stroke");
+        }
+        fprintf(fileP, "M%g %g",
+                START_POINT(first).x, height - START_POINT(first).y);
+
+        outSplineList(fileP, splineList, height);
+
+        lastColor = splineList.color;
+    }
+
+    if (SPLINE_LIST_ARRAY_LENGTH(shape) > 0) {
+        spline_list_type const lastSplineList =
+            SPLINE_LIST_ARRAY_ELT(shape, SPLINE_LIST_ARRAY_LENGTH(shape)-1);
+
+        if (!(shape.centerline || lastSplineList.open))
+            fputs("z", fileP);
+
+        /* Close last <path> element */
+        fputs("\"/>\n", fileP);
+    }
+}
+
+
+
+int
+output_svg_writer(FILE *                    const fileP,
+                  const char *              const name,
+                  int                       const llx,
+                  int                       const lly,
+                  int                       const urx,
+                  int                       const ury, 
+                  at_output_opts_type *     const opts,
+                  at_spline_list_array_type const shape,
+                  at_msg_func                     msg_func, 
+                  void *                    const msg_data) {
+
+    int const width  = urx - llx;
+    int const height = ury - lly;
+
+    fputs("<?xml version=\"1.0\" standalone=\"yes\"?>\n", fileP);
+
+    fprintf(fileP, "<svg width=\"%d\" height=\"%d\">\n", width, height);
+
+    out_splines(fileP, shape, height);
+
+    fputs("</svg>\n", fileP);
+    
+    return 0;
+}
diff --git a/converter/other/pamtosvg/output-svg.h b/converter/other/pamtosvg/output-svg.h
new file mode 100644
index 00000000..46fc8f8f
--- /dev/null
+++ b/converter/other/pamtosvg/output-svg.h
@@ -0,0 +1,37 @@
+/* output-svg.h - output in SVG format
+
+   Copyright (C) 1999, 2000, 2001 Bernhard Herzog
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public License
+   as published by the Free Software Foundation; either version 2.1 of
+   the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+   USA. */
+
+#ifndef OUTPUT_SVG_H
+#define OUTPUT_SVG_H
+
+int
+output_svg_writer(FILE *                    const file,
+                  const char *              const name,
+                  int                       const llx,
+                  int                       const lly,
+                  int                       const urx,
+                  int                       const ury, 
+                  at_output_opts_type *     const opts,
+                  at_spline_list_array_type const shape,
+                  at_msg_func                     msg_func, 
+                  void *                    const msg_data);
+
+
+#endif /* not OUTPUT_SVG_H */
+
diff --git a/converter/other/pamtosvg/pamtosvg.c b/converter/other/pamtosvg/pamtosvg.c
new file mode 100644
index 00000000..31597a4a
--- /dev/null
+++ b/converter/other/pamtosvg/pamtosvg.c
@@ -0,0 +1,395 @@
+/* main.c: main driver for autotrace -- convert bitmaps to splines. */
+
+#include <string.h>
+#include <assert.h>
+#include <math.h>
+
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "pam.h"
+
+#include "autotrace.h"
+#include "message.h"
+#include "logreport.h"
+#include "output-svg.h"
+#include "bitmap.h"
+
+#define dot_printer_max_column 50
+#define dot_printer_char '|'
+
+
+
+static void
+readImageToBitmap(FILE *            const ifP,
+                  at_bitmap_type ** const bitmapPP) {
+
+    at_bitmap_type * bitmapP;
+    struct pam pam;
+    tuple ** tuples;
+    uint row;
+    tuple * row255;
+
+    MALLOCVAR_NOFAIL(bitmapP);
+
+    tuples = pnm_readpam(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
+
+    bitmapP->width  = pam.width;
+    bitmapP->height = pam.height;
+    bitmapP->np     = pam.depth;
+
+    MALLOCARRAY(bitmapP->bitmap, pam.width * pam.height * pam.depth);
+
+    row255 = pnm_allocpamrow(&pam);
+
+    for (row = 0; row < pam.height; ++row) {
+        unsigned int col;
+
+        pnm_scaletuplerow(&pam, row255, tuples[row], 255);
+        
+        for (col = 0; col < pam.width; ++col) {
+            unsigned int plane;
+
+            for (plane = 0; plane < pam.depth; ++plane) {
+                unsigned int const bitmapIndex = 
+                    (row * pam.width + col) * pam.depth + plane;
+                bitmapP->bitmap[bitmapIndex] = row255[col][plane];
+            }
+        }
+    }
+    pnm_freepamrow(row255);
+    pnm_freepamarray(tuples, &pam);
+    
+    *bitmapPP = bitmapP;
+}
+
+
+
+static void
+dotPrinter(float  const percentage,
+           void * const clientData) {
+
+    int * const currentP = (int *)clientData;
+    float const unit     = (float)1.0 / (float)(dot_printer_max_column) ;
+    int   const maximum  = (int)(percentage / unit);
+    
+    while (*currentP < maximum) {
+        fputc(dot_printer_char, stderr);
+        (*currentP)++;
+    }
+}
+
+
+
+static void
+exceptionHandler(const char * const msg,
+                 at_msg_type  const type,
+                 void *       const data) {
+
+    if (type == AT_MSG_FATAL)
+        pm_error("%s", msg);
+    else if (type == AT_MSG_WARNING)
+        pm_message("%s", msg);
+    else
+        exceptionHandler("Wrong type of msg", AT_MSG_FATAL, NULL);
+}
+
+
+
+struct cmdlineInfo {
+    const char * inputFileName;
+    float        align_threshold;
+    unsigned int backgroundSpec;
+    pixel        background_color;
+    unsigned int centerline;
+    float        corner_always_threshold;
+    unsigned int corner_surround;
+    float        corner_threshold;
+    unsigned int dpi;
+    float        error_threshold;
+    unsigned int filter_iterations;
+    float        line_reversion_threshold;
+    float        line_threshold;
+    unsigned int log;
+    unsigned int preserve_width;
+    unsigned int remove_adjacent_corners;
+    unsigned int tangent_surround;
+    unsigned int report_progress;
+    float        width_weight_factor;
+};
+
+
+static void 
+parseCommandLine(int argc, 
+                 char ** argv, 
+                 struct cmdlineInfo  * const cmdlineP) {
+/* --------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+--------------------------------------------------------------------------*/
+    optEntry *option_def;
+    /* Instructions to optParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+
+    const char * background_colorOpt;
+  
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "align-threshold",     OPT_FLOAT,
+            &cmdlineP->align_threshold,  NULL,                              0);
+    OPTENT3(0, "background-color",    OPT_STRING,
+            &background_colorOpt,        &cmdlineP->backgroundSpec,         0);
+    OPTENT3(0, "centerline",          OPT_FLAG,
+            NULL,                        &cmdlineP->centerline,             0);
+    OPTENT3(0, "corner-always-threshold", OPT_FLOAT, 
+            &cmdlineP->corner_always_threshold, NULL,                       0);
+    OPTENT3(0, "corner-surround",     OPT_UINT,
+            &cmdlineP->corner_surround,  NULL,                              0);
+    OPTENT3(0, "corner-threshold",    OPT_FLOAT,
+            &cmdlineP->corner_threshold, NULL,                              0);
+    OPTENT3(0, "dpi",                 OPT_UINT,
+            &cmdlineP->dpi,              NULL,                              0);
+    OPTENT3(0, "error-threshold",     OPT_FLOAT,
+            &cmdlineP->error_threshold,  NULL,                              0);
+    OPTENT3(0, "filter-iterations",   OPT_UINT,
+            &cmdlineP->filter_iterations, NULL,                             0);
+    OPTENT3(0, "line-reversion-threshold", OPT_FLOAT,
+            &cmdlineP->line_reversion_threshold, NULL,                    0);
+    OPTENT3(0, "line-threshold",      OPT_FLOAT,
+            &cmdlineP->line_threshold, NULL,                                0);
+    OPTENT3(0, "log",                 OPT_FLAG,
+            NULL,                         &cmdlineP->log,                   0);
+    OPTENT3(0, "preserve-width",      OPT_FLAG,
+            NULL,                         &cmdlineP->preserve_width,        0);
+    OPTENT3(0, "remove-adjacent-corners", OPT_UINT,
+            NULL,                       &cmdlineP->remove_adjacent_corners, 0);
+    OPTENT3(0, "tangent-surround",    OPT_UINT,    
+            &cmdlineP->tangent_surround, NULL,                              0);
+    OPTENT3(0, "report-progress",     OPT_FLAG,
+            NULL,                       &cmdlineP->report_progress,         0);
+    OPTENT3(0, "width-weight-factor", OPT_FLOAT,    
+            &cmdlineP->width_weight_factor, NULL,                           0);
+
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;   /* We have no parms that are negative numbers */
+
+    /* Set some defaults the lazy way (using multiple setting of variables) */
+
+    cmdlineP->corner_always_threshold  = 60.0;
+    cmdlineP->corner_surround          = 4;
+    cmdlineP->corner_threshold         = 100.0;
+    cmdlineP->error_threshold          = 2.0;
+    cmdlineP->filter_iterations        = 4;
+    cmdlineP->line_reversion_threshold = 0.01;
+    cmdlineP->line_threshold           = 1.0;
+    cmdlineP->tangent_surround         = 3;
+    cmdlineP->width_weight_factor      = 6.0;
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+    /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (cmdlineP->backgroundSpec)
+        cmdlineP->background_color = ppm_parsecolor(background_colorOpt, 255);
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else {
+        cmdlineP->inputFileName = argv[1];
+        
+        if (argc-1 > 1)
+            pm_error("Too many arguments (%u).  The only non-option argument "
+                     "is the input file name.", argc-1);
+    }
+}
+
+
+
+static void
+fitSplines(at_bitmap_type *             const bitmapP,
+           struct cmdlineInfo           const cmdline,
+           at_msg_func                        exceptionHandler,
+           at_progress_func                   progressFunc,
+           at_spline_list_array_type ** const splinesPP) {
+
+    unsigned int progressStat;
+    at_fitting_opts_type * fittingOptsP;
+
+    progressStat = 0;
+           
+    fittingOptsP = at_fitting_opts_new();
+
+    fittingOptsP->backgroundSpec           = cmdline.backgroundSpec;
+    fittingOptsP->background_color         = cmdline.background_color;
+    fittingOptsP->corner_always_threshold  = cmdline.corner_always_threshold;
+    fittingOptsP->corner_surround          = cmdline.corner_surround;
+    fittingOptsP->corner_threshold         = cmdline.corner_threshold;
+    fittingOptsP->error_threshold          = cmdline.error_threshold;
+    fittingOptsP->filter_iterations        = cmdline.filter_iterations;
+    fittingOptsP->line_reversion_threshold = cmdline.line_reversion_threshold;
+    fittingOptsP->line_threshold           = cmdline.line_threshold;
+    fittingOptsP->remove_adjacent_corners  = cmdline.remove_adjacent_corners;
+    fittingOptsP->tangent_surround         = cmdline.tangent_surround;
+    fittingOptsP->centerline               = cmdline.centerline;
+    fittingOptsP->preserve_width           = cmdline.preserve_width;
+    fittingOptsP->width_weight_factor      = cmdline.width_weight_factor;
+
+    *splinesPP = at_splines_new_full(bitmapP, fittingOptsP,
+                                     exceptionHandler, NULL,
+                                     progressFunc, &progressStat,
+                                     NULL, NULL);
+
+    at_fitting_opts_free(fittingOptsP);
+}
+  
+
+
+static void
+writeSplines(at_spline_list_array_type * const splinesP,
+             struct cmdlineInfo          const cmdline,
+             at_output_write_func              outputWriter,
+             FILE *                      const ofP,
+             at_msg_func                       exceptionHandler) {
+
+    at_output_opts_type * outputOptsP;
+
+    outputOptsP = at_output_opts_new();
+    outputOptsP->dpi = cmdline.dpi;
+    
+    at_splines_write(outputWriter, ofP, outputOptsP,
+                     splinesP, exceptionHandler, NULL);
+
+    at_output_opts_free(outputOptsP);
+}  
+
+
+
+static const char *
+filenameRoot(const char * const filename) {
+/*----------------------------------------------------------------------------
+   Return the root of the filename.  E.g. for /home/bryanh/foo.ppm,
+   return 'foo'.
+-----------------------------------------------------------------------------*/
+    char * buffer;
+    bool foundSlash;
+    unsigned int slashPos;
+    bool foundDot;
+    unsigned int dotPos;
+    unsigned int rootStart, rootEnd;
+    unsigned int i, j;
+
+    for (i = 0, foundSlash = FALSE; i < strlen(filename); ++i) {
+        if (filename[i] == '/') {
+            foundSlash = TRUE;
+            slashPos = i;
+        }
+    }
+
+    if (foundSlash)
+        rootStart = slashPos + 1;
+    else
+        rootStart = 0;
+
+    for (i = rootStart, foundDot = FALSE; i < strlen(filename); ++i) {
+        if (filename[i] == '.') {
+            foundDot = TRUE;
+            dotPos = i;
+        }
+    }
+
+    if (foundDot)
+        rootEnd = dotPos;
+    else
+        rootEnd = strlen(filename);
+
+    MALLOCARRAY(buffer, rootEnd - rootStart + 1);
+    
+    j = 0;
+    for (i = rootStart; i < rootEnd; ++i)
+        buffer[j++] = filename[i];
+
+    buffer[j] = '\0';
+    
+    return buffer;
+}
+
+
+
+static void
+openLogFile(FILE **      const logFileP,
+            const char * const inputRootName) {
+
+    const char * logfileName;
+
+    asprintfN(&logfileName, "%s.log", inputRootName);
+
+    *logFileP = pm_openw(logfileName);
+
+    strfree(logfileName);
+}
+    
+
+
+int
+main(int argc, char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    at_bitmap_type * bitmapP;
+    at_spline_list_array_type * splinesP;
+    at_progress_func progressReporter;
+    const char * inputRootName;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    inputRootName = filenameRoot(cmdline.inputFileName);
+    if (inputRootName == NULL)
+        pm_error("Can't find the root portion of file name '%s'",
+                 cmdline.inputFileName);
+    
+    if (cmdline.log)
+        openLogFile(&log_file, inputRootName);
+
+    readImageToBitmap(ifP, &bitmapP);
+    
+    if (cmdline.report_progress) {
+        progressReporter = dotPrinter;
+        fprintf(stderr, "%-15s", cmdline.inputFileName);
+    } else
+        progressReporter = NULL;
+
+    fitSplines(bitmapP, cmdline, exceptionHandler,
+               progressReporter, &splinesP);
+
+    writeSplines(splinesP, cmdline, output_svg_writer, stdout,
+                 exceptionHandler);
+
+    strfree(inputRootName);
+
+    pm_close(stdout);
+    pm_close(ifP);
+    if (cmdline.log)
+        pm_close(log_file);
+    
+    at_splines_free(splinesP);
+    at_bitmap_free(bitmapP);
+
+    if (cmdline.report_progress)
+        fputs("\n", stderr);
+    
+    return 0;
+}
diff --git a/converter/other/pamtosvg/pamtosvg.test b/converter/other/pamtosvg/pamtosvg.test
new file mode 100644
index 00000000..df3a07d3
--- /dev/null
+++ b/converter/other/pamtosvg/pamtosvg.test
@@ -0,0 +1,6 @@
+# This will print nothing if successful (diff will find no difference)
+ppmmake black 20 20 | ppmdraw -script="line 5 2 15 17" | pamtosvg | \
+  diff testline.svg -
+
+# This will print nothing if successful (diff will find no difference)
+pamtosvg ../../../../testgrid.pbm | diff testgrid.svg -
diff --git a/converter/other/pamtosvg/point.h b/converter/other/pamtosvg/point.h
new file mode 100644
index 00000000..037ec8a0
--- /dev/null
+++ b/converter/other/pamtosvg/point.h
@@ -0,0 +1,8 @@
+#ifndef POINT_H_INCLUDED
+#define POINT_H_INCLUDED
+
+typedef struct {
+  float x, y, z;
+} float_coord;
+
+#endif
diff --git a/converter/other/pamtosvg/pxl-outline.c b/converter/other/pamtosvg/pxl-outline.c
new file mode 100644
index 00000000..68cd0565
--- /dev/null
+++ b/converter/other/pamtosvg/pxl-outline.c
@@ -0,0 +1,1370 @@
+/* pxl-outline.c: find the outlines of a bitmap image; each outline is
+   made up of one or more pixels; and each pixel participates via one
+   or more edges.
+*/
+
+#include <assert.h>
+
+#include "mallocvar.h"
+
+#include "message.h"
+#include "bitmap.h"
+#include "bitmap.h"
+#include "logreport.h"
+#include "pxl-outline.h"
+
+/* We consider each pixel to consist of four edges, and we travel along
+   edges, instead of through pixel centers.  This is necessary for those
+   unfortunate times when a single pixel is on both an inside and an
+   outside outline.
+
+   The numbers chosen here are not arbitrary; the code that figures out
+   which edge to move to depends on particular values.  See the
+   `TRY_PIXEL' macro in `edge.c'.  To emphasize this, I've written in the
+   numbers we need for each edge value.  */
+
+typedef enum
+  {
+    TOP = 1, LEFT = 2, BOTTOM = 3, RIGHT = 0, NO_EDGE = 4
+  } edge_type;
+
+/* This choice is also not arbitrary: starting at the top edge makes the
+   code find outside outlines before inside ones, which is certainly
+   what we want.  */
+#define START_EDGE  top
+
+typedef enum
+  {
+    NORTH = 0, NORTHWEST = 1, WEST = 2, SOUTHWEST = 3, SOUTH = 4,
+    SOUTHEAST = 5, EAST = 6, NORTHEAST = 7
+  } direction_type;
+
+#define NUM_EDGES NO_EDGE
+
+#define COMPUTE_DELTA(axis, dir)                        \
+  ((dir) % 2 != 0                                       \
+    ? COMPUTE_##axis##_DELTA ((dir) - 1)                \
+      + COMPUTE_##axis##_DELTA (((dir) + 1) % 8)        \
+    : COMPUTE_##axis##_DELTA (dir)                      \
+  )
+
+#define COMPUTE_ROW_DELTA(dir)                          \
+  ((dir) == NORTH ? -1 : (dir) == SOUTH ? +1 : 0)
+
+#define COMPUTE_COL_DELTA(dir)                  \
+  ((dir) == WEST ? -1 : (dir) == EAST ? +1 : 0)
+
+static void append_pixel_outline (pixel_outline_list_type *,
+                                  pixel_outline_type);
+static pixel_outline_type new_pixel_outline (void);
+static void free_pixel_outline (pixel_outline_type *);
+static void concat_pixel_outline (pixel_outline_type *,
+                                  const pixel_outline_type*);
+static bool is_marked_edge (edge_type, unsigned short, unsigned short, bitmap_type);
+
+static void
+mark_edge (edge_type e, unsigned short, unsigned short, bitmap_type *);
+
+static bool is_marked_dir(unsigned short, unsigned short, direction_type, bitmap_type);
+static bool is_other_dir_marked(unsigned short, unsigned short, direction_type, bitmap_type);
+static void mark_dir(unsigned short, unsigned short, direction_type, bitmap_type *);
+
+static unsigned
+num_neighbors(unsigned short, unsigned short, bitmap_type);
+
+#define CHECK_FATAL() if (at_exception_got_fatal(exp)) goto cleanup;
+#define RETURN_IF_FATAL() if (at_exception_got_fatal(exp)) return;
+
+
+
+
+static pixel
+getBitmapColor(bitmap_type  const bitmap,
+               unsigned int const row,
+               unsigned int const col) {
+
+    pixel pix;
+    unsigned char *p = BITMAP_PIXEL (bitmap, row, col);
+  
+    if (bitmap.np >= 3)
+        PPM_ASSIGN(pix, p[0], p[1], p[2]);
+    else
+        PPM_ASSIGN(pix, p[0], p[0], p[0]);
+
+    return pix;
+}
+
+
+
+
+static void
+append_outline_pixel(pixel_outline_type * const pixelOutlineP,
+                     pm_pixelcoord        const coord) {
+/*----------------------------------------------------------------------------
+  Add a point to the pixel list.
+-----------------------------------------------------------------------------*/
+
+    O_LENGTH(*pixelOutlineP)++;
+    REALLOCARRAY_NOFAIL(pixelOutlineP->data, O_LENGTH(*pixelOutlineP));
+
+    O_COORDINATE(*pixelOutlineP, O_LENGTH(*pixelOutlineP) - 1) = coord;
+}
+
+
+
+/* We check to see if the edge of the pixel at position ROW and COL
+   is an outline edge */
+
+static bool
+is_outline_edge (edge_type edge, bitmap_type bitmap,
+                 unsigned short row, unsigned short col, pixel color,
+                 at_exception_type * exp)
+{
+  /* If this pixel isn't of the same color, it's not part of the outline. */
+  if (!PPM_EQUAL (getBitmapColor (bitmap, row, col), color))
+    return false;
+
+  switch (edge)
+    {
+    case LEFT:
+      return (bool)
+          (col == 0 ||
+           !PPM_EQUAL (getBitmapColor (bitmap, row, col - 1), color));
+
+    case TOP:
+      return (bool)
+          (row == 0 ||
+           !PPM_EQUAL (getBitmapColor (bitmap, row - 1, col), color));
+
+    case RIGHT:
+        return (bool)
+            (col == bitmap.width - 1
+             || !PPM_EQUAL(getBitmapColor(bitmap, row, col + 1), color));
+
+    case BOTTOM:
+        return (bool)
+            (row == bitmap.height - 1
+             || !PPM_EQUAL(getBitmapColor (bitmap, row + 1, col), color));
+
+    case NO_EDGE:
+    default:
+      LOG1 ("is_outline_edge: Bad edge value(%d)", edge);
+      at_exception_fatal(exp, "is_outline_edge: Bad edge value");
+    }
+
+  return false; /* NOT REACHED */
+}
+
+
+/* Is this really an edge and is it still unmarked? */
+
+static bool
+is_unmarked_outline_edge(unsigned short row,
+                         unsigned short col,
+                         edge_type edge,
+                         bitmap_type bitmap,
+                         bitmap_type marked,
+                         pixel color,
+                         at_exception_type * exp)
+{
+  return
+    (bool)(!is_marked_edge (edge, row, col, marked)
+              && is_outline_edge (edge, bitmap, row, col, color, exp));
+}
+
+
+static bool
+is_valid_dir(unsigned int   const row,
+             unsigned int   const col,
+             direction_type const dir,
+             bitmap_type    const bitmap,
+             bitmap_type    const marked) {
+  
+    return(!is_marked_dir(row, col, dir, marked)
+           && COMPUTE_DELTA(ROW, dir)+row > 0
+           && COMPUTE_DELTA(COL, dir)+col > 0
+           && BITMAP_VALID_PIXEL(bitmap,
+                                 COMPUTE_DELTA(ROW, dir) + row,
+                                 COMPUTE_DELTA(COL, dir) + col)
+           && PPM_EQUAL(getBitmapColor(bitmap,
+                                       COMPUTE_DELTA(ROW, dir) + row,
+                                       COMPUTE_DELTA(COL, dir) + col),
+                        getBitmapColor(bitmap, row, col)));
+}
+
+
+
+static bool
+next_unmarked_pixel(unsigned int *   const row,
+                    unsigned int *   const col,
+                    direction_type * const dir,
+                    bitmap_type      const bitmap,
+                    bitmap_type *    const marked) {
+
+    unsigned int   const orig_row = *row;
+    unsigned int   const orig_col = *col;
+    direction_type const orig_dir = *dir;
+
+    direction_type test_dir;
+    pixel color;
+
+    test_dir = *dir;  /* initial value */
+    color = getBitmapColor(bitmap, *row, *col);
+
+    do {
+        if (is_valid_dir(orig_row, orig_col, test_dir, bitmap, *marked)) {
+            *row = orig_row + COMPUTE_DELTA(ROW, test_dir);
+            *col = orig_col + COMPUTE_DELTA(COL, test_dir);
+            *dir = test_dir;
+            break;
+        }
+
+        if (orig_dir == test_dir)
+            test_dir = (orig_dir + 2) % 8;
+        else if ((orig_dir + 2) % 8 == test_dir)
+            test_dir = (orig_dir + 6) % 8;
+        else if ((orig_dir + 6) % 8 == test_dir)
+            test_dir = (orig_dir + 1) % 8;
+        else if ((orig_dir + 1) % 8 == test_dir)
+            test_dir = (orig_dir + 7) % 8;
+        else if ((orig_dir + 7) % 8 == test_dir)
+            test_dir = (orig_dir + 3) % 8;
+        else if ((orig_dir + 3) % 8 == test_dir)
+            test_dir = (orig_dir + 5) % 8;
+        else if ((orig_dir + 5) % 8 == test_dir)
+            break;
+    } while (1);
+
+    if ((*row != orig_row || *col != orig_col) && 
+        (!(is_other_dir_marked(orig_row, orig_col, test_dir, *marked) &&
+           is_other_dir_marked(orig_row + COMPUTE_DELTA(ROW, test_dir),
+                               orig_col + COMPUTE_DELTA(COL, test_dir),
+                               test_dir,*marked))))
+        return true;
+    else
+        return false;
+}
+
+
+
+static pixel_outline_type
+find_one_centerline(bitmap_type    const bitmap,
+                    direction_type const original_dir,
+                    unsigned int   const original_row,
+                    unsigned int   const original_col,
+                    bitmap_type *  const marked) {
+
+    direction_type search_dir;
+    unsigned int row, col;
+    pixel_outline_type outline;
+
+    outline = new_pixel_outline();
+    outline.open  = false;
+    outline.color = getBitmapColor(bitmap, row, col);
+
+    /* Add the starting pixel to the output list, changing from bitmap
+       to Cartesian coordinates and specifying the left edge so that
+       the coordinates won't be adjusted.
+    */
+    {
+        pm_pixelcoord pos;
+        pos.col = col; pos.row = bitmap.height - row - 1;
+        LOG2(" (%d,%d)", pos.col, pos.row);
+        append_outline_pixel(&outline, pos);
+    }
+    search_dir = original_dir;  /* initial value */
+    row = original_row;         /* initial value */
+    col = original_col;         /* initial values */
+
+    for ( ; ; ) {
+        unsigned int const prev_row = row;
+        unsigned int const prev_col = col;
+
+        /* If there is no adjacent, unmarked pixel, we can't proceed
+           any further, so return an open outline.
+        */
+        if (!next_unmarked_pixel(&row, &col, &search_dir, bitmap, marked)) {
+            outline.open = true;
+            break;
+        }
+
+        /* If we've moved to a new pixel, mark all edges of the previous
+           pixel so that it won't be revisited.
+        */
+        if (!(prev_row == original_row && prev_col == original_col))
+            mark_dir(prev_row, prev_col, search_dir, marked);
+        mark_dir(row, col, (search_dir + 4) % 8, marked);
+
+        /* If we've returned to the starting pixel, we're done. */
+        if (row == original_row && col == original_col)
+            break;
+
+        
+        {
+            /* Add the new pixel to the output list. */
+            pm_pixelcoord pos;
+            pos.col = col; pos.row = bitmap.height - row - 1;
+            LOG2(" (%d,%d)", pos.col, pos.row);
+            append_outline_pixel(&outline, pos);
+        }
+    }
+    mark_dir(original_row, original_col, original_dir, marked);
+
+    return outline;
+}
+
+
+
+static bool
+wrongDirection(unsigned int   const row,
+               unsigned int   const col,
+               direction_type const dir,
+               bitmap_type    const bitmap,
+               bitmap_type    const marked) {
+
+    return (!is_valid_dir(row, col, dir, bitmap, marked)
+            || (!is_valid_dir(COMPUTE_DELTA(ROW, dir) + row,
+                              COMPUTE_DELTA(COL, dir) + col,
+                              dir, bitmap, marked)
+                && num_neighbors(row, col, bitmap) > 2)
+            || num_neighbors(row, col, bitmap) > 4
+            || num_neighbors(COMPUTE_DELTA(ROW, dir) + row,
+                             COMPUTE_DELTA(COL, dir) + col, bitmap) > 4
+            || (is_other_dir_marked(row, col, dir, marked)
+                && is_other_dir_marked(row + COMPUTE_DELTA(ROW, dir),
+                                       col + COMPUTE_DELTA(COL, dir),
+                                       dir, marked)));
+}
+
+
+
+pixel_outline_list_type
+find_centerline_pixels(bitmap_type         const bitmap,
+                       pixel               const bg_color, 
+                       at_progress_func          notify_progress,
+                       void *              const progress_data,
+                       at_testcancel_func        test_cancel,
+                       void *              const testcancel_data,
+                       at_exception_type * const exp) {
+
+  pixel_outline_list_type outline_list;
+  signed short row;
+  bitmap_type marked = new_bitmap(bitmap.width, bitmap.height);
+  unsigned int const max_progress = bitmap.height * bitmap.width;
+    
+  O_LIST_LENGTH(outline_list) = 0;
+  outline_list.data = NULL;
+
+  for (row = 0; row < bitmap.height; ++row) {
+      signed short col;
+      for (col = 0; col < bitmap.width; ) {
+          bool           const clockwise = false;
+
+          direction_type dir;
+          pixel_outline_type outline;
+
+          if (notify_progress)
+              notify_progress((float)(row * bitmap.width + col) /
+                              ((float) max_progress * (float)3.0),
+                              progress_data);
+
+		  if (PPM_EQUAL(getBitmapColor(bitmap, row, col), bg_color)) {
+	          ++col;
+			  continue;
+          }
+
+          dir = EAST;
+
+          if (wrongDirection(row, col, dir, bitmap, marked)) {
+              dir = SOUTHEAST;
+              if (wrongDirection(row, col, dir, bitmap, marked)) {
+                  dir = SOUTH;
+                  if (wrongDirection(row, col, dir, bitmap, marked)) {
+                      dir = SOUTHWEST;
+                      if (wrongDirection(row, col, dir, bitmap, marked)) {
+						  ++col;
+						  continue;
+                      }
+                  }
+              }
+          }
+
+          LOG2("#%u: (%sclockwise) ", O_LIST_LENGTH(outline_list),
+               clockwise ? "" : "counter");
+
+          outline = find_one_centerline(bitmap, dir, row, col, &marked);
+
+          /* If the outline is open (i.e., we didn't return to the
+             starting pixel), search from the starting pixel in the
+             opposite direction and concatenate the two outlines.
+          */
+
+          if (outline.open) {
+              pixel_outline_type partial_outline;
+              bool okay = false;
+
+              if (dir == EAST) {
+                  dir = SOUTH;
+                  okay = is_valid_dir(row, col, dir, bitmap, marked);
+                  if (!okay) {
+                      dir = SOUTHWEST;
+                      okay = is_valid_dir(row, col, dir, bitmap, marked);
+                      if (!okay) {
+                          dir = SOUTHEAST;
+                          okay = is_valid_dir(row, col, dir, bitmap, marked);
+                      }
+                  }
+              } else if (dir == SOUTHEAST) {
+                  dir = SOUTHWEST;
+                  okay = is_valid_dir(row, col, dir, bitmap, marked);
+                  if (!okay) {
+                      dir = EAST;
+                      okay=is_valid_dir(row, col, dir, bitmap, marked);
+                      if (!okay) {
+                          dir = SOUTH;
+                          okay = is_valid_dir(row, col, dir, bitmap, marked);
+                      }
+                  }
+              } else if (dir == SOUTH) {
+                  dir = EAST;
+                  okay = is_valid_dir(row, col, dir, bitmap, marked);
+                  if (!okay) {
+                      dir = SOUTHEAST;
+                      okay = is_valid_dir(row, col, dir, bitmap, marked);
+                      if (!okay) {
+                          dir = SOUTHWEST;
+                          okay = is_valid_dir(row, col, dir, bitmap, marked);
+                      }
+                  }
+              } else if (dir == SOUTHWEST) {
+                  dir = SOUTHEAST;
+                  okay = is_valid_dir(row, col, dir, bitmap, marked);
+                  if (!okay) {
+                      dir = EAST;
+                      okay = is_valid_dir(row, col, dir, bitmap, marked);
+                      if (!okay) {
+                          dir = SOUTH;
+                          okay = is_valid_dir(row, col, dir, bitmap, marked);
+                      }
+                  }
+              }
+              if (okay) {
+                  partial_outline =
+                      find_one_centerline(bitmap, dir, row, col, &marked);
+                  concat_pixel_outline(&outline, &partial_outline);
+                  if (partial_outline.data)
+                      free(partial_outline.data);
+              } else
+                  ++col;
+          }        
+            
+          /* Outside outlines will start at a top edge, and move
+             counterclockwise, and inside outlines will start at a
+             bottom edge, and move clockwise.  This happens because of
+             the order in which we look at the edges.
+          */
+          O_CLOCKWISE(outline) = clockwise;
+          if (O_LENGTH(outline) > 1)
+              append_pixel_outline(&outline_list, outline);
+          LOG1("(%s)", (outline.open ? " open" : " closed"));
+          LOG1(" [%u].\n", O_LENGTH(outline));
+          if (O_LENGTH(outline) == 1)
+              free_pixel_outline(&outline);
+        }
+  }
+  if (test_cancel && test_cancel(testcancel_data)) {
+      if (O_LIST_LENGTH (outline_list) != 0)
+          free_pixel_outline_list (&outline_list);
+  }
+  free_bitmap(&marked);
+  flush_log_output();
+  return outline_list;
+}
+
+
+
+/* Add an outline to an outline list. */
+
+static void
+append_pixel_outline (pixel_outline_list_type *outline_list,
+                      pixel_outline_type outline)
+{
+  O_LIST_LENGTH (*outline_list)++;
+  REALLOCARRAY_NOFAIL(outline_list->data, outline_list->length);
+  O_LIST_OUTLINE (*outline_list, O_LIST_LENGTH (*outline_list) - 1) = outline;
+}
+
+
+/* Free the list of outline lists. */
+
+void
+free_pixel_outline_list (pixel_outline_list_type *outline_list)
+{
+  unsigned this_outline;
+
+  for (this_outline = 0; this_outline < outline_list->length; this_outline++)
+    {
+      pixel_outline_type o = outline_list->data[this_outline];
+      free_pixel_outline (&o);
+    }
+  outline_list->length = 0;
+
+  if (outline_list->data != NULL)
+    {
+      free (outline_list->data);
+      outline_list->data = NULL;
+    }
+
+  flush_log_output ();
+}
+
+
+/* Return an empty list of pixels.  */
+
+
+static pixel_outline_type
+new_pixel_outline (void)
+{
+  pixel_outline_type pixel_outline;
+
+  O_LENGTH (pixel_outline) = 0;
+  pixel_outline.data = NULL;
+  pixel_outline.open = false;
+
+  return pixel_outline;
+}
+
+static void
+free_pixel_outline (pixel_outline_type * outline)
+{
+  if (outline->data)
+    {
+      free (outline->data) ;
+      outline->data   = NULL;
+      outline->length = 0;
+    }
+}
+
+/* Concatenate two pixel lists. The two lists are assumed to have the
+   same starting pixel and to proceed in opposite directions therefrom. */
+
+static void
+concat_pixel_outline(pixel_outline_type *o1, const pixel_outline_type *o2)
+{
+  int src, dst;
+  unsigned o1_length, o2_length;
+  if (!o1 || !o2 || O_LENGTH(*o2) <= 1) return;
+
+  o1_length = O_LENGTH(*o1);
+  o2_length = O_LENGTH(*o2);
+  O_LENGTH(*o1) += o2_length - 1;
+  /* Resize o1 to the sum of the lengths of o1 and o2 minus one (because
+     the two lists are assumed to share the same starting pixel). */
+  REALLOCARRAY_NOFAIL(o1->data, O_LENGTH(*o1));
+  /* Shift the contents of o1 to the end of the new array to make room
+     to prepend o2. */
+  for (src = o1_length - 1, dst = O_LENGTH(*o1) - 1; src >= 0; src--, dst--)
+    O_COORDINATE(*o1, dst) = O_COORDINATE(*o1, src);
+  /* Prepend the contents of o2 (in reverse order) to o1. */
+  for (src = o2_length - 1, dst = 0; src > 0; src--, dst++)
+    O_COORDINATE(*o1, dst) = O_COORDINATE(*o2, src);
+}
+
+
+/* If EDGE is not already marked, we mark it; otherwise, it's a fatal error.
+   The position ROW and COL should be inside the bitmap MARKED. EDGE can be
+   NO_EDGE. */
+
+static void
+mark_edge (edge_type edge, unsigned short row,
+           unsigned short col, bitmap_type *marked)
+{
+  *BITMAP_PIXEL (*marked, row, col) |= 1 << edge;
+}
+
+
+/* Mark the direction of the pixel ROW/COL in MARKED. */
+
+static void
+mark_dir(unsigned short row, unsigned short col, direction_type dir, bitmap_type *marked)
+{
+  *BITMAP_PIXEL(*marked, row, col) |= 1 << dir;
+}
+
+
+/* Test if the direction of pixel at ROW/COL in MARKED is marked. */
+
+static bool
+is_marked_dir(unsigned short row, unsigned short col, direction_type dir, bitmap_type marked)
+{
+  return (bool)((*BITMAP_PIXEL(marked, row, col) & 1 << dir) != 0);
+}
+
+
+static bool
+is_other_dir_marked(unsigned short row, unsigned short col, direction_type dir, bitmap_type marked)
+{
+  return (bool)((*BITMAP_PIXEL(marked, row, col) & (255 - (1 << dir) - (1 << ((dir + 4) % 8))) ) != 0);
+}
+
+
+/* Return the number of pixels adjacent to pixel ROW/COL that are black. */
+
+static unsigned
+num_neighbors(unsigned short row, unsigned short col, bitmap_type bitmap)
+{
+    unsigned dir, count = 0;
+    pixel color = getBitmapColor(bitmap, row, col);
+    for (dir = NORTH; dir <= NORTHEAST; dir++)
+    {
+	int delta_r = COMPUTE_DELTA(ROW, dir);
+	int delta_c = COMPUTE_DELTA(COL, dir);
+	unsigned int test_row = row + delta_r;
+	unsigned int test_col = col + delta_c;
+	if (BITMAP_VALID_PIXEL(bitmap, test_row, test_col)
+	    && PPM_EQUAL(getBitmapColor(bitmap, test_row, test_col), color))
+	    ++count;
+    }
+    return count;
+}
+
+
+/* Test if the edge EDGE at ROW/COL in MARKED is marked.  */
+
+static bool
+is_marked_edge (edge_type edge, unsigned short row, unsigned short col, bitmap_type marked)
+{
+  return
+    (bool)(edge == NO_EDGE ? false : (*BITMAP_PIXEL (marked, row, col) & (1 << edge)) != 0);
+}
+
+
+
+static void
+nextClockwisePointTop(bitmap_type         const bitmap,
+                      edge_type *         const edge,
+                      unsigned int *      const row,
+                      unsigned int *      const col,
+                      pixel               const color,
+                      bitmap_type         const marked,
+                      at_exception_type * const exp,
+                      pm_pixelcoord *     const posP) {
+
+    if ((*col >= 1 && !is_marked_edge(TOP, *row, *col-1, marked) &&
+             is_outline_edge(TOP, bitmap, *row, *col-1, color, exp))) {
+
+        /* WEST */
+
+        *edge = TOP;
+        --*col;
+        posP->col = *col;
+        posP->row = bitmap.height - *row;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+
+    if ((*col >= 1 && *row >= 1 &&
+         !is_marked_edge(RIGHT, *row-1, *col-1, marked) &&
+         is_outline_edge(RIGHT, bitmap, *row-1, *col-1,
+                         color, exp)) &&
+        !(is_marked_edge(LEFT, *row-1, *col, marked) &&
+          is_marked_edge(TOP, *row,*col-1, marked)) &&
+        !(is_marked_edge(BOTTOM, *row-1, *col, marked) &&
+          is_marked_edge(RIGHT, *row, *col-1, marked))) {
+
+        /* NORTHWEST */
+
+        *edge = RIGHT;
+        --*col;
+        --*row;
+        posP->col = *col + 1;
+        posP->row = bitmap.height - *row;
+        return;
+    } 
+
+    RETURN_IF_FATAL();
+
+    if ((!is_marked_edge(LEFT, *row, *col, marked)
+         && is_outline_edge(LEFT, bitmap, *row, *col, color, exp))) {
+
+        *edge = LEFT;
+        posP->col = *col;
+        posP->row = bitmap.height - *row - 1;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+
+    *edge = NO_EDGE;
+}
+
+
+
+static void
+nextClockwisePointRight(bitmap_type         const bitmap,
+                        edge_type *         const edge,
+                        unsigned int *      const row,
+                        unsigned int *      const col,
+                        pixel               const color,
+                        bitmap_type         const marked,
+                        at_exception_type * const exp,
+                        pm_pixelcoord *     const posP) {
+
+    if ((*row >= 1 &&
+         !is_marked_edge(RIGHT, *row-1, *col, marked) &&
+         is_outline_edge(RIGHT, bitmap, *row-1, *col, color, exp))) {
+
+         /* NORTH */
+        
+        *edge = RIGHT;
+        --*row;
+        posP->col = *col+1;
+        posP->row = bitmap.height - *row;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+    
+    if ((*col+1 < marked.width && *row >= 1 &&
+         !is_marked_edge(BOTTOM, *row-1, *col+1, marked) &&
+         is_outline_edge(BOTTOM, bitmap, *row-1, *col+1,
+                         color, exp)) &&
+        !(is_marked_edge(LEFT, *row, *col+1, marked) &&
+          is_marked_edge(BOTTOM, *row-1, *col, marked)) &&
+        !(is_marked_edge(TOP, *row, *col+1, marked) &&
+          is_marked_edge(RIGHT, *row-1, *col, marked))) {
+
+        /* NORTHEAST */
+        *edge = BOTTOM;
+        ++*col;
+        --*row;
+        posP->col = *col + 1;
+        posP->row = bitmap.height - *row - 1;
+        return;
+    } 
+
+    RETURN_IF_FATAL();
+
+    if ((!is_marked_edge(TOP, *row, *col, marked) &&
+         is_outline_edge(TOP, bitmap, *row, *col, color, exp))) {
+
+        *edge = TOP;
+        posP->col = *col;
+        posP->row = bitmap.height - *row;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+
+    *edge = NO_EDGE;
+}
+
+
+
+static void
+nextClockwisePointBottom(bitmap_type         const bitmap,
+                         edge_type *         const edge,
+                         unsigned int *      const row,
+                         unsigned int *      const col,
+                         pixel               const color,
+                         bitmap_type         const marked,
+                         at_exception_type * const exp,
+                         pm_pixelcoord *     const posP) {
+    
+    if ((*col+1 < marked.width &&
+         !is_marked_edge(BOTTOM, *row, *col+1, marked) &&
+         is_outline_edge(BOTTOM, bitmap, *row, *col+1, color, exp))) {
+
+        /* EAST */
+
+        *edge = BOTTOM;
+        ++*col;
+        posP->col = *col+1;
+        posP->row = bitmap.height - *row - 1;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+
+    if ((*col + 1 < marked.width && *row+1 < marked.height &&
+         !is_marked_edge(LEFT, *row+1,*col+1, marked) &&
+         is_outline_edge(LEFT, bitmap, *row+1, *col+1, color, exp)) &&
+        !(is_marked_edge(TOP, *row+1, *col, marked) &&
+          is_marked_edge(LEFT, *row, *col+1, marked)) &&
+        !(is_marked_edge(RIGHT, *row+1, *col, marked) &&
+          is_marked_edge(BOTTOM, *row, *col+1, marked))) {
+
+        /* SOUTHEAST */
+
+        *edge = LEFT;
+        ++*col;
+        ++*row;
+        posP->col = *col;
+        posP->row = bitmap.height - *row - 1;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+
+    if ((!is_marked_edge(RIGHT, *row, *col, marked) &&
+         is_outline_edge(RIGHT, bitmap, *row, *col, color, exp))) {
+
+        *edge = RIGHT;
+        posP->col = *col+1;
+        posP->row = bitmap.height - *row;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+
+    *edge = NO_EDGE;
+}
+
+
+
+static void
+nextClockwisePointLeft(bitmap_type         const bitmap,
+                       edge_type *         const edge,
+                       unsigned int *      const row,
+                       unsigned int *      const col,
+                       pixel               const color,
+                       bitmap_type         const marked,
+                       at_exception_type * const exp,
+                       pm_pixelcoord *     const posP) {
+
+    if ((*row+1 < marked.height &&
+         !is_marked_edge(LEFT, *row+1, *col, marked) &&
+         is_outline_edge(LEFT, bitmap, *row+1, *col, color, exp))) {
+
+        /* SOUTH */
+
+        *edge = LEFT;
+        ++*row;
+        posP->col = *col;
+        posP->row = bitmap.height - *row - 1;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+
+    if ((*col >= 1 && *row+1 < marked.height &&
+         !is_marked_edge(TOP, *row+1, *col-1, marked) &&
+         is_outline_edge(TOP, bitmap, *row+1, *col-1, color, exp)) &&
+        !(is_marked_edge(RIGHT, *row, *col-1, marked) &&
+          is_marked_edge(TOP, *row+1, *col, marked)) &&
+        !(is_marked_edge(BOTTOM, *row, *col-1, marked) &&
+          is_marked_edge(LEFT, *row+1, *col, marked))) {
+        
+        /* SOUTHWEST */
+        
+        *edge = TOP;
+        --*col;
+        ++*row;
+        posP->col = *col;
+        posP->row = bitmap.height - *row;
+        return;
+    } 
+
+    RETURN_IF_FATAL();
+
+    if ((!is_marked_edge(BOTTOM, *row, *col, marked) &&
+         is_outline_edge(BOTTOM, bitmap, *row, *col, color, exp))) {
+        *edge = BOTTOM;
+        posP->col = *col+1;
+        posP->row = bitmap.height - *row - 1;
+        return;
+    }
+    RETURN_IF_FATAL();
+
+    *edge = NO_EDGE;
+}
+
+
+
+static void
+nextClockwisePoint(bitmap_type         const bitmap,
+                   edge_type *         const edge,
+                   unsigned int *      const row,
+                   unsigned int *      const col,
+                   pixel               const color,
+                   bitmap_type         const marked,
+                   at_exception_type * const exp,
+                   pm_pixelcoord *     const posP) {
+    
+    switch (*edge) {
+    case TOP:
+        nextClockwisePointTop(  bitmap, edge, row, col, color,
+                                marked, exp, posP);
+        break;
+    case RIGHT: 
+        nextClockwisePointRight(bitmap, edge, row, col, color,
+                                marked, exp, posP);
+        break;
+    case BOTTOM: 
+        nextClockwisePointBottom(bitmap, edge, row, col, color,
+                                 marked, exp, posP);
+        break;
+    case LEFT: 
+        nextClockwisePointLeft(  bitmap, edge, row, col, color,
+                                 marked, exp, posP);
+        break;
+    case NO_EDGE:
+        break;
+    default:
+        *edge = NO_EDGE;
+        break;
+    }
+}
+
+
+
+static void
+nextCcwPointTop(bitmap_type         const bitmap,
+                edge_type *         const edge,
+                unsigned int *      const row,
+                unsigned int *      const col,
+                pixel               const color,
+                bitmap_type         const marked,
+                at_exception_type * const exp,
+                pm_pixelcoord *     const posP) {
+
+    if ((!is_marked_edge(LEFT, *row, *col, marked) &&
+         is_outline_edge(LEFT,bitmap,*row,*col, color, exp))) {
+
+        *edge = LEFT;
+        posP->col = *col;
+        posP->row = bitmap.height - *row - 1;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+
+    if ((*col >= 1 &&
+         !is_marked_edge(TOP, *row, *col-1, marked) &&
+         is_outline_edge(TOP, bitmap, *row, *col-1, color, exp))) {
+
+        /* WEST */
+
+        *edge = TOP;
+        --*col;
+        posP->col = *col;
+        posP->row = bitmap.height - *row;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+    
+    if ((*col >= 1 && *row >= 1 &&
+         !is_marked_edge(RIGHT, *row-1, *col-1, marked) &&
+         is_outline_edge(RIGHT, bitmap, *row-1, *col-1, color, exp))) {
+
+        /* NORTHWEST */
+
+        *edge = RIGHT;
+        --*col;
+        --*row;
+        posP->col = *col + 1;
+        posP->row = bitmap.height - *row;
+        return;
+    } 
+
+    RETURN_IF_FATAL();
+
+    *edge = NO_EDGE;
+}
+
+
+
+static void
+nextCcwPointRight(bitmap_type         const bitmap,
+                  edge_type *         const edge,
+                  unsigned int *      const row,
+                  unsigned int *      const col,
+                  pixel               const color,
+                  bitmap_type         const marked,
+                  at_exception_type * const exp,
+                  pm_pixelcoord *     const posP) {
+
+    if ((!is_marked_edge(TOP, *row, *col, marked) &&
+         is_outline_edge(TOP, bitmap, *row, *col, color, exp))) {
+
+        *edge = TOP;
+        posP->col = *col;
+        posP->row = bitmap.height - *row;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+
+    if ((*row >= 1 &&
+         !is_marked_edge(RIGHT, *row-1, *col, marked) &&
+         is_outline_edge(RIGHT, bitmap, *row-1, *col, color, exp))) {
+
+        /* NORTH */
+        
+        *edge = RIGHT;
+        --*row;
+        posP->col = *col + 1;
+        posP->row = bitmap.height - *row;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+
+    if ((*col + 1 < marked.width && *row >= 1 &&
+         !is_marked_edge(BOTTOM, *row-1, *col+1, marked) &&
+         is_outline_edge(BOTTOM, bitmap, *row-1, *col+1, color, exp))) {
+
+        /* NORTHEAST */
+
+        *edge = BOTTOM;
+        ++*col;
+        ++*row;
+        posP->col = *col + 1;
+        posP->row = bitmap.height - *row - 1;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+
+    *edge = NO_EDGE;
+}
+
+
+
+static void
+nextCcwPointBottom(bitmap_type         const bitmap,
+                   edge_type *         const edge,
+                   unsigned int *      const row,
+                   unsigned int *      const col,
+                   pixel               const color,
+                   bitmap_type         const marked,
+                   at_exception_type * const exp,
+                   pm_pixelcoord *     const posP) {
+
+    if ((!is_marked_edge(RIGHT, *row, *col, marked) &&
+         is_outline_edge(RIGHT, bitmap, *row, *col, color, exp))) {
+
+        *edge = RIGHT;
+        posP->col = *col + 1;
+        posP->row = bitmap.height - *row;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+
+    if ((*col + 1 < marked.width &&
+         !is_marked_edge(BOTTOM, *row, *col+1, marked) &&
+         is_outline_edge(BOTTOM, bitmap, *row, *col+1, color, exp))) {
+
+        /* EAST */
+
+        *edge = BOTTOM;
+        ++*col;
+        posP->col = *col + 1;
+        posP->row = bitmap.height - *row - 1;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+
+    if ((*col + 1 < marked.width && *row + 1 < marked.height &&
+         !is_marked_edge(LEFT, *row+1, *col+1, marked) &&
+         is_outline_edge(LEFT, bitmap, *row+1, *col+1, color, exp))) {
+
+        /* SOUTHEAST */
+
+        *edge = LEFT;
+        ++*col;
+        ++*row;
+        posP->col = *col;
+        posP->row = bitmap.height - *row - 1;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+    
+    *edge = NO_EDGE;
+}
+
+
+
+static void
+nextCcwPointLeft(bitmap_type         const bitmap,
+                 edge_type *         const edge,
+                 unsigned int *      const row,
+                 unsigned int *      const col,
+                 pixel               const color,
+                 bitmap_type         const marked,
+                 at_exception_type * const exp,
+                 pm_pixelcoord *     const posP) {
+
+
+    if ((!is_marked_edge(BOTTOM, *row, *col, marked) &&
+         is_outline_edge(BOTTOM, bitmap, *row, *col, color, exp))) {
+
+        *edge = BOTTOM;
+        posP->col = *col + 1;
+        posP->row = bitmap.height - *row - 1;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+
+    if ((*row + 1 < marked.height &&
+         !is_marked_edge(LEFT, *row+1, *col, marked) &&
+         is_outline_edge(LEFT, bitmap, *row+1, *col, color, exp))) {
+
+        /* SOUTH */
+
+        *edge = LEFT;
+        ++*row;
+        posP->col = *col;
+        posP->row = bitmap.height - *row - 1;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+    
+    if ((*col >= 1 && *row + 1 < marked.height &&
+         !is_marked_edge(TOP, *row+1, *col-1, marked) &&
+         is_outline_edge(TOP, bitmap, *row+1, *col-1, color, exp))) {
+
+        /* SOUTHWEST */
+
+        *edge = TOP;
+        --*col;
+        ++*row;
+        posP->col = *col;
+        posP->row = bitmap.height - *row;
+        return;
+    }
+
+    RETURN_IF_FATAL();
+
+    *edge = NO_EDGE;
+}
+
+static void
+nextCounterClockwisePoint(bitmap_type         const bitmap,
+                          edge_type *         const edge,
+                          unsigned int *      const row,
+                          unsigned int *      const col,
+                          pixel               const color,
+                          bitmap_type         const marked,
+                          at_exception_type * const exp,
+                          pm_pixelcoord *     const posP) {
+
+    switch (*edge) {
+    case TOP:
+        nextCcwPointTop(   bitmap, edge, row, col, color, marked, exp, posP);
+        break;
+    case RIGHT: 
+        nextCcwPointRight( bitmap, edge, row, col, color, marked, exp, posP);
+        break;
+    case BOTTOM: 
+        nextCcwPointBottom(bitmap, edge, row, col, color, marked, exp, posP);
+        break;
+    case LEFT: 
+        nextCcwPointLeft(  bitmap, edge, row, col, color, marked, exp, posP);
+        break;
+    case NO_EDGE:
+        break;
+    default: 
+        *edge = NO_EDGE;
+        break;
+    }
+}
+
+
+
+static void
+nextPoint(bitmap_type         const bitmap,
+          edge_type *         const edge,
+          unsigned int *      const row,
+          unsigned int *      const col,
+          pm_pixelcoord *     const nextPointP,
+          pixel               const color,
+          bool                const clockwise,
+          bitmap_type         const marked,
+          at_exception_type * const exp) {
+
+    if (!clockwise)
+        nextClockwisePoint(       bitmap, edge, row, col, color,
+                                  marked, exp, nextPointP);
+    else
+        nextCounterClockwisePoint(bitmap, edge, row, col, color,
+                                  marked, exp, nextPointP);
+}
+
+
+
+static pixel_outline_type
+find_one_outline(bitmap_type         const bitmap,
+                 edge_type           const originalEdge,
+                 unsigned int        const originalRow,
+                 unsigned int        const originalCol,
+                 bitmap_type *       const marked,
+                 bool                const clockwise,
+                 bool                const ignore,
+                 at_exception_type * const exp) {
+/*----------------------------------------------------------------------------
+  Calculate one single outline.  We pass the position of the
+  starting pixel and the starting edge.  We mark all edges we track along
+  and append the outline pixels to the coordinate list.
+-----------------------------------------------------------------------------*/
+    unsigned int row;
+    unsigned int col;
+    edge_type    edge;
+
+    pixel_outline_type outline;
+    pm_pixelcoord pos;
+    
+    outline = new_pixel_outline();
+    outline.color = getBitmapColor(bitmap, originalRow, originalCol);
+
+    row  = originalRow;   /* initial value */
+    col  = originalCol;   /* initial value */
+    edge = originalEdge;  /* initial value */
+
+    /* Set initial position */
+    pos.col = col + ((edge == RIGHT) || (edge == BOTTOM) ? 1 : 0);
+    pos.row = bitmap.height - row - 1 + 
+        (edge == TOP || edge == RIGHT ? 1 : 0);
+
+    do {
+        /* Put this edge into the output list */
+        if (!ignore) {
+            LOG2(" (%d,%d)", pos.col, pos.row);
+            append_outline_pixel(&outline, pos);
+        }
+        
+        mark_edge(edge, row, col, marked);
+        nextPoint(bitmap, &edge, &row, &col, &pos, outline.color, clockwise,
+                  *marked, exp);
+            /* edge, row, and col are both input and output in the above */
+    } while (edge != NO_EDGE && !at_exception_got_fatal(exp));
+
+    if (ignore || at_exception_got_fatal(exp))
+        free_pixel_outline(&outline);
+
+    return outline;
+}
+
+
+
+pixel_outline_list_type
+find_outline_pixels(bitmap_type         const bitmap,
+                    bool                const bg_spec,
+                    pixel               const bg_color, 
+                    at_progress_func          notify_progress,
+                    void *              const progress_data,
+                    at_testcancel_func        test_cancel,
+                    void *              const testcancel_data,
+                    at_exception_type * const exp) {
+/*----------------------------------------------------------------------------
+   Return a list of outlines in the image whose raster is 'bitmap'.
+
+   The background color of the image is 'bg_color' if 'bg_spec' is true;
+   otherwise, there is no background color.
+-----------------------------------------------------------------------------*/
+    /* We go through a bitmap TOP to BOTTOM, LEFT to RIGHT, looking for
+       each pixel with an unmarked edge that we consider a starting point
+       of an outline.  When we find one, we trace the outline and add it
+       to the list, marking the edges in it as we go.
+    */
+    unsigned int const max_progress = bitmap.height * bitmap.width;
+    
+    pixel_outline_list_type outline_list;
+    unsigned int row;
+    bitmap_type marked;
+    
+    marked = new_bitmap (bitmap.width, bitmap.height);
+    
+    O_LIST_LENGTH(outline_list) = 0;
+    outline_list.data = NULL;
+    
+    for (row = 0; row < bitmap.height; ++row) {
+        unsigned int col;
+        for (col = 0; col < bitmap.width; ++col) {
+            pixel const color = getBitmapColor(bitmap, row, col);
+            bool const is_background =
+                bg_spec && PPM_EQUAL(color, bg_color);
+
+            if (notify_progress)
+                notify_progress((float)(row * bitmap.width + col) /
+                                ((float) max_progress * (float)3.0),
+                                progress_data);
+
+            /* A valid edge can be TOP for an outside outline.
+               Outside outlines are traced counterclockwise.
+            */
+
+            if (!is_background &&
+                is_unmarked_outline_edge(row, col, TOP,
+                                         bitmap, marked, color, exp)) {
+                pixel_outline_type outline;
+                
+                CHECK_FATAL();   /* FREE(DONE) outline_list */
+                
+                LOG1("#%u: (counterclockwise)", O_LIST_LENGTH(outline_list));
+                
+                outline = find_one_outline(bitmap, TOP, row, col, &marked,
+                                           false, false, exp);
+                CHECK_FATAL();    /* FREE(DONE) outline_list */
+                
+                O_CLOCKWISE(outline) = false;
+                append_pixel_outline(&outline_list, outline);
+                
+                LOG1(" [%u].\n", O_LENGTH (outline));
+            } else
+                CHECK_FATAL ();	/* FREE(DONE) outline_list */
+
+            /* A valid edge can be BOTTOM for an inside outline.
+               Inside outlines are traced clockwise.
+            */
+            if (row > 0) {
+                pixel const colorAbove = getBitmapColor(bitmap, row-1, col);
+                if (!(bg_spec && PPM_EQUAL(colorAbove, bg_color)) &&
+                    is_unmarked_outline_edge(row-1, col, BOTTOM,
+                                             bitmap, marked, colorAbove,exp)) {
+                    CHECK_FATAL(); /* FREE(DONE) outline_list */
+                    
+                    /* This lines are for debugging only:*/
+                    if (is_background) {
+                        pixel_outline_type outline;
+                    
+                        LOG1("#%u: (clockwise)", O_LIST_LENGTH(outline_list));
+                        
+                        outline = find_one_outline(bitmap, BOTTOM, row-1, col,
+                                                   &marked, true, false, exp);
+                        CHECK_FATAL(); /* FREE(DONE) outline_list */
+                        
+                        O_CLOCKWISE(outline) = true;
+                        append_pixel_outline(&outline_list, outline);
+                        
+                        LOG1(" [%u].\n", O_LENGTH(outline));
+                    } else {
+                        find_one_outline(bitmap, BOTTOM, row-1, col,
+                                         &marked, true, true, exp);
+                        CHECK_FATAL(); /* FREE(DONE) outline_list */
+                    }
+                } else
+                    CHECK_FATAL();	/* FREE(DONE) outline_list */
+            }
+            if (test_cancel && test_cancel(testcancel_data)) {
+                free_pixel_outline_list(&outline_list);
+                goto cleanup;
+            }
+        }
+    }
+ cleanup:
+    free_bitmap(&marked);
+    flush_log_output();
+    if (at_exception_got_fatal(exp))
+        free_pixel_outline_list(&outline_list);
+
+    return outline_list;
+}
+
diff --git a/converter/other/pamtosvg/pxl-outline.h b/converter/other/pamtosvg/pxl-outline.h
new file mode 100644
index 00000000..e37ccaf6
--- /dev/null
+++ b/converter/other/pamtosvg/pxl-outline.h
@@ -0,0 +1,79 @@
+/* pxl-outline.h: find a list of outlines which make up one character. */
+
+#ifndef PXL_OUTLINE_H
+#define PXL_OUTLINE_H
+
+#include "ppm.h"
+
+#include "autotrace.h"
+#include "exception.h"
+#include "bitmap.h"
+
+/* This is a list of contiguous points on the bitmap.  */
+typedef struct
+{
+  pm_pixelcoord * data;
+  unsigned length;
+  bool clockwise;
+  pixel color;
+  bool open;
+} pixel_outline_type;
+
+
+/* The Nth coordinate in the list.  */
+#define O_COORDINATE(p_o, n) ((p_o).data[n])
+
+
+/* The length of the list.  */
+#define O_LENGTH(p_o) ((p_o).length)
+
+/* Whether the outline moves clockwise or counterclockwise.  */
+#define O_CLOCKWISE(p_o) ((p_o).clockwise)
+
+/* Since a pixel outline is cyclic, the index of the next coordinate
+   after the last is the first, and the previous coordinate before the
+   first is the last.  */
+#define O_NEXT(p_o, n) (((n) + 1) % O_LENGTH (p_o))
+#define O_PREV(p_o, n) ((n) == 0				\
+                         ? O_LENGTH (p_o) - 1			\
+                         : (n) - 1)
+
+/* And the character turns into a list of such lists.  */
+typedef struct
+{
+  pixel_outline_type *data;
+  unsigned length;
+} pixel_outline_list_type;
+
+/* The Nth list in the list of lists.  */
+#define O_LIST_OUTLINE(p_o_l, n) ((p_o_l).data[n])
+
+/* The length of the list of lists.  */
+#define O_LIST_LENGTH(p_o_l) ((p_o_l).length)
+
+/* Find all pixels on the outline in the character C.  */
+pixel_outline_list_type
+find_outline_pixels (bitmap_type         const type,
+                     bool                const bg_spec,
+                     pixel               const bg_color, 
+                     at_progress_func          notify_progress,
+                     void *              const progress_data,
+                     at_testcancel_func        test_cancel,
+                     void *              const testcancel_data,
+                     at_exception_type * const exp);
+
+/* Find all pixels on the center line of the character C.  */
+pixel_outline_list_type
+find_centerline_pixels (bitmap_type         const type,
+                        pixel               const bg_color, 
+                        at_progress_func          notify_progress,
+                        void *              const progress_data,
+                        at_testcancel_func        test_cancel,
+                        void *              const testcancel_data,
+                        at_exception_type * const exp);
+
+/* Free the memory in the list.  */
+extern void
+free_pixel_outline_list (pixel_outline_list_type *);
+
+#endif /* not PXL_OUTLINE_H */
diff --git a/converter/other/pamtosvg/spline.c b/converter/other/pamtosvg/spline.c
new file mode 100644
index 00000000..5bdf0c0e
--- /dev/null
+++ b/converter/other/pamtosvg/spline.c
@@ -0,0 +1,193 @@
+/* spline.c: spline and spline list (represented as arrays) manipulation. */
+
+#include <assert.h>
+
+#include "mallocvar.h"
+
+#include "message.h"
+#include "point.h"
+#include "spline.h"
+#include "vector.h"
+
+/* Print a spline in human-readable form.  */
+
+void
+print_spline (FILE *f, spline_type s)
+{
+  assert(SPLINE_DEGREE (s) == LINEARTYPE || SPLINE_DEGREE (s) == CUBICTYPE);
+
+  if (SPLINE_DEGREE (s) == LINEARTYPE)
+    fprintf (f, "(%.3f,%.3f)--(%.3f,%.3f).\n",
+                START_POINT (s).x, START_POINT (s).y,
+                END_POINT (s).x, END_POINT (s).y);
+
+  else if (SPLINE_DEGREE (s) == CUBICTYPE)
+    fprintf (f, "(%.3f,%.3f)..ctrls(%.3f,%.3f)&(%.3f,%.3f)..(%.3f,%.3f).\n",
+                START_POINT (s).x, START_POINT (s).y,
+                CONTROL1 (s).x, CONTROL1 (s).y,
+                CONTROL2 (s).x, CONTROL2 (s).y,
+                END_POINT (s).x, END_POINT (s).y);
+}
+
+
+/* Evaluate the spline S at a given T value.  This is an implementation
+   of de Casteljau's algorithm.  See Schneider's thesis, p.37.
+   The variable names are taken from there.  */
+
+float_coord
+evaluate_spline (spline_type s, float t)
+{
+  spline_type V[4];    /* We need degree+1 splines, but assert degree <= 3.  */
+  signed i, j;
+  float one_minus_t = (float) 1.0 - t;
+  polynomial_degree degree = SPLINE_DEGREE (s);
+
+  for (i = 0; i <= degree; i++)
+    {
+      V[0].v[i].x = s.v[i].x;
+      V[0].v[i].y = s.v[i].y;
+      V[0].v[i].z = s.v[i].z;
+    }
+
+  for (j = 1; j <= degree; j++)
+    for (i = 0; i <= degree - j; i++)
+      {
+        float_coord t1 = Pmult_scalar (V[j - 1].v[i], one_minus_t);
+        float_coord t2 = Pmult_scalar (V[j - 1].v[i + 1], t);
+        float_coord temp = Padd (t1, t2);
+        V[j].v[i].x = temp.x;
+        V[j].v[i].y = temp.y;
+        V[j].v[i].z = temp.z;
+      }
+
+  return V[degree].v[0];
+}
+
+
+/* Return a new, empty, spline list.  */
+
+spline_list_type *
+new_spline_list (void)
+{
+  spline_list_type *answer;
+
+  MALLOCVAR(answer);
+  *answer = empty_spline_list();
+  return answer;
+}
+
+spline_list_type 
+empty_spline_list (void)
+{
+  spline_list_type answer;
+  SPLINE_LIST_DATA (answer) = NULL;
+  SPLINE_LIST_LENGTH (answer) = 0;
+  return answer;
+}
+
+/* Return a new spline list with SPLINE as the first element.  */
+
+spline_list_type *
+new_spline_list_with_spline (spline_type spline)
+{
+  spline_list_type *answer;
+
+  answer = new_spline_list();
+  MALLOCVAR(SPLINE_LIST_DATA(*answer));
+  SPLINE_LIST_ELT (*answer, 0) = spline;
+  SPLINE_LIST_LENGTH (*answer) = 1;
+
+  return answer;
+}
+
+
+/* Free the storage in a spline list.  We don't have to free the
+   elements, since they are arrays in automatic storage.  And we don't
+   want to free the list if it was empty.  */
+
+void
+free_spline_list (spline_list_type spline_list)
+{
+  if (SPLINE_LIST_DATA (spline_list) != NULL)
+    free (SPLINE_LIST_DATA (spline_list));
+}
+
+
+/* Append the spline S to the list SPLINE_LIST.  */
+
+void
+append_spline (spline_list_type *l, spline_type s)
+{
+  assert (l != NULL);
+
+  SPLINE_LIST_LENGTH (*l)++;
+  REALLOCARRAY(SPLINE_LIST_DATA(*l), SPLINE_LIST_LENGTH(*l));
+  LAST_SPLINE_LIST_ELT (*l) = s;
+}
+
+
+/* Tack the elements in the list S2 onto the end of S1.
+   S2 is not changed.  */
+
+void
+concat_spline_lists (spline_list_type *s1, spline_list_type s2)
+{
+  unsigned this_spline;
+  unsigned new_length;
+
+  assert (s1 != NULL);
+
+  new_length = SPLINE_LIST_LENGTH (*s1) + SPLINE_LIST_LENGTH (s2);
+
+  REALLOCARRAY_NOFAIL(SPLINE_LIST_DATA(*s1), new_length);
+
+  for (this_spline = 0; this_spline < SPLINE_LIST_LENGTH (s2); this_spline++)
+    SPLINE_LIST_ELT (*s1, SPLINE_LIST_LENGTH (*s1)++)
+      = SPLINE_LIST_ELT (s2, this_spline);
+}
+
+
+/* Return a new, empty, spline list array.  */
+
+spline_list_array_type
+new_spline_list_array (void)
+{
+  spline_list_array_type answer;
+
+  SPLINE_LIST_ARRAY_DATA (answer) = NULL;
+  SPLINE_LIST_ARRAY_LENGTH (answer) = 0;
+
+  return answer;
+}
+
+
+/* Free the storage in a spline list array.  We don't
+   want to free the list if it is empty.  */
+void
+free_spline_list_array (spline_list_array_type *spline_list_array)
+{
+  unsigned this_list;
+
+  for (this_list = 0;
+       this_list < SPLINE_LIST_ARRAY_LENGTH (*spline_list_array);
+       this_list++)
+    free_spline_list (SPLINE_LIST_ARRAY_ELT (*spline_list_array, this_list));
+
+  if (SPLINE_LIST_ARRAY_DATA (*spline_list_array) != NULL)
+    free (SPLINE_LIST_ARRAY_DATA (*spline_list_array));
+
+  flush_log_output ();
+}
+
+
+/* Append the spline S to the list SPLINE_LIST_ARRAY.  */
+
+void
+append_spline_list (spline_list_array_type *l, spline_list_type s)
+{
+  SPLINE_LIST_ARRAY_LENGTH (*l)++;
+  REALLOCARRAY_NOFAIL(SPLINE_LIST_ARRAY_DATA(*l),
+                      SPLINE_LIST_ARRAY_LENGTH(*l));
+  LAST_SPLINE_LIST_ARRAY_ELT (*l) = s;
+}
+
diff --git a/converter/other/pamtosvg/spline.h b/converter/other/pamtosvg/spline.h
new file mode 100644
index 00000000..05a56e23
--- /dev/null
+++ b/converter/other/pamtosvg/spline.h
@@ -0,0 +1,90 @@
+/* spline.h: manipulate the spline representation.  */
+
+#ifndef SPLINE_H
+#define SPLINE_H
+
+#include <stdio.h>
+
+#include "point.h"
+#include "autotrace.h"
+
+typedef at_polynomial_degree polynomial_degree;
+typedef at_spline_type spline_type;
+
+#define LINEARTYPE          AT_LINEARTYPE
+#define QUADRATICTYPE       AT_QUADRATICTYPE
+#define CUBICTYPE           AT_CUBICTYPE
+#define PARALLELELLIPSETYPE AT_PARALLELELLIPSETYPE
+#define ELLIPSETYPE         AT_ELLIPSETYPE
+#define CIRCLETYPE          AT_CIRCLETYPE
+
+#define START_POINT(spl)        ((spl).v[0])
+#define CONTROL1(spl)           ((spl).v[1])
+#define CONTROL2(spl)           ((spl).v[2])
+#define END_POINT(spl)          ((spl).v[3])
+#define SPLINE_DEGREE(spl)      ((spl).degree)
+#define SPLINE_LINEARITY(spl)   ((spl).linearity)
+
+#ifndef _IMPORTING
+/* Print a spline on the given file.  */
+extern void print_spline (FILE *, spline_type);
+
+/* Evaluate SPLINE at the given T value.  */
+extern float_coord evaluate_spline (spline_type spline, float t);
+#endif
+
+/* Each outline in a character is typically represented by many
+   splines.  So, here is a list structure for that:  */
+typedef at_spline_list_type spline_list_type;
+
+
+/* An empty list will have length zero (and null data).  */
+#define SPLINE_LIST_LENGTH(spll)  ((spll).length)
+
+/* The address of the beginning of the array of data.  */
+#define SPLINE_LIST_DATA(spll)    ((spll).data)
+
+/* The element with index 'index' in S_L.  */
+#define SPLINE_LIST_ELT(spll, index) ((spll).data[index])
+
+/* The last element in S_L.  */
+#define LAST_SPLINE_LIST_ELT(s_l) \
+  (SPLINE_LIST_DATA (s_l)[SPLINE_LIST_LENGTH (s_l) - 1])
+
+/* The previous and next elements to INDEX in S_L.  */
+#define NEXT_SPLINE_LIST_ELT(s_l, index)                \
+  SPLINE_LIST_ELT (s_l, ((index) + 1) % SPLINE_LIST_LENGTH (s_l))
+#define PREV_SPLINE_LIST_ELT(s_l, index)                \
+  SPLINE_LIST_ELT (s_l, index == 0                  \
+                        ? SPLINE_LIST_LENGTH (s_l) - 1          \
+                        : index - 1)
+
+#ifndef _IMPORTING
+/* Construct and destroy new `spline_list_type' objects.  */
+extern spline_list_type *new_spline_list (void); /* Allocate new memory */
+extern spline_list_type empty_spline_list (void); /* No allocation */
+extern spline_list_type *new_spline_list_with_spline (spline_type);
+extern void free_spline_list (spline_list_type);
+
+/* Append the spline S to the list S_LIST.  */
+extern void append_spline (spline_list_type *s_list, spline_type s);
+
+/* Append the elements in list S2 to S1, changing S1.  */
+extern void concat_spline_lists (spline_list_type *s1, spline_list_type s2);
+#endif
+
+typedef at_spline_list_array_type spline_list_array_type;
+
+/* Turns out we can use the same definitions for lists of lists as for
+   just lists.  But we define the usual names, just in case.  */
+#define SPLINE_LIST_ARRAY_LENGTH(spll) ((spll).length)
+#define SPLINE_LIST_ARRAY_DATA     SPLINE_LIST_DATA
+#define SPLINE_LIST_ARRAY_ELT(spll, index) ((spll).data[index])
+#define LAST_SPLINE_LIST_ARRAY_ELT LAST_SPLINE_LIST_ELT
+
+extern spline_list_array_type new_spline_list_array (void);
+extern void append_spline_list (spline_list_array_type *, spline_list_type);
+extern void free_spline_list_array (spline_list_array_type *);
+
+#endif /* not SPLINE_H */
+
diff --git a/converter/other/pamtosvg/testgrid.svg b/converter/other/pamtosvg/testgrid.svg
new file mode 100644
index 00000000..e82c5551
--- /dev/null
+++ b/converter/other/pamtosvg/testgrid.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" standalone="yes"?>
+<svg width="14" height="16">
+<path style="fill:#000000; stroke:none;" d="M0 0L0 16L14 16L14 1L13 0L0 0z"/>
+<path style="fill:#ffffff; stroke:none;" d="M1 0L2 1L1 0M3 0L4 1L3 0M5 0L6 1L5 0M7 0L8 1L7 0M9 0L10 1L9 0M11 0L12 1L11 0M13 0L14 1L13 0M1 2L2 3L1 2M3 2L4 3L3 2M5 2L6 3L5 2M7 2L8 3L7 2M9 2L10 3L9 2M11 2L12 3L11 2M13 2L14 3L13 2M1 4L2 5L1 4M3 4L4 5L3 4M5 4L6 5L5 4M7 4L8 5L7 4M9 4L10 5L9 4M11 4L12 5L11 4M13 4L14 5L13 4M1 6L2 7L1 6M3 6L4 7L3 6M5 6L6 7L5 6M7 6L8 7L7 6M9 6L10 7L9 6M11 6L12 7L11 6M13 6L14 7L13 6M1 8L2 9L1 8M3 8L4 9L3 8M5 8L6 9L5 8M7 8L8 9L7 8M9 8L10 9L9 8M11 8L12 9L11 8M13 8L14 9L13 8M1 10L2 11L1 10M3 10L4 11L3 10M5 10L6 11L5 10M7 10L8 11L7 10M9 10L10 11L9 10M11 10L12 11L11 10M13 10L14 11L13 10M1 12L2 13L1 12M3 12L4 13L3 12M5 12L6 13L5 12M7 12L8 13L7 12M9 12L10 13L9 12M11 12L12 13L11 12M13 12L14 13L13 12M1 14L2 15L1 14M3 14L4 15L3 14M5 14L6 15L5 14M7 14L8 15L7 14M9 14L10 15L9 14M11 14L12 15L11 14M13 14L14 15L13 14z"/>
+</svg>
diff --git a/converter/other/pamtosvg/testline.svg b/converter/other/pamtosvg/testline.svg
new file mode 100644
index 00000000..e704ce74
--- /dev/null
+++ b/converter/other/pamtosvg/testline.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" standalone="yes"?>
+<svg width="20" height="20">
+<path style="fill:#000000; stroke:none;" d="M0 0L0 20L20 20L20 0L0 0z"/>
+<path style="fill:#ffffff; stroke:none;" d="M5 2L15 18L16 18L5 2z"/>
+</svg>
diff --git a/converter/other/pamtosvg/thin-image.c b/converter/other/pamtosvg/thin-image.c
new file mode 100644
index 00000000..40ced794
--- /dev/null
+++ b/converter/other/pamtosvg/thin-image.c
@@ -0,0 +1,373 @@
+/* thin-image.c: thin binary image
+
+   Copyright (C) 2001, 2002 Martin Weber
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public License
+   as published by the Free Software Foundation; either version 2.1 of
+   the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+   USA. */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mallocvar.h"
+
+#include "thin-image.h"
+#include "logreport.h"
+#include "message.h"
+#include "bitmap.h"
+ 
+#define PIXEL_SET(p, new)  ((void)memcpy((p), (new), sizeof(Pixel)))
+#define PIXEL_EQUAL(p1, p2) \
+    ((p1)[0] == (p2)[0] && (p1)[1] == (p2)[1] && (p1)[2] == (p2)[2])
+
+ 
+typedef unsigned char Pixel[3];  /* RGB pixel data type */ 
+
+ 
+void thin3(bitmap_type *image, Pixel colour); 
+void thin1(bitmap_type *image, unsigned char colour); 
+ 
+ 
+/* -------------------------------- ThinImage - Thin binary image. --------------------------- * 
+ *                                                            
+ *    Description:                                                    
+ *        Thins the supplied binary image using Rosenfeld's parallel   
+ *        thinning algorithm.                                         
+ *                                                                     
+ *    On Entry:                                                        
+ *        image = Image to thin.                                       
+ *                                                                     
+ * -------------------------------------------------------------------------------------------- */ 
+ 
+ 
+/* Direction masks:                  */ 
+/*   N     S     W        E            */ 
+static        unsigned int     masks[]         = { 0200, 0002, 0040, 0010 }; 
+ 
+/*    True if pixel neighbor map indicates the pixel is 8-simple and  */ 
+/*    not an end point and thus can be deleted.  The neighborhood     */ 
+/*    map is defined as an integer of bits abcdefghi with a non-zero  */ 
+/*    bit representing a non-zero pixel.  The bit assignment for the  */ 
+/*    neighborhood is:                                                */ 
+/*                                                                    */ 
+/*                            a b c                                   */ 
+/*                            d e f                                   */ 
+/*                            g h i                                   */ 
+ 
+static        unsigned char   todelete[512] = { 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 
+              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+              1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; 
+
+static pixel background;
+
+
+void
+thin_image(bitmap_type *image, bool bgSpec, pixel bg,
+           at_exception_type * exp)
+{ 
+    /* This is nasty as we need to call thin once for each  
+     * colour in the image the way I do this is to keep a second  
+     * copy of the bitmap and to use this to keep 
+     * track of which colours have not yet been processed, 
+     * trades time for pathological case memory.....*/ 
+    long m, n, num_pixels;
+    bitmap_type bm; 
+    unsigned int const spp = image->np;
+	unsigned int const width = image->width;
+	unsigned int const height = image->height;
+
+    if (bgSpec)
+        background = bg;
+    else 
+        PPM_ASSIGN(background, 255, 255, 255);
+
+    /* Clone the image */
+    bm.height = image->height;
+    bm.width = image->width;
+    bm.np = image->np;
+    MALLOCARRAY(bm.bitmap, height * width * spp); 
+    if (bm.bitmap == NULL)
+        pm_error("Unable to get memory for thin image bitmap clone");
+    memcpy(bm.bitmap, image->bitmap, height * width * spp); 
+
+    num_pixels = height * width;
+    switch (spp)
+    {
+	case 3:
+	{
+	    Pixel *ptr = (Pixel*)bm.bitmap;
+	    Pixel bg_color;
+	    bg_color[0] = PPM_GETR(background);
+	    bg_color[1] = PPM_GETG(background);
+	    bg_color[2] = PPM_GETB(background);
+
+	    for (n = num_pixels - 1; n >= 0L; --n)
+	    {
+		Pixel p;
+
+		PIXEL_SET(p, ptr[n]);
+		if (!PIXEL_EQUAL(p, bg_color))
+		{ 
+		    /* we have a new colour in the image */ 
+		    LOG3("Thinning colour (%x, %x, %x)\n", p[0], p[1], p[2]);
+		    for (m = n - 1; m >= 0L; --m)
+		    {
+			if (PIXEL_EQUAL(ptr[m], p))
+			    PIXEL_SET(ptr[m], bg_color);
+		    }
+		    thin3(image, p); 
+		} 
+	    } 
+	    break;
+	} 
+
+	case 1:
+	{
+	    unsigned char * const ptr = bm.bitmap;
+	    unsigned char bg_color;
+
+	    if (PPM_ISGRAY(background))
+            bg_color = PPM_GETR(background);
+	    else
+            bg_color = PPM_LUMIN(background);
+
+	    for (n = num_pixels - 1; n >= 0L; --n)
+	    {
+		unsigned char c = ptr[n];
+		if (c != bg_color)
+		{ 
+		    LOG1 ("Thinning colour %x\n", c);
+		    for (m = n - 1; m >= 0L; --m)
+			if (ptr[m] == c) ptr[m] = bg_color;
+		    thin1(image, c); 
+		} 
+	    } 
+	    break;
+	} 
+
+	default:
+	{
+	  LOG1 ("thin_image: %u-plane images are not supported", spp);
+	  at_exception_fatal(exp, "thin_image: wrong plane images are passed");
+	  goto cleanup;
+	}
+    }
+ cleanup:
+    free (bm.bitmap); 
+} 
+
+ 
+void thin3(bitmap_type *image, Pixel colour) 
+{ 
+      Pixel *ptr, *y_ptr, *y1_ptr;
+      Pixel bg_color;
+      unsigned int    xsize, ysize;   /* Image resolution             */ 
+      unsigned int    x, y;           /* Pixel location               */ 
+      unsigned int    i;              /* Pass index           */ 
+      unsigned int    pc      = 0;    /* Pass count           */ 
+      unsigned int    count   = 1;    /* Deleted pixel count          */ 
+      unsigned int    p, q;           /* Neighborhood maps of adjacent*/ 
+                                      /* cells                        */ 
+      unsigned char   *qb;            /* Neighborhood maps of previous*/ 
+                                      /* scanline                     */ 
+      unsigned int    m;              /* Deletion direction mask      */ 
+ 
+      bg_color[0] = PPM_GETR(background);
+      bg_color[1] = PPM_GETG(background);
+      bg_color[2] = PPM_GETB(background);
+
+      LOG (" Thinning image.....\n "); 
+      xsize = image->width;
+      ysize = image->height;
+      MALLOCARRAY_NOFAIL(qb, xsize); 
+      qb[xsize-1] = 0;                /* Used for lower-right pixel   */ 
+      ptr = (Pixel*)image->bitmap;
+ 
+      while ( count ) {               /* Scan image while deletions   */ 
+          pc++; 
+          count = 0; 
+ 
+          for ( i = 0 ; i < 4 ; i++ ) { 
+ 
+              m = masks[i]; 
+ 
+              /* Build initial previous scan buffer.                  */ 
+              p = PIXEL_EQUAL(ptr[0], colour); 
+              for ( x = 0 ; x < xsize-1 ; x++ ) 
+                  qb[x] = (unsigned char) (p = ((p<<1)&0006) | (unsigned int) PIXEL_EQUAL(ptr[x+1],
+				   colour)); 
+ 
+              /* Scan image for pixel deletion candidates.            */ 
+	      y_ptr = ptr; y1_ptr = ptr + xsize; 
+              for (y = 0; y < ysize - 1; y++, y_ptr += xsize, y1_ptr += xsize)
+	      { 
+                  q = qb[0]; 
+                  p = ((q<<2)&0330) | (unsigned int) PIXEL_EQUAL(y1_ptr[0], colour); 
+ 
+                  for ( x = 0 ; x < xsize-1 ; x++ ) { 
+                      q = qb[x]; 
+                      p = ((p<<1)&0666) | ((q<<3)&0110) | 
+			  (unsigned int) PIXEL_EQUAL(y1_ptr[x+1], colour);
+                      qb[x] = (unsigned char) p; 
+                      if ((i != 2 || x != 0) && ((p&m) == 0) && todelete[p] ) { 
+                          count++;  /* delete the pixel */ 
+			  PIXEL_SET(y_ptr[x], bg_color);
+                      } 
+                  } 
+ 
+                  /* Process right edge pixel.                        */ 
+                  p = (p<<1)&0666; 
+                  if  (i != 3 && (p&m) == 0 && todelete[p] ) { 
+                      count++; 
+		      PIXEL_SET(y_ptr[xsize-1], bg_color);
+                  } 
+              } 
+ 
+	      if (i != 1)
+	      {
+            /* Process bottom scan line.                            */ 
+            q = qb[0]; 
+            p = ((q<<2)&0330); 
+
+            y_ptr = ptr + xsize * (ysize - 1);
+            for ( x = 0 ; x < xsize ; x++ ) { 
+              q = qb[x]; 
+              p = ((p<<1)&0666) | ((q<<3)&0110); 
+              if ((i != 2 || x != 0) && (p&m) == 0 && todelete[p]) { 
+                count++; 
+                PIXEL_SET(y_ptr[x], bg_color);
+		      } 
+            } 
+           }
+          } 
+          LOG2 ("ThinImage: pass %d, %d pixels deleted\n", pc, count); 
+      } 
+      free (qb); 
+} 
+
+ 
+void thin1(bitmap_type *image, unsigned char colour) 
+{ 
+      unsigned char *ptr, *y_ptr, *y1_ptr;
+      unsigned char bg_color;
+      unsigned int    xsize, ysize;   /* Image resolution             */ 
+      unsigned int    x, y;           /* Pixel location               */ 
+      unsigned int    i;              /* Pass index           */ 
+      unsigned int    pc      = 0;    /* Pass count           */ 
+      unsigned int    count   = 1;    /* Deleted pixel count          */ 
+      unsigned int    p, q;           /* Neighborhood maps of adjacent*/ 
+                                      /* cells                        */ 
+      unsigned char   *qb;            /* Neighborhood maps of previous*/ 
+                                      /* scanline                     */ 
+      unsigned int    m;              /* Deletion direction mask      */ 
+
+      if (PPM_ISGRAY(background))
+          bg_color = PPM_GETR(background);
+      else
+          bg_color = PPM_LUMIN(background);
+
+      LOG (" Thinning image.....\n "); 
+      xsize = image->width;
+      ysize = image->height;
+      MALLOCARRAY_NOFAIL(qb, xsize); 
+      qb[xsize-1] = 0;                /* Used for lower-right pixel   */ 
+      ptr = image->bitmap;
+ 
+      while ( count ) {               /* Scan image while deletions   */ 
+          pc++; 
+          count = 0; 
+ 
+          for ( i = 0 ; i < 4 ; i++ ) { 
+ 
+              m = masks[i]; 
+ 
+              /* Build initial previous scan buffer.                  */ 
+              p = (ptr[0] == colour); 
+              for ( x = 0 ; x < xsize-1 ; x++ ) 
+                  qb[x] = (unsigned char) (p = ((p<<1)&0006) | (unsigned int)(ptr[x+1] == colour)); 
+ 
+              /* Scan image for pixel deletion candidates.            */ 
+	      y_ptr = ptr; y1_ptr = ptr + xsize; 
+              for (y = 0; y < ysize - 1; y++, y_ptr += xsize, y1_ptr += xsize)
+	      { 
+                  q = qb[0]; 
+                  p = ((q<<2)&0330) | (y1_ptr[0] == colour); 
+ 
+                  for ( x = 0 ; x < xsize-1 ; x++ ) { 
+                      q = qb[x]; 
+                      p = ((p<<1)&0666) | ((q<<3)&0110) | (unsigned int) (y1_ptr[x+1]==colour); 
+                      qb[x] = (unsigned char) p; 
+                      if  ( ((p&m) == 0) && todelete[p] ) { 
+                          count++; 
+			  y_ptr[x] = bg_color;  /* delete the pixel */ 
+                      } 
+                  } 
+ 
+                  /* Process right edge pixel.                        */ 
+                  p = (p<<1)&0666; 
+                  if  ( (p&m) == 0 && todelete[p] ) { 
+                      count++; 
+                      y_ptr[xsize-1] = bg_color;
+                  } 
+              } 
+ 
+              /* Process bottom scan line.                            */ 
+	      q = qb[0]; 
+	      p = ((q<<2)&0330); 
+ 
+	      y_ptr = ptr + xsize * (ysize - 1);
+              for ( x = 0 ; x < xsize ; x++ ) { 
+                  q = qb[x]; 
+                  p = ((p<<1)&0666) | ((q<<3)&0110); 
+                  if  ( (p&m) == 0 && todelete[p] ) { 
+                      count++; 
+                      y_ptr[x] = bg_color;
+                  } 
+              } 
+          } 
+          LOG2("thin1: pass %d, %d pixels deleted\n", pc, count); 
+      } 
+      free (qb); 
+} 
diff --git a/converter/other/pamtosvg/thin-image.h b/converter/other/pamtosvg/thin-image.h
new file mode 100644
index 00000000..4e0a77ee
--- /dev/null
+++ b/converter/other/pamtosvg/thin-image.h
@@ -0,0 +1,37 @@
+/* thin-image.h: thin binary image
+
+   Copyright (C) 2001, 2002 Martin Weber
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public License
+   as published by the Free Software Foundation; either version 2.1 of
+   the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+   USA. */
+
+#ifndef THIN_IMAGE_H
+#define THIN_IMAGE_H
+
+/*
+ * C code from the article
+ * "Efficient Binary Image Thinning using Neighborhood Maps"
+ * by Joseph M. Cychosz, 3ksnn64@ecn.purdue.edu
+ * in "Graphics Gems IV", Academic Press, 1994
+ */
+
+#include "bitmap.h"
+#include "exception.h"
+
+void
+thin_image(bitmap_type *image, bool bg_spec, pixel bg_color,
+           at_exception_type * exp);
+
+#endif /* not THIN_IMAGE_H */
diff --git a/converter/other/pamtosvg/vector.c b/converter/other/pamtosvg/vector.c
new file mode 100644
index 00000000..559163fc
--- /dev/null
+++ b/converter/other/pamtosvg/vector.c
@@ -0,0 +1,254 @@
+/* vector.c: vector/point operations. */
+
+#include <math.h>
+#include <errno.h>
+#include <assert.h>
+#include <string.h>
+
+#include "pm_c_util.h"
+
+#include "vector.h"
+#include "message.h"
+#include "epsilon-equal.h"
+
+static float acos_d (float, at_exception_type * excep);
+
+
+/* Given the point COORD, return the corresponding vector.  */
+
+vector_type
+make_vector (const float_coord c)
+{
+  vector_type v;
+
+  v.dx = c.x;
+  v.dy = c.y;
+  v.dz = c.z;
+
+  return v;
+}
+
+
+/* And the converse: given a vector, return the corresponding point.  */
+
+float_coord
+vector_to_point (const vector_type v)
+{
+  float_coord coord;
+
+  coord.x = v.dx;
+  coord.y = v.dy;
+
+  return coord;
+}
+
+
+float
+magnitude (const vector_type v)
+{
+  return (float) sqrt (v.dx * v.dx + v.dy * v.dy + v.dz * v.dz);
+}
+
+
+vector_type
+normalize (const vector_type v)
+{
+  vector_type new_v;
+  float m = magnitude (v);
+
+  /* assert (m > 0.0); */
+
+  if (m > 0.0)
+  {
+    new_v.dx = v.dx / m;
+    new_v.dy = v.dy / m;
+    new_v.dz = v.dz / m;
+  }
+  else
+  {
+	new_v.dx = v.dx;
+    new_v.dy = v.dy;
+    new_v.dz = v.dz;
+  }
+
+  return new_v;
+}
+
+
+vector_type
+Vadd (const vector_type v1, const vector_type v2)
+{
+  vector_type new_v;
+
+  new_v.dx = v1.dx + v2.dx;
+  new_v.dy = v1.dy + v2.dy;
+  new_v.dz = v1.dz + v2.dz;
+
+  return new_v;
+}
+
+
+float
+Vdot (const vector_type v1, const vector_type v2)
+{
+  return v1.dx * v2.dx + v1.dy * v2.dy + v1.dz * v2.dz;
+}
+
+
+vector_type
+Vmult_scalar (const vector_type v, const float r)
+{
+  vector_type new_v;
+
+  new_v.dx = v.dx * r;
+  new_v.dy = v.dy * r;
+  new_v.dz = v.dz * r;
+
+  return new_v;
+}
+
+
+/* Given the IN_VECTOR and OUT_VECTOR, return the angle between them in
+   degrees, in the range zero to 180.  */
+
+float
+Vangle (const vector_type in_vector, 
+	const vector_type out_vector,
+	at_exception_type * exp)
+{
+  vector_type v1 = normalize (in_vector);
+  vector_type v2 = normalize (out_vector);
+
+  return acos_d (Vdot (v2, v1), exp);
+}
+
+
+float_coord
+Vadd_point (const float_coord c, const vector_type v)
+{
+  float_coord new_c;
+
+  new_c.x = c.x + v.dx;
+  new_c.y = c.y + v.dy;
+  new_c.z = c.z + v.dz;
+  return new_c;
+}
+
+
+float_coord
+Vsubtract_point (const float_coord c, const vector_type v)
+{
+  float_coord new_c;
+
+  new_c.x = c.x - v.dx;
+  new_c.y = c.y - v.dy;
+  new_c.z = c.z - v.dz;
+  return new_c;
+}
+
+
+pm_pixelcoord
+Vadd_int_point(pm_pixelcoord const c,
+               vector_type   const v) {
+
+    pm_pixelcoord a;
+
+    a.col = ROUND ((float) c.col + v.dx);
+    a.row = ROUND ((float) c.row + v.dy);
+    
+    return a;
+}
+
+
+vector_type
+Vabs (const vector_type v)
+{
+  vector_type new_v;
+
+  new_v.dx = (float) fabs (v.dx);
+  new_v.dy = (float) fabs (v.dy);
+  new_v.dz = (float) fabs (v.dz);
+  return new_v;
+}
+
+
+/* Operations on points.  */
+
+float_coord
+Padd (const float_coord coord1, const float_coord coord2)
+{
+  float_coord sum;
+
+  sum.x = coord1.x + coord2.x;
+  sum.y = coord1.y + coord2.y;
+  sum.z = coord1.z + coord2.z;
+
+  return sum;
+}
+
+
+float_coord
+Pmult_scalar (const float_coord coord, const float r)
+{
+  float_coord answer;
+
+  answer.x = coord.x * r;
+  answer.y = coord.y * r;
+  answer.z = coord.z * r;
+
+  return answer;
+}
+
+
+vector_type
+Psubtract (const float_coord c1, const float_coord c2)
+{
+  vector_type v;
+
+  v.dx = c1.x - c2.x;
+  v.dy = c1.y - c2.y;
+  v.dz = c1.z - c2.z;
+
+  return v;
+}
+
+
+
+/* Operations on integer points.  */
+
+vector_type
+IPsubtract(pm_pixelcoord const coord1,
+           pm_pixelcoord const coord2) {
+
+    vector_type v;
+
+    v.dx = (int) (coord1.col - coord2.col);
+    v.dy = (int) (coord1.row - coord2.row);
+    v.dz = 0.0;
+    
+    return v;
+}
+
+
+
+static float
+acos_d (float v, at_exception_type * excep)
+{
+  float a;
+
+  if (epsilon_equal (v, 1.0))
+    v = 1.0;
+  else if (epsilon_equal (v, -1.0))
+    v = -1.0;
+
+  errno = 0;
+  a = (float) acos (v);
+  if (errno == ERANGE || errno == EDOM)
+    {
+      at_exception_fatal(excep, strerror(errno));
+      return 0.0;
+    }
+  
+  
+  return a * (float) 180.0 / (float) M_PI;
+}
diff --git a/converter/other/pamtosvg/vector.h b/converter/other/pamtosvg/vector.h
new file mode 100644
index 00000000..74fb2fd2
--- /dev/null
+++ b/converter/other/pamtosvg/vector.h
@@ -0,0 +1,71 @@
+/* vector.h: operations on vectors and points. */
+
+#ifndef VECTOR_H
+#define VECTOR_H
+
+#include "point.h"
+#include "exception.h"
+
+/* Our vectors are represented as displacements along the x and y axes.  */
+
+typedef struct
+{
+  float dx, dy, dz;
+} vector_type;
+
+
+/* Consider a point as a vector from the origin.  */
+extern vector_type make_vector (const float_coord);
+
+/* And a vector as a point, i.e., a displacement from the origin.  */
+extern float_coord vector_to_point (const vector_type);
+
+
+/* Definitions for these common operations can be found in any decent
+   linear algebra book, and most calculus books.  */
+
+extern float magnitude (const vector_type);
+extern vector_type normalize (const vector_type);
+
+extern vector_type Vadd (const vector_type, const vector_type);
+extern float Vdot (const vector_type, const vector_type);
+extern vector_type Vmult_scalar (const vector_type, const float);
+extern float Vangle (const vector_type in, const vector_type out, at_exception_type * exp);
+
+/* These operations could have been named `P..._vector' just as well as
+   V..._point, so we may as well allow both names.  */
+#define Padd_vector Vadd_point
+extern float_coord Vadd_point
+  (const float_coord, const vector_type);
+
+#define Psubtract_vector Vsubtract_point
+extern float_coord Vsubtract_point
+  (const float_coord, const vector_type);
+
+/* This returns the rounded sum.  */
+#define IPadd_vector Vadd_int_point
+
+pm_pixelcoord
+Vadd_int_point(pm_pixelcoord const c,
+               vector_type   const v);
+
+/* Take the absolute value of both components.  */
+extern vector_type Vabs (const vector_type);
+
+/* Operations on points with real coordinates.  It is not orthogonal,
+   but more convenient, to have the subtraction operator return a
+   vector, and the addition operator return a point.  */
+extern vector_type Psubtract
+  (const float_coord, const float_coord);
+
+vector_type
+IPsubtract(pm_pixelcoord const coord1,
+           pm_pixelcoord const coord2);
+
+/* These are heavily used in spline fitting.  */
+extern float_coord Padd (const float_coord,
+                                  const float_coord);
+extern float_coord Pmult_scalar (const float_coord, const float);
+
+#endif
+
diff --git a/converter/other/pamtotga.c b/converter/other/pamtotga.c
new file mode 100644
index 00000000..1e0808ed
--- /dev/null
+++ b/converter/other/pamtotga.c
@@ -0,0 +1,572 @@
+/* pamtotga.c - read a portable pixmap and produce a TrueVision Targa file
+**
+** Copyright (C) 1989, 1991 by Mark Shand and Jef Poskanzer
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _BSD_SOURCE  /* Make sure string.h contains strdup() */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <string.h>
+
+#include "pam.h"
+#include "pammap.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "tga.h"
+
+/* Max number of colors allowed for colormapped output. */
+#define MAXCOLORS 256
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+    char *outName;
+    enum TGAbaseImageType imgType;
+    bool defaultFormat;
+    unsigned int norle;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Parse the program arguments (given by argc and argv) into a form
+   the program can deal with more easily -- a cmdline_info structure.
+   If the syntax is invalid, issue a message and exit the program via
+   pm_error().
+
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+    unsigned int option_def_index;
+
+    unsigned int outNameSpec;
+    unsigned int cmap, mono, rgb;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "name",       OPT_STRING, 
+            &cmdlineP->outName, &outNameSpec, 0);
+    OPTENT3(0,   "cmap",       OPT_FLAG, 
+            NULL, &cmap, 0);
+    OPTENT3(0,   "mono",       OPT_FLAG, 
+            NULL, &mono, 0);
+    OPTENT3(0,   "rgb",        OPT_FLAG, 
+            NULL, &rgb, 0);
+    OPTENT3(0,   "norle",      OPT_FLAG, 
+            NULL, &cmdlineP->norle, 0);
+    
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (cmap + mono + rgb > 1)
+        pm_error("You may specify only one of -cmap, -mono, and -rgb.");
+
+    if (cmap + mono + rgb == 0)
+        cmdlineP->defaultFormat = TRUE;
+    else {
+        cmdlineP->defaultFormat = FALSE;
+    
+        if (cmap)
+            cmdlineP->imgType = TGA_MAP_TYPE;
+        else if (mono)
+            cmdlineP->imgType = TGA_MONO_TYPE;
+        else if (rgb)
+            cmdlineP->imgType = TGA_RGB_TYPE;
+    }
+
+    if (!outNameSpec)
+        cmdlineP->outName = NULL;
+    
+    if (argc-1 == 0) 
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->inputFilespec = argv[1];
+
+}
+
+
+static void
+writeTgaHeader(struct ImageHeader const tgaHeader) {
+
+    unsigned char flags;
+
+    putchar(tgaHeader.IdLength);
+    putchar(tgaHeader.CoMapType);
+    putchar(tgaHeader.ImgType);
+    putchar(tgaHeader.Index_lo);
+    putchar(tgaHeader.Index_hi);
+    putchar(tgaHeader.Length_lo);
+    putchar(tgaHeader.Length_hi);
+    putchar(tgaHeader.CoSize);
+    putchar(tgaHeader.X_org_lo);
+    putchar(tgaHeader.X_org_hi);
+    putchar(tgaHeader.Y_org_lo);
+    putchar(tgaHeader.Y_org_hi);
+    putchar(tgaHeader.Width_lo);
+    putchar(tgaHeader.Width_hi);
+    putchar(tgaHeader.Height_lo);
+    putchar(tgaHeader.Height_hi);
+    putchar(tgaHeader.PixelSize);
+    flags = (tgaHeader.AttBits & 0xf) | 
+        ((tgaHeader.Rsrvd & 0x1) << 4) |
+        ((tgaHeader.OrgBit & 0x1) << 5) | 
+        ((tgaHeader.OrgBit & 0x3) << 6);
+    putchar(flags);
+
+    if (tgaHeader.IdLength > 0)
+        fwrite(tgaHeader.Id, 1, (int) tgaHeader.IdLength, stdout);
+}
+    
+
+
+static void
+putPixel(struct pam *          const pamP,
+         tuple                 const tuple, 
+         enum TGAbaseImageType const imgType, 
+         bool                  const withAlpha,
+         tuplehash             const cht) {
+/*----------------------------------------------------------------------------
+   Write a single pixel of the TGA raster to Standard Output.  The
+   pixel is to have color 'tuple'.  The raster has format 'imgType'.
+   The color palette from which the specified color is to be drawn, if
+   'imgType' indicates use of a color palette, is 'cht'.
+-----------------------------------------------------------------------------*/
+    if (imgType == TGA_MAP_TYPE) {
+        int retval;
+        int found;
+
+        pnm_lookuptuple(pamP, cht, tuple, &found, &retval);
+        if (!found)
+            pm_error("Internal error: color not found in map that was "
+                     "generated from all the colors in the image");
+        putchar(retval);
+    } else {
+        if (imgType == TGA_RGB_TYPE && pamP->depth < 3) {
+            /* Make RGB pixel out of a single input plane */
+            unsigned int plane;
+            
+            for (plane = 0; plane < 3; ++plane) 
+                putchar(pnm_scalesample(tuple[0], 
+                                        pamP->maxval, TGA_MAXVAL));
+        } else if (imgType == TGA_MONO_TYPE)
+            putchar(pnm_scalesample(tuple[0], 
+                                    pamP->maxval, TGA_MAXVAL));
+        else {
+            putchar(pnm_scalesample(tuple[PAM_BLU_PLANE], 
+                                    pamP->maxval, TGA_MAXVAL));
+            putchar(pnm_scalesample(tuple[PAM_GRN_PLANE], 
+                                    pamP->maxval, TGA_MAXVAL));
+            putchar(pnm_scalesample(tuple[PAM_RED_PLANE], 
+                                    pamP->maxval, TGA_MAXVAL));
+            if (withAlpha)
+                putchar(pnm_scalesample(tuple[PAM_TRN_PLANE], 
+                                        pamP->maxval, TGA_MAXVAL));
+        }
+    }
+}
+
+
+
+static void
+putMapEntry(struct pam * const pamP, 
+            tuple        const value, 
+            int          const size) {
+
+    if (size == 15 || size == 16) {
+        /* 5 bits each of red, green, and blue.  Watch for byte order */
+
+        tuple const tuple31 = pnm_allocpamtuple(pamP);
+
+        pnm_scaletuple(pamP, tuple31, value, 31);
+        {
+            int const mapentry = 
+                tuple31[PAM_BLU_PLANE] << 0 |
+                tuple31[PAM_GRN_PLANE] << 5 |
+                tuple31[PAM_RED_PLANE] << 10;
+            
+            putchar(mapentry % 256);
+            putchar(mapentry / 256);
+        }
+        pnm_freepamtuple(tuple31);
+    } else if (size == 8)
+        putchar(pnm_scalesample(value[0], 
+                                pamP->maxval, TGA_MAXVAL));
+    else {
+        /* Must be 24 or 32 */
+        putchar(pnm_scalesample(value[PAM_BLU_PLANE], 
+                                pamP->maxval, TGA_MAXVAL));
+        putchar(pnm_scalesample(value[PAM_GRN_PLANE], 
+                                pamP->maxval, TGA_MAXVAL));
+        putchar(pnm_scalesample(value[PAM_RED_PLANE], 
+                                    pamP->maxval, TGA_MAXVAL));
+        if (size == 32)
+            putchar(pnm_scalesample(value[PAM_TRN_PLANE], 
+                                    pamP->maxval, TGA_MAXVAL));
+    }
+}
+
+
+
+static void
+computeRunlengths(struct pam * const pamP, 
+                   tuple *      const tuplerow, 
+                   int *        const runlength) {
+
+    int col, start;
+
+    /* Initialize all run lengths to 0.  (This is just an error check.) */
+    for (col = 0; col < pamP->width; ++col)
+        runlength[col] = 0;
+    
+    /* Find runs of identical pixels. */
+    for ( col = 0; col < pamP->width; ) {
+        start = col;
+        do {
+            ++col;
+        } while ( col < pamP->width &&
+                  col - start < 128 &&
+                  pnm_tupleequal(pamP, tuplerow[col], tuplerow[start]));
+        runlength[start] = col - start;
+    }
+    
+    /* Now look for runs of length-1 runs, and turn them into negative runs. */
+    for (col = 0; col < pamP->width; ) {
+        if (runlength[col] == 1) {
+            start = col;
+            while (col < pamP->width &&
+                   col - start < 128 &&
+                   runlength[col] == 1 ) {
+                runlength[col] = 0;
+                ++col;
+            }
+            runlength[start] = - ( col - start );
+        } else
+            col += runlength[col];
+    }
+}
+
+
+
+static void
+computeOutName(struct cmdlineInfo const cmdline, 
+               const char ** const outNameP) {
+    
+    char * workarea;
+
+    if (cmdline.outName)
+        workarea = strdup(cmdline.outName);
+    else if (streq(cmdline.inputFilespec, "-"))
+        workarea = NULL;
+    else {
+        char * cp;
+        workarea = strdup(cmdline.inputFilespec);
+        cp = strchr(workarea, '.');
+        if (cp != NULL)
+        	*cp = '\0';	/* remove extension */
+    }
+    
+    if (workarea == NULL)
+        *outNameP = NULL;
+    else {
+        /* Truncate the name to fit TGA specs */
+        if (strlen(workarea) > IMAGEIDFIELDMAXSIZE)
+            workarea[IMAGEIDFIELDMAXSIZE] = '\0';
+        *outNameP = workarea;
+    }
+}
+
+
+
+static void
+validateTupleType(struct pam * const pamP) {
+
+    if (streq(pamP->tuple_type, "RGB_ALPHA")) {
+        if (pamP->depth < 4)
+            pm_error("Invalid depth for tuple type RGB_ALPHA.  "
+                     "Should have at least 4 planes, but has %d.", 
+                     pamP->depth);
+    } else if (streq(pamP->tuple_type, "RGB")) {
+        if (pamP->depth < 3)
+            pm_error("Invalid depth for tuple type RGB.  "
+                     "Should have at least 3 planes, but has %d.", 
+                     pamP->depth);
+    } else if (streq(pamP->tuple_type, "GRAYSCALE")) {
+    } else if (streq(pamP->tuple_type, "BLACKANDWHITE")) {
+    } else 
+        pm_error("Invalid type of input.  PAM tuple type is '%s'.  "
+                 "This programs understands only RGB_ALPHA, RGB, GRAYSCALE, "
+                 "and BLACKANDWHITE.", pamP->tuple_type);
+}
+
+
+
+static void
+computeImageType_cht(struct pam *            const pamP,
+                     struct cmdlineInfo      const cmdline, 
+                     tuple **                const tuples,
+                     enum TGAbaseImageType * const baseImgTypeP,
+                     bool *                  const withAlphaP,
+                     tupletable *            const chvP,
+                     tuplehash *             const chtP, 
+                     int *                   const ncolorsP) {
+
+    unsigned int ncolors;
+    enum TGAbaseImageType baseImgType;
+    bool withAlpha;
+
+    validateTupleType(pamP);
+
+    withAlpha = (streq(pamP->tuple_type, "RGB_ALPHA"));
+
+    if (cmdline.defaultFormat) {
+        /* default the image type */
+        if (withAlpha) {
+            baseImgType = TGA_RGB_TYPE;
+            *chvP = NULL;
+        } else if (pamP->depth > 1) {
+            pm_message("computing colormap...");
+            *chvP = 
+                pnm_computetuplefreqtable(pamP, tuples, MAXCOLORS, &ncolors);
+            if (*chvP == NULL) {
+                pm_message("Too many colors for colormapped TGA.  Doing RGB.");
+                baseImgType = TGA_RGB_TYPE;
+            } else 
+                baseImgType = TGA_MAP_TYPE;
+        } else {
+            baseImgType = TGA_MONO_TYPE;
+            *chvP = NULL;
+        }
+    } else {
+        baseImgType = cmdline.imgType;
+
+        if (baseImgType == TGA_MAP_TYPE) {
+            if (withAlpha)
+                pm_error("Can't do a colormap because image has transparency "
+                         "information");
+            pm_message("computing colormap...");
+            *chvP = 
+                pnm_computetuplefreqtable(pamP, tuples, MAXCOLORS, &ncolors);
+            if (*chvP == NULL) 
+                pm_error("Too many colors for colormapped TGA.  "
+                         "Use 'pnmquant %d' to reduce the number of colors.", 
+                         MAXCOLORS);
+        } else
+            *chvP = NULL;
+        if (baseImgType == TGA_MONO_TYPE && pamP->depth > 1)
+            pm_error("For Mono TGA output, input must be "
+                     "GRAYSCALE or BLACKANDWHITE PAM or PBM or PGM");
+    }
+    
+    if (baseImgType == TGA_MAP_TYPE) {
+        pm_message("%d colors found.", ncolors);
+        /* Make a hash table for fast color lookup. */
+        *chtP = pnm_computetupletablehash(pamP, *chvP, ncolors);
+    } else
+        *chtP = NULL;
+
+    *baseImgTypeP = baseImgType;
+    *withAlphaP   = withAlpha;
+    *ncolorsP     = ncolors;
+}
+
+
+
+static void
+computeTgaHeader(struct pam *          const pamP,
+                 enum TGAbaseImageType const baseImgType,
+                 bool                  const withAlpha,
+                 bool                  const rle, 
+                 int                   const ncolors,
+                 unsigned char         const orgBit,
+                 const char *          const id,
+                 struct ImageHeader *  const tgaHeaderP) {
+
+    if (rle) {
+        switch (baseImgType ) {
+        case TGA_MONO_TYPE: tgaHeaderP->ImgType = TGA_RLEMono;          break;
+        case TGA_MAP_TYPE:  tgaHeaderP->ImgType = TGA_RLEMap;           break;
+        case TGA_RGB_TYPE:  tgaHeaderP->ImgType = TGA_RLERGB;           break;
+        }
+    } else {
+        switch(baseImgType) {
+        case TGA_MONO_TYPE: tgaHeaderP->ImgType = TGA_Mono;          break;
+        case TGA_MAP_TYPE:  tgaHeaderP->ImgType = TGA_Map;           break;
+        case TGA_RGB_TYPE:  tgaHeaderP->ImgType = TGA_RGB;           break;
+        }
+    }
+    
+    if (id) {
+        tgaHeaderP->IdLength = strlen(id);
+        tgaHeaderP->Id = strdup(id);
+    } else
+        tgaHeaderP->IdLength = 0;
+    tgaHeaderP->Index_lo = 0;
+    tgaHeaderP->Index_hi = 0;
+    if (baseImgType == TGA_MAP_TYPE) {
+        tgaHeaderP->CoMapType = 1;
+        tgaHeaderP->Length_lo = ncolors % 256;
+        tgaHeaderP->Length_hi = ncolors / 256;
+        tgaHeaderP->CoSize = 8 * pamP->depth;
+    } else {
+        tgaHeaderP->CoMapType = 0;
+        tgaHeaderP->Length_lo = 0;
+        tgaHeaderP->Length_hi = 0;
+        tgaHeaderP->CoSize = 0;
+    }
+    switch (baseImgType) {
+    case TGA_MAP_TYPE:
+        tgaHeaderP->PixelSize = 8;
+        break;
+    case TGA_RGB_TYPE:
+        tgaHeaderP->PixelSize = 8 * MAX((withAlpha ? 4: 3), pamP->depth);
+        break;
+    case TGA_MONO_TYPE:
+        tgaHeaderP->PixelSize = 8;
+    }
+    tgaHeaderP->X_org_lo = tgaHeaderP->X_org_hi = 0;
+    tgaHeaderP->Y_org_lo = tgaHeaderP->Y_org_hi = 0;
+    tgaHeaderP->Width_lo = pamP->width % 256;
+    tgaHeaderP->Width_hi = pamP->width / 256;
+    tgaHeaderP->Height_lo = pamP->height % 256;
+    tgaHeaderP->Height_hi = pamP->height / 256;
+    tgaHeaderP->AttBits = 0;
+    tgaHeaderP->Rsrvd = 0;
+    tgaHeaderP->IntrLve = 0;
+    tgaHeaderP->OrgBit = orgBit;
+}
+
+
+
+static void
+releaseTgaHeader(struct ImageHeader const tgaHeader) {
+
+    strfree(tgaHeader.Id);
+}
+
+
+
+static void 
+writeTgaRaster(struct pam *          const pamP,
+               tuple **              const tuples, 
+               tuplehash             const cht,
+               enum TGAbaseImageType const imgType,
+               bool                  const withAlpha,
+               bool                  const rle,
+               unsigned char         const orgBit) {
+
+    int* runlength;  /* malloc'ed */
+    int row;
+
+    if (rle)
+        MALLOCARRAY(runlength, pamP->width);
+
+    for (row = 0; row < pamP->height; ++row) {
+        int realrow;
+        realrow = (orgBit != 0) ? row : pamP->height - row - 1;
+        if (rle) {
+            int col;
+            computeRunlengths(pamP, tuples[realrow], runlength);
+            for (col = 0; col < pamP->width; ) {
+                if (runlength[col] > 0) {
+                    putchar(0x80 + runlength[col] - 1);
+                    putPixel(pamP, tuples[realrow][col], imgType, withAlpha,
+                             cht);
+                    col += runlength[col];
+                } else if (runlength[col] < 0) {
+                    int i;
+                    putchar(-runlength[col] - 1);
+                    for (i = 0; i < -runlength[col]; ++i)
+                        putPixel(pamP, tuples[realrow][col+i], 
+                                 imgType, withAlpha, cht);
+                    col += -runlength[col];
+                } else
+                    pm_error("Internal error: zero run length");
+            }
+        } else {
+            int col;
+            for (col = 0; col < pamP->width; ++col)
+                putPixel(pamP, tuples[realrow][col], imgType, withAlpha, cht);
+        }
+    }
+    if (rle)
+        free(runlength);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    tuple ** tuples;
+    struct pam pam;
+    int ncolors;
+    tupletable chv;
+    tuplehash cht;
+    struct ImageHeader tgaHeader;
+    enum TGAbaseImageType baseImgType;
+    bool withAlpha;
+    const char *outName;
+
+    pnm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    computeOutName(cmdline, &outName);
+
+    tuples = pnm_readpam(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
+    pm_close(ifP);
+
+    computeImageType_cht(&pam, cmdline, tuples, 
+                         &baseImgType, &withAlpha, &chv, &cht, &ncolors);
+
+    /* Do the Targa header */
+    computeTgaHeader(&pam, baseImgType, withAlpha, !cmdline.norle,
+                     ncolors, 0, outName, &tgaHeader);
+    writeTgaHeader(tgaHeader);
+    
+    if (baseImgType == TGA_MAP_TYPE) {
+        /* Write out the Targa colormap. */
+        int i;
+        for (i = 0; i < ncolors; ++i)
+            putMapEntry(&pam, chv[i]->tuple, tgaHeader.CoSize);
+    }
+
+    writeTgaRaster(&pam, tuples, cht, baseImgType, withAlpha, 
+                   !cmdline.norle, 0);
+
+    if (cht)
+        pnm_destroytuplehash(cht);
+    if (chv)
+        pnm_freetupletable(&pam, chv);
+
+    releaseTgaHeader(tgaHeader);
+    strfree(outName);
+    pnm_freepamarray(tuples, &pam);
+
+    return 0;
+}
diff --git a/converter/other/pamtotiff.c b/converter/other/pamtotiff.c
new file mode 100644
index 00000000..4174a431
--- /dev/null
+++ b/converter/other/pamtotiff.c
@@ -0,0 +1,1110 @@
+/*
+** pnmtotiff.c - converts a PNM image to a TIFF (Tagged Image File) image
+**
+** Derived by Jef Poskanzer from ras2tif.c, which is:
+**
+** Copyright (c) 1990 by Sun Microsystems, Inc.
+**
+** Author: Patrick J. Naughton
+** naughton@wind.sun.com
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted,
+** provided that the above copyright notice appear in all copies and that
+** both that copyright notice and this permission notice appear in
+** supporting documentation.
+**
+** This file is provided AS IS with no warranties of any kind.  The author
+** shall have no liability with respect to the infringement of copyrights,
+** trade secrets or any patents by this file or any part thereof.  In no
+** event will the author be liable for any lost revenue or profits or
+** other special, indirect and consequential damages.
+*/
+
+#define _XOPEN_SOURCE    /* Make sure stdio.h contains fileno() */
+#define _BSD_SOURCE      /* Make sure string.h contains strcasecmp() */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#ifdef VMS
+#ifdef SYSV
+#undef SYSV
+#endif
+#include <tiffioP.h>
+#endif
+/* tiffio.h has a weird problem on AIX.  tiffio.h wrongly typedefs
+   "int32".  That's wrong because such a name is likely to be used in
+   other parts of the program that includes tiffio.h.  And in fact, on
+   AIX, <inttypes.h> defines it in some cases.  One case in which <inttypes.h>
+   does _not_ define int32 is where _XOPEN_SOURCE is declared.  So we always
+   do that when including tiffio.h.  06.01.05
+*/
+#include <tiffio.h>
+
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "pam.h"
+#include "pammap.h"
+#include "pm_tiff.h"
+
+#define MAXCOLORS 256
+
+/* 2001.09.03: Old tiff.h doesn't contain these values: */
+#ifndef COMPRESSION_ADOBE_DEFLATE
+#define COMPRESSION_ADOBE_DEFLATE 8
+#endif
+
+struct sizeset {
+    bool b1, b2, b4, b8;
+};
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec;  /* Filespecs of input files */
+    int compression;
+        /* COMPRESSION Tiff tag value, that corresponds to the compression
+           option the user specified, or -1 if he didn't specify any.
+           The -tag option doesn't count.
+        */
+    int fillorder;     /* FILLORDER Tiff tag value */
+    int g3options;     /* G3OPTIONS Tiff tag value or 0 for none */
+    int predictor;     /* PREDICTOR Tiff tag value or -1 for none */
+    int rowsperstrip;  /* -1 = unspecified */
+    unsigned int minisblack;   /* logical: User wants MINISBLACK photometric */
+    unsigned int miniswhite;   /* logical: User wants MINISWHITE photometric */
+    unsigned int truecolor;    /* logical: User wants truecolor, not cmap */
+    unsigned int color;        /* logical: assume not grayscale */
+    float xresolution; /* XRESOLUTION Tiff tag value or -1 for none */
+    float yresolution; /* YRESOLUTION Tiff tag value or -1 for none */
+    int resolutionUnit;  /* RESOLUTIONUNIT Tiff tag value */
+    struct sizeset indexsizeAllowed;
+    /* Which bit widths are allowable in a raster of palette indices */
+    unsigned int verbose;
+    unsigned int append;
+    float resolution;  /* X and Y resolution */
+    struct optNameValue * taglist;
+};
+
+
+
+static void
+validateTagList(struct optNameValue const taglist[]) {
+
+    unsigned int i;
+    for (i = 0; taglist[i].name; ++i) {
+        const char * const tagName = taglist[i].name;
+        const tagDefinition * tagDefP = tagDefFind(tagName);
+        
+        if (!tagDefP)
+            pm_error("Unknown tag name '%s'", tagName);
+        else {
+            /* We don't allow the user to set directly any tag that we
+               control explicitly.  These are normally tags that have to be
+               consistent with other stuff we put in the image.  But in some
+               cases, they're just tags we had special options for before
+               -tag existed.  The latter should probably be converted
+               eventually to prefer -tag.
+            */
+            switch (tagDefP->tagnum) {
+            case TIFFTAG_IMAGEWIDTH:
+            case TIFFTAG_IMAGELENGTH:
+            case TIFFTAG_BITSPERSAMPLE:
+            case TIFFTAG_COMPRESSION:
+            case TIFFTAG_GROUP3OPTIONS:
+            case TIFFTAG_PREDICTOR:
+            case TIFFTAG_PHOTOMETRIC:
+            case TIFFTAG_FILLORDER:
+            case TIFFTAG_SAMPLESPERPIXEL:
+            case TIFFTAG_ROWSPERSTRIP:
+            case TIFFTAG_PLANARCONFIG:
+            case TIFFTAG_COLORMAP:
+            case TIFFTAG_RESOLUTIONUNIT:
+            case TIFFTAG_XRESOLUTION:
+            case TIFFTAG_YRESOLUTION:
+                pm_error("You cannot specify a '%s' tag with -tag.  "
+                         "Pamtotiff controls that internally or via other "
+                         "options.", tagName);
+            }
+        }
+    }
+}
+
+
+
+static void
+parseCommandLine(int                        argc,
+                 char **              const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+
+    optStruct3 opt;
+
+    unsigned int none, packbits, lzw, g3, g4, msb2lsb, lsb2msb, opt_2d, fill;
+    unsigned int flate, adobeflate;
+    char * indexbits;
+    char * resolutionUnit;
+
+    unsigned int predictorSpec, rowsperstripSpec, xresolutionSpec,
+        yresolutionSpec, indexbitsSpec, resolutionUnitSpec, tagSpec;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "verbose",      OPT_FLAG,   NULL, &cmdlineP->verbose,       0);
+    OPTENT3(0, "append",       OPT_FLAG,   NULL, &cmdlineP->append,        0);
+    OPTENT3(0, "none",         OPT_FLAG,   NULL, &none,                    0);
+    OPTENT3(0, "packbits",     OPT_FLAG,   NULL, &packbits,                0);
+    OPTENT3(0, "lzw",          OPT_FLAG,   NULL, &lzw,                     0);
+    OPTENT3(0, "g3",           OPT_FLAG,   NULL, &g3,                      0);
+    OPTENT3(0, "g4",           OPT_FLAG,   NULL, &g4,                      0);
+    OPTENT3(0, "flate",        OPT_FLAG,   NULL, &flate,                   0);
+    OPTENT3(0, "adobeflate",   OPT_FLAG,   NULL, &adobeflate,              0);
+    OPTENT3(0, "msb2lsb",      OPT_FLAG,   NULL, &msb2lsb,                 0);
+    OPTENT3(0, "lsb2msb",      OPT_FLAG,   NULL, &lsb2msb,                 0);
+    OPTENT3(0, "2d",           OPT_FLAG,   NULL, &opt_2d,                  0);
+    OPTENT3(0, "fill",         OPT_FLAG,   NULL, &fill,                    0);
+    OPTENT3(0, "minisblack",   OPT_FLAG,   NULL, &cmdlineP->minisblack,    0);
+    OPTENT3(0, "mb",           OPT_FLAG,   NULL, &cmdlineP->minisblack,    0);
+    OPTENT3(0, "miniswhite",   OPT_FLAG,   NULL, &cmdlineP->miniswhite,    0);
+    OPTENT3(0, "mw",           OPT_FLAG,   NULL, &cmdlineP->miniswhite,    0);
+    OPTENT3(0, "truecolor",    OPT_FLAG,   NULL, &cmdlineP->truecolor,     0);
+    OPTENT3(0, "color",        OPT_FLAG,   NULL, &cmdlineP->color,         0);
+    OPTENT3(0, "predictor",    OPT_UINT,   &cmdlineP->predictor,    
+            &predictorSpec,    0);
+    OPTENT3(0, "rowsperstrip", OPT_UINT,   &cmdlineP->rowsperstrip, 
+            &rowsperstripSpec, 0);
+    OPTENT3(0, "xresolution",  OPT_FLOAT,  &cmdlineP->xresolution,  
+            &xresolutionSpec,  0);
+    OPTENT3(0, "yresolution",  OPT_FLOAT,  &cmdlineP->yresolution,  
+            &yresolutionSpec,  0);
+    OPTENT3(0, "resolutionunit", OPT_STRING, &resolutionUnit,
+            &resolutionUnitSpec,    0);
+    OPTENT3(0, "indexbits",    OPT_STRING,   &indexbits, 
+            &indexbitsSpec,    0);
+    OPTENT3(0, "tag",          OPT_NAMELIST, &cmdlineP->taglist, &tagSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (none + packbits + lzw + g3 + g4 + flate + adobeflate > 1)
+        pm_error("You specified more than one compression option.  "
+                 "Only one of -none, -packbits, -lze, -g3, and -g4 "
+                 "is allowed.");
+    
+    if (none)
+        cmdlineP->compression = COMPRESSION_NONE;
+    else if (packbits)
+        cmdlineP->compression = COMPRESSION_PACKBITS;
+    else if (lzw)
+        cmdlineP->compression = COMPRESSION_LZW;
+    else if (g3)
+        cmdlineP->compression = COMPRESSION_CCITTFAX3;
+    else if (g4)
+        cmdlineP->compression = COMPRESSION_CCITTFAX4;
+    else if (adobeflate)
+        cmdlineP->compression = COMPRESSION_ADOBE_DEFLATE;
+    else if (flate)
+        cmdlineP->compression = COMPRESSION_DEFLATE;
+    else
+        cmdlineP->compression = COMPRESSION_NONE;
+    
+    if (msb2lsb + lsb2msb > 1)
+        pm_error("You specified both -msb2lsb and -lsb2msb.  "
+                 "These are conflicting options.");
+
+    if (msb2lsb)
+        cmdlineP->fillorder = FILLORDER_MSB2LSB;
+    else if (lsb2msb)
+        cmdlineP->fillorder = FILLORDER_LSB2MSB;
+    else 
+        cmdlineP->fillorder = FILLORDER_MSB2LSB;
+    
+
+    if (cmdlineP->miniswhite && cmdlineP->minisblack)
+        pm_error("You cannot specify both -miniswhite and -minisblack");
+
+    cmdlineP->g3options = 0;  /* initial value */
+    if (opt_2d)
+        cmdlineP->g3options |= GROUP3OPT_2DENCODING;
+    if (fill)
+        cmdlineP->g3options |= GROUP3OPT_FILLBITS;
+
+    if (predictorSpec) {
+        if (cmdlineP->predictor != 1 && cmdlineP->predictor != 2)
+            pm_error("-predictor may be only 1 or 2.  You specified %d.", 
+                     cmdlineP->predictor);
+    } else
+        cmdlineP->predictor = -1;
+
+    if (rowsperstripSpec) {
+        if (cmdlineP->rowsperstrip < 1)
+            pm_error("-rowsperstrip must be positive.  You specified %d.",
+                     cmdlineP->rowsperstrip);
+    } else
+        cmdlineP->rowsperstrip = -1;
+
+    if (xresolutionSpec) {
+        if (cmdlineP->xresolution < 1)
+            pm_error("-xresolution must be positive.  You specified %f.",
+                     cmdlineP->xresolution);
+    } else
+        cmdlineP->xresolution = -1;
+
+    if (yresolutionSpec) {
+        if (cmdlineP->yresolution < 1)
+            pm_error("-yresolution must be positive.  You specified %f.",
+                     cmdlineP->yresolution);
+    } else
+        cmdlineP->yresolution = -1;
+
+    if (resolutionUnitSpec) {
+        if (streq(resolutionUnit, "inch"))
+            cmdlineP->resolutionUnit = RESUNIT_INCH;
+        else if (streq(resolutionUnit, "in"))
+            cmdlineP->resolutionUnit = RESUNIT_INCH;
+        else if (streq(resolutionUnit, "centimeter"))
+            cmdlineP->resolutionUnit = RESUNIT_CENTIMETER;
+        else if (streq(resolutionUnit, "cm"))
+            cmdlineP->resolutionUnit = RESUNIT_CENTIMETER;
+        else if (streq(resolutionUnit, "none"))
+            cmdlineP->resolutionUnit = RESUNIT_NONE;
+        else if (streq(resolutionUnit, "no"))
+            cmdlineP->resolutionUnit = RESUNIT_NONE;
+        else
+            pm_error("The only acceptable values for -resolutionunit are "
+                     "inch, centimeter, none, in, cm, and no.  "
+                     "You specified '%s'.", resolutionUnit);
+    } else
+        cmdlineP->resolutionUnit = RESUNIT_INCH;
+
+    if (indexbitsSpec) {
+        if (strstr(indexbits, "1"))
+            cmdlineP->indexsizeAllowed.b1 = TRUE;
+        else
+            cmdlineP->indexsizeAllowed.b1 = FALSE;
+        if (strstr(indexbits, "2"))
+            cmdlineP->indexsizeAllowed.b2 = TRUE;
+        else
+            cmdlineP->indexsizeAllowed.b2 = FALSE;
+        if (strstr(indexbits, "4"))
+            cmdlineP->indexsizeAllowed.b4 = TRUE;
+        else
+            cmdlineP->indexsizeAllowed.b4 = FALSE;
+        if (strstr(indexbits, "8"))
+            cmdlineP->indexsizeAllowed.b8 = TRUE;
+        else
+            cmdlineP->indexsizeAllowed.b8 = FALSE;
+    } else {
+        cmdlineP->indexsizeAllowed.b1 = FALSE;
+        cmdlineP->indexsizeAllowed.b2 = FALSE;
+        cmdlineP->indexsizeAllowed.b4 = FALSE;
+        cmdlineP->indexsizeAllowed.b8 = TRUE;
+    }
+
+    if (tagSpec)
+        validateTagList(cmdlineP->taglist);
+    else {
+        MALLOCARRAY_NOFAIL(cmdlineP->taglist, 1);
+        cmdlineP->taglist[0].name = NULL;
+        cmdlineP->taglist[0].value = NULL;
+    }
+
+    if (argc-1 == 0) 
+        cmdlineP->input_filespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->input_filespec = argv[1];
+}
+
+
+
+static void
+putSample(sample           const s,
+          sample           const maxval,
+          sample           const tiff_maxval,
+          unsigned int     const bitspersample,
+          unsigned char ** const tPP) {
+
+    xelval s2;
+
+    s2 = s;
+    if (maxval != tiff_maxval)
+        s2 = s * tiff_maxval / maxval;
+    if (bitspersample > 8) {
+        *((unsigned short *)(*tPP)) = s;
+        (*tPP) += sizeof(short);
+    } else
+        *(*tPP)++ = s & 0xff;
+}
+
+
+
+
+/* Note: PUTSAMPLE doesn't work if bitspersample is 1-4. */
+
+#define PUTSAMPLE putSample(s, maxval, tiff_maxval, bitspersample, &tP);
+
+
+
+static void
+fillRowOfSubBytePixels(struct pam *    const pamP,
+                       const tuple *   const tuplerow,
+                       unsigned char * const buf,
+                       unsigned short  const tiff_maxval,
+                       unsigned short  const photometric,
+                       int             const fillorder,
+                       int             const bitspersample,
+                       tuplehash       const cht) {
+/*----------------------------------------------------------------------------
+  This subroutine deals with cases where multiple pixels are packed into
+  a single byte of the Tiff output image, i.e. bits per pixel < 8.
+-----------------------------------------------------------------------------*/
+    int col;
+    int bitshift;
+        /* The number of bits we have to shift a pixel value left to line
+           it up with where the current pixel goes in the current byte of
+           the output buffer.  
+        */
+    int const firstbitshift = 
+        (fillorder == FILLORDER_MSB2LSB) ? 8 - bitspersample : 0;
+        /* The value of 'bitshift' for the first pixel into a
+           byte of the output buffer.  (MSB2LSB is normal).
+        */
+    sample s;
+        /* The value of the current sample */
+    unsigned char* tP;
+        /* The address of the byte in the output buffer in which
+           the current pixel goes.
+        */
+    unsigned char byte;
+        /* The under-construction value of the byte pointed to by
+           tP, above.
+        */
+                
+    bitshift = firstbitshift;
+    byte = 0;
+    for (col = 0, tP = buf; col < pamP->width; ++col) {
+        if (cht == NULL) {
+            /* It's grayscale */
+            s = tuplerow[col][0];
+            if (pamP->maxval != tiff_maxval )
+                s = (long) s * tiff_maxval / pamP->maxval;
+ 
+            if (photometric == PHOTOMETRIC_MINISWHITE)
+                s = tiff_maxval - s;
+        } else {
+            /* It's a colormapped raster */
+            int si;
+            int found;
+            pnm_lookuptuple(pamP, cht, tuplerow[col], &found, &si);
+            if (!found)
+                pm_error("INTERNAL ERROR.  We made a color map, and a "
+                         "color map we need is not in it!.  "
+                         "col=%d  r=%lu g=%lu b=%lu",
+                         col, tuplerow[col][0], tuplerow[col][1],
+                         tuplerow[col][2]);
+            s = (unsigned char) si;
+        }
+        byte |= s << bitshift;
+        if (fillorder == FILLORDER_MSB2LSB)
+            bitshift -= bitspersample;  /* normal case */
+        else
+            bitshift += bitspersample;
+        if (bitshift < 0 || bitshift > 7) {
+            *tP++ = byte;
+            bitshift = firstbitshift;
+            byte = 0;
+        }
+    }
+    if (bitshift != firstbitshift)
+        *tP++ = byte;
+}
+
+
+
+static void
+fillRowOfWholeBytePixels(struct pam *    const pamP,
+                         tuple *         const tuplerow,
+                         unsigned char * const buf,
+                         unsigned short  const photometric,
+                         unsigned short  const tiffMaxval,
+                         unsigned int    const bitsPerSample) {
+
+    unsigned int col;
+    unsigned char * tP;
+    unsigned int planes;
+    
+    if (photometric == PHOTOMETRIC_RGB)
+        planes = pamP->depth;
+    else
+        /* Write only the first plane from the PAM, even if there
+           are more.
+        */
+        planes = 1;
+
+    for (col = 0, tP = buf; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < planes; ++plane) {
+            putSample(tuplerow[col][plane], pamP->maxval,
+                      tiffMaxval, bitsPerSample, &tP);
+            /* Advances tP */
+        }
+    }
+} 
+
+
+
+static void
+writeScanLines(struct pam *   const pamP,
+               TIFF *         const tif, 
+               tuplehash      const cht,
+               unsigned short const tiffMaxval,
+               unsigned short const bitspersample, 
+               unsigned short const photometric,
+               int            const bytesperrow, 
+               int            const fillorder) {
+/*----------------------------------------------------------------------------
+   Write out the raster for the input image described by 'pamP', whose
+   file is positioned to the raster of the image.
+-----------------------------------------------------------------------------*/
+    tuple * tuplerow;  /* malloc'ed */
+    unsigned char * buf; /* malloc'ed */
+    int row;
+
+    /* The man page for TIFFWriteScanLine doesn't tell the format of
+       it's 'buf' parameter, but here it is: Its format depends on the
+       bits per pixel of the TIFF image.  If it's 16, 'buf' is an
+       array of short (16 bit) integers, one per raster column.  If
+       it's 8, 'buf' is an array of characters (8 bit integers), one
+       per image column.  If it's less than 8, it's an array of characters,
+       each of which represents 1-8 raster columns, packed
+       into it in the order specified by the TIFF image's fill order,
+       with don't-care bits on the right such that each byte contains only
+       whole pixels.
+
+       In all cases, the array elements are in order left to right going
+       from low array indices to high array indices.
+    */
+    MALLOCARRAY(buf, bytesperrow);
+
+    if (buf == NULL)
+        pm_error("can't allocate memory for row buffer");
+
+    tuplerow = pnm_allocpamrow(pamP);
+    
+    for (row = 0; row < pamP->height; ++row) {
+        int col;
+
+        pnm_readpamrow(pamP, tuplerow);
+
+        if (cht == NULL) {
+            /* It's a direct, non-colormapped raster */
+
+            if (bitspersample == 8 || bitspersample == 16)
+                fillRowOfWholeBytePixels(pamP, tuplerow, buf, photometric,
+                                         tiffMaxval, bitspersample);
+            else
+                fillRowOfSubBytePixels(pamP, tuplerow, buf,
+                                       tiffMaxval, photometric,
+                                       fillorder, bitspersample, NULL);
+        } else {
+            /* It's a colormapped raster */
+            if (bitspersample == 8) {
+                for (col = 0; col < pamP->width; ++col) {
+                    int si;
+                    int found;
+                    
+                    pnm_lookuptuple(pamP, cht, tuplerow[col], &found, &si);
+                    
+                    if (!found)
+                        pm_error("INTERNAL ERROR.  We made a color map, and a "
+                                 "color map we need is not in it!  "
+                                 "col=%d  r=%lu g=%lu b=%lu",
+                                 col, tuplerow[col][0], tuplerow[col][1],
+                                 tuplerow[col][2]);
+                    buf[col] = (unsigned char) si;
+                }
+            } else {
+                fillRowOfSubBytePixels(pamP, tuplerow, buf, 0, 0, fillorder,
+                                       bitspersample, cht);
+            }
+        }
+        if (TIFFWriteScanline(tif, buf, row, 0) < 0)
+            pm_error("failed a scanline write on row %d", row);
+    }
+    pnm_freepamrow(tuplerow);
+    free(buf);
+}
+
+
+
+static void
+analyzeColorsInRgbInput(struct pam *        const pamP,
+                        struct cmdlineInfo  const cmdline,
+                        int                 const maxcolors, 
+                        tupletable *        const chvP, 
+                        unsigned int *      const colorsP, 
+                        bool *              const grayscaleP) {
+/*----------------------------------------------------------------------------
+   Same as analyzeColors(), except assuming input image has R/G/B tuples.
+-----------------------------------------------------------------------------*/
+    if (cmdline.color && cmdline.truecolor) {
+        *chvP = NULL;
+        *grayscaleP = FALSE;
+    } else {
+        tupletable chv;
+        bool grayscale;
+
+        pm_message("computing colormap...");
+        chv = pnm_computetuplefreqtable2(pamP, NULL, maxcolors,
+                                         pamP->maxval,
+                                         colorsP);
+        if (chv == NULL) {
+            grayscale = FALSE;
+        } else {
+            unsigned int i;
+            pm_message("%u color%s found", 
+                       *colorsP, *colorsP == 1 ? "" : "s");
+            grayscale = TRUE;  /* initial assumption */
+            for (i = 0; i < *colorsP && grayscale; ++i) {
+                if (!pnm_rgbtupleisgray(chv[i]->tuple))
+                    grayscale = FALSE;
+            }
+        }
+        *grayscaleP = grayscale;
+
+        if (grayscale || cmdline.truecolor) {
+            *chvP = NULL;
+            if (chv)
+                pnm_freetupletable(pamP, chv);
+        } else {
+            /* He wants a colormap.  But can we give it to him? */
+            if (chv)
+                *chvP = chv;
+            else {
+                pm_message("Too many colors - "
+                           "proceeding to write a 24-bit RGB file.");
+                pm_message("If you want an 8-bit palette file, "
+                           "try doing a 'pnmquant %d'.",
+                           maxcolors);
+                *chvP = NULL;
+            }
+        }
+    }
+}
+
+
+
+static void
+analyzeColors(struct pam *        const pamP,
+              struct cmdlineInfo  const cmdline,
+              int                 const maxcolors, 
+              tupletable *        const chvP, 
+              unsigned int *      const colorsP, 
+              bool *              const grayscaleP) {
+/*----------------------------------------------------------------------------
+   Analyze the colors in the input image described by 'pamP', whose file
+   is positioned to the raster. 
+
+   If the colors, combined with command line options 'cmdline', indicate
+   a colormapped TIFF should be generated, return as *chvP the address
+   of a color map (in newly malloc'ed space).  If a colormapped TIFF is
+   not indicated, return *chvP == NULL.
+
+   Return *grayscaleP == true iff the image should be stored as a grayscale
+   image (which means the image is monochromatic and the user doesn't
+   insist on color format).
+
+   Leave the file position undefined.
+-----------------------------------------------------------------------------*/
+    if (pamP->depth >= 3)
+        /* Assume it's a color-capable input image
+           (tuple type RGB or RGB_ALPHA)
+        */
+        analyzeColorsInRgbInput(pamP, cmdline, maxcolors,
+                                chvP, colorsP, grayscaleP);
+    else {
+        *chvP = NULL;
+        *grayscaleP = TRUE;
+    }
+}
+
+
+
+static void
+computeRasterParm(struct pam *     const pamP,
+                  tupletable       const chv, 
+                  int              const colors, 
+                  bool             const grayscale,
+                  int              const compression,
+                  bool             const minisblack,
+                  bool             const miniswhite,
+                  struct sizeset   const indexsizeAllowed,
+                  unsigned short * const samplesperpixelP,
+                  unsigned short * const bitspersampleP,
+                  unsigned short * const photometricP,
+                  int            * const bytesperrowP) {
+/*----------------------------------------------------------------------------
+   Compute the parameters of the raster portion of the TIFF image.
+
+   'minisblack' and 'miniswhite' mean the user requests the corresponding
+   photometric.  Both FALSE means user has no explicit requirement.
+-----------------------------------------------------------------------------*/
+    unsigned short defaultPhotometric;
+    /* The photometric we use if the user specified no preference */
+
+    /* We determine the bits per sample by the maxval, except that if
+       it would be more than 8, we just use 16.  I don't know if bits
+       per sample between 8 and 16 are legal, but they aren't very
+       nice in any case.  If users want them, we should provide an
+       option.  It is not clear why we don't use bits per pixel < 8
+       for RGB images.  Note that code to handle maxvals <= 255 was
+       written long before maxval > 255 was possible and there are
+       backward compatibility requirements.  
+    */
+
+    if (pamP->depth == 1 && pamP->maxval == 1) {
+        *samplesperpixelP = 1;
+        *bitspersampleP = 1;
+        if ((compression == COMPRESSION_CCITTFAX3) ||
+            (compression == COMPRESSION_CCITTFAX4))
+            defaultPhotometric = PHOTOMETRIC_MINISWHITE;
+        else
+            defaultPhotometric = PHOTOMETRIC_MINISBLACK;
+    } else {
+        if (chv) {
+            *samplesperpixelP = 1;  /* Pixel is just the one index value */
+            *bitspersampleP = 
+                colors <=   2 && indexsizeAllowed.b1 ? 1 :
+                colors <=   4 && indexsizeAllowed.b2 ? 2 :
+                colors <=  16 && indexsizeAllowed.b4 ? 4 :
+                colors <= 256 && indexsizeAllowed.b8 ? 8 :
+                0;
+            if (*bitspersampleP == 0)
+                pm_error("Your -indexbits option is insufficient for the "
+                         "%d colors in this image.", colors);
+
+            defaultPhotometric = PHOTOMETRIC_PALETTE;
+        } else {
+            if (grayscale) {
+                /* We'll write just the first plane regardless of how many are
+                   in the input PAM.
+                */
+                *samplesperpixelP = 1;
+                *bitspersampleP =
+                    pamP->maxval > 255 ? 16 : pm_maxvaltobits(pamP->maxval);
+                defaultPhotometric = PHOTOMETRIC_MINISBLACK;
+            } else {
+                *samplesperpixelP = pamP->depth;
+                *bitspersampleP = pamP->maxval > 255 ? 16 : 8;
+                defaultPhotometric = PHOTOMETRIC_RGB;
+            }
+        }
+    }
+
+    if (miniswhite)
+        *photometricP = PHOTOMETRIC_MINISWHITE;
+    else if (minisblack)
+        *photometricP = PHOTOMETRIC_MINISBLACK;
+    else
+        *photometricP = defaultPhotometric;
+
+    {
+        unsigned int const samplesPerRow = pamP->width * *samplesperpixelP;
+
+        if (*bitspersampleP < 8) {
+            int samplesperbyte;
+            samplesperbyte = 8 / *bitspersampleP;
+            *bytesperrowP =
+                (samplesPerRow + samplesperbyte-1) / samplesperbyte;
+        } else {
+            if (UINT_MAX / *bitspersampleP < samplesPerRow)
+                pm_error("Too many bytes per row to compute");
+            *bytesperrowP = (samplesPerRow * *bitspersampleP) / 8;
+        }
+    }
+}
+
+
+
+static void
+validateSeekableOutputFile(int          const ofd,
+                           const char * const outFileName) {
+/*----------------------------------------------------------------------------
+   Validate that the file attached to file descriptor 'ofd' is capable
+   of seeking.  If not, fail the program.
+
+   This is useful because the TIFF library requires seekable output and
+   fails with an unhelpful error message about a file I/O error if it is
+   not.  We, on the other hand, give a helpful error message.
+
+   We leave the file positioned to the beginning.
+-----------------------------------------------------------------------------*/
+    int rc;
+
+    /* We'd like to get the current position and leave the file positioned
+       where we found it.  But that entails the mess with some systems
+       having 32 bit file offsets and some having 64 bit file offsets.
+    */
+
+    /* Some files can seek ahead, but not back.  So we test for the
+       more difficult backward seek.
+    */
+    lseek(ofd, 1, SEEK_SET);
+    rc = lseek(ofd, 0, SEEK_SET);
+            
+    if (rc < 0)
+        pm_error("Output file (%s) is not seekable.  lseek() returned "
+                 "errno %d (%s).  "
+                 "The TIFF library can write only to "
+                 "a seekable file.", 
+                 outFileName, errno, strerror(errno));
+}
+
+
+
+static void
+createTiffGenerator(int          const ofd, 
+                    const char * const outFileName,
+                    bool         const append,
+                    TIFF **      const tifPP) {
+
+    const char * option;
+
+    /* Before 10.12 (November 2002), we set O_NONBLOCK here:
+
+       fcntl( 1, F_SETFL, O_NONBLOCK ) ; 
+   
+       I have no idea why.  The comment attached said, 
+
+         acooke dec99 - otherwise blocks on read inside 
+         next line (Linux i386) 
+    */
+
+    validateSeekableOutputFile(ofd, outFileName);
+
+    if (append)
+        option = "a";
+    else
+        option = "w";
+
+    *tifPP = TIFFFdOpen(ofd, outFileName, option);
+    if (*tifPP == NULL)
+        pm_error("error opening standard output as TIFF file.  "
+                 "TIFFFdOpen() failed.");
+}
+
+
+
+static void
+destroyTiffGenerator(TIFF * const tifP) {
+
+    TIFFFlushData(tifP);
+    TIFFClose(tifP);
+}
+
+
+
+static void
+createTiffColorMap(struct pam *       const pamP,
+                   unsigned int       const bitspersample,
+                   tupletable         const chv,
+                   unsigned int       const colors,
+                   unsigned short *** const tiffColorMapP) {
+
+    unsigned int const colorMapSize = 1 << bitspersample;
+    unsigned short ** tiffColorMap;
+    unsigned int plane;
+    unsigned int i;
+    
+    MALLOCARRAY_NOFAIL(tiffColorMap, pamP->depth);
+    for (plane = 0; plane < pamP->depth; ++plane)
+        MALLOCARRAY_NOFAIL(tiffColorMap[plane], colorMapSize);
+    
+    for (i = 0; i < colorMapSize; ++i) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane) {
+            if (i < colors)
+                tiffColorMap[plane][i] =
+                    chv[i]->tuple[plane] * 65535L / pamP->maxval;
+            else
+                tiffColorMap[plane][i] = 0;
+        }
+    }
+    *tiffColorMapP = tiffColorMap;
+}
+        
+
+
+static void
+destroyTiffColorMap(struct pam *      const pamP,
+                    unsigned short ** const tiffColorMap) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane)
+        free(tiffColorMap[plane]);
+
+    free(tiffColorMap);
+}
+
+
+
+static void
+setTagListFields(const struct optNameValue * const taglist,
+                 TIFF *                      const tifP) {
+
+    unsigned int i;
+
+    for (i = 0; taglist[i].name; ++i) {
+        const tagDefinition * const tagDefP = tagDefFind(taglist[i].name);
+        
+        if (tagDefP->put)
+            tagDefP->put(tifP, tagDefP->tagnum, taglist[i].value,
+                         tagDefP->choices);
+    }
+}
+
+
+
+static void
+setTiffFields(TIFF *              const tifP,
+              struct cmdlineInfo  const cmdline,
+              struct pam *        const pamP,
+              unsigned short      const bitspersample,
+              unsigned short      const photometric,
+              unsigned short      const samplesperpixel,
+              unsigned short **   const tiffColorMap,
+              const char *        const inputFileDescription,
+              struct optNameValue const taglist[]) {
+
+    /* TODO: It would be cleaner to set all tags in a tag list and
+       then call setTagListFields() on that list.  That way,
+       TIFFSetField() is in only one place and all tags get set the
+       same way and we don't have this icky setting of default and
+       then resetting with user choice (e.g. DOCUMENTNAME).
+    */
+
+    /* Set TIFF parameters. */
+    TIFFSetField(tifP, TIFFTAG_IMAGEWIDTH,    pamP->width);
+    TIFFSetField(tifP, TIFFTAG_IMAGELENGTH,   pamP->height);
+    TIFFSetField(tifP, TIFFTAG_BITSPERSAMPLE, bitspersample);
+    if (cmdline.compression != -1)
+        TIFFSetField(tifP, TIFFTAG_COMPRESSION, cmdline.compression);
+    if (cmdline.compression == COMPRESSION_CCITTFAX3 && cmdline.g3options != 0)
+        TIFFSetField( tifP, TIFFTAG_GROUP3OPTIONS, cmdline.g3options);
+    if (cmdline.compression == COMPRESSION_LZW && cmdline.predictor != -1)
+        TIFFSetField( tifP, TIFFTAG_PREDICTOR, cmdline.predictor);
+    TIFFSetField(tifP, TIFFTAG_PHOTOMETRIC, photometric);
+    if (cmdline.fillorder != -1)
+        TIFFSetField(tifP, TIFFTAG_FILLORDER, cmdline.fillorder);
+    TIFFSetField(tifP, TIFFTAG_SAMPLESPERPIXEL, samplesperpixel);
+    if (cmdline.rowsperstrip != -1)
+        TIFFSetField(tifP, TIFFTAG_ROWSPERSTRIP, cmdline.rowsperstrip);
+    else
+        TIFFSetField(tifP, TIFFTAG_ROWSPERSTRIP,
+                     TIFFDefaultStripSize(tifP, 0));
+    if (cmdline.xresolution != -1 ||
+        cmdline.yresolution != -1 ||
+        cmdline.resolutionUnit != -1) {
+        TIFFSetField(tifP, TIFFTAG_RESOLUTIONUNIT,
+                     cmdline.resolutionUnit != -1 ?
+                     cmdline.resolutionUnit : RESUNIT_NONE);
+    }
+    if (cmdline.xresolution > 0)
+        TIFFSetField(tifP, TIFFTAG_XRESOLUTION, cmdline.xresolution);
+    if ( cmdline.yresolution > 0)
+        TIFFSetField(tifP, TIFFTAG_YRESOLUTION, cmdline.yresolution);
+    TIFFSetField(tifP, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
+
+    if (tiffColorMap)
+        TIFFSetField(tifP, TIFFTAG_COLORMAP,
+                     tiffColorMap[PAM_RED_PLANE],
+                     tiffColorMap[PAM_GRN_PLANE],
+                     tiffColorMap[PAM_BLU_PLANE]);
+
+
+    TIFFSetField(tifP, TIFFTAG_DOCUMENTNAME,     inputFileDescription);
+    TIFFSetField(tifP, TIFFTAG_IMAGEDESCRIPTION, "converted PNM file");
+ 
+    /* Some of taglist[] overrides defaults we set above.  But taglist[]
+       is defined not to specify any tag types that are not purely user
+       choice.
+    */
+    setTagListFields(taglist, tifP);
+}
+
+
+
+static void
+convertImage(FILE *             const ifP,
+             TIFF *             const tifP,
+             const char *       const inputFileDescription,
+             struct cmdlineInfo const cmdline) {
+
+    tupletable chv;
+    tuplehash cht;
+    unsigned short ** tiffColorMap;  /* malloc'ed */
+    struct pam pam;
+    unsigned int colors;
+    bool grayscale;
+    unsigned short photometric;
+    unsigned short samplesperpixel;
+    unsigned short bitspersample;
+    unsigned short tiff_maxval;
+    /* This is the maxval of the samples in the tiff file.  It is 
+       determined solely by the bits per sample ('bitspersample').
+       */
+    int bytesperrow;
+    pm_filepos rasterPos;
+
+    pnm_readpaminit(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
+
+    pm_tell2(ifP, &rasterPos, sizeof(rasterPos));
+
+    analyzeColors(&pam, cmdline, MAXCOLORS, &chv, &colors, &grayscale);
+
+    /* Go back to beginning of raster */
+    pm_seek2(ifP, &rasterPos, sizeof(rasterPos));  
+
+    /* Figure out TIFF parameters. */
+
+    computeRasterParm(&pam, chv, colors, grayscale, 
+                      cmdline.compression,
+                      cmdline.minisblack, cmdline.miniswhite,
+                      cmdline.indexsizeAllowed,
+                      &samplesperpixel, &bitspersample, &photometric,
+                      &bytesperrow);
+
+    tiff_maxval = pm_bitstomaxval(bitspersample);
+
+    if (!chv) {
+        cht = NULL;
+        tiffColorMap = NULL;
+    } else {
+        createTiffColorMap(&pam, bitspersample, chv, colors, &tiffColorMap);
+
+        /* Convert color vector to color hash table, for fast lookup. */
+        cht = pnm_computetupletablehash(&pam, chv, colors);
+        pnm_freetupletable(&pam, chv);
+    }
+
+    setTiffFields(tifP, cmdline, &pam, bitspersample, photometric,
+                  samplesperpixel, tiffColorMap, inputFileDescription,
+                  cmdline.taglist);
+
+    writeScanLines(&pam, tifP, cht,
+                   tiff_maxval, bitspersample, photometric, bytesperrow, 
+                   cmdline.fillorder);
+
+    if (tiffColorMap)
+        destroyTiffColorMap(&pam, tiffColorMap);
+}
+
+
+
+static void
+validateReadableStdout(void) {
+/*----------------------------------------------------------------------------
+  We validate that Standard Output is readable and fail the program if
+  it isn't.
+
+  This is useful because there are situations in which the TIFF library
+  must read the output file and if it can't, it fails with an unhelpful
+  error message about a file I/O error.  We, on the other hand, produce
+  a helpful error message.
+-----------------------------------------------------------------------------*/
+    int flags;
+
+    flags = fcntl(STDOUT_FILENO, F_GETFL);
+
+    if (flags < 0) {
+        /* We couldn't get the flags.  So just assume the file's OK */
+    } else {
+        if ((flags & O_RDONLY) || (flags & O_RDWR)) {
+            /* File is readable.  All is well. */
+        } else
+            pm_error("Standard Output is not opened for reading.  "
+                     "In order to create a multi-image TIFF stream, "
+                     "Standard Output must be both readable and writable.");
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+    struct cmdlineInfo cmdline;
+    const char * inputFileDescription;
+    FILE* ifP;
+    TIFF* tifP;
+    bool eof;
+    unsigned int imageSeq;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    ifP = pm_openr_seekable(cmdline.input_filespec);
+
+    if (streq(cmdline.input_filespec, "-"))
+        inputFileDescription = "Standard Input";
+    else 
+        inputFileDescription = cmdline.input_filespec;
+
+    if (cmdline.append)
+        validateReadableStdout();
+
+    createTiffGenerator(STDOUT_FILENO, "Standard Output", cmdline.append,
+                        &tifP);
+
+    eof = FALSE;  /* initial assumption */
+    imageSeq = 0;
+
+    while (!eof) {
+        bool success;
+
+        if (cmdline.verbose)
+            pm_message("Converting Image %u", imageSeq);
+
+        pnm_nextimage(ifP, &eof);
+
+        if (!eof) {
+            if (imageSeq > 0)
+                validateReadableStdout();
+
+            convertImage(ifP, tifP, inputFileDescription, cmdline);
+            
+            success = TIFFWriteDirectory(tifP);
+            if (!success)
+                pm_error("Unable to write TIFF image %u to file.  "
+                         "tifWriteDirectory() failed.", imageSeq);
+            ++imageSeq;
+        }
+    }
+
+    destroyTiffGenerator(tifP);
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/converter/other/pamtouil.c b/converter/other/pamtouil.c
new file mode 100644
index 00000000..b9ddc749
--- /dev/null
+++ b/converter/other/pamtouil.c
@@ -0,0 +1,438 @@
+/* pamtouil.c - convert PBM, PGM, PPM, or PPM+alpha to Motif UIL icon file
+**
+** Bryan Henderson converted ppmtouil to pamtouil on 2002.05.04.
+**
+** Jef Poskanzer derived pamtouil from ppmtoxpm, which is
+** Copyright (C) 1990 by Mark W. Snitily.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _BSD_SOURCE  /* Make sure string.h contains strdup() */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+#include <ctype.h>
+#include <string.h>
+#include "pam.h"
+#include "pammap.h"
+#include "colorname.h"
+#include "shhopt.h"
+#include "nstring.h"
+
+/* Max number of colors allowed in ppm input. */
+#define MAXCOLORS 256
+
+/* Lower bound and upper bound of character-pixels printed in UIL output. */
+#define LOW_CHAR '`'
+#define HIGH_CHAR '~'
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespecs of input files */
+    char *outname;         /* output filename, less "_icon" */
+    unsigned int verbose;
+};
+
+
+
+typedef struct {    /* character-pixel mapping */
+    const char* cixel;    /* character string printed for pixel */
+    const char* rgbname;  /* ascii rgb color, either mnemonic or #rgb value */
+    const char* uilname;  /* same, with spaces replaced by underbars */
+    bool        transparent;
+} cixel_map;
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.  The outname array is in newly
+   malloc'ed storage.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc( 100*sizeof( optEntry ) );
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int outnameSpec;
+    const char *outnameOpt;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "name",       OPT_STRING, &outnameOpt, 
+            &outnameSpec,       0);
+    OPTENT3(0, "verbose",    OPT_FLAG,   NULL, 
+            &cmdlineP->verbose, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 == 0)
+        cmdlineP->inputFilespec = "-";  /* stdin */
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else 
+        pm_error("Too many arguments (%d).  "
+                 "Only need one: the input filespec", argc-1);
+
+    if (outnameSpec) {
+        char * barPos;
+
+        cmdlineP->outname = strdup(outnameOpt);
+
+        /* Remove trailing "_icon" */
+        barPos = strrchr(cmdlineP->outname, '_');
+        if (STREQ(barPos, "_icon")) 
+            *barPos = '\0';
+    } else {
+        if (STREQ(cmdlineP->inputFilespec, "-"))
+            cmdlineP->outname = strdup("noname");
+        else {
+            char * dotPos;
+
+            cmdlineP->outname = strdup(cmdlineP->inputFilespec);
+
+            /* remove extension */
+            dotPos = strrchr(cmdlineP->outname, '.');
+            if (dotPos)
+                *dotPos = '\0';
+        }
+    }
+}
+
+
+
+
+static char*
+genNumstr(int  const number, 
+          int  const base, 
+          char const low_char, 
+          int  const digits ) {
+/*----------------------------------------------------------------------------
+  Generate a string 'digits' characters long in newly malloc'ed
+  storage that is the number 'number' displayed in base 'base'.  Fill it on
+  the left with zero digits and truncate on the left if it doesn't fit.
+
+  Use the characters 'low_char' to 'low_char' + 'base' to represent the
+  digits 0 to 'base'. 
+-----------------------------------------------------------------------------*/
+    char* str;
+    char* p;
+    int d;
+    int i;
+
+    /* Allocate memory for printed number.  Abort if error. */
+    str = (char*) malloc(digits + 1);
+    if (str == NULL)
+        pm_error("out of memory allocating number string");
+
+    /* Generate characters starting with least significant digit. */
+    i = number;
+    p = str + digits;
+    *p-- = '\0';    /* nul terminate string */
+    while (p >= str) {
+        d = i % base;
+        i /= base;
+        *p-- = low_char + d;
+    }
+    return str;
+}
+
+
+
+static const char * 
+uilName(const char * const rgbname, bool const transparent) {
+/*----------------------------------------------------------------------------
+   Return a string in newly malloc'ed storage which is an appropriate 
+   color name for the UIL palette.  It is the same as the rgb name,
+   except that blanks are replaced by underscores, and if 'transparent'
+   is true, it is "background color".  The latter is a strange name of
+   a color, but it works pretty much the same in the UIL colortable() value.
+-----------------------------------------------------------------------------*/
+    char * output;
+
+    if (transparent)
+        output = strdup("background color");
+    else {
+        int i;
+
+        output = malloc(strlen(rgbname) + 5 + 1);
+        if (output == NULL)
+            pm_error( "out of memory allocating color name" );
+        
+        for (i = 0; i < strlen(rgbname); ++i) {
+            if (rgbname[i] == ' ')
+                output[i] = '_';
+            else
+                output[i] = rgbname[i];
+        }
+        output[strlen(rgbname)] = '\0';
+    }
+
+    return output;
+}
+
+
+
+static void
+genCmap(struct pam *   const pamP,
+        tupletable     const chv, 
+        unsigned int   const ncolors, 
+        cixel_map            cmap[MAXCOLORS], 
+        unsigned int * const charsppP,
+        bool           const verbose) {
+
+    unsigned int const base = (int) HIGH_CHAR - (int) LOW_CHAR + 1;
+    char* colorname;
+    unsigned int colorIndex;
+    {
+        /* Figure out how many characters per pixel we'll be using.
+        ** Don't want to be forced to link with libm.a, so using a
+        ** division loop rather than a log function.  
+        */
+        unsigned int i;
+        for (*charsppP = 0, i = ncolors; i > 0; ++(*charsppP))
+            i /= base;
+    }
+
+    /* Generate the character-pixel string and the rgb name for each colormap
+    ** entry.
+    */
+    for (colorIndex = 0; colorIndex < ncolors; ++colorIndex) {
+        bool nameAlreadyInCmap;
+        unsigned int indexOfName;
+        bool transparent;
+        int j;
+        
+        if (pamP->depth-1 < PAM_TRN_PLANE)
+            transparent = FALSE;
+        else 
+            transparent = 
+                chv[colorIndex]->tuple[PAM_TRN_PLANE] < pamP->maxval/2;
+
+        /* Generate color name string. */
+        colorname = pam_colorname(pamP, chv[colorIndex]->tuple, 
+                                  PAM_COLORNAME_ENGLISH);
+
+        /* We may have already assigned a character code to this color
+           name/transparency because the same color name can apply to
+           two different colors because we said we wanted the closest
+           matching color that has an English name, and we recognize
+           only one transparent color.  If that's the case, we just
+           make a cross-reference.  
+        */
+        nameAlreadyInCmap = FALSE;   /* initial assumption */
+        for (j = 0; j < colorIndex; ++j) {
+            if (cmap[j].rgbname != NULL && 
+                STREQ(colorname, cmap[j].rgbname) &&
+                cmap[j].transparent == transparent) {
+                nameAlreadyInCmap = TRUE;
+                indexOfName = j;
+            }
+        }
+        if (nameAlreadyInCmap) {
+            /* Make the entry a cross-reference to the earlier entry */
+            cmap[colorIndex].uilname = NULL;
+            cmap[colorIndex].rgbname = NULL;
+            cmap[colorIndex].cixel = cmap[indexOfName].cixel;
+        } else {
+            cmap[colorIndex].uilname = uilName(colorname, transparent);
+            cmap[colorIndex].rgbname = strdup(colorname);
+            if (cmap[colorIndex].rgbname == NULL)
+                pm_error( "out of memory allocating color name" );
+            
+            cmap[colorIndex].transparent = transparent;
+            
+            /* Generate color value characters. */
+            cmap[colorIndex].cixel = 
+                genNumstr(colorIndex, base, LOW_CHAR, *charsppP);
+            if (verbose)
+                pm_message("Adding color '%s' %s = '%s' to UIL colormap",
+                           cmap[colorIndex].rgbname, 
+                           cmap[colorIndex].transparent ? "TRANS" : "OPAQUE",
+                           cmap[colorIndex].cixel);
+        }
+    }
+}
+
+
+
+static void
+writeUilHeader(const char * const outname) {
+    /* Write out the UIL header. */
+    printf( "module %s\n", outname );
+    printf( "version = 'V1.0'\n" );
+    printf( "names = case_sensitive\n" );
+    printf( "include file 'XmAppl.uil';\n" );
+}
+
+
+
+static void
+writeColormap(const char * const outname, 
+              cixel_map          cmap[MAXCOLORS], 
+              unsigned int const ncolors) {
+
+    {
+        int i;
+
+        /* Write out the colors. */
+        printf("\n");
+        printf("value\n");
+        for (i = 0; i < ncolors; ++i)
+            if (cmap[i].uilname != NULL && !cmap[i].transparent) 
+                printf("    %s : color( '%s' );\n", 
+                       cmap[i].uilname, cmap[i].rgbname );
+    }
+    {
+        /* Write out the ascii colormap. */
+
+        int i;
+        bool printedOne;
+
+        printf("\n");
+        printf("value\n");
+        printf("  %s_rgb : color_table (\n", outname);
+        printedOne = FALSE; 
+        for (i = 0; i < ncolors; ++i)
+            if (cmap[i].uilname != NULL) {
+                if (printedOne)
+                    printf(",\n");
+                printf("    %s = '%s'", cmap[i].uilname, cmap[i].cixel);
+                printedOne = TRUE;
+            }     
+        printf("\n");
+        printf("    );\n");
+    }
+}
+
+
+
+static void
+writeRaster(struct pam *  const pamP, 
+            tuple **      const tuples,
+            const char *  const outname,
+            cixel_map           cmap[MAXCOLORS], 
+            unsigned int  const ncolors,
+            tuplehash     const cht, 
+            unsigned int  const charspp) {
+    
+    int row;
+    /* Write out the ascii character-pixel image. */
+
+    printf("\n");
+    printf("%s_icon : exported icon( color_table = %s_rgb,\n",
+           outname, outname);
+    for (row = 0; row < pamP->height; ++row) {
+        int col;
+
+        printf("    '");
+        for (col = 0; col < pamP->width; ++col) {
+            int colorIndex;
+            int found;
+            if ((col * charspp) % 70 == 0 && col > 0)
+                printf( "\\\n" );       /* line continuation */
+            pnm_lookuptuple(pamP, cht, tuples[row][col], &found, &colorIndex);
+            if (!found)
+                pm_error("INTERNAL ERROR: color not found in colormap");
+            printf("%s", cmap[colorIndex].cixel);
+        }
+        if (row != pamP->height - 1)
+            printf("',\n");
+        else
+            printf("'\n"); 
+    }
+    printf(");\n");
+    printf("\n");
+}
+
+
+static void
+freeString(const char * const s) {
+/*----------------------------------------------------------------------------
+   This is just free(), but with type checking for const char *.
+-----------------------------------------------------------------------------*/
+    free((void *)s);
+}
+
+
+
+static void
+freeCmap(cixel_map cmap[], unsigned int const ncolors) {
+
+    int i;
+
+    for (i = 0; i < ncolors; ++i) {
+        cixel_map const cmapEntry = cmap[i];
+        if (cmapEntry.uilname)
+            freeString(cmapEntry.uilname);
+        if (cmapEntry.rgbname)
+            freeString(cmapEntry.uilname);
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    struct pam pam;   /* Input PAM image */
+    FILE* ifP;
+    tuple** tuples;
+    unsigned int ncolors;
+    tuplehash cht;
+    tupletable chv;
+    cixel_map cmap[MAXCOLORS];
+    unsigned int charspp;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+    tuples = pnm_readpam(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
+    pm_close(ifP);
+
+    pm_message("computing colormap...");
+
+    chv = pnm_computetuplefreqtable(&pam, tuples, MAXCOLORS, &ncolors);
+    if (chv == NULL)
+        pm_error("too many colors - try doing a 'pnmquant %u'", MAXCOLORS);
+    if (cmdline.verbose)
+        pm_message("%u colors found", ncolors);
+
+    /* Make a hash table for fast color lookup. */
+    cht = pnm_computetupletablehash(&pam, chv, ncolors);
+
+    /* Now generate the character-pixel colormap table. */
+    pm_message("looking up color names, assigning character codes...");
+    genCmap(&pam, chv, ncolors, cmap, &charspp, cmdline.verbose);
+
+    pm_message("generating UIL...");
+    writeUilHeader(cmdline.outname);
+
+    writeColormap(cmdline.outname, cmap, ncolors);
+
+    writeRaster(&pam, tuples, cmdline.outname, cmap, ncolors, cht, charspp);
+
+    printf("end module;\n");
+
+    free(cmdline.outname);
+    freeCmap(cmap, ncolors);
+
+    return 0;
+}
diff --git a/converter/other/pamtoxvmini.c b/converter/other/pamtoxvmini.c
new file mode 100644
index 00000000..dc56e912
--- /dev/null
+++ b/converter/other/pamtoxvmini.c
@@ -0,0 +1,250 @@
+/*=============================================================================
+                                    pamtoxvmini
+===============================================================================
+   Convert Netpbm image to XV mini thumbnail.
+
+   Written by Bryan Henderson in April 2006 and contributed to the public
+   domain.
+=============================================================================*/
+
+#include <assert.h>
+#include <limits.h>
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "nstring.h"
+#include "pam.h"
+#include "pammap.h"
+
+typedef struct xvPalette {
+    unsigned int red[256];
+    unsigned int grn[256];
+    unsigned int blu[256];
+} xvPalette;
+
+
+struct cmdlineInfo {
+    const char * inputFileName;
+};
+
+
+
+static void
+parseCommandLine(int const argc,
+                 char *    argv[],
+                 struct cmdlineInfo * const cmdlineP) {
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else {
+        cmdlineP->inputFileName = argv[1];
+
+        if (argc-1 > 1)
+            pm_error("Too many arguments: %u.  Only argument is optional "
+                     "input file name.", argc-1);
+    }
+}
+
+
+
+static void
+makeXvPalette(xvPalette * const xvPaletteP) {
+
+    unsigned int paletteIndex;
+    unsigned int r;
+
+    paletteIndex = 0;
+
+    for (r = 0; r < 8; ++r) {
+        unsigned int g;
+        for (g = 0; g < 8; ++g) {
+            unsigned int b;
+            for (b = 0; b < 4; ++b) {
+                xvPaletteP->red[paletteIndex] = (r*255)/7;
+                xvPaletteP->grn[paletteIndex] = (g*255)/7;
+                xvPaletteP->blu[paletteIndex] = (b*255)/3;
+                ++paletteIndex;
+            }
+        }
+    }
+
+}
+
+
+
+static void
+writeXvHeader(FILE *       const ofP,
+              unsigned int const cols,
+              unsigned int const rows,
+              unsigned int const maxval) {
+           
+    fprintf(ofP, "P7 332\n");
+
+    fprintf(ofP, "# Created by Pamtoxvmini\n");
+    fprintf(ofP, "#END_OF_COMMENTS\n");
+
+    fprintf(ofP, "%u %u %u\n", cols, rows, maxval);
+}
+
+
+
+static void
+findClosestColor(struct pam *      const pamP,
+                 tuple             const tuple, 
+                 const xvPalette * const xvPaletteP,
+                 unsigned int *    const paletteIndexP) {
+/*----------------------------------------------------------------------------
+   Find the color in the palette *xvPaletteP that is closest to the color
+   'tuple' and return its index in the palette.
+-----------------------------------------------------------------------------*/
+    unsigned int paletteIndex;
+    unsigned int bestPaletteIndex;
+    unsigned int bestDistanceSoFar;
+
+    /* An entry condition is that the tuple have the same form as the
+       colors in the XV palette:
+    */
+    assert(pamP->depth >= 3);
+    assert(pamP->maxval = 255);
+
+    bestPaletteIndex = 0;
+    bestDistanceSoFar = UINT_MAX;
+
+    for (paletteIndex = 0; paletteIndex < 256; ++paletteIndex) {
+        unsigned int const tupleRed = tuple[PAM_RED_PLANE];
+        unsigned int const tupleGrn = tuple[PAM_GRN_PLANE];
+        unsigned int const tupleBlu = tuple[PAM_BLU_PLANE];
+        
+        unsigned int const paletteRed = xvPaletteP->red[paletteIndex];
+        unsigned int const paletteGrn = xvPaletteP->grn[paletteIndex];
+        unsigned int const paletteBlu = xvPaletteP->blu[paletteIndex];
+
+        unsigned int const distance = 
+            SQR((int)tupleRed - (int)paletteRed) +
+            SQR((int)tupleGrn - (int)paletteGrn) +
+            SQR((int)tupleBlu - (int)paletteBlu);
+
+        if (distance < bestDistanceSoFar) {
+            bestDistanceSoFar = distance;
+            bestPaletteIndex = paletteIndex;
+        }
+    }
+    *paletteIndexP = bestPaletteIndex;
+}
+
+
+
+static void
+getPaletteIndexThroughCache(struct pam *      const pamP,
+                            tuple             const tuple,
+                            const xvPalette * const xvPaletteP,
+                            tuplehash         const paletteHash,
+                            unsigned int *    const paletteIndexP) {
+/*----------------------------------------------------------------------------
+   Return as *paletteIndexP the index into the palette *xvPaletteP of
+   the color that most closely resembles the color 'tuple'.
+
+   Use the hash table *paletteIndexP as a cache to speed up the search.
+   If the tuple-index association is in *paletteIndexP, use it.  If not,
+   find it the hard way and add it to *palettedIndexP for the next guy.
+-----------------------------------------------------------------------------*/
+    bool found;
+    int paletteIndex;
+
+    pnm_lookuptuple(pamP, paletteHash, tuple, &found, &paletteIndex);
+    if (found)
+        *paletteIndexP = paletteIndex;
+    else {
+        bool fits;
+        findClosestColor(pamP, tuple, xvPaletteP, paletteIndexP);
+        
+        pnm_addtotuplehash(pamP, paletteHash, tuple, *paletteIndexP, &fits);
+        
+        if (!fits)
+            pm_error("Can't get memory for palette hash.");
+    }
+}
+
+    
+
+static void
+writeXvRaster(struct pam * const pamP,
+              xvPalette *  const xvPaletteP,
+              FILE *       const ofP) {
+/*----------------------------------------------------------------------------
+   Write out the XV image, from the Netpbm input file ifP, which is
+   positioned to the raster.
+
+   The XV raster contains palette indices into the palette *xvPaletteP.
+
+   If there is any color in the image which is not in the palette, we
+   fail the program.  We really should use the closest color in the palette
+   instead.
+-----------------------------------------------------------------------------*/
+    tuplehash paletteHash;
+    tuple * tuplerow;
+    unsigned int row;
+    unsigned char * xvrow;
+
+    paletteHash = pnm_createtuplehash();
+
+    tuplerow = pnm_allocpamrow(pamP);
+    xvrow = (unsigned char*)pm_allocrow(pamP->width, 1);
+
+    for (row = 0; row < pamP->height; ++row) {
+        unsigned int col;
+
+        pnm_readpamrow(pamP, tuplerow);
+        pnm_scaletuplerow(pamP, tuplerow, tuplerow, 255);
+        pnm_makerowrgb(pamP, tuplerow);
+
+        for (col = 0; col < pamP->width; ++col) {
+            unsigned int paletteIndex;
+
+            getPaletteIndexThroughCache(pamP, tuplerow[col], xvPaletteP,
+                                        paletteHash, &paletteIndex);
+
+            assert(paletteIndex < 256);
+
+            xvrow[col] = paletteIndex;
+        }
+        fwrite(xvrow, 1, pamP->width, ofP);
+    }
+
+    pm_freerow((char*)xvrow);
+    pnm_freepamrow(pamP);
+
+    pnm_destroytuplehash(paletteHash);
+}
+
+
+
+int 
+main(int    argc,
+     char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    struct pam pam;
+    xvPalette xvPalette;
+ 
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    makeXvPalette(&xvPalette);
+
+    pnm_readpaminit(ifP, &pam, PAM_STRUCT_SIZE(allocation_depth));
+
+    pnm_setminallocationdepth(&pam, 3);
+
+    writeXvHeader(stdout, pam.width, pam.height, pam.maxval);
+    
+    writeXvRaster(&pam, &xvPalette, stdout);
+
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/converter/other/pbmtopgm.c b/converter/other/pbmtopgm.c
new file mode 100644
index 00000000..69b20fb2
--- /dev/null
+++ b/converter/other/pbmtopgm.c
@@ -0,0 +1,80 @@
+/* pbmtopgm.c - convert PBM to PGM by totalling pixels over sample area
+ * AJCD 12/12/90
+ */
+
+#include <stdio.h>
+
+#include "pm_c_util.h"
+#include "pgm.h"
+
+int
+main(int argc, char *argv[]) {
+
+    gray *outrow, maxval;
+    int right, left, down, up;
+    bit **inbits;
+    int rows, cols;
+    FILE *ifd;
+    int row;
+    int width, height;
+    const char * const usage = "<w> <h> [pbmfile]";
+   
+
+    pgm_init( &argc, argv );
+
+    if (argc > 4 || argc < 3)
+        pm_usage(usage);
+
+    width = atoi(argv[1]);
+    height = atoi(argv[2]);
+    if (width < 1 || height < 1)
+        pm_error("width and height must be > 0");
+    left=width/2; right=width-left;
+    up=width/2; down=height-up;
+
+    if (argc == 4)
+        ifd = pm_openr(argv[3]);
+    else
+        ifd = stdin ;
+
+    inbits = pbm_readpbm(ifd, &cols, &rows) ;
+    
+    if (width > cols)
+        pm_error("You specified a sample width (%u columns) which is greater "
+                 "than the image width (%u columns)", height, rows);
+    if (height > rows)
+        pm_error("You specified a sample height (%u rows) which is greater "
+                 "than the image height (%u rows)", height, rows);
+
+    outrow = pgm_allocrow(cols) ;
+    maxval = MIN(PGM_OVERALLMAXVAL, width*height);
+    pgm_writepgminit(stdout, cols, rows, maxval, 0) ;
+
+    for (row = 0; row < rows; row++) {
+        int const t = (row > up) ? (row-up) : 0;
+        int const b = (row+down < rows) ? (row+down) : rows;
+        int const onv = height - (t-row+up) - (row+down-b);
+        unsigned int col;
+        for (col = 0; col < cols; col++) {
+            int const l = (col > left) ? (col-left) : 0;
+            int const r = (col+right < cols) ? (col+right) : cols;
+            int const onh = width - (l-col+left) - (col+right-r);
+            int value;
+            int x;
+
+            value = 0;  /* initial value */
+
+            for (x = l; x < r; ++x) {
+                int y;
+                for (y = t; y < b; ++y)
+                    if (inbits[y][x] == PBM_WHITE) 
+                        ++value;
+            }
+            outrow[col] = maxval*value/(onh*onv);
+        }
+        pgm_writepgmrow(stdout, outrow, cols, maxval, 0) ;
+    }
+    pm_close(ifd);
+
+    return 0;
+}
diff --git a/converter/other/pclxl.h b/converter/other/pclxl.h
new file mode 100644
index 00000000..b1d1c043
--- /dev/null
+++ b/converter/other/pclxl.h
@@ -0,0 +1,390 @@
+#ifndef PCLXL_H_INCLUDED
+#define PCLXL_H_INCLUDED
+
+/* This file defines elements of Hewlett Packard's PCL-XL printer
+   control language (fka PCL-6)
+*/
+
+enum ArcDirection {
+    eClockWise = 0,
+    eCounterClockWise = 1
+};
+
+enum CharSubModeArray {
+    eNoSubstitution = 0,
+    eVerticalSubstitution = 1
+};
+
+enum ClipRegion {
+    eInterior = 0,
+    eExterior = 1
+};
+
+enum ColorDepth {
+    e1Bit = 0,
+    e4Bit = 1,
+    e8Bit = 2
+};
+
+enum ColrMapping {
+    eDirectPixel = 0,
+    eIndexedPixel = 1
+};
+
+enum Colorspace {
+    eGray = 1,
+    eRGB = 2
+};
+
+enum Compression {
+    eNoCompression = 0,
+    eRLECompression = 1,
+    eJPEGCompression = 2
+};
+
+enum DataOrg {
+    eBinaryHighByteFirst = 0,
+    eBinaryLowByteFirst = 1
+};
+
+enum DataSource {
+    eDefault = 0
+};
+
+enum DataType {
+    eUByte = 0,
+    eSByte = 1,
+    eUint16 = 2,
+    eSint16 = 3
+};
+
+enum DitherMatrix {  
+    eDeviceBest = 0
+}; 
+
+
+enum DuplexPageMode {
+    eDuplexHorizontalBinding = 0,
+    eDuplexVerticalBinding = 1
+};
+
+enum DuplexPageSide {
+    eFrontMediaSide = 0,
+    eBackMediaSide = 1
+};
+
+enum ErrorReport {
+    eNoReporting = 0,
+    eBackChannel = 1,
+    eErrorPage = 2,
+    eBackChAndErrPage = 3,
+    eNWBackChannel = 4 ,
+    eNWErrorPage = 5,
+    eNWBackChAndErrPage = 6
+};
+
+enum FillMode {
+    eNonZeroWinding = 0,
+    eEvenOdd = 1
+};
+
+enum LineCap {
+    eButtCap = 0,
+    eRoundCap = 1,
+    eSquareCap = 2,
+    eTriangleCap = 3
+};
+
+enum LineJoin {
+    eMiterJoin = 0,
+    eRoundJoin = 1,
+    eBevelJoin = 2,
+    eNoJoin = 3
+};
+
+enum Measure {
+    eInch = 0,
+    eMillimeter = 1,
+    eTenthsOfAMillimeter = 2
+};
+
+enum MediaSource {
+    eDefaultSource = 0,
+    eAutoSelect = 1,
+    eManualFeed = 2,
+    eMultiPurposeTray = 3,
+    eUpperCassette = 4,
+    eLowerCassette = 5,
+    eEnvelopeTray = 6,
+    eThirdCassette = 7
+};
+
+enum MediaDestination {
+    eDefaultDestination = 0,
+    eFaceDownBin = 1,
+    eFaceUpBin = 2,
+    eJobOffsetBin = 3
+};
+
+enum Orientation {
+    ePortraitOrientation = 0,
+    eLandscapeOrientation = 1,
+    eReversePortrait = 2,
+    eReverseLandscape = 3
+};
+
+enum PatternPersistence {
+    eTempPattern = 0,
+    ePagePattern = 1,
+    eSessionPattern = 2
+};
+
+enum SimplexPageMode {
+    eSimplexFrontSide = 0
+};
+
+enum TxMode {
+    eOpaque = 0,
+    eTransparent = 1
+};
+
+enum WritingMode{
+    eHorizontal = 0,
+    eVertical = 1
+};
+
+enum Attribute {
+    aPaletteDepth        =   2,
+    aColorSpace          =   3,
+    aNullBrush           =   4,
+    aNullPen             =   5,
+    aPaletteData         =   6,
+    aPatternSelectID     =   8,
+    aGrayLevel           =   9,
+    aRGBColor            =  11,
+    aPatternOrigin       =  12,
+    aNewDestinationSize  =  13,
+    aPrimaryArray        =  14,
+    aPrimaryDepth        =  15,
+    aDeviceMatrix        =  33,
+    aDitherMatrixDataType=  34,
+    aDitherOrigin        =  35,
+    aMediaDestination    =  36,
+    aMediaSize           =  37,
+    aMediaSource         =  38,
+    aMediaType           =  39,
+    aOrientation         =  40,
+    aPageAngle           =  41,
+    aPageOrigin          =  42,
+    aPageScale           =  43,
+    aROP3                =  44,
+    aTxMode              =  45,
+    aCustomMediaSize     =  47,
+    aCustomMediaUnits    =  48,
+    aPageCopies          =  49,
+    aDitherMatrixSize    =  50,
+    aDitherMatrixDepth   =  51,
+    aSimplexPageMode     =  52,
+    aDuplexPageMode      =  53,
+    aDuplexPageSide      =  54,
+    aArcDirection        =  65,
+    aBoundingBox         =  66,
+    aDashOffset          =  67,
+    aEllipseDimension    =  68,
+    aEndPoint            =  69,
+    aFillMode            =  70,
+    aLineCapStyle        =  71,
+    aLineJoinStyle       =  72,
+    aMiterLength         =  73,
+    aLineDashStyle       =  74,
+    aPenWidth            =  75,
+    aPoint               =  76,
+    aNumberOfPoints      =  77,
+    aSolidLine           =  78,
+    aStartPoint          =  79,
+    aPointType           =  80,
+    aControlPoint1       =  81,
+    aControlPoint2       =  82,
+    aClipRegion          =  83,
+    aClipMode            =  84,
+    aColorDepth          =  98,
+    aBlockHeight         =  99,
+    aColorMapping        = 100,
+    aCompressMode        = 101,
+    aDestinationBox      = 102,
+    aDestinationSize     = 103,
+    aPatternPersistence  = 104,
+    aPatternDefineID     = 105,
+    aSourceHeight        = 107,
+    aSourceWidth         = 108,
+    aStartLine           = 109,
+    aPadBytesMultiple    = 110,
+    aBlockByteLength     = 111,
+    aNumberOfScanLines   = 115,
+    aColorTreatment      = 120,
+    aCommentData         = 129,
+    aDataOrg             = 130,
+    aMeasure             = 134,
+    aSourceType          = 136,
+    aUnitsPerMeasure     = 137,
+    aStreamName          = 139,
+    aStreamDataLength    = 140,
+    aErrorReport         = 143,
+    aCharAngle           = 161,
+    aCharCode            = 162,
+    aCharDataSize        = 163,
+    aCharScale           = 164,
+    aCharShear           = 165,
+    aCharSize            = 166,
+    aFontHeaderLength    = 167,
+    aFontName            = 168,
+    aFontFormat          = 169,
+    aSymbolSet           = 170,
+    aTextData            = 171,
+    aCharSubModeArray    = 172,
+    aXSpacingData        = 175,
+    aYSpacingData        = 176,
+    aCharBoldValue       = 177
+};
+                                          
+enum Operator {
+    oBeginSession        = 0x41,
+    oEndSession          = 0x42,
+    oBeginPage           = 0x43,
+    oEndPage             = 0x44,
+    oComment             = 0x47,
+    oOpenDataSource      = 0x48,
+    oCloseDataSource     = 0x49,
+    oBeginFontHeader     = 0x4f,
+    oReadFontHeader      = 0x50,
+    oEndFontHeader       = 0x51,
+    oBeginChar           = 0x52,
+    oReadChar            = 0x53,
+    oEndChar             = 0x54,
+    oRemoveFont          = 0x55,
+    oSetCharAttributes   = 0x56,
+    oBeginStream         = 0x5b,
+    oReadStream          = 0x5c,
+    oEndStream           = 0x5d,
+    oExecStream          = 0x5e,
+    oRemoveStream        = 0x5f,
+    oPopGS               = 0x60,
+    oPushGS              = 0x61,
+    oSetClipReplace      = 0x62,
+    oSetBrushSource      = 0x63,
+    oSetCharAngle        = 0x64,
+    oSetCharScale        = 0x65,
+    oSetCharShear        = 0x66,
+    oSetClipIntersect    = 0x67,
+    oSetClipRectangle    = 0x68,
+    oSetClipToPage       = 0x69,
+    oSetColorSpace       = 0x6a,
+    oSetCursor           = 0x6b,
+    oSetCursorRel        = 0x6c,
+    oSetHalftoneMethod   = 0x6d,
+    oSetFillMode         = 0x6e,
+    oSetFont             = 0x6f,
+    oSetLineDash         = 0x70,
+    oSetLineCap          = 0x71,
+    oSetLineJoin         = 0x72,
+    oSetMiterLimit       = 0x73,
+    oSetPageDefaultCTM   = 0x74,
+    oSetPageOrigin       = 0x75,
+    oSetPageRotation     = 0x76,
+    oSetPageScale        = 0x77,
+    oSetPatternTxMode    = 0x78,
+    oSetPenSource        = 0x79,
+    oSetPenWidth         = 0x7a,
+    oSetROP              = 0x7b,
+    oSetSourceTxMode     = 0x7c,
+    oSetCharBoldValue    = 0x7d,
+    oSetClipMode         = 0x7f,
+    oSetPathToClip       = 0x80,
+    oSetCharSubMode      = 0x81,
+    oCloseSubPath        = 0x84,
+    oNewPath             = 0x85,
+    oPaintPath           = 0x86,
+    oArcPath             = 0x91,
+    oBezierPath          = 0x93,
+    oBezierRelPath       = 0x95,
+    oChord               = 0x96,
+    oChordPath           = 0x97,
+    oEllipse             = 0x98,
+    oEllipsePath         = 0x99,
+    oLinePath            = 0x9b,
+    oLineRelPath         = 0x9d,
+    oPie                 = 0x9e,
+    oPiePath             = 0x9f,
+    oRectangle           = 0xa0,
+    oRectanglePath       = 0xa1,
+    oRoundRectangle      = 0xa2,  
+    oRoundRectanglePath  = 0xa3,
+    oText                = 0xa8,
+    oTextPath            = 0xa9,
+    oBeginImage          = 0xb0,
+    oReadImage           = 0xb1,
+    oEndImage            = 0xb2,
+    oBeginRastPattern    = 0xb3,
+    oReadRastPattern     = 0xb4,
+    oEndRastPattern      = 0xb5,
+    oBeginScan           = 0xb6,
+    oEndScan             = 0xb8,
+    oScanLineRel         = 0xb9
+};
+
+enum MediaSize {
+    eLetterPaper,
+    eLegalPaper,
+    eA4Paper,
+    eExecPaper,
+    eLedgerPaper,
+    eA3Paper,
+    eCOM10Envelope,
+    eMonarchEnvelope,
+    eC5Envelope,
+    eDLEnvelope,
+    eJB4Paper,
+    eJB5Paper,
+    etralala,
+    eB5Envelope,
+    eJPostcard,
+    eJDoublePostcard,
+    eA5Paper,
+    eA6Paper,
+    eJB6Paper
+};
+
+struct sPaperFormat {
+    const char *name;
+    int xl_nr;
+    float width,height; /* inch */
+} xlPaperFormats[] = {
+    {"letter",eLetterPaper,8.5,11.0},
+    {"legal",eLegalPaper,8.5,14},
+    {"a4",eA4Paper,8.26389,11.6944},
+    {"exec",eExecPaper,190/2.54,254/2.54},
+    {"ledger",eLedgerPaper,279/2.54,432/2.54},
+    {"a3",eA3Paper,11.6944,16.5278},
+    {"com10envelope",eCOM10Envelope,105/2.54,241/2.54},
+    {"monarchenvelope",eMonarchEnvelope,98/2.54,191/2.54},
+    {"c5envelope",eC5Envelope,162/2.54,229/2.54},
+    {"dlenvelope",eDLEnvelope,110/2.54,220/2.54},
+    {"jb4",eJB4Paper,257/2.54,364/2.54},
+    {"jb5",eJB5Paper,182/2.54,257/2.54},
+    {"b5envelope",eB5Envelope,176/2.54,250/2.54},
+    {"",-1,0,0},
+    {"jpostcard",eJPostcard,148/2.54,200/2.54},
+    {"jdoublepostcard",eJDoublePostcard,100/2.54,148/2.54},
+    {"a5",eA5Paper, 5.84722,8.26389},
+    {"a6",eA6Paper,4.125,5.84722},
+    {"jb6",eJB6Paper,0,0},
+    {NULL,0,0,0}
+};
+
+enum {
+    eSTART,
+    eRLE,
+    eLIT 
+} RLEstates;
+
+#endif
diff --git a/converter/other/pfmtopam.c b/converter/other/pfmtopam.c
new file mode 100644
index 00000000..9430f5fa
--- /dev/null
+++ b/converter/other/pfmtopam.c
@@ -0,0 +1,395 @@
+/*****************************************************************************
+                                  pfmtopam
+******************************************************************************
+  This program converts a PFM (Portable Float Map) image to PAM.
+  
+  By Bryan Henderson, San Jose, CA April 2004.
+
+  Contributed to the public domain by its author.
+
+*****************************************************************************/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <assert.h>
+
+#include "pam.h"
+#include "pm_gamma.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+
+struct cmdlineInfo {
+    const char * inputFilespec;
+    unsigned int verbose;
+    sample maxval;
+};
+
+
+
+static void 
+parseCommandLine(int argc, 
+                 char ** argv, 
+                 struct cmdlineInfo  * const cmdlineP) {
+/* --------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+--------------------------------------------------------------------------*/
+    optEntry *option_def = malloc( 100*sizeof( optEntry ) );
+    /* Instructions to optParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+  
+    unsigned int option_def_index;
+    unsigned int maxvalSpec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "maxval",   OPT_UINT, &cmdlineP->maxval, &maxvalSpec,        0);
+    OPTENT3(0, "verbose",  OPT_FLAG, NULL,             &cmdlineP->verbose, 0);
+  
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;   /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+    /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (!maxvalSpec)
+        cmdlineP->maxval = PNM_MAXMAXVAL;
+
+    if (cmdlineP->maxval > PNM_OVERALLMAXVAL)
+        pm_error("Maximum allowed -maxval is %u.  You specified %u",
+                 PNM_OVERALLMAXVAL, (unsigned)cmdlineP->maxval);
+    else if (cmdlineP->maxval == 0)
+        pm_error("-maxval cannot be 0");
+
+    /* Get the program parameters */
+
+    if (argc-1 >= 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        cmdlineP->inputFilespec = "-";
+    
+    if (argc-1 > 1)
+        pm_error("Program takes at most one argument:  the file name.  "
+                 "You specified %d", argc-1);
+}
+
+
+enum endian {ENDIAN_BIG, ENDIAN_LITTLE};
+
+
+static enum endian machineEndianness;
+
+
+
+static enum endian
+thisMachineEndianness(void) {
+/*----------------------------------------------------------------------------
+   Endianness is a component of the format in which a machine represents
+   a number in memory or a register.  It is the only component of the format
+   that varies among typical machines.
+
+   Big endianness is the natural format.  In this format, if an integer is
+   4 bytes, to be stored at memory address 100-103, the most significant 
+   byte goes at 100, the next most significant at 101, and the least
+   significant byte at 103.  This is natural because it matches the way
+   humans read and write numbers.  I.e. 258 is stored as 0x00000102.
+
+   Little endian is extremely common because it is used by IA32.  In the
+   example above, the least significant byte goes first, so 258 would be
+   stored as 0x02010000.
+
+   You can extend this concept to floating point numbers, even though the
+   bytes of a floating point number differ by more than significance.
+-----------------------------------------------------------------------------*/
+    short const testNumber = 0x0001;
+
+    unsigned char * const storedNumber = (unsigned char *)&testNumber;
+    enum endian endianness;
+    
+    if (storedNumber[0] == 0x01)
+        endianness = ENDIAN_LITTLE;
+    else
+        endianness = ENDIAN_BIG;
+
+    return endianness;
+}
+
+
+
+typedef union {
+    unsigned char bytes[4];      /* as read from the file */
+    float value;
+        /* This is valid only if the pfmSample has the same endianness
+           as the machine we're running on.
+        */
+} pfmSample;
+
+
+
+static float
+floatFromPfmSample(pfmSample   const sample, 
+                   enum endian const pfmEndianness) {
+/*----------------------------------------------------------------------------
+   Type converter
+-----------------------------------------------------------------------------*/
+    if (machineEndianness == pfmEndianness) {
+        return sample.value;
+    } else {
+        pfmSample rightEndianSample;
+        unsigned int i, j;
+        
+        for (i = 0, j = sizeof(sample.bytes)-1; 
+             i < sizeof(sample.bytes); 
+             ++i, --j)
+            
+            rightEndianSample.bytes[i] = sample.bytes[j];
+
+        return rightEndianSample.value;
+    }
+}
+
+
+
+struct pfmHeader {
+    unsigned int width;
+    unsigned int height;
+    bool color;
+    float scaleFactor;
+    enum endian endian;
+};
+
+
+static void
+readPfmHeader(FILE *             const ifP,
+              struct pfmHeader * const pfmHeaderP) {
+
+    int firstChar;
+    int secondChar;
+    float scaleFactorEndian;
+
+    firstChar = fgetc(ifP);
+    if (firstChar == EOF)
+        pm_error("Error reading first character of PFM file");
+    secondChar = fgetc(ifP);
+    if (secondChar == EOF)
+        pm_error("Error reading second character of PFM file");
+
+    if (firstChar != 'P' || (secondChar != 'F' && secondChar != 'f'))
+        pm_error("First two characters of input file are '%c%c', but "
+                 "for a valid PFM file, they must be 'PF' or 'Pf'.",
+                 firstChar, secondChar);
+
+    {
+        int whitespace;
+
+        whitespace = fgetc(ifP);
+        if (whitespace == EOF)
+            pm_error("Error reading third character of PFM file");
+
+        if (!isspace(whitespace))
+            pm_error("The 3rd character of the input file is not whitespace.");
+    }
+    {
+        int rc;
+        char whitespace;
+
+        rc = fscanf(ifP, "%u %u%c", 
+                    &pfmHeaderP->width, &pfmHeaderP->height, &whitespace);
+
+        if (rc == EOF)
+            pm_error("Error reading the width and height from input file.");
+        else if (rc != 3)
+            pm_error("Invalid input file format where width and height "
+                     "are supposed to be (should be two positive decimal "
+                     "integers separated by a space and followed by "
+                     "white space)");
+        
+        if (!isspace(whitespace))
+            pm_error("Invalid input file format -- '%c' instead of "
+                     "white space after height", whitespace);
+
+        if (pfmHeaderP->width == 0)
+            pm_error("Invalid input file: image width is zero");
+        if (pfmHeaderP->height == 0)
+            pm_error("Invalid input file: image height is zero");
+    }
+    {
+        int rc;
+        char whitespace;
+
+        rc = fscanf(ifP, "%f%c", &scaleFactorEndian, &whitespace);
+
+        if (rc == EOF)
+            pm_error("Error reading the scale factor from input file.");
+        else if (rc != 2)
+            pm_error("Invalid input file format where scale factor "
+                     "is supposed to be (should be a floating point decimal "
+                     "number followed by white space");
+        
+        if (!isspace(whitespace))
+            pm_error("Invalid input file format -- '%c' instead of "
+                     "white space after scale factor", whitespace);
+    }
+
+    pfmHeaderP->color = (secondChar == 'F');  
+        /* 'PF' = RGB, 'Pf' = monochrome */
+
+    if (scaleFactorEndian > 0.0) {
+        pfmHeaderP->endian = ENDIAN_BIG;
+        pfmHeaderP->scaleFactor = scaleFactorEndian;
+    } else if (scaleFactorEndian < 0.0) {
+        pfmHeaderP->endian = ENDIAN_LITTLE;
+        pfmHeaderP->scaleFactor = - scaleFactorEndian;
+    } else
+        pm_error("Scale factor/endianness in PFM header is 0");
+}
+
+
+static void
+dumpPfmHeader(struct pfmHeader const pfmHeader) {
+
+    pm_message("width: %u, height: %u", pfmHeader.width, pfmHeader.height);
+    pm_message("color: %s", pfmHeader.color ? "YES" : "NO");
+    pm_message("endian: %s", 
+               pfmHeader.endian == ENDIAN_BIG ? "BIG" : "LITTLE");
+    pm_message("scale factor: %f", pfmHeader.scaleFactor);
+}
+
+
+
+static void
+initPam(struct pam * const pamP, 
+        int          const width, 
+        int          const height, 
+        bool         const color,
+        sample       const maxval) {
+
+    pamP->size        = sizeof(*pamP);
+    pamP->len         = PAM_STRUCT_SIZE(tuple_type);
+    pamP->file        = stdout;
+    pamP->format      = PAM_FORMAT;
+    pamP->plainformat = FALSE;
+    pamP->width       = width;
+    pamP->height      = height;
+    pamP->maxval      = maxval;
+    if (color) {
+        pamP->depth = 3;
+        strcpy(pamP->tuple_type, "RGB");
+    } else {
+        pamP->depth = 1;
+        strcpy(pamP->tuple_type, "GRAYSCALE");
+    }
+}
+
+
+
+static void
+makePamRow(struct pam * const pamP,
+           FILE *       const ifP,
+           unsigned int const pfmRow,
+           unsigned int const pfmSamplesPerRow,
+           tuplen **    const tuplenArray,
+           enum endian  const endian,
+           float        const scaleFactor,
+           pfmSample *  const pfmRowBuffer) {
+/*----------------------------------------------------------------------------
+   Make a PAM (tuple) row of the form described by *pamP, from the next
+   row in the PFM file identified by 'ifP'.  Place it in the proper location
+   in the tuple array 'tuplenArray'.
+  
+   'endian' is the endianness of the samples in the PFM file.
+
+   'pfmRowNum' is the sequence number (starting at 0, which is the
+   bottommost row of the image) of the row we are converting in the
+   PFM file.
+
+   Use 'pfmRowBuffer' as a work space; Caller must ensure this array
+   has at least 'pfmSamplesPerRow' elements of space in it.
+-----------------------------------------------------------------------------*/
+    int      const row       = pamP->height - pfmRow - 1;
+    tuplen * const tuplenRow = tuplenArray[row];
+
+    int col;
+    int pfmCursor;
+    int rc;
+
+    rc = fread(pfmRowBuffer, sizeof(pfmSample), pfmSamplesPerRow, ifP);
+    if (rc != pfmSamplesPerRow)
+        pm_error("End of file in the middle of row %d", pfmRow);
+
+    pfmCursor = 0;
+
+    for (col = 0; col < pamP->width; ++col) {
+        /* The order of planes (R, G, B) is the same in PFM as in PAM. */
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane) {
+            float const val = 
+                floatFromPfmSample(pfmRowBuffer[pfmCursor++], endian);
+            tuplenRow[col][plane] = val / scaleFactor;
+        }
+    }
+    assert(pfmCursor == pfmSamplesPerRow);
+}
+
+
+
+int
+main(int argc, char **argv ) {
+
+    struct cmdlineInfo cmdline;
+    FILE* ifP;
+    struct pam pam;
+    struct pfmHeader pfmHeader;
+    pfmSample * pfmRowBuffer;
+    unsigned int pfmSamplesPerRow;
+    unsigned pfmRow;
+    tuplen ** tuplenArray;
+
+    machineEndianness = thisMachineEndianness();
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    readPfmHeader(ifP, &pfmHeader);
+
+    if (cmdline.verbose)
+        dumpPfmHeader(pfmHeader);
+
+    initPam(&pam, 
+            pfmHeader.width, pfmHeader.height, pfmHeader.color,
+            cmdline.maxval);
+
+    tuplenArray = pnm_allocpamarrayn(&pam);
+
+    pfmSamplesPerRow = pam.width * pam.depth;
+    
+    MALLOCARRAY_NOFAIL(pfmRowBuffer, pfmSamplesPerRow);
+
+    /* PFMs are upside down like BMPs */
+    for (pfmRow = 0; pfmRow < pam.height; ++pfmRow)
+        makePamRow(&pam, ifP, pfmRow, pfmSamplesPerRow,
+                   tuplenArray, pfmHeader.endian, pfmHeader.scaleFactor,
+                   pfmRowBuffer);
+
+    pnm_writepamn(&pam, tuplenArray);
+
+    pnm_freepamarrayn(tuplenArray, &pam);
+    free(pfmRowBuffer);
+    
+    pm_close(ifP);
+    pm_close(pam.file);
+
+    return 0;
+}
diff --git a/converter/other/pgmtopbm.c b/converter/other/pgmtopbm.c
new file mode 100644
index 00000000..98bdc332
--- /dev/null
+++ b/converter/other/pgmtopbm.c
@@ -0,0 +1,723 @@
+/* pgmtopbm.c - read a portable graymap and write a portable bitmap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <assert.h>
+#include "pgm.h"
+#include "dithers.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+
+enum halftone {QT_FS, QT_THRESH, QT_DITHER8, QT_CLUSTER, QT_HILBERT};
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *  inputFilespec;
+    enum halftone halftone;
+    unsigned int  clumpSize;
+    unsigned int  clusterRadius;  
+        /* Defined only for halftone == QT_CLUSTER */
+    float         threshval;
+};
+
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int floydOpt, hilbertOpt, thresholdOpt, dither8Opt,
+        cluster3Opt, cluster4Opt, cluster8Opt;
+    unsigned int valueSpec, clumpSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "floyd",     OPT_FLAG,  NULL, &floydOpt,     0);
+    OPTENT3(0, "fs",        OPT_FLAG,  NULL, &floydOpt,     0);
+    OPTENT3(0, "threshold", OPT_FLAG,  NULL, &thresholdOpt, 0);
+    OPTENT3(0, "hilbert",   OPT_FLAG,  NULL, &hilbertOpt,   0);
+    OPTENT3(0, "dither8",   OPT_FLAG,  NULL, &dither8Opt,   0);
+    OPTENT3(0, "d8",        OPT_FLAG,  NULL, &dither8Opt,   0);
+    OPTENT3(0, "cluster3",  OPT_FLAG,  NULL, &cluster3Opt,  0);
+    OPTENT3(0, "c3",        OPT_FLAG,  NULL, &cluster3Opt,  0);
+    OPTENT3(0, "cluster4",  OPT_FLAG,  NULL, &cluster4Opt,  0);
+    OPTENT3(0, "c4",        OPT_FLAG,  NULL, &cluster4Opt,  0);
+    OPTENT3(0, "cluster8",  OPT_FLAG,  NULL, &cluster8Opt,  0);
+    OPTENT3(0, "c8",        OPT_FLAG,  NULL, &cluster8Opt,  0);
+    OPTENT3(0, "value",     OPT_FLOAT, &cmdlineP->threshval, 
+            &valueSpec, 0);
+    OPTENT3(0, "clump",     OPT_UINT,  &cmdlineP->clumpSize, 
+            &clumpSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (floydOpt + thresholdOpt + hilbertOpt + dither8Opt + 
+        cluster3Opt + cluster4Opt + cluster8Opt == 0)
+        cmdlineP->halftone = QT_FS;
+    else if (floydOpt + thresholdOpt + dither8Opt + 
+        cluster3Opt + cluster4Opt + cluster8Opt > 1)
+        pm_error("No cannot specify more than one halftoning type");
+    else {
+        if (floydOpt)
+            cmdlineP->halftone = QT_FS;
+        else if (thresholdOpt)
+            cmdlineP->halftone = QT_THRESH;
+        else if (hilbertOpt)
+            cmdlineP->halftone = QT_HILBERT;
+        else if (dither8Opt)
+            cmdlineP->halftone = QT_DITHER8;
+        else if (cluster3Opt) {
+            cmdlineP->halftone = QT_CLUSTER;
+            cmdlineP->clusterRadius = 3;
+        } else if (cluster4Opt) {
+            cmdlineP->halftone = QT_CLUSTER;
+            cmdlineP->clusterRadius = 4;
+        } else if (cluster8Opt) {
+            cmdlineP->halftone = QT_CLUSTER;
+            cmdlineP->clusterRadius = 8;
+        } else 
+            pm_error("INTERNAL ERROR.  No halftone option");
+    }
+
+    if (!valueSpec)
+        cmdlineP->threshval = 0.5;
+    else {
+        if (cmdlineP->threshval < 0.0)
+            pm_error("-value cannot be negative.  You specified %f",
+                     cmdlineP->threshval);
+        if (cmdlineP->threshval > 1.0)
+            pm_error("-value cannot be greater than one.  You specified %f",
+                     cmdlineP->threshval);
+    }
+            
+    if (!clumpSpec)
+        cmdlineP->clumpSize = 5;
+    else {
+        if (cmdlineP->clumpSize < 2)
+            pm_error("-clump must be at least 2.  You specified %u",
+                     cmdlineP->clumpSize);
+    }
+
+    if (argc-1 > 1)
+        pm_error("Too many arguments (%d).  There is at most one "
+                 "non-option argument:  the file name",
+                 argc-1);
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        cmdlineP->inputFilespec = "-";
+}
+
+
+
+/* Hilbert curve tracer */
+
+#define MAXORD 18
+
+static int hil_order,hil_ord;
+static int hil_turn;
+static int hil_dx,hil_dy;
+static int hil_x,hil_y;
+static int hil_stage[MAXORD];
+static int hil_width,hil_height;
+
+static void 
+init_hilbert(int const w, 
+             int const h) {
+/*----------------------------------------------------------------------------
+  Initialize the Hilbert curve tracer 
+-----------------------------------------------------------------------------*/
+    int big,ber;
+    hil_width = w;
+    hil_height = h;
+    big = w > h ? w : h;
+    for (ber = 2, hil_order = 1; ber < big; ber <<= 1, hil_order++);
+    if (hil_order > MAXORD)
+        pm_error("Sorry, hilbert order is too large");
+    hil_ord = hil_order;
+    hil_order--;
+}
+
+
+
+static int 
+hilbert(int * const px, int * const py) {
+/*----------------------------------------------------------------------------
+  Return non-zero if got another point
+-----------------------------------------------------------------------------*/
+    int temp;
+    if (hil_ord > hil_order) {
+        /* have to do first point */
+
+        hil_ord--;
+        hil_stage[hil_ord] = 0;
+        hil_turn = -1;
+        hil_dy = 1;
+        hil_dx = hil_x = hil_y = 0;
+        *px = *py = 0;
+        return 1;
+    }
+
+    /* Operate the state machine */
+    for(;;)  {
+        switch (hil_stage[hil_ord]) {
+        case 0:
+            hil_turn = -hil_turn;
+            temp = hil_dy;
+            hil_dy = -hil_turn * hil_dx;
+            hil_dx = hil_turn * temp;
+            if (hil_ord > 0) {
+                hil_stage[hil_ord] = 1;
+                hil_ord--;
+                hil_stage[hil_ord]=0;
+                continue;
+            }
+        case 1:
+            hil_x += hil_dx;
+            hil_y += hil_dy;
+            if (hil_x < hil_width && hil_y < hil_height) {
+                hil_stage[hil_ord] = 2;
+                *px = hil_x;
+                *py = hil_y;
+                return 1;
+            }
+        case 2:
+            hil_turn = -hil_turn;
+            temp = hil_dy;
+            hil_dy = -hil_turn * hil_dx;
+            hil_dx = hil_turn * temp;
+            if (hil_ord > 0) { 
+                /* recurse */
+
+                hil_stage[hil_ord] = 3;
+                hil_ord--;
+                hil_stage[hil_ord]=0;
+                continue;
+            }
+        case 3:
+            hil_x += hil_dx;
+            hil_y += hil_dy;
+            if (hil_x < hil_width && hil_y < hil_height) {
+                hil_stage[hil_ord] = 4;
+                *px = hil_x;
+                *py = hil_y;
+                return 1;
+            }
+        case 4:
+            if (hil_ord > 0) {
+                /* recurse */
+                hil_stage[hil_ord] = 5;
+                hil_ord--;
+                hil_stage[hil_ord]=0;
+                continue;
+            }
+        case 5:
+            temp = hil_dy;
+            hil_dy = -hil_turn * hil_dx;
+            hil_dx = hil_turn * temp;
+            hil_turn = -hil_turn;
+            hil_x += hil_dx;
+            hil_y += hil_dy;
+            if (hil_x < hil_width && hil_y < hil_height) {
+                hil_stage[hil_ord] = 6;
+                *px = hil_x;
+                *py = hil_y;
+                return 1;
+            }
+        case 6:
+            if (hil_ord > 0) {
+                /* recurse */
+                hil_stage[hil_ord] = 7;
+                hil_ord--;
+                hil_stage[hil_ord]=0;
+                continue;
+            }
+        case 7:
+            temp = hil_dy;
+            hil_dy = -hil_turn * hil_dx;
+            hil_dx = hil_turn * temp;
+            hil_turn = -hil_turn;
+            /* Return from a recursion */
+            if (hil_ord < hil_order)
+                hil_ord++;
+            else
+                return 0;
+        }
+    }
+}
+
+
+
+static void doHilbert(FILE *       const ifP,
+                      unsigned int const clump_size) {
+/*----------------------------------------------------------------------------
+  Use hilbert space filling curve dithering
+-----------------------------------------------------------------------------*/
+    /*
+     * This is taken from the article "Digital Halftoning with
+     * Space Filling Curves" by Luiz Velho, proceedings of
+     * SIGRAPH '91, page 81.
+     *
+     * This is not a terribly efficient or quick version of
+     * this algorithm, but it seems to work. - Graeme Gill.
+     * graeme@labtam.labtam.OZ.AU
+     *
+     */
+
+    int cols, rows;
+    gray maxval;
+    gray **grays;
+    bit **bits;
+    int end;
+    int *x,*y;
+    int sum = 0;
+
+    grays = pgm_readpgm(ifP, &cols,&rows, &maxval);
+    bits = pbm_allocarray(cols,rows);
+
+    MALLOCARRAY(x, clump_size);
+    MALLOCARRAY(y, clump_size);
+    if (x == NULL  || y == NULL)
+        pm_error("out of memory");
+    init_hilbert(cols,rows);
+
+    end = clump_size;
+    while (end == clump_size) {
+        int i;
+        /* compute the next clust co-ordinates along hilbert path */
+        for (i = 0; i < end; i++) {
+            if (hilbert(&x[i],&y[i])==0)
+                end = i;    /* we reached the end */
+        }
+        /* sum levels */
+        for (i = 0; i < end; i++)
+            sum += grays[y[i]][x[i]];
+        /* dither half and half along path */
+        for (i = 0; i < end; i++) {
+            if (sum >= maxval) {
+                bits[y[i]][x[i]] = PBM_WHITE;
+                sum -= maxval;
+            } else
+                bits[y[i]][x[i]] = PBM_BLACK;
+        }
+    }
+    pbm_writepbm(stdout, bits, cols, rows, 0);
+}
+
+
+
+struct converter {
+    void (*convertRow)(struct converter * const converterP,
+                       unsigned int       const row,
+                       gray                     grayrow[], 
+                       bit                      bitrow[]);
+    void (*destroy)(struct converter * const converterP);
+    unsigned int cols;
+    gray maxval;
+    void * stateP;
+};
+
+
+
+unsigned int const fs_scale      = 1024;
+unsigned int const half_fs_scale = 512;
+
+struct fsState {
+    long* thiserr;
+    long* nexterr;
+    bool fs_forward;
+    long threshval;  /* Threshold gray value, scaled by FS_SCALE */
+};
+
+
+static void
+fsConvertRow(struct converter * const converterP,
+             unsigned int       const row,
+             gray                     grayrow[],
+             bit                      bitrow[]) {
+
+    struct fsState * const stateP = converterP->stateP;
+
+    long * const thiserr = stateP->thiserr;
+    long * const nexterr = stateP->nexterr;
+
+    bit* bP;
+    gray* gP;
+
+    unsigned int limitcol;
+    unsigned int col;
+    
+    for (col = 0; col < converterP->cols + 2; ++col)
+        nexterr[col] = 0;
+    if (stateP->fs_forward) {
+        col = 0;
+        limitcol = converterP->cols;
+        gP = grayrow;
+        bP = bitrow;
+    } else {
+        col = converterP->cols - 1;
+        limitcol = -1;
+        gP = &(grayrow[col]);
+        bP = &(bitrow[col]);
+    }
+    do {
+        long sum;
+        sum = ((long) *gP * fs_scale) / converterP->maxval + 
+            thiserr[col + 1];
+        if (sum >= stateP->threshval) {
+            *bP = PBM_WHITE;
+            sum = sum - stateP->threshval - half_fs_scale;
+        } else
+            *bP = PBM_BLACK;
+        
+        if (stateP->fs_forward) {
+            thiserr[col + 2] += (sum * 7) / 16;
+            nexterr[col    ] += (sum * 3) / 16;
+            nexterr[col + 1] += (sum * 5) / 16;
+            nexterr[col + 2] += (sum    ) / 16;
+            
+            ++col;
+            ++gP;
+            ++bP;
+        } else {
+            thiserr[col    ] += (sum * 7) / 16;
+            nexterr[col + 2] += (sum * 3) / 16;
+            nexterr[col + 1] += (sum * 5) / 16;
+            nexterr[col    ] += (sum    ) / 16;
+            
+            --col;
+            --gP;
+            --bP;
+        }
+    } while (col != limitcol);
+    
+    stateP->thiserr = nexterr;
+    stateP->nexterr = thiserr;
+    stateP->fs_forward = ! stateP->fs_forward;
+}
+
+
+
+static void
+fsDestroy(struct converter * const converterP) {
+    free(converterP->stateP);
+}
+
+
+
+static struct converter
+createFsConverter(unsigned int const cols, 
+                  gray         const maxval,
+                  float        const threshFraction) {
+
+    struct fsState * stateP;
+    struct converter converter;
+
+    MALLOCVAR_NOFAIL(stateP);
+
+    /* Initialize Floyd-Steinberg error vectors. */
+    MALLOCARRAY_NOFAIL(stateP->thiserr, cols + 2);
+    MALLOCARRAY_NOFAIL(stateP->nexterr, cols + 2);
+    srand((int)(time(NULL) ^ getpid()));
+
+    {
+        /* (random errors in [-fs_scale/8 .. fs_scale/8]) */
+        unsigned int col;
+        for (col = 0; col < cols + 2; ++col)
+            stateP->thiserr[col] = 
+                (long)(rand() % fs_scale - half_fs_scale) / 4;
+    }
+
+    stateP->fs_forward = TRUE;
+    stateP->threshval = threshFraction * fs_scale;
+    converter.stateP = stateP;
+    converter.cols = cols;
+    converter.maxval = maxval;
+    converter.convertRow = &fsConvertRow;
+    converter.destroy = &fsDestroy;
+
+    return converter;
+}
+
+
+
+struct threshState {
+    gray threshval;
+};
+
+
+static void
+threshConvertRow(struct converter * const converterP,
+                 unsigned int       const row,
+                 gray                     grayrow[],
+                 bit                      bitrow[]) {
+    
+    struct threshState * const stateP = converterP->stateP;
+
+    unsigned int col;
+    for (col = 0; col < converterP->cols; ++col)
+        if (grayrow[col] >= stateP->threshval)
+            bitrow[col] = PBM_WHITE;
+        else
+            bitrow[col] = PBM_BLACK;
+}
+
+
+
+static void
+threshDestroy(struct converter * const converterP) {
+    free(converterP->stateP);
+}
+
+
+
+static struct converter
+createThreshConverter(unsigned int const cols, 
+                      gray         const maxval,
+                      float        const threshFraction) {
+
+    struct threshState * stateP;
+    struct converter converter;
+
+    MALLOCVAR_NOFAIL(stateP);
+
+    converter.cols       = cols;
+    converter.maxval     = maxval;
+    converter.convertRow = &threshConvertRow;
+    converter.destroy    = &threshDestroy;
+
+    stateP->threshval    = ROUNDU(maxval * threshFraction);
+    converter.stateP     = stateP;
+
+    return converter;
+}
+
+
+
+static void
+dither8ConvertRow(struct converter * const converterP,
+                  unsigned int       const row,
+                  gray                     grayrow[],
+                  bit                      bitrow[]) {
+
+    unsigned int col;
+
+    for (col = 0; col < converterP->cols; ++col)
+        if (grayrow[col] > dither8[row % 16][col % 16])
+            bitrow[col] = PBM_WHITE;
+        else
+            bitrow[col] = PBM_BLACK;
+}
+
+
+
+static struct converter
+createDither8Converter(unsigned int const cols, 
+                       gray         const maxval) {
+
+    struct converter converter;
+
+    unsigned int row;
+
+    converter.cols = cols;
+    converter.convertRow = &dither8ConvertRow;
+    converter.destroy = NULL;
+
+    /* Scale dither matrix. */
+    for (row = 0; row < 16; ++row) {
+        unsigned int col;
+        for (col = 0; col < 16; ++col)
+            dither8[row][col] = dither8[row][col] * maxval / 256;
+    }
+    return converter;
+}
+
+
+
+struct clusterState {
+    unsigned int radius;
+    int ** clusterMatrix;
+};
+
+
+
+static void
+clusterConvertRow(struct converter * const converterP,
+                  unsigned int       const row,
+                  gray                     grayrow[],
+                  bit                      bitrow[]) {
+
+    struct clusterState * const stateP = converterP->stateP;
+    unsigned int const diameter = 2 * stateP->radius;
+
+    unsigned int col;
+
+    for (col = 0; col < converterP->cols; ++col)
+        if (grayrow[col] >
+            stateP->clusterMatrix[row % diameter][col % diameter])
+            bitrow[col] = PBM_WHITE;
+        else
+            bitrow[col] = PBM_BLACK;
+}
+
+
+
+static void
+clusterDestroy(struct converter * const converterP) {
+
+    struct clusterState * const stateP = converterP->stateP;
+    unsigned int const diameter = 2 * stateP->radius;
+
+    unsigned int row;
+
+    for (row = 0; row < diameter; ++row)
+        free(stateP->clusterMatrix[row]);
+
+    free(stateP->clusterMatrix);
+    
+    free(stateP);
+}
+
+
+
+static struct converter
+createClusterConverter(unsigned int const radius,
+                       unsigned int const cols, 
+                       gray         const maxval) {
+    
+    int const clusterNormalizer = radius * radius * 2;
+    unsigned int const diameter = 2 * radius;
+
+    struct converter converter;
+    struct clusterState * stateP;
+    unsigned int row;
+
+    converter.cols = cols;
+    converter.convertRow = &clusterConvertRow;
+    converter.destroy = &clusterDestroy;
+
+    MALLOCVAR_NOFAIL(stateP);
+
+    stateP->radius = radius;
+
+    MALLOCARRAY_NOFAIL(stateP->clusterMatrix, diameter);
+    for (row = 0; row < diameter; ++row) {
+        unsigned int col;
+
+        MALLOCARRAY_NOFAIL(stateP->clusterMatrix[row], diameter);
+        
+        for (col = 0; col < diameter; ++col) {
+            int val;
+            switch (radius) {
+            case 3: val = cluster3[row][col]; break;
+            case 4: val = cluster4[row][col]; break;
+            case 8: val = cluster8[row][col]; break;
+            default:
+                pm_error("INTERNAL ERROR: invalid radius");
+            }
+            stateP->clusterMatrix[row][col] = val * maxval / clusterNormalizer;
+        }
+    }            
+
+    converter.stateP = stateP;
+
+    return converter;
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE* ifP;
+    gray* grayrow;
+    bit* bitrow;
+
+    pgm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    if (cmdline.halftone == QT_HILBERT)
+        doHilbert(ifP, cmdline.clumpSize);
+    else {
+        struct converter converter;
+        int cols, rows;
+        gray maxval;
+        int format;
+        int row;
+
+        pgm_readpgminit(ifP, &cols, &rows, &maxval, &format);
+        
+        pbm_writepbminit(stdout, cols, rows, 0);
+
+        switch (cmdline.halftone) {
+        case QT_FS:
+            converter = createFsConverter(cols, maxval, cmdline.threshval);
+            break;
+        case QT_THRESH:
+            converter = createThreshConverter(cols, maxval, cmdline.threshval);
+            break;
+        case QT_DITHER8: 
+            converter = createDither8Converter(cols, maxval); 
+            break;
+        case QT_CLUSTER: 
+            converter = 
+                createClusterConverter(cmdline.clusterRadius, cols, maxval);
+            break;
+        case QT_HILBERT: 
+                pm_error("INTERNAL ERROR: halftone is QT_HILBERT where it "
+                         "shouldn't be.");
+                break;
+        }
+
+        grayrow = pgm_allocrow(cols);
+        bitrow  = pbm_allocrow(cols);
+
+        for (row = 0; row < rows; ++row) {
+            pgm_readpgmrow(ifP, grayrow, cols, maxval, format);
+
+            converter.convertRow(&converter, row, grayrow, bitrow);
+            
+            pbm_writepbmrow(stdout, bitrow, cols, 0);
+        }
+        pbm_freerow(bitrow);
+        pgm_freerow(grayrow);
+
+        if (converter.destroy)
+            converter.destroy(&converter);
+    }
+
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/converter/other/pgmtoppm.c b/converter/other/pgmtoppm.c
new file mode 100644
index 00000000..c695ddd5
--- /dev/null
+++ b/converter/other/pgmtoppm.c
@@ -0,0 +1,153 @@
+/* pgmtoppm.c - colorize a portable graymap into a portable pixmap
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+#include "ppm.h"
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    gray* grayrow;
+    register gray* gP;
+    pixel p;
+    pixel* pixelrow;
+    register pixel* pP;
+    pixel** mappixels;
+    int argn, rows, cols, format, maprows, mapcols, mapmaxcolor, row;
+    register int col;
+    gray maxval;
+    pixval mapmaxval;
+    char* color0;
+    char* color1;
+    pixval red0, grn0, blu0, red1, grn1, blu1;
+    const char* const usage = "<colorspec> [pgmfile]\n                 <colorspec1>,<colorspec2> [pgmfile]\n                 -map mapfile [pgmfile]";
+
+
+    ppm_init( &argc, argv );
+
+    argn = 1;
+    mappixels = (pixel**) 0;
+
+    if ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+	{
+	if ( pm_keymatch( argv[argn], "-map", 2 ) )
+	    {
+	    ++argn;
+	    if ( argn == argc )
+		pm_usage( usage );
+	    ifp = pm_openr( argv[argn] );
+	    mappixels = ppm_readppm( ifp, &mapcols, &maprows, &mapmaxval );
+	    pm_close( ifp );
+	    mapmaxcolor = maprows * mapcols - 1;
+	    }
+	else
+	    pm_usage( usage );
+	++argn;
+	}
+
+    if ( mappixels == (pixel**) 0 )
+	{
+	if ( argn == argc )
+	    pm_usage( usage );
+	color0 = argv[argn];
+	++argn;
+	}
+
+    if ( argn != argc )
+	{
+	ifp = pm_openr( argv[argn] );
+	++argn;
+	}
+    else
+	ifp = stdin;
+
+    if ( argn != argc )
+	pm_usage( usage );
+
+    pgm_readpgminit( ifp, &cols, &rows, &maxval, &format );
+    grayrow = pgm_allocrow( cols );
+    if ( mappixels == (pixel**) 0 )
+	ppm_writeppminit( stdout, cols, rows, (pixval) maxval, 0 );
+    else
+	ppm_writeppminit( stdout, cols, rows, mapmaxval, 0 );
+    pixelrow = ppm_allocrow( cols );
+
+    if ( mappixels == (pixel**) 0 )
+	{
+	color1 = strchr( color0, '-' );
+	if ( color1 == 0 )
+	    {
+	    color1 = color0;
+	    red0 = 0;
+	    grn0 = 0;
+	    blu0 = 0;
+	    }
+	else
+	    {
+	    *color1 = '\0';
+	    ++color1;
+	    p = ppm_parsecolor( color0, (pixval) maxval );
+	    red0 = PPM_GETR( p );
+	    grn0 = PPM_GETG( p );
+	    blu0 = PPM_GETB( p );
+	    }
+	p = ppm_parsecolor( color1, (pixval) maxval );
+	red1 = PPM_GETR( p );
+	grn1 = PPM_GETG( p );
+	blu1 = PPM_GETB( p );
+	}
+
+    for ( row = 0; row < rows; ++row )
+	{
+	pgm_readpgmrow( ifp, grayrow, cols, maxval, format );
+
+	if ( mappixels == (pixel**) 0 )
+	    {
+	    for ( col = 0, gP = grayrow, pP = pixelrow;
+		  col < cols;
+		  ++col, ++gP, ++pP )
+		PPM_ASSIGN(
+		    *pP,
+		    ( red0 * ( maxval - *gP ) + red1 * *gP ) / maxval,
+		    ( grn0 * ( maxval - *gP ) + grn1 * *gP ) / maxval,
+		    ( blu0 * ( maxval - *gP ) + blu1 * *gP ) / maxval );
+
+	    }
+	else
+	    {
+	    register int c;
+
+	    for ( col = 0, gP = grayrow, pP = pixelrow;
+		  col < cols;
+		  ++col, ++gP, ++pP )
+		{
+		if ( maxval == mapmaxcolor )
+		    c = *gP;
+		else
+		    c = *gP * mapmaxcolor / maxval;
+		*pP = mappixels[c / mapcols][c % mapcols];
+		}
+	    }
+
+	ppm_writeppmrow( stdout, pixelrow, cols, (pixval) maxval, 0 );
+	}
+
+    pm_close( ifp );
+
+    /* If the program failed, it previously aborted with nonzero completion
+       code, via various function calls.
+    */
+    return 0;
+    }
diff --git a/converter/other/pm_tiff.h b/converter/other/pm_tiff.h
new file mode 100644
index 00000000..f98110e3
--- /dev/null
+++ b/converter/other/pm_tiff.h
@@ -0,0 +1,41 @@
+#ifndef PM_TIFF_H_INCLUDED
+#define PM_TIFF_H_INCLUDED
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   This is an association between a tag value name and the integer that
+   represents the tag value in the TIFF.
+
+   E.g. for an ORIENTATION tag, the value named "TOPLEFT" is represented
+   by the integer 1.
+-----------------------------------------------------------------------------*/
+    const char *  name;
+    unsigned long value;
+} tagvalmap;
+
+typedef struct tagDefinition {
+/*----------------------------------------------------------------------------
+   This is the definition of a type of tag, e.g. ORIENTATION.
+-----------------------------------------------------------------------------*/
+    const char * name;
+        /* The name by which our user knows the tag type, e.g. 
+           "ORIENTATION"
+        */
+    unsigned int tagnum;
+        /* The integer by which libtiff knows the tag type, e.g.
+           TIFFTAG_ORIENTATION
+        */
+    void (*      put)(TIFF *, unsigned int, const char *, const tagvalmap *);
+    const tagvalmap * choices;
+        /* List of the possible values for the tag, if it is one with
+           enumerated values.  e.g. for ORIENTATION, it's TOPLEFT,
+           TOPRIGHT, etc.
+        */
+} tagDefinition;
+
+
+
+const tagDefinition *
+tagDefFind(const char * const name);
+
+#endif
diff --git a/converter/other/pngtopnm.c b/converter/other/pngtopnm.c
new file mode 100644
index 00000000..bb8afb8d
--- /dev/null
+++ b/converter/other/pngtopnm.c
@@ -0,0 +1,1096 @@
+/*
+** pngtopnm.c -
+** read a Portable Network Graphics file and produce a portable anymap
+**
+** Copyright (C) 1995,1998 by Alexander Lehmann <alex@hal.rhein-main.de>
+**                        and Willem van Schaik <willem@schaik.com>
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** modeled after giftopnm by David Koblas and
+** with lots of bits pasted from libpng.txt by Guy Eric Schalnat
+*/
+
+/* 
+   BJH 20000408:  rename PPM_MAXMAXVAL to PPM_OVERALLMAXVAL
+   BJH 20000303:  fix include statement so dependencies work out right.
+*/
+/* GRR 19991203:  moved VERSION to new version.h header file */
+
+/* GRR 19990713:  fixed redundant freeing of png_ptr and info_ptr in setjmp()
+ *  blocks and added "pm_close(ifp)" in each.  */
+
+/* GRR 19990317:  declared "clobberable" automatic variables in convertpng()
+ *  static to fix Solaris/gcc stack-corruption bug.  Also installed custom
+ *  error-handler to avoid jmp_buf size-related problems (i.e., jmp_buf
+ *  compiled with one size in libpng and another size here).  */
+
+#ifndef PNMTOPNG_WARNING_LEVEL
+#  define PNMTOPNG_WARNING_LEVEL 0   /* use 0 for backward compatibility, */
+#endif                               /*  2 for warnings (1 == error) */
+
+#include <math.h>
+#include <png.h>    /* includes zlib.h and setjmp.h */
+#define VERSION "2.37.4 (5 December 1999) +netpbm"
+
+#include "pnm.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+
+typedef struct _jmpbuf_wrapper {
+  jmp_buf jmpbuf;
+} jmpbuf_wrapper;
+
+/* GRR 19991205:  this is used as a test for pre-1999 versions of netpbm and
+ *   pbmplus vs. 1999 or later (in which pm_close was split into two)
+ */
+#ifdef PBMPLUS_RAWBITS
+#  define pm_closer pm_close
+#  define pm_closew pm_close
+#endif
+
+#ifndef TRUE
+#  define TRUE 1
+#endif
+#ifndef FALSE
+#  define FALSE 0
+#endif
+#ifndef NONE
+#  define NONE 0
+#endif
+
+enum alpha_handling {ALPHA_NONE, ALPHA_ONLY, ALPHA_MIX};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* '-' if stdin */
+    unsigned int verbose;
+    enum alpha_handling alpha;
+    const char * background;
+    float gamma;  /* -1.0 means unspecified */
+    const char * text;
+    unsigned int time;
+};
+
+
+typedef struct pngcolor {
+/*----------------------------------------------------------------------------
+   A color in a format compatible with the PNG library.
+
+   Note that the PNG library declares types png_color and png_color_16
+   which are similar.
+-----------------------------------------------------------------------------*/
+    png_uint_16 r;
+    png_uint_16 g;
+    png_uint_16 b;
+} pngcolor;
+
+
+static png_uint_16 maxval;
+static int verbose = FALSE;
+static int mtime;
+static jmpbuf_wrapper pngtopnm_jmpbuf_struct;
+
+
+static void
+parseCommandLine(int                 argc, 
+                 char **             argv,
+                 struct cmdlineInfo *cmdlineP ) {
+/*----------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int alphaSpec, mixSpec, backgroundSpec, gammaSpec, textSpec;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "verbose",     OPT_FLAG,   NULL,                  
+            &cmdlineP->verbose,       0);
+    OPTENT3(0, "alpha",       OPT_FLAG,   NULL,                  
+            &alphaSpec,               0);
+    OPTENT3(0, "mix",         OPT_FLAG,   NULL,                  
+            &mixSpec,                 0);
+    OPTENT3(0, "background",  OPT_STRING, &cmdlineP->background,
+            &backgroundSpec,          0);
+    OPTENT3(0, "gamma",       OPT_FLOAT,  &cmdlineP->gamma,
+            &gammaSpec,               0);
+    OPTENT3(0, "text",        OPT_STRING, &cmdlineP->text,
+            &textSpec,                0);
+    OPTENT3(0, "time",        OPT_FLAG,   NULL,                  
+            &cmdlineP->time,          0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+
+    if (alphaSpec && mixSpec)
+        pm_error("You cannot specify both -alpha and -mix");
+    else if (alphaSpec)
+        cmdlineP->alpha = ALPHA_ONLY;
+    else if (mixSpec)
+        cmdlineP->alpha = ALPHA_MIX;
+    else
+        cmdlineP->alpha = ALPHA_NONE;
+
+    if (backgroundSpec && !mixSpec)
+        pm_error("-background is useless without -mix");
+
+    if (!backgroundSpec)
+        cmdlineP->background = NULL;
+
+    if (!gammaSpec)
+        cmdlineP->gamma = -1.0;
+
+    if (!textSpec)
+        cmdlineP->text = NULL;
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        pm_error("Program takes at most one argument: input file name.  "
+            "you specified %d", argc-1);
+}
+
+
+
+
+#define get_png_val(p) _get_png_val (&(p), info_ptr->bit_depth)
+
+static png_uint_16
+_get_png_val (png_byte ** const pp,
+              int         const bit_depth) {
+
+    png_uint_16 c;
+    
+    if (bit_depth == 16)
+        c = (*((*pp)++)) << 8;
+    else
+        c = 0;
+
+    c |= (*((*pp)++));
+    
+    return c;
+}
+
+
+
+static void 
+setXel(xel *               const xelP, 
+       pngcolor            const foreground,
+       pngcolor            const background,
+       enum alpha_handling const alpha_handling,
+       png_uint_16         const alpha) {
+
+    if (alpha_handling == ALPHA_ONLY) {
+        PNM_ASSIGN1(*xelP, alpha);
+    } else {
+        if ((alpha_handling == ALPHA_MIX) && (alpha != maxval)) {
+            double const opacity      = (double)alpha / maxval;
+            double const transparency = 1.0 - opacity;
+
+            pngcolor mix;
+
+            mix.r = foreground.r * opacity + background.r * transparency + 0.5;
+            mix.g = foreground.g * opacity + background.g * transparency + 0.5;
+            mix.b = foreground.b * opacity + background.b * transparency + 0.5;
+            PPM_ASSIGN(*xelP, mix.r, mix.g, mix.b);
+        } else
+            PPM_ASSIGN(*xelP, foreground.r, foreground.g, foreground.b);
+    }
+}
+
+
+
+static png_uint_16
+gamma_correct(png_uint_16 const v,
+              float       const g) {
+
+    if (g != -1.0)
+        return (png_uint_16) (pow ((double) v / maxval, 
+                                   (1.0 / g)) * maxval + 0.5);
+    else
+        return v;
+}
+
+
+
+#ifdef __STDC__
+static int iscolor (png_color c)
+#else
+static int iscolor (c)
+png_color c;
+#endif
+{
+  return c.red != c.green || c.green != c.blue;
+}
+
+#ifdef __STDC__
+static void save_text (png_info *info_ptr, FILE *tfp)
+#else
+static void save_text (info_ptr, tfp)
+png_info *info_ptr;
+FILE *tfp;
+#endif
+{
+  int i, j, k;
+
+  for (i = 0 ; i < info_ptr->num_text ; i++) {
+    j = 0;
+    while (info_ptr->text[i].key[j] != '\0' && info_ptr->text[i].key[j] != ' ')
+      j++;    
+    if (info_ptr->text[i].key[j] != ' ') {
+      fprintf (tfp, "%s", info_ptr->text[i].key);
+      for (j = strlen (info_ptr->text[i].key) ; j < 15 ; j++)
+        putc (' ', tfp);
+    } else {
+      fprintf (tfp, "\"%s\"", info_ptr->text[i].key);
+      for (j = strlen (info_ptr->text[i].key) ; j < 13 ; j++)
+        putc (' ', tfp);
+    }
+    putc (' ', tfp); /* at least one space between key and text */
+    
+    for (j = 0 ; j < info_ptr->text[i].text_length ; j++) {
+      putc (info_ptr->text[i].text[j], tfp);
+      if (info_ptr->text[i].text[j] == '\n')
+        for (k = 0 ; k < 16 ; k++)
+          putc ((int)' ', tfp);
+    }
+    putc ((int)'\n', tfp);
+  }
+}
+
+#ifdef __STDC__
+static void show_time (png_info *info_ptr)
+#else
+static void show_time (info_ptr)
+png_info *info_ptr;
+#endif
+{
+    static const char * const month[] = {
+        "", "January", "February", "March", "April", "May", "June",
+        "July", "August", "September", "October", "November", "December"
+    };
+
+  if (info_ptr->valid & PNG_INFO_tIME) {
+    pm_message ("modification time: %02d %s %d %02d:%02d:%02d",
+                info_ptr->mod_time.day, month[info_ptr->mod_time.month],
+                info_ptr->mod_time.year, info_ptr->mod_time.hour,
+                info_ptr->mod_time.minute, info_ptr->mod_time.second);
+  }
+}
+
+#ifdef __STDC__
+static void pngtopnm_error_handler (png_structp png_ptr, png_const_charp msg)
+#else
+static void pngtopnm_error_handler (png_ptr, msg)
+png_structp png_ptr;
+png_const_charp msg;
+#endif
+{
+  jmpbuf_wrapper  *jmpbuf_ptr;
+
+  /* this function, aside from the extra step of retrieving the "error
+   * pointer" (below) and the fact that it exists within the application
+   * rather than within libpng, is essentially identical to libpng's
+   * default error handler.  The second point is critical:  since both
+   * setjmp() and longjmp() are called from the same code, they are
+   * guaranteed to have compatible notions of how big a jmp_buf is,
+   * regardless of whether _BSD_SOURCE or anything else has (or has not)
+   * been defined. */
+
+  pm_message("fatal libpng error: %s", msg);
+
+  jmpbuf_ptr = png_get_error_ptr(png_ptr);
+  if (jmpbuf_ptr == NULL) {
+      /* we are completely hosed now */
+      pm_error("EXTREMELY fatal error: jmpbuf unrecoverable; terminating.");
+  }
+
+  longjmp(jmpbuf_ptr->jmpbuf, 1);
+}
+
+
+
+static void
+dump_png_info(png_info *info_ptr) {
+
+    const char *type_string;
+    const char *filter_string;
+
+    switch (info_ptr->color_type) {
+      case PNG_COLOR_TYPE_GRAY:
+        type_string = "gray";
+        break;
+
+      case PNG_COLOR_TYPE_GRAY_ALPHA:
+        type_string = "gray+alpha";
+        break;
+
+      case PNG_COLOR_TYPE_PALETTE:
+        type_string = "palette";
+        break;
+
+      case PNG_COLOR_TYPE_RGB:
+        type_string = "truecolor";
+        break;
+
+      case PNG_COLOR_TYPE_RGB_ALPHA:
+        type_string = "truecolor+alpha";
+        break;
+    }
+
+    switch (info_ptr->filter_type) {
+    case PNG_FILTER_TYPE_BASE:
+        asprintfN(&filter_string, "base filter");
+        break;
+    default:
+        asprintfN(&filter_string, "unknown filter type %d", 
+                  info_ptr->filter_type);
+    }
+
+    pm_message("reading a %ldw x %ldh image, %d bit%s",
+               info_ptr->width, info_ptr->height,
+               info_ptr->bit_depth, info_ptr->bit_depth > 1 ? "s" : "");
+    pm_message("%s, %s, %s",
+               type_string,
+               info_ptr->interlace_type ? 
+               "Adam7 interlaced" : "not interlaced",
+               filter_string);
+    pm_message("background {index, gray, red, green, blue} = "
+               "{%d, %d, %d, %d, %d}",
+               info_ptr->background.index,
+               info_ptr->background.gray,
+               info_ptr->background.red,
+               info_ptr->background.green,
+               info_ptr->background.blue);
+
+    strfree(filter_string);
+
+    if (info_ptr->valid & PNG_INFO_tRNS)
+        pm_message("tRNS chunk (transparency): %u entries",
+                   info_ptr->num_trans);
+    else
+        pm_message("tRNS chunk (transparency): not present");
+
+    if (info_ptr->valid & PNG_INFO_gAMA)
+        pm_message("gAMA chunk (image gamma): gamma = %4.2f", info_ptr->gamma);
+    else
+        pm_message("gAMA chunk (image gamma): not present");
+
+    if (info_ptr->valid & PNG_INFO_sBIT)
+        pm_message("sBIT chunk: present");
+    else
+        pm_message("sBIT chunk: not present");
+
+    if (info_ptr->valid & PNG_INFO_cHRM)
+        pm_message("cHRM chunk: present");
+    else
+        pm_message("cHRM chunk: not present");
+
+    if (info_ptr->valid & PNG_INFO_PLTE)
+        pm_message("PLTE chunk: %d entries", info_ptr->num_palette);
+    else
+        pm_message("PLTE chunk: not present");
+
+    if (info_ptr->valid & PNG_INFO_bKGD)
+        pm_message("bKGD chunk: present");
+    else
+        pm_message("bKGD chunk: not present");
+
+    if (info_ptr->valid & PNG_INFO_PLTE)
+        pm_message("hIST chunk: present");
+    else
+        pm_message("hIST chunk: not present");
+
+    if (info_ptr->valid & PNG_INFO_pHYs)
+        pm_message("pHYs chunk: present");
+    else
+        pm_message("pHYs chunk: not present");
+
+    if (info_ptr->valid & PNG_INFO_oFFs)
+        pm_message("oFFs chunk: present");
+    else
+        pm_message("oFFs chunk: not present");
+
+    if (info_ptr->valid & PNG_INFO_tIME)
+        pm_message("tIME chunk: present");
+    else
+        pm_message("tIME chunk: not present");
+
+    if (info_ptr->valid & PNG_INFO_pCAL)
+        pm_message("pCAL chunk: present");
+    else
+        pm_message("pCAL chunk: not present");
+
+    if (info_ptr->valid & PNG_INFO_sRGB)
+        pm_message("sRGB chunk: present");
+    else
+        pm_message("sRGB chunk: not present");
+}
+
+
+
+static bool
+isTransparentColor(pngcolor   const color,
+                   png_info * const info_ptr,
+                   double     const totalgamma) {
+/*----------------------------------------------------------------------------
+   Return TRUE iff pixels of color 'color' are supposed to be transparent
+   everywhere they occur.  Assume it's an RGB image.
+-----------------------------------------------------------------------------*/
+    bool retval;
+
+    if (info_ptr->valid & PNG_INFO_tRNS) {
+        const png_color_16 * const transColorP = &info_ptr->trans_values;
+    
+
+        /* There seems to be a problem here: you can't compare real
+           numbers for equality.  Also, I'm not sure the gamma
+           corrected/uncorrected color spaces are right here.  
+        */
+
+        retval = 
+            color.r == gamma_correct(transColorP->red,   totalgamma) &&
+            color.g == gamma_correct(transColorP->green, totalgamma) &&
+            color.b == gamma_correct(transColorP->blue,  totalgamma);
+    } else 
+        retval = FALSE;
+
+    return retval;
+}
+
+
+
+#define SIG_CHECK_SIZE 4
+
+static void
+read_sig_buf(FILE * const ifP) {
+
+    unsigned char sig_buf[SIG_CHECK_SIZE];
+    size_t bytesRead;
+
+    bytesRead = fread(sig_buf, 1, SIG_CHECK_SIZE, ifP);
+    if (bytesRead != SIG_CHECK_SIZE)
+        pm_error ("input file is empty or too short");
+
+    if (png_sig_cmp(sig_buf, (png_size_t) 0, (png_size_t) SIG_CHECK_SIZE)
+        != 0)
+        pm_error ("input file is not a PNG file");
+}
+
+
+
+static void
+setupGammaCorrection(png_struct * const png_ptr,
+                     png_info *   const info_ptr,
+                     float        const displaygamma,
+                     float *      const totalgammaP) {
+
+    if (displaygamma == -1.0)
+        *totalgammaP = -1.0;
+    else {
+        if (info_ptr->valid & PNG_INFO_gAMA) {
+            if (displaygamma != info_ptr->gamma) {
+                png_set_gamma(png_ptr, displaygamma, info_ptr->gamma);
+                *totalgammaP =
+                    (double) info_ptr->gamma * (double) displaygamma;
+                /* in case of gamma-corrections, sBIT's as in the
+                   PNG-file are not valid anymore 
+                */
+                info_ptr->valid &= ~PNG_INFO_sBIT;
+                if (verbose)
+                    pm_message("image gamma is %4.2f, "
+                               "converted for display gamma of %4.2f",
+                               info_ptr->gamma, displaygamma);
+            }
+        } else {
+            if (displaygamma != info_ptr->gamma) {
+                png_set_gamma (png_ptr, displaygamma, 1.0);
+                *totalgammaP = (double) displaygamma;
+                info_ptr->valid &= ~PNG_INFO_sBIT;
+                if (verbose)
+                    pm_message("image gamma assumed 1.0, "
+                               "converted for display gamma of %4.2f",
+                               displaygamma);
+            }
+        }
+    }
+}
+
+
+
+static bool
+paletteHasPartialTransparency(png_info * const info_ptr) {
+
+    bool retval;
+
+    if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) {
+        if (info_ptr->valid & PNG_INFO_tRNS) {
+            bool foundGray;
+            unsigned int i;
+            
+            for (i = 0, foundGray = FALSE;
+                 i < info_ptr->num_trans && !foundGray;
+                 ++i) {
+                if (info_ptr->trans[i] != 0 &&
+                    info_ptr->trans[i] != maxval) {
+                    foundGray = TRUE;
+                }
+            }
+            retval = foundGray;
+        } else
+            retval = FALSE;
+    } else
+        retval = FALSE;
+
+    return retval;
+}
+
+
+
+static void
+setupSignificantBits(png_struct *        const png_ptr,
+                     png_info *          const info_ptr,
+                     enum alpha_handling const alpha,
+                     png_uint_16 *       const maxvalP,
+                     int *               const errorlevelP) {
+/*----------------------------------------------------------------------------
+  Figure out what maxval would best express the information in the PNG
+  described by 'png_ptr' and 'info_ptr', with 'alpha' telling which
+  information in the PNG we care about (image or alpha mask).
+
+  Return the result as *maxvalP.
+-----------------------------------------------------------------------------*/
+    /* Initial assumption of maxval */
+    if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) {
+        if (alpha == ALPHA_ONLY) {
+            if (info_ptr->color_type == PNG_COLOR_TYPE_GRAY ||
+                info_ptr->color_type == PNG_COLOR_TYPE_RGB)
+                /* The alpha mask will be all opaque, so maxval 1 is plenty */
+                *maxvalP = 1;
+            else if (paletteHasPartialTransparency(info_ptr))
+                /* Use same maxval as PNG transparency palette for simplicity*/
+                *maxvalP = 255;
+            else
+                /* A common case, so we conserve bits */
+                *maxvalP = 1;
+        } else
+            /* Use same maxval as PNG palette for simplicity */
+            *maxvalP = 255;
+    } else {
+        *maxvalP = (1l << info_ptr->bit_depth) - 1;
+    }
+
+    /* sBIT handling is very tricky. If we are extracting only the
+       image, we can use the sBIT info for grayscale and color images,
+       if the three values agree. If we extract the transparency/alpha
+       mask, sBIT is irrelevant for trans and valid for alpha. If we
+       mix both, the multiplication may result in values that require
+       the normal bit depth, so we will use the sBIT info only for
+       transparency, if we know that only solid and fully transparent
+       is used 
+    */
+    
+    if (info_ptr->valid & PNG_INFO_sBIT) {
+        switch (alpha) {
+        case ALPHA_MIX:
+            if (info_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA ||
+                info_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+                break;
+            if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE &&
+                (info_ptr->valid & PNG_INFO_tRNS)) {
+
+                bool trans_mix;
+                unsigned int i;
+                trans_mix = TRUE;
+                for (i = 0; i < info_ptr->num_trans; ++i)
+                    if (info_ptr->trans[i] != 0 && info_ptr->trans[i] != 255) {
+                        trans_mix = FALSE;
+                        break;
+                    }
+                if (!trans_mix)
+                    break;
+            }
+
+            /* else fall though to normal case */
+
+        case ALPHA_NONE:
+            if ((info_ptr->color_type == PNG_COLOR_TYPE_PALETTE ||
+                 info_ptr->color_type == PNG_COLOR_TYPE_RGB ||
+                 info_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA) &&
+                (info_ptr->sig_bit.red != info_ptr->sig_bit.green ||
+                 info_ptr->sig_bit.red != info_ptr->sig_bit.blue) &&
+                alpha == ALPHA_NONE) {
+                pm_message("This program cannot handle "
+                           "different bit depths for color channels");
+                pm_message("writing file with %d bit resolution",
+                           info_ptr->bit_depth);
+                *errorlevelP = PNMTOPNG_WARNING_LEVEL;
+            } else {
+                if ((info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) &&
+                    (info_ptr->sig_bit.red < 255)) {
+                    unsigned int i;
+                    for (i = 0; i < info_ptr->num_palette; ++i) {
+                        info_ptr->palette[i].red   >>=
+                            (8 - info_ptr->sig_bit.red);
+                        info_ptr->palette[i].green >>=
+                            (8 - info_ptr->sig_bit.green);
+                        info_ptr->palette[i].blue  >>=
+                            (8 - info_ptr->sig_bit.blue);
+                    }
+                    *maxvalP = (1l << info_ptr->sig_bit.red) - 1;
+                    if (verbose)
+                        pm_message ("image has fewer significant bits, "
+                                    "writing file with %d bits per channel", 
+                                    info_ptr->sig_bit.red);
+                } else
+                    if ((info_ptr->color_type == PNG_COLOR_TYPE_RGB ||
+                         info_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA) &&
+                        (info_ptr->sig_bit.red < info_ptr->bit_depth)) {
+                        png_set_shift (png_ptr, &(info_ptr->sig_bit));
+                        *maxvalP = (1l << info_ptr->sig_bit.red) - 1;
+                        if (verbose)
+                            pm_message("image has fewer significant bits, "
+                                       "writing file with %d "
+                                       "bits per channel", 
+                                       info_ptr->sig_bit.red);
+                    } else 
+                        if ((info_ptr->color_type == PNG_COLOR_TYPE_GRAY ||
+                             info_ptr->color_type ==
+                                 PNG_COLOR_TYPE_GRAY_ALPHA) &&
+                            (info_ptr->sig_bit.gray < info_ptr->bit_depth)) {
+                            png_set_shift (png_ptr, &(info_ptr->sig_bit));
+                            *maxvalP = (1l << info_ptr->sig_bit.gray) - 1;
+                            if (verbose)
+                                pm_message("image has fewer significant bits, "
+                                           "writing file with %d bits",
+                                           info_ptr->sig_bit.gray);
+                        }
+            }
+            break;
+
+        case ALPHA_ONLY:
+            if ((info_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA ||
+                 info_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) && 
+                (info_ptr->sig_bit.gray < info_ptr->bit_depth)) {
+                png_set_shift (png_ptr, &(info_ptr->sig_bit));
+                if (verbose)
+                    pm_message ("image has fewer significant bits, "
+                                "writing file with %d bits", 
+                                info_ptr->sig_bit.alpha);
+                *maxvalP = (1l << info_ptr->sig_bit.alpha) - 1;
+            }
+            break;
+
+        }
+    }
+}
+
+
+
+static bool
+imageHasColor(png_info * const info_ptr) {
+
+    bool retval;
+
+    if (info_ptr->color_type == PNG_COLOR_TYPE_GRAY ||
+        info_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+
+        retval = FALSE;
+    else if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) {
+        bool foundColor;
+        unsigned int i;
+            
+        for (i = 0, foundColor = FALSE;
+             i < info_ptr->num_palette && !foundColor;
+             ++i) {
+            if (iscolor(info_ptr->palette[i]))
+                foundColor = TRUE;
+        }
+        retval = foundColor;
+    } else
+        retval = TRUE;
+
+    return retval;
+}
+
+
+
+static void
+determineOutputType(png_info *          const info_ptr,
+                    enum alpha_handling const alphaHandling,
+                    xelval              const maxval,
+                    int *               const pnmTypeP) {
+
+    if (alphaHandling != ALPHA_ONLY && imageHasColor(info_ptr))
+        *pnmTypeP = PPM_TYPE;
+    else {
+        if (maxval > 1)
+            *pnmTypeP = PGM_TYPE;
+        else
+            *pnmTypeP = PBM_TYPE;
+    }
+}
+
+
+
+static void
+getBackgroundColor(png_info *        const info_ptr,
+                   const char *      const requestedColor,
+                   float             const totalgamma,
+                   xelval            const maxval,
+                   struct pngcolor * const bgColorP) {
+/*----------------------------------------------------------------------------
+   Figure out what the background color should be.  If the user requested
+   a particular color ('requestedColor' not null), that's the one.
+   Otherwise, if the PNG specifies a background color, that's the one.
+   And otherwise, it's white.
+-----------------------------------------------------------------------------*/
+    if (requestedColor) {
+        /* Background was specified from the command-line; we always
+           use that.  I chose to do no gamma-correction in this case;
+           which is a bit arbitrary.  
+        */
+        pixel const backcolor = ppm_parsecolor(requestedColor, maxval);
+        switch (info_ptr->color_type) {
+        case PNG_COLOR_TYPE_GRAY:
+        case PNG_COLOR_TYPE_GRAY_ALPHA:
+            bgColorP->r = bgColorP->g = bgColorP->b = PNM_GET1(backcolor);
+            break;
+        case PNG_COLOR_TYPE_PALETTE:
+        case PNG_COLOR_TYPE_RGB:
+        case PNG_COLOR_TYPE_RGB_ALPHA:
+            bgColorP->r = PPM_GETR(backcolor);
+            bgColorP->g = PPM_GETG(backcolor);
+            bgColorP->b = PPM_GETB(backcolor);
+            break;
+        }
+    } else if (info_ptr->valid & PNG_INFO_bKGD) {
+        /* didn't manage to get libpng to work (bugs?) concerning background
+           processing, therefore we do our own.
+        */
+        switch (info_ptr->color_type) {
+        case PNG_COLOR_TYPE_GRAY:
+        case PNG_COLOR_TYPE_GRAY_ALPHA:
+            bgColorP->r = bgColorP->g = bgColorP->b = 
+                gamma_correct(info_ptr->background.gray, totalgamma);
+            break;
+        case PNG_COLOR_TYPE_PALETTE: {
+            png_color const rawBgcolor = 
+                info_ptr->palette[info_ptr->background.index];
+            bgColorP->r = gamma_correct(rawBgcolor.red, totalgamma);
+            bgColorP->g = gamma_correct(rawBgcolor.green, totalgamma);
+            bgColorP->b = gamma_correct(rawBgcolor.blue, totalgamma);
+        }
+        break;
+        case PNG_COLOR_TYPE_RGB:
+        case PNG_COLOR_TYPE_RGB_ALPHA: {
+            png_color_16 const rawBgcolor = info_ptr->background;
+            
+            bgColorP->r = gamma_correct(rawBgcolor.red,   totalgamma);
+            bgColorP->g = gamma_correct(rawBgcolor.green, totalgamma);
+            bgColorP->b = gamma_correct(rawBgcolor.blue,  totalgamma);
+        }
+        break;
+        }
+    } else 
+        /* when no background given, we use white [from version 2.37] */
+        bgColorP->r = bgColorP->g = bgColorP->b = maxval;
+}
+
+
+
+static void
+writePnm(FILE *              const ofP,
+         xelval              const maxval,
+         int                 const pnm_type,
+         png_info *          const info_ptr,
+         png_byte **         const png_image,
+         pngcolor            const bgColor,
+         enum alpha_handling const alpha_handling,
+         double              const totalgamma) {
+/*----------------------------------------------------------------------------
+   Write a PNM of either the image or the alpha mask, according to
+   'alpha_handling' that is in the PNG image described by 'info_ptr' and
+   png_image.
+
+   'pnm_type' and 'maxval' are of the output image.
+
+   Use background color 'bgColor' in the output if the PNG is such that a
+   background color is needed.
+-----------------------------------------------------------------------------*/
+    xel * xelrow;
+    unsigned int row;
+
+    if (verbose)
+        pm_message ("writing a %s file (maxval=%u)",
+                    pnm_type == PBM_TYPE ? "PBM" :
+                    pnm_type == PGM_TYPE ? "PGM" :
+                    pnm_type == PPM_TYPE ? "PPM" :
+                    "UNKNOWN!", 
+                    maxval);
+    
+    xelrow = pnm_allocrow(info_ptr->width);
+
+    pnm_writepnminit(stdout, info_ptr->width, info_ptr->height, maxval,
+                     pnm_type, FALSE);
+
+    for (row = 0; row < info_ptr->height; ++row) {
+        png_byte * png_pixelP;
+        int col;
+
+        png_pixelP = &png_image[row][0];  /* initial value */
+        for (col = 0; col < info_ptr->width; ++col) {
+            switch (info_ptr->color_type) {
+            case PNG_COLOR_TYPE_GRAY: {
+                pngcolor fgColor;
+                fgColor.r = fgColor.g = fgColor.b = get_png_val(png_pixelP);
+                setXel(&xelrow[col], fgColor, bgColor, alpha_handling,
+                       ((info_ptr->valid & PNG_INFO_tRNS) &&
+                        (fgColor.r == 
+                         gamma_correct(info_ptr->trans_values.gray,
+                                       totalgamma))) ?
+                       0 : maxval);
+            }
+            break;
+
+            case PNG_COLOR_TYPE_GRAY_ALPHA: {
+                pngcolor fgColor;
+                png_uint_16 alpha;
+
+                fgColor.r = fgColor.g = fgColor.b = get_png_val(png_pixelP);
+                alpha = get_png_val(png_pixelP);
+                setXel(&xelrow[col], fgColor, bgColor, alpha_handling, alpha);
+            }
+            break;
+
+            case PNG_COLOR_TYPE_PALETTE: {
+                png_uint_16 const index        = get_png_val(png_pixelP);
+                png_color   const paletteColor = info_ptr->palette[index];
+
+                pngcolor fgColor;
+
+                fgColor.r = paletteColor.red;
+                fgColor.g = paletteColor.green;
+                fgColor.b = paletteColor.blue;
+
+                setXel(&xelrow[col], fgColor, bgColor, alpha_handling,
+                       (info_ptr->valid & PNG_INFO_tRNS) &&
+                       index < info_ptr->num_trans ?
+                       info_ptr->trans[index] : maxval);
+            }
+            break;
+                
+            case PNG_COLOR_TYPE_RGB: {
+                pngcolor fgColor;
+
+                fgColor.r = get_png_val(png_pixelP);
+                fgColor.g = get_png_val(png_pixelP);
+                fgColor.b = get_png_val(png_pixelP);
+                setXel(&xelrow[col], fgColor, bgColor, alpha_handling,
+                       isTransparentColor(fgColor, info_ptr, totalgamma) ?
+                       0 : maxval);
+            }
+            break;
+
+            case PNG_COLOR_TYPE_RGB_ALPHA: {
+                pngcolor fgColor;
+                png_uint_16 alpha;
+
+                fgColor.r = get_png_val(png_pixelP);
+                fgColor.g = get_png_val(png_pixelP);
+                fgColor.b = get_png_val(png_pixelP);
+                alpha     = get_png_val(png_pixelP);
+                setXel(&xelrow[col], fgColor, bgColor, alpha_handling, alpha);
+            }
+            break;
+
+            default:
+                pm_error ("unknown PNG color type: %d", info_ptr->color_type);
+            }
+        }
+        pnm_writepnmrow(ofP, xelrow, info_ptr->width, maxval, pnm_type, FALSE);
+    }
+    pnm_freerow (xelrow);
+}
+
+
+
+static void 
+convertpng(FILE *             const ifp, 
+           FILE *             const tfp, 
+           struct cmdlineInfo const cmdline,
+           int *              const errorlevelP) {
+
+    png_struct *png_ptr;
+    png_info *info_ptr;
+    png_byte **png_image;
+    int x, y;
+    int linesize;
+    int pnm_type;
+    pngcolor bgColor;
+    float totalgamma;
+
+    *errorlevelP = 0;
+
+    read_sig_buf(ifp);
+
+    png_ptr = png_create_read_struct(
+        PNG_LIBPNG_VER_STRING,
+        &pngtopnm_jmpbuf_struct, pngtopnm_error_handler, NULL);
+    if (png_ptr == NULL)
+        pm_error("cannot allocate main libpng structure (png_ptr)");
+
+    info_ptr = png_create_info_struct (png_ptr);
+    if (info_ptr == NULL)
+        pm_error("cannot allocate LIBPNG structures");
+
+    if (setjmp(pngtopnm_jmpbuf_struct.jmpbuf))
+        pm_error ("setjmp returns error condition");
+
+    png_init_io (png_ptr, ifp);
+    png_set_sig_bytes (png_ptr, SIG_CHECK_SIZE);
+    png_read_info (png_ptr, info_ptr);
+
+    MALLOCARRAY(png_image, info_ptr->height);
+    if (png_image == NULL) {
+        png_destroy_read_struct (&png_ptr, &info_ptr, (png_infopp)NULL);
+        pm_closer (ifp);
+        pm_error ("couldn't allocate space for image");
+    }
+
+    if (info_ptr->bit_depth == 16)
+        linesize = 2 * info_ptr->width;
+    else
+        linesize = info_ptr->width;
+
+    if (info_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+        linesize *= 2;
+    else
+        if (info_ptr->color_type == PNG_COLOR_TYPE_RGB)
+            linesize *= 3;
+        else
+            if (info_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+                linesize *= 4;
+
+    for (y = 0 ; y < info_ptr->height ; y++) {
+        png_image[y] = malloc (linesize);
+        if (png_image[y] == NULL) {
+            for (x = 0 ; x < y ; x++)
+                free (png_image[x]);
+            free (png_image);
+            png_destroy_read_struct (&png_ptr, &info_ptr, (png_infopp)NULL);
+            pm_closer (ifp);
+            pm_error ("couldn't allocate space for image");
+        }
+    }
+
+    if (info_ptr->bit_depth < 8)
+        png_set_packing (png_ptr);
+
+    setupGammaCorrection(png_ptr, info_ptr, cmdline.gamma, &totalgamma);
+
+    setupSignificantBits(png_ptr, info_ptr, cmdline.alpha,
+                         &maxval, errorlevelP);
+
+    getBackgroundColor(info_ptr, cmdline.background, totalgamma, maxval,
+                       &bgColor);
+
+    png_read_image (png_ptr, png_image);
+    png_read_end (png_ptr, info_ptr);
+
+    if (verbose)
+        /* Note that some of info_ptr is not defined until png_read_end() 
+       completes.  That's because it comes from chunks that are at the
+       end of the stream.
+    */
+        dump_png_info(info_ptr);
+
+    if (mtime)
+        show_time (info_ptr);
+    if (tfp)
+        save_text (info_ptr, tfp);
+
+    if (info_ptr->valid & PNG_INFO_pHYs) {
+        float r;
+        r = (float)info_ptr->x_pixels_per_unit / info_ptr->y_pixels_per_unit;
+        if (r != 1.0) {
+            pm_message ("warning - non-square pixels; "
+                        "to fix do a 'pamscale -%cscale %g'",
+                        r < 1.0 ? 'x' : 'y',
+                        r < 1.0 ? 1.0 / r : r );
+            *errorlevelP = PNMTOPNG_WARNING_LEVEL;
+        }
+    }
+
+    determineOutputType(info_ptr, cmdline.alpha, maxval, &pnm_type);
+
+    writePnm(stdout, maxval, pnm_type, info_ptr, png_image, bgColor, 
+             cmdline.alpha, totalgamma);
+
+    fflush(stdout);
+    for (y = 0 ; y < info_ptr->height ; y++)
+        free (png_image[y]);
+    free (png_image);
+    png_destroy_read_struct (&png_ptr, &info_ptr, (png_infopp)NULL);
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE *ifp, *tfp;
+    int errorlevel;
+
+    pnm_init (&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    verbose = cmdline.verbose;
+    mtime = cmdline.time;
+
+    ifp = pm_openr(cmdline.inputFilespec);
+
+    if (cmdline.text)
+        tfp = pm_openw(cmdline.text);
+    else
+        tfp = NULL;
+
+    convertpng (ifp, tfp, cmdline, &errorlevel);
+
+    if (tfp)
+        pm_close(tfp);
+
+    pm_close(ifp);
+    pm_close(stdout);
+
+    return errorlevel;
+}
diff --git a/converter/other/pngtxt.c b/converter/other/pngtxt.c
new file mode 100644
index 00000000..bbbec099
--- /dev/null
+++ b/converter/other/pngtxt.c
@@ -0,0 +1,315 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <png.h>
+
+#include "nstring.h"
+#include "pngtxt.h"
+#include "pm.h"
+#include "mallocvar.h"
+
+#define MAXCOMMENTS 256
+
+
+
+static void
+readOffKey(char           const textline[],
+           unsigned int   const lineLength,
+           unsigned int * const cursorP,
+           char **        const keyP) {
+
+    /* Get the comment key */
+    char * cp;
+    unsigned int cursor;
+
+    cursor = *cursorP;
+    
+    MALLOCARRAY(cp, lineLength + 1);  /* leave room for terminating NUL */
+    if (cp == NULL) 
+        pm_error("Unable to allocate memory for text chunks");
+    
+    *keyP = cp;
+    
+    if (textline[0] == '"') {
+        ++cursor;  /* skip past opening " */
+        while (textline[cursor] != '"') {
+            if (textline[cursor] == '\0') {
+                *cp = '\0';
+                pm_error("Invalid comment file format:  keyword contains "
+                         "a NUL character.  Text leading up to the NUL "
+                         "character is '%s'", *keyP);
+            }
+            *(cp++) = textline[cursor++];
+        }
+        ++cursor;  /* skip past closing " */
+    } else {
+        while (cursor < lineLength && 
+               textline[cursor] != ' '  && textline[cursor] != '\t' &&
+               textline[cursor] != '\0')
+            *(cp++) = textline[cursor++];
+    }
+    *cp++ = '\0';
+
+    *cursorP = cursor;
+}
+
+
+
+
+static void
+startComment(struct png_text_struct * const commentP, 
+             char                     const textline[],
+             unsigned int             const lineLength,
+             bool                     const compressed) {
+/*----------------------------------------------------------------------------
+   Assuming 'textline' is the first line of a comment in a comment file,
+   put the information from it in the comment record *commentP.
+   Use the text on this line as the comment text, even though the true
+   comment text may include text from subsequent continuation lines as
+   well.
+
+   'textline' is not NUL-terminated.  Its length is 'lineLength', and
+   it is at least one character long.  'textline' does not contain a
+   newline character.
+
+   'compressed' means the comment text is compressed.
+-----------------------------------------------------------------------------*/
+    unsigned int cursor;
+
+    /* the following is a not that accurate check on Author or Title */
+    if ((!compressed) || (textline[0] == 'A') || (textline[0] == 'T'))
+        commentP->compression = -1;
+    else
+        commentP->compression = 0;
+
+    cursor = 0;
+
+    readOffKey(textline, lineLength, &cursor, &commentP->key);
+
+    /* skip over delimiters between key and comment text */
+    while (cursor < lineLength && 
+           (textline[cursor] == ' ' || textline[cursor] == '\t' ||
+           textline[cursor] == '\0'))
+        ++cursor;
+    
+    {
+        /* Get the first line of the comment text */
+        unsigned int const startPos = cursor;
+        char *cp;
+
+        MALLOCARRAY(cp, lineLength+1);  /* leave room for safety NUL */
+        if (!cp) 
+            pm_error("Unable to allocate memory for text chunks");
+
+        memcpy(cp, textline + startPos, lineLength - startPos);
+        cp[lineLength - startPos] = '\0';  /* for safety - not part of text */
+        commentP->text = cp;
+        commentP->text_length = lineLength - startPos;
+    }
+}
+
+
+
+static void
+continueComment(struct png_text_struct * const commentP, 
+                char                     const textline[],
+                unsigned int             const lineLength) {
+/*----------------------------------------------------------------------------
+   Update the comment record *commentP by adding to it the text
+   from textline[], which is a continuation line from a comment file.
+
+   'textline' is not NUL-terminated.  Its length is 'lineLength', and
+   it is at least one character long.  'textline' does not contain a
+   newline character.
+-----------------------------------------------------------------------------*/
+    unsigned int cursor;  /* cursor into textline[] */
+
+    unsigned int const newTextLength =
+        commentP->text_length + lineLength + 1 + 1;
+
+    REALLOCARRAY(commentP->text, newTextLength);
+
+    if (commentP->text == NULL)
+        pm_error("Unable to allocate %u bytes of memory for comment chunk",
+                 newTextLength);
+
+    commentP->text[commentP->text_length++] = '\n';
+
+    /* Skip past leading delimiter characters in file line */
+    cursor = 0;
+    while (textline[cursor] == ' ' || textline[cursor] == '\t' ||
+           textline[cursor] == '\0')
+        ++cursor;
+
+    memcpy(commentP->text + commentP->text_length,
+           textline + cursor,
+           lineLength - cursor);
+
+    commentP->text_length += lineLength - cursor;
+
+    commentP->text[commentP->text_length] = '\0';  /* for safety */
+}
+
+
+
+static void
+getFileLine(FILE *         const fileP, 
+            const char **  const textP, 
+            unsigned int * const lengthP) {
+/*----------------------------------------------------------------------------
+   Read the next line (characters from current position through the first
+   newline character) and return it.  Put the text in newly malloc'ed 
+   storage.
+
+   Do not include the newline.
+
+   Add a terminating NUL for safety, but note that you can't rely on this
+   as the end of line marker because the line may contain a NUL.  *lengthP
+   does not include the NUL that we add.
+
+   If there are no more characters in the file, return NULL.
+-----------------------------------------------------------------------------*/
+    char * textline;  /* malloc'ed */
+    unsigned int cursor;  /* cursor into textline[] */
+    unsigned int allocated;
+        /* The number of characters of space that are allocated for
+           'textline' 
+        */
+    bool eol;
+    bool gotSomething;
+
+    allocated = 128;  /* initial value */
+
+    MALLOCARRAY(textline, allocated);
+    if (textline == NULL)
+        pm_error("Unable to allocate buffer to read a line of a file.");
+    
+    cursor = 0;
+    eol = FALSE;
+    gotSomething = FALSE;
+
+    while (!eol) {
+        int const c = getc(fileP);
+        
+        if (c != EOF)
+            gotSomething = TRUE;
+
+        if (c == '\n' || c == EOF)
+            eol = TRUE;
+        else {
+            if (cursor > allocated - 1 - 1) { /* leave space for safety NUL */
+                allocated *= 2;
+                REALLOCARRAY(textline, allocated);
+                if (textline == NULL)
+                    pm_error("Unable to allocate buffer to read a line of "
+                             "a file.");
+            }
+            textline[cursor++] = c;
+        }
+    }
+    textline[cursor] = '\0';  /* For safety; not part of line */
+
+    if (gotSomething) {
+        *textP = textline;
+        *lengthP = cursor;
+    } else {
+        free(textline);
+        *textP = NULL;
+    }
+}
+
+
+
+static void
+handleArrayAllocation(png_text **    const arrayP,
+                      unsigned int * const allocatedCommentsP,
+                      unsigned int   const commentIdx) {
+
+    if (commentIdx >= *allocatedCommentsP) {
+        *allocatedCommentsP *= 2;
+        REALLOCARRAY(*arrayP, *allocatedCommentsP);
+        if (*arrayP == NULL) 
+            pm_error("unable to allocate memory for comment array");
+    }
+}
+
+
+/******************************************************************************
+                            EXTERNAL SUBROUTINES
+******************************************************************************/
+
+
+void 
+pnmpng_read_text (png_info * const info_ptr, 
+                  FILE *     const tfp, 
+                  bool       const ztxt,
+                  bool       const verbose) {
+
+    const char * textline;
+    unsigned int lineLength;
+    unsigned int commentIdx;
+    bool noCommentsYet;
+    bool eof;
+    unsigned int allocatedComments;
+        /* Number of entries currently allocated for the info_ptr->text
+           array 
+        */
+
+    allocatedComments = 256;  /* initial value */
+
+    MALLOCARRAY(info_ptr->text, allocatedComments);
+    if (info_ptr->text == NULL) 
+        pm_error("unable to allocate memory for comment array");
+
+    commentIdx = 0;
+    noCommentsYet = TRUE;
+   
+    eof = FALSE;
+    while (!eof) {
+        getFileLine(tfp, &textline, &lineLength);
+        if (textline == NULL)
+            eof = TRUE;
+        else {
+            if (lineLength == 0) {
+                /* skip this empty line */
+            } else {
+                handleArrayAllocation(&info_ptr->text, &allocatedComments,
+                                      commentIdx);
+                if ((textline[0] != ' ') && (textline[0] != '\t')) {
+                    /* Line doesn't start with white space, which
+                       means it starts a new comment.  
+                    */
+                    if (noCommentsYet) {
+                        /* No previous comment to move past */
+                    } else
+                        ++commentIdx;
+                    noCommentsYet = FALSE;
+
+                    startComment(&info_ptr->text[commentIdx], 
+                                 textline, lineLength, ztxt);
+                } else {
+                    /* Line starts with whitespace, which means it is
+                       a continuation of the current comment.
+                    */
+                    if (noCommentsYet)
+                        pm_error("Invalid comment file format: "
+                                 "first line is a continuation line! "
+                                 "(It starts with whitespace)");
+                    continueComment(&info_ptr->text[commentIdx], 
+                                    textline, lineLength);
+                }
+            }
+            strfree(textline);
+        }
+    } 
+    if (noCommentsYet)
+        info_ptr->num_text = 0;
+    else
+        info_ptr->num_text = commentIdx + 1;
+
+    if (verbose)
+        pm_message("%d comments placed in text chunk", info_ptr->num_text);
+}
+
+
+
diff --git a/converter/other/pngtxt.h b/converter/other/pngtxt.h
new file mode 100644
index 00000000..ae7fe2c4
--- /dev/null
+++ b/converter/other/pngtxt.h
@@ -0,0 +1,13 @@
+#ifndef PNGTXT_H_INCLUDED
+#define PNGTXT_H_INCLUDED
+
+#include "pm_c_util.h"
+#include <png.h>
+
+void 
+pnmpng_read_text (png_info * const info_ptr, 
+                  FILE *     const tfp, 
+                  bool const ztxt,
+                  bool const verbose);
+
+#endif
diff --git a/converter/other/pnmtoddif.c b/converter/other/pnmtoddif.c
new file mode 100644
index 00000000..65152865
--- /dev/null
+++ b/converter/other/pnmtoddif.c
@@ -0,0 +1,611 @@
+/*
+ * $Log: pnmtoddif.c,v $
+ * Revision 1.6  1993/01/25  08:14:06  neideck
+ * Placed into public use form.
+ *
+ * Revision 1.5  1992/12/02  08:15:18  neideck
+ *  Added RCS id.
+ *
+ */
+
+/*
+ * Author:      Burkhard Neidecker-Lutz
+ *              Digital CEC Karlsruhe
+ *      neideck@nestvx.enet.dec.com 
+
+ Copyright (c) Digital Equipment Corporation, 1992
+
+ Permission to use, copy, modify, and distribute this software and its
+ documentation for any purpose and without fee is hereby granted,
+ provided that the above copyright notice appear in all copies and that
+ both that copyright notice and this permission notice appear in
+ supporting documentation, and that the name of Digital Equipment
+ Corporation not be used in advertising or publicity pertaining to
+ distribution of the software without specific, written prior
+ permission. Digital Equipment Corporation makes no representations
+ about the suitability of this software for any purpose. It is provided
+ "as is" without express or implied warranty.
+
+ Version: 1.0           30.07.92
+
+*/
+
+#include <string.h>
+#include "pnm.h"
+
+/* The structure we use to convey all sorts of "magic" data to the DDIF */
+/* header write procedure.                      */
+
+typedef struct {
+    int width;
+    int height;
+    int h_res;            /* Resolutions in dpi for bounding box */
+    int v_res;
+    int bits_per_pixel;
+    int bytes_per_line;
+    int spectral;         /* 2 == monochrome, 5 == rgb        */
+    int components;
+    int bits_per_component;
+    int polarity;         /* zeromin == 2 , zeromax == 1      */
+} imageparams;
+
+
+
+/* ASN.1 basic encoding rules magic number */
+#define UNIVERSAL 0
+#define APPLICATION 1
+#define CONTEXT 2
+#define PRIVATE 3
+
+#define PRIM 0
+#define CONS 1
+
+/* "tag": Emit an ASN tag of the specified class and tag number.    */
+/* This is used in conjuntion with the                  */
+/* wr_xxx routines that follow to construct the various ASN.1 entities. */
+/* Writing each entity is a two-step process, where first the tag is    */
+/* written and then the length and value.               */
+/* All of these routines take a pointer to a pointer into an output */
+/* buffer in the first argument and update it accordingly.      */
+
+static void tag(unsigned char ** buffer, int cl, int constructed,
+                unsigned int t)
+{
+    int tag_first;
+    unsigned int stack[10];
+    int sp;
+    unsigned char *p = *buffer;
+
+    tag_first = (cl << 6) | constructed << 5;
+    if (t < 31) {         /* Short tag form   */
+        *p++ = tag_first | t;
+    } else {          /* Long tag form */
+        *p++ = tag_first | 31;
+        sp = 0;
+        while (t > 0) {
+            stack[sp++] = t & 0x7f; 
+            t >>= 7;
+        }
+        while (--sp > 0) {  /* Tag values with continuation bits */
+            *p++ = stack[sp] | 0x80;
+        }
+        *p++ = stack[0];    /* Last tag portion without continuation bit */
+    }
+    *buffer = p;      /* Update buffer pointer */
+}
+
+
+
+/* Emit indefinite length encoding */
+static void 
+ind(unsigned char **buffer)
+{
+    unsigned char *p = *buffer;
+
+    *p++ = 0x80;
+    *buffer = p;
+}
+
+
+
+/* Emit ASN.1 NULL */
+static void 
+wr_null(unsigned char **buffer)
+{
+    unsigned char *p = *buffer;
+
+    *p++ = 0;
+    *buffer = p;
+}
+
+
+
+/* Emit ASN.1 length only into buffer, no data */
+static void 
+wr_length(unsigned char ** buffer, int amount)
+{
+    int length;
+    unsigned int mask;
+    unsigned char *p = *buffer;
+
+    length = 4;
+    mask = 0xff000000;
+
+    if (amount < 128) {
+        *p++ = amount;
+    } else {          /* > 127 */
+        while (!(amount & mask)) {  /* Search for first non-zero byte */
+            mask >>= 8;
+            --length;
+        }
+
+        *p++ = length | 0x80;       /* Number of length bytes */
+
+        while (--length >= 0) {     /* Put length bytes   */
+            *p++ =(amount >> (8*length)) & 0xff;
+        }
+
+    }
+    *buffer = p;
+}
+
+
+
+/* BER encode an integer and write it's length and value */
+static void 
+wr_int(unsigned char ** buffer, int val)
+{
+    int length;
+    int sign;
+    unsigned int mask;
+    unsigned char *p = *buffer;
+
+    if (val == 0) {
+        *p++ = 1;               /* length */
+        *p++ = 0;               /* value  */
+    } else {
+        sign = val < 0 ? 0xff : 0x00;   /* Sign bits */
+        length = 4;
+#ifdef __STDC__
+        mask  = 0xffu << 24;
+#else
+        mask  = 0xff << 24;
+#endif
+        while ((val & mask) == sign) {  /* Find the smallest representation */
+            length--;
+            mask >>= 8;
+        }
+        sign = (0x80 << ((length-1)*8)) & val; /* New sign bit */
+        if (((val < 0) && !sign) || ((val > 0) && sign)) { /* Sign error */
+            length++;
+        }
+        *p++ = length;          /* length */
+        while (--length >= 0) {
+            *p++ = (val >> (8*length)) & 0xff;
+        }
+    }
+    *buffer = p;
+}
+
+
+
+/* Emit and End Of Coding sequence  */
+static void 
+eoc(unsigned char ** buffer)
+{
+    unsigned char *p = *buffer;
+
+    *p++ = 0;
+    *p++ = 0;
+    *buffer = p;
+}
+
+
+
+/* Emit a simple string */
+static 
+void wr_string(unsigned char ** const buffer, const char * const val)
+{
+    int length;
+    unsigned char *p = *buffer;
+
+    length  = strlen(val);        
+    if (length > 127) {
+        fprintf(stderr,"Can't encode length > 127 yet (%d)\n",length);
+        exit(1);
+    }
+    *p++ = length;
+    {
+        const char * valCursor;
+        for (valCursor = val; *valCursor; ++valCursor)
+            *p++ = *valCursor;
+    }
+    *buffer = p;
+}
+
+
+
+/* Emit a ISOLATIN-1 string */
+static void 
+emit_isolatin1(unsigned char ** const buffer, const char * const val)
+{
+    int length;
+    unsigned char *p = *buffer;
+
+    length  = strlen(val) + 1;        /* One NULL byte and charset leader */
+    if (length > 127) {
+        fprintf(stderr,"Can't encode length > 127 yet (%d)\n",length);
+        exit(1);
+    }
+    *p++ = length;
+    *p++ = 1;             /* ISO LATIN-1 */
+    {
+        const char * valCursor;
+        for (valCursor = val; *valCursor; ++valCursor)
+            *p++ = *valCursor;
+    }
+    *buffer = p;
+}
+
+
+
+/* Write the DDIF grammar onto "file" up to the actual starting location */
+/* of the image data. The "ip" structure needs to be set to the right    */
+/* values. A lot of the values here are hardcoded to be just right for   */
+/* the bit grammars that the PBMPLUS formats want.           */
+
+static int 
+write_header(FILE *file, imageparams *ip)
+{
+    unsigned char buffer[300];            /* Be careful with the size ! */
+    unsigned char *p = buffer;
+    int headersize;
+    int bounding_x;
+    int bounding_y;
+    int i;
+
+    /* Calculate the bounding box from the resolutions    */
+    bounding_x = ((int) (1200 * ((double) (ip->width) / ip->h_res)));
+    bounding_y = ((int) (1200 * ((double) (ip->height) / ip->v_res)));
+
+    /* This is gross. The entire DDIF grammar is constructed by   */
+    /* hand. The indentation is meant to indicate DDIF document structure */
+
+    tag(&p,PRIVATE,CONS,16383); ind(&p);      /* DDIF Document */
+    tag(&p,CONTEXT,CONS, 0); ind(&p);        /* Document Descriptor */
+    tag(&p,CONTEXT,PRIM, 0); wr_int(&p,1);  /* Major Version */
+    tag(&p,CONTEXT,PRIM, 1); wr_int(&p,3);  /* Minor Version */
+    tag(&p,CONTEXT,PRIM, 2); wr_string(&p,"PBM+"); /* Product Indentifier */
+    tag(&p,CONTEXT,CONS, 3); ind(&p);       /* Product Name */
+    tag(&p,PRIVATE,PRIM, 9); emit_isolatin1(&p,"PBMPLUS Writer V1.0");
+    eoc(&p);
+    eoc(&p);                 /* Document Descriptor */
+    tag(&p,CONTEXT,CONS, 1); ind(&p);        /* Document Header     */
+    tag(&p,CONTEXT,CONS, 3); ind(&p);       /* Version */
+    tag(&p,PRIVATE,PRIM, 9); emit_isolatin1(&p,"1.0");
+    eoc(&p);
+    eoc(&p);                 /* Document Header */
+    tag(&p,CONTEXT,CONS, 2); ind(&p);        /* Document Content    */
+    tag(&p,APPLICATION,CONS,2); ind(&p);    /* Segment Primitive    */
+    eoc(&p);
+    tag(&p,APPLICATION,CONS,2); ind(&p);    /* Segment  */
+    tag(&p,CONTEXT,CONS, 3); ind(&p);      /* Segment Specific Attributes */
+    tag(&p,CONTEXT,PRIM, 2); wr_string(&p,"$I");  /* Category */
+    tag(&p,CONTEXT,CONS,22); ind(&p);     /* Image Attributes */
+    tag(&p,CONTEXT,CONS, 0); ind(&p);    /* Image Presentation Attributes */
+    tag(&p,CONTEXT,PRIM, 1); wr_int(&p,0);  /* Pixel Path */
+    tag(&p,CONTEXT,PRIM, 2); wr_int(&p,270); /* Line Progression */
+    tag(&p,CONTEXT,CONS, 3); ind(&p);   /* Pixel Aspect Ratio */
+    tag(&p,CONTEXT,PRIM, 0); wr_int(&p,1); /* PP Pixel Dist */
+    tag(&p,CONTEXT,PRIM, 1); wr_int(&p,1); /* LP Pixel Dist */
+    eoc(&p);                /* Pixel Aspect Ratio */
+    tag(&p,CONTEXT,PRIM, 4); wr_int(&p,ip->polarity);  
+        /* Brightness Polarity */
+    tag(&p,CONTEXT,PRIM, 5); wr_int(&p,1);  /* Grid Type    */
+    tag(&p,CONTEXT,PRIM, 7); wr_int(&p,ip->spectral);  /* Spectral Mapping */
+    tag(&p,CONTEXT,CONS,10); ind(&p);   /* Pixel Group Info */
+    tag(&p,CONTEXT,PRIM, 0); wr_int(&p,1); /* Pixel Group Size */
+    tag(&p,CONTEXT,PRIM, 1); wr_int(&p,1); /* Pixel Group Order */
+    eoc(&p);                /* Pixel Group Info */
+    eoc(&p);                     /* Image Presentation Attributes */
+    tag(&p,CONTEXT,CONS, 1); ind(&p);    /* Component Space Attributes */
+    tag(&p,CONTEXT,PRIM, 0); wr_int(&p,1);  /* Component Space Organization */
+    tag(&p,CONTEXT,PRIM, 1); wr_int(&p,1);  /* Planes per Pixel */
+    tag(&p,CONTEXT,PRIM, 2); wr_int(&p,1);  /* Plane Significance   */
+    tag(&p,CONTEXT,PRIM, 3); wr_int(&p,ip->components);  
+        /* Number of Components    */
+    tag(&p,CONTEXT,CONS, 4); ind(&p);   /* Bits per Component   */
+    for (i = 0; i < ip->components; i++) {
+        tag(&p,UNIVERSAL,PRIM,2); wr_int(&p,ip->bits_per_component);
+    }
+    eoc(&p);                /* Bits per Component   */
+    tag(&p,CONTEXT,CONS, 5); ind(&p);   /* Component Quantization Levels */
+    for (i = 0; i < ip->components; i++) {
+        tag(&p,UNIVERSAL,PRIM,2); wr_int(&p,1 << ip->bits_per_component);
+    }
+    eoc(&p);                /* Component Quantization Levels */
+    eoc(&p);                 /* Component Space Attributes */
+    eoc(&p);                  /* Image Attributes */
+    tag(&p,CONTEXT,CONS,23); ind(&p);     /* Frame Parameters */
+    tag(&p,CONTEXT,CONS, 1); ind(&p);    /* Bounding Box */
+    tag(&p,CONTEXT,CONS, 0); ind(&p);   /* lower-left   */
+    tag(&p,CONTEXT,CONS, 0); ind(&p);  /* XCoordinate  */
+    tag(&p,CONTEXT,PRIM, 0); wr_int(&p,0);
+    eoc(&p);                           /* XCoordinate  */
+    tag(&p,CONTEXT,CONS, 1); ind(&p);  /* YCoordinate  */
+    tag(&p,CONTEXT,PRIM, 0); wr_int(&p,0);
+    eoc(&p);                               /* YCoordinate  */
+    eoc(&p);                /* lower left */
+    tag(&p,CONTEXT,CONS, 1); ind(&p);       /* upper right */
+    tag(&p,CONTEXT,CONS, 0); ind(&p);      /* XCoordinate  */
+    tag(&p,CONTEXT,PRIM, 0); wr_int(&p,bounding_x);
+    eoc(&p);               /* XCoordinate  */
+    tag(&p,CONTEXT,CONS, 1); ind(&p);      /* YCoordinate  */
+    tag(&p,CONTEXT,PRIM, 0); wr_int(&p,bounding_y);
+    eoc(&p);                   /* YCoordinate  */
+    eoc(&p);                            /* upper right */
+    eoc(&p);                 /* Bounding Box */
+    tag(&p,CONTEXT,CONS, 4); ind(&p);    /* Frame Position */
+    tag(&p,CONTEXT,CONS, 0); ind(&p);   /* XCoordinate  */
+    tag(&p,CONTEXT,PRIM, 0); wr_int(&p,0);
+    eoc(&p);                /* XCoordinate  */
+    tag(&p,CONTEXT,CONS, 1); ind(&p);   /* YCoordinate  */
+    tag(&p,CONTEXT,PRIM, 0); wr_int(&p,0);
+    eoc(&p);                    /* YCoordinate  */
+    eoc(&p);                 /* Frame Position */
+    eoc(&p);                  /* Frame Parameters */
+    eoc(&p);                        /* Segment Specific Attributes */
+    eoc(&p);                    /* Segment */
+    tag(&p,APPLICATION,CONS,17); ind(&p);   /* Image Data Descriptor */
+    tag(&p,UNIVERSAL,CONS,16); ind(&p);    /* Sequence */
+    tag(&p,CONTEXT,CONS, 0); ind(&p);     /* Image Coding Attributes */
+    tag(&p,CONTEXT,PRIM, 1); wr_int(&p,ip->width); /* Pixels per Line    */
+    tag(&p,CONTEXT,PRIM, 2); wr_int(&p,ip->height);  /* Number of Lines  */
+    tag(&p,CONTEXT,PRIM, 3); wr_int(&p,2);   /* Compression Type */
+    tag(&p,CONTEXT,PRIM, 5); wr_int(&p,0);   /* Data Offset  */
+    tag(&p,CONTEXT,PRIM, 6); wr_int(&p,ip->bits_per_pixel);  /* Pixel Stride */
+    tag(&p,CONTEXT,PRIM, 7); wr_int(&p,ip->bytes_per_line * 8);
+        /* Scanline Stride    */
+    tag(&p,CONTEXT,PRIM, 8); wr_int(&p,1);   /* Bit Order        */
+    tag(&p,CONTEXT,PRIM, 9); wr_int(&p,ip->bits_per_pixel);  
+        /* Planebits per Pixel */
+    tag(&p,CONTEXT,CONS,10); ind(&p);    /* Byteorder Info   */
+    tag(&p,CONTEXT,PRIM, 0); wr_int(&p,1);  /* Byte Unit        */
+    tag(&p,CONTEXT,PRIM, 1); wr_int(&p,1);  /* Byte Order   */
+    eoc(&p);                 /* Byteorder Info   */
+    tag(&p,CONTEXT,PRIM,11); wr_int(&p,3);   /* Data Type        */
+    eoc(&p);                              /* Image Coding Attributes */
+    tag(&p,CONTEXT,PRIM, 1); wr_length(&p,ip->bytes_per_line*ip->height);  
+        /* Component Plane Data */
+    /* End of DDIF document Indentation */
+    headersize = p - buffer;
+    if (headersize >= 300)  {
+        fprintf(stderr,"Overran buffer area %d >= 300\n",headersize);
+        exit(1);
+    }
+
+    return (fwrite(buffer, 1, headersize, file) == headersize);
+}
+
+
+
+/* Write all the closing brackets of the DDIF grammar that are missing */
+/* The strange indentation reflects exactly the same indentation that  */
+/* we left off in the write_header procedure.                  */
+static int 
+write_trailer(FILE * file)
+{
+    unsigned char buffer[30];
+    unsigned char *p = buffer;
+    int trailersize;
+
+    /* Indentation below gives DDIF document structure */
+    eoc(&p);                        /* Sequence */
+    eoc(&p);                     /* Image Data Descriptor */
+    tag(&p,APPLICATION,PRIM,1); wr_null(&p);     /* End Segment */
+    tag(&p,APPLICATION,PRIM,1); wr_null(&p);     /* End Segment */
+    eoc(&p);                  /* Document Content */
+    eoc(&p);                   /* DDIF Document */
+    /* End of DDIF document Indentation */
+    trailersize = p - buffer;
+    if (trailersize >= 30)  {
+        fprintf(stderr,"Overran buffer area %d >= 30\n",trailersize);
+        exit(1);
+    }
+
+    return(fwrite(buffer, 1, trailersize, file) == trailersize);
+}
+
+
+
+int main(int argc, char *argv[])
+{
+    FILE           *ifd;
+    FILE       *ofd;
+    int             rows, cols;
+    xelval          maxval;
+    int             format;
+    const char     * const usage = "[-resolution x y] [pnmfile [ddiffile]]";
+    int             i, j;
+    char           *outfile;
+    int       argn;
+    int hor_resolution = 75;
+    int ver_resolution = 75;
+    imageparams ip;
+    unsigned char  *data, *p;
+
+    pnm_init(&argc, argv);
+
+    for (argn = 1;argn < argc && argv[argn][0] == '-';argn++) {
+        int arglen = strlen(argv[argn]);
+
+        if (!strncmp (argv[argn],"-resolution", arglen)) {
+            if (argn + 2 < argc) {
+                hor_resolution = atoi(argv[argn+1]);
+                ver_resolution = atoi(argv[argn+2]);
+                argn += 2;
+                continue;
+            } else {
+                pm_usage(usage);
+            }
+        } else {
+            pm_usage(usage);
+        }
+    }
+
+    if (hor_resolution <= 0 || ver_resolution <= 0) {
+        fprintf(stderr,"Unreasonable resolution values: %d x %d\n",
+                hor_resolution,ver_resolution);
+        exit(1);
+    }
+
+    if (argn == argc - 2) {
+        ifd = pm_openr(argv[argn]);
+        outfile = argv[argn+1];
+        if (!(ofd = fopen(outfile,"wb"))) {
+            perror(outfile);
+            exit(1);
+        }
+    } else if (argn == argc - 1) {
+        ifd = pm_openr(argv[argn]);
+        ofd = stdout;
+    } else {
+        ifd = stdin;
+        ofd = stdout;
+    }
+
+    pnm_readpnminit(ifd, &cols, &rows, &maxval, &format);
+
+    ip.width = cols;
+    ip.height = rows;
+    ip.h_res = hor_resolution;
+    ip.v_res = ver_resolution;
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PBM_TYPE:
+        ip.bits_per_pixel = 1;
+        ip.bytes_per_line = (cols + 7) / 8;
+        ip.spectral = 2;
+        ip.components = 1;
+        ip.bits_per_component = 1;
+        ip.polarity = 1;
+        break;
+    case PGM_TYPE:
+        ip.bytes_per_line = cols;
+        ip.bits_per_pixel = 8;
+        ip.spectral = 2;
+        ip.components = 1;
+        ip.bits_per_component = 8;
+        ip.polarity = 2;
+        break;
+    case PPM_TYPE:
+        ip.bytes_per_line = 3 * cols;
+        ip.bits_per_pixel = 24;
+        ip.spectral = 5;
+        ip.components = 3;
+        ip.bits_per_component = 8;
+        ip.polarity = 2;
+        break;
+    default:
+        fprintf(stderr, "Unrecognized PBMPLUS format %d\n", format);
+        exit(1);
+    }
+
+    if (!write_header(ofd,&ip)) {
+        perror("Writing header");
+        exit(1);
+    }
+
+    if (!(p = data = (unsigned char*)  malloc(ip.bytes_per_line))) {
+        perror("allocating line buffer");
+        exit(1);
+    }
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PBM_TYPE:
+    {
+        bit            *pixels;
+        int             mask;
+        int             k;
+
+        pixels = pbm_allocrow(cols);
+
+        for (i = 0; i < rows; i++) {
+            pbm_readpbmrow(ifd, pixels, cols, format);
+            mask = 0;
+            p = data;
+            for (j = 0, k = 0; j < cols; j++) {
+                if (pixels[j] == PBM_BLACK) {
+                    mask |= 1 << k;
+                }
+                if (k == 7) {
+                    *p++ = mask;
+                    mask = 0;
+                    k = 0;
+                } else {
+                    k++;
+                }
+            }
+            if (k != 7) {       /* Flush the rest of the column */
+                *p = mask;
+            }
+            if (fwrite(data,1,ip.bytes_per_line,ofd) != ip.bytes_per_line) {
+                perror("Writing image data\n");
+                exit(1);
+            }
+        }
+    }
+    break;
+    case PGM_TYPE:
+    {
+        gray           *pixels;
+
+        pixels = (gray *) data;
+
+        for (i = 0; i < rows; i++) {
+            pgm_readpgmrow(ifd, pixels, cols, maxval, format);
+            if (fwrite(data,1,ip.bytes_per_line,ofd) != ip.bytes_per_line) {
+                perror("Writing image data\n");
+                exit(1);
+            }
+        }
+    }
+    break;
+    case PPM_TYPE:
+    {
+        pixel          *pixels = ppm_allocrow(cols);
+
+        for (i = 0; i < rows; i++) {
+            p = data;
+            ppm_readppmrow(ifd, pixels, cols, maxval, format);
+            for (j = 0; j < cols; j++) {
+                *p++ = PPM_GETR(pixels[j]);
+                *p++ = PPM_GETG(pixels[j]);
+                *p++ = PPM_GETB(pixels[j]);
+            }
+            if (fwrite(data,1,ip.bytes_per_line,ofd) != ip.bytes_per_line) {
+                perror("Writing image data\n");
+                exit(1);
+            }
+        }
+        ppm_freerow(pixels);
+    }
+    break;
+    }
+
+    pm_close(ifd);
+
+    free(data);
+
+    if (!write_trailer(ofd)) {
+        perror("Writing trailer");
+        exit(1);
+    }
+
+    if (fclose(ofd) == EOF) {
+        perror("Closing output file");
+        exit(1);
+    };
+
+    return(0);
+}
diff --git a/converter/other/pnmtojpeg.c b/converter/other/pnmtojpeg.c
new file mode 100644
index 00000000..a0262331
--- /dev/null
+++ b/converter/other/pnmtojpeg.c
@@ -0,0 +1,1098 @@
+/*****************************************************************************
+                                pnmtojpeg
+******************************************************************************
+  This program is part of the Netpbm package.
+
+  This program converts from the PNM formats to the JFIF format
+  which is based on JPEG.
+
+  This program is by Bryan Henderson on 2000.03.06, but is derived
+  with permission from the program cjpeg, which is in the Independent
+  Jpeg Group's JPEG library package.  Under the terms of that permission,
+  redistribution of this software is restricted as described in the 
+  file README.JPEG.
+
+  Copyright (C) 1991-1998, Thomas G. Lane.
+
+*****************************************************************************/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <ctype.h>		/* to declare isdigit(), etc. */
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <assert.h>
+/* Note: jpeglib.h prerequires stdlib.h and ctype.h.  It should include them
+   itself, but doesn't.
+*/
+#include <jpeglib.h>
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+#define EXIT_WARNING 2   /* Goes with EXIT_SUCCESS, EXIT_FAILURE in stdlib.h */
+
+enum restart_unit {RESTART_MCU, RESTART_ROW, RESTART_NONE};
+enum density_unit {DEN_UNSPECIFIED, DEN_DOTS_PER_INCH, DEN_DOTS_PER_CM};
+
+struct density {
+    enum density_unit unit;
+        /* The units of density for 'horiz', 'vert' */
+    unsigned short horiz;  /* Not 0 */
+        /* Horizontal density, in units specified by 'unit' */
+    unsigned short vert;   /* Not 0 */
+        /* Same as 'horiz', but vertical */
+};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+     */
+    char *input_filespec;
+    unsigned int verbose;
+    unsigned int quality;
+    unsigned int force_baseline;
+    unsigned int progressive;
+    unsigned int arith_code;
+    J_DCT_METHOD dct_method;
+    unsigned int grayscale;
+    unsigned int rgb;
+    long int max_memory_to_use;
+    unsigned int trace_level;
+    char *qslots;
+    char *qtablefile;
+    char *sample;
+    char *scans;
+    int smoothing_factor;
+    unsigned int optimize;
+    unsigned int restart_value;
+    enum restart_unit restart_unit;
+    char *restart;
+    char *comment;              /* NULL if none */
+    const char *exif_filespec;  /* NULL if none */
+    unsigned int density_spec;
+        /* boolean: JFIF should specify a density.  If false, 'density'
+           is undefined.
+        */
+    struct density density;
+};
+
+static void 
+interpret_maxmemory (const char * const maxmemory, 
+                     long int * const max_memory_to_use_p) { 
+    long int lval;
+    char ch;
+    
+    if (maxmemory == NULL) {
+        *max_memory_to_use_p = -1;  /* unspecified */
+    } else if (sscanf(maxmemory, "%ld%c", &lval, &ch) < 1) {
+        pm_error("Invalid value for --maxmemory option: '%s'.", maxmemory);
+        exit(EXIT_FAILURE);
+    } else {
+        if (ch == 'm' || ch == 'M') lval *= 1000L;
+        *max_memory_to_use_p = lval * 1000L;
+    }
+}
+
+
+
+static void
+interpret_restart(const char * const restart,
+                  unsigned int * const restart_value_p,
+                  enum restart_unit * const restart_unit_p) {
+/*----------------------------------------------------------------------------
+   Interpret the restart command line option.  Return values suitable
+   for plugging into a jpeg_compress_struct to control compression.
+-----------------------------------------------------------------------------*/
+    if (restart == NULL) {
+        /* No --restart option.  Set default */
+        *restart_unit_p = RESTART_NONE;
+    } else {
+        /* Restart interval in MCU rows (or in MCUs with 'b'). */
+        long lval;
+        char ch;
+        unsigned int matches;
+        
+        matches= sscanf(restart, "%ld%c", &lval, &ch);
+        if (matches == 0) 
+            pm_error("Invalid value for the --restart option : '%s'.",
+                     restart);
+        else {
+            if (lval < 0 || lval > 65535L) {
+                pm_error("--restart value %ld is out of range.", lval);
+                exit(EXIT_FAILURE);
+            } else {
+                if (matches == 1) {
+                    *restart_value_p = lval;
+                    *restart_unit_p = RESTART_ROW;
+                } else {
+                    if (ch == 'b' || ch == 'B') {
+                        *restart_value_p = lval;
+                        *restart_unit_p = RESTART_MCU;
+                    } else pm_error("Invalid --restart value '%s'.", restart);
+                }
+            }
+        }
+    }
+}
+
+
+
+
+static void
+interpret_density(const char *        const densityString,
+                  struct density *    const densityP) {
+/*----------------------------------------------------------------------------
+   Interpret the value of the "-density" option.
+-----------------------------------------------------------------------------*/
+    if (strlen(densityString) < 1)
+        pm_error("-density value cannot be null.");
+    else {
+        char * unitName;  /* malloc'ed */
+        int matched;
+        int horiz, vert;
+
+        unitName = malloc(strlen(densityString)+1);
+    
+        matched = sscanf(densityString, "%dx%d%s", &horiz, &vert, unitName);
+
+        if (matched < 2)
+            pm_error("Invalid format for density option value '%s'.  It "
+                     "should follow the example '3x2' or '3x2dpi' or "
+                     "'3x2dpcm'.", densityString);
+        else {
+            if (horiz <= 0 || horiz >= 1<<16)
+                pm_error("Horizontal density %d is outside the range 1-65535",
+                         horiz);
+            else if (vert <= 0 || vert >= 1<<16)
+                pm_error("Vertical density %d is outside the range 1-65535",
+                         vert);
+            else {
+                densityP->horiz = horiz;
+                densityP->vert  = vert;
+
+                if (matched < 3) 
+                    densityP->unit = DEN_UNSPECIFIED;
+                else {
+                    if (streq(unitName, "dpi") || streq(unitName, "DPI"))
+                        densityP->unit = DEN_DOTS_PER_INCH;
+                    else if (streq(unitName, "dpcm") ||
+                             streq(unitName, "DPCM"))
+                        densityP->unit = DEN_DOTS_PER_CM;
+                    else
+                        pm_error("Unrecognized unit '%s' in the density value "
+                                 "'%s'.  I recognize only 'dpi' and 'dpcm'",
+                                 unitName, densityString);
+                }
+            }
+        }
+        free(unitName);
+    }
+}
+
+
+
+static void
+parseCommandLine(const int argc, char ** argv,
+                   struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that many of the strings that this function returns in the
+   *cmdlineP structure are actually in the supplied argv array.  And
+   sometimes, one of these strings is actually just a suffix of an entry
+   in argv!
+
+   On the other hand, unlike other option processing functions, we do
+   not change argv at all.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    int i;  /* local loop variable */
+
+    const char *dctval;
+    const char *maxmemory;
+    const char *restart;
+    const char *density;
+
+    unsigned int qualitySpec, smoothSpec;
+
+    unsigned int option_def_index;
+
+    int argc_parse;       /* argc, except we modify it as we parse */
+    char ** argv_parse;
+        /* argv, except we modify it as we parse */
+
+    MALLOCARRAY(argv_parse, argc);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "verbose",     OPT_FLAG,   NULL, &cmdlineP->verbose,        0);
+    OPTENT3(0, "quality",     OPT_UINT,   &cmdlineP->quality, 
+            &qualitySpec,        0);
+    OPTENT3(0, "baseline",    OPT_FLAG,   NULL, &cmdlineP->force_baseline, 0);
+    OPTENT3(0, "progressive", OPT_FLAG,   NULL, &cmdlineP->progressive,    0);
+    OPTENT3(0, "arithmetic",  OPT_FLAG,   NULL, &cmdlineP->arith_code,     0);
+    OPTENT3(0, "dct",         OPT_STRING, &dctval, NULL,                    0);
+    OPTENT3(0, "grayscale",   OPT_FLAG,   NULL, &cmdlineP->grayscale,      0);
+    OPTENT3(0, "greyscale",   OPT_FLAG,   NULL, &cmdlineP->grayscale,      0);
+    OPTENT3(0, "rgb",         OPT_FLAG,   NULL, &cmdlineP->rgb,            0);
+    OPTENT3(0, "maxmemory",   OPT_STRING, &maxmemory, NULL,                 0);
+    OPTENT3(0, "tracelevel",  OPT_UINT,   &cmdlineP->trace_level, NULL,    0);
+    OPTENT3(0, "qslots",      OPT_STRING, &cmdlineP->qslots,      NULL,    0);
+    OPTENT3(0, "qtables",     OPT_STRING, &cmdlineP->qtablefile,  NULL,    0);
+    OPTENT3(0, "sample",      OPT_STRING, &cmdlineP->sample,      NULL,    0);
+    OPTENT3(0, "scans",       OPT_STRING, &cmdlineP->scans,       NULL,    0);
+    OPTENT3(0, "smooth",      OPT_UINT,   &cmdlineP->smoothing_factor, 
+            &smoothSpec,  0);
+    OPTENT3(0, "optimize",    OPT_FLAG,   NULL, &cmdlineP->optimize,       0);
+    OPTENT3(0, "optimise",    OPT_FLAG,   NULL, &cmdlineP->optimize,       0);
+    OPTENT3(0, "restart",     OPT_STRING, &restart, NULL,                   0);
+    OPTENT3(0, "comment",     OPT_STRING, &cmdlineP->comment, NULL,        0);
+    OPTENT3(0, "exif",        OPT_STRING, &cmdlineP->exif_filespec, NULL,  0);
+    OPTENT3(0, "density",     OPT_STRING, &density, 
+            &cmdlineP->density_spec, 0);
+
+    /* Set the defaults */
+    dctval = NULL;
+    maxmemory = NULL;
+    cmdlineP->trace_level = 0;
+    cmdlineP->qslots = NULL;
+    cmdlineP->qtablefile = NULL;
+    cmdlineP->sample = NULL;
+    cmdlineP->scans = NULL;
+    restart = NULL;
+    cmdlineP->comment = NULL;
+    cmdlineP->exif_filespec = NULL;
+
+    /* Make private copy of arguments for optParseOptions to corrupt */
+    argc_parse = argc;
+    for (i=0; i < argc; i++) argv_parse[i] = argv[i];
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc_parse, argv_parse, opt, sizeof(opt), 0);
+
+    if (!qualitySpec)
+        cmdlineP->quality = -1;  /* unspecified */
+
+    if (!smoothSpec)
+        cmdlineP->smoothing_factor = -1;
+
+    if (cmdlineP->rgb && cmdlineP->grayscale)
+        pm_error("You can't specify both -rgb and -grayscale");
+
+    if (argc_parse - 1 == 0)
+        cmdlineP->input_filespec = strdup("-");  /* he wants stdin */
+    else if (argc_parse - 1 == 1)
+        cmdlineP->input_filespec = strdup(argv_parse[1]);
+    else 
+        pm_error("Too many arguments.  The only argument accepted "
+                 "is the input file specification.");
+    if (dctval == NULL)
+        cmdlineP->dct_method = JDCT_DEFAULT;
+    else {
+        if (streq(dctval, "int"))
+            cmdlineP->dct_method = JDCT_ISLOW;
+        else if (streq(dctval, "fast"))
+            cmdlineP->dct_method = JDCT_IFAST;
+        else if (streq(dctval, "float"))
+            cmdlineP->dct_method = JDCT_FLOAT;
+        else
+            pm_error("Invalid value for the --dct option: '%s'.", dctval);
+    }
+
+    interpret_maxmemory(maxmemory, &cmdlineP->max_memory_to_use);
+    interpret_restart(restart, &cmdlineP->restart_value,
+                      &cmdlineP->restart_unit);
+    if (cmdlineP->density_spec) 
+        interpret_density(density, &cmdlineP->density);
+    
+    if (cmdlineP->smoothing_factor > 100)
+        pm_error("Smoothing factor %d is greater than 100 (%%).",
+                 cmdlineP->smoothing_factor);
+
+    if (streq(cmdlineP->input_filespec, "=") &&
+        cmdlineP->exif_filespec && 
+        streq(cmdlineP->exif_filespec, "-"))
+
+        pm_error("Cannot have both input image and exif header be from "
+                 "Standard Input.");
+
+
+    free(argv_parse);
+}
+
+
+static void
+compute_rescaling_array(JSAMPLE ** const rescale_p, const pixval maxval,
+                        const struct jpeg_compress_struct cinfo);
+static void
+convert_scanlines(struct jpeg_compress_struct *cinfo_p, FILE *input_file,
+                  const pixval maxval, const int input_fmt,
+                  JSAMPLE xlate_table[]);
+
+static boolean read_quant_tables (j_compress_ptr cinfo, char * filename,
+                                  int scale_factor, boolean force_baseline);
+
+static boolean read_scan_script (j_compress_ptr cinfo, char * filename);
+
+static boolean set_quant_slots (j_compress_ptr cinfo, char *arg);
+
+static boolean set_sample_factors (j_compress_ptr cinfo, char *arg);
+
+
+static void
+report_compressor(const struct jpeg_compress_struct cinfo) {
+    
+    if (cinfo.scan_info == NULL)
+        pm_message("No scan script is being used");
+    else {
+        int i;
+        pm_message("A scan script with %d entries is being used:",
+                   cinfo.num_scans);
+        for (i = 0; i < cinfo.num_scans; i++) {
+            int j;
+            pm_message("    Scan %2d: Ss=%2d Se=%2d Ah=%2d Al=%2d  "
+                       "%d components", 
+                       i,
+                       cinfo.scan_info[i].Ss,
+                       cinfo.scan_info[i].Se,
+                       cinfo.scan_info[i].Ah,
+                       cinfo.scan_info[i].Al,
+                       cinfo.scan_info[i].comps_in_scan
+                       );
+            for (j = 0; j < cinfo.scan_info[i].comps_in_scan; j++)
+                pm_message("        Color component %d index: %d", j,
+                           cinfo.scan_info[i].component_index[j]);
+        }
+    }
+}
+
+
+
+static void
+setup_jpeg_source_parameters(struct jpeg_compress_struct * const cinfoP, 
+                             int const width, int const height, 
+                             int const format) {
+/*----------------------------------------------------------------------------
+   Set up in the compressor descriptor *cinfoP the description of the 
+   source image as required by the compressor.
+-----------------------------------------------------------------------------*/
+
+    switch PNM_FORMAT_TYPE(format) {
+    case PBM_TYPE:
+    case PGM_TYPE:
+        cinfoP->in_color_space = JCS_GRAYSCALE;
+        cinfoP->input_components = 1;
+        break;
+    case PPM_TYPE:
+        cinfoP->in_color_space = JCS_RGB; 
+        cinfoP->input_components = 3;
+        break;
+    default:
+        pm_error("INTERNAL ERROR; invalid format in "
+                 "setup_jpeg_source_parameters()");
+    }
+}
+
+
+
+static void
+setup_jpeg_density(struct jpeg_compress_struct * const cinfoP, 
+                   struct density                const density) {
+/*----------------------------------------------------------------------------
+   Set up in the compressor descriptor *cinfoP the density information
+   'density'.
+-----------------------------------------------------------------------------*/
+    switch(density.unit) {
+    case DEN_UNSPECIFIED:   cinfoP->density_unit = 0; break;
+    case DEN_DOTS_PER_INCH: cinfoP->density_unit = 1; break;
+    case DEN_DOTS_PER_CM:   cinfoP->density_unit = 2; break;
+    }
+    
+    cinfoP->X_density = density.horiz;
+    cinfoP->Y_density = density.vert;
+}
+
+
+
+static void
+setup_jpeg(struct jpeg_compress_struct * const cinfoP,
+           struct jpeg_error_mgr       * const jerrP,
+           struct cmdlineInfo            const cmdline, 
+           int                           const width,
+           int                           const height,
+           pixval                        const maxval,
+           int                           const input_fmt,
+           FILE *                        const output_file) {
+  
+    int quality;
+    int q_scale_factor;
+
+    /* Initialize the JPEG compression object with default error handling. */
+    cinfoP->err = jpeg_std_error(jerrP);
+    jpeg_create_compress(cinfoP);
+
+    setup_jpeg_source_parameters(cinfoP, width, height, input_fmt);
+
+    jpeg_set_defaults(cinfoP);
+
+    cinfoP->data_precision = BITS_IN_JSAMPLE; 
+        /* we always rescale data to this */
+    cinfoP->image_width = (unsigned int) width;
+    cinfoP->image_height = (unsigned int) height;
+
+    cinfoP->arith_code = cmdline.arith_code;
+    cinfoP->dct_method = cmdline.dct_method;
+    if (cmdline.trace_level == 0 && cmdline.verbose) 
+        cinfoP->err->trace_level = 1;
+    else cinfoP->err->trace_level = cmdline.trace_level;
+    if (cmdline.grayscale)
+        jpeg_set_colorspace(cinfoP, JCS_GRAYSCALE);
+    else if (cmdline.rgb)
+        /* This is not legal if the input is not JCS_RGB too, i.e. it's PPM */
+        jpeg_set_colorspace(cinfoP, JCS_RGB);
+    else
+        /* This default will be based on the in_color_space set above */
+        jpeg_default_colorspace(cinfoP);
+    if (cmdline.max_memory_to_use != -1)
+        cinfoP->mem->max_memory_to_use = cmdline.max_memory_to_use;
+    cinfoP->optimize_coding = cmdline.optimize;
+    if (cmdline.quality == -1) {
+        quality = 75;
+        q_scale_factor = 100;
+    } else {
+        quality = cmdline.quality;
+        q_scale_factor = jpeg_quality_scaling(cmdline.quality);
+    }
+    if (cmdline.smoothing_factor != -1) 
+        cinfoP->smoothing_factor = cmdline.smoothing_factor;
+
+    /* Set quantization tables for selected quality. */
+    /* Some or all may be overridden if user specified --qtables. */
+    jpeg_set_quality(cinfoP, quality, cmdline.force_baseline);
+                   
+    if (cmdline.qtablefile != NULL) {
+        if (! read_quant_tables(cinfoP, cmdline.qtablefile,
+                                q_scale_factor, cmdline.force_baseline)) 
+            pm_error("Can't use quantization table file '%s'.",
+                     cmdline.qtablefile);
+    }
+   
+    if (cmdline.qslots != NULL) {
+        if (! set_quant_slots(cinfoP, cmdline.qslots))
+            pm_error("Bad quantization-table-selectors parameter string '%s'.", 
+                     cmdline.qslots);
+    }
+          
+    if (cmdline.sample != NULL) {
+        if (! set_sample_factors(cinfoP, cmdline.sample))
+            pm_error("Bad sample-factors parameters string '%s'.",
+                     cmdline.sample);
+    }
+
+    if (cmdline.progressive)
+        jpeg_simple_progression(cinfoP);
+
+    if (cmdline.density_spec)
+        setup_jpeg_density(cinfoP, cmdline.density);
+
+    if (cmdline.scans != NULL) {
+        if (! read_scan_script(cinfoP, cmdline.scans)) {
+            pm_message("Error in scan script '%s'.", cmdline.scans);
+        }
+    }
+
+    /* Specify data destination for compression */
+    jpeg_stdio_dest(cinfoP, output_file);
+
+    if (cmdline.verbose) report_compressor(*cinfoP);
+
+    /* Start compressor */
+    jpeg_start_compress(cinfoP, TRUE);
+
+}
+
+
+
+static void
+write_exif_header(struct jpeg_compress_struct * const cinfoP,
+                  const char * const exif_filespec) {
+/*----------------------------------------------------------------------------
+   Generate an APP1 marker in the JFIF output that is an Exif header.
+
+   The contents of the Exif header are in the file with filespec 
+   'exif_filespec' (file spec and contents are not validated).
+
+   exif_filespec = "-" means Standard Input.
+
+   If the file contains just two bytes of zero, don't write any marker
+   but don't recognize any error either.
+-----------------------------------------------------------------------------*/
+    FILE * exif_file;
+    unsigned short length;
+    
+    exif_file = pm_openr(exif_filespec);
+
+    pm_readbigshort(exif_file, (short*)&length);
+
+    if (length == 0) {
+        /* Special value meaning "no header" */
+    } else if (length < 3)
+        pm_error("Invalid length %u at start of exif file", length);
+    else {
+        unsigned char * exif_data;
+        int rc;
+        size_t const data_length = length - 2;  
+            /* Subtract 2 byte length field*/
+
+        assert(data_length > 0);
+
+        exif_data = malloc(data_length);
+        if (exif_data == NULL)
+            pm_error("Unable to allocate %d bytes for exif header buffer",
+                     data_length);
+
+        rc = fread(exif_data, 1, data_length, exif_file);
+
+        if (rc != data_length)
+            pm_error("Premature end of file on exif header file.  Should be "
+                     "%d bytes of data, read only %d", data_length, rc);
+
+        jpeg_write_marker(cinfoP, JPEG_APP0+1, 
+                          (const JOCTET *) exif_data, data_length);
+
+        free(exif_data);
+    }
+    
+    pm_close(exif_file);
+}
+
+                  
+
+static void
+compute_rescaling_array(JSAMPLE ** const rescale_p, const pixval maxval,
+                        const struct jpeg_compress_struct cinfo) {
+/*----------------------------------------------------------------------------
+   Compute the rescaling array for a maximum pixval of 'maxval'.
+   Allocate the memory for it too.
+-----------------------------------------------------------------------------*/
+  const long half_maxval = maxval / 2;
+  long val;
+
+  *rescale_p = (JSAMPLE *)
+    (cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_IMAGE,
+                              (size_t) (((long) maxval + 1L) * 
+                                        sizeof(JSAMPLE)));
+  for (val = 0; val <= maxval; val++) {
+    /* The multiplication here must be done in 32 bits to avoid overflow */
+    (*rescale_p)[val] = (JSAMPLE) ((val*MAXJSAMPLE + half_maxval)/maxval);
+  }
+}
+
+
+
+static void
+translate_row(const pixel pnm_buffer[], 
+              JSAMPLE jpeg_buffer[], 
+              int const width, 
+              int const input_components,
+              const JSAMPLE translate[]) {
+/*----------------------------------------------------------------------------
+   Convert the input row, in pnm format, to an output row in JPEG compressor
+   input format.
+
+   This is a byte for byte copy, translated through the array 'translate'.
+-----------------------------------------------------------------------------*/
+  unsigned int column;
+  /* I'm not sure why the JPEG library data structures don't have some kind
+     of pixel data structure (such that a row buffer is an array of pixels,
+     rather than an array of samples).  But because of this, we have to
+     index jpeg_buffer the old fashioned way.
+     */
+
+  switch (input_components) {
+  case 1:
+      for (column = 0; column < width; column++) 
+          jpeg_buffer[column] = translate[(int)PNM_GET1(pnm_buffer[column])];
+      break;
+  case 3:
+      for (column = 0; column < width; column++) {
+          jpeg_buffer[column*3+0] = 
+              translate[(int)PPM_GETR(pnm_buffer[column])];
+          jpeg_buffer[column*3+1] = 
+              translate[(int)PPM_GETG(pnm_buffer[column])];
+          jpeg_buffer[column*3+2] = 
+              translate[(int)PPM_GETB(pnm_buffer[column])];
+      }
+      break;
+  default:
+      pm_error("INTERNAL ERROR: invalid number of input components in "
+               "translate_row()");
+  }
+
+}
+
+
+
+static void
+convert_scanlines(struct jpeg_compress_struct * const cinfo_p,
+                  FILE *                        const input_file,
+                  pixval                        const maxval,
+                  int                           const input_fmt,
+                  JSAMPLE                             xlate_table[]){
+/*----------------------------------------------------------------------------
+   Read scan lines from the input file, which is already opened in the 
+   netpbm library sense and ready for reading, and write them to the 
+   output JPEG object.  Translate the pnm sample values to JPEG sample
+   values through the thable xlate_table[].
+-----------------------------------------------------------------------------*/
+  xel * pnm_buffer;  
+    /* contains the row of the input image currently being processed,
+       in pnm_readpnmrow format
+    */
+  JSAMPARRAY buffer;
+    /* Row 0 of this array contains the row of the output image currently 
+       being processed, in JPEG compressor input format.  The array only
+       has that one row.
+    */
+
+  /* Allocate the libpnm output and compressor input buffers */
+  buffer = (*cinfo_p->mem->alloc_sarray)
+    ((j_common_ptr) cinfo_p, JPOOL_IMAGE,
+     (unsigned int) cinfo_p->image_width * cinfo_p->input_components, 
+     (unsigned int) 1);
+  
+  pnm_buffer = pnm_allocrow(cinfo_p->image_width);
+
+  while (cinfo_p->next_scanline < cinfo_p->image_height) {
+    if (cinfo_p->err->trace_level > 1) 
+        pm_message("Converting Row %d...", cinfo_p->next_scanline);
+    pnm_readpnmrow(input_file, pnm_buffer, cinfo_p->image_width, 
+                   maxval, input_fmt);
+    translate_row(pnm_buffer, buffer[0], 
+                  cinfo_p->image_width, cinfo_p->input_components,
+                  xlate_table);
+    jpeg_write_scanlines(cinfo_p, buffer, 1);
+    if (cinfo_p->err->trace_level > 1) 
+        pm_message("Done.");
+  }
+
+  pnm_freerow(pnm_buffer);
+  /* Dont' worry about the compressor input buffer; it gets freed 
+     automatically
+  */
+
+}
+
+/*----------------------------------------------------------------------------
+   The functions below here are essentially the file rdswitch.c from
+   the JPEG library.  They perform the functions specifed by the following
+   pnmtojpeg options:
+
+   -qtables file          Read quantization tables from text file
+   -scans file            Read scan script from text file
+   -qslots N[,N,...]      Set component quantization table selectors
+   -sample HxV[,HxV,...]  Set component sampling factors
+-----------------------------------------------------------------------------*/
+
+static int
+text_getc (FILE * file)
+/* Read next char, skipping over any comments (# to end of line) */
+/* A comment/newline sequence is returned as a newline */
+{
+    register int ch;
+  
+    ch = getc(file);
+    if (ch == '#') {
+        do {
+            ch = getc(file);
+        } while (ch != '\n' && ch != EOF);
+    }
+    return ch;
+}
+
+
+static boolean
+read_text_integer (FILE * file, long * result, int * termchar)
+/* Read an unsigned decimal integer from a file, store it in result */
+/* Reads one trailing character after the integer; returns it in termchar */
+{
+    register int ch;
+    register long val;
+  
+    /* Skip any leading whitespace, detect EOF */
+    do {
+        ch = text_getc(file);
+        if (ch == EOF) {
+            *termchar = ch;
+            return FALSE;
+        }
+    } while (isspace(ch));
+  
+    if (! isdigit(ch)) {
+        *termchar = ch;
+        return FALSE;
+    }
+
+    val = ch - '0';
+    while ((ch = text_getc(file)) != EOF) {
+        if (! isdigit(ch))
+            break;
+        val *= 10;
+        val += ch - '0';
+    }
+    *result = val;
+    *termchar = ch;
+    return TRUE;
+}
+
+
+static boolean
+read_quant_tables (j_compress_ptr cinfo, char * filename,
+                   int scale_factor, boolean force_baseline)
+/* Read a set of quantization tables from the specified file.
+ * The file is plain ASCII text: decimal numbers with whitespace between.
+ * Comments preceded by '#' may be included in the file.
+ * There may be one to NUM_QUANT_TBLS tables in the file, each of 64 values.
+ * The tables are implicitly numbered 0,1,etc.
+ * NOTE: does not affect the qslots mapping, which will default to selecting
+ * table 0 for luminance (or primary) components, 1 for chrominance components.
+ * You must use -qslots if you want a different component->table mapping.
+ */
+{
+    FILE * fp;
+    int tblno, i, termchar;
+    long val;
+    unsigned int table[DCTSIZE2];
+
+    if ((fp = fopen(filename, "rb")) == NULL) {
+        pm_message("Can't open table file %s", filename);
+        return FALSE;
+    }
+    tblno = 0;
+
+    while (read_text_integer(fp, &val, &termchar)) { /* read 1st element of table */
+        if (tblno >= NUM_QUANT_TBLS) {
+            pm_message("Too many tables in file %s", filename);
+            fclose(fp);
+            return FALSE;
+        }
+        table[0] = (unsigned int) val;
+        for (i = 1; i < DCTSIZE2; i++) {
+            if (! read_text_integer(fp, &val, &termchar)) {
+                pm_message("Invalid table data in file %s", filename);
+                fclose(fp);
+                return FALSE;
+            }
+            table[i] = (unsigned int) val;
+        }
+        jpeg_add_quant_table(cinfo, tblno, table, scale_factor, 
+                             force_baseline);
+        tblno++;
+    }
+
+    if (termchar != EOF) {
+        pm_message("Non-numeric data in file %s", filename);
+        fclose(fp);
+        return FALSE;
+    }
+
+    fclose(fp);
+    return TRUE;
+}
+
+
+static boolean
+read_scan_integer (FILE * file, long * result, int * termchar)
+/* Variant of read_text_integer that always looks for a non-space termchar;
+ * this simplifies parsing of punctuation in scan scripts.
+ */
+{
+    register int ch;
+
+    if (! read_text_integer(file, result, termchar))
+        return FALSE;
+    ch = *termchar;
+    while (ch != EOF && isspace(ch))
+        ch = text_getc(file);
+    if (isdigit(ch)) {		/* oops, put it back */
+        if (ungetc(ch, file) == EOF)
+            return FALSE;
+        ch = ' ';
+    } else {
+        /* Any separators other than ';' and ':' are ignored;
+         * this allows user to insert commas, etc, if desired.
+         */
+        if (ch != EOF && ch != ';' && ch != ':')
+            ch = ' ';
+    }
+    *termchar = ch;
+    return TRUE;
+}
+
+
+boolean
+read_scan_script (j_compress_ptr cinfo, char * filename)
+/* Read a scan script from the specified text file.
+ * Each entry in the file defines one scan to be emitted.
+ * Entries are separated by semicolons ';'.
+ * An entry contains one to four component indexes,
+ * optionally followed by a colon ':' and four progressive-JPEG parameters.
+ * The component indexes denote which component(s) are to be transmitted
+ * in the current scan.  The first component has index 0.
+ * Sequential JPEG is used if the progressive-JPEG parameters are omitted.
+ * The file is free format text: any whitespace may appear between numbers
+ * and the ':' and ';' punctuation marks.  Also, other punctuation (such
+ * as commas or dashes) can be placed between numbers if desired.
+ * Comments preceded by '#' may be included in the file.
+ * Note: we do very little validity checking here;
+ * jcmaster.c will validate the script parameters.
+ */
+{
+    FILE * fp;
+    int nscans, ncomps, termchar;
+    long val;
+#define MAX_SCANS  100      /* quite arbitrary limit */
+    jpeg_scan_info scans[MAX_SCANS];
+
+    if ((fp = fopen(filename, "r")) == NULL) {
+        pm_message("Can't open scan definition file %s", filename);
+        return FALSE;
+    }
+    nscans = 0;
+
+    while (read_scan_integer(fp, &val, &termchar)) {
+        nscans++;  /* We got another scan */
+        if (nscans > MAX_SCANS) {
+            pm_message("Too many scans defined in file %s", filename);
+            fclose(fp);
+            return FALSE;
+        }
+        scans[nscans-1].component_index[0] = (int) val;
+        ncomps = 1;
+        while (termchar == ' ') {
+            if (ncomps >= MAX_COMPS_IN_SCAN) {
+                pm_message("Too many components in one scan in file %s", 
+                           filename);
+                fclose(fp);
+                return FALSE;
+            }
+            if (! read_scan_integer(fp, &val, &termchar))
+                goto bogus;
+            scans[nscans-1].component_index[ncomps] = (int) val;
+            ncomps++;
+        }
+        scans[nscans-1].comps_in_scan = ncomps;
+        if (termchar == ':') {
+            if (! read_scan_integer(fp, &val, &termchar) || termchar != ' ')
+                goto bogus;
+            scans[nscans-1].Ss = (int) val;
+            if (! read_scan_integer(fp, &val, &termchar) || termchar != ' ')
+                goto bogus;
+            scans[nscans-1].Se = (int) val;
+            if (! read_scan_integer(fp, &val, &termchar) || termchar != ' ')
+                goto bogus;
+            scans[nscans-1].Ah = (int) val;
+            if (! read_scan_integer(fp, &val, &termchar))
+                goto bogus;
+            scans[nscans-1].Al = (int) val;
+        } else {
+            /* set non-progressive parameters */
+            scans[nscans-1].Ss = 0;
+            scans[nscans-1].Se = DCTSIZE2-1;
+            scans[nscans-1].Ah = 0;
+            scans[nscans-1].Al = 0;
+        }
+        if (termchar != ';' && termchar != EOF) {
+        bogus:
+            pm_message("Invalid scan entry format in file %s", filename);
+            fclose(fp);
+            return FALSE;
+        }
+    }
+
+    if (termchar != EOF) {
+        pm_message("Non-numeric data in file %s", filename);
+        fclose(fp);
+        return FALSE;
+    }
+
+    if (nscans > 0) {
+        /* Stash completed scan list in cinfo structure.  NOTE: in
+         * this program, JPOOL_IMAGE is the right lifetime for this
+         * data, but if you want to compress multiple images you'd
+         * want JPOOL_PERMANENT.  
+         */
+        const unsigned int scan_info_size = nscans * sizeof(jpeg_scan_info);
+        jpeg_scan_info * const scan_info = 
+            (jpeg_scan_info *)
+            (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
+                                        scan_info_size);
+        memcpy(scan_info, scans, scan_info_size);
+        cinfo->scan_info = scan_info;
+        cinfo->num_scans = nscans;
+    }
+
+    fclose(fp);
+    return TRUE;
+}
+
+
+static boolean
+set_quant_slots (j_compress_ptr cinfo, char *arg)
+/* Process a quantization-table-selectors parameter string, of the form
+ *     N[,N,...]
+ * If there are more components than parameters, the last value is replicated.
+ */
+{
+    int val = 0;			/* default table # */
+    int ci;
+    char ch;
+
+    for (ci = 0; ci < MAX_COMPONENTS; ci++) {
+        if (*arg) {
+            ch = ',';			/* if not set by sscanf, will be ',' */
+            if (sscanf(arg, "%d%c", &val, &ch) < 1)
+                return FALSE;
+            if (ch != ',')		/* syntax check */
+                return FALSE;
+            if (val < 0 || val >= NUM_QUANT_TBLS) {
+                pm_message("Invalid quantization table number: %d.  "
+                           "JPEG quantization tables are numbered 0..%d",
+                           val, NUM_QUANT_TBLS - 1);
+                return FALSE;
+            }
+            cinfo->comp_info[ci].quant_tbl_no = val;
+            while (*arg && *arg++ != ',') 
+                /* advance to next segment of arg string */
+                ;
+        } else {
+            /* reached end of parameter, set remaining components to last tbl*/
+            cinfo->comp_info[ci].quant_tbl_no = val;
+        }
+    }
+    return TRUE;
+}
+
+
+static boolean
+set_sample_factors (j_compress_ptr cinfo, char *arg)
+/* Process a sample-factors parameter string, of the form
+ *     HxV[,HxV,...]
+ * If there are more components than parameters, "1x1" is assumed for the rest.
+ */
+{
+    int ci, val1, val2;
+    char ch1, ch2;
+
+    for (ci = 0; ci < MAX_COMPONENTS; ci++) {
+        if (*arg) {
+            ch2 = ',';		/* if not set by sscanf, will be ',' */
+            if (sscanf(arg, "%d%c%d%c", &val1, &ch1, &val2, &ch2) < 3)
+                return FALSE;
+            if ((ch1 != 'x' && ch1 != 'X') || ch2 != ',') /* syntax check */
+                return FALSE;
+            if (val1 <= 0 || val1 > 4) {
+                pm_message("Invalid sampling factor: %d.  " 
+                           "JPEG sampling factors must be 1..4", val1);
+                return FALSE;
+            }
+            if (val2 <= 0 || val2 > 4) {
+                pm_message("Invalid sampling factor: %d.  "
+                           "JPEG sampling factors must be 1..4", val2);
+                return FALSE;
+            }
+            cinfo->comp_info[ci].h_samp_factor = val1;
+            cinfo->comp_info[ci].v_samp_factor = val2;
+            while (*arg && *arg++ != ',') 
+                /* advance to next segment of arg string */
+                ;
+        } else {
+            /* reached end of parameter, set remaining components 
+               to 1x1 sampling */
+            cinfo->comp_info[ci].h_samp_factor = 1;
+            cinfo->comp_info[ci].v_samp_factor = 1;
+        }
+    }
+    return TRUE;
+}
+
+
+
+int
+main(int argc, char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    struct jpeg_compress_struct cinfo;
+    struct jpeg_error_mgr jerr;
+    FILE *input_file;
+    FILE * output_file;
+    int height;  
+        /* height of the input image in rows, as specified by its header */
+    int width;   
+        /* width of the input image in columns, as specified by its header */
+    pixval maxval;  
+        /* maximum value of an input pixel component, as specified by header */
+    int input_fmt;
+        /* The input format, as determined by its header.  */
+    JSAMPLE *rescale;         /* => maxval-remapping array, or NULL */
+        /* This is an array that maps each possible pixval in the input to
+           a new value such that while the range of the input values is
+           0 .. maxval, the range of the output values is 0 .. MAXJSAMPLE.
+        */
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    input_file = pm_openr(cmdline.input_filespec);
+    free(cmdline.input_filespec);
+
+    output_file = stdout;
+
+    /* Open the pnm input */
+    pnm_readpnminit(input_file, &width, &height, &maxval, &input_fmt);
+    if (cmdline.verbose) {
+        pm_message("Input file has format %c%c.\n"
+                   "It has %d rows of %d columns of pixels "
+                   "with max sample value of %d.", 
+                   (char) (input_fmt/256), (char) (input_fmt % 256),
+                   height, width, maxval);
+    }
+
+    setup_jpeg(&cinfo, &jerr, cmdline, width, height, maxval, input_fmt,
+               output_file);
+
+    compute_rescaling_array(&rescale, maxval, cinfo);
+
+    if (cmdline.comment) 
+        jpeg_write_marker(&cinfo, JPEG_COM, (const JOCTET *) cmdline.comment, 
+                          strlen(cmdline.comment));
+
+    if (cmdline.exif_filespec)
+        write_exif_header(&cinfo, cmdline.exif_filespec);
+    
+    /* Translate and copy over the actual scanlines */
+    convert_scanlines(&cinfo, input_file, maxval, input_fmt, rescale);
+
+    /* Finish compression and release memory */
+    jpeg_finish_compress(&cinfo);
+    jpeg_destroy_compress(&cinfo);
+
+    /* Close files, if we opened them */
+    if (input_file != stdin)
+        fclose(input_file);
+
+    /* Program may have exited with non-zero completion code via
+       various function calls above. 
+    */
+    return jerr.num_warnings > 0 ? EXIT_WARNING : EXIT_SUCCESS;
+}
diff --git a/converter/other/pnmtopalm/LICENSE b/converter/other/pnmtopalm/LICENSE
new file mode 100644
index 00000000..740b6080
--- /dev/null
+++ b/converter/other/pnmtopalm/LICENSE
@@ -0,0 +1,16 @@
+LICENSE FOR PNMTOPALM SUBPACKAGE OF NETPBM.  THIS LICENSE APPLIES TO ALL
+THE MATERIALS IN THE PNMTOPALM DIRECTORY OF THE NETPBM SOURCE PACKAGE.
+
+Copyright 1995-2001 by Ian Goldberg, George Caswell, and Bill Janssen.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted, provided
+that the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.  This software is provided "as is" without express or
+implied warranty.
+
+
+(Bryan Henderson extracted this license memorandum from the comments in the
+original man page nroff source supplied to him with the rest of the Pnmtopalm
+package in January 2001 by Bill Janssen).
diff --git a/converter/other/pnmtopalm/Makefile b/converter/other/pnmtopalm/Makefile
new file mode 100644
index 00000000..2a76297e
--- /dev/null
+++ b/converter/other/pnmtopalm/Makefile
@@ -0,0 +1,35 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/other/pnmtopalm
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+BINARIES = palmtopnm pnmtopalm
+SCRIPTS =
+OBJECTS = $(BINARIES:%=%.o) palmcolormap.o
+MERGE_OBJECTS = $(BINARIES:%=%.o2) palmcolormap.o
+MERGEBINARIES = $(BINARIES)
+DATAFILES = palmcolor8.map palmgray1.map palmgray2.map palmgray4.map
+
+all: $(BINARIES)
+
+include $(SRCDIR)/Makefile.common
+
+LIBOPTS = $(shell $(LIBOPT) $(NETPBMLIB))
+
+$(BINARIES): %: %.o palmcolormap.o $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $< palmcolormap.o $(LIBOPTS) \
+	  $(MATHLIB) $(LDLIBS) $(RPATH) $(LADD)
+
+gen_palm_colormap : $(SUBDIR)/gen_palm_colormap.c palmcolormap.o
+	$(CC) $(INCLUDES) $(CFLAGS) $(LDFLAGS) -o $@ $< palmcolormap.o \
+	  $(LIBOPTS) $(MATHLIB) $(LDLIBS) $(LADD)
+
+
+clean: cleanspecial
+.PHONY: cleanspecial
+cleanspecial:
+	rm -f gen_palm_colormap
diff --git a/converter/other/pnmtopalm/README b/converter/other/pnmtopalm/README
new file mode 100644
index 00000000..ebae9492
--- /dev/null
+++ b/converter/other/pnmtopalm/README
@@ -0,0 +1,57 @@
+This is version 1.0 of pnmpalm, netpbm converters for Palm pixmaps.
+
+It's derived from version 1.0 of ppmtoTbmp.  The original 'ppmtoTbmp'
+program has been renamed 'pnmtopalm', and has been rewritten to handle
+the various bitmaps that exist as of PalmOS 3.5, and to handle
+colormaps, a transparent color, and the various compression formats
+recognized by Palm OS.  The 'Tbmptopnm' program is now 'palmtopnm',
+and has been updated to handle the various newer bitmap formats as
+well.  Man pages for 'pnmtopalm' and 'palmtopnm' have been added.  See
+those man pages for more information.  Note that command-line switches
+have changed.
+
+As with Tbmptoppm, ppm images must be quantified properly before
+being passed to pnmtopalm.  Several colormap files are provided to
+use for this:
+ - palmgray1.map -- 1-bit grayscale (i.e. monochrome)
+ - palmgray2.map -- 2-bit grayscale
+ - palmgray4.map -- 4-bit grayscale
+ - palmcolor8.map -- 8-bit color, using the default color palette
+
+-- Bill Janssen  <bill@janssen.org>
+
+
+One source of information on Palm image formats is
+http://www.kawt.de/doc/palmimages.html.
+
+
+
+The original ppmtoTbmp README read as follows:
+
+This is version 1.0 of ppmtoTbmp, a ppm to Pilot bitmap converter.
+
+To compile it, you'll need the netpbm package.  Last I checked, it was
+available at "ftp://ftp.x.org/contrib/utilities/netpbm-1mar1994.p1.tar.gz".
+Change the Makefile to point to the directory that contains the netpbm
+header files, and make.
+
+Usage: ppmtoTbmp [-2bit] [file.ppm]
+
+If the ppm file is not specified, one is read from stdin.  The -2bit option
+produces a 2-bit bitmap instead of the normal 1-bit bitmap.  The ppm must
+have at most 4 colors in it (for 2-bit) or at most 2 colors (for 1-bit).
+Common invokations might be:
+
+xbmtopbm icon.xbm | ppmquant -fs -map q2.map | ppmtoTbmp > tAIB03e8.bin
+giftopnm image.gif | ppmquant -fs -map q4.map | ppmtoTbmp -2bit > Tbmp0bb8.bin
+
+(q2.map and q4.map are trivial 2 and 4 color maps, respectively, and are
+included in this distribution.)
+
+To include the resulting bitmap in a .prc file, just name the output
+tAIB03e8.bin (for an application icon) or Tbmpxxxx.bin (for a form bitmap,
+where xxxx is replaced by four hex digits giving the bitmap ID).
+
+I'll probably make Tbmptopnm later, and put it in the 1.1 release.
+
+   - Ian Goldberg <iang@cs.berkeley.edu>
diff --git a/converter/other/pnmtopalm/gen_palm_colormap.c b/converter/other/pnmtopalm/gen_palm_colormap.c
new file mode 100644
index 00000000..4b65e631
--- /dev/null
+++ b/converter/other/pnmtopalm/gen_palm_colormap.c
@@ -0,0 +1,24 @@
+/* gen_palm_colormap.c - generate a ppm file containing the default Palm colormap
+ *
+ * Bill Janssen  <bill@janssen.org>
+ */
+
+#include "pnm.h"
+
+#include "palm.h"
+
+int main( int argc, char **argv ) {
+
+  int i;
+  Color_s current;
+  Colormap default_map = palmcolor_build_default_8bit_colormap ();
+
+  printf("P3\n%d 1\n255\n", default_map->ncolors);
+  for (i = 0;  i < default_map->ncolors;  i++) {
+    current = default_map->color_entries[i];
+    printf ("%d %d %d\n", (current & 0xFF0000) >> 16, (current & 0xFF00) >> 8, (current & 0xFF));
+    /* printf ("%x:  %d %d %d\n", (current & 0xFF000000) >> 24, (current & 0xFF0000) >> 16, (current & 0xFF00) >> 8, (current & 0xFF)); */
+  };
+  return 0;
+}
+
diff --git a/converter/other/pnmtopalm/palm.h b/converter/other/pnmtopalm/palm.h
new file mode 100644
index 00000000..170c8cec
--- /dev/null
+++ b/converter/other/pnmtopalm/palm.h
@@ -0,0 +1,66 @@
+#ifndef PALM_H_INCLUDED
+#define PALM_H_INCLUDED
+
+#define PALM_IS_COMPRESSED_FLAG     0x8000
+#define PALM_HAS_COLORMAP_FLAG      0x4000
+#define PALM_HAS_TRANSPARENCY_FLAG  0x2000
+#define PALM_INDIRECT_BITMAP        0x1000  /* Palm says internal use only */
+#define PALM_FOR_SCREEN             0x0800  /* Palm says internal use only */
+#define PALM_DIRECT_COLOR_FLAG      0x0400
+#define PALM_INDIRECT_COLORMAP      0x0200  /* Palm says internal use only */ 
+#define PALM_NO_DITHER_FLAG         0x0100  /* rather mysterious */
+
+#define PALM_COMPRESSION_SCANLINE   0x00
+#define PALM_COMPRESSION_RLE        0x01
+#define PALM_COMPRESSION_PACKBITS   0x02
+#define PALM_COMPRESSION_END        0x03    /* Palm says internal use only */
+#define PALM_COMPRESSION_BEST       0x64    /* Palm says internal use only */
+#define PALM_COMPRESSION_NONE       0xFF    /* Palm says internal use only */
+
+#define PALM_DENSITY_LOW             72
+#define PALM_DENSITY_ONEANDAHALF    108
+#define PALM_DENSITY_DOUBLE         144
+#define PALM_DENSITY_TRIPLE         216
+#define PALM_DENSITY_QUADRUPLE      288
+
+#define PALM_FORMAT_INDEXED     0x00
+#define PALM_FORMAT_565         0x01
+#define PALM_FORMAT_565LE       0x02    /* Palm says internal use only */
+#define PALM_FORMAT_INDEXEDLE   0x03    /* Palm says internal use only */
+
+typedef unsigned long Color_s;
+
+typedef Color_s * Color;
+
+typedef struct {
+    unsigned int nentries;
+        /* number of allocated entries in 'color_entries' */
+    unsigned int ncolors;
+        /* number of colors actually in 'color_entries' -- entries are
+           filled from 0 consecutively, one color per entry.
+        */
+    Color_s * color_entries;  /* Array of colors */
+} Colormap_s;
+
+typedef Colormap_s * Colormap;
+
+int
+palmcolor_compare_indices(const void * const p1,
+                          const void * const p2);
+
+int
+palmcolor_compare_colors(const void * const p1,
+                         const void * const p2);
+
+Colormap
+palmcolor_build_custom_8bit_colormap(unsigned int const rows,
+                                     unsigned int const cols,
+                                     pixel **     const pixels);
+
+Colormap
+palmcolor_build_default_8bit_colormap(void);
+
+Colormap
+palmcolor_read_colormap (FILE * const ifP);
+
+#endif
diff --git a/converter/other/pnmtopalm/palmcolor8.map b/converter/other/pnmtopalm/palmcolor8.map
new file mode 100644
index 00000000..2e054616
--- /dev/null
+++ b/converter/other/pnmtopalm/palmcolor8.map
@@ -0,0 +1,235 @@
+P3
+232 1
+255
+0 0 0
+0 0 0
+0 0 51
+0 0 102
+0 0 153
+0 0 204
+0 0 255
+0 51 0
+0 51 51
+0 51 102
+0 51 153
+0 51 204
+0 51 255
+0 102 0
+0 102 51
+0 102 102
+0 102 153
+0 102 204
+0 102 255
+0 128 0
+0 128 128
+0 153 0
+0 153 51
+0 153 102
+0 153 153
+0 153 204
+0 153 255
+0 204 0
+0 204 51
+0 204 102
+0 204 153
+0 204 204
+0 204 255
+0 255 0
+0 255 51
+0 255 102
+0 255 153
+0 255 204
+0 255 255
+17 17 17
+34 34 34
+51 0 0
+51 0 51
+51 0 102
+51 0 153
+51 0 204
+51 0 255
+51 51 0
+51 51 51
+51 51 102
+51 51 153
+51 51 204
+51 51 255
+51 102 0
+51 102 51
+51 102 102
+51 102 153
+51 102 204
+51 102 255
+51 153 0
+51 153 51
+51 153 102
+51 153 153
+51 153 204
+51 153 255
+51 204 0
+51 204 51
+51 204 102
+51 204 153
+51 204 204
+51 204 255
+51 255 0
+51 255 51
+51 255 102
+51 255 153
+51 255 204
+51 255 255
+68 68 68
+85 85 85
+102 0 0
+102 0 51
+102 0 102
+102 0 153
+102 0 204
+102 0 255
+102 51 0
+102 51 51
+102 51 102
+102 51 153
+102 51 204
+102 51 255
+102 102 0
+102 102 51
+102 102 102
+102 102 153
+102 102 204
+102 102 255
+102 153 0
+102 153 51
+102 153 102
+102 153 153
+102 153 204
+102 153 255
+102 204 0
+102 204 51
+102 204 102
+102 204 153
+102 204 204
+102 204 255
+102 255 0
+102 255 51
+102 255 102
+102 255 153
+102 255 204
+102 255 255
+119 119 119
+128 0 0
+128 0 128
+136 136 136
+153 0 0
+153 0 51
+153 0 102
+153 0 153
+153 0 204
+153 0 255
+153 51 0
+153 51 51
+153 51 102
+153 51 153
+153 51 204
+153 51 255
+153 102 0
+153 102 51
+153 102 102
+153 102 153
+153 102 204
+153 102 255
+153 153 0
+153 153 51
+153 153 102
+153 153 153
+153 153 204
+153 153 255
+153 204 0
+153 204 51
+153 204 102
+153 204 153
+153 204 204
+153 204 255
+153 255 0
+153 255 51
+153 255 102
+153 255 153
+153 255 204
+153 255 255
+170 170 170
+187 187 187
+192 192 192
+204 0 0
+204 0 51
+204 0 102
+204 0 153
+204 0 204
+204 0 255
+204 51 0
+204 51 51
+204 51 102
+204 51 153
+204 51 204
+204 51 255
+204 102 0
+204 102 51
+204 102 102
+204 102 153
+204 102 204
+204 102 255
+204 153 0
+204 153 51
+204 153 102
+204 153 153
+204 153 204
+204 153 255
+204 204 0
+204 204 51
+204 204 102
+204 204 153
+204 204 204
+204 204 255
+204 255 0
+204 255 51
+204 255 102
+204 255 153
+204 255 204
+204 255 255
+221 221 221
+238 238 238
+255 0 0
+255 0 51
+255 0 102
+255 0 153
+255 0 204
+255 0 255
+255 51 0
+255 51 51
+255 51 102
+255 51 153
+255 51 204
+255 51 255
+255 102 0
+255 102 51
+255 102 102
+255 102 153
+255 102 204
+255 102 255
+255 153 0
+255 153 51
+255 153 102
+255 153 153
+255 153 204
+255 153 255
+255 204 0
+255 204 51
+255 204 102
+255 204 153
+255 204 204
+255 204 255
+255 255 0
+255 255 51
+255 255 102
+255 255 153
+255 255 204
+255 255 255
diff --git a/converter/other/pnmtopalm/palmcolormap.c b/converter/other/pnmtopalm/palmcolormap.c
new file mode 100644
index 00000000..a1a1cec1
--- /dev/null
+++ b/converter/other/pnmtopalm/palmcolormap.c
@@ -0,0 +1,277 @@
+/* See LICENSE file for licensing information.
+*/
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "pnm.h"
+
+#include "palm.h"
+
+int
+palmcolor_compare_indices(const void * const p1,
+                          const void * const p2) {
+
+    if ((*((Color) p1) & 0xFF000000) < (*((Color) p2) & 0xFF000000))
+        return -1;
+    else if ((*((Color) p1) & 0xFF000000) > (*((Color) p2) & 0xFF000000))
+        return 1;
+    else
+        return 0;
+}
+
+
+
+int
+palmcolor_compare_colors(const void * const p1,
+                         const void * const p2) {
+
+    unsigned long const val1 = *((const unsigned long *) p1) & 0xFFFFFF;
+    unsigned long const val2 = *((const unsigned long *) p2) & 0xFFFFFF;
+
+    if (val1 < val2)
+        return -1;
+    else if (val1 > val2)
+        return 1;
+    else
+        return 0;
+}
+
+/***********************************************************************
+ ***********************************************************************
+ ***********************************************************************
+ ******* colortables from pilrc-2.6/bitmap.c ***************************
+ ***********************************************************************
+ ***********************************************************************
+ ***********************************************************************/
+
+#if 0
+
+/*
+ * The 1bit-2 color system palette for Palm Computing Devices.
+ */
+static int PalmPalette1bpp[2][3] = 
+{
+  { 255, 255, 255}, {   0,   0,   0 }
+};
+
+/*
+ * The 2bit-4 color system palette for Palm Computing Devices.
+ */
+static int PalmPalette2bpp[4][3] = 
+{
+  { 255, 255, 255}, { 192, 192, 192}, { 128, 128, 128 }, {   0,   0,   0 }
+};
+
+/*
+ * The 4bit-16 color system palette for Palm Computing Devices.
+ */
+static int PalmPalette4bpp[16][3] = 
+{
+  { 255, 255, 255}, { 238, 238, 238 }, { 221, 221, 221 }, { 204, 204, 204 },
+  { 187, 187, 187}, { 170, 170, 170 }, { 153, 153, 153 }, { 136, 136, 136 },
+  { 119, 119, 119}, { 102, 102, 102 }, {  85,  85,  85 }, {  68,  68,  68 },
+  {  51,  51,  51}, {  34,  34,  34 }, {  17,  17,  17 }, {   0,   0,   0 }
+};
+
+/*
+ * The 4bit-16 color system palette for Palm Computing Devices.
+ */
+static int PalmPalette4bppColor[16][3] = 
+{
+  { 255, 255, 255}, { 128, 128, 128 }, { 128,   0,   0 }, { 128, 128,   0 },
+  {   0, 128,   0}, {   0, 128, 128 }, {   0,   0, 128 }, { 128,   0, 128 },
+  { 255,   0, 255}, { 192, 192, 192 }, { 255,   0,   0 }, { 255, 255,   0 },
+  {   0, 255,   0}, {   0, 255, 255 }, {   0,   0, 255 }, {   0,   0,   0 }
+};
+
+#endif  /* 0 */
+
+/*
+ * The 8bit-256 color system palette for Palm Computing Devices.
+ *
+ * NOTE:  only the first 231, plus the last one, are valid.
+ */
+static int PalmPalette8bpp[256][3] = 
+{
+  { 255, 255, 255 }, { 255, 204, 255 }, { 255, 153, 255 }, { 255, 102, 255 }, 
+  { 255,  51, 255 }, { 255,   0, 255 }, { 255, 255, 204 }, { 255, 204, 204 }, 
+  { 255, 153, 204 }, { 255, 102, 204 }, { 255,  51, 204 }, { 255,   0, 204 }, 
+  { 255, 255, 153 }, { 255, 204, 153 }, { 255, 153, 153 }, { 255, 102, 153 }, 
+  { 255,  51, 153 }, { 255,   0, 153 }, { 204, 255, 255 }, { 204, 204, 255 },
+  { 204, 153, 255 }, { 204, 102, 255 }, { 204,  51, 255 }, { 204,   0, 255 },
+  { 204, 255, 204 }, { 204, 204, 204 }, { 204, 153, 204 }, { 204, 102, 204 },
+  { 204,  51, 204 }, { 204,   0, 204 }, { 204, 255, 153 }, { 204, 204, 153 },
+  { 204, 153, 153 }, { 204, 102, 153 }, { 204,  51, 153 }, { 204,   0, 153 },
+  { 153, 255, 255 }, { 153, 204, 255 }, { 153, 153, 255 }, { 153, 102, 255 },
+  { 153,  51, 255 }, { 153,   0, 255 }, { 153, 255, 204 }, { 153, 204, 204 },
+  { 153, 153, 204 }, { 153, 102, 204 }, { 153,  51, 204 }, { 153,   0, 204 },
+  { 153, 255, 153 }, { 153, 204, 153 }, { 153, 153, 153 }, { 153, 102, 153 },
+  { 153,  51, 153 }, { 153,   0, 153 }, { 102, 255, 255 }, { 102, 204, 255 },
+  { 102, 153, 255 }, { 102, 102, 255 }, { 102,  51, 255 }, { 102,   0, 255 },
+  { 102, 255, 204 }, { 102, 204, 204 }, { 102, 153, 204 }, { 102, 102, 204 },
+  { 102,  51, 204 }, { 102,   0, 204 }, { 102, 255, 153 }, { 102, 204, 153 },
+  { 102, 153, 153 }, { 102, 102, 153 }, { 102,  51, 153 }, { 102,   0, 153 },
+  {  51, 255, 255 }, {  51, 204, 255 }, {  51, 153, 255 }, {  51, 102, 255 },
+  {  51,  51, 255 }, {  51,   0, 255 }, {  51, 255, 204 }, {  51, 204, 204 },
+  {  51, 153, 204 }, {  51, 102, 204 }, {  51,  51, 204 }, {  51,   0, 204 },
+  {  51, 255, 153 }, {  51, 204, 153 }, {  51, 153, 153 }, {  51, 102, 153 },
+  {  51,  51, 153 }, {  51,   0, 153 }, {   0, 255, 255 }, {   0, 204, 255 },
+  {   0, 153, 255 }, {   0, 102, 255 }, {   0,  51, 255 }, {   0,   0, 255 },
+  {   0, 255, 204 }, {   0, 204, 204 }, {   0, 153, 204 }, {   0, 102, 204 },
+  {   0,  51, 204 }, {   0,   0, 204 }, {   0, 255, 153 }, {   0, 204, 153 },
+  {   0, 153, 153 }, {   0, 102, 153 }, {   0,  51, 153 }, {   0,   0, 153 },
+  { 255, 255, 102 }, { 255, 204, 102 }, { 255, 153, 102 }, { 255, 102, 102 },
+  { 255,  51, 102 }, { 255,   0, 102 }, { 255, 255,  51 }, { 255, 204,  51 },
+  { 255, 153,  51 }, { 255, 102,  51 }, { 255,  51,  51 }, { 255,   0,  51 },
+  { 255, 255,   0 }, { 255, 204,   0 }, { 255, 153,   0 }, { 255, 102,   0 },
+  { 255,  51,   0 }, { 255,   0,   0 }, { 204, 255, 102 }, { 204, 204, 102 },
+  { 204, 153, 102 }, { 204, 102, 102 }, { 204,  51, 102 }, { 204,   0, 102 },
+  { 204, 255,  51 }, { 204, 204,  51 }, { 204, 153,  51 }, { 204, 102,  51 },
+  { 204,  51,  51 }, { 204,   0,  51 }, { 204, 255,   0 }, { 204, 204,   0 },
+  { 204, 153,   0 }, { 204, 102,   0 }, { 204,  51,   0 }, { 204,   0,   0 },
+  { 153, 255, 102 }, { 153, 204, 102 }, { 153, 153, 102 }, { 153, 102, 102 },
+  { 153,  51, 102 }, { 153,   0, 102 }, { 153, 255,  51 }, { 153, 204,  51 },
+  { 153, 153,  51 }, { 153, 102,  51 }, { 153,  51,  51 }, { 153,   0,  51 },
+  { 153, 255,   0 }, { 153, 204,   0 }, { 153, 153,   0 }, { 153, 102,   0 },
+  { 153,  51,   0 }, { 153,   0,   0 }, { 102, 255, 102 }, { 102, 204, 102 },
+  { 102, 153, 102 }, { 102, 102, 102 }, { 102,  51, 102 }, { 102,   0, 102 },
+  { 102, 255,  51 }, { 102, 204,  51 }, { 102, 153,  51 }, { 102, 102,  51 },
+  { 102,  51,  51 }, { 102,   0,  51 }, { 102, 255,   0 }, { 102, 204,   0 },
+  { 102, 153,   0 }, { 102, 102,   0 }, { 102,  51,   0 }, { 102,   0,   0 },
+  {  51, 255, 102 }, {  51, 204, 102 }, {  51, 153, 102 }, {  51, 102, 102 },
+  {  51,  51, 102 }, {  51,   0, 102 }, {  51, 255,  51 }, {  51, 204,  51 },
+  {  51, 153,  51 }, {  51, 102,  51 }, {  51,  51,  51 }, {  51,   0,  51 },
+  {  51, 255,   0 }, {  51, 204,   0 }, {  51, 153,   0 }, {  51, 102,   0 },
+  {  51,  51,   0 }, {  51,   0,   0 }, {   0, 255, 102 }, {   0, 204, 102 },
+  {   0, 153, 102 }, {   0, 102, 102 }, {   0,  51, 102 }, {   0,   0, 102 },
+  {   0, 255,  51 }, {   0, 204,  51 }, {   0, 153,  51 }, {   0, 102,  51 },
+  {   0,  51,  51 }, {   0,   0,  51 }, {   0, 255,   0 }, {   0, 204,   0 },
+  {   0, 153,   0 }, {   0, 102,   0 }, {   0,  51,   0 }, {  17,  17,  17 },
+  {  34,  34,  34 }, {  68,  68,  68 }, {  85,  85,  85 }, { 119, 119, 119 },
+  { 136, 136, 136 }, { 170, 170, 170 }, { 187, 187, 187 }, { 221, 221, 221 },
+  { 238, 238, 238 }, { 192, 192, 192 }, { 128,   0,   0 }, { 128,   0, 128 },
+  {   0, 128,   0 }, {   0, 128, 128 }, {   0,   0,   0 }, {   0,   0,   0 },
+  {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 },
+  {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 },
+  {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 },
+  {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 },
+  {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 },
+  {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 }
+};
+
+
+
+Colormap
+palmcolor_build_default_8bit_colormap(void) {
+
+    unsigned int i;
+
+    Colormap cm;
+
+    MALLOCVAR_NOFAIL(cm);
+    cm->nentries = 232;
+    MALLOCARRAY_NOFAIL(cm->color_entries, cm->nentries);
+
+    /* Fill in the colors */
+    for (i = 0; i < 231; ++i) {
+        cm->color_entries[i] = ((i << 24) |
+                                (PalmPalette8bpp[i][0] << 16) |
+                                (PalmPalette8bpp[i][1] << 8) |
+                                (PalmPalette8bpp[i][2]));
+    }
+    cm->color_entries[231] = 0xFF000000;
+    cm->ncolors = 232;
+
+    /* now sort the table */
+    qsort (cm->color_entries, cm->ncolors, sizeof(Color_s), 
+           palmcolor_compare_colors);
+    return cm;
+}
+
+
+
+Colormap
+palmcolor_build_custom_8bit_colormap(unsigned int const rows,
+                                     unsigned int const cols,
+                                     pixel **     const pixels) {
+    unsigned int row;
+    Colormap colormap;
+
+    MALLOCVAR_NOFAIL(colormap);
+    colormap->nentries = 256;
+    MALLOCARRAY_NOFAIL(colormap->color_entries, colormap->nentries);
+    colormap->ncolors = 0;  /* initial value */
+    
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            Color found;
+            Color_s temp;
+
+            temp = ((PPM_GETR(pixels[row][col]) << 16) |
+                    (PPM_GETG(pixels[row][col]) << 8) |
+                    PPM_GETB(pixels[row][col]));
+            found = (bsearch (&temp,
+                              colormap->color_entries, colormap->ncolors,
+                              sizeof(Color_s), palmcolor_compare_colors));
+            if (!found) {
+                if (colormap->ncolors >= colormap->nentries)
+                    pm_error("Too many colors for custom colormap "
+                             "(max 256).  "
+                             "Try using pnmquant to reduce the number "
+                             "of colors.");
+                else {
+                    /* add the new color, and re-sort */
+                    temp |= ((colormap->ncolors) << 24);
+                    colormap->color_entries[colormap->ncolors] = temp;
+                    colormap->ncolors += 1;
+                    qsort(colormap->color_entries, colormap->ncolors, 
+                          sizeof(Color_s), palmcolor_compare_colors);
+                }
+            }
+        }
+    }
+    return colormap;
+}
+
+
+
+Colormap
+palmcolor_read_colormap (FILE * const ifP) {
+
+    unsigned short ncolors;
+    Colormap retval;
+    int rc;
+    
+    rc = pm_readbigshort(ifP, (short *) &ncolors);
+    if (rc != 0)
+        retval = NULL;
+    else {
+        long colorentry;
+        Colormap colormap;
+        unsigned int i;
+        bool error;
+
+        MALLOCVAR_NOFAIL(colormap);
+        colormap->nentries = ncolors;
+        MALLOCARRAY_NOFAIL(colormap->color_entries, colormap->nentries);
+        
+        for (i = 0, error = FALSE;  i < ncolors && !error;  ++i) {
+            int rc;
+            rc = pm_readbiglong(ifP, &colorentry);
+            if (rc != 0)
+                error = TRUE;
+            else
+                colormap->color_entries[i] = (colorentry & 0xFFFFFFFF);
+        }
+        if (error) {
+            free (colormap->color_entries);
+            free (colormap);
+            retval = NULL;
+        } else {
+            colormap->ncolors = ncolors;
+            retval = colormap;
+        }
+    }
+    return retval;
+}
diff --git a/converter/other/pnmtopalm/palmgray1.map b/converter/other/pnmtopalm/palmgray1.map
new file mode 100644
index 00000000..96e20400
--- /dev/null
+++ b/converter/other/pnmtopalm/palmgray1.map
@@ -0,0 +1,4 @@
+P1
+# Black and white palette
+1 2
+0 1
diff --git a/converter/other/pnmtopalm/palmgray2.map b/converter/other/pnmtopalm/palmgray2.map
new file mode 100644
index 00000000..e8c95b01
--- /dev/null
+++ b/converter/other/pnmtopalm/palmgray2.map
@@ -0,0 +1,6 @@
+P2
+# 4 level grayscale palette
+2 2
+3
+0 1
+2 3
diff --git a/converter/other/pnmtopalm/palmgray4.map b/converter/other/pnmtopalm/palmgray4.map
new file mode 100644
index 00000000..bba2e6b8
--- /dev/null
+++ b/converter/other/pnmtopalm/palmgray4.map
@@ -0,0 +1,8 @@
+P2
+# 16 level grayscale palette
+4 4
+15
+ 0  1  2  3
+ 4  5  6  7
+ 8  9 10 11
+12 13 14 15
diff --git a/converter/other/pnmtopalm/palmtopnm.c b/converter/other/pnmtopalm/palmtopnm.c
new file mode 100644
index 00000000..9cd695e3
--- /dev/null
+++ b/converter/other/pnmtopalm/palmtopnm.c
@@ -0,0 +1,1131 @@
+/******************************************************************************
+                             palmtopnm
+*******************************************************************************
+  By Bryan Henderson, San Jose, California, June 2004.
+
+  Inspired by and using methods from Tbmptopnm by Ian Goldberg
+  <iang@cs.berkeley.edu>, and Bill Janssen <bill@janssen.org>.
+
+  Major fixes and new capability added by Paul Bolle <pebolle@tiscali.nl>
+  in late 2004 / early 2005.
+
+  Bryan's work is contributed to the public domain by its author.
+******************************************************************************/
+
+#include <string.h>
+#include <assert.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+#include "palm.h"
+
+
+
+enum palmCompressionType {
+    COMPRESSION_NONE, 
+    COMPRESSION_RLE, 
+    COMPRESSION_SCANLINE,
+    COMPRESSION_PACKBITS
+};
+
+struct palmHeader {
+    unsigned short cols;
+    unsigned short rows;
+    unsigned short bytesPerRow;
+    unsigned short flags;
+    bool           directColor;
+        /* The header indicates a direct color raster, either by flag
+           (the old way) or by pixel format (the new way)
+        */
+    bool           hasColormap;
+    bool           hasTransparency;
+    unsigned char  pixelSizeCode;
+    unsigned int   pixelSize;
+    unsigned char  version;
+    unsigned int   transparentIndex;
+    enum palmCompressionType compressionType;
+    /* version 3 encoding specific */
+    unsigned char  size;
+    unsigned char  pixelFormat;
+    unsigned short density;
+    unsigned long  transparentValue;
+};
+
+
+
+struct directPixelFormat {
+    unsigned int redbits;
+    unsigned int greenbits;
+    unsigned int bluebits;
+};
+
+
+
+struct directColorInfo {
+    struct directPixelFormat pixelFormat;
+    Color_s                  transparentColor;
+};
+
+
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilespec;
+    unsigned int verbose;
+    unsigned int rendition;
+    unsigned int showhist;
+    unsigned int transparent;
+};
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc( 100*sizeof( optEntry ) );
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int renditionSpec;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "verbose",     OPT_FLAG, NULL,
+            &cmdlineP->verbose,  0);
+    OPTENT3(0, "showhist",    OPT_FLAG, NULL,
+            &cmdlineP->showhist, 0);
+    OPTENT3(0, "transparent",    OPT_FLAG, NULL,
+            &cmdlineP->transparent, 0);
+    OPTENT3(0, "rendition",  OPT_UINT, &cmdlineP->rendition, 
+            &renditionSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+
+    if (renditionSpec) {
+        if (cmdlineP->rendition < 1)
+            pm_error("The -rendition value must be at least 1");
+    } else 
+        cmdlineP->rendition = 1;
+    
+    if (cmdlineP->transparent && cmdlineP->showhist)
+        pm_error("You can't specify -showhist with -transparent");
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else {
+        cmdlineP->inputFilespec = argv[1];
+        if (argc-1 > 1)
+            pm_error("Too many arguments (%d).  The only non-option "
+                     "argument is the file name", argc-1);
+    }
+}
+
+
+
+static xelval *
+createGraymap(unsigned int const ncolors, 
+              xelval       const maxval) {
+    int i;
+    xelval *map;
+
+    MALLOCARRAY_NOFAIL(map, ncolors);
+    for (i = 0; i < ncolors; ++i) {
+        map[i] = maxval - (i * maxval) / (ncolors - 1);
+    }
+    return map;
+}
+
+
+
+static void 
+skipbytes(FILE *       const ifP, 
+          unsigned int const nbytes) {
+
+    unsigned char buf[256];
+    unsigned int n;
+    size_t bytesRead;
+
+    n = nbytes;  /* initial value */
+
+    while (n > 0) {
+        if (n > sizeof(buf)) {
+            bytesRead = fread(buf, sizeof(char), sizeof(buf), ifP);
+            if (bytesRead != sizeof(buf))
+               pm_error("Error reading Palm file.  Short read.");
+            n -= sizeof(buf);
+        } else {
+            bytesRead = fread(buf, sizeof(char), n, ifP);
+            if (bytesRead != n)
+               pm_error("Error reading Palm file.  Short read.");
+            n = 0;
+        }
+    }    
+}
+
+
+
+static void
+interpretCompression(unsigned char              const compressionValue,
+                     enum palmCompressionType * const compressionTypeP) {
+    
+    switch (compressionValue) {
+    case PALM_COMPRESSION_RLE:
+        *compressionTypeP = COMPRESSION_RLE;
+        break;
+    case PALM_COMPRESSION_SCANLINE:
+        *compressionTypeP = COMPRESSION_SCANLINE;
+        break;
+    case PALM_COMPRESSION_PACKBITS:
+        *compressionTypeP = COMPRESSION_PACKBITS;
+        break;
+    case PALM_COMPRESSION_NONE:
+        /* according to the spec this is not possible */
+        *compressionTypeP = COMPRESSION_NONE;
+        break;
+    default:
+        pm_error("The Palm image header has an unrecognized value for "
+                 "compression type: 0x%02x", (unsigned)compressionValue);
+    }
+}
+
+
+
+static void
+readRestOfHeaderVersion3(FILE *           const ifP,
+                         unsigned int     const pixelSize,
+                         unsigned char *  const sizeP,
+                         unsigned char *  const pixelFormatP,
+                         unsigned char *  const compressionTypeP,
+                         short *          const densityP,
+                         unsigned int *   const transparentIndexP,
+                         long *           const transparentValueP,
+                         long *           const nextBitmapOffsetP,
+                         short *          const nextDepthOffsetP) {
+
+    unsigned char unused;
+    
+    pm_readcharu(ifP, sizeP);
+    /* should be 0x18, but I can't see why we should really care */
+    if (*sizeP != 0x18)
+        pm_message("Strange value for Palm bitmap header size: %hu", *sizeP);
+
+    pm_readcharu(ifP, pixelFormatP);
+    if (*pixelFormatP != PALM_FORMAT_INDEXED &&
+        *pixelFormatP != PALM_FORMAT_565)
+        pm_error("Unrecognized pixelformat type: %u", *pixelFormatP);
+    
+    pm_readcharu(ifP, &unused);
+
+
+    pm_readcharu(ifP, compressionTypeP);
+
+    pm_readbigshort(ifP, densityP);
+    /* the specs imply that 0x00 is not valid */
+    if (*densityP != PALM_DENSITY_LOW &&
+        *densityP != PALM_DENSITY_ONEANDAHALF &&
+        *densityP != PALM_DENSITY_DOUBLE &&
+        *densityP != PALM_DENSITY_TRIPLE &&
+        *densityP != PALM_DENSITY_QUADRUPLE)
+        pm_error("Invalid value for -density: %d.", *densityP);
+ 
+    pm_readbiglong(ifP, transparentValueP);
+    if (pixelSize < 16)
+        *transparentIndexP = *transparentValueP;
+    else
+        *transparentIndexP = 0;
+ 
+    pm_readbiglong(ifP, nextBitmapOffsetP);
+    
+    /* version < 3 specific */
+    *nextDepthOffsetP = 0;
+}
+
+
+
+static void
+readRestOfHeaderOld(FILE *           const ifP,
+                    unsigned char *  const sizeP,
+                    unsigned char *  const pixelFormatP,
+                    unsigned char *  const compressionTypeP,
+                    short *          const densityP,
+                    unsigned int *   const transparentIndexP,
+                    long *           const transparentValueP,
+                    long *           const nextBitmapOffsetP,
+                    short *          const nextDepthOffsetP) {
+    
+    short pad;
+    unsigned char transparentIndex;
+    
+    pm_readbigshort(ifP, nextDepthOffsetP);
+    pm_readcharu(ifP, &transparentIndex);
+    *transparentIndexP = transparentIndex;
+ 
+    pm_readcharu(ifP,compressionTypeP);
+    
+    pm_readbigshort(ifP, &pad); /* reserved by Palm as of 8/9/00 */
+    
+    /* version 3 specific */
+    *sizeP = 0;
+    *pixelFormatP = 0;
+    *densityP = 0;
+    *transparentValueP = 0;
+    *nextBitmapOffsetP = 0;
+}
+
+
+
+static void
+interpretHeader(struct palmHeader * const palmHeaderP,
+                short               const cols,
+                short               const rows,
+                short               const bytesPerRow,
+                short               const flags,
+                unsigned char       const pixelSizeCode,
+                unsigned int        const pixelSize,
+                unsigned char       const version,
+                unsigned char       const size,
+                unsigned char       const pixelFormat,
+                short               const density,
+                long                const transparentValue,
+                unsigned int        const transparentIndex,
+                unsigned char       const compressionType) {
+    
+    palmHeaderP->cols = cols;
+    palmHeaderP->rows = rows;
+    palmHeaderP->bytesPerRow = bytesPerRow;
+    palmHeaderP->flags = flags;  /* Just for diagnostics */
+    palmHeaderP->hasColormap = !!(flags & PALM_HAS_COLORMAP_FLAG);
+    palmHeaderP->hasTransparency = !!(flags & PALM_HAS_TRANSPARENCY_FLAG);
+    palmHeaderP->pixelSizeCode = pixelSizeCode;
+    palmHeaderP->pixelSize = pixelSize;
+    palmHeaderP->version = version;
+    palmHeaderP->size = size;
+    palmHeaderP->pixelFormat = pixelFormat;
+    palmHeaderP->density = density;
+    palmHeaderP->transparentValue = transparentValue;
+    palmHeaderP->transparentIndex = transparentIndex;
+
+    if (palmHeaderP->version == 3 && (flags & PALM_DIRECT_COLOR_FLAG))
+        /* There's no directColorInfoType section in a version 3 Palm Bitmap */
+        pm_error("PALM_DIRECT_COLOR_FLAG is not valid for version 3 "
+                 "encoding type.");
+        
+    palmHeaderP->directColor = ((flags & PALM_DIRECT_COLOR_FLAG) || 
+                                palmHeaderP->pixelFormat == PALM_FORMAT_565);
+    
+    if (flags & PALM_IS_COMPRESSED_FLAG) 
+        interpretCompression(compressionType,
+                             &palmHeaderP->compressionType);
+    else
+        palmHeaderP->compressionType = COMPRESSION_NONE;
+}
+
+
+
+static void
+readHeader(FILE *              const ifP,
+           unsigned int        const requestedRendition,
+           struct palmHeader * const palmHeaderP) {
+/*----------------------------------------------------------------------------
+   Read the Palm Bitmap header from the file 'ifP'.  Read past all
+   renditions up to 'requestedRendition' and read the header of that
+   rendition.  Return the information contained in the header as *palmHeaderP.
+-----------------------------------------------------------------------------*/
+    bool gotHeader;
+    unsigned int currentRendition;
+
+    gotHeader = FALSE;
+    currentRendition = 1;
+    while (!gotHeader) {
+        short cols, rows, bytesPerRow, flags, nextDepthOffset, density;
+        unsigned char pixelSizeCode, version, compressionType, 
+            size, pixelFormat;
+        long transparentValue, nextBitmapOffset;
+        unsigned int pixelSize, transparentIndex;
+
+        pm_readbigshort(ifP, &cols);
+        pm_readbigshort(ifP, &rows);
+        pm_readbigshort(ifP, &bytesPerRow);
+        pm_readbigshort(ifP, &flags);
+ 
+        pm_readcharu(ifP, &pixelSizeCode);
+        pixelSize = pixelSizeCode == 0 ? 1 : pixelSizeCode;
+        if (pixelSizeCode != 0x00 &&
+            pixelSizeCode != 0x01 &&
+            pixelSizeCode != 0x02 &&
+            pixelSizeCode != 0x04 &&
+            pixelSizeCode != 0x08 &&
+            pixelSizeCode != 0x10 &&
+            pixelSizeCode != 0xFF)
+            pm_error("Invalid value for bits per pixel: %u.", pixelSizeCode);
+
+        if ((bytesPerRow * 8) < (cols * pixelSize))
+            pm_error("%u bytes per row is not valid with %u columns and %u "
+                     "bits per pixel.", bytesPerRow, cols, pixelSize);
+
+        pm_readcharu(ifP, &version);
+        if (version > 3) 
+            pm_error("Unknown encoding version type: %d", version);
+        else if (version == 3)
+            readRestOfHeaderVersion3(ifP, pixelSize,
+                                     &size, &pixelFormat, &compressionType,
+                                     &density, &transparentIndex, 
+                                     &transparentValue, &nextBitmapOffset,
+                                     &nextDepthOffset);
+        else
+            readRestOfHeaderOld(ifP,
+                                &size, &pixelFormat, &compressionType,
+                                &density, &transparentIndex,
+                                &transparentValue, &nextBitmapOffset,
+                                &nextDepthOffset);
+
+        if (currentRendition < requestedRendition) {
+             if (version < 3 && nextDepthOffset == 0 && pixelSizeCode != 0xFF) 
+                 pm_error("Not enough renditions in the input Palm Bitmap "
+                          "to extract the %dth", requestedRendition);
+             if (version == 3 && nextBitmapOffset == 0) 
+                 pm_error("Not enough renditions in the input Palm Bitmap "
+                          "to extract the %dth", requestedRendition);
+             /* nextDepthOffset is calculated in 4 byte words
+                from the beginning of this bitmap (so it equals its size) 
+             */
+             if (version < 3 && pixelSizeCode != 0xFF )
+                 skipbytes(ifP, (nextDepthOffset*4)-16);
+             else if (version == 3)
+                 /* FIXME rewrite skipbytes to accept longs? */
+                 skipbytes(ifP, (short) nextBitmapOffset-24); 
+             if (pixelSizeCode != 0xFF)
+                 ++currentRendition;
+        } else if (pixelSizeCode != 0xFF) {
+            gotHeader = TRUE;
+            
+            interpretHeader(palmHeaderP,
+                            cols, rows, bytesPerRow, flags, pixelSizeCode,
+                            pixelSize, version, size, pixelFormat, density,
+                            transparentValue, transparentIndex, 
+                            compressionType);
+        }
+    }
+}
+
+
+
+static const char *
+yesno(bool const arg) {
+
+    if (arg)
+        return "YES";
+    else
+        return "NO";
+}
+
+
+static void
+reportPalmHeader(struct palmHeader      const palmHeader,
+                 struct directColorInfo const directColorInfo) {
+
+    const char *ctype;
+
+    switch (palmHeader.compressionType) {
+    case COMPRESSION_RLE:
+        ctype = "rle (Palm OS 3.5)";
+        break;
+    case COMPRESSION_SCANLINE:
+        ctype = "scanline (Palm OS 2.0)";
+        break;
+    case COMPRESSION_PACKBITS:
+        ctype = "packbits (Palm OS 4.0)";
+        break;
+    case COMPRESSION_NONE:
+        ctype = "none";
+        break;
+    }
+    pm_message("Dimensions: %hu columns x %hu rows",
+               palmHeader.cols, palmHeader.rows);
+    pm_message("Row layout: %hu bytes per row, %hu bits per pixel",
+               palmHeader.bytesPerRow, palmHeader.pixelSize);
+    pm_message("Pixel Size code: %hu", palmHeader.pixelSizeCode);
+    pm_message("Flags: 0x%04hx", palmHeader.flags);
+    pm_message("  Direct Color: %s", yesno(palmHeader.directColor));
+    pm_message("  Colormap:     %s", yesno(palmHeader.hasColormap));
+    pm_message("  Transparency: %s", yesno(palmHeader.hasTransparency));
+    pm_message("Version %d", palmHeader.version);
+    if (palmHeader.hasTransparency) {
+        if (palmHeader.directColor) {
+            /* Copied from doTransparent(...) */
+            Color_s const color = directColorInfo.transparentColor;
+            pm_message("Transparent value: #%02x%02x%02x", 
+                       (unsigned int)((color >> 16) & 0xFF), 
+                       (unsigned int)((color >>  8) & 0xFF), 
+                       (unsigned int)((color >>  0) & 0xFF));
+        } else
+            pm_message("Transparent index: %u", palmHeader.transparentIndex);
+    }
+    pm_message("Compression type: %s", ctype);
+    if (palmHeader.version == 3)
+        pm_message("Density: %d", palmHeader.density);
+}
+
+
+
+static void
+determineOutputFormat(struct palmHeader const palmHeader,
+                      int *             const formatP,
+                      xelval *          const maxvalP) {
+
+    if (palmHeader.directColor) {
+        *formatP = PPM_TYPE;
+        *maxvalP = 255;
+    } else if (palmHeader.hasColormap) {
+        *formatP = PPM_TYPE;
+        *maxvalP = 255;
+    } else if (palmHeader.pixelSize == 1) {
+        *formatP = PBM_TYPE;
+        *maxvalP = 1;
+    } else if (palmHeader.pixelSize >= 8) {
+        *formatP = PPM_TYPE;
+        *maxvalP = pm_bitstomaxval(palmHeader.pixelSize);
+    } else {
+        *formatP = PGM_TYPE;
+        *maxvalP = pm_bitstomaxval(palmHeader.pixelSize);
+    }
+}
+
+
+
+static void
+readRgbFormat(FILE *                     const ifP,
+              struct directPixelFormat * const pixelFormatP) {
+
+    unsigned char r, g, b;
+
+    pm_readcharu(ifP, &r);
+    pm_readcharu(ifP, &g);
+    pm_readcharu(ifP, &b);
+    
+    if (r != 5 || g != 6 || b != 5)
+        pm_error("This image has a direct color pixel format of "
+                 "%u red, %u green, %u blue bits.  This program "
+                 "can handle only 5, 6, 5.", r, g, b);
+    else {
+        pixelFormatP->redbits   = r;
+        pixelFormatP->greenbits = g;
+        pixelFormatP->bluebits  = b;
+    }
+}
+
+
+
+static void
+readDirectTransparentColor(FILE *    const ifP,
+                           Color_s * const colorP) {
+
+    unsigned char r, g, b;
+
+    pm_readcharu(ifP, &r);
+    pm_readcharu(ifP, &g);
+    pm_readcharu(ifP, &b);
+
+    *colorP = (r << 16) | (g << 8) | (b << 0);
+}
+
+
+
+static void
+readDirectInfoType(FILE *                   const ifP,
+                   struct palmHeader        const palmHeader,
+                   struct directColorInfo * const directInfoTypeP) {
+/*----------------------------------------------------------------------------
+   Read the Palm Bitmap Direct Info Type section, if any.
+
+   The Direct Info Type section is a section of a pre-Version 3 direct
+   color Palm Bitmap that tells how to interpret the direct color
+   raster.
+
+   Return an undefined value as *directInfoTypeP if there is no such
+   section in this Palm Bitmap.
+-----------------------------------------------------------------------------*/
+    if ((palmHeader.directColor) && palmHeader.pixelSize != 16)
+        pm_error("The image is of the direct color type, but has %u "
+                 "bits per pixel.  The only kind of direct color images "
+                 "this program understands are 16 bit ones.", 
+                 palmHeader.pixelSize);
+
+    if (palmHeader.version == 3) {
+        /* All direct color info is in the header, because it'sversion
+           3 encoding.  No Direct Info Type section.  
+        */
+    } else {
+        if (palmHeader.directColor) {
+            unsigned char padding;
+
+            readRgbFormat(ifP, &directInfoTypeP->pixelFormat);
+
+            pm_readcharu(ifP, &padding);
+            pm_readcharu(ifP, &padding);
+
+            readDirectTransparentColor(ifP, 
+                                       &directInfoTypeP->transparentColor);
+        } else {
+            /* Not a direct color image; no Direct Info Type section. */
+        }
+    }
+}
+
+
+
+static void
+readColormap(FILE *            const ifP,
+             struct palmHeader const palmHeader,
+             Colormap *        const colormapP) {
+/*----------------------------------------------------------------------------
+   Read the colormap, if any from the Palm Bitmap.
+
+   If the image described by 'palmHeader' doesn't have a colormap,
+   return an undefined value as *colormapP.
+-----------------------------------------------------------------------------*/
+    if (palmHeader.hasColormap)
+        *colormapP = palmcolor_read_colormap(ifP);
+}
+
+
+
+static void
+getColorInfo(struct palmHeader        const palmHeader,
+             struct directColorInfo   const directInfoType,
+             Colormap                 const colormapFromImage,
+             Colormap *               const colormapP,
+             unsigned int *           const ncolorsP,
+             struct directColorInfo * const directColorInfoP) {
+/*----------------------------------------------------------------------------
+   Gather color encoding information from the various sources.
+
+   Note that 'directInfoType' and 'colormapFromImage' are meaningful only
+   with certain values of 'palmHeader'.
+
+   If it's a version 3 direct color, the pixel format must be "565".
+-----------------------------------------------------------------------------*/
+    if (palmHeader.version == 3 && palmHeader.directColor) {
+        *colormapP = NULL;
+
+        assert(palmHeader.pixelFormat == PALM_FORMAT_565);
+
+        directColorInfoP->pixelFormat.redbits   = 5;
+        directColorInfoP->pixelFormat.greenbits = 6;
+        directColorInfoP->pixelFormat.bluebits  = 5;
+        /* FIXME Just guessing here ... */
+        directColorInfoP->transparentColor = 
+            (((palmHeader.transparentValue >> 11) & 0x1F) << 16) |
+            (((palmHeader.transparentValue >>  5) & 0x3F) <<  8) |
+            (((palmHeader.transparentValue >>  0) & 0x1F) <<  0);
+    } else if (palmHeader.directColor) {
+        *colormapP = NULL;
+        *directColorInfoP = directInfoType;
+    } else if (palmHeader.hasColormap)
+        *colormapP = colormapFromImage;
+    else if (palmHeader.pixelSize >= 8) {
+        Colormap colormap;
+        colormap = palmcolor_build_default_8bit_colormap();
+        qsort(colormap->color_entries, colormap->ncolors, 
+              sizeof(Color_s), palmcolor_compare_indices);
+        *colormapP = colormap;
+    } else
+        *colormapP = NULL;
+
+    *ncolorsP = 1 << palmHeader.pixelSize;
+}
+
+
+
+static void
+doTransparent(FILE *                 const ofP,
+              bool                   const hasTransparency,
+              bool                   const directColor,
+              unsigned char          const transparentIndex,
+              unsigned char          const pixelSize,
+              Colormap               const colormap,
+              struct directColorInfo const directColorInfo) {
+/*----------------------------------------------------------------------------
+   Generate a PNM comment on *ofP telling what color in the raster is
+   supposed to be transparent.
+
+   Note that PNM itself doesn't have any way to represent transparency.
+   (But this program could be converted to a PAM program and use the
+   RGB_ALPHA and GRAYSCALE_ALPHA tuple types).
+-----------------------------------------------------------------------------*/
+    if (hasTransparency) {
+        if (colormap) {
+            Color_s const color = transparentIndex << 24;
+            Color const actualColor = (bsearch(&color,
+                                               colormap->color_entries, 
+                                               colormap->ncolors,
+                                               sizeof(color), 
+                                               palmcolor_compare_indices));
+            fprintf(ofP, "#%02x%02x%02x\n", 
+                   (unsigned int) ((*actualColor >> 16) & 0xFF),
+                   (unsigned int) ((*actualColor >>  8) & 0xFF), 
+                   (unsigned int) ((*actualColor >>  0) & 0xFF));
+        } else if (directColor) {
+            Color_s const color = directColorInfo.transparentColor;
+            fprintf(ofP, "#%02x%02x%02x\n", 
+                   (unsigned int)((color >> 16) & 0xFF), 
+                   (unsigned int)((color >>  8) & 0xFF), 
+                   (unsigned int)((color >>  0) & 0xFF));
+        } else {
+            unsigned int const maxval = pm_bitstomaxval(pixelSize);
+            unsigned int const grayval = 
+                ((maxval - transparentIndex) * 256) / maxval;
+            fprintf(ofP, "#%02x%02x%02x\n", grayval, grayval, grayval);
+        }
+    }
+}
+
+
+
+static void
+createHistogram(unsigned int    const ncolors,
+                unsigned int ** const seenP) {
+
+    unsigned int * seen;
+
+    MALLOCARRAY(seen, ncolors);
+    if (!seen)
+        pm_error("Can't allocate array for keeping track of "
+                 "how many pixels of each of %u colors are in the image.", 
+                 ncolors);
+
+    {    
+        /* Initialize the counter for each color to zero */
+        unsigned int i;
+        for (i = 0; i < ncolors; ++i)
+            seen[i] = 0;
+    }
+    *seenP = seen;
+}
+
+
+
+static void
+readScanlineRow(FILE *          const ifP,
+                unsigned char * const palmrow,
+                unsigned char * const lastrow,
+                unsigned int    const bytesPerRow,
+                bool            const firstRow) {
+
+    unsigned int j;
+
+    for (j = 0; j < bytesPerRow; j += 8) {
+        unsigned char diffmask;
+            /* A mask telling whether each of the 8 raster bytes indexed
+               j through j+7 is the same as in the previous row ('lastrow')
+               or is to be read from the file.  Bit 0 of the mask refers
+               to byte j, Bit 1 to byte j + 1, etc.
+            */
+        unsigned int byteCount;
+            /* How many bytes are covered by 'diffmask'.  Normally 8, but
+               at the end of the row, could be less.
+            */
+        unsigned int k;
+
+        pm_readcharu(ifP, &diffmask);
+        byteCount = MIN(bytesPerRow - j, 8);
+        
+        for (k = 0; k < byteCount; ++k) {
+            /* the first row cannot be compressed */
+            if (firstRow || ((diffmask & (1 << (7 - k))) != 0)) {
+                unsigned char inval;
+                pm_readcharu(ifP, &inval);
+                palmrow[j + k] = inval;
+            } else
+                palmrow[j + k] = lastrow[j + k];
+        }
+    }
+    memcpy(lastrow, palmrow, bytesPerRow);
+}
+
+
+
+static void
+readRleRow(FILE *          const ifP,
+           unsigned char * const palmrow,
+           unsigned int    const bytesPerRow) {
+
+    unsigned int j;
+
+    for (j = 0;  j < bytesPerRow; ) {
+        unsigned char incount;
+        unsigned char inval;
+
+        pm_readcharu(ifP, &incount);
+        if (incount == 0)
+            pm_error("Invalid (zero) count in RLE compression.");
+        if (j + incount > bytesPerRow)
+            pm_error("Bytes in RLE compressed row exceed bytes per row.  "
+                     "Bytes per row is %u.  A run length of %u bytes "
+                     "pushes the bytes in this row up to %u bytes (and then"
+                     "we gave up.", bytesPerRow, incount, j + incount);
+        pm_readcharu(ifP, &inval);
+        memset(palmrow + j, inval, incount);
+        j += incount;
+    }
+} 
+
+
+
+static void
+readPackBitsRow(FILE *          const ifP,
+                unsigned char * const palmrow,
+                unsigned int    const bytesPerRow) {
+
+    unsigned int j;
+
+    for (j = 0;  j < bytesPerRow; ) {
+        char incount;
+        pm_readchar(ifP, &incount);
+        if (incount < 0) { 
+            /* How do we handle incount == -128 ? */
+            unsigned int const runlength = -incount + 1;
+            unsigned char inval;
+            pm_readcharu(ifP, &inval);
+            memset(palmrow + j, inval, runlength);
+            j += runlength;
+        } else {
+            unsigned int const nonrunlength = incount + 1;
+            unsigned int k;
+            for (k = 0; k < nonrunlength; ++k) {
+                unsigned char inval;
+                pm_readcharu(ifP, &inval);
+                palmrow[j + k] = inval;
+            }
+            j += nonrunlength;
+        }
+        if (j > bytesPerRow) 
+            pm_error("Bytes in PackBits compressed row exceed bytes per row.");
+    }
+} 
+
+
+
+static void
+readUncompressedRow(FILE *          const ifP,
+                    unsigned char * const palmrow,
+                    unsigned int    const bytesPerRow) {
+
+    int bytesRead;
+    
+    bytesRead = fread(palmrow, 1, bytesPerRow, ifP);
+    if (bytesRead != bytesPerRow)
+        pm_error("Error reading Palm file.  Short read.");
+}
+
+
+
+static void
+readDecompressedRow(FILE *                   const ifP,
+                    unsigned char *          const palmrow,
+                    unsigned char *          const lastrow,
+                    enum palmCompressionType const compressionType,
+                    unsigned int             const bytesPerRow,
+                    bool                     const firstRow) {
+/*----------------------------------------------------------------------------
+   Read a row from Palm file 'ifP', in uncompressed form (i.e. decompress if
+   necessary).  Assume the row contains 'bytesPerRow' uncompressed bytes,
+   compressed according to 'compressionType'.  Return the data at 'palmrow'.
+
+   'firstRow' means decompress it as if it is the first row of the image
+   (some compression schemes transform the first row differently from the
+   rest, because each row depends on the row before it).
+
+   If 'compressionType' is COMPRESSION_SCANLINE, (which means
+   transformation of a row depends on the contents of the row before
+   it), then 'lastRow' is as input the uncompressed contents of the
+   previous row (undefined if 'firstRow' is true).  In that case, we
+   modify 'lastrow' to contain a copy of 'palmrow' (so Caller can
+   conveniently use it to read the next row).
+
+   If 'compressionType' is not COMPRESSION_SCANLINE, 'lastrow' is
+   undefined both as input and output.
+-----------------------------------------------------------------------------*/
+    switch (compressionType) {
+    case COMPRESSION_RLE:
+        readRleRow(ifP, palmrow, bytesPerRow);
+        break;
+    case COMPRESSION_SCANLINE:
+        readScanlineRow(ifP, palmrow, lastrow, bytesPerRow, firstRow);
+        break;
+    case COMPRESSION_PACKBITS:
+        readPackBitsRow(ifP, palmrow, bytesPerRow);
+        break;
+    case COMPRESSION_NONE:
+        readUncompressedRow(ifP, palmrow, bytesPerRow);
+        break;
+    }
+}
+
+
+
+static void
+convertRowToPnmDirect(const unsigned char * const palmrow,
+                      xel *                 const xelrow,
+                      unsigned int          const cols,
+                      xelval                const maxval,
+                      unsigned int *        const seen) {
+
+    /* There's a problem with this.  Take the Palm 16-bit
+       direct color.  That's 5 bits for the red, 6 for the
+       green, and 5 for the blue.  So what should the MAXVAL
+       be?  I decided to use 255 (8 bits) for everything,
+       since that's the theoretical max of the number of bits
+       in any one color, according to Palm.  So the Palm color
+       0xFFFF (white) would be red=0x1F, green=0x3F, and
+       blue=0x1F.  How do we promote those colors?  Simple
+       shift would give us R=248,G=252,B=248; which is
+       slightly green.  Hardly seems right.
+       
+       So I've perverted the math a bit.  Each color value is
+       multiplied by 255, then divided by either 31 (red or
+       blue) or 63 (green).  That's the right way to do it
+       anyway.  
+    */
+
+    const unsigned char *inbyte;
+    unsigned int j;
+    
+    for (inbyte = palmrow, j = 0;  j < cols;  ++j) {
+        unsigned int inval;
+        inval = *inbyte++ << 8;
+        inval |= *inbyte++;
+        
+        if (seen)
+            ++seen[inval];
+        
+        PPM_ASSIGN(xelrow[j], 
+                   (((inval >> 11) & 0x1F) * maxval) / 0x1F, 
+                   (((inval >>  5) & 0x3F) * maxval) / 0x3F, 
+                   (((inval >>  0) & 0x1F) * maxval) / 0x1F
+            );
+    }
+}
+
+
+
+static void
+convertRowToPnmNotDirect(const unsigned char * const palmrow,
+                         xel *                 const xelrow,
+                         unsigned int          const cols,
+                         Colormap              const colormap,
+                         xelval *              const graymap,
+                         unsigned int *        const seen,
+                         unsigned int          const pixelSize) {
+
+    unsigned int const mask = (1 << pixelSize) - 1;
+
+    const unsigned char *inbyte;
+    unsigned int inbit;
+    unsigned int j;
+    
+    inbit = 8 - pixelSize;
+    inbyte = palmrow;
+    for (j = 0; j < cols; ++j) {
+        short const color = ((*inbyte) & (mask << inbit)) >> inbit;
+        if (seen)
+            ++seen[color];
+        
+        if (colormap) {
+            Color_s const color2      = color << 24;
+            Color   const actualColor = (bsearch (&color2,
+                                                  colormap->color_entries, 
+                                                  colormap->ncolors,
+                                                  sizeof(color2), 
+                                                  palmcolor_compare_indices));
+            PPM_ASSIGN(xelrow[j], 
+                       (*actualColor >> 16) & 0xFF, 
+                       (*actualColor >>  8) & 0xFF, 
+                       (*actualColor >>  0) & 0xFF);
+        } else
+            PNM_ASSIGN1(xelrow[j], graymap[color]);
+        
+        if (!inbit) {
+            ++inbyte;
+            inbit = 8 - pixelSize;
+        } else
+            inbit -= pixelSize;
+    }
+}
+
+
+
+static void
+writePnm(FILE *            const ofP,
+         struct palmHeader const palmHeader,
+         FILE *            const ifP,
+         Colormap          const colormap,
+         xelval *          const graymap,
+         unsigned int      const nColors,
+         int               const format,
+         xelval            const maxval,
+         unsigned int **   const seenP) {
+
+    int const cols = palmHeader.cols;
+    int const rows = palmHeader.rows;
+
+    unsigned char * palmrow;
+    unsigned char * lastrow;
+    xel *           xelrow;
+    unsigned int *  seen;
+    unsigned int    row;
+    
+    pnm_writepnminit(ofP, cols, rows, maxval, format, 0);
+    xelrow = pnm_allocrow(cols);
+
+    /* Read the picture data, one row at a time */
+    MALLOCARRAY_NOFAIL(palmrow, palmHeader.bytesPerRow);
+    MALLOCARRAY_NOFAIL(lastrow, palmHeader.bytesPerRow); 
+    
+    if (seenP) {
+        createHistogram(nColors, &seen);
+        *seenP = seen;
+    } else
+        seen = NULL;
+
+    /* We should actually use compressedDataSizeNN for checking the sanity 
+       of the data we're reading ... 
+    */
+    if (palmHeader.compressionType != COMPRESSION_NONE) {
+        if (palmHeader.version < 3) {
+            short compressedDataSize16;
+            pm_readbigshort(ifP, &compressedDataSize16);
+        } else {
+            long compressedDataSize32;
+            pm_readbiglong(ifP, &compressedDataSize32);
+        }
+    }
+
+    for (row = 0; row < rows; ++row) {
+        readDecompressedRow(ifP, palmrow, lastrow, 
+                            palmHeader.compressionType, 
+                            palmHeader.bytesPerRow,
+                            row == 0);
+
+        if (palmHeader.directColor) {
+            assert(palmHeader.pixelSize == 16);
+            convertRowToPnmDirect(palmrow, xelrow, cols, maxval, seen);
+        } else
+            convertRowToPnmNotDirect(palmrow, xelrow, cols, colormap, graymap,
+                                     seen, palmHeader.pixelSize);
+
+        pnm_writepnmrow(ofP, xelrow, cols, maxval, format, 0);
+    }
+    free(lastrow);
+    free(palmrow);
+    pnm_freerow(xelrow);
+}
+
+
+
+static void
+showHistogram(unsigned int * const seen,
+              Colormap       const colormap,
+              const xelval * const graymap,
+              unsigned int   const ncolors) {
+
+    unsigned int colorIndex;
+    
+    for (colorIndex = 0;  colorIndex < ncolors; ++colorIndex) {
+        if (!colormap)
+            pm_message("%.3d -> %.3d:  %d", 
+                       colorIndex, graymap[colorIndex], seen[colorIndex]);
+        else {
+            Color_s const color = colorIndex << 24;
+            Color const actualColor = (bsearch(&color,
+                                               colormap->color_entries, 
+                                               colormap->ncolors,
+                                               sizeof(color), 
+                                               palmcolor_compare_indices));
+            if (actualColor)
+                pm_message("%.3d -> %ld,%ld,%ld:  %d", colorIndex,
+                           (*actualColor >> 16) & 0xFF,
+                           (*actualColor >> 8) & 0xFF,
+                           (*actualColor & 0xFF), seen[colorIndex]);
+        }
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    struct cmdlineInfo cmdline;
+
+    FILE* ifP;
+    struct palmHeader palmHeader;
+    struct directColorInfo directInfoType;
+    Colormap colormapFromImage;
+    Colormap colormap;
+    struct directColorInfo directColorInfo;
+    int format;
+    xelval maxval;
+    unsigned int nColors;
+
+    /* Parse default params */
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    readHeader(ifP, cmdline.rendition, &palmHeader);
+
+    readDirectInfoType(ifP, palmHeader, &directInfoType);
+   
+    readColormap(ifP, palmHeader, &colormapFromImage);
+    
+    determineOutputFormat(palmHeader, &format, &maxval);
+    
+    getColorInfo(palmHeader, directInfoType, colormapFromImage,
+                 &colormap, &nColors, &directColorInfo);
+
+    if (cmdline.verbose)
+        reportPalmHeader(palmHeader, directColorInfo);
+
+    if (cmdline.transparent)
+        doTransparent(stdout, 
+                      palmHeader.hasTransparency, palmHeader.directColor,
+                      palmHeader.transparentIndex, 
+                      palmHeader.pixelSize, colormap, directColorInfo);
+    else {
+        unsigned int * seen;
+        xelval * graymap;
+
+        graymap = createGraymap(nColors, maxval);
+
+        writePnm(stdout,
+                 palmHeader, ifP, colormap, graymap, nColors, format, maxval, 
+                 cmdline.showhist ? &seen : NULL);
+        
+        if (cmdline.showhist)
+            showHistogram(seen, colormap, graymap, nColors);
+        
+        free(graymap);
+    }
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/converter/other/pnmtopalm/pnmtopalm.c b/converter/other/pnmtopalm/pnmtopalm.c
new file mode 100644
index 00000000..3b9eec8f
--- /dev/null
+++ b/converter/other/pnmtopalm/pnmtopalm.c
@@ -0,0 +1,1235 @@
+/* pnmtopalm.c - read a PNM image and write a Palm Bitmap file
+ *
+ * Inspired by and using methods from ppmtoTbmp.c by Ian Goldberg
+ * <iang@cs.berkeley.edu>, which was based on ppmtopuzz.c by Jef
+ * Poskanzer, from the netpbm-1mar1994 package.
+ *
+ * Mods for multiple bits per pixel were added to ppmtoTbmp.c by
+ * George Caswell <tetsujin@sourceforge.net> and Bill Janssen
+ * <bill@janssen.org>.
+ *
+ * Major fixes and new capability added by Paul Bolle <pebolle@tiscali.nl>
+ * in late 2004 / early 2005.
+ *
+ * See LICENSE file for licensing information.
+ *
+ *  
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <limits.h>
+
+#include "pnm.h"
+#include "palm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+enum compressionType {COMP_NONE, COMP_SCANLINE, COMP_RLE, COMP_PACKBITS};
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespecs of input files */
+    char *transparent;          /* -transparent value.  Null if unspec */
+    unsigned int depth;         /* -depth value.  0 if unspec */
+    unsigned int maxdepth;      /* -maxdepth value.  0 if unspec */
+    enum compressionType compression;
+    unsigned int verbose;
+    unsigned int colormap;
+    unsigned int offset;        /* -offset specified */
+    unsigned int density;       /* screen density */
+    unsigned int withdummy;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv, struct cmdline_info *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry *option_def;
+    unsigned int option_def_index;
+
+    unsigned int transSpec, depthSpec, maxdepthSpec, densitySpec;
+    unsigned int scanline_compression, rle_compression, packbits_compression;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "transparent",      OPT_STRING, 
+            &cmdlineP->transparent, &transSpec, 0);
+    OPTENT3(0, "depth",            OPT_UINT, 
+            &cmdlineP->depth,       &depthSpec, 0);
+    OPTENT3(0, "maxdepth",         OPT_UINT, 
+            &cmdlineP->maxdepth,    &maxdepthSpec, 0);
+    OPTENT3(0, "scanline_compression", OPT_FLAG, 
+            NULL,                   &scanline_compression, 0);
+    OPTENT3(0, "rle_compression",  OPT_FLAG, 
+            NULL,                   &rle_compression, 0);
+    OPTENT3(0, "packbits_compression", OPT_FLAG, 
+            NULL,                   &packbits_compression, 0);
+    OPTENT3(0, "verbose",          OPT_FLAG, 
+            NULL,                   &cmdlineP->verbose, 0);
+    OPTENT3(0, "colormap",         OPT_FLAG, 
+            NULL,                   &cmdlineP->colormap, 0);
+    OPTENT3(0, "offset",           OPT_FLAG, 
+            NULL,                   &cmdlineP->offset, 0);
+    OPTENT3(0, "density",          OPT_UINT, 
+            &cmdlineP->density,     &densitySpec, 0);
+    OPTENT3(0, "withdummy",        OPT_FLAG, 
+            NULL,                   &cmdlineP->withdummy, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE; /* We have some short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (depthSpec) {
+        if (cmdlineP->depth != 1 && cmdlineP->depth != 2 
+            && cmdlineP->depth != 4 && cmdlineP->depth != 8
+            && cmdlineP->depth != 16)
+            pm_error("invalid value for -depth: %u.  Valid values are "
+                     "1, 2, 4, 8, and 16", cmdlineP->depth);
+    } else
+        cmdlineP->depth = 0;
+
+    if (maxdepthSpec) {
+        if (cmdlineP->maxdepth != 1 && cmdlineP->maxdepth != 2 
+            && cmdlineP->maxdepth != 4 && cmdlineP->maxdepth != 8
+            && cmdlineP->maxdepth != 16)
+            pm_error("invalid value for -maxdepth: %u.  Valid values are "
+                     "1, 2, 4, 8, and 16", cmdlineP->maxdepth);
+    } else
+        cmdlineP->maxdepth = 0;
+
+    if (depthSpec && maxdepthSpec && 
+        cmdlineP->depth > cmdlineP->maxdepth)
+        pm_error("-depth value (%u) is greater than -maxdepth (%u) value.",
+                 cmdlineP->depth, cmdlineP->maxdepth);
+
+    if (!transSpec)
+        cmdlineP->transparent = NULL;
+
+    if (densitySpec) {
+        if (cmdlineP->density != PALM_DENSITY_LOW &&
+            cmdlineP->density != PALM_DENSITY_ONEANDAHALF &&
+            cmdlineP->density != PALM_DENSITY_DOUBLE &&
+            cmdlineP->density != PALM_DENSITY_TRIPLE &&
+            cmdlineP->density != PALM_DENSITY_QUADRUPLE)
+            pm_error("Invalid value for -density: %d.  Valid values are "
+                     "%d, %d, %d, %d and %d.", cmdlineP->density, 
+                     PALM_DENSITY_LOW, PALM_DENSITY_ONEANDAHALF,
+                     PALM_DENSITY_DOUBLE, PALM_DENSITY_TRIPLE,
+                     PALM_DENSITY_QUADRUPLE);
+    } else
+        cmdlineP->density = PALM_DENSITY_LOW;
+
+    if (cmdlineP->density != PALM_DENSITY_LOW && cmdlineP->withdummy)
+            pm_error("You can't specify -withdummy with -density value %u.  "
+                     "It is valid only with low density (%u)",
+                     cmdlineP->density, PALM_DENSITY_LOW);
+
+    if (cmdlineP->withdummy && !cmdlineP->offset)
+        pm_error("-withdummy does not make sense without -offset");
+
+    if (scanline_compression + rle_compression + packbits_compression > 1)
+        pm_error("You may specify only one of -scanline_compression, "
+                 "-rle_compression, and -packbits_compression");
+    else {
+        if (scanline_compression)
+            cmdlineP->compression = COMP_SCANLINE;
+        else if (rle_compression)
+            cmdlineP->compression = COMP_RLE;
+        else if (packbits_compression)
+            cmdlineP->compression = COMP_PACKBITS;
+        else
+            cmdlineP->compression = COMP_NONE;
+    }
+        
+    if (argc-1 > 1)
+        pm_error("This program takes at most 1 argument: the file name.  "
+                 "You specified %d", argc-1);
+    else if (argc-1 > 0) 
+        cmdlineP->inputFilespec = argv[1];
+    else
+        cmdlineP->inputFilespec = "-";
+}
+
+
+
+static void
+determinePalmFormat(unsigned int   const cols, 
+                    unsigned int   const rows, 
+                    xelval         const maxval, 
+                    int            const format, 
+                    xel **         const xels,
+                    unsigned int   const specified_bpp,
+                    unsigned int   const max_bpp, 
+                    bool           const custom_colormap,
+                    bool           const verbose,
+                    unsigned int * const bppP, 
+                    bool *         const directColorP, 
+                    Colormap *     const colormapP) {
+
+    if (PNM_FORMAT_TYPE(format) == PBM_TYPE) {
+        if (custom_colormap)
+            pm_error("You specified -colormap with a black and white input "
+                     "image.  -colormap is valid only with color.");
+        if (specified_bpp)
+            *bppP = specified_bpp;
+        else
+            *bppP = 1;    /* no point in wasting bits */
+        *directColorP = FALSE;
+        *colormapP = NULL;
+        if (verbose)
+            pm_message("output is black and white");
+    } else if (PNM_FORMAT_TYPE(format) == PGM_TYPE) {
+        /* we can usually handle this one, but may not have enough
+           pixels.  So check... */
+        if (custom_colormap)
+            pm_error("You specified -colormap with a black and white input"
+                     "image.  -colormap is valid only with color.");
+        if (specified_bpp)
+            *bppP = specified_bpp;
+        else if (max_bpp && (maxval >= (1 << max_bpp)))
+            *bppP = max_bpp;
+        else if (maxval > 16)
+            *bppP = 4;
+        else {
+            /* scale to minimum number of bpp needed */
+            for (*bppP = 1;  (1 << *bppP) < maxval;  *bppP *= 2)
+                ;
+        }
+        if (*bppP > 4)
+            *bppP = 4;
+        if (verbose)
+            pm_message("output is grayscale %d bits-per-pixel", *bppP);
+        *directColorP = FALSE;
+        *colormapP = NULL;
+    } else if (PNM_FORMAT_TYPE(format) == PPM_TYPE) {
+
+        /* We assume that we only get a PPM if the image cannot be
+           represented as PBM or PGM.  There are two options here: either
+           8-bit with a colormap, either the standard one or a custom one,
+           or 16-bit direct color.  In the 8-bit case, if "custom_colormap"
+           is specified (not recommended by Palm) we will put in our own
+           colormap; otherwise we will assume that the colors have been
+           mapped to the default Palm colormap by appropriate use of
+           pnmquant.  We try for 8-bit color first, since it works on
+           more PalmOS devices. 
+        */
+        if ((specified_bpp == 16) || 
+            (specified_bpp == 0 && max_bpp == 16)) {
+            /* we do the 16-bit direct color */
+            *directColorP = TRUE;
+            *colormapP = NULL;
+            *bppP = 16;
+        } else if (!custom_colormap) {
+            /* standard indexed 8-bit color */
+            *colormapP = palmcolor_build_default_8bit_colormap();
+            *bppP = 8;
+            if (((specified_bpp != 0) && (specified_bpp != 8)) ||
+                ((max_bpp != 0) && (max_bpp < 8)))
+                pm_error("Must use depth of 8 for color Palm Bitmap without "
+                         "custom color table.");
+            *directColorP = FALSE;
+            if (verbose)
+                pm_message("Output is color with default colormap at 8 bpp");
+        } else {
+            /* indexed 8-bit color with a custom colormap */
+            *colormapP = 
+                palmcolor_build_custom_8bit_colormap(rows, cols, xels);
+            for (*bppP = 1; (1 << *bppP) < (*colormapP)->ncolors; *bppP *= 2);
+            if (specified_bpp != 0) {
+                if (specified_bpp >= *bppP)
+                    *bppP = specified_bpp;
+                else
+                    pm_error("Too many colors for specified depth.  "
+                             "Use pnmquant to reduce.");
+            } else if ((max_bpp != 0) && (max_bpp < *bppP)) {
+                pm_error("Too many colors for specified max depth.  "
+                         "Use pnmquant to reduce.");
+            }
+            *directColorP = FALSE;
+            if (verbose)
+                pm_message("Output is color with custom colormap "
+                           "with %d colors at %d bpp", 
+                           (*colormapP)->ncolors, *bppP);
+        }
+    } else {
+        pm_error("unknown format 0x%x on input file", (unsigned) format);
+    }
+}
+
+
+
+static const char * 
+formatName(int const format) {
+    
+    const char * retval;
+
+    switch(PNM_FORMAT_TYPE(format)) {
+    case PBM_TYPE: retval = "black and white"; break;
+    case PGM_TYPE: retval = "grayscale";       break;
+    case PPM_TYPE: retval = "color";           break;
+    default:       retval = "???";             break;
+    }
+    return retval;
+}
+
+        
+
+static void
+findTransparentColor(char *         const colorSpec, 
+                     pixval         const newMaxval,
+                     bool           const directColor, 
+                     pixval         const maxval, 
+                     Colormap       const colormap,
+                     xel *          const transcolorP, 
+                     unsigned int * const transindexP) {
+
+    *transcolorP = ppm_parsecolor(colorSpec, maxval);
+    if (!directColor) {
+        Color_s const temp_color = 
+            ((((PPM_GETR(*transcolorP)*newMaxval) / maxval) << 16) 
+             | (((PPM_GETG(*transcolorP)*newMaxval) / maxval) << 8)
+             | ((PPM_GETB(*transcolorP)*newMaxval) / maxval));
+        Color const found = 
+            (bsearch(&temp_color,
+                     colormap->color_entries, colormap->ncolors,
+                     sizeof(Color_s), palmcolor_compare_colors));
+        if (!found) {
+            pm_error("Specified transparent color %s not found "
+                     "in colormap.", colorSpec);
+        } else
+            *transindexP = (*found >> 24) & 0xFF;
+    }
+}
+
+
+
+static unsigned int
+bitmapVersion(unsigned int         const bpp,
+              bool                 const colormap,
+              bool                 const transparent,
+              enum compressionType const compression,
+              unsigned int         const density) {
+/*----------------------------------------------------------------------------
+   Return the version number of the oldest version that can represent
+   the specified attributes.
+-----------------------------------------------------------------------------*/
+    unsigned int version;
+    /* we need Version 1 if we use more than 1 bpp,
+       Version 2 if we use compression or transparency,
+       Version 3 if density is 108 or higher
+    */
+    if (density > PALM_DENSITY_LOW)
+        version = 3;
+    else if (transparent || compression != COMP_NONE)
+        version = 2;
+    else if (bpp > 1 || colormap)
+        version = 1;
+    else
+        version = 0;
+
+    return version;
+}
+
+
+
+static void
+writeCommonHeader(unsigned int         const cols,
+                  unsigned int         const rows,
+                  unsigned int         const rowbytes,
+                  enum compressionType const compression,
+                  bool                 const colormap,
+                  bool                 const transparent,
+                  bool                 const directColor,
+                  unsigned int         const bpp,
+                  unsigned int         const version) {
+/*----------------------------------------------------------------------------
+   Write the first 10 bytes of the Palm Bitmap header.
+   These are common to all encodings (versions 0, 1, 2 and 3).
+-----------------------------------------------------------------------------*/
+    unsigned short flags;
+
+    if (cols > USHRT_MAX)
+        pm_error("Too many columns for Palm Bitmap: %u", cols);
+    pm_writebigshort(stdout, cols);    /* width */
+    if (rows > USHRT_MAX)
+        pm_error("Too many columns for Palm Bitmap: %u", rows);
+    pm_writebigshort(stdout, rows);    /* height */
+    if (rowbytes > USHRT_MAX)
+        pm_error("Too many bytes per row for Palm Bitmap: %u", rowbytes);
+    pm_writebigshort(stdout, rowbytes);
+
+    flags = 0;  /* initial value */
+    if (compression != COMP_NONE)
+        flags |= PALM_IS_COMPRESSED_FLAG;
+    if (colormap)
+        flags |= PALM_HAS_COLORMAP_FLAG;
+    if (transparent)
+        flags |= PALM_HAS_TRANSPARENCY_FLAG;
+    if (directColor)
+        flags |= PALM_DIRECT_COLOR_FLAG;
+    pm_writebigshort(stdout, flags);
+    assert(bpp <= UCHAR_MAX);
+    fputc(bpp, stdout);
+
+    fputc(version, stdout);
+}
+
+
+
+static unsigned char 
+compressionFieldValue(enum compressionType const compression) {
+
+    unsigned char retval;
+
+    switch (compression) {
+    case COMP_SCANLINE:
+        retval = PALM_COMPRESSION_SCANLINE;
+        break;
+    case COMP_RLE:
+        retval = PALM_COMPRESSION_RLE;
+        break;
+    case COMP_PACKBITS:
+        retval = PALM_COMPRESSION_PACKBITS;
+        break;
+    case COMP_NONE:
+        retval = 0x00;  /* empty */
+        break;
+    }
+    return retval;
+}
+
+
+
+static void
+writeRemainingHeaderLow(unsigned int         const nextDepthOffset,
+                        unsigned int         const transindex,
+                        enum compressionType const compression,
+                        unsigned int         const bpp) {
+/*----------------------------------------------------------------------------
+   Write last 6 bytes of a low density Palm Bitmap header. 
+-----------------------------------------------------------------------------*/
+    if (nextDepthOffset > USHRT_MAX)
+        pm_error("Image too large for Palm Bitmap");
+
+    pm_writebigshort(stdout, nextDepthOffset);
+
+    if (bpp != 16) {
+        assert(transindex <= UCHAR_MAX);
+        fputc(transindex, stdout);    /* transparent index */
+    } else
+        fputc(0, stdout);    /* the DirectInfoType will hold this info */
+    
+    fputc(compressionFieldValue(compression), stdout);
+
+    pm_writebigshort(stdout, 0);  /* reserved by Palm */
+}
+
+
+
+static void
+writeRemainingHeaderHigh(unsigned int         const bpp,
+                         enum compressionType const compression,
+                         unsigned int         const density,
+                         xelval               const maxval,
+                         bool                 const transparent,
+                         xel                  const transcolor,
+                         unsigned int         const transindex,
+                         unsigned int         const nextBitmapOffset) {
+/*----------------------------------------------------------------------------
+   Write last 16 bytes of a high density Palm Bitmap header. 
+-----------------------------------------------------------------------------*/
+    if ((nextBitmapOffset >> 31) > 1)
+        pm_error("Image too large for Palm Bitmap.  nextBitmapOffset "
+            "value doesn't fit in 4 bytes");
+
+    fputc(0x18, stdout); /* size of this high density header */
+
+    if (bpp != 16)
+        fputc(PALM_FORMAT_INDEXED, stdout);
+    else
+        fputc(PALM_FORMAT_565, stdout);
+
+    fputc(0x00, stdout); /* unused */
+
+    fputc(compressionFieldValue(compression), stdout);
+
+    pm_writebigshort(stdout, density);
+
+    if (transparent) {
+        if (bpp == 16) {
+            /* Blind guess here */
+            fputc(0, stdout);
+            fputc((PPM_GETR(transcolor) * 255) / maxval, stdout);
+            fputc((PPM_GETG(transcolor) * 255) / maxval, stdout);
+            fputc((PPM_GETB(transcolor) * 255) / maxval, stdout);
+        } else {
+            assert(transindex <= UCHAR_MAX);
+            fputc(0, stdout);
+            fputc(0, stdout);
+            fputc(0, stdout);
+            fputc(transindex, stdout);   /* transparent index */
+        }
+    } else
+        pm_writebiglong(stdout, 0);
+
+    pm_writebiglong(stdout, nextBitmapOffset);
+}
+
+
+
+static void
+writeDummy() {
+/*----------------------------------------------------------------------------
+   Write a dummy Palm Bitmap header.  This is a 16 byte header, of
+   type version 1 and with (only) pixelSize set to 0xFF.
+
+   An old viewer will see this as invalid due to the pixelSize, and stop
+   reading the stream.  A new viewer will recognize this for what it is
+   (a dummy header designed to stop old viewers from reading further in
+   the stream) and continue reading the stream.  Presumably, what follows
+   in the stream is understandable by a new viewer, but would confuse an
+   old one.
+-----------------------------------------------------------------------------*/
+    pm_writebiglong(stdout, 0x00);
+    pm_writebiglong(stdout, 0x00);
+    fputc(0xFF, stdout);               /* pixelSize */
+    fputc(0x01, stdout);               /* version */
+    pm_writebigshort(stdout, 0x00); 
+    pm_writebiglong(stdout, 0x00);
+}
+
+
+
+static void
+writeColormap(bool         const explicitColormap,
+              Colormap     const colormap,
+              bool         const directColor,
+              unsigned int const bpp,
+              bool         const transparent,
+              xel          const transcolor,
+              xelval       const maxval,
+              unsigned int const version) {
+              
+    /* if there's a colormap, write it out */
+    if (explicitColormap) {
+        unsigned int row;
+        if (!colormap)
+            pm_error("Internal error: user specified -colormap, but we did "
+                     "not generate a colormap.");
+        qsort (colormap->color_entries, colormap->ncolors,
+               sizeof(Color_s), palmcolor_compare_indices);
+        pm_writebigshort( stdout, colormap->ncolors );
+        for (row = 0;  row < colormap->ncolors; ++row)
+            pm_writebiglong (stdout, colormap->color_entries[row]);
+    }
+
+    if (directColor && (version < 3)) {
+        /* write the DirectInfoType (8 bytes) */
+        if (bpp == 16) {
+            fputc(5, stdout);   /* # of bits of red */
+            fputc(6, stdout);   /* # of bits of green */    
+            fputc(5, stdout);   /* # of bits of blue */
+            fputc(0, stdout);   /* reserved by Palm */
+        } else
+            pm_error("Don't know how to create %d bit DirectColor bitmaps.", 
+                     bpp);
+        if (transparent) {
+            fputc(0, stdout);
+            fputc((PPM_GETR(transcolor) * 255) / maxval, stdout);
+            fputc((PPM_GETG(transcolor) * 255) / maxval, stdout);
+            fputc((PPM_GETB(transcolor) * 255) / maxval, stdout);
+        } else
+            pm_writebiglong(stdout, 0);     /* no transparent color */
+    }
+}
+
+
+
+static void
+computeRawRowDirectColor(const xel *     const xelrow,
+                         unsigned int    const cols,
+                         xelval          const maxval,
+                         unsigned char * const rowdata) {
+
+    unsigned int col;
+    unsigned char *outptr;
+    
+    for (col = 0, outptr = rowdata; col < cols; ++col) {
+        unsigned int const color = 
+            ((((PPM_GETR(xelrow[col])*31)/maxval) << 11) |
+             (((PPM_GETG(xelrow[col])*63)/maxval) << 5) |
+             ((PPM_GETB(xelrow[col])*31)/maxval));
+        *outptr++ = (color >> 8) & 0xFF;
+        *outptr++ = color & 0xFF;
+    }
+}
+
+
+
+static void
+computeRawRowNonDirect(const xel *     const xelrow,
+                       unsigned int    const cols,
+                       xelval          const maxval,
+                       unsigned int    const bpp,
+                       Colormap        const colormap,
+                       unsigned int    const newMaxval,
+                       unsigned char * const rowdata) {
+
+    unsigned int col;
+    unsigned char *outptr;
+    unsigned char outbyte;
+        /* Accumulated bits to be output */
+    unsigned char outbit;
+        /* The lowest bit number we want to access for this pixel */
+
+    outbyte = 0x00;
+    outptr = rowdata;
+
+    for (outbit = 8 - bpp, col = 0; col < cols; ++col) {
+        unsigned int color;
+        if (!colormap) {
+            /* we assume grayscale, and use simple scaling */
+            color = (PNM_GET1(xelrow[col]) * newMaxval)/maxval;
+            if (color > newMaxval)
+                pm_error("oops.  Bug in color re-calculation code.  "
+                         "color of %u.", color);
+            color = newMaxval - color; /* note grayscale maps are inverted */
+        } else {
+            Color_s const temp_color =
+                ((((PPM_GETR(xelrow[col])*newMaxval)/maxval)<<16) 
+                 | (((PPM_GETG(xelrow[col])*newMaxval)/maxval)<<8)
+                 | (((PPM_GETB(xelrow[col])*newMaxval)/maxval)));
+            Color const found = (bsearch (&temp_color,
+                                          colormap->color_entries, 
+                                          colormap->ncolors,
+                                          sizeof(Color_s), 
+                                          palmcolor_compare_colors));
+            if (!found) {
+                pm_error("Color %d:%d:%d not found in colormap.  "
+                         "Try using pnmquant to reduce the "
+                         "number of colors.",
+                         PPM_GETR(xelrow[col]), 
+                         PPM_GETG(xelrow[col]), 
+                         PPM_GETB(xelrow[col]));
+            }
+            color = (*found >> 24) & 0xFF;
+        }
+
+        if (color > newMaxval)
+            pm_error("oops.  Bug in color re-calculation code.  "
+                     "color of %u.", color);
+        outbyte |= (color << outbit);
+        if (outbit == 0) {
+            /* Bit buffer is full.  Flush to to rowdata. */
+            *outptr++ = outbyte;
+            outbyte = 0x00;
+            outbit = 8 - bpp;
+        } else
+            outbit -= bpp;
+    }
+    if ((cols % (8 / bpp)) != 0) {
+        /* Flush bits remaining in the bit buffer to rowdata */
+        *outptr++ = outbyte;
+    }
+}
+
+
+struct seqBuffer {
+/*----------------------------------------------------------------------------
+   A buffer to which one can write bytes sequentially.
+-----------------------------------------------------------------------------*/
+    char * buffer;
+    unsigned int allocatedSize;
+    unsigned int occupiedSize;
+};
+
+
+static void
+createBuffer(struct seqBuffer ** const bufferPP) {
+
+    struct seqBuffer * bufferP;
+
+    MALLOCVAR_NOFAIL(bufferP);
+
+    bufferP->allocatedSize = 4096;
+    MALLOCARRAY(bufferP->buffer, bufferP->allocatedSize);
+    if (bufferP == NULL)
+        pm_error("Unable to allocate %u bytes of buffer", 
+                 bufferP->allocatedSize);
+    bufferP->occupiedSize = 0;
+
+    *bufferPP = bufferP;
+}
+
+
+
+static void
+destroyBuffer(struct seqBuffer * const bufferP) {
+
+    free(bufferP->buffer);
+    free(bufferP);
+}
+
+
+
+static void
+addByteToBuffer(struct seqBuffer * const bufferP,
+                unsigned char      const newByte) {
+
+    assert(bufferP->allocatedSize >= bufferP->occupiedSize);
+
+    if (bufferP->allocatedSize == bufferP->occupiedSize) {
+        bufferP->allocatedSize *= 2;
+        REALLOCARRAY(bufferP->buffer, bufferP->allocatedSize);
+        if (bufferP->buffer == NULL)
+            pm_error("Couldn't (re)allocate %u bytes of memory "
+                     "for buffer.", bufferP->allocatedSize);
+    }
+    bufferP->buffer[bufferP->occupiedSize++] = newByte;
+}
+
+
+
+static unsigned int
+bufferLength(struct seqBuffer * const bufferP) {
+    return bufferP->occupiedSize;
+}
+
+
+
+static void
+writeOutBuffer(struct seqBuffer * const bufferP,
+               FILE *             const fileP) {
+
+    size_t bytesWritten;
+
+    bytesWritten = fwrite(bufferP->buffer, sizeof(char), 
+                          bufferP->occupiedSize, fileP);
+
+    if (bytesWritten != bufferP->occupiedSize)
+        pm_error("fwrite() failed to write out the buffer.");
+}
+
+
+
+static void
+copyRowToBuffer(const unsigned char * const rowdata,
+                unsigned int          const rowbytes,
+                struct seqBuffer *    const rasterBufferP) {
+
+    unsigned int pos;
+    for (pos = 0; pos < rowbytes; ++pos)
+        addByteToBuffer(rasterBufferP, rowdata[pos]);
+} 
+
+
+
+static void
+scanlineCompressAndBufferRow(const unsigned char * const rowdata,
+                             unsigned int          const rowbytes,
+                             struct seqBuffer *    const rasterBufferP,
+                             const unsigned char * const lastrow) {
+/*----------------------------------------------------------------------------
+   Take the raw Palm Bitmap row 'rowdata', which is 'rowbytes'
+   columns, and add the scanline-compressed representation of it to
+   the buffer with handle 'rasterBufferP'.
+
+   'lastrow' is the raw contents of the row immediately before the one
+   we're compressing -- i.e. we compress with respect to that row.  This
+   function does not work on the first row of an image.
+-----------------------------------------------------------------------------*/
+    unsigned int pos;
+
+    for (pos = 0;  pos < rowbytes;  pos += 8) {
+        unsigned int const limit = MIN(rowbytes - pos, 8);
+
+        unsigned char map;
+            /* mask indicating which of the next 8 pixels are
+               different from the previous row, and therefore present
+               in the file immediately following the map byte.  
+            */
+        unsigned char differentPixels[8];
+        unsigned char *outptr;
+        unsigned char outbit;
+            
+        for (outbit = 0, map = 0x00, outptr = differentPixels;
+             outbit < limit;  
+             ++outbit) {
+            if (!lastrow 
+                || (lastrow[pos + outbit] != rowdata[pos + outbit])) {
+                map |= (1 << (7 - outbit));
+                *outptr++ = rowdata[pos + outbit];
+            }
+        }
+
+        addByteToBuffer(rasterBufferP, map);
+        {
+            unsigned int j;
+            for (j = 0; j < (outptr - differentPixels); ++j)
+                addByteToBuffer(rasterBufferP, differentPixels[j]);
+        }
+    }
+}
+
+
+
+static void
+rleCompressAndBufferRow(const unsigned char * const rowdata,
+                        unsigned int          const rowbytes,
+                        struct seqBuffer *    const rasterBufferP) {
+/*----------------------------------------------------------------------------
+   Take the raw Palm Bitmap row 'rowdata', which is 'rowbytes' bytes,
+   and add the rle-compressed representation of it to the buffer with
+   handle 'rasterBufferP'.
+-----------------------------------------------------------------------------*/
+    unsigned int pos;
+
+    /* we output a count of the number of bytes a value is
+       repeated, followed by that byte value 
+    */
+    pos = 0;
+    while (pos < rowbytes) {
+        unsigned int repeatcount;
+        for (repeatcount = 1;  
+             repeatcount < (rowbytes - pos) && repeatcount  < 255;  
+             ++repeatcount)
+            if (rowdata[pos + repeatcount] != rowdata[pos])
+                break;
+
+        addByteToBuffer(rasterBufferP, repeatcount);
+        addByteToBuffer(rasterBufferP, rowdata[pos]);
+        pos += repeatcount;
+    }
+}
+
+
+
+static void
+computeNextPackbitsRun(const unsigned char * const rowdata,
+                       unsigned int          const rowbytes,
+                       unsigned int          const startPos,
+                       unsigned int *        const nextPosP,
+                       unsigned char *       const output,
+                       int *                 const countP) {
+
+    unsigned int pos;
+    int count;
+    
+    pos = startPos;
+    count = 0;
+    
+    if (rowdata[pos] == rowdata[pos + 1]) {
+        ++pos;
+        --count;
+        while ((count > -127) && (pos < (rowbytes - 1)) &&
+               (rowdata[pos] == rowdata[pos + 1])) {
+            ++pos;
+            --count;
+        }
+        ++pos;  /* push pos past end of this run */
+    } else {
+        output[count] = rowdata[pos];
+        ++pos;
+        while ((count < 127) && (pos < (rowbytes - 1)) && 
+               (rowdata[pos] != rowdata[pos + 1])) {
+            ++count;
+            output[count] = rowdata[pos];
+            ++pos;
+        }
+        /* trailing literal */
+        if ((count < 127) && (pos == (rowbytes - 1)) &&
+            (rowdata[pos - 1] != rowdata[pos])) {
+            ++count;
+            output[count] = rowdata[pos];
+            ++pos;
+        }
+    }
+    *nextPosP = pos;
+    *countP = count;
+}
+
+
+
+static void
+addPackbitsRunToBuffer(const unsigned char * const rowdata,
+                       unsigned int          const rowbytes,
+                       unsigned int          const pos,
+                       unsigned char *       const output,
+                       int                   const count,
+                       struct seqBuffer *    const rasterBufferP) {
+
+    addByteToBuffer(rasterBufferP, (unsigned char)(signed char)count);
+    if (count < 0) {
+        addByteToBuffer(rasterBufferP, rowdata[pos - 1]);
+    } else {
+        unsigned int j;
+        for (j = 0; j <= count; j++)
+            addByteToBuffer(rasterBufferP, output[j]);
+    }
+    
+    if (pos == (rowbytes - 1) && (rowdata[pos - 1] != rowdata[pos])) {
+        /* orphaned byte, treat as literal */
+        addByteToBuffer(rasterBufferP, 0);
+        addByteToBuffer(rasterBufferP, rowdata[pos]);
+    }
+}
+
+
+
+static void
+packbitsCompressAndBufferRow(const unsigned char * const rowdata,
+                             unsigned int          const rowbytes,
+                             struct seqBuffer *    const rasterBufferP) {
+/*----------------------------------------------------------------------------
+   Take the raw Palm Bitmap row 'rowdata', which is 'rowbytess' bytes, and
+   add the packbits-compressed representation of it to the buffer 
+   with handle 'rasterBufferP'.
+-----------------------------------------------------------------------------*/
+    unsigned int position;
+        /* byte position within the row */
+
+    position = 0;  /* Start at beginning of row */
+
+    while (position < rowbytes - 1) {
+        unsigned char output[128];
+        int count;
+
+        computeNextPackbitsRun(rowdata, rowbytes, position, 
+                               &position, output, &count);
+
+        addPackbitsRunToBuffer(rowdata, rowbytes, position, output, count,
+                               rasterBufferP);
+    }
+}
+
+
+
+static void
+bufferRowFromRawRowdata(const unsigned char *  const rowdata,
+                        unsigned int           const rowbytes,
+                        enum compressionType   const compression,
+                        const unsigned char *  const lastrow,
+                        struct seqBuffer *     const rasterBufferP) {
+/*----------------------------------------------------------------------------
+   Starting with a raw (uncompressed) Palm raster line, do the
+   compression identified by 'compression' and add the compressed row
+   to the buffer with handle 'rasterBufferP'.
+
+   If 'compression' indicates scanline compression, 'lastrow' is the
+   row immediately preceding this one in the image (and this function
+   doesn't work on the first row of an image).  Otherwise, 'lastrow'
+   is meaningless.
+-----------------------------------------------------------------------------*/
+    switch (compression) {
+    case COMP_NONE:
+        copyRowToBuffer(rowdata, rowbytes, rasterBufferP);
+        break;
+    case COMP_SCANLINE:
+        scanlineCompressAndBufferRow(rowdata, rowbytes, rasterBufferP, 
+                                     lastrow);
+        break;
+    case COMP_RLE:
+        rleCompressAndBufferRow(rowdata, rowbytes, rasterBufferP);
+        break;
+    case COMP_PACKBITS:
+        packbitsCompressAndBufferRow(rowdata, rowbytes, rasterBufferP);
+        break;
+    }
+}
+
+
+
+static void
+bufferRow(const xel *          const xelrow,
+          unsigned int         const cols,
+          xelval               const maxval,
+          unsigned int         const rowbytes,
+          unsigned int         const bpp,
+          unsigned int         const newMaxval,
+          enum compressionType const compression,
+          bool                 const directColor,
+          Colormap             const colormap,
+          unsigned char *      const rowdata,
+          unsigned char *      const lastrow,
+          struct seqBuffer *   const rasterBufferP) {
+/*----------------------------------------------------------------------------
+   Add a row of the Palm Bitmap raster to buffer 'rasterBufferP'.
+   
+   'xelrow' is the image contents of row.  It is 'cols' columns wide.
+
+   If 'compression' indicates scanline compression, 'lastrow' is the
+   row immediately preceding this one in the image (and this function
+   doesn't work on the first row of an image).  Otherwise, 'lastrow'
+   is meaningless.
+
+   'rowdata' is a work buffer 'rowbytes' in size.
+-----------------------------------------------------------------------------*/
+    if (directColor)
+        computeRawRowDirectColor(xelrow, cols, maxval, rowdata);
+    else 
+        computeRawRowNonDirect(xelrow, cols, maxval, bpp, colormap, newMaxval,
+                               rowdata);
+
+    bufferRowFromRawRowdata(rowdata, rowbytes, compression,
+                            lastrow, rasterBufferP);
+}
+
+
+
+static void 
+bufferRaster(xel **               const xels,
+             unsigned int         const cols,
+             unsigned int         const rows,
+             xelval               const maxval,
+             unsigned int         const rowbytes,
+             unsigned int         const bpp,
+             unsigned int         const newMaxval,
+             enum compressionType const compression,
+             bool                 const directColor,
+             Colormap             const colormap,
+             struct seqBuffer **  const rasterBufferPP) {
+    
+    unsigned char * rowdata;
+    unsigned char * lastrow;
+    unsigned int row;
+
+    createBuffer(rasterBufferPP);
+    
+    MALLOCARRAY_NOFAIL(rowdata, rowbytes);
+    MALLOCARRAY_NOFAIL(lastrow, rowbytes);
+
+    /* And write out the data. */
+    for (row = 0; row < rows; ++row) {
+        bufferRow(xels[row], cols, maxval, rowbytes, bpp, newMaxval,
+                  compression,
+                  directColor, colormap, rowdata, row > 0 ? lastrow : NULL,
+                  *rasterBufferPP);
+
+        if (compression == COMP_SCANLINE)
+            memcpy(lastrow, rowdata, rowbytes);
+    }
+    free(lastrow);
+    free(rowdata);
+}
+
+
+
+static void
+computeOffsetStuff(bool                 const offsetWanted,
+                   unsigned int         const version,
+                   bool                 const directColor,
+                   enum compressionType const compression,
+                   bool                 const colormap,
+                   unsigned int         const colormapColorCount,
+                   unsigned int         const sizePlusRasterSize,
+                   unsigned int *       const nextDepthOffsetP,
+                   unsigned int *       const nextBitmapOffsetP,
+                   unsigned int *       const padBytesRequiredP) {
+    
+    if (offsetWanted) {
+        /* Offset is measured in 4-byte words (double words in
+           Intel/Microsoft terminology).  Account for header,
+           colormap, and raster size and round up 
+        */
+        unsigned int const headerSize = ((version < 3) ? 16 : 24);
+        unsigned int const colormapSize =
+            (colormap ? (2 + colormapColorCount * 4) : 0);
+        if (version < 3) {
+            unsigned int const directSize = 
+                (directColor && version < 3) ? 8 : 0; 
+            if (compression != COMP_NONE && sizePlusRasterSize > USHRT_MAX)
+                pm_error("Oversized compressed bitmap: %u bytes",
+                         sizePlusRasterSize);
+            *padBytesRequiredP = 4 - (sizePlusRasterSize + headerSize + 
+                                      directSize + colormapSize) % 4;
+            *nextDepthOffsetP = 
+                (sizePlusRasterSize + headerSize + 
+                 directSize + colormapSize + *padBytesRequiredP) / 4;
+        } else {
+            if (compression != COMP_NONE && (sizePlusRasterSize >> 31) > 1)
+                pm_error("Oversized compressed bitmap: %u bytes",
+                         sizePlusRasterSize);
+            /* Does version 3 need padding? Probably won't hurt */
+            *padBytesRequiredP = 4 - (sizePlusRasterSize + headerSize +
+                                      colormapSize) % 4;
+            *nextBitmapOffsetP = sizePlusRasterSize + headerSize + 
+                colormapSize + *padBytesRequiredP;
+        }
+    } else {
+        *padBytesRequiredP = 0;
+        *nextDepthOffsetP = 0;
+        *nextBitmapOffsetP = 0;
+    }
+}
+
+
+
+static void
+writeRasterSize(unsigned int const sizePlusRasterSize,
+                unsigned int const version,
+                FILE *       const fileP) {
+/*----------------------------------------------------------------------------
+   Write to file 'fileP' a raster size field for a Palm Bitmap version
+   'version' header, indicating 'sizePlusRasterSize' bytes.
+-----------------------------------------------------------------------------*/
+    if (version < 3) 
+        pm_writebigshort(fileP, sizePlusRasterSize);
+    else
+        pm_writebiglong(fileP, sizePlusRasterSize);
+}
+
+
+
+static void
+writeBitmap(xel **               const xels,
+            unsigned int         const cols,
+            unsigned int         const rows,
+            xelval               const maxval,
+            unsigned int         const rowbytes,
+            unsigned int         const bpp,
+            unsigned int         const newMaxval,
+            enum compressionType const compression,
+            bool                 const transparent,
+            bool                 const directColor,
+            bool                 const offsetWanted,
+            bool                 const hasColormap,
+            Colormap             const colormap,
+            unsigned int         const transindex,
+            xel                  const transcolor,
+            unsigned int         const version,
+            unsigned int         const density,
+            bool                 const withdummy) {
+    
+    unsigned int sizePlusRasterSize;
+    unsigned int nextDepthOffset;
+    unsigned int nextBitmapOffset;
+        /* Offset from the beginning of the image we write to the beginning
+           of the next one, assuming user writes another one following this
+           one.
+           nextDepthOffset is used in encodings 1, 2 and is in 4 byte words 
+           nextBitmapOffset is used in encoding 3, is in 4 bytes 
+        */
+    unsigned int padBytesRequired;
+        /* Number of bytes of padding we need to put after the image in
+           order to align properly for User to add the next image to the
+           stream.
+        */
+    struct seqBuffer * rasterBufferP;
+
+    writeCommonHeader(cols, rows, rowbytes, compression, hasColormap, 
+                      transparent, directColor, bpp, version);
+    
+    bufferRaster(xels, cols, rows, maxval, rowbytes, bpp, newMaxval,
+                 compression, directColor, colormap, &rasterBufferP);
+
+    /* rasterSize itself takes 2 or 4 bytes */
+    if (version < 3)
+        sizePlusRasterSize = 2 + bufferLength(rasterBufferP);
+    else
+        sizePlusRasterSize = 4 + bufferLength(rasterBufferP);
+    
+    computeOffsetStuff(offsetWanted, version, directColor, compression,
+                       hasColormap, hasColormap ? colormap->ncolors : 0, 
+                       sizePlusRasterSize,
+                       &nextDepthOffset, &nextBitmapOffset,
+                       &padBytesRequired);
+
+    if (version < 3)
+        writeRemainingHeaderLow(nextDepthOffset, transindex, compression, bpp);
+    else
+        writeRemainingHeaderHigh(bpp, compression, density,
+                                 maxval, transparent, transcolor,
+                                 transindex, nextBitmapOffset);
+
+    writeColormap(hasColormap, colormap, directColor, bpp, 
+                  transparent, transcolor, maxval, version);
+
+    if (compression != COMP_NONE)
+        writeRasterSize(sizePlusRasterSize, version, stdout);
+
+    writeOutBuffer(rasterBufferP, stdout);
+
+    destroyBuffer(rasterBufferP);
+
+    {
+        unsigned int i;
+        for (i = 0; i < padBytesRequired; ++i)
+            fputc(0x00, stdout);
+    }
+
+    if (withdummy)
+        writeDummy();
+}        
+
+
+
+int 
+main( int argc, char **argv ) {
+    struct cmdline_info cmdline;
+    unsigned int version;
+    FILE* ifP;
+    xel** xels;
+    xel transcolor;
+    unsigned int transindex;
+    int rows, cols;
+    unsigned int rowbytes;
+    xelval maxval;
+    int format;
+    unsigned int bpp;
+    bool directColor;
+    unsigned int newMaxval;
+    Colormap colormap;
+    
+    /* Parse default params */
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    xels = pnm_readpnm(ifP, &cols, &rows, &maxval, &format);
+    pm_close(ifP);
+
+    if (cmdline.verbose)
+        pm_message("Input is %dx%d %s, maxval %d", 
+                   cols, rows, formatName(format), maxval);
+    
+    determinePalmFormat(cols, rows, maxval, format, xels, cmdline.depth,
+                        cmdline.maxdepth, cmdline.colormap, cmdline.verbose,
+                        &bpp, &directColor, &colormap);
+
+    newMaxval = (1 << bpp) - 1;
+
+    if (cmdline.transparent) 
+        findTransparentColor(cmdline.transparent, newMaxval, directColor,
+                             maxval, colormap, &transcolor, &transindex);
+    else 
+        transindex = 0;
+
+    rowbytes = ((cols + (16 / bpp -1)) / (16 / bpp)) * 2;    
+        /* bytes per row - always a word boundary */
+
+    version = bitmapVersion(bpp, cmdline.colormap, !!cmdline.transparent, 
+                            cmdline.compression, cmdline.density);
+
+    writeBitmap(xels, cols, rows, maxval,
+                rowbytes, bpp, newMaxval, cmdline.compression,
+                !!cmdline.transparent, directColor, cmdline.offset, 
+                cmdline.colormap, colormap, transindex, transcolor,
+                version, cmdline.density, cmdline.withdummy);
+    
+    return 0;
+}
diff --git a/converter/other/pnmtopclxl.c b/converter/other/pnmtopclxl.c
new file mode 100644
index 00000000..79ee092c
--- /dev/null
+++ b/converter/other/pnmtopclxl.c
@@ -0,0 +1,1204 @@
+/*
+ * -------------------------------------------------------------
+ *
+ *  (C) 2002 Jochen Karrer, Linuxdata GbR
+ *
+ *      convert a pnm to PCL-XL image 
+ *
+ * -------------------------------------------------------------
+ */
+
+/* Engineering note: One PCL-XL printer prints an error message like
+   this when it doesn't like the PCL it sees:
+
+   PCL XL error
+      Subsystem:  IMAGE
+      Error:      IllegalAttributeValue
+      Operator:   ReadImage
+      Position:   8
+
+   "Position" is the sequence number of the PCL operator it was trying
+   to execute.
+*/
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <ctype.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+#include "pclxl.h"
+
+#define PAPERWIDTH(format) (xlPaperFormats[format].width)
+#define PAPERHEIGHT(format) (xlPaperFormats[format].height)
+
+
+
+typedef struct InputSource {
+    const char *         name; 
+    struct InputSource * next;
+} InputSource;
+
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    InputSource * sourceP;
+    int dpi;
+    enum MediaSize format;
+    unsigned int feederSpec;
+    int feeder;
+    unsigned int outtraySpec;
+    int outtray;
+    unsigned int duplexSpec;
+    enum DuplexPageMode duplex;
+    unsigned int copiesSpec;
+    int copies;
+    unsigned int center;
+    float xoffs;
+    float yoffs;
+    unsigned int colorok;
+    unsigned int verbose;
+    const char * jobsetup;  /* -jobsetup option value.  NULL if none */
+    unsigned int rendergray;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc( 100*sizeof( optEntry ) );
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    char *formatOpt;
+    char *duplexOpt;
+    unsigned int dpiSpec, xoffsSpec, yoffsSpec, formatSpec, jobsetupSpec;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "dpi",       OPT_UINT,    &cmdlineP->dpi,
+            &dpiSpec,         0);
+    OPTENT3(0, "xoffs",     OPT_FLOAT,   &cmdlineP->xoffs, 
+            &xoffsSpec,        0);
+    OPTENT3(0, "yoffs",     OPT_FLOAT,   &cmdlineP->yoffs, 
+            &yoffsSpec,        0);
+    OPTENT3(0, "format",    OPT_STRING,  &formatOpt, 
+            &formatSpec,        0);
+    OPTENT3(0, "duplex",    OPT_STRING,  &duplexOpt, 
+            &cmdlineP->duplexSpec,        0);
+    OPTENT3(0, "copies",    OPT_UINT,    &cmdlineP->copies,
+            &cmdlineP->copiesSpec,        0);
+    OPTENT3(0, "colorok",   OPT_FLAG,    NULL,                  
+            &cmdlineP->colorok, 0);
+    OPTENT3(0, "center",    OPT_FLAG,    NULL,                  
+            &cmdlineP->center, 0 );
+    OPTENT3(0, "feeder",    OPT_STRING,  &cmdlineP->feeder,
+            &cmdlineP->feederSpec,        0);
+    OPTENT3(0, "outtray",   OPT_STRING,  &cmdlineP->outtray,
+            &cmdlineP->outtraySpec,       0);
+    OPTENT3(0, "verbose",   OPT_FLAG,    NULL,                  
+            &cmdlineP->verbose, 0);
+    OPTENT3(0, "jobsetup",  OPT_STRING,  &cmdlineP->jobsetup,
+            &jobsetupSpec,      0);
+    OPTENT3(0, "rendergray", OPT_FLAG,    NULL,
+            &cmdlineP->rendergray, 0 );
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!dpiSpec)
+        cmdlineP->dpi = 300;
+    if (!xoffsSpec)
+        cmdlineP->xoffs = 0.0;
+    if (!yoffsSpec)
+        cmdlineP->yoffs = 0.0;
+
+    if (cmdlineP->duplexSpec) {
+        if (strncmp(duplexOpt, "vertical", strlen(duplexOpt)) == 0)
+            cmdlineP->duplex = eDuplexVerticalBinding;
+        else if (strncmp(duplexOpt, "horizontal", strlen(duplexOpt)) == 0)
+            cmdlineP->duplex = eDuplexHorizontalBinding;
+        else
+            pm_error("Invalid value '%s' for -duplex option", duplexOpt);
+    }
+
+    if (formatSpec) {
+        bool found;
+        int i;
+        for (i = 0, found=FALSE; xlPaperFormats[i].name && !found; ++i) {
+            if (STREQ(xlPaperFormats[i].name, formatOpt)) {
+                found = TRUE;
+                cmdlineP->format = xlPaperFormats[i].xl_nr;
+            }
+        }
+        if (!found) {
+            int i;
+            pm_message("Valid -format values:");
+            for (i = 0; xlPaperFormats[i].name; ++i) {
+                if (xlPaperFormats[i].width > 0)
+                    pm_message("   %s", xlPaperFormats[i].name);
+            }
+            pm_error("Invalid -format option '%s' specified.", formatOpt);
+        }
+    } else
+        cmdlineP->format = eLetterPaper;
+
+    if (!jobsetupSpec)
+        cmdlineP->jobsetup = NULL;
+
+    if (argc-1 < 1) {
+        MALLOCVAR(cmdlineP->sourceP);
+        cmdlineP->sourceP->name = "-";
+    } else {
+        int i;
+        InputSource ** nextLinkP;
+
+        nextLinkP = &cmdlineP->sourceP;
+        for (i = 1; i < argc; ++i) {
+            InputSource * sourceP;
+            MALLOCVAR(sourceP);
+            sourceP->name = argv[i];
+            *nextLinkP = sourceP;
+            nextLinkP = &sourceP->next;
+            *nextLinkP = NULL;
+        }
+    }
+}
+
+
+
+#define XY_RLE_FBUFSIZE (1024)
+typedef struct XY_rle {
+    int error;
+    unsigned char buf[128];
+    int bpos;
+    int state;  
+    unsigned char *fbuf;
+    int fbpos;
+    int fbufsize;
+    int fd;
+} XY_rle;
+
+
+static void 
+XY_RLEreset(XY_rle *rle) 
+{   
+    rle->state = eSTART;
+    rle->bpos = 0;
+    rle->fbpos=0;
+    rle->error=0;
+}
+static XY_rle * 
+XY_RLEnew(int size) {
+    XY_rle *rle;
+
+    MALLOCVAR(rle);
+    if(rle==NULL)
+        return rle;
+    if(size<1024)
+        size=1024;
+    rle->fbuf=malloc(size);
+    rle->fbufsize=size;
+    if(!rle->fbuf) {
+        free(rle);
+        return NULL;
+    }
+    return rle;
+}
+static void
+XY_RLEdelete(XY_rle *rle) {
+    free(rle->fbuf);
+    free(rle);
+}
+
+static int 
+out(XY_rle *rle,int count) {
+    if(rle->state==eRLE) {
+        rle->fbuf[rle->fbpos++]=-count+1;
+        rle->fbuf[rle->fbpos++]=rle->buf[0];
+    } else if(rle->bpos>0) {
+        rle->fbuf[rle->fbpos++]=count-1;
+        memcpy(rle->fbuf+rle->fbpos,rle->buf,count);
+        rle->fbpos+=count;
+    }
+    if(rle->fbpos+129>rle->fbufsize) {
+        rle->fbufsize*=1.2; 
+        rle->fbuf=realloc(rle->fbuf,rle->fbufsize);
+        if(rle->fbuf==NULL) {
+            rle->error=-1;
+            rle->fbpos=0;
+            return -1;
+        }
+    }
+    rle->bpos=0;
+    rle->state=eSTART;
+    return 0;
+}
+static int
+XY_RLEfinish (XY_rle *rle) {
+    out(rle,rle->bpos);
+    if(rle->error<0) 
+        return rle->error;
+    else
+        return rle->fbpos;
+}
+static  void
+rle_putbyte(XY_rle *rle,unsigned char u) 
+{
+    switch (rle->state) {
+        case eRLE:
+            if(u!=rle->buf[0]) {
+                out(rle,rle->bpos);
+            }   
+            break;
+        case eLIT:
+            if((u==rle->buf[rle->bpos-1])&&(u==rle->buf[rle->bpos-2])) {
+                out(rle,rle->bpos-2);
+                rle->buf[0]=u;
+                rle->bpos+=2;
+                rle->state=eRLE;
+            }   
+            break;
+        case eSTART:
+            if(rle->bpos==1) {
+                if(u==rle->buf[rle->bpos-1]) {
+                    rle->state=eRLE;
+                } else {
+                    rle->state=eLIT;
+                }   
+            }
+            break;
+    
+    }
+    rle->buf[rle->bpos++]=u;
+    if(rle->bpos==128) {
+        out(rle,rle->bpos);
+    }
+}
+
+
+
+static void
+XY_RLEput(XY_rle *rle,const unsigned char buf[],int count) 
+{
+    int i;
+    for(i=0;i<count;i++) {
+        rle_putbyte(rle,buf[i]);
+    }
+    
+}
+
+
+static int
+XY_Write(int fd, const void *buf,int cnt) {
+        int len=0;
+        while(len<cnt) {
+                int n = write(fd,(char*)buf+len,cnt-len);
+                if(n<=0)
+                        return n;
+                len+=n;
+        }
+        return len;
+}
+#define XY_Puts(fd,str)  XY_Write(fd,str,strlen(str))
+
+typedef struct pclGenerator {
+    enum ColorDepth colorDepth;
+    enum Colorspace colorSpace;
+    int width,height;
+    int linelen; /* bytes per line */
+    unsigned char *data;
+    void (*getnextrow)(const struct pclGenerator *, struct pam *);
+} pclGenerator;
+
+struct tPrinter { 
+    const char *name;
+    float topmargin;
+    float bottommargin;
+    float leftmargin;
+    float rightmargin;
+} xlPrinters[] = {
+    { "lj2200",0,0,0,0 }
+};
+
+
+
+static int
+out_ubyte(int fd,unsigned char data) {
+    return XY_Write(fd,&data,1);
+}
+static  int 
+XL_Operator(int fd,enum Operator const data)  {
+    return out_ubyte(fd,data);
+}
+static int
+out_uint16(int fd,unsigned short data ) {
+    unsigned char c[2];
+    c[0]=data&0xff; c[1]=data>>8;
+    return XY_Write(fd,c,2);
+}
+static int
+out_uint32(int fd,unsigned int data ) {
+    unsigned char c[4];
+    c[0] = data&0xff; c[1]=(data>>8)&0xff; c[2]=(data>>16)&0xff; c[3]=data>>24;
+    return XY_Write(fd,c,4);
+}
+static int
+out_sint16(int fd,signed short sdata ) {
+    unsigned short data=(unsigned short)sdata;    
+    unsigned char c[2];
+    c[0]=data&0xff; c[1]=data>>8;
+    return XY_Write(fd,c,2);
+}
+#if 0
+static int
+out_sint32(int fd,signed int sdata ) {
+    unsigned int data=(unsigned int)sdata;
+    unsigned char c[4];
+    c[0] = data&0xff; c[1]=(data>>8)&0xff; c[2]=(data>>16)&0xff; c[3]=data>>24;
+    return XY_Write(fd,c,4);
+}
+#endif
+
+static int
+xl_ubyte(int fd,unsigned char data) {
+    unsigned char const tag=0xc0;
+    XY_Write(fd,&tag,1);
+    return out_ubyte(fd,data);
+}
+static int
+xl_uint16(int fd,unsigned short data ) {
+    unsigned char const tag=0xc1;
+    XY_Write(fd,&tag,1);
+    return out_uint16(fd,data);
+}
+#if 0
+static int
+xl_uint32(int fd,unsigned int data ) {
+    unsigned char const c=0xc2;
+    XY_Write(fd,&c,1);
+    return out_uint32(fd,data);
+}
+static int
+xl_sint16(int fd,signed short data ) {
+    unsigned char const c=0xc3;
+    XY_Write(fd,&c,1);
+    return out_sint16(fd,data);
+}
+static int
+xl_sint32(int fd,signed int data ) {
+    unsigned char const c=0xc4;
+    XY_Write(fd,&c,1);
+    return out_sint32(fd,data);
+}
+#endif
+
+
+
+static int
+xl_ubyte_array(int                   const fd,
+               const unsigned char * const data,
+               int                   const len) {
+
+    unsigned int i;
+    unsigned char head[4];
+    
+    head[0] = 0xc8;
+    head[1] = 0xc1;
+    head[2] = len&0xff;
+    head[3] = (len>>8)&0xff;
+
+    XY_Write(fd, head, 4);
+
+    for (i = 0; i < len; ++i)
+        out_ubyte(fd, data[i]);
+
+    return 0;
+}
+
+
+
+#if 0
+static int
+xl_uint16_array(int fd,unsigned short *data,int len) {
+    int i;
+    unsigned char head[4];
+    head[0]=0xc9;head[1]=0xc1;head[2]=len&0xff;head[3]=(len>>8)&0xff;
+    XY_Write(fd,head,4);
+    for(i=0;i<len;i++) {
+        out_uint16(fd,data[i]);
+    }
+    return 0;
+}
+static int
+xl_uint32_array(int fd,unsigned int *data,int len) {
+    int i;
+    unsigned char head[4];
+    head[0]=0xca;head[1]=0xc1;head[2]=len&0xff;head[3]=(len>>8)&0xff;
+    XY_Write(fd,head,4);
+    for(i=0;i<len;i++) {
+        out_uint32(fd,data[i]);
+    }
+    return 0;
+}
+static int
+xl_sint16_array(int fd,signed short *data,int len) {
+    int i;
+    unsigned char head[4];
+    head[0]=0xcb;head[1]=0xc1;head[2]=len&0xff;head[3]=(len>>8)&0xff;
+    XY_Write(fd,head,4);
+    for(i=0;i<len;i++) {
+        out_sint16(fd,data[i]);
+    }
+    return 0;
+}
+static int
+xl_sint32_array(int fd,signed int *data,int len) {
+    int i;
+    unsigned char head[4];
+    head[0]=0xcc;head[1]=0xc1;head[2]=len&0xff;head[3]=(len>>8)&0xff;
+    XY_Write(fd,head,4);
+    for(i=0;i<len;i++) {
+        out_sint32(fd,data[i]);
+    }
+    return 0;
+}
+
+static int
+xl_ubyte_xy(int fd,unsigned char xdata,unsigned char ydata) {
+    unsigned char const tag=0xd0;
+    XY_Write(fd,&data,1);
+    out_ubyte(fd,ydata);
+    return out_ubyte(fd,xdata);
+}
+#endif
+static int
+xl_uint16_xy(int fd,unsigned short xdata,unsigned short ydata ) {
+    unsigned char const tag=0xd1;
+    XY_Write(fd,&tag,1);
+    out_uint16(fd,xdata);
+    return out_uint16(fd,ydata);
+}
+#if 0
+static int
+xl_uint32_xy(int fd,unsigned int xdata,unsigned int ydata ) {
+    unsigned char const tag=0xd2;
+    XY_Write(fd,&tag,1);
+    out_uint32(fd,xdata);
+    return out_uint32(fd,ydata);
+}
+#endif
+static int
+xl_sint16_xy(int fd,signed short xdata,signed short ydata ) {
+    unsigned char const tag=0xd3;
+    XY_Write(fd,&tag,1);
+    out_sint16(fd,xdata);
+    return out_sint16(fd,ydata);
+}
+
+#if 0
+static int
+xl_sint32_xy(int fd,signed int xdata,signed int ydata ) {
+    unsigned char const tag=0xd4;
+    XY_Write(fd,&tag,1);
+    out_sint32(fd,xdata);
+    return out_sint32(fd,ydata);
+}
+#endif
+
+static int
+xl_attr_ubyte(int fd,enum Attribute const data) {
+    unsigned char const tag=0xf8;
+    XY_Write(fd,&tag,1);
+    return out_ubyte(fd,data);
+}
+#if 0
+static int
+xl_attr_uint16(int fd,enum Attribute const data ) {
+    unsigned char const tag=0xf9;
+    XY_Write(fd,&tag,1);
+    return out_uint16(fd,data);
+}
+#endif
+static int
+xl_dataLength(int fd,unsigned int dataLength ) {
+    unsigned char const tag=0xfa;
+    XY_Write(fd,&tag,1);
+    return out_uint32(fd,dataLength);
+}
+
+#if 0
+static int
+xl_dataLengthbytes(int fd,unsigned char dataLengthBytes) {
+    unsigned char const tag=0xfb;
+    XY_Write(fd,&tag,1);
+    return out_ubyte(fd,dataLengthBytes);
+}
+#endif 
+
+
+
+static void
+copyFile(const char * const sourceFileName,
+         int          const destFd) {
+
+    FILE * sourceFileP;
+
+    sourceFileP = pm_openr(sourceFileName);
+
+    while (!feof(sourceFileP)) {
+        char buffer[1024];
+        size_t bytesRead;
+        size_t totalBytesWritten;
+
+        bytesRead = fread(buffer, 1, sizeof(buffer), sourceFileP);
+
+        if (ferror(sourceFileP))
+            pm_error("Read from file failed.  errno=%d (%s)",
+                     errno, strerror(errno));
+        
+        totalBytesWritten = 0;
+        
+        while (totalBytesWritten < bytesRead) {
+            ssize_t rc;
+
+            rc = write(destFd, buffer, bytesRead);
+
+            if (rc < 0)
+                pm_error("Write to file failed. errno=%d (%s)",
+                         errno, strerror(errno));
+            else
+                totalBytesWritten += rc;
+        }
+    }
+    pm_close(sourceFileP);
+}
+
+
+
+static void
+jobHead(int          const outFd,
+        bool         const renderGray,
+        const char * const userJobSetupFileName) {
+/*----------------------------------------------------------------------------
+   Start a PJL job.
+
+   Switch printer to PCL-XL mode.  This is called "entering a printer
+   language" in PCL terms.  In particular, we enter the PCL-XL language,
+   as opposed to e.g. Postscript.
+-----------------------------------------------------------------------------*/
+    /* Reset */
+    XY_Puts(outFd,"\033%-12345X");  
+
+    if (userJobSetupFileName)
+        copyFile(userJobSetupFileName, outFd);
+
+    if (renderGray)
+        XY_Puts(outFd,"@PJL SET RENDERMODE=GRAYSCALE\n");  
+
+    XY_Puts(outFd,"@PJL ENTER LANGUAGE=PCLXL\n");  
+    XY_Puts(outFd,") HP-PCL XL;1;1;Generated by Netpbm Pnmtopclxl\n");  
+}
+
+
+
+static void
+jobEnd(int const outFd) {
+/*----------------------------------------------------------------------------
+   End a PJL job.
+
+   Reset printer to quiescent mode.  Exit the printer language.
+-----------------------------------------------------------------------------*/
+    XY_Puts(outFd,"\033%-12345X");  
+}
+
+
+
+static void
+beginPage(int                 const outFd,
+          bool                const doDuplex,
+          enum DuplexPageMode const duplex,
+          bool                const doMediaSource,
+          int                 const mediaSource,
+          bool                const doMediaDestination,
+          int                 const mediaDestination,
+          enum MediaSize      const format) {
+/*----------------------------------------------------------------------------
+   Emit a BeginPage printer command.
+-----------------------------------------------------------------------------*/
+    if (doDuplex) {
+        xl_ubyte(outFd, duplex);  xl_attr_ubyte(outFd, aDuplexPageMode);
+    }
+
+    if (doMediaSource) {
+        /* if not included same as last time in same session is selected */
+        xl_ubyte(outFd, mediaSource);  xl_attr_ubyte(outFd, aMediaSource);
+    }
+
+    if (doMediaDestination) {
+        xl_ubyte(outFd, mediaDestination);  
+        xl_attr_ubyte(outFd, aMediaDestination);
+    }
+
+    xl_ubyte(outFd, ePortraitOrientation); xl_attr_ubyte(outFd, aOrientation);
+    xl_ubyte(outFd, format); xl_attr_ubyte(outFd, aMediaSize);
+
+    XL_Operator(outFd, oBeginPage);
+}
+
+
+
+static void
+openDataSource(int             const outFd,
+               enum DataOrg    const dataOrg,
+               enum DataSource const dataSource) {
+
+    xl_ubyte(outFd, dataOrg); xl_attr_ubyte(outFd,aDataOrg);
+    xl_ubyte(outFd, dataSource); xl_attr_ubyte(outFd,aSourceType);
+    XL_Operator(outFd,oOpenDataSource);
+}
+
+
+
+static void
+setColorSpace(int                   const outFd,
+              enum Colorspace       const colorSpace,
+              const unsigned char * const palette,
+              unsigned int          const paletteSize,
+              enum ColorDepth       const paletteDepth) {
+/*----------------------------------------------------------------------------
+   Emit printer control to set the color space.
+
+   'palette' == NULL means no palette (raster contains colors, not indexes
+   into a palette).
+
+   'paletteSize' is the number of bytes in the palette (undefined if
+   'palette' is NULL)
+
+   'paletteDepth' is the color depth of the entries in the palette.
+   e8Bit means the palette contains 8 bit values.
+
+   The palette is a "direct color" palette: A separate table for each
+   color component (i.e. one table for grayscale; three tables for
+   RGB).  Each table is indexed by a value from the raster and yields
+   a byte of color component value.
+
+   The palette has to be the right size to fit the number of color
+   components and raster color depth (bits per component in the raster).
+
+   E.g. with raster color depth of 1 bit (e1Bit) and RGB color (eRGB),
+   'paletteSize' would have to be 6 -- a table for each of R, G, and
+   B, of two elements each.
+
+   It is not clear from the documentation what the situation is when
+   paletteDepth is not e8Bit.  Is each palette entry still a byte and only
+   some of the byte gets used?  Or are there multiple entries per byte?
+-----------------------------------------------------------------------------*/
+    xl_ubyte(outFd, colorSpace); xl_attr_ubyte(outFd, aColorSpace);   
+    if (palette) {
+        xl_ubyte(outFd, paletteDepth); 
+        xl_attr_ubyte(outFd, aPaletteDepth);   
+        xl_ubyte_array(outFd, palette, paletteSize); 
+        xl_attr_ubyte(outFd, aPaletteData);
+    }
+    XL_Operator(outFd, oSetColorSpace);
+}
+
+
+
+static void
+positionCursor(int            const outFd,
+               bool           const center,
+               float          const xoffs,
+               float          const yoffs,
+               int            const imageWidth,
+               int            const imageHeight,
+               int            const dpi,
+               enum MediaSize const format) {
+/*----------------------------------------------------------------------------
+   Emit printer control to position the cursor to start the page.
+-----------------------------------------------------------------------------*/
+    float xpos, ypos;
+
+    if (center) {
+        float const width  = 1.0 * imageWidth/dpi;  
+        float const height = 1.0 * imageHeight/dpi;    
+        xpos = (PAPERWIDTH(format) - width)/2;
+        ypos = (PAPERHEIGHT(format) - height)/2;
+    } else {
+        xpos = xoffs;
+        ypos = yoffs;
+    }
+    /* cursor positioning */
+    xl_sint16_xy(outFd, xpos * dpi, ypos * dpi); xl_attr_ubyte(outFd, aPoint);
+    XL_Operator(outFd, oSetCursor);
+}
+
+
+
+static void
+convertAndWriteRleBlock(int                  const outFd,
+                        const pclGenerator * const pclGeneratorP,
+                        struct pam *         const pamP,
+                        int                  const firstLine,
+                        int                  const nlines,
+                        XY_rle *             const rle) {
+
+    unsigned char const pad[4] = {0,0,0,0};
+    unsigned int const paddedLinelen = ((pclGeneratorP->linelen+3)/4)*4;
+    int rlelen;
+    unsigned int line;
+    
+    XY_RLEreset(rle);
+
+    for (line = firstLine; line < firstLine + nlines; ++line) {
+        pclGeneratorP->getnextrow(pclGeneratorP, pamP);
+        XY_RLEput(rle, pclGeneratorP->data, pclGeneratorP->linelen);
+        XY_RLEput(rle, pad, paddedLinelen - pclGeneratorP->linelen);
+    }
+    rlelen = XY_RLEfinish(rle);
+    if (rlelen<0) 
+        pm_error("Error on Making rle");
+
+    xl_dataLength(outFd, rlelen); 
+    XY_Write(outFd, rle->fbuf, rlelen);
+}
+
+
+
+/*
+ * ------------------------------------------------------------
+ * XL_WriteImage
+ *  Write a PCL-XL image to the datastream 
+ * ------------------------------------------------------------
+ */
+static void 
+convertAndWriteImage(int                  const outFd,
+                     const pclGenerator * const pclGenP,
+                     struct pam *         const pamP) {
+
+    int blockStartLine;
+    XY_rle * rle;
+
+    xl_ubyte(outFd, eDirectPixel); xl_attr_ubyte(outFd, aColorMapping);
+    xl_ubyte(outFd, pclGenP->colorDepth); xl_attr_ubyte(outFd, aColorDepth);
+    xl_uint16(outFd, pclGenP->width); xl_attr_ubyte(outFd, aSourceWidth);  
+    xl_uint16(outFd, pclGenP->height); xl_attr_ubyte(outFd, aSourceHeight);    
+    xl_uint16_xy(outFd, pclGenP->width*1, pclGenP->height*1); 
+    xl_attr_ubyte(outFd, aDestinationSize);   
+    XL_Operator(outFd, oBeginImage);
+
+    rle = XY_RLEnew(pclGenP->linelen*20);
+    if (!rle) 
+        pm_error("Unable to allocate %d bytes for the RLE buffer",
+                 pclGenP->linelen * 20);
+
+    blockStartLine = 0;
+    while (blockStartLine < pclGenP->height) {
+        unsigned int const blockHeight =
+            MIN(20, pclGenP->height-blockStartLine);
+        xl_uint16(outFd, blockStartLine); xl_attr_ubyte(outFd, aStartLine); 
+        xl_uint16(outFd, blockHeight); xl_attr_ubyte(outFd, aBlockHeight);
+        xl_ubyte(outFd, eRLECompression); xl_attr_ubyte(outFd, aCompressMode);
+        /* In modern PCL-XL, we could use a PadBytesMultiple attribute
+           here to avoid having to pad the data to a multiple of 4
+           bytes.  But PCL-XL 1.1 didn't have PadBytesMultiple.
+           xl_ubyte(outFd, 1); xl_attr_ubyte(outFd, aPadBytesMultiple); 
+        */
+        XL_Operator(outFd, oReadImage);
+        convertAndWriteRleBlock(outFd, pclGenP, pamP,
+                                blockStartLine, blockHeight, rle);
+        blockStartLine += blockHeight;
+    }
+    XY_RLEdelete(rle);
+    XL_Operator(outFd, oEndImage);
+}
+
+
+
+static void
+endPage(int          const outFd,
+        bool         const doCopies,
+        unsigned int const copies) {
+/*----------------------------------------------------------------------------
+   Emit an EndPage printer command.
+-----------------------------------------------------------------------------*/
+    if (doCopies) {
+        /* wrong in example in PCL-XL manual. Type is uint16 ! */
+        xl_uint16(outFd, copies); xl_attr_ubyte(outFd, aPageCopies);
+    }
+    XL_Operator(outFd, oEndPage);
+}
+
+
+
+static void
+convertAndPrintPage(int                  const outFd,
+                    const pclGenerator * const pclGeneratorP,
+                    struct pam *         const pamP,
+                    enum MediaSize       const format,
+                    int                  const dpi,
+                    bool                 const center,
+                    float                const xoffs,
+                    float                const yoffs,
+                    bool                 const doDuplex,
+                    enum DuplexPageMode  const duplex,
+                    bool                 const doCopies,
+                    unsigned int         const copies,
+                    bool                 const doMediaSource,
+                    int                  const mediaSource,
+                    bool                 const doMediaDestination,
+                    int                  const mediaDestination) {
+
+    beginPage(outFd, doDuplex, duplex, doMediaSource, mediaSource,
+              doMediaDestination, mediaDestination, format);
+
+    /* Before Netpbm 10.27 (March 2005), we always set up a two-byte 8
+       bit deep palette: {0, 255}.  I don't know why, because this
+       works only for e1Bit color depth an eGray color space, and in
+       that case does the same thing as having no palette at all.  But
+       in other cases, it doesn't work.  E.g. with eRGB, e8Bit, we got
+       an IllegalArraySize error from the printer on the SetColorSpace
+       command.
+
+       So we don't use a palette at all now.  
+    */
+    setColorSpace(outFd, pclGeneratorP->colorSpace, NULL, 0, 0);
+
+    positionCursor(outFd, center, xoffs, yoffs, 
+                   pclGeneratorP->width, pclGeneratorP->height, dpi, format);
+
+    convertAndWriteImage(outFd, pclGeneratorP, pamP);
+
+    endPage(outFd, doCopies, copies);
+}
+
+
+
+static void
+beginSession(int              const outFd,
+             unsigned int     const xdpi,
+             unsigned int     const ydpi,
+             enum Measure     const measure,
+             bool             const noReporting,
+             enum ErrorReport const errorReport) {
+
+    xl_uint16_xy(outFd, xdpi, ydpi); xl_attr_ubyte(outFd, aUnitsPerMeasure); 
+    xl_ubyte(outFd, measure);  xl_attr_ubyte(outFd, aMeasure);
+    /* xl_ubyte(outFd,eNoReporting); xl_attr_ubyte(outFd,aErrorReport); */
+    xl_ubyte(outFd,errorReport); xl_attr_ubyte(outFd,aErrorReport);
+    XL_Operator(outFd,oBeginSession);
+}
+
+
+             
+static void 
+endSession(int outFd) {
+    XL_Operator(outFd,oEndSession);
+}
+
+
+
+static void
+pnmToPcllinePackbits(const pclGenerator * const pclGeneratorP,
+                     struct pam *         const pamP) {
+
+    tuple * tuplerow;
+    unsigned int pcl_cursor;
+    unsigned char accum;
+    unsigned char bitmask;
+    unsigned int col;
+        
+    tuplerow = pnm_allocpamrow(pamP);
+
+    pnm_readpamrow(pamP, tuplerow);
+
+    pcl_cursor = 0; bitmask = 0x80; accum = 0x00;
+    for (col = 0; col < pamP->width; ++col) {
+        if (tuplerow[col][0] == PAM_PBM_WHITE)
+            accum |= bitmask;
+        bitmask >>= 1;
+        if (bitmask == 0) {
+            pclGeneratorP->data[pcl_cursor++] = accum;
+            bitmask = 0x80; accum = 0x0;
+        } 
+    }
+    if (bitmask != 0x80)
+        pclGeneratorP->data[pcl_cursor++] = accum;
+
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+static void
+createPclGeneratorPackbits(struct pam *    const pamP,
+                           pclGenerator ** const pclGeneratorPP) {
+
+    /* Samples are black or white and packed 8 to a byte */
+
+    pclGenerator * pclGeneratorP;
+
+    MALLOCVAR_NOFAIL(pclGeneratorP);
+
+    pclGeneratorP->colorDepth = e1Bit;
+    pclGeneratorP->colorSpace = eGray;
+    pclGeneratorP->linelen = (pamP->width+7)/8;
+    pclGeneratorP->height = pamP->height;
+    pclGeneratorP->width = (pamP->width);
+
+    pclGeneratorP->data = malloc(pclGeneratorP->linelen);
+    
+    if (pclGeneratorP->data == NULL)
+        pm_error("Unable to allocate row buffer.");
+
+    pclGeneratorP->getnextrow = pnmToPcllinePackbits;
+
+    *pclGeneratorPP = pclGeneratorP;
+}
+
+
+
+static void
+pnmToPcllineWholebytes(const pclGenerator * const pclGeneratorP,
+                       struct pam *         const pamP) {
+
+    tuple * tuplerow;
+    unsigned int pcl_cursor;
+    unsigned int col;
+
+    tuplerow = pnm_allocpamrow(pamP);
+
+    pnm_readpamrow(pamP, tuplerow);
+
+    pcl_cursor = 0; /* initial value */
+    
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane) {
+            pclGeneratorP->data[pcl_cursor++] = 
+                pnm_scalesample(tuplerow[col][plane], pamP->maxval, 255);
+        }
+    }
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+static void
+createPclGeneratorWholebytes(struct pam *    const pamP,
+                             pclGenerator ** const pclGenPP) {
+    /* One sample per byte */
+
+    pclGenerator * pclGenP;
+
+    MALLOCVAR_NOFAIL(pclGenP);
+    
+    if (pamP->depth <  3)
+        pclGenP->colorSpace = eGray;
+    else
+        pclGenP->colorSpace = eRGB;
+
+    pclGenP->colorDepth = e8Bit;
+    pclGenP->height     = pamP->height;
+    pclGenP->width      = pamP->width;
+    pclGenP->linelen    = pamP->width * pamP->depth;
+
+    if (UINT_MAX / pamP->width < pamP->depth)
+        pm_error("Image to big to process");
+    else
+        pclGenP->data = malloc(pamP->width * pamP->depth);
+
+    if (pclGenP->data == NULL)
+        pm_error("Unable to allocate row buffer.");
+    
+    pclGenP->getnextrow = pnmToPcllineWholebytes;
+
+    *pclGenPP = pclGenP;
+}
+
+
+
+static void
+destroyPclGenerator(pclGenerator * const pclGenP) {
+
+    free(pclGenP->data);
+
+    free(pclGenP);
+}
+
+
+
+static void 
+createPclGenerator(struct pam *        const pamP,
+                   pclGenerator **     const pclGeneratorPP,
+                   bool                const colorok) {
+
+    if (pamP->depth > 1 && !colorok)
+        pm_message("WARNING: generating a color print stream because the "
+                   "input image is PPM.  "
+                   "To generate a black and white print stream, run the input "
+                   "through Ppmtopgm.  To suppress this warning, use the "
+                   "-colorok option.");
+
+    if (pamP->depth == 1 && pamP->maxval == 1) 
+        createPclGeneratorPackbits(pamP, pclGeneratorPP);
+    else 
+        createPclGeneratorWholebytes(pamP, pclGeneratorPP);
+}
+
+
+
+
+static void
+printPages(int                 const outFd,
+           InputSource *       const firstSourceP,
+           enum MediaSize      const format,
+           int                 const dpi,
+           bool                const center,
+           float               const xoffs,
+           float               const yoffs,
+           bool                const doDuplex,
+           enum DuplexPageMode const duplex,
+           bool                const doCopies,
+           unsigned int        const copies,
+           bool                const doMediaSource,
+           int                 const mediaSource,
+           bool                const doMediaDestination,
+           int                 const mediaDestination,
+           bool                const colorok) {
+/*----------------------------------------------------------------------------
+  Loop over all input files, and each file, all images.
+-----------------------------------------------------------------------------*/
+    InputSource * sourceP;
+    unsigned int sourceNum;
+
+    sourceP = firstSourceP;    
+
+    openDataSource(outFd, eBinaryLowByteFirst, eDefaultSource);
+
+    sourceNum = 0;   /* initial value */
+
+    while (sourceP) {
+        FILE * in_file;
+        struct pam pam;
+        bool eof;
+        unsigned int pageNum;
+
+        in_file = pm_openr(sourceP->name);
+
+        ++sourceNum;
+
+        pageNum = 0;  /* initial value */
+
+        eof = FALSE;
+        while(!eof) {
+            pnm_nextimage(in_file, &eof);
+            if (!eof) {
+                pclGenerator * pclGeneratorP;
+
+                ++pageNum;
+                pm_message("Processing File %u, Page %u", sourceNum, pageNum);
+
+                pnm_readpaminit(in_file, &pam, PAM_STRUCT_SIZE(tuple_type));
+                
+                createPclGenerator(&pam, &pclGeneratorP, colorok);
+                
+                convertAndPrintPage(
+                    outFd, pclGeneratorP, &pam,
+                    format, dpi, center, xoffs, yoffs, doDuplex, duplex,
+                    doCopies, copies, doMediaSource, mediaSource,
+                    doMediaDestination, mediaDestination);
+
+                destroyPclGenerator(pclGeneratorP);
+            }
+        }
+        pm_close(in_file);
+        sourceP = sourceP->next; 
+    }
+    XL_Operator(outFd, oCloseDataSource);
+}
+
+
+
+static void
+freeSource(InputSource * const firstSourceP) {
+    
+    InputSource * sourceP;
+
+    sourceP = firstSourceP;
+    while(sourceP) {
+        InputSource * const nextP = sourceP->next;
+        free(sourceP);
+        sourceP = nextP;
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    int const outFd = STDOUT_FILENO;
+
+    struct cmdlineInfo cmdline;
+    
+    /* In case you're wondering why we do direct file descriptor I/O
+       instead of stream (FILE *), it's because Jochen originally 
+       wrote this code for an embedded system with diet-libc.  Without
+       the stream library, the statically linked binary was only about
+       5K big.
+    */
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    jobHead(outFd, cmdline.rendergray, cmdline.jobsetup);
+
+    beginSession(outFd, cmdline.dpi, cmdline.dpi, eInch, 
+                 FALSE, eBackChAndErrPage);
+
+    printPages(outFd,cmdline.sourceP,
+               cmdline.format, cmdline.dpi, cmdline.center,
+               cmdline.xoffs, cmdline.yoffs,
+               cmdline.duplexSpec, cmdline.duplex,
+               cmdline.copiesSpec, cmdline.copies,
+               cmdline.feederSpec, cmdline.feeder,
+               cmdline.outtraySpec, cmdline.outtray,
+               cmdline.colorok
+        );
+    endSession(outFd);
+
+    jobEnd(outFd);
+
+    freeSource(cmdline.sourceP);
+
+    return 0;
+}
diff --git a/converter/other/pnmtoplainpnm b/converter/other/pnmtoplainpnm
new file mode 100755
index 00000000..87c58597
--- /dev/null
+++ b/converter/other/pnmtoplainpnm
@@ -0,0 +1,3 @@
+#! /bin/sh
+
+pnmtopnm -plain $@
diff --git a/converter/other/pnmtopng.README b/converter/other/pnmtopng.README
new file mode 100644
index 00000000..bfa524dc
--- /dev/null
+++ b/converter/other/pnmtopng.README
@@ -0,0 +1,101 @@
+Pnmtopng and Pngtopnm are based on programs of the same name in the 
+Pnmtopng package owned by Alexander Lehmann and Willem Van Schaik,
+available at http://www.libpng.org/pub/png/src on 2001.07.14.
+
+I added it to and adapted it to Netpbm on 2000.03.02 to make it more
+easily available to people.  I applied a patch on 2000.06.03 to bring
+it up the 2.37.4 release of that package.  I updated it again on
+2001.07.14 to bring it up to Release 2.37.5.  There is no process in
+place to bring improvements to the base package into the Netpbm
+version, but there hasn't been a lot of update activity anyway.
+
+Attached below is the file README from Release 2.37.5 of the base
+package.
+
+Here are the differences between the base and the Netpbm version:
+
+  I added an "unsigned" to make formal and actual arguments to png_sig_cmp()
+  match and quiet a compiler warning.
+
+  I fixed an "include" statement so the dependencies work out right.
+
+  I removed the BIGGRAYS stuff, which became obsolete in Netpbm 9.0
+
+  I replaced a PPM_MAXVAL with PPM_OVERALLMAXAL to handle the new 16 bits
+  formats.
+
+  macro VERSION is defined directly in pngtopnm.c and pnmtopng.c instead
+  of being included via file version.h.
+
+  Pnmtopng, since June 2001, reads one row at a time instead of holding 
+  the entire image in memory.  That makes it work with large bitmaps
+  where it would otherwise run out of memory.  It also works faster with
+  bitmaps since a bit takes up only a bit of memory in a cached input 
+  file, but 96 bits of memory after reading it into a Netpbm data 
+  structure.
+
+  The base Pnmtopng ignores -transparent if it specifies a color that
+  isn't in the image.  Netpbm's Pnmtopng selects a nearby color that _is_
+  in the image, which is what base Pnmtopng did before October 2000.
+  Netpbm's Pnmtopng lets you put an '=' sign before the color to specify
+  that you don't want a nearby color to be chosen, i.e. you want the
+  base Pnmtopng function.  This is consistent with Pnmtogif.
+
+There were some other changes necessary before Netpbm 9.0, but the change
+of the xelval type from 1 byte to 4 made them unnecessary.
+
+
+** PNMTOPNG / PNGTOPNM
+** version 2.37.5 - 24 October 2000
+
+[This is a semi-official bug-fix and enhancement release; I sort of took over
+ maintenance of this package while Willem was on an extended bike trip, and
+ for now I'm continuing with periodic, small updates.  Version 2.37 (March
+ 1998) was never publicly released, partly because Willem had hoped to quiet
+ gcc's "<var> might be clobbered by `longjmp'" warnings.  Those are fixed in
+ 2.37.2; under Solaris, they resulted in stack corruption even when there was
+ no error in the image files or libraries.  Version 2.37.3 fixes a minor bug
+ w.r.t. error exits and generally does cleaner error exits (close files, etc.)
+ Version 2.37.4 fixes a bug that caused 16-shade grayscale images to be written
+ as 8-bit grayscale instead of (smaller) 4-bit colormapped images (bug report,
+ analysis and fix by Rafal Rzeczkowski), and it supports the new/upcoming
+ pbmplus release.  Version 2.37.5 fixes a bug in -transparent handling (pnmtopng
+ no longer chooses an approximate color if the specified one isn't present) and
+ quiets a gcc warning in the non-16-bit version.
+ --Greg Roelofs]
+
+The utilities pnmtopng and pngtopnm are based on other pbm tools and require
+the libraries included in the pbmplus/netpbm package. Also required are the
+png library and the zlib compression library.
+
+These can be found at:
+	ftp://swrinde.nde.swri.edu/pub/png/src/libpng-*
+	ftp://swrinde.nde.swri.edu/pub/png/src/zlib-*
+	ftp://ftp.x.org/contrib/utilities/netpbm-1mar1994*
+or see
+	http://www.libpng.org/pub/png/apps/pnmtopng.html
+	http://netpbm.sourceforge.net/
+	http://www.acme.com/software/pbmplus/		[update coming soon?]
+
+To compile and install a makefile is provided. Do check the directories
+where you have put the required libraries. Then either accommodate the 
+makefile or make links from generic names (e.g., zlib) to version-specific
+directories (e.g., zlib-1.1.3), which is the recommended way.
+
+For testing purposes, have a look at the test-set PngSuite.tar.gz, which
+contains a small test-image for every PNG color type and for most PNG chunk
+types. It can be found at:
+	http://www.schaik.com/pngsuite/pngsuite.html
+	ftp://swrinde.nde.swri.edu/pub/png/images/suite/
+
+Other web pages with PNG images are at:
+	http://www.libpng.org/pub/png/png-textures.html
+	http://www.libpng.org/pub/png/pngs-img.html
+	http://www.libpng.org/pub/png/pngpic2.html
+	http://www.libpng.org/pub/png/colorcube/
+	http://www.libpng.org/pub/png/pngmisc.html#images
+
+------
+Alexander Lehmann <lehmann@usa.net>
+Willem van Schaik <willem@schaik.com>
+Greg Roelofs <newt@pobox.com>
diff --git a/converter/other/pnmtopng.c b/converter/other/pnmtopng.c
new file mode 100644
index 00000000..9287d0ee
--- /dev/null
+++ b/converter/other/pnmtopng.c
@@ -0,0 +1,2745 @@
+/*
+** pnmtopng.c -
+** read a portable anymap and produce a Portable Network Graphics file
+**
+** derived from pnmtorast.c (c) 1990,1991 by Jef Poskanzer and some
+** parts derived from ppmtogif.c by Marcel Wijkstra <wijkstra@fwi.uva.nl>
+**
+** Copyright (C) 1995-1998 by Alexander Lehmann <alex@hal.rhein-main.de>
+**                        and Willem van Schaik <willem@schaik.com>
+** Copyright (C) 1999,2001 by Greg Roelofs <newt@pobox.com>
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* This Netpbm version of Pnmtopng was derived from the independently
+   distributed program of the same name, Version 2.37.6 (21 July 2001).
+*/
+
+/* A performance note: This program reads one row at a time because
+   the whole image won't fit in memory always.  When you realize that
+   in a Netpbm xel array a one bit pixel can take 96 bits of memory,
+   it's easy to see that an ordinary fax could deplete your virtual
+   memory and even if it didn't, it might deplete your real memory and
+   iterating through the array would cause thrashing.  This program
+   iterates through the image multiple times.  
+
+   So instead, we read the image into memory one row at a time, into a
+   single row buffer.  We use Netpbm's pm_openr_seekable() facility to
+   access the file.  That facility copies the file into a temporary
+   file if it isn't seekable, so we always end up with a file that we
+   can rewind and reread multiple times.
+
+   This shouldn't cause I/O delays because the entire image ought to fit
+   in the system's I/O cache (remember that the file is a lot smaller than
+   the xel array you'd get by doing a pnm_readpnm() of it).
+
+   However, it does introduce some delay because of all the system calls 
+   required to read the file.  A future enhancement might read the entire
+   file into an xel array in some cases, and read one row at a time in 
+   others, depending on the needs of the particular use.
+
+   We do still read the entire alpha mask (if there is one) into a
+   'gray' array, rather than access it one row at a time.  
+
+   Before May 2001, we did in fact read the whole image into an xel array,
+   and we got complaints.  Before April 2000, it wasn't as big a problem
+   because xels were only 24 bits.  Now they're 96.
+*/
+   
+#define GRR_GRAY_PALETTE_FIX
+
+#ifndef PNMTOPNG_WARNING_LEVEL
+#  define PNMTOPNG_WARNING_LEVEL 0   /* use 0 for backward compatibility, */
+#endif                               /*  2 for warnings (1 == error) */
+
+#include <assert.h>
+#include <string.h> /* strcat() */
+#include <limits.h>
+#include <png.h>    /* includes zlib.h and setjmp.h */
+#include "pnm.h"
+#include "pngtxt.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "version.h"
+
+struct zlibCompression {
+    /* These are parameters that describe a form of zlib compression.
+       Values have the same meaning as the similarly named arguments to
+       zlib's deflateInit2().  See zlib.h.
+    */
+    unsigned int levelSpec;
+    unsigned int level;
+    unsigned int memLevelSpec;
+    unsigned int mem_level;
+    unsigned int strategySpec;
+    unsigned int strategy;
+    unsigned int windowBitsSpec;
+    unsigned int window_bits;
+    unsigned int methodSpec;
+    unsigned int method;
+    unsigned int bufferSizeSpec;
+    unsigned int buffer_size;
+};
+
+struct chroma {
+    float wx;
+    float wy;
+    float rx;
+    float ry;
+    float gx;
+    float gy;
+    float bx;
+    float by;
+};
+
+struct phys {
+    int x;
+    int y;
+    int unit;
+};
+
+typedef struct cahitem {
+    xel color;
+    gray alpha;
+    int value;
+    struct cahitem * next;
+} cahitem;
+
+typedef cahitem ** coloralphahash_table;
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *  inputFilename;  /* '-' if stdin */
+    const char *  alpha;
+    unsigned int  verbose;
+    unsigned int  downscale;
+    unsigned int  interlace;
+    const char *  transparent;  /* NULL if none */
+    const char *  background;   /* NULL if none */
+    unsigned int  gammaSpec;
+    float         gamma;        /* Meaningless if !gammaSpec */
+    unsigned int  hist;
+    unsigned int  rgbSpec;
+    struct chroma rgb;          /* Meaningless if !rgbSpec */
+    unsigned int  sizeSpec;
+    struct phys   size;         /* Meaningless if !sizeSpec */
+    const char *  text;         /* NULL if none */
+    const char *  ztxt;         /* NULL if none */
+    unsigned int  modtimeSpec;
+    time_t        modtime;      /* Meaningless if !modtimeSpec */
+    const char *  palette;      /* NULL if none */
+    int           filterSet;
+    unsigned int  force;
+    unsigned int  libversion;
+    unsigned int  compressionSpec;
+    struct zlibCompression zlibCompression;
+};
+
+
+
+typedef struct _jmpbuf_wrapper {
+  jmp_buf jmpbuf;
+} jmpbuf_wrapper;
+
+#ifndef TRUE
+#  define TRUE 1
+#endif
+#ifndef FALSE
+#  define FALSE 0
+#endif
+#ifndef NONE
+#  define NONE 0
+#endif
+#define MAXCOLORS 256
+#define MAXPALETTEENTRIES 256
+
+/* PALETTEMAXVAL is the maxval used in a PNG palette */
+#define PALETTEMAXVAL 255
+
+#define PALETTEOPAQUE 255
+#define PALETTETRANSPARENT 0
+
+static bool verbose;
+
+static jmpbuf_wrapper pnmtopng_jmpbuf_struct;
+static int errorlevel;
+
+
+
+static void
+parseSizeOpt(const char *  const sizeOpt,
+             struct phys * const sizeP) {
+
+    int count;
+    
+    count = sscanf(sizeOpt, "%d %d %d", &sizeP->x, &sizeP->y, &sizeP->unit);
+
+    if (count != 3)
+        pm_error("Invalid syntax for the -size option value '%s'.  "
+                 "Should be 3 integers: x, y, and unit code", sizeOpt);
+}
+
+
+
+static void
+parseRgbOpt(const char *    const rgbOpt,
+            struct chroma * const rgbP) {
+
+    int count;
+    
+    count = sscanf(rgbOpt, "%f %f %f %f %f %f %f %f",
+                   &rgbP->wx, &rgbP->wy,
+                   &rgbP->rx, &rgbP->ry,
+                   &rgbP->gx, &rgbP->gy,
+                   &rgbP->bx, &rgbP->by);
+
+    if (count != 6)
+        pm_error("Invalid syntax for the -rgb option value '%s'.  "
+                 "Should be 6 floating point number: "
+                 "x and y for each of white, red, green, and blue",
+                 rgbOpt);
+}
+
+
+
+static void
+parseModtimeOpt(const char * const modtimeOpt,
+                time_t *     const modtimeP) {
+
+    struct tm brokenTime;
+    int year;
+    int month;
+    int count;
+
+    count = sscanf(modtimeOpt, "%d-%d-%d %d:%d:%d",
+                   &year,
+                   &month,
+                   &brokenTime.tm_mday,
+                   &brokenTime.tm_hour,
+                   &brokenTime.tm_min,
+                   &brokenTime.tm_sec);
+
+    if (count != 6)
+        pm_error("Invalid value for -modtime '%s'.   It should have "
+                 "the form [yy]yy-mm-dd hh:mm:ss.", modtimeOpt);
+    
+    if (year < 0)
+        pm_error("Year is negative in -modtime value '%s'", modtimeOpt);
+    if (year > 9999)
+        pm_error("Year is more than 4 digits in -modtime value '%s'",
+                 modtimeOpt);
+    if (month < 0)
+        pm_error("Month is negative in -modtime value '%s'", modtimeOpt);
+    if (month > 12)
+        pm_error("Month is >12 in -modtime value '%s'", modtimeOpt);
+    if (brokenTime.tm_mday < 0)
+        pm_error("Day of month is negative in -modtime value '%s'",
+                 modtimeOpt);
+    if (brokenTime.tm_mday > 31)
+        pm_error("Day of month is >31 in -modtime value '%s'", modtimeOpt);
+    if (brokenTime.tm_hour < 0)
+        pm_error("Hour is negative in -modtime value '%s'", modtimeOpt);
+    if (brokenTime.tm_hour > 23)
+        pm_error("Hour is >23 in -modtime value '%s'", modtimeOpt);
+    if (brokenTime.tm_min < 0)
+        pm_error("Minute is negative in -modtime value '%s'", modtimeOpt);
+    if (brokenTime.tm_min > 59)
+        pm_error("Minute is >59 in -modtime value '%s'", modtimeOpt);
+    if (brokenTime.tm_sec < 0)
+        pm_error("Second is negative in -modtime value '%s'", modtimeOpt);
+    if (brokenTime.tm_sec > 59)
+        pm_error("Second is >59 in -modtime value '%s'", modtimeOpt);
+
+    brokenTime.tm_mon = month - 1;
+    if (year >= 1900)
+        brokenTime.tm_year = year - 1900;
+    else
+        brokenTime.tm_year = year;
+
+    /* Note that mktime() considers brokeTime to be in local time.
+       This is what we want, since we got it from a user.  User should
+       set his local time zone to UTC if he wants absolute time.
+    */
+    *modtimeP = mktime(&brokenTime);
+}
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int alphaSpec, transparentSpec, backgroundSpec;
+    unsigned int textSpec, ztxtSpec, paletteSpec;
+    unsigned int filterSpec;
+
+    unsigned int nofilter, sub, up, avg, paeth, filter;
+    unsigned int chroma, phys, time;
+    const char * size;
+    const char * rgb;
+    const char * modtime;
+    const char * compMethod;
+    const char * compStrategy;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "alpha",            OPT_STRING,    &cmdlineP->alpha,
+            &alphaSpec,            0);
+    OPTENT3(0, "transparent",      OPT_STRING,    &cmdlineP->transparent,
+            &transparentSpec,      0);
+    OPTENT3(0, "background",       OPT_STRING,    &cmdlineP->background,
+            &backgroundSpec,       0);
+    OPTENT3(0, "rgb",              OPT_STRING,    &rgb,
+            &cmdlineP->rgbSpec,    0);
+    OPTENT3(0, "size",             OPT_STRING,    &size,
+            &cmdlineP->sizeSpec,   0);
+    OPTENT3(0, "text",             OPT_STRING,    &cmdlineP->text,
+            &textSpec,             0);
+    OPTENT3(0, "ztxt",             OPT_STRING,    &cmdlineP->ztxt,
+            &ztxtSpec,             0);
+    OPTENT3(0, "modtime",          OPT_STRING,    &modtime,
+            &cmdlineP->modtimeSpec,0);
+    OPTENT3(0, "palette",          OPT_STRING,    &cmdlineP->palette,
+            &paletteSpec,          0);
+    OPTENT3(0, "compression",      OPT_UINT,
+            &cmdlineP->zlibCompression.level,
+            &cmdlineP->zlibCompression.levelSpec,            0);
+    OPTENT3(0, "comp_mem_level",   OPT_UINT,
+            &cmdlineP->zlibCompression.mem_level,
+            &cmdlineP->zlibCompression.memLevelSpec,         0);
+    OPTENT3(0, "comp_strategy",    OPT_STRING,    &compStrategy,
+            &cmdlineP->zlibCompression.strategySpec,         0);
+    OPTENT3(0, "comp_window_bits", OPT_UINT,
+            &cmdlineP->zlibCompression.window_bits,
+            &cmdlineP->zlibCompression.windowBitsSpec,       0);
+    OPTENT3(0, "comp_method",      OPT_STRING,    &compMethod,
+            &cmdlineP->zlibCompression.methodSpec,           0);
+    OPTENT3(0, "comp_buffer_size", OPT_UINT,
+            &cmdlineP->zlibCompression.buffer_size,
+            &cmdlineP->zlibCompression.bufferSizeSpec,       0);
+    OPTENT3(0, "gamma",            OPT_FLOAT,     &cmdlineP->gamma,
+            &cmdlineP->gammaSpec,  0);
+    OPTENT3(0, "hist",             OPT_FLAG,      NULL,
+            &cmdlineP->hist,       0);
+    OPTENT3(0, "downscale",        OPT_FLAG,      NULL,
+            &cmdlineP->downscale,  0);
+    OPTENT3(0, "interlace",        OPT_FLAG,      NULL,
+            &cmdlineP->interlace,  0);
+    OPTENT3(0, "force",            OPT_FLAG,      NULL,
+            &cmdlineP->force,      0);
+    OPTENT3(0, "libversion",       OPT_FLAG,      NULL,
+            &cmdlineP->libversion, 0);
+    OPTENT3(0, "verbose",          OPT_FLAG,      NULL,
+            &cmdlineP->verbose,    0);
+    OPTENT3(0, "nofilter",         OPT_FLAG,      NULL,
+            &nofilter,             0);
+    OPTENT3(0, "sub",              OPT_FLAG,      NULL,
+            &sub,                  0);
+    OPTENT3(0, "up",               OPT_FLAG,      NULL,
+            &up,                   0);
+    OPTENT3(0, "avg",              OPT_FLAG,      NULL,
+            &avg,                  0);
+    OPTENT3(0, "paeth",            OPT_FLAG,      NULL,
+            &paeth,                0);
+    OPTENT3(0, "filter",           OPT_INT,       &filter,
+            &filterSpec,           0);
+    OPTENT3(0, "verbose",          OPT_FLAG,      NULL,
+            &cmdlineP->verbose,    0);
+    OPTENT3(0, "chroma",           OPT_FLAG,      NULL,
+            &chroma,               0);
+    OPTENT3(0, "phys",             OPT_FLAG,      NULL,
+            &phys,                 0);
+    OPTENT3(0, "time",             OPT_FLAG,      NULL,
+            &time,                 0);
+
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+
+    if (chroma)
+        pm_error("The -chroma option no longer exists.  Use -rgb instead.");
+    if (phys)
+        pm_error("The -phys option no longer exists.  Use -size instead.");
+    if (time)
+        pm_error("The -time option no longer exists.  Use -modtime instead.");
+
+    if (alphaSpec + transparentSpec > 1)
+        pm_error("You may not specify both -alpha and -transparent");
+    if (!alphaSpec)
+        cmdlineP->alpha = NULL;
+    if (!transparentSpec)
+        cmdlineP->transparent = NULL;
+    if (!backgroundSpec)
+        cmdlineP->background = NULL;
+    if (!textSpec)
+        cmdlineP->text = NULL;
+    if (!ztxtSpec)
+        cmdlineP->ztxt = NULL;
+    if (!paletteSpec)
+        cmdlineP->palette = NULL;
+    
+    if (filterSpec + nofilter + sub + up + avg + paeth > 1)
+        pm_error("You may specify at most one of "
+                 "-nofilter, -sub, -up, -avg, -paeth, and -filter");
+    
+    if (filterSpec) {
+        if (filter < 0 || filter > 4)
+            pm_error("-filter is obsolete.  Use -nofilter, -sub, -up, -avg, "
+                     "and -paeth options instead.");
+        else
+            switch (filter) {
+            case 0: cmdlineP->filterSet = PNG_FILTER_NONE;  break;
+            case 1: cmdlineP->filterSet = PNG_FILTER_SUB;   break;
+            case 2: cmdlineP->filterSet = PNG_FILTER_UP;    break;
+            case 3: cmdlineP->filterSet = PNG_FILTER_AVG;   break;
+            case 4: cmdlineP->filterSet = PNG_FILTER_PAETH; break;
+            }
+    } else {
+        if (nofilter)
+            cmdlineP->filterSet = PNG_FILTER_NONE;
+        else if (sub)
+            cmdlineP->filterSet = PNG_FILTER_SUB;
+        else if (up)
+            cmdlineP->filterSet = PNG_FILTER_UP;
+        else if (avg)
+            cmdlineP->filterSet = PNG_FILTER_AVG;
+        else if (paeth)
+            cmdlineP->filterSet = PNG_FILTER_PAETH;
+        else
+            cmdlineP->filterSet = PNG_FILTER_NONE;
+    }
+    
+    if (cmdlineP->sizeSpec)
+        parseSizeOpt(size, &cmdlineP->size);
+
+    if (cmdlineP->rgbSpec)
+        parseRgbOpt(rgb, &cmdlineP->rgb);
+    
+    if (cmdlineP->modtimeSpec)
+        parseModtimeOpt(modtime, &cmdlineP->modtime);
+
+    if (cmdlineP->zlibCompression.levelSpec &&
+        cmdlineP->zlibCompression.level > 9)
+        pm_error("-compression value must be from 0 (no compression) "
+                 "to 9 (maximum compression).  You specified %u",
+                 cmdlineP->zlibCompression.level);
+
+    if (cmdlineP->zlibCompression.memLevelSpec) {
+        if (cmdlineP->zlibCompression.mem_level  < 1 ||
+            cmdlineP->zlibCompression.mem_level > 9)
+        pm_error("-comp_mem_level value must be from 1 (minimum memory usage) "
+                 "to 9 (maximum memory usage).  You specified %u",
+                 cmdlineP->zlibCompression.mem_level);
+    }
+
+    if (cmdlineP->zlibCompression.methodSpec) {
+        if (STREQ(compMethod, "deflated"))
+            cmdlineP->zlibCompression.method = Z_DEFLATED;
+        else
+            pm_error("The only valid value for -method is 'deflated'.  "
+                     "You specified '%s'", compMethod);
+    }
+
+    if (cmdlineP->zlibCompression.strategySpec) {
+        if (STREQ(compStrategy, "huffman_only"))
+            cmdlineP->zlibCompression.strategy = Z_HUFFMAN_ONLY;
+        else if (STREQ(compStrategy, "filtered"))
+            cmdlineP->zlibCompression.strategy = Z_FILTERED;
+        else
+            pm_error("Valid values for -strategy are 'huffman_only' and "
+                     "filtered.  You specified '%s'", compStrategy);
+    }
+
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilename = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFilename = argv[1];
+    else
+        pm_error("Program takes at most one argument:  input file name");
+}
+
+
+
+static png_color_16
+xelToPngColor_16(xel const input, 
+                 xelval const maxval, 
+                 xelval const pngMaxval) {
+    png_color_16 retval;
+
+    xel scaled;
+    
+    PPM_DEPTH(scaled, input, maxval, pngMaxval);
+
+    retval.red   = PPM_GETR(scaled);
+    retval.green = PPM_GETG(scaled);
+    retval.blue  = PPM_GETB(scaled);
+    retval.gray  = PNM_GET1(scaled);
+
+    return retval;
+}
+
+
+
+static void
+closestColorInPalette(pixel          const targetColor, 
+                      pixel                palette_pnm[],
+                      unsigned int   const paletteSize,
+                      unsigned int * const bestIndexP,
+                      unsigned int * const bestMatchP) {
+    
+    unsigned int paletteIndex;
+    unsigned int bestIndex;
+    unsigned int bestMatch;
+
+    assert(paletteSize > 0);
+
+    bestMatch = UINT_MAX;
+    for (paletteIndex = 0; paletteIndex < paletteSize; ++paletteIndex) {
+        unsigned int const dist = 
+            PPM_DISTANCE(palette_pnm[paletteIndex], targetColor);
+
+        if (dist < bestMatch) {
+            bestMatch = dist;
+            bestIndex = paletteIndex;
+        }
+    }
+    if (bestIndexP != NULL)
+        *bestIndexP = bestIndex;
+    if (bestMatchP != NULL)
+        *bestMatchP = bestMatch;
+}
+
+
+
+/* We really ought to make this hash function actually depend upon
+   the "a" argument; we just don't know a decent prime number off-hand.
+*/
+#define HASH_SIZE 20023
+#define hashpixelalpha(p,a) ((((long) PPM_GETR(p) * 33023 + \
+                               (long) PPM_GETG(p) * 30013 + \
+                               (long) PPM_GETB(p) * 27011 ) \
+                              & 0x7fffffff ) % HASH_SIZE )
+
+static coloralphahash_table
+alloccoloralphahash(void)  {
+    coloralphahash_table caht;
+    int i;
+
+    MALLOCARRAY(caht,HASH_SIZE);
+    if (caht == NULL)
+        pm_error( "out of memory allocating hash table" );
+
+    for (i = 0; i < HASH_SIZE; ++i)
+        caht[i] = NULL;
+
+    return caht;
+}
+
+
+static void
+freecoloralphahash(coloralphahash_table const caht) {
+    int i;
+
+    for (i = 0; i < HASH_SIZE; ++i) {
+        cahitem * p;
+        cahitem * next;
+        for (p = caht[i]; p; p = next) {
+            next = p->next;
+            free(p);
+        }
+    }
+    free(caht);
+}
+
+
+
+static void
+addtocoloralphahash(coloralphahash_table const caht,
+                    pixel *              const colorP,
+                    gray *               const alphaP,
+                    int                  const value) {
+
+    int hash;
+    cahitem * itemP;
+
+    MALLOCVAR(itemP);
+    if (itemP == NULL)
+        pm_error("Out of memory building hash table");
+    hash = hashpixelalpha(*colorP, *alphaP);
+    itemP->color = *colorP;
+    itemP->alpha = *alphaP;
+    itemP->value = value;
+    itemP->next = caht[hash];
+    caht[hash] = itemP;
+}
+
+
+
+static int
+lookupColorAlpha(coloralphahash_table const caht,
+                 const pixel *        const colorP,
+                 const gray *         const alphaP) {
+
+    int hash;
+    cahitem * p;
+
+    hash = hashpixelalpha(*colorP, *alphaP);
+    for (p = caht[hash]; p; p = p->next)
+        if (PPM_EQUAL(p->color, *colorP) && p->alpha == *alphaP)
+            return p->value;
+
+    return -1;
+}
+
+
+
+static void
+pnmtopng_error_handler(png_structp     const png_ptr,
+                       png_const_charp const msg) {
+
+  jmpbuf_wrapper  *jmpbuf_ptr;
+
+  /* this function, aside from the extra step of retrieving the "error
+   * pointer" (below) and the fact that it exists within the application
+   * rather than within libpng, is essentially identical to libpng's
+   * default error handler.  The second point is critical:  since both
+   * setjmp() and longjmp() are called from the same code, they are
+   * guaranteed to have compatible notions of how big a jmp_buf is,
+   * regardless of whether _BSD_SOURCE or anything else has (or has not)
+   * been defined. */
+
+  fprintf(stderr, "pnmtopng:  fatal libpng error: %s\n", msg);
+  fflush(stderr);
+
+  jmpbuf_ptr = png_get_error_ptr(png_ptr);
+  if (jmpbuf_ptr == NULL) {         /* we are completely hosed now */
+    fprintf(stderr,
+      "pnmtopng:  EXTREMELY fatal error: jmpbuf unrecoverable; terminating.\n");
+    fflush(stderr);
+    exit(99);
+  }
+
+  longjmp(jmpbuf_ptr->jmpbuf, 1);
+}
+
+
+/* The following variables belong to getChv() and freeChv() */
+static bool getChv_computed = FALSE;
+static colorhist_vector getChv_chv;
+
+
+
+static void
+getChv(FILE *             const ifP, 
+       pm_filepos         const rasterPos,
+       int                const cols, 
+       int                const rows, 
+       xelval             const maxval,
+       int                const format, 
+       int                const maxColors, 
+       colorhist_vector * const chvP,
+       unsigned int *     const colorsP) {
+/*----------------------------------------------------------------------------
+   Return a list of all the colors in a libnetpbm image and the number of
+   times they occur.  The image is in the seekable file 'ifP', whose
+   raster starts at position 'rasterPos' of the file.  The image's properties
+   are 'cols', 'rows', 'maxval', and 'format'.
+
+   Return the number of colors as *colorsP.  Return the details of the 
+   colors in newly malloc'ed storage, and its address as *chvP.  If
+   there are more than 'maxColors' colors, though, just return NULL as
+   *chvP and leave *colorsP undefined.
+
+   Don't spend the time to read the file if this subroutine has been called
+   before.  In that case, just assume the inputs are all the same and return
+   the previously computed information.  Ick.
+
+   *chvP is in static program storage.
+-----------------------------------------------------------------------------*/
+    static unsigned int getChv_colors;
+
+    if (!getChv_computed) {
+        int colorCount;
+        if (verbose) 
+            pm_message ("Finding colors in input image...");
+
+        pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
+        getChv_chv = ppm_computecolorhist2(ifP, cols, rows, maxval, format, 
+                                           maxColors, &colorCount);
+        
+        getChv_colors = colorCount;
+
+        if (verbose) {
+            if (getChv_chv)
+                pm_message("%u colors found", getChv_colors);
+            else
+                pm_message("Too many colors (more than %u) found", maxColors);
+        }
+        getChv_computed = TRUE;
+    }
+    *chvP = getChv_chv;
+    *colorsP = getChv_colors;
+}
+
+
+
+static void freeChv(void) {
+
+    if (getChv_computed)
+        if (getChv_chv)
+            ppm_freecolorhist(getChv_chv);
+
+    getChv_computed = FALSE;
+}
+
+
+
+static bool
+pgmBitsAreRepeated(unsigned int const repeatedSize,
+                   FILE *       const ifP,
+                   pm_filepos   const rasterPos, 
+                   int          const cols,
+                   int          const rows,
+                   xelval       const maxval,
+                   int          const format) {
+/*----------------------------------------------------------------------------
+   Return TRUE iff all the samples in the image in file 'ifP',
+   described by 'cols', 'rows', 'maxval', and 'format', consist in the
+   rightmost 'repeatedSize' * 2 bits of two identical sets of
+   'repeatedSize' bits.
+
+   The file has arbitrary position, but the raster is at file position
+   'rasterPos'.
+
+   E.g. for repeatedSize = 2, a sample value of 0xaa would qualify.
+   So would 0x0a.
+
+   Leave the file positioned where we found it.
+-----------------------------------------------------------------------------*/
+    unsigned int const mask2 = (1 << repeatedSize*2) - 1;
+    unsigned int const mask1 = (1 << repeatedSize) - 1;
+
+    bool mayscale;
+    unsigned int row;
+    xel * xelrow;
+
+    xelrow = pnm_allocrow(cols);
+    
+    pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
+
+    mayscale = TRUE;  /* initial assumption */
+
+    for (row = 0; row < rows && mayscale; ++row) {
+        unsigned int col;
+        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+        for (col = 0; col < cols && mayscale; ++col) {
+            xelval const testbits2 = PNM_GET1(xelrow[col]) & mask2;
+                /* The bits of interest in the sample */
+            xelval const testbits1 = testbits2 & mask1;
+                /* The lower half of the bits of interest in the sample */
+            if (((testbits1 << repeatedSize) | testbits1) != testbits2)
+                mayscale = FALSE;
+        }
+    }
+    pnm_freerow(xelrow);
+
+    return mayscale;
+}
+
+
+
+static void
+meaningful_bits_pgm(FILE *         const ifP, 
+                    pm_filepos     const rasterPos, 
+                    int            const cols,
+                    int            const rows,
+                    xelval         const maxval,
+                    int            const format,
+                    unsigned int * const retvalP) {
+/*----------------------------------------------------------------------------
+   In the PGM raster with maxval 'maxval' at file offset 'rasterPos'
+   in file 'ifp', the samples may be composed of groups of 1, 2, 4, or 8
+   bits repeated.  This would be the case if the image were converted
+   at some point from a 2 bits-per-pixel image to an 8-bits-per-pixel
+   image, for example.
+
+   If this is the case, we find out and find out how small these repeated
+   groups of bits are and return the number of bits.
+-----------------------------------------------------------------------------*/
+    unsigned int maxMeaningfulBits;
+        /* progressive estimate of the maximum number of meaningful
+           (nonrepeated) bits in the samples.
+        */
+
+    maxMeaningfulBits = pm_maxvaltobits(maxval);  /* initial value */
+
+    if (maxval == 0xffff || maxval == 0xff || maxval == 0xf || maxval == 0x3) {
+        if (maxMeaningfulBits == 16) {
+            if (pgmBitsAreRepeated(8,
+                                   ifP, rasterPos, cols, rows, maxval, format))
+                maxMeaningfulBits = 8;
+        }
+        if (maxMeaningfulBits == 8) {
+            if (pgmBitsAreRepeated(4,
+                                   ifP, rasterPos, cols, rows, maxval, format))
+                maxMeaningfulBits = 4;
+        }
+        if (maxMeaningfulBits == 4) {
+            if (pgmBitsAreRepeated(2,
+                                   ifP, rasterPos, cols, rows, maxval, format))
+                maxMeaningfulBits = 2;
+        }
+        if (maxMeaningfulBits == 2) {
+            if (pgmBitsAreRepeated(1,
+                                   ifP, rasterPos, cols, rows, maxval, format))
+                maxMeaningfulBits = 1;
+        }
+    }
+    *retvalP = maxMeaningfulBits;
+}
+
+
+
+static void
+meaningful_bits_ppm(FILE *         const ifp, 
+                    pm_filepos     const rasterPos, 
+                    int            const cols,
+                    int            const rows,
+                    xelval         const maxval,
+                    int            const format,
+                    unsigned int * const retvalP) {
+/*----------------------------------------------------------------------------
+   In the PPM raster with maxval 'maxval' at file offset 'rasterPos'
+   in file 'ifp', the samples may be composed of groups of 8
+   bits repeated twice.  This would be the case if the image were converted
+   at some point from a 8 bits-per-pixel image to an 16-bits-per-pixel
+   image, for example.
+
+   We return the smallest number of bits we can take from the right of
+   a sample without losing information (8 or all).
+-----------------------------------------------------------------------------*/
+    int mayscale;
+    unsigned int row;
+    xel * xelrow;
+    unsigned int maxMeaningfulBits;
+        /* progressive estimate of the maximum number of meaningful
+           (nonrepeated) bits in the samples.
+        */
+
+    xelrow = pnm_allocrow(cols);
+
+    maxMeaningfulBits = pm_maxvaltobits(maxval);
+
+    if (maxval == 65535) {
+        mayscale = TRUE;   /* initial assumption */
+        pm_seek2(ifp, &rasterPos, sizeof(rasterPos));
+        for (row = 0; row < rows && mayscale; ++row) {
+            unsigned int col;
+            pnm_readpnmrow(ifp, xelrow, cols, maxval, format);
+            for (col = 0; col < cols && mayscale; ++col) {
+                xel const p = xelrow[col];
+                if ((PPM_GETR(p) & 0xff) * 0x101 != PPM_GETR(p) ||
+                    (PPM_GETG(p) & 0xff) * 0x101 != PPM_GETG(p) ||
+                    (PPM_GETB(p) & 0xff) * 0x101 != PPM_GETB(p))
+                    mayscale = FALSE;
+            }
+        }
+        if (mayscale)
+            maxMeaningfulBits = 8;
+    }
+    pnm_freerow(xelrow);
+
+    *retvalP = maxMeaningfulBits;
+}
+
+
+
+static void
+tryTransparentColor(FILE *     const ifp, 
+                    pm_filepos const rasterPos, 
+                    int        const cols, 
+                    int        const rows, 
+                    xelval     const maxval,
+                    int        const format, 
+                    gray **    const alphaMask,
+                    gray       const alphaMaxval,
+                    pixel      const transcolor,
+                    bool *     const singleColorIsTransP) {
+
+    int const pnm_type = PNM_FORMAT_TYPE(format);
+
+    xel * xelrow;
+    bool singleColorIsTrans;
+        /* So far, it looks like a single color is uniquely transparent */
+    int row;
+
+    xelrow = pnm_allocrow(cols);
+
+    pm_seek2(ifp, &rasterPos, sizeof(rasterPos));
+
+    singleColorIsTrans = TRUE;  /* initial assumption */
+        
+    for (row = 0; row < rows && singleColorIsTrans; ++row) {
+        int col;
+        pnm_readpnmrow(ifp, xelrow, cols, maxval, format);
+        for (col = 0 ; col < cols && singleColorIsTrans; ++col) {
+            if (alphaMask[row][col] == 0) { /* transparent */
+                /* If we have a second transparent color, we're
+                   disqualified
+                */
+                if (pnm_type == PPM_TYPE) {
+                    if (!PPM_EQUAL(xelrow[col], transcolor))
+                        singleColorIsTrans = FALSE;
+                } else {
+                    if (PNM_GET1(xelrow[col]) != PNM_GET1(transcolor))
+                        singleColorIsTrans = FALSE;
+                }
+            } else if (alphaMask[row][col] != alphaMaxval) {
+                /* Here's an area of the mask that is translucent.  That
+                   disqualified us.
+                */
+                singleColorIsTrans = FALSE;
+            } else {
+                /* Here's an area of the mask that is opaque.  If it's
+                   the same color as our candidate transparent color,
+                   that disqualifies us.
+                */
+                if (pnm_type == PPM_TYPE) {
+                    if (PPM_EQUAL(xelrow[col], transcolor))
+                        singleColorIsTrans = FALSE;
+                } else {
+                    if (PNM_GET1(xelrow[col]) == PNM_GET1(transcolor))
+                        singleColorIsTrans = FALSE;
+                }
+            }
+        }
+    }  
+    pnm_freerow(xelrow);
+}
+
+
+
+static void
+analyzeAlpha(FILE *     const ifp, 
+             pm_filepos const rasterPos, 
+             int        const cols, 
+             int        const rows, 
+             xelval     const maxval,
+             int        const format, 
+             gray **    const alphaMask,
+             gray       const alphaMaxval,
+             bool *     const allOpaqueP,
+             bool *     const singleColorIsTransP, 
+             pixel*     const alphaTranscolorP) {
+/*----------------------------------------------------------------------------
+  Get information about the alpha mask, in combination with the masked
+  image, that Caller can use to choose the most efficient way to
+  represent the information in the alpha mask in a PNG.  Simply
+  putting the alpha mask in the PNG is a last resort.  But if the mask
+  says all opaque, we can simply omit any mention of transparency
+  instead -- default is opaque.  And if the mask makes all the pixels
+  of a certain color fully transparent and every other pixel opaque,
+  we can simply identify that color in the PNG.
+
+  We have to do this before any scaling occurs, since alpha is only
+  possible with 8 and 16-bit.
+-----------------------------------------------------------------------------*/
+    xel * xelrow;
+    bool foundTransparentPixel;
+        /* We found a pixel in the image where the alpha mask says it is
+           transparent.
+        */
+    pixel transcolor;
+        /* Color of the transparent pixel mentioned above. */
+    
+    xelrow = pnm_allocrow(cols);
+
+    {
+        int row;
+        /* Find a candidate transparent color -- the color of any pixel in the
+           image that the alpha mask says should be transparent.
+        */
+        foundTransparentPixel = FALSE;  /* initial assumption */
+        pm_seek2(ifp, &rasterPos, sizeof(rasterPos));
+        for (row = 0 ; row < rows && !foundTransparentPixel ; ++row) {
+            int col;
+            pnm_readpnmrow(ifp, xelrow, cols, maxval, format);
+            for (col = 0; col < cols && !foundTransparentPixel; ++col) {
+                if (alphaMask[row][col] == 0) {
+                    foundTransparentPixel = TRUE;
+                    transcolor = xeltopixel(xelrow[col]);
+                }
+            }
+        }
+    }
+
+    pnm_freerow(xelrow);
+
+    if (foundTransparentPixel) {
+        *allOpaqueP = FALSE;
+        tryTransparentColor(ifp, rasterPos, cols, rows, maxval, format,
+                            alphaMask, alphaMaxval, transcolor,
+                            singleColorIsTransP);
+        *alphaTranscolorP = transcolor;
+    } else {
+        *allOpaqueP   = TRUE;
+        *singleColorIsTransP = FALSE;
+    }
+}
+
+
+
+static void
+findRedundantBits(FILE *         const ifp, 
+                  int            const rasterPos, 
+                  int            const cols,
+                  int            const rows,
+                  xelval         const maxval,
+                  int            const format,
+                  bool           const alpha,
+                  bool           const force,
+                  unsigned int * const meaningfulBitsP) {
+/*----------------------------------------------------------------------------
+   Find out if we can use just a subset of the bits from each input
+   sample.  Often, people create an image with e.g. 8 bit samples from
+   one that has e.g. only 4 bit samples by scaling by 256/16, which is
+   the same as repeating the bits.  E.g.  1011 becomes 10111011.  We
+   detect this case.  We return as *meaningfulBitsP the minimum number
+   of bits, starting from the least significant end, that contain
+   original information.
+-----------------------------------------------------------------------------*/
+  if (!alpha && PNM_FORMAT_TYPE(format) == PGM_TYPE && !force) 
+      meaningful_bits_pgm(ifp, rasterPos, cols, rows, maxval, format,
+                          meaningfulBitsP);
+  else if (PNM_FORMAT_TYPE(format) == PPM_TYPE && !force)
+      meaningful_bits_ppm(ifp, rasterPos, cols, rows, maxval, format,
+                          meaningfulBitsP);
+  else 
+      *meaningfulBitsP = pm_maxvaltobits(maxval);
+
+  if (verbose && *meaningfulBitsP != pm_maxvaltobits(maxval))
+      pm_message("Using only %d rightmost bits of input samples.  The "
+                 "rest are redundant.", *meaningfulBitsP);
+}
+
+
+
+static void
+readOrderedPalette(FILE *         const pfp,
+                   xel                  ordered_palette[], 
+                   unsigned int * const ordered_palette_size_p) {
+
+    xel ** xels;
+    int cols, rows;
+    xelval maxval;
+    int format;
+    
+    if (verbose)
+        pm_message("reading ordered palette (colormap)...");
+
+    xels = pnm_readpnm(pfp, &cols, &rows, &maxval, &format);
+    
+    if (PNM_FORMAT_TYPE(format) != PPM_TYPE) 
+        pm_error("ordered palette must be a PPM file, not type %d", format);
+
+    *ordered_palette_size_p = rows * cols;
+    if (*ordered_palette_size_p > MAXCOLORS) 
+        pm_error("ordered-palette image contains %d pixels.  Maximum is %d",
+                 *ordered_palette_size_p, MAXCOLORS);
+    if (verbose)
+        pm_message("%u colors found", *ordered_palette_size_p);
+
+    {
+        unsigned int j;
+        unsigned int row;
+        j = 0;  /* initial value */
+        for (row = 0; row < rows; ++row) {
+            int col;
+            for (col = 0; col < cols; ++col) 
+                ordered_palette[j++] = xels[row][col];
+        }
+    }
+    pnm_freearray(xels, rows);
+}        
+
+
+
+static void
+compute_nonalpha_palette(colorhist_vector const chv,
+                         int              const colors,
+                         pixval           const maxval,
+                         FILE *           const pfp,
+                         pixel                  palette_pnm[],
+                         unsigned int *   const paletteSizeP,
+                         gray                   trans_pnm[],
+                         unsigned int *   const transSizeP) {
+/*----------------------------------------------------------------------------
+   Compute the palette corresponding to the color set 'chv'
+   (consisting of 'colors' distinct colors) assuming a pure-color (no
+   transparency) palette.
+
+   If 'pfp' is non-null, assume it's a PPM file and read the palette
+   from that.  Make sure it contains the same colors as the palette
+   we computed ourself would have.  Caller supplied the file because he
+   wants the colors in a particular order in the palette.
+-----------------------------------------------------------------------------*/
+    unsigned int colorIndex;
+    
+    xel ordered_palette[MAXCOLORS];
+    unsigned int ordered_palette_size;
+
+    if (pfp) {
+        readOrderedPalette(pfp, ordered_palette, &ordered_palette_size);
+
+        if (colors != ordered_palette_size) 
+            pm_error("sizes of ordered palette (%d) "
+                     "and existing palette (%d) differ",
+                     ordered_palette_size, colors);
+        
+        /* Make sure the ordered palette contains all the colors in
+           the image 
+        */
+        for (colorIndex = 0; colorIndex < colors; colorIndex++) {
+            int j;
+            bool found;
+            
+            found = FALSE;
+            for (j = 0; j < ordered_palette_size && !found; ++j) {
+                if (PNM_EQUAL(ordered_palette[j], chv[colorIndex].color)) 
+                    found = TRUE;
+            }
+            if (!found) 
+                pm_error("failed to find color (%d, %d, %d), which is in the "
+                         "input image, in the ordered palette",
+                         PPM_GETR(chv[colorIndex].color),
+                         PPM_GETG(chv[colorIndex].color),
+                         PPM_GETB(chv[colorIndex].color));
+        }
+        /* OK, the ordered palette passes muster as a palette; go ahead
+           and return it as the palette.
+        */
+        for (colorIndex = 0; colorIndex < colors; ++colorIndex)
+            palette_pnm[colorIndex] = ordered_palette[colorIndex];
+    } else {
+        for (colorIndex = 0; colorIndex < colors; ++colorIndex) 
+            palette_pnm[colorIndex] = chv[colorIndex].color;
+    }
+    *paletteSizeP = colors;
+    *transSizeP = 0;
+}
+
+
+
+static void
+computeUnsortedAlphaPalette(FILE *           const ifP,
+                            int              const cols,
+                            int              const rows,
+                            xelval           const maxval,
+                            int              const format,
+                            pm_filepos       const rasterPos,
+                            gray **          const alpha_mask,
+                            unsigned int     const maxPaletteEntries,
+                            colorhist_vector const chv,
+                            int              const colors,
+                            gray *                 alphas_of_color[],
+                            unsigned int           alphas_first_index[],
+                            unsigned int           alphas_of_color_cnt[]) {
+/*----------------------------------------------------------------------------
+   Read the image at position 'rasterPos' in file *ifP, which is a PNM
+   described by 'cols', 'rows', 'maxval', and 'format'.
+
+   Using the alpha mask 'alpha_mask' and color map 'chv' (of size 'colors')
+   for the image, construct a palette of (color index, alpha) ordered pairs 
+   for the image, as follows.
+
+   The alpha/color palette is the set of all ordered pairs of
+   (color,alpha) in the PNG, including the background color.  The
+   actual palette is an array with up to 'maxPaletteEntries elements.  Each
+   array element contains a color index from the color palette and
+   an alpha value.  All the elements with the same color index are
+   contiguous.  alphas_first_index[x] is the index in the
+   alpha/color palette of the first element that has color index x.
+   alphas_of_color_cnt[x] is the number of elements that have color
+   index x.  alphas_of_color[x][y] is the yth alpha value that
+   appears with color index x (in order of appearance).
+   alpha_color_pair_count is the total number of elements, i.e. the
+   total number of combinations color and alpha.
+-----------------------------------------------------------------------------*/
+    colorhash_table cht;
+    int color_index;
+    int row;
+    xel * xelrow;
+
+    cht = ppm_colorhisttocolorhash (chv, colors);
+
+    for (color_index = 0 ; color_index < colors + 1 ; ++color_index) {
+        /* TODO: It sure would be nice if we didn't have to allocate
+           256 words here for what is normally only 0 or 1 different
+           alpha values!  Maybe we should do some sophisticated reallocation.
+        */
+        MALLOCARRAY(alphas_of_color[color_index], maxPaletteEntries);
+        if (alphas_of_color[color_index] == NULL)
+            pm_error ("out of memory allocating alpha/palette entries");
+        alphas_of_color_cnt[color_index] = 0;
+    }
+ 
+    pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
+
+    xelrow = pnm_allocrow(cols);
+
+    for (row = 0 ; row < rows ; ++row) {
+        int col;
+        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+        pnm_promoteformatrow(xelrow, cols, maxval, format, maxval, PPM_TYPE);
+        for (col = 0 ; col < cols ; ++col) {
+            int i;
+            int const color = ppm_lookupcolor(cht, &xelrow[col]);
+            for (i = 0 ; i < alphas_of_color_cnt[color] ; ++i) {
+                if (alpha_mask[row][col] == alphas_of_color[color][i])
+                    break;
+            }
+            if (i == alphas_of_color_cnt[color]) {
+                alphas_of_color[color][i] = alpha_mask[row][col];
+                alphas_of_color_cnt[color]++;
+            }
+        }
+    }
+    {
+        int i;
+        alphas_first_index[0] = 0;
+        for (i = 1 ; i < colors ; i++)
+            alphas_first_index[i] = alphas_first_index[i-1] +
+                alphas_of_color_cnt[i-1];
+    }
+    pnm_freerow(xelrow);
+    ppm_freecolorhash(cht);
+}
+
+
+
+static void
+sortAlphaPalette(gray *alphas_of_color[],
+                 unsigned int alphas_first_index[],
+                 unsigned int alphas_of_color_cnt[],
+                 unsigned int const colors,
+                 unsigned int mapping[],
+                 unsigned int * const transSizeP) {
+/*----------------------------------------------------------------------------
+   Remap the palette indices so opaque entries are last.
+
+   alphas_of_color[], alphas_first_index[], and alphas_of_color_cnt[]
+   describe an unsorted PNG (alpha/color) palette.  We generate
+   mapping[] such that mapping[x] is the index into the sorted PNG
+   palette of the alpha/color pair whose index is x in the unsorted
+   PNG palette.  This mapping sorts the palette so that opaque entries
+   are last.
+-----------------------------------------------------------------------------*/
+    unsigned int bot_idx;
+    unsigned int top_idx;
+    unsigned int colorIndex;
+    
+    /* We start one index at the bottom of the palette index range
+       and another at the top.  We run through the unsorted palette,
+       and when we see an opaque entry, we map it to the current top
+       cursor and bump it down.  When we see a non-opaque entry, we map 
+       it to the current bottom cursor and bump it up.  Because the input
+       and output palettes are the same size, the two cursors should meet
+       right when we process the last entry of the unsorted palette.
+    */    
+    bot_idx = 0;
+    top_idx = alphas_first_index[colors-1] + alphas_of_color_cnt[colors-1] - 1;
+    
+    for (colorIndex = 0;  colorIndex < colors;  ++colorIndex) {
+        unsigned int j;
+        for (j = 0; j < alphas_of_color_cnt[colorIndex]; ++j) {
+            unsigned int const paletteIndex = 
+                alphas_first_index[colorIndex] + j;
+            if (alphas_of_color[colorIndex][j] == PALETTEOPAQUE)
+                mapping[paletteIndex] = top_idx--;
+                else
+                    mapping[paletteIndex] = bot_idx++;
+        }
+    }
+    /* indices should have just crossed paths */
+    if (bot_idx != top_idx + 1) {
+        pm_error ("internal inconsistency: "
+                  "remapped bot_idx = %u, top_idx = %u",
+                  bot_idx, top_idx);
+    }
+    *transSizeP = bot_idx;
+}
+
+
+
+static void
+compute_alpha_palette(FILE *         const ifP, 
+                      int            const cols,
+                      int            const rows,
+                      xelval         const maxval,
+                      int            const format,
+                      pm_filepos     const rasterPos,
+                      gray **        const alpha_mask,
+                      pixel                palette_pnm[],
+                      gray                 trans_pnm[],
+                      unsigned int * const paletteSizeP,
+                      unsigned int * const transSizeP,
+                      bool *         const tooBigP) {
+/*----------------------------------------------------------------------------
+   Return the palette of color/alpha pairs for the image indicated by
+   'ifP', 'cols', 'rows', 'maxval', 'format', and 'rasterPos'.
+   alpha_mask[] is the Netpbm-style alpha mask for the image.
+
+   Return the palette as the arrays palette_pnm[] and trans_pnm[].
+   The ith entry in the palette is the combination of palette[i],
+   which defines the color, and trans[i], which defines the
+   transparency.
+
+   Return the number of entries in the palette as *paletteSizeP.
+
+   The palette is sorted so that the opaque entries are last, and we return
+   *transSizeP as the number of non-opaque entries.
+
+   palette[] and trans[] are allocated by the caller to at least 
+   MAXPALETTEENTRIES elements.
+
+   If there are more than MAXPALETTEENTRIES color/alpha pairs in the image, 
+   don't return any palette information -- just return *tooBigP == TRUE.
+-----------------------------------------------------------------------------*/
+    colorhist_vector chv;
+    unsigned int colors;
+
+    gray *alphas_of_color[MAXPALETTEENTRIES];
+    unsigned int alphas_first_index[MAXPALETTEENTRIES];
+    unsigned int alphas_of_color_cnt[MAXPALETTEENTRIES];
+ 
+    getChv(ifP, rasterPos, cols, rows, maxval, format, MAXCOLORS, 
+           &chv, &colors);
+
+    computeUnsortedAlphaPalette(ifP, cols, rows, maxval, format, rasterPos,
+                                alpha_mask, MAXPALETTEENTRIES, chv, colors,
+                                alphas_of_color,
+                                alphas_first_index,
+                                alphas_of_color_cnt);
+
+    *paletteSizeP = 
+        alphas_first_index[colors-1] + alphas_of_color_cnt[colors-1];
+    if (*paletteSizeP > MAXPALETTEENTRIES) {
+        *tooBigP = TRUE;
+    } else {
+        unsigned int mapping[MAXPALETTEENTRIES];
+            /* Sorting of the alpha/color palette.  mapping[x] is the
+               index into the sorted PNG palette of the alpha/color
+               pair whose index is x in the unsorted PNG palette.
+               This mapping sorts the palette so that opaque entries
+               are last.  
+            */
+
+        *tooBigP = FALSE;
+
+        /* Make the opaque palette entries last */
+        sortAlphaPalette(alphas_of_color, alphas_first_index,
+                         alphas_of_color_cnt, colors,
+                         mapping, transSizeP);
+
+        {
+            unsigned int colorIndex;
+
+            for (colorIndex = 0; colorIndex < colors; ++colorIndex) {
+                unsigned int j;
+                for (j = 0; j < alphas_of_color_cnt[colorIndex]; ++j) {
+                    unsigned int const paletteIndex = 
+                        alphas_first_index[colorIndex] + j;
+                    palette_pnm[mapping[paletteIndex]] = chv[colorIndex].color;
+                    trans_pnm[mapping[paletteIndex]] = 
+                    alphas_of_color[colorIndex][j];
+                }
+            }
+        }
+    }
+    { 
+        unsigned int colorIndex;
+        for (colorIndex = 0; colorIndex < colors + 1; ++colorIndex)
+            free(alphas_of_color[colorIndex]);
+    }
+} 
+
+
+
+static void
+makeOneColorTransparentInPalette(xel            const transColor, 
+                                 bool           const exact,
+                                 pixel                palette_pnm[],
+                                 unsigned int   const paletteSize,
+                                 gray                 trans_pnm[],
+                                 unsigned int * const transSizeP) {
+/*----------------------------------------------------------------------------
+   Find the color 'transColor' in the color/alpha palette defined by
+   palette_pnm[], paletteSize, trans_pnm[] and *transSizeP.  
+
+   Make that entry fully transparent.
+
+   Rearrange the palette so that that entry is first.  (The PNG compressor
+   can do a better job when the opaque entries are all last in the 
+   color/alpha palette).
+
+   If the specified color is not there and exact == TRUE, return
+   without changing anything, but issue a warning message.  If it's
+   not there and exact == FALSE, just find the closest color.
+
+   We assume every entry in the palette is opaque upon entry.
+
+   A valid palette has at least one color.
+-----------------------------------------------------------------------------*/
+    unsigned int transparentIndex;
+    unsigned int distance;
+
+    assert(paletteSize > 0);
+    
+    if (*transSizeP != 0)
+        pm_error("Internal error: trying to make a color in the palette "
+                 "transparent where there already is one.");
+
+    closestColorInPalette(transColor, palette_pnm, paletteSize, 
+                          &transparentIndex, &distance);
+
+    if (distance != 0 && exact) {
+        pm_message("specified transparent color not present in palette; "
+                   "ignoring -transparent");
+        errorlevel = PNMTOPNG_WARNING_LEVEL;
+    } else {        
+        /* Swap this with the first entry in the palette */
+        pixel tmp;
+    
+        tmp = palette_pnm[transparentIndex];
+        palette_pnm[transparentIndex] = palette_pnm[0];
+        palette_pnm[0] = tmp;
+        
+        /* Make it transparent */
+        trans_pnm[0] = PGM_TRANSPARENT;
+        *transSizeP = 1;
+        if (verbose) {
+            pixel const p = palette_pnm[0];
+            pm_message("Making all occurences of color (%u, %u, %u) "
+                       "transparent.",
+                       PPM_GETR(p), PPM_GETG(p), PPM_GETB(p));
+        }
+    }
+}
+
+
+
+static void
+findOrAddBackgroundInPalette(pixel          const backColor, 
+                             pixel                palette_pnm[], 
+                             unsigned int * const paletteSizeP,
+                             unsigned int * const backgroundIndexP) {
+/*----------------------------------------------------------------------------
+  Add the background color 'backColor' to the palette, unless
+  it's already in there.  If it's not present and there's no room to
+  add it, choose a background color that's already in the palette,
+  as close to 'backColor' as possible.
+
+  If we add an entry to the palette, make it opaque.  But in searching the 
+  existing palette, ignore transparency.
+
+  Note that PNG specs say that transparency of the background is meaningless;
+  i.e. a viewer must ignore the transparency of the palette entry when 
+  using the background color.
+
+  Return the palette index of the background color as *backgroundIndexP.
+-----------------------------------------------------------------------------*/
+    int backgroundIndex;  /* negative means not found */
+    unsigned int paletteIndex;
+
+    backgroundIndex = -1;
+    for (paletteIndex = 0; 
+         paletteIndex < *paletteSizeP; 
+         ++paletteIndex) 
+        if (PPM_EQUAL(palette_pnm[paletteIndex], backColor))
+            backgroundIndex = paletteIndex;
+
+    if (backgroundIndex >= 0) {
+        /* The background color is already in the palette. */
+        *backgroundIndexP = backgroundIndex;
+        if (verbose) {
+            pixel const p = palette_pnm[*backgroundIndexP];
+            pm_message("background color (%u, %u, %u) appears in image.",
+                       PPM_GETR(p), PPM_GETG(p), PPM_GETB(p));
+        }
+    } else {
+        /* Try to add the background color, opaque, to the palette. */
+        if (*paletteSizeP < MAXCOLORS) {
+            /* There's room, so just add it to the end of the palette */
+
+            /* Because we're not expanding the transparency palette, this
+               entry is not in it, and is thus opaque.
+            */
+            *backgroundIndexP = (*paletteSizeP)++;
+            palette_pnm[*backgroundIndexP] = backColor;
+            if (verbose) {
+                pixel const p = palette_pnm[*backgroundIndexP];
+                pm_message("added background color (%u, %u, %u) to palette.",
+                           PPM_GETR(p), PPM_GETG(p), PPM_GETB(p));
+            }
+        } else {
+            closestColorInPalette(backColor, palette_pnm, *paletteSizeP,
+                                  backgroundIndexP, NULL);
+            errorlevel = PNMTOPNG_WARNING_LEVEL;
+            {
+                pixel const p = palette_pnm[*backgroundIndexP];
+                pm_message("no room in palette for background color; "
+                           "using closest match (%u, %u, %u) instead",
+                           PPM_GETR(p), PPM_GETG(p), PPM_GETB(p));
+            }
+        }
+    }
+}
+
+
+
+static void 
+buildColorLookup(pixel                   palette_pnm[], 
+                 unsigned int      const paletteSize,
+                 colorhash_table * const chtP) {
+/*----------------------------------------------------------------------------
+   Create a colorhash_table out of the palette described by
+   palette_pnm[] (which has 'paletteSize' entries) so one can look up
+   the palette index of a given color.
+
+   Where the same color appears twice in the palette, the lookup table
+   finds an arbitrary one of them.  We don't consider transparency of
+   palette entries, so if the same color appears in the palette once
+   transparent and once opaque, the lookup table finds an arbitrary one
+   of those two.
+-----------------------------------------------------------------------------*/
+    colorhash_table const cht = ppm_alloccolorhash();
+    unsigned int paletteIndex;
+
+    for (paletteIndex = 0; paletteIndex < paletteSize; ++paletteIndex) {
+        ppm_addtocolorhash(cht, &palette_pnm[paletteIndex], paletteIndex);
+    }
+    *chtP = cht;
+}
+
+
+static void 
+buildColorAlphaLookup(pixel              palette_pnm[], 
+                      unsigned int const paletteSize,
+                      gray               trans_pnm[], 
+                      unsigned int const transSize,
+                      gray         const alphaMaxval,
+                      coloralphahash_table * const cahtP) {
+    
+    coloralphahash_table const caht = alloccoloralphahash();
+
+    unsigned int paletteIndex;
+
+    for (paletteIndex = 0; paletteIndex < paletteSize; ++paletteIndex) {
+        gray paletteTrans;
+
+        if (paletteIndex < transSize)
+            paletteTrans = alphaMaxval;
+        else
+            paletteTrans = trans_pnm[paletteIndex];
+
+
+        addtocoloralphahash(caht, &palette_pnm[paletteIndex],
+                            &trans_pnm[paletteIndex], paletteIndex);
+    }
+    *cahtP = caht;
+}
+
+
+
+static void
+tryAlphaPalette(FILE *         const ifP,
+                int            const cols,
+                int            const rows,
+                xelval         const maxval,
+                int            const format,
+                pm_filepos     const rasterPos,
+                gray **        const alpha_mask,
+                FILE *         const pfP,
+                pixel *        const palette_pnm,
+                unsigned int * const paletteSizeP,
+                gray *         const trans_pnm,
+                unsigned int * const transSizeP,
+                const char **  const impossibleReasonP) {
+/*----------------------------------------------------------------------------
+   Try to make an alpha palette as 'trans_pnm', size *transSizeP.
+
+   If it's impossible, return as *impossibleReasonP newly malloced storage
+   containing text that tells why.  But if we succeed, return
+   *impossibleReasonP == NULL.
+-----------------------------------------------------------------------------*/
+    bool tooBig;
+    if (pfP)
+        pm_error("This program is not capable of generating "
+                 "a PNG with transparency when you specify "
+                 "the palette with -palette.");
+
+    compute_alpha_palette(ifP, cols, rows, maxval, format, 
+                          rasterPos,  alpha_mask, palette_pnm, trans_pnm, 
+                          paletteSizeP, transSizeP, &tooBig);
+    if (tooBig) {
+        asprintfN(impossibleReasonP,
+                  "too many color/transparency pairs "
+                  "(more than the PNG maximum of %u", 
+                  MAXPALETTEENTRIES);
+    } else
+        *impossibleReasonP = NULL;
+} 
+
+
+
+static void
+computePixelWidth(int            const pnm_type,
+                  unsigned int   const pnm_meaningful_bits,
+                  bool           const alpha,
+                  unsigned int * const bitsPerSampleP,
+                  unsigned int * const bitsPerPixelP) {
+
+    unsigned int bitsPerSample, bitsPerPixel;
+
+    if (pnm_type == PPM_TYPE || alpha) {
+        /* PNG allows only depths of 8 and 16 for a truecolor image 
+           and for a grayscale image with an alpha channel.
+          */
+        if (pnm_meaningful_bits > 8)
+            bitsPerSample = 16;
+        else 
+            bitsPerSample = 8;
+    } else {
+        /* A grayscale, non-colormapped, no-alpha PNG may have any 
+             bit depth from 1 to 16
+          */
+        if (pnm_meaningful_bits > 8)
+            bitsPerSample = 16;
+        else if (pnm_meaningful_bits > 4)
+            bitsPerSample = 8;
+        else if (pnm_meaningful_bits > 2)
+            bitsPerSample = 4;
+        else if (pnm_meaningful_bits > 1)
+            bitsPerSample = 2;
+        else
+            bitsPerSample = 1;
+    }
+    if (alpha) {
+        if (pnm_type == PPM_TYPE)
+            bitsPerPixel = 4 * bitsPerSample;
+        else
+            bitsPerPixel = 2 * bitsPerSample;
+    } else {
+        if (pnm_type == PPM_TYPE)
+            bitsPerPixel = 3 * bitsPerSample;
+        else
+            bitsPerPixel = bitsPerSample;
+    }
+    if (bitsPerPixelP)
+        *bitsPerPixelP = bitsPerPixel;
+    if (bitsPerSampleP)
+        *bitsPerSampleP = bitsPerSample;
+}
+
+
+
+static unsigned int
+paletteIndexBits(unsigned int const nColors) {
+/*----------------------------------------------------------------------------
+  Return the number of bits that a palette index in the PNG will
+  occupy given that the palette has 'nColors' colors in it.  It is 1,
+  2, 4, or 8 bits.
+  
+  If 'nColors' is not a valid PNG palette size, return 0.
+-----------------------------------------------------------------------------*/
+    unsigned int retval;
+
+    if (nColors < 1)
+        retval = 0;
+    else if (nColors <= 2)
+        retval = 1;
+    else if (nColors <= 4)
+        retval = 2;
+    else if (nColors <= 16)
+        retval = 4;
+    else if (nColors <= 256)
+        retval = 8;
+    else
+        retval = 0;
+
+    return retval;
+}
+
+
+
+static void
+computeColorMap(FILE *         const ifP,
+                pm_filepos     const rasterPos,
+                int            const cols,
+                int            const rows,
+                xelval         const maxval,
+                int            const format,
+                bool           const force,
+                FILE *         const pfP,
+                bool           const alpha,
+                bool           const transparent,
+                pixel          const transcolor,
+                bool           const transexact,
+                bool           const background,
+                pixel          const backcolor,
+                gray **        const alpha_mask,
+                unsigned int   const pnm_meaningful_bits,
+                /* Outputs */
+                pixel *        const palette_pnm,
+                unsigned int * const paletteSizeP,
+                gray *         const trans_pnm,
+                unsigned int * const transSizeP,
+                unsigned int * const backgroundIndexP,
+                const char **  const noColormapReasonP) {
+/*---------------------------------------------------------------------------
+  Determine whether to do a colormapped or truecolor PNG and if
+  colormapped, compute the full PNG palette -- both color and
+  transparency.
+
+  If we decide to do truecolor, we return as *noColormapReasonP a text
+  description of why, in newly malloced memory.  If we decide to go
+  with colormapped, we return *noColormapReasonP == NULL.
+
+  In the colormapped case, we return the palette as arrays
+  palette_pnm[] and trans_pnm[], allocated by Caller, with sizes
+  *paletteSizeP and *transSizeP.
+
+  'background' means the image is to have a background color, and that
+  color is 'backcolor'.  'backcolor' is meaningless when 'background'
+  is false.
+
+  If the image is to have a background color, we return the palette index
+  of that color as *backgroundIndexP.
+-------------------------------------------------------------------------- */
+    if (force)
+        asprintfN(noColormapReasonP, "You requested no color map");
+    else if (maxval > PALETTEMAXVAL)
+        asprintfN(noColormapReasonP, "The maxval of the input image (%u) "
+                  "exceeds the PNG palette maxval (%u)", 
+                  maxval, PALETTEMAXVAL);
+    else {
+        unsigned int bitsPerPixel;
+        computePixelWidth(PNM_FORMAT_TYPE(format), pnm_meaningful_bits, alpha,
+                          NULL, &bitsPerPixel);
+
+        if (!pfP && bitsPerPixel == 1)
+            /* No palette can beat 1 bit per pixel -- no need to waste time
+               counting the colors.
+            */
+            asprintfN(noColormapReasonP, "pixel is already only 1 bit");
+        else {
+            /* We'll have to count the colors ('colors') to know if a
+               palette is possible and desirable.  Along the way, we'll
+               compute the actual set of colors (chv) too, and then create
+               the palette itself if we decide we want one.
+            */
+            colorhist_vector chv;
+            unsigned int colors;
+            
+            getChv(ifP, rasterPos, cols, rows, maxval, format, MAXCOLORS, 
+                   &chv, &colors);
+
+            if (chv == NULL) {
+                asprintfN(noColormapReasonP, 
+                          "More than %u colors found -- too many for a "
+                          "colormapped PNG", MAXCOLORS);
+            } else {
+                /* There are few enough colors that a palette is possible */
+                if (bitsPerPixel <= paletteIndexBits(colors) && !pfP)
+                    asprintfN(noColormapReasonP, 
+                              "palette index for %u colors would be "
+                              "no smaller than the indexed value (%u bits)", 
+                              colors, bitsPerPixel);
+                else {
+                    unsigned int paletteSize;
+                    unsigned int transSize;
+                    if (alpha)
+                        tryAlphaPalette(ifP, cols, rows, maxval, format,
+                                        rasterPos, alpha_mask, pfP,
+                                        palette_pnm, &paletteSize, 
+                                        trans_pnm, &transSize,
+                                        noColormapReasonP);
+
+                    else {
+                        *noColormapReasonP = NULL;
+
+                        compute_nonalpha_palette(chv, colors, maxval, pfP,
+                                                 palette_pnm, &paletteSize, 
+                                                 trans_pnm, &transSize);
+    
+                        if (transparent)
+                            makeOneColorTransparentInPalette(
+                                transcolor, transexact, 
+                                palette_pnm, paletteSize, trans_pnm, 
+                                &transSize);
+                    }
+                    if (!*noColormapReasonP) {
+                        if (background)
+                            findOrAddBackgroundInPalette(
+                                backcolor, palette_pnm, &paletteSize,
+                                backgroundIndexP);
+                        *paletteSizeP = paletteSize;
+                        *transSizeP   = transSize;
+                    }
+                }
+            }
+            freeChv();
+        }
+    }
+}
+
+
+
+static void computeColorMapLookupTable(
+    bool                   const colorMapped,
+    pixel                        palette_pnm[],
+    unsigned int           const palette_size,
+    gray                         trans_pnm[],
+    unsigned int           const trans_size,
+    bool                   const alpha,
+    xelval                 const alpha_maxval,
+    colorhash_table *      const chtP,
+    coloralphahash_table * const cahtP) {
+/*----------------------------------------------------------------------------
+   Compute applicable lookup tables for the palette index.  If there's no
+   alpha mask, this is just a standard Netpbm colorhash_table.  If there's
+   an alpha mask, it is the slower Pnmtopng-specific 
+   coloralphahash_table.
+
+   If a lookup table is not applicable to the image, return NULL as
+   its address.  (If the image is not colormapped, both will be NULL).
+-----------------------------------------------------------------------------*/
+    if (colorMapped) {
+        if (alpha) {
+            buildColorAlphaLookup(palette_pnm, palette_size, 
+                                  trans_pnm, trans_size, alpha_maxval, cahtP);
+            *chtP = NULL;
+        } else { 
+            buildColorLookup(palette_pnm, palette_size, chtP);
+            *cahtP = NULL;
+        }
+        if (verbose)
+            pm_message("PNG palette has %u entries, %u of them non-opaque",
+                       palette_size, trans_size);
+    } else {
+        *chtP = NULL;
+        *cahtP = NULL;
+    }
+}
+
+
+
+static void
+computeRasterWidth(bool           const colorMapped,
+                   unsigned int   const palette_size,
+                   int            const pnm_type,
+                   unsigned int   const pnm_meaningful_bits,
+                   bool           const alpha,
+                   unsigned int * const bitsPerSampleP,
+                   unsigned int * const bitsPerPixelP) {
+/*----------------------------------------------------------------------------
+   Compute the number of bits per raster sample and per raster pixel:
+   *bitsPerSampleP and *bitsPerPixelP.  Note that a raster element may be a
+   palette index, or a gray value or color with or without alpha mask.
+-----------------------------------------------------------------------------*/
+    if (colorMapped) {
+        /* The raster element is a palette index */
+        if (palette_size <= 2)
+            *bitsPerSampleP = 1;
+        else if (palette_size <= 4)
+            *bitsPerSampleP = 2;
+        else if (palette_size <= 16)
+            *bitsPerSampleP = 4;
+        else
+            *bitsPerSampleP = 8;
+        *bitsPerPixelP = *bitsPerSampleP;
+        if (verbose)
+            pm_message("Writing %d-bit color indexes", *bitsPerSampleP);
+    } else {
+        /* The raster element is an explicit pixel -- color and transparency */
+        computePixelWidth(pnm_type, pnm_meaningful_bits, alpha,
+                          bitsPerSampleP, bitsPerPixelP);
+
+        if (verbose)
+            pm_message("Writing %d bits per component per pixel", 
+                       *bitsPerSampleP);
+    }
+}
+
+
+static void
+createPngPalette(pixel              palette_pnm[], 
+                 unsigned int const paletteSize, 
+                 pixval       const maxval,
+                 gray               trans_pnm[],
+                 unsigned int const transSize,
+                 gray               alpha_maxval,
+                 png_color          palette[],
+                 png_byte           trans[]) {
+/*----------------------------------------------------------------------------
+   Create the data structure to be passed to the PNG compressor to represent
+   the palette -- the whole palette, color + transparency.
+
+   This is basically just a maxval conversion from the Netpbm-format
+   equivalents we get as input.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+
+    for (i = 0; i < paletteSize; ++i) {
+        pixel p;
+        PPM_DEPTH(p, palette_pnm[i], maxval, PALETTEMAXVAL);
+        palette[i].red   = PPM_GETR(p);
+        palette[i].green = PPM_GETG(p);
+        palette[i].blue  = PPM_GETB(p);
+    }
+
+    for (i = 0; i < transSize; ++i) {
+        unsigned int const newmv = PALETTEMAXVAL;
+        unsigned int const oldmv = alpha_maxval;
+        trans[i] = (trans_pnm[i] * newmv + (oldmv/2)) / oldmv;
+    }
+}
+
+
+
+static void
+setCompressionSize(png_struct * const png_ptr,
+                   int const    buffer_size) {
+
+#if PNG_LIBPNG_VER >= 10009
+    png_set_compression_buffer_size(png_ptr, buffer_size);
+#else
+    pm_error("Your PNG library cannot set the compression buffer size.  "
+             "You need at least Version 1.0.9 of Libpng; you have Version %s",
+             PNG_LIBPNG_VER_STRING);
+#endif
+}
+
+
+
+static void
+setZlibCompression(png_struct *           const png_ptr,
+                   struct zlibCompression const zlibCompression) {
+
+    if (zlibCompression.levelSpec)
+        png_set_compression_level(png_ptr, zlibCompression.level);
+
+    if (zlibCompression.memLevelSpec)
+        png_set_compression_mem_level(png_ptr, zlibCompression.mem_level);
+
+    if (zlibCompression.strategySpec)
+        png_set_compression_strategy(png_ptr, zlibCompression.strategy);
+
+    if (zlibCompression.windowBitsSpec)
+        png_set_compression_window_bits(png_ptr, zlibCompression.window_bits);
+
+    if (zlibCompression.methodSpec)
+        png_set_compression_method(png_ptr, zlibCompression.method);
+
+    if (zlibCompression.bufferSizeSpec) {
+        setCompressionSize(png_ptr, zlibCompression.buffer_size);
+    }
+}
+                  
+
+
+static void
+makePngLine(png_byte *           const line,
+            const xel *          const xelrow,
+            unsigned int         const cols,
+            xelval               const maxval,
+            bool                 const alpha,
+            gray *               const alpha_mask,
+            colorhash_table      const cht,
+            coloralphahash_table const caht,
+            png_info *           const info_ptr,
+            xelval               const png_maxval,
+            unsigned int         const depth) {
+            
+    unsigned int col;
+    png_byte *pp;
+
+    pp = line;  /* start at beginning of line */
+    for (col = 0; col < cols; ++col) {
+        xel p_png;
+        xel const p = xelrow[col];
+        PPM_DEPTH(p_png, p, maxval, png_maxval);
+        if (info_ptr->color_type == PNG_COLOR_TYPE_GRAY ||
+            info_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+            if (depth == 16)
+                *pp++ = PNM_GET1(p_png) >> 8;
+            *pp++ = PNM_GET1(p_png) & 0xff;
+        } else if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) {
+            unsigned int paletteIndex;
+            if (alpha)
+                paletteIndex = lookupColorAlpha(caht, &p, &alpha_mask[col]);
+            else
+                paletteIndex = ppm_lookupcolor(cht, &p);
+            *pp++ = paletteIndex;
+        } else if (info_ptr->color_type == PNG_COLOR_TYPE_RGB ||
+                   info_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
+            if (depth == 16)
+                *pp++ = PPM_GETR(p_png) >> 8;
+            *pp++ = PPM_GETR(p_png) & 0xff;
+            if (depth == 16)
+                *pp++ = PPM_GETG(p_png) >> 8;
+            *pp++ = PPM_GETG(p_png) & 0xff;
+            if (depth == 16)
+                *pp++ = PPM_GETB(p_png) >> 8;
+            *pp++ = PPM_GETB(p_png) & 0xff;
+        } else
+            pm_error("INTERNAL ERROR: undefined color_type");
+                
+        if (info_ptr->color_type & PNG_COLOR_MASK_ALPHA) {
+            int const png_alphaval = (int)
+                alpha_mask[col] * (float) png_maxval / maxval + 0.5;
+            if (depth == 16)
+                *pp++ = png_alphaval >> 8;
+            *pp++ = png_alphaval & 0xff;
+        }
+    }
+}
+
+
+
+static void
+writeRaster(png_struct *         const png_ptr,
+            png_info *           const info_ptr,
+            FILE *               const ifP,
+            pm_filepos           const rasterPos,
+            unsigned int         const cols,
+            unsigned int         const rows,
+            xelval               const maxval,
+            int                  const format,
+            xelval               const png_maxval,
+            unsigned             const int depth,
+            bool                 const alpha,
+            gray **              const alpha_mask,
+            colorhash_table      const cht,
+            coloralphahash_table const caht
+            ) {
+/*----------------------------------------------------------------------------
+   Write the PNG raster via compressor *png_ptr, reading the PNM raster
+   from file *ifP, position 'rasterPos'.
+
+   The PNG raster consists of IDAT chunks.
+
+   'alpha_mask' is defined only if 'alpha' is true.
+-----------------------------------------------------------------------------*/
+    xel * xelrow;
+    png_byte * line;
+    unsigned int pass;
+
+    xelrow = pnm_allocrow(cols);
+
+    /* max: 3 color channels, one alpha channel, 16-bit */
+    MALLOCARRAY(line, cols * 8);
+    if (line == NULL)
+        pm_error("out of memory allocating PNG row buffer");
+
+    for (pass = 0; pass < png_set_interlace_handling(png_ptr); ++pass) {
+        unsigned int row;
+        pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
+        for (row = 0; row < rows; ++row) {
+            pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+            pnm_promoteformatrow(xelrow, cols, maxval, format, maxval,
+                                 PPM_TYPE);
+            
+            makePngLine(line, xelrow, cols, maxval,
+                        alpha, alpha ? alpha_mask[row] : NULL,
+                        cht, caht, info_ptr, png_maxval, depth);
+
+            png_write_row(png_ptr, line);
+        }
+    }
+    pnm_freerow(xelrow);
+}
+
+
+
+static void
+doGamaChunk(struct cmdlineInfo const cmdline,
+            png_info *         const info_ptr) {
+            
+    if (cmdline.gammaSpec) {
+        /* gAMA chunk */
+        info_ptr->valid |= PNG_INFO_gAMA;
+        info_ptr->gamma = cmdline.gamma;
+    }
+}
+
+
+
+static void
+doChrmChunk(struct cmdlineInfo const cmdline,
+            png_info *         const info_ptr) {
+
+    if (cmdline.rgbSpec) {
+        /* cHRM chunk */
+        info_ptr->valid |= PNG_INFO_cHRM;
+
+        info_ptr->x_white = cmdline.rgb.wx;
+        info_ptr->y_white = cmdline.rgb.wy;
+        info_ptr->x_red   = cmdline.rgb.rx;
+        info_ptr->y_red   = cmdline.rgb.ry;
+        info_ptr->x_green = cmdline.rgb.gx;
+        info_ptr->y_green = cmdline.rgb.gy;
+        info_ptr->x_blue  = cmdline.rgb.bx;
+        info_ptr->y_blue  = cmdline.rgb.by;
+    }
+}
+
+
+
+static void
+doPhysChunk(struct cmdlineInfo const cmdline,
+            png_info *         const info_ptr) {
+
+    if (cmdline.sizeSpec) {
+        /* pHYS chunk */
+        info_ptr->valid |= PNG_INFO_pHYs;
+
+        info_ptr->x_pixels_per_unit = cmdline.size.x;
+        info_ptr->y_pixels_per_unit = cmdline.size.y;
+        info_ptr->phys_unit_type    = cmdline.size.unit;
+    }
+}
+
+
+
+
+static void
+doTimeChunk(struct cmdlineInfo const cmdline,
+            png_info *         const info_ptr) {
+
+    if (cmdline.modtimeSpec) {
+        /* tIME chunk */
+        info_ptr->valid |= PNG_INFO_tIME;
+
+        png_convert_from_time_t(&info_ptr->mod_time, cmdline.modtime);
+    }
+}
+
+
+
+static void
+doSbitChunk(png_info * const pngInfoP,
+            xelval     const pngMaxval,
+            xelval     const maxval,
+            bool       const alpha,
+            xelval     const alphaMaxval) {
+
+    if (pngInfoP->color_type != PNG_COLOR_TYPE_PALETTE &&
+        (pngMaxval > maxval || (alpha && pngMaxval > alphaMaxval))) {
+
+        /* We're writing in a bit depth that doesn't match the maxval
+           of the input image and the alpha mask.  So we write an sBIT
+           chunk to tell what the original image's maxval was.  The
+           sBit chunk doesn't let us specify any maxval -- only powers
+           of two minus one.  So we pick the power of two minus one
+           which is greater than or equal to the actual input maxval.
+           
+           PNG also doesn't let an sBIT chunk indicate a maxval
+           _greater_ than the the PNG maxval.  The designers probably
+           did not conceive of the case where that would happen.  The
+           case is this: We detected redundancy in the bits so were
+           able to store fewer bits than the user provided.  But since
+           PNG doesn't allow it, we don't attempt to create such an
+           sBIT chunk.
+        */
+
+        pngInfoP->valid |= PNG_INFO_sBIT;
+
+        {
+            int const sbitval = pm_maxvaltobits(MIN(maxval, pngMaxval));
+
+            if (pngInfoP->color_type & PNG_COLOR_MASK_COLOR) {
+                pngInfoP->sig_bit.red   = sbitval;
+                pngInfoP->sig_bit.green = sbitval;
+                pngInfoP->sig_bit.blue  = sbitval;
+            } else
+                pngInfoP->sig_bit.gray = sbitval;
+            
+            if (verbose)
+                pm_message("Writing sBIT chunk with bits = %d", sbitval);
+        }
+        if (pngInfoP->color_type & PNG_COLOR_MASK_ALPHA) {
+            pngInfoP->sig_bit.alpha =
+                pm_maxvaltobits(MIN(alphaMaxval, pngMaxval));
+            if (verbose)
+                pm_message("  alpha bits = %d", pngInfoP->sig_bit.alpha);
+        }
+    }
+}
+
+
+
+static void 
+convertpnm(struct cmdlineInfo const cmdline,
+           FILE *             const ifp,
+           FILE *             const afp,
+           FILE *             const pfp,
+           FILE *             const tfp,
+           int *              const errorLevelP
+    ) {
+/*----------------------------------------------------------------------------
+   Design note:  It's is really a modularity violation that we have
+   all the command line parameters as an argument.  We do it because we're
+   lazy -- it takes a great deal of work to carry all that information as
+   separate arguments -- and it's only a very small violation.
+-----------------------------------------------------------------------------*/
+  xel p;
+  int rows, cols, format;
+  xelval maxval;
+      /* The maxval of the input image */
+  xelval png_maxval;
+      /* The maxval of the samples in the PNG output 
+         (must be 1, 3, 7, 15, 255, or 65535)
+      */
+  pixel transcolor;
+      /* The color that is to be transparent, with maxval equal to that
+         of the input image.
+      */
+  int transexact;  
+    /* boolean: the user wants only the exact color he specified to be
+       transparent; not just something close to it.
+    */
+  int transparent;
+  bool alpha;
+    /* There will be an alpha mask */
+  unsigned int pnm_meaningful_bits;
+  pixel backcolor;
+      /* The background color, with maxval equal to that of the input
+         image.
+      */
+  png_struct *png_ptr;
+  png_info *info_ptr;
+
+  bool colorMapped;
+  pixel palette_pnm[MAXCOLORS];
+  png_color palette[MAXCOLORS];
+      /* The color part of the color/alpha palette passed to the PNG
+         compressor 
+      */
+  unsigned int palette_size;
+
+  gray trans_pnm[MAXCOLORS];
+  png_byte  trans[MAXCOLORS];
+      /* The alpha part of the color/alpha palette passed to the PNG
+         compressor 
+      */
+  unsigned int trans_size;
+
+  colorhash_table cht;
+  coloralphahash_table caht;
+
+  unsigned int background_index;
+      /* Index into palette[] of the background color. */
+
+  png_uint_16 histogram[MAXCOLORS];
+  gray alpha_maxval;
+  int alpha_rows;
+  int alpha_cols;
+  const char * noColormapReason;
+      /* The reason that we shouldn't make a colormapped PNG, or NULL if
+         we should.  malloc'ed null-terminated string.
+      */
+  unsigned int depth;
+      /* The number of bits per sample in the (uncompressed) png 
+         raster -- if the raster contains palette indices, this is the
+         number of bits in the index.
+      */
+  unsigned int fulldepth;
+      /* The total number of bits per pixel in the (uncompressed) png
+         raster, including all channels 
+      */
+  pm_filepos rasterPos;  
+      /* file position in input image file of start of image (i.e. after
+         the header)
+      */
+  xel *xelrow;    /* malloc'ed */
+      /* The row of the input image currently being processed */
+
+  int pnm_type;
+  xelval maxmaxval;
+  gray ** alpha_mask;
+
+  /* these guys are initialized to quiet compiler warnings: */
+  maxmaxval = 255;
+  alpha_mask = NULL;
+  depth = 0;
+  errorlevel = 0;
+
+  png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
+    &pnmtopng_jmpbuf_struct, pnmtopng_error_handler, NULL);
+  if (png_ptr == NULL) {
+    pm_closer (ifp);
+    pm_error ("cannot allocate main libpng structure (png_ptr)");
+  }
+
+  info_ptr = png_create_info_struct (png_ptr);
+  if (info_ptr == NULL) {
+    png_destroy_write_struct (&png_ptr, (png_infopp)NULL);
+    pm_closer (ifp);
+    pm_error ("cannot allocate libpng info structure (info_ptr)");
+  }
+
+  if (setjmp (pnmtopng_jmpbuf_struct.jmpbuf)) {
+    png_destroy_write_struct (&png_ptr, &info_ptr);
+    pm_closer (ifp);
+    pm_error ("setjmp returns error condition (1)");
+  }
+
+  pnm_readpnminit (ifp, &cols, &rows, &maxval, &format);
+  pm_tell2(ifp, &rasterPos, sizeof(rasterPos));
+  pnm_type = PNM_FORMAT_TYPE (format);
+
+  xelrow = pnm_allocrow(cols);
+
+  if (verbose) {
+    if (pnm_type == PBM_TYPE)    
+      pm_message ("reading a PBM file (maxval=%d)", maxval);
+    else if (pnm_type == PGM_TYPE)    
+      pm_message ("reading a PGM file (maxval=%d)", maxval);
+    else if (pnm_type == PPM_TYPE)    
+      pm_message ("reading a PPM file (maxval=%d)", maxval);
+  }
+
+  if (pnm_type == PGM_TYPE)
+    maxmaxval = PGM_OVERALLMAXVAL;
+  else if (pnm_type == PPM_TYPE)
+    maxmaxval = PPM_OVERALLMAXVAL;
+
+  if (cmdline.transparent) {
+      const char * transstring2;  
+          /* The -transparent value, but with possible leading '=' removed */
+      if (cmdline.transparent[0] == '=') {
+          transexact = 1;
+          transstring2 = &cmdline.transparent[1];
+      } else {
+          transexact = 0;
+          transstring2 = cmdline.transparent;
+      }  
+      /* We do this funny PPM_DEPTH thing instead of just passing 'maxval'
+         to ppm_parsecolor() because ppm_parsecolor() does a cheap maxval
+         scaling, and this is more precise.
+      */
+      PPM_DEPTH (transcolor, ppm_parsecolor(transstring2, maxmaxval),
+                 maxmaxval, maxval);
+  }
+  if (cmdline.alpha) {
+    pixel alpha_transcolor;
+    bool alpha_can_be_transparency_index;
+    bool all_opaque;
+
+    if (verbose)
+      pm_message ("reading alpha-channel image...");
+    alpha_mask = pgm_readpgm (afp, &alpha_cols, &alpha_rows, &alpha_maxval);
+
+    if (alpha_cols != cols || alpha_rows != rows) {
+      png_destroy_write_struct (&png_ptr, &info_ptr);
+      pm_closer (ifp);
+      pm_error ("dimensions for image and alpha mask do not agree");
+    }
+    analyzeAlpha(ifp, rasterPos, cols, rows, maxval, format, 
+                 alpha_mask, alpha_maxval, &all_opaque,
+                 &alpha_can_be_transparency_index, &alpha_transcolor);
+
+    if (alpha_can_be_transparency_index && !cmdline.force) {
+      if (verbose)
+        pm_message ("converting alpha mask to transparency index");
+      alpha = FALSE;
+      transparent = 2;
+      transcolor = alpha_transcolor;
+    } else if (all_opaque) {
+        alpha = FALSE;
+        transparent = -1;
+    } else {
+      alpha = TRUE;
+      transparent = -1;
+    }
+  } else {
+      /* Though there's no alpha_mask, we still need an alpha_maxval for
+         use with trans[], which can have stuff in it if the user specified
+         a transparent color.
+      */
+      alpha = FALSE;
+      alpha_maxval = 255;
+      transparent = cmdline.transparent ? 1 : -1;
+  }
+  if (cmdline.background) 
+      PPM_DEPTH(backcolor, ppm_parsecolor(cmdline.background, maxmaxval), 
+                maxmaxval, maxval);;
+
+  /* first of all, check if we have a grayscale image written as PPM */
+
+  if (pnm_type == PPM_TYPE && !cmdline.force) {
+      unsigned int row;
+      bool isgray;
+
+      isgray = TRUE;  /* initial assumption */
+      pm_seek2(ifp, &rasterPos, sizeof(rasterPos));
+      for (row = 0; row < rows && isgray; ++row) {
+          unsigned int col;
+          pnm_readpnmrow(ifp, xelrow, cols, maxval, format);
+          for (col = 0; col < cols && isgray; ++col) {
+              p = xelrow[col];
+              if (PPM_GETR(p) != PPM_GETG(p) || PPM_GETG(p) != PPM_GETB(p))
+                  isgray = FALSE;
+          }
+      }
+      if (isgray)
+          pnm_type = PGM_TYPE;
+  }
+
+  /* handle `odd' maxvalues */
+
+  if (maxval > 65535 && !cmdline.downscale) {
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      pm_closer(ifp);
+      pm_error("can only handle files up to 16-bit "
+               "(use -downscale to override");
+  }
+
+  findRedundantBits(ifp, rasterPos, cols, rows, maxval, format, alpha,
+                    cmdline.force, &pnm_meaningful_bits);
+  
+  computeColorMap(ifp, rasterPos, cols, rows, maxval, format,
+                  cmdline.force, pfp,
+                  alpha, transparent >= 0, transcolor, transexact, 
+                  !!cmdline.background, backcolor,
+                  alpha_mask, pnm_meaningful_bits,
+                  palette_pnm, &palette_size, trans_pnm, &trans_size,
+                  &background_index, &noColormapReason);
+
+  if (noColormapReason) {
+      if (pfp)
+          pm_error("You specified a particular palette, but this image "
+                   "cannot be represented by any palette.  %s",
+                   noColormapReason);
+      if (verbose)
+          pm_message("Not using color map.  %s", noColormapReason);
+      strfree(noColormapReason);
+      colorMapped = FALSE;
+  } else
+      colorMapped = TRUE;
+  
+  computeColorMapLookupTable(colorMapped, palette_pnm, palette_size,
+                             trans_pnm, trans_size, alpha, alpha_maxval,
+                             &cht, &caht);
+
+  computeRasterWidth(colorMapped, palette_size, pnm_type, 
+                     pnm_meaningful_bits, alpha,
+                     &depth, &fulldepth);
+  if (verbose)
+    pm_message ("writing a%s %d-bit %s%s file%s",
+                fulldepth == 8 ? "n" : "", fulldepth,
+                colorMapped ? "palette": 
+                (pnm_type == PPM_TYPE ? "RGB" : "gray"),
+                alpha ? (colorMapped ? "+transparency" : "+alpha") : "",
+                cmdline.interlace ? " (interlaced)" : "");
+
+  /* now write the file */
+
+  png_maxval = pm_bitstomaxval(depth);
+
+  if (setjmp (pnmtopng_jmpbuf_struct.jmpbuf)) {
+    png_destroy_write_struct (&png_ptr, &info_ptr);
+    pm_closer (ifp);
+    pm_error ("setjmp returns error condition (2)");
+  }
+
+  png_init_io (png_ptr, stdout);
+  info_ptr->width = cols;
+  info_ptr->height = rows;
+  info_ptr->bit_depth = depth;
+
+  if (colorMapped)
+    info_ptr->color_type = PNG_COLOR_TYPE_PALETTE;
+  else if (pnm_type == PPM_TYPE)
+    info_ptr->color_type = PNG_COLOR_TYPE_RGB;
+  else
+    info_ptr->color_type = PNG_COLOR_TYPE_GRAY;
+
+  if (alpha && info_ptr->color_type != PNG_COLOR_TYPE_PALETTE)
+    info_ptr->color_type |= PNG_COLOR_MASK_ALPHA;
+
+  info_ptr->interlace_type = cmdline.interlace;
+
+  doGamaChunk(cmdline, info_ptr);
+
+  doChrmChunk(cmdline, info_ptr);
+
+  doPhysChunk(cmdline, info_ptr);
+
+  if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) {
+
+    /* creating PNG palette  (PLTE and tRNS chunks) */
+
+    createPngPalette(palette_pnm, palette_size, maxval,
+                     trans_pnm, trans_size, alpha_maxval, 
+                     palette, trans);
+    info_ptr->valid |= PNG_INFO_PLTE;
+    info_ptr->palette = palette;
+    info_ptr->num_palette = palette_size;
+    if (trans_size > 0) {
+        info_ptr->valid |= PNG_INFO_tRNS;
+        info_ptr->trans = trans;
+        info_ptr->num_trans = trans_size;   /* omit opaque values */
+    }
+    /* creating hIST chunk */
+    if (cmdline.hist) {
+        colorhist_vector chv;
+        unsigned int colors;
+        colorhash_table cht;
+        
+        getChv(ifp, rasterPos, cols, rows, maxval, format, MAXCOLORS, 
+               &chv, &colors);
+
+        cht = ppm_colorhisttocolorhash (chv, colors);
+                
+        { 
+            unsigned int i;
+            for (i = 0 ; i < MAXCOLORS; ++i) {
+                int const chvIndex = ppm_lookupcolor(cht, &palette_pnm[i]);
+                if (chvIndex == -1)
+                    histogram[i] = 0;
+                else
+                    histogram[i] = chv[chvIndex].value;
+            }
+        }
+
+        ppm_freecolorhash(cht);
+
+        info_ptr->valid |= PNG_INFO_hIST;
+        info_ptr->hist = histogram;
+        if (verbose)
+            pm_message("histogram created");
+    }
+  } else { /* color_type != PNG_COLOR_TYPE_PALETTE */
+    if (info_ptr->color_type == PNG_COLOR_TYPE_GRAY ||
+        info_ptr->color_type == PNG_COLOR_TYPE_RGB) {
+        if (transparent > 0) {
+            info_ptr->valid |= PNG_INFO_tRNS;
+            info_ptr->trans_values = 
+                xelToPngColor_16(transcolor, maxval, png_maxval);
+        }
+    } else {
+        /* This is PNG_COLOR_MASK_ALPHA.  Transparency will be handled
+           by the alpha channel, not a transparency color.
+        */
+    }
+    if (verbose) {
+        if (info_ptr->valid && PNG_INFO_tRNS) 
+            pm_message("Transparent color {gray, red, green, blue} = "
+                       "{%d, %d, %d, %d}",
+                       info_ptr->trans_values.gray,
+                       info_ptr->trans_values.red,
+                       info_ptr->trans_values.green,
+                       info_ptr->trans_values.blue);
+        else
+            pm_message("No transparent color");
+    }
+  }
+
+  /* bKGD chunk */
+  if (cmdline.background) {
+      info_ptr->valid |= PNG_INFO_bKGD;
+      if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) {
+          info_ptr->background.index = background_index;
+      } else {
+          info_ptr->background = 
+              xelToPngColor_16(backcolor, maxval, png_maxval);
+          if (verbose)
+              pm_message("Writing bKGD chunk with background color "
+                         " {gray, red, green, blue} = {%d, %d, %d, %d}",
+                         info_ptr->background.gray, 
+                         info_ptr->background.red, 
+                         info_ptr->background.green, 
+                         info_ptr->background.blue ); 
+      }
+  }
+
+  doSbitChunk(info_ptr, png_maxval, maxval, alpha, alpha_maxval);
+
+  /* tEXT and zTXT chunks */
+  if (cmdline.text || cmdline.ztxt)
+      pnmpng_read_text(info_ptr, tfp, !!cmdline.ztxt, cmdline.verbose);
+
+  doTimeChunk(cmdline, info_ptr);
+
+  if (cmdline.filterSet != 0)
+      png_set_filter(png_ptr, 0, cmdline.filterSet);
+
+  setZlibCompression(png_ptr, cmdline.zlibCompression);
+
+  /* write the png-info struct */
+  png_write_info(png_ptr, info_ptr);
+
+  if (cmdline.text || cmdline.ztxt)
+      /* prevent from being written twice with png_write_end */
+      info_ptr->num_text = 0;
+
+  if (cmdline.modtime)
+      /* prevent from being written twice with png_write_end */
+      info_ptr->valid &= ~PNG_INFO_tIME;
+
+  /* let libpng take care of, e.g., bit-depth conversions */
+  png_set_packing (png_ptr);
+
+  writeRaster(png_ptr, info_ptr, ifp, rasterPos, cols, rows, maxval, format,
+              png_maxval, depth, alpha, alpha_mask, cht, caht);
+
+  png_write_end (png_ptr, info_ptr);
+
+
+#if 0
+  /* The following code may be intended to solve some segfault problem
+     that arises with png_destroy_write_struct().  The latter is the
+     method recommended in the libpng documentation and this program 
+     will not compile under Cygwin because the Windows DLL for libpng
+     does not contain png_write_destroy() at all.  Since the author's
+     comment below does not make it clear what the segfault issue is,
+     we cannot consider it.  -Bryan 00.09.15
+*/
+
+  png_write_destroy (png_ptr);
+  /* flush first because free(png_ptr) can segfault due to jmpbuf problems
+     in png_write_destroy */
+  fflush (stdout);
+  free (png_ptr);
+  free (info_ptr);
+#else
+  png_destroy_write_struct(&png_ptr, &info_ptr);
+#endif
+
+  pnm_freerow(xelrow);
+
+  if (cht)
+      ppm_freecolorhash(cht);
+  if (caht)
+      freecoloralphahash(caht);
+
+  *errorLevelP = errorlevel;
+}
+
+
+
+static void
+displayVersion() {
+
+    fprintf(stderr,"Pnmtopng version %s.\n", NETPBM_VERSION);
+
+    /* We'd like to display the version of libpng with which we're
+       linked, as we do for zlib, but it isn't practical.
+       While libpng is capable of telling you what it's level
+       is, different versions of it do it two different ways: with
+       png_libpng_ver or with png_get_header_ver.  So we have to be
+       compiled for a particular version just to find out what
+       version it is! It's not worth having a link failure, much
+       less a compile failure, if we choose wrong.
+       png_get_header_ver is not in anything older than libpng 1.0.2a
+       (Dec 1998).  png_libpng_ver is not there in libraries built
+       without USE_GLOBAL_ARRAYS.  Cygwin versions are normally built
+       without USE_GLOBAL_ARRAYS.  -bjh 2002.06.17.
+    */
+    fprintf(stderr, "   Compiled with libpng %s.\n",
+            PNG_LIBPNG_VER_STRING);
+    fprintf(stderr, "   Compiled with zlib %s; using zlib %s.\n",
+            ZLIB_VERSION, zlib_version);
+    fprintf(stderr, "\n");
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    FILE * afP;
+    FILE * pfP;
+    FILE * tfP;
+
+    int errorlevel;
+    
+    pnm_init (&argc, argv);
+    
+    parseCommandLine(argc, argv, &cmdline);
+    
+    if (cmdline.libversion) {
+        displayVersion();
+        return 0;
+    }
+    verbose = cmdline.verbose;
+    
+    ifP = pm_openr_seekable(cmdline.inputFilename);
+    
+    if (cmdline.alpha)
+        afP = pm_openr(cmdline.alpha);
+    else
+        afP = NULL;
+    
+    if (cmdline.palette)
+        pfP = pm_openr(cmdline.palette);
+    else
+        pfP = NULL;
+    
+    if (cmdline.text)
+        tfP = pm_openr(cmdline.text);
+    else if (cmdline.ztxt)
+        tfP = pm_openr(cmdline.ztxt);
+    else
+        tfP = NULL;
+
+    convertpnm(cmdline, ifP, afP, pfP, tfP, &errorlevel);
+    
+    if (afP)
+        pm_close(afP);
+    if (pfP)
+        pm_close(pfP);
+    if (tfP)
+        pm_close(tfP);
+
+    pm_close(ifP);
+    pm_close(stdout);
+
+    return errorlevel;
+}
diff --git a/converter/other/pnmtops.c b/converter/other/pnmtops.c
new file mode 100644
index 00000000..9f6de526
--- /dev/null
+++ b/converter/other/pnmtops.c
@@ -0,0 +1,1323 @@
+/* pnmtops.c - read a PNM image and produce a PostScript program.
+
+   Copyright information is at end of file.
+
+   We produce two main kinds of Postscript program:
+
+      1) Use built in Postscript filters /ASCII85Decode, /ASCIIHexDecode,
+         /RunLengthDecode, and /FlateDecode;
+
+         We use methods we learned from Dirk Krause's program Bmeps and
+         raster encoding code copied almost directly from Bmeps.
+
+      2) Use our own filters and redefine /readstring .  This is aboriginal
+         Netpbm code, from when Postscript was young.
+
+   (2) is the default, because it's been working for ages and we have
+   more confidence in it.  But (1) gives more options.  The user
+   selects (1) with the -psfilter option.
+
+   We also do a few other bold new things only when the user specifies
+   -psfilter, because we're not sure they work for everyone.
+
+   (I actually don't know Postscript, so some of this description, not to
+   mention the code, may be totally bogus.)
+
+   NOTE: it is possible to put transparency information in an
+   encapsulated Postscript program.  Bmeps does this.  We don't.  It
+   might be hard to do, because in Postscript, the transparency information
+   goes in separate from the rest of the raster.
+*/
+
+#define _BSD_SOURCE  /* Make sure string.h contains strdup() */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <string.h>
+#include <assert.h>
+#include "pam.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "bmepsoe.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  /* Filespecs of input file */
+    float scale;
+    unsigned int dpiX;     /* horiz component of DPI option */
+    unsigned int dpiY;     /* vert component of DPI option */
+    unsigned int width;              /* in 1/72 inch */
+    unsigned int height;             /* in 1/72 inch */
+    unsigned int mustturn;
+    bool         canturn;
+    unsigned int rle;
+    bool         center;
+    unsigned int imagewidth;         /* in 1/72 inch; zero if unspec */
+    unsigned int imageheight;        /* in 1/72 inch; zero if unspec */
+    unsigned int equalpixels;
+    unsigned int setpage;
+    bool         showpage;
+    unsigned int level;
+    unsigned int levelSpec;
+    unsigned int psfilter;
+    unsigned int flate;
+    unsigned int ascii85;
+    unsigned int dict;
+    unsigned int vmreclaim;
+    unsigned int verbose;
+};
+
+
+static bool verbose;
+
+
+static void
+parseDpi(const char *   const dpiOpt, 
+         unsigned int * const dpiXP, 
+         unsigned int * const dpiYP) {
+
+    char *dpistr2;
+    unsigned int dpiX, dpiY;
+
+    dpiX = strtol(dpiOpt, &dpistr2, 10);
+    if (dpistr2 == dpiOpt) 
+        pm_error("Invalid value for -dpi: '%s'.  Must be either number "
+                 "or NxN ", dpiOpt);
+    else {
+        if (*dpistr2 == '\0') {
+            *dpiXP = dpiX;
+            *dpiYP = dpiX;
+        } else if (*dpistr2 == 'x') {
+            char * dpistr3;
+
+            dpistr2++;  /* Move past 'x' */
+            dpiY = strtol(dpistr2, &dpistr3, 10);        
+            if (dpistr3 != dpistr2 && *dpistr3 == '\0') {
+                *dpiXP = dpiX;
+                *dpiYP = dpiY;
+            } else {
+                pm_error("Invalid value for -dpi: '%s'.  Must be either "
+                         "number or NxN", dpiOpt);
+            }
+        }
+    }
+}
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+
+    unsigned int imagewidthSpec, imageheightSpec;
+    float imagewidth, imageheight;
+    unsigned int center, nocenter;
+    unsigned int nosetpage;
+    float width, height;
+    unsigned int noturn;
+    unsigned int showpage, noshowpage;
+    const char *dpiOpt;
+    unsigned int dpiSpec;
+
+    optStruct3 opt;
+    unsigned int option_def_index = 0;
+    optEntry *option_def;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    OPTENT3(0, "scale",       OPT_FLOAT, &cmdlineP->scale, NULL,         0);
+    OPTENT3(0, "dpi",         OPT_STRING, &dpiOpt,         &dpiSpec,     0);
+    OPTENT3(0, "width",       OPT_FLOAT, &width,           NULL,         0);
+    OPTENT3(0, "height",      OPT_FLOAT, &height,          NULL,         0);
+    OPTENT3(0, "psfilter",    OPT_FLAG,  NULL, &cmdlineP->psfilter,      0);
+    OPTENT3(0, "turn",        OPT_FLAG,  NULL, &cmdlineP->mustturn,      0);
+    OPTENT3(0, "noturn",      OPT_FLAG,  NULL, &noturn,                  0);
+    OPTENT3(0, "rle",         OPT_FLAG,  NULL, &cmdlineP->rle,           0);
+    OPTENT3(0, "runlength",   OPT_FLAG,  NULL, &cmdlineP->rle,           0);
+    OPTENT3(0, "ascii85",     OPT_FLAG,  NULL, &cmdlineP->ascii85,       0);
+    OPTENT3(0, "center",      OPT_FLAG,  NULL, &center,                  0);
+    OPTENT3(0, "nocenter",    OPT_FLAG,  NULL, &nocenter,                0);
+    OPTENT3(0, "equalpixels", OPT_FLAG,  NULL, &cmdlineP->equalpixels,   0);
+    OPTENT3(0, "imagewidth",  OPT_FLOAT, &imagewidth,  &imagewidthSpec,  0);
+    OPTENT3(0, "imageheight", OPT_FLOAT, &imageheight, &imageheightSpec, 0);
+    OPTENT3(0, "nosetpage",   OPT_FLAG,  NULL, &nosetpage,               0);
+    OPTENT3(0, "setpage",     OPT_FLAG,  NULL, &cmdlineP->setpage,       0);
+    OPTENT3(0, "noshowpage",  OPT_FLAG,  NULL, &noshowpage,              0);
+    OPTENT3(0, "flate",       OPT_FLAG,  NULL, &cmdlineP->flate,         0);
+    OPTENT3(0, "dict",        OPT_FLAG,  NULL, &cmdlineP->dict,          0);
+    OPTENT3(0, "vmreclaim",   OPT_FLAG,  NULL, &cmdlineP->vmreclaim,     0);
+    OPTENT3(0, "showpage",    OPT_FLAG,  NULL, &showpage,                0);
+    OPTENT3(0, "verbose",     OPT_FLAG,  NULL, &cmdlineP->verbose,       0);
+    OPTENT3(0, "level",       OPT_UINT, &cmdlineP->level, 
+            &cmdlineP->levelSpec,              0);
+    
+    /* DEFAULTS */
+    cmdlineP->scale = 1.0;
+    width = 8.5;
+    height = 11.0;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;
+    opt.allowNegNum = FALSE;
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+    if (cmdlineP->mustturn && noturn)
+        pm_error("You cannot specify both -turn and -noturn");
+    if (center && nocenter)
+        pm_error("You cannot specify both -center and -nocenter");
+    if (showpage && noshowpage)
+        pm_error("You cannot specify both -showpage and -noshowpage");
+    if (cmdlineP->setpage && nosetpage)
+        pm_error("You cannot specify both -setpage and -nosetpage");
+
+    if (dpiSpec)
+        parseDpi(dpiOpt, &cmdlineP->dpiX, &cmdlineP->dpiY);
+    else {
+        cmdlineP->dpiX = 300;
+        cmdlineP->dpiY = 300;
+    }
+
+    cmdlineP->center  =  !nocenter;
+    cmdlineP->canturn =  !noturn;
+    cmdlineP->showpage = !noshowpage;
+    
+    cmdlineP->width  = width * 72;
+    cmdlineP->height = height * 72;
+
+    if (imagewidthSpec)
+        cmdlineP->imagewidth = imagewidth * 72;
+    else
+        cmdlineP->imagewidth = 0;
+    if (imageheightSpec)
+        cmdlineP->imageheight = imageheight * 72;
+    else
+        cmdlineP->imageheight = 0;
+
+    if (!cmdlineP->psfilter &&
+        (cmdlineP->flate || cmdlineP->ascii85))
+        pm_error("You must specify -psfilter in order to specify "
+                 "-flate or -ascii85");
+
+    if (argc-1 == 0) 
+        cmdlineP->inputFileName = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->inputFileName = argv[1];
+
+}
+
+
+/*===========================================================================
+  The native output encoder.  This is archaic and uses global variables.
+  It is probably obsoleted by the bmeps output encoder; we just haven't
+  had a chance to verify that yet.
+===========================================================================*/
+
+/*----------------------------------------------------------------------------
+   The following global variables are the native output encoder state.
+-----------------------------------------------------------------------------*/
+static unsigned int itemsinline;
+    /* The number of items in the line we are currently building */
+static unsigned int bitsinitem;
+    /* The number of bits filled so far in the item we are currently
+       building 
+    */
+static unsigned int rlebitsinitem;
+    /* The number of bits filled so far in the item we are currently
+       building 
+    */
+static unsigned int bitspersample;
+static unsigned int item, bitshift, items;
+static unsigned int rleitem, rlebitshift;
+static unsigned int repeat, itembuf[128], count, repeatitem, repeatcount;
+
+
+
+static void
+initNativeOutputEncoder(bool const rle, unsigned int const bitspersample) {
+/*----------------------------------------------------------------------------
+   Initialize the native output encoder.  Call this once per
+   Postscript image that you will write with putitem(), before for the
+   first putitem().
+
+   We initialize the item putter state variables, which are the
+   global variable defined above.
+-----------------------------------------------------------------------------*/
+    itemsinline = 0;
+    items = 0;
+
+    if (rle) {
+        rleitem = 0;
+        rlebitsinitem = 0;
+        rlebitshift = 8 - bitspersample;
+        repeat = 1;
+        count = 0;
+    } else {
+        item = 0;
+        bitsinitem = 0;
+        bitshift = 8 - bitspersample;
+    }
+
+}
+
+
+
+static void
+putitem(void) {
+    const char* const hexits = "0123456789abcdef";
+
+    if (itemsinline == 30) {
+        putchar('\n');
+        itemsinline = 0;
+    }
+    putchar(hexits[item >> 4]);
+    putchar(hexits[item & 15]);
+    ++itemsinline;
+    ++items;
+    item = 0;
+    bitsinitem = 0;
+    bitshift = 8 - bitspersample;
+}
+
+
+
+static void
+flushitem() {
+    if (bitsinitem > 0)
+        putitem();
+}
+
+
+
+static void 
+putxelval(xelval const xv) {
+    if (bitsinitem == 8)
+        putitem();
+    item += xv << bitshift;
+    bitsinitem += bitspersample;
+    bitshift -= bitspersample;
+}
+
+
+
+static void
+rleputbuffer() {
+    if (repeat) {
+        item = 256 - count;
+        putitem();
+        item = repeatitem;
+        putitem();
+    } else {
+        unsigned int i;
+    
+        item = count - 1;
+        putitem();
+        for (i = 0; i < count; ++i) {
+            item = itembuf[i];
+            putitem();
+        }
+    }
+    repeat = 1;
+    count = 0;
+}
+
+
+
+static void
+rleputitem() {
+    int i;
+
+    if ( count == 128 )
+        rleputbuffer();
+
+    if ( repeat && count == 0 )
+    { /* Still initializing a repeat buf. */
+        itembuf[count] = repeatitem = rleitem;
+        ++count;
+    }
+    else if ( repeat )
+    { /* Repeating - watch for end of run. */
+        if ( rleitem == repeatitem )
+        { /* Run continues. */
+            itembuf[count] = rleitem;
+            ++count;
+        }
+        else
+        { /* Run ended - is it long enough to dump? */
+            if ( count > 2 )
+            { /* Yes, dump a repeat-mode buffer and start a new one. */
+                rleputbuffer();
+                itembuf[count] = repeatitem = rleitem;
+                ++count;
+            }
+            else
+            { /* Not long enough - convert to non-repeat mode. */
+                repeat = 0;
+                itembuf[count] = repeatitem = rleitem;
+                ++count;
+                repeatcount = 1;
+            }
+        }
+    }
+    else
+    { /* Not repeating - watch for a run worth repeating. */
+        if ( rleitem == repeatitem )
+        { /* Possible run continues. */
+            ++repeatcount;
+            if ( repeatcount > 3 )
+            { /* Long enough - dump non-repeat part and start repeat. */
+                count = count - ( repeatcount - 1 );
+                rleputbuffer();
+                count = repeatcount;
+                for ( i = 0; i < count; ++i )
+                    itembuf[i] = rleitem;
+            }
+            else
+            { /* Not long enough yet - continue as non-repeat buf. */
+                itembuf[count] = rleitem;
+                ++count;
+            }
+        }
+        else
+        { /* Broken run. */
+            itembuf[count] = repeatitem = rleitem;
+            ++count;
+            repeatcount = 1;
+        }
+    }
+
+    rleitem = 0;
+    rlebitsinitem = 0;
+    rlebitshift = 8 - bitspersample;
+}
+
+
+
+static void 
+rleputxelval(xelval const xv) {
+    if (rlebitsinitem == 8)
+        rleputitem();
+    rleitem += xv << rlebitshift;
+    rlebitsinitem += bitspersample;
+    rlebitshift -= bitspersample;
+}
+
+
+
+static void
+rleflush() {
+    if (rlebitsinitem > 0)
+        rleputitem();
+    if (count > 0)
+        rleputbuffer();
+}
+
+
+static void
+flushNativeOutput(bool const rle) {
+    if (rle)
+        rleflush();
+    else
+        flushitem();
+    printf("\n");
+}
+        
+/*===========================================================================
+  The BMEPS output encoder.
+===========================================================================*/
+
+/* This code is just a wrapper around the output encoder that is part of
+   Bmeps, to give it better modularity.
+*/
+
+struct bmepsoe {
+    Output_Encoder * oeP;
+    int * rleBuffer;
+    Byte * flateInBuffer;
+    Byte * flateOutBuffer;
+};
+
+
+
+static void
+createBmepsOutputEncoder(struct bmepsoe ** const bmepsoePP,
+                         FILE *            const ofP,
+                         bool              const rle,
+                         bool              const flate,
+                         bool              const ascii85) {
+
+    unsigned int const FLATE_IN_SIZE = 16384;
+    unsigned int const FLATE_OUT_SIZE = 17408;
+
+    struct bmepsoe * bmepsoeP;
+    int mode;
+
+    MALLOCVAR_NOFAIL(bmepsoeP);
+    MALLOCVAR_NOFAIL(bmepsoeP->oeP);
+    MALLOCARRAY_NOFAIL(bmepsoeP->rleBuffer, 129);
+    MALLOCARRAY_NOFAIL(bmepsoeP->flateInBuffer, FLATE_IN_SIZE);
+    MALLOCARRAY_NOFAIL(bmepsoeP->flateOutBuffer, FLATE_OUT_SIZE);
+
+    mode = 0;
+    if (rle)
+        mode |= OE_RL;
+    if (flate)
+        mode |= OE_FLATE;
+    if (ascii85)
+        mode |= OE_ASC85;
+
+    oe_init(bmepsoeP->oeP, ofP, mode, 9, 
+            bmepsoeP->rleBuffer, 
+            bmepsoeP->flateInBuffer, FLATE_IN_SIZE,
+            bmepsoeP->flateOutBuffer, FLATE_OUT_SIZE);
+
+    *bmepsoePP = bmepsoeP;
+}
+
+
+
+static void
+destroyBmepsOutputEncoder(struct bmepsoe * const bmepsoeP) {
+    
+    free(bmepsoeP->rleBuffer);
+    free(bmepsoeP->flateInBuffer);
+    free(bmepsoeP->flateOutBuffer);
+    
+    free(bmepsoeP);
+}
+
+
+
+static void
+outputBmepsSample(struct bmepsoe * const bmepsoeP,
+                  unsigned int     const sampleValue,
+          unsigned int     const bitsPerSample) {
+
+    if (bitsPerSample == 8)
+        oe_byte_add(bmepsoeP->oeP, sampleValue);
+    else {
+        unsigned int m;
+
+        for (m = 1 << (bitsPerSample-1); m != 0; m >>= 1)
+            /* depends on oe_bit_add accepting any value !=0 as 1 */
+            oe_bit_add(bmepsoeP->oeP, sampleValue & m); 
+    }
+}
+
+
+
+static void
+flushBmepsOutput(struct bmepsoe * const bmepsoeP) {
+    oe_byte_flush(bmepsoeP->oeP);
+}
+
+
+/*============================================================================
+   END OF OUTPUT ENCODERS
+============================================================================*/
+
+
+static void
+computeImagePosition(int     const dpiX, 
+                     int     const dpiY, 
+                     int     const icols, 
+                     int     const irows,
+                     bool    const mustturn,
+                     bool    const canturn,
+                     bool    const center,
+                     int     const pagewid, 
+                     int     const pagehgt, 
+                     float   const requestedScale,
+                     float   const imagewidth,
+                     float   const imageheight,
+                     bool    const equalpixels,
+                     float * const scolsP,
+                     float * const srowsP,
+                     float * const llxP, 
+                     float * const llyP,
+                     bool *  const turnedP ) {
+/*----------------------------------------------------------------------------
+   Determine where on the page the image is to go.  This means position,
+   dimensions, and orientation.
+
+   icols/irows are the dimensions of the PNM input in xels.
+
+   'mustturn' means we are required to rotate the image.
+
+   'canturn' means we may rotate the image if it fits better, but don't
+   have to.
+
+   *scolsP, *srowsP are the dimensions of the image in 1/72 inch.
+
+   *llxP, *llyP are the coordinates, in 1/72 inch, of the lower left
+   corner of the image on the page.
+
+   *turnedP is true iff the image is to be rotated 90 degrees on the page.
+
+   imagewidth/imageheight are the requested dimensions of the image on
+   the page, in 1/72 inch.  Image will be as large as possible within
+   those dimensions.  Zero means unspecified, so 'scale', 'pagewid',
+   'pagehgt', 'irows', and 'icols' determine image size.
+
+   'equalpixels' means the user wants one printed pixel per input pixel.
+   It is inconsistent with imagewidth or imageheight != 0
+
+   'requestedScale' is meaningful only when imageheight/imagewidth == 0
+   and equalpixels == FALSE.  It tells how many inches the user wants
+   72 pixels of input to occupy, if it fits on the page.
+-----------------------------------------------------------------------------*/
+    int cols, rows;
+        /* Number of columns, rows of input xels in the output, as
+           rotated if applicable
+        */
+    bool shouldturn;  /* The image fits the page better if we turn it */
+    
+    if (icols > irows && pagehgt > pagewid)
+        shouldturn = TRUE;
+    else if (irows > icols && pagewid > pagehgt)
+        shouldturn = TRUE;
+    else
+        shouldturn = FALSE;
+
+    if (mustturn || (canturn && shouldturn)) {
+        *turnedP = TRUE;
+        cols = irows;
+        rows = icols;
+    } else {
+        *turnedP = FALSE;
+        cols = icols;
+        rows = irows;
+    }
+    if (equalpixels) {
+        *scolsP = (72.0/dpiX)*cols;
+        *srowsP = (72.0/dpiY)*rows;
+    } else if (imagewidth > 0 || imageheight > 0) {
+        float scale;
+
+        if (imagewidth == 0)
+            scale = (float) imageheight/rows;
+        else if (imageheight == 0)
+            scale = (float) imagewidth/cols;
+        else
+            scale = MIN((float)imagewidth/cols, (float)imageheight/rows);
+        
+        *scolsP = cols*scale;
+        *srowsP = rows*scale;
+    } else {
+        /* He didn't give us a bounding box for the image so figure
+           out output image size from other inputs.
+        */
+        const int devpixX = dpiX / 72.0 + 0.5;        
+        const int devpixY = dpiY / 72.0 + 0.5;        
+            /* How many device pixels make up 1/72 inch, rounded to
+               nearest integer */
+        const float pixfacX = 72.0 / dpiX * devpixX;  /* 1, approx. */
+        const float pixfacY = 72.0 / dpiY * devpixY;  /* 1, approx. */
+        float scale;
+
+        scale = MIN(requestedScale, 
+                    MIN((float)pagewid/cols, (float)pagehgt/rows));
+
+        *scolsP = scale * cols * pixfacX;
+        *srowsP = scale * rows * pixfacY;
+        
+        if (scale != requestedScale)
+            pm_message("warning, image too large for page, rescaling to %g", 
+                       scale );
+
+        /* Before May 2001, Pnmtops enforced a 5% margin around the page.
+           If the image would be too big to leave a 5% margin, Pnmtops would
+           scale it down.  But people have images that are exactly the size
+           of a page, e.g. because they created them with Sane's 'scanimage'
+           program from a full page of input.  So we removed the gratuitous
+           5% margin.  -Bryan.
+        */
+    }
+    *llxP = (center) ? ( pagewid - *scolsP ) / 2 : 0;
+    *llyP = (center) ? ( pagehgt - *srowsP ) / 2 : 0;
+
+
+    if (verbose)
+        pm_message("Image will be %3.2f points wide by %3.2f points high, "
+                   "left edge %3.2f points from left edge of page, "
+                   "bottom edge %3.2f points from top of page; "
+                   "%sturned to landscape orientation",
+                   *scolsP, *srowsP, *llxP, *llyP, *turnedP ? "" : "NOT ");
+}
+
+
+
+static void
+determineDictionaryRequirement(bool           const userWantsDict,
+                               bool           const psFilter,
+                               unsigned int * const dictSizeP) {
+
+    if (userWantsDict) {
+        if (psFilter) {
+            /* The Postscript this program generates to use built-in
+               Postscript filters does not define any variables.
+            */
+            *dictSizeP = 0;
+        } else
+            *dictSizeP = 8;
+    } else
+        *dictSizeP = 0;
+}
+
+
+
+static void
+defineReadstring(bool const rle) {
+/*----------------------------------------------------------------------------
+   Write to Standard Output Postscript statements to define /readstring.
+-----------------------------------------------------------------------------*/
+    if (rle) {
+        printf("/rlestr1 1 string def\n");
+        printf("/readrlestring {\n");             /* s -- nr */
+        printf("  /rlestr exch def\n");           /* - */
+        printf("  currentfile rlestr1 readhexstring pop\n");  /* s1 */
+        printf("  0 get\n");                  /* c */
+        printf("  dup 127 le {\n");               /* c */
+        printf("    currentfile rlestr 0\n");         /* c f s 0 */
+        printf("    4 3 roll\n");             /* f s 0 c */
+        printf("    1 add  getinterval\n");           /* f s */
+        printf("    readhexstring pop\n");            /* s */
+        printf("    length\n");               /* nr */
+        printf("  } {\n");                    /* c */
+        printf("    256 exch sub dup\n");         /* n n */
+        printf("    currentfile rlestr1 readhexstring pop\n");/* n n s1 */
+        printf("    0 get\n");                /* n n c */
+        printf("    exch 0 exch 1 exch 1 sub {\n");       /* n c 0 1 n-1*/
+        printf("      rlestr exch 2 index put\n");
+        printf("    } for\n");                /* n c */
+        printf("    pop\n");                  /* nr */
+        printf("  } ifelse\n");               /* nr */
+        printf("} bind def\n");
+        printf("/readstring {\n");                /* s -- s */
+        printf("  dup length 0 {\n");             /* s l 0 */
+        printf("    3 copy exch\n");              /* s l n s n l*/
+        printf("    1 index sub\n");              /* s l n s n r*/
+        printf("    getinterval\n");              /* s l n ss */
+        printf("    readrlestring\n");            /* s l n nr */
+        printf("    add\n");                  /* s l n */
+        printf("    2 copy le { exit } if\n");        /* s l n */
+        printf("  } loop\n");                 /* s l l */
+        printf("  pop pop\n");                /* s */
+        printf("} bind def\n");
+    } else {
+        printf("/readstring {\n");                /* s -- s */
+        printf("  currentfile exch readhexstring pop\n");
+        printf("} bind def\n");
+    }
+}
+
+
+
+static void
+setupReadstringNative(bool         const rle,
+                      bool         const color,
+                      unsigned int const icols, 
+                      unsigned int const padright, 
+                      unsigned int const bps) {
+/*----------------------------------------------------------------------------
+   Write to Standard Output statements to define /readstring and also
+   arguments for it (/picstr or /rpicstr, /gpicstr, and /bpicstr).
+-----------------------------------------------------------------------------*/
+    unsigned int const bytesPerRow = (icols + padright) * bps / 8;
+
+    defineReadstring(rle);
+    
+    if (color) {
+        printf("/rpicstr %d string def\n", bytesPerRow);
+        printf("/gpicstr %d string def\n", bytesPerRow);
+        printf("/bpicstr %d string def\n", bytesPerRow);
+    } else
+        printf("/picstr %d string def\n", bytesPerRow);
+}
+
+
+
+static void
+putFilters(unsigned int const postscriptLevel,
+           bool         const rle,
+           bool         const flate,
+           bool         const ascii85,
+           bool         const color) {
+
+    assert(postscriptLevel > 1);
+    
+    if (ascii85)
+        printf("/ASCII85Decode filter ");
+    else 
+        printf("/ASCIIHexDecode filter ");
+    if (flate)
+        printf("/FlateDecode filter ");
+    if (rle) /* bmeps encodes rle before flate, so must decode after! */
+        printf("/RunLengthDecode filter ");
+}
+
+
+
+static void
+putReadstringNative(bool const color) {
+
+    if (color) {
+        printf("{ rpicstr readstring }\n");
+        printf("{ gpicstr readstring }\n");
+        printf("{ bpicstr readstring }\n");
+    } else
+        printf("{ picstr readstring }\n");
+}
+
+
+
+static void
+putSetup(unsigned int const dictSize,
+         bool         const psFilter,
+         bool         const rle,
+         bool         const color,
+         unsigned int const icols,
+         unsigned int const padright,
+         unsigned int const bps) {
+/*----------------------------------------------------------------------------
+   Put the setup section in the Postscript program on Standard Output.
+-----------------------------------------------------------------------------*/
+    printf("%%%%BeginSetup\n");
+
+    if (dictSize > 0)
+        /* inputf {r,g,b,}pictsr readstring readrlestring rlestring */
+        printf("%u dict begin\n", dictSize);
+    
+    if (!psFilter)
+        setupReadstringNative(rle, color, icols, padright, bps);
+
+    printf("%%%%EndSetup\n");
+}
+
+
+
+static void
+putImage(bool const psFilter,
+         bool const color) {
+/*----------------------------------------------------------------------------
+   Put the image/colorimage statement in the Postscript program on
+   Standard Output.
+-----------------------------------------------------------------------------*/
+    if (color) {
+        if (psFilter)
+            printf("false 3\n");
+        else
+            printf("true 3\n");
+        printf("colorimage");
+    } else
+        printf("image");
+}
+
+
+
+static void
+putInitPsFilter(unsigned int const postscriptLevel,
+                bool         const rle,
+                bool         const flate,
+                bool         const ascii85,
+                bool         const color) {
+
+    bool const filterTrue = TRUE;
+
+    printf("{ currentfile ");
+
+    putFilters(postscriptLevel, rle, flate, ascii85, color);
+
+    putImage(filterTrue, color);
+    
+    printf(" } exec");
+}
+
+
+
+static void
+putInitReadstringNative(bool const color) {
+
+    bool const filterFalse = FALSE;
+
+    putReadstringNative(color);
+    
+    putImage(filterFalse, color);
+}
+
+
+
+static void
+putInit(unsigned int const postscriptLevel,
+        char         const name[], 
+        int          const icols, 
+        int          const irows, 
+        float        const scols, 
+        float        const srows,
+        float        const llx, 
+        float        const lly,
+        int          const padright, 
+        int          const bps,
+        int          const pagewid, 
+        int          const pagehgt,
+        bool         const color, 
+        bool         const turned, 
+        bool         const rle,
+        bool         const flate,
+        bool         const ascii85,
+        bool         const setpage,
+        bool         const psFilter,
+        unsigned int const dictSize) {
+/*----------------------------------------------------------------------------
+   Write out to Standard Output the headers stuff for the Postscript
+   program (everything up to the raster).
+-----------------------------------------------------------------------------*/
+    /* The numbers in the %! line often confuse people. They are NOT the
+       PostScript language level.  The first is the level of the DSC comment
+       spec being adhered to, the second is the level of the EPSF spec being
+       adhered to.  It is *incorrect* to claim EPSF compliance if the file
+       contains a setpagedevice.
+    */
+    printf("%%!PS-Adobe-3.0%s\n", setpage ? "" : " EPSF-3.0");
+    printf("%%%%LanguageLevel: %u\n", postscriptLevel);
+    printf("%%%%Creator: pnmtops\n");
+    printf("%%%%Title: %s.ps\n", name);
+    printf("%%%%Pages: 1\n");
+    printf(
+        "%%%%BoundingBox: %d %d %d %d\n",
+        (int) llx, (int) lly,
+        (int) (llx + scols + 0.5), (int) (lly + srows + 0.5));
+    printf("%%%%EndComments\n");
+
+    putSetup(dictSize, psFilter, rle, color, icols, padright, bps);
+
+    printf("%%%%Page: 1 1\n");
+    if (setpage)
+        printf("<< /PageSize [ %d %d ] /ImagingBBox null >> setpagedevice\n",
+               pagewid, pagehgt);
+    printf("gsave\n");
+    printf("%g %g translate\n", llx, lly);
+    printf("%g %g scale\n", scols, srows);
+    if (turned)
+        printf("0.5 0.5 translate  90 rotate  -0.5 -0.5 translate\n");
+    printf("%d %d %d\n", icols, irows, bps);
+    printf("[ %d 0 0 -%d 0 %d ]\n", icols, irows, irows);
+
+    if (psFilter)
+        putInitPsFilter(postscriptLevel, rle, flate, ascii85, color);
+    else
+        putInitReadstringNative(color);
+
+    printf("\n");
+}
+
+
+
+static void
+putEnd(bool         const showpage, 
+       bool         const psFilter,
+       bool         const ascii85,
+       unsigned int const dictSize,
+       bool         const vmreclaim) {
+
+    if (psFilter) {
+        if (ascii85)
+            printf("%s\n", "~>");
+        else
+            printf("%s\n", ">");
+    } else {
+        printf("currentdict /inputf undef\n");
+        printf("currentdict /picstr undef\n");
+        printf("currentdict /rpicstr undef\n");
+        printf("currentdict /gpicstr undef\n");
+        printf("currentdict /bpicstr undef\n");
+    }
+
+    if (dictSize > 0)
+        printf("end\n");
+
+    if (vmreclaim)
+        printf("1 vmreclaim\n");
+
+    printf("grestore\n");
+
+    if (showpage)
+        printf("showpage\n");
+    printf("%%%%Trailer\n");
+}
+
+
+
+static void
+computeDepth(xelval         const inputMaxval,
+             unsigned int   const postscriptLevel, 
+             unsigned int * const bitspersampleP,
+             unsigned int * const psMaxvalP) {
+/*----------------------------------------------------------------------------
+   Figure out how many bits will represent each sample in the Postscript
+   program, and the maxval of the Postscript program samples.  The maxval
+   is just the maximum value allowable in the number of bits.
+-----------------------------------------------------------------------------*/
+    unsigned int const bitsRequiredByMaxval = pm_maxvaltobits(inputMaxval);
+
+    if (bitsRequiredByMaxval <= 1)
+        *bitspersampleP = 1;
+    else if (bitsRequiredByMaxval <= 2)
+        *bitspersampleP = 2;
+    else if (bitsRequiredByMaxval <= 4)
+        *bitspersampleP = 4;
+    else if (bitsRequiredByMaxval <= 8 || postscriptLevel < 2)
+        *bitspersampleP = 8;
+    else
+        *bitspersampleP = 12;
+
+    if (*bitspersampleP < bitsRequiredByMaxval)
+        pm_message("Maxval of input requires %u bit samples for full "
+                   "resolution, but we are using the Postscript level %u "
+                   "maximum of %u",
+                   bitsRequiredByMaxval, postscriptLevel, *bitspersampleP);
+
+    *psMaxvalP = pm_bitstomaxval(*bitspersampleP);
+
+    if (verbose)
+        pm_message("Input maxval is %u.  Postscript raster will have "
+                   "%u bits per sample, so maxval = %u",
+                   inputMaxval, *bitspersampleP, *psMaxvalP);
+}    
+
+
+
+static void
+convertRowNative(struct pam * const pamP, 
+                 tuple *      const tuplerow, 
+                 unsigned int const psMaxval, 
+                 bool         const rle, 
+                 unsigned int const padright) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        unsigned int col;
+        for (col= 0; col < pamP->width; ++col) {
+            sample const scaledSample = 
+                pnm_scalesample(tuplerow[col][plane], pamP->maxval, psMaxval);
+
+            if (rle)
+                rleputxelval(scaledSample);
+            else
+                putxelval(scaledSample);
+        }
+        for (col = 0; col < padright; ++col)
+            if (rle)
+                rleputxelval(0);
+        else
+            putxelval(0);
+        if (rle)
+            rleflush();
+    }
+}
+
+
+
+static void
+convertRowPsFilter(struct pam *     const pamP,
+                   tuple *          const tuplerow,
+                   struct bmepsoe * const bmepsoeP,
+                   unsigned int     const psMaxval) {
+
+    unsigned int const bitsPerSample = pm_maxvaltobits(psMaxval);
+    unsigned int const stragglers =
+        (((bitsPerSample * pamP->depth) % 8) * pamP->width) % 8;
+        /* Number of bits at the right edge that don't fill out a
+           whole byte
+        */
+
+    unsigned int col;
+    tuple scaledTuple;
+    
+    scaledTuple = pnm_allocpamtuple(pamP);
+
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        pnm_scaletuple(pamP, scaledTuple, tuplerow[col], psMaxval);
+        
+        for (plane = 0; plane < pamP->depth; ++plane)
+            outputBmepsSample(bmepsoeP, scaledTuple[plane], bitsPerSample);
+    }
+    if (stragglers > 0) {
+        unsigned int i;
+        for (i = stragglers; i < 8; ++i)
+            oe_bit_add(bmepsoeP->oeP, 0);
+    }
+    pnm_freepamtuple(scaledTuple);
+}
+
+
+
+static void
+selectPostscriptLevel(bool           const levelIsGiven,
+                      unsigned int   const levelGiven,
+                      bool           const color,
+                      bool           const dict,
+                      bool           const flate,
+                      bool           const ascii85,
+                      bool           const psFilter,
+                      unsigned int * const postscriptLevelP) {
+
+    unsigned int const maxPermittedLevel = 
+        levelIsGiven ? levelGiven : UINT_MAX;
+    unsigned int minPossibleLevel;
+
+    /* Until we know, later in this function, that we needs certain
+       features, we assume we can get by with classic Postscript Level 1:
+    */
+    minPossibleLevel = 1;
+
+    /* Now we increase 'minPossibleLevel' as we notice that each of
+       various features are required:
+    */
+    if (color) {
+        minPossibleLevel = MAX(minPossibleLevel, 2);
+        if (2 > maxPermittedLevel)
+            pm_error("Color requires at least Postscript level 2");
+    }
+    if (flate) {
+        minPossibleLevel = MAX(minPossibleLevel, 3);
+        if (2 > maxPermittedLevel)
+            pm_error("flate compression requires at least Postscript level 3");
+    }
+    if (ascii85) {
+        minPossibleLevel = MAX(minPossibleLevel, 2);
+        if (2 > maxPermittedLevel)
+            pm_error("ascii85 encoding requires at least Postscript level 2");
+    }
+    if (psFilter) {
+        minPossibleLevel = MAX(minPossibleLevel, 2);
+        if (2 > maxPermittedLevel)
+            pm_error("-psfilter requires at least Postscript level 2");
+    }
+    if (levelIsGiven)
+        *postscriptLevelP = levelGiven;
+    else
+        *postscriptLevelP = minPossibleLevel;
+}
+
+
+
+static void
+convertPage(FILE * const ifP, 
+            int    const turnflag, 
+            int    const turnokflag, 
+            bool   const psFilter,
+            bool   const rle, 
+            bool   const flate,
+            bool   const ascii85,
+            bool   const setpage,
+            bool   const showpage,
+            bool   const center, 
+            float  const scale,
+            int    const dpiX, 
+            int    const dpiY, 
+            int    const pagewid, 
+            int    const pagehgt,
+            int    const imagewidth, 
+            int    const imageheight, 
+            bool   const equalpixels,
+            char   const name[],
+            bool   const dict,
+            bool   const vmreclaim,
+            bool   const levelIsGiven,
+            bool   const levelGiven) {
+    
+    struct pam inpam;
+    tuple* tuplerow;
+    unsigned int padright;
+        /* Number of bits we must add to the right end of each Postscript
+           output line in order to have an integral number of bytes of output.
+           E.g. at 2 bits per sample with 10 columns, this would be 4.
+        */
+    int row;
+    unsigned int psMaxval;
+        /* The maxval of the Postscript program */
+    float scols, srows;
+    float llx, lly;
+    bool turned;
+    bool color;
+    unsigned int postscriptLevel;
+    struct bmepsoe * bmepsoeP;
+    unsigned int dictSize;
+        /* Size of Postscript dictionary we should define */
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+    
+    if (!STRSEQ(inpam.tuple_type, PAM_PBM_TUPLETYPE) &&
+        !STRSEQ(inpam.tuple_type, PAM_PGM_TUPLETYPE) &&
+        !STRSEQ(inpam.tuple_type, PAM_PPM_TUPLETYPE))
+        pm_error("Unrecognized tuple type %s.  This program accepts only "
+                 "PBM, PGM, PPM, and equivalent PAM input images", 
+                 inpam.tuple_type);
+
+    color = STRSEQ(inpam.tuple_type, PAM_PPM_TUPLETYPE);
+    
+    selectPostscriptLevel(levelIsGiven, levelGiven, color, 
+                          dict, flate, ascii85, psFilter, &postscriptLevel);
+    
+    if (color)
+        pm_message("generating color Postscript program.");
+
+    computeDepth(inpam.maxval, postscriptLevel, &bitspersample, &psMaxval);
+    {
+        unsigned int const realBitsPerLine = inpam.width * bitspersample;
+        unsigned int const paddedBitsPerLine = ((realBitsPerLine + 7) / 8) * 8;
+        padright = (paddedBitsPerLine - realBitsPerLine) / bitspersample;
+    }
+    /* In positioning/scaling the image, we treat the input image as if
+       it has a density of 72 pixels per inch.
+    */
+    computeImagePosition(dpiX, dpiY, inpam.width, inpam.height, 
+                         turnflag, turnokflag, center,
+                         pagewid, pagehgt, scale, imagewidth, imageheight,
+                         equalpixels,
+                         &scols, &srows, &llx, &lly, &turned);
+
+    determineDictionaryRequirement(dict, psFilter, &dictSize);
+    
+    putInit(postscriptLevel, name, inpam.width, inpam.height, 
+            scols, srows, llx, lly, padright, bitspersample, 
+            pagewid, pagehgt, color,
+            turned, rle, flate, ascii85, setpage, psFilter, dictSize);
+
+    createBmepsOutputEncoder(&bmepsoeP, stdout, rle, flate, ascii85);
+    initNativeOutputEncoder(rle, bitspersample);
+
+    tuplerow = pnm_allocpamrow(&inpam);
+
+    for (row = 0; row < inpam.height; ++row) {
+        pnm_readpamrow(&inpam, tuplerow);
+        if (psFilter)
+            convertRowPsFilter(&inpam, tuplerow, bmepsoeP, psMaxval);
+        else
+            convertRowNative(&inpam, tuplerow, psMaxval, rle, padright);
+    }
+
+    pnm_freepamrow(tuplerow);
+
+    if (psFilter)
+        flushBmepsOutput(bmepsoeP);
+    else
+        flushNativeOutput(rle);
+
+    destroyBmepsOutputEncoder(bmepsoeP);
+
+    putEnd(showpage, psFilter, ascii85, dictSize, vmreclaim);
+}
+
+
+
+static const char *
+basebasename(const char * const filespec) {
+/*----------------------------------------------------------------------------
+    Return filename up to first period
+-----------------------------------------------------------------------------*/
+    char const dirsep = '/';
+    const char * const lastSlashPos = strrchr(filespec, dirsep);
+
+    char * name;
+    const char * filename;
+
+    if (lastSlashPos)
+        filename = lastSlashPos + 1;
+    else
+        filename = filespec;
+
+    name = strdup(filename);
+    if (name != NULL) {
+        char * const dotPosition = strchr(name, '.');
+
+        if (dotPosition)
+            *dotPosition = '\0';
+    }
+    return name;
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    FILE* ifp;
+    const char *name;  /* malloc'ed */
+    struct cmdlineInfo cmdline;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    verbose = cmdline.verbose;
+
+    ifp = pm_openr(cmdline.inputFileName);
+
+    if (STREQ(cmdline.inputFileName, "-"))
+        name = strdup("noname");
+    else
+        name = basebasename(cmdline.inputFileName);
+    {
+        int eof;  /* There are no more images in the input file */
+        unsigned int imageSeq;
+
+        /* I don't know if this works at all for multi-image PNM input.
+           Before July 2000, it ignored everything after the first image,
+           so this probably is at least as good -- it should be identical
+           for a single-image file, which is the only kind which was legal
+           before July 2000.
+
+           Maybe there needs to be some per-file header and trailers stuff
+           in the Postscript program, with some per-page header and trailer
+           stuff inside.  I don't know Postscript.  - Bryan 2000.06.19.
+        */
+
+        eof = FALSE;  /* There is always at least one image */
+        for (imageSeq = 0; !eof; ++imageSeq) {
+            convertPage(ifp, cmdline.mustturn, cmdline.canturn, 
+                        cmdline.psfilter,
+                        cmdline.rle, cmdline.flate, cmdline.ascii85, 
+                        cmdline.setpage, cmdline.showpage,
+                        cmdline.center, cmdline.scale,
+                        cmdline.dpiX, cmdline.dpiY,
+                        cmdline.width, cmdline.height, 
+                        cmdline.imagewidth, cmdline.imageheight, 
+                        cmdline.equalpixels, name, 
+                        cmdline.dict, cmdline.vmreclaim,
+                        cmdline.levelSpec, cmdline.level);
+            pnm_nextimage(ifp, &eof);
+        }
+    }
+    strfree(name);
+
+    pm_close(ifp);
+    
+    return 0;
+}
+
+
+
+/*
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+**
+** -nocenter option added November 1993 by Wolfgang Stuerzlinger,
+**  wrzl@gup.uni-linz.ac.at.
+**
+*/
diff --git a/converter/other/pnmtorast.c b/converter/other/pnmtorast.c
new file mode 100644
index 00000000..7d1ae05a
--- /dev/null
+++ b/converter/other/pnmtorast.c
@@ -0,0 +1,310 @@
+/* pnmtorast.c - read a portable anymap and produce a Sun rasterfile
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pnm.h"
+#include "rast.h"
+#include "mallocvar.h"
+
+#define MAXCOLORS 256
+static colormap_t* make_pr_colormap ARGS(( colorhist_vector chv, int colors ));
+static colormap_t* make_gray_pr_colormap ARGS(( void ));
+static colormap_t* alloc_pr_colormap ARGS(( void ));
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+{
+    FILE* ifp;
+    xel** xels;
+    xel* xelrow;
+    xel p;
+    register xel* xP;
+    colorhist_vector chv;
+    colorhash_table cht;
+    colormap_t* pr_colormapP;
+    int argn, pr_type, rows, cols, format, i;
+    int depth, colors, linesize, row;
+    register int col, bitcount;
+    xelval maxval;
+    struct pixrect* pr;
+    unsigned char* data;
+    register unsigned char* byteP;
+    const char* const usage = "[-standard|-rle] [pnmfile]";
+
+    pnm_init( &argc, argv );
+
+    argn = 1;
+    pr_type = RT_BYTE_ENCODED;
+
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+    {
+        if ( pm_keymatch( argv[argn], "-standard", 2 ) )
+            pr_type = RT_STANDARD;
+        else if ( pm_keymatch( argv[argn], "-rle", 2 ) )
+            pr_type = RT_BYTE_ENCODED;
+        else
+            pm_usage( usage );
+        ++argn;
+    }
+
+    if ( argn != argc )
+    {
+        ifp = pm_openr( argv[argn] );
+        ++argn;
+    }
+    else
+        ifp = stdin;
+
+    if ( argn != argc )
+        pm_usage( usage );
+
+    xels = pnm_readpnm( ifp, &cols, &rows, &maxval, &format );
+
+    pm_close( ifp );
+
+    /* Figure out the proper depth and colormap. */
+    switch ( PNM_FORMAT_TYPE(format) )
+    {
+    case PPM_TYPE:
+        pm_message( "computing colormap..." );
+        chv = ppm_computecolorhist( xels, cols, rows, MAXCOLORS, &colors );
+        if ( chv == (colorhist_vector) 0 )
+        {
+            pm_message(
+                "Too many colors - proceeding to write a 24-bit non-mapped" );
+            pm_message(
+                "rasterfile.  If you want 8 bits, try doing a 'pnmquant %d'.",
+                MAXCOLORS );
+            depth = 24;
+            pr_type = RT_STANDARD;
+            pr_colormapP = (colormap_t*) 0;
+        }
+        else
+        {
+            pm_message( "%d colors found", colors );
+
+            if ( maxval != 255 )
+                for ( i = 0; i < colors; ++i )
+                    PPM_DEPTH( chv[i].color, chv[i].color, maxval, 255 );
+
+            /* Force white to slot 0 and black to slot 1, if possible. */
+            PPM_ASSIGN( p, 255, 255, 255 );
+            ppm_addtocolorhist( chv, &colors, MAXCOLORS, &p, 0, 0 );
+            PPM_ASSIGN( p, 0, 0, 0 );
+            ppm_addtocolorhist( chv, &colors, MAXCOLORS, &p, 0, 1 );
+
+            if ( colors == 2 )
+            {
+                /* Monochrome. */
+                depth = 1;
+                pr_colormapP = (colormap_t*) 0;
+            }
+            else
+            {
+                /* Turn the ppm colormap into the appropriate Sun colormap. */
+                depth = 8;
+                pr_colormapP = make_pr_colormap( chv, colors );
+            }
+            cht = ppm_colorhisttocolorhash( chv, colors );
+            ppm_freecolorhist( chv );
+        }
+
+        break;
+
+    case PGM_TYPE:
+        depth = 8;
+        pr_colormapP = make_gray_pr_colormap( );
+        break;
+
+    default:
+        depth = 1;
+        pr_colormapP = (colormap_t*) 0;
+        break;
+    }
+
+    if ( maxval > 255 && depth != 1 )
+        pm_message(
+            "maxval is not 255 - automatically rescaling colors" );
+    
+    /* Allocate space for the Sun-format image. */
+    if ( (pr = mem_create(cols, rows, depth)) == (struct pixrect*) 0 )
+        pm_error( "unable to create new pixrect" );
+    data = ( (struct mpr_data*) pr->pr_data )->md_image;
+    linesize = ( (struct mpr_data*) pr->pr_data )->md_linebytes;
+
+    /* And compute the Sun image.  The variables at this point are:
+    **   cht is null or not
+    **   depth is 1, 8, or 24
+    */
+    for ( row = 0; row < rows; ++row )
+    {
+        xelrow = xels[row];
+        byteP = data;
+        switch ( depth )
+        {
+        case 1:
+            *byteP = 0;
+            bitcount = 7;
+            for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+            {
+                register int color;
+
+                switch ( PNM_FORMAT_TYPE(format) )
+                {
+                case PPM_TYPE:
+                    if ( maxval != 255 )
+                        PPM_DEPTH( *xP, *xP, maxval, 255 );
+                    color = ppm_lookupcolor( cht, xP );
+                    if ( color == -1 )
+                        pm_error(
+                            "color not found?!?  row=%d col=%d  r=%d g=%d b=%d",
+                            row, col, PPM_GETR(*xP), PPM_GETG(*xP),
+                            PPM_GETB(*xP) );
+                    if ( color )
+                        *byteP |= 1 << bitcount;
+                    break;
+
+                default:
+                    color = PNM_GET1( *xP );
+                    if ( ! color )
+                        *byteP |= 1 << bitcount;
+                    break;
+                }
+                --bitcount;
+                if ( bitcount < 0 )
+                {
+                    ++byteP;
+                    *byteP = 0;
+                    bitcount = 7;
+                }
+            }
+            break;
+
+        case 8:
+            for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+            {
+                register int color;
+
+                switch ( PNM_FORMAT_TYPE(format) )
+                {
+                case PPM_TYPE:
+                    if ( maxval != 255 )
+                        PPM_DEPTH( *xP, *xP, maxval, 255 );
+                    color = ppm_lookupcolor( cht, xP );
+                    if ( color == -1 )
+                        pm_error(
+                            "color not found?!?  row=%d col=%d  r=%d g=%d b=%d",
+                            row, col, PPM_GETR(*xP), PPM_GETG(*xP),
+                            PPM_GETB(*xP) );
+                    break;
+
+                case PGM_TYPE:
+                    color = PNM_GET1( *xP );
+                    if ( maxval != 255 )
+                        color = color * 255 / maxval;
+                    break;
+
+                default:
+                    color = PNM_GET1( *xP );
+                }
+                *byteP++ = color;
+            }
+            break;
+
+        case 24:
+            /* If depth is 24, we do NOT have a valid cht. */
+            for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+            {
+                if ( maxval != 255 )
+                    PPM_DEPTH( *xP, *xP, maxval, 255 );
+                *byteP++ = PPM_GETB( *xP );
+                *byteP++ = PPM_GETG( *xP );
+                *byteP++ = PPM_GETR( *xP );
+            }
+            break;
+
+        default:
+            pm_error( "can't happen" );
+        }
+        data += linesize;
+    }
+    pnm_freearray( xels, rows );
+
+    /* Finally, write the sucker out. */
+    if ( pr_dump( pr, stdout, pr_colormapP, pr_type, 0 ) == PIX_ERR )
+        pm_error( "error writing rasterfile" );
+
+    exit( 0 );
+}
+
+static colormap_t*
+make_pr_colormap( chv, colors )
+    colorhist_vector chv;
+    int colors;
+{
+    colormap_t* pr_colormapP;
+    int i;
+
+    pr_colormapP = alloc_pr_colormap( );
+
+    for ( i = 0; i < colors; ++i )
+    {
+        pr_colormapP->map[0][i] = PPM_GETR( chv[i].color );
+        pr_colormapP->map[1][i] = PPM_GETG( chv[i].color );
+        pr_colormapP->map[2][i] = PPM_GETB( chv[i].color );
+    }
+    for ( ; i < MAXCOLORS; ++i )
+        pr_colormapP->map[0][i] = pr_colormapP->map[1][i] =
+            pr_colormapP->map[2][i] = 0;
+
+    return pr_colormapP;
+}
+
+static colormap_t*
+make_gray_pr_colormap( )
+{
+    colormap_t* pr_colormapP;
+    int i;
+
+    pr_colormapP = alloc_pr_colormap( );
+
+    for ( i = 0; i < MAXCOLORS; ++i )
+    {
+        pr_colormapP->map[0][i] = i;
+        pr_colormapP->map[1][i] = i;
+        pr_colormapP->map[2][i] = i;
+    }
+
+    return pr_colormapP;
+}
+
+static colormap_t*
+alloc_pr_colormap( )
+{
+    colormap_t* pr_colormapP;
+
+    MALLOCVAR(pr_colormapP);
+    if ( pr_colormapP == NULL )
+        pm_error( "out of memory" );
+    pr_colormapP->type = RMT_EQUAL_RGB;
+    pr_colormapP->length = MAXCOLORS;
+    MALLOCARRAY(pr_colormapP->map[0], MAXCOLORS);
+    MALLOCARRAY(pr_colormapP->map[1], MAXCOLORS);
+    MALLOCARRAY(pr_colormapP->map[2], MAXCOLORS);
+    if ( pr_colormapP->map[0] == NULL || 
+         pr_colormapP->map[1] == NULL ||
+         pr_colormapP->map[2] == NULL )
+        pm_error( "out of memory" );
+
+    return pr_colormapP;
+}
diff --git a/converter/other/pnmtorle.c b/converter/other/pnmtorle.c
new file mode 100644
index 00000000..8c6dc589
--- /dev/null
+++ b/converter/other/pnmtorle.c
@@ -0,0 +1,267 @@
+/*
+ * This is derived from the file of the same name dated June 5, 1995,
+ * copied from the Army High Performance Computing Research Center's
+ * media-tools.tar.gz package, received from 
+ * http://www.arc.umn.edu/gvl-software/media-tools.tar.gz on 2000.04.13.
+ *
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is
+ * preserved on all copies.
+ *
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ */
+/*
+ * pnmtorle - A program which will convert pbmplus (ppm or pgm) images
+ *            to Utah's "rle" image format.
+ *
+ * Author:      Wes Barris (wes@msc.edu)
+ *              AHPCRC
+ *              Minnesota Supercomputer Center, Inc.
+ * Date:        March 30, 1994
+ * Copyright (c) Minnesota Supercomputer Center, Inc.
+ * 
+ * 2000.04.13 adapted for Netpbm by Bryan Henderson.  Quieted compiler 
+ *            warnings.
+ *
+ */
+/*-----------------------------------------------------
+ * System includes.
+ */
+#include <string.h>
+#include <stdio.h>
+#include "pnm.h"
+#include "mallocvar.h"
+#include "rle.h"
+
+#define VPRINTF if (verbose || header) fprintf
+
+typedef unsigned char U_CHAR;
+/*
+ * Global variables.
+ */
+static FILE    *fp;
+static rle_hdr hdr;
+static int  format;
+static int width, height;
+static int verbose = 0, header = 0, do_alpha = 0;
+static gray    maxval;
+/*-----------------------------------------------------------------------------
+ *                                        Read the pnm image file header.
+ */
+static void 
+read_pnm_header()
+{
+    pnm_readpnminit(fp, &width, &height, &maxval, &format);
+    switch (format) {
+    case PBM_FORMAT:
+        VPRINTF(stderr, "Image type: plain pbm format\n");
+        break;
+    case RPBM_FORMAT:
+        VPRINTF(stderr, "Image type: raw pbm format\n");
+        break;
+    case PGM_FORMAT:
+        VPRINTF(stderr, "Image type: plain pgm format\n");
+        break;
+    case RPGM_FORMAT:
+        VPRINTF(stderr, "Image type: raw pgm format\n");
+        break;
+    case PPM_FORMAT:
+        VPRINTF(stderr, "Image type: plain ppm format\n");
+        break;
+    case RPPM_FORMAT:
+        VPRINTF(stderr, "Image type: raw ppm format\n");
+        break;
+    }
+    VPRINTF(stderr, "Full image: %dx%d\n", width, height);
+    VPRINTF(stderr, "Maxval:     %d\n", maxval);
+    if (do_alpha)
+        VPRINTF(stderr, "Computing alpha channel...\n");
+}
+/*-----------------------------------------------------------------------------
+ *                                             Write the rle image file header.
+ */
+static void 
+write_rle_header()
+{
+    hdr.xmin    = 0;
+    hdr.xmax    = width-1;
+    hdr.ymin    = 0;
+    hdr.ymax    = height-1;
+    hdr.background = 0;
+    switch (format) {
+    case PBM_FORMAT:
+    case RPBM_FORMAT:
+    case PGM_FORMAT:
+    case RPGM_FORMAT:
+        hdr.ncolors = 1;
+        RLE_SET_BIT(hdr, RLE_RED);
+        break;
+    case PPM_FORMAT:
+    case RPPM_FORMAT:
+        hdr.ncolors = 3;
+        RLE_SET_BIT(hdr, RLE_RED);
+        RLE_SET_BIT(hdr, RLE_GREEN);
+        RLE_SET_BIT(hdr, RLE_BLUE);
+        break;
+    }
+    if (do_alpha) {
+        hdr.alpha = 1;
+        RLE_SET_BIT(hdr, RLE_ALPHA);
+    }
+    rle_put_setup(&hdr);
+}
+/*-----------------------------------------------------------------------------
+ *                                      Write the rle data portion of the file.
+ */
+static void 
+write_rle_data()
+{
+    register int     x;
+    register int     scan;
+    register xel     *xelrow, *pP;
+    rle_pixel        ***scanlines, **scanline;
+/*
+ * Allocate some memory.
+ */
+    /*xelrow = pnm_allowcrow(width);*/
+    xelrow = (xel*) pm_allocrow( width, sizeof(xel) );
+    MALLOCARRAY(scanlines, height);
+    RLE_CHECK_ALLOC( hdr.cmd, scanlines, "scanline pointers" );
+
+    for ( scan = 0; scan < height; scan++ )
+        RLE_CHECK_ALLOC( hdr.cmd, (rle_row_alloc(&hdr, &scanlines[scan]) >= 0),
+                         "pixel memory" );
+/*
+ * Loop through the pnm files image window, read data and flip vertically.
+ */
+    switch (format) {
+    case PBM_FORMAT:
+    case RPBM_FORMAT:
+        for (scan = 0; scan < height; scan++) {
+            scanline = scanlines[height - scan - 1];
+            pnm_readpnmrow(fp, xelrow, width, maxval, format);
+            for (x = 0, pP = xelrow; x < width; x++, pP++) {
+                scanline[RLE_RED][x]   = (PNM_GET1(*pP) ? 255 : 0);
+                if (do_alpha) {
+                    scanline[RLE_ALPHA][x] = scanline[RLE_RED][x];
+                }
+            }
+        }
+        break;
+    case PGM_FORMAT:
+    case RPGM_FORMAT:
+        for (scan = 0; scan < height; scan++) {
+            scanline = scanlines[height - scan - 1];
+            pnm_readpnmrow(fp, xelrow, width, maxval, format);
+            for (x = 0, pP = xelrow; x < width; x++, pP++) {
+                scanline[RLE_RED][x]   = PNM_GET1(*pP);
+                if (do_alpha) {
+                    scanline[RLE_ALPHA][x] = (scanline[RLE_RED][x] ? 255 : 0);
+                }
+            }
+        }
+        break;
+    case PPM_FORMAT:
+    case RPPM_FORMAT:
+        for (scan = 0; scan < height; scan++) {
+            scanline = scanlines[height - scan - 1];
+            pnm_readpnmrow(fp, xelrow, width, maxval, format);
+            for (x = 0, pP = xelrow; x < width; x++, pP++) {
+                scanline[RLE_RED][x]   = PPM_GETR(*pP);
+                scanline[RLE_GREEN][x] = PPM_GETG(*pP);
+                scanline[RLE_BLUE][x]  = PPM_GETB(*pP);
+                if (do_alpha) {
+                    scanline[RLE_ALPHA][x] = (scanline[RLE_RED][x] ||
+                                              scanline[RLE_GREEN][x] ||
+                                              scanline[RLE_BLUE][x] ? 255 : 0);
+                }
+            }
+        }
+        break;
+    }
+/*
+ * Write out data in URT order (bottom to top).
+ */
+    for ( scan = 0; scan < height; scan++ ) {
+        rle_putrow(scanlines[scan], width, &hdr);
+        rle_row_free( &hdr, scanlines[scan] );
+    }
+    free( scanlines );
+
+    VPRINTF(stderr, "Done -- write eof to RLE data.\n");
+    rle_puteof(&hdr);
+}
+
+int
+main(argc, argv)
+    int argc;
+    char **argv;
+{
+    char     *pnmname = NULL, *outname = NULL;
+    static char  filename[BUFSIZ];
+    int      oflag, c;
+
+    pnm_init(&argc, argv);
+
+/*
+ * Get those options.
+ */
+    if (!scanargs(argc,argv,
+                  "% v%- h%- a%- o%-outfile!s pnmfile%s\n(\
+\tConvert a PNM file to URT RLE format.\n\
+\t-a\tFake an alpha channel.  Alpha=0 when input=0, 255 otherwise.\n\
+\t-h\tPrint header of PNM file and exit.\n\
+\t-v\tVerbose mode.)",
+                  &verbose,
+                  &header,
+                  &do_alpha,
+                  &oflag, &outname,
+                  &pnmname))
+        exit(-1);
+
+    hdr = *rle_hdr_init( (rle_hdr *)NULL );
+    rle_names( &hdr, cmd_name( argv ), outname, 0 );
+/*
+ * Open the file.
+ */
+    if (pnmname == NULL) {
+        strcpy(filename, "stdin");
+        fp = stdin;
+    }
+    else {
+        strcpy(filename, pnmname);
+        fp = pm_openr(filename);
+    }
+
+    hdr.rle_file = rle_open_f( hdr.cmd, outname, "wb" );
+    while ( (c = getc( fp )) != EOF ) {
+        ungetc( c, fp );
+/*
+ * Read the PPM file header.
+ */
+        read_pnm_header();
+        if (header)
+            break;
+/*
+ * Write the rle file header.
+ */
+        rle_addhist(argv, (rle_hdr *)NULL, &hdr);
+        write_rle_header();
+/*
+ * Write the rle file data.
+ */
+        write_rle_data();
+    }
+    fclose(fp);
+    return 0;
+}
diff --git a/converter/other/pnmtosgi.c b/converter/other/pnmtosgi.c
new file mode 100644
index 00000000..472b5197
--- /dev/null
+++ b/converter/other/pnmtosgi.c
@@ -0,0 +1,354 @@
+/* pnmtosgi.c - convert portable anymap to SGI image
+**
+** Copyright (C) 1994 by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
+**
+** Based on the SGI image description v0.9 by Paul Haeberli (paul@sgi.comp)
+** Available via ftp from sgi.com:graphics/SGIIMAGESPEC
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** 29Jan94: first version
+*/
+#include "pnm.h"
+#include "sgi.h"
+#include "mallocvar.h"
+
+/*#define DEBUG*/
+
+typedef short       ScanElem;
+typedef struct {
+    ScanElem *  data;
+    long        length;
+} ScanLine;
+
+/* prototypes */
+static void put_big_short ARGS((short s));
+static void put_big_long ARGS((long l));
+#define put_byte(b)     (void)(putc((unsigned char)(b), stdout))
+static void put_short_as_byte ARGS((short s));
+static void write_table  ARGS((long *table, int tabsize));
+static void write_channels ARGS((int cols, int rows, int channels, void (*put) ARGS((short)) ));
+static long * build_channels ARGS((FILE *ifp, int cols, int rows, xelval maxval, int format, int bpc, int channels));
+static ScanElem *compress ARGS((ScanElem *temp, int row, int rows, int cols, int chan_no, long *table, int bpc));
+static int rle_compress ARGS((ScanElem *inbuf, int cols));
+
+#define WORSTCOMPR(x)   (2*(x) + 2)
+
+
+#define MAXVAL_BYTE     255
+#define MAXVAL_WORD     65535
+
+static char storage = STORAGE_RLE;
+static ScanLine * channel[3];
+static ScanElem * rletemp;
+static xel * pnmrow;
+
+
+static void
+write_header(int const cols, 
+             int const rows, 
+             xelval const maxval, 
+             int const bpc, 
+             int const dimensions, 
+             int const channels, 
+             const char * const imagename)
+{
+    int i;
+
+#ifdef DEBUG
+    pm_message("writing header");
+#endif
+
+    put_big_short(SGI_MAGIC);
+    put_byte(storage);
+    put_byte((char)bpc);
+    put_big_short(dimensions);
+    put_big_short(cols);
+    put_big_short(rows);
+    put_big_short(channels);
+    put_big_long(0);                /* PIXMIN */
+    put_big_long(maxval);           /* PIXMAX */
+    for( i = 0; i < 4; i++ )
+        put_byte(0);
+    for( i = 0; i < 79 && imagename[i] != '\0'; i++ )
+        put_byte(imagename[i]);
+    for(; i < 80; i++ )
+        put_byte(0);
+    put_big_long(CMAP_NORMAL);
+    for( i = 0; i < 404; i++ )
+        put_byte(0);
+}
+
+
+
+int
+main(argc, argv)
+    int argc;
+    char *argv[];
+{
+    FILE *ifp;
+    int argn;
+    const char * const usage = "[-verbatim|-rle] [-imagename <name>] [pnmfile]";
+    int cols, rows, format;
+    xelval maxval, newmaxval;
+    const char *imagename = "no name";
+    int bpc, dimensions, channels;
+    long *table = NULL;
+
+    pnm_init(&argc, argv);
+
+    argn = 1;
+    while( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) {
+        if( pm_keymatch(argv[argn], "-verbatim", 2) )
+            storage = STORAGE_VERBATIM;
+        else
+        if( pm_keymatch(argv[argn], "-rle", 2) )
+            storage = STORAGE_RLE;
+        else
+        if( pm_keymatch(argv[argn], "-imagename", 2) ) {
+            if( ++argn >= argc )
+                pm_usage(usage);
+            imagename = argv[argn];
+        }
+        else
+            pm_usage(usage);
+        ++argn;
+    }
+
+    if( argn < argc ) {
+        ifp = pm_openr( argv[argn] );
+        argn++;
+    }
+    else
+        ifp = stdin;
+
+    if( argn != argc )
+        pm_usage(usage);
+
+    pnm_readpnminit(ifp, &cols, &rows, &maxval, &format);
+    pnmrow = pnm_allocrow(cols);
+
+    switch( PNM_FORMAT_TYPE(format) ) {
+        case PBM_TYPE:
+            newmaxval = PGM_MAXMAXVAL;
+            pm_message("promoting PBM to PGM");
+        case PGM_TYPE:
+            newmaxval = maxval;
+            dimensions = 2; channels = 1;
+            break;
+        case PPM_TYPE:
+            newmaxval = maxval;
+            dimensions = 3; channels = 3;
+            break;
+        default:
+            pm_error("can\'t happen");
+    }
+    if( newmaxval <= MAXVAL_BYTE )
+        bpc = 1;
+    else if( newmaxval <= MAXVAL_WORD )
+        bpc = 2;
+    else
+        pm_error("maxval too large - try using \"pnmdepth %d\"", MAXVAL_WORD);
+
+    table = build_channels(ifp, cols, rows, newmaxval, format, bpc, channels);
+    pnm_freerow(pnmrow);
+    pm_close(ifp);
+
+    write_header(cols, rows, newmaxval, bpc, dimensions, channels, imagename);
+    if( table )
+        write_table(table, rows * channels);
+    if( bpc == 1 )
+        write_channels(cols, rows, channels, put_short_as_byte);
+    else
+        write_channels(cols, rows, channels, put_big_short);
+
+    exit(0);
+}
+
+
+static void
+write_table(table, tabsize)
+    long *table;
+    int tabsize;
+{
+    int i;
+    long offset;
+
+#ifdef DEBUG
+    pm_message("writing table");
+#endif
+
+    offset = HeaderSize + tabsize * 8;
+    for( i = 0; i < tabsize; i++ ) {
+        put_big_long(offset);
+        offset += table[i];
+    }
+    for( i = 0; i < tabsize; i++ )
+        put_big_long(table[i]);
+}
+
+
+static void
+write_channels(cols, rows, channels, put)
+    int cols, rows, channels;
+    void (*put) ARGS((short));
+{
+    int i, row, col;
+
+#ifdef DEBUG
+    pm_message("writing image data");
+#endif
+
+    for( i = 0; i < channels; i++ ) {
+        for( row = 0; row < rows; row++ ) {
+            for( col = 0; col < channel[i][row].length; col++ ) {
+                (*put)(channel[i][row].data[col]);
+            }
+        }
+    }
+}
+
+static void
+put_big_short(short s)
+{
+    if ( pm_writebigshort( stdout, s ) == -1 )
+        pm_error( "write error" );
+}
+
+
+static void
+put_big_long(l)
+    long l;
+{
+    if ( pm_writebiglong( stdout, l ) == -1 )
+        pm_error( "write error" );
+}
+
+
+static void
+put_short_as_byte(short s)
+{
+    put_byte((unsigned char)s);
+}
+
+
+static long *
+build_channels(FILE *ifp, int cols, int rows, xelval maxval, 
+               int format, int bpc, int channels)
+{
+    int i, row, col, sgirow;
+    long *table = NULL;
+    ScanElem *temp;
+
+#ifdef DEBUG
+    pm_message("building channels");
+#endif
+
+    if( storage != STORAGE_VERBATIM ) {
+        MALLOCARRAY_NOFAIL(table, channels * rows);
+        MALLOCARRAY_NOFAIL(rletemp, WORSTCOMPR(cols));
+    }
+    MALLOCARRAY_NOFAIL(temp, cols);
+
+    for( i = 0; i < channels; i++ )
+        MALLOCARRAY_NOFAIL(channel[i], rows);
+
+    for( row = 0, sgirow = rows-1; row < rows; row++, sgirow-- ) {
+        pnm_readpnmrow(ifp, pnmrow, cols, maxval, format);
+        if( channels == 1 ) {
+            for( col = 0; col < cols; col++ )
+                temp[col] = (ScanElem)PNM_GET1(pnmrow[col]);
+            temp = compress(temp, sgirow, rows, cols, 0, table, bpc);
+        }
+        else {
+            for( col = 0; col < cols; col++ )
+                temp[col] = (ScanElem)PPM_GETR(pnmrow[col]);
+            temp = compress(temp, sgirow, rows, cols, 0, table, bpc);
+            for( col = 0; col < cols; col++ )
+                temp[col] = (ScanElem)PPM_GETG(pnmrow[col]);
+            temp = compress(temp, sgirow, rows, cols, 1, table, bpc);
+            for( col = 0; col < cols; col++ )
+                temp[col] = (ScanElem)PPM_GETB(pnmrow[col]);
+            temp = compress(temp, sgirow, rows, cols, 2, table, bpc);
+        }
+    }
+
+    free(temp);
+    if( table )
+        free(rletemp);
+    return table;
+}
+
+
+static ScanElem *
+compress(temp, row, rows, cols, chan_no, table, bpc)
+    ScanElem *temp;
+    int row, rows, cols, chan_no;
+    long *table;
+    int bpc;
+{
+    int len, i, tabrow;
+    ScanElem *p;
+
+    switch( storage ) {
+        case STORAGE_VERBATIM:
+            channel[chan_no][row].length = cols;
+            channel[chan_no][row].data = temp;
+            MALLOCARRAY_NOFAIL(temp, cols);
+            break;
+        case STORAGE_RLE:
+            tabrow = chan_no * rows + row;
+            len = rle_compress(temp, cols);    /* writes result into rletemp */
+            channel[chan_no][row].length = len;
+            MALLOCARRAY(p, len);
+            channel[chan_no][row].data = p;
+            for( i = 0; i < len; i++, p++ )
+                *p = rletemp[i];
+            table[tabrow] = len * bpc;
+            break;
+        default:
+            pm_error("unknown storage type - can\'t happen");
+    }
+    return temp;
+}
+
+
+/*
+slightly modified RLE algorithm from ppmtoilbm.c
+written by Robert A. Knop (rknop@mop.caltech.edu)
+*/
+static int
+rle_compress(inbuf, size)
+    ScanElem *inbuf;
+    int size;
+{
+    int in, out, hold, count;
+    ScanElem *outbuf = rletemp;
+
+    in=out=0;
+    while( in<size ) {
+        if( (in<size-1) && (inbuf[in]==inbuf[in+1]) ) {     /*Begin replicate run*/
+            for( count=0,hold=in; in<size && inbuf[in]==inbuf[hold] && count<127; in++,count++)
+                ;
+            outbuf[out++]=(ScanElem)(count);
+            outbuf[out++]=inbuf[hold];
+        }
+        else {  /*Do a literal run*/
+            hold=out; out++; count=0;
+            while( ((in>=size-2)&&(in<size)) || ((in<size-2) && ((inbuf[in]!=inbuf[in+1])||(inbuf[in]!=inbuf[in+2]))) ) {
+                outbuf[out++]=inbuf[in++];
+                if( ++count>=127 )
+                    break;
+            }
+            outbuf[hold]=(ScanElem)(count | 0x80);
+        }
+    }
+    outbuf[out++] = (ScanElem)0;     /* terminator */
+    return(out);
+}
+
diff --git a/converter/other/pnmtosir.c b/converter/other/pnmtosir.c
new file mode 100644
index 00000000..c8dec5b6
--- /dev/null
+++ b/converter/other/pnmtosir.c
@@ -0,0 +1,146 @@
+/* pnmtosir.c - read a portable anymap and produce a Solitaire Image Recorder
+**		file (MGI TYPE 11 or MGI TYPE 17)
+**
+** Copyright (C) 1991 by Marvin Landis
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pnm.h"
+
+#define MAXCOLORS 256
+
+int main(int argc, char * argv[]) {
+    FILE* ifp;
+    xel** xels;
+    register xel* xP;
+    const char* dumpname;
+    int rows, cols, format, row, col;
+    int m, n;
+    int grayscale;
+    xelval maxval;
+    const char* const usage = "[pnmfile]";
+    unsigned char ub;
+    unsigned short Header[16];
+    unsigned short LutHeader[16];
+    unsigned short Lut[2048];
+
+    pnm_init( &argc, argv );
+
+    if ( argc > 2 )
+        pm_usage( usage );
+
+    if ( argc == 2 )
+	{
+        dumpname = argv[1];
+        ifp = pm_openr( argv[1] );
+	}
+    else
+	{
+        dumpname = "Standard Input";
+        ifp = stdin;
+	}
+    
+    xels = pnm_readpnm( ifp, &cols, &rows, &maxval, &format );
+    pm_close( ifp );
+    
+    /* Figure out the colormap. */
+    switch ( PNM_FORMAT_TYPE(format) )
+	{
+	case PPM_TYPE:
+        grayscale = 0;
+        pm_message( "Writing a 24-bit SIR format (MGI TYPE 11)" );
+        break;
+
+    case PGM_TYPE:
+        grayscale = 1;
+        pm_message( "Writing a grayscale SIR format (MGI TYPE 17)" );
+        break;
+
+	default:
+        grayscale = 1;
+        pm_message( "Writing a monochrome SIR format (MGI TYPE 17)" );
+        break;
+	}
+
+    /* Set up the header. */
+    Header[0] = 0x3a4f;
+    Header[1] = 0;
+    if (grayscale)
+        Header[2] = 17;
+    else
+        Header[2] = 11;
+    Header[3] = cols;
+    Header[4] = rows;
+    Header[5] = 0;
+    Header[6] = 1;
+    Header[7] = 6;
+    Header[8] = 0;
+    Header[9] = 0;
+    for (n = 0; n < 10; n++)
+        pm_writelittleshort(stdout,Header[n]);
+    for (n = 10; n < 256; n++)
+        pm_writelittleshort(stdout,0);
+
+    /* Create color map */
+    LutHeader[0] = 0x1524;
+    LutHeader[1] = 0;
+    LutHeader[2] = 5;
+    LutHeader[3] = 256;
+    LutHeader[4] = 256;
+    for (n = 0; n < 5; n++)
+        pm_writelittleshort(stdout,LutHeader[n]);
+    for (n = 5; n < 256; n++)
+        pm_writelittleshort(stdout,0);
+ 
+    for(n = 0; n < 3; n ++)
+        for (m = 0; m < 256; m++)
+            Lut[m * 4 + n] = m << 8;
+    for (n = 0; n < 1024; n++)
+        pm_writelittleshort(stdout,Lut[n]);
+ 
+    /* Finally, write out the data. */
+    switch ( PNM_FORMAT_TYPE(format) )
+    {
+	case PPM_TYPE:
+	    for ( row = 0; row < rows; ++row )
+            for ( col = 0, xP = xels[row]; col < cols; ++col, ++xP )
+            {
+                ub = (char) ( PPM_GETR( *xP ) * ( 255 / maxval ) ); 
+                fputc( ub, stdout );
+            }
+        for ( row = 0; row < rows; ++row )
+            for ( col = 0, xP = xels[row]; col < cols; ++col, ++xP )
+            {  
+                ub = (char) ( PPM_GETG( *xP ) * ( 255 / maxval ) );
+                fputc( ub, stdout );
+            }
+        for ( row = 0; row < rows; ++row )
+            for ( col = 0, xP = xels[row]; col < cols; ++col, ++xP )
+            {  
+                ub = (char) ( PPM_GETB( *xP ) * ( 255 / maxval ) );
+                fputc( ub, stdout );
+            }
+	    break;
+
+    default:
+        for ( row = 0; row < rows; ++row )
+            for ( col = 0, xP = xels[row]; col < cols; ++col, ++xP )
+            {
+                register unsigned long val;
+
+                val = PNM_GET1( *xP );
+                ub = (char) ( val * ( 255 / maxval ) );
+                fputc( ub, stdout );
+            }
+        break;
+    }
+
+    exit( 0 );
+}
+
diff --git a/converter/other/pnmtotiffcmyk.c b/converter/other/pnmtotiffcmyk.c
new file mode 100644
index 00000000..0fda6b08
--- /dev/null
+++ b/converter/other/pnmtotiffcmyk.c
@@ -0,0 +1,1000 @@
+/*
+
+CMYKTiff - pnmtotiffcmyk - conversion from a pnm file to a CMYK
+encoded tiff file.
+
+Copyright (C) 1999 Andrew Cooke  (Jara Software)
+
+Comments to jara@andrewcooke.free-online.co.uk
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at
+your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA
+
+These conditions also apply to any other files distributed with the
+program that describe / document the CMYKTiff package.
+
+
+Much of the code uses ideas from other pnm programs, written by Jef
+Poskanzer (thanks go to him and libtiff maintainer Sam Leffler).  A
+small section of the code - some of the tiff tag settings - is derived
+directly from pnmtotiff, by Jef Poskanzer, which, in turn,
+acknowledges Patrick Naughton with the following text:
+
+** Derived by Jef Poskanzer from ras2tif.c, which is:
+**
+** Copyright (c) 1990 by Sun Microsystems, Inc.
+**
+** Author: Patrick J. Naughton
+** naughton@wind.sun.com
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted,
+** provided that the above copyright notice appear in all copies and that
+** both that copyright notice and this permission notice appear in
+** supporting documentation.
+**
+** This file is provided AS IS with no warranties of any kind.  The author
+** shall have no liability with respect to the infringement of copyrights,
+** trade secrets or any patents by this file or any part thereof.  In no
+** event will the author be liable for any lost revenue or profits or
+** other special, indirect and consequential damages.
+
+Software copyrights will soon need family trees... :-)
+
+*/
+
+#define _XOPEN_SOURCE
+
+#include <string.h>
+#include <math.h>
+/* float.h used to be included only if __osf__ was defined.  On some
+   platforms, float.h is also included automatically by math.h.  But at
+   least on Solaris, it isn't.  00.04.25.
+*/
+#include <float.h>
+#include <limits.h>
+#include <fcntl.h>
+/* See warning about tiffio.h in pamtotiff.c */
+#include <tiffio.h>
+
+#include "pm_c_util.h"
+#include "nstring.h"
+#include "pnm.h"
+
+/* first release after testing */
+/* second release after testing - small mods to netpbm package */
+#define VERSION 1.01
+
+/* only support 8 bit values */
+#define MAXTIFFBITS 8
+#define MAXTIFFVAL 255 
+
+
+/* definitions for error values */
+
+typedef int Err ;
+
+#define ERR_OK       0  /* no error */
+#define ERR_PNM      1  /* thrown by pnm library */
+#define ERR_MEMORY   2  /* could not allocate memory */
+#define ERR_ARG      3  /* unexpected argument */
+#define ERR_TIFF     4  /* failure in tiff library */
+#define ERR_HELP     5  /* terminate with -help */
+
+/* definitions for command line options */
+
+#define UNSET 2 /* Simple->remove value when gammap not set */
+#define K_NORMAL 0
+#define K_REMOVE 1
+#define K_ONLY 2
+
+
+/* definitions for conversion calculations */
+
+/* catch rounding errors to outside 0-1 range */
+#define LIMIT01(x) MAX( 0.0, MIN( 1.0, (x) ) )
+/* convert from 0-1 to 0-maxOut */
+#define TOMAX(x) MAX( 0, MIN( rt->maxOut, (int)(rt->maxOut * (x)) ) )
+#define TINY FLT_EPSILON
+#ifndef PI
+#define PI 3.1415926
+#endif
+#define ONETWENTY ( 2.0 * PI / 3.0 ) 
+#define TWOFORTY ( 4.0 * PI / 3.0 )
+#define THREESIXTY ( 2.0 * PI ) 
+/* expand from 0-90 to 0-120 degrees */
+#define EXPAND(x) ( 4.0 * ( x ) / 3.0 )
+/* contract from 0-120 to 0-90 degrees */
+#define CONTRACT(x) ( 3.0 * ( x ) / 4.0 )
+
+
+
+/* --- main structure for the process ----------------- */
+
+struct tagIn ;
+struct tagConv ;
+struct tagOut ;
+
+typedef struct {
+  struct tagIn *in ;
+  struct tagConv *conv ;
+  struct tagOut *out ;
+  int nCols ;
+  int nRows ;
+  int maxOut ;
+  const char *name ;
+} Root ;
+
+
+
+/* --- utils ------------------------------------------ */
+
+
+/* parse an arg with a float value.  name should be the full key name,
+   preceded by '-' */
+static Err 
+floatArg( float *arg, const char *name, int size, float lo, float hi,
+          int *argn, int argc, char **argv ) {
+
+  char extra ;
+  int count ;
+
+  if ( pm_keymatch( argv[*argn], name, size ) ) {
+    if ( ++(*argn) == argc ) {
+      fprintf( stderr, "no value for %s\n", name ) ;
+      return ERR_ARG ;
+    }
+    if ( ! (count = sscanf( argv[*argn], "%f%1c", arg, &extra )) ) {
+      fprintf( stderr, "cannot parse %s for %s\n", argv[*argn], name ) ;
+      return ERR_ARG ;
+    }
+    if ( count > 1 ) {
+      fprintf( stderr, "warning: ignored %c... in value for %s\n",
+               extra, name ) ;
+    }
+    if ( *arg > hi || *arg < lo ) {
+      fprintf( stderr, "%s (%f) must be in range %f to %f\n", 
+               name, *arg, lo, hi ) ;
+      return ERR_ARG ;
+    }
+    ++(*argn) ;
+  }
+
+  return ERR_OK ;
+}
+
+
+/* parse an arg with a long value.  name should be the full key name,
+   preceded by '-' */
+static Err 
+longArg( long *arg, const char *name, int size, long lo, long hi,
+         int *argn, int argc, char **argv ) {
+
+  char extra ;
+  int count ;
+
+  if ( pm_keymatch( argv[*argn], name, size ) ) {
+    if ( ++(*argn) == argc ) {
+      fprintf( stderr, "no value for %s\n", name ) ;
+      return ERR_ARG ;
+    }
+    if ( ! (count = sscanf( argv[*argn], "%ld%1c", arg, &extra )) ) {
+      fprintf( stderr, "cannot parse %s for %s\n", argv[*argn], name ) ;
+      return ERR_ARG ;
+    }
+    if ( count > 1 ) {
+      fprintf( stderr, "warning: ignored %c... in value for %s\n",
+               extra, name ) ;
+    }
+    if ( *arg > hi || *arg < lo ) {
+      fprintf( stderr, "%s (%ld) must be in range %ld to %ld\n", 
+               name, *arg, lo, hi ) ;
+      return ERR_ARG ;
+    }
+    ++(*argn) ;
+  }
+
+  return ERR_OK ;
+}
+
+
+/* print usage.  for simplicity this routine is *not* split amongst
+   the various components - when you add a component (eg a new
+   conversion algorithm, or maybe new input or output code), you must
+   also change this routine.  by keeping all the options in one place
+   it is also easier to calculate the minimum key name length (passed
+   to pnm_keymatch) */
+static void 
+printUsage( ) {
+  fprintf( stderr, "\nusage: pnmtocmyk [Compargs] [Tiffargs] [Convargs] [pnmfile]\n" ) ;
+  fprintf( stderr, " Compargs: [-none|-packbits|-lzw [-predictor 1|-predictor 2]]\n" ) ;
+  fprintf( stderr, " Tiffargs: [-msb2lsb|-lsb2msb] [-rowsperstrip n]\n" ) ;
+  fprintf( stderr, "           [-lowdotrange lo] [-highdotrange hi] [-knormal|-konly|-kremove]\n" ) ;
+  fprintf( stderr, " Convargs: [[-default] [Defargs]|-negative]\n" ) ;
+  fprintf( stderr, " Defargs:  [-theta deg] [-gamma g] [-gammap -1|-gammap g]\n" ) ;
+  fprintf( stderr, "where 0 <= lo < hi <= 255; -360 < deg < 360; 0.1 < g < 10; 0 < n < INT_MAX\n\n" ) ;
+  fprintf( stderr, "returns: 0 OK; 1 pnm library error ; 2 memory error ; 3 unexpected arg ;\n" ) ;
+  fprintf( stderr, "         4 tiff library error ; 5 -help key used\n\n" ) ;
+  fprintf( stderr, "Convert a pnm file to a CMYK tiff file.  Version %5.2f\n", VERSION ) ;
+  fprintf( stderr, "CMY under K will be removed unless -gammap -1 is used.\n" ) ;
+  fprintf( stderr, "(c) 1999 Andrew Cooke - Beta version, not for public use.\n" ) ;
+  fprintf( stderr, "No warranty.\n\n" ) ;
+}
+
+/* list of key args and number of significant letters required
+-default      2
+-gamma        6
+-gammap       7
+-highdotrange 2
+-knormal      3
+-konly        3
+-kremove      3
+-lowdotrange  3
+-lsb2msb      3
+-lzw          3
+-msb2lsb      2
+-negative     3
+-none         3
+-packbits     3
+-predictor    3
+-rowsperstrip 2
+-theta        2
+*/
+
+
+
+/* --- reading the input ------------------------------ */
+
+
+/* encapsulate the file reader - uses pnm library at the moment, but
+   could be changed if we move to libtiff */
+
+typedef Err OptIn( struct tagIn *conv, Root *r, 
+                   int *argn, int argc, char **argv ) ;
+typedef int HasMore( struct tagIn *in ) ;
+typedef Err Next( struct tagIn *in, float *r, float *g, float *b ) ;
+typedef Err OpenIn( struct tagIn *in, Root *r ) ; 
+typedef void CloseIn( struct tagIn *in ) ;
+
+typedef struct tagIn {
+  OptIn *opt ;
+  HasMore *hasMore ;
+  Next *next ;
+  OpenIn *open ;
+  CloseIn *close ;
+  void *private ;
+} In ;
+
+
+/* implementation for pnm files */
+
+typedef struct {
+  FILE *in ;
+  int type ;
+  xelval maxVal ;
+  int maxPix ;
+  int iPix ;
+  xel *row ;
+  int maxRow ;
+  int iRow ;
+} PnmIn ;
+
+
+/* the only output option is the filename, which will be the last
+   argument, if it doesn't start with '-' */
+static Err 
+pnmOpt( In *in, Root *r, int *argn, int argc, char **argv ) {
+  PnmIn *p = (PnmIn*)in->private ;
+  if ( *argn + 1 == argc && argv[*argn][0] != '\0' &&
+       argv[*argn][0] != '-' ) {
+    r->name = argv[(*argn)++] ;
+    p->in = pm_openr( r->name ) ;
+  }
+  return ERR_OK ;
+}
+
+
+/* free the row buffer when closing the input */
+static void 
+pnmClose( In *in ) {
+  if ( in ) {
+    PnmIn *p = (PnmIn*)in->private ;
+    if ( p ) {
+      if ( p->row ) pnm_freerow( p->row ) ;
+      if ( p->in ) pm_close( p->in ) ;
+      free( p ) ;
+    }
+  }
+}
+
+
+/* open the file, storing dimensions both locally and in the global
+   root structure */
+static Err 
+pnmOpen( In *in, Root *r ) {
+
+  PnmIn *p = (PnmIn*)in->private ;
+
+  if ( ! p->in ) p->in = stdin ;
+
+  pnm_readpnminit( p->in, &(r->nCols), &(r->nRows), &(p->maxVal), 
+                   &(p->type) ) ;
+  p->maxPix = r->nCols * r->nRows ;
+  p->iPix = 0 ;
+  p->maxRow = r->nCols ;
+  p->iRow = 0 ;
+  p->row = pnm_allocrow( p->maxRow ) ;
+
+  return ERR_OK ;
+}
+
+
+/* more data available? */
+static int 
+pnmHasMore( In *in ) {
+  PnmIn *p = (PnmIn*)in->private ;
+  return p->iPix < p->maxPix ;
+}
+
+
+/* read next pixel - buffered by row.  return values in range 0 to 1 */
+static Err 
+pnmNext( In *in, float *r, float *g, float *b ) {
+
+  PnmIn *p = (PnmIn*)in->private ;
+  float m = (float)p->maxVal ;
+
+  if ( p->iPix == 0 || p->iRow == p->maxRow ) {
+    p->iRow = 0 ;
+    pnm_readpnmrow( p->in, p->row, p->maxRow, p->maxVal, p->type ) ;
+  }
+  if ( PNM_FORMAT_TYPE( p->type ) == PPM_TYPE ) {
+    *r = (float)PPM_GETR( p->row[p->iRow] ) / m ;
+    *g = (float)PPM_GETG( p->row[p->iRow] ) / m ;
+    *b = (float)PPM_GETB( p->row[p->iRow] ) / m ;
+  } else {
+    *r = *g = *b = (float)PNM_GET1( p->row[p->iRow] ) / m ;
+  }
+  ++(p->iRow) ;
+  ++(p->iPix) ;
+
+  return ERR_OK ;
+}
+
+
+/* build the input struct */
+static Err 
+newPnmInput( In **in ) {
+  if ( ! (*in = (In*)calloc( 1, sizeof( In ) )) ) {
+    fprintf( stderr, "cannot allocate memory\n" ) ;
+    return ERR_MEMORY ;
+  }
+  (*in)->private = calloc( 1, sizeof( PnmIn ) ) ;
+  (*in)->opt = pnmOpt ;
+  (*in)->open = pnmOpen ;
+  (*in)->hasMore = pnmHasMore ;
+  (*in)->next = pnmNext ;
+  (*in)->close = pnmClose ;
+  return ERR_OK ;
+}
+
+
+
+/* --- writing the output ----------------------------- */
+
+
+/* encapsulate file writing (will probably always use libtiff, but may
+   as well be consistent) */
+
+typedef Err OptOut( struct tagOut *conv, Root *r,
+                    int *argn, int argc, char **argv ) ;
+typedef Err Write( struct tagOut *out, int c, int m, int y, int k ) ;
+typedef Err OpenOut( struct tagOut *out, Root *r ) ;
+typedef void CloseOut( struct tagOut *out ) ;
+
+typedef struct tagOut {
+  OptOut *opt ;
+  Write *write ;
+  OpenOut *open ;
+  CloseOut *close ;
+  void *private ;
+} Out ;
+
+
+/* implementation for tiff files */
+
+typedef struct {
+  tdata_t buffer ;
+  tsize_t maxBuffer ;
+  tsize_t iBuffer ;
+  uint32 iRow ;
+  TIFF *tiff ;
+  uint32 rowsperstrip ;
+  uint16 compression ;
+  uint16 fillorder ;
+  uint16 predictor ;
+  uint16 lowdotrange ;
+  uint16 highdotrange ;
+  int kFlag ;
+} TiffOut ;
+
+
+/* these options come from either the tiff 6.0 spec or the pnmtotiff
+   code */
+static Err 
+tiffOpt( Out *out, Root* rt, int *argn, int argc, char **argv ) {
+
+  Err err ;
+  int oldn ;
+  long lVal ;
+  TiffOut *t = (TiffOut*)out->private ;
+
+  if ( pm_keymatch( argv[*argn], "-none", 3 ) ) {
+    t->compression = COMPRESSION_NONE ;
+    ++(*argn) ;
+  } else if ( pm_keymatch( argv[*argn], "-packbits", 3 ) ) {
+    t->compression = COMPRESSION_PACKBITS ;
+    ++(*argn) ;
+  } else if ( pm_keymatch( argv[*argn], "-lzw", 3 ) ) {
+    t->compression = COMPRESSION_LZW ;
+    ++(*argn) ;
+  } else if ( pm_keymatch( argv[*argn], "-msb2lsb", 2 ) ) {
+    t->fillorder = FILLORDER_MSB2LSB ;
+    ++(*argn) ;
+  } else if ( pm_keymatch( argv[*argn], "-lsb2msb", 3 ) ) {
+    t->fillorder = FILLORDER_LSB2MSB ;
+    ++(*argn) ;
+  } else if ( pm_keymatch( argv[*argn], "-knormal", 3 ) ) {
+    t->kFlag = K_NORMAL ;
+    ++(*argn) ;
+  } else if ( pm_keymatch( argv[*argn], "-kremove", 3 ) ) {
+    t->kFlag = K_REMOVE ;
+    ++(*argn) ;
+  } else if ( pm_keymatch( argv[*argn], "-konly", 3 ) ) {
+    t->kFlag = K_ONLY ;
+    ++(*argn) ;
+  } else {
+    oldn = *argn ;
+    if ( (err = longArg( &lVal, "-predictor", 3, 1, 2,
+                         argn, argc, argv )) ) return err ;
+    if ( oldn != *argn ) {
+      t->predictor = (uint16)lVal ;
+    } else {
+      if ( (err = longArg( &lVal, "-rowsperstrip", 2, 1, INT_MAX,
+                           argn, argc, argv )) ) return err ;
+    }
+    if ( oldn != *argn ) {
+      t->rowsperstrip = (uint32)lVal ;
+    } else {
+      if ( (err = longArg( &lVal, "-lowdotrange", 3, 0, 
+                           (int)(t->highdotrange - 1),
+                           argn, argc, argv )) ) return err ;
+    }
+    if ( oldn != *argn ) {
+      t->lowdotrange = (uint16)lVal ;
+    } else {
+      if ( (err = longArg( &lVal, "-highdotrange", 2, 
+                           (int)(t->lowdotrange + 1), MAXTIFFVAL,
+                           argn, argc, argv )) ) return err ;
+    }
+    if ( oldn != *argn ) {
+      t->highdotrange = (uint16)lVal ;
+    }
+  }
+
+  return ERR_OK ;
+}
+
+
+/* helper routine - writes individual bytes */
+static Err 
+tiffWriteByte( TiffOut *t, int b ) {
+  ((unsigned char*)(t->buffer))[t->iBuffer] = (unsigned char)b ;
+  if ( ++(t->iBuffer) == t->maxBuffer ) {
+    if ( TIFFWriteScanline( t->tiff, t->buffer, t->iRow, 0 ) == -1 ) {
+      return ERR_TIFF ;
+    }
+    ++(t->iRow) ;
+    t->iBuffer = 0 ;
+  }
+  return ERR_OK ;
+}
+
+
+/* write the pixel to the tiff file */
+static Err 
+tiffWrite( Out *out, int c, int m, int y, int k ) {
+  Err err ;
+  TiffOut *t = (TiffOut*)out->private ;
+  if ( t->kFlag == K_ONLY ) {
+    c = m = y = k ;
+  } else if ( t->kFlag == K_REMOVE ) {
+    k = 0 ;
+  }
+  if ( (err = tiffWriteByte( t, c )) ) return err ;
+  if ( (err = tiffWriteByte( t, m )) ) return err ;
+  if ( (err = tiffWriteByte( t, y )) ) return err ;
+  if ( (err = tiffWriteByte( t, k )) ) return err ;
+  return ERR_OK ;
+}
+
+
+/* open output to stdout - see warning below */
+static Err 
+tiffOpen( Out* out, Root *r ) {
+
+  TiffOut *t = (TiffOut*)out->private ;
+
+  short samplesperpixel = 4 ; /* cmyk has four values */
+  uint16 bitspersample = MAXTIFFBITS ;
+  short photometric = PHOTOMETRIC_SEPARATED ; /* ie cmyk */
+  int bytesperrow = r->nCols ;
+
+  /* if i don't set stdout non-blocking on my machine then the read
+     that is called inside TIFFFdOpen hangs until the users types ^D.
+     this is also true for pnmtotiff */
+  fcntl( 1, F_SETFL, O_NONBLOCK ) ;
+  t->tiff = TIFFFdOpen( 1, "Standard Output", "w" ) ;
+  if ( ! t->tiff ) {
+    fprintf( stderr, "cannot open tiff stream to standard output\n" ) ;
+    return ERR_TIFF ;
+  }
+
+  /* from pnmtotiff - default is to have 8kb strips */
+  if ( ! t->rowsperstrip ) {
+    t->rowsperstrip = ( 8 * 1024 ) / bytesperrow ;
+  }
+
+  TIFFSetField( t->tiff, TIFFTAG_DOTRANGE, t->lowdotrange, t->highdotrange ) ;
+  TIFFSetField( t->tiff, TIFFTAG_IMAGEWIDTH, (uint32)r->nCols ) ;
+  TIFFSetField( t->tiff, TIFFTAG_IMAGELENGTH, (uint32)r->nRows ) ;
+  TIFFSetField( t->tiff, TIFFTAG_BITSPERSAMPLE, bitspersample ) ;
+  TIFFSetField( t->tiff, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT ) ;
+  TIFFSetField( t->tiff, TIFFTAG_COMPRESSION, t->compression ) ;
+  if ( t->compression == COMPRESSION_LZW && t->predictor ) {
+	TIFFSetField( t->tiff, TIFFTAG_PREDICTOR, t->predictor ) ;
+  }
+  TIFFSetField( t->tiff, TIFFTAG_PHOTOMETRIC, photometric ) ;
+  TIFFSetField( t->tiff, TIFFTAG_FILLORDER, t->fillorder ) ;
+  TIFFSetField( t->tiff, TIFFTAG_DOCUMENTNAME, r->name ) ;
+  TIFFSetField( t->tiff, TIFFTAG_IMAGEDESCRIPTION, "PNM -> CMYK tiff" ) ;
+  TIFFSetField( t->tiff, TIFFTAG_SAMPLESPERPIXEL, samplesperpixel ) ;
+  TIFFSetField( t->tiff, TIFFTAG_ROWSPERSTRIP, t->rowsperstrip ) ;
+  TIFFSetField( t->tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG ) ;
+
+  t->maxBuffer = TIFFScanlineSize( t->tiff ) ;
+  t->buffer = _TIFFmalloc( t->maxBuffer ) ;
+  t->iBuffer = 0 ;
+  t->iRow = 0 ;
+  if ( ! t->buffer ) {
+    fprintf( stderr, "cannot allocate memory\n" ) ;
+    return ERR_MEMORY ;
+  }
+  
+  return ERR_OK ;
+}
+
+
+/* close file and tidy memory */
+static void 
+tiffClose( Out *out ) {
+  if ( out ) {
+    TiffOut *t = (TiffOut*)out->private ;
+    if ( t ) {
+      if ( t->buffer ) _TIFFfree( t->buffer ) ;
+      if ( t->tiff ) TIFFClose( t->tiff ) ;
+      free( t ) ;
+    }
+    free( out ) ;
+  }
+}
+
+
+/* assemble the routines above into a single struct/object */
+static Err 
+newTiffOutput( Out **out ) {
+
+  TiffOut *t ;
+
+  if ( ! (*out = (Out*)calloc( 1, sizeof( Out ) )) ) goto error ;
+  if ( ! (t = (TiffOut*)calloc( 1, sizeof( TiffOut ) )) ) goto error ;
+  t->compression = COMPRESSION_LZW ;
+  t->fillorder = FILLORDER_MSB2LSB ;
+  t->predictor = 0 ;
+  t->rowsperstrip = 0 ;
+  t->lowdotrange = 0 ;
+  t->highdotrange = MAXTIFFVAL ;
+  t->kFlag = K_NORMAL ;
+  (*out)->private = t ;
+  (*out)->opt = tiffOpt ;
+  (*out)->open = tiffOpen ;
+  (*out)->write = tiffWrite ;
+  (*out)->close = tiffClose ;
+  return ERR_OK ;
+
+ error:
+  fprintf( stderr, "cannot allocate memory\n" ) ;
+  return ERR_MEMORY ;
+}
+
+
+
+/* --- conversion ------------------------------------- */
+
+
+/* encapsulate conversion - allow easy extension with other conversion
+   algorithms.  if selection of conversion routine is by command line
+   flag then that flag must precede conversion parameters (for
+   simplicity in argument parsing) */
+
+typedef Err Opt( struct tagConv *conv, Root *r,
+                 int *argn, int argc, char **argv ) ;
+typedef Err Convert( struct tagConv *conv, Root *rt,
+                     float r, float g, float b, 
+                     int *c, int *m, int *y, int *k ) ;
+typedef void Close( struct tagConv *conv ) ;
+
+typedef struct tagConv {
+  Opt *opt ;
+  Convert *convert ;
+  Close *close ;
+  void *private ;
+} Conv ;
+
+
+/* include two conversion routines to show how the code can be
+   extended.  first, the standard converter, as outlined in the
+   proposal, then a simple replacement that produces colour 
+   negatives */
+
+
+/* implementation with hue rotation, black gamma correction and
+   removal of cmy under k */
+
+typedef struct {
+  int initialized ;
+  float theta ;
+  float gamma ;
+  float gammap ;
+  int remove ;
+} Standard ;
+
+
+/* represent an rgb colour as a hue (angle), white level and colour
+   strength */
+static void 
+rgbToHueWhiteColour( float r, float g, float b, 
+                     double *phi, float *white, float *colour ) {
+  *white = MIN( r, MIN( g, b ) ) ;
+  r -= *white ;
+  g -= *white ;
+  b -= *white ;
+  *colour = sqrt( r * r + g * g + b * b ) ;
+  if ( r > TINY || g > TINY || b > TINY ) {
+    if ( b < r && b <= g ) { 
+      *phi = EXPAND( atan2( g, r ) ) ;
+    } else if ( r < g && r <= b ) {
+      *phi = ONETWENTY + EXPAND( atan2( b, g ) ) ;
+    } else {
+      *phi = TWOFORTY + EXPAND( atan2( r, b ) ) ;
+    }
+  } 
+}
+
+
+/* represent hue, white and colour values as rgb */
+static void 
+hueWhiteColourToRgb( double phi, float white, float colour,
+                     float *r, float *g, float *b ) {
+  while ( phi < 0 ) { phi += THREESIXTY ; }
+  while ( phi > THREESIXTY ) { phi -= THREESIXTY ; }
+  if ( phi < ONETWENTY ) {
+    phi = CONTRACT( phi ) ;
+    *r = colour * cos( phi ) ;
+    *g = colour * sin( phi ) ;
+    *b = 0.0 ;
+  } else if ( phi < TWOFORTY ) {
+    phi = CONTRACT( phi - ONETWENTY ) ;
+    *r = 0.0 ;
+    *g = colour * cos( phi ) ;
+    *b = colour * sin( phi ) ;
+  } else {
+    phi = CONTRACT( phi - TWOFORTY ) ;
+    *r = colour * sin( phi ) ;
+    *g = 0.0 ;
+    *b = colour * cos( phi ) ;
+  }
+  *r += white ;
+  *g += white ;
+  *b += white ;
+}
+
+
+/* for details, see the proposal.  it's pretty simple - a rotation
+   before conversion, with colour removal */
+static Err 
+standardConvert( Conv *conv, Root *rt, float r, float g, float b, 
+                 int *c, int *m, int *y, int *k ) {
+
+  float c0, m0, y0, k0 ; /* CMYK before colour removal */
+  float gray, white, colour ;
+  double phi ;
+
+  Standard *s ; /* private conversion data */
+
+  s = (Standard*)(conv->private) ;
+
+  if ( ! s->initialized ) {
+    s->theta *= 2.0 * PI / 360.0 ; /* to radians */
+    /* if gammap never specified in options, set to gamma now */
+    if ( s->remove == UNSET ) {
+      s->remove = 1 ;
+      s->gammap = s->gamma ;
+    }
+    s->initialized = 1 ;
+  }
+
+  /* rotate in rgb */
+  if ( fabs( s->theta ) > TINY ) {
+    rgbToHueWhiteColour( r, g, b, &phi, &white, &colour ) ;
+    hueWhiteColourToRgb( phi + s->theta, white, colour, &r, &g, &b ) ;
+  }
+
+  c0 = LIMIT01( 1.0 - r ) ;
+  m0 = LIMIT01( 1.0 - g ) ;
+  y0 = LIMIT01( 1.0 - b ) ;
+  k0 = MIN( c0, MIN( y0, m0 ) ) ;
+
+  /* apply gamma corrections to modify black levels */
+  *k = TOMAX( pow( k0, s->gamma ) ) ;
+
+  /* remove colour under black and convert to integer range 0 - rt->maxOut */
+  if ( s->remove ) {
+    gray = pow( k0, s->gammap ) ;
+  } else {
+    gray = 0.0 ;
+  }
+  *c = TOMAX( c0 - gray ) ;
+  *m = TOMAX( m0 - gray ) ;
+  *y = TOMAX( y0 - gray ) ;
+
+  return ERR_OK ;
+}
+
+
+/* parse options for this conversion - note the ugly use of -1 for
+   gammap to indicate no removal of cmy under k */
+static Err 
+standardOpt( Conv *conv, Root *r, int *argn, int argc, char **argv ) {
+
+  Err err ;
+  int oldn ;
+  Standard *p = (Standard*)conv->private ;
+
+  oldn = *argn ;
+  if ( (err = floatArg( &(p->theta), "-theta", 2, -360.0, 360.0,
+                        argn, argc, argv )) ) return err ;
+  if ( oldn == *argn ) {
+    if ( (err = floatArg( &(p->gamma), "-gamma", 6, 0.1, 10.0,
+                          argn, argc, argv )) ) return err ;
+    /* gammap traces gamma unless set separately */
+    if ( oldn != *argn && p->remove == UNSET ) p->gammap = p->gamma ;
+  }
+  /* handle the special case of -1 (no removal) */
+  if ( oldn == *argn && pm_keymatch( argv[*argn], "-gammap", 7 ) &&
+       *argn + 1 < argc && STREQ(argv[*argn + 1], "-1") ) {
+    p->remove = 0 ;
+    *argn = (*argn) + 2 ;
+  } 
+  if ( oldn == *argn ) {
+    if ( (err = floatArg( &(p->gammap), "-gammap", 7, 0.1, 10.0,
+                               argn, argc, argv )) ) return err ;
+    if ( oldn != *argn ) p->remove = 1 ;
+  }
+
+  return ERR_OK ;
+}
+
+
+/* free conversion structure */
+static void 
+standardClose( Conv *conv ) {
+  if ( conv ) {
+    if ( conv->private ) { free( conv->private ) ; }
+    free( conv ) ; 
+  }
+}
+
+
+/* build new conversion structure */
+static Err 
+newStandardMap( Conv **conv ) {
+
+  Standard *s ;
+
+  if ( ! (*conv = (Conv*)calloc( 1, sizeof( Conv ) )) ) goto error ;
+  if ( ! (s = (Standard*)calloc( 1, sizeof( Standard ) )) ) goto error ;
+  s->initialized = 0 ;
+  s->remove = UNSET ; /* gammap unset */
+  s->gamma = 1.0 ;
+  (*conv)->private = s ;
+  (*conv)->opt = standardOpt ;
+  (*conv)->convert = standardConvert ;
+  (*conv)->close = standardClose ;
+
+  return ERR_OK ;
+
+ error:
+  fprintf( stderr, "cannot allocate memory\n" ) ;
+  return ERR_MEMORY ;
+}
+
+
+
+/* next a simple demonstration of how to implement other conversion
+   algorithms - in this case, something that produces a "colour
+   negative" (just to be different) */
+
+
+/* the conversion routine must match the Convert typedef */
+static Err 
+negativeConvert( Conv *conv, Root *rt, float r, float g, float b,
+                 int *c, int *m, int *y, int *k ) {
+
+  /* the simple conversion is c=1-r, but for negatives we will use
+     c=r, so only need to convert from rgb 0-1 floats to cmyk
+     0-MAXTIFFVAL ints */
+  *c = TOMAX( r ) ;
+  *m = TOMAX( g ) ;
+  *y = TOMAX( b ) ;
+  *k = MIN( *c, MIN( *m, *y ) ) ;
+
+  return ERR_OK ;
+}
+
+
+/* since this simple conversion takes no parameters, we don't need to
+   parse them - this routine must match the Opt typedef */
+static Err 
+negativeOpt( Conv *conv, Root *r, int *argn, int argc, char **argv ) {
+  return ERR_OK ;
+}
+
+
+/* with no parameters we haven't needed the private data structure, so
+   closing is trivial - this routine must match the Close typedef */
+static void 
+negativeClose( Conv *conv ) { }
+
+
+/* and that's it, apart from assembling the routines above into a
+   single struct/object (and adding code in parseOpts to select the
+   algorithm and printUsage to help the user) */
+static Err 
+newNegativeMap( Conv **conv ) {
+
+  if ( ! (*conv = (Conv*)calloc( 1, sizeof( Conv ) )) ) goto error ;
+  (*conv)->opt = negativeOpt ;
+  (*conv)->convert = negativeConvert ;
+  (*conv)->close = negativeClose ;
+
+  return ERR_OK ;
+
+ error:
+  fprintf( stderr, "cannot allocate memory\n" ) ;
+  return ERR_MEMORY ;
+}
+
+
+
+/* --- general routines ------------------------------- */
+
+
+/* run through args, passing to sub components */
+static Err 
+parseOpts( int argc, char **argv, Root *r ) {
+
+  int argn = 1 ;
+  int oldn ;
+  Err err ;
+
+  /* set default reader, writer and converter */
+  if ( (err = newPnmInput( &(r->in) )) ) return err ;
+  if ( (err = newTiffOutput( &(r->out) )) ) return err ;
+  if ( (err = newStandardMap( &(r->conv) )) ) return err ;
+
+  /* minimum number of chars from inspection - this is standard netpbm
+     approach - so must check when adding more options */
+  while ( argn < argc ) {
+
+    /* first, high-level options that select components - currently
+       just the default or negative converter */
+    if ( pm_keymatch( argv[argn], "-default", 2 ) ) {
+      if ( r->conv ) { r->conv->close( r->conv ) ; }
+      if ( (err = newStandardMap( &(r->conv) )) ) return err ;
+      ++argn ;
+    } else if ( pm_keymatch( argv[argn], "-negative", 3 ) ) {
+      if ( r->conv ) { r->conv->close( r->conv ) ; }
+      if ( (err = newNegativeMap( &(r->conv) )) ) return err ;
+      ++argn ;
+    } else if ( pm_keymatch( argv[argn], "-help", 2 ) ) {
+      printUsage( ) ;
+      return ERR_HELP ;
+    } else {
+      /* next, try passing the option to each subcomponent */
+      oldn = argn ;
+      if ( (err = r->in->opt( r->in, r, &argn, argc, argv )) ) {
+        return err ;
+      } else if ( oldn == argn &&
+                  (err = r->out->opt( r->out, r, &argn, argc, argv )) ) {
+        return err ;
+      } else if ( oldn == argn &&
+                  (err = r->conv->opt( r->conv, r, &argn, argc, argv )) ) {
+        return err ;
+      } else if ( oldn == argn ) {
+        fprintf( stderr, "unexpected arg: %s\n", argv[argn] ) ;
+        return ERR_ARG ;
+      }
+    }
+  }
+
+  return ERR_OK ;
+}
+
+
+/* drive the reading, conversion, and writing */
+int main( int argc, char **argv ) {
+
+  Root *rt ;
+  float r, g, b ;
+  int c, m, y, k ;
+  Err err = ERR_OK ;
+
+  pnm_init(&argc, argv);
+
+  if ( ! (rt = (Root*)calloc( 1, sizeof( Root ) )) ) {
+    err = ERR_MEMORY ;
+    fprintf( stderr, "cannot allocate memory\n" ) ;
+    goto exit ;
+  }
+  rt->name = "Standard input" ;
+  rt->maxOut = MAXTIFFVAL ;
+
+  if ( (err = parseOpts( argc, argv, rt )) ) goto exit ;
+  
+  if ( (err = rt->in->open( rt->in, rt )) ) goto exit ;
+  if ( (err = rt->out->open( rt->out, rt )) ) goto exit ;
+
+  while ( rt->in->hasMore( rt->in ) ) {
+    if ( (err = rt->in->next( rt->in, &r, &g, &b )) ) goto exit ;
+    if ( (err = rt->conv->convert( rt->conv, rt, r, g, b, &c, &m, &y, &k )) )
+      goto exit ;
+    if ( (err = rt->out->write( rt->out, c, m, y, k )) ) goto exit ;
+  }
+
+ exit:
+
+  if ( rt && rt->out && rt->out->close ) rt->out->close( rt->out ) ;
+  if ( rt && rt->conv && rt->conv->close ) rt->conv->close( rt->conv ) ;
+  if ( rt && rt->in && rt->in->close ) rt->in->close( rt->in ) ;
+  if ( rt ) free( rt ) ;
+
+  if ( err == ERR_ARG ) printUsage( ) ;
+
+  return err ;
+}
+
+
+
diff --git a/converter/other/pnmtoxwd.c b/converter/other/pnmtoxwd.c
new file mode 100644
index 00000000..32fb8a7b
--- /dev/null
+++ b/converter/other/pnmtoxwd.c
@@ -0,0 +1,505 @@
+/* pnmtoxwd.c - read a portable anymap and produce a color X11 window dump
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "x11wd.h"
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespecs of input file */
+    unsigned int pseudodepth;
+    unsigned int directcolor;
+};
+
+
+
+static void 
+parseCommandLine(int argc, 
+                 char ** argv, 
+                 struct cmdlineInfo  * const cmdlineP) {
+/* --------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+--------------------------------------------------------------------------*/
+    optEntry *option_def;
+    /* Instructions to optParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int depthSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+  
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "directcolor", OPT_FLAG,    NULL,   &cmdlineP->directcolor,  0);
+    OPTENT3(0, "pseudodepth",    OPT_UINT,  &cmdlineP->pseudodepth,    
+            &depthSpec,          0);
+  
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;   /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+    /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!depthSpec)
+        cmdlineP->pseudodepth = 8;
+    else {
+        if (cmdlineP->pseudodepth < 1)
+            pm_error("-pseudodepth option value must be at least 1.  "
+                     "You specified %u", cmdlineP->pseudodepth);
+        else if (cmdlineP->pseudodepth > 16)
+            pm_error("-pseudodepth option value must be at most 16.  "
+                     "You specified %u", cmdlineP->pseudodepth);
+    }
+
+    if (argc-1 == 0) 
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+static void
+setupX11Header(X11WDFileHeader * const h11P,
+               const char * const dumpname,
+               unsigned int const cols,
+               unsigned int const rows,
+               int          const format,
+               bool         const direct,
+               bool         const grayscale,
+               unsigned int const colors,
+               unsigned int const pseudodepth) {
+
+    /* Set up the header. */
+
+    h11P->header_size = sizeof(*h11P) + (xwdval) strlen(dumpname) + 1;
+    h11P->file_version = X11WD_FILE_VERSION;
+    h11P->pixmap_format = ZPixmap;
+    h11P->pixmap_width = cols;
+    h11P->pixmap_height = rows;
+    h11P->xoffset = 0;
+    h11P->byte_order = MSBFirst;
+    h11P->bitmap_bit_order = MSBFirst;
+    h11P->window_width = cols;
+    h11P->window_height = rows;
+    h11P->window_x = 0;
+    h11P->window_y = 0;
+    h11P->window_bdrwidth = 0;
+
+    if (direct) {
+        h11P->pixmap_depth = 24;
+        h11P->bitmap_unit = 32;
+        h11P->bitmap_pad = 32;
+        h11P->bits_per_pixel = 32;
+        h11P->visual_class = DirectColor;
+        h11P->colormap_entries = 256;
+        h11P->ncolors = 256;
+        h11P->red_mask = 0xff0000;
+        h11P->green_mask = 0xff00;
+        h11P->blue_mask = 0xff;
+        h11P->bytes_per_line = cols * 4;
+    } else {  /* pseudocolor -- i.e. regular paletted raster */
+        if (grayscale) {
+            if (PNM_FORMAT_TYPE(format) == PBM_TYPE) {
+                h11P->pixmap_depth = 1;
+                h11P->bits_per_pixel = 1;
+                h11P->colormap_entries = colors;
+                h11P->bytes_per_line = (cols + 7) / 8;
+            } else {
+                h11P->pixmap_depth = pseudodepth;
+                h11P->bits_per_pixel = pseudodepth;
+                h11P->colormap_entries = colors;
+                h11P->bytes_per_line = cols;
+            }
+            h11P->bitmap_unit = 8;
+            h11P->bitmap_pad = 8;
+            h11P->visual_class = StaticGray;
+            h11P->red_mask = 0;
+            h11P->green_mask = 0;
+            h11P->blue_mask = 0;
+        } else {
+            h11P->pixmap_depth = pseudodepth;
+            h11P->bits_per_pixel = pseudodepth;
+            h11P->visual_class = PseudoColor;
+            h11P->colormap_entries = 1 << pseudodepth;
+            h11P->red_mask = 0;
+            h11P->green_mask = 0;
+            h11P->blue_mask = 0;
+            h11P->bytes_per_line = cols;
+            h11P->bitmap_unit = 8;
+            h11P->bitmap_pad = 8;
+        }
+        h11P->ncolors = colors;
+    }
+    h11P->bits_per_rgb = h11P->pixmap_depth;
+}
+
+
+
+
+static void
+writeX11Header(X11WDFileHeader const h11,
+               FILE *          const ofP) {
+
+    /* Write out the header in big-endian order. */
+
+    pm_writebiglong(ofP, h11.header_size);
+    pm_writebiglong(ofP, h11.file_version);
+    pm_writebiglong(ofP, h11.pixmap_format);
+    pm_writebiglong(ofP, h11.pixmap_depth);
+    pm_writebiglong(ofP, h11.pixmap_width);
+    pm_writebiglong(ofP, h11.pixmap_height);
+    pm_writebiglong(ofP, h11.xoffset);
+    pm_writebiglong(ofP, h11.byte_order);
+    pm_writebiglong(ofP, h11.bitmap_unit);
+    pm_writebiglong(ofP, h11.bitmap_bit_order);
+    pm_writebiglong(ofP, h11.bitmap_pad);
+    pm_writebiglong(ofP, h11.bits_per_pixel);
+    pm_writebiglong(ofP, h11.bytes_per_line);
+    pm_writebiglong(ofP, h11.visual_class);
+    pm_writebiglong(ofP, h11.red_mask);
+    pm_writebiglong(ofP, h11.green_mask);
+    pm_writebiglong(ofP, h11.blue_mask);
+    pm_writebiglong(ofP, h11.bits_per_rgb);
+    pm_writebiglong(ofP, h11.colormap_entries);
+    pm_writebiglong(ofP, h11.ncolors);
+    pm_writebiglong(ofP, h11.window_width);
+    pm_writebiglong(ofP, h11.window_height);
+    pm_writebiglong(ofP, h11.window_x);
+    pm_writebiglong(ofP, h11.window_y);
+    pm_writebiglong(ofP, h11.window_bdrwidth);
+}
+
+
+
+static void
+writePseudoColormap(FILE *           const ofP,
+                    colorhist_vector const chv,
+                    unsigned int     const colors,
+                    bool             const grayscale,
+                    bool             const backwardMap,
+                    xelval           const maxval) {
+    /* Write out the colormap, big-endian order. */
+    
+    X11XColor color;
+    unsigned int i;
+
+    color.flags = 7;
+    color.pad = 0;
+    for (i = 0; i < colors; ++i) {
+        color.num = i;
+        if (grayscale) {
+            /* Stupid hack because xloadimage and xwud disagree on
+               how to interpret bitmaps. 
+            */
+            if (backwardMap)
+                color.red = (long) (colors-1-i) * 65535L / (colors - 1);
+            else
+                color.red = (long) i * 65535L / (colors - 1);
+            
+            color.green = color.red;
+            color.blue = color.red;
+        } else {
+            color.red = PPM_GETR(chv[i].color);
+            color.green = PPM_GETG(chv[i].color);
+            color.blue = PPM_GETB(chv[i].color);
+            if (maxval != 65535L) {
+                color.red = (long) color.red * 65535L / maxval;
+                color.green = (long) color.green * 65535L / maxval;
+                color.blue = (long) color.blue * 65535L / maxval;
+            }
+        }
+        pm_writebiglong( ofP, color.num);
+        pm_writebigshort(ofP, color.red);
+        pm_writebigshort(ofP, color.green);
+        pm_writebigshort(ofP, color.blue);
+        putc(color.flags, ofP);
+        putc(color.pad,   ofP);
+    }
+}
+
+
+
+static void
+writeDirectColormap(FILE * const ofP) {
+/*----------------------------------------------------------------------------
+   Write the XWD colormap.
+
+   We use a constant (independent of input) color map which simply has
+   each of the values 0-255, scaled to 16 bits, for each of the three
+   maps
+-----------------------------------------------------------------------------*/
+    X11XColor color;
+    unsigned int i;
+
+    color.flags = 7;
+    color.pad = 0;
+
+    for (i = 0; i < 256; ++i) {
+        color.red   = (short)(i << 8 | i);
+        color.green = (short)(i << 8 | i);
+        color.blue  = (short)(i << 8 | i);
+        color.num   = i << 16 | i << 8 | i;
+        
+        pm_writebiglong( ofP, color.num);
+        pm_writebigshort(ofP, color.red);
+        pm_writebigshort(ofP, color.green);
+        pm_writebigshort(ofP, color.blue);
+        putc(color.flags, ofP);
+        putc(color.pad,   ofP);
+    }
+}
+
+
+
+static void
+writeRowDirect(FILE *       const ofP,
+               xel *        const xelrow,
+               unsigned int const cols,
+               int          const format,
+               long         const xmaxval,
+               xelval       const maxval) {
+    
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PPM_TYPE: {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            unsigned long const ul = 
+                ((PPM_GETR(xelrow[col]) * xmaxval / maxval) << 16) |
+                ((PPM_GETG(xelrow[col]) * xmaxval / maxval) << 8) |
+                (PPM_GETB(xelrow[col]) * xmaxval / maxval);
+            pm_writebiglong(ofP, ul);
+        }
+    }
+    break;
+
+    default: {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            unsigned long const val = PNM_GET1(xelrow[col]);
+            unsigned long const ul =
+                ((val * xmaxval / maxval) << 16) |
+                ((val * xmaxval / maxval) << 8) |
+                (val * xmaxval / maxval);
+            pm_writebiglong(ofP, ul);
+        }
+        break;
+    }
+    }
+}
+
+
+
+static void
+writeRowGrayscale(FILE *       const ofP,
+                  xel *        const xelrow,
+                  unsigned int const cols,
+                  xelval       const maxval,
+                  bool         const backwardMap,
+                  unsigned int const bitsPerPixel) {
+
+    xelval bigger_maxval;
+    int bitshift;
+    unsigned char byte;
+    unsigned int col;
+    
+    bigger_maxval = pm_bitstomaxval(bitsPerPixel);
+    bitshift = 8 - bitsPerPixel;
+    byte = 0;
+    for (col = 0; col < cols; ++col) {
+        xelval s;
+        s = PNM_GET1(xelrow[col]);
+        if (backwardMap)
+            s = 1 - s;
+        
+        if (maxval != bigger_maxval)
+            s = (long) s * bigger_maxval / maxval;
+        byte |= s << bitshift;
+        bitshift -= bitsPerPixel;
+        if (bitshift < 0) {
+            putc(byte, stdout);
+            bitshift = 8 - bitsPerPixel;
+            byte = 0;
+        }
+    }
+    if (bitshift < 8 - bitsPerPixel)
+        putc(byte, ofP);
+}
+
+
+
+static void
+writeRowPseudoColor(FILE *          const ofP,
+                    xel *           const xelrow,
+                    unsigned int    const cols,
+                    colorhash_table const cht) {
+                       
+    unsigned int col;
+
+    for (col = 0; col < cols; ++col)
+        putc(ppm_lookupcolor(cht, &xelrow[col]), ofP);
+}
+
+
+
+static void
+writeRaster(FILE *           const ofP,
+            xel **           const xels,
+            unsigned int     const cols,
+            unsigned int     const rows,
+            int              const format,
+            xelval           const maxval,
+            long             const xmaxval,
+            colorhist_vector const chv,
+            unsigned int     const colors,
+            bool             const direct,
+            bool             const grayscale,
+            bool             const backwardMap,
+            unsigned int     const bitsPerPixel) {
+
+    unsigned int row;
+    colorhash_table cht;
+
+    if (chv)
+        cht = ppm_colorhisttocolorhash(chv, colors);
+            
+    for (row = 0; row < rows; ++row) {
+        if (direct)
+            writeRowDirect(ofP, xels[row], cols, format, xmaxval, maxval);
+        else {
+            if (grayscale)
+                writeRowGrayscale(ofP, xels[row], cols, maxval, backwardMap,
+                                  bitsPerPixel);
+            else
+                writeRowPseudoColor(ofP, xels[row], cols, cht);
+        }
+    }
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE* ifP;
+    xel ** xels;
+    int rows, cols, format, colors;
+    xelval maxval;
+    long xmaxval;
+    colorhist_vector chv;
+    X11WDFileHeader h11;
+    bool direct, grayscale;
+    const char * dumpname;
+    bool backwardMap;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    xels = pnm_readpnm(ifP, &cols, &rows, &maxval, &format);
+    xmaxval = (1 << cmdline.pseudodepth) - 1;
+    pm_close(ifP);
+    
+    if (cmdline.directcolor) {
+        direct = TRUE;
+        grayscale = FALSE;
+        chv = NULL;
+    } else {
+        /* Figure out the colormap. */
+        switch (PNM_FORMAT_TYPE(format)) {
+        case PPM_TYPE:
+            pm_message("computing colormap...");
+            chv = ppm_computecolorhist(xels, cols, rows, xmaxval+1, &colors);
+            if (!chv) {
+                pm_message("Too many colors - "
+                           "proceeding to write a 24-bit DirectColor "
+                           "dump file.  If you want PseudoColor, "
+                           "try doing a 'pnmquant %ld'.",
+                           xmaxval);
+                direct = TRUE;
+            } else {
+                pm_message("%d colors found", colors);
+                direct = FALSE;
+            }
+            grayscale = FALSE;
+            break;
+
+        case PBM_TYPE:
+            chv = NULL;
+            direct = FALSE;
+            grayscale = TRUE;
+            colors = 2;
+            break;
+        case PGM_TYPE:
+            chv = NULL;
+            direct = FALSE;
+            grayscale = TRUE;
+            colors = xmaxval + 1;
+            break;
+        default:
+            pm_error("INTERNAL ERROR: impossible format type");
+        }
+    }
+
+    if (STREQ(cmdline.inputFilespec, "-"))
+        dumpname = "stdin";
+    else {
+        if (strlen(cmdline.inputFilespec) > XWDVAL_MAX - sizeof(h11) - 1)
+            pm_error("Input file name is ridiculously long.");
+        else
+            dumpname = cmdline.inputFilespec;
+    }
+
+    setupX11Header(&h11, dumpname, cols, rows, format, 
+                   direct, grayscale, colors,
+                   cmdline.pseudodepth);
+
+    writeX11Header(h11, stdout);
+
+    /* Write out the dump name. */
+    fwrite(dumpname, 1, strlen(dumpname) + 1, stdout);
+
+    backwardMap = PNM_FORMAT_TYPE(format) == PBM_TYPE;
+
+    if (direct)
+        writeDirectColormap(stdout);
+    else
+        writePseudoColormap(stdout, chv, colors, 
+                            grayscale, backwardMap, maxval);
+    
+    writeRaster(stdout, xels, cols, rows, format, maxval, xmaxval, 
+                chv, colors, direct, grayscale,
+                backwardMap, h11.bits_per_pixel);
+
+    return 0;
+}
diff --git a/converter/other/ppmtopgm.c b/converter/other/ppmtopgm.c
new file mode 100644
index 00000000..86e7ae6a
--- /dev/null
+++ b/converter/other/ppmtopgm.c
@@ -0,0 +1,95 @@
+/* ppmtopgm.c - convert a portable pixmap to a portable graymap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "pgm.h"
+#include "lum.h"
+
+static void
+convertRaster(FILE *       const ifP,
+              unsigned int const cols,
+              unsigned int const rows,
+              pixval       const maxval,
+              int          const format, 
+              pixel *      const inputRow,
+              gray *       const outputRow, 
+              FILE *       const ofP) {
+
+    unsigned int row;
+
+    for (row = 0; row < rows; ++row) {
+        ppm_readppmrow( ifP, inputRow, cols, maxval, format );
+        if (maxval <= 255) {
+            /* Use fast approximation to 0.299 r + 0.587 g + 0.114 b */
+            unsigned int col;
+            for (col = 0; col < cols; ++col)
+                outputRow[col] = (gray) ppm_fastlumin(inputRow[col]);
+        } else {
+            /* Can't use fast approximation, so fall back on floats. */
+            int col;
+            for (col = 0; col < cols; ++col) 
+                outputRow[col] = (gray) (PPM_LUMIN(inputRow[col]) + 0.5);
+        }
+        pgm_writepgmrow(ofP, outputRow, cols, maxval, 0);
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE* ifP;
+    const char * inputFilespec;
+    int eof;
+    
+    ppm_init( &argc, argv );
+
+    if (argc-1 > 1)
+        pm_error("The only argument is the (optional) input filename");
+
+    if (argc == 2)
+        inputFilespec = argv[1];
+    else
+        inputFilespec = "-";
+    
+    ifP = pm_openr(inputFilespec);
+
+    eof = FALSE;  /* initial assumption */
+
+    while (!eof) {
+        ppm_nextimage(ifP, &eof);
+        if (!eof) {
+            int rows, cols, format;
+            pixval maxval;
+            pixel* inputRow;
+            gray* outputRow;
+
+            ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
+            pgm_writepgminit(stdout, cols, rows, maxval, 0);
+
+            inputRow = ppm_allocrow(cols);
+            outputRow = pgm_allocrow(cols);
+
+            convertRaster(ifP, cols, rows, maxval, format, 
+                          inputRow, outputRow, stdout);
+
+            ppm_freerow(inputRow);
+            pgm_freerow(outputRow);
+        }
+    }
+    pm_close(ifP);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/converter/other/pstopnm.c b/converter/other/pstopnm.c
new file mode 100644
index 00000000..a31c3f64
--- /dev/null
+++ b/converter/other/pstopnm.c
@@ -0,0 +1,899 @@
+/*----------------------------------------------------------------------------
+                                 pstopnm
+------------------------------------------------------------------------------
+  Use Ghostscript to convert a Postscript file into a PBM, PGM, or PNM
+  file.
+
+  Implementation note: This program feeds the input file to Ghostcript
+  directly (with possible statements preceding it), and uses
+  Ghostscript's PNM output device drivers.  As an alternative,
+  Ghostscript also comes with the Postscript program pstoppm.ps which
+  we could run and it would read the input file and produce PNM
+  output.  It isn't clear to me what pstoppm.ps adds to what you get
+  from just feeding your input directly to Ghostscript as the main program.
+
+-----------------------------------------------------------------------------*/
+
+#define _BSD_SOURCE 1   /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  
+    /* Make sure fdopen() is in stdio.h and strdup() is in string.h */
+
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <sys/wait.h>  
+#include <sys/stat.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "nstring.h"
+
+enum orientation {PORTRAIT, LANDSCAPE, UNSPECIFIED};
+struct box {
+    /* Description of a rectangle within an image; all coordinates 
+       measured in points (1/72") with lower left corner of page being the 
+       origin.
+    */
+    int llx;  /* lower left X coord */
+        /* -1 for llx means whole box is undefined. */
+    int lly;  /* lower left Y coord */
+    int urx;  /* upper right X coord */
+    int ury;  /* upper right Y coord */
+};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec;  /* Filespecs of input files */
+    unsigned int forceplain;
+    struct box extract_box;
+    unsigned int nocrop;
+    unsigned int format_type;
+    unsigned int verbose;
+    float xborder;
+    unsigned int xmax;
+    unsigned int xsize;  /* zero means unspecified */
+    float yborder;
+    unsigned int ymax;
+    unsigned int ysize;  /* zero means unspecified */
+    unsigned int dpi;    /* zero means unspecified */
+    enum orientation orientation;
+    unsigned int goto_stdout;
+};
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc( 100*sizeof( optEntry ) );
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int pbm_opt, pgm_opt, ppm_opt;
+    unsigned int portrait_opt, landscape_opt;
+    float llx, lly, urx, ury;
+    unsigned int llxSpec, llySpec, urxSpec, urySpec;
+    unsigned int xmaxSpec, ymaxSpec, xsizeSpec, ysizeSpec, dpiSpec;
+    
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "forceplain", OPT_FLAG,  NULL, &cmdlineP->forceplain,     0);
+    OPTENT3(0, "llx",        OPT_FLOAT, &llx, &llxSpec,                  0);
+    OPTENT3(0, "lly",        OPT_FLOAT, &lly, &llySpec,                  0);
+    OPTENT3(0, "urx",        OPT_FLOAT, &urx, &urxSpec,                  0);
+    OPTENT3(0, "ury",        OPT_FLOAT, &ury, &urySpec,                  0);
+    OPTENT3(0, "nocrop",     OPT_FLAG,  NULL, &cmdlineP->nocrop,         0);
+    OPTENT3(0, "pbm",        OPT_FLAG,  NULL, &pbm_opt,                  0);
+    OPTENT3(0, "pgm",        OPT_FLAG,  NULL, &pgm_opt,                  0);
+    OPTENT3(0, "ppm",        OPT_FLAG,  NULL, &ppm_opt,                  0);
+    OPTENT3(0, "verbose",    OPT_FLAG,  NULL, &cmdlineP->verbose,        0);
+    OPTENT3(0, "xborder",    OPT_FLOAT, &cmdlineP->xborder, NULL,        0);
+    OPTENT3(0, "xmax",       OPT_UINT,  &cmdlineP->xmax, &xmaxSpec,      0);
+    OPTENT3(0, "xsize",      OPT_UINT,  &cmdlineP->xsize, &xsizeSpec,    0);
+    OPTENT3(0, "yborder",    OPT_FLOAT, &cmdlineP->yborder, NULL,        0);
+    OPTENT3(0, "ymax",       OPT_UINT,  &cmdlineP->ymax, &ymaxSpec,      0);
+    OPTENT3(0, "ysize",      OPT_UINT,  &cmdlineP->ysize, &ysizeSpec,    0);
+    OPTENT3(0, "dpi",        OPT_UINT,  &cmdlineP->dpi, &dpiSpec,        0);
+    OPTENT3(0, "portrait",   OPT_FLAG,  NULL, &portrait_opt,             0);
+    OPTENT3(0, "landscape",  OPT_FLAG,  NULL, &landscape_opt,            0);
+    OPTENT3(0, "stdout",     OPT_FLAG,  NULL, &cmdlineP->goto_stdout,    0);
+
+    /* Set the defaults */
+    cmdlineP->xborder = cmdlineP->yborder = 0.1;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (xmaxSpec) {
+        if (cmdlineP->xmax == 0)
+            pm_error("zero is not a valid value for -xmax");
+    } else
+        cmdlineP->xmax = 612;
+
+    if (ymaxSpec) {
+        if (cmdlineP->ymax == 0)
+            pm_error("zero is not a valid value for -ymax");
+    } else 
+        cmdlineP->ymax = 792;
+
+    if (xsizeSpec) {
+        if (cmdlineP->xsize == 0)
+            pm_error("zero is not a valid value for -xsize");
+    } else
+        cmdlineP->xsize = 0;
+
+    if (ysizeSpec) {
+        if (cmdlineP->ysize == 0)
+            pm_error("zero is not a valid value for -ysize");
+    } else 
+        cmdlineP->ysize = 0;
+
+    if (portrait_opt & !landscape_opt)
+        cmdlineP->orientation = PORTRAIT;
+    else if (!portrait_opt & landscape_opt)
+        cmdlineP->orientation = LANDSCAPE;
+    else if (!portrait_opt & !landscape_opt)
+        cmdlineP->orientation = UNSPECIFIED;
+    else
+        pm_error("Cannot specify both -portrait and -landscape options");
+
+    if (pbm_opt)
+        cmdlineP->format_type = PBM_TYPE;
+    else if (pgm_opt)
+        cmdlineP->format_type = PGM_TYPE;
+    else if (ppm_opt)
+        cmdlineP->format_type = PPM_TYPE;
+    else
+        cmdlineP->format_type = PPM_TYPE;
+
+    /* If any one of the 4 bounding box coordinates is given on the
+       command line, we default any of the 4 that aren't.  
+    */
+    if (llxSpec || llySpec || urxSpec || urySpec) {
+        if (!llxSpec) cmdlineP->extract_box.llx = 72;
+        else cmdlineP->extract_box.llx = llx * 72;
+        if (!llySpec) cmdlineP->extract_box.lly = 72;
+        else cmdlineP->extract_box.lly = lly * 72;
+        if (!urxSpec) cmdlineP->extract_box.urx = 540;
+        else cmdlineP->extract_box.urx = urx * 72;
+        if (!urySpec) cmdlineP->extract_box.ury = 720;
+        else cmdlineP->extract_box.ury = ury * 72;
+    } else {
+        cmdlineP->extract_box.llx = -1;
+    }
+
+    if (dpiSpec) {
+        if (cmdlineP->dpi == 0)
+            pm_error("Zero is not a valid value for -dpi");
+    } else
+        cmdlineP->dpi = 0;
+
+    if (dpiSpec && xsizeSpec + ysizeSpec + xmaxSpec + ymaxSpec > 0)
+        pm_error("You may not specify both size options and -dpi");
+
+    if (argc-1 == 0)
+        cmdlineP->input_filespec = "-";  /* stdin */
+    else if (argc-1 == 1)
+        cmdlineP->input_filespec = argv[1];
+    else 
+        pm_error("Too many arguments (%d).  "
+                 "Only need one: the Postscript filespec", argc-1);
+}
+
+
+
+static void
+add_ps_to_filespec(const char orig_filespec[], char ** const new_filespec_p,
+                   const int verbose) {
+/*----------------------------------------------------------------------------
+   If orig_filespec[] does not name an existing file, but the same
+   name with ".ps" added to the end does, return the name with the .ps
+   attached.  Otherwise, just return orig_filespec[].
+
+   Return the name in newly malloc'ed storage, pointed to by
+   *new_filespec_p.
+-----------------------------------------------------------------------------*/
+    struct stat statbuf;
+    int stat_rc;
+
+    stat_rc = lstat(orig_filespec, &statbuf);
+    
+    if (stat_rc == 0)
+        *new_filespec_p = strdup(orig_filespec);
+    else {
+        const char *filespec_plus_ps;
+
+        asprintfN(&filespec_plus_ps, "%s.ps", orig_filespec);
+
+        stat_rc = lstat(filespec_plus_ps, &statbuf);
+        if (stat_rc == 0)
+            *new_filespec_p = strdup(filespec_plus_ps);
+        else
+            *new_filespec_p = strdup(orig_filespec);
+        strfree(filespec_plus_ps);
+    }
+    if (verbose)
+        pm_message("Input file is %s", *new_filespec_p);
+}
+
+
+
+static void
+computeSizeResFromSizeSpec(unsigned int   const requestedXsize,
+                           unsigned int   const requestedYsize,
+                           unsigned int   const imageWidth,
+                           unsigned int   const imageHeight,
+                           unsigned int * const xsizeP,
+                           unsigned int * const ysizeP,
+                           unsigned int * const xresP,
+                           unsigned int * const yresP) {
+
+    if (requestedXsize) {
+        *xsizeP = requestedXsize;
+        *xresP = (unsigned int) (requestedXsize * 72 / imageWidth + 0.5);
+        if (!requestedYsize) {
+            *yresP = *xresP;
+            *ysizeP = (unsigned int) (imageHeight * (float)*yresP/72 + 0.5);
+            }
+        }
+
+    if (requestedYsize) {
+        *ysizeP = requestedYsize;
+        *yresP = (unsigned int) (requestedYsize * 72 / imageHeight + 0.5);
+        if (!requestedXsize) {
+            *xresP = *yresP;
+            *xsizeP = (unsigned int) (imageWidth * (float)*xresP/72 + 0.5);
+        }
+    } 
+}
+
+
+
+static void
+computeSizeResBlind(unsigned int   const xmax,
+                    unsigned int   const ymax,
+                    unsigned int   const imageWidth,
+                    unsigned int   const imageHeight,
+                    bool           const nocrop,
+                    unsigned int * const xsizeP,
+                    unsigned int * const ysizeP,
+                    unsigned int * const xresP,
+                    unsigned int * const yresP) {
+
+    *xresP = *yresP = MIN(xmax * 72 / imageWidth, 
+                          ymax * 72 / imageHeight);
+    
+    if (nocrop) {
+        *xsizeP = xmax;
+        *ysizeP = ymax;
+    } else {
+        *xsizeP = (unsigned int) (imageWidth * (float)*xresP / 72 + 0.5);
+        *ysizeP = (unsigned int) (imageHeight * (float)*yresP / 72 + 0.5);
+    }
+}
+
+
+
+static void
+compute_size_res(struct cmdlineInfo const cmdline, 
+                 enum orientation   const orientation, 
+                 struct box         const bordered_box,
+                 unsigned int *     const xsizeP, 
+                 unsigned int *     const ysizeP,
+                 unsigned int *     const xresP, 
+                 unsigned int *     const yresP) {
+/*----------------------------------------------------------------------------
+  Figure out how big the output image should be (return as
+  *xsizeP and *ysizeP) and what output device resolution Ghostscript
+  should assume (return as *xresP, *yresP).
+
+  A resolution number is the number of pixels per inch that the a
+  printer prints.  Since we're emulating a printed page with a PNM
+  image, and a PNM image has no spatial dimension (you can't say how
+  many inches wide a PNM image is), it's kind of confusing.  
+
+  If the user doesn't select a resolution, we choose the resolution
+  that causes the image to be a certain number of pixels, knowing how
+  big (in inches) Ghostscript wants the printed picture to be.  For
+  example, the part of the Postscript image we are going to print is 2
+  inches wide.  We want the PNM image to be 1000 pixels wide.  So we
+  tell Ghostscript that our horizontal output device resolution is 500
+  pixels per inch.
+  
+  *xresP and *yresP are in dots per inch.
+-----------------------------------------------------------------------------*/
+    unsigned int sx, sy;
+        /* The horizontal and vertical sizes of the input image, in points
+           (1/72 inch)
+        */
+
+    if (orientation == LANDSCAPE) {
+        sx = bordered_box.ury - bordered_box.lly;
+        sy = bordered_box.urx - bordered_box.llx;
+    } else {
+        sx = bordered_box.urx - bordered_box.llx;
+        sy = bordered_box.ury - bordered_box.lly;
+    }
+
+    if (cmdline.dpi) {
+        /* User gave resolution; we figure out output image size */
+        *xresP = *yresP = cmdline.dpi;
+        *xsizeP = (int) (cmdline.dpi * sx / 72 + 0.5);
+        *ysizeP = (int) (cmdline.dpi * sy / 72 + 0.5);
+    } else  if (cmdline.xsize || cmdline.ysize)
+        computeSizeResFromSizeSpec(cmdline.xsize, cmdline.ysize, sx, sy,
+                                   xsizeP, ysizeP, xresP, yresP);
+    else 
+        computeSizeResBlind(cmdline.xmax, cmdline.ymax, sx, sy, cmdline.nocrop,
+                            xsizeP, ysizeP, xresP, yresP);
+
+    if (cmdline.verbose) {
+        pm_message("output is %u pixels wide X %u pixels high",
+                   *xsizeP, *ysizeP);
+        pm_message("output device resolution is %u dpi horiz, %u dpi vert",
+                   *xresP, *yresP);
+    }
+}
+
+
+
+enum postscript_language {COMMON_POSTSCRIPT, ENCAPSULATED_POSTSCRIPT};
+
+static enum postscript_language
+language_declaration(const char input_filespec[], int const verbose) {
+/*----------------------------------------------------------------------------
+  Return the Postscript language in which the file declares it is written.
+  (Except that if the file is on Standard Input or doesn't validly declare
+  a languages, just say it is Common Postscript).
+-----------------------------------------------------------------------------*/
+    enum postscript_language language;
+
+    if (STREQ(input_filespec, "-"))
+        /* Can't read stdin, because we need it to remain positioned for the 
+           Ghostscript interpreter to read it.
+        */
+        language = COMMON_POSTSCRIPT;
+    else {
+        FILE *infile;
+        char line[80];
+
+        infile = pm_openr(input_filespec);
+
+        if (fgets(line, sizeof(line), infile) == NULL)
+            language = COMMON_POSTSCRIPT;
+        else {
+            const char eps_header[] = " EPSF-";
+
+            if (strstr(line, eps_header))
+                language = ENCAPSULATED_POSTSCRIPT;
+            else
+                language = COMMON_POSTSCRIPT;
+        }
+        fclose(infile);
+    }
+    if (verbose)
+        pm_message("language is %s",
+                   language == ENCAPSULATED_POSTSCRIPT ?
+                   "encapsulated postscript" :
+                   "not encapsulated postscript");
+    return language;
+}
+
+
+
+static struct box
+compute_box_to_extract(struct box const cmdline_extract_box,
+                       char       const input_filespec[],
+                       bool       const verbose) {
+
+    struct box retval;
+
+    if (cmdline_extract_box.llx != -1)
+        /* User told us what box to extract, so that's what we'll do */
+        retval = cmdline_extract_box;
+    else {
+        /* Try to get the bounding box from the DSC %%BoundingBox
+           statement (A Postscript comment) in the input.
+        */
+        struct box ps_bb;  /* Box described by %%BoundingBox stmt in input */
+
+        if (STREQ(input_filespec, "-"))
+            /* Can't read stdin, because we need it to remain
+               positioned for the Ghostscript interpreter to read it.  
+            */
+            ps_bb.llx = -1;
+        else {
+            FILE *infile;
+            int found_BB, eof;  /* logical */
+            infile = pm_openr(input_filespec);
+            
+            found_BB = FALSE;
+            eof = FALSE;
+            while (!eof && !found_BB) {
+                char line[200];
+                
+                if (fgets(line, sizeof(line), infile) == NULL)
+                    eof = TRUE;
+                else {
+                    int rc;
+                    rc = sscanf(line, "%%%%BoundingBox: %d %d %d %d",
+                                &ps_bb.llx, &ps_bb.lly, 
+                                &ps_bb.urx, &ps_bb.ury);
+                    if (rc == 4) 
+                        found_BB = TRUE;
+                }
+            }
+            fclose(infile);
+
+            if (!found_BB) {
+                ps_bb.llx = -1;
+                pm_message("Warning: no %%%%BoundingBox statement "
+                           "in the input or command line.\n"
+                           "Will use defaults");
+            }
+        }
+        if (ps_bb.llx != -1) {
+            if (verbose)
+                pm_message("Using %%%%BoundingBox statement from input.");
+            retval = ps_bb;
+        } else { 
+            /* Use the center of an 8.5" x 11" page with 1" border all around*/
+            retval.llx = 72;
+            retval.lly = 72;
+            retval.urx = 540;
+            retval.ury = 720;
+        }
+    }
+    if (verbose)
+        pm_message("Extracting the box ((%d,%d),(%d,%d))",
+                   retval.llx, retval.lly, retval.urx, retval.ury);
+    return retval;
+}
+
+
+
+static enum orientation
+compute_orientation(struct cmdlineInfo const cmdline, 
+                    struct box         const extract_box) {
+
+    enum orientation retval;
+    unsigned int const input_width  = extract_box.urx - extract_box.llx;
+    unsigned int const input_height = extract_box.ury - extract_box.lly;
+
+    if (cmdline.orientation != UNSPECIFIED)
+        retval = cmdline.orientation;
+    else {
+        if ((!cmdline.xsize || !cmdline.ysize) &
+            (cmdline.xsize || cmdline.ysize)) {
+            /* User specified one output dimension, but not the other,
+               so we can't use output dimensions to make the decision.  So
+               just use the input dimensions.
+            */
+            if (input_height > input_width) retval = PORTRAIT;
+            else retval = LANDSCAPE;
+        } else {
+            int output_width, output_height;
+            if (cmdline.xsize) {
+                /* He gave xsize and ysize, so that's the output size */
+                output_width = cmdline.xsize;
+                output_height = cmdline.ysize;
+            } else {
+                /* Well then we'll just use his (or default) xmax, ymax */
+                output_width = cmdline.xmax;
+                output_height = cmdline.ymax;
+            }
+
+            if (input_height > input_width && output_height > output_width)
+                retval = PORTRAIT;
+            else if (input_height < input_width && 
+                     output_height < output_width)
+                retval = PORTRAIT;
+            else 
+                retval = LANDSCAPE;
+        }
+    }
+    return retval;
+}
+
+
+
+static struct box
+add_borders(const struct box input_box, 
+            const float xborder_scale, float yborder_scale,
+            const int verbose) {
+/*----------------------------------------------------------------------------
+   Return a box which is 'input_box' plus some borders.
+
+   Add left and right borders that are the fraction 'xborder_scale' of the
+   width of the input box; likewise for top and bottom borders with 
+   'yborder_scale'.
+-----------------------------------------------------------------------------*/
+    struct box retval;
+
+    const int left_right_border_size = 
+        (int) ((input_box.urx - input_box.llx) * xborder_scale + 0.5);
+    const int top_bottom_border_size = 
+        (int) ((input_box.ury - input_box.lly) * yborder_scale + 0.5);
+
+    retval.llx = input_box.llx - left_right_border_size;
+    retval.lly = input_box.lly - top_bottom_border_size;
+    retval.urx = input_box.urx + left_right_border_size;
+    retval.ury = input_box.ury + top_bottom_border_size;
+
+    if (verbose)
+        pm_message("With borders, extracted box is ((%d,%d),(%d,%d))",
+                   retval.llx, retval.lly, retval.urx, retval.ury);
+
+    return retval;
+}
+
+
+
+static const char *
+compute_pstrans(const struct box box, const enum orientation orientation,
+                const int xsize, const int ysize, 
+                const int xres, const int yres) {
+
+    const char * retval;
+
+    if (orientation == PORTRAIT) {
+        int llx, lly;
+        llx = box.llx - (xsize * 72 / xres - (box.urx - box.llx)) / 2;
+        lly = box.lly - (ysize * 72 / yres - (box.ury - box.lly)) / 2;
+        asprintfN(&retval, "%d neg %d neg translate", llx, lly);
+    } else {
+        int llx, ury;
+        llx = box.llx - (ysize * 72 / yres - (box.urx - box.llx)) / 2;
+        ury = box.ury + (xsize * 72 / xres - (box.ury - box.lly)) / 2;
+        asprintfN(&retval, "90 rotate %d neg %d neg translate", llx, ury);
+    }
+
+    if (retval == NULL)
+        pm_error("Unable to allocate memory for pstrans");
+
+    return retval;
+}
+
+
+
+static const char *
+compute_outfile_arg(const struct cmdlineInfo cmdline) {
+
+    const char *retval;  /* malloc'ed */
+
+    if (cmdline.goto_stdout)
+        retval = strdup("-");
+    else if (STREQ(cmdline.input_filespec, "-"))
+        retval = strdup("-");
+    else {
+        char * basename;
+        const char * suffix;
+        
+        basename  = strdup(cmdline.input_filespec);
+        if (strlen(basename) > 3 && 
+            STREQ(basename+strlen(basename)-3, ".ps")) 
+            /* The input filespec ends in ".ps".  Chop it off. */
+            basename[strlen(basename)-3] = '\0';
+
+        switch (cmdline.format_type) {
+        case PBM_TYPE: suffix = "pbm"; break;
+        case PGM_TYPE: suffix = "pgm"; break;
+        case PPM_TYPE: suffix = "ppm"; break;
+        default: pm_error("Internal error: invalid value for format_type: %d",
+                          cmdline.format_type);
+        }
+        asprintfN(&retval, "%s%%03d.%s", basename, suffix);
+
+        strfree(basename);
+    }
+    return(retval);
+}
+
+
+
+static const char *
+compute_gs_device(const int format_type, const int forceplain) {
+
+    const char * basetype;
+    const char * retval;
+
+    switch (format_type) {
+    case PBM_TYPE: basetype = "pbm"; break;
+    case PGM_TYPE: basetype = "pgm"; break;
+    case PPM_TYPE: basetype = "ppm"; break;
+    default: pm_error("Internal error: invalid value format_type");
+    }
+    if (forceplain)
+        retval = strdup(basetype);
+    else
+        asprintfN(&retval, "%sraw", basetype);
+
+    if (retval == NULL)
+        pm_error("Unable to allocate memory for gs device");
+
+    return(retval);
+}
+
+
+
+static void
+findGhostscriptProg(const char ** const retvalP) {
+    
+    *retvalP = NULL;  /* initial assumption */
+    if (getenv("GHOSTSCRIPT"))
+        *retvalP = strdup(getenv("GHOSTSCRIPT"));
+    if (*retvalP == NULL) {
+        if (getenv("PATH") != NULL) {
+            char *pathwork;  /* malloc'ed */
+            const char * candidate;
+
+            pathwork = strdup(getenv("PATH"));
+            
+            candidate = strtok(pathwork, ":");
+
+            *retvalP = NULL;
+            while (!*retvalP && candidate) {
+                struct stat statbuf;
+                const char * filename;
+                int rc;
+
+                asprintfN(&filename, "%s/gs", candidate);
+                rc = stat(filename, &statbuf);
+                if (rc == 0) {
+                    if (S_ISREG(statbuf.st_mode))
+                        *retvalP = strdup(filename);
+                } else if (errno != ENOENT)
+                    pm_error("Error looking for Ghostscript program.  "
+                             "stat(\"%s\") returns errno %d (%s)",
+                             filename, errno, strerror(errno));
+                strfree(filename);
+
+                candidate = strtok(NULL, ":");
+            }
+            free(pathwork);
+        }
+    }
+    if (*retvalP == NULL)
+        *retvalP = strdup("/usr/bin/gs");
+}
+
+
+
+static void
+execGhostscript(int const inputPipeFd,
+                const char ghostscript_device[],
+                const char outfile_arg[], 
+                int const xsize, int const ysize, 
+                int const xres, int const yres,
+                const char input_filespec[], int const verbose) {
+    
+    const char *arg0;
+    const char *ghostscriptProg;
+    const char *deviceopt;
+    const char *outfileopt;
+    const char *gopt;
+    const char *ropt;
+    int rc;
+
+    findGhostscriptProg(&ghostscriptProg);
+
+    /* Put the input pipe on Standard Input */
+    rc = dup2(inputPipeFd, STDIN_FILENO);
+    close(inputPipeFd);
+
+    asprintfN(&arg0, "gs");
+    asprintfN(&deviceopt, "-sDEVICE=%s", ghostscript_device);
+    asprintfN(&outfileopt, "-sOutputFile=%s", outfile_arg);
+    asprintfN(&gopt, "-g%dx%d", xsize, ysize);
+    asprintfN(&ropt, "-r%dx%d", xres, yres);
+
+    /* -dSAFER causes Postscript to disable %pipe and file operations,
+       which are almost certainly not needed here.  This prevents our
+       Postscript program from doing crazy unexpected things, possibly
+       as a result of a malicious booby trapping of our Postscript file.
+    */
+
+    if (verbose) {
+        pm_message("execing '%s' with args '%s' (arg 0), "
+                   "'%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'",
+                   ghostscriptProg, arg0,
+                   deviceopt, outfileopt, gopt, ropt, "-q", "-dNOPAUSE", 
+                   "-dSAFER", "-");
+    }
+
+    execl(ghostscriptProg, arg0, deviceopt, outfileopt, gopt, ropt, "-q",
+          "-dNOPAUSE", "-dSAFER", "-", NULL);
+    
+    pm_error("execl() of Ghostscript ('%s') failed, errno=%d (%s)",
+             ghostscriptProg, errno, strerror(errno));
+}
+
+
+
+
+static void
+execute_ghostscript(const char pstrans[], const char ghostscript_device[],
+                    const char outfile_arg[], 
+                    const int xsize, const int ysize, 
+                    const int xres, const int yres,
+                    const char input_filespec[], 
+                    const enum postscript_language language,
+                    const int verbose) {
+
+    int gs_exit;  /* wait4 exit code from Ghostscript */
+    FILE *gs;  /* Pipe to Ghostscript's standard input */
+    FILE *infile;
+    int rc;
+    int eof;  /* End of file on input */
+    int pipefd[2];
+
+    if (strlen(outfile_arg) > 80)
+        pm_error("output file spec too long.");
+    
+    rc = pipe(pipefd);
+    if (rc < 0)
+        pm_error("Unable to create pipe to talk to Ghostscript process.  "
+                 "errno = %d (%s)", errno, strerror(errno));
+    
+    rc = fork();
+    if (rc < 0)
+        pm_error("Unable to fork a Ghostscript process.  errno=%d (%s)",
+                 errno, strerror(errno));
+    else if (rc == 0) {
+        /* Child process */
+        close(pipefd[1]);
+        execGhostscript(pipefd[0], ghostscript_device, outfile_arg,
+                        xsize, ysize, xres, yres, input_filespec, verbose);
+    } else {
+        pid_t const ghostscriptPid = rc;
+        int const pipeToGhostscriptFd = pipefd[1];
+        /* parent process */
+        close(pipefd[0]);
+
+        gs = fdopen(pipeToGhostscriptFd, "w");
+        if (gs == NULL) 
+            pm_error("Unable to open stream on pipe to Ghostscript process.");
+    
+        infile = pm_openr(input_filespec);
+        /*
+          In encapsulated Postscript, we the encapsulator are supposed to
+          handle showing the page (which we do by passing a showpage
+          statement to Ghostscript).  Any showpage statement in the 
+          input must be defined to have no effect.
+          
+          See "Enscapsulated PostScript Format File Specification",
+          v. 3.0, 1 May 1992, in particular Example 2, p. 21.  I found
+          it at 
+          http://partners.adobe.com/asn/developer/pdfs/tn/5002.EPSF_Spec.pdf
+          The example given is a much fancier solution than we need
+          here, I think, so I boiled it down a bit.  JM 
+        */
+        if (language == ENCAPSULATED_POSTSCRIPT)
+            fprintf(gs, "\n/b4_Inc_state save def /showpage { } def\n");
+ 
+        if (verbose) 
+            pm_message("Postscript prefix command: '%s'", pstrans);
+
+        fprintf(gs, "%s\n", pstrans);
+
+        /* If our child dies, it closes the pipe and when we next write to it,
+           we get a SIGPIPE.  We must survive that signal in order to report
+           on the fate of the child.  So we ignore SIGPIPE:
+        */
+        signal(SIGPIPE, SIG_IGN);
+
+        eof = FALSE;
+        while (!eof) {
+            char buffer[4096];
+            int bytes_read;
+            
+            bytes_read = fread(buffer, 1, sizeof(buffer), infile);
+            if (bytes_read == 0) 
+                eof = TRUE;
+            else 
+                fwrite(buffer, 1, bytes_read, gs);
+        }
+        pm_close(infile);
+
+        if (language == ENCAPSULATED_POSTSCRIPT)
+            fprintf(gs, "\nb4_Inc_state restore showpage\n");
+
+        fclose(gs);
+        
+        waitpid(ghostscriptPid, &gs_exit, 0);
+        if (rc < 0)
+            pm_error("Wait for Ghostscript process to terminated failed.  "
+                     "errno = %d (%s)", errno, strerror(errno));
+
+        if (gs_exit != 0) {
+            if (WIFEXITED(gs_exit))
+                pm_error("Ghostscript failed.  Exit code=%d\n", 
+                         WEXITSTATUS(gs_exit));
+            else if (WIFSIGNALED(gs_exit))
+                pm_error("Ghostscript process died due to a signal %d.",
+                         WTERMSIG(gs_exit));
+            else 
+                pm_error("Ghostscript process died with exit code %d", 
+                         gs_exit);
+        }
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    struct cmdlineInfo cmdline;
+    char *input_filespec;  /* malloc'ed */
+        /* The file specification of our Postscript input file */
+    unsigned int xres, yres;    /* Resolution in pixels per inch */
+    unsigned int xsize, ysize;  /* output image size in pixels */
+    struct box extract_box;
+        /* coordinates of the box within the input we are to extract; i.e.
+           that will become the output. 
+           */
+    struct box bordered_box;
+        /* Same as above, but expanded to include borders */
+
+    enum postscript_language language;
+    enum orientation orientation;
+    const char *ghostscript_device;
+    const char *outfile_arg;
+    const char *pstrans;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    add_ps_to_filespec(cmdline.input_filespec, &input_filespec,
+                       cmdline.verbose);
+
+    extract_box = compute_box_to_extract(cmdline.extract_box, input_filespec, 
+                                         cmdline.verbose);
+
+    language = language_declaration(input_filespec, cmdline.verbose);
+    
+    orientation = compute_orientation(cmdline, extract_box);
+
+    bordered_box = add_borders(extract_box, cmdline.xborder, cmdline.yborder,
+                               cmdline.verbose);
+
+    compute_size_res(cmdline, orientation, bordered_box, 
+                     &xsize, &ysize, &xres, &yres);
+    
+    pstrans = compute_pstrans(bordered_box, orientation,
+                              xsize, ysize, xres, yres);
+
+    outfile_arg = compute_outfile_arg(cmdline);
+
+    ghostscript_device = 
+        compute_gs_device(cmdline.format_type, cmdline.forceplain);
+    
+    pm_message("Writing %s file", ghostscript_device);
+    
+    execute_ghostscript(pstrans, ghostscript_device, outfile_arg, 
+                        xsize, ysize, xres, yres, input_filespec,
+                        language, cmdline.verbose);
+
+    strfree(ghostscript_device);
+    strfree(outfile_arg);
+    strfree(pstrans);
+    
+    return 0;
+}
+
+
diff --git a/converter/other/pstopnm.csh b/converter/other/pstopnm.csh
new file mode 100755
index 00000000..adde3e6f
--- /dev/null
+++ b/converter/other/pstopnm.csh
@@ -0,0 +1,301 @@
+#!/bin/csh -f
+#
+#	Uses ghostscript to translate an Encapsulated PostScript file to
+#	Portable Anymap format file(s).
+#	pstopnm will create as many files as the number of pages in 
+#	the Postscript document.  The name of the files will be 
+#	psfile001.ppm, psfile002.ppm, etc.
+#	The ouput files will contain the area inside the BoundingBox.
+#	If BoundingBox parameters are not found in the PostScript
+#	document, default values are used.
+#
+#
+#       Usage: pstopnm [-forceplain] [-help] [-llx s] [-lly s] 
+#		       [-urx s] [-ury s] [-nocrop] [-pbm|-pgm|-ppm] 
+#		       [-verbose] [-xborder n] [-xmax n] [-xsize n] 
+#		       [-yborder n] [-ymax n] [-ysize n] 
+#		       [-portrait] [-landscape] psfile[.ps]
+# 
+# 	Copyright (C) 1992 by Alberto Accomazzi, Smithsonian Astrophysical
+#	Observatory (alberto@cfa.harvard.edu).
+#
+# 	Permission to use, copy, modify, and distribute this software and its
+#	documentation for any purpose and without fee is hereby granted, 
+#	provided that the above copyright notice appear in all copies and 
+#	that both that copyright notice and this permission notice appear 
+#	in supporting documentation.  This software is provided "as is" 
+#	without express or implied warranty.
+#
+set noglob
+
+set progname = $0
+set progname = $progname:t
+set filtertail = "raw"
+set filterhead = "ppm"
+set xsize = 0
+set ysize = 0
+set xres = ""
+set yres = ""
+
+# default values: max image x and y sizes
+set xmax = 612
+set ymax = 792
+# default values: image area fits in a 8.5x11 sheet with 1 inch border
+set llx = 72
+set lly = 72
+set urx = 540
+set ury = 720
+# default values: x and y borders are 10% of x and y size
+set xborder = "0.1"
+set yborder = "0.1"
+# default values: orientation is unknown
+set orient = 0
+
+set psfile = ""
+set USAGE = "Usage: $progname [-forceplain] [-help] [-llx s] [-lly s]\
+[-urx s] [-ury s] [-landscape] [-portrait]\
+[-nocrop] [-pbm|-pgm|-ppm] [-verbose] [-xborder s] [-xmax s]\
+[-xsize s] [-yborder s] [-ymax s] [-ysize s] psfile[.ps]"
+alias usage 'echo $USAGE; exit 1'
+
+while ($#argv > 0)
+    switch ($argv[1])
+    case -h*:   # -help
+        usage
+        breaksw
+    case -pbm:
+    case -pgm:
+    case -ppm:
+    	set filterhead = `echo "$argv[1]" | sed "s/-//1"`
+	breaksw
+    case -llx:
+        shift argv
+        if ($#argv == 0) eval usage
+    	set llx = `(echo "scale=4";echo "$argv[1] * 72")|bc -l`
+	set nobb
+	breaksw
+    case -lly:
+        shift argv
+        if ($#argv == 0) eval usage
+	set lly = `(echo "scale=4";echo "$argv[1] * 72")|bc -l`
+	set nobb
+	breaksw
+    case -urx:
+        shift argv
+        if ($#argv == 0) eval usage
+	set urx = `(echo "scale=4";echo "$argv[1] * 72")|bc -l`
+	set nobb
+	breaksw
+    case -ury:
+        shift argv
+        if ($#argv == 0) eval usage
+	set ury = `(echo "scale=4";echo "$argv[1] * 72")|bc -l`
+	set nobb
+	breaksw
+    case -no*:	# -nocrop
+	set nocrop
+	breaksw
+    case -xs*:	# -xsize
+        shift argv
+        if ($#argv == 0) eval usage
+	@ xsize = $argv[1]
+	breaksw
+    case -ys*:	# -ysize
+        shift argv
+        if ($#argv == 0) eval usage
+	@ ysize = $argv[1]
+	breaksw
+    case -xm*:	# -xmax
+        shift argv
+        if ($#argv == 0) eval usage
+	@ xmax = $argv[1]
+	breaksw
+    case -ym*:	# -ymax
+        shift argv
+        if ($#argv == 0) eval usage
+	@ ymax = $argv[1]
+	breaksw
+    case -xb*:	# -xborder
+        shift argv
+        if ($#argv == 0) eval usage
+	set xborder = $argv[1]
+	breaksw
+    case -yb*:	# -yborder
+        shift argv
+        if ($#argv == 0) eval usage
+	set yborder = $argv[1]
+	breaksw
+    case -f*:	# -forceplain
+	set filtertail = ""
+	breaksw
+    case -s*:	# -stdout
+	set goto_stdout
+	breaksw
+    case -v*:	# -verbose
+	set verb
+	breaksw
+    case -po*:	# -portrait
+	set orient = 1
+	breaksw
+    case -la*:	# -landscape
+	set orient = 2
+	breaksw
+    case -*:
+        echo "${progname}: Unknown option $argv[1]"
+        usage
+        breaksw
+    default:	# input file
+	set psfile = $argv[1]
+	set ppmfile = `basename $argv[1] .ps`
+        breaksw
+    endsw
+    shift argv
+end
+
+if ($psfile =~ "") eval usage
+if (! -f $psfile) then
+    echo "${progname}: file $psfile not found"
+    usage
+endif
+
+set bb = `grep "%%BoundingBox" $psfile`
+if ($?nobb == 0 && $#bb == 5) then
+    set llx = $bb[2]
+    set lly = $bb[3]
+    set urx = $bb[4]
+    set ury = $bb[5]
+else
+    if ($?nobb == 0) \
+    	echo "${progname}: warning: BoundingBox not found in input file"
+endif
+
+set tmpsx = `(echo "scale=4";echo "$urx - $llx")|bc -l`
+set tmpsy = `(echo "scale=4";echo "$ury - $lly")|bc -l`
+
+# see if orientation was specified 
+if ($orient == 0) then
+    # no orientation was specified; compute default orientation
+    set tmpx = 0
+    set tmpy = 0
+    set tmpsx1 = $tmpsx:r
+    set tmpsy1 = $tmpsy:r
+    # default is landscape mode
+    set orient = 2
+    if ($xsize == 0 && $ysize == 0) then
+	set tmpx = $xmax
+	set tmpy = $ymax
+    else
+	if ($xsize != 0) set tmpx = $xsize
+	if ($ysize != 0) set tmpy = $ysize
+    endif
+    if ($tmpx == 0 || $tmpy == 0) then
+	# only one size was specified
+	if ($tmpsy1 > $tmpsx1) set orient = 1
+    else
+	# no size or both sizes were specified
+	if ($tmpsy1 > $tmpsx1 && $tmpy > $tmpx) set orient = 1
+	if ($tmpsx1 > $tmpsy1 && $tmpx > $tmpy) set orient = 1
+    endif
+endif
+
+# now reset BoundingBox llc and total size to take into account margin
+set llx = `(echo "scale=4";echo "$llx - $tmpsx * $xborder")|bc -l`
+set lly = `(echo "scale=4";echo "$lly - $tmpsy * $yborder")|bc -l`
+set urx = `(echo "scale=4";echo "$urx + $tmpsx * $xborder")|bc -l`
+set ury = `(echo "scale=4";echo "$ury + $tmpsy * $yborder")|bc -l`
+# compute image area size 
+set sx = `(echo "scale=4";echo "$tmpsx + 2 * $xborder * $tmpsx")|bc -l`
+set sy = `(echo "scale=4";echo "$tmpsy + 2 * $yborder * $tmpsy")|bc -l`
+    
+if ($orient != 1) then
+    # render image in landscape mode
+    set tmpsx = $sx
+    set sx = $sy
+    set sy = $tmpsx
+endif
+
+# if xsize or ysize was specified, compute resolution from them
+if ($xsize != 0) set xres = `(echo "scale=4";echo "$xsize *72 / $sx")|bc -l`
+if ($ysize != 0) set yres = `(echo "scale=4";echo "$ysize *72 / $sy")|bc -l`
+
+if ($xres =~ "" && $yres !~ "") then
+    # ysize was specified, xsize was not; compute xsize based on ysize
+    set xres = $yres 
+    set xsize = `(echo "scale=4";echo "$sx * $xres /72 + 0.5")|bc -l`
+    set xsize = $xsize:r
+else 
+    if ($yres =~ "" && $xres !~ "") then
+	# xsize was specified, ysize was not; compute ysize based on xsize
+        set yres = $xres
+    	set ysize = `(echo "scale=4";echo "$sy * $yres /72 + 0.5")|bc -l`
+    	set ysize = $ysize:r
+    else
+	if ($xres =~ "" && $yres =~ "") then
+    	    # neither xsize nor ysize was specified; compute them from
+	    # xmax and ymax
+	    set xres = `(echo "scale=4";echo "$xmax *72/$sx")|bc -l`
+	    set yres = `(echo "scale=4";echo "$ymax *72/$sy")|bc -l`
+	    set xres = `(echo "scale=4";echo "if($xres>$yres)$yres";echo "if($yres>$xres)$xres";echo "if($xres==$yres)$xres")|bc -l`
+	    set yres = $xres
+	    if ($?nocrop) then
+		# keep output file dimensions equal to xmax and ymax
+		set xsize = $xmax
+		set ysize = $ymax
+	    else
+    	    	set xsize = `(echo "scale=4";echo "$sx * $xres /72+0.5")|bc -l`
+    	    	set ysize = `(echo "scale=4";echo "$sy * $yres /72+0.5")|bc -l`
+	    endif
+    	    set xsize = $xsize:r
+    	    set ysize = $ysize:r
+	endif
+    endif
+endif
+
+# translate + rotate image, if necessary
+if ($orient == 1) then
+    # portrait mode
+    # adjust offsets
+    set llx = `(echo "scale=4";echo "$llx - ($xsize *72/$xres - $sx)/2")|bc -l`
+    set lly = `(echo "scale=4";echo "$lly - ($ysize *72/$yres - $sy)/2")|bc -l`
+    set pstrans = "$llx neg $lly neg translate"
+else
+    # landscape mode
+    # adjust offsets
+    set llx = `(echo "scale=4";echo "$llx - ($ysize *72/$yres - $sy)/2")|bc -l`
+    set ury = `(echo "scale=4";echo "$ury + ($xsize *72/$xres - $sx)/2")|bc -l`
+    set pstrans = "90 rotate $llx neg $ury neg translate"
+endif
+   
+if ($?goto_stdout) then
+    set outfile = "-"
+else
+    set outfile = $ppmfile%03d.$filterhead 
+endif
+
+if ($?verb) then
+    echo "sx = $sx" 
+    echo "sy = $sy"
+    echo "xres  = $xres"
+    echo "yres  = $yres"
+    echo "xsize = $xsize"
+    echo "ysize = $ysize"
+    echo -n "orientation "
+    if ($orient == 1) then 
+	echo "portrait"
+    else
+	echo "landscape"
+    endif
+    echo "PS header: $pstrans"
+endif
+
+echo "${progname}: writing $filterhead file(s)" 1>&2
+
+echo $pstrans | \
+	gs -sDEVICE=${filterhead}${filtertail} \
+	   -sOutputFile=$outfile \
+	   -g${xsize}x${ysize} \
+	   -r${xres}x${yres} \
+	   -q - $psfile 
+
+
+
diff --git a/converter/other/rast.c b/converter/other/rast.c
new file mode 100644
index 00000000..91c50ccd
--- /dev/null
+++ b/converter/other/rast.c
@@ -0,0 +1,465 @@
+/* libpnm4.c - pnm utility library part 4
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pnm.h"
+#include "rast.h"
+#include "mallocvar.h"
+
+/*
+** Semi-work-alike versions of some Sun pixrect routines.  Just enough
+** for rasterfile reading and writing to work.
+*/
+
+struct pixrect*
+mem_create( w, h, depth )
+    int w, h, depth;
+{
+    struct pixrect* p;
+    struct mpr_data* m;
+
+    MALLOCVAR(p);
+    if ( p == NULL )
+        return NULL;
+    p->pr_ops = NULL;
+    p->pr_size.x = w;
+    p->pr_size.y = h;
+    p->pr_depth = depth;
+    MALLOCVAR(m);
+    if ( m == NULL )
+    {
+        free( p );
+        return NULL;
+    }
+    p->pr_data = m;
+
+    /* According to the documentation, linebytes is supposed to be rounded
+    ** up to a longword (except on 386 boxes).  However, this turns out
+    ** not to be the case.  In reality, all of Sun's code rounds up to
+    ** a short, not a long.
+    */
+    m->md_linebytes = ( w * depth + 15 ) / 16 * 2;
+    m->md_offset.x = 0;
+    m->md_offset.y = 0;
+    m->md_flags = 0;
+    MALLOCARRAY(m->md_image, m->md_linebytes * h );
+    if ( m->md_image == NULL )
+    {
+        free( m );
+        free( p );
+        return NULL;
+    }
+
+    return p;
+}
+
+void
+mem_free( p )
+    struct pixrect* p;
+{
+    free( p->pr_data->md_image );
+    free( p->pr_data );
+    free( p );
+}
+
+int
+pr_dump( p, out, colormap, type, copy_flag )
+    struct pixrect* p;
+    FILE* out;
+    colormap_t* colormap;
+    int type, copy_flag;
+{
+    struct rasterfile h;
+    int size, besize, count;
+    unsigned char* beimage;
+    unsigned char* bp;
+    unsigned char c, pc;
+    int i, j;
+
+    h.ras_magic = RAS_MAGIC;
+    h.ras_width = p->pr_size.x;
+    h.ras_height = p->pr_size.y;
+    h.ras_depth = p->pr_depth;
+
+    h.ras_type = type;
+    switch ( type )
+    {
+    case RT_OLD:
+        pm_error( "This program does not know the Old rasterfile type" );
+
+    case RT_FORMAT_TIFF:
+        pm_error( "This program does not know the TIFF rasterfile type" );
+
+    case RT_FORMAT_IFF:
+        pm_error( "This program does not know the IFF rasterfile type" );
+
+    case RT_EXPERIMENTAL:
+        pm_error( "This program does not know the Experimental "
+                  "rasterfile type" );
+
+    case RT_STANDARD:
+    case RT_FORMAT_RGB:
+        /* Ignore hP->ras_length. */
+        h.ras_length = p->pr_size.y * p->pr_data->md_linebytes;
+        break;
+
+    case RT_BYTE_ENCODED:
+        size = p->pr_size.y * p->pr_data->md_linebytes;
+        bp = p->pr_data->md_image;
+        MALLOCARRAY(beimage, size * 3 / 2);  /* worst case */
+        if ( beimage == NULL )
+            return PIX_ERR;
+        besize = 0;
+        count = 0;
+        for ( i = 0; i < size; ++i )
+        {
+            c = *bp++;
+            if ( count > 0 )
+            {
+                if ( pc != c )
+                {
+                    if ( count == 1 && pc == 128 )
+                    {
+                        beimage[besize++] = 128;
+                        beimage[besize++] = 0;
+                        count = 0;
+                    }
+                    else if ( count > 2 || pc == 128 )
+                    {
+                        beimage[besize++] = 128;
+                        beimage[besize++] = count - 1;
+                        beimage[besize++] = pc;
+                        count = 0;
+                    }
+                    else
+                    {
+                        for ( j = 0; j < count; ++j )
+                            beimage[besize++] = pc;
+                        count = 0;
+                    }
+                }
+            }
+            pc = c;
+            ++count;
+            if ( count == 256 )
+            {
+                beimage[besize++] = 128;
+                beimage[besize++] = count - 1;
+                beimage[besize++] = c;
+                count = 0;
+            }
+        }
+        if ( count > 0 )
+        {
+            if ( count == 1 && c == 128 )
+            {
+                beimage[besize++] = 128;
+                beimage[besize++] = 0;
+            }
+            if ( count > 2 || c == 128 )
+            {
+                beimage[besize++] = 128;
+                beimage[besize++] = count - 1;
+                beimage[besize++] = c;
+            }
+            else
+            {
+                for ( j = 0; j < count; ++j )
+                    beimage[besize++] = c;
+            }
+        }
+        h.ras_length = besize;
+        break;
+
+    default:
+        pm_error( "unknown rasterfile type" );
+    }
+
+    if ( colormap == NULL )
+    {
+        h.ras_maptype = RMT_NONE;
+        h.ras_maplength = 0;
+    }
+    else
+    {
+        h.ras_maptype = colormap->type;
+        switch ( colormap->type )
+        {
+        case RMT_EQUAL_RGB:
+            h.ras_maplength = colormap->length * 3;
+            break;
+
+        case RMT_RAW:
+            h.ras_maplength = colormap->length;
+            break;
+
+        default:
+            pm_error( "unknown colormap type" );
+        }
+    }
+
+    if ( pm_writebiglong( out, h.ras_magic ) == -1 )
+        return PIX_ERR;
+    if ( pm_writebiglong( out, h.ras_width ) == -1 )
+        return PIX_ERR;
+    if ( pm_writebiglong( out, h.ras_height ) == -1 )
+        return PIX_ERR;
+    if ( pm_writebiglong( out, h.ras_depth ) == -1 )
+        return PIX_ERR;
+    if ( pm_writebiglong( out, h.ras_length ) == -1 )
+        return PIX_ERR;
+    if ( pm_writebiglong( out, h.ras_type ) == -1 )
+        return PIX_ERR;
+    if ( pm_writebiglong( out, h.ras_maptype ) == -1 )
+        return PIX_ERR;
+    if ( pm_writebiglong( out, h.ras_maplength ) == -1 )
+        return PIX_ERR;
+
+    if ( colormap != NULL )
+    {
+        switch ( colormap->type )
+        {
+        case RMT_EQUAL_RGB:
+            if ( fwrite( colormap->map[0], 1, colormap->length, out ) !=
+                 colormap->length )
+                return PIX_ERR;
+            if ( fwrite( colormap->map[1], 1, colormap->length, out ) !=
+                 colormap->length )
+                return PIX_ERR;
+            if ( fwrite( colormap->map[2], 1, colormap->length, out ) !=
+                 colormap->length )
+                return PIX_ERR;
+            break;
+
+        case RMT_RAW:
+            if ( fwrite( colormap->map[0], 1, colormap->length, out ) !=
+                 colormap->length )
+                return PIX_ERR;
+            break;
+        }
+    }
+
+    switch ( type )
+    {
+    case RT_STANDARD:
+    case RT_FORMAT_RGB:
+        if ( fwrite( p->pr_data->md_image, 1, h.ras_length, out ) !=
+             h.ras_length )
+            return PIX_ERR;
+        break;
+
+    case RT_BYTE_ENCODED:
+        if ( fwrite( beimage, 1, besize, out ) != besize )
+        {
+            free( beimage );
+            return PIX_ERR;
+        }
+        free( beimage );
+        break;
+    }
+
+    return 0;
+}
+
+int
+pr_load_header( in, hP )
+    FILE* in;
+    struct rasterfile* hP;
+{
+    if ( pm_readbiglong( in, &(hP->ras_magic) ) == -1 )
+        return PIX_ERR;
+    if ( hP->ras_magic != RAS_MAGIC )
+        return PIX_ERR;
+    if ( pm_readbiglong( in, &(hP->ras_width) ) == -1 )
+        return PIX_ERR;
+    if ( pm_readbiglong( in, &(hP->ras_height) ) == -1 )
+        return PIX_ERR;
+    if ( pm_readbiglong( in, &(hP->ras_depth) ) == -1 )
+        return PIX_ERR;
+    if ( pm_readbiglong( in, &(hP->ras_length) ) == -1 )
+        return PIX_ERR;
+    if ( pm_readbiglong( in, &(hP->ras_type) ) == -1 )
+        return PIX_ERR;
+    if ( pm_readbiglong( in, &(hP->ras_maptype) ) == -1 )
+        return PIX_ERR;
+    if ( pm_readbiglong( in, &(hP->ras_maplength) ) == -1 )
+        return PIX_ERR;
+    return 0;
+}
+
+int
+pr_load_colormap( in, hP, colormap )
+    FILE* in;
+    struct rasterfile* hP;
+    colormap_t* colormap;
+{
+    if ( colormap == NULL || hP->ras_maptype == RMT_NONE )
+    {
+        int i;
+
+        for ( i = 0; i < hP->ras_maplength; ++i )
+            if ( getc( in ) == EOF )
+                return PIX_ERR;
+    }
+    else
+    {
+        colormap->type = hP->ras_maptype;
+        switch ( hP->ras_maptype )
+        {
+        case RMT_EQUAL_RGB:
+            colormap->length = hP->ras_maplength / 3;
+            MALLOCARRAY( colormap->map[0], colormap->length );
+            if ( colormap->map[0] == NULL )
+                return PIX_ERR;
+            MALLOCARRAY( colormap->map[1], colormap->length );
+            if ( colormap->map[1] == NULL )
+            {
+                free( colormap->map[0] );
+                return PIX_ERR;
+            }
+            MALLOCARRAY( colormap->map[2], colormap->length );
+            if ( colormap->map[2] == NULL )
+            {
+                free( colormap->map[0] );
+                free( colormap->map[1] );
+                return PIX_ERR;
+            }
+            if ( fread( colormap->map[0], 1, colormap->length, in ) != 
+                 colormap->length ||
+                 fread( colormap->map[1], 1, colormap->length, in ) != 
+                 colormap->length ||
+                 fread( colormap->map[2], 1, colormap->length, in ) != 
+                 colormap->length )
+            {
+                free( colormap->map[0] );
+                free( colormap->map[1] );
+                free( colormap->map[2] );
+                return PIX_ERR;
+            }
+            break;
+
+        case RMT_RAW: {
+            size_t bytesRead;
+
+            colormap->length = hP->ras_maplength;
+            MALLOCARRAY( colormap->map[0], colormap->length );
+            if ( colormap->map[0] == NULL )
+                return PIX_ERR;
+            colormap->map[2] = colormap->map[1] = colormap->map[0];
+            bytesRead = fread( colormap->map[0], 1, hP->ras_maplength, in );
+            if ( bytesRead != hP->ras_maplength )
+            {
+                free( colormap->map[0] );
+                return PIX_ERR;
+            }
+        }
+            break;
+
+        default:
+            pm_error( "unknown colormap type" );
+        }
+    }
+    return 0;
+}
+
+struct pixrect*
+pr_load_image( in, hP, colormap )
+    FILE* in;
+    struct rasterfile* hP;
+    colormap_t* colormap;
+{
+    struct pixrect* p;
+    unsigned char* beimage;
+    register unsigned char* bep;
+    register unsigned char* bp;
+    register unsigned char c;
+    int i;
+    register int j, count;
+
+    p = mem_create( hP->ras_width, hP->ras_height, hP->ras_depth );
+    if ( p == NULL )
+        return NULL;
+
+    switch ( hP->ras_type )
+    {
+    case RT_OLD:
+        pm_error( "This program does not know the Old rasterfile type" );
+
+    case RT_FORMAT_TIFF:
+        pm_error( "This program does not know the TIFF rasterfile type" );
+
+    case RT_FORMAT_IFF:
+        pm_error( "This program does not know the IFF rasterfile type" );
+
+    case RT_EXPERIMENTAL:
+        pm_error( "This program does not know the Experimental "
+                  "rasterfile type" );
+
+    case RT_STANDARD:
+    case RT_FORMAT_RGB:
+        /* Ignore hP->ras_length. */
+        i = p->pr_size.y * p->pr_data->md_linebytes;
+        if ( fread( p->pr_data->md_image, 1, i, in ) != i )
+        {
+            mem_free( p );
+            return NULL;
+        }
+        break;
+
+    case RT_BYTE_ENCODED:
+        MALLOCARRAY( beimage, hP->ras_length );
+        if ( beimage == NULL )
+        {
+            mem_free( p );
+            return NULL;
+        }
+        if ( fread( beimage, 1, hP->ras_length, in ) != hP->ras_length )
+        {
+            mem_free( p );
+            free( beimage );
+            return NULL;
+        }
+        bep = beimage;
+        bp = p->pr_data->md_image;
+        for ( i = 0; i < hP->ras_length; )
+        {
+            c = *bep++;
+            if ( c == 128 )
+            {
+                count = ( *bep++ ) + 1;
+                if ( count == 1 )
+                {
+                    *bp++ = 128;
+                    i += 2;
+                }
+                else
+                {
+                    c = *bep++;
+                    for ( j = 0; j < count; ++j )
+                        *bp++ = c;
+                    i += 3;
+                }
+            }
+            else
+            {
+                *bp++ = c;
+                ++i;
+            }
+        }
+        free( beimage );
+        break;
+
+    default:
+        pm_error( "unknown rasterfile type" );
+    }
+
+    return p;
+}
diff --git a/converter/other/rast.h b/converter/other/rast.h
new file mode 100644
index 00000000..1854e495
--- /dev/null
+++ b/converter/other/rast.h
@@ -0,0 +1,111 @@
+/* rast.h - header file for Sun raster files
+**
+** The format of a Sun raster file is as follows.  First, a struct
+** rasterfile.  Note the 32-bit magic number at the beginning; this
+** identifies the file type and lets you figure out whether you need
+** to do little-endian / big-endian byte-swapping or not.  (The PBMPLUS
+** implementation does not do byte-swapping; instead, it reads all
+** multi-byte values a byte at a time.)
+**
+** After the struct is an optional colormap.  If ras_maptype is RMT_NONE,
+** no map is present; if it's RMT_EQUAL_RGB then the map consists of
+** three unsigned-char arrays ras_maplength long, one each for r g and b.
+** I don't know what RMT_RAW means.  Black and white bitmaps are stored
+** as ras_maptype == RMT_NONE and ras_depth == 1, with the bits stored
+** eight to a byte MSB first.
+**
+** Finally comes the image data.  If ras_type is RT_OLD or RT_STANDARD,
+** the data is just plain old uncompressed bytes, padded out to a multiple
+** of 16 bits in each row.  If ras_type is RT_BYTE_ENCODED, a run-length
+** compression scheme is used: an escape-byte of 128 indicates a run;
+** the next byte is a count, and the one after that is the byte to be
+** replicated.  The one exception to this is if the count is 1; then
+** there is no third byte in the packet, it means to put a single 128
+** in the data stream.
+*/
+
+#ifndef RAST_H_INCLUDED
+#define RAST_H_INCLUDED
+
+#define PIX_ERR		-1
+
+struct rasterfile {
+    long ras_magic;
+#define	RAS_MAGIC	0x59a66a95
+    long ras_width;
+    long ras_height;
+    long ras_depth;
+    long ras_length;
+    long ras_type;
+#define RT_OLD		0	/* Raw pixrect image in 68000 byte order */
+#define RT_STANDARD	1	/* Raw pixrect image in 68000 byte order */
+#define RT_BYTE_ENCODED	2	/* Run-length compression of bytes */
+#define RT_FORMAT_RGB	3	/* XRGB or RGB instead of XBGR or BGR */
+#define RT_FORMAT_TIFF	4	/* tiff <-> standard rasterfile */
+#define RT_FORMAT_IFF	5	/* iff (TAAC format) <-> standard rasterfile */
+#define RT_EXPERIMENTAL 0xffff	/* Reserved for testing */
+    long ras_maptype;
+#define RMT_NONE	0
+#define RMT_EQUAL_RGB	1
+#define RMT_RAW		2
+    long ras_maplength;
+    };
+
+struct pixrectops {
+    int	(*pro_rop)();
+    int	(*pro_stencil)();
+    int	(*pro_batchrop)();
+    int	(*pro_nop)();
+    int	(*pro_destroy)();
+    int	(*pro_get)();
+    int	(*pro_put)();
+    int	(*pro_vector)();
+    struct pixrect* (*pro_region)();
+    int	(*pro_putcolormap)();
+    int	(*pro_getcolormap)();
+    int	(*pro_putattributes)();
+    int	(*pro_getattributes)();
+    };
+
+struct pr_size {
+    int x, y;
+    };
+struct pr_pos {
+    int x, y;
+    };
+
+struct pixrect {
+    struct pixrectops* pr_ops;
+    struct pr_size pr_size;
+    int pr_depth;
+    struct mpr_data* pr_data;	/* work-alike only handles memory pixrects */
+    };
+
+struct mpr_data {
+    int md_linebytes;
+    unsigned char* md_image;	/* note, byte not short -- avoid pr_flip() */
+    struct pr_pos md_offset;
+    short md_primary;
+    short md_flags;
+    };
+
+typedef struct {
+    int type;
+    int length;
+    unsigned char* map[3];
+    } colormap_t;
+
+/* And the routine definitions. */
+
+struct pixrect* mem_create ARGS(( int w, int h, int depth ));
+void mem_free ARGS(( struct pixrect* p ));
+
+int pr_dump ARGS(( struct pixrect* p, FILE* out, colormap_t* colormap, int type, int copy_flag ));
+
+int pr_load_header ARGS(( FILE* in, struct rasterfile* hP ));
+
+int pr_load_colormap ARGS(( FILE* in, struct rasterfile* hP, colormap_t* colormap ));
+
+struct pixrect* pr_load_image ARGS(( FILE* in, struct rasterfile* hP, colormap_t* colormap ));
+
+#endif
diff --git a/converter/other/rasttopnm.c b/converter/other/rasttopnm.c
new file mode 100644
index 00000000..aa55850b
--- /dev/null
+++ b/converter/other/rasttopnm.c
@@ -0,0 +1,263 @@
+/* rasttopnm.c - read a Sun rasterfile and produce a portable anymap
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pnm.h"
+#include "rast.h"
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    struct rasterfile header;
+    colormap_t pr_colormap;
+    int grayscale;
+    struct pixrect* pr;
+    xel* xelrow;
+    register xel* xP;
+    int argn, rows, cols, format, depth, i, row, mask;
+    register int col;
+    xelval maxval;
+    xel zero, one;
+    int linesize;
+    unsigned char* data;
+    unsigned char* byteP;
+
+    pnm_init( &argc, argv );
+
+    argn = 1;
+
+    if ( argn != argc )
+	{
+	ifp = pm_openr( argv[argn] );
+	++argn;
+	}
+    else
+	ifp = stdin;
+
+    if ( argn != argc )
+	pm_usage( "[rastfile]" );
+
+    /* Read in the rasterfile.  First the header. */
+    if ( pr_load_header( ifp, &header ) != 0 )
+	pm_error( "unable to read in rasterfile header" );
+
+    cols = header.ras_width;
+    rows = header.ras_height;
+    depth = header.ras_depth;
+
+    if ( cols <= 0 )
+	pm_error( "invalid cols: %d", cols );
+    if ( rows <= 0 )
+	pm_error( "invalid rows: %d", rows );
+
+    /* If there is a color map, read it. */
+    grayscale = 1;
+    if ( header.ras_maplength != 0 )
+	{
+	if ( pr_load_colormap( ifp, &header, &pr_colormap ) != 0 )
+	    pm_error( "unable to skip colormap data" );
+	for ( i = 0; i < header.ras_maplength / 3; ++i )
+	    if ( pr_colormap.map[0][i] != pr_colormap.map[1][i] ||
+		 pr_colormap.map[1][i] != pr_colormap.map[2][i] )
+		{
+		grayscale = 0;
+		break;
+		}
+	}
+
+    /* Check the depth and color map. */
+    switch ( depth )
+	{
+	case 1:
+	if ( header.ras_maptype == RMT_NONE && header.ras_maplength == 0 )
+	    {
+	    maxval = 1;
+	    format = PBM_TYPE;
+	    PNM_ASSIGN1( zero, maxval );
+	    PNM_ASSIGN1( one, 0 );
+	    }
+	else if ( header.ras_maptype == RMT_EQUAL_RGB &&
+		  header.ras_maplength == 6 )
+	    {
+	    if ( grayscale )
+		{
+		maxval = 255;
+		format = PGM_TYPE;
+		PNM_ASSIGN1( zero, pr_colormap.map[0][0] );
+		PNM_ASSIGN1( one, pr_colormap.map[0][1] );
+		}
+	    else
+		{
+		maxval = 255;
+		format = PPM_TYPE;
+		PPM_ASSIGN(
+		    zero, pr_colormap.map[0][0], pr_colormap.map[1][0],
+		    pr_colormap.map[2][0] );
+		PPM_ASSIGN(
+		    one, pr_colormap.map[0][1], pr_colormap.map[1][1],
+		    pr_colormap.map[2][1] );
+		}
+	    }
+	else
+	    pm_error(
+            "this depth-1 rasterfile has a non-standard colormap - "
+            "type %ld length %ld",
+            header.ras_maptype, header.ras_maplength );
+	break;
+
+	case 8:
+	if ( grayscale )
+	    {
+	    maxval = 255;
+	    format = PGM_TYPE;
+	    }
+	else if ( header.ras_maptype == RMT_EQUAL_RGB )
+	    {
+	    maxval = 255;
+	    format = PPM_TYPE;
+	    }
+	else
+	    pm_error(
+            "this depth-8 rasterfile has a non-standard colormap - "
+            "type %ld length %ld",
+            header.ras_maptype, header.ras_maplength );
+	break;
+
+	case 24:
+	case 32:
+	if ( header.ras_maptype == RMT_NONE && header.ras_maplength == 0 )
+	    ;
+	else if ( header.ras_maptype == RMT_RAW || header.ras_maplength == 768 )
+	    ;
+	else
+	    pm_error(
+            "this depth-%d rasterfile has a non-standard colormap - "
+            "type %ld length %ld",
+            depth, header.ras_maptype, header.ras_maplength );
+	maxval = 255;
+	format = PPM_TYPE;
+	break;
+
+	default:
+	pm_error(
+	    "invalid depth: %d.  Can only handle depth 1, 8, 24, or 32.",
+	    depth );
+	}
+
+    /* Now load the data.  The pixrect returned is a memory pixrect. */
+    if ( ( pr = pr_load_image( ifp, &header, NULL ) ) == NULL )
+	pm_error(
+	    "unable to read in the image from the rasterfile" );
+
+    linesize = ( (struct mpr_data*) pr->pr_data )->md_linebytes;
+    data = ( (struct mpr_data*) pr->pr_data )->md_image;
+
+    pm_close( ifp );
+
+    /* Now write out the anymap. */
+    pnm_writepnminit( stdout, cols, rows, maxval, format, 0 );
+    xelrow = pnm_allocrow( cols );
+    switch ( PNM_FORMAT_TYPE(format) )
+        {
+        case PBM_TYPE:
+        pm_message( "writing PBM file" );
+        break;
+
+        case PGM_TYPE:
+        pm_message( "writing PGM file" );
+        break;
+
+        case PPM_TYPE:
+        pm_message( "writing PPM file" );
+        break;
+
+        default:
+        pm_error( "shouldn't happen" );
+        }
+
+    for ( row = 0; row < rows; ++row )
+	{
+	byteP = data;
+	switch ( depth )
+	    {
+	    case 1:
+	    mask = 0x80;
+	    for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+		{
+		if ( mask == 0 )
+		    {
+		    ++byteP;
+		    mask = 0x80;
+		    }
+		*xP = ( *byteP & mask ) ? one : zero;
+		mask = mask >> 1;
+		}
+	    break;
+
+	    case 8:
+	    for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+		{
+		if ( header.ras_maplength == 0 )
+		    PNM_ASSIGN1( *xP, *byteP );
+		else if ( grayscale )
+		    PNM_ASSIGN1( *xP, pr_colormap.map[0][*byteP] );
+		else
+		    PPM_ASSIGN(
+			*xP, pr_colormap.map[0][*byteP],
+			pr_colormap.map[1][*byteP],
+			pr_colormap.map[2][*byteP] );
+		++byteP;
+		}
+	    break;
+
+	    case 24:
+	    case 32:
+	    for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+		{
+		register xelval r, g, b;
+
+		if ( depth == 32 )
+		    ++byteP;
+		if ( header.ras_type == RT_FORMAT_RGB )
+		    {
+		    r = *byteP++;
+		    g = *byteP++;
+		    b = *byteP++;
+		    }
+		else
+		    {
+		    b = *byteP++;
+		    g = *byteP++;
+		    r = *byteP++;
+		    }
+		if ( header.ras_maplength == 0 )
+		    PPM_ASSIGN( *xP, r, g, b );
+		else
+		    PPM_ASSIGN(
+			*xP, pr_colormap.map[0][r], pr_colormap.map[1][g],
+			pr_colormap.map[2][b] );
+		}
+	    break;
+
+	    default:
+	    pm_error( "can't happen" );
+	    }
+	data += linesize;
+	pnm_writepnmrow( stdout, xelrow, cols, maxval, format, 0 );
+	}
+
+    pm_close( stdout );
+
+    exit( 0 );
+    }
diff --git a/converter/other/rla.h b/converter/other/rla.h
new file mode 100644
index 00000000..875cbfc6
--- /dev/null
+++ b/converter/other/rla.h
@@ -0,0 +1,45 @@
+typedef struct
+{
+    short left;
+    short right;
+    short bottom;
+    short top;
+} window_s;
+
+typedef struct
+{
+    window_s	window;
+    window_s	active_window;
+    short	    frame;
+    short       storage_type;
+    short       num_chan;
+    short       num_matte;
+    short       num_aux;
+    unsigned short       revision;
+    char        gamma[16];
+    char        red_pri[24];
+    char        green_pri[24];
+    char        blue_pri[24];
+    char        white_pri[24];
+    long        job_num;
+    char        name[128];
+    char        desc[128];
+    char        program[64];
+    char        machine[32];
+    char        user[32];
+    char        date[20];
+    char        aspect[24];
+    char        aspect_ratio[8];
+    char        chan[32];
+    short       field;
+    char        time[12];
+    char        filter[32];
+    short       chan_bits;
+    short       matte_type;
+    short       matte_bits;
+    short       aux_type;
+    short       aux_bits;
+    char        aux[32];
+    char        space[36];
+    long        next;
+} rlahdr;
diff --git a/converter/other/rlatopam.c b/converter/other/rlatopam.c
new file mode 100644
index 00000000..ae1326ca
--- /dev/null
+++ b/converter/other/rlatopam.c
@@ -0,0 +1,433 @@
+/** rlatopam.c - read Alias/Wavefront RLA or RPF file
+ **
+ ** Copyright (C) 2005 Matte World Digital
+ **
+ ** Author: Simon Walton
+ **
+ ** Permission to use, copy, modify, and distribute this software and its
+ ** documentation for any purpose and without fee is hereby granted, provided
+ ** that the above copyright notice appear in all copies and that both that
+ ** copyright notice and this permission notice appear in supporting
+ ** documentation.  This software is provided "as is" without express or
+ ** implied warranty.
+ **/
+
+
+#include <string.h>
+#include <errno.h>
+
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "pam.h"
+#include "rla.h"
+
+static int * offsets;
+static bool is_float;
+static bool has_matte;
+
+static unsigned int depth;
+static unsigned int width;
+static unsigned int height;
+static unsigned int chanBits;
+static short storageType;
+static struct pam outpam;
+static unsigned int numChan;
+
+
+static void
+parseCommandLine(int           const argc,
+                 char **       const argv,
+                 const char ** const inputFileNameP) {
+
+    if (argc-1 < 1)
+        *inputFileNameP = "-";
+    else {
+        *inputFileNameP = argv[1];
+
+        if (argc-1 > 1)
+            pm_error("There is at most one argument - input file name.  "
+                     "You specified %u", argc-1);
+    }
+}
+
+
+
+static bool littleEndian;
+
+
+static void
+determineEndianness(void) {
+
+    union {
+        unsigned char bytes[2];
+        unsigned short number;
+    } u;
+
+    u.number = 1;
+
+    littleEndian = (u.bytes[0] == 1);
+}
+
+
+
+static unsigned short
+byteswap(unsigned short const input) {
+
+    return (input << 8) | (input >> 8);
+}
+
+
+
+static void
+read_header(FILE *   const ifP,
+            rlahdr * const hdrP) {
+
+    rlahdr hdr;
+    size_t bytesRead;
+    
+    fseek (ifP, 0, SEEK_SET);
+    
+    /* Here we have a hack.  The bytes in the file are almost in the
+       same format as the compiler stores 'hdr' in memory.  The only
+       difference is that the compiler may store the integer values
+       in the obscene little-endian format.  So we just read the whole
+       header into 'hdr' as if it were the right format, and then
+       correct the integer values by swapping their bytes.
+
+       The _right_ way to do this is to read the file one field at a time,
+       using pm_readbigshort() where appropriate.
+    */
+
+    bytesRead = fread(&hdr, sizeof hdr, 1, ifP);
+    if (bytesRead != 1)
+        pm_error("Unexpected EOF on input file.");
+
+    if (littleEndian) {
+        hdr.window.left          = byteswap(hdr.window.left);
+        hdr.window.right         = byteswap(hdr.window.right);
+        hdr.window.bottom        = byteswap(hdr.window.bottom);
+        hdr.window.top           = byteswap(hdr.window.top);
+        hdr.active_window.left   = byteswap(hdr.active_window.left);
+        hdr.active_window.right  = byteswap(hdr.active_window.right);
+        hdr.active_window.bottom = byteswap(hdr.active_window.bottom);
+        hdr.active_window.top    = byteswap(hdr.active_window.top);
+        hdr.storage_type         = byteswap(hdr.storage_type);
+        hdr.num_chan             = byteswap(hdr.num_chan);
+        hdr.num_matte            = byteswap(hdr.num_matte);
+        hdr.revision             = byteswap(hdr.revision);
+        hdr.chan_bits            = byteswap(hdr.chan_bits);
+        hdr.matte_type           = byteswap(hdr.matte_type);
+        hdr.matte_bits           = byteswap(hdr.matte_bits);
+    }
+
+    if (hdr.revision != 0xfffe && hdr.revision != 0xfffd)
+        pm_error("Invalid file header.  \"revision\" field should contain "
+                 "0xfffe or 0xfffd, but contains 0x%04x", hdr.revision);
+
+    *hdrP = hdr;
+}
+
+
+
+static void
+decodeFP(unsigned char * const in,
+         unsigned char * const out,
+         int             const width,
+         int             const stride) {
+
+    unsigned int x;
+    unsigned char * inputCursor;
+    unsigned char * outputCursor;
+
+    inputCursor = &in[0];
+
+    for (x = 0; x < width; ++x) {
+        union {char bytes [4]; float fv;} fi;
+        unsigned short val;
+
+        if (littleEndian) {
+            fi.bytes [3] = *inputCursor++;
+            fi.bytes [2] = *inputCursor++;
+            fi.bytes [1] = *inputCursor++;
+            fi.bytes [0] = *inputCursor++;
+        } else {
+            fi.bytes [0] = *inputCursor++;
+            fi.bytes [1] = *inputCursor++;
+            fi.bytes [2] = *inputCursor++;
+            fi.bytes [3] = *inputCursor++;
+        }
+
+        val = fi.fv > 1 ? 65535 : (fi.fv < 0 ? 0 :
+                                   (unsigned short) (65535 * fi.fv + .5));
+        outputCursor[0] = val >> 8; outputCursor[1] = val & 0xff;
+        outputCursor += stride;
+    }
+}
+
+
+
+static unsigned char *
+decode(unsigned char * const input,
+       unsigned char * const output,
+       int             const xFile,
+       int             const xImage,
+       int             const stride) {
+
+    int x;
+    unsigned int bytes;
+    unsigned int useX;
+    unsigned char * inputCursor;
+    unsigned char * outputCursor;
+
+    inputCursor = &input[0];
+    outputCursor = &output[0];
+    x = xFile;
+    bytes = 0;
+    useX = 0;
+    
+    while (x > 0) {
+        int count;
+
+        count = *(signed char *)inputCursor++;
+        ++bytes;
+
+        if (count >= 0) {
+            /* Repeat pixel value (count + 1) times. */
+            while (count-- >= 0) {
+                if (useX < xImage) {
+                    *outputCursor = *inputCursor;
+                    outputCursor += stride;
+                }
+                --x;
+                ++useX;
+            }
+            ++inputCursor;
+            ++bytes;
+        } else {
+            /* Copy (-count) unencoded values. */
+            for (count = -count; count > 0; --count) {
+                if (useX < xImage) {
+                    *outputCursor = *inputCursor;
+                    outputCursor += stride;
+                }
+                ++inputCursor;
+                ++bytes;
+                --x;
+                ++useX;
+            }
+        }
+    }
+    return inputCursor;
+}
+
+
+
+static void
+decode_row(FILE *          const ifP,
+           int             const row,
+           unsigned char * const rb) {
+
+    static unsigned char * read_buffer = NULL;
+    unsigned int chan;
+    int rc;
+
+    if (!read_buffer) {
+        MALLOCARRAY(read_buffer, width * 4);
+        if (read_buffer == 0)
+            pm_error("Unable to get memory for read_buffer");
+    }
+
+    rc = fseek (ifP, offsets [height - row - 1], SEEK_SET);
+    if (rc != 0)
+        pm_error("fseek() failed with errno %d (%s)",
+                 errno, strerror(errno));
+
+    for (chan = 0; chan < outpam.depth; ++chan) {
+        unsigned short length;
+        size_t bytesRead;
+        
+        pm_readbigshortu(ifP, &length);
+        if (length > width * 4)
+            pm_error("Line too long - row %u, channel %u", row, chan);
+
+        bytesRead = fread(read_buffer, 1, length, ifP);
+        if (bytesRead != length)
+            pm_error("EOF encountered unexpectedly");
+
+        if (is_float)
+            decodeFP(read_buffer, rb + chan * 2, width, outpam.depth * 2);
+        else if (depth > 8) {
+            /* Hi byte */
+            unsigned char * const newpos =
+                decode(read_buffer, rb + chan * 2, width, width,
+                       outpam.depth * 2);
+            /* Lo byte */
+            decode(newpos, rb + chan * 2 + 1, width, width,
+                   outpam.depth * 2);
+        } else
+            decode(read_buffer, rb + chan, width, width, outpam.depth); 
+    }
+}
+
+
+
+static void
+getHeaderInfo(FILE *         const ifP,
+              unsigned int * const widthP,
+              unsigned int * const heightP,
+              unsigned int * const depthP,
+              bool *         const hasMatteP,
+              unsigned int * const chanBitsP,
+              short *        const storageType) {
+    
+    rlahdr hdr;
+    int width, height;
+
+    read_header(ifP, &hdr);
+
+    height = hdr.active_window.top - hdr.active_window.bottom + 1;
+    width  = hdr.active_window.right - hdr.active_window.left + 1;
+    if (width <=0)
+        pm_error("Invalid input image.  It says its width isn't positive");
+    if (height <=0)
+        pm_error("Invalid input image.  It says its height isn't positive");
+
+    *widthP  = width;
+    *heightP = height;
+
+    if (hdr.num_chan != 1 && hdr.num_chan != 3)
+        pm_error ("Input image has bad number of channels: %d.  "
+                  "Should be 1 or 3.",
+                  hdr.num_chan);
+
+    *depthP = hdr.chan_bits <= 8 ? 8 : 16;
+
+    *hasMatteP = (hdr.num_matte > 0);
+    *chanBitsP = hdr.chan_bits;
+}
+
+
+
+static void
+readOffsetArray(FILE *       const ifP,
+                int **       const offsetsP,
+                unsigned int const height) {
+
+    int * offsets;
+    unsigned int row;
+
+    MALLOCARRAY(offsets, height);
+    if (offsets == NULL)
+        pm_error("Unable to allocate memory for the offsets array");
+
+    for (row = 0; row < height; ++row) {
+        long l;
+        pm_readbiglong(ifP, &l);
+        offsets[row] = l;
+    }
+    *offsetsP = offsets;
+}
+
+
+
+static void
+destroyOffsetArray(int * const offsets) {
+
+    free(offsets);
+}
+
+
+
+static void
+readAndWriteRaster(FILE *             const ifP,
+                   const struct pam * const outpamP) {
+
+    unsigned char * rowBuffer;
+    tuple * tuplerow;
+    unsigned int row;
+
+    /* Hold one row of all image planes */
+    rowBuffer = calloc(1, width * outpamP->depth * 4);
+    if (rowBuffer == NULL)
+        pm_error("Unable to allocate memor for row buffer.");
+
+    tuplerow = pnm_allocpamrow(outpamP);
+
+    for (row = 0; row < height; ++row) {
+        unsigned int col;
+        unsigned char * rbP;
+
+        decode_row(ifP, row, rowBuffer);
+        for (col = 0, rbP = rowBuffer; col < width; ++col) {
+            unsigned int chan;
+            for (chan = 0; chan < outpamP->depth; ++chan) {
+                if (depth > 8) {
+                    tuplerow[col][chan] = 256 * rbP[0] + rbP[1];
+                    rbP += 2;
+                } else {
+                    tuplerow[col][chan] = *rbP;
+                    rbP += 1;
+                }
+            }
+        }
+        pnm_writepamrow(outpamP, tuplerow);
+    }
+    pnm_freepamrow(tuplerow);
+    free(rowBuffer);
+}
+
+
+
+int
+main(int    argc,
+     char * argv[]) {
+
+    const char * inputFileName;
+    FILE * ifP;
+
+    pnm_init(&argc, argv);
+
+    determineEndianness();
+
+    parseCommandLine(argc, argv, &inputFileName);
+
+    ifP = pm_openr_seekable(inputFileName);
+
+    getHeaderInfo(ifP, &width, &height, &depth, &has_matte,
+                  &chanBits, &storageType);
+
+    outpam.size   = outpam.len = sizeof (struct pam);
+    outpam.file   = stdout;
+    outpam.format = PAM_FORMAT;
+    outpam.height = height;
+    outpam.width  = width;
+    outpam.depth  = numChan + (has_matte ? 1 : 0);
+    outpam.maxval = (1 << (chanBits > 16 ? 
+                           (9 + (chanBits - 1) % 8)
+                                /* Take top 2 of 3 or 4 bytes */
+                           : chanBits)) - 1;
+
+    /* Most apps seem to assume 32 bit integer is really floating point */
+    if (chanBits == 32 || storageType == 4) {
+        is_float = TRUE;
+        outpam.maxval = 65535;
+        depth = 16;
+    }
+
+    outpam.bytes_per_sample = depth / 8;
+    strcpy(outpam.tuple_type, (numChan == 3 ? "RGB" : "GRAYSCALE"));
+    if (has_matte)
+        strcat(outpam.tuple_type, "A");
+
+    readOffsetArray(ifP, &offsets, height);
+
+    pnm_writepaminit(&outpam);
+
+    readAndWriteRaster(ifP, &outpam);
+
+    destroyOffsetArray(offsets);
+    
+    pm_close(ifP);
+
+    return 0; 
+}
diff --git a/converter/other/rletopnm.c b/converter/other/rletopnm.c
new file mode 100644
index 00000000..aaa86388
--- /dev/null
+++ b/converter/other/rletopnm.c
@@ -0,0 +1,497 @@
+/*
+ * This is derived from the file of the same name dated June 5, 1995,
+ * copied from the Army High Performance Computing Research Center's
+ * media-tools.tar.gz package, received from 
+ * http://www.arc.umn.edu/gvl-software/media-tools.tar.gz on 2000.04.13.
+ *
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is
+ * preserved on all copies.
+ *
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ */
+/*
+ * rletopnm - A conversion program to convert from Utah's "rle" image format
+ *            to pbmplus ppm or pgm image formats.
+ *
+ * Author:      Wes Barris (wes@msc.edu)
+ *              AHPCRC
+ *              Minnesota Supercomputer Center, Inc.
+ * Date:        March 30, 1994
+ * Copyright (c) Minnesota Supercomputer Center 1994
+ * 
+ * 2000.04.13 adapted for Netpbm by Bryan Henderson.  Quieted compiler 
+ *            warnings.  Added --alpha option.  Accept input on stdin
+ *
+ */
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+/*-----------------------------------------------------------------------------
+ * System includes.
+ */
+#include <string.h>
+#include <stdio.h>
+#define NO_DECLARE_MALLOC
+#include <rle.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+#define HMSG if (headerDump) pm_message
+#define GRAYSCALE   001 /* 8 bits, no colormap */
+#define PSEUDOCOLOR 010 /* 8 bits, colormap */
+#define TRUECOLOR   011 /* 24 bits, colormap */
+#define DIRECTCOLOR 100 /* 24 bits, no colormap */
+#define RLE_MAXVAL 255
+/*
+ * Utah type declarations.
+ */
+rle_hdr     hdr;
+rle_map     *colormap;
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    char *input_filename;
+    unsigned int headerdump;
+    unsigned int verbose;
+    char *alpha_filename;
+    bool alpha_stdout;
+};
+
+
+
+/*
+ * Other declarations.
+ */
+int     visual, maplen;
+int     width, height;
+
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                   struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that many of the strings that this function returns in the
+   *cmdlineP structure are actually in the supplied argv array.  And
+   sometimes, one of these strings is actually just a suffix of an entry
+   in argv!
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;  /* malloc'ed */
+    optStruct3 opt;
+    unsigned int option_def_index;
+
+    unsigned int alphaoutSpec;
+
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3('h', "headerdump", OPT_FLAG,   
+            NULL,                      &cmdlineP->headerdump,     0);
+    OPTENT3('v', "verbose",    OPT_FLAG,   
+            NULL,                      &cmdlineP->verbose,        0);
+    OPTENT3(0,   "alphaout",   OPT_STRING, 
+            &cmdlineP->alpha_filename, &alphaoutSpec,             0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = TRUE;  /* We have short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and all of *cmdlineP. */
+
+    if (!alphaoutSpec)
+        cmdlineP->alpha_filename = NULL;
+
+    if (argc - 1 == 0)
+        cmdlineP->input_filename = NULL;  /* he wants stdin */
+    else if (argc - 1 == 1) {
+        if (STREQ(argv[1], "-"))
+            cmdlineP->input_filename = NULL;  /* he wants stdin */
+        else 
+            cmdlineP->input_filename = strdup(argv[1]);
+    } else 
+        pm_error("Too many arguments.  The only argument accepted "
+                 "is the input file specification");
+
+    if (cmdlineP->alpha_filename && 
+        STREQ(cmdlineP->alpha_filename, "-"))
+        cmdlineP->alpha_stdout = TRUE;
+    else 
+        cmdlineP->alpha_stdout = FALSE;
+}
+
+
+
+static void
+reportRleGetSetupError(int const rleGetSetupRc) {
+    
+    switch (rleGetSetupRc) {
+    case -1:
+        pm_error("According to the URT library, the input is not "
+                 "an RLE file.  rle_get_setup() failed.");
+        break;
+    case -2:
+        pm_error("Unable to get memory for the color map.  "
+                 "rle_get_setup() failed.");
+        break;
+    case -3:
+        pm_error("Input file is empty.  rle_get_setup() failed.");
+        break;
+    case -4:
+        pm_error("End of file in the middle of where the RLE header should "
+                 "be.  rle_get_setup() failed.");
+        break;
+    default:
+        pm_error("rle_get_setup() failed for an unknown reason");
+    }
+}
+
+
+/*-----------------------------------------------------------------------------
+ *                                                         Read the rle header.
+ */
+static void 
+read_rle_header(FILE * const ifp,
+                bool   const headerDump) {
+    int rc;
+    int  i;
+    hdr.rle_file = ifp;
+    rc = rle_get_setup(&hdr);
+    if (rc != 0)
+        reportRleGetSetupError(rc);
+
+    width = hdr.xmax - hdr.xmin + 1;
+    height = hdr.ymax - hdr.ymin + 1;
+    HMSG("Image size: %dx%d", width, height);
+    if (hdr.ncolors == 1 && hdr.ncmap == 3) {
+        visual = PSEUDOCOLOR;
+        colormap = hdr.cmap;
+        maplen = (1 << hdr.cmaplen);
+        HMSG("Mapped color image with a map of length %d.",
+                maplen);
+    }
+    else if (hdr.ncolors == 3 && hdr.ncmap == 0) {
+        visual = DIRECTCOLOR;
+        HMSG("24 bit color image, no colormap.");
+    }
+    else if (hdr.ncolors == 3 && hdr.ncmap == 3) {
+        visual = TRUECOLOR;
+        colormap = hdr.cmap;
+        maplen = (1 << hdr.cmaplen);
+        HMSG(
+                "24 bit color image with color map of length %d" ,maplen);
+    }
+    else if (hdr.ncolors == 1 && hdr.ncmap == 0) {
+        visual = GRAYSCALE;
+        HMSG("Grayscale image.");
+    }
+    else {
+        fprintf(stderr,
+                "ncolors = %d, ncmap = %d, I don't know how to handle this!",
+                hdr.ncolors, hdr.ncmap);
+        exit(-1);
+    }
+    if (hdr.alpha == 0) {
+        HMSG("No alpha channel.");
+    } else if (hdr.alpha == 1) {
+        HMSG("Alpha channel exists!");
+    } else {
+        fprintf(stderr, "alpha = %d, I don't know how to handle this!\n",
+                hdr.alpha);
+        exit(-1);
+    }
+    switch (hdr.background) {
+    case 0:
+        HMSG("Use all pixels, ignore background color.");
+        break;
+    case 1:
+        HMSG("Use only non-background pixels, ignore background color.");
+        break;
+    case 2:
+        HMSG("Use only non-background pixels, "
+             "clear to background color (default).");
+        break;
+    default:
+        HMSG("Unknown background flag!");
+        break;
+    }
+    if (hdr.background == 2)
+        for (i = 0; i < hdr.ncolors; i++)
+            HMSG(" %d", hdr.bg_color[i]);
+    if (hdr.ncolors == 1 && hdr.ncmap == 3) {
+        HMSG(" (%d %d %d)",
+                hdr.cmap[hdr.bg_color[0]]>>8,
+                hdr.cmap[hdr.bg_color[0]+256]>>8,
+                hdr.cmap[hdr.bg_color[0]+512]>>8);
+    }
+    else if (hdr.ncolors == 3 && hdr.ncmap == 3) {
+        HMSG(" (%d %d %d)",
+                hdr.cmap[hdr.bg_color[0]]>>8,
+                hdr.cmap[hdr.bg_color[1]+256]>>8,
+                hdr.cmap[hdr.bg_color[2]+512]>>8);
+    }
+    if (hdr.comments)
+        for (i = 0; hdr.comments[i] != NULL; i++)
+            HMSG("%s", hdr.comments[i]);
+}
+/*-----------------------------------------------------------------------------
+ *                                                    Write the ppm image data.
+ */
+static void 
+write_ppm_data(FILE * const imageout_file,
+               FILE * const alpha_file) {
+
+    rle_pixel ***scanlines, **scanline;
+    pixval r, g, b;
+    pixel *pixelrow;
+    gray *alpharow;
+   
+    int scan, x, y;
+    /*
+     *  Allocate some stuff.
+     */
+    pixelrow = ppm_allocrow(width);
+    alpharow = pgm_allocrow(width);
+
+    MALLOCARRAY(scanlines, height);
+    RLE_CHECK_ALLOC( hdr.cmd, scanlines, "scanline pointers" );
+
+    for ( scan = 0; scan < height; scan++ )
+        RLE_CHECK_ALLOC( hdr.cmd, (rle_row_alloc(&hdr, &scanlines[scan]) >= 0),
+                         "pixel memory" );
+    /*
+     * Loop through those scan lines.
+     */
+    for (scan = 0; scan < height; scan++)
+        y = rle_getrow(&hdr, scanlines[height - scan - 1]);
+    for (scan = 0; scan < height; scan++) {
+        scanline = scanlines[scan];
+        switch (visual) {
+        case GRAYSCALE:    /* 8 bits without colormap */
+            for (x = 0; x < width; x++) {
+                r = scanline[0][x];
+                g = scanline[0][x];
+                b = scanline[0][x];
+                PPM_ASSIGN(pixelrow[x], r, g, b);
+                if (hdr.alpha)
+                    alpharow[x] = scanline[-1][x];
+                else 
+                    alpharow[x] = 0;
+            }
+            break;
+        case TRUECOLOR:    /* 24 bits with colormap */
+            for (x = 0; x < width; x++) {
+                r = colormap[scanline[0][x]]>>8;
+                g = colormap[scanline[1][x]+256]>>8;
+                b = colormap[scanline[2][x]+512]>>8;
+                PPM_ASSIGN(pixelrow[x], r, g, b);
+                if (hdr.alpha) 
+                    alpharow[x] = colormap[scanline[-1][x]];
+                else
+                    alpharow[x] = 0;
+            }
+            break;
+        case DIRECTCOLOR:  /* 24 bits without colormap */
+            for (x = 0; x < width; x++) {
+                r = scanline[0][x];
+                g = scanline[1][x];
+                b = scanline[2][x];
+                PPM_ASSIGN(pixelrow[x], r, g, b);
+                if (hdr.alpha)
+                    alpharow[x] = scanline[-1][x];
+                else
+                    alpharow[x] = 0;
+            }
+            break;
+        case PSEUDOCOLOR:  /* 8 bits with colormap */
+            for (x = 0; x < width; x++) {
+                r = colormap[scanline[0][x]]>>8;
+                g = colormap[scanline[0][x]+256]>>8;
+                b = colormap[scanline[0][x]+512]>>8;
+                PPM_ASSIGN(pixelrow[x], r, g, b);
+                if (hdr.alpha) 
+                    alpharow[x] = colormap[scanline[-1][x]];
+                else
+                    alpharow[x] = 0;
+            }
+            break;
+        default:
+            break;
+        }
+        /*
+         * Write the scan line.
+         */
+        if (imageout_file ) 
+            ppm_writeppmrow(imageout_file, pixelrow, width, RLE_MAXVAL, 0);
+        if (alpha_file)
+            pgm_writepgmrow(alpha_file, alpharow, width, RLE_MAXVAL, 0);
+
+    } /* end of for scan = 0 to height */
+
+    /* Free scanline memory. */
+    for ( scan = 0; scan < height; scan++ )
+        rle_row_free( &hdr, scanlines[scan] );
+    free( scanlines );
+    ppm_freerow(pixelrow);
+    pgm_freerow(alpharow);
+}
+
+
+
+static void 
+write_pgm_data(FILE * const imageout_file,
+               FILE * const alpha_file) {
+/*----------------------------------------------------------------------------
+   Write the PGM image data
+-----------------------------------------------------------------------------*/
+    rle_pixel ***scanlines, **scanline;
+    gray * pixelrow;
+    gray * alpharow;
+    int scan;
+    int y;
+    /*
+     *  Allocate some stuff.
+     */
+    pixelrow = pgm_allocrow(width);
+    alpharow = pgm_allocrow(width);
+
+    MALLOCARRAY(scanlines, height);
+    RLE_CHECK_ALLOC( hdr.cmd, scanlines, "scanline pointers" );
+
+    for (scan = 0; scan < height; ++scan)
+        RLE_CHECK_ALLOC(hdr.cmd, (rle_row_alloc(&hdr, &scanlines[scan]) >= 0),
+                        "pixel memory" );
+    /*
+     * Loop through those scan lines.
+     */
+    for (scan = 0; scan < height; ++scan)
+        y = rle_getrow(&hdr, scanlines[height - scan - 1]);
+
+    for (scan = 0; scan < height; ++scan) {
+        int x;
+        scanline = scanlines[scan];
+        for (x = 0; x < width; ++x) {
+            pixelrow[x] = scanline[0][x];
+            if (hdr.alpha) 
+                alpharow[x] = scanline[1][x];
+            else
+                alpharow[x] = 0;
+        }
+        if (imageout_file) 
+            pgm_writepgmrow(imageout_file, pixelrow, width, RLE_MAXVAL, 0);
+        if (alpha_file)
+            pgm_writepgmrow(alpha_file, alpharow, width, RLE_MAXVAL, 0);
+
+        }   /* end of for scan = 0 to height */
+
+    /* Free scanline memory. */
+    for (scan = 0; scan < height; ++scan)
+        rle_row_free(&hdr, scanlines[scan]);
+    free(scanlines);
+    pgm_freerow(pixelrow);
+    pgm_freerow(alpharow);
+}
+
+
+
+/*-----------------------------------------------------------------------------
+ *                               Convert a Utah rle file to a pbmplus pnm file.
+ */
+int
+main(int argc, char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE        *ifp;
+    FILE *imageout_file, *alpha_file;
+    char *fname = NULL;
+
+    pnm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    if ( cmdline.input_filename != NULL ) 
+        ifp = pm_openr( cmdline.input_filename );
+    else
+        ifp = stdin;
+
+    if (cmdline.alpha_stdout)
+        alpha_file = stdout;
+    else if (cmdline.alpha_filename == NULL) 
+        alpha_file = NULL;
+    else {
+        alpha_file = pm_openw(cmdline.alpha_filename);
+    }
+
+    if (cmdline.alpha_stdout) 
+        imageout_file = NULL;
+    else
+        imageout_file = stdout;
+
+
+    /*
+     * Open the file.
+     */
+    /* Initialize header. */
+    hdr = *rle_hdr_init( (rle_hdr *)NULL );
+    rle_names( &hdr, cmd_name( argv ), fname, 0 );
+
+    /*
+     * Read the rle file header.
+     */
+    read_rle_header(ifp, cmdline.headerdump || cmdline.verbose);
+    if (cmdline.headerdump)
+        exit(0);
+
+    /* 
+     * Write the alpha file header
+     */
+    if (alpha_file)
+        pgm_writepgminit(alpha_file, width, height, RLE_MAXVAL, 0);
+
+    /*
+     * Write the pnm file header.
+     */
+    switch (visual) {
+    case GRAYSCALE:   /* 8 bits without colormap -> pgm */
+        if (cmdline.verbose)
+            pm_message("Writing pgm file.");
+        if (imageout_file)
+            pgm_writepgminit(imageout_file, width, height, RLE_MAXVAL, 0);
+        write_pgm_data(imageout_file, alpha_file);
+        break;
+    default:      /* anything else -> ppm */
+        if (cmdline.verbose)
+            pm_message("Writing ppm file.");
+        if (imageout_file)
+            ppm_writeppminit(imageout_file, width, height, RLE_MAXVAL, 0);
+        write_ppm_data(imageout_file, alpha_file);
+        break;
+    }
+   
+    pm_close(ifp);
+    if (imageout_file) 
+        pm_close( imageout_file );
+    if (alpha_file)
+        pm_close( alpha_file );
+
+    return 0;
+}
diff --git a/converter/other/sgi.h b/converter/other/sgi.h
new file mode 100644
index 00000000..3700d356
--- /dev/null
+++ b/converter/other/sgi.h
@@ -0,0 +1,33 @@
+#ifndef SGI_H_INCLUDED
+#define SGI_H_INCLUDED
+
+/* sgi.h - definitions for SGI format */
+
+typedef struct {
+    short           magic;
+    char            storage;
+    char            bpc;            /* pixel size: 1 = bytes, 2 = shorts */
+    unsigned short  dimension;      /* 1 = single row, 2 = B/W, 3 = RGB */
+    unsigned short  xsize,          /* width in pixels */
+                    ysize,          /* height in pixels */
+                    zsize;          /* # of channels; B/W=1, RGB=3, RGBA=4 */
+    long            pixmin, pixmax; /* min/max pixel values */
+    char            dummy1[4];
+    char            name[80];
+    long            colormap;
+    char            dummy2[404];
+} Header;
+#define HeaderSize  512
+
+#define SGI_MAGIC           (short)474
+
+#define STORAGE_VERBATIM    0
+#define STORAGE_RLE         1
+
+#define CMAP_NORMAL         0
+#define CMAP_DITHERED       1   /* not supported */
+#define CMAP_SCREEN         2   /* not supported */
+#define CMAP_COLORMAP       3   /* not supported */
+
+#endif
+
diff --git a/converter/other/sgitopnm.c b/converter/other/sgitopnm.c
new file mode 100644
index 00000000..0d26bb9c
--- /dev/null
+++ b/converter/other/sgitopnm.c
@@ -0,0 +1,463 @@
+/* sgitopnm.c - read an SGI image and and produce a portable anymap
+**
+** Copyright (C) 1994 by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
+**
+** Based on the SGI image description v0.9 by Paul Haeberli (paul@sgi.comp)
+** Available via ftp from sgi.com:graphics/SGIIMAGESPEC
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** 29Jan94: first version
+** 08Feb94: minor bugfix
+** 29Jul00: added -channel option (smar@reptiles.org)
+*/
+#include "pnm.h"
+#include "sgi.h"
+#include "mallocvar.h"
+#ifndef VMS
+#include <unistd.h>
+#endif
+
+/*#define DEBUG*/
+
+#ifndef SEEK_SET
+#define SEEK_SET    0
+#endif
+
+
+/* entry in RLE offset table */
+typedef struct {
+    long start;     /* offset in file */
+    long length;    /* length of compressed scanline */
+} TabEntry;
+
+typedef short       ScanElem;
+typedef ScanElem *  ScanLine;
+
+/* prototypes */
+static unsigned char get_byte ARGS(( FILE* f ));
+static long get_big_long ARGS((FILE *f));
+static short get_big_short ARGS((FILE *f));
+static short get_byte_as_short ARGS((FILE *f));
+static void readerr ARGS((FILE *f));
+static const char *
+compression_name(char compr);
+static void       read_bytes ARGS((FILE *ifp, int n, char *buf));
+static Header *   read_header ARGS((FILE *ifp, int channel));
+static TabEntry * read_table ARGS((FILE *ifp, int tablen));
+static ScanLine * read_channels ARGS((FILE *ifp, Header *head, TabEntry *table, short (*func) ARGS((FILE *)), int ochan ));
+static void       image_to_pnm ARGS((Header *head, ScanLine *image, xelval maxval, int channel));
+static void       rle_decompress ARGS((ScanElem *src, int srclen, ScanElem *dest, int destlen));
+
+#define WORSTCOMPR(x)   (2*(x) + 2)
+
+
+static short verbose = 0;
+
+
+int
+main(argc, argv)
+    int argc;
+    char *argv[];
+{
+    FILE *ifp;
+    int argn;
+    const char * const usage = "[-verbose] [-channel n] [sgifile]";
+    TabEntry *table = NULL;
+    ScanLine *image;
+    Header *head;
+    pixval maxval;
+    int channel = -1;
+
+    pnm_init(&argc, argv);
+
+    argn = 1;
+    while( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) {
+        if( pm_keymatch(argv[argn], "-verbose", 2) )
+            verbose++;
+        else
+        if( pm_keymatch(argv[argn], "-noverbose", 4) )
+            verbose = 0;
+        else
+        if( pm_keymatch(argv[argn], "-channel", 2) ) {
+            char *s;
+
+            ++argn;
+            if( argn >= argc )
+                pm_usage(usage);
+
+            s = argv[argn];
+            channel = strtol(argv[argn], &s, 10);
+            if( s == argv[argn] || channel < 0)
+                pm_usage(usage);
+        }
+        else
+            pm_usage(usage);
+        ++argn;
+    }
+
+    if( argn < argc ) {
+        ifp = pm_openr( argv[argn] );
+        argn++;
+    }
+    else
+        ifp = stdin;
+
+    if( argn != argc )
+        pm_usage(usage);
+
+    head = read_header(ifp, channel);
+    maxval = head->pixmax - head->pixmin;
+    if( maxval > PNM_OVERALLMAXVAL )
+        pm_error("Maximum sample value in input image (%d) is too large.  "
+                 "This program's limit is %d.",
+                 maxval, PNM_OVERALLMAXVAL);
+    if (channel >= head->zsize)
+        pm_error("channel out of range - only %d channels in image",
+            head->zsize);
+    if( head->storage != STORAGE_VERBATIM )
+        table = read_table(ifp, head->ysize * head->zsize);
+    if( head->bpc == 1 )
+        image = read_channels(ifp, head, table, get_byte_as_short, channel);
+    else
+        image = read_channels(ifp, head, table, get_big_short, channel);
+
+    image_to_pnm(head, image, (xelval)maxval, channel);
+    pm_close(ifp);
+
+    exit(0);
+}
+
+
+static Header *
+read_header(ifp, channel)
+    FILE *ifp;
+    int channel;
+{
+    Header *head;
+
+    MALLOCVAR_NOFAIL(head);
+
+    head->magic     = get_big_short(ifp);
+    head->storage   = get_byte(ifp);
+    head->bpc       = get_byte(ifp);
+    head->dimension = get_big_short(ifp);
+    head->xsize     = get_big_short(ifp);
+    head->ysize     = get_big_short(ifp);
+    head->zsize     = get_big_short(ifp);
+    head->pixmin    = get_big_long(ifp);
+    head->pixmax    = get_big_long(ifp);
+    read_bytes(ifp, 4, head->dummy1);
+    read_bytes(ifp, 80, head->name);
+    head->colormap  = get_big_long(ifp);
+    read_bytes(ifp, 404, head->dummy2);
+
+    if( head->magic != SGI_MAGIC )
+        pm_error("bad magic number - not an SGI image");
+    if( head->storage != STORAGE_VERBATIM && head->storage != STORAGE_RLE )
+        pm_error("unknown compression type");
+    if( head->bpc < 1 || head->bpc > 2 )
+        pm_error("illegal precision value %d (only 1-2 allowed)", head->bpc );
+    if( head->colormap != CMAP_NORMAL )
+        pm_error("non-normal pixel data of a form we don't recognize");
+
+    /* adjust ysize/zsize to dimension, just to be sure */
+    switch( head->dimension ) {
+        case 1:
+            head->ysize = 1;
+            break;
+        case 2:
+            head->zsize = 1;
+            break;
+        case 3:
+            switch( head->zsize ) {
+                case 1:
+                    head->dimension = 2;
+                    break;
+                case 2:
+                    pm_error("don\'t know how to interpret 2-channel image");
+                    break;
+                case 3:
+                    break;
+                default:
+                    if (channel < 0)
+                        pm_message("%d-channel image, using only first 3 channels", head->zsize);
+                    break;
+            }
+            break;
+        default:
+            pm_error("illegal dimension value %d (only 1-3 allowed)", head->dimension);
+    }
+
+    if( verbose ) {
+        pm_message("raster size %dx%d, %d channels", head->xsize, head->ysize, head->zsize);
+        pm_message("compression: %d = %s", head->storage, compression_name(head->storage));
+        head->name[79] = '\0';  /* just to be safe */
+        pm_message("Image name: \"%s\"", head->name);
+#ifdef DEBUG
+        pm_message("bpc: %d    dimension: %d    zsize: %d", head->bpc, head->dimension, head->zsize);
+        pm_message("pixmin: %ld    pixmax: %ld    colormap: %ld", head->pixmin, head->pixmax, head->colormap);
+#endif
+    }
+
+    return head;
+}
+
+
+static TabEntry *
+read_table(ifp, tablen)
+    FILE *ifp;
+    int tablen;
+{
+    TabEntry *table;
+    int i;
+
+    MALLOCARRAY_NOFAIL(table, tablen);
+
+#ifdef DEBUG
+    pm_message("reading offset table");
+#endif
+
+    for( i = 0; i < tablen; i++ )
+        table[i].start = get_big_long(ifp);
+    for( i = 0; i < tablen; i++ )
+        table[i].length = get_big_long(ifp);
+
+    return table;
+}
+
+
+
+static ScanLine *
+read_channels(ifp, head, table, func, ochan)
+    FILE *ifp;
+    Header *head;
+    TabEntry *table;
+    short (*func) ARGS((FILE *));
+    int ochan;
+{
+    ScanLine *image;
+    ScanElem *temp;
+    int channel, maxchannel, row, sgi_index, i;
+    long offset, length;
+
+#ifdef DEBUG
+    pm_message("reading channels");
+#endif
+
+    if (ochan < 0) {
+        maxchannel = (head->zsize < 3) ? head->zsize : 3;
+        MALLOCARRAY_NOFAIL(image, head->ysize * maxchannel);
+    } else {
+        maxchannel = ochan + 1;
+        MALLOCARRAY_NOFAIL(image, head->ysize);
+    }
+    if ( table ) 
+        MALLOCARRAY_NOFAIL(temp, WORSTCOMPR(head->xsize));
+
+    for( channel = 0; channel < maxchannel;  channel++ ) {
+#ifdef DEBUG
+        pm_message("    channel %d", channel);
+#endif
+        for( row = 0; row < head->ysize; row++ ) {
+            int iindex;
+
+            sgi_index = channel * head->ysize + row;
+            iindex = (ochan < 0) ? sgi_index : row;
+            if (ochan < 0 || ochan == channel)
+                MALLOCARRAY_NOFAIL(image[iindex], head->xsize);
+
+            if( table ) {
+                if (channel < ochan)
+                    continue;
+
+                offset = table[sgi_index].start;
+                length = table[sgi_index].length;
+                if( head->bpc == 2 )
+                    length /= 2;   
+                    /* doc says length is in bytes, we are reading words */
+                if( fseek(ifp, offset, SEEK_SET) != 0 )
+                    pm_error("seek error for offset %ld", offset);
+
+                for( i = 0; i < length; i++ )
+                    temp[i] = (*func)(ifp);
+                rle_decompress(temp, length, image[iindex], head->xsize);
+            }
+            else {
+                for( i = 0; i < head->xsize; i++ )
+                    image[iindex][i] = (*func)(ifp);
+            }
+        }
+    }
+
+    if( table ) free(temp);
+    return image;
+}
+
+
+static void
+image_to_pnm(Header *head, ScanLine *image, xelval maxval, int channel)
+{
+    int col, row, format;
+    xel *pnmrow = pnm_allocrow(head->xsize);
+    int sub = head->pixmin;
+
+    if( head->zsize == 1 || channel >= 0) {
+        pm_message("writing PGM image");
+        format = PGM_TYPE;
+    }
+    else {
+        pm_message("writing PPM image");
+        format = PPM_TYPE;
+    }
+
+    pnm_writepnminit(stdout, head->xsize, head->ysize, (xelval)maxval, format, 0);
+    for( row = head->ysize-1; row >= 0; row-- ) {
+        for( col = 0; col < head->xsize; col++ ) {
+            if( format == PGM_TYPE )
+                PNM_ASSIGN1(pnmrow[col], image[row][col] - sub);
+            else {
+                pixval r, g, b;
+                r = image[row][col] - sub;
+                g = image[head->ysize + row][col] - sub;
+                b = image[2* head->ysize + row][col] - sub;
+                PPM_ASSIGN(pnmrow[col], r, g, b);
+            }
+        }
+        pnm_writepnmrow(stdout, pnmrow, head->xsize, (xelval)maxval, format, 0);
+    }
+    pnm_freerow(pnmrow);
+}
+
+
+static void
+rle_decompress(src, srcleft, dest, destleft)
+    ScanElem *src;
+    int srcleft;
+    ScanElem *dest;
+    int destleft;
+{
+    int count;
+    unsigned char el;
+
+    while( srcleft ) {
+        el = (unsigned char)(*src++ & 0xff);
+        --srcleft;
+        count = (int)(el & 0x7f);
+
+        if( count == 0 )
+            return;
+        if( destleft < count )
+            pm_error("RLE error: too much input data (space left %d, need %d)", destleft, count);
+        destleft -= count;
+        if( el & 0x80 ) {
+            if( srcleft < count )
+                pm_error("RLE error: not enough data for literal run (data left %d, need %d)", srcleft, count);
+            srcleft -= count;
+            while( count-- )
+                *dest++ = *src++;
+        }
+        else {
+            if( srcleft == 0 )
+                pm_error("RLE error: not enough data for replicate run");
+            while( count-- )
+                *dest++ = *src;
+            ++src;
+            --srcleft;
+        }
+    }
+    pm_error("RLE error: no terminating 0-byte");
+}
+
+
+/* basic I/O functions, taken from ilbmtoppm.c */
+
+static short
+get_big_short(ifp)
+    FILE *ifp;
+{
+    short s;
+
+    if( pm_readbigshort(ifp, &s) == -1 )
+        readerr(ifp);
+
+    return s;
+}
+
+static long
+get_big_long(ifp)
+    FILE *ifp;
+{
+    long l;
+
+    if( pm_readbiglong(ifp, &l) == -1 )
+        readerr(ifp);
+
+    return l;
+}
+
+static unsigned char
+get_byte(ifp)
+    FILE* ifp;
+{
+    int i;
+
+    i = getc(ifp);
+    if( i == EOF )
+        readerr(ifp);
+
+    return (unsigned char) i;
+}
+
+
+static void
+readerr(f)
+    FILE *f;
+{
+    if( ferror(f) )
+        pm_error("read error");
+    else
+        pm_error("premature EOF");
+}
+
+
+static void
+read_bytes(ifp, n, buf)
+    FILE *ifp;
+    int n;
+    char *buf;
+{
+    int r;
+
+    r = fread((void *)buf, 1, n, ifp);
+    if( r != n )
+        readerr(ifp);
+}
+
+
+static short
+get_byte_as_short(ifp)
+    FILE *ifp;
+{
+    return (short)get_byte(ifp);
+}
+
+
+static const char *
+compression_name(char compr)
+{
+    switch( compr ) {
+        case STORAGE_VERBATIM:
+            return "none";
+        case STORAGE_RLE:
+            return "RLE";
+        default:
+            return "unknown";
+    }
+}
+
diff --git a/converter/other/sirtopnm.c b/converter/other/sirtopnm.c
new file mode 100644
index 00000000..fafcc913
--- /dev/null
+++ b/converter/other/sirtopnm.c
@@ -0,0 +1,95 @@
+/* sirtopnm.c - read a Solitaire Image Recorder file and write a portable anymap
+**
+** Copyright (C) 1991 by Marvin Landis.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pnm.h"
+
+int main( argc, argv )
+int argc;
+char* argv[];
+{
+    FILE *ifp;
+    xel *xelrow, *xP;
+    unsigned char *sirarray;
+    int rows, cols, row, format, picsize, planesize;
+    register int col, i;
+    short info;
+
+    pnm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[sirfile]" );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    pm_readlittleshort( ifp, &info );
+    if ( info != 0x3a4f)
+	pm_error( "Input file is not a Solitaire file" );
+    pm_readlittleshort( ifp, &info );
+    pm_readlittleshort( ifp, &info );
+    if ( info == 17 )
+    {
+	format = PGM_TYPE;
+    }
+    else if ( info == 11 )
+    {
+	format = PPM_TYPE;
+    }
+    else
+	pm_error( "Input is not MGI TYPE 11 or MGI TYPE 17" );
+    pm_readlittleshort( ifp, &info );
+    cols = (int) ( info );
+    pm_readlittleshort( ifp, &info );
+    rows = (int) ( info );
+    for ( i = 1; i < 1531; i++ )
+	pm_readlittleshort( ifp, &info );
+
+    pnm_writepnminit( stdout, cols, rows, 255, format, 0 );
+    xelrow = pnm_allocrow( cols );
+    switch ( PNM_FORMAT_TYPE(format) )
+    {
+	case PGM_TYPE:
+            pm_message( "Writing a PGM file" );
+	    for ( row = 0; row < rows; ++row )
+	    {
+	        for ( col = 0, xP = xelrow; col < cols; col++, xP++ )
+	        	PNM_ASSIGN1( *xP, fgetc( ifp ) );
+	        pnm_writepnmrow( stdout, xelrow, cols, 255, format, 0 );
+	    }
+	    break;
+	case PPM_TYPE:
+	    picsize = cols * rows * 3;
+	    planesize = cols * rows;
+            if ( !( sirarray = (unsigned char*) malloc( picsize ) ) ) 
+	        pm_error( "Not enough memory to load SIR file" );
+	    if ( fread( sirarray, 1, picsize, ifp ) != picsize )
+	        pm_error( "Error reading SIR file" );
+            pm_message( "Writing a PPM file" );
+            for ( row = 0; row < rows; row++ )
+	    {
+	        for ( col = 0, xP = xelrow; col < cols; col++, xP++ )
+        	    PPM_ASSIGN( *xP, sirarray[row*cols+col],
+				 sirarray[planesize + (row*cols+col)],
+				 sirarray[2*planesize + (row*cols+col)] );
+                pnm_writepnmrow( stdout, xelrow, cols, 255, format, 0 );
+	    }
+	    break;
+	default:
+	    pm_error( "Shouldn't happen" );
+    }
+
+    pm_close( ifp );
+
+    exit( 0 );
+}
diff --git a/converter/other/svgtopam.c b/converter/other/svgtopam.c
new file mode 100644
index 00000000..268515ad
--- /dev/null
+++ b/converter/other/svgtopam.c
@@ -0,0 +1,940 @@
+/*============================================================================
+                               svgtopam
+==============================================================================
+  This is not useful today.  It is merely a stub from which someone who
+  cares about SVG can build a full converter.
+
+  The framework is all there; it should be just a matter of coding to
+  add each of the SVG features to it.
+
+  Today, the program works fine on an image that consists solely of
+  <path> elements, which use only the "M", "L", and "z" commands.
+
+  By Bryan Henderson, San Jose, California.  May 2006
+
+  Contributed to the public domain.
+============================================================================*/
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <libxml/xmlreader.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "pam.h"
+#include "ppm.h"
+#include "ppmdraw.h"
+
+
+static bool traceDraw;
+
+struct cmdlineInfo {
+    const char * inputFileName;
+    unsigned int trace;
+};
+
+
+
+static void 
+parseCommandLine(int argc, 
+                 char ** argv, 
+                 struct cmdlineInfo  * const cmdlineP) {
+/* --------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+--------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "trace",     OPT_FLAG,   NULL,                  
+            &cmdlineP->trace,       0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;   /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+    /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else {
+        cmdlineP->inputFileName = argv[1];
+        
+        if (argc-1 > 1)
+            pm_error("Too many arguments (%u).  The only non-option argument "
+                     "is the input file name.", argc-1);
+    }
+}
+
+
+
+/*============================================================================
+   Wrappers for libxml2 routines.
+
+   The difference is that these use conventional C data types and have
+   shorter names.
+=============================================================================*/
+
+static const char *
+getAttribute(xmlTextReaderPtr const xmlReaderP,
+             const char *     const attributeName) {
+
+    return (const char *)
+        xmlTextReaderGetAttribute(xmlReaderP, (const xmlChar *)attributeName);
+}
+
+
+
+static const char *
+currentNodeName(xmlTextReaderPtr const xmlReaderP) {
+
+    return (const char *)xmlTextReaderConstName(xmlReaderP);
+}
+
+
+
+/*===========================================================================*/
+
+#define OUTPUT_MAXVAL 255
+
+typedef struct {
+    unsigned int width;
+    unsigned int height;
+    pixel ** pixels;
+    pixval maxval;
+} canvas;
+
+typedef struct {
+    pixel fillColor;
+} style;
+
+
+
+typedef struct {
+    const char * pathText;
+        /* This is e.g. "M0 0 L1 1 L9 8 Z" */
+    style        style;
+        /* This is the style as given by a 'style' attribute of <path> */
+    unsigned int pathTextLength;
+        /* This is the length in characters of 'pathText'.  It's redundant
+           with 'pathText' and exists for convenience.
+        */
+} path;
+
+static void
+createPath(const char * const pathText,
+           style        const style,
+           path **      const pathPP) {
+/*----------------------------------------------------------------------------
+   Create a path as described by a <path> element whose "style" attribute
+   indicates style 'style' and whose "d" attribute indicates path data
+   'pathText'.
+-----------------------------------------------------------------------------*/
+    bool error;
+    path * pathP;
+    
+    MALLOCVAR(pathP);
+    if (pathP == NULL)
+        error = TRUE;
+    else {
+        pathP->style = style;
+
+        pathP->pathText = strdup(pathText);
+        if (pathP->pathText == NULL)
+            error = TRUE;
+        else {
+            pathP->pathTextLength = strlen(pathP->pathText);
+
+            error = FALSE;
+        }
+        if (error)
+            free(pathP);
+    }
+    if (error )
+        *pathPP = NULL;
+    else
+        *pathPP = pathP;
+}
+
+
+
+static void
+destroyPath(path * const pathP) {
+    
+    assert(pathP->pathTextLength == strlen(pathP->pathText));
+
+    strfree(pathP->pathText);
+
+    free(pathP);
+}
+
+
+
+typedef struct {
+    uint x;
+    uint y;
+} point;
+
+static point
+makePoint(uint const x,
+          uint const y) {
+
+    point p;
+    
+    p.x = x;
+    p.y = y;
+    
+    return p;
+}
+
+
+
+typedef enum {
+    PATH_MOVETO,
+    PATH_LINETO,
+    PATH_CLOSEPATH,
+    PATH_CUBIC
+} pathCommandVerb;
+
+typedef struct {
+    point dest;
+} pathMovetoArgs;
+
+typedef struct {
+    /* Draw a line segment from current point to 'dest' */
+    point dest;
+} pathLinetoArgs;
+
+typedef struct {
+    /* Draw a cubic spline from current point to 'dest' with control points
+       'ctl1' at the beginning of the curve and 'ctl2' at the end.
+
+       I.e. it's a section of a cubic curve which passes through the
+       current point and 'dest' and whose slope at the current point
+       is that of the line through the current point and 'ctl1' and
+       whose slope at 'dest' is that of the line through 'dest' and
+       'ctl2';
+
+       A cubic curve is a plot of a polynomial equation of degree 3
+       (or less, for our purposes).
+    */
+    point dest;
+    point ctl1;
+    point ctl2;
+} pathCubicArgs;
+
+typedef struct {
+    pathCommandVerb verb;
+    union {
+        pathMovetoArgs moveto;
+        pathLinetoArgs lineto;
+        pathCubicArgs  cubic;
+    } args;
+} pathCommand;
+
+
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   This is an object for reading through a path from beginning to end.
+-----------------------------------------------------------------------------*/
+    path *       pathP;
+    unsigned int cursor;
+} pathReader;
+
+static void
+createPathReader(path *        const pathP,
+                 pathReader ** const pathReaderPP) {
+
+    pathReader * pathReaderP;
+
+    MALLOCVAR_NOFAIL(pathReaderP);
+
+    pathReaderP->pathP = pathP;
+    pathReaderP->cursor = 0;
+
+    *pathReaderPP = pathReaderP;
+}
+
+static void
+destroyPathReader(pathReader * const pathReaderP) {
+    free(pathReaderP);
+}
+
+
+
+static void
+skipWhiteSpace(pathReader * const pathReaderP) {
+/*----------------------------------------------------------------------------
+   Move the cursor over any white space where it now points.
+-----------------------------------------------------------------------------*/
+    const path * const pathP = pathReaderP->pathP;
+
+    while (isspace(pathP->pathText[pathReaderP->cursor]) &&
+           pathReaderP->cursor < pathP->pathTextLength)
+        ++pathReaderP->cursor;
+}
+
+
+
+static void
+getNumber(pathReader * const pathReaderP,
+          uint *       const numberP) {
+
+    const path * const pathP          = pathReaderP->pathP;
+    const char * const pathText       = pathP->pathText;
+    size_t       const pathTextLength = pathP->pathTextLength;
+
+    assert(!isspace(pathText[pathReaderP->cursor]));
+
+    if (pathReaderP->cursor >= pathTextLength)
+        pm_error("Path description ends where a number was expected.");
+    else {
+        uint number;
+
+        number = 0;  /* initial value */
+
+        while (pathReaderP->cursor < pathTextLength &&
+               isdigit(pathText[pathReaderP->cursor])) {
+            number = 10 * number + (pathText[pathReaderP->cursor] - '0');
+            ++pathReaderP->cursor;
+        }
+        *numberP = number;
+    }
+}
+
+
+
+static void
+getNextCommand(pathReader *  const pathReaderP,
+               pathCommand * const pathCommandP,
+               bool *        const endOfPathP) {
+
+    const path * const pathP          = pathReaderP->pathP;
+    const char * const pathText       = pathP->pathText;
+    size_t       const pathTextLength = pathP->pathTextLength;
+
+    skipWhiteSpace(pathReaderP);
+
+    if (pathReaderP->cursor >= pathTextLength)
+        *endOfPathP = true;
+    else {
+        switch (pathText[pathReaderP->cursor++]) {
+        case 'M':
+            pathCommandP->verb = PATH_MOVETO;
+            skipWhiteSpace(pathReaderP);
+            getNumber(pathReaderP, &pathCommandP->args.moveto.dest.x);
+            skipWhiteSpace(pathReaderP);
+            getNumber(pathReaderP, &pathCommandP->args.moveto.dest.y);
+            break;
+        case 'L':
+            pathCommandP->verb = PATH_LINETO;
+            skipWhiteSpace(pathReaderP);
+            getNumber(pathReaderP, &pathCommandP->args.lineto.dest.x);
+            skipWhiteSpace(pathReaderP);
+            getNumber(pathReaderP, &pathCommandP->args.lineto.dest.y);
+            break;
+        case 'C':
+            pathCommandP->verb = PATH_CUBIC;
+            skipWhiteSpace(pathReaderP);
+            getNumber(pathReaderP, &pathCommandP->args.cubic.ctl1.x);
+            skipWhiteSpace(pathReaderP);
+            getNumber(pathReaderP, &pathCommandP->args.cubic.ctl1.y);
+            skipWhiteSpace(pathReaderP);
+            getNumber(pathReaderP, &pathCommandP->args.cubic.ctl2.x);
+            skipWhiteSpace(pathReaderP);
+            getNumber(pathReaderP, &pathCommandP->args.cubic.ctl2.y);
+            skipWhiteSpace(pathReaderP);
+            getNumber(pathReaderP, &pathCommandP->args.cubic.dest.x);
+            skipWhiteSpace(pathReaderP);
+            getNumber(pathReaderP, &pathCommandP->args.cubic.dest.y);
+            break;
+        case 'z':
+            pathCommandP->verb = PATH_CLOSEPATH;
+            break;
+        default:
+            pm_error("Unrecognized command in <path>: '%c'",
+                     pathText[pathReaderP->cursor++]);
+        }
+    }
+}
+
+
+static void
+outlineObject(path *           const pathP,
+              struct fillobj * const fillObjP) {
+/*----------------------------------------------------------------------------
+  Create a fill object, which contains and outline of the object and
+  can be used with ppmd_fill() to fill the figure.  The outline is as
+  described by *pathP.
+-----------------------------------------------------------------------------*/
+    pathReader * pathReaderP;
+    bool endOfPath;
+    point currentPos;
+    point subpathStart;
+        /* Point at which the current subpath starts */
+
+    endOfPath = false;
+    subpathStart = makePoint(0,0);
+    currentPos = subpathStart;
+
+    createPathReader(pathP, &pathReaderP);
+
+    while (!endOfPath) {
+        pathCommand pathCommand;
+        getNextCommand(pathReaderP, &pathCommand, &endOfPath);
+        if (!endOfPath) {
+            switch (pathCommand.verb) {
+            case PATH_MOVETO:
+                if (traceDraw)
+                    pm_message("Moving to (%u, %u)",
+                               pathCommand.args.moveto.dest.x,
+                               pathCommand.args.moveto.dest.y);
+                subpathStart = pathCommand.args.moveto.dest;
+                currentPos = subpathStart;
+                break;
+            case PATH_LINETO: {
+                point const dest = pathCommand.args.lineto.dest;
+                if (traceDraw)
+                    pm_message("Lining to (%u, %u)", dest.x, dest.y);
+                ppmd_line(NULL, 0, 0, 0,
+                          currentPos.x, currentPos.y, dest.x, dest.y,
+                          ppmd_fill_drawproc, fillObjP);
+                currentPos = dest;
+            } break;
+            case PATH_CLOSEPATH:
+                if (traceDraw)
+                    pm_message("Closing.");
+                ppmd_line(NULL, 0, 0, 0,
+                          currentPos.x, currentPos.y,
+                          subpathStart.x, subpathStart.y,
+                          ppmd_fill_drawproc, fillObjP);
+                currentPos = subpathStart;
+                break;
+            case PATH_CUBIC: {
+                point const dest = pathCommand.args.cubic.dest;
+                point const ctl1 = pathCommand.args.cubic.ctl1;
+                point const ctl2 = pathCommand.args.cubic.ctl2;
+                if (traceDraw)
+                    pm_message("Doing cubic spline to (%u, %u)",
+                               dest.x, dest.y);
+                /* We need to write ppmd_spline4() */
+                ppmd_spline4(NULL, 0, 0, 0,
+                             currentPos.x, currentPos.y,
+                             ctl1.x, ctl1.y,
+                             ctl2.x, ctl2.y,
+                             dest.x, dest.y,
+                             ppmd_fill_drawproc, fillObjP);
+                currentPos = dest;
+            } break;
+            }
+        }
+    }
+    destroyPathReader(pathReaderP);
+}
+
+
+
+static void
+drawPath(canvas * const canvasP,
+         path *   const pathP) {
+/*----------------------------------------------------------------------------
+   Draw the path 'pathP' on the canvas 'canvasP'.
+-----------------------------------------------------------------------------*/
+    struct fillobj * fillObjP;
+
+    if (traceDraw)
+        pm_message("Drawing path '%s' with fill color (%u, %u, %u)",
+                   pathP->pathText,
+                   pathP->style.fillColor.r,
+                   pathP->style.fillColor.g,
+                   pathP->style.fillColor.b);
+
+    fillObjP = ppmd_fill_create();
+
+    outlineObject(pathP, fillObjP);
+
+    ppmd_fill(canvasP->pixels, canvasP->width, canvasP->height,
+              canvasP->maxval,
+              fillObjP, 
+              PPMD_NULLDRAWPROC, &pathP->style.fillColor);
+
+    ppmd_fill_destroy(fillObjP);
+}
+
+
+
+static style
+interpretStyle(const char * const styleAttr) {
+
+    style style;
+
+    char * buffer;
+
+    char * p;
+
+    buffer = strdup(styleAttr);
+    if (buffer == NULL)
+        pm_error("Could not get memory for a buffer to parse style attribute");
+
+    p = &buffer[0];
+
+    while (p) {
+        const char * const token = strsepN(&p, ";");
+        const char * strippedToken;
+        const char * p;
+        char * buffer;
+
+        for (p = &token[0]; isspace(*p); ++p);
+        
+        strippedToken = p;
+
+        buffer = strdup(strippedToken);
+
+        if (strlen(strippedToken) > 0) {
+            char * const colonPos = strchr(buffer, ':');
+
+            if (colonPos == NULL)
+                pm_error("There is no colon in the attribute specification "
+                         "'%s' in the 'style' attribute of a <path> "
+                         "element.", strippedToken);
+            else {
+                const char * const value = colonPos + 1;
+                const char * const name  = &buffer[0];
+                
+                *colonPos = '\0';
+
+                if (streq(name, "fill")) {
+                    style.fillColor = ppm_parsecolor(value, OUTPUT_MAXVAL);
+                } else if (streq(name, "stroke")) {
+                    if (!streq(value, "none"))
+                        pm_error("Value of 'stroke' attribute in the 'style' "
+                                 "attribute of a <path> element is '%s'.  We "
+                                 "understand only 'none'", value);
+                } else
+                    pm_error("Unrecognized attribute '%s' "
+                             "in the 'style' attribute "
+                             "of a <path> element", name);
+            }
+        }
+        free(buffer);
+    }
+
+    free(buffer);
+
+    return style;
+}
+
+
+static void
+getPathAttributes(xmlTextReaderPtr const xmlReaderP,
+                  style *          const styleP,
+                  const char **    const pathP) {
+
+    const char * const style = getAttribute(xmlReaderP, "style");
+    const char * const d     = getAttribute(xmlReaderP, "d");
+
+    *styleP = interpretStyle(style);
+    *pathP = d;
+}
+
+
+
+static void
+processSubPathNode(xmlTextReaderPtr const xmlReaderP,
+                   bool *           const endOfPathP) {
+
+    xmlReaderTypes const nodeType  = xmlTextReaderNodeType(xmlReaderP);
+
+    *endOfPathP = FALSE;  /* initial assumption */
+
+    switch (nodeType) {
+    case XML_READER_TYPE_ELEMENT:
+        pm_error("<path> contains a <%s> element.  <path> should have "
+                 "no contents", currentNodeName(xmlReaderP));
+        break;
+    case XML_READER_TYPE_END_ELEMENT: {
+        const char * nodeName = currentNodeName(xmlReaderP);
+        if (streq(nodeName, "path"))
+            *endOfPathP = TRUE;
+        else
+            pm_error("</%s> found where </path> expected", nodeName);
+        } break;
+    default:
+        /* Just ignore whatever this is.  Contents of <path> are
+           meaningless; all the information is in the attributes 
+        */
+        break;
+    }
+}
+
+
+
+static void
+processPathElement(xmlTextReaderPtr const xmlReaderP,
+                   canvas *         const canvasP) {
+
+    style style;
+    const char * pathData;
+    path * pathP;
+    bool endOfPath;
+
+    assert(xmlTextReaderNodeType(xmlReaderP) == XML_READER_TYPE_ELEMENT);
+    assert(streq(currentNodeName(xmlReaderP), "path"));
+
+    getPathAttributes(xmlReaderP, &style, &pathData);
+
+    createPath(pathData, style, &pathP);
+
+    if (pathP) {
+        drawPath(canvasP, pathP);
+        destroyPath(pathP);
+    }
+
+    endOfPath = xmlTextReaderIsEmptyElement(xmlReaderP);
+
+    while (!endOfPath) {
+        int rc;
+
+        rc = xmlTextReaderRead(xmlReaderP);
+        
+        switch (rc) {
+        case 1:
+            processSubPathNode(xmlReaderP, &endOfPath);
+            break;
+        case 0:
+            pm_error("Input file ends in the middle of a <path>");
+            break;
+        default:
+            pm_error("xmlTextReaderRead() failed, rc=%d", rc);
+        }
+    }
+}
+
+
+
+static void
+stringToUint(const char *   const string,
+             unsigned int * const uintP,
+             const char **  const errorP) {
+
+    /* TODO: move this to nstring.c */
+
+    if (strlen(string) == 0)
+        asprintfN(errorP, "Value is a null string");
+    else {
+        char * tailptr;
+
+        *uintP = strtoul(string, &tailptr, 10);
+
+        if (*tailptr != '\0')
+            asprintfN(errorP, "Non-numeric crap in string: '%s'", tailptr);
+        else
+            *errorP = NULL;
+    }
+}
+
+
+
+static void
+getSvgAttributes(xmlTextReaderPtr const xmlReaderP,
+                 unsigned int *   const colsP,
+                 unsigned int *   const rowsP) {
+
+    const char * const width  = getAttribute(xmlReaderP, "width");
+    const char * const height = getAttribute(xmlReaderP, "height");
+
+    const char * error;
+
+    stringToUint(width, colsP, &error);
+    if (error) {
+        pm_error("'width' attribute of <svg> has invalid value.  %s", error);
+        strfree(error);
+    }
+    stringToUint(height, rowsP, &error);
+    if (error) {
+        pm_error("'height' attribute of <svg> has invalid value.  %s", error);
+        strfree(error);
+    }
+}
+
+
+
+static void
+processSubSvgElement(xmlTextReaderPtr const xmlReaderP,
+                     canvas *         const canvasP) {
+
+    const char * const nodeName = currentNodeName(xmlReaderP);
+
+    assert(xmlTextReaderNodeType(xmlReaderP) == XML_READER_TYPE_ELEMENT);
+    
+    if (streq(nodeName, "path"))
+        processPathElement(xmlReaderP, canvasP);
+    else
+        pm_error("This image contains a <%s> element.  This program "
+                 "understands only <path>!", nodeName);
+}
+
+
+
+static void
+processSubSvgNode(xmlTextReaderPtr const xmlReaderP,
+                  canvas *         const canvasP,
+                  bool *           const endOfSvgP) {
+
+    xmlReaderTypes const nodeType = xmlTextReaderNodeType(xmlReaderP);
+
+    *endOfSvgP = FALSE;  /* initial assumption */
+
+    switch (nodeType) {
+    case XML_READER_TYPE_ELEMENT:
+        processSubSvgElement(xmlReaderP, canvasP);
+        break;
+    case XML_READER_TYPE_END_ELEMENT: {
+        const char * const nodeName = currentNodeName(xmlReaderP);
+        if (streq(nodeName, "svg"))
+            *endOfSvgP = TRUE;
+        else
+            pm_error("</%s> found where </svg> expected", nodeName);
+    } break;
+    default:
+        /* Just ignore whatever this is */
+        break;
+    }
+}
+
+
+
+static void
+createCanvas(unsigned int const width,
+             unsigned int const height,
+             pixval       const maxval,
+             canvas **    const canvasPP) {
+
+    canvas * canvasP;
+
+    MALLOCVAR_NOFAIL(canvasP);
+
+    canvasP->width  = width;
+    canvasP->height = height;
+    canvasP->pixels = ppm_allocarray(width, height);
+    canvasP->maxval = maxval;
+
+    *canvasPP = canvasP;
+}
+
+
+
+static void
+destroyCanvas(canvas * const canvasP) {
+
+    ppm_freearray(canvasP->pixels, canvasP->height);
+
+    free(canvasP);
+}
+
+
+
+static void
+writePam(FILE *   const ofP,
+         canvas * const canvasP) {
+
+    unsigned int row;
+    struct pam pam;
+    tuple * tuplerow;
+
+    pam.size             = sizeof(pam);
+    pam.len              = PAM_STRUCT_SIZE(tuple_type);
+    pam.file             = ofP;
+    pam.format           = PAM_FORMAT;
+    pam.plainformat      = 0;
+    pam.width            = canvasP->width;
+    pam.height           = canvasP->height;
+    pam.depth            = 3;
+    pam.maxval           = OUTPUT_MAXVAL;
+    strcpy(pam.tuple_type, PAM_PPM_TUPLETYPE);
+    
+    pnm_writepaminit(&pam);
+
+    tuplerow = pnm_allocpamrow(&pam);
+
+    for (row = 0; row < (unsigned)pam.height; ++row) {
+        pixel * const pixelrow = canvasP->pixels[row];
+        unsigned int col;
+
+        for (col = 0; col < (unsigned)pam.width; ++col) {
+            pixel const thisPixel = pixelrow[col];
+            assert(pam.depth >= 3);
+
+            tuplerow[col][PAM_RED_PLANE] = PPM_GETR(thisPixel);
+            tuplerow[col][PAM_GRN_PLANE] = PPM_GETG(thisPixel);
+            tuplerow[col][PAM_BLU_PLANE] = PPM_GETB(thisPixel);
+        }
+        pnm_writepamrow(&pam, tuplerow);
+    }
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+static void
+processSvgElement(xmlTextReaderPtr const xmlReaderP,
+                  FILE *           const ofP) {
+
+    unsigned int width, height;
+    bool endOfSvg;
+    canvas * canvasP;
+
+    assert(xmlTextReaderNodeType(xmlReaderP) == XML_READER_TYPE_ELEMENT);
+    assert(streq(currentNodeName(xmlReaderP), "svg"));
+
+    getSvgAttributes(xmlReaderP, &width, &height);
+
+    createCanvas(width, height, 255, &canvasP);
+
+    endOfSvg = xmlTextReaderIsEmptyElement(xmlReaderP);
+
+    while (!endOfSvg) {
+        int rc;
+
+        rc = xmlTextReaderRead(xmlReaderP);
+        
+        switch (rc) {
+        case 1:
+            processSubSvgNode(xmlReaderP, canvasP, &endOfSvg);
+            break;
+        case 0:
+            pm_error("Input file ends in the middle of a <svg>");
+            break;
+        default:
+            pm_error("xmlTextReaderRead() failed, rc=%d", rc);
+        }
+    }
+    writePam(ofP, canvasP);
+
+    destroyCanvas(canvasP);
+}
+
+
+
+static void
+processTopLevelElement(xmlTextReaderPtr const xmlReaderP,
+                       FILE *           const ofP) {
+
+    const char * const nodeName = currentNodeName(xmlReaderP);
+
+    assert(xmlTextReaderNodeType(xmlReaderP) == XML_READER_TYPE_ELEMENT);
+    assert(xmlTextReaderDepth(xmlReaderP) == 0);
+
+    if (!streq(nodeName, "svg"))
+        pm_error("Not an SVG image.  This XML document consists of "
+                 "a <%s> element, whereas an SVG image is an <svg> "
+                 "element.", nodeName);
+    else
+        processSvgElement(xmlReaderP, ofP);
+}
+
+
+
+static void
+processTopLevelNode(xmlTextReaderPtr const xmlReaderP,
+                    FILE *           const ofP) {
+
+    unsigned int   const depth    = xmlTextReaderDepth(xmlReaderP);
+    xmlReaderTypes const nodeType = xmlTextReaderNodeType(xmlReaderP);
+
+    assert(depth == 0);
+
+    switch (nodeType) {
+    case XML_READER_TYPE_ELEMENT:
+        processTopLevelElement(xmlReaderP, ofP);
+        break;
+    default:
+        /* Just ignore whatever this is */
+        break;
+    }
+}
+
+
+
+static void
+processDocument(xmlTextReaderPtr const xmlReaderP,
+                FILE *           const ofP) {
+
+    bool eof;
+
+    eof = false;
+
+    while (!eof) {
+        int rc;
+
+        rc = xmlTextReaderRead(xmlReaderP);
+        
+        switch (rc) {
+        case 1:
+            processTopLevelNode(xmlReaderP, ofP);
+            break;
+        case 0:
+            eof = true;
+            break;
+        default:
+            pm_error("xmlTextReaderRead() failed, rc=%d", rc);
+        }
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    xmlTextReaderPtr xmlReaderP;
+
+    pnm_init(&argc, argv);
+
+    xmlInitParser();
+
+    LIBXML_TEST_VERSION;
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    traceDraw = cmdline.trace;
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    xmlReaderP = xmlReaderForFd(fileno(ifP), "SVG_IMAGE", NULL, 0);
+
+    if (xmlReaderP) {
+        processDocument(xmlReaderP, stdout);
+
+        /* xmlTextReaderIsValid() does not appear to work.  It always says
+           the document is invalid
+        */
+
+        xmlFreeTextReader(xmlReaderP);
+    } else
+        pm_error("Failed to create xmlReader");
+
+    xmlCleanupParser();
+
+    return 0;
+}
diff --git a/converter/other/tiff.c b/converter/other/tiff.c
new file mode 100644
index 00000000..a498571b
--- /dev/null
+++ b/converter/other/tiff.c
@@ -0,0 +1,468 @@
+/*============================================================================
+                                 tiff.c
+==============================================================================
+
+  Stuff specific to the TIFF format -- essentially and extension to libtiff.
+
+============================================================================*/
+
+#include <string.h>
+
+#ifdef VMS
+#ifdef SYSV
+#undef SYSV
+#endif
+#include <tiffioP.h>
+#endif
+#include <tiffio.h>
+
+#include "pm_c_util.h"
+#include "nstring.h"
+#include "mallocvar.h"
+#include "pm.h"
+#include "pm_tiff.h"
+
+
+
+static uint32
+number(const char * const value,
+       tagvalmap    const tagvallist[]) {
+    
+    char * ep;
+    long num;
+
+    if (strlen(value) == 0)
+        pm_error("null string where numeric tag value or enumerated tag "
+                 "value name expected");
+    
+    num = strtol(value, &ep, 10);
+    if (*ep != '\0') {
+        /* It's not a numeric string, so it must be an enumerated value name */
+        unsigned int i;
+        for (i = 0; tagvallist[i].name; ++i) {
+            if (STRCASEEQ(value, tagvallist[i].name))
+                return tagvallist[i].value;
+        }
+        pm_error("'%s' is neither a number nor a valid value name", value);
+        return 0; /* avoid compiler warning */
+    } else
+        return num;
+}
+
+
+
+static double
+dnumber(const char * const value) {
+
+    char * ep;
+    double num;
+    
+    num = strtod(value, &ep);
+    if (ep == value || *ep != '\0')
+        pm_error("Bad floating point number %s", value);
+
+    return num;
+}
+
+
+
+#if 0
+static void
+putByte(TIFF *          const tifP,
+        unsigned int    const tagnum,
+        const char *    const value,
+        tagvalmap const tagvallist[]) {
+
+    uint16 const num = number(value, tagvallist);
+
+    TIFFSetField(tifP, tagnum, num);
+}
+#endif
+
+
+
+static void
+putAscii(TIFF *       const tifP,
+         unsigned int const tagnum,
+         const char * const value,
+         tagvalmap    const tagvallist[]) {
+
+    TIFFSetField(tifP, tagnum, value);
+}
+
+
+
+static void
+putShort(TIFF *       const tifP,
+         unsigned     const tagnum,
+         const char * const value,
+         tagvalmap    const tagvallist[]) {
+
+    uint16 const num = number(value, tagvallist);
+
+    TIFFSetField(tifP, tagnum, num);
+}
+
+
+
+static void
+putLong(TIFF *       const tifP,
+        unsigned int const tagnum,
+        const char * const value,
+        tagvalmap    const tagvallist[]) {
+
+    uint32 const num = number(value, tagvallist);
+
+    TIFFSetField(tifP, tagnum, num);
+}
+
+
+
+static void
+putRational(TIFF *       const tifP,
+            unsigned     const tagnum,
+            const char * const value,
+            tagvalmap    const tagvallist[]) {
+
+    float const num = dnumber(value);
+
+    TIFFSetField(tifP, tagnum, num);
+}
+
+
+
+static void
+putCountBytes(TIFF *       const tifP,
+              unsigned     const tagnum,
+              const char * const value,
+              tagvalmap    const tagvallist[]) {
+
+    uint32 const len = strlen(value)/2;
+
+    char * data;
+    uint32 i;
+    bool   error;
+
+    MALLOCARRAY_NOFAIL(data, len);
+
+    for (i = 0, error = FALSE; !error && i < len; ++i) {
+        unsigned int val;
+        int    nb;
+        int    rc;
+        rc = sscanf(&value[i*2], "%2x%n", &val, &nb);
+        if (rc != 1 || nb != 2)
+            error = TRUE;
+        else
+            data[i] = val;
+    }
+    if (!error)
+        TIFFSetField(tifP, tagnum, len, (void *)data);
+
+    free(data);
+}
+
+
+
+#define TV(p,a) { #a, p##a, }
+
+static tagvalmap const 
+tvm_compression[] = {
+    TV(COMPRESSION_,NONE),
+    TV(COMPRESSION_,CCITTRLE),
+    TV(COMPRESSION_,CCITTFAX3),
+    TV(COMPRESSION_,CCITTFAX4),
+    TV(COMPRESSION_,LZW),
+    TV(COMPRESSION_,OJPEG),
+    TV(COMPRESSION_,JPEG),
+    TV(COMPRESSION_,NEXT),
+    TV(COMPRESSION_,CCITTRLEW),
+    TV(COMPRESSION_,PACKBITS),
+    TV(COMPRESSION_,THUNDERSCAN),
+    TV(COMPRESSION_,IT8CTPAD),
+    TV(COMPRESSION_,IT8LW),
+    TV(COMPRESSION_,IT8MP),
+    TV(COMPRESSION_,IT8BL),
+    TV(COMPRESSION_,PIXARFILM),
+    TV(COMPRESSION_,PIXARLOG),
+    TV(COMPRESSION_,DEFLATE),
+    TV(COMPRESSION_,ADOBE_DEFLATE),
+    TV(COMPRESSION_,DCS),
+    TV(COMPRESSION_,JBIG),
+    TV(COMPRESSION_,SGILOG),
+    TV(COMPRESSION_,SGILOG24),
+    { NULL, 0, },
+};
+
+static tagvalmap const
+tvm_faxmode[] = {
+    TV(FAXMODE_,CLASSIC),
+    TV(FAXMODE_,NORTC),
+    TV(FAXMODE_,NOEOL),
+    TV(FAXMODE_,BYTEALIGN),
+    TV(FAXMODE_,WORDALIGN),
+    TV(FAXMODE_,CLASSF),
+    { NULL, 0, },
+};
+
+static tagvalmap const
+tvm_fillorder[] = {
+    TV(FILLORDER_,MSB2LSB),
+    TV(FILLORDER_,LSB2MSB),
+    { NULL, 0, },
+};
+
+static tagvalmap
+const tvm_group3options[] = {
+    TV(GROUP3OPT_,2DENCODING),
+    TV(GROUP3OPT_,UNCOMPRESSED),
+    TV(GROUP3OPT_,FILLBITS),
+    { NULL, 0, },
+};
+
+static tagvalmap
+const tvm_group4options[] = {
+    TV(GROUP4OPT_,UNCOMPRESSED),
+    { NULL, 0, },
+};
+
+static tagvalmap
+const tvm_inkset[] = {
+    TV(INKSET_,CMYK),
+    { NULL, 0, },
+};
+
+static tagvalmap
+const tvm_orientation[] = {
+    TV(ORIENTATION_,TOPLEFT),
+    TV(ORIENTATION_,TOPRIGHT),
+    TV(ORIENTATION_,BOTRIGHT),
+    TV(ORIENTATION_,BOTLEFT),
+    TV(ORIENTATION_,LEFTTOP),
+    TV(ORIENTATION_,RIGHTTOP),
+    TV(ORIENTATION_,RIGHTBOT),
+    TV(ORIENTATION_,LEFTBOT),
+    { NULL, 0, },
+};
+
+static tagvalmap const
+tvm_photometric[] = {
+    TV(PHOTOMETRIC_,MINISWHITE),
+    TV(PHOTOMETRIC_,MINISBLACK),
+    TV(PHOTOMETRIC_,RGB),
+    TV(PHOTOMETRIC_,PALETTE),
+    TV(PHOTOMETRIC_,MASK),
+    TV(PHOTOMETRIC_,SEPARATED),
+    TV(PHOTOMETRIC_,YCBCR),
+    TV(PHOTOMETRIC_,CIELAB),
+    TV(PHOTOMETRIC_,LOGL),
+    TV(PHOTOMETRIC_,LOGLUV),
+    { NULL, 0, },
+};
+
+static tagvalmap const
+tvm_planarconfig[] = {
+    TV(PLANARCONFIG_,CONTIG),
+    TV(PLANARCONFIG_,SEPARATE),
+    { NULL, 0, },
+};
+
+static tagvalmap const
+tvm_resolutionunit[] = {
+    TV(RESUNIT_,NONE),
+    TV(RESUNIT_,INCH),
+    TV(RESUNIT_,CENTIMETER),
+    { NULL, 0, },
+};
+
+static tagvalmap const
+tvm_sampleformat[] = {
+    TV(SAMPLEFORMAT_,UINT),
+    TV(SAMPLEFORMAT_,INT),
+    TV(SAMPLEFORMAT_,IEEEFP),
+    TV(SAMPLEFORMAT_,VOID),
+    { NULL, 0, },
+};
+
+static tagvalmap const
+tvm_subfiletype[] = {
+    TV(FILETYPE_,REDUCEDIMAGE),
+    TV(FILETYPE_,PAGE),
+    TV(FILETYPE_,MASK),
+    { NULL, 0, },
+};
+
+static tagvalmap const
+tvm_threshholding[] =
+{
+    TV(THRESHHOLD_,BILEVEL),
+    TV(THRESHHOLD_,HALFTONE),
+    TV(THRESHHOLD_,ERRORDIFFUSE),
+    { NULL, 0, },
+};
+#undef TV
+
+/*
+ * For full functionality, some tags have an implied count
+ * (eg. colormap, dotrange). See TIFFGetField(3).
+ * Also, some are arrays.
+ */
+#define DECL(a,p,c) { #a, TIFFTAG_##a, p, c, }
+
+static tagDefinition const
+tagDefinitions[] = {
+    DECL(ARTIST,                  putAscii,    NULL),
+    DECL(BADFAXLINES,             putLong,     NULL),
+    DECL(BITSPERSAMPLE,           putShort,    NULL),
+    DECL(CELLLENGTH,              NULL,        NULL),
+    DECL(CELLWIDTH,               NULL,        NULL),
+    DECL(CLEANFAXDATA,            putShort,    NULL),
+    DECL(COLORMAP,                NULL,        NULL),
+    DECL(COLORRESPONSEUNIT,       NULL,        NULL),
+    DECL(COMPRESSION,             putShort,    tvm_compression),
+    DECL(CONSECUTIVEBADFAXLINES,  putLong,     NULL),
+    DECL(COPYRIGHT,               putAscii,    NULL),
+    DECL(DATATYPE,                putShort,    NULL),
+    DECL(DATETIME,                putAscii,    NULL),
+    DECL(DCSBALANCEARRAY,         NULL,        NULL),
+    DECL(DCSCALIBRATIONFD,        NULL,        NULL),
+    DECL(DCSCLIPRECTANGLE,        NULL,        NULL),
+    DECL(DCSCORRECTMATRIX,        NULL,        NULL),
+    DECL(DCSGAMMA,                NULL,        NULL),
+    DECL(DCSHUESHIFTVALUES,       NULL,        NULL),
+    DECL(DCSIMAGERTYPE,           NULL,        NULL),
+    DECL(DCSINTERPMODE,           NULL,        NULL),
+    DECL(DCSTOESHOULDERPTS,       NULL,        NULL),
+    DECL(DOCUMENTNAME,            putAscii,    NULL),
+    DECL(DOTRANGE,                NULL,        NULL),
+    DECL(EXTRASAMPLES,            NULL,        NULL),
+    DECL(FAXFILLFUNC,             NULL,        NULL),
+    DECL(FAXMODE,                 putLong,     tvm_faxmode),
+    DECL(FAXRECVPARAMS,           NULL,        NULL),
+    DECL(FAXRECVTIME,             NULL,        NULL),
+    DECL(FAXSUBADDRESS,           NULL,        NULL),
+    DECL(FEDEX_EDR,               NULL,        NULL),
+    DECL(FILLORDER,               putShort,    tvm_fillorder),
+    DECL(FRAMECOUNT,              NULL,        NULL),
+    DECL(FREEBYTECOUNTS,          NULL,        NULL),
+    DECL(FREEOFFSETS,             NULL,        NULL),
+    DECL(GRAYRESPONSECURVE,       NULL,        NULL),
+    DECL(GRAYRESPONSEUNIT,        NULL,        NULL),
+    DECL(GROUP3OPTIONS,           putLong,     tvm_group3options),
+    DECL(GROUP4OPTIONS,           putLong,     tvm_group4options),
+    DECL(HALFTONEHINTS,           NULL,        NULL),
+    DECL(HOSTCOMPUTER,            putAscii,    NULL),
+    DECL(ICCPROFILE,              putCountBytes, NULL),
+    DECL(IMAGEDEPTH,              putLong,     NULL),
+    DECL(IMAGEDESCRIPTION,        putAscii,    NULL),
+    DECL(IMAGELENGTH,             putLong,     NULL),
+    DECL(IMAGEWIDTH,              putLong,     NULL),
+    DECL(INKNAMES,                putAscii,    NULL),
+    DECL(INKSET,                  putShort,    tvm_inkset),
+    DECL(IT8BITSPEREXTENDEDRUNLENGTH, NULL,    NULL),
+    DECL(IT8BITSPERRUNLENGTH,     NULL,        NULL),
+    DECL(IT8BKGCOLORINDICATOR,    NULL,        NULL),
+    DECL(IT8BKGCOLORVALUE,        NULL,        NULL),
+    DECL(IT8COLORCHARACTERIZATION, NULL,       NULL),
+    DECL(IT8COLORSEQUENCE,        NULL,        NULL),
+    DECL(IT8COLORTABLE,           NULL,        NULL),
+    DECL(IT8HEADER,               NULL,        NULL),
+    DECL(IT8IMAGECOLORINDICATOR,  NULL,        NULL),
+    DECL(IT8IMAGECOLORVALUE,      NULL,        NULL),
+    DECL(IT8PIXELINTENSITYRANGE,  NULL,        NULL),
+    DECL(IT8RASTERPADDING,        NULL,        NULL),
+    DECL(IT8SITE,                 NULL,        NULL),
+    DECL(IT8TRANSPARENCYINDICATOR, NULL,       NULL),
+    DECL(JBIGOPTIONS,             NULL,        NULL),
+    DECL(JPEGACTABLES,            NULL,        NULL),
+    DECL(JPEGCOLORMODE,           NULL,        NULL),
+    DECL(JPEGDCTABLES,            NULL,        NULL),
+    DECL(JPEGIFBYTECOUNT,         NULL,        NULL),
+    DECL(JPEGIFOFFSET,            NULL,        NULL),
+    DECL(JPEGLOSSLESSPREDICTORS,  NULL,        NULL),
+    DECL(JPEGPOINTTRANSFORM,      NULL,        NULL),
+    DECL(JPEGPROC,                NULL,        NULL),
+    DECL(JPEGQTABLES,             NULL,        NULL),
+    DECL(JPEGQUALITY,             NULL,        NULL),
+    DECL(JPEGRESTARTINTERVAL,     NULL,        NULL),
+    DECL(JPEGTABLES,              NULL,        NULL),
+    DECL(JPEGTABLESMODE,          NULL,        NULL),
+    DECL(MAKE,                    putAscii,    NULL),
+    DECL(MATTEING,                putShort,    NULL),
+    DECL(MAXSAMPLEVALUE,          putShort,    NULL),
+    DECL(MINSAMPLEVALUE,          putShort,    NULL),
+    DECL(MODEL,                   putAscii,    NULL),
+    DECL(NUMBEROFINKS,            NULL,        NULL),
+    DECL(ORIENTATION,             putShort,    tvm_orientation),
+    DECL(OSUBFILETYPE,            NULL,        NULL),
+    DECL(PAGENAME,                putAscii,    NULL),
+    DECL(PAGENUMBER,              NULL,        NULL),
+    DECL(PHOTOMETRIC,             putShort,    tvm_photometric),
+    DECL(PHOTOSHOP,               NULL,        NULL),
+    DECL(PIXAR_FOVCOT,            NULL,        NULL),
+    DECL(PIXAR_IMAGEFULLLENGTH,   NULL,        NULL),
+    DECL(PIXAR_IMAGEFULLWIDTH,    NULL,        NULL),
+    DECL(PIXAR_MATRIX_WORLDTOCAMERA, NULL,     NULL),
+    DECL(PIXAR_MATRIX_WORLDTOSCREEN, NULL,     NULL),
+    DECL(PIXAR_TEXTUREFORMAT,     NULL,        NULL),
+    DECL(PIXAR_WRAPMODES,         NULL,        NULL),
+    DECL(PIXARLOGDATAFMT,         NULL,        NULL),
+    DECL(PIXARLOGQUALITY,         NULL,        NULL),
+    DECL(PLANARCONFIG,            putShort,    tvm_planarconfig),
+    DECL(PREDICTOR,               putShort,    NULL),
+    DECL(PRIMARYCHROMATICITIES,   NULL,        NULL),
+    DECL(REFERENCEBLACKWHITE,     NULL,        NULL),
+    DECL(REFPTS,                  NULL,        NULL),
+    DECL(REGIONAFFINE,            NULL,        NULL),
+    DECL(REGIONTACKPOINT,         NULL,        NULL),
+    DECL(REGIONWARPCORNERS,       NULL,        NULL),
+    DECL(RESOLUTIONUNIT,          putShort,    tvm_resolutionunit),
+    DECL(RICHTIFFIPTC,            NULL,        NULL),
+    DECL(ROWSPERSTRIP,            putLong,     NULL),
+    DECL(SAMPLEFORMAT,            putShort,    tvm_sampleformat),
+    DECL(SAMPLESPERPIXEL,         putShort,    NULL),
+    DECL(SGILOGDATAFMT,           NULL,        NULL),
+    DECL(SMAXSAMPLEVALUE,         NULL,        NULL),
+    DECL(SMINSAMPLEVALUE,         NULL,        NULL),
+    DECL(SOFTWARE,                putAscii,    NULL),
+    DECL(STONITS,                 NULL,        NULL),
+    DECL(STRIPBYTECOUNTS,         NULL,        NULL),
+    DECL(STRIPOFFSETS,            NULL,        NULL),
+    DECL(SUBFILETYPE,             putLong,     tvm_subfiletype),
+    DECL(SUBIFD,                  NULL,        NULL),
+    DECL(TARGETPRINTER,           putAscii,    NULL),
+    DECL(THRESHHOLDING,           putShort,    tvm_threshholding),
+    DECL(TILEBYTECOUNTS,          NULL,        NULL),
+    DECL(TILEDEPTH,               putLong,     NULL),
+    DECL(TILELENGTH,              putLong,     NULL),
+    DECL(TILEOFFSETS,             NULL,        NULL),
+    DECL(TILEWIDTH,               putLong,     NULL),
+    DECL(TRANSFERFUNCTION,        NULL,        NULL),
+    DECL(WHITEPOINT,              NULL,        NULL),
+    DECL(WRITERSERIALNUMBER,      NULL,        NULL),
+    DECL(XPOSITION,               putRational, NULL),
+    DECL(XRESOLUTION,             putRational, NULL),
+    DECL(YCBCRCOEFFICIENTS,       NULL,        NULL),
+    DECL(YCBCRPOSITIONING,        NULL,        NULL),
+    DECL(YCBCRSUBSAMPLING,        NULL,        NULL),
+    DECL(YPOSITION,               putRational, NULL),
+    DECL(YRESOLUTION,             putRational, NULL),
+    DECL(ZIPQUALITY,              NULL,        NULL),
+};
+#undef DECL
+
+
+
+const tagDefinition *
+tagDefFind(const char * const name) {
+
+    unsigned int i;
+
+    for (i = 0;
+         i < ARRAY_SIZE(tagDefinitions) && tagDefinitions[i].name;
+         ++i) {
+        if (STRCASEEQ(tagDefinitions[i].name, name))
+            return &tagDefinitions[i];
+    }
+
+    return NULL;
+}
diff --git a/converter/other/tifftopnm.c b/converter/other/tifftopnm.c
new file mode 100644
index 00000000..5969a49a
--- /dev/null
+++ b/converter/other/tifftopnm.c
@@ -0,0 +1,1062 @@
+/*
+** tifftopnm.c - converts a Tagged Image File to a portable anymap
+**
+** Derived by Jef Poskanzer from tif2ras.c, which is:
+**
+** Copyright (c) 1990 by Sun Microsystems, Inc.
+**
+** Author: Patrick J. Naughton
+** naughton@wind.sun.com
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted,
+** provided that the above copyright notice appear in all copies and that
+** both that copyright notice and this permission notice appear in
+** supporting documentation.
+**
+** This file is provided AS IS with no warranties of any kind.  The author
+** shall have no liability with respect to the infringement of copyrights,
+** trade secrets or any patents by this file or any part thereof.  In no
+** event will the author be liable for any lost revenue or profits or
+** other special, indirect and consequential damages.
+*/
+
+/* Design note:
+
+   We have two different ways of converting from Tiff, as provided by the
+   Tiff library:  
+
+   1) decode the entire image into memory at once, using
+      TIFFRGBAImageGet(), then convert to PNM and output row by row.
+   
+   2) read, convert, and output one row at a time using TIFFReadScanline().
+
+   (1) is preferable because the Tiff library does more of the work, which
+   means it understands more of the Tiff format possibilities now and in 
+   the future.  Also, some compressed TIFF formats don't allow you to 
+   extract an individual row.
+
+   (2) uses far less memory, and because our code does more of the work,
+   it's possible that it can be more flexible or at least give better
+   diagnostic information if there's something wrong with the TIFF.
+
+   In Netpbm, we stress function over performance, so by default we
+   try (1) first, and if we can't get enough memory for the decoded
+   image or TIFFRGBAImageGet() fails, we fall back to (2).  But we
+   give the user the -byrow option to order (2) only.
+*/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <string.h>
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+#ifdef VMS
+#ifdef SYSV
+#undef SYSV
+#endif
+#include <tiffioP.h>
+#endif
+/* See warning about tiffio.h in pamtotiff.c */
+#include <tiffio.h>
+
+/* The following are in current tiff.h, but so that we can compile against
+   older tiff libraries, we define them here.
+*/
+
+#ifndef PHOTOMETRIC_LOGL
+#define PHOTOMETRIC_LOGL 32844
+#endif
+#ifndef PHOTOMETRIC_LOGLUV
+#define PHOTOMETRIC_LOGLUV 32845
+#endif
+
+#define MAXCOLORS 1024
+#ifndef PHOTOMETRIC_DEPTH
+#define PHOTOMETRIC_DEPTH 32768
+#endif
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    char * inputFilename;
+    unsigned int headerdump;
+    char * alphaFilename;
+    bool alphaStdout;
+    unsigned int respectfillorder;   /* -respectfillorder option */
+    unsigned int byrow;
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that many of the strings that this function returns in the
+   *cmdlineP structure are actually in the supplied argv array.  And
+   sometimes, one of these strings is actually just a suffix of an entry
+   in argv!
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;
+    optEntry *option_def;
+    unsigned int option_def_index;
+    unsigned int alphaSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;
+    opt.allowNegNum = FALSE;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "verbose", 
+            OPT_FLAG,   NULL, &cmdlineP->verbose,              0);
+    OPTENT3(0, "respectfillorder", 
+            OPT_FLAG,   NULL, &cmdlineP->respectfillorder,     0);
+    OPTENT3(0,   "byrow",   
+            OPT_FLAG,   NULL, &cmdlineP->byrow,                0);
+    OPTENT3('h', "headerdump", 
+            OPT_FLAG,   NULL, &cmdlineP->headerdump,           0);
+    OPTENT3(0,   "alphaout",   
+            OPT_STRING, &cmdlineP->alphaFilename, &alphaSpec,  0);
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+    if (argc - 1 == 0)
+        cmdlineP->inputFilename = strdup("-");  /* he wants stdin */
+    else if (argc - 1 == 1)
+        cmdlineP->inputFilename = strdup(argv[1]);
+    else 
+        pm_error("Too many arguments.  The only argument accepted "
+                 "is the input file name");
+
+    if (alphaSpec) {
+        if (STREQ(cmdlineP->alphaFilename, "-"))
+            cmdlineP->alphaStdout = TRUE;
+        else
+            cmdlineP->alphaStdout = FALSE;
+    } else {
+        cmdlineP->alphaFilename = NULL;
+        cmdlineP->alphaStdout = FALSE;
+    }
+}
+
+
+
+static void 
+read_directory(TIFF * const tif,
+               unsigned short * const bps_p,
+               unsigned short * const spp_p,
+               unsigned short * const photomet_p,
+               unsigned short * const planarconfig_p,
+               unsigned short * const fillorder_p,
+               unsigned int *   const cols_p,
+               unsigned int *   const rows_p,
+               bool             const headerdump) {
+/*----------------------------------------------------------------------------
+   Read various values of TIFF tags from the TIFF directory, and
+   default them if not in there and make guesses where values are
+   invalid.  Exit program with error message if required tags aren't
+   there or values are inconsistent or beyond our capabilities.  if
+   'headerdump' is true, issue informational messages about what we
+   find.
+
+   The TIFF library is capable of returning invalid values (if the
+   input file contains invalid values).  We generally return those
+   invalid values to our caller.
+-----------------------------------------------------------------------------*/
+    int rc;
+    unsigned short tiff_bps;
+    unsigned short tiff_spp;
+
+    if (headerdump)
+        TIFFPrintDirectory(tif, stderr, TIFFPRINT_NONE);
+
+    rc = TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &tiff_bps);
+    *bps_p = (rc == 0) ? 1 : tiff_bps;
+
+    if (*bps_p < 1 || (*bps_p > 8 && *bps_p != 16 && *bps_p != 32))
+        pm_error("This program can process Tiff images with only "
+                 "1-8 or 16 bits per sample.  The input Tiff image "
+                 "has %d bits per sample.", *bps_p);
+
+    rc = TIFFGetFieldDefaulted(tif, TIFFTAG_FILLORDER, fillorder_p);
+    rc = TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &tiff_spp);
+    *spp_p = (rc == 0) ? 1: tiff_spp;
+
+    rc = TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, photomet_p);
+    if (rc == 0)
+        pm_error("PHOTOMETRIC tag is not in Tiff file.  "
+                 "TIFFGetField() of it failed.\n"
+                 "This means the input is not valid Tiff.");
+
+    if (*spp_p > 1) {
+        rc = TIFFGetField(tif, TIFFTAG_PLANARCONFIG, planarconfig_p);
+        if (rc == 0)
+            pm_error("PLANARCONFIG tag is not in Tiff file, though it "
+                     "has more than one sample per pixel.  "
+                     "TIFFGetField() of it failed.  This means the input "
+                     "is not valid Tiff.");
+    } else {
+        *planarconfig_p = PLANARCONFIG_CONTIG;
+    }
+
+    switch(*planarconfig_p) {
+    case PLANARCONFIG_CONTIG:
+        break;
+    case PLANARCONFIG_SEPARATE:
+        if (*photomet_p != PHOTOMETRIC_RGB && 
+            *photomet_p != PHOTOMETRIC_SEPARATED)
+            pm_error("This program can handle separate planes only "
+                     "with RGB (PHOTOMETRIC tag = %d) or SEPARATED "
+                     "(PHOTOMETRIC tag = %d) data.  The input Tiff file " 
+                     "has PHOTOMETRIC tag = %d.",
+                     PHOTOMETRIC_RGB, PHOTOMETRIC_SEPARATED, *photomet_p);
+        break;
+    default:
+        pm_error("Unrecognized PLANARCONFIG tag value in Tiff input: %d.\n",
+                 *planarconfig_p);
+    }
+
+    rc = TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, cols_p);
+    if (rc == 0)
+        pm_error("Input Tiff file is invalid.  It has no IMAGEWIDTH tag.");
+    rc = TIFFGetField( tif, TIFFTAG_IMAGELENGTH, rows_p );
+    if (rc == 0)
+        pm_error("Input Tiff file is invalid.  It has no IMAGELENGTH tag.");
+
+    if (headerdump) {
+        pm_message( "%dx%dx%d image", *cols_p, *rows_p, *bps_p * *spp_p );
+        pm_message( "%d bits/sample, %d samples/pixel", *bps_p, *spp_p );
+    }
+}
+
+
+
+static void
+readscanline(TIFF *         const tif, 
+             unsigned char  scanbuf[], 
+             int            const row, 
+             int            const plane,
+             unsigned int   const cols, 
+             unsigned short const bps,
+             unsigned short const spp,
+             unsigned short const fillorder,
+             unsigned int * const samplebuf) {
+/*----------------------------------------------------------------------------
+   Read one scanline out of the Tiff input and store it into samplebuf[].
+   Unlike the scanline returned by the Tiff library function, samplebuf[]
+   is composed of one sample per array element, which makes it easier for
+   our caller to process.
+
+   scanbuf[] is a scratch array for our use, which is big enough to hold
+   a Tiff scanline.
+-----------------------------------------------------------------------------*/
+    int rc;
+    const unsigned int bpsmask = (1 << bps) - 1;
+      /* A mask for taking the lowest 'bps' bits of a number */
+
+    /* The TIFFReadScanline man page doesn't tell the format of its
+       'buf' return value, but it is exactly the same format as the 'buf'
+       input to TIFFWriteScanline.  The man page for that doesn't say 
+       anything either, but the source code for Pnmtotiff contains a
+       specification.
+    */
+
+    rc = TIFFReadScanline(tif, scanbuf, row, plane);
+    if (rc < 0)
+        pm_error( "Unable to read row %d, plane %d of input Tiff image.  "
+                  "TIFFReadScanline() failed.",
+                  row, plane);
+    else if (bps == 8) {
+        int sample;
+        for (sample = 0; sample < cols*spp; sample++) 
+            samplebuf[sample] = scanbuf[sample];
+    } else if (bps < 8) {
+        /* Note that in this format, samples do not span bytes.  Rather,
+           each byte may have don't-care bits in the right-end positions.
+           At least that's how I infer the format from reading pnmtotiff.c
+           -Bryan 00.11.18
+           */
+        int sample;
+        int bitsleft;
+        unsigned char * inP;
+
+        for (sample = 0, bitsleft=8, inP=scanbuf; 
+             sample < cols*spp; 
+             sample++) {
+            if (bitsleft == 0) {
+                ++inP; 
+                bitsleft = 8;
+            } 
+            switch (fillorder) {
+            case FILLORDER_MSB2LSB:
+                samplebuf[sample] = (*inP >> (bitsleft-bps)) & bpsmask; 
+                break;
+            case FILLORDER_LSB2MSB:
+                samplebuf[sample] = (*inP >> (8-bitsleft)) & bpsmask;
+                break;
+            default:
+                pm_error("Internal error: invalid value for fillorder: %u", 
+                         fillorder);
+            }
+            bitsleft -= bps; 
+            if (bitsleft < bps)
+                /* Don't count dregs at end of byte */
+                bitsleft = 0;
+       }
+    } else if (bps == 16) {
+        /* Before Netpbm 9.17, this program assumed that scanbuf[]
+           contained an array of bytes as read from the Tiff file.  In
+           fact, in this bps == 16 case, it's an array of "shorts",
+           each stored in whatever format this platform uses (which is
+           none of our concern).  The pre-9.17 code also presumed that
+           the TIFF "FILLORDER" tag determined the order in which the
+           bytes of each sample appear in a TIFF file, which is
+           contrary to the TIFF spec.  
+        */
+        uint16 * const scanbuf16 = (uint16 *) scanbuf;
+        unsigned int sample;
+
+        for (sample = 0; sample < cols*spp; ++sample)
+            samplebuf[sample] = scanbuf16[sample];
+    } else if (bps == 32) {
+        uint32 * const scanbuf32 = (uint32 *) scanbuf;
+        unsigned int sample;
+        
+        for (sample = 0; sample < cols*spp; ++sample)
+            samplebuf[sample] = scanbuf32[sample];
+    } else 
+        pm_error("Internal error: invalid bits per sample passed to "
+                 "readscanline()");
+}
+
+
+
+static void
+pick_cmyk_pixel(const unsigned int samplebuf[], const int sample_cursor,
+                xelval * const r_p, xelval * const b_p, xelval * const g_p) {
+
+    /* Note that the TIFF spec does not say which of the 4 samples is
+       which, but common sense says they're in order C,M,Y,K.  Before
+       Netpbm 10.21 (March 2004), we assumed C,Y,M,K for some reason.
+       But combined with a compensating error in the CMYK->RGB
+       calculation, it had the same effect as C,M,Y,K.
+    */
+    unsigned int const c = samplebuf[sample_cursor+0];
+    unsigned int const m = samplebuf[sample_cursor+1];
+    unsigned int const y = samplebuf[sample_cursor+2];
+    unsigned int const k = samplebuf[sample_cursor+3];
+
+    /* The CMYK->RGB formula used by TIFFRGBAImageGet() in the TIFF 
+       library is the following, (with some apparent confusion with
+       the names of the yellow and magenta pigments being reversed).
+
+       R = (1-K)*(1-C)     (with C,Y,M,K normalized to 0..1)
+       G = (1-K)*(1-M)
+       B = (1-K)*(1-Y)
+
+       We used that too before Netpbm 10.21 (March 2004).
+
+       Now we use the inverse of what Pnmtotiffcmyk has always used, which
+       makes sense as follows:  A microliter of black ink is simply a 
+       substitute for a microliter each of cyan, magenta, and yellow ink.
+       Yellow ink removes blue light from what the white paper reflects.  
+    */
+
+    *r_p = 255 - MIN(255, c + k);
+    *g_p = 255 - MIN(255, m + k);
+    *b_p = 255 - MIN(255, y + k);
+}
+
+
+
+static void
+computeFillorder(unsigned short   const fillorderTag, 
+                 unsigned short * const fillorderP, 
+                 bool             const respectfillorder) {
+
+    if (respectfillorder) {
+        if (fillorderTag != FILLORDER_MSB2LSB && 
+            fillorderTag != FILLORDER_LSB2MSB)
+            pm_error("Invalid value in Tiff input for the FILLORDER tag: %u.  "
+                     "Valid values are %u and %u.  Try omitting the "
+                     "-respectfillorder option.", 
+                     fillorderTag, FILLORDER_MSB2LSB, FILLORDER_LSB2MSB);
+        else
+            *fillorderP = fillorderTag;
+    } else {
+        *fillorderP = FILLORDER_MSB2LSB;
+        if (fillorderTag != *fillorderP)
+            pm_message("Warning: overriding FILLORDER tag in the tiff input "
+                       "and assuming msb-to-lsb.  Consider the "
+                       "-respectfillorder option.");
+    }
+}
+
+
+
+static void
+analyzeImageType(TIFF *             const tif, 
+                 unsigned short     const bps, 
+                 unsigned short     const spp, 
+                 unsigned short     const photomet,
+                 xelval *           const maxvalP, 
+                 int *              const formatP, 
+                 xel                      colormap[],
+                 bool               const headerdump,
+                 struct cmdlineInfo const cmdline) {
+
+    bool grayscale; 
+
+    if (bps == 1 && spp == 1) {
+        if (cmdline.headerdump)
+            pm_message("bilevel");
+        grayscale = TRUE;
+        *maxvalP = 1;
+    } else {
+        /* How come we don't deal with the photometric for the monochrome 
+           case (make sure it's one we know)?  -Bryan 00.03.04
+        */
+        switch (photomet) {
+        case PHOTOMETRIC_MINISBLACK:
+        case PHOTOMETRIC_MINISWHITE:
+            if (spp != 1)
+                pm_error("This grayscale image has %d samples per pixel.  "
+                         "We understand only 1.", spp);
+            grayscale = TRUE;
+            *maxvalP = pm_bitstomaxval(MIN(bps,16));
+            if (headerdump)
+                pm_message("grayscale image, (min=%s) output maxval %u ", 
+                           photomet == PHOTOMETRIC_MINISBLACK ? 
+                           "black" : "white",
+                           *maxvalP
+                           );
+            break;
+            
+        case PHOTOMETRIC_PALETTE: {
+            int i;
+            int numcolors;
+            unsigned short* redcolormap;
+            unsigned short* greencolormap;
+            unsigned short* bluecolormap;
+
+            if (headerdump)
+                pm_message("colormapped");
+
+            if (spp != 1)
+                pm_error("This paletted image has %d samples per pixel.  "
+                         "We understand only 1.", spp);
+
+            if (!TIFFGetField(tif, TIFFTAG_COLORMAP, 
+                              &redcolormap, &greencolormap, &bluecolormap))
+                pm_error("error getting colormaps");
+
+            numcolors = 1 << bps;
+            if (numcolors > MAXCOLORS)
+                pm_error("too many colors");
+            *maxvalP = PNM_MAXMAXVAL;
+            grayscale = FALSE;
+            for (i = 0; i < numcolors; ++i) {
+                xelval r, g, b;
+                r = (long) redcolormap[i] * PNM_MAXMAXVAL / 65535L;
+                g = (long) greencolormap[i] * PNM_MAXMAXVAL / 65535L;
+                b = (long) bluecolormap[i] * PNM_MAXMAXVAL / 65535L;
+                PPM_ASSIGN(colormap[i], r, g, b);
+            }
+        }
+        break;
+
+        case PHOTOMETRIC_SEPARATED: {
+            unsigned short inkset;
+
+            if (headerdump)
+                pm_message("color separation");
+            if (TIFFGetField(tif, TIFFTAG_INKNAMES, &inkset) == 1
+                && inkset != INKSET_CMYK)
+            if (inkset != INKSET_CMYK) 
+                pm_error("This color separation file uses an inkset (%d) "
+                         "we can't handle.  We handle only CMYK.", inkset);
+            if (spp != 4) 
+                pm_error("This CMYK color separation file is %d samples per "
+                         "pixel.  "
+                         "We need 4 samples, though: C, M, Y, and K.  ",
+                         spp);
+            grayscale = FALSE;
+            *maxvalP = (1 << bps) - 1;
+        }
+        break;
+            
+        case PHOTOMETRIC_RGB:
+            if (headerdump)
+                pm_message("RGB truecolor");
+            grayscale = FALSE;
+
+            if (spp != 3 && spp != 4)
+                pm_error("This RGB image has %d samples per pixel.  "
+                         "We understand only 3 or 4.", spp);
+
+            *maxvalP = (1 << bps) - 1;
+            break;
+
+        case PHOTOMETRIC_MASK:
+            pm_error("don't know how to handle PHOTOMETRIC_MASK");
+
+        case PHOTOMETRIC_DEPTH:
+            pm_error("don't know how to handle PHOTOMETRIC_DEPTH");
+
+        case PHOTOMETRIC_YCBCR:
+            pm_error("don't know how to handle PHOTOMETRIC_YCBCR");
+
+        case PHOTOMETRIC_CIELAB:
+            pm_error("don't know how to handle PHOTOMETRIC_CIELAB");
+
+        case PHOTOMETRIC_LOGL:
+            pm_error("don't know how to handle PHOTOMETRIC_LOGL");
+
+        case PHOTOMETRIC_LOGLUV:
+            pm_error("don't know how to handle PHOTOMETRIC_LOGLUV");
+            
+        default:
+            pm_error("unknown photometric: %d", photomet);
+        }
+    }
+    if (*maxvalP > PNM_OVERALLMAXVAL)
+        pm_error("bits/sample (%d) in the input image is too large.",
+                 bps);
+    if (grayscale) {
+        if (*maxvalP == 1) {
+            *formatP = PBM_TYPE;
+            pm_message("writing PBM file");
+        } else {
+            *formatP = PGM_TYPE;
+            pm_message("writing PGM file");
+        }
+    } else {
+        *formatP = PPM_TYPE;
+        pm_message("writing PPM file");
+    }
+}
+
+
+
+static void
+convertRow(unsigned int   const samplebuf[], 
+           xel                  xelrow[], 
+           gray                 alpharow[], 
+           int            const cols, 
+           xelval         const maxval, 
+           unsigned short const photomet, 
+           unsigned short const spp,
+           xel            const colormap[]) {
+/*----------------------------------------------------------------------------
+   Assuming samplebuf[] is an array of raster values as returned by the Tiff
+   library, convert it to a libnetpbm row in xelrow[] and alpharow[].
+-----------------------------------------------------------------------------*/
+    switch (photomet) {
+    case PHOTOMETRIC_MINISBLACK: {
+        int col;
+        for (col = 0; col < cols; ++col) {
+            PNM_ASSIGN1(xelrow[col], samplebuf[col]);
+            alpharow[col] = 0;
+        }
+    }
+    break;
+    
+    case PHOTOMETRIC_MINISWHITE: {
+        int col;
+        for (col = 0; col < cols; ++col) {
+            PNM_ASSIGN1(xelrow[col], maxval - samplebuf[col]);
+            alpharow[col] = 0;
+        }
+    }
+    break;
+
+    case PHOTOMETRIC_PALETTE: {
+        int col;
+        for ( col = 0; col < cols; ++col ) {
+            /* We know the following array index is in bounds because
+               we filled samplebuf with samples of 'bps' bits each and
+               we verified that the largest number that fits in 'bps'
+               bits is less than MAXCOLORS, the dimension of the array.
+            */
+            xelrow[col] = colormap[samplebuf[col]];
+            alpharow[col] = 0;
+        }
+    }
+    break;
+
+    case PHOTOMETRIC_SEPARATED: {
+        int col, sample;
+        for (col = 0, sample = 0; col < cols; ++col, sample+=spp) {
+            xelval r, g, b;
+            pick_cmyk_pixel(samplebuf, sample, &r, &b, &g);
+            
+            PPM_ASSIGN(xelrow[col], r, g, b);
+            alpharow[col] = 0;
+        }
+    }
+    break;
+
+    case PHOTOMETRIC_RGB: {
+        int col, sample;
+        for (col = 0, sample = 0; col < cols; ++col, sample+=spp) {
+            PPM_ASSIGN(xelrow[col], samplebuf[sample+0],
+                       samplebuf[sample+1], samplebuf[sample+2]);
+            if (spp >= 4)
+                alpharow[col] = samplebuf[sample+3];
+            else
+                alpharow[col] = 0;
+        }
+        break;
+    }       
+    default:
+        pm_error("internal error:  unknown photometric in the picking "
+                 "routine: %d", photomet);
+    }
+}
+
+
+
+static void
+scale32to16(unsigned int       samplebuf[],
+            unsigned int const cols,
+            unsigned int const spp) {
+/*----------------------------------------------------------------------------
+  Convert every sample in samplebuf[] to something that can be expressed
+  in 16 bits, assuming it takes 32 bits now.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+    for (i = 0; i < cols * spp; ++i)
+        samplebuf[i] >>= 16; 
+}
+
+
+
+static void
+convertMultiPlaneRow(TIFF *         const tif,
+                     xel                   xelrow[],
+                     gray                  alpharow[],
+                     int             const cols,
+                     xelval          const maxval,
+                     int             const row,
+                     unsigned short  const photomet,
+                     unsigned short  const bps,
+                     unsigned short  const spp,
+                     unsigned short  const fillorder,
+                     unsigned char * const scanbuf,
+                     unsigned int *  const samplebuf) {
+
+    /* The input is in separate planes, so we need to read one
+       scanline for the reds, another for the greens, then another
+       for the blues.
+    */
+
+    int col;
+
+    if (photomet != PHOTOMETRIC_RGB)
+        pm_error("This is a multiple-plane file, but is not an RGB "
+                 "file.  This program does not know how to handle that.");
+    else {
+        /* First, clear the buffer so we can add red, green,
+           and blue one at a time.  
+                */
+        for (col = 0; col < cols; ++col) 
+            PPM_ASSIGN(xelrow[col], 0, 0, 0);
+
+        /* Read the reds */
+        readscanline(tif, scanbuf, row, 0, cols, bps, spp, fillorder, 
+                     samplebuf);
+        if (bps == 32)
+            scale32to16(samplebuf, cols, spp);
+        for (col = 0; col < cols; ++col) 
+            PPM_PUTR(xelrow[col], samplebuf[col]);
+                
+        /* Next the greens */
+        readscanline(tif, scanbuf, row, 1, cols, bps, spp, fillorder,
+                     samplebuf);
+        if (bps == 32)
+            scale32to16(samplebuf, cols, spp);
+        for (col = 0; col < cols; ++col) 
+            PPM_PUTG( xelrow[col], samplebuf[col] );
+            
+        /* And finally the blues */
+        readscanline(tif, scanbuf, row, 2, cols, bps, spp, fillorder,
+                     samplebuf);
+        if (bps == 32)
+            scale32to16(samplebuf, cols, spp);
+        for (col = 0; col < cols; ++col) 
+            PPM_PUTB(xelrow[col], samplebuf[col]);
+
+        /* Could there be an alpha plane?  (We assume no.  But if so,
+           here is where to read it) 
+        */
+        for (col = 0; col < cols; ++col) 
+            alpharow[col] = 0;
+    }
+}
+
+
+
+static void
+convertRasterByRows(FILE *         const imageoutFile, 
+                    FILE *         const alphaFile,
+                    unsigned int   const cols, 
+                    unsigned int   const rows,
+                    xelval         const maxval,
+                    int            const format, 
+                    TIFF *         const tif,
+                    unsigned short const photomet, 
+                    unsigned short const planarconfig,
+                    unsigned short const bps,
+                    unsigned short const spp,
+                    unsigned short const fillorder,
+                    xel                  colormap[]) {
+/*----------------------------------------------------------------------------
+   With the TIFF header all processed (and relevant information from it in 
+   our arguments), write out the TIFF raster to the file images *imageoutFile
+   and *alphaFile.
+
+   Do this one row at a time, employing the TIFF library's
+   TIFFReadScanline.
+-----------------------------------------------------------------------------*/
+    unsigned char * scanbuf;
+        /* Buffer for a raster line in the format returned by TIFF library's
+           TIFFReadScanline
+        */
+    unsigned int * samplebuf;
+        /* Same info as 'scanbuf' above, but with each raster column (sample)
+           represented as single array element, so it's easy to work with.
+        */
+    xel* xelrow;
+        /* The ppm-format row of the image row we are presently converting */
+    gray* alpharow;
+        /* The pgm-format row representing the alpha values for the image 
+           row we are presently converting.
+        */
+
+    int row;
+
+    scanbuf = (unsigned char *) malloc(TIFFScanlineSize(tif));
+    if (scanbuf == NULL)
+        pm_error("can't allocate memory for scanline buffer");
+
+    MALLOCARRAY(samplebuf, cols * spp);
+    if (samplebuf == NULL)
+        pm_error ("can't allocate memory for row buffer");
+
+    xelrow = pnm_allocrow(cols);
+    alpharow = pgm_allocrow(cols);
+
+    for ( row = 0; row < rows; ++row ) {
+        /* Read one row of samples into samplebuf[] */
+
+        if (planarconfig == PLANARCONFIG_CONTIG) {
+            readscanline(tif, scanbuf, row, 0, cols, bps, spp, fillorder, 
+                         samplebuf);
+            if (bps == 32)
+                scale32to16(samplebuf, cols, spp);
+            convertRow(samplebuf, xelrow, alpharow, cols, maxval, 
+                       photomet, spp, colormap);
+        } else 
+            convertMultiPlaneRow(tif, xelrow, alpharow, cols, maxval, row,
+                                 photomet, bps, spp, fillorder,
+                                 scanbuf, samplebuf);
+            
+        if (imageoutFile != NULL) 
+            pnm_writepnmrow( imageoutFile, 
+                             xelrow, cols, (xelval) maxval, format, 0 );
+        if (alphaFile != NULL) 
+            pgm_writepgmrow( alphaFile, alpharow, cols, (gray) maxval, 0);
+    }
+    pgm_freerow(alpharow);
+    pnm_freerow(xelrow);
+
+    free(samplebuf);
+    free(scanbuf);
+}    
+
+
+
+
+static void 
+convertTiffRaster(uint32 *        const raster, 
+                  unsigned int    const cols,
+                  unsigned int    const rows,
+                  xelval          const maxval,
+                  int             const format,
+                  FILE *          const imageoutFile,
+                  FILE *          const alphaFile) {
+/*----------------------------------------------------------------------------
+   Convert the raster 'raster' from the format generated by the TIFF library
+   to PPM (plus PGM alpha mask where applicable) and output it to
+   the files *imageoutFile and *alphaFile in format 'format' with maxval
+   'maxval'.  The raster is 'cols' wide by 'rows' high.
+-----------------------------------------------------------------------------*/
+    xel* xelrow;
+        /* The ppm-format row of the image row we are
+           presently converting 
+        */
+    gray* alpharow;
+        /* The pgm-format row representing the alpha values
+           for the image row we are presently converting.  
+        */
+    int row;
+
+    xelrow = pnm_allocrow(cols);
+    alpharow = pgm_allocrow(cols);
+
+    for (row = 0; row < rows; ++row) {
+        uint32* rp;  
+            /* Address of pixel in 'raster' we are presently converting */
+        int col;
+
+        /* Start at beginning of row: */
+        rp = raster + (rows - row - 1) * cols;
+    
+        for (col = 0; col < cols; ++col) {
+            uint32 const tiffPixel = *rp++;
+                    
+            PPM_ASSIGN(xelrow[col], 
+                       TIFFGetR(tiffPixel) * maxval / 255, 
+                       TIFFGetG(tiffPixel) * maxval / 255, 
+                       TIFFGetB(tiffPixel) * maxval / 255);
+            alpharow[col] = TIFFGetA(tiffPixel) * maxval / 255 ;
+        }
+        
+        if (imageoutFile != NULL) 
+            pnm_writepnmrow(imageoutFile, xelrow, cols, maxval, format, 0);
+        if (alphaFile != NULL) 
+            pgm_writepgmrow(alphaFile, alpharow, cols, maxval, 0);
+    }
+    
+    pgm_freerow(alpharow);
+    pnm_freerow(xelrow);
+}    
+
+
+
+enum convertDisp {CONV_DONE, CONV_OOM, CONV_UNABLE, CONV_FAILED, 
+                  CONV_NOTATTEMPTED};
+
+static void
+convertRasterInMemory(FILE *         const imageoutFile, 
+                      FILE *         const alphaFile,
+                      unsigned int   const cols, 
+                      unsigned int   const rows,
+                      xelval         const maxval,
+                      int            const format, 
+                      TIFF *         const tif,
+                      unsigned short const photomet, 
+                      unsigned short const planarconfig,
+                      unsigned short const bps,
+                      unsigned short const spp,
+                      unsigned short const fillorder,
+                      xel                  colormap[],
+                      enum convertDisp * const statusP) {
+/*----------------------------------------------------------------------------
+   With the TIFF header all processed (and relevant information from it in 
+   our arguments), write out the TIFF raster to the file images *imageoutFile
+   and *alphaFile.
+
+   Do this by reading the entire TIFF image into memory at once and formatting
+   it with the TIFF library's TIFFRGBAImageGet().
+
+   Return *statusP == CONV_OOM iff we are unable to proceed because we cannot
+   get memory to store the entire raster.  This means Caller may still be able
+   to do the conversion using a row-by-row strategy.  Like typical Netpbm
+   programs, we simply abort the program if we are unable to allocate
+   memory for other things.
+-----------------------------------------------------------------------------*/
+    if (rows == 0 || cols == 0) 
+        *statusP = CONV_DONE;
+    else {
+        char emsg[1024] ;
+        int ok;
+        ok = TIFFRGBAImageOK(tif, emsg);
+        if (!ok) {
+            pm_message(emsg);
+            *statusP = CONV_UNABLE;
+        } else {
+            uint32* raster ;
+
+            /* Note that TIFFRGBAImageGet() converts any bits per sample
+               to 8.  Maxval of the raster it returns is always 255.
+            */
+            MALLOCARRAY(raster, cols * rows);
+            if (raster == NULL) {
+                pm_message("Unable to allocate space for a raster of %u "
+                           "pixels.", cols * rows);
+                *statusP = CONV_OOM;
+            } else {
+                int const stopOnErrorFalse = FALSE;
+                TIFFRGBAImage img ;
+                int ok;
+                
+                ok = TIFFRGBAImageBegin(&img, tif, stopOnErrorFalse, emsg) ;
+                if (!ok) {
+                    pm_message(emsg);
+                    *statusP = CONV_FAILED;
+                } else {
+                    int ok;
+                    ok = TIFFRGBAImageGet(&img, raster, cols, rows);
+                    TIFFRGBAImageEnd(&img) ;
+                    if (!ok) {
+                        pm_message(emsg);
+                        *statusP = CONV_FAILED;
+                    } else {
+                        *statusP = CONV_DONE;
+                        convertTiffRaster(raster, cols, rows, maxval, format, 
+                                          imageoutFile, alphaFile);
+                    }
+                } 
+                free(raster);
+            }
+        }
+    }
+}
+
+
+
+static void
+convertImage(TIFF *             const tifP,
+             FILE *             const alphaFile, 
+             FILE *             const imageoutFile,
+             struct cmdlineInfo const cmdline) {
+
+    unsigned int cols, rows;
+    int format;
+    xelval maxval;
+    unsigned short bps, spp;
+
+    xel colormap[MAXCOLORS];
+    unsigned short photomet, planarconfig, fillorderTag;
+    unsigned short fillorder;
+
+    read_directory(tifP, &bps, &spp, &photomet, &planarconfig, &fillorderTag,
+                   &cols, &rows, 
+                   cmdline.headerdump);
+
+
+    computeFillorder(fillorderTag, &fillorder, cmdline.respectfillorder);
+
+    analyzeImageType(tifP, bps, spp, photomet, 
+                     &maxval, &format, colormap, cmdline.headerdump, cmdline);
+
+    if (imageoutFile != NULL) 
+        pnm_writepnminit( imageoutFile, 
+                          cols, rows, (xelval) maxval, format, 0 );
+    if (alphaFile != NULL) 
+        pgm_writepgminit( alphaFile, cols, rows, (gray) maxval, 0 );
+
+    {
+        enum convertDisp status;
+        if (cmdline.byrow)
+            status = CONV_NOTATTEMPTED;
+        else {
+            convertRasterInMemory(
+                imageoutFile, alphaFile, cols, rows, maxval, format, 
+                tifP, photomet, planarconfig, bps, spp, fillorder,
+                colormap, &status);
+        }
+        if (status == CONV_DONE) {
+            if (bps > 8)
+                pm_message("actual resolution has been reduced to 24 bits "
+                           "per pixel in the conversion.  You can get the "
+                           "full %u bits that are in the TIFF with the "
+                           "-byrow option.", bps);
+        } else {
+            if (status != CONV_NOTATTEMPTED)
+                pm_message("In-memory conversion failed; "
+                           "using more primitive row-by-row conversion.");
+            
+            convertRasterByRows(
+                imageoutFile, alphaFile, cols, rows, maxval, format, 
+                tifP, photomet, planarconfig, bps, spp, fillorder, colormap);
+        }
+    }
+}
+
+
+
+static void
+convertIt(TIFF *             const tifP,
+          FILE *             const alphaFile, 
+          FILE *             const imageoutFile,
+          struct cmdlineInfo const cmdline) {
+
+    unsigned int imageSeq;
+    bool eof;
+
+    imageSeq = 0;
+    eof = FALSE;
+
+    while (!eof) {
+        bool success;
+
+        if (cmdline.verbose)
+            pm_message("Converting Image %u", imageSeq);
+        convertImage(tifP, alphaFile, imageoutFile, cmdline);
+        success = TIFFReadDirectory(tifP);
+        eof = !success;
+        ++imageSeq;
+    }
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    TIFF * tif;
+    FILE * alphaFile;
+    FILE * imageoutFile;
+
+    pnm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    if (!STREQ(cmdline.inputFilename, "-")) {
+        tif = TIFFOpen(cmdline.inputFilename, "r");
+        if (tif == NULL)
+            pm_error("error opening TIFF file %s", cmdline.inputFilename);
+    } else {
+        tif = TIFFFdOpen(0, "Standard Input", "r");
+        if (tif == NULL)
+            pm_error("error opening standard input as TIFF file");
+    }
+
+    if (cmdline.alphaStdout)
+        alphaFile = stdout;
+    else if (cmdline.alphaFilename == NULL) 
+        alphaFile = NULL;
+    else
+        alphaFile = pm_openw(cmdline.alphaFilename);
+
+    if (cmdline.alphaStdout) 
+        imageoutFile = NULL;
+    else
+        imageoutFile = stdout;
+
+    convertIt(tif, alphaFile, imageoutFile, cmdline);
+
+    if (imageoutFile != NULL) 
+        pm_close( imageoutFile );
+    if (alphaFile != NULL)
+        pm_close( alphaFile );
+
+    strfree(cmdline.inputFilename);
+
+    /* If the program failed, it previously aborted with nonzero completion
+       code, via various function calls.
+    */
+    return 0;
+}
diff --git a/converter/other/x10wd.h b/converter/other/x10wd.h
new file mode 100644
index 00000000..3c4fb8ca
--- /dev/null
+++ b/converter/other/x10wd.h
@@ -0,0 +1,32 @@
+/* x10wd.h - the following defs are taken from various X10 header files
+*/
+
+#ifndef X10WD_H_INCLUDED
+#define X10WD_H_INCLUDED
+
+#define XYFormat 0
+#define ZFormat 1
+
+#define X10WD_FILE_VERSION 6
+typedef struct {
+    int header_size;		/* Size of the entire file header (bytes). */
+    int file_version;		/* X10WD_FILE_VERSION */
+    int display_type;		/* Display type. */
+    int display_planes;		/* Number of display planes. */
+    int pixmap_format;		/* Pixmap format. */
+    int pixmap_width;		/* Pixmap width. */
+    int pixmap_height;		/* Pixmap height. */
+    short window_width;		/* Window width. */
+    short window_height;	/* Window height. */
+    short window_x;		/* Window upper left X coordinate. */
+    short window_y;		/* Window upper left Y coordinate. */
+    short window_bdrwidth;	/* Window border width. */
+    short window_ncolors;	/* number of Color entries in this window */
+    } X10WDFileHeader;
+
+typedef struct {
+    int pixel;
+    unsigned short red, green, blue;
+    } X10Color;
+
+#endif
diff --git a/converter/other/x11wd.h b/converter/other/x11wd.h
new file mode 100644
index 00000000..711248f5
--- /dev/null
+++ b/converter/other/x11wd.h
@@ -0,0 +1,82 @@
+/* x11wd.h - the following defs are taken from various X.V11R2 header files
+*/
+
+#ifndef X11WD_H_INCLUDED
+#define X11WD_H_INCLUDED
+
+enum byteorder {LSBFirst=0, MSBFirst=1};
+/* They used to be defined wrongly as macros:  2000.05.08 -Bryan
+
+#define LSBFirst    0
+#define MSBFirst    1
+*/
+
+#define XYBitmap    0
+#define XYPixmap    1
+#define ZPixmap     2
+
+enum visualclass {StaticGray=0,GrayScale=1,StaticColor=2,PseudoColor=3,
+                  TrueColor=4, DirectColor=5};
+
+/* They used to be defined wrongly as macros:  2000.05.08 -Bryan
+#define StaticGray  0
+#define GrayScale   1
+#define StaticColor 2
+#define PseudoColor 3
+#define TrueColor   4
+#define DirectColor 5
+*/
+
+typedef uint32n xwdval;
+#define XWDVAL_MAX ((xwdval)(-1))
+#define X11WD_FILE_VERSION 7
+typedef struct {
+    xwdval header_size;     /* Size of the entire file header (bytes). */
+    xwdval file_version;    /* X11WD_FILE_VERSION */
+    xwdval pixmap_format;   /* Pixmap format */
+    xwdval pixmap_depth;    /* Pixmap depth */
+    xwdval pixmap_width;    /* Pixmap width */
+    xwdval pixmap_height;   /* Pixmap height */
+    xwdval xoffset;     /* Bitmap x offset */
+    xwdval byte_order;      /* MSBFirst, LSBFirst */
+    xwdval bitmap_unit;     /* Bitmap unit */
+    xwdval bitmap_bit_order;    /* MSBFirst, LSBFirst */
+    xwdval bitmap_pad;      /* Bitmap scanline pad */
+    xwdval bits_per_pixel;  /* Bits per pixel */
+    xwdval bytes_per_line;  /* Bytes per scanline */
+    xwdval visual_class;    /* Class of colormap */
+    xwdval red_mask;        /* Z red mask */
+    xwdval green_mask;      /* Z green mask */
+    xwdval blue_mask;       /* Z blue mask */
+    xwdval bits_per_rgb;    /* Log base 2 of distinct color values */
+    xwdval colormap_entries;
+        /* I have no idea what this is.  An old comment says "number of
+           entries in colormap," but readers seem to use 'ncolors' for that
+           instead.  That's how Pnmtoxwd sets ncolors, and is how Xwdtopnm
+           interprets it.  Xwdtopnm doesn't even look at 'colormap_entries'.
+
+           This could be an old mistake; maybe colormap_entries was 
+           originally the number of entries in the colormap, and ncolors
+           was the number of distinct colors in the image (which might be
+           less than colormap_entries or, for direct color, could be much
+           larger).
+        */
+    xwdval ncolors;
+        /* Number of entries in the color map (for direct color, it's the
+           number of entries in each of them).  See 'colormap_entries'. 
+        */
+    xwdval window_width;    /* Window width */
+    xwdval window_height;   /* Window height */
+    int32n window_x;        /* Window upper left X coordinate */
+    int32n window_y;        /* Window upper left Y coordinate */
+    xwdval window_bdrwidth; /* Window border width */
+    } X11WDFileHeader;
+
+typedef struct {
+    uint32n num;
+    unsigned short red, green, blue;
+    char flags;         /* do_red, do_green, do_blue */
+    char pad;
+    } X11XColor;
+
+#endif
diff --git a/converter/other/xwdtopnm.c b/converter/other/xwdtopnm.c
new file mode 100644
index 00000000..28c38cfc
--- /dev/null
+++ b/converter/other/xwdtopnm.c
@@ -0,0 +1,1280 @@
+/* xwdtopnm.c - read an X11 or X10 window dump file and write a PNM image.
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* IMPLEMENTATION NOTES:
+
+   The file X11/XWDFile.h from the X Window System is an authority for the
+   format of an XWD file.  Netpbm uses its own declaration, though.
+*/
+
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "pnm.h"
+#include "x10wd.h"
+#include "x11wd.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    char *input_filename;
+    unsigned int verbose;
+    unsigned int debug;
+    unsigned int headerdump;
+};
+
+
+static bool debug;
+static bool verbose;
+
+#ifdef DEBUG_PIXEL
+static unsigned int pixel_count = 0;
+#endif
+
+/* Byte-swapping junk. */
+
+static int
+zero_bits(const unsigned long mask) {
+/*----------------------------------------------------------------------------
+   Return the number of consective zero bits at the least significant end
+   of the binary representation of 'mask'.  E.g. if mask == 0x00fff800,
+   we would return 11.
+-----------------------------------------------------------------------------*/
+    int i;
+    unsigned long shifted_mask;
+
+    for (i=0, shifted_mask = mask; 
+         i < sizeof(mask)*8 && (shifted_mask & 0x00000001) == 0;
+         i++, shifted_mask >>= 1 );
+    return(i);
+}
+
+
+
+static int
+one_bits(const unsigned long input) {
+/*----------------------------------------------------------------------------
+   Return the number of one bits in the binary representation of 'input'.
+-----------------------------------------------------------------------------*/
+    int one_bits;
+    unsigned long mask;
+
+    one_bits = 0;   /* initial value */
+    for (mask = 0x00000001; mask != 0x00000000; mask <<= 1)
+        if (input & mask) one_bits++;
+
+    return(one_bits);
+}
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that many of the strings that this function returns in the
+   *cmdline_p structure are actually in the supplied argv array.  And
+   sometimes, one of these strings is actually just a suffix of an entry
+   in argv!
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+
+    OPTENT3(0,   "verbose",    OPT_FLAG,   NULL, &cmdlineP->verbose,       0);
+    OPTENT3(0,   "debug",      OPT_FLAG,   NULL, &cmdlineP->debug,         0);
+    OPTENT3(0,   "headerdump", OPT_FLAG,   NULL, &cmdlineP->headerdump,    0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc - 1 == 0)
+        cmdlineP->input_filename = NULL;  /* he wants stdin */
+    else if (argc - 1 == 1) {
+        if (STREQ(argv[1], "-"))
+            cmdlineP->input_filename = NULL;  /* he wants stdin */
+        else 
+            cmdlineP->input_filename = strdup(argv[1]);
+    } else 
+        pm_error("Too many arguments.  The only argument accepted\n"
+                 "is the input file specification");
+}
+
+
+
+static void
+processX10Header(X10WDFileHeader *  const h10P, 
+                 FILE *             const file,
+                 int *              const colsP, 
+                 int *              const rowsP, 
+                 int *              const padrightP, 
+                 xelval *           const maxvalP, 
+                 enum visualclass * const visualclassP, 
+                 int *              const formatP, 
+                 xel **             const colorsP, 
+                 int *              const bits_per_pixelP, 
+                 int *              const bits_per_itemP, 
+                 unsigned long *    const red_maskP, 
+                 unsigned long *    const green_maskP, 
+                 unsigned long *    const blue_maskP,
+                 enum byteorder *   const byte_orderP,
+                 enum byteorder *   const bit_orderP) {
+
+    int i;
+    X10Color* x10colors;
+    bool grayscale;
+    bool byte_swap;
+
+    *maxvalP = 65535;   /* Initial assumption */
+
+    if ( h10P->file_version != X10WD_FILE_VERSION ) {
+        byte_swap = TRUE;
+        h10P->header_size     = pm_bs_long(h10P->header_size);
+        h10P->file_version    = pm_bs_long(h10P->file_version);
+        h10P->display_type    = pm_bs_long(h10P->display_type);
+        h10P->display_planes  = pm_bs_long(h10P->display_planes);
+        h10P->pixmap_format   = pm_bs_long(h10P->pixmap_format);
+        h10P->pixmap_width    = pm_bs_long(h10P->pixmap_width);
+        h10P->pixmap_height   = pm_bs_long(h10P->pixmap_height);
+        h10P->window_width    = pm_bs_short(h10P->window_width);
+        h10P->window_height   = pm_bs_short(h10P->window_height);
+        h10P->window_x        = pm_bs_short(h10P->window_x);
+        h10P->window_y        = pm_bs_short(h10P->window_y);
+        h10P->window_bdrwidth = pm_bs_short(h10P->window_bdrwidth);
+        h10P->window_ncolors  = pm_bs_short(h10P->window_ncolors);
+    } else
+        byte_swap = FALSE;
+
+    for ( i = 0; i < h10P->header_size - sizeof(*h10P); ++i )
+        if ( getc( file ) == EOF )
+            pm_error( "couldn't read rest of X10 XWD file header" );
+
+    /* Check whether we can handle this dump. */
+    if ( h10P->window_ncolors > 256 )
+        pm_error( "can't handle X10 window_ncolors > %d", 256 );
+    if ( h10P->pixmap_format != ZFormat && h10P->display_planes != 1 )
+        pm_error(
+            "can't handle X10 pixmap_format %d with planes != 1",
+            h10P->pixmap_format );
+
+    grayscale = TRUE;  /* initial assumption */
+    if ( h10P->window_ncolors != 0 ) {
+        /* Read X10 colormap. */
+        MALLOCARRAY( x10colors, h10P->window_ncolors );
+        if ( x10colors == NULL )
+            pm_error( "out of memory" );
+        for ( i = 0; i < h10P->window_ncolors; ++i ) {
+            if ( fread( &x10colors[i], sizeof(X10Color), 1, file ) != 1 )
+                pm_error( "couldn't read X10 XWD colormap" );
+            if ( byte_swap ) {
+                x10colors[i].red   = pm_bs_short(x10colors[i].red);
+                x10colors[i].green = pm_bs_short(x10colors[i].green);
+                x10colors[i].blue  = pm_bs_short(x10colors[i].blue);
+            }
+            if ( x10colors[i].red != x10colors[i].green ||
+                 x10colors[i].green != x10colors[i].blue )
+                grayscale = FALSE;
+        }
+    }
+
+    if ( h10P->display_planes == 1 ) {
+        *formatP = PBM_TYPE;
+        *visualclassP = StaticGray;
+        *maxvalP = 1;
+        *colorsP = pnm_allocrow( 2 );
+        PNM_ASSIGN1( (*colorsP)[0], 0 );
+        PNM_ASSIGN1( (*colorsP)[1], *maxvalP );
+        *padrightP =
+            ( ( h10P->pixmap_width + 15 ) / 16 ) * 16 - h10P->pixmap_width;
+        *bits_per_itemP = 16;
+        *bits_per_pixelP = 1;
+    } else if ( h10P->window_ncolors == 0 ) { 
+        /* Must be grayscale. */
+        *formatP = PGM_TYPE;
+        *visualclassP = StaticGray;
+        *maxvalP = ( 1 << h10P->display_planes ) - 1;
+        *colorsP = pnm_allocrow( *maxvalP + 1 );
+        for ( i = 0; i <= *maxvalP; ++i )
+            PNM_ASSIGN1( (*colorsP)[i], i );
+        *padrightP =
+            ( ( h10P->pixmap_width + 15 ) / 16 ) * 16 - h10P->pixmap_width;
+        *bits_per_itemP = 16;
+        *bits_per_pixelP = 1;
+    } else {
+        *colorsP = pnm_allocrow( h10P->window_ncolors );
+        *visualclassP = PseudoColor;
+        if ( grayscale ) {
+            *formatP = PGM_TYPE;
+            for ( i = 0; i < h10P->window_ncolors; ++i )
+                PNM_ASSIGN1( (*colorsP)[i], x10colors[i].red );
+        } else {
+            *formatP = PPM_TYPE;
+            for ( i = 0; i < h10P->window_ncolors; ++i )
+                PPM_ASSIGN(
+                    (*colorsP)[i], x10colors[i].red, x10colors[i].green,
+                    x10colors[i].blue);
+        }
+
+        *padrightP = h10P->pixmap_width & 1;
+        *bits_per_itemP = 8;
+        *bits_per_pixelP = 8;
+    }
+    *colsP = h10P->pixmap_width;
+    *rowsP = h10P->pixmap_height;
+    *byte_orderP = MSBFirst;
+    *bit_orderP = LSBFirst;
+}
+
+
+
+static void
+fixH11ByteOrder(X11WDFileHeader *  const h11P,
+                X11WDFileHeader ** const h11FixedPP) {
+
+    X11WDFileHeader * h11FixedP;
+
+    MALLOCVAR_NOFAIL(h11FixedP);
+
+    if (h11P->file_version == X11WD_FILE_VERSION) {
+        memcpy(h11FixedP, h11P, sizeof(*h11FixedP));
+    } else {
+        h11FixedP->header_size      = pm_bs_long(h11P->header_size);
+        h11FixedP->file_version     = pm_bs_long(h11P->file_version);
+        h11FixedP->pixmap_format    = pm_bs_long(h11P->pixmap_format);
+        h11FixedP->pixmap_depth     = pm_bs_long(h11P->pixmap_depth);
+        h11FixedP->pixmap_width     = pm_bs_long(h11P->pixmap_width);
+        h11FixedP->pixmap_height    = pm_bs_long(h11P->pixmap_height);
+        h11FixedP->xoffset          = pm_bs_long(h11P->xoffset);
+        h11FixedP->byte_order       = pm_bs_long(h11P->byte_order);
+        h11FixedP->bitmap_unit      = pm_bs_long(h11P->bitmap_unit);
+        h11FixedP->bitmap_bit_order = pm_bs_long(h11P->bitmap_bit_order);
+        h11FixedP->bitmap_pad       = pm_bs_long(h11P->bitmap_pad);
+        h11FixedP->bits_per_pixel   = pm_bs_long(h11P->bits_per_pixel);
+        h11FixedP->bytes_per_line   = pm_bs_long(h11P->bytes_per_line);
+        h11FixedP->visual_class     = pm_bs_long(h11P->visual_class);
+        h11FixedP->red_mask         = pm_bs_long(h11P->red_mask);
+        h11FixedP->green_mask       = pm_bs_long(h11P->green_mask);
+        h11FixedP->blue_mask        = pm_bs_long(h11P->blue_mask);
+        h11FixedP->bits_per_rgb     = pm_bs_long(h11P->bits_per_rgb);
+        h11FixedP->colormap_entries = pm_bs_long(h11P->colormap_entries);
+        h11FixedP->ncolors          = pm_bs_long(h11P->ncolors);
+        h11FixedP->window_width     = pm_bs_long(h11P->window_width);
+        h11FixedP->window_height    = pm_bs_long(h11P->window_height);
+        h11FixedP->window_x         = pm_bs_long(h11P->window_x);
+        h11FixedP->window_y         = pm_bs_long(h11P->window_y);
+        h11FixedP->window_bdrwidth  = pm_bs_long(h11P->window_bdrwidth);
+    }
+    *h11FixedPP = h11FixedP;
+}
+
+
+
+static void
+readX11Colormap(FILE *      const file,
+                int         const ncolors, 
+                bool        const byteSwap,
+                X11XColor** const x11colorsP) {
+                
+    X11XColor * x11colors;
+    int rc;
+
+    /* Read X11 colormap. */
+    MALLOCARRAY(x11colors, ncolors);
+    if (x11colors == NULL)
+        pm_error("out of memory");
+    rc = fread(x11colors, sizeof(x11colors[0]), ncolors, file);
+    if (rc != ncolors)
+        pm_error("couldn't read X11 XWD colormap");
+    if (byteSwap) {
+        unsigned int i;
+        for (i = 0; i < ncolors; ++i) {
+            x11colors[i].red   = pm_bs_short(x11colors[i].red);
+            x11colors[i].green = pm_bs_short(x11colors[i].green);
+            x11colors[i].blue  = pm_bs_short(x11colors[i].blue);
+        }
+    }
+    if (debug) {
+        unsigned int i;
+        for (i = 0; i < ncolors && i < 8; ++i)
+            pm_message("Color %d r/g/b = %d/%d/%d", i, 
+                       x11colors[i].red, x11colors[i].green, 
+                       x11colors[i].blue);
+    }
+    *x11colorsP = x11colors;
+}
+
+
+
+static bool
+colormapAllGray(const X11XColor* const x11colors,
+                unsigned int     const ncolors) {
+
+    unsigned int i;
+    bool grayscale;
+
+    grayscale = TRUE;  /* initial assumption */
+    for (i = 0; i < ncolors; ++i) 
+        if (x11colors[i].red != x11colors[i].green ||
+            x11colors[i].green != x11colors[i].blue )
+            grayscale = FALSE;
+
+    return grayscale;
+}
+
+
+
+static void
+dumpX11Header(X11WDFileHeader * const h11P) {
+
+    const char * formatDesc;
+    const char * byteOrderDesc;
+    const char * bitOrderDesc;
+    const char * visualClassDesc;
+
+    X11WDFileHeader * h11FixedP;
+
+    fixH11ByteOrder(h11P, &h11FixedP);
+ 
+    switch(h11FixedP->pixmap_format) {
+    case XYBitmap: formatDesc = "XY bit map"; break;
+    case XYPixmap: formatDesc = "XY pix map"; break;
+    case ZPixmap:  formatDesc = "Z pix map";  break;
+    default:       formatDesc = "???";
+    }
+
+    switch(h11FixedP->byte_order) {
+    case LSBFirst: byteOrderDesc = "LSB first"; break;
+    case MSBFirst: byteOrderDesc = "MSB first"; break;
+    default:       byteOrderDesc = "???";
+    }
+
+    switch (h11FixedP->bitmap_bit_order) {
+    case LSBFirst: bitOrderDesc = "LSB first"; break;
+    case MSBFirst: bitOrderDesc = "MSB first"; break;
+    default:       bitOrderDesc = "???";
+    }
+
+    switch (h11FixedP->visual_class) {
+    case StaticGray:  visualClassDesc = "StaticGray";  break;
+    case GrayScale:   visualClassDesc = "GrayScale";   break;
+    case StaticColor: visualClassDesc = "StaticColor"; break;
+    case PseudoColor: visualClassDesc = "PseudoColor"; break;
+    case TrueColor:   visualClassDesc = "TrueColor"; break;
+    case DirectColor: visualClassDesc = "DirectColor"; break;
+    default:          visualClassDesc = "???";
+    }
+
+    pm_message("File version: %u", h11FixedP->file_version);
+    pm_message("Format: %s (%u)", formatDesc, h11FixedP->pixmap_format);
+    pm_message("Width:  %u", h11FixedP->pixmap_width);
+    pm_message("Height: %u", h11FixedP->pixmap_height);
+    pm_message("Depth: %u bits", h11FixedP->pixmap_depth);
+    pm_message("X offset: %u", h11FixedP->xoffset);
+    pm_message("byte order: %s (%u)", byteOrderDesc, h11FixedP->byte_order);
+    pm_message("bitmap unit: %u", h11FixedP->bitmap_unit);
+    pm_message("bit order: %s (%u)", 
+               bitOrderDesc, h11FixedP->bitmap_bit_order);
+    pm_message("bitmap pad: %u", h11FixedP->bitmap_pad);
+    pm_message("bits per pixel: %u", h11FixedP->bits_per_pixel);
+    pm_message("bytes per line: %u", h11FixedP->bytes_per_line);
+    pm_message("visual class: %s (%u)", 
+               visualClassDesc, h11FixedP->visual_class);
+    pm_message("red mask:   %08x", h11FixedP->red_mask);
+    pm_message("green mask: %08x", h11FixedP->green_mask);
+    pm_message("blue mask:  %08x", h11FixedP->blue_mask);
+    pm_message("bits per rgb: %u", h11FixedP->bits_per_rgb);
+    pm_message("number of colormap entries: %u", h11FixedP->colormap_entries);
+    pm_message("number of colors in colormap: %u", h11FixedP->ncolors);
+    pm_message("window width:  %u", h11FixedP->window_width);
+    pm_message("window height: %u", h11FixedP->window_height);
+    pm_message("window upper left X coordinate: %u", h11FixedP->window_x);
+    pm_message("window upper left Y coordinate: %u", h11FixedP->window_y);
+    pm_message("window border width: %u", h11FixedP->window_bdrwidth);
+
+    free(h11FixedP);
+}
+
+
+
+static void
+processX11Header(X11WDFileHeader *  const h11P, 
+                 FILE *             const file,
+                 int *              const colsP, 
+                 int *              const rowsP, 
+                 int *              const padrightP, 
+                 xelval *           const maxvalP, 
+                 enum visualclass * const visualclassP, 
+                 int *              const formatP, 
+                 xel **             const colorsP, 
+                 int *              const bits_per_pixelP, 
+                 int *              const bits_per_itemP, 
+                 unsigned long *    const red_maskP, 
+                 unsigned long *    const green_maskP, 
+                 unsigned long *    const blue_maskP,
+                 enum byteorder *   const byte_orderP,
+                 enum byteorder *   const bit_orderP) {
+
+    int i;
+    X11XColor* x11colors;
+    bool grayscale;
+    bool const byte_swap = (h11P->file_version != X11WD_FILE_VERSION);
+    X11WDFileHeader * h11FixedP;
+ 
+    fixH11ByteOrder(h11P, &h11FixedP);
+
+    if (byte_swap && verbose)
+        pm_message("Header is different endianness from this machine.");
+    
+    for (i = 0; i < h11FixedP->header_size - sizeof(*h11FixedP); ++i)
+        if (getc(file) == EOF)
+            pm_error("couldn't read rest of X11 XWD file header");
+
+    /* Check whether we can handle this dump. */
+    if ( h11FixedP->pixmap_depth > 24 )
+        pm_error( "can't handle X11 pixmap_depth > 24" );
+    if ( h11FixedP->bits_per_rgb > 24 )
+        pm_error( "can't handle X11 bits_per_rgb > 24" );
+    if ( h11FixedP->pixmap_format != ZPixmap && h11FixedP->pixmap_depth != 1 )
+        pm_error(
+            "can't handle X11 pixmap_format %d with depth != 1",
+            h11FixedP->pixmap_format );
+    if ( h11FixedP->bitmap_unit != 8 && h11FixedP->bitmap_unit != 16 &&
+         h11FixedP->bitmap_unit != 32 )
+        pm_error(
+            "X11 bitmap_unit (%d) is non-standard - can't handle",
+            h11FixedP->bitmap_unit );
+    /* The following check was added in 10.19 (November 2003) */
+    if ( h11FixedP->bitmap_pad != 8 && h11FixedP->bitmap_pad != 16 &&
+         h11FixedP->bitmap_pad != 32 )
+        pm_error(
+            "X11 bitmap_pad (%d) is non-standard - can't handle",
+            h11FixedP->bitmap_unit );
+
+    if ( h11FixedP->ncolors > 0 ) {
+        readX11Colormap( file, h11FixedP->ncolors, byte_swap, &x11colors );
+        grayscale = colormapAllGray( x11colors, h11FixedP->ncolors );
+    } else
+        grayscale = TRUE;
+
+    *visualclassP = (enum visualclass) h11FixedP->visual_class;
+    if ( *visualclassP == DirectColor ) {
+        unsigned int i;
+        *formatP = PPM_TYPE;
+        *maxvalP = 65535;
+        /*
+          DirectColor is like PseudoColor except that there are essentially
+          3 colormaps (shade maps) -- one for each color component.  Each pixel
+          is composed of 3 separate indices.
+        */
+
+        *colorsP = pnm_allocrow( h11FixedP->ncolors );
+        for ( i = 0; i < h11FixedP->ncolors; ++i )
+            PPM_ASSIGN(
+                (*colorsP)[i], x11colors[i].red, x11colors[i].green,
+                x11colors[i].blue);
+    } else if ( *visualclassP == TrueColor ) {
+        *formatP = PPM_TYPE;
+
+        *maxvalP = pm_lcm(pm_bitstomaxval(one_bits(h11FixedP->red_mask)),
+                          pm_bitstomaxval(one_bits(h11FixedP->green_mask)),
+                          pm_bitstomaxval(one_bits(h11FixedP->blue_mask)),
+                          PPM_OVERALLMAXVAL
+            );
+    }
+    else if ( *visualclassP == StaticGray && h11FixedP->bits_per_pixel == 1 ) {
+        *formatP = PBM_TYPE;
+        *maxvalP = 1;
+        *colorsP = pnm_allocrow( 2 );
+        PNM_ASSIGN1( (*colorsP)[0], *maxvalP );
+        PNM_ASSIGN1( (*colorsP)[1], 0 );
+    } else if ( *visualclassP == StaticGray ) {
+        unsigned int i;
+        *formatP = PGM_TYPE;
+        *maxvalP = ( 1 << h11FixedP->bits_per_pixel ) - 1;
+        *colorsP = pnm_allocrow( *maxvalP + 1 );
+        for ( i = 0; i <= *maxvalP; ++i )
+            PNM_ASSIGN1( (*colorsP)[i], i );
+    } else {
+        *colorsP = pnm_allocrow( h11FixedP->ncolors );
+        if ( grayscale ) {
+            unsigned int i;
+            *formatP = PGM_TYPE;
+            for ( i = 0; i < h11FixedP->ncolors; ++i )
+                PNM_ASSIGN1( (*colorsP)[i], x11colors[i].red );
+        } else {
+            unsigned int i;
+            *formatP = PPM_TYPE;
+            for ( i = 0; i < h11FixedP->ncolors; ++i )
+                PPM_ASSIGN(
+                    (*colorsP)[i], x11colors[i].red, x11colors[i].green,
+                    x11colors[i].blue);
+        }
+        *maxvalP = 65535;
+    }
+
+    *colsP = h11FixedP->pixmap_width;
+    *rowsP = h11FixedP->pixmap_height;
+    *padrightP =
+        h11FixedP->bytes_per_line * 8 / h11FixedP->bits_per_pixel -
+        h11FixedP->pixmap_width;
+    /* According to X11/XWDFile.h, the item size is 'bitmap_pad' for some
+       images and 'bitmap_unit' for others.  This is strange, so there may
+       be some subtlety of their definitions that we're missing.
+
+       See comments in getpix() about what an item is.
+
+       Ben Kelley in January 2002 had a 32 bits-per-pixel xwd file
+       from a truecolor 32 bit window on a Hummingbird Exceed X server
+       on Win32, and it had bitmap_unit = 8 and bitmap_pad = 32.
+
+       But Björn Eriksson in October 2003 had an xwd file from a 24
+       bit-per-pixel direct color window that had bitmap_unit = 32 and
+       bitmap_pad = 8.  This was made by Xwd in Red Hat Xfree86 4.3.0-2.
+
+       Before Netpbm 9.23 (January 2002), we used bitmap_unit as the
+       item size always.  Then, until 10.19 (November 2003), we used
+       bitmap_pad when pixmap_depth > 1 and pixmap_format == ZPixmap.
+       We still don't see any logic in these fields at all, but we
+       figure whichever one is greater (assuming both are meaningful)
+       has to be the item size.  
+    */
+    *bits_per_itemP = MAX(h11FixedP->bitmap_pad, h11FixedP->bitmap_unit);
+
+    *bits_per_pixelP = h11FixedP->bits_per_pixel;
+
+    *byte_orderP = (enum byteorder) h11FixedP->byte_order;
+    *bit_orderP = (enum byteorder) h11FixedP->bitmap_bit_order;
+    *red_maskP = h11FixedP->red_mask;
+    *green_maskP = h11FixedP->green_mask;
+    *blue_maskP = h11FixedP->blue_mask;
+
+    free(h11FixedP);
+} 
+
+
+
+static void
+getinit(FILE *             const ifP, 
+        int *              const colsP, 
+        int *              const rowsP, 
+        int *              const padrightP, 
+        xelval *           const maxvalP, 
+        enum visualclass * const visualclassP, 
+        int *              const formatP, 
+        xel **             const colorsP,
+        int *              const bits_per_pixelP, 
+        int *              const bits_per_itemP, 
+        unsigned long *    const red_maskP, 
+        unsigned long *    const green_maskP,
+        unsigned long *    const blue_maskP,
+        enum byteorder *   const byte_orderP,
+        enum byteorder *   const bit_orderP,
+        bool               const headerDump) {
+/*----------------------------------------------------------------------------
+   Read the header from the XWD image in input stream 'ifP'.  Leave
+   the stream positioned to the beginning of the raster.
+
+   Return various fields from the header.
+
+   Return as *padrightP the number of additional pixels of padding are
+   at the end of each line of input.  This says the input stream
+   contains *colsP pixels of image data plus *padrightP pixels of
+   padding.
+-----------------------------------------------------------------------------*/
+    /* Assume X11 headers are larger than X10 ones. */
+    unsigned char header[sizeof(X11WDFileHeader)];
+    X10WDFileHeader* h10P;
+    X11WDFileHeader* h11P;
+    int rc;
+
+    h10P = (X10WDFileHeader*) header;
+    h11P = (X11WDFileHeader*) header;
+    if ( sizeof(*h10P) > sizeof(*h11P) )
+        pm_error("ARGH!  On this machine, X10 headers are larger than "
+                 "X11 headers!\n    You will have to re-write xwdtopnm." );
+    
+    /* We read an X10 header's worth of data from the file, then look
+       at it to see if it looks like an X10 header.  If so we process
+       the X10 header.  If not, but it looks like the beginning of an
+       X11 header, we read more bytes so we have an X11 header's worth
+       of data, then process the X11 header.  Otherwise, we raise an
+       error.  
+    */
+
+    rc = fread(&header[0], sizeof(*h10P), 1, ifP);
+    if (rc != 1)
+        pm_error( "couldn't read XWD file header" );
+
+    if (h10P->file_version == X10WD_FILE_VERSION ||
+        pm_bs_long(h10P->file_version) == X10WD_FILE_VERSION) {
+        
+        if (verbose)
+            pm_message("Input is X10");
+        processX10Header(h10P, ifP, colsP, rowsP, padrightP, maxvalP, 
+                         visualclassP, formatP, 
+                         colorsP, bits_per_pixelP, bits_per_itemP, 
+                         red_maskP, green_maskP, blue_maskP, 
+                         byte_orderP, bit_orderP);
+    } else if (h11P->file_version == X11WD_FILE_VERSION ||
+               pm_bs_long(h11P->file_version) == X11WD_FILE_VERSION) {
+        
+        int rc;
+
+        if (verbose)
+            pm_message("Input is X11");
+
+        /* Read the balance of the X11 header */
+        rc = fread(&header[sizeof(*h10P)], 
+                   sizeof(*h11P) - sizeof(*h10P), 1, ifP);
+        if (rc != 1)
+            pm_error("couldn't read end of X11 XWD file header");
+
+        if (headerDump)
+            dumpX11Header(h11P);
+
+        processX11Header(h11P, ifP, colsP, rowsP, padrightP, maxvalP, 
+                         visualclassP, formatP, 
+                         colorsP, bits_per_pixelP, bits_per_itemP, 
+                         red_maskP, green_maskP, blue_maskP, 
+                         byte_orderP, bit_orderP);
+    } else
+        pm_error("unknown XWD file version: %u", h11P->file_version);
+}
+
+
+
+#ifdef DEBUG_PIXEL
+#define DEBUG_PIXEL_1 \
+   if (pixel_count < 4) \
+       pm_message("getting pixel %d", pixel_count);
+
+#define DEBUG_PIXEL_2 \
+    if (pixel_count < 4) \
+        pm_message("item: %.8lx", row_controlP->item.l); 
+
+
+#define DEBUG_PIXEL_3 \
+    if (pixel_count < 4) \
+        pm_message("  bits_taken: %lx(%d), carryover_bits: %lx(%d), " \
+                     "pixel: %lx", \
+                     bits_taken, bits_to_take, row_controlP->carryover_bits, \
+                     row_controlP->bits_carried_over, pixel);
+
+#define DEBUG_PIXEL_4 \
+    if (pixel_count < 4) { \
+        pm_message("  row_control.bits_carried_over = %d" \
+                   "  carryover_bits= %.8lx", \
+                   row_controlP->bits_carried_over, \
+                   row_controlP->carryover_bits); \
+        pm_message("  row_control.bits_used = %d", \
+                   row_controlP->bits_used); \
+        pm_message("  row_control.bits_left = %d", \
+                   row_controlP->bits_left); \
+                   } \
+    \
+    pixel_count++;
+#else
+#define DEBUG_PIXEL_1 do {} while(0)
+#define DEBUG_PIXEL_2 do {} while(0)
+#define DEBUG_PIXEL_3 do {} while(0)
+#define DEBUG_PIXEL_4 do {} while(0)
+#endif
+
+
+
+/*----------------------------------------------------------------------------
+   The pixel reader.
+
+   The pixel reader is an object that reads an XWD raster and gives you
+   one pixel at a time from it.
+
+   It consists of a structure of type 'pixelReader' and the
+   getpix() and pixelReaderInit() subroutines.
+-----------------------------------------------------------------------------*/
+
+typedef struct {
+    /* This structure contains the state of the getpix() reader as it
+       reads across a row in the input image.
+       */
+    FILE * fileP;
+    unsigned long itemBuffer;
+        /* The item buffer.  This contains what's left of the item
+           most recently read from the image file -- an item goes from the
+           XWD raster into here and then bits disappear from it as they
+           become part of pixels returned by the object.
+
+           'nBitsLeft' tells how many bits are in the buffer now.  It's
+           zero when nothing has ever been read from the file.  Only
+           the least signficant 'nBitsLeft' bits are meaningful.
+
+           The numeric value of the member is the number whose pure
+           binary representation is the bit string in the buffer.
+
+           That bit string starts out as the contents of one item of
+           the XWD raster, with the "byte order" value from the XWD
+           header applied.  E.g. Assume bits per item is 16 and the
+           and the "byte order" value is "MSB First".  Assume the
+           raster contains 0x01 at Offset 109 and 0x02 at Offset 110.
+           The value of this member just after that item is read is
+           two hundred fifty-eight.  If we are running on a
+           little-endian machine, it appears in memory as 0x02 at
+           Address A and 0x01 at Address A+1.
+
+           Then we pull bits from either the beginning or end of the
+           buffer according to the "bit order" value from the XWD
+           header.  E.g. in the example above, assume "bit order" is
+           lsb first.  and bits per pixel is 4.  After reading the
+           first pixel (0010b) from the buffer, 'itemBuffer' is
+           sixteen (0x010) and 'nBitsLeft' is 12.
+        */
+    unsigned int nBitsLeft;
+      /* This is the number of bits in the current item that have not yet
+         been returned as part of a pixel.
+      */
+    int bitsPerPixel;
+    int bitsPerItem;
+    enum byteorder byteOrder;
+    enum byteorder bitOrder;
+} pixelReader;
+
+
+
+static void
+pixelReaderInit(pixelReader *  const pixelReaderP,
+                FILE *         const fileP,
+                int            const bitsPerPixel,
+                int            const bitsPerItem, 
+                enum byteorder const byteOrder,
+                enum byteorder const bitOrder) {
+    
+    pixelReaderP->fileP           = fileP;
+    pixelReaderP->bitsPerPixel    = bitsPerPixel;
+    pixelReaderP->bitsPerItem     = bitsPerItem;
+    pixelReaderP->byteOrder       = byteOrder;
+    pixelReaderP->bitOrder        = bitOrder;
+
+    pixelReaderP->nBitsLeft       = 0;
+}    
+
+
+
+static void
+readItem(pixelReader * const rdrP) {
+/*----------------------------------------------------------------------------
+   Read one item from the XWD raster associated with pixel reader *rdrP.
+
+   Put the item into the item buffer of the pixel reader object *rdrP.
+-----------------------------------------------------------------------------*/
+    assert(rdrP->nBitsLeft == 0);
+
+    switch (rdrP->bitsPerItem) {
+    case 8: {
+        unsigned char const item8 = getc(rdrP->fileP);
+        rdrP->itemBuffer = item8;
+        rdrP->nBitsLeft = 8;
+    }
+        break;
+
+    case 16: {
+        short item16;
+
+        switch (rdrP->byteOrder) {
+        case MSBFirst:
+            pm_readbigshort(rdrP->fileP, &item16);
+            break;
+        case LSBFirst:
+            pm_readlittleshort(rdrP->fileP, &item16);
+            break;
+        }
+        rdrP->itemBuffer = (unsigned short)item16;
+        rdrP->nBitsLeft = 16;
+    }
+        break;
+        
+    case 32: {
+        long item32;
+        
+        switch (rdrP->byteOrder) {
+        case MSBFirst:
+            pm_readbiglong(rdrP->fileP, &item32);
+            break;
+        case LSBFirst:
+            pm_readlittlelong(rdrP->fileP, &item32);
+            break;
+        }
+        rdrP->itemBuffer = item32;
+        rdrP->nBitsLeft = 32;
+    }
+        break;
+    default:
+        pm_error("INTERNAL ERROR: impossible bits_per_item");
+    }
+}
+
+
+
+static unsigned long const lsbmask[] = {
+/*----------------------------------------------------------------------------
+   lsbmask[i] is the mask you use to select the i least signficant bits
+   of a bit string.
+-----------------------------------------------------------------------------*/
+    0x00000000,
+    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
+};
+
+
+static unsigned long
+getpix(pixelReader * const rdrP) {
+/*----------------------------------------------------------------------------
+   Get a pixel from the input image.
+
+   A pixel is a bit string.  It may be either an rgb triplet or an index
+   into the colormap (or even an rgb triplet of indices into the colormaps!).
+   We don't care -- it's just a bit string.
+
+   We return an integer.  It's the integer that the pixel represents as
+   pure binary cipher, with the first bit the most significant bit.
+   
+   The basic unit of storage in the input file is an "item."  An item
+   can be 1, 2, or 4 bytes, and 'bits_per_item' tells us which.  Each
+   item can have its bytes stored in forward or reverse order, and
+   'byte_order' tells us which.
+
+   Each item can contain one or more pixels, and may contain
+   fractional pixels.  'bits_per_pixel' tells us how many bits each
+   pixel has, and 'bits_per_pixel' is always less than or equal to
+   'bits_per_item', but not necessarily a factor of it.  Within an item,
+   after taking care of the endianness of its storage format, the pixels
+   may be arranged from left to right or right to left.  'bit_order' tells
+   us which.
+
+   But it's not that simple.  Sometimes dummy pixels are added to the
+   right edge of the image in order to make an integral number of
+   items in each row of the raster.  getpix() doesn't know anything
+   about that, though -- it gets the dummy pixels the same as any
+   other pixel.  (This program is written as if it is always whole
+   pixels that get added for padding, but I wonder.  When pixels are 3
+   or 4 bytes and items are 4 bytes, sub-pixel padding would make
+   sense.  But then, maybe in formats with 3 or 4 bytes pixels,
+   there's never padding.  That seems to be the case so far...).  The
+   XWD header has a field that tells how many bytes there are per XWD
+   raster line, so that's the final word on how much padding there is.
+   
+   The most difficult part of getting the pixel is the ones that span
+   items.  We detect when an item has part, but not all, of the next
+   pixel (after the one we return) in it and store the fragment as
+   "carryover" bits for use in the next call to this function.
+
+   All this state information (carryover bits, etc.) is kept in 
+   *row_controlP.
+
+-----------------------------------------------------------------------------*/
+    unsigned long pixel;
+        /* Accumulator for the value we ultimately return.  We shift in
+           bits from the right end.  The number of bits presently in the
+           accumulator is rdrP->bitsPerPixel - bitsStillNeeded .
+        */
+    
+    unsigned int nBitsStillNeeded;
+        /* How many bits we still need to add to 'pixel',
+           as we build it up to the
+           full amount we have to return.  The bits are right justified in
+           it -- additional bits will shift in from the right.
+        */
+
+    assert(rdrP->bitsPerPixel <= 32);
+
+    pixel = 0;
+    nBitsStillNeeded = rdrP->bitsPerPixel;
+
+    while (nBitsStillNeeded > 0) {
+        if (rdrP->nBitsLeft == 0)
+            /* Buffer's empty.  Have to go back to the well for
+               rdrP->bitsPerItem more bits.
+            */
+            readItem(rdrP);
+
+        {
+            unsigned int const nBitsToTake =
+                MIN(rdrP->nBitsLeft, nBitsStillNeeded);
+            unsigned long const bitsToTakeMask = lsbmask[nBitsToTake];
+                /* E.g. if nbitsToTake is 4, this is 0x0000000F */
+
+            unsigned long bitsToTake;
+                /* The actual bits we take, in the 'nBitsToTake' low bits */
+
+            assert(nBitsToTake <= 32);
+            
+            if (rdrP->bitOrder == MSBFirst) {
+                unsigned int const nBitsToLeave =
+                    rdrP->nBitsLeft - nBitsToTake;
+                bitsToTake =
+                    (rdrP->itemBuffer >> nBitsToLeave) & bitsToTakeMask;
+            } else {
+                bitsToTake = rdrP->itemBuffer & bitsToTakeMask;
+                rdrP->itemBuffer >>= nBitsToTake;
+            }                
+            /* Shift the bits into the right end of the accumulator */
+            pixel <<= nBitsToTake;
+            pixel |= bitsToTake;
+
+            rdrP->nBitsLeft -= nBitsToTake;
+            nBitsStillNeeded -= nBitsToTake;
+        }
+    }
+    return pixel;
+}
+
+
+
+static void
+reportInfo(int              const cols, 
+           int              const rows, 
+           int              const padright, 
+           xelval           const maxval, 
+           enum visualclass const visualclass,
+           int              const format, 
+           int              const bits_per_pixel,
+           int              const bits_per_item, 
+           int              const red_mask, 
+           int              const green_mask, 
+           int              const blue_mask,
+           enum byteorder   const byte_order, 
+           enum byteorder   const bit_order) {
+    
+    const char *visualclass_name;
+    const char *byte_order_name;
+    const char *bit_order_name;
+    switch (visualclass) {
+    case StaticGray:  visualclass_name="StaticGray";  break;
+    case GrayScale:   visualclass_name="Grayscale";   break;
+    case StaticColor: visualclass_name="StaticColor"; break;
+    case PseudoColor: visualclass_name="PseudoColor"; break;
+    case TrueColor:   visualclass_name="TrueColor";   break;
+    case DirectColor: visualclass_name="DirectColor"; break;
+    default:          visualclass_name="(invalid)";    break;
+    }
+    switch (byte_order) {
+    case MSBFirst: byte_order_name = "MSBFirst";  break;
+    case LSBFirst: byte_order_name = "LSBFirst";  break;
+    default:       byte_order_name = "(invalid)"; break;
+    }
+    switch (bit_order) {
+    case MSBFirst: bit_order_name = "MSBFirst";  break;
+    case LSBFirst: bit_order_name = "LSBFirst";  break;
+    default:       bit_order_name = "(invalid)"; break;
+    }
+    pm_message("%d rows of %d columns with maxval %d",
+               rows, cols, maxval);
+    pm_message("padright=%d.  visualclass = %s.  format=%d (%c%c)",
+               padright, visualclass_name, 
+               format, format/256, format%256);
+    pm_message("bits_per_pixel=%d; bits_per_item=%d",
+               bits_per_pixel, bits_per_item);
+    pm_message("byte_order=%s; bit_order=%s",
+               byte_order_name, bit_order_name);
+    pm_message("red_mask=0x%.8x; green_mask=0x%.8x; blue_mask=0x%.8x",
+               red_mask, green_mask, blue_mask);
+}
+
+
+
+static void 
+convertRowSimpleIndex(pixelReader *  const pixelReaderP,
+                      int            const cols,
+                      const xel *    const colors,
+                      xel *          const xelrow) {
+    
+    unsigned int col;
+    for (col = 0; col < cols; ++col)
+        xelrow[col] = colors[getpix(pixelReaderP)];
+}
+
+
+
+static void
+convertRowDirect(pixelReader *  const pixelReaderP,
+                 int            const cols,
+                 const xel *    const colors,
+                 unsigned long  const red_mask,
+                 unsigned long  const grn_mask,
+                 unsigned long  const blu_mask,
+                 xel *          const xelrow) {
+        
+    unsigned int col;
+
+    for (col = 0; col < cols; ++col) {
+        unsigned long pixel;
+            /* This is a triplet of indices into the color map, packed
+               into this bit string according to red_mask, etc.
+            */
+        unsigned int red_index, grn_index, blu_index;
+            /* These are indices into the color map, unpacked from 'pixel'.
+             */
+            
+        pixel = getpix(pixelReaderP);
+
+        red_index = (pixel & red_mask) >> zero_bits(red_mask);
+        grn_index = (pixel & grn_mask) >> zero_bits(grn_mask); 
+        blu_index = (pixel & blu_mask) >> zero_bits(blu_mask);
+
+        PPM_ASSIGN(xelrow[col],
+                   PPM_GETR(colors[red_index]),
+                   PPM_GETG(colors[grn_index]),
+                   PPM_GETB(colors[blu_index])
+            );
+    }
+}
+
+
+
+static void
+convertRowTrueColor(pixelReader *  const pixelReaderP,
+                    int                  const cols,
+                    pixval               const maxval,
+                    const xel *          const colors,
+                    unsigned long        const red_mask,
+                    unsigned long        const grn_mask,
+                    unsigned long        const blu_mask,
+                    xel *                const xelrow) {
+
+    unsigned int col;
+    unsigned int red_shift, grn_shift, blu_shift;
+    unsigned int red_maxval, grn_maxval, blu_maxval;
+
+    red_shift = zero_bits(red_mask);
+    grn_shift = zero_bits(grn_mask);
+    blu_shift = zero_bits(blu_mask);
+
+    red_maxval = red_mask >> red_shift;
+    grn_maxval = grn_mask >> grn_shift;
+    blu_maxval = blu_mask >> blu_shift;
+
+    for (col = 0; col < cols; ++col) {
+        unsigned long pixel;
+
+        pixel = getpix(pixelReaderP);
+
+        /* The parsing of 'pixel' used to be done with hardcoded layout
+           parameters.  See comments at end of this file.
+        */
+        PPM_ASSIGN(xelrow[col],
+                   ((pixel & red_mask) >> red_shift) * maxval / red_maxval,
+                   ((pixel & grn_mask) >> grn_shift) * maxval / grn_maxval,
+                   ((pixel & blu_mask) >> blu_shift) * maxval / blu_maxval
+            );
+
+    }
+}
+
+
+
+static void
+convertRow(pixelReader *    const pixelReaderP,
+           FILE *           const ofP,
+           int              const padright, 
+           int              const cols, 
+           xelval           const maxval,
+           int              const format, 
+           unsigned long    const red_mask, 
+           unsigned long    const green_mask, 
+           unsigned long    const blue_mask, 
+           const xel*       const colors, 
+           enum visualclass const visualclass) {
+/*----------------------------------------------------------------------------
+   Read a row from the XWD pixel input stream 'pixelReaderP' and write
+   it to the PNM output stream 'ofP'.
+
+   The row is 'cols' pixels.
+
+   After reading the 'cols' pixels, we read and discard an additional
+   'padright' pixels from the input stream, so as to read the entire
+   input line.
+-----------------------------------------------------------------------------*/
+    xel* xelrow;
+    xelrow = pnm_allocrow(cols);
+
+    switch (visualclass) {
+    case StaticGray:
+    case GrayScale:
+    case StaticColor:
+    case PseudoColor:
+        convertRowSimpleIndex(pixelReaderP, cols, colors, xelrow);
+        break;
+    case DirectColor: 
+        convertRowDirect(pixelReaderP, cols, colors,
+                         red_mask, green_mask, blue_mask,
+                         xelrow);
+        
+        break;
+    case TrueColor: 
+        convertRowTrueColor(pixelReaderP, cols, maxval, colors,
+                            red_mask, green_mask, blue_mask,
+                            xelrow);
+        break;
+            
+    default:
+        pm_error("unknown visual class");
+    }
+    {
+        unsigned int col;
+        for (col = 0; col < padright; ++col)
+            getpix(pixelReaderP);
+    }
+    pnm_writepnmrow(ofP, xelrow, cols, maxval, format, 0);
+    pnm_freerow(xelrow);
+}
+
+
+
+static void
+reportOutputType(int const format) {
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PBM_TYPE:
+        pm_message("writing PBM file");
+    break;
+
+    case PGM_TYPE:
+        pm_message("writing PGM file");
+    break;
+    
+    case PPM_TYPE:
+        pm_message("writing PPM file");
+    break;
+    
+    default:
+        pm_error("shouldn't happen");
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    int rows, cols, format, padright;
+    unsigned int row;
+    int bits_per_pixel;
+    int bits_per_item;
+    unsigned long red_mask, green_mask, blue_mask;
+    xelval maxval;
+    enum visualclass visualclass;
+    enum byteorder byte_order, bit_order;
+    xel *colors;  /* the color map */
+    pixelReader pixelReader;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    debug = cmdline.debug;
+    verbose = cmdline.verbose;
+
+    if (cmdline.input_filename != NULL) 
+        ifP = pm_openr(cmdline.input_filename);
+    else
+        ifP = stdin;
+
+    getinit(ifP, &cols, &rows, &padright, &maxval, &visualclass, &format, 
+            &colors, &bits_per_pixel, &bits_per_item, 
+            &red_mask, &green_mask, &blue_mask, &byte_order, &bit_order,
+            cmdline.headerdump);
+    
+    if (verbose) 
+        reportInfo(cols, rows, padright, maxval, visualclass,
+                   format, bits_per_pixel, bits_per_item,
+                   red_mask, green_mask, blue_mask, 
+                   byte_order, bit_order);
+
+    pixelReaderInit(&pixelReader, ifP, bits_per_pixel, bits_per_item,
+                    byte_order, bit_order);
+
+    pnm_writepnminit(stdout, cols, rows, maxval, format, 0);
+
+    reportOutputType(format);
+
+    for (row = 0; row < rows; ++row) {
+        convertRow(&pixelReader, stdout,
+                   padright, cols, maxval, format,
+                   red_mask, green_mask, blue_mask, colors, visualclass);
+    }
+    
+    pm_close(ifP);
+    pm_close(stdout);
+    
+    return 0;
+}
+
+
+/*
+   This used to be the way we parsed a direct/true color pixel.  I'm 
+   keeping it here in case we find out some application needs it this way.
+
+   There doesn't seem to be any reason to do this hard-coded stuff when
+   the header contains 32 bit masks that tell exactly how to extract the
+   3 colors in all cases.
+
+   We know for a fact that 16 bit TrueColor output from XFree86's xwd
+   doesn't match these hard-coded shift amounts, so we have replaced
+   this whole switch thing.  -Bryan 00.03.01
+
+   switch (bits_per_pixel) {
+                
+   case 16:
+       PPM_ASSIGN( *xP,
+                   ( ( ul & red_mask )   >> 0    ),
+                   ( ( ul & green_mask ) >> 5  ),
+                   ( ( ul & blue_mask )  >> 11) );
+       break;
+
+   case 24:
+   case 32:
+       PPM_ASSIGN( *xP, ( ( ul & 0xff0000 ) >> 16 ),
+                   ( ( ul & 0xff00 ) >> 8 ),
+                   ( ul & 0xff ) );
+       break;
+
+   default:
+       pm_error( "True/Direct is valid only with 16, 24, and 32 bits" );
+   }
+*/
diff --git a/converter/other/zeisstopnm.c b/converter/other/zeisstopnm.c
new file mode 100644
index 00000000..e94d9b44
--- /dev/null
+++ b/converter/other/zeisstopnm.c
@@ -0,0 +1,187 @@
+/* zeisstopnm.c - convert a Zeiss confocal image into a portable anymap
+**
+** Copyright (C) 1993 by Oliver Trepte, oliver@fysik4.kth.se
+**
+** Derived from the pbmplus package,
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+**
+** This conversion utility is based on a mail from Keith Bartels to
+** the confocal mail group (confocal@ubvm.cc.buffalo.edu) in June 1993.
+**
+** I'm including here a description of the Zeiss confocal image format. I
+** obtained this over the phone on 4-10-1991 form a Zeiss Engineer, and from
+** what I hear, it is probably not correct for the new scopes.
+** Zeiss puts its header information and the end of the file, so I call it
+** a tailer.  This is nice because most conversions programs that work with raw
+** image files will read the image simply ignore the tailer.  The file contains:
+**
+** The image data: NxN  1 byte pixels (where N is the number of pixels in a
+** scan line) in a standard raw rastering order.
+**
+** The tailer contains:
+** 256 bytes: the blue Look-Up-Table (LUT)
+** 256 bytes: the red LUT
+** 256 bytes: the green LUT
+** 8 bytes: empty
+** 2 bytes: no. of columns in the image. hi-byte, low-byte
+** 2 bytes: no. of rows in the image.
+** 2 bytes: x-position of upper left pixel  (0 for 512x512 images)
+** 2 bytes: y-position of upper left pixel  (0 for 512x512 images)
+** 16 bytes: empty
+** 32 bytes: test from upper right corner of image, ASCII.
+** 128 bytes: text from the bottom two rows of the screen, ASCII.
+** 64 bytes: reserved
+** -----------
+** 1024 bytes TOTAL
+**
+** So, image files contain NxN + 1024 bytes.
+**
+** Keith Bartels
+** keith@VISION.EE.UTEXAS.EDU
+**
+*/
+
+#include "pnm.h"
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    int argn, row, i;
+    register int col;
+    int rows=0, cols=0;
+    int format = 0;
+    xel* xelrow;
+    register xel* xP;
+    char* buf = NULL;
+    unsigned char *lutr, *lutg, *lutb;
+    long nread = 0;
+    unsigned char* byteP;
+    const char* const usage = "[-pgm|-ppm] [Zeissfile]";
+
+
+    pnm_init( &argc, argv );
+
+    argn = 1;
+
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+	{
+	if ( pm_keymatch( argv[argn], "-pgm", 3 ) )
+	    {
+	    if ( argn >= argc )
+		pm_usage( usage );
+	    format = PGM_TYPE;
+	    }
+	else if ( pm_keymatch( argv[argn], "-ppm", 3 ) )
+	    {
+	    if ( argn >= argc )
+		pm_usage( usage );
+	    format = PPM_TYPE;
+	    }
+	else
+	    pm_usage( usage );
+	++argn;
+	}
+
+    if ( argn < argc )
+	{
+	ifp = pm_openr( argv[argn] );
+	++argn;
+	}
+    else
+	ifp = stdin;
+
+    if ( argn != argc )
+	pm_usage( usage );
+
+    /* Read the image to a buffer */
+
+    buf = pm_read_unknown_size( ifp, &nread );
+
+    /* Check the format of the file */
+
+    if (nread <=1024)
+	pm_error( "Input file not in Zeiss format (too small)" );
+
+    lutg = (unsigned char *)buf+(nread-1024+512);
+    lutr = (unsigned char *)buf+(nread-1024+256);
+    lutb = (unsigned char *)buf+(nread-1024);
+
+    cols = ((unsigned char) buf[nread-1024+768+8]) +
+	(((unsigned char) buf[nread-1024+768+9]) << 8);
+    rows = ((unsigned char) buf[nread-1024+768+10]) +
+	(((unsigned char) buf[nread-1024+768+11]) << 8);
+
+    if ( cols <= 0 )
+	pm_error( "invalid cols: %d", cols );
+    if ( rows <= 0 )
+	pm_error( "invalid rows: %d", rows );
+
+    if (cols*rows != nread-1024)
+	pm_error( "Hmm, %d rows, %d cols, %ld total image size",
+		 rows, cols, nread-1024);
+
+    /* Choose pgm or ppm */
+    /* If the LUTs all contain 0,1,2,3,4..255, it is a pgm file */
+
+    for (i=0; i<256 && format==0; i++)
+	if (lutr[i] != i || lutg[i] != i || lutb[i] != i)
+	    format = PPM_TYPE;
+
+    if (format == 0)
+	format = PGM_TYPE;
+
+    pnm_writepnminit( stdout, cols, rows, 255, format, 0 );
+    xelrow = pnm_allocrow( cols );
+    byteP = (unsigned char *) buf;
+
+    switch ( PNM_FORMAT_TYPE(format) )
+        {
+        case PGM_TYPE:
+        pm_message( "writing PGM file, %d rows %d columns", rows, cols );
+        break;
+
+        case PPM_TYPE:
+        pm_message( "writing PPM file, %d rows %d columns", rows, cols );
+        break;
+
+        default:
+        pm_error( "shouldn't happen" );
+        }
+
+    for ( row = 0; row < rows; ++row )
+    {
+	switch ( PNM_FORMAT_TYPE(format) )
+	{
+	case PGM_TYPE:
+	    for ( col = 0, xP = xelrow; col < cols; ++col, ++xP, ++byteP )
+		PNM_ASSIGN1( *xP, *byteP );
+	    break;
+
+	case PPM_TYPE:
+	    for ( col = 0, xP = xelrow; col < cols; ++col, ++xP, ++byteP )
+		PPM_ASSIGN( *xP, lutr[*byteP], lutg[*byteP], lutb[*byteP] );
+	    break;
+
+        default:
+	    pm_error( "shouldn't happen" );
+        }
+
+	pnm_writepnmrow( stdout, xelrow, cols, 255, format, 0 );
+    }
+
+    free( buf );
+    pm_close( stdout );
+
+    exit( 0 );
+}
diff --git a/converter/pbm/Makefile b/converter/pbm/Makefile
new file mode 100644
index 00000000..747f2a4d
--- /dev/null
+++ b/converter/pbm/Makefile
@@ -0,0 +1,78 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/pbm
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+PORTBINARIES =	atktopbm brushtopbm cmuwmtopbm ddbugtopbm g3topbm escp2topbm \
+		icontopbm macptopbm mdatopbm mgrtopbm mrftopbm \
+		pbmto10x pbmto4425 pbmtoascii pbmtoatk \
+		pbmtobbnbg pbmtocmuwm pbmtodjvurle \
+		pbmtoepsi pbmtoepson pbmtoescp2 \
+		pbmtog3 pbmtogem pbmtogo pbmtoibm23xx pbmtoicon pbmtolj \
+		pbmtoln03 pbmtolps \
+		pbmtomacp pbmtomatrixorbital pbmtomda pbmtomgr pbmtomrf \
+		pbmtonokia \
+		pbmtopi3 pbmtoplot pbmtopsg3 pbmtoptx pbmtowbmp \
+		pbmtox10bm pbmtoxbm pbmtoybm pbmtozinc \
+		pi3topbm pktopbm \
+		wbmptopbm xbmtopbm ybmtopbm	
+
+ifneq ($(LEX)x,x)
+  PORTBINARIES += thinkjettopbm
+endif
+
+#pbmpage uses sqrt(), which is sometimes in libc, not libm.  Is it ever
+#in libm?
+MATHBINARIES =	pbmtopk
+BINARIES =	$(PORTBINARIES) $(MATHBINARIES)
+SCRIPTS =
+
+OBJECTS = $(BINARIES:%=%.o)
+
+MERGEBINARIES = $(BINARIES)
+MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2)
+
+SUBDIRS=pbmtoppa
+
+.PHONY: all
+all: $(BINARIES) $(SUBDIRS:%=%/all)
+
+include $(SRCDIR)/Makefile.common
+
+ifneq ($(LEX)x,x)
+thinkjettopbm.c1:%.c1:%.l
+	$(LEX) -t $< >$@
+endif
+
+thinkjettopbm.c:%.c:%.c1 $(SRCDIR)/lib/util/lexheader
+# Different versions of Lex produce subtly different output, from the
+# same .l source file.  The .c1 file contains the raw output from Lex.
+# We now massage it so it will compile.  We must add some definitions
+# at the top (the lexheader file).  We must remove any yylex and
+# yywrap prototype, as our .l file already contains one.  The Lex
+# version "Software Generation Utilities (SGU) Solaris-ELF (4.0)"
+# puts declarations for yylex and yywrap, as external symbols,
+# into its output, causing a duplicate declaration error at compile time.
+#
+# Schwarb Manfred reports that it compiles OK, but with warnings, on
+# Solaris.  Solaris Lex has a -e option that eliminates the lex
+# warnings, but causes compiler warnings.  AIX and Flex don't have a
+# -e option.  -Bryan 2001.05.16.
+#
+# But Peter Weisz reported on 2002.12.11 that on Solaris, compile
+# failed due to a duplicate declaration of yylex and yywrap with Netpbm
+# 10.12, which does not remove those declarations as the current version
+# does.
+	cat $(SRCDIR)/lib/util/lexheader $< | \
+	  grep -v "^[[:space:]]*int yylex(void);" | \
+	  grep -v "^[[:space:]]*int yywrap(void);" \
+	  >$@
+
+clean: localclean
+.PHONY: localclean
+localclean:
+	-rm -f thinkjettopbm.c
diff --git a/converter/pbm/atktopbm.c b/converter/pbm/atktopbm.c
new file mode 100644
index 00000000..c4a81808
--- /dev/null
+++ b/converter/pbm/atktopbm.c
@@ -0,0 +1,362 @@
+/* atktopbm.c - convert Andrew Toolkit raster object to portable bitmap
+**
+** Copyright (C) 1991 by Bill Janssen
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "nstring.h"
+#include "pbm.h"
+#include "mallocvar.h"
+
+
+/* readatkraster
+**
+** Routine for reading rasters in .raster form.  (BE2 rasters version 2.)
+*/
+
+/* codes for data stream */
+#define WHITEZERO   'f'
+#define WHITETWENTY 'z'
+#define BLACKZERO   'F'
+#define BLACKTWENTY 'Z'
+#define OTHERZERO   0x1F
+
+#define WHITEBYTE   0x00
+#define BLACKBYTE   0xFF
+
+/* error codes (copied from $ANDREW/atk/basics/common/dataobj.ch) */
+/* return values from Read */
+#define dataobject_NOREADERROR  0
+#define dataobject_PREMATUREEOF 1
+#define dataobject_NOTBE2DATASTREAM 2 /* backward compatibility */
+#define dataobject_NOTATKDATASTREAM 2 /* preferred version */
+#define dataobject_MISSINGENDDATAMARKER 3
+#define dataobject_OBJECTCREATIONFAILED 4
+#define dataobject_BADFORMAT 5
+
+/* ReadRow(file, row, length) 
+** Reads from 'file' the encoding of bytes to fill in 'row'.  Row will be
+** truncated or padded (with WHITE) to exactly 'length' bytes.
+**
+** Returns the code that terminated the row.  This may be
+**      '|'     correct end of line
+**      '\0'    if the length was satisfied (before a terminator)
+**      EOF     if the file ended
+**      '\'  '{'    other recognized ends. 
+** The '|' is the expected end and pads the row with WHITE.
+** The '\' and '{' are error conditions and may indicate the
+** beginning of some other portion of the data stream.
+** If the terminator is '\' or '{', it is left at the front of the input.
+** '|' is gobbled up.
+*/
+
+/* macros to generate case entries for switch statement */
+#define case1(v) case v
+#define case4(v) case v: case (v)+1: case (v)+2: case(v)+3
+#define case6(v) case4(v): case ((v)+4): case ((v)+5)
+#define case8(v) case4(v): case4((v)+4)
+
+static long
+ReadRow(FILE * const file, unsigned char * const row, long const length) {
+/*----------------------------------------------------------------------------
+  'file' is where to get them from.
+  'row' is where to put bytes.
+  'length' is how many bytes in row must be filled.
+-----------------------------------------------------------------------------*/
+    /* Each input character is processed by the central loop.  There are 
+    ** some input codes which require two or three characters for
+    ** completion; these are handled by advancing the state machine.
+    ** Errors are not processed; instead the state machine is reset
+    ** to the Ready state whenever a character unacceptable to the
+    ** current state is read.
+    */
+    enum stateCode {
+        Ready,      /* any input code is allowed */
+        HexDigitPending,    /* have seen the first of a hex digit pair */
+        RepeatPending,  /* repeat code has been seen:
+                   must be followed by two hex digits */
+        RepeatAndDigit};    /* have seen repeat code and its first
+                   following digit */
+    enum stateCode InputState;  /* current state */
+    register int c;     /* the current input character */
+    register long repeatcount = 0;  /* current repeat value */
+    register long hexval;   /* current hex value */
+    long pendinghex = 0;    /* the first of a pair of hex characters */
+    int lengthRemaining;
+    unsigned char * cursor;
+    
+    /* We cannot exit when length becomes zero because we need to check 
+    ** to see if a row ending character follows.  Thus length is checked
+    ** only when we get a data generating byte.  If length then is
+    ** zero, we ungetc the byte.
+    */
+
+    lengthRemaining = length;
+    cursor = row;
+
+    InputState = Ready;
+    while ((c=getc(file)) != EOF) switch (c) {
+
+    case8(0x0):
+    case8(0x8):
+    case8(0x10):
+    case8(0x18):
+    case1(' '):
+        /* control characters and space are legal and ignored */
+        break;
+    case1(0x40):    /* '@' */
+    case1(0x5B):    /* '[' */
+    case4(0x5D):    /*  ']'  '^'  '_'  '`' */
+    case4(0x7D):    /* '}'  '~'  DEL  0x80 */
+    default:        /* all above 0x80 */
+        /* error code:  Ignored at present.  Reset InputState. */
+        InputState = Ready;
+        break;
+
+    case1(0x7B):    /* '{' */
+    case1(0x5C):    /* '\\' */
+        /* illegal end of line:  exit anyway */
+        ungetc(c, file);    /* retain terminator in stream */
+        /* DROP THROUGH */
+    case1(0x7C):    /* '|' */
+        /* legal end of row: may have to pad  */
+        while (lengthRemaining-- > 0)
+            *cursor++ = WHITEBYTE;
+        return c;
+    
+    case1(0x21):
+    case6(0x22):
+    case8(0x28):
+        /* punctuation characters: repeat byte given by two
+        ** succeeding hex chars
+        */
+        if (lengthRemaining <= 0) {
+            ungetc(c, file);
+            return('\0');
+        }
+        repeatcount = c - OTHERZERO;
+        InputState = RepeatPending;
+        break;
+
+    case8(0x30):
+    case8(0x38):
+        /* digit (or following punctuation)  -  hex digit */
+        hexval = c - 0x30;
+        goto hexdigit;
+    case6(0x41):
+        /* A ... F    -  hex digit */
+        hexval = c - (0x41 - 0xA);
+        goto hexdigit;
+    case6(0x61):
+        /* a ... f  - hex digit */
+        hexval = c - (0x61 - 0xA);
+        goto hexdigit;
+
+    case8(0x67):
+    case8(0x6F):
+    case4(0x77):
+        /* g ... z   -   multiple WHITE bytes */
+        if (lengthRemaining <= 0) {
+            ungetc(c, file);
+            return('\0');
+        }
+        repeatcount = c - WHITEZERO;
+        hexval = WHITEBYTE;
+        goto store;
+    case8(0x47):
+    case8(0x4F):
+    case4(0x57):
+        /* G ... Z   -   multiple BLACK bytes */
+        if (lengthRemaining <= 0) {
+            ungetc(c, file);
+            return('\0');
+        }
+        repeatcount = c - BLACKZERO;
+        hexval = BLACKBYTE;
+        goto store;
+
+hexdigit:
+        /* process a hex digit.  Use InputState to determine
+            what to do with it. */
+        if (lengthRemaining <= 0) {
+            ungetc(c, file);
+            return('\0');
+        }
+        switch(InputState) {
+        case Ready:
+            InputState = HexDigitPending;
+            pendinghex = hexval << 4;
+            break;
+        case HexDigitPending:
+            hexval |= pendinghex;
+            repeatcount = 1;
+            goto store;
+        case RepeatPending:
+            InputState = RepeatAndDigit;
+            pendinghex = hexval << 4;
+            break;
+        case RepeatAndDigit:
+            hexval |= pendinghex;
+            goto store;
+        }
+        break;
+
+store:
+        /* generate byte(s) into the output row 
+            Use repeatcount, depending on state.  */
+        if (lengthRemaining < repeatcount) 
+            /* reduce repeat count if it would exceed
+                available space */
+            repeatcount = lengthRemaining;
+        lengthRemaining -= repeatcount;  /* do this before repeatcount-- */
+        while (repeatcount-- > 0)
+                *cursor++ = hexval;
+        InputState = Ready;
+        break;
+
+    } /* end of while( - )switch( - ) */
+    return EOF;
+}
+
+
+
+#undef case1
+#undef case4
+#undef case6
+#undef case8
+
+
+
+static void
+ReadATKRaster(FILE * const file, 
+              int * const rwidth, 
+              int * const rheight, 
+              unsigned char ** const destaddrP) {
+
+    int row, rowlen;  /* count rows;  byte length of row */
+    int version;
+    char keyword[6];
+    int discardid;
+    int objectid;     /* id read for the incoming pixel image */
+    long tc;            /* temp */
+    int width, height;      /* dimensions of image */
+
+    if (fscanf(file, "\\begindata{raster,%d", &discardid) != 1
+                || getc(file) != '}' || getc(file) != '\n')
+      pm_error ("input file not Andrew raster object");
+
+    fscanf(file, " %d ", &version);
+    if (version < 2) 
+      pm_error ("version too old to parse");
+
+    {
+        unsigned int options;
+        long xscale, yscale;
+        long xoffset, yoffset, subwidth, subheight;
+        /* ignore all these features: */
+        fscanf(file, " %u %ld %ld %ld %ld %ld %ld",  
+               &options, &xscale, &yscale, &xoffset, 
+               &yoffset, &subwidth, &subheight);
+    }
+    /* scan to end of line in case this is actually something beyond V2 */
+    while (((tc=getc(file)) != '\n') && (tc != '\\') && (tc != EOF)) {}
+
+    /* read the keyword */
+    fscanf(file, " %5s", keyword);
+    if (!STREQ(keyword, "bits"))
+      pm_error ("keyword is not 'bits'!");
+
+    fscanf(file, " %d %d %d ", &objectid, &width, &height);
+
+    if (width < 1 || height < 1 || width > 1000000 || height > 1000000) 
+      pm_error ("bad width or height");
+
+    *rwidth = width;
+    *rheight = height;
+    rowlen = (width + 7) / 8;
+    MALLOCARRAY(*destaddrP, height * rowlen);
+    if (destaddrP == NULL)
+        pm_error("Unable to allocate %u bytes for the input image.",
+                 height * rowlen);
+    for (row = 0;   row < height;   row++)
+      {
+        long c;
+
+        c = ReadRow(file, *destaddrP + (row * rowlen), rowlen);
+        if (c != '|')
+          {
+        if (c == EOF)
+          pm_error ("premature EOF");
+        else
+          pm_error ("bad format");
+        break;
+          }
+      }
+    while (! feof(file) && getc(file) != '\\') {};  /* scan for \enddata */
+    if (fscanf(file, "enddata{raster,%d", &discardid) != 1
+        || getc(file) != '}' || getc(file) != '\n')
+      pm_error ("missing end-of-object marker");
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    FILE *ifp;
+    register bit *bitrow, *bP;
+    int rows, cols, row, col, charcount;
+    unsigned char *data, mask;
+
+
+    pbm_init ( &argc, argv );
+
+    if ( argc > 2 )
+        pm_usage( "[raster obj]" );
+    
+    if ( argc == 2 )
+        ifp = pm_openr( argv[1] );
+    else
+        ifp = stdin;
+
+    ReadATKRaster( ifp, &cols, &rows, &data );
+
+    pm_close( ifp );
+
+    pbm_writepbminit( stdout, cols, rows, 0 );
+    bitrow = pbm_allocrow( cols );
+
+    for ( row = 0; row < rows; ++row )
+    {
+        charcount = 0;
+        mask = 0x80;
+        for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
+        {
+            if ( charcount >= 8 )
+            {
+                ++data;
+                charcount = 0;
+                mask = 0x80;
+            }
+            *bP = ( *data & mask ) ? PBM_BLACK : PBM_WHITE;
+            ++charcount;
+            mask >>= 1;
+        }
+        ++data;
+        pbm_writepbmrow( stdout, bitrow, cols, 0 );
+    }
+
+    pm_close( stdout );
+    exit( 0 );
+}
+
diff --git a/converter/pbm/brushtopbm.c b/converter/pbm/brushtopbm.c
new file mode 100644
index 00000000..0cffaa4d
--- /dev/null
+++ b/converter/pbm/brushtopbm.c
@@ -0,0 +1,107 @@
+/* brushtopbm.c - read a doodle brush file and write a portable bitmap
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+
+static void getinit ARGS(( FILE* file, int* colsP, int* rowsP ));
+static bit getbit ARGS(( FILE* file ));
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    bit* bitrow;
+    register bit* bP;
+    int rows, cols, padright, row, col;
+
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[brushfile]" );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    getinit( ifp, &cols, &rows );
+
+    pbm_writepbminit( stdout, cols, rows, 0 );
+    bitrow = pbm_allocrow( cols );
+
+    /* Compute padding to round cols up to the next multiple of 16. */
+    padright = ( ( cols + 15 ) / 16 ) * 16 - cols;
+
+    for ( row = 0; row < rows; ++row )
+	{
+	/* Get data. */
+        for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
+	    *bP = getbit( ifp );
+	/* Discard line padding. */
+        for ( col = 0; col < padright; ++col )
+	    (void) getbit( ifp );
+	/* Write row. */
+	pbm_writepbmrow( stdout, bitrow, cols, 0 );
+	}
+
+    pm_close( ifp );
+    pm_close( stdout );
+    
+    exit( 0 );
+    }
+
+
+static int item, bitsperitem, bitshift;
+
+static void
+getinit( file, colsP, rowsP )
+    FILE* file;
+    int* colsP;
+    int* rowsP;
+    {
+    int i;
+
+    if ( getc( file ) != 1 )
+	pm_error( "bad magic number 1" );
+    if ( getc( file ) != 0 )
+	pm_error( "bad magic number 2" );
+    *colsP = getc( file ) << 8;
+    *colsP += getc( file );
+    *rowsP = getc( file ) << 8;
+    *rowsP += getc( file );
+    bitsperitem = 8;
+
+    /* Junk rest of header. */
+    for ( i = 0; i < 10; ++i )  /* 10 is just a guess at the header size */
+	(void) getc( file );
+    }
+
+static bit
+getbit( file )
+    FILE* file;
+    {
+    bit b;
+
+    if ( bitsperitem == 8 )
+	{
+	item = getc( file );
+	bitsperitem = 0;
+	bitshift = 7;
+	}
+    ++bitsperitem;
+    b = ( ( item >> bitshift) & 1 ) ? PBM_WHITE : PBM_BLACK;
+    --bitshift;
+    return b;
+    }
diff --git a/converter/pbm/cmuwm.h b/converter/pbm/cmuwm.h
new file mode 100644
index 00000000..e667f25e
--- /dev/null
+++ b/converter/pbm/cmuwm.h
@@ -0,0 +1,17 @@
+/* cmuwm.h - definitions for the CMU window manager format
+*/
+
+#ifndef CMUWM_H_INCLUDED
+#define CMUWM_H_INCLUDED
+
+struct cmuwm_header
+    {
+    long magic;
+    long width;
+    long height;
+    short depth;
+    };
+
+#define CMUWM_MAGIC 0xf10040bbL
+
+#endif
diff --git a/converter/pbm/cmuwmtopbm.c b/converter/pbm/cmuwmtopbm.c
new file mode 100644
index 00000000..dce1d449
--- /dev/null
+++ b/converter/pbm/cmuwmtopbm.c
@@ -0,0 +1,114 @@
+/* cmuwmtopbm.c - read a CMU window manager bitmap and produce a portable bitmap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+#include "cmuwm.h"
+
+static void getinit ARGS(( FILE* file, int* colsP, int* rowsP, short* depthP, int* padrightP ));
+static bit getbit ARGS(( FILE* file ));
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    bit* bitrow;
+    register bit* bP;
+    int rows, cols, padright, row, col;
+    short depth;
+
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[cmuwmfile]" );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    getinit( ifp, &cols, &rows, &depth, &padright );
+    if ( depth != 1 )
+	pm_error(
+	    "CMU window manager file has depth of %d, must be 1",
+	    (int) depth );
+
+    pbm_writepbminit( stdout, cols, rows, 0 );
+    bitrow = pbm_allocrow( cols );
+
+    for ( row = 0; row < rows; ++row )
+	{
+	/* Get data. */
+        for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
+	    *bP = getbit( ifp );
+	/* Discard line padding */
+        for ( col = 0; col < padright; ++col )
+	    (void) getbit( ifp );
+	pbm_writepbmrow( stdout, bitrow, cols, 0 );
+	}
+
+    pm_close( ifp );
+    pm_close( stdout );
+
+    exit( 0 );
+    }
+
+static int item, bitsperitem, bitshift;
+
+static void
+getinit( file, colsP, rowsP, depthP, padrightP )
+    FILE* file;
+    int* colsP;
+    int* rowsP;
+    short* depthP;
+    int* padrightP;
+    {
+    long l;
+
+    if ( pm_readbiglong( file, &l ) == -1 )
+	pm_error( "EOF / read error" );
+    if ( l != CMUWM_MAGIC )
+	pm_error( "bad magic number in CMU window manager file" );
+    if ( pm_readbiglong( file, &l ) == -1 )
+	pm_error( "EOF / read error" );
+    *colsP = (int) l;
+    if ( pm_readbiglong( file, &l ) == -1 )
+	pm_error( "EOF / read error" );
+    *rowsP = (int) l;
+    if ( pm_readbigshort( file, depthP ) == -1 )
+	pm_error( "EOF / read error" );
+    *padrightP = ( ( *colsP + 7 ) / 8 ) * 8 - *colsP;
+
+    bitsperitem = 0;
+    }
+
+static bit
+getbit( file )
+    FILE* file;
+    {
+    bit b;
+
+    if ( bitsperitem == 0 )
+	{
+	item = getc( file );
+	if ( item == EOF )
+	    pm_error( "EOF / read error" );
+	bitsperitem = 8;
+	bitshift = 7;
+	}
+    b = ( ( item >> bitshift) & 1 ) ? PBM_WHITE : PBM_BLACK;
+    --bitsperitem;
+    --bitshift;
+    return b;
+    }
diff --git a/converter/pbm/ddbugtopbm.c b/converter/pbm/ddbugtopbm.c
new file mode 100644
index 00000000..8b0a6d0e
--- /dev/null
+++ b/converter/pbm/ddbugtopbm.c
@@ -0,0 +1,173 @@
+/* ddbugtopbm - convert Diddle/DiddleBug sketch DB to PBM files.
+ * Copyright (c) 2002 Russell Marks. See COPYING for licence details.
+ *
+ * The decompression code (uncompress_sketch() is directly from
+ * DiddleBug itself, which like ddbugtopbm is distributed under the
+ * terms of the GNU GPL. As such, the following copyright applies to
+ * uncompress_sketch():
+ *
+ * Copyright (c) 1999,2000 Mitch Blevins <mblevin@debian.org>.
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  
+ *
+ * Adapted to Netpbm by Bryan Henderson 2003.08.09.  Bryan got his copy
+ * from ftp:ibiblio.com/pub/linux/apps/graphics/convert, dated 2002.08.21.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "pm_c_util.h"
+#include "pbm.h"
+#include "mallocvar.h"
+
+/* this is basically UncompressSketch() from DiddleBug 2.50's diddlebug.c */
+static void 
+uncompress_sketch(unsigned char * const cPtr,
+                  unsigned char * const uPtr,
+                  int             const size) {
+    int i, j;
+    unsigned char num_bytes;
+
+    j = 0;
+    for (i=0; i<size; ++i) {
+        if (cPtr[i]&0x80) {
+            num_bytes = cPtr[i]&0x3f;  /* ~(0x40|0x80) */
+            ++num_bytes;
+            if (cPtr[i]&0x40) {
+                /* Mixed */
+                memmove(uPtr+j, cPtr+i+1, num_bytes);
+                i += num_bytes;
+            } else {
+                /* Black */
+                memset(uPtr+j, 0xff, num_bytes);
+            }
+        } else {
+            /* White */
+            num_bytes = cPtr[i]&0x7f; /* ~0x80 */
+            ++num_bytes;
+            memset(uPtr+j, 0x00, num_bytes);
+        }
+        j += num_bytes;
+    }
+}
+
+
+
+static const char *
+make_noname(void) {
+    static char name[128];
+    static int num=0;
+    FILE *out;
+
+    out = NULL;
+
+    do {
+        num++;
+        if (out != NULL) 
+            fclose(out);
+        sprintf(name, "sketch-%04d.pbm", num);
+    } while (num<10000 && (out = fopen(name, "rb")) != NULL);
+
+    if (num>=10000)
+        pm_error("too many unnamed sketches!");
+
+    return(name);
+}
+
+
+
+int 
+main(int argc, char ** argv) {
+    FILE * const in=stdin;
+
+    static unsigned char buf[64*1024];
+    int *recoffsets;
+    int f;
+    int numrecs;
+    bool is_diddle;
+
+    pbm_init(&argc, argv);
+
+    if (argc-1 > 0)
+        pm_error("Program takes no arguments.");
+
+    /* main DB header */
+    fread(buf, 1, 64, in);
+
+    fread(buf, 1, 14, in);
+    if (memcmp(buf, "DIDL", 4) != 0 && memcmp(buf, "DIDB", 4) !=0 )
+        pm_error("not a Diddle or DiddleBug DB.");
+    is_diddle = (memcmp(buf, "DIDL", 4) == 0);
+    numrecs = buf[12]*256+buf[13];
+
+    MALLOCARRAY_NOFAIL(recoffsets, numrecs);
+
+    for (f = 0; f < numrecs; ++f) {
+        fread(buf, 1, 8, in);
+        recoffsets[f] = ((buf[0]<<24)|(buf[1]<<16)|(buf[2]<<8)|buf[3]);
+    }
+
+    for (f = 0; f < numrecs; ++f) {
+        static unsigned char obuf[64*1024];
+        char * nameptr;
+        char * ptr;
+        const char * outfilename;
+        FILE *out;
+
+        fseek(in, recoffsets[f], SEEK_SET);
+        fread(buf, 1, 16*1024, in);  /* XXX crappy! */
+
+        if (is_diddle) {
+            /* Diddle */
+            memcpy(obuf, buf, 160*160/8);
+            nameptr=(char*)(buf+(160*160/8));
+        } else {
+            /* DiddleBug */
+            int const sketchlen = buf[8]*256+buf[9];
+            uncompress_sketch(buf+26+80+1, obuf, sketchlen-80-1);
+            nameptr = (char*)(buf+26+sketchlen);
+        }
+
+        for (ptr = nameptr; *ptr; ++ptr) {
+            if (!isalnum(*ptr) && strchr("()-_+=[]:;,.<>?",*ptr) == NULL)
+                *ptr='_';
+            if (isupper(*ptr)) 
+                *ptr = tolower(*ptr);
+        }
+
+        if (*nameptr==0)
+            outfilename = make_noname();
+        else {
+            strcat(nameptr, ".pbm");
+            outfilename = nameptr;
+        }
+
+
+        pm_message("extracting sketch %2d as `%s'", f, outfilename);
+        if((out=fopen(outfilename,"wb"))==NULL)
+            pm_message("WARNING: couldn't open file '%s'.  Carrying on...", 
+                       outfilename);
+        else {
+            pbm_writepbminit(out, 160, 160, FALSE);
+            fwrite(obuf,1,160*160/8,out);
+            fclose(out);
+        }
+    }
+    return 0;
+}
diff --git a/converter/pbm/escp2topbm.c b/converter/pbm/escp2topbm.c
new file mode 100644
index 00000000..049ed23c
--- /dev/null
+++ b/converter/pbm/escp2topbm.c
@@ -0,0 +1,143 @@
+/* escp2topbm.c - read an Epson ESC/P2 printer file and
+**                 create a pbm file from the raster data,
+**                 ignoring all other data.
+**                 Can be regarded as a simple raster printer emulator
+**                 with a RLE run length decoder.
+**                 This program was made primarily for the test of pbmtoescp2
+**
+** Copyright (C) 2003 by Ulrich Walcher (u.walcher@gmx.de)
+**                       and Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "pbm.h"
+#include "mallocvar.h"
+
+/* RLE decoder */
+static unsigned int 
+dec_epson_rle(unsigned        const int k, 
+              unsigned        const char * in, 
+              unsigned char * const out) {
+
+    unsigned int i;
+    unsigned int pos;
+    unsigned int dpos;
+
+    pos = 0;  /* initial value */
+    dpos = 0; /* initial value */
+
+    while (dpos < k) {
+        if (in[pos] < 128) {
+            for (i = 0; i < in[pos] + 1; ++i)
+                out[dpos+i] = in[pos + i + 1];     
+            /* copy through */
+            pos += i + 1;
+        } else {
+            for (i = 0; i < 257 - in[pos]; ++i)
+                out[dpos + i] = in[pos + 1];  
+            /* inflate this run */
+            pos += 2;
+        }
+        dpos += i;
+    }
+    return pos;        /* return number of treated input bytes */
+}
+
+
+
+int
+main(int    argc,
+     char * argv[]) {
+
+    unsigned int const size = 4096; /* arbitrary value */
+
+    FILE *ifP;
+    unsigned int i, len, pos, opos, width, height;
+    unsigned char *input, *output;
+    const char * fileName;
+
+    pbm_init(&argc, argv);
+
+    MALLOCARRAY(input, size);
+    MALLOCARRAY(output, size);
+    
+    if (input == NULL || output == NULL)
+        pm_error("Cannot allocate memory");
+
+    if (argc-1 > 1)
+        pm_error("Too many arguments (%u).  Only argument is filename.",
+                 argc-1);
+
+    if (argc == 2)
+        fileName = argv[1];
+    else
+        fileName = "-";
+
+    ifP = pm_openr(fileName);
+
+    /* read the whole file */
+    len = 0;  /* initial value */
+    for (i = 0; !feof(ifP); ++i) {
+        size_t bytesRead;
+        REALLOCARRAY(input, (i+1) * size);
+        if (input == NULL)
+            pm_error("Cannot allocate memory");
+        bytesRead = fread(input + i * size, 1, size, ifP);
+        len += bytesRead;
+    }
+
+    /* filter out raster data */
+    height = 0;  /* initial value */
+    pos = 0;     /* initial value */
+    opos = 0;    /* initial value */
+
+    while (pos < len) {
+        /* only ESC sequences are regarded  */
+        if (input[pos] == '\x1b' && input[pos+1] == '.') {
+            unsigned int const k =
+                input[pos+5] * ((input[pos+7] * 256 + input[pos+6] + 7) / 8);
+            height += input[pos+5];
+            width = input[pos+7] * 256 + input[pos+6];
+            REALLOCARRAY(output, opos + k);
+            if (output == NULL)
+                pm_error("Cannot allocate memory");
+
+            switch (input[pos+2]) {
+            case 0:
+                /* copy the data block */
+                memcpy(output + opos, input + pos + 8, k);        
+                pos += k + 8;
+                opos += k;
+                break;
+            case 1: {
+                /* inflate the data block */
+                unsigned int l;
+                l = dec_epson_rle(k,input+pos+8,output+opos);  
+                pos += l + 8;
+                opos += k;
+            }
+                break;
+            default:
+                pm_error("unknown compression mode");
+                break;
+            }
+        }
+        else
+            ++pos;      /* skip bytes outside the ESCX sequence */
+    }
+
+    pbm_writepbminit(stdout, width, height, 0);
+    fwrite(output, opos, 1, stdout);
+    free(input); free(output);
+    fclose(stdout); fclose(ifP);
+
+    return 0;
+}
diff --git a/converter/pbm/g3.h b/converter/pbm/g3.h
new file mode 100644
index 00000000..e982f2da
--- /dev/null
+++ b/converter/pbm/g3.h
@@ -0,0 +1,146 @@
+/* G3 fax format declarations */
+
+#ifndef G3_H_INCLUDED
+#define G3_H_INCLUDED
+
+/* G3 is nearly universal as the format for fax transmissions in the
+   US.  Its full name is CCITT Group 3 (G3).  It is specified in
+   Recommendations T.4 and T.30 and in EIA Standards EIA-465 and
+   EIA-466.  It dates to 1993.
+
+   G3 faxes are 204 dots per inch (dpi) horizontally and 98 dpi (196
+   dpi optionally, in fine-detail mode) vertically.  Since G3 neither
+   assumes error free transmission nor retransmits when errors occur,
+   the encoding scheme used is differential only over small segments
+   never exceeding 2 lines at standard resolution or 4 lines for
+   fine-detail. (The incremental G3 encoding scheme is called
+   two-dimensional and the number of lines so encoded is specified by
+   a parameter called k.)
+
+   G3 specifies much more than the format of the bit stream, which is
+   the subject of this header file.  It also specifies layers
+   underneath the bit stream.
+
+   There is also the newer G4.  
+*/
+
+typedef struct g3TableEntry {
+    short int code;
+    short int length;
+} g3TableEntry;
+
+static struct g3TableEntry ttable[] = {
+/*    TERMWHITE           TERMBLACK   */
+    { 0x35, 8 },    { 0x37, 10 },       /* white 0 , black 0 */
+    { 0x07, 6 },    { 0x02,  3 },
+    { 0x07, 4 },    { 0x03,  2 },
+    { 0x08, 4 },    { 0x02,  2 },
+    { 0x0b, 4 },    { 0x03,  3 },
+    { 0x0c, 4 },    { 0x03,  4 },
+    { 0x0e, 4 },    { 0x02,  4 },
+    { 0x0f, 4 },    { 0x03,  5 },
+    { 0x13, 5 },    { 0x05,  6 },
+    { 0x14, 5 },    { 0x04,  6 },
+    { 0x07, 5 },    { 0x04,  7 },
+    { 0x08, 5 },    { 0x05,  7 },
+    { 0x08, 6 },    { 0x07,  7 },
+    { 0x03, 6 },    { 0x04,  8 },
+    { 0x34, 6 },    { 0x07,  8 },
+    { 0x35, 6 },    { 0x18,  9 },
+    { 0x2a, 6 },    { 0x17, 10 },
+    { 0x2b, 6 },    { 0x18, 10 },
+    { 0x27, 7 },    { 0x08, 10 },
+    { 0x0c, 7 },    { 0x67, 11 },
+    { 0x08, 7 },    { 0x68, 11 },
+    { 0x17, 7 },    { 0x6c, 11 },
+    { 0x03, 7 },    { 0x37, 11 },
+    { 0x04, 7 },    { 0x28, 11 },
+    { 0x28, 7 },    { 0x17, 11 },
+    { 0x2b, 7 },    { 0x18, 11 },
+    { 0x13, 7 },    { 0xca, 12 },
+    { 0x24, 7 },    { 0xcb, 12 },
+    { 0x18, 7 },    { 0xcc, 12 },
+    { 0x02, 8 },    { 0xcd, 12 },
+    { 0x03, 8 },    { 0x68, 12 },
+    { 0x1a, 8 },    { 0x69, 12 },
+    { 0x1b, 8 },    { 0x6a, 12 },
+    { 0x12, 8 },    { 0x6b, 12 },
+    { 0x13, 8 },    { 0xd2, 12 },
+    { 0x14, 8 },    { 0xd3, 12 },
+    { 0x15, 8 },    { 0xd4, 12 },
+    { 0x16, 8 },    { 0xd5, 12 },
+    { 0x17, 8 },    { 0xd6, 12 },
+    { 0x28, 8 },    { 0xd7, 12 },
+    { 0x29, 8 },    { 0x6c, 12 },
+    { 0x2a, 8 },    { 0x6d, 12 },
+    { 0x2b, 8 },    { 0xda, 12 },
+    { 0x2c, 8 },    { 0xdb, 12 },
+    { 0x2d, 8 },    { 0x54, 12 },
+    { 0x04, 8 },    { 0x55, 12 },
+    { 0x05, 8 },    { 0x56, 12 },
+    { 0x0a, 8 },    { 0x57, 12 },
+    { 0x0b, 8 },    { 0x64, 12 },
+    { 0x52, 8 },    { 0x65, 12 },
+    { 0x53, 8 },    { 0x52, 12 },
+    { 0x54, 8 },    { 0x53, 12 },
+    { 0x55, 8 },    { 0x24, 12 },
+    { 0x24, 8 },    { 0x37, 12 },
+    { 0x25, 8 },    { 0x38, 12 },
+    { 0x58, 8 },    { 0x27, 12 },
+    { 0x59, 8 },    { 0x28, 12 },
+    { 0x5a, 8 },    { 0x58, 12 },
+    { 0x5b, 8 },    { 0x59, 12 },
+    { 0x4a, 8 },    { 0x2b, 12 },
+    { 0x4b, 8 },    { 0x2c, 12 },
+    { 0x32, 8 },    { 0x5a, 12 },
+    { 0x33, 8 },    { 0x66, 12 },
+    { 0x34, 8 },    { 0x67, 12 },       /* white 63 , black 63 */
+
+/* mtable */    
+/*    MKUPWHITE           MKUPBLACK   */
+    { 0x00, 0 },    { 0x00,  0 },   /* dummy to simplify pointer math */
+    { 0x1b, 5 },    { 0x0f, 10 },   /* white 64 , black 64 */
+    { 0x12, 5 },    { 0xc8, 12 },
+    { 0x17, 6 },    { 0xc9, 12 },
+    { 0x37, 7 },    { 0x5b, 12 },
+    { 0x36, 8 },    { 0x33, 12 },
+    { 0x37, 8 },    { 0x34, 12 },
+    { 0x64, 8 },    { 0x35, 12 },
+    { 0x65, 8 },    { 0x6c, 13 },
+    { 0x68, 8 },    { 0x6d, 13 },
+    { 0x67, 8 },    { 0x4a, 13 },
+    { 0xcc, 9 },    { 0x4b, 13 },
+    { 0xcd, 9 },    { 0x4c, 13 },
+    { 0xd2, 9 },    { 0x4d, 13 },
+    { 0xd3, 9 },    { 0x72, 13 },
+    { 0xd4, 9 },    { 0x73, 13 },
+    { 0xd5, 9 },    { 0x74, 13 },
+    { 0xd6, 9 },    { 0x75, 13 },
+    { 0xd7, 9 },    { 0x76, 13 },
+    { 0xd8, 9 },    { 0x77, 13 },
+    { 0xd9, 9 },    { 0x52, 13 },
+    { 0xda, 9 },    { 0x53, 13 },
+    { 0xdb, 9 },    { 0x54, 13 },
+    { 0x98, 9 },    { 0x55, 13 },
+    { 0x99, 9 },    { 0x5a, 13 },
+    { 0x9a, 9 },    { 0x5b, 13 },
+    { 0x18, 6 },    { 0x64, 13 },
+    { 0x9b, 9 },    { 0x65, 13 },
+    { 0x08, 11 },   { 0x08, 11 },        /* extable len = 1792 */
+    { 0x0c, 11 },   { 0x0c, 11 },
+    { 0x0d, 11 },   { 0x0d, 11 },
+    { 0x12, 12 },   { 0x12, 12 },
+    { 0x13, 12 },   { 0x13, 12 },
+    { 0x14, 12 },   { 0x14, 12 },
+    { 0x15, 12 },   { 0x15, 12 },
+    { 0x16, 12 },   { 0x16, 12 },
+    { 0x17, 12 },   { 0x17, 12 },
+    { 0x1c, 12 },   { 0x1c, 12 },
+    { 0x1d, 12 },   { 0x1d, 12 },
+    { 0x1e, 12 },   { 0x1e, 12 },
+    { 0x1f, 12 },   { 0x1f, 12 },
+};
+
+#define mtable ((ttable)+64*2)
+
+#endif
diff --git a/converter/pbm/g3topbm.c b/converter/pbm/g3topbm.c
new file mode 100644
index 00000000..1eefee96
--- /dev/null
+++ b/converter/pbm/g3topbm.c
@@ -0,0 +1,739 @@
+/*===========================================================================
+                            g3topbm
+=============================================================================
+
+  This program reads a Group 3 FAX file and produces a PBM image.
+
+  Bryan Henderson wrote this on August 5, 2004 and contributed it to 
+  the public domain.
+
+  This program is designed to be a drop-in replacement for the program
+  of the same name that was distributed with Pbmplus and Netpbm since
+  1989, written by Paul Haeberli <paul@manray.sgi.com>.
+
+  Bryan used ideas on processing G3 data from Haeberli's code, but did
+  not use any of the code.
+
+  Others have modified the program since Bryan's initial work, each
+  contributing their work to the public domain.
+===========================================================================*/
+
+#include "pbm.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "mallocvar.h"
+#include "g3.h"
+#include "bitreverse.h"
+
+#define MAXCOLS 10800
+#define MAXROWS 14400   /* this allows up to two pages of image */
+
+#define WHASHA 3510
+#define WHASHB 1178
+
+#define BHASHA 293
+#define BHASHB 2695
+
+#define HASHSIZE 1021
+
+static g3TableEntry * whash[HASHSIZE];
+static g3TableEntry * bhash[HASHSIZE];
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilespec;  /* Filespec of input file */
+    unsigned int reversebits;
+    unsigned int kludge;
+    unsigned int stretch;
+    unsigned int stop_error;
+    unsigned int expectedLineSize;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;  /* malloc'ed */
+        /* Instructions to OptParseOptions3 on how to parse our options.  */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int widthSpec, paper_sizeSpec;
+    const char * paperSize;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "reversebits",      OPT_FLAG,  NULL, &cmdlineP->reversebits,
+            0);
+    OPTENT3(0, "kludge",           OPT_FLAG,  NULL, &cmdlineP->kludge,
+            0);
+    OPTENT3(0, "stretch",          OPT_FLAG,  NULL, &cmdlineP->stretch, 
+            0);
+    OPTENT3(0, "stop_error",       OPT_FLAG,  NULL, &cmdlineP->stop_error, 
+            0);
+    OPTENT3(0, "width",            OPT_UINT,  &cmdlineP->expectedLineSize,
+            &widthSpec,                0);
+    OPTENT3(0, "paper_size",       OPT_STRING, &paperSize,
+            &paper_sizeSpec,           0);
+    
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (widthSpec && paper_sizeSpec)
+        pm_error("You can't specify both -width and -paper_size");
+
+    if (widthSpec) {
+        if (cmdlineP->expectedLineSize < 1)
+            pm_error("-width must be at least 1");
+    } else if (paper_sizeSpec) {
+        if (STRCASEEQ(paperSize, "A6"))
+            cmdlineP->expectedLineSize = 864;
+        else if (STRCASEEQ(paperSize, "A5"))
+            cmdlineP->expectedLineSize = 1216;
+        else if (STRCASEEQ(paperSize, "A4"))
+            cmdlineP->expectedLineSize = 1728;
+        else if (STRCASEEQ(paperSize, "B4"))
+            cmdlineP->expectedLineSize = 2048;
+        else if (STRCASEEQ(paperSize, "A3"))
+            cmdlineP->expectedLineSize = 2432;
+        else
+            pm_error("Unrecognized value for -paper_size '%s'.  "
+                     "We recognize only A3, A4, A5, A6, and B4.",
+                     paperSize);
+    } else
+        cmdlineP->expectedLineSize = 0;
+
+    if (argc-1 == 0) 
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+struct bitStream {
+
+    FILE * fileP;
+    bool reversebits;
+    int shdata;
+        /* 8-bit buffer for rawgetbit(). */
+    unsigned int shbit;
+        /* single bit mask for the bit of 'shdata' that is next in the stream.
+           zero when 'shdata' is empty.
+        */
+    unsigned int zeroBitCount;
+        /* Number of consecutive zero bits the stream has seen.  Note that
+           because an EOL mark ends in a one bit, this starts over for each
+           line.
+        */
+};
+
+
+
+static void
+readBit(struct bitStream * const bitStreamP,
+        unsigned int *     const bitP,
+        const char **      const errorP) {
+/*----------------------------------------------------------------------------
+   Return the next raw bit from the G3 input stream.
+
+   Do not call this outside of the bit stream object; Caller is responsible
+   for maintaining object state.
+-----------------------------------------------------------------------------*/
+    *errorP = NULL;  /* initial assumption */
+
+    if ((bitStreamP->shbit & 0xff) == 0) {
+        bitStreamP->shdata = getc(bitStreamP->fileP);
+        if (bitStreamP->shdata == EOF)
+            asprintfN(errorP, "EOF or error reading file");
+        else {
+            bitStreamP->shbit = 0x80;
+            if ( bitStreamP->reversebits )
+                bitStreamP->shdata = bitreverse[ bitStreamP->shdata ];
+            }
+    }
+
+    if (bitStreamP->shdata & bitStreamP->shbit)
+        *bitP = 1;
+    else
+        *bitP = 0;
+
+    bitStreamP->shbit >>= 1;
+}
+
+
+
+static void
+readBitAndDetectEol(struct bitStream * const bitStreamP,
+                    unsigned int *     const bitP,
+                    bool *             const eolP,
+                    const char **      const errorP) {
+/*----------------------------------------------------------------------------
+   Same as readBit(), but iff the bit read is the final bit of an EOL
+   mark, return *eolP == TRUE.
+-----------------------------------------------------------------------------*/
+    readBit(bitStreamP, bitP, errorP);
+    if (!*errorP) {
+        bool eol;
+
+        eol = FALSE;  /* initial assumption */
+        if (*bitP == 0)
+            ++bitStreamP->zeroBitCount;
+        else {
+            if (bitStreamP->zeroBitCount >= 11)
+                eol = TRUE;
+            bitStreamP->zeroBitCount = 0;
+        }
+        *eolP = eol;
+    }
+}
+
+
+static void
+initBitStream(struct bitStream * const bitStreamP,
+              FILE *             const fileP,
+              bool               const reversebits) {
+    
+    bitStreamP->fileP        = fileP;
+    bitStreamP->reversebits  = reversebits;
+    bitStreamP->shbit        = 0x00;
+    bitStreamP->zeroBitCount = 0;
+}
+
+
+
+static void
+skipToNextLine(struct bitStream * const bitStreamP) {
+
+    bool eol;
+    const char * error;
+
+    eol = FALSE;
+    error = NULL;
+    
+    while (!eol && !error) {
+        unsigned int bit;
+        
+        readBitAndDetectEol(bitStreamP, &bit, &eol, &error);
+    }
+}
+
+
+
+static void
+addtohash(g3TableEntry *     hash[], 
+          g3TableEntry       table[], 
+          unsigned int const n, 
+          int          const a, 
+          int          const b) {
+    
+    unsigned int i;
+
+    for (i = 0; i < n; ++i) {
+        g3TableEntry * const teP = &table[i*2];
+        unsigned int const pos =
+            ((teP->length + a) * (teP->code + b)) % HASHSIZE;
+        if (hash[pos])
+            pm_error("internal error: addtohash fatal hash collision");
+        hash[pos] = teP;
+    }
+}
+
+
+
+static g3TableEntry*
+hashfind(g3TableEntry *       hash[], 
+         int          const length, 
+         int          const code, 
+         int          const a, 
+         int          const b) {
+
+    unsigned int pos;
+    g3TableEntry * te;
+
+    pos = ((length + a) * (code + b)) % HASHSIZE;
+    te = hash[pos];
+    return ((te && te->length == length && te->code == code) ? te : 0);
+}
+
+
+
+static void
+buildHashes(g3TableEntry * (*whashP)[HASHSIZE],
+            g3TableEntry * (*bhashP)[HASHSIZE]) {
+
+    unsigned int i;
+
+    for (i = 0; i < HASHSIZE; ++i)
+        (*whashP)[i] = (*bhashP)[i] = NULL;
+
+    addtohash(*whashP, &ttable[0], 64, WHASHA, WHASHB);
+    addtohash(*whashP, &mtable[2], 40, WHASHA, WHASHB);
+
+    addtohash(*bhashP, &ttable[1], 64, BHASHA, BHASHB);
+    addtohash(*bhashP, &mtable[3], 40, BHASHA, BHASHB);
+
+}
+
+
+
+static void
+makeRowWhite(bit *        const bitrow,
+             unsigned int const cols) {
+
+    unsigned int col;
+    for (col = 0; col < MAXCOLS; ++col)
+        bitrow[col] = PBM_WHITE;
+}
+
+
+
+static g3TableEntry *
+g3code(unsigned int const curcode,
+       unsigned int const curlen,
+       bit          const color) {
+
+    g3TableEntry * retval;
+
+    switch (color) {
+    case PBM_WHITE:
+        if (curlen < 4)
+            retval = NULL;
+        else
+            retval = hashfind(whash, curlen, curcode, WHASHA, WHASHB);
+        break;
+    case PBM_BLACK:
+        if (curlen < 2)
+            retval = NULL;
+        else
+            retval = hashfind(bhash, curlen, curcode, BHASHA, BHASHB);
+        break;
+    default:
+        pm_error("INTERNAL ERROR: color is not black or white");
+    }
+    return retval;
+}
+
+
+
+enum g3tableId {TERMWHITE, TERMBLACK, MKUPWHITE, MKUPBLACK};
+
+
+
+static void
+processG3Code(g3TableEntry * const teP,
+              bit *          const bitrow,
+              unsigned int * const colP,
+              bit *          const colorP,
+              unsigned int * const countP) {
+              
+    enum g3tableId const teId =
+        (teP > mtable ? 2 : 0) + (teP - ttable) % 2;
+
+    unsigned int teCount;
+    
+    switch(teId) {
+    case TERMWHITE: teCount = (teP - ttable    ) / 2;      break;
+    case TERMBLACK: teCount = (teP - ttable - 1) / 2;      break;
+    case MKUPWHITE: teCount = (teP - mtable    ) / 2 * 64; break;
+    case MKUPBLACK: teCount = (teP - mtable - 1) / 2 * 64; break;
+    }
+
+    switch (teId) {
+    case TERMWHITE:
+    case TERMBLACK: {
+        unsigned int runLengthSoFar;
+        unsigned int col;
+        
+        col = *colP;
+        runLengthSoFar = MIN(*countP + teCount, MAXCOLS - col);
+
+        if (runLengthSoFar > 0) {
+            if (*colorP == PBM_WHITE) {
+                /* Row was initialized to white, so we just skip */
+                col += runLengthSoFar;
+            } else {
+                unsigned int i;
+                for (i = 0; i < runLengthSoFar; ++i)
+                    bitrow[col++] = PBM_BLACK;
+            }
+        }
+        *colorP = !*colorP;
+        *countP = 0;
+        *colP   = col;
+    } break;
+    case MKUPWHITE:
+    case MKUPBLACK:
+        *countP += teCount;
+        break;
+    default:
+        pm_error("Can't happen");
+    }
+}
+
+
+
+static void
+formatBadCodeException(const char ** const exceptionP,
+                       unsigned int  const col,
+                       unsigned int  const curlen,
+                       unsigned int  const curcode) {
+
+    asprintfN(exceptionP,
+        "bad code word at Column %u.  "
+        "No prefix of the %u bits 0x%x matches any recognized "
+        "code word and no code words longer than 12 bits are "
+        "defined.  ",
+        col, curlen, curcode);
+}
+
+
+
+static void
+readFaxRow(struct bitStream * const bitStreamP,
+           bit *              const bitrow,
+           unsigned int *     const lineLengthP,
+           const char **      const exceptionP,
+           const char **      const errorP) {
+/*----------------------------------------------------------------------------
+  Read one line of G3 fax from the bit stream *bitStreamP into 
+  bitrow[].  Return the length of the line, in pixels, as *lineLengthP.
+
+  If there's a problem with the line, return as much of it as we can,
+  advance the input stream past the next EOL mark, and put a text
+  description of the problem in newly malloc'ed storage at
+  *exceptionP.  If there's no problem, return *exceptionP = NULL.
+
+  We guarantee that we make progress through the input stream.
+
+  Iff there is an error, return a text description of it in newly
+  malloc'ed storage at *errorP and all other specified behavior 
+  (including return values) is unspecified.
+-----------------------------------------------------------------------------*/
+    unsigned int col;
+    unsigned int curlen;  
+        /* Number of bits we've read so far for the code we're currently 
+           reading
+        */
+    unsigned int curcode; 
+        /* What we've assembled so far of the code we're currently reading */
+    unsigned int count;
+        /* Number of consecutive pixels of the same color */
+    bit currentColor;
+        /* The color of the current run of pixels */
+    g3TableEntry * te;
+        /* Address of structure that describes the current G3 code */
+    bool done;
+
+    makeRowWhite(bitrow, MAXCOLS);  /* initialize row */
+
+    col = 0;
+    curlen = 0;
+    curcode = 0;
+    currentColor = PBM_WHITE;
+    count = 0;
+    *exceptionP = NULL;
+    *errorP = NULL;
+    done = FALSE;
+
+    while (!done) {
+        if (col >= MAXCOLS) {
+            asprintfN(exceptionP, "Line is too long for this program to "
+                      "handle -- longer than %u columns", MAXCOLS);
+            done = TRUE;
+        } else {
+            unsigned int bit;
+            bool eol;
+            const char * error;
+
+            readBitAndDetectEol(bitStreamP, &bit, &eol, &error);
+            if (error) {
+                if (col > 0)
+                    /* We got at least some of the row, so it's only an
+                       exception, not a fatal error.
+                    */
+                    *exceptionP = error;
+                else
+                    *errorP = error;
+                done = TRUE;
+            } else if (eol)
+                done = TRUE;
+            else {
+                curcode = (curcode << 1) | bit;
+                curlen++;
+            
+                if (curlen > 13) {
+                    formatBadCodeException(exceptionP, col, curlen, curcode);
+                    done = TRUE;
+                } else if (curcode != 0) {
+                    te = g3code(curcode, curlen, currentColor);
+                    
+                    if (te) {
+                        processG3Code(te, bitrow, &col, &currentColor, &count);
+                        
+                        curcode = 0;
+                        curlen = 0;
+                    }
+                }
+            }
+        }
+    }
+    if (*exceptionP)
+        skipToNextLine(bitStreamP);
+
+    *lineLengthP = col;
+}
+
+
+
+static void
+freeBits(bit **       const bits,
+         unsigned int const rows,
+         bool         const stretched) {
+
+    unsigned int row;
+
+    for (row = 0; row < rows; ++row) {
+        if (stretched && row % 2 == 1) {
+            /* This is just a pointer to the previous row; don't want to
+               free it twice.
+            */
+        } else 
+            pbm_freerow(bits[row]);
+    }
+    free(bits);
+}
+
+
+
+static void
+handleRowException(const char * const exception,
+                   const char * const error,
+                   unsigned int const row,
+                   bool         const tolerateErrors) {
+
+
+    if (exception) {
+        if (tolerateErrors)
+            pm_message("Problem reading Row %u.  Skipping rest of row.  %s",
+                       row, exception);
+        else
+            pm_error("Problem reading Row %u.  Aborting.  %s", row, exception);
+        strfree(exception);
+    }
+
+    if (error) {
+        if (tolerateErrors)
+            pm_message("Unable to read Row %u.  Skipping rest of image.  %s",
+                       row, error);
+        else
+            pm_error("Unable to read Row %u.  Aborting.  %s", row, error);
+        strfree(error);
+    }
+}
+
+
+
+typedef struct {
+    unsigned int expectedLineSize;
+        /* The size that lines are supposed to be.  Zero means we're happy
+           with any size.
+        */
+    unsigned int maxLineSize;
+        /* The maximum line size we have seen so far, or zero if we have
+           not seen any lines yet.
+        */
+    bool warned;
+        /* We have warned the user that he has a line length problem */
+    bool tolerateErrors;
+        /* Try to continue when we detect a line size error, as opposed to
+           aborting the program.
+        */
+} lineSizeAnalyzer;
+
+
+
+static void
+initializeLineSizeAnalyzer(lineSizeAnalyzer * const analyzerP,
+                           unsigned int       const expectedLineSize,
+                           bool               const tolerateErrors) {
+
+    analyzerP->expectedLineSize = expectedLineSize;
+    analyzerP->tolerateErrors   = tolerateErrors;
+
+    analyzerP->maxLineSize = 0;
+    analyzerP->warned      = FALSE;
+}
+
+
+
+static void
+analyzeLineSize(lineSizeAnalyzer * const analyzerP,
+                unsigned int       const thisLineSize) {
+
+    const char * error;
+
+    if (analyzerP->expectedLineSize &&
+        thisLineSize != analyzerP->expectedLineSize)
+        asprintfN(&error, "Image contains a line of %u pixels.  "
+                  "You specified lines should be %u pixels.",
+                  thisLineSize, analyzerP->expectedLineSize);
+    else {
+        if (analyzerP->maxLineSize && thisLineSize != analyzerP->maxLineSize)
+            asprintfN(&error, "There are at least two different "
+                      "line lengths in this image, "
+                      "%u pixels and %u pixels.  "
+                      "This is a violation of the G3 standard.  ",
+                      thisLineSize, analyzerP->maxLineSize);
+        else
+            error = NULL;
+    }
+
+    if (error) {
+        if (analyzerP->tolerateErrors) {
+            if (!analyzerP->warned) {
+                pm_message("Warning: %s.", error);
+                analyzerP->warned = TRUE;
+            }
+        } else
+            pm_error("%s", error);
+
+        strfree(error);
+    }
+    analyzerP->maxLineSize = MAX(thisLineSize, analyzerP->maxLineSize);
+}
+
+
+
+/* An empty line means EOF.  An ancient comment in the code said there
+   is supposed to 6 EOL marks in a row to indicate EOF, but the code
+   checked for 3 and considered 2 in row just to mean a zero length
+   line.  Starting in Netpbm 10.24 (August 2004), we assume there is
+   no valid reason to have an empty line and recognize EOF as any
+   empty line.  Alternatively, we could read off and ignore two empty
+   lines without a 3rd.  
+*/
+
+static void
+readFax(struct bitStream * const bitStreamP,
+        bool               const stretch,
+        unsigned int       const expectedLineSize,
+        bool               const tolerateErrors,
+        bit ***            const bitsP,
+        unsigned int *     const colsP,
+        unsigned int *     const rowsP) {
+
+    lineSizeAnalyzer lineSizeAnalyzer;
+    bit ** bits;
+    const char * error;
+    bool eof;
+    unsigned int row;
+    
+    MALLOCARRAY_NOFAIL(bits, MAXROWS);
+
+    initializeLineSizeAnalyzer(&lineSizeAnalyzer,
+                               expectedLineSize, tolerateErrors);
+
+    eof = FALSE;
+    error = NULL;
+    row = 0;
+
+    while (!eof && !error) {
+        unsigned int lineSize;
+
+        if (row >= MAXROWS)
+            asprintfN(&error, "Image is too tall.  This program can "
+                      "handle at most %u rows", MAXROWS);
+        else {
+            const char * exception;
+
+            bits[row] = pbm_allocrow(MAXCOLS);
+            readFaxRow(bitStreamP, bits[row], &lineSize, &exception, &error);
+
+            handleRowException(exception, error, row, tolerateErrors);
+
+            if (!error) {
+                if (lineSize == 0) {
+                    /* EOF.  See explanation above */
+                    eof = TRUE;
+                } else {
+                    analyzeLineSize(&lineSizeAnalyzer, lineSize);
+                    
+                    if (stretch) {
+                        ++row;
+                        if (row >= MAXROWS)
+                            asprintfN(&error, "Image is too tall.  This "
+                                      "program can handle at most %u rows "
+                                      "after stretching", MAXROWS);
+                        else
+                            bits[row] = bits[row-1];
+                    }
+                    ++row;
+                }
+            }
+        }
+    }
+    *rowsP  = row;
+    *colsP  = lineSizeAnalyzer.maxLineSize;
+    *bitsP  = bits;
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    struct bitStream bitStream;
+    unsigned int rows, cols;
+    bit ** bits;
+
+    pbm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    initBitStream(&bitStream, ifP, cmdline.reversebits);
+
+    if (cmdline.kludge) {
+        /* Skip extra lines to get in sync. */
+        skipToNextLine(&bitStream);
+        skipToNextLine(&bitStream);
+        skipToNextLine(&bitStream);
+    }
+    skipToNextLine(&bitStream);
+
+    buildHashes(&whash, &bhash);
+
+    readFax(&bitStream, cmdline.stretch, cmdline.expectedLineSize,
+            !cmdline.stop_error, 
+            &bits, &cols, &rows);
+
+    pm_close(ifP);
+
+    pbm_writepbm(stdout, bits, cols, rows, 0);
+    pm_close(stdout);
+
+    freeBits(bits, rows, cmdline.stretch);
+
+    return 0;
+}
diff --git a/converter/pbm/icontopbm.c b/converter/pbm/icontopbm.c
new file mode 100644
index 00000000..d6dba8ae
--- /dev/null
+++ b/converter/pbm/icontopbm.c
@@ -0,0 +1,159 @@
+/* icontopbm.c - read a Sun icon file and produce a portable bitmap
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "nstring.h"
+#include "pbm.h"
+
+/* size in bytes of a bitmap */
+#define BitmapSize(width, height) (((((width) + 15) >> 3) &~ 1) * (height))
+
+static void
+ReadIconFile(FILE *                const file, 
+             int *                 const widthP, 
+             int *                 const heightP, 
+             short unsigned int ** const dataP) {
+
+    char variable[80+1];
+    int ch;
+    int status, value, i, data_length, gotsome;
+
+    gotsome = 0;
+    *widthP = *heightP = -1;
+    for ( ; ; )
+    {
+        while ( ( ch = getc( file ) ) == ',' || ch == '\n' || ch == '\t' ||
+                ch == ' ' )
+            ;
+        for ( i = 0;
+              ch != '=' && ch != ',' && ch != '\n' && ch != '\t' && 
+                  ch != ' ' && (i < (sizeof(variable) - 1));
+              i++ )
+        {
+            variable[i] = ch;
+            if ((ch = getc( file )) == EOF)
+                pm_error( "invalid input file -- premature EOF" );
+        }
+        variable[i] = '\0';
+
+        if ( STREQ( variable, "*/" )&& gotsome )
+            break;
+
+        if ( fscanf( file, "%d", &value ) != 1 )
+            continue;
+
+        if ( STREQ( variable, "Width" ) )
+        {
+            *widthP = value;
+            gotsome = 1;
+        }
+        else if ( STREQ( variable, "Height" ) )
+        {
+            *heightP = value;
+            gotsome = 1;
+        }
+        else if ( STREQ( variable, "Depth" )  )
+        {
+            if ( value != 1 )
+                pm_error( "invalid depth" );
+            gotsome = 1;
+        }
+        else if ( STREQ( variable, "Format_version" ) )
+        {
+            if ( value != 1 )
+                pm_error( "invalid Format_version" );
+            gotsome = 1;
+        }
+        else if ( STREQ( variable, "Valid_bits_per_item" ) )
+        {
+            if ( value != 16 )
+                pm_error( "invalid Valid_bits_per_item" );
+            gotsome = 1;
+        }
+    }
+
+    if ( *widthP <= 0 )
+        pm_error( "invalid width (must be positive): %d", *widthP );
+    if ( *heightP <= 0 )
+        pm_error( "invalid height (must be positive): %d", *heightP );
+
+    data_length = BitmapSize( *widthP, *heightP );
+    *dataP = (short unsigned int *) malloc( data_length );
+    if ( *dataP == NULL )
+        pm_error( "out of memory" );
+    data_length /= sizeof( short );
+    
+    for ( i = 0 ; i < data_length; i++ )
+    {
+        if ( i == 0 )
+            status = fscanf( file, " 0x%4hx", *dataP );
+        else
+            status = fscanf( file, ", 0x%4hx", *dataP + i );
+        if ( status != 1 )
+            pm_error( "error 4 scanning bits item" );
+    }
+}
+
+
+
+int
+main(int  argc, char ** argv) {
+
+    FILE* ifp;
+    bit* bitrow;
+    register bit* bP;
+    int rows, cols, row, col, shortcount, mask;
+    short unsigned int * data;
+
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+        pm_usage( "[iconfile]" );
+
+    if ( argc == 2 )
+        ifp = pm_openr( argv[1] );
+    else
+        ifp = stdin;
+
+    ReadIconFile( ifp, &cols, &rows, &data );
+
+    pm_close( ifp );
+
+    pbm_writepbminit( stdout, cols, rows, 0 );
+    bitrow = pbm_allocrow( cols );
+
+    for ( row = 0; row < rows; row++ )
+    {
+        shortcount = 0;
+        mask = 0x8000;
+        for ( col = 0, bP = bitrow; col < cols; col++, bP++ )
+        {
+            if ( shortcount >= 16 )
+            {
+                data++;
+                shortcount = 0;
+                mask = 0x8000;
+            }
+            *bP = ( *data & mask ) ? PBM_BLACK : PBM_WHITE;
+            shortcount++;
+            mask = mask >> 1;
+        }
+        data++;
+        pbm_writepbmrow( stdout, bitrow, cols, 0 );
+    }
+
+    pm_close( stdout );
+    exit( 0 );
+}
+
diff --git a/converter/pbm/macp.h b/converter/pbm/macp.h
new file mode 100644
index 00000000..26a720a2
--- /dev/null
+++ b/converter/pbm/macp.h
@@ -0,0 +1,12 @@
+/* macp.h - header file for MacPaint files
+*/
+
+#ifndef MACP_H_INCLUDED
+#define MACP_H_INCLUDED
+
+#define	HEADER_LENGTH	512
+#define	MAX_LINES	720
+#define	BYTES_WIDE	72
+#define MAX_COLS	576	/* = BYTES_WIDE * 8 */
+
+#endif
diff --git a/converter/pbm/macptopbm.c b/converter/pbm/macptopbm.c
new file mode 100644
index 00000000..f4a341d3
--- /dev/null
+++ b/converter/pbm/macptopbm.c
@@ -0,0 +1,140 @@
+/* macptopbm.c - read a MacPaint file and produce a portable bitmap
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+#include "macp.h"
+
+static void ReadMacPaintFile ARGS(( FILE* file, int extraskip, int* scanLineP, unsigned char Pic[MAX_LINES][BYTES_WIDE] ));
+
+static unsigned char Pic[MAX_LINES][BYTES_WIDE];
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    bit* bitrow;
+    int argn, extraskip, scanLine, rows, cols, row, bcol, i;
+    const char* usage = "[-extraskip N] [macpfile]";
+
+
+    pbm_init( &argc, argv );
+
+    argn = 1;
+    extraskip = 0;
+
+    /* Check for flags. */
+    if ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+	{
+	if ( pm_keymatch( argv[argn], "-extraskip", 2 ) )
+	    {
+	    argn++;
+	    if ( argn == argc || sscanf( argv[argn], "%d", &extraskip ) != 1 )
+		pm_usage( usage );
+	    }
+	else
+	    pm_usage( usage );
+	argn++;
+	}
+
+    if ( argn < argc )
+	{
+	ifp = pm_openr( argv[argn] );
+	argn++;
+	}
+    else
+	ifp = stdin;
+
+    if ( argn != argc )
+	pm_usage( usage );
+
+    ReadMacPaintFile( ifp, extraskip, &scanLine, Pic );
+
+    pm_close( ifp );
+
+    cols = BYTES_WIDE * 8;
+    rows = scanLine;
+    pbm_writepbminit( stdout, cols, rows, 0 );
+    bitrow = pbm_allocrow( cols );
+
+    for ( row = 0; row < rows; row++ )
+	{
+	for ( bcol = 0; bcol < BYTES_WIDE; bcol++ )
+	    for ( i = 0; i < 8; i++ )
+		bitrow[bcol * 8 + i] =
+		    ( (Pic[row][bcol] >> (7 - i)) & 1 ) ? PBM_BLACK : PBM_WHITE;
+	pbm_writepbmrow( stdout, bitrow, cols, 0 );
+	}
+
+    pm_close( stdout );
+    exit( 0 );
+    }
+
+/*
+** Some of the following routine is:
+**
+**                Copyright 1987 by Patrick J. Naughton
+**                         All Rights Reserved
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted,
+** provided that the above copyright notice appear in all copies and that
+** both that copyright notice and this permission notice appear in
+** supporting documentation.
+*/
+
+static void
+ReadMacPaintFile( file, extraskip, scanLineP, Pic )
+    FILE* file;
+    int extraskip;
+    int* scanLineP;
+    unsigned char Pic[MAX_LINES][BYTES_WIDE];
+    {
+    unsigned int i, j, k;
+    unsigned char ch;
+
+    /* Skip over the header. */
+    for ( i = 0; i < extraskip; i++ )
+	getc( file );
+    for ( i = 0; i < HEADER_LENGTH; i++ )
+	getc( file );
+
+    *scanLineP = 0;
+    k = 0;
+
+    while ( *scanLineP < MAX_LINES )
+	{
+	ch = (unsigned char) getc( file );	/* Count byte */
+	i = (unsigned int) ch;
+	if ( ch < 0x80 )
+	    {	/* Unpack next (I+1) chars as is */
+	    for ( j = 0; j <= i; j++ )
+		if ( *scanLineP < MAX_LINES )
+		    {
+		    Pic[*scanLineP][k++] = (unsigned char) getc( file );
+		    if ( ! (k %= BYTES_WIDE) )
+			*scanLineP += 1;
+		    }
+	    }
+	else
+	    {	/* Repeat next char (2's comp I) times */
+	    ch = getc( file );
+	    for ( j = 0; j <= 256 - i; j++ )
+		if ( *scanLineP < MAX_LINES )
+		    {
+		    Pic[*scanLineP][k++] = (unsigned char) ch;
+		    if ( ! (k %= BYTES_WIDE) )
+			*scanLineP += 1;
+		    }
+	    }
+	}
+    }
diff --git a/converter/pbm/mdaspec.txt b/converter/pbm/mdaspec.txt
new file mode 100644
index 00000000..a93c0af7
--- /dev/null
+++ b/converter/pbm/mdaspec.txt
@@ -0,0 +1,239 @@
+
+         MicroDesign 3 page (.MDP) and area (.MDA) file specifications
+                                       
+   This publication and all information contained herein is Copyright ©
+   Creative Technology 1992. This information carries no guarantees of
+   accuracy or liabilities on the part of either Creative Technology or
+   myself.
+   
+   A hard copy of this document (with far better artwork) is available
+   from Creative Technology, 10 Park Street, Uttoxeter, Staffs ST14 7AG,
+   creative@net-shopper.co.uk.
+   
+   [Note: throughout this file hexadecimal numbers are represented as
+   #nn; 'one pixel' refers to one PCW screen pixel, ie. a MicroDesign
+   half-pixel; 'word' format indicates a 16-bit word stored with the LSBs
+   first]
+   
+The MDA (MicroDesign Area) file formats
+
+   There are two different MDA file formats. The earlier MicroDesign2
+   format uses a simple compression technique to reduce continuous areas
+   of white and black, while the more recent MicroDesign3 format uses a
+   more sophisticated technique which generally results in smaller disc
+   files.
+   
+   MicroDesign2, ProSCAN and Tweak (versions 1 and 2) can only load and
+   save the earlier format, but either format may be loaded or saved in
+   MicroDesign3. In MD3, the filetype is detected automatically on load,
+   but the user must choose whether to save in 'AREA2' or 'AREA3' format.
+   
+   The format is identified in byte 21 of the initial file 'stamp' record
+   - for a MicroDesign2 area this byte is "0" (#30), whereas for a
+   MicroDesign3 area it is "3" (#33).
+   
+   When loaded into memory and uncompressed an Area file can occupy up
+   the 720k of data, but its size on disc is indeterminate due to the
+   compression used. Because of the compression it is not possible to
+   perform 'random-access' reads or writes to the disc file - it must be
+   read sequentially in order correctly to decompress the data.
+   
+   The older MicroDesign2 Area file format is as follows:
+   
+Bytes 0..127: file 'stamp':
+    0 -   3 (#00 - #03)  .MDA            File Type  (4 bytes)
+    4 -  17 (#04 - #11)  MicroDesignPCW  Program Identifier (14 bytes)
+   18 -  22 (#12 - #16)  v1.00           File Version  (5 bytes)
+   23 -  24 (#17 - #18)  CR,LF           ie. 13,10 decimal (2 bytes)
+   25 -  31 (#19 - #1F)  xxxxxxx         User Serial No (ASCII) (7 bytes)
+   32 -  33 (#20 - #21)  CR,LF           ie. 13,10 decimal (2 bytes)
+   34 - 127 (#22 - #7F)                  fill with zeroes (#00) (94 bytes)
+
+Bytes 128..: file proper:
+  128 - 129 (#80 - #81)  Height in Lines (multiple of 4) (word)
+  130 - 131 (#82 - #83)  Width in Bytes (Pixels * 8) (word)
+  132 -     (#84 -    )  Bit-Image Data as follows...
+
+   Bytes read from left to right in lines, top line first.
+   
+   Each byte is standard 1-bit-per-pixel layout where MSB = LH pixel, LSB
+   = RH pixel, 1 = white ('on'), and 0 = black ('off'). Each #00 (all
+   black) or #FF (all white) byte is followed by a 'count' byte (ie. #00
+   #03 means 3 whole bytes width of solid black; #FF #A0 means 160 bytes
+   width of solid white). A value #00 for the count byte means 256. This
+   'count' can overrun into the next (several) lines.
+   
+   For example: (. represents black, # white)
+   
+    ....#### ##..##.. ####.... ........ ..###### ######## ########
+       0F       CC       F0      00,01     3F          FF,02
+
+    ####.... ........ ........ ........ ........ ........ ........
+       F0                            00,06
+
+   Because of this compression the file length is indeterminate, but
+   there must be HEIGHT * WIDTH bytes of actual image data by the time it
+   has been uncompressed.
+   
+   The newer MicroDesign3 Area file format is as follows:
+   
+Bytes 0..127: file 'stamp':
+    0 -   3 (#00 - #03)  .MDA            File Type  (4 bytes)
+    4 -  17 (#04 - #11)  MicroDesignPCW  Program Identifier (14 bytes)
+   18 -  22 (#12 - #16)  v1.30           File Version  (5 bytes)
+   23 -  24 (#17 - #18)  CR,LF           ie. 13,10 decimal (2 bytes)
+   25 -  31 (#19 - #1F)  xxxxxxx         User Serial No (ASCII) (7 bytes)
+   32 -  33 (#20 - #21)  CR,LF           ie. 13,10 decimal (2 bytes)
+   34 - 127 (#22 - #7F)                  fill with zeroes (#00) (94 bytes)
+
+Bytes 128..: file proper:
+  128 - 129 (#80 - #81)  Height in Lines (multiple of 4) (word)
+  130 - 131 (#82 - #83)  Width in Bytes (Pixels * 8) (word)
+  132 -     (#84 -    )  Bit-Image Data as follows...
+
+   Bytes read from left to right in lines, top line first.
+   
+   Each byte is standard 1-bit-per-pixel layout where MSB = LH pixel, LSB
+   = RH pixel, 1 = white ('on'), and 0 = black ('off').
+   
+   Each line of data is compressed according to one of three 'line
+   types'. The first byte of data for each line is the line type.
+   
+Line type byte:
+  #00: Line is ALL-SAME-BYTE type
+  #01: Line is DATA type
+  #02: Line is DIFFERENCE DATA type
+
+   The actual data for each line follows this type byte:
+   
+   ALL-SAME-BYTE type:
+          One more byte follows the initial #00 type byte - this is the
+          actual bit-image data with which to fill the whole line width.
+          
+          eg. Whole line of white = #00 #FF, whole line of black = #00
+          #00
+          
+   DATA type:
+          Following the initial #01 type byte, the data content of the
+          line is compressed as follows:
+          
+          Data is encoded in 'blocks', which are of *either* repeating
+          data bytes *or* non-repeating bytes. Each block starts with a
+          control byte, which determines whether the data which follows
+          it is *either* just one data byte to be repeated *or* a
+          sequence of dissimilar bytes.
+          
+          If the control byte N is negative (-1 to -127 in two's
+          complement - #FF for -1, #81 for -127), *one* data byte follows
+          which is to be repeated -N times. This means that there are to
+          be a total of (-N+1) occurrences of this data byte.
+          
+          If the control byte N is positive (0 to 127; #00 to #7F), it is
+          followed by (N+1) bytes of dissimilar data to load directly
+          into the line.
+          
+          For instance: 01 (line type), then:
+          
+....#### ##..##.. ####.... ........ ######## ######## ########
+          03,0F,CC,F0,00                       FE,FF
+
+#####... #.#.#.#. #.#.#.#. #.#.#.#. #.#.#.#. #.#..... ........
+  00,F8                 FD,AA                     01,A0,00
+
+          Note: the POSITIVE control bytes are 1 LESS than the number of
+          dissimilar bytes following them; NEGATIVE ones are (MINUS) 1
+          LESS than the number of occurrences of the byte following them.
+          
+   DIFFERENCE DATA type:
+          Following the initial #02 type byte, the difference between the
+          data content of this line and the content of the previous line
+          is stored: ie. this line is XORed with the previous line to
+          produce a 'difference' line, which is then compressed using the
+          same method as for a DATA type line.
+          
+          For instance, the following line stored as a 'difference' line
+          from the line above would be 02 (line type), then: (the second
+          pixel row below shows the results of XORing the first two
+          lines)
+          
+....#### ##..##.. ##..##.. ......## ######## ######## ########
+........ ........ ..####.. ......## ........ ........ ........
+      FF,00           01,3C,03                 FE,00
+
+######.. #.#.#.#. #.#.#.#. #.#.#.#. #.#.#.#. #.#.#.#. ........
+.....#.. ........ ........ ........ ........ ....#.#. ........
+ 00,04                  FD,00                     01,0A,00
+
+          Note: this DIFFERENCE encoding is used if it will result in
+          less bytes of data being stored in the file. The line type of
+          the previous line is irrelevant.
+          
+   Because of these various types of compression the file length on the
+   disc is indeterminate, but there must be HEIGHT * WIDTH bytes of
+   actual image data by the time it has been uncompressed.
+   
+The MDP (MicroDesign Page) file formats
+
+   MicroDesign3's Page (.MDP) files are identical to its Area (.MDA)
+   files except for the following differences:
+   
+Bytes 0..127: file 'stamp':
+    0 -   3 (#00 - #03)  .MDP            File Type  (4 bytes)
+   34       (#22)        nn: dpi         00 = 240dpi
+                                         01 = 360dpi
+                                         02 = 300dpi
+   35       (#23)        nn: format      00 = A5 portrait
+                                         01 = A5 landscape
+                                         02 = A4 portrait
+                                         03 = A4 landscape
+                                         04 = A5 portrait (hi-res)
+                                         05 = A5 landscape (hi-res)
+   36       (#24)        nn: page ram required in 16k blocks
+
+   In all other respects a Page (.MDP) file is identical to an Area
+   (.MDA) file (MD3 type).
+   
+The CUT image format
+
+   (Note: this information is in no way 'official' and results from my
+   own dabblings; it is separate from the above information on the MDA
+   format and is included here for convenience. No warranty expressed or
+   implied)
+   
+   CUT files have a format as follows:
+   
+    0 -   1 (#00 - #01)  Height code h1 (word) (see below)
+    2 -   3 (#02 - #03)  Width code w1 (word) (see below)
+    4 -     (#04 -    )  Bitmap data, row-by-row
+
+   The height in pixels h1 can be calculated from the height code h using
+   h = (h1+3)/2; the width in pixels is w = w1+2; the number of whole
+   bytes per row in the file is wb = INT((w+8)/8). Bitmap data in the
+   file is stored row-by-row, with a whole number of bytes per row; the
+   LSBs of the last byte that extend beyond the right edge of the image
+   should be discarded. As usual in bitmap data the MSB is towards the
+   left and the LSB towards the right.
+   
+   Note: the formula above for wb implies that if the image is a whole
+   multiple of 8 pixels wide, none of the bits from the last byte in the
+   row will be used. Strange, but there you go.
+   
+The GRF image format
+
+   (Same disclaimer applies; this is all from experiment)
+   
+   GRF files are substantially similar to CUTs (although slightly more
+   logical) and have a format as follows:
+   
+    0 -   1 (#00 - #01)  Width in pixels (word)
+    2 -   3 (#02 - #03)  Height in pixels (word)
+    4 -     (#04 -    )  Bitmap data, row-by-row
+
+   The number of bytes per row is simply the width in pixels divided by
+   8, rounded up; no catches here. As with CUTs unused rightmost bits
+   should be discarded.
+     _________________________________________________________________
+                                      
+   
+    Go to CP/M page or main page
+    Last updated 16 April 1997; Jacob Nevins
diff --git a/converter/pbm/mdatopbm.c b/converter/pbm/mdatopbm.c
new file mode 100644
index 00000000..d8e06572
--- /dev/null
+++ b/converter/pbm/mdatopbm.c
@@ -0,0 +1,271 @@
+
+/***************************************************************************
+
+    MDATOPBM: Convert Microdesign area to portable bitmap
+    Copyright (C) 1999 John Elliott <jce@seasip.demon.co.uk>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+    See the file mdaspec.txt for a specification of the MDA format.
+******************************************************************************/
+
+#include <string.h>
+#include <stdio.h>
+#include "pbm.h"
+#include "mallocvar.h"
+
+/* Simple MDA -> portable bitmap converter */
+
+typedef unsigned char mdbyte;   /* Must be exactly one byte */
+
+static FILE *infile;            /* Input file */
+static mdbyte header[128];      /* MDA file header */
+static bit **data;          /* PBM image */
+static mdbyte *mdrow;           /* MDA row after decompression (MD3 only) */
+static int bInvert = 0;     /* Invert image? */
+static int bScale  = 0;     /* Scale image? */
+static int bAscii  = 0;     /* Ouput ASCII PBM? */
+static int nInRows, nInCols;        /* Height, width of input (rows x bytes) */
+static int nOutCols, nOutRows;      /* Height, width of output (rows x bytes) */
+
+static mdbyte 
+getbyte(void) {
+    /* Read a byte from the input stream, with error trapping */
+    int b;
+
+    b = fgetc(infile);
+
+    if (b == EOF) pm_error("Unexpected end of MDA file\n");
+    
+    return (mdbyte)b;
+}
+
+
+
+static void 
+render_byte(int *col, int *xp, int *yp, int b) {
+
+/* Convert a byte to 8 cells in the destination bitmap 
+ *
+ * *col = source column
+ * *xp  = destination column
+ * *yp  = destination row
+ *  b   = byte to draw
+ *
+ * Will update *col, *xp and *yp to point to the next bit of the row.
+ */
+
+    int mask = 0x80;
+    int n;
+    int y3 = *yp;
+
+    if (bScale) y3 *= 2;
+
+    if (y3 >= nOutRows) return;
+
+    for (n = 0; n < 8; ++n) {
+        if (bInvert) data[y3][*xp] = (b & mask) ? PBM_BLACK : PBM_WHITE;
+        else         data[y3][*xp] = (b & mask) ? PBM_WHITE : PBM_BLACK;
+        mask = mask >> 1;
+        if (bScale) data[y3+1][*xp] = data[y3][*xp];
+        ++(*xp);
+    }
+    ++(*col);       /* Next byte */
+    if ((*col) >= nInCols) {
+        /* Onto next line? */
+        *col = 0;
+        *xp = 0;
+        ++(*yp);
+    }
+}
+
+
+static void 
+md2_trans(void) {
+    /* Convert a MicroDesign 2 area to PBM */
+    /* MD2 has RLE encoding that may go over */
+
+    int x1, y1, col;    /* multiple lines. */
+    mdbyte b;
+    int c;
+
+    x1 = y1 = col = 0;
+
+    while (y1 < nInRows) {
+        b = getbyte();
+    
+        if (b == 0 || b == 0xFF) {
+            /* RLE sequence */
+            c = getbyte();
+            if (c == 0) c = 256;
+            while (c > 0) { 
+                render_byte(&col, &x1, &y1, b); 
+                --c; 
+            }
+        }
+        else 
+            render_byte(&col, &x1, &y1, b);    /* Not RLE */
+    }
+}
+
+
+
+static void 
+md3_trans(void) {
+    /* Convert MD3 file. MD3 are encoded as rows, and 
+       there are three types. 
+    */
+    int x1, y1, col;
+    mdbyte b;
+    int c, d, n;
+
+    for (y1 = 0; y1 < nInRows; ++y1) {
+        b = getbyte();   /* Row type */
+        switch(b)  {
+        case 0: /* All the same byte */
+            c = getbyte();
+            for (n = 0; n < nInCols; n++) 
+                mdrow[n] = c;
+            break;
+            
+        case 1:      /* Encoded data */
+        case 2: col = 0; /* Encoded as XOR with previous row */
+            while (col < nInCols) {
+                c = getbyte();
+                if (c >= 129) {
+                    /* RLE sequence */
+                    c = 257 - c;
+                    d = getbyte();
+                    for (n = 0; n < c; ++n) {
+                        if (b == 1) 
+                            mdrow[col++] = d;
+                        else 
+                            mdrow[col++] ^= d;
+                    }   
+                } else {
+                    /* not RLE sequence */
+                        ++c;
+                        for (n = 0; n < c; ++n) {
+                            d = getbyte();
+                            if (b == 1) 
+                                mdrow[col++] = d;
+                            else
+                                mdrow[col++] ^= d;
+                        }
+                } 
+            }
+        }
+        /* Row loaded. Convert it. */
+        x1 = 0; col = 0; 
+        for (n = 0; n < nInCols; ++n) {
+            d  = y1;
+            render_byte(&col, &x1, &d, mdrow[n]);
+        }
+    }
+}
+
+
+
+static void 
+usage(char *s) {        
+    printf("mdatopbm v1.00, Copyright (C) 1999 "
+           "John Elliott <jce@seasip.demon.co.uk>\n"
+           "This program is redistributable under the terms of "
+           "the GNU General Public\n"
+           "License, version 2 or later.\n\n"
+           "Usage: %s [ -a ] [ -d ] [ -i ] [ -- ] [ infile ]\n\n"
+           "-a: Output an ASCII pbm file\n"
+           "-d: Double height (to compensate for the PCW aspect ratio)\n"
+           "-i: Invert colors\n"
+           "--: No more options (use if filename begins with a dash)\n",
+           s);
+
+    exit(0);
+}
+
+
+
+int 
+main(int argc, char **argv) {
+    int n, optstop = 0;
+    char *fname = NULL;
+
+    pbm_init(&argc, argv);
+
+    /* Parse options */
+
+    for (n = 1; n < argc; ++n) {
+        if (argv[n][0] == '-' && !optstop) {   
+            if (argv[n][1] == 'a' || argv[n][1] == 'A') bAscii = 1;
+            if (argv[n][1] == 'd' || argv[n][1] == 'D') bScale = 1;
+            if (argv[n][1] == 'i' || argv[n][1] == 'I') bInvert = 1;
+            if (argv[n][1] == 'h' || argv[n][1] == 'H') usage(argv[0]);
+            if (argv[n][1] == '-' && argv[n][2] == 0 && !fname) {
+                /* "--" */
+                optstop = 1;
+            }
+            if (argv[n][1] == '-' && (argv[n][2] == 'h' || argv[n][2] == 'H'))
+                usage(argv[0]);
+        }
+        else if (argv[n][0] && !fname) {
+            /* Filename */
+            fname = argv[n];
+        }
+    }
+
+    if (fname) 
+        infile = pm_openr(fname);
+    else
+        infile = stdin;
+
+    /* Read MDA file header */
+
+    if (fread(header, 1, 128, infile) < 128)
+        pm_error("Not a .MDA file\n");
+
+    if (strncmp((char*) header, ".MDA", 4) && 
+        strncmp((char*) header, ".MDP", 4))
+        pm_error("Not a .MDA file\n");
+
+    {
+        short yy;
+        pm_readlittleshort(infile, &yy); nInRows = yy;
+        pm_readlittleshort(infile, &yy); nInCols = yy;
+    }
+    
+    nOutCols = 8 * nInCols;
+    nOutRows = nInRows;
+    if (bScale) 
+        nOutRows *= 2;
+
+    data = pbm_allocarray(nOutCols, nOutRows);
+    
+    MALLOCARRAY_NOFAIL(mdrow, nInCols);
+
+    if (header[21] == '0') 
+        md2_trans();
+    else
+        md3_trans();
+
+    pbm_writepbm(stdout, data, nInCols*8, nOutRows, bAscii);
+
+    if (infile != stdin) 
+        pm_close(infile);
+    fflush(stdout);
+    pbm_freearray(data, nOutRows);
+    free(mdrow);
+
+    return 0;
+}
diff --git a/converter/pbm/mgr.h b/converter/pbm/mgr.h
new file mode 100644
index 00000000..eeb39d4f
--- /dev/null
+++ b/converter/pbm/mgr.h
@@ -0,0 +1,25 @@
+/* mgr.h - the following defs are taken from the MGR header file lib/dump.h
+*/
+
+#ifndef MGR_H_INCLUDED
+#define MGR_H_INCLUDED
+
+struct old_b_header {
+   char magic[2];
+   char h_wide;
+   char l_wide;
+   char h_high;
+   char l_high;
+   };
+
+struct b_header {
+   char magic[2];
+   char h_wide;
+   char l_wide;
+   char h_high;
+   char l_high;
+   char depth;
+   char _reserved;
+   };
+
+#endif
diff --git a/converter/pbm/mgrtopbm.c b/converter/pbm/mgrtopbm.c
new file mode 100644
index 00000000..cea4be48
--- /dev/null
+++ b/converter/pbm/mgrtopbm.c
@@ -0,0 +1,145 @@
+/* mgrtopbm.c - read a MGR bitmap and produce a portable bitmap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+#include <errno.h>
+
+#include "pbm.h"
+#include "mgr.h"
+
+
+static unsigned char item;
+static int bitsperitem, bitshift;
+
+static void
+getinit(FILE * const file, 
+        int *  const colsP, 
+        int *  const rowsP, 
+        int *  const depthP, 
+        int *  const padrightP,
+        int *  const bitsperitemP) {
+
+    struct b_header head;
+    int pad;
+
+    if (fread(&head, sizeof(struct old_b_header), 1, file ) != 1)
+        pm_error("Unable to read 1st byte of file.  "
+                 "fread() returns errno %d (%s)",
+                 errno, strerror(errno));
+    if (head.magic[0] == 'y' && head.magic[1] == 'z') { 
+        /* new style bitmap */
+        if (fread(&head.depth, 
+                  sizeof(head) - sizeof(struct old_b_header), 1, file) 
+             != 1 )
+            pm_error("Unable to read header after 1st byte.  "
+                     "fread() returns errno %d (%s)",
+                     errno, strerror(errno));
+        *depthP = (int) head.depth - ' ';
+        pad = 8;
+    } else if (head.magic[0] == 'x' && head.magic[1] == 'z') { 
+        /* old style bitmap with 32-bit padding */
+        *depthP = 1;
+        pad = 32;
+    } else if (head.magic[0] == 'z' && head.magic[1] == 'z') { 
+        /* old style bitmap with 16-bit padding */
+        *depthP = 1;
+        pad = 16;
+    } else if (head.magic[0] == 'z' && head.magic[1] == 'y') {
+        /* old style 8-bit pixmap with 16-bit padding */
+        *depthP = 8;
+        pad = 16;
+    } else {
+        pm_error("bad magic chars in MGR file: '%c%c'",
+                 head.magic[0], head.magic[1] );
+        pad = -1;  /* should never reach here */
+    }
+
+    if (head.h_wide < ' ' || head.l_wide < ' ')
+        pm_error("Invalid width field in MGR header");
+    if (head.h_high < ' ' || head.l_high < ' ')
+        pm_error("Invalid width field in MGR header");
+    
+    *colsP = (((int)head.h_wide - ' ') << 6) + ((int)head.l_wide - ' ');
+    *rowsP = (((int)head.h_high - ' ') << 6) + ((int) head.l_high - ' ');
+    *padrightP = ( ( *colsP + pad - 1 ) / pad ) * pad - *colsP;
+    
+    *bitsperitemP = 8;
+}
+
+
+
+static bit
+getbit( file )
+    FILE* file;
+    {
+    bit b;
+
+    if ( bitsperitem == 8 )
+	{
+	item = getc( file );
+	bitsperitem = 0;
+	bitshift = 7;
+	}
+    bitsperitem++;
+    b = ( ( item >> bitshift) & 1 ) ? PBM_BLACK : PBM_WHITE;
+    bitshift--;
+    return b;
+    }
+
+
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    bit* bitrow;
+    register bit* bP;
+    int rows, cols, depth, padright, row, col;
+
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[mgrfile]" );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    getinit( ifp, &cols, &rows, &depth, &padright, &bitsperitem );
+    if ( depth != 1 )
+	pm_error( "MGR file has depth of %d, must be 1", depth );
+
+    pbm_writepbminit( stdout, cols, rows, 0 );
+    bitrow = pbm_allocrow( cols );
+
+    for ( row = 0; row < rows; row++ )
+	{
+	/* Get data, bit-reversed within each byte. */
+        for ( col = 0, bP = bitrow; col < cols; col++, bP++ )
+	    *bP = getbit( ifp );
+	/* Discard line padding */
+        for ( col = 0; col < padright; col ++ )
+	    (void) getbit( ifp );
+	pbm_writepbmrow( stdout, bitrow, cols, 0 );
+	}
+
+    pm_close( ifp );
+    pm_close( stdout );
+
+    exit( 0 );
+    }
+
+
diff --git a/converter/pbm/mrftopbm.c b/converter/pbm/mrftopbm.c
new file mode 100644
index 00000000..696fe839
--- /dev/null
+++ b/converter/pbm/mrftopbm.c
@@ -0,0 +1,210 @@
+/* mrftopbm - convert mrf to pbm
+ * public domain by RJM
+ *
+ * Adapted to Netpbm by Bryan Henderson 2003.08.09.  Bryan got his copy from
+ * ftp://ibiblio.org/pub/linux/apps/convert, dated 1997.08.19.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "pm_c_util.h"
+#include "nstring.h"
+#include "pbm.h"
+
+
+static int bitbox;
+static int bitsleft;
+
+
+static void 
+bit_init(void) {
+    bitbox=0; 
+    bitsleft=0;
+}
+
+
+
+static int 
+bit_input(FILE * const in) {
+    if (bitsleft == 0)   {
+        bitbox = fgetc(in);
+        bitsleft = 8;
+    }
+    --bitsleft;
+    return((bitbox&(1<<bitsleft))?1:0);
+}
+
+
+
+static void 
+doSquare(FILE *          const in,
+          unsigned char * const image,
+          int             const ox,
+          int             const oy,
+          int             const w,
+          int             const size) {
+
+    if (size == 1 || bit_input(in)) { 
+        /* It's all black or all white.  Next bit says which. */
+
+        unsigned int const c = bit_input(in);
+
+        unsigned int y;
+
+        for (y = 0; y < size; ++y) {
+            unsigned int x;
+            for (x = 0; x < size; ++x)
+                image[(oy+y)*w+ox+x] = c;
+        }
+    } else {
+        /* not all one color, so recurse. */
+        doSquare(in, image, ox,      oy,     w, size >> 1);
+        doSquare(in, image, ox+size, oy,     w, size >> 1);
+        doSquare(in, image, ox,      oy+size,w, size >> 1);
+        doSquare(in, image, ox+size, oy+size,w, size >> 1);
+    }
+}
+
+
+
+static void
+writeOutput(FILE *                const ofP,
+            int                   const cols,
+            int                   const rows,
+            const unsigned char * const image) {
+            
+    /* w64 is units-of-64-bits width, h64 same for height */
+    unsigned int const w64 = (cols+63)/64;
+
+    bit * bitrow;
+    unsigned int row;
+
+    pbm_writepbminit(ofP, cols, rows, FALSE);
+
+    bitrow = pbm_allocrow(cols);
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+     
+        for (col = 0; col < cols; ++col)
+            bitrow[col] = 
+                (image[row * (w64*64) + col] == 1) ? PBM_WHITE : PBM_BLACK;
+
+        pbm_writepbmrow(ofP, bitrow, cols, FALSE);
+    }
+    pbm_freerow(bitrow);
+}
+
+
+
+static void
+readMrfImage(FILE *           const ifP,
+             bool             const expandAll,
+             unsigned char ** const imageP,
+             unsigned int *   const colsP,
+             unsigned int *   const rowsP) {
+
+    static unsigned char buf[128];
+    unsigned int rows;
+    unsigned int cols;
+    unsigned int w64, h64;
+
+    unsigned char * image;
+
+    fread(buf, 1, 13, ifP);
+
+    if (memcmp(buf, "MRF1", 4) != 0)
+        pm_error("Input is not an mrf image.  "
+                 "We know this because it does not start with 'MRF1'.");
+
+    if (buf[12] != 0)
+        pm_error("can't handle file subtype %u", buf[12]);
+
+    cols = (buf[4] << 24) | (buf[5] << 16) | (buf[06] << 8) | buf[07] << 0;
+    rows = (buf[8] << 24) | (buf[9] << 16) | (buf[10] << 8) | buf[11] << 0;
+
+    /* w64 is units-of-64-bits width, h64 same for height */
+    w64 = (cols+63)/64;
+    h64 = (rows+63)/64;
+    if (expandAll) {
+        *colsP = w64*64;
+        *rowsP = h64*64;
+    } else {
+        *colsP = cols;
+        *rowsP = rows;
+    }
+
+    if (UINT_MAX/w64/64/h64/64 == 0)
+        pm_error("Ridiculously large, unprocessable image: %u cols x %u rows",
+                 cols, rows);
+
+    image = calloc(w64*h64*64*64,1);
+    if (image == NULL)
+        pm_error("Unable to get memory for raster");
+                 
+    /* now recursively input squares. */
+
+    bit_init();
+
+    {
+        unsigned int row;
+        for (row = 0; row < h64; ++row) {
+            unsigned int col;
+            for (col = 0; col < w64; ++col)
+                doSquare(ifP, image, col*64, row*64, w64*64, 64);
+        }
+    }
+    *imageP = image;
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+
+    FILE *ifP;
+    FILE *ofP;
+    unsigned char *image;
+    bool expandAll;
+    unsigned int cols, rows;
+
+    pbm_init(&argc, argv);
+
+    expandAll = FALSE;  /* initial assumption */
+
+    if (argc-1 >= 1 && STREQ(argv[1], "-a")) {
+        expandAll = TRUE;
+        argc--,argv++;
+    }
+
+    if (argc-1 > 1)
+        pm_error("Too many arguments: %d.  Only argument is input file",
+                 argc-1);
+
+    if (argc-1 == 1) 
+        ifP = pm_openr(argv[1]);
+    else
+        ifP = stdin;
+
+    ofP = stdout;
+
+    readMrfImage(ifP, expandAll, &image, &cols, &rows);
+    
+    pm_close(ifP);
+    
+    writeOutput(ofP, cols, rows, image);
+
+    free(image);
+
+    return 0;
+}
+
+
+
+
+
+
diff --git a/converter/pbm/pbmto10x.c b/converter/pbm/pbmto10x.c
new file mode 100644
index 00000000..f8a38b5d
--- /dev/null
+++ b/converter/pbm/pbmto10x.c
@@ -0,0 +1,169 @@
+/* pbmto10x.c - read a portable bitmap and produce a Gemini 10X printer file
+**
+** Copyright (C) 1990, 1994 by Ken Yap
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** Modified to shorten stripes and eliminate blank stripes. Dec 1994.
+*/
+
+#include "pbm.h"
+#include "mallocvar.h"
+
+#define LOW_RES_ROWS    8       /* printed per pass */
+#define HIGH_RES_ROWS   16      /* printed per pass */
+
+static int  highres = 0;
+static FILE *ifp;
+static int  rows, cols, format;
+
+
+
+static void
+outstripe(char * const stripe, 
+          char * const sP, 
+          int    const reschar) {
+
+    int ncols;
+
+    char * p;
+
+    p = sP;  /* initial value */
+
+    /* scan backwards, removing empty columns */
+    while (p != stripe) 
+        if (*--p != 0) {
+            ++p;
+            break;
+        }
+    ncols = p - stripe;
+    if (ncols > 0) {
+        printf("\033%c%c%c", reschar, ncols % 256, ncols / 256);
+        fwrite(stripe, sizeof(char), ncols, stdout);
+    }
+    putchar('\n');          /* flush buffer */
+}
+
+
+
+static void
+res_60x72(void) {
+    int i, item, npins, row, col;
+    bit *bitrows[LOW_RES_ROWS], *bP[LOW_RES_ROWS];
+    char *stripe, *sP;
+
+    MALLOCARRAY(stripe, cols);
+    if (stripe == NULL)
+        pm_error("Unable to allocate %u bytes for a stripe buffer.",
+                 cols * sizeof(stripe[0]));
+    for (i = 0; i < LOW_RES_ROWS; ++i)
+        bitrows[i] = pbm_allocrow(cols);
+    printf("\033A\010");        /* '\n' = 8/72 */
+    for (row = 0, sP = stripe; row < rows; row += LOW_RES_ROWS, sP = stripe) {
+        if (row + LOW_RES_ROWS <= rows)
+            npins = LOW_RES_ROWS;
+        else
+            npins = rows - row;
+        for (i = 0; i < npins; ++i)
+            pbm_readpbmrow(ifp, bP[i] = bitrows[i], cols, format);
+        for (col = 0; col < cols; ++col) {
+            item = 0;
+            for (i = 0; i < npins; ++i)
+                if (*(bP[i]++) == PBM_BLACK)
+                    item |= 1 << (7 - i);
+            *sP++ = item;
+        }
+        outstripe(stripe, sP, 'K');
+    }
+    printf("\033@");
+    free(stripe);
+}
+
+
+
+static void
+res_120x144(void) {
+    int i, pin, item, npins, row, col;
+    bit *bitrows[HIGH_RES_ROWS], *bP[HIGH_RES_ROWS];
+    char *stripe, *sP;
+
+    MALLOCARRAY(stripe, cols);
+    if (stripe == NULL)
+        pm_error("Unable to allocate %u bytes for a stripe buffer.",
+                 cols * sizeof(stripe[0]));
+    for (i = 0; i < HIGH_RES_ROWS; ++i)
+        bitrows[i] = pbm_allocrow(cols);
+    printf("\0333\001");            /* \n = 1/144" */
+    for (row = 0, sP = stripe; row < rows; row += HIGH_RES_ROWS, sP = stripe) {
+        if (row + HIGH_RES_ROWS <= rows)
+            npins = HIGH_RES_ROWS;
+        else
+            npins = rows - row;
+        for (i = 0; i < npins; ++i)
+            pbm_readpbmrow(ifp, bP[i] = bitrows[i], cols, format);
+        for (col = 0; col < cols; ++col) {
+            item = 0;
+            /* even rows */
+            for (pin = i = 0; i < npins; i += 2, ++pin)
+                if (*(bP[i]++) == PBM_BLACK)
+                    item |= 1 << (7 - pin);
+            *sP++ = item;
+        }
+        outstripe(stripe, sP, 'L');
+        sP = stripe;
+        for (col = 0; col < cols; ++col) {
+            item = 0;
+            /* odd rows */
+            for (i = 1, pin = 0; i < npins; i += 2, ++pin)
+                if (*(bP[i]++) == PBM_BLACK)
+                    item |= 1 << (7 - pin);
+            *sP++ = item;
+        }
+        outstripe(stripe, sP, 'L');
+        printf("\033J\016");        /* 14/144 down, \n did 1/144 */
+    }
+    printf("\033@");
+    free(stripe);
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    const char * fname;
+
+    pbm_init( &argc, argv );
+
+    if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'h') {
+        highres = 1;
+        --argc;
+        ++argv;
+    }
+    if (argc-1 > 1)
+        pm_error("Too many arguments.  Only argument is file name");
+    else if (argc-1 == 1)
+        fname = argv[1];
+    else
+        fname = "-";
+    
+    ifp = pm_openr(fname);
+
+    pbm_readpbminit(ifp, &cols, &rows, &format);
+
+    if (highres)
+        res_120x144();
+    else
+        res_60x72();
+
+    pm_close(ifp);
+    exit(0);
+}
+
+
+
diff --git a/converter/pbm/pbmto4425.c b/converter/pbm/pbmto4425.c
new file mode 100644
index 00000000..605b12d5
--- /dev/null
+++ b/converter/pbm/pbmto4425.c
@@ -0,0 +1,179 @@
+#include <string.h>
+
+#include "nstring.h"
+#include "pbm.h"
+
+static char bit_table[2][3] = {
+{1, 4, 0x10},
+{2, 8, 0x40}
+};
+
+static int vmap_width;
+static int vmap_height;
+
+static int xres;
+static int yres;
+
+static char *vmap;
+
+
+static void
+init_map()
+{
+    int x, y;
+
+
+    for(x = 0; x < vmap_width; ++x)
+    {
+        for(y = 0; y < vmap_height; ++y)
+        {
+            vmap[y*(vmap_width) + x] = 0x20;
+        }
+    }
+}
+
+
+
+static void
+set_vmap(x, y)
+    int x, y;
+{
+    int ix, iy;
+
+    ix = x/2;
+    iy = y/3;
+
+    vmap[iy*(vmap_width) + ix] |= bit_table[x % 2][y % 3];
+}
+
+
+
+static void
+fill_map(pbmfp)
+    FILE *pbmfp;
+{
+    bit **pbm_image;
+    int cols;
+    int rows;
+    int x;
+    int y;
+
+    pbm_image = pbm_readpbm(pbmfp, &cols, &rows);
+    for(y = 0; y < rows && y < yres; ++y)
+    {
+        for(x = 0; x < cols && x < xres; ++x)
+        {
+            if(pbm_image[y][x] == PBM_WHITE)
+            {
+                set_vmap(x, y);
+            }
+        }
+    }
+}
+
+
+static void
+print_map()
+{
+    int x, y;
+    int last_byte;
+
+#ifdef BUFFERED
+    char *iobuf;
+    iobuf = (char *)malloc(BUFSIZ);
+    if(iobuf == NULL)
+    {
+        pm_message( "Can't allocate space for I/O buffer.  "
+                    "Using unbuffered I/O...\n" );
+        setbuf(stdout, NULL);
+    }
+    else
+    {
+        setbuf(stdout, iobuf);
+    }
+#endif
+
+    fputs("\033[H\033[J", stdout);	/* clear screen */
+    fputs("\033[?3h", stdout);	/* 132 column mode */
+    fputs("\033)}\016", stdout);	/* mosaic mode */
+
+    for(y = 0; y < vmap_height; ++y)
+    {
+        for(last_byte = vmap_width - 1;
+            last_byte >= 0
+                && vmap[y * vmap_width + last_byte] == 0x20;
+            --last_byte)
+            ;
+
+        for(x = 0; x <= last_byte; ++x)
+        {
+            fputc(vmap[y*(vmap_width) + x], stdout);
+        }
+        fputc('\n', stdout);
+    }
+
+    fputs("\033(B\017", stdout);
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+    int argn;
+    const char *pbmfile;
+    FILE *pbmfp;
+    const char *usage="[pbmfile]";
+
+    pbm_init( &argc, argv );
+    for(argn = 1;
+        argn < argc && argv[argn][0] == '-' && strlen(argv[argn]) > 1;
+        ++argn)
+    {
+        pm_usage(usage);
+    }
+
+    if(argn >= argc)
+    {
+        pbmfile = "-";
+    }
+    else if(argc - argn != 1)
+    {
+        pm_usage(usage);
+    }
+    else
+    {
+        pbmfile = argv[argn];
+    }
+
+    if(STREQ(pbmfile, "-"))
+    {
+        pbmfp = stdin;
+    }
+    else
+    {
+        pbmfp = pm_openr( argv[argn] );
+    }
+
+    vmap_width = 132;
+    vmap_height = 23;
+
+    xres = vmap_width * 2;
+    yres = vmap_height * 3;
+
+    vmap = malloc(vmap_width * vmap_height * sizeof(char));
+    if(vmap == NULL)
+	{
+        pm_error( "Cannot allocate memory" );
+	}
+
+    init_map();
+    fill_map(pbmfp);
+    print_map();
+    /* If the program failed, it previously aborted with nonzero completion
+       code, via various function calls.
+    */
+    return 0; 
+}
+
+
+
diff --git a/converter/pbm/pbmtoascii.c b/converter/pbm/pbmtoascii.c
new file mode 100644
index 00000000..0472f809
--- /dev/null
+++ b/converter/pbm/pbmtoascii.c
@@ -0,0 +1,165 @@
+/* pbmtoascii.c - read a portable bitmap and produce ASCII graphics
+**
+** Copyright (C) 1988, 1992 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+
+#define SQQ '\''
+#define BSQ '\\'
+
+/* Bit-map for 1x2 mode:
+**     1
+**     2
+*/
+static char carr1x2[4] = {
+/*   0    1    2    3   */
+    ' ', '"', 'o', 'M' };
+
+/* Bit-map for 2x4 mode (hex):
+**     1  2
+**     4  8
+**     10 20
+**     40 80
+** The idea here is first to preserve geometry, then to show density.
+*/
+#define D08 'M'
+#define D07 'H'
+#define D06 '&'
+#define D05 '$'
+#define D04 '?'
+static char carr2x4[256] = {
+/*0  1    2   3    4   5    6   7    8   9    A   B    C   D    E   F  */
+' ',SQQ, '`','"', '-',SQQ, SQQ,SQQ, '-','`', '`','`', '-','^', '^','"',/*00-0F*/
+'.',':', ':',':', '|','|', '/',D04, '/','>', '/','>', '~','+', '/','*',/*10-1F*/
+'.',':', ':',':', BSQ,BSQ, '<','<', '|',BSQ, '|',D04, '~',BSQ, '+','*',/*20-2F*/
+'-',':', ':',':', '~',D04, '<','<', '~','>', D04,'>', '=','b', 'd','#',/*30-3F*/
+'.',':', ':',':', ':','!', '/',D04, ':',':', '/',D04, ':',D04, D04,'P',/*40-4F*/
+',','i', '/',D04, '|','|', '|','T', '/',D04, '/','7', 'r','}', '/','P',/*50-5F*/
+',',':', ';',D04, '>',D04, 'S','S', '/',')', '|','7', '>',D05, D05,D06,/*60-6F*/
+'v',D04, D04,D05, '+','}', D05,'F', '/',D05, '/',D06, 'p','D', D06,D07,/*70-7F*/
+'.',':', ':',':', ':',BSQ, ':',D04, ':',BSQ, '!',D04, ':',D04, D04,D05,/*80-8F*/
+BSQ,BSQ, ':',D04, BSQ,'|', '(',D05, '<','%', D04,'Z', '<',D05, D05,D06,/*90-9F*/
+',',BSQ, 'i',D04, BSQ,BSQ, D04,BSQ, '|','|', '|','T', D04,BSQ, '4','9',/*A0-AF*/
+'v',D04, D04,D05, BSQ,BSQ, D05,D06, '+',D05, '{',D06, 'q',D06, D06,D07,/*B0-BF*/
+'_',':', ':',D04, ':',D04, D04,D05, ':',D04, D04,D05, ':',D05, D05,D06,/*C0-CF*/
+BSQ,D04, D04,D05, D04,'L', D05,'[', '<','Z', '/','Z', 'c','k', D06,'R',/*D0-DF*/
+',',D04, D04,D05, '>',BSQ, 'S','S', D04,D05, 'J',']', '>',D06, '1','9',/*E0-EF*/
+'o','b', 'd',D06, 'b','b', D06,'6', 'd',D06, 'd',D07, '#',D07, D07,D08 /*F0-FF*/
+    };
+
+
+
+int
+main( argc, argv )
+int argc;
+char* argv[];
+    {
+    FILE* ifp;
+    int argn, gridx, gridy, rows, cols, format;
+    int ccols, lastcol, row, subrow, subcol;
+    register int col, b;
+    bit* bitrow;
+    register bit* bP;
+    int* sig;
+    register int* sP;
+    char* line;
+    register char* lP;
+    char* carr;
+    const char* usage = "[-1x2|-2x4] [pbmfile]";
+
+    pbm_init( &argc, argv );
+
+    /* Set up default parameters. */
+    argn = 1;
+    gridx = 1;
+    gridy = 2;
+    carr = carr1x2;
+
+    /* Check for flags. */
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+	{
+	if ( pm_keymatch( argv[argn], "-1x2", 2 ) )
+	    {
+	    gridx = 1;
+	    gridy = 2;
+	    carr = carr1x2;
+	    }
+	else if ( pm_keymatch( argv[argn], "-2x4", 2 ) )
+	    {
+	    gridx = 2;
+	    gridy = 4;
+	    carr = carr2x4;
+	    }
+	else
+	    pm_usage( usage );
+	++argn;
+	}
+
+    if ( argn < argc )
+	{
+	ifp = pm_openr( argv[argn] );
+	++argn;
+	}
+    else
+	ifp = stdin;
+    
+    if ( argn != argc )
+        pm_usage( usage );
+
+    pbm_readpbminit( ifp, &cols, &rows, &format );
+    ccols = ( cols + gridx - 1 ) / gridx;
+    bitrow = pbm_allocrow( cols );
+    sig = (int*) pm_allocrow( ccols, sizeof(int) );
+    line = (char*) pm_allocrow( ccols + 1, sizeof(char) );
+
+    for ( row = 0; row < rows; row += gridy )
+	{
+	/* Get a character-row's worth of sigs. */
+	for ( col = 0; col < ccols; ++col )
+	    sig[col] = 0;
+	b = 1;
+	for ( subrow = 0; subrow < gridy; ++subrow )
+	    {
+	    if ( row + subrow < rows )
+		{
+		pbm_readpbmrow( ifp, bitrow, cols, format );
+		for ( subcol = 0; subcol < gridx; ++subcol )
+		    {
+		    for ( col = subcol, bP = &(bitrow[subcol]), sP = sig;
+			  col < cols;
+			  col += gridx, bP += gridx, ++sP )
+			if ( *bP == PBM_BLACK )
+			    *sP |= b;
+		    b <<= 1;
+		    }
+		}
+	    }
+	/* Ok, now remove trailing blanks.  */
+	for ( lastcol = ccols - 1; lastcol >= 0; --lastcol )
+	    if ( carr[sig[lastcol]] != ' ' )
+		break;
+	/* Copy chars to an array and print. */
+	for ( col = 0, sP = sig, lP = line; col <= lastcol; ++col, ++sP, ++lP )
+	    *lP = carr[*sP];
+	*lP++ = '\0';
+	puts( line );
+	}
+
+    pm_close( ifp );
+    pbm_freerow( bitrow );
+    pm_freerow( (char*) sig );
+    pm_freerow( (char*) line );
+
+    /* If the program failed, it previously aborted with nonzero completion
+       code, via various function calls.
+    */
+    return 0;
+    }
diff --git a/converter/pbm/pbmtoatk.c b/converter/pbm/pbmtoatk.c
new file mode 100644
index 00000000..de7adf63
--- /dev/null
+++ b/converter/pbm/pbmtoatk.c
@@ -0,0 +1,188 @@
+/* pbmtoatk.c - convert portable bitmap to Andrew Toolkit raster object
+**
+** Copyright (C) 1991 by Bill Janssen.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <stdio.h>
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "nstring.h"
+#include "pbm.h"
+
+#define DEFAULTSCALE (1<<16)
+#define RASTERVERSION 2
+
+
+static void
+write_atk_bytes(FILE *        const file, 
+                unsigned char const curbyte, 
+                unsigned int  const startcount) {
+
+    /* codes for data stream */
+    static unsigned char const whitezero   = 'f';
+    static unsigned char const whitetwenty = 'z';
+    static unsigned char const blackzero   = 'F';
+    static unsigned char const blacktwenty = 'Z';
+    static unsigned char const otherzero   = 0x1F;
+
+    #define WHITEBYTE 0x00
+    #define BLACKBYTE 0xFF
+
+    /* WriteRow table for conversion of a byte value to two character
+       hex representation 
+    */
+
+    static unsigned char hex[16] = {
+    '0', '1', '2', '3', '4', '5', '6', '7',
+    '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+    };
+
+    unsigned int curcount;
+
+    curcount = startcount;  /* initial value */
+
+    switch (curbyte) {
+    case WHITEBYTE:
+        while (curcount > 20) {
+            fputc(whitetwenty, file);
+            curcount -= 20;
+        }
+        fputc(whitezero + curcount, file);
+        break;
+    case BLACKBYTE:
+        while (curcount > 20) {
+            fputc(blacktwenty, file);
+            curcount -= 20;
+        }
+        fputc(blackzero + curcount, file);
+        break;
+    default:
+        while (curcount > 16) {
+            fputc(otherzero + 16, file);
+            fputc(hex[curbyte / 16], file);
+            fputc(hex[curbyte & 15], file);
+            curcount -= 16;
+        }
+        if (curcount > 1)
+            fputc(otherzero + curcount, file);
+        else ;  /* the byte written will represent a single instance */
+        fputc(hex[curbyte / 16], file);
+        fputc(hex[curbyte & 15], file);
+    }
+}
+
+
+
+static void
+process_atk_byte(int *           const pcurcount, 
+                 unsigned char * const pcurbyte, 
+                 FILE *          const file, 
+                 unsigned char   const newbyte, 
+                 int             const eolflag) {
+
+    int curcount;
+    unsigned char curbyte;
+
+    curcount = *pcurcount;  /* initial value */
+    curbyte  = *pcurbyte;  /* initial value */
+    
+    if (curcount < 1) {
+        *pcurbyte = curbyte = newbyte;
+        *pcurcount = curcount = 1;
+    } else if (newbyte == curbyte) {
+        *pcurcount = (curcount += 1);
+    }
+
+    if (curcount > 0 && newbyte != curbyte) {
+        write_atk_bytes (file, curbyte, curcount);
+        *pcurcount = 1;
+        *pcurbyte = newbyte;
+    }
+
+    if (eolflag) {
+        write_atk_bytes (file, *pcurbyte, *pcurcount);
+        fprintf(file, " |\n");
+        *pcurcount = 0;
+        *pcurbyte = 0;
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE *ifd;
+    bit *bitrow;
+    register bit *bP;
+    int rows, cols, format, row;
+    int col;
+    char name[100], *cp;
+    unsigned char curbyte, newbyte;
+    int curcount, gather;
+
+    pbm_init ( &argc, argv );
+
+    if (argc-1 > 1)
+        pm_error("Too many arguments.  Only argument is file name");
+
+    else if (argc-1 == 1) {
+        ifd = pm_openr( argv[1] );
+        strcpy(name, argv[1]);
+        if (STREQ( name, "-"))
+            strcpy(name, "noname");
+        
+        if ((cp = strchr(name, '.')) != 0)
+            *cp = '\0';
+    } else {
+        ifd = stdin;
+        strcpy( name, "noname" );
+    }
+
+    pbm_readpbminit(ifd, &cols, &rows, &format);
+    bitrow = pbm_allocrow(cols);
+
+    printf ("\\begindata{raster,%d}\n", 1);
+    printf ("%d %d %d %d ", RASTERVERSION, 0, DEFAULTSCALE, DEFAULTSCALE);
+    printf ("%d %d %d %d\n", 0, 0, cols, rows); /* subraster */
+    printf ("bits %d %d %d\n", 1, cols, rows);
+
+    for (row = 0; row < rows; ++row) {
+        pbm_readpbmrow(ifd, bitrow, cols, format);
+        bP = bitrow;
+        gather = 0;
+        newbyte = 0;
+        curbyte = 0;
+        curcount = 0;
+        col = 0;
+        while (col < cols) {
+            if (gather > 7) {
+                process_atk_byte (&curcount, &curbyte, stdout, newbyte, FALSE);
+                gather = 0;
+                newbyte = 0;
+            }
+            newbyte = (newbyte << 1) | (*bP++);
+            gather += 1;
+            col += 1;
+        }
+
+        if (gather > 0) {
+            newbyte = (newbyte << (8 - gather));
+            process_atk_byte (&curcount, &curbyte, stdout, newbyte, TRUE);
+        }
+    }
+
+    pm_close( ifd );
+    
+    printf ("\\enddata{raster, %d}\n", 1);
+
+    return 0;
+}
diff --git a/converter/pbm/pbmtobbnbg.c b/converter/pbm/pbmtobbnbg.c
new file mode 100644
index 00000000..e97ef4f2
--- /dev/null
+++ b/converter/pbm/pbmtobbnbg.c
@@ -0,0 +1,152 @@
+/* pbmtobg.c - read a portable bitmap and produce BitGraph graphics
+**
+** Copyright 1989 by Mike Parker.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+  
+/*
+** Changed to take advantage of negative Packed Pixed Data values and
+** supply ANSI-standard string terminator.  Paul Milazzo, 28 May 1990.
+*/
+
+#include "pbm.h"
+
+static void write16 ARGS(( unsigned int	));
+
+static int nco;
+
+int
+main(argc,argv)
+int argc;
+char **argv;
+{
+ int rows;
+ int cols;
+ int format;
+ bit *bitrow;
+ int row;
+ unsigned int sixteen;
+ int i;
+ unsigned int mask;
+ int op;
+ int x;
+ int y;
+
+
+ pbm_init( &argc, argv );
+
+ op = 3;
+ switch (argc)
+  { case 1:
+       break;
+    case 2:
+       op = atoi(argv[1]);
+       break;
+    case 3:
+       x = atoi(argv[1]);
+       y = atoi(argv[2]);
+       printf("\33:%d;%dm",x,y);
+       break;
+    case 4:
+       op = atoi(argv[1]);
+       x = atoi(argv[2]);
+       y = atoi(argv[3]);
+       printf("\33:%d;%dm",x,y);
+       break;
+  }
+ nco = 0;
+ pbm_readpbminit(stdin,&cols,&rows,&format);
+ printf("\33P:%d;%d;%ds\n",op,cols,rows);
+ bitrow = pbm_allocrow(cols);
+ for (row=0;row<rows;row++)
+  { pbm_readpbmrow(stdin,bitrow,cols,format);
+    sixteen = 0;
+    mask = 0x8000;
+    for (i=0;i<cols;i++)
+     { if (bitrow[i]==PBM_BLACK) sixteen |= mask;
+       mask >>= 1;
+       if (mask == 0)
+	{ mask = 0x8000;
+	  write16(sixteen);
+	  sixteen = 0;
+	}
+     }
+    if (mask != 0x8000)
+     { write16(sixteen);
+     }
+  }
+ puts("\033\\");
+ exit(0);
+}
+
+#ifdef POSITIVE_VALUES_ONLY
+static void
+write16(sixteen)
+unsigned int sixteen;
+{
+ if (nco > 75)
+  { putchar('\n');
+    nco = 0;
+  }
+ if (sixteen & 0xfc00)
+  { putchar(0100+(sixteen>>10));
+    nco ++;
+  }
+ if (sixteen & 0xfff0)
+  { putchar(0100+((sixteen>>4)&0x3f));
+    nco ++;
+  }
+ putchar(060+(sixteen&0xf));
+ nco ++;
+}
+#else
+/*
+ *  This version of "write16" uses negative Packed Pixel Data values to
+ *  represent numbers in the range 0x7fff--0xffff; negative values will
+ *  require fewer characters as they approach the upper end of that range.
+ */
+static void
+write16 (word)
+unsigned int	word;
+{
+    int		high;
+    int		mid;
+    int		low;
+    int		signChar;
+
+    if (nco > 75) {
+	putchar ('\n');
+	nco = 0;
+    }
+
+    if (word > 0x7fff) {
+	word = (unsigned int) (0x10000L - (long) word);
+	signChar = ' ';
+    }
+    else
+	signChar = '0';
+
+    high = (word >> 10) + '@';
+    mid	= ((word & 0x3f0) >> 4) + '@';
+    low	= (word & 0xf) + signChar;
+
+    if (high != '@') {
+	printf ("%c%c%c", high, mid, low);
+	nco += 3;
+    }
+    else if (mid != '@') {
+	printf ("%c%c", mid, low);
+	nco += 2;
+    }
+    else {
+	putchar (low);
+	nco++;
+    }
+}
+#endif
diff --git a/converter/pbm/pbmtocmuwm.c b/converter/pbm/pbmtocmuwm.c
new file mode 100644
index 00000000..64d7af40
--- /dev/null
+++ b/converter/pbm/pbmtocmuwm.c
@@ -0,0 +1,117 @@
+/* pbmtocmuwm.c - read a portable bitmap and produce a CMU window manager bitmap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+#include "cmuwm.h"
+
+static void putinit ARGS(( int rows, int cols ));
+static void putbit ARGS(( bit b ));
+static void putrest ARGS(( void ));
+static void putitem ARGS(( void ));
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    bit* bitrow;
+    register bit* bP;
+    int rows, cols, format, padright, row, col;
+
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[pbmfile]" );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    pbm_readpbminit( ifp, &cols, &rows, &format );
+    bitrow = pbm_allocrow( cols );
+    
+    /* Round cols up to the nearest multiple of 8. */
+    padright = ( ( cols + 7 ) / 8 ) * 8 - cols;
+
+    putinit( rows, cols );
+    for ( row = 0; row < rows; row++ )
+	{
+	pbm_readpbmrow( ifp, bitrow, cols, format );
+        for ( col = 0, bP = bitrow; col < cols; col++, bP++ )
+	    putbit( *bP );
+	for ( col = 0; col < padright; col++ )
+	    putbit( 0 );
+        }
+
+    pm_close( ifp );
+
+    putrest( );
+
+    exit( 0 );
+    }
+
+static unsigned char item;
+static int bitsperitem, bitshift;
+
+static void
+putinit( rows, cols )
+    int rows, cols;
+    {
+    if ( pm_writebiglong( stdout, CMUWM_MAGIC ) == -1 )
+	pm_error( "write error" );
+    if ( pm_writebiglong( stdout, cols ) == -1 )
+	pm_error( "write error" );
+    if ( pm_writebiglong( stdout, rows ) == -1 )
+	pm_error( "write error" );
+    if ( pm_writebigshort( stdout, (short) 1 ) == -1 )
+	pm_error( "write error" );
+
+    item = 0;
+    bitsperitem = 0;
+    bitshift = 7;
+    }
+
+#if __STDC__
+static void
+putbit( bit b )
+#else /*__STDC__*/
+static void
+putbit( b )
+    bit b;
+#endif /*__STDC__*/
+    {
+    if ( bitsperitem == 8 )
+	putitem( );
+    if ( b == PBM_WHITE )
+	item += 1 << bitshift;
+    bitsperitem++;
+    bitshift--;
+    }
+
+static void
+putrest( )
+    {
+    if ( bitsperitem > 0 )
+	putitem( );
+    }
+
+static void
+putitem( )
+    {
+    (void) putc( item, stdout );
+    item = 0;
+    bitsperitem = 0;
+    bitshift = 7;
+    }
diff --git a/converter/pbm/pbmtodjvurle.c b/converter/pbm/pbmtodjvurle.c
new file mode 100644
index 00000000..dbe96f31
--- /dev/null
+++ b/converter/pbm/pbmtodjvurle.c
@@ -0,0 +1,140 @@
+/*
+   Convert a PBM image into the DjVu Bitonal RLE format
+   described in the csepdjvu(1) documentation
+  
+   Copyright (c) 2004 Scott Pakin <scott+pbm@pakin.org>
+
+   All rights reserved.
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions
+   are met:
+   
+   1. Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+   2. Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+   3. The name of the author may not be used to endorse or promote products
+      derived from this software without specific prior written permission.
+   
+   THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
+   IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+   IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include "pbm.h"
+
+
+/* Write a byte to a file and check for errors. */
+static void
+writebyte(FILE *        const ofP,
+          unsigned char const c) {
+
+    if (fputc (c, ofP) == EOF)
+        pm_error ("failed to write to the RLE file.  Errno=%d (%s)",
+                  errno, strerror(errno));
+}
+
+
+/* Write a run length to the RLE file. */
+static void 
+write_rle (FILE *rlefile, uint32n tally)
+{
+  do {
+    /* Output a single run. */
+    if (tally < 192) {
+      /* Single-byte runs */
+      writebyte (rlefile, tally);
+      tally >>= 8;
+    }
+    else {
+      /* Two-byte runs */
+      writebyte (rlefile, ((tally>>8)&0x3F) + 0xC0);
+      writebyte (rlefile, tally&0xFF);
+      tally >>= 14;
+    }
+
+    /* Very large runs need to be split into smaller runs.  We
+     * therefore need to toggle back to the same color we had for the
+     * previous smaller run. */
+    if (tally > 0)
+      writebyte (rlefile, 0);
+  }
+  while (tally > 0);
+}
+
+
+
+int 
+main (int argc, char *argv[])
+{
+  FILE * const rlefile = stdout;    /* Generated Bitonal RLE file */
+
+  FILE *pbmfile;             /* PBM file to convert */
+  int numcols, numrows;      /* Width and height in pixels of the PBM file */
+  int format;                /* Original image type before conversion to PBM */
+  bit *pbmrow;               /* One row of the PBM file */
+  uint32n pixeltally = 0;    /* Run length of the current color */
+  int row, col;              /* Row and column loop variables */
+  const char * pbmfilename;  /* Name of input file */
+
+  /* Parse the command line. */
+  pbm_init (&argc, argv);
+
+  if (argc-1 < 1)
+      pbmfilename = "-";
+  else if (argc-1 == 1)
+      pbmfilename = argv[1];
+  else
+      pm_error("Program takes at most 1 argument -- the input file name.  "
+               "You specified %d", argc-1);
+
+  pbmfile = pm_openr(pbmfilename);
+
+  /* Write an RLE header. */
+  pbm_readpbminit (pbmfile, &numcols, &numrows, &format);
+  fprintf (rlefile, "R4\n");
+  fprintf (rlefile, "%d %d\n", numcols, numrows);
+
+  /* Write the RLE data. */
+  pbmrow = pbm_allocrow (numcols);
+  for (row=0; row<numrows; row++) {
+    bit prevpixel;        /* Previous pixel seen */
+
+    pbm_readpbmrow (pbmfile, pbmrow, numcols, format);
+    prevpixel = PBM_WHITE;   /* Bitonal RLE rows always start with white */
+    pixeltally = 0;
+    for (col=0; col<numcols; col++) {
+      bit newpixel = pbmrow[col];      /* Current pixel color */
+
+      if (newpixel == prevpixel)
+        pixeltally++;
+      else {
+        write_rle (rlefile, pixeltally);
+        pixeltally = 1;
+        prevpixel = newpixel;
+      }
+    }
+    write_rle (rlefile, pixeltally);
+  }
+
+  /* Finish up cleanly. */
+  pbm_freerow (pbmrow);
+  if (rlefile != stdout)
+    pm_close (rlefile);
+  if (pbmfile != stdin)
+    pm_close (pbmfile);
+  exit (0);
+}
diff --git a/converter/pbm/pbmtoepsi.c b/converter/pbm/pbmtoepsi.c
new file mode 100644
index 00000000..fc8cee7d
--- /dev/null
+++ b/converter/pbm/pbmtoepsi.c
@@ -0,0 +1,252 @@
+/* pbmtoepsi.c
+**
+**    by Doug Crabill, based heavily on pbmtoascii
+**
+**    Converts a pbm file to an encapsulated PostScript style bitmap.
+**    Note that it does NOT covert the pbm file to PostScript, only to
+**    a bitmap to be added to a piece of PostScript generated elsewhere.
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+#include "shhopt.h"
+
+#if !defined(MAXINT)
+#define MAXINT (0x7fffffff)
+#endif
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespecs of input files */
+
+    unsigned int dpiX;     /* horiz component of DPI option */
+    unsigned int dpiY;     /* vert component of DPI option */
+    unsigned int bbonly;
+
+    unsigned int verbose;
+};
+
+
+
+static void
+parse_dpi(char * const dpiOpt, 
+          unsigned int * const dpiXP, unsigned int * const dpiYP) {
+
+    char *dpistr2;
+    unsigned int dpiX, dpiY;
+
+    dpiX = strtol(dpiOpt, &dpistr2, 10);
+    if (dpistr2 == dpiOpt) 
+        pm_error("Invalid value for -dpi: '%s'.  Must be either number "
+                 "or NxN ", dpiOpt);
+    else {
+        if (*dpistr2 == '\0') {
+            *dpiXP = dpiX;
+            *dpiYP = dpiX;
+        } else if (*dpistr2 == 'x') {
+            char * dpistr3;
+
+            dpistr2++;  /* Move past 'x' */
+            dpiY = strtol(dpistr2, &dpistr3, 10);        
+            if (dpistr3 != dpistr2 && *dpistr3 == '\0') {
+                *dpiXP = dpiX;
+                *dpiYP = dpiY;
+            } else {
+                pm_error("Invalid value for -dpi: '%s'.  Must be either "
+                         "number or NxN", dpiOpt);
+            }
+        }
+    }
+}
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    char * dpiOpt;
+    unsigned int dpiOptSpec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "bbonly",     OPT_FLAG,   NULL, &cmdlineP->bbonly,        0);
+    OPTENT3(0, "verbose",    OPT_FLAG,   NULL, &cmdlineP->verbose,       0);
+    OPTENT3(0, "dpi",        OPT_STRING, &dpiOpt,         &dpiOptSpec,   0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+    
+
+    if (dpiOptSpec)
+        parse_dpi(dpiOpt, &cmdlineP->dpiX, &cmdlineP->dpiY);
+    else
+        cmdlineP->dpiX = cmdlineP->dpiY = 72;
+    
+    if ((argc-1) > 1)
+        pm_error("Too many arguments (%d).  Only argument is input filespec",
+                 argc-1);
+    
+    if (argc-1 == 0)
+        cmdlineP->inputFilespec = "-";
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+static void
+findPrincipalImage(bit ** const bits, 
+                   int    const rows,
+                   int    const cols,
+                   int *  const topP,
+                   int *  const bottomP,
+                   int *  const leftP,
+                   int *  const rightP) {
+
+    int top, bottom, left, right;
+    int row;
+
+    /* Initial values */
+    top = MAXINT;
+    bottom = -MAXINT;
+    left = MAXINT;
+    right = -MAXINT;
+ 
+    for (row = 0; row < rows; row++) {
+        int col;
+        for (col = 0; col < cols; col++) {
+            if (bits[row][col] == PBM_BLACK) {
+                if (row < top) 
+                    top = row;
+                if (row > bottom) 
+                    bottom = row;
+                if (col < left) 
+                    left = col;
+                if (col > right) 
+                    right = col;
+            }
+        }
+    }
+    *topP = top;
+    *bottomP = bottom;
+    *leftP = left;
+    *rightP = right;
+}
+
+
+
+static void
+outputBoundingBox(int const top, int const bottom,
+                  int const left, int const right,
+                  int const rows,
+                  unsigned int const dpiX, unsigned int const dpiY) {
+
+    float const xScale = 72.0 / dpiX;
+    float const yScale = 72.0 / dpiY;
+
+    printf("%%%%BoundingBox: %d %d %d %d\n", 
+           ROUND(left*xScale),  ROUND((rows - bottom)*yScale), 
+           ROUND(right*xScale), ROUND((rows - top)*yScale));
+}
+
+
+
+static unsigned char
+eightPixels(bit ** const bits,
+            int    const row,
+            int    const col,
+            int    const cols) {
+/*----------------------------------------------------------------------------
+  Compute a byte that represents the 8 pixels starting at Column 'col' of
+  row 'row' of the raster 'bits'.  The most significant bit of the result
+  represents the leftmost pixel, with 1 meaning black.
+
+  The row is 'cols' columns wide, so fill on the right with white if there
+  are not eight pixels in the row starting with Column 'col'.
+-----------------------------------------------------------------------------*/
+    unsigned int bitPos;
+    unsigned char octet;
+
+    octet = 0;  /* initial value */
+
+    for (bitPos = 0; bitPos < 8; ++bitPos) {
+        octet <<= 1;
+        if (col + bitPos < cols)
+            if (bits[row][col + bitPos] == PBM_BLACK)
+                octet += 1;
+    }
+    return(octet);
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE *ifP;
+    bit **bits;
+    int rows, cols;
+    int top, bottom, left, right;
+        /* boundaries of principal part of image -- i.e. excluding white
+           borders
+        */
+
+    pbm_init( &argc, argv );
+    
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+    bits = pbm_readpbm( ifP, &cols, &rows );
+    pm_close(ifP);
+
+    findPrincipalImage(bits, rows, cols, &top, &bottom, &left, &right);
+
+    printf("%%!PS-Adobe-2.0 EPSF-1.2\n");
+
+    outputBoundingBox(top, bottom, left, right, rows, 
+                      cmdline.dpiX, cmdline.dpiY);
+
+    if (!cmdline.bbonly) {
+        int row;
+        printf("%%%%BeginPreview: %d %d 1 %d\n", 
+               right - left + 1, bottom - top + 1, bottom - top + 1);
+
+        for (row = top; row <= bottom; row++) {
+            int col;
+
+            printf("%% ");
+
+            for (col = left; col <= right; col += 8) 
+                printf("%02x", eightPixels(bits, row, col, cols));
+
+            printf("\n");
+        }
+        printf("%%%%EndImage\n");
+        printf("%%%%EndPreview\n");
+    }
+    exit(0);
+}
diff --git a/converter/pbm/pbmtoepson.c b/converter/pbm/pbmtoepson.c
new file mode 100644
index 00000000..d26734d4
--- /dev/null
+++ b/converter/pbm/pbmtoepson.c
@@ -0,0 +1,338 @@
+/* pbmtoeps.c - read a PBM image and produce Epson graphics
+**
+** Copyright (C) 1990 by John Tiller (tiller@galois.msfc.nasa.gov)
+**			 and Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _BSD_SOURCE    /* Make sure strcasecmp() is in string.h */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "shhopt.h"
+#include "mallocvar.h"
+
+#include "pbm.h"
+
+
+static char const esc = 033;
+
+enum epsonProtocol {ESCP9, ESCP};
+
+enum adjacence {ADJACENT_ANY, ADJACENT_YES, ADJACENT_NO};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* '-' if stdin */
+    unsigned int dpi;  /* zero means "any" */
+    enum adjacence adjacence;
+    enum epsonProtocol protocol;
+};
+
+
+
+static void
+parseCommandLine(int                 argc, 
+                 char **             argv,
+                 struct cmdlineInfo *cmdlineP ) {
+/*----------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    char * protocol;
+    unsigned int adjacentSpec, nonadjacentSpec;
+    unsigned int dpiSpec, protocolSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "protocol",   OPT_UINT,   &protocol,
+            &protocolSpec,                    0);
+    OPTENT3(0, "dpi",        OPT_UINT,   &cmdlineP->dpi,
+            &dpiSpec,                    0);
+    OPTENT3(0, "adjacent",   OPT_FLAG,   NULL,
+            &adjacentSpec,                    0);
+    OPTENT3(0, "nonadjacent",   OPT_FLAG,   NULL,
+            &nonadjacentSpec,                    0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+
+    if (!dpiSpec)
+        cmdlineP->dpi = 0;
+    else {
+        if (cmdlineP->dpi == 0)
+            pm_error("-dpi must be positive");
+    }
+
+    if (!protocolSpec)
+        cmdlineP->protocol = ESCP9;
+    else {
+        if (strcasecmp(protocol, "escp9") == 0)
+            cmdlineP->protocol = ESCP9;
+        else if (strcasecmp(protocol, "escp") == 0)
+            cmdlineP->protocol = ESCP;
+        else if (strcasecmp(protocol, "escp2") == 0)
+            pm_error("This program cannot do ESC/P2.  Try Pbmtoescp2.");
+        else
+            pm_error("Unrecognized value '%s' for -protocol.  "
+                     "Only recognized values are 'escp9' and 'escp'",
+                     protocol);
+    }
+    
+    if (adjacentSpec && nonadjacentSpec)
+        pm_error("You can't specify both -adjacent and -nonadjacent");
+    else if (adjacentSpec)
+        cmdlineP->adjacence = ADJACENT_YES;
+    else if (nonadjacentSpec)
+        cmdlineP->adjacence = ADJACENT_NO;
+    else
+        cmdlineP->adjacence = ADJACENT_ANY;
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else {
+        cmdlineP->inputFilespec = argv[1];
+        if (argc-1 > 1)
+            pm_error("Too many arguments (%d).  The only non-option argument "
+                     "is the file name", argc-1);
+    }
+}
+
+
+
+static unsigned int
+lineWidth(const bit ** const stripeBits,
+          unsigned int const cols,
+          unsigned int const stripeRows) {
+/*----------------------------------------------------------------------------
+   Return the column number just past the rightmost column of the stripe
+   stripeBits[] that contains at least some black.
+
+   The stripe is 'cols' wide by 'stripeRows' high.
+-----------------------------------------------------------------------------*/
+    unsigned int col;
+    unsigned int endSoFar;
+    
+    endSoFar = 0;
+
+    for (col = 0; col < cols; ++ col) {
+        unsigned int stripeRow;  /* row number within stripe */
+
+        for (stripeRow = 0; stripeRow < stripeRows; ++stripeRow) {
+            if (stripeBits[stripeRow][col] == PBM_BLACK)
+                endSoFar = col+1;
+        }
+    }
+    return endSoFar;
+}
+
+
+
+static void
+printStripe(const bit ** const stripeBits,
+            unsigned int const cols,
+            unsigned int const stripeRows,
+            char         const m) {
+/*----------------------------------------------------------------------------
+   Print one stripe (a group of rows printed with one pass of the print
+   head.  The stripe is cols columns wide by stripeRows high.
+   stripeBits[row][col] is the pixel value for Row row, Column col within
+   the stripe.
+
+   'm' is the "m" parameter for the Select Bit Image command.  It controls
+   such things as the horizontal density.
+-----------------------------------------------------------------------------*/
+    unsigned int col;
+
+    /* Print header of Select Bit Image command */
+    printf("%c%c%c%c%c", esc, '*', m, cols % 256, cols / 256);
+    
+    /* Print the data part of the Select Bit Image command */
+    for (col = 0; col < cols; ++col) {
+        unsigned int stripeRow;
+        int val;
+        
+        val = 0;
+        for (stripeRow = 0; stripeRow < stripeRows; ++stripeRow) 
+            if (stripeBits[stripeRow][col] == PBM_BLACK)
+                val |= (1 << (8-1-stripeRow));
+        putchar(val);
+    }
+}
+
+
+
+static void
+computeM(enum epsonProtocol const protocol,
+         unsigned int       const dpi,
+         enum adjacence     const adjacence,
+         char *             const mP) {
+/*----------------------------------------------------------------------------
+   Compute the "m" parameter for the Select Bit Image command.
+-----------------------------------------------------------------------------*/
+    switch (dpi) {
+    case 0:
+        /* Special value meaning "any dpi you feel is appropriate" */
+        if (adjacence == ADJACENT_NO)
+            *mP = 2;
+        else {
+            switch (protocol) {
+            case ESCP9: *mP = 5; break;
+            case ESCP:  *mP = 6; break;
+            }
+        }
+        break;
+    case 60: 
+        if (adjacence == ADJACENT_NO)
+            pm_error("You can't print at %u dpi "
+                     "with adjacent dot printing", dpi);
+        *mP = 0;
+        break;
+    case 120:
+        *mP = adjacence == ADJACENT_NO ? 2 : 1;
+        break;
+    case 240:
+        if (adjacence == ADJACENT_YES)
+            pm_error("You can't print at %u dpi "
+                     "without adjacent dot printing", dpi);
+        *mP = 3;
+        break;
+    case 80:
+        if (adjacence == ADJACENT_NO)
+            pm_error("You can't print at %u dpi "
+                     "with adjacent dot printing", dpi);
+        *mP = 4;
+        break;
+    case 72:
+        if (protocol != ESCP9)
+            pm_error("%u dpi is possible only with the ESC/P 9-pin protocol", 
+                     dpi);
+        if (adjacence == ADJACENT_NO)
+            pm_error("You can't print at %u dpi "
+                     "with adjacent dot printing", dpi);
+        *mP = 5;
+        break;
+    case 90:
+        if (adjacence == ADJACENT_NO)
+            pm_error("You can't print at %u dpi "
+                     "with adjacent dot printing", dpi);
+        *mP = 6;
+        break;
+    case 144:
+        if (protocol != ESCP9)
+            pm_error("%u dpi is possible only with the ESC/P 9-pin protocol", 
+                     dpi);
+        if (adjacence == ADJACENT_NO)
+            pm_error("You can't print at %u dpi "
+                     "with adjacent dot printing", dpi);
+        *mP = 7;
+        break;
+    default:
+        pm_error("Invalid DPI value: %u.  This program knows only "
+                 "60, 72, 80, 90, 120, 144, and 240.", dpi);
+    }
+}
+
+
+
+static void
+convertToEpson(const bit **       const bits,
+               int                const cols,
+               int                const rows,
+               enum epsonProtocol const protocol,
+               unsigned int       const dpi,
+               enum adjacence     const adjacence) {
+    
+    unsigned int const rowsPerStripe = 8;
+    unsigned int const stripes = (rows + rowsPerStripe-1) / rowsPerStripe;
+
+    unsigned int stripe;
+    char m;
+    
+    computeM(protocol, dpi, adjacence, &m);
+
+    /* Change line spacing to 8/72 inches. */
+    printf("%c%c%c", esc, 'A', 8);
+
+    /* Write out the rows, one stripe at a time.  A stripe is 8 rows --
+       the amount written in one pass of the print head.  The bottommost
+       stripe can be fewer than 8 rows.
+    */
+
+    for (stripe = 0; stripe < stripes; ++stripe) {
+        const bit ** const stripeBits = &bits[stripe*rowsPerStripe];
+        unsigned int const stripeRows = 
+            MIN(rowsPerStripe, rows - stripe * rowsPerStripe);
+            /* Number of rows in this stripe (8 for all but bottom stripe) */
+        
+        unsigned int const endcol = lineWidth(stripeBits, cols, stripeRows);
+            /* Column where right margin (contiguous white area at right
+               end of stripe) begins.  Zero if entire stripe is white.
+            */
+
+        if (endcol > 0)
+            printStripe(stripeBits, endcol, stripeRows, m);
+
+        putchar('\n');
+    }
+    putchar('\f');
+
+    /* Restore normal line spacing. */
+    printf("%c%c", esc, '@');
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE* ifP;
+    const bit** bits;
+    int rows, cols;
+
+    pbm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    bits = (const bit **)pbm_readpbm(ifP, &cols, &rows);
+
+    pm_close(ifP);
+
+    convertToEpson(bits, cols, rows, 
+                   cmdline.protocol, cmdline.dpi, cmdline.adjacence);
+
+    pbm_freearray(bits, rows);
+
+    return 0;
+}
diff --git a/converter/pbm/pbmtoescp2.c b/converter/pbm/pbmtoescp2.c
new file mode 100644
index 00000000..787f7027
--- /dev/null
+++ b/converter/pbm/pbmtoescp2.c
@@ -0,0 +1,186 @@
+/* pbmtoescp2.c - read a portable bitmap and produce Epson ESC/P2 raster
+**                 graphics output data for Epson Stylus printers
+**
+** Copyright (C) 2003 by Ulrich Walcher (u.walcher@gmx.de)
+**                       and Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* I used the Epson ESC/P Reference Manual (1997) in writing this. */
+
+#include <string.h>
+
+#include "pbm.h"
+#include "shhopt.h"
+
+static char const esc = 033;
+
+struct cmdlineInfo {
+    const char * inputFileName;
+    unsigned int resolution;
+    unsigned int compress;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+
+    optStruct3 opt;
+    unsigned int option_def_index = 0;
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+
+    unsigned int compressSpec, resolutionSpec;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;
+    opt.allowNegNum = FALSE;
+    OPTENT3(0, "compress",     OPT_UINT,    &cmdlineP->compress,    
+            &compressSpec, 0);
+    OPTENT3(0, "resolution",   OPT_UINT,    &cmdlineP->resolution,  
+            &resolutionSpec, 0);
+    
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    
+    if (argc-1 > 1)
+        pm_error("Too many arguments: %d.  "
+                 "Only argument is the filename", argc-1);
+
+    if (compressSpec) {
+        if (cmdlineP->compress != 0 && cmdlineP->compress != 1)
+            pm_error("Invalid -compress value: %u.  Only 0 and 1 are valid.",
+                     cmdlineP->compress);
+    } else
+        cmdlineP->compress = 1;
+
+    if (resolutionSpec) {
+        if (cmdlineP->resolution != 360 && cmdlineP->resolution != 180)
+            pm_error("Invalid -resolution value: %u.  "
+                     "Only 180 and 360 are valid.", cmdlineP->resolution);
+    } else
+        cmdlineP->resolution = 360;
+
+    if (argc-1 == 1)
+        cmdlineP->inputFileName = argv[1];
+    else
+        cmdlineP->inputFileName = "-";
+}
+
+
+
+static unsigned int
+enc_epson_rle(unsigned int          const l, 
+              const unsigned char * const src, 
+              unsigned char *       const dest) {
+/*----------------------------------------------------------------------------
+  compress l data bytes from src to dest and return the compressed
+  length
+-----------------------------------------------------------------------------*/
+    unsigned int i;      /* index */
+    unsigned int state;  /* run state */
+    unsigned int pos;    /* source position */
+    unsigned int dpos;   /* destination position */
+
+    pos = dpos = state  = 0;
+    while ( pos < l )
+    {
+        for (i=0; i<128 && pos+i<l; i++)
+            /* search for begin of a run, smallest useful run is 3
+               equal bytes 
+            */
+            if(src[pos+i]==src[pos+i+1] && src[pos+i]==src[pos+i+2])
+            {
+                state=1;                       /* set run state */
+                break;
+            }
+	if(i)
+	{
+        /* set counter byte for copy through */
+        dest[dpos] = i-1;       
+        /* copy data bytes before run begin or end cond. */
+        memcpy(dest+dpos+1,src+pos,i);    
+        pos+=i; dpos+=i+1;                 /* update positions */
+	}
+    if (state)
+    {
+        for (i=0; src[pos+i]==src[pos+i+1] && i<128 && pos+i<l; i++);
+        /* found the runlength i */
+        dest[dpos]   = 257-i;           /* set counter for byte repetition */
+        dest[dpos+1] = src[pos];        /* set byte to be repeated */
+        pos+=i; dpos+=2; state=0;       /* update positions, reset run state */
+        }
+    }
+    return dpos;
+}
+
+
+
+int
+main(int argc, char* argv[]) {
+
+    FILE* ifP;
+    int rows, cols;
+    int format;
+    unsigned int row, idx, len;
+    unsigned int h, v;
+    unsigned char *bytes, *cprbytes;
+    struct cmdlineInfo cmdline;
+    
+    pbm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pbm_readpbminit(ifP, &cols, &rows, &format);
+
+    bytes = malloc(24*pbm_packed_bytes(cols)+2);
+    cprbytes = malloc(2*24*pbm_packed_bytes(cols));
+    if (bytes == NULL || cprbytes == NULL)
+        pm_error("Cannot allocate memory");
+
+    h = v = 3600/cmdline.resolution;
+
+    /* Set raster graphic mode. */
+    printf("%c%c%c%c%c%c", esc, '(', 'G', 1, 0, 1);
+
+    /* Set line spacing in units of 1/360 inches. */
+    printf("%c%c%c", esc, '+', 24*h/10);
+
+    /* Write out raster stripes 24 rows high. */
+    for (row = 0; row < rows; row += 24) {
+        unsigned int const linesThisStripe = (rows-row<24) ? rows%24 : 24;
+        printf("%c%c%c%c%c%c%c%c", esc, '.', cmdline.compress, 
+               v, h, linesThisStripe, 
+               cols%256, cols/256);
+        /* Read pbm rows, each padded to full byte */
+        for (idx = 0; idx < 24 && row+idx < rows; ++idx)
+            pbm_readpbmrow_packed(ifP,bytes+idx*pbm_packed_bytes(cols),
+                                  cols,format);
+        /* Write raster data. */
+        if (cmdline.compress != 0) {
+            /* compressed */
+            len = enc_epson_rle(linesThisStripe * pbm_packed_bytes(cols), 
+                                bytes, cprbytes);
+            fwrite(cprbytes,len,1,stdout);
+        } else
+            /* uncompressed */
+            fwrite(bytes, pbm_packed_bytes(cols), linesThisStripe, stdout);    
+
+        if (rows-row >= 24) putchar('\n');
+    }
+    free(bytes); free(cprbytes);
+    pm_close(ifP);
+
+    /* Reset printer. */
+    printf("%c%c", esc, '@');
+
+    return 0;
+}
diff --git a/converter/pbm/pbmtog3.c b/converter/pbm/pbmtog3.c
new file mode 100644
index 00000000..10db3afe
--- /dev/null
+++ b/converter/pbm/pbmtog3.c
@@ -0,0 +1,485 @@
+/* pbmtog3.c - read a PBM image and produce a Group 3 FAX file
+**
+** Copyright (C) 1989 by Paul Haeberli <paul@manray.sgi.com>.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/*
+   For specifications for Group 3 (G3) fax MH coding see ITU-T T.4
+   This program generates only MH.  It is coded with future expansion for
+   MR and MMR in mind.
+*/
+
+#include <assert.h>
+
+#include "pbm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "bitreverse.h"
+#include "wordaccess.h"
+#include "g3.h"
+
+#define TC_MC 64
+
+static bool const pbmtorl = 
+#ifdef PBMTORL
+    TRUE;
+#else
+    FALSE;
+#endif
+
+
+struct bitString {
+    /* A string of bits, up to as many fit in a word. */
+    unsigned int bitCount;
+        /* The length of the bit string */
+    wordint intBuffer;
+        /* The bits are in the 'bitCount' least significant bit positions 
+           of this number.  The rest of the bits of this number are always 
+           zero.
+        */
+
+    /* Example:  The bit string 010100, on a machine with a 32 bit word,
+       would be represented by bitCount = 6, intBuffer = 20
+       (N.B. 20 = 00000000 00000000 00000000 00010100 in binary)
+    */
+};
+
+
+
+struct outStream {
+    struct bitString buffer;
+    
+    bool reverseBits;
+};
+
+/* This is a global variable for speed. */
+static struct outStream out;
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;
+    unsigned int reversebits;
+    unsigned int nofixedwidth;
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions2 on how to parse our options.  */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "reversebits",      OPT_FLAG,  NULL, &cmdlineP->reversebits,
+            0);
+    OPTENT3(0,   "nofixedwidth",     OPT_FLAG,  NULL, &cmdlineP->nofixedwidth,
+            0);
+    OPTENT3(0,   "verbose",          OPT_FLAG,  NULL, &cmdlineP->verbose, 
+            0);
+
+    /* TODO
+       Explicit fixed widths: -A4 -B4 -A3
+    */
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = TRUE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 == 0) 
+        cmdlineP->inputFileName = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->inputFileName = argv[1];
+}
+
+
+
+static void
+reversebuffer(unsigned char * const p, 
+              unsigned int    const n) {
+
+    unsigned int i;
+    for (i = 0; i < n; ++i)
+        p[i] = bitreverse[p[i]];
+}
+
+
+
+static struct bitString
+makeBs(wordint      const bits, 
+       unsigned int const bitCount) {
+
+    struct bitString retval;
+    retval.intBuffer = bits;
+    retval.bitCount  = bitCount;
+
+    return retval;
+}
+
+    
+
+static __inline__ void
+putbits(struct bitString const newBits) {
+/*----------------------------------------------------------------------------
+   Push the bits 'newBits' onto the right end of output buffer
+   out.buffer (moving the bits already in the buffer left).
+
+   Flush the buffer to stdout as necessary to make room.
+
+   'newBits' must be shorter than a whole word.
+   
+   N.B. the definition of struct bitString requires upper bits to be zero.
+-----------------------------------------------------------------------------*/
+    unsigned int const spaceLeft = 
+        sizeof(out.buffer.intBuffer)*8 - out.buffer.bitCount;
+        /* Number of bits of unused space (at the high end) in buffer */
+
+    assert(newBits.bitCount < sizeof(out.buffer.intBuffer) * 8);
+    assert(newBits.intBuffer >> newBits.bitCount == 0);
+
+    if (spaceLeft > newBits.bitCount) {
+        /* New bits fit with bits to spare */
+        out.buffer.intBuffer = 
+            out.buffer.intBuffer << newBits.bitCount | newBits.intBuffer;
+        out.buffer.bitCount += newBits.bitCount;
+    } else { 
+        /* New bits fill buffer.  We'll have to flush the buffer to stdout
+           and put the rest of the bits in the new buffer.
+        */
+        unsigned int const nextBufBitCount = newBits.bitCount - spaceLeft;
+
+        wordintBytes outbytes;
+        size_t rc;
+
+        wordintToBytes(&outbytes, 
+                       (out.buffer.intBuffer << spaceLeft) 
+                       | (newBits.intBuffer >> nextBufBitCount));
+        if (out.reverseBits)
+            reversebuffer(outbytes, sizeof(outbytes));
+            
+        rc = fwrite(outbytes, 1, sizeof(outbytes), stdout);
+        if (rc != sizeof(outbytes))
+            pm_error("Output error.  Unable to fwrite() to stdout");
+        
+        out.buffer.intBuffer = newBits.intBuffer & ((1<<nextBufBitCount) - 1); 
+        out.buffer.bitCount = nextBufBitCount;
+    }
+}
+
+
+
+static void 
+initOutStream(bool const reverseBits) {
+    out.buffer.intBuffer = 0;
+    out.buffer.bitCount  = 0;
+    out.reverseBits = reverseBits;
+}
+
+
+
+static __inline__ void
+putcode(unsigned int const clr, 
+        unsigned int const ix) {
+
+    /* Note that this requires ttable to be aligned white entry, black
+       entry, white, black, etc.  
+    */
+    putbits(makeBs(ttable[ix * 2 + clr].code, ttable[ix * 2 + clr].length));
+}
+
+
+
+static __inline__ void
+putcode2(int const clr,
+         int const ix) {
+/*----------------------------------------------------------------------------
+   Output Make-up code and Terminating code at once.
+
+   For run lengths above TC_MC threshold (usually 64).
+
+   The codes are combined here to avoid calculations in putbits()
+   wordint is usually wide enough, with 32 or 64 bits.
+   Provisions are made for 16 bit wordint (for debugging).
+
+   Terminating code is max 12 bits, Make-up code is max 13 bits.
+   (See ttable, mtable entries in pbmtog3.h)
+
+   Also reduces object code size when putcode is compiled inline.
+-----------------------------------------------------------------------------*/
+    unsigned int const loIndex = ix % 64 * 2 + clr;
+    unsigned int const hiIndex = ix / 64 * 2 + clr;
+
+    if (sizeof(wordint) * 8 > 24) {
+        unsigned int const l1 = ttable[loIndex].length;
+        
+        putbits(
+            makeBs(mtable[hiIndex].code << l1 | ttable[loIndex].code,
+                   mtable[hiIndex].length + l1)
+            );
+    } else { /* typically 16 bit wordint used for debugging */
+        putbits(makeBs(mtable[hiIndex].code, mtable[hiIndex].length));
+        putbits(makeBs(ttable[loIndex].code, ttable[loIndex].length));
+    }
+}
+
+
+
+static __inline__ void
+putspan_normal(bit          const color, 
+               unsigned int const len) {
+
+    if (len < TC_MC)
+        putcode(color, len);
+    else if (len < 2624)
+        putcode2(color, len);
+    else {  /* len >= 2624 : rare */
+        unsigned int remainingLen;
+
+        for (remainingLen = len;
+             remainingLen >= 2624;
+             remainingLen -= 2623) {
+
+            putcode2(color, 2560+63);
+            putcode(!color, 0);
+        }
+        if (remainingLen < TC_MC)
+            putcode(color, remainingLen);
+        else  /* TC_MC <= len < 2624 */
+            putcode2(color, remainingLen);
+    }
+}
+
+
+
+static __inline__ void
+putspan(bit          const color, 
+        unsigned int const len) {
+/*----------------------------------------------------------------------------
+   Put a span of 'len' pixels of color 'color' in the output.
+-----------------------------------------------------------------------------*/
+    if (pbmtorl) {
+        if (len > 0) 
+            printf("%c %d\n", color == PBM_WHITE ? 'W' : 'B', len);
+    } else 
+        putspan_normal(color, len);
+}
+
+
+
+static void
+puteol(void) {
+
+    if (pbmtorl)
+        puts("EOL");
+    else {
+        struct bitString const eol = {12, 1};
+            
+        putbits(eol);
+    }
+}
+
+
+
+/*
+  PBM raw bitrow to inflection point array
+
+  Write inflection (=color change) points into array milepost[].  
+  It is easy to calculate run length from this.
+
+  In milepost, a white-to-black (black-to-white) inflection point
+  always has an even (odd) index.  A line starting with black is
+  indicated by bitrow[0] == 0.
+
+  WWWWWWWBBWWWWWWW ... = 7,2,7, ...
+  BBBBBWBBBBBWBBBB ... = 0,5,1,5,1,4, ...
+
+  Return the number of milepost elements written.
+  Note that max number of entries into milepost = cols+1 .
+
+  The inflection points are calculated like this:
+
+   r1: 00000000000111111110011111000000
+   r2: c0000000000011111111001111100000 0->carry
+  xor: ?0000000000100000001010000100000
+
+  The 1 bits in the xor above are the inflection points.
+*/
+
+static __inline__ void
+convertRowToRunLengths(unsigned char * const bitrow, 
+                       int             const cols, 
+                       unsigned int *  const milepost,
+                       unsigned int *  const lengthP) {
+
+    unsigned int   const bitsPerWord  = sizeof(wordint) * 8;
+    wordint      * const bitrowByWord = (wordint *) bitrow;
+    int            const wordCount    = (cols + bitsPerWord - 1)/bitsPerWord; 
+        /* Number of full and partial words in the row */
+        
+
+    if (cols % bitsPerWord != 0) {
+        /* Clean final word in row.  For loop simplicity */
+        wordint r1;
+        r1 = bytesToWordint((unsigned char *)&bitrowByWord[wordCount - 1]);
+        r1 >>= bitsPerWord - cols % bitsPerWord;
+        r1 <<= bitsPerWord - cols % bitsPerWord;
+        wordintToBytes((wordintBytes *)&bitrowByWord[wordCount - 1], r1);
+    }
+    {
+
+        wordint carry;
+        wordint r1, r2;
+        unsigned int n;
+        int i,c,k;
+
+        for (i = carry = n = 0; i < wordCount; ++i) {
+            r1 = r2 = bytesToWordint((unsigned char *)&bitrowByWord[i]);
+            r2 = r1 ^ (carry << (bitsPerWord-1) | r2 >> 1);
+            carry = r1 & 0x1;  k = 0;
+            while (r2 != 0) {
+                /* wordintClz(r2) reports most significant "1" bit of r2
+                   counting from MSB = position 0.
+                */
+                c = wordintClz(r2);
+                milepost[n++] = i * bitsPerWord + k + c;
+                r2 <<= c++; r2 <<= 1;  k += c; 
+            } 
+        }
+        if (milepost[n - 1] != cols) 
+            milepost[n++] = cols;
+        *lengthP = n;
+    }
+}
+
+
+
+static void
+padToDesiredWidth(unsigned int * const milepost,
+                  unsigned int * const nRunP,
+                  int            const existingCols,
+                  int            const desiredCols) {
+
+    if (existingCols < desiredCols) {
+        /* adjustment for narrow input in fixed width mode
+           nRun % 2 == 1 (0) means last (=rightmost) pixel is white (black)
+           if white, extend the last span to outwidth
+           if black, fill with a white span len (outwidth - readcols)
+        */
+        if (*nRunP % 2 == 0)
+            ++*nRunP;
+        milepost[*nRunP - 1] = desiredCols;
+    }
+}
+
+
+
+int
+main(int    argc,
+     char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    unsigned char * bitrow;
+       /* This is the bits of the current row, as read from the input and
+           modified various ways at various points in the program.  It has
+           a word of zero padding on the high (right) end for the convenience
+           of code that accesses this buffer in word-size bites.
+        */
+     
+    int rows;
+    int cols;
+    int readcols;
+    int outwidth;
+    int format;
+    int row;
+    unsigned int * milepost;
+
+    pbm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+     
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pbm_readpbminit(ifP, &cols, &rows, &format);
+    if (cmdline.nofixedwidth)
+        readcols = outwidth = cols;
+    else {
+        readcols = MIN(cols, 1728);
+        outwidth = 1728;
+    }
+
+    MALLOCARRAY_NOFAIL(bitrow, pbm_packed_bytes(cols) + sizeof(wordint));
+
+    MALLOCARRAY_NOFAIL(milepost, readcols + 1);
+
+    initOutStream(cmdline.reversebits);
+    puteol();
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int nRun;  /* Number of runs in milepost[] */
+        unsigned int p;
+        unsigned int i;
+
+        pbm_readpbmrow_packed(ifP, bitrow, cols, format);
+        
+        convertRowToRunLengths(bitrow, readcols, milepost, &nRun);
+        
+        padToDesiredWidth(milepost, &nRun, readcols, outwidth);
+
+        for (i = p = 0; i < nRun; p = milepost[i++])
+            putspan(i%2 == 0 ? PBM_WHITE : PBM_BLACK, milepost[i] - p);
+        /* TODO 2-dimensional coding MR, MMR */
+        puteol();
+    }
+
+    free(milepost);
+    {
+        unsigned int i;  
+        for( i = 0; i < 6; ++i)
+            puteol();
+    }
+    if (out.buffer.bitCount > 0) {
+        /* flush final partial buffer */
+        unsigned int const bytesToWrite = (out.buffer.bitCount+7)/8;
+        
+        unsigned char outbytes[sizeof(wordint)];
+        size_t rc;
+        wordintToBytes(&outbytes, 
+                       out.buffer.intBuffer << (sizeof(out.buffer.intBuffer)*8 
+                                                - out.buffer.bitCount));
+        if (out.reverseBits)
+            reversebuffer(outbytes, bytesToWrite);
+        rc = fwrite(outbytes, 1, bytesToWrite, stdout);
+        if (rc != bytesToWrite)
+            pm_error("Output error");
+    }
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/converter/pbm/pbmtog3.test b/converter/pbm/pbmtog3.test
new file mode 100644
index 00000000..9ca45079
--- /dev/null
+++ b/converter/pbm/pbmtog3.test
@@ -0,0 +1,23 @@
+echo Test 1.  Should print 3697098186 144
+./pbmtog3 ../../testgrid.pbm | cksum
+echo Test 2.  Should print 1248301383 122
+./pbmtog3 -nofixedwidth ../../testgrid.pbm | cksum
+echo Test 3.  Should print 686713716 144
+./pbmtog3 -reverse ../../testgrid.pbm | cksum
+echo Test 4.  Should print 215463240 122
+./pbmtog3 -nofixedwidth -reverse ../../testgrid.pbm | cksum
+echo Test 5.  Should print 28792587 47
+pbmmake -w 10 10 | ./pbmtog3 | cksum
+echo Test 6.  Should print 277456854 32
+pbmmake -w 10 10 | ./pbmtog3 -nofixedwidth | cksum
+echo Test 7.  Should print 28792587 47
+pbmmake -w 10000 10 | ./pbmtog3 | cksum
+echo Test 8.  Should print 871281767 162
+pbmmake -w 10000 10 | ./pbmtog3 -nofixedwidth | cksum
+echo Test 9.  Should print 3736247115 62
+pbmmake -b 10 10 | ./pbmtog3 | cksum
+echo Test 10.  Should print 2820255307 2191856
+pbmmake -g 1700 2286 | ./pbmtog3 | cksum
+echo Test 11.  Should print 4159089282 2226575
+pbmmake -g 1800 2286 | ./pbmtog3 | cksum
+echo Tests done.
diff --git a/converter/pbm/pbmtogem.c b/converter/pbm/pbmtogem.c
new file mode 100644
index 00000000..59f2b9cf
--- /dev/null
+++ b/converter/pbm/pbmtogem.c
@@ -0,0 +1,252 @@
+/* pbmtogem.c - read a portable bitmap and produce a GEM .img file
+**
+** Author: David Beckemeyer (bdt!david)
+**
+** Much of the code for this program was taken from other
+** pbmto* programs.  I just modified the code to produce
+** a .img header and generate .img "Bit Strings".
+**
+** Thanks to Diomidis D. Spinellis for the .img header format.
+**
+** Copyright (C) 1988 by David Beckemeyer (bdt!david) and Jef Poskanzer.
+**
+** Modified by Johann Haider to produce Atari ST compatible files
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+/*
+*  92/07/11 jh
+*
+*  Changes made to this program:
+*  changed header length to count words to conform with Atari ST documentation
+*  removed rounding of the imagewidth to the next word boundary
+*  removed arbitrary limit to imagewidth
+*  changed pattern length to 1 to simplify locating of compressable parts
+*	in real world images
+*  add solid run and pattern run compression
+*
+*  Deficiencies:
+*  Compression of repeated scanlines not added
+*  
+*	Johann Haider (jh@fortec.tuwien.ac.at)
+*
+* 94/01/31 Andreas Schwab (schwab@ls5.informatik.uni-dortmund.de)
+* Changed to remove architecture dependencies
+* Added compression of repeated scanlines
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include "pbm.h"
+
+#define SOLID_0 0
+#define SOLID_1 0xff
+#define MINRUN 4
+#define putsolid(v,c) putc((v&0x80)|c, stdout)
+#define putpattern(v,c) putc(0, stdout);putc(c, stdout);putc(v, stdout)
+
+static void putinit ARGS ((int rows, int cols));
+static void putbit ARGS(( bit b ));
+static void putitem ARGS(( void ));
+static void putrow ARGS(( void ));
+static void flushrow ARGS ((void));
+static void putstring ARGS((register unsigned char *p, register int n));
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    bit* bitrow;
+    register bit* bP;
+    int rows, cols, format, row, col;
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[pbmfile]" );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    pbm_readpbminit( ifp, &cols, &rows, &format );
+
+    bitrow = pbm_allocrow( cols );
+
+    putinit (rows, cols);
+    for ( row = 0; row < rows; ++row )
+	{
+#ifdef DEBUG
+	fprintf (stderr, "row %d\n", row);
+#endif
+	pbm_readpbmrow( ifp, bitrow, cols, format );
+        for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
+	    putbit( *bP );
+        putrow( );
+        }
+    flushrow ();
+
+    pm_close( ifp );
+
+
+    exit( 0 );
+    }
+
+static short item;
+static int outcol, outmax;
+static short bitsperitem, bitshift;
+static short linerepeat;
+static unsigned char *outrow, *lastrow;
+
+static void
+putinit (rows, cols)
+     int rows, cols;
+{
+  if (pm_writebigshort (stdout, (short) 1) == -1 /* Image file version */
+      || pm_writebigshort (stdout, (short) 8) == -1 /* Header length */
+      || pm_writebigshort (stdout, (short) 1) == -1 /* Number of planes */
+      || pm_writebigshort (stdout, (short) 1) == -1 /* Pattern length */
+      || pm_writebigshort (stdout, (short) 372) == -1 /* Pixel width */
+      || pm_writebigshort (stdout, (short) 372) == -1 /* Pixel height */
+      || pm_writebigshort (stdout, (short) cols) == -1
+      || pm_writebigshort (stdout, (short) rows) == -1)
+    pm_error ("write error");
+  item = 0;
+  bitsperitem = 0;
+  bitshift = 7;
+  outcol = 0;
+  outmax = (cols + 7) / 8;
+  outrow = (unsigned char *) pm_allocrow (outmax, sizeof (unsigned char));
+  lastrow = (unsigned char *) pm_allocrow (outmax, sizeof (unsigned char));
+  linerepeat = -1;
+}
+
+#if __STDC__
+static void
+putbit( bit b )
+#else /*__STDC__*/
+static void
+putbit( b )
+    bit b;
+#endif /*__STDC__*/
+    {
+    if ( bitsperitem == 8 )
+	putitem( );
+    ++bitsperitem;
+    if ( b == PBM_BLACK )
+	item += 1 << bitshift;
+    --bitshift;
+    }
+
+static void
+putitem( )
+    {
+    outrow[outcol++] = item;
+    item = 0;
+    bitsperitem = 0;
+    bitshift = 7;
+    }
+
+static void
+putstring (p, n)
+register unsigned char *p;
+register int n;
+{
+#ifdef DEBUG
+    fprintf (stderr, "Bitstring, length: %d, pos %d\n", n, outcol);
+#endif
+    (void) putc((char) 0x80, stdout);     /* a Bit string */
+    (void) putc(n, stdout);	/* count */
+    fwrite( p, n, 1, stdout );
+}
+
+static void
+putrow( )
+{
+  if (bitsperitem > 0)
+    putitem ();
+  outcol = 0;
+  if (linerepeat == -1 || linerepeat == 255
+      || memcmp (outrow, lastrow, outmax) != 0)
+    {
+      unsigned char *temp;
+      if (linerepeat != -1) /* Unless first line */
+	flushrow ();
+      /* Swap the pointers */
+      temp = outrow; outrow = lastrow; lastrow = temp;
+      linerepeat = 1;
+    }
+  else
+    /* Repeated line */
+    linerepeat++;
+}
+
+static void
+flushrow( )
+    {
+    register unsigned char *outp, *p, *q;
+    register int count;
+    int col = outmax;
+
+    if (linerepeat > 1)
+      {
+	/* Put out line repeat count */
+	fwrite ("\0\0\377", 3, 1, stdout);
+	putchar (linerepeat);
+      }
+    for (outp = p = lastrow; col > 0;)
+    {
+	    for (q = p, count=0; (count < col) && (*q == *p); q++,count++);
+	    if (count > MINRUN)
+	    {
+		if (p > outp)
+		{
+		    putstring (outp, p-outp);
+		    outp = p;
+		}
+		col -= count;
+		switch (*p)
+		{
+		case SOLID_0:
+#ifdef DEBUG
+/*			if (outcol > 0) */
+			fprintf (stderr, "Solid run 0, length: %d\n", count);
+#endif
+			putsolid (SOLID_0, count);
+			break;
+
+		case SOLID_1:
+#ifdef DEBUG
+			fprintf (stderr, "Solid run 1, length: %d, pos %d\n", count, outcol);
+#endif
+			putsolid (SOLID_1, count);
+			break;
+		default:
+#ifdef DEBUG
+			fprintf (stderr, "Pattern run, length: %d\n", count);
+#endif
+			putpattern (*p, count);
+			break;
+		}
+		outp = p = q;
+	    }
+	    else
+	    {
+		p++;
+		col--;
+	    }
+    }		
+    if (p > outp)
+         putstring (outp, p-outp);
+    if (ferror (stdout))
+      pm_error ("write error");
+}
+
diff --git a/converter/pbm/pbmtogo.c b/converter/pbm/pbmtogo.c
new file mode 100644
index 00000000..b7c12373
--- /dev/null
+++ b/converter/pbm/pbmtogo.c
@@ -0,0 +1,309 @@
+/* pbmtogo.c - read a PBM image and produce a GraphOn terminal raster file
+**	
+**	Rev 1.1 was based on pbmtolj.c
+**
+**	Bo Thide', Swedish Institute of Space Physics, bt@irfu.se
+**				   
+**
+** $Log:	pbmtogo.c,v $
+ * Revision 1.5  89/11/25  00:24:12  00:24:12  root (Bo Thide)
+ * Bug found: The byte after 64 repeated bytes sometimes lost. Fixed.
+ * 
+ * Revision 1.4  89/11/24  14:56:04  14:56:04  root (Bo Thide)
+ * Fixed the command line parsing since pbmtogo now always uses 2D
+ * compression.  Added a few comments to the source.
+ * 
+ * Revision 1.3  89/11/24  13:43:43  13:43:43  root (Bo Thide)
+ * Added capability for > 63 repeated bytes and > 62 repeated lines in
+ * the 2D compression scheme.
+ * 
+ * Revision 1.2  89/11/15  01:04:47  01:04:47  root (Bo Thide)
+ * First version that works reasonably well with GraphOn 2D runlength
+ * encoding/compression.
+ * 
+ * Revision 1.1  89/11/02  23:25:25  23:25:25  root (Bo Thide)
+ * Initial revision
+ * 
+**
+** Copyright (C) 1988, 1989 by Jef Poskanzer, Michael Haberler, and Bo Thide'.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <stdio.h>
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "pbm.h"
+
+#define BUFSIZE 132	/* GraphOn has 132 byte/1056 bit wide raster lines */
+#define REPEAT_CURRENT_LINE_MASK	0x00 
+#define SKIP_AND_PLOT_MASK		0x40 
+#define REPEAT_PLOT_MASK		0x80 
+#define PLOT_ARBITRARY_DATA_MASK	0xc0 
+#define MAX_REPEAT 64
+
+static unsigned char *scanlineptr;		/* Pointer to current scan line byte */
+
+static void putinit ARGS(( void ));
+static void putbit ARGS(( bit b ));
+static void putrest ARGS(( void ));
+static void putitem ARGS(( void ));
+
+int
+main( argc, argv )
+     int argc;
+     char* argv[];
+{
+    FILE* ifp;
+    bit* bitrow;
+    register bit* bP;
+    int argn, rows, cols, format, rucols, padright, row, col;
+    int nbyte, bytesperrow, ecount, ucount, nout, i, linerepeat;
+    int	olditem;
+    unsigned char oldscanline[BUFSIZE];
+    unsigned char newscanline[BUFSIZE];
+    unsigned char diff[BUFSIZE];
+    unsigned char buffer[BUFSIZE];
+    unsigned char outbuffer[2*(BUFSIZE+1)];	/* Worst case.  Should malloc */
+    const char* usage = "[-c] [pbmfile]";
+
+
+    pbm_init( &argc, argv );
+
+    argn = 2;
+
+    /* Check for flags. */
+    if (argc > argn + 1)
+      pm_usage(usage);
+
+    if (argc == argn)
+      ifp = pm_openr( argv[argn-1] );
+    else
+      ifp = stdin;
+
+    pbm_readpbminit(ifp, &cols, &rows, &format);
+    bitrow = pbm_allocrow(cols);
+
+    /* Round cols up to the nearest multiple of 8. */
+    rucols = ( cols + 7 ) / 8;
+    bytesperrow = rucols;       /* GraphOn uses bytes */
+    rucols = rucols * 8;
+    padright = rucols - cols;
+
+    for (i = 0; i < BUFSIZE; ++i )
+      buffer[i] = 0;
+    putinit();
+
+    /* Start donwloading screen raster */
+    printf("\033P0;1;0;4;1;%d;%d;1!R1/", rows, rucols);
+
+    linerepeat = 63; /*  63 means "Start new picture" */
+    nout = 0;  /* To prevent compiler warning */
+    for (row = 0; row < rows; row++) {
+        /* Store scan line data in the new scan line vector */
+        scanlineptr = newscanline;
+        pbm_readpbmrow(ifp, bitrow, cols, format);
+        /* Transfer raster graphics */
+        for (col = 0, bP = bitrow; col < cols; col++, bP++)
+          putbit(*bP);
+        for (col = 0; col < padright; col++)
+          putbit(0);
+        
+        /* XOR data from the new scan line with data from old scan line */
+        for (i = 0; i < bytesperrow; i++)
+          diff[i] = oldscanline[i]^newscanline[i];
+        
+        /*
+         ** If the difference map is different from current internal buffer,
+         ** encode the difference and put it in the output buffer. 
+         ** Else, increase the counter for the current buffer by one.
+         */
+        
+        if ((memcmp(buffer, diff, bytesperrow) != 0) || (row == 0)) {
+            /*    
+             **Since the data in the buffer has changed, send the
+             **scan line repeat count to cause the old line(s) to
+             **be plotted on the screen, copy the new data into
+             **the internal buffer, and reset the counters.  
+             */
+              
+            putchar(linerepeat);
+            for (i = 0; i < bytesperrow; ++i)
+              buffer[i] = diff[i];
+            nbyte = 0;          /* Internal buffer byte counter */
+            nout = 0;           /* Output buffer byte counter */
+              
+            /* Run length encode the new internal buffr (= difference map) */
+            while (TRUE) {
+                ucount = 0;     /* Unique items counter */
+                do              /* Find unique patterns */
+                  {
+                      olditem = buffer[nbyte++];
+                      ucount++;
+                  } while ((olditem != buffer[nbyte])
+                           && (ucount < MIN(bytesperrow, MAX_REPEAT)));
+                  
+                if ((ucount != MAX_REPEAT) && (nbyte != bytesperrow)) {
+                    /* Back up to the last truly unique pattern */
+                    ucount--;
+                    nbyte--;
+                }
+
+                if (ucount > 0) { 
+                    /* Output the unique patterns */
+                    outbuffer[nout++] = 
+                      (ucount-1) | PLOT_ARBITRARY_DATA_MASK;
+                    for (i = nbyte-ucount; i < nbyte; i++)
+                      outbuffer[nout++] = buffer[i];
+                }
+
+                /*
+                 ** If we already are at the end of the current scan
+                 ** line, skip the rest of the encoding and start
+                 ** with a new scan line.  
+                 */
+
+                if (nbyte >= bytesperrow)
+                  goto nextrow;
+                  
+                ecount = 0;     /* Equal items counter */
+                do              /* Find equal patterns */
+                  {
+                      olditem = buffer[nbyte++];
+                      ecount++;
+                  } while ((olditem == buffer[nbyte])
+                           && (ecount < MIN(bytesperrow, MAX_REPEAT)));
+                  
+                if (ecount > 1) {
+                    /* More than 1 equal pattern */
+                    if (olditem == '\0') {
+                        /* White patterns */
+                        if (nbyte >= bytesperrow-1) {
+                            /* No more valid data ahead */
+                            outbuffer[nout++] = 
+                              (ecount-2) | SKIP_AND_PLOT_MASK;
+                            outbuffer[nout++] = buffer[nbyte-1];
+                        } 
+                        else { 
+                            /* More valid data ahead */
+                            outbuffer[nout++] = 
+                              (ecount-1) | SKIP_AND_PLOT_MASK;
+                            outbuffer[nout++] = buffer[nbyte++];
+                        } 
+                    }
+                    else { 
+                        /* Non-white patterns */
+                        outbuffer[nout++] = (ecount-1) | REPEAT_PLOT_MASK;
+                        outbuffer[nout++] = olditem;
+                    } /* if (olditem == '\0') */
+                } /* if (ecount > 1) */
+                else
+                  nbyte--;      /* No equal items found */
+                  
+                if (nbyte >= bytesperrow)
+                  goto nextrow;
+            } /* while (TRUE) */
+              
+          nextrow: printf("%d/", nout+1); /* Total bytes to xfer = nout+1 */
+            fflush(stdout);
+
+            /* Output the plot data */
+            write(1, outbuffer, nout);
+
+            /* Reset the counters */
+            linerepeat = 0;
+        } else {
+            linerepeat++;
+            if (linerepeat == 62) /* 62 lines max, then restart */
+              {
+                  putchar(linerepeat);
+                  printf("%d/", nout+1);
+                  fflush(stdout);
+                  write(1, outbuffer, nout);
+                  linerepeat = 0;
+              }
+        }
+        
+        /* Now we are ready for a new scan line */
+        for (i = 0; i < bytesperrow; ++i)
+          oldscanline[i] = newscanline[i];
+    }
+    putchar(linerepeat);        /* For the last line(s) to be plotted */
+    pm_close(ifp);
+    putrest();
+    exit(0);
+}
+
+
+
+static int item, bitsperitem, bitshift;
+
+static void
+putinit()
+{
+  /* Enter graphics window */
+  printf("\0331");
+
+  /* Erase graphics window */
+  printf("\033\014");
+
+  /* Set graphics window in raster mode */
+  printf("\033r");
+
+  /* Select standard Tek coding **/
+  printf("\033[=11l");
+
+  bitsperitem = 1;
+  item = 0;
+  bitshift = 7;
+}
+
+#if __STDC__
+static void
+putbit(bit b)
+#else /*__STDC__*/
+static void
+putbit(b)
+bit b;
+#endif /*__STDC__*/
+{
+  if (b == PBM_BLACK)
+    item += 1 << bitshift;
+  bitshift--;
+  if (bitsperitem == 8)
+  {
+    putitem();
+    bitshift = 7;
+  }
+  bitsperitem++;
+}
+
+static void
+putrest()
+{
+  if (bitsperitem > 1)
+      putitem();
+
+  /* end raster downloading */
+  printf("\033\134");
+
+  /* Exit raster mode */
+  printf("\033t");
+
+  /* Exit graphics window
+  printf("\0332"); */
+}
+
+static void
+putitem()
+  {
+  *scanlineptr++ = item;
+  bitsperitem = 0;
+  item = 0;
+  }
diff --git a/converter/pbm/pbmtoibm23xx.c b/converter/pbm/pbmtoibm23xx.c
new file mode 100644
index 00000000..973f7de0
--- /dev/null
+++ b/converter/pbm/pbmtoibm23xx.c
@@ -0,0 +1,213 @@
+/*
+ * pbmtoibm23xx -- print pbm file on IBM 23XX printers
+ * Copyright (C) 2004  Jorrit Fahlke <jorrit@jorrit.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+/*
+ * This prgram is primarily based on the description of Brothers PPDS
+ * emulation (see
+ * http://www.brother.de/download/send_file.cfm?file_name=guide_ibmpro.pdf).
+ * However, there are some differences.  Their document states that
+ * ESC J does linefeed in terms of 1/216" -- my printer clearly does
+ * it in terms of 1/240".  Also, the quick and the slow mode for
+ * double density printing really makes a difference on my printer,
+ * the result of printing tiger.ps in quick double density mode was
+ * worse than printing it in single density mode.
+ *
+ * If anyone Knows of any better documentation of the language used by
+ * the IBM 23XX or PPDS in general, please send a mail to
+ * Jö Fahlke <jorrit@jorrit.de>.
+ *
+ * All the graphics modes of the printer differ only in the resolution
+ * in x they provide (and how quick they do their job).  They print a
+ * line of 8 pixels height and variable widths.  The bitlines within
+ * the line are 1/60" apart, so that is the resolution you can
+ * normally achieve in y.  But the printer is able to do line feeds in
+ * terms of 1/240", so the trick to print in higher resolutions is to
+ * print in several interleaved passes, and do a line feed of 1/240"
+ * or 1/120" inbetween.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pbm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+struct cmdlineInfo {
+    unsigned char graph_mode;
+    unsigned int passes;
+    unsigned int nFiles;
+    const char ** inputFile;
+};
+
+
+bool sent_xon; /* We have send x-on to enable the printer already */
+
+
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+
+    optStruct3 opt;
+    optEntry option_def[100];
+
+    unsigned int option_def_index = 0;
+    unsigned int xresSpec, yresSpec;
+    unsigned int xres, yres;
+    unsigned int slowMode;
+
+    OPTENT3(0, "xres", OPT_UINT, &xres, &xresSpec, 0);
+    OPTENT3(0, "yres", OPT_UINT, &yres, &yresSpec, 0);
+    OPTENT3(0, "slow", OPT_FLAG, NULL,  &slowMode, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = 0;
+    opt.allowNegNum = 0;
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!xresSpec)
+        pm_error("You must specify the -xres option");
+    if (!yresSpec)
+        pm_error("You must specify the -yres option");
+
+    switch (xres) {
+    case  60: cmdlineP->graph_mode = 'K';                  break;
+    case 120: cmdlineP->graph_mode = slowMode ? 'L' : 'Y'; break;
+    case 240: cmdlineP->graph_mode = 'Z';                  break;
+    default:
+        pm_error("Please specify 60, 120, or 240 for -xres");
+    }
+
+    if (yres != 60 && yres != 120 && yres != 240)
+        pm_error("Please specify 60, 120, or 240 for -yres");
+
+    cmdlineP->passes = yres / 60;
+
+    cmdlineP->nFiles = MAX(argc-1, 1);
+    MALLOCARRAY_NOFAIL(cmdlineP->inputFile, cmdlineP->nFiles);
+
+    if (argc-1 < 1)
+        cmdlineP->inputFile[0] = "-";
+    else {
+        unsigned int i;
+        for (i = 0; i < argc-1; ++i)
+            cmdlineP->inputFile[i] = argv[i+1];
+    }
+}
+
+
+
+/* Read all pbm images from a filehandle and print them */
+static void 
+process_handle(FILE *        const fh,
+               unsigned char const graph_mode,
+               unsigned int  const passes) {
+    int eof;
+
+    while(pbm_nextimage(fh, &eof), eof == 0) {
+        /* pbm header dats */
+        int cols, rows, format;
+        /* iteration variables */
+        unsigned int x, y;
+        unsigned int bitline; /* pixel line within a sigle printing line */
+        unsigned int pass;
+        /* here we build the to-be-printed data */
+        unsigned char *output;  /* for reading one row from the file */
+        bit *row;
+
+        /* Enable printer in case it is disabled, do it only once */
+        if(!sent_xon) {
+            putchar(0x11);
+            sent_xon = TRUE;
+        }
+
+        pbm_readpbminit(fh, &cols, &rows, &format);
+
+        output = malloc(sizeof(*output) * cols * passes);
+        if(output == NULL)
+            pm_error("Out of memory");
+        row = pbm_allocrow(cols);
+
+        for(y = 0; y < rows; y += 8 * passes) {
+            memset(output, 0, sizeof(*output) * cols * passes);
+            for(bitline = 0; bitline < 8; ++bitline)
+                for(pass = 0; pass < passes; ++pass)
+                    /* don't read beyond the end of the image if
+                       height is not a multiple of passes 
+                    */
+                    if(y + bitline * passes + pass < rows) {
+                        pbm_readpbmrow(fh, row, cols, format);
+                        for(x = 0; x < cols; ++x)
+                            if(row[x] == PBM_BLACK)
+                                output[cols * pass + x] |= 1 << (7 - bitline);
+                    }
+            for(pass = 0; pass < passes; ++pass){
+                /* write graphics data */
+                putchar(0x1b); putchar(graph_mode);
+                putchar(cols & 0xff); putchar((cols >> 8) & 0xff);
+                fwrite(output + pass * cols, sizeof(*output), cols, stdout);
+
+                /* Carriage return */
+                putchar('\r');
+
+                /* move one pixel down */
+                putchar(0x1b); putchar('J'); putchar(4 / passes);
+            }
+
+            /* move one line - passes pixel down */
+            putchar(0x1b); putchar('J'); putchar(24 - 4);
+        }
+        putchar(0x0c); /* Form-feed */
+
+        pbm_freerow(row);
+        free(output);
+    }
+}
+
+
+
+int 
+main(int argc,char **argv) {
+
+  struct cmdlineInfo cmdline;
+  unsigned int i;
+
+  pbm_init(&argc, argv);
+
+  parseCommandLine(argc, argv, &cmdline);
+
+  sent_xon = FALSE;
+
+  for (i = 0; i < cmdline.nFiles; ++i) {
+      FILE *ifP;
+      pm_message("opening '%s'", cmdline.inputFile[i]);
+      ifP = pm_openr(cmdline.inputFile[i]);
+      process_handle(ifP, cmdline.graph_mode, cmdline.passes);
+      pm_close(ifP);
+  }
+
+  free(cmdline.inputFile);
+
+  return 0;
+}
diff --git a/converter/pbm/pbmtoicon.c b/converter/pbm/pbmtoicon.c
new file mode 100644
index 00000000..0e21c202
--- /dev/null
+++ b/converter/pbm/pbmtoicon.c
@@ -0,0 +1,134 @@
+/* pbmtoicon.c - read a portable bitmap and produce a Sun icon file
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+
+static void putinit ARGS(( void ));
+static void putbit ARGS(( bit b ));
+static void putrest ARGS(( void ));
+static void putitem ARGS(( void ));
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    bit* bitrow;
+    register bit* bP;
+    int rows, cols, format, pad, padleft, padright, row, col;
+
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[pbmfile]" );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    pbm_readpbminit( ifp, &cols, &rows, &format );
+    bitrow = pbm_allocrow( cols );
+    
+    /* Round cols up to the nearest multiple of 16. */
+    pad = ( ( cols + 15 ) / 16 ) * 16 - cols;
+    padleft = pad / 2;
+    padright = pad - padleft;
+
+    printf( "/* Format_version=1, Width=%d, Height=%d", cols + pad, rows );
+    printf( ", Depth=1, Valid_bits_per_item=16\n */\n" );
+
+    putinit( );
+    for ( row = 0; row < rows; ++row )
+	{
+	pbm_readpbmrow( ifp, bitrow, cols, format );
+	for ( col = 0; col < padleft; ++col )
+	    putbit( 0 );
+        for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
+	    putbit( *bP );
+	for ( col = 0; col < padright; ++col )
+	    putbit( 0 );
+        }
+
+    pm_close( ifp );
+
+    putrest( );
+
+    exit( 0 );
+    }
+
+static int item, bitsperitem, bitshift, itemsperline, firstitem;
+
+static void
+putinit( )
+    {
+    itemsperline = 0;
+    bitsperitem = 0;
+    item = 0;
+    bitshift = 15;
+    firstitem = 1;
+    }
+
+#if __STDC__
+static void
+putbit( bit b )
+#else /*__STDC__*/
+static void
+putbit( b )
+bit b;
+#endif /*__STDC__*/
+    {
+    if ( bitsperitem == 16 )
+	putitem( );
+    ++bitsperitem;
+    if ( b == PBM_BLACK )
+	item += 1 << bitshift;
+    --bitshift;
+    }
+
+static void
+putrest( )
+    {
+    if ( bitsperitem > 0 )
+	putitem( );
+    putchar( '\n' );
+    }
+
+static void
+putitem( )
+    {
+    const char* hexits = "0123456789abcdef";
+
+    if ( firstitem )
+	firstitem = 0;
+    else
+	putchar( ',' );
+    if ( itemsperline == 8 )
+	{
+	putchar( '\n' );
+	itemsperline = 0;
+	}
+    if ( itemsperline == 0 )
+	putchar( '\t' );
+    putchar( '0' );
+    putchar( 'x' );
+    putchar( hexits[item >> 12] );
+    putchar( hexits[( item >> 8 ) & 15] );
+    putchar( hexits[( item >> 4 ) & 15] );
+    putchar( hexits[item & 15] );
+    ++itemsperline;
+    bitsperitem = 0;
+    item = 0;
+    bitshift = 15;
+    }
diff --git a/converter/pbm/pbmtolj.c b/converter/pbm/pbmtolj.c
new file mode 100644
index 00000000..e8373050
--- /dev/null
+++ b/converter/pbm/pbmtolj.c
@@ -0,0 +1,564 @@
+/* pbmtolj.c - read a portable bitmap and produce a LaserJet bitmap file
+**  
+**  based on pbmtops.c
+**
+**  Michael Haberler HP Vienna mah@hpuviea.uucp
+**                 mcvax!tuvie!mah
+**  misfeatures: 
+**      no positioning
+**
+**      Bug fix Dec 12, 1988 :
+**              lines in putbit() reshuffled 
+**              now runs OK on HP-UX 6.0 with X10R4 and HP Laserjet II
+**      Bo Thide', Swedish Institute of Space Physics, Uppsala <bt@irfu.se>
+**
+**  Flags added December, 1993:
+**      -noreset to suppress printer reset code
+**      -float to suppress positioning code (such as it is)
+**  Wim Lewis, Seattle <wiml@netcom.com>
+**
+** Copyright (C) 1988 by Jef Poskanzer and Michael Haberler.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include <string.h>
+#include <assert.h>
+
+static char *rowBuffer, *prevRowBuffer, *packBuffer, *deltaBuffer;
+static int rowBufferSize, rowBufferIndex, prevRowBufferIndex;
+static int packBufferSize, packBufferIndex;
+static int deltaBufferSize, deltaBufferIndex;
+
+static int item, bitsperitem, bitshift;
+
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilename;
+    unsigned int dpi;
+    unsigned int copies;     /* number of copies */
+    unsigned int floating;   /* suppress the ``ESC & l 0 E'' ? */
+    unsigned int noreset;
+    unsigned int pack;       /* use TIFF packbits compression */
+    unsigned int delta;      /* use row-delta compression */
+};
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int dpiSpec, copiesSpec, compressSpec;
+
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "resolution",  OPT_UINT, &cmdlineP->dpi, 
+            &dpiSpec, 0);
+    OPTENT3(0,   "copies",      OPT_UINT, &cmdlineP->copies,
+            &copiesSpec, 0);
+    OPTENT3(0,   "float",       OPT_FLAG, NULL,
+            &cmdlineP->floating, 0);
+    OPTENT3(0,   "noreset",     OPT_FLAG, NULL,
+            &cmdlineP->noreset, 0);
+    OPTENT3(0,   "packbits",    OPT_FLAG, NULL,
+            &cmdlineP->pack, 0);
+    OPTENT3(0,   "delta",       OPT_FLAG, NULL,
+            &cmdlineP->delta, 0);
+    OPTENT3(0,   "compress",    OPT_FLAG, NULL,
+            &compressSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 == 0) 
+        cmdlineP->inputFilename = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->inputFilename = argv[1];
+
+    if (!dpiSpec)
+        cmdlineP->dpi = 75;
+    if (!copiesSpec)
+        cmdlineP->copies = 1;
+    if (compressSpec) {
+        cmdlineP->pack = 1;
+        cmdlineP->delta = 1;
+    }
+}
+
+
+
+static void
+allocateBuffers(unsigned int const cols) {
+
+    rowBufferSize = (cols + 7) / 8;
+    packBufferSize = rowBufferSize + (rowBufferSize + 127) / 128 + 1;
+    deltaBufferSize = rowBufferSize + rowBufferSize / 8 + 10;
+
+    MALLOCARRAY_NOFAIL(prevRowBuffer, rowBufferSize);
+    MALLOCARRAY_NOFAIL(rowBuffer, rowBufferSize);
+    MALLOCARRAY_NOFAIL(packBuffer, packBufferSize);
+    MALLOCARRAY_NOFAIL(deltaBuffer, deltaBufferSize);
+}
+
+
+
+static void
+freeBuffers(void) {
+
+    free(deltaBuffer);
+    free(packBuffer);
+    free(rowBuffer);
+    free(prevRowBuffer);
+}
+
+
+
+static void
+putinit(struct cmdlineInfo const cmdline) {
+    if (!cmdline.noreset) {
+        /* Printer reset. */
+        printf("\033E");
+    }
+
+    if (cmdline.copies > 1) {
+        /* number of copies */
+        printf("\033&l%dX", cmdline.copies);
+    }
+    if (!cmdline.floating) {
+        /* Ensure top margin is zero */
+        printf("\033&l0E");
+    }
+
+    /* Set raster graphics resolution */
+    printf("\033*t%dR", cmdline.dpi);
+
+    /* Start raster graphics, relative adressing */
+    printf("\033*r1A");
+
+    bitsperitem = 1;
+    item = 0;
+    bitshift = 7;
+}
+
+
+
+static void
+putitem(void) {
+    assert(rowBufferIndex < rowBufferSize);
+    rowBuffer[rowBufferIndex++] = item;
+    bitsperitem = 0;
+    item = 0;
+}
+
+
+
+static void
+putbit(bit const b) {
+
+    if (b == PBM_BLACK)
+        item += 1 << bitshift;
+
+    --bitshift;
+
+    if (bitsperitem == 8) {
+        putitem();
+        bitshift = 7;
+    }
+    ++bitsperitem;
+}
+
+
+
+static void
+putflush(void) {
+    if (bitsperitem > 1)
+        putitem();
+}
+
+
+
+static void
+putrest(bool const reset) {
+    /* end raster graphics */
+    printf("\033*rB");
+
+    if (reset) {
+        /* Printer reset. */
+        printf("\033E");
+    }
+}
+
+
+
+static void
+packbits(void) {
+    int ptr, litStart, runStart, thisByte, startByte, chew, spit;
+    packBufferIndex = 0;
+    ptr = 0;
+    while (ptr < rowBufferIndex) {
+        litStart = ptr;
+        runStart = ptr;
+        startByte = rowBuffer[ptr];
+        ++ptr;
+        while (ptr < rowBufferIndex) {
+            thisByte = rowBuffer[ptr];
+            if (thisByte != startByte) {
+                if (ptr - runStart > 3) {
+                    /* found literal after nontrivial run */
+                    break;
+                }
+                startByte = thisByte;
+                runStart = ptr;
+            }
+            ++ptr;
+        }
+        /*
+          We drop out here after having found a [possibly empty]
+          literal, followed by a [possibly degenerate] run of repeated
+          bytes.  Degenerate runs can occur at the end of the scan line...
+          there may be a "repeat" of 1 byte (which can't actually be 
+          represented as a repeat) so we simply fold it into the previous
+          literal.
+        */
+        if (runStart == rowBufferIndex - 1) {
+            runStart = rowBufferIndex;
+        }
+        /*
+          Spit out the leading literal if it isn't empty
+        */
+        chew = runStart - litStart;
+        while (chew > 0) {
+            spit = (chew > 127) ? 127 : chew;
+            packBuffer[packBufferIndex++] = (char) (spit - 1);
+            memcpy(packBuffer+packBufferIndex, rowBuffer+litStart, spit);
+            packBufferIndex += spit;
+            litStart += spit;
+            chew -= spit;
+        }
+        /*
+          Spit out the repeat, if it isn't empty
+        */
+        chew = ptr - runStart;
+        while (chew > 0) {
+            spit = (chew > 128) ? 128 : chew;
+            if (chew == spit + 1) {
+                spit--; /* don't leave a degenerate run at the end */
+            }
+            if (spit == 1) {
+                fprintf(stderr, "packbits created a degenerate run!\n");
+            }
+            packBuffer[packBufferIndex++] = (char) -(spit - 1);
+            packBuffer[packBufferIndex++] = startByte;
+            chew -= spit;
+        }
+    }
+}
+
+
+
+static void
+deltarow(void) {
+    int burstStart, burstEnd, burstCode, mustBurst, ptr, skip, skipped, code;
+    deltaBufferIndex = 0;
+    if (memcmp(rowBuffer, prevRowBuffer, rowBufferIndex) == 0) {
+        return; /* exact match, no deltas required */
+    }
+    ptr = 0;
+    skipped = 0;
+    burstStart = -1;
+    burstEnd = -1;
+    mustBurst = 0;
+    while (ptr < rowBufferIndex) {
+        skip = 0;
+        if (ptr == 0 || skipped == 30 || rowBuffer[ptr] != prevRowBuffer[ptr]
+            || (burstStart != -1 && ptr == rowBufferIndex - 1)) {
+            /* we want to output this byte... */
+            if (burstStart == -1) {
+                burstStart = ptr;
+            }
+            if (ptr - burstStart == 7 || ptr == rowBufferIndex - 1) {
+                /* we have to output it now... */
+                burstEnd = ptr;
+                mustBurst = 1;
+            }
+        } else {
+            /* duplicate byte, we can skip it */
+            if (burstStart != -1) {
+                burstEnd = ptr - 1;
+                mustBurst = 1;
+            }
+            skip = 1;
+        }
+        if (mustBurst) {
+            burstCode = burstEnd - burstStart; 
+                /* 0-7, means 1-8 bytes follow */
+            code = (burstCode << 5) | skipped;
+            deltaBuffer[deltaBufferIndex++] = (char) code;
+            memcpy(deltaBuffer+deltaBufferIndex, rowBuffer+burstStart, 
+                   burstCode + 1);
+            deltaBufferIndex += burstCode + 1;
+            burstStart = -1;
+            burstEnd = -1;
+            mustBurst = 0;
+            skipped = 0;
+        }
+        if (skip) {
+            ++skipped;
+        }
+        ++ptr;
+    }
+}
+
+
+
+static void
+findRightmostBlackCol(const bit *    const bitrow,
+                      unsigned int   const cols,
+                      bool *         const allWhiteP,
+                      unsigned int * const blackColP) {
+
+    int i;
+
+    for (i = cols - 1; i >= 0 && bitrow[i] == PBM_WHITE; --i);
+
+    if (i < 0)
+        *allWhiteP = TRUE;
+    else {
+        *allWhiteP = FALSE;
+        *blackColP = i;
+    }
+}
+
+
+
+static void
+convertRow(const bit *    const bitrow,
+           unsigned int   const cols,
+           bool           const pack,
+           bool           const delta,
+           bool *         const rowIsBlankP) {
+
+    unsigned int rightmostBlackCol;
+        
+    findRightmostBlackCol(bitrow, cols, rowIsBlankP, &rightmostBlackCol);
+
+    if (!*rowIsBlankP) {
+        unsigned int const nzcol = rightmostBlackCol + 1;
+            /* Number of columns excluding white right margin */
+        unsigned int const rucols = ((nzcol + 7) / 8) * 8;
+            /* 'nzcol' rounded up to nearest multiple of 8 */
+        
+        unsigned int col;
+
+        memset(rowBuffer, 0, rowBufferSize);
+
+        rowBufferIndex = 0;
+
+        /* Generate the unpacked data */
+
+        for (col = 0; col < nzcol; ++col)
+            putbit(bitrow[col]);
+
+        /* Pad out to a full byte with white */
+        for (col = nzcol; col < rucols; ++col)
+            putbit(0);
+
+        putflush();
+
+        /* Try optional compression algorithms */
+
+        if (pack)
+            packbits();
+        else
+            packBufferIndex = rowBufferIndex + 999;
+
+        if (delta) {
+            /* May need to temporarily bump the row buffer index up to
+               whatever the previous line's was - if this line is shorter 
+               than the previous would otherwise leave dangling cruft.
+            */
+            unsigned int const savedRowBufferIndex = rowBufferIndex;
+
+            if (rowBufferIndex < prevRowBufferIndex)
+                rowBufferIndex = prevRowBufferIndex;
+
+            deltarow();
+
+            rowBufferIndex = savedRowBufferIndex;
+        } else
+            deltaBufferIndex = packBufferIndex + 999;
+    }
+}
+
+
+    
+static void
+printBlankRows(unsigned int const count) {
+
+    if (count > 0) {
+        unsigned int x;
+        /* The code used to be this, but Charles Howes reports that
+           this escape sequence does not exist on his HP Laserjet IIP
+           plus, so we use the following less elegant code instead.
+           
+           printf("\033*b%dY", (*blankRowsP)); 
+        */
+        for (x = 0; x < count; ++x) 
+            printf("\033*b0W");
+        
+        memset(prevRowBuffer, 0, rowBufferSize);
+    }
+}
+
+
+
+static void
+establishMode(int const newMode) {
+
+    static int mode = -1;
+
+    if (mode != newMode) {
+        printf("\033*b%uM", newMode);
+        mode = newMode;
+    }
+}
+
+
+
+static void
+printRow(void) {
+
+    if (deltaBufferIndex < packBufferIndex &&
+        deltaBufferIndex < rowBufferIndex) {
+        assert(deltaBufferIndex <= deltaBufferSize);
+        /*
+          It's smallest when delta'ed
+        */
+        establishMode(3);
+
+        printf("\033*b%dW", deltaBufferIndex);
+        fwrite(deltaBuffer, 1, deltaBufferIndex, stdout);
+    } else if (rowBufferIndex <= packBufferIndex) {
+        assert (rowBufferIndex <= rowBufferSize);
+        /*
+          It didn't pack - send it unpacked
+        */
+        establishMode(0);
+
+        printf("\033*b%dW", rowBufferIndex);
+        fwrite(rowBuffer, 1, rowBufferIndex, stdout);
+    } else {
+        assert (packBufferIndex <= packBufferSize);
+        /*
+          It's smaller when packed
+        */
+        establishMode(2);
+
+        printf("\033*b%dW", packBufferIndex);
+        fwrite(packBuffer, 1, packBufferIndex, stdout);
+    }
+    memcpy(prevRowBuffer, rowBuffer, rowBufferSize);
+    prevRowBufferIndex = rowBufferIndex;
+}
+
+
+
+static void
+doPage(FILE *             const ifP, 
+       struct cmdlineInfo const cmdline) {
+
+    bit * bitrow;
+    int rows, cols, format, row;
+    unsigned int blankRows;
+    bool rowIsBlank;
+
+    pbm_readpbminit(ifP, &cols, &rows, &format);
+
+    bitrow = pbm_allocrow(cols);
+
+    allocateBuffers(cols);
+
+    putinit(cmdline);
+
+    blankRows = 0;
+    prevRowBufferIndex = 0;
+    memset(prevRowBuffer, 0, rowBufferSize);
+
+    for (row = 0; row < rows; ++row) {
+        pbm_readpbmrow(ifP, bitrow, cols, format);
+
+        convertRow(bitrow, cols, cmdline.pack, cmdline.delta,
+                   &rowIsBlank);
+
+        if (rowIsBlank)
+            ++blankRows;
+        else {
+            printBlankRows(blankRows);
+            blankRows = 0;
+            
+            printRow();
+        }
+    }    
+    printBlankRows(blankRows);
+    blankRows = 0;
+
+    putrest(!cmdline.noreset);
+
+    freeBuffers();
+    pbm_freerow(bitrow);
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    bool eof;
+
+    pbm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilename);
+
+    eof = FALSE;
+    while (!eof) {
+        doPage(ifP, cmdline);
+        pbm_nextimage(ifP, &eof);
+    }
+
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/converter/pbm/pbmtoln03.c b/converter/pbm/pbmtoln03.c
new file mode 100644
index 00000000..07c8629f
--- /dev/null
+++ b/converter/pbm/pbmtoln03.c
@@ -0,0 +1,260 @@
+/*
+From tim@deakin.edu.au Fri May  7 00:18:57 1993
+From: Tim Cook <tim@deakin.edu.au>
+Date: Fri, 7 May 1993 15:18:34 -0500
+X-Mailer: Mail User's Shell (7.2.4 2/2/92)
+To: dyson@sunfish.Physics.UIowa.Edu
+Subject: Re: DEC LN03+ printer (not postscript) under SunOS (on SS10) ?
+Content-Length: 5893
+
+In a message dated 6 May,  9:32, dyson@sunfish.Physics.UIowa.Edu
+(Richard L. Dyson) wrote:
+> > Just in case anyone is interested, I have a pbmtoln03 utility I wrote
+> > when I was mucking about with an LN03+.  If you are interested in
+> > printing your bitmaps at 300x300dpi, ask me for the source.
+>
+> I would be interested.  We still only have LN03+ printers on our VMS
+> machines here...
+
+Ok, here goes.  Note that you will need the source from the pbmplus
+utilities, because my pbmtoln03 utility uses the library routines that
+are a part of pbmplus to read a PBM file (I linked it with libpbm.a).
+I have not tested this utility on VMS, but it looks like it should
+work.
+*/
+
+/* pbmtoln03.c -        Converts a PBM bitmap to DEC LN03 SIXEL bitmap
+ *
+ * SYNOPSIS
+ *      pbmtoln03 [pbm-file]
+ *
+ * OPTIONS
+ *      -l nn   Use "nn" as value for left margin (default 0).
+ *      -r nn   Use "nn" as value for right margin (default 2400).
+ *      -t nn   Use "nn" as value for top margin (default 0).
+ *      -b nn   Use "nn" as value for bottom margin (default 3400).
+ *      -f nn   Use "nn" as value for form length (default 3400).
+ *
+ * Tim Cook, 26 Feb 1992
+ * changed option parsing to PBM standards  - Ingo Wilken, 13 Oct 1993
+ */
+
+#include <stdio.h>
+#include "pbm.h"
+
+FILE *input ;
+
+#ifndef print
+#define print(s)        fputs (s, stdout)
+#define fprint(f, s)    fputs (s, f)
+#endif
+
+
+static void 
+output_sixel_record (unsigned char * record, int width) {
+
+   int i, j, k ;
+   unsigned char last_char ;
+   int start_repeat = 0 ;
+   int repeated ;
+   char repeated_str[16] ;
+   char *p ;
+
+   /* Do RLE */
+   last_char = record[0] ;
+   j = 0 ;
+
+   /* This will make the following loop complete */
+   record[width] = '\0' ;
+
+   for (i = 1 ; i <= width ; i++) {
+
+      repeated = i - start_repeat ;
+
+      if (record[i] != last_char || repeated >= 32766) {
+
+         /* Repeat has ended */
+
+         if (repeated > 3) {
+
+            /* Do an encoding */
+            record[j++] = '!' ;
+            sprintf (repeated_str, "%d", i - start_repeat) ;
+            for (p = repeated_str ; *p ; p++)
+               record[j++] = *p ;
+               record[j++] = last_char ; }
+
+         else {
+            for (k = 0 ; k < repeated ; k++)
+               record[j++] = last_char ; }
+
+         start_repeat = i ;
+         last_char = record[i] ; }
+      }
+
+   fwrite ((char *) record, j, 1, stdout) ;
+   putchar ('-') ;      /* DECGNL (graphics new-line) */
+   putchar ('\n') ;
+   }
+
+
+static void 
+convert (int width, int height, int format) {
+
+   int i ;
+   unsigned char *sixel ;               /* A row of sixels */
+   int sixel_row ;
+
+   bit *row = pbm_allocrow (width) ;
+
+   sixel = (unsigned char *) malloc (width + 2) ;
+
+   sixel_row = 0 ;
+   while (height--) {
+      pbm_readpbmrow (input, row, width, format) ;
+      switch (sixel_row) {
+         case 0 :
+           for (i = 0 ; i < width ; i++)
+              sixel[i] = row[i] ;
+           break ;
+         case 1 :
+           for (i = 0 ; i < width ; i++)
+              sixel[i] += row[i] << 1 ;
+           break ;
+         case 2 :
+           for (i = 0 ; i < width ; i++)
+              sixel[i] += row[i] << 2 ;
+           break ;
+         case 3 :
+           for (i = 0 ; i < width ; i++)
+              sixel[i] += row[i] << 3 ;
+           break ;
+         case 4 :
+           for (i = 0 ; i < width ; i++)
+              sixel[i] += row[i] << 4 ;
+           break ;
+         case 5 :
+           for (i = 0 ; i < width ; i++)
+              sixel[i] += (row[i] << 5) + 077 ;
+           output_sixel_record (sixel, width) ;
+           break ; }
+      if (sixel_row == 5)
+         sixel_row = 0 ;
+      else
+         sixel_row++ ;
+      }
+
+   if (sixel_row > 0) {
+      /* Incomplete sixel record needs to be output */
+      for (i = 0 ; i < width ; i++)
+         sixel[i] += 077 ;
+      output_sixel_record (sixel, width) ; }
+   }
+
+
+
+int
+main (int argc, char **argv) {
+   int argc_copy = argc ;
+   char **argv_copy = argv ;
+   int argn;
+   const char * const usage = "[-left <nn>] [-right <nn>] [-top <nn>] "
+       "[-bottom <nn>] [-formlength <nn>] [pbmfile]";
+
+   /* Options */
+   /* These defaults are for a DEC LN03 with A4 paper (2400x3400 pixels) */
+   const char *opt_left_margin = "0";
+   const char *opt_top_margin = opt_left_margin;
+   const char *opt_right_margin = "2400";
+   const char *opt_bottom_margin = "3400";
+   const char *opt_form_length = opt_bottom_margin;
+
+   int width, height, format ;
+
+   pbm_init (&argc_copy, argv_copy) ;
+
+   argn = 1;
+   while( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) {
+      if( pm_keymatch(argv[argn], "-left", 2) ) {
+         if( ++argn >= argc )
+            pm_usage(usage);
+         opt_left_margin = argv[argn];
+      }
+      else
+      if( pm_keymatch(argv[argn], "-right", 2) ) {
+         if( ++argn >= argc )
+            pm_usage(usage);
+         opt_right_margin = argv[argn];
+      }
+      else
+      if( pm_keymatch(argv[argn], "-top", 2) ) {
+         if( ++argn >= argc )
+            pm_usage(usage);
+         opt_top_margin = argv[argn];
+      }
+      else
+      if( pm_keymatch(argv[argn], "-bottom", 2) ) {
+         if( ++argn >= argc )
+            pm_usage(usage);
+         opt_bottom_margin = argv[argn];
+      }
+      else
+      if( pm_keymatch(argv[argn], "-formlength", 2) ) {
+         if( ++argn >= argc )
+            pm_usage(usage);
+         opt_form_length = argv[argn];
+      }
+      else
+         pm_usage(usage);
+      ++argn;
+   }
+
+   if( argn < argc ) {
+      input = pm_openr( argv[argn] );
+      argn++;
+   }
+   else
+      input = stdin;
+
+   if( argn != argc )
+      pm_usage(usage);
+
+
+   /* Initialize pbm file */
+   pbm_readpbminit (input, &width, &height, &format) ;
+
+   if (format != PBM_FORMAT && format != RPBM_FORMAT)
+      pm_error ("input not in PBM format") ;
+
+/*
+ * In explanation of the sequence below:
+ *      <ESC>[!p        DECSTR  soft terminal reset
+ *      <ESC>[11h       PUM     select unit of measurement
+ *      <ESC>[7 I       SSU     select pixel as size unit
+ *      <ESC>[?52l      DECOPM  origin is corner of printable area
+ *      <ESC>[%s;%ss    DECSLRM left and right margins
+ *      <ESC>[%s;%sr    DECSTBM top and bottom margins
+ *      <ESC>[%st       DECSLPP form length
+ *      <ESC>P0;0;1q            select sixel graphics mode
+ *      "1;1            DECGRA  aspect ratio (1:1)
+ */
+
+   /* Initialize sixel file */
+   printf ("\033[!p\033[11h\033[7 I\033[?52l\033[%s;%ss\033"
+           "[%s;%sr\033[%st\033P0;0;1q\"1;1",
+      opt_left_margin, opt_right_margin, opt_top_margin, opt_bottom_margin,
+      opt_form_length);
+
+   /* Convert data */
+   convert (width, height, format) ;
+
+   /* Terminate sixel data */
+   print ("\033\\\n") ;
+
+   /* If the program failed, it previously aborted with nonzero completion
+      code, via various function calls.
+   */
+   return 0;
+}
+
+
diff --git a/converter/pbm/pbmtolps.c b/converter/pbm/pbmtolps.c
new file mode 100644
index 00000000..13a14e2b
--- /dev/null
+++ b/converter/pbm/pbmtolps.c
@@ -0,0 +1,181 @@
+/*
+ * pbmtolps -- convert a Portable BitMap into Postscript.  The
+ * output Postscript uses lines instead of the image operator to
+ * generate a (device dependent) picture which will be imaged
+ * much faster.
+ *
+ * The Postscript path length is constrained to be less that 1000
+ * points so that no limits are overrun on the Apple Laserwriter
+ * and (presumably) no other printers.
+ *
+ * To do:
+ *      make sure encapsulated format is correct
+ *      repitition of black-white strips
+ *      make it more device independent (is this possible?)
+ *
+ * Author:
+ *      George Phillips <phillips@cs.ubc.ca>
+ *      Department of Computer Science
+ *      University of British Columbia
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include "nstring.h"
+#include "pbm.h"
+
+
+static int prev_white = -1;
+static int prev_black = -1;
+static char cmd = '\0';
+static int pointcount = 2;
+
+#ifdef RUN
+static int run = 1;
+#endif
+
+static char 
+morepoints(char cmd, int howmany_pbmtolps) {
+    pointcount += 2;
+    if (pointcount > 1000) {
+        pointcount = 2;
+        cmd += 'm' - 'a';
+    }
+    return(cmd);
+}
+
+
+
+static void 
+addstrip(int const white, 
+         int const black) {
+
+    if (cmd) {
+#ifdef RUN
+        if (white == prev_white && black == prev_black)
+            run++;
+        else {
+            if (run == 1)
+#endif
+                printf("%d %d %c ", 
+                       prev_black, prev_white, morepoints(cmd, 2));
+#ifdef RUN
+            else
+                                /* of course, we need to give a new command */
+                printf("%d %d %d %c ",
+                       prev_white, prev_black, run,
+                       morepoints(cmd + 'f' - 'a', 2 * run));
+            run = 1;
+        }
+#endif
+    }
+
+    prev_white = white;
+    prev_black = black;
+    cmd = 'a';
+}
+
+
+
+static void 
+nextline(void) {
+    /* need to check run, should have an outcommand */
+    if (cmd)
+        printf("%d %d %c\n", prev_black, prev_white, morepoints('c', 3));
+    else
+        printf("%c\n", morepoints('b', 1));
+    cmd = '\0';
+}
+
+
+
+int
+main(int argc, char ** argv) {
+    FILE*   fp;
+    bit*    bits;
+    int             row;
+    int             col;
+    int         rows;
+    int             cols;
+    int             format;
+    int             white;
+    int             black;
+    const char*   name;
+    float   dpi = 300.0;
+    float   sc_rows;
+    float   sc_cols;
+    int             i;
+    const char*   const usage = "[ -dpi n ] [ pbmfile ]";
+
+
+	pbm_init(&argc, argv);
+
+    i = 1;
+    if (i < argc && STREQ(argv[i], "-dpi")) {
+        if (i == argc - 1)
+            pm_usage(usage);
+        sscanf(argv[i + 1], "%f", &dpi);
+        i += 2;
+    }
+
+    if (i < argc - 1)
+        pm_usage(usage);
+
+    if (i == argc) {
+        name = "noname";
+        fp = stdin;
+    } else {
+        name = argv[i];
+        fp = pm_openr(name);
+    }
+    pbm_readpbminit(fp, &cols, &rows, &format);
+    bits = pbm_allocrow(cols);
+
+    sc_rows = (float)rows / dpi * 72.0;
+    sc_cols = (float)cols / dpi * 72.0;
+
+    puts("%!PS-Adobe-2.0 EPSF-2.0");
+    puts("%%Creator: pbmtolps");
+    printf("%%%%Title: %s\n", name);
+    printf("%%%%BoundingBox: %d %d %d %d\n",
+           (int)(305.5 - sc_cols / 2.0),
+           (int)(395.5 - sc_rows / 2.0),
+           (int)(306.5 + sc_cols / 2.0),
+           (int)(396.5 + sc_rows / 2.0));
+    puts("%%EndComments");
+    puts("%%EndProlog");
+    puts("gsave");
+
+    printf("%f %f translate\n", 306.0 - sc_cols / 2.0, 396.0 + sc_rows / 2.0);
+    printf("72 %f div dup neg scale\n", dpi);
+    puts("/a { 0 rmoveto 0 rlineto } def");
+    puts("/b { 0 row 1 add dup /row exch def moveto } def");
+    puts("/c { a b } def");
+    puts("/m { currentpoint stroke newpath moveto a } def");
+    puts("/n { currentpoint stroke newpath moveto b } def");
+    puts("/o { currentpoint stroke newpath moveto c } def");
+    puts("/row 0 def");
+    puts("newpath 0 0 moveto");
+
+    for (row = 0; row < rows; row++) {
+        pbm_readpbmrow(fp, bits, cols, format);
+        /* output white-strip+black-strip sequences */
+        for (col = 0; col < cols; ) {
+            for (white = 0; col < cols && bits[col] == PBM_WHITE; col++)
+                white++;
+            for (black = 0; col < cols && bits[col] == PBM_BLACK; col++)
+                black++;
+
+            if (black != 0)
+                addstrip(white, black);
+        }
+        nextline();
+    }
+    puts("stroke grestore showpage");
+    puts("%%Trailer");
+
+    pm_close(fp);
+
+    exit(0);
+}
diff --git a/converter/pbm/pbmtomacp.c b/converter/pbm/pbmtomacp.c
new file mode 100644
index 00000000..82b55904
--- /dev/null
+++ b/converter/pbm/pbmtomacp.c
@@ -0,0 +1,301 @@
+/* pbmtomacp.c - read a portable bitmap and produce a MacPaint bitmap file
+**
+** Copyright (C) 1988 by Douwe vand der Schaaf.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "pbm.h"
+#include "macp.h"
+
+#define TRUE		1
+#define FALSE		0
+#define EQUAL		1
+#define UNEQUAL		0
+
+static void fillbits ARGS(( bit **bits, bit **bitsr, int top, int left, int bottom, int right ));
+static void writemacp ARGS(( bit **bits ));
+static int packit ARGS(( bit *pb, bit *bits ));
+static void filltemp ARGS(( bit *dest, bit *src ));
+static void sendbytes ARGS(( bit *pb, register int npb ));
+static void header ARGS(( void ));
+
+static FILE *fdout;
+
+int
+main(argc, argv)
+int argc;
+char *argv[];
+{ FILE *ifp;
+  register bit **bits, **bitsr;
+  int argn, rows, cols;
+  int left,bottom,right,top;
+  int lflg, rflg, tflg, bflg;
+  char name[100];
+  const char * const usage = "[-l left] [-r right] [-b bottom] [-t top] [pbmfile]";
+
+
+  pbm_init( &argc, argv );
+
+  argn = 1;
+  fdout = stdout;
+  lflg = rflg = tflg = bflg = 0;
+  left = right = top = bottom = 0;  /* To quiet compiler warning */
+
+  while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+  { switch ( argv[argn][1] )
+    { case 'l':
+      lflg++;
+      argn++;
+      left = atoi( argv[argn] );
+      break;
+
+      case 'r':
+      rflg++;
+      argn++;
+      right = atoi( argv[argn] );
+      break;
+
+      case 't':
+      tflg++;
+      argn++;
+      top = atoi( argv[argn] );
+      break;
+
+      case 'b':
+      bflg++;
+      argn++;
+      bottom = atoi( argv[argn] );
+      break;
+
+      case '?':
+      default:
+      pm_usage( usage );
+    }
+    ++argn;
+  }
+
+  if ( argn == argc )
+  { ifp = stdin;
+    strcpy( name, "noname" );
+  }
+  else
+  { ifp = pm_openr( argv[argn] );
+    strcpy( name, argv[argn] );
+    ++argn;
+  }
+
+  if ( argn != argc )
+    pm_usage( usage );
+
+  bitsr = pbm_readpbm( ifp, &cols, &rows );
+
+  pm_close( ifp );
+
+  bits = pbm_allocarray( MAX_COLS, MAX_LINES );
+
+  if( !lflg )
+    left = 0;
+
+  if( rflg )
+  { if( right - left >= MAX_COLS )
+      right = left + MAX_COLS - 1;
+  }
+  else
+    right = ( left + MAX_COLS > cols ) ? ( cols - 1 ) : ( left + MAX_COLS - 1 );
+
+  if( !tflg )
+    top = 0;
+
+  if( bflg )
+  { if( bottom - top >= MAX_LINES )
+      bottom = top + MAX_LINES - 1;
+  }
+  else
+    bottom = ( top + MAX_LINES > rows ) ?
+		   ( rows - 1 ) : ( top + MAX_LINES - 1 );
+  
+    if( right <= left || left < 0 || right - left + 1 > MAX_COLS )
+      pm_error("error in right (= %d) and/or left (=%d)",right,left );
+    if( bottom <= top || top < 0 || bottom - top + 1 > MAX_LINES )
+      pm_error("error in bottom (= %d) and/or top (=%d)",bottom,top );
+
+  fillbits( bits, bitsr, top, left, bottom, right );
+
+  writemacp( bits );
+
+  exit( 0 );
+
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* centreer het over te zenden plaatje in het MacPaint document
+ *
+ * Het plaatje wordt vanaf al of niet opgegeven (left, bottom)
+ * in een pbm bitmap van de juist macpaint afmetingen gezet,
+ * en eventueel afgekapt.
+ */
+static void
+fillbits( bits, bitsr, top, left, bottom, right )
+bit **bits, **bitsr;
+int top, left, bottom, right;
+{ register bit *bi, *bir;
+  register int i, j;
+  register int bottomr, leftr, topr, rightr;
+  int width, height;
+
+  width = right - left + 1;
+  leftr = (MAX_COLS - width) / 2;
+  rightr = leftr + width - 1;
+
+  height = bottom - top + 1;
+  topr = ( MAX_LINES - height ) / 2;
+  bottomr = topr + height - 1;
+
+  for( i = 0; i < topr; i++ )
+  { bi = bits[i];
+    for( j = 0; j < MAX_COLS; j++ )
+      *bi++ = 0;
+  }
+
+  for( i = topr; i <= bottomr; i++ )
+  { bi = bits[i];
+    { for( j = 0; j < leftr; j++ )
+	*bi++ = 0;
+      bir = bitsr[ i - topr + top ];
+      for( j = leftr; j <= rightr; j++ )
+	*bi++ = bir[j - leftr + left];
+      for( j = rightr + 1; j < MAX_COLS; j++ )
+	*bi++ = 0;
+  } }
+
+  for( i = bottomr + 1; i < MAX_LINES; i++ )
+  { bi = bits[i];
+    for( j = 0; j < MAX_COLS; j++ )
+      *bi++ = 0;
+  }
+} /* fillbits */
+      
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+static void
+writemacp( bits )
+bit **bits;
+{ register int i;
+  bit pb[MAX_COLS * 2];
+  int npb;
+
+  header();
+  for( i=0; i < MAX_LINES; i++ )
+  { npb = packit( pb, bits[i] );
+    sendbytes( pb, npb );
+  }
+} /* writemacp */
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* pack regel van MacPaint doc in Apple's format
+ * return value = # of bytes in pb 
+ */
+static int
+packit( pb, bits )
+     bit *pb, *bits;
+{ register int charcount, npb, newcount, flg;
+  bit temp[72];
+  bit *count, *srcb, *destb, save;
+
+  srcb = bits; destb = temp;
+  filltemp( destb, srcb );
+  srcb = temp;
+  destb = pb;
+  npb = 0;
+  charcount = BYTES_WIDE;
+  flg = EQUAL;
+  while( charcount ) { 
+      save = *srcb++;
+      charcount--;
+      newcount = 1;
+      while( (*srcb == save) && charcount ) { 
+          srcb++;
+          newcount++;
+          charcount--;
+      }
+      if( newcount > 2 ) { 
+          count = destb++;
+          *count = 257 - newcount;
+          *destb++ = save;
+          npb += 2;
+          flg = EQUAL;
+      } else { 
+          if( flg == EQUAL ) { 
+              count = destb++;
+              *count = newcount - 1;
+              npb++;
+          } else
+            *count += newcount;
+          while( newcount-- ) { 
+              *destb++ = save;
+              npb++;
+          }
+          flg = UNEQUAL;
+      } 
+  }
+  return npb;
+} /* packit */
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+static void
+filltemp( dest, src )
+bit *dest, *src;
+{ register unsigned char ch, zero, acht;
+  register int i, j;
+
+  zero = '\0';
+  acht = 8;
+  i = BYTES_WIDE;
+  while( i-- )
+  { ch = zero; 
+    j = acht;
+    while( j-- )
+    { ch <<= 1;
+      if( *src++ )
+	ch++;
+    }
+    *dest++ = ch;
+  }
+} /* filltemp */
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+static void
+sendbytes( pb, npb )
+bit *pb;
+register int npb;
+{ register bit *b;
+
+  b = pb;
+  while( npb-- )
+    (void) putc( *b++, fdout );
+} /* sendbytes */
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+static void
+header()
+{ register int i;
+  register char ch;
+
+  /* header contains nothing ... */
+  ch = '\0';
+  for(i = 0; i < HEADER_LENGTH; i++ )
+    (void) putc( ch, fdout );
+} /* header */
diff --git a/converter/pbm/pbmtomatrixorbital.c b/converter/pbm/pbmtomatrixorbital.c
new file mode 100644
index 00000000..79347978
--- /dev/null
+++ b/converter/pbm/pbmtomatrixorbital.c
@@ -0,0 +1,87 @@
+#include "pbm.h"
+
+/* By Bryan Henderson, San Jose CA 2003.09.06.
+
+   Contributed to the public domain by its author.
+
+   This is a replacement of Joseph Sheedy's (hbarover2@yahoo.com) Perl
+   program of the same name, distributed in his Pbmtomatrixorbital
+   package.  This version uses Netpbm libraries and is fully
+   consistent with other Netpbm programs.
+*/
+
+
+static void
+generateMo(FILE * const ofP, 
+           bit ** const bits,
+           int    const cols,
+           int    const rows) {
+
+    unsigned int col;
+
+    fputc(cols, ofP);
+    fputc(rows, ofP);
+
+    for (col = 0; col < cols; ++col) {
+        unsigned int row;
+        unsigned int outbitpos;
+        unsigned char outchar;
+        
+        outbitpos = 0;  /* Start at 1st bit of 1st output byte */
+
+        for (row = 0; row < rows; ++row) {
+            if (outbitpos == 0)
+                /* We're starting a new byte; initialize it to zeroes */
+                outchar = 0;
+
+            outchar |= bits[row][col] << outbitpos;
+
+            if (outbitpos == 7) 
+                /* We filled up a byte.  Output it. */
+                fputc(outchar, ofP);
+
+            outbitpos = (outbitpos + 1) % 8;
+        }
+        if (outbitpos != 0)
+            /* Our last byte is partial, so must be output now. */
+            fputc(outchar, ofP);
+    }
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    FILE* ifp;
+    bit** bits;
+    int rows, cols;
+    const char * inputFilename;
+
+    pbm_init(&argc, argv);
+
+    if (argc-1 > 0)
+        pm_error("Too many arguments (%d).  The only valid argument is an "
+                 "input file name.", argc-1);
+    else if (argc-1 == 1) 
+        inputFilename = argv[1];
+    else
+        inputFilename = "-";
+
+    ifp = pm_openr(inputFilename);
+    
+    bits = pbm_readpbm(ifp, &cols, &rows);
+
+    if (rows > 255)
+        pm_error("Image is too high:  %d rows.  Max height: 255 rows", rows);
+    if (cols > 255)
+        pm_error("Image is too wide:  %d cols.  Max width: 255 cols", cols);
+
+    generateMo(stdout, bits, cols, rows);
+    
+    pm_close(ifp);
+
+    pbm_freearray(bits, rows);
+
+    exit(0);
+}
diff --git a/converter/pbm/pbmtomda.c b/converter/pbm/pbmtomda.c
new file mode 100644
index 00000000..3ad51499
--- /dev/null
+++ b/converter/pbm/pbmtomda.c
@@ -0,0 +1,201 @@
+
+/***************************************************************************
+
+    PBMTOMDA: Convert portable bitmap to Microdesign area
+    Copyright (C) 1999,2004 John Elliott <jce@seasip.demon.co.uk>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+******************************************************************************/
+
+#include <stdio.h>
+#include <string.h>
+
+#include "pbm.h"
+#include "mallocvar.h"
+
+/* I'm being somewhat conservative in the PBM -> MDA translation. I output 
+ * only the MD2 format and don't allow RLE over the ends of lines.
+ */
+
+typedef unsigned char mdbyte;
+
+static FILE *infile;
+static mdbyte header[128];
+static int bInvert = 0;
+static int bScale  = 0;
+
+/* Encode 8 pixels as a byte */
+
+static mdbyte 
+encode(bit ** const bits, int const row, int const col)
+{
+    int n;
+    int mask;
+    mdbyte b;
+
+    mask = 0x80;   /* initial value */
+    b = 0;  /* initial value */
+
+    for (n = 0; n < 8; n++) {
+        if (bits[row][col+n] == PBM_BLACK) b |= mask;
+        mask = mask >> 1;
+    }
+    return b;
+}
+
+/* Translate a pbm to MD2 format, one row at a time */
+
+static void 
+do_translation(bit ** const bits, 
+               int    const nOutCols, 
+               int    const nOutRows,
+               int    const nInRows)
+{
+    int row;
+    mdbyte *mdrow;  /* malloc'ed */
+
+    int const step = bScale ? 2 : 1;
+
+    MALLOCARRAY(mdrow, nOutCols);
+
+    if (mdrow == NULL)
+        pm_error("Not enough memory for conversion.");
+
+    for (row = 0; row < nOutRows; row+=step)
+    {
+        int col;
+        int x1;
+
+        /* Encode image into non-compressed bitmap */
+        for (col = 0; col < nOutCols; ++col) {
+            mdbyte b;
+
+            if (row < nInRows)
+                b = encode(bits, row, col*8);
+            else
+                b = 0xff;  /* All black */
+
+            mdrow[col] = bInvert ? b : ~b;
+        }
+
+        /* Encoded. Now RLE it */
+        for (col = 0; col < nOutCols; )
+        {
+            mdbyte const b = mdrow[col];
+
+            if (b != 0xFF && b != 0) /* Normal byte */
+            {
+                putchar(b);
+                ++col;
+            }
+            else    /* RLE a run of 0s or 0xFFs */
+            {
+                for (x1 = col; x1 < nOutCols; x1++)
+                {
+                    if (mdrow[x1] != b) break;
+                    if (x1 - col > 256) break;
+                }
+                x1 -= col;    /* x1 = no. of repeats */
+                if (x1 == 256) x1 = 0;
+                putchar(b);
+                putchar(x1);
+                col += x1;        
+            }   
+        }
+    }
+    free(mdrow);
+}
+
+
+static void usage(char *s)
+{        
+    printf("pbmtomda v1.01, Copyright (C) 1999,2004 John Elliott <jce@seasip.demon.co.uk>\n"
+         "This program is redistributable under the terms of the GNU General Public\n"
+                 "License, version 2 or later.\n\n"
+                 "Usage: %s [ -d ] [ -i ] [ -- ] [ infile ]\n\n"
+                 "-d: Halve height (to compensate for the PCW aspect ratio)\n"
+                 "-i: Invert colors\n"
+                 "--: No more options (use if filename begins with a dash)\n",
+        s);
+
+    exit(0);
+}
+
+int main(int argc, char **argv)
+{
+    int nOutRowsUnrounded;  /* Before rounding up to multiple of 4 */
+    int nOutCols, nOutRows;
+    int nInCols, nInRows;
+    bit **bits;
+    int rc;
+
+    int n, optstop = 0;
+    char *fname = NULL;
+
+    pbm_init(&argc, argv);
+
+    /* Output v2-format MDA images. Simulate MDA header...
+     * 2004-01-11: Hmm. Apparently some (but not all) MDA-reading 
+     * programs insist on the program identifier being exactly 
+     * 'MicroDesignPCW'. The spec does not make this clear. */
+    strcpy((char*) header, ".MDAMicroDesignPCWv1.00\r\npbm2mda\r\n");
+
+    for (n = 1; n < argc; n++)
+    {
+        if (argv[n][0] == '-' && !optstop)
+        {   
+            if (argv[n][1] == 'd' || argv[n][1] == 'D') bScale = 1;
+            if (argv[n][1] == 'i' || argv[n][1] == 'I') bInvert = 1;
+            if (argv[n][1] == 'h' || argv[n][1] == 'H') usage(argv[0]);
+            if (argv[n][1] == '-' && argv[n][2] == 0 && !fname)     /* "--" */
+            {
+                optstop = 1;
+            }
+            if (argv[n][1] == '-' && (argv[n][2] == 'h' || argv[n][2] == 'H')) usage(argv[0]);
+        }
+        else if (argv[n][0] && !fname)  /* Filename */
+        {
+            fname = argv[n];
+        }
+    }
+
+    if (fname) infile = pm_openr(fname);
+    else       infile = stdin;
+
+    bits = pbm_readpbm(infile, &nInCols, &nInRows);
+    
+    nOutRowsUnrounded = bScale ? nInRows/2 : nInRows;
+
+    nOutRows = ((nOutRowsUnrounded + 3) / 4) * 4;
+        /* MDA wants rows a multiple of 4 */   
+    nOutCols = nInCols / 8;
+
+    rc = fwrite(header, 1, 128, stdout);
+    if (rc < 128)
+        pm_error("Unable to write header to output file.  errno=%d (%s)",
+                 errno, strerror(errno));
+
+    pm_writelittleshort(stdout, nOutRows);
+    pm_writelittleshort(stdout, nOutCols);
+
+    do_translation(bits, nOutCols, nOutRows, nInRows);
+
+    pm_close(infile);
+    fflush(stdout);
+    pbm_freearray(bits, nInRows);
+    
+    return 0;
+}
diff --git a/converter/pbm/pbmtomgr.c b/converter/pbm/pbmtomgr.c
new file mode 100644
index 00000000..7a6e7fc1
--- /dev/null
+++ b/converter/pbm/pbmtomgr.c
@@ -0,0 +1,120 @@
+/* pbmtomgr.c - read a portable bitmap and produce a MGR bitmap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+#include "mgr.h"
+
+static void putinit ARGS(( int rows, int cols ));
+static void putbit ARGS(( bit b ));
+static void putrest ARGS(( void ));
+static void putitem ARGS(( void ));
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    bit* bitrow;
+    register bit* bP;
+    int rows, cols, format, padright, row, col;
+
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[pbmfile]" );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    pbm_readpbminit( ifp, &cols, &rows, &format );
+    bitrow = pbm_allocrow( cols );
+    
+    /* Round cols up to the nearest multiple of 8. */
+    padright = ( ( cols + 7 ) / 8 ) * 8 - cols;
+
+    putinit( rows, cols );
+    for ( row = 0; row < rows; ++row )
+	{
+	pbm_readpbmrow( ifp, bitrow, cols, format );
+        for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
+	    putbit( *bP );
+	for ( col = 0; col < padright; ++col )
+	    putbit( 0 );
+        }
+
+    pm_close( ifp );
+
+    putrest( );
+
+    exit( 0 );
+    }
+
+static unsigned char item;
+static int bitsperitem, bitshift;
+
+static void
+putinit( rows, cols )
+    int rows, cols;
+    {
+    struct b_header head;
+
+    head.magic[0] = 'y';
+    head.magic[1] = 'z';
+    head.h_wide = ( ( cols >> 6 ) & 0x3f ) + ' ';
+    head.l_wide = ( cols & 0x3f ) + ' ';
+    head.h_high = ( ( rows >> 6 ) & 0x3f ) + ' ';
+    head.l_high = ( rows & 0x3f ) + ' ';
+    head.depth = ( 1 & 0x3f ) + ' ';
+    head._reserved = ' ';
+    fwrite( &head, sizeof(head), 1, stdout );
+
+    item = 0;
+    bitsperitem = 0;
+    bitshift = 7;
+    }
+
+#if __STDC__
+static void
+putbit( bit b )
+#else /*__STDC__*/
+static void
+putbit( b )
+    bit b;
+#endif /*__STDC__*/
+    {
+    if ( bitsperitem == 8 )
+	putitem( );
+    ++bitsperitem;
+    if ( b == PBM_BLACK )
+	item += 1 << bitshift;
+    --bitshift;
+    }
+
+static void
+putrest( )
+    {
+    if ( bitsperitem > 0 )
+	putitem( );
+    }
+
+static void
+putitem( )
+    {
+    fwrite( &item, sizeof(item), 1, stdout );
+    item = 0;
+    bitsperitem = 0;
+    bitshift = 7;
+    }
diff --git a/converter/pbm/pbmtomrf.c b/converter/pbm/pbmtomrf.c
new file mode 100644
index 00000000..186e95f5
--- /dev/null
+++ b/converter/pbm/pbmtomrf.c
@@ -0,0 +1,338 @@
+/* pbmtomrf - convert pbm to mrf
+ * public domain by RJM
+ *
+ * Adapted to Netpbm by Bryan Henderson 2003.08.09.  Bryan got his copy from
+ * ftp://ibiblio.org/pub/linux/apps/convert, dated 1998.03.03.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "pm_c_util.h"
+#include "pbm.h"
+
+static int bitbox;
+static int bitsleft;
+
+static FILE *bit_out;
+
+
+static void 
+bit_init(FILE * const out) {
+    bitbox = 0; 
+    bitsleft = 8;
+    bit_out = out;
+}
+
+
+
+static void 
+bit_output(int const bit) {
+    --bitsleft;
+    bitbox |= (bit << bitsleft);
+    if (bitsleft == 0) {
+        fputc(bitbox, bit_out);
+        bitbox = 0;
+        bitsleft = 8;
+    }
+}
+
+
+
+static void 
+bit_flush(void) {
+    /* there are never 0 bits left outside of bit_output, but
+     * if 8 bits are left here there's nothing to flush, so
+     * only do it if bitsleft!=8.
+     */
+    if (bitsleft != 8) {
+        bitsleft = 1;
+        bit_output(0);    /* yes, really. This will always work. */
+    }
+}
+
+
+
+static void 
+doSquare(unsigned char * const image,
+         int             const ox,
+         int             const oy,
+         int             const w,
+         int             const size) {
+
+    unsigned int y;
+    unsigned int t;
+
+    /* check square to see if it's all black or all white. */
+
+    t = 0;
+    for (y = 0; y < size; ++y) {
+        unsigned int x;
+        for (x = 0; x < size; ++x)
+            t += image[(oy+y)*w + ox + x];
+    }        
+    /* if the total's 0, it's black. if it's size*size, it's white. */
+    if (t == 0 || t == size*size) {
+        if (size != 1)     /* (it's implicit when size is 1, of course) */
+            bit_output(1);  /* all same color */
+        bit_output(t?1:0);
+        return;
+    }
+    
+    /* otherwise, if our square is greater than 1x1, we need to recurse. */
+    if(size > 1) {
+        bit_output(0);    /* not all same */
+        doSquare(image, ox,      oy,      w, size>>1);
+        doSquare(image, ox+size, oy,      w, size>>1);
+        doSquare(image, ox,      oy+size, w, size>>1);
+        doSquare(image, ox+size, oy+size, w, size>>1);
+    }
+}
+    
+
+
+static void
+fiddleRightEdge(unsigned char * const image,
+                unsigned int    const w,
+                unsigned int    const h,
+                unsigned int    const pw,
+                bool *          const flippedP) {
+
+    unsigned int row;
+    unsigned int t;
+
+    for (row = t = 0; row < h; ++row)
+        t += image[row*pw + w - 1];
+
+    if (t*2 > h) {
+        unsigned int row;
+
+        *flippedP = TRUE;
+        for (row = 0; row < h; ++row) {
+            unsigned int col;
+            for (col = w; col < pw; ++col)
+                image[row*pw + col] = 1;
+        }
+    } else
+        *flippedP = FALSE;
+}
+
+
+
+static void
+fiddleBottomEdge(unsigned char * const image,
+                 unsigned int    const w,
+                 unsigned int    const h,
+                 unsigned int    const pw,
+                 unsigned int    const ph,
+                 bool *          const flippedP) {
+    
+    unsigned int col;
+    unsigned int t;
+
+    for (col = t = 0; col < w; ++col)
+        t += image[(h-1)*pw + col];
+
+    if (t*2 > w) {
+        unsigned int row;
+        *flippedP = TRUE;
+        for (row = h; row < ph; ++row) {
+            unsigned int col;
+            for (col = 0; col < w; ++col)
+                image[row*pw + col] = 1;
+        }
+    } else
+        *flippedP = FALSE;
+}
+
+
+
+
+static void
+fiddleBottomRightCorner(unsigned char * const image,
+                        unsigned int    const w,
+                        unsigned int    const h,
+                        unsigned int    const pw,
+                        unsigned int    const ph) {
+    unsigned int row;
+
+    for (row = h; row < ph; ++row) {
+        unsigned int col;
+        
+        for (col = w; col < pw; ++col)
+                    image[row*pw + col] = 1;
+    }
+}
+
+
+
+static void 
+fiddleEdges(unsigned char * const image,
+            int             const cols,
+            int             const rows) {
+/* the aim of this routine is play around with the edges which
+ * are compressed into the mrf but thrown away when it's decompressed,
+ * such that we get the best compression possible.
+ * If you don't see why this is a good idea, consider the simple case
+ * of a 1x1 white pixel. Placed on a black 64x64 this takes several bytes
+ * to compress. On a white 64x64, it takes two bits.
+ * (Clearly most cases will be more complicated, but you should get the
+ * basic idea from that.)
+ */
+
+    /* there are many possible approaches to this problem, and this one's
+         * certainly not the best, but at least it's quick and easy, and it's
+         * better than nothing. :-)
+         *
+         * So, all we do is flip the runoff area of an edge to white
+         * if more than half of the pixels on that edge are
+         * white. Then for the bottom-right runoff square (if there is
+         * one), we flip it if we flipped both edges.  
+         */
+        
+    /* w64 is units-of-64-bits width, h64 same for height */
+    unsigned int const w64 = (cols + 63) / 64;
+    unsigned int const h64 = (rows + 63) / 64;
+
+    int const pw=w64*64;
+    int const ph=h64*64;
+
+    bool flippedRight, flippedBottom;
+
+    if (cols % 64 != 0) 
+        fiddleRightEdge(image, cols, rows, pw, &flippedRight);
+    else
+        flippedRight = FALSE;
+
+    if (rows % 64 != 0) 
+        fiddleBottomEdge(image, cols, rows, pw, ph, &flippedBottom);
+    else
+        flippedBottom = FALSE;
+
+    if (flippedRight && flippedBottom) 
+        fiddleBottomRightCorner(image, cols, rows, pw, ph);
+}
+
+
+
+static void
+readPbmImage(FILE *           const ifP, 
+             unsigned char ** const imageP,
+             int *            const colsP,
+             int *            const rowsP) {
+    
+
+    /* w64 is units-of-64-bits width, h64 same for height */
+    unsigned int w64, h64;
+
+    unsigned char * image;
+    int cols, rows, format;
+    unsigned int row;
+    bit * bitrow;
+    
+    pbm_readpbminit(ifP, &cols, &rows, &format);
+
+    w64 = (cols + 63) / 64;
+    h64 = (rows + 63) / 64;
+
+    if (UINT_MAX/w64/64/h64/64 == 0)
+        pm_error("Ridiculously large, unprocessable image: %u cols x %u rows",
+                 cols, rows);
+
+    image = calloc(w64*h64*64*64,1);
+    if (image == NULL)
+        pm_error("Unable to get memory for raster");
+                 
+    /* get bytemap image rounded up into mod 64x64 squares */
+
+    bitrow = pbm_allocrow(cols);
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+
+        pbm_readpbmrow(ifP, bitrow, cols, format);
+
+        for (col =0; col < cols; ++col)
+            image[row*(w64*64) + col] = (bitrow[col] == PBM_WHITE ? 1 : 0);
+    }
+    pbm_freerow(bitrow);
+    *imageP = image;
+    *colsP = cols;
+    *rowsP = rows;
+}
+
+
+
+static void
+outputMrf(FILE *          const ofP, 
+          unsigned char * const image,
+          unsigned int    const cols,
+          unsigned int    const rows) {
+
+    unsigned int const w64 = (cols + 63) / 64;
+    unsigned int const h64 = (rows + 63) / 64;
+
+    unsigned int row;
+
+    fprintf(ofP, "MRF1");
+    fprintf(ofP, "%c%c%c%c", cols >> 24, cols >> 16, cols >> 8, cols >> 0);
+    fprintf(ofP, "%c%c%c%c", rows >> 24, rows >> 16, rows >> 8, rows >> 0);
+    fputc(0, ofP);   /* option byte, unused for now */
+    
+    /* now recursively check squares. */
+
+    bit_init(ofP);
+
+    for (row = 0; row < h64; ++row) {
+        unsigned int col;
+        for (col = 0; col < w64; ++col)
+            doSquare(image, col*64, row*64, w64*64, 64);
+    }
+    bit_flush();
+}
+
+
+
+int 
+main(int argc,char *argv[]) {
+
+    FILE * ifP;
+    FILE * ofP;
+    unsigned char *image;
+    int rows, cols;
+    
+    pbm_init(&argc, argv);
+
+    if (argc-1 > 1)
+        pm_error("Too many arguments: %d.  Only argument is input file", 
+                 argc-1);
+
+    if (argc-1 == 1)
+        ifP = pm_openr(argv[1]);
+    else
+        ifP = stdin;
+
+    ofP = stdout;
+ 
+    readPbmImage(ifP, &image, &cols, &rows);
+
+    pm_close(ifP);
+
+    /* if necessary, alter the unused outside area to aid compression of
+     * edges of image.
+     */
+
+    fiddleEdges(image, cols, rows);
+
+    outputMrf(ofP, image, cols, rows);
+
+    free(image);
+
+    return 0;
+}
+
+
+
+
diff --git a/converter/pbm/pbmtonokia.c b/converter/pbm/pbmtonokia.c
new file mode 100644
index 00000000..66678b7b
--- /dev/null
+++ b/converter/pbm/pbmtonokia.c
@@ -0,0 +1,225 @@
+/* pbmtonokia.c - convert a portable bitmap to Nokia Smart Messaging
+   Formats (NOL, NGG, HEX)
+
+** Copyright (C)2001 OMS Open Media System GmbH, Tim Rühsen
+** <tim.ruehsen@openmediasystem.de>.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+
+History
+  07.06.2001 Created
+  20.11.2001 Handle Picture Messages
+             new option -txt to embed text into Picture Messages
+             new option -net to specify operator network code for 
+                Nokia Operator Logos
+
+Notes:
+  - limited to rows <= 255 and columns <= 255
+  - limited to b/w graphics, not animated
+
+Testing:
+  Testing was done with SwissCom SMSC (Switzerland) and IC3S SMSC (Germany).
+  The data was send with EMI/UCP protocol over TCP/IP.
+
+  - 7.6.2001: tested with Nokia 3210: 72x14 Operator Logo
+  - 7.6.2001: tested with Nokia 6210: 72x14 Operator Logo and 
+              72x14 Group Graphic
+
+Todo:
+  - more testing
+  - sendsms compatibility ?
+  - are -fmt NOL and -fmt NGG working ok?  */
+
+#define _BSD_SOURCE    /* Make sure strcasecmp() is in string.h */
+#include <string.h>
+
+#include "nstring.h"
+#include "pbm.h"
+
+#define FMT_HEX_NOL   1
+#define FMT_HEX_NGG   2
+#define FMT_HEX_NPM   3
+#define FMT_NOL       4
+#define FMT_NGG       5
+
+static void 
+usage(char *myname)
+{
+    pm_message("Copyright (C)2001 OMS GmbH");
+    pm_message("Contact: Tim Ruehsen <tim.ruehsen@openmediasystem.de>\n");
+    pm_usage("[options] [pbmfile]\n"
+             "  Options:\n"
+             "    -fmt <HEX_NOL|HEX_NGG|HEX_NPM|NOL|NGG>  "
+             "Output format (default=HEX_NOL)\n"
+             "    -net <network code>                     "
+             "Network code for NOL operator logos\n"
+             "    -txt <text message>                     "
+             "Text for NMP picture messages\n");
+
+    exit(1);
+}
+
+int 
+main(int argc, char *argv[])
+{
+    FILE    *fp;
+    bit    **image;
+    unsigned int    c;
+    int    argpos, output=FMT_HEX_NOL, rows, cols, row, col, p, it, len;
+    char    header[32], *myname;
+    char    network_code[6+1];
+    char    *text=NULL;
+
+    if ((myname=strrchr(argv[0],'/'))!=NULL) myname++; else myname=argv[0];
+
+    pbm_init(&argc, argv);
+
+    strcpy(network_code, "62F210"); /* default is German D1 net */
+
+    for(argpos=1;argpos<argc;argpos++) {
+        if (argv[argpos][0]=='-') {
+            if (argv[argpos][1]=='-') {
+                if (argc>argpos+1 && ISDIGIT(argv[argpos+1][0]))
+                    {argpos++;break;}
+            } else if (STREQ(argv[argpos],"-fmt") && argc>argpos+1) {
+                ++argpos;
+                if (!strcasecmp(argv[argpos],"HEX_NOL")) output=FMT_HEX_NOL;
+                else if (!strcasecmp(argv[argpos],"HEX_NGG")) 
+                    output=FMT_HEX_NGG;
+                else if (!strcasecmp(argv[argpos],"HEX_NPM")) 
+                    output=FMT_HEX_NPM;
+                else if (!strcasecmp(argv[argpos],"NOL")) output=FMT_NOL;
+                else if (!strcasecmp(argv[argpos],"NGG")) output=FMT_NGG;
+                else usage(myname);
+            } else if (STREQ(argv[argpos],"-net") && argc>argpos+1) {
+                char * const network_code_arg=argv[++argpos];
+                unsigned int it;
+                len=strlen(network_code_arg);
+                if (len!=6) 
+                    pm_error("Network code must be 6 hex-digits long");
+                for (it=0;it<strlen(network_code_arg);it++) {
+                    if (!ISXDIGIT(network_code_arg[it])) 
+                        pm_error("Network code must contain hex-digits only");
+                    network_code[it]=TOUPPER(network_code_arg[it]);
+                }
+                network_code[it] = '\0';
+            } else if (STREQ(argv[argpos],"-txt") && argc>argpos+1) {
+                text=argv[++argpos];
+            }
+            else usage(myname);
+        } else break;
+    }
+
+    if (argpos==argc) {
+        image = pbm_readpbm(stdin, &cols, &rows);
+    } else {
+        fp=pm_openr(argv[argpos]);
+        image = pbm_readpbm(fp, &cols, &rows);
+        pm_close(fp);
+    }
+
+    memset(header,0,sizeof(header));
+
+    switch (output) {
+    case FMT_HEX_NOL:
+        /* header */
+        printf("06050415820000%s00%02X%02X01",network_code,cols,rows);
+
+        /* image */
+        for (row=0;row<rows;row++) {
+            for (p=c=col=0;col<cols;col++) {
+                if (image[row][col]==PBM_BLACK) c|=0x80>>p;
+                if (++p==8) {
+                    printf("%02X",c);
+                    p=c=0;
+                }
+            }
+            if (p) printf("%02X",c);
+        }
+        break;
+    case FMT_HEX_NGG:
+        /* header */
+        printf("0605041583000000%02X%02X01",cols,rows);
+
+        /* image */
+        for (row=0;row<rows;row++) {
+            for (p=c=col=0;col<cols;col++) {
+                if (image[row][col]==PBM_BLACK) c|=0x80>>p;
+                if (++p==8) {
+                    printf("%02X",c);
+                    p=c=0;
+                }
+            }
+            if (p) printf("%02X",c);
+        }
+        break;
+    case FMT_HEX_NPM:
+        /* header */
+        printf("060504158A0000");
+
+        /* text */
+        if (text!=NULL) {
+            printf("00%04X",(len=strlen(text)));
+            for (it=0;it<len;it++) printf("%02X",text[it]);
+        }
+
+        /* image */
+        printf("02%04X00%02X%02X01",(cols*rows)/8+4,cols,rows);
+        for (row=0;row<rows;row++) {
+            for (p=c=col=0;col<cols;col++) {
+                if (image[row][col]==PBM_BLACK) c|=0x80>>p;
+                if (++p==8) {
+                    printf("%02X",c);
+                    p=c=0;
+                }
+            }
+            if (p) printf("%02X",c);
+        }
+        break;
+    case FMT_NOL:
+        /* header - this is a hack */
+        header[1]=header[4]=header[5]=header[11]=header[13]=1;
+        header[3]=4;
+        header[7]=cols;
+        header[9]=rows;
+        header[15]=0x53;
+        fwrite(header,17,1,stdout);
+
+        /* image */
+        for (row=0;row<rows;row++) {
+            for (p=c=col=0;col<cols;col++) {
+                if (image[row][col]==PBM_BLACK) putchar('1');
+                else putchar('0');
+            }
+        }
+        break;
+    case FMT_NGG:
+        /* header - this is a hack */
+        header[1]=header[7]=header[9]=1;
+        header[3]=cols;
+        header[5]=rows;
+        header[11]=0x4a;
+        fwrite(header,13,1,stdout);
+
+        /* image */
+        for (row=0;row<rows;row++) {
+            for (p=c=col=0;col<cols;col++) {
+                if (image[row][col]==PBM_BLACK) putchar('1');
+                else putchar('0');
+            }
+        }
+        break;
+    default:
+        pm_error("Output format %d not implemented!\n"
+                 "Contact Tim Ruehsen <tim.ruehsen@openmediasystem.de>\n",
+                 output);
+        return 1;
+    }
+    return 0;
+}
+
diff --git a/converter/pbm/pbmtopi3.c b/converter/pbm/pbmtopi3.c
new file mode 100644
index 00000000..06023d7a
--- /dev/null
+++ b/converter/pbm/pbmtopi3.c
@@ -0,0 +1,123 @@
+/* pbmtopi3.c - read a portable bitmap and produce a Atari Degas .pi3 file
+**
+** Module created from other pbmplus tools by David Beckemeyer.
+**
+** Copyright (C) 1988 by David Beckemeyer and Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <stdio.h>
+#include "pbm.h"
+
+static void putinit ARGS(( void ));
+static void putbit ARGS(( bit b ));
+static void putrest ARGS(( void ));
+static void putitem ARGS(( void ));
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    bit* bitrow;
+    register bit* bP;
+    int rows, cols, format, padright, row, col;
+
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[pbmfile]" );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    pbm_readpbminit( ifp, &cols, &rows, &format );
+    if (cols > 640)
+	cols = 640;
+    if (rows > 400)
+	rows = 400;
+    bitrow = pbm_allocrow( cols );
+    
+    /* Compute padding to round cols up to 640 */
+    padright = 640 - cols;
+
+    putinit( );
+    for ( row = 0; row < rows; ++row )
+	{
+	pbm_readpbmrow( ifp, bitrow, cols, format );
+        for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
+	    putbit( *bP );
+	for ( col = 0; col < padright; ++col )
+	    putbit( 0 );
+        }
+    while (row++ < 400)
+	for ( col = 0; col < 640; ++col)
+	    putbit( 0 );
+
+    pm_close( ifp );
+
+    putrest( );
+
+    exit( 0 );
+    }
+
+static char item;
+static short bitsperitem, bitshift;
+
+static void
+putinit( )
+    {
+    int i;
+    if (pm_writebigshort (stdout, (short) 2) == -1
+	|| pm_writebigshort (stdout, (short) 0x777) == -1)
+      pm_error ("write error");
+    for (i = 1; i < 16; i++)
+      if (pm_writebigshort (stdout, (short) 0) == -1)
+	pm_error ("write error");
+    item = 0;
+    bitsperitem = 0;
+    bitshift = 7;
+    }
+
+#if __STDC__
+static void
+putbit( bit b )
+#else /*__STDC__*/
+static void
+putbit( b )
+    bit b;
+#endif /*__STDC__*/
+    {
+    if (bitsperitem == 8)
+	putitem( );
+    ++bitsperitem;
+    if ( b == PBM_BLACK )
+	item += 1 << bitshift;
+    --bitshift;
+    }
+
+static void
+putrest( )
+    {
+    if ( bitsperitem > 0 )
+	putitem( );
+    }
+
+static void
+putitem( )
+    {
+    putc (item, stdout);
+    item = 0;
+    bitsperitem = 0;
+    bitshift = 7;
+    }
diff --git a/converter/pbm/pbmtopk.c b/converter/pbm/pbmtopk.c
new file mode 100644
index 00000000..b84818b1
--- /dev/null
+++ b/converter/pbm/pbmtopk.c
@@ -0,0 +1,976 @@
+/*
+  pbmtopk, adapted from "pxtopk.c by tomas rokicki" by AJCD 1/8/90
+  
+  compile with: cc -o pbmtopk pbmtopk.c -lm -lpbm
+*/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <stdio.h>
+#include <math.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "pbm.h"
+#include "nstring.h"
+#include "mallocvar.h"
+
+#define MAXPKCHAR 256
+#define MAXOPTLINE 200
+#define MAXWIDTHTAB 256
+#define MAXHEIGHTTAB 16
+#define MAXDEPTHTAB 16
+#define MAXITALICTAB 64
+#define MAXPARAMS 30
+#define NAMELENGTH 80
+
+#define fixword(d) ((int)((double)(d)*1048576))
+#define unfixword(f) ((double)(f) / 1048576)
+#define fixrange(f) ((f) < 16777216 && (f) > -16777216)
+#define designunits(p) ((p)*72.27/(double)resolution/unfixword(designsize))
+
+/* character flags: in order of appearance in option files. */
+#define XOFFSET     1
+#define YOFFSET     2
+#define HORZESC     4
+#define VERTESC     8
+#define TFMWIDTH   16
+#define TFMHEIGHT  32
+#define TFMDEPTH   64
+#define TFMITALIC 128
+
+typedef int integer ;
+typedef char quarterword ;
+typedef char boolean ;
+typedef quarterword ASCIIcode ;
+typedef quarterword eightbits ;
+typedef unsigned char byte ;
+
+static integer resolution, designsize ;
+static char *filename[MAXPKCHAR] ;
+
+static integer xoffset[MAXPKCHAR] ;
+static integer yoffset[MAXPKCHAR] ;
+static integer horzesc[MAXPKCHAR] ;
+static integer vertesc[MAXPKCHAR] ;
+
+static byte tfmindex[MAXPKCHAR] ;
+static byte hgtindex[MAXPKCHAR] ;
+static byte depindex[MAXPKCHAR] ;
+static byte italindex[MAXPKCHAR] ;
+static byte charflags[MAXPKCHAR] ;
+
+static bit **bitmap ;
+static integer smallestch = MAXPKCHAR ;
+static integer largestch = -1;
+static integer emwidth  = 0;
+static integer checksum ;
+static const char *codingscheme = "GRAPHIC" ;
+static const char *familyname = "PBM" ;
+
+static integer widthtab[MAXWIDTHTAB] = {0}; /* TFM widths */
+static integer numwidth = 1;      /* number of entries in width table */
+static integer heighttab[MAXHEIGHTTAB]  = {0};
+static integer numheight = 1;
+static integer depthtab[MAXDEPTHTAB] = {0};
+static integer numdepth = 1;
+static integer italictab[MAXITALICTAB] = {0};
+static integer numitalic = 1;
+static integer parameters[MAXPARAMS] = {0};
+static integer numparam = 0;
+
+static ASCIIcode xord[128] ;
+static char xchr[256] = {
+    '?', '?', '?', '?', '?', '?', '?', '?',
+    '?', '?', '?', '?', '?', '?', '?', '?',
+    '?', '?', '?', '?', '?', '?', '?', '?',
+    '?', '?', '?', '?', '?', '?', '?', '?',
+    ' ', '!', '"', '#', '$', '%', '&', '\'',
+    '(', ')', '*', '+', ',', '-', '.', '/',
+    '0', '1', '2', '3', '4', '5', '6', '7',
+    '8', '9', ':', ';', '<', '=', '>', '?',
+    '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
+    'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+    'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
+    'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
+    '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+    'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+    'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+    'x', 'y', 'z', '{', '|', '}', '~', '?' };
+
+static FILE *tfmfile, *pkfile ;
+static char tfmname[NAMELENGTH+1], pkname[NAMELENGTH+1] ;
+static integer pbmtopk_pkloc = 0 ;
+static integer bitweight ;
+static integer outputbyte ;
+static integer car ;
+static integer hppp ;
+static integer width ;
+static integer height ;
+
+/* check sum algorithm (in Pascal):
+
+compute_checksum()
+    begin
+        c0:=bc; c1:=ec; c2:=bc; c3:=ec;
+        for c:=bc to ec do if char_wd[c]>0 then begin
+            temp_width:=memory[char_wd[c]];
+            if design_units<>unity then
+               temp_width:=round((temp_width/design_units)*1048576.0);
+            temp_width:=temp_width + (c+4)*@'20000000; 
+                {this should be positive}
+            c0:=(c0+c0+temp_width) mod 255;
+            c1:=(c1+c1+temp_width) mod 253;
+            c2:=(c2+c2+temp_width) mod 251;
+            c3:=(c3+c3+temp_width) mod 247;
+            end;
+        header_bytes[check_sum_loc]:=c0;
+        header_bytes[check_sum_loc+1]:=c1;
+        header_bytes[check_sum_loc+2]:=c2;
+        header_bytes[check_sum_loc+3]:=c3;
+        end
+*/
+
+#define add_tfmwidth(v) (add_tfmtable(widthtab, &numwidth, v, MAXWIDTHTAB,\
+                                      "TFM width"))
+#define add_tfmheight(v) (add_tfmtable(heighttab, &numheight, v, MAXHEIGHTTAB,\
+                                       "TFM height"))
+#define add_tfmdepth(v) (add_tfmtable(depthtab, &numdepth, v, MAXDEPTHTAB,\
+                                      "TFM depth"))
+#define add_tfmitalic(v) (add_tfmtable(italictab, &numitalic, v, MAXITALICTAB,\
+                                       "Italic correction"))
+
+
+static byte
+add_tfmtable(int *        const table, 
+             int *        const count, 
+             int          const value, 
+             int          const max_count, 
+             const char * const name) {
+    
+    integer i;
+    for (i = 0; i < *count; i++) /* search for value in tfm table */
+        if (table[i] == value) return (byte)i;
+    if (*count >= max_count)
+        pm_error("too many values in %s table", name) ;
+    if (!fixrange(value))
+        pm_error("%s %f for char %d out of range",
+                 name, unfixword(value), car);
+    table[*count] = value ;
+    return (*count)++ ;
+}
+
+
+
+/* add a suffix to a filename in an allocated space */
+static void 
+pbmtopk_add_suffix(char * const name, 
+                   const char * const suffix) {
+
+    char *slash = strrchr(name, '/');
+    char *dot = strrchr(name, '.');
+
+    if ((dot && slash ? dot < slash : !dot) && !STREQ(name, "-"))
+        strcat(name, suffix);
+}
+
+
+
+/* initialize the PK parameters */
+static void 
+initialize_pk(void) {
+    integer i ;
+    pm_message("This is PBMtoPK, version 2.4") ;
+    for (i = 127 ; i <= 255 ; i ++) xchr[i] = '?' ;
+    for (i = 0 ; i <= 127 ; i ++) xord[i] = 32 ;
+    for (i = 32 ; i < 127 ; i ++) xord[(int)xchr[i]] = i ;
+    for (i = 0; i < MAXPKCHAR; i++) {
+        filename[i] = NULL;
+        charflags[i] = 0;
+    }
+    designsize = fixword(1.0) ;
+}
+
+
+
+/* write a single byte to the PK file */
+static void 
+pbmtopk_pkbyte(integer const b_in) {
+    integer b;
+
+    b = b_in;  /* initial value */
+
+    if (b < 0) 
+        b += 256 ;
+    putc(b, pkfile) ;
+    pbmtopk_pkloc++ ;
+}
+
+
+
+/* write two bytes to the PK file */
+static void 
+pkhalfword(integer const a_in) {
+    integer a;
+
+    a = a_in;
+
+    if (a < 0) 
+        a += 65536 ;
+    pbmtopk_pkbyte(a >> 8) ;
+    pbmtopk_pkbyte(a & 255) ;
+}
+
+
+
+/* write three bytes to the PK file */
+static void 
+pkthreebytes(integer const a) {
+
+    pbmtopk_pkbyte((a>>16) & 255) ;
+    pbmtopk_pkbyte((a>>8) & 255) ;
+    pbmtopk_pkbyte(a & 255) ;
+}
+
+
+
+/* write four bytes to the PK file */
+static void 
+pkword(integer const a) {
+    pbmtopk_pkbyte((a>>24) & 255) ;
+    pbmtopk_pkbyte((a>>16) & 255) ;
+    pbmtopk_pkbyte((a>>8) & 255) ;
+    pbmtopk_pkbyte(a & 255) ;
+}
+
+
+
+/* write a nibble to the PK file */
+static void 
+pknyb(integer const a) {
+
+    if (bitweight == 16) {
+        outputbyte = (a<<4) ;
+        bitweight = 1 ;
+    } else {
+        pbmtopk_pkbyte(outputbyte + a) ;
+        bitweight = 16 ;
+    }
+}
+
+
+
+/* write preamble to PK file */
+static void 
+writepreamble(void) {
+    integer i ;
+    const char * const comment = "PBMtoPK 2.4 output" ;
+   
+    pbmtopk_pkbyte(247) ;                /* PRE command */
+    pbmtopk_pkbyte(89) ;             /* PK file type */
+    pbmtopk_pkbyte(strlen(comment)) ;            /* output comment */
+    for (i = 0 ; i < strlen(comment); i++) 
+        pbmtopk_pkbyte(xord[(int)comment[i]]) ;
+    pkword(designsize) ;             /* write designsize */
+    pkword(checksum) ;       /* write checksum; calculate if possible */
+    pkword(hppp) ;               /* write H pixels per point */
+    pkword(hppp) ;               /* write V pixels per point */
+}
+
+
+
+/* write postamble to PK file, padded to word length */
+static void 
+writepostamble(void) {
+    pbmtopk_pkbyte(245) ;                /* POST command */
+    while (pbmtopk_pkloc % 4)
+        pbmtopk_pkbyte(246) ;             /* pad with no-ops */
+    pm_message("%d bytes written to packed file.", pbmtopk_pkloc) ;
+}
+
+
+
+/* write a byte to the TFM file */
+static void 
+tfmbyte(integer const b_in) {
+    integer b;
+
+    b = b_in;
+
+    if (b < 0) b += 256 ;
+    putc(b, tfmfile) ;
+}
+
+
+
+/* write a half word to the TFM file */
+static void 
+tfmhalfword(integer const a_in) {
+    
+    integer a;
+
+    a = a_in;
+
+    if (a < 0) a += 65536 ;
+    tfmbyte(a >> 8) ;
+    tfmbyte(a & 255) ;
+}
+
+
+
+/* write a word to the TFM file */
+static void 
+tfmword(integer const a) {
+    tfmbyte((a>>24) & 255) ;
+    tfmbyte((a>>16) & 255) ;
+    tfmbyte((a>>8) & 255) ;
+    tfmbyte(a & 255) ;
+}
+
+
+
+/* write the whole TFM file for the font */
+static void 
+writetfmfile(void) {
+    integer totallength ;
+    integer headersize = 17;
+    integer i ;
+   
+    if (largestch - smallestch < 0) {
+        largestch = 0;
+        smallestch = 1;
+    }
+    if (numparam < 7) /* set default parameters */
+        switch (numparam) {
+        case 0: /* slant */
+            parameters[numparam++] = 0 ;
+        case 1: /* space */
+            parameters[numparam++] = fixword(designunits(emwidth/3.0));
+        case 2: /* space_stretch */
+            parameters[numparam++] = fixword(unfixword(parameters[1])/2.0) ;
+        case 3: /* space_shrink */
+            parameters[numparam++] = fixword(unfixword(parameters[1])/3.0) ;
+        case 4: /* x_height */
+            parameters[numparam++] = fixword(0.45);
+        case 5: /* quad */
+            parameters[numparam++] = fixword(designunits(emwidth)) ;
+        case 6: /* extra_space */
+            parameters[numparam++] = fixword(unfixword(parameters[1])/3.0) ;
+        }
+    totallength = 6 + headersize + (largestch+1-smallestch) +
+        numwidth + numheight + numdepth + numitalic + numparam ;
+    /* lengths */
+    tfmhalfword(totallength) ;           /* write file TFM length */
+    tfmhalfword(headersize) ;            /* write TFM header length */
+    tfmhalfword(smallestch) ;            /* write lowest char index */
+    tfmhalfword(largestch) ;         /* write highest char index */
+    tfmhalfword(numwidth) ;          /* write number of widths */
+    tfmhalfword(numheight) ;         /* write number of heights */
+    tfmhalfword(numdepth) ;          /* write number of depths */
+    tfmhalfword(numitalic) ;         /* write number of italcorrs */
+    tfmhalfword(0) ;             /* lig/kern table */
+    tfmhalfword(0) ;             /* kern table */
+    tfmhalfword(0) ;             /* extensible char table */
+    tfmhalfword(numparam) ;          /* number of fontdimens */
+    /* header */
+    tfmword(checksum) ;              /* write checksum */
+    tfmword(designsize) ;            /* write designsize */
+    if (strlen(codingscheme) > 39) 
+        tfmbyte(39) ; /* write coding scheme len */
+    else 
+        tfmbyte(strlen(codingscheme)) ;
+    for (i = 0; i < 39; i++)         /* write coding scheme */
+        if 
+            (*codingscheme) tfmbyte(xord[(int)(*codingscheme++)]) ;
+        else
+            tfmbyte(0) ;
+    if (strlen(familyname) > 19) 
+        tfmbyte(19) ;   /* write family length */
+    else 
+        tfmbyte(strlen(familyname)) ;
+    for (i = 0; i < 19; i++)         /* write family */
+        if (*familyname) 
+            tfmbyte(xord[(int)(*familyname++)]) ;
+        else 
+            tfmbyte(0) ;
+    /* char_info */
+    for (car = smallestch; car <= largestch; car++)
+        if (filename[car]) {          /* write character info */
+            tfmbyte(tfmindex[car]) ;
+            tfmbyte((hgtindex[car]<<4) + depindex[car]) ;
+            tfmbyte(italindex[car]<<2) ;
+            tfmbyte(0) ;
+        } else
+            tfmword(0) ;
+    /* width table */
+    for (i = 0; i < numwidth; i++) tfmword(widthtab[i]) ;
+    /* height table */
+    for (i = 0; i < numheight; i++) tfmword(heighttab[i]) ;
+    /* depth table */
+    for (i = 0; i < numdepth; i++) tfmword(depthtab[i]) ;
+    /* italic correction table */
+    for (i = 0; i < numitalic; i++) tfmword(italictab[i]) ;
+    /* no lig_kern, kern, or exten tables */
+    /* fontdimen table */
+    for (i = 0; i < numparam; i++)
+        if (i && !fixrange(parameters[i]))
+            pm_error("parameter %d out of range (-p)", i);
+        else
+            tfmword(parameters[i]) ;
+    pm_message("%d bytes written to tfm file.", totallength*4) ;
+}
+
+
+
+/* read a character from a PBM file */
+static void readcharacter(void) {
+    FILE *fp;
+   
+    fp = pm_openr(filename[car]);
+    bitmap = pbm_readpbm(fp, &width, &height) ;
+    pm_close(fp) ;
+   
+    if ((charflags[car] & HORZESC) == 0) horzesc[car] = width ;
+    if ((charflags[car] & VERTESC) == 0) vertesc[car] = 0;
+    if ((charflags[car] & XOFFSET) == 0) xoffset[car] = 0;
+    if ((charflags[car] & YOFFSET) == 0) yoffset[car] = height-1;
+    if ((charflags[car] & TFMWIDTH) == 0)
+        tfmindex[car] = add_tfmwidth(fixword(designunits(width)));
+    if ((charflags[car] & TFMHEIGHT) == 0)
+        hgtindex[car] = add_tfmheight(fixword(designunits(yoffset[car]+1)));
+    if ((charflags[car] & TFMDEPTH) == 0)
+        depindex[car] = 
+            add_tfmdepth(fixword(designunits(height-1-yoffset[car])));
+    if ((charflags[car] & TFMITALIC) == 0) italindex[car] = 0;
+   
+    if (car < smallestch) smallestch = car;
+    if (car > largestch) largestch = car;
+    if (width > emwidth) emwidth = width ;
+}
+
+
+
+/* test if two rows of the PBM are the same */
+static int 
+equal(const bit * const row1, 
+      const bit * const row2) {
+
+    integer i ;
+   
+    for (i = 0; i < width; i++)
+        if (row1[i] != row2[i]) 
+            return (0) ;
+
+    return(1) ;
+}
+
+
+
+static void 
+shipcharacter(void) {
+
+    integer compsize ;
+    integer i, j, k ;
+    bit *zerorow, *onesrow ;
+    integer *repeatptr, *bitcounts ;
+    integer count ;
+    integer test ;
+    integer curptr, rowptr ;
+    integer bitval ;
+    integer repeatflag ;
+    integer colptr ;
+    integer currepeat ;
+    integer dynf ;
+    integer deriv[14] ;
+    integer bcompsize ;
+    boolean firston ;
+    integer flagbyte ;
+    boolean state ;
+    boolean on ;
+    integer hbit ;
+    integer pbit ;
+    boolean ron, son ;
+    integer rcount, scount ;
+    integer ri, si ;
+    integer max2 ;
+    integer predpkloc ;
+    integer buff ;
+   
+    integer tfwid = widthtab[tfmindex[car]] ;
+    integer hesc = horzesc[car] ;
+    integer vesc = vertesc[car] ;
+    integer xoff = xoffset[car] ;
+    integer yoff = yoffset[car] ;
+   
+    MALLOCARRAY(repeatptr, height + 1);
+    MALLOCARRAY(bitcounts, height * width);
+    if (repeatptr == NULL || bitcounts == NULL)
+        pm_error("out of memory while allocating bit counts");
+    zerorow = pbm_allocrow(width) ;      /* initialize plain rows */
+    onesrow = pbm_allocrow(width) ;
+    for (i = 0 ; i < width ; i++) {
+        zerorow[i] = PBM_WHITE ;
+        onesrow[i] = PBM_BLACK ;
+    }
+    for (i=0; i < height; i = k) {       /* set repeat pointers */
+        k = i + 1;
+        if (!equal(bitmap[i], zerorow) && !equal(bitmap[i], onesrow)) {
+            while (k < height && equal(bitmap[i], bitmap[k]))
+                k++;
+            repeatptr[i] = k - i - 1;
+        } else {
+            repeatptr[i] = 0;
+        }
+    }
+    repeatptr[height] = 0 ;
+    colptr = width - 1 ;
+    repeatflag = currepeat = curptr = count = rowptr = 0 ;
+    test = PBM_WHITE ;
+    do {
+        colptr++ ;
+        if (colptr == width) {            /* end of row, get next row */
+            colptr = 0 ;
+            rowptr = currepeat ;
+            if (repeatptr[currepeat] > 0) {
+                repeatflag = repeatptr[currepeat] ;
+                currepeat += repeatflag ;
+                rowptr += repeatflag ;
+            }
+            currepeat++ ;
+        }
+        if (rowptr >= height) bitval = -1 ;
+        else bitval = bitmap[rowptr][colptr] ;
+        if (bitval == test) count++ ;     /* count repeated pixels */
+        else {                    /* end of pixel run */
+            bitcounts[curptr++] = count ;
+            if (curptr+3 >= height*width)
+                pm_error("out of memory while saving character counts");
+            count = 1 ;
+            test = bitval ;
+            if (repeatflag > 0) {
+                bitcounts[curptr++] = -repeatflag ;
+                repeatflag = 0 ;
+            }
+        }
+    } while (test != -1) ;
+    bitcounts[curptr] = 0 ;
+    bitcounts[curptr + 1] = 0 ;
+    for (i = 1 ; i <= 13 ; i ++) deriv[i] = 0 ;
+    i = firston = (bitcounts[0] == 0) ;
+    compsize = 0 ;
+    while (bitcounts[i] != 0) {          /* calculate dyn_f */
+        j = bitcounts[i] ;
+        if (j == -1) compsize++ ;
+        else {
+            if (j < 0) {
+                compsize++ ;
+                j = -j ;
+            }
+            if (j < 209) compsize += 2 ;
+            else {
+                k = j - 193 ;
+                while (k >= 16) {
+                    k >>= 4 ;
+                    compsize += 2 ;
+                }
+                compsize++ ;
+            }
+            if (j < 14) (deriv[j])-- ;
+            else if (j < 209) (deriv[(223 - j) / 15])++ ;
+            else {
+                k = 16 ;
+                while (((k<<4) < j + 3)) k <<= 4 ;
+                if (j - k <= 192)
+                    deriv[(207 - j + k) / 15] += 2 ;
+            }
+        }
+        i++ ;
+    }
+    bcompsize = compsize ;
+    dynf = 0 ;
+    for (i = 1 ; i <= 13 ; i ++) {
+        compsize += deriv[i] ;
+        if (compsize <= bcompsize) {
+            bcompsize = compsize ;
+            dynf = i ;
+        }
+    }
+    compsize = ((bcompsize + 1)>>1) ;
+    if ((compsize > ((height*width+7)>>3)) || (height*width == 0)) {
+        compsize = ((height*width+7)>>3) ;
+        dynf = 14 ;
+    }
+    flagbyte = (dynf<<4) ;
+    if (firston) flagbyte |= 8 ;
+    if (tfwid > 16777215 || tfwid < 0 || hesc < 0 || vesc != 0 ||
+        compsize > 196579 || width > 65535 || height > 65535 ||
+        xoff > 32767 || yoff > 32767 || xoff < -32768 || yoff < -32768) {
+        flagbyte |= 7 ;               /* long form preamble */
+        pbmtopk_pkbyte(flagbyte) ;
+        compsize += 28 ;
+        pkword(compsize) ;            /* char packet size */
+        pkword(car) ;             /* character number */
+        predpkloc = pbmtopk_pkloc + compsize ;
+        pkword(tfwid) ;               /* TFM width */
+        pkword(hesc<<16) ;            /* horiz escapement */
+        pkword(vesc<<16) ;            /* vert escapement */
+        pkword(width) ;               /* bounding box width */
+        pkword(height) ;              /* bounding box height */
+        pkword(xoff) ;                /* horiz offset */
+        pkword(yoff) ;                /* vert offset */
+    } else if (hesc > 255 || width > 255 || height > 255 ||
+               xoff > 127 || yoff > 127 || xoff < -128 ||
+               yoff < -128 || compsize > 1016) {
+        compsize += 13 ;              /* extended short preamble */
+        flagbyte += (compsize>>16) + 4 ;
+        pbmtopk_pkbyte(flagbyte) ;
+        pkhalfword(compsize & 65535) ;        /* char packet size */
+        pbmtopk_pkbyte(car) ;             /* character number */
+        predpkloc = pbmtopk_pkloc + compsize ;
+        pkthreebytes(tfwid) ;         /* TFM width */
+        pkhalfword(hesc) ;            /* horiz escapement */
+        pkhalfword(width) ;           /* bounding box width */
+        pkhalfword(height) ;          /* bounding box height */
+        pkhalfword(xoff) ;            /* horiz offset */
+        pkhalfword(yoff) ;            /* vert offset */
+    } else {
+        compsize += 8 ;               /* short form preamble */
+        flagbyte = flagbyte + (compsize>>8) ;
+        pbmtopk_pkbyte(flagbyte) ;
+        pbmtopk_pkbyte(compsize & 255) ;          /* char packet size */
+        pbmtopk_pkbyte(car) ;             /* character number */
+        predpkloc = pbmtopk_pkloc + compsize ;
+        pkthreebytes(tfwid) ;         /* TFM width */
+        pbmtopk_pkbyte(hesc) ;                /* horiz escapement */
+        pbmtopk_pkbyte(width) ;               /* bounding box width */
+        pbmtopk_pkbyte(height) ;              /* bounding box height */
+        pbmtopk_pkbyte(xoff) ;                /* horiz offset */
+        pbmtopk_pkbyte(yoff) ;                /* vert offset */
+    }
+    if (dynf != 14) {                /* write packed character */
+        bitweight = 16 ;
+        max2 = 208 - 15 * dynf ;
+        i = firston ;
+        while (bitcounts[i] != 0) {
+            j = bitcounts[i] ;
+            if (j == - 1) pknyb(15) ;
+            else {
+                if (j < 0) {
+                    pknyb(14) ;
+                    j = -j ;
+                }
+                if (j <= dynf) pknyb(j) ;
+                else if (j <= max2) {
+                    j -= dynf + 1 ;
+                    pknyb((j >> 4) + dynf + 1) ;
+                    pknyb((j & 15)) ;
+                } else {
+                    j -= max2 - 15 ;
+                    k = 16 ;
+                    while (k <= j) {
+                        k <<= 4 ;
+                        pknyb(0) ;
+                    }
+                    while (k > 1) {
+                        k >>= 4 ;
+                        pknyb(j / k) ;
+                        j = j % k ;
+                    }
+                }
+            }
+            i++ ;
+        }
+        if (bitweight != 16) pbmtopk_pkbyte(outputbyte) ;
+    } else {                 /* write bitmap character */
+        buff = 0 ;
+        pbit = 8 ;
+        i = firston ;
+        hbit = width ;
+        on = ! firston ;
+        state = 0 ;
+        count = repeatflag = 0 ;
+        while ((bitcounts[i] != 0) || state || (count > 0)) {
+            if (state) {
+                count = rcount ;
+                i = ri ;
+                on = ron ;
+                repeatflag-- ;
+            } else {
+                rcount = count ;
+                ri = i ;
+                ron = on ;
+            }
+            do {
+                if (count == 0) {
+                    if (bitcounts[i] < 0) {
+                        if (! state) repeatflag = -bitcounts[i] ;
+                        i++ ;
+                    }
+                    count = bitcounts[i] ;
+                    i++ ;
+                    on = !on ;
+                }
+                if ((count >= pbit) && (pbit < hbit)) {
+                    if (on) buff += (1 << pbit) - 1 ;
+                    pbmtopk_pkbyte(buff) ;
+                    buff = 0 ;
+                    hbit -= pbit ;
+                    count -= pbit ;
+                    pbit = 8 ;
+                } else if ((count < pbit) && (count < hbit)) {
+                    if (on) buff += (1 << pbit) - (1 << (pbit - count)) ;
+                    pbit -=  count ;
+                    hbit -= count ;
+                    count = 0 ;
+                } else {
+                    if (on) buff += (1 << pbit) - (1 << (pbit - hbit)) ;
+                    count -= hbit ;
+                    pbit -= hbit ;
+                    hbit = width ;
+                    if (pbit == 0) {
+                        pbmtopk_pkbyte(buff) ;
+                        buff = 0 ;
+                        pbit = 8 ;
+                    }
+                }
+            } while (hbit != width) ;
+            if (state && (repeatflag == 0)) {
+                count = scount ;
+                i = si ;
+                on = son ;
+                state = 0 ;
+            } else if (! state && (repeatflag > 0)) {
+                scount = count ;
+                si = i ;
+                son = on ;
+                state = 1 ;
+            }
+        }
+        if (pbit != 8) pbmtopk_pkbyte(buff) ;
+    }
+    if (predpkloc != pbmtopk_pkloc)
+        pm_error("bad predicted character length: character %d", car);
+    pbm_freerow(zerorow); 
+    pbm_freerow(onesrow); 
+    free((char *)repeatptr);
+    free((char *)bitcounts);
+}
+
+
+
+/* check that character is in valid range */
+static void 
+checkchar(void) {
+    if (car < 0 || car >= MAXPKCHAR)
+        pm_error("character must be in range 0 to %d", MAXPKCHAR-1) ;
+}
+
+
+
+/* read character information from an option file */
+static void 
+optionfile(const char * const name) {
+
+    FILE *fp ;
+    char buffer[MAXOPTLINE] ;
+   
+    fp = pm_openr(name);
+    while (!feof(fp)) {
+        char *here = buffer;
+      
+        if (fgets(buffer, MAXOPTLINE, fp) == NULL) break ;
+        while (ISSPACE(*here)) here++ ;
+        if (*here && *here == '=') {
+            if (sscanf(here+1, "%d", &car) != 1)
+                pm_error("bad option file line %s", buffer) ;
+        } else if (*here && *here != '%' && *here != '#') {
+            char str[NAMELENGTH] ;
+            integer i, n;
+     
+            checkchar() ;
+            if (sscanf(here, "%s%n", str, &n) != 1)
+                pm_error("bad option file line %s", buffer) ;
+            filename[car] = strdup(str);
+            if (filename[car] == NULL)
+                pm_error("out of memory allocating filename %s", str);
+            for (i = 1; i < 256; i<<=1) {
+                here += n;
+                if (sscanf(here, "%s%n", str, &n) != 1) break ;
+                if (strcmp(str, "*")) {
+                    charflags[car] |= i ;
+                    switch (i) {
+                    case XOFFSET:
+                        xoffset[car] = atoi(str) ;
+                        break ;
+                    case YOFFSET:
+                        yoffset[car] = atoi(str) ;
+                        break ;
+                    case HORZESC:
+                        horzesc[car] = atoi(str) ;
+                        break ;
+                    case VERTESC:
+                        vertesc[car] = atoi(str) ;
+                        break ;
+                    case TFMWIDTH:
+                        tfmindex[car] = add_tfmwidth(fixword(atof(str))) ;
+                        break ;
+                    case TFMHEIGHT:
+                        hgtindex[car] = add_tfmheight(fixword(atof(str))) ;
+                        break ;
+                    case TFMDEPTH:
+                        depindex[car] = add_tfmdepth(fixword(atof(str))) ;
+                        break ;
+                    case TFMITALIC:
+                        italindex[car] = add_tfmitalic(fixword(atof(str))) ;
+                        break ;
+                    }
+                }
+            }
+            car++ ;
+        }
+    }
+    pm_close(fp) ;
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+    integer i, hesc, vesc, xoff, yoff, tfwid, tfdep, tfhgt, tfital ;
+    byte flags ;
+    const char * const usage = "pkfile[.pk] tfmfile[.tfm] dpi "
+        "[-s designsize] [-p num param...]\n"
+        "[-C codingscheme ] [-F family] [-c num | <char>]...\n"
+        "<char> is:\n"
+        "[-W tfmwidth] [-H tfmheight] [-D tfmdepth] [-I ital_corr] "
+        "[-h horiz]\n"
+        "[-v vert] [-x xoffset] [-y yoffset] file\n"
+        "or:\n"
+        "-f optfile\n" ;
+
+    pbm_init(&argc, argv);
+    initialize_pk() ;
+   
+    if (--argc < 1) pm_usage(usage) ;
+    strcpy(pkname, *++argv) ;
+    pbmtopk_add_suffix(pkname, ".pk") ;
+   
+    if (--argc < 1) pm_usage(usage) ;
+    strcpy(tfmname, *++argv) ;
+    pbmtopk_add_suffix(tfmname, ".tfm") ;
+   
+    if (--argc < 1) pm_usage(usage) ;
+    resolution = atoi(*++argv) ;
+    if (resolution < 1 || resolution > 32767)
+        pm_error("unlikely resolution %d dpi", resolution);
+   
+    car = flags = hesc = vesc = xoff = yoff = tfwid = 0;
+    while (++argv, --argc) {
+        if (argv[0][0] == '-' && argv[0][1]) {
+            char c, *p;
+            c = argv[0][1] ;
+            if (argv[0][2]) p = *argv + 2 ;    /* set argument pointer */
+            else if (++argv, --argc) p = *argv ;
+            else pm_usage(usage) ;
+            switch (c) {
+            case 'C':
+                codingscheme = p;
+                break ;
+            case 'F':
+                familyname = p;
+                break ;
+            case 'c':
+                car = atoi(p) ;
+                break ;
+            case 's':
+                designsize = fixword(atof(p));
+                if (designsize < 1048576)
+                    pm_error("design size %f out of range", 
+                             unfixword(designsize));
+            case 'h':
+                hesc = atoi(p) ;
+                flags |= HORZESC ;
+                break ;
+            case 'v':
+                vesc = atoi(p) ;
+                flags |= VERTESC ;
+                break ;
+            case 'x':
+                xoff = atoi(p) ;
+                flags |= XOFFSET ;
+                break ;
+            case 'y':
+                yoff = atoi(p) ;
+                flags |= YOFFSET ;
+                break ;
+            case 'W':
+                tfwid = fixword(atof(p)) ;
+                flags |= TFMWIDTH ;
+                break ;
+            case 'H':
+                tfhgt = fixword(atof(p)) ;
+                flags |= TFMHEIGHT ;
+                break ;
+            case 'D':
+                tfdep = fixword(atof(p)) ;
+                flags |= TFMDEPTH ;
+                break ;
+            case 'I':
+                tfital = fixword(atof(p)) ;
+                flags |= TFMITALIC ;
+                break ;
+            case 'f':
+                optionfile(p) ;
+                break ;
+            case 'p':
+                numparam = atoi(p);
+                if (numparam < 1 || numparam > MAXPARAMS)
+                    pm_error("parameter count %d out of range", numparam);
+                for (i=0; i<numparam; i++)
+                    if (++argv,--argc)
+                        parameters[i] = fixword(atof(*argv)) ;
+                    else
+                        pm_error("not enough parameters (-p)");
+                break ;
+            default:
+                pm_usage(usage) ;
+            }
+        } else  {
+            checkchar() ;
+            if (flags & TFMWIDTH)
+                tfmindex[car] = add_tfmwidth(tfwid);
+            if (flags & TFMDEPTH)
+                depindex[car] = add_tfmdepth(tfdep);
+            if (flags & TFMHEIGHT)
+                hgtindex[car] = add_tfmheight(tfhgt);
+            if (flags & TFMITALIC)
+                italindex[car] = add_tfmitalic(tfital);
+            horzesc[car] = hesc ;
+            vertesc[car] = vesc ;
+            xoffset[car] = xoff ;
+            yoffset[car] = yoff ;
+            filename[car] = *argv ;
+            charflags[car] = flags ;
+            car++ ;
+            flags = 0;
+        }
+    }
+    hppp = ROUND((resolution<<16) / 72.27) ;
+    pkfile = pm_openw(pkname);
+    tfmfile = pm_openw(tfmname);
+    writepreamble() ;
+    for (car = 0 ; car < MAXPKCHAR ; car++)
+        if (filename[car]) {
+            readcharacter() ;
+            shipcharacter() ;
+        }
+    writepostamble() ;
+    writetfmfile() ;
+    pm_close(pkfile) ;
+    pm_close(tfmfile) ;
+
+    return 0;
+}
+
diff --git a/converter/pbm/pbmtoplot.c b/converter/pbm/pbmtoplot.c
new file mode 100644
index 00000000..8c9075b2
--- /dev/null
+++ b/converter/pbm/pbmtoplot.c
@@ -0,0 +1,76 @@
+/* pbmtoplot.c - read a portable bitmap and produce a UNIX-format plot file.
+**
+** Copyright (C) 1990 by Arthur David Olson.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <stdio.h>
+#include "pbm.h"
+
+static void puttwo ARGS((int i));
+static void
+puttwo( i )
+    int i;
+    {
+    (void) putchar(i);
+    (void) putchar(i >> 8);
+    }
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    register bit** bits;
+    register int row, col, scol;
+    int	rows, cols;
+
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[pbmfile]" );
+
+    ifp = (argc == 2) ? pm_openr( argv[1] ) : stdin;
+
+    bits = pbm_readpbm( ifp, &cols, &rows );
+
+    pm_close( ifp );
+
+    (void) putchar( 's' );
+    puttwo( 0 );
+    puttwo( 0 );
+    puttwo( rows - 1 );
+    puttwo( cols - 1 );
+    for ( row = 0; row < rows; ++row )
+	{
+	for ( col = 0; col < cols; ++col )
+	    {
+	    if ( bits[row][col] == PBM_WHITE )
+		continue;
+	    scol = col;
+	    while ( ++col < cols && bits[row][col] == PBM_BLACK )
+		; /* nothing */
+	    --col;
+	    if ( col == scol )
+		(void) putchar( 'p' );
+	    else
+		{
+		(void) putchar( 'l' );
+		puttwo( scol );
+		puttwo( rows - 1 - row );
+		}
+	    puttwo( col );
+	    puttwo( rows - 1 - row );
+	    }
+	}
+
+    exit( 0 );
+    }
diff --git a/converter/pbm/pbmtoppa/CREDITS b/converter/pbm/pbmtoppa/CREDITS
new file mode 100644
index 00000000..007d9a09
--- /dev/null
+++ b/converter/pbm/pbmtoppa/CREDITS
@@ -0,0 +1,12 @@
+CREDITS
+-------
+
+This project would not be where it is without the help of the following
+people:
+
+Ben Boule - first contacted me about the 720 series and helped with testing
+
+Jim Peterson - spent hours modifying the code for the 720 and adding lots of
+features, including all the configurability options.
+
+Kirk Reiten - helped with testing the 1000 series code
diff --git a/converter/pbm/pbmtoppa/INSTALL-MORE b/converter/pbm/pbmtoppa/INSTALL-MORE
new file mode 100644
index 00000000..2565c740
--- /dev/null
+++ b/converter/pbm/pbmtoppa/INSTALL-MORE
@@ -0,0 +1,187 @@
+Installation of ppa-0.8.5 with S.u.S.E. Linux
+(Special Installation with Hp820 and paper size A4)
+___________________________________________________
+
+
+0. Introduction
+
+This text describes how to use the package pbm2ppa written by Tim Norman
+with the S.u.S.E Linux System. This program allows the use of GDI (Winows
+only) printers with Linux. The program pbm2ppa is actually a converter
+between the two formats pbm (an output format from ghostscript) and the
+format understood by the HP printers 720, 820 and 1000. So anyway we have to
+use ghostscript to produce pbm (or faster: pbmraw) files. To print ascii
+files there is an extra step invoking enscript to convert the ascii to
+postscript files.  I rather constructed two new printer spoolers in printcap
+from scratch then using apsfilter: one for postscript files and another for
+ascii files. I welcome solutions in combination with the apsfilter script.
+The installation is quiet easy - after seven steps you should be ready to
+print postscript and ascii files, but it may take some time to adjust the
+constants properly, don't despair.
+
+0.1. Modifications
+
+This was modified on October 18, 1998 by Tim Norman to conform to the new
+A4 paper support in version 0.8.5.
+
+
+1. Installation of program package ppa-0.8.5
+
+Get the packate at http://www.rpi.edu/~normat/technical/ppa/ and compile it
+with
+
+# make 820
+
+or put in your printer number (720, 820 or 1000) (see also INSTALL and README
+file).
+
+
+2. To adjust the paper size to DIN A4, use the -s a4 option to pbm2ppa or
+change your pbm2ppa.conf file to read "papersize a4" (see step 5).
+
+
+3. You can now calibrate the printer with 
+
+For US size paper:
+# pbmtpg | pbm2ppa > /dev/lp1 ( as root )
+For A4 size paper:
+# pbmtpg -a4 | pbm2ppa -s a4 > /dev/lp1 ( as root )
+
+or you try first printing some sample files and calibrate in step 5.
+
+
+4. Now you can print (postscript) files with a shell script like this:
+
+Contents of print:
+
+cat $1 | gs -sDEVICE=pbmraw -q -dNOPAUSE -r600 -sOutputFile=- - | \
+pbm2ppa - - >/dev/lp1
+
+After changing the file modes (i.e. chmod 755 print) you are able to print
+a postscript file invoking the shell script print like:
+
+# print filename.ps
+
+
+To print ascii files just extend the script print with the use of enscript:
+
+Contents of printascii:
+
+enscript -2rj -p- $1 | \
+gs -sDEVICE=pbmraw -q -dNOPAUSE -r600 -sOutputFile=- - | \
+pbm2ppa - - >/dev/lp1
+
+
+Check the manpage for enscript to adjust the options to your flavour.
+Now you can also print ascii files with
+
+# printascii filename.ascii
+
+
+5. It may be possible that you have to recalibrate your printer (see 3.)
+
+Here follow the results from my calibration after printing ascii files with the
+shell script printascii (see 4.). The program pbm2ppa takes the arguments in the
+following order:
+
+1. shell arguments
+2. config file /etc/pbm2ppa.conf
+3. Compiled options from default.h
+
+So whenever you invoke pbm2ppa without arguments the program uses the options
+stored in the file /etc/pbm2ppa.conf, so I suggest to leave there a copy of
+this file.
+
+# Sample configuration file for the HP820 and DIN A4 paper size
+#
+# This file will be automatically read upon startup if it's placed in
+# /etc/pbm2ppa.conf
+#
+
+version  820
+
+papersize	a4
+
+xoff      0 # \ Adjust these for your printer.
+yoff    -600 # / (see CALIBRATE)
+
+# 1/4 inch margins all around (at 600 DPI)
+top       50
+bottom    50
+left      50
+right     50
+
+
+6. To integrate the converter into the Linux system we create two printer
+spooler in /etc/printcap. One to print postscript files and another to print
+plain ascii files.
+
+Contents of /etc/printcap:
+
+lp:\
+        :lp=/dev/lp1:\
+        :sd=/var/spool/lpd/lp:\
+        :lf=/var/spool/lpd/lp/log:\
+        :af=/var/spool/lpd/lp/acct:\
+        :if=/usr/local/bin/ps.if:\
+        :la:mx#0:\
+        :sh:sf:
+
+ascii:\
+        :lp=/dev/lp1:\
+        :sd=/var/spool/lpd/ascii:\
+        :lf=/var/spool/lpd/ascii/log:\
+        :af=/var/spool/lpd/ascii/acct:\
+        :if=/usr/local/bin/ascii.if:\
+        :la:mx#0:\
+        :sh:sf:
+
+
+Here follow some explanations (for more information consult the printcap
+manpage). We use the lp1 device, have two spool directories
+/var/spool/lpd/ascii and /var/spool/lpd/lp (better you create them now) a log
+file (lf) an accounting file (af), suppress form feeds (sf), suppress printing
+of burst page header (sh) and the maximum file size is unlimited (mx#0). To
+integrate the converter pbm2ppa into the system we use two input filters. Maybe
+you have a better solution in combination with apsfilter but until then try
+this way. Actually, the two input filter files are almost identical with the
+shell scripts print and printascii we created in step 4.
+
+File /usr/local/bin/ascii.if:
+
+#! /bin/sh
+enscript -2rj -p- | \
+gs -sDEVICE=pbmraw -q -dNOPAUSE -r600 -sOutputFile=- - | \
+/usr/local/bin/pbm2ppa - -
+
+
+File /usr/local/bin/ps.if:
+
+#! /bin/sh
+gs -sDEVICE=pbmraw -q -dNOPAUSE -r600 -sOutputFile=- - | \
+/usr/local/bin/pbm2ppa - -
+
+
+7. Place pbm2ppa in the directory /usr/local/bin. Now you are ready to print
+files with
+
+# lpr filename.ps
+
+and
+
+# lpr -P ascii filename.ascii
+
+like you are used to it.
+
+Enjoy
+
+
+19. May 1998
+
+Michael Buehlmann
+Badenerstrasse 285
+8003 Zuerich
+Switzerland
+
+mbuehlma@stud.ee.ethz.ch
+
diff --git a/converter/pbm/pbmtoppa/LICENSE b/converter/pbm/pbmtoppa/LICENSE
new file mode 100644
index 00000000..b8512125
--- /dev/null
+++ b/converter/pbm/pbmtoppa/LICENSE
@@ -0,0 +1,342 @@
+

+                    GNU GENERAL PUBLIC LICENSE

+                       Version 2, June 1991

+

+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.

+                          675 Mass Ave, Cambridge, MA 02139, USA

+ Everyone is permitted to copy and distribute verbatim copies

+ of this license document, but changing it is not allowed.

+

+                            Preamble

+

+  The licenses for most software are designed to take away your

+freedom to share and change it.  By contrast, the GNU General Public

+License is intended to guarantee your freedom to share and change free

+software--to make sure the software is free for all its users.  This

+General Public License applies to most of the Free Software

+Foundation's software and to any other program whose authors commit to

+using it.  (Some other Free Software Foundation software is covered by

+the GNU Library General Public License instead.)  You can apply it to

+your programs, too.

+

+  When we speak of free software, we are referring to freedom, not

+price.  Our General Public Licenses are designed to make sure that you

+have the freedom to distribute copies of free software (and charge for

+this service if you wish), that you receive source code or can get it

+if you want it, that you can change the software or use pieces of it

+in new free programs; and that you know you can do these things.

+

+  To protect your rights, we need to make restrictions that forbid

+anyone to deny you these rights or to ask you to surrender the rights.

+These restrictions translate to certain responsibilities for you if you

+distribute copies of the software, or if you modify it.

+

+  For example, if you distribute copies of such a program, whether

+gratis or for a fee, you must give the recipients all the rights that

+you have.  You must make sure that they, too, receive or can get the

+source code.  And you must show them these terms so they know their

+rights.

+

+  We protect your rights with two steps: (1) copyright the software, and

+(2) offer you this license which gives you legal permission to copy,

+distribute and/or modify the software.

+

+  Also, for each author's protection and ours, we want to make certain

+that everyone understands that there is no warranty for this free

+software.  If the software is modified by someone else and passed on, we

+want its recipients to know that what they have is not the original, so

+that any problems introduced by others will not reflect on the original

+authors' reputations.

+

+  Finally, any free program is threatened constantly by software

+patents.  We wish to avoid the danger that redistributors of a free

+program will individually obtain patent licenses, in effect making the

+program proprietary.  To prevent this, we have made it clear that any

+patent must be licensed for everyone's free use or not licensed at all.

+

+  The precise terms and conditions for copying, distribution and

+modification follow.

+

+                    GNU GENERAL PUBLIC LICENSE

+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

+

+  0. This License applies to any program or other work which contains

+a notice placed by the copyright holder saying it may be distributed

+under the terms of this General Public License.  The "Program", below,

+refers to any such program or work, and a "work based on the Program"

+means either the Program or any derivative work under copyright law:

+that is to say, a work containing the Program or a portion of it,

+either verbatim or with modifications and/or translated into another

+language.  (Hereinafter, translation is included without limitation in

+the term "modification".)  Each licensee is addressed as "you".

+

+Activities other than copying, distribution and modification are not

+covered by this License; they are outside its scope.  The act of

+running the Program is not restricted, and the output from the Program

+is covered only if its contents constitute a work based on the

+Program (independent of having been made by running the Program).

+Whether that is true depends on what the Program does.

+

+  1. You may copy and distribute verbatim copies of the Program's

+source code as you receive it, in any medium, provided that you

+conspicuously and appropriately publish on each copy an appropriate

+copyright notice and disclaimer of warranty; keep intact all the

+notices that refer to this License and to the absence of any warranty;

+and give any other recipients of the Program a copy of this License

+along with the Program.

+

+You may charge a fee for the physical act of transferring a copy, and

+you may at your option offer warranty protection in exchange for a fee.

+

+  2. You may modify your copy or copies of the Program or any portion

+of it, thus forming a work based on the Program, and copy and

+distribute such modifications or work under the terms of Section 1

+above, provided that you also meet all of these conditions:

+

+    a) You must cause the modified files to carry prominent notices

+    stating that you changed the files and the date of any change.

+

+    b) You must cause any work that you distribute or publish, that in

+    whole or in part contains or is derived from the Program or any

+    part thereof, to be licensed as a whole at no charge to all third

+    parties under the terms of this License.

+

+    c) If the modified program normally reads commands interactively

+    when run, you must cause it, when started running for such

+    interactive use in the most ordinary way, to print or display an

+    announcement including an appropriate copyright notice and a

+    notice that there is no warranty (or else, saying that you provide

+    a warranty) and that users may redistribute the program under

+    these conditions, and telling the user how to view a copy of this

+    License.  (Exception: if the Program itself is interactive but

+    does not normally print such an announcement, your work based on

+    the Program is not required to print an announcement.)

+

+These requirements apply to the modified work as a whole.  If

+identifiable sections of that work are not derived from the Program,

+and can be reasonably considered independent and separate works in

+themselves, then this License, and its terms, do not apply to those

+sections when you distribute them as separate works.  But when you

+distribute the same sections as part of a whole which is a work based

+on the Program, the distribution of the whole must be on the terms of

+this License, whose permissions for other licensees extend to the

+entire whole, and thus to each and every part regardless of who wrote it.

+

+Thus, it is not the intent of this section to claim rights or contest

+your rights to work written entirely by you; rather, the intent is to

+exercise the right to control the distribution of derivative or

+collective works based on the Program.

+

+In addition, mere aggregation of another work not based on the Program

+with the Program (or with a work based on the Program) on a volume of

+a storage or distribution medium does not bring the other work under

+the scope of this License.

+

+  3. You may copy and distribute the Program (or a work based on it,

+under Section 2) in object code or executable form under the terms of

+Sections 1 and 2 above provided that you also do one of the following:

+

+    a) Accompany it with the complete corresponding machine-readable

+    source code, which must be distributed under the terms of Sections

+    1 and 2 above on a medium customarily used for software interchange; or,

+

+    b) Accompany it with a written offer, valid for at least three

+    years, to give any third party, for a charge no more than your

+    cost of physically performing source distribution, a complete

+    machine-readable copy of the corresponding source code, to be

+    distributed under the terms of Sections 1 and 2 above on a medium

+    customarily used for software interchange; or,

+

+    c) Accompany it with the information you received as to the offer

+    to distribute corresponding source code.  (This alternative is

+    allowed only for noncommercial distribution and only if you

+    received the program in object code or executable form with such

+    an offer, in accord with Subsection b above.)

+

+The source code for a work means the preferred form of the work for

+making modifications to it.  For an executable work, complete source

+code means all the source code for all modules it contains, plus any

+associated interface definition files, plus the scripts used to

+control compilation and installation of the executable.  However, as a

+special exception, the source code distributed need not include

+anything that is normally distributed (in either source or binary

+form) with the major components (compiler, kernel, and so on) of the

+operating system on which the executable runs, unless that component

+itself accompanies the executable.

+

+If distribution of executable or object code is made by offering

+access to copy from a designated place, then offering equivalent

+access to copy the source code from the same place counts as

+distribution of the source code, even though third parties are not

+compelled to copy the source along with the object code.

+

+  4. You may not copy, modify, sublicense, or distribute the Program

+except as expressly provided under this License.  Any attempt

+otherwise to copy, modify, sublicense or distribute the Program is

+void, and will automatically terminate your rights under this License.

+However, parties who have received copies, or rights, from you under

+this License will not have their licenses terminated so long as such

+parties remain in full compliance.

+

+  5. You are not required to accept this License, since you have not

+signed it.  However, nothing else grants you permission to modify or

+distribute the Program or its derivative works.  These actions are

+prohibited by law if you do not accept this License.  Therefore, by

+modifying or distributing the Program (or any work based on the

+Program), you indicate your acceptance of this License to do so, and

+all its terms and conditions for copying, distributing or modifying

+the Program or works based on it.

+

+  6. Each time you redistribute the Program (or any work based on the

+Program), the recipient automatically receives a license from the

+original licensor to copy, distribute or modify the Program subject to

+these terms and conditions.  You may not impose any further

+restrictions on the recipients' exercise of the rights granted herein.

+You are not responsible for enforcing compliance by third parties to

+this License.

+

+  7. If, as a consequence of a court judgment or allegation of patent

+infringement or for any other reason (not limited to patent issues),

+conditions are imposed on you (whether by court order, agreement or

+otherwise) that contradict the conditions of this License, they do not

+excuse you from the conditions of this License.  If you cannot

+distribute so as to satisfy simultaneously your obligations under this

+License and any other pertinent obligations, then as a consequence you

+may not distribute the Program at all.  For example, if a patent

+license would not permit royalty-free redistribution of the Program by

+all those who receive copies directly or indirectly through you, then

+the only way you could satisfy both it and this License would be to

+refrain entirely from distribution of the Program.

+

+If any portion of this section is held invalid or unenforceable under

+any particular circumstance, the balance of the section is intended to

+apply and the section as a whole is intended to apply in other

+circumstances.

+

+It is not the purpose of this section to induce you to infringe any

+patents or other property right claims or to contest validity of any

+such claims; this section has the sole purpose of protecting the

+integrity of the free software distribution system, which is

+implemented by public license practices.  Many people have made

+generous contributions to the wide range of software distributed

+through that system in reliance on consistent application of that

+system; it is up to the author/donor to decide if he or she is willing

+to distribute software through any other system and a licensee cannot

+impose that choice.

+

+This section is intended to make thoroughly clear what is believed to

+be a consequence of the rest of this License.

+

+  8. If the distribution and/or use of the Program is restricted in

+certain countries either by patents or by copyrighted interfaces, the

+original copyright holder who places the Program under this License

+may add an explicit geographical distribution limitation excluding

+those countries, so that distribution is permitted only in or among

+countries not thus excluded.  In such case, this License incorporates

+the limitation as if written in the body of this License.

+

+  9. The Free Software Foundation may publish revised and/or new versions

+of the General Public License from time to time.  Such new versions will

+be similar in spirit to the present version, but may differ in detail to

+address new problems or concerns.

+

+Each version is given a distinguishing version number.  If the Program

+specifies a version number of this License which applies to it and "any

+later version", you have the option of following the terms and conditions

+either of that version or of any later version published by the Free

+Software Foundation.  If the Program does not specify a version number of

+this License, you may choose any version ever published by the Free Software

+Foundation.

+

+  10. If you wish to incorporate parts of the Program into other free

+programs whose distribution conditions are different, write to the author

+to ask for permission.  For software which is copyrighted by the Free

+Software Foundation, write to the Free Software Foundation; we sometimes

+make exceptions for this.  Our decision will be guided by the two goals

+of preserving the free status of all derivatives of our free software and

+of promoting the sharing and reuse of software generally.

+

+                            NO WARRANTY

+

+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY

+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN

+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES

+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED

+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF

+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS

+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE

+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,

+REPAIR OR CORRECTION.

+

+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING

+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR

+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,

+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING

+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED

+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY

+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER

+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE

+POSSIBILITY OF SUCH DAMAGES.

+

+                     END OF TERMS AND CONDITIONS

+

+        Appendix: How to Apply These Terms to Your New Programs

+

+  If you develop a new program, and you want it to be of the greatest

+possible use to the public, the best way to achieve this is to make it

+free software which everyone can redistribute and change under these terms.

+

+  To do so, attach the following notices to the program.  It is safest

+to attach them to the start of each source file to most effectively

+convey the exclusion of warranty; and each file should have at least

+the "copyright" line and a pointer to where the full notice is found.

+

+    <one line to give the program's name and a brief idea of what it does.>

+    Copyright (C) 19yy  <name of author>

+

+    This program is free software; you can redistribute it and/or modify

+    it under the terms of the GNU General Public License as published by

+    the Free Software Foundation; either version 2 of the License, or

+    (at your option) any later version.

+

+    This program is distributed in the hope that it will be useful,

+    but WITHOUT ANY WARRANTY; without even the implied warranty of

+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

+    GNU General Public License for more details.

+

+    You should have received a copy of the GNU General Public License

+    along with this program; if not, write to the Free Software

+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

+

+Also add information on how to contact you by electronic and paper mail.

+

+If the program is interactive, make it output a short notice like this

+when it starts in an interactive mode:

+

+    Gnomovision version 69, Copyright (C) 19yy name of author

+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.

+    This is free software, and you are welcome to redistribute it

+    under certain conditions; type `show c' for details.

+

+The hypothetical commands `show w' and `show c' should show the appropriate

+parts of the General Public License.  Of course, the commands you use may

+be called something other than `show w' and `show c'; they could even be

+mouse-clicks or menu items--whatever suits your program.

+

+You should also get your employer (if you work as a programmer) or your

+school, if any, to sign a "copyright disclaimer" for the program, if

+necessary.  Here is a sample; alter the names:

+

+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program

+  `Gnomovision' (which makes passes at compilers) written by James Hacker.

+

+  <signature of Ty Coon>, 1 April 1989

+  Ty Coon, President of Vice

+

+This General Public License does not permit incorporating your program into

+proprietary programs.  If your program is a subroutine library, you may

+consider it more useful to permit linking proprietary applications with the

+library.  If this is what you want to do, use the GNU Library General

+Public License instead of this License.

+

+

diff --git a/converter/pbm/pbmtoppa/Makefile b/converter/pbm/pbmtoppa/Makefile
new file mode 100644
index 00000000..c4be08b7
--- /dev/null
+++ b/converter/pbm/pbmtoppa/Makefile
@@ -0,0 +1,25 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/pbm/pbmtoppa
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+all: pbmtoppa
+
+BINARIES = pbmtoppa
+
+MERGEBINARIES = $(BINARIES)
+
+OBJECTS = pbmtoppa.o ppa.o pbm.o cutswath.o
+MERGE_OBJECTS = pbmtoppa.o2 ppa.o pbm.o cutswath.o
+
+include $(SRCDIR)/Makefile.common
+
+pbmtoppa: $(OBJECTS) $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o pbmtoppa $(OBJECTS) \
+	  -lm $(shell $(LIBOPT) $(NETPBMLIB)) $(LDLIBS) \
+	  $(RPATH) $(LADD)
+
diff --git a/converter/pbm/pbmtoppa/README.Netpbm b/converter/pbm/pbmtoppa/README.Netpbm
new file mode 100644
index 00000000..46c781e2
--- /dev/null
+++ b/converter/pbm/pbmtoppa/README.Netpbm
@@ -0,0 +1,30 @@
+Pbmtoppa was integrated into the Netpbm package in May 2000 by Bryan
+Henderson.  He took it from Tim Norton's pbm2ppa-0.8.6 package dated
+October 1998.
+
+Tim licenses the subject package to the public as described in the
+LICENSE file.
+
+The difference between what's in Netpbm and what was distributed by 
+Tim is:
+
+  - Tim called it 'pbm2ppa' Netpbm calls it 'pbmtoppa', to fit Netpbm
+    naming conventions.
+
+  - Tim's package included the program Pbmtpg, but the Netpbm directory
+    doesn't include that.  The program was integrated separately into
+    Netpbm as Pbmpage.
+
+  - Tim's package generated 3 different versions of Pbmtoppa, one for
+    each of the 720, 820 or 1000 printer models.  (They only differed
+    in their default parameters).  The netpbm version has only one
+    Pbmtoppa, and the (existing) -v option selects defaults for the 
+    specified printer model.
+
+  - Tim didn't have a man page.  His package had several other
+    documentation files, though, which are not in the Netpbm package
+    because the information is either specific to Tim's packaging or
+    the information in in the Netpbm man page.
+
+  - Jozsef Marak's extension to handle draft (300 dpi) printing is 
+    included.
diff --git a/converter/pbm/pbmtoppa/README.REDHAT b/converter/pbm/pbmtoppa/README.REDHAT
new file mode 100644
index 00000000..3586aea2
--- /dev/null
+++ b/converter/pbm/pbmtoppa/README.REDHAT
@@ -0,0 +1,29 @@
+RedHat users may find the following tip from Panayotis Vryonis <vrypan@hol.gr>
+helpful!
+
+Here is a tip to intergrate HP720C support in RedHat's printtool:
+
+Install pbm2ppa. Copy pbm2ppa to /usr/bin.
+Edit "printerdb" (in my system it is found in
+/usr/lib/rhs/rhs-printfilters )
+and append the following lines:
+----------------------Cut here
+-------------------------------------------
+StartEntry: DeskJet720C
+  GSDriver: pbm
+  Description: {HP DeskJet 720C}
+  About: { \
+           This driver supports the HP DeskJet 720C inkjet printer. \
+           It does does not support color printing. \
+           IMPORTANT! Insert \
+                "- | pbm2ppa -" \
+           in the "Extra GS Otions" field.\
+         }
+  Resolution: {600} {600} {}
+EndEntry
+--------------------------------------------------------------------------
+
+Now you can add an HP720C printer just like any other, using printtool.
+
+[Author's Note: The same should work for the 820 and 1000, but it hasn't
+been tested.  Also, use the pbmraw GSDriver if you have it; it's faster. ]
diff --git a/converter/pbm/pbmtoppa/cutswath.c b/converter/pbm/pbmtoppa/cutswath.c
new file mode 100644
index 00000000..0d44ce45
--- /dev/null
+++ b/converter/pbm/pbmtoppa/cutswath.c
@@ -0,0 +1,360 @@
+/* cutswath.c
+ * functions to cut a swath of a PBM file for PPA printers
+ * Copyright (c) 1998 Tim Norman.  See LICENSE for details.
+ * 3-15-98
+ *
+ * Mar 15, 1998  Jim Peterson  <jspeter@birch.ee.vt.edu>
+ *
+ *     Structured to accommodate both the HP820/1000, and HP720 series.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ppa.h"
+#include "ppapbm.h"
+#include "cutswath.h"
+
+extern int Width;
+extern int Height;
+extern int Pwidth;
+
+/* sweep_data->direction must be set already */
+/* Upon successful completion, sweep_data->image_data and
+   sweep_data->nozzle_data have been set to pointers which this routine
+   malloc()'d. */
+/* Upon successful completion, all members of *sweep_data have been set
+   except direction, vertical_pos, and next. */
+/* Returns: 0 if unsuccessful
+            1 if successful, but with non-printing result (end of page)
+            2 if successful, with printing result */
+int 
+cut_pbm_swath(pbm_stat* pbm,ppa_stat* prn,int maxlines,ppa_sweep_data* sweep_data)
+{
+  unsigned char *data, *ppa, *place, *maxplace;
+  int p_width, width8, p_width8;
+  int i, j, left, right, got_nonblank, numlines;
+  int horzpos, hp2;
+  int shift;
+  ppa_nozzle_data nozzles[2];
+
+  /* shift = 6 if DPI==300  */
+  /* shift = 12 if DPI==600 */ 
+  shift = ( prn->DPI == 300 ? 6:12 ) ;
+  
+  /* safeguard against the user freeing these */
+  sweep_data->image_data=NULL;
+  sweep_data->nozzle_data=NULL;
+
+  /* read the data from the input file */
+  width8 = (pbm->width + 7) / 8;
+ 
+/* 
+  fprintf(stderr,"cutswath(): width=%u\n",pbm->width);
+  fprintf(stderr,"cutswath(): height=%u\n",pbm->height);
+*/
+
+  if ((data=malloc(width8*maxlines)) == NULL)
+  {
+    fprintf(stderr,"cutswath(): could not malloc data storage\n");
+    return 0;
+  }
+
+  /* ignore lines that are above the upper margin */
+  while(pbm->current_line < prn->top_margin)
+    if(!pbm_readline(pbm,data))
+    {
+      fprintf(stderr,"cutswath(): A-could not read top margin\n");
+      free(data);
+      return 0;
+    }
+
+  /* eat all lines that are below the lower margin */
+  if(pbm->current_line >= Height - prn->bottom_margin)
+  {
+    while(pbm->current_line < pbm->height)
+      if(!pbm_readline(pbm,data))
+      {
+	fprintf(stderr,"cutswath(): could not clear bottom margin\n");
+	free(data);
+	return 0;
+      }
+    free(data);
+    return 1;
+  }
+
+  left = Pwidth-prn->right_margin/8;
+  right = prn->left_margin/8;
+
+  /* eat all beginning blank lines and then up to maxlines or lower margin */
+  got_nonblank=numlines=0;
+  while( (pbm->current_line < Height-prn->bottom_margin) &&
+	 (numlines < maxlines) )
+  {
+    if(!pbm_readline(pbm,data+width8*numlines))
+    {
+      fprintf(stderr,"cutswath(): B-could not read next line\n");
+      free(data);
+      return 0;
+    }
+    if(!got_nonblank)
+      for(j=prn->left_margin/8; j<left; j++)
+	if(data[j])
+	{
+	  left = j;
+	  got_nonblank=1;
+	  break;
+	}
+    if(got_nonblank)
+      {
+	int newleft = left, newright = right;
+
+	/* find left-most nonblank */
+	for (i = prn->left_margin/8; i < left; i++)
+	  if (data[width8*numlines+i])
+	    {
+	      newleft = i;
+	      break;
+	    }
+	/* find right-most nonblank */
+	for (i = Pwidth-prn->right_margin/8-1; i >= right; i--)
+	  if (data[width8*numlines+i])
+	    {
+	      newright = i;
+	      break;
+	    }
+	numlines++;
+
+	if (newright < newleft)
+	  {
+	    fprintf (stderr, "Ack! newleft=%d, newright=%d, left=%d, right=%d\n",
+		     newleft, newright, left, right);
+	    free (data);
+	    return 0;
+	  }
+
+	/* if the next line might push us over the buffer size, stop here! */
+	/* ignore this test for the 720 right now.  Will add better */
+	/* size-guessing for compressed data in the near future! */
+	if (numlines % 2 == 1 && prn->version != HP720)
+	  {
+	    int l = newleft, r = newright, w;
+	    
+	    l--;
+	    r+=2;
+	    l*=8;
+	    r*=8;
+	    w = r-l;
+	    w = (w+7)/8;
+	    
+	    if ((w+2*shift)*numlines > prn->bufsize)
+	      {
+		numlines--;
+		pbm_unreadline (pbm, data+width8*numlines);
+		break;
+	      }
+	    else
+	      {
+		left = newleft;
+		right = newright;
+	      }
+	  }
+	else
+	  {
+	    left = newleft;
+	    right = newright;
+	  }
+      }
+  }
+
+  if(!got_nonblank)
+  {
+    /* eat all lines that are below the lower margin */
+    if(pbm->current_line >= Height - prn->bottom_margin)
+    {
+      while(pbm->current_line < pbm->height)
+	if(!pbm_readline(pbm,data))
+	{
+	  fprintf(stderr,"cutswath(): could not clear bottom margin\n");
+	  free(data);
+	  return 0;
+	}
+      free(data);
+      return 1;
+    }
+    free(data);
+    return 0; /* error, since didn't get to lower margin, yet blank */
+  }
+
+  /* make sure numlines is even and >= 2 (b/c we have to pass the printer 
+     HALF of the number of pins used */
+  if (numlines == 1)
+    {
+      /* there's no way that we only have 1 line and not enough memory, so
+	 we're safe to increase numlines here.  Also, the bottom margin should
+	 be > 0 so we have some lines to read */
+      if(!pbm_readline(pbm,data+width8*numlines))
+	{
+	  fprintf(stderr,"cutswath(): C-could not read next line\n");
+	  free(data);
+	  return 0;
+	}
+      numlines++;
+    }
+  if (numlines % 2 == 1)
+    {
+      /* decrease instead of increasing so we don't max out the buffer */
+      numlines--;
+      pbm_unreadline (pbm, data+width8*numlines);
+    }
+
+  /* calculate vertical position */
+  sweep_data->vertical_pos = pbm->current_line;
+
+  /* change sweep params */
+  left--;
+  right+=2;
+  left *= 8;
+  right *= 8;
+
+  /* construct the sweep data */
+  p_width = right - left;
+  p_width8 = (p_width + 7) / 8;
+
+  if ((ppa = malloc ((p_width8+2*shift) * numlines)) == NULL)
+    {
+      fprintf(stderr,"cutswath(): could not malloc ppa storage\n");
+      free (data);
+      return 0;
+    }
+
+  place = ppa;
+
+  /* place 0's in the first 12 columns */
+  memset (place, 0, numlines/2 * shift);
+  place += numlines/2 * shift;
+
+
+  if(sweep_data->direction == right_to_left)  /* right-to-left */
+  {
+    for (i = p_width8+shift-1; i >= 0; i--)
+    {
+      if (i >= shift)
+      {
+	for (j = 0; j < numlines/2; j++)
+	  *place++ = data[j*2*width8 + i + left/8-shift];
+      }
+      else
+      {
+	memset (place, 0, numlines/2);
+	place += numlines/2;
+      }
+
+      if (i < p_width8)
+      {
+	for (j = 0; j < numlines/2; j++)
+	  *place++ = data[(j*2+1)*width8 + i + left/8];
+      }
+      else
+      {
+	memset (place, 0, numlines/2);
+	place += numlines/2;
+      }
+    }
+  }
+  else /* sweep_data->direction == left_to_right */
+  {
+    for (i = 0; i < p_width8+shift; i++)
+    {
+      if (i < p_width8)
+      {
+	for (j = 0; j < numlines/2; j++)
+	  *place++ = data[(j*2+1)*width8 + i + left/8];
+      }
+      else
+      {
+	memset (place, 0, numlines/2);
+	place += numlines/2;
+      }
+
+      if (i >= shift)
+      {
+	for (j = 0; j < numlines/2; j++)
+	  *place++ = data[j*2*width8 + i + left/8 - shift];
+      }
+      else
+      {
+	memset (place, 0, numlines/2);
+	place += numlines/2;
+      }
+    }
+  }
+
+  /* done with data */
+  free(data);
+
+  /* place 0's in the last 12 columns */
+  memset (place, 0, numlines/2 * shift);
+  place += numlines/2 * shift;
+  maxplace = place;
+
+  /* create sweep data */
+  sweep_data->image_data = ppa;
+  sweep_data->data_size = maxplace-ppa;
+  sweep_data->in_color = False;
+
+  /*
+  horzpos = left*600/prn->DPI + (sweep_data->direction==left_to_right ? 0*600/prn->DPI : 0);
+  */
+  horzpos = left * 600/prn->DPI;
+
+  hp2 = horzpos + ( p_width8 + 2*shift )*8 * 600/prn->DPI;
+  
+ 
+  sweep_data->left_margin = horzpos;
+  sweep_data->right_margin = hp2 + prn->marg_diff;
+
+  
+  for (i = 0; i < 2; i++)
+  {
+    nozzles[i].DPI = prn->DPI;
+        
+    nozzles[i].pins_used_d2 = numlines/2;
+    nozzles[i].unused_pins_p1 = 301-numlines;
+    nozzles[i].first_pin = 1;
+    if (i == 0)
+    {
+      nozzles[i].left_margin = horzpos + prn->marg_diff;
+      nozzles[i].right_margin = hp2 + prn->marg_diff;
+      if(sweep_data->direction == right_to_left)
+       /* 0 */
+	nozzles[i].nozzle_delay=prn->right_to_left_delay[0];
+      else
+       /* 6 */
+	nozzles[i].nozzle_delay=prn->left_to_right_delay[0];
+    }
+    else
+    {
+      nozzles[i].left_margin = horzpos;
+      nozzles[i].right_margin = hp2;
+      if(sweep_data->direction == right_to_left)
+       /* 2 */
+	nozzles[i].nozzle_delay=prn->right_to_left_delay[1];
+      else
+       /* 0 */
+	nozzles[i].nozzle_delay=prn->left_to_right_delay[1];
+
+    }
+  }
+
+  sweep_data->nozzle_data_size = 2;
+  sweep_data->nozzle_data = malloc(sizeof(nozzles));
+  if(sweep_data->nozzle_data == NULL)
+    return 0;
+  memcpy(sweep_data->nozzle_data,nozzles,sizeof(nozzles));
+
+  return 2;
+}
+
+
diff --git a/converter/pbm/pbmtoppa/cutswath.h b/converter/pbm/pbmtoppa/cutswath.h
new file mode 100644
index 00000000..430558de
--- /dev/null
+++ b/converter/pbm/pbmtoppa/cutswath.h
@@ -0,0 +1,4 @@
+int 
+cut_pbm_swath(pbm_stat* pbm,ppa_stat* prn,int maxlines,
+              ppa_sweep_data* sweep_data);
+
diff --git a/converter/pbm/pbmtoppa/defaults.h b/converter/pbm/pbmtoppa/defaults.h
new file mode 100644
index 00000000..481b9585
--- /dev/null
+++ b/converter/pbm/pbmtoppa/defaults.h
@@ -0,0 +1,65 @@
+/* defaults.h
+ * Default printer values.  Edit these and recompile if so desired.
+ * [Note: a /etc/pbm2ppa.conf file will override these]
+ */
+#ifndef _DEFAULTS_H
+#define _DEFAULTS_H
+
+/* Refer to CALIBRATION file about these settings */
+
+#define HP1000_PRINTER        ( HP1000 )
+
+#define HP1000_MARG_DIFF      (  0x62 )
+#define HP1000_BUFSIZE        ( 100*1024 )
+
+#define HP1000_X_OFFSET       (   100 )
+#define HP1000_Y_OFFSET       (  -650 )
+
+#define HP1000_TOP_MARGIN     (   150 )
+#define HP1000_LEFT_MARGIN    (   150 )
+#define HP1000_RIGHT_MARGIN   (   150 )
+#define HP1000_BOTTOM_MARGIN  (   150 )
+
+
+#define HP720_PRINTER        ( HP720 )
+
+#define HP720_MARG_DIFF      (     2 )
+#define HP720_BUFSIZE        ( 200*1024 )
+
+#define HP720_X_OFFSET       (   169 )
+#define HP720_Y_OFFSET       (  -569 )
+
+#define HP720_TOP_MARGIN     (   150 )
+#define HP720_LEFT_MARGIN    (   150 )
+#define HP720_RIGHT_MARGIN   (   150 )
+#define HP720_BOTTOM_MARGIN  (   150 )
+
+
+#define HP820_PRINTER        ( HP820 )
+
+#define HP820_MARG_DIFF      (  0x62 )
+#define HP820_BUFSIZE        ( 100*1024 )
+
+#define HP820_X_OFFSET       (    75 )
+#define HP820_Y_OFFSET       (  -500 )
+
+#define HP820_TOP_MARGIN     (    80 )
+#define HP820_LEFT_MARGIN    (    80 )
+#define HP820_RIGHT_MARGIN   (    80 )
+#define HP820_BOTTOM_MARGIN  (   150 )
+
+
+
+#define DEFAULT_PRINTER        HP1000_PRINTER
+
+#define DEFAULT_X_OFFSET       HP1000_X_OFFSET
+#define DEFAULT_Y_OFFSET       HP1000_Y_OFFSET
+
+#define DEFAULT_TOP_MARGIN     HP1000_TOP_MARGIN
+#define DEFAULT_LEFT_MARGIN    HP1000_LEFT_MARGIN
+#define DEFAULT_RIGHT_MARGIN   HP1000_RIGHT_MARGIN
+#define DEFAULT_BOTTOM_MARGIN  HP1000_BOTTOM_MARGIN
+
+#define DEFAULT_DPI ( 600 )
+
+#endif
diff --git a/converter/pbm/pbmtoppa/pbm.c b/converter/pbm/pbmtoppa/pbm.c
new file mode 100644
index 00000000..5c9798f2
--- /dev/null
+++ b/converter/pbm/pbmtoppa/pbm.c
@@ -0,0 +1,135 @@
+/* pbm.c
+ * code for reading the header of an ASCII PBM file
+ * Copyright (c) 1998 Tim Norman.  See LICENSE for details
+ * 2-25-98
+ *
+ * Mar 18, 1998  Jim Peterson  <jspeter@birch.ee.vt.edu>
+ *
+ *     Restructured to encapsulate more of the PBM handling.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ppapbm.h"
+
+int make_pbm_stat(pbm_stat* pbm,FILE* fptr)
+{
+  char line[1024];
+
+  pbm->fptr=fptr;
+  pbm->version=none;
+  pbm->current_line=0;
+  pbm->unread = 0;
+
+  if (fgets (line, 1024, fptr) == NULL)
+    return 0;
+  line[strlen(line)-1] = 0;
+
+  if(!strcmp(line,"P1")) pbm->version=P1;
+  if(!strcmp(line,"P4")) pbm->version=P4;
+  if(pbm->version == none)
+  {
+    fprintf(stderr,"pbm_readheader(): unknown PBM magic '%s'\n",line);
+    return 0;
+  }
+
+  do
+    if (fgets (line, 1024, fptr) == NULL)
+      return 0;
+  while (line[0] == '#');
+
+  if (2 != sscanf (line, "%d %d", &pbm->width, &pbm->height))
+    return 0;
+
+  return 1;
+}
+
+static int getbytes(FILE *fptr,int width,unsigned char* data)
+{
+  unsigned char mask,acc,*place;
+  int num;
+
+  if(!width) return 0;
+  for(mask=0x80, acc=0, num=0, place=data; num<width; )
+  {
+    switch(getc(fptr))
+    {
+    case EOF:      
+      return 0;
+    case '1':
+      acc|=mask;
+      /* fall through */
+    case '0':
+      mask>>=1;
+      num++;
+      if(!mask) /* if(num%8 == 0) */
+      {
+	*place++ = acc;
+	acc=0;
+	mask=0x80;
+      }
+    }
+  }
+  if(width%8)
+    *place=acc;
+  return 1;
+}
+
+/* Reads a single line into data which must be at least (pbm->width+7)/8
+   bytes of storage */
+int pbm_readline(pbm_stat* pbm,unsigned char* data)
+{
+  int tmp,tmp2;
+
+  if(pbm->current_line >= pbm->height) return 0;
+
+  if (pbm->unread)
+    {
+      memcpy (data, pbm->revdata, (pbm->width+7)/8);
+      pbm->current_line++;
+      pbm->unread = 0;
+      free (pbm->revdata);
+      return 1;
+    }
+
+  switch(pbm->version)
+  {
+  case P1:
+    if(getbytes(pbm->fptr,pbm->width,data))
+    {
+      pbm->current_line++;
+      return 1;
+    }
+    return 0;
+
+  case P4:
+    tmp=(pbm->width+7)/8;
+    tmp2=fread(data,1,tmp,pbm->fptr);
+    if(tmp2 == tmp)
+    {
+      pbm->current_line++;
+      return 1;
+    }
+    fprintf(stderr,"pbm_readline(): error reading line data (%d)\n",tmp2);
+    return 0;
+
+  default:
+    fprintf(stderr,"pbm_readline(): unknown PBM version\n");
+    return 0;
+  }
+}
+
+/* push a line back into the buffer; we read too much! */
+void pbm_unreadline (pbm_stat *pbm, void *data)
+{
+  /* can only store one line in the unread buffer */
+  if (pbm->unread)
+    return;
+
+  pbm->unread = 1;
+  pbm->revdata = malloc ((pbm->width+7)/8);
+  memcpy (pbm->revdata, data, (pbm->width+7)/8);
+  pbm->current_line--;
+}
diff --git a/converter/pbm/pbmtoppa/pbmtoppa.c b/converter/pbm/pbmtoppa/pbmtoppa.c
new file mode 100644
index 00000000..85a98529
--- /dev/null
+++ b/converter/pbm/pbmtoppa/pbmtoppa.c
@@ -0,0 +1,449 @@
+/* pbmtoppa.c
+ * program to print a 600 dpi PBM file on the HP DJ820Cse or the HP720 series.
+ * Copyright (c) 1998 Tim Norman.  See LICENSE for details
+ * 2-24-98
+ */
+
+#define _BSD_SOURCE
+    /* This makes sure strcasecmp() is in string.h */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "pbm.h"
+#include "ppa.h"
+#include "ppapbm.h"
+#include "cutswath.h"
+
+#include "defaults.h"
+
+/* Paper sizes in 600ths of an inch. */
+
+/* US is 8.5 in by 11 in */
+
+#define USWIDTH  (5100)
+#define USHEIGHT (6600)
+
+/* A4 is 210 mm by 297 mm == 8.27 in by 11.69 in */
+
+#define A4WIDTH  (4960)
+#define A4HEIGHT (7016)
+
+int Width;      /* width and height in 600ths of an inch */
+int Height;
+int Pwidth;     /* width in bytes */
+
+#define MAX_LINES 300
+
+ppa_stat printer;
+
+static int 
+print_pbm(FILE * const in) {
+
+    char line[1024];
+    pbm_stat pbm;
+    int done_page, numpages = 0;
+    ppa_sweep_data sweeps[2];
+    int current_sweep, previous_sweep;
+
+    ppa_init_job(&printer);
+
+    while(make_pbm_stat(&pbm, in)) {
+        if (pbm.width != Width || pbm.height != Height) 
+            pm_error("print_pbm(): Input image is not the size "
+                    "of a page for Page %d.  "
+                    "The input is %dW x %dH, "
+                     "while a page is %dW x %dH pixels.\n"
+                    "Page size is controlled by options and the configuration "
+                    "file.",
+                     numpages+1, pbm.width, pbm.height, Width, Height);
+
+        ppa_init_page(&printer);
+        ppa_load_page(&printer);
+
+        sweeps[0].direction = right_to_left;
+        sweeps[0].next=&sweeps[1];
+        sweeps[1].direction = left_to_right;
+        sweeps[1].next=&sweeps[0];
+
+        current_sweep=0;
+        previous_sweep=-1;
+
+        done_page=0;
+        while(!done_page) {
+            int rc;
+            rc = cut_pbm_swath(&pbm, &printer, MAX_LINES,
+                               &sweeps[current_sweep]);
+            switch(rc) {
+            case 0:
+                pm_error("print_pbm(): cut_pbm_swath() failed.");
+                break;
+            case 1:
+                done_page=1;
+                break;
+            case 2:
+                if (previous_sweep >= 0) {
+                    ppa_print_sweep(&printer, &sweeps[previous_sweep]);
+                    free(sweeps[previous_sweep].image_data);
+                    free(sweeps[previous_sweep].nozzle_data);
+                }
+                previous_sweep=current_sweep;
+                current_sweep= current_sweep==0 ? 1 : 0;
+                break;
+            default:
+                pm_error("print_pbm(): unknown return code from "
+                         "cut_pbm_swath()");
+            }
+        }
+        if (previous_sweep >= 0) {
+            sweeps[previous_sweep].next=NULL;
+            ppa_print_sweep(&printer,&sweeps[previous_sweep]);
+        }
+
+        free(sweeps[0].image_data);
+        free(sweeps[0].nozzle_data);
+        free(sweeps[1].image_data);
+        free(sweeps[1].nozzle_data);
+
+        ppa_eject_page(&printer);
+
+        /* eat any remaining whitespace */
+        if(pbm.version==P1)
+            fgets (line, 1024, in);
+
+        ++numpages;
+    }
+
+    if (numpages == 0)
+        pm_error("No pages printed!");
+
+    ppa_end_print(&printer);
+
+    fclose (pbm.fptr);
+    fclose (printer.fptr);
+
+    return 0;
+}
+
+
+
+static void 
+set_printer_specific_defaults()
+{
+    switch(printer.version)
+    {
+    case HP720:
+        printer.marg_diff     = HP720_MARG_DIFF;
+        printer.bufsize       = HP720_BUFSIZE;
+
+        printer.x_offset      = HP720_X_OFFSET;
+        printer.y_offset      = HP720_Y_OFFSET;
+        printer.top_margin    = HP720_TOP_MARGIN;
+        printer.left_margin   = HP720_LEFT_MARGIN;
+        printer.right_margin  = HP720_RIGHT_MARGIN;
+        printer.bottom_margin = HP720_BOTTOM_MARGIN;
+        break;
+    case HP820:
+        printer.marg_diff     = HP820_MARG_DIFF;
+        printer.bufsize       = HP820_BUFSIZE;
+
+        printer.x_offset      = HP820_X_OFFSET;
+        printer.y_offset      = HP820_Y_OFFSET;
+        printer.top_margin    = HP820_TOP_MARGIN;
+        printer.left_margin   = HP820_LEFT_MARGIN;
+        printer.right_margin  = HP820_RIGHT_MARGIN;
+        printer.bottom_margin = HP820_BOTTOM_MARGIN;
+        break;
+    case HP1000:
+        printer.marg_diff     = HP1000_MARG_DIFF;
+        printer.bufsize       = HP1000_BUFSIZE;
+
+        printer.x_offset      = HP1000_X_OFFSET;
+        printer.y_offset      = HP1000_Y_OFFSET;
+        printer.top_margin    = HP1000_TOP_MARGIN;
+        printer.left_margin   = HP1000_LEFT_MARGIN;
+        printer.right_margin  = HP1000_RIGHT_MARGIN;
+        printer.bottom_margin = HP1000_BOTTOM_MARGIN;
+        break;
+    default:
+        pm_error("set_printer_defaults(): unknown printer version");
+    }
+}
+
+
+
+static void 
+show_usage(const char* const prog)
+{
+    printf("usage: %s [ options ] [ <infile> [ <outfile> ] ]\n\n",prog);
+    printf("  Prints a pbm- or pbmraw-format <infile> to HP720/820/1000-format <outfile>.\n\n");
+    printf("    -v <version>    printer version (720, 820, or 1000)\n");
+    printf("    -x <xoff>       vertical offset adjustment in 1\"/600\n");
+    printf("    -y <yoff>       horizontal offset adjustment in 1\"/600\n");
+    printf("    -t <topmarg>    top margin in 1\"/600    (default: 150 = 0.25\")\n");
+    printf("    -l <leftmarg>   left margin in 1\"/600   (default: 150 = 0.25\")\n");
+    printf("    -r <rightmarg>  right margin in 1\"/600  (default: 150 = 0.25\")\n");
+    printf("    -b <botmarg>    bottom margin in 1\"/600 (default: 150 = 0.25\")\n");
+    printf("    -s <paper>      paper size (us, a4, default: us)\n");
+    printf("    -f <cfgfile>    read <cfgfile> as parameters\n\n");
+    printf("  The -x and -y options accumulate.  The -v option resets the horizontal and\n");
+    printf("  vertical adjustments to an internal default.  <infile> and <outfile> default\n");
+    printf("  to stdin and stdout.  '-' is a synonym for stdin and stdout.\n\n");
+    printf("  Configuration files specified with the '-f' parameter have the following\n  format:\n\n");
+    printf("    # Comment\n");
+    printf("    <key1> <value1>\n");
+    printf("    <key2> <value2>\n");
+    printf("    [etc.]\n\n");
+    printf("  Valid keys are 'version', 'xoffset', 'yoffset', 'topmargin', 'leftmargin',\n");
+    printf("  'rightmargin', 'bottommargin', 'papersize', or any non-null truncated\n");
+    printf("  version of these words.  Valid values are the same as with the corresponding\n");
+    printf("  command-line parameters.  Parameters in the configuration file act as though\n");
+    printf("  the corresponding parameters were substituted, in order, for the '-f'\n");
+    printf("  parameter which specified the file.\n\n");
+    printf("  The file /etc/pbmtoppa.conf, if it exists, is processed as a configuration\n");
+    printf("  file before any command-line parameters are processed.\n\n");
+}
+
+
+
+static void 
+parm_version(char* arg)
+{
+    if(!strcasecmp(arg,"hp720") || !strcmp(arg,"720"))
+        printer.version=HP720;
+    else if(!strcasecmp(arg,"hp820") || !strcmp(arg,"820"))
+        printer.version=HP820;
+    else if(!strcasecmp(arg,"hp1000") || !strcmp(arg,"1000"))
+        printer.version=HP1000;
+    else
+        pm_error("parm_version(): unknown printer version '%s'",arg);
+    set_printer_specific_defaults();
+}
+
+
+
+static void 
+parm_iversion(int arg)
+{
+    switch(arg)
+    {
+    case 720:
+        printer.version=HP720;
+        break;
+    case 820:
+        printer.version=HP820;
+        break;
+    case 1000:
+        printer.version=HP1000;
+        break;
+    default:
+        pm_error("parm_iversion(): unknown printer version '%d'", arg);
+    }
+    set_printer_specific_defaults();
+}
+
+
+
+static void 
+dump_config()
+{
+    printf("version:  ");
+    switch(printer.version)
+    {
+    case HP710:  printf("HP710\n");  break;
+    case HP720:  printf("HP720\n");  break;
+    case HP820:  printf("HP820\n");  break;
+    case HP1000: printf("HP1000\n"); break;
+    }
+    printf("x-offset: %d\ny-offset: %d\nmargins:\n top:    %d\n"
+           " left:   %d\n right:  %d\n bottom: %d\n",printer.x_offset,
+           printer.y_offset,printer.top_margin,printer.left_margin,
+           printer.right_margin,printer.bottom_margin);
+    exit(0);
+}
+
+
+
+static void 
+read_config_file(const char* const fname)
+{
+    FILE* cfgfile=fopen(fname,"r");
+    char line[1024],key[14],buf[10];
+    int len,value,lineno=1;
+
+    if(!cfgfile)
+        pm_error("read_config_file(): couldn't open file '%s'", fname);
+
+    while(fgets(line,1024,cfgfile))
+    {
+        if(strchr(line,'#'))
+            *strchr(line,'#')=0;
+        switch(sscanf(line,"%13s%9s",key,buf))
+        {
+        case 2:
+            value=atoi(buf);
+            len=strlen(key);
+            if(!strncmp(key,"version",len))
+                parm_iversion(value);
+            else if(!strncmp(key,"xoffset",len))
+                printer.x_offset=value;
+            else if(!strncmp(key,"yoffset",len))
+                printer.y_offset=value;
+            else if(!strncmp(key,"topmargin",len))
+                printer.top_margin=value;
+            else if(!strncmp(key,"leftmargin",len))
+                printer.left_margin=value;
+            else if(!strncmp(key,"rightmargin",len))
+                printer.right_margin=value;
+            else if(!strncmp(key,"bottommargin",len))
+                printer.bottom_margin=value;
+            else if(!strncmp(key,"papersize",len))
+            {
+                if(!strcmp(buf,"us"))
+                {
+                    Width = USWIDTH;
+                    Height = USHEIGHT;
+                }
+                else if(!strcmp(buf,"a4"))
+                {
+                    Width = A4WIDTH;
+                    Height = A4HEIGHT;
+                }
+                else
+                    pm_error("read_config_file(): unknown paper size %s", buf);
+            }
+            else if(!strcmp(key,"dump"))
+                dump_config();
+            else 
+                pm_error("read_config_file(): unrecognized parameter '%s' "
+                         "(line %d)", key, lineno);
+        case EOF:
+        case 0: 
+            break;
+        default:
+            pm_error("read_config_file(): error parsing config file "
+                     "(line %d)", lineno);
+        }
+        lineno++;
+    }
+
+    if(feof(cfgfile))
+    {
+        fclose(cfgfile);
+        return;
+    }
+
+    pm_error("read_config_file(): error parsing config file");
+}
+
+
+
+const char* const defaultcfgfile="/etc/pbmtoppa.conf";
+
+
+
+int 
+main(int argc, char *argv[]) {
+
+    int argn;
+    int got_in=0, got_out=0, do_continue=1;
+    FILE *in=stdin, *out=stdout;
+    struct stat tmpstat;
+
+    pbm_init(&argc, argv);
+
+    printer.version       = DEFAULT_PRINTER;
+    printer.x_offset      = DEFAULT_X_OFFSET;
+    printer.y_offset      = DEFAULT_Y_OFFSET;
+    printer.top_margin    = DEFAULT_TOP_MARGIN;
+    printer.left_margin   = DEFAULT_LEFT_MARGIN;
+    printer.right_margin  = DEFAULT_RIGHT_MARGIN;
+    printer.bottom_margin = DEFAULT_BOTTOM_MARGIN;
+    printer.DPI           = DEFAULT_DPI;
+    Width = USWIDTH;
+    Height = USHEIGHT;
+    set_printer_specific_defaults();
+
+    if(!stat(defaultcfgfile,&tmpstat))
+        read_config_file(defaultcfgfile);
+
+    for(argn=1; argn<argc; argn++)
+    {
+        if(!strcmp(argv[argn],"-h"))
+        {
+            show_usage(*argv);
+            return 0;
+        }
+        else if(!strcmp(argv[argn],"-d"))
+            dump_config();
+        else if(argn+1<argc)
+        {
+            do_continue=1;
+            if(!strcmp(argv[argn],"-v"))
+                parm_version(argv[++argn]);
+            else if(!strcmp(argv[argn],"-x"))
+                printer.x_offset+=atoi(argv[++argn]);
+            else if(!strcmp(argv[argn],"-y"))
+                printer.y_offset+=atoi(argv[++argn]);
+            else if(!strcmp(argv[argn],"-t"))
+                printer.top_margin=atoi(argv[++argn]);
+            else if(!strcmp(argv[argn],"-l"))
+                printer.left_margin=atoi(argv[++argn]);
+            else if(!strcmp(argv[argn],"-r"))
+                printer.right_margin=atoi(argv[++argn]);
+            else if(!strcmp(argv[argn],"-b"))
+                printer.bottom_margin=atoi(argv[++argn]);
+            else if(!strcmp(argv[argn],"-d"))
+                printer.DPI=atoi(argv[++argn]);
+            else if(!strcmp(argv[argn],"-s"))
+            {
+                argn++;
+                if(!strcmp(argv[argn],"us"))
+                {
+                    Width = USWIDTH;
+                    Height = USHEIGHT;
+                }
+                else if(!strcmp(argv[argn],"a4"))
+                {
+                    Width = A4WIDTH;
+                    Height = A4HEIGHT;
+                }
+                else
+                    pm_error("unknown paper size %s",argv[argn]);
+            }
+            else if(!strcmp(argv[argn],"-f"))
+                read_config_file(argv[++argn]);
+            else do_continue=0;
+            if(do_continue) continue;
+        }
+
+        if(!got_in)
+        {
+            if (strcmp (argv[argn], "-") == 0)
+                in = stdin;
+            else if ((in = fopen (argv[argn], "rb")) == NULL)
+                pm_error("main(): couldn't open file '%s'", 
+                         argv[argn]);
+            got_in=1;
+        }
+        else if(!got_out)
+        {
+            if (strcmp (argv[argn], "-") == 0)
+                out = stdout;
+            else if ((out = fopen (argv[argn], "wb")) == NULL)
+                pm_error("main(): couldn't open file '%s'", 
+                         argv[argn]);
+            got_out=1;
+        }
+        else
+            pm_error("main(): unrecognized parameter '%s'", argv[argn]);
+    }
+
+    Pwidth=(Width+7)/8;
+    printer.fptr=out;
+
+    return print_pbm (in);
+}
+
diff --git a/converter/pbm/pbmtoppa/pbmtoppa.conf.hp1000 b/converter/pbm/pbmtoppa/pbmtoppa.conf.hp1000
new file mode 100644
index 00000000..a3ba3260
--- /dev/null
+++ b/converter/pbm/pbmtoppa/pbmtoppa.conf.hp1000
@@ -0,0 +1,18 @@
+# Sample configuration file for the HP720
+#
+# This file will be automatically read upon startup if it's placed in
+# /etc/pbm2ppa.conf
+#
+
+version  1000
+
+papersize	us
+
+xoff     100 # \ Adjust these for your printer.
+yoff    -650 # / (see CALIBRATE)
+
+# 1/4 inch margins all around (at 600 DPI)
+top      150
+bottom   150
+left     150
+right    150
diff --git a/converter/pbm/pbmtoppa/pbmtoppa.conf.hp720 b/converter/pbm/pbmtoppa/pbmtoppa.conf.hp720
new file mode 100644
index 00000000..b8975564
--- /dev/null
+++ b/converter/pbm/pbmtoppa/pbmtoppa.conf.hp720
@@ -0,0 +1,18 @@
+# Sample configuration file for the HP720
+#
+# This file will be automatically read upon startup if it's placed in
+# /etc/pbm2ppa.conf
+#
+
+version  720
+
+papersize	us
+
+xoff     169 # \ Adjust these for your printer.
+yoff    -569 # / (see CALIBRATE)
+
+# 1/4 inch margins all around (at 600 DPI)
+top      150
+bottom   150
+left     150
+right    150
diff --git a/converter/pbm/pbmtoppa/pbmtoppa.conf.hp820 b/converter/pbm/pbmtoppa/pbmtoppa.conf.hp820
new file mode 100644
index 00000000..55a8cb7d
--- /dev/null
+++ b/converter/pbm/pbmtoppa/pbmtoppa.conf.hp820
@@ -0,0 +1,18 @@
+# Sample configuration file for the HP820
+#
+# This file will be automatically read upon startup if it's placed in
+# /etc/pbm2ppa.conf
+#
+
+version  820
+
+papersize	us
+
+xoff      75 # \ Adjust these for your printer.
+yoff    -500 # / (see CALIBRATE)
+
+# 1/4 inch margins all around (at 600 DPI)
+top       80
+bottom   150
+left      80
+right     80
diff --git a/converter/pbm/pbmtoppa/ppa.c b/converter/pbm/pbmtoppa/ppa.c
new file mode 100644
index 00000000..8363d927
--- /dev/null
+++ b/converter/pbm/pbmtoppa/ppa.c
@@ -0,0 +1,526 @@
+/* ppa.c
+ * functions to handle PPA communications
+ * Copyright (c) 1998 Tim Norman.  See LICENSE for details.
+ * 2-25-98
+ *
+ * Mar 3, 1998  Jim Peterson  <jspeter@birch.ee.vt.edu>
+ *
+ *     Restructured to accommodate both the HP820/1000, and HP720 series.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pm_config.h"
+  /* pm_config.h is necessary for __inline__ for those compilers that don't
+     define __inline__ themselves
+     */
+#include "ppa.h"
+
+/*
+  VLink packet structure:
+
+    Bytes  Description
+    --------------------------------------------
+      1    Packet-start marker (always '$')
+      1    Channel (0: image data, 1: commands/responses(*), 2: autostatus(*), 128: pacing(*))
+      2    Data length (N)
+      N    [remaining data (e.g., SCP or SCP2 packet when Channel=1)]
+
+  (*): responses, autostatus, and pacing are communicated from the printer to
+       the computer, and may be safely ignored.
+*/
+static void 
+vlink_put(FILE *fptr, int channel, int length, void *data)
+{
+  fputc ('$', fptr);
+  fputc (channel, fptr);
+  fputc (length>>8, fptr);     fputc (length&0xFF, fptr);
+  fwrite (data, length, 1, fptr);
+}
+
+/*
+  SCP packet structure:
+
+    Bytes  Description
+    --------------------------------------------
+      2    Command specifier
+      2    Command reference number
+      1    Priority
+      1    padding? (always zero)
+      2    Data length (N)
+      N    [remaining data]
+
+    ComSpec  ComRef     Command
+    -------------------------------
+       35       1     Initialize1
+      101       2     Initialize2
+       21       1     Initialize3
+       19       1     Handle Media
+       18       1     Print Sweep
+*/
+static void 
+scp_put(FILE *fptr, int comspec, int comref, int priority,
+	     int length, void *data)
+{
+  /* encapsulate the vlink_put call in here, to avoid a memcpy */
+  fputc ('$', fptr);
+  fputc (1, fptr);
+  fputc ((length+8)>>8, fptr);     fputc ((length+8)&0xFF, fptr);
+
+  fputc (comspec>>8, fptr);  fputc (comspec&0xFF, fptr);
+  fputc (comref>>8, fptr);   fputc (comref&0xFF, fptr);
+  fputc (priority, fptr);
+  fputc (0, fptr);
+  fputc (length>>8, fptr);   fputc (length&0xFF, fptr);
+
+  fwrite (data, length, 1, fptr);
+}
+
+
+/*
+  SCP2 packet structure:
+
+    Bytes  Description
+    --------------------------------------------
+      2    Command specifier
+      2    Packet length (N)
+      1    Priority
+      1    padding? (always zero)
+      2    Command reference number
+      4    Channel 0 buffer increment
+      4    version number? (always 00 02 00 00)
+     N-16  [remaining data]
+
+    ComSpec  ComRef     Command
+    -------------------------------
+     0x186      1     Initialize1
+     0x18f      2     Initialize2
+     0x183      1     Initialize3
+     0x181      1     Handle Media
+     0x180      1     Print Sweep
+*/
+static void 
+scp2_put(FILE *fptr,unsigned short comspec,unsigned short pkt_len_s16,
+	      unsigned char priority,unsigned short comref,unsigned data_len,
+	      void *data)
+{
+  /* encapsulate the vlink_put call in here, to avoid a memcpy */
+  fputc ('$', fptr);
+  fputc (1, fptr);
+  fputc ((pkt_len_s16+16)>>8, fptr);  fputc ((pkt_len_s16+16), fptr);
+
+  fputc (comspec>>8, fptr);  fputc (comspec, fptr);
+  fputc ((pkt_len_s16+16)>>8, fptr);  fputc ((pkt_len_s16+16), fptr);
+  fputc (priority, fptr);
+  fputc (0, fptr);
+  fputc (comref>>8, fptr);
+  fputc (comref, fptr);
+  fputc (data_len>>24, fptr);   fputc (data_len>>16, fptr);
+  fputc (data_len>>8, fptr);    fputc (data_len, fptr);
+  fputc (0, fptr);
+  fputc (2, fptr);
+  fputc (0, fptr);
+  fputc (0, fptr);
+
+  fwrite (data, pkt_len_s16, 1, fptr);
+}
+
+
+/*
+  SCP3 packet structure:
+
+    Bytes  Description
+    --------------------------------------------
+      2    Command specifier
+      2    Packet length (N)
+      1    Priority
+      1    padding? (always zero)
+      2    Command reference number
+      4    Channel 0 buffer increment
+      4    version number? (always 01 04 00 00)
+     N-16  [remaining data]
+
+    ComSpec  ComRef     Command
+    -------------------------------
+     0x186      1     Initialize1
+     0x18C     16     Initialize Printer name
+     0x1A1      1     Initialize4?
+     0x18f      2     Initialize2
+     0x183      1     Initialize3
+     0x181      1     Handle Media
+     0x180      1     Print Sweep
+*/
+static void 
+scp3_put(FILE *fptr,unsigned short comspec,unsigned short pkt_len_s16,
+	      unsigned char priority,unsigned short comref,unsigned data_len,
+	      void *data)
+{
+  /* encapsulate the vlink_put call in here, to avoid a memcpy */
+  fputc ('$', fptr);
+  fputc (1, fptr);
+  fputc ((pkt_len_s16+16)>>8, fptr);  fputc ((pkt_len_s16+16), fptr);
+
+  fputc (comspec>>8, fptr);  fputc (comspec, fptr);
+  fputc ((pkt_len_s16+16)>>8, fptr);  fputc ((pkt_len_s16+16), fptr);
+  fputc (priority, fptr);
+  fputc (0, fptr);
+  fputc (comref>>8, fptr);
+  fputc (comref, fptr);
+  fputc (data_len>>24, fptr);   fputc (data_len>>16, fptr);
+  fputc (data_len>>8, fptr);    fputc (data_len, fptr);
+  fputc (1, fptr);
+  fputc (4, fptr);
+  fputc (0, fptr);
+  fputc (0, fptr);
+
+  fwrite (data, pkt_len_s16, 1, fptr);
+}
+
+
+void ppa_init_job(ppa_stat* prn)
+{
+  unsigned char init1[8] = { 0x00, 0x00, 0x01, 0xf4, 0x01, 0x00, 0x00, 0x00 };
+  unsigned char init2[4] = { 0xde, 0xad, 0xbe, 0xef };
+  unsigned char init3[8] = { 0xde, 0xad, 0xbe, 0xef, 0x02, 0x00, 0x00, 0x00 };
+  unsigned char init4[60] = "!!TAZ            \x81*HP DeskJet 1000C Prin (Copy 2)*FILE!!\x00\x00\x00"; /* plus 0 terminator */
+  unsigned char init5[4] = { 0x01, 0x01, 0x00, 0x00 };
+
+  switch(prn->version)
+  {
+  case HP820:
+    scp_put (prn->fptr, 35, 1, 7, sizeof(init1), init1);
+    vlink_put (prn->fptr, 0, sizeof(init2), init2);
+    scp_put (prn->fptr, 101, 2, 7, sizeof(init3), init3);
+    break;
+  case HP720:
+    scp2_put (prn->fptr, 0x0186, sizeof(init1), 7, 1, 0, init1);
+    vlink_put(prn->fptr, 0, sizeof(init2), init2);
+    scp2_put (prn->fptr, 0x018f, sizeof(init3), 7, 2, 4, init3);
+    break;
+  case HP1000:
+    scp3_put (prn->fptr, 0x0186, sizeof(init1), 7, 16, 0, init1);
+    scp3_put (prn->fptr, 0x018C, sizeof(init4), 7, 1, 0, init4);
+    scp3_put (prn->fptr, 0x01A1, sizeof(init5), 7, 1, 0, init5);
+    vlink_put (prn->fptr, 0, sizeof(init2), init2);
+    scp3_put (prn->fptr, 0x018f, sizeof(init3), 7, 2, 4, init3);
+    break;
+  default:
+    fprintf(stderr,"ppa_init_job(): unknown printer verson\n");
+  }
+}
+
+void ppa_end_print(ppa_stat* prn)
+{
+  unsigned char pageA[4] = { 0x05, 0x01, 0x03, 0x84 };
+
+  if (prn->version == HP1000)
+    scp3_put (prn->fptr, 0x0181, sizeof(pageA), 7, 2, 0, pageA);
+}
+
+void ppa_init_page(ppa_stat* prn)
+{
+  unsigned char pageA[16] = {0x28, 0x2d, 0x00, 0x41, 0x29, 0x2e, 0x00, 0x42,
+			     0x29, 0x2e, 0x00, 0x42, 0x29, 0x2e, 0x00, 0x42 };
+  unsigned char pageB[16] = {0x28, 0x2d, 0x00, 0x41, 0x2d, 0x32, 0x00, 0x46,
+			     0x2d, 0x32, 0x00, 0x46, 0x2d, 0x32, 0x00, 0x46 };
+
+  switch(prn->version)
+  {
+  case HP820:
+    scp_put (prn->fptr, 21, 1, 5, sizeof(pageA), pageA);
+    break;
+  case HP720:
+    scp2_put (prn->fptr, 0x0183, sizeof(pageB), 5, 1, 0, pageB);
+    break;
+  case HP1000:
+    scp3_put (prn->fptr, 0x0183, sizeof(pageA), 5, 1, 0, pageA);
+    break;
+  default:
+    fprintf(stderr,"ppa_init_page(): unknown printer verson\n");
+  }
+}
+
+void ppa_load_page(ppa_stat* prn)
+{
+  unsigned char loadA[4] = {0x01, 0x01, 0x09, 0x60 };
+  unsigned char loadB[4] = {0x01, 0x01, 0x12, 0xc0 };
+  unsigned char loadC[4] = {0x01, 0x01, 0x07, 0x08 };
+
+  switch(prn->version)
+  {
+  case HP820:
+    scp_put (prn->fptr, 19, 1, 7, sizeof(loadA), loadA);
+    break;
+  case HP720:
+    scp2_put (prn->fptr, 0x0181, sizeof(loadB), 7, 1, 0, loadB);
+    break;
+  case HP1000:
+    scp3_put (prn->fptr, 0x0181, sizeof(loadC), 7, 1, 0, loadC);
+    break;
+  default:
+    fprintf(stderr,"ppa_load_page(): unknown printer verson\n");
+  }
+}
+
+void ppa_eject_page(ppa_stat* prn)
+{
+  unsigned char loadA[4] = {0x02, 0x01, 0x09, 0x60 };
+  unsigned char loadB[4] = {0x02, 0x01, 0x12, 0xc0 };
+  unsigned char loadC[4] = {0x02, 0x01, 0x07, 0x08 };
+
+  switch(prn->version)
+  {
+  case HP820:
+    scp_put (prn->fptr, 19, 1, 7, sizeof(loadA), loadA);
+    break;
+  case HP720:
+    scp2_put (prn->fptr, 0x0181, sizeof(loadB), 7, 1, 0, loadB);
+    break;
+  case HP1000:
+    scp3_put (prn->fptr, 0x0181, sizeof(loadC), 7, 1, 0, loadC);
+    break;
+  default:
+    fprintf(stderr,"ppa_eject_page(): unknown printer verson\n");
+  }
+}
+
+
+
+static int 
+compress(unsigned char *in, int num_lines_d2, int final_len, 
+             unsigned char *iout)
+{
+  unsigned char* out = iout;
+  int I,len=num_lines_d2;
+  for(I=0; I<final_len; I+=num_lines_d2, in+=num_lines_d2)
+  {
+    int i = 0;
+    while (i < len) {
+      /* Find the size of duplicate values */
+      int dup_len = 0;
+      while ((i + dup_len < len)
+	     && (in[i + dup_len] == in[i])) {
+	dup_len++;
+      }
+      /* See if we have enough zeros to be worth compressing. */
+      /* I figure one is enough. */
+      if ((dup_len >= 1) && (in[i] == 0)) {
+	/* Output run of zeros. */
+	while (dup_len >= 128) {
+	  /* Max is 128 */
+	  *out++ = 0x00;
+	  i += 128;
+	  dup_len -= 128;
+	}
+	if (dup_len >= 1)
+	{
+	  *out++ = dup_len;
+	  i += dup_len;
+	}
+	/* See if we have enough non-zeros to be worth compressing. */
+	/* Here two should be enough. */
+      }
+      else if (dup_len >= 2)
+      {
+	/* Output run of duplicates. */
+	while (dup_len >= 64) {
+	  /* Max is 64 */
+	  *out++ = 0x80;
+	  *out++ = in[i];
+	  i += 64;
+	  dup_len -= 64;
+	}
+	if (dup_len >= 2)
+	{
+	  *out++ = dup_len + 0x80;
+	  *out++ = in[i];
+	  i += dup_len;
+	}
+      }
+      else
+      {
+	/* Look for two zeros, or three duplicates to end literal run. */
+	/* Note this is one more than the number to start a run. */
+	int lit_len = -1;
+	int add_more = 1;
+	while (add_more) {
+	  lit_len++;
+	  if (i + lit_len == len) add_more = 0;
+	  /* Always add more if we are near the very end. */
+	  if (i + lit_len < len - 3) {
+	    char a = in[i + lit_len + 0];
+	    char b = in[i + lit_len + 1];
+	    char c = in[i + lit_len + 2];
+	    /* See if there are enough zeros */
+	    if ((a == b) && (b == 0)) add_more = 0;
+	    /* See if there are enough duplicates */
+	    if ((a == b) && (b == c)) add_more = 0;
+	  }
+	}
+	/* Output run of literals. */
+	while (lit_len >= 64) {
+	  /* Max is 64 */
+	  int j;
+	  *out++ = 0xc0;
+	  for (j = i; j < i + 64; j++) {
+	    *out++ = in[j];
+	  }
+	  i += 64;
+	  lit_len -= 64;
+	} 
+	if (lit_len) {
+	  int j;
+	  *out++ = lit_len + 0xc0;
+	  for (j = i; j < i + lit_len; j++) {
+	    *out++ = in[j];
+	  }
+	  i += lit_len;
+	}
+      }
+    }
+  }
+  return out-iout;
+}
+
+static void __inline__ place_2bytes(int x,unsigned char* y)
+{ y[0]=x>>8; y[1]=x; }
+static void __inline__ place_4bytes(int x,unsigned char* y)
+{ place_2bytes(x>>16,y); place_2bytes(x,y+2); }
+
+#define do_compress_data (1)
+void ppa_print_sweep(ppa_stat* prn,ppa_sweep_data* data)
+{
+  unsigned char* pc, *tpc;
+  unsigned i,datasize=data->data_size;
+  unsigned char sweep_packet[144];
+  int sweep_packet_size;
+  unsigned short HP720constants[] = { 0x8ca0, 0x4650, 0x12c0 };
+  unsigned short HP820constants[] = { 0x4650, 0x1c20, 0x0960 };
+  unsigned short HP1000constants[] = { 0x4650, 0x2328, 0x0708 };
+  unsigned short* constants;
+  int nozzle_data_size;
+  int MF; /* Multiplicative Factor -- quick hack */
+
+  pc=data->image_data;
+
+  if(do_compress_data)
+  {
+    if(!(pc=malloc((datasize/64+1)*65)))
+    {
+      fprintf(stderr,"ppa_print_sweep(): could not malloc storage for compressed data\n");
+      exit(-1);
+    }
+    datasize=compress(data->image_data,data->nozzle_data->pins_used_d2,datasize,pc);
+  }
+
+  /* send image data 16k at a time */
+  for(i=0, tpc=pc; i<datasize; tpc+=16384, i+=16384)
+    vlink_put(prn->fptr, 0, datasize-i > 16384 ? 16384 : datasize-i, tpc);
+
+  /* memory leak fix courtesy of John McKown */
+  if (do_compress_data)
+    free (pc);
+
+  /* construct sweep packet */
+  switch(prn->version)
+  {
+  case HP820:
+    constants=HP820constants;
+    MF=1;
+    break;
+  case HP720:
+    constants=HP720constants;
+    MF=2;
+    break;
+  case HP1000:
+    constants=HP1000constants;
+    MF=1;
+    break;
+  default:
+    fprintf(stderr,"ppa_print_sweep(): unknown printer verson\n");
+    return;
+  }
+
+  sweep_packet[0]=0;
+  sweep_packet[1]= do_compress_data;
+  sweep_packet[2]= data->direction==right_to_left ? 1 : 2;
+  sweep_packet[3]= data->in_color ? 14 : 1;
+
+  place_4bytes(datasize,sweep_packet+4);
+
+  memset(sweep_packet+8,0,8);
+
+  place_4bytes(MF*(data->vertical_pos+prn->y_offset),sweep_packet+16);
+  place_2bytes(constants[0],sweep_packet+20);
+  place_2bytes(MF*(data->left_margin+prn->x_offset),sweep_packet+22);
+  place_2bytes(MF*(data->right_margin+prn->x_offset),sweep_packet+24);
+  place_2bytes(constants[1],sweep_packet+26);
+  place_2bytes(constants[2],sweep_packet+28);
+  place_2bytes(0x100,sweep_packet+30);
+
+  if(data->next)
+  {
+    sweep_packet[32]= data->next->direction==right_to_left ? 1 : 2;
+    sweep_packet[33]= data->next->in_color ? 14 : 1;
+    place_4bytes(MF*(data->next->vertical_pos+prn->y_offset),sweep_packet+34);
+    place_2bytes(MF*(data->next->left_margin+prn->x_offset),sweep_packet+38);
+    place_2bytes(MF*(data->next->right_margin+prn->x_offset),sweep_packet+40);
+    place_2bytes(constants[1],sweep_packet+42);
+    place_2bytes(constants[2],sweep_packet+44);
+  }
+  else
+    memset(sweep_packet+32,0,14);
+  sweep_packet[46]=8;
+
+  nozzle_data_size=data->nozzle_data_size;
+  if(nozzle_data_size>6)
+  {
+    fprintf(stderr,"ppa_print_sweep(): truncating nozzle data from %d rows to 6 rows\n",nozzle_data_size);
+    nozzle_data_size=6;
+  }
+  sweep_packet[47]=nozzle_data_size;
+
+  for(i=0; i<nozzle_data_size; ++i)
+  {
+    unsigned char * const pc = sweep_packet + 48 + i*16;
+
+    place_2bytes(data->nozzle_data[i].DPI,pc);
+    place_2bytes(data->nozzle_data[i].pins_used_d2,pc+2);
+    place_2bytes(data->nozzle_data[i].unused_pins_p1,pc+4);
+    place_2bytes(data->nozzle_data[i].first_pin,pc+6);
+    place_2bytes(data->nozzle_data[i].pins_used_d2,pc+8);
+    place_2bytes(MF*(data->nozzle_data[i].left_margin+prn->x_offset),pc+10);
+    place_2bytes(MF*(data->nozzle_data[i].right_margin+prn->x_offset),pc+12);
+    pc[14]=data->nozzle_data[i].nozzle_delay;
+    pc[15]=0;
+  }
+
+  sweep_packet_size = data->in_color ? 144 : 80;
+
+  /* send sweep packet */
+  switch(prn->version)
+  {
+  case HP820:
+    scp_put (prn->fptr, 18, 1, 7, sweep_packet_size, sweep_packet);
+    break;
+  case HP720:
+    scp2_put (prn->fptr, 0x0180, sweep_packet_size, 7, 1, datasize, sweep_packet);
+    break;
+  case HP1000:
+    scp3_put (prn->fptr, 0x0180, sweep_packet_size, 7, 1, datasize, sweep_packet);
+    break;
+  default:
+    fprintf(stderr,"ppa_print_sweep(): unknown printer version\n");
+    return;
+  }
+}
+
+
+void ppa_print_sweeps(ppa_stat* prn,ppa_sweep_data* data)
+{
+  ppa_sweep_data* current_sweep;
+  for(current_sweep=data; current_sweep; current_sweep=current_sweep->next)
+    ppa_print_sweep(prn,current_sweep);
+}
diff --git a/converter/pbm/pbmtoppa/ppa.h b/converter/pbm/pbmtoppa/ppa.h
new file mode 100644
index 00000000..1c7e6f18
--- /dev/null
+++ b/converter/pbm/pbmtoppa/ppa.h
@@ -0,0 +1,74 @@
+/* ppa.h
+ * Copyright (c) 1998 Tim Norman.  See LICENSE for details.
+ * 2-25-98
+ *
+ * Mar 3, 1998  Jim Peterson  <jspeter@birch.ee.vt.edu>
+ *
+ *     Restructured to accommodate both the HP820/1000, and HP720 series.
+ */
+#ifndef _PPA_H
+#define _PPA_H
+#include <stdio.h>
+
+typedef struct
+{
+  FILE* fptr;
+  enum { HP820, HP1000, HP720, HP710 } version;
+  /* 300 , 600 dpi */
+  int DPI;
+  /* nozzle delay */
+  int right_to_left_delay[2];
+  int left_to_right_delay[2];
+  /* direction of printing */
+  enum { RIGHT_ONLY, LEFT_ONLY, BOTH_DIR } direction;
+  int x_offset;
+  int y_offset;
+  int marg_diff;
+  int top_margin;
+  int left_margin;
+  int right_margin;
+  int bottom_margin;
+  int bufsize;
+} ppa_stat;
+
+typedef struct
+{ 
+  int DPI;
+  int right;
+  int left;
+} printer_delay;
+
+typedef struct
+{
+  unsigned short DPI;
+  unsigned short pins_used_d2;
+  unsigned short unused_pins_p1;
+  unsigned short first_pin;
+  unsigned short left_margin;
+  unsigned short right_margin;
+  unsigned char nozzle_delay;
+} ppa_nozzle_data;
+
+typedef struct ppa_sweep_data_s
+{
+  unsigned char* image_data;
+  unsigned data_size;
+  enum { False=0, True=1 } in_color;
+  enum { right_to_left, left_to_right } direction;
+  int vertical_pos;
+  unsigned short left_margin;
+  unsigned short right_margin;
+  unsigned char nozzle_data_size;
+  ppa_nozzle_data* nozzle_data;
+  struct ppa_sweep_data_s* next; /* NULL indicates last print sweep */
+} ppa_sweep_data;
+
+void ppa_init_job(ppa_stat*);
+void ppa_init_page(ppa_stat*);
+void ppa_load_page(ppa_stat*);
+void ppa_eject_page(ppa_stat*);
+void ppa_end_print(ppa_stat*);
+void ppa_print_sweep(ppa_stat*,ppa_sweep_data*);  /* prints one sweep */
+void ppa_print_sweeps(ppa_stat*,ppa_sweep_data*); /* prints a linked-list of sweeps */
+
+#endif
diff --git a/converter/pbm/pbmtoppa/ppapbm.h b/converter/pbm/pbmtoppa/ppapbm.h
new file mode 100644
index 00000000..1ffc093b
--- /dev/null
+++ b/converter/pbm/pbmtoppa/ppapbm.h
@@ -0,0 +1,29 @@
+/* pbm.h
+ * Copyright (c) 1998 Tim Norman.  See LICENSE for details
+ * 2-25-98
+ *
+ * Mar 18, 1998  Jim Peterson  <jspeter@birch.ee.vt.edu>
+ *
+ *     Restructured to encapsulate more of the PBM handling.
+ */
+#ifndef _PBM_H
+#define _PBM_H
+
+#include <stdio.h>
+
+typedef struct
+{
+  FILE* fptr;
+  enum { none, P1, P4 } version;
+  int width, height;
+  int current_line;
+  void *revdata;
+  int unread;
+} pbm_stat;
+
+int make_pbm_stat(pbm_stat*,FILE*);
+int pbm_readline(pbm_stat*,unsigned char*); 
+  /* reads a single line into char* */
+void pbm_unreadline(pbm_stat*,void*); /* pushes a single line back */
+
+#endif
diff --git a/converter/pbm/pbmtopsg3.c b/converter/pbm/pbmtopsg3.c
new file mode 100644
index 00000000..68b265f0
--- /dev/null
+++ b/converter/pbm/pbmtopsg3.c
@@ -0,0 +1,374 @@
+/* pbmtopsg3
+
+   Reads a series of PBM images and writes a Postscript program
+   containing these images as individual pages with Fax-G3 
+   (CCITT-Fiter) compression. (Useful for combining scanned pages into
+   a comfortably printable document.)
+
+   Copyright (C) 2001 Kristof Koehler 
+       <kristof@fachschaft.physik.uni-karlsruhe.de>
+
+   Netpbm adaptation by Bryan Henderson June 2001.
+ 
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+ 
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "pbm.h"
+#include "shhopt.h"
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilespec;  /* Filespec of input file */
+    float        dpi;            /* requested resolution, dpi */
+    char *       title;          /* -title option.  NULL for none */
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdline_info *cmdlineP) {
+
+    optStruct3 opt;
+    unsigned int option_def_index = 0;
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+
+    unsigned int dpiSpec, titleSpec;
+    float dpiOpt;
+    char * titleOpt;
+
+    OPTENT3(0, "dpi",      OPT_FLOAT,  &dpiOpt,   &dpiSpec,   0);
+    OPTENT3(0, "title",    OPT_STRING, &titleOpt, &titleSpec, 0);
+    
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;
+    opt.allowNegNum = FALSE;
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+    if (argc-1 == 0)
+        cmdlineP->inputFilespec = "-";
+    else {
+        cmdlineP->inputFilespec = argv[1];
+        if (argc-1 > 1)
+            pm_error("Too many arguments.  The only argument is the input "
+                     "file specification");
+    }
+
+    cmdlineP->dpi = dpiSpec ? dpiOpt : 72.0;
+    cmdlineP->title = titleSpec ? titleOpt : NULL;
+}
+
+    
+
+static void 
+write85 ( unsigned int bits, int *col )
+{
+	char buf[5] ;
+	if ( bits == 0 ) {
+		fputc ( 'z', stdout ) ;
+		*col += 1 ;
+	} else {
+		buf[4] = bits % 85 + '!' ;
+		bits /= 85 ;
+		buf[3] = bits % 85 + '!' ;
+		bits /= 85 ;
+		buf[2] = bits % 85 + '!' ;
+		bits /= 85 ;
+		buf[1] = bits % 85 + '!' ;
+		bits /= 85 ;
+		buf[0] = bits % 85 + '!' ;
+		fwrite ( buf, 1, 5, stdout ) ;
+		*col += 5 ;
+	}
+	if ( *col > 70 ) {
+		printf ( "\n" ) ;
+		*col = 0 ;
+	}
+}
+
+
+static void 
+writebits ( unsigned int *outbits, int *outbitsidx, int *col,
+            unsigned int bits, int n )
+{
+	int k, m ;
+	unsigned int usedbits ;
+	while ( n > 0 ) {
+		if ( *outbitsidx == 0 )
+			*outbits = 0 ;
+		k = 32 - *outbitsidx ;
+		m = n > k ? k : n ;
+		usedbits = (bits >> (n-m)) & ((1<<m)-1) ;
+		*outbits |= usedbits << (k-m) ;
+		*outbitsidx += m ;
+		n -= m ;
+		if ( *outbitsidx == 32 ) {
+			write85 ( *outbits, col ) ;
+			*outbitsidx = 0 ;
+		}
+	}
+}
+
+
+static void 
+flushbits ( unsigned int *outbits, int *outbitsidx, int *col )
+{
+	if ( *outbitsidx > 0 ) {
+		write85 ( *outbits, col ) ;
+		*outbitsidx = 0 ;
+	}
+}
+
+struct { unsigned int b, l ; } makeup[40][2] = {
+    { { 0x001b, 5 } /*         11011 */ , { 0x000f,10 } /*    0000001111 */  },
+    { { 0x0012, 5 } /*         10010 */ , { 0x00c8,12 } /*  000011001000 */  },
+    { { 0x0017, 6 } /*        010111 */ , { 0x00c9,12 } /*  000011001001 */  },
+    { { 0x0037, 7 } /*       0110111 */ , { 0x005b,12 } /*  000001011011 */  },
+    { { 0x0036, 8 } /*      00110110 */ , { 0x0033,12 } /*  000000110011 */  },
+    { { 0x0037, 8 } /*      00110111 */ , { 0x0034,12 } /*  000000110100 */  },
+    { { 0x0064, 8 } /*      01100100 */ , { 0x0035,12 } /*  000000110101 */  },
+    { { 0x0065, 8 } /*      01100101 */ , { 0x006c,13 } /* 0000001101100 */  },
+    { { 0x0068, 8 } /*      01101000 */ , { 0x006d,13 } /* 0000001101101 */  },
+    { { 0x0067, 8 } /*      01100111 */ , { 0x004a,13 } /* 0000001001010 */  },
+    { { 0x00cc, 9 } /*     011001100 */ , { 0x004b,13 } /* 0000001001011 */  },
+    { { 0x00cd, 9 } /*     011001101 */ , { 0x004c,13 } /* 0000001001100 */  },
+    { { 0x00d2, 9 } /*     011010010 */ , { 0x004d,13 } /* 0000001001101 */  },
+    { { 0x00d3, 9 } /*     011010011 */ , { 0x0072,13 } /* 0000001110010 */  },
+    { { 0x00d4, 9 } /*     011010100 */ , { 0x0073,13 } /* 0000001110011 */  },
+    { { 0x00d5, 9 } /*     011010101 */ , { 0x0074,13 } /* 0000001110100 */  },
+    { { 0x00d6, 9 } /*     011010110 */ , { 0x0075,13 } /* 0000001110101 */  },
+    { { 0x00d7, 9 } /*     011010111 */ , { 0x0076,13 } /* 0000001110110 */  },
+    { { 0x00d8, 9 } /*     011011000 */ , { 0x0077,13 } /* 0000001110111 */  },
+    { { 0x00d9, 9 } /*     011011001 */ , { 0x0052,13 } /* 0000001010010 */  },
+    { { 0x00da, 9 } /*     011011010 */ , { 0x0053,13 } /* 0000001010011 */  },
+    { { 0x00db, 9 } /*     011011011 */ , { 0x0054,13 } /* 0000001010100 */  },
+    { { 0x0098, 9 } /*     010011000 */ , { 0x0055,13 } /* 0000001010101 */  },
+    { { 0x0099, 9 } /*     010011001 */ , { 0x005a,13 } /* 0000001011010 */  },
+    { { 0x009a, 9 } /*     010011010 */ , { 0x005b,13 } /* 0000001011011 */  },
+    { { 0x0018, 6 } /*        011000 */ , { 0x0064,13 } /* 0000001100100 */  },
+    { { 0x009b, 9 } /*     010011011 */ , { 0x0065,13 } /* 0000001100101 */  },
+    { { 0x0008,11 } /*   00000001000 */ , { 0x0008,11 } /*   00000001000 */  },
+    { { 0x000c,11 } /*   00000001100 */ , { 0x000c,11 } /*   00000001100 */  },
+    { { 0x000d,11 } /*   00000001101 */ , { 0x000d,11 } /*   00000001101 */  },
+    { { 0x0012,12 } /*  000000010010 */ , { 0x0012,12 } /*  000000010010 */  },
+    { { 0x0013,12 } /*  000000010011 */ , { 0x0013,12 } /*  000000010011 */  },
+    { { 0x0014,12 } /*  000000010100 */ , { 0x0014,12 } /*  000000010100 */  },
+    { { 0x0015,12 } /*  000000010101 */ , { 0x0015,12 } /*  000000010101 */  },
+    { { 0x0016,12 } /*  000000010110 */ , { 0x0016,12 } /*  000000010110 */  },
+    { { 0x0017,12 } /*  000000010111 */ , { 0x0017,12 } /*  000000010111 */  },
+    { { 0x001c,12 } /*  000000011100 */ , { 0x001c,12 } /*  000000011100 */  },
+    { { 0x001d,12 } /*  000000011101 */ , { 0x001d,12 } /*  000000011101 */  },
+    { { 0x001e,12 } /*  000000011110 */ , { 0x001e,12 } /*  000000011110 */  },
+    { { 0x001f,12 } /*  000000011111 */ , { 0x001f,12 } /*  000000011111 */  }
+} ;
+
+struct { unsigned int b, l ; } term[64][2] = {
+    { { 0x0035, 8 } /*      00110101 */ , { 0x0037,10 } /*    0000110111 */  },
+    { { 0x0007, 6 } /*        000111 */ , { 0x0002, 3 } /*           010 */  },
+    { { 0x0007, 4 } /*          0111 */ , { 0x0003, 2 } /*            11 */  },
+    { { 0x0008, 4 } /*          1000 */ , { 0x0002, 2 } /*            10 */  },
+    { { 0x000b, 4 } /*          1011 */ , { 0x0003, 3 } /*           011 */  },
+    { { 0x000c, 4 } /*          1100 */ , { 0x0003, 4 } /*          0011 */  },
+    { { 0x000e, 4 } /*          1110 */ , { 0x0002, 4 } /*          0010 */  },
+    { { 0x000f, 4 } /*          1111 */ , { 0x0003, 5 } /*         00011 */  },
+    { { 0x0013, 5 } /*         10011 */ , { 0x0005, 6 } /*        000101 */  },
+    { { 0x0014, 5 } /*         10100 */ , { 0x0004, 6 } /*        000100 */  },
+    { { 0x0007, 5 } /*         00111 */ , { 0x0004, 7 } /*       0000100 */  },
+    { { 0x0008, 5 } /*         01000 */ , { 0x0005, 7 } /*       0000101 */  },
+    { { 0x0008, 6 } /*        001000 */ , { 0x0007, 7 } /*       0000111 */  },
+    { { 0x0003, 6 } /*        000011 */ , { 0x0004, 8 } /*      00000100 */  },
+    { { 0x0034, 6 } /*        110100 */ , { 0x0007, 8 } /*      00000111 */  },
+    { { 0x0035, 6 } /*        110101 */ , { 0x0018, 9 } /*     000011000 */  },
+    { { 0x002a, 6 } /*        101010 */ , { 0x0017,10 } /*    0000010111 */  },
+    { { 0x002b, 6 } /*        101011 */ , { 0x0018,10 } /*    0000011000 */  },
+    { { 0x0027, 7 } /*       0100111 */ , { 0x0008,10 } /*    0000001000 */  },
+    { { 0x000c, 7 } /*       0001100 */ , { 0x0067,11 } /*   00001100111 */  },
+    { { 0x0008, 7 } /*       0001000 */ , { 0x0068,11 } /*   00001101000 */  },
+    { { 0x0017, 7 } /*       0010111 */ , { 0x006c,11 } /*   00001101100 */  },
+    { { 0x0003, 7 } /*       0000011 */ , { 0x0037,11 } /*   00000110111 */  },
+    { { 0x0004, 7 } /*       0000100 */ , { 0x0028,11 } /*   00000101000 */  },
+    { { 0x0028, 7 } /*       0101000 */ , { 0x0017,11 } /*   00000010111 */  },
+    { { 0x002b, 7 } /*       0101011 */ , { 0x0018,11 } /*   00000011000 */  },
+    { { 0x0013, 7 } /*       0010011 */ , { 0x00ca,12 } /*  000011001010 */  },
+    { { 0x0024, 7 } /*       0100100 */ , { 0x00cb,12 } /*  000011001011 */  },
+    { { 0x0018, 7 } /*       0011000 */ , { 0x00cc,12 } /*  000011001100 */  },
+    { { 0x0002, 8 } /*      00000010 */ , { 0x00cd,12 } /*  000011001101 */  },
+    { { 0x0003, 8 } /*      00000011 */ , { 0x0068,12 } /*  000001101000 */  },
+    { { 0x001a, 8 } /*      00011010 */ , { 0x0069,12 } /*  000001101001 */  },
+    { { 0x001b, 8 } /*      00011011 */ , { 0x006a,12 } /*  000001101010 */  },
+    { { 0x0012, 8 } /*      00010010 */ , { 0x006b,12 } /*  000001101011 */  },
+    { { 0x0013, 8 } /*      00010011 */ , { 0x00d2,12 } /*  000011010010 */  },
+    { { 0x0014, 8 } /*      00010100 */ , { 0x00d3,12 } /*  000011010011 */  },
+    { { 0x0015, 8 } /*      00010101 */ , { 0x00d4,12 } /*  000011010100 */  },
+    { { 0x0016, 8 } /*      00010110 */ , { 0x00d5,12 } /*  000011010101 */  },
+    { { 0x0017, 8 } /*      00010111 */ , { 0x00d6,12 } /*  000011010110 */  },
+    { { 0x0028, 8 } /*      00101000 */ , { 0x00d7,12 } /*  000011010111 */  },
+    { { 0x0029, 8 } /*      00101001 */ , { 0x006c,12 } /*  000001101100 */  },
+    { { 0x002a, 8 } /*      00101010 */ , { 0x006d,12 } /*  000001101101 */  },
+    { { 0x002b, 8 } /*      00101011 */ , { 0x00da,12 } /*  000011011010 */  },
+    { { 0x002c, 8 } /*      00101100 */ , { 0x00db,12 } /*  000011011011 */  },
+    { { 0x002d, 8 } /*      00101101 */ , { 0x0054,12 } /*  000001010100 */  },
+    { { 0x0004, 8 } /*      00000100 */ , { 0x0055,12 } /*  000001010101 */  },
+    { { 0x0005, 8 } /*      00000101 */ , { 0x0056,12 } /*  000001010110 */  },
+    { { 0x000a, 8 } /*      00001010 */ , { 0x0057,12 } /*  000001010111 */  },
+    { { 0x000b, 8 } /*      00001011 */ , { 0x0064,12 } /*  000001100100 */  },
+    { { 0x0052, 8 } /*      01010010 */ , { 0x0065,12 } /*  000001100101 */  },
+    { { 0x0053, 8 } /*      01010011 */ , { 0x0052,12 } /*  000001010010 */  },
+    { { 0x0054, 8 } /*      01010100 */ , { 0x0053,12 } /*  000001010011 */  },
+    { { 0x0055, 8 } /*      01010101 */ , { 0x0024,12 } /*  000000100100 */  },
+    { { 0x0024, 8 } /*      00100100 */ , { 0x0037,12 } /*  000000110111 */  },
+    { { 0x0025, 8 } /*      00100101 */ , { 0x0038,12 } /*  000000111000 */  },
+    { { 0x0058, 8 } /*      01011000 */ , { 0x0027,12 } /*  000000100111 */  },
+    { { 0x0059, 8 } /*      01011001 */ , { 0x0028,12 } /*  000000101000 */  },
+    { { 0x005a, 8 } /*      01011010 */ , { 0x0058,12 } /*  000001011000 */  },
+    { { 0x005b, 8 } /*      01011011 */ , { 0x0059,12 } /*  000001011001 */  },
+    { { 0x004a, 8 } /*      01001010 */ , { 0x002b,12 } /*  000000101011 */  },
+    { { 0x004b, 8 } /*      01001011 */ , { 0x002c,12 } /*  000000101100 */  },
+    { { 0x0032, 8 } /*      00110010 */ , { 0x005a,12 } /*  000001011010 */  },
+    { { 0x0033, 8 } /*      00110011 */ , { 0x0066,12 } /*  000001100110 */  },
+    { { 0x0034, 8 } /*      00110100 */ , { 0x0067,12 } /*  000001100111 */  } 
+} ;
+
+
+static void
+writelength ( unsigned int *outbits, int *outbitsidx, int *col,
+              int bit, int length )
+{
+	while ( length >= 64 ) {
+		int m = length / 64 ;
+		if ( m > 40 )
+			m = 40 ;
+		writebits ( outbits, outbitsidx, col,
+			    makeup[m-1][bit].b, makeup[m-1][bit].l ) ;
+		length -= 64*m ;
+	}
+	writebits ( outbits, outbitsidx, col,
+		    term[length][bit].b, term[length][bit].l ) ;
+}
+
+
+
+static void
+doPage(FILE *       const ifP,
+       unsigned int const pageNum,
+       double       const dpi) {
+
+    int cols, rows, format;
+    bit * bitrow;
+    unsigned int row;
+    unsigned int outbits ;
+    int outbitsidx, col ;
+
+    pbm_readpbminit(ifP, &cols, &rows, &format);
+        
+    bitrow = pbm_allocrow(cols);
+        
+    pm_message("[%u]\n", pageNum);
+
+    printf ("%%%%Page: %u %u\n", pageNum, pageNum);
+    printf ("%u %u 1 [ %f 0 0 %f 0 %u ]\n"
+            "{ currentfile /ASCII85Decode filter\n"
+            "  << /Columns %u /Rows %u /EndOfBlock false >> "
+                "/CCITTFaxDecode filter\n"
+            "  image } exec\n",
+            cols, rows, dpi/72.0, -dpi/72.0, rows, 
+            cols, rows) ;
+        
+    outbitsidx = col = 0 ;
+    for (row = 0 ; row < rows; ++row) {
+        int lastbit, cnt ;
+        unsigned int j;
+
+        pbm_readpbmrow(ifP, bitrow, cols, format);
+            
+        lastbit = cnt = 0 ;
+        for (j = 0; j < cols; ++j) {
+            if (bitrow[j] != lastbit) {
+                writelength(&outbits, &outbitsidx, &col, lastbit, cnt) ;
+                lastbit = 1 ^ lastbit ;
+                cnt = 0 ;
+            }
+            ++cnt;
+        }
+        writelength(&outbits, &outbitsidx, &col, lastbit, cnt);
+    }
+        
+    flushbits(&outbits, &outbitsidx, &col) ;
+    printf("~>\nshowpage\n") ;
+
+    pbm_freerow(bitrow);
+}
+
+
+
+static void 
+doPages(FILE *         const ifP,
+        unsigned int * const pagesP,
+        double         const dpi) {
+
+    bool eof;
+    unsigned int pagesDone;
+
+    eof = FALSE;
+    pagesDone = 0;
+
+    while (!eof) {
+        doPage(ifP, pagesDone + 1, dpi);
+        ++pagesDone;
+        pbm_nextimage(ifP, &eof);
+    }
+    *pagesP = pagesDone;
+}
+
+
+
+int
+main(int    argc,
+     char * argv[]) {
+
+    FILE *ifP;
+    unsigned int pages;
+    
+    struct cmdline_info cmdline;
+
+    pbm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    printf ("%%!PS-Adobe-3.0\n");
+    if (cmdline.title)
+        printf("%%%%Title: %s\n", cmdline.title) ;
+    printf ("%%%%Creator: pbmtopsg3, Copyright (C) 2001 Kristof Koehler\n"
+            "%%%%Pages: (atend)\n"
+            "%%%%EndComments\n") ;
+    
+    doPages(ifP, &pages, cmdline.dpi);
+
+    printf ("%%%%Trailer\n"
+            "%%%%Pages: %u\n"
+            "%%%%EOF\n",
+            pages);
+
+    pm_close(ifP);
+    pm_close(stdout);
+    
+    return 0;
+}
diff --git a/converter/pbm/pbmtoptx.c b/converter/pbm/pbmtoptx.c
new file mode 100644
index 00000000..5031efcb
--- /dev/null
+++ b/converter/pbm/pbmtoptx.c
@@ -0,0 +1,101 @@
+/* pbmtoptx.c - read a portable bitmap and produce a Printronix printer file
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+
+static void putinit ARGS(( void ));
+static void putbit ARGS(( bit b ));
+static void putrest ARGS(( void ));
+static void putitem ARGS(( void ));
+
+int
+main( argc, argv )
+int argc;
+char *argv[];
+    {
+    FILE *ifp;
+    register bit *bitrow, *bP;
+    int rows, cols, format, row, col;
+    const char * const usage = "[pbmfile]";
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( usage );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    pbm_readpbminit( ifp, &cols, &rows, &format );
+    bitrow = pbm_allocrow( cols );
+
+    putinit( );
+    for ( row = 0; row < rows; row++ )
+	{
+	pbm_readpbmrow( ifp, bitrow, cols, format );
+        for ( col = 0, bP = bitrow; col < cols; col++, bP++ )
+	    putbit( *bP );
+	putrest( );
+	putchar( 5 );
+	putchar( '\n' );
+        }
+
+    pm_close( ifp );
+    
+    exit( 0 );
+    }
+
+static char item;
+static int bitsperitem, bitshift;
+
+static void
+putinit( )
+    {
+    bitsperitem = 0;
+    item = 64;
+    bitshift = 0;
+    }
+
+#if __STDC__
+static void
+putbit( bit b )
+#else /*__STDC__*/
+static void
+putbit( b )
+    bit b;
+#endif /*__STDC__*/
+    {
+    if ( bitsperitem == 6 )
+	putitem( );
+    if ( b == PBM_BLACK )
+	item += 1 << bitshift;
+    bitsperitem++;
+    bitshift++;
+    }
+
+static void
+putrest( )
+    {
+    if ( bitsperitem > 0 )
+	putitem( );
+    }
+
+static void
+putitem( )
+    {
+    putchar( item );
+    bitsperitem = 0;
+    item = 64;
+    bitshift = 0;
+    }
diff --git a/converter/pbm/pbmtowbmp.c b/converter/pbm/pbmtowbmp.c
new file mode 100644
index 00000000..2907fd68
--- /dev/null
+++ b/converter/pbm/pbmtowbmp.c
@@ -0,0 +1,70 @@
+/* pbmtowbmp.c - convert a portable bitmap to a Wireless Bitmap file
+
+   This is derived for Netpbm from the pbmwbmp package from 
+   <http://www.looplab.com/wap/tools> on 2000.06.06.
+   
+   The specifications for the wbmp format are part of the Wireless 
+   Application Environment specification at
+   <http://www.wapforum.org/what/technical.htm>.
+
+** Copyright (C) 1999 Terje Sannum <terje@looplab.com>.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+
+static void 
+outputint(int i) {
+  int c = 1;
+  while(i & 0x7f << 7*c) c++;
+  while(c > 1) putchar(0x80 | ((i >> 7*--c) & 0xff));
+  putchar(i & 0x7f);
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+  FILE *f;
+  bit **image;
+  int rows, cols, row, col;
+  int c, p;
+
+  pbm_init(&argc, argv);
+  if(argc > 2) {
+    fprintf(stderr, "Copyright (C) 1999 Terje Sannum <terje@looplab.com>\n");
+    pm_usage("[pbmfile]");
+  }
+
+  f = argc == 2 ? pm_openr(argv[1]) : stdin;
+  image = pbm_readpbm(f, &cols, &rows);
+  pm_close(f);
+
+  /* Header */
+  putchar(0); /* Type 0: B/W, no compression */
+  putchar(0); /* FixHeaderField */
+  /* Geometry */
+  outputint(cols);
+  outputint(rows);
+  /* Image data */
+  for(row = 0; row < rows; row++) {
+    p = c = 0;
+    for(col = 0; col < cols; col++) {
+      if(image[row][col] == PBM_WHITE) c = c | (1 << (7-p));
+      if(++p == 8) {
+	putchar(c);
+	p = c = 0;
+      }
+    }
+    if(p) putchar(c);
+  }
+  
+  return 0;
+}
+
diff --git a/converter/pbm/pbmtox10bm.c b/converter/pbm/pbmtox10bm.c
new file mode 100644
index 00000000..ef31fb9b
--- /dev/null
+++ b/converter/pbm/pbmtox10bm.c
@@ -0,0 +1,120 @@
+/* pbmtox10bm.c - read a portable bitmap and produce an X10 bitmap file
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "nstring.h"
+#include "pbm.h"
+
+int
+main(int argc, char * argv[]) {
+
+    FILE* ifp;
+    bit* bitrow;
+    bit * bP;
+    int rows, cols, format, padright, row;
+    int col;
+    char name[100];
+    char* cp;
+    int itemsperline;
+    int bitsperitem;
+    int item;
+    int firstitem;
+    const char* const hexchar = "0123456789abcdef";
+
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+        pm_usage( "[pbmfile]" );
+
+    if ( argc == 2 )
+	{
+        ifp = pm_openr( argv[1] );
+        strcpy( name, argv[1] );
+        if (STREQ( name, "-" ))
+            strcpy( name, "noname" );
+
+        if ( ( cp = strchr( name, '.' ) ) != 0 )
+            *cp = '\0';
+	}
+    else
+	{
+        ifp = stdin;
+        strcpy( name, "noname" );
+	}
+
+    pbm_readpbminit( ifp, &cols, &rows, &format );
+    bitrow = pbm_allocrow( cols );
+
+    /* Compute padding to round cols up to the nearest multiple of 16. */
+    padright = ( ( cols + 15 ) / 16 ) * 16 - cols;
+
+    printf( "#define %s_width %d\n", name, cols );
+    printf( "#define %s_height %d\n", name, rows );
+    printf( "static short %s_bits[] = {\n", name );
+
+    itemsperline = 0;
+    bitsperitem = 0;
+    item = 0;
+    firstitem = 1;
+
+#define PUTITEM \
+    { \
+        if ( firstitem ) \
+	        firstitem = 0; \
+        else \
+	        putchar( ',' ); \
+    if ( itemsperline == 11 ) \
+	{ \
+	    putchar( '\n' ); \
+	    itemsperline = 0; \
+	} \
+    if ( itemsperline == 0 ) \
+	    putchar( ' ' ); \
+    ++itemsperline; \
+    putchar('0'); \
+    putchar('x'); \
+    putchar(hexchar[item >> 12]); \
+    putchar(hexchar[(item >> 8) & 15]); \
+    putchar(hexchar[(item >> 4) & 15]); \
+    putchar(hexchar[item & 15]); \
+    bitsperitem = 0; \
+    item = 0; \
+    }
+
+#define PUTBIT(b) \
+    { \
+    if ( bitsperitem == 16 ) \
+	    PUTITEM; \
+    if ( (b) == PBM_BLACK ) \
+	    item += 1 << bitsperitem; \
+    ++bitsperitem; \
+    }
+
+    for ( row = 0; row < rows; ++row )
+	{
+        pbm_readpbmrow( ifp, bitrow, cols, format );
+        for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
+            PUTBIT(*bP);
+        for ( col = 0; col < padright; ++col )
+            PUTBIT(0);
+    }
+
+    pm_close( ifp );
+    
+    if ( bitsperitem > 0 )
+        PUTITEM;
+    printf( "};\n" );
+
+    return 0;
+}
diff --git a/converter/pbm/pbmtoxbm.c b/converter/pbm/pbmtoxbm.c
new file mode 100644
index 00000000..96830a0c
--- /dev/null
+++ b/converter/pbm/pbmtoxbm.c
@@ -0,0 +1,165 @@
+/* pbmtoxbm.c - read a portable bitmap and produce an X11 bitmap file
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <string.h>
+
+#include "pbm.h"
+#include "nstring.h"
+
+
+static void
+generateName(char const filenameArg[], const char ** const nameP) {
+/*----------------------------------------------------------------------------
+   Generate a name for the image to put in the bitmap file.  Derive it from
+   the filename argument filenameArg[] and return it as a null-terminated
+   string in newly malloc'ed space at *nameP.
+
+   We take the part of the name after the rightmost slash
+   (i.e. filename without the directory path part), stopping before
+   any period.  We convert any punctuation to underscores.
+
+   If the argument is "-", meaning standard input, we return the name
+   "noname".  Also, if the argument is null or ends in a slash, we
+   return "noname".
+-----------------------------------------------------------------------------*/
+    if (STREQ(filenameArg, "-"))
+        *nameP = strdup("noname");
+    else {
+        int nameIndex, argIndex;
+        /* indices into the input and output buffers */
+
+        /* Start just after the rightmost slash, or at beginning if no slash */
+        if (strrchr(filenameArg, '/') == 0) 
+            argIndex = 0;
+        else argIndex = strrchr(filenameArg, '/') - filenameArg + 1;
+
+        if (filenameArg[argIndex] == '\0') 
+            *nameP = strdup("noname");
+        else {
+            char * name;
+            nameIndex = 0;  /* Start at beginning of name buffer */
+
+            name = malloc(strlen(filenameArg));
+    
+            while (filenameArg[argIndex] != '\0' 
+                   && filenameArg[argIndex] != '.') {
+                const char filenameChar = filenameArg[argIndex++];
+                name[nameIndex++] = 
+                    ISALNUM(filenameChar) ? filenameChar : '_';
+            }
+            name[nameIndex] = '\0';
+            *nameP = name;
+        }
+    }
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    FILE* ifp;
+    bit* bitrow;
+    int rows, cols, format;
+    int padright;
+    int row;
+    const char * inputFilename;
+    const char *name;
+    int itemsperline;
+    int bitsperitem;
+    int item;
+    int firstitem;
+    const char hexchar[] = "0123456789abcdef";
+
+    pbm_init(&argc, argv);
+
+    if (argc-1 > 1)
+        pm_error("Too many arguments (%d).  The only valid argument is an "
+                 "input file name.", argc-1);
+    else if (argc-1 == 1) 
+        inputFilename = argv[1];
+    else
+        inputFilename = "-";
+
+    generateName(inputFilename, &name);
+    ifp = pm_openr(inputFilename);
+    
+    pbm_readpbminit(ifp, &cols, &rows, &format);
+    bitrow = pbm_allocrow(cols);
+    
+    /* Compute padding to round cols up to the nearest multiple of 8. */
+    padright = ((cols + 7)/8) * 8 - cols;
+
+    printf("#define %s_width %d\n", name, cols);
+    printf("#define %s_height %d\n", name, rows);
+    printf("static char %s_bits[] = {\n", name);
+
+    itemsperline = 0;
+    bitsperitem = 0;
+    item = 0;
+    firstitem = 1;
+
+#define PUTITEM \
+    { \
+    if ( firstitem ) \
+        firstitem = 0; \
+    else \
+        putchar( ',' ); \
+    if ( itemsperline == 15 ) \
+        { \
+        putchar( '\n' ); \
+        itemsperline = 0; \
+        } \
+    if ( itemsperline == 0 ) \
+        putchar( ' ' ); \
+    ++itemsperline; \
+    putchar('0'); \
+    putchar('x'); \
+    putchar(hexchar[item >> 4]); \
+    putchar(hexchar[item & 15]); \
+    bitsperitem = 0; \
+    item = 0; \
+    }
+
+#define PUTBIT(b) \
+    { \
+    if ( bitsperitem == 8 ) \
+        PUTITEM; \
+    if ( (b) == PBM_BLACK ) \
+        item += 1 << bitsperitem; \
+    ++bitsperitem; \
+    }
+
+    for (row = 0; row < rows; ++row) {
+        int col;
+        pbm_readpbmrow(ifp, bitrow, cols, format);
+        for (col = 0; col < cols; ++col)
+            PUTBIT(bitrow[col]);
+        for (col = 0; col < padright; ++col)
+            PUTBIT(0);
+    }
+
+    pm_close(ifp);
+
+    if (bitsperitem > 0)
+        PUTITEM;
+    printf("};\n");
+
+    pbm_freerow(bitrow);
+
+    strfree(name);
+
+    exit(0);
+}
diff --git a/converter/pbm/pbmtoybm.c b/converter/pbm/pbmtoybm.c
new file mode 100644
index 00000000..1d2be3d9
--- /dev/null
+++ b/converter/pbm/pbmtoybm.c
@@ -0,0 +1,114 @@
+/* pbmtoybm.c - read a pbm and write a file for Bennet Yee's 'xbm' and 'face'
+** programs.
+**
+** Written by Jamie Zawinski based on code (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <stdio.h>
+#include "pbm.h"
+
+#define YBM_MAGIC  ( ( '!' << 8 ) | '!' )
+
+static void putinit ARGS(( int cols, int rows ));
+static void putbit ARGS(( bit b ));
+static void putrest ARGS(( void ));
+static void putitem ARGS(( void ));
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    bit* bitrow;
+    register bit* bP;
+    int rows, cols, format, padright, row, col;
+
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[pbmfile]" );
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    pbm_readpbminit( ifp, &cols, &rows, &format );
+    bitrow = pbm_allocrow( cols );
+    
+    /* Compute padding to round cols up to the nearest multiple of 16. */
+    padright = ( ( cols + 15 ) / 16 ) * 16 - cols;
+
+    putinit( cols, rows );
+    for ( row = 0; row < rows; ++row )
+	{
+	pbm_readpbmrow( ifp, bitrow, cols, format );
+        for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
+	    putbit( *bP );
+	for ( col = 0; col < padright; ++col )
+	    putbit( 0 );
+        }
+
+    if ( ifp != stdin )
+	fclose( ifp );
+
+    putrest( );
+
+    exit( 0 );
+    }
+
+static long item;
+static int bitsperitem, bitshift;
+
+static void
+putinit( cols, rows )
+    int cols, rows;
+    {
+    pm_writebigshort( stdout, YBM_MAGIC );
+    pm_writebigshort( stdout, cols );
+    pm_writebigshort( stdout, rows );
+    item = 0;
+    bitsperitem = 0;
+    bitshift = 0;
+    }
+
+#if __STDC__
+static void
+putbit( bit b )
+#else /*__STDC__*/
+static void
+putbit( b )
+    bit b;
+#endif /*__STDC__*/
+    {
+    if ( bitsperitem == 16 )
+	putitem( );
+    ++bitsperitem;
+    if ( b == PBM_BLACK )
+	item += 1 << bitshift;
+    ++bitshift;
+    }
+
+static void
+putrest( )
+    {
+    if ( bitsperitem > 0 )
+	putitem( );
+    }
+
+static void
+putitem( )
+    {
+    pm_writebigshort( stdout, item );
+    item = 0;
+    bitsperitem = 0;
+    bitshift = 0;
+    }
diff --git a/converter/pbm/pbmtozinc.c b/converter/pbm/pbmtozinc.c
new file mode 100644
index 00000000..d39b71bc
--- /dev/null
+++ b/converter/pbm/pbmtozinc.c
@@ -0,0 +1,128 @@
+/* pbmtozinc.c - read a portable bitmap and produce an bitmap file
+**               in the format used by the Zinc Interface Library (v1.0)
+**               November 1990.
+**
+** Author: James Darrell McCauley
+**         Department of Agricultural Engineering
+**         Texas A&M University
+**         College Station, Texas 77843-2117 USA
+**
+** Copyright (C) 1988 by James Darrell McCauley (jdm5548@diamond.tamu.edu)
+**                    and Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <stdio.h>
+#include <string.h>
+
+#include "nstring.h"
+#include "pbm.h"
+
+int
+main(int argc, char * argv[]) {
+
+    FILE* ifp;
+    bit* bitrow;
+    register bit* bP;
+    int rows, cols, format, padright, row;
+    register int col;
+    char name[100];
+    char* cp;
+    int itemsperline;
+    register int bitsperitem;
+    register int item;
+    int firstitem;
+    const char * const hexchar = "084c2a6e195d3b7f";
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+        pm_usage( "[pbmfile]" );
+
+    if ( argc == 2 )
+	{
+        ifp = pm_openr( argv[1] );
+        strcpy( name, argv[1] );
+        if ( STREQ( name, "-" ) )
+            strcpy( name, "noname" );
+
+        if ( ( cp = strchr( name, '.' ) ) != 0 )
+            *cp = '\0';
+	}
+    else
+	{
+        ifp = stdin;
+        strcpy( name, "noname" );
+	}
+
+    pbm_readpbminit( ifp, &cols, &rows, &format );
+    bitrow = pbm_allocrow( cols );
+
+    /* Compute padding to round cols up to the nearest multiple of 16. */
+    padright = ( ( cols + 15 ) / 16 ) * 16 - cols;
+
+    printf( "USHORT %s[] = {\n",name);
+    printf( "  %d\n", cols );
+    printf( "  %d\n", rows );
+
+    itemsperline = 0;
+    bitsperitem = 0;
+    item = 0;
+    firstitem = 1;
+
+#define PUTITEM \
+    { \
+    if ( firstitem ) \
+	firstitem = 0; \
+    else \
+	putchar( ',' ); \
+    if ( itemsperline == 11 ) \
+	{ \
+	putchar( '\n' ); \
+	itemsperline = 0; \
+	} \
+    if ( itemsperline == 0 ) \
+	putchar( ' ' ); \
+    ++itemsperline; \
+    putchar('0'); \
+    putchar('x'); \
+    putchar(hexchar[item & 15]); \
+    putchar(hexchar[(item >> 4) & 15]); \
+    putchar(hexchar[(item >> 8) & 15]); \
+    putchar(hexchar[item >> 12]); \
+    bitsperitem = 0; \
+    item = 0; \
+    }
+
+#define PUTBIT(b) \
+    { \
+    if ( bitsperitem == 16 ) \
+	PUTITEM; \
+    if ( (b) == PBM_BLACK ) \
+	item += 1 << bitsperitem; \
+    ++bitsperitem; \
+    }
+
+    for ( row = 0; row < rows; ++row )
+	{
+        pbm_readpbmrow( ifp, bitrow, cols, format );
+        for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
+            PUTBIT( *bP );
+        for ( col = 0; col < padright; ++col )
+            PUTBIT( 0 );
+    }
+
+    pm_close( ifp );
+    
+    if ( bitsperitem > 0 )
+        PUTITEM;
+    printf( "};\n" );
+
+    return 0;
+}
diff --git a/converter/pbm/pi3topbm.c b/converter/pbm/pi3topbm.c
new file mode 100644
index 00000000..8b3b21e3
--- /dev/null
+++ b/converter/pbm/pi3topbm.c
@@ -0,0 +1,112 @@
+/*
+ * Convert a ATARI Degas .pi3 file to a portable bitmap file.
+ *
+ * Author: David Beckemeyer
+ *
+ * This code was derived from the original gemtopbm program written
+ * by Diomidis D. Spinellis.
+ *
+ * (C) Copyright 1988 David Beckemeyer and Diomidis D. Spinellis.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and that
+ * both that copyright notice and this permission notice appear in
+ * supporting documentation.
+ *
+ * This file is provided AS IS with no warranties of any kind.  The author
+ * shall have no liability with respect to the infringement of copyrights,
+ * trade secrets or any patents by this file or any part thereof.  In no
+ * event will the author be liable for any lost revenue or profits or
+ * other special, indirect and consequential damages.
+ */
+
+#include <stdio.h>
+#include "pbm.h"
+
+int
+main(argc, argv)
+	int             argc;
+	char           *argv[];
+{
+	int             debug = 0;
+	FILE           *f;
+	int             x;
+	int             i, k;
+	int             c;
+	int		rows, cols;
+	bit		*bitrow;
+	short res;
+	int black, white;
+	const char * const usage = "[-debug] [pi3file]";
+	int argn = 1;
+
+	pbm_init( &argc, argv );
+
+	while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0')
+	  {
+	    if (pm_keymatch(argv[1], "-debug", 2))
+	      debug = 1;
+	    else
+	      pm_usage (usage);
+	    ++argn;
+	  }
+
+	if (argn == argc)
+	    f = stdin;
+	else
+	  {
+	    f = pm_openr (argv[argn]);
+	    ++argn;
+	  }
+
+	if (argn != argc)
+	  pm_usage (usage);
+
+	if (pm_readbigshort (f, &res) == -1)
+		pm_error ("EOF / read error");
+
+	if (debug)
+		pm_message ("resolution is %d", res);
+
+	/* only handles hi-rez 640x400 */
+	if (res != 2)
+		pm_error( "bad resolution" );
+
+	pm_readbigshort (f, &res);
+	if (res == 0)
+	  {
+	    black = PBM_WHITE;
+	    white = PBM_BLACK;
+	  }
+	else
+	  {
+	    black = PBM_BLACK;
+	    white = PBM_WHITE;
+	  }
+
+	for (i = 1; i < 16; i++)
+	  if (pm_readbigshort (f, &res) == -1)
+	    pm_error ("EOF / read error");
+
+	cols = 640;
+	rows = 400;
+	pbm_writepbminit( stdout, cols, rows, 0 );
+	bitrow = pbm_allocrow( cols );
+
+	for (i = 0; i < rows; ++i) {
+		x = 0;
+		while (x < cols) {
+			if ((c = getc(f)) == EOF)
+				pm_error( "end of file reached" );
+			for (k = 0x80; k; k >>= 1) {
+				bitrow[x] = (k & c) ? black : white;
+				++x;
+			}
+		}
+		pbm_writepbmrow( stdout, bitrow, cols, 0 );
+	}
+	pm_close( f );
+	pm_close( stdout );
+	exit(0);
+}
diff --git a/converter/pbm/pktopbm.c b/converter/pbm/pktopbm.c
new file mode 100644
index 00000000..32ed4fde
--- /dev/null
+++ b/converter/pbm/pktopbm.c
@@ -0,0 +1,442 @@
+/*
+  pktopbm, adapted from "pktopx in C by Tomas Rokicki" by AJCD 1/8/90
+  1998-09-22: jcn <janneke@gnu.org>
+     - lots of bugfixes:
+     * always read x/y offset bytes (3x)
+     * reset bmx, bmy to defaults for each char
+     * fix bitmap y placement of dynamically packed char
+     * skip char early if no output file allocated
+     - added debug output
+  
+  compile with: cc -lpbm -o pktopbm pktopbm.c
+  */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "nstring.h"
+#include "pbm.h"
+
+#define NAMELENGTH 80
+#define MAXROWWIDTH 3200
+#define MAXPKCHAR 256
+
+typedef int integer ;
+typedef unsigned char quarterword ;
+typedef char boolean ;
+typedef quarterword eightbits ;
+
+static FILE *pkfile ;
+static char pkname[NAMELENGTH+1] ;
+static integer pktopbm_pkloc = 0;
+static char *filename[MAXPKCHAR] ;
+static bit **bitmap = NULL ;
+static integer dynf ;
+static eightbits inputbyte ;
+static eightbits bitweight ;
+static integer repeatcount ;
+static integer flagbyte ;
+static integer debug=0;
+
+#define dprintf(s,d) if (debug) printf(s,d)
+#define dprintf0(s) if (debug) printf(s)
+
+/* add a suffix to a filename in an allocated space */
+static void
+pktopbm_add_suffix(char *       const name, 
+                   const char * const suffix) {
+
+    char * const slash = strrchr(name, '/');
+    char * const dot   = strrchr(name, '.');
+    
+    if ((dot && slash ? dot < slash : !dot) && !STREQ(name, "-"))
+        strcat(name, suffix);
+}
+
+
+
+/* get a byte from the PK file */
+static eightbits 
+pktopbm_pkbyte(void) {
+   pktopbm_pkloc++ ;
+   return(getc(pkfile)) ;
+}
+
+
+
+/* get a 16-bit half word from the PK file */
+static integer 
+get16(void) {
+   integer const a = pktopbm_pkbyte() ;
+   return((a<<8) + pktopbm_pkbyte()) ;
+}
+
+
+
+/* get a 32-bit word from the PK file */
+static integer get32(void) {
+    integer a;
+    a = get16() ;
+    if (a > 32767) a -= 65536 ;
+    return((a<<16) + get16()) ;
+}
+
+
+
+/* get a nibble from current input byte, or new byte if no current byte */
+static integer 
+getnyb(void) {
+    eightbits temp;
+    if (bitweight == 0) {
+        inputbyte = pktopbm_pkbyte() ;
+        bitweight = 16 ;
+    }
+    temp = inputbyte / bitweight ;
+    inputbyte -= temp * bitweight ;
+    bitweight >>= 4 ;
+    return(temp) ;
+}
+
+
+
+/* get a bit from the current input byte, or a new byte if no current byte */
+static bool
+getbit(void) {
+    bool temp ;
+    bitweight >>= 1 ;
+    if (bitweight == 0) {
+        inputbyte = pktopbm_pkbyte() ;
+        bitweight = 128 ;
+    }
+    temp = (inputbyte >= bitweight) ;
+    if (temp) inputbyte -= bitweight ;
+    return(temp) ;
+}
+
+
+
+/* unpack a dynamically packed number. dynf is dynamic packing threshold  */
+static integer 
+pkpackednum(void) {
+    integer i, j ;
+    i = getnyb() ;
+    if (i == 0) {           /* large run count, >= 3 nibbles */
+        do {
+            j = getnyb() ;          /* count extra nibbles */
+            i++ ;
+        } while (j == 0) ;
+        while (i > 0) {
+            j = (j<<4) + getnyb() ; /* add extra nibbles */
+            i-- ;
+        }
+        return (j - 15 +((13 - dynf)<<4) + dynf) ;
+    } else if (i <= dynf) return (i) ;  /* number > 0 and <= dynf */
+    else if (i < 14) return (((i - dynf - 1)<<4) + getnyb() + dynf + 1) ;
+    else {
+        if (i == 14) repeatcount = pkpackednum() ;  /* get repeat count */
+        else repeatcount = 1 ;      /* value 15 indicates repeat count 1 */
+        return(pkpackednum()) ;
+    }
+}
+
+
+
+/* skip specials in PK files, inserted by Metafont or some other program */
+static void
+skipspecials(void) {
+    integer i, j;
+    do {
+        flagbyte = pktopbm_pkbyte() ;
+        if (flagbyte >= 240)
+            switch(flagbyte) {
+            case 240:           /* specials of size 1-4 bytes */
+            case 241:
+            case 242:
+            case 243:
+                i = 0 ;
+                for (j = 240 ; j <= flagbyte ; ++j) 
+                    i = (i<<8) + pktopbm_pkbyte() ;
+                for (j = 1 ; j <= i ; ++j) 
+                    pktopbm_pkbyte() ;  /* ignore special */
+                break ;
+            case 244:           /* no-op, parameters to specials */
+                get32() ;
+            case 245:           /* start of postamble */
+            case 246:           /* no-op */
+                break ;
+            case 247:           /* pre-amble in wrong place */
+            case 248:
+            case 249:
+            case 250:
+            case 251:
+            case 252:
+            case 253:
+            case 254:
+            case 255:
+                pm_error("unexpected flag byte %d", flagbyte) ;
+            }
+    } while (!(flagbyte < 240 || flagbyte == 245)) ;
+}
+
+
+
+/* ignore character packet */
+static void
+ignorechar(integer const car, 
+           integer const endofpacket) {
+
+   while (pktopbm_pkloc != endofpacket) pktopbm_pkbyte() ;
+   if (car < 0 || car >= MAXPKCHAR)
+      pm_message("Character %d out of range", car) ;
+   skipspecials() ;
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+    integer x;
+    integer endofpacket ;
+    boolean turnon ;
+    integer i, j;
+    integer car ;
+    integer bmx=0, bmy=0;
+    integer set_bmx=0, set_bmy=0;
+    bit row[MAXROWWIDTH+1] ;
+    const char * const usage = 
+        "pkfile[.pk] [-d] [[-x width] [-y height] [-c num] pbmfile]...";
+   
+    pbm_init(&argc, argv);
+    for (i = 0 ; i < MAXPKCHAR ; i ++) filename[i] = NULL ;
+
+    pm_message("This is PKtoPBM, version 2.5") ;
+
+    if (--argc < 1) pm_usage(usage) ;
+
+    strcpy(pkname, *++argv) ;
+    pktopbm_add_suffix(pkname, ".pk") ;
+
+    car = 0 ;
+    /* urg: use getopt */
+    while (++argv, --argc) {
+        if (argv[0][0] == '-' && argv[0][1])
+            switch (argv[0][1]) {
+            case 'X':
+            case 'x':
+                if (argv[0][2]) bmx = atoi(*argv+2) ;
+                else if (++argv, --argc) set_bmx = atoi(*argv) ;
+                else pm_usage(usage) ;
+                continue ;
+            case 'Y':
+            case 'y':
+                if (argv[0][2]) bmy = atoi(*argv+2) ;
+                else if (++argv, --argc) set_bmy = atoi(*argv) ;
+                else pm_usage(usage) ;
+                continue ;
+            case 'C':
+            case 'c':
+                if (argv[0][2]) car = atoi(*argv+2) ;
+                else if (++argv, --argc) car = atoi(*argv) ;
+                else pm_usage(usage) ;
+                break ;
+            case 'd':
+                debug=1;
+                break ;
+            default:
+                pm_usage(usage) ;
+            } else if (car < 0 || car >= MAXPKCHAR) {
+                pm_error("character must be in range 0 to %d (-c)", 
+                         MAXPKCHAR-1) ;
+            } else filename[car++] = *argv ;
+    }
+
+    pkfile = pm_openr(pkname);
+    if (pktopbm_pkbyte() != 247)
+        pm_error("bad PK file (pre command missing)") ;
+    if (pktopbm_pkbyte() != 89)
+        pm_error("wrong version of packed file") ;
+    j = pktopbm_pkbyte() ;              /* get header comment size */
+    for (i = 1 ; i <= j ; i ++) pktopbm_pkbyte() ;  /* ignore header comment */
+    get32() ;                   /* ignore designsize */
+    get32() ;                   /* ignore checksum */
+    if (get32() != get32())         /* h & v pixels per point */
+        pm_message("Warning: aspect ratio not 1:1") ;
+    skipspecials() ;
+    while (flagbyte != 245) {           /* not at postamble */
+        integer cheight, cwidth ;
+        integer xoffs=0, yoffs=0;
+        FILE *ofp;
+
+        bmx=set_bmx;
+        bmy=set_bmy;
+        dynf = (flagbyte>>4) ;          /* get dynamic packing value */
+        flagbyte &= 15 ;
+        turnon = (flagbyte >= 8) ;      /* black or white initially? */
+        if (turnon) flagbyte &= 7 ;     /* long or short form */
+        if (flagbyte == 7) {            /* long form preamble */
+            integer packetlength = get32() ;    /* character packet length */
+            car = get32() ;         /* character number */
+            endofpacket = packetlength + pktopbm_pkloc;
+                /* calculate end of packet */
+            if ((car >= MAXPKCHAR) || !filename[car]) {
+                ignorechar(car, endofpacket);
+                continue;
+            }
+            dprintf0 ("flagbyte7\n");
+            dprintf ("car: %d\n", car);
+            get32() ;               /* ignore tfmwidth */
+            x=get32() ;             /* ignore horiz escapement */
+            x=get32() ;             /* ignore vert escapement */
+            dprintf ("horiz esc %d\n", x);
+            dprintf ("vert esc %d\n", x);
+            cwidth = get32() ;          /* bounding box width */
+            cheight = get32() ;         /* bounding box height */
+            dprintf ("cwidth %d\n", cwidth);
+            dprintf ("cheight %d\n", cheight);
+            if (cwidth < 0 || cheight < 0 || 
+                cwidth > 65535 || cheight > 65535) {
+                ignorechar(car, endofpacket);
+                continue;
+            }
+            xoffs= get32() ;              /* horiz offset */
+            yoffs= get32() ;              /* vert offset */
+            dprintf ("xoffs %d\n", xoffs);
+            dprintf ("yoffs %d\n", yoffs);
+        } else if (flagbyte > 3) {      /* extended short form */
+            integer packetlength = ((flagbyte - 4)<<16) + get16() ;
+            /* packet length */
+            car = pktopbm_pkbyte() ;            /* char number */
+            endofpacket = packetlength + pktopbm_pkloc ; 
+                /* calculate end of packet */
+            if ((car >= MAXPKCHAR) || !filename[car]) {
+                ignorechar(car, endofpacket);
+                continue;
+            }
+            dprintf0 ("flagbyte>3\n");
+            dprintf ("car: %d\n", car);
+            pktopbm_pkbyte() ;              /* ignore tfmwidth (3 bytes) */
+            get16() ;               /* ignore tfmwidth (3 bytes) */
+            get16() ;               /* ignore horiz escapement */
+            cwidth = get16() ;          /* bounding box width */
+            cheight = get16() ;         /* bounding box height */
+            dprintf ("cwidth %d\n", cwidth);
+            dprintf ("cheight %d\n", cheight);
+            xoffs=get16();                         /* horiz offset */
+            if (xoffs >= 32768)
+                xoffs-= 65536;
+            yoffs=get16();                         /* vert offset */
+            if (yoffs >= 32768)
+                yoffs-= 65536;
+            dprintf ("xoffs %d\n", xoffs);
+            dprintf ("yoffs %d\n", yoffs);
+        } else {                    /* short form preamble */
+            integer packetlength = (flagbyte<<8) + pktopbm_pkbyte() ;
+            /* packet length */
+            car = pktopbm_pkbyte() ;            /* char number */
+            endofpacket = packetlength + pktopbm_pkloc ;    
+                /* calculate end of packet */
+            if ((car >= MAXPKCHAR) || !filename[car]) {
+                ignorechar(car, endofpacket);
+                continue;
+            }
+            dprintf0 ("flagbyte<=3\n");
+            dprintf ("car: %d\n", car);
+            pktopbm_pkbyte() ;          /* ignore tfmwidth (3 bytes) */
+            get16() ;               /* ignore tfmwidth (3 bytes) */
+            x = pktopbm_pkbyte() ;  /* ignore horiz escapement */
+            dprintf ("horiz esc %d\n", x);
+            cwidth = pktopbm_pkbyte() ;            /* bounding box width */
+            cheight = pktopbm_pkbyte() ;           /* bounding box height */
+            dprintf ("cwidth %d\n", cwidth);
+            dprintf ("cheight %d\n", cheight);
+            xoffs=pktopbm_pkbyte ();               /* horiz offset */
+            if (xoffs >= 128)
+                xoffs-=256;
+            yoffs=pktopbm_pkbyte ();               /* vert offset */
+            if (yoffs >= 128)
+                yoffs-=256;
+            dprintf ("xoffs %d\n", xoffs);
+            dprintf ("yoffs %d\n", yoffs);
+        }
+        if (filename[car]) {
+            if (!bmx) bmx= cwidth;
+            if (!bmy) bmy= cheight;
+            bitmap = pbm_allocarray(bmx, bmy) ;
+            if (bitmap == NULL)
+                pm_error("out of memory allocating bitmap") ;
+        } else {
+            ignorechar(car, endofpacket);
+            continue;
+        }
+        bitweight = 0 ;
+        for (i = 0 ; i < bmy ; i ++)           /* make it blank */
+            for (j = 0 ; j < bmx ; j ++)
+                bitmap[i][j]= PBM_WHITE;
+        if (dynf == 14) {               /* bitmapped character */
+            dprintf ("bmy: %d\n ", bmy);
+            dprintf ("y: %d\n ", bmy-yoffs-1);
+            for (i = 0 ; i < cheight ; i ++) {
+                int yi= i+(bmy-yoffs-1);
+                for (j = 0 ; j < cwidth ; j ++) {
+                    int xj= j-xoffs;
+                    if (getbit() && 0<=xj && xj<bmx && 0<=yi && yi<bmy)
+                        bitmap[yi][xj] = PBM_BLACK ;
+                }
+            }
+        } else {                    /* dynamically packed char */
+            integer rowsleft = cheight ;
+            integer hbit = cwidth ;
+            integer rp = 0;
+            repeatcount = 0 ;
+            dprintf ("bmy: %d\n ", bmy);
+            dprintf ("y: %d\n", cheight-rowsleft+(bmy-2*yoffs-1));
+            while (rowsleft > 0) {
+                integer count = pkpackednum() ; /* get current color count */
+                while (count > 0) {
+                    if (count < hbit) {     /* doesn't extend past row */
+                        hbit -= count ;
+                        while (count--)
+                            row[rp++] = turnon ? PBM_BLACK : PBM_WHITE;
+                    } else {                /* reaches end of row */
+                        count -= hbit ;
+                        while (hbit--)
+                            row[rp++] = turnon ? PBM_BLACK : PBM_WHITE;
+                        for (i = 0; i <= repeatcount; i++) {  /* fill row */
+                            int yi= i+cheight-rowsleft-1;
+                            if (0<=yi && yi < bmy)
+                                for (j = 0; j < cwidth; j++) {
+                                    int xj= j-xoffs;
+                                    if (0<=xj && xj<bmx)
+                                        bitmap[yi][xj] = row[j] ;
+                                }
+                        }
+                        rowsleft -= repeatcount + 1;
+                        repeatcount = rp = 0 ;
+                        hbit = cwidth ;
+                    }
+                }
+                turnon = !turnon ;
+            }
+            if (rowsleft != 0 || hbit != cwidth)
+                pm_error("bad pk file (more bits than required)") ;
+        }
+        if (endofpacket != pktopbm_pkloc)
+            pm_error("bad pk file (bad packet length)") ;
+
+        ofp = pm_openw(filename[car]);
+        filename[car] = NULL;
+        pbm_writepbm(ofp, bitmap, bmx, bmy, 0) ;
+        pbm_freearray(bitmap, bmy) ;
+        pm_close(ofp) ;
+        skipspecials() ;
+    }
+    while (! feof(pkfile)) pktopbm_pkbyte() ;       /* skip trailing junk */
+    pm_close(pkfile);
+    for (car = 0; car < MAXPKCHAR; car++)
+        if (filename[car])
+            pm_message("Warning: No character in position %d (file %s).",
+                       car, filename[car]) ;
+    pm_message("%d bytes read from packed file.", pktopbm_pkloc-1) ;
+    return 0;
+}
diff --git a/converter/pbm/thinkjettopbm.c b/converter/pbm/thinkjettopbm.c
new file mode 100644
index 00000000..2f5f6fda
--- /dev/null
+++ b/converter/pbm/thinkjettopbm.c
@@ -0,0 +1,1792 @@
+/* This is the file 'lexheader'.  It contains extra stuff needed by
+   parsers generated by lex.
+
+   GNU Flex generates a parser that refers to the non-ansi C library 
+   subroutine fileno().  In order to let it compile without warnings,
+   we define _XOPEN_SOURCE to declare that fact.
+*/
+#define _XOPEN_SOURCE
+/* END OF lexheader */
+/* A lexical scanner generated by flex */
+
+/* Scanner skeleton version:
+ * $Header: /home/daffy/u0/vern/flex/RCS/flex.skl,v 2.91 96/09/10 16:58:48 vern Exp $
+ */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 5
+
+#include <stdio.h>
+
+
+/* cfront 1.2 defines "c_plusplus" instead of "__cplusplus" */
+#ifdef c_plusplus
+#ifndef __cplusplus
+#define __cplusplus
+#endif
+#endif
+
+
+#ifdef __cplusplus
+
+#include <stdlib.h>
+#include <unistd.h>
+
+/* Use prototypes in function declarations. */
+#define YY_USE_PROTOS
+
+/* The "const" storage-class-modifier is valid. */
+#define YY_USE_CONST
+
+#else	/* ! __cplusplus */
+
+#if __STDC__
+
+#define YY_USE_PROTOS
+#define YY_USE_CONST
+
+#endif	/* __STDC__ */
+#endif	/* ! __cplusplus */
+
+#ifdef __TURBOC__
+ #pragma warn -rch
+ #pragma warn -use
+#include <io.h>
+#include <stdlib.h>
+#define YY_USE_CONST
+#define YY_USE_PROTOS
+#endif
+
+#ifdef YY_USE_CONST
+#define yyconst const
+#else
+#define yyconst
+#endif
+
+
+#ifdef YY_USE_PROTOS
+#define YY_PROTO(proto) proto
+#else
+#define YY_PROTO(proto) ()
+#endif
+
+/* Returned upon end-of-file. */
+#define YY_NULL 0
+
+/* Promotes a possibly negative, possibly signed char to an unsigned
+ * integer for use as an array index.  If the signed char is negative,
+ * we want to instead treat it as an 8-bit unsigned char, hence the
+ * double cast.
+ */
+#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c)
+
+/* Enter a start condition.  This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN yy_start = 1 + 2 *
+
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state.  The YYSTATE alias is for lex
+ * compatibility.
+ */
+#define YY_START ((yy_start - 1) / 2)
+#define YYSTATE YY_START
+
+/* Action number for EOF rule of a given start state. */
+#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
+
+/* Special action meaning "start processing a new file". */
+#define YY_NEW_FILE yyrestart( yyin )
+
+#define YY_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#define YY_BUF_SIZE 16384
+
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+
+extern int yyleng;
+extern FILE *yyin, *yyout;
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+/* The funky do-while in the following #define is used to turn the definition
+ * int a single C statement (which needs a semi-colon terminator).  This
+ * avoids problems with code like:
+ *
+ * 	if ( condition_holds )
+ *		yyless( 5 );
+ *	else
+ *		do_something_else();
+ *
+ * Prior to using the do-while the compiler would get upset at the
+ * "else" because it interpreted the "if" statement as being all
+ * done when it reached the ';' after the yyless() call.
+ */
+
+/* Return all but the first 'n' matched characters back to the input stream. */
+
+#define yyless(n) \
+	do \
+		{ \
+		/* Undo effects of setting up yytext. */ \
+		*yy_cp = yy_hold_char; \
+		YY_RESTORE_YY_MORE_OFFSET \
+		yy_c_buf_p = yy_cp = yy_bp + n - YY_MORE_ADJ; \
+		YY_DO_BEFORE_ACTION; /* set up yytext again */ \
+		} \
+	while ( 0 )
+
+#define unput(c) yyunput( c, yytext_ptr )
+
+/* The following is because we cannot portably get our hands on size_t
+ * (without autoconf's help, which isn't available because we want
+ * flex-generated scanners to compile on their own).
+ */
+typedef unsigned int yy_size_t;
+
+
+struct yy_buffer_state
+	{
+	FILE *yy_input_file;
+
+	char *yy_ch_buf;		/* input buffer */
+	char *yy_buf_pos;		/* current position in input buffer */
+
+	/* Size of input buffer in bytes, not including room for EOB
+	 * characters.
+	 */
+	yy_size_t yy_buf_size;
+
+	/* Number of characters read into yy_ch_buf, not including EOB
+	 * characters.
+	 */
+	int yy_n_chars;
+
+	/* Whether we "own" the buffer - i.e., we know we created it,
+	 * and can realloc() it to grow it, and should free() it to
+	 * delete it.
+	 */
+	int yy_is_our_buffer;
+
+	/* Whether this is an "interactive" input source; if so, and
+	 * if we're using stdio for input, then we want to use getc()
+	 * instead of fread(), to make sure we stop fetching input after
+	 * each newline.
+	 */
+	int yy_is_interactive;
+
+	/* Whether we're considered to be at the beginning of a line.
+	 * If so, '^' rules will be active on the next match, otherwise
+	 * not.
+	 */
+	int yy_at_bol;
+
+	/* Whether to try to fill the input buffer when we reach the
+	 * end of it.
+	 */
+	int yy_fill_buffer;
+
+	int yy_buffer_status;
+#define YY_BUFFER_NEW 0
+#define YY_BUFFER_NORMAL 1
+	/* When an EOF's been seen but there's still some text to process
+	 * then we mark the buffer as YY_EOF_PENDING, to indicate that we
+	 * shouldn't try reading from the input source any more.  We might
+	 * still have a bunch of tokens to match, though, because of
+	 * possible backing-up.
+	 *
+	 * When we actually see the EOF, we change the status to "new"
+	 * (via yyrestart()), so that the user can continue scanning by
+	 * just pointing yyin at a new input file.
+	 */
+#define YY_BUFFER_EOF_PENDING 2
+	};
+
+static YY_BUFFER_STATE yy_current_buffer = 0;
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ */
+#define YY_CURRENT_BUFFER yy_current_buffer
+
+
+/* yy_hold_char holds the character lost when yytext is formed. */
+static char yy_hold_char;
+
+static int yy_n_chars;		/* number of characters read into yy_ch_buf */
+
+
+int yyleng;
+
+/* Points to current character in buffer. */
+static char *yy_c_buf_p = (char *) 0;
+static int yy_init = 1;		/* whether we need to initialize */
+static int yy_start = 0;	/* start state number */
+
+/* Flag which is used to allow yywrap()'s to do buffer switches
+ * instead of setting up a fresh yyin.  A bit of a hack ...
+ */
+static int yy_did_buffer_switch_on_eof;
+
+void yyrestart YY_PROTO(( FILE *input_file ));
+
+void yy_switch_to_buffer YY_PROTO(( YY_BUFFER_STATE new_buffer ));
+void yy_load_buffer_state YY_PROTO(( void ));
+YY_BUFFER_STATE yy_create_buffer YY_PROTO(( FILE *file, int size ));
+void yy_delete_buffer YY_PROTO(( YY_BUFFER_STATE b ));
+void yy_init_buffer YY_PROTO(( YY_BUFFER_STATE b, FILE *file ));
+void yy_flush_buffer YY_PROTO(( YY_BUFFER_STATE b ));
+#define YY_FLUSH_BUFFER yy_flush_buffer( yy_current_buffer )
+
+YY_BUFFER_STATE yy_scan_buffer YY_PROTO(( char *base, yy_size_t size ));
+YY_BUFFER_STATE yy_scan_string YY_PROTO(( yyconst char *yy_str ));
+YY_BUFFER_STATE yy_scan_bytes YY_PROTO(( yyconst char *bytes, int len ));
+
+static void *yy_flex_alloc YY_PROTO(( yy_size_t ));
+static void *yy_flex_realloc YY_PROTO(( void *, yy_size_t ));
+static void yy_flex_free YY_PROTO(( void * ));
+
+#define yy_new_buffer yy_create_buffer
+
+#define yy_set_interactive(is_interactive) \
+	{ \
+	if ( ! yy_current_buffer ) \
+		yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE ); \
+	yy_current_buffer->yy_is_interactive = is_interactive; \
+	}
+
+#define yy_set_bol(at_bol) \
+	{ \
+	if ( ! yy_current_buffer ) \
+		yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE ); \
+	yy_current_buffer->yy_at_bol = at_bol; \
+	}
+
+#define YY_AT_BOL() (yy_current_buffer->yy_at_bol)
+
+typedef unsigned char YY_CHAR;
+FILE *yyin = (FILE *) 0, *yyout = (FILE *) 0;
+typedef int yy_state_type;
+extern char *yytext;
+#define yytext_ptr yytext
+
+static yy_state_type yy_get_previous_state YY_PROTO(( void ));
+static yy_state_type yy_try_NUL_trans YY_PROTO(( yy_state_type current_state ));
+static int yy_get_next_buffer YY_PROTO(( void ));
+static void yy_fatal_error YY_PROTO(( yyconst char msg[] ));
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up yytext.
+ */
+#define YY_DO_BEFORE_ACTION \
+	yytext_ptr = yy_bp; \
+	yyleng = (int) (yy_cp - yy_bp); \
+	yy_hold_char = *yy_cp; \
+	*yy_cp = '\0'; \
+	yy_c_buf_p = yy_cp;
+
+#define YY_NUM_RULES 10
+#define YY_END_OF_BUFFER 11
+static yyconst short int yy_accept[34] =
+    {   0,
+        0,    0,    0,    0,    0,    0,   11,    9,    9,   10,
+        4,   10,    1,    0,    0,    0,    0,    0,    0,    0,
+        0,    0,    0,    0,    8,    0,    3,    5,    5,    7,
+        6,    2,    0
+    } ;
+
+static yyconst int yy_ec[256] =
+    {   0,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    2,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    3,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    4,    1,    1,
+        1,    5,    1,    1,    1,    6,    1,    7,    7,    7,
+        7,    7,    7,    7,    7,    7,    7,    1,    1,    1,
+        1,    1,    1,    1,    8,    9,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,   10,    1,    1,    1,   11,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,   12,    1,    1,
+
+        1,    1,    1,    1,    1,    1,    1,   13,    1,    1,
+        1,    1,    1,   14,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1
+    } ;
+
+static yyconst int yy_meta[15] =
+    {   0,
+        1,    2,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1
+    } ;
+
+static yyconst short int yy_base[38] =
+    {   0,
+       35,   34,    0,    5,    0,    0,   36,   39,    0,   39,
+       39,   30,   39,   21,    0,    1,   26,   25,    2,   24,
+       21,   15,    9,   11,   39,   12,   39,   39,   10,   39,
+       39,   39,   39,   23,   25,   27,    0
+    } ;
+
+static yyconst short int yy_def[38] =
+    {   0,
+       34,   34,   35,   35,   36,   36,   33,   33,   33,   33,
+       33,   33,   33,   33,   33,   33,   33,   33,   33,   33,
+       33,   37,   33,   33,   33,   33,   33,   33,   37,   33,
+       33,   33,    0,   33,   33,   33,   33
+    } ;
+
+static yyconst short int yy_nxt[54] =
+    {   0,
+       28,   11,   12,   14,   15,   11,   11,   12,   24,   25,
+       11,   18,   20,   19,   21,   23,   29,   24,   26,   30,
+       31,   29,   32,    8,    8,   10,   10,   13,   13,   27,
+       26,   23,   22,   17,   16,   33,    9,    9,    7,   33,
+       33,   33,   33,   33,   33,   33,   33,   33,   33,   33,
+       33,   33,   33
+    } ;
+
+static yyconst short int yy_chk[54] =
+    {   0,
+       37,    3,    3,    9,    9,    3,    4,    4,   19,   19,
+        4,   15,   16,   15,   16,   23,   29,   24,   26,   23,
+       24,   22,   26,   34,   34,   35,   35,   36,   36,   21,
+       20,   18,   17,   14,   12,    7,    2,    1,   33,   33,
+       33,   33,   33,   33,   33,   33,   33,   33,   33,   33,
+       33,   33,   33
+    } ;
+
+static yy_state_type yy_last_accepting_state;
+static char *yy_last_accepting_cpos;
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define yymore() yymore_used_but_not_detected
+#define YY_MORE_ADJ 0
+#define YY_RESTORE_YY_MORE_OFFSET
+char *yytext;
+#line 1 "thinkjettopbm.l"
+#define INITIAL 0
+/*
+ * A user reported in January 2005 a problem building Thinkjettopbm
+ * in which the opening comment delimiter above for some reason did
+ * not make it into the Lex output in the Netpbm build of this.
+ * Needless to say, that would not compile.  This user was using
+ * 'lex -t' on Tru64.  We did not find it worthwhile to debug it.
+ *
+ * Simple FLEX scanner to convert HP ThinkJet graphics image
+ * to PBM format.
+ *
+ * Implements a small subset of ThinkJet commands.
+ *
+ * Copyright (C) 2001 by W. Eric Norum
+ *
+ * Department of Electrical Engineering
+ * University of Saskatchewan
+ * Saskatoon, Saskatchewan, CANADA
+ * eric.norum@usask.ca
+ *
+ *  Permission to use, copy, modify, and distribute this software and
+ *  its documentation for any purpose and without fee is hereby granted,
+ *  provided that the above copyright notice appear in all copies and
+ *  that both that copyright notice and this permission notice appear in
+ *  supporting documentation.  This software is provided "as is" without
+ *  express or implied warranty.
+ *
+ *  Modified 2001.04.05 by Bryan Henderson for inclusion in the Netpbm
+ *  package.  Now uses Netpbm libraries and, for consistency with other
+ *  Netpbm programs, does not have PGM output option.
+ */
+#line 34 "thinkjettopbm.l"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include "pbm.h"
+#include "shhopt.h"
+
+/* The following macro definitions tell Lex what sort of code to generate.
+   GNU Flex does #if XXX for some of these, as opposed to #ifdef XXX, which
+   means that they properly have to be set to zero instead of just left
+   undefined.  (Simply leaving them undefined typically works anyway, but it
+   is a problem if you use compiler options that say to fail when someone
+   uses a macro he failed to define).
+*/
+#define YY_NO_UNPUT
+#define YY_STACK_USED 0
+#define YY_ALWAYS_INTERACTIVE 0
+#define YY_NEVER_INTERACTIVE 0
+#define YY_MAIN 0
+    /* Don't include the main() function.  We have our own */
+
+static int yylex(void);
+static int yywrap(void);
+/* This works, but generates a warning 
+static void yyrestart(FILE*);
+*/
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+
+    unsigned int debug;
+};
+
+
+
+struct RowInfo {
+    int     length;    /* length, in bytes */
+    char    *bits;     /* Bitmap */
+};
+
+static int maxRowLength;
+static int rowCount;
+static int rowCapacity;
+static struct RowInfo *rows;
+
+static int column;
+
+int debugFlag;
+static void debug (const char *format, ...);
+
+#define RASTERMODE 1
+#define ROWMODE 2
+
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int yywrap YY_PROTO(( void ));
+#else
+extern int yywrap YY_PROTO(( void ));
+#endif
+#endif
+
+#ifndef YY_NO_UNPUT
+static void yyunput YY_PROTO(( int c, char *buf_ptr ));
+#endif
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy YY_PROTO(( char *, yyconst char *, int ));
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen YY_PROTO(( yyconst char * ));
+#endif
+
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+static int yyinput YY_PROTO(( void ));
+#else
+static int input YY_PROTO(( void ));
+#endif
+#endif
+
+#if YY_STACK_USED
+static int yy_start_stack_ptr = 0;
+static int yy_start_stack_depth = 0;
+static int *yy_start_stack = 0;
+#ifndef YY_NO_PUSH_STATE
+static void yy_push_state YY_PROTO(( int new_state ));
+#endif
+#ifndef YY_NO_POP_STATE
+static void yy_pop_state YY_PROTO(( void ));
+#endif
+#ifndef YY_NO_TOP_STATE
+static int yy_top_state YY_PROTO(( void ));
+#endif
+
+#else
+#define YY_NO_PUSH_STATE 1
+#define YY_NO_POP_STATE 1
+#define YY_NO_TOP_STATE 1
+#endif
+
+#ifdef YY_MALLOC_DECL
+YY_MALLOC_DECL
+#else
+#if __STDC__
+#ifndef __cplusplus
+#include <stdlib.h>
+#endif
+#else
+/* Just try to get by without declaring the routines.  This will fail
+ * miserably on non-ANSI systems for which sizeof(size_t) != sizeof(int)
+ * or sizeof(void*) != sizeof(int).
+ */
+#endif
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#define YY_READ_BUF_SIZE 8192
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+
+#ifndef ECHO
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO (void) fwrite( yytext, yyleng, 1, yyout )
+#endif
+
+/* Gets input and stuffs it into "buf".  number of characters read, or YY_NULL,
+ * is returned in "result".
+ */
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+	if ( yy_current_buffer->yy_is_interactive ) \
+		{ \
+		int c = '*', n; \
+		for ( n = 0; n < max_size && \
+			     (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
+			buf[n] = (char) c; \
+		if ( c == '\n' ) \
+			buf[n++] = (char) c; \
+		if ( c == EOF && ferror( yyin ) ) \
+			YY_FATAL_ERROR( "input in flex scanner failed" ); \
+		result = n; \
+		} \
+	else if ( ((result = fread( buf, 1, max_size, yyin )) == 0) \
+		  && ferror( yyin ) ) \
+		YY_FATAL_ERROR( "input in flex scanner failed" );
+#endif
+
+/* No semi-colon after return; correct usage is to write "yyterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef yyterminate
+#define yyterminate() return YY_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef YY_FATAL_ERROR
+#define YY_FATAL_ERROR(msg) yy_fatal_error( msg )
+#endif
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL int yylex YY_PROTO(( void ))
+#endif
+
+/* Code executed at the beginning of each rule, after yytext and yyleng
+ * have been set up.
+ */
+#ifndef YY_USER_ACTION
+#define YY_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef YY_BREAK
+#define YY_BREAK break;
+#endif
+
+#define YY_RULE_SETUP \
+	YY_USER_ACTION
+
+YY_DECL
+	{
+	register yy_state_type yy_current_state;
+	register char *yy_cp, *yy_bp;
+	register int yy_act;
+
+#line 95 "thinkjettopbm.l"
+
+
+
+	if ( yy_init )
+		{
+		yy_init = 0;
+
+#ifdef YY_USER_INIT
+		YY_USER_INIT;
+#endif
+
+		if ( ! yy_start )
+			yy_start = 1;	/* first start state */
+
+		if ( ! yyin )
+			yyin = stdin;
+
+		if ( ! yyout )
+			yyout = stdout;
+
+		if ( ! yy_current_buffer )
+			yy_current_buffer =
+				yy_create_buffer( yyin, YY_BUF_SIZE );
+
+		yy_load_buffer_state();
+		}
+
+	while ( 1 )		/* loops until end-of-file is reached */
+		{
+		yy_cp = yy_c_buf_p;
+
+		/* Support of yytext. */
+		*yy_cp = yy_hold_char;
+
+		/* yy_bp points to the position in yy_ch_buf of the start of
+		 * the current run.
+		 */
+		yy_bp = yy_cp;
+
+		yy_current_state = yy_start;
+yy_match:
+		do
+			{
+			register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)];
+			if ( yy_accept[yy_current_state] )
+				{
+				yy_last_accepting_state = yy_current_state;
+				yy_last_accepting_cpos = yy_cp;
+				}
+			while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+				{
+				yy_current_state = (int) yy_def[yy_current_state];
+				if ( yy_current_state >= 34 )
+					yy_c = yy_meta[(unsigned int) yy_c];
+				}
+			yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+			++yy_cp;
+			}
+		while ( yy_base[yy_current_state] != 39 );
+
+yy_find_action:
+		yy_act = yy_accept[yy_current_state];
+		if ( yy_act == 0 )
+			{ /* have to back up */
+			yy_cp = yy_last_accepting_cpos;
+			yy_current_state = yy_last_accepting_state;
+			yy_act = yy_accept[yy_current_state];
+			}
+
+		YY_DO_BEFORE_ACTION;
+
+
+do_action:	/* This label is used only to access EOF actions. */
+
+
+		switch ( yy_act )
+	{ /* beginning of action switch */
+			case 0: /* must back up */
+			/* undo the effects of YY_DO_BEFORE_ACTION */
+			*yy_cp = yy_hold_char;
+			yy_cp = yy_last_accepting_cpos;
+			yy_current_state = yy_last_accepting_state;
+			goto yy_find_action;
+
+case 1:
+YY_RULE_SETUP
+#line 97 "thinkjettopbm.l"
+{
+                        rows[rowCount].bits[column++] = yytext[0]; 
+                        if (column >= rows[rowCount].length) {
+                            rowCount++;
+                            debug ("Done %d-byte row %d.\n", column, rowCount);
+                            BEGIN (RASTERMODE);
+                        }
+                        }
+	YY_BREAK
+case 2:
+YY_RULE_SETUP
+#line 106 "thinkjettopbm.l"
+{
+                            int l;
+                            if (rowCount >= rowCapacity) {
+                                rowCapacity += 100;
+                                rows = realloc (rows, rowCapacity * sizeof *rows);
+                                if (rows == NULL)
+                                    pm_error ("Out of memory.");
+                            }
+                            l = atoi (yytext+3);
+                            rows[rowCount].length = l;
+                            rows[rowCount].bits = malloc (l);
+                            if (rows[rowCount].bits == NULL)
+                                pm_error ("Out of memory.");
+                            if (l > maxRowLength)
+                                maxRowLength = l;
+                            debug ("Start %d-byte row.\n", l);
+                            column = 0;
+                            BEGIN (ROWMODE);
+                            }
+	YY_BREAK
+case 3:
+YY_RULE_SETUP
+#line 126 "thinkjettopbm.l"
+{
+                       debug ("Match <esc>*rB\n");
+                       BEGIN (0);
+                       }
+	YY_BREAK
+case 4:
+YY_RULE_SETUP
+#line 131 "thinkjettopbm.l"
+{ pm_error ("Unexpected character (%#x) in raster mode.\n", yytext[0]); }
+	YY_BREAK
+case 5:
+YY_RULE_SETUP
+#line 133 "thinkjettopbm.l"
+{ debug ("Match <esc>&l\n"); }
+	YY_BREAK
+case 6:
+YY_RULE_SETUP
+#line 134 "thinkjettopbm.l"
+{ debug ("Match <esc>*r#S\n"); }
+	YY_BREAK
+case 7:
+YY_RULE_SETUP
+#line 135 "thinkjettopbm.l"
+{ debug ("Match <esc>*r#w\n"); }
+	YY_BREAK
+case 8:
+YY_RULE_SETUP
+#line 136 "thinkjettopbm.l"
+{
+                       debug ("Match <esc>*rA\n");
+                       BEGIN (RASTERMODE);
+                       }
+	YY_BREAK
+case 9:
+YY_RULE_SETUP
+#line 141 "thinkjettopbm.l"
+{ /* Silently consume all other characters */ }
+	YY_BREAK
+case 10:
+YY_RULE_SETUP
+#line 143 "thinkjettopbm.l"
+ECHO;
+	YY_BREAK
+case YY_STATE_EOF(INITIAL):
+case YY_STATE_EOF(RASTERMODE):
+case YY_STATE_EOF(ROWMODE):
+	yyterminate();
+
+	case YY_END_OF_BUFFER:
+		{
+		/* Amount of text matched not including the EOB char. */
+		int yy_amount_of_matched_text = (int) (yy_cp - yytext_ptr) - 1;
+
+		/* Undo the effects of YY_DO_BEFORE_ACTION. */
+		*yy_cp = yy_hold_char;
+		YY_RESTORE_YY_MORE_OFFSET
+
+		if ( yy_current_buffer->yy_buffer_status == YY_BUFFER_NEW )
+			{
+			/* We're scanning a new file or input source.  It's
+			 * possible that this happened because the user
+			 * just pointed yyin at a new source and called
+			 * yylex().  If so, then we have to assure
+			 * consistency between yy_current_buffer and our
+			 * globals.  Here is the right place to do so, because
+			 * this is the first action (other than possibly a
+			 * back-up) that will match for the new input source.
+			 */
+			yy_n_chars = yy_current_buffer->yy_n_chars;
+			yy_current_buffer->yy_input_file = yyin;
+			yy_current_buffer->yy_buffer_status = YY_BUFFER_NORMAL;
+			}
+
+		/* Note that here we test for yy_c_buf_p "<=" to the position
+		 * of the first EOB in the buffer, since yy_c_buf_p will
+		 * already have been incremented past the NUL character
+		 * (since all states make transitions on EOB to the
+		 * end-of-buffer state).  Contrast this with the test
+		 * in input().
+		 */
+		if ( yy_c_buf_p <= &yy_current_buffer->yy_ch_buf[yy_n_chars] )
+			{ /* This was really a NUL. */
+			yy_state_type yy_next_state;
+
+			yy_c_buf_p = yytext_ptr + yy_amount_of_matched_text;
+
+			yy_current_state = yy_get_previous_state();
+
+			/* Okay, we're now positioned to make the NUL
+			 * transition.  We couldn't have
+			 * yy_get_previous_state() go ahead and do it
+			 * for us because it doesn't know how to deal
+			 * with the possibility of jamming (and we don't
+			 * want to build jamming into it because then it
+			 * will run more slowly).
+			 */
+
+			yy_next_state = yy_try_NUL_trans( yy_current_state );
+
+			yy_bp = yytext_ptr + YY_MORE_ADJ;
+
+			if ( yy_next_state )
+				{
+				/* Consume the NUL. */
+				yy_cp = ++yy_c_buf_p;
+				yy_current_state = yy_next_state;
+				goto yy_match;
+				}
+
+			else
+				{
+				yy_cp = yy_c_buf_p;
+				goto yy_find_action;
+				}
+			}
+
+		else switch ( yy_get_next_buffer() )
+			{
+			case EOB_ACT_END_OF_FILE:
+				{
+				yy_did_buffer_switch_on_eof = 0;
+
+				if ( yywrap() )
+					{
+					/* Note: because we've taken care in
+					 * yy_get_next_buffer() to have set up
+					 * yytext, we can now set up
+					 * yy_c_buf_p so that if some total
+					 * hoser (like flex itself) wants to
+					 * call the scanner after we return the
+					 * YY_NULL, it'll still work - another
+					 * YY_NULL will get returned.
+					 */
+					yy_c_buf_p = yytext_ptr + YY_MORE_ADJ;
+
+					yy_act = YY_STATE_EOF(YY_START);
+					goto do_action;
+					}
+
+				else
+					{
+					if ( ! yy_did_buffer_switch_on_eof )
+						YY_NEW_FILE;
+					}
+				break;
+				}
+
+			case EOB_ACT_CONTINUE_SCAN:
+				yy_c_buf_p =
+					yytext_ptr + yy_amount_of_matched_text;
+
+				yy_current_state = yy_get_previous_state();
+
+				yy_cp = yy_c_buf_p;
+				yy_bp = yytext_ptr + YY_MORE_ADJ;
+				goto yy_match;
+
+			case EOB_ACT_LAST_MATCH:
+				yy_c_buf_p =
+				&yy_current_buffer->yy_ch_buf[yy_n_chars];
+
+				yy_current_state = yy_get_previous_state();
+
+				yy_cp = yy_c_buf_p;
+				yy_bp = yytext_ptr + YY_MORE_ADJ;
+				goto yy_find_action;
+			}
+		break;
+		}
+
+	default:
+		YY_FATAL_ERROR(
+			"fatal flex scanner internal error--no action found" );
+	} /* end of action switch */
+		} /* end of scanning one token */
+	} /* end of yylex */
+
+
+/* yy_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ *	EOB_ACT_LAST_MATCH -
+ *	EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ *	EOB_ACT_END_OF_FILE - end of file
+ */
+
+static int yy_get_next_buffer()
+	{
+	register char *dest = yy_current_buffer->yy_ch_buf;
+	register char *source = yytext_ptr;
+	register int number_to_move, i;
+	int ret_val;
+
+	if ( yy_c_buf_p > &yy_current_buffer->yy_ch_buf[yy_n_chars + 1] )
+		YY_FATAL_ERROR(
+		"fatal flex scanner internal error--end of buffer missed" );
+
+	if ( yy_current_buffer->yy_fill_buffer == 0 )
+		{ /* Don't try to fill the buffer, so this is an EOF. */
+		if ( yy_c_buf_p - yytext_ptr - YY_MORE_ADJ == 1 )
+			{
+			/* We matched a single character, the EOB, so
+			 * treat this as a final EOF.
+			 */
+			return EOB_ACT_END_OF_FILE;
+			}
+
+		else
+			{
+			/* We matched some text prior to the EOB, first
+			 * process it.
+			 */
+			return EOB_ACT_LAST_MATCH;
+			}
+		}
+
+	/* Try to read more data. */
+
+	/* First move last chars to start of buffer. */
+	number_to_move = (int) (yy_c_buf_p - yytext_ptr) - 1;
+
+	for ( i = 0; i < number_to_move; ++i )
+		*(dest++) = *(source++);
+
+	if ( yy_current_buffer->yy_buffer_status == YY_BUFFER_EOF_PENDING )
+		/* don't do the read, it's not guaranteed to return an EOF,
+		 * just force an EOF
+		 */
+		yy_current_buffer->yy_n_chars = yy_n_chars = 0;
+
+	else
+		{
+		int num_to_read =
+			yy_current_buffer->yy_buf_size - number_to_move - 1;
+
+		while ( num_to_read <= 0 )
+			{ /* Not enough room in the buffer - grow it. */
+#ifdef YY_USES_REJECT
+			YY_FATAL_ERROR(
+"input buffer overflow, can't enlarge buffer because scanner uses REJECT" );
+#else
+
+			/* just a shorter name for the current buffer */
+			YY_BUFFER_STATE b = yy_current_buffer;
+
+			int yy_c_buf_p_offset =
+				(int) (yy_c_buf_p - b->yy_ch_buf);
+
+			if ( b->yy_is_our_buffer )
+				{
+				int new_size = b->yy_buf_size * 2;
+
+				if ( new_size <= 0 )
+					b->yy_buf_size += b->yy_buf_size / 8;
+				else
+					b->yy_buf_size *= 2;
+
+				b->yy_ch_buf = (char *)
+					/* Include room in for 2 EOB chars. */
+					yy_flex_realloc( (void *) b->yy_ch_buf,
+							 b->yy_buf_size + 2 );
+				}
+			else
+				/* Can't grow it, we don't own it. */
+				b->yy_ch_buf = 0;
+
+			if ( ! b->yy_ch_buf )
+				YY_FATAL_ERROR(
+				"fatal error - scanner input buffer overflow" );
+
+			yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset];
+
+			num_to_read = yy_current_buffer->yy_buf_size -
+						number_to_move - 1;
+#endif
+			}
+
+		if ( num_to_read > YY_READ_BUF_SIZE )
+			num_to_read = YY_READ_BUF_SIZE;
+
+		/* Read in more data. */
+		YY_INPUT( (&yy_current_buffer->yy_ch_buf[number_to_move]),
+			yy_n_chars, num_to_read );
+
+		yy_current_buffer->yy_n_chars = yy_n_chars;
+		}
+
+	if ( yy_n_chars == 0 )
+		{
+		if ( number_to_move == YY_MORE_ADJ )
+			{
+			ret_val = EOB_ACT_END_OF_FILE;
+			yyrestart( yyin );
+			}
+
+		else
+			{
+			ret_val = EOB_ACT_LAST_MATCH;
+			yy_current_buffer->yy_buffer_status =
+				YY_BUFFER_EOF_PENDING;
+			}
+		}
+
+	else
+		ret_val = EOB_ACT_CONTINUE_SCAN;
+
+	yy_n_chars += number_to_move;
+	yy_current_buffer->yy_ch_buf[yy_n_chars] = YY_END_OF_BUFFER_CHAR;
+	yy_current_buffer->yy_ch_buf[yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR;
+
+	yytext_ptr = &yy_current_buffer->yy_ch_buf[0];
+
+	return ret_val;
+	}
+
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+static yy_state_type yy_get_previous_state()
+	{
+	register yy_state_type yy_current_state;
+	register char *yy_cp;
+
+	yy_current_state = yy_start;
+
+	for ( yy_cp = yytext_ptr + YY_MORE_ADJ; yy_cp < yy_c_buf_p; ++yy_cp )
+		{
+		register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 6);
+		if ( yy_accept[yy_current_state] )
+			{
+			yy_last_accepting_state = yy_current_state;
+			yy_last_accepting_cpos = yy_cp;
+			}
+		while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+			{
+			yy_current_state = (int) yy_def[yy_current_state];
+			if ( yy_current_state >= 34 )
+				yy_c = yy_meta[(unsigned int) yy_c];
+			}
+		yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+		}
+
+	return yy_current_state;
+	}
+
+
+/* yy_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ *	next_state = yy_try_NUL_trans( current_state );
+ */
+
+#ifdef YY_USE_PROTOS
+static yy_state_type yy_try_NUL_trans( yy_state_type yy_current_state )
+#else
+static yy_state_type yy_try_NUL_trans( yy_current_state )
+yy_state_type yy_current_state;
+#endif
+	{
+	register int yy_is_jam;
+	register char *yy_cp = yy_c_buf_p;
+
+	register YY_CHAR yy_c = 6;
+	if ( yy_accept[yy_current_state] )
+		{
+		yy_last_accepting_state = yy_current_state;
+		yy_last_accepting_cpos = yy_cp;
+		}
+	while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+		{
+		yy_current_state = (int) yy_def[yy_current_state];
+		if ( yy_current_state >= 34 )
+			yy_c = yy_meta[(unsigned int) yy_c];
+		}
+	yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+	yy_is_jam = (yy_current_state == 33);
+
+	return yy_is_jam ? 0 : yy_current_state;
+	}
+
+
+#ifndef YY_NO_UNPUT
+#ifdef YY_USE_PROTOS
+static void yyunput( int c, register char *yy_bp )
+#else
+static void yyunput( c, yy_bp )
+int c;
+register char *yy_bp;
+#endif
+	{
+	register char *yy_cp = yy_c_buf_p;
+
+	/* undo effects of setting up yytext */
+	*yy_cp = yy_hold_char;
+
+	if ( yy_cp < yy_current_buffer->yy_ch_buf + 2 )
+		{ /* need to shift things up to make room */
+		/* +2 for EOB chars. */
+		register int number_to_move = yy_n_chars + 2;
+		register char *dest = &yy_current_buffer->yy_ch_buf[
+					yy_current_buffer->yy_buf_size + 2];
+		register char *source =
+				&yy_current_buffer->yy_ch_buf[number_to_move];
+
+		while ( source > yy_current_buffer->yy_ch_buf )
+			*--dest = *--source;
+
+		yy_cp += (int) (dest - source);
+		yy_bp += (int) (dest - source);
+		yy_current_buffer->yy_n_chars =
+			yy_n_chars = yy_current_buffer->yy_buf_size;
+
+		if ( yy_cp < yy_current_buffer->yy_ch_buf + 2 )
+			YY_FATAL_ERROR( "flex scanner push-back overflow" );
+		}
+
+	*--yy_cp = (char) c;
+
+
+	yytext_ptr = yy_bp;
+	yy_hold_char = *yy_cp;
+	yy_c_buf_p = yy_cp;
+	}
+#endif	/* ifndef YY_NO_UNPUT */
+
+
+#ifdef __cplusplus
+static int yyinput()
+#else
+static int input()
+#endif
+	{
+	int c;
+
+	*yy_c_buf_p = yy_hold_char;
+
+	if ( *yy_c_buf_p == YY_END_OF_BUFFER_CHAR )
+		{
+		/* yy_c_buf_p now points to the character we want to return.
+		 * If this occurs *before* the EOB characters, then it's a
+		 * valid NUL; if not, then we've hit the end of the buffer.
+		 */
+		if ( yy_c_buf_p < &yy_current_buffer->yy_ch_buf[yy_n_chars] )
+			/* This was really a NUL. */
+			*yy_c_buf_p = '\0';
+
+		else
+			{ /* need more input */
+			int offset = yy_c_buf_p - yytext_ptr;
+			++yy_c_buf_p;
+
+			switch ( yy_get_next_buffer() )
+				{
+				case EOB_ACT_LAST_MATCH:
+					/* This happens because yy_g_n_b()
+					 * sees that we've accumulated a
+					 * token and flags that we need to
+					 * try matching the token before
+					 * proceeding.  But for input(),
+					 * there's no matching to consider.
+					 * So convert the EOB_ACT_LAST_MATCH
+					 * to EOB_ACT_END_OF_FILE.
+					 */
+
+					/* Reset buffer status. */
+					yyrestart( yyin );
+
+					/* fall through */
+
+				case EOB_ACT_END_OF_FILE:
+					{
+					if ( yywrap() )
+						return EOF;
+
+					if ( ! yy_did_buffer_switch_on_eof )
+						YY_NEW_FILE;
+#ifdef __cplusplus
+					return yyinput();
+#else
+					return input();
+#endif
+					}
+
+				case EOB_ACT_CONTINUE_SCAN:
+					yy_c_buf_p = yytext_ptr + offset;
+					break;
+				}
+			}
+		}
+
+	c = *(unsigned char *) yy_c_buf_p;	/* cast for 8-bit char's */
+	*yy_c_buf_p = '\0';	/* preserve yytext */
+	yy_hold_char = *++yy_c_buf_p;
+
+
+	return c;
+	}
+
+
+#ifdef YY_USE_PROTOS
+void yyrestart( FILE *input_file )
+#else
+void yyrestart( input_file )
+FILE *input_file;
+#endif
+	{
+	if ( ! yy_current_buffer )
+		yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE );
+
+	yy_init_buffer( yy_current_buffer, input_file );
+	yy_load_buffer_state();
+	}
+
+
+#ifdef YY_USE_PROTOS
+void yy_switch_to_buffer( YY_BUFFER_STATE new_buffer )
+#else
+void yy_switch_to_buffer( new_buffer )
+YY_BUFFER_STATE new_buffer;
+#endif
+	{
+	if ( yy_current_buffer == new_buffer )
+		return;
+
+	if ( yy_current_buffer )
+		{
+		/* Flush out information for old buffer. */
+		*yy_c_buf_p = yy_hold_char;
+		yy_current_buffer->yy_buf_pos = yy_c_buf_p;
+		yy_current_buffer->yy_n_chars = yy_n_chars;
+		}
+
+	yy_current_buffer = new_buffer;
+	yy_load_buffer_state();
+
+	/* We don't actually know whether we did this switch during
+	 * EOF (yywrap()) processing, but the only time this flag
+	 * is looked at is after yywrap() is called, so it's safe
+	 * to go ahead and always set it.
+	 */
+	yy_did_buffer_switch_on_eof = 1;
+	}
+
+
+#ifdef YY_USE_PROTOS
+void yy_load_buffer_state( void )
+#else
+void yy_load_buffer_state()
+#endif
+	{
+	yy_n_chars = yy_current_buffer->yy_n_chars;
+	yytext_ptr = yy_c_buf_p = yy_current_buffer->yy_buf_pos;
+	yyin = yy_current_buffer->yy_input_file;
+	yy_hold_char = *yy_c_buf_p;
+	}
+
+
+#ifdef YY_USE_PROTOS
+YY_BUFFER_STATE yy_create_buffer( FILE *file, int size )
+#else
+YY_BUFFER_STATE yy_create_buffer( file, size )
+FILE *file;
+int size;
+#endif
+	{
+	YY_BUFFER_STATE b;
+
+	b = (YY_BUFFER_STATE) yy_flex_alloc( sizeof( struct yy_buffer_state ) );
+	if ( ! b )
+		YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+	b->yy_buf_size = size;
+
+	/* yy_ch_buf has to be 2 characters longer than the size given because
+	 * we need to put in 2 end-of-buffer characters.
+	 */
+	b->yy_ch_buf = (char *) yy_flex_alloc( b->yy_buf_size + 2 );
+	if ( ! b->yy_ch_buf )
+		YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+	b->yy_is_our_buffer = 1;
+
+	yy_init_buffer( b, file );
+
+	return b;
+	}
+
+
+#ifdef YY_USE_PROTOS
+void yy_delete_buffer( YY_BUFFER_STATE b )
+#else
+void yy_delete_buffer( b )
+YY_BUFFER_STATE b;
+#endif
+	{
+	if ( ! b )
+		return;
+
+	if ( b == yy_current_buffer )
+		yy_current_buffer = (YY_BUFFER_STATE) 0;
+
+	if ( b->yy_is_our_buffer )
+		yy_flex_free( (void *) b->yy_ch_buf );
+
+	yy_flex_free( (void *) b );
+	}
+
+
+#ifndef YY_ALWAYS_INTERACTIVE
+#ifndef YY_NEVER_INTERACTIVE
+extern int isatty YY_PROTO(( int ));
+#endif
+#endif
+
+#ifdef YY_USE_PROTOS
+void yy_init_buffer( YY_BUFFER_STATE b, FILE *file )
+#else
+void yy_init_buffer( b, file )
+YY_BUFFER_STATE b;
+FILE *file;
+#endif
+
+
+	{
+	yy_flush_buffer( b );
+
+	b->yy_input_file = file;
+	b->yy_fill_buffer = 1;
+
+#if YY_ALWAYS_INTERACTIVE
+	b->yy_is_interactive = 1;
+#else
+#if YY_NEVER_INTERACTIVE
+	b->yy_is_interactive = 0;
+#else
+	b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0;
+#endif
+#endif
+	}
+
+
+#ifdef YY_USE_PROTOS
+void yy_flush_buffer( YY_BUFFER_STATE b )
+#else
+void yy_flush_buffer( b )
+YY_BUFFER_STATE b;
+#endif
+
+	{
+	if ( ! b )
+		return;
+
+	b->yy_n_chars = 0;
+
+	/* We always need two end-of-buffer characters.  The first causes
+	 * a transition to the end-of-buffer state.  The second causes
+	 * a jam in that state.
+	 */
+	b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR;
+	b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR;
+
+	b->yy_buf_pos = &b->yy_ch_buf[0];
+
+	b->yy_at_bol = 1;
+	b->yy_buffer_status = YY_BUFFER_NEW;
+
+	if ( b == yy_current_buffer )
+		yy_load_buffer_state();
+	}
+
+
+#ifndef YY_NO_SCAN_BUFFER
+#ifdef YY_USE_PROTOS
+YY_BUFFER_STATE yy_scan_buffer( char *base, yy_size_t size )
+#else
+YY_BUFFER_STATE yy_scan_buffer( base, size )
+char *base;
+yy_size_t size;
+#endif
+	{
+	YY_BUFFER_STATE b;
+
+	if ( size < 2 ||
+	     base[size-2] != YY_END_OF_BUFFER_CHAR ||
+	     base[size-1] != YY_END_OF_BUFFER_CHAR )
+		/* They forgot to leave room for the EOB's. */
+		return 0;
+
+	b = (YY_BUFFER_STATE) yy_flex_alloc( sizeof( struct yy_buffer_state ) );
+	if ( ! b )
+		YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" );
+
+	b->yy_buf_size = size - 2;	/* "- 2" to take care of EOB's */
+	b->yy_buf_pos = b->yy_ch_buf = base;
+	b->yy_is_our_buffer = 0;
+	b->yy_input_file = 0;
+	b->yy_n_chars = b->yy_buf_size;
+	b->yy_is_interactive = 0;
+	b->yy_at_bol = 1;
+	b->yy_fill_buffer = 0;
+	b->yy_buffer_status = YY_BUFFER_NEW;
+
+	yy_switch_to_buffer( b );
+
+	return b;
+	}
+#endif
+
+
+#ifndef YY_NO_SCAN_STRING
+#ifdef YY_USE_PROTOS
+YY_BUFFER_STATE yy_scan_string( yyconst char *yy_str )
+#else
+YY_BUFFER_STATE yy_scan_string( yy_str )
+yyconst char *yy_str;
+#endif
+	{
+	int len;
+	for ( len = 0; yy_str[len]; ++len )
+		;
+
+	return yy_scan_bytes( yy_str, len );
+	}
+#endif
+
+
+#ifndef YY_NO_SCAN_BYTES
+#ifdef YY_USE_PROTOS
+YY_BUFFER_STATE yy_scan_bytes( yyconst char *bytes, int len )
+#else
+YY_BUFFER_STATE yy_scan_bytes( bytes, len )
+yyconst char *bytes;
+int len;
+#endif
+	{
+	YY_BUFFER_STATE b;
+	char *buf;
+	yy_size_t n;
+	int i;
+
+	/* Get memory for full buffer, including space for trailing EOB's. */
+	n = len + 2;
+	buf = (char *) yy_flex_alloc( n );
+	if ( ! buf )
+		YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" );
+
+	for ( i = 0; i < len; ++i )
+		buf[i] = bytes[i];
+
+	buf[len] = buf[len+1] = YY_END_OF_BUFFER_CHAR;
+
+	b = yy_scan_buffer( buf, n );
+	if ( ! b )
+		YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" );
+
+	/* It's okay to grow etc. this buffer, and we should throw it
+	 * away when we're done.
+	 */
+	b->yy_is_our_buffer = 1;
+
+	return b;
+	}
+#endif
+
+
+#ifndef YY_NO_PUSH_STATE
+#ifdef YY_USE_PROTOS
+static void yy_push_state( int new_state )
+#else
+static void yy_push_state( new_state )
+int new_state;
+#endif
+	{
+	if ( yy_start_stack_ptr >= yy_start_stack_depth )
+		{
+		yy_size_t new_size;
+
+		yy_start_stack_depth += YY_START_STACK_INCR;
+		new_size = yy_start_stack_depth * sizeof( int );
+
+		if ( ! yy_start_stack )
+			yy_start_stack = (int *) yy_flex_alloc( new_size );
+
+		else
+			yy_start_stack = (int *) yy_flex_realloc(
+					(void *) yy_start_stack, new_size );
+
+		if ( ! yy_start_stack )
+			YY_FATAL_ERROR(
+			"out of memory expanding start-condition stack" );
+		}
+
+	yy_start_stack[yy_start_stack_ptr++] = YY_START;
+
+	BEGIN(new_state);
+	}
+#endif
+
+
+#ifndef YY_NO_POP_STATE
+static void yy_pop_state()
+	{
+	if ( --yy_start_stack_ptr < 0 )
+		YY_FATAL_ERROR( "start-condition stack underflow" );
+
+	BEGIN(yy_start_stack[yy_start_stack_ptr]);
+	}
+#endif
+
+
+#ifndef YY_NO_TOP_STATE
+static int yy_top_state()
+	{
+	return yy_start_stack[yy_start_stack_ptr - 1];
+	}
+#endif
+
+#ifndef YY_EXIT_FAILURE
+#define YY_EXIT_FAILURE 2
+#endif
+
+#ifdef YY_USE_PROTOS
+static void yy_fatal_error( yyconst char msg[] )
+#else
+static void yy_fatal_error( msg )
+char msg[];
+#endif
+	{
+	(void) fprintf( stderr, "%s\n", msg );
+	exit( YY_EXIT_FAILURE );
+	}
+
+
+
+/* Redefine yyless() so it works in section 3 code. */
+
+#undef yyless
+#define yyless(n) \
+	do \
+		{ \
+		/* Undo effects of setting up yytext. */ \
+		yytext[yyleng] = yy_hold_char; \
+		yy_c_buf_p = yytext + n; \
+		yy_hold_char = *yy_c_buf_p; \
+		*yy_c_buf_p = '\0'; \
+		yyleng = n; \
+		} \
+	while ( 0 )
+
+
+/* Internal utility routines. */
+
+#ifndef yytext_ptr
+#ifdef YY_USE_PROTOS
+static void yy_flex_strncpy( char *s1, yyconst char *s2, int n )
+#else
+static void yy_flex_strncpy( s1, s2, n )
+char *s1;
+yyconst char *s2;
+int n;
+#endif
+	{
+	register int i;
+	for ( i = 0; i < n; ++i )
+		s1[i] = s2[i];
+	}
+#endif
+
+#ifdef YY_NEED_STRLEN
+#ifdef YY_USE_PROTOS
+static int yy_flex_strlen( yyconst char *s )
+#else
+static int yy_flex_strlen( s )
+yyconst char *s;
+#endif
+	{
+	register int n;
+	for ( n = 0; s[n]; ++n )
+		;
+
+	return n;
+	}
+#endif
+
+
+#ifdef YY_USE_PROTOS
+static void *yy_flex_alloc( yy_size_t size )
+#else
+static void *yy_flex_alloc( size )
+yy_size_t size;
+#endif
+	{
+	return (void *) malloc( size );
+	}
+
+#ifdef YY_USE_PROTOS
+static void *yy_flex_realloc( void *ptr, yy_size_t size )
+#else
+static void *yy_flex_realloc( ptr, size )
+void *ptr;
+yy_size_t size;
+#endif
+	{
+	/* The cast to (char *) in the following accommodates both
+	 * implementations that use char* generic pointers, and those
+	 * that use void* generic pointers.  It works with the latter
+	 * because both ANSI C and C++ allow castless assignment from
+	 * any pointer type to void*, and deal with argument conversions
+	 * as though doing an assignment.
+	 */
+	return (void *) realloc( (char *) ptr, size );
+	}
+
+#ifdef YY_USE_PROTOS
+static void yy_flex_free( void *ptr )
+#else
+static void yy_flex_free( ptr )
+void *ptr;
+#endif
+	{
+	free( ptr );
+	}
+
+#if YY_MAIN
+int main()
+	{
+	yylex();
+	return 0;
+	}
+#endif
+#line 143 "thinkjettopbm.l"
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "debug",      OPT_FLAG, NULL, &cmdlineP->debug,    0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 > 1)
+        pm_error("Too many parameters.  There is at most one: the input "
+                 "file specification.  You specified %d", argc-1);
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+/*
+ * Application entry point
+ */
+int
+main (int argc, char **argv)
+{
+    struct cmdlineInfo cmdline;
+
+    pbm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    if (strlen(cmdline.inputFilespec) > 0) {
+        FILE * ifP;
+        ifP = freopen(cmdline.inputFilespec, "rb", stdin);
+        if (ifP == NULL)
+            pm_error("Unable to open file '%s' as stdin.  errno=%d (%s)", 
+                     cmdline.inputFilespec, errno, strerror(errno));
+    }
+    debugFlag = cmdline.debug;
+    yylex ();
+    return 0;
+}
+
+/*
+ * Finish at end of file
+ */
+static int 
+yywrap (void)
+{
+    int row;
+    unsigned char * packed_bitrow;
+
+    debug ("Got %d rows, %d columns\n", rowCount, maxRowLength);
+
+    /*
+     * Quite simple since ThinkJet bit arrangement matches PBM
+     */
+    pbm_writepbminit(stdout, maxRowLength*8, rowCount, 0);
+
+    packed_bitrow = malloc(maxRowLength);
+    if (packed_bitrow == NULL) pm_error("Out of memory");
+
+    for (row = 0 ; row < rowCount ; row++) {
+        int col;
+        for (col = 0 ; col < rows[row].length ; col++) 
+            packed_bitrow[col] = rows[row].bits[col];
+        for (        ; col < maxRowLength;      col++)
+            packed_bitrow[col] = 0;
+        pbm_writepbmrow_packed(stdout, packed_bitrow, maxRowLength*8, 0);
+    }
+    free(packed_bitrow);
+    return 1;
+}
+
+/*
+ * Print debugging message
+ */
+static void
+debug (const char *format, ...)
+{
+    va_list args;
+
+    if (debugFlag) {
+        fprintf (stderr, "thinkjettopbm: ");
+        va_start (args, format);
+        vfprintf (stderr, format, args);
+        va_end (args);
+    }
+}
+
diff --git a/converter/pbm/thinkjettopbm.l b/converter/pbm/thinkjettopbm.l
new file mode 100644
index 00000000..a66ae07e
--- /dev/null
+++ b/converter/pbm/thinkjettopbm.l
@@ -0,0 +1,251 @@
+%pointer
+/*
+ * A user reported in January 2005 a problem building Thinkjettopbm
+ * in which the opening comment delimiter above for some reason did
+ * not make it into the Lex output in the Netpbm build of this.
+ * Needless to say, that would not compile.  This user was using
+ * 'lex -t' on Tru64.  We did not find it worthwhile to debug it.
+ *
+ * Simple FLEX scanner to convert HP ThinkJet graphics image
+ * to PBM format.
+ *
+ * Implements a small subset of ThinkJet commands.
+ *
+ * Copyright (C) 2001 by W. Eric Norum
+ *
+ * Department of Electrical Engineering
+ * University of Saskatchewan
+ * Saskatoon, Saskatchewan, CANADA
+ * eric.norum@usask.ca
+ *
+ *  Permission to use, copy, modify, and distribute this software and
+ *  its documentation for any purpose and without fee is hereby granted,
+ *  provided that the above copyright notice appear in all copies and
+ *  that both that copyright notice and this permission notice appear in
+ *  supporting documentation.  This software is provided "as is" without
+ *  express or implied warranty.
+ *
+ *  Modified 2001.04.05 by Bryan Henderson for inclusion in the Netpbm
+ *  package.  Now uses Netpbm libraries and, for consistency with other
+ *  Netpbm programs, does not have PGM output option.
+ */
+
+%{
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include "pbm.h"
+#include "shhopt.h"
+
+/* The following macro definitions tell Lex what sort of code to generate.
+   GNU Flex does #if XXX for some of these, as opposed to #ifdef XXX, which
+   means that they properly have to be set to zero instead of just left
+   undefined.  (Simply leaving them undefined typically works anyway, but it
+   is a problem if you use compiler options that say to fail when someone
+   uses a macro he failed to define).
+*/
+#define YY_NO_UNPUT
+#define YY_STACK_USED 0
+#define YY_ALWAYS_INTERACTIVE 0
+#define YY_NEVER_INTERACTIVE 0
+#define YY_MAIN 0
+    /* Don't include the main() function.  We have our own */
+
+static int yylex(void);
+static int yywrap(void);
+/* This works, but generates a warning 
+static void yyrestart(FILE*);
+*/
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+
+    unsigned int debug;
+};
+
+
+
+struct RowInfo {
+    int     length;    /* length, in bytes */
+    char    *bits;     /* Bitmap */
+};
+
+static int maxRowLength;
+static int rowCount;
+static int rowCapacity;
+static struct RowInfo *rows;
+
+static int column;
+
+int debugFlag;
+static void debug (const char *format, ...);
+
+%}
+
+DIG             [0-9]
+
+%x RASTERMODE ROWMODE
+
+%%
+
+<ROWMODE>[\0-\377]      {
+                        rows[rowCount].bits[column++] = yytext[0]; 
+                        if (column >= rows[rowCount].length) {
+                            rowCount++;
+                            debug ("Done %d-byte row %d.\n", column, rowCount);
+                            BEGIN (RASTERMODE);
+                        }
+                        }
+
+<RASTERMODE>\033\*b{DIG}+W  {
+                            int l;
+                            if (rowCount >= rowCapacity) {
+                                rowCapacity += 100;
+                                rows = realloc (rows, rowCapacity * sizeof *rows);
+                                if (rows == NULL)
+                                    pm_error ("Out of memory.");
+                            }
+                            l = atoi (yytext+3);
+                            rows[rowCount].length = l;
+                            rows[rowCount].bits = malloc (l);
+                            if (rows[rowCount].bits == NULL)
+                                pm_error ("Out of memory.");
+                            if (l > maxRowLength)
+                                maxRowLength = l;
+                            debug ("Start %d-byte row.\n", l);
+                            column = 0;
+                            BEGIN (ROWMODE);
+                            }
+
+<RASTERMODE>\033\*rB   {
+                       debug ("Match <esc>*rB\n");
+                       BEGIN (0);
+                       }
+
+<RASTERMODE>[.\0\n]    { pm_error ("Unexpected character (%#x) in raster mode.\n", yytext[0]); }
+
+\033\&l{DIG}+.         { debug ("Match <esc>&l\n"); }
+\033\*r{DIG}+S         { debug ("Match <esc>*r#S\n"); }
+\033\*b{DIG}+W         { debug ("Match <esc>*r#w\n"); }
+\033\*rA               {
+                       debug ("Match <esc>*rA\n");
+                       BEGIN (RASTERMODE);
+                       }
+
+[\0-\377]               { /* Silently consume all other characters */ }
+
+%%
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "debug",      OPT_FLAG, NULL, &cmdlineP->debug,    0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 > 1)
+        pm_error("Too many parameters.  There is at most one: the input "
+                 "file specification.  You specified %d", argc-1);
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+/*
+ * Application entry point
+ */
+int
+main (int argc, char **argv)
+{
+    struct cmdlineInfo cmdline;
+
+    pbm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    if (strlen(cmdline.inputFilespec) > 0) {
+        FILE * ifP;
+        ifP = freopen(cmdline.inputFilespec, "rb", stdin);
+        if (ifP == NULL)
+            pm_error("Unable to open file '%s' as stdin.  errno=%d (%s)", 
+                     cmdline.inputFilespec, errno, strerror(errno));
+    }
+    debugFlag = cmdline.debug;
+    yylex ();
+    return 0;
+}
+
+/*
+ * Finish at end of file
+ */
+static int 
+yywrap (void)
+{
+    int row;
+    unsigned char * packed_bitrow;
+
+    debug ("Got %d rows, %d columns\n", rowCount, maxRowLength);
+
+    /*
+     * Quite simple since ThinkJet bit arrangement matches PBM
+     */
+    pbm_writepbminit(stdout, maxRowLength*8, rowCount, 0);
+
+    packed_bitrow = malloc(maxRowLength);
+    if (packed_bitrow == NULL) pm_error("Out of memory");
+
+    for (row = 0 ; row < rowCount ; row++) {
+        int col;
+        for (col = 0 ; col < rows[row].length ; col++) 
+            packed_bitrow[col] = rows[row].bits[col];
+        for (        ; col < maxRowLength;      col++)
+            packed_bitrow[col] = 0;
+        pbm_writepbmrow_packed(stdout, packed_bitrow, maxRowLength*8, 0);
+    }
+    free(packed_bitrow);
+    return 1;
+}
+
+/*
+ * Print debugging message
+ */
+static void
+debug (const char *format, ...)
+{
+    va_list args;
+
+    if (debugFlag) {
+        fprintf (stderr, "thinkjettopbm: ");
+        va_start (args, format);
+        vfprintf (stderr, format, args);
+        va_end (args);
+    }
+}
+
diff --git a/converter/pbm/wbmptopbm.c b/converter/pbm/wbmptopbm.c
new file mode 100644
index 00000000..a3ce7ec3
--- /dev/null
+++ b/converter/pbm/wbmptopbm.c
@@ -0,0 +1,112 @@
+/* wbmptopbm.c - convert a wbmp file to a portable bitmap
+
+   This is derived for Netpbm from the pbmwbmp package from 
+   <http://www.looplab.com/wap/tools> on 2000.06.06.
+   
+   The specifications for the wbmp format are part of the Wireless 
+   Application Environment specification at
+   <http://www.wapforum.org/what/technical.htm>.
+
+** Copyright (C) 1999 Terje Sannum <terje@looplab.com>.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+
+static int 
+readc(FILE *f) {
+  int c = fgetc(f);
+  if(c == EOF) pm_error("EOF / read error");
+  return c;
+}
+
+
+
+static int 
+readint(FILE *f) {
+  int c=0, pos=0, sum=0;
+  do {
+    c = readc(f);
+    sum = (sum << 7*pos++) | (c & 0x7f);
+  } while(c & 0x80);
+  return sum;
+}
+
+
+
+static void 
+readheader(int h, FILE *f) {
+  int c,i;
+  switch(h & 0x60) {
+  case 0x00:
+    /* Type 00: read multi-byte bitfield */
+    do c=readc(f); while(c & 0x80);
+    break;
+  case 0x60:
+    /* Type 11: read name/value pair */
+    for(i=0; i < ((h & 0x70) >> 4) + (h & 0x0f); i++) c=readc(f);
+    break;
+  }
+}
+
+
+
+static bit **
+readwbmp(FILE *f, int *cols, int *rows) {
+  int i,j,k,row,c;
+  bit **image;
+  /* Type */
+  c = readint(f);
+  if(c != 0) pm_error("Unrecognized WBMP type");
+  /* Headers */
+  c = readc(f); /* FixHeaderField */
+  while(c & 0x80) { /* ExtHeaderFields */
+    c = readc(f);
+    readheader(c, f);
+  }
+  /* Geometry */
+  *cols = readint(f);
+  *rows = readint(f);
+  image = pbm_allocarray(*cols, *rows);
+  /* read image */
+  row = *cols/8;
+  if(*cols%8) row +=1;
+  for(i=0; i<*rows; i++) {
+    for(j=0; j<row; j++) {
+      c=readc(f);
+      for(k=0; k<8 && j*8+k<*cols; k++) {
+	image[i][j*8+k] = c & (0x80 >> k) ? PBM_WHITE : PBM_BLACK;
+      }
+    }
+  }
+  return image;
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+  FILE *f;
+  bit **image;
+  int rows, cols;
+
+  pbm_init(&argc, argv);
+  if(argc > 2) {
+    fprintf(stderr, "Copyright (C) 1999 Terje Sannum <terje@looplab.com>\n");
+    pm_usage("[wbmpfile]");
+  }
+
+  f = argc == 2 ? pm_openr(argv[1]) : stdin;
+  image = readwbmp(f, &cols, &rows);
+  pm_close(f);
+  pbm_writepbm(stdout, image, cols, rows, 0);
+  pm_close(stdout);
+  return 0;
+}
+
diff --git a/converter/pbm/xbmtopbm.c b/converter/pbm/xbmtopbm.c
new file mode 100644
index 00000000..7779a9b5
--- /dev/null
+++ b/converter/pbm/xbmtopbm.c
@@ -0,0 +1,255 @@
+/* xbmtopbm.c - read an X bitmap file and produce a portable bitmap
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "nstring.h"
+#include "pbm.h"
+
+#define TRUE 1
+#define FALSE 0
+
+static void ReadBitmapFile ARGS(( FILE* stream, int* widthP, int* heightP, char** dataP ));
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    bit* bitrow;
+    register bit* bP;
+    int rows, cols, row, col, charcount;
+    char* data;
+    char mask;
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+        pm_usage( "[bitmapfile]" );
+    
+    if ( argc == 2 )
+        ifp = pm_openr( argv[1] );
+    else
+        ifp = stdin;
+
+    ReadBitmapFile( ifp, &cols, &rows, &data );
+
+    pm_close( ifp );
+
+    pbm_writepbminit( stdout, cols, rows, 0 );
+    bitrow = pbm_allocrow( cols );
+
+    for ( row = 0; row < rows; ++row )
+        {
+        charcount = 0;
+        mask = 1;
+        for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
+            {
+            if ( charcount >= 8 )
+                {
+                ++data;
+                charcount = 0;
+                mask = 1;
+                }
+            *bP = ( *data & mask ) ? PBM_BLACK : PBM_WHITE;
+            ++charcount;
+            mask = mask << 1;
+            }
+        ++data;
+        pbm_writepbmrow( stdout, bitrow, cols, 0 );
+        }
+
+    pm_close( stdout );
+    exit( 0 );
+    }
+
+#define MAX_LINE 500
+
+static void
+ReadBitmapFile( stream, widthP, heightP, dataP )
+     FILE* stream;
+     int* widthP;
+     int* heightP;
+     char** dataP;
+{
+  char line[MAX_LINE], name_and_type[MAX_LINE];
+  char* ptr;
+  char* t;
+  int version10, raster_length, v;
+  register int bytes, bytes_per_line, padding;
+  register int c1, c2, value1, value2;
+  int hex_table[256];
+  int found_declaration;
+  /* In scanning through the bitmap file, we have found the first
+     line of the C declaration of the array (the "static char ..."
+     or whatever line)
+     */
+  int eof;
+  /* We've encountered end of file while searching file */
+
+  *widthP = *heightP = -1;
+
+  found_declaration = FALSE;    /* Haven't found it yet; haven't even looked*/
+  eof = FALSE;                  /* Haven't encountered end of file yet */
+
+  while (!found_declaration && !eof) {
+    if ( fgets( line, MAX_LINE, stream ) == NULL )
+      eof = TRUE;
+    else {
+      if ( strlen( line ) == MAX_LINE - 1 )
+        pm_error( "line too long" );
+
+      if ( sscanf( line, "#define %s %d", name_and_type, &v ) == 2 ) {
+        if ( ( t = strrchr( name_and_type, '_' ) ) == NULL )
+          t = name_and_type;
+        else
+          ++t;
+        if ( STREQ( "width", t ) )
+          *widthP = v;
+        else if ( STREQ( "height", t ) )
+          *heightP = v;
+        continue;
+      }
+        
+      if ( sscanf( line, "static short %s = {", name_and_type ) == 1 ) {
+        version10 = TRUE;
+        found_declaration = TRUE;
+      }
+      else if ( sscanf( line, "static char %s = {", name_and_type ) == 1 ) {
+        version10 = FALSE;
+        found_declaration = TRUE;
+      }
+      else if (sscanf(line, 
+                      "static unsigned char %s = {", name_and_type ) == 1 ) {
+        version10 = FALSE;
+        found_declaration = TRUE;
+      }
+    }
+  }
+ 
+  if (!found_declaration) 
+    pm_error("Unable to find a line in the file containing the start "
+             "of C array declaration (\"static char\" or whatever)");
+
+  if ( *widthP == -1 )
+    pm_error( "invalid width" );
+  if ( *heightP == -1 )
+    pm_error( "invalid height" );
+
+  padding = 0;
+  if ( ((*widthP % 16) >= 1) && ((*widthP % 16) <= 8) && version10 )
+    padding = 1;
+
+  bytes_per_line = (*widthP+7)/8 + padding;
+    
+  raster_length =  bytes_per_line * *heightP;
+  *dataP = (char*) malloc( raster_length );
+  if ( *dataP == (char*) 0 )
+    pm_error( "out of memory" );
+
+  /* Initialize hex_table. */
+  for ( c1 = 0; c1 < 256; ++c1 )
+    hex_table[c1] = 256;
+  hex_table['0'] = 0;
+  hex_table['1'] = 1;
+  hex_table['2'] = 2;
+  hex_table['3'] = 3;
+  hex_table['4'] = 4;
+  hex_table['5'] = 5;
+  hex_table['6'] = 6;
+  hex_table['7'] = 7;
+  hex_table['8'] = 8;
+  hex_table['9'] = 9;
+  hex_table['A'] = 10;
+  hex_table['B'] = 11;
+  hex_table['C'] = 12;
+  hex_table['D'] = 13;
+  hex_table['E'] = 14;
+  hex_table['F'] = 15;
+  hex_table['a'] = 10;
+  hex_table['b'] = 11;
+  hex_table['c'] = 12;
+  hex_table['d'] = 13;
+  hex_table['e'] = 14;
+  hex_table['f'] = 15;
+
+  if ( version10 )
+    for ( bytes = 0, ptr = *dataP; bytes < raster_length; bytes += 2 ) {
+      while ( ( c1 = getc( stream ) ) != 'x' )
+        if ( c1 == EOF )
+          pm_error( "EOF / read error" );
+      c1 = getc( stream );
+      c2 = getc( stream );
+      if ( c1 == EOF || c2 == EOF )
+        pm_error( "EOF / read error" );
+      value1 = ( hex_table[c1] << 4 ) + hex_table[c2];
+      if ( value1 >= 256 )
+        pm_error( "syntax error" );
+      c1 = getc( stream );
+      c2 = getc( stream );
+      if ( c1 == EOF || c2 == EOF )
+        pm_error( "EOF / read error" );
+      value2 = ( hex_table[c1] << 4 ) + hex_table[c2];
+      if ( value2 >= 256 )
+        pm_error( "syntax error" );
+      *ptr++ = value2;
+      if ( ( ! padding ) || ( ( bytes + 2 ) % bytes_per_line ) )
+        *ptr++ = value1;
+    }
+  else
+    for ( bytes = 0, ptr = *dataP; bytes < raster_length; ++bytes ) {
+      /*
+       ** Skip until digit is found.
+       */
+      for ( ; ; )
+        {
+          c1 = getc( stream );
+          if ( c1 == EOF )
+            pm_error( "EOF / read error" );
+          value1 = hex_table[c1];
+          if ( value1 != 256 )
+            break;
+        }
+      /*
+       ** Loop on digits.
+       */
+      for ( ; ; ) {
+        c2 = getc( stream );
+        if ( c2 == EOF )
+          pm_error( "EOF / read error" );
+        value2 = hex_table[c2];
+        if ( value2 != 256 ) {
+          value1 = (value1 << 4) | value2;
+          if ( value1 >= 256 )
+            pm_error( "syntax error" );
+        }
+        else if ( c2 == 'x' || c2 == 'X' )
+          if ( value1 == 0 )
+            continue;
+          else pm_error( "syntax error" );
+        else break;
+      }
+      *ptr++ = value1;
+    }
+}
+
+
+/*  CHANGE HISTORY:
+
+  99.09.08 bryanh    Recognize "static unsigned char" declaration.
+
+
+
+
+
+*/
diff --git a/converter/pbm/ybmtopbm.c b/converter/pbm/ybmtopbm.c
new file mode 100644
index 00000000..739e168a
--- /dev/null
+++ b/converter/pbm/ybmtopbm.c
@@ -0,0 +1,113 @@
+/* ybmtopbm.c - read a file from Bennet Yee's 'xbm' program and write a pbm.
+**
+** Written by Jamie Zawinski based on code (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <stdio.h>
+#include "pbm.h"
+
+static void getinit ARGS(( FILE* file, short* colsP, short* rowsP, short* depthP, short* padrightP ));
+static bit getbit ARGS(( FILE* file ));
+
+#define YBM_MAGIC  ( ( '!' << 8 ) | '!' )
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    bit* bitrow;
+    register bit* bP;
+    short rows, cols, padright, row, col;
+    short depth;
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[ybmfile]" );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    getinit( ifp, &cols, &rows, &depth, &padright );
+    if ( depth != 1 )
+	pm_error(
+	    "YBM file has depth of %d, must be 1",
+	    (int) depth );
+
+    pbm_writepbminit( stdout, cols, rows, 0 );
+    bitrow = pbm_allocrow( cols );
+
+    for ( row = 0; row < rows; ++row )
+	{
+	/* Get data. */
+        for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
+	    *bP = getbit( ifp );
+	/* Discard line padding */
+        for ( col = 0; col < padright; ++col )
+	    (void) getbit( ifp );
+	pbm_writepbmrow( stdout, bitrow, cols, 0 );
+	}
+
+    pm_close( ifp );
+    pm_close( stdout );
+
+    exit( 0 );
+    }
+
+static int item;
+static int bitsperitem, bitshift;
+
+static void
+getinit( file, colsP, rowsP, depthP, padrightP )
+    FILE* file;
+    short* colsP;
+    short* rowsP;
+    short* depthP;
+    short* padrightP;
+    {
+    short magic;
+
+    if ( pm_readbigshort( file, &magic ) == -1 )
+	pm_error( "EOF / read error" );
+    if ( magic != YBM_MAGIC )
+	pm_error( "bad magic number in YBM file" );
+    if ( pm_readbigshort( file, colsP ) == -1 )
+	pm_error( "EOF / read error" );
+      if ( pm_readbigshort( file, rowsP ) == -1 )
+	pm_error( "EOF / read error" );
+
+    *depthP = 1;
+    *padrightP = ( ( *colsP + 15 ) / 16 ) * 16 - *colsP;
+    bitsperitem = 0;
+    }
+
+static bit
+getbit( file )
+    FILE* file;
+    {
+    bit b;
+
+    if ( bitsperitem == 0 )
+	{
+	item = getc(file) | getc(file)<<8;
+	if ( item == EOF )
+	    pm_error( "EOF / read error" );
+	bitsperitem = 16;
+	bitshift = 0;
+	}
+    b = ( ( item >> bitshift) & 1 ) ? PBM_BLACK : PBM_WHITE;
+    --bitsperitem;
+    ++bitshift;
+    return b;
+    }
diff --git a/converter/pgm/Makefile b/converter/pgm/Makefile
new file mode 100644
index 00000000..f562fe92
--- /dev/null
+++ b/converter/pgm/Makefile
@@ -0,0 +1,23 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/pgm
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+PORTBINARIES =	asciitopgm bioradtopgm fstopgm hipstopgm \
+		lispmtopgm pgmtofs pgmtolispm pgmtopgm \
+	        psidtopgm spottopgm sbigtopgm
+MATHBINARIES =	rawtopgm
+BINARIES =	$(PORTBINARIES) $(MATHBINARIES)
+
+OBJECTS = $(BINARIES:%=%.o)
+MERGEBINARIES = $(BINARIES)
+MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2)
+
+.PHONY: all
+all: $(BINARIES)
+
+include $(SRCDIR)/Makefile.common
diff --git a/converter/pgm/asciitopgm.c b/converter/pgm/asciitopgm.c
new file mode 100644
index 00000000..b6b9c9a7
--- /dev/null
+++ b/converter/pgm/asciitopgm.c
@@ -0,0 +1,163 @@
+/* asciitopgm.c - read an ASCII graphics file and produce a portable graymap
+**
+** Copyright (C) 1989 by Wilson H. Bent, Jr
+**
+** - Based on fstopgm.c and other works which bear the following notice:
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "pgm.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+static char gmap [128] = {
+/*00 nul-bel*/  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/*08 bs -si */  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/*10 dle-etb*/  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/*18 can-us */  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/*20 sp - ' */  0x00, 0x21, 0x1b, 0x7f, 0x70, 0x25, 0x20, 0x0a,
+/*28  ( - / */  0x11, 0x11, 0x2a, 0x2b, 0x0b, 0x13, 0x04, 0x10,
+/*30  0 - 7 */  0x30, 0x28, 0x32, 0x68, 0x39, 0x35, 0x39, 0x16,
+/*38  8 - ? */  0x38, 0x39, 0x14, 0x15, 0x11, 0x1c, 0x11, 0x3f,
+/*40  @ - G */  0x40, 0x49, 0x52, 0x18, 0x44, 0x3c, 0x38, 0x38,
+/*48  H - O */  0x55, 0x28, 0x2a, 0x70, 0x16, 0x7f, 0x70, 0x14,
+/*50  P - W */  0x60, 0x20, 0x62, 0x53, 0x1a, 0x55, 0x36, 0x57,
+/*58  X - _ */  0x50, 0x4c, 0x5a, 0x24, 0x10, 0x24, 0x5e, 0x13,
+/*60  ` - g */  0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+/*68  h - o */  0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x2a,
+/*70  p - w */  0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+/*78  x -del*/  0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+};
+
+static gray maxval = 127;
+
+int
+main( argc, argv )
+    int argc;
+    char *argv[];
+{
+    FILE *ifd;
+    register gray **grays;
+    int argn, row;
+    register int c, i;
+    int rows = 0, cols = 0;
+    int divisor = 1;
+    bool warned;
+    int *obuf;
+    const char * const usage = "[-d <val>] height width [asciifile]";
+
+    pgm_init( &argc, argv );
+
+    warned = FALSE;
+
+    argn = 1;
+
+    if ( argc < 3 || argc > 6 )
+        pm_usage( usage );
+
+    if ( argv[argn][0] == '-' )
+    {
+        if ( STREQ( argv[argn], "-d" ) )
+        {
+            if ( argc == argn + 1 )
+                pm_usage( usage );
+            if ( sscanf( argv[argn+1], "%d", &divisor ) != 1 )
+                pm_usage( usage );
+            argn += 2;
+        }
+        else
+            pm_usage( usage );
+    }
+
+    if ( sscanf( argv[argn++], "%d", &rows ) != 1 )
+        pm_usage( usage );
+    if ( sscanf( argv[argn++], "%d", &cols ) != 1 )
+        pm_usage( usage );
+    if ( rows < 1 )
+        pm_error( "height is less than 1" );
+    if ( cols < 1 )
+        pm_error( "width is less than 1" );
+
+    if ( argc > argn + 1 )
+        pm_usage( usage );
+
+    if ( argc == argn + 1 )
+        ifd = pm_openr( argv[argn] );
+    else
+        ifd = stdin;
+
+    /* Here's where the work is done:
+     * - Usually, the graymap value of the input char is summed into grays
+     * - For a 'normal' newline, the current row is adjusted by the divisor
+     *   and the current row is incremented
+     * - If the first char in the input line is a '+', then the current row
+     *   stays the same to allow 'overstriking'
+     * NOTE that we assume the user specified a sufficiently large width!
+     */
+    MALLOCARRAY( obuf, cols );
+    if ( obuf == NULL )
+        pm_error( "Unable to allocate memory for %d columns.", cols);
+    else {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) obuf[col] = 0;
+    }
+    grays = pgm_allocarray( cols, rows );
+    row = i = 0;
+    while ( row < rows )
+    {
+        switch (c = getc (ifd))
+        {
+        case EOF:
+            goto line_done;
+        case '\n':
+            if ((c = getc (ifd)) == EOF)
+                goto line_done;
+            if (c == '+')
+                i = 0;
+            else
+            {
+            line_done:
+                for (i = 0; i < cols; ++i)
+                    grays[row][i] = maxval - (obuf[i] / divisor);
+                {
+                    unsigned int col;
+                    for (col = 0; col < cols; ++col) obuf[col] = 0;
+                }
+                i = 0;
+                ++row;
+                if ( row >= rows )
+                    break;
+                if (c != EOF)
+                    obuf[i++] += gmap[c];
+            }
+            break;
+        default:
+            if (c > 0x7f)       /* !isascii(c) */
+            {
+                if (!warned)
+                {
+                    pm_message("Warning: non-ASCII char(s) in input");
+                    warned = TRUE;
+                }
+                c &= 0x7f;      /* toascii(c) */
+            }
+            obuf[i++] += gmap[c];
+            break;
+        }
+    }
+    pm_close( ifd );
+
+    pgm_writepgm( stdout, grays, cols, rows, maxval, 0 );
+
+    return 0;
+}
diff --git a/converter/pgm/bioradtopgm.c b/converter/pgm/bioradtopgm.c
new file mode 100644
index 00000000..e0bc3584
--- /dev/null
+++ b/converter/pgm/bioradtopgm.c
@@ -0,0 +1,152 @@
+/* bioradtopgm.c - convert a Biorad confocal image into a portable graymap
+**
+** Copyright (C) 1993 by Oliver Trepte, oliver@fysik4.kth.se
+**
+** Derived from the pbmplus package,
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <ctype.h>
+
+#include "pgm.h"
+#include "nstring.h"
+
+#define BYTE unsigned char
+#define BIORAD_HEADER_LENGTH 76
+#define BYTE_TO_WORD(lsb,msb) (((BYTE) lsb) + (((BYTE) msb) << 8))
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    gray* grayrow;
+    register gray* gP;
+    int argn, row, i;
+    register int col, val, val2;
+    int rows=0, cols=0, image_num= -1, image_count, byte_word, check_word;
+    int maxval;
+    BYTE buf[BIORAD_HEADER_LENGTH];
+    const char* const usage = "[-image#] [Bioradfile]";
+
+
+    pgm_init( &argc, argv );
+
+    argn = 1;
+
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+    {
+	if ( ISDIGIT( argv[argn][1] ))
+	{
+	    image_num = atoi( (argv[argn]+1) );
+	}
+	else
+	    pm_usage( usage );
+	++argn;
+    }
+
+    if ( argn < argc )
+	{
+	ifp = pm_openr( argv[argn] );
+	++argn;
+	}
+    else
+	ifp = stdin;
+
+    if ( argn != argc )
+	pm_usage( usage );
+
+    for ( i = 0; i < BIORAD_HEADER_LENGTH; ++i )
+    {
+	val = getc( ifp );
+	if ( val == EOF )
+	    pm_error( "EOF / read error" );
+	buf[ i ] = val;
+    }
+
+    cols = BYTE_TO_WORD(buf[0], buf[1]);
+    rows = BYTE_TO_WORD(buf[2], buf[3]);
+    image_count = BYTE_TO_WORD(buf[4], buf[5]);
+    byte_word = BYTE_TO_WORD(buf[14], buf[15]);
+    check_word = BYTE_TO_WORD(buf[54], buf[55]);
+
+    if ( check_word != 12345 )
+	pm_error( "Not a Biorad file" );
+
+    if ( cols <= 0 )
+	pm_error( "Strange image size, cols = %d", cols);
+
+    if ( rows <= 0 )
+	pm_error( "Strange image size, rows = %d", rows);
+
+    if ( image_count <= 0 )
+	pm_error( "Number of images in file is %d", image_count);
+
+    if ( byte_word )
+	maxval = 255;
+    else
+    {
+	maxval = 65535;   /* Perhaps this should be something else */
+
+    }
+
+    pm_message( "Image size: %d cols, %d rows", cols, rows);
+    pm_message( "%s",
+	       (byte_word) ? "Byte image (8 bits)" : "Word image (16 bits)");
+
+    if ( image_num < 0 )
+	pm_message( "Input contains %d image%c",
+		   image_count, (image_count > 1) ? 's' : '\0');
+    else
+    {
+	if ( image_num >= image_count )
+	    pm_error( "Cannot extract image %d, input contains only %d image%s",
+		     image_num, image_count, (image_count > 1) ? "s" : "" );
+	for ( i = (byte_word) ? image_num : image_num*2 ; i > 0 ; --i ) {
+	    for ( row = 0; row < rows; ++row)
+		for ( col = 0; col < cols; ++col )
+		{
+		    val = getc( ifp );
+		    if ( val == EOF ) {
+			pm_error( "EOF / read error" );
+		    }
+		}
+	}
+
+	pgm_writepgminit( stdout, cols, rows, (gray) maxval, 0 );
+	grayrow = pgm_allocrow( cols );
+
+	for ( row = 0; row < rows; ++row)
+	{
+	    for ( col = 0, gP = grayrow; col < cols; ++col )
+	    {
+		val = getc( ifp );
+		if ( val == EOF )
+		    pm_error( "EOF / read error" );
+		if (byte_word)
+		    *gP++ = val;
+		else
+		{
+		    val2 = getc( ifp );
+		    if ( val2 == EOF )
+			pm_error( "EOF / read error" );
+		    *gP++ = BYTE_TO_WORD(val, val2);
+		}
+	    }
+	    pgm_writepgmrow( stdout, grayrow, cols, (gray) maxval, 0 );
+	}
+
+	pm_close( ifp );
+	pm_close( stdout );
+
+    }
+    exit( 0 );
+}
diff --git a/converter/pgm/fstopgm.c b/converter/pgm/fstopgm.c
new file mode 100644
index 00000000..2c636f41
--- /dev/null
+++ b/converter/pgm/fstopgm.c
@@ -0,0 +1,138 @@
+/* fstopgm.c - read a Usenix FaceSaver(tm) file and produce a portable graymap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "pgm.h"
+
+static int gethexit ARGS(( FILE* ifp ));
+
+int
+main( argc, argv )
+int argc;
+char *argv[];
+    {
+    FILE *ifp;
+    register gray **grays, *gP;
+    int argn, row;
+    register int col;
+    int maxval;
+    int rows = 0, cols = 0, depth = 0, xrows = 0, xcols = 0, xdepth = 0;
+#define STRSIZE 1000
+    char buf[STRSIZE], firstname[STRSIZE], lastname[STRSIZE], email[STRSIZE];
+
+
+    pgm_init( &argc, argv );
+
+    argn = 1;
+
+    if ( argn < argc )
+	{
+	ifp = pm_openr( argv[argn] );
+	argn++;
+	}
+    else
+	ifp = stdin;
+
+    if ( argn != argc )
+	pm_usage( "[fsfile]" );
+
+    /* Read the FaceSaver(tm) header. */
+    for ( ; ; )
+	{
+	if ( fgets( buf, STRSIZE, ifp ) == (char *) 0 )
+	    pm_error( "error reading header" );
+
+	/* Blank line ends header. */
+	if ( strlen( buf ) == 1 )
+	    break;
+
+	if ( sscanf( buf, "FirstName: %[^\n]", firstname ) == 1 )
+	    ;
+	else if ( sscanf( buf, "LastName: %[^\n]", lastname ) == 1 )
+	    ;
+	else if ( sscanf( buf, "E-mail: %[^\n]", email ) == 1 )
+	    ;
+	else if ( sscanf( buf, "PicData: %d %d %d\n",
+			  &cols, &rows, &depth ) == 3 )
+	    {
+	    if ( depth != 8 )
+		pm_error(
+		    "can't handle 'PicData' depth other than 8" );
+	    }
+	else if ( sscanf( buf, "Image: %d %d %d\n",
+			  &xcols, &xrows, &xdepth ) == 3 )
+	    {
+	    if ( xdepth != 8 )
+		pm_error(
+		    "can't handle 'Image' depth other than 8" );
+	    }
+	}
+    if ( cols <= 0 || rows <= 0 )
+	pm_error( "invalid header" );
+    maxval = pm_bitstomaxval( depth );
+    if ( maxval > PGM_OVERALLMAXVAL )
+	pm_error( "depth %d is too large.  Our maximum is %d",
+              maxval, PGM_OVERALLMAXVAL);
+    if ( xcols != 0 && xrows != 0 && ( xcols != cols || xrows != rows ) )
+	{
+	float rowratio, colratio;
+
+	rowratio = (float) xrows / (float) rows;
+	colratio = (float) xcols / (float) cols;
+	pm_message(
+	    "warning, non-square pixels; to fix do a 'pamscale -%cscale %g'",
+	    rowratio > colratio ? 'y' : 'x',
+	    rowratio > colratio ? rowratio / colratio : colratio / rowratio );
+	}
+
+    /* Now read the hex bits. */
+    grays = pgm_allocarray( cols, rows );
+    for ( row = rows - 1; row >= 0; row--)
+	{
+	for ( col = 0, gP = grays[row]; col < cols; col++, gP++ )
+	    {
+	    *gP = gethexit( ifp ) << 4;
+	    *gP += gethexit( ifp );
+	    }
+	}
+    pm_close( ifp );
+
+    /* And write out the graymap. */
+    pgm_writepgm( stdout, grays, cols, rows, (gray) maxval, 0 );
+    pm_close( stdout );
+
+    exit( 0 );
+    }
+
+static int
+gethexit( ifp )
+FILE *ifp;
+    {
+    register int i;
+    register char c;
+
+    for ( ; ; )
+	{
+	i = getc( ifp );
+	if ( i == EOF )
+	    pm_error( "EOF / read error" );
+	c = (char) i;
+	if ( c >= '0' && c <= '9' )
+	    return c - '0';
+	else if ( c >= 'A' && c <= 'F' )
+	    return c - 'A' + 10;
+	else if ( c >= 'a' && c <= 'f' )
+	    return c - 'a' + 10;
+	/* Else ignore - whitespace. */
+	}
+    }
diff --git a/converter/pgm/hipstopgm.c b/converter/pgm/hipstopgm.c
new file mode 100644
index 00000000..826a8511
--- /dev/null
+++ b/converter/pgm/hipstopgm.c
@@ -0,0 +1,181 @@
+/* hipstopgm.c - read a HIPS file and produce a portable graymap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "nstring.h"
+#include "pgm.h"
+
+struct HIPS_Header {
+    char* orig_name;	/* An indication of the originator of this sequence. */
+    char* seq_name;	/* The sequence name. */
+    int num_frame;	/* The number of frames in this sequence. */
+    char* orig_date;	/* The date the sequence was originated. */
+    int rows;		/* The number of rows in each image, the height. */
+    int cols;		/* The number of columns in each image, the width. */
+    int bits_per_pixel;	/* The number of significant bits per pixel. */
+    int bit_packing;	/* Nonzero if the bits were packed such as to
+                           eliminate any unused bits resulting from a
+                           bits_per_pixel value which was not an even
+                           multiple of eight. */
+    int pixel_format;	/* An indication of the format of each pixel. */
+    char* seq_history;	/* A description of the sequence of transformations
+                           leading up to the current image. */
+    char* seq_desc;	/* A free form description of the contents of the
+                       sequence. */
+};
+#define HIPS_PFBYTE 0
+#define HIPS_PFSHORT 1
+#define HIPS_PFINT 2
+#define HIPS_PFFLOAT 3
+#define HIPS_PFCOMPLEX 4
+
+
+
+static void
+read_line(FILE * const fd,
+          char * const buf,
+          int    const size) {
+
+    if (fgets( buf, size, fd ) == NULL)
+        pm_error("error reading header");
+}
+
+
+
+
+static void
+read_hips_header( fd, hP )
+    FILE* fd;
+    struct HIPS_Header* hP;
+{
+    char buf[5000];
+
+    /* Read and toss orig_name. */
+    read_line( fd, buf, 5000 );
+
+    /* Read and toss seq_name. */
+    read_line( fd, buf, 5000 );
+
+    /* Read num_frame. */
+    read_line( fd, buf, 5000 );
+    hP->num_frame = atoi( buf );
+
+    /* Read and toss orig_date. */
+    read_line( fd, buf, 5000 );
+
+    /* Read rows. */
+    read_line( fd, buf, 5000 );
+    hP->rows = atoi( buf );
+
+    /* Read cols. */
+    read_line( fd, buf, 5000 );
+    hP->cols = atoi( buf );
+
+    /* Read bits_per_pixel. */
+    read_line( fd, buf, 5000 );
+    hP->bits_per_pixel = atoi( buf );
+
+    /* Read bit_packing. */
+    read_line( fd, buf, 5000 );
+    hP->bit_packing = atoi( buf );
+
+    /* Read pixel_format. */
+    read_line( fd, buf, 5000 );
+    hP->pixel_format = atoi( buf );
+
+    /* Now read and toss lines until we get one with just a period. */
+    do
+	{
+        read_line( fd, buf, 5000 );
+	}
+    while ( !STREQ( buf, ".\n" ) );
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    FILE* ifp;
+    gray* grayrow;
+    register gray* gP;
+    int argn, row;
+    register int col;
+    int maxval;
+    int rows, cols;
+    struct HIPS_Header h;
+
+
+    pgm_init( &argc, argv );
+
+    argn = 1;
+
+    if ( argn < argc )
+	{
+        ifp = pm_openr( argv[argn] );
+        argn++;
+	}
+    else
+        ifp = stdin;
+
+    if ( argn != argc )
+        pm_usage( "[hipsfile]" );
+
+    read_hips_header( ifp, &h );
+
+    cols = h.cols;
+    rows = h.rows * h.num_frame;
+
+    switch ( h.pixel_format )
+	{
+	case HIPS_PFBYTE:
+        if ( h.bits_per_pixel != 8 )
+            pm_error(
+                "can't handle unusual bits_per_pixel %d", h.bits_per_pixel );
+        if ( h.bit_packing != 0 )
+            pm_error( "can't handle bit_packing" );
+        maxval = 255;
+        break;
+
+	default:
+        pm_error( "unknown pixel format %d", h.pixel_format );
+	}
+
+    pgm_writepgminit( stdout, cols, rows, (gray) maxval, 0 );
+    grayrow = pgm_allocrow( cols );
+    for ( row = 0; row < rows; row++)
+	{
+        for ( col = 0, gP = grayrow; col < cols; col++, gP++ )
+	    {
+            int ich;
+
+            switch ( h.pixel_format )
+            {
+            case HIPS_PFBYTE:
+                ich = getc( ifp );
+                if ( ich == EOF )
+                    pm_error( "EOF / read error" );
+                *gP = (gray) ich;
+                break;
+
+            default:
+                pm_error( "can't happen" );
+            }
+	    }
+        pgm_writepgmrow( stdout, grayrow, cols, (gray) maxval, 0 );
+	}
+    pm_close( ifp );
+    pm_close( stdout );
+
+    return 0;
+}
diff --git a/converter/pgm/lispmtopgm.c b/converter/pgm/lispmtopgm.c
new file mode 100644
index 00000000..7b98ef00
--- /dev/null
+++ b/converter/pgm/lispmtopgm.c
@@ -0,0 +1,172 @@
+/* lispmtopgm.c - read a file written by the tv:write-bit-array-file function
+** of TI Explorer and Symbolics Lisp Machines, and write a PGM.
+**
+** Written by Jamie Zawinski based on code (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+**   When one writes a multi-plane bitmap with tv:write-bit-array-file, it is
+**   usually a color image; but a color map is not written in the file, so we
+**   treat this as a graymap instead.  Since the pgm reader can also read pbms,
+**   this doesn't matter if you're using only single plane images.
+*/
+
+#include <stdio.h>
+#include <string.h>
+
+#include "nstring.h"
+#include "pgm.h"
+
+#define LISPM_MAGIC  "This is a BitMap file"
+
+static void getinit ARGS(( FILE* file, short* colsP, short* rowsP, short* depthP, short* padrightP ));
+static int depth_to_word_size ARGS(( int depth ));
+static unsigned int getval ARGS(( FILE* file ));
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    gray* grayrow;
+    register gray* gP;
+    short rows, cols, padright, row, col;
+    short depth;
+    int maxval;
+
+
+    pgm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[lispmfile]" );
+
+    if ( argc == 2 )
+        ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    getinit( ifp, &cols, &rows, &depth, &padright );
+    maxval = 1 << depth;
+
+    if ( maxval > PGM_OVERALLMAXVAL )
+        pm_error( "depth (%d bits) is too large", depth);
+
+    pgm_writepgminit( stdout, cols, rows, (gray) maxval, 0 );
+    grayrow = pgm_allocrow( ( cols + 7 ) / 8 * 8 );
+
+    for ( row = 0; row < rows; ++row )
+	{
+        for ( col = 0, gP = grayrow; col < cols; ++col, ++gP )
+	    *gP = getval( ifp );
+	pgm_writepgmrow( stdout, grayrow, cols, (gray) maxval, 0 );
+	}
+    pm_close( ifp );
+    pm_close( stdout );
+    exit( 0 );
+    }
+
+static long item, bitmask;
+static unsigned int bitsperitem, maxbitsperitem, bitshift;
+
+static void
+getinit( file, colsP, rowsP, depthP, padrightP )
+    FILE* file;
+    short* colsP;
+    short* rowsP;
+    short* padrightP;
+    short* depthP;
+    {
+    short cols_32;
+    char magic[sizeof(LISPM_MAGIC)];
+    int i;
+
+    for ( i = 0; i < sizeof(magic)-1; ++i )
+        magic[i] = getc( file );
+    magic[i]='\0';
+    if (!STREQ(LISPM_MAGIC, magic))
+        pm_error( "bad id string in Lispm file" );
+    
+    if ( pm_readlittleshort( file, colsP ) == -1 )
+        pm_error( "EOF / read error" );
+    if ( pm_readlittleshort( file, rowsP ) == -1 )
+        pm_error( "EOF / read error" );
+    if ( pm_readlittleshort( file, &cols_32 ) == -1 )
+        pm_error( "EOF / read error" );
+    *depthP = getc( file );
+    
+    if ( *depthP == 0 )
+	*depthP = 1;	/* very old file */
+    
+    *padrightP = ( ( *colsP + 31 ) / 32 ) * 32 - *colsP;
+    
+    if ( *colsP != (cols_32 - *padrightP) ) {
+/*    pm_message( "inconsistent input: Width and Width(mod32) fields don't agree" );  */
+/*    *padrightP = cols_32 - *colsP;   */ /*    hmmmm....   */
+      /* This is a dilemma.  Usually the output is rounded up to mod32, but not always.
+       * For the Lispm code to not round up, the array size must be the same size as the
+       * portion being written - that is, the array itself must be an odd size, not just
+       * the selected portion.  Since arrays that are odd sizes can't be handed to bitblt,
+       * such arrays are probably not image data - so punt on it for now.
+       *
+       * Also, the lispm code for saving bitmaps has a bug, in that if you are writing a
+       * bitmap which is not mod32 across, the file may be up to 7 bits too short!  They
+       * round down instead of up.
+       *
+       * The code in 'pgmtolispm.c' always rounds up to mod32, which is totally reasonable.
+       */
+      }
+    bitsperitem = 0;
+    maxbitsperitem = depth_to_word_size( *depthP );
+    bitmask = ( 1 << maxbitsperitem ) - 1;		/* for depth=3, mask=00000111 */
+
+    for ( i = 0; i < 9; ++i )
+	getc( file );	/* discard bytes reserved for future use */
+    }
+
+static int
+depth_to_word_size (depth)	
+     int depth;			
+     /* Lispm architecture specific - if a bitmap is written    */
+     /* out with a depth of 5, it really has a depth of 8, and  */
+     /* is stored that way in the file.			   */
+{				
+    if (depth==0 || depth==1)	return ( 1);
+    else if (depth ==  2)	return ( 2);
+    else if (depth <=  4)	return ( 4);
+    else if (depth <=  8)	return ( 8);
+    else if (depth <= 16)	return (16);
+    else if (depth <= 32)	return (32);
+    else {
+      pm_error( "depth was %d, which is not in the range 1-32.", depth );
+      /* Should never reach here */
+      return(-1);
+  }
+}
+
+
+
+static unsigned int
+getval( file )
+    FILE* file;
+    {
+    unsigned int b;
+
+    if ( bitsperitem == 0 )
+	{
+	if ( pm_readlittlelong( file, &item ) == -1 )
+	    pm_error( "EOF / read error" );
+	bitsperitem = 32;
+	bitshift = 0;
+	item = ~item;
+	}
+    b = ( ( item >> bitshift ) & bitmask );
+    bitsperitem = bitsperitem - maxbitsperitem;
+    bitshift = bitshift + maxbitsperitem;
+    return b;
+    }
diff --git a/converter/pgm/pgmtofs.c b/converter/pgm/pgmtofs.c
new file mode 100644
index 00000000..b34d77c4
--- /dev/null
+++ b/converter/pgm/pgmtofs.c
@@ -0,0 +1,153 @@
+/* pgmtofs.c - convert portable graymap to Usenix FaceSaver(tm) format
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pgm.h"
+
+static void putinit ARGS(( int cols, int rows, int bps ));
+static void putitem ARGS(( void ));
+static void putgray ARGS(( gray g ));
+static void putrest ARGS(( void ));
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+{
+    FILE* ifp;
+    gray** grays;
+    register gray* gP;
+    int argn, rows, cols, bps, padright, row, col;
+    gray maxval, nmaxval;
+    const char* const usage = "[pgmfile]";
+
+
+    pgm_init( &argc, argv );
+
+    argn = 1;
+
+    if ( argn < argc )
+	{
+        ifp = pm_openr( argv[argn] );
+        ++argn;
+	}
+    else
+	{
+        ifp = stdin;
+	}
+
+    if ( argn != argc )
+        pm_usage( usage );
+
+    grays = pgm_readpgm( ifp, &cols, &rows, &maxval );
+    pm_close( ifp );
+
+    /* Figure out bps. */
+    bps = pm_maxvaltobits( (int) maxval );
+    if ( bps > 2 && bps < 4 )
+        bps = 4;
+    else if ( bps > 4 && bps < 8 )
+        bps = 8;
+    else if ( bps > 8 )
+        pm_error(
+            "maxval of %d is too large for FaceSaver(tm)", maxval );
+    nmaxval = pm_bitstomaxval( bps );
+    
+    /* Compute padding to round cols * bps up to the nearest multiple of 8. */
+    padright = ( ( cols * bps + 7 ) / 8 ) * 8 - cols * bps;
+
+    putinit( cols, rows, bps );
+    for ( row = rows - 1; row >= 0; --row )
+	{
+        for ( col = 0, gP = grays[row]; col < cols; ++col, ++gP )
+	    {
+            if ( maxval != nmaxval )
+                *gP = (int) *gP * nmaxval / maxval;
+            putgray( *gP );
+	    }
+        for ( col = 0; col < padright; ++col )
+            putgray( 0 );
+    }
+
+    putrest( );
+
+    exit( 0 );
+}
+
+
+static int bitspersample, item, bitsperitem, bitshift, itemsperline, items;
+
+static void
+putinit( cols, rows, bps )
+    int cols, rows, bps;
+{
+    printf( "FirstName: \n" );
+    printf( "LastName: \n" );
+    printf( "E-mail: \n" );
+    printf( "Telephone: \n" );
+    printf( "Company: \n" );
+    printf( "Address1: \n" );
+    printf( "Address2: \n" );
+    printf( "CityStateZip: \n" );
+    printf( "Date: \n" );
+    printf( "PicData: %d %d %d\n", cols, rows, bps );
+    printf( "Image: %d %d %d\n", cols, rows, bps );
+    printf( "\n" );
+
+    bitspersample = bps;
+    itemsperline = items = 0;
+    item = 0;
+    bitsperitem = 0;
+    bitshift = 8 - bitspersample;
+}
+
+static void
+putitem( )
+{
+    const char* const hexits = "0123456789abcdef";
+
+    if ( itemsperline == 30 )
+	{
+        putchar( '\n' );
+        itemsperline = 0;
+	}
+    putchar( hexits[item >> 4] );
+    putchar( hexits[item & 15] );
+    ++itemsperline;
+    ++items;
+    item = 0;
+    bitsperitem = 0;
+    bitshift = 8 - bitspersample;
+}
+
+#if __STDC__
+static void
+putgray( gray g )
+#else /*__STDC__*/
+    static void
+putgray( g )
+    gray g;
+#endif /*__STDC__*/
+{
+    if ( bitsperitem == 8 )
+        putitem( );
+    item += g << bitshift;
+    bitsperitem += bitspersample;
+    bitshift -= bitspersample;
+}
+
+static void
+putrest( )
+{
+    if ( bitsperitem > 0 )
+        putitem( );
+    printf( "\n" );
+}
diff --git a/converter/pgm/pgmtolispm.c b/converter/pgm/pgmtolispm.c
new file mode 100644
index 00000000..02f2fd1e
--- /dev/null
+++ b/converter/pgm/pgmtolispm.c
@@ -0,0 +1,148 @@
+/* pgmtolispm.c - read a pgm and write a file acceptable to the 
+** tv:read-bit-array-file function of TI Explorer and Symbolics Lisp Machines.
+**
+** Written by Jamie Zawinski based on code (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+**   When one writes a multi-plane bitmap with tv:write-bit-array-file, it is
+**   usually a color image; but a color map is not written in the file, so we
+**   treat this as a graymap instead.  To convert a color image to Lispm 
+**   format, you must convert it to a pgm, and hand-edit a color map...  Ick.
+*/
+
+#include <stdio.h>
+#include "pgm.h"
+
+#define LISPM_MAGIC  "This is a BitMap file"
+
+static void putinit ARGS(( int cols, int rows, int depth ));
+static int depth_to_word_size ARGS(( int depth ));
+static void putval ARGS(( gray b ));
+static void putrest ARGS(( void ));
+static void putitem ARGS(( void ));
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    gray *grayrow;
+    register gray* gP;
+    int rows, cols, depth, format, padright, row, col;
+    gray maxval;
+
+
+    pgm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[pgmfile]" );
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    pgm_readpgminit( ifp, &cols, &rows, &maxval, &format );
+    grayrow = pgm_allocrow( cols );
+    depth = pm_maxvaltobits( maxval );
+
+    /* Compute padding to round cols up to the nearest multiple of 32. */
+    padright = ( ( cols + 31 ) / 32 ) * 32 - cols;
+
+    putinit( cols, rows, depth );
+    for ( row = 0; row < rows; ++row )
+	{
+	pgm_readpgmrow( ifp, grayrow, cols, maxval, format );
+        for ( col = 0, gP = grayrow; col < cols; ++col, ++gP )
+	    putval( *gP );
+	for ( col = 0; col < padright; ++col )
+	    putval( 0 );
+        }
+
+    pm_close( ifp );
+
+    putrest( );
+
+    exit( 0 );
+    }
+
+static unsigned int item;
+static unsigned int bitsperitem, maxbitsperitem, bitshift;
+
+static void
+putinit( cols, rows, depth )
+    int cols, rows, depth;
+    {
+    int i;
+    int cols32 = ( ( cols + 31 ) / 32 ) * 32;	/* Lispms are able to write bit files that are not mod32 wide, but we   */
+						/* don't.  This should be ok, since bit arrays which are not mod32 wide */
+    printf(LISPM_MAGIC);			/* are pretty useless on a lispm (can't hand them to bitblt).		*/
+    pm_writelittleshort( stdout, cols );
+    pm_writelittleshort( stdout, rows );
+    pm_writelittleshort( stdout, cols32 );
+    putchar(depth & 0xFF);
+
+    for ( i = 0; i < 9; ++i )
+	putchar( 0 );	/* pad bytes */
+
+    item = 0;
+    bitsperitem = 0;
+    maxbitsperitem = depth_to_word_size( depth );
+    bitshift = 0;
+    }
+
+static int
+depth_to_word_size (depth)	/* Lispm architecture specific - if a bitmap is written    */
+  int depth;			/* out with a depth of 5, it really has a depth of 8, and  */
+{				/* is stored that way in the file.			   */
+    if (depth==0 || depth==1)	return ( 1);
+    else if (depth ==  2)	return ( 2);
+    else if (depth <=  4)	return ( 4);
+    else if (depth <=  8)	return ( 8);
+    else if (depth <= 16)	return (16);
+    else if (depth <= 32)	return (32);
+    else {
+      pm_error( "depth was %d, which is not in the range 1-32", depth );
+      return(-1);  /* Should never reach here */
+  }
+}
+
+
+
+#if __STDC__
+static void
+putval( gray b )
+#else /*__STDC__*/
+static void
+putval( b )
+gray b;
+#endif /*__STDC__*/
+    {
+    if ( bitsperitem == 32 )
+	putitem( );
+    item = item | ( b << bitshift );
+    bitsperitem = bitsperitem + maxbitsperitem;
+    bitshift = bitshift + maxbitsperitem;
+    }
+
+static void
+putrest( )
+    {
+    if ( bitsperitem > 0 )
+	putitem( );
+    }
+
+static void
+putitem( )
+    {
+    pm_writelittlelong( stdout, ~item );
+    item = 0;
+    bitsperitem = 0;
+    bitshift = 0;
+    }
diff --git a/converter/pgm/pgmtopgm.c b/converter/pgm/pgmtopgm.c
new file mode 100644
index 00000000..c65e98e0
--- /dev/null
+++ b/converter/pgm/pgmtopgm.c
@@ -0,0 +1,44 @@
+/*----------------------------------------------------------------------------
+                               pgmtopgm
+------------------------------------------------------------------------------
+  Part of the Netpbm package.
+
+  Copy PGM image from Standard Input to Standard Output
+
+
+  By Bryan Henderson, San Jose CA 2002.09.07
+
+  Contributed to the public domain by its author 2002.09.07
+-----------------------------------------------------------------------------*/
+
+#include "pgm.h"
+
+int
+main(int argc, char *argv[]) {
+    int format;
+    int rows, cols;
+    gray maxval;
+    int row;
+    gray* grayrow;
+    
+    pgm_init(&argc, argv);
+    
+    if (argc-1 != 0)
+        pm_error("Program takes no arguments.  Input is from Standard Input");
+
+    pgm_readpgminit(stdin, &cols, &rows, &maxval, &format);
+
+    pgm_writepgminit(stdout, cols, rows, maxval, 0);
+
+    grayrow = pgm_allocrow(cols);
+
+    for (row = 0; row < rows; row++) {
+        pgm_readpgmrow(stdin, grayrow, cols, maxval, format);
+        pgm_writepgmrow(stdout, grayrow, cols, maxval, 0);
+    }
+    pgm_freerow(grayrow);
+
+    pm_close(stdin);
+
+    return 0;
+}
diff --git a/converter/pgm/psidtopgm.c b/converter/pgm/psidtopgm.c
new file mode 100644
index 00000000..07417d1b
--- /dev/null
+++ b/converter/pgm/psidtopgm.c
@@ -0,0 +1,128 @@
+/* psidtopgm.c - convert PostScript "image" data into a portable graymap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pgm.h"
+
+
+static int
+gethexit(FILE * const ifp) {
+
+    for ( ; ; ) {
+        int const i = getc(ifp);
+        if (i == EOF)
+            pm_error("EOF / read error");
+        else {
+            char const c = (char) i;
+            if (c >= '0' && c <= '9')
+                return c - '0';
+            else if (c >= 'A' && c <= 'F')
+                return c - 'A' + 10;
+            else if (c >= 'a' && c <= 'f')
+                return c - 'a' + 10;
+            /* Else ignore - whitespace. */
+        }
+    }
+}
+
+
+
+int
+main(int     argc,
+     char ** argv) {
+
+    FILE * ifP;
+    gray * grayrow;
+    int argn, row;
+    gray maxval;
+    int rows, cols, bitspersample;
+
+    pgm_init(&argc, argv);
+
+    argn = 1;
+    
+    if (argn + 3 > argc)
+        pm_error("Too few arguments");
+
+    cols = atoi(argv[argn++]);
+    rows = atoi(argv[argn++]);
+    bitspersample = atoi(argv[argn++]);
+    if (cols <= 0)
+        pm_error("Columns must be positive.  You specified %d",
+                 cols);
+    if (rows <= 0)
+        pm_error("Rows must be positive.  You specified %d",
+                 rows);
+    if (bitspersample <= 0)
+        pm_error("Bits per sample must be positive.  You specified %d",
+                 bitspersample);
+
+    if (argn < argc)
+        ifP = pm_openr(argv[argn++]);
+    else
+        ifP = stdin;
+
+    if (argn != argc)
+        pm_error("Too many arguments");
+
+    maxval = pm_bitstomaxval(bitspersample);
+    if (maxval > PGM_OVERALLMAXVAL)
+        pm_error("bits/sample (%d) is too large.", bitspersample);
+
+    pgm_writepgminit(stdout, cols, rows, maxval, 0);
+    grayrow = pgm_allocrow((cols + 7) / 8 * 8);
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cols; ) {
+            int val;
+            val = gethexit(ifP) << 4;
+            val += gethexit(ifP);
+            switch (bitspersample) {
+            case 1:
+                grayrow[col++] = val >> 7;
+                grayrow[col++] = ( val >> 6 ) & 0x1;
+                grayrow[col++] = ( val >> 5 ) & 0x1;
+                grayrow[col++] = ( val >> 4 ) & 0x1;
+                grayrow[col++] = ( val >> 3 ) & 0x1;
+                grayrow[col++] = ( val >> 2 ) & 0x1;
+                grayrow[col++] = ( val >> 1 ) & 0x1;
+                grayrow[col++] = val & 0x1;
+                break;
+
+            case 2:
+                grayrow[col++] = val >> 6;
+                grayrow[col++] = ( val >> 4 ) & 0x3;
+                grayrow[col++] = ( val >> 2 ) & 0x3;
+                grayrow[col++] = val & 0x3;
+                break;
+
+            case 4:
+                grayrow[col++] = val >> 4;
+                grayrow[col++] = val & 0xf;
+                break;
+
+            case 8:
+                grayrow[col++] = val;
+                break;
+
+            default:
+                pm_error("This program does not know how to interpret a"
+                         "%d bitspersample image", bitspersample );
+            }
+        }
+        pgm_writepgmrow(stdout, grayrow, cols, (gray) maxval, 0);
+    }
+    pgm_freerow(grayrow);
+    pm_close(ifP);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/converter/pgm/rawtopgm.c b/converter/pgm/rawtopgm.c
new file mode 100644
index 00000000..c91c6178
--- /dev/null
+++ b/converter/pgm/rawtopgm.c
@@ -0,0 +1,284 @@
+/* rawtopgm.c - convert raw grayscale bytes into a portable graymap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <math.h>
+#include "pgm.h"
+#include "shhopt.h"
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec;  /* Filespec of input file */
+    unsigned int headerskip;
+    float rowskip;
+    int bottomfirst;  /* the -bottomfirst/-bt option */
+    int autosize;  /* User wants us to figure out the size */
+    int width;
+    int height;
+    int bpp;
+      /* bytes per pixel in input format.  1 or 2 */
+    int littleendian;
+      /* logical: samples in input are least significant byte first */
+    int maxval;  /* -maxval option, or -1 if none */
+};
+
+
+static void
+parse_command_line(int argc, char ** argv,
+                   struct cmdline_info *cmdline_p) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct *option_def = malloc(100*sizeof(optStruct));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct2 opt;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENTRY(0,   "bottomfirst",   OPT_FLAG,   &cmdline_p->bottomfirst,    0);
+    OPTENTRY(0,   "bt",            OPT_FLAG,   &cmdline_p->bottomfirst,    0);
+    OPTENTRY(0,   "topbottom",     OPT_FLAG,   &cmdline_p->bottomfirst,    0);
+    OPTENTRY(0,   "tb",            OPT_FLAG,   &cmdline_p->bottomfirst,    0);
+    OPTENTRY(0,   "headerskip",    OPT_UINT,   &cmdline_p->headerskip,     0);
+    OPTENTRY(0,   "rowskip",       OPT_FLOAT,  &cmdline_p->rowskip,        0);
+    OPTENTRY(0,   "bpp",           OPT_INT,    &cmdline_p->bpp,            0);
+    OPTENTRY(0,   "littleendian",  OPT_FLAG,   &cmdline_p->littleendian,   0);
+    OPTENTRY(0,   "maxval",        OPT_UINT,   &cmdline_p->maxval,         0);
+
+    /* Set the defaults */
+    cmdline_p->bottomfirst = FALSE;
+    cmdline_p->headerskip = 0;
+    cmdline_p->rowskip = 0.0;
+    cmdline_p->bpp = 1;
+    cmdline_p->littleendian = 0;
+    cmdline_p->maxval = -1;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions2(&argc, argv, opt, 0);
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (argc-1 == 0) {
+        cmdline_p->input_filespec = "-";
+        cmdline_p->autosize = TRUE;
+    } else if (argc-1 == 1) {
+        cmdline_p->input_filespec = argv[1];
+        cmdline_p->autosize = TRUE;
+    } else if (argc-1 == 2) {
+        cmdline_p->input_filespec = "-";
+        cmdline_p->autosize = FALSE;
+        cmdline_p->width = atoi(argv[1]);
+        cmdline_p->height = atoi(argv[2]);
+    } else if (argc-1 == 3) {
+        cmdline_p->input_filespec = argv[3];
+        cmdline_p->autosize = FALSE;
+        cmdline_p->width = atoi(argv[1]);
+        cmdline_p->height = atoi(argv[2]);
+    } else
+        pm_error("Program takes zero, one, two, or three arguments.  You "
+                 "specified %d", argc-1);
+
+    if (cmdline_p->bpp != 1 && cmdline_p->bpp != 2) 
+        pm_error("Bytes per pixel (-bpp) must be 1 or 2.  You specified %d.",
+                 cmdline_p->bpp);
+
+    if (cmdline_p->maxval == 0)
+        pm_error("Maxval (-maxval) may not be zero.");
+
+    if (cmdline_p->maxval > 255 && cmdline_p->bpp == 1)
+        pm_error("You have specified one byte per pixel, but a maxval "
+                 "too large to fit in one byte: %d", cmdline_p->maxval);
+    if (cmdline_p->maxval > 65535)
+        pm_error("Maxval must be less than 65536.  You specified %d.",
+                 cmdline_p->maxval);
+
+    if (cmdline_p->width <= 0) 
+        pm_error("Width must be a positive number.");
+    if (cmdline_p->height <= 0) 
+        pm_error("Height must be a positive number.");
+
+    if (cmdline_p->rowskip && cmdline_p->autosize)
+        pm_error("If you specify -rowskip, you must also give the image "
+                 "dimensions.");
+    if (cmdline_p->rowskip && cmdline_p->bottomfirst)
+        pm_error("You canot specify both -rowskip and -bottomfirst.  This is "
+                 "a limitation of this program.");
+
+}
+
+
+
+static void
+compute_image_size(const struct cmdline_info cmdline, const long nread,
+                   int * const rows_p, int * const cols_p) {
+
+    if (cmdline.autosize) {
+        int sqrt_trunc = 
+            (int) sqrt((double) (nread-cmdline.headerskip));
+        if (sqrt_trunc*sqrt_trunc+cmdline.headerskip != nread) 
+            pm_error( "You must specify the dimensions of the image unless "
+                      "it is a quadratic image.  This one is not quadratic: "
+                      "The number of "
+                      "pixels in the input is %ld, which is not a perfect "
+                      "square.", nread-cmdline.headerskip);
+        *rows_p = *cols_p = sqrt_trunc;
+        pm_message( "Image size: %d cols, %d rows", *cols_p, *rows_p);
+    } else {
+        *rows_p = cmdline.height;
+        *cols_p = cmdline.width;
+    }
+}
+
+
+
+static void
+skip_header(FILE *ifp, const int headerskip) {
+    int i;
+
+    for ( i = 0; i < headerskip; ++i ) {
+        /* Read a byte out of the file */
+        int val;
+        val = getc( ifp );
+        if ( val == EOF )
+            pm_error("EOF / read error reading Byte %d in the header", i );
+    }
+}
+
+
+
+static gray
+read_from_file(FILE *ifp, const int bpp, const int row, const int col,
+               const int littleendian) {
+/*----------------------------------------------------------------------------
+   Return the next sample value from the input file 'ifp', assuming the
+   input stream is 'bpp' bytes per pixel (1 or 2).  In the case of two
+   bytes, if 'littleendian', assume least significant byte is first.
+   Otherwise, assume MSB first.
+   
+   In error messages, say this is Column 'col', Row 'row'.  Exit program if
+   error.
+-----------------------------------------------------------------------------*/
+    gray retval;
+
+    if (bpp == 1) {
+        int val;
+        val = getc(ifp);
+        if (val == EOF)
+            pm_error( "EOF / read error at Row %d Column %d",
+                      row, col);
+        retval = (gray) val;
+    } else {
+        short val;
+        int rc;
+        rc = littleendian ? 
+            pm_readlittleshort(ifp, &val) : pm_readbigshort(ifp, &val);
+        if (rc != 0)
+            pm_error( "EOF / read error at Row %d Column %d",
+                      row, col);
+        retval = (gray) val;
+    }
+    return retval;
+}
+
+
+
+int
+main(int argc, char *argv[] ) {
+
+    struct cmdline_info cmdline;
+    FILE* ifp;
+    gray* grayrow;
+    int rows, cols;
+    gray maxval;
+    char* buf;
+    /* pixels_1 and pixels_2 are the array of pixels in the input buffer
+       (assuming we are using an input buffer).  pixels_1 is the array
+       as if the pixels are one byte each.  pixels_2 is the array as if
+       they are two bytes each.
+       */
+    unsigned char *pixels_1;  
+    unsigned short *pixels_2;
+    long nread;
+    int row;
+    float toskip;
+
+    pgm_init( &argc, argv );
+
+    parse_command_line(argc, argv, &cmdline);
+
+    ifp = pm_openr(cmdline.input_filespec);
+
+    if (cmdline.autosize || cmdline.bottomfirst) {
+        buf = pm_read_unknown_size( ifp, &nread );
+        pixels_1 = (unsigned char *) buf;
+        pixels_2 = (unsigned short *) buf;
+    } else
+        buf = NULL;
+
+    compute_image_size(cmdline, nread, &rows, &cols);
+
+    if (!buf)
+        skip_header(ifp, cmdline.headerskip);
+
+    toskip = 0.00001;
+
+    if (cmdline.maxval == -1)
+        maxval = (cmdline.bpp == 1 ? (gray) 255 : (gray) 65535);
+    else
+        maxval = (gray) cmdline.maxval;
+
+    pgm_writepgminit( stdout, cols, rows, maxval, 0 );
+    grayrow = pgm_allocrow( cols );
+
+    for ( row = 0; row < rows; ++row) {
+        int col;
+        unsigned int rowpos; /* index of this row in pixel array */
+        if (cmdline.bottomfirst)
+            rowpos = (rows-row-1) * cols;
+        else
+            rowpos = row * cols;
+
+        for ( col = 0; col < cols; ++col )
+            if (buf) {
+                if (cmdline.bpp == 1)
+                    grayrow[col] = pixels_1[rowpos+col];
+                else
+                    grayrow[col] = pixels_2[rowpos+col];
+            } else {
+                grayrow[col] = read_from_file(ifp, cmdline.bpp, 
+                                              row, col,
+                                              cmdline.littleendian);
+            }
+        for ( toskip += cmdline.rowskip; toskip >= 1.0; toskip -= 1.0 ) {
+            /* Note that if we're using a buffer, cmdline.rowskip is zero */
+            int val;
+            val = getc( ifp );
+            if ( val == EOF )
+                pm_error( "EOF / read error skipping bytes at the end "
+                          "of Row %d.", row);
+        }
+        pgm_writepgmrow( stdout, grayrow, cols, maxval, 0 );
+    }
+    
+    if (buf)
+        free(buf);
+    pm_close( ifp );
+    pm_close( stdout );
+
+    exit( 0 );
+}
diff --git a/converter/pgm/sbigtopgm.c b/converter/pgm/sbigtopgm.c
new file mode 100644
index 00000000..bb3cb6fe
--- /dev/null
+++ b/converter/pgm/sbigtopgm.c
@@ -0,0 +1,208 @@
+/*
+
+    sbigtopgm.c - read a Santa Barbara Instruments Group CCDOPS file
+
+    Note: All SBIG CCD astronomical cameras produce 14 bits or
+	  (the ST-4 and ST-5) or 16 bits (ST-6 and later) per pixel.
+
+		  Copyright (C) 1998 by John Walker
+		       http://www.fourmilab.ch/
+
+    If you find yourself having to add functionality included subsequent
+    to the implementation of this program, you can probably find
+    documentation of any changes to the SBIG file format on their
+    Web site: http://www.sbig.com/
+
+    Permission to use, copy, modify, and distribute this software and
+    its documentation for any purpose and without fee is hereby
+    granted, provided that the above copyright notice appear in all
+    copies and that both that copyright notice and this permission
+    notice appear in supporting documentation.	This software is
+    provided "as is" without express or implied warranty.
+
+*/
+
+#include <string.h>
+
+#include "pgm.h"
+#include "nstring.h"
+
+#define SBIG_HEADER_LENGTH  2048      /* File header length */
+
+/*  looseCanon	--  Canonicalize a line from the file header so
+		    items more sloppily formatted than those
+		    written by CCDOPS are still accepted.  */
+
+static void looseCanon(cp)
+  char *cp;
+{
+    char *op = cp;
+    char c;
+    
+    while ((c = *cp++) != 0) {
+	if (!ISSPACE(c)) {
+	    if (ISUPPER(c)) {
+		c = tolower(c);
+	    }
+	    *op++ = c;
+	}
+    }
+    *op++ = 0;
+}
+
+int main(argc, argv)
+  int argc;
+  char* argv[];
+{
+    FILE *ifp;
+    gray *grayrow;
+    register gray *gP;
+    int argn, row;
+    register int col;
+    int maxval;
+    int comp, rows, cols;
+    char header[SBIG_HEADER_LENGTH];
+    char *hdr;
+    static char camera[80] = "ST-?";
+
+    pgm_init(&argc, argv);
+
+    argn = 1;
+
+    if (argn < argc) {
+	ifp = pm_openr(argv[argn]);
+	argn++;
+    } else {
+	ifp = stdin;
+    }
+
+    if (argn != argc)
+        pm_usage( "[sbigfile]" );
+
+    if (fread(header, SBIG_HEADER_LENGTH, 1, ifp) < 1) {
+        pm_error("error reading SBIG file header");
+    }
+
+    /*	Walk through the header and parse relevant parameters.	*/
+
+    comp = -1;
+    cols = -1;
+    rows = -1;
+
+    /*	The SBIG header specification equivalent to maxval is
+        "Sat_level", the saturation level of the image.  This
+	specification is optional, and was not included in files
+	written by early versions of CCDOPS. It was introduced when it
+	became necessary to distinguish 14-bit images with a Sat_level
+	of 16383 from 16-bit images which saturate at 65535.  In
+	addition, co-adding images or capturing with Track and
+	Accumulate can increase the saturation level.  Since files
+        which don't have a Sat_level line in the header were most
+	probably written by early drivers for the ST-4 or ST-5, it
+	might seem reasonable to make the default for maxval 16383,
+	the correct value for those cameras.  I chose instead to use
+	65535 as the default because the overwhelming majority of
+        cameras in use today are 16 bit, and it's possible some
+        non-SBIG software may omit the "optional" Sat_level
+	specification.	Also, no harm is done if a larger maxval is
+	specified than appears in the image--a simple contrast stretch
+	will adjust pixels to use the full 0 to maxval range.  The
+	converse, pixels having values greater than maxval, results in
+	an invalid file which may cause problems in programs which
+	attempt to process it.	*/
+
+    maxval = 65535;
+
+    hdr = header;
+
+    for (;;) {
+        char *cp = strchr(hdr, '\n');
+
+	if (cp == NULL) {
+            pm_error("malformed SBIG file header at character %d", hdr - header);
+	}
+	*cp = 0;
+        if (strncmp(hdr, "ST-", 3) == 0) {
+            char *ep = strchr(hdr + 3, ' ');
+
+	    if (ep != NULL) {
+		*ep = 0;
+		strcpy(camera, hdr);
+                *ep = ' ';
+	    }
+	}
+	looseCanon(hdr);
+        if (strncmp(hdr, "st-", 3) == 0) {
+            comp = strstr(hdr, "compressed") != NULL;
+        } else if (strncmp(hdr, "height=", 7) == 0) {
+	    rows = atoi(hdr + 7);
+        } else if (strncmp(hdr, "width=", 6) == 0) {
+	    cols = atoi(hdr + 6);
+        } else if (strncmp(hdr, "sat_level=", 10) == 0) {
+	    maxval = atoi(hdr + 10);
+        } else if (STREQ(hdr, "end")) {
+	    break;
+	}
+	hdr = cp + 1;
+    }
+
+    if ((comp == -1) || (rows == -1) || (cols == -1)) {
+        pm_error("required specification missing from SBIG file header");
+    }
+
+    fprintf(stderr, "SBIG %s %dx%d %s image, saturation level = %d.\n",
+        camera, cols, rows, comp ? "compressed" : "uncompressed", maxval);
+
+    if (maxval > PGM_OVERALLMAXVAL) {
+        pm_error("Saturation level (%d levels) is too large.\n"
+                 "This program's limit is %d.", maxval, PGM_OVERALLMAXVAL);
+    }
+
+    pgm_writepgminit(stdout, cols, rows, (gray) maxval, 0);
+    grayrow = pgm_allocrow(cols);
+
+#define DOSINT(fp) ((getc(fp) & 0xFF) | (getc(fp) << 8))
+
+    for (row = 0; row < rows; row++) {
+	int compthis = comp;
+
+	if (comp) {
+	    int rowlen = DOSINT(ifp); /* Compressed row length */
+
+	    /*	If compression results in a row length >= the uncompressed
+		row length, that row is output uncompressed.  We detect this
+		by observing that the compressed row length is equal to
+		that of an uncompressed row.  */
+
+	    if (rowlen == cols * 2) {
+		compthis = 0;
+	    }
+	}
+	for (col = 0, gP = grayrow; col < cols; col++, gP++) {
+	    gray g;
+
+	    if (compthis) {
+
+		if (col == 0) {
+		    g = DOSINT(ifp);
+		} else {
+		    int delta = getc(ifp);
+
+		    if (delta == 0x80) {
+			g = DOSINT(ifp);
+		    } else {
+			g += ((signed char) delta);
+		    }
+		}
+	    } else {
+		g = DOSINT(ifp);
+	    }
+	    *gP = g;
+	}
+	pgm_writepgmrow(stdout, grayrow, cols, (gray) maxval, 0);
+    }
+    pm_close(ifp);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/converter/pgm/spottopgm.c b/converter/pgm/spottopgm.c
new file mode 100644
index 00000000..59240ba8
--- /dev/null
+++ b/converter/pgm/spottopgm.c
@@ -0,0 +1,231 @@
+/* Spottopgm: Convert a SPOT satellite image to PGM format.
+ *
+ * Usage: spottopgm [-1|2|3] [Firstcol Firstline Lastcol Lastline] inputfile
+ *
+ * Author: Warren Toomey, 1992.
+ */
+
+#include <stdio.h>
+#include "pgm.h"
+
+/* Global variables */
+
+static FILE *spotfile;          /* The input file */
+static char linebuf[12000];        /* The line buffer */
+
+static long Firstline = 0,     /* The rectangle the user wants */
+    Lastline = 3000,   /* cut out of the image */
+    Firstcol = 0,
+    Lastcol = 0;
+static long Diff = 0;          /* Firstcol - Lastcol */
+static char *Bufptr;           /* Pointer into the input image */
+static int Color = 1;          /* Either 1, 2 or 3 */
+static int Colbool = 0;        /* 1 if color */
+
+
+/* Get_image extracts the pixel data from one line
+ * (i.e one record) in the SPOT input file. A SPOT image
+ * record has a header, data and trailer. The data lengths
+ * are fixed at 3960, 5400, 8640 or 10980 bytes.
+ *
+ * When we arrive here we have read in 12 bytes of the record.
+ * We then read in the rest of the record. We find the trailer
+ * and from that determine the number of pixels on the line.
+ *
+ * If the image is really color i.e interleaved 3 colors, we
+ * convert a line if its spectral sequence is the same as the one
+ * requested by the user (i.e 1, 2 or 3). I could create a ppm file
+ * but I couldn't be bothered with the rearranging of the data.
+ */
+static int 
+get_image(long length)
+{
+    int cnt;
+    struct Linehdr        /* Each line begins with the 12 bytes */
+    {             /* we have already, plus these 20 bytes */
+        long linenum;       /* The line number of the record */
+        short recseq;       /* The record sequence number */
+        short spectseq;     /* The spectral number of the line */
+        long linetime;      /* Time it was recorded (in ms). */
+        long leftpixmar;        /* The pixel number of the 1st pixel */
+        long rightpixmar;       /* The pixel number of the last pixel */
+    } linehdr;
+    long numpixels;       /* Number of pixels on the line */
+
+    /* Get the details of this line */
+    if (pm_readbiglong (spotfile, &linehdr.linenum) == -1
+        || pm_readbigshort (spotfile, &linehdr.recseq) == -1
+        || pm_readbigshort (spotfile, &linehdr.spectseq) == -1
+        || pm_readbiglong (spotfile, &linehdr.linetime) == -1
+        || pm_readbiglong (spotfile, &linehdr.leftpixmar) == -1
+        || pm_readbiglong (spotfile, &linehdr.rightpixmar) == -1)
+        pm_error ("EOF / read error reading line header");
+
+    /* Now read in the line data */
+    cnt = length - 20 - 88;
+    cnt = fread(linebuf, 1, cnt, spotfile);
+
+    if (!Diff)
+    {
+        cnt += 28;
+        if (fseek (spotfile, 24, 1) == EOF)
+            pm_error ("seek error");
+        if (pm_readbiglong (spotfile, &numpixels) == -1)
+            pm_error ("EOF / read error reading line ender");
+    
+        /* Determine the picture size */
+        Bufptr = &linebuf[Firstcol];
+        if (Lastcol == 0 || Lastcol > numpixels)
+            Lastcol = numpixels;
+        Diff = Lastcol - Firstcol;
+        /* Print out the header */
+        printf("P5\n%ld %ld\n255\n", Diff, Lastline - Firstline);
+        /* Inform about the image size */
+        if (Colbool) fprintf(stderr, "Color image, ");
+        fprintf(stderr, "%ld pixels wide\n", numpixels);
+    }
+
+    /* Output the line */
+    if (linehdr.linenum >= Firstline && linehdr.linenum <= Lastline
+        && linehdr.spectseq == Color)
+        fwrite(Bufptr, 1, Diff, stdout);
+    if (linehdr.linenum > Lastline) exit(0);
+
+#ifdef DEBUG
+    fprintf(stderr,
+            "Line %4d, %3d, %3d, time %4d, l/r "
+            "pixmar %4d %4d len %d pixnum %d\n",
+            linehdr.linenum, linehdr.recseq, 
+            linehdr.spectseq, linehdr.linetime,
+            linehdr.leftpixmar, linehdr.rightpixmar, length, numpixels);
+#endif
+    /* And return the amount to seek */
+    return (length - 20 - cnt);
+}
+
+
+
+/* The image header tells us if the image is in monochrome or color, and
+ * if the latter, if the input colors are interleaved. If interleaved
+ * color, lines are interleaved R, G, B, R, G, B etc. Technically, some
+ * interleaving of infra-red, visible and ultra-violet.
+ *
+ * In the description field below,
+ *  element 0 == P --> monochrome
+ *  element 0 == X --> color
+ *  element 9 == S --> sequential (i.e only one color here)
+ *  element 9 == I --> interleaved (1 or more colors)
+ */
+static int 
+get_imghdr(int length)
+{
+    struct Imghdr
+    {
+        long linewidth;
+        char dummy1[36];
+        char description[16];   /* Type of image */
+    } header;
+
+    if (pm_readbiglong (spotfile, &header.linewidth) == -1)
+        pm_error ("EOF / read error reading header");
+#ifdef DEBUG
+    if (fread (header.dummy1, 1, 36, spotfile) != 36)
+        pm_error ("EOF / read error reading header");
+#else
+    if (fseek (spotfile, 36, 1) == EOF)
+        pm_error ("seek error");
+#endif
+    if (fread (header.description, 1, 16, spotfile) != 16)
+        pm_error ("EOF / read error reading header");
+
+    /* Determine mono or color */
+    if (header.description[0] == 'X' && header.description[9] == 'S')
+        Colbool = 1;
+    else Colbool = 0;
+
+#ifdef DEBUG
+    fprintf(stderr, "Dummy str is >%s<\n", header.dummy1);
+    fprintf(stderr, "Imghdr str is >%s<, col %d\n", 
+            header.description, Colbool);
+#endif
+    /* Return the amount to fseek */
+    return (length - 56);
+}
+
+
+static void
+usage()
+{
+    fprintf(stderr,
+            "Usage: spottopgm [-1|2|3] [Firstcol Firstline Lastcol Lastline] "
+            "input_file\n");
+    exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct Record             /* A SPOT image is broken up into */
+    {                 /* records with the following fields */
+        long record;            /* The record number (1, 2, 3...) */
+        unsigned char sub1;         /* Record sub type 1 */
+        unsigned char type;         /* The record type */
+        unsigned char sub2;         /* Record sub type 2 */
+        unsigned char sub3;         /* Record sub type 3 */
+        long length;            /* Record length in bytes */
+    } arecord;
+
+    pgm_init( &argc, argv );
+   
+    switch (argc)
+    {
+    case 7:
+        Color= -(atoi(argv[1]));      /* Get the color to extract */
+        argv++;
+    case 6:
+        Firstcol = atoi(argv[1]);     /* Get the rectangle to extract */
+        Firstline = atoi(argv[2]);
+        Lastcol = atoi(argv[3]);
+        Lastline = atoi(argv[4]);
+        argv += 4;
+        goto openfile;            /* Yuk, a goto! */
+    case 3:
+        Color= -(atoi(argv[1]));      /* Get the color to extract */
+        argv++;
+    case 2:
+    openfile:
+    spotfile = fopen(argv[1], "rb");  /* Open the input file */
+    if (spotfile == NULL) { perror("fopen"); exit(1); }
+    break;
+    default:
+        usage();
+    }
+
+    while (1)             /* Get a record */
+    {
+        if (pm_readbiglong (spotfile, &arecord.record) == -1)
+            break;
+        arecord.sub1 = fgetc (spotfile);
+        arecord.type = fgetc (spotfile);
+        arecord.sub2 = fgetc (spotfile);
+        arecord.sub3 = fgetc (spotfile);
+        if (pm_readbiglong (spotfile, &arecord.length) == -1)
+            pm_error ("EOF / read error reading a record");
+
+        arecord.length -= 12;   /* Subtract header size as well */
+        if (arecord.type == 0355 && arecord.sub1 == 0355)
+            arecord.length = get_image(arecord.length);
+        else if (arecord.type == 0300 && arecord.sub1 == 077)
+            arecord.length = get_imghdr(arecord.length);
+#ifdef DEBUG
+        else
+            fprintf(stderr, 
+                    "Rcrd %3d, type %03o, stype %03o %03o %03o, length %d\n",
+                    arecord.record, arecord.type, arecord.sub1, arecord.sub2,
+                    (int) arecord.sub3 & 0xff, arecord.length);
+#endif
+        /* Seek to next record */
+        fseek(spotfile, arecord.length, 1);
+    }
+    exit (0);
+}
diff --git a/converter/ppm/411toppm.c b/converter/ppm/411toppm.c
new file mode 100644
index 00000000..a5b25ac1
--- /dev/null
+++ b/converter/ppm/411toppm.c
@@ -0,0 +1,261 @@
+/*
+   I recently had a visit from my mom who owns a Sony Mavica camera.
+   This camera produces standard MPEG and JPEG files, but it also
+   creates 64x48 pixel thumbnails for preview/index on its own tiny
+   LCD screen.  These files are named with an extension that is
+   ".411".
+
+   Sony appears not to want to document the ".411" file format, but it
+   is clear from various web pages that it is a variant of the
+   CCIR.601 standard YUV encoding used in MPEG.  The name indicates
+   that the file content consists of chunks of 6 bytes: 4 bytes of
+   image Y values, followed by 1 bytes of U and one byte of V values
+   that apply to the previous 4 Y pixel values.
+
+   There appear to be some commercial 411 file readers on the net, and
+   there is the Java-based Javica program, but I prefer Open Source
+   command-line utilities.  So, I grabbed a copy of netpbm-9.11 from
+   SourceForge and hacked the eyuvtoppm.c file so that it looks like
+   this.  While this may not be exactly the right thing to do, it
+   produces results which are close enough for me.
+
+   There are all sorts of user-interface gotchas possible in here that
+   I'm not going to bother changing -- especially not without actual
+   documentation from Sony about what they intend to do with ".411"
+   files in the future.  I place my modifications into the public
+   domain, but I ask that my name & e-mail be mentioned in the
+   commentary of any derived version.
+
+   Steve Allen <sla@alumni.caltech.edu>, 2001-03-01
+
+   Bryan Henderson reworked the program to use the Netpbm libraries to
+   create the PPM output and follow some other Netpbm conventions.
+   2001-03-03.  Bryan's contribution is public domain.  
+*/
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.  */
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "ppm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+typedef unsigned char uint8;
+
+#define CHOP(x)     ((x < 0) ? 0 : ((x > 255) ? 255 : x))
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec;  /* Filespec of input file */
+    int width;
+    int height;
+};
+
+
+
+static void
+parse_command_line(int argc, char ** argv,
+                   struct cmdline_info *cmdline_p) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+
+    optStruct *option_def = malloc(100*sizeof(optStruct));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct2 opt;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENTRY(0,   "width",      OPT_INT,    &cmdline_p->width,          0);
+    OPTENTRY(0,   "height",     OPT_INT,    &cmdline_p->height,         0);
+
+    /* Set the defaults */
+    cmdline_p->width = 64;
+    cmdline_p->height = 48;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+    
+    optParseOptions2(&argc, argv, opt, 0);
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (cmdline_p->width <= 0)
+        pm_error("-width must be positive.");
+    if (cmdline_p->height <= 0)
+        pm_error("-height must be positive.");
+
+    if (argc > 2)
+        pm_error("There is at most 1 argument: the input file spec.  "
+                 "You supplied %d", argc-1);
+    else {
+        if (argc > 1)
+            cmdline_p->input_filespec = argv[1];
+        else
+            cmdline_p->input_filespec = "-";
+    }
+}
+
+
+
+static void 
+ReadYUV(FILE *fpointer, const int width, const int height, 
+        uint8 ** const orig_y, uint8 ** const orig_cb, 
+        uint8 ** const orig_cr) {
+
+    int y, x, i;
+
+    for (y = 0; y < height; y++) {
+        for (x = 0, i = 0; x < width; x+=4, i++) {
+            fread(orig_y[y]+x , 1, 4, fpointer);
+            fread(orig_cb[y]+i, 1, 1, fpointer);
+            fread(orig_cr[y]+i, 1, 1, fpointer);
+        }
+    }
+}
+
+
+
+static void
+AllocYCC(const int width, const int height,
+         uint8 *** const orig_yP, uint8 *** const orig_crP, 
+         uint8 *** const orig_cbP) {
+    int y;
+
+    MALLOCARRAY_NOFAIL(*orig_yP, height);
+    for (y = 0; y < height; y++) 
+        MALLOCARRAY_NOFAIL((*orig_yP)[y], width);
+
+    MALLOCARRAY_NOFAIL(*orig_crP, height);
+    for (y = 0; y < height; y++) 
+        MALLOCARRAY_NOFAIL((*orig_crP)[y], width/4);
+
+    MALLOCARRAY_NOFAIL(*orig_cbP, height);
+    for (y = 0; y < height; y++) 
+        MALLOCARRAY_NOFAIL((*orig_cbP)[y], width/4);
+}
+
+
+
+static void 
+YUVtoPPM(const int width, const int height, 
+         uint8 ** const orig_y, 
+         uint8 ** const orig_cb,
+         uint8 ** const orig_cr,
+         pixel ** const ppm_image
+         ) {
+    int     **Y, **U, **V;
+    int    y;
+
+    /* first, allocate tons of memory */
+
+    MALLOCARRAY_NOFAIL(Y, height);
+    for (y = 0; y < height; y++) 
+        MALLOCARRAY_NOFAIL(Y[y], width);
+    
+    MALLOCARRAY_NOFAIL(U, height);
+    for (y = 0; y < height; y++) 
+        MALLOCARRAY_NOFAIL(U[y], width / 4);
+
+    MALLOCARRAY_NOFAIL(V, height);
+    for (y = 0; y < height; y++) 
+        MALLOCARRAY_NOFAIL(V[y], width/4);
+
+	for ( y = 0; y < height; y ++ ) {
+        int x;
+	    for ( x = 0; x < width/4; x ++ ) {
+            U[y][x] = orig_cb[y][x] - 128;
+            V[y][x] = orig_cr[y][x] - 128;
+	    }
+    }
+	for ( y = 0; y < height; y ++ ) {
+        int x;
+	    for ( x = 0; x < width; x ++ )
+	    {
+            Y[y][x] = orig_y[y][x] - 16;
+	    }
+    }
+    for ( y = 0; y < height; y++ ) {
+        int x;
+        for ( x = 0; x < width; x++ ) {
+            pixval r, g, b;
+            long   tempR, tempG, tempB;
+            /* look at yuvtoppm source for explanation */
+            
+            tempR = 104635*V[y][x/4];
+            tempG = -25690*U[y][x/4] + -53294 * V[y][x/4];
+            tempB = 132278*U[y][x/4];
+            
+            tempR += (Y[y][x]*76310);
+            tempG += (Y[y][x]*76310);
+            tempB += (Y[y][x]*76310);
+            
+            r = CHOP((int)(tempR >> 16));
+            g = CHOP((int)(tempG >> 16));
+            b = CHOP((int)(tempB >> 16));
+
+            PPM_ASSIGN(ppm_image[y][x], r, g, b);
+        }
+    }
+    /* We really should free the Y, U, and V arrays now */
+}
+
+
+
+int
+main(int argc, char **argv) {
+    FILE *infile;
+    struct cmdline_info cmdline;
+    uint8 **orig_y, **orig_cb, **orig_cr;
+    pixel **ppm_image;
+
+    ppm_init(&argc, argv);
+
+    parse_command_line(argc, argv, &cmdline);
+
+    AllocYCC(cmdline.width, cmdline.height, &orig_y, &orig_cr, &orig_cb);
+    ppm_image = ppm_allocarray(cmdline.width, cmdline.height);
+
+    pm_message("Reading (%dx%d):  %s\n", cmdline.width, cmdline.height, 
+               cmdline.input_filespec);
+    infile = pm_openr(cmdline.input_filespec);
+    ReadYUV(infile, cmdline.width, cmdline.height, orig_y, orig_cb, orig_cr);
+    pm_close(infile);
+
+    YUVtoPPM(cmdline.width, cmdline.height, orig_y, orig_cb, orig_cr, 
+             ppm_image);
+
+    ppm_writeppm(stdout, ppm_image, cmdline.width, cmdline.height, 255, 0);
+
+    ppm_freearray(ppm_image, cmdline.height);
+
+    return 0;
+}
+
diff --git a/converter/ppm/Makefile b/converter/ppm/Makefile
new file mode 100644
index 00000000..31ca826b
--- /dev/null
+++ b/converter/ppm/Makefile
@@ -0,0 +1,49 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/ppm
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+SUBDIRS = hpcdtoppm ppmtompeg
+
+PORTBINARIES =	411toppm eyuvtoppm gouldtoppm ilbmtoppm imgtoppm \
+		leaftoppm mtvtoppm neotoppm \
+		pcxtoppm pc1toppm pi1toppm picttoppm pjtoppm \
+		ppmtoacad ppmtoarbtxt \
+		ppmtobmp ppmtoeyuv ppmtogif ppmtoicr ppmtoilbm \
+		ppmtoleaf ppmtolj ppmtomitsu ppmtoneo \
+		ppmtopcx ppmtopi1 ppmtopict ppmtopj \
+		ppmtopjxl ppmtoppm ppmtopuzz ppmtorgb3 ppmtosixel ppmtoterm \
+		ppmtowinicon ppmtoxpm ppmtoyuv ppmtoyuvsplit \
+		qrttoppm rawtoppm rgb3toppm spctoppm \
+		sputoppm tgatoppm winicontoppm ximtoppm xpmtoppm xvminitoppm \
+		yuvtoppm yuvsplittoppm
+
+MATHBINARIES = sldtoppm 
+
+# We don't build vidtoppm by default, because it requires special libraries
+# and there is no known requirement for vidtoppm.
+
+
+MERGEBINARIES = $(PORTBINARIES) $(MATHBINARIES)
+NOMERGEBINARIES =
+
+BINARIES = $(MERGEBINARIES) $(NOMERGEBINARIES) 
+
+DATAFILES = pcxstd.ppm
+
+OBJECTS = $(BINARIES:%=%.o)
+
+MERGE_OBJECTS = $(BINARIES:%=%.o2)
+
+.PHONY: all
+all: $(BINARIES) $(SUBDIRS:%=%/all)
+
+include $(SRCDIR)/Makefile.common
+
+ppmtobmp.o ppmtobmp.o2: bmp.h
+
+tgatoppm.o tgatoppm.o2: tga.h
diff --git a/converter/ppm/autocad.h b/converter/ppm/autocad.h
new file mode 100644
index 00000000..3005782b
--- /dev/null
+++ b/converter/ppm/autocad.h
@@ -0,0 +1,64 @@
+#ifndef AUTOCAD_H_INCLUDED
+#define AUTOCAD_H_INCLUDED
+/* The following table maps the  256 standard AutoCAD colors into RGB
+   values with a Maxval of 255.  These colors are actually derived in
+   an  algorithmic way from the HLS color system, but it's easier and
+   faster  to  just provide a table than to compute the RGB components
+   on the fly from the color index. */
+
+static unsigned char acadcol[256][3] = {
+    {0, 0, 0}, {255, 0, 0}, {255, 255, 0}, {0, 255, 0}, {0, 255, 255},
+    {0, 0, 255}, {255, 0, 255}, {255, 255, 255}, {255, 255, 255},
+    {255, 255, 255}, {255, 0, 0}, {255, 127, 127}, {165, 0, 0}, {165, 82, 82},
+    {127, 0, 0}, {127, 63, 63}, {76, 0, 0}, {76, 38, 38}, {38, 0, 0},
+    {38, 19, 19}, {255, 63, 0}, {255, 159, 127}, {165, 41, 0}, {165, 103, 82},
+    {127, 31, 0}, {127, 79, 63}, {76, 19, 0}, {76, 47, 38}, {38, 9, 0},
+    {38, 23, 19}, {255, 127, 0}, {255, 191, 127}, {165, 82, 0}, {165, 124, 82},
+    {127, 63, 0}, {127, 95, 63}, {76, 38, 0}, {76, 57, 38}, {38, 19, 0},
+    {38, 28, 19}, {255, 191, 0}, {255, 223, 127}, {165, 124, 0},
+    {165, 145, 82}, {127, 95, 0}, {127, 111, 63}, {76, 57, 0}, {76, 66, 38},
+    {38, 28, 0}, {38, 33, 19}, {255, 255, 0}, {255, 255, 127}, {165, 165, 0},
+    {165, 165, 82}, {127, 127, 0}, {127, 127, 63}, {76, 76, 0}, {76, 76, 38},
+    {38, 38, 0}, {38, 38, 19}, {191, 255, 0}, {223, 255, 127},
+    {124, 165, 0}, {145, 165, 82}, {95, 127, 0}, {111, 127, 63}, {57, 76, 0},
+    {66, 76, 38}, {28, 38, 0}, {33, 38, 19}, {127, 255, 0}, {191, 255, 127},
+    {82, 165, 0}, {124, 165, 82}, {63, 127, 0}, {95, 127, 63}, {38, 76, 0},
+    {57, 76, 38}, {19, 38, 0}, {28, 38, 19}, {63, 255, 0}, {159, 255, 127},
+    {41, 165, 0}, {103, 165, 82}, {31, 127, 0}, {79, 127, 63}, {19, 76, 0},
+    {47, 76, 38}, {9, 38, 0}, {23, 38, 19}, {0, 255, 0}, {127, 255, 127},
+    {0, 165, 0}, {82, 165, 82}, {0, 127, 0}, {63, 127, 63}, {0, 76, 0},
+    {38, 76, 38}, {0, 38, 0}, {19, 38, 19}, {0, 255, 63}, {127, 255, 159},
+    {0, 165, 41}, {82, 165, 103}, {0, 127, 31}, {63, 127, 79}, {0, 76, 19},
+    {38, 76, 47}, {0, 38, 9}, {19, 38, 23}, {0, 255, 127}, {127, 255, 191},
+    {0, 165, 82}, {82, 165, 124}, {0, 127, 63}, {63, 127, 95}, {0, 76, 38},
+    {38, 76, 57}, {0, 38, 19}, {19, 38, 28}, {0, 255, 191}, {127, 255, 223},
+    {0, 165, 124}, {82, 165, 145}, {0, 127, 95}, {63, 127, 111}, {0, 76, 57},
+    {38, 76, 66}, {0, 38, 28}, {19, 38, 33}, {0, 255, 255}, {127, 255, 255},
+    {0, 165, 165}, {82, 165, 165}, {0, 127, 127}, {63, 127, 127}, {0, 76, 76},
+    {38, 76, 76}, {0, 38, 38}, {19, 38, 38}, {0, 191, 255}, {127, 223, 255},
+    {0, 124, 165}, {82, 145, 165}, {0, 95, 127}, {63, 111, 127}, {0, 57, 76},
+    {38, 66, 76}, {0, 28, 38}, {19, 33, 38}, {0, 127, 255}, {127, 191, 255},
+    {0, 82, 165}, {82, 124, 165}, {0, 63, 127}, {63, 95, 127}, {0, 38, 76},
+    {38, 57, 76}, {0, 19, 38}, {19, 28, 38}, {0, 63, 255}, {127, 159, 255},
+    {0, 41, 165}, {82, 103, 165}, {0, 31, 127}, {63, 79, 127}, {0, 19, 76},
+    {38, 47, 76}, {0, 9, 38}, {19, 23, 38}, {0, 0, 255}, {127, 127, 255},
+    {0, 0, 165}, {82, 82, 165}, {0, 0, 127}, {63, 63, 127}, {0, 0, 76},
+    {38, 38, 76}, {0, 0, 38}, {19, 19, 38}, {63, 0, 255}, {159, 127, 255},
+    {41, 0, 165}, {103, 82, 165}, {31, 0, 127}, {79, 63, 127}, {19, 0, 76},
+    {47, 38, 76}, {9, 0, 38}, {23, 19, 38}, {127, 0, 255}, {191, 127, 255},
+    {82, 0, 165}, {124, 82, 165}, {63, 0, 127}, {95, 63, 127}, {38, 0, 76},
+    {57, 38, 76}, {19, 0, 38}, {28, 19, 38}, {191, 0, 255}, {223, 127, 255},
+    {124, 0, 165}, {145, 82, 165}, {95, 0, 127}, {111, 63, 127}, {57, 0, 76},
+    {66, 38, 76}, {28, 0, 38}, {33, 19, 38}, {255, 0, 255}, {255, 127, 255},
+    {165, 0, 165}, {165, 82, 165}, {127, 0, 127}, {127, 63, 127}, {76, 0, 76},
+    {76, 38, 76}, {38, 0, 38}, {38, 19, 38}, {255, 0, 191}, {255, 127, 223},
+    {165, 0, 124}, {165, 82, 145}, {127, 0, 95}, {127, 63, 111}, {76, 0, 57},
+    {76, 38, 66}, {38, 0, 28}, {38, 19, 33}, {255, 0, 127}, {255, 127, 191},
+    {165, 0, 82}, {165, 82, 124}, {127, 0, 63}, {127, 63, 95}, {76, 0, 38},
+    {76, 38, 57}, {38, 0, 19}, {38, 19, 28}, {255, 0, 63}, {255, 127, 159},
+    {165, 0, 41}, {165, 82, 103}, {127, 0, 31}, {127, 63, 79},	{76, 0, 19},
+    {76, 38, 47}, {38, 0, 9}, {38, 19, 23}, {84, 84, 84}, {118, 118, 118},
+    {152, 152, 152}, {186, 186, 186}, {220, 220, 220}, {255, 255, 255}
+};
+
+#endif
diff --git a/converter/ppm/eyuvtoppm.c b/converter/ppm/eyuvtoppm.c
new file mode 100644
index 00000000..dcbb9547
--- /dev/null
+++ b/converter/ppm/eyuvtoppm.c
@@ -0,0 +1,338 @@
+/* Bryan got this from mm.ftp-cs.berkeley.edu from the package
+   mpeg-encode-1.5b-src under the name eyuvtoppm.c on March 30, 2000.  
+   The file was dated April 14, 1995.  
+
+   Bryan rewrote the program entirely to match Netpbm coding style,
+   use the Netpbm libraries and also to output to stdout and ignore
+   any specification of an output file on the command line and not
+   segfault when called with no arguments.
+
+   There was no attached documentation except for this:  Encoder/Berkeley
+   YUV format is merely the concatenation of Y, U, and V data in order.
+   Compare with Abekda YUV, which interlaces Y, U, and V data.  */
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.  */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "ppm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+typedef unsigned char uint8;
+
+#define CHOP(x)     ((x < 0) ? 0 : ((x > 255) ? 255 : x))
+
+
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec;  /* Filespecs of input file */
+    unsigned int width;
+    unsigned int height;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdline_info *cmdlineP) {
+
+    optStruct3 opt;   /* Set by OPTENT3 */
+    unsigned int option_def_index;
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3('w', "width",     OPT_UINT,  &cmdlineP->width,   NULL,         0);
+    OPTENT3('h', "height",    OPT_UINT,  &cmdlineP->height,  NULL,         0);
+    
+    /* DEFAULTS */
+    cmdlineP->width = 352;
+    cmdlineP->height = 240;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = TRUE;
+    opt.allowNegNum = FALSE;
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+    if (cmdlineP->width == 0)
+        pm_error("The width cannot be zero.");
+    if (cmdlineP->width % 2 != 0)
+        pm_error("The width of an eyuv image must be an even number.  "
+                 "You specified %u.", cmdlineP->width);
+    if (cmdlineP->height == 0)
+        pm_error("The height cannot be zero.");
+    if (cmdlineP->height % 2 != 0)
+        pm_error("The height of an eyuv image must be an even number.  "
+                 "You specified %u.", cmdlineP->height);
+
+
+    if (argc-1 == 0) 
+        cmdlineP->input_filespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->input_filespec = argv[1];
+
+}
+
+
+
+static uint8 ** 
+AllocUint8Array(unsigned int const cols, unsigned int const rows) {
+
+    uint8 **retval;
+    unsigned int y;
+
+    MALLOCARRAY(retval, rows);
+    if (retval == NULL)
+        pm_error("Unable to allocate storage for %d x %d byte array.",
+                 cols, rows);
+
+    for (y = 0; y < rows; y++) {
+        MALLOCARRAY(retval[y], cols);
+        if (retval[y] == NULL)
+            pm_error("Unable to allocate storage for %d x %d byte array.",
+                     cols, rows);
+    }
+    return retval;
+}
+
+
+
+static int ** 
+AllocIntArray(unsigned int const cols, unsigned int const rows) {
+
+    int **retval;
+    unsigned int y;
+
+    MALLOCARRAY(retval, rows);
+    if (retval == NULL)
+        pm_error("Unable to allocate storage for %d x %d byte array.",
+                 cols, rows);
+
+    for (y = 0; y < rows; y++) {
+        MALLOCARRAY(retval[y], cols);
+        if (retval[y] == NULL)
+            pm_error("Unable to allocate storage for %d x %d byte array.",
+                     cols, rows);
+    }
+    return retval;
+}
+
+
+
+static void
+allocateStorage(int const cols, int const rows,
+                int *** const YP, int *** const UP, int *** const VP,
+                pixel *** const pixelsP, 
+                uint8 *** const orig_yP, uint8 *** const orig_cbP,
+                uint8 *** const orig_crP) {
+
+    *YP = AllocIntArray(cols, rows);
+    *UP = AllocIntArray(cols, rows);
+    *VP = AllocIntArray(cols, rows);
+
+    *pixelsP = ppm_allocarray(cols, rows);
+
+    *orig_yP  = AllocUint8Array(cols, rows);
+    *orig_cbP = AllocUint8Array(cols, rows);
+    *orig_crP = AllocUint8Array(cols, rows);
+}
+
+
+
+static void 
+FreeArray(void ** const array, unsigned int const rows) {
+
+    unsigned int y;
+
+    for (y = 0; y < rows; y++)
+        free(array[y]);
+    free(array);
+}
+
+
+
+static void
+freeStorage(int const rows,
+            int ** const Y, int ** const U, int ** const V,
+            pixel ** const pixels, 
+            uint8 ** const orig_y, uint8 ** const orig_cb,
+            uint8 ** const orig_cr) {
+    
+    FreeArray((void**) orig_y, rows); 
+    FreeArray((void**) orig_cb, rows); 
+    FreeArray((void**) orig_cr, rows);
+
+    ppm_freearray(pixels, rows);
+
+    FreeArray((void**) Y, rows);
+    FreeArray((void**) U, rows);
+    FreeArray((void**) V, rows);
+}
+
+
+
+static void 
+YUVtoPPM(unsigned int const cols, unsigned int const rows,
+         uint8 ** const orig_y, uint8 ** const orig_cb, uint8 ** const orig_cr,
+         pixel ** const pixels, 
+         int ** const Y, int ** const U, int ** const V) {
+/*----------------------------------------------------------------------------
+   Convert the YUV image in arrays orig_y[][], orig_cb[][], and orig_cr[][]
+   to a PPM image in the array (already allocated) pixels[][].
+
+   Use the preallocated areas Y[][], U[][], and V[][] for working space.
+-----------------------------------------------------------------------------*/
+    
+    int y;
+
+    for ( y = 0; y < rows/2; y ++ ) {
+        int x;
+        for ( x = 0; x < cols/2; x ++ ) {
+            U[y][x] = orig_cb[y][x] - 128;
+            V[y][x] = orig_cr[y][x] - 128;
+        }
+    }
+
+    for ( y = 0; y < rows; y ++ ) {
+        int x;
+        for ( x = 0; x < cols; x ++ ) 
+            Y[y][x] = orig_y[y][x] - 16;
+    }
+
+    for ( y = 0; y < rows; y++ ) {
+        int x;
+        for ( x = 0; x < cols; x++ ) {
+            long   tempR, tempG, tempB;
+            int     r, g, b;
+            /* look at yuvtoppm source for explanation */
+
+            tempR = 104635*V[y/2][x/2];
+            tempG = -25690*U[y/2][x/2] + -53294 * V[y/2][x/2];
+            tempB = 132278*U[y/2][x/2];
+
+            tempR += (Y[y][x]*76310);
+            tempG += (Y[y][x]*76310);
+            tempB += (Y[y][x]*76310);
+            
+            r = CHOP((int)(tempR >> 16));
+            g = CHOP((int)(tempG >> 16));
+            b = CHOP((int)(tempB >> 16));
+            
+            PPM_ASSIGN(pixels[y][x], r, g, b);
+        }
+    }
+}
+
+
+
+static void 
+ReadYUV(FILE * const yuvfile,
+        unsigned int const cols, unsigned int const rows,
+        uint8 ** const orig_y, 
+        uint8 ** const orig_cb, 
+        uint8 ** const orig_cr,
+        bool * const eofP) {
+
+    unsigned int y;
+    int c;
+
+    c = fgetc(yuvfile);
+    if (c < 0)
+        *eofP = TRUE;
+    else {
+        *eofP = FALSE;
+        ungetc(c, yuvfile);
+    }
+    if (!*eofP) {
+        for (y = 0; y < rows; y++)            /* Y */
+            fread(orig_y[y], 1, cols, yuvfile);
+        
+        for (y = 0; y < rows / 2; y++)            /* U */
+            fread(orig_cb[y], 1, cols / 2, yuvfile);
+        
+        for (y = 0; y < rows / 2; y++)            /* V */
+            fread(orig_cr[y], 1, cols / 2, yuvfile);
+        if (feof(yuvfile))
+            pm_error("Premature end of file reading EYUV input file");
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    FILE *ifp;
+    struct cmdline_info cmdline;
+    unsigned int frameSeq;
+
+    /* The following are addresses of malloc'ed storage areas for use by
+       subroutines.
+    */
+    int ** Y;
+    int ** U;
+    int ** V;
+    uint8 **orig_y, **orig_cb, **orig_cr;
+    pixel ** pixels;
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    /* Allocate all the storage once, to save time. */
+    allocateStorage(cmdline.width, cmdline.height,
+                    &Y, &U, &V, &pixels, &orig_y, &orig_cb, &orig_cr);
+
+    ifp = pm_openr(cmdline.input_filespec);
+
+    for (frameSeq = 0; !feof(ifp); frameSeq++) {
+        bool eof;
+
+        ReadYUV(ifp, cmdline.width, cmdline.height, 
+                orig_y, orig_cb, orig_cr, &eof);
+        if (!eof) {
+            pm_message("Converting Frame %u", frameSeq);
+
+            YUVtoPPM(cmdline.width, cmdline.height, orig_y, orig_cb, orig_cr, 
+                     pixels, Y, U, V);
+            ppm_writeppm(stdout, pixels, cmdline.width, cmdline.height, 
+                         255, FALSE);
+        }
+    }
+
+    freeStorage(cmdline.height, Y, U, V, pixels, orig_y, orig_cb, orig_cr);
+
+    pm_close(ifp);
+    exit(0);
+}
+
+
+
+
+
diff --git a/converter/ppm/gouldtoppm.c b/converter/ppm/gouldtoppm.c
new file mode 100644
index 00000000..5db7c51a
--- /dev/null
+++ b/converter/ppm/gouldtoppm.c
@@ -0,0 +1,122 @@
+/* gouldtoppm.c - read GOULD imaging system files and write a portable pixmap
+**
+** Copyright (C) 1990  Stephen P. Lesniewski
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <stdio.h>
+#include "ppm.h"
+
+#define MAXVAL 255
+
+static void getgouldheader ARGS(( FILE *infile, unsigned long *cols, unsigned long *nlines, unsigned long *bytesperline, unsigned long *bitsperpixel, unsigned long *NB ));
+
+int
+main( argc, argv )
+int argc;
+char *argv[];
+{
+	FILE *ifp;
+	pixel *pixrow;
+
+	unsigned long cols, nlines, bytesperline, bitsperpixel, NB, x, y;
+	unsigned char pbuffer[4];
+
+	int color_type;
+
+
+	ppm_init( &argc, argv );
+
+	if ( argc > 2 )
+		pm_usage( "[gouldfile]" );
+
+	if ( argc == 2 )
+		ifp = pm_openr( argv[1] );
+	else
+		ifp = stdin;
+
+	getgouldheader( ifp, &cols, &nlines, &bytesperline, &bitsperpixel, &NB);
+
+	ppm_writeppminit(stdout, cols, nlines, MAXVAL, 0);
+
+	color_type = bitsperpixel/8;
+	if (color_type == 0) color_type = NB;
+
+	pixrow = ppm_allocrow(cols);
+
+	for (y = 0; y < nlines; ++y)
+	{
+		for (x = 0; x < cols; ++x)
+		{
+			switch (color_type)
+			{
+			case 0:
+				pm_error("incorrect color type" );
+
+			case 1:
+				fread(pbuffer, 1, 1, ifp);
+				PPM_ASSIGN( pixrow[x], pbuffer[0], pbuffer[0], pbuffer[0]);
+				break;
+
+			case 2:
+				fread(pbuffer, 2, 1, ifp);
+				PPM_ASSIGN( pixrow[x], pbuffer[0], pbuffer[1], pbuffer[1]);
+				break;
+
+			case 3:
+				fread(pbuffer, 3, 1, ifp);
+				PPM_ASSIGN( pixrow[x], pbuffer[0], pbuffer[1], pbuffer[2]);
+				break;
+
+			default :
+				fread(pbuffer, 3, 1, ifp);
+				PPM_ASSIGN( pixrow[x], pbuffer[0], pbuffer[1], pbuffer[2]);
+				break;
+
+			} /* switch */
+		}
+		ppm_writeppmrow(stdout, pixrow, cols, MAXVAL, 0);
+	}
+
+	pm_close(ifp);
+	pm_close(stdout);
+
+	exit(0);
+}
+
+static void
+getgouldheader( infile, cols, nlines, bytesperline, bitsperpixel, NB)
+FILE *infile;
+unsigned long *cols, *nlines, *bytesperline, *bitsperpixel, *NB;
+{
+	unsigned long nlines_new, bytesperline_new, numheaderrec;
+	unsigned char headerblk[512];
+	int i;
+
+	if (fread(headerblk, 512, 1, infile) == 0)
+		pm_error("cannot read gould header" );
+
+	*nlines       = (headerblk[3]<<8) | headerblk[2];
+	*bytesperline = (headerblk[5]<<8) | headerblk[4];
+	*bitsperpixel = (headerblk[7]<<8) | headerblk[6];
+	numheaderrec  = (headerblk[9]<<8) | headerblk[8];
+	nlines_new    = (headerblk[15]<<24)| (headerblk[14]<<16) | (headerblk[13]<<8) | (headerblk[12]);
+	bytesperline_new = (headerblk[19]<<24)| (headerblk[18]<<16) | (headerblk[17]<<8) | (headerblk[16]);
+	*NB           = (headerblk[23]<<24)| (headerblk[22]<<16) | (headerblk[21]<<8) | (headerblk[20]);
+
+	if (numheaderrec > 1)
+		for (i = 1 ; i <numheaderrec; ++i)
+			if (fread(headerblk, 512, 1, infile) == 0)
+				pm_error("cannot read gould header(2nd)" );
+
+	if (*nlines==0) *nlines=nlines_new;
+	if (*bytesperline==0) *bytesperline=bytesperline_new;
+
+	*cols = (*bytesperline)*8 / (*bitsperpixel);
+}
diff --git a/converter/ppm/hpcdtoppm/Makefile b/converter/ppm/hpcdtoppm/Makefile
new file mode 100644
index 00000000..9a7c67d0
--- /dev/null
+++ b/converter/ppm/hpcdtoppm/Makefile
@@ -0,0 +1,25 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/ppm/hpcdtoppm
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+all: hpcdtoppm
+
+SCRIPTS = hpcdtoppm pcdovtoppm
+MERGE_OBJECTS =
+
+include $(SRCDIR)/Makefile.common
+
+install: install.bin.local
+.PHONY: install.bin.local
+install.bin.local: $(PKGDIR)/bin
+# In June 2002, pcdovtoppm replaced pcdindex
+	cd $(PKGDIR)/bin ; \
+	$(SYMLINK) pcdindex$(EXE) pcdovtoppm
+
+
+FORCE:
diff --git a/converter/ppm/hpcdtoppm/README b/converter/ppm/hpcdtoppm/README
new file mode 100644
index 00000000..a1c7c8c2
--- /dev/null
+++ b/converter/ppm/hpcdtoppm/README
@@ -0,0 +1,20 @@
+This 'hpcdtoppm' directory is really just a placeholder for the real
+Hpcdtoppm source code.  
+
+The real Hpcdtoppm source code cannot be distributed on Sourceforge
+because a copyright holder does not license it in a way open enough to
+meet Sourceforge's requirements.
+
+Therefore, the Netpbm maintainer distributes Hpcdtoppm via another channel
+and distributes this dummy directory in the main Netpbm source tree on 
+Sourceforge.  When you run the program named 'hpcdtoppm' in this directory,
+it tells you how to get the real Hpcdtoppm.
+
+When you get the real Hpcdtoppm tarball, just unpack it and replace
+this entire 'hpcdtoppm' directory with its contents.  Then build
+Netpbm normally.
+
+Bear in mind when you get the real Hpcdtoppm, that it is not as openly
+licensed as what's in the rest of the Netpbm source tree.  As you'll
+see in the license that comes with Hpcdtoppm, you may _not_ sell it to
+someone else.)
diff --git a/converter/ppm/hpcdtoppm/hpcdtoppm b/converter/ppm/hpcdtoppm/hpcdtoppm
new file mode 100755
index 00000000..cb6d084b
--- /dev/null
+++ b/converter/ppm/hpcdtoppm/hpcdtoppm
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+cat <<EOF 1>&2
+You are running a program named 'hpcdtoppm' that stands in for the
+real program by that name.
+
+The real 'hpcdtoppm' is a program that converts an image from Photo CD
+format to PPM format.  The program you are running now just issues the
+message you are reading.
+
+Please get a copy of Hpcdtoppm from 
+
+  ftp://ibiblio.org/pub/linux/graphics/convert
+
+and replace this stand-in program with the real one.
+
+The point of this is that this stand-in 'hpcdtoppm' is distributed
+with Netpbm on Sourceforge.  Hpcdtoppm does not meet the requirements
+to be distributed on Sourceforge because a copyright holder does not
+permit people to sell copies.  In the past the real Hpcdtoppm was
+distributed with Netpbm on Sourceforge by mistake.
+
+EOF
+
+exit 1
\ No newline at end of file
diff --git a/converter/ppm/hpcdtoppm/pcdovtoppm b/converter/ppm/hpcdtoppm/pcdovtoppm
new file mode 100755
index 00000000..1f8b3006
--- /dev/null
+++ b/converter/ppm/hpcdtoppm/pcdovtoppm
@@ -0,0 +1,214 @@
+#!/bin/sh
+#
+# pcdovtoppm - generate a single PPM file from a PCD overview file
+#
+# Based on pnmindex (PBMPLUS), which was written by Jef Poskanzer,
+# this script makes also use of hpcdtoppm, written by Hadmut Danisch.
+#
+# Formerly called Pcdindex.
+#
+# A similar result can be achieved by using "hpcdtoppm -Overview"
+# followed by "pnmindex -black" on the generated PPM images.
+# This shell just makes it more convenient and transparent to
+# convert from one PCD to one PPM overview file.
+#
+# Additional options (compared to pnmindex) are -maxwidth and
+# -font <font>. See "man pbmtext" on how to create your own font.
+#
+# Pieter S. van der Meulen, 1992.
+# Rewritten in sh by Steve McIntyre <93sam@debian.org>, 2001
+
+# You may want to change the default values in the next 6 lines:
+maxwidth=1152   # maximum width of the index image
+size=192                # make the images about this big
+across=6                # show this many images per row
+colors="noquant"        # maximum amount of colors or noquant (no quantization)
+back="-black"   # default background color
+font=" "                # default font or none (pbmtext's internal font)
+
+usage ()
+{
+    echo "Usage: $0 [-m W] [-s S] [-a A] [-c N|n] [-f F] [-b|-w] <overview.pcd>"
+    echo " with"
+    echo "      W = maximum width of the result image   (default: $maxwidth)"
+    echo "      S = maximum size of each of the images  (default: $size)"
+    echo "      A = maximum number of images across     (default: $across)"
+    echo "      N = maximum number of colors or noquant (default: $colors)"
+    echo -n "   F = font to be used for annotation      (default: "
+    if [ "$font" = " " ] ; then
+                echo "internal font)"
+    else
+                echo "$font)"
+    fi
+    echo "      -b/-w = black/white background color    (default: $back)"
+    echo " "
+    echo " e.g.: $0 -m 768 -s 96 -f smallfont.pbm overview.pcd > overview.ppm"
+    echo " or  : $0 /cdrom/photo_cd/overview.pcd | ppmtojpeg > overview.jpg"
+    exit 1
+}
+
+# Parse the options
+while :; do
+    case "$1" in 
+        -m*)
+             if [ $# -lt 2 ] ; then usage; fi
+             maxwidth="$2"
+             shift
+             shift
+             ;;
+
+        -s*)
+             if [ $# -lt 2 ] ; then usage; fi
+             size="$2"
+             shift
+             shift
+             ;;
+
+            -a*)
+                if [ $# -lt 2 ] ; then usage; fi
+                across="$2"
+                shift
+                shift
+                ;;
+
+            -c*)
+                if [ $# -lt 2 ] ; then usage; fi
+                colors="$2"
+                shift
+                shift
+                ;;
+
+            -f*)
+                if [ $# -lt 2 ] ; then usage; fi
+                font="-font $2"
+                shift
+                shift
+                ;;
+
+            -b*)
+                back="-black"
+                shift
+                ;;
+
+            -w*)
+                back="-white"
+                shift
+                ;;
+
+            -*)
+                echo "$0 : Unknown option $1"
+                echo " "
+                usage
+                ;;
+
+            *)
+                break
+                ;;
+
+    esac
+done
+
+if [ $# = 0 ]; then
+    usage
+fi
+
+tmpfile=`tempfile -p pi -m 600`
+
+rowfiles=()
+imagefiles=()
+row=1
+col=1
+width=$size
+
+# Convert the PCD overview file to many PPM images
+if [ -f $1 ] ; then
+    hpcdtoppm -Overview $1 $tmpfile
+else
+    echo "$0 : Could not access $1"
+    echo " "
+    usage
+fi
+
+for i in "$tmpfile"* 
+do
+    if [ -f $i ] ; then
+        description=`pnmfile $i`
+        if [ "${description[4]}" -le $size -a \
+             "${description[6]}" -le $size ] ; then
+            cat $i > $tmpfile
+                else
+            if [ "$colors" = "n" ] ; then
+                pnmscale -quiet -xysize $size $size $i > $tmpfile
+            else
+                pnmscale -quiet -xysize $size $size $i | \
+                ppmquant -quiet $colors > $tmpfile
+            fi
+        fi
+    fi
+    imagefile=pi.${row}.${col}.$$
+    rm -f $imagefile
+    ttext="$i:t"
+
+    if [ "$back" = "-white" ] ; then
+        pbmtext $font "$ttext" | pnmcrop -quiet | pnmmargin -white 2| \
+        pnmcat $back -tb $tmpfile - > $imagefile
+    else
+        pbmtext $font "$ttext" | pnmcrop -quiet | pnmmargin -white 2 | \
+        pnminvert | pnmcat $back -tb $tmpfile - > $imagefile
+    fi
+
+    rm -f $tmpfile
+    description=`pnmfile $imagefile`
+    width=$(( $width + ${description[4]} ))
+    imagefiles="$imagefiles $imagefile"
+
+    if [ $col -ge $across -o $width -gt $maxwidth ] ; then
+        rowfile=pi.${row}.$$
+        rm -f $rowfile
+    if [ "$colors" = "n" ] ; then
+        pnmcat $back -lr -jbottom $imagefiles > $rowfile
+    else
+        pnmcat $back -lr -jbottom $imagefiles | \
+        ppmquant -quiet $colors > $rowfile
+    fi
+    rm -f $imagefiles
+    imagefiles=()
+    rowfiles="$rowfiles $rowfile"
+    col=1
+    row=$(( $row + 1 ))
+    width=$size
+    else
+        col=$(( $col + 1 ))
+    fi
+done
+
+if [ ${#imagefiles[*]} -gt 0 ] ; then
+    rowfile=pi.${row}.$$
+    rm -f $rowfile
+    if [ "$colors" = "n" ] ; then
+        pnmcat $back -lr -jbottom $imagefiles > $rowfile
+    else
+        pnmcat $back -lr -jbottom $imagefiles | \
+        ppmquant -quiet $colors > $rowfile
+    fi
+    rm -f $imagefiles
+    rowfiles="$rowfiles $rowfile"
+fi
+
+if [ ${#rowfiles[*]} == 1 ]; then
+    cat $rowfiles
+else
+    if [ "$colors" = "n" ] ; then
+        pnmcat $back -tb $rowfiles
+    else
+        pnmcat $back -tb $rowfiles | ppmquant -quiet $colors
+    fi
+fi
+
+rm -f $rowfiles
+
+exit 0
+
+
+
+
diff --git a/converter/ppm/ilbm.h b/converter/ppm/ilbm.h
new file mode 100644
index 00000000..68657956
--- /dev/null
+++ b/converter/ppm/ilbm.h
@@ -0,0 +1,256 @@
+#ifndef ILBM_H_INCLUDED
+#define ILBM_H_INCLUDED
+
+/* ilbm.h - definitions for IFF ILBM files */
+
+#define RowBytes(cols)          ((((cols) + 15) / 16) * 2)
+
+
+/* definitions for BMHD */
+
+typedef struct {
+    unsigned short w, h;
+    short x, y;
+    unsigned char nPlanes, masking, compression, flags;
+    unsigned short transparentColor;
+    unsigned char xAspect, yAspect;
+    short pageWidth, pageHeight;
+} BitMapHeader;
+#define BitMapHeaderSize    20
+
+#define BMHD_FLAGS_CMAPOK       (1<<7)      /* valid 8bit colormap */
+
+#define mskNone                 0
+#define mskHasMask              1
+#define mskHasTransparentColor  2
+#define mskLasso                3       /* not supported */
+#define mskMAXKNOWN             mskLasso
+static const char * mskNAME[] = { 
+    "none", "mask plane", "transparent color", "lasso" 
+};
+
+#define cmpNone                 0
+#define cmpByteRun1             1
+#define cmpMAXKNOWN             cmpByteRun1
+static const char * cmpNAME[] = { "none", "byterun1" };
+
+
+/* definitions for CMAP */
+
+#if 0   /* not used */
+typedef struct {
+    unsigned char r, g, b;
+} ColorRegister;
+#endif
+
+
+/* definitions for CAMG */
+
+#define CAMGChunkSize       4
+
+#define vmLACE              0x0004
+#define vmEXTRA_HALFBRITE   0x0080
+#define vmHAM               0x0800
+#define vmHIRES             0x8000
+
+#define HAMCODE_CMAP      0     /* look up color in colormap */
+#define HAMCODE_BLUE      1     /* new blue component */
+#define HAMCODE_RED       2     /* new red component */
+#define HAMCODE_GREEN     3     /* new green component */
+
+
+/* multipalette PCHG chunk definitions */
+
+/* get number of longwords in line mask from PCHG.LineCount */
+#define MaskLongWords(x)    (((x) + 31) / 32)
+
+typedef struct {
+    unsigned short  Compression;
+    unsigned short  Flags;
+    short           StartLine;      /* may be negative */
+    unsigned short  LineCount;
+    unsigned short  ChangedLines;
+    unsigned short  MinReg;
+    unsigned short  MaxReg;
+    unsigned short  MaxChanges;
+    unsigned long   TotalChanges;
+} PCHGHeader;
+#define PCHGHeaderSize      20
+
+/* Compression modes */
+#define PCHG_COMP_NONE      0
+#define PCHG_COMP_HUFFMAN   1
+
+/* Flags */
+#define PCHGF_12BIT         (1 << 0)    /* use SmallLineChanges */
+#define PCHGF_32BIT         (1 << 1)    /* use BigLineChanges */
+#define PCHGF_USE_ALPHA     (1 << 2)    /* meaningful only if PCHG_32BIT is on:
+                                           use the Alpha channel info */
+typedef struct {
+    unsigned long   CompInfoSize;
+    unsigned long   OriginalDataSize;
+} PCHGCompHeader;
+#define PCHGCompHeaderSize  8
+
+#if 0   /* not used */
+typedef struct {
+    unsigned char   ChangeCount16;
+    unsigned char   ChangeCount32;
+    unsigned short  *PaletteChange;
+} SmallLineChanges;
+
+typedef struct {
+    unsigned short  Register;
+    unsigned char   Alpha, Red, Blue, Green;    /* ARBG, not ARGB */
+} BigPaletteChange;
+
+typedef struct {
+    unsigned short      ChangeCount;
+    BigPaletteChange    *PaletteChange;
+} BigLineChanges;
+#endif /* 0 */
+
+
+/* definitions for CLUT */
+
+#if 0 /* not used */
+typedef struct {
+    unsigned long type;
+    unsigned long reserved0;
+    unsigned char lut[256];
+} ColorLUT;
+#endif /* 0 */
+#define CLUTSize    (256+4+4)
+
+/* types */
+#define CLUT_MONO   0
+#define CLUT_RED    1
+#define CLUT_GREEN  2
+#define CLUT_BLUE   3
+#define CLUT_HUE    4   /* not supported */
+#define CLUT_SAT    5   /* not supported */
+
+
+/* unofficial DCOL chunk for direct-color */
+
+typedef struct {
+    unsigned char r, g, b, pad1;
+} DirectColor;
+#define DirectColorSize     4
+
+
+
+/* IFF chunk IDs */
+
+typedef unsigned long   IFF_ID;
+
+#define MAKE_ID(a, b, c, d) \
+    ((IFF_ID)(a)<<24 | (IFF_ID)(b)<<16 | (IFF_ID)(c)<<8 | (IFF_ID)(d))
+
+#define ID_FORM     MAKE_ID('F', 'O', 'R', 'M')     
+    /* EA IFF 85 group identifier */
+#define ID_CAT      MAKE_ID('C', 'A', 'T', ' ')     
+    /* EA IFF 85 group identifier */
+#define ID_LIST     MAKE_ID('L', 'I', 'S', 'T')     
+    /* EA IFF 85 group identifier */
+#define ID_PROP     MAKE_ID('P', 'R', 'O', 'P')     
+    /* EA IFF 85 group identifier */
+#define ID_END      MAKE_ID('E', 'N', 'D', ' ')     
+    /* unofficial END-of-FORM identifier (see Amiga RKM Devices Ed.3
+       page 376) */
+#define ID_ILBM     MAKE_ID('I', 'L', 'B', 'M')     
+    /* EA IFF 85 raster bitmap form */
+#define ID_DEEP     MAKE_ID('D', 'E', 'E', 'P')     
+    /* Chunky pixel image files (Used in TV Paint) */
+#define ID_RGB8     MAKE_ID('R', 'G', 'B', '8')     
+    /* RGB image forms, Turbo Silver (Impulse) */
+#define ID_RGBN     MAKE_ID('R', 'G', 'B', 'N')     
+    /* RGB image forms, Turbo Silver (Impulse) */
+#define ID_PBM      MAKE_ID('P', 'B', 'M', ' ')     
+    /* 256-color chunky format (DPaint 2 ?) */
+#define ID_ACBM     MAKE_ID('A', 'C', 'B', 'M')     
+    /* Amiga Contiguous Bitmap (AmigaBasic) */
+
+/* generic */
+
+#define ID_FVER     MAKE_ID('F', 'V', 'E', 'R')     
+    /* AmigaOS version string */
+#define ID_JUNK     MAKE_ID('J', 'U', 'N', 'K')     
+    /* always ignore this chunk */
+#define ID_ANNO     MAKE_ID('A', 'N', 'N', 'O')     
+    /* EA IFF 85 Generic Annotation chunk */
+#define ID_AUTH     MAKE_ID('A', 'U', 'T', 'H')     
+    /* EA IFF 85 Generic Author chunk */
+#define ID_CHRS     MAKE_ID('C', 'H', 'R', 'S')     
+    /* EA IFF 85 Generic character string chunk */
+#define ID_NAME     MAKE_ID('N', 'A', 'M', 'E')     
+    /* EA IFF 85 Generic Name of art, music, etc. chunk */
+#define ID_TEXT     MAKE_ID('T', 'E', 'X', 'T')     
+    /* EA IFF 85 Generic unformatted ASCII text chunk */
+#define ID_copy     MAKE_ID('(', 'c', ')', ' ')     
+/* EA IFF 85 Generic Copyright text chunk */
+
+/* ILBM chunks */
+
+#define ID_BMHD     MAKE_ID('B', 'M', 'H', 'D')     
+    /* ILBM BitmapHeader */
+#define ID_CMAP     MAKE_ID('C', 'M', 'A', 'P')     
+    /* ILBM 8bit RGB colormap */
+#define ID_GRAB     MAKE_ID('G', 'R', 'A', 'B')     
+    /* ILBM "hotspot" coordiantes */
+#define ID_DEST     MAKE_ID('D', 'E', 'S', 'T')     
+    /* ILBM destination image info */
+#define ID_SPRT     MAKE_ID('S', 'P', 'R', 'T')     
+    /* ILBM sprite identifier */
+#define ID_CAMG     MAKE_ID('C', 'A', 'M', 'G')     
+    /* Amiga viewportmodes */
+#define ID_BODY     MAKE_ID('B', 'O', 'D', 'Y')     
+    /* ILBM image data */
+#define ID_CRNG     MAKE_ID('C', 'R', 'N', 'G')     
+    /* color cycling */
+#define ID_CCRT     MAKE_ID('C', 'C', 'R', 'T')     
+    /* color cycling */
+#define ID_CLUT     MAKE_ID('C', 'L', 'U', 'T')     
+    /* Color Lookup Table chunk */
+#define ID_DPI      MAKE_ID('D', 'P', 'I', ' ')     
+    /* Dots per inch chunk */
+#define ID_DPPV     MAKE_ID('D', 'P', 'P', 'V')     
+    /* DPaint perspective chunk (EA) */
+#define ID_DRNG     MAKE_ID('D', 'R', 'N', 'G')     
+    /* DPaint IV enhanced color cycle chunk (EA) */
+#define ID_EPSF     MAKE_ID('E', 'P', 'S', 'F')     
+    /* Encapsulated Postscript chunk */
+#define ID_CMYK     MAKE_ID('C', 'M', 'Y', 'K')     
+    /* Cyan, Magenta, Yellow, & Black color map (Soft-Logik) */
+#define ID_CNAM     MAKE_ID('C', 'N', 'A', 'M')     
+    /* Color naming chunk (Soft-Logik) */
+#define ID_PCHG     MAKE_ID('P', 'C', 'H', 'G')     
+    /* Line by line palette control information (Sebastiano Vigna) */
+#define ID_PRVW     MAKE_ID('P', 'R', 'V', 'W')     
+    /* A mini duplicate ILBM used for preview (Gary Bonham) */
+#define ID_XBMI     MAKE_ID('X', 'B', 'M', 'I')     
+    /* eXtended BitMap Information (Soft-Logik) */
+#define ID_CTBL     MAKE_ID('C', 'T', 'B', 'L')     
+    /* Newtek Dynamic Ham color chunk */
+#define ID_DYCP     MAKE_ID('D', 'Y', 'C', 'P')     
+    /* Newtek Dynamic Ham chunk */
+#define ID_SHAM     MAKE_ID('S', 'H', 'A', 'M')     
+    /* Sliced HAM color chunk */
+#define ID_ABIT     MAKE_ID('A', 'B', 'I', 'T')     
+    /* ACBM body chunk */
+#define ID_DCOL     MAKE_ID('D', 'C', 'O', 'L')     
+    /* unofficial direct color */
+#define ID_DPPS     MAKE_ID('D', 'P', 'P', 'S')
+    /* ? */
+#define ID_TINY     MAKE_ID('T', 'I', 'N', 'Y')
+    /* ? */
+
+/* other stuff */
+
+#define MAXPLANES       16
+typedef unsigned short  rawtype;
+
+#define MAXCMAPCOLORS   (1 << MAXPLANES)
+#define MAXCOLVAL       255     /* max value of color component */
+
+#endif
diff --git a/converter/ppm/ilbmtoppm.c b/converter/ppm/ilbmtoppm.c
new file mode 100644
index 00000000..f9f9bac3
--- /dev/null
+++ b/converter/ppm/ilbmtoppm.c
@@ -0,0 +1,2381 @@
+/* ilbmtoppm.c - read an IFF ILBM file and produce a portable pixmap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** Modified by Mark Thompson on 10/4/90 to accomodate 24-bit IFF files
+** as used by ASDG, NewTek, etc.
+**
+** Modified by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
+**  20/Jun/93:
+**  - row-by-row operation
+**  - better de-interleave algorithm
+**  - colormap files
+**  - direct color
+**  04/Oct/93:
+**  - multipalette capability (PCHG chunk)
+**  - options -ignore, -isham, -isehb and -adjustcolors
+**  22/May/94:
+**  - minor change: check first for 24 planes, then for HAM
+**  21/Sep/94:
+**  - write mask plane to a file if -maskfile option used
+**  - write colormap file
+**  - added sliced HAM/dynamic HAM/dynamic Hires multipalette formats (SHAM, CTBL chunk)
+**  - added color lookup tables (CLUT chunk)
+**  - major rework of colormap/multipalette handling
+**  - now uses numeric IFF IDs
+**  24/Oct/94:
+**  - transparentColor capability
+**  - added RGBN/RGB8 image types
+**  - 24-bit & direct color modified to n-bit deep ILBM
+**  22/Feb/95:
+**  - direct color (DCOL) reimplemented
+**  29/Mar/95
+**  - added IFF-PBM format
+*/
+
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "ilbm.h"
+#include "mallocvar.h"
+
+/*#define DEBUG*/
+
+typedef struct {
+    int reg;            /* color register to change */
+    pixval r, g, b;     /* new colors for register */
+} PaletteChange;
+
+typedef struct {
+    pixel *color;
+    int    ncolors;
+    /* lookup tables */
+    unsigned char *redlut;
+    unsigned char *greenlut;
+    unsigned char *bluelut;
+    unsigned char *monolut;
+    /* multipalette stuff */
+    PaletteChange *mp_init;
+    PaletteChange **mp_change;
+    int mp_rows;                /* # of rows in change array */
+    int mp_type;                /* see below, higher types preferred */
+    int mp_flags;
+    IFF_ID  mp_id;
+} ColorMap;
+
+#define HAS_COLORMAP(cmap)      ((cmap) && (cmap)->color)
+#define HAS_COLORLUT(cmap)      ((cmap) && ((cmap)->redlut || (cmap)->greenlut || (cmap)->bluelut))
+#define HAS_MONOLUT(cmap)       ((cmap) && (cmap)->monolut)
+#define HAS_MULTIPALETTE(cmap)  (HAS_COLORMAP(cmap) && (cmap)->mp_type)
+#define MP_TYPE_SHAM        1
+#define MP_TYPE_CTBL        2
+#define MP_TYPE_PCHG        3
+#define MP_REG_IGNORE       -1
+#define MP_REG_END          -2
+#define MP_FLAGS_SKIPLACED   (1<<0)
+
+#define FACTOR_4BIT     17      /* scale factor maxval 15 -> maxval 255 */
+
+static short verbose = 0;
+static short adjustcolors = 0;
+static unsigned char *ilbmrow;
+static pixel *pixelrow;
+static FILE *maskfile = NULL;
+static bit *maskrow = NULL;
+static short wrotemask = 0;
+static IFF_ID typeid;       /* ID_ILBM, ID_RGBN, ID_RGB8 */
+
+static pixel *transpColor = NULL;       /* transparent color */
+static short transpIndex = -1;
+static char *transpName = NULL;
+
+static bool debug = FALSE;
+
+
+
+static char *
+ID2string(id)
+    IFF_ID id;
+{
+    static char str[] = "abcd";
+
+    str[0] = (char)(id >> 24 & 0xff);
+    str[1] = (char)(id >> 16 & 0xff);
+    str[2] = (char)(id >>  8 & 0xff);
+    str[3] = (char)(id >>  0 & 0xff);
+
+    return str;
+}
+
+
+/****************************************************************************
+ Memory allocation
+ ****************************************************************************/
+static ColorMap *
+alloc_cmap(void) {
+
+    ColorMap * cmap;
+
+    MALLOCVAR_NOFAIL(cmap);
+
+    cmap->color     = NULL;
+    cmap->ncolors   = 0;
+    cmap->monolut   = NULL;
+    cmap->redlut    = NULL;
+    cmap->greenlut  = NULL;
+    cmap->bluelut   = NULL;
+    cmap->mp_init   = NULL;
+    cmap->mp_change = NULL;
+    cmap->mp_rows   = 0;
+    cmap->mp_type   = 0;
+    cmap->mp_flags  = 0;
+
+    return cmap;
+}
+
+
+
+static rawtype *
+alloc_rawrow(cols)
+    int cols;
+{
+    rawtype *r;
+    int i;
+
+    MALLOCARRAY_NOFAIL(r, cols);
+
+    for( i = 0; i < cols; i++ )
+        r[i] = 0;
+
+    return r;
+}
+
+
+/****************************************************************************
+ Basic I/O functions
+ ****************************************************************************/
+
+static void
+readerr(f, iffid)
+    FILE *f;
+    IFF_ID iffid;
+{
+    if( ferror(f) )
+        pm_error("read error");
+    else
+        pm_error("premature EOF in %s chunk", ID2string(iffid));
+}
+
+
+static void
+read_bytes(FILE *          const ifP,
+           int             const bytes,
+           unsigned char * const buffer,
+           IFF_ID          const iffid,
+           unsigned long * const counterP) {
+
+    if (counterP) {
+        if (*counterP < bytes)
+            pm_error("insufficient data in %s chunk", ID2string(iffid));
+        *counterP -= bytes;
+    }
+    if (fread(buffer, 1, bytes, ifP) != bytes)
+        readerr(ifP, iffid);
+}
+
+
+
+static unsigned char
+get_byte(ifp, iffid, counter)
+    FILE* ifp;
+    IFF_ID iffid;
+    long *counter;
+{
+    int i;
+
+    if( counter ) {
+        if( *counter == 0 )
+            pm_error("insufficient data in %s chunk", ID2string(iffid));
+        --(*counter);
+    }
+    i = getc(ifp);
+    if( i == EOF )
+        readerr(ifp, iffid);
+
+    return (unsigned char) i;
+}
+
+static long
+get_big_long(FILE *          const ifP,
+             IFF_ID          const iffid,
+             unsigned long * const counterP) {
+
+    long l;
+    
+    if (counterP) {
+        if (*counterP < 4)
+            pm_error("insufficient data in %s chunk", ID2string(iffid));
+        *counterP -= 4;
+    }
+    if (pm_readbiglong(ifP, &l) == -1)
+        readerr(ifP, iffid);
+
+    return l;
+}
+
+
+
+static short
+get_big_short(FILE *          const ifP,
+              IFF_ID          const iffid,
+              unsigned long * const counterP) {
+
+    short s;
+
+    if (counterP) {
+        if (*counterP < 2)
+            pm_error("insufficient data in %s chunk", ID2string(iffid));
+        *counterP -= 2;
+    }
+    if (pm_readbigshort(ifP, &s) == -1)
+        readerr(ifP, iffid);
+
+    return s;
+}
+
+
+
+/****************************************************************************
+ Chunk reader
+ ****************************************************************************/
+
+static void
+chunk_end(FILE *        const ifP,
+          IFF_ID        const iffid,
+          unsigned long const chunksize) {
+
+    if (chunksize > 0) {
+        unsigned long remainingChunksize;
+        pm_message("warning - %ld extraneous byte%s in %s chunk",
+                    chunksize, (chunksize == 1 ? "" : "s"), ID2string(iffid));
+        remainingChunksize = chunksize;  /* initial value */
+        while (remainingChunksize > 0)
+            get_byte(ifP, iffid, &remainingChunksize);
+    }
+}
+
+
+
+static void
+skip_chunk(FILE *        const ifP,
+           IFF_ID        const iffid,
+           unsigned long const chunksize) {
+    unsigned long remainingChunksize;
+
+    remainingChunksize = chunksize;  /* initial value */
+
+    while (remainingChunksize > 0)
+        get_byte(ifP, iffid, &remainingChunksize);
+}
+
+
+
+static void
+display_chunk(FILE *        const ifP,
+              IFF_ID        const iffid,
+              unsigned long const chunksize) {
+
+    int byte;
+    unsigned long remainingChunksize;
+
+    pm_message("contents of %s chunk:", ID2string(iffid));
+
+    remainingChunksize = chunksize;  /* initial value */
+    byte = '\0';
+
+    while (remainingChunksize > 0) {
+        byte = get_byte(ifP, iffid, &remainingChunksize);
+        if (fputc(byte, stderr) == EOF)
+            pm_error("write error");
+    }
+    if (byte != '\n')
+        if (fputc('\n', stderr) == EOF)
+            pm_error("write error");
+}
+
+
+static void
+read_cmap(FILE *     const ifp,
+          IFF_ID     const iffid,
+          long       const chunksize,
+          ColorMap * const cmap) {
+
+    long colors;
+
+    colors = chunksize / 3;
+    if( colors == 0 ) {
+        pm_error("warning - empty %s colormap", ID2string(iffid));
+        skip_chunk(ifp, iffid, chunksize);
+    } else {
+        unsigned int i;
+        if( cmap->color )               /* prefer CMAP-chunk over CMYK-chunk */
+            ppm_freerow(cmap->color);
+        cmap->color = ppm_allocrow(colors);
+        cmap->ncolors = colors;
+        
+        for( i = 0; i < colors; ++i ) {
+            int r, g, b;
+            r = get_byte(ifp, iffid, &chunksize);
+            g = get_byte(ifp, iffid, &chunksize);
+            b = get_byte(ifp, iffid, &chunksize);
+            PPM_ASSIGN(cmap->color[i], r, g, b);
+        }
+        chunk_end(ifp, iffid, chunksize);
+    }
+}
+
+
+
+static void
+read_cmyk(FILE *     const ifp,
+          IFF_ID     const iffid,
+          long       const chunksize,
+          ColorMap * const cmap) {
+
+    if( HAS_COLORMAP(cmap) ) {      /* prefer RGB color map */
+        skip_chunk(ifp, iffid, chunksize);
+    } else {
+        long const colors = chunksize/4;
+        if( colors == 0 ) {
+            pm_error("warning - empty %s colormap", ID2string(iffid));
+            skip_chunk(ifp, iffid, chunksize);
+        } else {
+            unsigned int i;
+            cmap->color = ppm_allocrow(colors);
+            cmap->ncolors = colors;
+            
+            for( i = 0; i < colors; ++i ) {
+                int c, m, y, k;
+                c = get_byte(ifp, iffid, &chunksize);
+                m = get_byte(ifp, iffid, &chunksize);
+                y = get_byte(ifp, iffid, &chunksize);
+                k = get_byte(ifp, iffid, &chunksize);
+
+                {
+                    pixval const red = 
+                        MAXCOLVAL - MIN(MAXCOLVAL,
+                                        c*(MAXCOLVAL-k)/MAXCOLVAL+k); 
+                    pixval const green = 
+                        MAXCOLVAL - MIN(MAXCOLVAL,
+                                        m*(MAXCOLVAL-k)/MAXCOLVAL+k);
+                    pixval const blue = 
+                        MAXCOLVAL - MIN(MAXCOLVAL,
+                                        y*(MAXCOLVAL-k)/MAXCOLVAL+k);
+
+                    PPM_ASSIGN(cmap->color[i], red, green, blue);
+                }
+            }
+            chunk_end(ifp, iffid, chunksize);
+        }
+    }
+}
+
+
+
+static void
+read_clut(FILE *        const ifp,
+          IFF_ID        const iffid,
+          unsigned long const chunksize,
+          ColorMap *    const cmap) {
+
+    if (chunksize != CLUTSize) {
+        pm_message("invalid size for %s chunk - skipping it", 
+                   ID2string(iffid));
+        skip_chunk(ifp, iffid, chunksize);
+    } else {
+        long type;
+        unsigned char * lut;
+        unsigned long remainingChunksize;
+        unsigned int i;
+
+        type = get_big_long(ifp, iffid, &remainingChunksize);
+        get_big_long(ifp, iffid, &remainingChunksize); /* skip reserved fld */
+
+        MALLOCARRAY_NOFAIL(lut, 256);
+        for( i = 0; i < 256; ++i )
+            lut[i] = get_byte(ifp, iffid, &remainingChunksize);
+
+        switch( type ) {
+        case CLUT_MONO:
+            cmap->monolut  = lut;
+            break;
+        case CLUT_RED:
+            cmap->redlut   = lut;
+            break;
+        case CLUT_GREEN:
+            cmap->greenlut = lut;
+            break;
+        case CLUT_BLUE:
+            cmap->bluelut  = lut;
+            break;
+        default:
+            pm_message("warning - %s type %ld not recognized",
+                       ID2string(iffid), type);
+            free(lut);
+        }
+    }
+}
+
+
+
+static BitMapHeader *
+read_bmhd(FILE *        const ifP,
+          IFF_ID        const iffid,
+          unsigned long const chunksize) {
+
+    BitMapHeader * bmhdP;
+
+    if( chunksize != BitMapHeaderSize ) {
+        pm_message("invalid size for %s chunk - skipping it", 
+                   ID2string(iffid));
+        skip_chunk(ifP, iffid, chunksize);
+        bmhdP = NULL;
+    } else {
+        unsigned long remainingChunksize;
+
+        MALLOCVAR_NOFAIL(bmhdP);
+
+        remainingChunksize = chunksize;  /* initial value */
+        
+        bmhdP->w = get_big_short(ifP, iffid, &remainingChunksize);
+        bmhdP->h = get_big_short(ifP, iffid, &remainingChunksize);
+        bmhdP->x = get_big_short(ifP, iffid, &remainingChunksize);
+        bmhdP->y = get_big_short(ifP, iffid, &remainingChunksize);
+        bmhdP->nPlanes = get_byte(ifP, iffid, &remainingChunksize);
+        bmhdP->masking = get_byte(ifP, iffid, &remainingChunksize);
+        bmhdP->compression = get_byte(ifP, iffid, &remainingChunksize);
+        bmhdP->flags = get_byte(ifP, iffid, &remainingChunksize);
+        bmhdP->transparentColor =
+            get_big_short(ifP, iffid, &remainingChunksize);
+        bmhdP->xAspect = get_byte(ifP, iffid, &remainingChunksize);
+        bmhdP->yAspect = get_byte(ifP, iffid, &remainingChunksize);
+        bmhdP->pageWidth  = get_big_short(ifP, iffid, &remainingChunksize);
+        bmhdP->pageHeight = get_big_short(ifP, iffid, &remainingChunksize);
+
+        if( verbose ) {
+            if( typeid == ID_ILBM )
+                pm_message("dimensions: %dx%d, %d planes", 
+                           bmhdP->w, bmhdP->h, bmhdP->nPlanes);
+            else
+                pm_message("dimensions: %dx%d", bmhdP->w, bmhdP->h);
+
+            if( typeid == ID_ILBM || typeid == ID_PBM ) {
+                pm_message("compression: %s",
+                           bmhdP->compression <= cmpMAXKNOWN ?
+                           cmpNAME[bmhdP->compression] : "unknown");
+
+                switch( bmhdP->masking ) {
+                case mskNone:
+                    break;
+                case mskHasMask:
+                case mskHasTransparentColor:
+                    if( !maskfile )
+                        pm_message("use '-maskfile <filename>' "
+                                   "to generate a PBM mask file from %s", 
+                                   mskNAME[bmhdP->masking]);
+                    break;
+                case mskLasso:
+                    pm_message("warning - masking type '%s' not recognized", 
+                               mskNAME[bmhdP->masking]);
+                    break;
+                default:
+                    pm_error("unknown masking type %d", bmhdP->masking);
+                }
+            }
+            else    /* RGBN/RGB8 */
+                if( !maskfile )
+                    pm_message("use '-maskfile <filename>' "
+                               "to generate a PBM mask file "
+                               "from genlock bits");
+        }
+
+        /* fix aspect ratio */
+        if( bmhdP->xAspect == 0 || bmhdP->yAspect == 0 ) {
+            pm_message("warning - illegal aspect ratio %d:%d, using 1:1", 
+                       bmhdP->xAspect, bmhdP->yAspect);
+            bmhdP->xAspect = bmhdP->yAspect = 1;
+        }
+
+        if( bmhdP->xAspect != bmhdP->yAspect ) {
+            pm_message("warning - non-square pixels; "
+                       "to fix do a 'pnmscale -%cscale %g'",
+                       bmhdP->xAspect > bmhdP->yAspect ? 'x' : 'y',
+                       bmhdP->xAspect > bmhdP->yAspect ? 
+                       (float)(bmhdP->xAspect)/bmhdP->yAspect : 
+                       (float)(bmhdP->yAspect)/bmhdP->xAspect);
+        }
+    }
+    return bmhdP;
+}
+
+
+/****************************************************************************
+ ILBM functions
+ ****************************************************************************/
+
+
+static void
+read_ilbm_plane(FILE *          const ifP,
+                unsigned long * const remainingChunksizeP,
+                int             const bytes,
+                int             const compression) {
+
+    unsigned char *ubp;
+    int j, byte;
+    int bytesRemaining;
+
+    bytesRemaining = bytes;  /* initial value */
+
+    switch(compression) {
+        case cmpNone:
+            read_bytes(ifP, bytesRemaining, ilbmrow,
+                       ID_BODY, remainingChunksizeP);
+            break;
+        case cmpByteRun1:
+            ubp = ilbmrow;
+            do {
+                byte = (int)get_byte(ifP, ID_BODY, remainingChunksizeP);
+                if( byte <= 127 ) {
+                    j = byte;
+                    bytesRemaining -= (j+1);
+                    if( bytesRemaining < 0 )
+                        pm_error("error doing ByteRun1 decompression");
+                    for( ; j >= 0; j-- )
+                        *ubp++ = get_byte(ifP, ID_BODY, remainingChunksizeP);
+                }
+                else
+                if ( byte != 128 ) {
+                    j = 256 - byte;
+                    bytesRemaining -= (j+1);
+                    if( bytesRemaining < 0 )
+                        pm_error("error doing ByteRun1 decompression");
+                    byte = (int)get_byte(ifP, ID_BODY, remainingChunksizeP);
+                    for( ; j >= 0; j-- )
+                        *ubp++ = (unsigned char)byte;
+                }
+                /* 128 is a NOP */
+            }
+            while( bytesRemaining > 0 );
+            break;
+        default:
+            pm_error("unknown compression type %d", compression);
+    }
+}
+
+
+static const unsigned char bit_mask[] = {1, 2, 4, 8, 16, 32, 64, 128};
+
+static void
+decode_row(FILE *          const ifP,
+           unsigned long * const remainingChunksizeP,
+           rawtype *       const chunkyrow,
+           int             const nPlanes,
+           BitMapHeader *  const bmhdP) {
+
+    int plane, col, cols, cbit, bytes;
+    unsigned char *ilp;
+    rawtype *chp;
+
+    cols = bmhdP->w;
+    bytes = RowBytes(cols);
+    for( plane = 0; plane < nPlanes; plane++ ) {
+        int mask;
+
+        mask = 1 << plane;
+        read_ilbm_plane(ifP, remainingChunksizeP, bytes, bmhdP->compression);
+
+        ilp = ilbmrow;
+        chp = chunkyrow;
+
+        cbit = 7;
+        for( col = 0; col < cols; col++, cbit--, chp++ ) {
+            if( cbit < 0 ) {
+                cbit = 7;
+                ilp++;
+            }
+            if( *ilp & bit_mask[cbit] )
+                *chp |= mask;
+            else
+                *chp &= ~mask;
+        }
+    }
+}
+
+
+static void
+decode_mask(FILE *          const ifP,
+            unsigned long * const remainingChunksizeP,
+            rawtype *       const chunkyrow,
+            BitMapHeader *  const bmhdP) {
+
+    int col, cols, cbit;
+    unsigned char *ilp;
+
+    cols = bmhdP->w;
+    switch( bmhdP->masking ) {
+    case mskNone:
+        break;
+    case mskHasMask:        /* mask plane */
+        read_ilbm_plane(ifP, remainingChunksizeP, RowBytes(cols), 
+                        bmhdP->compression);
+        if( maskfile ) {
+            ilp = ilbmrow;
+            cbit = 7;
+            for( col = 0; col < cols; col++, cbit-- ) {
+                if( cbit < 0 ) {
+                    cbit = 7;
+                    ilp++;
+                }
+                if( *ilp & bit_mask[cbit] )
+                    maskrow[col] = PBM_BLACK;
+                else
+                    maskrow[col] = PBM_WHITE;
+            }
+            pbm_writepbmrow(maskfile, maskrow, cols, 0);
+            wrotemask = 1;
+        }
+        break;
+    case mskHasTransparentColor:
+        if( !chunkyrow )
+            pm_error("decode_mask(): chunkyrow == NULL - can't happen");
+        
+        if( maskfile ) {
+            for( col = 0; col < cols; col++ ) {
+                if( chunkyrow[col] == bmhdP->transparentColor )
+                    maskrow[col] = PBM_WHITE;
+                else
+                    maskrow[col] = PBM_BLACK;
+            }
+            pbm_writepbmrow(maskfile, maskrow, cols, 0);
+                wrotemask = 1;
+        }
+        break;
+    case mskLasso:
+        pm_error("This program does not know how to process Lasso masking");
+        break;
+    default:
+        pm_error("decode_mask(): unknown masking type %d - can't happen", 
+                 bmhdP->masking);
+    }
+}
+
+
+/****************************************************************************
+ Multipalette handling
+ ****************************************************************************/
+
+
+static void
+multi_adjust(cmap, row, palchange)
+    ColorMap *cmap;
+    int row;
+    PaletteChange *palchange;
+{
+    int i, reg;
+
+    for( i = 0; palchange[i].reg != MP_REG_END; i++ ) {
+        reg = palchange[i].reg;
+        if( reg >= cmap->ncolors ) {
+            pm_message("warning - palette change register out of range");
+            pm_message("    row %d  change structure %d  reg=%d (max %d)", 
+                       row, i, reg, cmap->ncolors-1);
+            pm_message("    ignoring it...  "
+                       "colors might get messed up from here");
+        }
+        else
+        if( reg != MP_REG_IGNORE ) {
+            PPM_ASSIGN(cmap->color[reg], 
+                       palchange[i].r, palchange[i].g, palchange[i].b);
+        }
+    }
+}
+
+static void
+multi_init(cmap, viewportmodes)
+    ColorMap *cmap;
+    long viewportmodes;
+{
+    if( cmap->mp_init )
+        multi_adjust(cmap, -1, cmap->mp_init);
+    if( !(viewportmodes & vmLACE) )
+        cmap->mp_flags &= ~(MP_FLAGS_SKIPLACED);
+}
+
+static void
+multi_update(cmap, row)
+    ColorMap *cmap;
+    int row;
+{
+    if( cmap->mp_flags & MP_FLAGS_SKIPLACED ) {
+        if( ODD(row) )
+            return;
+        if( row/2 < cmap->mp_rows && cmap->mp_change[row/2] )
+            multi_adjust(cmap, row, cmap->mp_change[row/2]);
+    }
+    else {
+        if( row < cmap->mp_rows && cmap->mp_change[row] )
+            multi_adjust(cmap, row, cmap->mp_change[row]);
+    }
+}
+
+static void
+multi_free(cmap)
+    ColorMap *cmap;
+{
+    int i;
+
+    if( cmap->mp_init ) {
+        free(cmap->mp_init);
+        cmap->mp_init = NULL;
+    }
+    if( cmap->mp_change ) {
+        for( i = 0; i < cmap->mp_rows; i++ ) {
+            if( cmap->mp_change[i] )
+                free(cmap->mp_change[i]);
+        }
+        free(cmap->mp_change);
+        cmap->mp_change = NULL;
+    }
+    cmap->mp_rows = 0;
+    cmap->mp_type = 0;
+    cmap->mp_flags = 0;
+}
+
+
+/****************************************************************************
+ Colormap handling
+ ****************************************************************************/
+
+static void
+check_cmap(bmhd, cmap)
+    BitMapHeader *bmhd;
+    ColorMap *cmap;
+{
+    pixval colmaxval = 0;
+    int shifted = 1;
+    int i, r, g, b;
+
+    if( bmhd ) {
+        if( bmhd->masking == mskHasTransparentColor || 
+            bmhd->masking == mskLasso ) {
+            transpIndex = bmhd->transparentColor;
+            if( !transpName ) {
+                MALLOCVAR_NOFAIL(transpColor);
+                if( transpIndex >= cmap->ncolors ) {
+                    pm_message("using default transparent color (black)");
+                    PPM_ASSIGN(*transpColor, 0, 0, 0);
+                }
+                else
+                    *transpColor = cmap->color[transpIndex];
+            }
+        }
+
+        if( bmhd->flags & BMHD_FLAGS_CMAPOK )
+            return;
+    }
+
+    if( !HAS_COLORMAP(cmap) )
+        return;
+
+    for( i = 0; i < cmap->ncolors; i++ ) {
+        r = PPM_GETR(cmap->color[i]);
+        if( r > colmaxval ) colmaxval = r;
+        if( r & 0x0f ) shifted = 0;
+
+        g = PPM_GETG(cmap->color[i]);
+        if( g > colmaxval ) colmaxval = g;
+        if( g & 0x0f ) shifted = 0;
+
+        b = PPM_GETB(cmap->color[i]);
+        if( b > colmaxval ) colmaxval = b;
+        if( b & 0x0f ) shifted = 0;
+    }
+
+#ifdef DEBUG
+    pm_message("colormap maxval is %d", colmaxval);
+#endif
+    if( colmaxval == 0 )
+        pm_message("warning - black colormap");
+    else
+    if( shifted || colmaxval <= 15 ) {
+        if( !adjustcolors ) {
+            pm_message("warning - probably %s4-bit colormap",
+                        shifted ? "shifted " : "");
+            pm_message("    use '-adjustcolors' to scale colormap to 8 bits");
+        }
+        else {
+            pm_message("scaling colormap to 8 bits");
+            for( i = 0; i < cmap->ncolors; i++ ) {
+                r = PPM_GETR(cmap->color[i]);
+                g = PPM_GETG(cmap->color[i]);
+                b = PPM_GETB(cmap->color[i]);
+                if( shifted ) {
+                    r >>= 4;
+                    g >>= 4;
+                    b >>= 4;
+                }
+                r *= FACTOR_4BIT;
+                g *= FACTOR_4BIT;
+                b *= FACTOR_4BIT;
+                PPM_ASSIGN(cmap->color[i], r, g, b);
+            }
+        }
+    }
+}
+
+
+static pixval
+lookup_red(cmap, oldval)
+    ColorMap *cmap;
+    int oldval;
+{
+    if( cmap && cmap->redlut && oldval < 256 )
+        return cmap->redlut[oldval];
+    else
+        return oldval;
+}
+
+static pixval
+lookup_green(cmap, oldval)
+    ColorMap *cmap;
+    int oldval;
+{
+    if( cmap && cmap->greenlut && oldval < 256 )
+        return cmap->greenlut[oldval];
+    else
+        return oldval;
+}
+
+static pixval
+lookup_blue(cmap, oldval)
+    ColorMap *cmap;
+    int oldval;
+{
+    if( cmap && cmap->bluelut && oldval < 256 )
+        return cmap->bluelut[oldval];
+    else
+        return oldval;
+}
+
+static pixval
+lookup_mono(cmap, oldval)
+    ColorMap *cmap;
+    int oldval;
+{
+    if( cmap && cmap->monolut && oldval < 256 )
+        return cmap->monolut[oldval];
+    else
+        return oldval;
+}
+
+static ColorMap *
+ehbcmap(cmap)
+    ColorMap *cmap;
+{
+    pixel *tempcolor = NULL;
+    int i, col;
+
+    col = cmap->ncolors;
+
+    tempcolor = ppm_allocrow(col * 2);
+    for( i = 0; i < col; i++ ) {
+        tempcolor[i] = cmap->color[i];
+        PPM_ASSIGN(tempcolor[col + i],  PPM_GETR(cmap->color[i]) / 2,
+                                        PPM_GETG(cmap->color[i]) / 2,
+                                        PPM_GETB(cmap->color[i]) / 2 );
+    }
+    ppm_freerow(cmap->color);
+    cmap->color = tempcolor;
+    cmap->ncolors *= 2;
+
+    return cmap;
+}
+
+
+
+static pixval
+lut_maxval(ColorMap * const cmap, 
+           pixval     const maxval) {
+
+    pixval retval;
+    
+    if (maxval >= 255)
+        retval = maxval;
+    else {
+        if (!HAS_COLORLUT(cmap))
+            retval = maxval;
+        else {
+            unsigned int i;
+            unsigned char maxlut;
+            maxlut = maxval;
+            for( i = 0; i < maxval; i++ ) {
+                if( cmap->redlut   && cmap->redlut[i]   > maxlut ) 
+                    maxlut = cmap->redlut[i];
+                if( cmap->greenlut && cmap->greenlut[i] > maxlut ) 
+                    maxlut = cmap->greenlut[i];
+                if( cmap->bluelut  && cmap->bluelut[i]  > maxlut ) 
+                    maxlut = cmap->bluelut[i];
+            }
+            pm_message("warning - "
+                       "%d-bit index into 8-bit color lookup table, "
+                       "table maxval=%d", 
+                       pm_maxvaltobits(maxval), maxlut);
+            if( maxlut != maxval )
+                retval = 255;
+            else
+                retval = maxval;
+            pm_message("    assuming image maxval=%d", retval);
+        }
+    }
+    return retval;
+}
+
+
+
+static void
+get_color(cmap, idx, red, green, blue)
+    ColorMap *cmap;
+    int idx;
+    pixval *red, *green, *blue;
+{
+    if( HAS_COLORMAP(cmap) ) {
+        pixval r, g, b;
+
+        if( idx >= cmap->ncolors )
+            pm_error("color index out of range: %d (max %d)", 
+                     idx, cmap->ncolors);
+        r = PPM_GETR(cmap->color[idx]);
+        g = PPM_GETG(cmap->color[idx]);
+        b = PPM_GETB(cmap->color[idx]);
+
+        *red    = lookup_red(cmap, r);
+        *green  = lookup_green(cmap, g);
+        *blue   = lookup_blue(cmap, b);
+    }
+    else {
+        *red = *green = *blue = lookup_mono(cmap, idx);
+    }
+}
+
+
+/****************************************************************************
+ Conversion functions
+ ****************************************************************************/
+
+static void
+std_to_ppm(FILE *         const ifp, 
+           long           const chunksize, 
+           BitMapHeader * const bmhd, 
+           ColorMap *     const cmap, 
+           long           const viewportmodes);
+
+static void
+ham_to_ppm(FILE *         const ifp, 
+           long           const chunksize, 
+           BitMapHeader * const bmhd, 
+           ColorMap *     const cmap, 
+           long           const viewportmodes) {
+
+    int cols, rows, hambits, hammask, hamshift, hammask2, col, row;
+    pixval maxval;
+    rawtype *rawrow;
+    unsigned char hamlut[256];
+
+    cols = bmhd->w;
+    rows = bmhd->h;
+    hambits = bmhd->nPlanes - 2;
+    hammask = (1 << hambits) - 1;
+    hamshift = 8 - hambits;
+    hammask2 = (1 << hamshift) - 1;
+
+    if( hambits > 8 || hambits < 1 ) {
+        int const assumed_viewportmodes = viewportmodes & ~(vmHAM);
+
+        pm_message("%d-plane HAM?? - interpreting image as a normal ILBM", 
+                   bmhd->nPlanes);
+        std_to_ppm(ifp, chunksize, bmhd, cmap, assumed_viewportmodes);
+        return;
+    } else {
+        unsigned long remainingChunksize;
+
+        pm_message("input is a %sHAM%d file", 
+                   HAS_MULTIPALETTE(cmap) ? "multipalette " : "", 
+                   bmhd->nPlanes);
+
+        if( HAS_COLORLUT(cmap) || HAS_MONOLUT(cmap) ) {
+            pm_message("warning - color lookup tables ignored in HAM");
+            if( cmap->redlut )      free(cmap->redlut);
+            if( cmap->greenlut )    free(cmap->greenlut);
+            if( cmap->bluelut )     free(cmap->bluelut);
+            if( cmap->monolut )     free(cmap->monolut);
+            cmap->redlut = cmap->greenlut = cmap->bluelut = 
+                cmap->monolut = NULL;
+        }
+        if( !HAS_COLORMAP(cmap) ) {
+            pm_message("no colormap - interpreting values as grayscale");
+            maxval = pm_bitstomaxval(hambits);
+            for( col = 0; col <= maxval; col++ )
+                hamlut[col] = col * MAXCOLVAL / maxval;
+            cmap->monolut = hamlut;
+        }
+
+        if( transpName ) {
+            MALLOCVAR_NOFAIL(transpColor);
+            *transpColor = ppm_parsecolor(transpName, MAXCOLVAL);
+        }
+
+        if( HAS_MULTIPALETTE(cmap) )
+            multi_init(cmap, viewportmodes);
+
+        rawrow = alloc_rawrow(cols);
+
+        ppm_writeppminit(stdout, cols, rows, MAXCOLVAL, 0);
+
+        remainingChunksize = chunksize;  /* initial value */
+
+        for (row = 0; row < rows; ++row) {
+            pixval r, g, b;
+
+            if( HAS_MULTIPALETTE(cmap) )
+                multi_update(cmap, row);
+
+            decode_row(ifp, &remainingChunksize, rawrow, bmhd->nPlanes, bmhd);
+            decode_mask(ifp, &remainingChunksize, rawrow, bmhd);
+
+            r = g = b = 0;
+            for( col = 0; col < cols; col++ ) {
+                int idx = rawrow[col] & hammask;
+
+                if( transpColor && maskrow && maskrow[col] == PBM_WHITE )
+                    pixelrow[col] = *transpColor;
+                else {
+                    switch((rawrow[col] >> hambits) & 0x03) {
+                    case HAMCODE_CMAP:
+                        get_color(cmap, idx, &r, &g, &b);
+                        break;
+                    case HAMCODE_BLUE:
+                        b = ((b & hammask2) | (idx << hamshift));
+                        /*b = hamlut[idx];*/
+                        break;
+                    case HAMCODE_RED:
+                        r = ((r & hammask2) | (idx << hamshift));
+                        /*r = hamlut[idx];*/
+                        break;
+                    case HAMCODE_GREEN:
+                        g = ((g & hammask2) | (idx << hamshift));
+                        /*g = hamlut[idx];*/
+                        break;
+                    default:
+                        pm_error("ham_to_ppm(): "
+                                 "impossible HAM code - can't happen");
+                    }
+                    PPM_ASSIGN(pixelrow[col], r, g, b);
+                }
+            }
+            ppm_writeppmrow(stdout, pixelrow, cols, MAXCOLVAL, 0);
+        }
+        chunk_end(ifp, ID_BODY, remainingChunksize);
+    }
+}
+
+
+
+static void
+std_to_ppm(FILE *         const ifP, 
+           long           const chunksize, 
+           BitMapHeader * const bmhd, 
+           ColorMap *     const cmap, 
+           long           const viewportmodes) {
+
+    if (viewportmodes & vmHAM) {
+        ham_to_ppm(ifP, chunksize, bmhd, cmap, viewportmodes);
+    } else {
+        unsigned int const cols = bmhd->w;
+        unsigned int const rows = bmhd->h;
+
+        rawtype *rawrow;
+        unsigned int row, col;
+        pixval maxval;
+        unsigned long remainingChunksize;
+
+        pm_message("input is a %d-plane %s%sILBM", bmhd->nPlanes,
+                   HAS_MULTIPALETTE(cmap) ? "multipalette " : "",
+                   viewportmodes & vmEXTRA_HALFBRITE ? "EHB " : ""
+            );
+
+        if( bmhd->nPlanes > MAXPLANES )
+            pm_error("too many planes (max %d)", MAXPLANES);
+
+        if( HAS_COLORMAP(cmap) ) {
+            if( viewportmodes & vmEXTRA_HALFBRITE )
+                ehbcmap(cmap);  /* Modifies *cmap */
+            maxval = MAXCOLVAL;
+        }
+        else {
+            pm_message("no colormap - interpreting values as grayscale");
+            maxval = lut_maxval(cmap, pm_bitstomaxval(bmhd->nPlanes));
+            if( maxval > PPM_OVERALLMAXVAL )
+                pm_error("nPlanes is too large");
+        }
+
+        if( transpName ) {
+            MALLOCVAR_NOFAIL(transpColor);
+            *transpColor = ppm_parsecolor(transpName, maxval);
+        }
+
+        rawrow = alloc_rawrow(cols);
+
+        if( HAS_MULTIPALETTE(cmap) )
+            multi_init(cmap, viewportmodes);
+
+        ppm_writeppminit( stdout, cols, rows, maxval, 0 );
+
+        remainingChunksize = chunksize;  /* initial value */
+    
+        for (row = 0; row < rows; ++row) {
+
+            if( HAS_MULTIPALETTE(cmap) )
+                multi_update(cmap, row);
+
+            decode_row(ifP, &remainingChunksize, rawrow, bmhd->nPlanes, bmhd);
+            decode_mask(ifP, &remainingChunksize, rawrow, bmhd);
+
+            for( col = 0; col < cols; col++ ) {
+                pixval r, g, b;
+                if( transpColor && maskrow && maskrow[col] == PBM_WHITE )
+                    pixelrow[col] = *transpColor;
+                else {
+                    get_color(cmap, rawrow[col], &r, &g, &b);
+                    PPM_ASSIGN(pixelrow[col], r, g, b);
+                }
+            }
+            ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
+        }
+        chunk_end(ifP, ID_BODY, remainingChunksize);
+    }
+}
+
+
+
+static void
+deep_to_ppm(FILE *         const ifP, 
+            long           const chunksize, 
+            BitMapHeader * const bmhdP, 
+            ColorMap *     const cmap) {
+
+    unsigned int const cols = bmhdP->w;
+    unsigned int const rows = bmhdP->h;
+    unsigned int const planespercolor = bmhdP->nPlanes / 3;
+
+    unsigned int col, row;
+    rawtype *Rrow, *Grow, *Brow;
+    pixval maxval;
+    unsigned long remainingChunksize;
+
+    pm_message("input is a deep (%d-bit) ILBM", bmhdP->nPlanes);
+    if( planespercolor > MAXPLANES )
+        pm_error("too many planes (max %d)", MAXPLANES * 3);
+
+    if( bmhdP->masking == mskHasTransparentColor || 
+        bmhdP->masking == mskLasso ) {
+        pm_message("masking type '%s' in a deep ILBM?? - ignoring it", 
+                   mskNAME[bmhdP->masking]);
+        bmhdP->masking = mskNone;
+    }
+
+    maxval = lut_maxval(cmap, pm_bitstomaxval(planespercolor));
+    if( maxval > PPM_OVERALLMAXVAL )
+        pm_error("nPlanes is too large");
+
+    if( transpName ) {
+        MALLOCVAR_NOFAIL(transpColor);
+        *transpColor = ppm_parsecolor(transpName, maxval);
+    }
+
+    Rrow = alloc_rawrow(cols);
+    Grow = alloc_rawrow(cols);
+    Brow = alloc_rawrow(cols);
+
+    ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+    remainingChunksize = chunksize;  /* initial value */
+
+    for( row = 0; row < rows; row++ ) {
+        decode_row(ifP, &remainingChunksize, Rrow, planespercolor, bmhdP);
+        decode_row(ifP, &remainingChunksize, Grow, planespercolor, bmhdP);
+        decode_row(ifP, &remainingChunksize, Brow, planespercolor, bmhdP);
+        decode_mask(ifP, &remainingChunksize, NULL, bmhdP);
+
+        for( col = 0; col < cols; col++ ) {
+            if( transpColor && maskrow && maskrow[col] == PBM_WHITE )
+                pixelrow[col] = *transpColor;
+            else
+                PPM_ASSIGN(pixelrow[col],   lookup_red(cmap, Rrow[col]),
+                                            lookup_green(cmap, Grow[col]),
+                                            lookup_blue(cmap, Brow[col]) );
+        }
+        ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
+    }
+    chunk_end(ifP, ID_BODY, remainingChunksize);
+}
+
+
+
+static void
+dcol_to_ppm(FILE *         const ifP,
+            long           const chunksize,
+            BitMapHeader * const bmhdP,
+            ColorMap *     const cmap,
+            DirectColor *  const dcol) {
+
+    unsigned int const cols = bmhdP->w;
+    unsigned int const rows = bmhdP->h;
+    unsigned int const redplanes   = dcol->r;
+    unsigned int const greenplanes = dcol->g;
+    unsigned int const blueplanes  = dcol->b;
+    
+    int col, row, i;
+    rawtype *Rrow, *Grow, *Brow;
+    pixval maxval, redmaxval, greenmaxval, bluemaxval;
+    pixval *redtable, *greentable, *bluetable;
+    unsigned long remainingChunksize;
+
+    pm_message("input is a %d:%d:%d direct color ILBM",
+                redplanes, greenplanes, blueplanes);
+
+    if( redplanes > MAXPLANES || 
+        blueplanes > MAXPLANES || 
+        greenplanes > MAXPLANES )
+        pm_error("too many planes (max %d per color component)", MAXPLANES);
+
+    if( bmhdP->nPlanes != (redplanes + greenplanes + blueplanes) )
+        pm_error("%s/%s plane number mismatch", 
+                 ID2string(ID_BMHD), ID2string(ID_DCOL));
+
+    if( bmhdP->masking == mskHasTransparentColor || 
+        bmhdP->masking == mskLasso ) {
+        pm_message("masking type '%s' in a direct color ILBM?? - ignoring it",
+                   mskNAME[bmhdP->masking]);
+        bmhdP->masking = mskNone;
+    }
+
+    if( HAS_COLORLUT(cmap) ) {
+        pm_error("This program does not know how to process a %s chunk "
+                 "in direct color", ID2string(ID_CLUT));
+        cmap->redlut = cmap->greenlut = cmap->bluelut = NULL;
+    }
+
+    redmaxval   = pm_bitstomaxval(redplanes);
+    greenmaxval = pm_bitstomaxval(greenplanes);
+    bluemaxval  = pm_bitstomaxval(blueplanes);
+    maxval = MAX(redmaxval, MAX(greenmaxval, bluemaxval));
+
+    if( maxval > PPM_OVERALLMAXVAL )
+        pm_error("too many planes");
+
+    if( redmaxval != maxval || greenmaxval != maxval || bluemaxval != maxval )
+        pm_message("scaling colors to %d bits", pm_maxvaltobits(maxval));
+    
+    MALLOCARRAY_NOFAIL(redtable,   redmaxval   +1);
+    MALLOCARRAY_NOFAIL(greentable, greenmaxval +1);
+    MALLOCARRAY_NOFAIL(bluetable,  bluemaxval  +1);
+
+    for( i = 0; i <= redmaxval; i++ )
+        redtable[i] = (i * maxval + redmaxval/2)/redmaxval;
+    for( i = 0; i <= greenmaxval; i++ )
+        greentable[i] = (i * maxval + greenmaxval/2)/greenmaxval;
+    for( i = 0; i <= bluemaxval; i++ )
+        bluetable[i] = (i * maxval + bluemaxval/2)/bluemaxval;
+
+    if( transpName ) {
+        MALLOCVAR_NOFAIL(transpColor);
+        *transpColor = ppm_parsecolor(transpName, maxval);
+    }
+
+    Rrow = alloc_rawrow(cols);
+    Grow = alloc_rawrow(cols);
+    Brow = alloc_rawrow(cols);
+
+    ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+    remainingChunksize = chunksize;  /* initial value */
+
+    for( row = 0; row < rows; row++ ) {
+        decode_row(ifP, &remainingChunksize, Rrow, redplanes, bmhdP);
+        decode_row(ifP, &remainingChunksize, Grow, greenplanes, bmhdP);
+        decode_row(ifP, &remainingChunksize, Brow, blueplanes, bmhdP);
+        decode_mask(ifP, &remainingChunksize, NULL, bmhdP);
+
+        for( col = 0; col < cols; col++ ) {
+            if( transpColor && maskrow && maskrow[col] == PBM_WHITE )
+                pixelrow[col] = *transpColor;
+            else
+                PPM_ASSIGN( pixelrow[col],  redtable[Rrow[col]],
+                                            greentable[Grow[col]],
+                                            bluetable[Brow[col]]   );
+        }
+        ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
+    }
+    chunk_end(ifP, ID_BODY, remainingChunksize);
+}
+
+
+
+static void
+cmap_to_ppm(cmap)
+    ColorMap *cmap;
+{
+    ppm_colorrowtomapfile(stdout, cmap->color, cmap->ncolors, MAXCOLVAL);
+#if 0
+    int i;
+    ppm_writeppminit(stdout, cmap->ncolors, 1, MAXCOLVAL, 1);
+    for( i = 0; i < cmap->ncolors; i++ )
+        ppm_writeppmrow(stdout, &(cmap->color[i]), 1, MAXCOLVAL, 1);
+#endif
+}
+
+
+
+static void
+ipbm_to_ppm(FILE *         const ifP,
+            long           const chunksize,
+            BitMapHeader * const bmhdP,
+            ColorMap *     const cmap,
+            long           const viewportmodes) {
+
+    unsigned int const cols = bmhdP->w;
+    unsigned int const rows = bmhdP->h;
+
+    int col, row;
+    pixval maxval;
+    unsigned long remainingChunksize;
+
+    pm_message("input is a %sPBM ", 
+               HAS_MULTIPALETTE(cmap) ? "multipalette " : "");
+
+    if( bmhdP->nPlanes != 8 )
+        pm_error("invalid number of planes for IFF-PBM: %d (must be 8)", 
+                 bmhdP->nPlanes);
+
+    if( bmhdP->masking == mskHasMask )
+        pm_error("Image has a maskplane, which is invalid in IFF-PBM");
+
+    if( HAS_COLORMAP(cmap) )
+        maxval = MAXCOLVAL;
+    else {
+        pm_message("no colormap - interpreting values as grayscale");
+        maxval = lut_maxval(cmap, pm_bitstomaxval(bmhdP->nPlanes));
+    }
+
+    if( transpName ) {
+        MALLOCVAR_NOFAIL(transpColor);
+        *transpColor = ppm_parsecolor(transpName, maxval);
+    }
+
+    if( HAS_MULTIPALETTE(cmap) )
+        multi_init(cmap, viewportmodes);
+
+    MALLOCARRAY_NOFAIL(ilbmrow, cols);
+
+    ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+    remainingChunksize = chunksize;  /* initial value */
+
+    for( row = 0; row < rows; row++ ) {
+        if( HAS_MULTIPALETTE(cmap) )
+            multi_update(cmap, row);
+        
+        read_ilbm_plane(ifP, &remainingChunksize, cols, bmhdP->compression);
+
+        for( col = 0; col < cols; col++ ) {
+            pixval r, g, b;
+            if( transpColor && maskrow && maskrow[col] == PBM_WHITE )
+                pixelrow[col] = *transpColor;
+            else {
+                get_color(cmap, ilbmrow[col], &r, &g, &b);
+                PPM_ASSIGN(pixelrow[col], r, g, b);
+            }
+        }
+        ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
+    }
+    chunk_end(ifP, ID_BODY, remainingChunksize);
+}
+
+
+
+static void
+rgbn_to_ppm(FILE *         const ifP,
+            long           const chunksize,
+            BitMapHeader * const bmhdP,
+            ColorMap *     const cmap  /* for CLUTs */
+    ) {
+
+    unsigned int const rows = bmhdP->h;
+    unsigned int const cols = bmhdP->w;
+
+    int row, col, count, genlock, tries;
+    pixval r, g, b, maxval;
+    unsigned long remainingChunksize;
+
+    pm_message("input is a %d-bit RGB image", (typeid == ID_RGB8 ? 8 : 4));
+
+    if( bmhdP->compression != 4 )
+        pm_error("invalid compression mode for %s: %d (must be 4)", 
+                 ID2string(typeid), bmhdP->compression);
+
+    switch( typeid ) {
+        case ID_RGBN:
+            if( bmhdP->nPlanes != 13 )
+                pm_error("invalid number of planes for %s: %d (must be 13)", 
+                         ID2string(typeid), bmhdP->nPlanes);
+            maxval = lut_maxval(cmap, 15);
+            break;
+        case ID_RGB8:
+            if( bmhdP->nPlanes != 25 )
+                pm_error("invalid number of planes for %s: %d (must be 25)", 
+                         ID2string(typeid), bmhdP->nPlanes);
+            maxval = 255;
+            break;
+        default:
+            pm_error("rgbn_to_ppm(): invalid IFF ID %s - can't happen", 
+                     ID2string(typeid));
+    }
+
+    if( transpName ) {
+        MALLOCVAR_NOFAIL(transpColor);
+        *transpColor = ppm_parsecolor(transpName, maxval);
+    }
+
+    ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+    remainingChunksize = chunksize;  /* initial value */
+    count = 0;
+    for( row = 0; row < rows; row++ ) {
+        for( col = 0; col < cols; col++ ) {
+            tries = 0;
+            while( !count ) {
+                if( typeid == ID_RGB8 ) {
+                    r = lookup_red(cmap,   get_byte(ifP, ID_BODY, 
+                                                    &remainingChunksize));
+                    g = lookup_green(cmap, get_byte(ifP, ID_BODY,
+                                                    &remainingChunksize));
+                    b = lookup_blue(cmap,  get_byte(ifP, ID_BODY,
+                                                    &remainingChunksize));
+                    count = get_byte(ifP, ID_BODY, &remainingChunksize);
+                    genlock = count & 0x80;
+                    count &= 0x7f;
+                }
+                else {
+                    int word;
+                    word = get_big_short(ifP, ID_BODY, &remainingChunksize);
+                    r = lookup_red(cmap, (word & 0xf000) >> 12);
+                    g = lookup_green(cmap, (word & 0x0f00) >> 8);
+                    b = lookup_blue(cmap, (word & 0x00f0) >> 4);
+                    genlock = word & 0x0008;
+                    count = word & 0x0007;
+                }
+                if( !count ) {
+                    count = get_byte(ifP, ID_BODY, &remainingChunksize);
+                    if( !count )
+                        count =
+                            get_big_short(ifP, ID_BODY, &remainingChunksize);
+                        if( !count )
+                            ++tries;
+                }
+            }
+            if( tries ) {
+                pm_message("warning - repeat count 0 at col %d row %d: "
+                           "skipped %d RGB entr%s",
+                            col, row, tries, (tries == 1 ? "y" : "ies"));
+            }
+            if( maskfile ) {
+                /* genlock bit set -> transparent */
+                if( genlock )
+                    maskrow[col] = PBM_WHITE;
+                else
+                    maskrow[col] = PBM_BLACK;
+            }
+            if( transpColor && maskrow && maskrow[col] == PBM_WHITE )
+                pixelrow[col] = *transpColor;
+            else
+                PPM_ASSIGN(pixelrow[col], r, g, b);
+            --count;
+        }
+        ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
+        if( maskfile ) {
+            pbm_writepbmrow(maskfile, maskrow, cols, 0);
+            wrotemask = 1;
+        }
+    }
+    chunk_end(ifP, ID_BODY, remainingChunksize);
+}
+
+/****************************************************************************
+ Multipalette chunk reader
+
+    Currently there are three multipalette formats:
+        SHAM - sliced HAM (obselete)
+        CTBL - dynamic HAM/Hires (obselete)
+        PCHG - palette change
+    There is no official documentation available for SHAM and CTBL, so
+   this is mostly guesswork from other sources and hexdumps of pictures...
+
+    CTBL format (dynamic HAM/Hires):
+        16 bigendian words per row (16 bit: 0000rrrrggggbbbb)
+    This is simply an entire 4-bit colormap per row.
+    I have no idea what the DYCP chunk is for.
+
+    SHAM format (sliced HAM):
+        1 word  - version info (?) - always 0
+        16 bigendian words per row (16 bit: 0000rrrrggggbbbb)
+    If the picture is laced, then there are only rows/2 changes, don't change
+    palette on odd lines.
+
+    PCHG format: A detailed description of this format is available
+    from AmiNet as PCHGLib12.lha.
+
+ ****************************************************************************/
+
+/* Turn big-endian 4-byte long and 2-byte short stored at x (unsigned char *)
+ * into the native format of the CPU
+ */
+#define BIG_LONG(x) (   ((unsigned long)((x)[0]) << 24) + \
+                        ((unsigned long)((x)[1]) << 16) + \
+                        ((unsigned long)((x)[2]) <<  8) + \
+                        ((unsigned long)((x)[3]) <<  0) )
+#define BIG_WORD(x) (   ((unsigned short)((x)[0]) << 8) + \
+                        ((unsigned short)((x)[1]) << 0) )
+
+
+
+static void
+read_4bit_mp(FILE *     const ifP,
+             IFF_ID     const iffid,
+             long       const chunksize,
+             ColorMap * const cmap) {
+
+    int row, rows, i, type;
+    short data;
+    unsigned long remainingChunksize;
+
+    type = (iffid == ID_SHAM ? MP_TYPE_SHAM : MP_TYPE_CTBL);
+
+    if( cmap->mp_type >= type ) {
+        skip_chunk(ifP, iffid, chunksize);
+    } else {
+        if( cmap->mp_type )
+            multi_free(cmap);
+        cmap->mp_type = type;
+
+        remainingChunksize = chunksize;  /* initial value */
+
+        if( type == MP_TYPE_SHAM ) {
+            cmap->mp_flags = MP_FLAGS_SKIPLACED;
+            get_big_short(ifP, iffid, &remainingChunksize); /* skip first wd */
+        }
+
+        cmap->mp_rows = rows = remainingChunksize/32; /* sizeof(word) * 16 */
+        MALLOCARRAY_NOFAIL(cmap->mp_change, rows);
+
+        for( row = 0; row < rows; row++ ) {
+            MALLOCARRAY_NOFAIL(cmap->mp_change[row], 17);   /* 16 + sentinel */
+            for( i = 0; i < 16; i++ ) {
+                data = get_big_short(ifP, iffid, &remainingChunksize);
+                cmap->mp_change[row][i].reg = i;
+                cmap->mp_change[row][i].r =
+                    ((data & 0x0f00) >> 8) * FACTOR_4BIT;
+                cmap->mp_change[row][i].g =
+                    ((data & 0x00f0) >> 4) * FACTOR_4BIT;
+                cmap->mp_change[row][i].b =
+                    ((data & 0x000f) >> 0) * FACTOR_4BIT;
+            }
+            cmap->mp_change[row][16].reg = MP_REG_END;   /* sentinel */
+        }
+        chunk_end(ifP, iffid, remainingChunksize);
+    }
+}
+
+
+
+static void
+PCHG_DecompHuff(src, dest, tree, origsize)
+    unsigned char *src, *dest;
+    short *tree;
+    unsigned long origsize;
+{
+    unsigned long i = 0, bits = 0;
+    unsigned char thisbyte;
+    short *p;
+
+    p = tree;
+    while( i < origsize ) {
+        if( bits == 0 ) {
+            thisbyte = *src++;
+            bits = 8;
+        }
+        if( thisbyte & (1 << 7) ) {
+            if( *p >= 0 ) {
+                *dest++ = (unsigned char)*p;
+                i++;
+                p = tree;
+            }
+            else
+                p += (*p / 2);
+        }
+        else {
+            p--;
+            if( *p > 0 && (*p & 0x100) ) {
+                *dest++ = (unsigned char )*p;
+                i++;
+                p = tree;
+            }
+        }
+        thisbyte <<= 1;
+        bits--;
+    }
+}
+
+
+static void
+PCHG_Decompress(PCHG, CompHdr, compdata, compsize, comptree, data)
+    PCHGHeader *PCHG;
+    PCHGCompHeader *CompHdr;
+    unsigned char *compdata;
+    unsigned long compsize;
+    unsigned char *comptree;
+    unsigned char *data;
+{
+    short *hufftree;
+    unsigned long huffsize, i;
+    unsigned long treesize = CompHdr->CompInfoSize;
+
+    switch( PCHG->Compression ) {
+        case PCHG_COMP_HUFFMAN:
+
+#ifdef DEBUG
+            pm_message("PCHG Huffman compression");
+#endif
+            /* turn big-endian 2-byte shorts into native format */
+            huffsize = treesize/2;
+            MALLOCVAR_NOFAIL(hufftree);
+            for( i = 0; i < huffsize; i++ ) {
+                hufftree[i] = (short)BIG_WORD(comptree);
+                comptree += 2;
+            }
+
+            /* decompress the change structure data */
+            PCHG_DecompHuff(compdata, data, &hufftree[huffsize-1], 
+                            CompHdr->OriginalDataSize);
+
+            free(hufftree);
+            break;
+        default:
+            pm_error("unknown PCHG compression type %d", PCHG->Compression);
+    }
+}
+
+
+static void
+PCHG_ConvertSmall(PCHG, cmap, mask, datasize)
+    PCHGHeader *PCHG;
+    ColorMap *cmap;
+    unsigned char *mask;
+    unsigned long datasize;
+{
+    unsigned char *data;
+    unsigned char thismask;
+    int bits, row, i, changes, masklen, reg;
+    unsigned char ChangeCount16, ChangeCount32;
+    unsigned short SmallChange;
+    unsigned long totalchanges = 0;
+    int changedlines = PCHG->ChangedLines;
+
+    masklen = 4 * MaskLongWords(PCHG->LineCount);
+    data = mask + masklen; datasize -= masklen;
+
+    bits = 0;
+    for( row = PCHG->StartLine; changedlines && row < 0; row++ ) {
+        if( bits == 0 ) {
+            if( masklen == 0 ) goto fail2;
+            thismask = *mask++;
+            --masklen;
+            bits = 8;
+        }
+        if( thismask & (1<<7) ) {
+            if( datasize < 2 ) goto fail;
+            ChangeCount16 = *data++;
+            ChangeCount32 = *data++;
+            datasize -= 2;
+
+            changes = ChangeCount16 + ChangeCount32;
+            for( i = 0; i < changes; i++ ) {
+                if( totalchanges >= PCHG->TotalChanges ) goto fail;
+                if( datasize < 2 ) goto fail;
+                SmallChange = BIG_WORD(data); data += 2; datasize -= 2;
+                reg = ((SmallChange & 0xf000) >> 12) + 
+                    (i >= ChangeCount16 ? 16 : 0);
+                cmap->mp_init[reg - PCHG->MinReg].reg = reg;
+                cmap->mp_init[reg - PCHG->MinReg].r = 
+                    ((SmallChange & 0x0f00) >> 8) * FACTOR_4BIT;
+                cmap->mp_init[reg - PCHG->MinReg].g = 
+                    ((SmallChange & 0x00f0) >> 4) * FACTOR_4BIT;
+                cmap->mp_init[reg - PCHG->MinReg].b = 
+                    ((SmallChange & 0x000f) >> 0) * FACTOR_4BIT;
+                ++totalchanges;
+            }
+            --changedlines;
+        }
+        thismask <<= 1;
+        bits--;
+    }
+
+    for( row = PCHG->StartLine; changedlines && row < cmap->mp_rows; row++ ) {
+        if( bits == 0 ) {
+            if( masklen == 0 ) goto fail2;
+            thismask = *mask++;
+            --masklen;
+            bits = 8;
+        }
+        if( thismask & (1<<7) ) {
+            if( datasize < 2 ) goto fail;
+            ChangeCount16 = *data++;
+            ChangeCount32 = *data++;
+            datasize -= 2;
+
+            changes = ChangeCount16 + ChangeCount32;
+            MALLOCARRAY_NOFAIL(cmap->mp_change[row], changes + 1);
+            for( i = 0; i < changes; i++ ) {
+                if( totalchanges >= PCHG->TotalChanges ) goto fail;
+                if( datasize < 2 ) goto fail;
+                SmallChange = BIG_WORD(data); data += 2; datasize -= 2;
+                reg = ((SmallChange & 0xf000) >> 12) + 
+                    (i >= ChangeCount16 ? 16 : 0);
+                cmap->mp_change[row][i].reg = reg;
+                cmap->mp_change[row][i].r = 
+                    ((SmallChange & 0x0f00) >> 8) * FACTOR_4BIT;
+                cmap->mp_change[row][i].g = 
+                    ((SmallChange & 0x00f0) >> 4) * FACTOR_4BIT;
+                cmap->mp_change[row][i].b = 
+                    ((SmallChange & 0x000f) >> 0) * FACTOR_4BIT;
+                ++totalchanges;
+            }
+            cmap->mp_change[row][changes].reg = MP_REG_END;
+            --changedlines;
+        }
+        thismask <<= 1;
+        bits--;
+    }
+    if( totalchanges != PCHG->TotalChanges )
+        pm_message("warning - got %ld change structures, "
+                   "chunk header reports %ld", 
+                   totalchanges, PCHG->TotalChanges);
+    return;
+fail:
+    pm_error("insufficient data in SmallLineChanges structures");
+fail2:
+    pm_error("insufficient data in line mask");
+}
+
+
+static void
+PCHG_ConvertBig(PCHG, cmap, mask, datasize)
+    PCHGHeader *PCHG;
+    ColorMap *cmap;
+    unsigned char *mask;
+    unsigned long datasize;
+{
+    unsigned char *data;
+    unsigned char thismask;
+    int bits, row, i, changes, masklen, reg;
+    unsigned long totalchanges = 0;
+    int changedlines = PCHG->ChangedLines;
+
+    masklen = 4 * MaskLongWords(PCHG->LineCount);
+    data = mask + masklen; datasize -= masklen;
+
+    bits = 0;
+    for( row = PCHG->StartLine; changedlines && row < 0; row++ ) {
+        if( bits == 0 ) {
+            if( masklen == 0 ) goto fail2;
+            thismask = *mask++;
+            --masklen;
+            bits = 8;
+        }
+        if( thismask & (1<<7) ) {
+            if( datasize < 2 ) goto fail;
+            changes = BIG_WORD(data); data += 2; datasize -= 2;
+
+            for( i = 0; i < changes; i++ ) {
+                if( totalchanges >= PCHG->TotalChanges ) goto fail;
+                if( datasize < 6 ) goto fail;
+                reg = BIG_WORD(data); data += 2;
+                cmap->mp_init[reg - PCHG->MinReg].reg = reg;
+                ++data; /* skip alpha */
+                cmap->mp_init[reg - PCHG->MinReg].r = *data++;
+                cmap->mp_init[reg - PCHG->MinReg].b = *data++;  /* yes, RBG */
+                cmap->mp_init[reg - PCHG->MinReg].g = *data++;
+                datasize -= 6;
+                ++totalchanges;
+            }
+            --changedlines;
+        }
+        thismask <<= 1;
+        bits--;
+    }
+
+    for( row = PCHG->StartLine; changedlines && row < cmap->mp_rows; row++ ) {
+        if( bits == 0 ) {
+            if( masklen == 0 ) goto fail2;
+            thismask = *mask++;
+            --masklen;
+            bits = 8;
+        }
+        if( thismask & (1<<7) ) {
+            if( datasize < 2 ) goto fail;
+            changes = BIG_WORD(data); data += 2; datasize -= 2;
+
+            MALLOCARRAY_NOFAIL(cmap->mp_change[row], changes + 1);
+            for( i = 0; i < changes; i++ ) {
+                if( totalchanges >= PCHG->TotalChanges ) goto fail;
+                if( datasize < 6 ) goto fail;
+                reg = BIG_WORD(data); data += 2;
+                cmap->mp_change[row][i].reg = reg;
+                ++data; /* skip alpha */
+                cmap->mp_change[row][i].r = *data++;
+                cmap->mp_change[row][i].b = *data++;    /* yes, RBG */
+                cmap->mp_change[row][i].g = *data++;
+                datasize -= 6;
+                ++totalchanges;
+            }
+            cmap->mp_change[row][changes].reg = MP_REG_END;
+            --changedlines;
+        }
+        thismask <<= 1;
+        bits--;
+    }
+    if( totalchanges != PCHG->TotalChanges )
+        pm_message("warning - got %ld change structures, "
+                   "chunk header reports %ld", 
+                   totalchanges, PCHG->TotalChanges);
+    return;
+fail:
+    pm_error("insufficient data in BigLineChanges structures");
+fail2:
+    pm_error("insufficient data in line mask");
+}
+
+
+static void
+read_pchg(FILE *     const ifp,
+          IFF_ID     const iffid,
+          long       const chunksize,
+          ColorMap * const cmap) {
+
+    if( cmap->mp_type >= MP_TYPE_PCHG ) {
+        skip_chunk(ifp, iffid, chunksize);
+    } else {
+        PCHGHeader      PCHG;
+        unsigned char   *data;
+        unsigned long   datasize;
+        unsigned long   remainingChunksize;
+        int i;
+
+        if( cmap->mp_type )
+            multi_free(cmap);
+        cmap->mp_type = MP_TYPE_PCHG;
+
+        remainingChunksize = chunksize;  /* initial value */
+
+        PCHG.Compression = get_big_short(ifp, iffid, &remainingChunksize);
+        PCHG.Flags       = get_big_short(ifp, iffid, &remainingChunksize);
+        PCHG.StartLine   = get_big_short(ifp, iffid, &remainingChunksize);
+        PCHG.LineCount   = get_big_short(ifp, iffid, &remainingChunksize);
+        PCHG.ChangedLines= get_big_short(ifp, iffid, &remainingChunksize);
+        PCHG.MinReg      = get_big_short(ifp, iffid, &remainingChunksize);
+        PCHG.MaxReg      = get_big_short(ifp, iffid, &remainingChunksize);
+        PCHG.MaxChanges  = get_big_short(ifp, iffid, &remainingChunksize);
+        PCHG.TotalChanges= get_big_long(ifp, iffid, &remainingChunksize);
+
+#ifdef DEBUG
+        pm_message("PCHG StartLine   : %d", PCHG.StartLine);
+        pm_message("PCHG LineCount   : %d", PCHG.LineCount);
+        pm_message("PCHG ChangedLines: %d", PCHG.ChangedLines);
+        pm_message("PCHG TotalChanges: %d", PCHG.TotalChanges);
+#endif
+
+        if( PCHG.Compression != PCHG_COMP_NONE ) {
+            PCHGCompHeader  CompHdr;
+            unsigned char *compdata, *comptree;
+            long treesize, compsize;
+
+            CompHdr.CompInfoSize     =
+                get_big_long(ifp, iffid, &remainingChunksize);
+            CompHdr.OriginalDataSize =
+                get_big_long(ifp, iffid, &remainingChunksize);
+
+            treesize = CompHdr.CompInfoSize;
+            MALLOCVAR_NOFAIL(comptree);
+            read_bytes(ifp, treesize, comptree, iffid, &remainingChunksize);
+
+            compsize = remainingChunksize;
+            MALLOCARRAY_NOFAIL(compdata, remainingChunksize);
+            read_bytes(ifp, compsize, compdata, iffid, &remainingChunksize);
+
+            datasize = CompHdr.OriginalDataSize;
+            MALLOCARRAY_NOFAIL(data, datasize);
+            PCHG_Decompress(&PCHG, &CompHdr, compdata, 
+                            compsize, comptree, data);
+
+            free(comptree);
+            free(compdata);
+        } else {
+#ifdef DEBUG
+            pm_message("uncompressed PCHG");
+#endif
+            datasize = remainingChunksize;
+            MALLOCARRAY_NOFAIL(data, datasize);
+            read_bytes(ifp, datasize, data, iffid, &remainingChunksize);
+        }
+
+        if( PCHG.Flags & PCHGF_USE_ALPHA )
+            pm_message("warning - ignoring PCHG alpha channel because "
+                       "this program doesn't know what to do with it");
+
+        cmap->mp_rows = PCHG.StartLine + PCHG.LineCount;
+        MALLOCARRAY_NOFAIL(cmap->mp_change, cmap->mp_rows);
+        for( i = 0; i < cmap->mp_rows; i++ )
+            cmap->mp_change[i] = NULL;
+        if( PCHG.StartLine < 0 ) {
+            int nch;
+            nch = PCHG.MaxReg - PCHG.MinReg +1;
+            MALLOCARRAY_NOFAIL(cmap->mp_init, nch + 1);
+            for( i = 0; i < nch; i++ )
+                cmap->mp_init[i].reg = MP_REG_IGNORE;
+            cmap->mp_init[nch].reg = MP_REG_END;
+        }
+
+        if( PCHG.Flags & PCHGF_12BIT ) {
+#ifdef DEBUG
+            pm_message("SmallLineChanges");
+#endif
+            PCHG_ConvertSmall(&PCHG, cmap, data, datasize);
+        }
+        else {
+            if( PCHG.Flags & PCHGF_32BIT ) {
+#ifdef DEBUG
+                pm_message("BigLineChanges");
+#endif
+                PCHG_ConvertBig(&PCHG, cmap, data, datasize);
+            }
+            else
+                pm_error("unknown palette changes structure "
+                         "format in %s chunk", 
+                         ID2string(iffid));
+        }
+        free(data);
+        chunk_end(ifp, iffid, remainingChunksize);
+    }
+}
+
+
+
+static bool
+ignored_iffid(IFF_ID       const iffid,
+              IFF_ID       const ignorelist[],
+              unsigned int const ignorecount) {
+
+    bool ignore;
+
+    unsigned int i;
+    ignore = FALSE;  /* initial assumption */
+    for( i = 0; i < ignorecount && !ignore; i++ ) {
+        if( iffid == ignorelist[i] )
+            ignore = TRUE;
+    }
+    return ignore;
+}
+
+
+
+static void 
+process_body( FILE *          const ifp,
+              long            const chunksize,
+              BitMapHeader *  const bmhdP,
+              ColorMap *      const cmap,
+              FILE *          const maskfile,
+              int             const fakeviewport,
+              int             const isdeepopt,
+              DirectColor *   const dcol,
+              int *           const viewportmodesP) {
+    
+    if( bmhdP == NULL )
+        pm_error("%s chunk without %s chunk", 
+                 ID2string(ID_BODY), ID2string(ID_BMHD));
+
+    check_cmap(bmhdP, cmap);
+
+    pixelrow = ppm_allocrow(bmhdP->w);
+    if( maskfile ) {
+        maskrow = pbm_allocrow(bmhdP->w);
+        pbm_writepbminit(maskfile, bmhdP->w, bmhdP->h, 0);
+    }
+
+    if( typeid == ID_ILBM ) {
+        int isdeep;
+
+        MALLOCARRAY_NOFAIL(ilbmrow, RowBytes(bmhdP->w));
+        *viewportmodesP |= fakeviewport;      /* -isham/-isehb */
+
+        if( isdeepopt > 0 && (bmhdP->nPlanes % 3 != 0) ) {
+            pm_message("cannot interpret %d-plane image as 'deep' "
+                       "(# of planes must be divisible by 3)", 
+                       bmhdP->nPlanes);
+            isdeep = 0;
+        } else
+            isdeep = isdeepopt;
+        
+        if( isdeep > 0 )
+            deep_to_ppm(ifp, chunksize, bmhdP, cmap);
+        else if( dcol )
+            dcol_to_ppm(ifp, chunksize, bmhdP, cmap, dcol);
+        else if( bmhdP->nPlanes > 8 ) {
+            if( bmhdP->nPlanes <= 16 && HAS_COLORMAP(cmap) )
+                std_to_ppm(ifp, chunksize, bmhdP, cmap, *viewportmodesP);
+            else if( isdeep >= 0 && (bmhdP->nPlanes % 3 == 0) )
+                deep_to_ppm(ifp, chunksize, bmhdP, cmap);
+            else if( bmhdP->nPlanes <= 16 )   
+                /* will be interpreted as grayscale */
+                std_to_ppm(ifp, chunksize, bmhdP, cmap, *viewportmodesP);
+            else
+                pm_error("don't know how to interpret %d-plane image", 
+                         bmhdP->nPlanes);
+        } else
+            std_to_ppm(ifp, chunksize, bmhdP, cmap, *viewportmodesP);
+    } else if( typeid == ID_PBM )
+        ipbm_to_ppm(ifp, chunksize, bmhdP, cmap, *viewportmodesP);
+    else   /* RGBN or RGB8 */
+        rgbn_to_ppm(ifp, chunksize, bmhdP, cmap);
+}
+
+
+
+static void 
+process_chunk(FILE *          const ifp,
+              long            const formsize,
+              IFF_ID          const ignorelist[],
+              unsigned int    const ignorecount,
+              int             const fakeviewport,
+              int             const viewportmask,
+              int             const isdeepopt,
+              bool            const cmaponly,
+              bool *          const bodyChunkProcessedP,
+              bool *          const endchunkP,
+              BitMapHeader ** const bmhdP,
+              ColorMap *      const cmap,
+              DirectColor **  const dcolP,
+              int *           const viewportmodesP,
+              long *          const bytesReadP
+    ) {
+
+    IFF_ID iffid;
+    long chunksize;
+    long bytesread;
+
+    bytesread = 0;
+
+    iffid = get_big_long(ifp, ID_FORM, NULL);
+    chunksize = get_big_long(ifp, iffid, NULL);
+    bytesread += 8;
+
+    if (debug)
+        pm_message("reading %s chunk: %ld bytes", 
+                   ID2string(iffid), chunksize);
+
+    if( ignored_iffid(iffid, ignorelist, ignorecount) ) {
+        pm_message("ignoring %s chunk", ID2string(iffid));
+        skip_chunk(ifp, iffid, chunksize);
+    } else if( iffid == ID_END ) {
+        /* END chunks are not officially valid in IFF, but
+           suggested as a future expansion for stream-writing,
+           see Amiga RKM Devices, 3rd Ed, page 376 
+        */
+        if( chunksize != 0 ) {
+            pm_message("warning - non-0 %s chunk", ID2string(iffid));
+            skip_chunk(ifp, iffid, chunksize);
+        }
+        if( formsize != 0xffffffff )
+            pm_message("warning - %s chunk with FORM size 0x%08lx "
+                       "(should be 0x%08x)",
+                       ID2string(iffid), formsize, 0xffffffff);
+        *endchunkP = 1;
+    } else if( *bodyChunkProcessedP ) {
+        pm_message("%s chunk found after %s chunk - skipping", 
+                   ID2string(iffid), ID2string(ID_BODY));
+        skip_chunk(ifp, iffid, chunksize);
+    } else
+        switch( iffid ) {
+        case ID_BMHD:
+            *bmhdP = read_bmhd(ifp, iffid, chunksize);
+            break;
+        case ID_CMAP:
+            read_cmap(ifp, iffid, chunksize, cmap);
+            break;
+        case ID_CMYK:
+            read_cmyk(ifp, iffid, chunksize, cmap);
+            break;
+        case ID_CLUT:
+            read_clut(ifp, iffid, chunksize, cmap);
+            break;
+        case ID_CAMG:
+            if( chunksize != CAMGChunkSize )
+                pm_error("%s chunk size mismatch", ID2string(iffid));
+            *viewportmodesP = get_big_long(ifp, ID_CAMG, NULL);
+            *viewportmodesP &= viewportmask;      /* -isnotham/-isnotehb */
+            break;
+        case ID_PCHG:
+            read_pchg(ifp, iffid, chunksize, cmap);
+            break;
+        case ID_CTBL:
+        case ID_SHAM:
+            read_4bit_mp(ifp, iffid, chunksize, cmap);
+            break;
+        case ID_DCOL:
+            if( chunksize != DirectColorSize )
+                pm_error("%s chunk size mismatch", ID2string(iffid));
+            MALLOCVAR_NOFAIL(*dcolP);
+            (*dcolP)->r = get_byte(ifp, iffid, NULL);
+            (*dcolP)->g = get_byte(ifp, iffid, NULL);
+            (*dcolP)->b = get_byte(ifp, iffid, NULL);
+            (void)get_byte(ifp, iffid, NULL);       /* skip pad byte */
+            break;
+        case ID_BODY: 
+            if( cmaponly || (*bmhdP && (*bmhdP)->nPlanes == 0 )) {
+                skip_chunk(ifp, ID_BODY,  chunksize);
+                return;
+            }
+    
+            process_body(ifp, chunksize, *bmhdP, cmap, 
+                         maskfile, fakeviewport, isdeepopt, *dcolP,
+                         viewportmodesP);
+
+            *bodyChunkProcessedP = TRUE;
+
+            break;
+        case ID_GRAB:   case ID_DEST:   case ID_SPRT:   case ID_CRNG:
+        case ID_CCRT:   case ID_DYCP:   case ID_DPPV:   case ID_DRNG:
+        case ID_EPSF:   case ID_JUNK:   case ID_CNAM:   case ID_PRVW:
+        case ID_TINY:   case ID_DPPS:
+            skip_chunk(ifp, iffid, chunksize);
+            break;
+        case ID_copy:   case ID_AUTH:   case ID_NAME:   case ID_ANNO:
+        case ID_TEXT:   case ID_FVER:
+            if( verbose )
+                display_chunk(ifp, iffid, chunksize);
+            else
+                skip_chunk(ifp, iffid, chunksize);
+            break;
+        case ID_DPI:
+        {
+            int x, y;
+
+            x = get_big_short(ifp, ID_DPI, NULL);
+            y = get_big_short(ifp, ID_DPI, NULL);
+            if( verbose )
+                pm_message("%s chunk:  dpi_x = %d    dpi_y = %d", 
+                           ID2string(ID_DPI), x, y);
+        }
+        break;
+        default:
+            pm_message("unknown chunk type %s - skipping", 
+                       ID2string(iffid));
+            skip_chunk(ifp, iffid, chunksize);
+            break;
+        }
+
+    bytesread += chunksize;
+
+    if( ODD(chunksize) ) {
+        (void) get_byte(ifp, iffid, NULL);
+        bytesread += 1;
+    } 
+    *bytesReadP = bytesread;
+}
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE *ifp;
+    int argn;
+    short cmaponly = 0, isdeepopt = 0;
+    bool endchunk;
+    bool bodyChunkProcessed;
+    long formsize, bytesread;
+    int viewportmodes = 0, fakeviewport = 0, viewportmask;
+    IFF_ID firstIffid;
+    BitMapHeader *bmhdP = NULL;
+    ColorMap * cmap;
+    DirectColor *dcol = NULL;
+#define MAX_IGNORE  16
+    IFF_ID ignorelist[MAX_IGNORE];
+    int ignorecount = 0;
+    char *maskname;
+    const char * const usage =
+        "[-verbose] [-ignore <chunkID> [-ignore <chunkID>] ...] "
+        "[-isham|-isehb|-isdeep|-isnotham|-isnotehb|-isnotdeep] "
+        "[-cmaponly] [-adjustcolors] "
+        "[-transparent <color>] [-maskfile <filename>] [ilbmfile]";
+    ppm_init(&argc, argv);
+
+    viewportmask = 0xffffffff;
+
+    argn = 1;
+    while( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) {
+        if( pm_keymatch(argv[argn], "-verbose", 2) )
+            verbose = 1;
+        else if( pm_keymatch(argv[argn], "-noverbose", 4) )
+            verbose = 0;
+        else if( pm_keymatch(argv[argn], "-isham", 4) )
+            fakeviewport |= vmHAM;
+        else if( pm_keymatch(argv[argn], "-isehb", 4) )
+            fakeviewport |= vmEXTRA_HALFBRITE;
+        else if( pm_keymatch(argv[argn], "-isdeep", 4) )
+            isdeepopt = 1;
+        else if( pm_keymatch(argv[argn], "-isnotham", 7) )
+            viewportmask &= ~(vmHAM);
+        else if( pm_keymatch(argv[argn], "-isnotehb", 7) )
+            viewportmask &= ~(vmEXTRA_HALFBRITE);
+        else if( pm_keymatch(argv[argn], "-isnotdeep", 7) )
+            isdeepopt = -1;
+        else if( pm_keymatch(argv[argn], "-cmaponly", 2) )
+            cmaponly = 1;
+        else if( pm_keymatch(argv[argn], "-adjustcolors", 2) )
+            adjustcolors = 1;
+        else if( pm_keymatch(argv[argn], "-noadjustcolors", 4) )
+            adjustcolors = 0;
+        else  if( pm_keymatch(argv[argn], "-transparent", 2) ) {
+            if( ++argn >= argc )
+                pm_usage(usage);
+            transpName = argv[argn];
+        } else if( pm_keymatch(argv[argn], "-maskfile", 2) ) {
+            if( ++argn >= argc )
+                pm_usage(usage);
+            maskname = argv[argn];
+            maskfile = pm_openw(maskname);
+        } else if( pm_keymatch(argv[argn], "-ignore", 2) ) {
+            if( ++argn >= argc )
+                pm_usage(usage);
+            if( strlen(argv[argn]) != 4 )
+                pm_error("'-ignore' option needs a 4 byte chunk ID string "
+                         "as argument");
+            if( ignorecount >= MAX_IGNORE )
+                pm_error("max %d chunk IDs to ignore", MAX_IGNORE);
+            ignorelist[ignorecount++] = 
+                MAKE_ID(argv[argn][0], argv[argn][1], argv[argn][2], 
+                        argv[argn][3]);
+        } else
+            pm_usage(usage);
+        ++argn;
+    }    
+
+    if( argn < argc ) {
+        ifp = pm_openr( argv[argn] );
+        argn++;
+    } else
+        ifp = stdin;
+
+    if( argn != argc )
+        pm_usage(usage);
+
+    /* Read in the ILBM file. */
+
+    firstIffid = get_big_long(ifp, ID_FORM, NULL);
+    if( firstIffid != ID_FORM )
+        pm_error("input is not a FORM type IFF file");
+    formsize = get_big_long(ifp, ID_FORM, NULL);
+    typeid = get_big_long(ifp, ID_FORM, NULL);
+    if( typeid != ID_ILBM && typeid != ID_RGBN && typeid != ID_RGB8 && 
+        typeid != ID_PBM )
+        pm_error( "input is not an ILBM, RGBN, RGB8 or PBM "
+                  "type FORM IFF file" );
+    bytesread = 4;  /* FORM and formsize do not count */
+
+    cmap = alloc_cmap();
+
+    /* Main loop, parsing the IFF FORM. */
+    bodyChunkProcessed = FALSE;
+    endchunk = FALSE;
+    while( !endchunk && formsize-bytesread >= 8 ) {
+        long bytes_read_for_chunk;
+
+        process_chunk(ifp, formsize, ignorelist, ignorecount,
+                      fakeviewport, viewportmask,
+                      isdeepopt, cmaponly,
+                      &bodyChunkProcessed,
+                      &endchunk, &bmhdP, cmap, &dcol,
+                      &viewportmodes, &bytes_read_for_chunk);
+
+        bytesread += bytes_read_for_chunk;
+    }
+
+    if( maskfile ) {
+        pm_close(maskfile);
+        if( !wrotemask )
+            remove(maskname);
+        pbm_freerow(maskrow);
+    }
+
+    if( cmaponly || (bmhdP && bmhdP->nPlanes == 0 )) {
+        if( HAS_COLORMAP(cmap) ) {
+            check_cmap(bmhdP, cmap);
+            cmap_to_ppm(cmap);
+        } else
+            pm_error("no colormap");
+    } else if( bodyChunkProcessed ) {
+        if( HAS_COLORMAP(cmap) ) {
+            pm_message("input is a colormap file");
+            check_cmap(bmhdP, cmap);
+            cmap_to_ppm(cmap);
+        } else
+            pm_error("no %s or %s chunk found", 
+                     ID2string(ID_BODY), ID2string(ID_CMAP));
+    }
+
+    {
+        unsigned int skipped;
+        
+        for( skipped = 0; fgetc(ifp) != EOF; ++skipped )
+            bytesread++;
+
+        if( skipped > 0 )
+            pm_message("skipped %u extraneous byte%s after last chunk",
+                       skipped, (skipped == 1 ? "" : "s"));
+    }
+    pm_close(ifp);
+
+    if( !endchunk && bytesread != formsize ) {
+        pm_message("warning - file length/FORM size field mismatch "
+                   "(%ld != %ld+8)",
+                   bytesread+8 /* FORM+size */, formsize);
+    }
+
+    return 0;
+}
diff --git a/converter/ppm/imgtoppm.c b/converter/ppm/imgtoppm.c
new file mode 100644
index 00000000..7078b882
--- /dev/null
+++ b/converter/ppm/imgtoppm.c
@@ -0,0 +1,142 @@
+/* imgtoppm.c - read an Img-whatnot file and produce a portable pixmap
+**
+** Based on a simple conversion program posted to comp.graphics by Ed Falk.
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+#include "ppm.h"
+
+
+
+int
+main(int argc, char ** argv) {
+
+    FILE* ifp;
+    pixel* pixelrow;
+    pixel colormap[256];
+    register pixel* pP;
+    unsigned int cols;
+    unsigned int rows;
+    int argn, row, i;
+    int col;
+    pixval maxval;
+    unsigned int cmaplen;
+    int len, gotAT, gotCM, gotPD;
+    unsigned char buf[4096];
+    unsigned char* bP;
+
+
+    ppm_init( &argc, argv );
+
+    argn = 1;
+
+    if ( argn < argc )
+    {
+        ifp = pm_openr( argv[argn] );
+        argn++;
+    }
+    else
+        ifp = stdin;
+
+    if ( argn != argc )
+        pm_usage( "[imgfile]" );
+
+    /* Get signature. */
+    fread( buf, 8, 1, ifp );
+    buf[8] = '\0';
+
+    /* Get entries. */
+    gotAT = 0;
+    gotCM = 0;
+    gotPD = 0;
+    while ( fread( buf, 2, 1, ifp ) == 1 )
+    {
+        if ( strncmp( (char*) buf, "AT", 2 ) == 0 )
+        {
+            if ( fread( buf, 8, 1, ifp ) != 1 )
+                pm_error( "bad attributes header" );
+            buf[8] = '\0';
+            len = atoi( (char*) buf );
+            if ( fread( buf, len, 1, ifp ) != 1 )
+                pm_error( "bad attributes buf" );
+            buf[len] = '\0';
+            sscanf( (char*) buf, "%4u%4u%4u", &cols, &rows, &cmaplen );
+            maxval = 255;
+            gotAT = 1;
+        }
+
+        else if ( strncmp( (char*) buf, "CM", 2 ) == 0 )
+        {
+            if ( ! gotAT )
+                pm_error( "missing attributes header" );
+            if ( fread( buf, 8, 1, ifp ) != 1 )
+                pm_error( "bad colormap header" );
+            buf[8] = '\0';
+            len = atoi((char*) buf );
+            if ( fread( buf, len, 1, ifp ) != 1 )
+                pm_error( "bad colormap buf" );
+            if ( cmaplen * 3 != len )
+            {
+                pm_message(
+                    "cmaplen (%d) and colormap buf length (%d) do not match",
+                    cmaplen, len );
+                if ( cmaplen * 3 < len )
+                    len = cmaplen * 3;
+                else if ( cmaplen * 3 > len )
+                    cmaplen = len / 3;
+            }
+            for ( i = 0; i < len; i += 3 )
+                PPM_ASSIGN( colormap[i / 3], buf[i], buf[i + 1], buf[i + 2] );
+            gotCM = 1;
+        }
+
+        else if ( strncmp( (char*) buf, "PD", 2 ) == 0 )
+        {
+            if ( fread( buf, 8, 1, ifp ) != 1 )
+                pm_error( "bad pixel data header" );
+            buf[8] = '\0';
+            len = atoi((char*) buf );
+            if ( len != cols * rows )
+                pm_message(
+                    "pixel data length (%d) does not match image size (%d)",
+                    len, cols * rows );
+
+            ppm_writeppminit( stdout, cols, rows, maxval, 0 );
+            pixelrow = ppm_allocrow( cols );
+
+            for ( row = 0; row < rows; row++ )
+            {
+                if ( fread( buf, 1, cols, ifp ) != cols )
+                    pm_error( "EOF / read error" );
+                for ( col = 0, pP = pixelrow, bP = buf;
+                      col < cols; col++, pP++, bP++ )
+                {
+                    if ( gotCM )
+                        *pP = colormap[*bP];
+                    else
+                        PPM_ASSIGN( *pP, *bP, *bP, *bP );
+                }
+                ppm_writeppmrow( stdout, pixelrow, cols, maxval, 0 );
+            }
+            gotPD = 1;
+
+        }
+    }
+    if ( ! gotPD )
+        pm_error( "missing pixel data header" );
+
+    pm_close( ifp );
+    /* If the program failed, it previously aborted with nonzero completion
+       code, via various function calls.
+    */
+    return 0;
+}
diff --git a/converter/ppm/leaftoppm.c b/converter/ppm/leaftoppm.c
new file mode 100644
index 00000000..889400f6
--- /dev/null
+++ b/converter/ppm/leaftoppm.c
@@ -0,0 +1,216 @@
+/* leaftoppm.c - read an ileaf img file and write a PPM
+ *
+ * Copyright (C) 1994 by Bill O'Donnell.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  This software is provided "as is" without express or
+ * implied warranty.
+ *
+ * known problems: doesn't do compressed ileaf images.
+ * 
+ */
+
+#include <stdio.h>
+#include "ppm.h"
+
+#define MAXCOLORS 256
+#define LEAF_MAXVAL 255
+
+static void
+leaf_init(FILE *  const fp, 
+          int *   const colsP,
+          int *   const rowsP, 
+          int *   const depthP, 
+          int *   const ncolorsP, 
+          pixel * const colors) {
+
+    unsigned char buf[256];
+    unsigned char compressed;
+    short version;
+    short cols;
+    short rows;
+    short depth;
+    long magic;
+    
+    pm_readbiglong(fp, &magic);
+    if (magic != 0x894f5053)
+        pm_error("Bad magic number.  First 4 bytes should be "
+                 "0x894f5053 but are instead 0x%08x", (unsigned)magic);
+    
+    /* version =   2 bytes
+       hres =      2 
+       vres =      2
+       unique id = 4
+       offset x =  2
+       offset y =  2
+       TOTAL    =  14 bytes 
+    */
+    
+    pm_readbigshort(fp, &version);
+
+    if (fread(buf, 1, 12, fp) != 12)
+        pm_error("bad header, short file?");
+    
+    pm_readbigshort(fp, &cols);
+    *colsP = cols;
+    pm_readbigshort(fp, &rows);
+    *rowsP = rows;
+    pm_readbigshort(fp, &depth);
+    *depthP = depth;
+    
+    if ((compressed = fgetc(fp)) != 0)
+        pm_error("Can't do compressed images.");
+
+    if ((*depthP == 1) && (version < 4)) {
+        fgetc(fp); 
+        *ncolorsP = 0;
+    } else if ((*depthP == 8) && (version < 4)) {
+        fgetc(fp); 
+        *ncolorsP = 0;
+    } else {
+        long format;
+        pm_readbiglong(fp, &format);
+        if (format == 0x29000000) {
+            /* color image */
+            short ncolors;
+            pm_readbigshort(fp, &ncolors);
+
+            if (ncolors > 0) {
+                /* 8-bit image */
+                unsigned int i;
+
+                if (ncolors > 256)
+                    pm_error("Can't have > 256 colors in colormap.");
+                /* read colormap */
+                for (i=0; i < 256; ++i)
+                    PPM_PUTR(colors[i], fgetc(fp));
+                for (i=0; i < 256; ++i)
+                    PPM_PUTR(colors[i], fgetc(fp));
+                for (i=0; i < 256; ++i)
+                    PPM_PUTR(colors[i], fgetc(fp));
+                *ncolorsP = ncolors;
+            } else {
+                /* 24-bit image */
+                *ncolorsP = 0;
+            }
+        } else if (*depthP ==1) {
+            /* mono image */
+            short dummy;
+            pm_readbigshort(fp, &dummy);  /* must toss cmap size */
+            *ncolorsP = 0;
+        } else {
+            /* gray image */
+            short dummy;
+            pm_readbigshort(fp, &dummy);  /* must toss cmap size */
+            *ncolorsP = 0;
+        }
+    }
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    pixval const maxval = LEAF_MAXVAL;
+
+    FILE *ifd;
+    pixel colormap[MAXCOLORS];
+    int rows, cols, row, col, depth, ncolors;
+
+    
+    ppm_init(&argc, argv);
+    
+    if (argc-1 > 1)
+        pm_error("Too many arguments.  Only argument is ileaf file name");
+    
+    if (argc-1 == 1)
+        ifd = pm_openr(argv[1]);
+    else
+        ifd = stdin;
+    
+    leaf_init(ifd, &cols, &rows, &depth, &ncolors, colormap);
+    
+    if ((depth == 8) && (ncolors == 0)) {
+        /* gray image */
+        gray * grayrow;
+        unsigned int row;
+        pgm_writepgminit( stdout, cols, rows, maxval, 0 );
+        grayrow = pgm_allocrow(cols);
+        for (row = 0; row < rows; ++row) {
+            unsigned int col;
+            for ( col = 0; col < cols; ++col)
+                grayrow[col] = (gray)fgetc(ifd);
+            if (cols % 2)
+                fgetc (ifd); /* padding */
+            pgm_writepgmrow(stdout, grayrow, cols, maxval, 0);
+        }
+        pgm_freerow(grayrow);
+    } else if (depth == 24) {
+        pixel * pixrow;
+        unsigned int row;
+        ppm_writeppminit(stdout, cols, rows, maxval, 0);
+        pixrow = ppm_allocrow(cols);
+        /* true color */
+        for (row = 0; row < rows; ++row) {
+            for (col = 0; col < cols; ++col)
+                PPM_PUTR(pixrow[col], fgetc(ifd));
+            if (cols % 2)
+                fgetc (ifd); /* padding */
+            for (col = 0; col < cols; ++col)
+                PPM_PUTG(pixrow[col], fgetc(ifd));
+            if (cols % 2)
+                fgetc (ifd); /* padding */
+            for (col = 0; col < cols; ++col)
+                PPM_PUTB(pixrow[col], fgetc(ifd));
+            if (cols % 2)
+                fgetc (ifd); /* padding */
+            ppm_writeppmrow(stdout, pixrow, cols, maxval, 0);
+        }
+        ppm_freerow(pixrow);
+    } else if (depth == 8) {
+        /* 8-bit (color mapped) image */
+        pixel * pixrow;
+
+        ppm_writeppminit(stdout, cols, rows, (pixval) maxval, 0);
+        pixrow = ppm_allocrow( cols );
+    
+        for (row = 0; row < rows; ++row) {
+            for (col = 0; col < cols; ++col)
+                pixrow[col] = colormap[fgetc(ifd)];
+            if (cols %2)
+                fgetc(ifd); /* padding */
+            ppm_writeppmrow(stdout, pixrow, cols, maxval, 0);
+        }
+        ppm_freerow(pixrow);
+    } else if (depth == 1) {
+        /* mono image */
+        bit *bitrow;
+        unsigned int row;
+        
+        pbm_writepbminit(stdout, cols, rows, 0);
+        bitrow = pbm_allocrow(cols);
+    
+        for (row = 0; row < rows; ++row) {
+            unsigned char bits;
+            bits = 0x00;  /* initial value */
+            for (col = 0; col < cols; ++col) {
+                int const shift = col % 8;
+                if (shift == 0) 
+                    bits = (unsigned char) fgetc(ifd);
+                bitrow[col] = (bits & (unsigned char)(0x01 << (7 - shift))) ? 
+                    PBM_WHITE : PBM_BLACK;
+            }
+            if ((cols % 16) && (cols % 16) <= 8)
+                fgetc(ifd);  /* 16 bit pad */
+            pbm_writepbmrow(stdout, bitrow, cols, 0);
+        }
+        pbm_freerow(bitrow);
+    }
+    pm_close(ifd);
+    
+    return 0;
+}
diff --git a/converter/ppm/mitsu.h b/converter/ppm/mitsu.h
new file mode 100644
index 00000000..8676a39d
--- /dev/null
+++ b/converter/ppm/mitsu.h
@@ -0,0 +1,97 @@
+#ifndef MITSU_H_INCLUDED
+#define MITSU_H_INCLUDED
+
+/* static char SCCSid[] = "@(#)mitsu.h\t\t1.3\t(SPZ)\t3/11/92\n"; */
+#define MAXLUTCOL   255
+
+#define A4_MAXCOLS  1184
+#define A4_MAXROWS  1452
+#define A4S_MAXROWS 1754
+#define A_MAXCOLS   1216
+#define A_MAXROWS   1350
+#define AS_MAXROWS  1650
+
+#define ONLINE         cmd('\021')
+#define CLRMEM         cmd('\033'), cmd('Z')
+
+struct mediasize {
+        char size;
+        int  maxcols, maxrows;
+};
+
+const struct mediasize MSize_User={' ',1184,1350};
+const struct mediasize MSize_A4  ={'0',1184,1452};
+const struct mediasize MSize_A   ={'1',1216,1350};
+const struct mediasize MSize_A4S ={'2',1184,1754};
+const struct mediasize MSize_AS  ={'3',1216,1650};
+#define MEDIASIZE(chr) cmd('\033'), cmd('#'), cmd('P'), cmd((chr).size)
+
+#define HENLARGE(enl)  cmd('\033'), cmd('&'), cmd('P'), cmd(enl), cmd('\001')
+#define VENLARGE(enl)  cmd('\033'), cmd('&'), cmd('Q'), cmd(enl), cmd('\001')
+#define NOENLARGE '\001'
+#define ENLARGEx2 '\002'
+#define ENLARGEx3 '\003'
+
+#define COLREVERSION(arg) cmd('\033'), cmd('&'), cmd('W'), cmd(arg)
+#define DONTREVERTCOLOR '0'
+#define REVERTCOLOR   '2'
+
+#define NUMCOPY(num)   cmd('\033'), cmd('#'), cmd('C'), cmd((num) & 0xff)
+
+#define HOFFINCH(off)  cmd('\033'), cmd('&'), cmd('S'), cmd((off) & 0xff)
+#define VOFFINCH(off)  cmd('\033'), cmd('&'), cmd('T'), cmd((off) & 0xff)
+
+#define CENTERING(cen) cmd('\033'), cmd('&'), cmd('C'), cmd(cen)
+#define DONTCENTER '0'
+#define DOCENTER   '1'
+
+#define TRANSFERFORMAT(fmt) cmd('\033'), cmd('&'), cmd('A'), cmd(fmt)
+#define FRAMEORDER  '0'
+#define LINEORDER   '1'
+#define LOOKUPTABLE '3'
+
+#define COLORSYSTEM(cs) cmd('\033'), cmd('&'), cmd('I'), cmd(cs)
+#define RGB '0'
+#define YMC '1'
+
+#define SHARPNESS(spn) cmd('\033'), cmd('#'), cmd('E'), cmd(spn)
+#define SP_USER ' '
+#define SP_NONE '0'
+#define SP_LOW  '1'
+#define SP_MIDLOW '2'
+#define SP_MIDHIGH '3'
+#define SP_HIGH '4'
+
+#define COLORDES(col) cmd('\033'), cmd('C'), cmd(col)
+#define RED   '1'
+#define GREEN '2'
+#define BLUE  '3'
+#define YELLOW  '1'
+#define MAGENTA '2'
+#define CYAN    '3'
+
+#define HPIXELS(hpix) cmd('\033'), cmd('&'), cmd('H'),\
+                                                        cmd(((hpix) >> 8) & 0xff), cmd((hpix) & 0xff)
+#define VPIXELS(vpix) cmd('\033'), cmd('&'), cmd('V'),\
+                                                        cmd(((vpix) >> 8) & 0xff), cmd((vpix) & 0xff)
+#define HPIXELSOFF(hoff) cmd('\033'), cmd('&'), cmd('J'),\
+                                                        cmd(((hoff) >> 8) & 0xff), cmd((hoff) & 0xff)
+#define VPIXELSOFF(voff) cmd('\033'), cmd('&'), cmd('K'),\
+                                                        cmd(((voff) >> 8) & 0xff), cmd((voff) & 0xff)
+
+#define GRAYSCALELVL(lvl) cmd('\033'), cmd('#'), cmd('L'), cmd(lvl)
+#define BIT_6 '\006'
+#define BIT_8 '\010'
+
+#define LOADLOOKUPTABLE cmd('\033'), cmd('&'), cmd('L')
+#define DONELOOKUPTABLE cmd('\004')
+
+#define ROTATEIMG(rot)  cmd('\033'), cmd('#'), cmd('R'), cmd(rot)
+#define DONTROTATE '0'
+#define DOROTATE   '1'
+
+#define DATASTART cmd('\033'), cmd('O')
+#define PRINTIT cmd('\014')
+#define OFFLINE cmd('\023')
+
+#endif
diff --git a/converter/ppm/mtvtoppm.c b/converter/ppm/mtvtoppm.c
new file mode 100644
index 00000000..e8558632
--- /dev/null
+++ b/converter/ppm/mtvtoppm.c
@@ -0,0 +1,68 @@
+/* mtvtoppm.c - read an MTV ray-tracer output file and produce a portable pixmap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    pixel* pixelrow;
+    register pixel* pP;
+    int rows, cols, row, col;
+    pixval maxval;
+#define MAXLINE 500
+    char line[MAXLINE];
+    unsigned char buf[3];
+
+
+    ppm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[mtvfile]" );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    /* Read in the MTV file.  First the header. */
+    if ( fgets( line, MAXLINE, ifp ) == NULL )
+	pm_error( "unable to read MTV file header" );
+    if ( sscanf( line, "%d%d", &cols, &rows ) != 2 )
+	pm_error( "unable to parse MTV file header" );
+
+    if ( cols <= 0 || rows <= 0 )
+	pm_error( "invalid size: %d %d", cols, rows );
+    maxval = 255;
+
+    ppm_writeppminit( stdout, cols, rows, maxval, 0 );
+    pixelrow = ppm_allocrow( cols );
+
+    for ( row = 0; row < rows; row++ )
+	{
+	for ( col = 0, pP = pixelrow; col < cols; col++, pP++ )
+	    {
+	    if ( fread( buf, sizeof(buf), 1, ifp ) != 1 )
+		pm_error( "EOF / read error" );
+	    PPM_ASSIGN( *pP, buf[0], buf[1], buf[2] );
+	    }
+	ppm_writeppmrow( stdout, pixelrow, cols, maxval, 0 );
+	}
+
+    pm_close( ifp );
+    pm_close( stdout );
+
+    exit( 0 );
+    }
diff --git a/converter/ppm/neotoppm.c b/converter/ppm/neotoppm.c
new file mode 100644
index 00000000..d8cf2918
--- /dev/null
+++ b/converter/ppm/neotoppm.c
@@ -0,0 +1,104 @@
+/* neotoppm.c - read a Neochrome NEO file and produce a portable pixmap
+**
+** Copyright (C) 2001 by Teemu Hukkanen <tjhukkan@iki.fi>
+**  Based on pi1toppm by Steve Belczyk (seb3@gte.com) and Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+
+#define ROWS 200
+#define COLS 320
+#define MAXVAL 7
+
+static short screen[ROWS*COLS/4];        /* simulates the Atari's video RAM */
+
+int
+main( int argc, char * argv[] ) {
+    FILE* ifp;
+    pixel pal[16];                      /* Degas palette */
+    pixel* pixelrow;
+    int row;
+
+    ppm_init( &argc, argv );
+
+    /* Check args. */
+    if ( argc > 2 )
+        pm_usage( "[neofile]" );
+
+    if ( argc == 2 )
+        ifp = pm_openr( argv[1] );
+    else
+        ifp = stdin;
+
+    /* Check header */
+    {
+        /* Flag */
+        short j;
+        pm_readbigshort (ifp, &j);
+        if ( j != 0 )
+            pm_error( "not a NEO file" );
+    }
+    {
+        /* Check resolution word */
+        short j;
+        pm_readbigshort (ifp, &j);
+        if ( j != 0 )
+            pm_error( "not a NEO file" );
+    }
+    {
+        int i;
+        /* Read the palette. */
+        for ( i = 0; i < 16; ++i ) {
+            short j;
+            
+            pm_readbigshort (ifp, &j);
+            PPM_ASSIGN( pal[i],
+                        ( j & 0x700 ) >> 8,
+                        ( j & 0x070 ) >> 4,
+                        ( j & 0x007 ) );
+        }
+    }
+
+    /* Skip rest of header */
+    fseek(ifp, 128, SEEK_SET);
+
+    {
+        /* Read the screen data */
+        int i;
+        for ( i = 0; i < ROWS*COLS/4; ++i )
+            pm_readbigshort( ifp, &screen[i] );
+    }
+    pm_close( ifp );
+
+    /* Ok, get set for writing PPM. */
+    ppm_writeppminit( stdout, COLS, ROWS, MAXVAL, 0 );
+    pixelrow = ppm_allocrow( COLS );
+
+    /* Now do the conversion. */
+    for ( row = 0; row < ROWS; ++row ) {
+        int col;
+        for ( col = 0; col < COLS; ++col ) {
+            int c, ind, b, plane;
+
+            ind = 80 * row + ( ( col >> 4 ) << 2 );
+            b = 0x8000 >> ( col & 0xf );
+            c = 0;
+            for ( plane = 0; plane < 4; ++plane )
+                if ( b & screen[ind+plane] )
+                    c |= (1 << plane);
+            pixelrow[col] = pal[c];
+        }
+        ppm_writeppmrow( stdout, pixelrow, COLS, MAXVAL, 0 );
+    }
+
+    pm_close( stdout );
+
+    exit( 0 );
+}
diff --git a/converter/ppm/pc1toppm.c b/converter/ppm/pc1toppm.c
new file mode 100644
index 00000000..5ba247e9
--- /dev/null
+++ b/converter/ppm/pc1toppm.c
@@ -0,0 +1,233 @@
+/* pc1toppm.c - read a Degas Elite PC1 file and produce a PPM
+
+ Copyright (C) 1991 by Steve Belczyk (seb3@gte.com) and Jef Poskanzer.
+ Copyright (C) 2004 by Roine Gustafsson (roine@users.sourceforge.net)
+
+ Converted to modern Netpbm code style by Bryan Henderson April 2004.
+ That work is contributed to the public domain by Bryan.
+
+ Permission to use, copy, modify, and distribute this software and its
+ documentation for any purpose and without fee is hereby granted, provided
+ that the above copyright notice appear in all copies and that both that
+ copyright notice and this permission notice appear in supporting
+ documentation.  This software is provided "as is" without express or
+ implied warranty.
+
+ Algorithm for PC1 compression from "gimp-degas" GIMP plugin 
+ by Markus F.X.J. Oberhumer
+
+*/
+
+#include "ppm.h"
+
+/* The code has not been tested for other resolutions than 320x200 */
+static unsigned int const rows   = 200;
+static unsigned int const cols   = 320;
+static unsigned int const planes = 4;  /* 4 bits for 16 colors */
+static pixval       const maxval = 7;
+
+unsigned int const colsPerBlock = 16;
+
+#define BLOCKS (cols/colsPerBlock)
+#define ROWBYTES (cols * planes / 8)
+#define BLOCKBYTES (colsPerBlock / 8)
+#define PLANEBYTES (ROWBYTES / planes)
+#define ROWSHORTS (cols * planes / 16)
+#define BLOCKSHORTS (colsPerBlock * planes / 16)
+#define PLANESHORTS (ROWSHORTS / planes)
+
+
+
+static void
+readPalette(FILE *   const ifP, 
+            pixel (* const palP)[]) {
+
+    /* Read the palette. */
+    unsigned int i;
+    for (i = 0; i < 16; ++i) {
+        unsigned short j;
+        pm_readbigshortu(ifP, &j);
+        PPM_ASSIGN((*palP)[i],
+                   (j & 0x700) >> 8,
+                   (j & 0x070) >> 4,
+                   (j & 0x007) >> 0);
+    }
+}
+
+
+
+static void
+processStretch(unsigned int     const countbyte,
+               FILE *           const ifP,
+               unsigned int *   const colP,
+               unsigned char ** const lineposP) {
+
+    if (countbyte <= 127) {
+        /* countbyte+1 literal bytes follows */
+
+        unsigned int const count = countbyte + 1;
+        unsigned int i;
+
+        if (*colP + count > ROWBYTES)
+            pm_error("Error in PC1 file.  "
+                     "A literal stretch extends beyond the end of a row");
+
+        for (i = 0; i < count; ++i) {
+            *(*lineposP)++ = getc(ifP);
+            ++(*colP);
+        }
+    } else {
+        /* next byte repeated 257-countbyte times */
+                
+        unsigned char const duplicated_color = getc(ifP);
+        unsigned int  const count = 257 - countbyte;
+        unsigned int i;
+
+        if (*colP + count > ROWBYTES)
+            pm_error("Error in PC1 file.  "
+                     "A run extends beyond the end of a row.");
+        for (i = 0; i < count; ++i) {
+            *(*lineposP)++ = duplicated_color;
+            ++(*colP);
+        }
+    }
+}
+
+
+
+static void
+readPc1(FILE * const ifP,
+        pixel (*palP)[],
+        unsigned char (*bufferP)[]) {
+
+    unsigned short j;
+    unsigned int row;
+    unsigned char* bufferCursor;
+
+    /* Check resolution word */
+    pm_readbigshortu(ifP, &j);
+    if (j != 0x8000)
+        pm_error("This is not a PC1 file.  "
+                 "The first two bytes are 0x%04X instead of 0x8000", j);
+
+    readPalette(ifP, palP);
+
+    /* Read the screen data */
+    bufferCursor = &(*bufferP)[0];
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+
+        for (col = 0; col < ROWBYTES;) {
+            unsigned int const countbyte = getc(ifP);
+
+            processStretch(countbyte, ifP, &col, &bufferCursor);
+	    }
+	}
+}
+
+
+
+static void
+reInterleave(unsigned char     const buffer[],
+             unsigned short (* const screenP)[]) {
+    
+    /* The buffer is in one plane for each line, to optimize packing */
+    /* Re-interleave to match the Atari screen layout                */
+
+    unsigned short * const screen = *screenP;
+    unsigned int row;
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int block;
+        for (block = 0; block < BLOCKS; ++block) {
+            unsigned int plane;
+            for (plane = 0; plane < planes; ++plane) {
+                unsigned int const blockIndex = 
+                    row*ROWBYTES + plane*PLANEBYTES + block*BLOCKBYTES;
+
+                screen[row*ROWSHORTS + block*BLOCKSHORTS + plane] = 
+                    (buffer[blockIndex+0] << 8) + (buffer[blockIndex+1]);
+            }
+        }
+    }
+}
+
+
+
+static void
+writePpm(FILE *         const ofP,
+         unsigned short const screen[],
+         pixel          const palette[]) {
+
+    pixel* pixelrow;
+    unsigned int row;
+
+    ppm_writeppminit(ofP, cols, rows, maxval, 0);
+    pixelrow = ppm_allocrow(cols);
+
+    for (row = 0; row < rows; ++row) {
+        /* Each row is arranged into blocks of 16 columns.  Each block
+           is represented by 4 shorts (16 bit integers).  The
+           first is for Plane 0, the second for Plane 1, etc.  Each short
+           contains the bits for that plane for each of the 16 columns,
+           arranged from most signficant bit to least in increasing column
+           number.
+        */
+        unsigned int col0ScreenIndex = cols/16*planes * row;
+
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            unsigned int const ind = col0ScreenIndex + col/16 * planes;
+            unsigned int const b = 0x8000 >> (col % 16);
+
+            unsigned int plane;
+            unsigned int colorIndex;
+
+            colorIndex = 0;
+            for (plane = 0; plane < planes; ++plane)
+                if (b & screen[ind+plane])
+                    colorIndex |= (1 << plane);
+            pixelrow[col] = palette[colorIndex];
+        }
+        ppm_writeppmrow(ofP, pixelrow, cols, maxval, 0);
+    }
+    ppm_freerow(pixelrow);
+}
+
+
+
+int
+main(int argc, char ** argv) {
+
+    const char * inputFilename;
+    FILE* ifP;
+    pixel palette[16];  /* Degas palette */
+    static unsigned short screen[32000/2];   
+        /* simulates the Atari's video RAM */
+    static unsigned char buffer[32000];   
+        /* simulates the Atari's video RAM */
+
+    ppm_init(&argc, argv);
+
+    if (argc-1 == 0)
+        inputFilename = "-";
+    else if (argc-1 == 1)
+        inputFilename = argv[1];
+    else
+        pm_error("There is at most one argument to this program:  "
+                 "the input file name.  You specified %d", argc-1);
+
+    ifP = pm_openr(inputFilename);
+
+    readPc1(ifP, &palette, &buffer);
+
+    pm_close(ifP);
+
+    reInterleave(buffer, &screen);
+
+    writePpm(stdout, screen, palette);
+
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/converter/ppm/pcxstd.ppm b/converter/ppm/pcxstd.ppm
new file mode 100644
index 00000000..513602e7
--- /dev/null
+++ b/converter/ppm/pcxstd.ppm
@@ -0,0 +1,19 @@
+P3
+16 1
+255
+  0   0   0
+  0   0 170
+  0 170   0
+  0 170 170
+170   0   0
+170   0 170
+170 170   0
+170 170 170
+ 85  85  85
+ 85  85 255
+ 85 255  85
+ 85 255 255
+255  85  85
+255  85 255
+255 255  85
+255 255 255
diff --git a/converter/ppm/pcxtoppm.c b/converter/ppm/pcxtoppm.c
new file mode 100644
index 00000000..9f403538
--- /dev/null
+++ b/converter/ppm/pcxtoppm.c
@@ -0,0 +1,710 @@
+/*
+ * pcxtoppm.c - Converts from a PC Paintbrush PCX file to a PPM file.
+ *
+ * Copyright (c) 1990 by Michael Davidson
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and that
+ * both that copyright notice and this permission notice appear in
+ * supporting documentation.
+ *
+ * This file is provided AS IS with no warranties of any kind.  The author
+ * shall have no liability with respect to the infringement of copyrights,
+ * trade secrets or any patents by this file or any part thereof.  In no
+ * event will the author be liable for any lost revenue or profits or
+ * other special, indirect and consequential damages.
+ *
+ * Modifications by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
+ * 20/Apr/94:
+ *  - checks if 16-color-palette is completely black -> use standard palette
+ *  - "-stdpalette" option to enforce this
+ *  - row-by-row operation (PPM output)
+ *  11/Dec/94:
+ *  - capability for 24bit and 32bit (24bit + 8bit intensity) images
+ *  - row-by-row operation (PCX input, for 16-color and truecolor images)
+ *  - some code restructuring
+ *  15/Feb/95:
+ *  - bugfix for 16 color-images: few bytes allocated for rawrow in some cases
+ *  - added sanity checks for cols<->BytesPerLine
+ *  17/Jul/95:
+ *  - moved check of 16-color-palette into pcx_16col_to_ppm(),
+ *    now checks if it contains only a single color
+ */
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "ppm.h"
+
+#define PCX_MAGIC       0x0a            /* PCX magic number             */
+#define PCX_HDR_SIZE    128             /* size of PCX header           */
+#define PCX_256_COLORS  0x0c            /* magic number for 256 colors  */
+
+#define PCX_MAXVAL      (pixval)255
+
+/* standard palette */
+static unsigned char const StdRed[]   = { 0, 255,   0,   0, 170, 170, 170, 170, 85,  85,  85,  85, 255, 255, 255, 255 };
+static unsigned char const StdGreen[] = { 0, 255, 170, 170,   0,   0, 170, 170, 85,  85, 255, 255,  85,  85, 255, 255 };
+static unsigned char const StdBlue[]  = { 0, 255,   0, 170,   0, 170,   0, 170, 85, 255,  85, 255,  85, 255,  85, 255 };
+
+static pixel stdPalette[16];
+
+static void
+generateStdPalette(void) {
+
+    int i;
+    for (i = 0; i < 16; i++)
+        PPM_ASSIGN(stdPalette[i], StdRed[i], StdGreen[i], StdBlue[i]);
+}
+
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* '-' if stdin */
+    unsigned int verbose;  
+    unsigned int stdpalette;
+};
+
+
+
+static void
+parseCommandLine ( int argc, char ** argv,
+                   struct cmdlineInfo *cmdlineP ) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc( 100*sizeof( optEntry ) );
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "stdpalette",     OPT_FLAG,   NULL,                  
+            &cmdlineP->stdpalette,    0 );
+    OPTENT3(0, "verbose",        OPT_FLAG,   NULL,                  
+            &cmdlineP->verbose,       0 );
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        pm_error("Program takes at most one argument "
+                 "(input file specification).  You specified %d",
+                 argc-1);
+}
+
+
+
+struct pcxHeader {
+    int Version;
+    /* Xmin, Ymin, Xmax, and Ymax are positions in some field (in units of
+       pixels) of the edges of the image.  They may be negative.  You can
+       derive the image width and height from these.
+    */
+    short Xmin;            
+    short Ymin;
+    short Xmax;
+    short Ymax;
+    short Encoding;
+    short Planes;
+    short BitsPerPixel;
+    short BytesPerLine;
+        /* Number of decompressed bytes each plane of each row of the image 
+           takes.  Due to padding (this is always an even number), there may
+           be garbage on the right end that isn't part of the image.
+        */
+    short PaletteInfo;
+    short HorizontalResolution;
+    short VerticalResolution;
+    pixel cmap16[16];
+};
+
+
+
+static void
+readInt(FILE * const ifP, short * const retvalP) {
+/*----------------------------------------------------------------------------
+   Read a 2-byte little-endian word from the file.
+-----------------------------------------------------------------------------*/
+    int rc;
+
+    rc = pm_readlittleshort(ifP, retvalP);
+
+    if (rc != 0)
+        pm_error("EOF/error reading integer from input file.");
+}
+
+
+
+static int
+GetByte(FILE * const fp) {
+
+    int    c;
+
+    if ((c = fgetc(fp)) == EOF)
+        pm_error("unexpected end of file" );
+
+    return c;
+}
+
+
+
+static void
+readPcxHeader(FILE *             const ifP, 
+              struct pcxHeader * const pcxHeaderP) {
+/*----------------------------------------------------------------------------
+   Read the PCX header
+-----------------------------------------------------------------------------*/
+    if (GetByte(ifP) != PCX_MAGIC)
+        pm_error("bad magic number - not a PCX file");
+
+    pcxHeaderP->Version = GetByte(ifP);  /* get version # */
+
+    pcxHeaderP->Encoding = GetByte(ifP);
+    if (pcxHeaderP->Encoding != 1)    /* check for PCX run length encoding   */
+        pm_error("unknown encoding scheme: %d", pcxHeaderP->Encoding);
+
+    pcxHeaderP->BitsPerPixel= GetByte(ifP);
+    readInt(ifP, &pcxHeaderP->Xmin);
+    readInt(ifP, &pcxHeaderP->Ymin);
+    readInt(ifP, &pcxHeaderP->Xmax);
+    readInt(ifP, &pcxHeaderP->Ymax);
+
+    if (pcxHeaderP->Xmax < pcxHeaderP->Xmin)
+        pm_error("Invalid PCX input:  minimum X value (%d) is greater than "
+                 "maximum X value (%d).", 
+                 pcxHeaderP->Xmin, pcxHeaderP->Xmax);
+    if (pcxHeaderP->Ymax < pcxHeaderP->Ymin)
+        pm_error("Invalid PCX input:  minimum Y value (%d) is greater than "
+                 "maximum Y value (%d).", 
+                 pcxHeaderP->Ymin, pcxHeaderP->Ymax);
+
+    readInt(ifP, &pcxHeaderP->HorizontalResolution);  
+    readInt(ifP, &pcxHeaderP->VerticalResolution);  
+
+    {
+        int i;
+        /*
+         * get the 16-color color map
+         */
+        for (i = 0; i < 16; i++) {
+            unsigned int const r = GetByte(ifP);
+            unsigned int const g = GetByte(ifP);
+            unsigned int const b = GetByte(ifP);
+            PPM_ASSIGN(pcxHeaderP->cmap16[i], r, g, b);
+        }
+    }
+
+    GetByte(ifP);                /* skip reserved byte       */
+    pcxHeaderP->Planes      = GetByte(ifP);     /* # of color planes        */
+    readInt(ifP, &pcxHeaderP->BytesPerLine);
+    readInt(ifP, &pcxHeaderP->PaletteInfo);
+
+    /* Read past a bunch of reserved space in the header.  We have read
+       70 bytes of the header so far.  We would just seek here, except that
+       we want to work with unseekable (e.g. pipe input).
+    */
+    {
+        unsigned int pos;
+
+        for (pos = 70; pos < PCX_HDR_SIZE; ++pos)
+            GetByte(ifP);
+    }
+}
+
+
+
+static void 
+reportPcxHeader(struct pcxHeader const pcxHeader) {
+
+    pm_message("Version: %d", pcxHeader.Version);
+    pm_message("BitsPerPixel: %d", pcxHeader.BitsPerPixel);
+    pm_message("Xmin: %d   Ymin: %d   Xmax: %d   Ymax: %d", 
+               pcxHeader.Xmin, pcxHeader.Ymin, pcxHeader.Xmax, pcxHeader.Ymax);
+    pm_message("Planes: %d    BytesPerLine: %d    PaletteInfo: %d", 
+               pcxHeader.Planes, pcxHeader.BytesPerLine, 
+               pcxHeader.PaletteInfo);
+    pm_message("Color map in image:  (index: r/g/b)");
+    if (pcxHeader.BitsPerPixel < 8) {
+        unsigned int colorIndex;
+        for (colorIndex = 0; colorIndex < 16; ++colorIndex) {
+            pixel const p = pcxHeader.cmap16[colorIndex];
+            pm_message("  %u: %u/%u/%u", colorIndex, 
+                       PPM_GETR(p), PPM_GETG(p), PPM_GETB(p));
+        }
+    }
+}
+
+
+
+static bool
+allBlackPalette(pixel cmap16[]) {
+
+    unsigned int colorIndex;
+    bool allBlack;
+
+    allBlack = TRUE;  /* initial assumption */
+    for (colorIndex = 0; colorIndex < 16; ++colorIndex) {
+        pixel const p = cmap16[colorIndex];
+
+        if (PPM_GETR(p) != 0 ||
+            PPM_GETG(p) != 0 ||
+            PPM_GETB(p) != 0)
+
+            allBlack = FALSE;
+    }
+    return allBlack;
+}
+
+
+
+/*
+ *  read a single row encoded row, handles encoding across rows
+ */
+static void
+GetPCXRow(FILE * const ifP, unsigned char * const pcxrow, 
+          int const bytesperline) {
+/*----------------------------------------------------------------------------
+   Read one row from the PCX raster.
+   
+   The PCX raster is run length encoded as follows:  If the upper two
+   bits of a byte are 11, the lower 6 bits are a repetition count for the
+   raster byte that follows.  If the upper two bits are not 11, the byte 
+   _is_ a raster byte, with repetition count 1.
+
+   A run can't span rows, but it can span planes within a row.  That's
+   why 'repetitionsLeft' and 'c' are static variables in this
+   subroutine.
+-----------------------------------------------------------------------------*/
+    static int repetitionsLeft = 0;
+    static int c;
+    int bytesGenerated;
+
+    bytesGenerated = 0;
+    while( bytesGenerated < bytesperline ) {
+        if(repetitionsLeft > 0) {
+            pcxrow[bytesGenerated++] = c;
+            --repetitionsLeft;
+        } else {
+            c = GetByte(ifP);
+            if ((c & 0xc0) != 0xc0)
+                /* This is a 1-shot byte, not a repetition count */
+                pcxrow[bytesGenerated++] = c;
+            else {
+                /* This is a repetition count for the following byte */
+                repetitionsLeft = c & 0x3f;
+                c = GetByte(ifP);
+            }
+        }
+    }
+}
+
+
+
+/*
+ * convert packed pixel format into 1 pixel per byte
+ */
+static void
+pcx_unpack_pixels(pixels, bitplanes, bytesperline, planes, bitsperpixel)
+    unsigned char   *pixels;
+    unsigned char   *bitplanes;
+    int             bytesperline;
+    int             planes;
+    int             bitsperpixel;
+{
+    register int        bits;
+
+    if (planes != 1)
+        pm_error("can't handle packed pixels with more than 1 plane" );
+#if 0
+    if (bitsperpixel == 8)
+    {
+        while (--bytesperline >= 0)
+            *pixels++ = *bitplanes++;
+    }
+    else
+#endif
+        if (bitsperpixel == 4)
+        {
+            while (--bytesperline >= 0)
+            {
+                bits        = *bitplanes++;
+                *pixels++   = (bits >> 4) & 0x0f;
+                *pixels++   = (bits     ) & 0x0f;
+            }
+        }
+        else if (bitsperpixel == 2)
+        {
+            while (--bytesperline >= 0)
+            {
+                bits        = *bitplanes++;
+                *pixels++   = (bits >> 6) & 0x03;
+                *pixels++   = (bits >> 4) & 0x03;
+                *pixels++   = (bits >> 2) & 0x03;
+                *pixels++   = (bits     ) & 0x03;
+            }
+        }
+        else if (bitsperpixel == 1)
+        {
+            while (--bytesperline >= 0)
+            {
+                bits        = *bitplanes++;
+                *pixels++   = ((bits & 0x80) != 0);
+                *pixels++   = ((bits & 0x40) != 0);
+                *pixels++   = ((bits & 0x20) != 0);
+                *pixels++   = ((bits & 0x10) != 0);
+                *pixels++   = ((bits & 0x08) != 0);
+                *pixels++   = ((bits & 0x04) != 0);
+                *pixels++   = ((bits & 0x02) != 0);
+                *pixels++   = ((bits & 0x01) != 0);
+            }
+        }
+        else
+            pm_error("pcx_unpack_pixels - can't handle %d bits per pixel", 
+                     bitsperpixel);
+}
+
+
+
+/*
+ * convert multi-plane format into 1 pixel per byte
+ */
+static void
+pcx_planes_to_pixels(pixels, bitplanes, bytesperline, planes, bitsperpixel)
+    unsigned char   *pixels;
+    unsigned char   *bitplanes;
+    int             bytesperline;
+    int             planes;
+    int             bitsperpixel;
+{
+    int  i, j;
+    int  npixels;
+    unsigned char    *p;
+
+    if (planes > 4)
+        pm_error("can't handle more than 4 planes" );
+    if (bitsperpixel != 1)
+        pm_error("can't handle more than 1 bit per pixel" );
+
+    /*
+     * clear the pixel buffer
+     */
+    npixels = (bytesperline * 8) / bitsperpixel;
+    p    = pixels;
+    while (--npixels >= 0)
+        *p++ = 0;
+
+    /*
+     * do the format conversion
+     */
+    for (i = 0; i < planes; i++)
+    {
+        int pixbit, bits, mask;
+
+        p    = pixels;
+        pixbit    = (1 << i);
+        for (j = 0; j < bytesperline; j++)
+        {
+            bits = *bitplanes++;
+            for (mask = 0x80; mask != 0; mask >>= 1, p++)
+                if (bits & mask)
+                    *p |= pixbit;
+        }
+    }
+}
+
+
+
+static void
+pcx_16col_to_ppm(FILE *       const ifP,
+                 unsigned int const headerCols,
+                 unsigned int const rows,
+                 unsigned int const BytesPerLine,
+                 unsigned int const BitsPerPixel,
+                 unsigned int const Planes,
+                 pixel *      const cmap) {
+
+    unsigned int cols;
+    int row, col, rawcols, colors;
+    unsigned char * pcxrow;
+    unsigned char * rawrow;
+    pixel * ppmrow;
+    bool paletteOk;
+
+    paletteOk = FALSE;
+
+    /* check if palette is ok  */
+    colors = (1 << BitsPerPixel) * (1 << Planes);
+    for (col = 0; col < colors - 1; ++col) {
+        if (!PPM_EQUAL(cmap[col], cmap[col+1])) {
+            paletteOk = TRUE;
+            break;
+        }
+    }
+    if (!paletteOk) {
+        unsigned int col;
+        pm_message("warning - useless header palette, "
+                   "using builtin standard palette");
+        for (col = 0; col < colors; ++col)
+            PPM_ASSIGN(cmap[col], StdRed[col], StdGreen[col], StdBlue[col]);
+    }
+
+    /*  BytesPerLine should be >= BitsPerPixel * cols / 8  */
+    rawcols = BytesPerLine * 8 / BitsPerPixel;
+    if (headerCols > rawcols) {
+        pm_message("warning - BytesPerLine = %d, "
+                   "truncating image to %d pixels",
+                   BytesPerLine, rawcols);
+        cols = rawcols;
+    } else
+        cols = headerCols;
+
+    MALLOCARRAY(pcxrow, Planes * BytesPerLine);
+    if (pcxrow == NULL)
+        pm_error("Out of memory");
+    MALLOCARRAY(rawrow, rawcols);
+    if (rawrow == NULL)
+        pm_error("Out of memory");
+
+    ppmrow = ppm_allocrow(cols);
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+
+        GetPCXRow(ifP, pcxrow, Planes * BytesPerLine);
+
+        if (Planes == 1)
+            pcx_unpack_pixels(rawrow, pcxrow, BytesPerLine, 
+                              Planes, BitsPerPixel);
+        else
+            pcx_planes_to_pixels(rawrow, pcxrow, BytesPerLine, 
+                                 Planes, BitsPerPixel);
+
+        for (col = 0; col < cols; ++col)
+            ppmrow[col] = cmap[rawrow[col]];
+        ppm_writeppmrow(stdout, ppmrow, cols, PCX_MAXVAL, 0);
+    }
+    ppm_freerow(ppmrow);
+    free(rawrow);
+    free(pcxrow);
+}
+
+
+
+static void
+pcx_256col_to_ppm(FILE *       const ifP,
+                  unsigned int const headerCols,
+                  unsigned int const rows,
+                  unsigned int const BytesPerLine) {
+
+    unsigned int cols;
+    pixel colormap[256];
+    pixel * ppmrow;
+    unsigned char ** image;
+    unsigned char colormapSignature;
+    unsigned int row;
+
+    if (headerCols > BytesPerLine) {
+        pm_message("warning - BytesPerLine = %u, "
+                   "truncating image to %u pixels",
+                   BytesPerLine,  BytesPerLine);
+        cols = BytesPerLine;
+    } else
+        cols = headerCols;
+
+    image = (unsigned char **)pm_allocarray(BytesPerLine, rows, 
+                                            sizeof(unsigned char));
+    for (row = 0; row < rows; ++row)
+        GetPCXRow(ifP, image[row], BytesPerLine);
+
+    /*
+     * 256 color images have their color map at the end of the file
+     * preceeded by a magic byte
+     */
+    colormapSignature = GetByte(ifP);
+    if (colormapSignature != PCX_256_COLORS)
+        pm_error("bad color map signature.  In a 1-plane PCX image "
+                 "such as this, we expect a magic number of %u in the byte "
+                 "following the raster, to introduce the color map.  "
+                 "Instead, this image has %u.", 
+                 PCX_256_COLORS, colormapSignature);
+    else {
+        unsigned int colorIndex;
+        
+        for (colorIndex = 0; colorIndex < 256; ++colorIndex) {
+            pixval const r = GetByte(ifP);
+            pixval const g = GetByte(ifP);
+            pixval const b = GetByte(ifP);
+            PPM_ASSIGN(colormap[colorIndex], r, g, b);
+        }
+    }
+
+    ppmrow = ppm_allocrow(cols);
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cols; ++col)
+            ppmrow[col] = colormap[image[row][col]];
+        ppm_writeppmrow(stdout, ppmrow, cols, PCX_MAXVAL, 0);
+    }
+
+    ppm_freerow(ppmrow);
+    pm_freearray((void *)image, rows);
+}
+
+
+
+static void
+pcx_truecol_to_ppm(FILE *       const ifP,
+                   unsigned int const headerCols,
+                   unsigned int const rows,
+                   unsigned int const BytesPerLine,
+                   unsigned int const Planes) {
+
+    unsigned int cols;
+    unsigned char *redrow, *greenrow, *bluerow, *intensityrow;
+    pixel * ppmrow;
+    unsigned int row;
+    
+    if (headerCols > BytesPerLine) {
+        pm_message("warning - BytesPerLine = %u, "
+                   "truncating image to %u pixels",
+                   BytesPerLine,  BytesPerLine);
+        cols = BytesPerLine;
+    } else
+        cols = headerCols;
+
+    MALLOCARRAY(redrow, BytesPerLine);
+    MALLOCARRAY(greenrow, BytesPerLine);
+    MALLOCARRAY(bluerow, BytesPerLine);
+
+    if (redrow == NULL || greenrow == NULL || bluerow == NULL)
+        pm_error("out of memory");
+
+    if (Planes == 4) {
+        MALLOCARRAY(intensityrow, BytesPerLine);
+        if (intensityrow == NULL)
+            pm_error("out of memory");
+    } else
+        intensityrow = NULL;
+
+    ppmrow = ppm_allocrow(cols);
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        GetPCXRow(ifP, redrow, BytesPerLine);
+        GetPCXRow(ifP, greenrow, BytesPerLine);
+        GetPCXRow(ifP, bluerow, BytesPerLine);
+        if( intensityrow )
+            GetPCXRow(ifP, intensityrow, BytesPerLine);
+        for (col = 0; col < cols; ++col) {
+            unsigned int const r = redrow[col];
+            unsigned int const g = greenrow[col];
+            unsigned int const b = bluerow[col];
+            unsigned int const i = intensityrow ? intensityrow[col] : 256;
+
+            PPM_ASSIGN(ppmrow[col],
+                       r * i / 256, g * i / 256, b * i / 256);
+        }
+        ppm_writeppmrow(stdout, ppmrow, cols, PCX_MAXVAL, 0);
+    }
+
+    ppm_freerow(ppmrow);
+    if (intensityrow)
+        free(intensityrow);
+    free(bluerow);
+    free(greenrow);
+    free(redrow);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE * ifP;
+    struct cmdlineInfo cmdline;
+    struct pcxHeader pcxHeader;
+    unsigned int Width, Height;
+    pixel * cmap16;
+
+    ppm_init(&argc, argv);
+
+    generateStdPalette();
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    readPcxHeader(ifP, &pcxHeader);
+
+    if (cmdline.verbose)
+        reportPcxHeader(pcxHeader);
+
+    Width  = (pcxHeader.Xmax - pcxHeader.Xmin) + 1;
+    Height = (pcxHeader.Ymax - pcxHeader.Ymin) + 1;
+
+    if (cmdline.stdpalette || allBlackPalette(pcxHeader.cmap16))
+        cmap16 = stdPalette;
+    else
+        cmap16 = pcxHeader.cmap16;
+
+    ppm_writeppminit(stdout, Width, Height, PCX_MAXVAL, 0);
+    switch (pcxHeader.BitsPerPixel) {
+    case 1:
+        if(pcxHeader.Planes >= 1 && pcxHeader.Planes <= 4)
+            pcx_16col_to_ppm(ifP, Width, Height, pcxHeader.BytesPerLine, 
+                             pcxHeader.BitsPerPixel, pcxHeader.Planes, cmap16);
+        else
+            goto fail;
+        break;
+    case 2:
+    case 4:
+        if (pcxHeader.Planes == 1)
+            pcx_16col_to_ppm(ifP, Width, Height, pcxHeader.BytesPerLine, 
+                             pcxHeader.BitsPerPixel, pcxHeader.Planes, cmap16);
+        else
+            goto fail;
+        break;
+    case 8:
+        switch(pcxHeader.Planes) {
+        case 1:
+            pcx_256col_to_ppm(ifP, Width, Height, pcxHeader.BytesPerLine);
+            break;
+        case 3:
+        case 4:
+            pcx_truecol_to_ppm(ifP, Width, Height, 
+                               pcxHeader.BytesPerLine, pcxHeader.Planes);
+            break;
+        default:
+            goto fail;
+        }
+        break;
+    default:
+    fail:
+        pm_error("can't handle %d bits per pixel image with %d planes",
+                 pcxHeader.BitsPerPixel, pcxHeader.Planes);
+    }
+    pm_close(ifP);
+    
+    return 0;
+}
+
+
diff --git a/converter/ppm/pi1toppm.c b/converter/ppm/pi1toppm.c
new file mode 100644
index 00000000..69b99863
--- /dev/null
+++ b/converter/ppm/pi1toppm.c
@@ -0,0 +1,92 @@
+/* pi1toppm.c - read a Degas PI1 file and produce a portable pixmap
+**
+** Copyright (C) 1991 by Steve Belczyk (seb3@gte.com) and Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+
+#define ROWS 200
+#define COLS 320
+#define MAXVAL 7
+
+static short screen[ROWS*COLS/4];          /* simulates the Atari's video RAM */
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    pixel pal[16];                      /* Degas palette */
+    short i;
+    short j;
+    pixel* pixelrow;
+    register pixel* pP;
+    int row, col;
+
+
+    ppm_init( &argc, argv );
+
+    /* Check args. */
+    if ( argc > 2 )
+        pm_usage( "[pi1file]" );
+
+    if ( argc == 2 )
+        ifp = pm_openr( argv[1] );
+    else
+        ifp = stdin;
+
+    /* Check resolution word */
+    (void) pm_readbigshort (ifp, &j);
+    if ( j != 0 )
+        pm_error( "not a PI1 file" );
+
+    /* Read the palette. */
+    for ( i = 0; i < 16; ++i )
+        {
+        (void) pm_readbigshort (ifp, &j);
+        PPM_ASSIGN( pal[i],
+            ( j & 0x700 ) >> 8,
+            ( j & 0x070 ) >> 4,
+            ( j & 0x007 ) );
+        }
+
+    /* Read the screen data */
+    for ( i = 0; i < ROWS*COLS/4; ++i )
+        (void) pm_readbigshort( ifp, &screen[i] );
+
+    pm_close( ifp );
+
+    /* Ok, get set for writing PPM. */
+    ppm_writeppminit( stdout, COLS, ROWS, (pixval) MAXVAL, 0 );
+    pixelrow = ppm_allocrow( COLS );
+
+    /* Now do the conversion. */
+    for ( row = 0; row < ROWS; ++row )
+        {
+        for ( col = 0, pP = pixelrow; col < COLS; ++col, ++pP )
+            {
+            register int c, ind, b, plane;
+
+            ind = 80 * row + ( ( col >> 4 ) << 2 );
+            b = 0x8000 >> ( col & 0xf );
+            c = 0;
+            for ( plane = 0; plane < 4; ++plane )
+                if ( b & screen[ind+plane] )
+                    c |= (1 << plane);
+            *pP = pal[c];
+            }
+        ppm_writeppmrow( stdout, pixelrow, COLS, (pixval) MAXVAL, 0 );
+        }
+
+    pm_close( stdout );
+
+    exit( 0 );
+    }
diff --git a/converter/ppm/picttoppm.c b/converter/ppm/picttoppm.c
new file mode 100644
index 00000000..cfc5760e
--- /dev/null
+++ b/converter/ppm/picttoppm.c
@@ -0,0 +1,3788 @@
+/*
+ * picttoppm.c -- convert a MacIntosh PICT file to PPM format.
+ *
+ * Copyright 1989,1992,1993 George Phillips
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  This software is provided "as is" without express or
+ * implied warranty.
+ *
+ * George Phillips <phillips@cs.ubc.ca>
+ * Department of Computer Science
+ * University of British Columbia
+ *
+ *
+ * 2003-02:    Handling for DirectBitsRgn opcode (0x9b) added by 
+ *             kabe@sra-tohoku.co.jp.
+ *
+ * 2004-03-27: Several bugs fixed by Steve Summit, scs@eskimo.com.
+ *
+ */
+
+#define _XOPEN_SOURCE
+
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "pbmfont.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+
+/*
+ * Typical byte, 2 byte and 4 byte integers.
+ */
+typedef unsigned char byte;
+typedef char signed_byte;
+typedef unsigned short word;
+typedef unsigned long longword;
+
+
+/*
+ * Data structures for QuickDraw (and hence PICT) stuff.
+ */
+
+struct Rect {
+    word top;
+    word left;
+    word bottom;
+    word right;
+};
+
+struct pixMap {
+    struct Rect Bounds;
+    word version;
+    word packType;
+    longword packSize;
+    longword hRes;
+    longword vRes;
+    word pixelType;
+    word pixelSize;
+    word cmpCount;
+    word cmpSize;
+    longword planeBytes;
+    longword pmTable;
+    longword pmReserved;
+};
+
+struct RGBColor {
+    word red;
+    word grn;
+    word blu;
+};
+
+struct Point {
+    word x;
+    word y;
+};
+
+struct Pattern {
+    byte pix[64];
+};
+
+struct rgbPlanes {
+    word * red;
+    word * grn;
+    word * blu;
+};
+
+typedef void (*transfer_func) (struct RGBColor* src, struct RGBColor* dst);
+
+static const char* stage;
+static struct Rect picFrame;
+static word* red;
+static word* green;
+static word* blue;
+static word rowlen;
+static word collen;
+static longword planelen;
+static int verbose;
+static int fullres;
+static int recognize_comment;
+
+static struct RGBColor black = { 0, 0, 0 };
+static struct RGBColor white = { 0xffff, 0xffff, 0xffff };
+
+/* various bits of drawing state */
+static struct RGBColor foreground = { 0, 0, 0 };
+static struct RGBColor background = { 0xffff, 0xffff, 0xffff };
+static struct RGBColor op_color;
+static struct Pattern bkpat;
+static struct Pattern fillpat;
+static struct Rect clip_rect;
+static struct Rect cur_rect;
+static struct Point current;
+static struct Pattern pen_pat;
+static word pen_width;
+static word pen_height;
+static word pen_mode;
+static transfer_func pen_trf;
+static word text_font;
+static byte text_face;
+static word text_mode;
+static transfer_func text_trf;
+static word text_size;
+static struct font* tfont;
+
+/* state for magic printer comments */
+static int ps_text;
+static byte ps_just;
+static byte ps_flip;
+static word ps_rotation;
+static byte ps_linespace;
+static int ps_cent_x;
+static int ps_cent_y;
+static int ps_cent_set;
+
+struct opdef {
+    const char* name;
+    int len;
+    void (*impl) (int);
+    const char* description;
+};
+
+struct raster {
+/*----------------------------------------------------------------------------
+   An image raster.  May be either truecolor or paletted.
+
+   This is an array of pixels in row-major order, with 'rowSize'
+   bytes per row, 'rowCount' high.
+
+   Within a row, pixels go left to right.  The rows go top to bottom.
+
+   Each pixel is either a palette index or an RGB triple, depending on
+   the format of the associated PICT.
+   
+   Each pixel is one byte if the associated PICT has 8 or fewer bits
+   per pixel.  If the associated PICT has 16 or 32 bits per pixel, an
+   element herein is 2 or 4 bytes, respectively.
+
+   For 16 bits per pixel, the two bytes for each pixel encode RGB values
+   as described in decode16().
+
+   For 32 bits per pixel, each row is divided into 4 planes.  Red,
+   green, blue, and something else, in that order.  The format of a
+   plane is one byte per pixel, left to right.
+-----------------------------------------------------------------------------*/
+    unsigned char * bytes;  /* malloc'ed */
+    unsigned int rowSize;
+    unsigned int rowCount;
+};
+
+
+static void
+allocateRaster(struct raster * const rasterP,
+               unsigned int    const width,
+               unsigned int    const height,
+               unsigned int    const bitsPerPixel) {
+
+    if (width > UINT_MAX/4)
+        pm_error("Width %u pixels too large for arithmetic", width);
+
+    rasterP->rowCount = height;
+
+    switch (bitsPerPixel) {
+    case 32:
+        /* TODO: I'm still trying to figure out this format.
+
+           My theory today:
+           The row data is in plane order (a row consists of red
+           plane, then, green, then blue, then some 4th plane).
+
+           The old hack code said 3 bytes per pixel here, and could get
+           away with it because it never got to decoding the 4th plane.
+
+           But the new clean code needs to tell it like it is and allocate
+           4 bytes per pixel.  If we say 3 bytes per pixel here, we get an
+           "invalid PICT" error because the image actually contains 4
+           bytes per pixel and as we decompress it, we run out of place
+           to put the data.
+
+           We have yet to see if we can properly interpret the data.
+        */
+
+        rasterP->rowSize = width * 4;
+        break;
+    case 16:
+        rasterP->rowSize = width * 2;
+        break;
+    case 8:
+    case 4:
+    case 2:
+    case 1:
+        rasterP->rowSize = width * 1;
+        break;
+    default:
+        pm_error("INTERNAL ERROR: impossible bitsPerPixel value in "
+                 "unpackbits(): %u", bitsPerPixel);
+    }    
+    if (UINT_MAX / rasterP->rowSize < rasterP->rowCount)
+        pm_error("Arithmetic overflow computing size of %u x %u pixel "
+                 "array.", rasterP->rowSize, rasterP->rowCount);
+
+    MALLOCARRAY(rasterP->bytes, rasterP->rowSize * rasterP->rowCount);
+    if (rasterP->bytes == NULL)
+        pm_error("unable to get memory for %u x %u pixel packbits rectangle",
+                 width, height);
+}
+
+
+static void
+freeRaster(struct raster const raster) {
+
+    free(raster.bytes);
+}
+
+
+struct blit_info {
+    struct Rect        srcRect;
+    struct Rect        srcBounds;
+    struct raster      srcplane;
+    int                pixSize;
+    struct Rect        dstRect;
+    struct RGBColor *  color_map;
+    int                mode;
+    struct blit_info * next;
+};
+
+static struct blit_info* blit_list = 0;
+static struct blit_info** last_bl = &blit_list;
+
+#define WORD_LEN (-1)
+
+/*
+ * a table of the first 194(?) opcodes.  The table is too empty.
+ *
+ * Probably could use an entry specifying if the opcode is valid in version
+ * 1, etc.
+ */
+
+/* for reserved opcodes of known length */
+#define res(length) \
+{ "reserved", (length), NULL, "reserved for Apple use" }
+
+/* for reserved opcodes of length determined by a function */
+#define resf(skipfunction) \
+{ "reserved", NA, (skipfunction), "reserved for Apple use" }
+
+/* seems like RGB colors are 6 bytes, but Apple says they're variable */
+/* I'll use 6 for now as I don't care that much. */
+#define RGB_LEN (6)
+
+
+static FILE* ifp;
+static int align = 0;
+
+
+
+static byte
+read_byte(void) {
+    int c;
+
+    if ((c = fgetc(ifp)) == EOF)
+        pm_error("EOF / read error while %s", stage);
+
+    ++align;
+    return c & 255;
+}
+
+
+
+static word
+read_word(void) {
+    byte b;
+
+    b = read_byte();
+
+    return (b << 8) | read_byte();
+}
+
+
+
+static void read_point(struct Point * const p) {
+    p->y = read_word();
+    p->x = read_word();
+}
+
+
+
+static longword
+read_long(void) {
+    word i;
+
+    i = read_word();
+    return (i << 16) | read_word();
+}
+
+
+
+static signed_byte
+read_signed_byte(void) {
+    return (signed_byte)read_byte();
+}
+
+
+
+static void 
+read_short_point(struct Point * const p) {
+    p->x = read_signed_byte();
+    p->y = read_signed_byte();
+}
+
+
+
+static void
+skip(int const byteCount) {
+    static byte buf[1024];
+    int n;
+
+    align += byteCount;
+
+    for (n = byteCount; n > 0; n -= 1024)
+        if (fread(buf, n > 1024 ? 1024 : n, 1, ifp) != 1)
+            pm_error("EOF / read error while %s", stage);
+}
+
+
+
+struct const_name {
+    int value;
+    const char * name;
+};
+
+struct const_name const transfer_name[] = {
+    { 0,    "srcCopy" },
+    { 1,    "srcOr" },
+    { 2,    "srcXor" },
+    { 3,    "srcBic" },
+    { 4,    "notSrcCopy" },
+    { 5,    "notSrcOr" },
+    { 6,    "notSrcXor" },
+    { 7,    "notSrcBic" },
+    { 32,   "blend" },
+    { 33,   "addPin" },
+    { 34,   "addOver" },
+    { 35,   "subPin" },
+    { 36,   "transparent" },
+    { 37,   "adMax" },
+    { 38,   "subOver" },
+    { 39,   "adMin" },
+    { -1,   0 }
+};
+
+struct const_name font_name[] = {
+    { 0,    "systemFont" },
+    { 1,    "applFont" },
+    { 2,    "newYork" },
+    { 3,    "geneva" },
+    { 4,    "monaco" },
+    { 5,    "venice" },
+    { 6,    "london" },
+    { 7,    "athens" },
+    { 8,    "sanFran" },
+    { 9,    "toronto" },
+    { 11,   "cairo" },
+    { 12,   "losAngeles" },
+    { 20,   "times" },
+    { 21,   "helvetica" },
+    { 22,   "courier" },
+    { 23,   "symbol" },
+    { 24,   "taliesin" },
+    { -1,   0 }
+};
+
+struct const_name ps_just_name[] = {
+    { 0,    "no" },
+    { 1,    "left" },
+    { 2,    "center" },
+    { 3,    "right" },
+    { 4,    "full" },
+    { -1,   0 }
+};
+
+struct const_name ps_flip_name[] = {
+    { 0,    "no" },
+    { 1,    "horizontal" },
+    { 2,    "vertical" },
+    { -1,   0 }
+};
+
+
+
+static const char*
+const_name(const struct const_name * const table,
+           unsigned int              const ct) {
+
+    static char numbuf[32];
+
+    unsigned int i;
+
+    for (i = 0; table[i].name; ++i)
+        if (table[i].value == ct)
+            return table[i].name;
+    
+    sprintf(numbuf, "? (%u)", ct);
+    return numbuf;
+}
+
+
+
+static void 
+picComment(word const type, 
+           int const length) {
+
+    unsigned int remainingLength;
+
+    switch (type) {
+    case 150:
+        if (verbose) pm_message("TextBegin");
+        if (length >= 6) {
+            ps_just = read_byte();
+            ps_flip = read_byte();
+            ps_rotation = read_word();
+            ps_linespace = read_byte();
+            remainingLength = length - 5;
+            if (recognize_comment)
+                ps_text = 1;
+            ps_cent_set = 0;
+            if (verbose) {
+                pm_message("%s justification, %s flip, %d degree rotation, "
+                           "%d/2 linespacing",
+                           const_name(ps_just_name, ps_just),
+                           const_name(ps_flip_name, ps_flip),
+                           ps_rotation, ps_linespace);
+            }
+        } else
+            remainingLength = length;
+        break;
+    case 151:
+        if (verbose) pm_message("TextEnd");
+        ps_text = 0;
+        remainingLength = length;
+        break;
+    case 152:
+        if (verbose) pm_message("StringBegin");
+        remainingLength = length;
+        break;
+    case 153:
+        if (verbose) pm_message("StringEnd");
+        remainingLength = length;
+        break;
+    case 154:
+        if (verbose) pm_message("TextCenter");
+        if (length < 8)
+            remainingLength = length;
+        else {
+            ps_cent_y = read_word();
+            if (ps_cent_y > 32767)
+                ps_cent_y -= 65536;
+            skip(2); /* ignore fractional part */
+            ps_cent_x = read_word();
+            if (ps_cent_x > 32767)
+                ps_cent_x -= 65536;
+            skip(2); /* ignore fractional part */
+            remainingLength = length - 8;
+            if (verbose)
+                pm_message("offset %d %d", ps_cent_x, ps_cent_y);
+        }
+        break;
+    case 155:
+        if (verbose) pm_message("LineLayoutOff");
+        remainingLength = length;
+        break;
+    case 156:
+        if (verbose) pm_message("LineLayoutOn");
+        remainingLength = length;
+        break;
+    case 160:
+        if (verbose) pm_message("PolyBegin");
+        remainingLength = length;
+        break;
+    case 161:
+        if (verbose) pm_message("PolyEnd");
+        remainingLength = length;
+        break;
+    case 163:
+        if (verbose) pm_message("PolyIgnore");
+        remainingLength = length;
+        break;
+    case 164:
+        if (verbose) pm_message("PolySmooth");
+        remainingLength = length;
+        break;
+    case 165:
+        if (verbose) pm_message("picPlyClo");
+        remainingLength = length;
+        break;
+    case 180:
+        if (verbose) pm_message("DashedLine");
+        remainingLength = length;
+        break;
+    case 181:
+        if (verbose) pm_message("DashedStop");
+        remainingLength = length;
+        break;
+    case 182:
+        if (verbose) pm_message("SetLineWidth");
+        remainingLength = length;
+        break;
+    case 190:
+        if (verbose) pm_message("PostScriptBegin");
+        remainingLength = length;
+        break;
+    case 191:
+        if (verbose) pm_message("PostScriptEnd");
+        remainingLength = length;
+        break;
+    case 192:
+        if (verbose) pm_message("PostScriptHandle");
+        remainingLength = length;
+        break;
+    case 193:
+        if (verbose) pm_message("PostScriptFile");
+        remainingLength = length;
+        break;
+    case 194:
+        if (verbose) pm_message("TextIsPostScript");
+        remainingLength = length;
+        break;
+    case 195:
+        if (verbose) pm_message("ResourcePS");
+        remainingLength = length;
+        break;
+    case 200:
+        if (verbose) pm_message("RotateBegin");
+        remainingLength = length;
+        break;
+    case 201:
+        if (verbose) pm_message("RotateEnd");
+        remainingLength = length;
+        break;
+    case 202:
+        if (verbose) pm_message("RotateCenter");
+        remainingLength = length;
+        break;
+    case 210:
+        if (verbose) pm_message("FormsPrinting");
+        remainingLength = length;
+        break;
+    case 211:
+        if (verbose) pm_message("EndFormsPrinting");
+        remainingLength = length;
+        break;
+    default:
+        if (verbose) pm_message("%d", type);
+        remainingLength = length;
+        break;
+    }
+    if (remainingLength > 0)
+        skip(remainingLength);
+}
+
+
+
+static void
+ShortComment(int const version) {
+    picComment(read_word(), 0);
+}
+
+
+
+static void
+LongComment(int const version) {
+    word type;
+
+    type = read_word();
+    picComment(type, read_word());
+}
+
+
+
+static void
+skip_poly_or_region(int const version) {
+    stage = "skipping polygon or region";
+    skip(read_word() - 2);
+}
+
+
+#define NA (0)
+
+#define FNT_BOLD    (1)
+#define FNT_ITALIC  (2)
+#define FNT_ULINE   (4)
+#define FNT_OUTLINE (8)
+#define FNT_SHADOW  (16)
+#define FNT_CONDENSE    (32)
+#define FNT_EXTEND  (64)
+
+/* Some font searching routines */
+
+struct fontinfo {
+    int font;
+    int size;
+    int style;
+    char* filename;
+    struct font* loaded;
+    struct fontinfo* next;
+};
+
+static struct fontinfo* fontlist = 0;
+static struct fontinfo** fontlist_ins = &fontlist;
+
+
+
+static int 
+load_fontdir(const char * const dirfile) {
+/*----------------------------------------------------------------------------
+   Load the font directory from file named 'dirfile'.  Add its contents
+   to the global list of fonts 'fontlist'.
+-----------------------------------------------------------------------------*/
+    FILE* fp;
+    int n, nfont;
+    char* arg[5], line[1024];
+    struct fontinfo* fontinfo;
+
+    if (!(fp = fopen(dirfile, "rb")))
+        return -1;
+    
+    nfont = 0;
+    while (fgets(line, 1024, fp)) {
+        if ((n = mk_argvn(line, arg, 5)) == 0 || arg[0][0] == '#')
+            continue;
+        if (n != 4)
+            continue;
+        MALLOCVAR(fontinfo);
+        if (fontinfo == NULL)
+            pm_error("out of memory for font information");
+        MALLOCARRAY(fontinfo->filename, strlen(arg[3] + 1));
+        if (fontinfo->filename == NULL)
+            pm_error("out of memory for font information file name");
+
+        fontinfo->font = atoi(arg[0]);
+        fontinfo->size = atoi(arg[1]);
+        fontinfo->style = atoi(arg[2]);
+        strcpy(fontinfo->filename, arg[3]);
+        fontinfo->loaded = 0;
+
+        fontinfo->next = 0;
+        *fontlist_ins = fontinfo;
+        fontlist_ins = &fontinfo->next;
+        nfont++;
+    }
+
+    return nfont;
+}
+
+
+
+static void
+read_rect(struct Rect * const r) {
+    r->top = read_word();
+    r->left = read_word();
+    r->bottom = read_word();
+    r->right = read_word();
+}
+
+
+
+static void
+dumpRect(const char * const label,
+         struct Rect  const rectangle) {
+
+    pm_message("%s (%u,%u) (%u,%u)",
+               label,
+               rectangle.left,  rectangle.top,
+               rectangle.right, rectangle.bottom);
+}
+
+
+
+static int
+rectwidth(const struct Rect * const r) {
+    return r->right - r->left;
+}
+
+
+
+static int
+rectheight(const struct Rect * const r) {
+    return r->bottom - r->top;
+}
+
+
+
+static bool
+rectsamesize(const struct Rect * const r1, 
+             const struct Rect * const r2) {
+    return r1->right - r1->left == r2->right - r2->left &&
+           r1->bottom - r1->top == r2->bottom - r2->top ;
+}
+
+
+
+static void
+rectinter(struct Rect   const r1, 
+          struct Rect   const r2, 
+          struct Rect * const intersectionP) {
+
+    intersectionP->left   = MAX(r1.left,   r2.left);
+    intersectionP->top    = MAX(r1.top,    r2.top);
+    intersectionP->right  = MIN(r1.right,  r2.right);
+    intersectionP->bottom = MIN(r1.bottom, r2.bottom);
+}
+
+
+
+static void
+rectscale(struct Rect * const r, 
+          double        const xscale, 
+          double        const yscale) {
+    r->left *= xscale;
+    r->right *= xscale;
+    r->top *= yscale;
+    r->bottom *= yscale;
+}
+
+
+
+static struct blit_info* 
+add_blit_list(void) {
+
+    struct blit_info * bi;
+    
+    MALLOCVAR(bi);
+    if (bi == NULL)
+        pm_error("out of memory for blit list");
+    
+    bi->next = 0;
+    *last_bl = bi;
+    last_bl = &bi->next;
+    
+    return bi;
+}
+
+
+
+/* Various transfer functions for blits.
+ *
+ * Note src[Not]{Or,Xor,Copy} only work if the source pixmap was originally
+ * a bitmap.
+ * There's also a small bug that the foreground and background colors
+ * are not used in a srcCopy; this wouldn't be hard to fix.
+ * It IS a problem since the foreground and background colors CAN be changed.
+ */
+
+static bool
+rgbAllSame(const struct RGBColor * const colorP,
+           unsigned int            const value) {
+
+    return (colorP->red == value &&
+            colorP->grn == value &&
+            colorP->blu == value);
+}
+
+
+static bool
+rgbIsWhite(const struct RGBColor * const colorP) {
+
+    return rgbAllSame(colorP, 0xffff);
+}
+
+static bool
+rgbIsBlack(const struct RGBColor * const colorP) {
+
+    return rgbAllSame(colorP, 0);
+}
+
+
+static void 
+srcCopy(struct RGBColor * const src, 
+        struct RGBColor * const dst) {
+
+    if (rgbIsBlack(src))
+        *dst = foreground;
+    else
+        *dst = background;
+}
+
+
+
+static void 
+srcOr(struct RGBColor * const src, 
+      struct RGBColor * const dst) {
+    if (rgbIsBlack(src))
+        *dst = foreground;
+}
+
+
+
+static void 
+srcXor(struct RGBColor * const src, 
+       struct RGBColor * const dst) {
+    dst->red ^= ~src->red;
+    dst->grn ^= ~src->grn;
+    dst->blu ^= ~src->blu;
+}
+
+
+
+static void 
+srcBic(struct RGBColor * const src, 
+       struct RGBColor * const dst) {
+    if (rgbIsBlack(src))
+        *dst = background;
+}
+
+
+
+static void 
+notSrcCopy(struct RGBColor * const src, 
+           struct RGBColor * const dst) {
+    if (rgbIsWhite(src))
+        *dst = foreground;
+    else if (rgbIsBlack(src))
+        *dst = background;
+}
+
+
+
+static void 
+notSrcOr(struct RGBColor * const src, 
+         struct RGBColor * const dst) {
+    if (rgbIsWhite(src))
+        *dst = foreground;
+}
+
+
+
+static void 
+notSrcBic(struct RGBColor * const src, 
+          struct RGBColor * const dst) {
+    if (rgbIsWhite(src))
+        *dst = background;
+}
+
+
+
+static void 
+notSrcXor(struct RGBColor * const src, 
+          struct RGBColor * const dst) {
+    dst->red ^= src->red;
+    dst->grn ^= src->grn;
+    dst->blu ^= src->blu;
+}
+
+
+
+static void 
+addOver(struct RGBColor * const src, 
+        struct RGBColor * const dst) {
+    dst->red += src->red;
+    dst->grn += src->grn;
+    dst->blu += src->blu;
+}
+
+
+
+static void 
+addPin(struct RGBColor * const src, 
+       struct RGBColor * const dst) {
+    if ((long)dst->red + (long)src->red > (long)op_color.red)
+        dst->red = op_color.red;
+    else
+        dst->red = dst->red + src->red;
+
+    if ((long)dst->grn + (long)src->grn > (long)op_color.grn)
+        dst->grn = op_color.grn;
+    else
+        dst->grn = dst->grn + src->grn;
+
+    if ((long)dst->blu + (long)src->blu > (long)op_color.blu)
+        dst->blu = op_color.blu;
+    else
+        dst->blu = dst->blu + src->blu;
+}
+
+
+
+static void 
+subOver(struct RGBColor * const src, 
+        struct RGBColor * const dst) {
+    dst->red -= src->red;
+    dst->grn -= src->grn;
+    dst->blu -= src->blu;
+}
+
+
+
+/* or maybe its src - dst; my copy of Inside Mac is unclear */
+
+
+static void 
+subPin(struct RGBColor * const src, 
+       struct RGBColor * const dst) {
+    if ((long)dst->red - (long)src->red < (long)op_color.red)
+        dst->red = op_color.red;
+    else
+        dst->red = dst->red - src->red;
+
+    if ((long)dst->grn - (long)src->grn < (long)op_color.grn)
+        dst->grn = op_color.grn;
+    else
+        dst->grn = dst->grn - src->grn;
+
+    if ((long)dst->blu - (long)src->blu < (long)op_color.blu)
+        dst->blu = op_color.blu;
+    else
+        dst->blu = dst->blu - src->blu;
+}
+
+
+
+static void 
+adMax(struct RGBColor * const src, 
+      struct RGBColor * const dst) {
+    if (src->red > dst->red) dst->red = src->red;
+    if (src->grn > dst->grn) dst->grn = src->grn;
+    if (src->blu > dst->blu) dst->blu = src->blu;
+}
+
+
+
+static void 
+adMin(struct RGBColor * const src, 
+      struct RGBColor * const dst) {
+    if (src->red < dst->red) dst->red = src->red;
+    if (src->grn < dst->grn) dst->grn = src->grn;
+    if (src->blu < dst->blu) dst->blu = src->blu;
+}
+
+
+
+static void 
+blend(struct RGBColor * const src, 
+      struct RGBColor * const dst) {
+#define blend_component(cmp)    \
+    ((long)src->cmp * (long)op_color.cmp) / 65536 +    \
+    ((long)dst->cmp * (long)(65536 - op_color.cmp) / 65536)
+
+    dst->red = blend_component(red);
+    dst->grn = blend_component(grn);
+    dst->blu = blend_component(blu);
+}
+
+
+
+static void 
+transparent(struct RGBColor * const src, 
+            struct RGBColor * const dst) {
+    if (src->red != background.red ||
+        src->grn != background.grn ||
+        src->blu != background.blu) {
+        *dst = *src;
+    }
+}
+
+
+
+static transfer_func 
+transfer(int const mode) {
+    switch (mode) {
+    case  0: return srcCopy;
+    case  1: return srcOr;
+    case  2: return srcXor;
+    case  3: return srcBic;
+    case  4: return notSrcCopy;
+    case  5: return notSrcOr;
+    case  6: return notSrcXor;
+    case  7: return notSrcBic;
+    case 32: return blend;
+    case 33: return addPin;
+    case 34: return addOver;
+    case 35: return subPin;
+    case 36: return transparent;
+    case 37: return adMax;
+    case 38: return subOver;
+    case 39: return adMin;
+    default:
+        if (mode != 0)
+            pm_message("no transfer function for code %s, using srcCopy",
+                const_name(transfer_name, mode));
+        return srcCopy;
+    }
+}
+
+
+
+static pixval
+redepth(pixval const c,
+        pixval const oldMaxval) {
+    
+    return (c * PPM_MAXMAXVAL + oldMaxval / 2) / oldMaxval;
+}
+
+
+
+static struct RGBColor
+decode16(unsigned char * const sixteen) {
+/*----------------------------------------------------------------------------
+   Decode a 16 bit PICT encoding of RGB:
+
+      Bit   0:    nothing
+      Bits  1- 5: red
+      Bits  6-10: green
+      Bits 11-15: blue
+
+   'sixteen' is a two byte array.
+-----------------------------------------------------------------------------*/
+    struct RGBColor retval;
+
+    retval.red = (sixteen[0] & 0x7c) >> 2;
+    retval.grn = (sixteen[0] & 0x03) << 3 | (sixteen[1] & 0xe0) >> 5;
+    retval.blu = (sixteen[1] & 0x1f) >> 0;
+                
+    return retval;
+}
+
+
+
+static void
+doDiffSize(struct Rect       const clipsrc,
+           struct Rect       const clipdst,
+           int               const pixSize,
+           int               const xsize,
+           int               const ysize,
+           transfer_func     const trf,
+           struct RGBColor * const color_map, 
+           unsigned char *   const src,
+           int               const srcwid, 
+           struct rgbPlanes  const dst,
+           unsigned int      const dstwid) {
+
+    unsigned int const dstadd = dstwid - xsize;
+
+    FILE * pnmscalePipeP;
+    const char * command;
+    FILE * scaled;
+    int cols, rows, format;
+    pixval maxval;
+    pixel * row;
+    pixel * rowp;
+    FILE * tempFileP;
+    const char * tempFilename;
+    word * reddst;
+    word * grndst;
+    word * bludst;
+
+    reddst = dst.red;  /* initial value */
+    grndst = dst.grn;  /* initial value */
+    bludst = dst.blu;  /* initial value */
+
+    pm_make_tmpfile(&tempFileP, &tempFilename);
+
+    pm_close(tempFileP);
+
+    asprintfN(&command, "pnmscale -xsize %d -ysize %d > %s",
+              rectwidth(&clipdst), rectheight(&clipdst), tempFilename);
+
+    pm_message("running command '%s'", command);
+
+    pnmscalePipeP = popen(command, "w");
+    if (pnmscalePipeP == NULL)
+        pm_error("cannot execute command '%s'  popen() errno = %s (%d)",
+                 command, strerror(errno), errno);
+
+    strfree(command);
+
+    fprintf(pnmscalePipeP, "P6\n%d %d\n%d\n",
+            rectwidth(&clipsrc), rectheight(&clipsrc), PPM_MAXMAXVAL);
+
+    switch (pixSize) {
+    case 8: {
+        unsigned int rowNumber;
+        for (rowNumber = 0; rowNumber < ysize; ++rowNumber) {
+            unsigned char * const row = &src[rowNumber * srcwid];
+            unsigned int colNumber;
+            for (colNumber = 0; colNumber < xsize; ++colNumber) {
+                unsigned int const colorIndex = row[colNumber];
+                struct RGBColor * const ct = &color_map[colorIndex];
+                fputc(redepth(ct->red, 65535L), pnmscalePipeP);
+                fputc(redepth(ct->grn, 65535L), pnmscalePipeP);
+                fputc(redepth(ct->blu, 65535L), pnmscalePipeP);
+            }
+        }
+    }
+    break;
+    case 16: {
+        unsigned int rowNumber;
+        for (rowNumber = 0; rowNumber < ysize; ++rowNumber) {
+            unsigned char * const row = &src[rowNumber * srcwid];
+            unsigned int colNumber;
+            for (colNumber = 0; colNumber < xsize; ++colNumber) {
+                struct RGBColor const color = decode16(&row[colNumber * 2]);
+                fputc(redepth(color.red, 32), pnmscalePipeP);
+                fputc(redepth(color.grn, 32), pnmscalePipeP);
+                fputc(redepth(color.blu, 32), pnmscalePipeP);
+            }
+        }
+    }
+    break;
+    case 32: {
+        unsigned int const planeSize = srcwid / 4;
+        unsigned int rowNumber;
+        
+        for (rowNumber = 0; rowNumber < ysize; ++rowNumber) {
+            unsigned char * const row = &src[rowNumber * srcwid];
+            unsigned char * const redPlane = &row[planeSize * 0];
+            unsigned char * const grnPlane = &row[planeSize * 1];
+            unsigned char * const bluPlane = &row[planeSize * 2];
+
+            unsigned int colNumber;
+            for (colNumber = 0; colNumber < xsize; ++colNumber) {
+                fputc(redepth(redPlane[colNumber], 256), pnmscalePipeP);
+                fputc(redepth(grnPlane[colNumber], 256), pnmscalePipeP);
+                fputc(redepth(bluPlane[colNumber], 256), pnmscalePipeP);
+            }
+        }
+    }
+    break;
+    }
+
+    if (pclose(pnmscalePipeP))
+        pm_error("pnmscale failed.  pclose() returned Errno %s (%d)",
+                 strerror(errno), errno);
+
+    ppm_readppminit(scaled = pm_openr(tempFilename), &cols, &rows,
+                    &maxval, &format);
+    row = ppm_allocrow(cols);
+    /* couldn't hurt to assert cols, rows and maxval... */  
+
+    if (trf == NULL) {
+        while (rows-- > 0) {
+            unsigned int i;
+            ppm_readppmrow(scaled, row, cols, maxval, format);
+            for (i = 0, rowp = row; i < cols; ++i, ++rowp) {
+                *reddst++ = PPM_GETR(*rowp) * 65536L / (maxval + 1); 
+                *grndst++ = PPM_GETG(*rowp) * 65536L / (maxval + 1); 
+                *bludst++ = PPM_GETB(*rowp) * 65536L / (maxval + 1); 
+            }
+            reddst += dstadd;
+            grndst += dstadd;
+            bludst += dstadd;
+        }
+    }
+    else {
+        while (rows-- > 0) {
+            unsigned int i;
+            ppm_readppmrow(scaled, row, cols, maxval, format);
+            for (i = 0, rowp = row; i < cols; i++, rowp++) {
+                struct RGBColor dst_c, src_c;
+                dst_c.red = *reddst;
+                dst_c.grn = *grndst;
+                dst_c.blu = *bludst;
+                src_c.red = PPM_GETR(*rowp) * 65536L / (maxval + 1); 
+                src_c.grn = PPM_GETG(*rowp) * 65536L / (maxval + 1); 
+                src_c.blu = PPM_GETB(*rowp) * 65536L / (maxval + 1); 
+                (*trf)(&src_c, &dst_c);
+                *reddst++ = dst_c.red;
+                *grndst++ = dst_c.grn;
+                *bludst++ = dst_c.blu;
+            }
+            reddst += dstadd;
+            grndst += dstadd;
+            bludst += dstadd;
+        }
+    }
+
+    pm_close(scaled);
+    ppm_freerow(row);
+    strfree(tempFilename);
+    unlink(tempFilename);
+}
+
+
+
+static void
+getRgb(struct rgbPlanes  const planes,
+       unsigned int      const index,
+       struct RGBColor * const rgbP) {
+
+    rgbP->red = planes.red[index];
+    rgbP->grn = planes.grn[index];
+    rgbP->blu = planes.blu[index];
+}
+
+
+
+static void
+putRgb(struct RGBColor  const rgb,
+       unsigned int     const index,
+       struct rgbPlanes const planes) {
+
+    planes.red[index] = rgb.red;
+    planes.grn[index] = rgb.grn;
+    planes.blu[index] = rgb.blu;
+}
+
+
+
+static void
+doSameSize(transfer_func           trf,
+           int               const pixSize,
+           int               const xsize,
+           int               const ysize,
+           unsigned char *   const src,
+           unsigned int      const srcwid,
+           struct RGBColor * const color_map,
+           struct rgbPlanes  const dst,
+           unsigned int      const dstwid) {
+/*----------------------------------------------------------------------------
+   Generalized (but slow) blit.
+
+   Transfer pixels from 'src' to 'dst', applying the transfer function
+   'trf'.
+
+   'src' has the same format as the 'bytes' member of struct raster.
+   'srcwid' is the size in bytes of each row, like raster.rowSize.
+
+   We use only the first 'ysize' rows and only the first 'xsize'
+   pixels of each row.
+
+   We really should clean this up so that we can take pixels out of
+   the middle of a row and rows out of the middle of the raster.  As
+   it stands, Caller achieves the same result by passing as 'src'
+   a pointer into the middle of a raster -- the upper left corner of
+   the rectangle he wants.  But that is messy and nonobvious.
+
+   Each plane of 'dst' is one word per pixel and contains actual
+   colors, never a palette index.  It is an array in row-major order
+   with 'dstwid' words per row.
+-----------------------------------------------------------------------------*/
+    switch (pixSize) {
+    case 8: {
+        unsigned int rowNumber;
+
+        for (rowNumber = 0; rowNumber < ysize; ++rowNumber) {
+            unsigned char * const srcrow = &src[rowNumber * srcwid];
+            unsigned int const dstRowCurs = rowNumber * dstwid;
+
+            unsigned int colNumber;
+            for (colNumber = 0; colNumber < xsize; ++colNumber) {
+                unsigned int const dstCursor = dstRowCurs + colNumber;
+                unsigned int const colorIndex = srcrow[colNumber];
+                struct RGBColor dstColor;
+                getRgb(dst, dstCursor, &dstColor);
+                (*trf)(&color_map[colorIndex], &dstColor);
+                putRgb(dstColor, dstCursor, dst);
+            }
+        }
+    } break;
+    case 16: {
+        unsigned int rowNumber;
+
+        for (rowNumber = 0; rowNumber < ysize; ++rowNumber) {
+            unsigned char * const row = &src[rowNumber * srcwid];
+            unsigned int const dstRowCurs = rowNumber * dstwid;
+
+            unsigned int colNumber;
+            for (colNumber = 0; colNumber < xsize; ++colNumber) {
+                unsigned int const dstCursor = dstRowCurs + colNumber;
+                struct RGBColor const srcColor = decode16(&row[colNumber*2]);
+                struct RGBColor dstColor;
+                struct RGBColor scaledSrcColor;
+                scaledSrcColor.red = srcColor.red << 11;
+                scaledSrcColor.grn = srcColor.grn << 11;
+                scaledSrcColor.blu = srcColor.blu << 11;
+                getRgb(dst, dstCursor, &dstColor);
+                (*trf)(&scaledSrcColor, &dstColor);
+                putRgb(dstColor, dstCursor, dst);
+            }
+        }
+    } break;
+    case 32: {
+        unsigned int const planeSize = srcwid / 4;
+        unsigned int rowNumber;
+
+        for (rowNumber = 0; rowNumber < ysize; ++rowNumber) {
+            unsigned char * const row = &src[rowNumber * srcwid];
+            unsigned char * const redPlane = &row[planeSize * 0];
+            unsigned char * const grnPlane = &row[planeSize * 1];
+            unsigned char * const bluPlane = &row[planeSize * 2];
+            unsigned int const dstRowCurs = rowNumber * dstwid;
+
+            unsigned int colNumber;
+
+            for (colNumber = 0; colNumber < xsize; ++colNumber) {
+                unsigned int const dstCursor = dstRowCurs + colNumber;
+                struct RGBColor srcColor, dstColor;
+                getRgb(dst, dstCursor, &dstColor);
+                srcColor.red = redPlane[colNumber] << 8;
+                srcColor.grn = grnPlane[colNumber] << 8;
+                srcColor.blu = bluPlane[colNumber] << 8;
+                (*trf)(&srcColor, &dstColor);
+                putRgb(dstColor, dstCursor, dst);
+            }
+        }
+    } break;
+    default:
+        pm_error("Impossible value of pixSize: %u", pixSize);
+    }
+}
+
+
+
+static void
+blitIdempotent(unsigned int          const pixSize,
+               unsigned int          const xsize,
+               unsigned int          const ysize,
+               unsigned char *       const src,
+               unsigned int          const srcwid,
+               struct RGBColor *     const colorMap, 
+               struct rgbPlanes      const dst,
+               unsigned int          const dstwid) {
+/*----------------------------------------------------------------------------
+  This is the same as doSameSize(), except optimized for the case that
+  the transfer function is idempotent (i.e. it's just a straight copy).
+  The original author's comments suggest that this optimization isn't
+  all that important -- that he just wrote this first and instead of
+  expanding it to handle arbitrary transfer functions, added functions
+  for that.
+-----------------------------------------------------------------------------*/
+    switch (pixSize) {
+    case 8: {
+        unsigned int rowNumber;
+        
+        for (rowNumber = 0; rowNumber < ysize; ++rowNumber) {
+            unsigned char * const srcrow = &src[rowNumber * srcwid];
+            unsigned int const dstRowCurs = rowNumber * dstwid;
+            unsigned int colNumber;
+            for (colNumber = 0; colNumber < xsize; ++colNumber) {
+                unsigned int const dstCursor = dstRowCurs + colNumber;
+                struct RGBColor * const ct = colorMap + srcrow[colNumber];
+                dst.red[dstCursor] = ct->red;
+                dst.grn[dstCursor] = ct->grn;
+                dst.blu[dstCursor] = ct->blu;
+            }
+        }
+    } break;
+    case 16: {
+        unsigned int rowNumber;
+
+        for (rowNumber = 0; rowNumber < ysize; ++rowNumber) {
+            unsigned char * const srcrow = &src[rowNumber * srcwid];
+            unsigned int const dstRowCurs = rowNumber * dstwid;
+            unsigned int colNumber;
+            for (colNumber = 0; colNumber < xsize; ++colNumber) {
+                unsigned int const dstCursor = dstRowCurs + colNumber;
+                struct RGBColor const srcColor =
+                    decode16(&srcrow[colNumber * 2]);
+                dst.red[dstCursor] = srcColor.red << 11;
+                dst.grn[dstCursor] = srcColor.grn << 11;
+                dst.blu[dstCursor] = srcColor.blu << 11;
+            }
+        }
+    } break;
+    case 32: {
+        unsigned int const planeSize = srcwid / 4;
+        unsigned int rowNumber;
+
+        for (rowNumber = 0; rowNumber < ysize; ++rowNumber) {
+            unsigned char * const srcrow = &src[rowNumber * srcwid];
+            unsigned char * const redPlane = &srcrow[planeSize * 0];
+            unsigned char * const grnPlane = &srcrow[planeSize * 1];
+            unsigned char * const bluPlane = &srcrow[planeSize * 2];
+            unsigned int const dstRowCurs = rowNumber * dstwid;
+
+            unsigned int colNumber;
+            for (colNumber = 0; colNumber < xsize; ++colNumber) {
+                unsigned int const dstCursor = dstRowCurs + colNumber;
+                dst.red[dstCursor] = redPlane[colNumber] << 8;
+                dst.grn[dstCursor] = grnPlane[colNumber] << 8;
+                dst.blu[dstCursor] = bluPlane[colNumber] << 8;
+            }
+        }
+    } break;
+    default:
+        pm_error("INTERNAL ERROR: invalid bits per pixel (%u) in "
+                 "blitIdempotent()", pixSize);
+    }
+}
+
+
+
+static void
+generalBlit(struct Rect       const srcRect, 
+            struct Rect       const srcBounds, 
+            struct raster     const srcplane,
+            int               const pixSize, 
+            struct Rect       const dstRect, 
+            struct Rect       const dstBounds, 
+            int               const dstwid, 
+            struct RGBColor * const color_map, 
+            int               const mode,
+            struct Rect       const clipsrc,
+            struct Rect       const clipdst) {
+    
+    unsigned char * src;
+    struct rgbPlanes dst;
+    int dstoff;
+    int xsize;
+    int ysize;
+    int srcadd;
+    transfer_func trf;
+
+    if (verbose) {
+        dumpRect("copying from:", clipsrc);
+        dumpRect("to:          ", clipdst);
+        pm_message("a %u x %u area to a %u x %u area",
+                   rectwidth(&clipsrc), rectheight(&clipsrc),
+                   rectwidth(&clipdst), rectheight(&clipdst));
+    }
+
+    {
+        unsigned int const pkpixsize = pixSize == 16 ? 2 : 1;
+        unsigned int const srcRowNumber = clipsrc.top - srcBounds.top;
+        unsigned int const srcRowOffset =
+            (clipsrc.left - srcBounds.left) * pkpixsize;
+        assert(srcRowNumber < srcplane.rowCount);
+        assert(srcRowOffset < srcplane.rowSize);
+        src = srcplane.bytes + srcRowNumber * srcplane.rowSize + srcRowOffset;
+        xsize = clipsrc.right - clipsrc.left;
+        ysize = clipsrc.bottom - clipsrc.top;
+        srcadd = srcplane.rowSize - xsize * pkpixsize;
+    }
+
+    dstoff = (clipdst.top - dstBounds.top) * dstwid +
+        (clipdst.left - dstBounds.left);
+    dst.red = red + dstoff;
+    dst.grn = green + dstoff;
+    dst.blu = blue + dstoff;
+
+    /* get rid of Text mask mode bit, if (erroneously) set */
+    if ((mode & ~64) == 0)
+        trf = NULL;    /* optimized srcCopy */
+    else
+        trf = transfer(mode & ~64);
+
+    if (!rectsamesize(&clipsrc, &clipdst))
+        doDiffSize(clipsrc, clipdst, pixSize, xsize, ysize,
+                   trf, color_map, src, srcplane.rowSize, dst, dstwid);
+    else {
+        if (trf == NULL)
+            blitIdempotent(pixSize, xsize, ysize, src, srcplane.rowSize,
+                           color_map, dst, dstwid);
+        else
+            doSameSize(trf, pixSize, xsize, ysize, src, srcplane.rowSize,
+                       color_map, dst, dstwid);
+    }
+}
+
+
+
+static int
+blit(struct Rect       const srcRect, 
+     struct Rect       const srcBounds, 
+     struct raster     const srcplane,
+     int               const pixSize, 
+     struct Rect       const dstRect, 
+     struct Rect       const dstBounds, 
+     int               const dstwid, 
+     struct RGBColor * const color_map, 
+     int               const mode) {
+
+    /* I can't tell what the result value of this function is supposed to mean,
+       but I found several return statements that did not set it to anything,
+       and several calls that examine it.  I'm guessing that "1" is the 
+       appropriate thing to return in those cases, so I made it so.
+       -Bryan 00.03.02
+    */
+
+    int retval;
+
+    if (ps_text)
+        retval = 1;
+    else {
+        /* Almost got it.  Clip source rect with source bounds.
+           clip dest rect with dest bounds.  If source and
+           destination are not the same size, use Pnmscale
+           to get a nicely sized rectangle.
+        */
+        struct Rect clipsrc;
+        struct Rect clipdst;
+
+        rectinter(srcBounds, srcRect, &clipsrc);
+        rectinter(dstBounds, dstRect, &clipdst);
+
+        if (fullres) {
+            struct blit_info * bi;
+            bi = add_blit_list();
+            bi->srcRect   = clipsrc;
+            bi->srcBounds = srcBounds;
+            bi->srcplane  = srcplane;
+            bi->pixSize   = pixSize;
+            bi->dstRect   = clipdst;
+            bi->color_map = color_map;
+            bi->mode      = mode;
+
+            retval = 0;
+        } else {
+            generalBlit(srcRect, srcBounds, srcplane, pixSize,
+                        dstRect, dstBounds, dstwid, color_map, mode,
+                        clipsrc, clipdst);
+
+            retval = 1;
+        }
+    }
+    return retval;
+}
+
+
+
+/* allocation is same for version 1 or version 2.  We are super-duper
+ * wasteful of memory for version 1 picts.  Someday, we'll separate
+ * things and only allocate a byte per pixel for version 1 (or heck,
+ * even only a bit, but that would require even more extra work).
+ */
+
+static void 
+allocPlanes(struct rgbPlanes * const planesP) {
+
+    struct rgbPlanes planes;
+
+    rowlen = picFrame.right - picFrame.left;
+    collen = picFrame.bottom - picFrame.top;
+
+    clip_rect = picFrame;
+
+    planelen = rowlen * collen;
+    MALLOCARRAY(planes.red,  planelen);
+    MALLOCARRAY(planes.grn, planelen);
+    MALLOCARRAY(planes.blu, planelen);
+    if (planes.red == NULL || planes.grn == NULL || planes.blu == NULL)
+        pm_error("not enough memory to hold picture");
+
+    /* initialize background to white */
+    memset(planes.red, 255, planelen * sizeof(word));
+    memset(planes.grn, 255, planelen * sizeof(word));
+    memset(planes.blu, 255, planelen * sizeof(word));
+
+    /* Until we wean this program off of global variables, we have to
+       set these:
+    */
+
+    red   = planes.red;
+    green = planes.grn;
+    blue  = planes.blu;
+}
+
+
+
+static void
+freePlanes(struct rgbPlanes const planes) {
+
+    free(planes.red);
+    free(planes.grn);
+    free(planes.blu);
+}
+
+
+
+static unsigned char
+compact(word const input) {
+
+    return (input >> 8) & 0xff;
+}
+
+
+
+static void
+do_blits(struct rgbPlanes * const planesP) {
+
+    struct blit_info* bi;
+    int srcwidth, dstwidth, srcheight, dstheight;
+    double  scale, scalelow, scalehigh;
+    double  xscale = 1.0;
+    double  yscale = 1.0;
+    double  lowxscale, highxscale, lowyscale, highyscale;
+    int     xscalecalc = 0, yscalecalc = 0;
+
+    if (!blit_list) return;
+
+    fullres = 0;
+
+    for (bi = blit_list; bi; bi = bi->next) {
+        srcwidth = rectwidth(&bi->srcRect);
+        dstwidth = rectwidth(&bi->dstRect);
+        srcheight = rectheight(&bi->srcRect);
+        dstheight = rectheight(&bi->dstRect);
+        if (srcwidth > dstwidth) {
+            scalelow  = (double)(srcwidth      ) / (double)dstwidth;
+            scalehigh = (double)(srcwidth + 1.0) / (double)dstwidth;
+            switch (xscalecalc) {
+            case 0:
+                lowxscale = scalelow;
+                highxscale = scalehigh;
+                xscalecalc = 1;
+                break;
+            case 1:
+                if (scalelow < highxscale && scalehigh > lowxscale) {
+                    if (scalelow > lowxscale) lowxscale = scalelow;
+                    if (scalehigh < highxscale) highxscale = scalehigh;
+                }
+                else {
+                    scale = (lowxscale + highxscale) / 2.0;
+                    xscale = (double)srcwidth / (double)dstwidth;
+                    if (scale > xscale) xscale = scale;
+                    xscalecalc = 2;
+                }
+                break;
+            case 2:
+                scale = (double)srcwidth / (double)dstwidth;
+                if (scale > xscale) xscale = scale;
+                break;
+            }
+        }
+
+        if (srcheight > dstheight) {
+            scalelow =  (double)(srcheight      ) / (double)dstheight;
+            scalehigh = (double)(srcheight + 1.0) / (double)dstheight;
+            switch (yscalecalc) {
+            case 0:
+                lowyscale = scalelow;
+                highyscale = scalehigh;
+                yscalecalc = 1;
+                break;
+            case 1:
+                if (scalelow < highyscale && scalehigh > lowyscale) {
+                    if (scalelow > lowyscale) lowyscale = scalelow;
+                    if (scalehigh < highyscale) highyscale = scalehigh;
+                }
+                else {
+                    scale = (lowyscale + highyscale) / 2.0;
+                    yscale = (double)srcheight / (double)dstheight;
+                    if (scale > yscale) yscale = scale;
+                    yscalecalc = 2;
+                }
+                break;
+            case 2:
+                scale = (double)srcheight / (double)dstheight;
+                if (scale > yscale) yscale = scale;
+                break;
+            }
+        }
+    }
+
+    if (xscalecalc == 1) {
+        if (1.0 >= lowxscale && 1.0 <= highxscale)
+            xscale = 1.0;
+        else
+            xscale = lowxscale;
+    }
+    if (yscalecalc == 1) {
+        if (1.0 >= lowyscale && 1.0 <= lowyscale)
+            yscale = 1.0;
+        else
+            yscale = lowyscale;
+    }
+
+    if (xscale != 1.0 || yscale != 1.0) {
+        for (bi = blit_list; bi; bi = bi->next)
+            rectscale(&bi->dstRect, xscale, yscale);
+
+        pm_message("Scaling output by %f in X and %f in Y",
+                   xscale, yscale);
+        rectscale(&picFrame, xscale, yscale);
+    }
+
+    allocPlanes(planesP);
+
+    for (bi = blit_list; bi; bi = bi->next) {
+        blit(bi->srcRect, bi->srcBounds, bi->srcplane,
+             bi->pixSize,
+             bi->dstRect, picFrame, rowlen,
+             bi->color_map,
+             bi->mode);
+    }
+}
+
+
+
+static void
+outputPpm(struct rgbPlanes const planes) {
+
+    unsigned int width;
+    unsigned int height;
+    pixel * pixelrow;
+    unsigned int row;
+    unsigned int srcCursor;
+
+    stage = "writing PPM";
+
+    assert(picFrame.right  > picFrame.left);
+    assert(picFrame.bottom > picFrame.top);
+
+    width  = picFrame.right  - picFrame.left;
+    height = picFrame.bottom - picFrame.top;
+
+    ppm_writeppminit(stdout, width, height, PPM_MAXMAXVAL, 0);
+    pixelrow = ppm_allocrow(width);
+    srcCursor = 0;
+    for (row = 0; row < height; ++row) {
+        unsigned int col;
+        for (col = 0; col < width; ++col) {
+            PPM_ASSIGN(pixelrow[col],
+                       compact(planes.red[srcCursor]),
+                       compact(planes.grn[srcCursor]),
+                       compact(planes.blu[srcCursor])
+                );
+            ++srcCursor;
+        }
+        ppm_writeppmrow(stdout, pixelrow, width, PPM_MAXMAXVAL, 0);
+    }
+    pm_close(stdout);
+}
+
+
+
+/*
+ * All data in version 2 is 2-byte word aligned.  Odd size data
+ * is padded with a null.
+ */
+static word
+get_op(int const version) {
+    if ((align & 1) && version == 2) {
+        stage = "aligning for opcode";
+        read_byte();
+    }
+
+    stage = "reading opcode";
+
+    if (version == 1)
+        return read_byte();
+    else
+        return read_word();
+}
+
+
+
+static void
+Clip(int const version) {
+    word len;
+
+    len = read_word();
+
+    if (len == 0x000a) {    /* null rgn */
+        read_rect(&clip_rect);
+        /* XXX should clip this by picFrame */
+        if (verbose)
+            dumpRect("clipping to", clip_rect);
+    }
+    else
+        skip(len - 2);
+}
+
+
+
+static void
+OpColor(int const version) {
+    op_color.red = read_word();
+    op_color.grn = read_word();
+    op_color.blu = read_word();
+}
+
+
+
+static void
+read_pixmap(struct pixMap * const p) {
+
+    stage = "getting pixMap header";
+
+    read_rect(&p->Bounds);
+    p->version = read_word();
+    p->packType = read_word();
+    p->packSize = read_long();
+    p->hRes = read_long();
+    p->vRes = read_long();
+    p->pixelType = read_word();
+    p->pixelSize = read_word();
+    p->cmpCount = read_word();
+    p->cmpSize = read_word();
+    p->planeBytes = read_long();
+    p->pmTable = read_long();
+    p->pmReserved = read_long();
+
+    if (verbose) {
+        pm_message("pixelType: %d", p->pixelType);
+        pm_message("pixelSize: %d", p->pixelSize);
+        pm_message("cmpCount:  %d", p->cmpCount);
+        pm_message("cmpSize:   %d", p->cmpSize);
+    }
+
+    if (p->pixelType != 0)
+        pm_error("sorry, I do only chunky format.  "
+                 "This image has pixel type %hu", p->pixelType);
+    if (p->cmpCount != 1)
+        pm_error("sorry, cmpCount != 1");
+    if (p->pixelSize != p->cmpSize)
+        pm_error("oops, pixelSize != cmpSize");
+}
+
+
+
+static struct RGBColor*
+read_color_table(void) {
+    longword ctSeed;
+    word ctFlags;
+    word ctSize;
+    word val;
+    int i;
+    struct RGBColor* color_table;
+
+    stage = "getting color table info";
+
+    ctSeed = read_long();
+    ctFlags = read_word();
+    ctSize = read_word();
+
+    if (verbose) {
+        pm_message("ctSeed:  %ld", ctSeed);
+        pm_message("ctFlags: %d", ctFlags);
+        pm_message("ctSize:  %d", ctSize);
+    }
+
+    stage = "reading color table";
+
+    MALLOCARRAY(color_table, ctSize + 1);
+    if (color_table == NULL)
+        pm_error("no memory for color table");
+
+    for (i = 0; i <= ctSize; i++) {
+        val = read_word();
+        /* The indices in a device color table are bogus and usually == 0.
+         * so I assume we allocate up the list of colors in order.
+         */
+        if (ctFlags & 0x8000)
+            val = i;
+        if (val > ctSize)
+            pm_error("pixel value greater than color table size");
+        color_table[val].red = read_word();
+        color_table[val].grn = read_word();
+        color_table[val].blu = read_word();
+
+        if (verbose > 1)
+            pm_message("%d: [%d,%d,%d]", val,
+                color_table[val].red,
+                color_table[val].grn,
+                color_table[val].blu);
+    }
+
+    return color_table;
+}
+
+
+
+static void
+readBytes(FILE *          const ifP,
+          unsigned int    const n, 
+          unsigned char * const buf) {
+
+    align += n;
+
+    if (fread(buf, n, 1, ifP) != 1)
+        pm_error("EOF / read error while %s", stage);
+}
+
+
+
+static void
+copyFullBytes(unsigned char * const packed,
+              unsigned char * const expanded,
+              unsigned int    const packedLen) {
+
+    unsigned int i;
+
+    for (i = 0; i < packedLen; ++i)
+        expanded[i] = packed[i];
+}
+
+
+
+static void
+expand4Bits(unsigned char * const packed,
+            unsigned char * const expanded,
+            unsigned int    const packedLen) {
+
+    unsigned int i;
+    unsigned char * dst;
+
+    dst = &expanded[0];
+    
+    for (i = 0; i < packedLen; ++i) {
+        *dst++ = (packed[i] >> 4) & 0x0f;
+        *dst++ = (packed[i] >> 0) & 0x0f;
+    }
+}
+
+
+
+static void
+expand2Bits(unsigned char * const packed,
+            unsigned char * const expanded,
+            unsigned int    const packedLen) {
+        
+    unsigned int i;
+    unsigned char * dst;
+
+    dst = &expanded[0];
+    
+    for (i = 0; i < packedLen; ++i) {
+        *dst++ = (packed[i] >> 6) & 0x03;
+        *dst++ = (packed[i] >> 4) & 0x03;
+        *dst++ = (packed[i] >> 2) & 0x03;
+        *dst++ = (packed[i] >> 0) & 0x03;
+    }
+}
+
+
+
+static void
+expand1Bit(unsigned char * const packed,
+           unsigned char * const expanded,
+           unsigned int    const packedLen) {
+
+    unsigned int i;
+    unsigned char * dst;
+
+    dst = &expanded[0];
+    
+    for (i = 0; i < packedLen; ++i) {
+        *dst++ = (packed[i] >> 7) & 0x01;
+        *dst++ = (packed[i] >> 6) & 0x01;
+        *dst++ = (packed[i] >> 5) & 0x01;
+        *dst++ = (packed[i] >> 4) & 0x01;
+        *dst++ = (packed[i] >> 3) & 0x01;
+        *dst++ = (packed[i] >> 2) & 0x01;
+        *dst++ = (packed[i] >> 1) & 0x01;
+        *dst++ = (packed[i] >> 0) & 0x01;
+    }
+}
+
+
+
+static void
+unpackBuf(unsigned char *  const packed, 
+          unsigned int     const packedLen,
+          int              const bitsPerPixel,
+          unsigned char ** const expandedP,
+          unsigned int *   const expandedLenP) {
+/*----------------------------------------------------------------------------
+   Expand the bit string 'packed', which is 'packedLen' bytes long
+   into an array of bytes, with one byte per pixel.  Each 'bitsPerPixel'
+   of 'packed' is a pixel.
+
+   So e.g. if it's 4 bits per pixel and 'packed' is 0xabcdef01, we
+   return 0x0a0b0c0d0e0f0001 as *expandedP.
+
+   As a special case, if there are multiple bytes per pixel, we just
+   return the exact same bit string.
+
+   *expandedP is static storage.
+
+   'packedLen' must not be greater than 256.
+-----------------------------------------------------------------------------*/
+    static unsigned char expanded[256 * 8];
+    unsigned char * src;
+    unsigned char * dst;
+
+    assert(packedLen <= 256);
+
+    src = &packed[0];
+    dst = &expanded[0];
+
+    switch (bitsPerPixel) {
+    case 8:
+    case 16:
+    case 32:
+        assert(packedLen <= sizeof(expanded));
+        copyFullBytes(packed, expanded, packedLen);
+        *expandedLenP = packedLen;
+        break;
+    case 4:
+        assert(packedLen * 2 <= sizeof(expanded));
+        expand4Bits(packed, expanded, packedLen);
+        *expandedLenP = packedLen * 2;
+        break;
+    case 2:
+        assert(packedLen * 4 <= sizeof(expanded));
+        expand2Bits(packed, expanded, packedLen);
+        *expandedLenP = packedLen * 4;
+        break;
+    case 1:
+        assert(packedLen * 8 <= sizeof(expanded));
+        expand1Bit(packed, expanded, packedLen);
+        *expandedLenP = packedLen * 8;
+        break;
+    default:
+        pm_error("INTERNAL ERROR: bitsPerPixel = %u in unpackBuf",
+                 bitsPerPixel);
+    }
+    *expandedP = expanded;
+}
+
+
+
+static void
+unpackUncompressedBits(FILE *          const ifP,
+                       struct raster   const raster,
+                       unsigned int    const rowBytes,
+                       unsigned int    const bitsPerPixel) {
+/*----------------------------------------------------------------------------
+   Read the raster from the file into 'raster'.  The data in the file is not
+   compressed (but may still be packed multiple pixels per byte).
+
+   In PICT terminology, it appears that compression is called
+   "packing" and I don't know what packing is called.  But we don't
+   use that confusing terminology in this program, except when talking
+   to the user.
+-----------------------------------------------------------------------------*/
+    unsigned int rowOfRect;
+    unsigned char * linebuf;
+
+    MALLOCARRAY(linebuf, rowBytes + 100);
+    if (linebuf == NULL)
+        pm_error("can't allocate memory for line buffer");
+
+    for (rowOfRect = 0; rowOfRect < raster.rowCount; ++rowOfRect) { 
+        unsigned char * bytePixels;
+        unsigned int expandedByteCount;
+        unsigned char * rasterRow;
+        unsigned int i;
+
+        rasterRow = raster.bytes + rowOfRect * raster.rowSize;
+
+        readBytes(ifP, rowBytes, linebuf);
+
+        unpackBuf(linebuf, rowBytes, bitsPerPixel,
+                  &bytePixels, &expandedByteCount);
+
+        assert(expandedByteCount <= raster.rowSize);
+
+        for (i = 0; i < expandedByteCount; ++i)
+            rasterRow[i] = bytePixels[i];
+    }
+    free(linebuf);
+}
+
+
+
+static void
+expandRun(unsigned char * const block,
+          unsigned int    const blockLimit,
+          unsigned int    const bitsPerPixel,
+          unsigned char * const expanded,
+          unsigned int    const expandedSize,
+          unsigned int *  const blockLengthP,
+          unsigned int *  const expandedByteCountP) {
+/*----------------------------------------------------------------------------
+   Expand a run (the data says, "repeat the next pixel N times").
+
+   Return the expanded run as expanded[], which has room for 'expandedSize'
+   elements.  Return as *expandedByteCountP the number of elements actually
+   returned.
+-----------------------------------------------------------------------------*/
+    unsigned int const pkpixsize = bitsPerPixel == 16 ? 2 : 1;
+
+    if (1 + pkpixsize > blockLimit)
+        pm_error("PICT run block runs off the end of its line.  "
+                 "Invalid PICT file.");
+    else {
+        unsigned int const runLength = (block[0] ^ 0xff) + 2;
+        
+        unsigned int i;
+        unsigned char * bytePixels;  /* Points to static storage */
+        unsigned int expandedByteCount;
+        unsigned int outputCursor;
+        
+        assert(block[0] & 0x80);  /* It's a run */
+
+        if (verbose > 1)
+            pm_message("Block: run of %u pixels or plane samples", runLength);
+        
+        unpackBuf(&block[1], pkpixsize, bitsPerPixel,
+                  &bytePixels, &expandedByteCount);
+        
+        if (expandedByteCount > expandedSize)
+            pm_error("Invalid PICT image.  It contains a row with more pixels "
+                     "than the width of the image");
+        
+        outputCursor = 0;
+        for (i = 0; i < runLength; ++i) {
+            unsigned int j;
+            for (j = 0; j < expandedByteCount; ++j)
+                expanded[outputCursor++] = bytePixels[j];
+        }
+        *blockLengthP = 1 + pkpixsize;
+        *expandedByteCountP = expandedByteCount * runLength;
+    }
+}
+
+
+
+static void
+copyPixelGroup(unsigned char * const block,
+               unsigned int    const blockLimit,
+               unsigned int    const bitsPerPixel,
+               unsigned char * const dest,
+               unsigned int    const destSize,
+               unsigned int *  const blockLengthP,
+               unsigned int *  const rasterBytesGeneratedP) {
+/*----------------------------------------------------------------------------
+   Copy a group of pixels (the data says, "take the following N pixels").
+-----------------------------------------------------------------------------*/
+    unsigned int const pkpixsize = bitsPerPixel == 16 ? 2 : 1;
+    unsigned int const groupLen = block[0] + 1;
+    unsigned int const blockLength = 1 + groupLen * pkpixsize;
+
+    if (blockLength > blockLimit)
+        pm_error("PICT non-run block (length %u) "
+                 "runs off the end of its line.  "
+                 "Invalid PICT file.", blockLength);
+    else {
+        unsigned int i;
+        unsigned char * bytePixels;  /* Points to static storage */
+        unsigned int bytePixelLen;
+        
+        assert(blockLimit >= 1);  /* block[0] exists */
+        assert((block[0] & 0x80) == 0);  /* It's not a run */
+        
+        if (verbose > 1)
+            pm_message("Block: %u individual pixels or plane samples",
+                       groupLen);
+        
+        unpackBuf(&block[1], groupLen * pkpixsize, bitsPerPixel,
+                  &bytePixels, &bytePixelLen);
+        
+        if (bytePixelLen > destSize)
+            pm_error("Invalid PICT image.  It contains a row with more pixels "
+                     "than the width of the image");
+        
+        for (i = 0; i < bytePixelLen; ++i)
+            dest[i] = bytePixels[i];
+        
+        *blockLengthP = blockLength;
+        *rasterBytesGeneratedP = bytePixelLen;
+    }
+}
+
+
+
+static void
+interpretOneRasterBlock(unsigned char * const block,
+                        unsigned int    const blockLimit,
+                        unsigned int    const bitsPerPixel,
+                        unsigned char * const raster,
+                        unsigned int    const rasterSize,
+                        unsigned int *  const blockLengthP,
+                        unsigned int *  const rasterBytesGeneratedP) {
+/*----------------------------------------------------------------------------
+   Return the pixels described by the PICT block block[], assuming
+   the PICT format is 'bitsPerPixel' bits per pixel.
+
+   Return them as raster[], which has 'rasterSize' elements of space.
+   Return as *rasterBytesGeneratedP the number of elements actually
+   returned.
+
+   block[] self-describes its length, and we return that as
+   *blockLengthP.  But there are at most 'blockLimit' bytes there, so
+   if block[] claims to be longer than that, some of the block is
+   missing (i.e. corrupt PICT).
+-----------------------------------------------------------------------------*/
+    if (block[0] & 0x80)
+        expandRun(block, blockLimit, bitsPerPixel, raster, rasterSize,
+                  blockLengthP, rasterBytesGeneratedP);
+    else
+        copyPixelGroup(block, blockLimit, bitsPerPixel, raster, rasterSize,
+                       blockLengthP, rasterBytesGeneratedP);
+
+    assert(*rasterBytesGeneratedP <= rasterSize);
+}
+
+
+
+static unsigned int const maxPixelBytesPerBlock = 1024;
+
+
+
+static void
+unpackCompressedBits(FILE *          const ifP,
+                     struct raster   const raster,
+                     unsigned int    const rowBytes,
+                     unsigned int    const bitsPerPixel) {
+/*----------------------------------------------------------------------------
+   Read the raster of on file *ifP and place it in 'raster'.
+
+   The data in the file is compressed with run length encoding and
+   possibly packed multiple pixels per byte as well.
+
+   In PICT terminology, it appears that compression is called
+   "packing" and I don't know what packing is called.  But we don't
+   use that confusing terminology in this program, except when talking
+   to the user.
+
+   *boundsP describes the rectangle.
+-----------------------------------------------------------------------------*/
+    unsigned int const llsize = rowBytes > 200 ? 2 : 1;
+    unsigned int rowOfRect;
+    unsigned char * linebuf;
+    unsigned int linebufSize;
+
+    linebufSize = rowBytes;
+    MALLOCARRAY(linebuf, linebufSize);
+    if (linebuf == NULL)
+        pm_error("can't allocate memory for line buffer");
+
+    for (rowOfRect = 0; rowOfRect < raster.rowCount; ++rowOfRect) {
+        unsigned char * const rowRaster =
+            &raster.bytes[rowOfRect * raster.rowSize];
+        unsigned int linelen;
+        unsigned int lineCursor;
+        unsigned int rasterCursor;
+
+        if (llsize == 2)
+            linelen = read_word();
+        else
+            linelen = read_byte();
+
+        if (verbose > 1)
+            pm_message("linelen: %u", linelen);
+
+        if (linelen > linebufSize) {
+            linebufSize = linelen;
+            REALLOCARRAY(linebuf, linebufSize);
+            if (linebuf == NULL)
+                pm_error("can't allocate memory for line buffer");
+        }
+        readBytes(ifP, linelen, linebuf);
+
+        for (lineCursor = 0, rasterCursor = 0; lineCursor < linelen; ) {
+            unsigned int blockLength, rasterBytesGenerated;
+
+            assert(lineCursor <= linelen);
+            
+            interpretOneRasterBlock(
+                &linebuf[lineCursor], linelen - lineCursor,
+                bitsPerPixel,
+                &rowRaster[rasterCursor], raster.rowSize - rasterCursor,
+                &blockLength, &rasterBytesGenerated);
+
+            lineCursor += blockLength;
+            rasterCursor += rasterBytesGenerated;
+            assert(rasterCursor <= raster.rowSize);
+        }
+        if (verbose > 1)
+            pm_message("row %u: got %u", rowOfRect, rasterCursor);
+    }
+    free(linebuf);
+}
+
+
+
+static void
+unpackbits(FILE *           const ifP,
+           struct Rect *    const boundsP,
+           word             const rowBytesArg, 
+           int              const bitsPerPixel,
+           struct raster *  const rasterP) {
+
+    unsigned int const rectHeight = boundsP->bottom - boundsP->top;
+    unsigned int const rectWidth  = boundsP->right  - boundsP->left;
+    
+    struct raster raster;
+    unsigned int rowBytes;
+
+    stage = "unpacking packbits";
+
+    if (verbose)
+        pm_message("rowBytes = %u, bitsPerPixel = %d",
+                   rowBytesArg, bitsPerPixel);
+        
+    allocateRaster(&raster, rectWidth, rectHeight, bitsPerPixel);
+
+    rowBytes = rowBytesArg ? rowBytesArg : raster.rowSize;
+
+    if (verbose)
+        pm_message("raster.rowSize: %u bytes; file row = %u bytes",
+                   raster.rowSize, rowBytes);
+
+    if (rowBytes < 8)
+        unpackUncompressedBits(ifP, raster, rowBytes, bitsPerPixel);
+    else
+        unpackCompressedBits(ifP, raster, rowBytes, bitsPerPixel);
+
+    *rasterP = raster;
+}
+
+
+
+static void
+interpretRowBytesWord(word           const rowBytesWord,
+                      bool *         const pixMapP,
+                      unsigned int * const rowBytesP) {
+
+    *pixMapP   = !!(rowBytesWord & 0x8000);
+    *rowBytesP = rowBytesWord & 0x7fff;
+
+    if (verbose)
+        pm_message("PCT says %s, %u bytes per row",
+                   *pixMapP ? "pixmap" : "bitmap", *rowBytesP);
+}
+
+
+
+/* this just skips over a version 2 pattern.  Probably will return
+ * a pattern in the fabled complete version.
+ */
+static void
+read_pattern(void) {
+
+    word PatType;
+
+    stage = "Reading a pattern";
+
+    PatType = read_word();
+
+    switch (PatType) {
+    case 2:
+        skip(8); /* old pattern data */
+        skip(5); /* RGB for pattern */
+        break;
+    case 1: {
+        word rowBytesWord;
+        bool pixMap;
+        unsigned int rowBytes;
+        struct pixMap p;
+        struct raster raster;
+        struct RGBColor * ct;
+
+        skip(8); /* old pattern data */
+        rowBytesWord = read_word();
+        interpretRowBytesWord(rowBytesWord, &pixMap, &rowBytes);
+        read_pixmap(&p);
+        ct = read_color_table();
+        unpackbits(ifp, &p.Bounds, rowBytes, p.pixelSize, &raster);
+        freeRaster(raster);
+        free(ct);
+    } break;
+    default:
+        pm_error("unknown pattern type in read_pattern");
+    }
+}
+
+
+
+/* these 3 do nothing but skip over their data! */
+
+static void
+BkPixPat(int const version) {
+    read_pattern();
+}
+
+static void
+PnPixPat(int const version) {
+    read_pattern();
+}
+
+static void
+FillPixPat(int const version) {
+    read_pattern();
+}
+
+static void
+read_8x8_pattern(struct Pattern * const pat) {
+    unsigned char buf[8];
+    unsigned char * exp;
+    unsigned int len;
+    unsigned int expandedLen;
+    unsigned int i;
+
+    len = 8;  /* initial value */
+    readBytes(ifp, len, buf);
+    if (verbose) {
+        pm_message("pattern: %02x%02x%02x%02x",
+                   buf[0], buf[1], buf[2], buf[3]);
+        pm_message("pattern: %02x%02x%02x%02x",
+            buf[4], buf[5], buf[6], buf[7]);
+    }
+    unpackBuf(buf, len, 1, &exp, &expandedLen);
+    for (i = 0; i < 64; ++i)
+        pat->pix[i] = exp[i];
+}
+
+
+
+static void 
+BkPat(int const version) {
+    read_8x8_pattern(&bkpat);
+}
+
+
+
+static void 
+PnPat(int const version) {
+    read_8x8_pattern(&pen_pat);
+}
+
+
+
+static void 
+FillPat(int const version) {
+    read_8x8_pattern(&fillpat);
+}
+
+
+
+static void 
+PnSize(int const version) {
+    pen_height = read_word();
+    pen_width = read_word();
+    if (verbose)
+        pm_message("pen size %d x %d", pen_width, pen_height);
+}
+
+
+
+static void 
+PnMode(int const version) {
+
+    pen_mode = read_word();
+
+    if (pen_mode >= 8 && pen_mode < 15)
+        pen_mode -= 8;
+    if (verbose)
+        pm_message("pen transfer mode = %s",
+            const_name(transfer_name, pen_mode));
+    
+    pen_trf = transfer(pen_mode);
+}
+
+
+
+static void 
+read_rgb(struct RGBColor * const rgb) {
+    rgb->red = read_word();
+    rgb->grn = read_word();
+    rgb->blu = read_word();
+}
+
+
+
+static void 
+RGBFgCol(int const v) {
+    read_rgb(&foreground);
+    if (verbose)
+        pm_message("foreground now [%d,%d,%d]", 
+            foreground.red, foreground.grn, foreground.blu);
+}
+
+
+
+static void 
+RGBBkCol(int const v) {
+    read_rgb(&background);
+    if (verbose)
+        pm_message("background now [%d,%d,%d]", 
+            background.red, background.grn, background.blu);
+}
+
+
+
+#define PIXEL_INDEX(x,y) ((y) - picFrame.top) * rowlen + (x) - picFrame.left
+
+static void 
+draw_pixel(int                const x, 
+           int                const y, 
+           struct RGBColor * const clr, 
+           transfer_func            trf) {
+
+    int i;
+    struct RGBColor dst;
+
+    if (x < clip_rect.left || x >= clip_rect.right ||
+        y < clip_rect.top || y >= clip_rect.bottom)
+    {
+        return;
+    }
+
+    i = PIXEL_INDEX(x, y);
+    dst.red = red[i];
+    dst.grn = green[i];
+    dst.blu = blue[i];
+    (*trf)(clr, &dst);
+    red[i] = dst.red;
+    green[i] = dst.grn;
+    blue[i] = dst.blu;
+}
+
+
+
+static void 
+draw_pen_rect(struct Rect * const r) {
+    int const rowadd = rowlen - (r->right - r->left);
+
+    int i;
+    int x, y;
+    struct RGBColor dst;
+
+    i = PIXEL_INDEX(r->left, r->top);  /* initial value */
+    
+    for (y = r->top; y < r->bottom; y++) {
+        for (x = r->left; x < r->right; x++) {
+            dst.red = red[i];
+            dst.grn = green[i];
+            dst.blu = blue[i];
+            if (pen_pat.pix[(x & 7) + (y & 7) * 8])
+                (*pen_trf)(&black, &dst);
+            else
+                (*pen_trf)(&white, &dst);
+            red[i] = dst.red;
+            green[i] = dst.grn;
+            blue[i] = dst.blu;
+
+            i++;
+        }
+        i += rowadd;
+    }
+}
+
+
+
+static void 
+draw_pen(int const x, 
+         int const y) {
+    struct Rect penrect;
+
+    penrect.left = x;
+    penrect.right = x + pen_width;
+    penrect.top = y;
+    penrect.bottom = y + pen_height;
+
+    rectinter(penrect, clip_rect, &penrect);
+
+    draw_pen_rect(&penrect);
+}
+
+/*
+ * Digital Line Drawing
+ * by Paul Heckbert
+ * from "Graphics Gems", Academic Press, 1990
+ */
+
+/*
+ * digline: draw digital line from (x1,y1) to (x2,y2),
+ * calling a user-supplied procedure at each pixel.
+ * Does no clipping.  Uses Bresenham's algorithm.
+ *
+ * Paul Heckbert    3 Sep 85
+ */
+static void 
+scan_line(short const x1, 
+          short const y1, 
+          short const x2, 
+          short const y2) {
+    int d, x, y, ax, ay, sx, sy, dx, dy;
+
+    if (!(pen_width == 0 && pen_height == 0)) {
+
+        dx = x2-x1;  ax = ABS(dx)<<1;  sx = SGN(dx);
+        dy = y2-y1;  ay = ABS(dy)<<1;  sy = SGN(dy);
+
+        x = x1;
+        y = y1;
+        if (ax>ay) {        /* x dominant */
+            d = ay-(ax>>1);
+            for (;;) {
+                draw_pen(x, y);
+                if (x==x2) return;
+                if ((x > rowlen) && (sx > 0)) return;
+                if (d>=0) {
+                    y += sy;
+                    d -= ax;
+                }
+                x += sx;
+                d += ay;
+            }
+        }
+        else {          /* y dominant */
+            d = ax-(ay>>1);
+            for (;;) {
+                draw_pen(x, y);
+                if (y==y2) return;
+                if ((y > collen) && (sy > 0)) return;
+                if (d>=0) {
+                    x += sx;
+                    d -= ay;
+                }
+                y += sy;
+                d += ax;
+            }
+        }
+    }
+}
+
+
+
+static void 
+Line(int const v) {
+  struct Point p1;
+  read_point(&p1);
+  read_point(&current);
+  if (verbose)
+    pm_message("(%d,%d) to (%d, %d)",
+           p1.x,p1.y,current.x,current.y);
+  scan_line(p1.x,p1.y,current.x,current.y);
+}
+
+
+
+static void 
+LineFrom(int const v) {
+  struct Point p1;
+  read_point(&p1);
+  if (verbose)
+    pm_message("(%d,%d) to (%d, %d)",
+           current.x,current.y,p1.x,p1.y);
+
+  if (!fullres)
+      scan_line(current.x,current.y,p1.x,p1.y);
+
+  current.x = p1.x;
+  current.y = p1.y;
+}
+
+
+
+static void 
+ShortLine(int const v) {
+  struct Point p1;
+  read_point(&p1);
+  read_short_point(&current);
+  if (verbose)
+    pm_message("(%d,%d) delta (%d, %d)",
+           p1.x,p1.y,current.x,current.y);
+  current.x += p1.x;
+  current.y += p1.y;
+
+  if (!fullres)
+      scan_line(p1.x,p1.y,current.x,current.y);
+}
+
+
+
+static void 
+ShortLineFrom(int const v) {
+  struct Point p1;
+  read_short_point(&p1);
+  if (verbose)
+    pm_message("(%d,%d) delta (%d, %d)",
+               current.x,current.y,p1.x,p1.y);
+  p1.x += current.x;
+  p1.y += current.y;
+  if (!fullres)
+      scan_line(current.x,current.y,p1.x,p1.y);
+  current.x = p1.x;
+  current.y = p1.y;
+}
+
+static void 
+do_paintRect(struct Rect const prect) {
+    struct Rect rect;
+  
+    if (fullres)
+        return;
+
+    if (verbose)
+        dumpRect("painting", prect);
+
+    rectinter(clip_rect, prect, &rect);
+
+    draw_pen_rect(&rect);
+}
+
+
+
+static void 
+paintRect(int const v) {
+    read_rect(&cur_rect);
+    do_paintRect(cur_rect);
+}
+
+
+
+static void 
+paintSameRect(int const v) {
+    do_paintRect(cur_rect);
+}
+
+
+
+static void 
+do_frameRect(struct Rect const rect) {
+    int x, y;
+
+    if (fullres)
+        return;
+  
+    if (verbose)
+        dumpRect("framing", rect);
+
+    if (pen_width == 0 || pen_height == 0)
+        return;
+
+    for (x = rect.left; x <= rect.right - pen_width; x += pen_width) {
+        draw_pen(x, rect.top);
+        draw_pen(x, rect.bottom - pen_height);
+    }
+
+    for (y = rect.top; y <= rect.bottom - pen_height ; y += pen_height) {
+        draw_pen(rect.left, y);
+        draw_pen(rect.right - pen_width, y);
+    }
+}
+
+
+
+static void 
+frameRect(int const v) {
+    read_rect(&cur_rect);
+    do_frameRect(cur_rect);
+}
+
+
+
+static void 
+frameSameRect(int const v) {
+    do_frameRect(cur_rect);
+}
+
+
+
+/* a stupid shell sort - I'm so embarassed  */
+
+static void 
+poly_sort(int const sort_index, struct Point points[]) {
+  int d, i, j, temp;
+
+  /* initialize and set up sort interval */
+  d = 4;
+  while (d<=sort_index) d <<= 1;
+  d -= 1;
+
+  while (d > 1) {
+    d >>= 1;
+    for (j = 0; j <= (sort_index-d); j++) {
+      for(i = j; i >= 0; i -= d) {
+    if ((points[i+d].y < points[i].y) ||
+        ((points[i+d].y == points[i].y) &&
+         (points[i+d].x <= points[i].x))) {
+      /* swap x1,y1 with x2,y2 */
+      temp = points[i].y;
+      points[i].y = points[i+d].y;
+      points[i+d].y = temp;
+      temp = points[i].x;
+      points[i].x = points[i+d].x;
+      points[i+d].x = temp;
+    }
+      }
+    }
+  }
+}
+
+
+
+/* Watch out for the lack of error checking in the next two functions ... */
+
+static void 
+scan_poly(int          const np, 
+          struct Point       pts[]) {
+  int dx,dy,dxabs,dyabs,i,scan_index,j,k,px,py;
+  int sdx,sdy,x,y,toggle,old_sdy,sy0;
+
+  /* This array needs to be at least as large as the largest dimension of
+     the bounding box of the poly (but I don't check for overflows ...) */
+  struct Point coord[5000];
+
+  scan_index = 0;
+
+  /* close polygon */
+  px = pts[np].x = pts[0].x;
+  py = pts[np].y = pts[0].y;
+
+  /*  This section draws the polygon and stores all the line points
+   *  in an array. This doesn't work for concave or non-simple polys.
+   */
+  /* are y levels same for first and second points? */
+  if (pts[1].y == pts[0].y) {
+    coord[scan_index].x = px;
+    coord[scan_index].y = py;
+    scan_index++;
+  }
+
+#define sign(x) ((x) > 0 ? 1 : ((x)==0 ? 0:(-1)) )   
+
+  old_sdy = sy0 = sign(pts[1].y - pts[0].y);
+  for (j=0; j<np; j++) {
+    /* x,y difference between consecutive points and their signs  */
+    dx = pts[j+1].x - pts[j].x;
+    dy = pts[j+1].y - pts[j].y;
+    sdx = SGN(dx);
+    sdy = SGN(dy);
+    dxabs = abs(dx);
+    dyabs = abs(dy);
+    x = y = 0;
+
+    if (dxabs >= dyabs)
+      {
+    for (k=0; k < dxabs; k++) {
+      y += dyabs;
+      if (y >= dxabs) {
+        y -= dxabs;
+        py += sdy;
+        if (old_sdy != sdy) {
+          old_sdy = sdy;
+          scan_index--;
+        }
+        coord[scan_index].x = px+sdx;
+        coord[scan_index].y = py;
+        scan_index++;
+      }
+      px += sdx;
+      draw_pen(px, py);
+    }
+      }
+    else
+      {
+    for (k=0; k < dyabs; k++) {
+      x += dxabs;
+      if (x >= dyabs) {
+        x -= dyabs;
+        px += sdx;
+      }
+      py += sdy;
+      if (old_sdy != sdy) {
+        old_sdy = sdy;
+        if (sdy != 0) scan_index--;
+      }
+      draw_pen(px,py);
+      coord[scan_index].x = px;
+      coord[scan_index].y = py;
+      scan_index++;
+    }
+      }
+  }
+
+  /* after polygon has been drawn now fill it */
+
+  scan_index--;
+  if (sy0 + sdy == 0) scan_index--;
+
+  poly_sort(scan_index, coord);
+  
+  toggle = 0;
+  for (i = 0; i < scan_index; i++) {
+    if ((coord[i].y == coord[i+1].y) && (toggle == 0))
+      {
+    for (j = coord[i].x; j <= coord[i+1].x; j++)
+      draw_pen(j, coord[i].y);
+    toggle = 1;
+      }
+    else
+      toggle = 0;
+  }
+}
+  
+
+  
+static void 
+paintPoly(int const v) {
+  struct Rect bb;
+  struct Point pts[100];
+  int i, np = (read_word() - 10) >> 2;
+
+  read_rect(&bb);
+  for (i=0; i<np; ++i)
+    read_point(&pts[i]);
+
+  /* scan convert poly ... */
+  if (!fullres)
+      scan_poly(np, pts);
+}
+
+
+
+static void 
+PnLocHFrac(int const version) {
+    word frac = read_word();
+
+    if (verbose)
+        pm_message("PnLocHFrac = %d", frac);
+}
+
+
+
+static void 
+TxMode(int const version) {
+    text_mode = read_word();
+
+    if (text_mode >= 8 && text_mode < 15)
+        text_mode -= 8;
+    if (verbose)
+        pm_message("text transfer mode = %s",
+            const_name(transfer_name, text_mode));
+    
+    /* ignore the text mask bit 'cause we don't handle it yet */
+    text_trf = transfer(text_mode & ~64);
+}
+
+
+
+static void 
+TxFont(int const version) {
+    text_font = read_word();
+    if (verbose)
+        pm_message("text font %s", const_name(font_name, text_font));
+}
+
+
+
+static void 
+TxFace(int const version) {
+    text_face = read_byte();
+    if (verbose)
+        pm_message("text face %d", text_face);
+}
+
+
+
+static void 
+TxSize(int const version) {
+    text_size = read_word();
+    if (verbose)
+        pm_message("text size %d", text_size);
+}
+
+
+
+static void
+skip_text(void) {
+    skip(read_byte());
+}
+
+
+
+static int 
+abs_value(int const x) {
+    if (x < 0)
+        return -x;
+    else
+        return x;
+}
+
+
+
+static struct font* 
+get_font(int const font, 
+         int const size, 
+         int const style) {
+    int closeness, bestcloseness;
+    struct fontinfo* fi, *best;
+
+    best = 0;
+    for (fi = fontlist; fi; fi = fi->next) {
+        closeness = abs_value(fi->font - font) * 10000 +
+            abs_value(fi->size - size) * 100 +
+            abs_value(fi->style - style);
+        if (!best || closeness < bestcloseness) {
+            best = fi;
+            bestcloseness = closeness;
+        }
+    }
+
+    if (best) {
+        if (best->loaded)
+            return best->loaded;
+
+        if ((best->loaded = pbm_loadbdffont(best->filename)))
+            return best->loaded;
+    }
+
+    /* It would be better to go looking for the nth best font, really */
+    return 0;
+}
+
+
+
+/* This only does 0, 90, 180 and 270 degree rotations */
+
+static void 
+rotate(int * const x, 
+       int * const y) {
+    int tmp;
+
+    if (ps_rotation >= 315 || ps_rotation <= 45)
+        return;
+
+    *x -= ps_cent_x;
+    *y -= ps_cent_y;
+
+    if (ps_rotation > 45 && ps_rotation < 135) {
+        tmp = *x;
+        *x = *y;
+        *y = tmp;
+    }
+    else if (ps_rotation >= 135 && ps_rotation < 225) {
+        *x = -*x;
+    }
+    else if (ps_rotation >= 225 && ps_rotation < 315) {
+        tmp = *x;
+        *x = *y;
+        *y = -tmp;
+    }
+    *x += ps_cent_x;
+    *y += ps_cent_y;
+}
+
+
+
+static void
+do_ps_text(word const tx, 
+           word const ty) {
+    int len, width, i, w, h, x, y, rx, ry, o;
+    byte str[256], ch;
+    struct glyph* glyph;
+
+    current.x = tx;
+    current.y = ty;
+
+    if (!ps_cent_set) {
+        ps_cent_x += tx;
+        ps_cent_y += ty;
+        ps_cent_set = 1;
+    }
+
+    len = read_byte();
+
+    /* XXX this width calculation is not completely correct */
+    width = 0;
+    for (i = 0; i < len; i++) {
+        ch = str[i] = read_byte();
+        if (tfont->glyph[ch])
+            width += tfont->glyph[ch]->xadd;
+    }
+
+    if (verbose) {
+        str[len] = '\0';
+        pm_message("ps text: %s", str);
+    }
+
+    /* XXX The width is calculated in order to do different justifications.
+     * However, I need the width of original text to finish the job.
+     * In other words, font metrics for Quickdraw fonts
+     */
+
+    x = tx;
+
+    for (i = 0; i < len; i++) {
+        if (!(glyph = tfont->glyph[str[i]]))
+            continue;
+        
+        y = ty - glyph->height - glyph->y;
+        for (h = 0; h < glyph->height; h++) {
+            for (w = 0; w < glyph->width; w++) {
+                rx = x + glyph->x + w;
+                ry = y;
+                rotate(&rx, &ry);
+                if ((rx >= picFrame.left) && (rx < picFrame.right) &&
+                    (ry >= picFrame.top) && (ry < picFrame.bottom))
+                {
+                    o = PIXEL_INDEX(rx, ry);
+                    if (glyph->bmap[h * glyph->width + w]) {
+                        red[o] = foreground.red;
+                        green[o] = foreground.grn;
+                        blue[o] = foreground.blu;
+                    }
+                }
+            }
+            y++;
+        }
+        x += glyph->xadd;
+    }
+}
+
+
+
+static void
+do_text(word const startx, 
+        word const starty) {
+    if (fullres)
+        skip_text();
+    else {
+        if (!(tfont = get_font(text_font, text_size, text_face)))
+            tfont = pbm_defaultfont("bdf");
+
+        if (ps_text)
+            do_ps_text(startx, starty);
+        else {
+            int len;
+            word x, y;
+
+            x = startx;
+            y = starty;
+            for (len = read_byte(); len > 0; --len) {
+                struct glyph* const glyph = tfont->glyph[read_byte()];
+                if (glyph) {
+                    int dy;
+                    int h;
+                    for (h = 0, dy = y - glyph->height - glyph->y;
+                         h < glyph->height; 
+                         ++h, ++dy) {
+                        int w;
+                        for (w = 0; w < glyph->width; ++w) {
+                            struct RGBColor * const colorP = 
+                                glyph->bmap[h * glyph->width + w] ?
+                                &black : &white;
+                            draw_pixel(x + w + glyph->x, dy, colorP, text_trf);
+                        }
+                    }
+                    x += glyph->xadd;
+                }
+            }
+            current.x = x;
+            current.y = y;
+        }
+    }
+}
+
+
+
+static void
+LongText(int const version) {
+    struct Point p;
+
+    read_point(&p);
+    do_text(p.x, p.y);
+}
+
+
+
+static void
+DHText(int const version) {
+    current.x += read_byte();
+    do_text(current.x, current.y);
+}
+
+
+
+static void
+DVText(int const version) {
+    current.y += read_byte();
+    do_text(current.x, current.y);
+}
+
+
+
+static void
+DHDVText(int const version) {
+    byte dh, dv;
+
+    dh = read_byte();
+    dv = read_byte();
+
+    if (verbose)
+        pm_message("dh, dv = %d, %d", dh, dv);
+
+    current.x += dh;
+    current.y += dv;
+    do_text(current.x, current.y);
+}
+
+
+
+/*
+ * This could use read_pixmap, but I'm too lazy to hack read_pixmap.
+ */
+
+static void
+directBits(unsigned int const pictVersion, 
+           bool         const skipRegion) {
+
+    struct pixMap   p;
+    struct Rect     srcRect;
+    struct Rect     dstRect;
+    struct raster   raster;
+    word            mode;
+    unsigned int    rectWidth;
+
+    /* skip fake len, and fake EOF */
+    skip(4);    /* Ptr baseAddr == 0x000000ff */
+    read_word();    /* version */
+    read_rect(&p.Bounds);
+    rectWidth = p.Bounds.right - p.Bounds.left;
+    p.packType = read_word();
+    p.packSize = read_long();
+    p.hRes = read_long();
+    p.vRes = read_long();
+    p.pixelType = read_word();
+    p.pixelSize = read_word();
+    p.pixelSize = read_word();    /* XXX twice??? */
+    p.cmpCount = read_word();
+    p.cmpSize = read_word();
+    p.planeBytes = read_long();
+    p.pmTable = read_long();
+    p.pmReserved = read_long();
+
+    read_rect(&srcRect);
+    if (verbose)
+        dumpRect("source rectangle:", srcRect);
+
+    read_rect(&dstRect);
+    if (verbose)
+        dumpRect("destination rectangle:", dstRect);
+
+    mode = read_word();
+    if (verbose)
+        pm_message("transfer mode = %s", const_name(transfer_name, mode));
+
+    if (skipRegion) 
+        skip_poly_or_region(pictVersion);
+
+    unpackbits(ifp, &p.Bounds, 0, p.pixelSize, &raster);
+
+    blit(srcRect, p.Bounds, raster, p.pixelSize,
+         dstRect, picFrame, rowlen, NULL, mode);
+
+    freeRaster(raster);
+}
+
+
+
+#define SKIP_REGION_TRUE TRUE
+#define SKIP_REGION_FALSE FALSE
+
+static void
+DirectBitsRect(int const version) {
+
+    directBits(version, SKIP_REGION_FALSE);
+}
+
+
+
+static void
+DirectBitsRgn(int const version) {
+
+    directBits(version, SKIP_REGION_TRUE);
+}
+
+
+
+static void
+do_pixmap(int  const version, 
+          word const rowBytes, 
+          int  const is_region) {
+/*----------------------------------------------------------------------------
+   Do a paletted image.
+-----------------------------------------------------------------------------*/
+    word mode;
+    struct pixMap p;
+    struct raster raster;
+    struct RGBColor * color_table;
+    struct Rect srcRect;
+    struct Rect dstRect;
+
+    read_pixmap(&p);
+
+    if (verbose)
+        pm_message("%u x %u paletted image",
+                   p.Bounds.right - p.Bounds.left,
+                   p.Bounds.bottom - p.Bounds.top);
+
+    color_table = read_color_table();
+
+    read_rect(&srcRect);
+
+    if (verbose)
+        dumpRect("source rectangle:", srcRect);
+
+    read_rect(&dstRect);
+
+    if (verbose)
+        dumpRect("destination rectangle:", dstRect);
+
+    mode = read_word();
+
+    if (verbose)
+        pm_message("transfer mode = %s", const_name(transfer_name, mode));
+
+    if (is_region)
+        skip_poly_or_region(version);
+
+    stage = "unpacking rectangle";
+
+    unpackbits(ifp, &p.Bounds, rowBytes, p.pixelSize, &raster);
+
+    blit(srcRect, p.Bounds, raster, 8,
+         dstRect, picFrame, rowlen, color_table, mode);
+
+    free(color_table);
+    freeRaster(raster);
+}
+
+
+
+static void
+do_bitmap(int const version, 
+          int const rowBytes, 
+          int const is_region) {
+/*----------------------------------------------------------------------------
+   Do a bitmap.  That's one bit per pixel, 0 is white, 1 is black.
+-----------------------------------------------------------------------------*/
+    struct Rect Bounds;
+    struct Rect srcRect;
+    struct Rect dstRect;
+    word mode;
+    struct raster raster;
+    static struct RGBColor color_table[] = { 
+        {65535L, 65535L, 65535L}, {0, 0, 0} };
+
+    read_rect(&Bounds);
+    read_rect(&srcRect);
+    read_rect(&dstRect);
+    mode = read_word();
+    if (verbose)
+        pm_message("transfer mode = %s", const_name(transfer_name, mode));
+
+    if (is_region)
+        skip_poly_or_region(version);
+
+    stage = "unpacking rectangle";
+
+    unpackbits(ifp, &Bounds, rowBytes, 1, &raster);
+
+    blit(srcRect, Bounds, raster, 8,
+         dstRect, picFrame, rowlen, color_table, mode);
+
+    freeRaster(raster);
+}
+
+
+
+static void
+BitsRect(int const version) {
+
+    word rowBytesWord;
+    bool pixMap;
+    unsigned int rowBytes;
+
+    stage = "Reading rowBytes word for bitsrect";
+    rowBytesWord = read_word();
+
+    interpretRowBytesWord(rowBytesWord, &pixMap, &rowBytes);
+
+    if (pixMap)
+        do_pixmap(version, rowBytes, 0);
+    else
+        do_bitmap(version, rowBytes, 0);
+}
+
+
+
+static void
+BitsRegion(int const version) {
+
+    word rowBytesWord;
+    bool pixMap;
+    unsigned int rowBytes;
+
+    stage = "Reading rowBytes for bitsregion";
+    rowBytesWord = read_word();
+
+    interpretRowBytesWord(rowBytesWord, &pixMap, &rowBytes);
+
+    if (pixMap)
+        do_pixmap(version, rowBytes, 1);
+    else
+        do_bitmap(version, rowBytes, 1);
+}
+
+
+
+ /*
+  * See http://developer.apple.com/techpubs/mac/QuickDraw/QuickDraw-461.html
+  * for opcode description
+  */
+static struct opdef const optable[] = {
+/* 0x00 */  { "NOP", 0, NULL, "nop" },
+/* 0x01 */  { "Clip", NA, Clip, "clip" },
+/* 0x02 */  { "BkPat", 8, BkPat, "background pattern" },
+/* 0x03 */  { "TxFont", 2, TxFont, "text font (word)" },
+/* 0x04 */  { "TxFace", 1, TxFace, "text face (byte)" },
+/* 0x05 */  { "TxMode", 2, TxMode, "text mode (word)" },
+/* 0x06 */  { "SpExtra", 4, NULL, "space extra (fixed point)" },
+/* 0x07 */  { "PnSize", 4, PnSize, "pen size (point)" },
+/* 0x08 */  { "PnMode", 2, PnMode, "pen mode (word)" },
+/* 0x09 */  { "PnPat", 8, PnPat, "pen pattern" },
+/* 0x0a */  { "FillPat", 8, FillPat, "fill pattern" },
+/* 0x0b */  { "OvSize", 4, NULL, "oval size (point)" },
+/* 0x0c */  { "Origin", 4, NULL, "dh, dv (word)" },
+/* 0x0d */  { "TxSize", 2, TxSize, "text size (word)" },
+/* 0x0e */  { "FgColor", 4, NULL, "foreground color (longword)" },
+/* 0x0f */  { "BkColor", 4, NULL, "background color (longword)" },
+/* 0x10 */  { "TxRatio", 8, NULL, "numerator (point), denominator (point)" },
+/* 0x11 */  { "Version", 1, NULL, "version (byte)" },
+/* 0x12 */  { "BkPixPat", NA, BkPixPat, "color background pattern" },
+/* 0x13 */  { "PnPixPat", NA, PnPixPat, "color pen pattern" },
+/* 0x14 */  { "FillPixPat", NA, FillPixPat, "color fill pattern" },
+/* 0x15 */  { "PnLocHFrac", 2, PnLocHFrac, "fractional pen position" },
+/* 0x16 */  { "ChExtra", 2, NULL, "extra for each character" },
+/* 0x17 */  res(0),
+/* 0x18 */  res(0),
+/* 0x19 */  res(0),
+/* 0x1a */  { "RGBFgCol", RGB_LEN, RGBFgCol, "RGB foreColor" },
+/* 0x1b */  { "RGBBkCol", RGB_LEN, RGBBkCol, "RGB backColor" },
+/* 0x1c */  { "HiliteMode", 0, NULL, "hilite mode flag" },
+/* 0x1d */  { "HiliteColor", RGB_LEN, NULL, "RGB hilite color" },
+/* 0x1e */  { "DefHilite", 0, NULL, "Use default hilite color" },
+/* 0x1f */  { "OpColor", NA, OpColor, "RGB OpColor for arithmetic modes" },
+/* 0x20 */  { "Line", 8, Line, "pnLoc (point), newPt (point)" },
+/* 0x21 */  { "LineFrom", 4, LineFrom, "newPt (point)" },
+/* 0x22 */  { "ShortLine", 6, ShortLine, 
+              "pnLoc (point, dh, dv (-128 .. 127))" },
+/* 0x23 */  { "ShortLineFrom", 2, ShortLineFrom, "dh, dv (-128 .. 127)" },
+/* 0x24 */  res(WORD_LEN),
+/* 0x25 */  res(WORD_LEN),
+/* 0x26 */  res(WORD_LEN),
+/* 0x27 */  res(WORD_LEN),
+/* 0x28 */  { "LongText", NA, LongText, 
+              "txLoc (point), count (0..255), text" },
+/* 0x29 */  { "DHText", NA, DHText, "dh (0..255), count (0..255), text" },
+/* 0x2a */  { "DVText", NA, DVText, "dv (0..255), count (0..255), text" },
+/* 0x2b */  { "DHDVText", NA, DHDVText, 
+              "dh, dv (0..255), count (0..255), text" },
+/* 0x2c */  res(WORD_LEN),
+/* 0x2d */  res(WORD_LEN),
+/* 0x2e */  res(WORD_LEN),
+/* 0x2f */  res(WORD_LEN),
+/* 0x30 */  { "frameRect", 8, frameRect, "rect" },
+/* 0x31 */  { "paintRect", 8, paintRect, "rect" },
+/* 0x32 */  { "eraseRect", 8, NULL, "rect" },
+/* 0x33 */  { "invertRect", 8, NULL, "rect" },
+/* 0x34 */  { "fillRect", 8, NULL, "rect" },
+/* 0x35 */  res(8),
+/* 0x36 */  res(8),
+/* 0x37 */  res(8),
+/* 0x38 */  { "frameSameRect", 0, frameSameRect, "rect" },
+/* 0x39 */  { "paintSameRect", 0, paintSameRect, "rect" },
+/* 0x3a */  { "eraseSameRect", 0, NULL, "rect" },
+/* 0x3b */  { "invertSameRect", 0, NULL, "rect" },
+/* 0x3c */  { "fillSameRect", 0, NULL, "rect" },
+/* 0x3d */  res(0),
+/* 0x3e */  res(0),
+/* 0x3f */  res(0),
+/* 0x40 */  { "frameRRect", 8, NULL, "rect" },
+/* 0x41 */  { "paintRRect", 8, NULL, "rect" },
+/* 0x42 */  { "eraseRRect", 8, NULL, "rect" },
+/* 0x43 */  { "invertRRect", 8, NULL, "rect" },
+/* 0x44 */  { "fillRRrect", 8, NULL, "rect" },
+/* 0x45 */  res(8),
+/* 0x46 */  res(8),
+/* 0x47 */  res(8),
+/* 0x48 */  { "frameSameRRect", 0, NULL, "rect" },
+/* 0x49 */  { "paintSameRRect", 0, NULL, "rect" },
+/* 0x4a */  { "eraseSameRRect", 0, NULL, "rect" },
+/* 0x4b */  { "invertSameRRect", 0, NULL, "rect" },
+/* 0x4c */  { "fillSameRRect", 0, NULL, "rect" },
+/* 0x4d */  res(0),
+/* 0x4e */  res(0),
+/* 0x4f */  res(0),
+/* 0x50 */  { "frameOval", 8, NULL, "rect" },
+/* 0x51 */  { "paintOval", 8, NULL, "rect" },
+/* 0x52 */  { "eraseOval", 8, NULL, "rect" },
+/* 0x53 */  { "invertOval", 8, NULL, "rect" },
+/* 0x54 */  { "fillOval", 8, NULL, "rect" },
+/* 0x55 */  res(8),
+/* 0x56 */  res(8),
+/* 0x57 */  res(8),
+/* 0x58 */  { "frameSameOval", 0, NULL, "rect" },
+/* 0x59 */  { "paintSameOval", 0, NULL, "rect" },
+/* 0x5a */  { "eraseSameOval", 0, NULL, "rect" },
+/* 0x5b */  { "invertSameOval", 0, NULL, "rect" },
+/* 0x5c */  { "fillSameOval", 0, NULL, "rect" },
+/* 0x5d */  res(0),
+/* 0x5e */  res(0),
+/* 0x5f */  res(0),
+/* 0x60 */  { "frameArc", 12, NULL, "rect, startAngle, arcAngle" },
+/* 0x61 */  { "paintArc", 12, NULL, "rect, startAngle, arcAngle" },
+/* 0x62 */  { "eraseArc", 12, NULL, "rect, startAngle, arcAngle" },
+/* 0x63 */  { "invertArc", 12, NULL, "rect, startAngle, arcAngle" },
+/* 0x64 */  { "fillArc", 12, NULL, "rect, startAngle, arcAngle" },
+/* 0x65 */  res(12),
+/* 0x66 */  res(12),
+/* 0x67 */  res(12),
+/* 0x68 */  { "frameSameArc", 4, NULL, "rect, startAngle, arcAngle" },
+/* 0x69 */  { "paintSameArc", 4, NULL, "rect, startAngle, arcAngle" },
+/* 0x6a */  { "eraseSameArc", 4, NULL, "rect, startAngle, arcAngle" },
+/* 0x6b */  { "invertSameArc", 4, NULL, "rect, startAngle, arcAngle" },
+/* 0x6c */  { "fillSameArc", 4, NULL, "rect, startAngle, arcAngle" },
+/* 0x6d */  res(4),
+/* 0x6e */  res(4),
+/* 0x6f */  res(4),
+/* 0x70 */  { "framePoly", NA, skip_poly_or_region, "poly" },
+/* 0x71 */  { "paintPoly", NA, paintPoly, "poly" },
+/* 0x72 */  { "erasePoly", NA, skip_poly_or_region, "poly" },
+/* 0x73 */  { "invertPoly", NA, skip_poly_or_region, "poly" },
+/* 0x74 */  { "fillPoly", NA, skip_poly_or_region, "poly" },
+/* 0x75 */  resf(skip_poly_or_region),
+/* 0x76 */  resf(skip_poly_or_region),
+/* 0x77 */  resf(skip_poly_or_region),
+/* 0x78 */  { "frameSamePoly", 0, NULL, "poly (NYI)" },
+/* 0x79 */  { "paintSamePoly", 0, NULL, "poly (NYI)" },
+/* 0x7a */  { "eraseSamePoly", 0, NULL, "poly (NYI)" },
+/* 0x7b */  { "invertSamePoly", 0, NULL, "poly (NYI)" },
+/* 0x7c */  { "fillSamePoly", 0, NULL, "poly (NYI)" },
+/* 0x7d */  res(0),
+/* 0x7e */  res(0),
+/* 0x7f */  res(0),
+/* 0x80 */  { "frameRgn", NA, skip_poly_or_region, "region" },
+/* 0x81 */  { "paintRgn", NA, skip_poly_or_region, "region" },
+/* 0x82 */  { "eraseRgn", NA, skip_poly_or_region, "region" },
+/* 0x83 */  { "invertRgn", NA, skip_poly_or_region, "region" },
+/* 0x84 */  { "fillRgn", NA, skip_poly_or_region, "region" },
+/* 0x85 */  resf(skip_poly_or_region),
+/* 0x86 */  resf(skip_poly_or_region),
+/* 0x87 */  resf(skip_poly_or_region),
+/* 0x88 */  { "frameSameRgn", 0, NULL, "region (NYI)" },
+/* 0x89 */  { "paintSameRgn", 0, NULL, "region (NYI)" },
+/* 0x8a */  { "eraseSameRgn", 0, NULL, "region (NYI)" },
+/* 0x8b */  { "invertSameRgn", 0, NULL, "region (NYI)" },
+/* 0x8c */  { "fillSameRgn", 0, NULL, "region (NYI)" },
+/* 0x8d */  res(0),
+/* 0x8e */  res(0),
+/* 0x8f */  res(0),
+/* 0x90 */  { "BitsRect", NA, BitsRect, "copybits, rect clipped" },
+/* 0x91 */  { "BitsRgn", NA, BitsRegion, "copybits, rgn clipped" },
+/* 0x92 */  res(WORD_LEN),
+/* 0x93 */  res(WORD_LEN),
+/* 0x94 */  res(WORD_LEN),
+/* 0x95 */  res(WORD_LEN),
+/* 0x96 */  res(WORD_LEN),
+/* 0x97 */  res(WORD_LEN),
+/* 0x98 */  { "PackBitsRect", NA, BitsRect, "packed copybits, rect clipped" },
+/* 0x99 */  { "PackBitsRgn", NA, BitsRegion, "packed copybits, rgn clipped" },
+/* 0x9a */  { "DirectBitsRect", NA, DirectBitsRect, 
+              "PixMap, srcRect, dstRect, int copymode, PixData" },
+/* 0x9b */  { "DirectBitsRgn", NA, DirectBitsRgn, 
+              "PixMap, srcRect, dstRect, int copymode, maskRgn, PixData" },
+/* 0x9c */  res(WORD_LEN),
+/* 0x9d */  res(WORD_LEN),
+/* 0x9e */  res(WORD_LEN),
+/* 0x9f */  res(WORD_LEN),
+/* 0xa0 */  { "ShortComment", 2, ShortComment, "kind (word)" },
+/* 0xa1 */  { "LongComment", NA, LongComment, 
+              "kind (word), size (word), data" }
+};
+
+
+
+static void
+interpret_pict(void) {
+    byte ch;
+    word picSize;
+    word opcode;
+    word len;
+    unsigned int version;
+    int i;
+    struct rgbPlanes planes;
+
+    for (i = 0; i < 64; i++)
+        pen_pat.pix[i] = bkpat.pix[i] = fillpat.pix[i] = 1;
+    pen_width = pen_height = 1;
+    pen_mode = 0; /* srcCopy */
+    pen_trf = transfer(pen_mode);
+    text_mode = 0; /* srcCopy */
+    text_trf = transfer(text_mode);
+
+    stage = "Reading picture size";
+    picSize = read_word();
+
+    if (verbose)
+        pm_message("picture size = %d (0x%x)", picSize, picSize);
+
+    stage = "reading picture frame";
+    read_rect(&picFrame);
+
+    if (verbose) {
+        dumpRect("Picture frame:", picFrame);
+        pm_message("Picture size is %d x %d",
+            picFrame.right - picFrame.left,
+            picFrame.bottom - picFrame.top);
+    }
+
+    if (!fullres)
+        allocPlanes(&planes);
+
+    while ((ch = read_byte()) == 0)
+        ;
+    if (ch != 0x11)
+        pm_error("No version number");
+
+    version = read_byte();
+
+    switch (version) {
+    case 1:
+        break;
+    case 2: {
+        unsigned char const subcode = read_byte();
+        if (subcode != 0xff)
+            pm_error("The only Version 2 PICT images this program "
+                     "undertands are subcode 0xff.  This image has "
+                     "subcode 0x%02x", subcode);
+    } break;
+    default:
+        pm_error("Unrecognized PICT version %u", version);
+    }
+
+    if (verbose)
+        pm_message("PICT version %u", version);
+
+    while((opcode = get_op(version)) != 0xff) {
+        if (opcode < 0xa2) {
+            stage = optable[opcode].name;
+            if (verbose) {
+                if (STREQ(stage, "reserved"))
+                    pm_message("reserved opcode=0x%x", opcode);
+                else
+                    pm_message("Opcode: %s", optable[opcode].name);
+            }
+
+            if (optable[opcode].impl != NULL)
+                (*optable[opcode].impl)(version);
+            else if (optable[opcode].len >= 0)
+                skip(optable[opcode].len);
+            else switch (optable[opcode].len) {
+            case WORD_LEN:
+                len = read_word();
+                skip(len);
+                break;
+            default:
+                pm_error("can't do length %u", optable[opcode].len);
+            }
+        } else if (opcode == 0xc00) {
+            if (verbose)
+                pm_message("HeaderOp");
+            stage = "HeaderOp";
+            skip(24);
+        } else if (opcode >= 0xa2 && opcode <= 0xaf) {
+            stage = "skipping reserved";
+            if (verbose)
+                pm_message("%s 0x%x", stage, opcode);
+            skip(read_word());
+        } else if (opcode >= 0xb0 && opcode <= 0xcf) {
+            /* just a reserved opcode, no data */
+            if (verbose)
+                pm_message("reserved 0x%x", opcode);
+        } else if (opcode >= 0xd0 && opcode <= 0xfe) {
+            stage = "skipping reserved";
+            if (verbose)
+                pm_message("%s 0x%x", stage, opcode);
+            skip(read_long());
+        } else if (opcode >= 0x100 && opcode <= 0x7fff) {
+            stage = "skipping reserved";
+            if (verbose)
+                pm_message("%s 0x%x", stage, opcode);
+            skip((opcode >> 7) & 255);
+        } else if (opcode >= 0x8000 && opcode <= 0x80ff) {
+            /* just a reserved opcode */
+            if (verbose)
+                pm_message("reserved 0x%x", opcode);
+        } else if (opcode >= 0x8100) {
+            stage = "skipping reserved";
+            if (verbose)
+                pm_message("%s 0x%x", stage, opcode);
+            skip(read_long());
+        } else
+            pm_error("This program does not understand opcode 0x%04x", opcode);
+    }
+    
+    if (fullres)
+        do_blits(&planes);
+
+    outputPpm(planes);
+
+    freePlanes(planes);
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+    int argn;
+    int header;
+    const char* const usage =
+"[-verbose] [-fullres] [-noheader] [-quickdraw] [-fontdir file] [pictfile]";
+
+    ppm_init( &argc, argv );
+
+    argn = 1;
+    verbose = 0;
+    fullres = 0;
+    header = 1;
+    recognize_comment = 1;
+
+    while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0') {
+        if (pm_keymatch(argv[argn], "-verbose", 2))
+            verbose++;
+        else if (pm_keymatch(argv[argn], "-fullres", 3))
+            fullres = 1;
+        else if (pm_keymatch(argv[argn], "-noheader", 2))
+            header = 0;
+        else if (pm_keymatch(argv[argn], "-quickdraw", 2))
+            recognize_comment = 0;
+        else if (pm_keymatch(argv[argn], "-fontdir", 3)) {
+            argn++;
+            if (!argv[argn])
+                pm_usage(usage);
+            else
+                load_fontdir(argv[argn]);
+        }
+        else
+            pm_usage(usage);
+        ++argn;
+    }
+
+    load_fontdir("fontdir");
+
+    if (argn < argc) {
+        ifp = pm_openr(argv[argn]);
+        ++argn;
+    } else
+        ifp = stdin;
+
+    if (argn != argc)
+        pm_usage(usage);
+
+    if (header) {
+        stage = "Reading 512 byte header";
+        skip(512);
+    }
+
+    interpret_pict();
+
+    return 0;
+}
diff --git a/converter/ppm/pjtoppm.c b/converter/ppm/pjtoppm.c
new file mode 100644
index 00000000..7b694fb3
--- /dev/null
+++ b/converter/ppm/pjtoppm.c
@@ -0,0 +1,253 @@
+/* pjtoppm.c - convert an HP PainJetXL image to a portable pixmap file
+**
+** Copyright (C) 1990 by Christos Zoulas (christos@ee.cornell.edu)
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+#include "mallocvar.h"
+
+static char usage[] =  "[paintjetfile]";
+
+static int egetc ARGS((FILE *fp));
+static int
+egetc(fp)
+    FILE *fp;
+{
+    int c;
+    if ((c = fgetc(fp)) == -1)
+        pm_error("unexpected end of file");
+    return(c);
+}
+
+int
+main(argc, argv)
+    int argc;
+    char *argv[];
+{
+    int cmd, val;
+    char buffer[BUFSIZ];
+    int planes = 3, rows = -1, cols = -1;
+    int r = 0, c = 0, p = 0, i;
+    unsigned char **image = NULL;
+    int *imlen;
+    FILE *fp = stdin;
+    int mode;
+    int argn;
+    unsigned char bf[3];
+    pixel *pixrow;
+
+
+    ppm_init(&argc, argv);
+    argn = 1;
+    if (argn != argc)
+        fp = pm_openr(argv[argn++]);
+    else
+        fp = stdin;
+
+    if (argn != argc)
+        pm_usage(usage);
+
+    while ((c = fgetc(fp)) != -1) {
+        if (c != '\033')
+            continue;
+        switch (c = egetc(fp)) {
+        case 'E':   /* reset */
+            break;
+        case '*':
+            cmd = egetc(fp);
+            for (i = 0; i < BUFSIZ; i++) {
+                if (!isdigit(c = egetc(fp)) && c != '+' && c != '-')
+                    break;
+                buffer[i] = c;
+            }
+            if (i != 0) {
+                buffer[i] = '\0';
+                if (sscanf(buffer, "%d", &val) != 1) 
+                    pm_error("bad value `%s' at <ESC>*%c%c", buffer, cmd, c);
+            }
+            else
+                val = -1;
+            switch (cmd) {
+            case 't':
+                switch (c) {
+                case 'J':   /* render */
+                    break;
+                case 'K':   /* back scale */
+                    break;
+                case 'I':   /* gamma */
+                    break;
+                case 'R':
+                    break;  /* set resolution */
+                default:
+                    pm_message("uninmplemented <ESC>*%c%d%c", cmd, val, c);
+                    break;
+                }
+                break;
+            case 'r':
+                switch (c) {
+                case 'S':   /* width */
+                    cols = val;
+                    break;
+                case 'T':   /* height */
+                    rows = val;
+                    break;
+                case 'U':   /* planes */
+                    planes = val;
+                    if (planes != 3) 
+                        pm_error("can handle only 3 plane files");
+                    break;
+                case 'A':   /* begin raster */
+                    break;
+                case 'B':
+                case 'C':   /* end raster */
+                    break;
+                case 'V':
+                    break;  /* set deci height */
+                case 'H':
+                    break;  /* set deci width */
+                default:
+                    pm_message("uninmplemented <ESC>*%c%d%c", cmd, val, c);
+                    break;
+                }
+                break;
+            case 'b':
+                switch (c) {
+                case 'M':   /* transmission mode */
+                    if (val != 0 && val != 1)
+                        pm_error("unimplemented trasmission mode %d", val);
+                    mode = val;
+                    break;
+                case 'V':   /* send plane */
+                case 'W':   /* send last plane */
+                    if (rows == -1 || r >= rows || image == NULL) {
+                        if (rows == -1 || r >= rows)
+                            rows += 100;
+                        if (image == NULL) {
+                            MALLOCARRAY(image, rows * planes);
+                            MALLOCARRAY(imlen, rows * planes);
+                        }
+                        else {
+                            image = (unsigned char **) 
+                                realloc(image, 
+                                        rows * planes * 
+                                        sizeof(unsigned char *));
+                            imlen = (int *) 
+                                realloc(imlen, rows * planes * sizeof(int));
+                        }
+                    }
+                    if (image == NULL || imlen == NULL)
+                        pm_error("out of memory");
+                    if (p == planes) 
+                        pm_error("too many planes");
+                    cols = cols > val ? cols : val;
+                    imlen[r * planes + p] = val;
+                    MALLOCARRAY(image[r * planes + p], val);
+                    if (image[r * planes + p] == NULL) 
+                        pm_error("out of memory");
+                    if (fread(image[r * planes + p], 1, val, fp) != val) 
+                        pm_error("short data");
+                    if (c == 'V')
+                        p++;
+                    else {
+                        p = 0;
+                        r++;
+                    }
+                    break;
+                default:
+                    pm_message("uninmplemented <ESC>*%c%d%c", cmd, val, c);
+                    break;
+                }
+                break;
+            case 'p': /* Position */
+                if (p != 0) 
+                    pm_error("changed position in the middle of "
+                             "transferring planes");
+                switch (c) {
+                case 'X':
+                    pm_message("can only position in y");
+                    break;
+                case 'Y':
+                    if (buffer[0] == '+')
+                        val = r + val;
+                    if (buffer[0] == '-')
+                        val = r - val;
+                    for (; val > r; r++) 
+                        for (p = 0; p < 3; p++) {
+                            imlen[r * planes + p] = 0;
+                            image[r * planes + p] = NULL;
+                        }
+                    r = val;
+                    break;
+                default:
+                    pm_message("uninmplemented <ESC>*%c%d%c", cmd, val, c);
+                    break;
+                }
+            default:
+                pm_message("uninmplemented <ESC>*%c%d%c", cmd, val, c);
+                break;
+            }
+        }
+    }
+    pm_close(fp);
+    rows = r;
+    if (mode == 1) {
+        unsigned char *buf;
+        int newcols = 0;
+        newcols = 10240; /* It could not be larger that that! */
+        cols = 0;
+        for (r = 0; r < rows; r++) {
+            if (image[r * planes] == NULL)
+                continue;
+            for (p = 0; p < planes; p++) {
+                MALLOCARRAY(buf, newcols);
+                if (buf == NULL) 
+                    pm_error("out of memory");
+                for (i = 0, c = 0; c < imlen[p + r * planes]; c += 2)
+                    for (cmd = image[p + r * planes][c],
+                             val = image[p + r * planes][c+1]; 
+                         cmd >= 0 && i < newcols; cmd--, i++) 
+                        buf[i] = val;
+                cols = cols > i ? cols : i;
+                free(image[p + r * planes]);
+                /* 
+                 * This is less than what we have so it realloc should 
+                 * not return null. Even if it does, tough! We will
+                 * lose a line, and probably die on the next line anyway
+                 */
+                image[p + r * planes] = (unsigned char *) realloc(buf, i);
+            }
+        }
+        cols *= 8;
+    }
+            
+       
+    ppm_writeppminit(stdout, cols, rows, (pixval) 255, 0);
+    pixrow = ppm_allocrow(cols);
+    for (r = 0; r < rows; r++) {
+        if (image[r * planes] == NULL) {
+            for (c = 0; c < cols; c++)
+                PPM_ASSIGN(pixrow[c], 0, 0, 0);
+            continue;
+        }
+        for (cmd = 0, c = 0; c < cols; c += 8, cmd++) 
+            for (i = 0; i < 8 && c + i < cols; i++) {
+                for (p = 0; p < planes; p++) 
+                    if (mode == 0 && cmd >= imlen[r * planes + p])
+                        bf[p] = 0;
+                    else
+                        bf[p] = (image[r * planes + p][cmd] & 
+                                 (1 << (7 - i))) ? 255 : 0;
+                PPM_ASSIGN(pixrow[c + i], bf[0], bf[1], bf[2]);
+            }
+        ppm_writeppmrow(stdout, pixrow, cols, (pixval) 255, 0);
+    }
+    pm_close(stdout);
+    exit(0);
+}
diff --git a/converter/ppm/ppmtoacad.c b/converter/ppm/ppmtoacad.c
new file mode 100644
index 00000000..4f927f02
--- /dev/null
+++ b/converter/ppm/ppmtoacad.c
@@ -0,0 +1,394 @@
+/*
+
+      Convert a portable pixmap to an AutoCAD slide or DXB file
+
+    Author:
+            John Walker
+            Autodesk SA
+            Avenue des Champs-Montants 14b
+            CH-2074 MARIN
+            Switzerland
+            Usenet: kelvin@Autodesk.com
+            Fax:    038/33 88 15
+            Voice:  038/33 76 33
+
+    Permission  to  use, copy, modify, and distribute this software and
+    its documentation  for  any  purpose  and  without  fee  is  hereby
+    granted,  without any conditions or restrictions.  This software is
+    provided "as is" without express or implied warranty.
+
+*/
+
+#include <stdio.h>
+#include "ppm.h"
+
+#define TRUE     1
+#define FALSE    0
+
+#define EOS     '\0'
+
+#define MAXHIST         32767         /* Color histogram maximum size */
+
+static pixel **pixels;                /* Input pixel map */
+static colorhash_table cht;           /* Color hash table */
+static int curcol = -1;               /* Current slide output color */
+static int polymode = FALSE;          /* Output filled polygons ? */
+static int dxbmode = FALSE;           /* Output .dxb format ? */
+static int bgcol = -1;                /* Screen background color */
+static double aspect = 1.0;           /* Pixel aspect ratio correction */
+static int gamut = 256;               /* Output color gamut */
+
+#include "autocad.h"                  /* AutoCAD standard color assignments */
+
+/* prototypes */
+static void outrun ARGS((int color, int ysize, int y, int xstart, int xend));
+static void slideout ARGS((int xdots, int ydots, int ncolors,
+        unsigned char *red, unsigned char *green, unsigned char *blue));
+
+
+/*  OUTRUN  --  Output a run of pixels. */
+
+static void outrun(color, ysize, y, xstart, xend)
+  int color, ysize, y, xstart, xend;
+{
+    if (color == 0) {
+        return;                       /* Let screen background handle this */
+    }
+
+    if (curcol != color) {
+        if (dxbmode) {
+            putchar((char)136);
+            (void) pm_writelittleshort(stdout, color);
+        } else {
+            (void) pm_writelittleshort(stdout, (short) 0xFF00 | color);
+        }
+        curcol = color;
+    }
+    if (polymode) {
+        int v, yb = (ysize - y) + 1, yspan = 1;
+
+        /* Since  we're emitting filled polygons,  let's scan downward
+           in the pixmap and see if we can extend the run on this line
+           vertically  as  well.   If  so, emit a polygon that handles
+           both the horizontal and vertical run and clear  the  pixels
+           in the subsequent lines to the background color. */
+
+        for (v = y + 1; v <= ysize; v++) {
+            int j, mismatch = FALSE;
+
+            for (j = xstart; j <= xend; j++) {
+                if (PPM_GETR(pixels[y][j]) != PPM_GETR(pixels[v][j])) {
+                    mismatch = TRUE;
+                    break;
+                }
+            }
+            if (mismatch) {
+                break;
+            }
+            for (j = xstart; j <= xend; j++) {
+                PPM_ASSIGN(pixels[v][j], 0, 0, 0);
+            }
+        }
+        yspan = v - y;
+
+        if (dxbmode) {
+            putchar(11);              /* Solid */
+            (void) pm_writelittleshort(
+                stdout, (int) (xstart * aspect + 0.4999));
+            (void) pm_writelittleshort(stdout, yb);
+
+            (void) pm_writelittleshort(
+                stdout, (int) ((xend + 1) * aspect + 0.4999));
+            (void) pm_writelittleshort(stdout, yb);
+
+            (void) pm_writelittleshort(
+                stdout, (int) (xstart * aspect + 0.4999));
+            (void) pm_writelittleshort(stdout, yb - yspan);
+
+            (void) pm_writelittleshort(
+                stdout, (int) ((xend + 1) * aspect + 0.4999));
+            (void) pm_writelittleshort(stdout, yb - yspan);
+        } else {
+            (void) pm_writelittleshort(stdout, (short) 0xFD00); 
+              /* Solid fill header */
+            (void) pm_writelittleshort(stdout, 4);      
+              /* Vertices to follow */
+            (void) pm_writelittleshort(stdout, -2);     /* Fill type */
+
+            (void) pm_writelittleshort(stdout, (short)0xFD00); 
+              /* Solid fill vertex */
+            (void) pm_writelittleshort(stdout, xstart);
+            (void) pm_writelittleshort(stdout, yb);
+
+            (void) pm_writelittleshort(stdout, (short) 0xFD00); 
+              /* Solid fill vertex */
+            (void) pm_writelittleshort(stdout, xend + 1);
+            (void) pm_writelittleshort(stdout, yb);
+
+            (void) pm_writelittleshort(stdout, (short) 0xFD00); 
+              /* Solid fill vertex */
+            (void) pm_writelittleshort(stdout, xend + 1);
+            (void) pm_writelittleshort(stdout, yb - yspan);
+
+            (void) pm_writelittleshort(stdout, (short) 0xFD00); 
+              /* Solid fill vertex */
+            (void) pm_writelittleshort(stdout, xstart);
+            (void) pm_writelittleshort(stdout, yb - yspan);
+
+            (void) pm_writelittleshort(stdout, (short) 0xFD00); 
+              /* Solid fill trailer */
+            (void) pm_writelittleshort(stdout, 4); /* Vertices that precede */
+            (void) pm_writelittleshort(stdout, -2);     /* Fill type */
+        }
+    } else {
+        (void) pm_writelittleshort(stdout, xstart);     /* Vector:  From X */
+        (void) pm_writelittleshort(stdout, ysize - y);  /*          From Y */
+        (void) pm_writelittleshort(stdout, xend);       /*          To   X */
+        (void) pm_writelittleshort(stdout, ysize - y);  /*          To   Y */
+    }
+}
+
+/*  SLIDEOUT  --  Write an AutoCAD slide.  */
+
+static void slideout(xdots, ydots, ncolors, red, green, blue)
+  int xdots, ydots, ncolors;
+  unsigned char *red, *green, *blue;
+{
+    static char sldhead[18] = "AutoCAD Slide\r\n\32";
+    static char dxbhead[20] = "AutoCAD DXB 1.0\r\n\32";
+    unsigned char *acadmap;
+    int i, xsize, ysize;
+
+    /* If the user has specified a non-black screen background color,
+       swap the screen background color into color  index  zero  and
+       move  black into the slot previously occupied by the background
+       color. */
+
+    if (bgcol > 0) {
+        acadcol[0][0] = acadcol[bgcol][0];
+        acadcol[0][1] = acadcol[bgcol][1];
+        acadcol[0][2] = acadcol[bgcol][2];
+        acadcol[bgcol][0] = acadcol[bgcol][1] = acadcol[bgcol][2] = 0;
+    }
+
+    acadmap = (unsigned char *) pm_allocrow(ncolors, sizeof(unsigned char));
+    xsize = polymode ? xdots : (xdots - 1);
+    ysize = polymode ? ydots : (ydots - 1);
+    if (dxbmode) {
+        fwrite(dxbhead, 19, 1, stdout); /* DXB file header */
+        putchar((char)135);                 /* Number mode */
+        (void) pm_writelittleshort(stdout, 0);        /* ...short integers */
+    } else {
+        fwrite(sldhead, 17, 1, stdout); /* Slide file header */
+        putchar(86);                  /* Number format indicator */
+        putchar(2);                   /* File level number */
+        (void) pm_writelittleshort(stdout, xsize); /* Max X co-ordinate value */
+        (void) pm_writelittleshort(stdout, ysize); /* Max Y co-ordinate value */
+                                      /* Aspect ratio indicator */
+        (void) pm_writelittlelong(
+            stdout, (long) ((((double) xsize) / ysize) * aspect * 1E7));
+        (void) pm_writelittleshort(stdout, 2);        /* Polygon fill type */
+        (void) pm_writelittleshort(stdout, 0x1234);   /* Byte order indicator */
+    }
+
+    /* Now  build  a  mapping  from  the  image's computed color map
+       indices to the closest representation  of  each  color  within
+       AutoCAD's color repertoire. */
+
+    for (i = 0; i < ncolors; i++) {
+        int best, j;
+        long dist = 3 * 256 * 256;
+
+        for (j = 0; j < gamut; j++) {
+            long dr = red[i] - acadcol[j][0],
+                 dg = green[i] - acadcol[j][1],
+                 db = blue[i] - acadcol[j][2];
+            long tdist = dr * dr + dg * dg + db * db;
+
+            if (tdist < dist) {
+                dist = tdist;
+                best = j;
+            }
+        }
+        acadmap[i] = best;
+    }
+
+    /* Swoop  over the entire map and replace each  RGB pixel with its
+       closest  AutoCAD  color  representation.   We  do  this  in  a
+       separate  pass  since it avoids repetitive mapping of pixels as
+       we examine them for compression. */
+
+    for (i = 0; i < ydots; i++) {
+        int x;
+
+        for (x = 0; x < xdots; x++) {
+            PPM_ASSIGN(pixels[i][x],
+                acadmap[ppm_lookupcolor(cht, &pixels[i][x])], 0 ,0);
+        }
+    }
+
+    /* Output a run-length encoded expression of the pixmap as AutoCAD
+       vectors. */
+
+    for (i = 0; i < ydots; i++) {
+        int x, rx, rpix = -1, nrun = 0;
+
+        for (x = 0; x < xdots; x++) {
+            int pix = PPM_GETR(pixels[i][x]);
+
+            if (pix != rpix) {
+                if (nrun > 0) {
+                    if (rpix > 0) {
+                        outrun(rpix, ydots - 1, i, rx, x - 1);
+                    }
+                }
+                rpix = pix;
+                rx = x;
+                nrun = 1;
+            }
+        }
+        if ((nrun > 0) && (rpix > 0)) {
+            outrun(rpix, ydots - 1, i, rx, xdots - 1);
+        }
+    }
+    if (dxbmode) {
+        putchar(0);                   /* DXB end sentinel */
+    } else {
+        (void) pm_writelittleshort(stdout, (short) 0xFC00); 
+          /* End of file marker */
+    }
+    pm_freerow((char *) acadmap);
+}
+
+/*  Main program.  */
+
+int main(argc, argv)
+  int argc;
+  char* argv[];
+{
+    FILE *ifp;
+    int argn, rows, cols, ncolors, i;
+    int aspectspec = FALSE;
+    pixval maxval;
+    colorhist_vector chv;
+    unsigned char *Red, *Green, *Blue;
+    const char * const usage =
+        "[-poly] [-dxb] [-white] [-background <col>]\n\
+                  [-aspect <f>] [-8] [ppmfile]";
+
+
+    ppm_init(&argc, argv);
+
+    argn = 1;
+    while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != EOS) {
+        if (pm_keymatch(argv[argn], "-dxb", 2)) {
+            dxbmode = polymode = TRUE;
+        } else if (pm_keymatch(argv[argn], "-poly", 2)) {
+            polymode = TRUE;
+        } else if (pm_keymatch(argv[argn], "-white", 2)) {
+            if (bgcol >= 0) {
+                pm_error("already specified a background color");
+            }
+            bgcol = 7;
+        } else if (pm_keymatch(argv[argn], "-background", 2)) {
+            if (bgcol >= 0) {
+                pm_error("already specified a background color");
+            }
+            argn++;
+            if ((argn == argc) || (sscanf(argv[argn], "%d", &bgcol) != 1))
+                pm_usage(usage);
+            if (bgcol < 0) {
+                pm_error("background color must be >= 0 and <= 255");
+            }
+        } else if (pm_keymatch(argv[argn], "-aspect", 2)) {
+            if (aspectspec) {
+                pm_error("already specified an aspect ratio");
+            }
+            argn++;
+            if ((argn == argc) || (sscanf(argv[argn], "%lf", &aspect) != 1))
+                pm_usage(usage);
+            if (aspect <= 0.0) {
+                pm_error("aspect ratio must be greater than 0");
+            }
+            aspectspec = TRUE;
+        } else if (pm_keymatch(argv[argn], "-8", 2)) {
+            gamut = 8;
+        } else {
+            pm_usage(usage);
+        }
+        argn++;
+    }
+
+    if (argn < argc) {
+        ifp = pm_openr(argv[argn]);
+        argn++;
+    } else {
+        ifp = stdin;
+    }
+
+    if (argn != argc) {
+        pm_usage(usage);
+    }
+
+    pixels = ppm_readppm(ifp, &cols, &rows, &maxval);
+
+    pm_close(ifp);
+
+    /* Figure out the colormap.  Logic for squeezing depth to limit the
+       number of colors in the image was swiped from ppmquant.c. */
+
+    while (TRUE) {
+        int row, col;
+        pixval newmaxval;
+        pixel *pP;
+
+        pm_message("computing colormap...");
+        chv = ppm_computecolorhist(pixels, cols, rows, MAXHIST, &ncolors);
+        if (chv != (colorhist_vector) 0)
+            break;
+        newmaxval = maxval / 2;
+        pm_message(
+        "scaling colors from maxval=%d to maxval=%d to improve clustering...",
+                   maxval, newmaxval );
+        for (row = 0; row < rows; ++row) {
+            for (col = 0, pP = pixels[row]; col < cols; ++col, ++pP) {
+                PPM_DEPTH(*pP, *pP, maxval, newmaxval);
+            }
+        }
+        maxval = newmaxval;
+    }
+    pm_message("%d colors found", ncolors);
+
+    /* Scale the color map derived for the PPM file into one compatible
+       with AutoCAD's convention of 8 bit intensities. */
+
+    if (maxval != 255) {
+        pm_message("maxval is not 255 - automatically rescaling colors");
+    }
+    Red = (unsigned char *) pm_allocrow(ncolors, sizeof(unsigned char));
+    Green = (unsigned char *) pm_allocrow(ncolors, sizeof(unsigned char));
+    Blue = (unsigned char *) pm_allocrow(ncolors, sizeof(unsigned char));
+
+    for (i = 0; i < ncolors; ++i) {
+        if ( maxval == 255 ) {
+            Red[i] = PPM_GETR(chv[i].color);
+            Green[i] = PPM_GETG(chv[i].color);
+            Blue[i] = PPM_GETB( chv[i].color );
+        } else {
+            Red[i] = ((int) PPM_GETR(chv[i].color) * 255) / maxval;
+            Green[i] = ((int) PPM_GETG(chv[i].color) * 255) / maxval;
+            Blue[i] = ((int) PPM_GETB(chv[i].color) * 255) / maxval;
+        }
+    }
+
+    /* And make a hash table for fast lookup. */
+
+    cht = ppm_colorhisttocolorhash(chv, ncolors);
+    ppm_freecolorhist(chv);
+
+    slideout(cols, rows, ncolors, Red, Green, Blue);
+    pm_freerow((char *) Red);
+    pm_freerow((char *) Green);
+    pm_freerow((char *) Blue);
+    exit(0);
+}
diff --git a/converter/ppm/ppmtoarbtxt.c b/converter/ppm/ppmtoarbtxt.c
new file mode 100644
index 00000000..774a47c4
--- /dev/null
+++ b/converter/ppm/ppmtoarbtxt.c
@@ -0,0 +1,505 @@
+/* ppmtoarbtxt.c - convert portable pixmap to cleartext
+**
+** Renamed from ppmtotxt.c by Bryan Henderson in January 2003.
+**
+** Copyright (C) 1995 by Peter Kirchgessner
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "ppm.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+typedef enum {
+/* The types of object we handle */
+    BDATA, IRED, IGREEN, IBLUE, ILUM, FRED, FGREEN, FBLUE, FLUM,
+    WIDTH, HEIGHT, POSX, POSY
+} SKL_OBJ_TYP;
+
+/* Maximum size for a format string ("%d" etc.) */
+#define MAXFORMAT 16
+
+/* The data we keep for each object */
+typedef union
+ {
+  struct BNDAT { char *bdat;   /* Binary data (text with newlines etc.) */
+                 int ndat;
+               } bin_data;
+
+  struct ICDAT { char icformat[MAXFORMAT];  /* Integer colors */
+                 int icolmin, icolmax;
+               } icol_data;
+
+  struct FCDAT { char fcformat[MAXFORMAT];  /* Float colors */
+                 double fcolmin, fcolmax;
+               } fcol_data;
+
+  struct IDAT  { char iformat[MAXFORMAT];   /* Integer data */
+               } i_data;
+ } SKL_OBJ_DATA;
+
+
+/* Each object has a type and some data */
+typedef struct
+ { 
+   SKL_OBJ_TYP otyp;
+   SKL_OBJ_DATA odata;
+ } SKL_OBJ;
+
+
+#define MAX_SKL_HEAD_OBJ 64
+#define MAX_SKL_BODY_OBJ 256
+#define MAX_SKL_TAIL_OBJ 64
+#define MAX_LINE_BUF 1024
+#define MAX_OBJ_BUF 80
+
+
+static void write_txt (fout, nobj, obj, width, height, x, y, red, green, blue)
+FILE *fout;
+int nobj;
+SKL_OBJ *obj[];
+int width, height, x, y;
+double red, green, blue;
+
+{register int count;
+
+#define WRITE_BNDAT(fd,theobj) \
+ {struct BNDAT *bdata = &((theobj)->odata.bin_data); \
+       fwrite (bdata->bdat,bdata->ndat,1,fd); }
+
+#define WRITE_ICOL(fd,theobj,thecol) \
+ {struct ICDAT *icdata = &((theobj)->odata.icol_data); \
+  fprintf (fd,icdata->icformat,(int)(icdata->icolmin \
+                + (icdata->icolmax - icdata->icolmin)*(thecol))); }
+
+#define WRITE_FCOL(fd,theobj,thecol) \
+ {struct FCDAT *fcdata = &((theobj)->odata.fcol_data); \
+  fprintf (fd,fcdata->fcformat,(double)(fcdata->fcolmin \
+                + (fcdata->fcolmax - fcdata->fcolmin)*(thecol))); }
+
+#define WRITE_IDAT(fd,theobj,thedat) \
+ {struct IDAT *idata = &((theobj)->odata.i_data); \
+  fprintf (fd,idata->iformat,thedat); }
+
+ for (count = 0; count < nobj; count++)
+ {
+   switch (obj[count]->otyp)
+   {
+     case BDATA:
+       WRITE_BNDAT (fout,obj[count]);
+       break;
+     case IRED:
+       WRITE_ICOL (fout,obj[count],red);
+       break;
+     case IGREEN:
+       WRITE_ICOL (fout,obj[count],green);
+       break;
+     case IBLUE:
+       WRITE_ICOL (fout,obj[count],blue);
+       break;
+     case ILUM:
+       WRITE_ICOL (fout,obj[count],0.299*red+0.587*green+0.114*blue);
+       break;
+     case FRED:
+       WRITE_FCOL (fout,obj[count],red);
+       break;
+     case FGREEN:
+       WRITE_FCOL (fout,obj[count],green);
+       break;
+     case FBLUE:
+       WRITE_FCOL (fout,obj[count],blue);
+       break;
+     case FLUM:
+       WRITE_FCOL (fout,obj[count],0.299*red+0.587*green+0.114*blue);
+       break;
+     case WIDTH:
+       WRITE_IDAT (fout,obj[count],width);
+       break;
+     case HEIGHT:
+       WRITE_IDAT (fout,obj[count],height);
+       break;
+     case POSX:
+       WRITE_IDAT (fout,obj[count],x);
+       break;
+     case POSY:
+       WRITE_IDAT (fout,obj[count],y);
+       break;
+   }
+ }
+}
+
+
+static SKL_OBJ *
+save_bin_data(int    const ndat, 
+              char * const bdat) {
+
+    /* Save binary data in Object */
+
+    SKL_OBJ *obj;
+
+    obj = (SKL_OBJ *)malloc (sizeof (SKL_OBJ) + ndat);
+    if (obj != NULL)
+    {
+        obj->otyp = BDATA;
+        obj->odata.bin_data.ndat = ndat;
+        obj->odata.bin_data.bdat = ((char *)(obj))+sizeof (SKL_OBJ);
+        memcpy (obj->odata.bin_data.bdat,bdat,ndat);
+    }
+    return (obj);
+}
+
+
+
+/* Save integer color data in Object */
+static SKL_OBJ *save_icol_data (ctyp,format,icolmin,icolmax)
+SKL_OBJ_TYP ctyp;
+char *format;
+int icolmin, icolmax;
+
+{SKL_OBJ *obj;
+
+ obj = (SKL_OBJ *)malloc (sizeof (SKL_OBJ));
+ if (obj != NULL)
+ {
+   obj->otyp = ctyp;
+   strcpy (obj->odata.icol_data.icformat,format);
+   obj->odata.icol_data.icolmin = icolmin;
+   obj->odata.icol_data.icolmax = icolmax;
+ }
+ return (obj);
+}
+
+
+/* Save float color data in Object */
+static SKL_OBJ *save_fcol_data (ctyp,format,fcolmin,fcolmax)
+SKL_OBJ_TYP ctyp;
+char *format;
+double fcolmin, fcolmax;
+
+{SKL_OBJ *obj;
+
+ obj = (SKL_OBJ *)malloc (sizeof (SKL_OBJ));
+ if (obj != NULL)
+ {
+   obj->otyp = ctyp;
+   strcpy (obj->odata.fcol_data.fcformat,format);
+   obj->odata.fcol_data.fcolmin = fcolmin;
+   obj->odata.fcol_data.fcolmax = fcolmax;
+ }
+ return (obj);
+}
+
+
+/* Save universal data in Object */
+static SKL_OBJ *save_i_data (ctyp,format)
+SKL_OBJ_TYP ctyp;
+char *format;
+
+{SKL_OBJ *obj;
+
+ obj = (SKL_OBJ *)malloc (sizeof (SKL_OBJ));
+ if (obj != NULL)
+ {
+   obj->otyp = ctyp;
+   strcpy (obj->odata.i_data.iformat,format);
+ }
+ return (obj);
+}
+
+
+/* Read skeleton file */
+static int read_skeleton (filename,maxskl,nskl,skl)
+char *filename;
+int maxskl,*nskl;
+SKL_OBJ **skl;
+
+{FILE *sklfile;
+ int slen, objlen, chr, n_odata;
+ int icolmin,icolmax;
+ double fcolmin,fcolmax;
+ SKL_OBJ_TYP otyp;
+ char line[MAX_LINE_BUF+MAX_OBJ_BUF+16];
+ char objstr[MAX_OBJ_BUF],typstr[MAX_OBJ_BUF];
+ char formstr[MAX_OBJ_BUF];
+ char meta1 = '#', meta2 = '(', meta3 = ')';
+
+#define SAVE_BIN(slen,line) \
+ { if ((skl[*nskl] = save_bin_data (slen,line)) != NULL) (*nskl)++; \
+   slen = 0; }
+
+#define ADD_STR(slen,line,addlen,addstr) \
+ {int count=0; line[slen++] = meta1; line[slen++] = meta2; \
+  while (count++ < addlen) line[slen++] = addstr[count]; }
+
+ if ((sklfile = fopen (filename,"r")) == NULL)
+   return (-1);
+
+ /* Parse skeleton file */
+ *nskl = 0;
+
+ slen = 0;
+ while ((chr = getc (sklfile)) != EOF)   /* Up to end of skeleton file */
+ {
+   if (*nskl >= maxskl) return (-1);
+
+   if (slen+1 >= MAX_LINE_BUF)   /* Buffer finished ? Save as binary object */
+   {
+     SAVE_BIN (slen,line);
+   }
+
+   if (chr != meta1)      /* Look for start of replacement string */
+   {
+     line[slen++] = chr;
+     continue;
+   }
+
+   if ((chr = getc (sklfile)) == EOF)
+   {
+     line[slen++] = meta1;
+     break;
+   }
+   if (chr != meta2) /* '(' ? Otherwise no replacement */
+   {
+     line[slen++] = meta1;
+     line[slen++] = chr;
+     continue;
+   }
+
+   objlen = 0;
+   for (;;)   /* Read replacement string up to ')' */
+   {
+     if (objlen == sizeof (objstr)) break; /* ')' not found */
+     if ((chr = getc (sklfile)) == EOF) break;
+     if (chr == meta3) break;
+     objstr[objlen++] = chr;
+   }
+   objstr[objlen] = '\0'; /* Now objstr keeps data without metas */
+
+   if (chr != meta3)    /* Object not found ? */
+   {
+     ADD_STR (slen,line,objlen,objstr);   /* Save what we already read */
+     if (chr == EOF) break;
+     continue;
+   }
+
+   typstr[0] = '\0';           /* Get typ of data */
+   sscanf (objstr,"%s",typstr);
+
+                   /* Check for integer colors */
+   if      (STREQ(typstr,"ired")  ) otyp = IRED;
+   else if (STREQ(typstr,"igreen")) otyp = IGREEN;
+   else if (STREQ(typstr,"iblue") ) otyp = IBLUE;
+   else if (STREQ(typstr,"ilum")  ) otyp = ILUM;
+                   /* Check for real colors */
+   else if (STREQ(typstr,"fred")  ) otyp = FRED;
+   else if (STREQ(typstr,"fgreen")) otyp = FGREEN;
+   else if (STREQ(typstr,"fblue") ) otyp = FBLUE;
+   else if (STREQ(typstr,"flum")  ) otyp = FLUM;
+                   /* Check for integer data */
+   else if (STREQ(typstr,"width") ) otyp = WIDTH;
+   else if (STREQ(typstr,"height")) otyp = HEIGHT;
+   else if (STREQ(typstr,"posx")  ) otyp = POSX;
+   else if (STREQ(typstr,"posy")  ) otyp = POSY;
+   else                                    otyp = BDATA;
+
+   if ((otyp == IRED) || (otyp == IGREEN) || (otyp == IBLUE) || (otyp == ILUM))
+   {
+     n_odata = sscanf (objstr,"%*s%s%d%d",formstr,&icolmin,&icolmax);
+
+     if (n_odata == EOF)  /* No arguments specified ? Use defaults */
+     {
+       strcpy (formstr,"%d"); icolmin = 0; icolmax = 255;
+     }
+     else if (n_odata != 3)  /* Wrong specification */
+     {
+       otyp = BDATA;
+     }
+   }
+
+   if ((otyp == FRED) || (otyp == FGREEN) || (otyp == FBLUE) || (otyp == FLUM))
+   {
+     n_odata = sscanf (objstr,"%*s%s%lf%lf",formstr,&fcolmin,&fcolmax);
+
+     if (n_odata == EOF)  /* No arguments specified ? Use defaults */
+     {
+       strcpy (formstr,"%f"); fcolmin = 0.0; fcolmax = 1.0;
+     }
+     else if (n_odata != 3)  /* Wrong specification */
+     {
+       otyp = BDATA;
+     }
+   }
+
+   if (   (otyp == WIDTH) || (otyp == HEIGHT)
+       || (otyp == POSX) || (otyp == POSY))
+   {
+     n_odata = sscanf (objstr,"%*s%s",formstr);
+
+     if (n_odata == EOF)  /* No arguments specified ? Use defaults */
+     {
+       strcpy (formstr,"%d");
+     }
+     else if (n_odata != 1)  /* Wrong specification */
+     {
+       otyp = BDATA;
+     }
+   }
+
+   if (otyp != BDATA)   /* Got an object definition ? */
+   {
+     if (slen > 0)      /* Save what we already found */
+     {
+       SAVE_BIN (slen,line);
+     }
+   }
+
+   /* Now process the object in objstr */
+   switch (otyp)
+   {
+     case BDATA:   /* Bad object definition ? Save as text */
+       ADD_STR (slen,line,objlen,objstr);
+       break;
+
+     case IRED:
+     case IGREEN:
+     case IBLUE:
+     case ILUM:
+       skl[*nskl] = save_icol_data (otyp,formstr,icolmin,icolmax);
+       if (skl[*nskl] != NULL) (*nskl)++;
+       break;
+
+     case FRED:
+     case FGREEN:
+     case FBLUE:
+     case FLUM:
+       skl[*nskl] = save_fcol_data (otyp,formstr,fcolmin,fcolmax);
+       if (skl[*nskl] != NULL) (*nskl)++;
+       break;
+
+     case WIDTH:
+     case HEIGHT:
+     case POSX:
+     case POSY:
+       skl[*nskl] = save_i_data (otyp,formstr);
+       if (skl[*nskl] != NULL) (*nskl)++;
+       break;
+   }
+ } /* EOF of skeleton file */
+
+ if (slen > 0)      /* Drop finishing newline character */
+ {
+   if (line[slen-1] == '\n') slen--;
+ }
+
+ if (slen > 0)      /* Something left ? */
+ {
+   SAVE_BIN (slen,line);   /* Save it */
+ }
+
+ fclose (sklfile);
+ return (0);
+}
+
+
+int main( argc, argv )
+int argc;
+char* argv[];
+
+{register int col;
+ register pixel* xP;
+ pixel* pixelrow;
+ pixval maxval,red,green,blue;
+ double dmaxval;
+ int argn, rows, cols, format, row;
+ int head_nskl,body_nskl,tail_nskl;
+ SKL_OBJ *head_skl[MAX_SKL_HEAD_OBJ];
+ SKL_OBJ *body_skl[MAX_SKL_BODY_OBJ];
+ SKL_OBJ *tail_skl[MAX_SKL_TAIL_OBJ];
+ FILE *ifp;
+ const char *usage = "bodyskl [ -hd headskl ] [ -tl tailskl ] [pnmfile]";
+
+ ppm_init( &argc, argv );
+
+ argn = 1;
+ if (argn == argc)
+   pm_usage( usage );
+                          /* Read body skeleton file */
+ if (read_skeleton (argv[argn],sizeof (body_skl)/sizeof (SKL_OBJ *),
+                    &body_nskl,body_skl) < 0)
+   pm_usage ( usage );
+ ++argn;
+
+ head_nskl = tail_nskl = 0;
+
+ while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0')
+ {
+   if ( pm_keymatch ( argv[argn], "-hd", 1) && argn+1 < argc )
+   {
+     argn++;           /* Read header skeleton */
+     if (read_skeleton (argv[argn],sizeof (head_skl)/sizeof (SKL_OBJ *),
+                        &head_nskl,head_skl) < 0)
+       pm_usage ( usage );
+   }
+   else if ( pm_keymatch ( argv[argn], "-tl", 1) && argn+1 < argc )
+   {
+     argn++;           /* Read tail skeleton */
+     if (read_skeleton (argv[argn],sizeof (tail_skl)/sizeof (SKL_OBJ *),
+                        &tail_nskl,tail_skl) < 0)
+       pm_usage ( usage );
+   }
+   else
+   {
+     pm_usage ( usage );
+   }
+   argn++;
+ }
+
+ if ( argn != argc )
+ {
+   ifp = pm_openr( argv[argn] );
+   ++argn;
+ }
+ else 
+ {
+   ifp = stdin;
+ }
+
+ if ( argn != argc )
+   pm_usage( usage );
+
+ ppm_readppminit( ifp, &cols, &rows, &maxval, &format );
+ pixelrow = ppm_allocrow( cols );
+ dmaxval = (double)maxval;
+
+ if (head_nskl > 0)    /* Write header */
+   write_txt (stdout,head_nskl,head_skl,cols,rows,0,0,0.0,0.0,0.0);
+
+ for ( row = 0; row < rows; ++row )
+ {
+   ppm_readppmrow( ifp, pixelrow, cols, maxval, format );
+
+   for ( col = 0, xP = pixelrow; col < cols; ++col, ++xP )
+   {
+     red = PPM_GETR( *xP );
+     green = PPM_GETG( *xP );
+     blue = PPM_GETB( *xP );
+     write_txt (stdout,body_nskl,body_skl,cols,rows,col,row,
+                red/dmaxval,green/dmaxval,blue/dmaxval);
+   }
+ }
+
+ if (tail_nskl > 0)    /* Write trailer */
+   write_txt (stdout,tail_nskl,tail_skl,cols,rows,0,0,0.0,0.0,0.0);
+
+ pm_close( ifp );
+
+ exit( 0 );
+}
diff --git a/converter/ppm/ppmtobmp.c b/converter/ppm/ppmtobmp.c
new file mode 100644
index 00000000..071f3b12
--- /dev/null
+++ b/converter/ppm/ppmtobmp.c
@@ -0,0 +1,795 @@
+/*
+ * ppmtobmp.c - Converts from a PPM file to a Microsoft Windows or OS/2
+ * .BMP file.
+ *
+ * Copyright (C) 1992 by David W. Sanderson.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and
+ * that both that copyright notice and this permission notice appear
+ * in supporting documentation.  This software is provided "as is"
+ * without express or implied warranty.
+ *
+ */
+
+#define _BSD_SOURCE 1    /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <assert.h>
+#include <string.h>
+#include "bmp.h"
+#include "ppm.h"
+#include "shhopt.h"
+#include "bitio.h"
+
+#define MAXCOLORS 256
+
+enum colortype {TRUECOLOR, PALETTE};
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   A color map for a BMP file.
+-----------------------------------------------------------------------------*/
+    unsigned int count;
+        /* Number of colors in the map.  The first 'count' elements of these
+           arrays are defined; all others are not.
+        */
+    colorhash_table cht;
+
+    /* Indices in the following arrays are the same as in 'cht', above. */
+    unsigned char red[MAXCOLORS];
+    unsigned char grn[MAXCOLORS];
+    unsigned char blu[MAXCOLORS];
+} colorMap;
+
+
+
+static void
+freeColorMap(const colorMap * const colorMapP) {
+
+    if (colorMapP->cht)
+        ppm_freecolorhash(colorMapP->cht);
+}
+
+
+
+static struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    char *input_filename;
+    int class;  /* C_WIN or C_OS2 */
+    unsigned int bppSpec;
+    unsigned int bpp;
+} cmdline;
+
+
+static void
+parse_command_line(int argc, char ** argv,
+                   struct cmdline_info *cmdline_p) {
+/*----------------------------------------------------------------------------
+   Note that many of the strings that this function returns in the
+   *cmdline_p structure are actually in the supplied argv array.  And
+   sometimes, one of these strings is actually just a suffix of an entry
+   in argv!
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int windowsSpec, os2Spec;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3('w', "windows",   OPT_FLAG, NULL, &windowsSpec,            0);
+    OPTENT3('o', "os2",       OPT_FLAG, NULL, &os2Spec,                0);
+    OPTENT3(0,   "bpp",       OPT_UINT, &cmdline_p->bpp, 
+            &cmdline_p->bppSpec,      0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+    if (windowsSpec && os2Spec) 
+        pm_error("Can't specify both -windows and -os2 options.");
+    else if (windowsSpec) 
+        cmdline_p->class = C_WIN;
+    else if (os2Spec)
+        cmdline_p->class = C_OS2;
+    else 
+        cmdline_p->class = C_WIN;
+
+
+    if (cmdline_p->bppSpec) {
+        if (cmdline_p->bpp != 1 && cmdline_p->bpp != 4 && 
+            cmdline_p->bpp != 8 && cmdline_p->bpp != 24)
+        pm_error("Invalid -bpp value specified: %u.  The only values valid\n"
+                 "in the BMP format are 1, 4, 8, and 24 bits per pixel",
+                 cmdline_p->bpp);
+    }
+
+    if (argc - 1 == 0)
+        cmdline_p->input_filename = strdup("-");  /* he wants stdin */
+    else if (argc - 1 == 1)
+        cmdline_p->input_filename = strdup(argv[1]);
+    else 
+        pm_error("Too many arguments.  The only argument accepted\n"
+                 "is the input file specificaton");
+
+}
+
+
+
+static void
+PutByte(FILE * const fp, unsigned char const v) {
+    if (putc(v, fp) == EOF) 
+        pm_error("Write of a byte to a file failed.");
+
+    /* Note:  a Solaris/SPARC user reported on 2003.09.29 that the above
+       putc() returned EOF when a former version of this code declared
+       v as "char" instead of "unsigned char".  This was apparently due
+       to a bug in his C library that caused 255 to look like -1 at some
+       critical internal point.
+    */
+}
+
+
+
+static void
+PutShort(FILE * const fp, short const v) {
+    if (pm_writelittleshort(fp, v) == -1) 
+        pm_error("Write of a halfword to a file failed.");
+}
+
+
+
+static void
+PutLong(FILE * const fp, long const v) {
+    if (pm_writelittlelong(fp, v) == -1)
+        pm_error("Write of a word to a file failed.");
+}
+
+
+
+/*
+ * BMP writing
+ */
+
+static int
+BMPwritefileheader(FILE *        const fp, 
+                   int           const class, 
+                   unsigned long const bitcount, 
+                   unsigned long const x, 
+                   unsigned long const y) {
+/*----------------------------------------------------------------------------
+  Return the number of bytes written, or -1 on error.
+-----------------------------------------------------------------------------*/
+    PutByte(fp, 'B');
+    PutByte(fp, 'M');
+
+    /* cbSize */
+    PutLong(fp, BMPlenfile(class, bitcount, -1, x, y));
+    
+    /* xHotSpot */
+    PutShort(fp, 0);
+    
+    /* yHotSpot */
+    PutShort(fp, 0);
+    
+    /* offBits */
+    PutLong(fp, BMPoffbits(class, bitcount, -1));
+    
+    return 14;
+}
+
+
+
+static int
+BMPwriteinfoheader(FILE *        const fp, 
+                   int           const class, 
+                   unsigned long const bitcount, 
+                   unsigned long const x, 
+                   unsigned long const y) {
+/*----------------------------------------------------------------------------
+  Return the number of bytes written, or -1 on error.
+----------------------------------------------------------------------------*/
+    long cbFix;
+
+    switch (class) {
+    case C_WIN: {
+        cbFix = 40;
+        PutLong(fp, cbFix);
+
+        PutLong(fp, x);         /* cx */
+        PutLong(fp, y);         /* cy */
+        PutShort(fp, 1);        /* cPlanes */
+        PutShort(fp, bitcount); /* cBitCount */
+
+        /*
+         * We've written 16 bytes so far, need to write 24 more
+         * for the required total of 40.
+         */
+
+        PutLong(fp, 0);   /* Compression */
+        PutLong(fp, 0);   /* ImageSize */
+        PutLong(fp, 0);   /* XpixelsPerMeter */
+        PutLong(fp, 0);   /* YpixelsPerMeter */
+        PutLong(fp, 0);   /* ColorsUsed */
+        PutLong(fp, 0);   /* ColorsImportant */
+    }
+    break;
+    case C_OS2: {
+        cbFix = 12;
+        PutLong(fp, cbFix);
+
+        PutShort(fp, x);        /* cx */
+        PutShort(fp, y);        /* cy */
+        PutShort(fp, 1);        /* cPlanes */
+        PutShort(fp, bitcount); /* cBitCount */
+    }
+    break;
+    default:
+        pm_error(er_internal, "BMPwriteinfoheader");
+    }
+
+    return cbFix;
+}
+
+
+
+static int
+BMPwritergb(FILE * const fp, 
+            int    const class, 
+            pixval const R, 
+            pixval const G, 
+            pixval const B) {
+/*----------------------------------------------------------------------------
+  Return the number of bytes written, or -1 on error.
+-----------------------------------------------------------------------------*/
+    switch (class) {
+    case C_WIN:
+        PutByte(fp, B);
+        PutByte(fp, G);
+        PutByte(fp, R);
+        PutByte(fp, 0);
+        return 4;
+    case C_OS2:
+        PutByte(fp, B);
+        PutByte(fp, G);
+        PutByte(fp, R);
+        return 3;
+    default:
+        pm_error(er_internal, "BMPwritergb");
+    }
+    return -1;
+}
+
+
+
+static int
+BMPwritecolormap(FILE *           const ifP, 
+                 int              const class, 
+                 int              const bpp,
+                 const colorMap * const colorMapP) {
+/*----------------------------------------------------------------------------
+  Return the number of bytes written, or -1 on error.
+-----------------------------------------------------------------------------*/
+    long const ncolors = (1 << bpp);
+
+    unsigned int  nbyte;
+    unsigned int  i;
+
+    nbyte = 0;
+    for (i = 0; i < colorMapP->count; ++i)
+        nbyte += BMPwritergb(ifP, class,
+                             colorMapP->red[i],
+                             colorMapP->grn[i],
+                             colorMapP->blu[i]);
+
+    for (; i < ncolors; ++i)
+        nbyte += BMPwritergb(ifP, class, 0, 0, 0);
+
+    return nbyte;
+}
+
+
+
+static int
+BMPwriterow_palette(FILE *          const fp, 
+                    const pixel *   const row, 
+                    unsigned long   const cx, 
+                    unsigned short  const bpp, 
+                    colorhash_table const cht) {
+/*----------------------------------------------------------------------------
+  Return the number of bytes written, or -1 on error.
+-----------------------------------------------------------------------------*/
+    BITSTREAM    b;
+    int retval;
+    
+    b = pm_bitinit(fp, "w");
+    if (b == NULL)
+        retval = -1;
+    else {
+        unsigned int nbyte;
+        unsigned int x;
+        bool         error;
+        
+        nbyte = 0;      /* initial value */
+        error = FALSE;  /* initial value */
+        
+        for (x = 0; x < cx && !error; ++x) {
+            int rc;
+            rc = pm_bitwrite(b, bpp, ppm_lookupcolor(cht, &row[x]));
+            if (rc == -1)
+                error = TRUE;
+            else
+                nbyte += rc;
+        }
+        if (error)
+            retval = -1;
+        else {
+            int rc;
+
+            rc = pm_bitfini(b);
+            if (rc == -1)
+                retval = -1;
+            else {
+                nbyte += rc;
+                
+                /* Make sure we write a multiple of 4 bytes.  */
+                while (nbyte % 4 != 0) {
+                    PutByte(fp, 0);
+                    ++nbyte;
+                }
+                retval = nbyte;
+            }
+        }
+    }
+    return retval;
+}
+
+
+
+static int
+BMPwriterow_truecolor(FILE *        const fp, 
+                      const pixel * const row, 
+                      unsigned long const cols,
+                      pixval        const maxval) {
+/*----------------------------------------------------------------------------
+  Write a row of a truecolor BMP image to the file 'fp'.  The row is 
+  'row', which is 'cols' columns long.
+
+  Return the number of bytes written.
+
+  On error, issue error message and exit program.
+-----------------------------------------------------------------------------*/
+    /* This works only for 24 bits per pixel.  To implement this for the
+       general case (which is only hypothetical -- this program doesn't
+       write any truecolor images except 24 bit and apparently no one
+       else does either), you would move this function into 
+       BMPwriterow_palette, which writes arbitrary bit strings.  But
+       that would be a lot slower and less robust.
+    */
+
+    int nbyte;  /* Number of bytes we have written to file so far */
+    int col;  
+        
+    nbyte = 0;  /* initial value */
+    for (col = 0; col < cols; col++) {
+        /* We scale to the BMP maxval, which is always 255. */
+        PutByte(fp, PPM_GETB(row[col]) * 255 / maxval);
+        PutByte(fp, PPM_GETG(row[col]) * 255 / maxval);
+        PutByte(fp, PPM_GETR(row[col]) * 255 / maxval);
+        nbyte += 3;
+    }
+
+    /*
+     * Make sure we write a multiple of 4 bytes.
+     */
+    while (nbyte % 4) {
+        PutByte(fp, 0);
+        nbyte++;
+    }
+    
+    return nbyte;
+}
+
+
+
+static int
+BMPwritebits(FILE *          const fp, 
+             unsigned long   const cx, 
+             unsigned long   const cy, 
+             enum colortype  const colortype,
+             unsigned short  const cBitCount, 
+             const pixel **  const pixels, 
+             pixval          const maxval,
+             colorhash_table const cht) {
+/*----------------------------------------------------------------------------
+  Return the number of bytes written, or -1 on error.
+-----------------------------------------------------------------------------*/
+    int  nbyte;
+    long y;
+
+    if (cBitCount > 24)
+        pm_error("cannot handle cBitCount: %d", cBitCount);
+
+    nbyte = 0;  /* initial value */
+
+    /* The picture is stored bottom line first, top line last */
+
+    for (y = cy - 1; y >= 0; --y) {
+        int rc;
+        if (colortype == PALETTE)
+            rc = BMPwriterow_palette(fp, pixels[y], cx, 
+                                     cBitCount, cht);
+        else 
+            rc = BMPwriterow_truecolor(fp, pixels[y], cx, maxval);
+
+        if (rc == -1)
+            pm_error("couldn't write row %ld", y);
+        if (rc % 4 != 0)
+            pm_error("row had bad number of bytes: %d", rc);
+        nbyte += rc;
+    }
+
+    return nbyte;
+}
+
+
+
+static void
+BMPEncode(FILE *           const ifP, 
+          int              const class, 
+          enum colortype   const colortype,
+          int              const bpp,
+          int              const x, 
+          int              const y, 
+          const pixel **   const pixels, 
+          pixval           const maxval,
+          const colorMap * const colorMapP) {
+/*----------------------------------------------------------------------------
+  Write a BMP file of the given class.
+-----------------------------------------------------------------------------*/
+    unsigned long nbyte;
+
+    if (colortype == PALETTE)
+        pm_message("Writing %d bits per pixel with a color palette", bpp);
+    else
+        pm_message("Writing %d bits per pixel truecolor (no palette)", bpp);
+
+    nbyte = 0;  /* initial value */
+    nbyte += BMPwritefileheader(ifP, class, bpp, x, y);
+    nbyte += BMPwriteinfoheader(ifP, class, bpp, x, y);
+    if (colortype == PALETTE)
+        nbyte += BMPwritecolormap(ifP, class, bpp, colorMapP);
+
+    if (nbyte != (BMPlenfileheader(class)
+                  + BMPleninfoheader(class)
+                  + BMPlencolormap(class, bpp, -1)))
+        pm_error(er_internal, "BMPEncode 1");
+
+    nbyte += BMPwritebits(ifP, x, y, colortype, bpp, pixels, maxval,
+                          colorMapP->cht);
+    if (nbyte != BMPlenfile(class, bpp, -1, x, y))
+        pm_error(er_internal, "BMPEncode 2");
+}
+
+
+
+static void
+makeBilevelColorMap(colorMap * const colorMapP) {
+
+    colorMapP->count  = 2;
+    colorMapP->cht    = NULL;
+    colorMapP->red[0] = 0;
+    colorMapP->grn[0] = 0;
+    colorMapP->blu[0] = 0;
+    colorMapP->red[1] = 255;
+    colorMapP->grn[1] = 255;
+    colorMapP->blu[1] = 255;
+}
+
+
+
+static void
+BMPEncodePBM(FILE *           const ifP, 
+             int              const class, 
+             int              const cols, 
+             int              const rows, 
+             unsigned char ** const bitrow) {
+/*----------------------------------------------------------------------------
+  Write a bi-level BMP file of the given class.
+-----------------------------------------------------------------------------*/
+    /* Note:
+       Only PBM input uses this routine.  Color images represented by 1 bpp via
+       color palette use the general BMPEncode().
+    */
+    unsigned int const adjustedCols = (cols + 31) / 32 * 32;
+    unsigned int const packedBytes  = adjustedCols / 8;
+
+    unsigned long nbyte;
+    colorMap bilevelColorMap;
+    unsigned int row;
+    
+    /* colortype == PALETTE */
+    pm_message("Writing 1 bit per pixel with a black-white palette");
+
+    nbyte = 0;  /* initial value */
+    nbyte += BMPwritefileheader(ifP, class, 1, cols, rows);
+    nbyte += BMPwriteinfoheader(ifP, class, 1, cols, rows);
+
+    makeBilevelColorMap(&bilevelColorMap);
+
+    nbyte += BMPwritecolormap(ifP, class, 1, &bilevelColorMap);
+
+    if (nbyte != (BMPlenfileheader(class)
+                  + BMPleninfoheader(class)
+                  + BMPlencolormap(class, 1, -1)))
+        pm_error(er_internal, "BMPEncodePBM 1");
+   
+    for (row = 0; row < rows; ++row){
+        size_t bytesWritten;
+
+        bytesWritten = fwrite(bitrow[row], 1, packedBytes, ifP);
+        if (bytesWritten != packedBytes){
+            if (feof(ifP))
+                pm_error("End of file writing row %u of BMP raster.", row);
+            else 
+                pm_error("Error writing BMP raster.  Errno=%d (%s)",
+                         errno, strerror(errno));
+        }  else
+            nbyte += bytesWritten;
+    }
+
+    if (nbyte != BMPlenfile(class, 1, -1, cols, rows))
+        pm_error(er_internal, "BMPEncodePBM 2");
+}
+
+
+
+static void
+analyze_colors(const pixel **    const pixels, 
+               int               const cols, 
+               int               const rows, 
+               pixval            const maxval, 
+               int *             const minimum_bpp_p,
+               colorMap *        const colorMapP) {
+/*----------------------------------------------------------------------------
+  Look at the colors in the image 'pixels' and compute values to use in
+  representing those colors in a BMP image.  
+
+  First of all, count the distinct colors.  Return as *minimum_bpp_p
+  the minimum number of bits per pixel it will take to represent all
+  the colors in BMP format.
+
+  If there are few enough colors to represent with a palette, also
+  return the number of colors as *colors_p and a suitable palette
+  (colormap) and a hash table in which to look up indexes into that
+  palette as *colorMapP.  Use only the first *colors_p entries of the
+  map.
+
+  If there are too many colors for a palette, return colorMapP->cht
+  == NULL.
+
+  Issue informational messages.
+-----------------------------------------------------------------------------*/
+    /* Figure out the colormap. */
+    colorhist_vector chv;
+    int colorCount;
+
+    pm_message("analyzing colors...");
+    chv = ppm_computecolorhist((pixel**)pixels, cols, rows, MAXCOLORS, 
+                               &colorCount);
+    colorMapP->count = colorCount;
+    if (chv == NULL) {
+        pm_message("More than %u colors found", MAXCOLORS);
+        *minimum_bpp_p = 24;
+        colorMapP->cht = NULL;
+    } else {
+        unsigned int const minbits = pm_maxvaltobits(colorMapP->count - 1);
+
+        unsigned int i;
+
+        pm_message("%u colors found", colorMapP->count);
+
+        /* Only 1, 4, 8, and 24 are defined in the BMP spec we
+           implement and other bpp's have in fact been seen to confuse
+           viewers.  There is an extended BMP format that has 16 bpp
+           too, but this program doesn't know how to generate that
+           (see Bmptopnm.c, though).  
+        */
+        if (minbits == 1)
+            *minimum_bpp_p = 1;
+        else if (minbits <= 4)
+            *minimum_bpp_p = 4;
+        else if (minbits <= 8)
+            *minimum_bpp_p = 8;
+        else
+            *minimum_bpp_p = 24;
+
+        /*
+         * Now scale the maxval to 255 as required by BMP format.
+         */
+        for (i = 0; i < colorMapP->count; ++i) {
+            colorMapP->red[i] = (pixval) PPM_GETR(chv[i].color) * 255 / maxval;
+            colorMapP->grn[i] = (pixval) PPM_GETG(chv[i].color) * 255 / maxval;
+            colorMapP->blu[i] = (pixval) PPM_GETB(chv[i].color) * 255 / maxval;
+        }
+    
+        /* And make a hash table for fast lookup. */
+        colorMapP->cht = ppm_colorhisttocolorhash(chv, colorMapP->count);
+        ppm_freecolorhist(chv);
+    }
+}
+
+
+
+static void
+choose_colortype_bpp(struct cmdline_info const cmdline,
+                     unsigned int        const colors, 
+                     unsigned int        const minimum_bpp,
+                     enum colortype *    const colortype_p, 
+                     unsigned int *      const bits_per_pixel_p) {
+
+    if (!cmdline.bppSpec) {
+        /* User has no preference as to bits per pixel.  Choose the
+           smallest number possible for this image.
+        */
+        *bits_per_pixel_p = minimum_bpp;
+    } else {
+        if (cmdline.bpp < minimum_bpp)
+            pm_error("There are too many colors in the image to "
+                     "represent in the\n"
+                     "number of bits per pixel you requested: %d.\n"
+                     "You may use Ppmquant to reduce the number of "
+                     "colors in the image.",
+                     cmdline.bpp);
+        else
+            *bits_per_pixel_p = cmdline.bpp;
+    }
+
+    assert(*bits_per_pixel_p == 1 || 
+           *bits_per_pixel_p == 4 || 
+           *bits_per_pixel_p == 8 || 
+           *bits_per_pixel_p == 24);
+
+    if (*bits_per_pixel_p > 8) 
+        *colortype_p = TRUECOLOR;
+    else {
+        *colortype_p = PALETTE;
+    }
+}
+
+
+
+static void
+doPbm(FILE *       const ifP,
+      unsigned int const cols,
+      unsigned int const rows,
+      int          const format,
+      int          const class,
+      FILE *       const ofP) {
+    
+    /*  In the PBM case the raster is read directly from the input by 
+        pbm_readpbmrow_packed.  The raster format is almost identical,
+        except that BMP specifies rows to be zero-filled to 32 bit borders 
+        and that in BMP the bottom row comes first in order.
+    */
+
+    int const CHARBITS = (sizeof(unsigned char)*8); 
+    int const colChars = pbm_packed_bytes(cols);
+    int const adjustedCols = (cols+31) /32 * 32;
+    int const packedBytes  =  adjustedCols /8;
+
+    unsigned char ** bitrow;    
+    unsigned int row;
+
+    bitrow = pbm_allocarray_packed(adjustedCols, rows);
+
+    for (row = 0; row < rows; ++row) {
+        unsigned char * const thisRow = bitrow[rows - row - 1];
+
+        /* Clear end of each row */
+        thisRow[packedBytes-1] = 0x00;
+        thisRow[packedBytes-2] = 0x00;
+        thisRow[packedBytes-3] = 0x00;
+        thisRow[packedBytes-4] = 0x00;
+        
+        pbm_readpbmrow_packed(ifP, thisRow, cols, format);
+
+        {
+            unsigned int i;
+            for (i = 0; i < colChars; ++i) 
+                thisRow[i] = ~thisRow[i]; /* flip all pixels */
+        }
+        /* This may seem unnecessary, because the color palette 
+           (RGB[] in BMPEncodePBM) can be inverted for the same effect.
+           However we take this precaution, for there is indication that
+           some BMP viewers may get confused with that.
+        */
+
+        if (cols % 8 >0) {
+            /* adjust final partial byte */
+            thisRow[colChars-1] >>= CHARBITS - cols % CHARBITS;
+            thisRow[colChars-1] <<= CHARBITS - cols % CHARBITS;
+        }
+    }
+
+    BMPEncodePBM(ofP, class, cols, rows, bitrow);
+}            
+
+
+
+static void
+doPgmPpm(FILE * const ifP,
+         unsigned int const cols,
+         unsigned int const rows,
+         pixval       const maxval,
+         int          const ppmFormat,
+         int          const class,
+         FILE *       const ofP) {
+
+    /* PGM and PPM.  The input image is read into a PPM array, scanned
+       for color analysis and converted to a BMP raster.
+       Logic works for PBM.
+    */
+    int minimumBpp;
+    unsigned int bitsPerPixel;
+    enum colortype colortype;
+    unsigned int row;
+    
+    pixel ** pixels;
+    colorMap colorMap;
+    
+    pixels = ppm_allocarray(cols, rows);
+    
+    for (row = 0; row < rows; ++row)
+        ppm_readppmrow(ifP, pixels[row], cols, maxval, ppmFormat);
+    
+    analyze_colors((const pixel**)pixels, cols, rows, maxval, 
+                   &minimumBpp, &colorMap);
+    
+    choose_colortype_bpp(cmdline, colorMap.count, minimumBpp, &colortype, 
+                         &bitsPerPixel);
+    
+    BMPEncode(stdout, class, colortype, bitsPerPixel,
+              cols, rows, (const pixel**)pixels, maxval, &colorMap);
+    
+    freeColorMap(&colorMap);
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    FILE * ifP;
+    int rows;
+    int cols;
+    pixval maxval;
+    int ppmFormat;
+
+    ppm_init(&argc, argv);
+
+    parse_command_line(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.input_filename);
+    
+    ppm_readppminit(ifP, &cols, &rows, &maxval, &ppmFormat);
+    
+    if (PPM_FORMAT_TYPE(ppmFormat) == PBM_TYPE)
+        doPbm(ifP, cols, rows, ppmFormat, cmdline.class, stdout);
+    else
+        doPgmPpm(ifP, cols, rows, maxval, ppmFormat, cmdline.class, stdout);
+
+    pm_close(ifP);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/converter/ppm/ppmtoeyuv.c b/converter/ppm/ppmtoeyuv.c
new file mode 100644
index 00000000..f5ce1156
--- /dev/null
+++ b/converter/ppm/ppmtoeyuv.c
@@ -0,0 +1,396 @@
+/* Bryan got this from mm.ftp-cs.berkeley.edu from the package
+   mpeg-encode-1.5b-src under the name ppmtoeyuv.c on March 30, 2000.  
+   The file was dated January 19, 1995.  
+
+   Bryan changed the program to take an argument as the input filename
+   and fixed a crash when the input image has an odd number of rows or 
+   columns.
+
+   Then Bryan updated the program on March 15, 2001 to use the Netpbm
+   libraries to read the PPM input and handle multi-image PPM files
+   and arbitrary maxvals.
+
+   There was no attached documentation except for this:  Encoder/Berkeley
+   YUV format is merely the concatenation of Y, U, and V data in order.
+   Compare with Abekas YUV, which interlaces Y, U, and V data. 
+
+   Future enhancement: It may be useful to have an option to do the
+   calculations without multiplication tables to save memory at the
+   expense of execution speed for large maxvals.  Actually, a large
+   maxval without a lot of colors might actually make the tables
+   slower.
+
+*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/picasso/users/keving/encode/src/RCS/readframe.c,v 1.1 1993/07/22 22:23:43 keving Exp keving $
+ *  $Log: readframe.c,v $
+ * Revision 1.1  1993/07/22  22:23:43  keving
+ * nothing
+ *
+ */
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "mallocvar.h"
+
+typedef	unsigned char uint8;
+
+/* Multiplication tables */
+
+#define YUVMAXVAL 255
+#define HALFYUVMAXVAL 128
+/* multXXX are multiplication tables used in RGB-YCC calculations for 
+   speed.  mult299[x] is x * .299, scaled to a maxval of 255.  These
+   are malloc'ed and essentially constant.
+
+   We use these tables because it is much faster to do a
+   multiplication once for each possible sample value than once for
+   each pixel in the image.  
+*/
+static float *mult299, *mult587, *mult114;
+static float *mult16874, *mult33126, *mult5;
+static float *mult41869, *mult08131;
+
+static __inline__ float
+luminance(const pixel p) {
+    return mult299[PPM_GETR(p)]
+        + mult587[PPM_GETG(p)] 
+        + mult114[PPM_GETB(p)]
+        ;
+}
+
+static __inline__ float
+chrominance_red(const pixel p) {
+    return mult5[PPM_GETR(p)] 
+        + mult41869[PPM_GETG(p)]
+        + mult08131[PPM_GETB(p)]
+        ;
+}
+
+static __inline__ float
+chrominance_blue(const pixel p) {
+    return mult16874[PPM_GETR(p)] 
+        + mult33126[PPM_GETG(p)]
+        + mult5[PPM_GETB(p)]
+        ;
+}
+
+
+
+static void
+create_multiplication_tables(const pixval maxval) {
+
+    int index;
+
+    MALLOCARRAY_NOFAIL(mult299   , maxval+1);
+    MALLOCARRAY_NOFAIL(mult587   , maxval+1);
+    MALLOCARRAY_NOFAIL(mult114   , maxval+1);
+    MALLOCARRAY_NOFAIL(mult16874 , maxval+1);
+    MALLOCARRAY_NOFAIL(mult33126 , maxval+1);
+    MALLOCARRAY_NOFAIL(mult5     , maxval+1);
+    MALLOCARRAY_NOFAIL(mult41869 , maxval+1);
+    MALLOCARRAY_NOFAIL(mult08131 , maxval+1);
+
+    if (maxval == YUVMAXVAL) {
+        /* fast path */
+        for ( index = 0; index <= maxval; index++ ) {
+            mult299[index]   =  0.29900*index;
+            mult587[index]   =  0.58700*index;
+            mult114[index]   =  0.11400*index;
+            mult5[index]     =  0.50000*index;
+            mult41869[index] = -0.41869*index;
+            mult08131[index] = -0.08131*index;
+            mult16874[index] = -0.16874*index;
+            mult33126[index] = -0.33126*index;
+        }
+    } else {
+        for ( index = 0; index <= maxval; index++ ) {
+            mult299[index]   =  0.29900*index*(maxval/YUVMAXVAL);
+            mult587[index]   =  0.58700*index*(maxval/YUVMAXVAL);
+            mult114[index]   =  0.11400*index*(maxval/YUVMAXVAL);
+            mult5[index]     =  0.50000*index*(maxval/YUVMAXVAL);
+            mult41869[index] = -0.41869*index*(maxval/YUVMAXVAL);
+            mult08131[index] = -0.08131*index*(maxval/YUVMAXVAL);
+            mult16874[index] = -0.16874*index*(maxval/YUVMAXVAL);
+            mult33126[index] = -0.33126*index*(maxval/YUVMAXVAL);
+        }
+        
+    }
+}
+
+
+
+static void
+free_multiplication_tables(void) {
+    free(mult299   );
+    free(mult587   );
+    free(mult114   );
+    free(mult16874 );
+    free(mult33126 );
+    free(mult5     );
+    free(mult41869 );
+    free(mult08131 );
+}
+
+
+
+/*===========================================================================*
+ *
+ * PPMtoYUV
+ *
+ *	convert PPM data into YUV data
+ *	assumes that ydivisor = 1
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ * This function processes the input file in 4 pixel squares.  If the
+ * Image does not have an even number of rows and columns, the rightmost
+ * column or the bottom row gets ignored and output has one fewer row
+ * or column than the input. 
+ *
+ *===========================================================================*/
+static void 
+PPMtoYUV(pixel ** const ppm_image, const int width, const int height,
+         uint8 *** const orig_yP, 
+         uint8 *** const orig_crP, 
+         uint8 *** const orig_cbP) {
+
+    int y;
+    uint8 ** orig_y;
+    uint8 ** orig_cr;
+    uint8 ** orig_cb;
+
+    orig_y = *orig_yP;
+    orig_cr = *orig_crP;
+    orig_cb = *orig_cbP;
+
+    for (y = 0; y + 1 < height; y += 2) {
+        uint8 *dy0, *dy1;
+        uint8 *dcr, *dcb;
+        const pixel *src0, *src1;
+          /* Pair of contiguous rows of the ppm input image we are
+             converting */
+        int x;
+
+        src0 = ppm_image[y];
+        src1 = ppm_image[y + 1];
+
+        dy0 = orig_y[y];
+        dy1 = orig_y[y + 1];
+        dcr = orig_cr[y / 2];
+        dcb = orig_cb[y / 2];
+
+        for ( x = 0; x + 1 < width; x += 2) {
+            dy0[x] = luminance(src0[x]);
+            dy1[x] = luminance(src1[x]);
+
+            dy0[x+1] = luminance(src0[x+1]);
+            dy1[x+1] = luminance(src1[x+1]);
+
+            dcr[x/2] = ((
+                chrominance_red(src0[x]) +
+                chrominance_red(src1[x]) +
+                chrominance_red(src0[x+1]) +
+                chrominance_red(src1[x+1]) 
+                ) / 4) + HALFYUVMAXVAL;
+
+            dcb[x/2] = ((
+                chrominance_blue(src0[x]) +
+                chrominance_blue(src1[x]) +
+                chrominance_blue(src0[x+1]) +
+                chrominance_blue(src1[x+1]) 
+                ) / 4) + HALFYUVMAXVAL;
+        }
+    }
+}
+
+
+
+static void 
+WriteYUV(FILE *fpointer, const int width, const int height,
+         uint8 ** const orig_y, uint8 ** const orig_cr, uint8 ** const orig_cb)
+{
+    register int y;
+
+    for (y = 0; y < height; y++)                        /* Y */
+        fwrite(orig_y[y], 1, width, fpointer);
+
+    for (y = 0; y < height / 2; y++)                    /* U */
+        fwrite(orig_cb[y], 1, width / 2, fpointer);
+
+    for (y = 0; y < height / 2; y++)                    /* V */
+        fwrite(orig_cr[y], 1, width / 2, fpointer);
+}
+
+
+
+static void
+AllocYUV(int       const width, 
+         int       const height,
+         uint8 *** const orig_yP, 
+         uint8 *** const orig_crP,
+         uint8 *** const orig_cbP) {
+
+    int y;
+    uint8 ** orig_y;
+    uint8 ** orig_cr;
+    uint8 ** orig_cb;
+
+    MALLOCARRAY_NOFAIL(*orig_yP, height);
+    orig_y = *orig_yP;
+    for (y = 0; y < height; y++) 
+        MALLOCARRAY_NOFAIL(orig_y[y], width);
+
+    MALLOCARRAY_NOFAIL(*orig_crP, height / 2);
+    orig_cr = *orig_crP;
+    for (y = 0; y < height / 2; y++) 
+        MALLOCARRAY_NOFAIL(orig_cr[y], width / 2);
+
+    MALLOCARRAY_NOFAIL(*orig_cbP, height / 2);
+    orig_cb = *orig_cbP;
+    for (y = 0; y < height / 2; y++) 
+        MALLOCARRAY_NOFAIL(orig_cb[y], width / 2);
+}
+
+
+
+static void
+FreeYUV(const int width, const int height,
+        uint8 ** const orig_y, uint8 ** const orig_cr, uint8 ** const orig_cb){
+
+    int y;
+
+    if (orig_y) {
+       for (y = 0; y < height; y++)
+           free(orig_y[y]);
+       free(orig_y);
+    }
+
+    if (orig_cr) {
+       for (y = 0; y < height / 2; y++)
+           free(orig_cr[y]);
+       free(orig_cr);
+    }
+
+    if (orig_cb) {
+       for (y = 0; y < height / 2; y++)
+           free(orig_cb[y]);
+       free(orig_cb);
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+    const char *input_filename;  /* NULL for stdin */
+    FILE * ifp;
+    int width, height;
+    pixval maxval;
+    pixel **ppm_image;   /* malloc'ed */
+    uint8 **orig_y, **orig_cr, **orig_cb;
+        /* orig_y is the height x width array of individual pixel luminances 
+           orig_cr and orig_cb are the height/2 x width/2 arrays of average
+           red and blue chrominance values over each 4 pixel square.
+        */
+    int eof;
+
+    /* The following are width, height, and maxval of the image we 
+       processed before this one.  Zero if there was no image before 
+       this one.
+    */
+    int last_width, last_height;
+    pixval last_maxval;
+
+    ppm_init(&argc, argv);
+
+    if (argc > 2) {
+        pm_error("Program takes either one argument -- "
+                "the input filename -- or no arguments (input is stdin)");
+        exit(1);
+    } else if (argc == 2)
+        input_filename = argv[1];
+    else input_filename = NULL;
+
+    if (input_filename == NULL) ifp = stdin;
+    else ifp = pm_openr(input_filename);
+
+    eof = FALSE;
+    last_maxval = 0;  /* No previous maxval */
+    last_width = 0;	/* No previous width */
+    last_height = 0;	/* No previous height */
+    orig_y = orig_cr = orig_cb = 0;
+
+    while (!eof) {
+        ppm_image = ppm_readppm(ifp, &width, &height, &maxval);
+
+        if (width % 2 != 0) 
+            pm_message("Input image has odd number of columns.  The rightmost "
+                       "column will be omitted from the output.");
+        if (height % 2 != 0) 
+            pm_message("Input image has odd number of rows.  The bottom "
+                       "row will be omitted from the output.");
+
+        if (maxval != last_maxval) {
+            /* We're going to need all new multiplication tables. */
+            free_multiplication_tables();
+            create_multiplication_tables(maxval);
+        }
+        last_maxval = maxval;
+        
+        if (height != last_height || width != last_width) {
+            FreeYUV(width, height, orig_y, orig_cr, orig_cb);
+            /* Need new YUV buffers for different size */
+            AllocYUV(width, height, &orig_y, &orig_cr, &orig_cb);
+        }
+        last_height = height;
+        last_width = width;
+
+        PPMtoYUV(ppm_image, width, height, &orig_y, &orig_cr, &orig_cb);
+
+        WriteYUV(stdout, (width/2)*2, (height/2)*2, orig_y, orig_cr, orig_cb);
+
+        ppm_freearray(ppm_image, height);
+        ppm_nextimage(ifp, &eof);
+    }
+    FreeYUV(width, height, orig_y, orig_cr, orig_cb);
+    free_multiplication_tables();
+    pm_close(ifp);
+        
+    return 0;
+}
+
diff --git a/converter/ppm/ppmtogif.c b/converter/ppm/ppmtogif.c
new file mode 100644
index 00000000..9521237b
--- /dev/null
+++ b/converter/ppm/ppmtogif.c
@@ -0,0 +1,1681 @@
+/* ppmtogif.c - read a portable pixmap and produce a GIF file
+**
+** Based on GIFENCOD by David Rowley <mgardi@watdscu.waterloo.edu>.A
+** Lempel-Zim compression based on "compress".
+**
+** Modified by Marcel Wijkstra <wijkstra@fwi.uva.nl>
+**
+** The non-LZW GIF generation stuff was adapted from the Independent
+** JPEG Group's djpeg on 2001.09.29.  The uncompressed output subroutines
+** are derived directly from the corresponding subroutines in djpeg's
+** wrgif.c source file.  Its copyright notice say:
+
+ * Copyright (C) 1991-1997, Thomas G. Lane.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+   The reference README file is README.JPEG in the Netpbm package.
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** The Graphics Interchange Format(c) is the Copyright property of
+** CompuServe Incorporated.  GIF(sm) is a Service Mark property of
+** CompuServe Incorporated.
+*/
+
+/* TODO: merge the LZW and uncompressed subroutines.  They are separate
+   only because they had two different lineages and the code is too
+   complicated for me quickly to rewrite it.
+*/
+#include <assert.h>
+#include <string.h>
+
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "ppm.h"
+
+#define MAXCMAPSIZE 256
+
+static unsigned int const gifMaxval = 255;
+
+static bool verbose;
+/*
+ * a code_int must be able to hold 2**BITS values of type int, and also -1
+ */
+typedef int code_int;
+
+typedef long int          count_int;
+
+
+struct cmap {
+    /* This is the information for the GIF colormap (aka palette). */
+
+    int red[MAXCMAPSIZE], green[MAXCMAPSIZE], blue[MAXCMAPSIZE];
+        /* These arrays arrays map a color index, as is found in
+           the raster part of the GIF, to an intensity value for the indicated
+           RGB component.
+        */
+    int perm[MAXCMAPSIZE], permi[MAXCMAPSIZE];
+        /* perm[i] is the position in the sorted colormap of the color which
+           is at position i in the unsorted colormap.  permi[] is the inverse
+           function of perm[].
+        */
+    unsigned int cmapsize;
+        /* Number of entries in the GIF colormap.  I.e. number of colors
+           in the image, plus possibly one fake transparency color.
+        */
+    int transparent;
+        /* color index number in GIF palette of the color that is to be
+           transparent.  -1 if no color is transparent.
+        */
+    colorhash_table cht;
+        /* A hash table that relates a PPM pixel value to to a pre-sort
+           GIF colormap index.
+        */
+    pixval maxval;
+        /* The maxval for the colors in 'cht'. */
+};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec; /* Filespec of input file */
+    const char *alpha_filespec; /* Filespec of alpha file; NULL if none */
+    const char *alphacolor;     /* -alphacolor option value or default */
+    unsigned int interlace;     /* -interlace option value */
+    unsigned int sort;          /* -sort option value */
+    const char *mapfile;        /* -mapfile option value.  NULL if none. */
+    const char *transparent;    /* -transparent option value.  NULL if none. */
+    const char *comment;        /* -comment option value; NULL if none */
+    unsigned int nolzw;         /* -nolzw option */
+    unsigned int verbose;
+};
+
+
+static void
+handleLatex2htmlHack(void) {
+/*----------------------------------------------------------------------------
+  This program used to put out a "usage" message when it saw an option
+  it didn't understand.  Latex2html's configure program does a
+  ppmtogif -h (-h was never a valid option) to elicit that message and
+  then parses the message to see if it included the strings
+  "-interlace" and "-transparent".  That way it knows if the
+  'ppmtogif' program it found has those options or not.  I don't think
+  any 'ppmtogif' you're likely to find today lacks those options, but
+  latex2html checks anyway, and we don't want it to conclude that we
+  don't have them.
+
+  So we issue a special error message just to trick latex2html into
+  deciding that we have -interlace and -transparent options.  The function
+  is not documented in the man page.  We would like to see Latex2html 
+  either stop checking or check like configure programs usually do -- 
+  try the option and see if you get success or failure.
+
+  -Bryan 2001.11.14
+-----------------------------------------------------------------------------*/
+     pm_error("latex2html, you should just try the -interlace and "
+              "-transparent options to see if they work instead of "
+              "expecting a 'usage' message from -h");
+}
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Parse the program arguments (given by argc and argv) into a form
+   the program can deal with more easily -- a cmdline_info structure.
+   If the syntax is invalid, issue a message and exit the program via
+   pm_error().
+
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;  /* malloc'ed */
+    optStruct3 opt;  /* set by OPTENT3 */
+    unsigned int option_def_index;
+
+    unsigned int latex2htmlhack;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "interlace",   OPT_FLAG,   
+            NULL,                       &cmdlineP->interlace, 0);
+    OPTENT3(0,   "sort",        OPT_FLAG,   
+            NULL,                       &cmdlineP->sort, 0);
+    OPTENT3(0,   "nolzw",       OPT_FLAG,   
+            NULL,                       &cmdlineP->nolzw, 0);
+    OPTENT3(0,   "mapfile",     OPT_STRING, 
+            &cmdlineP->mapfile,        NULL, 0);
+    OPTENT3(0,   "transparent", OPT_STRING, 
+            &cmdlineP->transparent,    NULL, 0);
+    OPTENT3(0,   "comment",     OPT_STRING, 
+            &cmdlineP->comment,        NULL, 0);
+    OPTENT3(0,   "alpha",       OPT_STRING, 
+            &cmdlineP->alpha_filespec, NULL, 0);
+    OPTENT3(0,   "alphacolor",  OPT_STRING, 
+            &cmdlineP->alphacolor,     NULL, 0);
+    OPTENT3(0,   "h",           OPT_FLAG, 
+            NULL,                       &latex2htmlhack, 0);
+    OPTENT3(0,   "verbose",     OPT_FLAG, 
+            NULL,                       &cmdlineP->verbose, 0);
+    
+    /* Set the defaults */
+    cmdlineP->mapfile = NULL;
+    cmdlineP->transparent = NULL;  /* no transparency */
+    cmdlineP->comment = NULL;      /* no comment */
+    cmdlineP->alpha_filespec = NULL;      /* no alpha file */
+    cmdlineP->alphacolor = "rgb:0/0/0";      
+        /* We could say "black" here, but then we depend on the color names
+           database existing.
+        */
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (latex2htmlhack) 
+        handleLatex2htmlHack();
+
+    if (argc-1 == 0) 
+        cmdlineP->input_filespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->input_filespec = argv[1];
+
+    if (cmdlineP->alpha_filespec && cmdlineP->transparent)
+        pm_error("You cannot specify both -alpha and -transparent.");
+}
+
+
+
+/*
+ * Write out a word to the GIF file
+ */
+static void
+Putword(int const w, FILE * const fp) {
+
+    fputc( w & 0xff, fp );
+    fputc( (w / 256) & 0xff, fp );
+}
+
+
+static int
+closestcolor(pixel         const color,
+             pixval        const maxval,
+             struct cmap * const cmapP) {
+/*----------------------------------------------------------------------------
+   Return the pre-sort colormap index of the color in the colormap *cmapP
+   that is closest to the color 'color', whose maxval is 'maxval'.
+
+   Also add 'color' to the colormap hash, with the colormap index we
+   are returning.  Caller must ensure that the color is not already in
+   there.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+    unsigned int imin, dmin;
+
+    pixval const r = PPM_GETR(color) * gifMaxval / maxval;
+    pixval const g = PPM_GETG(color) * gifMaxval / maxval;
+    pixval const b = PPM_GETB(color) * gifMaxval / maxval;
+
+    dmin = SQR(255) * 3;
+    imin = 0;
+    for (i=0;i < cmapP->cmapsize; i++) {
+        int const d = SQR(r-cmapP->red[i]) + 
+            SQR(g-cmapP->green[i]) + 
+            SQR(b-cmapP->blue[i]);
+        if (d < dmin) {
+            dmin = d;
+            imin = i; 
+        } 
+    }
+    ppm_addtocolorhash(cmapP->cht, &color, cmapP->permi[imin]);
+
+    return cmapP->permi[imin];
+}
+
+
+
+enum pass {MULT8PLUS0, MULT8PLUS4, MULT4PLUS2, MULT2PLUS1};
+
+
+typedef struct {
+    FILE * fileP;
+        /* The PPM file stream from which pixels come.  The position
+           of this file is also part of the state of this pixelReader.
+        */
+    unsigned int width;
+        /* Width of the image, in columns */
+    unsigned int height;
+        /* Height of the image, in rows */
+    pixval maxval;
+    int format;
+    pm_filepos rasterPos;
+        /* Position in file fileP of the start of the raster */
+    bool interlace;
+        /* We're accessing the image in interlace fashion */
+    unsigned int nPixelsLeft;
+        /* Number of pixels we have left to read in the image */
+    pm_pixelcoord next;
+        /* Location of next pixel to read */
+    enum pass pass;
+        /* The interlace pass.  Undefined if !interlace */
+    pixel * curPixelRow;
+        /* The pixels of the current row (the one numbered in with pixel
+           'next' resides).
+           Dynamically allocated.
+        */
+} pixelReader;
+
+
+
+static void
+pixelReaderReadCurrentRow(pixelReader * const rdrP) {
+
+    ppm_readppmrow(rdrP->fileP, rdrP->curPixelRow,
+                   rdrP->width, rdrP->maxval, rdrP->format);
+}
+
+
+
+static void
+pixelReaderCreate(FILE *         const ifP,
+                  unsigned int   const width,
+                  unsigned int   const height,
+                  pixval         const maxval,
+                  int            const format,
+                  pm_filepos     const rasterPos,
+                  bool           const interlace,
+                  pixelReader ** const pixelReaderPP) {
+
+    pixelReader * rdrP;
+
+    MALLOCVAR_NOFAIL(rdrP);
+
+    rdrP->fileP       = ifP;
+    rdrP->width       = width;
+    rdrP->height      = height;
+    rdrP->maxval      = maxval;
+    rdrP->format      = format;
+    rdrP->rasterPos   = rasterPos;
+    rdrP->interlace   = interlace;
+    rdrP->pass        = MULT8PLUS0;
+    rdrP->next.col    = 0;
+    rdrP->next.row    = 0;
+    rdrP->nPixelsLeft = width * height;
+
+    rdrP->curPixelRow = ppm_allocrow(width);
+
+    pm_seek2(rdrP->fileP, &rasterPos, sizeof(rasterPos));
+
+    pixelReaderReadCurrentRow(rdrP);
+
+    *pixelReaderPP = rdrP;
+}
+
+
+
+static void
+pixelReaderDestroy(pixelReader * const pixelReaderP) {
+
+    ppm_freerow(pixelReaderP->curPixelRow);
+
+    free(pixelReaderP);
+}
+
+
+
+static size_t
+bytesPerSample(pixval const maxval) {
+
+    return maxval < (1 << 16) ? 1 : 2;
+}
+
+
+
+/* TODO - move this to libnetpbm */
+
+
+
+void
+ppm_seek(FILE *             const fileP,
+         const pm_filepos * const rasterPosP,
+         size_t             const rasterPosSize,
+         unsigned int       const cols,
+         pixval             const maxval,
+         unsigned int       const col,
+         unsigned int       const row);
+
+void
+ppm_seek(FILE *             const fileP,
+         const pm_filepos * const rasterPosP,
+         size_t             const rasterPosSize,
+         unsigned int       const cols,
+         pixval             const maxval,
+         unsigned int       const col,
+         unsigned int       const row) {
+
+    pm_filepos rasterPos;
+    pm_filepos pixelPos;
+
+    if (rasterPosSize == sizeof(pm_filepos)) 
+        rasterPos = *rasterPosP;
+    else if (rasterPosSize == sizeof(long))
+        rasterPos = *(long*)rasterPosP;
+    else
+        pm_error("File position size passed to ppm_seek() is invalid: %u.  "
+                 "Valid sizes are %u and %u", 
+                 rasterPosSize, sizeof(pm_filepos), sizeof(long));
+
+    pixelPos = rasterPos + row * cols * bytesPerSample(maxval) + col;
+
+    pm_seek2(fileP, &pixelPos, sizeof(pixelPos));
+}
+
+
+
+
+static void
+pixelReaderGotoNextInterlaceRow(pixelReader * const rdrP) {
+/*----------------------------------------------------------------------------
+  Position reader to the next row in the interlace pattern.
+
+  Assume there is at least one more row to read.
+-----------------------------------------------------------------------------*/
+    assert(rdrP->nPixelsLeft >= rdrP->width);
+
+    /* There are 4 passes:
+       MULT8PLUS0: Rows 0, 8, 16, 24, 32, etc.
+       MULT8PLUS4: Rows 4, 12, 20, 28, etc.
+       MULT4PLUS2: Rows 2, 6, 10, 14, etc.
+       MULT2PLUS1: Rows 1, 3, 5, 7, 9, etc.
+    */
+    
+    switch (rdrP->pass) {
+    case MULT8PLUS0:
+        rdrP->next.row += 8;
+        break;
+    case MULT8PLUS4:
+        rdrP->next.row += 8;
+        break;
+    case MULT4PLUS2:
+        rdrP->next.row += 4;
+        break;
+    case MULT2PLUS1:
+        rdrP->next.row += 2;
+        break;
+    }
+
+    /* If we've finished a pass, next.row is now beyond the end of
+       the image.  In that case, we switch to the next pass now.
+
+       Note that if there are more than 4 rows, the sequence of passes
+       is sequential, but when there are fewer than 4, we may skip
+       e.g. from MULT8PLUS0 to MULT4PLUS2.
+    */
+    while (rdrP->next.row >= rdrP->height) {
+        switch (rdrP->pass) {
+        case MULT8PLUS0:
+            rdrP->pass = MULT8PLUS4;
+            rdrP->next.row = 4;
+            break;
+        case MULT8PLUS4:
+            rdrP->pass = MULT4PLUS2;
+            rdrP->next.row = 2;
+            break;
+        case MULT4PLUS2:
+            rdrP->pass = MULT2PLUS1;
+            rdrP->next.row = 1;
+            break;
+        case MULT2PLUS1:
+            /* An entry condition is that there be a row left to read,
+               but we have finished the last pass.  That can't be:
+            */
+            assert(false);
+            break;
+        }
+    }
+    /* Now that we know which row should be current, get its
+       pixels into the buffer.
+    */
+    ppm_seek(rdrP->fileP, &rdrP->rasterPos, sizeof(rdrP->rasterPos),
+             rdrP->width, rdrP->maxval, 0, rdrP->next.row);
+}
+
+
+
+static void
+pixelReaderRead(pixelReader * const rdrP,
+                pixel *       const pixelP,
+                bool *        const eofP) {
+
+    if (rdrP->nPixelsLeft == 0)
+        *eofP = TRUE;
+    else {
+        *eofP = FALSE;
+
+        *pixelP = rdrP->curPixelRow[rdrP->next.col];
+
+        --rdrP->nPixelsLeft;
+
+        /* Move one column to the right */
+        ++rdrP->next.col;
+
+        if (rdrP->next.col >= rdrP->width) {
+            /* That pushed us past the end of a row. */
+            if (rdrP->nPixelsLeft > 0) {
+                /* Reset to the left edge ... */
+                rdrP->next.col = 0;
+                
+                /* ... of the next row */
+                if (!rdrP->interlace)
+                    ++rdrP->next.row;
+                else
+                    pixelReaderGotoNextInterlaceRow(rdrP);
+                
+                pixelReaderReadCurrentRow(rdrP);
+            }
+        }
+    }
+}
+
+
+
+static pm_pixelcoord
+pixelReaderNextCoord(pixelReader * const pixelReaderP) {
+
+    return pixelReaderP->next;
+}
+
+
+
+static void
+gifNextPixel(pixelReader *      const pixelReaderP,
+             pixval             const inputMaxval,
+             gray **            const alpha,
+             gray               const alphaThreshold, 
+             struct cmap *      const cmapP,
+             unsigned int *     const colorIndexP,
+             bool *             const eofP) {
+/*----------------------------------------------------------------------------
+   Return as *colorIndexP the colormap index of the next pixel supplied by
+   pixel reader 'pixelReaderP', using colormap *cmapP.
+
+   Iff the reader is at the end of the image, return *eofP == TRUE
+   and nothing as *colorIndexP.
+
+   'alphaThreshold' is the gray level such that a pixel in the alpha
+   map whose value is less that that represents a transparent pixel
+   in the output.
+-----------------------------------------------------------------------------*/
+    pm_pixelcoord const coord = pixelReaderNextCoord(pixelReaderP);
+
+    pixel pixel;
+
+    pixelReaderRead(pixelReaderP, &pixel, eofP);
+    if (!*eofP) {
+        int colorindex;
+
+        if (alpha && alpha[coord.row][coord.col] < alphaThreshold)
+            colorindex = cmapP->transparent;
+        else {
+            int presortColorindex;
+            
+            presortColorindex = ppm_lookupcolor(cmapP->cht, &pixel);
+            if (presortColorindex == -1)
+                presortColorindex = closestcolor(pixel, inputMaxval, cmapP);
+            colorindex = cmapP->perm[presortColorindex];
+        }
+        *colorIndexP = colorindex;
+    }
+}
+
+
+
+static void
+write_transparent_color_index_extension(FILE *fp, const int Transparent) {
+/*----------------------------------------------------------------------------
+   Write out extension for transparent color index.
+-----------------------------------------------------------------------------*/
+
+    fputc( '!', fp );
+    fputc( 0xf9, fp );
+    fputc( 4, fp );
+    fputc( 1, fp );
+    fputc( 0, fp );
+    fputc( 0, fp );
+    fputc( Transparent, fp );
+    fputc( 0, fp );
+}
+
+
+
+static void
+write_comment_extension(FILE *fp, const char comment[]) {
+/*----------------------------------------------------------------------------
+   Write out extension for a comment
+-----------------------------------------------------------------------------*/
+    char *segment;
+    
+    fputc('!', fp);   /* Identifies an extension */
+    fputc(0xfe, fp);  /* Identifies a comment */
+
+    /* Write it out in segments no longer than 255 characters */
+    for (segment = (char *) comment; 
+         segment < comment+strlen(comment); 
+         segment += 255) {
+
+        const int length_this_segment = MIN(255, strlen(segment));
+
+        fputc(length_this_segment, fp);
+
+        fwrite(segment, 1, length_this_segment, fp);
+    }
+
+    fputc(0, fp);   /* No more comment blocks in this extension */
+}
+
+
+
+/***************************************************************************
+ *
+ *  GIFCOMPR.C       - GIF Image compression routines
+ *
+ *  Lempel-Ziv compression based on 'compress'.  GIF modifications by
+ *  David Rowley (mgardi@watdcsu.waterloo.edu)
+ *
+ ***************************************************************************/
+
+/*
+ * General DEFINEs
+ */
+
+#define BITS    12
+
+#define HSIZE  5003            /* 80% occupancy */
+
+#ifdef NO_UCHAR
+ typedef char   char_type;
+#else /*NO_UCHAR*/
+ typedef        unsigned char   char_type;
+#endif /*NO_UCHAR*/
+
+/*
+ *
+ * GIF Image compression - modified 'compress'
+ *
+ * Based on: compress.c - File compression ala IEEE Computer, June 1984.
+ *
+ * By Authors:  Spencer W. Thomas       (decvax!harpo!utah-cs!utah-gr!thomas)
+ *              Jim McKie               (decvax!mcvax!jim)
+ *              Steve Davies            (decvax!vax135!petsd!peora!srd)
+ *              Ken Turkowski           (decvax!decwrl!turtlevax!ken)
+ *              James A. Woods          (decvax!ihnp4!ames!jaw)
+ *              Joe Orost               (decvax!vax135!petsd!joe)
+ *
+ */
+#include <ctype.h>
+
+#define ARGVAL() (*++(*argv) || (--argc && *++argv))
+
+static code_int const maxmaxcode = (code_int)1 << BITS;
+    /* should NEVER generate this code */
+#define MAXCODE(n_bits)        (((code_int) 1 << (n_bits)) - 1)
+
+static long htab [HSIZE];
+static unsigned short codetab [HSIZE];
+#define HashTabOf(i)       htab[i]
+#define CodeTabOf(i)    codetab[i]
+
+/*
+ * To save much memory, we overlay the table used by compress() with those
+ * used by decompress().  The tab_prefix table is the same size and type
+ * as the codetab.  The tab_suffix table needs 2**BITS characters.  We
+ * get this from the beginning of htab.  The output stack uses the rest
+ * of htab, and contains characters.  There is plenty of room for any
+ * possible stack (stack used to be 8000 characters).
+ */
+
+#define tab_prefixof(i) CodeTabOf(i)
+#define tab_suffixof(i)        ((char_type*)(htab))[i]
+#define de_stack               ((char_type*)&tab_suffixof((code_int)1<<BITS))
+
+static code_int free_ent = 0;                  /* first unused entry */
+
+/*
+ * block compression parameters -- after all codes are used up,
+ * and compression rate changes, start over.
+ */
+static int clear_flg = 0;
+
+static int offset;
+static long int in_count = 1;            /* length of input */
+static long int out_count = 0;           /* # of codes output (for debugging) */
+
+/*
+ * compress stdin to stdout
+ *
+ * Algorithm:  use open addressing double hashing (no chaining) on the
+ * prefix code / next character combination.  We do a variant of Knuth's
+ * algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
+ * secondary probe.  Here, the modular division first probe is gives way
+ * to a faster exclusive-or manipulation.  Also do block compression with
+ * an adaptive reset, whereby the code table is cleared when the compression
+ * ratio decreases, but after the table fills.  The variable-length output
+ * codes are re-sized at this point, and a special CLEAR code is generated
+ * for the decompressor.  Late addition:  construct the table according to
+ * file size for noticeable speed improvement on small files.  Please direct
+ * questions about this implementation to ames!jaw.
+ */
+
+static int ClearCode;
+static int EOFCode;
+
+/***************************************************************************
+*                          BYTE OUTPUTTER                 
+***************************************************************************/
+
+typedef struct {
+    FILE * fileP;  /* The file to which to output */
+    unsigned int count;
+        /* Number of bytes so far in the current data block */
+    unsigned char buffer[256];
+        /* The current data block, under construction */
+} byteBuffer;
+
+
+
+static byteBuffer *
+byteBuffer_create(FILE * const fileP) {
+
+    byteBuffer * byteBufferP;
+
+    MALLOCVAR_NOFAIL(byteBufferP);
+
+    byteBufferP->fileP = fileP;
+    byteBufferP->count = 0;
+
+    return byteBufferP;
+}
+
+
+
+static void
+byteBuffer_destroy(byteBuffer * const byteBufferP) {
+
+    free(byteBufferP);
+}
+
+
+
+static void
+byteBuffer_flush(byteBuffer * const byteBufferP) {
+/*----------------------------------------------------------------------------
+   Write the current data block to the output file, then reset the current 
+   data block to empty.
+-----------------------------------------------------------------------------*/
+    if (byteBufferP->count > 0 ) {
+        if (verbose)
+            pm_message("Writing %u byte block", byteBufferP->count);
+        fputc(byteBufferP->count, byteBufferP->fileP);
+        fwrite(byteBufferP->buffer, 1, byteBufferP->count, byteBufferP->fileP);
+        byteBufferP->count = 0;
+    }
+}
+
+
+
+static void
+byteBuffer_flushFile(byteBuffer * const byteBufferP) {
+    
+    fflush(byteBufferP->fileP);
+    
+    if (ferror(byteBufferP->fileP))
+        pm_error("error writing output file");
+}
+
+
+
+static void
+byteBuffer_out(byteBuffer *  const byteBufferP,
+               unsigned char const c) {
+/*----------------------------------------------------------------------------
+  Add a byte to the end of the current data block, and if it is now 254
+  characters, flush the data block to the output file.
+-----------------------------------------------------------------------------*/
+    byteBufferP->buffer[byteBufferP->count++] = c;
+    if (byteBufferP->count >= 254)
+        byteBuffer_flush(byteBufferP);
+}
+
+
+
+struct gif_dest {
+    /* This structure controls output of uncompressed GIF raster */
+
+    byteBuffer * byteBufferP;  /* Where the full bytes go */
+
+    /* State for packing variable-width codes into a bitstream */
+    int n_bits;         /* current number of bits/code */
+    int maxcode;        /* maximum code, given n_bits */
+    int cur_accum;      /* holds bits not yet output */
+    int cur_bits;       /* # of bits in cur_accum */
+
+    /* State for GIF code assignment */
+    int ClearCode;      /* clear code (doesn't change) */
+    int EOFCode;        /* EOF code (ditto) */
+    int code_counter;   /* counts output symbols */
+};
+
+
+
+static unsigned long const masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F,
+                                       0x001F, 0x003F, 0x007F, 0x00FF,
+                                       0x01FF, 0x03FF, 0x07FF, 0x0FFF,
+                                       0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };
+
+typedef struct {
+    byteBuffer * byteBufferP;
+    unsigned int initBits;
+    unsigned int n_bits;                        /* number of bits/code */
+    code_int maxcode;                  /* maximum code, given n_bits */
+    unsigned long curAccum;
+    int curBits;
+} codeBuffer;
+
+
+
+static codeBuffer *
+codeBuffer_create(FILE *       const ofP,
+                  unsigned int const initBits) {
+
+    codeBuffer * codeBufferP;
+
+    MALLOCVAR_NOFAIL(codeBufferP);
+
+    codeBufferP->initBits    = initBits;
+    codeBufferP->n_bits      = codeBufferP->initBits;
+    codeBufferP->maxcode     = MAXCODE(codeBufferP->n_bits);
+    codeBufferP->byteBufferP = byteBuffer_create(ofP);
+    codeBufferP->curAccum    = 0;
+    codeBufferP->curBits     = 0;
+
+    return codeBufferP;
+}
+
+
+
+static void
+codeBuffer_destroy(codeBuffer * const codeBufferP) {
+
+    byteBuffer_destroy(codeBufferP->byteBufferP);
+
+    free(codeBufferP);
+}
+
+
+
+static void
+codeBuffer_output(codeBuffer * const codeBufferP,
+                  code_int     const code) {
+/*----------------------------------------------------------------------------
+   Output one GIF code to the file, through the code buffer.
+
+   The code is represented as n_bits bits in the file -- the lower
+   n_bits bits of 'code'.
+
+   If the code is EOF, flush the code buffer to the file.
+
+   In some cases, change n_bits and recalculate maxcode to go with it.
+-----------------------------------------------------------------------------*/
+    /*
+      Algorithm:
+      Maintain a BITS character long buffer (so that 8 codes will
+      fit in it exactly).  Use the VAX insv instruction to insert each
+      code in turn.  When the buffer fills up empty it and start over.
+    */
+    
+    codeBufferP->curAccum &= masks[codeBufferP->curBits];
+
+    if (codeBufferP->curBits > 0)
+        codeBufferP->curAccum |= ((long)code << codeBufferP->curBits);
+    else
+        codeBufferP->curAccum = code;
+
+    codeBufferP->curBits += codeBufferP->n_bits;
+
+    while (codeBufferP->curBits >= 8) {
+        byteBuffer_out(codeBufferP->byteBufferP,
+                       codeBufferP->curAccum & 0xff);
+        codeBufferP->curAccum >>= 8;
+        codeBufferP->curBits -= 8;
+    }
+
+    if (clear_flg) {
+        codeBufferP->n_bits = codeBufferP->initBits;
+        codeBufferP->maxcode = MAXCODE(codeBufferP->n_bits);
+        clear_flg = 0;
+    } else if (free_ent > codeBufferP->maxcode) {
+        /* The next entry is going to be too big for the code size, so
+           increase it, if possible.
+        */
+        ++codeBufferP->n_bits;
+        if (codeBufferP->n_bits == BITS)
+            codeBufferP->maxcode = maxmaxcode;
+        else
+            codeBufferP->maxcode = MAXCODE(codeBufferP->n_bits);
+    }
+    
+    if (code == EOFCode) {
+        /* We're at EOF.  Output the possible partial byte in the buffer */
+        if (codeBufferP->curBits > 0) {
+            byteBuffer_out(codeBufferP->byteBufferP,
+                           codeBufferP->curAccum & 0xff);
+            codeBufferP->curBits = 0;
+        }
+        byteBuffer_flush(codeBufferP->byteBufferP);
+        
+        byteBuffer_flushFile(codeBufferP->byteBufferP);
+    }
+}
+
+
+
+static void
+cl_hash(long const hsize) {
+    /* reset code table */
+
+    long const m1 = -1;
+
+    long * htab_p;
+    long i;
+
+    htab_p = htab + hsize;  /* initial value */
+
+    i = hsize - 16;
+    do {                            /* might use Sys V memset(3) here */
+        *(htab_p-16) = m1;
+        *(htab_p-15) = m1;
+        *(htab_p-14) = m1;
+        *(htab_p-13) = m1;
+        *(htab_p-12) = m1;
+        *(htab_p-11) = m1;
+        *(htab_p-10) = m1;
+        *(htab_p-9) = m1;
+        *(htab_p-8) = m1;
+        *(htab_p-7) = m1;
+        *(htab_p-6) = m1;
+        *(htab_p-5) = m1;
+        *(htab_p-4) = m1;
+        *(htab_p-3) = m1;
+        *(htab_p-2) = m1;
+        *(htab_p-1) = m1;
+        htab_p -= 16;
+    } while ((i -= 16) >= 0);
+
+    for (i += 16; i > 0; --i)
+        *--htab_p = m1;
+}
+
+
+
+static void
+cl_block(codeBuffer * const codeBufferP) {
+/*----------------------------------------------------------------------------
+  Clear out the hash table
+-----------------------------------------------------------------------------*/
+    cl_hash(HSIZE);
+    free_ent = ClearCode + 2;
+    clear_flg = 1;
+    
+    codeBuffer_output(codeBufferP, (code_int)ClearCode);
+}
+
+
+
+static void
+writeRasterLzw(pixelReader * const pixelReaderP,
+               pixval        const inputMaxval,
+               gray **       const alpha,
+               gray          const alphaMaxval, 
+               struct cmap * const cmapP, 
+               int           const initBits,
+               FILE *        const ofP) {
+/*----------------------------------------------------------------------------
+   Write the raster to file 'ofP'.
+
+   The raster to write is 'pixels', which has maxval 'inputMaxval',
+   modified by alpha mask 'alpha', which has maxval 'alphaMaxval'.
+
+   Use the colormap 'cmapP' to generate the raster ('pixels' is 
+   composed of RGB samples; the GIF raster is colormap indices).
+
+   Write the raster using LZW compression.
+-----------------------------------------------------------------------------*/
+    gray const alpha_threshold = (alphaMaxval + 1) / 2;
+        /* gray levels below this in the alpha mask indicate transparent
+           pixels in the output image.
+        */
+    code_int ent;
+    code_int disp;
+    int hshift;
+    bool eof;
+    codeBuffer * codeBufferP;
+    unsigned int colorIndex;
+    
+    codeBufferP = codeBuffer_create(ofP, initBits);
+    
+    /*
+     * Set up the necessary values
+     */
+    offset = 0;
+    out_count = 0;
+    clear_flg = 0;
+    in_count = 1;
+
+    ClearCode = (1 << (initBits - 1));
+    EOFCode = ClearCode + 1;
+    free_ent = ClearCode + 2;
+
+    gifNextPixel(pixelReaderP, inputMaxval, alpha, alpha_threshold, cmapP,
+                 &colorIndex, &eof);
+    ent = colorIndex;
+
+    {
+        long fcode;
+        hshift = 0;
+        for (fcode = HSIZE; fcode < 65536L; fcode *= 2L)
+            ++hshift;
+        hshift = 8 - hshift;                /* set hash code range bound */
+    }
+    cl_hash(HSIZE);            /* clear hash table */
+
+    codeBuffer_output(codeBufferP, (code_int)ClearCode);
+
+    while (!eof) {
+        unsigned int gifpixel;
+            /* The value for the pixel in the GIF image.  I.e. the colormap
+               index.
+            */
+        gifNextPixel(pixelReaderP, inputMaxval, alpha, alpha_threshold, cmapP,
+                     &gifpixel, &eof);
+        if (!eof) {
+            long const fcode = (long) (((long) gifpixel << BITS) + ent);
+            code_int i;
+                /* xor hashing */
+
+            ++in_count;
+
+            i = (((code_int)gifpixel << hshift) ^ ent);    
+
+            if (HashTabOf (i) == fcode) {
+                ent = CodeTabOf (i);
+                continue;
+            } else if ((long)HashTabOf(i) < 0)      /* empty slot */
+                goto nomatch;
+            disp = HSIZE - i;        /* secondary hash (after G. Knott) */
+            if (i == 0)
+                disp = 1;
+        probe:
+            if ((i -= disp) < 0)
+                i += HSIZE;
+
+            if (HashTabOf(i) == fcode) {
+                ent = CodeTabOf(i);
+                continue;
+            }
+            if ((long)HashTabOf(i) > 0)
+                goto probe;
+        nomatch:
+            codeBuffer_output(codeBufferP, (code_int)ent);
+            ++out_count;
+            ent = gifpixel;
+            if (free_ent < maxmaxcode) {
+                CodeTabOf(i) = free_ent++; /* code -> hashtable */
+                HashTabOf(i) = fcode;
+            } else
+                cl_block(codeBufferP);
+        }
+    }
+    /* Put out the final code. */
+    codeBuffer_output(codeBufferP, (code_int)ent);
+    ++out_count;
+    codeBuffer_output(codeBufferP, (code_int) EOFCode);
+
+    codeBuffer_destroy(codeBufferP);
+}
+
+
+
+/* Routine to convert variable-width codes into a byte stream */
+
+static void
+outputUncompressed(struct gif_dest * const dinfoP,
+                   int               const code) {
+
+    /* Emit a code of n_bits bits */
+    /* Uses cur_accum and cur_bits to reblock into 8-bit bytes */
+    dinfoP->cur_accum |= ((int) code) << dinfoP->cur_bits;
+    dinfoP->cur_bits += dinfoP->n_bits;
+
+    while (dinfoP->cur_bits >= 8) {
+        byteBuffer_out(dinfoP->byteBufferP, dinfoP->cur_accum & 0xFF);
+        dinfoP->cur_accum >>= 8;
+        dinfoP->cur_bits -= 8;
+    }
+}
+
+
+static void
+writeRasterUncompressedInit(FILE *            const ofP,
+                            struct gif_dest * const dinfoP, 
+                            int               const i_bits) {
+/*----------------------------------------------------------------------------
+   Initialize pseudo-compressor
+-----------------------------------------------------------------------------*/
+
+    /* init all the state variables */
+    dinfoP->n_bits = i_bits;
+    dinfoP->maxcode = MAXCODE(dinfoP->n_bits);
+    dinfoP->ClearCode = (1 << (i_bits - 1));
+    dinfoP->EOFCode = dinfoP->ClearCode + 1;
+    dinfoP->code_counter = dinfoP->ClearCode + 2;
+    /* init output buffering vars */
+    dinfoP->byteBufferP = byteBuffer_create(ofP);
+    dinfoP->cur_accum = 0;
+    dinfoP->cur_bits = 0;
+    /* GIF specifies an initial Clear code */
+    outputUncompressed(dinfoP, dinfoP->ClearCode);
+}
+
+
+
+static void
+writeRasterUncompressedPixel(struct gif_dest * const dinfoP, 
+                             unsigned int      const colormapIndex) {
+/*----------------------------------------------------------------------------
+   "Compress" one pixel value and output it as a symbol.
+
+   'colormapIndex' must be less than dinfoP->n_bits wide.
+-----------------------------------------------------------------------------*/
+    assert(colormapIndex >> dinfoP->n_bits == 0);
+
+    outputUncompressed(dinfoP, colormapIndex);
+    /* Issue Clear codes often enough to keep the reader from ratcheting up
+     * its symbol size.
+     */
+    if (dinfoP->code_counter < dinfoP->maxcode) {
+        ++dinfoP->code_counter;
+    } else {
+        outputUncompressed(dinfoP, dinfoP->ClearCode);
+        dinfoP->code_counter = dinfoP->ClearCode + 2;	/* reset the counter */
+    }
+}
+
+
+
+static void
+writeRasterUncompressedTerm(struct gif_dest * const dinfoP) {
+
+    outputUncompressed(dinfoP, dinfoP->EOFCode);
+
+    if (dinfoP->cur_bits > 0)
+        byteBuffer_out(dinfoP->byteBufferP, dinfoP->cur_accum & 0xFF);
+
+    byteBuffer_flush(dinfoP->byteBufferP);
+
+    byteBuffer_destroy(dinfoP->byteBufferP);
+}
+
+
+
+static void
+writeRasterUncompressed(pixelReader *  const pixelReaderP,
+                        pixval         const inputMaxval,
+                        gray **        const alpha,
+                        gray           const alphaMaxval, 
+                        struct cmap *  const cmapP, 
+                        int            const initBits,
+                        FILE *         const ofP) {
+/*----------------------------------------------------------------------------
+   Write the raster to file 'ofP'.
+   
+   Same as writeRasterLzw(), except written out one code per
+   pixel (plus some clear codes), so no compression.  And no use
+   of the LZW patent.
+-----------------------------------------------------------------------------*/
+    gray const alphaThreshold = (alphaMaxval + 1) / 2;
+        /* gray levels below this in the alpha mask indicate transparent
+           pixels in the output image.
+        */
+    bool eof;
+    struct gif_dest gifDest;
+
+    writeRasterUncompressedInit(ofP, &gifDest, initBits);
+
+    eof = FALSE;
+    while (!eof) {
+        unsigned int gifpixel;
+            /* The value for the pixel in the GIF image.  I.e. the colormap
+               index.
+            */
+        gifNextPixel(pixelReaderP, inputMaxval, alpha, alphaThreshold, cmapP,
+                     &gifpixel, &eof);
+        if (!eof)
+            writeRasterUncompressedPixel(&gifDest, gifpixel);
+    }
+    writeRasterUncompressedTerm(&gifDest);
+}
+
+
+
+/******************************************************************************
+ *
+ * GIF Specific routines
+ *
+ *****************************************************************************/
+
+static void
+writeGifHeader(FILE * const fp,
+               int const Width, int const Height, 
+               int const GInterlace, int const Background, 
+               int const BitsPerPixel, struct cmap * const cmapP,
+               const char comment[]) {
+
+    int B;
+    int const Resolution = BitsPerPixel;
+    int const ColorMapSize = 1 << BitsPerPixel;
+
+    /* Write the Magic header */
+    if (cmapP->transparent != -1 || comment)
+        fwrite("GIF89a", 1, 6, fp);
+    else
+        fwrite("GIF87a", 1, 6, fp);
+
+    /* Write out the screen width and height */
+    Putword( Width, fp );
+    Putword( Height, fp );
+
+    /* Indicate that there is a global color map */
+    B = 0x80;       /* Yes, there is a color map */
+
+    /* OR in the resolution */
+    B |= (Resolution - 1) << 4;
+
+    /* OR in the Bits per Pixel */
+    B |= (BitsPerPixel - 1);
+
+    /* Write it out */
+    fputc( B, fp );
+
+    /* Write out the Background color */
+    fputc( Background, fp );
+
+    /* Byte of 0's (future expansion) */
+    fputc( 0, fp );
+
+    {
+        /* Write out the Global Color Map */
+        /* Note that the Global Color Map is always a power of two colors
+           in size, but *cmapP could be smaller than that.  So we pad with
+           black.
+        */
+        int i;
+        for ( i=0; i < ColorMapSize; ++i ) {
+            if ( i < cmapP->cmapsize ) {
+                fputc( cmapP->red[i], fp );
+                fputc( cmapP->green[i], fp );
+                fputc( cmapP->blue[i], fp );
+            } else {
+                fputc( 0, fp );
+                fputc( 0, fp );
+                fputc( 0, fp );
+            }
+        }
+    }
+        
+    if ( cmapP->transparent >= 0 ) 
+        write_transparent_color_index_extension(fp, cmapP->transparent);
+
+    if ( comment )
+        write_comment_extension(fp, comment);
+}
+
+
+
+static void
+writeImageHeader(FILE *       const ofP,
+                 unsigned int const leftOffset,
+                 unsigned int const topOffset,
+                 unsigned int const gWidth,
+                 unsigned int const gHeight,
+                 unsigned int const gInterlace,
+                 unsigned int const initCodeSize) {
+
+    Putword(leftOffset, ofP);
+    Putword(topOffset,  ofP);
+    Putword(gWidth,     ofP);
+    Putword(gHeight,    ofP);
+
+    /* Write out whether or not the image is interlaced */
+    if (gInterlace)
+        fputc(0x40, ofP);
+    else
+        fputc(0x00, ofP);
+
+    /* Write out the initial code size */
+    fputc(initCodeSize, ofP);
+}
+
+
+
+static void
+gifEncode(FILE *        const ofP, 
+          FILE *        const ifP,
+          int           const gWidth,
+          int           const gHeight, 
+          pixval        const inputMaxval,
+          int           const inputFormat,
+          pm_filepos    const rasterPos,
+          gray **       const alpha,
+          gray          const alphaMaxval,
+          int           const gInterlace,
+          int           const background, 
+          int           const bitsPerPixel,
+          struct cmap * const cmapP,
+          char          const comment[],
+          bool          const nolzw) {
+
+    unsigned int const leftOffset = 0;
+    unsigned int const topOffset  = 0;
+
+    unsigned int const initCodeSize = bitsPerPixel <= 1 ? 2 : bitsPerPixel;
+        /* The initial code size */
+
+    pixelReader * pixelReaderP;
+
+    writeGifHeader(ofP, gWidth, gHeight, gInterlace, background,
+                   bitsPerPixel, cmapP, comment);
+
+    /* Write an Image separator */
+    fputc(',', ofP);
+
+    writeImageHeader(ofP, leftOffset, topOffset, gWidth, gHeight, gInterlace,
+                     initCodeSize);
+
+    pixelReaderCreate(ifP, gWidth, gHeight, inputMaxval, inputFormat,
+                      rasterPos, gInterlace, &pixelReaderP);
+
+    /* Write the actual raster */
+    if (nolzw)
+        writeRasterUncompressed(pixelReaderP,
+                                inputMaxval, alpha, alphaMaxval, cmapP, 
+                                initCodeSize + 1, ofP);
+    else
+        writeRasterLzw(pixelReaderP, 
+                       inputMaxval, alpha, alphaMaxval, cmapP, 
+                       initCodeSize + 1, ofP);
+
+    pixelReaderDestroy(pixelReaderP);
+
+    /* Write out a zero length data block (to end the series) */
+    fputc(0, ofP);
+
+    /* Write the GIF file terminator */
+    fputc(';', ofP);
+}
+
+
+
+static int
+compute_transparent(const char colorarg[], 
+                    struct cmap * const cmapP) {
+/*----------------------------------------------------------------------------
+   Figure out the color index (index into the colormap) of the color
+   that is to be transparent in the GIF.
+
+   colorarg[] is the string that specifies the color the user wants to
+   be transparent (e.g. "red", "#fefefe").  Its maxval is the maxval
+   of the colormap.  'cmap' is the full colormap except that its
+   'transparent' component isn't valid.
+
+   colorarg[] is a standard Netpbm color specification, except that
+   may have a "=" prefix, which means it specifies a particular exact
+   color, as opposed to without the "=", which means "the color that
+   is closest to this and actually in the image."
+
+   Return -1 if colorarg[] specifies an exact color and that color is not
+   in the image.  Also issue an informational message.
+-----------------------------------------------------------------------------*/
+    int retval;
+
+    const char *colorspec;
+    bool exact;
+    int presort_colorindex;
+    pixel transcolor;
+
+    if (colorarg[0] == '=') {
+        colorspec = &colorarg[1];
+        exact = TRUE;
+    } else {
+        colorspec = colorarg;
+        exact = FALSE;
+    }
+        
+    transcolor = ppm_parsecolor((char*)colorspec, cmapP->maxval);
+    presort_colorindex = ppm_lookupcolor(cmapP->cht, &transcolor);
+    
+    if (presort_colorindex != -1)
+        retval = cmapP->perm[presort_colorindex];
+    else if (!exact)
+        retval = cmapP->perm[closestcolor(transcolor, cmapP->maxval, cmapP)];
+    else {
+        retval = -1;
+        pm_message(
+            "Warning: specified transparent color does not occur in image.");
+    }
+    return retval;
+}
+
+
+
+static void
+sort_colormap(int const sort, struct cmap * const cmapP) {
+/*----------------------------------------------------------------------------
+   Sort (in place) the colormap *cmapP.
+
+   Create the perm[] and permi[] mappings for the colormap.
+
+   'sort' is logical:  true means to sort the colormap by red intensity,
+   then by green intensity, then by blue intensity.  False means a null
+   sort -- leave it in the same order in which we found it.
+-----------------------------------------------------------------------------*/
+    int * const Red = cmapP->red;
+    int * const Blue = cmapP->blue;
+    int * const Green = cmapP->green;
+    int * const perm = cmapP->perm;
+    int * const permi = cmapP->permi;
+    unsigned int const cmapsize = cmapP->cmapsize;
+    
+    int i;
+
+    for (i=0; i < cmapsize; i++)
+        permi[i] = i;
+
+    if (sort) {
+        pm_message("sorting colormap");
+        for (i=0; i < cmapsize; i++) {
+            int j;
+            for (j=i+1; j < cmapsize; j++)
+                if (((Red[i]*MAXCMAPSIZE)+Green[i])*MAXCMAPSIZE+Blue[i] >
+                    ((Red[j]*MAXCMAPSIZE)+Green[j])*MAXCMAPSIZE+Blue[j]) {
+                    int tmp;
+                    
+                    tmp=permi[i]; permi[i]=permi[j]; permi[j]=tmp;
+                    tmp=Red[i]; Red[i]=Red[j]; Red[j]=tmp;
+                    tmp=Green[i]; Green[i]=Green[j]; Green[j]=tmp;
+                    tmp=Blue[i]; Blue[i]=Blue[j]; Blue[j]=tmp; } }
+    }
+
+    for (i=0; i < cmapsize; i++)
+        perm[permi[i]] = i;
+}
+
+
+
+static void
+normalize_to_255(colorhist_vector const chv, struct cmap * const cmapP) {
+/*----------------------------------------------------------------------------
+   With a PPM color histogram vector 'chv' as input, produce a colormap
+   of integers 0-255 as output in *cmapP.
+-----------------------------------------------------------------------------*/
+    int i;
+    pixval const maxval = cmapP->maxval;
+
+    if ( maxval != 255 )
+        pm_message(
+            "maxval is not 255 - automatically rescaling colors" );
+
+    for ( i = 0; i < cmapP->cmapsize; ++i ) {
+        if ( maxval == 255 ) {
+            cmapP->red[i] = (int) PPM_GETR( chv[i].color );
+            cmapP->green[i] = (int) PPM_GETG( chv[i].color );
+            cmapP->blue[i] = (int) PPM_GETB( chv[i].color );
+        } else {
+            cmapP->red[i] = (int) PPM_GETR( chv[i].color ) * 255 / maxval;
+            cmapP->green[i] = (int) PPM_GETG( chv[i].color ) * 255 / maxval;
+            cmapP->blue[i] = (int) PPM_GETB( chv[i].color ) * 255 / maxval;
+        }
+    }
+}
+
+
+
+static void add_to_colormap(struct cmap * const cmapP, 
+                            const char *  const colorspec, 
+                            int *         const new_indexP) {
+/*----------------------------------------------------------------------------
+  Add a new entry to the colormap.  Make the color that specified by
+  'colorspec', and return the index of the new entry as *new_indexP.
+
+  'colorspec' is a color specification given by the user, e.g.
+  "red" or "rgb:ff/03.0d".  The maxval for this color specification is
+  that for the colormap *cmapP.
+-----------------------------------------------------------------------------*/
+    pixel const transcolor = ppm_parsecolor((char*)colorspec, cmapP->maxval);
+    
+    *new_indexP = cmapP->cmapsize++; 
+
+    cmapP->red[*new_indexP] = PPM_GETR(transcolor);
+    cmapP->green[*new_indexP] = PPM_GETG(transcolor); 
+    cmapP->blue[*new_indexP] = PPM_GETB(transcolor); 
+}
+
+
+
+static void
+colormapFromFile(char               const filespec[],
+                 unsigned int       const maxcolors,
+                 colorhist_vector * const chvP, 
+                 pixval *           const maxvalP,
+                 unsigned int *     const colorsP) {
+/*----------------------------------------------------------------------------
+   Read a colormap from the PPM file filespec[].  Return the color histogram
+   vector (which is practically a colormap) of the input image as *cvhP
+   and the maxval for that histogram as *maxvalP.
+-----------------------------------------------------------------------------*/
+    FILE * mapfileP;
+    int cols, rows;
+    pixel ** colormapPpm;
+    int colors;
+
+    mapfileP = pm_openr(filespec);
+    colormapPpm = ppm_readppm(mapfileP, &cols, &rows, maxvalP);
+    pm_close(mapfileP);
+    
+    pm_message("computing other colormap ...");
+    *chvP = ppm_computecolorhist(colormapPpm, cols, rows, maxcolors, &colors);
+
+    *colorsP = colors;
+
+    ppm_freearray(colormapPpm, rows); 
+}
+
+
+
+static void
+get_alpha(const char * const alpha_filespec, int const cols, int const rows,
+          gray *** const alphaP, gray * const maxvalP) {
+
+    if (alpha_filespec) {
+        int alpha_cols, alpha_rows;
+        *alphaP = pgm_readpgm(pm_openr(alpha_filespec),
+                              &alpha_cols, &alpha_rows, maxvalP);
+        if (alpha_cols != cols || alpha_rows != rows)
+            pm_error("alpha mask is not the same dimensions as the "
+                     "input file (alpha is %dW x %dH; image is %dW x %dH)",
+                     alpha_cols, alpha_rows, cols, rows);
+    } else 
+        *alphaP = NULL;
+}
+
+
+
+static void
+computePpmColormap(FILE *             const ifP,
+                   unsigned int       const cols,
+                   unsigned int       const rows,
+                   pixval             const maxval,
+                   int                const format,
+                   bool               const haveAlpha, 
+                   const char *       const mapfile,
+                   colorhist_vector * const chvP,
+                   colorhash_table *  const chtP,
+                   pixval *           const colormapMaxvalP, 
+                   unsigned int *     const colorsP) {
+/*----------------------------------------------------------------------------
+   Compute a colormap, PPM style, for the image on file 'ifP', which
+   is positioned to the raster and is 'cols' by 'rows' with maxval
+   'maxval' and format 'format'.  If 'mapfile' is non-null, Use the
+   colors in that (PPM) file for the color map instead of the colors
+   in 'ifP'.
+
+   Return the colormap as *chvP and *chtP.  Return the maxval for that
+   colormap as *colormapMaxvalP.
+
+   While we're at it, count the colors and validate that there aren't
+   too many.  Return the count as *colorsP.  In determining if there are
+   too many, allow one slot for a fake transparency color if 'have_alpha'
+   is true.  If there are too many, issue an error message and abort the
+   program.
+-----------------------------------------------------------------------------*/
+    unsigned int maxcolors;
+        /* The most colors we can tolerate in the image.  If we have
+           our own made-up entry in the colormap for transparency, it
+           isn't included in this count.
+        */
+
+    if (haveAlpha)
+        maxcolors = MAXCMAPSIZE - 1;
+    else
+        maxcolors = MAXCMAPSIZE;
+
+    if (mapfile) {
+        /* Read the colormap from a separate colormap file. */
+        colormapFromFile(mapfile, maxcolors, chvP, colormapMaxvalP, 
+                         colorsP);
+    } else {
+        /* Figure out the color map from the input file */
+        int colors;
+        pm_message("computing colormap...");
+        *chvP = ppm_computecolorhist2(ifP, cols, rows, maxval, format,
+                                      maxcolors, &colors); 
+        *colorsP = colors;
+        *colormapMaxvalP = maxval;
+    }
+
+    if (*chvP == NULL)
+        pm_error("too many colors - try doing a 'pnmquant %d'", maxcolors);
+    pm_message("%d colors found", *colorsP);
+
+    /* And make a hash table for fast lookup. */
+    *chtP = ppm_colorhisttocolorhash(*chvP, *colorsP);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    int rows, cols;
+    pixval inputMaxval;
+    int inputFormat;   
+    int BitsPerPixel;
+    gray ** alpha;     /* The supplied alpha mask; NULL if none */
+    gray alpha_maxval; /* Maxval for 'alpha' */
+    pm_filepos rasterPos;
+
+    struct cmap cmap;
+        /* The colormap, with all its accessories */
+    colorhist_vector chv;
+    int fake_transparent;
+        /* colormap index of the fake transparency color we're using to
+           implement the alpha mask.  Undefined if we're not doing an alpha
+           mask.
+        */
+
+    ppm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    verbose = cmdline.verbose;
+
+    ifP = pm_openr_seekable(cmdline.input_filespec);
+
+    ppm_readppminit(ifP, &cols, &rows, &inputMaxval, &inputFormat);
+
+    pm_tell2(ifP, &rasterPos, sizeof(rasterPos));
+
+    get_alpha(cmdline.alpha_filespec, cols, rows, &alpha, &alpha_maxval);
+
+    computePpmColormap(ifP, cols, rows, inputMaxval, inputFormat,
+                       (alpha != NULL), cmdline.mapfile, 
+                       &chv, &cmap.cht, &cmap.maxval, &cmap.cmapsize);
+
+    /* Now turn the ppm colormap into the appropriate GIF colormap. */
+
+    normalize_to_255(chv, &cmap);
+
+    ppm_freecolorhist(chv);
+
+    if (alpha) {
+        /* Add a fake entry to the end of the colormap for transparency.  
+           Make its color black. 
+        */
+        add_to_colormap(&cmap, cmdline.alphacolor, &fake_transparent);
+    }
+    sort_colormap(cmdline.sort, &cmap);
+
+    BitsPerPixel = pm_maxvaltobits(cmap.cmapsize-1);
+
+    if (alpha) {
+        cmap.transparent = cmap.perm[fake_transparent];
+    } else {
+        if (cmdline.transparent)
+            cmap.transparent = 
+                compute_transparent(cmdline.transparent, &cmap);
+        else 
+            cmap.transparent = -1;
+    }
+
+    /* All set, let's do it. */
+    gifEncode(stdout, ifP, cols, rows, inputMaxval, inputFormat, rasterPos,
+              alpha, alpha_maxval, 
+              cmdline.interlace, 0, BitsPerPixel, &cmap, cmdline.comment,
+              cmdline.nolzw);
+
+    if (alpha)
+        pgm_freearray(alpha, rows);
+
+    pm_close(ifP);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/converter/ppm/ppmtoicr.c b/converter/ppm/ppmtoicr.c
new file mode 100644
index 00000000..feca0c18
--- /dev/null
+++ b/converter/ppm/ppmtoicr.c
@@ -0,0 +1,320 @@
+/* ppmtoicr.c - convert a portable pixmap to NCSA ICR protocol
+**
+** Copyright (C) 1990 by Kanthan Pillay (svpillay@Princeton.EDU)
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+
+#define MAXCOLORS 256
+#define CLUTCOLORS 768
+
+static int colorstobpp ARGS(( int colors ));
+static int GetPixel ARGS(( int x, int y ));
+static int rleit ARGS(( char* buf, char* bufto, int len ));
+
+static pixel** pixels;
+static colorhash_table cht;
+static char* testimage;
+
+int
+main(argc, argv)
+int argc;
+char* argv[];
+{
+	FILE* ifp;
+	int argn, rows, cols, colors, i, j, BitsPerPixel, newxsize;
+	pixval maxval;
+	colorhist_vector chv;
+	char rgb[CLUTCOLORS];
+	const char* windowname;
+	char* thischar;
+	char* thisline;
+	char* space;
+	register unsigned char c;
+	register char* p;
+	int display, expand;
+	int rleflag, winflag;
+	const char* const usage = "[-windowname windowname] [-expand expand] [-display display] [-rle] [ppmfile]";
+
+
+	ppm_init( &argc, argv );
+
+	argn = 1;
+	windowname = "untitled";
+	winflag = 0;
+	expand = 1;
+	display = 0;
+	rleflag = 0;
+
+	while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+	    {
+	    if ( pm_keymatch(argv[argn],"-windowname",2) && argn + 1 < argc )
+		{
+		++argn;
+		windowname = argv[argn];
+		winflag = 1;
+		}
+	    else if ( pm_keymatch(argv[argn],"-expand",2) && argn + 1 < argc )
+		{
+		++argn;
+		if ( sscanf( argv[argn], "%d",&expand ) != 1 )
+		    pm_usage( usage );
+		}
+	    else if ( pm_keymatch(argv[argn],"-display",2) && argn + 1 < argc )
+		{
+		++argn;
+		if ( sscanf( argv[argn], "%d",&display ) != 1 )
+		    pm_usage( usage );
+		}
+	    else if ( pm_keymatch(argv[argn],"-rle",2) )
+		rleflag = 1;
+	    else if ( pm_keymatch(argv[argn],"-norle",2) )
+		rleflag = 0;
+	    else
+		pm_usage( usage );
+	    }
+
+	if ( argn < argc )
+	    {
+	    ifp = pm_openr( argv[argn] );
+	    if ( ! winflag )
+		windowname = argv[argn];
+	    ++argn;
+	    }
+	else
+	    ifp = stdin;
+
+	if ( argn != argc )
+	    pm_usage( usage );
+
+	pixels = ppm_readppm( ifp, &cols, &rows, &maxval );
+
+	pm_close( ifp );
+
+	for (i = 0; i < CLUTCOLORS; i++)
+	    rgb[i] = 0;
+
+	/* Figure out the colormap. */
+	pm_message("computing colormap..." );
+	chv = ppm_computecolorhist(pixels, cols, rows, MAXCOLORS, &colors);
+	if (chv == (colorhist_vector) 0)
+	pm_error( "too many colors - try doing a 'pnmquant %d'", MAXCOLORS );
+	pm_message("%d colors found", colors );
+
+	/* Turn the ppm colormap into an ICR colormap. */
+	if (maxval > 255)
+	pm_message(
+		"maxval is not 255 - automatically rescaling colors" );
+	for (i = 0; i < colors; i++)
+	{
+	j = (3 * i);
+	if (maxval == 255)
+		{
+		rgb[j] = PPM_GETR(chv[i].color) ;
+		j++;
+		rgb[j] = PPM_GETG(chv[i].color) ;
+		j++;
+		rgb[j] = PPM_GETB(chv[i].color) ;
+		}
+	else
+		{
+		rgb[j] = (int) PPM_GETR(chv[i].color) * 255 / maxval;
+		j++;
+		rgb[j] = (int) PPM_GETG(chv[i].color) * 255 / maxval;
+		j++;
+		rgb[j] = (int) PPM_GETB(chv[i].color) * 255 / maxval;
+		}
+	}
+	BitsPerPixel = colorstobpp(colors);
+
+	/* And make a hash table for fast lookup. */
+	cht = ppm_colorhisttocolorhash(chv, colors);
+	ppm_freecolorhist(chv);
+
+
+	/************** Create a new window using ICR protocol *********/
+	/* Format is "ESC^W;left;top;width;height;display;windowname"  */
+
+	pm_message("creating window %s ...", windowname );
+	(void)printf("\033^W;%d;%d;%d;%d;%d;%s^",0,0,cols*expand,rows*expand,display,windowname);
+	fflush(stdout);
+
+
+	/****************** Download the colormap.  ********************/
+	pm_message("downloading colormap for %s ...", windowname );
+
+	(void)printf("\033^M;%d;%d;%d;%s^",0,MAXCOLORS,CLUTCOLORS,windowname);
+	thischar = rgb;
+	for (j=0; j<CLUTCOLORS; j++) {
+	c = *thischar++;
+		if (c > 31 && c < 123 ) {	 /* printable ASCII */
+		putchar(c);
+		}
+		else {
+		putchar((c>>6)+123);	 /* non-printable, so encode it */
+		putchar((c & 0x3f) + 32);
+		}
+	}
+	fflush(stdout);
+
+	/**************** send out picture *************************/
+	/* Protocol's RLE scheme is quicker but buggy              */
+
+	if (rleflag) {	
+		pm_message("sending run-length encoded picture data ..." );
+		testimage = (char*) malloc(rows*cols);
+		p = testimage;
+		for (i=0; i<rows; i++)
+			for (j=0; j<cols; j++) 
+			*p++ = GetPixel(j,i);
+		space = (char*) malloc(rows*3);
+		thisline = testimage;
+		for (i = 0; i < rows; i++) {
+			newxsize = rleit(thisline,space,cols);
+			thisline += cols;	/* increment to next line */
+		(void)printf("\033^R;%d;%d;%d;%d;%s^",0,i*expand,expand,newxsize,windowname);
+		thischar = space;
+		for (j=0; j< newxsize; j++) {
+			c= *thischar++;  /*get byte to send */
+			if (c>31 && c <123) {
+				putchar(c);
+				}
+			else {
+				putchar((c>>6) + 123);
+				putchar((c & 0x3f) + 32);
+				}
+			}
+			fflush(stdout);
+		}
+		free(space);
+		exit(0);
+		}
+
+	/* Otherwise, send out uncompressed pixel data via the slow method */
+
+		else {
+		pm_message("sending picture data ..." );
+		for (i = 0; i < rows; i++) {
+			(void)printf("\033^P;%d;%d;%d;%d;%s^",0,i*expand,expand,cols,windowname);
+			for (j = 0; j < cols; j++) {
+				c  = GetPixel(j,i);
+				if (c > 31 && c < 123) {
+						putchar(c);
+						}
+				else		{
+						putchar((c>>6)+123);
+						putchar((c & 0x3f) + 32);
+						}
+				}
+			}
+		fflush(stdout);
+		exit(0);
+		}
+	}
+
+static int
+colorstobpp(colors)
+int colors;
+	{
+	int bpp;
+
+	if (colors <= 2)
+	bpp = 1;
+	else if (colors <= 4)
+	bpp = 2;
+	else if (colors <= 8)
+	bpp = 3;
+	else if (colors <= 16)
+	bpp = 4;
+	else if (colors <= 32)
+	bpp = 5;
+	else if (colors <= 64)
+	bpp = 6;
+	else if (colors <= 128)
+	bpp = 7;
+	else if (colors <= 256)
+	bpp = 8;
+	else
+	pm_error("can't happen" );
+	return bpp;
+	}
+
+static int
+GetPixel(x, y)
+int x, y;
+	{
+	int color;
+
+	color = ppm_lookupcolor(cht, &pixels[y][x]);
+	return color;
+	}
+
+
+/* rleit   compress with run length encoding as per NCSA's documentation */
+
+static int
+rleit(buf,bufto,len)
+	char* buf;
+	char* bufto;
+	int len;
+	{
+	register char* p;
+	register char* q;
+	register char* cfoll;
+	register char* clead;
+	char* begp;
+	int i;
+
+	p = buf;
+	cfoll = bufto;
+	clead = cfoll + 1;
+
+	begp = p;
+	while (len > 0 ) {		/* encode until gone */
+		
+		q = p + 1;
+		i = len-1;
+	while (*p == *q && i+120 > len && i) {
+		q++;
+		i--;
+	}
+
+	if (q > p +2) {			/* three in a row */
+		if (p > begp) {
+			*cfoll = p - begp;
+			cfoll = clead;
+		}
+		*cfoll++ = 128 | (q-p);		/*len of seq*/
+		*cfoll++ = *p;			/* char of seq */
+		len -= q-p;		/* subtract len of seq */
+		p = q;
+		clead = cfoll+1;
+		begp = p;
+	}
+	else {
+		*clead++ = *p++;	/* copy one char */
+		len--;
+		if (p>begp + 120) {
+			*cfoll = p - begp;
+			cfoll = clead++;
+			begp = p;
+		}
+	}
+	}
+
+/* fillin last bytecount */
+
+	if (p>begp)
+		*cfoll = (p - begp);
+	else
+		clead--;
+
+	return((int) (clead-bufto));	/*how many stored as encoded */
+}
diff --git a/converter/ppm/ppmtoilbm.c b/converter/ppm/ppmtoilbm.c
new file mode 100644
index 00000000..6c04c9be
--- /dev/null
+++ b/converter/ppm/ppmtoilbm.c
@@ -0,0 +1,2362 @@
+/* ppmtoilbm.c - read a portable pixmap and produce an IFF ILBM file
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+** Modified by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
+**  20/Jun/93:
+**  - 24-bit capability (new options -24if, -24force)
+**  - HAM8 capability (well, anything from HAM3 to HAM(MAXPLANES))
+**  - now writes up to 8 (16) planes (new options -maxplanes, -fixplanes)
+**  - colormap file (new option -map)
+**  - write colormap only (new option -cmaponly)
+**  - only writes CAMG chunk if it is a HAM-picture
+**  29/Aug/93:
+**  - operates row-by-row whenever possible
+**  - faster colorscaling with lookup-table (~20% faster on HAM pictures)
+**  - options -ham8 and -ham6 now imply -hamforce
+**  27/Nov/93:
+**  - byterun1 compression (this is now default) with new options:
+**    -compress, -nocompress, -cmethod, -savemem
+**  - floyd-steinberg error diffusion (for std+mapfile and HAM)
+**  - new options: -lace and -hires --> write CAMG chunk
+**  - LUT for luminance calculation (used by ppm_to_ham)
+**  23/Oct/94:
+**  - rework of mapfile handling
+**  - added RGB8 & RGBN image types
+**  - added maskplane and transparent color capability
+**  - 24-bit & direct color modified to n-bit deep ILBM
+**  - removed "-savemem" option
+**  22/Feb/95:
+**  - minor bugfixes
+**  - fixed "-camg 0" behaviour: now writes a CAMG chunk with value 0
+**  - "-24if" is now default
+**  - "-mmethod" and "-cmethod" options accept numeric args and keywords
+**  - direct color (DCOL) reimplemented
+**  - mapfile useable for HAM
+**  - added HAM colormap "fixed"
+**  29/Mar/95:
+**  - added HAM colormap "rgb4" and "rgb5" (compute with 4/5-bit table)
+**  - added IFF text chunks
+**
+**  TODO:
+**  - multipalette capability (PCHG chunk) for std and HAM
+**
+**
+**           std   HAM  deep  cmap  RGB8  RGBN
+**  -------+-----+-----+-----+-----+-----+-----
+**  BMHD     yes   yes   yes   yes   yes   yes
+**  CMAP     yes   (1)   no    yes   no    no
+**  BODY     yes   yes   yes   no    yes   yes
+**  CAMG     (2)   yes   (2)   no    yes   yes
+**  nPlanes  1-16  3-16  3-48  0     25    13
+**
+**  (1): grayscale colormap
+**  (2): only if "-lace", "-hires" or "-camg" option used
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "ppm.h"
+#include "ppmfloyd.h"
+#include "pbm.h"
+#include "ilbm.h"
+#include "lum.h"
+
+/*#define DEBUG*/
+
+#define MODE_RGB8       6   /* RGB8: 8-bit RGB */
+#define MODE_RGBN       5   /* RGBN: 4-bit RGB */
+#define MODE_CMAP       4   /* ILBM: colormap only */
+#define MODE_DCOL       3   /* ILBM: direct color */
+#define MODE_DEEP       2   /* ILBM: deep (24-bit) */
+#define MODE_HAM        1   /* ILBM: hold-and-modify (HAM) */
+#define MODE_NONE       0   /* ILBM: colormapped */
+
+#define HAMMODE_GRAY    0   /* HAM colormap: grayscale */
+#define HAMMODE_FIXED   1   /* HAM colormap: 7 "rays" in RGB cube */
+#define HAMMODE_MAPFILE 2   /* HAM colormap: loaded from mapfile */
+#define HAMMODE_RGB4    3   /* HAM colormap: compute, 4bit RGB */
+#define HAMMODE_RGB5    4   /* HAM colormap: compute, 5bit RGB */
+
+#define ECS_MAXPLANES   5
+#define ECS_HAMPLANES   6
+#define AGA_MAXPLANES   8
+#define AGA_HAMPLANES   8
+
+#define HAMMAXPLANES    10  /* maximum planes for HAM */
+
+#define DEF_MAXPLANES   ECS_MAXPLANES
+#define DEF_HAMPLANES   ECS_HAMPLANES
+#define DEF_COMPRESSION cmpByteRun1
+#define DEF_DEEPPLANES  8
+#define DEF_DCOLPLANES  5
+#define DEF_IFMODE      MODE_DEEP
+
+static void put_big_short ARGS((short s));
+static void put_big_long ARGS((long l));
+#define put_byte(b)     (void)(putc((unsigned char)(b), stdout))
+static void write_bytes ARGS((unsigned char *buffer, int bytes));
+static void ppm_to_ham ARGS((FILE *fp, int cols, int rows, int maxval, pixel *colormap, int colors, int cmapmaxval, int hamplanes));
+static void ppm_to_deep ARGS((FILE *fp, int cols, int rows, int maxval, int bitspercolor));
+static void ppm_to_dcol ARGS((FILE *fp, int cols, int rows, int maxval, DirectColor *dcol));
+static void ppm_to_rgb8 ARGS((FILE *fp, int cols, int rows, int maxval));
+static void ppm_to_rgbn ARGS((FILE *fp, int cols, int rows, int maxval));
+static void ppm_to_std ARGS((FILE *fp, int cols, int rows, int maxval, pixel *colormap, int colors, int cmapmaxval, int maxcolors, int nPlanes));
+static void ppm_to_cmap ARGS((pixel *colormap, int colors, int maxval));
+static void write_bmhd ARGS((int cols, int rows, int nPlanes));
+static void write_cmap ARGS((pixel *colormap, int colors, int maxval));
+static long encode_row ARGS((FILE *outfile, rawtype *rawrow, int cols, int nPlanes));
+static long encode_maskrow ARGS((FILE *outfile, rawtype *rawrow, int cols));
+static int compress_row ARGS((int bytes));
+static void store_bodyrow ARGS((unsigned char *row, int len));
+static int runbyte1 ARGS((int bytes));
+static pixel * next_pixrow ARGS((FILE *fp, int row));
+static int * make_val_table ARGS((int oldmaxval, int newmaxval));
+static void init_read ARGS((FILE *fp, int *colsP, int *rowsP, pixval *maxvalP, int *formatP, int readall));
+static void write_body_rows ARGS((void));
+static void write_camg ARGS((void));
+static int  length_of_text_chunks ARGS((void));
+static void write_text_chunks ARGS((void));
+#define PAD(n)      (ODD(n) ? 1 : 0)    /* pad to a word */
+
+
+/* global data */
+static unsigned char *coded_rowbuf; /* buffer for uncompressed scanline */
+static unsigned char *compr_rowbuf; /* buffer for compressed scanline */
+static pixel **pixels;  /* PPM image (NULL for row-by-row operation) */
+static pixel *pixrow;   
+    /* current row in PPM image (pointer into pixels array, or buffer
+       for row-by-row operation) 
+    */
+
+static long viewportmodes = 0;
+static int slicesize = 1; 
+    /* rows per slice for multipalette images - NOT USED */
+
+static unsigned char compmethod = DEF_COMPRESSION;   /* default compression */
+static unsigned char maskmethod = mskNone;
+
+static pixel *transpColor = NULL;   /* transparent color */
+static short  transpIndex = -1;     /* index of transparent color */
+
+static short hammapmode = HAMMODE_GRAY;
+static short sortcmap = 0;     /* sort colormap */
+
+static FILE *maskfile = NULL;
+static bit *maskrow = NULL;
+static int maskcols, maskformat;
+#define TOTALPLANES(nplanes) ((nplanes) + ((maskmethod == mskHasMask) ? 1 : 0))
+
+
+#define ROWS_PER_BLOCK  1024
+typedef struct bodyblock {
+    int used;
+    unsigned char *row[ROWS_PER_BLOCK];
+    int            len[ROWS_PER_BLOCK];
+    struct bodyblock *next;
+} bodyblock;
+static bodyblock firstblock = { 0 };
+static bodyblock *cur_block = &firstblock;
+
+static char *anno_chunk, *auth_chunk, *name_chunk, *text_chunk, *copyr_chunk;
+
+/* flags */
+static short compr_force = 0;   
+    /* force compressed output, even if the image got larger  - NOT USED */
+static short floyd = 0;         /* apply floyd-steinberg error diffusion */
+static short gen_camg = 0;      /* write CAMG chunk */
+
+#define WORSTCOMPR(bytes)       ((bytes) + (bytes)/128 + 1)
+#define DO_COMPRESS             (compmethod != cmpNone)
+
+
+/***** parse options and figure out what kind of ILBM to write *****/
+
+static int get_int_val ARGS((char *string, char *option, int bot, int top));
+static int get_compr_method ARGS((char *string));
+static int get_mask_type ARGS((char *string));
+static int get_hammap_mode ARGS((char *string));
+
+
+
+#define NEWDEPTH(pix, table) PPM_ASSIGN((pix), (table)[PPM_GETR(pix)], (table)[PPM_GETG(pix)], (table)[PPM_GETB(pix)])
+
+
+static void
+report_too_many_colors(int         const ifmode,
+                       int         const maxplanes,
+                       int         const hamplanes,
+                       DirectColor const dcol,
+                       int         const deepbits) {
+    
+    int const maxcolors = 1 << maxplanes;
+
+    switch( ifmode ) {
+    case MODE_HAM:
+        pm_message("too many colors for %d planes - "
+                   "proceeding to write a HAM%d file", 
+                   maxplanes, hamplanes);
+        pm_message("if you want a non-HAM file, try doing a 'pnmquant %d'", 
+                   maxcolors);
+        break;
+    case MODE_DCOL:
+        pm_message("too many colors for %d planes - "
+                   "proceeding to write a %d:%d:%d direct color ILBM", 
+                   maxplanes, dcol.r, dcol.g, dcol.b);
+        pm_message("if you want a non-direct color file, "
+                   "try doing a 'pnmquant %d'", maxcolors);
+        break;
+    case MODE_DEEP:
+        pm_message("too many colors for %d planes - "
+                   "proceeding to write a %d-bit \'deep\' ILBM", 
+                   maxplanes, deepbits*3);
+        pm_message("if you want a non-deep file, "
+                   "try doing a 'pnmquant %d'", 
+                   maxcolors);
+        break;
+    default:
+        pm_error("too many colors for %d planes - "
+                 "try doing a 'pnmquant %d'", 
+                 maxplanes, maxcolors);
+        break;
+    }
+}
+
+
+static int
+get_int_val(string, option, bot, top)
+    char *string, *option;
+    int bot, top;
+{
+    int val;
+
+    if( sscanf(string, "%d", &val) != 1 )
+        pm_error("option \"%s\" needs integer argument", option);
+
+    if( val < bot || val > top )
+        pm_error("option \"%s\" argument value out of range (%d..%d)", 
+                 option, bot, top);
+
+    return val;
+}
+
+
+static int
+get_compr_method(string)
+    char *string;
+{
+    int retval;
+    if( pm_keymatch(string, "none", 1) || pm_keymatch(string, "0", 1) )
+        retval = cmpNone;
+    else if( pm_keymatch(string, "byterun1", 1) || 
+             pm_keymatch(string, "1", 1) )
+        retval = cmpByteRun1;
+    else 
+        pm_error("unknown compression method: %s", string);
+    return retval;
+}
+
+
+static int
+get_mask_type(string)
+    char *string;
+{
+    int retval;
+
+    if( pm_keymatch(string, "none", 1) || pm_keymatch(string, "0", 1) )
+        retval = mskNone;
+    else
+    if( pm_keymatch(string, "plane", 1) || 
+        pm_keymatch(string, "maskplane", 1) ||
+        pm_keymatch(string, "1", 1) )
+        retval = mskHasMask;
+    else
+    if( pm_keymatch(string, "transparentcolor", 1) || 
+        pm_keymatch(string, "2", 1) )
+        retval = mskHasTransparentColor;
+    else
+    if( pm_keymatch(string, "lasso", 1) || pm_keymatch(string, "3", 1) )
+        retval = mskLasso;
+    else
+        pm_error("unknown masking method: %s", string);
+    return retval;
+}
+
+
+static int
+get_hammap_mode(string)
+    char *string;
+{
+    int retval;
+
+    if( pm_keymatch(string, "grey", 1) || pm_keymatch(string, "gray", 1) )
+        retval =  HAMMODE_GRAY;
+    else
+    if( pm_keymatch(string, "fixed", 1) )
+        retval =  HAMMODE_FIXED;
+    else
+    if( pm_keymatch(string, "rgb4", 4) )
+        retval = HAMMODE_RGB4;
+    else
+    if( pm_keymatch(string, "rgb5", 4) )
+        retval = HAMMODE_RGB5;
+    else 
+        pm_error("unknown HAM colormap selection mode: %s", string);
+    return retval;
+}
+
+
+/************ colormap file ************/
+
+static void
+ppm_to_cmap(colorrow, colors, maxval)
+    pixel *colorrow;
+    int colors;
+    int maxval;
+{
+    int formsize, cmapsize;
+
+    cmapsize = colors * 3;
+
+    formsize =
+        4 +                                 /* ILBM */
+        4 + 4 + BitMapHeaderSize +          /* BMHD size header */
+        4 + 4 + cmapsize + PAD(cmapsize) +  /* CMAP size colormap */
+        length_of_text_chunks();
+
+    put_big_long(ID_FORM);
+    put_big_long(formsize);
+    put_big_long(ID_ILBM);
+
+    write_bmhd(0, 0, 0);
+    write_text_chunks();
+    write_cmap(colorrow, colors, maxval);
+}
+
+/************ HAM ************/
+
+static long 
+do_ham_body     ARGS((FILE *ifP, FILE *ofp, int cols, int rows, pixval maxval,
+                pixval hammaxval, int nPlanes, pixel *cmap, int colors));
+
+
+static int hcmp (const void *va, const void *vb);
+static pixel *compute_ham_cmap ARGS((int cols, int rows, int maxval, 
+                                     int maxcolors, int *colorsP, int hbits));
+
+
+typedef struct {
+    long count;
+    pixval r, g, b;
+} hentry;
+
+
+static int
+hcmp(const void *va, const void *vb)
+{
+    return(((hentry *)vb)->count - ((hentry *)va)->count);  
+        /* reverse sort, highest count first */
+}
+
+
+static pixel *
+compute_ham_cmap(cols, rows, maxval, maxcolors, colorsP, hbits)
+    int cols, rows, maxval, maxcolors;
+    int *colorsP;
+    int hbits;
+{
+    int colors;
+    hentry *hmap;
+    pixel *cmap;
+    pixval hmaxval;
+    int i, r, g, b, col, row, *htable;
+    unsigned long dist, maxdist;
+
+    pm_message("initializing HAM colormap...");
+
+    colors = 1<<(3*hbits);
+    MALLOCARRAY(hmap, colors);
+    if (hmap == NULL)
+        pm_error("Unable to allocate memory for HAM colormap.");
+    hmaxval = pm_bitstomaxval(hbits);
+
+    i = 0;
+    for( r = 0; r <= hmaxval; r++ ) {
+        for( g = 0; g <= hmaxval; g++ ) {
+            for( b = 0; b <= hmaxval; b++ ) {
+                hmap[i].r = r; hmap[i].g = g; hmap[i].b = b;
+                hmap[i].count = 0;
+                i++;
+            }
+        }
+    }
+
+    htable = make_val_table(maxval, hmaxval);
+    for( row = 0; row < rows; row++ ) {
+        unsigned int col;
+        for( col = 0; col < cols; ++col) {
+            pixel const p = pixels[row][col];
+            pixval const r = PPM_GETR(p);
+            pixval const g = PPM_GETG(p);
+            pixval const b = PPM_GETB(p);
+            i = (htable[r]<<(2*hbits)) + (htable[g]<<hbits) + htable[b];
+            hmap[i].count++;
+        }
+    }
+    free(htable);
+
+    qsort((void *)hmap, colors, sizeof(hentry), hcmp);
+    for( i = colors-1; i >= 0; i-- ) {
+        if( hmap[i].count )
+            break;
+    }
+    colors = i+1;
+
+    if( colors > maxcolors ) {
+        pm_message("selecting HAM colormap from %d colors...", colors);
+        for( maxdist = 1; ; maxdist++ ) {
+            for( col = colors-1; col > 0; col-- ) {
+                r = hmap[col].r; g = hmap[col].g; b = hmap[col].b;
+                for( i = 0; i < col; i++ ) {
+                    register int tmp;
+
+                    tmp = hmap[i].r - r; dist = tmp * tmp;
+                    tmp = hmap[i].g - g; dist += tmp * tmp;
+                    tmp = hmap[i].b - b; dist += tmp * tmp;
+
+                    if( dist <= maxdist ) {
+                        int sum = hmap[i].count + hmap[col].count;
+
+                        hmap[i].r = (hmap[i].r * hmap[i].count + 
+                                     r * hmap[col].count + sum/2)/sum;
+                        hmap[i].g = (hmap[i].g * hmap[i].count + 
+                                     g * hmap[col].count + sum/2)/sum;
+                        hmap[i].b = (hmap[i].b * hmap[i].count + 
+                                     b * hmap[col].count + sum/2)/sum;
+                        hmap[i].count = sum;
+
+                        hmap[col] = hmap[i];    /* temp store */
+                        for( tmp = i-1; 
+                             tmp >= 0 && hmap[tmp].count < hmap[col].count; 
+                             tmp-- )
+                            hmap[tmp+1] = hmap[tmp];
+                        hmap[tmp+1] = hmap[col];
+
+                        for( tmp = col; tmp < colors-1; tmp++ )
+                            hmap[tmp] = hmap[tmp+1];
+                        if( --colors <= maxcolors )
+                            goto out;
+                        break;
+                    }
+                }
+            }
+#ifdef DEBUG
+            pm_message("\tmaxdist=%ld: %d colors left", maxdist, colors);
+#endif
+        }
+    }
+out:
+    pm_message("%d colors in HAM colormap", colors);
+
+    cmap = ppm_allocrow(colors);
+    *colorsP = colors;
+
+    for( i = 0; i < colors; i++ ) {
+        r = hmap[i].r; g = hmap[i].g; b = hmap[i].b;
+        PPM_ASSIGN(cmap[i], r, g, b);
+    }
+
+    ppm_freerow(hmap);
+    return cmap;
+}
+
+
+static void
+ppm_to_ham(fp, cols, rows, maxval, colormap, colors, cmapmaxval, hamplanes)
+    FILE *fp;
+    int cols, rows, maxval;
+    pixel *colormap;
+    int colors, cmapmaxval, hamplanes;
+{
+    int hamcolors, nPlanes, i, hammaxval;
+    long oldsize, bodysize, formsize, cmapsize;
+    int *table = NULL;
+
+    if( maskmethod == mskHasTransparentColor ) {
+        pm_message("masking method '%s' not usable with HAM - "
+                   "using '%s' instead",
+                   mskNAME[mskHasTransparentColor], mskNAME[mskHasMask]);
+        maskmethod = mskHasMask;
+    }
+
+    hamcolors = 1 << (hamplanes-2);
+    hammaxval = pm_bitstomaxval(hamplanes-2);
+
+    if( colors == 0 ) {
+        /* no colormap, make our own */
+        switch( hammapmode ) {
+            case HAMMODE_GRAY:
+                colors = hamcolors;
+                MALLOCARRAY_NOFAIL(colormap, colors);
+#ifdef DEBUG
+                pm_message("generating grayscale colormap");
+#endif
+                table = make_val_table(hammaxval, MAXCOLVAL);
+                for( i = 0; i < colors; i++ )
+                    PPM_ASSIGN(colormap[i], table[i], table[i], table[i]);
+                free(table);
+                cmapmaxval = MAXCOLVAL;
+                break;
+            case HAMMODE_FIXED: {
+                int entries, val;
+                double step;
+
+#ifdef DEBUG
+                pm_message("generating rgb colormap");
+#endif
+                /* generate a colormap of 7 "rays" in an RGB color cube:
+                        r, g, b, r+g, r+b, g+b, r+g+b
+                   we need one colormap entry for black, so the number of
+                   entries per ray is (maxcolors-1)/7 */
+
+                entries = (hamcolors-1)/7;
+                colors = 7*entries+1;
+                MALLOCARRAY_NOFAIL(colormap, colors);
+                step = (double)MAXCOLVAL / (double)entries;
+
+                PPM_ASSIGN(colormap[0], 0, 0, 0);
+                for( i = 1; i <= entries; i++ ) {
+                    val = (int)((double)i * step);
+                    PPM_ASSIGN(colormap[          i], val,   0,   0); /* r */
+                    PPM_ASSIGN(colormap[  entries+i],   0, val,   0); /* g */
+                    PPM_ASSIGN(colormap[2*entries+i],   0,   0, val); /* b */
+                    PPM_ASSIGN(colormap[3*entries+i], val, val,   0); /* r+g */
+                    PPM_ASSIGN(colormap[4*entries+i], val,   0, val); /* r+b */
+                    PPM_ASSIGN(colormap[5*entries+i],   0, val, val); /* g+b */
+                    PPM_ASSIGN(colormap[6*entries+i], val, val, val); /*r+g+b*/
+                }
+                cmapmaxval = MAXCOLVAL;
+            }
+            break;
+            case HAMMODE_RGB4:
+                colormap = compute_ham_cmap(cols, rows, maxval, hamcolors, 
+                                            &colors, 4);
+                cmapmaxval = 15;
+                break;
+            case HAMMODE_RGB5:
+                colormap = compute_ham_cmap(cols, rows, maxval, 
+                                            hamcolors, &colors, 5);
+                cmapmaxval = 31;
+                break;
+            default:
+                pm_error("ppm_to_ham(): unknown hammapmode - can't happen");
+        }
+    }
+    else {
+        hammapmode = HAMMODE_MAPFILE;
+        if( colors > hamcolors ) {
+            pm_message("colormap too large - using first %d colors", 
+                       hamcolors);
+            colors = hamcolors;
+        }
+    }
+
+    if( cmapmaxval != maxval ) {
+        int i, *table;
+        pixel *newcmap;
+
+        newcmap = ppm_allocrow(colors);
+        table = make_val_table(cmapmaxval, maxval);
+        for( i = 0; i < colors; i++ )
+            PPM_ASSIGN(newcmap[i], 
+                       table[PPM_GETR(colormap[i])], 
+                       table[PPM_GETG(colormap[i])], 
+                       table[PPM_GETB(colormap[i])]);
+        free(table);
+        ppm_freerow(colormap);
+        colormap = newcmap;
+    }
+    if( sortcmap )
+        ppm_sortcolorrow(colormap, colors, PPM_STDSORT);
+
+    nPlanes = hamplanes;
+    cmapsize = colors * 3;
+
+    bodysize = oldsize = rows * TOTALPLANES(nPlanes) * RowBytes(cols);
+    if( DO_COMPRESS ) {
+        bodysize = do_ham_body(fp, NULL, cols, rows, maxval, 
+                               hammaxval, nPlanes, colormap, colors);
+        /*bodysize = do_ham_body(fp, NULL, cols, 
+          rows, maxval, hammaxval, nPlanes, colbits, nocolor);*/
+        if( bodysize > oldsize )
+            pm_message("warning - %s compression increases BODY size "
+                       "by %ld%%", 
+                       cmpNAME[compmethod], 100*(bodysize-oldsize)/oldsize);
+        else
+            pm_message("BODY compression (%s): %ld%%", 
+                       cmpNAME[compmethod], 100*(oldsize-bodysize)/oldsize);
+    }
+
+
+    formsize =
+        4 +                                 /* ILBM */
+        4 + 4 + BitMapHeaderSize +          /* BMHD size header */
+        4 + 4 + CAMGChunkSize +             /* CAMG size viewportmodes */
+        4 + 4 + cmapsize + PAD(cmapsize) +  /* CMAP size colormap */
+        4 + 4 + bodysize + PAD(bodysize) +  /* BODY size data */
+        length_of_text_chunks();
+
+    put_big_long(ID_FORM);
+    put_big_long(formsize);
+    put_big_long(ID_ILBM);
+
+    write_bmhd(cols, rows, nPlanes);
+    write_text_chunks();
+    write_camg();       /* HAM requires CAMG chunk */
+    write_cmap(colormap, colors, maxval);
+
+    /* write body */
+    put_big_long(ID_BODY);
+    put_big_long(bodysize);
+    if( DO_COMPRESS )
+        write_body_rows();
+    else
+        do_ham_body(fp, NULL, cols, rows, maxval, hammaxval, 
+                    nPlanes, colormap, colors);
+}
+
+
+static long
+#ifdef __STDC__
+do_ham_body(FILE *ifP, FILE *ofp, int cols, int rows,
+            pixval maxval, pixval hammaxval, int nPlanes,
+            pixel *colormap, int colors)
+#else
+do_ham_body(ifP, ofp, cols, rows, maxval, hammaxval, nPlanes, colormap, colors)
+    FILE *ifP, *ofp;
+    int cols, rows;
+    pixval maxval;      /* maxval of image color components */
+    pixval hammaxval;   /* maxval of HAM color changes */
+    int nPlanes;
+    pixel *colormap;
+    int colors;
+#endif
+{
+    register int col, row, i;
+    rawtype *raw_rowbuf;
+    ppm_fs_info *fi = NULL;
+    colorhash_table cht, cht2;
+    long bodysize = 0;
+    int *itoh;      /* table image -> ham */
+    int usehash = 1;
+    int colbits;
+    int hamcode_red, hamcode_green, hamcode_blue;
+
+    MALLOCARRAY_NOFAIL(raw_rowbuf, cols);
+
+    cht = ppm_colorrowtocolorhash(colormap, colors);
+    cht2 = ppm_alloccolorhash();
+    colbits = pm_maxvaltobits(hammaxval);
+
+    hamcode_red   = HAMCODE_RED << colbits;
+    hamcode_green = HAMCODE_GREEN << colbits;
+    hamcode_blue  = HAMCODE_BLUE << colbits;
+
+    itoh = make_val_table(maxval, hammaxval);
+
+    if( floyd )
+        fi = ppm_fs_init(cols, maxval, 0);
+
+    for( row = 0; row < rows; row++ ) {
+        int noprev;
+        int spr, spg, spb;   /* scaled values of previous pixel */
+        int upr, upg, upb;   /* unscaled values of previous pixel, for floyd */
+        pixel *prow;
+
+        noprev = 1;
+        prow = next_pixrow(ifP, row);
+        for( col = ppm_fs_startrow(fi, prow); 
+             col < cols; 
+             col = ppm_fs_next(fi, col) ) {
+
+            pixel const p = prow[col];
+
+            /* unscaled values of current pixel */
+            pixval const ur = PPM_GETR(p);
+            pixval const ug = PPM_GETG(p);
+            pixval const ub = PPM_GETB(p);
+
+            /* scaled values of current pixel */
+            int const sr = itoh[ur];
+            int const sg = itoh[ug];
+            int const sb = itoh[ub];
+
+            i = ppm_lookupcolor(cht, &p);
+            if( i == -1 ) { /* no matching color in cmap, find closest match */
+                int ucr, ucg, ucb;  /* unscaled values of colormap entry */
+
+                if(  hammapmode == HAMMODE_GRAY ) {
+                    if( maxval <= 255 ) 
+                        /* Use fast approximation to 
+                           0.299 r + 0.587 g + 0.114 b. */
+                        i = (int)ppm_fastlumin(p);
+                    else 
+                        /* Can't use fast approximation, 
+                           so fall back on floats. 
+                        */
+                        i = (int)(PPM_LUMIN(p) + 0.5); 
+                            /* -IUW added '+ 0.5' */
+                    i = itoh[i];
+                }
+                else {
+                    i = ppm_lookupcolor(cht2, &p);
+                    if( i == -1 ) {
+                        i = ppm_findclosestcolor(colormap, colors, &p);
+                        if( usehash ) {
+                            if( ppm_addtocolorhash(cht2, &p, i) < 0 ) {
+                                pm_message("out of memory "
+                                           "adding to hash table, "
+                                           "proceeding without it");
+                                usehash = 0;
+                            }
+                        }
+                    }
+                }
+                ucr = PPM_GETR(colormap[i]); 
+                ucg = PPM_GETG(colormap[i]); 
+                ucb = PPM_GETB(colormap[i]);
+
+                if( noprev ) {  /* no previous pixel, must use colormap */
+                    raw_rowbuf[col] = i;    /* + (HAMCODE_CMAP << colbits) */
+                    upr = ucr;          upg = ucg;          upb = ucb;
+                    spr = itoh[upr];    spg = itoh[upg];    spb = itoh[upb];
+                    noprev = 0;
+                } else {
+                    register long di, dr, dg, db;
+                    int scr, scg, scb;   /* scaled values of colormap entry */
+
+                    scr = itoh[ucr]; scg = itoh[ucg]; scb = itoh[ucb];
+
+                    /* compute distances for the four options */
+#if 1
+                    dr = abs(sg - spg) + abs(sb - spb);
+                    dg = abs(sr - spr) + abs(sb - spb);
+                    db = abs(sr - spr) + abs(sg - spg);
+                    di = abs(sr - scr) + abs(sg - scg) + abs(sb - scb);
+#else
+                    dr = (sg - spg)*(sg - spg) + (sb - spb)*(sb - spb);
+                    dg = (sr - spr)*(sr - spr) + (sb - spb)*(sb - spb);
+                    db = (sr - spr)*(sr - spr) + (sg - spg)*(sg - spg);
+                    di = (sr - scr)*(sr - scr) + (sg - scg)*(sg - scg) +
+                        (sb - scb)*(sb - scb);
+#endif
+
+                    if( di <= dr && di <= dg && di <= db ) {    
+                        /* prefer colormap lookup */
+                        raw_rowbuf[col] = i; 
+                        upr = ucr;  upg = ucg;  upb = ucb;
+                        spr = scr;  spg = scg;  spb = scb;
+                    }
+                    else
+                    if( db <= dr && db <= dg ) {
+                        raw_rowbuf[col] = sb + hamcode_blue; 
+                        spb = sb;
+                        upb = ub;
+                    }
+                    else
+                    if( dr <= dg ) {
+                        raw_rowbuf[col] = sr + hamcode_red;  
+                        spr = sr;
+                        upr = ur;
+                    }
+                    else {
+                        raw_rowbuf[col] = sg + hamcode_green;
+                        spg = sg;
+                        upg = ug;
+                    }
+                }
+            }
+            else {  /* prefect match in cmap */
+                raw_rowbuf[col] = i;    /* + (HAMCODE_CMAP << colbits) */
+                upr = PPM_GETR(colormap[i]); 
+                upg = PPM_GETG(colormap[i]); 
+                upb = PPM_GETB(colormap[i]);
+                spr = itoh[upr];            
+                spg = itoh[upg];            
+                spb = itoh[upb];
+            }
+            ppm_fs_update3(fi, col, upr, upg, upb);
+        }
+        bodysize += encode_row(ofp, raw_rowbuf, cols, nPlanes);
+        if( maskmethod == mskHasMask )
+            bodysize += encode_maskrow(ofp, raw_rowbuf, cols);
+        ppm_fs_endrow(fi);
+    }
+    if( ofp && ODD(bodysize) )
+        put_byte(0);
+
+    free(itoh);
+
+    /* clean up */
+    free(raw_rowbuf);
+    ppm_fs_free(fi);
+
+    return bodysize;
+}
+
+
+/************ deep (24-bit) ************/
+
+static long do_deep_body      ARGS((FILE *ifP, FILE *ofp, 
+                                    int cols, int rows, 
+                                    pixval maxval, int bitspercolor));
+
+static void
+ppm_to_deep(fp, cols, rows, maxval, bitspercolor)
+    FILE *fp;
+    int cols, rows, maxval, bitspercolor;
+{
+    int nPlanes;
+    long bodysize, oldsize, formsize;
+
+    if( maskmethod == mskHasTransparentColor ) {
+        pm_message("masking method '%s' not usable with deep ILBM - "
+                   "using '%s' instead",
+                    mskNAME[mskHasTransparentColor], mskNAME[mskHasMask]);
+        maskmethod = mskHasMask;
+    }
+
+    nPlanes = 3*bitspercolor;
+
+    bodysize = oldsize = rows * TOTALPLANES(nPlanes) * RowBytes(cols);
+    if( DO_COMPRESS ) {
+        bodysize = do_deep_body(fp, NULL, cols, rows, maxval, bitspercolor);
+        if( bodysize > oldsize )
+            pm_message("warning - %s compression increases BODY size by %ld%%",
+                       cmpNAME[compmethod], 100*(bodysize-oldsize)/oldsize);
+        else
+            pm_message("BODY compression (%s): %ld%%", 
+                       cmpNAME[compmethod], 100*(oldsize-bodysize)/oldsize);
+    }
+
+
+    formsize =
+        4 +                                 /* ILBM */
+        4 + 4 + BitMapHeaderSize +          /* BMHD size header */
+        4 + 4 + bodysize + PAD(bodysize) +  /* BODY size data */
+        length_of_text_chunks();
+    if( gen_camg )
+        formsize += 4 + 4 + CAMGChunkSize;  /* CAMG size viewportmodes */
+
+    put_big_long(ID_FORM);
+    put_big_long(formsize);
+    put_big_long(ID_ILBM);
+
+    write_bmhd(cols, rows, nPlanes);
+    write_text_chunks();
+    if( gen_camg )
+        write_camg();
+
+    /* write body */
+    put_big_long(ID_BODY);
+    put_big_long(bodysize);
+    if( DO_COMPRESS )
+        write_body_rows();
+    else
+        do_deep_body(fp, stdout, cols, rows, maxval, bitspercolor);
+}
+
+
+static long
+#if __STDC__
+do_deep_body(FILE *ifP, FILE *ofp, int cols, int rows, pixval maxval, 
+             int bitspercolor)
+#else
+do_deep_body(ifP, ofp, cols, rows, maxval, bitspercolor)
+    FILE *ifP, *ofp;
+    int cols, rows;
+    pixval maxval;
+    int bitspercolor;
+#endif
+{
+    register int row, col;
+    pixel *pP;
+    int *table = NULL;
+    long bodysize = 0;
+    rawtype *redbuf, *greenbuf, *bluebuf;
+    int newmaxval;
+
+    MALLOCARRAY_NOFAIL(redbuf,   cols);
+    MALLOCARRAY_NOFAIL(greenbuf, cols);
+    MALLOCARRAY_NOFAIL(bluebuf,  cols);
+
+    newmaxval = pm_bitstomaxval(bitspercolor);
+    if( maxval != newmaxval ) {
+        pm_message("maxval is not %d - automatically rescaling colors", 
+                   newmaxval);
+        table = make_val_table(maxval, newmaxval);
+    }
+
+    for( row = 0; row < rows; row++ ) {
+        pP = next_pixrow(ifP, row);
+        if( table ) {
+            for( col = 0; col < cols; col++, pP++ ) {
+                redbuf[col]     = table[PPM_GETR(*pP)];
+                greenbuf[col]   = table[PPM_GETG(*pP)];
+                bluebuf[col]    = table[PPM_GETB(*pP)];
+            }
+        }
+        else {
+            for( col = 0; col < cols; col++, pP++ ) {
+                redbuf[col]     = PPM_GETR(*pP);
+                greenbuf[col]   = PPM_GETG(*pP);
+                bluebuf[col]    = PPM_GETB(*pP);
+            }
+        }
+        bodysize += encode_row(ofp, redbuf,   cols, bitspercolor);
+        bodysize += encode_row(ofp, greenbuf, cols, bitspercolor);
+        bodysize += encode_row(ofp, bluebuf,  cols, bitspercolor);
+        if( maskmethod == mskHasMask )
+            bodysize += encode_maskrow(ofp, redbuf, cols);
+    }
+    if( ofp && ODD(bodysize) )
+        put_byte(0);
+
+    /* clean up */
+    if( table )
+        free(table);
+    free(redbuf);
+    free(greenbuf);
+    free(bluebuf);
+
+    return bodysize;
+}
+
+
+/************ direct color ************/
+
+static long do_dcol_body      ARGS((FILE *ifP, FILE *ofp, int cols, int rows, 
+                                    pixval maxval, DirectColor *dcol));
+
+static void
+ppm_to_dcol(fp, cols, rows, maxval, dcol)
+    FILE *fp;
+    int cols, rows, maxval;
+    DirectColor *dcol;
+{
+    int nPlanes;
+    long bodysize, oldsize, formsize;
+
+    if( maskmethod == mskHasTransparentColor ) {
+        pm_message("masking method '%s' not usable with deep ILBM - "
+                   "using '%s' instead",
+                   mskNAME[mskHasTransparentColor], mskNAME[mskHasMask]);
+        maskmethod = mskHasMask;
+    }
+
+    nPlanes = dcol->r + dcol->g + dcol->b;
+
+    bodysize = oldsize = rows * TOTALPLANES(nPlanes) * RowBytes(cols);
+    if( DO_COMPRESS ) {
+        bodysize = do_dcol_body(fp, NULL, cols, rows, maxval, dcol);
+        if( bodysize > oldsize )
+            pm_message("warning - %s compression increases BODY size by %ld%%",
+                       cmpNAME[compmethod], 
+                       100*(bodysize-oldsize)/oldsize);
+        else
+            pm_message("BODY compression (%s): %ld%%", cmpNAME[compmethod], 
+                       100*(oldsize-bodysize)/oldsize);
+    }
+
+
+    formsize =
+        4 +                                 /* ILBM */
+        4 + 4 + BitMapHeaderSize +          /* BMHD size header */
+        4 + 4 + DirectColorSize +           /* DCOL size dcol */
+        4 + 4 + bodysize + PAD(bodysize) +  /* BODY size data */
+        length_of_text_chunks();
+    if( gen_camg )
+        formsize += 4 + 4 + CAMGChunkSize;  /* CAMG size viewportmodes */
+
+    put_big_long(ID_FORM);
+    put_big_long(formsize);
+    put_big_long(ID_ILBM);
+
+    write_bmhd(cols, rows, nPlanes);
+    write_text_chunks();
+
+    put_big_long(ID_DCOL);
+    put_big_long(DirectColorSize);
+    put_byte(dcol->r);
+    put_byte(dcol->g);
+    put_byte(dcol->b);
+    put_byte(0);    /* pad */
+
+    if( gen_camg )
+        write_camg();
+
+    /* write body */
+    put_big_long(ID_BODY);
+    put_big_long(bodysize);
+    if( DO_COMPRESS )
+        write_body_rows();
+    else
+        do_dcol_body(fp, stdout, cols, rows, maxval, dcol);
+}
+
+
+static long
+#if __STDC__
+do_dcol_body(FILE *ifP, FILE *ofp, int cols, int rows, pixval maxval, 
+             DirectColor *dcol)
+#else
+do_dcol_body(ifP, ofp, cols, rows, maxval, dcol)
+    FILE *ifP, *ofp;
+    int cols, rows;
+    pixval maxval;
+    DirectColor *dcol;
+#endif
+{
+    register int row, col;
+    pixel *pP;
+    long bodysize = 0;
+    rawtype *redbuf, *greenbuf, *bluebuf;
+    int *redtable, *greentable, *bluetable;
+
+    MALLOCARRAY_NOFAIL(redbuf,   cols);
+    MALLOCARRAY_NOFAIL(greenbuf, cols);
+    MALLOCARRAY_NOFAIL(bluebuf,  cols);
+
+    redtable   = make_val_table(maxval, pm_bitstomaxval(dcol->r));
+    greentable = make_val_table(maxval, pm_bitstomaxval(dcol->g));
+    bluetable  = make_val_table(maxval, pm_bitstomaxval(dcol->b));
+
+    for( row = 0; row < rows; row++ ) {
+        pP = next_pixrow(ifP, row);
+        for( col = 0; col < cols; col++, pP++ ) {
+            redbuf[col]   = redtable[PPM_GETR(*pP)];
+            greenbuf[col] = greentable[PPM_GETG(*pP)];
+            bluebuf[col]  = bluetable[PPM_GETB(*pP)];
+        }
+        bodysize += encode_row(ofp, redbuf,   cols, dcol->r);
+        bodysize += encode_row(ofp, greenbuf, cols, dcol->g);
+        bodysize += encode_row(ofp, bluebuf,  cols, dcol->b);
+        if( maskmethod == mskHasMask )
+            bodysize += encode_maskrow(ofp, redbuf, cols);
+    }
+    if( ofp && ODD(bodysize) )
+        put_byte(0);
+
+    /* clean up */
+    free(redtable);
+    free(greentable);
+    free(bluetable);
+    free(redbuf);
+    free(greenbuf);
+    free(bluebuf);
+
+    return bodysize;
+}
+
+
+/************ normal colormapped ************/
+
+static long do_std_body     ARGS((FILE *ifP, FILE *ofp, int cols, int rows, 
+                                  pixval maxval, pixel *colormap, 
+                                  int colors, int nPlanes));
+
+static void
+ppm_to_std(fp, cols, rows, maxval, colormap, colors, cmapmaxval, 
+           maxcolors, nPlanes)
+    FILE *fp;
+    int cols, rows, maxval;
+    pixel *colormap;
+    int cmapmaxval, colors, maxcolors, nPlanes;
+{
+    long formsize, cmapsize, bodysize, oldsize;
+
+    if( maskmethod == mskHasTransparentColor ) {
+        if( transpColor ) {
+            transpIndex = 
+                ppm_addtocolorrow(colormap, &colors, maxcolors, transpColor);
+        }
+        else
+        if( colors < maxcolors )
+            transpIndex = colors;
+
+        if( transpIndex < 0 ) {
+            pm_message("too many colors for masking method '%s' - "
+                       "using '%s' instead",
+                       mskNAME[mskHasTransparentColor], mskNAME[mskHasMask]);
+            maskmethod = mskHasMask;
+        }
+    }
+
+    if( cmapmaxval != maxval ) {
+        int i, *table;
+        pixel *newcmap;
+
+        newcmap = ppm_allocrow(colors);
+        table = make_val_table(cmapmaxval, maxval);
+        for (i = 0; i < colors; ++i)
+            PPM_ASSIGN(newcmap[i], 
+                       table[PPM_GETR(colormap[i])], 
+                       table[PPM_GETG(colormap[i])], 
+                       table[PPM_GETB(colormap[i])]);
+        free(table);
+        colormap = newcmap;
+    }
+    if( sortcmap )
+        ppm_sortcolorrow(colormap, colors, PPM_STDSORT);
+
+    bodysize = oldsize = rows * TOTALPLANES(nPlanes) * RowBytes(cols);
+    if( DO_COMPRESS ) {
+        bodysize = do_std_body(fp, NULL, cols, rows, maxval, colormap, 
+                               colors, nPlanes);
+        if( bodysize > oldsize )
+            pm_message("warning - %s compression increases BODY size by %ld%%",
+                       cmpNAME[compmethod], 100*(bodysize-oldsize)/oldsize);
+        else
+            pm_message("BODY compression (%s): %ld%%", 
+                       cmpNAME[compmethod], 100*(oldsize-bodysize)/oldsize);
+    }
+
+    cmapsize = colors * 3;
+
+    formsize =
+        4 +                                 /* ILBM */
+        4 + 4 + BitMapHeaderSize +          /* BMHD size header */
+        4 + 4 + cmapsize + PAD(cmapsize) +  /* CMAP size colormap */
+        4 + 4 + bodysize + PAD(bodysize) +  /* BODY size data */
+        length_of_text_chunks();
+    if( gen_camg )
+        formsize += 4 + 4 + CAMGChunkSize;  /* CAMG size viewportmodes */
+
+    put_big_long(ID_FORM);
+    put_big_long(formsize);
+    put_big_long(ID_ILBM);
+
+    write_bmhd(cols, rows, nPlanes);
+    write_text_chunks();
+    if( gen_camg )
+        write_camg();
+    write_cmap(colormap, colors, maxval);
+
+    /* write body */
+    put_big_long(ID_BODY);
+    put_big_long(bodysize);
+    if( DO_COMPRESS )
+        write_body_rows();
+    else
+        do_std_body(fp, stdout, cols, rows, maxval, colormap, colors, nPlanes);
+}
+
+
+static long
+#if __STDC__
+do_std_body(FILE *ifP, FILE *ofp, int cols, int rows, pixval maxval,
+            pixel *colormap, int colors, int nPlanes)
+#else
+do_std_body(ifP, ofp, cols, rows, maxval, colormap, colors, nPlanes)
+    FILE *ifP, *ofp;
+    int cols, rows;
+    pixval maxval;
+    pixel *colormap;
+    int colors;
+    int nPlanes;
+#endif
+{
+    register int row, col, i;
+    pixel *pP;
+    rawtype *raw_rowbuf;
+    ppm_fs_info *fi = NULL;
+    long bodysize = 0;
+    int usehash = 1;
+    colorhash_table cht;
+
+    MALLOCARRAY_NOFAIL(raw_rowbuf, cols);
+    cht = ppm_colorrowtocolorhash(colormap, colors);
+    if( floyd )
+        fi = ppm_fs_init(cols, maxval, FS_ALTERNATE);
+
+    for( row = 0; row < rows; row++ ) {
+        pixel *prow;
+        prow = next_pixrow(ifP, row);
+
+        for( col = ppm_fs_startrow(fi, prow); 
+             col < cols; 
+             col = ppm_fs_next(fi, col) ) {
+            pP = &prow[col];
+
+            if( maskmethod == mskHasTransparentColor && 
+                maskrow[col] == PBM_WHITE )
+                i = transpIndex;
+            else {
+                /* Check hash table to see if we have already matched
+                   this color. 
+                */
+                i = ppm_lookupcolor(cht, pP);
+                if( i == -1 ) {
+                    i = ppm_findclosestcolor(colormap, colors, pP);    
+                        /* No; search colormap for closest match. */
+                    if( usehash ) {
+                        if( ppm_addtocolorhash(cht, pP, i) < 0 ) {
+                            pm_message("out of memory adding to hash table, "
+                                       "proceeding without it");
+                            usehash = 0;
+                        }
+                    }
+                }
+            }
+            raw_rowbuf[col] = i;
+            ppm_fs_update(fi, col, &colormap[i]);
+        }
+        bodysize += encode_row(ofp, raw_rowbuf, cols, nPlanes);
+        if( maskmethod == mskHasMask )
+            bodysize += encode_maskrow(ofp, raw_rowbuf, cols);
+        ppm_fs_endrow(fi);
+    }
+    if( ofp && ODD(bodysize) )
+        put_byte(0);
+
+    /* clean up */
+    ppm_freecolorhash(cht);
+    free(raw_rowbuf);
+    ppm_fs_free(fi);
+
+    return bodysize;
+}
+
+/************ RGB8 ************/
+
+static void
+ppm_to_rgb8(ifP, cols, rows, maxval)
+    FILE *ifP;
+    int cols, rows;
+    int maxval;
+{
+    long bodysize, oldsize, formsize;
+    pixel *pP;
+    int *table = NULL;
+    int row, col1, col2, compr_len, len;
+    unsigned char *compr_row;
+
+    maskmethod = 0;     /* no masking - RGB8 uses genlock bits */
+    compmethod = 4;     /* RGB8 files are always compressed */
+    MALLOCARRAY_NOFAIL(compr_row, cols * 4);
+
+    if( maxval != 255 ) {
+        pm_message("maxval is not 255 - automatically rescaling colors");
+        table = make_val_table(maxval, 255);
+    }
+
+    oldsize = cols * rows * 4;
+    bodysize = 0;
+    for( row = 0; row < rows; row++ ) {
+        pP = next_pixrow(ifP, row);
+        compr_len = 0;
+        for( col1 = 0; col1 < cols; col1 = col2 ) {
+            col2 = col1 + 1;
+            if( maskrow ) {
+                while( col2 < cols && PPM_EQUAL(pP[col1], pP[col2]) && 
+                       maskrow[col1] == maskrow[col2] )
+                    col2++;
+            }
+            else {
+                while( col2 < cols && PPM_EQUAL(pP[col1], pP[col2]) )
+                    col2++;
+            }
+            len = col2 - col1;
+            while( len ) {
+                int count;
+                count = (len > 127 ? 127 : len);
+                len -= count;
+                if( table ) {
+                    compr_row[compr_len++] = table[PPM_GETR(pP[col1])];
+                    compr_row[compr_len++] = table[PPM_GETG(pP[col1])];
+                    compr_row[compr_len++] = table[PPM_GETB(pP[col1])];
+                }
+                else {
+                    compr_row[compr_len++] = PPM_GETR(pP[col1]);
+                    compr_row[compr_len++] = PPM_GETG(pP[col1]);
+                    compr_row[compr_len++] = PPM_GETB(pP[col1]);
+                }
+                compr_row[compr_len] = count;
+                if( maskrow && maskrow[col1] == PBM_WHITE )
+                    compr_row[compr_len] |= 1<<7;     /* genlock bit */
+                ++compr_len;
+            }
+        }
+        store_bodyrow(compr_row, compr_len);
+        bodysize += compr_len;
+    }
+
+    pm_message("BODY compression: %ld%%", 100*(oldsize-bodysize)/oldsize);
+
+    formsize =
+        4 +                                 /* RGB8 */
+        4 + 4 + BitMapHeaderSize +          /* BMHD size header */
+        4 + 4 + CAMGChunkSize +             /* CAMG size viewportmode */
+        4 + 4 + bodysize + PAD(bodysize) +  /* BODY size data */
+        length_of_text_chunks();
+
+    /* write header */
+    put_big_long(ID_FORM);
+    put_big_long(formsize);
+    put_big_long(ID_RGB8);
+
+    write_bmhd(cols, rows, 25);
+    write_text_chunks();
+    write_camg();               /* RGB8 requires CAMG chunk */
+
+    put_big_long(ID_BODY);
+    put_big_long(bodysize);
+    write_body_rows();
+}
+
+
+/************ RGBN ************/
+
+static void
+ppm_to_rgbn(ifP, cols, rows, maxval)
+    FILE *ifP;
+    int cols, rows;
+    int maxval;
+{
+    long bodysize, oldsize, formsize;
+    pixel *pP;
+    int *table = NULL;
+    int row, col1, col2, compr_len, len;
+    unsigned char *compr_row;
+
+    maskmethod = 0;     /* no masking - RGBN uses genlock bits */
+    compmethod = 4;     /* RGBN files are always compressed */
+    MALLOCARRAY_NOFAIL(compr_row, cols * 2);
+
+    if( maxval != 15 ) {
+        pm_message("maxval is not 15 - automatically rescaling colors");
+        table = make_val_table(maxval, 15);
+    }
+
+    oldsize = cols * rows * 2;
+    bodysize = 0;
+    for( row = 0; row < rows; row++ ) {
+        pP = next_pixrow(ifP, row);
+        compr_len = 0;
+        for( col1 = 0; col1 < cols; col1 = col2 ) {
+            col2 = col1 + 1;
+            if( maskrow ) {
+                while( col2 < cols && PPM_EQUAL(pP[col1], pP[col2]) && 
+                       maskrow[col1] == maskrow[col2] )
+                    col2++;
+            }
+            else {
+                while( col2 < cols && PPM_EQUAL(pP[col1], pP[col2]) )
+                    col2++;
+            }
+            len = col2 - col1;
+            while( len ) {
+                int count;
+                count = (len > 65535 ? 65535 : len);
+                len -= count;
+                if( table ) {
+                    compr_row[compr_len]  = table[PPM_GETR(pP[col1])] << 4;
+                    compr_row[compr_len] |= table[PPM_GETG(pP[col1])];
+                    ++compr_len;
+                    compr_row[compr_len]  = table[PPM_GETB(pP[col1])] << 4;
+                }
+                else {
+                    compr_row[compr_len]  = PPM_GETR(pP[col1]) << 4;
+                    compr_row[compr_len] |= PPM_GETG(pP[col1]);
+                    ++compr_len;
+                    compr_row[compr_len]  = PPM_GETB(pP[col1]) << 4;
+                }
+                if( maskrow && maskrow[col1] == PBM_WHITE )
+                    compr_row[compr_len] |= 1<<3;   /* genlock bit */
+                if( count <= 7 )
+                    compr_row[compr_len++] |= count;  /* 3 bit repeat count */
+                else {
+                    ++compr_len;                  /* 3 bit repeat count = 0 */
+                    if( count <= 255 )
+                        compr_row[compr_len++] = (unsigned char)count;  
+                            /* byte repeat count */
+                    else {
+                        compr_row[compr_len++] = (unsigned char)0;   
+                            /* byte repeat count = 0 */
+                        compr_row[compr_len++] = (count >> 8) & 0xff; 
+                            /* word repeat count MSB */
+                        compr_row[compr_len++] = count & 0xff;    
+                            /* word repeat count LSB */
+                    }
+                }
+            }
+        }
+        store_bodyrow(compr_row, compr_len);
+        bodysize += compr_len;
+    }
+
+    pm_message("BODY compression: %ld%%", 100*(oldsize-bodysize)/oldsize);
+
+    formsize =
+        4 +                                 /* RGBN */
+        4 + 4 + BitMapHeaderSize +          /* BMHD size header */
+        4 + 4 + CAMGChunkSize +             /* CAMG size viewportmode */
+        4 + 4 + bodysize + PAD(bodysize) +  /* BODY size data */
+        length_of_text_chunks();
+
+    /* write header */
+    put_big_long(ID_FORM);
+    put_big_long(formsize);
+    put_big_long(ID_RGBN);
+
+    write_bmhd(cols, rows, 13);
+    write_text_chunks();
+    write_camg();               /* RGBN requires CAMG chunk */
+
+    put_big_long(ID_BODY);
+    put_big_long(bodysize);
+    write_body_rows();
+}
+
+
+/************ multipalette ************/
+
+#ifdef ILBM_PCHG
+static pixel *ppmslice[2];  /* need 2 for laced ILBMs, else 1 */
+
+void ppm_to_pchg()
+{
+/*
+    read first slice
+    build a colormap from this slice
+    select upto <maxcolors> colors
+    build colormap from selected colors
+    map slice to colormap
+    write slice
+    while( !finished ) {
+        read next slice
+        compute distances for each pixel and select upto
+            <maxchangesperslice> unused colors in this slice
+        modify selected colors to the ones with maximum(?) distance
+        map slice to colormap
+        write slice
+    }
+
+
+    for HAM use a different mapping:
+        compute distance to closest color in colormap
+        if( there is no matching color in colormap ) {
+            compute distances for the three "modify" cases
+            use the shortest distance from the four cases
+        }
+*/
+}
+#endif
+
+
+/************ ILBM functions ************/
+
+static int
+length_of_text_chunks ARGS((void))
+{
+    int len, n;
+
+    len = 0;
+    if( anno_chunk ) {
+        n = strlen(anno_chunk);
+        len += 4 + 4 + n + PAD(n);      /* ID chunksize text */
+    }
+    if( auth_chunk ) {
+        n = strlen(auth_chunk);
+        len += 4 + 4 + n + PAD(n);      /* ID chunksize text */
+    }
+    if( name_chunk ) {
+        n = strlen(name_chunk);
+        len += 4 + 4 + n + PAD(n);      /* ID chunksize text */
+    }
+    if( copyr_chunk ) {
+        n = strlen(copyr_chunk);
+        len += 4 + 4 + n + PAD(n);      /* ID chunksize text */
+    }
+    if( text_chunk ) {
+        n = strlen(text_chunk);
+        len += 4 + 4 + n + PAD(n);      /* ID chunksize text */
+    }
+    return len;
+}
+
+
+static void
+write_text_chunks ARGS((void))
+{
+    int n;
+
+    if( anno_chunk ) {
+        n = strlen(anno_chunk);
+        put_big_long(ID_ANNO);
+        put_big_long(n);
+        write_bytes((unsigned char *)anno_chunk, n);
+        if( ODD(n) )
+            put_byte(0);
+    }
+    if( auth_chunk ) {
+        n = strlen(auth_chunk);
+        put_big_long(ID_AUTH);
+        put_big_long(n);
+        write_bytes((unsigned char *)auth_chunk, n);
+        if( ODD(n) )
+            put_byte(0);
+    }
+    if( copyr_chunk ) {
+        n = strlen(copyr_chunk);
+        put_big_long(ID_copy);
+        put_big_long(n);
+        write_bytes((unsigned char *)copyr_chunk, n);
+        if( ODD(n) )
+            put_byte(0);
+    }
+    if( name_chunk ) {
+        n = strlen(name_chunk);
+        put_big_long(ID_NAME);
+        put_big_long(n);
+        write_bytes((unsigned char *)name_chunk, n);
+        if( ODD(n) )
+            put_byte(0);
+    }
+    if( text_chunk ) {
+        n = strlen(text_chunk);
+        put_big_long(ID_TEXT);
+        put_big_long(n);
+        write_bytes((unsigned char *)text_chunk, n);
+        if( ODD(n) )
+            put_byte(0);
+    }
+}
+
+
+static void
+write_cmap(colormap, colors, maxval)
+    pixel *colormap;
+    int colors, maxval;
+{
+    int cmapsize, i;
+
+    cmapsize = 3 * colors;
+
+    /* write colormap */
+    put_big_long(ID_CMAP);
+    put_big_long(cmapsize);
+    if( maxval != MAXCOLVAL ) {
+        int *table;
+        pm_message("maxval is not %d - automatically rescaling colors", 
+                   MAXCOLVAL);
+        table = make_val_table(maxval, MAXCOLVAL);
+        for( i = 0; i < colors; i++ ) {
+            put_byte(table[PPM_GETR(colormap[i])]);
+            put_byte(table[PPM_GETG(colormap[i])]);
+            put_byte(table[PPM_GETB(colormap[i])]);
+        }
+        free(table);
+    }
+    else {
+        for( i = 0; i < colors; i++ ) {
+            put_byte(PPM_GETR(colormap[i]));
+            put_byte(PPM_GETG(colormap[i]));
+            put_byte(PPM_GETB(colormap[i]));
+        }
+    }
+    if( ODD(cmapsize) )
+        put_byte(0);
+}
+
+
+static void
+write_bmhd(cols, rows, nPlanes)
+    int cols, rows, nPlanes;
+{
+    unsigned char xasp = 10, yasp = 10;
+
+    if( viewportmodes & vmLACE )
+        xasp *= 2;
+    if( viewportmodes & vmHIRES )
+        yasp *= 2;
+
+    put_big_long(ID_BMHD);
+    put_big_long(BitMapHeaderSize);
+
+    put_big_short(cols);
+    put_big_short(rows);
+    put_big_short(0);                       /* x-offset */
+    put_big_short(0);                       /* y-offset */
+    put_byte(nPlanes);                      /* no of planes */
+    put_byte(maskmethod);                   /* masking */
+    put_byte(compmethod);                   /* compression */
+    put_byte(BMHD_FLAGS_CMAPOK);            /* flags */
+    if( maskmethod == mskHasTransparentColor )
+        put_big_short(transpIndex);
+    else
+        put_big_short(0);
+    put_byte(xasp);                         /* x-aspect */
+    put_byte(yasp);                         /* y-aspect */
+    put_big_short(cols);                    /* pageWidth */
+    put_big_short(rows);                    /* pageHeight */
+}
+
+
+/* encode algorithm by Johan Widen (jw@jwdata.se) */
+static const unsigned char bitmask[] = {1, 2, 4, 8, 16, 32, 64, 128};
+
+static long
+encode_row(outfile, rawrow, cols, nPlanes)
+    FILE *outfile;  /* if non-NULL, write uncompressed row to this file */
+    rawtype *rawrow;
+    int cols, nPlanes;
+{
+    int plane, bytes;
+    long retbytes = 0;
+
+    bytes = RowBytes(cols);
+
+    /* Encode and write raw bytes in plane-interleaved form. */
+    for( plane = 0; plane < nPlanes; plane++ ) {
+        register int col, cbit;
+        register rawtype *rp;
+        register unsigned char *cp;
+        int mask;
+
+        mask = 1 << plane;
+        cbit = -1;
+        cp = coded_rowbuf-1;
+        rp = rawrow;
+        for( col = 0; col < cols; col++, cbit--, rp++ ) {
+            if( cbit < 0 ) {
+                cbit = 7;
+                *++cp = 0;
+            }
+            if( *rp & mask )
+                *cp |= bitmask[cbit];
+        }
+        if( outfile ) {
+            write_bytes(coded_rowbuf, bytes);
+            retbytes += bytes;
+        }
+        else
+            retbytes += compress_row(bytes);
+    }
+    return retbytes;
+}
+
+
+static long
+encode_maskrow(ofp, rawrow, cols)
+    FILE *ofp;
+    rawtype *rawrow;
+    int cols;
+{
+    int col;
+
+    for( col = 0; col < cols; col++ ) {
+        if( maskrow[col] == PBM_BLACK )
+            rawrow[col] = 1;
+        else
+            rawrow[col] = 0;
+    }
+    return encode_row(ofp, rawrow, cols, 1);
+}
+
+
+static int
+compress_row(bytes)
+    int bytes;
+{
+    int newbytes;
+
+    switch( compmethod ) {
+        case cmpByteRun1:
+            newbytes = runbyte1(bytes);
+            break;
+        default:
+            pm_error("compress_row(): unknown compression method %d", 
+                     compmethod);
+    }
+    store_bodyrow(compr_rowbuf, newbytes);
+
+    return newbytes;
+}
+
+
+static void
+store_bodyrow(row, len)
+    unsigned char *row;
+    int len;
+{
+    int idx = cur_block->used;
+    if( idx >= ROWS_PER_BLOCK ) {
+        MALLOCVAR_NOFAIL(cur_block->next);
+        cur_block = cur_block->next;
+        cur_block->used = idx = 0;
+        cur_block->next = NULL;
+    }
+    MALLOCARRAY_NOFAIL(cur_block->row[idx], len);
+    cur_block->len[idx] = len;
+    memcpy(cur_block->row[idx], row, len);
+    cur_block->used++;
+}
+
+
+static void
+write_body_rows ARGS((void))
+{
+    bodyblock *b;
+    int i;
+    long total = 0;
+
+    for( b = &firstblock; b != NULL; b = b->next ) {
+        for( i = 0; i < b->used; i++ ) {
+            write_bytes(b->row[i], b->len[i]);
+            total += b->len[i];
+        }
+    }
+    if( ODD(total) )
+        put_byte(0);
+}
+
+
+static void
+write_camg ARGS((void))
+{
+    put_big_long(ID_CAMG);
+    put_big_long(CAMGChunkSize);
+    put_big_long(viewportmodes);
+}
+
+
+/************ compression ************/
+
+
+/* runbyte1 algorithm by Robert A. Knop (rknop@mop.caltech.edu) */
+static int
+runbyte1(size)
+   int size;
+{
+    int in,out,count,hold;
+    register unsigned char *inbuf = coded_rowbuf;
+    register unsigned char *outbuf = compr_rowbuf;
+
+
+    in=out=0;
+    while( in<size ) {
+        if( (in<size-1) && (inbuf[in]==inbuf[in+1]) ) {    
+            /*Begin replicate run*/
+            for( count=0, hold=in; 
+                 in < size && inbuf[in] == inbuf[hold] && count < 128; 
+                 in++, count++)
+                ;
+            outbuf[out++]=(unsigned char)(char)(-count+1);
+            outbuf[out++]=inbuf[hold];
+        }
+        else {  /*Do a literal run*/
+            hold=out; out++; count=0;
+            while( ((in>=size-2)&&(in<size)) || 
+                   ((in<size-2) && ((inbuf[in]!=inbuf[in+1])
+                                    ||(inbuf[in]!=inbuf[in+2]))) ) {
+                outbuf[out++]=inbuf[in++];
+                if( ++count>=128 )
+                    break;
+            }
+            outbuf[hold]=count-1;
+        }
+    }
+    return(out);
+}
+
+
+
+/************ other utility functions ************/
+
+static void
+#if __STDC__
+put_big_short(short s)
+#else
+put_big_short(s)
+    short s;
+#endif
+{
+    if ( pm_writebigshort( stdout, s ) == -1 )
+        pm_error( "write error" );
+}
+
+
+static void
+put_big_long(l)
+    long l;
+{
+    if ( pm_writebiglong( stdout, l ) == -1 )
+        pm_error( "write error" );
+}
+
+
+static void
+write_bytes(buffer, bytes)
+    unsigned char *buffer;
+    int bytes;
+{
+    if( fwrite(buffer, 1, bytes, stdout) != bytes )
+        pm_error("write error");
+}
+
+
+static int *
+make_val_table(oldmaxval, newmaxval)
+    int oldmaxval, newmaxval;
+{
+    int i;
+    int *table;
+
+    MALLOCARRAY_NOFAIL(table, oldmaxval + 1);
+    for(i = 0; i <= oldmaxval; i++ )
+        table[i] = (i * newmaxval + oldmaxval/2) / oldmaxval;
+
+    return table;
+}
+
+
+
+static int  gFormat;
+static int  gCols;
+static int  gMaxval;
+
+static void
+init_read(fp, colsP, rowsP, maxvalP, formatP, readall)
+    FILE *fp;
+    int *colsP, *rowsP;
+    pixval *maxvalP;
+    int *formatP;
+    int readall;
+{
+    ppm_readppminit(fp, colsP, rowsP, maxvalP, formatP);
+    if( readall ) {
+        int row;
+
+        pixels = ppm_allocarray(*colsP, *rowsP);
+        for( row = 0; row < *rowsP; row++ )
+            ppm_readppmrow(fp, pixels[row], *colsP, *maxvalP, *formatP);
+        /* pixels = ppm_readppm(fp, colsP, rowsP, maxvalP); */
+    }
+    else {
+        pixrow = ppm_allocrow(*colsP);
+    }
+    gCols = *colsP;
+    gMaxval = *maxvalP;
+    gFormat = *formatP;
+}
+
+
+static pixel *
+next_pixrow(fp, row)
+    FILE *fp;
+    int row;
+{
+    if( pixels )
+        pixrow = pixels[row];
+    else {
+        ppm_readppmrow(fp, pixrow, gCols, gMaxval, gFormat);
+    }
+    if( maskrow ) {
+        int col;
+
+        if( maskfile )
+            pbm_readpbmrow(maskfile, maskrow, maskcols, maskformat);
+        else {
+            for( col = 0; col < gCols; col++ )
+                maskrow[col] = PBM_BLACK;
+        }
+        if( transpColor ) {
+            for( col = 0; col < gCols; col++ )
+                if( PPM_EQUAL(pixrow[col], *transpColor) )
+                    maskrow[col] = PBM_WHITE;
+        }
+    }
+    return pixrow;
+}
+
+
+
+int
+main(int argc, char ** argv) {
+
+    FILE * ifP;
+    int argn, rows, cols, format, nPlanes;
+    int ifmode, forcemode, maxplanes, fixplanes, mode;
+    int hamplanes;
+    int deepbits;   /* bits per color component in deep ILBM */
+    DirectColor dcol;
+#define MAXCOLORS       (1<<maxplanes)
+    pixval maxval;
+    pixel * colormap;
+    int colors = 0;
+    pixval cmapmaxval;      /* maxval of colors in cmap */
+    const char * mapfile;
+    const char * transpname;
+
+    ppm_init(&argc, argv);
+
+    colormap = NULL;  /* initial value */
+    ifmode = DEF_IFMODE; forcemode = MODE_NONE;
+    maxplanes = DEF_MAXPLANES; fixplanes = 0;
+    hamplanes = DEF_HAMPLANES;
+    deepbits = DEF_DEEPPLANES;
+    dcol.r = dcol.g = dcol.b = DEF_DCOLPLANES;
+    mapfile = transpname = NULL;
+
+    argn = 1;
+    while( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) {
+        if( pm_keymatch(argv[argn], "-ilbm", 5) ) {
+            if( forcemode == MODE_RGB8 || forcemode == MODE_RGBN )
+                forcemode = MODE_NONE;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-rgb8", 5) )
+            forcemode = MODE_RGB8;
+        else
+        if( pm_keymatch(argv[argn], "-rgbn", 5) )
+            forcemode = MODE_RGBN;
+        else
+        if( pm_keymatch(argv[argn], "-maxplanes", 4) || 
+            pm_keymatch(argv[argn], "-mp", 3) ) {
+            if( ++argn >= argc )
+                pm_error("-maxplanes requires a value");
+            maxplanes = get_int_val(argv[argn], argv[argn-1], 1, MAXPLANES);
+            fixplanes = 0;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-fixplanes", 4) || 
+            pm_keymatch(argv[argn], "-fp", 3) ) {
+            if( ++argn >= argc )
+                pm_error("-fixplanes requires a value");
+            fixplanes = get_int_val(argv[argn], argv[argn-1], 1, MAXPLANES);
+            maxplanes = fixplanes;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-mapfile", 4) ) {
+            if( ++argn >= argc )
+                pm_error("-mapfile requires a value");
+            mapfile = argv[argn];
+        }
+        else
+        if( pm_keymatch(argv[argn], "-mmethod", 3) ) {
+            if( ++argn >= argc )
+                pm_error("-mmethod requires a value");
+            maskmethod = get_mask_type(argv[argn]);
+            switch( maskmethod ) {
+                case mskNone:
+                case mskHasMask:
+                case mskHasTransparentColor:
+                    break;
+                default:
+                    pm_error("This program does not know how to handle "
+                             "masking method '%s'", 
+                             mskNAME[maskmethod]);
+            }
+        }
+        else
+        if( pm_keymatch(argv[argn], "-maskfile", 4) ) {
+            if( ++argn >= argc )
+                pm_error("-maskfile requires a value");
+            maskfile = pm_openr(argv[argn]);
+            if( maskmethod == mskNone )
+                maskmethod = mskHasMask;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-transparent", 3) ) {
+            if( ++argn >= argc )
+                pm_error("-transparent requires a value");
+            transpname = argv[argn];
+            if( maskmethod == mskNone )
+                maskmethod = mskHasTransparentColor;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-sortcmap", 5) )
+            sortcmap = 1;
+        else
+        if( pm_keymatch(argv[argn], "-cmaponly", 3) ) {
+            forcemode = MODE_CMAP;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-lace", 2) ) {
+            slicesize = 2;
+            viewportmodes |= vmLACE;
+            gen_camg = 1;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-nolace", 4) ) {
+            slicesize = 1;
+            viewportmodes &= ~vmLACE;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-hires", 3) ) {
+            viewportmodes |= vmHIRES;
+            gen_camg = 1;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-nohires", 5) )
+            viewportmodes &= ~vmHIRES;
+        else
+        if( pm_keymatch(argv[argn], "-camg", 5) ) {
+            char *tail;
+            long value = 0L;
+
+            if( ++argn >= argc )
+                pm_error("-camg requires a value");
+            value = strtol(argv[argn], &tail, 16);
+            /* TODO: should do some error checking here */
+            viewportmodes |= value;
+            gen_camg = 1;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-ecs", 2) ) {
+            maxplanes = ECS_MAXPLANES;
+            hamplanes = ECS_HAMPLANES;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-aga", 3) ) {
+            maxplanes = AGA_MAXPLANES;
+            hamplanes = AGA_HAMPLANES;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-hamplanes", 5) ) {
+            if( ++argn >= argc )
+                pm_error("-hamplanes requires a value");
+            hamplanes = get_int_val(argv[argn], argv[argn-1], 3, HAMMAXPLANES);
+        }
+        else
+        if( pm_keymatch(argv[argn], "-hambits", 5) ) {
+            if( ++argn >= argc )
+                pm_usage("-hambits requires a value");
+            hamplanes = 
+                get_int_val(argv[argn], argv[argn-1], 3, HAMMAXPLANES-2) +2;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-ham6", 5) ) {
+            hamplanes = ECS_HAMPLANES;
+            forcemode = MODE_HAM;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-ham8", 5) ) {
+            hamplanes = AGA_HAMPLANES;
+            forcemode = MODE_HAM;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-hammap", 5) ) {
+            if( ++argn >= argc )
+                pm_error("-hammap requires a value");
+            hammapmode = get_hammap_mode(argv[argn]);
+        }
+        else
+        if( pm_keymatch(argv[argn], "-hamif", 5) )
+            ifmode = MODE_HAM;
+        else
+        if( pm_keymatch(argv[argn], "-nohamif", 7) ) {
+            if( ifmode == MODE_HAM )
+                ifmode = MODE_NONE;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-hamforce", 4) )
+            forcemode = MODE_HAM;
+        else
+        if( pm_keymatch(argv[argn], "-nohamforce", 6) ) {
+            if( forcemode == MODE_HAM )
+                forcemode = MODE_NONE;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-24if", 4) ) {
+            ifmode = MODE_DEEP;
+            deepbits = 8;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-no24if", 6) ) {
+            if( ifmode == MODE_DEEP )
+                ifmode = MODE_NONE;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-24force", 3) ) {
+            forcemode = MODE_DEEP;
+            deepbits = 8;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-no24force", 5) ) {
+            if( forcemode == MODE_DEEP )
+                forcemode = MODE_NONE;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-deepplanes", 6) ) {
+            if( ++argn >= argc )
+                pm_error("-deepplanes requires a value");
+            deepbits = get_int_val(argv[argn], argv[argn-1], 3, 3*MAXPLANES);
+            if( deepbits % 3 != 0 )
+                pm_error("option \"%s\" argument value must be divisible by 3",
+                         argv[argn-1]);
+            deepbits /= 3;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-deepbits", 6) ) {
+            if( ++argn >= argc )
+                pm_error("-deepbits requires a value");
+            deepbits = get_int_val(argv[argn], argv[argn-1], 1, MAXPLANES);
+        }
+        else
+        if( pm_keymatch(argv[argn], "-deepif", 6) )
+            ifmode = MODE_DEEP;
+        else
+        if( pm_keymatch(argv[argn], "-nodeepif", 8) ) {
+            if( ifmode == MODE_DEEP )
+                ifmode = MODE_NONE;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-deepforce", 5) )
+            forcemode = MODE_DEEP;
+        else
+        if( pm_keymatch(argv[argn], "-nodeepforce", 7) ) {
+            if( forcemode == MODE_DEEP )
+                forcemode = MODE_NONE;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-dcif", 4) )
+            ifmode = MODE_DCOL;
+        else
+        if( pm_keymatch(argv[argn], "-nodcif", 6) ) {
+            if( ifmode == MODE_DCOL )
+                ifmode = MODE_NONE;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-dcforce", 4) )
+            forcemode = MODE_DCOL;
+        else
+        if( pm_keymatch(argv[argn], "-nodcforce", 6) ) {
+            if( forcemode == MODE_DCOL )
+                forcemode = MODE_NONE;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-dcbits", 4) || 
+            pm_keymatch(argv[argn], "-dcplanes", 4) ) {
+            if( argc - argn < 4 )
+                pm_error("-dcbits requires 4 arguments");
+            dcol.r = get_int_val(argv[argn+1], argv[argn], 1, MAXPLANES);
+            dcol.g = get_int_val(argv[argn+2], argv[argn], 1, MAXPLANES);
+            dcol.b = get_int_val(argv[argn+3], argv[argn], 1, MAXPLANES);
+            argn += 3;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-normal", 4) ) {
+            ifmode = forcemode = MODE_NONE;
+            compmethod = DEF_COMPRESSION;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-compress", 4) ) {
+            compr_force = 1;
+            if( compmethod == cmpNone )
+#if DEF_COMPRESSION == cmpNone
+                    compmethod = cmpByteRun1;
+#else
+                    compmethod = DEF_COMPRESSION;
+#endif
+        }
+        else
+        if( pm_keymatch(argv[argn], "-nocompress", 4) ) {
+            compr_force = 0;
+            compmethod = cmpNone;
+        }
+        else
+        if( pm_keymatch(argv[argn], "-cmethod", 4) ) {
+            if( ++argn >= argc )
+                pm_error("-cmethod requires a value");
+            compmethod = get_compr_method(argv[argn]);
+        }
+        else
+        if( pm_keymatch(argv[argn], "-floyd", 3) || 
+            pm_keymatch(argv[argn], "-fs", 3) )
+            floyd = 1;
+        else
+        if( pm_keymatch(argv[argn], "-nofloyd", 5) || 
+            pm_keymatch(argv[argn], "-nofs", 5) )
+            floyd = 0;
+        else
+        if( pm_keymatch(argv[argn], "-annotation", 3) ) {
+            if( ++argn >= argc )
+                pm_error("-annotation requires a value");
+            anno_chunk = argv[argn];
+        }
+        else
+        if( pm_keymatch(argv[argn], "-author", 3) ) {
+            if( ++argn >= argc )
+                pm_error("-author requires a value");
+            auth_chunk = argv[argn];
+        }
+        else
+        if( pm_keymatch(argv[argn], "-copyright", 4) ) {
+            if( ++argn >= argc )
+                pm_error("-copyright requires a value");
+            copyr_chunk = argv[argn];
+        }
+        else
+        if( pm_keymatch(argv[argn], "-name", 3) ) {
+            if( ++argn >= argc )
+                pm_error("-name requires a value");
+            name_chunk = argv[argn];
+        }
+        else
+        if( pm_keymatch(argv[argn], "-text", 3) ) {
+            if( ++argn >= argc )
+                pm_error("-text requires a value");
+            text_chunk = argv[argn];
+        }
+        else
+            pm_error("invalid option: %s", argv[argn]);
+        ++argn;
+    }
+
+    if( argn < argc ) {
+        ifP = pm_openr(argv[argn]);
+        ++argn;
+    }
+    else
+        ifP = stdin;
+
+    if( argn != argc )
+        pm_error("Program takes no arguments.");
+
+    mode = forcemode;
+    switch(forcemode) {
+        case MODE_HAM:
+            if (hammapmode == HAMMODE_RGB4 || hammapmode == HAMMODE_RGB5)
+                init_read(ifP, &cols, &rows, &maxval, &format, 1);
+            else
+                init_read(ifP, &cols, &rows, &maxval, &format, 0);
+            break;
+        case MODE_DCOL:
+        case MODE_DEEP:
+            mapfile = NULL;
+            init_read(ifP, &cols, &rows, &maxval, &format, 0);
+            break;
+        case MODE_RGB8:
+            mapfile = NULL;
+            init_read(ifP, &cols, &rows, &maxval, &format, 0);
+            break;
+        case MODE_RGBN:
+            mapfile = NULL;
+            init_read(ifP, &cols, &rows, &maxval, &format, 0);
+            break;
+        case MODE_CMAP:
+            /* Figure out the colormap. */
+            pm_message("computing colormap...");
+            colormap = ppm_mapfiletocolorrow(ifP, MAXCOLORS, &colors, 
+                                             &cmapmaxval);
+            if (colormap == NULL)
+                pm_error("too many colors - try doing a 'pnmquant %d'", 
+                         MAXCOLORS);
+            pm_message("%d colors found", colors);
+            break;
+        default:
+            if (mapfile)
+                init_read(ifP, &cols, &rows, &maxval, &format, 0);
+            else {
+                init_read(ifP, &cols, &rows, &maxval, &format, 1);  
+                    /* read file into memory */
+                pm_message("computing colormap...");
+                colormap = 
+                    ppm_computecolorrow(pixels, cols, rows, MAXCOLORS, 
+                                        &colors);
+                if (colormap) {
+                    cmapmaxval = maxval;
+                    pm_message("%d colors found", colors);
+                    nPlanes = pm_maxvaltobits(colors-1);
+                    if (fixplanes > nPlanes)
+                        nPlanes = fixplanes;
+                } else {  /* too many colors */
+                    mode = ifmode;
+                    report_too_many_colors(ifmode, maxplanes, hamplanes,
+                                           dcol, deepbits );
+                }
+            }
+    }
+
+    if (mapfile) {
+        FILE * mapfp;
+
+        pm_message("reading colormap file...");
+        mapfp = pm_openr(mapfile);
+        colormap = ppm_mapfiletocolorrow(mapfp, MAXCOLORS, &colors, 
+                                         &cmapmaxval);
+        pm_close(mapfp);
+        if (colormap == NULL)
+            pm_error("too many colors in mapfile for %d planes", maxplanes);
+        if (colors == 0)
+            pm_error("empty colormap??");
+        pm_message("%d colors found in colormap", colors);
+    }
+
+    if (maskmethod != mskNone) {
+        if (transpname) {
+            MALLOCVAR_NOFAIL(transpColor);
+            *transpColor = ppm_parsecolor(transpname, maxval);
+        }
+        if (maskfile) {
+            int maskrows;
+            pbm_readpbminit(maskfile, &maskcols, &maskrows, &maskformat);
+            if (maskcols < cols || maskrows < rows)
+                pm_error("maskfile too small - try scaling it");
+            if (maskcols > cols || maskrows > rows)
+                pm_message("warning - maskfile larger than image");
+        } else
+            maskcols = rows;
+        maskrow = pbm_allocrow(maskcols);
+    }
+
+    if (mode != MODE_CMAP) {
+        unsigned int i;
+        MALLOCARRAY_NOFAIL(coded_rowbuf, RowBytes(cols));
+        for (i = 0; i < RowBytes(cols); ++i)
+            coded_rowbuf[i] = 0;
+        if (DO_COMPRESS)
+            MALLOCARRAY_NOFAIL(compr_rowbuf, WORSTCOMPR(RowBytes(cols)));
+    }
+    
+    switch (mode) {
+        case MODE_HAM:
+            viewportmodes |= vmHAM;
+            ppm_to_ham(ifP, cols, rows, maxval, 
+                       colormap, colors, cmapmaxval, hamplanes);
+            break;
+        case MODE_DEEP:
+            ppm_to_deep(ifP, cols, rows, maxval, deepbits);
+            break;
+        case MODE_DCOL:
+            ppm_to_dcol(ifP, cols, rows, maxval, &dcol);
+            break;
+        case MODE_RGB8:
+            ppm_to_rgb8(ifP, cols, rows, maxval);
+            break;
+        case MODE_RGBN:
+            ppm_to_rgbn(ifP, cols, rows, maxval);
+            break;
+        case MODE_CMAP:
+            ppm_to_cmap(colormap, colors, cmapmaxval);
+            break;
+        default:
+            if (mapfile == NULL)
+                floyd = 0;          /* would only slow down conversion */
+            ppm_to_std(ifP, cols, rows, maxval, colormap, colors, 
+                       cmapmaxval, MAXCOLORS, nPlanes);
+            break;
+    }
+    pm_close(ifP);
+    return 0;
+}
diff --git a/converter/ppm/ppmtoleaf.c b/converter/ppm/ppmtoleaf.c
new file mode 100644
index 00000000..fa5fdaf5
--- /dev/null
+++ b/converter/ppm/ppmtoleaf.c
@@ -0,0 +1,250 @@
+/* ppmtoleaf.c - read a portable pixmap and produce a ileaf img file
+ *
+ * Copyright (C) 1994 by Bill O'Donnell.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  This software is provided "as is" without express or
+ * implied warranty.
+ *
+ * Known problems: pgms are not converted to leaf grayscales; they are
+ * converted to 8-bit color images with all gray for colors.
+ * 
+ */
+
+#include <stdio.h>
+#include "ppm.h"
+
+#define MAXCOLORS 256
+
+pixel **pixels;
+colorhash_table cht;
+
+int Red[MAXCOLORS], Green[MAXCOLORS], Blue[MAXCOLORS];
+
+
+
+static int
+colorstobpp( colors )
+int colors;
+{
+    int bpp;
+    
+    if ( colors <= 2 )
+	bpp = 1;
+    else if ( colors <= 256 )
+	bpp = 8;
+    else
+	bpp = 24;
+    return bpp;
+}
+
+
+
+static int
+GetPixel( x, y )
+int x, y;
+{
+    int color;
+    
+    color = ppm_lookupcolor( cht, &pixels[y][x] );
+    return color;
+}
+
+
+
+/* OK, this routine is not wicked efficient, but it is simple to follow
+   and it works. */
+static void
+leaf_writeimg(width, height, depth, ncolors, maxval)
+int width;
+int height;
+int depth;
+int ncolors;
+{
+    int i,row,col;
+    
+    /* NOTE: byte order in ileaf img file fmt is big-endian, always! */
+    
+    /* magic */
+    fputc(0x89, stdout);
+    fputc(0x4f, stdout);
+    fputc(0x50, stdout);
+    fputc(0x53, stdout);
+    
+    /* version 4 */
+    fputc(0x00, stdout);
+    fputc(0x04, stdout);
+    
+    /* h resolution: pixels/inch: say 75=screen resolution */
+    fputc(0x00, stdout);
+    fputc(75, stdout);
+    
+    /* v resolution: pixels/inch: say 75=screen resolution */
+    fputc(0x00, stdout);
+    fputc(75, stdout);
+    
+    /* unique id, could be anything */
+    fputc(0x01, stdout);
+    fputc(0x02, stdout);
+    fputc(0x03, stdout);
+    fputc(0x04, stdout);
+    
+    /* x offset, always zero */
+    fputc(0x00, stdout);
+    fputc(0x00, stdout);
+    
+    /* y offset, always zero */
+    fputc(0x00, stdout);
+    fputc(0x00, stdout);
+    
+    /* dimensions 64k x 64k max */
+    fputc((unsigned char)((width >> 8) & 0x00ff), stdout);
+    fputc((unsigned char)(width  & 0x00ff), stdout);
+    fputc((unsigned char)((height >> 8) & 0x00ff), stdout);
+    fputc((unsigned char)(height  & 0x00ff), stdout);
+    
+    /* depth */
+    fputc(0x00, stdout);
+    fputc((unsigned char)depth, stdout);
+    
+    /* compressed, 0=uncompressed, 1=compressed */
+    fputc(0x00, stdout);
+    
+    /* format, mono/gray = 0x20000000, RGB=0x29000000 */
+    if (depth == 1)
+	fputc(0x20, stdout);
+    else
+	fputc(0x29, stdout);
+    fputc(0x00, stdout);
+    fputc(0x00, stdout);
+    fputc(0x00, stdout);
+    
+    /* colormap size */
+    if (depth == 8)
+    {
+	fputc((unsigned char)((ncolors >> 8) & 0x00ff), stdout);
+	fputc((unsigned char)(ncolors  & 0x00ff), stdout);
+	for (i=0; i<256; i++)
+	    fputc((unsigned char) Red[i]*255/maxval, stdout);
+	for (i=0; i<256; i++)
+	    fputc((unsigned char) Green[i]*255/maxval, stdout);
+	for (i=0; i<256; i++)
+	    fputc((unsigned char) Blue[i]*255/maxval, stdout);
+	
+	for (row=0; row<height; row++) 
+	{
+	    for (col=0; col<width; col++) 
+		fputc(GetPixel(col, row), stdout);
+	    if (width % 2)
+		fputc(0x00, stdout); /* pad to 2-bytes */
+	}
+    } else if (depth == 1) {
+	/* mono image */
+	/* no colormap */
+	fputc(0x00, stdout);
+	fputc(0x00, stdout);
+
+	for (row=0; row<height; row++) 
+	{
+	    unsigned char bits = 0;
+	    for (col=0; col<width; col++) {
+		if (GetPixel(col,row))
+		    bits |= (unsigned char) (0x0080 >> (col % 8));
+		if (((col + 1) % 8) == 0)  {
+		    fputc(bits, stdout);
+		    bits = 0;
+		}
+	    }
+	    if ((width % 8) != 0)
+		fputc(bits, stdout);
+	    if ((width % 16) && (width % 16) <= 8)
+		fputc(0x00, stdout);  /* 16 bit pad */
+	}
+    } else {
+	/* no colormap, direct or true color (24 bit) image */
+	fputc(0x00, stdout);
+	fputc(0x00, stdout);
+	
+	for (row=0; row<height; row++) 
+	{
+	    for (col=0; col<width; col++) 
+		fputc(pixels[row][col].r * 255 / maxval, stdout);
+	    if (width % 2)
+		fputc(0x00, stdout); /* pad to 2-bytes */
+	    for (col=0; col<width; col++) 
+		fputc(pixels[row][col].g * 255 / maxval, stdout);
+	    if (width % 2)
+		fputc(0x00, stdout); /* pad to 2-bytes */
+	    for (col=0; col<width; col++) 
+		fputc(pixels[row][col].b * 255 / maxval, stdout);
+	    if (width % 2)
+		fputc(0x00, stdout); /* pad to 2-bytes */
+	}
+    }
+}
+
+
+
+int
+main( argc, argv )
+int argc;
+char *argv[];
+{
+    FILE *ifd;
+    int argn, rows, cols, colors, i, BitsPerPixel;
+    pixval maxval;
+    colorhist_vector chv;
+    const char * const usage = "[ppmfile]";
+    
+    ppm_init(&argc, argv);
+    
+    argn = 1;
+    
+    if ( argn < argc )
+    {
+	ifd = pm_openr( argv[argn] );
+	argn++;
+    } else
+	ifd = stdin;
+    
+    if ( argn != argc )
+	pm_usage( usage );
+    
+    pixels = ppm_readppm( ifd, &cols, &rows, &maxval );
+    
+    pm_close( ifd );
+    
+    /* Figure out the colormap. */
+    fprintf( stderr, "(Computing colormap..." );
+    fflush( stderr );
+    chv = ppm_computecolorhist( pixels, cols, rows, MAXCOLORS, &colors );
+    if ( chv != (colorhist_vector) 0 )
+    {
+	fprintf( stderr, "  Done.  %d colors found.)\n", colors );
+	
+	for ( i = 0; i < colors; i++ )
+	{
+	    Red[i] = (int) PPM_GETR( chv[i].color );
+	    Green[i] = (int) PPM_GETG( chv[i].color );
+	    Blue[i] = (int) PPM_GETB( chv[i].color );
+	}
+	BitsPerPixel = colorstobpp( colors );
+	
+	/* And make a hash table for fast lookup. */
+	cht = ppm_colorhisttocolorhash( chv, colors );
+	ppm_freecolorhist( chv );
+    } else {
+	BitsPerPixel = 24;
+	fprintf( stderr, "  Done.  24-bit true color %d color image.)\n", colors );
+    }
+    
+    leaf_writeimg(cols, rows, BitsPerPixel, colors, maxval);
+    
+    return( 0 );
+}
+
+
+
diff --git a/converter/ppm/ppmtolj.c b/converter/ppm/ppmtolj.c
new file mode 100644
index 00000000..7ed814ed
--- /dev/null
+++ b/converter/ppm/ppmtolj.c
@@ -0,0 +1,297 @@
+/* ppmtolj.c - convert a portable pixmap to an HP PCL 5 color image
+**
+** Copyright (C) 2000 by Jonathan Melvin (jonathan.melvin@heywood.co.uk)
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "ppm.h"
+
+static int compress_row_delta (unsigned char *op, unsigned char *prev_op, 
+                               unsigned char *cp, int bufsize);
+
+#define C_RESET                 "\033E"
+#define C_PRESENTATION          "\033*r%dF"
+#define C_PRESENTATION_LOGICAL  0
+#define C_PRESENTATION_PHYSICAL 3
+#define C_GAMMA                 "\033*t%dI"
+#define C_IMAGE_WIDTH           "\033*r%dS"
+#define C_IMAGE_HEIGHT          "\033*r%dT"
+#define C_DATA_PLANES           "\033*r%dU"
+#define C_TRANS_MODE            "\033*b%dM"
+#define C_TRANS_MODE_STD        0 /*compression modes*/
+#define C_TRANS_MODE_RLE        1 /*no good for rgb*/
+#define C_TRANS_MODE_TIFF       2 /*no good for rgb*/
+#define C_TRANS_MODE_DELTA      3 /*only on to use for rgb values*/
+#define C_CONFIG_IMAGE_DATA     "\033*v6W"
+#define C_SEND_ROW              "\033*b%dW"
+#define C_BEGIN_RASTER          "\033*r%dA"
+#define C_BEGIN_RASTER_CUR      1
+#define C_END_RASTER            "\033*r%dC"
+#define C_END_RASTER_UNUSED     0
+#define C_RESOLUTION            "\033*t%dR"
+#define C_RESOLUTION_300DPI     300
+#define C_MOVE_X                "\033*p+%dX"
+#define C_MOVE_Y                "\033*p+%dY"
+#define C_LEFT_MARGIN           "\033*r%dA"
+#define C_Y_OFFSET              "\033*b%dY"
+
+
+/*
+ * delta encoding. 
+ */
+/*
+op row buffer
+prev_op     previous row buffer
+bufsize     length of row
+cp          buffer for compressed data
+*/
+static int
+compress_row_delta(op, prev_op, cp, bufsize)
+unsigned char *op, *prev_op, *cp;
+int bufsize;
+{
+    int burstStart, burstEnd, burstCode, mustBurst, ptr, skip, skipped, code;
+    int deltaBufferIndex = 0;
+    if (memcmp(op, prev_op , bufsize/*rowBufferIndex*/) == 0) 
+        return 0; /* exact match, no deltas required */
+
+    ptr = 0;
+    skipped = 0;
+    burstStart = -1;
+    burstEnd = -1;
+    mustBurst = 0;
+    while (ptr < bufsize/*rowBufferIndex*/) 
+    {
+        skip = 0;
+        if (ptr == 0 || skipped == 30 || op[ptr] != prev_op[ptr] ||
+            (burstStart != -1 && ptr == bufsize - 1)) 
+        {
+            /* we want to output this byte... */
+            if (burstStart == -1) 
+            {
+                burstStart = ptr;
+            }
+            if (ptr - burstStart == 7 || ptr == bufsize - 1) 
+            {
+                /* we have to output it now... */
+                burstEnd = ptr;
+                mustBurst = 1;
+            }
+        } 
+        else 
+        {
+            /* duplicate byte, we can skip it */
+            if (burstStart != -1) 
+            {
+                burstEnd = ptr - 1;
+                mustBurst = 1;
+            }
+            skip = 1;
+        }
+        if (mustBurst) 
+        {
+            burstCode = burstEnd - burstStart; /* 0-7 means 1-8 bytes follow */
+            code = (burstCode << 5) | skipped;
+            cp[deltaBufferIndex++] = (char) code;
+            memcpy(cp+deltaBufferIndex, op+burstStart, burstCode + 1);
+            deltaBufferIndex += burstCode + 1;
+            burstStart = -1;
+            burstEnd = -1;
+            mustBurst = 0;
+            skipped = 0;
+        }
+        if (skip) 
+        {
+            skipped ++;
+        }
+        ptr ++;
+    }
+    return deltaBufferIndex;
+}
+
+
+int main(int argc, char *argv[]) {
+    pixel * pixelrow;
+    FILE *ifp;
+    int argn, rows, cols, r, c, k;
+    pixval maxval;
+    unsigned char *obuf, *op, *cbuf, *previous_obuf;
+    int format;
+    int gamma = 0;
+    int mode = C_TRANS_MODE_STD;
+    int currentmode = 0;
+    int floating = 0;  /* suppress the ``ESC & l 0 E'' ? */
+    int resets = 3;    /* bit mask for when to emit printer reset seq */
+
+    int resolution = C_RESOLUTION_300DPI;
+
+    char CID[6] =  { 0, 3, 0, 8, 8, 8 }; 
+        /*data for the configure image data command*/
+
+    const char * const usage = "[-noreset][-float][-delta][-gamma <val>] [-resolution N] "
+        "[ppmfile]\n\tresolution = [75|100|150|300|600] (dpi)";
+
+    ppm_init( &argc, argv );
+
+    argn = 1;
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+    {
+        if( pm_keymatch( argv[argn], "-resolution", 2 ) && argn + 1 < argc )
+        {
+            ++argn;
+            if ( argn == argc || sscanf( argv[argn], "%d", &resolution ) != 1 )
+                pm_usage( usage );
+        }
+        else if ( pm_keymatch(argv[argn],"-gamma",2) && argn + 1 < argc )
+        {
+            ++argn;
+            if ( sscanf( argv[argn], "%d",&gamma ) != 1 )
+                pm_usage( usage );
+        }
+        else if (pm_keymatch(argv[argn],"-delta",2))
+            mode = C_TRANS_MODE_DELTA;
+        else if (pm_keymatch(argv[argn],"-float",2))
+            floating = 1;
+        else if (pm_keymatch(argv[argn],"-noreset",2))
+            resets = 0;
+
+        else
+            pm_usage( usage );
+        ++argn;
+    }
+
+    if ( argn < argc )
+    {
+        ifp = pm_openr( argv[argn] );
+        ++argn;
+    }
+    else
+        ifp = stdin;
+
+    if ( argn != argc )
+        pm_usage( usage );
+
+    ppm_readppminit( ifp, &cols, &rows, &maxval, &format );
+    pixelrow = ppm_allocrow( cols );
+
+    obuf = (unsigned char *) pm_allocrow(cols * 3, sizeof(unsigned char));
+    cbuf = (unsigned char *) pm_allocrow(cols * 6, sizeof(unsigned char));
+    if (mode == C_TRANS_MODE_DELTA)
+    {
+        previous_obuf = 
+            (unsigned char *) pm_allocrow(cols * 3, sizeof(unsigned char));
+        memset(previous_obuf, 0, cols * 3);
+    }
+
+    if(resets & 1)
+    {
+        /* Printer reset. */
+        printf(C_RESET);
+    }
+
+    if(!floating)
+    {
+        /* Ensure top margin is zero */
+        printf("\033&l0E");
+    }
+
+    /*Set Presentation mode*/
+    (void) printf(C_PRESENTATION, C_PRESENTATION_PHYSICAL);
+    /* Set the resolution */
+    (void) printf(C_RESOLUTION, resolution);
+    /* Set raster height*/
+    (void) printf(C_IMAGE_HEIGHT, rows);
+    /* Set raster width*/
+    (void) printf(C_IMAGE_WIDTH, cols);
+    /* set left margin to current x pos*/
+    /*(void) printf(C_LEFT_MARGIN, 1);*/
+    /* set the correct color mode */
+    (void) printf(C_CONFIG_IMAGE_DATA);
+    (void) fwrite(CID, 1, 6, stdout);
+    /* Start raster graphics */
+    (void) printf(C_BEGIN_RASTER, C_BEGIN_RASTER_CUR);  /*posscale);*/
+    /* set Y offset to 0 */
+    (void) printf(C_Y_OFFSET, 0);
+/*  
+    if (xoff)
+        (void) printf(C_MOVE_X, xoff);
+    if (yoff)
+        (void) printf(C_MOVE_Y, yoff);
+*/
+    /* Set raster compression */
+    (void) printf(C_TRANS_MODE, mode);
+    currentmode = mode;
+    
+    if(gamma)
+        (void) printf(C_GAMMA,   gamma);
+    
+    for (r = 0; r < rows; r++)
+    {
+        ppm_readppmrow(ifp, pixelrow, cols, maxval, format);
+
+        /* get a row of data with 3 bytes per pixel */
+        for (c = 0, op = &obuf[-1]; c < cols; c++)
+        {
+            ++op;
+            *op = (PPM_GETR(pixelrow[c])*255)/maxval;
+            ++op;
+            *op = (PPM_GETG(pixelrow[c])*255)/maxval;
+            ++op;
+            *op = (PPM_GETB(pixelrow[c])*255)/maxval;
+        }
+        ++op;
+        k = op - obuf; /*size of row*/
+        /*compress the row if required*/
+        if(mode == C_TRANS_MODE_STD)
+        {/*no compression*/
+            op = obuf;
+        }
+
+        if(mode ==  C_TRANS_MODE_DELTA)
+        {/*delta compression*/
+            int newmode = 0;
+            int deltasize = 
+                compress_row_delta(obuf, previous_obuf, cbuf, cols*3);
+            if(deltasize >= k)/*normal is best?*/
+            {
+                op = obuf;
+            }
+            else /*delta is best*/
+            {
+                k = deltasize;
+                op = cbuf;
+                newmode = C_TRANS_MODE_DELTA;
+            }
+            memcpy(previous_obuf, obuf, cols*3);
+
+            if(currentmode != newmode)
+            {
+                (void) printf(C_TRANS_MODE, newmode);
+                currentmode = newmode;
+            }
+        }
+
+        (void) printf(C_SEND_ROW, k);
+        (void) fwrite(op, 1, k, stdout);
+        
+    }
+
+    (void) printf(C_END_RASTER, C_END_RASTER_UNUSED);
+    if(resets & 2)
+    {
+        /* Printer reset. */
+        printf(C_RESET);
+    }
+    pm_close( ifp );
+    ppm_freerow(pixelrow);
+
+    return 0;
+}
diff --git a/converter/ppm/ppmtomitsu.c b/converter/ppm/ppmtomitsu.c
new file mode 100644
index 00000000..3934ae45
--- /dev/null
+++ b/converter/ppm/ppmtomitsu.c
@@ -0,0 +1,604 @@
+/* ppmtomitsu.c - read a portable pixmap and produce output for the
+**                Mitsubishi S340-10 Thermo-Sublimation Printer
+**                (or the S3410-30 parallel interface)
+**
+** Copyright (C) 1992,93 by S.Petra Zeidler
+** Minor modifications by Ingo Wilken:
+x**  - mymalloc() and check_and_rotate() functions for often used
+**    code fragments.  Reduces code size by a few KB.
+**  - use pm_error() instead of fprintf(stderr)
+**  - localized allocation of colorhastable
+**
+** This software was written for the Max Planck Institut fuer Radioastronomie,
+** Bonn, Germany, Optical Interferometry group
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "nstring.h"
+#include "mallocvar.h"
+
+#include "mitsu.h"
+
+
+#include <stdio.h>
+
+#define HASHSIZE 2048
+#define myhash(x) ((PPM_GETR(x)*3 + PPM_GETG(x)*5 + PPM_GETB(x)*7) % HASHSIZE)
+
+typedef struct hashinfo {
+        pixel     color;
+        long      flag;
+        struct hashinfo *next;
+} hashinfo;
+
+#ifdef __STDC__
+static void lineputinit(int cols, int rows, int sharpness, int enlarge, int
+                        copy, struct mediasize medias);
+static void frametransferinit(int cols, int rows, int sharpness, int enlarge,
+                              int copy, struct mediasize medias);
+static void lookuptableinit(int sharpness, int enlarge, int copy,
+                            struct mediasize medias);
+static void lookuptabledata(int cols, int rows, int enlarge,
+                            struct mediasize medias);
+static void check_and_rotate(int cols, int rows, int enlarge,
+                             struct mediasize medias);
+#define CONST const
+#else /*__STDC__*/
+static int lineputinit();
+static int lookuptableinit();
+static int lookuptabledata();
+static int frametransferinit();
+static int check_and_rotate();
+#define CONST
+#endif
+
+#define cmd(arg)           fputc((arg), stdout)
+#define datum(arg)         fputc((char)(arg), stdout)
+#define data(arg,num)      fwrite((arg), sizeof(char), (num), stdout)
+
+
+#ifdef __STDC__
+int main(int argc, char *argv[] )
+#else
+int main( argc, argv )
+    int argc;
+    char* argv[];
+#endif
+    {
+    FILE             *ifp;
+    /*hashinfo         colorhashtable[HASHSIZE];*/
+    struct hashinfo  *hashrun;
+    pixel            *xP;
+    int              argn;
+    bool             dpi300;
+    int              cols, rows, format, col, row;
+    int              sharpness, enlarge, copy, tiny;
+    pixval           maxval;
+    struct mediasize medias;
+    char             media[16];
+    const char * const usage = "[-sharpness <1-4>] [-enlarge <1-3>] [-media <a,a4,as,a4s>] [-copy <1-9>] [-tiny] [-dpi300] [ppmfile]";
+
+    ppm_init(&argc, argv);
+
+    dpi300 = FALSE;
+    argn = 1;
+    sharpness = 32;
+    enlarge   = 1;
+    copy      = 1;
+    memset(media, '\0', 16);
+    tiny      = FALSE;
+
+    /* check for flags */
+    while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0') {
+    if (pm_keymatch(argv[argn], "-sharpness", 2)) {
+        ++argn;
+        if (argn == argc || sscanf(argv[argn], "%d", &sharpness) != 1)
+            pm_usage(usage);
+        else if (sharpness < 1 || sharpness > 4)
+            pm_usage(usage);
+        }
+    else if (pm_keymatch(argv[argn], "-enlarge", 2)) {
+        ++argn;
+        if (argn == argc || sscanf(argv[argn], "%d", &enlarge) != 1)
+            pm_usage(usage);
+        else if (enlarge < 1 || enlarge > 3)
+            pm_usage(usage);
+        }
+    else if (pm_keymatch(argv[argn], "-media", 2)) {
+        ++argn;
+        if (argn == argc || sscanf(argv[argn], "%15s", media) < 1)
+            pm_usage(usage);
+        else if (TOUPPER(media[0]) != 'A')
+            pm_usage(usage);
+    }
+    else if (pm_keymatch(argv[argn], "-copy", 2)) {
+        ++argn;
+        if (argn == argc || sscanf(argv[argn], "%d", &copy) != 1)
+            pm_usage(usage);
+        else if (copy < 1 || copy > 9)
+            pm_usage(usage);
+        }
+    else if (pm_keymatch(argv[argn], "-dpi300", 2))
+        dpi300 = TRUE;
+    else if (pm_keymatch(argv[argn], "-tiny", 2))
+        tiny = TRUE;
+    else
+        pm_usage(usage);
+        ++argn;
+    }
+
+    if (argn < argc) {
+        ifp = pm_openr(argv[argn]);
+        ++argn;
+    }
+    else
+        ifp = stdin;
+
+    if (argn != argc)
+        pm_usage(usage);
+
+    if (TOUPPER(media[0]) == 'A')
+        switch (TOUPPER(media[1])) {
+        case 'S':
+            medias = MSize_AS;
+            break;
+        case '4':
+            if(TOUPPER(media[2]) == 'S')
+                medias = MSize_A4S;
+            else {
+                medias = MSize_A4;
+            }
+            break;
+        default:
+            medias = MSize_A;
+        }
+    else
+        medias = MSize_User;
+
+        if (dpi300) {
+                medias.maxcols *= 2;
+                medias.maxrows *= 2;
+        }
+
+    if (tiny) {
+        pixel            *pixelrow;
+        char             *redrow, *greenrow, *bluerow;
+
+        ppm_readppminit(ifp, &cols, &rows, &maxval, &format);
+        pixelrow = (pixel *) ppm_allocrow(cols);
+        MALLOCARRAY_NOFAIL(redrow, cols);
+        MALLOCARRAY_NOFAIL(greenrow, cols);
+        MALLOCARRAY_NOFAIL(bluerow, cols);
+        lineputinit(cols, rows, sharpness, enlarge, copy, medias);
+
+        for ( row = 0; row < rows; ++row ) {
+            ppm_readppmrow(ifp, pixelrow, cols, maxval, format);
+            switch(PPM_FORMAT_TYPE(format)) {
+            /* color */
+            case PPM_TYPE:
+                for (col = 0, xP = pixelrow; col < cols; col++, xP++) {
+                    /* First red. */
+                    redrow[col] = PPM_GETR(*xP);
+                    /* Then green. */
+                    greenrow[col] = PPM_GETG(*xP);
+                    /* And blue. */
+                    bluerow[col] = PPM_GETB(*xP);
+                }
+                data(redrow,   cols);
+                data(greenrow, cols);
+                data(bluerow,  cols);
+                break;
+            /* grayscale */
+            default:
+                for (col = 0, xP = pixelrow; col < cols; col++, xP++)
+                    bluerow[col] = PPM_GETB(*xP);
+                data(bluerow, cols);
+                data(bluerow, cols);
+                data(bluerow, cols);
+                break;
+            }
+        }
+        pm_close(ifp);
+    }
+    else {
+        pixel            **pixelpic;
+        int              colanz, colval;
+        int                 i;
+        colorhist_vector table;
+
+        ppm_readppminit( ifp, &cols, &rows, &maxval, &format );
+        pixelpic = ppm_allocarray( cols, rows );
+        for (row = 0; row < rows; row++)
+            ppm_readppmrow( ifp, pixelpic[row], cols, maxval, format );
+        pm_close(ifp);
+
+        /* first check wether we can use the lut transfer */
+
+        table = ppm_computecolorhist(pixelpic, cols, rows, MAXLUTCOL+1, 
+                                     &colanz);
+        if (table != NULL) {
+            hashinfo *colorhashtable;
+
+            MALLOCARRAY_NOFAIL(colorhashtable, HASHSIZE);
+            for (i=0; i<HASHSIZE; i++) {
+                colorhashtable[i].flag = -1;
+                colorhashtable[i].next = NULL;
+            }
+
+            /* we can use the lookuptable */
+            pm_message("found %d colors - using the lookuptable-method",
+                       colanz);
+            lookuptableinit(sharpness, enlarge, copy, medias);
+            switch(PPM_FORMAT_TYPE(format)) {
+            /* color */
+            case PPM_TYPE:
+                for (colval=0; colval<colanz; colval++) {
+                    cmd('$');
+                    datum(colval);
+                    datum(PPM_GETR((table[colval]).color));
+                    datum(PPM_GETG((table[colval]).color));
+                    datum(PPM_GETB((table[colval]).color));
+
+                    hashrun = &colorhashtable[myhash((table[colval]).color)];
+                    if (hashrun->flag == -1) {
+                        hashrun->color = (table[colval]).color;
+                        hashrun->flag  = colval;
+                    }
+                    else {
+                        while (hashrun->next != NULL)
+                            hashrun = hashrun->next;
+                        MALLOCVAR_NOFAIL(hashrun->next);
+                        hashrun = hashrun->next;
+                        hashrun->color = (table[colval]).color;
+                        hashrun->flag  = colval;
+                        hashrun->next  = NULL;
+                    }
+                }
+                break;
+            /* other */
+            default:
+                for (colval=0; colval<colanz; colval++) {
+                    cmd('$');
+                    datum(colval);
+                    datum(PPM_GETB((table[colval]).color));
+                    datum(PPM_GETB((table[colval]).color));
+                    datum(PPM_GETB((table[colval]).color));
+
+                    hashrun = &colorhashtable[myhash((table[colval]).color)];
+                    if (hashrun->flag == -1) {
+                        hashrun->color = (table[colval]).color;
+                        hashrun->flag  = colval;
+                    }
+                    else {
+                        while (hashrun->next != NULL)
+                            hashrun = hashrun->next;
+                        MALLOCVAR_NOFAIL(hashrun->next);
+                        hashrun = hashrun->next;
+                        hashrun->color = (table[colval]).color;
+                        hashrun->flag  = colval;
+                        hashrun->next  = NULL;
+                    }
+                }
+            }
+            lookuptabledata(cols, rows, enlarge, medias);
+            for (row=0; row<rows; row++) {
+                xP = pixelpic[row];
+                for (col=0; col<cols; col++, xP++) {
+                    hashrun = &colorhashtable[myhash(*xP)];
+                    while (!PPM_EQUAL((hashrun->color), *xP))
+                        if (hashrun->next != NULL)
+                            hashrun = hashrun->next;
+                        else {
+                            pm_error("you just found a lethal bug.");
+                        }
+                    datum(hashrun->flag);
+                }
+            }
+            free(colorhashtable);
+        }
+        else {
+        /* $#%@^!& no lut possible, so send the pic as 24bit */
+            pm_message("found too many colors for fast lookuptable mode");
+            frametransferinit(cols, rows, sharpness, enlarge, copy, medias);
+            switch(PPM_FORMAT_TYPE(format)) {
+            /* color */
+            case PPM_TYPE:
+                COLORDES(RED);
+                DATASTART;                    /* red coming */
+                for (row=0; row<rows; row++) {
+                    xP = pixelpic[row];
+                    for (col=0; col<cols; col++, xP++)
+                        datum(PPM_GETR(*xP));
+                }
+                COLORDES(GREEN);
+                DATASTART;                    /* green coming */
+                for (row=0; row<rows; row++) {
+                    xP = pixelpic[row];
+                    for (col=0; col<cols; col++, xP++)
+                        datum(PPM_GETG(*xP));
+                }
+                COLORDES(BLUE);
+                DATASTART;                    /* blue coming */
+                for (row=0; row<rows; row++) {
+                    xP = pixelpic[row];
+                    for (col=0; col<cols; col++, xP++)
+                        datum(PPM_GETB(*xP));
+                }
+                break;
+            /* grayscale */
+            default:
+                COLORDES(RED);
+                DATASTART;                    /* red coming */
+                for (row=0; row<rows; row++) {
+                    xP = pixelpic[row];
+                    for (col=0; col<cols; col++, xP++)
+                        datum(PPM_GETB(*xP));
+                }
+                COLORDES(GREEN);
+                DATASTART;                    /* green coming */
+                for (row=0; row<rows; row++) {
+                    xP = pixelpic[row];
+                    for (col=0; col<cols; col++, xP++)
+                        datum(PPM_GETB(*xP));
+                }
+                COLORDES(BLUE);
+                DATASTART;                    /* blue coming */
+                for (row=0; row<rows; row++) {
+                    xP = pixelpic[row];
+                    for (col=0; col<cols; col++, xP++)
+                        datum(PPM_GETB(*xP));
+                }
+            }
+        }
+    }
+    PRINTIT;
+    exit(0);
+}
+
+#ifdef __STDC__
+static void lineputinit(int cols, int rows,
+                        int sharpness, int enlarge, int copy,
+                        struct mediasize medias)
+#else /*__STDC__*/
+static int lineputinit(cols, rows, sharpness, enlarge, copy, medias)
+    int cols, rows;
+    int sharpness, enlarge, copy;
+    struct mediasize medias;
+#endif /*__STDC__*/
+{
+    ONLINE;
+    CLRMEM;
+    MEDIASIZE(medias);
+
+    switch (enlarge) {
+    case 2:
+        HENLARGE(ENLARGEx2); /* enlarge horizontal */
+        VENLARGE(ENLARGEx2); /* enlarge vertical */
+        break;
+    case 3:
+        HENLARGE(ENLARGEx3); /* enlarge horizontal */
+        VENLARGE(ENLARGEx3); /* enlarge vertical */
+        break;
+    default:
+        HENLARGE(NOENLARGE); /* enlarge horizontal */
+        VENLARGE(NOENLARGE); /* enlarge vertical */
+    }
+
+    COLREVERSION(DONTREVERTCOLOR);
+    NUMCOPY(copy);
+
+    HOFFINCH('\000');
+    VOFFINCH('\000');
+    CENTERING(DONTCENTER);
+
+    TRANSFERFORMAT(LINEORDER);
+    COLORSYSTEM(RGB);
+    GRAYSCALELVL(BIT_8);
+
+    switch (sharpness) {          /* sharpness :-) */
+    case 0:
+        SHARPNESS(SP_NONE);
+        break;
+    case 1:
+        SHARPNESS(SP_LOW);
+        break;
+    case 2:
+        SHARPNESS(SP_MIDLOW);
+        break;
+    case 3:
+        SHARPNESS(SP_MIDHIGH);
+        break;
+    case 4:
+        SHARPNESS(SP_HIGH);
+        break;
+    default:
+        SHARPNESS(SP_USER);
+    }
+    check_and_rotate(cols, rows, enlarge, medias);
+    DATASTART;
+    return;
+}
+
+#ifdef __STDC__
+static void lookuptableinit(int sharpness, int enlarge, int copy,
+                            struct mediasize medias)
+#else /*__STDC__*/
+static int lookuptableinit(sharpness, enlarge, copy, medias)
+    int sharpness, enlarge, copy;
+    struct mediasize medias;
+#endif /*__STDC__*/
+{
+    ONLINE;
+    CLRMEM;
+    MEDIASIZE(medias);
+
+    switch (enlarge) {
+    case 2:
+        HENLARGE(ENLARGEx2); /* enlarge horizontal */
+        VENLARGE(ENLARGEx2); /* enlarge vertical */
+        break;
+    case 3:
+        HENLARGE(ENLARGEx3); /* enlarge horizontal */
+        VENLARGE(ENLARGEx3); /* enlarge vertical */
+        break;
+    default:
+        HENLARGE(NOENLARGE); /* enlarge horizontal */
+        VENLARGE(NOENLARGE); /* enlarge vertical */
+    }
+
+    COLREVERSION(DONTREVERTCOLOR);
+    NUMCOPY(copy);
+
+    HOFFINCH('\000');
+    VOFFINCH('\000');
+    CENTERING(DONTCENTER);
+
+    TRANSFERFORMAT(LOOKUPTABLE);
+
+    switch (sharpness) {          /* sharpness :-) */
+    case 0:
+        SHARPNESS(SP_NONE);
+        break;
+    case 1:
+        SHARPNESS(SP_LOW);
+        break;
+    case 2:
+        SHARPNESS(SP_MIDLOW);
+        break;
+    case 3:
+        SHARPNESS(SP_MIDHIGH);
+        break;
+    case 4:
+        SHARPNESS(SP_HIGH);
+        break;
+    default:
+        SHARPNESS(SP_USER);
+    }
+
+    LOADLOOKUPTABLE;
+    return;
+}
+
+#ifdef __STDC__
+static void lookuptabledata(int cols, int rows, int enlarge,
+                                                        struct mediasize medias)
+#else /*__STDC__*/
+static int lookuptabledata(cols, rows, enlarge, medias)
+    int   rows, cols;
+    int   enlarge;
+    struct mediasize medias;
+#endif /*__STDC__*/
+{
+    DONELOOKUPTABLE;
+    check_and_rotate(cols, rows, enlarge, medias);
+    DATASTART;
+    return;
+}
+
+#ifdef __STDC__
+static void frametransferinit(int cols, int rows, int sharpness,
+                              int enlarge, int copy, struct mediasize medias)
+#else
+static int frametransferinit(cols, rows, sharpness, enlarge, copy, medias)
+
+    int     rows, cols;
+    int     sharpness, enlarge, copy;
+    struct mediasize medias;
+#endif
+{
+    ONLINE;
+    CLRMEM;
+    MEDIASIZE(medias);
+
+    switch (enlarge) {
+    case 2:
+        HENLARGE(ENLARGEx2); /* enlarge horizontal */
+        VENLARGE(ENLARGEx2); /* enlarge vertical */
+        break;
+    case 3:
+        HENLARGE(ENLARGEx3); /* enlarge horizontal */
+        VENLARGE(ENLARGEx3); /* enlarge vertical */
+        break;
+    default:
+        HENLARGE(NOENLARGE); /* enlarge horizontal */
+        VENLARGE(NOENLARGE); /* enlarge vertical */
+    }
+
+    COLREVERSION(DONTREVERTCOLOR);
+    NUMCOPY(copy);
+
+    HOFFINCH('\000');
+    VOFFINCH('\000');
+    CENTERING(DONTCENTER);
+
+    TRANSFERFORMAT(FRAMEORDER);
+    COLORSYSTEM(RGB);
+    GRAYSCALELVL(BIT_8);
+
+    switch (sharpness) {          /* sharpness :-) */
+    case 0:
+        SHARPNESS(SP_NONE);
+        break;
+    case 1:
+        SHARPNESS(SP_LOW);
+        break;
+    case 2:
+        SHARPNESS(SP_MIDLOW);
+        break;
+    case 3:
+        SHARPNESS(SP_MIDHIGH);
+        break;
+    case 4:
+        SHARPNESS(SP_HIGH);
+        break;
+    default:
+        SHARPNESS(SP_USER);
+    }
+    check_and_rotate(cols, rows, enlarge, medias);
+    return;
+}
+
+
+#ifdef __STDC__
+static void
+check_and_rotate(int cols, int rows, int enlarge, struct mediasize medias)
+#else
+static int
+check_and_rotate(cols, rows, enlarge, medias)
+    int cols, rows, enlarge;
+    struct mediasize medias;
+#endif
+{
+    if (cols > rows) {
+        ROTATEIMG(DOROTATE);                        /* rotate image */
+        if (enlarge*rows > medias.maxcols || enlarge*cols > medias.maxrows) {
+            pm_error("Image too large, MaxPixels = %d x %d", medias.maxrows, medias.maxcols);
+        }
+        HPIXELS(cols);
+        VPIXELS(rows);
+        HPIXELSOFF((medias.maxcols/enlarge - rows)/2);
+        VPIXELSOFF((medias.maxrows/enlarge - cols)/2);
+        pm_message("rotating image for output");
+    }
+    else {
+        ROTATEIMG(DONTROTATE);
+        if (enlarge*rows > medias.maxrows || enlarge*cols > medias.maxcols) {
+            pm_error("Image too large, MaxPixels = %d x %d", medias.maxrows, medias.maxcols);
+        }
+        HPIXELS(cols);
+        VPIXELS(rows);
+        HPIXELSOFF((medias.maxcols/enlarge - cols)/2);
+        VPIXELSOFF((medias.maxrows/enlarge - rows)/2);
+    }
+}
+
diff --git a/converter/ppm/ppmtompeg/BUGS b/converter/ppm/ppmtompeg/BUGS
new file mode 100644
index 00000000..64269dfb
--- /dev/null
+++ b/converter/ppm/ppmtompeg/BUGS
@@ -0,0 +1,43 @@
+Known BUGS:
+
+1. <fixed>
+
+2. rate control and specifics files interact badly
+
+3. using REMOTE parallel execution:
+   `command` to find INPUT files does not work
+   SPECIFICS_FILE (CDL_FILE)
+   USER_DATA 
+     dont work (should send the files over the socket, but do not
+
+4. using any sort of parallel code:
+     REFERENCE_FRAME	DECODED
+   does not work
+
+5. Cannot use both STDIN and CDL_FILEs (ok, since it doesnt make sense...)
+
+6. <fixed>
+
+7. Patterns of BBI...  seem to cause problems for parallel encoding.  
+   I believe the bugs are fixed for sequential, but if you hit one, let us
+   know!
+
+8. Sometimes parallel encoding results in a zero size being written into
+   the stream.  To fix, specify SIZE of the actual size.
+
+8.1 (related? minor, anyway) in parallel is also writes the Quant matrix
+   even when it is the default one
+
+9. It is unclear (to us anyway) if we should generate Y's from 0..255 or 16..224
+
+10. <fixed>
+
+11. <fixed>
+
+12. reading JPEG from stdin, the encoder will sometimes stop before the end
+of the stream (if the images are smaller than half the buffer size).
+
+13. Sometimes INPUT_CONVERT * is not good (requires "cat *"), in a parallel
+case reported by a user it was needed.
+
+14. Parallel encoding often generates improper temporal_ref's...
diff --git a/converter/ppm/ppmtompeg/CHANGES b/converter/ppm/ppmtompeg/CHANGES
new file mode 100644
index 00000000..fffa7a65
--- /dev/null
+++ b/converter/ppm/ppmtompeg/CHANGES
@@ -0,0 +1,180 @@
+Changes chronology
+------------------
+1.5b Release	August 1995
+	- added date to default USER_DATA string
+	- made prototypes happier
+	- renamed SPECIFICS_FILE to CDL_FILE
+	- fixed bit_rate param in constrained param setting (was too restrictive)
+	- fixed obscure P-block motion vector bug
+	- added a file name replication:
+	    foo.ppm  [1-10] will make 10 copies of foo.ppm
+	- generalized fopen calls to specify binary mode (MSDOG)
+	- generalized times calls into elapsed_time to handle different library routines
+	- fixed motion vector bug in bframes; skip block bug
+	- fixed minor bugs in parallel, mpeg, main, iframe, param, ....
+	- accepts extra whitespace at the end of lines in parameter files
+	- "PIXEL FULL" bug in B-frames fixed (caused ghosting)
+        - B-blocks "can I skip" rutine now looks at color too (more ghosting problems)
+        - switched from JPEG4 to JPEG5 (if they crash for you,
+                  do a -DJPEG4 for the encoder, and get jpeg4.tar.gz off our site)
+        - combine server (parallel) waits longer for files to exist (
+                         thanks to apian@ise.fhg.de)
+	- killing the parallel server kills the clients!!!! (apian@ise.fhg.de)
+
+1.5 Release	8 May 1995  Changes since version 1.3 release (to 1.5)
+= BUG FIXES
+	- removed SetBlocksPerSlice() from GOPStoMPEG
+	- don't access bb after freeing
+	- non-integer frame rates now work for all machines
+	- fixed parsing of -mv_histogram
+	- fixed numPadding bug (file name problem)
+	- fixed full pixel assertation bug
+        - corrected ASPECT_RATIO bug (was forced to 1)
+	- buffer size is now set correctly
+        - complains when file is too small
+	- fixed bug with non-ranged INPUT_FILE names
+	- forced MB at start/end of slice in P-frame bug fixed
+	- Actually evaluates the constrained parameter settings
+	- fixed a Cr/Cb mixup in bframe.c
+
+= NEW FEATURES
+	- rate control
+	- optional B search range different from P range
+	- can read images from stdin, allowing encoding on-the-fly
+        - Does Y files (no U or V)
+	- New way of specifying files `cmd`, like `ls jan-*-94`
+	- Can encode Abekas and Phillips style YUV formats
+	- gamma correction
+	- added -realquiet mode
+	- JMOVIE and JPEGv4 input format fully supported
+        - -float_dct uses the (slower) double precision floating point DCT 
+	- automatically identifies itself in (sequence header) userdata
+	- enforced [0][0] entry of Intra Q-table to be 8
+	- added Jim Boucher's resize code
+        - added -mse flag to print mean squared error on a per-block basis
+	- can specify Sequence header user data
+        - prints version number
+        - finds more skip blocks in GenBFrame
+	- can generate multiple sequence headers (every N GOPs)
+
+= SPEED IMPROVEMENTS
+
+
+= MAINTENANCE
+	- removed mpeg_jrevdct_sparse(), since it wasn't used
+	- added CompileTests();
+	- uses libpnmrw rather than entire pbmplus directory
+        - redid RCS
+        - reorganized [ipb]frame.c to make playing with rate controls easier
+
+= PORTABILITY
+	- added #ifdef's, #ifndef's LINUX'es to ease porting
+
+
+Changes from 1.2 to 1.3
+-----------------------
+= BUG FIXES
+	+ deletes frame files when done with them in parallel version
+	+ fixed bug in ComputeFrameTable
+	+ completion time estimation now is closer to actual time
+	+ modified ComputeSNR to avoid overflow of varOrig
+	+ fixed bug that caused I/P/B times on HP's to be wrong
+	+ fixed bug that made TwoLevel search out of the search window
+	+ fixed bug in -quiet 0
+	+ fixed memory leak when using PNM files
+	+ fixed bug:  crashed when CPU time was 0 (very rare)
+	+ fixed bug in -gop option
+	+ fixed bug in AppendFiles()
+
+= NEW FEATURES
+	+ added FORCE_ENCODE_LAST_FRAME option (allows encoding of all frames)
+	+ added PARALLEL_CHUNK_TAPER option
+	+ added -bit_rate_info option to show bit rate per frame
+	+ added -mv_histogram option to show motion vector histogram
+	+ custom quantization tables allowed
+	+ can specify frame rate, aspect ratio
+
+= SPEED IMPROVEMENTS
+	+ replaced most remaining binary multiplies/divides with shifts
+		(except those concerning negative numbers)
+
+= MAINTENANCE
+	+ got rid of & before array warning in block.c
+	+ got rid of references to srandom, random in pbmplus.h
+	+ undefine 'index' if defined as 'strchr' (since we use index as
+	  variable name)
+	+ modified frame type code to be more flexible
+
+= PORTABILITY
+	+ replaced all bzero() calls with memset() calls
+
+
+1.2 Release	20 October 1993
+
+Changes from 1.1 to 1.2
+-----------------------
+= BUG FIXES
+	+ allow comments in PPM files
+	+ first and last macroblocks in a slice may not be skipped macroblocks
+	+ fixed YUV bug:  should be able to handle dimensions which
+		aren't multiples of 16
+	+ fixed the separate I/O conversion for parallel NFS version
+	+ no_frame_summary can now be last option
+
+= NEW FEATURES
+	+ using DECODED frames as reference frames in parallel
+	  version now works
+	+ implemented multiple I/O Servers
+	+ FORCE_I_ALIGN added
+	+ reorganized Server structure slightly (only I/O Servers
+	  handle individual frame files on disk)
+	+ add option to allow exact division of frames per processor
+	  (PARALLEL_PERFECT)
+
+= SPEED IMPROVEMENTS
+	+ don't start Input server if not needed
+
+= MAINTENANCE
+	+ got rid of niceProcesses line in main.c
+	+ changed write() to SafeWrite() in parallel.c
+	+ commented stuff out after #endif's
+	+ fixed prototype warnings (eliminated non-4-byte args)
+
+
+1.1 Release	August 1993
+
+Changes from 1.0 to 1.1
+-----------------------
+= BUG FIXES
+	+ fixed bug in parallel Server code which didn't htonl() frame
+	  numbers correctly (so it didn't work on little-endian machines)
+	+ fixed bug -- B-frames were always getting just 1 slice
+	+ fixed listen() to use max connections allowed
+	+ fixed noparallel.c so it doesn't complain during non-parallel
+	  execution
+
+= NEW FEATURES
+	+ added level-2 p-search (exhaustive full, then half)
+	+ now prints signal-to-noise ratio
+	+ parallel code complains if absolute path not used for parameter
+	  file
+	+ changed single I/O server into separate Input and Output Servers
+	  (and have Output Server combine frames in parallel)
+
+= SPEED IMPROVEMENTS
+	+ slight improvement in Mpost_QuantZigBlock (.10 ms/call down to .08 ms
+	  on Sparc-10)
+	+ improvement in speed of BlockifyFrame (45.8 ms/call down to 21.2 ms)
+	+ improvement in ComputeMotionBlock (0.02 ms/call down to 0.01 ms)
+	+ improvement in ComputeMotionLumBlock (0.04 ms/call down to 0.02 ms)
+	+ improvement in LumAddMotionError (0.06 ms/call down to 0.05 ms)
+		(changed /2 to >>1)
+	
+= MAINTENANCE
+	+ removed most memory.h references (except jrevdct)
+	+ added CFLAGS lines in Makefile for SVR4.0 and SGI machines
+	+ removed mproto.h
+	+ got rid of printing connection times
+
+
+1.0 Release	July 1993
diff --git a/converter/ppm/ppmtompeg/COPYRIGHT b/converter/ppm/ppmtompeg/COPYRIGHT
new file mode 100644
index 00000000..08216f34
--- /dev/null
+++ b/converter/ppm/ppmtompeg/COPYRIGHT
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ * 
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ * 
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
diff --git a/converter/ppm/ppmtompeg/HISTORY b/converter/ppm/ppmtompeg/HISTORY
new file mode 100644
index 00000000..c9f4932a
--- /dev/null
+++ b/converter/ppm/ppmtompeg/HISTORY
@@ -0,0 +1,313 @@
+The entire ppmtojpeg directory was adapted by Bryan from the package
+mpeg_encode-1.5b-src (subdirectory mpeg_encode) on March 30, 1999.  The 
+program was called mpeg_encode in that package.  It was dated August 16,
+1995 and came from ftp://mm-ftp.cs.berkeley.edu/pub/multimedia/mpeg/
+encode/mpeg_encode-1.5b-src.tar.gz
+
+Changes for Netpbm include:
+
+  - mpeg_encode recognizes two input formats:  "PPM" and "PNM".  For
+    PPM, mpeg_encode parses it itself.  For PNM, it uses the Netpbm
+    PNM library routines.
+    
+    In the Netpbm version, PPM is gone and "PPM" is accepted as a 
+    synonym for "PNM".  For PNM, it uses the PPM (not PNM) Netpbm
+    library routines.
+
+  - mpeg_encode PNM code is broken for maxval != 255 (divides by zero
+    if maxval < 255 and overly quantize if > 255 because PNMtoYUV()
+    uses an integer divisor = maxval/256).
+
+In November 2004, Bryan rewrote large portions of the code so that he
+could read it easily enough to debug some problems.  He eliminated
+long subroutines, global variables, gotos, and the like.
+
+See the file LOGIC for some documentation on how the code works.
+
+
+The following is the README from the aforementioned mpeg_encode subdirectory.
+
+
+                 MPEG-1 Video Software Encoder
+                (Version 1.5; February 1, 1995)
+
+     Lawrence A. Rowe, Kevin Gong, Eugene Hung, Ketan Patel, Steve Smoot
+       and Dan Wallach
+    Computer Science Division-EECS, Univ. of Calif. at Berkeley
+
+This directory contains the freely distributed Berkeley MPEG-1 Video 
+Encoder.  The encoder implements the standard described in the ISO/IEC
+International Standard 11172-2.  The code has been compiled and tested 
+on the following platforms:
+
+ DECstation 5000 and Alpha
+ HP PA-RISC (HP/UX 9.X) (i.e., HP 9000/7XX and 9000/3XX)
+ SGI Indigo running IRIX 5.0.1
+ Sun Sparc (SunOS 4.X)
+
+In addition, Rainer Menes from the Technical University of Munich has
+ported the encoder and decoder to the Macintosh.  You can get that code
+directly from him (menes@statistik.tu-muenchen.de), or from the 
+Berkeley FTP archive (mm-ftp.CS.Berkeley.EDU).  If you decide to port 
+the code to a new architecture, please let us know so that we can 
+incorporate the changes into our sources.
+
+This directory contains everything required to build the encoder
+and run it.  We have included source code, makefiles, binaries
+for selected platforms, documentation, and test data.  Installation 
+instructions are given in the file named src/mpeg_encode/INSTALL.  A man 
+page is given in the file doc/mpeg_encode.1.  A detailed user 
+manual is provided in postscript format in the file doc/user-manual.ps.
+
+The encoder will accept any input file format as long as you provide 
+a script to convert the images to PPM, YUV, JPEG, or JMOVIE format.  Input 
+file processing is described in the file doc/INPUT.FORMAT.  Options to 
+control input file processing and compression parameters are specified in 
+a parameter file.  Very little error processing is done when reading 
+this file.  We suggest you start with the sample parameter file 
+examples/template.param and modify it.  See also examples/default.param.
+
+The convert directory of Mpeg-Tools contains utilities you might find 
+useful including: 
+
+programs to do PPM/YUV conversion and programs to convert Parallax
+XVideo JPEG files into PPM, YUV, or JPEG frames.
+
+The motion vector search window can be specified, including half-pixel
+block matching, in the parameter file.  We have implemented several 
+search algorithms for P-frames including: 1) exhaustive search, 
+2) subsampled search, and 3) logarithmic search.  We have also implemented
+several alternatives for B-frame block matching including: 1) interpolate
+best forward and best backward block, 2) find backward block for best
+forward or vice-versa (called CROSS2), and 3) exhaustive cross product
+(i.e., go out for coffee and a donut!). The search algorithms are controlled
+by options in the parameters file.  For tips on choosing the right search
+technique, see the user manual.
+
+The encoder can be run on one computer (i.e., sequential) or on several
+computers (i.e., parallel).  Our goal is to produce a portable, easy-to-use
+encoder that we can use to encode large volumes of video material for
+the Berkeley VOD system (see paper VodsProp93.ps.Z on the FTP archive).
+The parallelism is done on a sequence of pictures.  In other words, you 
+can spawn one or more children to encode continuous runs pictures. The 
+uncompressed data can be accessed either through NFS or TCP sockets.  
+The goal is to allow you to encode using multiple processors, think 
+spare cycles on workstations, to speed up the encoding time.  Although
+performance depends on the speed of individual processors, the file system
+and network, and the P/B frame search methods, we have encoded 3.75
+frames/second on 8 HP Snakes running in parallel as compared with 0.6
+frames/second on 1 Snake.  These are preliminary results. We are continuing 
+to experiment with and tune the code.  Instructions to run the parallel system 
+are given in the man page and the parallel.param example parameter file.
+
+We have done some tuning to produce a reasonable encoder, but there are
+many more optimizations that we would like to incorporate.  These 
+extensions are listed in the file doc/EXTENSIONS.  If you succeed in 
+implementing any of them, please let us know! 
+
+Send bug reports to:
+
+mpeg-bugs@CS.Berkeley.EDU
+   Problems, questions, or patches should be sent to this address.
+
+Anyone interested in providing financial support for this research or 
+discussing other aspects of this project should contact Larry Rowe at 
+Rowe@CS.Berkeley.EDU (+1 510-642-5117).
+
+This software is freely distributed.  That means, you may use it for 
+any non-commercial purpose.  However, patents are held by several companies 
+on various aspects of the MPEG video standard.  Companies or individuals
+who want to develop commercial products that include this code must
+acquire licenses from these companies.  For information on licensing, see
+Appendix F in the standard.
+
+ACKNOWLEDGEMENTS:
+
+We gratefully thank Hewlett-Packard and Fujitsu who provided financial
+support for this work.  We also want to thank the following people and
+organizations for their help:
+
+    Jef Poskanzer who developed the pbmplus package.
+    ---------
+    Copyright (C) 1989, 1991 by Jef Poskanzer.
+
+    Permission to use, copy, modify, and distribute this software and its
+    documentation for any purpose and without fee is hereby granted, provided
+    that the above copyright notice appear in all copies and that both that
+    copyright notice and this permission notice appear in supporting
+    documentation.  This software is provided "as is" without express or
+    implied warranty.
+    ---------
+
+    Eiichi Kowashi of Intel and Avideh Zakhor of U.C. Berkeley who
+    provided valuable suggestions on motion vector searching.
+
+    Chad Fogg of the University of Washington who has helped us 
+    understand many issues in MPEG coding and decoding.
+
+    Rainer Menes of the Technical University of Munich who has ported the
+    the Berkeley MPEG encoder and decoder to the Macintosh, and he has 
+    provided us with many suggestions to improve the code.
+
+    Robert Safranek of ATT for comments, suggestions, and most of the
+    code for custom quantization tables.
+
+    Jim Boucher of Boston University for jmovie2jpeg.
+
+    The San Diego SuperComputing Center for providing facilities to 
+    develop some of the code contained within.
+
+
+This is the TODO file from the original Berkeley package:
+
+
+TODO list for next release
+--------------------------
+
+Add option to do searches in Cr/Cb as well as Lum blocks
+jpeg5! (below)
+last-frame must be P/I error, and pattern interact badly
+add gnuconfigure so the Makefile is cake
+YUV correct (cf mail below)
+fix the "ifdef BUGGY_CODE" code in bframe.c
+Change sizing stuff so it works with non multiples of 16
+Does "RESIZE WxH" work?  Seems to for PPm but not JPG.  Fix it.  Document it
+mpeg_encode in parallel generates a "zero size warning"  It should 
+    exit(1) here, when not in parallel, and not warn when in parallel.
+     sometimes it generates a 0x0 MPEG!
+Give time estimates for parallel encoding
+Verify YUV file sizes with stat() call (when not STDIN)
+
+--------------------
+
+YUV mail:
+Please have a look on these few lines extracted from the ISO
+mpeg2encode/readpic.c/read_ppm() available on 
+ftp.netcom.com:/pub/cf/cfogg/mpeg2 :
+
+    .........
+
+     /* convert to YUV */
+
+     y = cr*r +cg*g +cb*b;
+     u = cu*(b-y);
+     v = cv*(r-y);
+     yp[j] = (219.0/256.0)*y + 16.5;  /* nominal range : 16..235 */
+     up[j] = (224.0/256.0)*u + 128.5; /* nominal range : 16..240 */
+     vp[j] = (224.0/256.0)*v + 128.5; /* nominal range : 16..240 */
+    ...........
+
+I think there is a slight misunderstanding in the Berkeley's mpeg1 codec about
+what the YUV format looks like, exactly about how to translate from PPM to YUV 
+and vice versa : the dynamic of YUV format has to be reduced as described
+above. Otherwise, on a full color display a Berkeley's MPEG bitstream has not 
+exactly the right colors if played by an ISO compliant player.
+
+Best regards
+
+
+                            Thierry GODIN
+
+--------------------
+And just for fun, kevin's list:
+To-do list	(in no particular order)
+----------
+	- should delete decoded frame files when done with them
+		(need to make sure no one else needs it)
+	- port to CM5
+	- try on Snake cluster, and other clusters (FDDI -- 100Mb/s)
+	- fix bug on snakes (look at header file for queue length)
+	- look at 24-bit display
+	- try having I/O server getting things in order, and asking Master
+		where to send them
+	- bug:  closing connections at end (on DEC 5000)
+	- GOP on space in input list
+	- pnm convolve
+	- telescopic search
+	- include system layer
+	- update documentation
+	- show error images
+	- graphical interface (showing motion vectors, etc.)
+	- use DCT-space when computing error terms
+	- vary the q-scale according to the error term
+	- modify the program to have a finer-grained parallelism option -- we
+	  can probably encode slices in parallel (this will only be useful if
+	  we want to do a few B-frames using exhaustive search)
+	- make search technique stop at x.5, not x.0
+	- pel aspect ratio in parameter file (2.4.3.2)
+	- skipped blocks options?
+	- recover from parallel machine errors?
+	- subsample B-search
+	- bug:  decoded with 1 machine can freeze
+	- malloc bug:  hello.param, with DECODED frames only
+	- portability:
+		times() function
+		popen/pclose
+
+Oh yes, I liked the concept of a spiral for your full search algorithm,
+however I thought this code a little difficult to read.  What about
+using a look up table (pre generated at compile time) to generate
+the coord offsets that would then spiral around the location in question?
+
+	+ change MAXPATHLEN to something else
+	+ put ./ in test in Makefile
+
+Currently, the IPPBPBB sequence is fixed for the entire sequence.  A later
+version should probably either test for scene changes or allow the user to
+specify them.
+
+	+ check all << and >> to make sure they are used properly
+		(note truncation of >>)
+	+ allow variable bit rate
+	+ allow size of video sequences to be set
+	+ make REMOTE usage more clear
+	+ fix bug:  when I-frame only, and decoded, does a lot of extra work
+	+ replace ZAG[i] with a pointer? (in quantization)
+		(and speed up by using 31 times the space (one for each
+		 q-value)
+	+ add interrupt handler to parallel encoder
+	+ should pad black instead of truncating
+	- graph histogram of motion vectors
+	- allow new PATTERN statements inside of file listing
+	- put in run-time checks for size of int32, int16, etc.
+	- replace pnm crap with ppm.dwallach
+	- add option to allow different p-search in b-search
+	- allow -frames option along with parallel option (shouldn't be
+		too difficult)
+	- incorrect compression rates given if do -frames option
+
+	- enforce:  >Hmmm...I will have to look at the standard.  I did find something earlier --
+>"forced updating" makes it illegal to have more than 132 P-frames without an
+>I-frame.  But "forced updating" does not disallow any number of consecutive
+>B-frames.  I'll have to check on limits for GOP lengths...
+
+	- rectangular search windows
+
+	- make parallel stats to 4 digits
+
+One of my friend just fixed the problem.....she 
+retype the whole parameter file and it is working
+now.  I think the problem was there were some 
+spacing problem......for example, if there
+are some space is the line, when the 
+program read in....it just mess everything up.
+
+It really become a problem if there is space after the image name...
+or even after the path name.
+
+	Subject: may not want to >> 4	in postdct.c
+
+------------------------------------------------------------------------
+P.S. In the future versions (is one already been released), you could add
+option for encoder to remove picture after encoding it & therefore saving
+space on disk + option to sleep while picture it's waiting for is not done.
+I've hacked this: (in readframe.c)
+
+        while ((tempfile = fopen(fullFileName, "r")) == NULL) {
+            fprintf(stderr, "Cannot open '%s', retrying...\n", fullFileName);
+            sleep(120);
+        }
+
+        fclose(tempfile);
+
+arijan@kette.fer.uni-lj.si
diff --git a/converter/ppm/ppmtompeg/LOGIC b/converter/ppm/ppmtompeg/LOGIC
new file mode 100644
index 00000000..8c19dc8d
--- /dev/null
+++ b/converter/ppm/ppmtompeg/LOGIC
@@ -0,0 +1,126 @@
+This file describes (a little) how the program works.
+
+PARALLEL MODE
+-------------
+
+In parallel mode, two processes run on the machine where you invoke
+Ppmtompeg: the master server and the combine server.  Sometimes, there's
+a third process called a decode server, but I don't know what that's for
+yet.
+
+Other processes, which are typically on separate machines, one per
+machine, are called encoder processes.  The code normally calls these
+"child" processes and the documentation normally calls them "slave"
+processes.  We really should fix that.
+
+The master server's job is to feed work assignments to the encoder
+processes.  A work assignment is a set of frames to encode.
+
+The combine server's job is to take the encoded output from the encoder
+processes and combine them into a single MPEG movie.
+
+The master process is the first process that exists.  The first thing
+it does is create the combine server.  Then it creates the encoder
+processes.  It creates each encoder process with an initial
+assignment.  The master process then waits in a loop for an encoder
+process to report that it has finished its assignment and gives a new
+assignment to it.
+
+The master process and the combine process both have a listening TCP
+port.  They choose an available port number.  When the master server
+creates the combine server, it passes the master server's TCP listen
+port number to the combine server.  It then waits to hear from the
+combine process what TCP listen port it has chosen.  The combine
+server connects to the master server's listen port and tells it.  The
+master server then passes to each encode server, as it creates it,
+the TCP listen ports of both the master server and the combine server.
+
+When the combine server has processed all the frames, it shuts down.
+It connects to the master server TCP listen port and tells the master
+server it is shutting down.
+
+When an encoder server finishes a frame, it connects to the combine
+server's TCP listen port and tells the combine server that the frame
+is ready.
+
+When an encoder server finishes an assignment, it connects to the master
+server TCP listen port and tells the master it is done, and receives over
+the same connetion its next assignment.  If there is no more work to do,
+the master server instead tells the encoder server just to terminate.
+
+When the master server has told every encoder server to terminate, it
+waits for the combine server to say that it has terminated.  The master
+server then terminates.
+
+To create the combine server, the master server forks and execs
+'ppmtompeg' with the -output_server option.
+
+To create an encoder server, the master server invokes Ssh (or something
+like it) and tells the remote shell to execute 'ppmtompeg' with the
+-child option.
+
+The various processes communicate frame data via a shared filesystem
+(e.g. NFS).  An encoder server grabs the input frames from the shared
+filesystem and writes the encoded frames to it.  The combine server
+grabs the encoded frames from it and writes the MPEG movie file to it.
+The encoded frames go in individual files.  The combine server deletes
+each file after it has copied it to the MPEG movie file.  Use the
+KEEP_TEMP_FILES parameter file statement to keep them around for 
+debugging.
+
+There's also an alternative "remote I/O" method of communication that
+doesn't require a shared filesystem.  I haven't studied that.
+
+The master server and combine server code are in parallel.c.  The
+encoder server code is roughly the same as what the single encoder
+process executes when you aren't running in parallel mode.
+
+
+THE ENCODING PROCEDURE
+----------------------
+
+Encoding is done by ppmtompeg.c, which calls GenMPEGStream in mpeg.c to
+do most of the work.
+
+The encoder goes through the input frames in movie sequence.  But the
+frames don't go into an MPEG stream in movie sequence.  A B frame not
+only can't be encoded without the subsequent I or P frame (reference
+frame); it can't be decoded without it either.  So in the MPEG stream,
+B frames come immediately after the reference frame which comes
+immediately after the B frames in the actual movie.
+
+When the input is from a sequential stream (the only way that's possible
+today is with Standard Input), the encoder saves up B frame input until
+after it has encoded and output the post reference frame.  It does
+this by chaining the input frames to the pre reference frame.  We really
+should clean this up so that a single module handles input of all kinds
+and presents only a sequential stream to the encoder.  The encoder should
+not have special cases for the different types of input.  That input
+module would let the encoder ask from Frame N even if the input is
+sequential.  The input module would buffer frames from the sequential
+input as necessary.
+
+In parallel mode, the combine server takes encoded frames from the
+encoder servers as it comes.  It can be in any order.  The combine
+server knows which frames go where in the output stream and outputs them
+in the proper order.  This is important for two reasons.  First, the
+encoder servers don't finish assignments in the same order in which they
+get them.  Second, when an encoder server gets an assignment that ends
+with a B frame, the combine server has to wait for the encoder server
+that has the next assignment to produce the post reference frame before
+the combine server can do anything with that trailing B frame.
+
+genMPEGStream() encodes a frame at a time, but it inserts a stream
+header and GOP headers where they belong.  genMPEGStream() operates in
+three modes: Whole Stream, GOP, and Just Frames.  In Whole Stream
+mode, it inserts all the required headers.  In GOP mode, it inserts
+GOP headers but not the stream header.  In Just Frames mode, it
+inserts no headers at all.  In parallel mode, genMPEGStream generates
+one frame per file, and only Just Frames mode makes any sense.  We
+really should fix this up so that the encoder always works in Just
+Frames mode and a GOP builder module takes the encoder's output and
+splices it with GOP headers and a stream builder module takes the GOP
+builder's output and adds it to a stream header.  In parallel mode,
+the combine server would run the GOP builder and stream builder
+modules.
+
diff --git a/converter/ppm/ppmtompeg/Makefile b/converter/ppm/ppmtompeg/Makefile
new file mode 100644
index 00000000..5e923fee
--- /dev/null
+++ b/converter/ppm/ppmtompeg/Makefile
@@ -0,0 +1,140 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = converter/ppm/ppmtompeg
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+ifeq ($(JPEGLIB),NONE)
+  # 'nojpeg' is a module that implements all the jpeg access routines as
+  # error messages that tell you we don't have jpeg capability
+  JPEG_MODULE = nojpeg
+  JPEGLIBX =
+else
+  # 'jpeg' is a module that accesses J-movies via the JPEG library.
+  JPEG_MODULE = jpeg
+  JPEGLIBX = $(JPEGLIB)
+endif
+
+INCLUDES = -I$(SRCDIR)/$(SUBDIR)/headers 
+
+ifneq ($(JPEGHDR_DIR),NONE)
+  ifneq ($(JPEGHDR_DIR)x,x)
+    INCLUDES += -I$(JPEGHDR_DIR)
+  endif
+endif
+
+# use -DLONG_32 iff
+#	1) long's are 32 bits and
+#	2) int's are not
+#
+# if you are using a non-ANSI compiler, then use:
+#	-DNON_ANSI_COMPILER
+#
+# one other option:
+#	-DHEINOUS_DEBUG_MODE
+#
+
+MP_BASE_OBJS = mfwddct.o postdct.o huff.o bitio.o mheaders.o
+MP_ENCODE_OBJS = iframe.o pframe.o bframe.o psearch.o bsearch.o block.o 
+MP_OTHER_OBJS = mpeg.o subsample.o param.o rgbtoycc.o \
+	readframe.o combine.o jrevdct.o frame.o fsize.o frametype.o \
+	specifics.o rate.o opts.o input.o gethostname.o
+ifeq ($(OMIT_NETWORK),y)
+  MP_PARALLEL_OBJS = noparallel.o
+else
+  MP_PARALLEL_OBJS = parallel.o psocket.o
+endif
+NONMAIN_OBJS = $(MP_BASE_OBJS) $(MP_OTHER_OBJS) $(MP_ENCODE_OBJS) \
+	      $(MP_PARALLEL_OBJS) $(JPEG_MODULE).o
+OBJECTS = ppmtompeg.o $(NONMAIN_OBJS)
+MERGE_OBJECTS = ppmtompeg.o2 $(NONMAIN_OBJS)
+MP_INCLUDE = mproto.h mtypes.h huff.h bitio.h
+MP_MISC = Makefile huff.table parse_huff.pl
+
+BINARIES = ppmtompeg
+MERGEBINARIES = $(BINARIES)
+SCRIPTS = 
+
+.PHONY: all
+all: ppmtompeg
+
+include $(SRCDIR)/Makefile.common
+
+ifeq ($(NEED_RUNTIME_PATH),Y)
+  LIBOPTR = -runtime
+else
+  LIBOPTR =
+endif
+
+ppmtompeg: $(OBJECTS) $(NETPBMLIB) $(LIBOPT)
+	$(LD) -o $@ $(LDFLAGS) \
+          $(OBJECTS) $(shell $(LIBOPT) $(NETPBMLIB) $(LIBOPTR) $(JPEGLIBX)) \
+	  $(NETWORKLD) $(MATHLIB) $(LDLIBS) \
+	  $(RPATH) $(LADD)
+
+profile: $(OBJECTS) $(NETPBMLIB) $(LIBOPT)
+	$(LD) -o $@ -Bstatic -pg $(LDFLAGS) \
+          $(OBJECTS) $(shell $(LIBOPT) $(NETPBMLIB) $(LIBOPTR) $(JPEGLIBX)) \
+	  $(NETWORKLD) $(MATHLIB) $(LDLIBS) \
+	  $(RPATH) $(LADD)
+
+
+#########
+# OTHER #
+#########
+
+#
+# Perl is necessary if you want to modify the Huffman RLE encoding table.
+#
+PERL = perl
+
+# The following stuff is for the Huffman encoding tables.  It's commented-out
+# because you probably don't want to change this.  If you do, then uncommment
+# it.
+#
+# huff.h: huff.c
+#
+# huff.c: parse_huff.pl huff.table
+#	$(PERL) parse_huff.pl huff.table
+
+MP_ALL_SRCS = $(patsubst %.o, %.c, $(MP_ALL_OBJS))
+
+wc:;		wc -l *.[ch] headers/*.h *.pl *.table
+ci:;		ci -l $(MP_ALL_SRCS) $(MP_INCLUDE) $(MP_MISC)
+tags: $(MP_ALL_SRCS)
+	ctags -t $(MP_ALL_SRCS)
+	etags -f TAGS -t $(MP_ALL_SRCS) headers/*.h
+
+#
+# WARNING: this assumes you're using GNU indent...
+#
+indent:;	indent -T FILE -T int8 -T int16 -T int32 -T uint8 -T uint16 -T uint32  -T BitBucket -T MpegFrame -T Block -T FlatBlock $(MP_ALL_SRCS)
+
+spotless: clean	
+	rm -f huff.c huff.h *.pure.a
+
+FORCE:
+
+# 
+# Copyright (c) 1995 The Regents of the University of California.
+# All rights reserved.
+# 
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose, without fee, and without written agreement is
+# hereby granted, provided that the above copyright notice and the following
+# two paragraphs appear in all copies of this software.
+# 
+# IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+# OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+# CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# 
+# THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+# ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+# PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+# 
diff --git a/converter/ppm/ppmtompeg/bframe.c b/converter/ppm/ppmtompeg/bframe.c
new file mode 100644
index 00000000..5dfb76d3
--- /dev/null
+++ b/converter/ppm/ppmtompeg/bframe.c
@@ -0,0 +1,1347 @@
+/*===========================================================================*
+ * bframe.c
+ *
+ *  Procedures concerned with the B-frame encoding
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "all.h"
+#include <sys/param.h>
+#include <assert.h>
+
+#include "ppm.h"
+
+#include "mtypes.h"
+#include "bitio.h"
+#include "frames.h"
+#include "prototypes.h"
+#include "block.h"
+#include "fsize.h"
+#include "param.h"
+#include "mheaders.h"
+#include "postdct.h"
+#include "rate.h"
+#include "opts.h"
+#include "specifics.h"
+
+extern int **bfmvHistogram;
+extern int **bbmvHistogram;
+
+/*==================*
+ * STATIC VARIABLES *
+ *==================*/
+
+static int32 totalTime = 0;
+static int qscaleB;
+static float    totalSNR = 0.0;
+static float    totalPSNR = 0.0;
+
+static struct bframeStats {
+    unsigned int BSkipped;
+    unsigned int BIBits;
+    unsigned int BBBits;
+    unsigned int Frames;
+    unsigned int FrameBits;
+    unsigned int BIBlocks;
+    unsigned int BBBlocks;
+    unsigned int BFOBlocks;    /* forward only */
+    unsigned int BBABlocks;    /* backward only */
+    unsigned int BINBlocks;    /* interpolate */
+    unsigned int BFOBits;
+    unsigned int BBABits;
+    unsigned int BINBits;
+} bframeStats = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+
+/*====================*
+ * EXTERNAL VARIABLES *
+ *====================*/
+
+extern Block **dct, **dctr, **dctb;
+extern dct_data_type **dct_data;
+#define NO_MOTION 0
+#define MOTION 1
+#define SKIP 2  /* used in useMotion in dct_data */
+
+/*=====================*
+ * INTERNAL PROCEDURES *
+ *=====================*/
+
+static void
+zeroMotion(motion * const motionP) {
+    
+    motionP->fwd.y = motionP->fwd.x = motionP->bwd.y = motionP->bwd.x = 0;
+}
+
+
+
+static motion
+halfMotion(motion const motion) {
+    struct motion half;
+
+    half.fwd.y = motion.fwd.y / 2;
+    half.fwd.x = motion.fwd.x / 2;
+    half.bwd.y = motion.bwd.y / 2;
+    half.bwd.x = motion.bwd.x / 2;
+
+    return half;
+}
+
+
+
+
+/*===========================================================================*
+ *
+ *  compute the block resulting from motion compensation
+ *
+ * RETURNS: motionBlock is modified
+ *
+ * SIDE EFFECTS:    none
+ *
+ * PRECONDITION:    the motion vectors must be valid!
+ *
+ *===========================================================================*/
+static void
+ComputeBMotionBlock(MpegFrame * const prev,
+                    MpegFrame * const next,
+                    int         const by,
+                    int         const bx,
+                    int         const mode,
+                    motion      const motion,
+                    Block *     const motionBlockP,
+                    int         const type) {
+
+    Block prevBlock, nextBlock;
+
+    switch(mode) {
+    case MOTION_FORWARD:
+        switch (type) {
+        case LUM_BLOCK:
+            ComputeMotionBlock(prev->ref_y, by, bx, motion.fwd, motionBlockP);
+            break;
+        case CB_BLOCK:
+            ComputeMotionBlock(prev->ref_cb, by, bx, motion.fwd, motionBlockP);
+            break;
+        case CR_BLOCK:
+            ComputeMotionBlock(prev->ref_cr, by, bx, motion.fwd, motionBlockP);
+        }
+        break;
+    case MOTION_BACKWARD:
+        switch (type) {
+        case LUM_BLOCK:
+            ComputeMotionBlock(next->ref_y, by, bx, motion.bwd, motionBlockP);
+            break;
+        case CB_BLOCK:
+            ComputeMotionBlock(next->ref_cb, by, bx, motion.bwd, motionBlockP);
+            break;
+        case CR_BLOCK:
+            ComputeMotionBlock(next->ref_cr, by, bx, motion.bwd, motionBlockP);
+            break;
+        }
+        break;
+    case MOTION_INTERPOLATE:
+        switch (type) {
+        case LUM_BLOCK:
+            ComputeMotionBlock(prev->ref_y, by, bx, motion.fwd, &prevBlock);
+            ComputeMotionBlock(next->ref_y, by, bx, motion.bwd, &nextBlock);
+            break;
+        case CB_BLOCK:
+            ComputeMotionBlock(prev->ref_cb, by, bx, motion.fwd, &prevBlock);
+            ComputeMotionBlock(next->ref_cb, by, bx, motion.bwd, &nextBlock);
+            break;
+        case CR_BLOCK:
+            ComputeMotionBlock(prev->ref_cr, by, bx, motion.fwd, &prevBlock);
+            ComputeMotionBlock(next->ref_cr, by, bx, motion.bwd, &nextBlock);
+            break;
+        }
+        {
+            unsigned int y;
+            for (y = 0; y < 8; ++y) {
+                int16 * const blockRow = (*motionBlockP)[y];
+                int16 * const prevRow  = prevBlock[y];
+                int16 * const nextRow  = nextBlock[y];
+                unsigned int x;
+                for (x = 0; x < 8; ++x)
+                    blockRow[x] = (prevRow[x] + nextRow[x] + 1) / 2;
+            }
+        }
+        break;
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ *  compute the DCT of the error term
+ *
+ * RETURNS: appropriate blocks of current will contain the DCTs
+ *
+ * SIDE EFFECTS:    none
+ *
+ * PRECONDITION:    the motion vectors must be valid!
+ *
+ *===========================================================================*/
+static void
+ComputeBDiffDCTs(MpegFrame * const current,
+                 MpegFrame * const prev,
+                 MpegFrame * const next,
+                 int         const by,
+                 int         const bx,
+                 int         const mode,
+                 motion      const motion,
+                 int *       const patternP) {
+
+    Block motionBlock;
+    
+    if (*patternP & 0x20) {
+        boolean significantDiff;
+        ComputeBMotionBlock(prev, next, by, bx, mode, motion,
+                            &motionBlock, LUM_BLOCK);
+        ComputeDiffDCTBlock(current->y_blocks[by][bx], dct[by][bx],
+                            motionBlock, &significantDiff);
+        if (!significantDiff) 
+            *patternP ^=  0x20;
+    }
+
+    if (*patternP & 0x10) {
+        boolean significantDiff;
+        ComputeBMotionBlock(prev, next, by, bx+1, mode, motion,
+                            &motionBlock, LUM_BLOCK);
+        ComputeDiffDCTBlock(current->y_blocks[by][bx+1], dct[by][bx+1],
+                            motionBlock, &significantDiff);
+        if (!significantDiff) 
+            *patternP ^=  0x10;
+    }
+
+    if (*patternP & 0x8) {
+        boolean significantDiff;
+        ComputeBMotionBlock(prev, next, by+1, bx, mode, motion,
+                            &motionBlock, LUM_BLOCK);
+        ComputeDiffDCTBlock(current->y_blocks[by+1][bx], dct[by+1][bx],
+                            motionBlock, &significantDiff);
+        if (!significantDiff)
+            *patternP ^= 0x8;
+    }
+
+    if (*patternP & 0x4) {
+        boolean significantDiff;
+        ComputeBMotionBlock(prev, next, by+1, bx+1, mode, motion,
+                            &motionBlock, LUM_BLOCK);
+        ComputeDiffDCTBlock(current->y_blocks[by+1][bx+1], dct[by+1][bx+1],
+                            motionBlock, &significantDiff);
+        if (!significantDiff)
+            *patternP ^= 0x4;
+    }
+
+    if (*patternP & 0x2) {
+        boolean significantDiff;
+        ComputeBMotionBlock(prev, next, by/2, bx/2, mode,
+                            halfMotion(motion), &motionBlock, CB_BLOCK);
+        ComputeDiffDCTBlock(current->cb_blocks[by/2][bx/2],
+                            dctb[by/2][bx/2], motionBlock,
+                            &significantDiff);
+        if (!significantDiff)
+            *patternP ^= 0x2;
+    }
+
+    if (*patternP & 0x1) {
+        boolean significantDiff;
+        ComputeBMotionBlock(prev, next, by/2, bx/2, mode,
+                            halfMotion(motion),
+                            &motionBlock, CR_BLOCK);
+        ComputeDiffDCTBlock(current->cr_blocks[by/2][bx/2],
+                            dctr[by/2][bx/2], motionBlock,
+                            &significantDiff);
+        if (!significantDiff)
+            *patternP ^= 0x1;
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ *  decides if this block should be coded as intra-block
+ *
+ * RETURNS: TRUE if intra-coding should be used; FALSE otherwise
+ *
+ * SIDE EFFECTS:    none
+ *
+ * PRECONDITION:    the motion vectors must be valid!
+ *
+ *===========================================================================*/
+static boolean
+DoBIntraCode(MpegFrame * const current,
+             MpegFrame * const prev,
+             MpegFrame * const next,
+             int         const by,
+             int         const bx,
+             int         const mode,
+             motion      const motion) {
+
+    boolean retval;
+    unsigned int y;
+    int32 sum = 0, vard = 0, varc = 0;
+    LumBlock motionBlock;
+    int fy, fx;
+
+    ComputeBMotionLumBlock(prev, next, by, bx, mode, motion, &motionBlock);
+
+    MotionToFrameCoord(by, bx, 0, 0, &fy, &fx);
+
+    for (y = 0; y < 16; ++y) {
+        unsigned int x;
+        for (x = 0; x < 16; ++x) {
+            int32 const currPixel = current->orig_y[fy+y][fx+x];
+            int32 const prevPixel = motionBlock.l[y][x];
+            int32 const dif = currPixel - prevPixel;
+
+            sum += currPixel;
+            varc += SQR(currPixel);
+            vard += SQR(dif);
+        }
+    }
+
+    vard >>= 8;     /* divide by 256; assumes mean is close to zero */
+    varc = (varc>>8) - (sum>>8)*(sum>>8);
+
+    if (vard <= 64)
+        retval = FALSE;
+    else if (vard < varc)
+        retval = FALSE;
+    else
+        retval = TRUE;
+    return retval;
+}
+
+
+
+static int
+ComputeBlockColorDiff(Block current,
+                      Block motionBlock) {
+
+    unsigned int y;
+    int diffTotal;
+    
+    diffTotal = 0;
+
+    for (y = 0; y < 8; ++y) {
+        unsigned int x;
+        for (x = 0; x < 8; ++x) {
+            int const diffTmp = current[y][x] - motionBlock[y][x];
+            diffTotal += ABS(diffTmp);
+        }
+    }
+    return diffTotal;
+}
+
+
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+
+
+/*===========================================================================*
+ * MotionSufficient
+ *
+ *  decides if this motion vector is sufficient without DCT coding
+ *
+ * RETURNS: TRUE if no DCT is needed; FALSE otherwise
+ *
+ * SIDE EFFECTS:    none
+ *
+ * PRECONDITION:    the motion vectors must be valid!
+ *
+ *===========================================================================*/
+static boolean
+MotionSufficient(MpegFrame *      const curr,
+                 const LumBlock * const currBlockP,
+                 MpegFrame *      const prev,
+                 MpegFrame *      const next,
+                 int              const by,
+                 int              const bx,
+                 int              const mode,
+                 motion           const motion) {
+
+    LumBlock mLumBlock;
+    Block mColorBlock;
+    int lumErr, colorErr;
+
+    /* check bounds */
+    if ( mode != MOTION_BACKWARD ) {
+        if ( (by*DCTSIZE+(motion.fwd.y-1)/2 < 0) ||
+             ((by+2)*DCTSIZE+(motion.fwd.y+1)/2-1 >= Fsize_y) ) {
+            return FALSE;
+        }
+        if ( (bx*DCTSIZE+(motion.fwd.x-1)/2 < 0) ||
+             ((bx+2)*DCTSIZE+(motion.fwd.x+1)/2-1 >= Fsize_x) ) {
+            return FALSE;
+        }
+    }
+
+    if ( mode != MOTION_FORWARD ) {
+        if ( (by*DCTSIZE+(motion.bwd.y-1)/2 < 0) ||
+             ((by+2)*DCTSIZE+(motion.bwd.y+1)/2-1 >= Fsize_y) ) {
+            return FALSE;
+        }
+        if ( (bx*DCTSIZE+(motion.bwd.x-1)/2 < 0) ||
+             ((bx+2)*DCTSIZE+(motion.bwd.x+1)/2-1 >= Fsize_x) ) {
+            return FALSE;
+        }
+    }
+
+    /* check Lum */
+    ComputeBMotionLumBlock(prev, next, by, bx, mode, motion, &mLumBlock);
+    lumErr =  LumBlockMAD(currBlockP, &mLumBlock, 0x7fffffff);
+    if (lumErr > 512) {
+        return FALSE;
+    }
+
+    /* check color */
+    ComputeBMotionBlock(prev, next, by/2, bx/2, mode,
+                        halfMotion(motion), &mColorBlock, CR_BLOCK);
+    colorErr = ComputeBlockColorDiff(curr->cr_blocks[by/2][bx/2],
+                                     mColorBlock);
+    ComputeBMotionBlock(prev, next, by/2, bx/2, mode, halfMotion(motion),
+                        &mColorBlock, CB_BLOCK);
+    colorErr += ComputeBlockColorDiff(curr->cr_blocks[by/2][bx/2],
+                                      mColorBlock);
+    
+    return (colorErr < 256); /* lumErr checked above */
+}
+
+
+
+
+struct stats {
+    int IBlocks;
+    int BBlocks;
+    int Skipped;
+    int totalBits;
+    int IBits;
+    int BBits;
+};
+
+
+
+static void
+initializeStats(struct stats * const statsP) {
+    statsP->IBlocks = 0;
+    statsP->BBlocks = 0;
+    statsP->Skipped = 0;
+    statsP->totalBits  = 0;
+    statsP->IBits   = 0;
+    statsP->BBits   = 0;
+}
+
+
+
+static void
+checkSpecifics(MpegFrame *      const curr, 
+               int              const mbAddress,
+               int              const QScale,
+               boolean *        const skipItP,
+               boolean *        const doBsearchP,
+               int *            const modeP,
+               motion *         const motionP) {
+/*----------------------------------------------------------------------------
+   We return *modeP iff we return *doBsearchP == FALSE.
+
+   We return *motionP iff we return *skipItP == FALSE.
+-----------------------------------------------------------------------------*/
+    BlockMV * info;
+
+    SpecLookup(curr->id, 2, mbAddress, &info, QScale);
+    if (info == NULL) {
+        *doBsearchP = TRUE;
+        *skipItP = FALSE;
+    } else {
+        *doBsearchP = FALSE;
+
+        switch (info->typ) {
+        case TYP_SKIP:
+            *skipItP = TRUE;
+            break;
+        case TYP_FORW:
+            *skipItP = FALSE;
+            motionP->fwd.y = info->fy;
+            motionP->fwd.x = info->fx;
+            *modeP = MOTION_FORWARD;
+            break;
+        case TYP_BACK:
+            *skipItP = FALSE;
+            motionP->bwd.y = info->by;
+            motionP->bwd.x = info->bx;
+            *modeP = MOTION_BACKWARD;
+            break;
+        case TYP_BOTH:
+            *skipItP = FALSE;
+            motionP->fwd.y = info->fy;
+            motionP->fwd.x = info->fx;
+            motionP->bwd.y = info->by;
+            motionP->bwd.x = info->bx;
+            *modeP = MOTION_INTERPOLATE;
+            break;
+        default:
+            pm_error("Unreachable code in GenBFrame!");
+        }
+    }
+}
+
+
+
+static void
+makeNonSkipBlock(int              const y,
+                 int              const x, 
+                 MpegFrame *      const curr, 
+                 MpegFrame *      const prev, 
+                 MpegFrame *      const next,
+                 boolean          const specificsOn,
+                 int              const mbAddress,
+                 int              const QScale,
+                 const LumBlock * const currentBlockP,
+                 int *            const modeP,
+                 int *            const oldModeP,
+                 boolean          const IntrPBAllowed,
+                 boolean *        const lastIntraP,
+                 motion *         const motionP,
+                 motion *         const oldMotionP,
+                 struct stats *   const statsP) {
+
+    motion motion;
+    int mode;
+    boolean skipIt;
+    boolean doBsearch;
+
+    if (specificsOn)
+        checkSpecifics(curr, mbAddress, QScale, &skipIt, &doBsearch,
+                       &mode, &motion);
+    else {
+        skipIt = FALSE;
+        doBsearch = TRUE;
+    }
+    if (skipIt)
+        dct_data[y][x].useMotion = SKIP;
+    else {
+        if (doBsearch) {
+            motion = *motionP;  /* start with old motion */
+            mode = BMotionSearch(currentBlockP, prev, next, y, x,
+                                 &motion, mode);
+        }
+        /* STEP 2:  INTRA OR NON-INTRA CODING */
+        if (IntraPBAllowed && 
+            DoBIntraCode(curr, prev, next, y, x, mode, motion)) {
+            /* output I-block inside a B-frame */
+            ++statsP->IBlocks;
+            zeroMotion(oldMotionP);
+            *lastIntraP = TRUE;
+            dct_data[y][x].useMotion = NO_MOTION;
+            *oldModeP = MOTION_FORWARD;
+            /* calculate forward dct's */
+            if (collect_quant && (collect_quant_detailed & 1)) 
+                fprintf(collect_quant_fp, "l\n");
+            mp_fwd_dct_block2(curr->y_blocks[y][x], dct[y][x]);
+            mp_fwd_dct_block2(curr->y_blocks[y][x+1], dct[y][x+1]);
+            mp_fwd_dct_block2(curr->y_blocks[y+1][x], dct[y+1][x]);
+            mp_fwd_dct_block2(curr->y_blocks[y+1][x+1], dct[y+1][x+1]);
+            if (collect_quant && (collect_quant_detailed & 1)) {
+                fprintf(collect_quant_fp, "c\n");
+            }
+            mp_fwd_dct_block2(curr->cb_blocks[y>>1][x>>1], 
+                              dctb[y>>1][x>>1]);
+            mp_fwd_dct_block2(curr->cr_blocks[y>>1][x>>1], 
+                              dctr[y>>1][x>>1]);
+
+        } else { /* dct P/Bi/B block */
+            int pattern;
+
+            pattern = 63;
+            *lastIntraP = FALSE;
+            ++statsP->BBlocks;
+            dct_data[y][x].mode = mode;
+            *oldModeP = mode;
+            dct_data[y][x].fmotionY = motion.fwd.y;
+            dct_data[y][x].fmotionX = motion.fwd.x;
+            dct_data[y][x].bmotionY = motion.bwd.y;
+            dct_data[y][x].bmotionX = motion.bwd.x;
+
+            switch (mode) {
+            case MOTION_FORWARD:
+                ++bframeStats.BFOBlocks;
+                oldMotionP->fwd = motion.fwd;
+                break;
+            case MOTION_BACKWARD:
+                ++bframeStats.BBABlocks;
+                oldMotionP->bwd = motion.bwd;
+                break;
+            case MOTION_INTERPOLATE:
+                ++bframeStats.BINBlocks;
+                *oldMotionP = motion;
+                break;
+            default:
+                pm_error("INTERNAL ERROR:  Illegal mode: %d", mode);
+            }
+        
+            ComputeBDiffDCTs(curr, prev, next, y, x, mode, motion, &pattern);
+        
+            dct_data[y][x].pattern = pattern;
+            dct_data[y][x].useMotion = MOTION;
+            if ( computeMVHist ) {
+                assert(motion.fwd.x + searchRangeB + 1 >= 0);
+                assert(motion.fwd.y + searchRangeB + 1 >= 0);
+                assert(motion.fwd.x + searchRangeB + 1 <= 2*searchRangeB + 2);
+                assert(motion.fwd.y + searchRangeB + 1 <= 2*searchRangeB + 2);
+                assert(motion.bwd.x + searchRangeB + 1 >= 0);
+                assert(motion.bwd.y + searchRangeB + 1 >= 0);
+                assert(motion.bwd.x + searchRangeB + 1 <= 2*searchRangeB + 2);
+                assert(motion.bwd.y + searchRangeB + 1 <= 2*searchRangeB + 2);
+                
+                ++bfmvHistogram[motion.fwd.x + searchRangeB + 1]
+                    [motion.fwd.y + searchRangeB + 1];
+                ++bbmvHistogram[motion.bwd.x + searchRangeB + 1]
+                    [motion.bwd.y + searchRangeB + 1];
+            }
+        } /* motion-block */
+    }
+    *motionP = motion;
+    *modeP   = mode;
+}
+
+
+
+/*===========================================================================*
+ *
+ * GenBFrame
+ *
+ *  generate a B-frame from previous and next frames, adding the result
+ *  to the given bit bucket
+ *
+ * RETURNS: frame appended to bb
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+GenBFrame(BitBucket * const bb, 
+          MpegFrame * const curr, 
+          MpegFrame * const prev, 
+          MpegFrame * const next) {
+
+    FlatBlock fba[6], fb[6];
+    Block     dec[6];
+    int32 y_dc_pred, cr_dc_pred, cb_dc_pred;
+    int x, y;
+    struct motion motion;
+    struct motion oldMotion;
+    int oldMode = MOTION_FORWARD;
+    int mode = MOTION_FORWARD;
+    int offsetX, offsetY;
+    struct motion motionRem;
+    struct motion motionQuot;
+    struct stats stats;
+    boolean lastIntra = TRUE;
+    boolean    motionForward, motionBackward;
+    int     totalFrameBits;
+    int32    startTime, endTime;
+    int lastX, lastY;
+    int lastBlockX, lastBlockY;
+    int ix, iy;
+    LumBlock currentBlock;
+    int         fy, fx;
+    boolean make_skip_block;
+    int mbAddrInc = 1;
+    int mbAddress;
+    int     slicePos;
+    float   snr[3], psnr[3];
+    int     idx;
+    int     QScale;
+    BlockMV *info;
+    int     bitstreamMode, newQScale;
+    int     rc_blockStart=0;
+    boolean overflowChange=FALSE;
+    int overflowValue = 0;
+
+    assert(prev != NULL);
+    assert(next != NULL);
+
+    initializeStats(&stats);
+
+    if (collect_quant) {fprintf(collect_quant_fp, "# B\n");}
+    if (dct == NULL) AllocDctBlocks();
+    ++bframeStats.Frames;
+    totalFrameBits = bb->cumulativeBits;
+    startTime = time_elapsed();
+
+    /*   Rate Control */
+    bitstreamMode = getRateMode();
+    if (bitstreamMode == FIXED_RATE) {
+        targetRateControl(curr);
+    }
+ 
+    QScale = GetBQScale();
+    Mhead_GenPictureHeader(bb, B_FRAME, curr->id, fCodeB);
+    /* Check for Qscale change */
+    if (specificsOn) {
+        newQScale = SpecLookup(curr->id, 0, 0 /* junk */, &info, QScale);
+        if (newQScale != -1) {
+            QScale = newQScale;
+        }
+        /* check for slice */
+        newQScale = SpecLookup(curr->id, 1, 1, &info, QScale);
+        if (newQScale != -1) {
+            QScale = newQScale;
+        }
+    }
+
+    Mhead_GenSliceHeader(bb, 1, QScale, NULL, 0);
+
+    Frame_AllocBlocks(curr);
+    BlockifyFrame(curr);
+
+    if ( printSNR ) {
+        Frame_AllocDecoded(curr, FALSE);
+    }
+
+    /* for I-blocks */
+    y_dc_pred = cr_dc_pred = cb_dc_pred = 128;
+
+    stats.totalBits = bb->cumulativeBits;
+
+    if ( ! pixelFullSearch ) {
+        if ( ! prev->halfComputed ) {
+            ComputeHalfPixelData(prev);
+        }
+
+        if ( ! next->halfComputed ) {
+            ComputeHalfPixelData(next);
+        }
+    }
+
+    lastBlockX = Fsize_x / 8;
+    lastBlockY = Fsize_y / 8;
+    lastX = lastBlockX - 2;
+    lastY = lastBlockY - 2;
+    mbAddress = 0;
+
+    /* Start with zero motion assumption */
+    zeroMotion(&motion); 
+    zeroMotion(&oldMotion);
+    zeroMotion(&motionRem);
+    zeroMotion(&motionQuot);
+
+    /* find motion vectors and do dcts */
+    /* In this first loop, all MVs are in half-pixel scope, (if FULL
+       is set then they will be multiples of 2).  This is not true in
+       the second loop. 
+    */
+    for (y = 0;  y < lastBlockY;  y += 2) {
+        for (x = 0;  x < lastBlockX;  x += 2) {
+            slicePos = (mbAddress % blocksPerSlice);
+
+            /* compute currentBlock */
+            BLOCK_TO_FRAME_COORD(y, x, fy, fx);
+            for ( iy = 0; iy < 16; iy++ ) {
+                for ( ix = 0; ix < 16; ix++ ) {
+                    currentBlock.l[iy][ix] = (int16)curr->orig_y[fy+iy][fx+ix];
+                }
+            }
+        
+            if (slicePos == 0) {
+                zeroMotion(&oldMotion);
+                oldMode = MOTION_FORWARD;
+                lastIntra = TRUE;
+            }
+
+            /* STEP 1:  Select Forward, Backward, or Interpolated motion 
+               vectors */
+            /* see if old motion is good enough */
+            /* but force last block to be non-skipped */
+            /* can only skip if:
+             *     1)  not the last block in frame
+             *     2)  not the last block in slice
+             *     3)  not the first block in slice
+             *     4)  previous block was not intra-coded
+             */
+            if ( ((y < lastY) || (x < lastX)) &&
+                 (slicePos+1 != blocksPerSlice) &&
+                 (slicePos != 0) &&
+                 (! lastIntra) &&
+                 (BSkipBlocks) ) {
+                make_skip_block =
+                    MotionSufficient(curr, &currentBlock, 
+                                     prev, next, y, x, oldMode, oldMotion);
+            } else
+                make_skip_block = FALSE;
+
+            if (make_skip_block) {
+                /* skipped macro block */
+                dct_data[y][x].useMotion = SKIP;
+            } else
+                makeNonSkipBlock(y, x, curr, prev, next, specificsOn,
+                                 mbAddress,
+                                 QScale, &currentBlock,
+                                 &mode, &oldMode,
+                                 IntraPBAllowed,
+                                 &lastIntra, &motion, &oldMotion, &stats);
+
+            ++mbAddress;
+        }
+    }
+
+    /* reset everything */
+    zeroMotion(&oldMotion);
+    oldMode = MOTION_FORWARD;
+    lastIntra = TRUE;
+    y_dc_pred = cr_dc_pred = cb_dc_pred = 128;
+    mbAddress = 0;
+
+    /* Now generate the frame */
+    for (y = 0; y < lastBlockY; y += 2) {
+      for (x = 0; x < lastBlockX; x += 2) {
+    slicePos = (mbAddress % blocksPerSlice);
+
+    if ( (slicePos == 0) && (mbAddress != 0) ) {
+      if (specificsOn) {
+        /* Make sure no slice Qscale change */
+        newQScale = 
+            SpecLookup(curr->id,1,mbAddress/blocksPerSlice, &info, QScale);
+        if (newQScale != -1) QScale = newQScale;
+      }
+      Mhead_GenSliceEnder(bb);
+      Mhead_GenSliceHeader(bb, 1+(y>>1), QScale, NULL, 0);
+
+      /* reset everything */
+      zeroMotion(&oldMotion);
+      oldMode = MOTION_FORWARD;
+      lastIntra = TRUE;
+      y_dc_pred = cr_dc_pred = cb_dc_pred = 128;
+
+      mbAddrInc = 1+(x>>1);
+    }
+
+    /*  Determine if new Qscale needed for Rate Control purposes */
+    if (bitstreamMode == FIXED_RATE) {
+      rc_blockStart =  bb->cumulativeBits;
+      newQScale = needQScaleChange(QScale,
+                       curr->y_blocks[y][x],
+                       curr->y_blocks[y][x+1],
+                       curr->y_blocks[y+1][x],
+                       curr->y_blocks[y+1][x+1]);
+      if (newQScale > 0) {
+        QScale = newQScale;
+      }
+    }
+ 
+    if (specificsOn) {
+      newQScale = SpecLookup(curr->id, 2, mbAddress, &info, QScale);
+      if (newQScale != -1) {
+        QScale = newQScale;
+      }}
+
+    if (dct_data[y][x].useMotion == NO_MOTION) {
+
+      GEN_I_BLOCK(B_FRAME, curr, bb, mbAddrInc, QScale);
+      mbAddrInc = 1;
+      stats.IBits += (bb->cumulativeBits - stats.totalBits);
+      stats.totalBits = bb->cumulativeBits;
+          
+      /* reset because intra-coded */
+      zeroMotion(&oldMotion);
+      oldMode = MOTION_FORWARD;
+      lastIntra = TRUE;
+          
+      if ( printSNR ) {
+        /* need to decode block we just encoded */
+        /* and reverse the DCT transform */
+        for ( idx = 0; idx < 6; idx++ ) {
+          Mpost_UnQuantZigBlock(fb[idx], dec[idx], QScale, TRUE);
+          mpeg_jrevdct((int16 *)dec[idx]);
+        }
+
+        /* now, unblockify */
+        BlockToData(curr->decoded_y, dec[0], y, x);
+        BlockToData(curr->decoded_y, dec[1], y, x+1);
+        BlockToData(curr->decoded_y, dec[2], y+1, x);
+        BlockToData(curr->decoded_y, dec[3], y+1, x+1);
+        BlockToData(curr->decoded_cb, dec[4], y>>1, x>>1);
+        BlockToData(curr->decoded_cr, dec[5], y>>1, x>>1);
+      }
+    } else if (dct_data[y][x].useMotion == SKIP) {
+      ++stats.Skipped;
+      mbAddrInc++;
+          
+      /* decode skipped block */
+      if (printSNR) {
+          struct motion motion;
+        
+          for (idx = 0; idx < 6; ++idx)
+              memset((char *)dec[idx], 0, sizeof(Block)); 
+        
+          if (pixelFullSearch) {
+              motion.fwd.y = 2 * oldMotion.fwd.y;
+              motion.fwd.x = 2 * oldMotion.fwd.x;
+              motion.bwd.y = 2 * oldMotion.bwd.y;
+              motion.bwd.x = 2 * oldMotion.bwd.x;
+          } else
+              motion = oldMotion;
+          
+          /* now add the motion block */
+          AddBMotionBlock(dec[0], prev->decoded_y,
+                          next->decoded_y, y, x, mode, motion);
+          AddBMotionBlock(dec[1], prev->decoded_y,
+                          next->decoded_y, y, x+1, mode, motion);
+          AddBMotionBlock(dec[2], prev->decoded_y,
+                          next->decoded_y, y+1, x, mode, motion);
+          AddBMotionBlock(dec[3], prev->decoded_y,
+                          next->decoded_y, y+1, x+1, mode, motion);
+          AddBMotionBlock(dec[4], prev->decoded_cb,
+                          next->decoded_cb, y/2, x/2, mode,
+                          halfMotion(motion));
+          AddBMotionBlock(dec[5], prev->decoded_cr,
+                          next->decoded_cr, y/2, x/2, mode,
+                          halfMotion(motion));
+        
+          /* now, unblockify */
+          BlockToData(curr->decoded_y, dec[0], y, x);
+          BlockToData(curr->decoded_y, dec[1], y, x+1);
+          BlockToData(curr->decoded_y, dec[2], y+1, x);
+          BlockToData(curr->decoded_y, dec[3], y+1, x+1);
+          BlockToData(curr->decoded_cb, dec[4], y/2, x/2);
+          BlockToData(curr->decoded_cr, dec[5], y/2, x/2);
+      }
+    } else   /* B block */ {
+        int const fCode = fCodeB;   
+        int pattern;
+        
+        pattern = dct_data[y][x].pattern;
+        motion.fwd.y = dct_data[y][x].fmotionY;
+        motion.fwd.x = dct_data[y][x].fmotionX;
+        motion.bwd.y = dct_data[y][x].bmotionY;
+        motion.bwd.x = dct_data[y][x].bmotionX;
+
+        if (pixelFullSearch)
+            motion = halfMotion(motion);
+          
+        /* create flat blocks and update pattern if necessary */
+    calc_blocks:
+        /* Note DoQuant references QScale, overflowChange, overflowValue,
+           pattern, and the calc_blocks label                 */
+        DoQuant(0x20, dct[y][x], fba[0]);
+        DoQuant(0x10, dct[y][x+1], fba[1]);
+        DoQuant(0x08, dct[y+1][x], fba[2]);
+        DoQuant(0x04, dct[y+1][x+1], fba[3]);
+        DoQuant(0x02, dctb[y/2][x/2], fba[4]);
+        DoQuant(0x01, dctr[y/2][x/2], fba[5]);
+
+        motionForward  = (dct_data[y][x].mode != MOTION_BACKWARD);
+        motionBackward = (dct_data[y][x].mode != MOTION_FORWARD);
+        
+        /* Encode Vectors */
+        if (motionForward) {
+            /* transform the fMotion vector into the appropriate values */
+            offsetY = motion.fwd.y - oldMotion.fwd.y;
+            offsetX = motion.fwd.x - oldMotion.fwd.x;
+
+            encodeMotionVector(offsetX, offsetY,
+                               &motionQuot.fwd, &motionRem.fwd,
+                               FORW_F, fCode);
+            oldMotion.fwd = motion.fwd;
+        }
+          
+        if (motionBackward) {
+            /* transform the bMotion vector into the appropriate values */
+            offsetY = motion.bwd.y - oldMotion.bwd.y;
+            offsetX = motion.bwd.x - oldMotion.bwd.x;
+            encodeMotionVector(offsetX, offsetY,
+                               &motionQuot.bwd, &motionRem.bwd,
+                               BACK_F, fCode);
+            oldMotion.bwd = motion.bwd;
+        }
+          
+        oldMode = dct_data[y][x].mode;
+          
+        if (printSNR) { /* Need to decode */
+            if (pixelFullSearch) {
+                motion.fwd.x *= 2; motion.fwd.y *= 2;
+                motion.bwd.x *= 2; motion.bwd.y *= 2;
+            }
+            for ( idx = 0; idx < 6; idx++ ) {
+                if ( pattern & (1 << (5-idx)) ) {
+                    Mpost_UnQuantZigBlock(fba[idx], dec[idx], QScale, FALSE);
+                    mpeg_jrevdct((int16 *)dec[idx]);
+                } else {
+                    memset((char *)dec[idx], 0, sizeof(Block));
+                }
+            }
+
+            /* now add the motion block */
+            AddBMotionBlock(dec[0], prev->decoded_y,
+                            next->decoded_y, y, x, mode, motion);
+            AddBMotionBlock(dec[1], prev->decoded_y,
+                            next->decoded_y, y, x+1, mode, motion);
+            AddBMotionBlock(dec[2], prev->decoded_y,
+                            next->decoded_y, y+1, x, mode, motion);
+            AddBMotionBlock(dec[3], prev->decoded_y,
+                            next->decoded_y, y+1, x+1, mode, motion);
+            AddBMotionBlock(dec[4], prev->decoded_cb,
+                            next->decoded_cb, y/2, x/2, mode,
+                            halfMotion(motion));
+            AddBMotionBlock(dec[5], prev->decoded_cr,
+                            next->decoded_cr, y/2, x/2, mode,
+                            halfMotion(motion));
+
+            /* now, unblockify */
+            BlockToData(curr->decoded_y,  dec[0], y,   x);
+            BlockToData(curr->decoded_y,  dec[1], y,   x+1);
+            BlockToData(curr->decoded_y,  dec[2], y+1, x);
+            BlockToData(curr->decoded_y,  dec[3], y+1, x+1);
+            BlockToData(curr->decoded_cb, dec[4], y/2, x/2);
+            BlockToData(curr->decoded_cr, dec[5], y/2, x/2);
+        }
+
+        /* reset because non-intra-coded */
+        y_dc_pred = cr_dc_pred = cb_dc_pred = 128;
+        lastIntra = FALSE;
+        mode = dct_data[y][x].mode;
+        
+        /*      DBG_PRINT(("MB Header(%d,%d)\n", x, y));  */
+        Mhead_GenMBHeader(
+            bb, 3 /* pict_code_type */, mbAddrInc /* addr_incr */,
+            QScale /* q_scale */,
+            fCodeB /* forw_f_code */, fCodeB /* back_f_code */,
+            motionRem.fwd.x /* horiz_forw_r */,
+            motionRem.fwd.y /* vert_forw_r */,
+            motionRem.bwd.x /* horiz_back_r */,
+            motionRem.bwd.y /* vert_back_r */,
+            motionForward /* motion_forw */,
+            motionQuot.fwd.x /* m_horiz_forw */,
+            motionQuot.fwd.y /* m_vert_forw */,
+            motionBackward /* motion_back */,
+            motionQuot.bwd.x /* m_horiz_back */,
+            motionQuot.bwd.y /* m_vert_back */,
+            pattern /* mb_pattern */, FALSE /* mb_intra */);
+
+        mbAddrInc = 1;
+          
+        /* now output the difference */
+        {
+            unsigned int x;
+            for (x = 0; x < 6; ++x) {
+                if (GET_ITH_BIT(pattern, 5-x))
+                    Mpost_RLEHuffPBlock(fba[x], bb);
+            }
+        }
+          
+        switch (mode) {
+        case MOTION_FORWARD:
+            bframeStats.BFOBits += (bb->cumulativeBits - stats.totalBits);
+            break;
+        case MOTION_BACKWARD:
+            bframeStats.BBABits += (bb->cumulativeBits - stats.totalBits);
+            break;
+        case MOTION_INTERPOLATE:
+            bframeStats.BINBits += (bb->cumulativeBits - stats.totalBits);
+            break;
+        default:
+            pm_error("PROGRAMMER ERROR:  Illegal mode: %d", mode);
+      }
+      
+      stats.BBits += (bb->cumulativeBits - stats.totalBits);
+      stats.totalBits = bb->cumulativeBits;
+    
+      if (overflowChange) {
+        /* undo an overflow-caused Qscale change */
+        overflowChange = FALSE;
+        QScale -= overflowValue;
+        overflowValue = 0;
+      }
+    } /* if I-block, skip, or B */
+
+    mbAddress++;
+    /*   Rate Control  */
+    if (bitstreamMode == FIXED_RATE) {
+      incMacroBlockBits( bb->cumulativeBits - rc_blockStart);
+      rc_blockStart = bb->cumulativeBits;
+      MB_RateOut(TYPE_BFRAME);
+    }
+    
+      }
+    }
+
+    if ( printSNR ) {
+      BlockComputeSNR(curr,snr,psnr);
+      totalSNR += snr[0];
+      totalPSNR += psnr[0];
+    }
+    
+    Mhead_GenSliceEnder(bb);
+    /*   Rate Control  */
+    if (bitstreamMode == FIXED_RATE) {
+      updateRateControl(TYPE_BFRAME);
+    }
+    
+    endTime = time_elapsed();
+    totalTime += (endTime-startTime);
+    
+    if ( showBitRatePerFrame ) {
+      /* ASSUMES 30 FRAMES PER SECOND */
+      fprintf(bitRateFile, "%5d\t%8d\n", curr->id,
+          30*(bb->cumulativeBits-totalFrameBits));
+    }
+    
+    if ( frameSummary && !realQuiet) {
+      fprintf(stdout, "FRAME %d (B):  "
+              "I BLOCKS: %5d;  B BLOCKS: %5d   SKIPPED: %5d (%ld seconds)\n",
+              curr->id, stats.IBlocks, stats.BBlocks, stats.Skipped,
+              (long)((endTime-startTime)/TIME_RATE));
+      if ( printSNR )
+    fprintf(stdout, "FRAME %d:  "
+            "SNR:  %.1f\t%.1f\t%.1f\tPSNR:  %.1f\t%.1f\t%.1f\n",
+            curr->id, snr[0], snr[1], snr[2],
+            psnr[0], psnr[1], psnr[2]);
+    }
+    
+    bframeStats.FrameBits += (bb->cumulativeBits-totalFrameBits);
+    bframeStats.BIBlocks += stats.IBlocks;
+    bframeStats.BBBlocks += stats.BBlocks;
+    bframeStats.BSkipped += stats.Skipped;
+    bframeStats.BIBits   += stats.IBits;
+    bframeStats.BBBits   += stats.BBits;
+  }
+
+
+/*===========================================================================*
+ *
+ * SetBQScale
+ *
+ *  set the B-frame Q-scale
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    qscaleB
+ *
+ *===========================================================================*/
+void
+SetBQScale(qB)
+    int qB;
+{
+    qscaleB = qB;
+}
+
+
+/*===========================================================================*
+ *
+ * GetBQScale
+ *
+ *  get the B-frame Q-scale
+ *
+ * RETURNS: the Q-scale
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+int
+GetBQScale()
+{
+    return qscaleB;
+}
+
+
+/*===========================================================================*
+ *
+ * ResetBFrameStats
+ *
+ *  reset the B-frame stats
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+ResetBFrameStats() {
+
+    bframeStats.BIBlocks = 0;
+    bframeStats.BBBlocks = 0;
+    bframeStats.BSkipped = 0;
+    bframeStats.BIBits = 0;
+    bframeStats.BBBits = 0;
+    bframeStats.Frames = 0;
+    bframeStats.FrameBits = 0;
+    totalTime = 0;
+}
+
+
+
+float
+BFrameTotalTime(void) {
+    return (float)totalTime/(float)TIME_RATE;
+}
+
+
+
+void
+ShowBFrameSummary(unsigned int const inputFrameBits, 
+                  unsigned int const totalBits, 
+                  FILE *       const fpointer) {
+
+    if (bframeStats.Frames > 0) {
+        fprintf(fpointer, "-------------------------\n");
+        fprintf(fpointer, "*****B FRAME SUMMARY*****\n");
+        fprintf(fpointer, "-------------------------\n");
+
+        if (bframeStats.BIBlocks > 0) {
+            fprintf(fpointer,
+                    "  I Blocks:  %5d     (%6d bits)     (%5d bpb)\n",
+                    bframeStats.BIBlocks, bframeStats.BIBits,
+                    bframeStats.BIBits/bframeStats.BIBlocks);
+        } else
+            fprintf(fpointer, "  I Blocks:  %5d\n", 0);
+
+        if (bframeStats.BBBlocks > 0) {
+            fprintf(fpointer,
+                    "  B Blocks:  %5d     (%6d bits)     (%5d bpb)\n",
+                    bframeStats.BBBlocks, bframeStats.BBBits,
+                    bframeStats.BBBits/bframeStats.BBBlocks);
+            fprintf(fpointer,
+                    "  B types:   %5d     (%4d bpb) "
+                    "forw  %5d (%4d bpb) back   %5d (%4d bpb) bi\n",
+                    bframeStats.BFOBlocks,
+                    (bframeStats.BFOBlocks==0) ? 
+                        0 : bframeStats.BFOBits/bframeStats.BFOBlocks,
+                    bframeStats.BBABlocks,
+                    (bframeStats.BBABlocks==0) ? 
+                        0 : bframeStats.BBABits/bframeStats.BBABlocks,
+                    bframeStats.BINBlocks,
+                    (bframeStats.BINBlocks==0) ? 
+                        0 : bframeStats.BINBits/bframeStats.BINBlocks);
+        } else
+            fprintf(fpointer, "  B Blocks:  %5d\n", 0);
+
+        fprintf(fpointer, "  Skipped:   %5d\n", bframeStats.BSkipped);
+
+        fprintf(fpointer, "  Frames:    %5d     (%6d bits)     "
+                "(%5d bpf)     (%2.1f%% of total)\n",
+                bframeStats.Frames, bframeStats.FrameBits,
+                bframeStats.FrameBits/bframeStats.Frames,
+                100.0*(float)bframeStats.FrameBits/(float)totalBits);        
+        fprintf(fpointer, "  Compression:  %3d:1     (%9.4f bpp)\n",
+                bframeStats.Frames*inputFrameBits/bframeStats.FrameBits,
+                24.0*(float)bframeStats.FrameBits/
+                    (float)(bframeStats.Frames*inputFrameBits));
+        if (printSNR)
+            fprintf(fpointer, "  Avg Y SNR/PSNR:  %.1f     %.1f\n",
+                    totalSNR/(float)bframeStats.Frames,
+                    totalPSNR/(float)bframeStats.Frames);
+        if (totalTime == 0) {
+            fprintf(fpointer, "  Seconds:  NONE\n");
+        } else {
+            fprintf(fpointer, "  Seconds:  %9ld     (%9.4f fps)  "
+                    "(%9ld pps)  (%9ld mps)\n",
+                    (long)(totalTime/TIME_RATE),
+                    (float)((float)(TIME_RATE*bframeStats.Frames)/
+                            (float)totalTime),
+                    (long)((float)TIME_RATE*(float)bframeStats.Frames *
+                           (float)inputFrameBits/(24.0*(float)totalTime)),
+                    (long)((float)TIME_RATE*(float)bframeStats.Frames *
+                           (float)inputFrameBits/(256.0*24.0 *
+                                                  (float)totalTime)));
+        }
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ *  compute the luminance block resulting from motion compensation
+ *
+ * RETURNS: motionBlock modified
+ *
+ * SIDE EFFECTS:    none
+ *
+ * PRECONDITION:    the motion vectors must be valid!
+ *
+ *===========================================================================*/
+void
+ComputeBMotionLumBlock(MpegFrame * const prev,
+                       MpegFrame * const next,
+                       int         const by,
+                       int         const bx,
+                       int         const mode,
+                       motion      const motion,
+                       LumBlock *  const motionBlockP) {
+
+    switch(mode) {
+    case MOTION_FORWARD:
+        ComputeMotionLumBlock(prev, by, bx, motion.fwd, motionBlockP);
+        break;
+    case MOTION_BACKWARD:
+        ComputeMotionLumBlock(next, by, bx, motion.bwd, motionBlockP);
+        break;
+    case MOTION_INTERPOLATE: {
+        LumBlock prevBlock, nextBlock;
+        unsigned int y;
+
+        ComputeMotionLumBlock(prev, by, bx, motion.fwd, &prevBlock);
+        ComputeMotionLumBlock(next, by, bx, motion.bwd, &nextBlock);
+        
+        for (y = 0; y < 16; ++y) {
+            unsigned int x;
+            for (x = 0; x < 16; ++x)
+                motionBlockP->l[y][x] =
+                    (prevBlock.l[y][x] + nextBlock.l[y][x] + 1) / 2;
+        }
+    } break;
+    default:
+        pm_error("Bad mode!  Programmer error!");
+    }  /* switch */
+}
+
+
+/*===========================================================================*
+ *
+ *  estimate the seconds to compute a B-frame
+ *
+ * RETURNS: the time, in seconds
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+float
+EstimateSecondsPerBFrame() {
+    if (bframeStats.Frames == 0)
+        return 20.0;
+    else
+        return (float)totalTime/((float)TIME_RATE*(float)bframeStats.Frames);
+}
+
+
diff --git a/converter/ppm/ppmtompeg/bitio.c b/converter/ppm/ppmtompeg/bitio.c
new file mode 100644
index 00000000..e0dc4d4e
--- /dev/null
+++ b/converter/ppm/ppmtompeg/bitio.c
@@ -0,0 +1,536 @@
+/*===========================================================================*
+ * bitio.c
+ *
+ *  Procedures concerned with the bit-wise I/O
+ *
+ * EXPORTED PROCEDURES:
+ *  Bitio_New
+ *  Bitio_Free
+ *  Bitio_Write
+ *  Bitio_Flush
+ *  Bitio_WriteToSocket
+ *  Bitio_BytePad
+ *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include <assert.h>
+#include <time.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "pm.h"
+#include "intcode.h"
+
+#include "all.h"
+#include "byteorder.h"
+#include "bitio.h"
+#include "mtypes.h"
+
+
+
+/*==================*
+ * STATIC VARIABLES *
+ *==================*/
+
+static uint32 lower_mask[33] = {
+    0,
+    0x1, 0x3, 0x7, 0xf,
+    0x1f, 0x3f, 0x7f, 0xff,
+    0x1ff, 0x3ff, 0x7ff, 0xfff,
+    0x1fff, 0x3fff, 0x7fff, 0xffff,
+    0x1ffff, 0x3ffff, 0x7ffff, 0xfffff,
+    0x1fffff, 0x3fffff, 0x7fffff, 0xffffff,
+    0x1ffffff, 0x3ffffff, 0x7ffffff, 0xfffffff,
+    0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff
+};
+
+
+extern time_t IOtime;
+
+
+/*===========================================================================*
+ *
+ * Dump
+ *
+ *  Writes out the first MAX_BITS bits of the bit bucket to the
+ *  appropriate output file
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:  none
+ *
+ *===========================================================================*/
+static void
+Dump(BitBucket * const bbPtr) {
+    struct bitBucket *ptr, *tempPtr;
+    int i, nitems;
+    int     bitsWritten = 0;
+    time_t  tempTimeStart, tempTimeEnd;
+
+    time(&tempTimeStart);
+
+    for (ptr = bbPtr->firstPtr; ptr && (bitsWritten < MAX_BITS);
+         ptr = ptr->nextPtr) {
+
+        bigend32 buffer[WORDS_PER_BUCKET];
+
+        if (ptr->bitsleftcur == 32 && ptr->currword == 0) {
+            continue;       /* empty */
+        }
+
+        for (i = 0; i <= ptr->currword; ++i)
+            buffer[i] = pm_bigendFromUint32(ptr->bits[i]);
+
+        nitems = fwrite(buffer, sizeof(buffer[0]), ptr->currword + 1, 
+                        bbPtr->filePtr);
+        if (nitems != ptr->currword+1) {
+            fprintf(stderr, 
+                    "Whoa!  Trouble writing %u words (wrote %u words)!  "
+                    "Game over, dude!\n",
+                    ptr->currword+1, nitems);
+            exit(1);
+        }
+
+        bitsWritten += ((ptr->currword + 1) * 32);
+    }
+
+    while ( bbPtr->firstPtr != ptr ) {
+        tempPtr = bbPtr->firstPtr;
+        bbPtr->firstPtr = tempPtr->nextPtr;
+        free(tempPtr);
+    }
+
+    bbPtr->totalbits -= bitsWritten;
+    bbPtr->bitsWritten += bitsWritten;
+
+    time(&tempTimeEnd);
+    IOtime += (tempTimeEnd-tempTimeStart);
+}
+
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+
+
+/*===========================================================================*
+ *
+ * Bitio_New
+ *
+ *  Create a new bit bucket; filePtr is a pointer to the open file the
+ *  bits should ultimately be written to.
+ *
+ * RETURNS: pointer to the resulting bit bucket
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+BitBucket *
+Bitio_New(FILE * const filePtr) {
+
+    BitBucket *bbPtr;
+
+    bbPtr = (BitBucket *) malloc(sizeof(BitBucket));
+    ERRCHK(bbPtr, "malloc");
+
+    bbPtr->firstPtr = bbPtr->lastPtr = malloc(sizeof(struct bitBucket));
+    ERRCHK(bbPtr->firstPtr, "malloc");
+
+    bbPtr->totalbits = 0;
+    bbPtr->cumulativeBits = 0;
+    bbPtr->bitsWritten = 0;
+    bbPtr->filePtr = filePtr;
+
+    bbPtr->firstPtr->nextPtr = NULL;
+    bbPtr->firstPtr->bitsleft = MAXBITS_PER_BUCKET;
+    bbPtr->firstPtr->bitsleftcur = 32;
+    bbPtr->firstPtr->currword = 0;
+    memset((char *)bbPtr->firstPtr->bits, 0, 
+           sizeof(uint32) * WORDS_PER_BUCKET);
+
+    return bbPtr;
+}
+
+
+
+BitBucket *
+Bitio_New_Filename(const char * const fileName) {
+
+    FILE * outputFile;
+    
+    outputFile = fopen(fileName, "wb");
+    if (outputFile == NULL)
+        pm_error("Could not open output file '%s'.  "
+                 "Errno=%d (%s)", fileName, errno, strerror(errno));
+
+    return Bitio_New(outputFile);
+}
+
+
+
+/*===========================================================================*
+ *
+ * Bitio_Free
+ *
+ *  Frees the memory associated with the given bit bucket
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Bitio_Free(BitBucket * const bbPtr) {
+
+    struct bitBucket *tmpPtr, *nextPtr;
+
+    for (tmpPtr = bbPtr->firstPtr; tmpPtr != NULL; tmpPtr = nextPtr) {
+        nextPtr = tmpPtr->nextPtr;
+        free(tmpPtr);
+    }
+    free(bbPtr);
+}
+
+
+/*===========================================================================*
+ *
+ * Bitio_Write
+ *
+ *  Writes 'nbits' bits from 'bits' into the given bit bucket
+ *  'nbits' must be between 0 and 32
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    if the number of bits in the bit bucket surpasses
+ *          MAX_BITS, then that many bits are flushed to the
+ *          appropriate output file
+ *
+ *===========================================================================*/
+void
+Bitio_Write(BitBucket * const bbPtr, 
+            uint32      const bits_arg, 
+            int         const nbits) {
+
+    register struct bitBucket *lastPtr, *newPtr;
+    register int delta;
+    uint32 bits;
+    
+    bits=bits_arg;
+    assert(nbits <= 32 && nbits >= 0);
+
+    /*
+     * Clear top bits if not part of data, necessary due to down and
+     * dirty calls of Bitio_Write with unecessary top bits set.
+     */
+
+    bits &= lower_mask[nbits];
+    bits = bits & lower_mask[nbits];
+
+    bbPtr->totalbits += nbits;
+    bbPtr->cumulativeBits += nbits;
+    lastPtr = bbPtr->lastPtr;
+
+    delta = nbits - lastPtr->bitsleft;
+    if (delta >= 0) {
+        /*
+         * there's not enough room in the current bucket, so we're
+         * going to have to allocate another bucket
+         */
+        newPtr = lastPtr->nextPtr = (struct bitBucket *) 
+            malloc(sizeof(struct bitBucket));
+        ERRCHK(newPtr, "malloc");
+        newPtr->nextPtr = NULL;
+        newPtr->bitsleft = MAXBITS_PER_BUCKET;
+        newPtr->bitsleftcur = 32;
+        newPtr->currword = 0;
+        memset((char *)newPtr->bits, 0, sizeof(uint32) * WORDS_PER_BUCKET);
+        bbPtr->lastPtr = newPtr;
+
+        assert(lastPtr->currword == WORDS_PER_BUCKET - 1);
+        lastPtr->bits[WORDS_PER_BUCKET - 1] |= (bits >> delta);
+        lastPtr->bitsleft = 0;
+        lastPtr->bitsleftcur = 0;
+        /* lastPtr->currword++; */
+
+        if (!delta) {
+            if ( bbPtr->totalbits > MAX_BITS ) {
+                Dump(bbPtr);
+            }
+        }
+
+        assert(delta <= 32);
+        newPtr->bits[0] = (bits & lower_mask[delta]) << (32 - delta);
+        newPtr->bitsleft -= delta;
+        newPtr->bitsleftcur -= delta;
+    } else {
+        /*
+         * the current bucket will be sufficient
+         */
+        delta = nbits - lastPtr->bitsleftcur;
+        lastPtr->bitsleftcur -= nbits;
+        lastPtr->bitsleft -= nbits;
+
+        if (delta >= 0)
+        {
+            /*
+             * these bits will span more than one word
+             */
+            lastPtr->bits[lastPtr->currword] |= (bits >> delta);
+            lastPtr->currword++;
+            lastPtr->bits[lastPtr->currword] = 
+                (bits & lower_mask[delta]) << (32 - delta);
+            lastPtr->bitsleftcur = 32 - delta;
+        } else {
+            /*
+         * these bits will fit, whole
+         */
+            lastPtr->bits[lastPtr->currword] |= (bits << (-delta));
+        }
+    }
+
+    if ( bbPtr->totalbits > MAX_BITS )  /* flush bits */
+        Dump(bbPtr);
+}
+
+
+/*===========================================================================*
+ *
+ * Bitio_Flush
+ *
+ *  Flushes all of the remaining bits in the given bit bucket to the
+ *  appropriate output file.  It will generate up to the nearest 8-bit
+ *  unit of bits, which means that up to 7 extra 0 bits will be appended
+ *  to the end of the file.
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    frees the bit bucket
+ *
+ *===========================================================================*/
+void
+Bitio_Flush(BitBucket * const bbPtr) {
+
+    struct bitBucket *ptr, *tempPtr;
+    uint32  lastWord;
+    int i, nitems;
+    int     bitsWritten = 0;
+    int     bitsLeft;
+    unsigned int     numWords;
+    uint8   charBuf[4];
+    boolean    flushHere = FALSE;
+    time_t  tempTimeStart, tempTimeEnd;
+
+    time(&tempTimeStart);
+
+    bitsLeft = bbPtr->totalbits;
+
+    for (ptr = bbPtr->firstPtr; ptr; ptr = ptr->nextPtr) {
+        if (ptr->bitsleftcur == 32 && ptr->currword == 0) {
+            continue;       /* empty */
+        }
+
+        if ( bitsLeft >= 32 ) {
+            bigend32 buffer[WORDS_PER_BUCKET];
+
+            if ( ((ptr->currword + 1) * 32) > bitsLeft ) {
+                numWords = ptr->currword;
+                flushHere = TRUE;
+            } else
+                numWords = ptr->currword+1;
+
+            for (i = 0; i < numWords; ++i)
+                buffer[i] = pm_bigendFromUint32(ptr->bits[i]);
+
+            nitems = fwrite(buffer, sizeof(buffer[0]), numWords,
+                            bbPtr->filePtr);
+            if (nitems != numWords) {
+                if (ferror(bbPtr->filePtr))
+                    pm_error("Error writing %u words to flush a bit bucket.  "
+                             "fwrite() gives errno %d (%s)", 
+                             numWords, errno, strerror(errno));
+                else
+                    pm_error("Problem writing %u words "
+                             "to flush a bit bucket.  "
+                             "Only %d words transferred.", 
+                             numWords, nitems);
+            }
+
+            bitsWritten += (numWords * 32);
+            bitsLeft -= (numWords * 32);
+        } else {
+            flushHere = TRUE;
+        }
+
+        if ( (bitsLeft < 32) && flushHere ) {
+            lastWord = ptr->bits[ptr->currword];
+
+            /* output the lastPtr word in big-endian order (network) */
+
+            /* now write out lastPtr bits */
+            while ( bitsLeft > 0 ) {
+                charBuf[0] = (lastWord >> 24);
+                charBuf[0] &= lower_mask[8];
+                fwrite(charBuf, 1, sizeof(uint8), bbPtr->filePtr);
+                lastWord = (lastWord << 8);
+                bitsLeft -= 8;
+                bitsWritten += 8;
+            }
+        }
+    }
+    fflush(bbPtr->filePtr);
+    while ( bbPtr->firstPtr != ptr ) {
+        tempPtr = bbPtr->firstPtr;
+        bbPtr->firstPtr = tempPtr->nextPtr;
+        free(tempPtr);
+    }
+
+    free(bbPtr);
+
+    time(&tempTimeEnd);
+    IOtime += (tempTimeEnd-tempTimeStart);
+}
+
+
+
+void
+Bitio_Close(BitBucket * const bbPtr) {
+
+    fclose(bbPtr->filePtr);
+}
+
+
+
+/*===========================================================================*
+ *
+ * Bitio_WriteToSocket
+ *
+ *  Writes all of the remaining bits in the given bit bucket to the
+ *  given socket.  May pad the end of the socket stream with extra 0
+ *  bits as does Bitio_Flush.
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    frees the bit bucket
+ *
+ *===========================================================================*/
+void
+Bitio_WriteToSocket(BitBucket * const bbPtr, 
+                    int         const socket) {
+
+    struct bitBucket *ptr, *tempPtr;
+    uint32  lastWord;
+    int i, nitems;
+    int     bitsWritten = 0;
+    int     bitsLeft;
+    int     numWords;
+    uint8   charBuf[4];
+    boolean    flushHere = FALSE;
+
+    bitsLeft = bbPtr->totalbits;
+
+    for (ptr = bbPtr->firstPtr; ptr; ptr = ptr->nextPtr) {
+        if (ptr->bitsleftcur == 32 && ptr->currword == 0) {
+            continue;       /* empty */
+        }
+
+        if ( bitsLeft >= 32 ) {
+            bigend32 buffer[WORDS_PER_BUCKET];
+
+            if ( ((ptr->currword + 1) * 32) > bitsLeft ) {
+                numWords = ptr->currword;
+                flushHere = TRUE;
+            } else {
+                numWords = ptr->currword+1;
+            }
+
+            for (i = 0; i < numWords; ++i)
+                buffer[i] = pm_bigendFromUint32(ptr->bits[i]);
+
+            nitems = write(socket, buffer, numWords * sizeof(buffer[0]));
+            if (nitems != numWords*sizeof(uint32)) {
+                fprintf(stderr, "Whoa!  Trouble writing %u bytes "
+                        "(wrote %u bytes)!  "
+                        "Game over, dude!\n",
+                        (unsigned)(numWords*sizeof(buffer[0])), nitems);
+                exit(1);
+            }
+
+            bitsWritten += (numWords * 32);
+            bitsLeft -= (numWords * 32);
+        } else {
+            flushHere = TRUE;
+        }
+
+        if ( (bitsLeft < 32) && flushHere ) {
+            lastWord = ptr->bits[ptr->currword];
+
+            /* output the lastPtr word in big-endian order (network) */
+
+            /* now write out lastPtr bits */
+            while ( bitsLeft > 0 ) {
+                charBuf[0] = (lastWord >> 24);
+                charBuf[0] &= lower_mask[8];
+                if ( write(socket, charBuf, 1) != 1 ) {
+                    fprintf(stderr, "ERROR:  write of lastPtr bits\n");
+                    exit(1);
+                }
+                lastWord = (lastWord << 8);
+                bitsLeft -= 8;
+                bitsWritten += 8;
+            }
+        }
+    }
+
+    while ( bbPtr->firstPtr != ptr ) {
+        tempPtr = bbPtr->firstPtr;
+        bbPtr->firstPtr = tempPtr->nextPtr;
+        free(tempPtr);
+    }
+
+    free(bbPtr);
+}
+
+
+/*===========================================================================*
+ *
+ * Bitio_BytePad
+ *
+ *  Pads the end of the bit bucket to the nearest byte with 0 bits
+ *
+ * RETURNS: nothing
+ *
+ *===========================================================================*/
+void
+Bitio_BytePad(BitBucket * const bbPtr) {
+
+    struct bitBucket *lastPtrPtr = bbPtr->lastPtr;
+
+    if (lastPtrPtr->bitsleftcur % 8) {
+        Bitio_Write(bbPtr, 0, lastPtrPtr->bitsleftcur % 8);
+    }
+}
diff --git a/converter/ppm/ppmtompeg/block.c b/converter/ppm/ppmtompeg/block.c
new file mode 100644
index 00000000..913518c4
--- /dev/null
+++ b/converter/ppm/ppmtompeg/block.c
@@ -0,0 +1,1045 @@
+/*===========================================================================*
+ * block.c
+ *
+ *  Block routines
+ *
+ * NOTES:   MAD =   Mean Absolute Difference
+ *===========================================================================*/
+
+/* Copyright information is at end of file */
+
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "all.h"
+#include "mtypes.h"
+#include "frames.h"
+#include "bitio.h"
+#include "prototypes.h"
+#include "fsize.h"
+#include "opts.h"
+#include "postdct.h"
+
+#include "block.h"
+
+#define TRUNCATE_UINT8(x)   ((x < 0) ? 0 : ((x > 255) ? 255 : x))
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+
+extern Block **dct, **dctb, **dctr;
+
+
+static vector
+halfVector(vector const v) {
+    vector half;
+
+    half.y = v.y/2;
+    half.x = v.x/2;
+
+    return half;
+}
+
+/*===========================*
+ * COMPUTE DCT OF DIFFERENCE *
+ *===========================*/
+
+/*===========================================================================*
+ *
+ *  compute current-motionBlock, take the DCT, and put the difference
+ *  back into current
+ *
+ * RETURNS: current block modified
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+ComputeDiffDCTBlock(Block           current,
+                    Block           dest,
+                    Block           motionBlock,
+                    boolean * const significantDifferenceP) {
+
+    unsigned int y;
+    int diff;
+
+    diff = 0;  /* initial value */
+
+    for (y = 0; y < 8; ++y) {
+        unsigned int x;
+        for (x = 0; x < 8; ++x) {
+            current[y][x] -= motionBlock[y][x];
+            diff += ABS(current[y][x]);
+        }
+    }
+    /* Kill the block if change is too small     */
+    /* (block_bound defaults to 128, see opts.c) */
+    if (diff < block_bound)
+        *significantDifferenceP = FALSE;
+    else {
+        mp_fwd_dct_block2(current, dest);
+        *significantDifferenceP = TRUE;
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ *  appropriate (according to pattern, the coded block pattern) blocks
+ *  of 'current' are diff'ed and DCT'd.
+ *
+ * RETURNS: current blocks modified
+ *
+ * SIDE EFFECTS:    Can remove too-small difference blocks from pattern
+ *
+ * PRECONDITIONS:   appropriate blocks of 'current' have not yet been
+ *          modified
+ *
+ *===========================================================================*/
+void
+ComputeDiffDCTs(MpegFrame * const current,
+                MpegFrame * const prev,
+                int         const by,
+                int         const bx,
+                vector      const m,
+                int *       const patternP) {
+    
+    Block motionBlock;
+
+    if (collect_quant && (collect_quant_detailed & 1))
+        fprintf(collect_quant_fp, "l\n");
+    if (*patternP & 0x20) {
+        boolean significantDiff;
+        ComputeMotionBlock(prev->ref_y, by, bx, m, &motionBlock);
+        ComputeDiffDCTBlock(current->y_blocks[by][bx], dct[by][bx],
+                            motionBlock, &significantDiff);
+        if (!significantDiff)
+            *patternP ^= 0x20;
+    }
+
+    if (*patternP & 0x10) {
+        boolean significantDiff;
+        ComputeMotionBlock(prev->ref_y, by, bx+1, m, &motionBlock);
+        ComputeDiffDCTBlock(current->y_blocks[by][bx+1], dct[by][bx+1],
+                            motionBlock, &significantDiff);
+        if (!significantDiff)
+            *patternP ^= 0x10;
+    }
+
+    if (*patternP & 0x8) {
+        boolean significantDiff;
+        ComputeMotionBlock(prev->ref_y, by+1, bx, m, &motionBlock);
+        ComputeDiffDCTBlock(current->y_blocks[by+1][bx], dct[by+1][bx],
+                            motionBlock, &significantDiff);
+        if (!significantDiff)
+            *patternP ^= 0x8;
+    }
+
+    if (*patternP & 0x4) {
+        boolean significantDiff;
+        ComputeMotionBlock(prev->ref_y, by+1, bx+1, m, &motionBlock);
+        ComputeDiffDCTBlock(current->y_blocks[by+1][bx+1], dct[by+1][bx+1],
+                            motionBlock, &significantDiff);
+        if (!significantDiff)
+            *patternP ^= 0x4;
+    }
+
+    if (collect_quant && (collect_quant_detailed & 1))
+        fprintf(collect_quant_fp, "c\n");
+
+    if (*patternP & 0x2) {
+        boolean significantDiff;
+        ComputeMotionBlock(prev->ref_cb, by/2, bx/2, halfVector(m),
+                           &motionBlock);
+        ComputeDiffDCTBlock(current->cb_blocks[by/2][bx/2],
+                            dctb[by/2][bx/2], motionBlock,
+                            &significantDiff);
+        if (!significantDiff)
+            *patternP ^= 0x2;
+    }
+
+    if (*patternP & 0x1) {
+        boolean significantDiff;
+        ComputeMotionBlock(prev->ref_cr, by/2, bx/2, halfVector(m),
+                           &motionBlock);
+        ComputeDiffDCTBlock(current->cr_blocks[by/2][bx/2],
+                            dctr[by/2][bx/2], motionBlock,
+                            &significantDiff);
+        if (!significantDiff)
+            *patternP ^= 0x1;
+    }
+}
+
+
+
+static void
+computePrevFyFx(MpegFrame * const prevFrame,
+                int         const by,
+                int         const bx,
+                vector      const m,
+                uint8 ***   const prevP,
+                int *       const fyP,
+                int *       const fxP) {
+
+    boolean const xHalf = (ABS(m.x) % 2 == 1);
+    boolean const yHalf = (ABS(m.y) % 2 == 1);
+
+    MotionToFrameCoord(by, bx, m.y/2, m.x/2, fyP, fxP);
+
+    assert(*fyP >= 0); assert(*fxP >= 0);
+
+    /* C integer arithmetic rounds toward zero.  But what we need is a
+       "floor" -- i.e. round down.  So we adjust now for where the dividend
+       in the above divide by two was negative.
+    */
+
+    if (xHalf) {
+        if (m.x < 0)
+            --*fxP;
+
+        if (yHalf) {
+            if (m.y < 0)
+                --*fyP;
+        
+            *prevP = prevFrame->halfBoth;
+        } else
+            *prevP = prevFrame->halfX;
+    } else if (yHalf) {
+        if (m.y < 0)
+            --*fyP;
+        
+        *prevP = prevFrame->halfY;
+    } else
+        *prevP = prevFrame->ref_y;
+}
+
+
+
+/*======================*
+ * COMPUTE MOTION BLOCK *
+ *======================*/
+
+/*===========================================================================*
+ *
+ *  compute the motion-compensated block
+ *
+ * RETURNS: motionBlock
+ *
+ * SIDE EFFECTS:    none
+ *
+ * PRECONDITIONS:   motion vector MUST be valid
+ *
+ * NOTE:  could try to speed this up using halfX, halfY, halfBoth,
+ *    but then would have to compute for chrominance, and it's just
+ *    not worth the trouble (this procedure is not called relatively
+ *    often -- a constant number of times per macroblock)
+ *
+ *===========================================================================*/
+void
+ComputeMotionBlock(uint8 ** const prev,
+                   int      const by,
+                   int      const bx,
+                   vector   const m,
+                   Block *  const motionBlockP) {
+
+    int fy, fx;
+    boolean xHalf, yHalf;
+
+    xHalf = (ABS(m.x) % 2 == 1);
+    yHalf = (ABS(m.y) % 2 == 1);
+
+    MotionToFrameCoord(by, bx, m.y/2, m.x/2, &fy, &fx);
+
+    if (xHalf && yHalf) {
+        unsigned int y;
+        /* really should be fy+y-1 and fy+y so do (fy-1)+y = fy+y-1 and
+           (fy-1)+y+1 = fy+y
+        */
+        if (m.y < 0)
+            --fy;
+
+        if (m.x < 0)
+            --fx;
+    
+        for (y = 0; y < 8; ++y) {
+            int16 * const destPtr = (*motionBlockP)[y];
+            uint8 * const srcPtr  = &(prev[fy+y][fx]);
+            uint8 * const srcPtr2 = &(prev[fy+y+1][fx]);
+            unsigned int x;
+
+            for (x = 0; x < 8; ++x)
+                destPtr[x] =
+                    (srcPtr[x]+srcPtr[x+1]+srcPtr2[x]+srcPtr2[x+1]+2) >> 2;
+        }
+    } else if (xHalf) {
+        unsigned int y;
+        if (m.x < 0)
+            --fx;
+        
+        for (y = 0; y < 8; ++y) {
+            int16 * const destPtr = (*motionBlockP)[y];
+            uint8 * const srcPtr  = &(prev[fy+y][fx]);
+            unsigned int x;
+
+            for (x = 0; x < 8; ++x)
+                destPtr[x] = (srcPtr[x]+srcPtr[x+1]+1) >> 1;
+        }
+    } else if (yHalf) {
+        unsigned int y;
+        if ( m.y < 0 )
+            fy--;
+
+        for (y = 0; y < 8; ++y) {
+            int16 * const destPtr = (*motionBlockP)[y];
+            uint8 * const srcPtr  = &(prev[fy+y][fx]);
+            uint8 * const srcPtr2 = &(prev[fy+y+1][fx]);
+            unsigned int x;
+
+            for (x = 0; x < 8; ++x)
+                destPtr[x] = (srcPtr[x]+srcPtr2[x]+1) >> 1;
+        }
+    } else {
+        unsigned int y;
+        for (y = 0; y < 8; ++y) {
+            int16 * const destPtr = (*motionBlockP)[y];
+            uint8 * const srcPtr  = &(prev[fy+y][fx]);
+            unsigned int x;
+
+            for (x = 0; x < 8; ++x)
+                destPtr[x] = srcPtr[x];
+        }
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ *  compute the motion-compensated luminance block
+ *
+ * RETURNS: motionBlock
+ *
+ * SIDE EFFECTS:    none
+ *
+ * PRECONDITIONS:   motion vector MUST be valid
+ *
+ * NOTE:  see ComputeMotionBlock
+ *
+ *===========================================================================*/
+void
+ComputeMotionLumBlock(MpegFrame * const prevFrame,
+                      int         const by,
+                      int         const bx,
+                      vector      const m,
+                      LumBlock *  const motionBlockP) {
+
+    unsigned int y;
+    uint8 ** prev;
+    int fy, fx;
+
+    computePrevFyFx(prevFrame, by, bx, m, &prev, &fy, &fx);
+
+    for (y = 0; y < 16; ++y) {
+        uint8 * const across  = &(prev[fy+y][fx]);
+        int32 * const macross = motionBlockP->l[y];
+        unsigned int x;
+
+        for (x = 0; x < 16; ++x)
+            macross[x] = across[x];
+    }
+
+    /* this is what's really happening, in slow motion:
+     *
+     *  for (y = 0; y < 16; ++y, ++py)
+     *      for (x = 0; x < 16; ++x, ++px)
+     *          motionBlock[y][x] = prev[fy+y][fx+x];
+     *
+     */
+}
+
+
+/*=======================*
+ * BASIC ERROR FUNCTIONS *
+ *=======================*/
+
+
+/*===========================================================================*
+ *
+ *  return the MAD of two luminance blocks
+ *
+ * RETURNS: the MAD, if less than bestSoFar, or some number bigger if not
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+int32
+LumBlockMAD(const LumBlock * const currentBlockP,
+            const LumBlock * const motionBlockP,
+            int32            const bestSoFar) {
+
+    int32 diff;    /* max value of diff is 255*256 = 65280 */
+    unsigned int y;
+
+    diff = 0;  /* initial value */
+
+    for (y = 0; y < 16; ++y) {
+        const int32 * const currentRow = currentBlockP->l[y];
+        const int32 * const motionRow  = motionBlockP->l[y];
+        unsigned int x;
+        for (x = 0; x < 16; ++x)
+            diff += ABS(currentRow[x] - motionRow[x]);
+
+        if (diff > bestSoFar)
+            /* We already know the MAD won't be less than bestSoFar;
+               Caller doesn't care by how much we missed, so just return
+               this.
+            */
+            return diff;
+    }
+    /* Return the actual MAD */
+    return diff;
+}
+
+
+
+/*===========================================================================*
+ *
+ *  return the MAD of the currentBlock and the motion-compensated block
+ *      (without TUNEing)
+ *
+ *  (by, bx) is the location of the block in the frame 
+ *  (block number coordinates).  'm' is the motion of the block in pixels.
+ *  The moved block must be wholly within the frame.
+ *
+ * RETURNS: the MAD, if less than bestSoFar, or
+ *      some number bigger if not
+ *
+ * SIDE EFFECTS:    none
+ *
+ * PRECONDITIONS:  motion vector MUST be valid
+ *
+ * NOTES:  this is the procedure that is called the most, and should therefore
+ *         be the most optimized!!!
+ *
+ *===========================================================================*/
+int32
+LumMotionError(const LumBlock * const currentBlockP,
+               MpegFrame *      const prevFrame,
+               int              const by,
+               int              const bx,
+               vector           const m,
+               int32            const bestSoFar) {
+
+    int32 adiff;
+    int32 diff;    /* max value of diff is 255*256 = 65280 */
+    int y;
+    uint8 **prev;
+    int fy, fx;
+
+    computePrevFyFx(prevFrame, by, bx, m, &prev, &fy, &fx);
+
+    assert(fy >= 0); assert(fx >= 0);
+
+    adiff = 0;  /* initial value */
+    diff = 0; /* initial value */
+
+    switch (SearchCompareMode) {
+    case DEFAULT_SEARCH: /* Default. */
+        for (y = 0; y < 16; ++y) {
+            const int32 * const cacross = currentBlockP->l[y];
+            uint8 *       const across  = &prev[fy+y][fx];
+            unsigned int x;
+            
+            for (x = 0; x < 16; ++x) {
+                int32 const localDiff = across[x]-cacross[x];
+                diff += ABS(localDiff);
+            }
+            if (diff > bestSoFar)
+                return diff;
+        }
+        break;
+      
+    case LOCAL_DCT: {
+        Block     dctdiff[4], dctquant[4];
+        FlatBlock quant;
+        int x, i, tmp;
+        int distortion=0, datarate=0;
+        int pq = GetPQScale();
+      
+        for (y = 0;  y < 16;  ++y) {
+            const int32 * const cacross = currentBlockP->l[y];
+            uint8 * const across = &(prev[fy+y][fx]);
+            for (x = 0;  x < 16;  ++x) {
+                dctdiff[(x>7)+2*(y>7)][y%8][x%8] = cacross[x]-across[x];
+            }}
+
+        /* Calculate rate */
+        for (i = 0;  i < 4;  ++i) {
+            mp_fwd_dct_block2(dctdiff[i], dctdiff[i]);
+            if (Mpost_QuantZigBlock(dctdiff[i], quant, pq, FALSE) ==
+                MPOST_ZERO) {
+                /* no sense in continuing */
+                memset((char *)dctquant[i], 0, sizeof(Block));
+            } else {
+                Mpost_UnQuantZigBlock(quant, dctquant[i], pq, FALSE);
+                mpeg_jrevdct((int16 *)dctquant[i]);
+                datarate += CalcRLEHuffLength(quant);
+            }
+        }
+      
+        /* Calculate distortion */
+        for (y = 0;  y < 16;  ++y) {
+            const int32 * const cacross = currentBlockP->l[y];
+            uint8 * const across = &(prev[fy+y][fx]);
+            for (x = 0;  x < 16;  ++x) {
+                tmp = across[x] - cacross[x] +
+                    dctquant[(x>7)+2*(y>7)][y%8][x%8];
+                distortion += tmp*tmp;
+            }}
+        distortion /= 256;
+        distortion *= LocalDCTDistortScale;
+        datarate *= LocalDCTRateScale;
+        diff = (int) sqrt(distortion*distortion + datarate*datarate);
+        break;
+    }
+
+    case NO_DC_SEARCH: {
+        extern int32 niqtable[];
+        int pq = niqtable[0]*GetPQScale();
+        unsigned int y;
+        
+        for (y = 0; y < 16; ++y) {
+            const int32 * const cacross = currentBlockP->l[y];
+            uint8 * const across = &(prev[fy+y][fx]);
+            unsigned int x;
+            
+            for (x = 0; x < 16; ++x) {
+                int32 const localDiff = across[x]-cacross[x];
+                diff += localDiff;
+                adiff += ABS(localDiff);
+            }
+        }
+
+        diff /= 64*pq;  /* diff is now the DC difference (with QSCALE 1) */
+        adiff -= 64*pq*ABS(diff);
+        diff = adiff;
+    }
+    break;
+
+    case DO_Mean_Squared_Distortion:
+        for (y = 0; y < 16; ++y) {
+            const int32 * const cacross = currentBlockP->l[y];
+            uint8 * const across = &(prev[fy+y][fx]);
+            unsigned int x;
+
+            for (x = 0; x < 16; ++x) {
+                int32 const localDiff = across[x] - cacross[x];
+                diff += localDiff * localDiff;
+            }
+            if (diff > bestSoFar)
+                return diff;
+        }
+        break;
+    } /* End of Switch */
+
+    return diff;
+}
+
+
+
+/*===========================================================================*
+ *
+ *  return the MAD of the currentBlock and the average of the blockSoFar
+ *  and the motion-compensated block (this is used for B-frame searches)
+ *
+ * RETURNS: the MAD, if less than bestSoFar, or
+ *      some number bigger if not
+ *
+ * SIDE EFFECTS:    none
+ *
+ * PRECONDITIONS:  motion vector MUST be valid
+ *
+ *===========================================================================*/
+int32
+LumAddMotionError(const LumBlock * const currentBlockP,
+                  const LumBlock * const blockSoFarP,
+                  MpegFrame *      const prevFrame,
+                  int              const by,
+                  int              const bx,
+                  vector           const m,
+                  int32            const bestSoFar) {
+
+    int32 diff;    /* max value of diff is 255*256 = 65280 */
+    int y;
+    uint8 **prev;
+    int fy, fx;
+
+    computePrevFyFx(prevFrame, by, bx, m, &prev, &fy, &fx);
+
+    diff = 0; /* initial value */
+
+    /* do we add 1 before dividing by two?  Yes -- see MPEG-1 doc page 46 */
+
+    for (y = 0; y < 16; ++y) {
+        unsigned int x;
+        const uint8 * const across  = &prev[fy+y][fx];
+        const int32 * const bacross = blockSoFarP->l[y];
+        const int32 * const cacross = currentBlockP->l[y];
+
+        for (x = 0; x < 16; ++x) {
+            int32 const localDiff =
+                ((across[x] + bacross[x] + 1) / 2) - cacross[x];
+            diff += ABS(localDiff);
+        }
+
+        if (diff > bestSoFar)
+            return diff;
+    }
+    
+    /* This is what's happening:
+     *
+     *  ComputeMotionLumBlock(prevFrame, by, bx, my, mx, lumMotionBlock);
+     *
+     *  for (y = 0; y < 16; ++y)
+     *      for (x = 0; x < 16; ++x) {
+     *          localDiff = currentBlock[y][x] - lumMotionBlock[y][x];
+     *          diff += ABS(localDiff);
+     *      }
+     */
+
+    return diff;
+}
+
+
+
+/*===========================================================================*
+ *
+ *  adds the motion-compensated block to the given block
+ *
+ * RETURNS: block modified
+ *
+ * SIDE EFFECTS:    none
+ *
+ * PRECONDITIONS:  motion vector MUST be valid
+ *
+ *===========================================================================*/
+void
+AddMotionBlock(Block          block,
+               uint8 ** const prev,
+               int      const by,
+               int      const bx,
+               vector   const m) {
+
+    int     fy, fx;
+    boolean xHalf, yHalf;
+
+    xHalf = (ABS(m.x) % 2 == 1);
+    yHalf = (ABS(m.y) % 2 == 1);
+
+    MotionToFrameCoord(by, bx, (m.y/2), (m.x/2), &fy, &fx);
+
+    if (xHalf && yHalf) {
+        unsigned int y;
+        /* really should be fy+y-1 and fy+y so do (fy-1)+y = fy+y-1 and
+           (fy-1)+y+1 = fy+y
+        */
+        if (m.y < 0)
+            --fy;
+        if (m.x < 0)
+            --fx;
+
+        for (y = 0; y < 8; ++y) {
+            unsigned int x;
+            for (x = 0; x < 8; ++x)
+                block[y][x] += (prev[fy+y][fx+x]+prev[fy+y][fx+x+1]+
+                                prev[fy+y+1][fx+x]+prev[fy+y+1][fx+x+1]+2)>>2;
+        }
+    } else if (xHalf) {
+        unsigned int y;
+        if (m.x < 0)
+            --fx;
+
+        for (y = 0; y < 8; ++y) {
+            unsigned int x;
+            for (x = 0; x < 8; ++x) {
+                block[y][x] += (prev[fy+y][fx+x]+prev[fy+y][fx+x+1]+1)>>1;
+            }
+        }
+    } else if ( yHalf ) {
+        unsigned int y;
+        if (m.y < 0)
+            --fy;
+
+        for (y = 0; y < 8; ++y) {
+            unsigned int x;
+            for (x = 0; x < 8; ++x) {
+                block[y][x] += (prev[fy+y][fx+x]+prev[fy+y+1][fx+x]+1)>>1;
+            }
+        }
+    } else {
+        unsigned int y;
+        for (y = 0; y < 8; ++y) {
+            unsigned int x;
+            for (x = 0; x < 8; ++x) {
+                block[y][x] += (int16)prev[fy+y][fx+x];
+            }
+        }
+    }
+}
+
+
+/*===========================================================================*
+ *
+ *  adds the motion-compensated B-frame block to the given block
+ *
+ * RETURNS: block modified
+ *
+ * SIDE EFFECTS:    none
+ *
+ * PRECONDITIONS:  motion vectors MUST be valid
+ *
+ *===========================================================================*/
+void
+AddBMotionBlock(Block          block,
+                uint8 ** const prev,
+                uint8 ** const next,
+                int      const by,
+                int      const bx,
+                int      const mode,
+                motion   const motion) {
+
+    unsigned int y;
+    Block prevBlock, nextBlock;
+
+    switch (mode) {
+    case MOTION_FORWARD:
+        AddMotionBlock(block, prev, by, bx, motion.fwd);
+        break;
+    case MOTION_BACKWARD:
+        AddMotionBlock(block, next, by, bx, motion.bwd);
+        break;
+    default:
+        ComputeMotionBlock(prev, by, bx, motion.fwd, &prevBlock);
+        ComputeMotionBlock(next, by, bx, motion.bwd, &nextBlock);
+    }
+    for (y = 0; y < 8; ++y) {
+        unsigned int x;
+        for (x = 0; x < 8; ++x) {
+            block[y][x] += (prevBlock[y][x] + nextBlock[y][x] + 1) / 2;
+        }
+    }
+}
+
+
+/*===========================================================================*
+ *
+ *  copies the given block into the appropriate data area
+ *
+ * RETURNS: data modified
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+BlockToData(uint8 ** const data,
+            Block          block,
+            int      const by,
+            int      const bx) {
+
+    int x, y;
+    int fy, fx;
+    int16    blockItem;
+
+    BLOCK_TO_FRAME_COORD(by, bx, fy, fx);
+
+    for ( y = 0; y < 8; y++ ) {
+    for ( x = 0; x < 8; x++ ) {
+        blockItem = block[y][x];
+        data[fy+y][fx+x] = TRUNCATE_UINT8(blockItem);
+    }
+    }
+}
+
+
+/*===========================================================================*
+ *
+ *  copies data into appropriate blocks
+ *
+ * RETURNS: mf modified
+ *
+ * SIDE EFFECTS:    none
+ *
+ * NOTES:  probably shouldn't be in this file
+ *
+ *===========================================================================*/
+void
+BlockifyFrame(MpegFrame * const frameP) {
+
+    int dctx, dcty;
+    int x, y;
+    int bx, by;
+    int fy, fx;
+    int16 *destPtr;
+    uint8 *srcPtr;
+    int16 *destPtr2;
+    uint8 *srcPtr2;
+    Block *blockPtr;
+    Block *blockPtr2;
+
+    dctx = Fsize_x / DCTSIZE;
+    dcty = Fsize_y / DCTSIZE;
+
+    /*
+     * copy y data into y_blocks
+     */
+    for (by = 0; by < dcty; by++) {
+    fy = by*DCTSIZE;
+    for (bx = 0; bx < dctx; bx++) {
+        fx = bx*DCTSIZE;
+        blockPtr = (Block *) &(frameP->y_blocks[by][bx][0][0]);
+        for (y = 0; y < DCTSIZE; y++) {
+        destPtr = &((*blockPtr)[y][0]);
+        srcPtr = &(frameP->orig_y[fy+y][fx]);
+        for (x = 0; x < DCTSIZE; x++) {
+            destPtr[x] = srcPtr[x];
+        }
+        }
+    }
+    }
+
+    /*
+     * copy cr/cb data into cr/cb_blocks
+     */
+    for (by = 0; by < (dcty >> 1); by++) {
+    fy = by*DCTSIZE;
+    for (bx = 0; bx < (dctx >> 1); bx++) {
+        fx = bx*DCTSIZE;
+        blockPtr = (Block *) &(frameP->cr_blocks[by][bx][0][0]);
+        blockPtr2 = (Block *) &(frameP->cb_blocks[by][bx][0][0]);
+        for (y = 0; y < DCTSIZE; y++) {
+        destPtr = &((*blockPtr)[y][0]);
+        srcPtr = &(frameP->orig_cr[fy+y][fx]);
+        destPtr2 = &((*blockPtr2)[y][0]);
+        srcPtr2 = &(frameP->orig_cb[fy+y][fx]);
+        for (x = 0; x < DCTSIZE; x++) {
+            destPtr[x] = srcPtr[x];
+            destPtr2[x] = srcPtr2[x];
+        }
+        }
+    }
+    }
+}
+
+
+/*===========================================================================*
+ *                                       *
+ * UNUSED PROCEDURES                                 *
+ *                                       *
+ *  The following procedures are all unused by the encoder           *
+ *                                       *
+ *  They are listed here for your convenience.  You might want to use    *
+ *  them if you experiment with different search techniques          *
+ *                                       *
+ *===========================================================================*/
+
+#ifdef UNUSED_PROCEDURES
+
+/* this procedure calculates the subsampled motion block (obviously)
+ *
+ * for speed, this procedure is probably not called anywhere (it is
+ * incorporated directly into LumDiffA, LumDiffB, etc.
+ *
+ * but leave it here anyway for clarity
+ *
+ * (startY, startX) = (0,0) for A....(0,1) for B...(1,0) for C...(1,1) for D
+ *  
+ */
+void
+ComputeSubSampledMotionLumBlock(MpegFrame * const prevFrame,
+                                int         const by,
+                                int         const bx,
+                                int         const my,
+                                int         const mx,
+                                LumBlock    const motionBlock,
+                                int         const startY,
+                                int         const startX) {
+
+    uint8 *across;
+    int32 *macross;
+    int32 *lastx;
+    int y;
+    uint8 **prev;
+    int    fy, fx;
+    boolean xHalf, yHalf;
+
+    xHalf = (ABS(mx) % 2 == 1);
+    yHalf = (ABS(my) % 2 == 1);
+
+    MotionToFrameCoord(by, bx, my/2, mx/2, &fy, &fx);
+
+    if ( xHalf ) {
+    if ( mx < 0 ) {
+        fx--;
+    }
+
+    if ( yHalf ) {
+        if ( my < 0 ) {
+        fy--;
+        }
+        
+        prev = prevFrame->halfBoth;
+    } else {
+        prev = prevFrame->halfX;
+    }
+    } else if ( yHalf ) {
+    if ( my < 0 ) {
+        fy--;
+    }
+
+    prev = prevFrame->halfY;
+    } else {
+    prev = prevFrame->ref_y;
+    }
+
+    for ( y = startY; y < 16; y += 2 ) {
+    across = &(prev[fy+y][fx+startX]);
+    macross = &(motionBlock[y][startX]);
+    lastx = &(motionBlock[y][16]);
+    while ( macross < lastx ) {
+        (*macross) = (*across);
+        across += 2;
+        macross += 2;
+    }
+    }
+
+    /* this is what's really going on in slow motion:
+     *
+     *  for ( y = startY; y < 16; y += 2 )
+     *      for ( x = startX; x < 16; x += 2 )
+     *      motionBlock[y][x] = prev[fy+y][fx+x];
+     *
+     */
+}
+
+
+/*===========================================================================*
+ *
+ *  return the MAD of the currentBlock and the motion-compensated block,
+ *  subsampled 4:1 with given starting coordinates (startY, startX)
+ *
+ * RETURNS: the MAD
+ *
+ * SIDE EFFECTS:    none
+ *
+ * PRECONDITIONS:  motion vector MUST be valid
+ *
+ * NOTES:  this procedure is never called.  Instead, see subsample.c.  This
+ *         procedure is provided only for possible use in extensions
+ *
+ *===========================================================================*/
+int32
+LumMotionErrorSubSampled(LumBlock    const currentBlock,
+                         MpegFrame * const prevFrame,
+                         int         const by,
+                         int         const bx,
+                         int         const my,
+                         int         const mx,
+                         int         const startY,
+                         int         const startX) {
+
+    int32 diff;     /* max value of diff is 255*256 = 65280 */
+    int32 localDiff;
+    int32 *cacross;
+    uint8 *macross;
+    int32 *lastx;
+    int y;
+    uint8 **prev;
+    int    fy, fx;
+    boolean xHalf, yHalf;
+
+    xHalf = (ABS(mx) % 2 == 1);
+    yHalf = (ABS(my) % 2 == 1);
+
+    motionToFrameCoord(by, bx, my/2, mx/2, &fy, &fx);
+
+    if ( xHalf ) {
+    if ( mx < 0 ) {
+        fx--;
+    }
+
+    if ( yHalf ) {
+        if ( my < 0 ) {
+        fy--;
+        }
+        
+        prev = prevFrame->halfBoth;
+    } else {
+        prev = prevFrame->halfX;
+    }
+    } else if ( yHalf ) {
+    if ( my < 0 ) {
+        fy--;
+    }
+
+    prev = prevFrame->halfY;
+    } else {
+    prev = prevFrame->ref_y;
+    }
+
+    diff = 0; /* initial value */
+
+    for ( y = startY; y < 16; y += 2 ) {
+    macross = &(prev[fy+y][fx+startX]);
+    cacross = &(currentBlock[y][startX]);
+    lastx = &(currentBlock[y][16]);
+    while ( cacross < lastx ) {
+        localDiff = (*cacross)-(*macross);
+        diff += ABS(localDiff);
+        macross += 2;
+        cacross += 2;
+    }
+    }
+
+    /* this is what's really happening:
+     *
+     *  ComputeSubSampledMotionLumBlock(prevFrame, by, bx, my, mx,
+     *                  lumMotionBlock, startY, startX);
+     *
+     *  for ( y = startY; y < 16; y += 2 )
+     *      for ( x = startX; x < 16; x += 2 )
+     *      {
+     *          localDiff = currentBlock[y][x] - lumMotionBlock[y][x];
+     *      diff += ABS(localDiff);
+     *      }
+     *
+     */
+
+    return (int32)diff;
+}
+
+
+#endif /* UNUSED_PROCEDURES */
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
diff --git a/converter/ppm/ppmtompeg/bsearch.c b/converter/ppm/ppmtompeg/bsearch.c
new file mode 100644
index 00000000..142987f5
--- /dev/null
+++ b/converter/ppm/ppmtompeg/bsearch.c
@@ -0,0 +1,1088 @@
+/*===========================================================================*
+ * bsearch.c
+ *
+ *  Procedures concerned with the B-frame motion search
+ *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/picasso/project/mpeg/mpeg_dist/mpeg_encode/RCS/bsearch.c,v 1.10 1995/08/07 21:49:01 smoot Exp $
+ *  $Log: bsearch.c,v $
+ *  Revision 1.10  1995/08/07 21:49:01  smoot
+ *  fixed bug in initial-B-frame searches
+ *
+ *  Revision 1.9  1995/06/26 21:36:07  smoot
+ *  added new ordering constraints
+ *  (B frames which are backward P's at the start of a sequence)
+ *
+ *  Revision 1.8  1995/03/27 19:17:43  smoot
+ *  killed useless type error messge (int32 defiend as int)
+ *
+ * Revision 1.7  1995/01/19  23:07:20  eyhung
+ * Changed copyrights
+ *
+ * Revision 1.6  1994/12/07  00:40:36  smoot
+ * Added seperate P and B search ranges
+ *
+ * Revision 1.5  1994/03/15  00:27:11  keving
+ * nothing
+ *
+ * Revision 1.4  1993/12/22  19:19:01  keving
+ * nothing
+ *
+ * Revision 1.3  1993/07/22  22:23:43  keving
+ * nothing
+ *
+ * Revision 1.2  1993/06/30  20:06:09  keving
+ * nothing
+ *
+ * Revision 1.1  1993/06/03  21:08:08  keving
+ * nothing
+ *
+ * Revision 1.1  1993/03/02  18:27:05  keving
+ * nothing
+ *
+ */
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "pm_c_util.h"
+#include "pm.h"
+#include "all.h"
+#include "mtypes.h"
+#include "frames.h"
+#include "motion_search.h"
+#include "fsize.h"
+
+
+/*==================*
+ * STATIC VARIABLES *
+ *==================*/
+
+static int  bsearchAlg;
+
+
+/*===========================*
+ * INITIALIZATION PROCEDURES *
+ *===========================*/
+
+
+/*===========================================================================*
+ *
+ * SetBSearchAlg
+ *
+ *  set the B-search algorithm
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    bsearchAlg modified
+ *
+ *===========================================================================*/
+void
+SetBSearchAlg(const char * const alg) {
+    if (strcmp(alg, "SIMPLE") == 0)
+        bsearchAlg = BSEARCH_SIMPLE;
+    else if (strcmp(alg, "CROSS2") == 0)
+        bsearchAlg = BSEARCH_CROSS2;
+    else if (strcmp(alg, "EXHAUSTIVE") == 0)
+        bsearchAlg = BSEARCH_EXHAUSTIVE;
+    else
+        pm_error("ERROR:  Impossible bsearch alg:  %s", alg);
+}
+
+
+/*===========================================================================*
+ *
+ * BSearchName
+ *
+ *  return the text of the B-search algorithm
+ *
+ * RETURNS: a pointer to the string
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+const char *
+BSearchName(void)
+{
+    const char *retval;
+
+    switch(bsearchAlg) {
+    case BSEARCH_SIMPLE:
+        retval = "SIMPLE";break;
+    case BSEARCH_CROSS2:
+        retval = "CROSS2";break;
+    case BSEARCH_EXHAUSTIVE:
+        retval = "EXHAUSTIVE";break;
+    default:
+        pm_error("ERROR:  Impossible BSEARCH ALG:  %d", psearchAlg);
+    }
+    return retval;
+}
+
+
+
+/*===========================================================================*
+ *
+ * FindBestMatchExhaust
+ *
+ *  tries to find matching motion vector
+ *  see FindBestMatch for generic description
+ *
+ * DESCRIPTION:  uses an exhaustive search
+ *
+ *===========================================================================*/
+static int32
+FindBestMatchExhaust(const LumBlock * const blockP,
+                     const LumBlock * const currentBlockP,
+                     MpegFrame *      const prev,
+                     int              const by,
+                     int              const bx,
+                     vector *         const motionP,
+                     int32            const bestSoFar,
+                     int              const searchRange) {
+
+    register int mx, my;
+    int32 bestDiff;
+    int   stepSize;
+    int   leftMY, leftMX;
+    int   rightMY, rightMX;
+    int   distance;
+    int   tempRightMY, tempRightMX;
+    boolean changed = FALSE;
+
+    stepSize = (pixelFullSearch ? 2 : 1);
+
+    COMPUTE_MOTION_BOUNDARY(by,bx,stepSize,leftMY,leftMX,rightMY,rightMX);
+
+    /* try old motion vector first */
+    if (VALID_MOTION(*motionP)) {
+        bestDiff = LumAddMotionError(currentBlockP, blockP, prev, by, bx,
+                                     *motionP, bestSoFar);
+
+        if (bestSoFar < bestDiff)
+            bestDiff = bestSoFar;
+    } else {
+        motionP->y = motionP->x = 0;
+        bestDiff = bestSoFar;
+    }
+
+    /* maybe should try spiral pattern centered around  prev motion vector? */
+
+    /* try a spiral pattern */    
+    for (distance = stepSize;
+         distance <= searchRange;
+         distance += stepSize) {
+        tempRightMY = MIN(distance, rightMY);
+        tempRightMX = MIN(distance, rightMX);
+
+        /* do top, bottom */
+        for (my = -distance; my < tempRightMY;
+             my += max(tempRightMY+distance-stepSize, stepSize)) {
+            if (my >= leftMY) {
+                for (mx = -distance; mx < tempRightMX; mx += stepSize) {
+                    if (mx >= leftMX) {
+                        int diff;
+                        vector m;
+                        m.y = my; m.x = mx;
+                        diff = LumAddMotionError(currentBlockP, blockP, prev,
+                                                 by, bx, m, bestDiff);
+                        
+                        if (diff < bestDiff) {
+                            *motionP = m;
+                            bestDiff = diff;
+                        }
+                    }
+                }
+            }
+        }
+
+        /* do left, right */
+        for (mx = -distance;
+             mx < tempRightMX;
+             mx += max(tempRightMX+distance-stepSize, stepSize)) {
+            if (mx >= leftMX) {
+                for (my = -distance+stepSize;
+                     my < tempRightMY-stepSize;
+                     my += stepSize) {
+                    if (my >= leftMY) {
+                        int diff;
+                        vector m;
+                        m.y = my; m.x = mx;
+                        diff = LumAddMotionError(currentBlockP, blockP, prev,
+                                                 by, bx,
+                                                 m, bestDiff);
+                        
+                        if (diff < bestDiff) {
+                            *motionP = m;
+                            bestDiff = diff;
+                            changed = TRUE;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    if (!changed)
+        ++bestDiff;
+
+    return bestDiff;
+}
+
+
+/*===========================================================================*
+ *
+ * FindBestMatchTwoLevel
+ *
+ *  tries to find matching motion vector
+ *  see FindBestMatch for generic description
+ *
+ * DESCRIPTION:  uses an exhaustive full-pixel search, then looks at
+ *       neighboring half-pixels
+ *
+ *===========================================================================*/
+static int32
+FindBestMatchTwoLevel(const LumBlock * const blockP,
+                      const LumBlock * const currentBlockP,
+                      MpegFrame *      const prev,
+                      int              const by,
+                      int              const bx,
+                      vector *         const motionP,
+                      int32            const bestSoFar,
+                      int              const searchRange) {
+
+    int mx, my;
+    int32 bestDiff;
+    int leftMY, leftMX;
+    int rightMY, rightMX;
+    int distance;
+    int tempRightMY, tempRightMX;
+    boolean changed = FALSE;
+    int yOffset, xOffset;
+
+    /* exhaustive full-pixel search first */
+
+    COMPUTE_MOTION_BOUNDARY(by,bx,2,leftMY,leftMX,rightMY,rightMX);
+
+    --rightMY;
+    --rightMX;
+
+    /* convert vector into full-pixel vector */
+    if (motionP->y > 0 ) {
+        if ((motionP->y % 2) == 1)
+            --motionP->y;
+    } else if ((-motionP->y % 2) == 1)
+        ++motionP->y;
+
+    if (motionP->x > 0 ) {
+        if ((motionP->x % 2) == 1 )
+            --motionP->x;
+    } else if ((-motionP->x % 2) == 1)
+        ++motionP->x;
+
+    /* try old motion vector first */
+    if (VALID_MOTION(*motionP)) {
+        bestDiff = LumAddMotionError(currentBlockP, blockP, prev, by, bx,
+                                     *motionP, bestSoFar);
+        
+        if (bestSoFar < bestDiff)
+            bestDiff = bestSoFar;
+    } else {
+        motionP->y = motionP->x = 0;
+        bestDiff = bestSoFar;
+    }
+
+    ++rightMY;
+    ++rightMX;
+
+    /* maybe should try spiral pattern centered around  prev motion vector? */
+
+    /* try a spiral pattern */    
+    for ( distance = 2; distance <= searchRange; distance += 2 ) {
+        tempRightMY = MIN(distance, rightMY);
+        tempRightMX = MIN(distance, rightMX);
+
+        /* do top, bottom */
+        for (my = -distance; my < tempRightMY;
+             my += max(tempRightMY+distance-2, 2)) {
+            if (my >= leftMY) {
+                for ( mx = -distance; mx < tempRightMX; mx += 2 ) {
+                    if (mx >= leftMX) {
+                        int diff;
+                        vector m;
+                        m.y = my; m.x = mx;
+                        diff = LumAddMotionError(currentBlockP, blockP, prev,
+                                                 by, bx, m, bestDiff);
+                        
+                        if (diff < bestDiff) {
+                            *motionP = m;
+                            bestDiff = diff;
+                        }
+                    }
+                }
+            }
+        }
+
+        /* do left, right */
+        for (mx = -distance;
+             mx < tempRightMX;
+             mx += max(tempRightMX+distance-2, 2)) {
+            if (mx >= leftMX) {
+                for (my = -distance+2; my < tempRightMY-2; my += 2) {
+                    if (my >= leftMY) {
+                        int diff;
+                        vector m;
+                        m.y = my; m.x = mx;
+                        diff = LumAddMotionError(currentBlockP, blockP, prev,
+                                                 by, bx, m, bestDiff);
+                        
+                        if (diff < bestDiff) {
+                            *motionP = m;
+                            bestDiff = diff;
+                            changed = TRUE;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /* now look at neighboring half-pixels */
+    my = motionP->y;
+    mx = motionP->x;
+
+    --rightMY;
+    --rightMX;
+
+    for (yOffset = -1; yOffset <= 1; ++yOffset) {
+        for (xOffset = -1; xOffset <= 1; ++xOffset) {
+            if ((yOffset != 0) || (xOffset != 0)) {
+                vector m;
+                m.y = my + yOffset;
+                m.x = mx + xOffset;
+                if (VALID_MOTION(m)) {
+                    int diff;
+                    diff = LumAddMotionError(currentBlockP, blockP,
+                                             prev, by, bx, m, bestDiff);
+                    if (diff < bestDiff) {
+                        *motionP = m;
+                        bestDiff = diff;
+                        changed = TRUE;
+                    }
+                }
+            }
+        }
+    }
+
+    if (!changed)
+        ++bestDiff;
+
+    return bestDiff;
+}
+
+
+
+static void
+trySpacing(int              const spacing,
+           vector           const center,
+           int              const bestDiffSoFar,
+           vector *         const newCenterP,
+           int32 *          const newBestDiffP,
+           int              const leftMY,
+           int              const leftMX,
+           int              const rightMY,
+           int              const rightMX,
+           const LumBlock * const currentBlockP,
+           const LumBlock * const blockP,
+           MpegFrame *      const prev,
+           int              const by,
+           int              const bx) {
+           
+    int tempRightMY, tempRightMX;
+    int my;
+    int bestDiff;
+    vector newCenter;
+
+    /* Initial values */
+    newCenter = center;
+    bestDiff   = bestDiffSoFar;
+
+    tempRightMY = MIN(rightMY, center.y + spacing + 1);
+    tempRightMX = MIN(rightMX, center.x + spacing + 1);
+    
+    for (my = center.y - spacing; my < tempRightMY; my += spacing) {
+        if (my >= leftMY) {
+            int mx;
+            for (mx = center.x - spacing; mx < tempRightMX; mx += spacing) {
+                if (mx >= leftMX) {
+                    int32 diff;
+                    vector m;
+                    m.y = my; m.x = mx;
+                    diff = LumAddMotionError(currentBlockP, blockP, prev,
+                                             by, bx, m, bestDiff);
+                    
+                    if (diff < bestDiff) {
+                        /* We have a new best */
+                        newCenter = m;
+                        bestDiff   = diff;
+                    }
+                }
+            }
+        }
+    }
+    *newCenterP  = newCenter;
+    *newBestDiffP = bestDiff;
+}
+
+
+
+static void
+chooseNewSpacing(int   const oldSpacing,
+                 int   const stepSize,
+                 int * const newSpacingP) {
+        
+    if (stepSize == 2) {  /* make sure spacing is even */
+        if (oldSpacing == 2)
+            *newSpacingP = 0;
+        else {
+            int const trialSpacing = (oldSpacing + 1) / 2;
+            if ((trialSpacing % 2) != 0)
+                *newSpacingP = trialSpacing + 1;
+            else
+                *newSpacingP = trialSpacing;
+        }
+    } else {
+        if (oldSpacing == 1)
+            *newSpacingP = 0;
+        else
+            *newSpacingP = (oldSpacing + 1) / 2;
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * FindBestMatchLogarithmic
+ *
+ *  tries to find matching motion vector
+ *  see FindBestMatch for generic description
+ *
+ * DESCRIPTION:  uses a logarithmic search
+ *
+ *===========================================================================*/
+static int32
+FindBestMatchLogarithmic(const LumBlock * const blockP,
+                         const LumBlock * const currentBlockP,
+                         MpegFrame *      const prev,
+                         int              const by,
+                         int              const bx,
+                         vector *         const motionP,
+                         int32            const bestSoFar,
+                         int              const searchRange) {
+
+    int const stepSize = (pixelFullSearch ? 2 : 1);
+
+    int32 diff, bestDiff;
+    int leftMY, leftMX;
+    int rightMY, rightMX;
+    int spacing;
+    vector center;
+
+    COMPUTE_MOTION_BOUNDARY(by, bx, stepSize,
+                            leftMY, leftMX, rightMY, rightMX);
+
+    bestDiff = 0x7fffffff;
+
+    /* grid spacing */
+    if (stepSize == 2) {  /* make sure spacing is even */
+        spacing = (searchRange + 1) / 2;
+        if ((spacing % 2) != 0)
+            ++spacing;
+    } else
+        spacing = (searchRange + 1) / 2;
+
+    /* Start at (0,0) */
+    center.y = center.x = 0;
+    
+    while (spacing >= stepSize) {
+        trySpacing(spacing, center, bestDiff,
+                   &center, &bestDiff,
+                   leftMY, leftMX, rightMY, rightMX,
+                   currentBlockP, blockP, prev, by, bx);
+
+        chooseNewSpacing(spacing, stepSize, &spacing);
+    }
+
+    /* check old motion -- see if it's better */
+    if ((motionP->y >= leftMY) && (motionP->y < rightMY) &&
+        (motionP->x >= leftMX) && (motionP->x < rightMX)) {
+        diff = LumAddMotionError(currentBlockP, blockP, prev, by, bx,
+                                 *motionP, bestDiff);
+    } else
+        diff = 0x7fffffff;
+
+    if (bestDiff < diff)
+        *motionP = center;
+    else
+        bestDiff = diff;
+
+    return bestDiff;
+}
+
+
+
+/*===========================================================================*
+ *
+ * FindBestMatchSubSample
+ *
+ *  tries to find matching motion vector
+ *  see FindBestMatch for generic description
+ *
+ * DESCRIPTION:  should use subsampling method, but too lazy to write all
+ *       the code for it (so instead just calls FindBestMatchExhaust)
+ *
+ *===========================================================================*/
+static int32
+FindBestMatchSubSample(const LumBlock * const blockP,
+                       const LumBlock * const currentBlockP,
+                       MpegFrame *      const prev,
+                       int              const by,
+                       int              const bx,
+                       vector *         const motionP,
+                       int32            const bestSoFar,
+                       int              const searchRange) {
+
+    /* too lazy to write the code for this... */
+    
+    return FindBestMatchExhaust(blockP, currentBlockP, prev,
+                                by, bx, motionP, bestSoFar,
+                                searchRange);
+}
+
+
+/*===========================================================================*
+ *
+ * FindBestMatch
+ *
+ *  given a motion-compensated block in one direction, tries to find
+ *  the best motion vector in the opposite direction to match it
+ *
+ * RETURNS: the best vector (*motionY, *motionX), and the corresponding
+ *      error is returned if it is better than bestSoFar.  If not,
+ *      then a number greater than bestSoFar is returned and
+ *      (*motionY, *motionX) has no meaning.
+ *
+ * SIDE EFFECTS:  none
+ *
+ *===========================================================================*/
+static int32
+FindBestMatch(const LumBlock * const blockP,
+              const LumBlock * const currentBlockP,
+              MpegFrame *      const prev,
+              int              const by,
+              int              const bx,
+              vector *         const motionP,
+              int32            const bestSoFar,
+              int              const searchRange) {
+
+    int32 result;
+
+    switch(psearchAlg) {
+    case PSEARCH_SUBSAMPLE:
+        result = FindBestMatchSubSample(
+            blockP, currentBlockP, prev, by, bx,
+            motionP, bestSoFar, searchRange);
+        break;
+    case PSEARCH_EXHAUSTIVE:
+        result = FindBestMatchExhaust(
+            blockP, currentBlockP, prev, by, bx,
+            motionP, bestSoFar, searchRange);
+        break;
+    case PSEARCH_LOGARITHMIC:
+        result = FindBestMatchLogarithmic(
+            blockP, currentBlockP, prev, by, bx,
+            motionP, bestSoFar, searchRange);
+        break;
+    case PSEARCH_TWOLEVEL:
+        result = FindBestMatchTwoLevel(
+            blockP, currentBlockP, prev, by, bx,
+            motionP, bestSoFar, searchRange);
+        break;
+    default:
+        pm_error("ERROR:  Impossible P-search alg %d", psearchAlg);
+    }
+    return result;
+}
+
+
+
+/*===========================================================================*
+ *
+ *  finds the best backward and forward motion vectors
+ *  if backNeeded == FALSE, then won't find best backward vector if it
+ *  is worse than the best forward vector
+ *
+ * *motionP is input as well as output.  We do not update it
+ * if it would make the error worse than the existing value.
+ *
+ * RETURNS: *motionP and associated errors *forwardErrP and *backErrP.
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static void
+BMotionSearchNoInterp(const LumBlock * const currentBlockP,
+                      MpegFrame *      const prev,
+                      MpegFrame *      const next,
+                      int              const by,
+                      int              const bx,
+                      motion *         const motionP,
+                      int32 *          const forwardErrP,
+                      int32 *          const backErrP,
+                      boolean          const backNeeded) {
+
+    /* CALL SEARCH PROCEDURE */
+    switch(psearchAlg) {
+    case PSEARCH_SUBSAMPLE:
+        *forwardErrP = PSubSampleSearch(currentBlockP, prev, by, bx, 
+                                        &motionP->fwd,searchRangeB);
+        *backErrP = PSubSampleSearch(currentBlockP, next, by, bx, 
+                                     &motionP->bwd, searchRangeB);
+        break;
+    case PSEARCH_EXHAUSTIVE:
+        *forwardErrP = PLocalSearch(currentBlockP, prev, by, bx,
+                                    &motionP->fwd,
+                                    0x7fffffff, searchRangeB);
+        if (backNeeded)
+            *backErrP = PLocalSearch(currentBlockP, next, by, bx,
+                                     &motionP->bwd,
+                                     0x7fffffff, searchRangeB);
+        else
+            *backErrP = PLocalSearch(currentBlockP, next, by, bx,
+                                     &motionP->bwd,
+                                     *forwardErrP, searchRangeB);
+        break;
+    case PSEARCH_LOGARITHMIC:
+        *forwardErrP = PLogarithmicSearch(currentBlockP, prev, by, bx, 
+                                          &motionP->fwd, searchRangeB);
+        *backErrP = PLogarithmicSearch(currentBlockP, next, by, bx, 
+                                       &motionP->bwd, searchRangeB);
+        break;
+    case PSEARCH_TWOLEVEL:
+        *forwardErrP =
+            PTwoLevelSearch(currentBlockP, prev, by, bx,
+                            &motionP->fwd, 0x7fffffff, searchRangeB);
+        if ( backNeeded ) {
+            *backErrP =
+                PTwoLevelSearch(currentBlockP, next, by, bx,
+                                &motionP->bwd, 0x7fffffff, searchRangeB);
+        } else {
+            *backErrP =
+                PTwoLevelSearch(currentBlockP, next, by, bx,
+                                &motionP->bwd, *forwardErrP, searchRangeB);
+        }
+        break;
+    default:
+        pm_error("ERROR:  Impossible PSEARCH ALG:  %d", psearchAlg);
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * BMotionSearchSimple
+ *
+ *  does a simple search for B-frame motion vectors
+ *  see BMotionSearch for generic description
+ *
+ * DESCRIPTION:
+ *  1)  find best backward and forward vectors
+ *  2)  compute interpolated error using those two vectors
+ *  3)  return the best of the three choices
+ *
+ * *fmyP,fmxP,bmyP,bmxP are inputs as well as outputs.  We do not update
+ * them if it would make the error worse than the existing values.  Otherwise,
+ * we update them to the vectors we find to be best.
+ * 
+ *===========================================================================*/
+static int
+BMotionSearchSimple(const LumBlock * const currentBlockP,
+                    MpegFrame *      const prev,
+                    MpegFrame *      const next,
+                    int              const by,
+                    int              const bx,
+                    motion *         const motionP,
+                    int              const oldMode) {
+
+    int retval;
+    int32 forwardErr, backErr, interpErr;
+    LumBlock interpBlock;
+    int32 bestSoFar;
+
+    /* STEP 1 */
+    BMotionSearchNoInterp(currentBlockP, prev, next, by, bx, motionP,
+                          &forwardErr, &backErr, TRUE);
+              
+    /* STEP 2 */
+
+    ComputeBMotionLumBlock(prev, next, by, bx, MOTION_INTERPOLATE,
+                           *motionP, &interpBlock);
+    bestSoFar = min(backErr, forwardErr);
+    interpErr =
+        LumBlockMAD(currentBlockP, &interpBlock, bestSoFar);
+
+    /* STEP 3 */
+
+    if (interpErr <= forwardErr) {
+        if (interpErr <= backErr)
+            retval = MOTION_INTERPOLATE;
+        else
+            retval = MOTION_BACKWARD;
+    } else if (forwardErr <= backErr)
+        retval = MOTION_FORWARD;
+    else
+        retval = MOTION_BACKWARD;
+
+    return retval;
+}
+
+
+/*===========================================================================*
+ *
+ * BMotionSearchCross2
+ *
+ *  does a cross-2 search for B-frame motion vectors
+ *  see BMotionSearch for generic description
+ *
+ * DESCRIPTION:
+ *  1)  find best backward and forward vectors
+ *  2)  find best matching interpolating vectors
+ *  3)  return the best of the 4 choices
+ *
+ * *fmyP,fmxP,bmyP,bmxP are inputs as well as outputs.  We do not update
+ * them if it would make the error worse than the existing values.  Otherwise,
+ * we update them to the vectors we find to be best.
+ *===========================================================================*/
+static int
+BMotionSearchCross2(const LumBlock * const currentBlockP,
+                    MpegFrame *      const prev,
+                    MpegFrame *      const next,
+                    int              const by,
+                    int              const bx,
+                    motion *         const motionP,
+                    int              const oldMode) {
+    
+    int retval;
+    LumBlock forwardBlock, backBlock;
+    int32   forwardErr, backErr;
+    motion newMotion;
+    int32   interpErrF, interpErrB, interpErr;
+    int32   bestErr;
+
+    /* STEP 1 */
+
+    BMotionSearchNoInterp(currentBlockP, prev, next, by, bx, motionP,
+                          &forwardErr, &backErr, TRUE);
+
+    bestErr = min(forwardErr, backErr);
+
+    {
+        /* STEP 2 */
+        
+        struct motion motion;
+        motion.fwd = motionP->fwd;
+        motion.bwd.y = motion.bwd.x = 0;
+        ComputeBMotionLumBlock(prev, next, by, bx, MOTION_FORWARD, motion,
+                               &forwardBlock);
+        
+        motion.fwd.y = motion.fwd.x = 0;
+        motion.bwd = motionP->bwd;
+        ComputeBMotionLumBlock(prev, next, by, bx, MOTION_BACKWARD, motion,
+                               &backBlock);
+    }
+    /* try a cross-search; total of 4 local searches */    
+    newMotion = *motionP;
+
+    interpErrF = FindBestMatch(&forwardBlock, currentBlockP,
+                               next, by, bx, &newMotion.bwd,
+                               bestErr, searchRangeB);
+    bestErr = min(bestErr, interpErr);
+    interpErrB = FindBestMatch(&backBlock, currentBlockP,
+                               prev, by, bx, &newMotion.fwd,
+                               bestErr, searchRangeB);
+
+    /* STEP 3 */
+
+    if (interpErrF <= interpErrB) {
+        newMotion.fwd = motionP->fwd;
+        interpErr = interpErrF;
+    } else {
+        newMotion.bwd = motionP->bwd;
+        interpErr = interpErrB;
+    }
+
+    if (interpErr <= forwardErr) {
+        if (interpErr <= backErr) {
+            *motionP = newMotion;
+            retval = MOTION_INTERPOLATE;
+        } else
+            retval = MOTION_BACKWARD;
+    } else if (forwardErr <= backErr)
+        retval = MOTION_FORWARD;
+    else
+        retval = MOTION_BACKWARD;
+
+    return retval;
+}
+
+
+/*===========================================================================*
+ *
+ * BMotionSearchExhaust
+ *
+ *  does an exhaustive search for B-frame motion vectors
+ *  see BMotionSearch for generic description
+ *
+ * DESCRIPTION:
+ *  1)  find best backward and forward vectors
+ *  2)  use exhaustive search to find best interpolating vectors
+ *  3)  return the best of the 3 choices
+ *
+ * *fmyP,fmxP,bmyP,bmxP are inputs as well as outputs.  We do not update
+ * them if it would make the error worse than the existing values.  Otherwise,
+ * we update them to the vectors we find to be best.
+ *===========================================================================*/
+static int
+BMotionSearchExhaust(const LumBlock * const currentBlockP,
+                     MpegFrame *      const prev,
+                     MpegFrame *      const next,
+                     int              const by,
+                     int              const bx,
+                     motion *         const motionP,
+                     int              const oldMode) {
+
+    int mx, my;
+    int32 bestDiff;
+    int     stepSize;
+    LumBlock    forwardBlock;
+    int32   forwardErr, backErr;
+    int     leftMY, leftMX;
+    int     rightMY, rightMX;
+    boolean result;
+
+    /* STEP 1 */
+
+    BMotionSearchNoInterp(currentBlockP, prev, next, by, bx, motionP,
+                          &forwardErr, &backErr, FALSE);
+
+    if (forwardErr <= backErr) {
+        bestDiff = forwardErr;
+        result = MOTION_FORWARD;
+    } else {
+        bestDiff = backErr;
+        result = MOTION_BACKWARD;
+    }
+
+    /* STEP 2 */
+
+    stepSize = (pixelFullSearch ? 2 : 1);
+
+    COMPUTE_MOTION_BOUNDARY(by,bx,stepSize,leftMY,leftMX,rightMY,rightMX);
+
+    if (searchRangeB < rightMY)
+        rightMY = searchRangeB;
+    if (searchRangeB < rightMX)
+        rightMX = searchRangeB;
+
+    for (my = -searchRangeB; my < rightMY; my += stepSize) {
+        if (my >= leftMY) {
+            for (mx = -searchRangeB; mx < rightMX; mx += stepSize) {
+                if (mx >= leftMX) {
+                    struct motion motion;
+                    vector newMotion;
+                    int32 diff;
+                    motion.fwd.y = my; motion.fwd.x = mx;
+                    motion.bwd.y = 0;  motion.bwd.x = 0;
+                    ComputeBMotionLumBlock(prev, next, by, bx, MOTION_FORWARD,
+                                           motion, &forwardBlock);
+
+                    newMotion = motion.fwd;
+                    
+                    diff = FindBestMatch(&forwardBlock,
+                                         currentBlockP, next, by, bx,
+                                         &newMotion, bestDiff, searchRangeB);
+                    
+                    if (diff < bestDiff) {
+                        motionP->fwd = motion.fwd;
+                        motionP->bwd = newMotion;
+                        bestDiff = diff;
+                        result = MOTION_INTERPOLATE;
+                    }
+                }
+            }
+        }
+    }
+    return result;
+}
+
+
+
+/*===========================================================================*
+ *
+ *  search for the best B-frame motion vectors
+ *
+ * RETURNS: MOTION_FORWARD      forward motion should be used
+ *      MOTION_BACKWARD     backward motion should be used
+ *      MOTION_INTERPOLATE  both should be used and interpolated
+ *
+ * OUTPUTS: *motionP  =   TWICE the forward motion vectors
+ *
+ * SIDE EFFECTS:    none
+ *
+ * PRECONDITIONS:   The relevant block in 'current' is valid (it has not
+ *          been dct'd).  Thus, the data in 'current' can be
+ *          accesed through y_blocks, cr_blocks, and cb_blocks.
+ *          This is not the case for the blocks in 'prev' and
+ *          'next.'  Therefore, references into 'prev' and 'next'
+ *          should be done
+ *          through the struct items ref_y, ref_cr, ref_cb
+ *
+ * POSTCONDITIONS:  current, prev, next should be unchanged.
+ *          Some computation could be saved by requiring
+ *          the dct'd difference to be put into current's block
+ *          elements here, depending on the search technique.
+ *          However, it was decided that it mucks up the code
+ *          organization a little, and the saving in computation
+ *          would be relatively little (if any).
+ *
+ * NOTES:   the search procedure MAY return (0,0) motion vectors
+ *
+ *===========================================================================*/
+int
+BMotionSearch(const LumBlock * const currentBlockP,
+              MpegFrame *      const prev,
+              MpegFrame *      const next,
+              int              const by,
+              int              const bx,
+              motion *         const motionP,
+              int              const oldMode) {
+
+    int retval;
+
+    /* If we are an initial B frame, no possibility of forward motion */
+    if (prev == (MpegFrame *) NULL) {
+        PMotionSearch(currentBlockP, next, by, bx, &motionP->bwd);
+        return MOTION_BACKWARD;
+    }
+  
+    /* otherwise simply call the appropriate algorithm, based on user
+       preference
+    */
+
+    switch(bsearchAlg) {
+    case BSEARCH_SIMPLE:
+        retval = BMotionSearchSimple(currentBlockP, prev, next, by, bx,
+                                     motionP, oldMode);
+        break;
+    case BSEARCH_CROSS2:
+        retval = BMotionSearchCross2(currentBlockP, prev, next, by, bx,
+                                     motionP, oldMode);
+        break;
+    case BSEARCH_EXHAUSTIVE:
+        retval = BMotionSearchExhaust(currentBlockP, prev, next, by, bx,
+                                      motionP, oldMode);
+        break;
+    default:
+        pm_error("Impossible B-frame motion search algorithm:  %d",
+                 bsearchAlg);
+    }
+    return retval;
+}
+
+
+/*===========================================================================*
+ *                                       *
+ * UNUSED PROCEDURES                                 *
+ *                                       *
+ *  The following procedures are all unused by the encoder           *
+ *                                       *
+ *  They are listed here for your convenience.  You might want to use    *
+ *  them if you experiment with different search techniques          *
+ *                                       *
+ *===========================================================================*/
+
+#ifdef UNUSED_PROCEDURES
+
+/*===========================================================================*
+ *
+ * ValidBMotion
+ *
+ *  decides if the given B-frame motion is valid
+ *
+ * RETURNS: TRUE if the motion is valid, FALSE otherwise
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+boolean
+ValidBMotion(by, bx, mode, fmy, fmx, bmy, bmx)
+    int by;
+    int bx;
+    int mode;
+    int fmy;
+    int fmx;
+    int bmy;
+    int bmx;
+{
+    if ( mode != MOTION_BACKWARD ) {
+    /* check forward motion for bounds */
+    if ( (by*DCTSIZE+(fmy-1)/2 < 0) || ((by+2)*DCTSIZE+(fmy+1)/2-1 >= Fsize_y) ) {
+        return FALSE;
+    }
+    if ( (bx*DCTSIZE+(fmx-1)/2 < 0) || ((bx+2)*DCTSIZE+(fmx+1)/2-1 >= Fsize_x) ) {
+        return FALSE;
+    }
+    }
+
+    if ( mode != MOTION_FORWARD ) {
+    /* check backward motion for bounds */
+    if ( (by*DCTSIZE+(bmy-1)/2 < 0) || ((by+2)*DCTSIZE+(bmy+1)/2-1 >= Fsize_y) ) {
+        return FALSE;
+    }
+    if ( (bx*DCTSIZE+(bmx-1)/2 < 0) || ((bx+2)*DCTSIZE+(bmx+1)/2-1 >= Fsize_x) ) {
+        return FALSE;
+    }
+    }
+
+    return TRUE;
+}
+
+
+#endif /* UNUSED_PROCEDURES */
diff --git a/converter/ppm/ppmtompeg/combine.c b/converter/ppm/ppmtompeg/combine.c
new file mode 100644
index 00000000..341bb5dc
--- /dev/null
+++ b/converter/ppm/ppmtompeg/combine.c
@@ -0,0 +1,357 @@
+/*===========================================================================*
+ * combine.c
+ *
+ *  Procedures to combine frames or GOPS into an MPEG sequence
+ *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "all.h"
+#include <time.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "ppm.h"
+#include "nstring.h"
+
+#include "mtypes.h"
+#include "frames.h"
+#include "frametype.h"
+#include "motion_search.h"
+#include "mpeg.h"
+#include "prototypes.h"
+#include "parallel.h"
+#include "param.h"
+#include "readframe.h"
+#include "mheaders.h"
+#include "fsize.h"
+#include "input.h"
+#include "combine.h"
+
+
+#define READ_ATTEMPTS 5 /* number of times (seconds) to retry an input file */
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+extern int  yuvWidth, yuvHeight;
+
+
+/*===========================================================================*
+ *
+ * AppendFile
+ *
+ *  appends the output file with the contents of the given input file
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static void
+AppendFile(FILE * const ofP,
+           FILE * const ifP) {
+
+    uint8 data[9999];
+    unsigned int readItems;
+
+    readItems = 9999;
+    while (readItems == 9999) {
+        readItems = fread(data, sizeof(uint8), 9999, ifP);
+        if (readItems > 0)
+            fwrite(data, sizeof(uint8), readItems, ofP);
+    }
+    fclose(ifP);
+}
+
+
+
+static void
+appendSpecifiedGopFiles(struct inputSource * const inputSourceP,
+                        const char *         const currentGopPath,
+                        FILE *               const ofP) {
+
+    unsigned int fileSeq;
+
+    for (fileSeq = 0; fileSeq < inputSourceP->numInputFiles; ++fileSeq) {
+        const char * inputFileName;
+        const char * fileName;
+        unsigned int nAttempts;
+        FILE * ifP;
+
+        GetNthInputFileName(inputSourceP, fileSeq, &inputFileName);
+        asprintfN(&fileName, "%s/%s", currentGOPPath, inputFileName);
+
+        for (nAttempts = 0, ifP = NULL;
+             nAttempts < READ_ATTEMPTS && !ifP;
+             ++nAttempts) {
+
+            FILE * ifP;
+            ifP = fopen(fileName, "rb");
+            if (ifP == NULL) {
+                pm_message("ERROR:  Couldn't read file '%s'.  retry %u", 
+                           fileName, nAttempts);
+                sleep(1);
+            }
+        }
+        if (ifP) {
+            if (!realQuiet)
+                pm_message("Appending file %u '%s'", fileSeq, fileName);
+            AppendFile(ofP, ifP);
+        } else
+            pm_error("Unable to read file '%s' after %u attempts.",
+                     fileName, READ_ATTEMPTS);
+        strfree(fileName);
+        strfree(inputFileName);
+    }
+} 
+
+
+
+static void
+appendDefaultGopFiles(const char * const outputFileName,
+                      FILE *       const ofP) {
+
+    unsigned int fileSeq;
+    bool endOfFiles;
+    
+    for (fileSeq = 0, endOfFiles = FALSE; !endOfFiles; ++fileSeq) {
+        const char * fileName;
+        FILE * ifP;
+
+        asprintfN(&fileName, "%s.gop.%u", outputFileName, fileSeq);
+        
+        ifP = fopen(fileName, "rb");
+        if (ifP == NULL)
+            endOfFiles = TRUE;
+        else {
+            if (!realQuiet)
+                pm_message("appending file:  %s", fileName);
+            
+            AppendFile(ofP, ifP);
+        }
+        strfree(fileName);
+    }
+}
+
+
+
+void
+GOPsToMPEG(struct inputSource * const inputSourceP,
+           const char *         const outputFileName, 
+           FILE *               const ofP) {
+/*----------------------------------------------------------------------------
+   Combine individual GOPs (one per file) into a single MPEG sequence file.
+-----------------------------------------------------------------------------*/
+    BitBucket * bb;
+
+    {
+        /* Why is this reset called? */
+        int x=Fsize_x, y=Fsize_y;
+        Fsize_Reset();
+        Fsize_Note(0, yuvWidth, yuvHeight);
+        if (Fsize_x == 0 || Fsize_y == 0)
+            Fsize_Note(0, x, y);
+    }
+    
+    bb = Bitio_New(ofP);
+
+    Mhead_GenSequenceHeader(bb, Fsize_x, Fsize_y, /* pratio */ aspectRatio,
+                            /* pict_rate */ frameRate, /* bit_rate */ -1,
+                            /* buf_size */ -1, /*c_param_flag */ 1,
+                            /* iq_matrix */ customQtable, 
+                            /* niq_matrix */ customNIQtable,
+                            /* ext_data */ NULL, /* ext_data_size */ 0,
+                            /* user_data */ NULL, /* user_data_size */ 0);
+
+    /* it's byte-padded, so we can dump it now */
+    Bitio_Flush(bb);
+
+    if (inputSourceP->stdinUsed)
+        AppendFile(ofP, stdin);
+    else {
+        if (inputSourceP->numInputFiles > 0)
+            appendSpecifiedGopFiles(inputSourceP, currentGOPPath, ofP);
+        else
+            appendDefaultGopFiles(outputFileName, ofP);
+    }
+    bb = Bitio_New(ofP);
+
+    /* SEQUENCE END CODE */
+    Mhead_GenSequenceEnder(bb);
+
+    Bitio_Flush(bb);
+
+    fclose(ofP);
+}
+
+
+
+static void
+makeGOPHeader(FILE *       const outputFileP,
+              unsigned int const totalFramesSent,
+              unsigned int const frameNumber,
+              unsigned int const seqWithinGop) {
+
+    boolean closed = (totalFramesSent == frameNumber);
+
+    BitBucket * bbP;
+
+    if (!realQuiet)
+        fprintf(stdout, "Creating new GOP (closed = %d) after %d frames\n",
+                closed, seqWithinGop);
+
+    /* new GOP */
+    bbP = Bitio_New(outputFileP);
+    Mhead_GenGOPHeader(bbP, /* drop_frame_flag */ 0,
+                       tc_hrs, tc_min, tc_sec, tc_pict,
+                       closed, /* broken_link */ 0,
+                       /* ext_data */ NULL, /* ext_data_size */ 0,
+                       /* user_data */ NULL, 
+                       /* user_data_size */ 0);
+    Bitio_Flush(bbP);
+    SetGOPStartTime(frameNumber);
+}        
+
+
+
+void
+FramesToMPEG(FILE *               const outputFile, 
+             void *               const inputHandle,
+             fileAcquisitionFn          acquireInputFile,
+             fileDispositionFn          disposeInputFile) {
+/*----------------------------------------------------------------------------
+   Combine a bunch of frames, one per file, into a single MPEG
+   sequence file.
+
+   acquireInputFile() opens a file that contains an encoded frame,
+   identified by frame number.  It returns NULL when the frame number
+   is beyond the frames available.
+-----------------------------------------------------------------------------*/
+    unsigned int frameNumber;
+    BitBucket *bb;
+    FILE *inputFile;
+    int pastRefNum = -1;
+    int futureRefNum = -1;
+    boolean inputLeft;
+    unsigned int seqWithinGop;
+        /* The sequence of the current frame within its GOP.  0 is the
+           first frame of a GOP, etc.
+        */
+
+    tc_hrs = 0; tc_min = 0; tc_sec = 0; tc_pict = 0; tc_extra = 0;
+
+    {
+        /* Why is this reset called? */
+        int x=Fsize_x, y=Fsize_y;
+        Fsize_Reset();
+        Fsize_Note(0, yuvWidth, yuvHeight);
+        if (Fsize_x == 0 || Fsize_y == 0)
+            Fsize_Note(0, x, y);
+    }
+    SetBlocksPerSlice();
+
+    bb = Bitio_New(outputFile);
+    Mhead_GenSequenceHeader(bb, Fsize_x, Fsize_y, /* pratio */ aspectRatio,
+                            /* pict_rate */ frameRate, /* bit_rate */ -1,
+                            /* buf_size */ -1, /*c_param_flag */ 1,
+                            /* iq_matrix */ qtable, /* niq_matrix */ niqtable,
+                            /* ext_data */ NULL, /* ext_data_size */ 0,
+                            /* user_data */ NULL, /* user_data_size */ 0);
+    /* it's byte-padded, so we can dump it now */
+    Bitio_Flush(bb);
+
+    /* need to do these in the right order!!! */
+    /* also need to add GOP headers */
+
+    seqWithinGop = 0;
+    totalFramesSent = 0;
+
+    inputLeft = TRUE;
+    frameNumber = 0;
+
+    makeGOPHeader(outputFile, totalFramesSent, frameNumber, seqWithinGop);
+    
+    while (inputLeft) {
+        if (FType_Type(frameNumber) != 'b') {
+            pastRefNum = futureRefNum;
+            futureRefNum = frameNumber;
+
+            if ((FType_Type(frameNumber) == 'i') && seqWithinGop >= gopSize) {
+                makeGOPHeader(outputFile,
+                              totalFramesSent, frameNumber, seqWithinGop);
+                seqWithinGop -= gopSize;
+            }
+
+            acquireInputFile(inputHandle, frameNumber, &inputFile);
+            if (inputFile == NULL)
+                inputLeft = FALSE;
+            else {
+                AppendFile(outputFile, inputFile);
+
+                disposeInputFile(inputHandle, frameNumber);
+
+                ++seqWithinGop;
+                IncrementTCTime();
+
+                /* now, output the B-frames */
+                if (pastRefNum != -1) {
+                    unsigned int bNum;
+                    
+                    for (bNum = pastRefNum+1; bNum < futureRefNum; ++bNum) {
+                        acquireInputFile(inputHandle, bNum, &inputFile);
+
+                        if (inputFile) {
+                            AppendFile(outputFile, inputFile);
+
+                            disposeInputFile(inputHandle, bNum);
+            
+                            ++seqWithinGop;
+                            IncrementTCTime();
+                        }
+                    }
+                }
+            }
+        }
+        ++frameNumber;
+    }
+
+    if (!realQuiet)
+        fprintf(stdout, "Wrote %d frames\n", totalFramesSent);
+
+    bb = Bitio_New(outputFile);
+
+    /* SEQUENCE END CODE */
+    Mhead_GenSequenceEnder(bb);
+
+    Bitio_Flush(bb);
+
+    fclose(outputFile);
+}
+
+
+
diff --git a/converter/ppm/ppmtompeg/docs/EXTENSIONS b/converter/ppm/ppmtompeg/docs/EXTENSIONS
new file mode 100644
index 00000000..c683fbf8
--- /dev/null
+++ b/converter/ppm/ppmtompeg/docs/EXTENSIONS
@@ -0,0 +1,41 @@
+EXTENSIONS
+----------
+
+This is a list of things that we'd like to incorporate into the encoder.
+If you succeed in implementing any of them, please let us know!
+
+*  better B-frame search technique
+*  use DCT-space when computing error terms
+*  vary the q-scale according to the error term
+*  other motion vector search techniques
+*  modify the program to have a finer-grained parallelism option -- we
+   can probably encode slices in parallel (this will only be useful if we
+   want to do a few B-frames using exhaustive search)
+*  include system layer
+*  VBV delay with rate control
+
+
+CREATING YOUR OWN MOTION SEARCH ROUTINES
+----------------------------------------
+
+Adding your own special motion search routine is very easy.  We'll explain
+adding a P-frame search routine; adding a B-frame routine is similar.
+
+First, edit the procedures PMotionSearch and SetPSearchAlg (both in the
+file psearch.c) to recognize your new search routine.  You probably want
+to define a constant
+    PSEARCH_<your search name> in headers/search.h
+
+Have PMotionSearch call your search procedure just as it calls the other
+standard search procedures.  Make sure your procedure follows the guidelines
+in the comments for PMotionSearch.
+
+Note:  The encoder uses MAD as its search criterion.  The reason for this:
+	"Among the various criteria that can be used as a measure of the
+	 match between the two blocks, the mean absolute difference (MAD)
+	 is favored because it requires no multiplication and gives
+	 similar performance as the mean squared error (MSE)."
+	- Liu and Zaccarin,
+	  "New Fast Algorithms for the Estimation of Block Motion Vectors,"
+	  IEEE Transactions on Circuits and Systems for Video Technology
+	  Volume 3 No. 2 (April 1993)
diff --git a/converter/ppm/ppmtompeg/docs/INPUT.FORMAT b/converter/ppm/ppmtompeg/docs/INPUT.FORMAT
new file mode 100644
index 00000000..8a663d3a
--- /dev/null
+++ b/converter/ppm/ppmtompeg/docs/INPUT.FORMAT
@@ -0,0 +1,79 @@
+			----------------
+			| FILE FORMATS |
+			----------------
+
+PPM FORMAT
+----------
+
+See http://netpbm.sourceforge.net/doc/ppm.html .
+
+
+UCB YUV FORMAT
+--------------
+
+You should be aware that the YUV format used in the MPEG encoder is DIFFERENT
+than the Abekas YUV format.  The reason for this is that in MPEG, the U and
+V components are subsampled 4:1.
+
+To give you an idea of what format the YUV file must be in, the following
+code will read in a YUV file:
+
+    unsigned char **y_data, **cr_data, **cb_data;
+
+    void	ReadYUV(char *fileName, int width, int height)
+    {
+	FILE *fpointer;
+	register int y;
+
+	/* should allocate memory for y_data, cr_data, cb_data here */
+
+	fpointer = fopen(fileName, "r");
+
+	for (y = 0; y < height; y++)			/* Y */
+	    fread(y_data[y], 1, width, fpointer);
+
+	for (y = 0; y < height / 2; y++)			/* U */
+	    fread(cb_data[y], 1, width / 2, fpointer);
+
+	for (y = 0; y < height / 2; y++)			/* V */
+	    fread(cr_data[y], 1, width / 2, fpointer);
+
+	fclose(fpointer);
+    }
+
+There are two reasons why you'd want to use YUV files rather than PPM files:
+	1)  The YUV files are 50% the size of the corresponding PPM files
+	2)  The ENCODER will run slightly faster, since it doesn't have to
+	    do the RGB to YUV conversion itself.
+
+
+ABEKAS YUV FORMAT 
+-----------------
+
+The Abekas YUV Format interlaces the Y, U, and V values in a 4:2:2 format.
+The interlacing pattern is
+
+UYVY
+
+for each group of 4 bytes in the file.
+
+
+PHILLIPS YUV FORMAT 
+-------------------
+
+The Phillips YUV Format interlaces the Y, U, and V values in a 4:2:2 format.
+The interlacing pattern is
+
+YVYU
+
+for each group of 4 bytes in the file.
+
+
+You may specify either ABEKAS, PHILLIPS, or UCB as the YUV_FORMAT when
+encoding ; the encoder defaults to UCB YUV_FORMAT if not specified.
+In addition, if you've got a weird interlacing format, you can also
+try and de-interlace it by giving the YUV pattern in the YUV_FORMAT.
+So a YUV 4:4:4 format would be
+
+YUVYUV
+
diff --git a/converter/ppm/ppmtompeg/docs/parallel.param b/converter/ppm/ppmtompeg/docs/parallel.param
new file mode 100644
index 00000000..0702d32c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/docs/parallel.param
@@ -0,0 +1,142 @@
+# parameter file template with parallel execution
+#
+# you can use this as a template, copying it to a separate file then modifying
+# the copy
+#
+#
+# any line beginning with '#' is a comment
+#
+# no line should be longer than 255 characters
+#
+#
+# general format of each line is:
+#	<option> <spaces and/or tabs> <value>
+#
+# lines can generally be in any order
+#
+# only exception is the option 'INPUT' which must be followed by input
+# files in the order in which they must appear, followed by 'END_INPUT'
+#
+# <option> MUST be in UPPER CASE
+#
+
+PATTERN		IBBPBBI
+OUTPUT		/n/picasso/users/keving/encode/output.mpg
+
+# mpeg_encode really only accepts 3 different file formats, but using a
+# conversion statement it can effectively handle ANY file format
+#
+# you must specify whether you will convert to PNM or PPM or YUV format
+#	(must be upper case)
+#
+BASE_FILE_FORMAT	YUV
+
+#
+# if YUV format (or using parallel version), must provide width and height
+# YUV_SIZE	widthxheight
+# this option is ignored if BASE_FILE_FORMAT is PPM or PNM and you're running
+# on just one machine
+#
+YUV_SIZE	352x240
+
+# the conversion statement
+#
+# Each occurrence of '*' will be replaced by the input file
+#
+# e.g., if you have a bunch of GIF files, then this might be:
+#	INPUT_CONVERT	giftoppm *
+#
+# e.g., if you have a bunch of files like a.Y a.U a.V, etc., then:
+#	INPUT_CONVERT	cat *.Y *.U *.V
+#
+# e.g., if you are grabbing from laser disc you might have something like
+#	INPUT_CONVERT	goto frame *; grabppm
+# 'INPUT_CONVERT *' means the files are already in the base file format
+#
+INPUT_CONVERT	*
+
+# number of frames in a GOP.
+#
+# since each GOP must have at least one I-frame, the encoder will find the
+# the first I-frame after GOP_SIZE frames to start the next GOP
+#
+# later, will add more flexible GOP signalling
+#
+GOP_SIZE	6
+
+# number of slices in a frame
+#
+# 1 is a good number.  another possibility is the number of macroblock rows
+# (which is the height divided by 16)
+#
+SLICES_PER_FRAME	1
+
+# directory to get all input files from (makes this file easier to read)
+# can't use stdin with parallel encoding
+INPUT_DIR	/n/picasso/users/keving/encode/input/tennis
+
+INPUT
+# '*' is replaced by the numbers 01, 02, 03, 04
+# if I instead do [01-11], it would be 01, 02, ..., 09, 10, 11
+# if I instead do [1-11], it would be 1, 2, 3, ..., 9, 10, 11
+# if I instead do [1-11+3], it would be 1, 4, 7, 10
+# the program assumes none of your input files has a name ending in ']'
+# if you do, too bad!!!
+#
+#
+stennis.*.yuv	[0-7]
+# can have more files here if you want...there is no limit on the number
+# of files
+END_INPUT
+
+
+
+# all of the remaining options have to do with the motion search and qscale
+
+# FULL or HALF -- must be upper case
+PIXEL		HALF
+
+# means +/- this many pixels
+RANGE		10
+
+# this must be one of {EXHAUSTIVE, SUBSAMPLE, LOGARITHMIC}
+PSEARCH_ALG	LOGARITHMIC
+
+# this must be one of {SIMPLE, CROSS2, EXHAUSTIVE}
+#
+# note that EXHAUSTIVE is really, really, really slow
+#
+BSEARCH_ALG	CROSS2
+
+#
+# these specify the q-scale for I, P, and B frames
+# (values must be between 1 and 31)
+#
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# this must be ORIGINAL or DECODED
+REFERENCE_FRAME	ORIGINAL
+
+
+# the following two are optional  (default = 10, 60)
+
+# number of frames to do initially to gauge speed of machine
+PARALLEL_TEST_FRAMES	3
+
+# number of seconds per chunk thereafter
+PARALLEL_TIME_CHUNKS	30
+
+
+PARALLEL
+# lines must be of form "machine <whitespace> username <whitespace> executable"
+charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#REMOTE charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode ~keving/encode/examples/parallel.param
+# remote machine: "REMOTE machine username executable param_file"
+# mickey		keving	~keving/encode/bin/dec-5000/mpeg_encode
+#REMOTE mickey		keving	~keving/encode/bin/dec-5000/mpeg_encode ~keving/encode/examples/parallel.param
+#REMOTE mickey		keving	~keving/encode/bin/dec-5000/mpeg_encode ~keving/encode/examples/parallel.param
+REMOTE woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode ~keving/encode/examples/parallel.param
+END_PARALLEL
diff --git a/converter/ppm/ppmtompeg/docs/param-summary b/converter/ppm/ppmtompeg/docs/param-summary
new file mode 100644
index 00000000..d8322bfb
--- /dev/null
+++ b/converter/ppm/ppmtompeg/docs/param-summary
@@ -0,0 +1,14 @@
+Different MPEG coding strategies
+--------------------------------
+
+Strategy			Speed	Compression	Quality
+--------			-----	-----------	-------
+Increase MV Search Range	Down	Up		Up?
+Increasing Q-Scale		Same	Up		Down
+Decreasing Q-Scale		Same	Down		Up
+Use half-pixel search		Down	Up		Up?
+Use pixel subsampling		Up	Down		Down?
+Use motion vector subsampling	Up	Down		Down?
+Use decoded frame as reference	Same	Unknown		Up?
+Varying q-scale			Down	Up		Down
+
diff --git a/converter/ppm/ppmtompeg/docs/template.param b/converter/ppm/ppmtompeg/docs/template.param
new file mode 100644
index 00000000..66b6dd98
--- /dev/null
+++ b/converter/ppm/ppmtompeg/docs/template.param
@@ -0,0 +1,154 @@
+# parameter file template with lots of comments to assist you
+#
+# you can use this as a template, copying it to a separate file then modifying
+# the copy
+#
+#
+# any line beginning with '#' is a comment
+#
+# no line should be longer than 255 characters
+#
+#
+# general format of each line is:
+#	<option> <spaces and/or tabs> <value>
+#
+# lines can generally be in any order
+#
+# an exception is the option 'INPUT' which must be followed by input
+# files in the order in which they must appear, followed by 'END_INPUT'
+#
+# Also, if you use the `command` method of generating input file names,
+# the command will only be executed in the INPUT_DIR if INPUT_DIR preceeds
+# the INPUT parameter.
+#
+# <option> MUST be in UPPER CASE
+#
+
+PATTERN		IBBPBBPBBPBBPBBP
+OUTPUT		output.mpg
+
+# mpeg_encode really only accepts 3 different file formats, but using a
+# conversion statement it can effectively handle ANY file format
+#
+# You must specify the type of the input files.  The choices are:
+#    YUV, PPM, JMOVIE, Y, JPEG, PNM
+#	(must be upper case)
+#
+BASE_FILE_FORMAT	YUV
+
+#
+# if YUV format (or using parallel version), must provide width and height
+# YUV_SIZE	widthxheight
+# this option is ignored if BASE_FILE_FORMAT is not YUV and you're running
+# on just one machine
+#
+YUV_SIZE	352x240
+
+# If you are using YUV, there are different supported file formats.
+# EYUV or UCB are the same as previous versions of this encoder.
+# (All the Y's, then U's then V's, in 4:2:0 subsampling.)
+# Other formats, such as Abekas, Phillips, or a general format are
+# permissible, the general format is a string of Y's, U's, and V's
+# to specify the file order.
+
+INPUT_FORMAT UCB
+
+# the conversion statement
+#
+# Each occurrence of '*' will be replaced by the input file
+#
+# e.g., if you have a bunch of GIF files, then this might be:
+#	INPUT_CONVERT	giftoppm *
+#
+# e.g., if you have a bunch of files like a.Y a.U a.V, etc., then:
+#	INPUT_CONVERT	cat *.Y *.U *.V
+#
+# e.g., if you are grabbing from laser disc you might have something like
+#	INPUT_CONVERT	goto frame *; grabppm
+# 'INPUT_CONVERT *' means the files are already in the base file format
+#
+INPUT_CONVERT	*
+
+# number of frames in a GOP.
+#
+# since each GOP must have at least one I-frame, the encoder will find the
+# the first I-frame after GOP_SIZE frames to start the next GOP
+#
+# later, will add more flexible GOP signalling
+#
+GOP_SIZE	16
+
+# number of slices in a frame
+#
+# 1 is a good number.  another possibility is the number of macroblock rows
+# (which is the height divided by 16)
+#
+SLICES_PER_FRAME	1
+
+# directory to get all input files from (makes this file easier to read)
+INPUT_DIR	../input/tennis
+
+# There are a bunch of ways to specify the input files.
+# from a simple one-per-line listing, to the following 
+# way of numbering them.  See the manual for more information.
+INPUT
+# '*' is replaced by the numbers 01, 02, 03, 04
+# if I instead do [01-11], it would be 01, 02, ..., 09, 10, 11
+# if I instead do [1-11], it would be 1, 2, 3, ..., 9, 10, 11
+# if I instead do [1-11+3], it would be 1, 4, 7, 10
+# the program assumes none of your input files has a name ending in ']'
+# if you do, too bad!!!
+#
+#
+stennis.*.yuv	[0-23]
+# can have more files here if you want...there is no limit on the number
+# of files
+END_INPUT
+
+
+
+# Many of the remaining options have to do with the motion search and qscale
+
+# FULL or HALF -- must be upper case
+PIXEL		HALF
+
+# means +/- this many pixels for both P and B frame searches
+# specify two numbers if you wish to serc different ranges in the two.
+RANGE		10
+
+# this must be one of {EXHAUSTIVE, SUBSAMPLE, LOGARITHMIC}
+PSEARCH_ALG	LOGARITHMIC
+
+# this must be one of {SIMPLE, CROSS2, EXHAUSTIVE}
+#
+# note that EXHAUSTIVE is really, really, really slow
+#
+BSEARCH_ALG	CROSS2
+
+#
+# these specify the q-scale for I, P, and B frames
+# (values must be between 1 and 31)
+# These are the Qscale values for the entire frame in variable bit-rate
+# mode, and starting points (but not important) for constant bit rate
+#
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# this must be ORIGINAL or DECODED
+REFERENCE_FRAME	ORIGINAL
+
+# for parallel parameters see parallel.param in the exmaples subdirectory
+
+# if you want constant bit-rate mode, specify it as follows (number is bits/sec):
+BIT_RATE  1000000
+
+# To specify the buffer size (327680 is default, measused in bits, for 16bit words)
+BUFFER_SIZE 327680
+
+# The frame rate is the number of frames/second (legal values:
+# 23.976, 24, 25, 29.97, 30, 50 ,59.94, 60
+FRAME_RATE 30
+
+# There are many more options, see the users manual for examples....
+# ASPECT_RATIO, USER_DATA, GAMMA, IQTABLE, etc.
diff --git a/converter/ppm/ppmtompeg/docs/users-guide.ps b/converter/ppm/ppmtompeg/docs/users-guide.ps
new file mode 100644
index 00000000..2e39be0a
--- /dev/null
+++ b/converter/ppm/ppmtompeg/docs/users-guide.ps
@@ -0,0 +1,3233 @@
+%!
+%%BoundingBox: (atend)
+%%Pages: (atend)
+%%DocumentFonts: (atend)
+%%EndComments
+%
+% FrameMaker PostScript Prolog 3.0, for use with FrameMaker 3.0
+% Copyright (c) 1986,87,89,90,91 by Frame Technology Corporation.
+% All rights reserved.
+%
+% Known Problems:
+%	Due to bugs in Transcript, the 'PS-Adobe-' is omitted from line 1
+/FMversion (3.0) def 
+% Set up Color vs. Black-and-White
+	/FMPrintInColor systemdict /colorimage known
+		systemdict /currentcolortransfer known or def
+% Uncomment this line to force b&w on color printer
+%   /FMPrintInColor false def
+/FrameDict 195 dict def 
+systemdict /errordict known not {/errordict 10 dict def
+		errordict /rangecheck {stop} put} if
+% The readline in 23.0 doesn't recognize cr's as nl's on AppleTalk
+FrameDict /tmprangecheck errordict /rangecheck get put 
+errordict /rangecheck {FrameDict /bug true put} put 
+FrameDict /bug false put 
+mark 
+% Some PS machines read past the CR, so keep the following 3 lines together!
+currentfile 5 string readline
+00
+0000000000
+cleartomark 
+errordict /rangecheck FrameDict /tmprangecheck get put 
+FrameDict /bug get { 
+	/readline {
+		/gstring exch def
+		/gfile exch def
+		/gindex 0 def
+		{
+			gfile read pop 
+			dup 10 eq {exit} if 
+			dup 13 eq {exit} if 
+			gstring exch gindex exch put 
+			/gindex gindex 1 add def 
+		} loop
+		pop 
+		gstring 0 gindex getinterval true 
+		} def
+	} if
+/FMVERSION {
+	FMversion ne {
+		/Times-Roman findfont 18 scalefont setfont
+		100 100 moveto
+		(FrameMaker version does not match postscript_prolog!)
+		dup =
+		show showpage
+		} if
+	} def 
+/FMLOCAL {
+	FrameDict begin
+	0 def 
+	end 
+	} def 
+	/gstring FMLOCAL
+	/gfile FMLOCAL
+	/gindex FMLOCAL
+	/orgxfer FMLOCAL
+	/orgproc FMLOCAL
+	/organgle FMLOCAL
+	/orgfreq FMLOCAL
+	/yscale FMLOCAL
+	/xscale FMLOCAL
+	/manualfeed FMLOCAL
+	/paperheight FMLOCAL
+	/paperwidth FMLOCAL
+/FMDOCUMENT { 
+	array /FMfonts exch def 
+	/#copies exch def
+	FrameDict begin
+	0 ne dup {setmanualfeed} if
+	/manualfeed exch def
+	/paperheight exch def
+	/paperwidth exch def
+	/yscale exch def
+	/xscale exch def
+	currenttransfer cvlit /orgxfer exch def
+	currentscreen cvlit /orgproc exch def
+	/organgle exch def /orgfreq exch def
+	setpapername 
+	manualfeed {true} {papersize} ifelse 
+	{manualpapersize} {false} ifelse 
+	{desperatepapersize} if
+	end 
+	} def 
+	/pagesave FMLOCAL
+	/orgmatrix FMLOCAL
+	/landscape FMLOCAL
+/FMBEGINPAGE { 
+	FrameDict begin 
+	/pagesave save def
+	3.86 setmiterlimit
+	/landscape exch 0 ne def
+	landscape { 
+		90 rotate 0 exch neg translate pop 
+		}
+		{pop pop}
+		ifelse
+	xscale yscale scale
+	/orgmatrix matrix def
+	gsave 
+	} def 
+/FMENDPAGE {
+	grestore 
+	pagesave restore
+	end 
+	showpage
+	} def 
+/FMFONTDEFINE { 
+	FrameDict begin
+	findfont 
+	ReEncode 
+	1 index exch 
+	definefont 
+	FMfonts 3 1 roll 
+	put
+	end 
+	} def 
+/FMFILLS {
+	FrameDict begin
+	array /fillvals exch def
+	end 
+	} def 
+/FMFILL {
+	FrameDict begin
+	 fillvals 3 1 roll put
+	end 
+	} def 
+/FMNORMALIZEGRAPHICS { 
+	newpath
+	0.0 0.0 moveto
+	1 setlinewidth
+	0 setlinecap
+	0 0 0 sethsbcolor
+	0 setgray 
+	} bind def
+	/fx FMLOCAL
+	/fy FMLOCAL
+	/fh FMLOCAL
+	/fw FMLOCAL
+	/llx FMLOCAL
+	/lly FMLOCAL
+	/urx FMLOCAL
+	/ury FMLOCAL
+/FMBEGINEPSF { 
+	end 
+	/FMEPSF save def 
+	/showpage {} def 
+	FMNORMALIZEGRAPHICS 
+	[/fy /fx /fh /fw /ury /urx /lly /llx] {exch def} forall 
+	fx fy translate 
+	rotate
+	fw urx llx sub div fh ury lly sub div scale 
+	llx neg lly neg translate 
+	} bind def
+/FMENDEPSF {
+	FMEPSF restore
+	FrameDict begin 
+	} bind def
+FrameDict begin 
+/setmanualfeed {
+%%BeginFeature *ManualFeed True
+	 statusdict /manualfeed true put
+%%EndFeature
+	} def
+/max {2 copy lt {exch} if pop} bind def
+/min {2 copy gt {exch} if pop} bind def
+/inch {72 mul} def
+/pagedimen { 
+	paperheight sub abs 16 lt exch 
+	paperwidth sub abs 16 lt and
+	{/papername exch def} {pop} ifelse
+	} def
+	/papersizedict FMLOCAL
+/setpapername { 
+	/papersizedict 14 dict def 
+	papersizedict begin
+	/papername /unknown def 
+		/Letter 8.5 inch 11.0 inch pagedimen
+		/LetterSmall 7.68 inch 10.16 inch pagedimen
+		/Tabloid 11.0 inch 17.0 inch pagedimen
+		/Ledger 17.0 inch 11.0 inch pagedimen
+		/Legal 8.5 inch 14.0 inch pagedimen
+		/Statement 5.5 inch 8.5 inch pagedimen
+		/Executive 7.5 inch 10.0 inch pagedimen
+		/A3 11.69 inch 16.5 inch pagedimen
+		/A4 8.26 inch 11.69 inch pagedimen
+		/A4Small 7.47 inch 10.85 inch pagedimen
+		/B4 10.125 inch 14.33 inch pagedimen
+		/B5 7.16 inch 10.125 inch pagedimen
+	end
+	} def
+/papersize {
+	papersizedict begin
+		/Letter {lettertray letter} def
+		/LetterSmall {lettertray lettersmall} def
+		/Tabloid {11x17tray 11x17} def
+		/Ledger {ledgertray ledger} def
+		/Legal {legaltray legal} def
+		/Statement {statementtray statement} def
+		/Executive {executivetray executive} def
+		/A3 {a3tray a3} def
+		/A4 {a4tray a4} def
+		/A4Small {a4tray a4small} def
+		/B4 {b4tray b4} def
+		/B5 {b5tray b5} def
+		/unknown {unknown} def
+	papersizedict dup papername known {papername} {/unknown} ifelse get
+	end
+	/FMdicttop countdictstack 1 add def 
+	statusdict begin stopped end 
+	countdictstack -1 FMdicttop {pop end} for 
+	} def
+/manualpapersize {
+	papersizedict begin
+		/Letter {letter} def
+		/LetterSmall {lettersmall} def
+		/Tabloid {11x17} def
+		/Ledger {ledger} def
+		/Legal {legal} def
+		/Statement {statement} def
+		/Executive {executive} def
+		/A3 {a3} def
+		/A4 {a4} def
+		/A4Small {a4small} def
+		/B4 {b4} def
+		/B5 {b5} def
+		/unknown {unknown} def
+	papersizedict dup papername known {papername} {/unknown} ifelse get
+	end
+	stopped 
+	} def
+/desperatepapersize {
+	statusdict /setpageparams known
+		{
+		paperwidth paperheight 0 1 
+		statusdict begin
+		{setpageparams} stopped pop 
+		end
+		} if
+	} def
+/savematrix {
+	orgmatrix currentmatrix pop
+	} bind def
+/restorematrix {
+	orgmatrix setmatrix
+	} bind def
+/dmatrix matrix def
+/dpi    72 0 dmatrix defaultmatrix dtransform
+    dup mul exch   dup mul add   sqrt def
+/freq dpi 18.75 div 8 div round dup 0 eq {pop 1} if 8 mul dpi exch div def
+/sangle 1 0 dmatrix defaultmatrix dtransform exch atan def
+/DiacriticEncoding [
+/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef
+/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef
+/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef
+/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef
+/.notdef /.notdef /.notdef /.notdef /space /exclam /quotedbl
+/numbersign /dollar /percent /ampersand /quotesingle /parenleft
+/parenright /asterisk /plus /comma /hyphen /period /slash /zero /one
+/two /three /four /five /six /seven /eight /nine /colon /semicolon
+/less /equal /greater /question /at /A /B /C /D /E /F /G /H /I /J /K
+/L /M /N /O /P /Q /R /S /T /U /V /W /X /Y /Z /bracketleft /backslash
+/bracketright /asciicircum /underscore /grave /a /b /c /d /e /f /g /h
+/i /j /k /l /m /n /o /p /q /r /s /t /u /v /w /x /y /z /braceleft /bar
+/braceright /asciitilde /.notdef /Adieresis /Aring /Ccedilla /Eacute
+/Ntilde /Odieresis /Udieresis /aacute /agrave /acircumflex /adieresis
+/atilde /aring /ccedilla /eacute /egrave /ecircumflex /edieresis
+/iacute /igrave /icircumflex /idieresis /ntilde /oacute /ograve
+/ocircumflex /odieresis /otilde /uacute /ugrave /ucircumflex
+/udieresis /dagger /.notdef /cent /sterling /section /bullet
+/paragraph /germandbls /registered /copyright /trademark /acute
+/dieresis /.notdef /AE /Oslash /.notdef /.notdef /.notdef /.notdef
+/yen /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef
+/ordfeminine /ordmasculine /.notdef /ae /oslash /questiondown
+/exclamdown /logicalnot /.notdef /florin /.notdef /.notdef
+/guillemotleft /guillemotright /ellipsis /.notdef /Agrave /Atilde
+/Otilde /OE /oe /endash /emdash /quotedblleft /quotedblright
+/quoteleft /quoteright /.notdef /.notdef /ydieresis /Ydieresis
+/fraction /currency /guilsinglleft /guilsinglright /fi /fl /daggerdbl
+/periodcentered /quotesinglbase /quotedblbase /perthousand
+/Acircumflex /Ecircumflex /Aacute /Edieresis /Egrave /Iacute
+/Icircumflex /Idieresis /Igrave /Oacute /Ocircumflex /.notdef /Ograve
+/Uacute /Ucircumflex /Ugrave /dotlessi /circumflex /tilde /macron
+/breve /dotaccent /ring /cedilla /hungarumlaut /ogonek /caron
+] def
+/ReEncode { 
+	dup 
+	length 
+	dict begin 
+	{
+	1 index /FID ne 
+		{def} 
+		{pop pop} ifelse 
+	} forall 
+	0 eq {/Encoding DiacriticEncoding def} if 
+	currentdict 
+	end 
+	} bind def
+/graymode true def
+	/bwidth FMLOCAL
+	/bpside FMLOCAL
+	/bstring FMLOCAL
+	/onbits FMLOCAL
+	/offbits FMLOCAL
+	/xindex FMLOCAL
+	/yindex FMLOCAL
+	/x FMLOCAL
+	/y FMLOCAL
+/setpattern {
+	 /bwidth  exch def
+	 /bpside  exch def
+	 /bstring exch def
+	 /onbits 0 def  /offbits 0 def
+	 freq sangle landscape {90 add} if 
+		{/y exch def
+		 /x exch def
+		 /xindex x 1 add 2 div bpside mul cvi def
+		 /yindex y 1 add 2 div bpside mul cvi def
+		 bstring yindex bwidth mul xindex 8 idiv add get
+		 1 7 xindex 8 mod sub bitshift and 0 ne
+		 {/onbits  onbits  1 add def 1}
+		 {/offbits offbits 1 add def 0}
+		 ifelse
+		}
+		setscreen
+	 {} settransfer
+	 offbits offbits onbits add div FMsetgray
+	/graymode false def
+	} bind def
+/grayness {
+	FMsetgray
+	graymode not {
+		/graymode true def
+		orgxfer cvx settransfer
+		orgfreq organgle orgproc cvx setscreen
+		} if
+	} bind def
+	/HUE FMLOCAL
+	/SAT FMLOCAL
+	/BRIGHT FMLOCAL
+	/Colors FMLOCAL
+FMPrintInColor 
+	
+	{
+	/HUE 0 def
+	/SAT 0 def
+	/BRIGHT 0 def
+	% array of arrays Hue and Sat values for the separations [HUE BRIGHT]
+	/Colors   
+	[[0    0  ]    % black
+	 [0    0  ]    % white
+	 [0.00 1.0]    % red
+	 [0.37 1.0]    % green
+	 [0.60 1.0]    % blue
+	 [0.50 1.0]    % cyan
+	 [0.83 1.0]    % magenta
+	 [0.16 1.0]    % comment / yellow
+	 ] def
+      
+	/BEGINBITMAPCOLOR { 
+		BITMAPCOLOR} def
+	/BEGINBITMAPCOLORc { 
+		BITMAPCOLORc} def
+	/BEGINBITMAPTRUECOLOR { 
+		BITMAPTRUECOLOR } def
+	/BEGINBITMAPTRUECOLORc { 
+		BITMAPTRUECOLORc } def
+	/K { 
+		Colors exch get dup
+		0 get /HUE exch store 
+		1 get /BRIGHT exch store
+		  HUE 0 eq BRIGHT 0 eq and
+			{1.0 SAT sub setgray}
+			{HUE SAT BRIGHT sethsbcolor} 
+		  ifelse
+		} def
+	/FMsetgray { 
+		/SAT exch 1.0 exch sub store 
+		  HUE 0 eq BRIGHT 0 eq and
+			{1.0 SAT sub setgray}
+			{HUE SAT BRIGHT sethsbcolor} 
+		  ifelse
+		} bind def
+	}
+	
+	{
+	/BEGINBITMAPCOLOR { 
+		BITMAPGRAY} def
+	/BEGINBITMAPCOLORc { 
+		BITMAPGRAYc} def
+	/BEGINBITMAPTRUECOLOR { 
+		BITMAPTRUEGRAY } def
+	/BEGINBITMAPTRUECOLORc { 
+		BITMAPTRUEGRAYc } def
+	/FMsetgray {setgray} bind def
+	/K { 
+		pop
+		} def
+	}
+ifelse
+/normalize {
+	transform round exch round exch itransform
+	} bind def
+/dnormalize {
+	dtransform round exch round exch idtransform
+	} bind def
+/lnormalize { 
+	0 dtransform exch cvi 2 idiv 2 mul 1 add exch idtransform pop
+	} bind def
+/H { 
+	lnormalize setlinewidth
+	} bind def
+/Z {
+	setlinecap
+	} bind def
+	/fillvals FMLOCAL
+/X { 
+	fillvals exch get
+	dup type /stringtype eq
+	{8 1 setpattern} 
+	{grayness}
+	ifelse
+	} bind def
+/V { 
+	gsave eofill grestore
+	} bind def
+/N { 
+	stroke
+	} bind def
+/M {newpath moveto} bind def
+/E {lineto} bind def
+/D {curveto} bind def
+/O {closepath} bind def
+	/n FMLOCAL
+/L { 
+ 	/n exch def
+	newpath
+	normalize
+	moveto 
+	2 1 n {pop normalize lineto} for
+	} bind def
+/Y { 
+	L 
+	closepath
+	} bind def
+	/x1 FMLOCAL
+	/x2 FMLOCAL
+	/y1 FMLOCAL
+	/y2 FMLOCAL
+	/rad FMLOCAL
+/R { 
+	/y2 exch def
+	/x2 exch def
+	/y1 exch def
+	/x1 exch def
+	x1 y1
+	x2 y1
+	x2 y2
+	x1 y2
+	4 Y 
+	} bind def
+/RR { 
+	/rad exch def
+	normalize
+	/y2 exch def
+	/x2 exch def
+	normalize
+	/y1 exch def
+	/x1 exch def
+	newpath
+	x1 y1 rad add moveto
+	x1 y2 x2 y2 rad arcto
+	x2 y2 x2 y1 rad arcto
+	x2 y1 x1 y1 rad arcto
+	x1 y1 x1 y2 rad arcto
+	closepath
+	16 {pop} repeat
+	} bind def
+/C { 
+	grestore
+	gsave
+	R 
+	clip
+	} bind def
+	/FMpointsize FMLOCAL
+/F { 
+	FMfonts exch get
+	FMpointsize scalefont
+	setfont
+	} bind def
+/Q { 
+	/FMpointsize exch def
+	F 
+	} bind def
+/T { 
+	moveto show
+	} bind def
+/RF { 
+	rotate
+	0 ne {-1 1 scale} if
+	} bind def
+/TF { 
+	gsave
+	moveto 
+	RF
+	show
+	grestore
+	} bind def
+/P { 
+	moveto
+	0 32 3 2 roll widthshow
+	} bind def
+/PF { 
+	gsave
+	moveto 
+	RF
+	0 32 3 2 roll widthshow
+	grestore
+	} bind def
+/S { 
+	moveto
+	0 exch ashow
+	} bind def
+/SF { 
+	gsave
+	moveto
+	RF
+	0 exch ashow
+	grestore
+	} bind def
+/B { 
+	moveto
+	0 32 4 2 roll 0 exch awidthshow
+	} bind def
+/BF { 
+	gsave
+	moveto
+	RF
+	0 32 4 2 roll 0 exch awidthshow
+	grestore
+	} bind def
+/G { 
+	gsave
+	newpath
+	normalize translate 0.0 0.0 moveto 
+	dnormalize scale 
+	0.0 0.0 1.0 5 3 roll arc 
+	closepath fill
+	grestore
+	} bind def
+/A { 
+	gsave
+	savematrix
+	newpath
+	2 index 2 div add exch 3 index 2 div sub exch 
+	normalize 2 index 2 div sub exch 3 index 2 div add exch 
+	translate 
+	scale 
+	0.0 0.0 1.0 5 3 roll arc 
+	restorematrix
+	stroke
+	grestore
+	} bind def
+	/x FMLOCAL
+	/y FMLOCAL
+	/w FMLOCAL
+	/h FMLOCAL
+	/xx FMLOCAL
+	/yy FMLOCAL
+	/ww FMLOCAL
+	/hh FMLOCAL
+	/FMsaveobject FMLOCAL
+	/FMoptop FMLOCAL
+	/FMdicttop FMLOCAL
+/BEGINPRINTCODE { 
+	/FMdicttop countdictstack 1 add def 
+	/FMoptop count 4 sub def 
+	/FMsaveobject save def
+	userdict begin 
+	/showpage {} def 
+	FMNORMALIZEGRAPHICS 
+	3 index neg 3 index neg translate
+	} bind def
+/ENDPRINTCODE {
+	count -1 FMoptop {pop pop} for 
+	countdictstack -1 FMdicttop {pop end} for 
+	FMsaveobject restore 
+	} bind def
+/gn { 
+	0 
+	{	46 mul 
+		cf read pop 
+		32 sub 
+		dup 46 lt {exit} if 
+		46 sub add 
+		} loop
+	add 
+	} bind def
+	/str FMLOCAL
+/cfs { 
+	/str sl string def 
+	0 1 sl 1 sub {str exch val put} for 
+	str def 
+	} bind def
+/ic [ 
+	0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0223
+	0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0223
+	0
+	{0 hx} {1 hx} {2 hx} {3 hx} {4 hx} {5 hx} {6 hx} {7 hx} {8 hx} {9 hx}
+	{10 hx} {11 hx} {12 hx} {13 hx} {14 hx} {15 hx} {16 hx} {17 hx} {18 hx}
+	{19 hx} {gn hx} {0} {1} {2} {3} {4} {5} {6} {7} {8} {9} {10} {11} {12}
+	{13} {14} {15} {16} {17} {18} {19} {gn} {0 wh} {1 wh} {2 wh} {3 wh}
+	{4 wh} {5 wh} {6 wh} {7 wh} {8 wh} {9 wh} {10 wh} {11 wh} {12 wh}
+	{13 wh} {14 wh} {gn wh} {0 bl} {1 bl} {2 bl} {3 bl} {4 bl} {5 bl} {6 bl}
+	{7 bl} {8 bl} {9 bl} {10 bl} {11 bl} {12 bl} {13 bl} {14 bl} {gn bl}
+	{0 fl} {1 fl} {2 fl} {3 fl} {4 fl} {5 fl} {6 fl} {7 fl} {8 fl} {9 fl}
+	{10 fl} {11 fl} {12 fl} {13 fl} {14 fl} {gn fl}
+	] def
+	/sl FMLOCAL
+	/val FMLOCAL
+	/ws FMLOCAL
+	/im FMLOCAL
+	/bs FMLOCAL
+	/cs FMLOCAL
+	/len FMLOCAL
+	/pos FMLOCAL
+/ms { 
+	/sl exch def 
+	/val 255 def 
+	/ws cfs 
+	/im cfs 
+	/val 0 def 
+	/bs cfs 
+	/cs cfs 
+	} bind def
+400 ms 
+/ip { 
+	is 
+	0 
+	cf cs readline pop 
+	{	ic exch get exec 
+		add 
+		} forall 
+	pop 
+	
+	} bind def
+/wh { 
+	/len exch def 
+	/pos exch def 
+	ws 0 len getinterval im pos len getinterval copy pop
+	pos len 
+	} bind def
+/bl { 
+	/len exch def 
+	/pos exch def 
+	bs 0 len getinterval im pos len getinterval copy pop
+	pos len 
+	} bind def
+/s1 1 string def
+/fl { 
+	/len exch def 
+	/pos exch def 
+	/val cf s1 readhexstring pop 0 get def
+	pos 1 pos len add 1 sub {im exch val put} for
+	pos len 
+	} bind def
+/hx { 
+	3 copy getinterval 
+	cf exch readhexstring pop pop 
+	} bind def
+	/h FMLOCAL
+	/w FMLOCAL
+	/d FMLOCAL
+	/lb FMLOCAL
+	/bitmapsave FMLOCAL
+	/is FMLOCAL
+	/cf FMLOCAL
+/wbytes { 
+	dup 
+	8 eq {pop} {1 eq {7 add 8 idiv} {3 add 4 idiv} ifelse} ifelse
+	} bind def
+/BEGINBITMAPBWc { 
+	1 {} COMMONBITMAPc
+	} bind def
+/BEGINBITMAPGRAYc { 
+	8 {} COMMONBITMAPc
+	} bind def
+/BEGINBITMAP2BITc { 
+	2 {} COMMONBITMAPc
+	} bind def
+/COMMONBITMAPc { 
+	/r exch def
+	/d exch def
+	gsave
+	translate rotate scale /h exch def /w exch def
+	/lb w d wbytes def 
+	sl lb lt {lb ms} if 
+	/bitmapsave save def 
+	r                    
+	/is im 0 lb getinterval def 
+	ws 0 lb getinterval is copy pop 
+	/cf currentfile def 
+	w h d [w 0 0 h neg 0 h] 
+	{ip} image 
+	bitmapsave restore 
+	grestore
+	} bind def
+/BEGINBITMAPBW { 
+	1 {} COMMONBITMAP
+	} bind def
+/BEGINBITMAPGRAY { 
+	8 {} COMMONBITMAP
+	} bind def
+/BEGINBITMAP2BIT { 
+	2 {} COMMONBITMAP
+	} bind def
+/COMMONBITMAP { 
+	/r exch def
+	/d exch def
+	gsave
+	translate rotate scale /h exch def /w exch def
+	/bitmapsave save def 
+	r                    
+	/is w d wbytes string def
+	/cf currentfile def 
+	w h d [w 0 0 h neg 0 h] 
+	{cf is readhexstring pop} image
+	bitmapsave restore 
+	grestore
+	} bind def
+	/proc1 FMLOCAL
+	/proc2 FMLOCAL
+	/newproc FMLOCAL
+/Fmcc {
+    /proc2 exch cvlit def
+    /proc1 exch cvlit def
+    /newproc proc1 length proc2 length add array def
+    newproc 0 proc1 putinterval
+    newproc proc1 length proc2 putinterval
+    newproc cvx
+} bind def
+/ngrayt 256 array def
+/nredt 256 array def
+/nbluet 256 array def
+/ngreent 256 array def
+	/gryt FMLOCAL
+	/blut FMLOCAL
+	/grnt FMLOCAL
+	/redt FMLOCAL
+	/indx FMLOCAL
+	/cynu FMLOCAL
+	/magu FMLOCAL
+	/yelu FMLOCAL
+	/k FMLOCAL
+	/u FMLOCAL
+/colorsetup {
+	currentcolortransfer
+	/gryt exch def
+	/blut exch def
+	/grnt exch def
+	/redt exch def
+	0 1 255 {
+		/indx exch def
+		/cynu 1 red indx get 255 div sub def
+		/magu 1 green indx get 255 div sub def
+		/yelu 1 blue indx get 255 div sub def
+		/k cynu magu min yelu min def
+		/u k currentundercolorremoval exec def
+		nredt indx 1 0 cynu u sub max sub redt exec put
+		ngreent indx 1 0 magu u sub max sub grnt exec put
+		nbluet indx 1 0 yelu u sub max sub blut exec put
+		ngrayt indx 1 k currentblackgeneration exec sub gryt exec put
+	} for
+	{255 mul cvi nredt exch get}
+	{255 mul cvi ngreent exch get}
+	{255 mul cvi nbluet exch get}
+	{255 mul cvi ngrayt exch get}
+	setcolortransfer
+	{pop 0} setundercolorremoval
+	{} setblackgeneration
+	} bind def
+	/tran FMLOCAL
+/fakecolorsetup {
+	/tran 256 string def
+	0 1 255 {/indx exch def 
+		tran indx
+		red indx get 77 mul
+		green indx get 151 mul
+		blue indx get 28 mul
+		add add 256 idiv put} for
+	currenttransfer
+	{255 mul cvi tran exch get 255.0 div}
+	exch Fmcc settransfer
+} bind def
+/BITMAPCOLOR { 
+	/d 8 def
+	gsave
+	translate rotate scale /h exch def /w exch def
+	/bitmapsave save def 
+	colorsetup
+	/is w d wbytes string def
+	/cf currentfile def 
+	w h d [w 0 0 h neg 0 h] 
+	{cf is readhexstring pop} {is} {is} true 3 colorimage 
+	bitmapsave restore 
+	grestore
+	} bind def
+/BITMAPCOLORc { 
+	/d 8 def
+	gsave
+	translate rotate scale /h exch def /w exch def
+	/lb w d wbytes def 
+	sl lb lt {lb ms} if 
+	/bitmapsave save def 
+	colorsetup
+	/is im 0 lb getinterval def 
+	ws 0 lb getinterval is copy pop 
+	/cf currentfile def 
+	w h d [w 0 0 h neg 0 h] 
+	{ip} {is} {is} true 3 colorimage
+	bitmapsave restore 
+	grestore
+	} bind def
+/BITMAPTRUECOLORc { 
+        gsave
+        translate rotate scale /h exch def /w exch def
+        /bitmapsave save def 
+        
+        /is w string def
+        
+        ws 0 w getinterval is copy pop 
+        /cf currentfile def 
+        w h 8 [w 0 0 h neg 0 h] 
+        {ip} {gip} {bip} true 3 colorimage
+        bitmapsave restore 
+        grestore
+        } bind def
+/BITMAPTRUECOLOR { 
+        gsave
+        translate rotate scale /h exch def /w exch def
+        /bitmapsave save def 
+        /is w string def
+        /gis w string def
+        /bis w string def
+        /cf currentfile def 
+        w h 8 [w 0 0 h neg 0 h] 
+        { cf is readhexstring pop } 
+        { cf gis readhexstring pop } 
+        { cf bis readhexstring pop } 
+        true 3 colorimage 
+        bitmapsave restore 
+        grestore
+        } bind def
+/BITMAPTRUEGRAYc { 
+        gsave
+        translate rotate scale /h exch def /w exch def
+        /bitmapsave save def 
+        
+        /is w string def
+        
+        ws 0 w getinterval is copy pop 
+        /cf currentfile def 
+        w h 8 [w 0 0 h neg 0 h] 
+        {ip gip bip w gray} image
+        bitmapsave restore 
+        grestore
+        } bind def
+/ww FMLOCAL
+/r FMLOCAL
+/g FMLOCAL
+/b FMLOCAL
+/i FMLOCAL
+/gray { 
+        /ww exch def
+        /b exch def
+        /g exch def
+        /r exch def
+        0 1 ww 1 sub { /i exch def r i get .299 mul g i get .587 mul
+			b i get .114 mul add add r i 3 -1 roll floor cvi put } for
+        r
+        } bind def
+/BITMAPTRUEGRAY { 
+        gsave
+        translate rotate scale /h exch def /w exch def
+        /bitmapsave save def 
+        /is w string def
+        /gis w string def
+        /bis w string def
+        /cf currentfile def 
+        w h 8 [w 0 0 h neg 0 h] 
+        { cf is readhexstring pop 
+          cf gis readhexstring pop 
+          cf bis readhexstring pop w gray}  image
+        bitmapsave restore 
+        grestore
+        } bind def
+/BITMAPGRAY { 
+	8 {fakecolorsetup} COMMONBITMAP
+	} bind def
+/BITMAPGRAYc { 
+	8 {fakecolorsetup} COMMONBITMAPc
+	} bind def
+/ENDBITMAP {
+	} bind def
+end 
+	/ALDsave FMLOCAL
+	/ALDmatrix matrix def ALDmatrix currentmatrix pop
+/StartALD {
+	/ALDsave save def
+	 savematrix
+	 ALDmatrix setmatrix
+	} bind def
+/InALD {
+	 restorematrix
+	} bind def
+/DoneALD {
+	 ALDsave restore
+	} bind def
+%%EndProlog
+%%BeginSetup
+(3.0) FMVERSION
+1 1 612 792 0 1 12 FMDOCUMENT
+0 0 /Times-Roman FMFONTDEFINE
+1 0 /Times-Bold FMFONTDEFINE
+2 0 /Courier FMFONTDEFINE
+3 0 /Times-Italic FMFONTDEFINE
+32 FMFILLS
+0 0 FMFILL
+1 .1 FMFILL
+2 .3 FMFILL
+3 .5 FMFILL
+4 .7 FMFILL
+5 .9 FMFILL
+6 .97 FMFILL
+7 1 FMFILL
+8 <0f1e3c78f0e1c387> FMFILL
+9 <0f87c3e1f0783c1e> FMFILL
+10 <cccccccccccccccc> FMFILL
+11 <ffff0000ffff0000> FMFILL
+12 <8142241818244281> FMFILL
+13 <03060c183060c081> FMFILL
+14 <8040201008040201> FMFILL
+16 1 FMFILL
+17 .9 FMFILL
+18 .7 FMFILL
+19 .5 FMFILL
+20 .3 FMFILL
+21 .1 FMFILL
+22 0.03 FMFILL
+23 0 FMFILL
+24 <f0e1c3870f1e3c78> FMFILL
+25 <f0783c1e0f87c3e1> FMFILL
+26 <3333333333333333> FMFILL
+27 <0000ffff0000ffff> FMFILL
+28 <7ebddbe7e7dbbd7e> FMFILL
+29 <fcf9f3e7cf9f3f7e> FMFILL
+30 <7fbfdfeff7fbfdfe> FMFILL
+%%EndSetup
+%%Page: "11" 11
+%%BeginPaperSize: Letter
+%%EndPaperSize
+612 792 0 FMBEGINPAGE
+57.97 26.65 554.03 40.82 R
+7 X
+0 K
+V
+0 10 Q
+0 X
+(11) 301 34.15 T
+56.69 54.99 297.64 735.31 R
+7 X
+V
+1 12 Q
+0 X
+(7. Fr) 56.69 727.31 T
+(equently Asked Questions) 81.12 727.31 T
+0 F
+(7.1 Questions) 56.69 699.31 T
+(1. How do I encode a sequence that can be) 82.49 672.31 T
+(played by the Xing player?) 56.69 658.31 T
+(2. I\325m using the Parallax XV) 82.49 644.31 T
+(ideo card to) 220.34 644.31 T
+(digitize; how do I MPEG-encode the resulting) 56.69 630.31 T
+(data?) 56.69 616.31 T
+(3. How do I convert the MPEG-encoder) 82.49 602.31 T
+(YUV \336les into PPM \336les?) 56.69 588.31 T
+(7.2 Answers) 56.69 546.31 T
+-0.76 (1. The XING player samples video at 160x120 and) 56.69 518.31 P
+(expands to output 320x240. This is where their) 56.69 504.31 T
+(speed comes from. The player cannot buf) 56.69 490.31 T
+(fer a) 255.32 490.31 T
+(320x240 and thus had data overruns. The xing) 56.69 476.31 T
+(player would \325) 56.69 462.31 T
+(theoretically\325 handle 160x120 I) 126.42 462.31 T
+(frames.) 56.69 448.31 T
+(Thus, to encode, use P) 56.69 420.31 T
+(A) 163.52 420.31 T
+(TTERN I and 160x120) 170.85 420.31 T
+(frames.) 56.69 406.31 T
+(\050jboucher@\337ash.bu.edu\051) 56.69 392.31 T
+(2. Use the type JMOVIE, or use the jmovie2jpeg) 56.69 364.31 T
+(utility in the misc/ directory) 56.69 350.31 T
+(.) 189.84 350.31 T
+(3. Stanford\325) 56.69 322.31 T
+(s CVv1.2.2.tar) 113.33 322.31 T
+(.Z includes) 182.62 322.31 T
+(cyuv2ppm.c.) 56.69 308.31 T
+-0.2 (Which after you split the Y) 56.69 294.31 P
+-0.2 (, U, and V components) 185.05 294.31 P
+(out, works \336ne. \050curly@hsn.cftnet.com\051) 56.69 280.31 T
+(This can be ftp\325d from) 56.69 252.31 T
+2 F
+(havefun.stanford.edu) 56.69 238.31 T
+0 F
+(, in the directory) 200.61 238.31 T
+2 F
+(/) 282.89 238.31 T
+(pub/cv/) 56.69 224.31 T
+0 F
+(.) 107.07 224.31 T
+314.36 54.99 552.76 735.31 R
+7 X
+V
+FMENDPAGE
+%%EndPage: "11" 10
+%%Page: "10" 10
+612 792 0 FMBEGINPAGE
+57.97 26.65 554.03 40.82 R
+7 X
+0 K
+V
+0 10 Q
+0 X
+(10) 301 34.15 T
+56.69 54.99 297.64 735.31 R
+7 X
+V
+0 X
+(Usage:) 85.04 728.64 T
+2 F
+(ppmtoeyuv < input.ppm > output.yuv) 85.04 715.64 T
+0 F
+0.33 (This takes as input a ppm file and outputs a subsam-) 85.04 689.64 P
+(pled yuv file suitable for the encoder.) 56.69 678.64 T
+0 12 Q
+(6.2 jmovie2jpeg) 56.69 636.31 T
+0 10 Q
+(Usage:) 85.04 610.64 T
+2 F
+4.9 (jmovie2jpeg infile outfile start-) 85.04 597.64 P
+(frame end-frame) 56.69 586.64 T
+(infile) 85.04 560.64 T
+0 F
+( is a version 2 Parallax J_Movie) 121.02 560.64 T
+2 F
+(outfile) 85.04 547.64 T
+0 F
+( is a base file name for the output files) 127.02 547.64 T
+2 F
+5.44 (start-frame) 85.04 534.64 P
+0 F
+2.27 ( and) 151 534.64 P
+2 F
+5.44 (end-frame) 174.97 534.64 P
+0 F
+2.27 ( are the starting) 228.94 534.64 P
+(and ending frame numbers) 56.69 523.64 T
+1.39 (This takes as input a J_Movie and creates separate) 85.04 497.64 P
+0.09 (JFIF compatible JPEG files with the names base<num>.jpg,) 56.69 486.64 P
+(where base is outfile, and <num> are the frame numbers.) 56.69 475.64 T
+9.6 (jmovie2jpeg was written by Jim Boucher) 85.04 462.64 P
+(\050jboucher@\337ash.bu.edu\051.) 56.69 451.64 T
+0 12 Q
+(6.3 movieT) 56.69 424.31 T
+(oV) 111.15 424.31 T
+(id) 125.09 424.31 T
+0 10 Q
+(Usage:) 85.04 398.64 T
+2 F
+22.34 (movieToVid movieFile dataDir) 85.04 385.64 P
+(indexDir srcHostName) 56.69 374.64 T
+0 F
+0.95 (This program is used to convert a Parallax J Movie) 85.04 348.64 P
+0.58 (into a \322.vid\323 file, which is video only. vid files are used by) 56.69 337.64 P
+(some of the programs described later.) 56.69 326.64 T
+0.55 (See the README file in misc/mtv/ for more details) 85.04 313.64 P
+(on usage.) 56.69 302.64 T
+0.58 (movieToVid was written by Brian Smith \050bsmith@-) 85.04 289.64 P
+(cs.berkeley.edu\051) 56.69 278.64 T
+0 12 Q
+(6.4 eyuvtojpeg) 56.69 236.31 T
+0 10 Q
+(Usage:) 85.04 210.64 T
+2 F
+(eyuvtojpeg infile outfile) 85.04 197.64 T
+0 F
+0.27 (This takes as input an encoder yuv file and outputs a) 85.04 171.64 P
+(jpeg file. It uses cjpeg to do the compression.) 56.69 160.64 T
+0 12 Q
+(6.5 blockrun) 56.69 118.31 T
+0 10 Q
+(Usage:) 85.04 92.64 T
+2 F
+2.9 (blockrun command num_args firstnum) 85.04 79.64 P
+(lastnum skip arg1 ... argn) 56.69 68.64 T
+314.36 54.99 552.76 735.31 R
+7 X
+V
+0 F
+0 X
+7.53 (This runs the given command \050which has) 342.71 715.64 P
+2 F
+0.32 (num_args) 314.36 704.64 P
+0 F
+0.13 ( args\051, with the args) 362.34 704.64 P
+2 F
+0.32 (arg1 ... argn) 444.61 704.64 P
+0 F
+0.13 (, where) 523.21 704.64 P
+-0.04 (any \325=\325 character is replaced by a number from) 314.36 693.64 P
+2 F
+-0.09 (firstnum) 504.78 693.64 P
+0 F
+(to) 314.36 682.64 T
+2 F
+(lastnum) 324.64 682.64 T
+0 F
+( skipping by) 366.61 682.64 T
+2 F
+(skip) 418.54 682.64 T
+0 F
+(. For example:) 442.52 682.64 T
+2 F
+7.23 (blockrun eyuvtojpeg 2 13 19 3) 342.71 669.64 P
+(flow=.yuv flow=.jpg) 314.36 658.64 T
+0 F
+(will run:) 342.71 632.64 T
+2 F
+(eyuvtojpeg flow13.yuv flow13.jpg) 342.71 606.64 T
+(eyuvtojpeg flow16.yuv flow16.jpg) 342.71 593.64 T
+(eyuvtojpeg flow19.yuv flow19.jpg) 342.71 580.64 T
+0 12 Q
+(6.6 vidtoppm) 314.36 553.31 T
+0 10 Q
+(Usage:) 342.71 527.64 T
+2 F
+10.05 (vidtoppm filename width height) 342.71 514.64 P
+(start end outbase [quality]) 314.36 503.64 T
+0 F
+0.39 (This takes as input a .vid file of given) 342.71 477.64 P
+2 F
+0.94 (height) 499.45 477.64 P
+0 F
+0.39 ( and) 535.43 477.64 P
+2 F
+2.91 (width) 314.36 466.64 P
+0 F
+1.22 (, and turns them into a bunch of ppm files named) 344.35 466.64 P
+2 F
+(outbase) 314.36 455.64 T
+0 F
+(.N, where N is a number from) 356.34 455.64 T
+2 F
+(start) 478.74 455.64 T
+0 F
+( to) 508.73 455.64 T
+2 F
+(end) 521.5 455.64 T
+0 F
+(.) 539.49 455.64 T
+0 12 Q
+(6.7 vidtojpeg) 314.36 428.31 T
+0 10 Q
+(Usage:) 342.71 402.64 T
+2 F
+8.05 (vidtojpeg filename width height) 342.71 389.64 P
+(start end outbase [quality]) 314.36 378.64 T
+0 F
+2.83 (This is the same as vidtoppm, except it outputs) 342.71 352.64 P
+(JPEG files instead of PPM files.) 314.36 341.64 T
+0 12 Q
+(6.8 vidtoeyuv) 314.36 314.31 T
+0 10 Q
+(Usage:) 342.71 288.64 T
+2 F
+8.05 (vidtoeyuv filename width height) 342.71 275.64 P
+(start nth outbase [quality]) 314.36 264.64 T
+0 F
+0.39 (This takes as input a .vid file of given) 342.71 238.64 P
+2 F
+0.94 (height) 499.45 238.64 P
+0 F
+0.39 ( and) 535.43 238.64 P
+2 F
+3.58 (width) 314.36 227.64 P
+0 F
+1.49 (, and turns them into a bunch of yuv files named) 344.35 227.64 P
+2 F
+2.87 (outbase) 314.36 216.64 P
+0 F
+1.2 (.N, where N is a number from) 356.34 216.64 P
+2 F
+2.87 (start) 487.12 216.64 P
+0 F
+1.2 ( to) 517.1 216.64 P
+2 F
+2.87 (end) 532.27 216.64 P
+0 F
+1.2 (,) 550.26 216.64 P
+(skipping by) 314.36 205.64 T
+2 F
+(nth) 363.79 205.64 T
+0 F
+(.) 381.77 205.64 T
+0 12 Q
+(6.8 PBMPLUS) 314.36 176.31 T
+0 10 Q
+-0.04 (There is a very useful package called pbmplus avail-) 342.71 150.64 P
+1.46 (able for ftp \050ee.utah.edu:/pbmplus for example\051. This has) 314.36 139.64 P
+0.31 (conversions from TIFF, GIF, and many other common for-) 314.36 128.64 P
+0.86 (mats to PPMs, which the encoder can read. You can even) 314.36 117.64 P
+0.58 (keep the originals in their own format, and do conversions) 314.36 106.64 P
+(via) 314.36 95.64 T
+2 F
+(INPUT_CONVERT.) 329.07 95.64 T
+FMENDPAGE
+%%EndPage: "10" 9
+%%Page: "9" 9
+612 792 0 FMBEGINPAGE
+57.97 26.65 554.03 40.82 R
+7 X
+0 K
+V
+0 10 Q
+0 X
+(9) 303.5 34.15 T
+56.69 54.99 297.64 735.31 R
+7 X
+V
+0 X
+0.08 (ating input files must work on the remote machine. Also the) 56.69 728.64 P
+1.78 (USER_DATA and CDL_FILE files must be available on) 56.69 717.64 P
+2.53 (the remote sites as specified in the parameter file. This) 56.69 706.64 P
+(should be fixed in future versions.) 56.69 695.64 T
+1 12 Q
+(4. Performance) 56.69 668.31 T
+0 10 Q
+1.23 (Table seven shows a comparison of sequential per-) 85.04 641.64 P
+(formance on different machine types.) 56.69 630.64 T
+1.31 (Parallel performance is dependent not only on pro-) 85.04 422.64 P
+1.87 (cessor performance, but network performance. If you are) 56.69 411.64 P
+-0.21 (using a 10 Mb/s Ethernet, don\325t expect to get better than 4 or) 56.69 400.64 P
+0.89 (5 frames per second -- no matter how fast your processors) 56.69 389.64 P
+(are.) 56.69 378.64 T
+2.5 (Parallel performance is also greatly dependent on) 85.04 365.64 P
+1.16 (how big the input files are \050YUV is better than PPM, and) 56.69 354.64 P
+0.81 (JPEG is better than both\051, and how big the output files are) 56.69 343.64 P
+(\050better compression will lead to less I/O\051.) 56.69 332.64 T
+1 12 Q
+(5. Other Options) 56.69 305.31 T
+0 10 Q
+-0 (This section gives example of some more rarely used) 85.04 278.64 P
+0.12 (options in the parameter \336le, such as customizing the Quan-) 56.69 267.64 P
+(tization tables, or setting the aspect ratio.) 56.69 256.64 T
+0 12 Q
+(5.1 Custom Quantization T) 56.69 229.31 T
+(ables \050parameter \336le\051) 186.45 229.31 T
+0 10 Q
+(You can specify your own custom quantization tables.) 56.69 206.64 T
+-0.31 (Currently you can only do this once per MPEG file.) 56.69 194.64 P
+-0.31 ( Y) 260.99 194.64 P
+-0.31 (ou can) 269.39 194.64 P
+-0.06 (specify both Intra- and Non-intra quantization tables. If you) 56.69 182.64 P
+-0.44 (don\325) 56.69 170.64 P
+-0.44 (t specify them, then the default tables are used \050c.f. page) 74.83 170.64 P
+(D-16, D-17 of the standard\051.) 56.69 158.64 T
+(Usage:) 85.04 141.64 T
+2 F
+(IQTABLE) 85.04 115.64 T
+(table row 1) 85.04 102.64 T
+(table row 2) 85.04 89.64 T
+(...) 85.04 76.64 T
+(table row 8) 85.04 63.64 T
+100.63 478.31 253.7 484.31 C
+0 0 612 792 C
+0 10 Q
+0 X
+0 K
+0.06 (a. Macroblocks per second; a) 118.63 471.64 P
+1.5 (320x240 pixel image is 300) 118.63 459.64 P
+(macroblocks per frame.) 118.63 447.64 T
+1 12 Q
+(T) 99.59 594.31 T
+(able 7: Machine Comparison) 106.49 594.31 T
+0 10 Q
+(Machine) 121.41 572.64 T
+(MPS) 203.66 572.64 T
+0 8 Q
+(a) 223.66 576.64 T
+0 10 Q
+(HP 9000/755) 120.13 554.64 T
+(280) 207.94 554.64 T
+(DEC 3000/400) 112.92 538.64 T
+(247) 207.94 538.64 T
+(HP 9000/750) 120.13 522.64 T
+(191) 207.94 522.64 T
+(Sparc 10) 137.91 506.64 T
+(104) 207.94 506.64 T
+(DEC 5000) 130.69 490.64 T
+(68) 210.44 490.64 T
+100.63 584.06 100.63 484.56 2 L
+V
+0.5 H
+0 Z
+N
+177.16 584.56 177.16 484.06 2 L
+V
+N
+253.7 584.06 253.7 484.56 2 L
+V
+N
+100.38 584.31 253.95 584.31 2 L
+V
+N
+100.88 565.56 253.45 565.56 2 L
+V
+N
+100.88 563.06 253.45 563.06 2 L
+V
+N
+100.38 548.31 253.95 548.31 2 L
+V
+N
+100.38 532.31 253.95 532.31 2 L
+V
+N
+100.38 516.31 253.95 516.31 2 L
+V
+N
+100.38 500.31 253.95 500.31 2 L
+V
+N
+100.38 484.31 253.95 484.31 2 L
+V
+N
+314.36 54.99 552.76 735.31 R
+7 X
+V
+0 X
+0.96 (This specifies the intra-coding quantization table \050I) 342.71 715.64 P
+0.01 (frames and I-blocks in P and B frames\051. Each) 314.36 704.64 P
+2 F
+0.03 (table row) 498.76 704.64 P
+0 F
+(is simply 8 integers, separated by tabs and/or spaces.) 314.36 693.64 T
+(Usage:) 342.71 680.64 T
+2 F
+(NIQTABLE) 342.71 654.64 T
+(table row 1) 342.71 641.64 T
+(table row 2) 342.71 628.64 T
+(...) 342.71 615.64 T
+(table row 8) 342.71 602.64 T
+0 F
+4.63 (This specifies the non-intra-coding quantization) 342.71 576.64 P
+(table \050difference vectors in P and B frames\051.) 314.36 565.64 T
+0 12 Q
+(5.2 Aspect Ratio \050parameter \336le\051) 314.36 538.31 T
+0 10 Q
+(Y) 340.16 512.64 T
+(ou can specify the aspect ratio to be one of the) 346.37 512.64 T
+(fourteen legal values as speci\336ed in the standard \050c.f.) 314.36 500.64 T
+(Section 2.4.3.2\051. This sets the requested aspect ration for) 314.36 488.64 T
+(playback.) 314.36 476.64 T
+0 12 Q
+(Usage:) 340.16 463.31 T
+(ASPECT_RA) 340.16 449.31 T
+(TIO \337oat) 406.13 449.31 T
+2 10 Q
+8.03 (float) 342.71 436.64 P
+0 F
+3.34 ( is one of {1.0, 0.6735, 0.7031, 0.7615,) 372.69 436.64 P
+2.25 (0.8055, 0.8437, 0.8935, 0.9157, 0.9815, 1.0255, 1.0695,) 314.36 425.64 P
+(1.0950, 1.1575, 1.2015}.) 314.36 414.64 T
+0 12 Q
+(5.3 Frame Rate \050parameter \336le\051) 314.36 374.31 T
+0 10 Q
+-0 (You can specify the frame rate to be one of the eight) 342.71 348.64 P
+1.97 (legal values \050c.f. Section 2.4.3.2\051. This is used by some) 314.36 337.64 P
+(playback systems to gauge the playback rate.) 314.36 326.64 T
+(Usage:) 342.71 313.64 T
+2 F
+(FRAME_RATE float) 342.71 300.64 T
+4.91 (float) 342.71 287.64 P
+0 F
+2.05 ( is one of {23.976, 24, 25, 29.97, 30, 50,) 372.69 287.64 P
+(59.94, 60}.) 314.36 276.64 T
+0 12 Q
+(5.4 Floating Point DCT \050command line\051) 314.36 249.31 T
+0 10 Q
+2.13 (The encoder normally uses a quick algorithm for) 342.71 236.64 P
+2.06 (forward and reverse DCTs. However, in sequences with) 314.36 225.64 P
+0.5 (many P frames, this can result in errors when decoded ref-) 314.36 214.64 P
+0.4 (erence frames are used. To use the \050slow\051 double precision) 314.36 203.64 P
+(accurate dcts, use the following flag:) 314.36 192.64 T
+(Usage:) 342.71 179.64 T
+(mpeg_encode -float_dct) 342.71 166.64 T
+1 12 Q
+(6. Other T) 314.36 139.31 T
+(ools) 366.89 139.31 T
+0 10 Q
+(The misc/ directory contains several useful tools.) 314.36 112.64 T
+0 12 Q
+(6.1 ppmtoeyuv) 314.36 83.31 T
+FMENDPAGE
+%%EndPage: "9" 8
+%%Page: "8" 8
+612 792 0 FMBEGINPAGE
+57.97 26.65 554.03 40.82 R
+7 X
+0 K
+V
+0 10 Q
+0 X
+(8) 303.5 34.15 T
+56.69 54.99 297.64 735.31 R
+7 X
+V
+0 X
+(frame-to-I-frame scale.) 56.69 728.64 T
+(Usage:) 85.04 715.64 T
+2 F
+(-bit_rate_info rate_file) 85.04 702.64 T
+0 F
+(This puts the bit rate info into the specified file) 85.04 689.64 T
+(\050order of info, etc.\051) 85.04 676.64 T
+1 12 Q
+(3. Parallel Usage) 56.69 641.31 T
+0 10 Q
+1.03 (In parallel execution there are slave processes. You) 85.04 610.64 P
+(can have those processes run nicely if you want.) 56.69 599.64 T
+(Usage:) 85.04 586.64 T
+2 F
+(-nice) 85.04 573.64 T
+0 F
+3.56 (This makes all slave processes run nicely. This) 85.04 560.64 P
+0.8 (means that interactive users take precedence, so they don\325t) 56.69 549.64 P
+-0.2 (feel like they\325re running in molasses. If you want to be mean) 56.69 538.64 P
+(to them, don\325t use this option. :-\051) 56.69 527.64 T
+0 12 Q
+(3.1 Architecture Overview) 56.69 500.31 T
+0 10 Q
+1.3 (Figure 1 shows a diagram of the system architecture. The) 56.69 474.64 P
+-0.03 (slaves exist on the different slave machines which you spec-) 56.69 463.64 P
+1.5 (ify \050see Section 3.2\051. The server processes all live on the) 56.69 452.64 P
+(machine you run the encoder on.) 56.69 441.64 T
+0 12 Q
+(3.2 Specifying Slave Machines \050both\051) 56.69 412.31 T
+0 10 Q
+0.03 (You specify the slave machines in the parameter file.) 85.04 386.64 P
+-0.2 (For each slave you must specify the username to use, as well) 56.69 375.64 P
+0.06 (as the executable mpeg_encode program. If a slave does not) 56.69 364.64 P
+1.52 (have NFS access, then it is REMOTE and you must also) 56.69 353.64 P
+(specify where the parameter file is.) 56.69 342.64 T
+(Usage:) 85.04 316.64 T
+2 F
+(PARALLEL) 85.04 303.64 T
+(slave_specification) 85.04 290.64 T
+(END_PARALLEL) 85.04 277.64 T
+(slave_specification) 85.04 251.64 T
+0 F
+( can be either:) 198.98 251.64 T
+2 F
+(machine username executable) 85.04 238.64 T
+0 F
+(or) 85.04 225.64 T
+2 F
+2.9 (REMOTE machine username executable) 85.04 212.64 P
+(param_file) 56.69 201.64 T
+0 F
+0.76 (You must have an account with the given username) 85.04 175.64 P
+0.06 (on each machine, and you must place your machine/login in) 56.69 164.64 P
+(the appropriate .rhosts files.) 56.69 153.64 T
+1.59 (To make it easier to run experiments with varying) 85.04 127.64 P
+1.35 (numbers of processors, there is a command-line argument) 56.69 116.64 P
+(which limits the number of slave machines.) 56.69 105.64 T
+(Usage:) 85.04 79.64 T
+2 F
+(-max_machines num_machines) 85.04 66.64 T
+314.36 54.99 552.76 735.31 R
+7 X
+V
+0 F
+0 X
+0.98 (This means that the encoder will use no more than) 342.71 728.64 P
+2 F
+(num_machines) 314.36 717.64 T
+0 F
+( machines as slaves.) 386.32 717.64 T
+0 12 Q
+(3.3 Remote Shell \050parameter \336le\051) 314.36 677.31 T
+0 10 Q
+0.98 (To run processes on the slave machines, mpeg_en-) 342.71 651.64 P
+1.52 (code uses the remote shell command. On most machines) 314.36 640.64 P
+-0.09 (this is the command) 314.36 629.64 P
+2 F
+-0.2 (rsh) 396.76 629.64 P
+0 F
+-0.09 (. On HP machines, however, rsh is) 414.75 629.64 P
+0.39 (the restricted shell; on HP machines, the right command to) 314.36 618.64 P
+(use is) 314.36 607.64 T
+2 F
+(remsh) 339.35 607.64 T
+0 F
+(, rather than) 369.33 607.64 T
+2 F
+(rsh) 419.84 607.64 T
+0 F
+(.) 437.83 607.64 T
+(Usage:) 342.71 594.64 T
+2 F
+(RSH <rsh command>) 342.71 568.64 T
+0 12 Q
+(3.4 Scheduling Algorithms \050parameter \336le\051) 314.36 528.31 T
+0 10 Q
+0.87 (The encoder provides 3 different scheduling algorithms to) 314.36 502.64 P
+(schedule which processors get which frames.) 314.36 491.64 T
+2.03 (The first scheduling algorithm simply assigns N/P) 340.16 478.64 P
+0.28 (frames to each processor, where N is the number of frames) 314.36 467.64 P
+0.72 (and P is the number of processors. This has the advantage) 314.36 456.64 P
+-0.01 (of minimal overhead, but only works well when all the pro-) 314.36 445.64 P
+0.3 (cessors run at nearly the same speed. Also, since most pro-) 314.36 434.64 P
+0.37 (cessors will finish at about the same time, you will have to) 314.36 423.64 P
+1.41 (wait at the end while the Combine Server gathers all the) 314.36 412.64 P
+(frame files together.) 314.36 401.64 T
+(Usage:) 340.16 388.64 T
+(PARALLEL_PERFECT) 340.16 375.64 T
+3.6 (The second scheduling algorithm first assigns S) 340.16 349.64 P
+-0.09 (frames to each processor. When a processor is finished, it is) 314.36 338.64 P
+1.49 (assigned T seconds of work \050the scheduler estimates this) 314.36 327.64 P
+1.97 (based on previous performance\051. S should be at least 3,) 314.36 316.64 P
+0.37 (preferably at least 5 or 6, to insure a good estimate of each) 314.36 305.64 P
+(processor\325s speed.) 314.36 294.64 T
+(Usage:) 340.16 281.64 T
+(PARALLEL_TEST_FRAMES S) 340.16 268.64 T
+(PARALLEL_TIME_CHUNKS T) 340.16 255.64 T
+0.79 (The third scheduling algorithm is probably the best.) 340.16 229.64 P
+2.43 (It also first assigns S frames to each processor. Subse-) 314.36 218.64 P
+3.44 (quently, however, whenever a processor finishes, it is) 314.36 207.64 P
+1.1 (assigned enough work to keep it busy until almost every-) 314.36 196.64 P
+2.04 (thing is done. Effectively, a processor is assigned many) 314.36 185.64 P
+-0.06 (frames, and then fewer and fewer frames as more work gets) 314.36 174.64 P
+2.92 (done. This insures good load balancing, while limiting) 314.36 163.64 P
+(scheduling overhead.) 314.36 152.64 T
+(Usage:) 340.16 139.64 T
+(PARALLEL_TEST_FRAMES S) 340.16 126.64 T
+(PARALLEL_CHUNK_TAPER) 340.16 113.64 T
+0 12 Q
+(3.5 Parallel problems \050parameter \336le\051) 314.36 86.31 T
+0 10 Q
+8.16 (There are some unsupported features using) 340.16 73.64 P
+-0.18 (REMOTE to specify slaves: The \324command\324 form of gener-) 314.36 62.64 P
+FMENDPAGE
+%%EndPage: "8" 7
+%%Page: "7" 7
+612 792 0 FMBEGINPAGE
+57.97 26.65 554.03 40.82 R
+7 X
+0 K
+V
+0 10 Q
+0 X
+(7) 303.5 34.15 T
+56.69 54.99 297.64 477.36 R
+7 X
+V
+0 X
+(you can specify frame files to combine.) 56.69 470.69 T
+1.11 (The parameter file may specify input frame files in) 85.04 457.69 P
+0.94 (the same manner as normal input files -- except instead of) 56.69 446.69 P
+7.11 (using INPUT_DIR, INPUT, and END_INPUT, use) 56.69 435.69 P
+28.3 (FRAME_INPUT_DIR, FRAME_INPUT, and) 56.69 424.69 P
+1.83 (FRAME_END_INPUT. If no input frame files are speci-) 56.69 413.69 P
+0.16 (fied, then the default is to use the output file name with suf-) 56.69 402.69 P
+(fix \322.frame.<frame_num>\323 starting from 0 as the input files.) 56.69 391.69 T
+0 12 Q
+(2.15 Stats and Other Options \050command line\051) 56.69 364.36 T
+0 10 Q
+0.43 (There are several options for printing or suppressing) 85.04 338.69 P
+(useful information.) 56.69 327.69 T
+0.75 (The encoder always prints \050to stdout\051 parameter file) 85.04 314.69 P
+2.87 (information and statistics about how many I, P, and B) 56.69 303.69 P
+0.8 (frames there were, and information about compression and) 56.69 292.69 P
+2.31 (quality. You can send these statistics, in addition to the) 56.69 281.69 P
+(screen, to a file.) 56.69 270.69 T
+(Usage:) 85.04 257.69 T
+2 F
+( -stat stat_file) 110.83 244.69 T
+0 F
+2.56 (This appends the parameter file info and stats to) 85.04 231.69 P
+2 F
+(stat_file) 56.69 220.69 T
+0 F
+0.87 (Normally, the statistics do not include any informa-) 85.04 194.69 P
+1.77 (tion about quality. This is because computing the quality) 56.69 183.69 P
+2.02 (takes a little more time. If you wish to have the quality) 56.69 172.69 P
+1.17 (included in the statistics, use the -snr command line argu-) 56.69 161.69 P
+(ment.) 56.69 150.69 T
+(Usage:) 85.04 137.69 T
+2 F
+(-snr) 85.04 124.69 T
+0 F
+1.78 (This prints the signal-to-noise ratio \050snr\051 and peak) 85.04 111.69 P
+(snr.) 56.69 100.69 T
+3.69 (An additional statistic measure is mean squared) 85.04 74.69 P
+0.78 (error. If you wish to see the per-block mean squared error,) 56.69 63.69 P
+314.36 54.99 552.76 477.36 R
+7 X
+V
+0 X
+(use the -mse command line flag \050sets -snr as a side effect\051.) 314.36 470.69 T
+(Usage:) 342.71 457.69 T
+2 F
+(-mse) 342.71 444.69 T
+0 F
+(This prints the MSE for each block encoded) 342.71 431.69 T
+1.02 (Another set of data which can be useful is a histo-) 342.71 418.69 P
+0.44 (gram of the motion vectors. The encoder can keep track of) 314.36 407.69 P
+-0.16 (P-frame motion vectors and forward and backward B-frame) 314.36 396.69 P
+0.4 (motion vectors. The output is in the form of a matrix, each) 314.36 385.69 P
+0.98 (entry corresponding to a motion vector in the search win-) 314.36 374.69 P
+0.49 (dow. The center of the matrix represents \0500,0\051 motion vec-) 314.36 363.69 P
+(tors.) 314.36 352.69 T
+(Usage:) 342.71 339.69 T
+2 F
+(-mv_histogram) 342.71 326.69 T
+0 F
+1.47 (During normal execution, the encoder outputs two) 342.71 300.69 P
+0.58 (kinds of information. It prints a single line for each frame,) 314.36 289.69 P
+0.86 (summarizing block type and time info. It also prints, after) 314.36 278.69 P
+-0.07 (each frame, an estimate of the remaining running time. You) 314.36 267.69 P
+(can modify how often or if this information is to be shown.) 314.36 256.69 T
+(Usage:) 342.71 243.69 T
+2 F
+( -quiet num) 342.71 230.69 T
+( -no_frame_summary) 342.71 217.69 T
+(-realquiet) 349.8 204.69 T
+0 F
+-0.13 (If) 342.71 191.69 P
+2 F
+-0.31 (num) 351.74 191.69 P
+0 F
+-0.13 ( is negative, the time estimate is never shown;) 369.73 191.69 P
+1.94 (otherwise, it reports a time estimate no more often than) 314.36 180.69 P
+1.22 (every) 314.36 169.69 P
+2 F
+2.92 (num) 340.27 169.69 P
+0 F
+1.22 ( seconds \050unless the time estimate rises, which) 358.26 169.69 P
+1 (will happen near the beginning of the run\051. The default is) 314.36 158.69 P
+2 F
+(num) 314.36 147.69 T
+0 F
+( = 0, which means report after every frame.) 332.35 147.69 T
+1.08 (If) 342.71 134.69 P
+2 F
+2.59 (-no_frame_summary) 352.94 134.69 P
+0 F
+1.08 ( is given, then informa-) 454.89 134.69 P
+(tion about each frame is not printed.) 314.36 123.69 T
+2 F
+10.05 (-realquiet stops all printing,) 342.71 110.69 P
+(other than error messages.) 314.36 99.69 T
+0 F
+1 (Another nice feature is that the encoder can output) 342.71 73.69 P
+0.75 (the bit rate, on both a frame-to-frame scale, and also an I-) 314.36 62.69 P
+385.51 518.46 470.55 575.15 R
+7 X
+V
+0.5 H
+0 Z
+0 X
+N
+141.05 721.49 294.05 766.49 18 RR
+7 X
+V
+0 X
+N
+1 18 Q
+(Master Server) 165.05 738.43 T
+102.05 623.49 181.05 660.49 R
+7 X
+V
+0 X
+N
+(Slave) 121.05 637.49 T
+452.05 625.49 531.05 662.49 R
+7 X
+V
+0 X
+N
+(Slave) 471.05 639.49 T
+333.05 624.49 412.05 661.49 R
+7 X
+V
+0 X
+N
+(Slave) 352.05 638.49 T
+218.05 623.49 297.05 660.49 R
+7 X
+V
+0 X
+N
+(Slave) 237.05 637.49 T
+147.47 523.06 300.47 568.06 18 RR
+7 X
+V
+0 X
+N
+(Combine Server) 165.31 539.33 T
+498.64 673.25 501.04 661.49 492.77 670.19 495.71 671.72 4 Y
+V
+472.44 709.73 470.04 721.49 478.31 712.79 475.37 711.26 4 Y
+V
+495.71 671.72 475.38 711.26 2 L
+7 X
+V
+0 X
+N
+137.07 670.41 129.04 661.49 131.12 673.31 134.09 671.86 4 Y
+V
+149.02 710.57 157.04 719.49 154.97 707.67 151.99 709.12 4 Y
+V
+134.1 671.86 152 709.12 2 L
+7 X
+V
+0 X
+N
+164.04 661.55 152.04 661.49 162.18 667.9 163.11 664.73 4 Y
+V
+344.03 719.43 356.03 719.49 345.89 713.08 344.96 716.26 4 Y
+V
+163.12 664.72 344.98 716.25 2 L
+7 X
+V
+0 X
+N
+392.55 667.26 382.02 661.5 387.89 671.96 390.22 669.61 4 Y
+V
+430.5 714.73 441.02 720.49 435.15 710.03 432.82 712.38 4 Y
+V
+390.24 669.6 432.85 712.37 2 L
+7 X
+V
+0 X
+N
+277.89 664.38 266.04 662.49 275.09 670.37 276.49 667.37 4 Y
+V
+377.19 717.6 389.04 719.49 379.98 711.61 378.59 714.61 4 Y
+V
+276.5 667.37 378.6 714.61 2 L
+7 X
+V
+0 X
+N
+241.78 672.73 248.04 662.49 237.31 667.86 239.55 670.29 4 Y
+V
+192.3 709.25 186.04 719.49 196.78 714.12 194.54 711.69 4 Y
+V
+239.55 670.29 194.54 711.69 2 L
+7 X
+V
+0 X
+N
+353.94 670.32 363.03 662.49 351.17 664.31 352.56 667.32 4 Y
+V
+245.13 712.66 236.04 720.49 247.9 718.67 246.52 715.67 4 Y
+V
+352.57 667.31 246.52 715.66 2 L
+7 X
+V
+0 X
+N
+470.93 667.95 481.05 661.49 469.05 661.61 469.99 664.78 4 Y
+V
+287.16 714.03 277.05 720.49 289.05 720.37 288.1 717.2 4 Y
+V
+469.99 664.78 288.1 717.2 2 L
+7 X
+V
+0 X
+N
+336.05 720.49 489.05 765.49 18 RR
+7 X
+V
+0 X
+N
+(Decode Server) 360.05 737.43 T
+134.71 613.56 130.39 624.76 139.99 617.55 137.35 615.56 4 Y
+V
+168.59 579.26 172.91 568.07 163.31 575.27 165.95 577.27 4 Y
+V
+137.35 615.56 165.96 577.26 2 L
+7 X
+V
+0 X
+N
+234.18 617.55 243.77 624.76 239.45 613.56 236.82 615.56 4 Y
+V
+210.85 575.27 201.26 568.07 205.58 579.26 208.21 577.27 4 Y
+V
+236.82 615.56 208.22 577.26 2 L
+7 X
+V
+0 X
+N
+352.39 622.81 364.23 624.77 355.23 616.83 353.81 619.82 4 Y
+V
+255.6 570.03 243.76 568.08 252.77 576.01 254.18 573.02 4 Y
+V
+353.83 619.81 254.2 573.01 2 L
+7 X
+V
+0 X
+N
+465.63 624.68 477.63 624.76 467.5 618.33 466.56 621.51 4 Y
+V
+291.21 568.14 279.21 568.07 289.34 574.49 290.28 571.32 4 Y
+V
+466.57 621.51 290.28 571.31 2 L
+7 X
+V
+0 X
+N
+393.94 616.24 385.49 624.76 397.17 622.01 395.55 619.12 4 Y
+V
+440.83 597.86 449.27 589.33 437.59 592.09 439.21 594.98 4 Y
+V
+395.57 619.11 439.23 594.97 2 L
+7 X
+V
+0 X
+N
+281.34 617.08 272.12 624.76 284.01 623.14 282.67 620.11 4 Y
+V
+376.28 582.84 385.5 575.16 373.61 576.78 374.94 579.81 4 Y
+V
+282.68 620.1 374.96 579.8 2 L
+7 X
+V
+0 X
+N
+168.9 618.38 158.74 624.76 170.74 624.73 169.82 621.56 4 Y
+V
+375.34 567.36 385.51 560.98 373.51 561 374.43 564.18 4 Y
+V
+169.82 621.55 374.43 564.18 2 L
+7 X
+V
+0 X
+N
+312.01 543.5 300.47 546.8 312.01 550.11 312.01 546.8 4 Y
+V
+373.98 550.11 385.51 546.8 373.98 543.5 373.98 546.8 4 Y
+V
+312.01 546.8 373.98 546.8 2 L
+7 X
+V
+0 X
+N
+302.9 498.61 259.93 498.61 2 L
+V
+1.14 H
+N
+1 12 Q
+(Figure 1) 259.93 499.8 T
+385.51 498.49 302.9 498.49 2 L
+V
+0.59 H
+N
+0 F
+(: Network Model) 302.9 499.8 T
+495.43 619 505.95 624.77 500.08 614.3 497.76 616.65 4 Y
+V
+473.96 588.02 463.44 582.25 469.3 592.72 471.63 590.37 4 Y
+V
+497.79 616.64 471.66 590.35 2 L
+7 X
+V
+0.5 H
+0 X
+N
+1 18 Q
+(Disk) 410.52 530.49 T
+7 X
+90 450 42.52 14.17 428.03 575.15 G
+0 X
+90 450 42.52 14.17 428.03 575.15 A
+7 X
+180 270 42.94 21.26 428.45 521.28 G
+2 Z
+0 X
+180 270 42.94 21.26 428.45 521.28 A
+7 X
+270 360 42.94 21.26 427.68 521.61 G
+0 X
+270 360 42.94 21.26 427.68 521.61 A
+FMENDPAGE
+%%EndPage: "7" 6
+%%Page: "6" 6
+612 792 0 FMBEGINPAGE
+57.97 26.65 554.03 40.82 R
+7 X
+0 K
+V
+0 10 Q
+0 X
+(6) 303.5 34.15 T
+56.69 54.99 297.64 735.31 R
+7 X
+V
+0 X
+3.03 (number\051. This field entry can be changed by adding a) 56.69 728.64 P
+1.27 (USER_DATA parameter whose vale is the name of a file) 56.69 717.64 P
+(continuing the data to add to the header.) 56.69 706.64 T
+(Usage:) 85.04 693.64 T
+(USER_DATA ./user_data.txt) 85.04 667.64 T
+0 12 Q
+(2.1) 56.69 640.31 T
+(1 Compression Decision List \050CDL, also) 71.24 640.31 T
+(Speci\336cs File \050parameter \336le\051\051) 56.69 626.31 T
+0 10 Q
+0.18 (If you want to be very exact in what is set during the) 85.04 599.64 P
+1.71 (encoding, use CDL_FILE \050the older SPECIFICS_FILE is) 56.69 588.64 P
+0.18 (supported as well\051 to point to a file describing the exact set-) 56.69 577.64 P
+0.22 (tings wished for the encoding. The version 1.0 of CDL sup-) 56.69 566.64 P
+(port has the following format:) 56.69 555.64 T
+(version 1) 85.04 542.64 T
+(frame FN T Q) 85.04 529.64 T
+(slice SN Q) 85.04 516.64 T
+(block BN Q | BN Q skip | BN Q bi fx fy bx by |) 85.04 503.64 T
+(BN Q forw fx fy | BN Q back bx by) 109.7 490.64 T
+3.51 (FN, SN, and BN signal which frame/slice/block) 85.04 477.64 P
+1.64 (number the command applies to. Note that if you have a) 56.69 466.64 P
+2.22 (block or slice command, must be proceeded by a frame) 56.69 455.64 P
+0.63 (command for that frame. T sets the type of the frame \050I, P,) 56.69 444.64 P
+-0.06 (B, or - to not set\051. Q sets the q-scale \0501-31 or +N -N for rela-) 56.69 433.64 P
+0.17 (tive scaling, or 0 for no change\051. The detailed block specifi-) 56.69 422.64 P
+2.81 (cations set the motion vectors \050in half-pixel units\051. See) 56.69 411.64 P
+(specifications.c for more information.) 56.69 400.64 T
+2.25 (Version 2 CDL files have relative Qscales, so \3222) 85.04 387.64 P
+1.89 (\322means decrease the Qscale by 2, \3222\323 means increase it.) 56.69 376.64 P
+(Unsigned numbers like \3224\323 set the Qscale \050to 4\051.) 56.69 365.64 T
+(Usage:) 85.04 352.64 T
+(CDL_FILE filename) 85.04 326.64 T
+(CDL_DEFINES string) 85.04 313.64 T
+3 (where filename contains the specifics, and string) 85.04 300.64 P
+0.61 (\050optional\051 are defines to be passed to the C preprocessor to) 56.69 289.64 P
+(use on the file \050-Db=block for example\051.) 56.69 278.64 T
+0 12 Q
+(2.12 Gamma Correction \050parameter \336le\051) 56.69 251.31 T
+0 10 Q
+-0.13 (If your movies are too light or too dark for your play-) 85.04 224.64 P
+(back system, you can pre-gamma correct them.) 56.69 213.64 T
+(Usage:) 85.04 200.64 T
+2 F
+(GAMMA gamma-val) 85.04 174.64 T
+0 F
+1.5 (gamma-corrects by raising each luminance fraction) 85.04 161.64 P
+(to the power) 56.69 150.64 T
+2 F
+(gamma-val) 109.15 150.64 T
+0 F
+(\050a float\051) 169.12 150.64 T
+0.18 (This works by converting the luminance \050brightness\051) 85.04 137.64 P
+-0.17 (of the input image to a fraction zero to one, and then raises it) 56.69 126.64 P
+-0.17 (to the power) 56.69 115.64 P
+2 F
+-0.4 (gamma-val) 108.65 115.64 P
+0 F
+-0.17 (. Thus values less than 1 brighten,) 162.62 115.64 P
+2.36 (and greater than 1 dim. If your output device has good) 56.69 104.64 P
+0.92 (brightness controls, it is better to control brightness at that) 56.69 93.64 P
+(end.) 56.69 82.64 T
+314.36 54.99 552.76 735.31 R
+7 X
+V
+0 12 Q
+0 X
+(2.13 Encoding GOPs at a T) 314.36 727.31 T
+(ime \050command line\051) 445.86 727.31 T
+0 10 Q
+2.65 (Instead of encoding an entire sequence, you can) 342.71 700.64 P
+1.57 (encode a single GOP. GOPs can later be joined together) 314.36 689.64 P
+(with the encoder to form an MPEG file.) 314.36 678.64 T
+(Usage:) 342.71 665.64 T
+2 F
+(-gop num) 342.71 652.64 T
+0 F
+1.81 (This only encodes the numbered GOP \050which are) 342.71 639.64 P
+(numbered beginning at 0.) 314.36 628.64 T
+1.2 (The output file will be the normal output filename) 342.71 615.64 P
+(with the suffix \322.gop.<gop_num>\323) 314.36 604.64 T
+1.18 (GOP files can be joined at any time using the fol-) 342.71 578.64 P
+(lowing command-line argument.) 314.36 567.64 T
+(Usage:) 342.71 554.64 T
+2 F
+(-combine_gops) 342.71 541.64 T
+0 F
+2.05 (This causes the encoder to simply combine some) 342.71 528.64 P
+0.52 (GOP files into a single MPEG stream. A sequence header/) 314.36 517.64 P
+0.03 (ender are inserted. In this case, the parameter file need only) 314.36 506.64 P
+0.18 (contain the YUV_SIZE value, an output file, and perhaps a) 314.36 495.64 P
+0.21 (list of input GOP files. If no list of input GOP files is used,) 314.36 484.64 P
+0.45 (then the encoder assumes you\325re using the same parameter) 314.36 473.64 P
+-0.08 (file you used with the) 314.36 462.64 P
+2 F
+-0.19 (-gop) 403.08 462.64 P
+0 F
+-0.08 (option, and calculates the cor-) 432.88 462.64 P
+0.52 (responding gop filenames itself. If this is not the case, you) 314.36 451.64 P
+0.58 (can specify input GOP files in the same manner as normal) 314.36 440.64 P
+0.44 (input files -- except instead of using INPUT_DIR, INPUT,) 314.36 429.64 P
+1.79 (and END_INPUT, use GOP_INPUT_DIR, GOP_INPUT,) 314.36 418.64 P
+1.38 (and GOP_END_INPUT. If no input GOP files are speci-) 314.36 407.64 P
+-0.05 (fied, then the default is to use the output file name with suf-) 314.36 396.64 P
+(fix \322.gop.<gop_num>\323 starting from 0 as the input files.) 314.36 385.64 T
+4.1 (Thus, to summarize, unless you\325re mixing and) 342.71 372.64 P
+0.2 (matching GOP files from different sources, you can simply) 314.36 361.64 P
+1.37 (use the same parameter file for the) 314.36 350.64 P
+2 F
+3.28 (-gop) 464.63 350.64 P
+0 F
+1.37 ( and) 488.62 350.64 P
+2 F
+3.28 (-combi-) 510.78 350.64 P
+(ne_gops) 314.36 339.64 T
+0 F
+( options.) 356.34 339.64 T
+0 12 Q
+(2.14 Encoding Frames at a T) 314.36 299.31 T
+(ime \050command line\051) 452.51 299.31 T
+0 10 Q
+2.65 (Instead of encoding an entire sequence, you can) 342.71 273.64 P
+0.34 (encode individual frames. These frames can later be joined) 314.36 262.64 P
+(together to form an MPEG file.) 314.36 251.64 T
+(Usage:) 342.71 238.64 T
+(-frames first_frame last_frame) 342.71 225.64 T
+2.37 (This causes the encoder to encode the numbered) 342.71 212.64 P
+(frames in the given range, inclusive.) 314.36 201.64 T
+0.89 (The output will be placed in separate files, one per) 342.71 188.64 P
+0.21 (frame, with the filenames being the normal output file with) 314.36 177.64 P
+(the suffix \322.frame.<frame num>\323) 314.36 166.64 T
+(The frame files can later be combined as follows:) 342.71 140.64 T
+(Usage:) 342.71 127.64 T
+(-combine_frames) 342.71 114.64 T
+2.05 (This causes the encoder to simply combine some) 342.71 101.64 P
+2.21 (frames into a single MPEG stream. Sequence and GOP) 314.36 90.64 P
+1.11 (headers are inserted appropriately. You can either use the) 314.36 79.64 P
+1.39 (same parameter file for -frames and -combine_frames, or) 314.36 68.64 P
+FMENDPAGE
+%%EndPage: "6" 5
+%%Page: "5" 5
+612 792 0 FMBEGINPAGE
+57.97 26.65 554.03 40.82 R
+7 X
+0 K
+V
+0 10 Q
+0 X
+(5) 303.5 34.15 T
+72 72 297 732.47 R
+7 X
+V
+2 F
+0 X
+(sflowg.07.yuv) 100.35 725.81 T
+(sflowg.08.yuv) 100.35 712.81 T
+(sflowg.09.yuv) 100.35 699.81 T
+(sflowg.10.yuv) 100.35 686.81 T
+0 F
+1.37 (If there is no star, then the file name is simple) 100.35 660.81 P
+0.99 (repeated the appropriate number of times \050[1-10] is 10) 72 649.81 P
+(times\051.) 72 638.81 T
+-0.18 (Commands can be used to dynamically create the) 100.35 625.81 P
+(list of files, for example:) 72 614.81 T
+2 F
+(INPUT) 100.35 588.81 T
+(\324ls July-*.ppm\324) 100.35 575.81 T
+(\324cat file-list\324) 100.35 562.81 T
+(END_INPUT) 100.35 549.81 T
+0 F
+1.41 (The command\050s\051 will be executed in the direc-) 100.35 523.81 P
+0.44 (tory named by INPUT_DIR if it appears before INPUT) 72 512.81 P
+2.09 (in the parameter file. Note that the encoder-provided) 72 501.81 P
+(filling in of *\325s is not supported in this mode.) 72 490.81 T
+0.36 (The encoder allows you to use other file formats) 100.35 477.81 P
+1.61 (by providing an input conversion specifier. You must) 72 466.81 P
+0.18 (describe how to convert the input format into one of the) 72 455.81 P
+(base file types.) 72 444.81 T
+2 F
+(Usage:) 100.35 431.81 T
+(INPUT_CONVERT conversion) 100.35 405.81 T
+3.25 (conversion) 100.35 379.81 P
+0 F
+1.35 ( must be a multi-star expression.) 160.31 379.81 P
+-0.16 (If) 72 368.81 P
+2 F
+-0.37 (conversion) 81 368.81 P
+0 F
+-0.16 ( is simply \324*\325, then no conversion takes) 140.97 368.81 P
+0.92 (place. Otherwise, each of the file lines are replaced by) 72 357.81 P
+0.3 (the conversion line with the file name wherever there is) 72 346.81 P
+0.88 (a \324*\325. The conversion line must send the output to std-) 72 335.81 P
+-0.12 (out. For example, suppose we have a bunch of GIF files.) 72 324.81 P
+(Then we would do:) 72 313.81 T
+2 F
+(BASE_FILE_FORMAT) 100.35 300.81 T
+( PPM) 196.29 300.81 T
+(INPUT) 100.35 287.81 T
+(pictures.*.gif [0-10]) 100.35 274.81 T
+(END_INPUT) 100.35 261.81 T
+(INPUT_CONVERT giftoppm *) 100.35 248.81 T
+0 F
+0.9 (Another example: Suppose we have separate Y,) 100.35 222.81 P
+1.18 (U, and V files \050where the U and V have already been) 72 211.81 P
+(subsampled\051. Then we might have:) 72 200.81 T
+2 F
+(BASE_FILE_FORMAT) 100.35 187.81 T
+( YUV) 196.29 187.81 T
+(INPUT) 100.35 174.81 T
+(pictures.* [0-10]) 100.35 161.81 T
+(END_INPUT) 100.35 148.81 T
+(INPUT_CONVERT cat *.Y *.U *.V) 100.35 135.81 T
+(YUV_FORMAT UCB) 100.35 122.81 T
+0 F
+0.68 (As you can see, the \322files\323 between) 100.35 109.81 P
+2 F
+1.63 (INPUT) 249.41 109.81 P
+0 F
+0.68 ( and) 279.39 109.81 P
+2 F
+2.41 (END_INPUT) 72 98.81 P
+0 F
+1 ( don\325t have to be files at all! This can be) 125.97 98.81 P
+(very useful.) 72 87.81 T
+315 72 540 732.47 R
+7 X
+V
+0 X
+(To read data from standard input, set:) 343.35 712.81 T
+2 F
+(INPUT_DIR stdin) 343.35 699.81 T
+0 F
+1.05 (Note that you cannot use the stdin option when) 343.35 686.81 P
+0.07 (coding in parallel. \050Use GOPINPUTDIR or FRAMEIN-) 315 675.81 P
+(PUTDIR if combining frames/GOPs.\051) 315 664.81 T
+(The output file is specified by:) 343.35 638.81 T
+2 F
+(OUTPUT filename) 343.35 625.81 T
+0 F
+(for example:) 343.35 612.81 T
+2 F
+(OUTPUT /u/keving/mpg/flowers.mpg) 343.35 599.81 T
+0 12 Q
+(2.8 Original or Decoded \050parameter \336le\051) 315 559.47 T
+0 10 Q
+0.09 (The encoder can use either the original frames as) 343.35 532.81 P
+3 (reference frames, or the decoded frames. Using the) 315 521.81 P
+2.48 (decoded frames gives better playback quality, but is) 315 510.81 P
+1.93 (slower and seems to give worse compression. It also) 315 499.81 P
+0.63 (causes some complications with parallel encoding. \050see) 315 488.81 P
+0.95 (the section on parallel encoding\051 One recommendation) 315 477.81 P
+0.52 (is to use original, and lower the q-scale if the quality is) 315 466.81 P
+(not good enough. Table six shows the trade-offs.) 315 455.81 T
+(Usage:) 343.35 429.81 T
+2 F
+(REFERENCE_FRAME ORIGINAL) 343.35 403.81 T
+0 12 Q
+(2.9 Bit-rate Control \050parameter \336le\051) 315 272.47 T
+0 10 Q
+1.53 (The default encoding uses variable bit rate. To) 343.35 245.81 P
+0.33 (limit the bit rate, the MPEG-2 Standard\325s algorithm has) 315 234.81 P
+2.39 (been implemented \050suitably adjusted\051. There are two) 315 223.81 P
+(parameters which must be set to use bit-rate control:) 315 212.81 T
+2 F
+(BUFFER_SIZE N \050in bits\051) 343.35 199.81 T
+(BIT_RATE M \050in bytes/sec\051) 343.35 186.81 T
+0 F
+-0.09 (N sets the largest required buffer, M specifies the) 343.35 173.81 P
+1 (continual rate. N is set in number of bits, the buffer is) 315 162.81 P
+(actually in 16bit ints.) 315 151.81 T
+0 12 Q
+(2.10 Userdata \050parameter \336le\051) 315 124.47 T
+0 10 Q
+0.01 (An identification string is added by default to the) 343.35 97.81 P
+2.96 (Sequence layer user-data field. It is \322UCB Encoder) 315 86.81 P
+1.41 (Vers\323 \050where Vers is replaced by the encoder version) 315 75.81 P
+1 12 Q
+-0.15 (T) 315 380.47 P
+-0.15 (able 6: Original or Decoded? \050Normalized\051) 321.9 380.47 P
+0 10 Q
+(Reference) 323.62 352.81 T
+(Compress) 373.49 358.81 T
+(ion) 387.1 346.81 T
+(Speed) 429.46 352.81 T
+(Quality) 486.21 358.81 T
+(I/P/B) 490.65 346.81 T
+(Decoded) 329.87 328.81 T
+(1000) 383.49 328.81 T
+(1000) 431.68 328.81 T
+(1000/969/919) 473.44 328.81 T
+(Original) 332.08 312.81 T
+(885) 385.99 312.81 T
+(1373) 431.68 312.81 T
+(1000/912/884) 473.44 312.81 T
+318.37 370.22 318.37 306.72 2 L
+V
+0.5 H
+0 Z
+N
+369.39 370.72 369.39 306.22 2 L
+V
+N
+417.58 370.72 417.58 306.22 2 L
+V
+N
+465.77 370.72 465.77 306.22 2 L
+V
+N
+536.63 370.22 536.63 306.72 2 L
+V
+N
+318.12 370.47 536.88 370.47 2 L
+V
+N
+318.62 339.72 536.38 339.72 2 L
+V
+N
+318.62 337.22 536.38 337.22 2 L
+V
+N
+318.12 322.47 536.88 322.47 2 L
+V
+N
+318.12 306.47 536.88 306.47 2 L
+V
+N
+FMENDPAGE
+%%EndPage: "5" 4
+%%Page: "4" 4
+612 792 0 FMBEGINPAGE
+57.97 26.65 554.03 40.82 R
+7 X
+0 K
+V
+0 10 Q
+0 X
+(4) 303.5 34.15 T
+56.69 54.99 297.64 735.31 R
+7 X
+V
+0 X
+(A standard sequence is IBBPBBPBBPBBPB) 85.04 715.64 T
+(Usage:) 85.04 702.64 T
+2 F
+(PATTERN) 85.04 676.64 T
+( <IPB pattern>) 127.02 676.64 T
+0 F
+1.84 (Note that if the last frame in an encoding is a B-) 85.04 650.64 P
+0.07 (frame, it will not be encoded \050since it has no future frame to) 56.69 639.64 P
+1.81 (reference from\051. Pre-I patters like BBIBBP are legal, but) 56.69 628.64 P
+0 (seem to have bugs, so watch out! To insure that every frame) 56.69 617.64 P
+0.63 (is encoded, the encoder can force the last frame to be an I-) 56.69 606.64 P
+(frame.) 56.69 595.64 T
+(Usage:) 85.04 582.64 T
+2 F
+(FORCE_ENCODE_LAST_FRAME) 85.04 556.64 T
+0 12 Q
+(2.7 Specifying Input Files \050parameter \336le\051) 56.69 516.31 T
+0 10 Q
+0.03 (The encoder can accept five base types of input files:) 85.04 489.64 P
+0.08 (PPM, PNM, JMOVIE, JPEG, and YUV. Note that PPM is a) 56.69 478.64 P
+1.38 (subset of PNM; the PPM option is available because it is) 56.69 467.64 P
+0.49 (faster to read if the files are known to be PPM. JMOVIE is) 56.69 456.64 P
+0.19 (the format created by the Parallax video grabber. JPEGs are) 56.69 445.64 P
+-0.06 (a standard image format. YUV formats are described below.) 56.69 434.64 P
+0.98 (If you use YUV format, you must specify the pixel) 85.04 421.64 P
+0.61 (size of the image in the parameter file and the YUV_FOR-) 56.69 410.64 P
+(MAT.) 56.69 399.64 T
+(Usage:) 85.04 386.64 T
+2 F
+(BASE_FILE_FORMAT format) 85.04 360.64 T
+(YUV_SIZE widthxheight) 85.04 347.64 T
+(YUV_FORMAT yuv_format) 85.04 334.64 T
+0.1 (format) 85.04 308.64 P
+0 F
+0.04 ( is one of {) 121.02 308.64 P
+2 F
+0.1 (YUV, PPM, PNM, JMOVIE,) 165.4 308.64 P
+(JPEG) 56.69 297.64 T
+0 F
+(}) 80.68 297.64 T
+2 F
+(width) 85.04 284.64 T
+0 F
+( and) 115.02 284.64 T
+2 F
+(height) 134.45 284.64 T
+0 F
+( are integers \050like 320x240\051) 170.43 284.64 T
+2 F
+5.81 (yuv_format) 85.04 271.64 P
+0 F
+2.42 (is one of) 156.81 271.64 P
+2 F
+5.81 ( {ABEKAS, EYUV,) 196.07 271.64 P
+1.66 (PHILLIPS, UCB, {SPECIAL}},) 56.69 260.64 P
+0 F
+0.69 (where) 223.59 260.64 P
+2 F
+1.66 ( SPECIAL) 248 260.64 P
+0 F
+1.83 (is a specification of the pattern of Y, U, and V, such as) 56.69 249.64 P
+2 F
+-0.57 (UYVY) 56.69 238.64 P
+0 F
+-0.24 (for) 86.1 238.64 P
+2 F
+-0.57 ( ABEKAS. The pattern can be of any) 97.76 238.64 P
+1.18 (length, or order, but must consist only) 56.69 227.64 P
+0.15 (of Ys, Us, andVs, and must represent two) 56.69 216.64 P
+3.18 (pixels of data \050thus YUVYUV for 4:4:4) 56.69 205.64 P
+(source\051.) 56.69 194.64 T
+0 F
+1.73 (You must specify the directory in which the input) 85.04 168.64 P
+-0.12 (files are located. You can use \324.\325 to specify the current direc-) 56.69 157.64 P
+(tory.) 56.69 146.64 T
+(Usage:) 85.04 133.64 T
+(INPUT_DIR directory) 85.04 107.64 T
+1.08 (You must also specify the names of the files them-) 85.04 81.64 P
+1.11 (selves. You list them sequentially, one per line, in display) 56.69 70.64 P
+2.12 (order. There are shortcuts, however, which allow you to) 56.69 59.64 P
+314.36 54.99 552.76 735.31 R
+7 X
+V
+0 X
+(condense many files into one line.) 314.36 728.64 T
+(Usage:) 342.71 715.64 T
+2 F
+(INPUT) 342.71 689.64 T
+(file) 342.71 676.64 T
+2 8 Q
+(1) 366.7 674.14 T
+2 10 Q
+(file) 342.71 663.64 T
+2 8 Q
+(2) 366.7 661.14 T
+2 10 Q
+(...) 342.71 650.64 T
+(file) 342.71 637.64 T
+2 8 Q
+(n) 366.7 635.14 T
+2 10 Q
+(END_INPUT) 342.71 624.64 T
+8.17 (file) 342.71 598.64 P
+2 8 Q
+6.53 (i) 366.7 596.14 P
+0 10 Q
+3.4 ( can be either a file name, a single-star) 371.49 598.64 P
+0.28 (expression followed by a bracketed expansion for star, or a) 314.36 587.64 P
+0.28 (command to be executed. There are two types of bracketed) 314.36 576.64 P
+(expansions. For example:) 314.36 565.64 T
+2 F
+(sflowg.*.yuv [0-10]) 342.71 539.64 T
+0 F
+(is expanded to:) 342.71 513.64 T
+2 F
+(sflowg.0.yuv) 342.71 487.64 T
+(sflowg.1.yuv) 342.71 474.64 T
+(sflowg.2.yuv) 342.71 461.64 T
+(sflowg.3.yuv) 342.71 448.64 T
+(sflowg.4.yuv) 342.71 435.64 T
+(sflowg.5.yuv) 342.71 422.64 T
+(sflowg.6.yuv) 342.71 409.64 T
+(sflowg.7.yuv) 342.71 396.64 T
+(sflowg.8.yuv) 342.71 383.64 T
+(sflowg.9.yuv) 342.71 370.64 T
+(sflowg.10.yuv) 342.71 357.64 T
+(sflowg.*.yuv [0-10+3]) 342.71 331.64 T
+0 F
+(is expanded to:) 342.71 305.64 T
+2 F
+(sflowg.0.yuv) 342.71 279.64 T
+(sflowg.3.yuv) 342.71 266.64 T
+(sflowg.6.yuv) 342.71 253.64 T
+(sflowg.9.yuv) 342.71 240.64 T
+0 F
+(Also, the encoder will pad with 0\325s if necessary:) 342.71 214.64 T
+2 F
+(sflowg.*.yuv [00-10]) 342.71 188.64 T
+0 F
+(is expanded to:) 342.71 162.64 T
+2 F
+(sflowg.00.yuv) 342.71 136.64 T
+(sflowg.01.yuv) 342.71 123.64 T
+(sflowg.02.yuv) 342.71 110.64 T
+(sflowg.03.yuv) 342.71 97.64 T
+(sflowg.04.yuv) 342.71 84.64 T
+(sflowg.05.yuv) 342.71 71.64 T
+(sflowg.06.yuv) 342.71 58.64 T
+FMENDPAGE
+%%EndPage: "4" 3
+%%Page: "3" 3
+612 792 0 FMBEGINPAGE
+57.97 26.65 554.03 40.82 R
+7 X
+0 K
+V
+0 10 Q
+0 X
+(3) 303.5 34.15 T
+56.69 54.99 297.64 735.31 R
+7 X
+V
+0 X
+1.31 (For some reason Simple seems to give better com-) 85.04 616.64 P
+(pression, but it depends on the image sequence.) 56.69 605.64 T
+(Usage:) 85.04 579.64 T
+2 F
+(PSEARCH_ALG ptechnique) 85.04 553.64 T
+(BSEARCH_ALG btechnique) 85.04 540.64 T
+0 F
+0.87 (where) 85.04 514.64 P
+2 F
+2.08 (ptechnique) 112.82 514.64 P
+0 F
+0.87 (is one of {LOGARITHMIC,) 180.87 514.64 P
+(SUBSAMPLE, TWOLEVEL, EXHAUSTIVE}) 56.69 503.64 T
+3.14 (where) 85.04 490.64 P
+2 F
+7.54 (btechnique) 115.1 490.64 P
+0 F
+3.14 ( is one of {EXHAUSTIVE,) 175.07 490.64 P
+(CROSS2, SIMPLE}) 56.69 479.64 T
+0 12 Q
+(2.3 GOP \050parameter \336le\051) 56.69 439.31 T
+0 10 Q
+1.94 (A Group of Pictures \050GOP\051 is a roughly indepen-) 85.04 412.64 P
+3.02 (dently decodable sequence of frames. An MPEG video) 56.69 401.64 P
+0 (stream is made of one or more GOPs. You may specify how) 56.69 390.64 P
+0.8 (many frames each GOP should be. A GOP must start with) 56.69 379.64 P
+0.2 (an I-frame, and the encoder will enforce that by taking your) 56.69 368.64 P
+(number as the) 56.69 357.64 T
+3 F
+(minimum) 115.26 357.64 T
+0 F
+( number of frames in a GOP.) 152.46 357.64 T
+(Usage:) 85.04 331.64 T
+2 F
+(GOP_SIZE) 85.04 305.64 T
+( num) 133.01 305.64 T
+0 F
+(where) 85.04 292.64 T
+2 F
+(num) 111.96 292.64 T
+0 F
+( = the number of frames in a GOP) 129.95 292.64 T
+0 12 Q
+(2.4 Slice \050parameter \336le\051) 56.69 252.31 T
+0 10 Q
+-0.25 (A slice is an independently decodable unit in a frame.) 85.04 226.64 P
+0.54 (It can be as small as one macroblock, or it can be as big as) 56.69 215.64 P
+1.47 (the entire frame. Barring transmission error, adding slices) 56.69 204.64 P
+0.55 (does not change quality or speed; the only effect is slightly) 56.69 193.64 P
+-0.1 (worse compression. More slices are used for noisy transmis-) 56.69 182.64 P
+2.81 (sion so that errors are more recoverable. Because most) 56.69 171.64 P
+0.28 (transmission systems have more sophisticated error correct-) 56.69 160.64 P
+(ing routines, we usually just use one slice per frame.) 56.69 149.64 T
+(Usage:) 85.04 123.64 T
+2 F
+(SLICES_PER_FRAME) 85.04 97.64 T
+( num) 180.99 97.64 T
+0 F
+(where) 85.04 84.64 T
+2 F
+(num) 111.96 84.64 T
+0 F
+( is the number of slices in a frame) 129.95 84.64 T
+2.31 (Note: Some MPEG playback systems require that) 85.04 58.64 P
+1 12 Q
+(T) 76.29 727.31 T
+(able 4: B-frame Motion V) 83.18 727.31 T
+(ector Sear) 213.99 727.31 T
+(ch) 266.05 727.31 T
+(\050Normalized\051) 143.19 713.31 T
+0 10 Q
+(T) 84.14 691.64 T
+(echnique) 89.54 691.64 T
+(Compression) 139.45 691.64 T
+(Speed) 204.64 691.64 T
+(Quality) 242.96 691.64 T
+(Exhaustive) 86.23 673.64 T
+(1000) 155.83 673.64 T
+(?) 214.63 673.64 T
+(1000) 247.96 673.64 T
+(Cross2) 102.88 657.64 T
+(975) 158.33 657.64 T
+(1000) 206.86 657.64 T
+(996) 250.46 657.64 T
+(Simple) 102.32 641.64 T
+(938) 158.33 641.64 T
+(1765) 206.86 641.64 T
+(991) 250.46 641.64 T
+75.12 703.06 75.12 635.56 2 L
+V
+0.5 H
+0 Z
+N
+134.65 703.56 134.65 635.06 2 L
+V
+N
+197.01 703.56 197.01 635.06 2 L
+V
+N
+236.69 703.56 236.69 635.06 2 L
+V
+N
+279.21 703.06 279.21 635.56 2 L
+V
+N
+74.87 703.31 279.46 703.31 2 L
+V
+N
+75.37 684.56 278.96 684.56 2 L
+V
+N
+75.37 682.06 278.96 682.06 2 L
+V
+N
+74.87 667.31 279.46 667.31 2 L
+V
+N
+74.87 651.31 279.46 651.31 2 L
+V
+N
+74.87 635.31 279.46 635.31 2 L
+V
+N
+314.36 54.99 552.76 735.31 R
+7 X
+V
+0 X
+1.26 (each slice must consist of whole rows of macroblocks. If) 314.36 728.64 P
+0.55 (this is the case, then if the height of the image is H pixels,) 314.36 717.64 P
+1.97 (then you should set the SLICES_PER_FRAME to some) 314.36 706.64 P
+0.32 (number which divides H/16. For example, if H = 240, then) 314.36 695.64 P
+0.82 (you should only use SLICES_PER_FRAME values of 15,) 314.36 684.64 P
+(5, 3, or 1.) 314.36 673.64 T
+0.3 (Note to the note: these MPEG playback systems are) 342.71 660.64 P
+1.07 (really at fault, since the MPEG standard says this doesn\325t) 314.36 649.64 P
+(have to be so.) 314.36 638.64 T
+0 12 Q
+(2.5 Search W) 314.36 611.31 T
+(indow \050parameter \336le\051) 378.83 611.31 T
+0 10 Q
+0.65 (The search window is the window in which motion) 342.71 585.64 P
+0.47 (vectors are searched for. The window is a square. You can) 314.36 574.64 P
+1.22 (specify the size of the square, and whether to allow half-) 314.36 563.64 P
+(pixel motion vectors or not.) 314.36 552.64 T
+(Usage:) 342.71 526.64 T
+2 F
+(PIXEL) 342.71 500.64 T
+( <FULL or HALF>) 372.69 500.64 T
+(RANGE num [numB]) 342.71 487.64 T
+-0.54 (HALF) 342.71 461.64 P
+0 F
+-0.22 ( means that half-pixel vectors are allowed. The) 366.7 461.64 P
+0.19 (search window is +/-) 314.36 450.64 P
+2 F
+0.47 (num) 401.27 450.64 P
+0 F
+0.19 ( pixels in the X and Y directions.) 419.26 450.64 P
+-0.02 (It is usually important that you use) 314.36 439.64 P
+2 F
+-0.05 (HALF) 455.54 439.64 P
+0 F
+-0.02 (, because it results) 479.53 439.64 P
+2.12 (in both better quality and better compression. It is only) 314.36 428.64 P
+(undesirable for computer-generated images.) 314.36 417.64 T
+2 F
+-0.25 (num) 342.71 404.64 P
+0 F
+-0.1 ( should probably be set to at least 8 or 10 pixels.) 360.7 404.64 P
+2.35 (This number depends on the image. Using much larger) 314.36 393.64 P
+0.68 (numbers such as 20 or 30 doesn\325t seem to help much, and) 314.36 382.64 P
+0.02 (increases the CPU cost drastically. The optional numB is in) 314.36 371.64 P
+2.67 (case you wish to specify different ranges for predicted) 314.36 360.64 P
+3.37 (frames \050P-frames, num\051, and Bi-directional frames \050B-) 314.36 349.64 P
+0.31 (frames, numB\051. B-frame limits are optional as indicated by) 314.36 338.64 P
+-0 (the braces above \050so \322RANGE 10 6\323 is a valid command as) 314.36 327.64 P
+(is \322RANGE 9\323\051.) 314.36 316.64 T
+0 12 Q
+(2.6 IPB Pattern \050parameter \336le\051) 314.36 276.31 T
+0 10 Q
+2.24 (You can set the sequence of I, P, and B-frames.) 342.71 249.64 P
+0.45 (Later versions will allow you to do more than set a repeat-) 314.36 238.64 P
+2.49 (ing IPB pattern. The pattern affects speed, quality, and) 314.36 227.64 P
+(compression. Table five shows some of the trade-offs.) 314.36 216.64 T
+(\050this is given a certain Q-scale\051) 342.71 59.64 T
+1 12 Q
+(T) 337.17 193.31 T
+(able 5: Comparison of I/P/B-Frames) 344.06 193.31 T
+(\050Normalized\051) 399.59 179.31 T
+0 F
+(Frame) 331.84 156.31 T
+(T) 335.26 142.31 T
+(ype) 341.74 142.31 T
+(Compress) 384.37 156.31 T
+(ion) 400.7 142.31 T
+(Speed) 451.3 149.31 T
+(Quality) 505.57 149.31 T
+(I-frames) 334.94 122.31 T
+(1000) 396.36 122.31 T
+(1000) 453.96 122.31 T
+(1000) 511.57 122.31 T
+(P-frames) 332.26 104.31 T
+(409) 399.36 104.31 T
+(601) 456.96 104.31 T
+(969) 514.56 104.31 T
+(B-frames) 330.93 86.31 T
+(72) 402.36 86.31 T
+(260) 456.96 86.31 T
+(919) 514.56 86.31 T
+314.76 169.06 314.76 79.56 2 L
+V
+N
+379.56 169.56 379.56 79.06 2 L
+V
+N
+437.16 169.56 437.16 79.06 2 L
+V
+N
+494.76 169.56 494.76 79.06 2 L
+V
+N
+552.36 169.06 552.36 79.56 2 L
+V
+N
+314.51 169.31 552.61 169.31 2 L
+V
+N
+315.01 134.56 552.11 134.56 2 L
+V
+N
+315.01 132.06 552.11 132.06 2 L
+V
+N
+314.51 115.31 552.61 115.31 2 L
+V
+N
+314.51 97.31 552.61 97.31 2 L
+V
+N
+314.51 79.31 552.61 79.31 2 L
+V
+N
+FMENDPAGE
+%%EndPage: "3" 2
+%%Page: "2" 2
+612 792 0 FMBEGINPAGE
+57.97 26.65 554.03 40.82 R
+7 X
+0 K
+V
+0 10 Q
+0 X
+(2) 303.5 34.15 T
+72 72 297 720 R
+7 X
+V
+0 X
+2.84 (Here is a description of the different command-line) 72 700.33 P
+0.03 (options available and parameter-file options available in) 72 689.33 P
+2.37 (sequential \050one-machine\051 encoding. You should defi-) 72 678.33 P
+3.43 (nitely read sections 2.1-2.9. The other sections are) 72 667.33 P
+(optional.) 72 656.33 T
+0.21 (In the following, whenever a space in the parameter file) 72 630.33 P
+4.29 (appears, it can be represented by any amount of) 72 619.33 P
+(whitespace \050tabs or spaces\051.) 72 608.33 T
+0 12 Q
+(2.1 Q-Scale \050parameter \336le\051) 72 581 T
+0 10 Q
+1.78 (The quantization scale values \050Q-Scale\051 give a) 100.35 554.33 P
+1.37 (trade-off between quality and compression. Using dif-) 72 543.33 P
+-0.16 (ferent Q-Scale values has very little effect on speed. The) 72 532.33 P
+1.74 (Q-Scale values can be set separately for I, P, and B-) 72 521.33 P
+(frames.) 72 510.33 T
+(Usage:) 100.35 484.33 T
+2 F
+(IQ-Scale) 126.21 471.33 T
+( num) 174.18 471.33 T
+(PQ-Scale num) 125.5 458.33 T
+(BQ-Scale num) 126.21 445.33 T
+0 F
+(num in all three cases is a number from 1 to 31.) 100.35 432.33 T
+3.47 (Larger numbers give better compression, but) 100.35 406.33 P
+0.3 (worse quality. In the following, the quality numbers are) 72 395.33 P
+(peak signal-to-noise ratio, defined as:) 72 384.33 T
+(where MSE is the mean squared error.) 100.35 332.18 T
+0.12 (Tables one and two show the Q-scale vs. Quality) 100.35 306.18 P
+(relationship for the flower-garden sequence.) 72 295.18 T
+1.84 (Note that when rate-control \050Section 2.9\051 is in) 100.35 282.18 P
+0.19 (use, the rate control mechanism will change the Q-scale) 72 271.18 P
+1.37 (throughout the blocks of the frame, so these specified) 72 260.18 P
+(values are merely starting points.) 72 249.18 T
+1 12 Q
+(T) 95.77 212.85 T
+(able 1: Q-Scale vs. Quality \050SNR\051) 102.66 212.85 T
+0 10 Q
+(Q-Scale) 91.87 191.18 T
+(I-Frames) 140.95 191.18 T
+(P-Frames) 190.86 191.18 T
+(B-Frames) 241.33 191.18 T
+(1) 124.48 173.18 T
+(43.2) 150.24 173.18 T
+(46.3) 201.27 173.18 T
+(46.5) 252.29 173.18 T
+(6) 124.48 157.18 T
+(32.6) 150.24 157.18 T
+(34.6) 201.27 157.18 T
+(34.3) 252.29 157.18 T
+(1) 119.85 141.18 T
+(1) 124.48 141.18 T
+(28.6) 150.24 141.18 T
+(29.5) 201.27 141.18 T
+(30.0) 252.29 141.18 T
+(16) 119.48 125.18 T
+(26.3) 150.24 125.18 T
+(26.8) 201.27 125.18 T
+(28.6) 252.29 125.18 T
+(21) 119.48 109.18 T
+(24.7) 150.24 109.18 T
+(25.0) 201.27 109.18 T
+(27.9) 252.29 109.18 T
+(26) 119.48 93.18 T
+(23.5) 150.24 93.18 T
+(23.9) 201.27 93.18 T
+(27.5) 252.29 93.18 T
+82.45 202.6 82.45 87.1 2 L
+V
+0.5 H
+0 Z
+N
+133.48 203.1 133.48 86.6 2 L
+V
+N
+184.5 203.1 184.5 86.6 2 L
+V
+N
+235.52 203.1 235.52 86.6 2 L
+V
+N
+286.55 202.6 286.55 87.1 2 L
+V
+N
+82.2 202.85 286.8 202.85 2 L
+V
+N
+82.7 184.1 286.3 184.1 2 L
+V
+N
+82.7 181.6 286.3 181.6 2 L
+V
+N
+82.2 166.85 286.8 166.85 2 L
+V
+N
+82.2 150.85 286.8 150.85 2 L
+V
+N
+82.2 134.85 286.8 134.85 2 L
+V
+N
+82.2 118.85 286.8 118.85 2 L
+V
+N
+82.2 102.85 286.8 102.85 2 L
+V
+N
+82.2 86.85 286.8 86.85 2 L
+V
+N
+72 72 297 720 C
+72 341.85 297 381 C
+0 12 Q
+0 X
+0 K
+(2) 148.86 360.39 T
+(0) 154.85 360.39 T
+3 F
+(l) 161.56 360.39 T
+(o) 165.6 360.39 T
+(g) 172.3 360.39 T
+0 9 Q
+(1) 178.76 356.63 T
+(0) 183.25 356.63 T
+0 12 Q
+(2) 196.32 367.58 T
+(5) 202.32 367.58 T
+(5) 208.31 367.58 T
+3 F
+(M) 196.45 349.78 T
+(S) 207.15 349.78 T
+(E) 213.85 349.78 T
+221.18 360.98 196.45 360.98 2 L
+0.33 H
+0 Z
+N
+196.45 360.98 193.45 347.78 2 L
+N
+193.45 347.78 191.45 351.58 2 L
+N
+191.45 351.58 190.45 349.68 2 L
+N
+189.45 362.98 220.93 362.98 2 L
+N
+72 72 297 720 C
+0 0 612 792 C
+315 72 540 720 R
+7 X
+0 K
+V
+0 12 Q
+0 X
+(2.2 Search T) 315 471 T
+(echniques \050parameter \336le\051) 375.11 471 T
+0 10 Q
+0.77 (There are several different motion vector search) 343.35 445.33 P
+2.44 (techniques available for both P-frame search and B-) 315 434.33 P
+0.54 (frame search. Using different search techniques present) 315 423.33 P
+-0.04 (little difference in quality, but a large difference in com-) 315 412.33 P
+(pression and speed.) 315 401.33 T
+0.26 (There are 4 types of P-frame search: Exhaustive,) 343.35 388.33 P
+(TwoLevel, SubSample, and Logarithmic.) 315 377.33 T
+0.1 (There are 3 types of B-frame search: Exhaustive,) 343.35 364.33 P
+(Cross2, and Simple.) 315 353.33 T
+1.53 (The suggested search techniques are TwoLevel) 343.35 340.33 P
+2.17 (and Logarithmic for P-frame search, and Cross2 and) 315 329.33 P
+0.68 (Simple for B-frame search. Tables three and four com-) 315 318.33 P
+(pare the different search methods:) 315 307.33 T
+318.37 170 536.63 176 C
+0 0 612 792 C
+0 10 Q
+0 X
+0 K
+(a. Smaller numbers mean better compression) 336.37 163.33 T
+(b. Larger numbers mean faster execution) 336.37 151.33 T
+(c. Larger numbers mean better quality) 336.37 139.33 T
+(31) 362.48 672.33 T
+(22.6) 393.24 672.33 T
+(23.0) 444.27 672.33 T
+(27.3) 495.29 672.33 T
+1 12 Q
+(T) 342.04 646 T
+(able 2: Q-Scale vs. Compr) 348.94 646 T
+(ession) 482.3 646 T
+0 10 Q
+(Q-Scale) 334.87 624.33 T
+(I-Frames) 383.95 624.33 T
+(P-Frames) 433.86 624.33 T
+(B-Frames) 484.33 624.33 T
+(1) 367.48 606.33 T
+(2:1) 395.6 606.33 T
+(2:1) 446.62 606.33 T
+(2:1) 497.65 606.33 T
+(6) 367.48 590.33 T
+(7) 399.49 590.33 T
+(10) 448.01 590.33 T
+(15) 499.04 590.33 T
+(1) 362.85 574.33 T
+(1) 367.48 574.33 T
+(1) 397.18 574.33 T
+(1) 401.8 574.33 T
+(18) 448.01 574.33 T
+(43) 499.04 574.33 T
+(16) 362.48 558.33 T
+(15) 396.99 558.33 T
+(29) 448.01 558.33 T
+(97) 499.04 558.33 T
+(21) 362.48 542.33 T
+(19) 396.99 542.33 T
+(41) 448.01 542.33 T
+(173) 496.54 542.33 T
+(26) 362.48 526.33 T
+(24) 396.99 526.33 T
+(56) 448.01 526.33 T
+(256) 496.54 526.33 T
+(31) 362.48 510.33 T
+(28) 396.99 510.33 T
+(73) 448.01 510.33 T
+(330) 496.54 510.33 T
+1 12 Q
+(T) 326.96 284 T
+(able 3: P-frame Motion V) 333.86 284 T
+(ector Sear) 463.98 284 T
+(ch) 516.05 284 T
+(\050Normalized\051) 393.53 270 T
+0 10 Q
+(T) 327.39 248.33 T
+(echnique) 332.79 248.33 T
+(Compression) 382.34 248.33 T
+0 8 Q
+(a) 435.09 252.33 T
+0 10 Q
+(Speed) 454.39 248.33 T
+0 8 Q
+(b) 478.82 252.33 T
+0 10 Q
+(Quality) 498.61 248.33 T
+0 8 Q
+(c) 528.59 252.33 T
+0 10 Q
+(Exhaustive) 329.48 230.33 T
+(1000) 400.5 230.33 T
+(1000) 458.61 230.33 T
+(1000) 505.38 230.33 T
+(SubSample) 328.36 214.33 T
+(1008) 400.5 214.33 T
+(2456) 458.61 214.33 T
+(1000) 505.38 214.33 T
+(T) 333.52 198.33 T
+(woLevel) 338.92 198.33 T
+(1009) 400.5 198.33 T
+(3237) 458.61 198.33 T
+(1000) 505.38 198.33 T
+(Logarithmic) 324.48 182.33 T
+(1085) 400.5 182.33 T
+(8229) 458.61 182.33 T
+(998) 507.88 182.33 T
+1 12 Q
+(T) 338.77 712 T
+(able 1: Q-Scale vs. Quality \050SNR\051) 345.66 712 T
+0 10 Q
+(Q-Scale) 334.87 690.33 T
+(I-Frames) 383.95 690.33 T
+(P-Frames) 433.86 690.33 T
+(B-Frames) 484.33 690.33 T
+325.45 701.75 325.45 666.25 2 L
+V
+0.5 H
+0 Z
+N
+376.48 702.25 376.48 665.75 2 L
+V
+N
+427.5 702.25 427.5 665.75 2 L
+V
+N
+478.52 702.25 478.52 665.75 2 L
+V
+N
+529.55 701.75 529.55 666.25 2 L
+V
+N
+325.2 702 529.8 702 2 L
+V
+N
+325.7 683.25 529.3 683.25 2 L
+V
+N
+325.7 680.75 529.3 680.75 2 L
+V
+N
+325.2 666 529.8 666 2 L
+V
+N
+325.45 635.75 325.45 504.25 2 L
+V
+N
+376.48 636.25 376.48 503.75 2 L
+V
+N
+427.5 636.25 427.5 503.75 2 L
+V
+N
+478.52 636.25 478.52 503.75 2 L
+V
+N
+529.55 635.75 529.55 504.25 2 L
+V
+N
+325.2 636 529.8 636 2 L
+V
+N
+325.7 617.25 529.3 617.25 2 L
+V
+N
+325.7 614.75 529.3 614.75 2 L
+V
+N
+325.2 600 529.8 600 2 L
+V
+N
+325.2 584 529.8 584 2 L
+V
+N
+325.2 568 529.8 568 2 L
+V
+N
+325.2 552 529.8 552 2 L
+V
+N
+325.2 536 529.8 536 2 L
+V
+N
+325.2 520 529.8 520 2 L
+V
+N
+325.2 504 529.8 504 2 L
+V
+N
+318.37 259.75 318.37 176.25 2 L
+V
+N
+377.89 260.25 377.89 175.75 2 L
+V
+N
+443.09 260.25 443.09 175.75 2 L
+V
+N
+494.11 260.25 494.11 175.75 2 L
+V
+N
+536.63 259.75 536.63 176.25 2 L
+V
+N
+318.12 260 536.88 260 2 L
+V
+N
+318.62 241.25 536.38 241.25 2 L
+V
+N
+318.62 238.75 536.38 238.75 2 L
+V
+N
+318.12 224 536.88 224 2 L
+V
+N
+318.12 208 536.88 208 2 L
+V
+N
+318.12 192 536.88 192 2 L
+V
+N
+318.12 176 536.88 176 2 L
+V
+N
+FMENDPAGE
+%%EndPage: "2" 1
+%%Page: "1" 1
+612 792 0 FMBEGINPAGE
+56.69 111.69 297.64 565.23 R
+7 X
+0 K
+V
+1 12 Q
+0 X
+(Contents) 56.69 557.23 T
+0 10 Q
+(0. Contacts/History) 56.69 539.56 T
+(1. Installation) 56.69 526.56 T
+(2. Sequential Usage) 56.69 513.56 T
+(2.1 Q-Scale) 85.04 500.56 T
+(2.2 Search Techniques) 85.04 487.56 T
+(2.3 GOP) 85.04 474.56 T
+(2.4 Slices) 85.04 461.56 T
+(2.5) 85.04 448.56 T
+( Search window) 97.53 448.56 T
+(2.6 IPB pattern) 85.04 435.56 T
+(2.7 Specifying Input Files) 85.04 422.56 T
+(2.8) 85.04 409.56 T
+( Original or Decoded) 97.53 409.56 T
+(2.9 Bit-rate Control) 85.04 396.56 T
+(2.10 User-data) 85.04 383.56 T
+(2.11 Compression Decision List \050CDL\051) 85.04 370.56 T
+(2.12 Gamma Correction) 85.04 357.56 T
+(2.13 Encoding GOPs at a time) 85.04 344.56 T
+(2.14) 85.04 331.56 T
+( Encoding Frames at a time) 102.53 331.56 T
+(2.15) 85.04 318.56 T
+( Stats and other info) 102.53 318.56 T
+(3. Parallel Usage) 56.69 305.56 T
+(3.1) 85.04 292.56 T
+( Architecture) 97.53 292.56 T
+(3.2 Specifying slave machines) 85.04 279.56 T
+(3.3) 85.04 266.56 T
+( Remote Shell) 97.53 266.56 T
+(3.4 Scheduling Algorithms) 85.04 253.56 T
+(3.5 Parallel Problems) 85.04 240.56 T
+0 12 Q
+(4. Performance) 56.69 226.23 T
+0 10 Q
+(5. Other Options) 56.69 213.56 T
+(5.1) 85.04 200.56 T
+( Custom Quantization Tables) 97.53 200.56 T
+(5.2 Aspect Ratio) 85.04 187.56 T
+(5.3) 85.04 174.56 T
+( Frame Rate) 97.53 174.56 T
+(6. Other Tools) 56.69 161.56 T
+(6.1) 85.04 148.56 T
+(ppmtoeyuv) 100.03 148.56 T
+(6.2 jmovie2jpeg) 85.04 135.56 T
+(6.3 movieToVid) 85.04 122.56 T
+314.36 54.99 552.76 565.23 R
+7 X
+V
+0 X
+(6.4) 342.71 558.56 T
+(yuvtojpeg) 357.7 558.56 T
+(6.5) 342.71 545.56 T
+( blockrun) 355.2 545.56 T
+(6.6) 342.71 532.56 T
+( vidtoppm) 355.2 532.56 T
+(6.7 vidtojpeg) 342.71 519.56 T
+(6.8 vidtoeyuv) 342.71 506.56 T
+(7. Frequently Asked Questions) 314.36 493.56 T
+(7.1 Questions) 342.71 480.56 T
+(7.2 Answers) 342.71 467.56 T
+1 12 Q
+(0. Contacts/History) 314.36 432.23 T
+0 10 Q
+2.56 (The Berkeley MPEG encoder was written by Kevin L.) 314.36 414.56 P
+0.59 (Gong in the Berkeley Plateau Multimedia Research group,) 314.36 403.56 P
+2.29 (headed by Professor Lawrence Rowe. It has since been) 314.36 392.56 P
+2.69 (modified by Stephen Smoot, Eugene Hung, and Darryl) 314.36 381.56 P
+0.69 (Brown. Please use the following e-mail addresses to reach) 314.36 370.56 P
+(us:) 314.36 359.56 T
+-0.23 (mpeg-bugs@plateau.cs.berkeley.edu \050bug reports and ques-) 314.36 346.56 P
+(tions\051 larry@cs.berkeley.edu \050research funding!\051) 314.36 335.56 T
+1 12 Q
+(1. Installation) 314.36 300.23 T
+0 10 Q
+(To install, read the directions in doc/INSTALL.) 342.71 282.56 T
+1.32 (Note that the) 342.71 269.56 P
+2 F
+3.17 (bin/) 400.8 269.56 P
+0 F
+1.32 ( directory contains binaries for) 424.79 269.56 P
+0.35 (several different platforms. The program has been success-) 314.36 258.56 P
+(fully ported to the following platforms:) 314.36 247.56 T
+(SunOS 4.x) 342.71 234.56 T
+(DEC Alpha running OSF1) 342.71 221.56 T
+(DECstation 5000 running Ultrix) 342.71 208.56 T
+(HP 9000 series) 342.71 195.56 T
+-0.06 (If you are successful in porting to a new platform, or) 342.71 169.56 P
+0.15 (have problems installing, please let us know \050at the address) 314.36 158.56 P
+(above\051.) 314.36 147.56 T
+1 12 Q
+(2. Sequential Usage) 314.36 112.23 T
+0 10 Q
+(The encoder is invoked in the following manner:) 314.36 81.56 T
+1 F
+(mpeg_encode) 352.7 68.56 T
+0 F
+( <options> parameter_file) 410.44 68.56 T
+56.69 570.9 552.76 735.31 R
+7 X
+V
+1 18 Q
+0 X
+(Berkeley MPEG-1 V) 174.89 723.31 T
+(ideo Encoder) 333.11 723.31 T
+290.64 685.53 254.68 685.53 2 L
+V
+1.71 H
+0 Z
+N
+(User) 254.68 687.31 T
+296.95 685.53 290.96 685.53 2 L
+V
+N
+(\325) 290.96 687.31 T
+354.77 685.53 296.29 685.53 2 L
+V
+N
+(s Guide) 296.29 687.31 T
+3 12 Q
+(Plateau Resear) 245.54 657.31 T
+(ch Gr) 318.71 657.31 T
+(oup) 345.92 657.31 T
+(Computer Science Division) 239.1 643.31 T
+(University of California) 247.08 629.31 T
+(Berkeley) 239.59 615.31 T
+(, California 94720) 280.89 615.31 T
+(mpeg-bugs@cs.berkeley) 236.26 596.31 T
+(.edu) 352.87 596.31 T
+56.69 54.99 297.64 104.6 R
+7 X
+V
+56.69 94.59 297.64 104.6 C
+56.69 94.59 297.64 104.6 R
+7 X
+0 K
+V
+56.69 103.59 525.7 103.59 2 L
+V
+1 H
+2 Z
+0 X
+N
+0 0 612 792 C
+0 9 Q
+0 X
+0 K
+(Last Updated: 30 January 1995) 56.69 88.59 T
+FMENDPAGE
+%%EndPage: "1" 0
+%%Trailer
+%%BoundingBox: 0 0 612 792
+%%Pages: 11 -1
+%%DocumentFonts: Times-Roman
+%%+ Times-Bold
+%%+ Courier
+%%+ Times-Italic
diff --git a/converter/ppm/ppmtompeg/examples/basicspeed.param b/converter/ppm/ppmtompeg/examples/basicspeed.param
new file mode 100644
index 00000000..94b03de7
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/basicspeed.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/speed.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/decoded.test b/converter/ppm/ppmtompeg/examples/decoded.test
new file mode 100644
index 00000000..7496524c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/decoded.test
@@ -0,0 +1,81 @@
+# speed test parameter file
+
+PATTERN		ibpbpb
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	cat *.Y *.U *.V
+
+INPUT_DIR	/n/picasso/users/keving/links/flowg
+
+INPUT
+sflowg.*	[0-6]
+END_INPUT
+
+# the following two are optional  (default = 10, 60)
+
+# number of frames to do initially to gauge speed of machine
+PARALLEL_TEST_FRAMES	3
+
+# number of seconds per chunk thereafter
+PARALLEL_TIME_CHUNKS	30
+
+
+PARALLEL
+# lines must be of form "machine <whitespace> username <whitespace> executable"
+# these guys are sorta slow:
+#elmer-fudd	keving	~keving/encode/bin/sun/mpeg_encode
+#zonker		keving	~keving/encode/bin/sun/mpeg_encode
+#roger-rabbit	keving	~keving/encode/bin/sun/mpeg_encode
+#tweety		keving	~keving/encode/bin/sun/mpeg_encode
+#mickey		keving	~keving/encode/bin/mickey/mpeg_encode
+#
+# these guys are pretty fast:
+#
+#big-bird	keving	~keving/encode/bin/hp/mpeg_encode
+#gumby		keving	~keving/encode/bin/hp/mpeg_encode
+#charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#bugs-bunny	keving	~keving/encode/bin/sun/mpeg_encode
+#
+# remotes
+#
+#REMOTE anaconda	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE adder	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE moccasin	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE cobra	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE boa	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE asp	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE rattler	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE viper	keving	~keving/mpeg_encode ~keving/parallel.test
+# mamba doesn't seem to work for whatever reason...don't know why
+#REMOTE mamba	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE cse.lbl.gov kevin	~kevin/mpeg_encode ~kevin/parallel.test
+#REMOTE roger-rabbit keving	~keving/src/encode/bin/sun/mpeg_encode ~keving/src/encode/ParamExamples/parallel.test
+END_PARALLEL
+
+
+# motion vector search parameters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		8
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		1
+PQSCALE		1
+BQSCALE		25
+
+REFERENCE_FRAME		DECODED
+#REFERENCE_FRAME		ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/decoded2.test b/converter/ppm/ppmtompeg/examples/decoded2.test
new file mode 100644
index 00000000..b5e9a865
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/decoded2.test
@@ -0,0 +1,81 @@
+# speed test parameter file
+
+PATTERN		ibbbpbbbbbp
+OUTPUT		/tmp/ts.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	512x512
+
+INPUT_CONVERT	giftoppm *
+
+INPUT_DIR	../test/ts
+
+INPUT
+med*.gif [030-039]
+END_INPUT
+
+# the following two are optional  (default = 10, 60)
+
+# number of frames to do initially to gauge speed of machine
+PARALLEL_TEST_FRAMES	3
+
+# number of seconds per chunk thereafter
+PARALLEL_TIME_CHUNKS	30
+
+
+PARALLEL
+# lines must be of form "machine <whitespace> username <whitespace> executable"
+# these guys are sorta slow:
+#elmer-fudd	keving	~keving/encode/bin/sun/mpeg_encode
+#zonker		keving	~keving/encode/bin/sun/mpeg_encode
+#roger-rabbit	keving	~keving/encode/bin/sun/mpeg_encode
+#tweety		keving	~keving/encode/bin/sun/mpeg_encode
+#mickey		keving	~keving/encode/bin/mickey/mpeg_encode
+#
+# these guys are pretty fast:
+#
+#big-bird	keving	~keving/encode/bin/hp/mpeg_encode
+#gumby		keving	~keving/encode/bin/hp/mpeg_encode
+#charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#bugs-bunny	keving	~keving/encode/bin/sun/mpeg_encode
+#
+# remotes
+#
+#REMOTE anaconda	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE adder	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE moccasin	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE cobra	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE boa	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE asp	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE rattler	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE viper	keving	~keving/mpeg_encode ~keving/parallel.test
+# mamba doesn't seem to work for whatever reason...don't know why
+#REMOTE mamba	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE cse.lbl.gov kevin	~kevin/mpeg_encode ~kevin/parallel.test
+#REMOTE roger-rabbit keving	~keving/src/encode/bin/sun/mpeg_encode ~keving/src/encode/ParamExamples/parallel.test
+END_PARALLEL
+
+
+# motion vector search parameters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		8
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+REFERENCE_FRAME		DECODED
+#REFERENCE_FRAME		ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/decorig.param b/converter/ppm/ppmtompeg/examples/decorig.param
new file mode 100644
index 00000000..3c570bcd
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/decorig.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBP
+
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-3]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/default.param b/converter/ppm/ppmtompeg/examples/default.param
new file mode 100644
index 00000000..17d8fe5d
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/default.param
@@ -0,0 +1,34 @@
+# parameter file with good default values
+#
+# use this as a guideline for any parameters you don't really understand
+# or don't care about
+#
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		output.mpg
+
+BASE_FILE_FORMAT	YUV
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+PIXEL		HALF
+RANGE		10
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+REFERENCE_FRAME	ORIGINAL
+
+#
+# you really need to understand the following
+#
+YUV_SIZE	352x240
+INPUT_CONVERT	*
+
+INPUT_DIR	input/flowg
+
+INPUT
+sflowg.*.yuv	[0-23]
+END_INPUT
diff --git a/converter/ppm/ppmtompeg/examples/fastspeed.param b/converter/ppm/ppmtompeg/examples/fastspeed.param
new file mode 100644
index 00000000..c45e0341
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/fastspeed.param
@@ -0,0 +1,46 @@
+# speed test parameter file
+
+PATTERN		ibbpbbpbbpbbpbb
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	cat *.Y *.U *.V
+
+INPUT_DIR	/n/picasso/project/mm/mpeg/mpeg_encode/input/flowg
+
+INPUT
+sflowg.*	[0-149]
+END_INPUT
+
+PARALLEL_TEST_FRAMES	3
+PARALLEL_TIME_CHUNKS	15
+
+PARALLEL
+charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+END_PARALLEL
+
+# motion vector search parameters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		8
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+REFERENCE_FRAME	ORIGINAL
+
diff --git a/converter/ppm/ppmtompeg/examples/foobar.param b/converter/ppm/ppmtompeg/examples/foobar.param
new file mode 100644
index 00000000..1ba6f029
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/foobar.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard20.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[000-027]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		10
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/frame.test b/converter/ppm/ppmtompeg/examples/frame.test
new file mode 100644
index 00000000..fbefb8c4
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/frame.test
@@ -0,0 +1,37 @@
+# speed test parameter file
+
+PATTERN		IBPB
+OUTPUT		output/food
+GOP_SIZE	20
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	cat *.Y *.U *.V
+
+INPUT_DIR	links/flowg
+
+INPUT
+sflowg.*	[0-4]
+END_INPUT
+
+
+# motion vector search parameters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		8
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		10
+PQSCALE		31
+BQSCALE		31
+
diff --git a/converter/ppm/ppmtompeg/examples/gop.combine b/converter/ppm/ppmtompeg/examples/gop.combine
new file mode 100644
index 00000000..6b5c3e89
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/gop.combine
@@ -0,0 +1,11 @@
+# speed test parameter file
+
+OUTPUT		output/gop.mpg
+
+YUV_SIZE	352x240
+
+INPUT_DIR	.
+
+INPUT
+output/food.gop.*	[0-2]
+END_INPUT
diff --git a/converter/ppm/ppmtompeg/examples/gop.test b/converter/ppm/ppmtompeg/examples/gop.test
new file mode 100644
index 00000000..c2ae346b
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/gop.test
@@ -0,0 +1,43 @@
+# speed test parameter file
+
+PATTERN		IBPBP
+
+OUTPUT		output/food
+GOP_SIZE	5
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	cat *.Y *.U *.V
+
+INPUT_DIR	links/flowg
+
+INPUT
+sflowg.*	[0-14]
+END_INPUT
+
+
+GOP_INPUT_DIR	output
+GOP_INPUT
+food.gop.*	[1-2]
+GOP_END_INPUT
+
+# motion vector search parameters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		8
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		10
+PQSCALE		31
+BQSCALE		31
+
diff --git a/converter/ppm/ppmtompeg/examples/gop1.param b/converter/ppm/ppmtompeg/examples/gop1.param
new file mode 100644
index 00000000..c89a5b9a
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/gop1.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	6
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/hello.param b/converter/ppm/ppmtompeg/examples/hello.param
new file mode 100644
index 00000000..579b01ed
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/hello.param
@@ -0,0 +1,60 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/hello.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-49]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+#REFERENCE_FRAME	DECODED
+REFERENCE_FRAME	ORIGINAL
+
+PARALLEL_TEST_FRAMES	3
+
+# number of seconds per chunk thereafter
+PARALLEL_TIME_CHUNKS	30
+
+PARALLEL
+#big-bird	keving	~keving/encode/bin/hp/mpeg_encode
+#gumby		keving	~keving/encode/bin/hp/mpeg_encode
+charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#REMOTE cory keving ~keving/bin/mpeg_encode ~keving/hello.param
+REMOTE scorpius.EECS.Berkeley.EDU keving ~keving/bin/mpeg_encode ~keving/hello.param
+REMOTE monoceros.EECS.Berkeley.EDU keving ~keving/bin/mpeg_encode ~keving/hello.param
+REMOTE lepus.EECS.Berkeley.EDU keving ~keving/bin/mpeg_encode ~keving/hello.param
+REMOTE fornax.EECS.Berkeley.EDU keving ~keving/bin/mpeg_encode ~keving/hello.param
+REMOTE delphinus.EECS.Berkeley.EDU keving ~keving/bin/mpeg_encode ~keving/hello.param
+REMOTE cephus.EECS.Berkeley.EDU keving ~keving/bin/mpeg_encode ~keving/hello.param
+REMOTE carina.EECS.Berkeley.EDU keving ~keving/bin/mpeg_encode ~keving/hello.param
+REMOTE bootes.EECS.Berkeley.EDU keving ~keving/bin/mpeg_encode ~keving/hello.param
+REMOTE aquila.EECS.Berkeley.EDU keving ~keving/bin/mpeg_encode ~keving/hello.param
+#
+END_PARALLEL
+
+# FORCE_I_ALIGN
diff --git a/converter/ppm/ppmtompeg/examples/i.test b/converter/ppm/ppmtompeg/examples/i.test
new file mode 100644
index 00000000..af62afae
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/i.test
@@ -0,0 +1,37 @@
+# speed test parameter file
+
+PATTERN		I
+OUTPUT		output/food
+GOP_SIZE	20
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	cat *.Y *.U *.V
+
+INPUT_DIR	links/flowg
+
+INPUT
+sflowg.*	[0-5]
+END_INPUT
+
+
+# motion vector search parameters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		8
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		10
+PQSCALE		31
+BQSCALE		31
+
diff --git a/converter/ppm/ppmtompeg/examples/ispeed.jpeg.param b/converter/ppm/ppmtompeg/examples/ispeed.jpeg.param
new file mode 100644
index 00000000..ce3d4ed3
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/ispeed.jpeg.param
@@ -0,0 +1,48 @@
+# speed test parameter file
+
+PATTERN		I
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard1.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	djpeg *
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg/jpeg
+
+INPUT
+sflowg.*.jpeg	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
+
+
+PARALLEL_TEST_FRAMES	3
+PARALLEL_TIME_CHUNKS	30
+
+PARALLEL
+big-bird	keving	~keving/encode/bin/hp/mpeg_encode
+gumby		keving	~keving/encode/bin/hp/mpeg_encode
+charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+END_PARALLEL
+
+IO_SERVER_CONVERT       *
+SLAVE_CONVERT           djpeg *
diff --git a/converter/ppm/ppmtompeg/examples/ispeed.param b/converter/ppm/ppmtompeg/examples/ispeed.param
new file mode 100644
index 00000000..01381e85
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/ispeed.param
@@ -0,0 +1,45 @@
+# speed test parameter file
+
+PATTERN		I
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard1.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
+
+
+PARALLEL_TEST_FRAMES	3
+PARALLEL_TIME_CHUNKS	30
+
+PARALLEL
+big-bird	keving	~keving/encode/bin/hp/mpeg_encode
+gumby		keving	~keving/encode/bin/hp/mpeg_encode
+charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+END_PARALLEL
diff --git a/converter/ppm/ppmtompeg/examples/jpeg.test b/converter/ppm/ppmtompeg/examples/jpeg.test
new file mode 100644
index 00000000..75e3eb8f
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/jpeg.test
@@ -0,0 +1,66 @@
+# speed test parameter file
+
+PATTERN		ibbpbbpbbpbbpbb
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.jpeg.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	352x240
+
+INPUT_CONVERT	djpeg *
+
+INPUT_DIR	/n/picasso/project/mm/mpeg/mpeg_encode/input/flowg/jpeg
+
+INPUT
+sflowg.*.jpeg	[1-20]
+END_INPUT
+
+# the following two are optional  (default = 10, 60)
+
+# number of frames to do initially to gauge speed of machine
+PARALLEL_TEST_FRAMES	3
+
+# number of seconds per chunk thereafter
+PARALLEL_TIME_CHUNKS	30
+
+# specifies what the IO server must do before transmitting to remote sites
+IO_SERVER_CONVERT	*
+
+# specifies what the remote slave must do after receiving from IO server
+SLAVE_CONVERT		djpeg *
+
+PARALLEL
+# lines must be of form "machine <whitespace> username <whitespace> executable"
+# these guys are sorta slow:
+#elmer-fudd	keving	~keving/encode/bin/sun/mpeg_encode
+#roger-rabbit	keving	~keving/encode/bin/sun/mpeg_encode
+#tweety		keving	~keving/encode/bin/sun/mpeg_encode
+#mickey		keving	~keving/encode/bin/mickey/mpeg_encode
+#
+# these guys are pretty fast:
+#
+#big-bird	keving	~keving/encode/bin/hp/mpeg_encode
+#gumby		keving	~keving/encode/bin/hp/mpeg_encode
+charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#bugs-bunny	keving	~keving/encode/bin/sun/mpeg_encode
+#
+# remotes
+#
+END_PARALLEL
+
+
+# motion vector search parameters
+
+PIXEL		HALF
+RANGE		8
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+REFERENCE_FRAME	ORIGINAL
+
diff --git a/converter/ppm/ppmtompeg/examples/jpeg19.param b/converter/ppm/ppmtompeg/examples/jpeg19.param
new file mode 100644
index 00000000..15013895
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/jpeg19.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	JPEG
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg/jpeg
+
+INPUT
+sflowg.*.jpeg	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/jpegout19.param b/converter/ppm/ppmtompeg/examples/jpegout19.param
new file mode 100644
index 00000000..d31383d3
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/jpegout19.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	352x240
+
+INPUT_CONVERT	djpeg *
+
+INPUT_DIR	../input/flowg/jpeg
+
+INPUT
+sflowg.*.jpeg	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/localdisk.param b/converter/ppm/ppmtompeg/examples/localdisk.param
new file mode 100644
index 00000000..e7f49f33
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/localdisk.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		I
+OUTPUT		/usr/tmp/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/usr/tmp
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/localremote.test b/converter/ppm/ppmtompeg/examples/localremote.test
new file mode 100644
index 00000000..6c91721f
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/localremote.test
@@ -0,0 +1,80 @@
+# speed test parameter file
+
+#PATTERN		ibbpbbpbbpbbpbb
+PATTERN		iiiiiiii
+OUTPUT		/n/picasso/users/keving/src/encode/output/good
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	cat *.Y *.U *.V
+
+INPUT_DIR	/n/picasso/users/keving/links/flowg
+
+INPUT
+sflowg.*	[0-149]
+END_INPUT
+
+# the following two are optional  (default = 10, 60)
+
+# number of frames to do initially to gauge speed of machine
+PARALLEL_TEST_FRAMES	3
+
+# number of seconds per chunk thereafter
+PARALLEL_TIME_CHUNKS	30
+
+
+PARALLEL
+# lines must be of form "machine <whitespace> username <whitespace> executable"
+# these guys are sorta slow:
+#REMOTE charlie-brown	keving	~keving/src/encode/bin/dec/mpeg_encode ~keving/src/encode/ParamExamples/localremote.test
+#REMOTE woodstock	keving	~keving/src/encode/bin/dec/mpeg_encode ~keving/src/encode/ParamExamples/localremote.test
+#REMOTE gumby		keving	~keving/src/encode/bin/hp/mpeg_encode ~keving/src/encode/ParamExamples/localremote.test
+#REMOTE big-bird		keving	~keving/src/encode/bin/hp/mpeg_encode ~keving/src/encode/ParamExamples/localremote.test
+#REMOTE elmer-fudd	keving	~keving/src/encode/bin/sun/mpeg_encode ~keving/src/encode/ParamExamples/localremote.test
+#REMOTE mickey		keving	~keving/src/encode/bin/mickey/mpeg_encode ~keving/src/encode/ParamExamples/localremote.test
+#zonker		keving	~keving/src/encode/bin/sun/mpeg_encode
+#roger-rabbit	keving	~keving/src/encode/bin/sun/mpeg_encode
+#tweety		keving	~keving/src/encode/bin/sun/mpeg_encode
+#
+# these guys are pretty fast:
+#
+#bugs-bunny	keving	~keving/src/encode/bin/sun/mpeg_encode
+#
+# remotes
+#
+#REMOTE anaconda	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE adder	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE moccasin	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE cobra	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE boa	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE asp	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE rattler	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE viper	keving	~keving/mpeg_encode ~keving/parallel.test
+# mamba doesn't seem to work for whatever reason...don't know why
+#REMOTE mamba	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE cse.lbl.gov kevin	~kevin/mpeg_encode ~kevin/parallel.test
+#REMOTE roger-rabbit keving	~keving/src/encode/bin/sun/mpeg_encode ~keving/src/encode/ParamExamples/parallel.test
+END_PARALLEL
+
+
+# motion vector search parameters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		8
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
diff --git a/converter/ppm/ppmtompeg/examples/luxo.param b/converter/ppm/ppmtompeg/examples/luxo.param
new file mode 100644
index 00000000..6c92dca1
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/luxo.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/luxo.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	320x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/luxo
+
+INPUT
+luxo*.yuv	[1201-1400]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		20
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/luxo18.param b/converter/ppm/ppmtompeg/examples/luxo18.param
new file mode 100644
index 00000000..4a020325
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/luxo18.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/luxo19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	320x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/luxo
+
+INPUT
+luxo*.yuv	[1201-1400]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/luxo19.5.param b/converter/ppm/ppmtompeg/examples/luxo19.5.param
new file mode 100644
index 00000000..edacc7c7
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/luxo19.5.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/luxo19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	320x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/luxo
+
+INPUT
+luxo*.yuv	[1201-1216]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/luxo19.param b/converter/ppm/ppmtompeg/examples/luxo19.param
new file mode 100644
index 00000000..7accb23a
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/luxo19.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/luxo19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	320x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/luxo
+
+INPUT
+luxo*.yuv	[1201-1400]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/luxo2.param b/converter/ppm/ppmtompeg/examples/luxo2.param
new file mode 100644
index 00000000..f75b7e1c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/luxo2.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		I
+OUTPUT		/n/picasso/users/keving/encode/output/luxo2.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	320x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/luxo
+
+INPUT
+luxo1399.yuv
+luxo1400.yuv
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		20
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/luxo48.param b/converter/ppm/ppmtompeg/examples/luxo48.param
new file mode 100644
index 00000000..599f6d8c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/luxo48.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/luxo19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	320x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/luxo
+
+INPUT
+luxo*.yuv	[1201-1400]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	SUBSAMPLE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/luxo49.param b/converter/ppm/ppmtompeg/examples/luxo49.param
new file mode 100644
index 00000000..5a493e40
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/luxo49.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/luxo19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	320x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/luxo
+
+INPUT
+luxo*.yuv	[1201-1400]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/luxo50.param b/converter/ppm/ppmtompeg/examples/luxo50.param
new file mode 100644
index 00000000..f3afe8e8
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/luxo50.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/luxo19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	320x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/luxo
+
+INPUT
+luxo*.yuv	[1201-1400]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/luxo52.5.param b/converter/ppm/ppmtompeg/examples/luxo52.5.param
new file mode 100644
index 00000000..f3f16854
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/luxo52.5.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/luxo19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	320x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/luxo
+
+INPUT
+luxo*.yuv	[1201-1216]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/luxo52.param b/converter/ppm/ppmtompeg/examples/luxo52.param
new file mode 100644
index 00000000..8874ad97
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/luxo52.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/luxo19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	320x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/luxo
+
+INPUT
+luxo*.yuv	[1201-1400]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/luxo53.param b/converter/ppm/ppmtompeg/examples/luxo53.param
new file mode 100644
index 00000000..d084b18b
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/luxo53.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/luxo19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	320x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/luxo
+
+INPUT
+luxo*.yuv	[1201-1216]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	EXHAUSTIVE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/luxobug.param b/converter/ppm/ppmtompeg/examples/luxobug.param
new file mode 100644
index 00000000..73e9de9f
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/luxobug.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/luxobug.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	320x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/luxo-skip10
+
+INPUT
+luxo*.yuv		[1351-1701+5]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/luxoskip.param b/converter/ppm/ppmtompeg/examples/luxoskip.param
new file mode 100644
index 00000000..12768ed4
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/luxoskip.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/luxoskip.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/luxo-skip10
+
+INPUT
+luxo*1 		[1-307]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		20
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		20
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/med.param b/converter/ppm/ppmtompeg/examples/med.param
new file mode 100644
index 00000000..0e3bbd8d
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/med.param
@@ -0,0 +1,36 @@
+# 'med' parameter file
+
+PATTERN		IBBPBBPBBPBBPBBI
+OUTPUT		output/med.mpg
+GOP_SIZE	20
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+INPUT_CONVERT	giftoppm *
+
+INPUT_DIR	links/gcmovie
+
+INPUT
+med*.gif	[001-073]
+END_INPUT
+
+
+# motion vector search parameters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		HALF
+
+# means +/- this many pixels
+RANGE		8
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	CROSS2
+
+
+IQSCALE		13
+PQSCALE		16
+BQSCALE		26
+
diff --git a/converter/ppm/ppmtompeg/examples/med18.param b/converter/ppm/ppmtompeg/examples/med18.param
new file mode 100644
index 00000000..b8ad664c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/med18.param
@@ -0,0 +1,34 @@
+# 'med' parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/med18.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+INPUT_CONVERT	giftoppm *
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/gcmovie
+
+INPUT
+med*.gif	[001-073]
+END_INPUT
+
+
+# motion vector search parameters
+
+# FULL or HALF -- must be upper case
+PIXEL		HALF
+
+# means +/- this many pixels
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/medspeed.param b/converter/ppm/ppmtompeg/examples/medspeed.param
new file mode 100644
index 00000000..d91f4dda
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/medspeed.param
@@ -0,0 +1,38 @@
+# speed test parameter file
+
+PATTERN		ibbpbbpbbpbbpbb
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	cat *.Y *.U *.V
+
+INPUT_DIR	/n/picasso/project/mm/mpeg/mpeg_encode/input/flowg
+
+INPUT
+sflowg.*	[0-149]
+END_INPUT
+
+# motion vector search parameters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		10
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	CROSS2
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+REFERENCE_FRAME	ORIGINAL
+
diff --git a/converter/ppm/ppmtompeg/examples/nametest.param b/converter/ppm/ppmtompeg/examples/nametest.param
new file mode 100644
index 00000000..445622fb
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/nametest.param
@@ -0,0 +1,60 @@
+# test suite parameter file
+
+PATTERN		IBBBPBBBBP
+OUTPUT		/tmp/foobar.mpg
+
+YUV_SIZE	352x240
+
+BASE_FILE_FORMAT	YUV
+INPUT_CONVERT	cat *.Y *.U *.V
+GOP_SIZE	30
+SLICES_PER_FRAME  1
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/tennis
+
+INPUT
+stennis.0
+stennis.*	[1-3]
+stennis.*	[4-5]
+stennis.*	[6-6]
+stennis.7
+stennis.*	[8-15]
+stennis.16
+stennis.17
+stennis.*	[18-34]
+END_INPUT
+
+
+
+# all of the remaining options have to do with the motion search and qscale
+#
+# change this only if you're unsatisfied with the CPU time or quality, or
+# are experimenting
+#
+
+# if this appears in the file, then in addition to testing luminance when
+# computing motion vectors, program will also take into account chrominance
+#
+# this option MUST appear before ERROR option, or it will be ignored
+#
+# CHROMINANCE
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		8
+
+PSEARCH_ALG	LOGARITHMIC
+
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/nosearch.param b/converter/ppm/ppmtompeg/examples/nosearch.param
new file mode 100644
index 00000000..453356c0
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/nosearch.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		I
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard1.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/par3.param b/converter/ppm/ppmtompeg/examples/par3.param
new file mode 100644
index 00000000..ca664334
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/par3.param
@@ -0,0 +1,43 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-39]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
+
+PARALLEL_TEST_FRAMES	3
+PARALLEL_TIME_CHUNKS	30
+
+PARALLEL
+bugs-bunny	keving	~keving/encode/bin/sun/mpeg_encode
+linus		keving	~keving/encode/bin/sun/mpeg_encode
+END_PARALLEL
diff --git a/converter/ppm/ppmtompeg/examples/par4.param b/converter/ppm/ppmtompeg/examples/par4.param
new file mode 100644
index 00000000..95a72c34
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/par4.param
@@ -0,0 +1,45 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-49]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
+
+PARALLEL_TEST_FRAMES	3
+# PARALLEL_TIME_CHUNKS	20
+PARALLEL_CHUNK_TAPER
+
+PARALLEL
+bugs-bunny	keving	~keving/encode/bin/sun/mpeg_encode
+linus		keving	~keving/encode/bin/sun/mpeg_encode
+zonker		keving	~keving/encode/bin/sun/mpeg_encode
+END_PARALLEL
diff --git a/converter/ppm/ppmtompeg/examples/par5.param b/converter/ppm/ppmtompeg/examples/par5.param
new file mode 100644
index 00000000..ad51a519
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/par5.param
@@ -0,0 +1,47 @@
+# speed test parameter file
+
+#PATTERN		IBBPBBPBBPBBPBB
+PATTERN		I
+
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
+
+PARALLEL_TEST_FRAMES	3
+PARALLEL_CHUNK_TAPER
+
+PARALLEL
+woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+gumby		keving	~keving/encode/bin/hp/mpeg_encode
+big-bird	keving	~keving/encode/bin/hp/mpeg_encode
+roger-rabbit	keving	~keving/encode/bin/sun/mpeg_encode
+END_PARALLEL
diff --git a/converter/ppm/ppmtompeg/examples/parallel.2 b/converter/ppm/ppmtompeg/examples/parallel.2
new file mode 100644
index 00000000..0ab153fb
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/parallel.2
@@ -0,0 +1,87 @@
+# speed test parameter file
+
+PATTERN		ibbpbbpbbpbbpbb
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	352x240
+
+INPUT_CONVERT	djpeg *
+
+INPUT_DIR	/n/picasso/project/mm/mpeg/mpeg_encode/input/flowg/jpeg
+
+INPUT
+sflowg.*.jpeg	[1-30]
+END_INPUT
+
+# the following are optional
+
+PARALLEL_PERFECT
+
+
+
+# specifies what the IO server must do before transmitting to remote sites
+IO_SERVER_CONVERT	*
+
+# specifies what the remote slave must do after receiving from IO server
+SLAVE_CONVERT		djpeg *
+
+PARALLEL
+# lines must be of form "machine <whitespace> username <whitespace> executable"
+# these guys are sorta slow:
+#elmer-fudd	keving	~keving/encode/bin/sun/mpeg_encode
+#elmer-fudd	keving	~keving/encode/bin/sun/mpeg_encode
+#bugs-bunny	keving	~keving/encode/bin/sun/mpeg_encode
+#REMOTE bugs-bunny keving ~keving/encode/src/mpeg_encode ~keving/encode/examples/parallel.test
+#roger-rabbit	keving	~keving/encode/bin/sun/mpeg_encode
+#tweety		keving	~keving/encode/bin/sun/mpeg_encode
+#mickey		keving	~keving/encode/bin/mickey/mpeg_encode
+#
+# these guys are pretty fast:
+#
+#big-bird	keving	~keving/encode/bin/hp/mpeg_encode
+#gumby		keving	~keving/encode/bin/hp/mpeg_encode
+charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#bugs-bunny	keving	~keving/encode/bin/sun/mpeg_encode
+#
+# remotes
+#
+#REMOTE anaconda	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE adder	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE moccasin	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE cobra	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE boa	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE asp	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE rattler	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE viper	keving	~keving/mpeg_encode ~keving/parallel.test
+# mamba doesn't seem to work for whatever reason...don't know why
+#REMOTE mamba	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE cse.lbl.gov kevin	~kevin/mpeg_encode ~kevin/parallel.test
+#REMOTE roger-rabbit keving	~keving/src/encode/bin/sun/mpeg_encode ~keving/src/encode/ParamExamples/parallel.test
+END_PARALLEL
+
+
+# motion vector search parameters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		8
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+
+REFERENCE_FRAME	ORIGINAL
+
diff --git a/converter/ppm/ppmtompeg/examples/parallel.param b/converter/ppm/ppmtompeg/examples/parallel.param
new file mode 100644
index 00000000..a158eae5
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/parallel.param
@@ -0,0 +1,141 @@
+# parameter file template with parallel execution
+#
+# you can use this as a template, copying it to a separate file then modifying
+# the copy
+#
+#
+# any line beginning with '#' is a comment
+#
+# no line should be longer than 255 characters
+#
+#
+# general format of each line is:
+#	<option> <spaces and/or tabs> <value>
+#
+# lines can generally be in any order
+#
+# only exception is the option 'INPUT' which must be followed by input
+# files in the order in which they must appear, followed by 'END_INPUT'
+#
+# <option> MUST be in UPPER CASE
+#
+
+PATTERN		IBBPBBPBBPBBP
+OUTPUT		/n/picasso/users/keving/encode/output.mpg
+
+# mpeg_encode really only accepts 3 different file formats, but using a
+# conversion statement it can effectively handle ANY file format
+#
+# you must specify whether you will convert to PNM or PPM or YUV format
+#	(must be upper case)
+#
+BASE_FILE_FORMAT	YUV
+
+#
+# if YUV format (or using parallel version), must provide width and height
+# YUV_SIZE	widthxheight
+# this option is ignored if BASE_FILE_FORMAT is PPM or PNM and you're running
+# on just one machine
+#
+YUV_SIZE	352x240
+
+# the conversion statement
+#
+# Each occurrence of '*' will be replaced by the input file
+#
+# e.g., if you have a bunch of GIF files, then this might be:
+#	INPUT_CONVERT	giftoppm *
+#
+# e.g., if you have a bunch of files like a.Y a.U a.V, etc., then:
+#	INPUT_CONVERT	cat *.Y *.U *.V
+#
+# e.g., if you are grabbing from laser disc you might have something like
+#	INPUT_CONVERT	goto frame *; grabppm
+# 'INPUT_CONVERT *' means the files are already in the base file format
+#
+INPUT_CONVERT	*
+
+# number of frames in a GOP.
+#
+# since each GOP must have at least one I-frame, the encoder will find the
+# the first I-frame after GOP_SIZE frames to start the next GOP
+#
+# later, will add more flexible GOP signalling
+#
+GOP_SIZE	6
+
+# number of slices in a frame
+#
+# 1 is a good number.  another possibility is the number of macroblock rows
+# (which is the height divided by 16)
+#
+SLICES_PER_FRAME	1
+
+# directory to get all input files from (makes this file easier to read)
+INPUT_DIR	/n/picasso/users/keving/encode/input/tennis
+
+INPUT
+# '*' is replaced by the numbers 01, 02, 03, 04
+# if I instead do [01-11], it would be 01, 02, ..., 09, 10, 11
+# if I instead do [1-11], it would be 1, 2, 3, ..., 9, 10, 11
+# if I instead do [1-11+3], it would be 1, 4, 7, 10
+# the program assumes none of your input files has a name ending in ']'
+# if you do, too bad!!!
+#
+#
+stennis.*.yuv	[0-7]
+# can have more files here if you want...there is no limit on the number
+# of files
+END_INPUT
+
+
+
+# all of the remaining options have to do with the motion search and qscale
+
+# FULL or HALF -- must be upper case
+PIXEL		HALF
+
+# means +/- this many pixels
+RANGE		10
+
+# this must be one of {EXHAUSTIVE, SUBSAMPLE, LOGARITHMIC}
+PSEARCH_ALG	LOGARITHMIC
+
+# this must be one of {SIMPLE, CROSS2, EXHAUSTIVE}
+#
+# note that EXHAUSTIVE is really, really, really slow
+#
+BSEARCH_ALG	CROSS2
+
+#
+# these specify the q-scale for I, P, and B frames
+# (values must be between 1 and 31)
+#
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# this must be ORIGINAL or DECODED
+REFERENCE_FRAME	ORIGINAL
+
+
+# the following two are optional  (default = 10, 60)
+
+# number of frames to do initially to gauge speed of machine
+PARALLEL_TEST_FRAMES	3
+
+# number of seconds per chunk thereafter
+PARALLEL_TIME_CHUNKS	30
+
+
+PARALLEL
+# lines must be of form "machine <whitespace> username <whitespace> executable"
+charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#REMOTE charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode ~keving/encode/examples/parallel.param
+# remote machine: "REMOTE machine username executable param_file"
+# mickey		keving	~keving/encode/bin/dec-5000/mpeg_encode
+#REMOTE mickey		keving	~keving/encode/bin/dec-5000/mpeg_encode ~keving/encode/examples/parallel.param
+#REMOTE mickey		keving	~keving/encode/bin/dec-5000/mpeg_encode ~keving/encode/examples/parallel.param
+REMOTE woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode ~keving/encode/examples/parallel.param
+END_PARALLEL
diff --git a/converter/ppm/ppmtompeg/examples/parallel.test b/converter/ppm/ppmtompeg/examples/parallel.test
new file mode 100644
index 00000000..2489d847
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/parallel.test
@@ -0,0 +1,85 @@
+# speed test parameter file
+
+PATTERN		ibbpbbpbbpbbpbb
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	352x240
+
+INPUT_CONVERT	djpeg *
+
+INPUT_DIR	/n/picasso/project/mm/mpeg/mpeg_encode/input/flowg/jpeg
+
+INPUT
+sflowg.*.jpeg	[1-30]
+END_INPUT
+
+# the following two are optional  (default = 10, 60)
+
+# number of frames to do initially to gauge speed of machine
+PARALLEL_TEST_FRAMES	3
+
+# number of seconds per chunk thereafter
+PARALLEL_TIME_CHUNKS	30
+
+# specifies what the IO server must do before transmitting to remote sites
+IO_SERVER_CONVERT	*
+
+# specifies what the remote slave must do after receiving from IO server
+SLAVE_CONVERT		djpeg *
+
+PARALLEL
+# lines must be of form "machine <whitespace> username <whitespace> executable"
+# these guys are sorta slow:
+#REMOTE bugs-bunny keving ~keving/encode/src/mpeg_encode ~keving/encode/examples/parallel.test
+#mickey		keving	~keving/encode/bin/mickey/mpeg_encode
+#
+# these guys are pretty fast:
+#
+#big-bird	keving	~keving/encode/bin/hp/mpeg_encode
+#gumby		keving	~keving/encode/bin/hp/mpeg_encode
+#charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+bugs-bunny	keving	~keving/encode/bin/sun/mpeg_encode
+#roger-rabbit	keving	~keving/encode/bin/sun/mpeg_encode
+#
+# remotes
+#
+#REMOTE anaconda	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE adder	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE moccasin	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE cobra	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE boa	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE asp	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE rattler	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE viper	keving	~keving/mpeg_encode ~keving/parallel.test
+# mamba doesn't seem to work for whatever reason...don't know why
+#REMOTE mamba	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE cse.lbl.gov kevin	~kevin/mpeg_encode ~kevin/parallel.test
+#REMOTE roger-rabbit keving	~keving/src/encode/bin/sun/mpeg_encode ~keving/src/encode/ParamExamples/parallel.test
+END_PARALLEL
+
+
+# motion vector search parameters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		8
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+
+REFERENCE_FRAME	ORIGINAL
+
diff --git a/converter/ppm/ppmtompeg/examples/param.template b/converter/ppm/ppmtompeg/examples/param.template
new file mode 100644
index 00000000..ce81e474
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/param.template
@@ -0,0 +1,107 @@
+# parameter file template
+#
+# any line beginning with '#' is a comment
+#
+# no line should be longer than 255 characters
+#
+#
+# general format of each line is:
+#	<option> <spaces and/or tabs> <value>
+#
+# lines can generally be in any order
+#
+# only exception is the option 'INPUT' which must be followed by input
+# files in the order in which they must appear, followed by 'END_INPUT'
+#
+# <option> MUST be in UPPER CASE
+#
+
+PATTERN		IBBPBBPBBPBBP
+OUTPUT		output/food
+
+# mpeg_encode really only accepts 2 different file formats, but using a
+# conversion statement it can effectively handle ANY file format
+#
+# you must specify whether you will convert to PPM or YUV format
+#	(must be upper case)
+#
+BASE_FILE_FORMAT	YUV
+
+#
+# if YUV format, must provide width and height
+# YUV_SIZE	width height
+# this option is ignored if BASE_FILE_FORMAT is PPM
+#
+YUV_SIZE	352x240
+
+# the conversion statement
+#
+# Each occurrence of '*' will be replaced by the input file
+#
+# e.g., if you have a bunch of GIF files, then this might be:
+#	INPUT_CONVERT	giftoppm *
+#
+# e.g., if you have a bunch of files like a.Y a.U a.V, etc., then:
+#	INPUT_CONVERT	cat *.Y *.U *.V
+#
+# e.g., if you are grabbing from laser disc you might have something like
+#	INPUT_CONVERT	goto frame *; grabppm
+# 'INPUT_CONVERT *' means the files are already in the base file format
+#
+INPUT_CONVERT	cat *.Y *.U *.V
+
+# number of frames in a GOP.
+#
+# since each GOP must have at least one I-frame, the encoder will find the
+# the first I-frame after GOP_SIZE frames to start the next GOP
+#
+# later, will add more flexible GOP signalling
+#
+GOP_SIZE	30
+
+# directory to get all input files from (makes this file easier to read)
+INPUT_DIR	links/payam
+
+INPUT
+# '*' is replaced by the numbers 01, 02, 03, 04
+# if I instead do [01-11], it would be 01, 02, ..., 09, 10, 11
+# if I instead do [1-11], it would be 1, 2, 3, ..., 9, 10, 11
+# the program assumes none of your input files has a name ending in ']'
+# if you do, too bad!!!
+#
+#
+stennis.*	[0-23]
+# can have more files here if you want...as many as you want
+END_INPUT
+
+
+
+# all of the remaining options have to do with the motion search and qscale
+#
+# change this only if you're unsatisfied with the CPU time or quality, or
+# are experimenting
+#
+
+# if this appears in the file, then in addition to testing luminance when
+# computing motion vectors, program will also take into account chrominance
+#
+# this option MUST appear before ERROR option, or it will be ignored
+#
+# CHROMINANCE
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		8
+
+# YES or NO -- must be upper case
+SUBSAMPLE	NO
+
+IQSCALE		10
+PQSCALE		15
+BQSCALE		25
+
diff --git a/converter/ppm/ppmtompeg/examples/payam.param b/converter/ppm/ppmtompeg/examples/payam.param
new file mode 100644
index 00000000..ce886914
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/payam.param
@@ -0,0 +1,37 @@
+# parameter file for payam's images
+
+#PATTERN		IBBPBBPBBPBBPBBI
+PATTERN		II
+OUTPUT		output/payam.mpg
+GOP_SIZE	20
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PNM
+
+INPUT_CONVERT *
+INPUT_DIR	links/payam
+
+INPUT
+kh*.pnm	[1-3]
+END_INPUT
+
+
+# motion vector search paramters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		8
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+
+IQSCALE		10
+PQSCALE		15
+BQSCALE		25
+
diff --git a/converter/ppm/ppmtompeg/examples/payam18.param b/converter/ppm/ppmtompeg/examples/payam18.param
new file mode 100644
index 00000000..7f7b767e
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/payam18.param
@@ -0,0 +1,34 @@
+# parameter file for payam's images
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/payam18.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PNM
+
+INPUT_CONVERT *
+INPUT_DIR	/n/picasso/users/keving/encode/input/payam
+
+INPUT
+kh*.pnm	[1-39]
+END_INPUT
+
+
+# motion vector search paramters
+
+# FULL or HALF -- must be upper case
+PIXEL		HALF
+
+# means +/- this many pixels
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/pnm.param b/converter/ppm/ppmtompeg/examples/pnm.param
new file mode 100644
index 00000000..6fce2be4
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/pnm.param
@@ -0,0 +1,37 @@
+# speed test parameter file
+
+PATTERN		I
+OUTPUT		output/food
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	links/flowg
+
+INPUT
+sflowg.*.ppm	[01-10]
+END_INPUT
+
+
+# motion vector search parameters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		8
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		31
+PQSCALE		10
+BQSCALE		25
+
diff --git a/converter/ppm/ppmtompeg/examples/ppm.param b/converter/ppm/ppmtompeg/examples/ppm.param
new file mode 100644
index 00000000..6fce2be4
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/ppm.param
@@ -0,0 +1,37 @@
+# speed test parameter file
+
+PATTERN		I
+OUTPUT		output/food
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	links/flowg
+
+INPUT
+sflowg.*.ppm	[01-10]
+END_INPUT
+
+
+# motion vector search parameters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		8
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		31
+PQSCALE		10
+BQSCALE		25
+
diff --git a/converter/ppm/ppmtompeg/examples/prof.b.param b/converter/ppm/ppmtompeg/examples/prof.b.param
new file mode 100644
index 00000000..2b8253fd
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/prof.b.param
@@ -0,0 +1,48 @@
+# test suite parameter file
+
+PATTERN		IBBBBBBBBI
+OUTPUT		output/food
+
+BASE_FILE_FORMAT	PPM
+INPUT_CONVERT	giftoppm *
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+INPUT_DIR	Test/ts
+
+INPUT
+med*.gif	[030-039]
+END_INPUT
+
+
+
+# all of the remaining options have to do with the motion search and qscale
+#
+# change this only if you're unsatisfied with the CPU time or quality, or
+# are experimenting
+#
+
+# if this appears in the file, then in addition to testing luminance when
+# computing motion vectors, program will also take into account chrominance
+#
+# this option MUST appear before ERROR option, or it will be ignored
+#
+# CHROMINANCE
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		8
+
+# YES or NO -- must be upper case
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
diff --git a/converter/ppm/ppmtompeg/examples/prof.bhalf.param b/converter/ppm/ppmtompeg/examples/prof.bhalf.param
new file mode 100644
index 00000000..8d6de83a
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/prof.bhalf.param
@@ -0,0 +1,48 @@
+# test suite parameter file
+
+PATTERN		IBBBBBBBBI
+OUTPUT		output/food
+
+BASE_FILE_FORMAT	PPM
+INPUT_CONVERT	giftoppm *
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+INPUT_DIR	Test/ts
+
+INPUT
+med*.gif	[030-039]
+END_INPUT
+
+
+
+# all of the remaining options have to do with the motion search and qscale
+#
+# change this only if you're unsatisfied with the CPU time or quality, or
+# are experimenting
+#
+
+# if this appears in the file, then in addition to testing luminance when
+# computing motion vectors, program will also take into account chrominance
+#
+# this option MUST appear before ERROR option, or it will be ignored
+#
+# CHROMINANCE
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		HALF
+
+# means +/- this many pixels
+RANGE		8
+
+# YES or NO -- must be upper case
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	CROSS2
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
diff --git a/converter/ppm/ppmtompeg/examples/prof.param b/converter/ppm/ppmtompeg/examples/prof.param
new file mode 100644
index 00000000..591eaa67
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/prof.param
@@ -0,0 +1,36 @@
+# speed test parameter file
+
+PATTERN I
+#PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	CROSS2
+
+#REFERENCE_FRAME	DECODED
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/prof.ss.param b/converter/ppm/ppmtompeg/examples/prof.ss.param
new file mode 100644
index 00000000..ef339e7a
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/prof.ss.param
@@ -0,0 +1,48 @@
+# test suite parameter file
+
+PATTERN		IPPPPPPPPP
+OUTPUT		output/food
+
+BASE_FILE_FORMAT	PPM
+INPUT_CONVERT	giftoppm *
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+INPUT_DIR	Test/ts
+
+INPUT
+med*.gif	[030-039]
+END_INPUT
+
+
+
+# all of the remaining options have to do with the motion search and qscale
+#
+# change this only if you're unsatisfied with the CPU time or quality, or
+# are experimenting
+#
+
+# if this appears in the file, then in addition to testing luminance when
+# computing motion vectors, program will also take into account chrominance
+#
+# this option MUST appear before ERROR option, or it will be ignored
+#
+# CHROMINANCE
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		16
+
+# YES or NO -- must be upper case
+PSEARCH_ALG	SUBSAMPLE
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
diff --git a/converter/ppm/ppmtompeg/examples/prof2.param b/converter/ppm/ppmtompeg/examples/prof2.param
new file mode 100644
index 00000000..4073606a
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/prof2.param
@@ -0,0 +1,36 @@
+# speed test parameter file
+
+PATTERN IPPP
+#PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
+
diff --git a/converter/ppm/ppmtompeg/examples/prof3.param b/converter/ppm/ppmtompeg/examples/prof3.param
new file mode 100644
index 00000000..4581eade
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/prof3.param
@@ -0,0 +1,36 @@
+# speed test parameter file
+
+PATTERN IBB
+#PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
+
diff --git a/converter/ppm/ppmtompeg/examples/prof4.param b/converter/ppm/ppmtompeg/examples/prof4.param
new file mode 100644
index 00000000..583fb43c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/prof4.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
+
diff --git a/converter/ppm/ppmtompeg/examples/remote.test b/converter/ppm/ppmtompeg/examples/remote.test
new file mode 100644
index 00000000..d548510f
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/remote.test
@@ -0,0 +1,85 @@
+# speed test parameter file
+
+PATTERN		ibbpbbpbbpbbpbb
+OUTPUT		/n/picasso/users/keving/encode/output/good.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	cat *.Y *.U *.V
+
+INPUT_DIR	/n/picasso/users/keving/links/flowg
+
+INPUT
+sflowg.*	[0-40]
+END_INPUT
+
+# the following two are optional  (default = 10, 60)
+
+# number of frames to do initially to gauge speed of machine
+PARALLEL_TEST_FRAMES	3
+
+# number of seconds per chunk thereafter
+PARALLEL_TIME_CHUNKS	30
+
+
+PARALLEL
+# lines must be of form "machine <whitespace> username <whitespace> executable"
+# these guys are sorta slow:
+#elmer-fudd	keving	~keving/src/encode/bin/sun/mpeg_encode
+#zonker		keving	~keving/src/encode/bin/sun/mpeg_encode
+#roger-rabbit	keving	~keving/src/encode/bin/sun/mpeg_encode
+#tweety		keving	~keving/src/encode/bin/sun/mpeg_encode
+#mickey		keving	~keving/src/encode/bin/mickey/mpeg_encode
+#
+REMOTE zonker keving ~keving/encode/src/mpeg_encode ~keving/encode/examples/remote.test
+REMOTE linus keving ~keving/encode/src/mpeg_encode ~keving/encode/examples/remote.test
+REMOTE roger-rabbit keving ~keving/encode/src/mpeg_encode ~keving/encode/examples/remote.test
+REMOTE bugs-bunny keving ~keving/encode/src/mpeg_encode ~keving/encode/examples/remote.test
+REMOTE elmer-fudd keving ~keving/encode/src/mpeg_encode ~keving/encode/examples/remote.test
+# these guys are pretty fast:
+#
+#big-bird	keving	~keving/src/encode/bin/hp/mpeg_encode
+#gumby		keving	~keving/src/encode/bin/hp/mpeg_encode
+#charlie-brown	keving	~keving/src/encode/bin/dec/mpeg_encode
+#woodstock	keving	~keving/src/encode/bin/dec/mpeg_encode
+#bugs-bunny	keving	~keving/src/encode/bin/sun/mpeg_encode
+#
+# remotes
+#
+#REMOTE anaconda	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE adder	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE moccasin	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE cobra	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE boa	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE asp	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE rattler	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE viper	keving	~keving/mpeg_encode ~keving/parallel.test
+# mamba doesn't seem to work for whatever reason...don't know why
+#REMOTE mamba	keving	~keving/mpeg_encode ~keving/parallel.test
+#REMOTE cse.lbl.gov kevin	~kevin/mpeg_encode ~kevin/parallel.test
+#REMOTE roger-rabbit keving	~keving/src/encode/bin/sun/mpeg_encode ~keving/src/encode/ParamExamples/parallel.test
+END_PARALLEL
+
+
+# motion vector search parameters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		FULL
+
+# means +/- this many pixels
+RANGE		4
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search1.param b/converter/ppm/ppmtompeg/examples/search1.param
new file mode 100644
index 00000000..779ffa0f
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search1.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard1.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search10.param b/converter/ppm/ppmtompeg/examples/search10.param
new file mode 100644
index 00000000..c44c7c72
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search10.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard10.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search11.param b/converter/ppm/ppmtompeg/examples/search11.param
new file mode 100644
index 00000000..78935268
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search11.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard11.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		20
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search12.param b/converter/ppm/ppmtompeg/examples/search12.param
new file mode 100644
index 00000000..d7a115c3
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search12.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard12.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search13.param b/converter/ppm/ppmtompeg/examples/search13.param
new file mode 100644
index 00000000..80bb57f5
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search13.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard13.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		20
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search14.param b/converter/ppm/ppmtompeg/examples/search14.param
new file mode 100644
index 00000000..af0ebb9c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search14.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search15.param b/converter/ppm/ppmtompeg/examples/search15.param
new file mode 100644
index 00000000..1df18350
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search15.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard15.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		20
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search16.param b/converter/ppm/ppmtompeg/examples/search16.param
new file mode 100644
index 00000000..3523dce3
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search16.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard16.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		20
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search17.param b/converter/ppm/ppmtompeg/examples/search17.param
new file mode 100644
index 00000000..89c3cb91
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search17.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard17.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		20
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search18.param b/converter/ppm/ppmtompeg/examples/search18.param
new file mode 100644
index 00000000..d659d914
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search18.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard18.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search19.5.param b/converter/ppm/ppmtompeg/examples/search19.5.param
new file mode 100644
index 00000000..22827fce
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search19.5.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-15]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search19.param b/converter/ppm/ppmtompeg/examples/search19.param
new file mode 100644
index 00000000..88f9d613
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search19.param
@@ -0,0 +1,42 @@
+# speed test parameter file
+
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/charlie-brown/project/mm/mpeg/mpeg_encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+
+
+
+PATTERN		IBBPBBPBBPBBPBB
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+# LOGARITHMIC, TWOLEVEL, SUBSAMPLE, EXHAUSTIVE
+PSEARCH_ALG	LOGARITHMIC
+
+# CROSS2, SIMPLE
+BSEARCH_ALG	CROSS2
+
+# DECODED or ORIGINAL
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search2.param b/converter/ppm/ppmtompeg/examples/search2.param
new file mode 100644
index 00000000..c4f6300c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search2.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard2.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search20.param b/converter/ppm/ppmtompeg/examples/search20.param
new file mode 100644
index 00000000..efd05f6d
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search20.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard20.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		10
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search21.param b/converter/ppm/ppmtompeg/examples/search21.param
new file mode 100644
index 00000000..6acb79ac
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search21.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard21.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search22.param b/converter/ppm/ppmtompeg/examples/search22.param
new file mode 100644
index 00000000..252cb25b
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search22.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard22.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		20
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search23.param b/converter/ppm/ppmtompeg/examples/search23.param
new file mode 100644
index 00000000..5e7a7620
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search23.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard23.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		20
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search24.param b/converter/ppm/ppmtompeg/examples/search24.param
new file mode 100644
index 00000000..0ca71e6d
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search24.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard24.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	SUBSAMPLE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search25.param b/converter/ppm/ppmtompeg/examples/search25.param
new file mode 100644
index 00000000..724ce5a4
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search25.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard25.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	SUBSAMPLE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search26.param b/converter/ppm/ppmtompeg/examples/search26.param
new file mode 100644
index 00000000..1a7081cd
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search26.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard26.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		10
+
+PSEARCH_ALG	SUBSAMPLE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search27.param b/converter/ppm/ppmtompeg/examples/search27.param
new file mode 100644
index 00000000..49da7e7a
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search27.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard27.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		20
+
+PSEARCH_ALG	SUBSAMPLE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search28.param b/converter/ppm/ppmtompeg/examples/search28.param
new file mode 100644
index 00000000..f78331aa
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search28.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard28.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		20
+
+PSEARCH_ALG	SUBSAMPLE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search29.param b/converter/ppm/ppmtompeg/examples/search29.param
new file mode 100644
index 00000000..52f89a90
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search29.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard29.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search3.param b/converter/ppm/ppmtompeg/examples/search3.param
new file mode 100644
index 00000000..4c00af6c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search3.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard3.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		20
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search30.param b/converter/ppm/ppmtompeg/examples/search30.param
new file mode 100644
index 00000000..5664dbb8
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search30.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard30.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		20
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search31.param b/converter/ppm/ppmtompeg/examples/search31.param
new file mode 100644
index 00000000..3d8bc0cd
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search31.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search32.param b/converter/ppm/ppmtompeg/examples/search32.param
new file mode 100644
index 00000000..a4675cee
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search32.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard32.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		4
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search33.param b/converter/ppm/ppmtompeg/examples/search33.param
new file mode 100644
index 00000000..465132c6
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search33.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard33.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		4
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search34.param b/converter/ppm/ppmtompeg/examples/search34.param
new file mode 100644
index 00000000..f53a91f3
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search34.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard34.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		4
+
+PSEARCH_ALG	SUBSAMPLE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search35.param b/converter/ppm/ppmtompeg/examples/search35.param
new file mode 100644
index 00000000..5a7fa27f
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search35.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard35.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		4
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search36.param b/converter/ppm/ppmtompeg/examples/search36.param
new file mode 100644
index 00000000..66fb8e7f
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search36.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard36.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		4
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search37.param b/converter/ppm/ppmtompeg/examples/search37.param
new file mode 100644
index 00000000..f5f5d4d3
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search37.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard37.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		4
+
+PSEARCH_ALG	SUBSAMPLE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search38.param b/converter/ppm/ppmtompeg/examples/search38.param
new file mode 100644
index 00000000..b8186ce2
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search38.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard38.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		4
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search39.param b/converter/ppm/ppmtompeg/examples/search39.param
new file mode 100644
index 00000000..ab18d28b
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search39.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard39.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		4
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search4.param b/converter/ppm/ppmtompeg/examples/search4.param
new file mode 100644
index 00000000..68285e6d
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search4.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard4.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		20
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search40.param b/converter/ppm/ppmtompeg/examples/search40.param
new file mode 100644
index 00000000..3fd65b6e
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search40.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard40.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		8
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search41.param b/converter/ppm/ppmtompeg/examples/search41.param
new file mode 100644
index 00000000..3a47baf7
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search41.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard41.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		8
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search42.param b/converter/ppm/ppmtompeg/examples/search42.param
new file mode 100644
index 00000000..3ade2c85
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search42.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard42.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		8
+
+PSEARCH_ALG	SUBSAMPLE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search43.param b/converter/ppm/ppmtompeg/examples/search43.param
new file mode 100644
index 00000000..e2c044a4
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search43.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard43.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		8
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search44.param b/converter/ppm/ppmtompeg/examples/search44.param
new file mode 100644
index 00000000..47c97e89
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search44.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard44.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		8
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search45.param b/converter/ppm/ppmtompeg/examples/search45.param
new file mode 100644
index 00000000..c4131d33
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search45.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard45.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		8
+
+PSEARCH_ALG	SUBSAMPLE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search46.param b/converter/ppm/ppmtompeg/examples/search46.param
new file mode 100644
index 00000000..cc9c67bf
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search46.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard46.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		8
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search47.param b/converter/ppm/ppmtompeg/examples/search47.param
new file mode 100644
index 00000000..7527228e
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search47.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard47.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		8
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search48.param b/converter/ppm/ppmtompeg/examples/search48.param
new file mode 100644
index 00000000..3d75a6a2
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search48.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	SUBSAMPLE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search49.param b/converter/ppm/ppmtompeg/examples/search49.param
new file mode 100644
index 00000000..c25ab724
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search49.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search5.param b/converter/ppm/ppmtompeg/examples/search5.param
new file mode 100644
index 00000000..5a1ad629
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search5.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard5.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		20
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search50.param b/converter/ppm/ppmtompeg/examples/search50.param
new file mode 100644
index 00000000..6711ebb7
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search50.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search51.param b/converter/ppm/ppmtompeg/examples/search51.param
new file mode 100644
index 00000000..6609e381
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search51.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-19]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	EXHAUSTIVE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search52.5.param b/converter/ppm/ppmtompeg/examples/search52.5.param
new file mode 100644
index 00000000..be34be4b
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search52.5.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-15]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search52.param b/converter/ppm/ppmtompeg/examples/search52.param
new file mode 100644
index 00000000..246aa978
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search52.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search53.param b/converter/ppm/ppmtompeg/examples/search53.param
new file mode 100644
index 00000000..81afb3bc
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search53.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-15]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	EXHAUSTIVE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search6.param b/converter/ppm/ppmtompeg/examples/search6.param
new file mode 100644
index 00000000..47c36a0c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search6.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard6.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		20
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/search7.param b/converter/ppm/ppmtompeg/examples/search7.param
new file mode 100644
index 00000000..f5d1224d
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search7.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard7.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		10
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search8.param b/converter/ppm/ppmtompeg/examples/search8.param
new file mode 100644
index 00000000..f3b17e96
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search8.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard8.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		20
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/search9.param b/converter/ppm/ppmtompeg/examples/search9.param
new file mode 100644
index 00000000..80051112
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/search9.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard9.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		FULL
+
+RANGE		20
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/seq.param b/converter/ppm/ppmtompeg/examples/seq.param
new file mode 100644
index 00000000..665c22a7
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/seq.param
@@ -0,0 +1,36 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/zonker/video/sequoia.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	/n/zonker/video/getframe *
+
+INPUT_DIR	/n/zonker/video/seqframes
+
+INPUT
+# really goes up to 200
+*	[0-1000]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		15
+PQSCALE		18
+BQSCALE		30
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/sequoia.param b/converter/ppm/ppmtompeg/examples/sequoia.param
new file mode 100644
index 00000000..1b93d880
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/sequoia.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/sequoia.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	352x240
+
+INPUT_CONVERT	djpeg *
+
+INPUT_DIR	/n/zonker/video/seqframes
+
+INPUT
+sequoia*.jpeg	[378-600]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		22
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/sequoia19.param b/converter/ppm/ppmtompeg/examples/sequoia19.param
new file mode 100644
index 00000000..52e5a72b
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/sequoia19.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/charlie-brown/project/mm/mpeg/mpeg_encode/output/seq.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	352x240
+
+INPUT_CONVERT	/n/zonker/video/seqframes/gframe /n/video/video/sequoia * 640x480.100 352 240
+
+INPUT_DIR	/n/zonker/video
+
+INPUT
+*	[0-199]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/sequoia2.param b/converter/ppm/ppmtompeg/examples/sequoia2.param
new file mode 100644
index 00000000..676cc2e4
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/sequoia2.param
@@ -0,0 +1,36 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/sequoia.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	352x240
+
+INPUT_CONVERT	djpeg *
+
+INPUT_DIR	/n/zonker/video/seqframes
+
+INPUT
+# really goes up to 200
+sequoia*.jpeg	[001-200]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		15
+PQSCALE		18
+BQSCALE		30
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/simple.param b/converter/ppm/ppmtompeg/examples/simple.param
new file mode 100644
index 00000000..14964829
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/simple.param
@@ -0,0 +1,40 @@
+# Simple parameter file
+
+OUTPUT		./test.mpg
+GOP_SIZE	15
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/charlie-brown/project/mm/mpeg/mpeg_encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-14]
+END_INPUT
+
+PATTERN		IBBPBBPBBPBBPBBPBBPBBP
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10 4
+
+# LOGARITHMIC, TWOLEVEL, SUBSAMPLE, EXHAUSTIVE
+PSEARCH_ALG	 LOGARITHMIC
+
+# CROSS2, SIMPLE
+BSEARCH_ALG	CROSS2
+
+# DECODED or ORIGINAL
+REFERENCE_FRAME	ORIGINAL
+
diff --git a/converter/ppm/ppmtompeg/examples/slice.param b/converter/ppm/ppmtompeg/examples/slice.param
new file mode 100644
index 00000000..308d2053
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/slice.param
@@ -0,0 +1,47 @@
+# test suite parameter file
+
+PATTERN		IIIIIIIII
+OUTPUT		ts.mpg
+
+BASE_FILE_FORMAT	PPM
+INPUT_CONVERT	giftoppm *
+GOP_SIZE		1
+SLICES_PER_FRAME	170000
+
+INPUT_DIR	ts
+
+INPUT
+med*.gif	[030-034]
+END_INPUT
+
+
+
+# all of the remaining options have to do with the motion search and qscale
+#
+# change this only if you're unsatisfied with the CPU time or quality, or
+# are experimenting
+#
+
+# if this appears in the file, then in addition to testing luminance when
+# computing motion vectors, program will also take into account chrominance
+#
+# this option MUST appear before ERROR option, or it will be ignored
+#
+# CHROMINANCE
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		HALF
+
+# means +/- this many pixels
+RANGE		4
+
+# YES or NO -- must be upper case
+SUBSAMPLE	NO
+
+IQSCALE		31
+PQSCALE		10
+BQSCALE		25
+
diff --git a/converter/ppm/ppmtompeg/examples/slimed.param b/converter/ppm/ppmtompeg/examples/slimed.param
new file mode 100644
index 00000000..b5938b94
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/slimed.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		I
+OUTPUT		/n/picasso/users/keving/encode/output/slimed.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	480x360
+
+INPUT_CONVERT	djpeg *
+
+INPUT_DIR	../input/slimed
+
+INPUT
+slimed.*.jpeg	[1-100]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		20
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/slowspeed.param b/converter/ppm/ppmtompeg/examples/slowspeed.param
new file mode 100644
index 00000000..321313be
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/slowspeed.param
@@ -0,0 +1,38 @@
+# speed test parameter file
+
+PATTERN		ibbpbbpbbpbbpbb
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	cat *.Y *.U *.V
+
+INPUT_DIR	/n/picasso/project/mm/mpeg/mpeg_encode/input/flowg
+
+INPUT
+sflowg.*	[0-149]
+END_INPUT
+
+# motion vector search parameters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		HALF
+
+# means +/- this many pixels
+RANGE		12
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	CROSS2
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+REFERENCE_FRAME	ORIGINAL
+
diff --git a/converter/ppm/ppmtompeg/examples/stanford.param b/converter/ppm/ppmtompeg/examples/stanford.param
new file mode 100644
index 00000000..4c87334c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/stanford.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/stanford.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		4
+PQSCALE		4
+BQSCALE		8
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		8
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/subtest.param b/converter/ppm/ppmtompeg/examples/subtest.param
new file mode 100644
index 00000000..7fb4f585
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/subtest.param
@@ -0,0 +1,34 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard18.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	SUB4
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg/yuvsub
+
+INPUT
+*	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/temp.param b/converter/ppm/ppmtompeg/examples/temp.param
new file mode 100644
index 00000000..209fdfb4
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/temp.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/flow.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/flowg
+
+INPUT
+sflowg.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/template.param b/converter/ppm/ppmtompeg/examples/template.param
new file mode 100644
index 00000000..66b6dd98
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/template.param
@@ -0,0 +1,154 @@
+# parameter file template with lots of comments to assist you
+#
+# you can use this as a template, copying it to a separate file then modifying
+# the copy
+#
+#
+# any line beginning with '#' is a comment
+#
+# no line should be longer than 255 characters
+#
+#
+# general format of each line is:
+#	<option> <spaces and/or tabs> <value>
+#
+# lines can generally be in any order
+#
+# an exception is the option 'INPUT' which must be followed by input
+# files in the order in which they must appear, followed by 'END_INPUT'
+#
+# Also, if you use the `command` method of generating input file names,
+# the command will only be executed in the INPUT_DIR if INPUT_DIR preceeds
+# the INPUT parameter.
+#
+# <option> MUST be in UPPER CASE
+#
+
+PATTERN		IBBPBBPBBPBBPBBP
+OUTPUT		output.mpg
+
+# mpeg_encode really only accepts 3 different file formats, but using a
+# conversion statement it can effectively handle ANY file format
+#
+# You must specify the type of the input files.  The choices are:
+#    YUV, PPM, JMOVIE, Y, JPEG, PNM
+#	(must be upper case)
+#
+BASE_FILE_FORMAT	YUV
+
+#
+# if YUV format (or using parallel version), must provide width and height
+# YUV_SIZE	widthxheight
+# this option is ignored if BASE_FILE_FORMAT is not YUV and you're running
+# on just one machine
+#
+YUV_SIZE	352x240
+
+# If you are using YUV, there are different supported file formats.
+# EYUV or UCB are the same as previous versions of this encoder.
+# (All the Y's, then U's then V's, in 4:2:0 subsampling.)
+# Other formats, such as Abekas, Phillips, or a general format are
+# permissible, the general format is a string of Y's, U's, and V's
+# to specify the file order.
+
+INPUT_FORMAT UCB
+
+# the conversion statement
+#
+# Each occurrence of '*' will be replaced by the input file
+#
+# e.g., if you have a bunch of GIF files, then this might be:
+#	INPUT_CONVERT	giftoppm *
+#
+# e.g., if you have a bunch of files like a.Y a.U a.V, etc., then:
+#	INPUT_CONVERT	cat *.Y *.U *.V
+#
+# e.g., if you are grabbing from laser disc you might have something like
+#	INPUT_CONVERT	goto frame *; grabppm
+# 'INPUT_CONVERT *' means the files are already in the base file format
+#
+INPUT_CONVERT	*
+
+# number of frames in a GOP.
+#
+# since each GOP must have at least one I-frame, the encoder will find the
+# the first I-frame after GOP_SIZE frames to start the next GOP
+#
+# later, will add more flexible GOP signalling
+#
+GOP_SIZE	16
+
+# number of slices in a frame
+#
+# 1 is a good number.  another possibility is the number of macroblock rows
+# (which is the height divided by 16)
+#
+SLICES_PER_FRAME	1
+
+# directory to get all input files from (makes this file easier to read)
+INPUT_DIR	../input/tennis
+
+# There are a bunch of ways to specify the input files.
+# from a simple one-per-line listing, to the following 
+# way of numbering them.  See the manual for more information.
+INPUT
+# '*' is replaced by the numbers 01, 02, 03, 04
+# if I instead do [01-11], it would be 01, 02, ..., 09, 10, 11
+# if I instead do [1-11], it would be 1, 2, 3, ..., 9, 10, 11
+# if I instead do [1-11+3], it would be 1, 4, 7, 10
+# the program assumes none of your input files has a name ending in ']'
+# if you do, too bad!!!
+#
+#
+stennis.*.yuv	[0-23]
+# can have more files here if you want...there is no limit on the number
+# of files
+END_INPUT
+
+
+
+# Many of the remaining options have to do with the motion search and qscale
+
+# FULL or HALF -- must be upper case
+PIXEL		HALF
+
+# means +/- this many pixels for both P and B frame searches
+# specify two numbers if you wish to serc different ranges in the two.
+RANGE		10
+
+# this must be one of {EXHAUSTIVE, SUBSAMPLE, LOGARITHMIC}
+PSEARCH_ALG	LOGARITHMIC
+
+# this must be one of {SIMPLE, CROSS2, EXHAUSTIVE}
+#
+# note that EXHAUSTIVE is really, really, really slow
+#
+BSEARCH_ALG	CROSS2
+
+#
+# these specify the q-scale for I, P, and B frames
+# (values must be between 1 and 31)
+# These are the Qscale values for the entire frame in variable bit-rate
+# mode, and starting points (but not important) for constant bit rate
+#
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# this must be ORIGINAL or DECODED
+REFERENCE_FRAME	ORIGINAL
+
+# for parallel parameters see parallel.param in the exmaples subdirectory
+
+# if you want constant bit-rate mode, specify it as follows (number is bits/sec):
+BIT_RATE  1000000
+
+# To specify the buffer size (327680 is default, measused in bits, for 16bit words)
+BUFFER_SIZE 327680
+
+# The frame rate is the number of frames/second (legal values:
+# 23.976, 24, 25, 29.97, 30, 50 ,59.94, 60
+FRAME_RATE 30
+
+# There are many more options, see the users manual for examples....
+# ASPECT_RATIO, USER_DATA, GAMMA, IQTABLE, etc.
diff --git a/converter/ppm/ppmtompeg/examples/tennis.param b/converter/ppm/ppmtompeg/examples/tennis.param
new file mode 100644
index 00000000..978e1904
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/tennis.param
@@ -0,0 +1,37 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		output/tennis.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	cat *.Y *.U *.V
+
+INPUT_DIR	links/tennis
+
+INPUT
+stennis.*	[0-149]
+END_INPUT
+
+
+# motion vector search parameters
+
+# MAD or MSE -- must be upper case
+ERROR		MAD
+
+# FULL or HALF -- must be upper case
+PIXEL		HALF
+
+# means +/- this many pixels
+RANGE		8
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	CROSS2
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
diff --git a/converter/ppm/ppmtompeg/examples/tennis18.param b/converter/ppm/ppmtompeg/examples/tennis18.param
new file mode 100644
index 00000000..48979f3e
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/tennis18.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/tennis.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/tennis
+
+INPUT
+stennis.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/tennis19.5.param b/converter/ppm/ppmtompeg/examples/tennis19.5.param
new file mode 100644
index 00000000..196b7a28
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/tennis19.5.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/tennis.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/tennis
+
+INPUT
+stennis.*.yuv	[0-15]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/tennis19.param b/converter/ppm/ppmtompeg/examples/tennis19.param
new file mode 100644
index 00000000..542c237c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/tennis19.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/tennis.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/tennis
+
+INPUT
+stennis.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/tennis48.param b/converter/ppm/ppmtompeg/examples/tennis48.param
new file mode 100644
index 00000000..9db63638
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/tennis48.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/tennis.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/tennis
+
+INPUT
+stennis.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	SUBSAMPLE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/tennis49.param b/converter/ppm/ppmtompeg/examples/tennis49.param
new file mode 100644
index 00000000..cbecb035
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/tennis49.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/tennis.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/tennis
+
+INPUT
+stennis.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/tennis50.param b/converter/ppm/ppmtompeg/examples/tennis50.param
new file mode 100644
index 00000000..bbfe3cb0
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/tennis50.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/tennis.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/tennis
+
+INPUT
+stennis.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/tennis52.5.param b/converter/ppm/ppmtompeg/examples/tennis52.5.param
new file mode 100644
index 00000000..a00e4322
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/tennis52.5.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/tennis.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/tennis
+
+INPUT
+stennis.*.yuv	[0-15]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/tennis52.param b/converter/ppm/ppmtompeg/examples/tennis52.param
new file mode 100644
index 00000000..0ae84f00
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/tennis52.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/tennis.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/tennis
+
+INPUT
+stennis.*.yuv	[0-149]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/tennis53.param b/converter/ppm/ppmtompeg/examples/tennis53.param
new file mode 100644
index 00000000..bc311376
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/tennis53.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/tennis.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/tennis
+
+INPUT
+stennis.*.yuv	[0-15]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	EXHAUSTIVE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/tennisbug.param b/converter/ppm/ppmtompeg/examples/tennisbug.param
new file mode 100644
index 00000000..4a655b4a
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/tennisbug.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IPPPPPPPPPPP
+
+OUTPUT		/n/picasso/users/keving/encode/output/tennis.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	../input/tennis
+
+INPUT
+stennis.*.yuv	[0-49]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		1
+PQSCALE		1
+BQSCALE		1
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/testp.param b/converter/ppm/ppmtompeg/examples/testp.param
new file mode 100644
index 00000000..ea8dee18
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/testp.param
@@ -0,0 +1,45 @@
+# speed test parameter file
+
+PATTERN		IPPPPPPPPP
+
+OUTPUT		/n/picasso/users/keving/encode/output/testp.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input
+
+INPUT
+me.0.ppm
+me.0.ppm
+me.0.ppm
+me.0.ppm
+me.0.ppm
+me.0.ppm
+me.0.ppm
+me.0.ppm
+me.0.ppm
+me.0.ppm
+me.0.ppm
+END_INPUT
+
+# quality parameters
+
+IQSCALE		4
+PQSCALE		4
+BQSCALE		4
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/walk.param b/converter/ppm/ppmtompeg/examples/walk.param
new file mode 100644
index 00000000..52be9d2e
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/walk.param
@@ -0,0 +1,61 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/walk1.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	zcat *
+
+INPUT_DIR	/n/zonker/cluster/keving/walk
+
+INPUT
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+f.*.Z	[1-1800]
+END_INPUT
+
+PARALLEL_TEST_FRAMES	10
+PARALLEL_TIME_CHUNKS	30
+
+
+IO_SERVER_CONVERT *
+SLAVE_CONVERT zcat *
+
+PARALLEL
+#charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+END_PARALLEL
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/walk1.param b/converter/ppm/ppmtompeg/examples/walk1.param
new file mode 100644
index 00000000..52be9d2e
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/walk1.param
@@ -0,0 +1,61 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/walk1.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	zcat *
+
+INPUT_DIR	/n/zonker/cluster/keving/walk
+
+INPUT
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+f.*.Z	[1-1800]
+END_INPUT
+
+PARALLEL_TEST_FRAMES	10
+PARALLEL_TIME_CHUNKS	30
+
+
+IO_SERVER_CONVERT *
+SLAVE_CONVERT zcat *
+
+PARALLEL
+#charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+END_PARALLEL
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/walk18.param b/converter/ppm/ppmtompeg/examples/walk18.param
new file mode 100644
index 00000000..80ed48f3
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/walk18.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/walk19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	zcat *
+
+INPUT_DIR	/n/zonker/cluster/keving/walk
+
+INPUT
+f.*.Z	[1-200]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/walk19.5.param b/converter/ppm/ppmtompeg/examples/walk19.5.param
new file mode 100644
index 00000000..7fb724d1
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/walk19.5.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/walk19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	zcat *
+
+INPUT_DIR	/n/zonker/cluster/keving/walk
+
+INPUT
+f.*.Z	[1-16]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/walk19.param b/converter/ppm/ppmtompeg/examples/walk19.param
new file mode 100644
index 00000000..acc8f627
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/walk19.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/walk19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	zcat *
+
+INPUT_DIR	/n/zonker/cluster/keving/walk
+
+INPUT
+f.*.Z	[1-200]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/walk2.param b/converter/ppm/ppmtompeg/examples/walk2.param
new file mode 100644
index 00000000..cbb28705
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/walk2.param
@@ -0,0 +1,61 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/walk2.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	zcat *
+
+INPUT_DIR	/n/zonker/cluster/keving/walk
+
+INPUT
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+f.*.Z	[1-1800]
+END_INPUT
+
+PARALLEL_TEST_FRAMES	10
+PARALLEL_TIME_CHUNKS	30
+
+
+IO_SERVER_CONVERT *
+SLAVE_CONVERT zcat *
+
+PARALLEL
+#charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+END_PARALLEL
+
+# quality parameters
+
+IQSCALE		6
+PQSCALE		8
+BQSCALE		15
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/examples/walk3.param b/converter/ppm/ppmtompeg/examples/walk3.param
new file mode 100644
index 00000000..86a30812
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/walk3.param
@@ -0,0 +1,61 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/walk3.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	zcat *
+
+INPUT_DIR	/n/zonker/cluster/keving/walk
+
+INPUT
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+f.*.Z	[1-1800]
+END_INPUT
+
+PARALLEL_TEST_FRAMES	10
+PARALLEL_TIME_CHUNKS	30
+
+
+IO_SERVER_CONVERT *
+SLAVE_CONVERT zcat *
+
+PARALLEL
+#charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+END_PARALLEL
+
+# quality parameters
+
+IQSCALE		6
+PQSCALE		8
+BQSCALE		15
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/walk4.param b/converter/ppm/ppmtompeg/examples/walk4.param
new file mode 100644
index 00000000..12d1f505
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/walk4.param
@@ -0,0 +1,61 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+OUTPUT		/n/picasso/users/keving/encode/output/walk4.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	zcat *
+
+INPUT_DIR	/n/zonker/cluster/keving/walk
+
+INPUT
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+f.*.Z	[1-1800]
+END_INPUT
+
+PARALLEL_TEST_FRAMES	10
+PARALLEL_TIME_CHUNKS	30
+
+
+IO_SERVER_CONVERT *
+SLAVE_CONVERT zcat *
+
+PARALLEL
+#charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+END_PARALLEL
+
+# quality parameters
+
+IQSCALE		4
+PQSCALE		6
+BQSCALE		10
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		7
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/walk48.param b/converter/ppm/ppmtompeg/examples/walk48.param
new file mode 100644
index 00000000..2c6df697
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/walk48.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/walk19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	zcat *
+
+INPUT_DIR	/n/zonker/cluster/keving/walk
+
+INPUT
+f.*.Z	[1-200]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	SUBSAMPLE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/walk49.param b/converter/ppm/ppmtompeg/examples/walk49.param
new file mode 100644
index 00000000..72c17a94
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/walk49.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/walk19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	zcat *
+
+INPUT_DIR	/n/zonker/cluster/keving/walk
+
+INPUT
+f.*.Z	[1-200]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/walk5.param b/converter/ppm/ppmtompeg/examples/walk5.param
new file mode 100644
index 00000000..e792064f
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/walk5.param
@@ -0,0 +1,62 @@
+# speed test parameter file
+
+#PATTERN		IBBPBBPBBPBBPBB
+PATTERN		I
+OUTPUT		/n/picasso/users/keving/encode/output/walk5.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PNM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	zcat *
+
+INPUT_DIR	/n/zonker/cluster/keving/walk
+
+INPUT
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+f.*.Z	[1-80]
+END_INPUT
+
+PARALLEL_TEST_FRAMES	10
+PARALLEL_TIME_CHUNKS	30
+
+
+IO_SERVER_CONVERT *
+SLAVE_CONVERT zcat *
+
+PARALLEL
+#charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+END_PARALLEL
+
+# quality parameters
+
+IQSCALE		1
+PQSCALE		6
+BQSCALE		10
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		7
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/walk50.param b/converter/ppm/ppmtompeg/examples/walk50.param
new file mode 100644
index 00000000..8f4d27e6
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/walk50.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/walk19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	zcat *
+
+INPUT_DIR	/n/zonker/cluster/keving/walk
+
+INPUT
+f.*.Z	[1-200]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	EXHAUSTIVE
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/walk52.5.param b/converter/ppm/ppmtompeg/examples/walk52.5.param
new file mode 100644
index 00000000..f9d038f6
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/walk52.5.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/walk19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	zcat *
+
+INPUT_DIR	/n/zonker/cluster/keving/walk
+
+INPUT
+f.*.Z	[1-16]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/walk52.param b/converter/ppm/ppmtompeg/examples/walk52.param
new file mode 100644
index 00000000..69626d2c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/walk52.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/walk19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	zcat *
+
+INPUT_DIR	/n/zonker/cluster/keving/walk
+
+INPUT
+f.*.Z	[1-200]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/walk53.param b/converter/ppm/ppmtompeg/examples/walk53.param
new file mode 100644
index 00000000..72f57c7a
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/walk53.param
@@ -0,0 +1,35 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/walk19.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	zcat *
+
+INPUT_DIR	/n/zonker/cluster/keving/walk
+
+INPUT
+f.*.Z	[1-16]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	EXHAUSTIVE
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/examples/walk93.param b/converter/ppm/ppmtompeg/examples/walk93.param
new file mode 100644
index 00000000..243d6032
--- /dev/null
+++ b/converter/ppm/ppmtompeg/examples/walk93.param
@@ -0,0 +1,62 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+#PATTERN		I
+OUTPUT		/n/picasso/users/keving/encode/output/walk93.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	1
+
+BASE_FILE_FORMAT	PPM
+YUV_SIZE	320x240
+
+INPUT_CONVERT	zcat *
+
+INPUT_DIR	/n/zonker/cluster/keving/walk
+
+INPUT
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+title.ppm.Z
+f.*.Z	[1-2]
+END_INPUT
+
+PARALLEL_TEST_FRAMES	10
+PARALLEL_TIME_CHUNKS	30
+
+
+IO_SERVER_CONVERT *
+SLAVE_CONVERT zcat *
+
+PARALLEL
+#charlie-brown	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+#woodstock	keving	~keving/encode/bin/dec-alpha/mpeg_encode
+END_PARALLEL
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		7
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	DECODED
diff --git a/converter/ppm/ppmtompeg/file.c b/converter/ppm/ppmtompeg/file.c
new file mode 100644
index 00000000..ae741962
--- /dev/null
+++ b/converter/ppm/ppmtompeg/file.c
@@ -0,0 +1,322 @@
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/picasso/project/mm/mpeg/mpeg_dist/mpeg_encode/RCS/file.c,v 1.2 1993/06/30 20:06:09 keving Exp $
+ *  $Log: file.c,v $
+ * Revision 1.2  1993/06/30  20:06:09  keving
+ * nothing
+ *
+ * Revision 1.1  1993/06/03  21:08:08  keving
+ * nothing
+ *
+ */
+
+#include "tk.h"
+
+#include "all.h"
+
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <time.h>
+#include <string.h>
+#include <dirent.h>
+#include <strings.h>
+
+#define MAX_FILES   1000
+#define MAX_NAME_LEN	256
+#define MAX_STRING_LEN	MAX_NAME_LEN
+
+typedef int boolean;
+#define TRUE 1
+#define FALSE 0
+
+extern char currentPath[MAXPATHLEN];
+
+char	globString[1024];
+
+static DIR *dfd;
+
+void	ResetPath(void);
+int ListDirectory(ClientData nulldata, Tcl_Interp *interp, int argc,
+		  char **argv);
+int ChangeDirectory(ClientData nulldata, Tcl_Interp *interp, int argc,
+		  char **argv);
+void	SortFiles(int numStrings, char strings[MAX_FILES][MAX_NAME_LEN],
+		  boolean *dirList, int permute[]);
+
+static void	UpdatePath(Tcl_Interp *interp, char *directory);
+static boolean	MatchesGlob(char *string, char *glob);
+
+
+
+void	ResetPath()
+{
+    if ( getwd(currentPath) == 0 )
+    {
+	fprintf(stderr, "Error getting pathname!!!\n");
+	exit(1);
+    }
+
+    strcpy(&currentPath[strlen(currentPath)], "/");
+
+    dfd = opendir(currentPath);
+    if ( dfd == NULL )
+    {
+	fprintf(stderr, "can't open '%s'\n", currentPath);
+	exit(1);
+    }
+}
+
+
+static void	UpdatePath(Tcl_Interp *interp, char *directory)
+{
+    int length;
+    char *charPtr;
+
+    length = strlen(currentPath);
+
+    if ( strcmp(directory, "./") == 0 )
+	return /* nothing */ ;
+    else if ( strcmp(directory, "../") == 0 )
+    {
+	/* delete backwards up to '/' */
+
+	if ( length < 2 )
+	{
+	    fprintf(stderr, "Error:  backing up from root directory!!!\n");
+	    exit(1);
+	}
+
+	charPtr = &currentPath[length-2];
+	while ( (charPtr != currentPath) && (*charPtr != '/') )
+	    charPtr--;
+	charPtr++;	/* leave the '/' */
+	*charPtr = '\0';
+    }
+    else
+    {
+	strcpy(&currentPath[length], directory);
+    }
+}
+
+
+int ChangeDirectory(ClientData nulldata, Tcl_Interp *interp, int argc,
+		  char **argv)
+{
+    char *directory = argv[1];
+
+    UpdatePath(interp, directory);
+
+    fprintf(stdout, "Opening directory: '%s'\n", currentPath);
+
+    dfd = opendir(currentPath);
+    if ( dfd == NULL )
+    {
+	fprintf(stderr, "can't open '%s'\n", currentPath);
+	return TCL_OK;	/* shouldn't, really */
+    }
+
+    return TCL_OK;
+}
+
+
+int ListDirectory(ClientData nulldata, Tcl_Interp *interp, int argc,
+		  char **argv)
+{
+    struct dirent *dp;
+    struct stat stbuf;
+    char command[256];
+    char fileName[MAX_FILES][MAX_NAME_LEN];
+    boolean dirList[MAX_FILES];
+    int permute[MAX_FILES];
+    int	fileCount = 0;
+    register int index;
+    char fullName[MAXPATHLEN];
+    char    *restPtr;
+
+    sprintf(command, "ShowCurrentDirectory %s", currentPath);
+    Tcl_Eval(interp, command, 0, (char **) NULL);
+
+    if ( dfd == NULL )
+    {
+	fprintf(stderr, "TRIED TO LIST NULL DIRECTORY\n");
+
+	return TCL_OK;
+    }
+
+/* check if root directory */
+    if ( strlen(currentPath) != 1 )
+    {
+	sprintf(fileName[fileCount], "../");
+	dirList[fileCount] = TRUE;
+	fileCount++;
+    }
+
+    strcpy(fullName, currentPath);
+    restPtr = &fullName[strlen(fullName)];
+
+    while ( (dp = readdir(dfd)) != NULL )
+    {
+	strcpy(restPtr, dp->d_name);
+	stat(fullName, &stbuf);
+
+	if ( dp->d_name[0] != '.' )
+	{
+	    if ( S_ISDIR(stbuf.st_mode) )
+	    {
+		sprintf(fileName[fileCount], "%s/", dp->d_name);
+		dirList[fileCount] = TRUE;
+		fileCount++;
+	    }
+	    else
+	    {
+		if ( MatchesGlob(dp->d_name, globString) )
+		{
+		    strcpy(fileName[fileCount], dp->d_name);
+		    dirList[fileCount] = FALSE;
+		    fileCount++;
+		}
+	    }
+	}
+    }
+
+    SortFiles(fileCount, fileName, dirList, permute);
+
+    for ( index = 0; index < fileCount; index++ )
+    {
+	sprintf(command, "AddBrowseFile %s", fileName[permute[index]]);
+	Tcl_Eval(interp, command, 0, (char **) NULL);
+    }
+
+    closedir(dfd);
+
+    return TCL_OK;
+}
+
+
+void	SortFiles(int numStrings, char strings[MAX_FILES][MAX_NAME_LEN],
+		  boolean *dirList, int permute[])
+{
+    register int i, j;
+    int temp;
+    int	numDirs;
+    int	ptr;
+
+    for ( i = 0; i < numStrings; i++ )
+	permute[i] = i;
+
+    /* put all directories at front */
+    numDirs = 0;
+    ptr = numStrings-1;
+    while ( numDirs != ptr )
+    {
+	/* go past dirs */
+	while ( (numDirs < ptr) && (dirList[permute[numDirs]]) )
+	    numDirs++;
+
+	/* go past non-dirs */
+	while ( (numDirs < ptr) && (! dirList[permute[ptr]]) )
+	    ptr--;
+
+	if ( numDirs != ptr )
+	{
+	    temp = permute[numDirs];
+	    permute[numDirs] = ptr;
+	    permute[ptr] = temp;
+	}
+    }
+
+    if ( dirList[permute[numDirs]] )
+	numDirs++;
+
+    for ( i = 0; i < numDirs; i++ )
+	for ( j = i+1; j < numDirs; j++ )
+	{
+	    if ( strcmp(&strings[permute[j]][0], &strings[permute[i]][0]) < 0 )
+	    {
+		temp = permute[j];
+		permute[j] = permute[i];
+		permute[i] = temp;
+	    }
+	}
+
+    for ( i = numDirs; i < numStrings; i++ )
+	for ( j = i+1; j < numStrings; j++ )
+	{
+	    if ( strcmp(&strings[permute[j]][0], &strings[permute[i]][0]) < 0 )
+	    {
+		temp = permute[j];
+		permute[j] = permute[i];
+		permute[i] = temp;
+	    }
+	}
+}
+
+
+int SetBrowseGlob (ClientData nulldata, Tcl_Interp *interp,
+		   int argc, char **argv)
+{
+    if (argc == 2 )
+    {
+	strcpy(globString, argv[1]);
+
+	fprintf(stdout, "GLOB:  %s\n", globString);
+
+	return TCL_OK;
+    }
+
+	Tcl_AppendResult (interp, 
+            "wrong args: should be \"", argv[0]," string\"", (char *) NULL);
+	return TCL_ERROR;
+}
+
+
+static boolean	MatchesGlob(char *string, char *glob)
+{
+    char    *stringRight, *globRight;
+
+    while ( (*glob != '\0') && (*glob != '*') )	    /* match left side */
+    {
+	if ( (*string == '\0') || (*string != *glob) )
+	    return FALSE;
+	string++;
+	glob++;
+    }
+
+    if ( *glob == '\0' )	/* no star */
+	return TRUE;
+
+    /* now match right side */
+    stringRight = &string[strlen(string)-1];
+    globRight = &glob[strlen(glob)-1];
+
+    while ( *globRight != '*' )
+    {
+	if ( (stringRight < string) || (*stringRight != *globRight) )
+	    return FALSE;
+	globRight--;
+	stringRight--;
+    }
+
+    return TRUE;
+}
diff --git a/converter/ppm/ppmtompeg/frame.c b/converter/ppm/ppmtompeg/frame.c
new file mode 100644
index 00000000..09f46f66
--- /dev/null
+++ b/converter/ppm/ppmtompeg/frame.c
@@ -0,0 +1,836 @@
+/*===========================================================================*
+ * frame.c                                   *
+ *                                       *
+ *  basic frame procedures                           *
+ *                                       *
+ * EXPORTED PROCEDURES:                              *
+ *  Frame_Init                               *
+ *  Frame_Exit                               *
+ *  Frame_New                                *
+ *  Frame_Free                               *
+ *  Frame_AllocBlocks                            *
+ *  Frame_AllocYCC                               *
+ *  Frame_AllocDecoded                           *
+ *  Frame_AllocHalf                                  *
+ *  Frame_Resize                                     * 
+ *                                       *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+
+#include "mallocvar.h"
+
+#include "all.h"
+#include "mtypes.h"
+#include "frames.h"
+#include "frame.h"
+#include "fsize.h"
+#include "dct.h"
+
+/*===========*
+ * CONSTANTS *
+ *===========*/
+
+/* The maximum number of B-Frames allowed between reference frames. */
+#define  B_FRAME_RUN  16    
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+static MpegFrame * frameMemory[B_FRAME_RUN+2];
+static unsigned int numOfFrames;
+
+
+/*====================================================
+* Resize_Array_Width
+*    
+*   This function will resize any array width up
+* or down in size.  The algorithm is based on the
+* least common multiple approach more commonly
+* used in audio frequency adjustments.
+*=====================================================*/
+static void 
+Resize_Array_Width(uint8 ** const inarray,
+                   int      const in_x,
+                   int      const in_y,
+                   uint8 ** const outarray,
+                   int      const out_x) {
+
+    unsigned int i;
+    int in_total;
+    int out_total;
+    uint8 *inptr;
+    uint8 *outptr;
+    uint8 pointA,pointB;
+    /* double slope,diff; */
+    
+    for (i = 0; i < in_y; ++i) {     /* For each row */
+        unsigned int j;
+        inptr = &inarray[i][0];
+        outptr = &outarray[i][0];
+        in_total = 0;
+        out_total = 0;
+        for (j=0; j < out_x; ++j) {      /* For each output value */
+            if (in_total == out_total) {
+                *outptr = *inptr;
+                outptr++;
+                out_total=out_total+in_x;
+                while(in_total < out_total){
+                    in_total = in_total + out_x;
+                    ++inptr;
+                }
+                if (in_total > out_total) {
+                    in_total = in_total - out_x;
+                    --inptr;
+                }
+            } else {  
+                pointA = *inptr;
+                ++inptr;
+                pointB = *inptr;
+                --inptr;
+#if 0
+                /*Interpolative solution */
+                slope = ((double)(pointB -pointA))/((double)(out_x));
+                diff = (((double)(out_total - in_total)));
+                if (diff < (out_x/2)){
+                    *outptr = (pointA + (uint8)(slope*diff));
+                } else {
+                    *outptr = (pointB -
+                               (uint8)(slope*(((float)(out_x)) - diff)));
+                } 
+#endif
+                /* Non-Interpolative solution */
+                *outptr = *inptr;  
+
+                ++outptr;
+                out_total = out_total + in_x;
+                while(in_total < out_total) {
+                    in_total = in_total + out_x;
+                    ++inptr;
+                }
+                if (in_total > out_total) {
+                    in_total = in_total - out_x;
+                    --inptr;
+                }
+            }  /* end if */
+        }  /* end for each output value */
+    }  /* end for each row */
+}  /* end main */
+
+
+
+/*==============================
+* Resize_Array_Height
+*
+*    Resize any array height larger or smaller.
+* Same as Resize_array_Width except pointer
+* manipulation must change.
+*===============================*/
+static void 
+Resize_Array_Height(uint8 ** const inarray,
+                    int      const in_x,
+                    int      const in_y,
+                    uint8 ** const outarray,
+                    int      const out_y) {
+
+    unsigned int i;
+
+    for(i=0; i < in_x; ++i){    /* for each column */
+        int in_total;
+        int out_total;
+        uint8 pointA, pointB;
+        double slope, diff;
+        unsigned int j;
+        int k;
+
+        in_total = 0;
+        out_total = 0;
+        k = 0;
+        for(j=0; j < out_y; ++j){  /* for each output value */
+            if (in_total == out_total) {  
+                outarray[j][i] = inarray[k][i];
+                out_total=out_total+in_y;
+                while(in_total < out_total){
+                    in_total = in_total + out_y;
+                    ++k;
+                }
+                if (in_total > out_total) {
+                    in_total = in_total - out_y;
+                    --k;
+                }
+            } else {  
+                pointA = inarray[k][i];
+                if (k != (in_y -1)) {
+                    pointB = inarray[k+1][i];
+                } else
+                    pointB = pointA;
+                /* Interpolative case */
+                slope = ((double)(pointB -pointA))/(double)(out_y);
+                diff = (double)(out_total - in_total);
+                /*  outarray[j][i] = (inarray[k][i] + (uint8)(slope*diff)); */
+                /* Non-Interpolative case */
+                outarray[j][i] = inarray[k][i];
+                out_total = out_total + in_y;
+                while (in_total < out_total) {
+                    in_total = in_total + out_y;
+                    ++k;
+                }
+                if (in_total > out_total){
+                    in_total = in_total - out_y;
+                    --k;
+                }
+            } 
+        }
+    }
+}
+
+
+
+/*========================================================
+* Resize_Width
+*======================================================*/
+static void  
+Resize_Width(MpegFrame * const omfrw,
+             MpegFrame * const mfrw,
+             int         const in_x,
+             int         const in_y,
+             int         const out_x) {
+
+    int y;
+
+    omfrw->orig_y = NULL;
+    Fsize_x = out_x;
+
+    /* Allocate new frame memory */
+    MALLOCARRAY(omfrw->orig_y, Fsize_y);
+    ERRCHK(omfrw->orig_y, "malloc");
+    for (y = 0; y < Fsize_y; ++y) {
+        MALLOCARRAY(omfrw->orig_y[y], out_x);
+        ERRCHK(omfrw->orig_y[y], "malloc");
+    }
+
+    MALLOCARRAY(omfrw->orig_cr, Fsize_y / 2);
+    ERRCHK(omfrw->orig_cr, "malloc");
+    for (y = 0; y < Fsize_y / 2; ++y) {
+        MALLOCARRAY(omfrw->orig_cr[y], out_x / 2);
+        ERRCHK(omfrw->orig_cr[y], "malloc");
+    }
+
+    MALLOCARRAY(omfrw->orig_cb, Fsize_y / 2);
+    ERRCHK(omfrw->orig_cb, "malloc");
+    for (y = 0; y < Fsize_y / 2; ++y) {
+        MALLOCARRAY(omfrw->orig_cb[y], out_x / 2);
+        ERRCHK(omfrw->orig_cb[y], "malloc");
+    }
+
+    if (referenceFrame == ORIGINAL_FRAME) {
+        omfrw->ref_y = omfrw->orig_y;
+        omfrw->ref_cr = omfrw->orig_cr;
+        omfrw->ref_cb = omfrw->orig_cb;
+    }
+
+    /* resize each component array separately */
+    Resize_Array_Width(mfrw->orig_y, in_x, in_y, omfrw->orig_y, out_x);
+    Resize_Array_Width(mfrw->orig_cr, (in_x/2), (in_y/2), omfrw->orig_cr,
+                       (out_x/2));
+    Resize_Array_Width(mfrw->orig_cb, (in_x/2), (in_y/2), omfrw->orig_cb,
+                       (out_x/2));
+
+    /* Free old frame memory */
+    if (mfrw->orig_y) {
+        unsigned int i;
+        for (i = 0; i < in_y; ++i) {
+            free(mfrw->orig_y[i]);
+        }
+        free(mfrw->orig_y);
+        
+        for (i = 0; i < in_y / 2; ++i) {
+            free(mfrw->orig_cr[i]);
+        }
+        free(mfrw->orig_cr);
+        
+        for (i = 0; i < in_y / 2; ++i) {
+            free(mfrw->orig_cb[i]);
+        }
+        free(mfrw->orig_cb);
+    }
+}
+
+
+
+/*=======================================================
+* Resize_Height
+*
+*   Resize Frame height up or down
+*=======================================================*/
+static  void
+Resize_Height(MpegFrame * const omfrh,
+              MpegFrame * const mfrh,
+              int         const in_x,
+              int         const in_y,
+              int         const out_y) {
+    
+    unsigned int y; 
+    
+    Fsize_y = out_y;
+
+    /* Allocate new frame memory */
+    MALLOCARRAY(omfrh->orig_y, out_y);
+    ERRCHK(omfrh->orig_y, "malloc");
+    for (y = 0; y < out_y; ++y) {
+        MALLOCARRAY(omfrh->orig_y[y], Fsize_x);
+        ERRCHK(omfrh->orig_y[y], "malloc");
+    }
+
+    MALLOCARRAY(omfrh->orig_cr, out_y / 2);
+    ERRCHK(omfrh->orig_cr, "malloc");
+    for (y = 0; y < out_y / 2; ++y) {
+        MALLOCARRAY(omfrh->orig_cr[y], Fsize_x / 2);
+        ERRCHK(omfrh->orig_cr[y], "malloc");
+    }
+
+    MALLOCARRAY(omfrh->orig_cb, out_y / 2);
+    ERRCHK(omfrh->orig_cb, "malloc");
+    for (y = 0; y < out_y / 2; ++y) {
+        MALLOCARRAY(omfrh->orig_cb[y], Fsize_x / 2);
+        ERRCHK(omfrh->orig_cb[y], "malloc");
+    }
+
+    if (referenceFrame == ORIGINAL_FRAME) {
+        omfrh->ref_y = omfrh->orig_y;
+        omfrh->ref_cr = omfrh->orig_cr;
+        omfrh->ref_cb = omfrh->orig_cb;
+    }
+
+    /* resize component arrays separately */
+    Resize_Array_Height(mfrh->orig_y, in_x, in_y, omfrh->orig_y, out_y);
+    Resize_Array_Height(mfrh->orig_cr, (in_x/2), (in_y/2), omfrh->orig_cr,
+                        (out_y/2));
+    Resize_Array_Height(mfrh->orig_cb, (in_x/2), (in_y/2), omfrh->orig_cb,
+                        (out_y/2));
+
+    /* Free old frame memory */
+    if (mfrh->orig_y) {
+        unsigned int i;
+        for (i = 0; i < in_y; ++i) {
+            free(mfrh->orig_y[i]);
+        }
+        free(mfrh->orig_y);
+        
+    for (i = 0; i < in_y / 2; ++i) {
+        free(mfrh->orig_cr[i]);
+    }
+    free(mfrh->orig_cr);
+    
+    for (i = 0; i < in_y / 2; ++i) {
+        free(mfrh->orig_cb[i]);
+    }
+    free(mfrh->orig_cb);
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * Frame_Init
+ *
+ *  initializes the memory associated with all frames ever
+ *  If the input is not coming in from stdin, only 3 frames are needed ;
+ *      else, the program must create frames equal to the greatest distance
+ *      between two reference frames to hold the B frames while it is parsing
+ *      the input from stdin.
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    frameMemory, numOfFrames
+ *
+ *===========================================================================*/
+void
+Frame_Init(unsigned int const numOfFramesRequested) {
+    int idx;
+
+    numOfFrames = numOfFramesRequested;
+
+    for (idx = 0; idx < numOfFrames; ++idx) {
+        MALLOCVAR(frameMemory[idx]);
+        frameMemory[idx]->inUse = FALSE;
+        frameMemory[idx]->orig_y = NULL;    
+        frameMemory[idx]->y_blocks = NULL; 
+        frameMemory[idx]->decoded_y = NULL;
+        frameMemory[idx]->halfX = NULL;
+        frameMemory[idx]->next = NULL;
+    }
+}
+
+
+/*===========================================================================*
+ *
+ * FreeFrame
+ *
+ *  frees the memory associated with the given frame
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static void
+FreeFrame(MpegFrame * const frameP) {
+
+    if (frameP) {
+        if (frameP->orig_y) {
+            unsigned int i;
+            for (i = 0; i < Fsize_y; ++i)
+                free(frameP->orig_y[i]);
+            free(frameP->orig_y);
+
+            for (i = 0; i < (Fsize_y / 2); ++i)
+                free(frameP->orig_cr[i]);
+            free(frameP->orig_cr);
+
+            for (i = 0; i < (Fsize_y / 2); ++i)
+                free(frameP->orig_cb[i]);
+            free(frameP->orig_cb);
+        }
+        if (frameP->decoded_y) {
+            unsigned int i;
+            for (i = 0; i < Fsize_y; ++i)
+                free(frameP->decoded_y[i]);
+            free(frameP->decoded_y);
+
+            for (i = 0; i < (Fsize_y / 2); ++i)
+                free(frameP->decoded_cr[i]);
+            free(frameP->decoded_cr);
+
+            for (i = 0; i < (Fsize_y / 2); ++i)
+                free(frameP->decoded_cb[i]);
+            free(frameP->decoded_cb);
+        }
+
+        if (frameP->y_blocks) {
+            unsigned int i;
+            for (i = 0; i < Fsize_y / DCTSIZE; ++i)
+                free(frameP->y_blocks[i]);
+            free(frameP->y_blocks);
+
+            for (i = 0; i < Fsize_y / (2 * DCTSIZE); ++i)
+                free(frameP->cr_blocks[i]);
+            free(frameP->cr_blocks);
+
+            for (i = 0; i < Fsize_y / (2 * DCTSIZE); ++i)
+                free(frameP->cb_blocks[i]);
+            free(frameP->cb_blocks);
+        }
+        if (frameP->halfX) {
+            unsigned int i;
+            for ( i = 0; i < Fsize_y; ++i )
+                free(frameP->halfX[i]);
+            free(frameP->halfX);
+            
+            for (i = 0; i < Fsize_y-1; ++i)
+                free(frameP->halfY[i]);
+            free(frameP->halfY);
+            
+            for (i = 0; i < Fsize_y-1; ++i)
+                free(frameP->halfBoth[i]);
+            free(frameP->halfBoth);
+        }
+        free(frameP);
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * Frame_Exit
+ *
+ *  frees the memory associated with frames
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    frameMemory
+ *
+ *===========================================================================*/
+void
+Frame_Exit(void) {
+
+    int idx;
+
+    for (idx = 0; idx < numOfFrames; ++idx) {
+        FreeFrame(frameMemory[idx]);
+    }
+}
+
+
+/*===========================================================================*
+ *
+ * Frame_Free
+ *
+ *  frees the given frame -- allows it to be re-used
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Frame_Free(MpegFrame * const frameP) {
+    frameP->inUse = FALSE;
+}
+
+
+
+/*===========================================================================*
+ *
+ * GetUnusedFrame
+ *
+ *  return an unused frame
+ *
+ * RETURNS: the frame
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static MpegFrame *
+GetUnusedFrame() {
+    unsigned int idx;
+
+    for (idx = 0; idx < numOfFrames; ++idx) {
+        if (!frameMemory[idx]->inUse) {
+            frameMemory[idx]->inUse = TRUE;
+            break;
+        }
+    }
+    if (idx >= numOfFrames) {
+        fprintf(stderr, "ERROR:  No unused frames!!!\n");
+        fprintf(stderr, "        If you are using stdin for input, "
+                "it is likely that you have too many\n");
+        fprintf(stderr, "        B-frames between two reference frames.  "
+                "See the man page for help.\n");
+        exit(1);
+    }
+    return frameMemory[idx]; 
+}
+
+
+
+/*===========================================================================*
+ *
+ * ResetFrame
+ *
+ *  reset a frame to the given id and type
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static void
+ResetFrame(int         const id,
+           int         const type,
+           MpegFrame * const frame) {
+
+    switch (type) {
+    case 'i':
+        frame->type = TYPE_IFRAME;
+        break;
+    case 'p':
+        frame->type = TYPE_PFRAME;
+        break;
+    case 'b':
+        frame->type = TYPE_BFRAME;
+        break;
+    default:
+        fprintf(stderr, "Invalid MPEG frame type %c\n", type);
+        exit(1);
+    }
+
+    frame->id = id;
+    frame->halfComputed = FALSE;
+    frame->next = NULL;
+}
+
+
+
+/*===========================================================================*
+ *
+ * Frame_New
+ *
+ *  finds a frame that isn't currently being used and resets it
+ *
+ * RETURNS: the frame
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+MpegFrame *
+Frame_New(int const id,
+          int const type) {
+
+    MpegFrame *frame;
+
+    frame = GetUnusedFrame();
+    ResetFrame(id, type, frame);
+
+    return frame;
+}
+
+
+
+/*===========================================================================*
+ *
+ * Frame_AllocBlocks
+ *
+ *  allocate memory for blocks for the given frame, if required
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Frame_AllocBlocks(MpegFrame * const frameP) {
+
+    if (frameP->y_blocks != NULL) {
+        /* already allocated */
+    } else {
+        int const dctx = Fsize_x / DCTSIZE;
+        int const dcty = Fsize_y / DCTSIZE;
+
+        unsigned int i;
+
+        MALLOCARRAY(frameP->y_blocks, dcty);
+        ERRCHK(frameP->y_blocks, "malloc");
+        for (i = 0; i < dcty; ++i) {
+            MALLOCARRAY(frameP->y_blocks[i], dctx);
+            ERRCHK(frameP->y_blocks[i], "malloc");
+        }
+    
+        MALLOCARRAY(frameP->cr_blocks, dcty / 2);
+        ERRCHK(frameP->cr_blocks, "malloc");
+        MALLOCARRAY(frameP->cb_blocks, dcty / 2);
+        ERRCHK(frameP->cb_blocks, "malloc");
+        for (i = 0; i < (dcty / 2); ++i) {
+            MALLOCARRAY(frameP->cr_blocks[i], dctx / 2);
+            ERRCHK(frameP->cr_blocks[i], "malloc");
+            MALLOCARRAY(frameP->cb_blocks[i], dctx / 2);
+            ERRCHK(frameP->cb_blocks[i], "malloc");
+        }
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * Frame_AllocYCC
+ *
+ *  allocate memory for YCC info for the given frame, if required
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Frame_AllocYCC(MpegFrame * const frameP) {
+
+    if (frameP->orig_y != NULL) {
+        /* already allocated */
+    } else {
+        unsigned int y;
+    
+        DBG_PRINT(("ycc_calc:\n"));
+        /*
+         * first, allocate tons of memory
+         */
+        MALLOCARRAY(frameP->orig_y, Fsize_y);
+        ERRCHK(frameP->orig_y, "malloc");
+        for (y = 0; y < Fsize_y; ++y) {
+            MALLOCARRAY(frameP->orig_y[y], Fsize_x);
+            ERRCHK(frameP->orig_y[y], "malloc");
+        }
+
+        MALLOCARRAY(frameP->orig_cr, Fsize_y / 2);
+        ERRCHK(frameP->orig_cr, "malloc");
+        for (y = 0; y < (Fsize_y / 2); ++y) {
+            MALLOCARRAY(frameP->orig_cr[y], Fsize_x / 2);
+            ERRCHK(frameP->orig_cr[y], "malloc");
+        }
+
+        MALLOCARRAY(frameP->orig_cb, Fsize_y / 2);
+        ERRCHK(frameP->orig_cb, "malloc");
+        for (y = 0; y < (Fsize_y / 2); ++y) {
+            MALLOCARRAY(frameP->orig_cb[y], Fsize_x / 2);
+            ERRCHK(frameP->orig_cb[y], "malloc");
+        }
+
+        if (referenceFrame == ORIGINAL_FRAME) {
+            frameP->ref_y  = frameP->orig_y;
+            frameP->ref_cr = frameP->orig_cr;
+            frameP->ref_cb = frameP->orig_cb;
+        }
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * Frame_AllocHalf
+ *
+ *  allocate memory for half-pixel values for the given frame, if required
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Frame_AllocHalf(MpegFrame * const frameP) {
+
+    if (frameP->halfX != NULL) {
+    } else {
+        unsigned int y;
+
+        MALLOCARRAY(frameP->halfX, Fsize_y);
+        ERRCHK(frameP->halfX, "malloc");
+        for (y = 0; y < Fsize_y; ++y) {
+            MALLOCARRAY(frameP->halfX[y], Fsize_x - 1);
+            ERRCHK(frameP->halfX[y], "malloc");
+        }
+        MALLOCARRAY(frameP->halfY, Fsize_y - 1);
+        ERRCHK(frameP->halfY, "malloc");
+        for (y = 0; y < Fsize_y - 1; ++y) {
+            MALLOCARRAY(frameP->halfY[y], Fsize_x);
+            ERRCHK(frameP->halfY[y], "malloc");
+        }
+        MALLOCARRAY(frameP->halfBoth, Fsize_y - 1);
+        ERRCHK(frameP->halfBoth, "malloc");
+        for (y = 0; y < Fsize_y - 1; ++y) {
+            MALLOCARRAY(frameP->halfBoth[y], Fsize_x - 1);
+            ERRCHK(frameP->halfBoth[y], "malloc");
+        }
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * Frame_AllocDecoded
+ *
+ *  allocate memory for decoded frame for the given frame, if required
+ *  if makeReference == TRUE, then makes it reference frame
+ * 
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Frame_AllocDecoded(MpegFrame * const frameP,
+                   boolean     const makeReference) {
+
+    if (frameP->decoded_y != NULL) {
+        /* already allocated */
+    } else {
+        unsigned int y;
+
+        /* allocate memory for decoded image */
+        /* can probably reuse original image memory, but may decide to use
+           it for some reason, so do it this way at least for now -- more
+           flexible
+        */
+        MALLOCARRAY(frameP->decoded_y, Fsize_y);
+        ERRCHK(frameP->decoded_y, "malloc");
+        for (y = 0; y < Fsize_y; ++y) {
+            MALLOCARRAY(frameP->decoded_y[y], Fsize_x);
+            ERRCHK(frameP->decoded_y[y], "malloc");
+        }
+        
+        MALLOCARRAY(frameP->decoded_cr, Fsize_y / 2);
+        ERRCHK(frameP->decoded_cr, "malloc");
+        for (y = 0; y < (Fsize_y / 2); ++y) {
+            MALLOCARRAY(frameP->decoded_cr[y], Fsize_x / 2);
+            ERRCHK(frameP->decoded_cr[y], "malloc");
+        }
+        
+        MALLOCARRAY(frameP->decoded_cb, Fsize_y / 2);
+        ERRCHK(frameP->decoded_cb, "malloc");
+        for (y = 0; y < (Fsize_y / 2); ++y) {
+            MALLOCARRAY(frameP->decoded_cb[y], Fsize_x / 2);
+            ERRCHK(frameP->decoded_cb[y], "malloc");
+        }
+
+        if (makeReference) {
+            frameP->ref_y  = frameP->decoded_y;
+            frameP->ref_cr = frameP->decoded_cr;
+            frameP->ref_cb = frameP->decoded_cb;
+        }
+    }
+}
+
+
+
+/*===============================================================
+ *
+ * Frame_Resize                  by James Boucher
+ *                Boston University Multimedia Communications Lab
+ *  
+ *     This function takes the mf input frame, read in READFrame(),
+ * and resizes all the input component arrays to the output
+ * dimensions specified in the parameter file as OUT_SIZE.
+ * The new frame is returned with the omf pointer.  As well,
+ * the values of Fsize_x and Fsize_y are adjusted.
+ ***************************************************************/
+void
+Frame_Resize(MpegFrame * const omf,
+             MpegFrame * const mf,
+             int         const insize_x,
+             int         const insize_y,
+             int         const outsize_x,
+             int         const outsize_y) {
+
+    MpegFrame * frameAP;  /* intermediate frame */
+
+    MALLOCVAR_NOFAIL(frameAP);
+    
+    if (insize_x != outsize_x && insize_y != outsize_y) {
+        Resize_Width(frameAP, mf, insize_x, insize_y, outsize_x);
+        Resize_Height(omf, frameAP, outsize_x, insize_y, outsize_y);
+    } else 
+        if (insize_x ==outsize_x && insize_y != outsize_y) {
+            Resize_Height(omf, mf, insize_x, insize_y, outsize_y);
+        } else
+            if (insize_x !=outsize_x && insize_y == outsize_y) {
+                Resize_Width(omf, mf, insize_x, insize_y, outsize_x);
+            } else
+                exit(1);
+
+    free(frameAP);
+    free(mf);
+}
diff --git a/converter/ppm/ppmtompeg/frametype.c b/converter/ppm/ppmtompeg/frametype.c
new file mode 100644
index 00000000..b7daacc9
--- /dev/null
+++ b/converter/ppm/ppmtompeg/frametype.c
@@ -0,0 +1,365 @@
+/*===========================================================================*
+ * frametype.c								     *
+ *									     *
+ *	procedures to keep track of frame types (I, P, B)		     *
+ *									     *
+ * EXPORTED PROCEDURES:							     *
+ *	FType_Type						             *
+ *	FType_FutureRef						             *
+ *	FType_PastRef						             *
+ *									     *
+ * SYNOPSIS								     *
+ *	FType_Type	returns the type of the given numbered frame	     *
+ *	FType_FutureRef	returns the number of the future reference frame     *
+ *	FType_PastRef	returns the number of the past reference frame	     *
+ *									     *
+ * 00.12.07 change malloc from frameTable to calloc to fix bug 
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "mallocvar.h"
+#include "all.h"
+#include "frames.h"
+#include "frame.h"
+#include "param.h"
+#include "specifics.h"
+#include "frametype.h"
+
+
+static FrameTable *frameTable=NULL;
+static boolean use_cache = FALSE;
+static int firstI = 0;
+static int numFrames;
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+extern int framePatternLen;
+extern char * framePattern;
+
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+
+/*===========================================================================*
+ *
+ * FType_Type
+ *
+ *	returns the type of the given numbered frame
+ *
+ * RETURNS:	the type
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+char
+FType_Type(unsigned int const frameNum) {
+
+    char const patternedType = framePattern[frameNum % framePatternLen];
+
+    char retval;
+
+    if (use_cache) 
+        return frameTable[frameNum].typ;
+  
+    if (frameNum+1 == numFrames) {
+        /* It's the last frame in the sequence.  If the pattern says it's
+           a B, we convert it to I because a B frame makes no sense as the
+           last frame of a sequence.
+        */
+        if (patternedType == 'b') 
+            retval = 'i';
+        else 
+            retval = patternedType;
+    } else {
+        if (specificsOn) {
+            static int lastI = -1;
+            int newtype;
+      
+            if (lastI > frameNum) 
+                lastI = -1;
+            newtype = SpecTypeLookup(frameNum);
+            switch (newtype) {
+            case 1:
+                lastI = frameNum;
+                retval = 'i';
+                break;
+            case 2:
+                retval = 'p';
+                break;
+            case 3:
+                retval = 'b';
+                break;
+            default:
+                if (lastI != -1) {
+                    unsigned int const pretendFrameNumber = 
+                        (frameNum - lastI + firstI) % framePatternLen;
+                    retval = framePattern[pretendFrameNumber];
+                } else 
+                    retval = patternedType;
+            }
+        } else 
+            retval = patternedType;
+    }
+    return retval;
+}
+
+
+
+unsigned int
+FType_FutureRef(unsigned int const currFrameNum) {
+/*----------------------------------------------------------------------------
+  Return the number of the future reference frame for the B frame
+  'currentFrameNum'.
+-----------------------------------------------------------------------------*/
+    unsigned int retval;
+
+    if (use_cache) {
+        retval = frameTable[currFrameNum].next->number;
+    } else {
+        int const index = currFrameNum % framePatternLen;
+        int const futureIndex = frameTable[index].next->number;
+        unsigned int const patternedFutureRef =
+            currFrameNum +
+            (((futureIndex-index)+framePatternLen) % framePatternLen);
+
+        retval = MIN(patternedFutureRef, numFrames-1);
+    }
+    return retval;
+}
+
+
+
+/*===========================================================================*
+ *
+ * FType_PastRef
+ *
+ *	returns the number of the past reference frame
+ *
+ * RETURNS:	the number
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+int
+FType_PastRef(currFrameNum)
+    int currFrameNum;
+{
+    int	    index;
+    int	    pastIndex;
+
+    if (use_cache) {
+      return frameTable[currFrameNum].prev->number;
+    } else {
+      index = currFrameNum % framePatternLen;
+      pastIndex = frameTable[index].prev->number;
+      
+      return currFrameNum -
+	(((index-pastIndex)+framePatternLen) % framePatternLen);
+    }
+}
+
+
+/*===========================================================================*
+ *
+ * SetFramePattern
+ *
+ *	set the IPB pattern; calls ComputeFrameTable to set up table
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    framePattern, framePatternLen, frameTable
+ *
+ *===========================================================================*/
+#define SIMPLE_ASCII_UPPER(x)  (((x)>='a') ? ((x)-'a'+'A') : (x))
+void
+SetFramePattern(const char * const pattern) {
+    unsigned int const len = strlen(pattern);
+
+    char *buf;
+    unsigned int index;
+
+    if (!pattern)
+        pm_error("INTERNAL ERROR: pattern cannot be NULL "
+            "in SetFramePattern");
+
+    if (SIMPLE_ASCII_UPPER(pattern[0]) != 'I') {
+        unsigned int index;
+        for (index = 0; index < len; ++index) {
+            if (SIMPLE_ASCII_UPPER(pattern[index]) == 'I') {
+                break;
+            } else if (SIMPLE_ASCII_UPPER(pattern[index]) == 'P')
+                pm_error("first reference frame must be 'i', not '%c'",
+                         pattern[index]);
+        }
+    }
+
+    buf = (char *)malloc(sizeof(char)*(len+1));
+    ERRCHK(buf, "malloc");
+
+    firstI = -1;
+    for (index = 0; index < len; index++) {
+        switch( SIMPLE_ASCII_UPPER(pattern[index]) ) {
+        case 'I':	
+            buf[index] = 'i';
+            if (firstI == -1) firstI = index;
+            break;
+        case 'P':	
+            buf[index] = 'p'; 
+            break;
+        case 'B':	
+            buf[index] = 'b';
+            break;
+        default:
+            pm_error("Invalid MPEG Frame type '%c'.", pattern[index]);
+        }
+    }
+    buf[len] = 0;
+
+    if (firstI == -1)
+        pm_error("Must have an I-frame in PATTERN");
+
+    framePattern = buf;
+    framePatternLen = len;
+    
+    /* Used to ComputeFrameTable(), but now must wait until param
+       parsed. (STDIN or not)
+    */
+}
+
+
+
+void
+ComputeFrameTable(unsigned int const numFramesArg) {
+/*----------------------------------------------------------------------------
+  Compute a table of I, P, B frames to help in determining dependencie
+
+  'numFrames' == 0 means number of frames is not known at this time.
+-----------------------------------------------------------------------------*/
+    int index;
+    FrameTable	*lastI, *lastIP, *firstB, *secondIP;
+    FrameTable	*ptr;
+    char typ;
+    int table_size;
+
+    numFrames = numFramesArg;
+
+    if (numFrames)
+        table_size = numFrames;
+    else
+        table_size = framePatternLen;
+
+    MALLOCARRAY_NOFAIL(frameTable, 1 + table_size);
+
+    lastI = NULL;
+    lastIP = NULL;
+    firstB = NULL;
+    secondIP = NULL;
+    for ( index = 0; index < table_size; index++ ) {
+        frameTable[index].number = index;
+        typ = FType_Type(index);
+        frameTable[index].typ = typ;
+        switch( typ ) {
+	    case 'i':
+            ptr = firstB;
+            while ( ptr != NULL ) {
+                ptr->next = &(frameTable[index]);
+                ptr = ptr->nextOutput;
+            }
+            frameTable[index].nextOutput = firstB;
+            frameTable[index].prev = lastIP;	/* for freeing */
+            if ( lastIP != NULL ) {
+                lastIP->next = &(frameTable[index]);
+                if ( secondIP == NULL ) {
+                    secondIP = &(frameTable[index]);
+                }
+            }
+            lastIP = &(frameTable[index]);
+            firstB = NULL;
+            break;
+	    case 'p':
+            ptr = firstB;
+            while ( ptr != NULL ) {
+                ptr->next = &(frameTable[index]);
+                ptr = ptr->nextOutput;
+            }
+            frameTable[index].nextOutput = firstB;
+            frameTable[index].prev = lastIP;
+            if ( lastIP != NULL ) {
+                lastIP->next = &(frameTable[index]);
+                if ( secondIP == NULL ) {
+                    secondIP = &(frameTable[index]);
+                }
+            }
+            lastIP = &(frameTable[index]);
+            firstB = NULL;
+            break;
+	    case 'b':
+            if ( (index+1 == framePatternLen) ||
+                 (FType_Type(index+1) != 'b') ) {
+                frameTable[index].nextOutput = NULL;
+            } else {
+                frameTable[index].nextOutput = &(frameTable[index+1]);
+            }
+            frameTable[index].prev = lastIP;
+            if ( firstB == NULL ) {
+                firstB = &(frameTable[index]);
+            }
+            break;
+	    default:
+	        fprintf(stderr, "Programmer Error in ComputeFrameTable (%d)\n",
+                    framePattern[index]);
+	        exit(1);
+	        break;
+        }
+    }
+    
+    /* why? SRS */
+    frameTable[table_size].number = framePatternLen;
+    ptr = firstB;
+    while ( ptr != NULL ) {
+        ptr->next = &(frameTable[table_size]);
+        ptr = ptr->nextOutput;
+    }
+    frameTable[table_size].nextOutput = firstB;
+    frameTable[table_size].prev = lastIP;
+    if ( secondIP == NULL )
+        frameTable[table_size].next = &(frameTable[0]);
+    else
+        frameTable[table_size].next = secondIP;
+
+    frameTable[0].prev = lastIP;
+    if ( lastIP != NULL ) {
+        lastIP->next = &(frameTable[table_size]);
+    }
+
+    if (numFrames)
+        use_cache = TRUE;
+}
diff --git a/converter/ppm/ppmtompeg/fsize.c b/converter/ppm/ppmtompeg/fsize.c
new file mode 100644
index 00000000..3808405c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/fsize.c
@@ -0,0 +1,134 @@
+/*===========================================================================*
+ * fsize.c								     *
+ *									     *
+ *	procedures to keep track of frame size				     *
+ *									     *
+ * EXPORTED PROCEDURES:							     *
+ *	Fsize_Reset							     *
+ *	Fsize_Note							     *
+ *	Fsize_Validate							     *
+ *									     *
+ * EXPORTED VARIABLES:							     *
+ *	Fsize_x								     *
+ *	Fsize_y								     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "all.h"
+#include "fsize.h"
+#include "dct.h"
+
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+int Fsize_x = 0;
+int Fsize_y = 0;
+
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+
+/*===========================================================================*
+ *
+ * Fsize_Reset
+ *
+ *	reset the frame size to 0
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    Fsize_x, Fsize_y
+ *
+ *===========================================================================*/
+void
+Fsize_Reset(void) {
+    Fsize_x = Fsize_y = 0;
+}
+
+
+
+/*===========================================================================*
+ *
+ * Fsize_Validate
+ *
+ *	make sure that the x, y values are 16-pixel aligned
+ *
+ * RETURNS:	modifies the x, y values to 16-pixel alignment
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Fsize_Validate(int * const xP,
+               int * const yP) {
+
+    *xP &= ~(DCTSIZE * 2 - 1);  /* Zero the low-order bits */
+    *yP &= ~(DCTSIZE * 2 - 1);  /* Zero the low-order bits */
+}
+
+
+/*===========================================================================*
+ *
+ * Fsize_Note
+ *
+ *	note the given frame size and modify the global values as appropriate
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    Fsize_x, Fsize_y
+ *
+ *===========================================================================*/
+void
+Fsize_Note(int          const id,
+           unsigned int const width,
+           unsigned int const height) {
+
+    Fsize_x = width;
+    Fsize_y = height;
+    Fsize_Validate(&Fsize_x, &Fsize_y);
+
+    if ((Fsize_x == 0) || (Fsize_y == 0)) {
+        fprintf(stderr,"Frame %d:  size is less than the minimum: %d x %d!\n",
+                id, DCTSIZE*2, DCTSIZE*2);
+        exit(1);
+    }
+
+#ifdef BLEAH
+    if (Fsize_x == 0) {
+	Fsize_x = width;
+	Fsize_y = height;
+	Fsize_Validate(&Fsize_x, &Fsize_y);
+    } else if (width < Fsize_x || height < Fsize_y) {
+	fprintf(stderr, "Frame %d: wrong size: (%d,%d).  Should be greater or equal to: (%d,%d)\n",
+		id, width, height, Fsize_x, Fsize_y);
+	exit(1);
+    }
+#endif
+}
diff --git a/converter/ppm/ppmtompeg/gethostname.c b/converter/ppm/ppmtompeg/gethostname.c
new file mode 100644
index 00000000..014b42e8
--- /dev/null
+++ b/converter/ppm/ppmtompeg/gethostname.c
@@ -0,0 +1,26 @@
+#define _BSD_SOURCE   /* Make sure strdup() is in string.h */
+
+#include <string.h>
+#include <errno.h>
+#include <sys/utsname.h>
+
+#include "pm.h"
+
+#include "gethostname.h"
+
+const char *
+GetHostName(void) {
+/*----------------------------------------------------------------------------
+   Return the host name of this system.
+-----------------------------------------------------------------------------*/
+    struct utsname utsname;
+    int rc;
+
+    rc = uname(&utsname);
+
+    if (rc < 0)
+        pm_error("Unable to find out host name.  "
+                 "uname() failed with errno %d (%s)", errno, strerror(errno));
+
+    return strdup(utsname.nodename);
+}
diff --git a/converter/ppm/ppmtompeg/gethostname.h b/converter/ppm/ppmtompeg/gethostname.h
new file mode 100644
index 00000000..e5450175
--- /dev/null
+++ b/converter/ppm/ppmtompeg/gethostname.h
@@ -0,0 +1,7 @@
+#ifndef GETHOSTNAME_H_INCLUDED
+#define GETHOSTNAME_H_INCLUDED
+
+const char *
+GetHostName(void);
+
+#endif
diff --git a/converter/ppm/ppmtompeg/headers/all.h b/converter/ppm/ppmtompeg/headers/all.h
new file mode 100644
index 00000000..e350aab8
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/all.h
@@ -0,0 +1,95 @@
+/*===========================================================================*
+ * all.h								     *
+ *									     *
+ *	stuff included from ALL source files				     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /u/smoot/md/mpeg_encode/headers/RCS/all.h,v 1.9 1995/06/05 21:11:06 smoot Exp $
+ *  $Log: all.h,v $
+ * Revision 1.9  1995/06/05  21:11:06  smoot
+ * added little_endian force for irizx
+ *
+ * Revision 1.8  1995/02/02  22:02:18  smoot
+ * added ifdefs for compatability on stranger and stranger architectures...
+ *
+ * Revision 1.7  1995/02/02  07:26:45  eyhung
+ * added parens to all.h to remove compiler warning
+ *
+ * Revision 1.6  1995/02/02  01:47:11  eyhung
+ * added MAXINT
+ *
+ * Revision 1.5  1995/01/19  23:54:33  eyhung
+ * Changed copyrights
+ *
+ * Revision 1.4  1994/11/14  22:52:04  smoot
+ * Added linux #include for time.h
+ *
+ * Revision 1.3  1994/11/12  02:12:13  keving
+ * nothing
+ *
+ * Revision 1.2  1993/07/22  22:24:23  keving
+ * nothing
+ *
+ * Revision 1.1  1993/07/09  00:17:23  keving
+ * nothing
+ *
+ */
+
+
+#ifndef ENCODE_ALL_INCLUDED
+#define ENCODE_ALL_INCLUDED
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <memory.h>
+#include <limits.h>
+
+/* There's got to be a better way.... */
+#ifdef LINUX
+#include <time.h>
+#endif
+#ifdef MIPS
+#include <time.h>
+#endif
+#ifdef IRIX
+#define FORCE_LITTLE_ENDIAN
+#include <time.h>
+#endif
+
+#include "ansi.h"
+#include "general.h"
+
+/* some machines have #define index strchr; get rid of this nonsense */
+#ifdef index
+#undef index
+#endif /* index */
+
+#ifndef MAXINT
+#define MAXINT 0x7fffffff
+#endif
+
+#endif /* ENCODE_ALL_INCLUDED */
diff --git a/converter/ppm/ppmtompeg/headers/ansi.h b/converter/ppm/ppmtompeg/headers/ansi.h
new file mode 100644
index 00000000..b3c3ab17
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/ansi.h
@@ -0,0 +1,76 @@
+/*===========================================================================*
+ * ansi.h								     *
+ *									     *
+ *	macro for non-ansi compilers					     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/picasso/project/mpeg/mpeg_dist/mpeg_encode/headers/RCS/ansi.h,v 1.6 1995/08/15 23:43:13 smoot Exp $
+ *  $Log: ansi.h,v $
+ *  Revision 1.6  1995/08/15 23:43:13  smoot
+ *  *** empty log message ***
+ *
+ *  Revision 1.5  1995/01/19 23:54:35  eyhung
+ *  Changed copyrights
+ *
+ * Revision 1.4  1994/11/12  02:12:13  keving
+ * nothing
+ *
+ * Revision 1.3  1993/07/22  22:24:23  keving
+ * nothing
+ *
+ * Revision 1.2  1993/07/09  00:17:23  keving
+ * nothing
+ *
+ * Revision 1.1  1993/06/14  22:50:22  keving
+ * nothing
+ *
+ */
+
+
+#ifndef ANSI_INCLUDED
+#define ANSI_INCLUDED
+
+
+/*  
+ *  _ANSI_ARGS_ macro stolen from Tcl6.5 by John Ousterhout
+ */
+#undef _ANSI_ARGS_
+#undef const
+#ifdef NON_ANSI_COMPILER
+#define _ANSI_ARGS_(x)       ()
+#define CONST
+#else
+#define _ANSI_ARGS_(x)   x
+#define CONST const
+#ifdef __cplusplus
+#define VARARGS (...)
+#else
+#define VARARGS ()
+#endif
+#endif
+
+
+#endif /* ANSI_INCLUDED */
diff --git a/converter/ppm/ppmtompeg/headers/bitio.h b/converter/ppm/ppmtompeg/headers/bitio.h
new file mode 100644
index 00000000..89e61fbb
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/bitio.h
@@ -0,0 +1,140 @@
+/*===========================================================================*
+ * bitio.h								     *
+ *									     *
+ *	bitwise input/output						     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/charlie-brown/project/mm/mpeg/mpeg_dist/mpeg_encode/headers/RCS/bitio.h,v 1.8 1995/01/19 23:54:37 eyhung Exp $
+ *  $Log: bitio.h,v $
+ * Revision 1.8  1995/01/19  23:54:37  eyhung
+ * Changed copyrights
+ *
+ * Revision 1.7  1994/11/12  02:12:14  keving
+ * nothing
+ *
+ * Revision 1.6  1993/07/22  22:24:23  keving
+ * nothing
+ *
+ * Revision 1.5  1993/07/09  00:17:23  keving
+ * nothing
+ *
+ * Revision 1.4  1993/06/03  21:08:53  keving
+ * nothing
+ *
+ * Revision 1.3  1993/01/18  10:20:02  dwallach
+ * *** empty log message ***
+ *
+ * Revision 1.2  1993/01/18  10:17:29  dwallach
+ * RCS headers installed, code indented uniformly
+ *
+ * Revision 1.2  1993/01/18  10:17:29  dwallach
+ * RCS headers installed, code indented uniformly
+ *
+ */
+
+
+#ifndef BIT_IO_INCLUDED
+#define BIT_IO_INCLUDED
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "general.h"
+#include "ansi.h"
+
+
+/*===========*
+ * CONSTANTS *
+ *===========*/
+
+#define WORDS_PER_BUCKET 128
+#define MAXBITS_PER_BUCKET	(WORDS_PER_BUCKET * 32)
+#define	MAX_BUCKETS	128
+#define MAX_BITS	MAX_BUCKETS*MAXBITS_PER_BUCKET
+
+
+/*=======================*
+ * STRUCTURE DEFINITIONS *
+ *=======================*/
+
+typedef struct bitBucket {
+    struct bitBucket *nextPtr;
+    uint32 bits[WORDS_PER_BUCKET];
+    int bitsleft, bitsleftcur, currword;
+} ActualBucket;
+
+typedef struct _BitBucket {
+    int totalbits;
+    int	cumulativeBits;
+    int	bitsWritten;
+    FILE    *filePtr;
+    ActualBucket *firstPtr;
+    ActualBucket *lastPtr;
+} BitBucket;
+
+
+/*========*
+ * MACROS *
+ *========*/
+
+#define	SET_ITH_BIT(bits, i)	(bits |= (1 << (i)))
+#define	GET_ITH_BIT(bits, i)	(bits & (1 << (i)))
+
+
+/*===============================*
+ * EXTERNAL PROCEDURE prototypes *
+ *===============================*/
+
+void	    
+Bitio_Free(BitBucket * const bbPtr);
+
+void
+Bitio_Write(BitBucket * const bbPtr, 
+            uint32      const bits, 
+            int         const nbits);
+
+void
+Bitio_BytePad(BitBucket * const bbPtr);
+
+BitBucket *
+Bitio_New(FILE * const filePtr);
+
+BitBucket *
+Bitio_New_Filename(const char * const fileName);
+
+void
+Bitio_Flush(BitBucket * const bbPtr);
+
+void
+Bitio_Close(BitBucket * const bbPtr);
+
+void
+Bitio_WriteToSocket(BitBucket * const bbPtr, 
+                    int         const socket);
+
+#endif /* BIT_IO_INCLUDED */
diff --git a/converter/ppm/ppmtompeg/headers/block.h b/converter/ppm/ppmtompeg/headers/block.h
new file mode 100644
index 00000000..46050492
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/block.h
@@ -0,0 +1,53 @@
+void
+ComputeDiffDCTs(MpegFrame * const current,
+                MpegFrame * const prev,
+                int         const by,
+                int         const bx,
+                vector      const m,
+                int *       const pattern);
+
+void
+ComputeDiffDCTBlock(Block           current,
+                    Block           dest,
+                    Block           motionBlock,
+                    boolean * const significantDifferenceP);
+
+void
+ComputeMotionBlock(uint8 ** const prev,
+                   int      const by,
+                   int      const bx,
+                   vector   const m,
+                   Block *  const motionBlockP);
+
+void
+ComputeMotionLumBlock(MpegFrame * const prevFrame,
+                      int         const by,
+                      int         const bx,
+                      vector      const m,
+                      LumBlock *  const motionBlockP);
+
+void
+BlockToData(uint8 ** const data,
+            Block          block,
+            int      const by,
+            int      const bx);
+
+void
+AddMotionBlock(Block          block,
+               uint8 ** const prev,
+               int      const by,
+               int      const bx,
+               vector   const m);
+
+void
+AddBMotionBlock(Block          block,
+                uint8 ** const prev,
+                uint8 ** const next,
+                int      const by,
+                int      const bx,
+                int      const mode,
+                motion   const motion);
+
+void
+BlockifyFrame(MpegFrame * const frameP);
+
diff --git a/converter/ppm/ppmtompeg/headers/byteorder.h b/converter/ppm/ppmtompeg/headers/byteorder.h
new file mode 100644
index 00000000..0070252a
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/byteorder.h
@@ -0,0 +1,77 @@
+/*===========================================================================*
+ * byteorder.h								     *
+ *									     *
+ *	stuff to handle different byte order				     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /u/smoot/md/mpeg_encode/headers/RCS/byteorder.h,v 1.3 1995/01/19 23:54:39 eyhung Exp $
+ *  $Log: byteorder.h,v $
+ * Revision 1.3  1995/01/19  23:54:39  eyhung
+ * Changed copyrights
+ *
+ * Revision 1.3  1995/01/19  23:54:39  eyhung
+ * Changed copyrights
+ *
+ * Revision 1.2  1994/11/12  02:12:15  keving
+ * nothing
+ *
+ * Revision 1.1  1993/07/22  22:24:23  keving
+ * nothing
+ *
+ */
+
+
+#include "general.h"
+
+
+#ifdef FORCE_BIG_ENDIAN
+    /* leave byte order as it is */
+#define htonl(x) (x)
+#define ntohl(x) (x)
+#define htons(x) (x)
+#define ntohs(x) (x)
+#else
+#ifdef FORCE_LITTLE_ENDIAN
+    /* need to reverse byte order */
+    /* note -- we assume here that htonl is called on a variable, not a
+     * constant; thus, this is not for general use, but works with bitio.c
+     */
+#define htonl(x)    \
+    ((((unsigned char *)(&x))[0] << 24) |	\
+     (((unsigned char *)(&x))[1] << 16) |	\
+     (((unsigned char *)(&x))[2] << 8) |	\
+     (((unsigned char *)(&x))[3]))
+#define ntohl(x)    htonl(x)
+#define htons(x)    \
+    ((((unsigned char *)(&x))[0] << 8) |	\
+     ((unsigned char *)(&x))[1])
+#define ntohs(x)    htons(x)
+#else
+    /* let in.h handle it, if possible */		   
+#include <sys/types.h>
+#include <netinet/in.h>
+#endif /* FORCE_LITTLE_ENDIAN */
+#endif /* FORCE_BIG_ENDIAN */
diff --git a/converter/ppm/ppmtompeg/headers/combine.h b/converter/ppm/ppmtompeg/headers/combine.h
new file mode 100644
index 00000000..e28c6dee
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/combine.h
@@ -0,0 +1,20 @@
+struct inputSource;
+
+void
+GOPsToMPEG(struct inputSource * const inputSourceP,
+           const char *         const outputFileName, 
+           FILE *               const outputFilePtr);
+
+typedef void (*fileAcquisitionFn)(void *       const handle,
+                                  unsigned int const frameNumber,
+                                  FILE **      const ifPP);
+
+
+typedef void (*fileDispositionFn)(void *       const handle,
+                                  unsigned int const frameNumber);
+
+void
+FramesToMPEG(FILE *               const outputFile, 
+             void *               const inputHandle,
+             fileAcquisitionFn          acquireInputFile,
+             fileDispositionFn          disposeInputFile);
diff --git a/converter/ppm/ppmtompeg/headers/dct.h b/converter/ppm/ppmtompeg/headers/dct.h
new file mode 100644
index 00000000..e024b6c1
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/dct.h
@@ -0,0 +1,79 @@
+/*===========================================================================*
+ * dct.h								     *
+ *									     *
+ *	DCT procedures							     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+
+#ifndef DCT_INCLUDED
+#define DCT_INCLUDED
+
+
+#include "ansi.h"
+
+
+
+#define DCTSIZE     8   /* you really don't want to change this */
+#define DCTSIZE_SQ 64   /* you really don't want to change this */
+
+#define DCTSIZE2    64
+typedef short DCTELEM;
+typedef DCTELEM DCTBLOCK[DCTSIZE2];
+typedef DCTELEM DCTBLOCK_2D[DCTSIZE][DCTSIZE];
+
+
+/*  
+ *  from mfwddct.c:
+ */
+extern void mp_fwd_dct_block2 _ANSI_ARGS_((DCTBLOCK_2D src, DCTBLOCK_2D dest));
+
+/* jrevdct.c */
+extern void init_pre_idct _ANSI_ARGS_((void ));
+extern void mpeg_jrevdct _ANSI_ARGS_((DCTBLOCK data ));
+
+
+/* We assume that right shift corresponds to signed division by 2 with
+ * rounding towards minus infinity.  This is correct for typical "arithmetic
+ * shift" instructions that shift in copies of the sign bit.  But some
+ * C compilers implement >> with an unsigned shift.  For these machines you
+ * must define RIGHT_SHIFT_IS_UNSIGNED.
+ * RIGHT_SHIFT provides a proper signed right shift of an int32 quantity.
+ * It is only applied with constant shift counts.  SHIFT_TEMPS must be
+ * included in the variables of any routine using RIGHT_SHIFT.
+ */
+
+#ifdef RIGHT_SHIFT_IS_UNSIGNED
+#define SHIFT_TEMPS     int32 shift_temp;
+#define RIGHT_SHIFT(x,shft)  \
+        ((shift_temp = (x)) < 0 ? \
+         (shift_temp >> (shft)) | ((~((int32) 0)) << (32-(shft))) : \
+         (shift_temp >> (shft)))
+#else
+#define SHIFT_TEMPS
+#define RIGHT_SHIFT(x,shft)     ((x) >> (shft))
+#endif
+
+
+#endif /* DCT_INCLUDED */
diff --git a/converter/ppm/ppmtompeg/headers/frame.h b/converter/ppm/ppmtompeg/headers/frame.h
new file mode 100644
index 00000000..1003ee15
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/frame.h
@@ -0,0 +1,147 @@
+/*===========================================================================*
+ * frame.h								     *
+ *									     *
+ *	basic frames procedures						     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+
+#ifndef FRAME_INCLUDED
+#define FRAME_INCLUDED
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "general.h"
+#include "ansi.h"
+#include "mtypes.h"
+
+/*===========*
+ * CONSTANTS *
+ *===========*/
+#define TYPE_IFRAME	2
+#define TYPE_PFRAME	3
+#define TYPE_BFRAME	4
+
+
+/*=======================*
+ * STRUCTURE DEFINITIONS *
+ *=======================*/
+
+typedef struct mpegFrame {
+    int type;
+    char    inputFileName[256];
+    int id;           /* the frame number -- starts at 0 */
+    boolean inUse;	/* TRUE iff this frame is currently being used */
+			/* FALSE means any data here can be thrashed */
+
+    /*  
+     *  now, the YCrCb data.  All pixel information is stored in unsigned
+     *  8-bit pieces.  We separate y, cr, and cb because cr and cb are
+     *  subsampled by a factor of 2.
+     *
+     *  if orig_y is NULL, then orig_cr, orig_cb are undefined
+     */
+    uint8 **orig_y, **orig_cr, **orig_cb;
+
+    /* now, the decoded data -- relevant only if
+     *	    referenceFrame == DECODED_FRAME
+     *
+     * if decoded_y is NULL, then decoded_cr, decoded_cb are undefined 
+     */
+    uint8 **decoded_y, **decoded_cr, **decoded_cb;
+
+    /* reference data */
+    uint8 **ref_y, **ref_cr, **ref_cb;
+
+    /*  
+     *  these are the Blocks which will ultimately compose MacroBlocks.
+     *  A Block is in a format that mp_fwddct() can crunch.
+     *  if y_blocks is NULL, then cr_blocks, cb_blocks are undefined
+     */
+    Block **y_blocks, **cr_blocks, **cb_blocks;
+
+    /*
+     *  this is the half-pixel luminance data (for reference frames)
+     */
+    uint8 **halfX, **halfY, **halfBoth;
+
+    boolean   halfComputed;        /* TRUE iff half-pixels already computed */
+
+    struct mpegFrame *next;  /* points to the next B-frame to be encoded, if
+		       * stdin is used as the input. 
+		       */
+} MpegFrame;
+
+
+void
+Frame_Init(unsigned int const numOfFramesRequested);
+
+void
+Frame_Exit(void);
+
+void
+Frame_Free(MpegFrame * const frameP);
+
+MpegFrame *
+Frame_New(int const id,
+          int const type);
+
+void
+Frame_AllocBlocks(MpegFrame * const frameP);
+
+void
+Frame_AllocYCC(MpegFrame * const frameP);
+
+void
+Frame_AllocHalf(MpegFrame * const frameP);
+
+void
+Frame_AllocDecoded(MpegFrame * const frameP,
+                   boolean     const makeReference);
+
+void
+Frame_Resize(MpegFrame * const omf,
+             MpegFrame * const mf,
+             int         const insize_x,
+             int         const insize_y,
+             int         const outsize_x,
+             int         const outsize_y);
+
+
+extern void	  Frame_Free _ANSI_ARGS_((MpegFrame *frame));
+extern void	  Frame_Exit _ANSI_ARGS_((void));
+extern void	  Frame_AllocPPM _ANSI_ARGS_((MpegFrame * frame));
+extern void	  Frame_AllocYCC _ANSI_ARGS_((MpegFrame * mf));
+extern void	  Frame_AllocDecoded _ANSI_ARGS_((MpegFrame *frame,
+						  boolean makeReference));
+extern void	  Frame_AllocHalf _ANSI_ARGS_((MpegFrame *frame));
+extern void	  Frame_AllocBlocks _ANSI_ARGS_((MpegFrame *mf));
+extern void	  Frame_Resize _ANSI_ARGS_((MpegFrame *omf, MpegFrame *mf,
+					    int insize_x, int insize_y,
+					    int outsize_x, int outsize_y));
+
+
+#endif /* FRAME_INCLUDED */
diff --git a/converter/ppm/ppmtompeg/headers/frames.h b/converter/ppm/ppmtompeg/headers/frames.h
new file mode 100644
index 00000000..3fefaea7
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/frames.h
@@ -0,0 +1,487 @@
+/*===========================================================================*
+ * frames.h                                  
+ *                                       
+ *  stuff dealing with frames                        
+ *                                       
+ *===========================================================================*/
+
+#ifndef FRAMES_INCLUDED
+#define FRAMES_INCLUDED
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "ansi.h"
+#include "mtypes.h"
+#include "mheaders.h"
+#include "frame.h"
+
+
+/*===========*
+ * CONSTANTS *
+ *===========*/
+
+#define I_FRAME 1
+#define P_FRAME 2
+#define B_FRAME 3
+
+#define LUM_BLOCK   0
+#define CHROM_BLOCK 1
+#define CR_BLOCK    2
+#define CB_BLOCK    3
+
+#define MOTION_FORWARD      0
+#define MOTION_BACKWARD     1
+#define MOTION_INTERPOLATE  2
+
+
+#define USE_HALF    0
+#define USE_FULL    1
+
+    /* motion vector stuff */
+#define FORW_F_CODE fCode       /* from picture header */
+#define BACK_F_CODE fCode
+#define FORW_F  (1 << (FORW_F_CODE - 1))
+#define BACK_F  (1 << (BACK_F_CODE - 1))
+#define RANGE_NEG   (-(1 << (3 + FORW_F_CODE)))
+#define RANGE_POS   ((1 << (3 + FORW_F_CODE))-1)
+#define MODULUS     (1 << (4 + FORW_F_CODE))
+
+#define ORIGINAL_FRAME  0
+#define DECODED_FRAME   1
+
+
+/*=======================*
+ * STRUCTURE DEFINITIONS *
+ *=======================*/
+
+typedef struct FrameTableStruct {
+    /* the following are all initted once and never changed */
+    /* (they depend only on the pattern */
+    char typ;
+    struct FrameTableStruct *next;
+    struct FrameTableStruct *prev;
+
+    /* nextOutput is a pointer to next frame table entry to output */
+    struct FrameTableStruct *nextOutput;
+
+    boolean freeNow;    /* TRUE iff no frames point back to this */
+
+    int number;
+
+    int bFrameNumber;       /* actual frame number, if a b-frame */
+    
+} FrameTable;
+
+
+/*==================*
+ * TYPE DEFINITIONS *
+ *==================*/
+
+typedef struct dct_data_tye_struct {
+  char useMotion;
+  char pattern, mode;
+  int fmotionX, fmotionY, bmotionX, bmotionY;
+} dct_data_type;
+
+void    EncodeYDC _ANSI_ARGS_((int32 dc_term, int32 *pred_term, BitBucket *bb));
+void
+EncodeCDC(int32       const dc_term,
+          int32     * const pred_term,
+          BitBucket * const bb);
+
+
+/*========*
+ * MACROS *
+ *========*/
+
+/* return ceiling(a/b) where a, b are ints, using temp value c */
+#define int_ceil_div(a,b,c)     ((b*(c = a/b) < a) ? (c+1) : c)
+#define int_floor_div(a,b,c)    ((b*(c = a/b) > a) ? (c-1) : c)
+
+/* assumes many things:
+ * block indices are (y,x)
+ * variables y_dc_pred, cr_dc_pred, and cb_dc_pred
+ * flat block fb exists
+ */
+#define GEN_I_BLOCK(frameType, frame, bb, mbAI, qscale) {                   \
+    boolean overflow, overflowChange=FALSE;                             \
+        int overflowValue = 0;                                              \
+        do {                                                                \
+      overflow =  Mpost_QuantZigBlock(dct[y][x], fb[0],                 \
+             qscale, TRUE)==MPOST_OVERFLOW;                     \
+          overflow |= Mpost_QuantZigBlock(dct[y][x+1], fb[1],               \
+                 qscale, TRUE)==MPOST_OVERFLOW;                     \
+      overflow |= Mpost_QuantZigBlock(dct[y+1][x], fb[2],               \
+                         qscale, TRUE)==MPOST_OVERFLOW;                     \
+      overflow |= Mpost_QuantZigBlock(dct[y+1][x+1], fb[3],             \
+                         qscale, TRUE)==MPOST_OVERFLOW;                     \
+      overflow |= Mpost_QuantZigBlock(dctb[y >> 1][x >> 1],             \
+                         fb[4], qscale, TRUE)==MPOST_OVERFLOW;              \
+      overflow |= Mpost_QuantZigBlock(dctr[y >> 1][x >> 1],             \
+             fb[5], qscale, TRUE)==MPOST_OVERFLOW;              \
+          if ((overflow) && (qscale!=31)) {                                 \
+           overflowChange = TRUE; overflowValue++;                          \
+       qscale++;                                                        \
+       } else overflow = FALSE;                                         \
+    } while (overflow);                                                 \
+        Mhead_GenMBHeader(bb,                           \
+            frameType /* pict_code_type */, mbAI /* addr_incr */,   \
+            qscale /* q_scale */,                               \
+            0 /* forw_f_code */, 0 /* back_f_code */,           \
+            0 /* horiz_forw_r */, 0 /* vert_forw_r */,          \
+            0 /* horiz_back_r */, 0 /* vert_back_r */,          \
+            0 /* motion_forw */, 0 /* m_horiz_forw */,          \
+            0 /* m_vert_forw */, 0 /* motion_back */,           \
+            0 /* m_horiz_back */, 0 /* m_vert_back */,          \
+            0 /* mb_pattern */, TRUE /* mb_intra */);           \
+                                        \
+    /* Y blocks */                              \
+        EncodeYDC(fb[0][0], &y_dc_pred, bb);                            \
+    Mpost_RLEHuffIBlock(fb[0], bb);                         \
+    EncodeYDC(fb[1][0], &y_dc_pred, bb);                        \
+        Mpost_RLEHuffIBlock(fb[1], bb);                             \
+    EncodeYDC(fb[2][0], &y_dc_pred, bb);                        \
+    Mpost_RLEHuffIBlock(fb[2], bb);                         \
+    EncodeYDC(fb[3][0], &y_dc_pred, bb);                        \
+    Mpost_RLEHuffIBlock(fb[3], bb);                         \
+                                        \
+    /* CB block */                              \
+    EncodeCDC(fb[4][0], &cb_dc_pred, bb);                   \
+    Mpost_RLEHuffIBlock(fb[4], bb);                     \
+                                        \
+    /* CR block */                              \
+    EncodeCDC(fb[5][0], &cr_dc_pred, bb);                   \
+    Mpost_RLEHuffIBlock(fb[5], bb);                     \
+    if (overflowChange) qscale -= overflowValue;                        \
+    }
+
+#define BLOCK_TO_FRAME_COORD(bx1, bx2, x1, x2) {    \
+    x1 = (bx1)*DCTSIZE;             \
+    x2 = (bx2)*DCTSIZE;             \
+    }
+
+static __inline__ void
+MotionToFrameCoord(int   const by,
+                   int   const bx,
+                   int   const my,
+                   int   const mx,
+                   int * const yP,
+                   int * const xP) {
+/*----------------------------------------------------------------------------
+   Given a DCT block location and a motion vector, return the pixel
+   coordinates to which the upper left corner of the block moves.
+
+   Return negative coordinates if it moves out of frame.
+-----------------------------------------------------------------------------*/
+    *yP = by * DCTSIZE + my;
+    *xP = bx * DCTSIZE + mx;
+}
+
+#define COORD_IN_FRAME(fy,fx, type)                 \
+    ((type == LUM_BLOCK) ?                      \
+     ((fy >= 0) && (fx >= 0) && (fy < Fsize_y) && (fx < Fsize_x)) : \
+     ((fy >= 0) && (fx >= 0) && (fy < (Fsize_y>>1)) && (fx < (Fsize_x>>1))))
+
+
+static __inline__ void
+encodeMotionVector(int      const x,
+                   int      const y,
+                   vector * const qP,
+                   vector * const rP,
+                   int      const f,
+                   int      const fCode) {
+
+    int tempX;
+    int tempY;
+    int tempC;
+
+    if (x < RANGE_NEG)
+        tempX = x + MODULUS;
+    else if (x > RANGE_POS)
+        tempX = x - MODULUS;
+    else
+        tempX = x;
+
+    if (y < RANGE_NEG)
+        tempY = y + MODULUS;
+    else if (y > RANGE_POS)
+        tempY = y - MODULUS;
+    else
+        tempY = y;
+
+    if (tempX >= 0) {
+        qP->x = int_ceil_div(tempX, f, tempC);
+        rP->x = f - 1 + tempX - qP->x*f;
+    } else {
+        qP->x = int_floor_div(tempX, f, tempC);
+        rP->x = f - 1 - tempX + qP->x*f;
+    }
+
+    if (tempY >= 0) {
+        qP->y = int_ceil_div(tempY, f, tempC);
+        rP->y = f - 1 + tempY - qP->y*f;
+    } else {
+        qP->y = int_floor_div(tempY, f, tempC);
+        rP->y = f - 1 - tempY + qP->y*f;
+    }
+}
+
+
+#define DoQuant(bit, src, dest)                                         \
+  if (pattern & bit) {                                                  \
+    switch (Mpost_QuantZigBlock(src, dest, QScale, FALSE)) {            \
+    case MPOST_NON_ZERO:                                                \
+      break;                                                            \
+    case MPOST_ZERO:                                                    \
+      pattern ^= bit;                                                   \
+      break;                                                            \
+    case MPOST_OVERFLOW:                                                \
+      if (QScale != 31) {                                               \
+    QScale++;                                                       \
+    overflowChange = TRUE;                                          \
+    overflowValue++;                                                \
+    goto calc_blocks;                                               \
+      }                                                                 \
+      break;                                                            \
+    }                                                                   \
+  }
+
+/*===============================*
+ * EXTERNAL PROCEDURE prototypes *
+ *===============================*/
+
+void
+ComputeBMotionLumBlock(MpegFrame * const prev,
+                       MpegFrame * const next,
+                       int         const by,
+                       int         const bx,
+                       int         const mode,
+                       motion      const motion,
+                       LumBlock *  const motionBlockP);
+
+int
+BMotionSearch(const LumBlock * const currentBlockP,
+              MpegFrame *      const prev,
+              MpegFrame *      const next,
+              int              const by,
+              int              const bx,
+              motion *         const motionP,
+              int              const oldMode);
+
+void GenIFrame (BitBucket * const bb,
+                MpegFrame * const mf);
+void GenPFrame (BitBucket * const bb,
+                MpegFrame * const current,
+                MpegFrame * const prev);
+void GenBFrame (BitBucket * const bb,
+                MpegFrame * const curr,
+                MpegFrame * const prev,
+                MpegFrame * const next);
+void    AllocDctBlocks _ANSI_ARGS_((void ));
+
+
+float
+IFrameTotalTime(void);
+
+float
+PFrameTotalTime(void);
+
+float
+BFrameTotalTime(void);
+
+void
+ShowIFrameSummary(unsigned int const inputFrameBits, 
+                  unsigned int const totalBits, 
+                  FILE *       const fpointer);
+
+void
+ShowPFrameSummary(unsigned int const inputFrameBits, 
+                  unsigned int const totalBits, 
+                  FILE *       const fpointer);
+
+void
+ShowBFrameSummary(unsigned int const inputFrameBits, 
+                  unsigned int const totalBits, 
+                  FILE *       const fpointer);
+
+/* DIFFERENCE FUNCTIONS */
+
+int32
+LumBlockMAD(const LumBlock * const currentBlockP,
+            const LumBlock * const motionBlockP,
+            int32            const bestSoFar);
+
+int32
+LumBlockMSE(const LumBlock * const currentBlockP,
+            const LumBlock * const motionBlockP,
+            int32            const bestSoFar);
+
+int32
+LumMotionError(const LumBlock * const currentBlockP,
+               MpegFrame *      const prev,
+               int              const by,
+               int              const bx,
+               vector           const m,
+               int32            const bestSoFar);
+
+int32
+LumAddMotionError(const LumBlock * const currentBlockP,
+                  const LumBlock * const blockSoFarP,
+                  MpegFrame *      const prev,
+                  int              const by,
+                  int              const bx,
+                  vector           const m,
+                  int32            const bestSoFar);
+
+int32
+LumMotionErrorA(const LumBlock * const currentBlockP,
+                MpegFrame *      const prevFrame,
+                int              const by,
+                int              const bx,
+                vector           const m,
+                int32            const bestSoFar);
+
+int32
+LumMotionErrorB(const LumBlock * const currentP,
+                MpegFrame *      const prevFrame,
+                int              const by,
+                int              const bx,
+                vector           const m,
+                int32            const bestSoFar);
+
+int32
+LumMotionErrorC(const LumBlock * const currentP,
+                MpegFrame *      const prevFrame,
+                int              const by,
+                int              const bx,
+                vector           const m,
+                int32            const bestSoFar);
+
+int32
+LumMotionErrorD(const LumBlock * const currentP,
+                MpegFrame *      const prevFrame,
+                int              const by,
+                int              const bx,
+                vector           const m,
+                int32            const bestSoFar);
+
+int32
+LumMotionErrorSubSampled(const LumBlock * const currentBlockP,
+                         MpegFrame *      const prevFrame,
+                         int              const by,
+                         int              const bx,
+                         vector           const m,
+                         int              const startY,
+                         int              const startX);
+
+void
+BlockComputeSNR(MpegFrame * const current,
+                float *     const snr,
+                float *     const psnr);
+
+int32
+time_elapsed(void);
+
+void
+AllocDctBlocks(void);
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+extern int pixelFullSearch;
+extern int searchRangeP,searchRangeB;  /* defined in psearch.c */
+extern int qscaleI;
+extern int gopSize;
+extern int slicesPerFrame;
+extern int blocksPerSlice;
+extern int referenceFrame;
+extern int specificsOn;
+extern int quietTime;       /* shut up for at least quietTime seconds;
+                 * negative means shut up forever
+                 */
+extern boolean realQuiet;   /* TRUE = no messages to stdout */
+
+extern boolean frameSummary;    /* TRUE = frame summaries should be printed */
+extern boolean  printSNR;
+extern boolean  printMSE;
+extern boolean  decodeRefFrames;    /* TRUE = should decode I and P frames */
+extern int  fCodeI,fCodeP,fCodeB;
+extern boolean    forceEncodeLast;
+extern int TIME_RATE;
+
+#endif /* FRAMES_INCLUDED */
+
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/picasso/project/mpeg/mpeg_dist/mpeg_encode/headers/RCS/frames.h,v 1.13 1995/08/15 23:43:04 smoot Exp $
+ *  $Log: frames.h,v $
+ *  Revision 1.13  1995/08/15 23:43:04  smoot
+ *  *** empty log message ***
+ *
+ * Revision 1.12  1995/04/14  23:13:18  smoot
+ * Reorganized for better rate control.  Added overflow in DCT values
+ * handling.
+ *
+ * Revision 1.11  1995/01/19  23:54:46  smoot
+ * allow computediffdcts to un-assert parts of the pattern
+ *
+ * Revision 1.10  1995/01/16  07:43:10  eyhung
+ * Added realQuiet
+ *
+ * Revision 1.9  1995/01/10  23:15:28  smoot
+ * Fixed searchRange lack of def
+ *
+ * Revision 1.8  1994/11/15  00:55:36  smoot
+ * added printMSE
+ *
+ * Revision 1.7  1994/11/14  22:51:02  smoot
+ * added specifics flag.  Added BlockComputeSNR parameters
+ *
+ * Revision 1.6  1994/11/01  05:07:23  darryl
+ *  with rate control changes added
+ *
+ * Revision 1.1  1994/09/27  01:02:55  darryl
+ * Initial revision
+ *
+ * Revision 1.5  1993/07/22  22:24:23  keving
+ * nothing
+ *
+ * Revision 1.4  1993/07/09  00:17:23  keving
+ * nothing
+ *
+ * Revision 1.3  1993/06/03  21:08:53  keving
+ * nothing
+ *
+ * Revision 1.2  1993/03/02  19:00:27  keving
+ * nothing
+ *
+ * Revision 1.1  1993/02/19  20:15:51  keving
+ * nothing
+ *
+ */
+
+
diff --git a/converter/ppm/ppmtompeg/headers/frametype.h b/converter/ppm/ppmtompeg/headers/frametype.h
new file mode 100644
index 00000000..63bee964
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/frametype.h
@@ -0,0 +1,17 @@
+#ifndef FRAMETYPE_H_INCLUDED
+#define FRAMETYPE_H_INCLUDED
+
+char
+FType_Type(unsigned int const frameNum);
+
+unsigned int
+FType_FutureRef(unsigned int const currFrameNum);
+
+int	FType_PastRef _ANSI_ARGS_((int currFrameNum));
+
+void SetFramePattern(const char * const pattern);
+
+void
+ComputeFrameTable(unsigned int const numFrames);
+
+#endif
diff --git a/converter/ppm/ppmtompeg/headers/fsize.h b/converter/ppm/ppmtompeg/headers/fsize.h
new file mode 100644
index 00000000..6a1c910e
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/fsize.h
@@ -0,0 +1,49 @@
+/*===========================================================================*
+ * fsize.h								     *
+ *									     *
+ *	procedures to deal with frame size				     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+
+#ifndef FSIZE_H_INCLUDED
+#define FSIZE_H_INCLUDED
+
+extern int Fsize_x;
+extern int Fsize_y;
+
+
+void
+Fsize_Reset(void);
+
+void
+Fsize_Validate(int * const xP,
+               int * const yP);
+
+void
+Fsize_Note(int          const id,
+           unsigned int const width,
+           unsigned int const height);
+
+#endif
diff --git a/converter/ppm/ppmtompeg/headers/general.h b/converter/ppm/ppmtompeg/headers/general.h
new file mode 100644
index 00000000..f29c9445
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/general.h
@@ -0,0 +1,174 @@
+/*===========================================================================*
+ * general.h								     *
+ *									     *
+ *	very general stuff						     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/picasso/project/mpeg/mpeg_dist/mpeg_encode/headers/RCS/general.h,v 1.7 1995/08/04 23:34:13 smoot Exp $
+ *  $Log: general.h,v $
+ *  Revision 1.7  1995/08/04 23:34:13  smoot
+ *  jpeg5 changed the silly HAVE_BOOLEAN define....
+ *
+ *  Revision 1.6  1995/01/19 23:54:49  eyhung
+ *  Changed copyrights
+ *
+ * Revision 1.5  1994/11/12  02:12:48  keving
+ * nothing
+ *
+ * Revision 1.4  1993/07/22  22:24:23  keving
+ * nothing
+ *
+ * Revision 1.3  1993/07/09  00:17:23  keving
+ * nothing
+ *
+ * Revision 1.2  1993/06/03  21:08:53  keving
+ * nothing
+ *
+ * Revision 1.1  1993/02/22  22:39:19  keving
+ * nothing
+ *
+ */
+
+
+#ifndef GENERAL_INCLUDED
+#define GENERAL_INCLUDED
+
+
+/* prototypes for library procedures
+ *
+ * if your /usr/include headers do not have these, then pass -DMISSING_PROTOS
+ * to your compiler
+ *
+ */ 
+#ifdef MISSING_PROTOS
+int fprintf();
+int fwrite();
+int fread();
+int fflush();
+int fclose();
+
+int sscanf();
+int bzero();
+int bcopy();
+int system();
+int time();
+int perror();
+
+int socket();
+int bind();
+int listen();
+int accept();
+int connect();
+int close();
+int read();
+int write();
+
+int pclose();
+
+#endif
+
+
+/*===========*
+ * CONSTANTS *
+ *===========*/
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#define FALSE 0
+#endif
+
+#define SPACE ' '
+#define TAB '\t'
+#define SEMICOLON ';'
+#define NULL_CHAR '\0'
+#define NEWLINE '\n'
+
+
+/*==================*
+ * TYPE DEFINITIONS *
+ *==================*/
+
+#ifndef HAVE_BOOLEAN
+typedef int boolean;
+#define HAVE_BOOLEAN
+/* JPEG library also defines boolean */
+#endif
+
+/* In the following, we need the "signed" in order to make these typedefs
+   match those in AIX system header files.  Otherwise, compile fails on 
+   AIX.  2000.09.11.
+*/
+typedef unsigned char uint8;
+typedef signed char int8;
+typedef unsigned short uint16;
+typedef signed short int16;
+
+/* The 32 bit integer types are probably obsolete.
+
+   parallel.c used to use them as buffers for socket I/O, because in the
+   protocol on the socket, an integer is represented by 4 octets.  But
+   the proper type for that is unsigned char[4], so we changed parallel.c
+   to that in September 2004.
+
+   A user of TRU64 4.0f in May 2000 used a -DLONG_32 option, but did
+   not indicate that it was really necessary.  After that, Configure
+   added -DLONG_32 for all TRU64 builds.  A user of TRU64 5.1A in July
+   2003 demonstrated that int is in fact 4 bytes on his machine (and
+   long is 8 bytes).  So we removed the -DLONG_32 from Configure.  This
+   whole issue may too be obsolete because of the fixing of parallel.c
+   mentioned above.
+*/
+
+    /* LONG_32 should only be defined iff
+     *	    1) long's are 32 bits and
+     *	    2) int's are not
+     */
+#ifdef LONG_32		
+typedef unsigned long uint32;
+typedef long int32;
+#else
+typedef unsigned int uint32;
+typedef signed int int32;
+#endif
+
+
+/*========*
+ * MACROS *
+ *========*/
+
+#undef max
+#define max(a,b) ((a) > (b) ? (a) : (b))
+#undef min
+#define min(a,b) ((a) < (b) ? (a) : (b))
+#undef abs
+#define abs(a) ((a) >= 0 ? (a) : -(a))
+
+
+#endif
diff --git a/converter/ppm/ppmtompeg/headers/huff.h b/converter/ppm/ppmtompeg/headers/huff.h
new file mode 100644
index 00000000..47ffb843
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/huff.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/charlie-brown/project/mm/mpeg/mpeg_dist/mpeg_encode/headers/RCS/huff.h,v 1.3 1995/01/19 23:54:51 eyhung Exp $
+ */
+
+/*  
+ *  THIS FILE IS MACHINE GENERATED!  DO NOT EDIT!
+ */
+#define HUFF_MAXRUN	32
+#define HUFF_MAXLEVEL	41
+
+extern int huff_maxlevel[];
+extern uint32 *huff_table[];
+extern int *huff_bits[];
diff --git a/converter/ppm/ppmtompeg/headers/input.h b/converter/ppm/ppmtompeg/headers/input.h
new file mode 100644
index 00000000..75734237
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/input.h
@@ -0,0 +1,70 @@
+#ifndef INPUT_H_INCLUDED
+#define INPUT_H_INCLUDED
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "frame.h"
+
+struct InputFileEntry;
+
+struct inputSource {
+/*----------------------------------------------------------------------------
+   This describes the source of data for the program.
+   Typically, the source data is a bunch of raw frames in PPM format.
+   But sometimes, it is a bunch of already encoded frames or GOPs.
+-----------------------------------------------------------------------------*/
+    bool stdinUsed;
+    int numInputFiles;
+        /* This is the maximum number of input files available.  If
+           we're reading from explicitly named files, it is exactly
+           the number available.  If we're reading from a stream, it's
+           infinity.  (At the moment, "reading from a stream" is
+           equivalent to "reading from Standard Input").
+        */
+
+    /* Members below here defined only if 'stdinUsed' is false */
+
+    struct InputFileEntry ** inputFileEntries;
+        /* Each element of this array describes a set of input files.
+           Valid elements are consecutive starting at index 0.
+        */
+    unsigned int             numInputFileEntries;
+        /* Number of valid entries in array inputFileEntries[] */
+    unsigned int             ifArraySize;
+        /* Number of allocated entries in the array inputFileEntries[] */
+};
+
+
+void
+GetNthInputFileName(struct inputSource * const inputSourceP,
+                    unsigned int         const n,
+                    const char **        const fileName);
+
+void
+ReadNthFrame(struct inputSource * const inputSourceP,
+             unsigned int         const frameNumber,
+             boolean              const remoteIO,
+             boolean              const childProcess,
+             boolean              const separateConversion,
+             const char *         const slaveConversion,
+             const char *         const inputConversion,
+             MpegFrame *          const frameP,
+             bool *               const endOfStreamP);
+
+void
+JM2JPEG(struct inputSource * const inputSourceP);
+
+void
+AddInputFiles(struct inputSource * const inputSourceP,
+              const char *         const input);
+
+void
+SetStdinInput(struct inputSource * const inputSourceP);
+
+void
+CreateInputSource(struct inputSource ** const inputSourcePP);
+
+void
+DestroyInputSource(struct inputSource * const inputSourceP);
+
+#endif
diff --git a/converter/ppm/ppmtompeg/headers/jpeg.h b/converter/ppm/ppmtompeg/headers/jpeg.h
new file mode 100644
index 00000000..62c6f930
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/jpeg.h
@@ -0,0 +1,12 @@
+#include "ansi.h"
+
+
+void
+JMovie2JPEG(const char * const infilename,
+            const char * const obase,
+            int          const start,
+            int          const end);
+
+void
+ReadJPEG(MpegFrame * const mf,
+         FILE *      const fp);
diff --git a/converter/ppm/ppmtompeg/headers/mheaders.h b/converter/ppm/ppmtompeg/headers/mheaders.h
new file mode 100644
index 00000000..21d43e3d
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/mheaders.h
@@ -0,0 +1,114 @@
+/*===========================================================================*
+ * mheaders.h								     *
+ *									     *
+ *	MPEG headers							     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/picasso/project/mm/mpeg/mpeg_dist/mpeg_encode/headers/RCS/mheaders.h,v 1.4 1995/03/27 19:29:24 smoot Exp $
+ *  $Log: mheaders.h,v $
+ * Revision 1.4  1995/03/27  19:29:24  smoot
+ * changed to remove mb_quant
+ *
+ * Revision 1.3  1995/01/19  23:54:56  eyhung
+ * Changed copyrights
+ *
+ * Revision 1.2  1994/11/12  02:12:51  keving
+ * nothing
+ *
+ * Revision 1.1  1993/07/22  22:24:23  keving
+ * nothing
+ *
+ *
+ */
+
+
+#ifndef MHEADERS_INCLUDED
+#define MHEADERS_INCLUDED
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "general.h"
+#include "ansi.h"
+#include "bitio.h"
+
+
+/*===============================*
+ * EXTERNAL PROCEDURE prototypes *
+ *===============================*/
+
+void	SetGOPStartTime _ANSI_ARGS_((int index));
+
+void
+Mhead_GenSequenceHeader(BitBucket *   const bbPtr, 
+                        uint32        const hsize, 
+                        uint32        const vsize,
+                        int32         const pratio, 
+                        int32         const pict_rate, 
+                        int32         const bit_rate_arg,
+                        int32         const buf_size_arg, 
+                        int32         const c_param_flag_arg, 
+                        const int32 * const iq_matrix, 
+                        const int32 * const niq_matrix,
+                        uint8 *       const ext_data, 
+                        int32         const ext_data_size, 
+                        uint8 *       const user_data,
+                        int32         const user_data_size);
+
+void	Mhead_GenSequenceEnder _ANSI_ARGS_((BitBucket *bbPtr));
+void	Mhead_GenGOPHeader _ANSI_ARGS_((BitBucket *bbPtr,
+	   int32 drop_frame_flag,
+           int32 tc_hrs, int32 tc_min,
+           int32 tc_sec, int32 tc_pict,
+           int32 closed_gop, int32 broken_link,
+           uint8 *ext_data, int32 ext_data_size,
+           uint8 *user_data, int32 user_data_size));
+void	Mhead_GenPictureHeader _ANSI_ARGS_((BitBucket *bbPtr, int frameType,
+					    int pictCount, int f_code));
+void	Mhead_GenSliceHeader _ANSI_ARGS_((BitBucket *bbPtr, uint32 slicenum,
+					  uint32 qscale, uint8 *extra_info,
+					  uint32 extra_info_size));
+void	Mhead_GenSliceEnder _ANSI_ARGS_((BitBucket *bbPtr));
+void	Mhead_GenMBHeader _ANSI_ARGS_((BitBucket *bbPtr,
+	  uint32 pict_code_type, uint32 addr_incr,
+          uint32 q_scale,
+          uint32 forw_f_code, uint32 back_f_code,
+          uint32 horiz_forw_r, uint32 vert_forw_r,
+          uint32 horiz_back_r, uint32 vert_back_r,
+          int32 motion_forw, int32 m_horiz_forw,
+          int32 m_vert_forw, int32 motion_back,
+          int32 m_horiz_back, int32 m_vert_back,
+          uint32 mb_pattern, uint32 mb_intra));
+
+
+#endif /* MHEADERS_INCLUDED */
+
+
+
+
+
diff --git a/converter/ppm/ppmtompeg/headers/motion_search.h b/converter/ppm/ppmtompeg/headers/motion_search.h
new file mode 100644
index 00000000..ab83cbca
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/motion_search.h
@@ -0,0 +1,154 @@
+/*===========================================================================*
+ * search.h                                  *
+ *                                       *
+ *  stuff dealing with the motion search                     *
+ *                                       *
+ *===========================================================================*/
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "ansi.h"
+
+
+/*===========*
+ * CONSTANTS *
+ *===========*/
+
+#define PSEARCH_SUBSAMPLE   0
+#define PSEARCH_EXHAUSTIVE  1
+#define PSEARCH_LOGARITHMIC 2
+#define PSEARCH_TWOLEVEL    3
+
+#define BSEARCH_EXHAUSTIVE  0
+#define BSEARCH_CROSS2      1
+#define BSEARCH_SIMPLE      2
+
+
+/*========*
+ * MACROS *
+ *========*/
+
+#define COMPUTE_MOTION_BOUNDARY(by,bx,stepSize,leftMY,leftMX,rightMY,rightMX)\
+leftMY = -2*DCTSIZE*by; /* these are valid motion vectors */         \
+leftMX = -2*DCTSIZE*bx;                          \
+/* these are invalid motion vectors */       \
+rightMY = 2*(Fsize_y - (by+2)*DCTSIZE + 1) - 1;              \
+rightMX = 2*(Fsize_x - (bx+2)*DCTSIZE + 1) - 1;              \
+\
+if ( stepSize == 2 ) { \
+    rightMY++;      \
+    rightMX++;      \
+    }
+    
+#define VALID_MOTION(m)   \
+(((m).y >= leftMY) && ((m).y < rightMY) &&   \
+ ((m).x >= leftMX) && ((m).x < rightMX) )
+
+
+/*===============================*
+ * EXTERNAL PROCEDURE prototypes *
+ *===============================*/
+
+void 
+SetPSearchAlg(const char * const alg);
+void 
+SetBSearchAlg(const char * const alg);
+const char *
+BSearchName(void);
+const char *
+PSearchName(void);
+
+int
+PLogarithmicSearch(const LumBlock * const currentBlockP,
+                   MpegFrame *      const prev,
+                   int              const by,
+                   int              const bx,
+                   vector *         const motionP,
+                   int              const searchRange);
+
+int
+PSubSampleSearch(const LumBlock * const currentBlockP,
+                 MpegFrame *      const prev,
+                 int              const by,
+                 int              const bx,
+                 vector *         const motionP,
+                 int              const searchRange);
+
+int
+PLocalSearch(const LumBlock * const currentBlockP,
+             MpegFrame *      const prev,
+             int              const by,
+             int              const bx,
+             vector *         const motionP,
+             int              const bestSoFar,
+             int              const searchRange);
+
+int
+PTwoLevelSearch(const LumBlock * const currentBlockP,
+                MpegFrame *      const prev,
+                int              const by,
+                int              const bx,
+                vector *         const motionP,
+                int              const bestSoFar,
+                int              const searchRange);
+void
+PMotionSearch(const LumBlock * const currentBlockP,
+              MpegFrame *      const prev, 
+              int              const by,
+              int              const bx, 
+              vector *         const motionP);
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+extern int psearchAlg;
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/picasso/project/mpeg/mpeg_dist/mpeg_encode/headers/RCS/search.h,v 1.6 1995/08/15 23:43:36 smoot Exp $
+ *  $Log: search.h,v $
+ *  Revision 1.6  1995/08/15 23:43:36  smoot
+ *  *** empty log message ***
+ *
+ *  Revision 1.5  1995/01/19 23:55:20  eyhung
+ *  Changed copyrights
+ *
+ * Revision 1.4  1994/12/07  00:42:01  smoot
+ * Added seperate P and B search ranges
+ *
+ * Revision 1.3  1994/11/12  02:12:58  keving
+ * nothing
+ *
+ * Revision 1.2  1993/07/22  22:24:23  keving
+ * nothing
+ *
+ * Revision 1.1  1993/07/09  00:17:23  keving
+ * nothing
+ *
+ */
+
+
+
diff --git a/converter/ppm/ppmtompeg/headers/mpeg.h b/converter/ppm/ppmtompeg/headers/mpeg.h
new file mode 100644
index 00000000..d739dede
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/mpeg.h
@@ -0,0 +1,116 @@
+/*===========================================================================*
+ * mpeg.h								     *
+ *									     *
+ *	no comment							     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include <time.h>
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "ansi.h"
+#include "mtypes.h"
+#include "frame.h"
+
+struct inputSource;
+
+/*===============================*
+ * EXTERNAL PROCEDURE prototypes *
+ *===============================*/
+
+enum frameContext {CONTEXT_WHOLESTREAM, CONTEXT_GOP, CONTEXT_JUSTFRAMES};
+
+void
+GenMPEGStream(struct inputSource * const inputSourceP,
+              enum frameContext    const context, 
+              unsigned int         const frameStart, 
+              unsigned int         const frameEnd, 
+              int32                const qtable[], 
+              int32                const niqtable[], 
+              bool                 const childProcess,
+              FILE *               const ofp, 
+              const char *         const outputFileName,
+              bool                 const wantVbvUnderflowWarning,
+              bool                 const wantVbvOverflowWarning,
+              unsigned int *       const inputFrameBitsP,
+              unsigned int *       const totalBitsP);
+
+void
+PrintStartStats(time_t               const startTime, 
+                bool                 const specificFrames,
+                unsigned int         const firstFrame, 
+                unsigned int         const lastFrame,
+                struct inputSource * const inputSourceP);
+
+void
+PrintEndStats(time_t       const startTime,
+              time_t       const endTime,
+              unsigned int const inputFrameBits, 
+              unsigned int const totalBits);
+
+void
+ComputeGOPFrames(int            const whichGOP, 
+                 unsigned int * const firstFrameP, 
+                 unsigned int * const lastFrameP, 
+                 unsigned int   const numFrames);
+
+extern void	IncrementTCTime _ANSI_ARGS_((void));
+void SetReferenceFrameType(const char * const type);
+
+boolean
+NonLocalRefFrame(int     const id);
+
+void
+ReadDecodedRefFrame(MpegFrame *  const frameP, 
+                    unsigned int const frameNumber);
+
+extern void	WriteDecodedFrame _ANSI_ARGS_((MpegFrame *frame));
+extern void	SetBitRateFileName _ANSI_ARGS_((char *fileName));
+extern void	SetFrameRate _ANSI_ARGS_((void));
+
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+extern MpegFrame *frameMemory[3];
+extern int32	  tc_hrs, tc_min, tc_sec, tc_pict, tc_extra;
+extern int	  totalFramesSent;
+extern int	  gopSize;
+extern char	 *framePattern;
+extern int	  framePatternLen;
+extern int32 qtable[];
+extern int32 niqtable[];
+extern int32 *customQtable;
+extern int32 *customNIQtable;
+extern int  aspectRatio;
+extern int  frameRate;
+extern int     frameRateRounded;
+extern boolean    frameRateInteger;
+
diff --git a/converter/ppm/ppmtompeg/headers/mproto.h b/converter/ppm/ppmtompeg/headers/mproto.h
new file mode 100644
index 00000000..c3b0f4b3
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/mproto.h
@@ -0,0 +1,132 @@
+/*===========================================================================*
+ * mproto.h								     *
+ *									     *
+ *	basically a lot of miscellaneous prototypes			     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/picasso/project/mm/mpeg/mpeg_dist/mpeg_encode/headers/RCS/mproto.h,v 1.12 1995/03/29 20:14:29 smoot Exp $
+ *  $Log: mproto.h,v $
+ * Revision 1.12  1995/03/29  20:14:29  smoot
+ * deleted unneeded dct prototype
+ *
+ * Revision 1.11  1995/01/19  23:55:02  eyhung
+ * Changed copyrights
+ *
+ * Revision 1.10  1995/01/16  06:20:10  eyhung
+ * Changed ReadYUV to ReadEYUV
+ *
+ * Revision 1.9  1993/07/22  22:24:23  keving
+ * nothing
+ *
+ * Revision 1.8  1993/07/09  00:17:23  keving
+ * nothing
+ *
+ * Revision 1.7  1993/06/03  21:08:53  keving
+ * nothing
+ *
+ * Revision 1.6  1993/02/24  19:13:33  keving
+ * nothing
+ *
+ * Revision 1.5  1993/02/17  23:18:20  dwallach
+ * checkin prior to keving's joining the project
+ *
+ * Revision 1.4  1993/01/18  10:20:02  dwallach
+ * *** empty log message ***
+ *
+ * Revision 1.3  1993/01/18  10:17:29  dwallach
+ * RCS headers installed, code indented uniformly
+ *
+ * Revision 1.3  1993/01/18  10:17:29  dwallach
+ * RCS headers installed, code indented uniformly
+ *
+ */
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "general.h"
+#include "ansi.h"
+#include "bitio.h"
+
+
+#define DCTSIZE2    DCTSIZE*DCTSIZE
+typedef short DCTELEM;
+typedef DCTELEM DCTBLOCK[DCTSIZE2];
+
+
+
+/*===============================*
+ * EXTERNAL PROCEDURE prototypes *
+ *===============================*/
+
+/*  
+ *  from mbasic.c:
+ */
+void mp_reset _ANSI_ARGS_((void));
+void mp_free _ANSI_ARGS_((MpegFrame *mf));
+MpegFrame *mp_new _ANSI_ARGS_((int fnumber, char type, MpegFrame *oldFrame));
+void mp_ycc_calc _ANSI_ARGS_((MpegFrame *mf));
+void mp_dct_blocks _ANSI_ARGS_((MpegFrame *mf));
+void	AllocDecoded _ANSI_ARGS_((MpegFrame *frame));
+
+/*  
+ *  from moutput.c:
+ */
+boolean mp_quant_zig_block _ANSI_ARGS_((Block in, FlatBlock out, int qscale, int iblock));
+void	UnQuantZig _ANSI_ARGS_((FlatBlock in, Block out, int qscale, boolean iblock));
+void mp_rle_huff_block _ANSI_ARGS_((FlatBlock in, BitBucket *out));
+void mp_rle_huff_pblock _ANSI_ARGS_((FlatBlock in, BitBucket *out));
+void mp_create_blocks _ANSI_ARGS_((MpegFrame *mf));
+
+
+
+
+void	ReadEYUV _ANSI_ARGS_((MpegFrame * mf, FILE *fpointer, int width,
+			    int height));
+boolean	ReadPPM _ANSI_ARGS_((MpegFrame *mf, FILE *fpointer));
+void PPMtoYCC _ANSI_ARGS_((MpegFrame * mf));
+
+void	MotionSearchPreComputation _ANSI_ARGS_((MpegFrame *frame));
+boolean	PMotionSearch _ANSI_ARGS_((LumBlock currentBlock, MpegFrame *prev,
+				   int by, int bx, int *motionY, int *motionX));
+void	ComputeHalfPixelData _ANSI_ARGS_((MpegFrame *frame));
+void mp_validate_size _ANSI_ARGS_((int *x, int *y));
+void AllocYCC _ANSI_ARGS_((MpegFrame * mf));
+
+
+/* jrevdct.c */
+void init_pre_idct _ANSI_ARGS_((void ));
+void j_rev_dct_sparse _ANSI_ARGS_((DCTBLOCK data , int pos ));
+void j_rev_dct _ANSI_ARGS_((DCTBLOCK data ));
+void j_rev_dct_sparse _ANSI_ARGS_((DCTBLOCK data , int pos ));
+void j_rev_dct _ANSI_ARGS_((DCTBLOCK data ));
+
+/* block.c */
+void	BlockToData _ANSI_ARGS_((uint8 **data, Block block, int by, int bx));
+void	AddMotionBlock _ANSI_ARGS_((Block block, uint8 **prev, int by, int bx,
+		       int my, int mx));
diff --git a/converter/ppm/ppmtompeg/headers/mtypes.h b/converter/ppm/ppmtompeg/headers/mtypes.h
new file mode 100644
index 00000000..0db5a330
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/mtypes.h
@@ -0,0 +1,109 @@
+/*===========================================================================*
+ * mtypes.h
+ *
+ *	MPEG data types
+ *
+ *===========================================================================*/
+
+/* Copyright information is at end of file */
+
+#ifndef MTYPES_INCLUDED
+#define MTYPES_INCLUDED
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "general.h"
+#include "dct.h"
+
+
+/*===========*
+ * CONSTANTS *
+ *===========*/
+
+#define TYPE_BOGUS	0   /* for the header of the circular list */
+#define TYPE_VIRGIN	1
+
+#define STATUS_EMPTY	0
+#define STATUS_LOADED	1
+#define STATUS_WRITTEN	2
+
+
+typedef struct vector {
+    int y;
+    int x;
+} vector;
+
+typedef struct motion {
+    vector fwd;
+    vector bwd;
+} motion;
+
+/*==================*
+ * TYPE DEFINITIONS *
+ *==================*/
+
+/*  
+ *  your basic Block type
+ */
+typedef int16 Block[DCTSIZE][DCTSIZE];
+typedef int16 FlatBlock[DCTSIZE_SQ];
+typedef	struct {
+    int32 l[2*DCTSIZE][2*DCTSIZE];
+} LumBlock;
+typedef	int32 ChromBlock[DCTSIZE][DCTSIZE];
+
+/*========*
+ * MACROS *
+ *========*/
+
+#ifdef ABS
+#undef ABS
+#endif
+
+#define ABS(x) (((x)<0)?-(x):(x))
+
+#ifdef HEINOUS_DEBUG_MODE
+#define DBG_PRINT(x) {printf x; fflush(stdout);}
+#else
+#define DBG_PRINT(x)
+#endif
+
+#define ERRCHK(bool, str) {if(!(bool)) {perror(str); exit(1);}}
+
+/* For Specifics */
+typedef struct detalmv_def {
+  int typ,fx,fy,bx,by;
+} BlockMV;
+#define TYP_SKIP 0
+#define TYP_FORW 1
+#define TYP_BACK 2
+#define TYP_BOTH 3
+
+
+#endif /* MTYPES_INCLUDED */
+
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
diff --git a/converter/ppm/ppmtompeg/headers/opts.h b/converter/ppm/ppmtompeg/headers/opts.h
new file mode 100644
index 00000000..5901a677
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/opts.h
@@ -0,0 +1,125 @@
+/*
+ * opts.h - set optional parameters
+ */
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/picasso/project/mpeg/mpeg_dist/mpeg_encode/headers/RCS/opts.h,v 1.3 1995/08/15 23:43:43 smoot Exp $
+ *  $Log: opts.h,v $
+ *  Revision 1.3  1995/08/15 23:43:43  smoot
+ *  *** empty log message ***
+ *
+ * Revision 1.2  1995/05/02  22:00:51  smoot
+ * added TUNEing stuff
+ *
+ * Revision 1.1  1995/04/14  23:12:53  smoot
+ * Initial revision
+ *
+ */
+
+#include "general.h"
+#include "ansi.h"
+#include "mtypes.h"
+
+/*
+ TUNE b [limit] lower limit on how different a block must be to be DCT coded
+ TUNE c [file [color-diff]] Collect statistics on Quantization
+ TUNE d [RateScale DistortionScale] Do a DCT in the P search, not just DIFF
+ TUNE k [breakpt end [slope]] Squash small lum values
+ TUNE l Figure out Laplacian distrib and use them to dequantize and do snr calc
+ TUNE n Dont consider DC differenece in DCT searches
+ TUNE q Do MSE for distortion measure, not MAD
+ TUNE s [Max] | [LumMax ChromMax] Squash small differences in successive frames
+ TUNE u disallow skip blocks in B frames
+ TUNE w filename [c]  Write I block distortion numbers to file [with bit-rates]
+ TUNE z Zaps Intra blocks in P/B frames.
+
+ [ Note k and s make -snr numbers a lie, by playing with input ]
+ [ Note d n and q are contradictory (can only use one)         ]
+ [ Note c will not work on parallel encodings                  ]
+*/
+
+extern boolean tuneingOn;
+
+/* Smash to no-change a motion block DCT with MAD less than: */
+/* DETAL b value               */
+extern int block_bound;
+
+/* Collect info on quantization */
+extern boolean collect_quant;
+extern int collect_quant_detailed;
+extern FILE   *collect_quant_fp;
+
+/* Nuke dim areas */
+extern int kill_dim, kill_dim_break, kill_dim_end;
+extern float kill_dim_slope;
+
+
+/* Stuff to control MV search comparisons */
+#define DEFAULT_SEARCH 0
+#define LOCAL_DCT  1 /* Do DCT in search (SLOW!!!!) */
+#define NO_DC_SEARCH  2  /* Dont consider DC component in motion searches */
+#define DO_Mean_Squared_Distortion  3 /* Do Squared distortion, not ABS */
+
+/* Parameters for special searches */
+/* LOCAL_DCT */
+extern float LocalDCTRateScale, LocalDCTDistortScale;
+
+/* Search Type Variable */
+extern int SearchCompareMode;
+
+/* squash small differences */
+extern boolean squash_small_differences;
+extern int SquashMaxLum, SquashMaxChr;
+
+/* Disallows Intra blocks in P/B code */
+extern boolean IntraPBAllowed;
+
+/* Write out distortion numbers */
+extern boolean WriteDistortionNumbers;
+extern int collect_distortion_detailed;
+extern FILE *distortion_fp;
+extern FILE *fp_table_rate[31], *fp_table_dist[31];
+
+/* Laplacian Distrib */
+extern boolean DoLaplace;
+extern double **L1, **L2, **Lambdas;
+extern int LaplaceNum, LaplaceCnum;
+
+/* Turn on/off skipping in B frames */
+extern boolean BSkipBlocks;
+
+/* Procedures Prototypes */
+int	GetIQScale _ANSI_ARGS_((void));
+int	GetPQScale _ANSI_ARGS_((void));
+int	GetBQScale _ANSI_ARGS_((void));
+void	Tune_Init _ANSI_ARGS_((void));
+int     CalcRLEHuffLength _ANSI_ARGS_((FlatBlock in));
+void    ParseTuneParam(const char * const charPtr);
+int     mse _ANSI_ARGS_((Block blk1, Block blk2));
+void    Mpost_UnQuantZigBlockLaplace _ANSI_ARGS_((FlatBlock in, Block out, int qscale, boolean iblock));
+extern void CalcLambdas(void);
+
+
+
+
diff --git a/converter/ppm/ppmtompeg/headers/parallel.h b/converter/ppm/ppmtompeg/headers/parallel.h
new file mode 100644
index 00000000..e18d3f46
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/parallel.h
@@ -0,0 +1,135 @@
+/*===========================================================================*
+ * parallel.h          
+ *                     
+ *  parallel encoding  
+ *                     
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "ansi.h"
+#include "bitio.h"
+#include "frame.h"
+
+
+struct inputSource;
+
+/*===============================*
+ * EXTERNAL PROCEDURE prototypes *
+ *===============================*/
+
+void
+MasterServer(struct inputSource * const inputSourceP,
+             const char *         const paramFileName, 
+             const char *         const outputFileName);
+
+void
+NotifyMasterDone(const char * const hostName, 
+                 int          const portNum, 
+                 int          const machineNumber, 
+                 unsigned int const seconds, 
+                 boolean *    const moreWorkToDoP,
+                 int *        const frameStartP,
+                 int *        const frameEndP);
+
+void
+IoServer(struct inputSource * const inputSourceP,
+         const char *         const parallelHostName, 
+         int                  const portNum);
+
+void
+CombineServer(int          const numInputFiles, 
+              const char * const masterHostName, 
+              int          const masterPortNum,
+              const char*  const outputFileName);
+
+void
+DecodeServer(int          const numInputFiles, 
+             const char * const decodeFileName, 
+             const char * const parallelHostName, 
+             int          const portNum);
+
+void
+WaitForOutputFile(int number);
+
+void
+GetRemoteFrame(MpegFrame * const frameP,
+               int         const frameNumber);
+
+void
+SendRemoteFrame(int         const frameNumber,
+                BitBucket * const bbP);
+
+void
+NoteFrameDone(int frameStart, int frameEnd);
+
+void
+SetIOConvert(boolean separate);
+
+void
+SetRemoteShell(const char * const shell);
+
+void 
+NotifyDecodeServerReady(int const id);
+
+void 
+WaitForDecodedFrame(int id);
+
+void 
+SendDecodedFrame(MpegFrame * const frameP);
+
+void 
+GetRemoteDecodedRefFrame(MpegFrame * const frameP,
+                         int         const frameNumber);
+
+void 
+SetParallelPerfect(boolean val);
+
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+extern int parallelTestFrames;
+extern int parallelTimeChunks;
+
+extern const char *IOhostName;
+extern int ioPortNumber;
+extern int decodePortNumber;
+
+extern boolean  ioServer;
+extern boolean  niceProcesses;
+extern boolean  forceIalign;
+extern int    machineNumber;
+extern boolean remoteIO;
+extern boolean  separateConversion;
+
+
+
+
+
+
+
diff --git a/converter/ppm/ppmtompeg/headers/param.h b/converter/ppm/ppmtompeg/headers/param.h
new file mode 100644
index 00000000..31be61ee
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/param.h
@@ -0,0 +1,87 @@
+/*===========================================================================*
+ * param.h								     *
+ *									     *
+ *	reading the parameter file					     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+#include "pm_c_util.h"
+#include "ansi.h"
+#include "input.h"
+
+
+/*===========*
+ * CONSTANTS *
+ *===========*/
+
+#define MAX_MACHINES	    256
+#ifndef MAXPATHLEN
+#define MAXPATHLEN  1024
+#endif
+
+#define	ENCODE_FRAMES	0
+#define COMBINE_GOPS	1
+#define COMBINE_FRAMES	2
+
+
+struct params {
+    struct inputSource * inputSourceP;
+    bool warnUnderflow;
+    bool warnOverflow;
+};
+
+
+void
+ReadParamFile(const char *    const fileName, 
+              int             const function,
+              struct params * const paramP);
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+/* All this stuff ought to be in a struct param instead */
+
+extern char	outputFileName[256];
+extern int	whichGOP;
+extern int numMachines;
+extern char	machineName[MAX_MACHINES][256];
+extern char	userName[MAX_MACHINES][256];
+extern char	executable[MAX_MACHINES][1024];
+extern char	remoteParamFile[MAX_MACHINES][1024];
+extern boolean	remote[MAX_MACHINES];
+extern char	currentPath[MAXPATHLEN];
+extern char	currentFramePath[MAXPATHLEN];
+extern char	currentGOPPath[MAXPATHLEN];
+extern char inputConversion[1024];
+extern char yuvConversion[256];
+extern int  yuvWidth, yuvHeight;
+extern int  realWidth, realHeight;
+extern char ioConversion[1024];
+extern char slaveConversion[1024];
+extern FILE *bitRateFile;
+extern boolean showBitRatePerFrame;
+extern boolean computeMVHist;
+extern const double VidRateNum[9];
+extern boolean keepTempFiles;
diff --git a/converter/ppm/ppmtompeg/headers/postdct.h b/converter/ppm/ppmtompeg/headers/postdct.h
new file mode 100644
index 00000000..3f3b51fe
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/postdct.h
@@ -0,0 +1,40 @@
+/*===========================================================================*
+ * postdct.h								     *
+ *									     *
+ *	MPEG post-DCT processing					     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+#include "bitio.h"
+
+
+int     Mpost_QuantZigBlock (Block in, FlatBlock out, int qscale, int iblock);
+void	Mpost_UnQuantZigBlock (FlatBlock in, Block out,
+                               int qscale, boolean iblock);
+void	Mpost_RLEHuffIBlock (FlatBlock in, BitBucket *out);
+void	Mpost_RLEHuffPBlock (FlatBlock in, BitBucket *out);
+
+#define MPOST_ZERO 0
+#define MPOST_NON_ZERO 1
+#define MPOST_OVERFLOW (-1)
diff --git a/converter/ppm/ppmtompeg/headers/prototypes.h b/converter/ppm/ppmtompeg/headers/prototypes.h
new file mode 100644
index 00000000..d729aafa
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/prototypes.h
@@ -0,0 +1,78 @@
+/*===========================================================================*
+ * prototypes.h                                  *
+ *                                       *
+ *  miscellaneous prototypes                         *
+ *                                       *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "general.h"
+#include "ansi.h"
+#include "frame.h"
+
+
+/*===============================*
+ * EXTERNAL PROCEDURE prototypes *
+ *===============================*/
+
+int GetBQScale _ANSI_ARGS_((void));
+int GetPQScale _ANSI_ARGS_((void));
+void    ResetBFrameStats _ANSI_ARGS_((void));
+void    ResetPFrameStats _ANSI_ARGS_((void));
+void SetSearchRange (int const pixelsP,
+                     int const pixelsB);
+void    ResetIFrameStats _ANSI_ARGS_((void));
+void
+SetPixelSearch(const char * const searchType);
+void    SetIQScale _ANSI_ARGS_((int qI));
+void    SetPQScale _ANSI_ARGS_((int qP));
+void    SetBQScale _ANSI_ARGS_((int qB));
+float   EstimateSecondsPerIFrame _ANSI_ARGS_((void));
+float   EstimateSecondsPerPFrame _ANSI_ARGS_((void));
+float   EstimateSecondsPerBFrame _ANSI_ARGS_((void));
+void    SetGOPSize _ANSI_ARGS_((int size));
+void
+SetStatFileName(const char * const fileName);
+void    SetSlicesPerFrame _ANSI_ARGS_((int number));
+void    SetBlocksPerSlice _ANSI_ARGS_((void));
+
+
+void DCTFrame _ANSI_ARGS_((MpegFrame * mf));
+
+void PPMtoYCC _ANSI_ARGS_((MpegFrame * mf));
+
+void    MotionSearchPreComputation _ANSI_ARGS_((MpegFrame *frame));
+
+void    ComputeHalfPixelData _ANSI_ARGS_((MpegFrame *frame));
+void mp_validate_size _ANSI_ARGS_((int *x, int *y));
+
+extern void SetFCode _ANSI_ARGS_((void));
+
+
+/* psearch.c */
+void    ShowPMVHistogram _ANSI_ARGS_((FILE *fpointer));
+void    ShowBBMVHistogram _ANSI_ARGS_((FILE *fpointer));
+void    ShowBFMVHistogram _ANSI_ARGS_((FILE *fpointer));
diff --git a/converter/ppm/ppmtompeg/headers/psocket.h b/converter/ppm/ppmtompeg/headers/psocket.h
new file mode 100644
index 00000000..214f5dce
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/psocket.h
@@ -0,0 +1,41 @@
+#ifndef PSOCKET_H_INCLUDED
+#define PSOCKET_H_INCLUDED
+
+#include <netdb.h>
+
+void
+ReadInt(int   const socketFd,
+        int * const valueP);
+
+void
+ReadBytes(int             const fd,
+          unsigned char * const buf,
+          unsigned int    const nbyte);
+
+void
+WriteInt(int const socketFd,
+         int const value);
+
+void
+WriteBytes(int             const fd,
+           unsigned char * const buf,
+           unsigned int    const nbyte);
+
+void
+ConnectToSocket(const char *      const machineName, 
+                int               const portNum, 
+                struct hostent ** const hostEnt,
+                int *             const socketFdP,
+                const char **     const errorP);
+
+void
+CreateListeningSocket(int *         const socketP,
+                      int *         const portNumP,
+                      const char ** const errorP);
+
+void
+AcceptConnection(int           const listenSocketFd,
+                 int *         const connectSocketFdP,
+                 const char ** const errorP);
+
+#endif
diff --git a/converter/ppm/ppmtompeg/headers/rate.h b/converter/ppm/ppmtompeg/headers/rate.h
new file mode 100644
index 00000000..df5ca1cc
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/rate.h
@@ -0,0 +1,204 @@
+/*===========================================================================*
+ * rate.h								     *
+ *									     *
+ *	Procedures concerned with rate control
+ *									     *
+ * EXPORTED PROCEDURES:							     *
+ *  getRateMode()
+ *  setBitRate()
+ *  getBitRate()
+ *  setBufferSize()
+ *  getBufferSize()
+ *	initRateControl()
+ *	targetRateControl()
+ * 	updateRateControl()
+ *	MB_RateOut()
+ *									     *
+ *===========================================================================*/
+
+/* 	COPYRIGHT INFO HERE	*/
+
+#define VARIABLE_RATE 0
+#define FIXED_RATE 1
+
+
+/*==================*
+ * Exported VARIABLES *
+ *==================*/
+
+
+extern int rc_bitsThisMB;
+extern int rc_numBlocks;
+extern int rc_totalQuant;
+extern int rc_quantOverride;
+
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+
+/*===========================================================================*
+ *
+ * initRateControl
+ *
+ *	initialize the allocation parameters.
+ *===========================================================================*/
+int
+initRateControl(bool const wantUnderflowWarning,
+                bool const wantOverflowWarning);
+
+/*===========================================================================*
+ *
+ * targetRateControl
+ *
+ *      Determine the target allocation for given picture type.
+ *
+ * RETURNS:     target size in bits
+ *===========================================================================*/
+void
+targetRateControl(MpegFrame * const frameP);
+
+
+/*===========================================================================*
+ *
+ * MB_RateOut
+ *
+ *      Prints out sampling of MB rate control data.  Every "nth" block
+ *	stats are printed, with "n" controled by global RC_MB_SAMPLE_RATE
+ *
+ * RETURNS:     nothing
+ *===========================================================================*/
+extern void MB_RateOut _ANSI_ARGS_((int type));
+
+
+/*===========================================================================*
+ *
+ * updateRateControl
+ *
+ *      Update the statistics kept, after end of frame
+ *
+ * RETURNS:     nothing
+ *
+ * SIDE EFFECTS:   many global variables
+ *===========================================================================*/
+void
+updateRateControl(int const type);
+
+
+/*===========================================================================*
+ *
+ * needQScaleChange(current Q scale, 4 luminance blocks)
+ *
+ *
+ * RETURNS:     new Qscale
+ *===========================================================================*/
+extern int needQScaleChange _ANSI_ARGS_((int oldQScale,  Block blk0, Block blk1, Block blk2, Block blk3));
+
+/*===========================================================================*
+ *
+ * incNumBlocks()
+ *
+ *
+ * RETURNS:   nothing
+ *===========================================================================*/
+extern void incNumBlocks _ANSI_ARGS_((int num));
+
+
+/*===========================================================================*
+ *
+ * incMacroBlockBits()
+ *
+ *  Increments the number of Macro Block bits and the total of Frame
+ *  bits by the number passed.
+ *
+ * RETURNS:   nothing
+ *===========================================================================*/
+extern void incMacroBlockBits _ANSI_ARGS_((int num));
+
+
+/*===========================================================================*
+ *
+ * SetRateControl ()
+ *
+ *      Checks the string parsed from the parameter file.  Verifies
+ *  number and sets global values.
+ *
+ * RETURNS:     nothing
+ *===========================================================================*/
+extern void SetRateControl _ANSI_ARGS_((char *charPtr));
+
+
+/*===========================================================================*
+ *
+ * setBufferSize ()
+ *
+ *      Checks the string parsed from the parameter file.  Verifies
+ *  number and sets global values.
+ *
+ * RETURNS:     nothing
+ *===========================================================================*/
+extern void 
+setBufferSize(const char * const charPtr);
+
+
+/*===========================================================================*
+ *
+ * getBufferSize ()
+ *
+ *      returns the buffer size read from the parameter file.  Size is
+ *  in bits- not in units of 16k as written to the sequence header.
+ *
+ * RETURNS:     int (or -1 if invalid)
+ *===========================================================================*/
+extern int getBufferSize _ANSI_ARGS_((void));
+
+
+/*===========================================================================*
+ *
+ * setBitRate ()
+ *
+ *      Checks the string parsed from the parameter file.  Verifies
+ *  number and sets global values.
+ *
+ * RETURNS:     nothing
+ *
+ * SIDE EFFECTS:   global variables
+ *===========================================================================*/
+extern void 
+setBitRate(const char * const charPtr);
+
+
+/*===========================================================================*
+ *
+ * getBitRate ()
+ *
+ *      Returns the bit rate read from the parameter file.  This is the
+ *  real rate in bits per second, not in 400 bit units as is written to
+ *  the sequence header.
+ *
+ * RETURNS:     int (-1 if Variable mode operation)
+ *===========================================================================*/
+extern int getBitRate _ANSI_ARGS_((void));
+
+
+/*===========================================================================*
+ *
+ * getRateMode ()
+ *
+ *      Returns the rate mode- interpreted waa either Fixed or Variable
+ *
+ * RETURNS:     integer
+ *===========================================================================*/
+extern int getRateMode _ANSI_ARGS_((void));
+
+
+/*===========================================================================*
+ *
+ * incQuantOverride()
+ *
+ *  counter of override of quantization
+ *
+ * RETURNS:   nothing
+ *===========================================================================*/
+extern void incQuantOverride  _ANSI_ARGS_((int num));
+
diff --git a/converter/ppm/ppmtompeg/headers/readframe.h b/converter/ppm/ppmtompeg/headers/readframe.h
new file mode 100644
index 00000000..3a6876b1
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/readframe.h
@@ -0,0 +1,69 @@
+/*===========================================================================*
+ * readframe.h								     *
+ *									     *
+ *	stuff dealing with reading frames				     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+#include "pm_c_util.h"
+
+/*===========*
+ * CONSTANTS *
+ *===========*/
+
+#define	PPM_FILE_TYPE	    0
+#define YUV_FILE_TYPE	    2
+#define ANY_FILE_TYPE	    3
+#define BASE_FILE_TYPE	    4
+#define PNM_FILE_TYPE	    5
+#define SUB4_FILE_TYPE	    6
+#define JPEG_FILE_TYPE	    7
+#define JMOVIE_FILE_TYPE    8
+#define Y_FILE_TYPE	    9
+
+
+struct inputSource;
+
+void
+ReadFrameFile(MpegFrame *  const frameP,
+              FILE *       const ifP,
+              const char * const conversion,
+              bool *       const eofP);
+
+void
+ReadFrame(MpegFrame *          const frameP, 
+          struct inputSource * const inputSourceP,
+          unsigned int         const frameNumber,
+          const char *         const conversion,
+          bool *               const endOfStreamP);
+
+FILE *
+ReadIOConvert(struct inputSource * const inputSourceP,
+              unsigned int         const frameNumber);
+
+extern void	SetFileType(const char * const conversion);
+extern void	SetFileFormat(const char * const format);
+extern void	SetResize(bool const set);
+
+extern int	baseFormat;
diff --git a/converter/ppm/ppmtompeg/headers/rgbtoycc.h b/converter/ppm/ppmtompeg/headers/rgbtoycc.h
new file mode 100644
index 00000000..52159963
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/rgbtoycc.h
@@ -0,0 +1,39 @@
+/*===========================================================================*
+ * rgbtoycc.h								     *
+ *									     *
+ *	Procedures to convert from RGB space to YUV space		     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+
+
+#include "frame.h"
+#include "pnm.h"
+
+void
+PNMtoYUV(MpegFrame *  const frame,
+         xel **       const xels,
+         unsigned int const cols,
+         unsigned int const rows,
+         xelval       const maxval);
diff --git a/converter/ppm/ppmtompeg/headers/specifics.h b/converter/ppm/ppmtompeg/headers/specifics.h
new file mode 100644
index 00000000..7bcf4ace
--- /dev/null
+++ b/converter/ppm/ppmtompeg/headers/specifics.h
@@ -0,0 +1,36 @@
+#include "ansi.h"
+
+
+/*===========*
+ * TYPES     *
+ *===========*/
+
+typedef struct bs_def {
+  int num;
+  boolean relative;
+  char qscale;
+  BlockMV *mv;  /* defined in mtypes.h */
+  struct bs_def *next;
+} Block_Specifics;
+
+typedef struct detail_def {
+  int num;
+  char qscale;
+  struct detail_def *next;
+}  Slice_Specifics;
+
+typedef struct fsl_def {
+  int framenum; 
+  int frametype;
+  char qscale;
+  Slice_Specifics *slc;
+  Block_Specifics *bs;
+  struct fsl_def *next;
+} FrameSpecList;
+
+
+void	Specifics_Init _ANSI_ARGS_((void));
+int     SpecLookup _ANSI_ARGS_((int fn, int typ, int num, 
+				BlockMV **info, int start_qs));
+int SpecTypeLookup _ANSI_ARGS_((int fn));
+
diff --git a/converter/ppm/ppmtompeg/huff.c b/converter/ppm/ppmtompeg/huff.c
new file mode 100644
index 00000000..821daca1
--- /dev/null
+++ b/converter/ppm/ppmtompeg/huff.c
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/charlie-brown/project/mm/mpeg/mpeg_dist/mpeg_encode/RCS/huff.c,v 1.6 1995/01/19 23:07:39 eyhung Exp $
+ */
+
+/*  
+ *  THIS FILE IS MACHINE GENERATED!  DO NOT EDIT!
+ */
+#include "mtypes.h"
+#include "huff.h"
+
+int huff_maxlevel[32] = { 41, 19, 6, 5, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 };
+
+uint32 huff_table0[41] = { 0x0, 0x6, 0x8, 0xa, 0xc, 0x4c, 0x42, 0x14, 0x3a, 0x30, 0x26, 0x20, 0x34, 0x32, 0x30, 0x2e, 0x3e, 0x3c, 0x3a, 0x38, 0x36, 0x34, 0x32, 0x30, 0x2e, 0x2c, 0x2a, 0x28, 0x26, 0x24, 0x22, 0x20, 0x30, 0x2e, 0x2c, 0x2a, 0x28, 0x26, 0x24, 0x22, 0x20 };
+int huff_bits0[41] = { 0, 3, 5, 6, 8, 9, 9, 11, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16 };
+
+uint32 huff_table1[19] = { 0x0, 0x6, 0xc, 0x4a, 0x18, 0x36, 0x2c, 0x2a, 0x3e, 0x3c, 0x3a, 0x38, 0x36, 0x34, 0x32, 0x26, 0x24, 0x22, 0x20 };
+int huff_bits1[19] = { 0, 4, 7, 9, 11, 13, 14, 14, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17 };
+
+uint32 huff_table2[6] = { 0x0, 0xa, 0x8, 0x16, 0x28, 0x28 };
+int huff_bits2[6] = { 0, 5, 8, 11, 13, 14 };
+
+uint32 huff_table3[5] = { 0x0, 0xe, 0x48, 0x38, 0x26 };
+int huff_bits3[5] = { 0, 6, 9, 13, 14 };
+
+uint32 huff_table4[4] = { 0x0, 0xc, 0x1e, 0x24 };
+int huff_bits4[4] = { 0, 6, 11, 13 };
+
+uint32 huff_table5[4] = { 0x0, 0xe, 0x12, 0x24 };
+int huff_bits5[4] = { 0, 7, 11, 14 };
+
+uint32 huff_table6[4] = { 0x0, 0xa, 0x3c, 0x28 };
+int huff_bits6[4] = { 0, 7, 13, 17 };
+
+uint32 huff_table7[3] = { 0x0, 0x8, 0x2a };
+int huff_bits7[3] = { 0, 7, 13 };
+
+uint32 huff_table8[3] = { 0x0, 0xe, 0x22 };
+int huff_bits8[3] = { 0, 8, 13 };
+
+uint32 huff_table9[3] = { 0x0, 0xa, 0x22 };
+int huff_bits9[3] = { 0, 8, 14 };
+
+uint32 huff_table10[3] = { 0x0, 0x4e, 0x20 };
+int huff_bits10[3] = { 0, 9, 14 };
+
+uint32 huff_table11[3] = { 0x0, 0x46, 0x34 };
+int huff_bits11[3] = { 0, 9, 17 };
+
+uint32 huff_table12[3] = { 0x0, 0x44, 0x32 };
+int huff_bits12[3] = { 0, 9, 17 };
+
+uint32 huff_table13[3] = { 0x0, 0x40, 0x30 };
+int huff_bits13[3] = { 0, 9, 17 };
+
+uint32 huff_table14[3] = { 0x0, 0x1c, 0x2e };
+int huff_bits14[3] = { 0, 11, 17 };
+
+uint32 huff_table15[3] = { 0x0, 0x1a, 0x2c };
+int huff_bits15[3] = { 0, 11, 17 };
+
+uint32 huff_table16[3] = { 0x0, 0x10, 0x2a };
+int huff_bits16[3] = { 0, 11, 17 };
+
+uint32 huff_table17[2] = { 0x0, 0x3e };
+int huff_bits17[2] = { 0, 13 };
+
+uint32 huff_table18[2] = { 0x0, 0x34 };
+int huff_bits18[2] = { 0, 13 };
+
+uint32 huff_table19[2] = { 0x0, 0x32 };
+int huff_bits19[2] = { 0, 13 };
+
+uint32 huff_table20[2] = { 0x0, 0x2e };
+int huff_bits20[2] = { 0, 13 };
+
+uint32 huff_table21[2] = { 0x0, 0x2c };
+int huff_bits21[2] = { 0, 13 };
+
+uint32 huff_table22[2] = { 0x0, 0x3e };
+int huff_bits22[2] = { 0, 14 };
+
+uint32 huff_table23[2] = { 0x0, 0x3c };
+int huff_bits23[2] = { 0, 14 };
+
+uint32 huff_table24[2] = { 0x0, 0x3a };
+int huff_bits24[2] = { 0, 14 };
+
+uint32 huff_table25[2] = { 0x0, 0x38 };
+int huff_bits25[2] = { 0, 14 };
+
+uint32 huff_table26[2] = { 0x0, 0x36 };
+int huff_bits26[2] = { 0, 14 };
+
+uint32 huff_table27[2] = { 0x0, 0x3e };
+int huff_bits27[2] = { 0, 17 };
+
+uint32 huff_table28[2] = { 0x0, 0x3c };
+int huff_bits28[2] = { 0, 17 };
+
+uint32 huff_table29[2] = { 0x0, 0x3a };
+int huff_bits29[2] = { 0, 17 };
+
+uint32 huff_table30[2] = { 0x0, 0x38 };
+int huff_bits30[2] = { 0, 17 };
+
+uint32 huff_table31[2] = { 0x0, 0x36 };
+int huff_bits31[2] = { 0, 17 };
+
+uint32 *huff_table[32] = { huff_table0, huff_table1, huff_table2, huff_table3, huff_table4, huff_table5, huff_table6, huff_table7, huff_table8, huff_table9, huff_table10, huff_table11, huff_table12, huff_table13, huff_table14, huff_table15, huff_table16, huff_table17, huff_table18, huff_table19, huff_table20, huff_table21, huff_table22, huff_table23, huff_table24, huff_table25, huff_table26, huff_table27, huff_table28, huff_table29, huff_table30, huff_table31 };
+int *huff_bits[32] = { huff_bits0, huff_bits1, huff_bits2, huff_bits3, huff_bits4, huff_bits5, huff_bits6, huff_bits7, huff_bits8, huff_bits9, huff_bits10, huff_bits11, huff_bits12, huff_bits13, huff_bits14, huff_bits15, huff_bits16, huff_bits17, huff_bits18, huff_bits19, huff_bits20, huff_bits21, huff_bits22, huff_bits23, huff_bits24, huff_bits25, huff_bits26, huff_bits27, huff_bits28, huff_bits29, huff_bits30, huff_bits31 };
diff --git a/converter/ppm/ppmtompeg/huff.h b/converter/ppm/ppmtompeg/huff.h
new file mode 100644
index 00000000..4d0b8840
--- /dev/null
+++ b/converter/ppm/ppmtompeg/huff.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/charlie-brown/project/mm/mpeg/mpeg_dist/mpeg_encode/RCS/huff.h,v 1.2 1995/01/19 23:08:28 eyhung Exp $
+ */
+
+/*  
+ *  THIS FILE IS MACHINE GENERATED!  DO NOT EDIT!
+ */
+#define HUFF_MAXRUN	32
+#define HUFF_MAXLEVEL	41
+
+extern int huff_maxlevel[];
+extern uint32 *huff_table[];
+extern int *huff_bits[];
diff --git a/converter/ppm/ppmtompeg/huff.table b/converter/ppm/ppmtompeg/huff.table
new file mode 100644
index 00000000..4f01d325
--- /dev/null
+++ b/converter/ppm/ppmtompeg/huff.table
@@ -0,0 +1,172 @@
+# 
+# Copyright (c) 1993 The Regents of the University of California.
+# All rights reserved.
+# 
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose, without fee, and without written agreement is
+# hereby granted, provided that the above copyright notice and the following
+# two paragraphs appear in all copies of this software.
+# 
+# IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+# OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+# CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# 
+# THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+# ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+# PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+# 
+
+#  
+#  $Header: /n/picasso/users/dwallach/vid2/mpeg_encode/RCS/huff.table,v 1.3 1993/02/17 23:21:58 dwallach Exp $
+#  $Log: huff.table,v $
+# Revision 1.3  1993/02/17  23:21:58  dwallach
+# checkin prior to keving's joining the project
+#
+# Revision 1.2  1993/01/18  10:20:02  dwallach
+# *** empty log message ***
+#
+#
+
+# this files is the raw Huffman encoding tables, from
+# the MPEG draft standard, P. D-41 - D-43 (Table D-2.15)
+
+# Format:
+# Run	Level	VLC Code
+
+# Run 0, Level 1 is special -- this table has the "NOT FIRST COEFF" entry
+# The "IF FIRST COEFF" would be: 0	1	1s
+
+0	1	11s
+
+0	2	0100 s
+0	3	0010 1s
+0	4	0000 110s
+0	5	0010 0110 s
+0	6	0010 0001 s
+0	7	0000 0010 10s
+0	8	0000 0001 1101 s
+0	9	0000 0001 1000 s
+0	10	0000 0001 0011 s
+0	11	0000 0001 0000 s
+0	12	0000 0000 1101 0s
+0	13	0000 0000 1100 1s
+0	14	0000 0000 1100 0s
+0	15	0000 0000 1011 1s
+0	16	0000 0000 0111 11s
+0	17	0000 0000 0111 10s
+0	18	0000 0000 0111 01s
+0	19	0000 0000 0111 00s
+0	20	0000 0000 0110 11s
+0	21	0000 0000 0110 10s
+0	22	0000 0000 0110 01s
+0	23	0000 0000 0110 00s
+0	24	0000 0000 0101 11s
+0	25	0000 0000 0101 10s
+0	26	0000 0000 0101 01s
+0	27	0000 0000 0101 00s
+0	28	0000 0000 0100 11s
+0	29	0000 0000 0100 10s
+0	30	0000 0000 0100 01s
+0	31	0000 0000 0100 00s
+0	32	0000 0000 0011 000s
+0	33	0000 0000 0010 111s
+0	34	0000 0000 0010 110s
+0	35	0000 0000 0010 101s
+0	36	0000 0000 0010 100s
+0	37	0000 0000 0010 011s
+0	38	0000 0000 0010 010s
+0	39	0000 0000 0010 001s
+0	40	0000 0000 0010 000s
+
+1	1	011s
+1	2	0001 10s
+1	3	0010 0101 s
+1	4	0000 0011 00s
+1	5	0000 0001 1011 s
+1	6	0000 0000 1011 0s
+1	7	0000 0000 1010 1s
+1	8	0000 0000 0011 111s
+1	9	0000 0000 0011 110s
+1	10	0000 0000 0011 101s
+1	11	0000 0000 0011 100s
+1	12	0000 0000 0011 011s
+1	13	0000 0000 0011 010s
+1	14	0000 0000 0011 001s
+1	15	0000 0000 0001 0011 s
+1	16	0000 0000 0001 0010 s
+1	17	0000 0000 0001 0001 s
+1	18	0000 0000 0001 0000 s
+
+2	1	0101 s
+2	2	0000 100s
+2	3	0000 0010 11s
+2	4	0000 0001 0100 s
+2	5	0000 0000 1010 0s
+
+3	1	0011 1s
+3	2	0010 0100 s
+3	3	0000 0001 1100 s
+3	4	0000 0000 1001 1s
+
+4	1	0011 0s
+4	2	0000 0011 11s
+4	3	0000 0001 0010 s
+
+5	1	0001 11s
+5	2	0000 0010 01s
+5	3	0000 0000 1001 0s
+
+6	1	0001 01s
+6	2	0000 0001 1110 s
+6	3	0000 0000 0001 0100 s
+
+7	1	0001 00s
+7	2	0000 0001 0101 s
+
+8	1	0000 111s
+8	2	0000 0001 0001 s
+
+9	1	0000 101s
+9	2	0000 0000 1000 1s
+
+10	1	0010 0111 s
+10	2	0000 0000 1000 0s
+
+11	1	0010 0011 s
+11	2	0000 0000 0001 1010 s
+
+12	1	0010 0010 s
+12	2	0000 0000 0001 1001 s
+
+13	1	0010 0000 s
+13	2	0000 0000 0001 1000 s
+
+14	1	0000 0011 10s
+14	2	0000 0000 0001 0111 s
+
+15	1	0000 0011 01s
+15	2	0000 0000 0001 0110 s
+
+16	1	0000 0010 00s
+16	2	0000 0000 0001 0101 s
+
+17	1	0000 0001 1111 s
+18	1	0000 0001 1010 s
+19	1	0000 0001 1001 s
+20	1	0000 0001 0111 s
+21	1	0000 0001 0110 s
+
+22	1	0000 0000 1111 1s
+23	1	0000 0000 1111 0s
+24	1	0000 0000 1110 1s
+25	1	0000 0000 1110 0s
+26	1	0000 0000 1101 1s
+
+27	1	0000 0000 0001 1111 s
+28	1	0000 0000 0001 1110 s
+29	1	0000 0000 0001 1101 s
+30	1	0000 0000 0001 1100 s
+31	1	0000 0000 0001 1011 s
diff --git a/converter/ppm/ppmtompeg/iframe.c b/converter/ppm/ppmtompeg/iframe.c
new file mode 100644
index 00000000..cb6d4683
--- /dev/null
+++ b/converter/ppm/ppmtompeg/iframe.c
@@ -0,0 +1,1062 @@
+/*===========================================================================*
+ * iframe.c                                  *
+ *                                       *
+ *  Procedures concerned with the I-frame encoding               *
+ *                                       *
+ * EXPORTED PROCEDURES:                              *
+ *  GenIFrame                                *
+ *  SetSlicesPerFrame                            *
+ *  SetBlocksPerSlice                            *
+ *  SetIQScale                               *
+ *  GetIQScale                               *
+ *  ResetIFrameStats                             *
+ *  ShowIFrameSummary                            *
+ *  EstimateSecondsPerIFrame                         *
+ *  EncodeYDC                                *
+ *  EncodeCDC                                *
+ *      time_elapsed                                                         *
+ *                                       *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+
+#ifdef CLOCKS_PER_SEC
+#include <times.h>
+#else
+#include <sys/times.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include "all.h"
+#include "mtypes.h"
+#include "frames.h"
+#include "prototypes.h"
+#include "block.h"
+#include "mpeg.h"
+#include "param.h"
+#include "mheaders.h"
+#include "fsize.h"
+#include "parallel.h"
+#include "postdct.h"
+#include "rate.h"
+#include "specifics.h"
+#include "opts.h"
+
+/*==================*
+ * STATIC VARIABLES *
+ *==================*/
+
+static  int lastNumBits = 0;
+static  int lastIFrame = 0;
+static int numBlocks = 0;
+static int numBits;
+static int numFrames = 0;
+static int numFrameBits = 0;
+static int32 totalTime = 0;
+static float    totalSNR = 0.0;
+static float    totalPSNR = 0.0;
+
+static int lengths[256] = {
+    0, 1, 2, 2, 3, 3, 3, 3,     /* 0 - 7 */
+    4, 4, 4, 4, 4, 4, 4, 4,     /* 8 - 15 */
+    5, 5, 5, 5, 5, 5, 5, 5,     /* 16 - 31 */
+    5, 5, 5, 5, 5, 5, 5, 5,
+    6, 6, 6, 6, 6, 6, 6, 6,     /* 32 - 63 */
+    6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6,
+    7, 7, 7, 7, 7, 7, 7, 7,     /* 64 - 127 */
+    7, 7, 7, 7, 7, 7, 7, 7,
+    7, 7, 7, 7, 7, 7, 7, 7,
+    7, 7, 7, 7, 7, 7, 7, 7,
+    7, 7, 7, 7, 7, 7, 7, 7,
+    7, 7, 7, 7, 7, 7, 7, 7,
+    7, 7, 7, 7, 7, 7, 7, 7,
+    7, 7, 7, 7, 7, 7, 7, 7,
+    8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8
+};
+
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+int qscaleI;
+int slicesPerFrame;
+int blocksPerSlice;
+int fCodeI, fCodeP, fCodeB;
+boolean printSNR = FALSE;
+boolean printMSE = FALSE;
+boolean decodeRefFrames = FALSE;
+Block **dct=NULL, **dctr=NULL, **dctb=NULL;
+dct_data_type   **dct_data; /* used in p/bframe.c */
+int  TIME_RATE;
+
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+extern void PrintItoIBitRate _ANSI_ARGS_((int numBits, int frameNum));
+
+/*===============================*
+ * INTERNAL PROCEDURE prototypes *
+ *===============================*/
+void AllocDctBlocks(void );
+int SetFCodeHelper (int sr);
+void CalcDistortion (MpegFrame *current, int y, int x);
+
+int
+SetFCodeHelper(int const SR) {
+
+    int     range,fCode;
+
+    if ( pixelFullSearch ) {
+        range = SR;
+    } else {
+        range = SR*2;
+    }
+
+    if ( range < 256 ) {
+        if ( range < 64 ) {
+            if ( range < 32 ) {
+                fCode = 1;
+            } else {
+                fCode = 2;
+            }
+        } else {
+            if ( range < 128 ) {
+                fCode = 3;
+            } else {
+                fCode = 4;
+            }
+        }
+    } else {
+        if ( range < 1024 ) {
+            if ( range < 512 ) {
+                fCode = 5;
+            } else {
+                fCode = 6;
+            }
+        } else {
+            if ( range < 2048 ) {
+                fCode = 7;
+            } else {
+                fprintf(stderr, "ERROR:  INVALID SEARCH RANGE!!!\n");
+                exit(1);
+            }
+        }
+    }
+    return fCode;
+}
+
+
+
+/*===========================================================================*
+ *
+ * SetFCode
+ *
+ *  set the forward_f_code and backward_f_code according to the search
+ *  range.  Must be called AFTER pixelFullSearch and searchRange have
+ *  been initialized.  Irrelevant for I-frames, but computation is
+ *  negligible (done only once, as well)
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    fCodeI,fCodeP,fCodeB
+ *
+ *===========================================================================*/
+void
+SetFCode(void) {
+    fCodeI = SetFCodeHelper(1); /* GenIFrame ignores value */
+    fCodeP = SetFCodeHelper(searchRangeP);
+    fCodeB = SetFCodeHelper(searchRangeB);
+}
+
+
+
+/*===========================================================================*
+ *
+ * SetSlicesPerFrame
+ *
+ *  set the number of slices per frame
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    slicesPerFrame
+ *
+ *===========================================================================*/
+void
+SetSlicesPerFrame(int const number) {
+
+    slicesPerFrame = number;
+}
+
+
+
+/*===========================================================================*
+ *
+ * SetBlocksPerSlice
+ *
+ *  set the number of blocks per slice, based on slicesPerFrame
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    blocksPerSlice
+ *
+ *===========================================================================*/
+void
+SetBlocksPerSlice(void) {
+
+    int     totalBlocks;
+    
+    totalBlocks = (Fsize_y>>4)*(Fsize_x>>4);
+
+    if ( slicesPerFrame > totalBlocks ) {
+        blocksPerSlice = 1;
+    } else {
+        blocksPerSlice = totalBlocks/slicesPerFrame;
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * SetIQScale
+ *
+ *  set the I-frame Q-scale
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    qscaleI
+ *
+ *===========================================================================*/
+void
+SetIQScale(int const qI) {
+    qscaleI = qI;
+}
+
+
+
+/*===========================================================================*
+ *
+ * GetIQScale
+ *
+ *  Get the I-frame Q-scale
+ *
+ * RETURNS: the Iframe Qscale
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+int
+GetIQScale(void) {
+    return qscaleI;
+}
+
+
+
+/*===========================================================================*
+ *
+ * GenIFrame
+ *
+ *  generate an I-frame; appends result to bb
+ *
+ * RETURNS: I-frame appended to bb
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+GenIFrame(BitBucket * const bb, 
+          MpegFrame * const current) {
+
+    int x, y;
+    int index;
+    FlatBlock    fb[6];
+    Block    dec[6];
+    int32 y_dc_pred, cr_dc_pred, cb_dc_pred;
+    int          totalBits;
+    int          totalFrameBits;
+    int32        startTime, endTime;
+    float        snr[3], psnr[3];
+    int          mbAddress;
+    int          QScale;
+    BlockMV      *info; /* Not used in Iframes, but nice to pass in anyway */
+    int          bitstreamMode, newQScale;
+    int          rc_blockStart=0;
+
+    if (dct==NULL) AllocDctBlocks();
+    if (collect_quant) {fprintf(collect_quant_fp, "# I\n");}
+
+    /* set-up for statistics */
+    numFrames++;
+    totalFrameBits = bb->cumulativeBits;
+    if ( showBitRatePerFrame ) {
+        if ( lastNumBits == 0 ) {
+            lastNumBits = bb->cumulativeBits;
+            lastIFrame = current->id;
+        } else {
+            /* ASSUMES 30 FRAMES PER SECOND */
+    
+            if (! realQuiet) {
+                fprintf(stdout, "I-to-I (frames %5d to %5d) bitrate:  %8d\n",
+                        lastIFrame, current->id-1,
+                        ((bb->cumulativeBits-lastNumBits)*30)/
+                        (current->id-lastIFrame));
+            }
+    
+            fprintf(bitRateFile, "I-to-I (frames %5d to %5d) bitrate:  %8d\n",
+                    lastIFrame, current->id-1,
+                    ((bb->cumulativeBits-lastNumBits)*30)/
+                    (current->id-lastIFrame));
+            lastNumBits = bb->cumulativeBits;       
+            lastIFrame = current->id;
+        }
+    }
+    
+    startTime = time_elapsed();
+    
+    Frame_AllocBlocks(current);
+    BlockifyFrame(current);
+
+    DBG_PRINT(("Generating iframe\n"));
+    QScale = GetIQScale();
+    /*   Allocate bits for this frame for rate control purposes */
+    bitstreamMode = getRateMode();
+    if (bitstreamMode == FIXED_RATE) {
+        targetRateControl(current);
+    }
+
+    Mhead_GenPictureHeader(bb, I_FRAME, current->id, fCodeI);
+    /* Check for Qscale change */
+    if (specificsOn) {
+        newQScale = SpecLookup(current->id, 0, 0 /* junk */, &info, QScale);
+        if (newQScale != -1) {
+            QScale = newQScale;
+        }
+        /* check for slice */
+        newQScale = SpecLookup(current->id, 1, 1, &info, QScale);
+        if (newQScale != -1) {
+            QScale = newQScale;
+        }
+    }
+    Mhead_GenSliceHeader(bb, 1, QScale, NULL, 0);
+    
+    if ( referenceFrame == DECODED_FRAME ) {
+        Frame_AllocDecoded(current, TRUE);
+    } else if ( printSNR ) {
+        Frame_AllocDecoded(current, FALSE);
+    }
+    
+    y_dc_pred = cr_dc_pred = cb_dc_pred = 128;
+    totalBits = bb->cumulativeBits;
+    mbAddress = 0;
+
+    /* DCT the macroblocks */
+    for (y = 0;  y < (Fsize_y >> 3);  y += 2) {
+        for (x = 0;  x < (Fsize_x >> 3);  x += 2) {
+            if (collect_quant && (collect_quant_detailed & 1)) 
+                fprintf(collect_quant_fp, "l\n");
+            if (DoLaplace) {LaplaceCnum = 0;}
+            mp_fwd_dct_block2(current->y_blocks[y][x], dct[y][x]);
+            mp_fwd_dct_block2(current->y_blocks[y][x+1], dct[y][x+1]);
+            mp_fwd_dct_block2(current->y_blocks[y+1][x], dct[y+1][x]);
+            mp_fwd_dct_block2(current->y_blocks[y+1][x+1], dct[y+1][x+1]);
+            if (collect_quant && (collect_quant_detailed & 1)) 
+                fprintf(collect_quant_fp, "c\n");
+            if (DoLaplace) {LaplaceCnum = 1;}
+            mp_fwd_dct_block2(current->cb_blocks[y>>1][x>>1], 
+                              dctb[y>>1][x>>1]);
+            if (DoLaplace) {LaplaceCnum = 2;}
+            mp_fwd_dct_block2(current->cr_blocks[y>>1][x>>1], 
+                              dctr[y>>1][x>>1]);
+        }
+    }
+    
+    if (DoLaplace)
+        CalcLambdas();
+
+    for (y = 0;  y < (Fsize_y >> 3);  y += 2) {
+        for (x = 0;  x < (Fsize_x >> 3);  x += 2) {
+            /* Check for Qscale change */
+            if (specificsOn) {
+                newQScale = 
+                    SpecLookup(current->id, 2, mbAddress, &info, QScale);
+                if (newQScale != -1) {
+                    QScale = newQScale;
+                }
+            }
+    
+            /*  Determine if new Qscale needed for Rate Control purposes  */
+            if (bitstreamMode == FIXED_RATE) {
+                rc_blockStart = bb->cumulativeBits;
+                newQScale = needQScaleChange(qscaleI,
+                                             current->y_blocks[y][x],
+                                             current->y_blocks[y][x+1],
+                                             current->y_blocks[y+1][x],
+                                             current->y_blocks[y+1][x+1]);
+                if (newQScale > 0) {
+                    QScale = newQScale;
+                }
+            }
+    
+            if ( (mbAddress % blocksPerSlice == 0) && (mbAddress != 0) ) {
+                /* create a new slice */
+                if (specificsOn) {
+                    /* Make sure no slice Qscale change */
+                    newQScale = SpecLookup(current->id, 1,
+                                           mbAddress/blocksPerSlice, &info, 
+                                           QScale);
+                    if (newQScale != -1) QScale = newQScale;
+                }
+                Mhead_GenSliceEnder(bb);
+                Mhead_GenSliceHeader(bb, 1+(y>>1), QScale, NULL, 0);
+                y_dc_pred = cr_dc_pred = cb_dc_pred = 128;
+      
+                GEN_I_BLOCK(I_FRAME, current, bb, 1+(x>>1), QScale);
+            } else {
+                GEN_I_BLOCK(I_FRAME, current, bb, 1, QScale);
+            }
+
+            if (WriteDistortionNumbers) {
+                CalcDistortion(current, y, x);
+            }
+    
+            if ( decodeRefFrames ) {
+                /* now, reverse the DCT transform */
+                LaplaceCnum = 0;
+                for ( index = 0; index < 6; index++ ) {
+                    if (!DoLaplace) {
+                        Mpost_UnQuantZigBlock(fb[index], dec[index], QScale, 
+                                              TRUE);
+                    } else {
+                        if (index == 4) {LaplaceCnum = 1;}
+                        if (index == 5) {LaplaceCnum = 2;}
+                        Mpost_UnQuantZigBlockLaplace(fb[index], dec[index], 
+                                                     QScale, TRUE);
+                    }
+                    mpeg_jrevdct((int16 *)dec[index]);      
+                }
+      
+                /* now, unblockify */
+                BlockToData(current->decoded_y, dec[0], y, x);
+                BlockToData(current->decoded_y, dec[1], y, x+1);
+                BlockToData(current->decoded_y, dec[2], y+1, x);
+                BlockToData(current->decoded_y, dec[3], y+1, x+1);
+                BlockToData(current->decoded_cb, dec[4], y>>1, x>>1);
+                BlockToData(current->decoded_cr, dec[5], y>>1, x>>1);
+            }
+    
+            numBlocks++;
+            mbAddress++;
+            /*   Rate Control */
+            if (bitstreamMode == FIXED_RATE) {
+                incMacroBlockBits(bb->cumulativeBits - rc_blockStart);
+                rc_blockStart = bb->cumulativeBits;
+                MB_RateOut(TYPE_IFRAME);
+            }
+        }
+    }
+    
+    if ( printSNR ) {
+        BlockComputeSNR(current,snr,psnr);
+        totalSNR += snr[0];
+        totalPSNR += psnr[0];
+    }
+    
+    numBits += (bb->cumulativeBits-totalBits);
+    
+    DBG_PRINT(("End of frame\n"));
+    
+    Mhead_GenSliceEnder(bb);
+    /*   Rate Control  */
+    if (bitstreamMode == FIXED_RATE) {
+        updateRateControl(TYPE_IFRAME);
+    }
+    
+    endTime = time_elapsed();
+    totalTime += (endTime-startTime);
+    
+    numFrameBits += (bb->cumulativeBits-totalFrameBits);
+    
+    if ( showBitRatePerFrame ) {
+        /* ASSUMES 30 FRAMES PER SECOND */
+        fprintf(bitRateFile, "%5d\t%8d\n", current->id,
+                30*(bb->cumulativeBits-totalFrameBits));
+    }
+    
+    if ( frameSummary && !realQuiet ) {
+        
+        /* ASSUMES 30 FRAMES PER SECOND */
+        fprintf(stdout, 
+                "FRAME %d (I):  %ld seconds  (%d bits/s output)\n", 
+                current->id, (long)((endTime-startTime)/TIME_RATE),
+                30*(bb->cumulativeBits-totalFrameBits));
+        if ( printSNR ) {
+            fprintf(stdout, 
+                    "FRAME %d:  SNR:  %.1f\t%.1f\t%.1f\t"
+                    "PSNR:  %.1f\t%.1f\t%.1f\n",
+                        current->id, snr[0], snr[1], snr[2],
+                    psnr[0], psnr[1], psnr[2]);
+        }
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * ResetIFrameStats
+ *
+ *  reset the I-frame statistics
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+ResetIFrameStats(void) {
+    numBlocks = 0;
+    numBits = 0;
+    numFrames = 0;
+    numFrameBits = 0;
+    totalTime = 0;
+}
+
+
+
+float
+IFrameTotalTime(void) {
+    return (float)totalTime/(float)TIME_RATE;
+}
+
+
+
+void
+ShowIFrameSummary(unsigned int const inputFrameBits, 
+                  unsigned int const totalBits, 
+                  FILE *       const fpointer) {
+/*----------------------------------------------------------------------------
+   Print out statistics on all I frames.
+-----------------------------------------------------------------------------*/
+    if (numFrames > 0) {
+        fprintf(fpointer, "-------------------------\n");
+        fprintf(fpointer, "*****I FRAME SUMMARY*****\n");
+        fprintf(fpointer, "-------------------------\n");
+
+        fprintf(fpointer, "  Blocks:    %5d     (%6d bits)     (%5d bpb)\n",
+                numBlocks, numBits, numBits/numBlocks);
+        fprintf(fpointer, "  Frames:    %5d     (%6d bits)     (%5d bpf)"
+                "(%2.1f%% of total)\n",
+                numFrames, numFrameBits, numFrameBits/numFrames,
+                100.0*(float)numFrameBits/(float)totalBits);
+        fprintf(fpointer, "  Compression:  %3d:1     (%9.4f bpp)\n",
+                numFrames*inputFrameBits/numFrameBits,
+                24.0*(float)numFrameBits/(float)(numFrames*inputFrameBits));
+        if ( printSNR )
+            fprintf(fpointer, "  Avg Y SNR/PSNR:  %.1f     %.1f\n",
+                    totalSNR/(float)numFrames, totalPSNR/(float)numFrames);
+        if ( totalTime == 0 ) {
+            fprintf(fpointer, "  Seconds:  NONE\n");
+        } else {
+            fprintf(fpointer, "  Seconds:  %9ld     (%9.4f fps)  (%9ld pps)  "
+                    "(%9ld mps)\n",
+                    (long)(totalTime/TIME_RATE),
+                    (float)((float)(TIME_RATE*numFrames)/(float)totalTime),
+                    (long)((float)TIME_RATE * (float)numFrames *
+                           (float)inputFrameBits/(24.0*(float)totalTime)),
+                    (long)((float)TIME_RATE*(float)numFrames *
+                           (float)inputFrameBits/(256.0*24.0 *
+                                                  (float)totalTime)));
+        }
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * EstimateSecondsPerIFrame
+ *
+ *  estimates the number of seconds required per I-frame
+ *
+ * RETURNS: seconds (floating point value)
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+float
+EstimateSecondsPerIFrame()
+{
+    return (float)totalTime/((float)TIME_RATE*(float)numFrames);
+}
+
+
+/*===========================================================================*
+ *
+ * EncodeYDC
+ *
+ *  Encode the DC portion of a DCT of a luminance block
+ *
+ * RETURNS: result appended to bb
+ *
+ * SIDE EFFECTS:    updates pred_term
+ *
+ *===========================================================================*/
+void
+EncodeYDC(int32       const dc_term,
+          int32 *     const pred_term,
+          BitBucket * const bb) {
+
+    /* see Table B.5a -- MPEG-I doc */
+    static int codes[9] = {
+    0x4, 0x0, 0x1, 0x5, 0x6, 0xe, 0x1e, 0x3e, 0x7e
+    };
+    static int codeLengths[9] = {
+    3,   2,   2,   3,   3,   4,   5,    6,    7
+    };
+    int ydiff, ydiff_abs;
+    int length;
+
+    ydiff = (dc_term - (*pred_term));
+    if (ydiff > 255) {
+#ifdef BLEAH 
+      fprintf(stdout, "TRUNCATED\n");
+#endif
+    ydiff = 255;
+    } else if (ydiff < -255) {
+#ifdef BLEAH 
+      fprintf(stdout, "TRUNCATED\n");
+#endif
+    ydiff = -255;
+    }
+
+    ydiff_abs = ABS(ydiff);
+    length = lengths[ydiff_abs];
+    Bitio_Write(bb, codes[length], codeLengths[length]);
+    if ( length != 0 ) {
+    if ( ydiff > 0 ) {
+        Bitio_Write(bb, ydiff_abs, length);
+    } else {
+        Bitio_Write(bb, ~ydiff_abs, length);
+    }
+    }
+
+    (*pred_term) += ydiff;
+}
+
+
+
+/*===========================================================================*
+ *
+ * EncodeCDC
+ *
+ *  Encode the DC portion of a DCT of a chrominance block
+ *
+ * RETURNS: result appended to bb
+ *
+ * SIDE EFFECTS:    updates pred_term
+ *
+ *===========================================================================*/
+void
+EncodeCDC(int32       const dc_term,
+          int32     * const pred_term,
+          BitBucket * const bb) {
+
+    /* see Table B.5b -- MPEG-I doc */
+    static int codes[9] = {
+        0x0, 0x1, 0x2, 0x6, 0xe, 0x1e, 0x3e, 0x7e, 0xfe
+    };
+    static int codeLengths[9] = {
+        2,   2,   2,   3,   4,   5,    6,    7,    8
+    };
+    int cdiff, cdiff_abs;
+    int length;
+
+    cdiff = (dc_term - (*pred_term));
+    if (cdiff > 255) {
+#ifdef BLEAH
+        fprintf(stdout, "TRUNCATED\n"); 
+#endif
+        cdiff = 255;
+    } else if (cdiff < -255) {
+#ifdef BLEAH
+        fprintf(stdout, "TRUNCATED\n"); 
+#endif
+        cdiff = -255;
+    }
+
+    cdiff_abs = ABS(cdiff);
+    length = lengths[cdiff_abs];
+    Bitio_Write(bb, codes[length], codeLengths[length]);
+    if ( length != 0 ) {
+        if ( cdiff > 0 ) {
+            Bitio_Write(bb, cdiff_abs, length);
+        } else {
+            Bitio_Write(bb, ~cdiff_abs, length);
+        }
+    }
+
+    (*pred_term) += cdiff;
+}
+
+
+
+void
+BlockComputeSNR(MpegFrame * const current,
+                float *     const snr,
+                float *     const psnr) {
+
+    int32    tempInt;
+    int y, x;
+    int32 varDiff[3];
+    double    ratio[3];
+    double    total[3];
+    uint8 **origY=current->orig_y, **origCr=current->orig_cr, 
+        **origCb=current->orig_cb;
+    uint8 **newY=current->decoded_y, **newCr=current->decoded_cr, 
+        **newCb=current->decoded_cb;
+    static int32       **SignalY,  **NoiseY;
+    static int32       **SignalCb, **NoiseCb;
+    static int32       **SignalCr, **NoiseCr;
+    static short   ySize[3], xSize[3];
+    static boolean needs_init=TRUE;
+  
+    /* Init */
+    if (needs_init) {
+        int ysz = (Fsize_y>>3) * sizeof(int32 *);
+        int xsz = (Fsize_x>>3);
+    
+        needs_init = FALSE;
+        for (y=0; y<3; y++) {
+            varDiff[y] = ratio[y] = total[y] = 0.0;
+        }
+        ySize[0]=Fsize_y;     xSize[0]=Fsize_x;
+        ySize[1]=Fsize_y>>1;  xSize[1]=Fsize_x>>1;
+        ySize[2]=Fsize_y>>1;  xSize[2]=Fsize_x>>1;
+        SignalY  = (int32 **) malloc(ysz);
+        NoiseY   = (int32 **) malloc(ysz);
+        SignalCb = (int32 **) malloc(ysz);
+        NoiseCb  = (int32 **) malloc(ysz);
+        SignalCr = (int32 **) malloc(ysz);
+        NoiseCr  = (int32 **) malloc(ysz);
+        if (SignalY == NULL || NoiseY == NULL || SignalCr == NULL || 
+            NoiseCb == NULL || SignalCb == NULL || NoiseCr == NULL) {
+            fprintf(stderr, "Out of memory in BlockComputeSNR\n");
+            exit(-1);
+        }
+        for (y = 0; y < ySize[0]>>3; y++) {
+            SignalY[y]  = (int32 *) calloc(xsz,4);
+            SignalCr[y]  = (int32 *) calloc(xsz,4);
+            SignalCb[y]  = (int32 *) calloc(xsz,4);
+            NoiseY[y]  = (int32 *) calloc(xsz,4);
+            NoiseCr[y]  = (int32 *) calloc(xsz,4);
+            NoiseCb[y]  = (int32 *) calloc(xsz,4);
+        }
+    } else {
+        for (y = 0; y < ySize[0]>>3; y++) {
+            memset((char *) &NoiseY[y][0], 0, (xSize[0]>>3) * 4);
+            memset((char *) &SignalY[y][0], 0, (xSize[0]>>3) * 4);
+            memset((char *) &NoiseCb[y][0], 0, (xSize[0]>>3) * 4);
+            memset((char *) &NoiseCr[y][0], 0, (xSize[0]>>3) * 4);
+            memset((char *) &SignalCb[y][0], 0, (xSize[0]>>3) * 4);
+            memset((char *) &SignalCr[y][0], 0, (xSize[0]>>3) * 4);
+        }
+    }
+  
+    /* find all the signal and noise */
+    for (y = 0; y < ySize[0]; y++) {
+        for (x = 0; x < xSize[0]; x++) {
+            tempInt = (origY[y][x] - newY[y][x]);
+            NoiseY[y>>4][x>>4] += tempInt*tempInt;
+            total[0] += (double)abs(tempInt);
+            tempInt = origY[y][x];
+            SignalY[y>>4][x>>4] += tempInt*tempInt;
+        }}
+    for (y = 0; y < ySize[1]; y++) {
+        for (x = 0; x < xSize[1]; x ++) {
+            tempInt = (origCb[y][x] - newCb[y][x]);
+            NoiseCb[y>>3][x>>3] += tempInt*tempInt;
+            total[1] += (double)abs(tempInt);
+            tempInt = origCb[y][x];
+            SignalCb[y>>3][x>>3] += tempInt*tempInt;
+            tempInt = (origCr[y][x]-newCr[y][x]);
+            NoiseCr[y>>3][x>>3] += tempInt*tempInt;
+            total[2] += (double)abs(tempInt);
+            tempInt = origCr[y][x];
+            SignalCr[y>>3][x>>3] += tempInt*tempInt;
+        }}
+  
+    /* Now sum up that noise */
+    for(y=0; y<Fsize_y>>4; y++){
+        for(x=0; x<Fsize_x>>4; x++){
+            varDiff[0] += NoiseY[y][x];
+            varDiff[1] += NoiseCb[y][x];
+            varDiff[2] += NoiseCr[y][x];
+            if (printMSE) printf("%4d ",(int)(NoiseY[y][x]/256.0));
+        }
+        if (printMSE) puts("");
+    }
+  
+    /* Now look at those ratios! */
+    for(y=0; y<Fsize_y>>4; y++){
+        for(x=0; x<Fsize_x>>4; x++){
+            ratio[0] += (double)SignalY[y][x]/(double)varDiff[0];
+            ratio[1] += (double)SignalCb[y][x]/(double)varDiff[1];
+            ratio[2] += (double)SignalCr[y][x]/(double)varDiff[2];
+        }}
+  
+    for (x=0; x<3; x++) {
+        snr[x] = 10.0 * log10(ratio[x]);
+        psnr[x] = 20.0 * log10(255.0/sqrt((double)varDiff[x]/
+                                          (double)(ySize[x]*xSize[x])));
+
+        if (! realQuiet) {
+            fprintf(stdout, "Mean error[%1d]:  %f\n",
+                    x, total[x] / (double)(xSize[x] * ySize[x]));
+        }
+
+    }
+}
+
+
+
+void
+WriteDecodedFrame(MpegFrame * const frame) {
+
+    FILE * fpointer;
+    char   fileName[256];
+    int    width, height;
+    int    y;
+
+    /* need to save decoded frame to disk because it might be accessed
+       by another process */
+
+    width = Fsize_x;
+    height = Fsize_y;
+
+    sprintf(fileName, "%s.decoded.%d", outputFileName, frame->id);
+
+    if (!realQuiet) {
+        fprintf(stdout, "Outputting to %s\n", fileName);
+        fflush(stdout);
+    }
+
+    fpointer = fopen(fileName, "wb");
+
+    for ( y = 0; y < height; y++ ) {
+        fwrite(frame->decoded_y[y], 1, width, fpointer);
+    }
+
+    for (y = 0; y < (height >> 1); y++) {           /* U */
+        fwrite(frame->decoded_cb[y], 1, width >> 1, fpointer);
+    }
+
+    for (y = 0; y < (height >> 1); y++) {           /* V */
+        fwrite(frame->decoded_cr[y], 1, width >> 1, fpointer);
+    }
+    fflush(fpointer);
+    fclose(fpointer);
+}
+
+
+
+void
+PrintItoIBitRate(int const numBits,
+                 int const frameNum) {
+
+    if ( showBitRatePerFrame ) {
+        /* ASSUMES 30 FRAMES PER SECOND */
+
+        if (! realQuiet) {
+            fprintf(stdout, "I-to-I (frames %5d to %5d) bitrate:  %8d\n",
+                    lastIFrame, frameNum-1,
+                    ((numBits-lastNumBits)*30)/
+                    (frameNum-lastIFrame));
+        }
+
+        fprintf(bitRateFile, "I-to-I (frames %5d to %5d) bitrate:  %8d\n",
+                lastIFrame, frameNum-1,
+                ((numBits-lastNumBits)*30)/
+                (frameNum-lastIFrame));
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * AllocDctBlocks
+ *
+ *  allocate memory for dct blocks
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    creates dct, dctr, dctb
+ *
+ *===========================================================================*/
+void
+AllocDctBlocks(void) {
+    int dctx, dcty;
+    int i;
+
+    dctx = Fsize_x / DCTSIZE;
+    dcty = Fsize_y / DCTSIZE;
+
+    dct = (Block **) malloc(sizeof(Block *) * dcty);
+    ERRCHK(dct, "malloc");
+    for (i = 0; i < dcty; i++) {
+        dct[i] = (Block *) malloc(sizeof(Block) * dctx);
+        ERRCHK(dct[i], "malloc");
+    }
+
+    dct_data = (dct_data_type **) malloc(sizeof(dct_data_type *) * dcty);
+    ERRCHK(dct_data, "malloc");
+    for (i = 0; i < dcty; i++) {
+        dct_data[i] = (dct_data_type *) malloc(sizeof(dct_data_type) * dctx);
+        ERRCHK(dct[i], "malloc");
+    }
+
+    dctr = (Block **) malloc(sizeof(Block *) * (dcty >> 1));
+    dctb = (Block **) malloc(sizeof(Block *) * (dcty >> 1));
+    ERRCHK(dctr, "malloc");
+    ERRCHK(dctb, "malloc");
+    for (i = 0; i < (dcty >> 1); i++) {
+        dctr[i] = (Block *) malloc(sizeof(Block) * (dctx >> 1));
+        dctb[i] = (Block *) malloc(sizeof(Block) * (dctx >> 1));
+        ERRCHK(dctr[i], "malloc");
+        ERRCHK(dctb[i], "malloc");
+    }
+}
+
+
+
+/*======================================================================*
+ *
+ * time_elapsed
+ *
+ *     Handle different time systems on different machines
+ *
+ *  RETURNS number of seconds process time used
+ *
+ *======================================================================*/
+int32 time_elapsed(void) {
+#ifdef CLOCKS_PER_SEC
+    /* ANSI C */
+    TIME_RATE = CLOCKS_PER_SEC;
+    return (int32) clock();
+#else
+    struct tms   timeBuffer;
+    TIME_RATE = 60;
+    times(&timeBuffer);
+    return timeBuffer.tms_utime + timeBuffer.tms_stime;
+#endif
+}
+
+
+
+void
+CalcDistortion(MpegFrame * const current,
+               int         const y,
+               int         const x) {
+
+    int qscale, distort=0;
+    Block decblk;
+    FlatBlock fblk;
+    int datarate = 0;
+  
+    for (qscale = 1; qscale < 32; qscale ++) {
+        distort = 0;
+        datarate = 0;
+        Mpost_QuantZigBlock(dct[y][x], fblk, qscale, TRUE);
+        Mpost_UnQuantZigBlock(fblk, decblk, qscale, TRUE);
+        if (collect_distortion_detailed) datarate += CalcRLEHuffLength(fblk);
+        mpeg_jrevdct((int16 *)decblk);
+        distort += mse(current->y_blocks[y][x], decblk);
+
+        Mpost_QuantZigBlock(dct[y][x+1], fblk, qscale, TRUE);
+        Mpost_UnQuantZigBlock(fblk, decblk, qscale, TRUE);
+        if (collect_distortion_detailed) datarate += CalcRLEHuffLength(fblk);
+        mpeg_jrevdct((int16 *)decblk);
+        distort += mse(current->y_blocks[y][x+1], decblk);
+
+        Mpost_QuantZigBlock(dct[y+1][x], fblk, qscale, TRUE);
+        Mpost_UnQuantZigBlock(fblk, decblk, qscale, TRUE);
+        if (collect_distortion_detailed) datarate += CalcRLEHuffLength(fblk);
+        mpeg_jrevdct((int16 *)decblk);
+        distort += mse(current->y_blocks[y+1][x], decblk);
+
+        Mpost_QuantZigBlock(dct[y+1][x+1], fblk, qscale, TRUE);
+        Mpost_UnQuantZigBlock(fblk, decblk, qscale, TRUE);
+        if (collect_distortion_detailed) datarate += CalcRLEHuffLength(fblk);
+        mpeg_jrevdct((int16 *)decblk);
+        distort += mse(current->y_blocks[y+1][x+1], decblk);
+
+        Mpost_QuantZigBlock(dctb[y >> 1][x >> 1], fblk, qscale, TRUE);
+        Mpost_UnQuantZigBlock(fblk, decblk, qscale, TRUE);
+        if (collect_distortion_detailed) datarate += CalcRLEHuffLength(fblk);
+        mpeg_jrevdct((int16 *)decblk);
+        distort += mse(current->cb_blocks[y>>1][x>>1], decblk);
+
+        Mpost_QuantZigBlock(dctr[y >> 1][x >> 1], fblk, qscale, TRUE);
+        Mpost_UnQuantZigBlock(fblk, decblk, qscale, TRUE);
+        if (collect_distortion_detailed) datarate += CalcRLEHuffLength(fblk);
+        mpeg_jrevdct((int16 *)decblk);
+        distort += mse(current->cr_blocks[y >> 1][x >> 1], decblk);
+
+        if (!collect_distortion_detailed) {
+            fprintf(distortion_fp, "\t%d\n", distort);
+        } else if (collect_distortion_detailed == 1) {
+            fprintf(distortion_fp, "\t%d\t%d\n", distort, datarate);
+        } else {
+            fprintf(fp_table_rate[qscale-1], "%d\n", datarate);
+            fprintf(fp_table_dist[qscale-1], "%d\n", distort);
+        }
+    }
+}
+
+
+
+
diff --git a/converter/ppm/ppmtompeg/input.c b/converter/ppm/ppmtompeg/input.c
new file mode 100644
index 00000000..2b87afa7
--- /dev/null
+++ b/converter/ppm/ppmtompeg/input.c
@@ -0,0 +1,515 @@
+/*===========================================================================*
+ * input.c              
+ *                      
+ *  Stuff for getting the raw frame input for the program
+ *                                    
+ *===========================================================================*/
+
+/* COPYRIGHT INFORMATION IS AT THE END OF THIS FILE */
+
+#define _XOPEN_SOURCE 1
+    /* This makes sure popen() is in stdio.h.  In GNU libc 2.1.3, 
+     _POSIX_C_SOURCE = 2 is sufficient, but on AIX 4.3, the higher level
+     _XOPEN_SOURCE is required.  2000.09.09 
+    */
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+
+#include "nstring.h"
+#include "mallocvar.h"
+#include "parallel.h"
+#include "readframe.h"
+#include "param.h"
+#include "jpeg.h"
+#include "input.h"
+
+extern boolean realQuiet;	/* TRUE = no messages to stdout */
+
+struct InputFileEntry {
+    char    left[256];
+    char    right[256];
+    bool    glob;       /* if FALSE, left is complete name */
+    int     startID;
+    int     endID;
+    int     skip;
+    int     numPadding;     /* -1 if there is none */
+    int     numFiles;
+    bool    repeat;
+};
+
+
+struct inputSource inputSource;
+
+
+
+void
+GetNthInputFileName(struct inputSource * const inputSourceP,
+                    unsigned int         const n,
+                    const char **        const fileNameP) {
+/*----------------------------------------------------------------------------
+   Return the file name of the Nth input file.
+-----------------------------------------------------------------------------*/
+    static int	lastN = 0, lastMapN = 0, lastSoFar = 0;
+    int	    mapN;
+    int     index;
+    int	    soFar;
+    int	    numPadding;
+    struct InputFileEntry * mapNEntryP;
+
+    assert(!inputSourceP->stdinUsed);
+    assert(n < inputSourceP->numInputFiles);
+
+    if (n >= lastN) {
+        soFar = lastSoFar;
+        index = lastMapN;
+    } else {
+        soFar = 0;
+        index = 0;
+    }
+
+    assert(index < inputSourceP->numInputFileEntries);
+
+    while (soFar + inputSourceP->inputFileEntries[index]->numFiles <= n) {
+        soFar +=  inputSourceP->inputFileEntries[index]->numFiles;
+        ++index;
+        assert(index < inputSourceP->numInputFileEntries);
+    }
+
+    mapN = index;
+    
+    mapNEntryP = inputSourceP->inputFileEntries[mapN];
+
+    index = mapNEntryP->startID + mapNEntryP->skip*(n - soFar);
+
+    numPadding = mapNEntryP->numPadding;
+
+    if (numPadding != -1) {
+        char    numBuffer[33];
+        int	    loop;
+
+        sprintf(numBuffer, "%32d", index);
+        for (loop = 32-numPadding; loop < 32; ++loop) {
+            if (numBuffer[loop] != ' ')
+                break;
+            else
+                numBuffer[loop] = '0';
+        }
+
+        if (mapNEntryP->repeat != TRUE)
+            asprintfN(fileNameP, "%s%s%s",
+                      mapNEntryP->left, &numBuffer[32-numPadding],
+                      mapNEntryP->right);
+        else
+            asprintfN(fileNameP, "%s", mapNEntryP->left);
+    } else {
+        if (mapNEntryP->repeat != TRUE)
+            asprintfN(fileNameP, "%s%d%s",
+                      mapNEntryP->left, index, mapNEntryP->right);
+        else
+            asprintfN(fileNameP, "%s", mapNEntryP->left);
+    }
+
+    lastN = n;
+    lastMapN = mapN;
+    lastSoFar = soFar;
+}
+
+
+
+void
+ReadNthFrame(struct inputSource * const inputSourceP,
+             unsigned int         const frameNumber,
+             boolean              const remoteIO,
+             boolean              const childProcess,
+             boolean              const separateConversion,
+             const char *         const slaveConversion,
+             const char *         const inputConversion,
+             MpegFrame *          const frameP,
+             bool *               const endOfStreamP) {
+
+    if (remoteIO)
+        /* Get the frame from the remote I/O server */
+        GetRemoteFrame(frameP, frameNumber);
+    else {
+        /* Get the frame out of the file in which it lives */
+
+        const char * conversion;
+
+        if (childProcess && separateConversion)
+            conversion = slaveConversion;
+        else
+            conversion = inputConversion;
+
+        ReadFrame(frameP, inputSourceP, frameNumber, conversion, endOfStreamP);
+    }
+}
+
+
+
+/* Jim Boucher's code */
+
+void
+JM2JPEG(struct inputSource * const inputSourceP) {
+    char full_path[1024];
+    char inter_file[1024]; 
+    int ci;
+    
+    for(ci = 0; ci < inputSourceP->numInputFileEntries; ci++) {
+        inter_file[0] = '\0';
+        full_path[0] = '\0';
+        strcpy(full_path, currentPath);
+    
+        if (!inputSource.stdinUsed) {
+            strcat(full_path, "/");
+            strcat(full_path, inputSourceP->inputFileEntries[ci]->left);
+            strcpy(inter_file,full_path);
+    
+            if (!realQuiet)
+                fprintf(stdout, "Extracting JPEG's in the JMOVIE from %s\n",
+                        full_path);
+    
+            JMovie2JPEG(full_path,
+                        inter_file,
+                        inputSourceP->inputFileEntries[ci]->startID, 
+                        inputSourceP->inputFileEntries[ci]->endID);
+        } else
+            pm_error("ERROR: Cannot use JMovie format on Standard Input");
+    }
+}
+
+
+
+static const char *
+SkipSpacesTabs(const char * const start) {
+
+    const char * p;
+
+    for (p = start; *p == ' ' || *p == '\t'; ++p);
+
+    return p;
+}
+
+
+
+static void
+processGlob(const char *            const input, 
+            struct InputFileEntry * const inputFileEntryP) {
+
+    const char *globPtr;
+    char *  charPtr;
+    char    left[256], right[256];
+    char    leftNumText[256], rightNumText[256];
+    char    skipNumText[256];
+    int	    leftNum, rightNum;
+    int	    skipNum;
+    boolean padding;
+    int	    numPadding = 0;
+
+    inputFileEntryP->glob = TRUE;
+    inputFileEntryP->repeat = FALSE;
+
+    /* star expand */
+
+    globPtr = input;
+    charPtr = left;
+    /* copy left of '*' */
+    while ( (*globPtr != '\0') && (*globPtr != '*') ) {
+        *charPtr = *globPtr;
+        charPtr++;
+        globPtr++;
+    }
+    *charPtr = '\0';
+
+    if (*globPtr == '\0') {
+        fprintf(stderr, 
+                "WARNING: expanding non-star regular expression\n");
+        inputFileEntryP->repeat = TRUE;
+        globPtr = input;
+        charPtr = left;
+        /* recopy left of whitespace */
+        while ( (*globPtr != '\0') && (*globPtr != '*') && 
+                (*globPtr != ' ')  && (*globPtr != '\t')) {
+            *charPtr = *globPtr;
+            charPtr++;
+            globPtr++;
+        }
+        *charPtr = '\0';
+        *right = '\0';
+    } else {
+
+        globPtr++;
+        charPtr = right;
+        /* copy right of '*' */
+        while ( (*globPtr != '\0') && (*globPtr != ' ') &&
+                (*globPtr != '\t') ) {
+            *charPtr = *globPtr;
+            charPtr++;
+            globPtr++;
+        }
+        *charPtr = '\0';
+    }
+      
+    globPtr = SkipSpacesTabs(globPtr);
+
+    if ( *globPtr != '[' ) {
+        fprintf(stderr, 
+                "ERROR:  "
+                "Invalid input file expansion expression (no '[')\n");
+        exit(1);
+    }
+
+    globPtr++;
+    charPtr = leftNumText;
+    /* copy left number */
+    while ( ISDIGIT(*globPtr) ) {
+        *charPtr = *globPtr;
+        charPtr++;
+        globPtr++;
+    }
+    *charPtr = '\0';
+
+    if ( *globPtr != '-' ) {
+        fprintf(stderr, 
+                "ERROR:  "
+                "Invalid input file expansion expression (no '-')\n");
+        exit(1);
+    }
+
+    globPtr++;
+    charPtr = rightNumText;
+    /* copy right number */
+    while ( ISDIGIT(*globPtr) ) {
+        *charPtr = *globPtr;
+        charPtr++;
+        globPtr++;
+    }
+    *charPtr = '\0';
+    if ( atoi(rightNumText) < atoi(leftNumText) ) {
+        fprintf(stderr, 
+                "ERROR:  "
+                "Beginning of input range is higher than end.\n");
+        exit(1);
+    }
+
+
+    if ( *globPtr != ']' ) {
+        if ( *globPtr != '+' ) {
+            fprintf(stderr, 
+                    "ERROR:  "
+                    "Invalid input file expansion expression "
+                    "(no ']')\n");
+            exit(1);
+        }
+
+        globPtr++;
+        charPtr = skipNumText;
+        /* copy skip number */
+        while ( ISDIGIT(*globPtr) ) {
+            *charPtr = *globPtr;
+            charPtr++;
+            globPtr++;
+        }
+        *charPtr = '\0';
+
+        if ( *globPtr != ']' ) {
+            fprintf(stderr, 
+                    "ERROR:  Invalid input file expansion expression "
+                    "(no ']')\n");
+            exit(1);
+        }
+
+        skipNum = atoi(skipNumText);
+    } else {
+        skipNum = 1;
+    }
+
+    leftNum = atoi(leftNumText);
+    rightNum = atoi(rightNumText);
+
+    if ( (leftNumText[0] == '0') && (leftNumText[1] != '\0') ) {
+        padding = TRUE;
+        numPadding = strlen(leftNumText);
+    } else {
+        padding = FALSE;
+    }
+
+    inputFileEntryP->startID = leftNum;
+    inputFileEntryP->endID = rightNum;
+    inputFileEntryP->skip = skipNum;
+    inputFileEntryP->numFiles = 
+        (rightNum-leftNum+1)/skipNum;
+    strcpy(inputFileEntryP->left, left);
+    strcpy(inputFileEntryP->right, right);
+    if ( padding ) {
+        inputFileEntryP->numPadding = numPadding;
+    } else {
+        inputFileEntryP->numPadding = -1;
+    }
+}
+
+
+
+static void processJmovie(const char *            const input, 
+                          struct InputFileEntry * const inputFileEntryP) {
+    FILE    *jmovie;
+    char    full_path[1024];
+
+    inputFileEntryP->glob = TRUE;
+    full_path[0] = '\0';
+    strcpy(full_path, currentPath);
+    
+    strcat(full_path, "/");
+    strcat(full_path, input);
+    jmovie = fopen(input, "rb"); 
+    
+    if (jmovie == NULL) {
+        perror (input); 
+        exit (1);
+    }
+    
+    fseek (jmovie, (8*sizeof(char)), 0);
+    fseek (jmovie, (2*sizeof(int)), 1);
+    
+    if (fread(&(inputFileEntryP->numFiles),
+              sizeof(int), 1, jmovie) != 1) {
+        perror ("Error in reading number of frames in JMOVIE");
+        exit(1);
+    }
+    fclose (jmovie);
+
+    strcpy(inputFileEntryP->right,".jpg");
+    inputFileEntryP->numPadding = -1;
+    inputFileEntryP->startID = 1;
+    inputFileEntryP->endID = (inputFileEntryP->numFiles-1);
+    inputFileEntryP->skip = 1;
+    if (! realQuiet) {
+        fprintf (stdout, 
+                 "Encoding all %d frames from JMOVIE.\n", 
+                 inputFileEntryP->endID);
+    }
+} 
+
+
+
+static void
+processSimpleFileName(const char *            const input,
+                      struct InputFileEntry * const inputFileEntryP) {
+
+    inputFileEntryP->glob = FALSE;
+    inputFileEntryP->numFiles = 1;
+    /* fixes a bug from version 1.3: */
+    inputFileEntryP->numPadding = 0;
+    /* fixes a bug from version 1.4 */
+    strcpy(inputFileEntryP->right,"\0");
+    inputFileEntryP->startID = 0;
+    inputFileEntryP->endID = 0;
+    inputFileEntryP->skip = 0;
+}
+
+
+#define INPUT_ENTRY_BLOCK_SIZE   128
+
+void
+AddInputFiles(struct inputSource * const inputSourceP,
+              const char *         const input) {
+
+    unsigned int const currentIndex = inputSourceP->numInputFileEntries;
+
+    struct InputFileEntry * currentEntryP;
+
+    if (currentIndex >= inputSourceP->ifArraySize) {
+        /* Get more space */
+        inputSourceP->ifArraySize += INPUT_ENTRY_BLOCK_SIZE;
+        REALLOCARRAY_NOFAIL(inputSourceP->inputFileEntries, 
+                            inputSourceP->ifArraySize);
+    }
+    MALLOCVAR_NOFAIL(inputSourceP->inputFileEntries[currentIndex]);
+    currentEntryP = inputSourceP->inputFileEntries[currentIndex];
+
+    if (input[strlen(input)-1] == ']') 
+        processGlob(input, currentEntryP);
+    else {
+        strcpy(currentEntryP->left, input);
+        if (baseFormat == JMOVIE_FILE_TYPE) 
+            processJmovie(input, currentEntryP);
+        else 
+            processSimpleFileName(input, currentEntryP);
+    }
+    inputSourceP->numInputFiles += currentEntryP->numFiles;
+    ++inputSourceP->numInputFileEntries;
+}
+
+
+
+void
+SetStdinInput(struct inputSource * const inputSourceP) {
+
+    assert(inputSourceP->numInputFileEntries == 0);
+
+    inputSourceP->stdinUsed = TRUE;
+    inputSourceP->numInputFiles = INT_MAX;
+}
+
+
+
+void
+CreateInputSource(struct inputSource ** const inputSourcePP) {
+
+    struct inputSource * inputSourceP;
+
+    MALLOCVAR_NOFAIL(inputSourceP);
+
+    inputSourceP->stdinUsed = FALSE;
+    inputSourceP->numInputFileEntries = 0;
+    inputSourceP->ifArraySize = 1;
+    inputSourceP->numInputFiles = 0;
+    MALLOCARRAY_NOFAIL(inputSourceP->inputFileEntries, 1);
+    
+    *inputSourcePP = inputSourceP;
+}
+
+
+
+void
+DestroyInputSource(struct inputSource * const inputSourceP) {
+
+    unsigned int i;
+
+    for (i = 0; i < inputSourceP->numInputFileEntries; ++i)
+        free(inputSourceP->inputFileEntries[i]);
+
+    free(inputSourceP->inputFileEntries);
+
+    free(inputSourceP);
+}
+
+
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
diff --git a/converter/ppm/ppmtompeg/jpeg.c b/converter/ppm/ppmtompeg/jpeg.c
new file mode 100644
index 00000000..3aad6364
--- /dev/null
+++ b/converter/ppm/ppmtompeg/jpeg.c
@@ -0,0 +1,634 @@
+/*===========================================================================*
+ * jpeg.c                              
+ *                                     
+ *  procedures to deal with JPEG files 
+ *                                     
+ * EXPORTED PROCEDURES:                
+ *  JMovie2JPEG                        
+ *      ReadJPEG                      
+ *                                    
+ *===========================================================================*/
+
+/* COPYRIGHT INFORMATION IS AT THE END OF THIS FILE */
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+#define _XOPEN_SOURCE    /* Make sure stdio.h contains fileno() */
+#include <stdio.h>
+#include "all.h"
+/* With the lossless jpeg patch applied to the Jpeg library
+    (ftp://ftp.wizards.dupont.com/pub/ImageMagick/delegates/ljpeg-6b.tar.gz),
+    the name of min_DCT_scaled_size changes to min_codec_data_unit,
+    for some reason.  With this macro, we change it back.  
+*/
+#define min_codec_data_unit min_DCT_scaled_size
+#include <jpeglib.h>
+#undef min_codec_data_unit
+#include "mtypes.h"
+#include "frames.h"
+#include "prototypes.h"
+#include "param.h"
+#include "readframe.h"
+#include "fsize.h"
+#include "rgbtoycc.h"
+#include "jpeg.h"
+
+#include "mallocvar.h"
+
+/* make it happier.... */
+#undef DCTSIZE2
+
+/* jcopy_sample_rows() is an internal routine in the JPEG library, not 
+   meant for use by us.  We should figure out what the official interface
+   for this is and use it.  The following is copied out of jpegint.h, which
+   is part of the JPEG library source code.
+   */
+extern void jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row,
+                    JSAMPARRAY output_array, int dest_row,
+                    int num_rows, JDIMENSION num_cols));
+
+
+#define HEADER_SIZE 607   /*JFIF header size used on output images*/
+
+
+
+/*=======================================================================*
+ *                                                                       *
+ * JMovie2JPEG                                                           *
+ *                                                                       *
+ *      Splits up a Parallax J_Movie into a set of JFIF image files      *
+ *                                                                       *
+ * RETURNS:     nothing                                                  *
+ *                                                                       *
+ * SIDE EFFECTS:    none                                                 *
+ *                                                                       *
+ *   Contributed By James Boucher(jboucher@flash.bu.edu)                 *
+ *               Boston University Multimedia Communications Lab         *
+ * This code was adapted from the Berkeley Playone.c and Brian Smith's   *
+ * JGetFrame code after being modified on 10-7-93 by Dinesh Venkatesh    *
+ * of BU.                                                                *
+ *       This code converts a version 2 Parallax J_Movie into a          *
+ * set of JFIF compatible JPEG images. It works for all image            *
+ * sizes and qualities.                                                  *
+ ************************************************************************/
+void
+JMovie2JPEG(const char * const infilename,
+                /* input filename string */
+            const char * const obase,
+                /* output filename base string=>obase##.jpg */ 
+            int          const start,
+                /* first frame to be extracted */
+            int          const end
+                /* last frame to be extracted */
+    ) {
+
+    FILE *inFile;         /* Jmovie file pointer */
+    FILE *outFile;        /* JPEG file pointer for output file */
+    int fd, i;            /* input file descriptor and a counting variable*/
+    char ofname[256];     /* output filename string */
+    int Temp = 0, temp = 0;   /* dummy variables */
+    int image_offset = 0;     /* counting variable */
+    /* J_Movie header infomation */
+    int ver_no;           /* version number - expected to be 2 */
+    int fps;              /* frame rate - frames per second */
+    int no_frames;        /* total number of frames in jmovie */
+    int bandwidth;        /* bandwidth required for normal playback*/
+    int qfactor;          /* quality factor used to scale Q matrix */
+    int mapsize;          /* number of color map entries - 2^24 */
+    int audio_tracks;     /* number of audio tracks ==1    */
+    int audiosize;        /*number of bytes in audio tracks */
+    int *inoffsets;       /* input frame offsets from start of jmovie*/
+    int width;            /* image width */
+    int height;           /* image height */
+    int size;             /* total image size in bytes */
+    char op_code;         /* jmovie op_code */
+    char jpeg_size[4];        /* jpeg data size */
+    static char junk[1000];   /* data sink for audio data */
+    int last;             /* Number of last frame we will process */
+
+    /* The next array represents the default JFIF header for
+       quality = 100 and size = 320x240. The values are
+       adjusted as the J_Movie header is read.  The default
+       size of this array is set large so as to make room
+       for the appending of the jpeg bitstream. It can be
+       made smaller if you have a better idea of its expected size
+    */
+    static unsigned char inbuffer[300000] = {    
+        0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46,  
+        0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
+        0x00, 0x01, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x11,  
+        0x08, 0x00, 0xF0, 0x01, 0x40, 0x03, 0x01, 0x21,
+        0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF,  
+        0xDB, 0x00, 0x84, 0x00, 0x10, 0x0B, 0x0C, 0x0E,
+        0x0C, 0x0A, 0x10, 0x0E, 0x0D, 0x0E, 0x12,  
+        0x11, 0x10, 0x13, 0x18, 0x28, 0x1A, 0x18, 0x16,
+        0x16, 0x18, 0x31, 0x23, 0x25, 0x1D, 0x28, 0x3A,  
+        0x33, 0x3D, 0x3C, 0x39, 0x33, 0x38, 0x37, 0x40,
+        0x48, 0x5C, 0x4E, 0x40, 0x44, 0x57, 0x45, 0x37,  
+        0x38, 0x50, 0x6D, 0x51, 0x57, 0x5F, 0x62, 0x67,
+        0x68, 0x67, 0x3E, 0x4D, 0x71, 0x79, 0x70, 0x64,  
+        0x78, 0x5C, 0x65, 0x67, 0x63, 0x01, 0x11, 0x12,
+        0x12, 0x18, 0x15, 0x18, 0x2F, 0x1A, 0x1A, 0x2F,  
+        0x63, 0x42, 0x38, 0x42, 0x63, 0x63, 0x63, 0x63,
+        0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,  
+        0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,  
+        0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,  
+        0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,  
+        0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,  
+        0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0xFF, 0xC4,
+        0x01, 0xA2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01,  
+        0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04,  
+        0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
+        0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04,  
+        0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01,
+        0x7D, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05,  
+        0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61,
+        0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1,  
+        0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1,
+        0xF0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09,   
+        0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26,
+        0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37,  
+        0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47,
+        0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57,  
+        0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67,
+        0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77,  
+        0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87,
+        0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96,  
+        0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5,
+        0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4,  
+        0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3,
+        0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2,  
+        0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA,
+        0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8,  
+        0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
+        0xF7, 0xF8, 0xF9, 0xFA, 0x01, 0x00, 0x03, 0x01,  
+        0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02,  
+        0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 
+        0x0A, 0x0B, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04,  
+        0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00,
+        0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11,  
+        0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51,
+        0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08,  
+        0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23,
+        0x33, 0x52, 0xF0, 0x15, 0x62, 0x72, 0xD1,   
+        0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17,
+        0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A,  
+        0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44,
+        0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54,  
+        0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64,
+        0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74,  
+        0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83,
+        0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92,  
+        0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A,
+        0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9,  
+        0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8,
+        0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7,  
+        0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6,
+        0xD7, 0xD8, 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5,  
+        0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4,
+        0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFF, 0xDA,  
+        0x00, 0x0C, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03,
+        0x11, 0x00, 0x3F, 0x00  
+
+    };
+    
+    if (start > end) {
+        fprintf(stderr,"bad frame numbers\n");
+        exit(1);
+    }
+    
+    /* open J_Movie */
+    inFile = fopen(infilename, "rb");
+    if (inFile == NULL) {
+        perror (infilename);
+        exit(1);
+    }
+    
+    /* get file descriptor */    
+    fd = fileno(inFile);
+    
+    /* The following lines parse the jpeg_movie header and recover the */
+    /* relavant information */
+
+    fseek(inFile, 8 * sizeof(char), 0);
+    
+    if (fread(&ver_no, sizeof(int), 1, inFile) != 1) {
+        perror("Error in reading version");
+        exit(1);
+    }  
+    if (ver_no != 2) {
+        perror("Unrecognized version - Quantization tables may be wrong\n");
+    }
+    if (fread(&fps, sizeof(int), 1, inFile) != 1) {
+        perror("Error in reading fps");
+        exit(1);
+    }  
+    if (fread (&no_frames, sizeof(int), 1, inFile) != 1) {
+        perror("Error in reading no_frames");
+        exit(1);
+    }  
+
+    MALLOCARRAY(inoffsets, no_frames);
+    
+    if (fread(&width, sizeof(int), 1, inFile) != 1) {
+        perror("Error in reading width");
+        exit(1);
+    }  
+    /* set image width in JFIF header */
+    inbuffer[27] = (char)(0xFF & (width >> 8));
+    inbuffer[28] = (char)(0xFF & width);
+ 
+    if (fread(&height, sizeof(int), 1, inFile) != 1) {
+        perror("Error in reading height");
+        exit(1);
+    }  
+    /* set image height in JFIF header */
+    inbuffer[25] = (char)(0xFF & (height >> 8));
+    inbuffer[26] = (char)(0xFF & height);
+    
+    if (fread(&bandwidth, sizeof(int), 1, inFile) != 1) {
+        perror("Error in reading bandwidth");
+        exit(1);
+    }  
+    
+    if (fread(&qfactor, sizeof(int), 1, inFile) != 1) {
+        perror("Error in reading qfactor");
+        exit(1);
+    }  
+    /* The default quality factor = 100, therefore, if
+       our quality factor does not equal 100 we must
+       scale the quantization matrices in the JFIF header
+    */    
+    /* Note values are clipped to a max of 255 */
+    if (qfactor != 100) {
+        for (Temp = 44; Temp < 108; ++Temp) {
+            temp= (inbuffer[Temp]*qfactor)/100;
+            inbuffer[Temp] = (char)((temp<255) ? temp : 255);
+        }
+        for (Temp = 109; Temp < 173; ++Temp) {
+            temp = (inbuffer[Temp]*qfactor)/100;
+            inbuffer[Temp] = (char)((temp<255) ? temp : 255);
+        }    
+    }
+  
+    if (fread(&mapsize, sizeof(int), 1, inFile) != 1) {
+        perror("Error in reading mapsize");
+        exit(1);
+    }  
+    if (fread (&image_offset, sizeof(int), 1, inFile) != 1) {
+        perror("Error in reading image offset");
+        exit(1);
+    }
+    if (fread (&audio_tracks, sizeof(int), 1, inFile) != 1) {
+        perror("Error in reading audio tracks");
+        exit(1);
+    }
+    
+    fread(junk,sizeof(int),1,inFile);
+    
+    if (fread (&audiosize, sizeof(int), 1, inFile) != 1) {
+        perror("Error in reading audiosize");
+        exit(1);
+    }
+    
+    fseek (inFile, image_offset, 0);
+    
+    last = MIN(end, no_frames-1);
+
+    for (i = 0; i < no_frames; ++i)  {
+        fread(&(inoffsets[i]), sizeof(int), 1, inFile);
+    } /* Reads in the frame sizes into the array */
+    
+    rewind(inFile);
+
+    /* Extract JFIF files from J_Movie */    
+    for (i = start; i <= last; ++i) {
+        size = inoffsets[i] - inoffsets[i-1]- 5;
+        lseek(fd, inoffsets[i-1], 0); 
+        read(fd, &(op_code), 1);
+        while (op_code !=  0xffffffec) {
+            read(fd,junk,audiosize);
+            read(fd, &(op_code), 1);  
+            size = size - audiosize ;
+        } /* To skip the audio bytes in each frame */
+        read(fd, jpeg_size, 4);
+        read(fd, &(inbuffer[607]), size);
+        sprintf(ofname, "%s%d.jpg", obase, i);
+        outFile = fopen(ofname, "wb");
+        fwrite(inbuffer, (size+607), sizeof(char), outFile);
+        fclose(outFile);        
+    }
+    free(inoffsets);
+    fclose(inFile);
+}
+
+
+
+
+/*===========================================================================*
+ *
+ * ReadJPEG  contributed by James Arthur Boucher of Boston University's
+ *                                Multimedia Communications Lab
+ *
+ *      read a JPEG file and copy data into frame original data arrays
+ *
+ * RETURNS:     mf modified
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+/*************************JPEG LIBRARY INTERFACE*********************/
+/*
+ * THE BIG PICTURE:
+ *
+ * The rough outline this JPEG decompression operation is:
+ *
+ *      allocate and initialize JPEG decompression object
+ *      specify data source (eg, a file)
+ *      jpeg_read_header();     // obtain image dimensions and other parameters
+ *      set parameters for decompression
+ *      jpeg_start_decompress();
+ *      while (scan lines remain to be read)
+ *              jpeg_read_scanlines(...);
+ *      jpeg_finish_decompress();
+ *      release JPEG decompression object
+ *
+ */
+void
+ReadJPEG(MpegFrame * const mf,
+         FILE *      const fp) {
+
+    static struct jpeg_decompress_struct cinfo;
+        /* The JPEG decompression parameters and pointers to
+           working data (which is allocated as needed by the JPEG library).
+        */
+    struct jpeg_error_mgr jerr;
+    JSAMPARRAY scanarray[3];
+    int ci,cd,cp;
+    JDIMENSION ncols[3];
+    JDIMENSION nrows[3];
+    jpeg_component_info *compptr;
+    int buffer_height;
+    int current_row[3];
+    uint8 **orig[3];
+    int h_samp[3],v_samp[3];
+    int max_h_samp,max_v_samp;
+    int temp_h, temp_v;
+    int temp;
+
+    /* Allocate and initialize JPEG decompression object */
+    cinfo.err = jpeg_std_error(&jerr);
+
+    /*
+    ** If we're reading from stdin we want to create the cinfo struct
+    ** ONCE (during the first read).  This is because when reading jpeg
+    ** from stdin we will not release the cinfo struct, because doing
+    ** so would totally screw up the read buffer and make it impossible
+    ** to read jpegs from stdin.
+    ** Dave Scott (dhs), UofO, 7/19/95
+    */
+    {
+        static int first_stdin;
+        first_stdin = TRUE;  /* initial value */
+        if ((fp != stdin) || first_stdin) {
+            first_stdin = FALSE;
+            /* Now we can initialize the JPEG decompression object. */
+            jpeg_create_decompress(&cinfo);
+            /* specify data source (eg, a file) */
+            jpeg_stdio_src(&cinfo, fp);
+        }
+    }
+  
+    /* specify data source (eg, a file) */
+  
+    jpeg_stdio_src(&cinfo, fp);
+  
+    /* read file parameters with jpeg_read_header() */
+  
+  
+    (void) jpeg_read_header(&cinfo, TRUE);
+    /* We can ignore the return value from jpeg_read_header since
+     *   (a) suspension is not possible with the stdio data source, and
+     *   (b) we passed TRUE to reject a tables-only JPEG file as an error.
+     */
+  
+    /* set parameters for decompression */
+#ifdef JPEG4
+    cinfo.want_raw_output = TRUE;
+#else
+    cinfo.raw_data_out = TRUE;
+#endif
+    cinfo.out_color_space = JCS_YCbCr;
+  
+    /* calculate image output dimensions */
+    jpeg_calc_output_dimensions(&cinfo);
+    /* the above calculation will set these soon */
+    /* for now we'll set them ourselves */
+    
+    /* tell mpeg_encode the size of the JPEG Image*/
+    Fsize_Note(mf->id,(int)(cinfo.image_width),(int)(cinfo.image_height));
+  
+    /* Allocate memory for the raw YCbCr data to occupy*/
+    Frame_AllocYCC(mf);      /*allocate space for mpeg frame*/
+  
+    /* copy pointers to array structure- this make the following
+       code more compact
+    */
+    orig[0] = mf->orig_y;
+    orig[1] = mf->orig_cb;
+    orig[2] = mf->orig_cr;
+  
+    /* Note that we can use the info obtained from jpeg_read_header.
+     */
+  
+    /* Start decompressor */
+  
+    jpeg_start_decompress(&cinfo);
+  
+  
+    /* JSAMPLEs per row in output buffer  */
+    /* collect component subsample values*/
+    for (cp=0, compptr = cinfo.comp_info;
+         cp < cinfo.num_components;
+         cp++, compptr++) {
+        h_samp[cp] = compptr->h_samp_factor;
+        v_samp[cp] = compptr->v_samp_factor;
+    }
+    /* calculate max subsample values*/
+    temp_h = (h_samp[0]<h_samp[1]) ? h_samp[1] : h_samp[0];
+    max_h_samp = (temp_h<h_samp[2]) ? h_samp[2]:temp_h;
+    temp_v = (v_samp[0]<v_samp[1]) ? v_samp[1] : v_samp[0];
+    max_v_samp = (temp_v<v_samp[2]) ? v_samp[2]:temp_v;
+  
+    /* Make an 8-row-high sample array that will go away when done
+       with image
+    */
+#ifdef JPEG4
+    buffer_height = 8;  /* could be 2, 4,8 rows high */
+#else
+    buffer_height = cinfo.max_v_samp_factor * cinfo.min_DCT_scaled_size;
+#endif
+  
+    for(cp=0,compptr = cinfo.comp_info;cp<cinfo.num_components;
+        cp++,compptr++) {
+        ncols[cp] = (JDIMENSION)((cinfo.image_width*compptr->h_samp_factor)/
+                                 max_h_samp);
+    
+        nrows[cp] = (JDIMENSION)((buffer_height*compptr->v_samp_factor)/
+                                 max_v_samp);
+    
+        scanarray[cp] = (*cinfo.mem->alloc_sarray)
+            ((j_common_ptr) &cinfo, JPOOL_IMAGE, ncols[cp], nrows[cp]);
+    }
+  
+    /*  while (scan lines remain to be read)
+           jpeg_read_scanlines(...);
+    */
+  
+    /* Here we use the library's state variable cinfo.output_scanline as the
+     * loop counter, so that we don't have to keep track ourselves.
+     */
+
+    while (cinfo.output_scanline < cinfo.output_height) {
+
+#ifdef JPEG4
+        (void) jpeg_read_raw_scanlines(&cinfo, scanarray, buffer_height);
+#else
+        (void) jpeg_read_raw_data(&cinfo, scanarray, buffer_height);
+#endif
+
+        /* alter subsample ratio's if neccessary */
+        if ((h_samp[0]==2) && (h_samp[1]==1) && (h_samp[2]==1) &&
+            (v_samp[0]==2) && (v_samp[1]==1) && (v_samp[2]==1)) {
+            /* we are 4:1:1 as expected by the encoder*/
+        } else if((h_samp[0] == 2) && (h_samp[1] == 1) && (h_samp[2]==1) &&
+                  (v_samp[0] == 1) && (v_samp[1] == 1) && (v_samp[2]==1)) {
+            /* must subsample 2:1 vertically and adjust params*/
+            for (ci = 1; ci < 3; ++ci) {
+                for (cp = 0; cp < (buffer_height/2); cp = cp+1) {
+                    for (cd = 0; cd < ncols[ci]; ++cd) {
+                        temp = ((scanarray[ci][cp*2][cd] +
+                                 scanarray[ci][(cp*2)+1][cd]) / 2);
+                        scanarray[ci][cp][cd] = (JSAMPLE)(temp);
+                    }
+                }
+            }
+            /* only reset values the first time through*/
+            if (cinfo.output_scanline == buffer_height) {
+                nrows[1] = nrows[1]/2;
+                nrows[2] = nrows[2]/2;
+                max_v_samp = 2;
+                v_samp[0] = 2;
+            }
+        } else
+            pm_error("Invalid subsampling ratio");
+    
+        /* transfer data from jpeg buffer to MPEG frame */
+        /* calculate the row we wish to output into */
+        for (ci = 0, compptr = cinfo.comp_info;
+             ci < cinfo.num_components;
+             ++ci, ++compptr) {
+            current_row[ci] =((cinfo.output_scanline - buffer_height)*
+                              (v_samp[ci])/max_v_samp);  
+      
+            jcopy_sample_rows(scanarray[ci],0,(JSAMPARRAY)(orig[ci]),
+                              current_row[ci],nrows[ci],ncols[ci]);
+        }
+    }  
+  
+    /* Step 7: Finish decompression */
+  
+    (void) jpeg_finish_decompress(&cinfo);
+    /* We can ignore the return value since suspension is not possible
+     * with the stdio data source.
+     */
+  
+    /* Step 8: Release JPEG decompression object */
+  
+    /*
+    ** DO NOT release the cinfo struct if we are reading from stdin, this
+    ** is because the cinfo struct contains the read buffer, and the read
+    ** buffer may (and almost always does) contain the end of one image and
+    ** the beginning of another.  If we throw away the read buffer then
+    ** we loose the beginning of the next image, and we're screwed.
+    ** Dave Scott (dhs), UofO, 7/19/95
+    */
+    if (fp == stdin) {
+        static int no_from_stdin = 0;
+        ++no_from_stdin;
+    } else {
+        /* This is an important step since it will release a good deal
+           of memory.
+        */
+        jpeg_destroy_decompress(&cinfo);
+    }
+
+    /* After finish_decompress, we can close the input file.  Here we
+       postpone it until after no more JPEG errors are possible, so as
+       to simplify the setjmp error logic above.  (Actually, I don't
+       think that jpeg_destroy can do an error exit, but why assume
+       anything...)
+    */
+  
+    /* At this point you may want to check to see whether any corrupt-data
+     * warnings occurred (test whether jerr.pub.num_warnings is nonzero).
+     * If you prefer to treat corrupt data as a fatal error, override the
+     * error handler's emit_message method to call error_exit on a warning.
+     */
+}
+
+
+
+/*
+ * SOME FINE POINTS:
+ *
+ * In the above loop, we ignored the return value of jpeg_read_scanlines,
+ * which is the number of scanlines actually read.  We could get away with
+ * this for the same reasons discussed in the compression example.  Actually
+ * there is one perfectly normal situation in which jpeg_read_scanlines may
+ * return fewer lines than you asked for: at the bottom of the image.  But the
+ * loop above can't ask for more lines than there are in the image since it
+ * reads only one line at a time.
+ *
+ * In some high-speed operating modes, some data copying can be saved by
+ * making the buffer passed to jpeg_read_scanlines be cinfo.rec_outbuf_height
+ * lines high (or a multiple thereof).  This will usually be 1, 2, or 4 lines.
+ *
+ * To decompress multiple images, you can repeat the whole sequence, or you
+ * can keep the JPEG object around and just repeat steps 2-7.  This will
+ * save a little bit of startup/shutdown time.
+ *
+ * As with compression, some operating modes may require temporary files.
+ * On some systems you may need to set up a signal handler to ensure that
+ * temporary files are deleted if the program is interrupted.
+ *
+ * Scanlines are returned in the same order as they appear in the JPEG file,
+ * which is standardly top-to-bottom.  If you must have data supplied
+ * bottom-to-top, you can use one of the virtual arrays provided by the
+ * JPEG memory manager to invert the data.  See wrrle.c for an example.
+ */
+
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
diff --git a/converter/ppm/ppmtompeg/jrevdct.c b/converter/ppm/ppmtompeg/jrevdct.c
new file mode 100644
index 00000000..2e99a67a
--- /dev/null
+++ b/converter/ppm/ppmtompeg/jrevdct.c
@@ -0,0 +1,1278 @@
+/*
+ * jrevdct.c
+ *
+ * Copyright (C) 1991, 1992, Thomas G. Lane.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file contains the basic inverse-DCT transformation subroutine.
+ *
+ * This implementation is based on an algorithm described in
+ *   C. Loeffler, A. Ligtenberg and G. Moschytz, "Practical Fast 1-D DCT
+ *   Algorithms with 11 Multiplications", Proc. Int'l. Conf. on Acoustics,
+ *   Speech, and Signal Processing 1989 (ICASSP '89), pp. 988-991.
+ * The primary algorithm described there uses 11 multiplies and 29 adds.
+ * We use their alternate method with 12 multiplies and 32 adds.
+ * The advantage of this method is that no data path contains more than one
+ * multiplication; this allows a very simple and accurate implementation in
+ * scaled fixed-point arithmetic, with a minimal number of shifts.
+ * 
+ * I've made lots of modifications to attempt to take advantage of the
+ * sparse nature of the DCT matrices we're getting.  Although the logic
+ * is cumbersome, it's straightforward and the resulting code is much
+ * faster.
+ *
+ * A better way to do this would be to pass in the DCT block as a sparse
+ * matrix, perhaps with the difference cases encoded.
+ */
+
+#include <memory.h>
+#include "all.h"
+#include "ansi.h"
+#include "dct.h"
+
+
+#define CONST_BITS 13
+
+/*
+ * This routine is specialized to the case DCTSIZE = 8.
+ */
+
+#if DCTSIZE != 8
+  Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */
+#endif
+
+
+/*
+ * A 2-D IDCT can be done by 1-D IDCT on each row followed by 1-D IDCT
+ * on each column.  Direct algorithms are also available, but they are
+ * much more complex and seem not to be any faster when reduced to code.
+ *
+ * The poop on this scaling stuff is as follows:
+ *
+ * Each 1-D IDCT step produces outputs which are a factor of sqrt(N)
+ * larger than the true IDCT outputs.  The final outputs are therefore
+ * a factor of N larger than desired; since N=8 this can be cured by
+ * a simple right shift at the end of the algorithm.  The advantage of
+ * this arrangement is that we save two multiplications per 1-D IDCT,
+ * because the y0 and y4 inputs need not be divided by sqrt(N).
+ *
+ * We have to do addition and subtraction of the integer inputs, which
+ * is no problem, and multiplication by fractional constants, which is
+ * a problem to do in integer arithmetic.  We multiply all the constants
+ * by CONST_SCALE and convert them to integer constants (thus retaining
+ * CONST_BITS bits of precision in the constants).  After doing a
+ * multiplication we have to divide the product by CONST_SCALE, with proper
+ * rounding, to produce the correct output.  This division can be done
+ * cheaply as a right shift of CONST_BITS bits.  We postpone shifting
+ * as long as possible so that partial sums can be added together with
+ * full fractional precision.
+ *
+ * The outputs of the first pass are scaled up by PASS1_BITS bits so that
+ * they are represented to better-than-integral precision.  These outputs
+ * require BITS_IN_JSAMPLE + PASS1_BITS + 3 bits; this fits in a 16-bit word
+ * with the recommended scaling.  (To scale up 12-bit sample data further, an
+ * intermediate int32 array would be needed.)
+ *
+ * To avoid overflow of the 32-bit intermediate results in pass 2, we must
+ * have BITS_IN_JSAMPLE + CONST_BITS + PASS1_BITS <= 26.  Error analysis
+ * shows that the values given below are the most effective.
+ */
+
+#ifdef EIGHT_BIT_SAMPLES
+#define PASS1_BITS  2
+#else
+#define PASS1_BITS  1		/* lose a little precision to avoid overflow */
+#endif
+
+#define ONE	((int32) 1)
+
+#define CONST_SCALE (ONE << CONST_BITS)
+
+/* Convert a positive real constant to an integer scaled by CONST_SCALE.
+ * IMPORTANT: if your compiler doesn't do this arithmetic at compile time,
+ * you will pay a significant penalty in run time.  In that case, figure
+ * the correct integer constant values and insert them by hand.
+ */
+
+/* Actually FIX is no longer used, we precomputed them all */
+#define FIX(x)	((int32) ((x) * CONST_SCALE + 0.5)) 
+
+/* Descale and correctly round an int32 value that's scaled by N bits.
+ * We assume RIGHT_SHIFT rounds towards minus infinity, so adding
+ * the fudge factor is correct for either sign of X.
+ */
+
+#define DESCALE(x,n)  RIGHT_SHIFT((x) + (ONE << ((n)-1)), n)
+
+/* Multiply an int32 variable by an int32 constant to yield an int32 result.
+ * For 8-bit samples with the recommended scaling, all the variable
+ * and constant values involved are no more than 16 bits wide, so a
+ * 16x16->32 bit multiply can be used instead of a full 32x32 multiply;
+ * this provides a useful speedup on many machines.
+ * There is no way to specify a 16x16->32 multiply in portable C, but
+ * some C compilers will do the right thing if you provide the correct
+ * combination of casts.
+ * NB: for 12-bit samples, a full 32-bit multiplication will be needed.
+ */
+
+#ifdef EIGHT_BIT_SAMPLES
+#ifdef SHORTxSHORT_32		/* may work if 'int' is 32 bits */
+#define MULTIPLY(var,const)  (((INT16) (var)) * ((INT16) (const)))
+#endif
+#ifdef SHORTxLCONST_32		/* known to work with Microsoft C 6.0 */
+#define MULTIPLY(var,const)  (((INT16) (var)) * ((int32) (const)))
+#endif
+#endif
+
+#ifndef MULTIPLY		/* default definition */
+#define MULTIPLY(var,const)  ((var) * (const))
+#endif
+
+
+/* 
+  Unlike our decoder where we approximate the FIXes, we need to use exact
+ones here or successive P-frames will drift too much with Reference frame coding 
+*/
+#define FIX_0_211164243 1730
+#define FIX_0_275899380 2260
+#define FIX_0_298631336 2446
+#define FIX_0_390180644 3196
+#define FIX_0_509795579 4176
+#define FIX_0_541196100 4433
+#define FIX_0_601344887 4926
+#define FIX_0_765366865 6270
+#define FIX_0_785694958 6436
+#define FIX_0_899976223 7373
+#define FIX_1_061594337 8697
+#define FIX_1_111140466 9102
+#define FIX_1_175875602 9633
+#define FIX_1_306562965 10703
+#define FIX_1_387039845 11363
+#define FIX_1_451774981 11893
+#define FIX_1_501321110 12299
+#define FIX_1_662939225 13623
+#define FIX_1_847759065 15137
+#define FIX_1_961570560 16069
+#define FIX_2_053119869 16819
+#define FIX_2_172734803 17799
+#define FIX_2_562915447 20995
+#define FIX_3_072711026 25172
+
+/*
+  Switch on reverse_dct choices
+*/
+void reference_rev_dct _ANSI_ARGS_((int16 *block));
+void mpeg_jrevdct_quick _ANSI_ARGS_((int16 *block));
+void init_idctref _ANSI_ARGS_((void));
+
+extern boolean pureDCT;
+
+void
+mpeg_jrevdct(data)
+    DCTBLOCK data;
+{
+  if (pureDCT) reference_rev_dct(data);
+  else mpeg_jrevdct_quick(data);
+}
+
+/*
+ * Perform the inverse DCT on one block of coefficients.
+ */
+
+void
+mpeg_jrevdct_quick(data)
+    DCTBLOCK data;
+{
+  int32 tmp0, tmp1, tmp2, tmp3;
+  int32 tmp10, tmp11, tmp12, tmp13;
+  int32 z1, z2, z3, z4, z5;
+  int32 d0, d1, d2, d3, d4, d5, d6, d7;
+  register DCTELEM *dataptr;
+  int rowctr;
+  SHIFT_TEMPS
+   
+  /* Pass 1: process rows. */
+  /* Note results are scaled up by sqrt(8) compared to a true IDCT; */
+  /* furthermore, we scale the results by 2**PASS1_BITS. */
+
+  dataptr = data;
+
+  for (rowctr = DCTSIZE-1; rowctr >= 0; rowctr--) {
+    /* Due to quantization, we will usually find that many of the input
+     * coefficients are zero, especially the AC terms.  We can exploit this
+     * by short-circuiting the IDCT calculation for any row in which all
+     * the AC terms are zero.  In that case each output is equal to the
+     * DC coefficient (with scale factor as needed).
+     * With typical images and quantization tables, half or more of the
+     * row DCT calculations can be simplified this way.
+     */
+
+    register int *idataptr = (int*)dataptr;
+    d0 = dataptr[0];
+    d1 = dataptr[1];
+    if ((d1 == 0) && (idataptr[1] | idataptr[2] | idataptr[3]) == 0) {
+      /* AC terms all zero */
+      if (d0) {
+	  /* Compute a 32 bit value to assign. */
+	  DCTELEM dcval = (DCTELEM) (d0 << PASS1_BITS);
+	  register int v = (dcval & 0xffff) | ((dcval << 16) & 0xffff0000);
+	  
+	  idataptr[0] = v;
+	  idataptr[1] = v;
+	  idataptr[2] = v;
+	  idataptr[3] = v;
+      }
+      
+      dataptr += DCTSIZE;	/* advance pointer to next row */
+      continue;
+    }
+    d2 = dataptr[2];
+    d3 = dataptr[3];
+    d4 = dataptr[4];
+    d5 = dataptr[5];
+    d6 = dataptr[6];
+    d7 = dataptr[7];
+
+    /* Even part: reverse the even part of the forward DCT. */
+    /* The rotator is sqrt(2)*c(-6). */
+{
+    if (d6) {
+	if (d4) {
+	    if (d2) {
+		if (d0) {
+		    /* d0 != 0, d2 != 0, d4 != 0, d6 != 0 */
+		    z1 = MULTIPLY(d2 + d6, FIX_0_541196100);
+		    tmp2 = z1 + MULTIPLY(-d6, FIX_1_847759065);
+		    tmp3 = z1 + MULTIPLY(d2, FIX_0_765366865);
+
+		    tmp0 = (d0 + d4) << CONST_BITS;
+		    tmp1 = (d0 - d4) << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp1 + tmp2;
+		    tmp12 = tmp1 - tmp2;
+		} else {
+		    /* d0 == 0, d2 != 0, d4 != 0, d6 != 0 */
+		    z1 = MULTIPLY(d2 + d6, FIX_0_541196100);
+		    tmp2 = z1 + MULTIPLY(-d6, FIX_1_847759065);
+		    tmp3 = z1 + MULTIPLY(d2, FIX_0_765366865);
+
+		    tmp0 = d4 << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp2 - tmp0;
+		    tmp12 = -(tmp0 + tmp2);
+		}
+	    } else {
+		if (d0) {
+		    /* d0 != 0, d2 == 0, d4 != 0, d6 != 0 */
+		    tmp2 = MULTIPLY(-d6, FIX_1_306562965);
+		    tmp3 = MULTIPLY(d6, FIX_0_541196100);
+
+		    tmp0 = (d0 + d4) << CONST_BITS;
+		    tmp1 = (d0 - d4) << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp1 + tmp2;
+		    tmp12 = tmp1 - tmp2;
+		} else {
+		    /* d0 == 0, d2 == 0, d4 != 0, d6 != 0 */
+		    tmp2 = MULTIPLY(-d6, FIX_1_306562965);
+		    tmp3 = MULTIPLY(d6, FIX_0_541196100);
+
+		    tmp0 = d4 << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp2 - tmp0;
+		    tmp12 = -(tmp0 + tmp2);
+		}
+	    }
+	} else {
+	    if (d2) {
+		if (d0) {
+		    /* d0 != 0, d2 != 0, d4 == 0, d6 != 0 */
+		    z1 = MULTIPLY(d2 + d6, FIX_0_541196100);
+		    tmp2 = z1 + MULTIPLY(-d6, FIX_1_847759065);
+		    tmp3 = z1 + MULTIPLY(d2, FIX_0_765366865);
+
+		    tmp0 = d0 << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp0 + tmp2;
+		    tmp12 = tmp0 - tmp2;
+		} else {
+		    /* d0 == 0, d2 != 0, d4 == 0, d6 != 0 */
+		    z1 = MULTIPLY(d2 + d6, FIX_0_541196100);
+		    tmp2 = z1 + MULTIPLY(-d6, FIX_1_847759065);
+		    tmp3 = z1 + MULTIPLY(d2, FIX_0_765366865);
+
+		    tmp10 = tmp3;
+		    tmp13 = -tmp3;
+		    tmp11 = tmp2;
+		    tmp12 = -tmp2;
+		}
+	    } else {
+		if (d0) {
+		    /* d0 != 0, d2 == 0, d4 == 0, d6 != 0 */
+		    tmp2 = MULTIPLY(-d6, FIX_1_306562965);
+		    tmp3 = MULTIPLY(d6, FIX_0_541196100);
+
+		    tmp0 = d0 << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp0 + tmp2;
+		    tmp12 = tmp0 - tmp2;
+		} else {
+		    /* d0 == 0, d2 == 0, d4 == 0, d6 != 0 */
+		    tmp2 = MULTIPLY(-d6, FIX_1_306562965);
+		    tmp3 = MULTIPLY(d6, FIX_0_541196100);
+
+		    tmp10 = tmp3;
+		    tmp13 = -tmp3;
+		    tmp11 = tmp2;
+		    tmp12 = -tmp2;
+		}
+	    }
+	}
+    } else {
+	if (d4) {
+	    if (d2) {
+		if (d0) {
+		    /* d0 != 0, d2 != 0, d4 != 0, d6 == 0 */
+		    tmp2 = MULTIPLY(d2, FIX_0_541196100);
+		    tmp3 = MULTIPLY(d2, FIX_1_306562965);
+
+		    tmp0 = (d0 + d4) << CONST_BITS;
+		    tmp1 = (d0 - d4) << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp1 + tmp2;
+		    tmp12 = tmp1 - tmp2;
+		} else {
+		    /* d0 == 0, d2 != 0, d4 != 0, d6 == 0 */
+		    tmp2 = MULTIPLY(d2, FIX_0_541196100);
+		    tmp3 = MULTIPLY(d2, FIX_1_306562965);
+
+		    tmp0 = d4 << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp2 - tmp0;
+		    tmp12 = -(tmp0 + tmp2);
+		}
+	    } else {
+		if (d0) {
+		    /* d0 != 0, d2 == 0, d4 != 0, d6 == 0 */
+		    tmp10 = tmp13 = (d0 + d4) << CONST_BITS;
+		    tmp11 = tmp12 = (d0 - d4) << CONST_BITS;
+		} else {
+		    /* d0 == 0, d2 == 0, d4 != 0, d6 == 0 */
+		    tmp10 = tmp13 = d4 << CONST_BITS;
+		    tmp11 = tmp12 = -tmp10;
+		}
+	    }
+	} else {
+	    if (d2) {
+		if (d0) {
+		    /* d0 != 0, d2 != 0, d4 == 0, d6 == 0 */
+		    tmp2 = MULTIPLY(d2, FIX_0_541196100);
+		    tmp3 = MULTIPLY(d2, FIX_1_306562965);
+
+		    tmp0 = d0 << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp0 + tmp2;
+		    tmp12 = tmp0 - tmp2;
+		} else {
+		    /* d0 == 0, d2 != 0, d4 == 0, d6 == 0 */
+		    tmp2 = MULTIPLY(d2, FIX_0_541196100);
+		    tmp3 = MULTIPLY(d2, FIX_1_306562965);
+
+		    tmp10 = tmp3;
+		    tmp13 = -tmp3;
+		    tmp11 = tmp2;
+		    tmp12 = -tmp2;
+		}
+	    } else {
+		if (d0) {
+		    /* d0 != 0, d2 == 0, d4 == 0, d6 == 0 */
+		    tmp10 = tmp13 = tmp11 = tmp12 = d0 << CONST_BITS;
+		} else {
+		    /* d0 == 0, d2 == 0, d4 == 0, d6 == 0 */
+		    tmp10 = tmp13 = tmp11 = tmp12 = 0;
+		}
+	    }
+	}
+      }
+
+    /* Odd part per figure 8; the matrix is unitary and hence its
+     * transpose is its inverse.  i0..i3 are y7,y5,y3,y1 respectively.
+     */
+
+    if (d7) {
+	if (d5) {
+	    if (d3) {
+		if (d1) {
+		    /* d1 != 0, d3 != 0, d5 != 0, d7 != 0 */
+		    z1 = d7 + d1;
+		    z2 = d5 + d3;
+		    z3 = d7 + d3;
+		    z4 = d5 + d1;
+		    z5 = MULTIPLY(z3 + z4, FIX_1_175875602);
+		    
+		    tmp0 = MULTIPLY(d7, FIX_0_298631336); 
+		    tmp1 = MULTIPLY(d5, FIX_2_053119869);
+		    tmp2 = MULTIPLY(d3, FIX_3_072711026);
+		    tmp3 = MULTIPLY(d1, FIX_1_501321110);
+		    z1 = MULTIPLY(-z1, FIX_0_899976223);
+		    z2 = MULTIPLY(-z2, FIX_2_562915447);
+		    z3 = MULTIPLY(-z3, FIX_1_961570560);
+		    z4 = MULTIPLY(-z4, FIX_0_390180644);
+		    
+		    z3 += z5;
+		    z4 += z5;
+		    
+		    tmp0 += z1 + z3;
+		    tmp1 += z2 + z4;
+		    tmp2 += z2 + z3;
+		    tmp3 += z1 + z4;
+		} else {
+		    /* d1 == 0, d3 != 0, d5 != 0, d7 != 0 */
+		    z2 = d5 + d3;
+		    z3 = d7 + d3;
+		    z5 = MULTIPLY(z3 + d5, FIX_1_175875602);
+		    
+		    tmp0 = MULTIPLY(d7, FIX_0_298631336); 
+		    tmp1 = MULTIPLY(d5, FIX_2_053119869);
+		    tmp2 = MULTIPLY(d3, FIX_3_072711026);
+		    z1 = MULTIPLY(-d7, FIX_0_899976223);
+		    z2 = MULTIPLY(-z2, FIX_2_562915447);
+		    z3 = MULTIPLY(-z3, FIX_1_961570560);
+		    z4 = MULTIPLY(-d5, FIX_0_390180644);
+		    
+		    z3 += z5;
+		    z4 += z5;
+		    
+		    tmp0 += z1 + z3;
+		    tmp1 += z2 + z4;
+		    tmp2 += z2 + z3;
+		    tmp3 = z1 + z4;
+		}
+	    } else {
+		if (d1) {
+		    /* d1 != 0, d3 == 0, d5 != 0, d7 != 0 */
+		    z1 = d7 + d1;
+		    z4 = d5 + d1;
+		    z5 = MULTIPLY(d7 + z4, FIX_1_175875602);
+		    
+		    tmp0 = MULTIPLY(d7, FIX_0_298631336); 
+		    tmp1 = MULTIPLY(d5, FIX_2_053119869);
+		    tmp3 = MULTIPLY(d1, FIX_1_501321110);
+		    z1 = MULTIPLY(-z1, FIX_0_899976223);
+		    z2 = MULTIPLY(-d5, FIX_2_562915447);
+		    z3 = MULTIPLY(-d7, FIX_1_961570560);
+		    z4 = MULTIPLY(-z4, FIX_0_390180644);
+		    
+		    z3 += z5;
+		    z4 += z5;
+		    
+		    tmp0 += z1 + z3;
+		    tmp1 += z2 + z4;
+		    tmp2 = z2 + z3;
+		    tmp3 += z1 + z4;
+		} else {
+		    /* d1 == 0, d3 == 0, d5 != 0, d7 != 0 */
+		    tmp0 = MULTIPLY(-d7, FIX_0_601344887); 
+		    z1 = MULTIPLY(-d7, FIX_0_899976223);
+		    z3 = MULTIPLY(-d7, FIX_1_961570560);
+		    tmp1 = MULTIPLY(-d5, FIX_0_509795579);
+		    z2 = MULTIPLY(-d5, FIX_2_562915447);
+		    z4 = MULTIPLY(-d5, FIX_0_390180644);
+		    z5 = MULTIPLY(d5 + d7, FIX_1_175875602);
+		    
+		    z3 += z5;
+		    z4 += z5;
+		    
+		    tmp0 += z3;
+		    tmp1 += z4;
+		    tmp2 = z2 + z3;
+		    tmp3 = z1 + z4;
+		}
+	    }
+	} else {
+	    if (d3) {
+		if (d1) {
+		    /* d1 != 0, d3 != 0, d5 == 0, d7 != 0 */
+		    z1 = d7 + d1;
+		    z3 = d7 + d3;
+		    z5 = MULTIPLY(z3 + d1, FIX_1_175875602);
+		    
+		    tmp0 = MULTIPLY(d7, FIX_0_298631336); 
+		    tmp2 = MULTIPLY(d3, FIX_3_072711026);
+		    tmp3 = MULTIPLY(d1, FIX_1_501321110);
+		    z1 = MULTIPLY(-z1, FIX_0_899976223);
+		    z2 = MULTIPLY(-d3, FIX_2_562915447);
+		    z3 = MULTIPLY(-z3, FIX_1_961570560);
+		    z4 = MULTIPLY(-d1, FIX_0_390180644);
+		    
+		    z3 += z5;
+		    z4 += z5;
+		    
+		    tmp0 += z1 + z3;
+		    tmp1 = z2 + z4;
+		    tmp2 += z2 + z3;
+		    tmp3 += z1 + z4;
+		} else {
+		    /* d1 == 0, d3 != 0, d5 == 0, d7 != 0 */
+		    z3 = d7 + d3;
+		    
+		    tmp0 = MULTIPLY(-d7, FIX_0_601344887); 
+		    z1 = MULTIPLY(-d7, FIX_0_899976223);
+		    tmp2 = MULTIPLY(d3, FIX_0_509795579);
+		    z2 = MULTIPLY(-d3, FIX_2_562915447);
+		    z5 = MULTIPLY(z3, FIX_1_175875602);
+		    z3 = MULTIPLY(-z3, FIX_0_785694958);
+		    
+		    tmp0 += z3;
+		    tmp1 = z2 + z5;
+		    tmp2 += z3;
+		    tmp3 = z1 + z5;
+		}
+	    } else {
+		if (d1) {
+		    /* d1 != 0, d3 == 0, d5 == 0, d7 != 0 */
+		    z1 = d7 + d1;
+		    z5 = MULTIPLY(z1, FIX_1_175875602);
+
+		    z1 = MULTIPLY(z1, FIX_0_275899380);
+		    z3 = MULTIPLY(-d7, FIX_1_961570560);
+		    tmp0 = MULTIPLY(-d7, FIX_1_662939225); 
+		    z4 = MULTIPLY(-d1, FIX_0_390180644);
+		    tmp3 = MULTIPLY(d1, FIX_1_111140466);
+
+		    tmp0 += z1;
+		    tmp1 = z4 + z5;
+		    tmp2 = z3 + z5;
+		    tmp3 += z1;
+		} else {
+		    /* d1 == 0, d3 == 0, d5 == 0, d7 != 0 */
+		    tmp0 = MULTIPLY(-d7, FIX_1_387039845);
+		    tmp1 = MULTIPLY(d7, FIX_1_175875602);
+		    tmp2 = MULTIPLY(-d7, FIX_0_785694958);
+		    tmp3 = MULTIPLY(d7, FIX_0_275899380);
+		}
+	    }
+	}
+    } else {
+	if (d5) {
+	    if (d3) {
+		if (d1) {
+		    /* d1 != 0, d3 != 0, d5 != 0, d7 == 0 */
+		    z2 = d5 + d3;
+		    z4 = d5 + d1;
+		    z5 = MULTIPLY(d3 + z4, FIX_1_175875602);
+		    
+		    tmp1 = MULTIPLY(d5, FIX_2_053119869);
+		    tmp2 = MULTIPLY(d3, FIX_3_072711026);
+		    tmp3 = MULTIPLY(d1, FIX_1_501321110);
+		    z1 = MULTIPLY(-d1, FIX_0_899976223);
+		    z2 = MULTIPLY(-z2, FIX_2_562915447);
+		    z3 = MULTIPLY(-d3, FIX_1_961570560);
+		    z4 = MULTIPLY(-z4, FIX_0_390180644);
+		    
+		    z3 += z5;
+		    z4 += z5;
+		    
+		    tmp0 = z1 + z3;
+		    tmp1 += z2 + z4;
+		    tmp2 += z2 + z3;
+		    tmp3 += z1 + z4;
+		} else {
+		    /* d1 == 0, d3 != 0, d5 != 0, d7 == 0 */
+		    z2 = d5 + d3;
+		    
+		    z5 = MULTIPLY(z2, FIX_1_175875602);
+		    tmp1 = MULTIPLY(d5, FIX_1_662939225);
+		    z4 = MULTIPLY(-d5, FIX_0_390180644);
+		    z2 = MULTIPLY(-z2, FIX_1_387039845);
+		    tmp2 = MULTIPLY(d3, FIX_1_111140466);
+		    z3 = MULTIPLY(-d3, FIX_1_961570560);
+		    
+		    tmp0 = z3 + z5;
+		    tmp1 += z2;
+		    tmp2 += z2;
+		    tmp3 = z4 + z5;
+		}
+	    } else {
+		if (d1) {
+		    /* d1 != 0, d3 == 0, d5 != 0, d7 == 0 */
+		    z4 = d5 + d1;
+		    
+		    z5 = MULTIPLY(z4, FIX_1_175875602);
+		    z1 = MULTIPLY(-d1, FIX_0_899976223);
+		    tmp3 = MULTIPLY(d1, FIX_0_601344887);
+		    tmp1 = MULTIPLY(-d5, FIX_0_509795579);
+		    z2 = MULTIPLY(-d5, FIX_2_562915447);
+		    z4 = MULTIPLY(z4, FIX_0_785694958);
+		    
+		    tmp0 = z1 + z5;
+		    tmp1 += z4;
+		    tmp2 = z2 + z5;
+		    tmp3 += z4;
+		} else {
+		    /* d1 == 0, d3 == 0, d5 != 0, d7 == 0 */
+		    tmp0 = MULTIPLY(d5, FIX_1_175875602);
+		    tmp1 = MULTIPLY(d5, FIX_0_275899380);
+		    tmp2 = MULTIPLY(-d5, FIX_1_387039845);
+		    tmp3 = MULTIPLY(d5, FIX_0_785694958);
+		}
+	    }
+	} else {
+	    if (d3) {
+		if (d1) {
+		    /* d1 != 0, d3 != 0, d5 == 0, d7 == 0 */
+		    z5 = d1 + d3;
+		    tmp3 = MULTIPLY(d1, FIX_0_211164243);
+		    tmp2 = MULTIPLY(-d3, FIX_1_451774981);
+		    z1 = MULTIPLY(d1, FIX_1_061594337);
+		    z2 = MULTIPLY(-d3, FIX_2_172734803);
+		    z4 = MULTIPLY(z5, FIX_0_785694958);
+		    z5 = MULTIPLY(z5, FIX_1_175875602);
+		    
+		    tmp0 = z1 - z4;
+		    tmp1 = z2 + z4;
+		    tmp2 += z5;
+		    tmp3 += z5;
+		} else {
+		    /* d1 == 0, d3 != 0, d5 == 0, d7 == 0 */
+		    tmp0 = MULTIPLY(-d3, FIX_0_785694958);
+		    tmp1 = MULTIPLY(-d3, FIX_1_387039845);
+		    tmp2 = MULTIPLY(-d3, FIX_0_275899380);
+		    tmp3 = MULTIPLY(d3, FIX_1_175875602);
+		}
+	    } else {
+		if (d1) {
+		    /* d1 != 0, d3 == 0, d5 == 0, d7 == 0 */
+		    tmp0 = MULTIPLY(d1, FIX_0_275899380);
+		    tmp1 = MULTIPLY(d1, FIX_0_785694958);
+		    tmp2 = MULTIPLY(d1, FIX_1_175875602);
+		    tmp3 = MULTIPLY(d1, FIX_1_387039845);
+		} else {
+		    /* d1 == 0, d3 == 0, d5 == 0, d7 == 0 */
+		    tmp0 = tmp1 = tmp2 = tmp3 = 0;
+		}
+	    }
+	}
+    }
+}
+    /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */
+
+    dataptr[0] = (DCTELEM) DESCALE(tmp10 + tmp3, CONST_BITS-PASS1_BITS);
+    dataptr[7] = (DCTELEM) DESCALE(tmp10 - tmp3, CONST_BITS-PASS1_BITS);
+    dataptr[1] = (DCTELEM) DESCALE(tmp11 + tmp2, CONST_BITS-PASS1_BITS);
+    dataptr[6] = (DCTELEM) DESCALE(tmp11 - tmp2, CONST_BITS-PASS1_BITS);
+    dataptr[2] = (DCTELEM) DESCALE(tmp12 + tmp1, CONST_BITS-PASS1_BITS);
+    dataptr[5] = (DCTELEM) DESCALE(tmp12 - tmp1, CONST_BITS-PASS1_BITS);
+    dataptr[3] = (DCTELEM) DESCALE(tmp13 + tmp0, CONST_BITS-PASS1_BITS);
+    dataptr[4] = (DCTELEM) DESCALE(tmp13 - tmp0, CONST_BITS-PASS1_BITS);
+
+    dataptr += DCTSIZE;		/* advance pointer to next row */
+  }
+
+  /* Pass 2: process columns. */
+  /* Note that we must descale the results by a factor of 8 == 2**3, */
+  /* and also undo the PASS1_BITS scaling. */
+
+  dataptr = data;
+  for (rowctr = DCTSIZE-1; rowctr >= 0; rowctr--) {
+    /* Columns of zeroes can be exploited in the same way as we did with rows.
+     * However, the row calculation has created many nonzero AC terms, so the
+     * simplification applies less often (typically 5% to 10% of the time).
+     * On machines with very fast multiplication, it's possible that the
+     * test takes more time than it's worth.  In that case this section
+     * may be commented out.
+     */
+
+    d0 = dataptr[DCTSIZE*0];
+    d1 = dataptr[DCTSIZE*1];
+    d2 = dataptr[DCTSIZE*2];
+    d3 = dataptr[DCTSIZE*3];
+    d4 = dataptr[DCTSIZE*4];
+    d5 = dataptr[DCTSIZE*5];
+    d6 = dataptr[DCTSIZE*6];
+    d7 = dataptr[DCTSIZE*7];
+
+    /* Even part: reverse the even part of the forward DCT. */
+    /* The rotator is sqrt(2)*c(-6). */
+    if (d6) {
+	if (d4) {
+	    if (d2) {
+		if (d0) {
+		    /* d0 != 0, d2 != 0, d4 != 0, d6 != 0 */
+		    z1 = MULTIPLY(d2 + d6, FIX_0_541196100);
+		    tmp2 = z1 + MULTIPLY(-d6, FIX_1_847759065);
+		    tmp3 = z1 + MULTIPLY(d2, FIX_0_765366865);
+
+		    tmp0 = (d0 + d4) << CONST_BITS;
+		    tmp1 = (d0 - d4) << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp1 + tmp2;
+		    tmp12 = tmp1 - tmp2;
+		} else {
+		    /* d0 == 0, d2 != 0, d4 != 0, d6 != 0 */
+		    z1 = MULTIPLY(d2 + d6, FIX_0_541196100);
+		    tmp2 = z1 + MULTIPLY(-d6, FIX_1_847759065);
+		    tmp3 = z1 + MULTIPLY(d2, FIX_0_765366865);
+
+		    tmp0 = d4 << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp2 - tmp0;
+		    tmp12 = -(tmp0 + tmp2);
+		}
+	    } else {
+		if (d0) {
+		    /* d0 != 0, d2 == 0, d4 != 0, d6 != 0 */
+		    tmp2 = MULTIPLY(-d6, FIX_1_306562965);
+		    tmp3 = MULTIPLY(d6, FIX_0_541196100);
+
+		    tmp0 = (d0 + d4) << CONST_BITS;
+		    tmp1 = (d0 - d4) << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp1 + tmp2;
+		    tmp12 = tmp1 - tmp2;
+		} else {
+		    /* d0 == 0, d2 == 0, d4 != 0, d6 != 0 */
+		    tmp2 = MULTIPLY(-d6, FIX_1_306562965);
+		    tmp3 = MULTIPLY(d6, FIX_0_541196100);
+
+		    tmp0 = d4 << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp2 - tmp0;
+		    tmp12 = -(tmp0 + tmp2);
+		}
+	    }
+	} else {
+	    if (d2) {
+		if (d0) {
+		    /* d0 != 0, d2 != 0, d4 == 0, d6 != 0 */
+		    z1 = MULTIPLY(d2 + d6, FIX_0_541196100);
+		    tmp2 = z1 + MULTIPLY(-d6, FIX_1_847759065);
+		    tmp3 = z1 + MULTIPLY(d2, FIX_0_765366865);
+
+		    tmp0 = d0 << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp0 + tmp2;
+		    tmp12 = tmp0 - tmp2;
+		} else {
+		    /* d0 == 0, d2 != 0, d4 == 0, d6 != 0 */
+		    z1 = MULTIPLY(d2 + d6, FIX_0_541196100);
+		    tmp2 = z1 + MULTIPLY(-d6, FIX_1_847759065);
+		    tmp3 = z1 + MULTIPLY(d2, FIX_0_765366865);
+
+		    tmp10 = tmp3;
+		    tmp13 = -tmp3;
+		    tmp11 = tmp2;
+		    tmp12 = -tmp2;
+		}
+	    } else {
+		if (d0) {
+		    /* d0 != 0, d2 == 0, d4 == 0, d6 != 0 */
+		    tmp2 = MULTIPLY(-d6, FIX_1_306562965);
+		    tmp3 = MULTIPLY(d6, FIX_0_541196100);
+
+		    tmp0 = d0 << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp0 + tmp2;
+		    tmp12 = tmp0 - tmp2;
+		} else {
+		    /* d0 == 0, d2 == 0, d4 == 0, d6 != 0 */
+		    tmp2 = MULTIPLY(-d6, FIX_1_306562965);
+		    tmp3 = MULTIPLY(d6, FIX_0_541196100);
+
+		    tmp10 = tmp3;
+		    tmp13 = -tmp3;
+		    tmp11 = tmp2;
+		    tmp12 = -tmp2;
+		}
+	    }
+	}
+    } else {
+	if (d4) {
+	    if (d2) {
+		if (d0) {
+		    /* d0 != 0, d2 != 0, d4 != 0, d6 == 0 */
+		    tmp2 = MULTIPLY(d2, FIX_0_541196100);
+		    tmp3 = MULTIPLY(d2, FIX_1_306562965);
+
+		    tmp0 = (d0 + d4) << CONST_BITS;
+		    tmp1 = (d0 - d4) << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp1 + tmp2;
+		    tmp12 = tmp1 - tmp2;
+		} else {
+		    /* d0 == 0, d2 != 0, d4 != 0, d6 == 0 */
+		    tmp2 = MULTIPLY(d2, FIX_0_541196100);
+		    tmp3 = MULTIPLY(d2, FIX_1_306562965);
+
+		    tmp0 = d4 << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp2 - tmp0;
+		    tmp12 = -(tmp0 + tmp2);
+		}
+	    } else {
+		if (d0) {
+		    /* d0 != 0, d2 == 0, d4 != 0, d6 == 0 */
+		    tmp10 = tmp13 = (d0 + d4) << CONST_BITS;
+		    tmp11 = tmp12 = (d0 - d4) << CONST_BITS;
+		} else {
+		    /* d0 == 0, d2 == 0, d4 != 0, d6 == 0 */
+		    tmp10 = tmp13 = d4 << CONST_BITS;
+		    tmp11 = tmp12 = -tmp10;
+		}
+	    }
+	} else {
+	    if (d2) {
+		if (d0) {
+		    /* d0 != 0, d2 != 0, d4 == 0, d6 == 0 */
+		    tmp2 = MULTIPLY(d2, FIX_0_541196100);
+		    tmp3 = MULTIPLY(d2, FIX_1_306562965);
+
+		    tmp0 = d0 << CONST_BITS;
+
+		    tmp10 = tmp0 + tmp3;
+		    tmp13 = tmp0 - tmp3;
+		    tmp11 = tmp0 + tmp2;
+		    tmp12 = tmp0 - tmp2;
+		} else {
+		    /* d0 == 0, d2 != 0, d4 == 0, d6 == 0 */
+		    tmp2 = MULTIPLY(d2, FIX_0_541196100);
+		    tmp3 = MULTIPLY(d2, FIX_1_306562965);
+
+		    tmp10 = tmp3;
+		    tmp13 = -tmp3;
+		    tmp11 = tmp2;
+		    tmp12 = -tmp2;
+		}
+	    } else {
+		if (d0) {
+		    /* d0 != 0, d2 == 0, d4 == 0, d6 == 0 */
+		    tmp10 = tmp13 = tmp11 = tmp12 = d0 << CONST_BITS;
+		} else {
+		    /* d0 == 0, d2 == 0, d4 == 0, d6 == 0 */
+		    tmp10 = tmp13 = tmp11 = tmp12 = 0;
+		}
+	    }
+	}
+    }
+
+    /* Odd part per figure 8; the matrix is unitary and hence its
+     * transpose is its inverse.  i0..i3 are y7,y5,y3,y1 respectively.
+     */
+    if (d7) {
+	if (d5) {
+	    if (d3) {
+		if (d1) {
+		    /* d1 != 0, d3 != 0, d5 != 0, d7 != 0 */
+		    z1 = d7 + d1;
+		    z2 = d5 + d3;
+		    z3 = d7 + d3;
+		    z4 = d5 + d1;
+		    z5 = MULTIPLY(z3 + z4, FIX_1_175875602);
+		    
+		    tmp0 = MULTIPLY(d7, FIX_0_298631336); 
+		    tmp1 = MULTIPLY(d5, FIX_2_053119869);
+		    tmp2 = MULTIPLY(d3, FIX_3_072711026);
+		    tmp3 = MULTIPLY(d1, FIX_1_501321110);
+		    z1 = MULTIPLY(-z1, FIX_0_899976223);
+		    z2 = MULTIPLY(-z2, FIX_2_562915447);
+		    z3 = MULTIPLY(-z3, FIX_1_961570560);
+		    z4 = MULTIPLY(-z4, FIX_0_390180644);
+		    
+		    z3 += z5;
+		    z4 += z5;
+		    
+		    tmp0 += z1 + z3;
+		    tmp1 += z2 + z4;
+		    tmp2 += z2 + z3;
+		    tmp3 += z1 + z4;
+		} else {
+		    /* d1 == 0, d3 != 0, d5 != 0, d7 != 0 */
+		    z1 = d7;
+		    z2 = d5 + d3;
+		    z3 = d7 + d3;
+		    z5 = MULTIPLY(z3 + d5, FIX_1_175875602);
+		    
+		    tmp0 = MULTIPLY(d7, FIX_0_298631336); 
+		    tmp1 = MULTIPLY(d5, FIX_2_053119869);
+		    tmp2 = MULTIPLY(d3, FIX_3_072711026);
+		    z1 = MULTIPLY(-d7, FIX_0_899976223);
+		    z2 = MULTIPLY(-z2, FIX_2_562915447);
+		    z3 = MULTIPLY(-z3, FIX_1_961570560);
+		    z4 = MULTIPLY(-d5, FIX_0_390180644);
+		    
+		    z3 += z5;
+		    z4 += z5;
+		    
+		    tmp0 += z1 + z3;
+		    tmp1 += z2 + z4;
+		    tmp2 += z2 + z3;
+		    tmp3 = z1 + z4;
+		}
+	    } else {
+		if (d1) {
+		    /* d1 != 0, d3 == 0, d5 != 0, d7 != 0 */
+		    z1 = d7 + d1;
+		    z2 = d5;
+		    z3 = d7;
+		    z4 = d5 + d1;
+		    z5 = MULTIPLY(z3 + z4, FIX_1_175875602);
+		    
+		    tmp0 = MULTIPLY(d7, FIX_0_298631336); 
+		    tmp1 = MULTIPLY(d5, FIX_2_053119869);
+		    tmp3 = MULTIPLY(d1, FIX_1_501321110);
+		    z1 = MULTIPLY(-z1, FIX_0_899976223);
+		    z2 = MULTIPLY(-d5, FIX_2_562915447);
+		    z3 = MULTIPLY(-d7, FIX_1_961570560);
+		    z4 = MULTIPLY(-z4, FIX_0_390180644);
+		    
+		    z3 += z5;
+		    z4 += z5;
+		    
+		    tmp0 += z1 + z3;
+		    tmp1 += z2 + z4;
+		    tmp2 = z2 + z3;
+		    tmp3 += z1 + z4;
+		} else {
+		    /* d1 == 0, d3 == 0, d5 != 0, d7 != 0 */
+		    tmp0 = MULTIPLY(-d7, FIX_0_601344887); 
+		    z1 = MULTIPLY(-d7, FIX_0_899976223);
+		    z3 = MULTIPLY(-d7, FIX_1_961570560);
+		    tmp1 = MULTIPLY(-d5, FIX_0_509795579);
+		    z2 = MULTIPLY(-d5, FIX_2_562915447);
+		    z4 = MULTIPLY(-d5, FIX_0_390180644);
+		    z5 = MULTIPLY(d5 + d7, FIX_1_175875602);
+		    
+		    z3 += z5;
+		    z4 += z5;
+		    
+		    tmp0 += z3;
+		    tmp1 += z4;
+		    tmp2 = z2 + z3;
+		    tmp3 = z1 + z4;
+		}
+	    }
+	} else {
+	    if (d3) {
+		if (d1) {
+		    /* d1 != 0, d3 != 0, d5 == 0, d7 != 0 */
+		    z1 = d7 + d1;
+		    z3 = d7 + d3;
+		    z5 = MULTIPLY(z3 + d1, FIX_1_175875602);
+		    
+		    tmp0 = MULTIPLY(d7, FIX_0_298631336); 
+		    tmp2 = MULTIPLY(d3, FIX_3_072711026);
+		    tmp3 = MULTIPLY(d1, FIX_1_501321110);
+		    z1 = MULTIPLY(-z1, FIX_0_899976223);
+		    z2 = MULTIPLY(-d3, FIX_2_562915447);
+		    z3 = MULTIPLY(-z3, FIX_1_961570560);
+		    z4 = MULTIPLY(-d1, FIX_0_390180644);
+		    
+		    z3 += z5;
+		    z4 += z5;
+		    
+		    tmp0 += z1 + z3;
+		    tmp1 = z2 + z4;
+		    tmp2 += z2 + z3;
+		    tmp3 += z1 + z4;
+		} else {
+		    /* d1 == 0, d3 != 0, d5 == 0, d7 != 0 */
+		    z3 = d7 + d3;
+		    
+		    tmp0 = MULTIPLY(-d7, FIX_0_601344887); 
+		    z1 = MULTIPLY(-d7, FIX_0_899976223);
+		    tmp2 = MULTIPLY(d3, FIX_0_509795579);
+		    z2 = MULTIPLY(-d3, FIX_2_562915447);
+		    z5 = MULTIPLY(z3, FIX_1_175875602);
+		    z3 = MULTIPLY(-z3, FIX_0_785694958);
+		    
+		    tmp0 += z3;
+		    tmp1 = z2 + z5;
+		    tmp2 += z3;
+		    tmp3 = z1 + z5;
+		}
+	    } else {
+		if (d1) {
+		    /* d1 != 0, d3 == 0, d5 == 0, d7 != 0 */
+		    z1 = d7 + d1;
+		    z5 = MULTIPLY(z1, FIX_1_175875602);
+
+		    z1 = MULTIPLY(z1, FIX_0_275899380);
+		    z3 = MULTIPLY(-d7, FIX_1_961570560);
+		    tmp0 = MULTIPLY(-d7, FIX_1_662939225); 
+		    z4 = MULTIPLY(-d1, FIX_0_390180644);
+		    tmp3 = MULTIPLY(d1, FIX_1_111140466);
+
+		    tmp0 += z1;
+		    tmp1 = z4 + z5;
+		    tmp2 = z3 + z5;
+		    tmp3 += z1;
+		} else {
+		    /* d1 == 0, d3 == 0, d5 == 0, d7 != 0 */
+		    tmp0 = MULTIPLY(-d7, FIX_1_387039845);
+		    tmp1 = MULTIPLY(d7, FIX_1_175875602);
+		    tmp2 = MULTIPLY(-d7, FIX_0_785694958);
+		    tmp3 = MULTIPLY(d7, FIX_0_275899380);
+		}
+	    }
+	}
+    } else {
+	if (d5) {
+	    if (d3) {
+		if (d1) {
+		    /* d1 != 0, d3 != 0, d5 != 0, d7 == 0 */
+		    z2 = d5 + d3;
+		    z4 = d5 + d1;
+		    z5 = MULTIPLY(d3 + z4, FIX_1_175875602);
+		    
+		    tmp1 = MULTIPLY(d5, FIX_2_053119869);
+		    tmp2 = MULTIPLY(d3, FIX_3_072711026);
+		    tmp3 = MULTIPLY(d1, FIX_1_501321110);
+		    z1 = MULTIPLY(-d1, FIX_0_899976223);
+		    z2 = MULTIPLY(-z2, FIX_2_562915447);
+		    z3 = MULTIPLY(-d3, FIX_1_961570560);
+		    z4 = MULTIPLY(-z4, FIX_0_390180644);
+		    
+		    z3 += z5;
+		    z4 += z5;
+		    
+		    tmp0 = z1 + z3;
+		    tmp1 += z2 + z4;
+		    tmp2 += z2 + z3;
+		    tmp3 += z1 + z4;
+		} else {
+		    /* d1 == 0, d3 != 0, d5 != 0, d7 == 0 */
+		    z2 = d5 + d3;
+		    
+		    z5 = MULTIPLY(z2, FIX_1_175875602);
+		    tmp1 = MULTIPLY(d5, FIX_1_662939225);
+		    z4 = MULTIPLY(-d5, FIX_0_390180644);
+		    z2 = MULTIPLY(-z2, FIX_1_387039845);
+		    tmp2 = MULTIPLY(d3, FIX_1_111140466);
+		    z3 = MULTIPLY(-d3, FIX_1_961570560);
+		    
+		    tmp0 = z3 + z5;
+		    tmp1 += z2;
+		    tmp2 += z2;
+		    tmp3 = z4 + z5;
+		}
+	    } else {
+		if (d1) {
+		    /* d1 != 0, d3 == 0, d5 != 0, d7 == 0 */
+		    z4 = d5 + d1;
+		    
+		    z5 = MULTIPLY(z4, FIX_1_175875602);
+		    z1 = MULTIPLY(-d1, FIX_0_899976223);
+		    tmp3 = MULTIPLY(d1, FIX_0_601344887);
+		    tmp1 = MULTIPLY(-d5, FIX_0_509795579);
+		    z2 = MULTIPLY(-d5, FIX_2_562915447);
+		    z4 = MULTIPLY(z4, FIX_0_785694958);
+		    
+		    tmp0 = z1 + z5;
+		    tmp1 += z4;
+		    tmp2 = z2 + z5;
+		    tmp3 += z4;
+		} else {
+		    /* d1 == 0, d3 == 0, d5 != 0, d7 == 0 */
+		    tmp0 = MULTIPLY(d5, FIX_1_175875602);
+		    tmp1 = MULTIPLY(d5, FIX_0_275899380);
+		    tmp2 = MULTIPLY(-d5, FIX_1_387039845);
+		    tmp3 = MULTIPLY(d5, FIX_0_785694958);
+		}
+	    }
+	} else {
+	    if (d3) {
+		if (d1) {
+		    /* d1 != 0, d3 != 0, d5 == 0, d7 == 0 */
+		    z5 = d1 + d3;
+		    tmp3 = MULTIPLY(d1, FIX_0_211164243);
+		    tmp2 = MULTIPLY(-d3, FIX_1_451774981);
+		    z1 = MULTIPLY(d1, FIX_1_061594337);
+		    z2 = MULTIPLY(-d3, FIX_2_172734803);
+		    z4 = MULTIPLY(z5, FIX_0_785694958);
+		    z5 = MULTIPLY(z5, FIX_1_175875602);
+		    
+		    tmp0 = z1 - z4;
+		    tmp1 = z2 + z4;
+		    tmp2 += z5;
+		    tmp3 += z5;
+		} else {
+		    /* d1 == 0, d3 != 0, d5 == 0, d7 == 0 */
+		    tmp0 = MULTIPLY(-d3, FIX_0_785694958);
+		    tmp1 = MULTIPLY(-d3, FIX_1_387039845);
+		    tmp2 = MULTIPLY(-d3, FIX_0_275899380);
+		    tmp3 = MULTIPLY(d3, FIX_1_175875602);
+		}
+	    } else {
+		if (d1) {
+		    /* d1 != 0, d3 == 0, d5 == 0, d7 == 0 */
+		    tmp0 = MULTIPLY(d1, FIX_0_275899380);
+		    tmp1 = MULTIPLY(d1, FIX_0_785694958);
+		    tmp2 = MULTIPLY(d1, FIX_1_175875602);
+		    tmp3 = MULTIPLY(d1, FIX_1_387039845);
+		} else {
+		    /* d1 == 0, d3 == 0, d5 == 0, d7 == 0 */
+		    tmp0 = tmp1 = tmp2 = tmp3 = 0;
+		}
+	    }
+	}
+    }
+
+    /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */
+
+    dataptr[DCTSIZE*0] = (DCTELEM) DESCALE(tmp10 + tmp3,
+					   CONST_BITS+PASS1_BITS+3);
+    dataptr[DCTSIZE*7] = (DCTELEM) DESCALE(tmp10 - tmp3,
+					   CONST_BITS+PASS1_BITS+3);
+    dataptr[DCTSIZE*1] = (DCTELEM) DESCALE(tmp11 + tmp2,
+					   CONST_BITS+PASS1_BITS+3);
+    dataptr[DCTSIZE*6] = (DCTELEM) DESCALE(tmp11 - tmp2,
+					   CONST_BITS+PASS1_BITS+3);
+    dataptr[DCTSIZE*2] = (DCTELEM) DESCALE(tmp12 + tmp1,
+					   CONST_BITS+PASS1_BITS+3);
+    dataptr[DCTSIZE*5] = (DCTELEM) DESCALE(tmp12 - tmp1,
+					   CONST_BITS+PASS1_BITS+3);
+    dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp13 + tmp0,
+					   CONST_BITS+PASS1_BITS+3);
+    dataptr[DCTSIZE*4] = (DCTELEM) DESCALE(tmp13 - tmp0,
+					   CONST_BITS+PASS1_BITS+3);
+    
+    dataptr++;			/* advance pointer to next column */
+  }
+}
+
+
+/* here is the reference one, in case of problems with the normal one */
+
+/* idctref.c, Inverse Discrete Fourier Transform, double precision          */
+
+/* Copyright (C) 1994, MPEG Software Simulation Group. All Rights Reserved. */
+
+/*
+ * Disclaimer of Warranty
+ *
+ * These software programs are available to the user without any license fee or
+ * royalty on an "as is" basis.  The MPEG Software Simulation Group disclaims
+ * any and all warranties, whether express, implied, or statuary, including any
+ * implied warranties or merchantability or of fitness for a particular
+ * purpose.  In no event shall the copyright-holder be liable for any
+ * incidental, punitive, or consequential damages of any kind whatsoever
+ * arising from the use of these programs.
+ *
+ * This disclaimer of warranty extends to the user of these programs and user's
+ * customers, employees, agents, transferees, successors, and assigns.
+ *
+ * The MPEG Software Simulation Group does not represent or warrant that the
+ * programs furnished hereunder are free of infringement of any third-party
+ * patents.
+ *
+ * Commercial implementations of MPEG-1 and MPEG-2 video, including shareware,
+ * are subject to royalty fees to patent holders.  Many of these patents are
+ * general enough such that they are unavoidable regardless of implementation
+ * design.
+ *
+ */
+
+/*  Perform IEEE 1180 reference (64-bit floating point, separable 8x1
+ *  direct matrix multiply) Inverse Discrete Cosine Transform
+*/
+
+
+/* Here we use math.h to generate constants.  Compiler results may
+   vary a little */
+
+#ifndef PI
+#ifdef M_PI
+#define PI M_PI
+#else
+#define PI 3.14159265358979323846
+#endif
+#endif
+
+/* cosine transform matrix for 8x1 IDCT */
+static double itrans_coef[8][8];
+
+/* initialize DCT coefficient matrix */
+
+void init_idctref()
+{
+  int freq, time;
+  double scale;
+
+  for (freq=0; freq < 8; freq++)
+  {
+    scale = (freq == 0) ? sqrt(0.125) : 0.5;
+    for (time=0; time<8; time++)
+      itrans_coef[freq][time] = scale*cos((PI/8.0)*freq*(time + 0.5));
+  }
+}
+
+/* perform IDCT matrix multiply for 8x8 coefficient block */
+
+void reference_rev_dct(block)
+int16 *block;
+{
+  int i, j, k, v;
+  double partial_product;
+  double tmp[64];
+
+  for (i=0; i<8; i++)
+    for (j=0; j<8; j++)
+    {
+      partial_product = 0.0;
+
+      for (k=0; k<8; k++)
+        partial_product+= itrans_coef[k][j]*block[8*i+k];
+
+      tmp[8*i+j] = partial_product;
+    }
+
+  /* Transpose operation is integrated into address mapping by switching 
+     loop order of i and j */
+
+  for (j=0; j<8; j++)
+    for (i=0; i<8; i++)
+    {
+      partial_product = 0.0;
+
+      for (k=0; k<8; k++)
+        partial_product+= itrans_coef[k][i]*tmp[8*k+j];
+
+      v = floor(partial_product+0.5);
+      block[8*i+j] = (v<-256) ? -256 : ((v>255) ? 255 : v);
+    }
+}
diff --git a/converter/ppm/ppmtompeg/memory.c b/converter/ppm/ppmtompeg/memory.c
new file mode 100644
index 00000000..f117994a
--- /dev/null
+++ b/converter/ppm/ppmtompeg/memory.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*
+ *  $Header: /n/charlie-brown/project/mm/mpeg/mpeg_dist/mpeg_encode/RCS/memory.c,v 1.3 1995/01/19 23:08:43 eyhung Exp $
+ *  $Log: memory.c,v $
+ * Revision 1.3  1995/01/19  23:08:43  eyhung
+ * Changed copyrights
+ *
+ * Revision 1.2  1993/06/03  21:08:08  keving
+ * nothing
+ *
+ * Revision 1.1  1993/04/27  21:32:26  keving
+ * nothing
+ *
+ *
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include "memory.h"
+
+
+/* memory handling routines
+ *
+ */
+
+long	totalMemory = 0;
+long	maxMemory = 0;
+
+
+char	*MemAlloc(size_t size)
+{
+    totalMemory += (long)size;
+    if ( totalMemory > maxMemory )
+    {
+	maxMemory = totalMemory;
+    }
+
+    return malloc(size);
+}
+
+void	MemFree(char *ptr, long bytes)
+{
+    totalMemory -= bytes;
+    free(ptr);
+}
+
+void	PrintMaxMemory(void)
+{
+    fprintf(stdout, "MMMMM-----MAX MEMORY-----MMMMM = %ld\n", maxMemory);
+}
diff --git a/converter/ppm/ppmtompeg/mfwddct.c b/converter/ppm/ppmtompeg/mfwddct.c
new file mode 100644
index 00000000..4643bf25
--- /dev/null
+++ b/converter/ppm/ppmtompeg/mfwddct.c
@@ -0,0 +1,393 @@
+
+/*
+ * mfwddct.c (derived from jfwddct.c, which carries the following info)
+ *
+ * Copyright (C) 1991, 1992, Thomas G. Lane. This file is part of the
+ * Independent JPEG Group's software. For conditions of distribution and use,
+ * see the accompanying README file.
+ *
+ * This file contains the basic DCT (Discrete Cosine Transform) transformation
+ * subroutine.
+ *
+ * This implementation is based on Appendix A.2 of the book "Discrete Cosine
+ * Transform---Algorithms, Advantages, Applications" by K.R. Rao and P. Yip
+ * (Academic Press, Inc, London, 1990). It uses scaled fixed-point arithmetic
+ * instead of floating point.
+ */
+
+#include "all.h"
+
+#include "dct.h"
+#include "mtypes.h"
+#include "opts.h"
+
+/*
+ * The poop on this scaling stuff is as follows:
+ *
+ * We have to do addition and subtraction of the integer inputs, which is no
+ * problem, and multiplication by fractional constants, which is a problem to
+ * do in integer arithmetic.  We multiply all the constants by DCT_SCALE and
+ * convert them to integer constants (thus retaining LG2_DCT_SCALE bits of
+ * precision in the constants).  After doing a multiplication we have to
+ * divide the product by DCT_SCALE, with proper rounding, to produce the
+ * correct output.  The division can be implemented cheaply as a right shift
+ * of LG2_DCT_SCALE bits.  The DCT equations also specify an additional
+ * division by 2 on the final outputs; this can be folded into the
+ * right-shift by shifting one more bit (see UNFIXH).
+ *
+ * If you are planning to recode this in assembler, you might want to set
+ * LG2_DCT_SCALE to 15.  This loses a bit of precision, but then all the
+ * multiplications are between 16-bit quantities (given 8-bit JSAMPLEs!) so
+ * you could use a signed 16x16=>32 bit multiply instruction instead of full
+ * 32x32 multiply.  Unfortunately there's no way to describe such a multiply
+ * portably in C, so we've gone for the extra bit of accuracy here.
+ */
+
+#define EIGHT_BIT_SAMPLES
+#ifdef EIGHT_BIT_SAMPLES
+#define LG2_DCT_SCALE 16
+#else
+#define LG2_DCT_SCALE 15	/* lose a little precision to avoid overflow */
+#endif
+
+#define ONE	((int32) 1)
+
+#define DCT_SCALE (ONE << LG2_DCT_SCALE)
+
+/* In some places we shift the inputs left by a couple more bits, */
+/* so that they can be added to fractional results without too much */
+/* loss of precision. */
+#define LG2_OVERSCALE 2
+#define OVERSCALE  (ONE << LG2_OVERSCALE)
+#define OVERSHIFT(x)  ((x) <<= LG2_OVERSCALE)
+
+/* Scale a fractional constant by DCT_SCALE */
+#define FIX(x)	((int32) ((x) * DCT_SCALE + 0.5))
+
+/* Scale a fractional constant by DCT_SCALE/OVERSCALE */
+/* Such a constant can be multiplied with an overscaled input */
+/* to produce something that's scaled by DCT_SCALE */
+#define FIXO(x)  ((int32) ((x) * DCT_SCALE / OVERSCALE + 0.5))
+
+/* Descale and correctly round a value that's scaled by DCT_SCALE */
+#define UNFIX(x)   RIGHT_SHIFT((x) + (ONE << (LG2_DCT_SCALE-1)), LG2_DCT_SCALE)
+
+/* Same with an additional division by 2, ie, correctly rounded UNFIX(x/2) */
+#define UNFIXH(x)  RIGHT_SHIFT((x) + (ONE << LG2_DCT_SCALE), LG2_DCT_SCALE+1)
+
+/* Take a value scaled by DCT_SCALE and round to integer scaled by OVERSCALE */
+#define UNFIXO(x)  RIGHT_SHIFT((x) + (ONE << (LG2_DCT_SCALE-1-LG2_OVERSCALE)),\
+			       LG2_DCT_SCALE-LG2_OVERSCALE)
+
+/* Here are the constants we need */
+/* SIN_i_j is sine of i*pi/j, scaled by DCT_SCALE */
+/* COS_i_j is cosine of i*pi/j, scaled by DCT_SCALE */
+
+#define SIN_1_4 FIX(0.707106781)
+#define COS_1_4 SIN_1_4
+
+#define SIN_1_8 FIX(0.382683432)
+#define COS_1_8 FIX(0.923879533)
+#define SIN_3_8 COS_1_8
+#define COS_3_8 SIN_1_8
+
+#define SIN_1_16 FIX(0.195090322)
+#define COS_1_16 FIX(0.980785280)
+#define SIN_7_16 COS_1_16
+#define COS_7_16 SIN_1_16
+
+#define SIN_3_16 FIX(0.555570233)
+#define COS_3_16 FIX(0.831469612)
+#define SIN_5_16 COS_3_16
+#define COS_5_16 SIN_3_16
+
+/* OSIN_i_j is sine of i*pi/j, scaled by DCT_SCALE/OVERSCALE */
+/* OCOS_i_j is cosine of i*pi/j, scaled by DCT_SCALE/OVERSCALE */
+
+#define OSIN_1_4 FIXO(0.707106781)
+#define OCOS_1_4 OSIN_1_4
+
+#define OSIN_1_8 FIXO(0.382683432)
+#define OCOS_1_8 FIXO(0.923879533)
+#define OSIN_3_8 OCOS_1_8
+#define OCOS_3_8 OSIN_1_8
+
+#define OSIN_1_16 FIXO(0.195090322)
+#define OCOS_1_16 FIXO(0.980785280)
+#define OSIN_7_16 OCOS_1_16
+#define OCOS_7_16 OSIN_1_16
+
+#define OSIN_3_16 FIXO(0.555570233)
+#define OCOS_3_16 FIXO(0.831469612)
+#define OSIN_5_16 OCOS_3_16
+#define OCOS_5_16 OSIN_3_16
+
+/* Prototypes */
+void reference_fwd_dct _ANSI_ARGS_((Block block, Block dest));
+void mp_fwd_dct_fast _ANSI_ARGS_((Block data2d, Block dest2d));
+void init_fdct _ANSI_ARGS_((void));
+
+/*
+ * --------------------------------------------------------------
+ *
+ * mp_fwd_dct_block2 --
+ *
+ * Select the appropriate mp_fwd_dct routine
+ *
+ * Results: None
+ *
+ * Side effects: None
+ *
+ * --------------------------------------------------------------
+ */
+extern boolean pureDCT;
+void
+mp_fwd_dct_block2(data, dest)
+    Block data, dest;
+{
+  if (pureDCT) reference_fwd_dct(data, dest);
+  else mp_fwd_dct_fast(data, dest);
+}
+
+/*
+ * --------------------------------------------------------------
+ *
+ * mp_fwd_dct_fast --
+ *
+ * Perform the forward DCT on one block of samples.
+ *
+ * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT on each
+ * column.
+ *
+ * Results: None
+ *
+ * Side effects: Overwrites the input data
+ *
+ * --------------------------------------------------------------
+ */
+
+void
+mp_fwd_dct_fast(data2d, dest2d)
+    Block data2d, dest2d;
+{
+    int16 *data = (int16 *) data2d;	/* this algorithm wants
+					 * a 1-d array */
+    int16 *dest = (int16 *) dest2d;
+    int pass, rowctr;
+    register int16 *inptr, *outptr;
+    int16 workspace[DCTSIZE_SQ];
+    SHIFT_TEMPS
+
+#ifdef ndef
+    {
+	int y;
+
+	printf("fwd_dct (beforehand):\n");
+	for (y = 0; y < 8; y++)
+	    printf("%4d %4d %4d %4d %4d %4d %4d %4d\n",
+		   data2d[y][0], data2d[y][1],
+		   data2d[y][2], data2d[y][3],
+		   data2d[y][4], data2d[y][5],
+		   data2d[y][6], data2d[y][7]);
+    }
+#endif
+
+    /*
+     * Each iteration of the inner loop performs one 8-point 1-D DCT. It
+     * reads from a *row* of the input matrix and stores into a *column*
+     * of the output matrix.  In the first pass, we read from the data[]
+     * array and store into the local workspace[].  In the second pass,
+     * we read from the workspace[] array and store into data[], thus
+     * performing the equivalent of a columnar DCT pass with no variable
+     * array indexing.
+     */
+
+    inptr = data;		/* initialize pointers for first pass */
+    outptr = workspace;
+    for (pass = 1; pass >= 0; pass--) {
+	for (rowctr = DCTSIZE - 1; rowctr >= 0; rowctr--) {
+	    /*
+	     * many tmps have nonoverlapping lifetime -- flashy
+	     * register colorers should be able to do this lot
+	     * very well
+	     */
+	    int32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
+	    int32 tmp10, tmp11, tmp12, tmp13;
+	    int32 tmp14, tmp15, tmp16, tmp17;
+	    int32 tmp25, tmp26;
+	    /* SHIFT_TEMPS */
+
+	    /* temp0 through tmp7:  -512 to +512 */
+	    /* if I-block, then -256 to +256 */
+	    tmp0 = inptr[7] + inptr[0];
+	    tmp1 = inptr[6] + inptr[1];
+	    tmp2 = inptr[5] + inptr[2];
+	    tmp3 = inptr[4] + inptr[3];
+	    tmp4 = inptr[3] - inptr[4];
+	    tmp5 = inptr[2] - inptr[5];
+	    tmp6 = inptr[1] - inptr[6];
+	    tmp7 = inptr[0] - inptr[7];
+
+	    /* tmp10 through tmp13:  -1024 to +1024 */
+	    /* if I-block, then -512 to +512 */
+	    tmp10 = tmp3 + tmp0;
+	    tmp11 = tmp2 + tmp1;
+	    tmp12 = tmp1 - tmp2;
+	    tmp13 = tmp0 - tmp3;
+
+	    outptr[0] = (int16) UNFIXH((tmp10 + tmp11) * SIN_1_4);
+	    outptr[DCTSIZE * 4] = (int16) UNFIXH((tmp10 - tmp11) * COS_1_4);
+
+	    outptr[DCTSIZE * 2] = (int16) UNFIXH(tmp13 * COS_1_8 + tmp12 * SIN_1_8);
+	    outptr[DCTSIZE * 6] = (int16) UNFIXH(tmp13 * SIN_1_8 - tmp12 * COS_1_8);
+
+	    tmp16 = UNFIXO((tmp6 + tmp5) * SIN_1_4);
+	    tmp15 = UNFIXO((tmp6 - tmp5) * COS_1_4);
+
+	    OVERSHIFT(tmp4);
+	    OVERSHIFT(tmp7);
+
+	    /*
+	     * tmp4, tmp7, tmp15, tmp16 are overscaled by
+	     * OVERSCALE
+	     */
+
+	    tmp14 = tmp4 + tmp15;
+	    tmp25 = tmp4 - tmp15;
+	    tmp26 = tmp7 - tmp16;
+	    tmp17 = tmp7 + tmp16;
+
+	    outptr[DCTSIZE] = (int16) UNFIXH(tmp17 * OCOS_1_16 + tmp14 * OSIN_1_16);
+	    outptr[DCTSIZE * 7] = (int16) UNFIXH(tmp17 * OCOS_7_16 - tmp14 * OSIN_7_16);
+	    outptr[DCTSIZE * 5] = (int16) UNFIXH(tmp26 * OCOS_5_16 + tmp25 * OSIN_5_16);
+	    outptr[DCTSIZE * 3] = (int16) UNFIXH(tmp26 * OCOS_3_16 - tmp25 * OSIN_3_16);
+
+	    inptr += DCTSIZE;	/* advance inptr to next row */
+	    outptr++;		/* advance outptr to next column */
+	}
+	/* end of pass; in case it was pass 1, set up for pass 2 */
+	inptr = workspace;
+	outptr = dest;
+    }
+#ifdef ndef
+    {
+	int y;
+
+	printf("fwd_dct (afterward):\n");
+	for (y = 0; y < 8; y++)
+	    printf("%4d %4d %4d %4d %4d %4d %4d %4d\n",
+		   dest2d[y][0], dest2d[y][1],
+		   dest2d[y][2], dest2d[y][3],
+		   dest2d[y][4], dest2d[y][5],
+		   dest2d[y][6], dest2d[y][7]);
+    }
+#endif
+}
+
+
+/* Modifies from the MPEG2 verification coder */
+/* fdctref.c, forward discrete cosine transform, double precision           */
+
+/* Copyright (C) 1994, MPEG Software Simulation Group. All Rights Reserved. */
+
+/*
+ * Disclaimer of Warranty
+ *
+ * These software programs are available to the user without any license fee or
+ * royalty on an "as is" basis.  The MPEG Software Simulation Group disclaims
+ * any and all warranties, whether express, implied, or statuary, including any
+ * implied warranties or merchantability or of fitness for a particular
+ * purpose.  In no event shall the copyright-holder be liable for any
+ * incidental, punitive, or consequential damages of any kind whatsoever
+ * arising from the use of these programs.
+ *
+ * This disclaimer of warranty extends to the user of these programs and user's
+ * customers, employees, agents, transferees, successors, and assigns.
+ *
+ * The MPEG Software Simulation Group does not represent or warrant that the
+ * programs furnished hereunder are free of infringement of any third-party
+ * patents.
+ *
+ * Commercial implementations of MPEG-1 and MPEG-2 video, including shareware,
+ * are subject to royalty fees to patent holders.  Many of these patents are
+ * general enough such that they are unavoidable regardless of implementation
+ * design.
+ *
+ */
+
+#ifndef PI
+#ifdef M_PI
+#define PI M_PI
+#else
+#define PI 3.14159265358979323846
+#endif
+#endif
+
+/* private data */
+static double trans_coef[8][8]; /* transform coefficients */
+
+void init_fdct()
+{
+  int i, j;
+  double s;
+
+  for (i=0; i<8; i++)
+  {
+    s = (i==0) ? sqrt(0.125) : 0.5;
+
+    for (j=0; j<8; j++)
+      trans_coef[i][j] = s * cos((PI/8.0)*i*(j+0.5));
+  }
+}
+
+void reference_fwd_dct(block, dest)
+Block block, dest;
+{
+  int i, j, k;
+  double s;
+  double tmp[64];
+
+  if (DoLaplace) {
+    LaplaceNum++;
+  }
+
+  for (i=0; i<8; i++)
+    for (j=0; j<8; j++)
+    {
+      s = 0.0;
+
+      for (k=0; k<8; k++)
+        s += trans_coef[j][k] * block[i][k];
+
+      tmp[8*i+j] = s;
+    }
+
+  for (i=0; i<8; i++)
+    for (j=0; j<8; j++)
+    {
+      s = 0.0;
+
+      for (k=0; k<8; k++)
+        s += trans_coef[i][k] * tmp[8*k+j];
+
+      if (collect_quant) {
+	fprintf(collect_quant_fp, "%d %f\n", 8*i+j, s);
+      } 
+      if (DoLaplace) {
+	L1[LaplaceCnum][i*8+j] += s*s;
+	L2[LaplaceCnum][i*8+j] += s;
+      }
+
+
+      dest[i][j] = (int)floor(s+0.499999);
+      /*
+       * reason for adding 0.499999 instead of 0.5:
+       * s is quite often x.5 (at least for i and/or j = 0 or 4)
+       * and setting the rounding threshold exactly to 0.5 leads to an
+       * extremely high arithmetic implementation dependency of the result;
+       * s being between x.5 and x.500001 (which is now incorrectly rounded
+       * downwards instead of upwards) is assumed to occur less often
+       * (if at all)
+       */
+    }
+}
diff --git a/converter/ppm/ppmtompeg/mheaders.c b/converter/ppm/ppmtompeg/mheaders.c
new file mode 100644
index 00000000..2a5f2c63
--- /dev/null
+++ b/converter/ppm/ppmtompeg/mheaders.c
@@ -0,0 +1,1192 @@
+/*===========================================================================*
+ * mheaders.c								     *
+ *									     *
+ *	Procedures to generate MPEG headers				     *
+ *									     *
+ * EXPORTED PROCEDURES:							     *
+ *	Mhead_GenPictureHeader						     *
+ *	Mhead_GenSequenceHeader						     *
+ *	Mhead_GenSequenceEnder						     *
+ *	Mhead_GenGOPHeader						     *
+ *	Mhead_GenSliceHeader						     *
+ *	Mhead_GenSliceEnder						     *
+ *	Mhead_GenMBHeader						     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*
+ *  $Header: /n/picasso/project/mpeg/mpeg_dist/mpeg_encode/RCS/mheaders.c,v 1.15 1995/08/07 21:45:19 smoot Exp $
+ *  $Log: mheaders.c,v $
+ *  Revision 1.15  1995/08/07 21:45:19  smoot
+ *  check for illegal MVs (shouldnt ever be called, but....)
+ *  fix bug which made us not weite Iframe Qscale changes
+ *  warns if writing a size=0 mpeg
+ *
+ *  Revision 1.14  1995/05/22 20:53:35  smoot
+ *  corrected bit_rate value in constrained params flag
+ *
+ * Revision 1.13  1995/05/02  01:50:38  eyhung
+ * made VidRateNum un-static
+ *
+ * Revision 1.12  1995/03/27  19:28:23  smoot
+ * auto-determines Qscale changes (was mb_quant)
+ *
+ * Revision 1.11  1995/02/16  09:12:39  eyhung
+ * fixed compile bug with HP7xx
+ *
+ * Revision 1.10  1995/01/25  22:53:50  smoot
+ * Better buf_size checking, and actually check constrained params
+ *
+ * Revision 1.9  1995/01/19  23:08:47  eyhung
+ * Changed copyrights
+ *
+ * Revision 1.8  1995/01/16  08:45:10  eyhung
+ * BLEAH'ed hsize and vsize
+ *
+ * Revision 1.7  1994/12/09  22:27:17  smoot
+ * Fixed buffer size in stream
+ *
+ * Revision 1.6  1994/11/12  02:11:54  keving
+ * nothing
+ *
+ * Revision 1.5  1994/03/15  00:27:11  keving
+ * nothing
+ *
+ * Revision 1.4  1993/12/22  19:19:01  keving
+ * nothing
+ *
+ * Revision 1.3  1993/07/22  22:23:43  keving
+ * nothing
+ *
+ * Revision 1.2  1993/06/30  20:06:09  keving
+ * nothing
+ *
+ * Revision 1.1  1993/06/03  21:08:08  keving
+ * nothing
+ *
+ * Revision 1.6  1993/03/01  23:03:40  keving
+ * nothing
+ *
+ * Revision 1.5  1993/02/17  23:18:20  dwallach
+ * checkin prior to keving's joining the project
+ *
+ * Revision 1.4  1993/01/18  10:20:02  dwallach
+ * *** empty log message ***
+ *
+ * Revision 1.3  1993/01/18  10:17:29  dwallach
+ * RCS headers installed, code indented uniformly
+ *
+ * Revision 1.3  1993/01/18  10:17:29  dwallach
+ * RCS headers installed, code indented uniformly
+ *
+ */
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "all.h"
+#include "bitio.h"
+#include "frames.h"
+#include "mheaders.h"
+
+
+/*==================*
+ * STATIC VARIABLES *
+ *==================*/
+
+static int gopStartFrame = 0;
+static int lastGOPStart = 0;
+static int lastQSSet;
+
+static uint32 mbAddrIncrTable[][2] = {
+    {0x0, 0},
+    {0x1, 1},
+    {0x3, 3},
+    {0x2, 3},
+    {0x3, 4},
+    {0x2, 4},
+    {0x3, 5},
+    {0x2, 5},
+    {0x7, 7},
+    {0x6, 7},
+    {0xb, 8},
+    {0xa, 8},
+    {0x9, 8},
+    {0x8, 8},
+    {0x7, 8},
+    {0x6, 8},
+    {0x17, 10},
+    {0x16, 10},
+    {0x15, 10},
+    {0x14, 10},
+    {0x13, 10},
+    {0x12, 10},
+    {0x23, 11},
+    {0x22, 11},
+    {0x21, 11},
+    {0x20, 11},
+    {0x1f, 11},
+    {0x1e, 11},
+    {0x1d, 11},
+    {0x1c, 11},
+    {0x1b, 11},
+    {0x1a, 11},
+    {0x19, 11},
+    {0x18, 11}};
+
+static uint32 mbMotionVectorTable[][2] = {
+    {0x19, 11},
+    {0x1b, 11},
+    {0x1d, 11},
+    {0x1f, 11},
+    {0x21, 11},
+    {0x23, 11},
+    {0x13, 10},
+    {0x15, 10},
+    {0x17, 10},
+    {0x7, 8},
+    {0x9, 8},
+    {0xb, 8},
+    {0x7, 7},
+    {0x3, 5},
+    {0x3, 4},
+    {0x3, 3},
+    {0x1, 1},
+    {0x2, 3},
+    {0x2, 4},
+    {0x2, 5},
+    {0x6, 7},
+    {0xa, 8},
+    {0x8, 8},
+    {0x6, 8},
+    {0x16, 10},
+    {0x14, 10},
+    {0x12, 10},
+    {0x22, 11},
+    {0x20, 11},
+    {0x1e, 11},
+    {0x1c, 11},
+    {0x1a, 11},
+    {0x18, 11}};
+
+static uint32 mbPatTable[][2] = {
+    {0x0, 0},
+    {0xb, 5},
+    {0x9, 5},
+    {0xd, 6},
+    {0xd, 4},
+    {0x17, 7},
+    {0x13, 7},
+    {0x1f, 8},
+    {0xc, 4},
+    {0x16, 7},
+    {0x12, 7},
+    {0x1e, 8},
+    {0x13, 5},
+    {0x1b, 8},
+    {0x17, 8},
+    {0x13, 8},
+    {0xb, 4},
+    {0x15, 7},
+    {0x11, 7},
+    {0x1d, 8},
+    {0x11, 5},
+    {0x19, 8},
+    {0x15, 8},
+    {0x11, 8},
+    {0xf, 6},
+    {0xf, 8},
+    {0xd, 8},
+    {0x3, 9},
+    {0xf, 5},
+    {0xb, 8},
+    {0x7, 8},
+    {0x7, 9},
+    {0xa, 4},
+    {0x14, 7},
+    {0x10, 7},
+    {0x1c, 8},
+    {0xe, 6},
+    {0xe, 8},
+    {0xc, 8},
+    {0x2, 9},
+    {0x10, 5},
+    {0x18, 8},
+    {0x14, 8},
+    {0x10, 8},
+    {0xe, 5},
+    {0xa, 8},
+    {0x6, 8},
+    {0x6, 9},
+    {0x12, 5},
+    {0x1a, 8},
+    {0x16, 8},
+    {0x12, 8},
+    {0xd, 5},
+    {0x9, 8},
+    {0x5, 8},
+    {0x5, 9},
+    {0xc, 5},
+    {0x8, 8},
+    {0x4, 8},
+    {0x4, 9},
+    {0x7, 3},
+    {0xa, 5},	/* grrr... 61, 62, 63 added - Kevin */
+    {0x8, 5},
+    {0xc, 6}
+};
+
+/*===========*
+ * CONSTANTS *
+ *===========*/
+
+#define SEQ_HEAD_CODE 0x000001b3
+#define EXT_START_CODE 0x000001b5
+#define USER_START_CODE 0x000001b2
+#define GOP_START_CODE 0x000001b8
+#define PICT_START_CODE 0x00000100
+#define SLICE_BASE_CODE 0x00000100
+
+#define SEQ_END_CODE	0x000001b7
+
+/* not static anymore because information is used for computing frame rate 
+ * and for statistics */
+const double VidRateNum[9]={1.0, 23.976, 24.0, 25.0, 29.97, 30.0,
+                             50.0 ,59.94, 60.0};
+
+
+/*===============================*
+ * INTERNAL PROCEDURE prototypes *
+ *===============================*/
+
+static void	GenMBAddrIncr _ANSI_ARGS_((BitBucket *bb, uint32 addr_incr));
+static void	GenPictHead _ANSI_ARGS_((BitBucket *bb, uint32 temp_ref,
+		    uint32 code_type, uint32 vbv_delay,
+		    int32 full_pel_forw_flag, uint32 forw_f_code,
+		    int32 full_pel_back_flag, uint32 back_f_code,
+		    uint8 *extra_info, uint32 extra_info_size,
+		    uint8 *ext_data, uint32 ext_data_size,
+		    uint8 *user_data, uint32 user_data_size));
+static void	GenMBType _ANSI_ARGS_((BitBucket *bb, uint32 pict_code_type,
+		  uint32 mb_quant, uint32 motion_forw, uint32 motion_back,
+		  uint32 mb_pattern, uint32 mb_intra));
+static void	GenMotionCode _ANSI_ARGS_((BitBucket *bb, int32 vector));
+static void	GenBlockPattern _ANSI_ARGS_((BitBucket *bb,
+					     uint32 mb_pattern));
+
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+
+
+/*===========================================================================*
+ *
+ * SetGOPStartTime
+ *
+ *	sets the start frame of the GOP; to be used with GenPictureHeader
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+SetGOPStartTime(index)
+    int index;
+{
+    lastGOPStart = gopStartFrame;
+    gopStartFrame = index;
+}
+
+
+/*===========================================================================*
+ *
+ * Mhead_GenPictureHeader
+ *
+ *	generate picture header with given frame type and picture count
+ *	append result to the specified bitstream
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Mhead_GenPictureHeader(bbPtr, frameType, pictCount, f_code)
+    BitBucket *bbPtr;
+    int frameType;
+    int pictCount;
+    int f_code;
+{
+    int	    temporalRef;
+
+    if ( pictCount >= gopStartFrame ) {
+	temporalRef = (pictCount-gopStartFrame);
+    } else {
+	temporalRef = (pictCount-lastGOPStart);
+    }
+    temporalRef = (temporalRef % 1024);
+	
+    DBG_PRINT(("Picture Header\n"));
+    GenPictHead(bbPtr, temporalRef, frameType,
+		0 /* vbv_delay */,
+		pixelFullSearch /* full_pel_forw_flag */,
+		f_code /* forw_f_code */,
+		pixelFullSearch /* full_pel_back_flag */,
+		f_code /* back_f_code */,
+		NULL, 0, NULL, 0, NULL, 0);
+}
+
+
+/*===========================================================================*
+ *
+ * Mhead_GenSequenceHeader
+ *
+ *	generate sequence header with given attributes
+ *	append result to the specified bitstream
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Mhead_GenSequenceHeader(BitBucket *   const bbPtr, 
+                        uint32        const hsize, 
+                        uint32        const vsize,
+                        int32         const pratio, 
+                        int32         const pict_rate, 
+                        int32         const bit_rate_arg,
+                        int32         const buf_size_arg, 
+                        int32         const c_param_flag_arg, 
+                        const int32 * const iq_matrix, 
+                        const int32 * const niq_matrix,
+                        uint8 *       const ext_data, 
+                        int32         const ext_data_size, 
+                        uint8 *       const user_data,
+                        int32         const user_data_size) {
+
+    extern int ZAG[];
+    int i;
+    int32 bit_rate;
+    int32 buf_size;
+    int32 c_param_flag;
+
+    /* Write seq start code. */
+
+    Bitio_Write(bbPtr, SEQ_HEAD_CODE, 32);
+
+    /* Write horiz. and vert. sizes. */
+
+#ifdef BLEAH
+    fprintf(stdout, "hsize, vsize = %d, %d\n", hsize, vsize);
+#endif
+
+    if (hsize==0 || vsize==0) {
+        fprintf(stderr, "Writing zero size to stream!\n");
+    }
+    Bitio_Write(bbPtr, hsize, 12);
+    Bitio_Write(bbPtr, vsize, 12);
+
+    /* Write pixel aspect ratio, negative values default to 1. */
+
+    if (pratio < 0) {
+        fprintf(stderr, "PROGRAMMER ERROR:  pratio = %d\n", pratio);
+        exit(1);
+    }
+    Bitio_Write(bbPtr, pratio, 4);
+
+    /* Wrtie picture rate, negative values default to 30 fps. */
+
+    if (pict_rate < 0) {
+        fprintf(stderr, "PROGRAMMER ERROR:  pict_rate = %d\n", pict_rate);
+        exit(1);
+    }
+    Bitio_Write(bbPtr, pict_rate, 4);
+
+    /* Write bit rate, negative values default to variable. */
+
+    if (bit_rate_arg < 0) {
+        bit_rate = -1;
+    } else {
+        bit_rate = bit_rate_arg / 400;
+    }
+
+    Bitio_Write(bbPtr, bit_rate, 18);
+
+    /* Marker bit. */
+    Bitio_Write(bbPtr, 0x1, 1);
+
+    /* Write VBV buffer size. Negative values default to zero. */
+    if (buf_size_arg < 0)
+        buf_size = 0;
+    else
+        buf_size = buf_size_arg;
+
+    buf_size = (buf_size + (16*1024 - 1)) / (16*1024);
+    if (buf_size>=0x400) 
+        buf_size=0x3ff;
+    Bitio_Write(bbPtr, buf_size, 10);
+
+    /* Write constrained parameter flag. */
+    {
+        int num_mb = ((hsize+15)/16) * ((vsize+15)/16);
+        /* At present we cheat on buffer size */
+        c_param_flag = ((bit_rate <= 4640) &&
+                        (bit_rate >0) &&
+                        (buf_size <= 20) &&
+                        (pict_rate >= 1) &&
+                        (pict_rate <= 5) &&
+                        (hsize <= 768) &&
+                        (vsize <= 576) &&
+                        (num_mb <= 396) &&
+                        (num_mb*VidRateNum[pict_rate] <= 9900) &&
+                        (fCodeP<=4) &&
+                        (fCodeB<=4));
+    }
+
+    if (c_param_flag) {
+        Bitio_Write(bbPtr, 0x01, 1);
+    } else {
+        Bitio_Write(bbPtr, 0x00, 1);
+    }
+
+    /* Write intra quant matrix if present. */
+
+    if (iq_matrix != NULL) {
+        Bitio_Write(bbPtr, 0x01, 1);
+        for (i = 0; i < 64; i++) {
+            Bitio_Write(bbPtr, iq_matrix[ZAG[i]], 8);
+        }
+    } else {
+        Bitio_Write(bbPtr, 0x00, 1);
+    }
+
+    /* Write non intra quant matrix if present. */
+
+    if (niq_matrix != NULL) {
+        Bitio_Write(bbPtr, 0x01, 1);
+        for (i = 0; i < 64; i++) {
+            Bitio_Write(bbPtr, niq_matrix[ZAG[i]], 8);
+        }
+    } else {
+        Bitio_Write(bbPtr, 0x00, 1);
+    }
+
+    /* next start code */
+    Bitio_BytePad(bbPtr);
+
+
+    /* Write ext data if present. */
+
+    if (ext_data != NULL) {
+        Bitio_Write(bbPtr, EXT_START_CODE, 32);
+
+        for (i = 0; i < ext_data_size; i++) {
+            Bitio_Write(bbPtr, ext_data[i], 8);
+        }
+        Bitio_BytePad(bbPtr);
+    }
+    /* Write user data if present. */
+    if ((user_data != NULL) && (user_data_size != 0)) {
+        Bitio_Write(bbPtr, USER_START_CODE, 32);
+
+        for (i = 0; i < user_data_size; i++) {
+            Bitio_Write(bbPtr, user_data[i], 8);
+        }
+        Bitio_BytePad(bbPtr);
+    }
+}
+
+
+/*===========================================================================*
+ *
+ * Mhead_GenSequenceEnder
+ *
+ *	generate sequence ender
+ *	append result to the specified bitstream
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Mhead_GenSequenceEnder(bbPtr)
+    BitBucket *bbPtr;
+{
+    Bitio_Write(bbPtr, SEQ_END_CODE, 32);
+}
+
+
+/*===========================================================================*
+ *
+ * Mhead_GenGOPHeader
+ *
+ *	generate GOP header with specified attributes
+ *	append result to the specified bitstream
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Mhead_GenGOPHeader(bbPtr, drop_frame_flag, tc_hrs, tc_min, tc_sec, tc_pict,
+		   closed_gop, broken_link, ext_data, ext_data_size,
+		   user_data, user_data_size)
+    BitBucket *bbPtr;
+    int32 drop_frame_flag;
+    int32 tc_hrs;
+    int32 tc_min;
+    int32 tc_sec;
+    int32 tc_pict;
+    int32 closed_gop;
+    int32 broken_link;
+    uint8 *ext_data;
+    int32 ext_data_size;
+    uint8 *user_data;
+    int32 user_data_size;
+{
+    int i;
+
+    /* Write gop start code. */
+    Bitio_Write(bbPtr, GOP_START_CODE, 32);
+
+		/* Construct and write timecode. */
+
+    /* Drop frame flag. */
+    if (drop_frame_flag) {
+	Bitio_Write(bbPtr, 0x01, 1);
+    } else {
+	Bitio_Write(bbPtr, 0x00, 1);
+    }
+
+    /* Time code hours. */
+    Bitio_Write(bbPtr, tc_hrs, 5);
+
+    /* Time code minutes. */
+    Bitio_Write(bbPtr, tc_min, 6);
+
+    /* Marker bit. */
+    Bitio_Write(bbPtr, 0x01, 1);
+
+    /* Time code seconds. */
+    Bitio_Write(bbPtr, tc_sec, 6);
+
+    /* Time code pictures. */
+    Bitio_Write(bbPtr, tc_pict, 6);
+
+
+    /* Closed gop flag. */
+    if (closed_gop) {
+	Bitio_Write(bbPtr, 0x01, 1);
+    } else {
+	Bitio_Write(bbPtr, 0x00, 1);
+    }
+
+    /* Broken link flag. */
+    if (broken_link) {
+	Bitio_Write(bbPtr, 0x01, 1);
+    } else {
+	Bitio_Write(bbPtr, 0x00, 1);
+    }
+
+    /* next start code */
+    Bitio_BytePad(bbPtr);
+
+    /* Write ext data if present. */
+
+    if (ext_data != NULL) {
+	Bitio_Write(bbPtr, EXT_START_CODE, 32);
+
+	for (i = 0; i < ext_data_size; i++) {
+	    Bitio_Write(bbPtr, ext_data[i], 8);
+	}
+	Bitio_BytePad(bbPtr);
+    }
+    /* Write user data if present. */
+    if (user_data != NULL) {
+	Bitio_Write(bbPtr, USER_START_CODE, 32);
+
+	for (i = 0; i < user_data_size; i++) {
+	    Bitio_Write(bbPtr, user_data[i], 8);
+	}
+	Bitio_BytePad(bbPtr);
+    }
+}
+
+
+/*===========================================================================*
+ *
+ * Mhead_GenSliceHeader
+ *
+ *	generate slice header with specified attributes
+ *	append result to the specified bitstream
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Mhead_GenSliceHeader(bbPtr, verticalPos, qscale, extra_info, extra_info_size)
+    BitBucket *bbPtr;
+    uint32 verticalPos;
+    uint32 qscale;
+    uint8 *extra_info;
+    uint32 extra_info_size;
+{
+    int i;
+
+    /* Write slice start code. */
+    Bitio_Write(bbPtr, (SLICE_BASE_CODE + verticalPos), 32);
+
+    /* Quant. scale. */
+    Bitio_Write(bbPtr, qscale, 5);
+    lastQSSet = qscale;
+
+    /* Extra bit slice info. */
+
+    if (extra_info != NULL) {
+	for (i = 0; i < extra_info_size; i++) {
+	    Bitio_Write(bbPtr, 0x01, 1);
+	    Bitio_Write(bbPtr, extra_info[i], 8);
+	}
+    }
+
+    /* extra_bit_slice */
+    Bitio_Write(bbPtr, 0x00, 1);
+}
+
+
+/*===========================================================================*
+ *
+ * Mhead_GenSliceEnder
+ *
+ *	generate slice ender
+ *	append result to the specified bitstream
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Mhead_GenSliceEnder(bbPtr)
+    BitBucket *bbPtr;
+{
+    Bitio_BytePad(bbPtr);
+}
+
+
+/*===========================================================================*
+ *
+ * Mhead_GenMBHeader
+ *
+ *	generate macroblock header with given attributes
+ *	append result to the specified bitstream
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Mhead_GenMBHeader(bbPtr, pict_code_type, addr_incr, q_scale,
+		  forw_f_code, back_f_code, horiz_forw_r, vert_forw_r,
+		  horiz_back_r, vert_back_r, motion_forw, m_horiz_forw,
+		  m_vert_forw, motion_back, m_horiz_back, m_vert_back,
+		  mb_pattern, mb_intra)
+    BitBucket *bbPtr;
+    uint32 pict_code_type;
+    uint32 addr_incr;
+    uint32 q_scale;
+    uint32 forw_f_code;
+    uint32 back_f_code;
+    uint32 horiz_forw_r;
+    uint32 vert_forw_r;
+    uint32 horiz_back_r;
+    uint32 vert_back_r;
+    int32 motion_forw;
+    int32 m_horiz_forw;
+    int32 m_vert_forw;
+    int32 motion_back;
+    int32 m_horiz_back;
+    int32 m_vert_back;
+    uint32 mb_pattern;
+    uint32 mb_intra;
+{
+    uint32 mb_quant;
+
+    /* MB escape sequences if necessary. */
+
+#ifdef BLEAH
+if ( addr_incr != 1 )
+    fprintf(stdout, "Creating MB_INCR:  %d\n", addr_incr);
+#endif
+
+    while (addr_incr > 33) {
+	Bitio_Write(bbPtr, 0x008, 11);
+	addr_incr -= 33;
+    }
+
+    /* Generate addr incr code. */
+    GenMBAddrIncr(bbPtr, addr_incr);
+
+    /* Determine mb_quant  (true if change in q scale) */
+    if ((q_scale != lastQSSet) && ((mb_pattern != 0) || (mb_intra == TRUE))) {
+      mb_quant = TRUE;
+      lastQSSet = q_scale;
+    } else {
+      mb_quant = FALSE;
+    }
+
+    /* Generate mb type code. */
+    GenMBType(bbPtr, pict_code_type, mb_quant, motion_forw, motion_back, mb_pattern, mb_intra);
+
+    /* MB quant. */
+    if (mb_quant) {
+	Bitio_Write(bbPtr, q_scale, 5);
+    }
+    /* Forward predictive vector stuff. */
+
+    if (motion_forw) {
+	int forw_f, forw_r_size;
+
+	forw_r_size = forw_f_code - 1;
+	forw_f = 1 << forw_r_size;	/* 1 > 0 */
+	if ((m_horiz_forw > 16*forw_f-1) || (m_horiz_forw < -16*forw_f)) {
+	  fprintf(stderr, "Illegal motion? %d %d\n", m_horiz_forw, 16*forw_f);
+	}
+	if ((m_vert_forw > 16*forw_f-1) || (m_vert_forw < -16*forw_f)) {
+	  fprintf(stderr, "Illegal motion? %d %d\n", m_vert_forw, 16*forw_f);
+	}
+	GenMotionCode(bbPtr, m_horiz_forw);
+
+	if ((forw_f != 1) && (m_horiz_forw != 0)) {
+	    Bitio_Write(bbPtr, horiz_forw_r, forw_r_size);
+	}
+	GenMotionCode(bbPtr, m_vert_forw);
+
+	if ((forw_f != 1) && (m_vert_forw != 0)) {
+	    Bitio_Write(bbPtr, vert_forw_r, forw_r_size);
+	}
+    }
+    /* Back predicted vector stuff. */
+
+    if (motion_back) {
+	int back_f, back_r_size;
+
+	back_r_size = back_f_code - 1;
+	back_f = 1 << back_r_size;	/* 1 > 0 */
+
+	if ((m_horiz_back > 16*back_f-1) || (m_horiz_back < -16*back_f)) {
+	  fprintf(stderr, "Illegal motion? %d %d\n", m_horiz_back, 16*back_f);
+	}
+	if ((m_vert_back > 16*back_f-1) || (m_vert_back < -16*back_f)) {
+	  fprintf(stderr, "Illegal motion? %d %d\n", m_vert_back, 16*back_f);
+	}
+
+	GenMotionCode(bbPtr, m_horiz_back);
+
+	if ((back_f != 1) && (m_horiz_back != 0)) {
+	    Bitio_Write(bbPtr, horiz_back_r, back_r_size);
+	}
+	GenMotionCode(bbPtr, m_vert_back);
+
+	if ((back_f != 1) && (m_vert_back != 0)) {
+	    Bitio_Write(bbPtr, vert_back_r, back_r_size);
+	}
+    }
+    /* MB pattern. */
+
+    if (mb_pattern) {
+	GenBlockPattern(bbPtr, mb_pattern);
+    }
+}
+
+
+/*=====================*
+ * INTERNAL PROCEDURES *
+ *=====================*/
+
+/*===========================================================================*
+ *
+ * GenMBType
+ *
+ *	generate macroblock type with given attributes
+ *	append result to the specified bitstream
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static void
+GenMBType(bbPtr, pict_code_type, mb_quant, motion_forw, motion_back,
+	  mb_pattern, mb_intra)
+    BitBucket *bbPtr;
+    uint32 pict_code_type;
+    uint32 mb_quant;
+    uint32 motion_forw;
+    uint32 motion_back;
+    uint32 mb_pattern;
+    uint32 mb_intra;
+{
+    int code;
+
+    switch (pict_code_type) {
+    case 1:
+	if ((motion_forw != 0) || (motion_back != 0) || (mb_pattern != 0) || (mb_intra != 1)) {
+	    perror("Illegal parameters for macroblock type.");
+	    exit(-1);
+	}
+	if (mb_quant) {
+	    Bitio_Write(bbPtr, 0x1, 2);
+	} else {
+	    Bitio_Write(bbPtr, 0x1, 1);
+	}
+	break;
+
+    case 2:
+	code = 0;
+	if (mb_quant) {
+	    code += 16;
+	}
+	if (motion_forw) {
+	    code += 8;
+	}
+	if (motion_back) {
+	    code += 4;
+	}
+	if (mb_pattern) {
+	    code += 2;
+	}
+	if (mb_intra) {
+	    code += 1;
+	}
+
+	switch (code) {
+	case 1:
+	    Bitio_Write(bbPtr, 0x3, 5);
+	    break;
+	case 2:
+	    Bitio_Write(bbPtr, 0x1, 2);
+	    break;
+	case 8:
+	    Bitio_Write(bbPtr, 0x1, 3);
+	    break;
+	case 10:
+	    Bitio_Write(bbPtr, 0x1, 1);
+	    break;
+	case 17:
+	    Bitio_Write(bbPtr, 0x1, 6);
+	    break;
+	case 18:
+	    Bitio_Write(bbPtr, 0x1, 5);
+	    break;
+	case 26:
+	    Bitio_Write(bbPtr, 0x2, 5);
+	    break;
+	default:
+	    perror("Illegal parameters for macroblock type.");
+	    exit(-1);
+	    break;
+	}
+	break;
+
+    case 3:
+	code = 0;
+	if (mb_quant) {
+	    code += 16;
+	}
+	if (motion_forw) {
+	    code += 8;
+	}
+	if (motion_back) {
+	    code += 4;
+	}
+	if (mb_pattern) {
+	    code += 2;
+	}
+	if (mb_intra) {
+	    code += 1;
+	}
+
+	switch (code) {
+	case 12:
+	    Bitio_Write(bbPtr, 0x2, 2);
+	    break;
+	case 14:
+	    Bitio_Write(bbPtr, 0x3, 2);
+	    break;
+	case 4:
+	    Bitio_Write(bbPtr, 0x2, 3);
+	    break;
+	case 6:
+	    Bitio_Write(bbPtr, 0x3, 3);
+	    break;
+	case 8:
+	    Bitio_Write(bbPtr, 0x2, 4);
+	    break;
+	case 10:
+	    Bitio_Write(bbPtr, 0x3, 4);
+	    break;
+	case 1:
+	    Bitio_Write(bbPtr, 0x3, 5);
+	    break;
+	case 30:
+	    Bitio_Write(bbPtr, 0x2, 5);
+	    break;
+	case 26:
+	    Bitio_Write(bbPtr, 0x3, 6);
+	    break;
+	case 22:
+	    Bitio_Write(bbPtr, 0x2, 6);
+	    break;
+	case 17:
+	    Bitio_Write(bbPtr, 0x1, 6);
+	    break;
+	default:
+	    perror("Illegal parameters for macroblock type.");
+	    exit(-1);
+	    break;
+	}
+	break;
+    }
+}
+
+
+/*===========================================================================*
+ *
+ * GenMotionCode
+ *
+ *	generate motion vector output with given value
+ *	append result to the specified bitstream
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static void
+GenMotionCode(BitBucket * const bbPtr,
+              int32       const vector) {
+
+    uint32 code, num;
+
+    if ((vector < -16) || (vector > 16)) {
+	perror("Motion vector out of range.");
+	fprintf(stderr, "Motion vector out of range:  vector = %d\n", vector);
+	exit(-1);
+    }
+    code = mbMotionVectorTable[vector + 16][0];
+    num = mbMotionVectorTable[vector + 16][1];
+
+    Bitio_Write(bbPtr, code, num);
+}
+
+
+/*===========================================================================*
+ *
+ * GenBlockPattern
+ *
+ *	generate macroblock pattern output
+ *	append result to the specified bitstream
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static void
+GenBlockPattern(bbPtr, mb_pattern)
+    BitBucket *bbPtr;
+    uint32 mb_pattern;
+{
+    uint32 code, num;
+
+    code = mbPatTable[mb_pattern][0];
+    num = mbPatTable[mb_pattern][1];
+
+    Bitio_Write(bbPtr, code, num);
+}
+
+
+/*===========================================================================*
+ *
+ * GenMBAddrIncr
+ *
+ *	generate macroblock address increment output
+ *	append result to the specified bitstream
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static void
+GenMBAddrIncr(bbPtr, addr_incr)
+    BitBucket *bbPtr;
+    uint32 addr_incr;
+{
+    uint32 code;
+    uint32 num;
+
+    code = mbAddrIncrTable[addr_incr][0];
+    num = mbAddrIncrTable[addr_incr][1];
+
+    Bitio_Write(bbPtr, code, num);
+}
+
+
+/*===========================================================================*
+ *
+ * GenPictHead
+ *
+ *	generate picture header with given attributes
+ *	append result to the specified bitstream
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static void
+GenPictHead(bbPtr, temp_ref, code_type, vbv_delay, full_pel_forw_flag,
+	    forw_f_code, full_pel_back_flag, back_f_code, extra_info,
+	    extra_info_size, ext_data, ext_data_size, user_data,
+	    user_data_size)
+    BitBucket *bbPtr;
+    uint32 temp_ref;
+    uint32 code_type;
+    uint32 vbv_delay;
+    int32 full_pel_forw_flag;
+    uint32 forw_f_code;
+    int32 full_pel_back_flag;
+    uint32 back_f_code;
+    uint8 *extra_info;
+    uint32 extra_info_size;
+    uint8 *ext_data;
+    uint32 ext_data_size;
+    uint8 *user_data;
+    uint32 user_data_size;
+{
+    int i;
+
+    /* Write picture start code. */
+    Bitio_Write(bbPtr, PICT_START_CODE, 32);
+
+    /* Temp reference. */
+    Bitio_Write(bbPtr, temp_ref, 10);
+
+    /* Code_type. */
+    if (code_type == 0) {
+	code_type = 1;
+    }
+    Bitio_Write(bbPtr, code_type, 3);
+
+    /* vbv_delay. */
+    vbv_delay = 0xffff;		    /* see page 36 (section 2.4.3.4) */
+    Bitio_Write(bbPtr, vbv_delay, 16);
+
+    if ((code_type == 2) || (code_type == 3)) {
+
+	/* Full pel forw flag. */
+
+	if (full_pel_forw_flag) {
+	    Bitio_Write(bbPtr, 0x01, 1);
+	} else {
+	    Bitio_Write(bbPtr, 0x00, 1);
+	}
+
+	/* Forw f code. */
+
+	Bitio_Write(bbPtr, forw_f_code, 3);
+    }
+    if (code_type == 3) {
+
+	/* Full pel back flag. */
+
+	if (full_pel_back_flag) {
+	    Bitio_Write(bbPtr, 0x01, 1);
+	} else {
+	    Bitio_Write(bbPtr, 0x00, 1);
+	}
+
+	/* Back f code. */
+
+	Bitio_Write(bbPtr, back_f_code, 3);
+    }
+    /* Extra bit picture info. */
+
+    if (extra_info != NULL) {
+	for (i = 0; i < extra_info_size; i++) {
+	    Bitio_Write(bbPtr, 0x01, 1);
+	    Bitio_Write(bbPtr, extra_info[i], 8);
+	}
+    }
+    Bitio_Write(bbPtr, 0x00, 1);
+
+    /* next start code */
+    Bitio_BytePad(bbPtr);
+
+    /* Write ext data if present. */
+
+    if (ext_data != NULL) {
+	Bitio_Write(bbPtr, EXT_START_CODE, 32);
+
+	for (i = 0; i < ext_data_size; i++) {
+	    Bitio_Write(bbPtr, ext_data[i], 8);
+	}
+	Bitio_BytePad(bbPtr);
+    }
+    /* Write user data if present. */
+    if (user_data != NULL) {
+	Bitio_Write(bbPtr, USER_START_CODE, 32);
+
+	for (i = 0; i < user_data_size; i++) {
+	    Bitio_Write(bbPtr, user_data[i], 8);
+	}
+	Bitio_BytePad(bbPtr);
+    }
+}
+
+
+#ifdef UNUSED_PROCEDURES
+
+/* GenMBEnd only used for `D` pictures. Shouldn't really ever be called. */
+/* - dwallach */
+void
+GenMBEnd(bbPtr)
+    BitBucket *bbPtr;
+{
+    Bitio_Write(bbPtr, 0x01, 1);
+}
+
+#endif /* UNUSED_PROCEDURES */
diff --git a/converter/ppm/ppmtompeg/moutput.c b/converter/ppm/ppmtompeg/moutput.c
new file mode 100644
index 00000000..b682efab
--- /dev/null
+++ b/converter/ppm/ppmtompeg/moutput.c
@@ -0,0 +1,442 @@
+/*===========================================================================*
+ * moutput.c								     *
+ *									     *
+ *	Procedures concerned with quantization and RLE			     *
+ *									     *
+ * EXPORTED PROCEDURES:							     *
+ *	mp_quant_zig_block						     *
+ *	mp_rle_huff_block						     *
+ *	mp_rle_huff_pblock						     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*
+ *  $Header: /n/charlie-brown/project/mm/mpeg/mpeg_dist/mpeg_encode/RCS/moutput.c,v 1.12 1995/01/19 23:08:49 eyhung Exp $
+ *  $Log: moutput.c,v $
+ * Revision 1.12  1995/01/19  23:08:49  eyhung
+ * Changed copyrights
+ *
+ * Revision 1.11  1993/07/22  22:23:43  keving
+ * nothing
+ *
+ * Revision 1.10  1993/06/30  20:06:09  keving
+ * nothing
+ *
+ * Revision 1.9  1993/06/03  21:08:08  keving
+ * nothing
+ *
+ * Revision 1.8  1993/02/24  18:57:19  keving
+ * nothing
+ *
+ * Revision 1.7  1993/02/23  22:58:36  keving
+ * nothing
+ *
+ * Revision 1.6  1993/02/23  22:54:56  keving
+ * nothing
+ *
+ * Revision 1.5  1993/02/17  23:18:20  dwallach
+ * checkin prior to keving's joining the project
+ *
+ * Revision 1.4  1993/01/18  10:20:02  dwallach
+ * *** empty log message ***
+ *
+ * Revision 1.3  1993/01/18  10:17:29  dwallach
+ * RCS headers installed, code indented uniformly
+ *
+ * Revision 1.3  1993/01/18  10:17:29  dwallach
+ * RCS headers installed, code indented uniformly
+ *
+ */
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "all.h"
+#include "mtypes.h"
+#include "mproto.h"
+#include "huff.h"
+
+
+/*==================*
+ * STATIC VARIABLES *
+ *==================*/
+
+/* ZAG[i] is the natural-order position of the i'th element of zigzag order. */
+static int ZAG[] =
+{
+    0, 1, 8, 16, 9, 2, 3, 10,
+    17, 24, 32, 25, 18, 11, 4, 5,
+    12, 19, 26, 33, 40, 48, 41, 34,
+    27, 20, 13, 6, 7, 14, 21, 28,
+    35, 42, 49, 56, 57, 50, 43, 36,
+    29, 22, 15, 23, 30, 37, 44, 51,
+    58, 59, 52, 45, 38, 31, 39, 46,
+    53, 60, 61, 54, 47, 55, 62, 63
+};
+
+
+/*
+ * possible optimization: reorder the qtable in the correct zigzag order, to
+ * reduce the number of necessary lookups
+ *
+ * this table comes from the MPEG draft, p. D-16, Fig. 2-D.15.
+ */
+static int qtable[] =
+{
+    8, 16, 19, 22, 26, 27, 29, 34,
+    16, 16, 22, 24, 27, 29, 34, 37,
+    19, 22, 26, 27, 29, 34, 34, 38,
+    22, 22, 26, 27, 29, 34, 37, 40,
+    22, 26, 27, 29, 32, 35, 40, 48,
+    26, 27, 29, 32, 35, 40, 48, 58,
+    26, 27, 29, 34, 38, 46, 56, 69,
+    27, 29, 35, 38, 46, 56, 69, 83};
+
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+
+
+void	UnQuantZig(FlatBlock in, Block out, int qscale, boolean iblock)
+{
+    register int index;
+    int	    start;
+    int	    position;
+    register int	    qentry;
+    int	    level, coeff;
+    register int16 temp;
+
+    if ( iblock )
+    {
+	((int16 *)out)[0] = (int16)(in[0]*qtable[0]);
+
+	start = 1;
+    }
+    else
+	start = 0;
+
+    for ( index = start; index < DCTSIZE_SQ; index++ )
+    {
+	position = ZAG[index];
+
+	if (iblock)
+	    qentry = qtable[position] * qscale;
+	else
+	    qentry = 16 * qscale;
+
+	level = in[index];
+        coeff = (level * qentry) >> 3;
+        if (level < 0) {
+            coeff += (coeff & 1);
+	} else {
+            coeff -= (coeff & 1);
+	}
+
+	((int16 *)out)[position] = coeff;
+    }
+
+#ifdef BLEAH
+    for ( index = 0; index < 64; index++ )
+	fprintf(stdout, "DCT[%d] = %d\n", index, 
+		((int16 *)out)[index]);
+#endif
+}
+
+
+/*
+ * --------------------------------------------------------------
+ *
+ * mp_quant_zig_block --
+ *
+ * Quantizes and zigzags a block -- removing information
+ *
+ * Results: TRUE iff resulting 'out' is non-zero, FALSE if all
+ *	    zero
+ *
+ * Side effects: Modifies the out block.
+ *
+ * --------------------------------------------------------------
+ */
+boolean mp_quant_zig_block(Block in, FlatBlock out, int qscale, int iblock)
+{
+    register int i;
+    register int y, x;
+    register int16 temp;
+    register int qentry;
+    int start;
+    boolean nonZero = FALSE;
+
+    DBG_PRINT(("mp_quant_zig_block...\n"));
+    if (iblock) {
+	/*
+	 * the DC coefficient is handled specially -- it's not
+	 * sensitive to qscale, but everything else is
+	 */
+	temp = ((int16 *) in)[ZAG[0]];
+	qentry = qtable[ZAG[0]];
+	if (temp < 0) {
+	    temp = -temp;
+	    temp += qentry >> 1;
+	    temp /= qentry;
+	    temp = -temp;
+	} else {
+	    temp += qentry >> 1;
+	    temp /= qentry;
+	}
+	if ( temp != 0 )
+	    nonZero = TRUE;
+	out[0] = temp;
+	start = 1;
+    } else
+	start = 0;
+
+    for (i = start; i < DCTSIZE_SQ; i++) {
+	x = ZAG[i] % 8;
+	y = ZAG[i] / 8;
+	temp = in[y][x];
+	DBG_PRINT(("    in[%d][%d] = %d;  ", y, x, temp));
+
+	if (iblock)
+	    qentry = qtable[ZAG[i]] * qscale;
+	else
+	    qentry = 16 * qscale;
+
+	DBG_PRINT(("quantized with %d = ", qentry));
+
+	if (temp < 0) {
+	    temp = -temp;
+	    temp *= 8;
+	    temp += qentry >> 1;
+	    temp /= qentry;
+	    temp = -temp;
+	} else {
+	    temp *= 8;
+	    temp += qentry >> 1;
+	    temp /= qentry;
+	}
+	if ( temp != 0 )
+	    nonZero = TRUE;
+	out[i] = temp;
+	DBG_PRINT(("%d\n", temp));
+    }
+
+    return nonZero;
+}
+
+
+
+/*
+ * --------------------------------------------------------------
+ *
+ * mp_rle_huff_block --
+ *
+ * Given a FlatBlock, generates the Huffman bits
+ *
+ * Results: None.
+ *
+ * Side effects: Output bits changed
+ *
+ * --------------------------------------------------------------
+ */
+
+void	mp_rle_huff_block(FlatBlock in, BitBucket *out)
+{
+    register int i;
+    register int nzeros = 0;
+    register int16 cur;
+    register int16 acur;
+    register uint32 code;
+    register int nbits;
+
+    /*
+     * yes, Virginia, we start at 1.  The DC coefficient is handled
+     * specially, elsewhere.  Not here.
+     */
+    for (i = 1; i < DCTSIZE_SQ; i++) {
+	cur = in[i];
+	acur = ABS(cur);
+	if (cur) {
+	    if (nzeros < HUFF_MAXRUN && acur < huff_maxlevel[nzeros]) {
+	        /*
+		 * encode using the Huffman tables
+		 */
+
+		DBG_PRINT(("rle_huff %02d: Run %02d, Level %02d\n", i, nzeros, cur));
+		assert(cur);
+
+		code = (huff_table[nzeros])[acur];
+		nbits = (huff_bits[nzeros])[acur];
+
+		assert(nbits);
+
+		if (cur < 0)
+		    code |= 1;	/* the sign bit */
+		Bitio_Write(out, code, nbits);
+	    } else {
+		/*
+		 * encode using the escape code
+		 */
+		DBG_PRINT(("Escape\n"));
+		Bitio_Write(out, 0x1, 6);	/* ESCAPE */
+		DBG_PRINT(("Run Length\n"));
+		Bitio_Write(out, nzeros, 6);	/* Run-Length */
+
+		assert(cur != 0);
+
+		/*
+	         * this shouldn't happen, but the other
+	         * choice is to bomb out and dump core...
+	         */
+		if (cur < -255)
+		    cur = -255;
+		else if (cur > 255)
+		    cur = 255;
+
+		DBG_PRINT(("Level\n"));
+		if (acur < 128) {
+		    Bitio_Write(out, cur, 8);
+		} else {
+		    if (cur < 0) {
+			Bitio_Write(out, 0x8001 + cur + 255, 16);
+		    } else
+			Bitio_Write(out, cur, 16);
+		}
+	    }
+	    nzeros = 0;
+	} else
+	    nzeros++;
+    }
+    DBG_PRINT(("End of block\n"));
+    Bitio_Write(out, 0x2, 2);	/* end of block marker */
+}
+
+
+/*
+ * --------------------------------------------------------------
+ *
+ * mp_rle_huff_pblock --
+ *
+ * Given a FlatBlock, generates the Huffman bits for P DCT
+ *
+ * Results: None.
+ *
+ * Side effects: Output bits changed
+ *
+ * --------------------------------------------------------------
+ */
+
+void	mp_rle_huff_pblock(FlatBlock in, BitBucket *out)
+{
+    register int i;
+    register int nzeros = 0;
+    register int16 cur;
+    register int16 acur;
+    register uint32 code;
+    register int nbits;
+    boolean first_dct = TRUE;
+
+    /*
+     * yes, Virginia, we start at 0.
+     */
+    for (i = 0; i < DCTSIZE_SQ; i++) {
+	cur = in[i];
+	acur = ABS(cur);
+	if (cur) {
+	    if (nzeros < HUFF_MAXRUN && acur < huff_maxlevel[nzeros]) {
+	        /*
+		 * encode using the Huffman tables
+		 */
+
+		DBG_PRINT(("rle_huff %02d: Run %02d, Level %02d\n", i, nzeros, cur));
+		assert(cur);
+
+		if ( first_dct && (nzeros == 0) && (acur == 1) )
+		{
+		    /* actually, only needs = 0x2 */
+		    code = (cur == 1) ? 0x2 : 0x3;
+		    nbits = 2;
+		}
+		else
+		{
+		    code = (huff_table[nzeros])[acur];
+		    nbits = (huff_bits[nzeros])[acur];
+		}
+
+		assert(nbits);
+
+		if (cur < 0)
+		    code |= 1;	/* the sign bit */
+		Bitio_Write(out, code, nbits);
+		first_dct = FALSE;
+	    } else {
+		/*
+		 * encode using the escape code
+		 */
+		DBG_PRINT(("Escape\n"));
+		Bitio_Write(out, 0x1, 6);	/* ESCAPE */
+		DBG_PRINT(("Run Length\n"));
+		Bitio_Write(out, nzeros, 6);	/* Run-Length */
+
+		assert(cur != 0);
+
+		/*
+	         * this shouldn't happen, but the other
+	         * choice is to bomb out and dump core...
+	         */
+		if (cur < -255)
+		    cur = -255;
+		else if (cur > 255)
+		    cur = 255;
+
+		DBG_PRINT(("Level\n"));
+		if (acur < 128) {
+		    Bitio_Write(out, cur, 8);
+		} else {
+		    if (cur < 0) {
+			Bitio_Write(out, 0x8001 + cur + 255, 16);
+		    } else
+			Bitio_Write(out, cur, 16);
+		}
+
+		first_dct = FALSE;
+	    }
+	    nzeros = 0;
+	} else
+	    nzeros++;
+    }
+
+    /* actually, should REALLY return FALSE and not use this! */
+    if ( first_dct )	/* have to give a first_dct even if all 0's */
+    {
+	fprintf(stdout, "HUFF called with all-zero coefficients\n");
+	fprintf(stdout, "exiting...\n");
+	exit(1);
+    }
+
+    DBG_PRINT(("End of block\n"));
+    Bitio_Write(out, 0x2, 2);	/* end of block marker */
+}
diff --git a/converter/ppm/ppmtompeg/mpeg.c b/converter/ppm/ppmtompeg/mpeg.c
new file mode 100644
index 00000000..a934ed09
--- /dev/null
+++ b/converter/ppm/ppmtompeg/mpeg.c
@@ -0,0 +1,1717 @@
+/*===========================================================================*
+ * mpeg.c
+ *
+ *  Procedures to generate the MPEG sequence
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#define _BSD_SOURCE   /* Make sure strdup() is in string.h */
+
+#include "all.h"
+#include <time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+#ifdef MIPS
+#include <sys/types.h>
+#endif
+#include <sys/stat.h>
+
+#include "ppm.h"
+#include "nstring.h"
+
+#include "mtypes.h"
+#include "frames.h"
+#include "motion_search.h"
+#include "prototypes.h"
+#include "parallel.h"
+#include "param.h"
+#include "readframe.h"
+#include "fsize.h"
+#include "mheaders.h"
+#include "rate.h"
+#include "input.h"
+#include "frametype.h"
+#include "mpeg.h"
+
+
+/*===========*
+ *  VERSION  *
+ *===========*/
+
+#define VERSION "1.5b"
+
+
+/*===========*
+ * CONSTANTS *
+ *===========*/
+
+#define FPS_30  0x5   /* from MPEG standard sect. 2.4.3.2 */
+#define ASPECT_1    0x1 /* aspect ratio, from MPEG standard sect. 2.4.3.2 */
+
+
+/*==================*
+ * STATIC VARIABLES *
+ *==================*/
+
+static unsigned int framesOutput;
+static int      realStart, realEnd;
+static int  currentGOP;
+static int      timeMask;
+static int      numI, numP, numB;
+static boolean  frameCountsUnknown;
+
+
+/*==================*
+ * GLOBAL VARIABLES *   
+ *==================*/
+
+/* important -- don't initialize anything here */
+/* must be re-initted anyway in GenMPEGStream */
+
+extern int  IOtime;
+extern boolean  resizeFrame;
+extern int outputWidth, outputHeight;
+int     gopSize = 100;  /* default */
+int32       tc_hrs, tc_min, tc_sec, tc_pict, tc_extra;
+int     totalFramesSent;
+int     yuvWidth, yuvHeight;
+int     realWidth, realHeight;
+char        currentPath[MAXPATHLEN];
+char        statFileName[256];
+char        bitRateFileName[256];
+time_t      timeStart, timeEnd;
+FILE       *statFile;
+FILE       *bitRateFile = NULL;
+char       *framePattern;
+int     framePatternLen;
+int     referenceFrame;
+int     frameRate = FPS_30;
+int     frameRateRounded = 30;
+boolean     frameRateInteger = TRUE;
+int     aspectRatio = ASPECT_1;
+extern char userDataFileName[];
+extern int mult_seq_headers;
+
+int32 bit_rate, buf_size;
+
+/*===============================*
+ * INTERNAL PROCEDURE prototypes *
+ *===============================*/
+
+static void ComputeDHMSTime _ANSI_ARGS_((int32 someTime, char *timeText));
+static void OpenBitRateFile _ANSI_ARGS_((void));
+static void CloseBitRateFile _ANSI_ARGS_((void));
+
+
+static void
+ShowRemainingTime(boolean const childProcess) {
+/*----------------------------------------------------------------------------
+   Print out an estimate of the time left to encode
+-----------------------------------------------------------------------------*/
+
+    if (childProcess) {
+        /* nothing */;
+    } else if ( numI + numP + numB == 0 ) {
+        /* no time left */
+    } else if ( timeMask != 0 ) {   
+        /* haven't encoded all types yet */
+    } else {
+        static int  lastTime = 0;
+        float   total;
+        time_t  nowTime;
+        float   secondsPerFrame;
+        
+        time(&nowTime);
+        secondsPerFrame = (nowTime-timeStart)/(float)framesOutput;
+        total = secondsPerFrame*(float)(numI+numP+numB);
+
+        if ((quietTime >= 0) && (!realQuiet) && (!frameCountsUnknown) &&
+            ((lastTime < (int)total) || ((lastTime-(int)total) >= quietTime) ||
+             (lastTime == 0) || (quietTime == 0))) {
+            if (total > 270.0)
+                pm_message("ESTIMATED TIME OF COMPLETION:  %d minutes",
+                           ((int)total+30)/60);
+            else
+                pm_message("ESTIMATED TIME OF COMPLETION:  %d seconds",
+                           (int)total);
+        }
+        lastTime = (int)total;
+    }
+}
+
+
+
+static void
+initTCTime(unsigned int const firstFrameNumber) {
+
+    unsigned int frameNumber;
+    
+    tc_hrs = 0; tc_min = 0; tc_sec = 0; tc_pict = 0; tc_extra = 0;
+    for (frameNumber = 0; frameNumber < firstFrameNumber; ++frameNumber)
+        IncrementTCTime();
+}
+
+
+
+/*===========================================================================*
+ *
+ * IncrementTCTime
+ *
+ *  increment the tc time by one second (and update min, hrs if necessary)
+ *  also increments totalFramesSent
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    totalFramesSent, tc_pict, tc_sec, tc_min, tc_hrs, tc_extra
+ *
+ *===========================================================================*/
+void
+IncrementTCTime() {
+    /* if fps = an integer, then tc_extra = 0 and is ignored
+
+       otherwise, it is the number of extra 1/1001 frames we've passed by
+
+       so far; for example, if fps = 24000/1001, then 24 frames = 24024/24000
+       seconds = 1 second + 24/24000 seconds = 1 + 1/1000 seconds; similary,
+       if fps = 30000/1001, then 30 frames = 30030/30000 = 1 + 1/1000 seconds
+       and if fps = 60000/1001, then 60 frames = 1 + 1/1000 seconds
+
+       if fps = 24000/1001, then 1/1000 seconds = 24/1001 frames
+       if fps = 30000/1001, then 1/1000 seconds = 30/1001 frames
+       if fps = 60000/1001, then 1/1000 seconds = 60/1001 frames     
+     */
+
+    totalFramesSent++;
+    tc_pict++;
+    if ( tc_pict >= frameRateRounded ) {
+        tc_pict = 0;
+        tc_sec++;
+        if ( tc_sec == 60 ) {
+            tc_sec = 0;
+            tc_min++;
+            if ( tc_min == 60 ) {
+                tc_min = 0;
+                tc_hrs++;
+            }
+        }
+        if ( ! frameRateInteger ) {
+            tc_extra += frameRateRounded;
+            if ( tc_extra >= 1001 ) {   /* a frame's worth */
+                tc_pict++;
+                tc_extra -= 1001;
+            }
+        }
+    }
+}
+
+
+
+static void
+initializeRateControl(bool const wantUnderflowWarning,
+                      bool const wantOverflowWarning) {
+/*----------------------------------------------------------------------------
+   Initialize rate control
+-----------------------------------------------------------------------------*/
+    int32 const bitstreamMode = getRateMode();
+
+    if (bitstreamMode == FIXED_RATE) {
+        initRateControl(wantUnderflowWarning, wantOverflowWarning);
+        /*
+          SetFrameRate();
+        */
+    }
+}
+    
+
+
+/*===========================================================================*
+ *
+ * SetReferenceFrameType
+ *
+ *  set the reference frame type to be original or decoded
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    referenceFrame
+ *
+ *===========================================================================*/
+void
+SetReferenceFrameType(const char * const type) {
+
+    if (strcmp(type, "ORIGINAL") == 0)
+        referenceFrame = ORIGINAL_FRAME;
+    else if ( strcmp(type, "DECODED") == 0 )
+        referenceFrame = DECODED_FRAME;
+    else
+        pm_error("INTERNAL ERROR: Illegal reference frame type: '%s'", type);
+}
+
+
+
+void
+SetBitRateFileName(fileName)
+    char *fileName;
+{
+    strcpy(bitRateFileName, fileName);
+}
+
+
+
+
+static void
+finishFrameOutput(MpegFrame * const frameP,
+                  BitBucket * const bbP,
+                  boolean     const separateFiles,
+                  int         const referenceFrame,
+                  boolean     const childProcess,
+                  boolean     const remoteIO) {
+
+    if ((referenceFrame == DECODED_FRAME) && 
+        childProcess && NonLocalRefFrame(frameP->id)) {
+        if (remoteIO)
+            SendDecodedFrame(frameP);
+        else
+            WriteDecodedFrame(frameP);
+            
+        NotifyDecodeServerReady(frameP->id);
+    }
+    
+    if (separateFiles) {
+        if (remoteIO)
+            SendRemoteFrame(frameP->id, bbP);
+        else {
+            Bitio_Flush(bbP);
+            Bitio_Close(bbP);
+        }
+    }
+}
+
+    
+
+
+static void
+outputIFrame(MpegFrame * const frameP,
+             BitBucket * const bb,
+             int         const realStart,
+             int         const realEnd,
+             MpegFrame * const pastRefFrameP,
+             boolean     const separateFiles) {
+      
+    /* only start a new GOP with I */
+    /* don't start GOP if only doing frames */
+    if ((!separateFiles) && (currentGOP >= gopSize)) {
+        boolean const closed = 
+            (totalFramesSent == frameP->id || pastRefFrameP == NULL);
+
+        static int num_gop = 0;
+    
+        /* first, check to see if closed GOP */
+    
+        /* new GOP */
+        if (num_gop != 0 && mult_seq_headers && 
+            num_gop % mult_seq_headers == 0) {
+            if (!realQuiet) {
+                fprintf(stdout, 
+                        "Creating new Sequence before GOP %d\n", num_gop);
+                fflush(stdout);
+            }
+      
+            Mhead_GenSequenceHeader(
+                bb, Fsize_x, Fsize_y,
+                /* pratio */    aspectRatio,
+                /* pict_rate */ frameRate, /* bit_rate */ bit_rate,
+                /* buf_size */  buf_size,  /* c_param_flag */ 1,
+                /* iq_matrix */ customQtable, 
+                /* niq_matrix */ customNIQtable,
+                /* ext_data */ NULL,  /* ext_data_size */ 0,
+                /* user_data */ NULL, /* user_data_size */ 0);
+        }
+    
+        if (!realQuiet)
+            pm_message("Creating new GOP (closed = %s) before frame %d\n",
+                       closed ? "YES" : "NO", frameP->id);
+    
+        ++num_gop;
+        Mhead_GenGOPHeader(bb,  /* drop_frame_flag */ 0,
+                           tc_hrs, tc_min, tc_sec, tc_pict,
+                           closed, /* broken_link */ 0,
+                           /* ext_data */ NULL, /* ext_data_size */ 0,
+                           /* user_data */ NULL, /* user_data_size */ 0);
+        currentGOP -= gopSize;
+        if (pastRefFrameP == NULL)
+            SetGOPStartTime(0);
+        else
+            SetGOPStartTime(pastRefFrameP->id+1);
+    }
+      
+    if ((frameP->id >= realStart) && (frameP->id <= realEnd))
+        GenIFrame(bb, frameP);
+      
+    numI--;
+    timeMask &= 0x6;
+      
+    currentGOP++;
+    IncrementTCTime();
+}
+
+
+
+static void
+outputPFrame(MpegFrame * const frameP,
+             BitBucket * const bbP,
+             int         const realStart,
+             int         const realEnd,
+             MpegFrame * const pastRefFrameP) {
+
+    if ((frameP->id >= realStart) && (frameP->id <= realEnd))
+        GenPFrame(bbP, frameP, pastRefFrameP);
+
+    numP--;
+    timeMask &= 0x5;
+    
+    currentGOP++;
+    IncrementTCTime();
+}
+
+
+
+static BitBucket *
+bitioNew(const char * const outputFileName,
+         unsigned int const frameNumber,
+         boolean      const remoteIO) {
+
+    BitBucket * bbP;
+
+    if (remoteIO)
+        bbP = Bitio_New(NULL);
+    else {
+        const char * fileName;
+
+        asprintfN(&fileName, "%s.frame.%d", outputFileName, frameNumber);
+
+        bbP = Bitio_New_Filename(fileName);
+
+        strfree(fileName);
+    }
+    return bbP;
+}
+
+
+
+static void
+getBFrame(int                  const frameNum,
+          struct inputSource * const inputSourceP,
+          MpegFrame *          const pastRefFrameP,
+          boolean              const childProcess,
+          boolean              const remoteIO,
+          MpegFrame **         const bFramePP,
+          int *                const IOtimeP,
+          unsigned int *       const framesReadP) {
+/*----------------------------------------------------------------------------
+   Get Frame 'frameNum', which is a B frame related to previous reference
+   frame 'pastRefFrameP'.  Return it as *bFramePP.
+
+   We have various ways of getting the frame, corresponding to the
+   multitude of modes in which Ppmtompeg works.
+-----------------------------------------------------------------------------*/
+    if (!inputSourceP->stdinUsed) {
+        time_t tempTimeStart, tempTimeEnd;
+        MpegFrame * bFrameP;
+        bool endOfStream;
+
+        bFrameP = Frame_New(frameNum, 'b');
+
+        time(&tempTimeStart);
+
+        ReadNthFrame(inputSourceP, frameNum, remoteIO, childProcess,
+                     separateConversion, slaveConversion, inputConversion,
+                     bFrameP, &endOfStream);
+
+        assert(!endOfStream);  /* Because it's not a stream */
+
+        time(&tempTimeEnd);
+        *IOtimeP += (tempTimeEnd-tempTimeStart);
+
+        ++(*framesReadP);
+        *bFramePP = bFrameP;
+    } else {
+        /* As the frame input is serial, we can't read the B frame now.
+           Rather, Caller has already read it and chained it to 
+           the previous reference frame.  So we get that copy now.
+        */
+        *bFramePP = pastRefFrameP->next;
+        pastRefFrameP->next = (*bFramePP)->next;  /* unlink from list */
+    }
+}
+
+
+
+static void
+processBFrames(MpegFrame *          const pastRefFrameP,
+               MpegFrame *          const futureRefFrameP,
+               int                  const realStart,
+               int                  const realEnd,
+               struct inputSource * const inputSourceP,
+               boolean              const remoteIo,
+               boolean              const childProcess,
+               int *                const IOtimeP,
+               BitBucket *          const wholeStreamBbP,
+               const char *         const outputFileName,
+               unsigned int *       const framesReadP,
+               unsigned int *       const framesOutputP,
+               int *                const currentGopP) {
+/*----------------------------------------------------------------------------
+   Process the B frames that go between 'pastRefFrameP' and
+   'futureRefFrame' in the movie (but go after 'futureRefFrameP' in the
+   MPEG stream, so reader doesn't have to read ahead).
+
+   Remember that a B frame is one which is described by data in the
+   MPEG stream that describes the frame with respect to a frame somewhere
+   before it, and a frame somewhere after it (i.e. reference frames).
+
+   But do only those B frames whose frame numbers are within the range
+   'realStart' through 'realEnd'.
+-----------------------------------------------------------------------------*/
+    boolean const separateFiles = (wholeStreamBbP == NULL);
+    unsigned int const firstBFrameNum = pastRefFrameP->id + 1;
+
+    int frameNum;
+
+    assert(pastRefFrameP != NULL);
+    assert(futureRefFrameP != NULL);
+    
+    for (frameNum = MAX(realStart, firstBFrameNum); 
+         frameNum < MIN(realEnd, futureRefFrameP->id); 
+         ++frameNum) {
+
+        MpegFrame * bFrame;
+        BitBucket * bbP;
+
+        getBFrame(frameNum, inputSourceP, pastRefFrameP, childProcess, 
+                  remoteIO,
+                  &bFrame, IOtimeP, framesReadP);
+
+        if (separateFiles)
+            bbP = bitioNew(outputFileName, bFrame->id, remoteIO);
+        else
+            bbP = wholeStreamBbP;
+
+        GenBFrame(bbP, bFrame, pastRefFrameP, futureRefFrameP);
+        ++(*framesOutputP);
+
+        if (separateFiles) {
+            if (remoteIO)
+                SendRemoteFrame(bFrame->id, bbP);
+            else {
+                Bitio_Flush(bbP);
+                Bitio_Close(bbP);
+            }
+        }
+
+        /* free this B frame right away */
+        Frame_Free(bFrame);
+
+        numB--;
+        timeMask &= 0x3;
+        ShowRemainingTime(childProcess);
+
+        ++(*currentGopP);
+        IncrementTCTime();
+    }
+}
+
+
+
+static void
+processRefFrame(MpegFrame *    const frameP, 
+                BitBucket *    const bb_arg,
+                int            const realStart,
+                int            const realEnd,
+                MpegFrame *    const pastRefFrameP,
+                boolean        const childProcess,
+                const char *   const outputFileName,
+                unsigned int * const framesReadP,
+                unsigned int * const framesOutputP) {
+/*----------------------------------------------------------------------------
+   Process an I or P frame.  Encode and output it.
+
+   But only if its frame number is within the range 'realStart'
+   through 'realEnd'.
+-----------------------------------------------------------------------------*/
+    if (frameP->id >= realStart && frameP->id <= realEnd) {
+        boolean separateFiles;
+        BitBucket * bb;
+  
+        separateFiles = (bb_arg == NULL);
+  
+        if ( separateFiles )
+            bb = bitioNew(outputFileName, frameP->id, remoteIO);
+        else
+            bb = bb_arg;
+  
+        /* first, output this reference frame */
+        switch (frameP->type) {
+        case TYPE_IFRAME:
+            outputIFrame(frameP, bb, realStart, realEnd, pastRefFrameP, 
+                         separateFiles);
+            break;
+        case TYPE_PFRAME:
+            outputPFrame(frameP, bb, realStart, realEnd, pastRefFrameP);
+            ShowRemainingTime(childProcess);
+            break;
+        default:
+            pm_error("INTERNAL ERROR: non-reference frame passed to "
+                     "ProcessRefFrame()");
+        }  
+        
+        ++(*framesOutputP);
+        
+        finishFrameOutput(frameP, bb, separateFiles, referenceFrame,
+                          childProcess, remoteIO);
+    }
+}
+
+
+
+static void
+countFrames(unsigned int const firstFrame,
+            unsigned int const lastFrame,
+            boolean      const stdinUsed,
+            int *        const numIP,
+            int *        const numPP,
+            int *        const numBP,
+            int *        const timeMaskP,
+            boolean *    const frameCountsUnknownP) {
+/*----------------------------------------------------------------------------
+  Count number of I, P, and B frames
+-----------------------------------------------------------------------------*/
+    unsigned int numI, numP, numB;
+    unsigned int timeMask;
+            
+    numI = 0; numP = 0; numB = 0;
+    timeMask = 0;
+    if (stdinUsed) {
+        numI = numP = numB = MAXINT/4;
+        *frameCountsUnknownP = TRUE;
+    } else {
+        unsigned int i;
+        for (i = firstFrame; i <= lastFrame; ++i) {
+            char const frameType = FType_Type(i);
+            switch(frameType) {
+            case 'i':        numI++;            timeMask |= 0x1;    break;
+            case 'p':        numP++;            timeMask |= 0x2;    break;
+            case 'b':        numB++;            timeMask |= 0x4;    break;
+            }
+        }
+        *frameCountsUnknownP = FALSE;
+    }
+
+    *numIP     = numI;
+    *numPP     = numP;
+    *numBP     = numB;
+    *timeMaskP = timeMask;
+    *frameCountsUnknownP = frameCountsUnknown;
+}
+
+
+
+static void
+readAndSaveFrame(struct inputSource * const inputSourceP,
+                 unsigned int         const frameNumber,
+                 char                 const frameType,
+                 const char *         const inputConversion,
+                 MpegFrame *          const pastRefFrameP,
+                 unsigned int *       const framesReadP,
+                 int *                const ioTimeP,
+                 bool *               const endOfStreamP) {
+/*----------------------------------------------------------------------------
+   Read the next frame from Standard Input and add it to the linked list
+   at *pastRefFrameP.  Assume it is Frame Number 'frameNumber' and is of
+   type 'frameType'.
+
+   Increment *framesReadP.
+   
+   Add the time it took to read it, in seconds, to *iotimeP.
+
+   Iff we can't read because we hit end of file, return
+   *endOfStreamP == TRUE and *framesReadP and *iotimeP untouched.
+-----------------------------------------------------------------------------*/
+    /* This really should be part of ReadNthFrame.  The frame should be chained
+       to the input object, not the past reference frame.
+    */
+       
+    MpegFrame * p;
+    MpegFrame * frameP;
+    time_t ioTimeStart, ioTimeEnd;
+    
+    time(&ioTimeStart);
+
+    frameP = Frame_New(frameNumber, frameType);
+    ReadFrame(frameP, inputSourceP, frameNumber, inputConversion,
+              endOfStreamP);
+
+    if (*endOfStreamP)
+        Frame_Free(frameP);
+    else {
+        ++(*framesReadP);
+    
+        time(&ioTimeEnd);
+        *ioTimeP += (ioTimeEnd - ioTimeStart);
+
+        /* Add the B frame to the end of the queue of B-frames 
+           for later encoding.
+        */
+        assert(pastRefFrameP != NULL);
+        
+        p = pastRefFrameP;
+        while (p->next != NULL)
+            p = p->next;
+        p->next = frameP;
+    }
+}
+
+
+
+static void
+doFirstFrameStuff(enum frameContext const context,
+                  const char *      const userDataFileName,
+                  BitBucket *       const bb,
+                  int               const fsize_x,
+                  int               const fsize_y,
+                  int               const aspectRatio,
+                  int               const frameRate,
+                  int32             const qtable[],
+                  int32             const niqtable[],
+                  unsigned int *    const inputFrameBitsP) {
+/*----------------------------------------------------------------------------
+   Do stuff we have to do after reading the first frame in a sequence
+   of frames requested of GenMPEGStream().
+-----------------------------------------------------------------------------*/
+    *inputFrameBitsP = 24*Fsize_x*Fsize_y;
+    SetBlocksPerSlice();
+          
+    if (context == CONTEXT_WHOLESTREAM) {
+        int32 const bitstreamMode = getRateMode();
+        char * userData;
+        unsigned int userDataSize;
+
+        assert(bb != NULL);
+
+        DBG_PRINT(("Generating sequence header\n"));
+        if (bitstreamMode == FIXED_RATE) {
+            bit_rate = getBitRate();
+            buf_size = getBufferSize();
+        } else {
+            bit_rate = -1;
+            buf_size = -1;
+        }
+        
+        if (strlen(userDataFileName) != 0) {
+            struct stat statbuf;
+            FILE *fp;
+          
+            stat(userDataFileName,&statbuf);
+            userDataSize = statbuf.st_size;
+            userData = malloc(userDataSize);
+            fp = fopen(userDataFileName,"rb");
+            if (fp == NULL) {
+                pm_message("Could not open userdata file '%s'.",
+                           userDataFileName);
+                userData = NULL;
+                userDataSize = 0;
+            } else {
+                size_t bytesRead;
+
+                bytesRead = fread(userData,1,userDataSize,fp);
+                if (bytesRead != userDataSize) {
+                    pm_message("Could not read %d bytes from "
+                               "userdata file '%s'.",
+                               userDataSize,userDataFileName);
+                    userData = NULL;
+                    userDataSize = 0;
+                }
+            }
+        } else { /* Put in our UserData Header */
+            const char * userDataString;
+            time_t now;
+                    
+            time(&now);
+            asprintfN(&userDataString,"MPEG stream encoded by UCB Encoder "
+                      "(mpeg_encode) v%s on %s.",
+                      VERSION, ctime(&now));
+            userData = strdup(userDataString);
+            userDataSize = strlen(userData);
+            strfree(userDataString);
+        }
+        Mhead_GenSequenceHeader(bb, Fsize_x, Fsize_y,
+                                /* pratio */ aspectRatio,
+                                /* pict_rate */ frameRate, 
+                                /* bit_rate */ bit_rate,
+                                /* buf_size */ buf_size,
+                                /*c_param_flag */ 1,
+                                /* iq_matrix */ qtable, 
+                                /* niq_matrix */ niqtable,
+                                /* ext_data */ NULL,
+                                /* ext_data_size */ 0,
+                                /* user_data */ (uint8*) userData,
+                                /* user_data_size */ userDataSize);
+    }
+}
+
+
+
+static void
+getPreviousFrame(unsigned int         const frameStart,
+                 int                  const referenceFrame,
+                 struct inputSource * const inputSourceP,
+                 boolean              const childProcess,
+                 const char *         const slaveConversion,
+                 const char *         const inputConversion,
+                 MpegFrame **         const framePP,
+                 unsigned int *       const framesReadP,
+                 int *                const ioTimeP) {
+
+    /* This needs to be modularized.  It shouldn't issue messages about
+       encoding GOPs and B frames, since it knows nothing about those.
+       It should work for Standard Input too, through a generic Standard
+       Input reader that buffers stuff for backward reading.
+    */
+
+    MpegFrame * frameP;
+    time_t ioTimeStart, ioTimeEnd;
+
+    /* can't find the previous frame interactively */
+    if (inputSourceP->stdinUsed)
+        pm_error("Cannot encode GOP from stdin when "
+                 "first frame is a B-frame.");
+
+    if (frameStart < 1)
+        pm_error("Cannot encode GOP when first frame is a B-frame "
+                 "and is not preceded by anything.");
+
+    /* need to load in previous frame; call it an I frame */
+    frameP = Frame_New(frameStart-1, 'i');
+
+    time(&ioTimeStart);
+
+    if ((referenceFrame == DECODED_FRAME) && childProcess) {
+        WaitForDecodedFrame(frameStart);
+
+        if (remoteIO)
+            GetRemoteDecodedRefFrame(frameP, frameStart - 1);
+        else
+            ReadDecodedRefFrame(frameP, frameStart - 1);
+    } else {
+        bool endOfStream;
+        ReadNthFrame(inputSourceP, frameStart - 1, remoteIO, childProcess,
+                     separateConversion, slaveConversion, inputConversion,
+                     frameP, &endOfStream);
+        assert(!endOfStream);  /* Because Stdin causes failure above */
+    }            
+    ++(*framesReadP);
+    
+    time(&ioTimeEnd);
+    *ioTimeP += (ioTimeEnd-ioTimeStart);
+
+    *framePP = frameP;
+}
+
+
+
+static void
+computeFrameRange(unsigned int         const frameStart,
+                  unsigned int         const frameEnd,
+                  enum frameContext    const context, 
+                  struct inputSource * const inputSourceP,
+                  unsigned int *       const firstFrameP,
+                  unsigned int *       const lastFrameP) {
+
+    switch (context) {
+    case CONTEXT_GOP:
+        *firstFrameP = frameStart;
+        *lastFrameP  = frameEnd;
+        break;
+    case CONTEXT_JUSTFRAMES: {
+        /* if first frame is P or B, need to read in P or I frame before it */
+        if (FType_Type(frameStart) != 'i') {
+            /* can't find the previous frame interactively */
+            if (inputSourceP->stdinUsed)
+                pm_error("Cannot encode frames from Standard Input "
+                         "when first frame is not an I-frame.");
+
+            *firstFrameP = FType_PastRef(frameStart);
+        } else
+            *firstFrameP = frameStart;
+
+        /* if last frame is B, need to read in P or I frame after it */
+        if ((FType_Type(frameEnd) == 'b') && 
+            (frameEnd != inputSourceP->numInputFiles-1)) {
+            /* can't find the next reference frame interactively */
+            if (inputSourceP->stdinUsed)
+                pm_error("Cannot encode frames from Standard Input "
+                         "when last frame is a B-frame.");
+            
+            *lastFrameP = FType_FutureRef(frameEnd);
+        } else
+            *lastFrameP = frameEnd;
+    }
+    break;
+    case CONTEXT_WHOLESTREAM:
+        *firstFrameP = frameStart;
+        *lastFrameP  = frameEnd;
+    }
+}
+
+
+
+static void
+getFrame(MpegFrame **         const framePP,
+         struct inputSource * const inputSourceP,
+         unsigned int         const frameNumber,
+         char                 const frameType,
+         unsigned int         const realStart,
+         unsigned int         const realEnd,
+         int                  const referenceFrame,
+         boolean              const childProcess,
+         boolean              const remoteIo,
+         boolean              const separateConversion,
+         const char *         const slaveConversion,
+         const char *         const inputConversion,
+         unsigned int *       const framesReadP,
+         int *                const ioTimeP) {
+/*----------------------------------------------------------------------------
+   Get frame with number 'frameNumber' as *frameP.
+
+   Increment *framesReadP.
+
+   Add to *ioTimeP the time in seconds we spent reading it.
+
+   Iff we fail to get the frame because the stream ends, return
+   *frameP == NULL, don't increment *framesReadP, and leave
+   *ioTimeP unchanged.
+-----------------------------------------------------------------------------*/
+    time_t ioTimeStart, ioTimeEnd;
+    MpegFrame * frameP;
+    bool endOfStream;
+    
+    time(&ioTimeStart);
+
+    frameP = Frame_New(frameNumber, frameType);
+            
+    if ((referenceFrame == DECODED_FRAME) &&
+        ((frameNumber < realStart) || (frameNumber > realEnd)) ) {
+        WaitForDecodedFrame(frameNumber);
+
+        if (remoteIo)
+            GetRemoteDecodedRefFrame(frameP, frameNumber);
+        else
+            ReadDecodedRefFrame(frameP, frameNumber);
+
+        /* I don't know what this block of code does, so I don't know
+           what endOfStream should be.  Here's a guess:
+        */
+        endOfStream = FALSE;
+    } else
+        ReadNthFrame(inputSourceP, frameNumber, remoteIO, childProcess,
+                     separateConversion, slaveConversion, inputConversion,
+                     frameP, &endOfStream);
+    
+    if (endOfStream) {
+        Frame_Free(frameP);
+        *framePP = NULL;
+    } else {
+        ++(*framesReadP);
+            
+        time(&ioTimeEnd);
+        *ioTimeP += (ioTimeEnd - ioTimeStart);
+
+        *framePP = frameP;
+    }
+}
+
+
+
+static void
+handleBitRate(unsigned int const realEnd,
+              unsigned int const numBits,
+              boolean      const childProcess,
+              boolean      const showBitRatePerFrame) {
+
+    extern void PrintItoIBitRate (int numBits, int frameNum);
+
+    if (FType_Type(realEnd) != 'i')
+        PrintItoIBitRate(numBits, realEnd+1);
+
+    if ((!childProcess) && showBitRatePerFrame)
+        CloseBitRateFile();
+}
+
+
+
+static void
+doAFrame(unsigned int         const frameNumber,
+         struct inputSource * const inputSourceP,
+         enum frameContext    const context, 
+         unsigned int         const frameStart, 
+         unsigned int         const frameEnd, 
+         unsigned int         const realStart,
+         unsigned int         const realEnd,
+         bool                 const childProcess,
+         const char *         const outputFileName,
+         MpegFrame *          const pastRefFrameP,
+         MpegFrame **         const newPastRefFramePP,
+         unsigned int *       const framesReadP,
+         unsigned int *       const framesOutputP,
+         bool *               const firstFrameDoneP,
+         BitBucket *          const bbP,
+         unsigned int *       const inputFrameBitsP,
+         bool *               const endOfStreamP) {
+/*----------------------------------------------------------------------------
+   *endOfStreamP returned means we were unable to do a frame because
+   the input stream has ended.  In that case, none of the other outputs
+   are valid.
+-----------------------------------------------------------------------------*/
+    char const frameType = FType_Type(frameNumber);
+    
+    *endOfStreamP = FALSE;  /* initial assumption */
+
+    if (frameType == 'b') {
+        /* We'll process this non-reference frame later.  If reading
+           from stdin, we read it now and save it.  Otherwise, we can
+           just read it later.
+        */
+        *newPastRefFramePP = pastRefFrameP;
+        if (inputSourceP->stdinUsed) 
+            readAndSaveFrame(inputSourceP,
+                             frameNumber, frameType, inputConversion,
+                             pastRefFrameP, framesReadP, &IOtime,
+                             endOfStreamP);
+    } else {
+        MpegFrame * frameP;
+        
+        getFrame(&frameP, inputSourceP, frameNumber, frameType,
+                 realStart, realEnd, referenceFrame, childProcess,
+                 remoteIO,
+                 separateConversion, slaveConversion, inputConversion,
+                 framesReadP, &IOtime);
+        
+        if (frameP) {
+            *endOfStreamP = FALSE;
+
+            if (!*firstFrameDoneP) {
+                doFirstFrameStuff(context, userDataFileName,
+                                  bbP, Fsize_x, Fsize_y, aspectRatio,
+                                  frameRate, qtable, niqtable, 
+                                  inputFrameBitsP);
+            
+                *firstFrameDoneP = TRUE;
+            }
+            processRefFrame(frameP, bbP, frameStart, frameEnd,
+                            pastRefFrameP, childProcess, outputFileName, 
+                            framesReadP, framesOutputP);
+                
+            if (pastRefFrameP) {
+                processBFrames(pastRefFrameP, frameP, realStart, realEnd,
+                               inputSourceP, remoteIO, childProcess, 
+                               &IOtime, bbP, outputFileName,
+                               framesReadP, framesOutputP, &currentGOP);
+            }
+            if (pastRefFrameP != NULL)
+                Frame_Free(pastRefFrameP);
+        
+            *newPastRefFramePP = frameP;
+        } else
+            *endOfStreamP = TRUE;
+    }
+}
+
+
+
+void
+GenMPEGStream(struct inputSource * const inputSourceP,
+              enum frameContext    const context, 
+              unsigned int         const frameStart, 
+              unsigned int         const frameEnd, 
+              int32                const qtable[], 
+              int32                const niqtable[], 
+              bool                 const childProcess,
+              FILE *               const ofP, 
+              const char *         const outputFileName,
+              bool                 const wantVbvUnderflowWarning,
+              bool                 const wantVbvOverflowWarning,
+              unsigned int *       const inputFrameBitsP,
+              unsigned int *       const totalBitsP) {
+/*----------------------------------------------------------------------------
+   Encode a bunch of frames into an MPEG sequence stream or a part thereof.
+
+   'context' tells what in addition to the frames themselves must go into
+   the stream:
+
+      CONTEXT_JUSTFRAMES:  Nothing but the indicated frames
+      CONTEXT_GOP:         GOP header/trailer stuff to make a single GOP
+                           that contains the indicated frames
+      CONTEXT_WHOLESTREAM: A whole stream consisting of the indicated
+                           frames -- a sequence of whole GOPS, with stream
+                           header/trailer stuff as well.
+
+   'frameStart' and 'frameEnd' are the numbers of the first and last
+   frames we are to encode, except that if the input source is a stream,
+   we stop where the stream ends if that is before 'frameEnd'.
+
+-----------------------------------------------------------------------------*/
+    BitBucket * bbP;
+    unsigned int frameNumber;
+    bool endOfStream;
+    bool firstFrameDone;
+    int numBits;
+    unsigned int firstFrame, lastFrame;
+    /* Frame numbers of the first and last frames we look at.  This
+       could be more than the the frames we actually encode because
+       we may need context (i.e. to encode a B frame, we need the subsequent
+       I or P frame).
+    */
+    unsigned int framesRead;
+        /* Number of frames we have read; for statistical purposes */
+    MpegFrame * pastRefFrameP;
+        /* The frame that will be the past reference frame for the next
+           B or P frame that we put into the stream
+        */
+    if (frameEnd + 1 > inputSourceP->numInputFiles)
+        pm_error("Last frame (number %u) is beyond the end of the stream "
+                 "(%u frames)", frameEnd, inputSourceP->numInputFiles);
+
+    if (context == CONTEXT_WHOLESTREAM &&
+        !inputSourceP->stdinUsed && 
+        FType_Type(inputSourceP->numInputFiles-1) == 'b')
+        pm_message("WARNING:  "
+                   "One or more B-frames at end will not be encoded.  "
+                   "See FORCE_ENCODE_LAST_FRAME parameter file statement.");
+
+    time(&timeStart);
+
+    framesRead = 0;
+
+    ResetIFrameStats();
+    ResetPFrameStats();
+    ResetBFrameStats();
+
+    Fsize_Reset();
+
+    framesOutput = 0;
+
+    if (childProcess && separateConversion)
+        SetFileType(slaveConversion);
+    else
+        SetFileType(inputConversion);
+
+    realStart = frameStart;
+    realEnd   = frameEnd;
+
+    computeFrameRange(frameStart, frameEnd, context, inputSourceP,
+                      &firstFrame, &lastFrame);
+
+    if (context == CONTEXT_GOP && FType_Type(frameStart) == 'b')
+        getPreviousFrame(frameStart, referenceFrame, inputSourceP,
+                         childProcess, slaveConversion, inputConversion,
+                         &pastRefFrameP, &framesRead, &IOtime);
+    else
+        pastRefFrameP = NULL;
+
+    countFrames(firstFrame, lastFrame, inputSourceP->stdinUsed,
+                &numI, &numP, &numB, &timeMask, &frameCountsUnknown);
+
+    if (showBitRatePerFrame)
+        OpenBitRateFile();  /* May modify showBitRatePerFrame */
+
+    if (context == CONTEXT_WHOLESTREAM)
+        bbP = Bitio_New(ofP);
+    else
+        bbP = NULL;
+
+    initTCTime(firstFrame);
+
+    totalFramesSent = firstFrame;
+    currentGOP = gopSize;        /* so first I-frame generates GOP Header */
+
+    initializeRateControl(wantVbvUnderflowWarning, wantVbvOverflowWarning);
+
+    firstFrameDone = FALSE;
+    for (frameNumber = firstFrame, endOfStream = FALSE;
+         frameNumber <= lastFrame && !endOfStream;
+         ++frameNumber) {
+
+        doAFrame(frameNumber, inputSourceP, context, 
+                 frameStart, frameEnd, realStart, realEnd,
+                 childProcess, outputFileName,
+                 pastRefFrameP, &pastRefFrameP,
+                 &framesRead, &framesOutput, &firstFrameDone, bbP,
+                 inputFrameBitsP, &endOfStream);
+    }
+    
+    if (pastRefFrameP != NULL)
+        Frame_Free(pastRefFrameP);
+    
+    /* SEQUENCE END CODE */
+    if (context == CONTEXT_WHOLESTREAM)
+        Mhead_GenSequenceEnder(bbP);
+    
+    if (context == CONTEXT_WHOLESTREAM)
+        numBits = bbP->cumulativeBits;
+    else {
+        /* What should the correct value be?  Most likely 1.  "numBits" is
+           used below, so we need to make sure it's properly initialized 
+           to somthing (anything).  
+        */
+        numBits = 1;
+    }
+
+    if (context != CONTEXT_JUSTFRAMES) {
+        Bitio_Flush(bbP);
+        bbP = NULL;
+        fclose(ofP);
+    }
+    handleBitRate(realEnd, numBits, childProcess, showBitRatePerFrame);
+
+    *totalBitsP  = numBits;
+}
+
+
+
+/*===========================================================================*
+ *
+ * SetStatFileName
+ *
+ *  set the statistics file name
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    statFileName
+ *
+ *===========================================================================*/
+void
+SetStatFileName(const char * const fileName) {
+    strcpy(statFileName, fileName);
+}
+
+
+/*===========================================================================*
+ *
+ * SetGOPSize
+ *
+ *  set the GOP size (frames per GOP)
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    gopSize
+ *
+ *===========================================================================*/
+void
+SetGOPSize(size)
+    int size;
+{
+    gopSize = size;
+}
+
+
+/*===========================================================================*
+ *
+ * PrintStartStats
+ *
+ *  print out the starting statistics (stuff from the param file)
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+PrintStartStats(time_t               const startTime, 
+                bool                 const specificFrames,
+                unsigned int         const firstFrame, 
+                unsigned int         const lastFrame,
+                struct inputSource * const inputSourceP) {
+
+    FILE *fpointer;
+    int i;
+
+    if (statFileName[0] == '\0') {
+        statFile = NULL;
+    } else {
+        statFile = fopen(statFileName, "a");    /* open for appending */
+        if (statFile == NULL) {
+            fprintf(stderr, "ERROR:  Could not open stat file:  %s\n",
+                    statFileName);
+            fprintf(stderr, "        Sending statistics to stdout only.\n");
+            fprintf(stderr, "\n\n");
+        } else if (! realQuiet) {
+            fprintf(stdout, "Appending statistics to file:  %s\n",
+                    statFileName);
+            fprintf(stdout, "\n\n");
+        }
+    }
+    
+    for (i = 0; i < 2; ++i) {
+        if ( ( i == 0 ) && (! realQuiet) ) {
+            fpointer = stdout;
+        } else if ( statFile != NULL ) {
+            fpointer = statFile;
+        } else {
+            continue;
+        }
+
+        fprintf(fpointer, "MPEG ENCODER STATS (%s)\n",VERSION);
+        fprintf(fpointer, "------------------------\n");
+        fprintf(fpointer, "TIME STARTED:  %s", ctime(&startTime));
+        if (getenv("HOST") != NULL)
+            fprintf(fpointer, "MACHINE:  %s\n", getenv("HOST"));
+        else
+            fprintf(fpointer, "MACHINE:  unknown\n");
+
+        if (inputSourceP->stdinUsed)
+            fprintf(fpointer, "INPUT:  stdin\n");
+        else {
+            const char * inputFileName;
+
+            fprintf(fpointer, "INPUT FROM FILES:\n");
+
+            GetNthInputFileName(inputSourceP, 0, &inputFileName);
+            fprintf(fpointer, "FIRST FILE:  %s/%s\n", 
+                    currentPath, inputFileName);
+            strfree(inputFileName);
+            GetNthInputFileName(inputSourceP, inputSourceP->numInputFiles-1, 
+                                &inputFileName);
+            fprintf(fpointer, "LAST FILE:  %s/%s\n", 
+                    currentPath, inputFileName);
+            strfree(inputFileName);
+        }    
+        fprintf(fpointer, "OUTPUT:  %s\n", outputFileName);
+
+        if (resizeFrame)
+            fprintf(fpointer, "RESIZED TO:  %dx%d\n",
+                    outputWidth, outputHeight);
+        fprintf(fpointer, "PATTERN:  %s\n", framePattern);
+        fprintf(fpointer, "GOP_SIZE:  %d\n", gopSize);
+        fprintf(fpointer, "SLICES PER FRAME:  %d\n", slicesPerFrame);
+        if (searchRangeP==searchRangeB)
+            fprintf(fpointer, "RANGE:  +/-%d\n", searchRangeP/2);
+        else fprintf(fpointer, "RANGES:  +/-%d %d\n", 
+                     searchRangeP/2,searchRangeB/2);
+        fprintf(fpointer, "PIXEL SEARCH:  %s\n", 
+                pixelFullSearch ? "FULL" : "HALF");
+        fprintf(fpointer, "PSEARCH:  %s\n", PSearchName());
+        fprintf(fpointer, "BSEARCH:  %s\n", BSearchName());
+        fprintf(fpointer, "QSCALE:  %d %d %d\n", qscaleI, 
+                GetPQScale(), GetBQScale());
+        if (specificsOn) 
+            fprintf(fpointer, "(Except as modified by Specifics file)\n");
+        if ( referenceFrame == DECODED_FRAME ) {
+            fprintf(fpointer, "REFERENCE FRAME:  DECODED\n");
+        } else if ( referenceFrame == ORIGINAL_FRAME ) {
+            fprintf(fpointer, "REFERENCE FRAME:  ORIGINAL\n");
+        } else
+            pm_error("Illegal referenceFrame!!!");
+
+        /*  For new Rate control parameters */
+        if (getRateMode() == FIXED_RATE) {
+            fprintf(fpointer, "PICTURE RATE:  %d\n", frameRateRounded);
+            if (getBitRate() != -1) {
+                fprintf(fpointer, "\nBIT RATE:  %d\n", getBitRate());
+            }
+            if (getBufferSize() != -1) {
+                fprintf(fpointer, "BUFFER SIZE:  %d\n", getBufferSize());
+            }
+        }
+    }
+    if (!realQuiet)
+        fprintf(stdout, "\n\n");
+}
+
+
+
+boolean
+NonLocalRefFrame(int const id) {
+/*----------------------------------------------------------------------------
+   Return TRUE if frame number 'id' might be referenced from a non-local
+   process.  This is a conservative estimate.  We return FALSE iff there
+   is no way based on the information we have that the frame could be
+   referenced by a non-local process.
+-----------------------------------------------------------------------------*/
+    boolean retval;
+
+    int const lastIPid = FType_PastRef(id);
+    
+    /* might be accessed by B-frame */
+    
+    if (lastIPid+1 < realStart)
+        retval = TRUE;
+    else {
+        unsigned int const nextIPid = FType_FutureRef(id);
+        
+        /* if B-frame is out of range, then current frame can be
+           ref'd by it 
+        */
+        
+        /* might be accessed by B-frame */
+        if (nextIPid > realEnd+1)
+            retval = TRUE;
+        
+        /* might be accessed by P-frame */
+        if ((nextIPid > realEnd) && (FType_Type(nextIPid) == 'p'))
+            retval = TRUE;
+    }
+    return retval;
+}
+
+
+ 
+/*===========================================================================*
+ *
+ * SetFrameRate
+ *
+ *  sets global frame rate variables.  value passed is MPEG frame rate code.
+ *
+ * RETURNS: TRUE or FALSE
+ *
+ * SIDE EFFECTS:    frameRateRounded, frameRateInteger
+ *
+ *===========================================================================*/
+void
+SetFrameRate()
+{
+    switch(frameRate) {
+    case 1:
+        frameRateRounded = 24;
+        frameRateInteger = FALSE;
+        break;
+    case 2:
+        frameRateRounded = 24;
+        frameRateInteger = TRUE;
+        break;
+    case 3:
+        frameRateRounded = 25;
+        frameRateInteger = TRUE;
+        break;
+    case 4:
+        frameRateRounded = 30;
+        frameRateInteger = FALSE;
+        break;
+    case 5:
+        frameRateRounded = 30;
+        frameRateInteger = TRUE;
+        break;
+    case 6:
+        frameRateRounded = 50;
+        frameRateInteger = TRUE;
+        break;
+    case 7:
+        frameRateRounded = 60;
+        frameRateInteger = FALSE;
+        break;
+    case 8:
+        frameRateRounded = 60;
+        frameRateInteger = TRUE;
+        break;
+    }
+    printf("frame rate(%d) set to %d\n", frameRate, frameRateRounded);
+}
+
+
+/*=====================*
+ * INTERNAL PROCEDURES *
+ *=====================*/
+
+/*===========================================================================*
+ *
+ * ComputeDHMSTime
+ *
+ *  turn some number of seconds (someTime) into a string which
+ *  summarizes that time according to scale (days, hours, minutes, or
+ *  seconds)
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static void
+ComputeDHMSTime(someTime, timeText)
+    int32 someTime;
+    char *timeText;
+{
+    int     days, hours, mins, secs;
+
+    days = someTime / (24*60*60);
+    someTime -= days*24*60*60;
+    hours = someTime / (60*60);
+    someTime -= hours*60*60;
+    mins = someTime / 60;
+    secs = someTime - mins*60;
+
+    if ( days > 0 ) {
+        sprintf(timeText, "Total time:  %d days and %d hours", days, hours);
+    } else if ( hours > 0 ) {
+        sprintf(timeText, "Total time:  %d hours and %d minutes", hours, mins);
+    } else if ( mins > 0 ) {
+        sprintf(timeText, "Total time:  %d minutes and %d seconds", mins, secs);
+    } else {
+    sprintf(timeText, "Total time:  %d seconds", secs);
+    }
+}
+
+
+
+void
+ComputeGOPFrames(int            const whichGOP, 
+                 unsigned int * const firstFrameP, 
+                 unsigned int * const lastFrameP, 
+                 unsigned int   const numFrames) {
+/*----------------------------------------------------------------------------
+   Figure out which frames are in GOP number 'whichGOP'.
+-----------------------------------------------------------------------------*/
+    unsigned int passedB;
+    unsigned int currGOP;
+    unsigned int gopNum;
+    unsigned int frameNum;
+    unsigned int firstFrame, lastFrame;
+    bool foundGop;
+
+    /* calculate first, last frames of whichGOP GOP */
+
+    gopNum = 0;
+    frameNum = 0;
+    passedB = 0;
+    currGOP = 0;
+    foundGop = FALSE;
+
+    while (!foundGop) {
+        if (frameNum >= numFrames)
+            pm_error("There aren't that many GOPs!");
+
+        if (gopNum == whichGOP) {
+            foundGop = TRUE;
+            firstFrame = frameNum;
+        }           
+
+        /* go past one gop */
+        /* must go past at least one frame */
+        do {
+            currGOP += (1 + passedB);
+
+            ++frameNum;
+
+            passedB = 0;
+            while ((frameNum < numFrames) && (FType_Type(frameNum) == 'b')) {
+                ++frameNum;
+                ++passedB;
+            }
+        } while ((frameNum < numFrames) && 
+                 ((FType_Type(frameNum) != 'i') || (currGOP < gopSize)));
+
+        currGOP -= gopSize;
+
+        if (gopNum == whichGOP)
+            lastFrame = (frameNum - passedB - 1);
+
+        ++gopNum;
+    }
+    *firstFrameP = firstFrame;
+    *lastFrameP  = lastFrame;
+}
+
+
+
+static void
+doEndStats(FILE *       const fpointer,
+           time_t       const startTime,
+           time_t       const endTime,
+           unsigned int const inputFrameBits,
+           unsigned int const totalBits,
+           float        const totalCPU) {
+
+    int32 const diffTime = endTime - startTime;
+
+    char    timeText[256];
+
+    ComputeDHMSTime(diffTime, timeText);
+
+    fprintf(fpointer, "TIME COMPLETED:  %s", ctime(&endTime));
+    fprintf(fpointer, "%s\n\n", timeText);
+        
+    ShowIFrameSummary(inputFrameBits, totalBits, fpointer);
+    ShowPFrameSummary(inputFrameBits, totalBits, fpointer);
+    ShowBFrameSummary(inputFrameBits, totalBits, fpointer);
+
+    fprintf(fpointer, "---------------------------------------------\n");
+    fprintf(fpointer, "Total Compression:  %3d:1     (%9.4f bpp)\n",
+            framesOutput*inputFrameBits/totalBits,
+            24.0*(float)(totalBits)/(float)(framesOutput*inputFrameBits));
+    if (diffTime > 0) {
+        fprintf(fpointer, "Total Frames Per Sec Elapsed:  %f (%ld mps)\n",
+                (float)framesOutput/(float)diffTime,
+                (long)((float)framesOutput * 
+                       (float)inputFrameBits /
+                       (256.0*24.0*(float)diffTime)));
+    } else {
+        fprintf(fpointer, "Total Frames Per Sec Elapsed:  Infinite!\n");
+    }
+    if ( totalCPU == 0.0 ) {
+        fprintf(fpointer, "CPU Time:  NONE!\n");
+    } else {
+        fprintf(fpointer, "Total Frames Per Sec CPU    :  %f (%ld mps)\n",
+                (float)framesOutput/totalCPU,
+                (long)((float)framesOutput *
+                       (float)inputFrameBits/(256.0*24.0*totalCPU)));
+    }
+    fprintf(fpointer, "Total Output Bit Rate (%d fps):  %d bits/sec\n",
+            frameRateRounded, frameRateRounded*totalBits/framesOutput);
+    fprintf(fpointer, "MPEG file created in :  %s\n", outputFileName);
+    fprintf(fpointer, "\n\n");
+        
+    if ( computeMVHist ) {
+        ShowPMVHistogram(fpointer);
+        ShowBBMVHistogram(fpointer);
+        ShowBFMVHistogram(fpointer);
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * PrintEndStats
+ *
+ *  print end statistics (summary, time information)
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+PrintEndStats(time_t       const startTime,
+              time_t       const endTime,
+              unsigned int const inputFrameBits, 
+              unsigned int const totalBits) {
+
+    float   totalCPU;
+
+    totalCPU = 0.0;
+    totalCPU += IFrameTotalTime();
+    totalCPU += PFrameTotalTime();
+    totalCPU += BFrameTotalTime();
+
+    if (!realQuiet) {
+        fprintf(stdout, "\n\n");
+        doEndStats(stdout, startTime, endTime, inputFrameBits,
+                   totalBits, totalCPU);
+    }
+    
+    if (statFile) {
+        doEndStats(statFile, startTime, endTime, inputFrameBits,
+                   totalBits, totalCPU);
+
+        fclose(statFile);
+    }
+}
+
+
+
+void
+ReadDecodedRefFrame(MpegFrame *  const frameP, 
+                    unsigned int const frameNumber) {
+
+    FILE    *fpointer;
+    char    fileName[256];
+    int width, height;
+    register int y;
+
+    width = Fsize_x;
+    height = Fsize_y;
+
+    sprintf(fileName, "%s.decoded.%u", outputFileName, frameNumber);
+    if (! realQuiet) {
+        fprintf(stdout, "reading %s\n", fileName);
+        fflush(stdout);
+    }
+
+    if ((fpointer = fopen(fileName, "rb")) == NULL) {
+        sleep(1);
+        if ((fpointer = fopen(fileName, "rb")) == NULL) {
+            fprintf(stderr, "Cannot open %s\n", fileName);
+            exit(1);
+        }}
+
+    Frame_AllocDecoded(frameP, TRUE);
+    
+    for ( y = 0; y < height; y++ ) {
+        size_t bytesRead;
+
+        bytesRead = fread(frameP->decoded_y[y], 1, width, fpointer);
+        if (bytesRead != width)
+            pm_error("Could not read enough bytes from '%s;", fileName);
+    }
+    
+    for (y = 0; y < (height >> 1); y++) {           /* U */
+        size_t const bytesToRead = width/2;
+        size_t bytesRead;
+
+        bytesRead = fread(frameP->decoded_cb[y], 1, bytesToRead, fpointer);
+        if (bytesRead != bytesToRead)
+            pm_message("Could not read enough bytes from '%s'", fileName);
+    }
+    
+    for (y = 0; y < (height >> 1); y++) {           /* V */
+        size_t const bytesToRead = width/2;
+        size_t bytesRead;
+        bytesRead = fread(frameP->decoded_cr[y], 1, bytesToRead, fpointer);
+        if (bytesRead != bytesToRead)
+            pm_message("Could not read enough bytes from '%s'", fileName);
+    }
+    fclose(fpointer);
+}
+
+
+
+static void
+OpenBitRateFile() {
+    bitRateFile = fopen(bitRateFileName, "w");
+    if ( bitRateFile == NULL ) {
+        pm_message("ERROR:  Could not open bit rate file:  '%s'", 
+                   bitRateFileName);
+        showBitRatePerFrame = FALSE;
+    }
+}
+
+
+
+static void
+CloseBitRateFile() {
+    fclose(bitRateFile);
+}
diff --git a/converter/ppm/ppmtompeg/mquant.c b/converter/ppm/ppmtompeg/mquant.c
new file mode 100644
index 00000000..1f8ca63a
--- /dev/null
+++ b/converter/ppm/ppmtompeg/mquant.c
@@ -0,0 +1,44 @@
+#include "mtypes.h"
+#include "mproto.h"
+
+static int qtable[][8] = {
+    { 8,16,19,22,26,27,29,34},
+    {16,16,22,24,27,29,34,37},
+    {19,22,26,27,29,34,34,38},
+    {22,22,26,27,29,34,37,40},
+    {22,26,27,29,32,35,40,48},
+    {26,27,29,32,35,40,48,58},
+    {26,27,29,34,38,46,56,69},
+    {27,29,35,38,46,56,69,83} };
+
+
+/*
+ *--------------------------------------------------------------
+ *
+ * mp_quant_block --
+ *
+ *	Quantizes a block -- removing information
+ *	It's safe for out == in.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	Modifies the out block.
+ *
+ *--------------------------------------------------------------
+ */
+void mp_quant_block(Block in, Block out) {
+    int i;
+
+    for(i=0;i<8;i++) {
+	out[i][0] = in[i][0] / qtable[i][0];
+	out[i][1] = in[i][1] / qtable[i][1];
+	out[i][2] = in[i][2] / qtable[i][2];
+	out[i][3] = in[i][3] / qtable[i][3];
+	out[i][4] = in[i][4] / qtable[i][4];
+	out[i][5] = in[i][5] / qtable[i][5];
+	out[i][6] = in[i][6] / qtable[i][6];
+	out[i][7] = in[i][7] / qtable[i][7];
+    }
+}
diff --git a/converter/ppm/ppmtompeg/nojpeg.c b/converter/ppm/ppmtompeg/nojpeg.c
new file mode 100644
index 00000000..38c05a9e
--- /dev/null
+++ b/converter/ppm/ppmtompeg/nojpeg.c
@@ -0,0 +1,61 @@
+/*===========================================================================*
+ * nojpeg.c								     *
+ *									     *
+ *	procedures to deal with JPEG files				     *
+ *									     *
+ * EXPORTED PROCEDURES:							     *
+ *	JMovie2JPEG							     *
+ *      ReadJPEG							     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+#include "all.h"
+#include "mtypes.h"
+#include "frames.h"
+#include "prototypes.h"
+#include "param.h"
+#include "readframe.h"
+#include "fsize.h"
+#include "rgbtoycc.h"
+#include "jpeg.h"
+
+
+void
+JMovie2JPEG(const char * const infilename,
+            const char * const obase,
+            int          const start,
+            int          const end) {
+
+    pm_error("This program has not been built with the "
+             "ability to handle JPEG input files");
+}
+
+
+void
+ReadJPEG(MpegFrame * const mf,
+         FILE *      const fp) {
+
+    pm_error("This program has not been built with the "
+             "ability to handle JPEG input files");
+}
diff --git a/converter/ppm/ppmtompeg/noparallel.c b/converter/ppm/ppmtompeg/noparallel.c
new file mode 100644
index 00000000..016e3c44
--- /dev/null
+++ b/converter/ppm/ppmtompeg/noparallel.c
@@ -0,0 +1,229 @@
+/*===========================================================================*
+ *  noparallel.c
+ *
+ *  Would be procedures to make encoder to run in parallel -- except
+ *  this machine doesn't have sockets, so we can only run sequentially
+ *  so this file has dummy procedures which lets it compile
+ *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include <time.h>
+
+#include <pm.h>
+
+#include "all.h"
+#include "mtypes.h"
+#include "parallel.h"
+#include "frame.h"
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+int parallelTestFrames = 10;
+int parallelTimeChunks = 60;
+const char *IOhostName;
+int ioPortNumber;
+int combinePortNumber;
+int decodePortNumber;
+boolean niceProcesses = FALSE;
+boolean forceIalign = FALSE;
+int     machineNumber = -1;
+boolean remoteIO = FALSE;
+boolean separateConversion;
+time_t  IOtime = 0;
+
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+
+/*=================*
+ * IO SERVER STUFF *
+ *=================*/
+
+
+void
+IoServer(struct inputSource * const inputSourceP,
+         const char *         const parallelHostName, 
+         int                  const portNum) {
+
+    pm_error("This version of Ppmtompeg cannot run an I/O server because "
+             "it does not have socket capability.");
+}
+
+
+
+void
+SetIOConvert(boolean const separate) {
+    /* do nothing -- this may be called during non-parallel execution */
+}
+
+
+
+void
+SetParallelPerfect(boolean const val) {
+    /* do nothing -- this may be called during non-parallel execution */
+}
+
+
+void
+SetRemoteShell(const char * const shell) {
+    /* do nothing -- this may be called during non-parallel execution */
+}
+
+
+
+void
+NoteFrameDone(int const frameStart,
+              int const frameEnd) {
+    fprintf(stdout, 
+            "ERROR:  (NoteFrameDone) "
+            "This machine can NOT run parallel version\n");
+    exit(1);
+}
+
+
+
+/* SendRemoteFrame
+ */
+void
+SendRemoteFrame(int         const frameNumber,
+                BitBucket * const bb) {
+    fprintf(stdout, "ERROR:  (SendRemoteFrame) "
+            "This machine can NOT run parallel version\n");
+    exit(1);
+}
+
+
+
+/* GetRemoteFrame
+ */
+void
+GetRemoteFrame(MpegFrame * const frame,
+               int         const frameNumber) {
+
+    fprintf(stdout, "ERROR:  (GetRemoteFrame) "
+            "This machine can NOT run parallel version\n");
+    exit(1);
+}
+
+
+
+void
+WaitForOutputFile(int const number) {
+    fprintf(stdout, "ERROR:  (WaitForOutputFile) "
+            "This machine can NOT run parallel version\n");
+    exit(1);
+}
+
+
+
+/*=======================*
+ * PARALLEL SERVER STUFF *
+ *=======================*/
+
+
+void
+MasterServer(struct inputSource * const inputSourceP,
+             const char *         const paramFileName, 
+             const char *         const outputFileName) {
+
+    pm_error("This version of Ppmtompeg cannot run a master server because "
+             "it does not have socket capability.");
+}
+
+
+
+void
+CombineServer(int          const numFrames, 
+              const char * const masterHostName, 
+              int          const masterPortNum) {
+
+    pm_error("This version of Ppmtompeg cannot run combine server because "
+             "it does not have socket capability.");
+}
+
+
+
+void
+DecodeServer(int          const numInputFiles, 
+             const char * const decodeFileName, 
+             const char * const masterHostName, 
+             int          const masterPortNum) {
+
+    pm_error("This version of Ppmtompeg cannot run a decode server because "
+             "it does not have socket capability.");
+}
+
+
+
+void
+NotifyMasterDone(const char * const hostName, 
+                 int          const portNum, 
+                 int          const machineNumber, 
+                 unsigned int const seconds,
+                 boolean *    const moreWorkToDoP,
+                 int *        const frameStartP,
+                 int *        const frameEndP) {
+    pm_error("This version of Ppmtompeg cannot run parallel mode because "
+             "it does not have socket capability.");
+}
+
+
+
+void
+NotifyDecodeServerReady(int const id) {
+    pm_error("This version of Ppmtompeg cannot run parallel mode because "
+             "it does not have socket capability.");
+}
+
+
+
+void
+WaitForDecodedFrame(int const id) {
+    pm_error("This version of Ppmtompeg cannot run parallel mode because "
+             "it does not have socket capability.");
+}
+
+
+
+void
+SendDecodedFrame(MpegFrame * const frame) {
+    pm_error("This version of Ppmtompeg cannot run parallel mode because "
+             "it does not have socket capability.");
+}
+
+
+
+void
+GetRemoteDecodedRefFrame(MpegFrame * const frame,
+                         int         const frameNumber) {
+    pm_error("This version of Ppmtompeg cannot run parallel mode because "
+             "it does not have socket capability.");
+}
diff --git a/converter/ppm/ppmtompeg/opts.c b/converter/ppm/ppmtompeg/opts.c
new file mode 100644
index 00000000..c4d0c0b3
--- /dev/null
+++ b/converter/ppm/ppmtompeg/opts.c
@@ -0,0 +1,536 @@
+/*===========================================================================*
+ * opts.c								     *
+ *									     *
+ *      Special C code to handle TUNEing options                             *
+ *									     *
+ * EXPORTED PROCEDURES:							     *
+ *      Tune_Init                                                            *
+ *      CollectQuantStats                                                    *
+ *									     *
+ *===========================================================================*/
+
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ * 
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ * 
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include "opts.h"
+
+/*==============*
+ * EXTERNALS    *
+ *==============*/
+
+extern char    outputFileName[];
+extern boolean pureDCT;
+extern int32   qtable[], niqtable[];
+extern int     ZAG[];
+extern boolean printSNR, decodeRefFrames;
+
+void init_idctref _ANSI_ARGS_((void));
+void init_fdct _ANSI_ARGS_((void));
+
+
+/*===================*
+ * GLOBALS MADE HERE *
+ *===================*/
+
+boolean tuneingOn = FALSE;
+int block_bound = 128;
+boolean collect_quant = FALSE;
+int collect_quant_detailed = 0;
+FILE *collect_quant_fp;
+int kill_dim = FALSE;
+int kill_dim_break, kill_dim_end;
+float kill_dim_slope;
+int SearchCompareMode = DEFAULT_SEARCH;
+boolean squash_small_differences = FALSE;
+int SquashMaxLum, SquashMaxChr;
+float LocalDCTRateScale = 1.0, LocalDCTDistortScale = 1.0;
+boolean IntraPBAllowed = TRUE;
+boolean WriteDistortionNumbers = FALSE;
+int collect_distortion_detailed = 0;
+FILE *distortion_fp;
+FILE *fp_table_rate[31], *fp_table_dist[31];
+boolean DoLaplace = FALSE;
+double **L1, **L2, **Lambdas;
+int LaplaceNum, LaplaceCnum;
+boolean BSkipBlocks = TRUE;
+
+/* define this as it too much of a pain to find toupper on different arch'es */
+#define ASCII_TOUPPER(c) ((c>='a') && (c<='z')) ? c-'a'+'A' : c
+
+/*===============*
+ * Internals     *
+ *===============*/
+
+/*===========================================================================*
+ *
+ * SkipSpacesTabs
+ *
+ *	skip all spaces and tabs
+ *
+ * RETURNS:	point to next character not a space or tab
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static const char *
+SkipSpacesTabs(const char * const start) {
+
+    const char * p;
+
+    for (p = start; *p == ' ' || *p == '\t'; ++p);
+
+    return p;
+}
+
+
+
+/*===========================================================================*
+ *
+ * SetupCollectQuantStats
+ *
+ *     Setup variables to collect statistics on quantization values
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    sets collect_quant and collect_quant_fp
+ *
+ *===========================================================================*/
+static void 
+SetupCollectQuantStats(const char * const charPtr)
+{
+  char fname[256];
+  const char * cp;
+  cp = charPtr;
+  while ( (*cp != ' ') && (*cp != '\t') && (*cp != '\n')) {
+    cp++;
+  }
+
+  strncpy(fname, charPtr, cp-charPtr);
+  fname[cp-charPtr] = '\0';
+  collect_quant = TRUE;
+  if ((collect_quant_fp = fopen(fname,"w")) == NULL) {
+    fprintf(stderr, "Error opening %s for quant statistics\n", fname);
+    fprintf(stderr, "Using stdout (ick!)\n");
+    collect_quant_fp = stdout;
+  }
+
+  cp = SkipSpacesTabs(cp);
+  if (*cp != '\n') {
+    switch (*cp) {
+    case 'c':
+      collect_quant_detailed = 1;
+      break;
+    default:
+      fprintf(stderr, "Unknown TUNE parameter setting format %s\n", cp);
+    }}
+}
+
+
+
+
+/*===========================================================================*
+ *
+ * SetupKillDimAreas
+ *
+ *     Do a transform on small lum values
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    sets kill_dim, kill_dim_break, kill_dim_end
+ *
+ *===========================================================================*/
+static void 
+SetupKillDimAreas(const char * const charPtr)
+{
+  int items_scanned;
+
+  kill_dim = TRUE;
+  items_scanned = sscanf(charPtr, "%d %d %f", 
+			 &kill_dim_break, &kill_dim_end, &kill_dim_slope);
+  if (items_scanned != 3) {
+    kill_dim_slope = 0.25;
+    items_scanned = sscanf(charPtr, "%d %d", 
+			   &kill_dim_break, &kill_dim_end);
+    if (items_scanned != 2) {
+      /* Use defaults */
+      kill_dim_break = 20;
+      kill_dim_end   = 25;
+    }
+  }
+  /* check values */
+  if (kill_dim_break > kill_dim_end) {
+    fprintf(stderr, "TUNE parameter k: break > end is illegal.\n");
+    exit(-1);
+  }
+  if (kill_dim_slope < 0) {
+    fprintf(stderr, "TUNE parameter k: slope < 0 is illegal.\n");
+    exit(-1);
+  }
+}
+
+
+
+/*===========================================================================*
+ *
+ * SetupSquashSmall
+ *
+ *     Setup encoder to squash small changes in Y or Cr/Cb values
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    sets squash_max_differences SquashMaxLum SquashMaxChr 
+ *
+ *===========================================================================*/
+static void 
+SetupSquashSmall(const char * const charPtr)
+{
+  squash_small_differences = TRUE;
+
+  if (sscanf(charPtr, "%d %d", &SquashMaxLum, &SquashMaxChr) == 1) {
+    /* Only set one, do both */
+    SquashMaxChr = SquashMaxLum;
+  }
+}
+
+
+/*===========================================================================*
+ *
+ * SetupLocalDCT
+ *
+ *     Setup encoder to use DCT for rate-distortion estimat ein Psearches
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    sets SearchCompareMode and
+ *                        can change LocalDCTRateScale, LocalDCTDistortScale
+ *
+ *===========================================================================*/
+static void 
+SetupLocalDCT(const char * const charPtr)
+{
+  int num_scales=0;
+
+  SearchCompareMode = LOCAL_DCT;
+
+  /* Set scaling factors if present */
+  num_scales = sscanf(charPtr, "%f %f", &LocalDCTRateScale, &LocalDCTDistortScale);
+  if (num_scales == 1) {
+    fprintf(stderr, "Invalid number of scaling factors for local DCT\n");
+    fprintf(stderr, "Must specify Rate Scale and Distorion scale (both floats)\n");
+    fprintf(stderr, "Continuing with 1.0 1.0\n");
+    LocalDCTRateScale = 1.0;
+    LocalDCTDistortScale = 1.0;
+  }
+}
+
+
+/*===========================================================================*
+ *
+ * SetupLaplace
+ *
+ *     Setup encoder to find distrubution for I-frames, and use for -snr
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    sets DoLaplace, L1, L2, and Lambdas
+ *
+ *===========================================================================*/
+static void 
+SetupLaplace()
+{
+  int i;
+
+  DoLaplace = TRUE;
+  LaplaceNum = 0;
+  L1 = (double **)malloc(sizeof(double *)*3);
+  L2 = (double **)malloc(sizeof(double *)*3);
+  Lambdas = (double **)malloc(sizeof(double *)*3);
+  if (L1 == NULL || L2 == NULL || Lambdas == NULL) {
+    fprintf(stderr,"Out of memory!!!\n");
+    exit(1);
+  }
+  for (i = 0; i < 3; i++) {
+    L1[i] = (double *)calloc(64, sizeof(double));
+    L2[i] = (double *)calloc(64, sizeof(double));
+    Lambdas[i] = (double *)malloc(sizeof(double) * 64);
+    if (L1[i] == NULL || L2[i] == NULL || Lambdas[i] == NULL) {
+      fprintf(stderr,"Out of memory!!!\n");
+      exit(1);
+    }
+  }
+}
+
+static void
+SetupWriteDistortions(const char * const charPtr)
+{
+  char fname[256];
+  const char * cp;
+  int i;
+
+  WriteDistortionNumbers = TRUE;
+  cp = charPtr;
+  while ( (*cp != ' ') && (*cp != '\t') && (*cp != '\n')) {
+    cp++;
+  }
+
+  strncpy(fname, charPtr, cp-charPtr);
+  fname[cp-charPtr] = '\0';
+  collect_quant = TRUE;
+  if ((distortion_fp = fopen(fname,"w")) == NULL) {
+    fprintf(stderr, "Error opening %s for quant statistics\n", fname);
+    fprintf(stderr, "Using stdout (ick!)\n");
+    distortion_fp = stdout;
+  }
+
+  cp = SkipSpacesTabs(cp);
+  if (*cp != '\n') {
+    switch (*cp) {
+    case 'c':
+      collect_distortion_detailed = TRUE;
+      break;
+    case 't': {
+      char scratch[256];
+      collect_distortion_detailed = 2;
+      for (i = 1;  i < 32;  i++) {
+	sprintf(scratch, "%srate%d", fname, i);
+	fp_table_rate[i-1] = fopen(scratch, "w");
+	sprintf(scratch, "%sdist%d", fname, i);
+	fp_table_dist[i-1] = fopen(scratch, "w");
+	}}
+      break;
+    default:
+      fprintf(stderr, "Unknown TUNE parameter setting format %s\n", cp);
+    }}
+}  
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+
+void 
+CalcLambdas(void) {
+
+  int i,j,n;
+  double var;
+  
+  n = LaplaceNum;
+  for (i = 0;   i < 3;  i++) {
+    for (j = 0;  j < 64;  j++) {
+      var = (n*L1[i][j] + L2[i][j]*L2[i][j]) / (n*(n-1));
+      Lambdas[i][j] = sqrt(2.0) / sqrt(var);
+    }
+  }
+}
+
+
+/*===========================================================================*
+ *
+ * Mpost_UnQuantZigBlockLaplace
+ *
+ *	unquantize and zig-zag (decode) a single block, using the distrib to get vals
+ *      Iblocks only now
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Mpost_UnQuantZigBlockLaplace(in, out, qscale, iblock)
+    FlatBlock in;
+    Block out;
+    int qscale;
+    boolean iblock;
+{
+    register int index;
+    int	    position;
+    register int	    qentry;
+    int	    level, coeff;
+    double low, high;
+    double mid,lam;
+
+    /* qtable[0] must be 8 */
+    out[0][0] = (int16)(in[0] * 8);
+    
+    for ( index = 1;  index < DCTSIZE_SQ;  index++ ) {
+      position = ZAG[index];
+      level = in[index];
+      
+      if (level == 0) {
+	((int16 *)out)[position] = 0;
+	continue;
+      }
+      qentry = qtable[position] * qscale;
+      coeff = (level*qentry)/8;
+      low = ((ABS(level)-.5)*qentry)/8;
+      high = ((ABS(level)+.5)*qentry)/8;
+      lam = Lambdas[LaplaceCnum][position];
+      mid = (1.0/lam) * log(0.5*(exp(-lam*low)+exp(-lam*high)));
+      mid = ABS(mid);
+      if (mid - floor(mid) > .4999) {
+	mid = ceil(mid);
+      } else {
+	mid = floor(mid);
+      }
+      if (level<0) {mid = -mid;}
+/*printf("(%2.1lf-%2.1lf): old: %d vs %d\n",low,high,coeff,(int) mid);*/
+      coeff = mid;
+      if ( (coeff & 1) == 0 ) {
+	if ( coeff < 0 ) {
+	  coeff++;
+	} else if ( coeff > 0 ) {
+	  coeff--;
+	}
+      }
+      ((int16 *)out)[position] = coeff;
+    }
+}
+
+int 
+mse(Block blk1, Block blk2)
+{
+  register int index, error, tmp;
+  int16 *bp1, *bp2;
+
+  bp1 = (int16 *)blk1;
+  bp2 = (int16 *)blk2;
+  error = 0;
+  for ( index = 0;  index < DCTSIZE_SQ;  index++ ) {
+    tmp = *bp1++ - *bp2++;
+    error += tmp*tmp;
+  }
+  return error;
+}
+
+
+
+
+/*===========================================================================*
+ *
+ * Tune_Init
+ *
+ *     Do any setup needed before coding stream
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:  varies
+ *
+ *===========================================================================*/
+void Tune_Init()
+{
+  int i;
+
+  /* Just check for each, and do whats needed */
+  if (collect_quant) {
+    if (!pureDCT) {
+      pureDCT = TRUE;
+      init_idctref();
+      init_fdct();
+    }
+    fprintf(collect_quant_fp, "# %s\n", outputFileName);
+    fprintf(collect_quant_fp, "#");
+    for (i=0; i<64; i++) 
+      fprintf(collect_quant_fp, " %d", qtable[i]);
+    fprintf(collect_quant_fp, "\n#");
+    for (i=0; i<64; i++) 
+      fprintf(collect_quant_fp, " %d", niqtable[i]);
+    fprintf(collect_quant_fp, "\n# %d %d %d\n\n", 
+	    GetIQScale(), GetPQScale(), GetBQScale());
+    
+  }
+
+  if (DoLaplace) {
+    if (!pureDCT) {
+      pureDCT = TRUE;
+      init_idctref();
+      init_fdct();
+    }
+    decodeRefFrames = TRUE;
+    printSNR = TRUE;
+  }
+    
+}
+
+/*===========================================================================*
+ *
+ * ParseTuneParam
+ *
+ *     Handle the strings following TUNE
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:  varies
+ *
+ *===========================================================================*/
+void ParseTuneParam(const char * const charPtr)
+{
+  switch (ASCII_TOUPPER(*charPtr)) {
+  case 'B': 
+    if (1 != sscanf(charPtr+2, "%d", &block_bound)) {
+      fprintf(stderr, "Invalid tuning parameter (b) in parameter file.\n");
+    }
+    break;
+  case 'C':
+    SetupCollectQuantStats(charPtr+2);
+    break;
+  case 'D':
+    SetupLocalDCT(SkipSpacesTabs(charPtr+1));
+    break;
+  case 'K':
+    SetupKillDimAreas(SkipSpacesTabs(charPtr+1));
+    break;
+  case 'L':
+    SetupLaplace();
+    break;
+  case 'N':
+    SearchCompareMode = NO_DC_SEARCH;
+    break;
+  case 'Q':
+    SearchCompareMode = DO_Mean_Squared_Distortion;
+    break;
+  case 'S':
+    SetupSquashSmall(SkipSpacesTabs(charPtr+1));
+    break;
+  case 'W':
+    SetupWriteDistortions(SkipSpacesTabs(charPtr+1));
+    break;
+  case 'U':
+    BSkipBlocks = FALSE;
+    break;
+  case 'Z':
+     IntraPBAllowed = FALSE;
+    break;
+  default:
+    fprintf(stderr, "Unknown tuning (%s) in parameter file.\n",charPtr);
+    break;
+  }
+}
+
+
diff --git a/converter/ppm/ppmtompeg/parallel.c b/converter/ppm/ppmtompeg/parallel.c
new file mode 100644
index 00000000..021e6d2b
--- /dev/null
+++ b/converter/ppm/ppmtompeg/parallel.c
@@ -0,0 +1,2278 @@
+/*===========================================================================*
+ * parallel.c              
+ *                         
+ *  Procedures to make encoder run in parallel   
+ *                             
+ *===========================================================================*/
+
+/* COPYRIGHT INFORMATION IS AT THE END OF THIS FILE */
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#define _XOPEN_SOURCE 500 /* Make sure stdio.h contains pclose() */
+/* _ALL_SOURCE is needed on AIX to make the C library include the 
+   socket services (e.g. define struct sockaddr) 
+
+   Note that AIX standards.h actually sets feature declaration macros such
+   as _XOPEN_SOURCE, unless they are already set.
+*/
+#define _ALL_SOURCE
+
+/* On AIX, pm_config.h includes standards.h, which expects to be included
+   after feature declaration macros such as _XOPEN_SOURCE.  So we include
+   pm_config.h as late as possible.
+*/
+
+
+#include <stdarg.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <netdb.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/times.h>
+
+#include "mallocvar.h"
+#include "nstring.h"
+
+#include "pm.h"
+
+#include "all.h"
+#include "param.h"
+#include "mpeg.h"
+#include "prototypes.h"
+#include "readframe.h"
+#include "fsize.h"
+#include "combine.h"
+#include "frames.h"
+#include "input.h"
+#include "psocket.h"
+#include "frametype.h"
+#include "gethostname.h"
+
+#include "parallel.h"
+
+
+struct childState {
+    boolean      finished;
+    unsigned int startFrame;
+    unsigned int numFrames;
+    unsigned int lastNumFrames;
+    unsigned int numSeconds;
+    float        fps;
+};
+
+
+struct scheduler {
+    /* This tracks the state of the subsystem that determines the assignments
+       for the children
+    */
+    unsigned int nextFrame;
+        /* The next frame that needs to be assigned to a child */
+    unsigned int numFramesInJob;
+        /* Total number of frames in the whole run of Ppmtompeg */
+    unsigned int numMachines;
+};
+
+
+
+#define MAX_IO_SERVERS  10
+#ifndef SOMAXCONN
+#define SOMAXCONN 5
+#endif
+
+/*==================*
+ * CONSTANTS        *
+ *==================*/
+
+#define TERMINATE_PID_SIGNAL    SIGTERM  /* signal used to terminate forked childs */
+#ifndef MAXARGS
+#define MAXARGS     1024   /* Max Number of arguments in safe_fork command */
+#endif
+
+/*==================*
+ * STATIC VARIABLES *
+ *==================*/
+
+static char rsh[256];
+static struct hostent *hostEntry = NULL;
+static boolean  *frameDone;
+static int  outputServerSocket;
+static int  decodeServerSocket;
+static boolean  parallelPerfect = FALSE;
+static  int current_max_forked_pid=0;
+
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+extern int yuvHeight, yuvWidth;
+extern char statFileName[256];
+extern FILE *statFile;
+extern boolean debugMachines;
+extern boolean debugSockets;
+int parallelTestFrames = 10;
+int parallelTimeChunks = 60;
+const char *IOhostName;
+int ioPortNumber;
+int decodePortNumber;
+boolean niceProcesses = FALSE;
+boolean forceIalign = FALSE;
+int     machineNumber = -1;
+boolean remoteIO = FALSE;
+bool separateConversion;
+    /* The I/O server will convert from the input format to the base format,
+       and the slave will convert from the base format to the YUV internal
+       format.  If false, the I/O server assumes the input format is the
+       base format and converts from the base format to the YUV internal
+       format; the slave does no conversion.
+    */
+time_t  IOtime = 0;
+extern char encoder_name[];
+int     ClientPid[MAX_MACHINES+4];
+
+
+/*=====================*
+ * INTERNAL PROCEDURES *
+ *=====================*/
+
+
+static void PM_GNU_PRINTF_ATTR(1,2)
+machineDebug(const char format[], ...) {
+
+    va_list args;
+
+    va_start(args, format);
+
+    if (debugMachines) {
+        const char * const hostname = GetHostName();
+        fprintf(stderr, "%s: ---", hostname);
+        strfree(hostname);
+        vfprintf(stderr, format, args);
+        fputc('\n', stderr);
+    }
+    va_end(args);
+}
+
+
+
+static void PM_GNU_PRINTF_ATTR(1,2)
+errorExit(const char format[], ...) {
+
+    const char * const hostname = GetHostName();
+
+    va_list args;
+
+    va_start(args, format);
+
+    fprintf(stderr, "%s: FATAL ERROR.  ", hostname);
+    strfree(hostname);
+    vfprintf(stderr, format, args);
+    fputc('\n', stderr);
+
+    exit(1);
+
+    va_end(args);
+}
+
+
+
+static void
+TransmitPortNum(const char * const hostName, 
+                int          const portNum, 
+                int          const newPortNum) {
+/*----------------------------------------------------------------------------
+   Transmit the port number 'newPortNum' to the master on port 'portNum'
+   of host 'hostName'.
+-----------------------------------------------------------------------------*/
+    int clientSocket;
+    const char * error;
+    
+    ConnectToSocket(hostName, portNum, &hostEntry, &clientSocket, &error);
+    
+    if (error)
+        errorExit("Can't connect in order to transmit port number.  %s",
+                  error);
+
+    WriteInt(clientSocket, newPortNum);
+    
+    close(clientSocket);
+}
+
+
+
+static void
+readYUVDecoded(int          const socketFd,
+               unsigned int const Fsize_x,
+               unsigned int const Fsize_y,
+               MpegFrame *  const frameP) {
+    
+    unsigned int y;
+    
+    for (y = 0; y < Fsize_y; ++y)         /* Y */
+        ReadBytes(socketFd, 
+                  (unsigned char *)frameP->decoded_y[y], Fsize_x);
+    
+    for (y = 0; y < (Fsize_y >> 1); ++y)  /* U */
+        ReadBytes(socketFd, 
+                  (unsigned char *)frameP->decoded_cb[y], (Fsize_x >> 1));
+    
+    for (y = 0; y < (Fsize_y >> 1); ++y)  /* V */
+        ReadBytes(socketFd, 
+                  (unsigned char *)frameP->decoded_cr[y], (Fsize_x >> 1));
+}
+
+
+
+static void
+writeYUVDecoded(int          const socketFd,
+                unsigned int const Fsize_x,
+                unsigned int const Fsize_y,
+                MpegFrame *  const frameP) {
+    
+    unsigned int y;
+    
+    for (y = 0; y < Fsize_y; ++y)         /* Y */
+        WriteBytes(socketFd, 
+                  (unsigned char *)frameP->decoded_y[y], Fsize_x);
+    
+    for (y = 0; y < (Fsize_y >> 1); ++y)  /* U */
+        WriteBytes(socketFd, 
+                   (unsigned char *)frameP->decoded_cb[y], (Fsize_x >> 1));
+    
+    for (y = 0; y < (Fsize_y >> 1); ++y)  /* V */
+        WriteBytes(socketFd, 
+                   (unsigned char *)frameP->decoded_cr[y], (Fsize_x >> 1));
+}
+
+
+
+static void
+writeYUVOrig(int          const socketFd,
+             unsigned int const Fsize_x,
+             unsigned int const Fsize_y,
+             MpegFrame *  const frameP) {
+    
+    unsigned int y;
+    
+    for (y = 0; y < Fsize_y; ++y)         /* Y */
+        WriteBytes(socketFd, 
+                  (unsigned char *)frameP->orig_y[y], Fsize_x);
+    
+    for (y = 0; y < (Fsize_y >> 1); ++y)  /* U */
+        WriteBytes(socketFd, 
+                   (unsigned char *)frameP->orig_cb[y], (Fsize_x >> 1));
+    
+    for (y = 0; y < (Fsize_y >> 1); ++y)  /* V */
+        WriteBytes(socketFd, 
+                   (unsigned char *)frameP->orig_cr[y], (Fsize_x >> 1));
+}
+
+
+
+static void
+readYUVOrig(int          const socketFd,
+            unsigned int const Fsize_x,
+            unsigned int const Fsize_y,
+            MpegFrame *  const frameP) {
+    
+    unsigned int y;
+    
+    for (y = 0; y < Fsize_y; ++y)         /* Y */
+        ReadBytes(socketFd, 
+                  (unsigned char *)frameP->orig_y[y], Fsize_x);
+    
+    for (y = 0; y < (Fsize_y >> 1); ++y)  /* U */
+        ReadBytes(socketFd, 
+                  (unsigned char *)frameP->orig_cb[y], (Fsize_x >> 1));
+    
+    for (y = 0; y < (Fsize_y >> 1); ++y)  /* V */
+        ReadBytes(socketFd, 
+                  (unsigned char *)frameP->orig_cr[y], (Fsize_x >> 1));
+}
+
+
+
+/*===========================================================================*
+ *
+ * EndIOServer
+ *
+ *  called by the master process -- tells the I/O server to commit
+ *  suicide
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static void
+  EndIOServer()
+{
+  /* send signal to IO server:  -1 as frame number */
+  GetRemoteFrame(NULL, -1);
+}
+
+
+/*===========================================================================*
+ *
+ * NotifyDecodeServerReady
+ *
+ *  called by a slave to the Decode Server to tell it a decoded frame
+ *  is ready and waiting
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+NotifyDecodeServerReady(int const id) {
+
+    int   clientSocket;
+    time_t  tempTimeStart, tempTimeEnd;
+    const char * error;
+    
+    time(&tempTimeStart);
+    
+    ConnectToSocket(IOhostName, decodePortNumber, &hostEntry, &clientSocket,
+                    &error);
+    
+    if (error)
+        errorExit("CHILD: Can't connect to decode server to tell it a frame "
+                "is ready.  %s", error);
+    
+    WriteInt(clientSocket, id);
+    
+    close(clientSocket);
+    
+    time(&tempTimeEnd);
+    IOtime += (tempTimeEnd-tempTimeStart);
+}
+
+
+
+/*===========================================================================*
+ *
+ * WaitForDecodedFrame
+ *
+ *  blah blah blah
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+  WaitForDecodedFrame(id)
+int id;
+{
+  int const negativeTwo = -2;
+  int   clientSocket;
+  int     ready;
+  const char * error;
+
+  /* wait for a decoded frame */
+  if ( debugSockets ) {
+    fprintf(stdout, "WAITING FOR DECODED FRAME %d\n", id);
+  }
+
+  ConnectToSocket(IOhostName, decodePortNumber, &hostEntry, &clientSocket,
+                  &error);
+
+  if (error)
+      errorExit("CHILD: Can't connect to decode server "
+                "to get decoded frame.  %s",
+                error);
+
+  /* first, tell DecodeServer we're waiting for this frame */
+  WriteInt(clientSocket, negativeTwo);
+
+  WriteInt(clientSocket, id);
+
+  ReadInt(clientSocket, &ready);
+
+  if ( ! ready ) {
+    int     waitSocket;
+    int     waitPort;
+    int     otherSock;
+    const char * error;
+
+    /* it's not ready; set up a connection and wait for decode server */
+    CreateListeningSocket(&waitSocket, &waitPort, &error);
+    if (error)
+        errorExit("Unable to create socket on which to listen for "
+                  "decoded frame.  %s", error);
+
+    /* tell decode server where we are */
+    WriteInt(clientSocket, machineNumber);
+
+    WriteInt(clientSocket, waitPort);
+
+    close(clientSocket);
+
+    if ( debugSockets ) {
+      fprintf(stdout, "SLAVE:  WAITING ON SOCKET %d\n", waitPort);
+      fflush(stdout);
+    }
+
+    AcceptConnection(waitSocket, &otherSock, &error);
+    if (error)
+        errorExit("I/O SERVER: Failed to accept next connection.  %s", error);
+
+    /* should we verify this is decode server? */
+    /* for now, we won't */
+
+    close(otherSock);
+
+    close(waitSocket);
+  } else {
+    close(clientSocket);
+  }
+
+  if ( debugSockets ) {
+    fprintf(stdout, "YE-HA FRAME %d IS NOW READY\n", id);
+  }
+}
+
+
+/*===========================================================================*
+ *
+ * SendDecodedFrame
+ *
+ *  Send the frame to the decode server.
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+SendDecodedFrame(MpegFrame * const frameP) {
+/*----------------------------------------------------------------------------
+   Send frame *frameP to the decode server.
+-----------------------------------------------------------------------------*/
+    int const negativeTwo = -2;
+    
+    int clientSocket;
+    const char * error;
+    
+    /* send to IOServer */
+    ConnectToSocket(IOhostName, ioPortNumber, &hostEntry,
+                    &clientSocket, &error);
+    if (error)
+        errorExit("CHILD: Can't connect to decode server to "
+                  "give it a decoded frame.  %s", error);
+    
+    WriteInt(clientSocket, negativeTwo);
+    
+    WriteInt(clientSocket, frameP->id);
+
+    writeYUVDecoded(clientSocket, Fsize_x, Fsize_y, frameP);
+    
+    close(clientSocket);
+}
+
+
+/*===========================================================================*
+ *
+ * GetRemoteDecodedFrame
+ *
+ *  get the decoded frame from the decode server.
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:   
+ *
+ *===========================================================================*/
+void
+GetRemoteDecodedRefFrame(MpegFrame * const frameP, 
+                         int         const frameNumber) {
+/*----------------------------------------------------------------------------
+   Get decoded frame number 'frameNumber' *frameP from the decode server.
+-----------------------------------------------------------------------------*/
+  int const negativeThree = -3;
+  int clientSocket;
+  const char * error;
+
+  /* send to IOServer */
+  ConnectToSocket(IOhostName, ioPortNumber, &hostEntry,
+                  &clientSocket, &error);
+  if (error)
+      errorExit("CHILD: Can't connect to decode server "
+                "to get a decoded frame.  %s",
+                error);
+
+  /* ask IOServer for decoded frame */
+  WriteInt(clientSocket, negativeThree);
+
+  WriteInt(clientSocket, frameP->id);
+
+  readYUVDecoded(clientSocket, Fsize_x, Fsize_y, frameP);
+
+  close(clientSocket);
+}
+
+
+/*********
+  routines handling forks, execs, PIDs and signals
+  save, system-style forks
+  apian@ise.fhg.de
+  *******/
+
+
+/*===========================================================================*
+ *
+ * cleanup_fork
+ *
+ *  Kill all the children, to be used when we get killed
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:   kills other processes
+ *
+ *===========================================================================*/
+static void cleanup_fork( dummy )       /* try to kill all child processes */
+     int dummy;
+{
+  register int i;
+  for (i = 0;  i < current_max_forked_pid;  ++i ) {
+
+#ifdef DEBUG_FORK
+    fprintf(stderr, "cleanup_fork: killing PID %d\n", ClientPid[i]);
+#endif
+
+    if (kill(ClientPid[i], TERMINATE_PID_SIGNAL)) {
+      fprintf(stderr, "cleanup_fork: killed PID=%d failed (errno %d)\n", 
+          ClientPid[i], errno);
+    }
+  }
+}
+
+/*===========================================================================*
+ *
+ * safe_fork
+ *
+ *  fork a command
+ *
+ * RETURNS:     success/failure
+ *
+ * SIDE EFFECTS:   Fork the command, and save to PID so you can kil it later!
+ *
+ *===========================================================================*/
+static int safe_fork(command)       /* fork child process and remember its PID */
+     char *command;
+{
+  static int init=0;
+  char *argis[MAXARGS];
+  register int i=1;
+  
+  if (!(argis[0] = strtok(command, " \t"))) return(0); /* tokenize */
+  while ((argis[i] = strtok(NULL, " \t")) && i < MAXARGS) ++i;
+  argis[i] = NULL;
+  
+#ifdef DEBUG_FORK
+  {register int i=0; 
+   fprintf(stderr, "Command %s becomes:\n", command);
+   while(argis[i]) {fprintf(stderr, "--%s--\n", argis[i]); ++i;} }
+#endif
+  
+  if (!init) {          /* register clean-up routine */
+    signal (SIGQUIT, cleanup_fork);
+    signal (SIGTERM, cleanup_fork);
+    signal (SIGINT , cleanup_fork);
+    init=1;
+  }
+  
+  if (-1 == (ClientPid[current_max_forked_pid] = fork()) )  {
+    perror("safe_fork: fork failed ");
+    return(-1);
+  }
+  if( !ClientPid[current_max_forked_pid]) { /* we are in child process */
+    execvp(argis[0], argis );
+    perror("safe_fork child: exec failed ");
+    exit(1);
+  }
+#ifdef DEBUG_FORK
+  fprintf(stderr, "parallel: forked PID=%d\n", ClientPid[current_max_forked_pid]);
+#endif
+  current_max_forked_pid++;
+  return(0);
+}
+
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+
+            /*=================*
+             * IO SERVER STUFF *
+             *=================*/
+
+
+/*===========================================================================*
+ *
+ * SetIOConvert
+ *
+ *  sets the IO conversion to be separate or not.  If separate, then
+ *  some post-processing is done at slave end
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+SetIOConvert(bool const separate) {
+    separateConversion = separate;
+}
+
+
+/*===========================================================================*
+ *
+ * SetParallelPerfect
+ *
+ *  If this is called, then frames will be divided up completely, and
+ *  evenly (modulo rounding) between all the processors
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    Sets parallelPerfect ....
+ *
+ *===========================================================================*/
+void
+SetParallelPerfect(val)
+boolean val;
+{
+    parallelPerfect = val;
+}
+
+
+/*===========================================================================*
+ *
+ * SetRemoteShell
+ *
+ *  sets the remote shell program (usually rsh, but different on some
+ *  machines)
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+SetRemoteShell(const char * const shell) {
+    strcpy(rsh, shell);
+}
+
+
+static void
+decodedFrameToDisk(int const otherSock) {
+/*----------------------------------------------------------------------------
+  Get a decoded from from socket 'otherSock' and write it to disk.
+-----------------------------------------------------------------------------*/
+    int frameNumber;
+    MpegFrame * frameP;
+
+    ReadInt(otherSock, &frameNumber);
+    
+    if (debugSockets) {
+        fprintf(stdout, "INPUT SERVER:  GETTING DECODED FRAME %d\n", 
+                frameNumber);
+        fflush(stdout);
+    }
+
+    /* should read frame from socket, then write to disk */
+    frameP = Frame_New(frameNumber, 'i');
+    
+    Frame_AllocDecoded(frameP, TRUE);
+    
+    readYUVDecoded(otherSock, Fsize_x, Fsize_y, frameP);
+
+    /* now output to disk */
+    WriteDecodedFrame(frameP);
+
+    Frame_Free(frameP);
+}        
+
+
+
+static void
+decodedFrameFromDisk(int const otherSock) {
+
+    /* request for decoded frame from disk */
+            
+    int frameNumber;
+    MpegFrame * frameP;
+
+    ReadInt(otherSock, &frameNumber);
+    
+    if (debugSockets) {
+        fprintf(stdout, "INPUT SERVER:  READING DECODED FRAME %d "
+                "from DISK\n", frameNumber);
+        fflush(stdout);
+    }
+
+    /* should read frame from disk, then write to socket */
+    frameP = Frame_New(frameNumber, 'i');
+    
+    Frame_AllocDecoded(frameP, TRUE);
+    
+    ReadDecodedRefFrame(frameP, frameNumber);
+    
+    writeYUVDecoded(otherSock, Fsize_x, Fsize_y, frameP);
+    
+    Frame_Free(frameP);
+}        
+
+
+
+static void
+routeFromSocketToDisk(int              const otherSock,
+                      unsigned char ** const bigBufferP,
+                      unsigned int *   const bigBufferSizeP) {
+
+    /* routing output frame from socket to disk */
+
+    int frameNumber;
+    int numBytes;
+    unsigned char * bigBuffer;
+    unsigned int bigBufferSize;
+    const char * fileName;
+    FILE * filePtr;
+
+    bigBuffer     = *bigBufferP;
+    bigBufferSize = *bigBufferSizeP;
+
+    ReadInt(otherSock, &frameNumber);
+    ReadInt(otherSock, &numBytes);
+
+    /* Expand bigBuffer if necessary to fit this frame */
+    if (numBytes > bigBufferSize) {
+        bigBufferSize = numBytes;
+        if (bigBuffer != NULL)
+            free(bigBuffer);
+
+        MALLOCARRAY_NOFAIL(bigBuffer, bigBufferSize);
+    }
+    
+    /* now read in the bytes */
+    ReadBytes(otherSock, bigBuffer, numBytes);
+    
+    /* open file to output this stuff to */
+    asprintfN(&fileName, "%s.frame.%d", outputFileName, frameNumber);
+    filePtr = fopen(fileName, "wb");
+
+    if (filePtr == NULL)
+        errorExit("I/O SERVER: Could not open output file(3):  %s", fileName);
+
+    strfree(fileName);
+
+    /* now write the bytes here */
+    fwrite(bigBuffer, sizeof(char), numBytes, filePtr);
+    
+    fclose(filePtr);
+    
+    if (debugSockets) {
+        fprintf(stdout, "====I/O SERVER:  WROTE FRAME %d to disk\n",
+                frameNumber);
+        fflush(stdout);
+    }
+
+    *bigBufferP     = bigBuffer;
+    *bigBufferSizeP = bigBufferSize;
+}        
+
+
+
+static void
+readConvertWriteToSocket(struct inputSource * const inputSourceP,
+                         int                  const otherSock,
+                         int                  const frameNumber,
+                         bool *               const endOfStreamP) {
+/*----------------------------------------------------------------------------
+   Get the frame numbered 'frameNumber' from input source
+   *inputSourceP, apply format conversion User requested, and write
+   the "base format" result to socket 'otherSock'.
+-----------------------------------------------------------------------------*/
+    FILE * convertedFileP;
+    
+    convertedFileP = ReadIOConvert(inputSourceP, frameNumber);
+    if (convertedFileP) {
+        bool eof;
+        eof = FALSE;  /* initial value */
+        while (!eof) {
+            unsigned char buffer[1024];
+            unsigned int numBytes;
+            
+            numBytes = fread(buffer, 1, sizeof(buffer), convertedFileP);
+            
+            if (numBytes > 0) {
+                WriteInt(otherSock, numBytes);
+                WriteBytes(otherSock, buffer, numBytes);
+            } else
+                eof = TRUE;
+        }
+        
+        if (strcmp(ioConversion, "*") == 0 )
+            fclose(convertedFileP);
+        else
+            pclose(convertedFileP);
+        
+        *endOfStreamP = FALSE;
+    } else
+        *endOfStreamP = TRUE;
+}
+
+
+
+static void
+readWriteYuvToSocket(struct inputSource * const inputSourceP,
+                     int                  const otherSock,
+                     int                  const frameNumber,
+                     bool *               const endOfStreamP) {
+/*----------------------------------------------------------------------------
+   Read Frame number 'frameNumber' from the input source *inputSourceP,
+   assuming it is in base format, and write its contents in YUV format
+   to socket 'otherSock'.
+
+   Wait for acknowledgement that consumer has received it.
+-----------------------------------------------------------------------------*/
+    MpegFrame * frameP;
+
+    frameP = Frame_New(frameNumber, 'i');
+    
+    ReadFrame(frameP, inputSourceP, frameNumber, inputConversion,
+              endOfStreamP);
+    
+    if (!*endOfStreamP) {
+        writeYUVOrig(otherSock, Fsize_x, Fsize_y, frameP);
+        
+        {
+            /* Make sure we don't leave until other processor read
+               everything
+            */
+            int dummy;
+            ReadInt(otherSock, &dummy);
+            assert(dummy == 0);
+        }
+    }
+    Frame_Free(frameP);
+}
+
+
+
+static void
+readFrameWriteToSocket(struct inputSource * const inputSourceP,
+                       int                  const otherSock,
+                       int                  const frameNumber,
+                       bool *               const endOfStreamP) {
+/*----------------------------------------------------------------------------
+   Read Frame number 'frameNumber' from the input source *inputSourceP
+   and write it to socket 'otherSock'.
+-----------------------------------------------------------------------------*/
+    if (debugSockets) {
+        fprintf(stdout, "I/O SERVER GETTING FRAME %d\n", frameNumber);
+        fflush(stdout);
+    }
+
+    if (separateConversion)
+        readConvertWriteToSocket(inputSourceP, otherSock, frameNumber,
+                                 endOfStreamP);
+    else
+        readWriteYuvToSocket(inputSourceP, otherSock, frameNumber,
+                             endOfStreamP);
+
+    if (debugSockets) {
+        fprintf(stdout, "====I/O SERVER:  READ FRAME %d\n", frameNumber);
+    }
+}
+
+
+
+static void
+processNextConnection(int                  const serverSocket,
+                      struct inputSource * const inputSourceP,
+                      bool *               const doneP,
+                      unsigned char **     const bigBufferP,
+                      unsigned int *       const bigBufferSizeP) {
+
+    int          otherSock;
+    int          command;
+    const char * error;
+    
+    AcceptConnection(serverSocket, &otherSock, &error);
+    if (error)
+        errorExit("I/O SERVER: Failed to accept next connection.  %s", error);
+
+    ReadInt(otherSock, &command);
+
+    switch (command) {
+    case -1:
+        *doneP = TRUE;
+        break;
+    case -2:
+        decodedFrameToDisk(otherSock);
+        break;
+    case -3:
+        decodedFrameFromDisk(otherSock);
+        break;
+    case -4:
+        routeFromSocketToDisk(otherSock, bigBufferP, bigBufferSizeP);
+        break;
+    default: {
+        unsigned int const frameNumber = command;
+
+        bool endOfStream;
+
+        readFrameWriteToSocket(inputSourceP, otherSock, frameNumber,
+                               &endOfStream);
+
+        if (endOfStream) {
+            /* We don't do anything.  Closing the socket with having written
+               anything is our signal that there is no more input.
+
+               (Actually, Ppmtompeg cannot handle stream input in parallel
+               mode -- this code is just infrastructure so maybe it can some
+               day).
+            */
+        }
+    }
+    }
+    close(otherSock);
+}
+ 
+
+
+void
+IoServer(struct inputSource * const inputSourceP,
+         const char *         const parallelHostName, 
+         int                  const portNum) {
+/*----------------------------------------------------------------------------
+   Execute an I/O server.
+
+   An I/O server is the partner on the master machine of a child process
+   on a "remote" system.  "Remote" here doesn't just mean on another system.
+   It means on a system that isn't even in the same cluster -- specifically,
+   a system that doesn't have access to the same filesystem as the master.
+
+   The child process passes frame contents between it and the master via
+   the I/O server.
+-----------------------------------------------------------------------------*/
+    int       ioPortNum;
+    int       serverSocket;
+    boolean   done;
+    unsigned char   *bigBuffer;
+        /* A work buffer that we keep around permanently.  We increase
+           its size as needed, but never shrink it.
+        */
+    unsigned int bigBufferSize;
+        /* The current allocated size of bigBuffer[] */
+    const char * error;
+
+    bigBufferSize = 0;  /* Start with no buffer */
+    bigBuffer = NULL;
+    
+    /* once we get IO port num, should transmit it to parallel server */
+
+    CreateListeningSocket(&serverSocket, &ioPortNum, &error);
+    if (error)
+        errorExit("Unable to create socket on which to listen for "
+                  "reports from children.  %s", error);
+
+    if (debugSockets)
+        fprintf(stdout, "====I/O USING PORT %d\n", ioPortNum);
+
+    TransmitPortNum(parallelHostName, portNum, ioPortNum);
+
+    if (separateConversion)
+        SetFileType(ioConversion);  /* for reading */
+    else
+        SetFileType(inputConversion);
+
+    done = FALSE;  /* initial value */
+
+    while (!done)
+        processNextConnection(serverSocket, inputSourceP,
+                              &done, &bigBuffer, &bigBufferSize);
+
+    close(serverSocket);
+
+    if ( debugSockets ) {
+        fprintf(stdout, "====I/O SERVER:  Shutting Down\n");
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * SendRemoteFrame
+ *
+ *  called by a slave to the I/O server; sends an encoded frame
+ *  to the server to be sent to disk
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+SendRemoteFrame(int const frameNumber, BitBucket * const bb) {
+
+    int const negativeFour = -4;
+    int clientSocket;
+    time_t  tempTimeStart, tempTimeEnd;
+    const char * error;
+
+    time(&tempTimeStart);
+
+    ConnectToSocket(IOhostName, ioPortNumber, &hostEntry,
+                    &clientSocket, &error);
+    if (error)
+        errorExit("CHILD: Can't connect to I/O server to deliver results.  %s",
+                  error);
+
+    WriteInt(clientSocket, negativeFour);
+
+    WriteInt(clientSocket, frameNumber);
+    
+    if (frameNumber != -1) {
+        /* send number of bytes */
+        
+        WriteInt(clientSocket, (bb->totalbits+7)>>3);
+    
+        /* now send the bytes themselves */
+        Bitio_WriteToSocket(bb, clientSocket);
+    }
+
+    close(clientSocket);
+
+    time(&tempTimeEnd);
+    IOtime += (tempTimeEnd-tempTimeStart);
+}
+
+
+
+void
+GetRemoteFrame(MpegFrame * const frameP, 
+               int         const frameNumber) {
+/*----------------------------------------------------------------------------
+   Get a frame from the I/O server.
+   
+   This is intended for use by a child.
+-----------------------------------------------------------------------------*/
+    int           clientSocket;
+    const char * error;
+
+    Fsize_Note(frameNumber, yuvWidth, yuvHeight);
+
+    if (debugSockets) {
+        fprintf(stdout, "MACHINE %s REQUESTING connection for FRAME %d\n",
+                getenv("HOST"), frameNumber);
+        fflush(stdout);
+    }
+
+    ConnectToSocket(IOhostName, ioPortNumber, &hostEntry,
+                    &clientSocket, &error);
+
+    if (error)
+        errorExit("CHILD: Can't connect to I/O server to get a frame.  %s", 
+                  error);
+
+    WriteInt(clientSocket, frameNumber);
+
+    if (frameNumber != -1) {
+        if (separateConversion) {
+            unsigned char buffer[1024];
+            /* This is by design the exact size of the data per message (except
+               the last message for a frame) the I/O server sends.
+            */
+            int numBytes;  /* Number of data bytes in message */
+            FILE * filePtr = pm_tmpfile();
+
+            /* read in stuff, write to file, perform local conversion */
+            do {
+                ReadInt(clientSocket, &numBytes);
+
+                if (numBytes > sizeof(buffer))
+                    errorExit("Invalid message received: numBytes = %d, "
+                              "which is greater than %d\n", 
+                              numBytes, sizeof(numBytes));
+                ReadBytes(clientSocket, buffer, numBytes);
+
+                fwrite(buffer, 1, numBytes, filePtr);
+            } while ( numBytes == sizeof(buffer) );
+            fflush(filePtr);
+            {
+                bool endOfStream;
+                rewind(filePtr);
+                /* I/O Server gave us base format.  Read it as an MpegFrame */
+                ReadFrameFile(frameP, filePtr, slaveConversion, &endOfStream);
+                assert(!endOfStream);
+            }
+            fclose(filePtr);
+        } else {
+            Frame_AllocYCC(frameP);
+
+            if (debugSockets) {
+                fprintf(stdout, "MACHINE %s allocated YCC FRAME %d\n",
+                        getenv("HOST"), frameNumber);
+                fflush(stdout);
+            }
+            /* I/O Server gave us internal YUV format.  Read it as MpegFrame */
+            readYUVOrig(clientSocket, yuvWidth, yuvHeight, frameP);
+        }
+    }
+
+    WriteInt(clientSocket, 0);
+
+    close(clientSocket);
+
+    if (debugSockets) {
+        fprintf(stdout, "MACHINE %s READ COMPLETELY FRAME %d\n",
+                getenv("HOST"), frameNumber);
+        fflush(stdout);
+    }
+}
+
+
+struct combineControl {
+    unsigned int numFrames;
+};
+
+
+
+
+static void
+getAndProcessACombineConnection(int const outputServerSocket) {
+    int          otherSock;
+    int          command;
+    const char * error;
+
+    AcceptConnection(outputServerSocket, &otherSock, &error);
+
+    if (error)
+        errorExit("COMBINE SERVER: "
+                  "Failed to accept next connection.  %s", error);
+    
+    ReadInt(otherSock, &command);
+    
+    if (command == -2) {
+        /* this is notification from non-remote process that a
+           frame is done.
+            */
+        int frameStart, frameEnd;
+
+        ReadInt(otherSock, &frameStart);
+        ReadInt(otherSock, &frameEnd);
+            
+        machineDebug("COMBINE_SERVER: Frames %d - %d done",
+                     frameStart, frameEnd);
+        {
+            unsigned int i;
+            for (i = frameStart; i <= frameEnd; ++i)
+                frameDone[i] = TRUE;
+        }
+    } else
+        errorExit("COMBINE SERVER: Unrecognized command %d received.",
+                  command);
+
+    close(otherSock);
+}
+
+
+
+#define READ_ATTEMPTS 5 /* number of times (seconds) to retry an input file */
+
+
+static void
+openInputFile(const char * const fileName,
+              FILE **      const inputFilePP) {
+
+    FILE * inputFileP;
+    unsigned int attempts;
+    
+    inputFileP = NULL;
+    attempts = 0;
+
+    while (!inputFileP && attempts < READ_ATTEMPTS) {
+        inputFileP = fopen(fileName, "rb");
+        if (inputFileP == NULL) {
+            pm_message("ERROR  Couldn't read frame file '%s' errno = %d (%s)"
+                       "attempt %d", 
+                       fileName, errno, strerror(errno), attempts);
+            sleep(1);
+        }
+        ++attempts;
+    }
+    if (inputFileP == NULL)
+        pm_error("Unable to open file '%s' after %d attempts.", 
+                 fileName, attempts);
+
+    *inputFilePP = inputFileP;
+}
+
+
+
+static void
+waitForOutputFile(void *        const inputHandle,
+                  unsigned int  const frameNumber,
+                  FILE **       const ifPP) {
+/*----------------------------------------------------------------------------
+   Keep handling output events until we get the specified frame number.
+   Open the file it's in and return the stream handle.
+-----------------------------------------------------------------------------*/
+    struct combineControl * const combineControlP = (struct combineControl *)
+        inputHandle;
+
+    if (frameNumber >= combineControlP->numFrames)
+        *ifPP = NULL;
+    else {
+        const char * fileName;
+
+        while (!frameDone[frameNumber]) {
+            machineDebug("COMBINE_SERVER: Waiting for frame %u done", 
+                         frameNumber);
+
+            getAndProcessACombineConnection(outputServerSocket);
+        }
+        machineDebug("COMBINE SERVER: Wait for frame %u over", frameNumber);
+
+        asprintfN(&fileName, "%s.frame.%u", outputFileName, frameNumber);
+
+        openInputFile(fileName, ifPP);
+
+        strfree(fileName);
+    }
+}
+
+
+
+static void
+unlinkFile(void *       const inputHandle,
+           unsigned int const frameNumber) {
+
+    if (!keepTempFiles) {
+        const char * fileName;
+
+        asprintfN(&fileName, "%s.frame.%u", outputFileName, frameNumber);
+
+        unlink(fileName);
+
+        strfree(fileName);
+    }
+}
+
+
+
+void
+CombineServer(int          const numFrames, 
+              const char * const masterHostName, 
+              int          const masterPortNum,
+              const char * const outputFileName) {
+/*----------------------------------------------------------------------------
+   Execute a combine server.
+
+   This handles combination of frames.
+-----------------------------------------------------------------------------*/
+  int    combinePortNum;
+  FILE * ofP;
+  const char * error;
+  struct combineControl combineControl;
+  
+  /* once we get Combine port num, should transmit it to parallel server */
+  
+  CreateListeningSocket(&outputServerSocket, &combinePortNum, &error);
+  if (error)
+      errorExit("Unable to create socket on which to listen.  %s", error);
+
+  machineDebug("COMBINE SERVER: LISTENING ON PORT %d", combinePortNum);
+  
+  TransmitPortNum(masterHostName, masterPortNum, combinePortNum);
+  
+  MALLOCARRAY_NOFAIL(frameDone, numFrames);
+  {
+      unsigned int i;
+      for (i = 0; i < numFrames; ++i)
+          frameDone[i] = FALSE;
+  }
+  ofP = pm_openw(outputFileName);
+  
+  combineControl.numFrames = numFrames;
+
+  FramesToMPEG(ofP, &combineControl, &waitForOutputFile, &unlinkFile);
+
+  machineDebug("COMBINE SERVER: Shutting down");
+  
+  /* tell Master server we are done */
+  TransmitPortNum(masterHostName, masterPortNum, combinePortNum);
+  
+  close(outputServerSocket);
+
+  fclose(ofP);
+}
+
+
+/*=====================*
+ * MASTER SERVER STUFF *
+ *=====================*/
+
+
+static void
+startCombineServer(const char * const encoderName,
+                   unsigned int const numMachines,
+                   const char * const masterHostName,
+                   int          const masterPortNum,
+                   unsigned int const numInputFiles,
+                   const char * const paramFileName,
+                   int          const masterSocket,
+                   int *        const combinePortNumP) {
+
+    char         command[1024];
+    int          otherSock;
+    const char * error;
+
+    snprintf(command, sizeof(command), 
+             "%s %s -max_machines %d -output_server %s %d %d %s",
+             encoderName, 
+             debugMachines ? "-debug_machines" : "",
+             numMachines, masterHostName, masterPortNum, 
+             numInputFiles, paramFileName);
+
+    machineDebug("MASTER: Starting combine server with shell command '%s'",
+                 command);
+
+    safe_fork(command);
+    
+    machineDebug("MASTER: Listening for connection back from "
+                 "new Combine server");
+
+    AcceptConnection(masterSocket, &otherSock, &error);
+    if (error)
+        errorExit("MASTER SERVER: "
+                  "Failed to accept next connection.  %s", error);
+
+    ReadInt(otherSock, combinePortNumP);
+    close(otherSock);
+
+    machineDebug("MASTER:  Combine port number = %d", *combinePortNumP);
+}
+
+
+
+static void
+startDecodeServer(const char * const encoderName,
+                  unsigned int const numMachines,
+                  const char * const masterHostName,
+                  int          const masterPortNum,
+                  unsigned int const numInputFiles,
+                  const char * const paramFileName,
+                  int          const masterSocket,
+                  int *        const decodePortNumP) {
+
+    char         command[1024];
+    int          otherSock;
+    const char * error;
+
+    snprintf(command, sizeof(command), 
+             "%s %s -max_machines %d -decode_server %s %d %d %s",
+             encoder_name, 
+             debugMachines ? "-debug_machines" : "",
+             numMachines, masterHostName, masterPortNum,
+             numInputFiles, paramFileName);
+
+    machineDebug("MASTER: Starting decode server with shell command '%s'",
+                 command);
+
+    safe_fork(command);
+
+    machineDebug("MASTER: Listening for connection back from "
+                 "new Decode server");
+
+    AcceptConnection(masterSocket, &otherSock, &error);
+    if (error)
+        errorExit("MASTER SERVER: "
+                  "Failed to accept connection back from the new "
+                  "decode server.  %s", error);
+
+    ReadInt(otherSock, decodePortNumP);
+    
+    close(otherSock);
+    
+    machineDebug("MASTER:  Decode port number = %d", *decodePortNumP);
+}
+
+
+
+static void
+startIoServer(const char *   const encoderName,
+              unsigned int   const numChildren,
+              const char *   const masterHostName,
+              int            const masterPortNum,
+              int            const masterSocket,
+              const char *   const paramFileName,
+              int *          const ioPortNumP) {
+              
+    char         command[1024];
+    int          otherSock;
+    const char * error;
+    
+    sprintf(command, "%s -max_machines %d -io_server %s %d %s",
+            encoderName, numChildren, masterHostName, masterPortNum,
+            paramFileName);
+
+    machineDebug("MASTER: Starting I/O server with remote shell command '%s'",
+                 command);
+
+    safe_fork(command);
+    
+    machineDebug("MASTER: Listening for connection back from "
+                 "new I/O server");
+
+    AcceptConnection(masterSocket, &otherSock, &error);
+    if (error)
+        errorExit("MASTER SERVER: "
+                  "Failed to accept connection back from the new "
+                  "I/O server.  %s", error);
+    
+    ReadInt(otherSock, ioPortNumP);
+    close(otherSock);
+    
+    machineDebug("MASTER:  I/O port number = %d", *ioPortNumP);
+}    
+    
+    
+
+static void
+extendToEndOfPattern(unsigned int * const nFramesP,
+                     unsigned int   const startFrame,
+                     unsigned int   const framePatternLen,
+                     unsigned int   const numFramesInStream) {
+
+    assert(framePatternLen >= 1);
+        
+    while (startFrame + *nFramesP < numFramesInStream &&
+           (startFrame + *nFramesP) % framePatternLen != 0)
+        ++(*nFramesP);
+}
+
+
+
+static void
+allocateInitialFrames(struct scheduler * const schedulerP,
+                      boolean            const parallelPerfect,
+                      boolean            const forceIalign,
+                      unsigned int       const framePatternLen,
+                      unsigned int       const parallelTestFrames,
+                      unsigned int       const childNum,
+                      unsigned int *     const startFrameP,
+                      unsigned int *     const nFramesP) {
+/*----------------------------------------------------------------------------
+   Choose which frames, to hand out to the new child numbered 'childNum'.
+-----------------------------------------------------------------------------*/
+    unsigned int const framesPerChild = 
+        MAX(1, ((schedulerP->numFramesInJob - schedulerP->nextFrame) /
+                (schedulerP->numMachines - childNum)));
+
+    unsigned int nFrames;
+
+    if (parallelPerfect)
+        nFrames = framesPerChild;
+    else {
+        assert(parallelTestFrames >= 1);
+
+        nFrames = MIN(parallelTestFrames, framesPerChild);
+    }
+    if (forceIalign)
+        extendToEndOfPattern(&nFrames, schedulerP->nextFrame,
+                             framePatternLen, schedulerP->numFramesInJob);
+
+    nFrames = MIN(nFrames, schedulerP->numFramesInJob - schedulerP->nextFrame);
+
+    *startFrameP = schedulerP->nextFrame;
+    *nFramesP = nFrames;
+    schedulerP->nextFrame += nFrames;
+}
+
+
+
+static float
+taperedGoalTime(struct childState const childState[],
+                unsigned int      const remainingFrameCount) {
+        
+    float        goalTime;
+    float        allChildrenFPS;
+    float        remainingJobTime;
+        /* How long we expect it to be before the whole movie is encoded*/
+    float        sum;
+    int          numMachinesToEstimate;
+    unsigned int childNum;
+    
+    /* frames left = lastFrameInStream - startFrame + 1 */
+    for (childNum = 0, sum = 0.0, numMachinesToEstimate = 0; 
+         childNum < numMachines; ++childNum) {
+        if (!childState[childNum].finished) {
+            if (childState[childNum].fps < 0.0 )
+                ++numMachinesToEstimate;
+            else
+                sum += childState[childNum].fps;
+        }
+    }
+    
+    allChildrenFPS = (float)numMachines *
+        (sum/(float)(numMachines-numMachinesToEstimate));
+    
+    remainingJobTime = (float)remainingFrameCount/allChildrenFPS;
+    
+    goalTime = MAX(5.0, remainingJobTime/2);
+
+    return goalTime;
+}
+
+
+
+static void
+allocateMoreFrames(struct scheduler * const schedulerP,
+                   unsigned int       const childNum,
+                   struct childState  const childState[],
+                   bool               const forceIalign,
+                   unsigned int       const framePatternLen,
+                   bool               const goalTimeSpecified,
+                   unsigned int       const goalTimeArg,
+                   unsigned int *     const startFrameP,
+                   unsigned int *     const nFramesP) {
+/*----------------------------------------------------------------------------
+   Decide which frames should be child 'childNum''s next assignment,
+   given the state/history of all children is childState[].
+
+   The lowest numbered frame which needs yet to be encoded is frame
+   number 'startFrame' and 'lastFrameInStream' is the highest.
+
+   The allocation always starts at the lowest numbered frame that
+   hasn't yet been allocated and is sequential.  We return as
+   *startFrameP the frame number of the first frame in the allocation
+   and as *nFramesP the number of frames.
+
+   If 'goalTimeSpecified' is true, we try to make the assignment take
+   'goalTimeArg' seconds.  If 'goalTimeSpecified' is not true, we choose
+   a goal time ourselves, which is based on how long we think it will
+   take for all the children to finish all the remaining frames.
+-----------------------------------------------------------------------------*/
+    float goalTime;
+        /* Number of seconds we want the assignment to take.  We size the
+           assignment to try to meet this goal.
+        */
+    unsigned int nFrames;
+    float avgFps;
+
+    if (!goalTimeSpecified) {
+        goalTime = taperedGoalTime(childState,
+                                   schedulerP->numFramesInJob - 
+                                   schedulerP->nextFrame);
+    
+        pm_message("MASTER: ASSIGNING %s %.2f seconds of work",
+                   machineName[childNum], goalTime);
+    } else
+        goalTime = goalTimeArg;
+    
+    if (childState[childNum].numSeconds != 0)
+        avgFps = (float)childState[childNum].numFrames / 
+            childState[childNum].numSeconds;
+    else
+        avgFps = 0.1;       /* arbitrary small value */
+
+    nFrames = MAX(1u, (unsigned int)(goalTime * avgFps + 0.5));
+    
+    nFrames = MIN(nFrames, 
+                  schedulerP->numFramesInJob - schedulerP->nextFrame);
+
+    if (forceIalign)
+        extendToEndOfPattern(&nFrames, schedulerP->nextFrame,
+                             framePatternLen, schedulerP->numFramesInJob);
+
+    *startFrameP = schedulerP->nextFrame;
+    *nFramesP = nFrames;
+    schedulerP->nextFrame += nFrames;
+}
+
+
+
+static void
+startChildren(struct scheduler *   const schedulerP,
+              const char *         const encoderName,
+              const char *         const masterHostName,
+              int                  const masterPortNum,
+              const char *         const paramFileName,
+              boolean              const parallelPerfect,
+              boolean              const forceIalign,
+              unsigned int         const framePatternLen,
+              unsigned int         const parallelTestFrames,
+              boolean              const beNice,
+              int                  const masterSocket,
+              int                  const combinePortNum,
+              int                  const decodePortNum,
+              int *                const ioPortNum,
+              unsigned int *       const numIoServersP,
+              struct childState ** const childStateP) {
+/*----------------------------------------------------------------------------
+   Start up the children.  Tell them to work for the master at
+   'masterHostName':'masterPortNum'.
+
+   Start I/O servers (as processes on this system) as required and return
+   the port numbers of the TCP ports on which they listen as
+   ioPortNum[] and the number of them as *numIoServersP.
+
+   Give each of the children some initial work to do.  This may be just
+   a small amount for timing purposes.
+
+   We access and manipulate the various global variables that represent
+   the state of the children, and the scheduler structure.
+-----------------------------------------------------------------------------*/
+    struct childState * childState;  /* malloc'ed */
+    unsigned int childNum;
+    unsigned int numIoServers;
+    unsigned int childrenLeftCurrentIoServer;
+        /* The number of additional children we can hook up to the
+           current I/O server before reaching our maximum children per
+           I/O server.  0 if there is no current I/O server.
+        */
+
+    MALLOCARRAY_NOFAIL(childState, schedulerP->numMachines);
+
+    childrenLeftCurrentIoServer = 0;  /* No current I/O server yet */
+    
+    numIoServers = 0;  /* None created yet */
+
+    for (childNum = 0; childNum < schedulerP->numMachines; ++childNum) {
+        char command[1024];
+        unsigned int startFrame;
+        unsigned int nFrames;
+
+        childState[childNum].fps        = -1.0;  /* illegal value as flag */
+        childState[childNum].numSeconds = 0;
+
+        allocateInitialFrames(schedulerP, parallelPerfect, forceIalign,
+                              framePatternLen, parallelTestFrames,
+                              childNum, &startFrame, &nFrames);
+
+        if (nFrames == 0) {
+            childState[childNum].finished = TRUE;
+            machineDebug("MASTER: No more frames; not starting child '%s'",
+                         machineName[childNum]);
+        } else {
+            childState[childNum].finished   = FALSE;
+        
+            if (remote[childNum]) {
+                if (childrenLeftCurrentIoServer == 0) {
+                    startIoServer(encoderName, schedulerP->numMachines, 
+                                  masterHostName, masterPortNum, masterSocket,
+                                  paramFileName, &ioPortNum[numIoServers++]);
+                    
+                    childrenLeftCurrentIoServer = SOMAXCONN;
+                }
+                --childrenLeftCurrentIoServer;
+            } 
+            snprintf(command, sizeof(command),
+                     "%s %s -l %s %s "
+                     "%s %s -child %s %d %d %d %d %d %d "
+                     "-frames %d %d %s",
+                     rsh,
+                     machineName[childNum], userName[childNum],
+                     beNice ? "nice" : "",
+                     executable[childNum],
+                     debugMachines ? "-debug_machines" : "",
+                     masterHostName, masterPortNum, 
+                     remote[childNum] ? ioPortNum[numIoServers-1] : 0,
+                     combinePortNum, decodePortNum, childNum,
+                     remote[childNum] ? 1 : 0,
+                     startFrame, startFrame + nFrames - 1,
+                     remote[childNum] ? 
+                         remoteParamFile[childNum] : paramFileName
+            );
+        
+            machineDebug("MASTER: Starting child server "
+                         "with shell command '%s'", command);
+
+            safe_fork(command);
+
+            machineDebug("MASTER: Frames %d-%d assigned to new child %s",
+                         startFrame, startFrame + nFrames - 1, 
+                         machineName[childNum]);
+        }
+        childState[childNum].startFrame = startFrame;
+        childState[childNum].lastNumFrames = nFrames;
+        childState[childNum].numFrames = childState[childNum].lastNumFrames;
+    }
+    *childStateP   = childState;
+    *numIoServersP = numIoServers;
+}
+
+
+
+static void
+noteFrameDone(const char * const combineHostName,
+              int          const combinePortNum,
+              unsigned int const frameStart, 
+              unsigned int const frameEnd) {
+/*----------------------------------------------------------------------------
+   Tell the Combine server that frames 'frameStart' through 'frameEnd'
+   are done.
+-----------------------------------------------------------------------------*/
+    int const negativeTwo = -2;
+    int clientSocket;
+    time_t  tempTimeStart, tempTimeEnd;
+    const char * error;
+    struct hostent * hostEntP;
+
+    time(&tempTimeStart);
+
+    hostEntP = NULL;
+
+    ConnectToSocket(combineHostName, combinePortNum, &hostEntP,
+                    &clientSocket, &error);
+    
+    if (error)
+        errorExit("MASTER: Can't connect to Combine server to tell it frames "
+                  "are done.  %s", error);
+
+    WriteInt(clientSocket, negativeTwo);
+
+    WriteInt(clientSocket, frameStart);
+
+    WriteInt(clientSocket, frameEnd);
+
+    close(clientSocket);
+
+    time(&tempTimeEnd);
+    IOtime += (tempTimeEnd-tempTimeStart);
+}
+
+
+
+static void
+feedTheChildren(struct scheduler * const schedulerP,
+                struct childState        childState[],
+                int                const masterSocket,
+                const char *       const combineHostName,
+                int                const combinePortNum,
+                bool               const forceIalign,
+                unsigned int       const framePatternLen,
+                bool               const goalTimeSpecified,
+                unsigned int       const goalTime) {
+/*----------------------------------------------------------------------------
+   Listen for children to tell us they have finished their assignments
+   and give them new assignments, until all the frames have been assigned
+   and all the children have finished.
+
+   As children finish assignments, inform the combine server at
+   'combineHostName':'combinePortNum' of such.
+
+   Note that the children got initial assigments when they were created.
+   So the first thing we do is wait for them to finish those.
+-----------------------------------------------------------------------------*/
+    unsigned int numFinished;
+        /* Number of child machines that have been excused because there
+           is no more work for them.
+        */
+    unsigned int framesDone;
+
+    numFinished = 0;
+    framesDone = 0;
+
+    while (numFinished != schedulerP->numMachines) {
+        int                 otherSock;
+        int                 childNum;
+        int                 seconds;
+        float               framesPerSecond;
+        struct childState * csP;
+        const char *        error;
+        unsigned int nextFrame;
+        unsigned int nFrames;
+
+        machineDebug("MASTER: Listening for a connection...");
+
+        AcceptConnection(masterSocket, &otherSock, &error);
+        if (error)
+            errorExit("MASTER SERVER: "
+                      "Failed to accept next connection.  %s", error);
+
+        ReadInt(otherSock, &childNum);
+        ReadInt(otherSock, &seconds);
+
+        csP = &childState[childNum];
+        
+        csP->numSeconds += seconds;
+        csP->fps = (float)csP->numFrames / (float)csP->numSeconds;
+
+        if (seconds != 0)
+            framesPerSecond = (float)csP->lastNumFrames / (float)seconds;
+        else
+            framesPerSecond = (float)csP->lastNumFrames * 2.0;
+
+        machineDebug("MASTER: Child %s FINISHED ASSIGNMENT.  "
+                     "%f frames per second", 
+                     machineName[childNum], framesPerSecond);
+
+        noteFrameDone(combineHostName, combinePortNum, csP->startFrame,
+                      csP->startFrame + csP->lastNumFrames - 1);
+
+        framesDone += csP->lastNumFrames;
+
+        allocateMoreFrames(schedulerP, childNum, childState,
+                           forceIalign, framePatternLen,
+                           goalTimeSpecified, goalTime,
+                           &nextFrame, &nFrames);
+
+        if (nFrames == 0) {
+            WriteInt(otherSock, -1);
+            WriteInt(otherSock, 0);
+
+            ++numFinished;
+
+            machineDebug("MASTER: NO MORE WORK FOR CHILD %s.  "
+                         "(%d of %d children now done)",
+                         machineName[childNum], numFinished, numMachines);
+        } else {
+            WriteInt(otherSock, nextFrame);
+            WriteInt(otherSock, nextFrame + nFrames - 1);
+
+            machineDebug("MASTER: Frames %d-%d assigned to child %s",
+                         nextFrame, nextFrame + nFrames - 1,
+                         machineName[childNum]);
+
+            csP->startFrame    = nextFrame;
+            csP->lastNumFrames = nFrames;
+            csP->numFrames    += csP->lastNumFrames;
+        }
+        close(otherSock);
+
+        machineDebug("MASTER: %d/%d DONE; %d ARE ASSIGNED",
+                     framesDone, schedulerP->numFramesInJob, 
+                     schedulerP->nextFrame - framesDone);
+    }
+}
+
+
+
+static void
+stopIoServers(const char * const hostName,
+              int          const ioPortNum[],
+              unsigned int const numIoServers) {
+
+    unsigned int childNum;
+
+    IOhostName = hostName;
+    for (childNum = 0; childNum < numIoServers; ++childNum) {
+        ioPortNumber = ioPortNum[childNum];
+        EndIOServer();
+    }
+}
+
+
+
+static void
+waitForCombineServerToTerminate(int const masterSocket) {
+
+    int otherSock;
+    const char * error;
+
+    machineDebug("MASTER SERVER: Waiting for combine server to terminate");
+
+    AcceptConnection(masterSocket, &otherSock, &error);
+    if (error)
+        errorExit("MASTER SERVER: "
+                  "Failed to accept connection expected from a "
+                  "terminating combine server.  %s", error);
+
+    {
+        int dummy;
+        ReadInt(otherSock, &dummy);
+    }
+    close(otherSock);
+}
+    
+
+
+static void
+printFinalStats(FILE *            const statfileP,
+                time_t            const startUpBegin,
+                time_t            const startUpEnd,
+                time_t            const shutDownBegin,
+                time_t            const shutDownEnd,
+                unsigned int      const numChildren,
+                struct childState const childState[],
+                unsigned int      const numFrames) {
+
+    unsigned int pass;
+    FILE * fileP;
+
+    for (pass = 0; pass < 2; ++pass) {
+        if (pass == 0)
+            fileP = stdout;
+        else
+            fileP = statfileP;
+
+        if (fileP) {
+            unsigned int childNum;
+            float totalFPS;
+
+            fprintf(fileP, "\n\n");
+            fprintf(fileP, "PARALLEL SUMMARY\n");
+            fprintf(fileP, "----------------\n");
+            fprintf(fileP, "\n");
+            fprintf(fileP, "START UP TIME:  %u seconds\n",
+                    (unsigned int)(startUpEnd - startUpBegin));
+            fprintf(fileP, "SHUT DOWN TIME:  %u seconds\n",
+                    (unsigned int)(shutDownEnd - shutDownBegin));
+            
+            fprintf(fileP, 
+                    "%14.14s %8.8s %8.8s %12.12s %9.9s\n",
+                    "MACHINE", "Frames", "Seconds", "Frames/Sec",
+                    "Self Time");
+
+            fprintf(fileP, 
+                    "%14.14s %8.8s %8.8s %12.12s %9.9s\n",
+                    "--------------", "--------", "--------", "------------",
+                    "---------");
+
+            totalFPS = 0.0;
+            for (childNum = 0; childNum < numChildren; ++childNum) {
+                float const localFPS = 
+                    (float)childState[childNum].numFrames /
+                    childState[childNum].numSeconds;
+                fprintf(fileP, "%14.14s %8u %8u %12.4f %8u\n",
+                        machineName[childNum], 
+                        childState[childNum].numFrames, 
+                        childState[childNum].numSeconds,
+                        localFPS, 
+                        (unsigned int)((float)numFrames/localFPS));
+                totalFPS += localFPS;
+            }
+
+            fprintf(fileP, 
+                    "%14.14s %8.8s %8.8s %12.12s %9.9s\n",
+                    "--------------", "--------", "--------", "------------",
+                    "---------");
+
+            fprintf(fileP, "%14s %8.8s %8u %12.4f\n", 
+                    "OPTIMAL", "", 
+                    (unsigned int)((float)numFrames/totalFPS),
+                    totalFPS);
+            
+            {
+                unsigned int const diffTime = shutDownEnd - startUpBegin;
+                
+                fprintf(fileP, "%14s %8.8s %8u %12.4f\n", 
+                        "ACTUAL", "", diffTime, 
+                        (float)numFrames / diffTime);
+            }
+            fprintf(fileP, "\n\n");
+        }
+    }
+}
+
+
+
+void
+MasterServer(struct inputSource * const inputSourceP,
+             const char *         const paramFileName, 
+             const char *         const outputFileName) {
+/*----------------------------------------------------------------------------
+   Execute the master server function.
+
+   Start all the other servers.
+-----------------------------------------------------------------------------*/
+    const char *hostName;
+    int       portNum;
+    int       masterSocket;
+        /* The file descriptor for the socket on which the master listens */
+    int ioPortNum[MAX_IO_SERVERS];
+    int       combinePortNum, decodePortNum;
+    struct childState * childState;  /* malloc'ed */
+    unsigned int numIoServers;
+    time_t  startUpBegin, startUpEnd;
+    time_t  shutDownBegin, shutDownEnd;
+    const char * error;
+    struct scheduler scheduler;
+
+    time(&startUpBegin);
+
+    scheduler.nextFrame = 0;
+    scheduler.numFramesInJob = inputSourceP->numInputFiles;
+    scheduler.numMachines = numMachines;
+
+    PrintStartStats(startUpBegin, FALSE, 0, 0, inputSourceP);
+
+    hostName = GetHostName();
+
+    hostEntry = gethostbyname(hostName);
+    if (hostEntry == NULL)
+        errorExit("Could not find host name '%s' in database", hostName);
+
+    CreateListeningSocket(&masterSocket, &portNum, &error);
+    if (error)
+        errorExit("Unable to create socket on which to listen.  %s", error);
+
+    if (debugSockets)
+        fprintf(stdout, "---MASTER USING PORT %d\n", portNum);
+
+    startCombineServer(encoder_name, numMachines, hostName, portNum,
+                       inputSourceP->numInputFiles, 
+                       paramFileName, masterSocket, 
+                       &combinePortNum);
+
+    if (referenceFrame == DECODED_FRAME)
+        startDecodeServer(encoder_name, numMachines, hostName, portNum,
+                          inputSourceP->numInputFiles, 
+                          paramFileName, masterSocket,
+                          &decodePortNum);
+
+    startChildren(&scheduler, encoder_name, hostName, portNum,
+                  paramFileName, parallelPerfect, forceIalign,
+                  framePatternLen, parallelTestFrames, 
+                  niceProcesses,
+                  masterSocket, combinePortNum, decodePortNum, 
+                  ioPortNum, &numIoServers,
+                  &childState);
+
+    time(&startUpEnd);
+
+    feedTheChildren(&scheduler, childState,
+                    masterSocket, hostName, combinePortNum,
+                    forceIalign, framePatternLen,
+                    parallelTimeChunks != -1, parallelTimeChunks);
+
+    assert(scheduler.nextFrame == scheduler.numFramesInJob);
+
+    time(&shutDownBegin);
+
+    stopIoServers(hostName, ioPortNum, numIoServers);
+
+    waitForCombineServerToTerminate(masterSocket);
+
+    close(masterSocket);
+
+    time(&shutDownEnd);
+
+    printFinalStats(statFile, startUpBegin, startUpEnd,
+                    shutDownBegin, shutDownEnd, numMachines,
+                    childState, inputSourceP->numInputFiles);
+
+    if (statFile)
+        fclose(statFile);
+
+    free(childState);
+    strfree(hostName);
+}
+
+
+
+void
+NotifyMasterDone(const char * const masterHostName, 
+                 int          const masterPortNum, 
+                 int          const childNum,
+                 unsigned int const seconds, 
+                 boolean *    const moreWorkToDoP,
+                 int *        const nextFrameStartP,
+                 int *        const nextFrameEndP) {
+/*----------------------------------------------------------------------------
+   Tell the master, at 'masterHostName':'masterPortNum' that child
+   number 'childNum' has finished its assignment, and the decoding
+   took 'seconds' wall clock seconds.  Get the next assignment, if
+   any, from the master.
+
+   If the master gives us a new assignment, return *moreWorkToDoP ==
+   TRUE and the frames the master wants us to do as *nextFrameStartP
+   and nextFrameEndP.  Otherwise (there is no more work for machine
+   'childNum' to do), return *moreWorkToDoP == FALSE.
+-----------------------------------------------------------------------------*/
+    int    clientSocket;
+    time_t tempTimeStart, tempTimeEnd;
+    const char * error;
+
+    machineDebug("CHILD: NOTIFYING MASTER Machine %d assignment complete", 
+                 childNum);
+
+    time(&tempTimeStart);
+    
+    ConnectToSocket(masterHostName, masterPortNum, &hostEntry,
+                    &clientSocket, &error);
+    if (error)
+        errorExit("CHILD: Can't connect to master to tell him we've finished "
+                  "our assignment.  %s", error);
+
+    WriteInt(clientSocket, childNum);
+    WriteInt(clientSocket, seconds);
+
+    ReadInt(clientSocket, nextFrameStartP);
+    ReadInt(clientSocket, nextFrameEndP);
+
+    *moreWorkToDoP = (*nextFrameStartP >= 0);
+
+    if (*moreWorkToDoP)
+        machineDebug("CHILD: Master says next assignment: start %d end %d",
+                     *nextFrameStartP, *nextFrameEndP);
+    else
+        machineDebug("CHILD: Master says no more work for us.");
+
+    close(clientSocket);
+
+    time(&tempTimeEnd);
+    IOtime += (tempTimeEnd-tempTimeStart);
+}
+
+
+
+void
+DecodeServer(int          const numInputFiles, 
+             const char * const decodeFileName, 
+             const char * const masterHostName, 
+             int          const masterPortNum) {
+/*----------------------------------------------------------------------------
+   Execute the decode server.
+
+   The decode server handles transfer of decoded frames to/from processes.
+
+   It is necessary only if referenceFrame == DECODED_FRAME.
+
+   Communicate to the master at hostname 'masterHostName':'masterPortNum'.
+
+-----------------------------------------------------------------------------*/
+    int     otherSock;
+    int     decodePortNum;
+    int     frameReady;
+    boolean *ready;
+    int     *waitMachine;
+    int     *waitPort;
+    int     *waitList;
+    int     slaveNumber;
+    int     slavePort;
+    int     waitPtr;
+    struct hostent *nullHost = NULL;
+    int     clientSocket;
+    const char * error;
+
+    /* should keep list of port numbers to notify when frames become ready */
+
+    ready = (boolean *) calloc(numInputFiles, sizeof(boolean));
+    waitMachine = (int *) calloc(numInputFiles, sizeof(int));
+    waitPort = (int *) malloc(numMachines*sizeof(int));
+    waitList = (int *) calloc(numMachines, sizeof(int));
+
+    CreateListeningSocket(&decodeServerSocket, &decodePortNum, &error);
+    if (error)
+        errorExit("Unable to create socket on which to listen.  %s", error);
+
+    machineDebug("DECODE SERVER LISTENING ON PORT %d", decodePortNum);
+
+    TransmitPortNum(masterHostName, masterPortNum, decodePortNum);
+
+    frameDone = (boolean *) malloc(numInputFiles*sizeof(boolean));
+    memset((char *)frameDone, 0, numInputFiles*sizeof(boolean));
+
+    /* wait for ready signals and requests */
+    while ( TRUE ) {
+        const char * error;
+
+        AcceptConnection(decodeServerSocket, &otherSock, &error);
+        if (error)
+            errorExit("DECODE SERVER: "
+                      "Failed to accept next connection.  %s", error);
+
+        ReadInt(otherSock, &frameReady);
+
+        if ( frameReady == -2 ) {
+            ReadInt(otherSock, &frameReady);
+
+            machineDebug("DECODE SERVER:  REQUEST FOR FRAME %d", frameReady);
+
+            /* now respond if it's ready yet */
+            WriteInt(otherSock, frameDone[frameReady]);
+
+            if ( ! frameDone[frameReady] ) {
+                /* read machine number, port number */
+                ReadInt(otherSock, &slaveNumber);
+                ReadInt(otherSock, &slavePort);
+
+                machineDebug("DECODE SERVER: WAITING:  SLAVE %d, PORT %d",
+                             slaveNumber, slavePort);
+
+                waitPort[slaveNumber] = slavePort;
+                if ( waitMachine[frameReady] == 0 ) {
+                    waitMachine[frameReady] = slaveNumber+1;
+                } else {
+                    /* someone already waiting for this frame */
+                    /* follow list of waiters to the end */
+                    waitPtr = waitMachine[frameReady]-1;
+                    while ( waitList[waitPtr] != 0 ) {
+                        waitPtr = waitList[waitPtr]-1;
+                    }
+
+                    waitList[waitPtr] = slaveNumber+1;
+                    waitList[slaveNumber] = 0;
+                }
+            }
+        } else {
+            frameDone[frameReady] = TRUE;
+            
+            machineDebug("DECODE SERVER:  FRAME %d READY", frameReady);
+
+            if ( waitMachine[frameReady] ) {
+                /* need to notify one or more machines it's ready */
+                waitPtr = waitMachine[frameReady]-1;
+                while ( waitPtr >= 0 ) {
+                    const char * error;
+                    ConnectToSocket(machineName[waitPtr], waitPort[waitPtr],
+                                    &nullHost,
+                                    &clientSocket, &error);
+                    if (error)
+                        errorExit("DECODE SERVER: "
+                                  "Can't connect to child machine.  %s", 
+                                  error);
+                    close(clientSocket);
+                    waitPtr = waitList[waitPtr]-1;
+                }
+            }
+        }
+
+        close(otherSock);
+    }
+    
+    machineDebug("DECODE SERVER:  Shutting down");
+
+    /* tell Master server we are done */
+    TransmitPortNum(masterHostName, masterPortNum, decodePortNum);
+
+    close(decodeServerSocket);
+}
+
+
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
diff --git a/converter/ppm/ppmtompeg/param.c b/converter/ppm/ppmtompeg/param.c
new file mode 100644
index 00000000..5ea69ab6
--- /dev/null
+++ b/converter/ppm/ppmtompeg/param.c
@@ -0,0 +1,1077 @@
+/*===========================================================================*
+ * param.c              
+ *                      
+ *  Procedures to read in parameter file  
+ *                                    
+ *===========================================================================*/
+
+/* COPYRIGHT INFORMATION IS AT THE END OF THIS FILE */
+
+#define _XOPEN_SOURCE 500
+    /* This makes sure popen() is in stdio.h.  In GNU libc 2.1.3, 
+     _POSIX_C_SOURCE = 2 is sufficient, but on AIX 4.3, the higher level
+     _XOPEN_SOURCE is required.  2000.09.09 
+
+     This also makes sure strdup() is in string.h.
+    */
+#define _BSD_SOURCE 1
+    /* Make sure string.h defines strncasecmp */
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "nstring.h"
+#include "mallocvar.h"
+#include "all.h"
+#include "mtypes.h"
+#include "mpeg.h"
+#include "motion_search.h"
+#include "prototypes.h"
+#include "parallel.h"
+#include "readframe.h"
+#include "fsize.h"
+#include "frames.h"
+#include "jpeg.h"
+#include "input.h"
+#include "frametype.h"
+#include "param.h"
+
+#include "rate.h"
+#include "opts.h"
+
+/*===========*
+ * CONSTANTS *
+ *===========*/
+
+#define INPUT_ENTRY_BLOCK_SIZE   128
+
+#define FIRST_OPTION           0
+#define OPTION_GOP             0
+#define OPTION_PATTERN         1
+#define OPTION_PIXEL           2
+#define OPTION_PQSCALE         3
+#define OPTION_OUTPUT          4
+#define OPTION_RANGE           5
+#define OPTION_PSEARCH_ALG     6
+#define OPTION_IQSCALE         7
+#define OPTION_INPUT_DIR       8
+#define OPTION_INPUT_CONVERT   9
+#define OPTION_INPUT          10
+#define OPTION_BQSCALE        11
+#define OPTION_BASE_FORMAT    12
+#define OPTION_SPF            13
+#define OPTION_BSEARCH_ALG    14
+#define OPTION_REF_FRAME      15
+#define LAST_OPTION           15
+
+/* put any non-required options after LAST_OPTION */
+#define OPTION_RESIZE	      16
+#define OPTION_IO_CONVERT     17
+#define OPTION_SLAVE_CONVERT  18
+#define OPTION_IQTABLE	      19
+#define OPTION_NIQTABLE	      20
+#define OPTION_FRAME_RATE     21
+#define OPTION_ASPECT_RATIO   22
+#define OPTION_YUV_SIZE	      23
+#define OPTION_SPECIFICS      24
+#define OPTION_DEFS_SPECIFICS 25
+#define OPTION_BUFFER_SIZE    26
+#define OPTION_BIT_RATE       27
+#define OPTION_USER_DATA      28
+#define OPTION_YUV_FORMAT     29
+#define OPTION_GAMMA          30
+#define OPTION_PARALLEL       31
+#define OPTION_FRAME_INPUT    32
+#define OPTION_GOP_INPUT      33
+
+#define NUM_OPTIONS           33
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+extern char currentPath[MAXPATHLEN];
+char	outputFileName[256];
+int	outputWidth, outputHeight;
+char inputConversion[1024];
+char ioConversion[1024];
+char slaveConversion[1024];
+char yuvConversion[256];
+char specificsFile[256],specificsDefines[1024]="";
+boolean GammaCorrection=FALSE;
+float   GammaValue;
+char userDataFileName[256]={0};
+boolean specificsOn = FALSE;
+char currentGOPPath[MAXPATHLEN];
+char currentFramePath[MAXPATHLEN];
+boolean keepTempFiles;
+
+static const char * const optionText[LAST_OPTION+1] = { 
+    "GOP_SIZE", "PATTERN", "PIXEL", "PQSCALE",
+    "OUTPUT", "RANGE", "PSEARCH_ALG", "IQSCALE", "INPUT_DIR",
+    "INPUT_CONVERT", "INPUT", "BQSCALE", "BASE_FILE_FORMAT",
+    "SLICES_PER_FRAME", "BSEARCH_ALG", "REFERENCE_FRAME"};
+static boolean optionSeen[NUM_OPTIONS+1];
+    /* optionSeen[x] means we have seen option x in the parameter file we've
+       been reading.
+    */
+
+int numMachines;
+char	machineName[MAX_MACHINES][256];
+char	userName[MAX_MACHINES][256];
+char	executable[MAX_MACHINES][1024];
+char	remoteParamFile[MAX_MACHINES][1024];
+boolean	remote[MAX_MACHINES];
+int mult_seq_headers = 0;  /* 0 for none, N for header/N GOPs */
+
+
+/*===========================================================================*
+ *
+ * SkipSpacesTabs
+ *
+ *	skip all spaces and tabs
+ *
+ * RETURNS:	point to next character not a space or tab
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static const char *
+SkipSpacesTabs(const char * const start) {
+
+    const char * p;
+
+    for (p = start; *p == ' ' || *p == '\t'; ++p);
+
+    return p;
+}
+
+
+
+/*===========================================================================*
+ *
+ * GetAspectRatio
+ *
+ * take a character string with the pixel aspect ratio
+ * and returns the correct aspect ratio code for use in the Sequence header
+ *
+ * RETURNS: aspect ratio code as per MPEG-I spec
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static int
+GetAspectRatio(const char * const p)
+{
+  float   ratio;
+  int	  ttRatio;
+  int     retval;
+
+  sscanf(p, "%f", &ratio);
+  ttRatio = (int)(0.5+ratio*10000.0);
+
+  if ( ttRatio == 10000 )	      retval = 1;
+  else if ( ttRatio ==  6735 )    retval = 2;
+  else if ( ttRatio ==  7031 )    retval = 3;
+  else if ( ttRatio ==  7615 )    retval = 4;
+  else if ( ttRatio ==  8055 )    retval = 5;
+  else if ( ttRatio ==  8437 )    retval = 6;
+  else if ( ttRatio ==  8935 )    retval = 7;
+  else if ( ttRatio ==  9157 )    retval = 8;
+  else if ( ttRatio ==  9815 )    retval = 9;
+  else if ( ttRatio == 10255 )    retval = 10;
+  else if ( ttRatio == 10695 )    retval = 11;
+  else if ( ttRatio == 10950 )    retval = 12;
+  else if ( ttRatio == 11575 )    retval = 13;
+  else if ( ttRatio == 12015 )    retval = 14;
+  else {
+    fprintf(stderr,"INVALID ASPECT RATIO: %s frames/sec\n", p);
+    exit(1);
+  }
+  return retval;
+}
+
+
+
+/*===========================================================================*
+ *
+ * ReadMachineNames
+ *
+ *	read a list of machine names for parallel execution
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    machine info updated
+ *
+ *===========================================================================*/
+static void
+ReadMachineNames(FILE * const fpointer)
+{
+  char    input[256];
+  const char *charPtr;
+
+  while ( (fgets(input, 256, fpointer) != NULL) &&
+	 (strncmp(input, "END_PARALLEL", 12) != 0) ) {
+    if ( input[0] == '#' || input[0] == '\n') {
+      continue;
+    }
+
+    if ( strncmp(input, "REMOTE", 6) == 0 ) {
+      charPtr = SkipSpacesTabs(&input[6]);
+      remote[numMachines] = TRUE;
+
+      sscanf(charPtr, "%s %s %s %s", machineName[numMachines],
+	     userName[numMachines], executable[numMachines],
+	     remoteParamFile[numMachines]);
+    } else {
+      remote[numMachines] = FALSE;
+
+      sscanf(input, "%s %s %s", machineName[numMachines],
+	     userName[numMachines], executable[numMachines]);
+    }
+
+    numMachines++;
+  }
+}
+
+
+/*===========================================================================*
+ *
+ * GetFrameRate
+ *
+ * take a character string with the input frame rate 
+ * and return the correct frame rate code for use in the Sequence header
+ *
+ * RETURNS: frame rate code as per MPEG-I spec
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static int
+GetFrameRate(const char * const p)
+{
+  float   rate;
+  int	  thouRate;
+  int     retval;
+
+  sscanf(p, "%f", &rate);
+  thouRate = (int)(0.5+1000.0*rate);
+
+  if ( thouRate == 23976 )	       retval = 1;
+  else if ( thouRate == 24000 )    retval = 2;
+  else if ( thouRate == 25000 )    retval = 3;
+  else if ( thouRate == 29970 )    retval = 4;
+  else if ( thouRate == 30000 )    retval = 5;
+  else if ( thouRate == 50000 )    retval = 6;
+  else if ( thouRate == 59940 )    retval = 7;
+  else if ( thouRate == 60000 )    retval = 8;
+  else {
+    fprintf(stderr,"INVALID FRAME RATE: %s frames/sec\n", p);
+    exit(1);
+  }
+  return retval;
+}
+
+
+
+static void
+mergeInputSource(struct inputSource * const baseSourceP,
+                 struct inputSource * const addedSourceP) {
+
+    unsigned int i;
+
+    baseSourceP->ifArraySize += addedSourceP->numInputFileEntries;
+    REALLOCARRAY_NOFAIL(baseSourceP->inputFileEntries, 
+                        baseSourceP->ifArraySize);
+    for (i = 0; i < addedSourceP->numInputFileEntries; ++i)
+        baseSourceP->inputFileEntries[baseSourceP->numInputFileEntries++] =
+            addedSourceP->inputFileEntries[i];
+
+    free(addedSourceP);
+    /* Note the space allocated for the *addedSourceP input file
+       entries themselves is still allocated, and used by 
+       *baseSourceP.
+    */
+}
+
+
+
+/* Forward declaration for recursively called function */
+static void
+ReadInputFileNames(FILE *               const fpointer, 
+                   const char *         const endInput,
+                   struct inputSource * const inputSourceP);
+
+static void
+expandBackTickLine(const char *         const input,
+                   struct inputSource * const inputSourceP) {
+
+    FILE *fp;
+    char cmd[300];
+    const char * start;
+    const char * end;
+    char cdcmd[110];
+
+    start = &input[1];
+    end = &input[strlen(input)-1];
+
+    while (*end != '`') {
+        end--;
+    }
+
+    end--;
+
+    if (optionSeen[OPTION_INPUT_DIR])
+        sprintf(cdcmd,"cd %s;",currentPath);
+    else
+        strcpy(cdcmd,"");
+
+    {
+        char tmp[300];
+        strncpy(tmp,start,end-start+1);
+        sprintf(cmd,"(%s %s)", cdcmd, tmp);
+    }
+
+    fp = popen(cmd, "r");
+    if (fp == NULL) {
+        pm_error("Unable to resolve backtick entry in input file list.  "
+                 "Could not open piped command: '%s'", cmd);
+    } else {
+        struct inputSource subInputSource;
+        ReadInputFileNames(fp, "HOPE-THIS_ISNT_A_FILENAME.xyz5555",
+                           &subInputSource);
+
+        mergeInputSource(inputSourceP, &subInputSource);
+    }
+}
+
+
+
+static void
+ReadInputFileNames(FILE *               const fpointer, 
+                   const char *         const endInput,
+                   struct inputSource * const inputSourceP) {
+/*----------------------------------------------------------------------------
+   Read a list of input file names from the parameter file.  Add 
+   the information to *inputSourceP.
+
+   If inputSourceP == NULL, read off the list, but ignore it.
+-----------------------------------------------------------------------------*/
+    char input[256];
+    bool endStatementRead;
+
+    /* read param file up through endInput statement.  Each line until
+       endInput is a file specification.
+    */
+    endStatementRead = FALSE;
+
+    while (!endStatementRead) {
+        char * rc;
+        rc = fgets(input, sizeof(input), fpointer);
+
+        if (feof(fpointer))
+            pm_error("End of file before section end statement '%s'",
+                     endInput);
+        else if (rc == NULL)
+            pm_error("Error reading file names from parameter file.");
+        else {
+            if (strncmp(input, endInput, strlen(endInput)) == 0)
+                endStatementRead = TRUE;
+            else if ((input[0] == '#') || (input[0] == '\n')) { 
+                /* It's a comment or blank line.  Ignore it */
+            } else if (input[0] == '`' ) {
+                expandBackTickLine(input, inputSourceP);
+            } else {
+                /* get rid of trailing whitespace including newline */
+                while (ISSPACE(input[strlen(input)-1]))
+                    input[strlen(input)-1] = '\0';
+                if (inputSourceP)
+                    AddInputFiles(inputSourceP, input);
+            }
+        }
+    }
+}
+
+
+
+static void
+initOptionSeen(void) {
+
+    unsigned int index;
+    
+    for (index = FIRST_OPTION; index < NUM_OPTIONS; ++index)
+        optionSeen[index] = FALSE;
+}
+
+
+
+static void
+verifyNoMissingEncodeFramesOption(void) {
+    if (!optionSeen[OPTION_GOP])
+        pm_error("GOP_SIZE option missing");
+    if (!optionSeen[OPTION_PATTERN])
+        pm_error("PATTERN option missing");
+    if (!optionSeen[OPTION_PIXEL])
+        pm_error("PIXEL option missing");
+    if (!optionSeen[OPTION_PQSCALE])
+        pm_error("PQSCALE option missing");
+    if (!optionSeen[OPTION_OUTPUT])
+        pm_error("OUTPUT option missing");
+    if (!optionSeen[OPTION_RANGE])
+        pm_error("RANGE option missing");
+    if (!optionSeen[OPTION_PSEARCH_ALG])
+        pm_error("PSEARCH_ALG option missing");
+    if (!optionSeen[OPTION_IQSCALE])
+        pm_error("IQSCALE option missing");
+    if (!optionSeen[OPTION_INPUT_DIR])
+        pm_error("INPUT_DIR option missing");
+    if (!optionSeen[OPTION_INPUT_CONVERT])
+        pm_error("INPUT_CONVERT option missing");
+    if (!optionSeen[OPTION_BQSCALE])
+        pm_error("BQSCALE option missing");
+    if (!optionSeen[OPTION_BASE_FORMAT])
+        pm_error("BASE_FILE_FORMAT option missing");
+    if (!optionSeen[OPTION_SPF])
+        pm_error("SLICES_PER_FRAME option missing");
+    if (!optionSeen[OPTION_BSEARCH_ALG])
+        pm_error("BSEARCH_ALG option missing");
+    if (!optionSeen[OPTION_REF_FRAME])
+        pm_error("REFERENCE_FRAME option missing");
+}
+
+
+
+static void
+verifyNoMissingCombineGopsOption(void) {
+
+    if (!optionSeen[OPTION_GOP_INPUT])
+        pm_error("GOP_INPUT option missing");
+    if (!optionSeen[OPTION_YUV_SIZE])
+        pm_error("YUV_SIZE option missing");
+    if (!optionSeen[OPTION_OUTPUT])
+        pm_error("OUTPUT option missing");
+}
+
+
+
+static void
+verifyNoMissingCombineFramesOption(void) {
+
+    if (!optionSeen[OPTION_GOP])
+        pm_error("GOP_SIZE option missing");
+    if (!optionSeen[OPTION_YUV_SIZE])
+        pm_error("YUV_SIZE option missing");
+    if (!optionSeen[OPTION_OUTPUT])
+        pm_error("OUTPUT option missing");
+}
+
+
+
+static void
+verifyNoMissingOption(int  const function) {
+/*----------------------------------------------------------------------------
+  Verify that the parameter file contains every option it is supposed to. 
+  Abort program if not.
+-----------------------------------------------------------------------------*/
+    switch(function) {
+    case ENCODE_FRAMES:
+        verifyNoMissingEncodeFramesOption();
+        break;
+    case COMBINE_GOPS:
+        verifyNoMissingCombineGopsOption();
+        break;
+    case COMBINE_FRAMES:
+        verifyNoMissingCombineFramesOption();
+        break;
+    default:
+        pm_error("Internal error - impossible value for 'function': %d",
+                 function);
+    }
+}
+
+
+
+static void
+processIqtable(FILE * const fpointer) {
+
+    unsigned int row;
+    unsigned int col;
+    char input[256];
+
+    for (row = 0; row < 8; ++row) {
+        const char * charPtr;
+
+        fgets(input, 256, fpointer);
+        charPtr = input;
+        if (8 != sscanf(charPtr,"%d %d %d %d %d %d %d %d",
+                        &qtable[row*8+0],  &qtable[row*8+1],
+                        &qtable[row*8+2],  &qtable[row*8+3],
+                        &qtable[row*8+4],  &qtable[row*8+5],
+                        &qtable[row*8+6],  &qtable[row*8+7])) {
+            pm_error("Line %d of IQTABLE doesn't have 8 elements!", 
+                     row);
+        }
+        for (col = 0; col < 8; ++col) {
+            if ((qtable[row*8+col]<1) || (qtable[row*8+col]>255)) {
+                pm_message(
+                    "Warning:  IQTable Element %1d,%1d (%d) "
+                    "corrected to 1-255.",
+                    row+1, col+1, qtable[row*8+col]);
+                qtable[row*8+col] = (qtable[row*8+col]<1)?1:255;
+            }
+        }
+    }
+            
+    if (qtable[0] != 8) {
+        pm_message("Warning:  IQTable Element 1,1 reset to 8, "
+                   "since it must be 8.");
+        qtable[0] = 8;
+    }
+    customQtable = qtable;
+}
+
+
+
+static void
+setInputSource(int                   const function,
+               struct inputSource ** const inputSourcePP,
+               struct inputSource *  const inputSourceP,
+               struct inputSource *  const gopInputSourceP,
+               struct inputSource *  const frameInputSourceP) {
+/*----------------------------------------------------------------------------
+   Given three the three input sources described by the parameter
+   file, 'inputSourceP', 'gopInputSourceP', and 'frameInputSourceP',
+   return as *inputSourcePP the appropriate one of them for the
+   function 'function', and destroy the other two.
+-----------------------------------------------------------------------------*/
+    switch (function) {
+    case ENCODE_FRAMES:
+        *inputSourcePP = inputSourceP;
+        DestroyInputSource(gopInputSourceP);
+        DestroyInputSource(frameInputSourceP);
+        break;
+    case COMBINE_GOPS:
+        *inputSourcePP = gopInputSourceP;
+        DestroyInputSource(inputSourceP);
+        DestroyInputSource(frameInputSourceP);
+        break;
+    case COMBINE_FRAMES:
+        *inputSourcePP = frameInputSourceP;
+        DestroyInputSource(inputSourceP);
+        DestroyInputSource(gopInputSourceP);
+        break;
+    default:
+      pm_error("INTERNAL ERROR: invalid 'function' %d", function);
+    }
+}
+
+
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+
+
+
+static void
+removeTrailingWhite(const char *  const rawLine,
+                    const char ** const editedLineP) {
+
+    char * buffer;
+    char * p;
+
+    buffer = strdup(rawLine);
+
+    if (!buffer)
+        pm_error("Unable to get memory to process parameter file");
+
+    p = buffer + strlen(buffer) - 1;  /* Point to last character */
+    
+    /* Position p to just before the trailing white space (might be one
+       character before beginning of string!)
+    */
+    while (p >= buffer && isspace(*p))
+        --p;
+    
+    ++p;  /* Move to first trailing whitespace character */
+    
+    *p = '\0';  /* Chop off all the trailing whitespace */
+
+    *editedLineP = buffer;
+}
+
+
+
+static void
+readNiqTable(FILE * const fpointer) {
+
+    unsigned int row;
+    for (row = 0; row < 8; ++row) {
+        char input[256];
+        unsigned int col;
+        fgets(input, sizeof(input), fpointer);
+        if (8 != sscanf(input, "%d %d %d %d %d %d %d %d",
+                        &niqtable[row*8+0],  &niqtable[row*8+1],
+                        &niqtable[row*8+2],  &niqtable[row*8+3],
+                        &niqtable[row*8+4],  &niqtable[row*8+5],
+                        &niqtable[row*8+6],  &niqtable[row*8+7])) {
+            pm_error("Line %d of NIQTABLE doesn't have 8 elements!", 
+                     row);
+        }
+        for ( col = 0; col < 8; col++ ) {
+            if ((niqtable[row*8+col]<1) || (niqtable[row*8+col]>255)) {
+                pm_message(
+                    "Warning:  NIQTable Element %1d,%1d (%d) "
+                    "corrected to 1-255.",
+                    row + 1, col + 1, niqtable[row * 8 + col]);
+                niqtable[row * 8 + col] = (niqtable[row*8+col] < 1) ? 1 : 255;
+            }
+        }
+    }
+
+    customNIQtable = niqtable;
+}
+
+
+
+static void
+processRanges(const char * const arg) {
+
+    int numRanges;
+    int a, b;
+
+    numRanges = sscanf(arg, "%d %d", &a, &b);
+
+    if (numRanges == 2)
+        SetSearchRange(a, b);
+    else if (sscanf(arg, "%d [%d]", &a, &b) == 2)
+        SetSearchRange(a, b);
+    else
+        SetSearchRange(a, a);
+}
+
+
+
+static void
+processParamLine(char const input[],
+                 FILE * const fpointer,
+                 bool * const yuvUsedP,
+                 struct inputSource * const inputSourceP,
+                 struct inputSource * const frameInputSourceP,
+                 struct inputSource * const gopInputSourceP,
+                 struct params * const paramP) {
+
+    switch(input[0]) {
+    case 'A':
+        if (STRNEQ(input, "ASPECT_RATIO", 12)) {
+            aspectRatio = GetAspectRatio(SkipSpacesTabs(&input[12]));
+            optionSeen[OPTION_ASPECT_RATIO] = TRUE;
+        }
+        break;
+        
+    case 'B':
+        if (STRNEQ(input, "BQSCALE", 7)) {
+            SetBQScale(atoi(SkipSpacesTabs(&input[7])));
+            optionSeen[OPTION_BQSCALE] = TRUE;
+        } else if (STRNEQ(input, "BASE_FILE_FORMAT", 16)) {
+            const char * arg = SkipSpacesTabs(&input[16]);
+            SetFileFormat(arg);
+            if (STRNEQ(arg, "YUV", 3) || STREQ(arg, "Y"))
+                *yuvUsedP = TRUE;
+            optionSeen[OPTION_BASE_FORMAT] = TRUE;
+        } else if (STRNEQ(input, "BSEARCH_ALG", 11)) {
+            SetBSearchAlg(SkipSpacesTabs(&input[11]));
+            optionSeen[OPTION_BSEARCH_ALG] = TRUE;
+        } else if (STRNEQ(input, "BIT_RATE", 8)) {
+            setBitRate(SkipSpacesTabs(&input[8]));
+            optionSeen[OPTION_BIT_RATE] = TRUE;
+        } else if (STRNEQ(input, "BUFFER_SIZE", 11)) {
+            setBufferSize(SkipSpacesTabs(&input[11]));
+            optionSeen[OPTION_BUFFER_SIZE] = TRUE;                  
+        }
+        break;
+
+    case 'C':
+        if (STRNEQ(input, "CDL_FILE", 8)) {
+            strcpy(specificsFile, SkipSpacesTabs(&input[8]));
+            specificsOn = TRUE;
+            optionSeen[OPTION_SPECIFICS] = TRUE;
+        } else if (STRNEQ(input, "CDL_DEFINES", 11)) {
+            strcpy(specificsDefines, SkipSpacesTabs(&input[11]));
+            optionSeen[OPTION_DEFS_SPECIFICS] = TRUE;
+        }
+        break;
+
+    case 'F':
+        if (STRNEQ(input, "FRAME_INPUT_DIR", 15)) {
+            const char * const arg = SkipSpacesTabs(&input[15]);
+            if (STRNCASEEQ(arg, "stdin", 5))
+                SetStdinInput(frameInputSourceP);
+
+            strcpy(currentFramePath, arg);
+        } else if (STRNEQ(input, "FRAME_INPUT", 11)) {
+            ReadInputFileNames(fpointer, "FRAME_END_INPUT", 
+                               frameInputSourceP->stdinUsed ? 
+                               NULL : frameInputSourceP);
+            optionSeen[OPTION_FRAME_INPUT] = TRUE;
+        } else if (STRNEQ(input, "FORCE_I_ALIGN", 13)) {
+            forceIalign = TRUE;
+        } else if (STRNEQ(input, "FORCE_ENCODE_LAST_FRAME", 23)) {
+            /* NO-OP.  We used to drop trailing B frames by default and you
+               needed this option to change the last frame to I so you could
+               encode all the frames.  Now we just do that all the time.  
+               Why wouldn't we?
+            */
+        } else if (STRNEQ(input, "FRAME_RATE", 10)) {
+            frameRate = GetFrameRate(SkipSpacesTabs(&input[10]));
+            frameRateRounded = (int) VidRateNum[frameRate];
+            if ((frameRate % 3) == 1)
+                frameRateInteger = FALSE;
+            optionSeen[OPTION_FRAME_RATE] = TRUE;
+        }
+        break;
+        
+    case 'G':
+        if (STRNEQ(input, "GOP_SIZE", 8)) {
+            SetGOPSize(atoi(SkipSpacesTabs(&input[8])));
+            optionSeen[OPTION_GOP] = TRUE;
+        } else if (STRNEQ(input, "GOP_INPUT_DIR", 13)) {
+            const char * const arg = SkipSpacesTabs(&input[13]);
+            if (STRNCASEEQ(arg, "stdin", 5))
+                SetStdinInput(gopInputSourceP);
+
+            strcpy(currentGOPPath, arg);
+        } else if (STRNEQ(input, "GOP_INPUT", 9)) {
+            ReadInputFileNames(fpointer, "GOP_END_INPUT", 
+                               gopInputSourceP->stdinUsed ? 
+                               NULL : gopInputSourceP);
+            optionSeen[OPTION_GOP_INPUT] = TRUE;
+        } else if (STRNEQ(input, "GAMMA", 5)) {
+            GammaCorrection = TRUE;
+            sscanf(SkipSpacesTabs(&input[5]), "%f", &GammaValue);
+            optionSeen[OPTION_GAMMA] = TRUE;
+        }
+        break;
+        
+    case 'I':
+        if (STRNEQ(input, "IQSCALE", 7)) {
+            SetIQScale(atoi(SkipSpacesTabs(&input[7])));
+            optionSeen[OPTION_IQSCALE] = TRUE;
+        } else if (STRNEQ(input, "INPUT_DIR", 9)) {
+            const char * const arg = SkipSpacesTabs(&input[9]);
+            if (STRNCASEEQ(arg, "stdin", 5))
+                SetStdinInput(inputSourceP);
+            strcpy(currentPath, arg);
+            optionSeen[OPTION_INPUT_DIR] = TRUE;
+        } else if (STRNEQ(input, "INPUT_CONVERT", 13)) {
+            strcpy(inputConversion, SkipSpacesTabs(&input[13]));
+            optionSeen[OPTION_INPUT_CONVERT] = TRUE;
+        } else if (STREQ(input, "INPUT")) {
+            ReadInputFileNames(fpointer, "END_INPUT", 
+                               inputSourceP->stdinUsed ?
+                               NULL : inputSourceP);
+            optionSeen[OPTION_INPUT] = TRUE;
+        } else if (STRNEQ(input, "IO_SERVER_CONVERT", 17)) {
+            strcpy(ioConversion, SkipSpacesTabs(&input[17]));
+            optionSeen[OPTION_IO_CONVERT] = TRUE;
+        } else if (STRNEQ(input, "IQTABLE", 7)) {
+            processIqtable(fpointer);
+
+            optionSeen[OPTION_IQTABLE] = TRUE;
+        }
+        break;
+
+    case 'K':
+        if (STRNEQ(input, "KEEP_TEMP_FILES", 15))
+            keepTempFiles = TRUE;
+        break;
+        
+    case 'N':
+      if (STRNEQ(input, "NIQTABLE", 8)) {
+          readNiqTable(fpointer);
+
+          optionSeen[OPTION_NIQTABLE] = TRUE;
+      }
+      break;
+
+    case 'O':
+        if (STRNEQ(input, "OUTPUT", 6)) {
+            const char * const arg = SkipSpacesTabs(&input[6]);
+            if ( whichGOP == -1 )
+                strcpy(outputFileName, arg);
+            else
+                sprintf(outputFileName, "%s.gop.%d", arg, whichGOP);
+            
+            optionSeen[OPTION_OUTPUT] = TRUE;
+        }
+        break;
+        
+    case 'P':
+        if (STRNEQ(input, "PATTERN", 7)) {
+            SetFramePattern(SkipSpacesTabs(&input[7]));
+            optionSeen[OPTION_PATTERN] = TRUE;
+        } else if (STRNEQ(input, "PIXEL", 5)) {
+            SetPixelSearch(SkipSpacesTabs(&input[5]));
+            optionSeen[OPTION_PIXEL] = TRUE;
+        } else if (STRNEQ(input, "PQSCALE", 7)) {
+            SetPQScale(atoi(SkipSpacesTabs(&input[7])));
+            optionSeen[OPTION_PQSCALE] = TRUE;
+        } else if (STRNEQ(input, "PSEARCH_ALG", 11)) {
+            SetPSearchAlg(SkipSpacesTabs(&input[11]));
+            optionSeen[OPTION_PSEARCH_ALG] = TRUE;
+        } else if (STRNEQ(input, "PARALLEL_TEST_FRAMES", 20)) {
+            SetParallelPerfect(FALSE);
+            parallelTestFrames = atoi(SkipSpacesTabs(&input[20]));
+        } else if (STRNEQ(input, "PARALLEL_TIME_CHUNKS", 20)) {
+            SetParallelPerfect(FALSE);
+            parallelTimeChunks = atoi(SkipSpacesTabs(&input[20]));
+        } else if (STRNEQ(input, "PARALLEL_CHUNK_TAPER", 20)) {
+            SetParallelPerfect(FALSE);
+            parallelTimeChunks = -1;
+        } else if (STRNEQ(input, "PARALLEL_PERFECT", 16)) {
+            SetParallelPerfect(TRUE);
+        } else if (STRNEQ(input, "PARALLEL", 8)) {
+            ReadMachineNames(fpointer);
+            optionSeen[OPTION_PARALLEL] = TRUE;
+        }
+        break;
+        
+    case 'R':
+        if (STRNEQ(input, "RANGE", 5)) {
+            processRanges(SkipSpacesTabs(&input[5]));
+            optionSeen[OPTION_RANGE] = TRUE;
+        } else if (STRNEQ(input, "REFERENCE_FRAME", 15)) {
+            SetReferenceFrameType(SkipSpacesTabs(&input[15]));
+            optionSeen[OPTION_REF_FRAME] = TRUE;
+        } else if (STRNEQ(input, "RSH", 3)) {
+            SetRemoteShell(SkipSpacesTabs(&input[3]));
+        } else if (STRNEQ(input, "RESIZE", 6)) {
+            const char * const arg = SkipSpacesTabs(&input[6]);
+            sscanf(arg, "%dx%d", &outputWidth, &outputHeight);
+            outputWidth &= ~(DCTSIZE * 2 - 1);
+            outputHeight &= ~(DCTSIZE * 2 - 1);
+            optionSeen[OPTION_RESIZE] = TRUE;
+        }
+        break;
+
+    case 'S':
+        if (STRNEQ(input, "SLICES_PER_FRAME", 16)) {
+            SetSlicesPerFrame(atoi(SkipSpacesTabs(&input[16])));
+            optionSeen[OPTION_SPF] = TRUE;
+        } else if (STRNEQ(input, "SLAVE_CONVERT", 13)) {
+            strcpy(slaveConversion, SkipSpacesTabs(&input[13]));
+            optionSeen[OPTION_SLAVE_CONVERT] = TRUE;
+        } else if (STRNEQ(input, "SPECIFICS_FILE", 14)) {
+            strcpy(specificsFile, SkipSpacesTabs(&input[14]));
+            specificsOn = TRUE;
+            optionSeen[OPTION_SPECIFICS] = TRUE;
+        } else if (STRNEQ(input, "SPECIFICS_DEFINES", 16)) {
+            strcpy(specificsDefines, SkipSpacesTabs(&input[17]));
+            optionSeen[OPTION_DEFS_SPECIFICS] = TRUE;
+        } else if (STRNEQ(input, "SEQUENCE_SIZE", 13)) {
+            mult_seq_headers = atoi(SkipSpacesTabs(&input[13]));
+        } else if (STRNEQ(input, "SIZE", 4)) {
+            const char * const arg = SkipSpacesTabs(&input[4]);
+            sscanf(arg, "%dx%d", &yuvWidth, &yuvHeight);
+            realWidth = yuvWidth;
+            realHeight = yuvHeight;
+            Fsize_Validate(&yuvWidth, &yuvHeight);
+            optionSeen[OPTION_YUV_SIZE] = TRUE;
+        }
+        break;
+
+    case 'T':
+        if (STRNEQ(input, "TUNE", 4)) {
+            tuneingOn = TRUE;
+            ParseTuneParam(SkipSpacesTabs(&input[4]));
+        }
+        break;
+
+    case 'U':
+        if (STRNEQ(input, "USER_DATA", 9)) {
+            strcpy(userDataFileName, SkipSpacesTabs(&input[9]));
+            optionSeen[OPTION_USER_DATA] = TRUE;
+        }
+        break;
+        
+    case 'W':
+        if (STRNEQ(input, "WARN_UNDERFLOW", 14))
+            paramP->warnUnderflow = TRUE;
+        if (STRNEQ(input, "WARN_OVERFLOW", 13))
+            paramP->warnOverflow = TRUE;
+        break;
+        
+    case 'Y':
+        if (STRNEQ(input, "YUV_SIZE", 8)) {
+            const char * const arg = SkipSpacesTabs(&input[8]);
+            sscanf(arg, "%dx%d", &yuvWidth, &yuvHeight);
+            realWidth = yuvWidth;
+            realHeight = yuvHeight;
+            Fsize_Validate(&yuvWidth, &yuvHeight);
+            optionSeen[OPTION_YUV_SIZE] = TRUE;
+        } else if (STRNEQ(input, "Y_SIZE", 6)) {
+            const char * const arg = SkipSpacesTabs(&input[6]);
+            sscanf(arg, "%dx%d", &yuvWidth, &yuvHeight);
+            realWidth = yuvWidth;
+            realHeight = yuvHeight;
+            Fsize_Validate(&yuvWidth, &yuvHeight);
+            optionSeen[OPTION_YUV_SIZE] = TRUE;
+        } else if (STRNEQ(input, "YUV_FORMAT", 10)) {
+            strcpy(yuvConversion,  SkipSpacesTabs(&input[10]));
+            optionSeen[OPTION_YUV_FORMAT] = TRUE;
+        }
+        break;
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * ReadParamFile
+ *
+ *	read the parameter file
+ *	function is ENCODE_FRAMES, COMBINE_GOPS, or COMBINE_FRAMES, and
+ *	    will slightly modify the procedure's behavior as to what it
+ *	    is looking for in the parameter file
+ *
+ * SIDE EFFECTS:    sets parameters accordingly, as well as machine info for
+ *		    parallel execution and input file names
+ *
+ *===========================================================================*/
+void
+ReadParamFile(const char *    const fileName, 
+              int             const function,
+              struct params * const paramP) {
+
+  FILE *fpointer;
+  char    buffer[256];
+  bool yuvUsed;
+  struct inputSource * inputSourceP;
+      /* Contents of INPUT section */
+  struct inputSource * frameInputSourceP;
+      /* Contents of FRAME_INPUT section */
+  struct inputSource * gopInputSourceP;
+      /* Contents of GOP_INPUT section */
+
+  fpointer = fopen(fileName, "r");
+  if (fpointer == NULL)
+      pm_error("Error:  Cannot open parameter file:  %s", fileName);
+
+  CreateInputSource(&inputSourceP);
+  CreateInputSource(&frameInputSourceP);
+  CreateInputSource(&gopInputSourceP);
+
+  /* should set defaults */
+  numMachines = 0;
+  sprintf(currentPath, ".");
+  sprintf(currentGOPPath, ".");
+  sprintf(currentFramePath, ".");
+  SetRemoteShell("rsh");
+  keepTempFiles = FALSE;
+  paramP->warnOverflow = FALSE;
+  paramP->warnUnderflow = FALSE;
+
+  initOptionSeen();
+
+  yuvUsed = FALSE;  /* initial value */
+
+  while (fgets(buffer, 256, fpointer) != NULL) {
+      const char * input;
+
+      removeTrailingWhite(buffer, &input);
+
+      if (strlen(input) == 0) {
+          /* Ignore blank line */
+      } else if (input[0] == '#') {
+          /* Ignore comment */
+      } else
+          processParamLine(input, fpointer, &yuvUsed,
+                           inputSourceP, frameInputSourceP, gopInputSourceP,
+                           paramP);
+              /* May read additional lines from file */
+
+      strfree(input);
+  }
+
+  fclose(fpointer);
+
+  setInputSource(function, &paramP->inputSourceP,
+                 inputSourceP, gopInputSourceP, frameInputSourceP);
+
+  verifyNoMissingOption(function);
+
+  /* error checking */
+
+  if (!paramP->inputSourceP->stdinUsed &&
+      paramP->inputSourceP->numInputFiles == 0)
+      pm_error("You have not specified any input.  See the "
+               "INPUT_DIR, INPUT, GOP_INPUT_DIR, GOP_INPUT, "
+               "FRAME_INPUT_DIR, and FRAME_INPUT options");
+
+  if (yuvUsed) {
+      if (!optionSeen[OPTION_YUV_SIZE])
+          pm_error("YUV format used but YUV_SIZE not given");
+      
+      if (!optionSeen[OPTION_YUV_FORMAT]) {
+          strcpy (yuvConversion, "EYUV");
+          pm_message("WARNING:  YUV format not specified; "
+                     "defaulting to Berkeley YUV (EYUV)");
+      }
+  }
+
+  if (paramP->inputSourceP->stdinUsed && optionSeen[OPTION_PARALLEL] )
+      pm_error("stdin reading for parallel execution not enabled yet.");
+
+  if (optionSeen[OPTION_PARALLEL] && !optionSeen[OPTION_YUV_SIZE])
+      pm_error("Specify SIZE WxH for parallel encoding");
+
+  if (optionSeen[OPTION_IO_CONVERT] != optionSeen[OPTION_SLAVE_CONVERT])
+      pm_error("Must have either both IO_SERVER_CONVERT and SLAVE_CONVERT "
+               "or neither");
+      
+  if (optionSeen[OPTION_DEFS_SPECIFICS] && !optionSeen[OPTION_SPECIFICS])
+      pm_error("Does not make sense to define Specifics file options, "
+               "but no specifics file!");
+
+  SetIOConvert(optionSeen[OPTION_IO_CONVERT]);
+
+  SetResize(optionSeen[OPTION_RESIZE]);
+
+  if (function == ENCODE_FRAMES) {
+      SetFCode();
+
+      if (psearchAlg == PSEARCH_TWOLEVEL)
+          SetPixelSearch("HALF");
+  }
+}
+
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
diff --git a/converter/ppm/ppmtompeg/parse_huff.pl b/converter/ppm/ppmtompeg/parse_huff.pl
new file mode 100644
index 00000000..1fc6466c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/parse_huff.pl
@@ -0,0 +1,198 @@
+# 
+# Copyright (c) 1993 The Regents of the University of California.
+# All rights reserved.
+# 
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose, without fee, and without written agreement is
+# hereby granted, provided that the above copyright notice and the following
+# two paragraphs appear in all copies of this software.
+# 
+# IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+# OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+# CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# 
+# THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+# ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+# PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+# 
+
+#  
+#  $Header: /n/picasso/users/dwallach/vid2/mpeg_encode/RCS/parse_huff.pl,v 1.3 1993/01/18 10:56:03 dwallach Exp $
+#  $Log: parse_huff.pl,v $
+# Revision 1.3  1993/01/18  10:56:03  dwallach
+# got RCS headers in huff.c and huff.h working
+#
+# Revision 1.3  1993/01/18  10:56:03  dwallach
+# got RCS headers in huff.c and huff.h working
+#
+# Revision 1.2  1993/01/18  10:17:29  dwallach
+# RCS headers installed, code indented uniformly
+#
+# Revision 1.2  1993/01/18  10:17:29  dwallach
+# RCS headers installed, code indented uniformly
+#
+#
+
+# this program's purpose in life is to parse the Huffman tables
+# and output C code which is awfully useful for translation
+
+# typical usage:
+# perl parse_huff.pl huffman.table
+# ---> generates huff.c and huff.h
+
+# source format:  # is a comment character
+# Otherwise, there are three tab-separated fields in a line:
+#    Run, Level, and VLC Code
+# Run and Level are integers, corresponding to the RLE coding.
+# The VLC code is what bits to generate, and is expected to be
+#    composed of 1, 0, space, and 's'.  Spaces are ignored, and
+#    s corresponds to the sign bit.  In the output of this program,
+#    We'll completely right-shift the data, with a 0 for the sign
+#    bit.  The encoder will make appropriate changes before outputing.
+
+
+open(HUFFC, "> huff.c") || die "Can't write huff.c: $!\n";
+open(HUFFH, "> huff.h") || die "Can't write huff.h: $!\n";
+
+sub encode {
+    local($run, $level, $vlc) = @_;
+    local($result) = 0;
+    local($tmp);
+    $vlc =~ s/\s//g;           # remove whitespace
+
+    local($bits) = length($vlc);
+
+    foreach (split(//, $vlc)) {
+	$_ = 0 if $_ eq 's';
+	$result = ($result << 1) + $_;
+    }
+
+    $tmp = sprintf("0x%x", $result);
+    eval "\$vlc$run[$level] = \$tmp";
+    eval "\$bits$run[$level] = \$bits";
+}
+
+while(<>) {
+    chop;
+    s/\s*#.*$//;       # remove comments
+    next if /^\s*$/;   # continue if the line is purely whitespace
+
+    ($run, $level, $vlc) = split(/\t/);
+    &encode($run, $level, $vlc);
+
+    #
+    # we have the +1's to insure the sizes of C arrays are correct
+    #
+    $maxlevel[$run] = $level+1 if $level >= $maxlevel[$run];
+    $maxlevel = $level+1 if $level >= $maxlevel;
+    $maxrun = $run+1 if $run >= $maxrun;
+}
+
+#
+# fix the entries that aren't defined
+#
+for($run = 0; $run < $maxrun; $run++) {
+    eval "\$vlc$run[0] = '0x0' if \$vlc$run[0] eq ''";
+    eval "\$bits$run[0] = '0' if \$bits$run[0] eq ''";
+}
+
+#
+# now, output some C code
+#
+
+printf HUFFC <<'EOF', $maxrun, join(", ", @maxlevel);
+/*
+ * Copyright (c) 1993 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/picasso/users/dwallach/vid2/mpeg_encode/RCS/parse_huff.pl,v 1.3 1993/01/18 10:56:03 dwallach Exp $
+ */
+
+/*  
+ *  THIS FILE IS MACHINE GENERATED!  DO NOT EDIT!
+ */
+#include "mtypes.h"
+#include "huff.h"
+
+int huff_maxlevel[%s] = { %s };
+
+EOF
+for($run = 0; $run < $maxrun; $run++) {
+    printf HUFFC <<EOF, eval "join(', ', \@vlc$run)", eval "join(', ', \@bits$run)";
+uint32 huff_table$run[$maxlevel[$run]] = { %s };
+int huff_bits$run[$maxlevel[$run]] = { %s };
+
+EOF
+}
+
+printf HUFFC "uint32 *huff_table[$maxrun] = { ";
+for($run = 0; $run < $maxrun; $run++) {
+    printf HUFFC "%shuff_table$run", ($run)?", ":"";
+}
+printf HUFFC " };\n";
+
+printf HUFFC "int *huff_bits[$maxrun] = { ";
+for($run = 0; $run < $maxrun; $run++) {
+    printf HUFFC "%shuff_bits$run", ($run)?", ":"";
+}
+printf HUFFC " };\n";
+close HUFFC;
+
+printf HUFFH <<'EOF', $maxrun, $maxlevel;
+/*
+ * Copyright (c) 1993 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/picasso/users/dwallach/vid2/mpeg_encode/RCS/parse_huff.pl,v 1.3 1993/01/18 10:56:03 dwallach Exp $
+ */
+
+/*  
+ *  THIS FILE IS MACHINE GENERATED!  DO NOT EDIT!
+ */
+#define HUFF_MAXRUN	%s
+#define HUFF_MAXLEVEL	%s
+
+extern int huff_maxlevel[];
+extern uint32 *huff_table[];
+extern int *huff_bits[];
+EOF
+
diff --git a/converter/ppm/ppmtompeg/pframe.c b/converter/ppm/ppmtompeg/pframe.c
new file mode 100644
index 00000000..e72fe5d6
--- /dev/null
+++ b/converter/ppm/ppmtompeg/pframe.c
@@ -0,0 +1,1072 @@
+/*===========================================================================*
+ * pframe.c                                  
+ *                                       
+ *  Procedures concerned with generation of P-frames             
+ *                                       
+ * EXPORTED PROCEDURES:                              
+ *  GenPFrame                                
+ *  ResetPFrameStats                             
+ *  ShowPFrameSummary                            
+ *  EstimateSecondsPerPFrame                         
+ *  ComputeHalfPixelData                             
+ *  SetPQScale                               
+ *  GetPQScale                               
+ *                                                                           
+ * NOTE:  when motion vectors are passed as arguments, they are passed as    
+ *        twice their value.  In other words, a motion vector of (3,4) will  
+ *        be passed as (6,8).  This allows half-pixel motion vectors to be   
+ *        passed as integers.  This is true throughout the program.          
+ *                                       
+ *===========================================================================*/
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include <assert.h>
+#include <sys/param.h>
+#include "pm.h"
+#include "pm_c_util.h"
+#include "all.h"
+#include "mtypes.h"
+#include "bitio.h"
+#include "frames.h"
+#include "motion_search.h"
+#include "prototypes.h"
+#include "block.h"
+#include "param.h"
+#include "mheaders.h"
+#include "fsize.h"
+#include "postdct.h"
+#include "mpeg.h"
+#include "parallel.h"
+#include "rate.h"
+#include "opts.h"
+#include "specifics.h"
+
+/*==================*
+ * STATIC VARIABLES *
+ *==================*/
+
+static int32    zeroDiff;
+static int      numPIBlocks = 0;
+static int      numPPBlocks = 0;
+static int      numPSkipped = 0;
+static int      numPIBits = 0;
+static int      numPPBits = 0;
+static int      numFrames = 0;
+static int      numFrameBits = 0;
+static int32    totalTime = 0;
+static int      qscaleP;
+static float    totalSNR = 0.0;
+static float    totalPSNR = 0.0;
+extern Block    **dct, **dctr, **dctb;
+extern dct_data_type   **dct_data;
+
+/*=====================*
+ * INTERNAL PROCEDURES *
+ *=====================*/
+
+static vector
+halfVector(vector const vector) {
+    struct vector half;
+
+    half.y = vector.y/2;
+    half.x = vector.x/2;
+
+    return half;
+}
+
+/*===========================================================================*
+ *
+ *  decide if (0,0) motion is better than the given motion vector
+ *
+ * RETURNS: TRUE if (0,0) is better, FALSE if (my,mx) is better
+ *
+ * SIDE EFFECTS:    none
+ *
+ * PRECONDITIONS:   The relevant block in 'current' is valid (it has not
+ *          been dct'd).  'zeroDiff' has already been computed
+ *          as the LumMotionError() with (0,0) motion
+ *
+ * NOTES:   This procedure follows the algorithm described on
+ *      page D-48 of the MPEG-1 specification
+ *
+ *===========================================================================*/
+static boolean
+ZeroMotionBetter(const LumBlock * const currentBlockP,
+                 MpegFrame *      const prev,
+                 int              const by,
+                 int              const bx,
+                 vector           const m) {
+
+    int bestDiff;
+    int CompareMode;
+
+    /* Junk needed to adapt for TUNEing */ 
+    CompareMode = SearchCompareMode;
+    SearchCompareMode = DEFAULT_SEARCH;
+    bestDiff = LumMotionError(currentBlockP, prev, by, bx, m, 0x7fffffff);
+    SearchCompareMode = CompareMode;
+
+    if ( zeroDiff < 256*3 ) {
+    if ( 2*bestDiff >= zeroDiff ) {
+        return TRUE;
+    }
+    } else {
+    if ( 11*bestDiff >= 10*zeroDiff ) {
+        return TRUE;
+    }
+    }
+    return FALSE;
+}
+
+
+/*===========================================================================*
+ *
+ *                USER-MODIFIABLE
+ *
+ * DoIntraCode
+ *
+ *  decide if intra coding is necessary
+ *
+ * RETURNS: TRUE if intra-block coding is better; FALSE if not
+ *
+ * SIDE EFFECTS:    none
+ *
+ * PRECONDITIONS:   The relevant block in 'current' is valid (it has not
+ *          been dct'd).
+ *
+ * NOTES:   This procedure follows the algorithm described on
+ *      page D-49 of the MPEG-1 specification
+ *
+ *===========================================================================*/
+static boolean
+DoIntraCode(const LumBlock * const currentBlockP,
+            MpegFrame *      const prev,
+            int              const by,
+            int              const bx,
+            vector           const motion) {
+
+    unsigned int y;
+    int32 sum = 0, vard = 0, varc = 0;
+    int32 currPixel, prevPixel;
+    LumBlock motionBlock;
+
+    ComputeMotionLumBlock(prev, by, bx, motion, &motionBlock);
+
+    for (y = 0; y < 16; ++y) {
+        unsigned int x;
+        for (x = 0; x < 16; ++x) {
+            currPixel = currentBlockP->l[y][x];
+            prevPixel = motionBlock.l[y][x];
+            
+            sum += currPixel;
+            varc += currPixel*currPixel;
+            
+            vard += SQR(currPixel - prevPixel);
+        }
+    }
+    
+    vard /= 256;     /* divide by 256; assumes mean is close to zero */
+    varc = (varc/256) - (sum/256) * (sum/256);
+
+    if (vard <= 64)
+        return FALSE;
+    else if (vard < varc)
+        return FALSE;
+    else
+        return TRUE;
+}
+
+
+
+/*===========================================================================*
+ *
+ *                USER-MODIFIABLE
+ *
+ * ZeroMotionSufficient
+ *
+ *  decide if zero motion is sufficient without DCT correction
+ *
+ * RETURNS: TRUE no DCT required; FALSE otherwise
+ *
+ * SIDE EFFECTS:    none
+ *
+ * PRECONDITIONS:   The relevant block in 'current' is raw YCC data
+ *
+ *===========================================================================*/
+static boolean
+ZeroMotionSufficient(const LumBlock * const currentBlockP,
+                     MpegFrame *      const prev,
+                     int              const by,
+                     int              const bx) {
+
+    LumBlock motionBlock;
+    int fy, fx;
+    unsigned int y;
+
+    fy = by * DCTSIZE;
+    fx = bx * DCTSIZE;
+    for (y = 0; y < 16; ++y) {
+        unsigned int x;
+        for (x = 0; x < 16; ++x) {
+            motionBlock.l[y][x] = prev->ref_y[fy+y][fx+x];
+        }
+    }
+
+    zeroDiff = LumBlockMAD(currentBlockP, &motionBlock, 0x7fffffff);
+
+    return (zeroDiff <= 256);
+}
+                 
+
+
+static void 
+computeCurrentBlock(MpegFrame * const current, 
+                    int         const y,
+                    int         const x,
+                    LumBlock *  const currentBlockP) {
+    int fy, fx;
+    int iy;
+
+    BLOCK_TO_FRAME_COORD(y, x, fy, fx);
+    for ( iy = 0; iy < 16; iy++ ) {
+        int ix;
+        for ( ix = 0; ix < 16; ix++ ) {
+            currentBlockP->l[iy][ix] = 
+                (int16)current->orig_y[fy+iy][fx+ix];
+        }
+    }
+}
+
+
+
+static void
+computeMotionVectors(bool             const specificsOn, 
+                     bool             const IntraPBAllowed,
+                     MpegFrame *      const current, 
+                     MpegFrame *      const prev,
+                     int              const mbAddress,
+                     BlockMV **       const infoP,
+                     int              const QScale, 
+                     const LumBlock * const currentBlockP,
+                     int              const y, 
+                     int              const x,
+                     bool *           const useMotionP, 
+                     vector *         const motionP) {
+
+    bool useCached;
+    BlockMV * info;
+
+    /* See if we have a cached answer */
+    if (specificsOn) {
+        SpecLookup(current->id, 2, mbAddress, &info, QScale);
+        if (info != (BlockMV*)NULL) 
+            useCached = TRUE;
+        else
+            useCached = FALSE;
+    } else
+        useCached = FALSE;
+
+    if (useCached) {
+        if (info->typ == TYP_SKIP) {
+            motionP->x = motionP->y = 0;
+            *useMotionP = TRUE;
+        } else {        /* assume P, since we're a P frame.... */
+            motionP->x = info->fx;
+            motionP->y = info->fy;
+            *useMotionP = TRUE;
+        }
+    } else {
+        /* see if we should use motion vectors, and if so, what those
+         * vectors should be
+         */
+        if (ZeroMotionSufficient(currentBlockP, prev, y, x)) {
+            motionP->x = 0;
+            motionP->y = 0;
+            *useMotionP = TRUE;
+        } else {
+            vector motion;
+            motion.y = motion.x = 0;  /* initial values */
+            PMotionSearch(currentBlockP, prev, y, x, &motion);
+            if (ZeroMotionBetter(currentBlockP, prev, y, x, motion)) {
+                motionP->y = 0;
+                motionP->x = 0;
+            } else
+                *motionP = motion;
+            if (IntraPBAllowed) 
+                *useMotionP = !DoIntraCode(currentBlockP, prev, y, x, motion);
+            else
+                *useMotionP = TRUE;
+        }
+    }
+    *infoP = info;
+}
+
+
+
+static void
+calculateForwardDcts(MpegFrame * const current, 
+                     int const y, int const x,
+                     Block ** const dct) {
+
+    /* calculate forward dct's */
+    if (collect_quant && (collect_quant_detailed & 1)) 
+        fprintf(collect_quant_fp, "l\n");
+
+    mp_fwd_dct_block2(current->y_blocks[y][x], dct[y][x]);
+    mp_fwd_dct_block2(current->y_blocks[y][x+1], dct[y][x+1]);
+    mp_fwd_dct_block2(current->y_blocks[y+1][x], dct[y+1][x]);
+    mp_fwd_dct_block2(current->y_blocks[y+1][x+1], dct[y+1][x+1]);
+
+    if (collect_quant && (collect_quant_detailed & 1)) 
+        fprintf(collect_quant_fp, "c\n");
+
+    mp_fwd_dct_block2(current->cb_blocks[y/2][x/2], dctb[y/2][x/2]);
+
+    mp_fwd_dct_block2(current->cr_blocks[y/2][x/2], dctr[y/2][x/2]);
+}
+
+
+
+static void
+computeMotionAndDct(int         const lastBlockY,
+                    int         const lastBlockX,
+                    bool        const specificsOn,
+                    bool        const IntraPBAllowed,
+                    MpegFrame * const current,
+                    MpegFrame * const prev,
+                    BlockMV **  const infoP,
+                    int         const QScale,
+                    int         const searchRangeP,
+                    Block **    const dct,
+                    int *       const numPBlocksP,
+                    int *       const numIBlocksP,
+                    int **      const pmvHistogram) {
+/*----------------------------------------------------------------------------
+   Loop through the frame finding motion/not and DCTing
+-----------------------------------------------------------------------------*/
+    int mbAddress;
+    int y;
+
+    mbAddress = 0;                        
+
+    for (y = 0; y < lastBlockY; y += 2) {
+        int x;
+        for (x = 0; x < lastBlockX; x += 2) {
+            LumBlock currentBlock;
+            vector motion;
+            bool useMotion;
+
+            computeCurrentBlock(current, y, x, &currentBlock);
+
+            computeMotionVectors(specificsOn,  IntraPBAllowed,
+                                 current, prev, mbAddress, infoP,
+                                 QScale, &currentBlock, y, x,
+                                 &useMotion, &motion);
+
+            dct_data[y][x].useMotion = useMotion;
+
+            if (useMotion) {
+                int pattern;
+
+                (*numPBlocksP)++;
+
+                pattern = 63;
+                ComputeDiffDCTs(current, prev, y, x, motion, &pattern);
+
+                assert(motion.x + searchRangeP + 1 >= 0);
+                assert(motion.y + searchRangeP + 1 >= 0);
+
+                if (computeMVHist) {
+                    assert(motion.x + searchRangeP + 1 <= 2*searchRangeP + 2);
+                    assert(motion.y + searchRangeP + 1 <= 2*searchRangeP + 2);
+                    ++pmvHistogram[motion.x + searchRangeP + 1]
+                        [motion.y + searchRangeP + 1];
+                }
+                /* Save specs for next loops */
+                dct_data[y][x].pattern  = pattern;
+                dct_data[y][x].fmotionX = motion.x;
+                dct_data[y][x].fmotionY = motion.y;
+            } else {
+                /* output I-block inside a P-frame */
+                ++*numIBlocksP;
+
+                calculateForwardDcts(current, y, x, dct);
+            }
+            ++mbAddress;
+        }
+    }
+}
+
+
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+
+/*===========================================================================*
+ *
+ * GenPFrame
+ *
+ *  generate a P-frame from previous frame, adding the result to the
+ *  given bit bucket
+ *
+ * RETURNS: frame appended to bb
+ *
+ *===========================================================================*/
+void
+GenPFrame(BitBucket * const bb,
+          MpegFrame * const current, 
+          MpegFrame * const prev) {
+
+    extern int **pmvHistogram;
+    FlatBlock fba[6], fb[6];
+    Block   dec[6];
+    int32 y_dc_pred, cr_dc_pred, cb_dc_pred;
+    int x, y;
+    vector motion;
+    vector oldMotion;
+    int offsetX, offsetY;
+    vector motionRem;
+    vector motionQuot;
+    int pattern;
+    int mbAddrInc = 1;
+    int numIBlocks = 0;
+    int numPBlocks = 0;
+    int numSkipped = 0;
+    int numIBits = 0;
+    int numPBits = 0;
+    int totalBits;
+    int totalFrameBits;
+    int32    startTime, endTime;
+    int lastBlockX, lastBlockY;
+    int lastX, lastY;
+    int mbAddress;
+    int slicePos;
+    register int index;
+    float   snr[3], psnr[3];
+    int QScale;
+    BlockMV *info;
+    int bitstreamMode, newQScale;
+    int rc_blockStart = 0;
+    boolean overflowChange = FALSE;
+    int     overflowValue  = 0;
+
+
+    oldMotion.x = oldMotion.y = 0;
+
+    if (collect_quant) {fprintf(collect_quant_fp, "# P\n");}
+    if (dct==NULL) AllocDctBlocks();
+    numFrames++;
+    totalFrameBits = bb->cumulativeBits;
+    startTime = time_elapsed();
+
+    DBG_PRINT(("Generating pframe\n"));
+
+    QScale = GetPQScale();
+    /*   bit allocation for rate control purposes */
+    bitstreamMode = getRateMode();
+    if (bitstreamMode == FIXED_RATE) {
+        targetRateControl(current);
+    }
+ 
+    Mhead_GenPictureHeader(bb, P_FRAME, current->id, fCodeP);
+    /* Check for Qscale change */  
+    if (specificsOn) {
+        /* Set a Qscale for this frame? */
+        newQScale = 
+            SpecLookup(current->id, 0, 0 /* junk */, &info /*junk*/, QScale);
+        if (newQScale != -1) {
+            QScale = newQScale;
+        }
+        /* Set for slice? */
+        newQScale = SpecLookup(current->id, 1, 1, &info /*junk*/, QScale);
+        if (newQScale != -1) {
+            QScale = newQScale;
+        }
+    }
+
+    DBG_PRINT(("Slice Header\n"));
+    Mhead_GenSliceHeader(bb, 1, QScale, NULL, 0);
+
+    if ( referenceFrame == DECODED_FRAME ) {
+        Frame_AllocDecoded(current, TRUE);
+    } else if ( printSNR ) {
+        Frame_AllocDecoded(current, FALSE);
+    }
+
+    /* don't do dct on blocks yet */
+    Frame_AllocBlocks(current);
+    BlockifyFrame(current);
+
+    /* for I-blocks */
+    y_dc_pred = cr_dc_pred = cb_dc_pred = 128;
+
+    totalBits = bb->cumulativeBits;
+
+    if ( (! pixelFullSearch) && (! prev->halfComputed) ) {
+        ComputeHalfPixelData(prev);
+    }
+
+    lastBlockX = Fsize_x>>3;
+    lastBlockY = Fsize_y>>3;
+    lastX = lastBlockX-2;
+    lastY = lastBlockY-2;
+
+    computeMotionAndDct(lastBlockY, lastBlockX,
+                        specificsOn, IntraPBAllowed, current, prev,
+                        &info, QScale, searchRangeP, dct, 
+                        &numPBlocks, &numIBlocks, pmvHistogram);
+    
+    mbAddress = 0;
+    for (y = 0; y < lastBlockY; y += 2) {
+        for (x = 0; x < lastBlockX; x += 2) {
+            slicePos = (mbAddress % blocksPerSlice);
+
+            if ( (slicePos == 0) && (mbAddress != 0) ) {
+                if (specificsOn) {
+                    /* Make sure no slice Qscale change */
+                    newQScale = 
+                        SpecLookup(current->id, 1, mbAddress/blocksPerSlice,
+                                   &info /*junk*/, QScale);
+                    if (newQScale != -1) QScale = newQScale;
+                }
+
+                Mhead_GenSliceEnder(bb);
+                Mhead_GenSliceHeader(bb, 1+(y>>1), QScale, NULL, 0);
+
+                /* reset everything */
+                oldMotion.x = oldMotion.y = 0;
+                y_dc_pred = cr_dc_pred = cb_dc_pred = 128;
+
+                mbAddrInc = 1+(x>>1);
+            }
+        
+            /*  Determine if new Qscale needed for Rate Control purposes  */
+            if (bitstreamMode == FIXED_RATE) {
+                rc_blockStart =  bb->cumulativeBits;
+                newQScale = needQScaleChange(qscaleP,
+                                             current->y_blocks[y][x],
+                                             current->y_blocks[y][x+1],
+                                             current->y_blocks[y+1][x],
+                                             current->y_blocks[y+1][x+1]);
+                if (newQScale > 0) {
+                    QScale = newQScale;
+                }
+            }
+        
+            /* Check for Qscale change */
+            if (specificsOn) {
+                newQScale = 
+                    SpecLookup(current->id, 2, mbAddress, &info, QScale);
+                if (newQScale != -1) {
+                    QScale = newQScale;
+                }
+            }
+
+            if (! dct_data[y][x].useMotion) {
+                GEN_I_BLOCK(P_FRAME, current, bb, mbAddrInc, QScale);
+                mbAddrInc = 1;
+
+                numIBits += (bb->cumulativeBits-totalBits);
+                totalBits = bb->cumulativeBits;
+
+                /* reset because intra-coded */
+                oldMotion.x = oldMotion.y = 0;
+
+                if ( decodeRefFrames ) {
+                    /* need to decode block we just encoded */
+                    Mpost_UnQuantZigBlock(fb[0], dec[0], QScale, TRUE);
+                    Mpost_UnQuantZigBlock(fb[1], dec[1], QScale, TRUE);
+                    Mpost_UnQuantZigBlock(fb[2], dec[2], QScale, TRUE);
+                    Mpost_UnQuantZigBlock(fb[3], dec[3], QScale, TRUE);
+                    Mpost_UnQuantZigBlock(fb[4], dec[4], QScale, TRUE);
+                    Mpost_UnQuantZigBlock(fb[5], dec[5], QScale, TRUE);
+
+                    /* now, reverse the DCT transform */
+                    for ( index = 0; index < 6; index++ ) {
+                        mpeg_jrevdct((int16 *)dec[index]);
+                    }
+
+                    /* now, unblockify */
+                    BlockToData(current->decoded_y, dec[0], y, x);
+                    BlockToData(current->decoded_y, dec[1], y, x+1);
+                    BlockToData(current->decoded_y, dec[2], y+1, x);
+                    BlockToData(current->decoded_y, dec[3], y+1, x+1);
+                    BlockToData(current->decoded_cb, dec[4], y>>1, x>>1);
+                    BlockToData(current->decoded_cr, dec[5], y>>1, x>>1);
+                }
+            } else {
+                int fCode = fCodeP;
+
+                /* reset because non-intra-coded */
+                y_dc_pred = cr_dc_pred = cb_dc_pred = 128;
+
+                pattern = dct_data[y][x].pattern;
+                motion.x = dct_data[y][x].fmotionX;
+                motion.y = dct_data[y][x].fmotionY;
+
+#ifdef BLEAH
+                ComputeAndPrintPframeMAD(currentBlock, prev, y, x, motion, 
+                                         mbAddress);
+#endif
+
+                if ( pixelFullSearch ) { /* should be even */
+                    motion.y /= 2;
+                    motion.x /= 2;
+                }
+
+                /* transform the motion vector into the appropriate values */
+                offsetX = motion.x - oldMotion.x;
+                offsetY = motion.y - oldMotion.y;
+                /*  if ((offsetX+(8*x)) >= (Fsize_x-8)) log(10.0); */
+                encodeMotionVector(offsetX, offsetY, &motionQuot, &motionRem,
+                                   FORW_F, fCode);
+
+#ifdef BLEAH
+                if ( (motion.x != 0) || (motion.y != 0) ) {
+                    fprintf(stdout, "FRAME (y, x)  %d, %d (block %d)\n", 
+                            y, x, mbAddress);
+                    fprintf(stdout, "motion.x = %d, motion.y = %d\n", 
+                            motion.x, motion.y);
+                    fprintf(stdout, 
+                            "    mxq, mxr = %d, %d    myq, myr = %d, %d\n",
+                            motionQuot.x, motionRem.x,
+                            motionQuot.y, motionRem.y);
+                }
+#endif
+
+                oldMotion = motion;
+
+                if ( pixelFullSearch ) { /* reset for use with PMotionSearch */
+                    motion.y *= 2;
+                    motion.x *= 2;
+                }
+            calc_blocks:
+                /* create flat blocks and update pattern if necessary */
+                /* Note DoQuant references QScale, overflowChange,
+                   overflowValue, pattern, and the calc_blocks label */
+                DoQuant(0x20, dct[y][x], fba[0]);
+                DoQuant(0x10, dct[y][x+1], fba[1]);
+                DoQuant(0x08, dct[y+1][x], fba[2]);
+                DoQuant(0x04, dct[y+1][x+1], fba[3]);
+                DoQuant(0x02, dctb[y/2][x/2], fba[4]);
+                DoQuant(0x01, dctr[y/2][x/2], fba[5]);
+
+                if ( decodeRefFrames) {
+                    for ( index = 0; index < 6; index++ ) {
+                        if ( pattern & (1 << (5-index))) {
+                            Mpost_UnQuantZigBlock(fba[index], dec[index], 
+                                                  QScale, FALSE);
+                            mpeg_jrevdct((int16 *)dec[index]);
+                        } else {
+                            memset((char *)dec[index], 0, sizeof(Block));
+                        }
+                    }
+
+                    /* now add the motion block */
+                    AddMotionBlock(dec[0], prev->decoded_y, y, x, motion);
+                    AddMotionBlock(dec[1], prev->decoded_y, y, x+1, motion);
+                    AddMotionBlock(dec[2], prev->decoded_y, y+1, x, motion);
+                    AddMotionBlock(dec[3], prev->decoded_y, y+1, x+1, motion);
+                    AddMotionBlock(dec[4], prev->decoded_cb, y/2, x/2,
+                                   halfVector(motion));
+                    AddMotionBlock(dec[5], prev->decoded_cr, y/2, x/2, 
+                                   halfVector(motion));
+
+                    /* now, unblockify */
+                    BlockToData(current->decoded_y, dec[0], y, x);
+                    BlockToData(current->decoded_y, dec[1], y, x+1);
+                    BlockToData(current->decoded_y, dec[2], y+1, x);
+                    BlockToData(current->decoded_y, dec[3], y+1, x+1);
+                    BlockToData(current->decoded_cb, dec[4], y/2, x/2);
+                    BlockToData(current->decoded_cr, dec[5], y/2, x/2);
+                } 
+
+                if ((motion.x == 0) && (motion.y == 0)) {
+                    if ( pattern == 0 ) {
+                        /* can only skip if:
+                         *     1)  not the last block in frame
+                         *     2)  not the last block in slice
+                         *     3)  not the first block in slice
+                         */
+
+                        if ( ((y < lastY) || (x < lastX)) &&
+                             (slicePos+1 != blocksPerSlice) &&
+                             (slicePos != 0) ) {
+                            mbAddrInc++;    /* skipped macroblock */
+                            numSkipped++;
+                            numPBlocks--;
+                        } else {        /* first/last macroblock */
+                            Mhead_GenMBHeader(bb, 2 /* pict_code_type */, 
+                                              mbAddrInc /* addr_incr */,
+                                              QScale /* q_scale */,
+                                              fCode /* forw_f_code */, 
+                                              1 /* back_f_code */,
+                                              motionRem.x /* horiz_forw_r */, 
+                                              motionRem.y /* vert_forw_r */,
+                                              0 /* horiz_back_r */, 
+                                              0 /* vert_back_r */,
+                                              1 /* motion_forw */, 
+                                              motionQuot.x /* m_horiz_forw */,
+                                              motionQuot.y /* m_vert_forw */, 
+                                              0 /* motion_back */,
+                                              0 /* m_horiz_back */, 
+                                              0 /* m_vert_back */,
+                                              0 /* mb_pattern */, 
+                                              0 /* mb_intra */);
+                            mbAddrInc = 1;
+                        }
+                    } else {
+                        DBG_PRINT(("MB Header(%d,%d)\n", x, y));
+                        Mhead_GenMBHeader(bb, 2 /* pict_code_type */, 
+                                          mbAddrInc /* addr_incr */,
+                                          QScale /* q_scale */,
+                                          fCode /* forw_f_code */, 
+                                          1 /* back_f_code */,
+                                          0 /* horiz_forw_r */, 
+                                          0 /* vert_forw_r */,
+                                          0 /* horiz_back_r */, 
+                                          0 /* vert_back_r */,
+                                          0 /* motion_forw */, 
+                                          0 /* m_horiz_forw */,
+                                          0 /* m_vert_forw */, 
+                                          0 /* motion_back */,
+                                          0 /* m_horiz_back */, 
+                                          0 /* m_vert_back */,
+                                          pattern /* mb_pattern */, 
+                                          0 /* mb_intra */);
+                        mbAddrInc = 1;
+                    }
+                } else {
+                    /*      DBG_PRINT(("MB Header(%d,%d)\n", x, y));  */
+          
+                    Mhead_GenMBHeader(bb, 2 /* pict_code_type */, 
+                                      mbAddrInc /* addr_incr */,
+                                      QScale /* q_scale */,
+                                      fCode /* forw_f_code */, 
+                                      1 /* back_f_code */,
+                                      motionRem.x /* horiz_forw_r */, 
+                                      motionRem.y /* vert_forw_r */,
+                                      0 /* horiz_back_r */, 
+                                      0 /* vert_back_r */,
+                                      1 /* motion_forw */, 
+                                      motionQuot.x /* m_horiz_forw */,
+                                      motionQuot.y /* m_vert_forw */, 
+                                      0 /* motion_back */,
+                                      0 /* m_horiz_back */, 
+                                      0 /* m_vert_back */,
+                                      pattern /* mb_pattern */, 
+                                      0 /* mb_intra */);
+                    mbAddrInc = 1;
+                }
+
+                /* now output the difference */
+                {
+                    unsigned int x;
+                    for (x = 0; x < 6; ++x) {
+                        if (GET_ITH_BIT(pattern, 5-x))
+                            Mpost_RLEHuffPBlock(fba[x], bb);
+                    }
+                }
+                numPBits += (bb->cumulativeBits-totalBits);
+                totalBits = bb->cumulativeBits;
+            }
+
+            if (overflowChange) {
+                /* undo an overflow-caused Qscale change */
+                overflowChange = FALSE;
+                QScale -= overflowValue;
+                overflowValue = 0;
+            }
+
+            mbAddress++;
+            /*   Rate Control  */
+            if (bitstreamMode == FIXED_RATE) {
+                incMacroBlockBits( bb->cumulativeBits- rc_blockStart);
+                rc_blockStart = bb->cumulativeBits;
+                MB_RateOut(TYPE_PFRAME);
+            }
+        }
+    }
+
+    if ( printSNR ) {
+        BlockComputeSNR(current,snr,psnr);
+        totalSNR += snr[0];
+        totalPSNR += psnr[0];
+    }
+
+#ifdef BLEAHBLEAH
+    {
+        FILE *filePtr;
+
+        filePtr = fopen("PFRAME.yuv", "wb");
+
+        for ( y = 0; y < Fsize_y; y++ )
+        {
+            for ( x = 0; x < Fsize_x; x++ )
+                fprintf(filePtr, "%d ", current->decoded_y[y][x]);
+            fprintf(filePtr, "\n");
+        }
+
+        fclose(filePtr);
+    }
+#endif
+
+    Mhead_GenSliceEnder(bb);
+    /*   Rate Control */
+    if (bitstreamMode == FIXED_RATE) {
+        updateRateControl(TYPE_PFRAME);
+    }
+
+    /* UPDATE STATISTICS */
+    endTime = time_elapsed();
+    totalTime += (endTime-startTime);
+
+    if ( showBitRatePerFrame ) {
+        /* ASSUMES 30 FRAMES PER SECOND */
+        fprintf(bitRateFile, "%5d\t%8d\n", current->id,
+                30*(bb->cumulativeBits-totalFrameBits));
+    }
+
+    if ( frameSummary && (! realQuiet) ) {
+        fprintf(stdout, "FRAME %d (P):  I BLOCKS:  %d;  "
+                "P BLOCKS:  %d   SKIPPED:  %d  (%ld seconds)\n",
+                current->id, numIBlocks, numPBlocks, numSkipped, 
+                (long)(endTime-startTime)/TIME_RATE);
+        if ( printSNR ) {
+            fprintf(stdout, "FRAME %d:  SNR:  %.1f\t%.1f\t%.1f\t"
+                    "PSNR:  %.1f\t%.1f\t%.1f\n",
+                    current->id, snr[0], snr[1], snr[2],
+                    psnr[0], psnr[1], psnr[2]);
+        }
+    }
+
+    numFrameBits += (bb->cumulativeBits-totalFrameBits);
+    numPIBlocks += numIBlocks;
+    numPPBlocks += numPBlocks;
+    numPSkipped += numSkipped;
+    numPIBits += numIBits;
+    numPPBits += numPBits;
+}
+
+
+/*===========================================================================*
+ *
+ * ResetPFrameStats
+ *
+ *  reset the P-frame statistics
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+  ResetPFrameStats()
+{
+  numPIBlocks = 0;
+  numPPBlocks = 0;
+  numPSkipped = 0;
+  numPIBits = 0;
+  numPPBits = 0;
+  numFrames = 0;
+  numFrameBits = 0;
+  totalTime = 0;
+}
+
+
+/*===========================================================================*
+ *
+ * SetPQScale
+ *
+ *  set the P-frame Q-scale
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    qscaleP
+ *
+ *===========================================================================*/
+void
+  SetPQScale(qP)
+int qP;
+{
+  qscaleP = qP;
+}
+
+
+/*===========================================================================*
+ *
+ * GetPQScale
+ *
+ *  return the P-frame Q-scale
+ *
+ * RETURNS: the P-frame Q-scale
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+int
+  GetPQScale()
+{
+  return qscaleP;
+}
+
+
+float
+PFrameTotalTime(void) {
+    return (float)totalTime/(float)TIME_RATE;
+}
+
+
+
+void
+ShowPFrameSummary(unsigned int const inputFrameBits, 
+                  unsigned int const totalBits, 
+                  FILE *       const fpointer) {
+
+    if (numFrames > 0) {
+
+        fprintf(fpointer, "-------------------------\n");
+        fprintf(fpointer, "*****P FRAME SUMMARY*****\n");
+        fprintf(fpointer, "-------------------------\n");
+
+        if ( numPIBlocks != 0 ) {
+            fprintf(fpointer, "  I Blocks:  %5d     (%6d bits)     (%5d bpb)\n",
+                    numPIBlocks, numPIBits, numPIBits/numPIBlocks);
+        } else {
+            fprintf(fpointer, "  I Blocks:  %5d\n", 0);
+        }
+
+        if ( numPPBlocks != 0 ) {
+            fprintf(fpointer, "  P Blocks:  %5d     (%6d bits)     (%5d bpb)\n",
+                    numPPBlocks, numPPBits, numPPBits/numPPBlocks);
+        } else {
+            fprintf(fpointer, "  P Blocks:  %5d\n", 0);
+        }
+
+        fprintf(fpointer, "  Skipped:   %5d\n", numPSkipped);
+
+        fprintf(fpointer, "  Frames:    %5d     (%6d bits)     (%5d bpf)     (%2.1f%% of total)\n",
+                numFrames, numFrameBits, numFrameBits/numFrames,
+                100.0*(float)numFrameBits/(float)totalBits);
+        fprintf(fpointer, "  Compression:  %3d:1     (%9.4f bpp)\n",
+                numFrames*inputFrameBits/numFrameBits,
+                24.0*(float)numFrameBits/(float)(numFrames*inputFrameBits));
+        if ( printSNR )
+            fprintf(fpointer, "  Avg Y SNR/PSNR:  %.1f     %.1f\n",
+                    totalSNR/(float)numFrames, totalPSNR/(float)numFrames);
+        if ( totalTime == 0 ) {
+            fprintf(fpointer, "  Seconds:  NONE\n");
+        } else {
+            fprintf(fpointer, "  Seconds:  %9ld     (%9.4f fps)  (%9ld pps)  (%9ld mps)\n",
+                    (long)(totalTime/TIME_RATE),
+                    (float)((float)(TIME_RATE*numFrames)/(float)totalTime),
+                    (long)((float)TIME_RATE*(float)numFrames*(float)inputFrameBits/(24.0*(float)totalTime)),
+                    (long)((float)TIME_RATE*(float)numFrames*(float)inputFrameBits/(256.0*24.0*(float)totalTime)));
+        }
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * EstimateSecondsPerPFrame
+ *
+ *  compute an estimate of the number of seconds required per P-frame
+ *
+ * RETURNS: the estimate, in seconds
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+float
+  EstimateSecondsPerPFrame()
+{
+  if ( numFrames == 0 ) {
+    return 10.0;
+  } else {
+    return (float)totalTime/((float)TIME_RATE*(float)numFrames);
+  }
+}
+
+
+/*===========================================================================*
+ *
+ * ComputeHalfPixelData
+ *
+ *  compute all half-pixel data required for half-pixel motion vector
+ *  search (luminance only)
+ *
+ * RETURNS: frame->halfX, ->halfY, and ->halfBoth modified
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+  ComputeHalfPixelData(frame)
+MpegFrame *frame;
+{
+  register int x, y;
+
+  /* we add 1 before dividing by 2 because .5 is supposed to be rounded up
+   * (see MPEG-1, page D-31)
+   */
+
+  if ( frame->halfX == NULL ) { /* need to allocate memory */
+    Frame_AllocHalf(frame);
+  }
+
+  /* compute halfX */
+  for ( y = 0; y < Fsize_y; y++ ) {
+    for ( x = 0; x < Fsize_x-1; x++ ) {
+      frame->halfX[y][x] = (frame->ref_y[y][x]+
+                frame->ref_y[y][x+1]+1)>>1;
+    }
+  }
+
+  /* compute halfY */
+  for ( y = 0; y < Fsize_y-1; y++ ) {
+    for ( x = 0; x < Fsize_x; x++ ) {
+      frame->halfY[y][x] = (frame->ref_y[y][x]+
+                frame->ref_y[y+1][x]+1)>>1;
+    }
+  }
+
+  /* compute halfBoth */
+  for ( y = 0; y < Fsize_y-1; y++ ) {
+    for ( x = 0; x < Fsize_x-1; x++ ) {
+      frame->halfBoth[y][x] = (frame->ref_y[y][x]+
+                   frame->ref_y[y][x+1]+
+                   frame->ref_y[y+1][x]+
+                   frame->ref_y[y+1][x+1]+2)>>2;
+    }
+  }
+
+  frame->halfComputed = TRUE;
+}
+
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
diff --git a/converter/ppm/ppmtompeg/postdct.c b/converter/ppm/ppmtompeg/postdct.c
new file mode 100644
index 00000000..56a23de9
--- /dev/null
+++ b/converter/ppm/ppmtompeg/postdct.c
@@ -0,0 +1,626 @@
+/*===========================================================================*
+ * postdct.c								     *
+ *									     *
+ *	Procedures concerned with MPEG post-DCT processing:		     *
+ *	    quantization and RLE Huffman encoding			     *
+ *									     *
+ * EXPORTED PROCEDURES:							     *
+ *	Mpost_QuantZigBlock						     *
+ *	Mpost_RLEHuffIBlock						     *
+ *	Mpost_RLEHuffPBlock						     *
+ *	Mpost_UnQuantZigBlock						     *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*
+ *  $Header: /n/picasso/project/mm/mpeg/mpeg_dist/mpeg_encode/RCS/postdct.c,v 1.12 1995/06/21 18:26:39 smoot Exp $
+ *  $Log: postdct.c,v $
+ * Revision 1.12  1995/06/21  18:26:39  smoot
+ * added length estimator for P-blocks
+ *
+ * Revision 1.11  1995/04/23  23:22:59  eyhung
+ * nothing changed
+ *
+ * Revision 1.10  1995/04/14  23:10:46  smoot
+ * Added overflow detection to MPOST_DCT so it will adjust Qscales (above)
+ *
+ * Revision 1.9  1995/02/15  23:15:32  smoot
+ * killed useless asserts
+ *
+ * Revision 1.8  1995/02/01  21:48:41  smoot
+ * assure out is set properly, short circuit 0 revquant
+ *
+ * Revision 1.7  1995/01/30  19:56:37  smoot
+ * Killed a <0 shift
+ *
+ * Revision 1.6  1995/01/25  23:07:33  smoot
+ * Better DBG_PRINTs, multiply/divide instead of shifts
+ *
+ * Revision 1.5  1995/01/19  23:09:10  eyhung
+ * Changed copyrights
+ *
+ * Revision 1.4  1995/01/16  08:17:08  eyhung
+ * added realQuiet
+ *
+ * Revision 1.3  1994/11/12  02:11:58  keving
+ * nothing
+ *
+ * Revision 1.2  1994/03/15  00:27:11  keving
+ * nothing
+ *
+ * Revision 1.2  1994/03/15  00:27:11  keving
+ * nothing
+ *
+ * Revision 1.1  1993/12/22  19:19:01  keving
+ * nothing
+ *
+ * Revision 1.11  1993/07/22  22:23:43  keving
+ * nothing
+ *
+ * Revision 1.10  1993/06/30  20:06:09  keving
+ * nothing
+ *
+ * Revision 1.9  1993/06/03  21:08:08  keving
+ * nothing
+ *
+ * Revision 1.8  1993/02/24  18:57:19  keving
+ * nothing
+ *
+ * Revision 1.7  1993/02/23  22:58:36  keving
+ * nothing
+ *
+ * Revision 1.6  1993/02/23  22:54:56  keving
+ * nothing
+ *
+ * Revision 1.5  1993/02/17  23:18:20  dwallach
+ * checkin prior to keving's joining the project
+ *
+ * Revision 1.4  1993/01/18  10:20:02  dwallach
+ * *** empty log message ***
+ *
+ * Revision 1.3  1993/01/18  10:17:29  dwallach
+ * RCS headers installed, code indented uniformly
+ *
+ * Revision 1.3  1993/01/18  10:17:29  dwallach
+ * RCS headers installed, code indented uniformly
+ *
+ */
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include <assert.h>
+#include "all.h"
+#include "mtypes.h"
+#include "bitio.h"
+#include "huff.h"
+#include "postdct.h"
+#include "opts.h"
+
+/*==================*
+ * STATIC VARIABLES *
+ *==================*/
+
+/* ZAG[i] is the natural-order position of the i'th element of zigzag order. */
+int ZAG[] = {
+    0, 1, 8, 16, 9, 2, 3, 10,
+    17, 24, 32, 25, 18, 11, 4, 5,
+    12, 19, 26, 33, 40, 48, 41, 34,
+    27, 20, 13, 6, 7, 14, 21, 28,
+    35, 42, 49, 56, 57, 50, 43, 36,
+    29, 22, 15, 23, 30, 37, 44, 51,
+    58, 59, 52, 45, 38, 31, 39, 46,
+    53, 60, 61, 54, 47, 55, 62, 63
+};
+
+/*
+ * possible optimization: reorder the qtable in the correct zigzag order, to
+ * reduce the number of necessary lookups
+ *
+ * this table comes from the MPEG draft, p. D-16, Fig. 2-D.15.
+ */
+int32 qtable[] = {
+    8, 16, 19, 22, 26, 27, 29, 34,
+    16, 16, 22, 24, 27, 29, 34, 37,
+    19, 22, 26, 27, 29, 34, 34, 38,
+    22, 22, 26, 27, 29, 34, 37, 40,
+    22, 26, 27, 29, 32, 35, 40, 48,
+    26, 27, 29, 32, 35, 40, 48, 58,
+    26, 27, 29, 34, 38, 46, 56, 69,
+    27, 29, 35, 38, 46, 56, 69, 83
+};
+
+int32 niqtable[] = {
+     16, 16, 16, 16, 16, 16, 16, 16,
+     16, 16, 16, 16, 16, 16, 16, 16,
+     16, 16, 16, 16, 16, 16, 16, 16,
+     16, 16, 16, 16, 16, 16, 16, 16,
+     16, 16, 16, 16, 16, 16, 16, 16,
+     16, 16, 16, 16, 16, 16, 16, 16,
+     16, 16, 16, 16, 16, 16, 16, 16,
+     16, 16, 16, 16, 16, 16, 16, 16
+};
+
+int32	*customQtable = NULL;
+int32	*customNIQtable = NULL;
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+extern boolean realQuiet;
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+
+/*===========================================================================*
+ *
+ * Mpost_UnQuantZigBlock
+ *
+ *	unquantize and zig-zag (decode) a single block
+ *	see section 2.4.4.1 of MPEG standard
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Mpost_UnQuantZigBlock(in, out, qscale, iblock)
+    FlatBlock in;
+    Block out;
+    int qscale;
+    boolean iblock;
+{
+    register int index;
+    int	    start;
+    int	    position;
+    register int	    qentry;
+    int	    level, coeff;
+    
+    if ( iblock ) {
+	/* qtable[0] must be 8 */
+	out[0][0] = (int16)(in[0] * 8);
+
+	/* don't need to do anything fancy here, because we saved orig
+	    value, not encoded dc value */
+	start = 1;
+    } else {
+	start = 0;
+    }
+
+    for ( index = start;  index < DCTSIZE_SQ;  index++ ) {
+	position = ZAG[index];
+	level = in[index];
+
+	if (level == 0) {
+	  ((int16 *)out)[position] = 0;
+	  continue;
+	}
+
+
+	if ( iblock ) {
+	    qentry = qtable[position] * qscale;
+	    coeff = (level*qentry)/8;
+	    if ( (coeff & 1) == 0 ) {
+		if ( coeff < 0 ) {
+		    coeff++;
+		} else if ( coeff > 0 ) {
+		    coeff--;
+		}
+	    }
+	} else {
+	    qentry = niqtable[position] * qscale;
+	    if ( level == 0 ) {
+		coeff = 0;
+	    } else if ( level < 0 ) {
+		coeff = (((2*level)-1)*qentry) / 16;
+		if ( (coeff & 1) == 0 ) {
+		    coeff++;
+		}
+	    } else {
+		coeff = (((2*level)+1)*qentry) >> 4;
+		if ( (coeff & 1) == 0 ) {
+		    coeff--;
+		}
+	    }
+
+	    if ( coeff > 2047 ) {
+		coeff = 2047;
+	    } else if ( coeff < -2048 ) {
+		coeff = -2048;
+	    }
+        }
+
+	((int16 *)out)[position] = coeff;
+    }
+}
+
+
+/*===========================================================================*
+ *
+ * Mpost_QuantZigBlock
+ *
+ *	quantize and zigzags a block
+ *
+ * RETURNS:	MPOST_OVERFLOW if a generated value is outside |255|
+ *              MPOST_ZERO     if all coeffs are zero
+ *              MPOST_NON_ZERO otherwisw
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+int
+Mpost_QuantZigBlock(in, out, qscale, iblock)
+    Block in;
+    FlatBlock out;
+    register int qscale;
+    int iblock;
+{
+  register int i;
+  register int16 temp;
+  register int qentry;
+  register int position;
+  boolean nonZero = FALSE;
+  boolean overflow = FALSE;
+  
+  DBG_PRINT(("Mpost_QuantZigBlock...\n"));
+  if (iblock) {
+    /*
+     * the DC coefficient is handled specially -- it's not
+     * sensitive to qscale, but everything else is
+     */
+    temp = ((int16 *) in)[ZAG[0]];
+    qentry = qtable[ZAG[0]];
+    
+    if (temp < 0) {
+      temp = -temp;
+      temp += (qentry >> 1);
+      temp /= qentry;
+      temp = -temp;
+    } else {
+      temp += (qentry >> 1);
+      temp /= qentry;
+    }
+    if ( temp != 0 ) {
+      nonZero = TRUE;
+    }
+    out[0] = temp;
+    
+    for (i = 1; i < DCTSIZE_SQ; i++) {
+      position = ZAG[i];
+      temp = ((int16 *) in)[position];
+      qentry = qtable[position] * qscale;
+      
+      /* see 1993 MPEG doc, section D.6.3.4 */
+      if (temp < 0) {
+	temp = -temp;
+	temp = (temp << 3);	/* temp > 0 */
+	temp += (qentry >> 1);
+	temp /= qentry;
+	temp = -temp;
+      } else {
+	temp = (temp << 3);	/* temp > 0 */
+	temp += (qentry >> 1);
+	temp /= qentry;
+      }
+      
+      if ( temp != 0 ) {
+	nonZero = TRUE;
+	out[i] = temp;
+	if (temp < -255) {
+	  temp = -255;
+	  overflow = TRUE;
+	} else if (temp > 255) {
+	  temp = 255;
+	  overflow = TRUE;
+	}
+      } else out[i]=0;
+    }
+  } else {
+    for (i = 0; i < DCTSIZE_SQ; i++) {
+      position = ZAG[i];
+      temp = ((int16 *) in)[position];
+      
+      /* multiply by non-intra qtable */
+      qentry = qscale * niqtable[position];
+      
+      /* see 1993 MPEG doc, D.6.4.5 */
+      temp *= 8;
+      temp /= qentry;	    /* truncation toward 0 -- correct */
+      
+      if ( temp != 0 ) {
+	nonZero = TRUE;
+	out[i] = temp;
+	if (temp < -255) {
+	  temp = -255;
+	  overflow = TRUE;
+	} else if (temp > 255) {
+	  temp = 255;
+	  overflow = TRUE;
+	}
+	
+      } else out[i]=0;
+    }
+  }
+
+ if (overflow) return MPOST_OVERFLOW;
+ if (nonZero)  return MPOST_NON_ZERO;
+ return MPOST_ZERO;
+}
+
+
+
+/*===========================================================================*
+ *
+ * Mpost_RLEHuffIBlock
+ *
+ *	generate the huffman bits from an I-block
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Mpost_RLEHuffIBlock(in, out)
+    FlatBlock in;
+    BitBucket *out;
+{
+    register int i;
+    register int nzeros = 0;
+    register int16 cur;
+    register int16 acur;
+    register uint32 code;
+    register int nbits;
+
+    /*
+     * yes, Virginia, we start at 1.  The DC coefficient is handled
+     * specially, elsewhere.  Not here.
+     */
+    for (i = 1; i < DCTSIZE_SQ; i++) {
+	cur = in[i];
+	acur = ABS(cur);
+	if (cur) {
+	    if ( (nzeros < HUFF_MAXRUN) && (acur < huff_maxlevel[nzeros])) {
+	        /*
+		 * encode using the Huffman tables
+		 */
+
+		DBG_PRINT(("rle_huff %02d.%02d: Run %02d, Level %4d\n", i,  ZAG[i], nzeros, cur));
+		code = (huff_table[nzeros])[acur];
+		nbits = (huff_bits[nzeros])[acur];
+
+		if (cur < 0) {
+		    code |= 1;	/* the sign bit */
+		}
+		Bitio_Write(out, code, nbits);
+	    } else {
+		/*
+		 * encode using the escape code
+		 */
+		DBG_PRINT(("Escape\n"));
+		Bitio_Write(out, 0x1, 6);	/* ESCAPE */
+		DBG_PRINT(("Run Length\n"));
+		Bitio_Write(out, nzeros, 6);	/* Run-Length */
+
+		/*
+	         * this shouldn't happen, but the other
+	         * choice is to bomb out and dump core...
+		 * Hmmm, seems to happen with small Qtable entries (1) -srs
+	         */
+		if (cur < -255) {
+		    cur = -255;
+		} else if (cur > 255) {
+		    cur = 255;
+		}
+
+		DBG_PRINT(("Level\n"));
+		if (acur < 128) {
+		    Bitio_Write(out, cur, 8);
+		} else {
+		    if (cur < 0) {
+			Bitio_Write(out, 0x8001 + cur + 255, 16);
+		    } else {
+			Bitio_Write(out, cur, 16);
+		    }
+		}
+	    }
+	    nzeros = 0;
+	} else {
+	    nzeros++;
+	}
+    }
+    DBG_PRINT(("End of block\n"));
+    Bitio_Write(out, 0x2, 2);	/* end of block marker */
+}
+
+
+/*===========================================================================*
+ *
+ * Mpost_RLEHuffPBlock
+ *
+ *	generate the huffman bits from an P-block
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+Mpost_RLEHuffPBlock(in, out)
+    FlatBlock in;
+    BitBucket *out;
+{
+    register int i;
+    register int nzeros = 0;
+    register int16 cur;
+    register int16 acur;
+    register uint32 code;
+    register int nbits;
+    boolean first_dct = TRUE;
+
+    /*
+     * yes, Virginia, we start at 0.
+     */
+    for (i = 0; i < DCTSIZE_SQ; i++) {
+	cur = in[i];
+	acur = ABS(cur);
+	if (cur) {
+	    if ((nzeros < HUFF_MAXRUN) && (acur < huff_maxlevel[nzeros])) {
+	        /*
+		 * encode using the Huffman tables
+		 */
+
+		DBG_PRINT(("rle_huff %02d.%02d: Run %02d, Level %4d\n", i, ZAG[i], nzeros, cur));
+		if ( first_dct && (nzeros == 0) && (acur == 1) ) {
+		    /* actually, only needs = 0x2 */
+		    code = (cur == 1) ? 0x2 : 0x3;
+		    nbits = 2;
+		} else {
+		    code = (huff_table[nzeros])[acur];
+		    nbits = (huff_bits[nzeros])[acur];
+		  }
+
+		assert(nbits);
+
+		if (cur < 0) {
+		    code |= 1;	/* the sign bit */
+		}
+		Bitio_Write(out, code, nbits);
+		first_dct = FALSE;
+	    } else {
+		/*
+		 * encode using the escape code
+		 */
+		DBG_PRINT(("Escape\n"));
+		Bitio_Write(out, 0x1, 6);	/* ESCAPE */
+		DBG_PRINT(("Run Length\n"));
+		Bitio_Write(out, nzeros, 6);	/* Run-Length */
+
+		/*
+	         * this shouldn't happen, but the other
+	         * choice is to bomb out and dump core...
+		 * Hmmm, seems to happen with small Qtable entries (1) -srs
+	         */
+		if (cur < -255) {
+		  cur = -255;
+		} else if (cur > 255) {
+		  cur = 255;
+		}
+
+		DBG_PRINT(("Level\n"));
+		if (acur < 128) {
+		    Bitio_Write(out, cur, 8);
+		} else {
+		    if (cur < 0) {
+			Bitio_Write(out, 0x8001 + cur + 255, 16);
+		    } else {
+			Bitio_Write(out, cur, 16);
+		    }
+		}
+
+		first_dct = FALSE;
+	    }
+	    nzeros = 0;
+	} else {
+	    nzeros++;
+	}
+    }
+
+    /* actually, should REALLY return FALSE and not use this! */
+
+    if ( first_dct ) {	/* have to give a first_dct even if all 0's */
+	fprintf(stderr, "HUFF called with all-zero coefficients\n");
+	fprintf(stderr, "exiting...\n");
+	exit(1);
+    }
+
+    DBG_PRINT(("End of block\n"));
+    Bitio_Write(out, 0x2, 2);	/* end of block marker */
+}
+
+
+/*===========================================================================*
+ *
+ * CalcRLEHuffLength
+ *
+ *	count the huffman bits for an P-block
+ *
+ * RETURNS:	number of bits
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+int
+CalcRLEHuffLength(in)
+    FlatBlock in;
+{
+  register int i;
+  register int nzeros = 0;
+  register int16 cur;
+  register int16 acur;
+  register int nbits;
+  register int countbits=0;
+  boolean first_dct = TRUE;
+  
+  for (i = 0; i < DCTSIZE_SQ; i++) {
+    cur = in[i];
+    acur = ABS(cur);
+    if (cur) {
+      if ((nzeros < HUFF_MAXRUN) && (acur < huff_maxlevel[nzeros])) {
+	/*
+	 * encode using the Huffman tables
+	 */
+
+	if ( first_dct && (nzeros == 0) && (acur == 1) ) {
+	  nbits = 2;
+	} else {
+	  nbits = (huff_bits[nzeros])[acur];
+	}
+	countbits += nbits;
+	first_dct = FALSE;
+      } else {
+	countbits += 12;	/* ESCAPE + runlength */
+
+	if (acur < 128) {
+	  countbits += 8;
+	} else {
+	  countbits += 16;
+	}
+
+	first_dct = FALSE;
+      }
+      nzeros = 0;
+    } else {
+      nzeros++;
+    }
+  }
+  
+  countbits += 2; /* end of block marker */
+  return countbits;
+}
diff --git a/converter/ppm/ppmtompeg/ppmtompeg.c b/converter/ppm/ppmtompeg/ppmtompeg.c
new file mode 100644
index 00000000..2296e2cb
--- /dev/null
+++ b/converter/ppm/ppmtompeg/ppmtompeg.c
@@ -0,0 +1,722 @@
+/*===========================================================================*
+ * main.c
+ *
+ * Main procedure
+ *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#define _BSD_SOURCE   /* Make sure strdup() is in string.h */
+
+#include <assert.h>
+#include <sys/utsname.h>
+
+#include "all.h"
+#include "mtypes.h"
+#include "mpeg.h"
+#include "motion_search.h"
+#include "prototypes.h"
+#include "param.h"
+#include "parallel.h"
+#include "readframe.h"
+#include "combine.h"
+#include "frames.h"
+#include "jpeg.h"
+#include "specifics.h"
+#include "opts.h"
+#include "frametype.h"
+#include "input.h"
+#include "gethostname.h"
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "nstring.h"
+
+#include <time.h>
+
+int main _ANSI_ARGS_((int argc, char **argv));
+
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+boolean showBitRatePerFrame;
+boolean frameSummary;
+
+extern time_t IOtime;
+int whichGOP = -1;
+boolean ioServer = FALSE;
+boolean outputServer = FALSE;
+boolean decodeServer = FALSE;
+int quietTime = 0;
+boolean realQuiet = FALSE;
+boolean noFrameSummaryOption = FALSE;
+boolean debugSockets = FALSE;
+boolean debugMachines = FALSE;
+boolean bitRateInfoOption = FALSE;
+boolean computeMVHist = FALSE;
+int     baseFormat;
+extern  boolean specificsOn;
+extern  FrameSpecList *fsl;
+boolean pureDCT=FALSE;
+char    encoder_name[1024];
+const char * hostname;
+
+
+/*================================*
+ * External PROCEDURE prototypes  *
+ *================================*/
+
+void init_idctref _ANSI_ARGS_((void));
+void init_fdct _ANSI_ARGS_((void));
+
+
+struct cmdlineInfo {
+    bool         childProcess;
+    int          function;
+    const char * masterHostname;
+    int          masterPortNumber;
+    unsigned int outputFrames;
+    int          maxMachines;
+    const char * paramFileName;
+    bool         specificFrames;
+    unsigned int frameStart;
+    unsigned int frameEnd;
+};
+
+
+
+static void
+parseArgs(int     const argc,
+          char ** const argv,
+          struct cmdlineInfo * const cmdlineP) {
+
+    int idx;
+
+    if (argc-1 < 1)
+        pm_error("You must specify at least one argument: the parameter "
+                 "file name");
+
+    cmdlineP->function = ENCODE_FRAMES;
+    cmdlineP->childProcess = FALSE;  /* initial assumption */
+    cmdlineP->outputFrames = 0;
+    cmdlineP->maxMachines = MAXINT;
+    cmdlineP->specificFrames = FALSE;
+    
+    /* parse the arguments */
+    idx = 1;
+    while ( idx < argc-1 ) {
+        if ( argv[idx][0] != '-' )
+            pm_error("argument '%s', which must be an option because "
+                     "it is not the last argument, "
+                     "does not start with '-'", argv[idx]);
+
+        if ( strcmp(argv[idx], "-stat") == 0 ) {
+            if ( idx+1 < argc-1 ) {
+                SetStatFileName(argv[idx+1]);
+                idx += 2;
+            } else {
+                pm_error("Invalid -stat option");
+            }
+        } else if ( strcmp(argv[idx], "-gop") == 0 ) {
+            if ((cmdlineP->function != ENCODE_FRAMES) || 
+                (cmdlineP->specificFrames))
+                pm_error("Invalid -gop option");
+            
+            if ( idx+1 < argc-1 ) {
+                whichGOP = atoi(argv[idx+1]);
+                idx += 2;
+            } else {
+                pm_error("Invalid -gop option");
+            }
+        } else if ( strcmp(argv[idx], "-frames") == 0 ) {
+            if ( (cmdlineP->function != ENCODE_FRAMES) || (whichGOP != -1) ) {
+                pm_error("invalid -frames option");
+            }
+
+            if ( idx+2 < argc-1 ) {
+                int const frameStart = atoi(argv[idx+1]);
+                int const frameEnd = atoi(argv[idx+2]);
+
+                if (frameStart > frameEnd)
+                    pm_error("Start frame number %d is greater than end "
+                             "frame number %d", frameStart, frameEnd);
+                if (frameStart < 0)
+                    pm_error("Start frame number %d is less than zero",
+                             frameStart);
+
+                cmdlineP->specificFrames = TRUE;
+                cmdlineP->frameStart = frameStart;
+                cmdlineP->frameEnd   = frameEnd;
+                
+                idx += 3;
+            } else
+                pm_error("-frames needs to be followed by two values");
+        } else if (strcmp(argv[idx], "-combine_gops") == 0) {
+            if ((cmdlineP->function != ENCODE_FRAMES) || (whichGOP != -1) || 
+                (cmdlineP->specificFrames)) {
+                pm_error("Invalid -combine_gops option");
+            }
+
+            cmdlineP->function = COMBINE_GOPS;
+            idx++;
+        } else if (strcmp(argv[idx], "-combine_frames") == 0) {
+            if ((cmdlineP->function != ENCODE_FRAMES) || (whichGOP != -1) ||
+                (cmdlineP->specificFrames))
+                pm_error("Invalid -combine_frames option");
+
+            cmdlineP->function = COMBINE_FRAMES;
+            idx++;
+        } else if ( strcmp(argv[idx], "-child") == 0 ) {
+            if ( idx+7 < argc-1 ) {
+                int combinePortNumber;
+                    /* This used to be important information, when the child
+                       notified the combine server.  Now the master notifies
+                       the combine server after the child notifies the master
+                       it is done.  So this value is unused.
+                    */
+                cmdlineP->masterHostname = argv[idx+1];
+                cmdlineP->masterPortNumber = atoi(argv[idx+2]);
+                ioPortNumber = atoi(argv[idx+3]);
+                combinePortNumber = atoi(argv[idx+4]);
+                decodePortNumber = atoi(argv[idx+5]);
+                machineNumber = atoi(argv[idx+6]);
+                remoteIO = atoi(argv[idx+7]);
+
+                IOhostName = cmdlineP->masterHostname;
+            } else
+                pm_error("Not enough option values for -child option.  "
+                         "Need 7.");
+
+            cmdlineP->childProcess = TRUE;
+            idx += 8;
+        } else if ( strcmp(argv[idx], "-io_server") == 0 ) {
+            if ( idx+2 < argc-1 ) {
+                cmdlineP->masterHostname = argv[idx+1];
+                cmdlineP->masterPortNumber = atoi(argv[idx+2]);
+            } else {
+                pm_error("Invalid -io_server option");
+            }
+
+            ioServer = TRUE;
+            idx += 3;
+        } else if ( strcmp(argv[idx], "-output_server") == 0 ) {
+            if ( idx+3 < argc-1 ) {
+                cmdlineP->masterHostname = argv[idx+1];
+                cmdlineP->masterPortNumber = atoi(argv[idx+2]);
+                cmdlineP->outputFrames = atoi(argv[idx+3]);
+            } else {
+                pm_error("-output_server option requires 3 option values.  "
+                         "You specified %d", argc-1 - idx);
+            }
+
+            outputServer = TRUE;
+            idx += 4;
+        } else if ( strcmp(argv[idx], "-decode_server") == 0 ) {
+            if ( idx+3 < argc-1 ) {
+                cmdlineP->masterHostname = argv[idx+1];
+                cmdlineP->masterPortNumber = atoi(argv[idx+2]);
+                cmdlineP->outputFrames = atoi(argv[idx+3]);
+            } else {
+                pm_error("Invalid -decode_server option");
+            }
+
+            cmdlineP->function = COMBINE_FRAMES;
+            decodeServer = TRUE;
+            idx += 4;
+        } else if ( strcmp(argv[idx], "-nice") == 0 ) {
+            niceProcesses = TRUE;
+            idx++;
+        } else if ( strcmp(argv[idx], "-max_machines") == 0 ) {
+            if ( idx+1 < argc-1 ) {
+                cmdlineP->maxMachines = atoi(argv[idx+1]);
+            } else {
+                pm_error("Invalid -max_machines option");
+            }
+
+            idx += 2;
+        } else if ( strcmp(argv[idx], "-quiet") == 0 ) {
+            if ( idx+1 < argc-1 ) {
+                quietTime = atoi(argv[idx+1]);
+            } else {
+                pm_error("Invalid -quiet option");
+            }
+
+            idx += 2;
+        } else if ( strcmp(argv[idx], "-realquiet") == 0 ) {
+            realQuiet = TRUE;
+            idx++;
+        } else if (( strcmp(argv[idx], "-float_dct") == 0 ) ||
+                   ( strcmp(argv[idx], "-float-dct") == 0 )) {
+            pureDCT = TRUE;
+            init_idctref();
+            init_fdct();
+            idx++;
+        } else if ( strcmp(argv[idx], "-no_frame_summary") == 0 ) {
+            if ( idx < argc-1 ) {
+                noFrameSummaryOption = TRUE;
+            } else {
+                pm_error("Invalid -no_frame_summary option");
+            }
+
+            idx++;
+        } else if ( strcmp(argv[idx], "-snr") == 0 ) {
+            printSNR = TRUE;
+            idx++;
+        } else if ( strcmp(argv[idx], "-mse") == 0 ) {
+            printSNR =  printMSE = TRUE;
+            idx++;
+        } else if ( strcmp(argv[idx], "-debug_sockets") == 0 ) {
+            debugSockets = TRUE;
+            idx++;
+        } else if ( strcmp(argv[idx], "-debug_machines") == 0 ) {
+            debugMachines = TRUE;
+            idx++;
+        } else if ( strcmp(argv[idx], "-bit_rate_info") == 0 ) {
+            if ( idx+1 < argc-1 ) {
+                bitRateInfoOption = TRUE;
+                SetBitRateFileName(argv[idx+1]);
+                idx += 2;
+            } else {
+                pm_error("Invalid -bit_rate_info option");
+            }
+        } else if ( strcmp(argv[idx], "-mv_histogram") == 0 ) {
+            computeMVHist = TRUE;
+            idx++;
+        } else {
+            pm_error("Unrecognized option: '%s'", argv[idx]);
+        }
+    }
+
+    cmdlineP->paramFileName = argv[argc-1];
+}
+
+
+
+static void
+compileTests() {
+
+    /* There was code here (assert() calls) that verified that uint16
+       is 16 bits, etc.  It caused compiler warnings that said, "Of
+       course it is!"  (actually, "statement has no effect").  There
+       isn't enough chance that uint16, etc. are defined incorrectly
+       to warrant those asserts.  2000.05.07
+    */
+
+    if ( (-8 >> 3) != -1 ) {
+        fprintf(stderr, "ERROR:  Right shifts are NOT arithmetic!!!\n");
+        fprintf(stderr, "Change >> to multiplies by powers of 2\n");
+        exit(1);
+    }
+}
+
+
+
+static void
+announceJob(enum frameContext const context,
+            bool              const childProcess,
+            unsigned int      const frameStart,
+            unsigned int      const frameEnd,
+            const char *      const outputFileName) {
+
+    if (!realQuiet) {
+        const char * outputDest;
+        const char * combineDest;
+
+        if (context == CONTEXT_JUSTFRAMES)
+            asprintfN(&outputDest, "to individual frame files");
+        else
+            asprintfN(&outputDest, "to file '%s'", outputFileName);
+
+        if (childProcess)
+            combineDest = strdup("for delivery to combine server");
+        else
+            combineDest = strdup("");
+    
+        pm_message("%s:  ENCODING FRAMES %u-%u to %s %s",
+                   hostname, frameStart, frameEnd, outputDest, combineDest);
+
+        strfree(combineDest);
+        strfree(outputDest);
+    }
+}
+
+
+
+static void
+encodeSomeFrames(struct inputSource * const inputSourceP,
+                 boolean              const childProcess,
+                 enum frameContext    const context, 
+                 unsigned int         const frameStart,
+                 unsigned int         const frameEnd,
+                 int32                const qtable[],
+                 int32                const niqtable[],
+                 FILE *               const ofp,
+                 const char *         const outputFileName,
+                 bool                 const wantVbvUnderflowWarning,
+                 bool                 const wantVbvOverflowWarning,
+                 boolean              const printStats,
+                 unsigned int *       const encodeTimeP) {
+    
+    time_t framesTimeStart, framesTimeEnd;
+    unsigned int inputFrameBits;
+    unsigned int totalBits;
+
+    announceJob(context, childProcess, frameStart, frameEnd, outputFileName);
+
+    time(&framesTimeStart);
+    if (printStats)
+        PrintStartStats(framesTimeStart, context == CONTEXT_JUSTFRAMES,
+                        frameStart, frameEnd, inputSourceP);
+
+    GenMPEGStream(inputSourceP, context, frameStart, frameEnd,
+                  qtable, niqtable, childProcess, ofp, outputFileName, 
+                  wantVbvUnderflowWarning, wantVbvOverflowWarning,
+                  &inputFrameBits, &totalBits);
+
+    time(&framesTimeEnd);
+
+    *encodeTimeP = (unsigned int)(framesTimeEnd - framesTimeStart);
+
+    if (!realQuiet)
+        pm_message("%s:  COMPLETED FRAMES %u-%u (%u seconds)",
+                   hostname, frameStart, frameEnd, *encodeTimeP);
+
+    if (printStats)
+        PrintEndStats(framesTimeStart, framesTimeEnd, 
+                      inputFrameBits, totalBits);
+}
+
+
+
+
+static void
+encodeFrames(struct inputSource * const inputSourceP,
+             boolean              const childProcess,
+             const char *         const masterHostname,
+             int                  const masterPortNumber,
+             int                  const whichGOP,
+             bool                 const specificFrames,
+             unsigned int         const whichFrameStart,
+             unsigned int         const whichFrameEnd,
+             int32                const qtable[],
+             int32                const niqtable[],
+             FILE *               const ofp,
+             const char *         const outputFileName,
+             bool                 const wantVbvUnderflowWarning,
+             bool                 const wantVbvOverflowWarning) {
+/*----------------------------------------------------------------------------
+  Encode the stream.  If 'specificFrames' is true, then encode frames
+  'whichFrameStart' through 'whichFrameEnd' individually.  Otherwise,
+  encode the entire input stream as a complete MPEG stream.
+  
+  'childProcess' means to do it as a child process that is under the
+  supervision of a master process and is possibly doing only part of
+  a larger batch.
+  
+  (If we had proper modularity, we wouldn't care, but parallel operation
+  was glued on to this program after it was complete).
+  
+  One thing we don't do when running as a child process is print
+  statistics; our master will do that for the whole job.
+----------------------------------------------------------------------------*/
+    unsigned int frameStart, frameEnd;
+    enum frameContext context;
+    unsigned int      lastEncodeTime;
+        /* How long it took the encoder to do the last set of frames */
+    boolean           printStats;
+        /* We want the encoder to print start & end stats */
+
+    if (whichGOP != -1) {
+        /* He wants just one particular GOP from the middle of the movie. */
+        ComputeGOPFrames(whichGOP, &frameStart, &frameEnd, 
+                         inputSourceP->numInputFiles);
+        context = CONTEXT_GOP;
+    } else if (specificFrames) {
+        /* He wants some pure frames from the middle of the movie */
+        if (whichFrameStart > whichFrameEnd)
+            pm_error("You specified a starting frame number (%d) that is "
+                     "greater than the ending frame number (%d) "
+                     "you specified.", whichFrameStart, whichFrameEnd);
+        if (whichFrameEnd + 1 > inputSourceP->numInputFiles)
+            pm_error("You specified ending frame number %d, which is "
+                     "beyond the number of input files you supplied (%u)",
+                     whichFrameEnd, inputSourceP->numInputFiles);
+
+        frameStart = whichFrameStart;
+        frameEnd   = whichFrameEnd;
+        context = CONTEXT_JUSTFRAMES;
+    } else {
+        /* He wants the whole movie */
+        frameStart = 0;
+        frameEnd   = inputSourceP->numInputFiles - 1;
+        context = CONTEXT_WHOLESTREAM;
+    }
+    
+    printStats = !childProcess;
+    
+    encodeSomeFrames(inputSourceP, childProcess, context, frameStart, frameEnd,
+                     customQtable, customNIQtable,
+                     ofp, outputFileName, 
+                     wantVbvUnderflowWarning, wantVbvOverflowWarning,
+                     printStats,
+                     &lastEncodeTime);
+
+    if (childProcess) {
+        boolean moreWorkToDo;
+        
+        /* A child is not capable of generating GOP or stream headers */
+        assert(context == CONTEXT_JUSTFRAMES);
+        
+        moreWorkToDo = TRUE;  /* initial assumption */
+        while (moreWorkToDo) {
+            int nextFrameStart, nextFrameEnd;
+
+            NotifyMasterDone(masterHostname, masterPortNumber, machineNumber, 
+                             lastEncodeTime, &moreWorkToDo,
+                             &nextFrameStart, &nextFrameEnd);
+            if (moreWorkToDo)
+                encodeSomeFrames(inputSourceP, childProcess, 
+                                 CONTEXT_JUSTFRAMES, 
+                                 nextFrameStart, nextFrameEnd,
+                                 customQtable, customNIQtable,
+                                 NULL, outputFileName, 
+                                 wantVbvUnderflowWarning, 
+                                 wantVbvOverflowWarning,
+                                 FALSE,
+                                 &lastEncodeTime);
+        }
+        if (!realQuiet)
+            pm_message("%s: Child exiting.  Master has no more work.",
+                       hostname);
+    }
+}
+
+
+
+
+static void
+runMaster(struct inputSource * const inputSourceP,
+          const char *         const paramFileName,
+          const char *         const outputFileName) {
+
+    if (paramFileName[0] != '/' && paramFileName[0] != '~')
+        pm_error("For parallel mode, you must "
+                 "use an absolute path for parameter file.  "
+                 "You specified '%s'", paramFileName);
+    else
+        MasterServer(inputSourceP, paramFileName, outputFileName);
+}
+
+
+
+static void
+getUserFrameFile(void *       const handle,
+                 unsigned int const frameNumber,
+                 FILE **      const ifPP) {
+    
+    struct inputSource * const inputSourceP = (struct inputSource *) handle;
+
+    if (inputSourceP->stdinUsed)
+        pm_error("You cannot combine frames from Standard Input.");
+            /* Because Caller detects end of frames by EOF */
+    
+    if (frameNumber >= inputSourceP->numInputFiles)
+        *ifPP = NULL;
+    else {
+        const char * fileName;
+        const char * inputFileName;
+
+        GetNthInputFileName(inputSourceP, frameNumber, &inputFileName);
+        
+        asprintfN(&fileName, "%s/%s", currentFramePath, inputFileName);
+        
+        *ifPP = fopen(fileName, "rb");
+        if (*ifPP == NULL)
+            pm_error("Unable to open file '%s'.  Errno = %d (%s)",
+                     fileName, errno, strerror(errno));
+        
+        strfree(inputFileName);
+        strfree(fileName);
+    }
+}
+
+
+
+static void
+nullDisposeFile(void *       const handle,
+                unsigned int const frameNumber) {
+
+
+}
+
+
+
+static unsigned int
+framePoolSize(bool const sequentialInput) {
+/*----------------------------------------------------------------------------
+   Return the number of frames that our frame memory pool needs to have.
+
+   'sequentialInput' means we'll be reading from input that comes in frame
+   number order and we can't go back, so we'll have to have a bigger buffer
+   of frames than otherwise.
+-----------------------------------------------------------------------------*/
+    unsigned int numOfFrames;
+
+    numOfFrames = 0;  /* initial value */
+
+    if (sequentialInput) {
+        unsigned int idx;
+        unsigned int bcount;
+
+        for ( idx = 0, bcount = 0; idx < strlen(framePattern); idx++) {
+
+            /* counts the maximum number of B frames between two reference
+             * frames. 
+             */
+            
+            switch( framePattern[idx] ) {
+            case 'b': 
+                bcount++;
+                break;
+            case 'i':
+            case 'p':
+                if (bcount > numOfFrames)
+                    numOfFrames = bcount;
+                bcount = 0;
+                break;
+            }
+            
+            /* add 2 to hold the forward and past reference frames in addition
+             * to the maximum number of B's 
+             */
+        }
+        
+        numOfFrames += 2;
+
+    } else {
+        /* non-interactive, only 3 frames needed */
+        numOfFrames = 3;
+    }
+    return numOfFrames;
+}
+
+
+
+int
+main(int argc, char **argv) {
+    FILE *ofP;
+    time_t  initTimeStart;
+    struct cmdlineInfo cmdline;
+    struct params params;
+
+    ppm_init(&argc, argv);
+
+    strcpy(encoder_name, argv[0]);
+
+    compileTests();
+
+    time(&initTimeStart);
+
+    SetStatFileName("");
+
+    hostname = GetHostName();
+
+    parseArgs(argc, argv, &cmdline);
+
+    ReadParamFile(cmdline.paramFileName, cmdline.function, &params);
+
+    /* Jim Boucher's stuff:
+       if we are using a movie format then break up into frames
+    */
+    if ((!cmdline.childProcess) && (baseFormat == JMOVIE_FILE_TYPE))
+        JM2JPEG(params.inputSourceP);
+
+    if (printSNR || (referenceFrame == DECODED_FRAME))
+        decodeRefFrames = TRUE;
+
+    showBitRatePerFrame = (bitRateInfoOption && !cmdline.childProcess);
+    frameSummary = (!noFrameSummaryOption && !cmdline.childProcess);
+
+    numMachines = min(numMachines, cmdline.maxMachines);
+
+    Tune_Init();
+    Frame_Init(framePoolSize(params.inputSourceP->stdinUsed));
+
+    if (specificsOn) 
+        Specifics_Init();
+
+    ComputeFrameTable(params.inputSourceP->stdinUsed ? 
+                      0 : params.inputSourceP->numInputFiles);
+
+    if (ioServer) {
+        IoServer(params.inputSourceP, cmdline.masterHostname, 
+                 cmdline.masterPortNumber);
+        return 0;
+    } else if (outputServer) {
+        CombineServer(cmdline.outputFrames, 
+                      cmdline.masterHostname, cmdline.masterPortNumber,
+                      outputFileName);
+    } else if (decodeServer) {
+        DecodeServer(cmdline.outputFrames, outputFileName, 
+                     cmdline.masterHostname, cmdline.masterPortNumber);
+    } else {
+        if ((!cmdline.specificFrames) &&
+            ((numMachines == 0) || (cmdline.function != ENCODE_FRAMES)) ) {
+            ofP = fopen(outputFileName, "wb");
+            if (ofP == NULL)
+                pm_error("Could not open output file!");
+        } else
+            ofP = NULL;
+        
+        if (cmdline.function == ENCODE_FRAMES) {
+            if ((numMachines == 0) || (cmdline.specificFrames)) {
+                encodeFrames(params.inputSourceP,
+                             cmdline.childProcess, 
+                             cmdline.masterHostname, cmdline.masterPortNumber,
+                             whichGOP, cmdline.specificFrames,
+                             cmdline.frameStart, cmdline.frameEnd,
+                             customQtable, customNIQtable,
+                             ofP, outputFileName,
+                             params.warnUnderflow, params.warnOverflow);
+                
+            } else
+                runMaster(params.inputSourceP,
+                          cmdline.paramFileName, outputFileName);
+        } else if (cmdline.function == COMBINE_GOPS)
+            GOPsToMPEG(params.inputSourceP, outputFileName, ofP);
+        else if (cmdline.function == COMBINE_FRAMES)
+            FramesToMPEG(ofP, params.inputSourceP,
+                         &getUserFrameFile, &nullDisposeFile);
+    } 
+    Frame_Exit();
+        
+    strfree(hostname);
+
+    return 0;
+}
diff --git a/converter/ppm/ppmtompeg/psearch.c b/converter/ppm/ppmtompeg/psearch.c
new file mode 100644
index 00000000..aea1a29b
--- /dev/null
+++ b/converter/ppm/ppmtompeg/psearch.c
@@ -0,0 +1,989 @@
+/*===========================================================================*
+ * psearch.c                                     
+ *                                       
+ *  Procedures concerned with the P-frame motion search          
+ *                                       
+ *===========================================================================*/
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "all.h"
+#include "mtypes.h"
+#include "frames.h"
+#include "motion_search.h"
+#include "prototypes.h"
+#include "fsize.h"
+#include "param.h"
+
+
+/*==================*
+ * STATIC VARIABLES *
+ *==================*/
+
+/* none */
+
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+int **pmvHistogram = NULL;  /* histogram of P-frame motion vectors */
+int **bbmvHistogram = NULL; /* histogram of B-frame bkwd motion vectors */
+int **bfmvHistogram = NULL; /* histogram of B-frame fwd motion vectors */
+int pixelFullSearch;
+int searchRangeP,searchRangeB;
+/* The range, in half pixels in each direction, that we are to search
+       when detecting motion.  Specified by RANGE statement in parameter file.
+    */
+int psearchAlg;
+/* specified by parameter file. */
+
+/*===============================*
+ * INTERNAL PROCEDURE prototypes *
+ *===============================*/
+
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+
+/*===========================================================================*
+ *
+ *  Compute the best P-frame motion vector we can.  If it's better than
+ *  *motionP, update *motionP to it.
+ *
+ * PRECONDITIONS:   The relevant block in 'current' is valid (it has not
+ *          been dct'd).  Thus, the data in 'current' can be
+ *          accesed through y_blocks, cr_blocks, and cb_blocks.
+ *          This is not the case for the blocks in 'prev.'
+ *          Therefore, references into 'prev' should be done
+ *          through the struct items ref_y, ref_cr, ref_cb
+ *
+ * POSTCONDITIONS:  current, prev unchanged.
+ *          Some computation could be saved by requiring
+ *          the dct'd difference to be put into current's block
+ *          elements here, depending on the search technique.
+ *          However, it was decided that it mucks up the code
+ *          organization a little, and the saving in computation
+ *          would be relatively little (if any).
+ *
+ * NOTES:   the search procedure need not check the (0,0) motion vector
+ *      the calling procedure has a preference toward (0,0) and it
+ *      will check it itself
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+PMotionSearch(const LumBlock * const currentBlockP, 
+              MpegFrame *      const prev, 
+              int              const by,
+              int              const bx, 
+              vector *         const motionP) {
+
+    /* CALL SEARCH PROCEDURE */
+
+    switch(psearchAlg) {
+    case PSEARCH_SUBSAMPLE:
+        PSubSampleSearch(currentBlockP, prev, by, bx, motionP, searchRangeP);
+        break;
+    case PSEARCH_EXHAUSTIVE:
+        PLocalSearch(currentBlockP, prev, by, bx, 
+                     motionP, INT_MAX, searchRangeP);
+        break;
+    case PSEARCH_LOGARITHMIC:
+        PLogarithmicSearch(currentBlockP, prev, by, bx, motionP, searchRangeP);
+        break;
+    case PSEARCH_TWOLEVEL:
+        PTwoLevelSearch(currentBlockP, prev, by, bx, 
+                        motionP, INT_MAX, searchRangeP);
+        break;
+    default:
+        pm_error("IMPOSSIBLE PSEARCH ALG:  %d", psearchAlg);
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * SetPixelSearch
+ *
+ *  set the pixel search type (half or full)
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    pixelFullSearch
+ *
+ *===========================================================================*/
+void
+SetPixelSearch(const char * const searchType) {
+    if ( (strcmp(searchType, "FULL") == 0 ) || 
+         ( strcmp(searchType, "WHOLE") == 0 )) {
+        pixelFullSearch = TRUE;
+    } else if ( strcmp(searchType, "HALF") == 0 ) {
+        pixelFullSearch = FALSE;
+    } else {
+        fprintf(stderr, "ERROR:  Invalid pixel search type:  %s\n",
+                searchType);
+        exit(1);
+    }
+}
+
+
+/*===========================================================================*
+ *
+ * SetPSearchAlg
+ *
+ *  set the P-search algorithm
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    psearchAlg
+ *
+ *===========================================================================*/
+void
+SetPSearchAlg(const char * const alg)
+{
+    if ( strcmp(alg, "EXHAUSTIVE") == 0 ) {
+        psearchAlg = PSEARCH_EXHAUSTIVE;
+    } else if (strcmp(alg, "SUBSAMPLE") == 0 ) {
+        psearchAlg = PSEARCH_SUBSAMPLE;
+    } else if ( strcmp(alg, "LOGARITHMIC") == 0 ) {
+        psearchAlg = PSEARCH_LOGARITHMIC;
+    } else if ( strcmp(alg, "TWOLEVEL") == 0 ) {
+        psearchAlg = PSEARCH_TWOLEVEL;
+    } else {
+        fprintf(stderr, "ERROR:  Invalid psearch algorithm:  %s\n", alg);
+        exit(1);
+    }
+}
+
+
+/*===========================================================================*
+ *
+ * PSearchName
+ *
+ *  returns a string containing the name of the search algorithm
+ *
+ * RETURNS: pointer to the string
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+const char *
+PSearchName(void)
+{
+    const char *retval;
+
+    switch(psearchAlg) {
+    case PSEARCH_EXHAUSTIVE:
+        retval = "EXHAUSTIVE";break;
+    case PSEARCH_SUBSAMPLE:
+        retval = "SUBSAMPLE";break;
+    case PSEARCH_LOGARITHMIC:
+        retval = "LOGARITHMIC";break;
+    case PSEARCH_TWOLEVEL:
+        retval = "TWOLEVEL";break;
+    default:
+        fprintf(stderr, "ERROR:  Illegal PSEARCH ALG:  %d\n", psearchAlg);
+        exit(1);
+        break;
+    }
+    return retval;
+}
+
+
+/*===========================================================================*
+ *
+ * SetSearchRange
+ *
+ *  sets the range of the search to the given number of pixels,
+ *  allocate histogram storage
+ *
+ *===========================================================================*/
+void
+SetSearchRange(int const pixelsP, int const pixelsB) {
+
+    searchRangeP = 2*pixelsP;   /* +/- 'pixels' pixels */
+    searchRangeB = 2*pixelsB;
+
+    if ( computeMVHist ) {
+        int const max_search = max(searchRangeP, searchRangeB);
+
+        int index;
+    
+        pmvHistogram = (int **) malloc((2*searchRangeP+3)*sizeof(int *));
+        bbmvHistogram = (int **) malloc((2*searchRangeB+3)*sizeof(int *));
+        bfmvHistogram = (int **) malloc((2*searchRangeB+3)*sizeof(int *));
+        for ( index = 0; index < 2*max_search+3; index++ ) {
+            pmvHistogram[index] = 
+                (int *) calloc(2*searchRangeP+3, sizeof(int));
+            bbmvHistogram[index] = 
+                (int *) calloc(2*searchRangeB+3, sizeof(int));
+            bfmvHistogram[index] = 
+                (int *) calloc(2*searchRangeB+3, sizeof(int));
+        }
+    }
+}
+
+
+/*===========================================================================*
+ *
+ *              USER-MODIFIABLE
+ *
+ * MotionSearchPreComputation
+ *
+ *  do whatever you want here; this is called once per frame, directly
+ *  after reading
+ *
+ * RETURNS: whatever
+ *
+ * SIDE EFFECTS:    whatever
+ *
+ *===========================================================================*/
+void
+MotionSearchPreComputation(MpegFrame * const frameP) {
+    /* do nothing */
+}
+
+
+/*===========================================================================*
+ *
+ * PSubSampleSearch
+ *
+ *  uses the subsampling algorithm to compute the P-frame vector
+ *
+ * RETURNS: motion vector
+ *
+ * SIDE EFFECTS:    none
+ *
+ * REFERENCE:  Liu and Zaccarin:  New Fast Algorithms for the Estimation
+ *      of Block Motion Vectors, IEEE Transactions on Circuits
+ *      and Systems for Video Technology, Vol. 3, No. 2, 1993.
+ *
+ *===========================================================================*/
+int
+PSubSampleSearch(const LumBlock * const currentBlockP,
+                 MpegFrame *      const prev, 
+                 int              const by,
+                 int              const bx,
+                 vector *         const motionP,
+                 int              const searchRange) {
+    
+    int my, mx;
+    int bestBestDiff;
+    int stepSize;
+    int x;
+    int bestMY[4], bestMX[4], bestDiff[4];
+    int leftMY, leftMX;
+    int rightMY, rightMX;
+
+    stepSize = (pixelFullSearch ? 2 : 1);
+
+    COMPUTE_MOTION_BOUNDARY(by,bx,stepSize,leftMY,leftMX,rightMY,rightMX);
+
+    if ( searchRange < rightMY ) {
+        rightMY = searchRange;
+    }
+
+    if ( searchRange < rightMX ) {
+        rightMX = searchRange;
+    }
+
+    for ( x = 0; x < 4; x++ ) {
+        bestMY[x] = 0;
+        bestMX[x] = 0;
+        bestDiff[x] = INT_MAX;
+    }
+
+    /* do A pattern */
+    for (my = -searchRange; my < rightMY; my += 2*stepSize) {
+        if (my >= leftMY) {
+            for ( mx = -searchRange; mx < rightMX; mx += 2*stepSize ) {
+                if (mx >= leftMX) {
+                    int diff;
+                    vector m;
+                    m.y = my; m.x = mx;
+                    diff = LumMotionErrorA(currentBlockP, prev, by, bx, m,
+                                           bestDiff[0]);
+                    
+                    if (diff < bestDiff[0]) {
+                        bestMY[0] = my;
+                        bestMX[0] = mx;
+                        bestDiff[0] = diff;
+                    }
+                }
+            }
+        }
+    }
+
+    /* do B pattern */
+    for (my = stepSize-searchRange; my < rightMY; my += 2*stepSize) {
+        if (my >= leftMY) {
+            for (mx = -searchRange; mx < rightMX; mx += 2*stepSize) {
+                if (mx >= leftMX) {
+                    int diff;
+                    vector m;
+                    m.y = my; m.x = mx;
+                    diff = LumMotionErrorB(currentBlockP, prev, by, bx, m, 
+                                           bestDiff[1]);
+                    
+                    if (diff < bestDiff[1]) {
+                        bestMY[1] = my;
+                        bestMX[1] = mx;
+                        bestDiff[1] = diff;
+                    }
+                }
+            }
+        }
+    }
+
+    /* do C pattern */
+    for (my = stepSize-searchRange; my < rightMY; my += 2*stepSize) {
+        if (my >= leftMY) {
+            for ( mx = stepSize-searchRange; mx < rightMX; mx += 2*stepSize ) {
+                if (mx >= leftMX) {
+                    int diff;
+                    vector m;
+                    m.y = my; m.x = mx;
+                    diff = LumMotionErrorC(currentBlockP, prev, by, bx, m,
+                                           bestDiff[2]);
+                    
+                    if (diff < bestDiff[2]) {
+                        bestMY[2] = my;
+                        bestMX[2] = mx;
+                        bestDiff[2] = diff;
+                    }
+                }
+            }
+        }
+    }
+
+    /* do D pattern */
+    for (my = -searchRange; my < rightMY; my += 2*stepSize) {
+        if (my >= leftMY) {
+            for (mx = stepSize-searchRange; mx < rightMX; mx += 2*stepSize) {
+                if (mx >= leftMX) {
+                    int diff;
+                    vector m;
+                    m.y = my; m.x = mx;
+                    diff = LumMotionErrorD(currentBlockP, prev, by, bx, m,
+                                           bestDiff[3]);
+                    
+                    if (diff < bestDiff[3]) {
+                        bestMY[3] = my;
+                        bestMX[3] = mx;
+                        bestDiff[3] = diff;
+                    }
+                }
+            }
+        }
+    }
+
+    /* first check old motion */
+    if ((motionP->y >= leftMY) && (motionP->y < rightMY) &&
+        (motionP->x >= leftMX) && (motionP->x < rightMX)) {
+        bestBestDiff = LumMotionError(currentBlockP, prev, by, bx, 
+                                      *motionP, INT_MAX);
+    } else
+        bestBestDiff = INT_MAX;
+
+    /* look at Error of 4 different motion vectors */
+    for (x = 0; x < 4; ++x) {
+        vector m;
+        m.y = bestMY[x];
+        m.x = bestMX[x];
+        bestDiff[x] = LumMotionError(currentBlockP, prev, by, bx, m,
+                                     bestBestDiff);
+
+        if (bestDiff[x] < bestBestDiff) {
+            bestBestDiff = bestDiff[x];
+            *motionP     = m;
+        }
+    }
+    return bestBestDiff;
+}
+
+
+
+static void
+findBestSpaced(int              const minMY,
+               int              const minMX,
+               int              const maxMY,
+               int              const maxMX,
+               int              const spacing,
+               const LumBlock * const currentBlockP, 
+               MpegFrame *      const prev,
+               int              const by,
+               int              const bx,
+               int *            const bestDiffP, 
+               vector *         const centerP) {
+/*----------------------------------------------------------------------------
+   Examine every 'spacing'th half-pixel within the rectangle 
+   ('minBoundX', 'minBoundY', 'maxBoundX', 'maxBoundY'), 
+
+   If one of the half-pixels examined has a lower "LumMotionError" value
+   than *bestDiffP, update *bestDiffP to that value and update
+   *centerP to the location of that half-pixel.
+-----------------------------------------------------------------------------*/
+    int const minBoundY = MAX(minMY, centerP->y - spacing);
+    int const minBoundX = MAX(minMX, centerP->x - spacing);
+    int const maxBoundY = MIN(maxMY, centerP->y + spacing + 1);
+    int const maxBoundX = MIN(maxMX, centerP->x + spacing + 1);
+
+    int my;
+
+    for (my = minBoundY; my < maxBoundY; my += spacing) {
+        int mx;
+
+        for (mx = minBoundX; mx < maxBoundX; mx += spacing) {
+            int diff;
+            vector m;
+
+            m.y = my; m.x = mx;
+            
+            diff = LumMotionError(currentBlockP, prev, by, bx, m, *bestDiffP);
+            
+            if (diff < *bestDiffP) {
+                *centerP   = m;
+                *bestDiffP = diff;
+            }
+        }
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * PLogarithmicSearch
+ *
+ *  uses logarithmic search to compute the P-frame vector
+ *
+ * RETURNS: motion vector
+ *
+ * SIDE EFFECTS:    none
+ *
+ * REFERENCE:  MPEG-I specification, pages 32-33
+ *
+ *===========================================================================*/
+int
+PLogarithmicSearch(const LumBlock * const currentBlockP,
+                   MpegFrame *      const prev, 
+                   int              const by,
+                   int              const bx, 
+                   vector *         const motionP,
+                   int              const searchRange) {
+
+    int const stepSize = (pixelFullSearch ? 2 : 1);
+
+    int minMY, minMX, maxMY, maxMX;
+    int spacing;  /* grid spacing */
+    vector motion;
+        /* Distance from (bx,by) (in half-pixels) of the block that is most
+           like the current block among those that we have examined so far.
+           (0,0) means we haven't examined any.
+        */
+    int bestDiff;
+        /* The difference between the current block and the block offset
+           'motion' from it.
+        */
+    
+    COMPUTE_MOTION_BOUNDARY(by, bx, stepSize, minMY, minMX, maxMY, maxMX);
+    minMX = max(minMX, - searchRange);
+    minMY = max(minMY, - searchRange);
+    maxMX = min(maxMX, + searchRange);
+    maxMY = min(maxMY, + searchRange);
+
+    /* Note: The clipping to 'searchRange' above may seem superfluous because
+       the basic algorithm would never want to look more than 'searchRange'
+       pixels away, but with rounding error, it can.
+    */
+
+    motion.x = motion.y = 0;
+    bestDiff = INT_MAX;
+
+    for (spacing = searchRange; spacing >= stepSize;) {
+        if (stepSize == 2) {  /* make sure spacing is even */
+            if (spacing == 2) 
+                spacing = 0;
+            else {
+                spacing = (spacing+1)/2;
+                if (spacing % 2 != 0) 
+                    --spacing;
+            }
+        } else {
+            if (spacing == 1) {
+                spacing = 0;
+            } else 
+                spacing = (spacing + 1) / 2;
+        }
+        if (spacing >= stepSize) 
+            findBestSpaced(minMY, minMX, maxMY, maxMX,
+                           spacing, currentBlockP, prev, by, bx,
+                           &bestDiff, &motion);
+    }
+
+    {
+        int diff;
+        /* check old motion -- see if it's better */
+        if ((motionP->y >= minMY) && (motionP->y < maxMY) &&
+            (motionP->x >= minMX) && (motionP->x < maxMX)) {
+            diff = LumMotionError(currentBlockP, prev, by, bx, 
+                                  *motionP, bestDiff);
+        } else 
+            diff = INT_MAX;
+        
+        if (bestDiff < diff)
+            *motionP = motion;
+        else 
+            bestDiff = diff;
+    }
+
+    return bestDiff;
+}
+
+
+
+/*===========================================================================*
+ *
+ * PLocalSearch
+ *
+ *  uses local exhaustive search to compute the P-frame vector
+ *
+ * RETURNS: motion vector
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+int
+PLocalSearch(const LumBlock * const currentBlockP,
+             MpegFrame *      const prev, 
+             int              const by,
+             int              const bx,
+             vector *         const motionP,
+             int              const bestSoFar,
+             int              const searchRange) {
+
+    int mx, my;
+    int bestDiff;
+    int stepSize;
+    int leftMY, leftMX;
+    int rightMY, rightMX;
+    int distance;
+    int tempRightMY, tempRightMX;
+
+    stepSize = (pixelFullSearch ? 2 : 1);
+
+    COMPUTE_MOTION_BOUNDARY(by,bx,stepSize,leftMY,leftMX,rightMY,rightMX);
+
+    /* try old motion vector first */
+    if (VALID_MOTION(*motionP)) {
+        bestDiff = LumMotionError(currentBlockP, prev, by, bx, 
+                                  *motionP, bestSoFar);
+
+        if (bestSoFar < bestDiff)
+            bestDiff = bestSoFar;
+    } else {
+        motionP->y = motionP->x = 0;
+        bestDiff = bestSoFar;
+    }
+
+    /* try a spiral pattern */    
+    for (distance = stepSize; distance <= searchRange; distance += stepSize) {
+        tempRightMY = MIN(distance, rightMY);
+        tempRightMX = MIN(distance, rightMX);
+
+        /* do top, bottom */
+        for (my = -distance; my < tempRightMY;
+             my += max(tempRightMY+distance-stepSize, stepSize)) {
+            if (my >= leftMY) {
+                for ( mx = -distance; mx < tempRightMX; mx += stepSize ) {
+                    if (mx >= leftMX) {
+                        int diff;
+                        vector m;
+                        
+                        m.y = my; m.x = mx;
+                        diff = LumMotionError(currentBlockP, prev, by, bx, m, 
+                                              bestDiff);
+                        
+                        if (diff < bestDiff) {
+                            *motionP = m;
+                            bestDiff = diff;
+                        }
+                    }
+                }
+            }
+        }
+
+        /* do left, right */
+        for (mx = -distance; mx < tempRightMX;
+             mx += max(tempRightMX+distance-stepSize, stepSize)) {
+
+            if (mx >= leftMX) {
+                for (my = -distance+stepSize; my < tempRightMY-stepSize;
+                     my += stepSize) {
+                    if (my >= leftMY) {
+                        int diff;
+                        vector m;
+
+                        m.y = my; m.x = mx;
+                        diff = LumMotionError(currentBlockP, prev, by, bx, m,
+                                              bestDiff);
+                        
+                        if (diff < bestDiff) {
+                            *motionP = m;
+                            bestDiff = diff;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return bestDiff;
+}
+
+
+/*===========================================================================*
+ *
+ * PTwoLevelSearch
+ *
+ *  uses two-level search to compute the P-frame vector
+ *  first does exhaustive full-pixel search, then looks at neighboring
+ *  half-pixel motion vectors
+ *
+ * RETURNS: motion vector
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+int
+PTwoLevelSearch(const LumBlock * const currentBlockP,
+                MpegFrame *      const prev, 
+                int              const by,
+                int              const bx, 
+                vector *         const motionP,
+                int              const bestSoFar,
+                int              const searchRange) {
+    int mx, my;
+    int loopInc;
+    int diff, bestDiff;
+    int leftMY, leftMX;
+    int rightMY, rightMX;
+    int distance;
+    int tempRightMY, tempRightMX;
+    int xOffset, yOffset;
+
+    /* exhaustive full-pixel search first */
+
+    COMPUTE_MOTION_BOUNDARY(by,bx,2,leftMY,leftMX,rightMY,rightMX);
+
+    rightMY--;
+    rightMX--;
+
+    /* convert vector into full-pixel vector */
+    if (motionP->y > 0) {
+        if ((motionP->y % 2) == 1) {
+            --motionP->y;
+        }
+    } else if (((-motionP->y) % 2) == 1)
+        ++motionP->y;
+
+    if (motionP->x > 0) {
+        if ((motionP->x % 2) == 1)
+            --motionP->x;
+    } else if ((-motionP->x % 2) == 1)
+        ++motionP->x;
+
+    /* try old motion vector first */
+    if (VALID_MOTION(*motionP)) {
+        bestDiff = LumMotionError(currentBlockP, prev, by, bx, 
+                                  *motionP, bestSoFar);
+
+        if ( bestSoFar < bestDiff ) {
+            bestDiff = bestSoFar;
+        }
+    } else {
+        motionP->y = motionP->x = 0;
+        bestDiff = bestSoFar;
+    }
+
+    ++rightMY;
+    ++rightMX;
+
+    /* try a spiral pattern */    
+    for ( distance = 2; distance <= searchRange; distance += 2 ) {
+        tempRightMY = MIN(distance, rightMY);
+        tempRightMX = MIN(distance, rightMX);
+
+        /* do top, bottom */
+        loopInc = max(tempRightMY + distance - 2, 2);
+        for (my = -distance; my < tempRightMY; my += loopInc) {
+            if (my >= leftMY) {
+                for (mx = -distance; mx < tempRightMX; mx += 2) {
+                    if (mx >= leftMX) {
+                        vector m;
+                        m.y = my; m.x = mx;
+                        diff = LumMotionError(currentBlockP, prev, by, bx, m, 
+                                              bestDiff);
+                        
+                        if (diff < bestDiff) {
+                            *motionP = m;
+                            bestDiff = diff;
+                        }
+                    }
+                }
+            }
+        }
+
+        /* do left, right */
+        loopInc = max(tempRightMX+distance-2, 2);
+        for (mx = -distance; mx < tempRightMX; mx += loopInc) {
+            if (mx >= leftMX) {
+                for ( my = -distance+2; my < tempRightMY-2; my += 2 ) {
+                    if (my >= leftMY) {
+                        int diff;
+                        vector m;
+                        m.y = my; m.x = mx;
+                        diff = LumMotionError(currentBlockP, prev, by, bx, m, 
+                                              bestDiff);
+                        
+                        if ( diff < bestDiff ) {
+                            *motionP = m;
+                            bestDiff = diff;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /* now look at neighboring half-pixels */
+    my = motionP->y;
+    mx = motionP->x;
+
+    --rightMY;
+    --rightMX;
+
+    for (yOffset = -1; yOffset <= 1; ++yOffset) {
+        for (xOffset = -1; xOffset <= 1; ++xOffset) {
+            if ((yOffset != 0) || (xOffset != 0)) {
+                vector m;
+                m.y = my+yOffset; m.x = mx+xOffset;
+                if (VALID_MOTION(m)) {
+                    int diff;
+                    diff = LumMotionError(currentBlockP, prev, by, bx,
+                                          m, bestDiff);
+                    if (diff < bestDiff) {
+                        *motionP = m;
+                        bestDiff = diff;
+                    }
+                }
+            }
+        }
+    }
+    return bestDiff;
+}
+
+
+
+void
+ShowPMVHistogram(fpointer)
+    FILE *fpointer;
+{
+    register int x, y;
+    int *columnTotals;
+    int rowTotal;
+
+    columnTotals = (int *) calloc(2*searchRangeP+3, sizeof(int));
+
+#ifdef COMPLETE_DISPLAY
+    fprintf(fpointer, "    ");
+    for ( y = 0; y < 2*searchRange+3; y++ ) {
+        fprintf(fpointer, "%3d ", y-searchRangeP-1);
+    }
+    fprintf(fpointer, "\n");
+#endif
+
+    for ( x = 0; x < 2*searchRangeP+3; x++ ) {
+#ifdef COMPLETE_DISPLAY
+        fprintf(fpointer, "%3d ", x-searchRangeP-1);
+#endif
+        rowTotal = 0;
+        for ( y = 0; y < 2*searchRangeP+3; y++ ) {
+            fprintf(fpointer, "%3d ", pmvHistogram[x][y]);
+            rowTotal += pmvHistogram[x][y];
+            columnTotals[y] += pmvHistogram[x][y];
+        }
+#ifdef COMPLETE_DISPLAY
+        fprintf(fpointer, "%4d\n", rowTotal);
+#else
+        fprintf(fpointer, "\n");
+#endif
+    }
+
+#ifdef COMPLETE_DISPLAY
+    fprintf(fpointer, "Tot ");
+    for ( y = 0; y < 2*searchRangeP+3; y++ ) {
+        fprintf(fpointer, "%3d ", columnTotals[y]);
+    }
+#endif
+    fprintf(fpointer, "\n");
+}
+
+
+void
+ShowBBMVHistogram(fpointer)
+    FILE *fpointer;
+{
+    register int x, y;
+    int *columnTotals;
+    int rowTotal;
+
+    fprintf(fpointer, "B-frame Backwards:\n");
+
+    columnTotals = (int *) calloc(2*searchRangeB+3, sizeof(int));
+
+#ifdef COMPLETE_DISPLAY
+    fprintf(fpointer, "    ");
+    for ( y = 0; y < 2*searchRangeB+3; y++ ) {
+        fprintf(fpointer, "%3d ", y-searchRangeB-1);
+    }
+    fprintf(fpointer, "\n");
+#endif
+
+    for ( x = 0; x < 2*searchRangeB+3; x++ ) {
+#ifdef COMPLETE_DISPLAY
+        fprintf(fpointer, "%3d ", x-searchRangeB-1);
+#endif
+        rowTotal = 0;
+        for ( y = 0; y < 2*searchRangeB+3; y++ ) {
+            fprintf(fpointer, "%3d ", bbmvHistogram[x][y]);
+            rowTotal += bbmvHistogram[x][y];
+            columnTotals[y] += bbmvHistogram[x][y];
+        }
+#ifdef COMPLETE_DISPLAY
+        fprintf(fpointer, "%4d\n", rowTotal);
+#else
+        fprintf(fpointer, "\n");
+#endif
+    }
+
+#ifdef COMPLETE_DISPLAY
+    fprintf(fpointer, "Tot ");
+    for ( y = 0; y < 2*searchRangeB+3; y++ ) {
+        fprintf(fpointer, "%3d ", columnTotals[y]);
+    }
+#endif
+    fprintf(fpointer, "\n");
+}
+
+
+void
+ShowBFMVHistogram(fpointer)
+    FILE *fpointer;
+{
+    register int x, y;
+    int *columnTotals;
+    int rowTotal;
+
+    fprintf(fpointer, "B-frame Forwards:\n");
+
+    columnTotals = (int *) calloc(2*searchRangeB+3, sizeof(int));
+
+#ifdef COMPLETE_DISPLAY
+    fprintf(fpointer, "    ");
+    for ( y = 0; y < 2*searchRangeB+3; y++ ) {
+        fprintf(fpointer, "%3d ", y-searchRangeB-1);
+    }
+    fprintf(fpointer, "\n");
+#endif
+
+    for ( x = 0; x < 2*searchRangeB+3; x++ ) {
+#ifdef COMPLETE_DISPLAY
+        fprintf(fpointer, "%3d ", x-searchRangeB-1);
+#endif
+        rowTotal = 0;
+        for ( y = 0; y < 2*searchRangeB+3; y++ ) {
+            fprintf(fpointer, "%3d ", bfmvHistogram[x][y]);
+            rowTotal += bfmvHistogram[x][y];
+            columnTotals[y] += bfmvHistogram[x][y];
+        }
+#ifdef COMPLETE_DISPLAY
+        fprintf(fpointer, "%4d\n", rowTotal);
+#else
+        fprintf(fpointer, "\n");
+#endif
+    }
+
+#ifdef COMPLETE_DISPLAY
+    fprintf(fpointer, "Tot ");
+    for ( y = 0; y < 2*searchRangeB+3; y++ ) {
+        fprintf(fpointer, "%3d ", columnTotals[y]);
+    }
+#endif
+    fprintf(fpointer, "\n");
+}
+
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /u/smoot/md/mpeg_encode/RCS/psearch.c,v 1.9 1995/01/19 23:09:12 eyhung Exp $
+ *  $Log: psearch.c,v $
+ * Revision 1.9  1995/01/19  23:09:12  eyhung
+ * Changed copyrights
+ *
+ * Revision 1.9  1995/01/19  23:09:12  eyhung
+ * Changed copyrights
+ *
+ * Revision 1.8  1994/12/07  00:40:36  smoot
+ * Added seperate P and B search ranges
+ *
+ * Revision 1.7  1994/11/12  02:09:45  eyhung
+ * full pixel bug
+ * fixed on lines 512 and 563
+ *
+ * Revision 1.6  1994/03/15  00:27:11  keving
+ * nothing
+ *
+ * Revision 1.5  1993/12/22  19:19:01  keving
+ * nothing
+ *
+ * Revision 1.4  1993/07/22  22:23:43  keving
+ * nothing
+ *
+ * Revision 1.3  1993/06/30  20:06:09  keving
+ * nothing
+ *
+ * Revision 1.2  1993/06/03  21:08:08  keving
+ * nothing
+ *
+ * Revision 1.1  1993/03/02  18:27:05  keving
+ * nothing
+ *
+ */
+
+
diff --git a/converter/ppm/ppmtompeg/psocket.c b/converter/ppm/ppmtompeg/psocket.c
new file mode 100644
index 00000000..707f1d84
--- /dev/null
+++ b/converter/ppm/ppmtompeg/psocket.c
@@ -0,0 +1,452 @@
+/*===========================================================================*
+                              psocket.c
+==============================================================================
+
+   low level communication facilities for Ppmtompeg parallel operation
+
+   By Bryan Henderson 2004.10.13.  Contributed to the public domain by
+   its author.
+
+============================================================================*/
+
+#define _XOPEN_SOURCE 500 /* Make sure stdio.h contains pclose() */
+/* _ALL_SOURCE is needed on AIX to make the C library include the 
+   socket services (e.g. define struct sockaddr) 
+
+   Note that AIX standards.h actually sets feature declaration macros such
+   as _XOPEN_SOURCE, unless they are already set.
+*/
+#define _ALL_SOURCE
+#define __EXTENSIONS__
+  /* __EXTENSIONS__ is for a broken Sun C library (uname SunOS kosh 5.8 
+     generic_108528-16 sun4u sparc).  When you define _XOPEN_SOURCE,
+     it's vnode.h and resource.h fail to define some data types that they
+     need (e.g. timestruct_t).  But with __EXTENSIONS__, they declare the
+     needed types anyway.  Our #include <sys/socket.h> causes the broken
+     header files to get included.
+  */
+
+/* On AIX, pm_config.h includes standards.h, which expects to be included
+   after feature declaration macros such as _XOPEN_SOURCE.  So we include
+   pm_config.h as late as possible.
+*/
+
+#include "pm_config.h" /* For POSIX_IS_IMPLIED */
+
+#ifdef POSIX_IS_IMPLIED
+/* The OpenBSD C library, at least, is broken in that when _XOPEN_SOURCE
+   is defined, its sys/socket.h refers to types "u_char", etc. but does
+   not define them.  But it is also one of the C libraries where
+   POSIX is implied so that we don't need to define _XOPEN_SOURCE in order
+   to get the POSIX routines such as pclose() defined.  So we circumvent
+   the problem by undefining _XOPEN_SOURCE:
+*/
+#undef _XOPEN_SOURCE
+#endif
+
+#include <stdarg.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "pm.h"
+#include "pm_c_util.h"
+#include "nstring.h"
+
+#include "gethostname.h"
+
+#include "psocket.h"
+
+
+/* We use type socklenx_t where we should use socklen_t from the C
+  library, but older systems don't have socklen_t.  And there doesn't
+  appear to be any way for the preprocessor to know whether it exists
+  or not.  On older systems with no socklen_t, a message length is a
+  signed integer, but on modern systems, socklen_t is an unsigned
+  integer.  Until we have some kind of build-time check for the existence
+  of socklen_t, we just use this socklenx_t, which is an unsigned
+  integer, and accept compiler warnings on older system.
+  -Bryan 2001.04.22.
+*/
+typedef unsigned int socklenx_t;
+
+#ifndef SOMAXCONN
+#define SOMAXCONN 5
+#endif
+
+
+
+static void PM_GNU_PRINTF_ATTR(1,2)
+errorExit(const char format[], ...) {
+
+    const char * const hostname = GetHostName();
+
+    va_list args;
+
+    va_start(args, format);
+
+    fprintf(stderr, "%s: FATAL ERROR.  ", hostname);
+    strfree(hostname);
+    vfprintf(stderr, format, args);
+    fputc('\n', stderr);
+
+    exit(1);
+
+    va_end(args);
+}
+
+
+
+static void
+unmarshallInt(unsigned char const buffer[],
+              int *         const valueP) {
+/*----------------------------------------------------------------------------
+  Interpret a number which is formatted for one of our network packets.
+
+  To wit, 32 bit big-endian pure binary.
+-----------------------------------------------------------------------------*/
+    union {
+        uint32_t value;
+        unsigned char bytes[4];
+    } converter;
+
+    memcpy(&converter.bytes, buffer, 4);
+
+    /* Note that contrary to what the C data types suggest, ntohl() is
+       a 32 bit converter, even if a C "long" is bigger than that.
+    */
+    *valueP = ntohl(converter.value);
+}
+
+
+
+static void
+safeRead(int             const fd, 
+         unsigned char * const buf, 
+         unsigned int    const nbyte) {
+/*----------------------------------------------------------------------------
+    Safely read from file 'fd'.  Keep reading until we get
+    'nbyte' bytes.
+-----------------------------------------------------------------------------*/
+    unsigned int numRead;
+
+    numRead = 0;  /* initial value */
+
+    while (numRead < nbyte) {
+        int const result = read(fd, &buf[numRead], nbyte-numRead);
+
+        if (result == -1)
+            errorExit("read (of %u bytes (total %u) ) returned "
+                      "errno %d (%s)",
+                      nbyte-numRead, nbyte, errno, strerror(errno));
+        else 
+            numRead += result;
+    }
+}
+
+
+
+void
+ReadBytes(int             const fd,
+          unsigned char * const buf,
+          unsigned int    const nbyte) {
+
+    safeRead(fd, buf, nbyte);
+}
+
+
+
+void
+ReadInt(int   const socketFd,
+        int * const valueP) {
+
+    unsigned char buffer[4];
+
+    safeRead(socketFd, buffer, sizeof(buffer));
+
+    unmarshallInt(buffer, valueP);
+}
+
+
+
+static void
+marshallInt(int              const value,
+            unsigned char (* const bufferP)[]) {
+/*----------------------------------------------------------------------------
+   Put the number 'value' into the buffer at *bufferP in the form required
+   for one of our network packets.
+
+   To wit, 32 bit big-endian pure binary.
+-----------------------------------------------------------------------------*/
+    union {
+        uint32_t value;
+        unsigned char bytes[4];
+    } converter;
+
+    unsigned char testbuffer[4];
+
+    /* Note that contrary to what the C data types suggest, htonl() is
+       a 32 bit converter, even if a C "long" is bigger than that.
+    */
+    converter.value = htonl(value);
+
+    (*bufferP)[0] = 7;
+    memcpy(testbuffer, &converter.bytes, 4);
+    memcpy(*bufferP, &converter.bytes, 4);
+}
+
+
+
+static void
+safeWrite(int             const fd, 
+          unsigned char * const buf, 
+          unsigned int    const nbyte) {
+/*----------------------------------------------------------------------------
+  Safely write to file 'fd'.  Keep writing until we write 'nbyte'
+  bytes.
+-----------------------------------------------------------------------------*/
+    unsigned int numWritten;
+
+    numWritten = 0;  /* initial value */
+
+    while (numWritten < nbyte) {
+        int const result = write(fd, &buf[numWritten], nbyte-numWritten);
+
+        if (result == -1) 
+            errorExit("write (of %u bytes (total %u) ) returned "
+                      "errno %d (%s)",
+                      nbyte-numWritten, nbyte, errno, strerror(errno));
+        numWritten += result;
+    }
+}
+
+
+
+void
+WriteBytes(int             const fd,
+           unsigned char * const buf,
+           unsigned int    const nbyte) {
+
+    safeWrite(fd, buf, nbyte);
+}
+
+
+
+void
+WriteInt(int const socketFd,
+         int const value) {
+
+    unsigned char buffer[4];
+
+    marshallInt(value, &buffer);
+
+    safeWrite(socketFd, buffer, sizeof(buffer));
+}
+
+
+
+void
+ConnectToSocket(const char *      const machineName, 
+                int               const portNum, 
+                struct hostent ** const hostEnt,
+                int *             const socketFdP,
+                const char **     const errorP) {
+/*----------------------------------------------------------------------------
+   Create a socket and connect it to the specified TCP endpoint.
+
+   That endpoint is fundamentally defined by 'machineName' and
+   'portNum', but *hostEnt is the address of a host entry that caches
+   the results of the host name lookup.  If *hostEnt is non-null, we
+   use it.  If *hostEnt is NULL, we look up the information and update
+   **hostEnt.
+-----------------------------------------------------------------------------*/
+    int rc;
+    
+    *errorP = NULL;  /* initial value */
+
+    if ((*hostEnt) == NULL) {
+        (*hostEnt) = gethostbyname(machineName);
+        if ((*hostEnt) == NULL)
+            asprintfN(errorP, "Couldn't get host by name (%s)", machineName);
+    }
+    if (!*errorP) {
+        rc = socket(AF_INET, SOCK_STREAM, 0);
+        if (rc < 0)
+            asprintfN(errorP, "socket() failed with errno %d (%s)", 
+                      errno, strerror(errno));
+        else {
+            int const socketFd = rc;
+            
+            int rc;
+            unsigned short tempShort;
+            struct sockaddr_in  nameEntry;
+            
+            nameEntry.sin_family = AF_INET;
+            memset((void *) nameEntry.sin_zero, 0, 8);
+            memcpy((void *) &(nameEntry.sin_addr.s_addr),
+                   (void *) (*hostEnt)->h_addr_list[0],
+                   (size_t) (*hostEnt)->h_length);
+            tempShort = portNum;
+            nameEntry.sin_port = htons(tempShort);
+            
+            rc = connect(socketFd, (struct sockaddr *) &nameEntry,
+                         sizeof(struct sockaddr));
+            
+            if (rc != 0)
+                asprintfN(errorP, 
+                          "connect() to host '%s', port %d failed with "
+                          "errno %d (%s)",
+                          machineName, portNum, errno, strerror(errno));
+            else {
+                *errorP = NULL;
+                *socketFdP = socketFd;
+            }
+            if (*errorP)
+                close(socketFd);
+        }
+    }
+}
+
+
+
+static bool
+portInUseErrno(int const testErrno) {
+/*----------------------------------------------------------------------------
+   Return TRUE iff 'testErrno' is what a bind() would return if one requestd
+   a port number that is unavailable (but other port numbers might be).
+-----------------------------------------------------------------------------*/
+    bool retval;
+
+    switch (testErrno) {
+    case EINVAL:
+    case EADDRINUSE:
+    case EADDRNOTAVAIL:
+        retval = TRUE;
+        break;
+    default:
+        retval = FALSE;
+    }
+    return retval;
+}
+
+
+
+static void
+bindToUnusedPort(int              const socketFd,
+                 unsigned short * const portNumP,
+                 const char **    const errorP) {
+    
+    bool foundPort;
+    unsigned short trialPortNum;
+
+    *errorP = NULL;  /* initial value */
+
+    for (foundPort = FALSE, trialPortNum = 2048; 
+         !foundPort && trialPortNum < 16384 && !*errorP; 
+         ++trialPortNum) {
+        
+        struct sockaddr_in nameEntry;
+        int rc;
+        
+        memset((char *) &nameEntry, 0, sizeof(nameEntry));
+        nameEntry.sin_family = AF_INET;
+        nameEntry.sin_port   = htons(trialPortNum);
+
+        rc = bind(socketFd, (struct sockaddr *) &nameEntry,
+                  sizeof(struct sockaddr));
+
+        if (rc == 0) {
+            foundPort = TRUE;
+            *portNumP = trialPortNum;
+        } else if (!portInUseErrno(errno))
+            asprintfN(errorP, "bind() of TCP port number %hu failed "
+                      "with errno %d (%s)", 
+                      trialPortNum, errno, strerror(errno));
+    }
+    
+    if (!*errorP && !foundPort)
+        asprintfN(errorP, "Unable to find a free port.  Every TCP port "
+                  "in the range 2048-16383 is in use");
+}
+
+
+
+void
+CreateListeningSocket(int *         const socketP,
+                      int *         const portNumP,
+                      const char ** const errorP) {
+/*----------------------------------------------------------------------------
+   Create a TCP socket and bind it to the first unused port number we
+   can find.
+
+   Return as *socketP a file handle for the socket (on which Caller can
+   listen()), and as *portNumP the TCP port number (to which Caller's
+   partner can connect).
+-----------------------------------------------------------------------------*/
+    int rc;
+    
+    rc = socket(AF_INET, SOCK_STREAM, 0);
+    if (rc < 0)
+        asprintfN(errorP,
+                  "Unable to create socket.  "
+                  "socket() failed with errno %d (%s)",
+                  errno, strerror(errno));
+    else {
+        int const socketFd = rc;
+
+        unsigned short portNum;
+
+        *socketP = socketFd;
+
+        bindToUnusedPort(socketFd, &portNum, errorP);
+        if (!*errorP) {
+            int rc;
+
+            *portNumP = portNum;
+
+            /* would really like to wait for 1+numMachines machines,
+              but this is max allowable, unfortunately
+            */
+            rc = listen(socketFd, SOMAXCONN);
+            if (rc != 0)
+                asprintfN(errorP, "Unable to listen on TCP socket.  "
+                          "listen() fails with errno %d (%s)", 
+                          errno, strerror(errno));
+        }
+        if (*errorP)
+            close(socketFd);
+    }
+}
+
+
+
+void
+AcceptConnection(int           const listenSocketFd,
+                 int *         const connectSocketFdP,
+                 const char ** const errorP) {
+
+    struct sockaddr otherSocket;
+    socklenx_t      otherSize;
+        /* This is an ugly dual-meaning variable.  As input to accept(),
+           it is the storage size of 'otherSocket'.  As output, it is the 
+           data length of 'otherSocket'.
+        */
+    int             rc;
+    
+    otherSize = sizeof(otherSocket);
+
+    rc = accept(listenSocketFd, &otherSocket, &otherSize);
+
+    if (rc < 0)
+        asprintfN(errorP, "accept() failed with errno %d (%s).  ",
+                  errno, strerror(errno));
+    else {
+        *connectSocketFdP = rc;
+        *errorP = NULL;
+    }
+}
diff --git a/converter/ppm/ppmtompeg/qtest.c b/converter/ppm/ppmtompeg/qtest.c
new file mode 100644
index 00000000..b3d26593
--- /dev/null
+++ b/converter/ppm/ppmtompeg/qtest.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/charlie-brown/project/mm/mpeg/mpeg_dist/mpeg_encode/RCS/qtest.c,v 1.5 1995/01/19 23:09:15 eyhung Exp $
+ *  $Log: qtest.c,v $
+ * Revision 1.5  1995/01/19  23:09:15  eyhung
+ * Changed copyrights
+ *
+ * Revision 1.4  1993/01/18  10:20:02  dwallach
+ * *** empty log message ***
+ *
+ * Revision 1.3  1993/01/18  10:17:29  dwallach
+ * RCS headers installed, code indented uniformly
+ *
+ * Revision 1.3  1993/01/18  10:17:29  dwallach
+ * RCS headers installed, code indented uniformly
+ *
+ */
+
+#include <stdio.h>
+#include "mtypes.h"
+#include "mproto.h"
+
+main()
+{
+    Block a;
+    FlatBlock b;
+    BitBucket *bb;
+    int i, j;
+
+    bb = new_bitbucket();
+
+    for (i = 0; i < 8; i++)
+	for (j = 0; j < 8; j++)
+	    a[i][j] = rand() % 100;
+    mp_quant_zig_block(a, b, 1, 1);
+    for (i = 0; i < 64; i++)
+	printf("%6d ", b[i]);
+    printf("\n");
+
+    mp_rle_huff_block(b, bb);	/* intuititve names, huh? */
+
+    printf("Huffman output is %d bits\n", bb->totalbits);
+}
diff --git a/converter/ppm/ppmtompeg/rate.c b/converter/ppm/ppmtompeg/rate.c
new file mode 100644
index 00000000..3940956c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/rate.c
@@ -0,0 +1,986 @@
+/*============================================================================*
+ * rate.c								      *
+ *									      * 
+ *	Procedures concerned with rate control                                *
+ *									      *
+ * EXPORTED PROCEDURES:							      *
+ *      initRatecontrol()                                                     *
+ *      targetRateControl()                                                   *
+ *      updateRateControl()                                                   *
+ *      MB_RateOut()                                                          *
+ *      needQScaleChange()                                                    *
+ *      incNumBlocks()                                                        *
+ *      incQuant()                                                            *
+ *	incMacroBlockBits()                                                   *
+ *      setPictureRate()                                                      *
+ *      setBitRate()                                                          *
+ *      getBitRate()                                                          *
+ *      setBufferSize()                                                       *
+ *      getBufferSize()                                                       *
+ *                                                                            *
+ * NOTES:                                                                     *
+ *	Naming conventions follow those of MPEG-2 draft algorithm (chap. 10)  *
+ *============================================================================*/
+
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/times.h>
+
+#include "ppm.h"
+#include "nstring.h"
+
+#include "all.h"
+#include "mtypes.h"
+#include "bitio.h"
+#include "frames.h"
+#include "prototypes.h"
+#include "param.h"
+#include "mheaders.h"
+#include "fsize.h"
+#include "postdct.h"
+#include "mpeg.h"
+#include "parallel.h"
+#include "dct.h"
+#include "rate.h"
+
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+#define MAX_BIT_RATE 104857600		/* 18 digit number in units of 400 */
+#define MAX_BUFFER_SIZE 16760832        /* 10 digit number in units of 16k */
+#define DEFAULT_BUFFER_SIZE 327680      /* maximun for "constrained" bitstream */
+#define DEFAULT_VBV_FULLNESS 3          /* wait till 1/3 full */
+#define DEFAULT_PICT_RATE_CODE 5        /* code for 30 Frames/sec */
+#define DEFAULT_PICT_RATE 30            /* 30 frames per second */
+#define MAX_VBV_DELAY 32768             /* 16 digits */
+
+
+/*	  Variables from Parameter File */
+
+static int	RateControlMode = VARIABLE_RATE;
+static int32 buffer_size = DEFAULT_BUFFER_SIZE;
+static int32 bit_rate = -1;
+
+static bool wantVbvUnderflowWarning;
+static bool wantVbvOverflowWarning;
+
+/*   Variables for the VBV buffer defined in MPEG specs */
+static unsigned int VBV_remainingDelay; 
+    /* delay in units of 1/90000 seconds */
+static int32 VBV_buffer = 0;	  /* fullness of the theoretical VBV buffer */
+static int32 bufferFillRate = 0;    /* constant rate at which buffer filled */
+static int32 frameDelayIncrement = 0;	/* number of "delay" units/Frame */
+
+/*  Global complexity measure variables */
+static int Xi, Xp, Xb;  /*  Global complexity measure  */
+
+static int Si, Sp, Sb;  /*  Total # bits for last pict of type (Overhead?) */
+
+static float Qi, Qp, Qb; /* avg quantizaton for last picture of type  */
+     
+/*  Target bit allocations for each type of picture*/
+int Ti, Tp, Tb;
+
+int current_Tx;	/* allocation for current frame */
+
+/*  Count of number of pictures of each type remaining */
+int GOP_X;
+int GOP_I;
+int GOP_P;
+int GOP_B;
+
+int Nx = 0;
+int Ni = 0;
+int Np = 0;
+int Nb = 0;
+
+/*   Counters used while encoding frames   */
+
+int rc_numBlocks = 0;
+int rc_totalQuant = 0;
+int rc_bitsThisMB;
+int rc_totalMBBits;
+int rc_totalFrameBits;
+int rc_totalOverheadBits = 0;
+
+
+/*	Want to print out Macroblock info every Nth MB */
+int RC_MB_SAMPLE_RATE = 0;
+
+static float Ki = .7;
+static float Kp = 1;
+static float Kb = 1.4;
+static int rc_R;
+static int rc_G;
+
+/*   Rate Control variables   */
+
+/*   Virtual buffers for each frame type */
+static int d0_i;   /* Initial fullnesses */
+static int d0_p;
+static int d0_b;
+
+static int lastFrameVirtBuf;   /* fullness after last frame of this type */
+static int currentVirtBuf;     /* fullness during current encoding*/
+
+static int MB_cnt = -1;	       /* Number of MB's in picture */
+
+static int rc_Q;               /* reference quantization parameter */
+
+static int reactionParameter;  /*  Reaction parameter */
+
+/*	Adaptive Quantization variables */
+static int act_j;              /*  spatial activity measure */
+static float N_act;            /*  Normalized spacial activity */
+static int avg_act;	   /*  average activity value in last picture encoded */
+static int total_act_j;	       /*  Sum of activity values in current frame */
+
+static int var_sblk;	       /* sub-block activity */
+static int P_mean;	       /* Mean value of pixels in 8x8 sub-block */
+
+static int mquant;	       /* Raw Quantization value */
+static int Qscale;	       /* Clipped, truncated quantization value */
+
+
+
+/*  Output-related variables */
+#ifdef RC_STATS_FILE
+static FILE *RC_FILE;
+#endif
+
+static const char * const Frame_header1 = 
+"  Fm         #     Bit      GOP                    V                ";
+static const char * const Frame_header2 = 
+"   #  type   MBs   Alloc    left  Ni Np Nb  N_act  buff   Q_rc Qscale";
+static const char * const Frame_header3 = 
+"----     -  ----  ------ -------  -- -- --  -----  ------ ----   ----";
+static const char * const Frame_trailer1 = 
+"                      avg          virt     %    GOP      %     VBV";
+static const char * const Frame_trailer2 = 
+"    Sx    Qx      Xx  act N_act  buffer alloc    left  left     buf  delay";
+static const char * const Frame_trailer3 = 
+"------ --.-- -------  --- --.-- -------   --- -------   --- ------- ------";
+
+static const char * const MB_header1 = 
+"MB#  #bits  Q mqt     Dj  Q_j   actj  N_act  totbits b/MB %alloc %done";
+static const char * const MB_header2 = 
+"---  ----- -- --- ------  ---  -----  --.--   ------ ----    ---   ---";
+
+static char rc_buffer[101];
+
+/*	EXTERNAL Variables  */
+extern char *framePattern;
+extern int framePatternLen;
+
+
+/*===============================*
+ * INTERNAL PROCEDURE prototypes *
+ *===============================*/
+
+int initGOPRateControl _ANSI_ARGS_((void));
+int determineMBCount _ANSI_ARGS_((void));
+void checkBufferFullness _ANSI_ARGS_((int count));
+void checkSpatialActivity _ANSI_ARGS_((Block blk0, Block blk1, Block blk2, Block blk3));
+void incNumBlocks _ANSI_ARGS_((int num));
+void calculateVBVDelay _ANSI_ARGS_((int num));
+int BlockExperiments  _ANSI_ARGS_((int16 *OrigBlock, int16 *NewBlock, int control));
+     
+     
+
+static void
+analyzePattern(const char *  const framePattern,
+               int           const framePatternLen,
+               int *         const gop_xP,
+               int *         const gop_iP,
+               int *         const gop_pP,
+               int *         const gop_bP,
+               const char ** const errorP) {
+
+    unsigned int i;
+
+    /*  Initialize Pattern info */
+    *gop_xP = framePatternLen;    
+
+    for (i = 0, *gop_iP = 0, *gop_pP = 0, *gop_bP = 0, *errorP = NULL;
+         i < framePatternLen && !*errorP; 
+         ++i) {
+        switch(framePattern[i]) {
+        case 'i': ++*gop_iP; break;
+        case 'p': ++*gop_pP; break;
+        case 'b': ++*gop_bP; break;
+        default:
+            asprintfN(errorP, "Bad pattern - not composed of i, p, and b");
+        }
+    }
+    assert(*gop_xP == *gop_iP + *gop_pP + *gop_bP);
+}
+  
+
+
+/*===========================================================================*
+ *
+ * initRateControl
+ *
+ *	initialize the allocation parameters.
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:   many global variables 
+ *
+ * NOTES:  Get rid of the redundant pattern stuff!!
+ *===========================================================================*/
+int
+initRateControl(bool const wantUnderflowWarning,
+                bool const wantOverflowWarning) {
+    int result;
+    const char * error;
+
+    wantVbvUnderflowWarning = wantUnderflowWarning;
+    wantVbvOverflowWarning  = wantOverflowWarning;
+
+    DBG_PRINT(("Initializing Allocation Data\n"));
+  
+#ifdef RC_STATS_FILE
+    RC_FILE = fopen("RC_STATS_FILE", "w");
+    if ( RC_FILE  == NULL) {
+        DBG_PRINT(("Open of RC file failed, using stderr\n"));
+        RC_FILE = stderr;
+        fprintf(RC_FILE, "Open of RC file failed, using stderr\n");
+        fflush(RC_FILE);
+    }
+#endif
+
+    VBV_remainingDelay = 0;
+  
+    analyzePattern(framePattern, framePatternLen,
+                   &GOP_X, &GOP_I, &GOP_P, &GOP_B, &error);
+
+    if (error) {
+        pm_message("Unable to set up rate control.  Switching to variable.  "
+                   "%s", error);
+        strfree(error);
+        RateControlMode = VARIABLE_RATE;
+        return -1;
+    }
+
+
+    /* Initializing GOP bit allocation */	
+    rc_R = 0;
+    rc_G = (bit_rate * GOP_X/frameRateRounded);
+  
+    /*   Initialize the "global complexity measures" */
+    Xi = (160 * bit_rate/115);
+    Xp = (60 * bit_rate/115);
+    Xb = (42 * bit_rate/115);
+  
+    /*   Initialize MB counters */
+    rc_totalMBBits= rc_bitsThisMB= rc_totalFrameBits=rc_totalOverheadBits = 0;
+    rc_numBlocks = rc_totalQuant = 0;
+  
+    /*   init virtual buffers  */
+    reactionParameter = (2 * bit_rate / frameRateRounded);
+    d0_i = (10 * reactionParameter / 31);
+    d0_p = (Kp * d0_i);
+    d0_b = (Kb * d0_i);
+  
+    lastFrameVirtBuf = d0_i;	/*  start with I Frame */
+    rc_Q = lastFrameVirtBuf  * 31 / reactionParameter;
+  
+    /*   init spatial activity measures */
+    avg_act = 400;		/* Suggested initial value */
+    N_act = 1;
+  
+    mquant = rc_Q * N_act;
+  
+    frameDelayIncrement = (90000 / frameRateRounded);
+        /* num of "delay" units per frame */
+    bufferFillRate = bit_rate / frameRateRounded; 
+        /* VBV buf fills at constant rate */
+    VBV_buffer = buffer_size;
+    DBG_PRINT(("VBV- delay: %d, fill rate: %d, delay/Frame: %d units, "
+               "buffer size: %d\n",
+               VBV_remainginDelay, bufferFillRate, frameDelayIncrement, 
+               buffer_size));
+  
+    result = initGOPRateControl();
+  
+    return result;
+}
+
+/*===========================================================================*
+ *
+ * initGOPRateControl
+ *
+ *		(re)-initialize the RC for the a new Group of Pictures.
+ *	New bit allocation, but carry over complexity measures.
+ *
+ * RETURNS:	nothing
+ *
+ * SIDE EFFECTS:   many global variables 
+ *
+ *===========================================================================*/
+int
+initGOPRateControl()
+{
+    DBG_PRINT(("Initializing new GOP\n"));
+  
+    Nx = GOP_X;
+    Ni = GOP_I;
+    Np = GOP_P;
+    Nb = GOP_B;
+  
+    rc_R += rc_G;
+  
+    DBG_PRINT(("bufsize: %d, bitrate: %d, pictrate: %d, GOP bits: %d\n",
+               buffer_size, bit_rate, frameRateRounded, rc_R));
+    DBG_PRINT(("Xi: %d, Xp: %d, Xb: %d Nx: %d, Ni: %d, Np: %d, Nb: %d\n",
+               Xi, Xp, Xb, Nx,Ni,Np,Nb));
+    DBG_PRINT(("d0_i: %d, d0_p: %d, d0_b: %d, avg_act: %d, rc_Q: %d, "
+               "mquant: %d\n",
+               d0_i, d0_p, d0_b, avg_act, rc_Q, mquant));
+    return 1;
+}
+
+
+
+/*===========================================================================*
+ *
+ * targetRateControl
+ *
+ *      Determine the target allocation for given picture type, initiates
+ *  variables for rate control process.
+ *
+ * RETURNS:     nothing.
+ *
+ * SIDE EFFECTS:   many global variables
+ *
+ *===========================================================================*/
+void
+targetRateControl(MpegFrame * const frame) {
+
+    float temp1, minimumBits;
+    float tempX, tempY, tempZ;
+    int result;
+    int frameType;
+    const char *strPtr;
+  
+    minimumBits = (bit_rate / (8 * frameRateRounded));
+  
+    /*   Check if new GOP */
+    if (Nx == 0) {
+        initGOPRateControl();
+    }
+  
+    if (MB_cnt < 0) {MB_cnt = determineMBCount();}
+  
+    switch (frame->type) {
+    case TYPE_IFRAME:
+        frameType = 'I';
+    
+        tempX = ( (Np * Ki * Xp) / (Xi * Kp) );
+        tempY = ( (Nb * Ki * Xb) / (Xi*Kb) );
+        tempZ = Ni + tempX + tempY;
+        temp1 = (rc_R / tempZ);
+        result = (int) (temp1 > minimumBits ? temp1 :  minimumBits);
+        current_Tx = Ti = result;
+        lastFrameVirtBuf = d0_i;
+        break;
+    
+    case TYPE_PFRAME:
+        frameType = 'P';
+        tempX =  ( (Ni * Kp * Xi) / (Ki * Xp) );
+        tempY =  ( (Nb * Kp * Xb) / (Kb * Xp) );
+        tempZ = Np + tempX + tempY;
+        temp1 = (rc_R/ tempZ);
+        result = (int) (temp1 > minimumBits ? temp1 :  minimumBits);
+        current_Tx = Tp = result;
+        lastFrameVirtBuf = d0_p;
+        break;
+    
+    case TYPE_BFRAME:
+        frameType = 'B';
+        tempX =  ( (Ni * Kb * Xi) / (Ki * Xb) );
+        tempY =  ( (Np * Kb * Xp) / (Kp * Xb) );
+        tempZ = Nb + tempX + tempY;
+        temp1 = (rc_R/ tempZ);
+        result = (int) (temp1 > minimumBits ? temp1 :  minimumBits);
+        current_Tx = Tb = result;
+        lastFrameVirtBuf = d0_b;
+        break;
+    
+    default:
+        frameType = 'X';
+    }
+  
+    N_act = 1;
+    rc_Q = lastFrameVirtBuf  * 31 / reactionParameter;
+    mquant = rc_Q * N_act;
+    Qscale = (mquant > 31 ? 31 : mquant);
+    Qscale = (Qscale < 1 ? 1 : Qscale);
+  
+    /*   Print headers for Frame info */
+    strPtr = Frame_header1;
+    DBG_PRINT(("%s\n",strPtr));
+    strPtr = Frame_header2;
+    DBG_PRINT(("%s\n",strPtr));
+    strPtr = Frame_header3;
+    DBG_PRINT(("%s\n",strPtr));
+  
+    /*   Print Frame info */
+    sprintf(rc_buffer, "%4d     %1c  %4d  %6d %7d  "
+            "%2d %2d %2d   %2.2f  %6d %4d    %3d",
+            frame->id,frameType,MB_cnt,current_Tx,rc_R,Ni,Np,Nb, 
+            N_act, lastFrameVirtBuf, rc_Q, Qscale);
+  
+#ifdef RC_STATS_FILE
+    fprintf(RC_FILE,"%s\n", rc_buffer);
+    fflush(RC_FILE);
+#endif
+    DBG_PRINT(("%s\n",rc_buffer));
+  
+    /*  Print headers for Macroblock info */
+    if (RC_MB_SAMPLE_RATE) {
+        strPtr = MB_header1;
+        DBG_PRINT(("%s\n",strPtr));
+        strPtr = MB_header2;
+        DBG_PRINT(("%s\n",strPtr));
+    }
+}
+
+
+
+
+static void 
+updateVBVBuffer(int const frameBits) {
+/*----------------------------------------------------------------------------
+  Update the VBV buffer after each frame.  This theoretical buffer is
+  being filled at constant rate 'bufferFillRate' from an mpeg stream.
+  It is emptied as each frame is grabbed by the decoder.  Exception is
+  that the decoder will wait until the "delay" is over.
+
+  We mark the passing of one unit of time, in which 'frameBits' bits of
+  mpeg data were grabbed from the buffer by the decoder.
+-----------------------------------------------------------------------------*/
+    if (VBV_remainingDelay)
+        VBV_remainingDelay -= MIN(frameDelayIncrement, VBV_remainingDelay);
+    else
+        VBV_buffer -= frameBits;
+
+    VBV_buffer += bufferFillRate;
+    if (VBV_buffer < 0 && wantVbvUnderflowWarning)
+        pm_message("WARNING - VBV buffer underflow (%d)", VBV_buffer);
+    if (VBV_buffer > buffer_size && wantVbvOverflowWarning)
+        pm_message("WARNING - VBV buffer overflow (%d > %d)",
+                   VBV_buffer, buffer_size);
+}
+
+
+
+/*===========================================================================*
+ *
+ * updateRateControl
+ *
+ *      Update the statistics kept, after end of frame.  Resets
+ *  various global variables
+ *
+ * RETURNS:     nothing
+ *
+ * SIDE EFFECTS:   many global variables
+ *
+ *===========================================================================*/
+void
+updateRateControl(int const type) {
+    int totalBits, frameComplexity, pctAllocUsed, pctGOPUsed;
+    float avgQuant;
+    const char *strPtr;
+
+    totalBits = rc_totalFrameBits;
+    avgQuant = ((float) rc_totalQuant / (float) rc_numBlocks);
+    frameComplexity = totalBits * avgQuant;
+    pctAllocUsed = (totalBits *100 / current_Tx);
+    rc_R -= totalBits;
+    pctGOPUsed = (rc_R *100/ rc_G);
+  
+    avg_act = (total_act_j / MB_cnt);
+  
+    updateVBVBuffer(totalBits);
+  
+    switch (type) {
+    case TYPE_IFRAME:
+        Ti = current_Tx;
+        d0_i = currentVirtBuf;
+        Ni--;
+        Si = totalBits;
+        Qi = avgQuant;
+        Xi = frameComplexity;
+        break;
+    case TYPE_PFRAME:
+        Tp = current_Tx;
+        d0_p = currentVirtBuf;
+        Np--;
+        Sp = totalBits;
+        Qp = avgQuant;
+        Xp = frameComplexity;
+        break;
+    case TYPE_BFRAME:
+        Tb = current_Tx;
+        d0_b = currentVirtBuf;
+        Nb--;
+        Sb = totalBits;
+        Qb = avgQuant;
+        Xb = frameComplexity;
+        break;
+    }
+  
+  
+    /*  Print Frame info */
+    strPtr = Frame_trailer1;
+    DBG_PRINT(("%s\n",strPtr));
+    strPtr = Frame_trailer2;
+    DBG_PRINT(("%s\n",strPtr));
+    strPtr = Frame_trailer3;
+    DBG_PRINT(("%s\n",strPtr));
+  
+    sprintf(rc_buffer, "%6d  %2.2f  %6d  %3d  %2.2f %7d   "
+            "%3d %7d   %3d  %6d %6d",
+            totalBits, avgQuant, frameComplexity, avg_act, N_act, 
+            currentVirtBuf, pctAllocUsed, rc_R, pctGOPUsed, 
+            VBV_buffer, VBV_remainingDelay);
+#ifdef RC_STATS_FILE
+    fprintf(RC_FILE,"%s\n", rc_buffer);
+    fflush(RC_FILE);
+#endif
+    DBG_PRINT(("%s\n",rc_buffer));
+  
+    Nx--;
+    rc_totalMBBits= rc_bitsThisMB= rc_totalFrameBits=rc_totalOverheadBits = 0;
+    rc_numBlocks = rc_totalQuant = total_act_j = currentVirtBuf = 0;
+  
+    DBG_PRINT(("GOP now has %d bits remaining (%3d%%) for %d frames .. , "
+               "Ni= %d, Np= %d, Nb= %d\n", 
+               rc_R, (rc_R*100/rc_G), (Ni+Np+Nb), Ni, Np, Nb));
+  
+}
+
+
+/*===========================================================================*
+ *
+ * MB_RateOut
+ *
+ *      Prints out sampling of MB rate control data.  Every "nth" block
+ *	stats are printed, with "n" controled by global RC_MB_SAMPLE_RATE
+ *	(NB. "skipped" blocks do not go through this function and thus do not
+ *		show up in the sample )
+ *
+ * RETURNS:     nothing
+ *
+ * SIDE EFFECTS:   none
+ *
+ * NOTES:
+ *
+ *===========================================================================*/
+void
+  MB_RateOut(type)
+int type;
+{
+  int totalBits;
+  int pctUsed, pctDone;
+  int bitsThisMB;
+  int bitsPerMB;
+  
+  bitsThisMB = rc_bitsThisMB;
+  totalBits = rc_totalFrameBits;
+  bitsPerMB = (totalBits / rc_numBlocks); 
+  pctDone = (rc_numBlocks * 100/ MB_cnt); 
+  pctUsed = (totalBits *100/current_Tx);
+  
+  sprintf(rc_buffer, "%3d  %5d %2d %3d %6d  %3d %6d   %2.2f   %6d %4d    %3d   %3d\n",
+	  (rc_numBlocks - 1), bitsThisMB, Qscale, mquant, currentVirtBuf, 
+	  rc_Q, act_j, N_act, totalBits, bitsPerMB, pctUsed, pctDone);
+#ifdef RC_STATS_FILE
+  fprintf(RC_FILE, "%s", rc_buffer);
+  fflush(RC_FILE);
+#endif
+  
+  if ( (RC_MB_SAMPLE_RATE) && ((rc_numBlocks -1) % RC_MB_SAMPLE_RATE)) {
+    DBG_PRINT(("%s\n", rc_buffer));
+  } else {
+    return;
+  }
+}
+
+
+
+/*===========================================================================*
+ *
+ * incNumBlocks()
+ *
+ *
+ * RETURNS:   nothing
+ *
+ * SIDE EFFECTS:  rc_numBlocks
+ *
+ * NOTES:
+ *
+ *===========================================================================*/
+void incNumBlocks(num)
+     int num;
+{
+  rc_numBlocks += num;
+}
+
+
+/*===========================================================================*
+ *
+ * incMacroBlockBits()
+ *
+ *	Increments the number of Macro Block bits and the total of Frame
+ *  bits by the number passed.
+ *
+ * RETURNS:   nothing
+ *
+ * SIDE EFFECTS:  rc_totalMBBits
+ *
+ * NOTES:
+ *
+ *===========================================================================*/
+void incMacroBlockBits(num)
+     int num;
+{
+  rc_bitsThisMB = num;
+  rc_totalMBBits += num;
+  rc_totalFrameBits += num;
+}
+
+
+/*===========================================================================*
+ *
+ *   	needQScaleChange(current Q scale, 4 luminance blocks)
+ *
+ *
+ * RETURNS:     new Qscale
+ *
+ * SIDE EFFECTS:   
+ *
+ *===========================================================================*/
+int needQScaleChange(oldQScale, blk0, blk1, blk2, blk3)
+     int oldQScale;
+     Block blk0;
+     Block blk1;
+     Block blk2;
+     Block blk3;
+{
+  
+  /*   One more MacroBlock seen */
+  rc_numBlocks++;		/* this notes each block num in MB */
+  
+  checkBufferFullness(oldQScale);
+  
+  checkSpatialActivity(blk0, blk1, blk2, blk3);
+  
+  mquant = rc_Q * N_act;
+  Qscale = (mquant > 31 ? 31 : mquant);
+  Qscale = (Qscale < 1 ? 1 : Qscale);
+  rc_totalQuant += Qscale;
+  
+  if (oldQScale == Qscale)
+    return -1;
+  else
+    return Qscale;
+}
+
+
+/*===========================================================================*
+ *
+ * determineMBCount() 
+ *
+ *      Determines number of Macro Blocks in frame from the frame sizes
+ *	passed.
+ *
+ * RETURNS:     nothing
+ *
+ * SIDE EFFECTS:   sets the count passed
+ *
+ *===========================================================================*/
+int
+  determineMBCount ()
+{
+  int y,x;
+  
+  x = (Fsize_x +15)/16;
+  y = (Fsize_y +15)/16;
+  return  (x * y);
+}
+
+
+
+/*===========================================================================*
+ *
+ * void checkBufferFullness ()
+ *
+ *      Calculates the fullness of the virtual buffer for each
+ *  frame type.  Called before encoding each macro block.  Along
+ *  with the normalized spatial activity measure (N_act), it
+ *  determine the quantization factor for the next macroblock.
+ *
+ * RETURNS:     nothing
+ *
+ * SIDE EFFECTS:   the "currentVirtBuf" variable
+ *
+ * NOTES:
+ *
+ *===========================================================================*/
+void checkBufferFullness (oldQScale)
+     int oldQScale;
+{
+  int temp;
+  
+  temp = lastFrameVirtBuf + rc_totalFrameBits;
+  temp -=  (current_Tx * rc_numBlocks / MB_cnt);
+  currentVirtBuf = temp;
+  
+  rc_Q = (currentVirtBuf * 31 / reactionParameter);
+  return;
+}
+
+
+/*===========================================================================*
+ *
+ * void checkSpatialActivity()
+ *
+ *      Calcualtes the spatial activity for the four luminance blocks of the
+ *	macroblock.  Along with the normalized reference quantization parameter 
+ *  (rc_Q) , it determines the quantization factor for the next macroblock.
+ *
+ * RETURNS:     nothing
+ *
+ * SIDE EFFECTS:   the Adaptive quantization variables- act_j, N_act.
+ *
+ * NOTES:
+ *
+ *===========================================================================*/
+void checkSpatialActivity(blk0, blk1, blk2, blk3)
+     Block blk0;
+     Block blk1;
+     Block blk2;
+     Block blk3;
+{
+  int temp;
+  int16 *blkArray[4]; 
+  int16 *curBlock;
+  int16 *blk_ptr;
+  int var[4];
+  int i, j;
+  
+  
+  blkArray[0] = (int16 *) blk0;
+  blkArray[1] = (int16 *) blk1;
+  blkArray[2] = (int16 *) blk2;
+  blkArray[3] = (int16 *) blk3;
+  
+  
+  for (i =0; i < 4; i++) {	/* Compute the activity in each block */
+    curBlock = blkArray[i];
+    blk_ptr = curBlock;
+    P_mean = 0;
+    /*  Find the mean pixel value */
+    for (j=0; j < DCTSIZE_SQ; j ++) {
+      P_mean += *(blk_ptr++);
+      /*			P_mean += curBlock[j]; 
+				if (curBlock[j] != *(blk_ptr++)) {
+				printf("ARRAY ERROR: block %d\n", j);
+				}
+				*/
+    }
+    P_mean /= DCTSIZE_SQ;
+    
+    /*  Now find the variance  */
+    curBlock = blkArray[i];
+    blk_ptr = curBlock;
+    var[i] = 0;
+    for (j=0; j < DCTSIZE_SQ; j++) {
+#ifdef notdef
+      if (curBlock[j] != *(blk_ptr++)) {
+	printf("ARRAY ERROR: block %d\n", j);
+      }
+      temp = curBlock[j] - P_mean;
+#endif      
+      temp = *(blk_ptr++) - P_mean;
+      var[i] += (temp * temp);
+    }
+    var[i] /= DCTSIZE_SQ;
+  }
+  
+  /*  Choose the minimum variance from the 4 blocks and use as the activity */
+  var_sblk  = var[0];
+  for (i=1; i < 4; i++) {
+    var_sblk = (var_sblk < var[i] ? var_sblk : var[i]);
+  }
+  
+  
+  act_j = 1 + var_sblk;
+  total_act_j += act_j;
+  temp = (2 * act_j + avg_act);
+  N_act = ( (float) temp / (float) (act_j + 2*avg_act) );
+  
+  return;
+}
+
+
+
+
+/*============================================================================*
+ *
+ * getRateMode ()
+ *
+ *      Returns the rate mode- interpreted as either Fixed or Variable
+ *
+ * RETURNS:     integer
+ *
+ * SIDE EFFECTS:   none
+ *
+ *
+ *==========================================================================*/
+int getRateMode()
+{
+  return RateControlMode;
+}
+
+
+/*===========================================================================*
+ *
+ * setBitRate ()
+ *
+ *      Checks the string parsed from the parameter file.  Verifies
+ *  number and sets global values. MPEG standard specifies that bit rate
+ *	be rounded up to nearest 400 bits/sec.
+ *
+ * RETURNS:     nothing
+ *
+ * SIDE EFFECTS:   global variables
+ *
+ * NOTES:	Should this be in the 400-bit units used in sequence header?
+ *
+ *===========================================================================*/
+void setBitRate (const char * const charPtr)
+{
+  int rate, rnd;
+  
+  rate = atoi(charPtr);
+  if (rate > 0) {
+    RateControlMode = FIXED_RATE;
+  } else {
+    printf("Parameter File Error:  invalid BIT_RATE: \"%s\", defaults to Variable ratemode\n",
+	   charPtr);
+    RateControlMode = VARIABLE_RATE;
+    bit_rate = -1;
+  }
+  rnd = (rate % 400);
+  rate += (rnd ? 400 -rnd : 0); /* round UP to nearest 400 bps */
+  rate = (rate > MAX_BIT_RATE ? MAX_BIT_RATE : rate);
+  bit_rate = rate;
+  DBG_PRINT(("Bit rate is: %d\n", bit_rate));
+} 
+
+
+
+/*===========================================================================*
+ *
+ * getBitRate ()
+ *
+ *      Returns the bit rate read from the parameter file.  This is the
+ *  real rate in bits per second, not in 400 bit units as is written to
+ *  the sequence header.
+ *
+ * RETURNS:     int (-1 if Variable mode operation)
+ *
+ * SIDE EFFECTS:   none
+ *
+ *===========================================================================*/
+int getBitRate ()
+{
+  return bit_rate;
+}
+
+
+
+
+/*===========================================================================*
+ *
+ * setBufferSize ()
+ *
+ *      Checks the string parsed from the parameter file.  Verifies
+ *  number and sets global values.
+ *
+ * RETURNS:     nothing
+ *
+ * SIDE EFFECTS:   buffer_size global variable.
+ *
+ * NOTES:	The global is in bits, NOT the 16kb units used in sequence header
+ *
+ *===========================================================================*/
+void setBufferSize (const char * const charPtr)
+{
+  int size;
+  
+  size = atoi(charPtr);
+  size = (size > MAX_BUFFER_SIZE ? MAX_BUFFER_SIZE : size);
+  if (size > 0) {
+    size = (16*1024) * ((size + (16*1024 - 1)) / (16*1024));
+    buffer_size = size;
+  } else {
+    buffer_size = DEFAULT_BUFFER_SIZE;
+    printf("Parameter File Error:  invalid BUFFER_SIZE: \"%s\", defaults to : %d\n",
+	   charPtr, buffer_size);
+  }
+  DBG_PRINT(("Buffer size is: %d\n", buffer_size));
+}
+
+
+/*===========================================================================*
+ *
+ * getBufferSize ()
+ *
+ *      returns the buffer size read from the parameter file.  Size is
+ *  in bits- not in units of 16k as written to the sequence header.
+ *
+ * RETURNS:     int (or -1 if invalid)
+ *
+ * SIDE EFFECTS:   none
+ *
+ *===========================================================================*/
+int getBufferSize ()
+{
+  return buffer_size;
+}
+
+
+
diff --git a/converter/ppm/ppmtompeg/readframe.c b/converter/ppm/ppmtompeg/readframe.c
new file mode 100644
index 00000000..d1423c1f
--- /dev/null
+++ b/converter/ppm/ppmtompeg/readframe.c
@@ -0,0 +1,1016 @@
+/*===========================================================================*
+ * readframe.c                    
+ *                                
+ *  procedures to read in frames  
+ *                                
+ * EXPORTED PROCEDURES:           
+ *  ReadFrame                     
+ *  SetFileType                   
+ *  SetFileFormat                 
+ *                                
+ *===========================================================================*/
+
+/* COPYRIGHT INFORMATION IS AT THE END OF THIS FILE */
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#define _BSD_SOURCE   /* Make sure popen() is in stdio.h */
+#include "all.h"
+#include <time.h>
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+#include "ppm.h"
+#include "nstring.h"
+
+#include "mtypes.h"
+#include "frames.h"
+#include "prototypes.h"
+#include "parallel.h"
+#include "param.h"
+#include "input.h"
+#include "fsize.h"
+#include "rgbtoycc.h"
+#include "jpeg.h"
+#include "opts.h"
+#include "readframe.h"
+
+
+/*==================*
+ * STATIC VARIABLES *
+ *==================*/
+
+static int  fileType = BASE_FILE_TYPE;
+struct YuvLine {
+    uint8   data[3072];
+    uint8   y[1024];
+    int8    cr[1024];
+    int8    cb[1024];
+};
+
+
+/*==================*
+ * Portability      *
+ *==================*/
+#ifdef __OS2__
+  #define popen _popen
+#endif
+   
+
+/*==================*
+ * Global VARIABLES *
+ *==================*/
+
+extern boolean GammaCorrection;
+extern float GammaValue;
+extern int outputWidth,outputHeight;
+boolean resizeFrame;
+const char *CurrFile;
+
+/*===============================*
+ * INTERNAL PROCEDURE prototypes *
+ *===============================*/
+
+static void ReadEYUV _ANSI_ARGS_((MpegFrame * mf, FILE *fpointer,
+                 int width, int height));
+static void ReadAYUV _ANSI_ARGS_((MpegFrame * mf, FILE *fpointer,
+                 int width, int height));
+static void SeparateLine _ANSI_ARGS_((FILE *fpointer, struct YuvLine *lineptr,
+                     int width));
+static void ReadY _ANSI_ARGS_((MpegFrame * mf, FILE *fpointer,
+                 int width, int height));
+static void ReadSub4 _ANSI_ARGS_((MpegFrame * mf, FILE *fpointer,
+                  int width, int height));
+static void DoGamma  _ANSI_ARGS_((MpegFrame *mf, int width, int height));
+
+static void DoKillDim _ANSI_ARGS_((MpegFrame *mf, int w, int h));
+
+#define safe_fread(ptr,sz,len,fileptr)                           \
+    if ((safe_read_count=fread(ptr,sz,len,fileptr))!=sz*len) {   \
+      fprintf(stderr,"Input file too small! (%s)\n",CurrFile);   \
+      exit(1);}                                                  \
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+
+
+
+void    
+SetResize(bool const set) {
+    resizeFrame = set;
+}
+
+
+
+static void
+ReadPNM(MpegFrame * const mpegFrameP,
+        FILE *      const fp) {
+/*----------------------------------------------------------------------------
+   Read a PPM file containing one movie frame.
+-----------------------------------------------------------------------------*/
+    int pnmCols, pnmRows;
+    xelval maxval;
+    xel ** xels;
+
+    xels = ppm_readppm(fp, &pnmCols, &pnmRows, &maxval);
+    ERRCHK(mpegFrameP, "ppm_readppm");
+
+    /*
+     * if this is the first frame read, set the global frame size
+     */
+    Fsize_Note(mpegFrameP->id, pnmCols, pnmRows);
+
+    PNMtoYUV(mpegFrameP, xels, Fsize_x, Fsize_y, maxval);
+    ppm_freearray(xels, pnmRows);
+}
+
+
+
+static void
+openFile(struct inputSource * const inputSourceP,
+         unsigned int         const frameNumber,
+         const char *         const conversion, 
+         FILE **              const ifPP) {
+
+    if (inputSourceP->stdinUsed) {
+        if (fileType == ANY_FILE_TYPE)
+            pm_error(
+                "ERROR : You cannot use a converter on frames when "
+                "you supply them as Standard Input.  Either specify "
+                "INPUT_CONVERTER * in the parameter file or supply the "
+                "frames in files by specifying a directory with "
+                "INPUT_DIRECTORY in the parameter file.");
+        
+        *ifPP = stdin;
+    } else {
+        const char * fileName;
+        const char * fullFileName;
+        
+        GetNthInputFileName(inputSourceP, frameNumber, &fileName);
+        
+        asprintfN(&fullFileName, "%s/%s", currentPath, fileName);
+        
+        CurrFile = fullFileName;
+        
+        if (fileType == ANY_FILE_TYPE) {
+            char command[1024];
+            const char * convertPtr;
+            char * commandPtr;
+            const char * charPtr;
+            
+            /* replace every occurrence of '*' with fullFileName */
+            convertPtr = conversion;
+            commandPtr = command;
+            while (*convertPtr != '\0') {
+                while ((*convertPtr != '\0') && (*convertPtr != '*')) {
+                    *commandPtr = *convertPtr;
+                    ++commandPtr;
+                    ++convertPtr;
+                }
+                
+                if (*convertPtr == '*') {
+                    /* copy fullFileName */
+                    charPtr = fullFileName;
+                    while (*charPtr != '\0') {
+                        *commandPtr = *charPtr;
+                        ++commandPtr;
+                        ++charPtr;
+                    }
+                    ++convertPtr;   /* go past '*' */
+                }
+            }
+            *commandPtr = '\0';
+            
+            *ifPP = popen(command, "r");
+            if (*ifPP == NULL) {
+                pm_message(
+                    "ERROR:  Couldn't execute input conversion command "
+                    "'%s'.  errno=%d (%s)",
+                    command, errno, strerror(errno));
+                if (ioServer)
+                    pm_error("IO SERVER:  EXITING!!!");
+                else
+                    pm_error("SLAVE EXITING!!!");
+            }
+        } else {
+            *ifPP = fopen(fullFileName, "rb");
+            if (*ifPP == NULL)
+                pm_error("Couldn't open input file '%s'", fullFileName);
+            
+            if (baseFormat == JMOVIE_FILE_TYPE)
+                unlink(fullFileName);
+        }
+        strfree(fullFileName);
+        strfree(fileName);
+    }
+}
+
+
+
+static void
+closeFile(struct inputSource * const inputSourceP,
+          FILE *               const ifP) {
+
+    if (!inputSourceP->stdinUsed) {
+        if (fileType == ANY_FILE_TYPE) {
+            int rc;
+            rc = pclose(ifP);
+            if (rc != 0)
+                pm_message("WARNING:  pclose() failed with errno %d (%s)",
+                           errno, strerror(errno));
+        } else
+            fclose(ifP);
+    }
+}
+
+
+
+static bool
+fileIsAtEnd(FILE * const ifP) {
+
+    int c;
+    bool eof;
+
+    c = getc(ifP);
+    if (c == EOF) {
+        if (feof(ifP)) 
+            eof = TRUE;
+        else
+            pm_error("File error on getc() to position to image");
+    } else {
+        int rc;
+
+        eof = FALSE;
+
+        rc = ungetc(c, ifP);
+        if (rc == EOF) 
+            pm_error("File error doing ungetc() to position to image.");
+    }
+    return eof;
+}
+
+
+
+void
+ReadFrameFile(MpegFrame *  const frameP,
+              FILE *       const ifP,
+              const char * const conversion,
+              bool *       const eofP) {
+/*----------------------------------------------------------------------------
+   Read a frame from the file 'ifP'.
+
+   Return *eofP == TRUE iff we encounter EOF before we can get the
+   frame.
+-----------------------------------------------------------------------------*/
+    MpegFrame   tempFrame;
+    MpegFrame * framePtr;
+
+    /* To make this code fit Netpbm properly, we should remove handling
+       of all types except PNM and use pm_nextimage() to handle sensing
+       of end of stream.
+    */
+
+    if (fileIsAtEnd(ifP))
+        *eofP = TRUE;
+    else {
+        *eofP = FALSE;
+
+        if (resizeFrame) {
+            tempFrame.inUse = FALSE;
+            tempFrame.orig_y = NULL;
+            tempFrame.y_blocks = NULL;
+            tempFrame.decoded_y = NULL;
+            tempFrame.halfX = NULL;
+            framePtr = &tempFrame;
+        } else
+            framePtr = frameP;
+
+        switch(baseFormat) {
+        case YUV_FILE_TYPE:
+
+            /* Encoder YUV */
+            if ((strncmp (yuvConversion, "EYUV", 4) == 0) ||
+                (strncmp (yuvConversion, "UCB", 3) == 0)) 
+
+                ReadEYUV(framePtr, ifP, realWidth, realHeight);
+
+            else
+                /* Abekas-type (interlaced) YUV */
+                ReadAYUV(framePtr, ifP, realWidth, realHeight);
+
+            break;
+        case Y_FILE_TYPE:
+            ReadY(framePtr, ifP, realWidth, realHeight);
+            break;
+        case PNM_FILE_TYPE:
+            ReadPNM(framePtr, ifP);
+            break;
+        case SUB4_FILE_TYPE:
+            ReadSub4(framePtr, ifP, yuvWidth, yuvHeight);
+            break;
+        case JPEG_FILE_TYPE:
+        case JMOVIE_FILE_TYPE:
+            ReadJPEG(framePtr, ifP);
+            break;
+        default:
+            break;
+        }
+
+        if (resizeFrame)
+            Frame_Resize(frameP, &tempFrame, Fsize_x, Fsize_y, 
+                         outputWidth, outputHeight);
+    
+        if (GammaCorrection)
+            DoGamma(frameP, Fsize_x, Fsize_y);
+
+        if (kill_dim)
+            DoKillDim(frameP, Fsize_x, Fsize_y);
+
+        MotionSearchPreComputation(frameP);
+    }
+}
+
+
+
+void
+ReadFrame(MpegFrame *          const frameP, 
+          struct inputSource * const inputSourceP,
+          unsigned int         const frameNumber,
+          const char *         const conversion,
+          bool *               const endOfStreamP) {
+/*----------------------------------------------------------------------------
+   Read the given frame, performing conversion as necessary.
+-----------------------------------------------------------------------------*/
+    FILE * ifP;
+
+    openFile(inputSourceP, frameNumber, conversion, &ifP);
+
+    ReadFrameFile(frameP, ifP, conversion, endOfStreamP);
+
+    if (*endOfStreamP && !inputSourceP->stdinUsed)
+        pm_error("Premature EOF on file containing Frame %u", frameNumber);
+
+    closeFile(inputSourceP, ifP);
+}
+
+
+
+/*===========================================================================*
+ *
+ * SetFileType
+ *
+ *  set the file type to be either a base type (no conversion), or
+ *  any type (conversion required)
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    fileType
+ *
+ *===========================================================================*/
+void
+SetFileType(const char * const conversion)
+{
+    if ( strcmp(conversion, "*") == 0 ) {
+    fileType = BASE_FILE_TYPE;
+    } else {
+    fileType = ANY_FILE_TYPE;
+    }
+}
+
+
+/*===========================================================================*
+ *
+ * SetFileFormat
+ *
+ *  set the file format (PNM, YUV, JPEG)
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    baseFormat
+ *
+ *===========================================================================*/
+void
+SetFileFormat(const char * const format)
+{
+    if ( strcmp(format, "PPM") == 0 ) {
+    baseFormat = PNM_FILE_TYPE;
+    } else if ( strcmp(format, "YUV") == 0 ) {
+    baseFormat = YUV_FILE_TYPE;
+    } else if ( strcmp(format, "Y") == 0 ) {
+    baseFormat = Y_FILE_TYPE;
+    } else if ( strcmp(format, "PNM") == 0 ) {
+    baseFormat = PNM_FILE_TYPE;
+    } else if (( strcmp(format, "JPEG") == 0 ) || ( strcmp(format, "JPG") == 0 )) {
+    baseFormat = JPEG_FILE_TYPE;
+    } else if ( strcmp(format, "JMOVIE") == 0 ) {
+    baseFormat = JMOVIE_FILE_TYPE;
+    } else if ( strcmp(format, "SUB4") == 0 ) {
+    baseFormat = SUB4_FILE_TYPE;
+    } else {
+    fprintf(stderr, "ERROR:  Invalid file format:  %s\n", format);
+    exit(1);
+    }
+}
+
+
+
+FILE *
+ReadIOConvert(struct inputSource * const inputSourceP,
+              unsigned int         const frameNumber) {
+/*----------------------------------------------------------------------------
+   Do conversion; return handle to the appropriate file.
+-----------------------------------------------------------------------------*/
+    FILE    *ifp;
+    char    command[1024];
+    const char * fullFileName;
+    char *convertPtr, *commandPtr;
+    const char * fileName;
+
+    GetNthInputFileName(inputSourceP, frameNumber, &fileName);
+
+    asprintfN(&fullFileName, "%s/%s", currentPath, fileName);
+
+    if ( strcmp(ioConversion, "*") == 0 ) {
+        char buff[1024];
+        ifp = fopen(fullFileName, "rb");
+        sprintf(buff,"fopen \"%s\"",fullFileName);
+        ERRCHK(ifp, buff);
+        return ifp;
+    }
+
+    /* replace every occurrence of '*' with fullFileName */
+    convertPtr = ioConversion;
+    commandPtr = command;
+    while ( *convertPtr != '\0' ) {
+        while ( (*convertPtr != '\0') && (*convertPtr != '*') ) {
+            *commandPtr = *convertPtr;
+            commandPtr++;
+            convertPtr++;
+        }
+
+        if ( *convertPtr == '*' ) {
+            /* copy fullFileName */
+            const char * charPtr;
+            charPtr = fullFileName;
+            while ( *charPtr != '\0' ) {
+                *commandPtr = *charPtr;
+                commandPtr++;
+                charPtr++;
+            }
+
+            convertPtr++;   /* go past '*' */
+        }
+    }
+    *commandPtr = '\0';
+
+    if ( (ifp = popen(command, "r")) == NULL ) {
+        fprintf(stderr, "ERROR:  "
+                "Couldn't execute input conversion command:\n");
+        fprintf(stderr, "\t%s\n", command);
+        fprintf(stderr, "errno = %d\n", errno);
+        if ( ioServer ) {
+            fprintf(stderr, "IO SERVER:  EXITING!!!\n");
+        } else {
+            fprintf(stderr, "SLAVE EXITING!!!\n");
+        }
+        exit(1);
+    }
+
+    strfree(fullFileName);
+    strfree(fileName);
+
+    return ifp;
+}
+
+
+
+/*===========================================================================*
+ *
+ * ReadEYUV
+ *
+ *  read a Encoder-YUV file (concatenated Y, U, and V)
+ *
+ * RETURNS: mf modified
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static void
+ReadEYUV(mf, fpointer, width, height)
+    MpegFrame *mf;
+    FILE *fpointer;
+    int width;
+    int height;
+{
+    register int y;
+    uint8   junk[4096];
+    int     safe_read_count;
+
+    Fsize_Note(mf->id, width, height);
+
+    Frame_AllocYCC(mf);
+
+    for (y = 0; y < Fsize_y; y++) {         /* Y */
+    safe_fread(mf->orig_y[y], 1, Fsize_x, fpointer);
+
+    /* read the leftover stuff on the right side */
+    if ( width != Fsize_x ) {
+        safe_fread(junk, 1, width-Fsize_x, fpointer);
+    }
+    }
+
+    /* read the leftover stuff on the bottom */
+    for (y = Fsize_y; y < height; y++) {
+    safe_fread(junk, 1, width, fpointer);
+    }
+
+    for (y = 0; y < (Fsize_y >> 1); y++) {          /* U */
+    safe_fread(mf->orig_cb[y], 1, Fsize_x >> 1, fpointer);
+
+    /* read the leftover stuff on the right side */
+    if ( width != Fsize_x ) {
+        safe_fread(junk, 1, (width-Fsize_x)>>1, fpointer);
+    }
+    }
+
+    /* read the leftover stuff on the bottom */
+    for (y = (Fsize_y >> 1); y < (height >> 1); y++) {
+    safe_fread(junk, 1, width>>1, fpointer);
+    }
+
+    for (y = 0; y < (Fsize_y >> 1); y++) {          /* V */
+    safe_fread(mf->orig_cr[y], 1, Fsize_x >> 1, fpointer);
+
+    /* read the leftover stuff on the right side */
+    if ( width != Fsize_x ) {
+        safe_fread(junk, 1, (width-Fsize_x)>>1, fpointer);
+    }
+    }
+
+    /* ignore leftover stuff on the bottom */
+}
+
+/*===========================================================================*
+ *
+ * ReadAYUV
+ *
+ *  read an Abekas-YUV file
+ *
+ * RETURNS: mf modified
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static void
+ReadAYUV(mf, fpointer, width, height)
+    MpegFrame *mf;
+    FILE *fpointer;
+    int width;
+    int height;
+{
+    register int x, y;
+    struct  YuvLine line1, line2;
+    uint8   junk[4096];
+    uint8    *cbptr, *crptr;
+    int     safe_read_count;
+
+    Fsize_Note(mf->id, width, height);
+
+    Frame_AllocYCC(mf);
+
+    for (y = 0; y < Fsize_y; y += 2) {
+    SeparateLine(fpointer, &line1, width);
+    SeparateLine(fpointer, &line2, width);
+
+    /* Copy the Y values for each line to the frame */
+    for (x = 0; x < Fsize_x; x++) {
+        mf->orig_y[y][x]   = line1.y[x];
+        mf->orig_y[y+1][x] = line2.y[x];
+    }
+
+    cbptr = &(mf->orig_cb[y>>1][0]);
+    crptr = &(mf->orig_cr[y>>1][0]);
+
+    /* One U and one V for each two pixels horizontal as well */
+    /* Toss the second line of Cr/Cb info, averaging was worse,
+       so just subsample */
+    for (x = 0; x < (Fsize_x >> 1); x ++) {
+        cbptr[x] =  line1.cb[x];
+        crptr[x] =  line1.cr[x];
+
+    }
+    }
+
+    /* read the leftover stuff on the bottom */
+    for (y = Fsize_y; y < height; y++) {
+    safe_fread(junk, 1, width<<1, fpointer);
+    }
+
+}
+
+/*===========================================================================*
+ *
+ * SeparateLine
+ *
+ *  Separates one line of pixels into Y, U, and V components
+ *
+ * RETURNS: lineptr modified
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static void
+SeparateLine(fpointer, lineptr, width)
+    FILE *fpointer;
+    struct YuvLine *lineptr;
+    int width;
+{
+    uint8   junk[4096];
+    int8    *crptr, *cbptr;
+    uint8   *yptr;
+    int     num, length;
+    int     safe_read_count;
+
+
+    /* Sets the deinterlacing pattern */
+
+    /* shorthand for UYVY */
+    if (strncmp(yuvConversion, "ABEKAS", 6) == 0) {
+    strcpy(yuvConversion, "UYVY");
+
+    /* shorthand for YUYV */
+    } else if (strncmp(yuvConversion, "PHILLIPS", 8) == 0) {
+    strcpy(yuvConversion, "YUYV");
+    }
+
+    length = strlen (yuvConversion);
+
+    if ((length % 2) != 0) {
+    fprintf (stderr, "ERROR : YUV_FORMAT must represent two pixels, hence must be even in length.\n");
+    exit(1);
+    }
+
+    /* each line in 4:2:2 chroma format takes 2X bytes to represent X pixels.
+     * each line in 4:4:4 chroma format takes 3X bytes to represent X pixels.
+     * Therefore, half of the length of the YUV_FORMAT represents 1 pixel.
+     */
+    safe_fread(lineptr->data, 1, Fsize_x*(length>>1), fpointer);
+
+    /* read the leftover stuff on the right side */
+    if ( width != Fsize_x ) {
+    safe_fread(junk, 1, (width-Fsize_x)*(length>>1), fpointer);
+    }
+
+    crptr = &(lineptr->cr[0]);
+    cbptr = &(lineptr->cb[0]);
+    yptr = &(lineptr->y[0]);
+
+    for (num = 0; num < (Fsize_x*(length>>1)); num++) {
+    switch (yuvConversion[num % length]) {
+    case 'U':
+    case 'u':
+        *(cbptr++) = (lineptr->data[num]);
+        break;
+    case 'V':
+    case 'v':
+        *(crptr++) = (lineptr->data[num]);
+        break;
+    case 'Y':
+    case 'y':
+        *(yptr++) = (lineptr->data[num]);
+        break;
+    default:
+            fprintf(stderr, "ERROR: YUV_FORMAT must be one of the following:\n");
+            fprintf(stderr, "       ABEKAS\n");
+            fprintf(stderr, "       EYUV\n");
+            fprintf(stderr, "       PHILLIPS\n");
+            fprintf(stderr, "       UCB\n");
+        fprintf(stderr, "       or any even-length string consisting of the letters U, V, and Y.\n");
+            exit(1);
+        }
+    
+    }
+
+}
+
+
+/*===========================================================================*
+ *
+ * ReadY
+ *
+ *  read a Y file
+ *
+ * RETURNS: mf modified
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static void
+ReadY(mf, fpointer, width, height)
+    MpegFrame *mf;
+    FILE *fpointer;
+    int width;
+    int height;
+{
+    register int y;
+    uint8   junk[4096];
+    int     safe_read_count;
+
+    Fsize_Note(mf->id, width, height);
+
+    Frame_AllocYCC(mf);
+
+    for (y = 0; y < Fsize_y; y++) {         /* Y */
+    safe_fread(mf->orig_y[y], 1, Fsize_x, fpointer);
+
+    /* read the leftover stuff on the right side */
+    if ( width != Fsize_x ) {
+        safe_fread(junk, 1, width-Fsize_x, fpointer);
+    }
+    }
+
+    /* read the leftover stuff on the bottom */
+    for (y = Fsize_y; y < height; y++) {
+    safe_fread(junk, 1, width, fpointer);
+    }
+    
+    for (y = 0 ; y < (Fsize_y >> 1); y++) {
+      memset(mf->orig_cb[y], 128, (Fsize_x>>1));
+      memset(mf->orig_cr[y], 128, (Fsize_x>>1));
+    }
+}
+
+
+/*===========================================================================*
+ *
+ * ReadSub4
+ *
+ *  read a YUV file (subsampled even further by 4:1 ratio)
+ *
+ * RETURNS: mf modified
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+static void
+ReadSub4(mf, fpointer, width, height)
+    MpegFrame *mf;
+    FILE *fpointer;
+    int width;
+    int height;
+{
+    register int y;
+    register int x;
+    uint8   buffer[1024];
+    int     safe_read_count;
+
+    Fsize_Note(mf->id, width, height);
+
+    Frame_AllocYCC(mf);
+
+    for (y = 0; y < (height>>1); y++) {         /* Y */
+    safe_fread(buffer, 1, width>>1, fpointer);
+    for ( x = 0; x < (width>>1); x++ ) {
+        mf->orig_y[2*y][2*x] = buffer[x];
+        mf->orig_y[2*y][2*x+1] = buffer[x];
+        mf->orig_y[2*y+1][2*x] = buffer[x];
+        mf->orig_y[2*y+1][2*x+1] = buffer[x];
+    }
+    }
+
+    for (y = 0; y < (height >> 2); y++) {           /* U */
+    safe_fread(buffer, 1, width>>2, fpointer);
+    for ( x = 0; x < (width>>2); x++ ) {
+        mf->orig_cb[2*y][2*x] = buffer[x];
+        mf->orig_cb[2*y][2*x+1] = buffer[x];
+        mf->orig_cb[2*y+1][2*x] = buffer[x];
+        mf->orig_cb[2*y+1][2*x+1] = buffer[x];
+    }
+    }
+
+    for (y = 0; y < (height >> 2); y++) {           /* V */
+    safe_fread(buffer, 1, width>>2, fpointer);
+    for ( x = 0; x < (width>>2); x++ ) {
+        mf->orig_cr[2*y][2*x] = buffer[x];
+        mf->orig_cr[2*y][2*x+1] = buffer[x];
+        mf->orig_cr[2*y+1][2*x] = buffer[x];
+        mf->orig_cr[2*y+1][2*x+1] = buffer[x];
+    }
+    }
+}
+
+
+/*=====================*
+ * INTERNAL PROCEDURES *
+ *=====================*/
+
+/*===========================================================================*
+ *
+ * DoGamma
+ *
+ *  Gamma Correct the Lum values
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    Raises Y values to power gamma.
+ *
+ *===========================================================================*/
+static void
+DoGamma(mf, w, h)
+MpegFrame *mf;
+int w,h;
+{
+  static int GammaVal[256];
+  static boolean init_done=FALSE;
+  int i,j;
+
+  if (!init_done) {
+    for(i=0; i<256; i++) 
+      GammaVal[i]=(unsigned char) (pow(((double) i)/255.0,GammaValue)*255.0+0.5);
+    init_done=TRUE;
+  }
+
+  for (i=0; i< h; i++) {  /* For each line */
+    for (j=0; j<w; j++) { /* For each Y value */
+      mf->orig_y[i][j] = GammaVal[mf->orig_y[i][j]];
+    }}
+}
+
+
+
+
+/*===========================================================================*
+ *
+ * DoKillDim
+ *
+ *  Applies an input filter to small Y values.
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    Changes Y values:
+ *
+ *  Output    |                 /
+              |                /
+              |               /
+              |              !
+              |             /
+              |            !
+              |           /
+              |          -
+              |        /
+              |      --
+              |     /
+              |   --
+              | /
+              ------------------------
+                        ^ kill_dim_break
+                             ^kill_dim_end
+              kill_dim_slope gives the slope (y = kill_dim_slope * x +0)
+              from 0 to kill_dim_break                      
+ *
+ *===========================================================================*/
+
+static void
+DoKillDim(mf, w, h)
+MpegFrame *mf;
+int w,h;
+{
+  static boolean init_done=FALSE;
+  static unsigned char mapper[256];
+  register int i,j;
+  double slope, intercept;
+
+  slope = (kill_dim_end - kill_dim_break*kill_dim_slope)*1.0 /
+    (kill_dim_end - kill_dim_break);
+  intercept = kill_dim_end * (1.0-slope);
+
+  if (!init_done) {
+    for(i=0; i<256; i++) {
+      if (i >= kill_dim_end) {
+        mapper[i] = (char) i;
+      } else if (i >= kill_dim_break) {
+        mapper[i] = (char) (slope*i + intercept);
+      } else { /* i <= kill_dim_break */
+        mapper[i] = (char) floor(i*kill_dim_slope + 0.49999);
+      }
+    }
+    init_done = TRUE;
+  }
+
+  for (i=0;  i < h;  i++) {  /* For each line */
+    for (j=0;   j < w;   j++) { /* For each Y value */
+      mf->orig_y[i][j] = mapper[mf->orig_y[i][j]];
+    }}
+}
+
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*  
+ *  $Header: /n/picasso/project/mpeg/mpeg_dist/mpeg_encode/RCS/readframe.c,v 1.27 1995/08/14 22:31:40 smoot Exp $
+ *  $Log: readframe.c,v $
+ *  Revision 1.27  1995/08/14 22:31:40  smoot
+ *  reads training info from PPms now (needed for piping reads)
+ *
+ *  Revision 1.26  1995/08/07 21:48:36  smoot
+ *  better error reporting, JPG == JPEG now
+ *
+ *  Revision 1.25  1995/06/12 20:30:12  smoot
+ *  added popen for OS2
+ *
+ * Revision 1.24  1995/06/08  20:34:36  smoot
+ * added "b"'s to fopen calls to make MSDOS happy
+ *
+ * Revision 1.23  1995/05/03  10:16:01  smoot
+ * minor compile bug with static f
+ *
+ * Revision 1.22  1995/05/02  22:00:12  smoot
+ * added TUNEing, setting near-black values to black
+ *
+ * Revision 1.21  1995/03/27  21:00:01  eyhung
+ * fixed bug with some long jpeg names
+ *
+ * Revision 1.20  1995/02/02  01:05:54  eyhung
+ * Fixed aAdded error checking for stdin
+ *
+ * Revision 1.19  1995/02/01  05:01:12  eyhung
+ * Removed troubleshooting printf
+ *
+ * Revision 1.18  1995/01/31  21:08:16  eyhung
+ * Improved YUV_FORMAT strings with better algorithm
+ *
+ * Revision 1.17  1995/01/27  23:34:09  eyhung
+ * Removed temporary JPEG files created by JMOVIE input
+ *
+ * Revision 1.16  1995/01/27  21:57:43  eyhung
+ * Added case for reading original JMOVIES
+ *
+ * Revision 1.14  1995/01/24  23:47:51  eyhung
+ * Confusion with Abekas format fixed : all other YUV revisions are wrong
+ *
+ * Revision 1.13  1995/01/20  00:02:30  smoot
+ * added gamma correction
+ *
+ * Revision 1.12  1995/01/19  23:09:21  eyhung
+ * Changed copyrights
+ *
+ * Revision 1.11  1995/01/17  22:23:07  aswan
+ * AbekasYUV chrominance implementation fixed
+ *
+ * Revision 1.10  1995/01/17  21:26:25  smoot
+ * Tore our average on Abekus/Phillips reconstruct
+ *
+ * Revision 1.9  1995/01/17  08:22:34  eyhung
+ * Debugging of ReadAYUV
+ *
+ * Revision 1.8  1995/01/16  13:18:24  eyhung
+ * Interlaced YUV format (e.g. Abekas) capability added (slightly buggy)
+ *
+ * Revision 1.7  1995/01/16  06:58:23  eyhung
+ * Added skeleton of ReadAYUV (for Abekas YUV files)
+ *
+ * Revision 1.6  1995/01/13  23:22:23  smoot
+ * Added ReadY, so we can make black&white movies (how artsy!)
+ *
+ * Revision 1.5  1994/12/16  00:20:40  smoot
+ * Now errors out on too small an input file
+ *
+ * Revision 1.4  1994/11/12  02:11:59  keving
+ * nothing
+ *
+ * Revision 1.3  1994/03/15  00:27:11  keving
+ * nothing
+ *
+ * Revision 1.2  1993/12/22  19:19:01  keving
+ * nothing
+ *
+ * Revision 1.1  1993/07/22  22:23:43  keving
+ * nothing
+ *
+ */
diff --git a/converter/ppm/ppmtompeg/rgbtoycc.c b/converter/ppm/ppmtompeg/rgbtoycc.c
new file mode 100644
index 00000000..766b8902
--- /dev/null
+++ b/converter/ppm/ppmtompeg/rgbtoycc.c
@@ -0,0 +1,204 @@
+/*===========================================================================*
+ * rgbtoycc.c                                    *
+ *                                       *
+ *  Procedures to convert from RGB space to YUV space            *
+ *                                       *
+ * EXPORTED PROCEDURES:                              *
+ *  PNMtoYUV                                 *
+ *  PPMtoYUV                                 *
+ *                                       *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "pnm.h"
+#include "all.h"
+#include "frame.h"
+#include "rgbtoycc.h"
+
+
+static float *mult299, *mult587, *mult114, *mult16874, *mult33126,
+    *mult5, *mult41869, *mult08131;  /* malloc'ed */
+    /* These are tables we use for fast arithmetic */
+static pixval table_maxval = 0;
+    /* The maxval used to compute the above arrays.  Zero means
+       the above arrays don't exist yet
+    */
+
+static void
+compute_mult_tables(const pixval maxval) {
+
+    /* For speed, we do the arithmetic with eight tables that reduce a
+       bunch of multiplications and divisions to a simple table lookup.
+       
+       Because a large maxval could require a significant amount of
+       table space, we allocate the space dynamically.
+
+       If we had to compute the tables for every frame, it wouldn't be 
+       fast at all, but since all the frames normally have the same
+       maxval, we only need to compute them once.  But just in case,
+       we check each frame to see if it has a different maxval and
+       recompute the tables if so.
+    */
+    
+    if (table_maxval != maxval) {
+        /* We need to compute or re-compute the multiplication tables */
+        if (table_maxval != 0) {
+            free(mult299); free(mult587); free(mult114); free(mult16874);
+            free(mult33126); free(mult5); free(mult41869); free(mult08131);  
+        } 
+        table_maxval = maxval;
+
+        mult299   = malloc((table_maxval+1)*sizeof(float));
+        mult587   = malloc((table_maxval+1)*sizeof(float));
+        mult114   = malloc((table_maxval+1)*sizeof(float));
+        mult16874 = malloc((table_maxval+1)*sizeof(float));
+        mult33126 = malloc((table_maxval+1)*sizeof(float));
+        mult5     = malloc((table_maxval+1)*sizeof(float));
+        mult41869 = malloc((table_maxval+1)*sizeof(float));
+        mult08131 = malloc((table_maxval+1)*sizeof(float));
+
+        if (mult299 == NULL || mult587 == NULL || mult114 == NULL ||
+            mult16874 == NULL || mult33126 == NULL || mult5 == NULL ||
+            mult41869 == NULL || mult08131 == NULL) 
+            pm_error("Unable to allocate storage for arithmetic tables.\n"
+                     "We need %d bytes, which is the maxval of the input "
+                     "image, plus 1,\n"
+                     "times the storage size of a floating point value.", 
+                     8 * (table_maxval+1)*sizeof(float));
+
+        {
+            int index;
+
+            for (index = 0; index <= table_maxval; index++ ) {
+                mult299[index]   = index*0.29900  * 255 / table_maxval; 
+                mult587[index]   = index*0.58700  * 255 / table_maxval;
+                mult114[index]   = index*0.11400  * 255 / table_maxval;
+                mult16874[index] = -0.16874*index * 255 / table_maxval;
+                mult33126[index] = -0.33126*index * 255 / table_maxval;
+                mult5[index]     = index*0.50000  * 255 / table_maxval;
+                mult41869[index] = -0.41869*index * 255 / table_maxval;
+                mult08131[index] = -0.08131*index * 255 / table_maxval;
+            }
+        }
+    }
+}
+
+
+/*=====================*
+ * EXPORTED PROCEDURES *
+ *=====================*/
+
+
+void
+PNMtoYUV(MpegFrame *  const frameP,
+         xel **       const xels,
+         unsigned int const cols,
+         unsigned int const rows,
+         xelval       const maxval) {
+/*----------------------------------------------------------------------------
+   Set the raster of the MPEG frame *frameP from the libnetpbm input
+   'xels'.  Note that the raster information in a MpegFrame is in YUV
+   form.
+-----------------------------------------------------------------------------*/
+    int x, y;
+    uint8 *dy0, *dy1;
+    uint8 *dcr, *dcb;
+    pixel *src0, *src1;
+
+    compute_mult_tables(maxval);  /* This sets up mult299[], etc. */
+
+    Frame_AllocYCC(frameP);
+
+    /*
+     * okay.  Now, convert everything into YCbCr space. (the specific
+     * numbers come from the JPEG source, jccolor.c) The conversion
+     * equations to be implemented are therefore
+     *
+     * Y  =  0.29900 * R + 0.58700 * G + 0.11400 * B
+     * Cb = -0.16874 * R - 0.33126 * G + 0.50000 * B
+     * Cr =  0.50000 * R - 0.41869 * G - 0.08131 * B
+     *
+     * With Y, Cb, and Cr then normalized to the range 0 - 255.
+     */
+
+    for (y = 0; y < rows; y += 2) {
+        for (x = 0, src0 = xels[y], src1 = xels[y + 1],
+                 dy0 = frameP->orig_y[y], dy1 = frameP->orig_y[y + 1],
+                 dcr = frameP->orig_cr[y >> 1], dcb = frameP->orig_cb[y >> 1];
+             x < cols;
+             x += 2, dy0 += 2, dy1 += 2, dcr++,
+                 dcb++, src0 += 2, src1 += 2) {
+
+            *dy0 = (mult299[PPM_GETR(*src0)] +
+                    mult587[PPM_GETG(*src0)] +
+                    mult114[PPM_GETB(*src0)]);
+
+            *dy1 = (mult299[PPM_GETR(*src1)] +
+                    mult587[PPM_GETG(*src1)] +
+                    mult114[PPM_GETB(*src1)]);
+
+            dy0[1] = (mult299[PPM_GETR(src0[1])] +
+                      mult587[PPM_GETG(src0[1])] +
+                      mult114[PPM_GETB(src0[1])]);
+
+            dy1[1] = (mult299[PPM_GETR(src1[1])] +
+                      mult587[PPM_GETG(src1[1])] +
+                      mult114[PPM_GETB(src1[1])]);
+
+            *dcb = ((mult16874[PPM_GETR(*src0)] +
+                     mult33126[PPM_GETG(*src0)] +
+                     mult5[PPM_GETB(*src0)] +
+                     mult16874[PPM_GETR(*src1)] +
+                     mult33126[PPM_GETG(*src1)] +
+                     mult5[PPM_GETB(*src1)] +
+                     mult16874[PPM_GETR(src0[1])] +
+                     mult33126[PPM_GETG(src0[1])] +
+                     mult5[PPM_GETB(src0[1])] +
+                     mult16874[PPM_GETR(src1[1])] +
+                     mult33126[PPM_GETG(src1[1])] +
+                     mult5[PPM_GETB(src1[1])]) / 4) + 128;
+
+            *dcr = ((mult5[PPM_GETR(*src0)] +
+                     mult41869[PPM_GETG(*src0)] +
+                     mult08131[PPM_GETB(*src0)] +
+                     mult5[PPM_GETR(*src1)] +
+                     mult41869[PPM_GETG(*src1)] +
+                     mult08131[PPM_GETB(*src1)] +
+                     mult5[PPM_GETR(src0[1])] +
+                     mult41869[PPM_GETG(src0[1])] +
+                     mult08131[PPM_GETB(src0[1])] +
+                     mult5[PPM_GETR(src1[1])] +
+                     mult41869[PPM_GETG(src1[1])] +
+                     mult08131[PPM_GETB(src1[1])]) / 4) + 128;
+
+            DBG_PRINT(("%3d,%3d: (%3d,%3d,%3d) --> (%3d,%3d,%3d)\n", 
+                       x, y, 
+                       PPM_GETR(*src0), PPM_GETG(*src0), PPM_GETB(*src0), 
+                       *dy0, *dcb, *dcr));
+        }
+    }
+}
diff --git a/converter/ppm/ppmtompeg/specifics.c b/converter/ppm/ppmtompeg/specifics.c
new file mode 100644
index 00000000..c7bbfb5b
--- /dev/null
+++ b/converter/ppm/ppmtompeg/specifics.c
@@ -0,0 +1,688 @@
+/*===========================================================================*
+ * specifics.c								     *
+ *									     *
+ *	basic procedures to deal with the specifics file                     *
+ *									     *
+ * EXPORTED PROCEDURES:							     *
+ *	Specifics_Init							     *
+ *      Spec_Lookup                                                          *
+ *      SpecTypeLookup                                                       *
+ *									     *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "all.h"
+#include "mtypes.h"
+#include "frames.h"
+#include "frame.h"
+#include "fsize.h"
+#include "dct.h"
+#include "specifics.h"
+#include <stdio.h>
+#include <string.h>
+#include "prototypes.h"
+
+/*====================*
+ * System Information *
+ *====================*/
+
+#define CPP_LOC "/lib/cpp"
+
+/*==================*
+ * GLOBAL VARIABLES *
+ *==================*/
+
+extern boolean specificsOn;
+extern char specificsFile[];
+extern char specificsDefines[];
+FrameSpecList *fsl;
+
+/*=====================*
+ * Internal procedures *
+ *=====================*/
+
+void Parse_Specifics_File _ANSI_ARGS_((FILE *fp));
+void Parse_Specifics_File_v1 _ANSI_ARGS_((FILE *fp));
+void Parse_Specifics_File_v2 _ANSI_ARGS_((FILE *fp));
+FrameSpecList *MakeFslEntry _ANSI_ARGS_((void));
+void AddSlc _ANSI_ARGS_((FrameSpecList *c,int snum, int qs));
+Block_Specifics *AddBs _ANSI_ARGS_((FrameSpecList *c,int bnum, 
+				    boolean rel, int qs));
+FrameSpecList *MakeFslEntry _ANSI_ARGS_((void));
+#define my_upper(c) (((c>='a') && (c<='z')) ? (c-'a'+'A') : c)
+#define CvtType(x) ReallyCvt(my_upper(x))
+#define ReallyCvt(x) (x=='I' ? 1 : (x=='P')?2: ((x=='B')?3:-1))
+#define SkipToSpace(lp) while ((*lp != ' ') && (*lp != '\n') && (*lp != '\0')) lp++
+#define EndString(lp)  ((*lp == '\n') || (*lp == '\0'))
+
+/*=============================================================
+ * SPEC FILE FORMAT (version 1):
+
+Specs files are processed with the c preprecoessor, so use C style comments
+and #defines if you wish.
+
+frames, blocks, and slices are numbered from 0.
+(sorry)
+
+In order by frame number, slice number, block number
+(if you skip slices it's fine).
+Can have specifics for any frame, block, or slice.
+Format:
+version N
+  Specify the version of the specifics file format (this is 1)
+frame N T M
+  Sets frame number N to type T and Qscale M
+  (type T is I,B,P,other, other means unspec.  I recomend - )
+slice M Q
+  Sets slice M (in frame N as defined by a previous frame command)
+  to qscale Q
+block M Q
+  Sets block M to qscale Q, in frame N previously specified.
+
+Unspecified frame types are set via the last I frame set, which is forced
+to act as the first I of the GOP.
+FORCE_ENCODE_LAST_FRAME overrides specifics on the final frame type.
+Note that Qscale changes in skipped blocks will be lost!
+
+Version 2:
+frames and slices are the same as above, but Q in blocks can be relative, i.e.
++N or -N.  Clipping to 1..31 is done but sequences like
+block 1 2
+block 2 -3
+block 3 +3
+
+has undefined results (as present block 3 would be Qscale 2).
+
+In addition motion vectors can be specified:
+block M Q skip
+  Says to skip the block  (not really an MV, but....)
+block M Q bi fx fy bx by
+  Sets block M to quality Q.  It will be a bidirectional block.
+  fx/fy is the forward (like a P frame) vector, bx/y is the back
+block M Q forw fx fy
+block M Q back bx by
+  Single directional.
+
+All vectors are specified in HALF PIXEL fixed point units, i.e.
+3.5 pixels is 7
+To specify a vector but not touch the q factor, set Q to 0
+
+*=============================================================*/
+
+
+/*=============*
+ * Local State *
+ *=============*/
+
+static char version = -1;
+
+/*================================================================
+ *
+ *   Specifics_Init
+ *
+ *   Cpp's and reads in the specifics file.  Creates fsl data structure.
+ *
+ *   Returns: nothing
+ * 
+ *   Modifies: fsl, file specificsFile".cpp"
+ *
+ *================================================================
+ */
+void Specifics_Init()
+{
+  char command[1100];
+  FILE *specificsFP;
+  
+  sprintf(command, "/bin/rm -f %s.cpp", specificsFile);
+  system(command);
+  sprintf(command, "%s -P %s %s %s.cpp",
+	  CPP_LOC, specificsDefines, specificsFile, specificsFile);
+  system(command);
+  strcat(specificsFile, ".cpp");
+  if ((specificsFP = fopen(specificsFile, "r")) == NULL) {
+    fprintf(stderr, "Error with specifics file, cannot open %s\n", specificsFile);
+    exit(1);
+  }
+  printf("Specifics file: %s\n", specificsFile);
+  Parse_Specifics_File(specificsFP);
+  sprintf(command, "/bin/rm -f %s.cpp", specificsFile);
+  system(command);
+
+}
+
+
+
+
+/*================================================================
+ *
+ *   Parse_Specifics_File
+ *
+ *   Read through the file passed in creating the fsl data structure
+ *   There is a primary routine, and helpers for the specific versions.
+ *
+ *   Returns: Nothing
+ *
+ *   Modifies: fsl
+ *
+ *================================================================
+ */
+void Parse_Specifics_File(fp)
+FILE *fp;
+{
+  char line[1024], *lp;
+  int vers;
+
+  while ((fgets(line, 1023, fp)) != NULL) {
+    lp = &line[0];
+    while ((*lp == ' ') || (*lp == '\t')) lp++;
+    if (( *lp == '#' ) || (*lp=='\n')) {
+      continue;
+    }
+
+    switch (my_upper(*lp)) {
+    case 'F': case 'S': case 'B':
+      fprintf(stderr, "Must specify version at beginning of specifics file\n");
+      exit(0);
+      break;
+    case 'V':
+      lp += 7;
+      if (1 != sscanf(lp, "%d", &vers)) {
+	fprintf(stderr," Improper version line in specs file: %s\n", line);
+      } else {
+	switch (vers) {
+	case 1:
+	  version = vers;
+	  Parse_Specifics_File_v1(fp);
+	  break;
+	case 2:
+	  version = vers;
+	  Parse_Specifics_File_v2(fp);
+	  break;
+	default:
+	  fprintf(stderr, "Improper version line in specs file: %s\n", line);
+	  fprintf(stderr, "\tSpecifics file will be IGNORED.\n");
+	  specificsOn = FALSE;
+	  return;
+	  break;
+	}}
+      break;
+    default:
+      fprintf(stderr, "Specifics file: What? *%s*\n", line);
+      break;
+    }}
+  
+}
+
+/* Version 1 */
+void Parse_Specifics_File_v1(fp)
+FILE *fp;
+{
+  char line[1024],*lp;
+  FrameSpecList *current, *new;
+  char typ; 
+  int fnum,snum, bnum, qs, newqs;
+  int num_scanned;
+
+  fsl = MakeFslEntry();
+  current = fsl;
+
+  while ((fgets(line,1023, fp)) != NULL) {
+    lp = &line[0];
+    while ((*lp == ' ') || (*lp == '\t')) lp++;
+    if (( *lp == '#' ) || (*lp=='\n')) {
+      continue;
+    }
+
+    switch (my_upper(*lp)) {
+    case 'F':
+      lp += 6;
+      sscanf(lp, "%d %c %d", &fnum, &typ, &qs);
+      if (current->framenum != -1) {
+	new=MakeFslEntry();
+	current->next = new;
+	current = new;
+      }
+      current->framenum = fnum;
+      current->frametype = CvtType(typ);
+      if (qs <= 0) qs = -1;
+      current->qscale = qs;
+      break;
+    case 'S':
+      lp += 6;
+      sscanf(lp, "%d %d", &snum, &newqs);
+      if (qs == newqs) break;
+      qs = newqs;
+      AddSlc(current, snum, qs);
+      break;
+    case 'B':
+      lp += 6;
+      num_scanned = sscanf(lp, "%d %d", &bnum, &newqs);
+      if (qs == newqs) break;
+      qs = newqs;
+      AddBs(current, bnum, FALSE, qs);
+      break;
+    case 'V':
+      fprintf(stderr, "Cannot specify version twice!  Taking first (%d)\n", version);
+      break;
+    default:
+      fprintf(stderr," What? *%s*\n", line);
+      break;
+    }}
+  
+}
+
+/* Version 2 */
+void Parse_Specifics_File_v2(fp)
+FILE *fp;
+{
+  char line[1024], *lp;
+  FrameSpecList *current, *new;
+  char typ;
+  int fnum, snum, bnum, qs, newqs;
+  int num_scanned, fx=0, fy=0, sx=0, sy=0;
+  char kind[100];
+  Block_Specifics *new_blk;
+  boolean relative;
+
+  fsl = MakeFslEntry();
+  current = fsl;
+
+  while ((fgets(line,1023,fp))!=NULL) {
+    lp = &line[0];
+    while ((*lp == ' ') || (*lp == '\t')) lp++;
+    if (( *lp == '#' ) || (*lp=='\n')) {
+      continue;
+    }
+
+    switch (my_upper(*lp)) {
+    case 'F':
+      lp += 6;
+      sscanf(lp,"%d %c %d", &fnum, &typ, &qs);
+      new = MakeFslEntry();
+      if (current->framenum != -1) {
+	current->next = new;
+	current = new;
+      }
+      current->framenum = fnum;
+      current->frametype = CvtType(typ);
+      if (qs <= 0) qs = -1;
+      current->qscale = qs;
+      break;
+    case 'S':
+      lp += 6;
+      sscanf(lp,"%d %d", &snum, &newqs);
+      if (qs == newqs) break;
+      qs = newqs;
+      AddSlc(current, snum, qs);
+      break;
+    case 'B':
+      lp += 6;
+      num_scanned = 0;
+      bnum = atoi(lp);
+      SkipToSpace(lp);
+      while ((*lp != '-') && (*lp != '+') &&
+	     ((*lp < '0') || (*lp > '9'))) lp++;
+      relative = ((*lp == '-') || (*lp == '+'));
+      newqs = atoi(lp);
+      SkipToSpace(lp);
+      if (EndString(lp)) {
+	num_scanned = 2;
+      } else {
+	num_scanned = 2+sscanf(lp, "%s %d %d %d %d", kind, &fx, &fy, &sx, &sy); 
+      }
+
+      qs = newqs;
+      new_blk = AddBs(current, bnum, relative, qs);
+      if (num_scanned > 2) {
+	BlockMV *tmp;
+	tmp = (BlockMV *) malloc(sizeof(BlockMV));
+	switch (num_scanned) {
+	case 7:
+	  tmp->typ = TYP_BOTH;
+	  tmp->fx = fx;
+	  tmp->fy = fy;
+	  tmp->bx = sx;
+	  tmp->by = sy;
+	  new_blk->mv = tmp;
+	  break;
+	case 3:
+	  tmp->typ = TYP_SKIP;
+	  new_blk->mv = tmp;
+	  break;
+	case 5:
+	  if (my_upper(kind[0]) == 'B') {
+	    tmp->typ = TYP_BACK;
+	    tmp->bx = fx;
+	    tmp->by = fy;
+	  } else {
+	    tmp->typ = TYP_FORW;
+	    tmp->fx = fx;
+	    tmp->fy = fy;
+	  }
+	  new_blk->mv = tmp;
+	  break;
+	default:
+	  fprintf(stderr,
+		  "Bug in specifics file!  Skipping short/long entry: %s\n",line);
+	  break;
+	}
+      } else {
+	new_blk->mv = (BlockMV *) NULL;
+      }
+
+      break;
+    case 'V':
+      fprintf(stderr,
+	      "Cannot specify version twice!  Taking first (%d).\n",
+	      version);
+      break;
+    default:
+      printf("What? *%s*\n",line);
+      break;
+    }}
+  
+}
+
+
+
+
+/*=================================================================
+ *
+ *     MakeFslEntry
+ *
+ *     Makes a single entry in for the fsl linked list (makes a frame)
+ *
+ *     Returns: the new entry
+ *
+ *     Modifies: nothing
+ *
+ *=================================================================
+ */
+FrameSpecList *MakeFslEntry()
+{
+  FrameSpecList *fslp;
+  fslp = (FrameSpecList *) malloc(sizeof(FrameSpecList));
+  fslp->framenum = -1;
+  fslp->slc = (Slice_Specifics *) NULL;
+  fslp->bs = (Block_Specifics *) NULL;
+  return fslp;
+}
+
+
+
+
+
+/*================================================================
+ *
+ *   AddSlc
+ *
+ *   Adds a slice to framespeclist c with values snum and qs
+ *
+ *   Returns: nothing
+ *
+ *   Modifies: fsl's structure
+ *
+ *================================================================
+ */
+void AddSlc(c, snum, qs)
+FrameSpecList *c;
+int snum,qs;
+{
+  Slice_Specifics *new;
+  static Slice_Specifics *last;
+
+  new = (Slice_Specifics *) malloc(sizeof(Slice_Specifics));
+  new->num = snum;
+  new->qscale = qs;
+  new->next = (Slice_Specifics *)NULL;
+  if (c->slc == (Slice_Specifics *)NULL) {
+    last = new;
+    c->slc = new;
+  } else {
+    last->next = new;
+    last = new;
+  }
+}
+
+
+
+
+
+/*================================================================
+ *
+ *   AddBs
+ *
+ *   Adds a sliceblock to framespeclist c with values bnum and qs
+ *
+ *   Returns: pointer to the new block spec
+ *
+ *   Modifies: fsl's structure
+ *
+ *================================================================
+ */
+Block_Specifics *AddBs(c,bnum,rel,qs)
+FrameSpecList *c;
+boolean rel;
+int bnum,qs;
+{
+  Block_Specifics *new;
+  static Block_Specifics *last;
+
+  new = (Block_Specifics *) malloc(sizeof(Block_Specifics));
+  new->num = bnum;
+  if (qs == 0) rel = TRUE;
+  new->relative = rel;
+  new->qscale = qs;
+  new->next = (Block_Specifics *)NULL;
+  new->mv = (BlockMV *) NULL;
+  if (c->bs == (Block_Specifics *)NULL) {
+    last = new;
+    c->bs = new;
+  } else {
+    last->next = new;
+    last = new;
+  }
+  return new;
+}
+
+
+
+
+
+
+/*================================================================
+ *
+ *  SpecLookup
+ *
+ *  Find out if there is any changes to be made for the qscale
+ *  at entry fn.num (which is of type typ).  Sets info to point to
+ *  motion vector info (if any), else NULL.
+ *
+ *  Returns: new qscale or -1
+ *
+ *  Modifies: *info (well, internal cache can change)
+ *
+ *================================================================
+ */
+
+int SpecLookup(fn,typ,num,info,start_qs)
+int fn,typ,num;
+BlockMV **info;
+int start_qs;
+{
+  static FrameSpecList *last = (FrameSpecList *) NULL;
+  Slice_Specifics *sptr=(Slice_Specifics *) NULL;
+  Block_Specifics *bptr=(Block_Specifics *) NULL;
+  FrameSpecList *tmp;
+  boolean found_it;
+  static int leftovers = 0;  /* Used in case of forced movement into 1..31 range */
+  
+  *info = (BlockMV * )NULL;
+  if (last == (FrameSpecList *) NULL){
+    /* No cache, try to find number fn */
+    tmp = fsl;
+    found_it = FALSE;
+    while (tmp != (FrameSpecList *) NULL) {
+      if (tmp->framenum == fn) {
+	found_it = TRUE;
+	break;
+      } else tmp = tmp->next;
+    }
+    if (!found_it) return -1;
+    last=tmp;
+  } else {
+    if (last->framenum != fn) { /* cache miss! */
+      /* first check if it is next */
+      if ((last->next != (FrameSpecList *) NULL) && 
+	  (last->next->framenum == fn)) {
+	last = last->next;
+      } else {
+	/* if not next, check from the start.
+	   (this allows people to put frames out of order,even
+	   though the spec doesnt allow it.) */
+	tmp = fsl;
+	found_it = FALSE;
+	while (tmp != (FrameSpecList *) NULL) {
+	  if (tmp->framenum==fn) {found_it = TRUE; break;}
+	  tmp = tmp->next;
+	}
+	if (!found_it) return -1;
+	last = tmp;
+      }
+    }
+  }
+  /* neither of these should ever be true, unless there is a bug above */
+  if (last == (FrameSpecList *) NULL) {
+    fprintf(stderr, "PROGRAMMER ERROR: last is null!\n");
+    return -1;
+  }
+  if (last->framenum!=fn) {
+    fprintf(stderr, "PROGRAMMER ERROR: last has wrong number!\n");
+    return -1; /* no data on it */
+  }
+  
+  switch(typ) {
+  case 0: /* Frame: num is ignored */
+    leftovers = 0;
+#ifdef BLEAH
+    printf("QSchange frame %d to %d\n", fn, last->qscale);
+#endif 
+    return last->qscale;
+    break;
+
+  case 1: /* Slice */
+    leftovers = 0;
+    /* So, any data on slices? */
+    if (last->slc == (Slice_Specifics *) NULL) return -1;
+    for (sptr = last->slc; sptr != (Slice_Specifics *) NULL; sptr = sptr->next) {
+      if (sptr->num == num) {
+#ifdef BLEAH
+	printf("QSchange Slice %d.%d to %d\n", fn, num, sptr->qscale);
+#endif
+	if (sptr->qscale == 0) return -1;
+	return sptr->qscale;
+      }
+    }
+    break;
+
+  case 2:  /* block */
+    /* So, any data on blocks? */
+    if (last->bs == (Block_Specifics *) NULL) {
+      return -1;
+    }
+    for (bptr=last->bs; bptr != (Block_Specifics *) NULL; bptr=bptr->next) {
+      if (bptr->num == num) {
+	int new_one;
+#ifdef BLEAH
+	printf("QSchange Block %d.%d to %d\n", fn, num, bptr->qscale);
+#endif
+	*info = bptr->mv;
+	if (bptr->relative) {
+	  if (bptr->qscale == 0) {
+	    /* Do nothing! */
+	    new_one = start_qs;
+	  } else {
+	    new_one = start_qs + bptr->qscale + leftovers;
+	    if (new_one < 1) {
+	      leftovers = new_one - 1;
+	      new_one = 1;
+	    } else if (new_one > 31) {
+	      leftovers = new_one - 31;
+	      new_one = 31;
+	    } else leftovers = 0;
+	  }}
+	else {
+	  new_one = bptr->qscale;
+	  leftovers = 0;
+	}
+	return new_one;
+      }
+    }
+    break;
+  default:
+    fprintf(stderr, "PROGRAMMER ERROR:  reached unreachable code in SpecLookup\n");
+    break;
+  }
+  /* no luck */
+  return -1;
+}
+     
+    
+/*================================================================
+ *
+ *  SpecTypeLookup
+ *
+ *  Find out if there is any changes to be made for the type of frame
+ *  at frame fn.
+ *
+ *  Returns: new type or -1 (unspecified)
+ *
+ *================================================================
+ */
+int SpecTypeLookup(fn)
+int fn;
+{
+  FrameSpecList *tmp;
+
+  /* try to find number fn */
+  tmp = fsl;
+  do {
+    if (tmp->framenum == fn) break;
+    else tmp = tmp->next;
+  } while (tmp != (FrameSpecList *) NULL);
+  if (tmp == (FrameSpecList *) NULL) {
+#ifdef BLEAH
+    printf("Frame %d type not specified\n", fn);
+#endif
+    return -1;
+  }
+#ifdef BLEAH
+  printf("Frame %d type set to %d\n", fn, tmp->frametype);
+#endif
+  return tmp->frametype;
+}
diff --git a/converter/ppm/ppmtompeg/subsample.c b/converter/ppm/ppmtompeg/subsample.c
new file mode 100644
index 00000000..5ec71814
--- /dev/null
+++ b/converter/ppm/ppmtompeg/subsample.c
@@ -0,0 +1,280 @@
+/*===========================================================================*
+ * subsample.c                                   *
+ *                                       *
+ *  Procedures concerned with subsampling                    *
+ *                                       *
+ * EXPORTED PROCEDURES:                              *
+ *  LumMotionErrorA                              *
+ *  LumMotionErrorB                              *
+ *  LumMotionErrorC                              *
+ *  LumMotionErrorD                              *
+ *                                       *
+ *===========================================================================*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/*==============*
+ * HEADER FILES *
+ *==============*/
+
+#include "pm_c_util.h"
+#include "all.h"
+#include "mtypes.h"
+#include "frames.h"
+#include "bitio.h"
+#include "prototypes.h"
+
+
+
+static void
+computePrevFyFx(MpegFrame * const prevFrame,
+                int         const by,
+                int         const bx,
+                vector      const m,
+                uint8 ***   const prevP,
+                int *       const fyP,
+                int *       const fxP) {
+
+    boolean const xHalf = (ABS(m.x) % 2 == 1);
+    boolean const yHalf = (ABS(m.y) % 2 == 1);
+
+    MotionToFrameCoord(by, bx, m.y/2, m.x/2, fyP, fxP);
+
+    if (xHalf) {
+        if (m.x < 0)
+            --*fxP;
+
+        if (yHalf) {
+            if (m.y < 0)
+                --*fyP;
+        
+            *prevP = prevFrame->halfBoth;
+        } else
+            *prevP = prevFrame->halfX;
+    } else if (yHalf) {
+        if (m.y < 0)
+            --*fyP;
+        
+        *prevP = prevFrame->halfY;
+    } else
+        *prevP = prevFrame->ref_y;
+}
+
+
+
+static int32
+evenColDiff(const uint8 * const macross,
+            const int32 * const currentRow) {
+
+    return 0
+        + ABS(macross[ 0] - currentRow[ 0])
+        + ABS(macross[ 2] - currentRow[ 2])
+        + ABS(macross[ 4] - currentRow[ 4])
+        + ABS(macross[ 6] - currentRow[ 6])
+        + ABS(macross[ 8] - currentRow[ 8])
+        + ABS(macross[10] - currentRow[10])
+        + ABS(macross[12] - currentRow[12])
+        + ABS(macross[14] - currentRow[14]);
+}
+
+
+
+static int32
+oddColDiff(const uint8 * const macross,
+           const int32 * const currentRow) {
+
+    return 0
+        + ABS(macross[ 1] - currentRow[ 1])
+        + ABS(macross[ 3] - currentRow[ 3])
+        + ABS(macross[ 5] - currentRow[ 5])
+        + ABS(macross[ 7] - currentRow[ 7])
+        + ABS(macross[ 9] - currentRow[ 9])
+        + ABS(macross[11] - currentRow[11])
+        + ABS(macross[13] - currentRow[13])
+        + ABS(macross[15] - currentRow[15]);
+}
+
+
+
+/*===========================================================================*
+ *
+ * LumMotionErrorA
+ *
+ *  compute the motion error for the A subsampling pattern
+ *
+ * RETURNS: the error, or some number greater if it is worse
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+int32
+LumMotionErrorA(const LumBlock * const currentBlockP,
+                MpegFrame *      const prevFrame,
+                int              const by,
+                int              const bx,
+                vector           const m,
+                int32            const bestSoFar) {
+
+    int32 diff; /* max value of diff is 255*256 = 65280 */
+    uint8 ** prev;
+    int fy, fx;
+    unsigned int rowNumber;
+
+    computePrevFyFx(prevFrame, by, bx, m, &prev, &fy, &fx);
+
+    diff = 0;  /* initial value */
+
+    for (rowNumber = 0; rowNumber < 16; rowNumber +=2) {
+        uint8 *       const macross    = &(prev[fy + rowNumber][fx]);
+        const int32 * const currentRow = currentBlockP->l[rowNumber];
+
+        diff += evenColDiff(macross, currentRow);
+
+        if (diff > bestSoFar)
+            return diff;
+    }
+    return diff;
+}
+
+
+
+/*===========================================================================*
+ *
+ * LumMotionErrorB
+ *
+ *  compute the motion error for the B subsampling pattern
+ *
+ * RETURNS: the error, or some number greater if it is worse
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+int32
+LumMotionErrorB(const LumBlock * const currentBlockP,
+                MpegFrame *      const prevFrame,
+                int              const by,
+                int              const bx,
+                vector           const m,
+                int32            const bestSoFar) {
+
+    int32 diff;  /* max value of diff is 255*256 = 65280 */
+    uint8 **prev;
+    int fy, fx;
+    unsigned int rowNumber;
+
+    computePrevFyFx(prevFrame, by, bx, m, &prev, &fy, &fx);
+
+    diff = 0;  /* initial value */
+    
+    for (rowNumber = 0; rowNumber < 16; rowNumber +=2) {
+        uint8 *       const macross    = &(prev[fy + rowNumber][fx]);
+        const int32 * const currentRow = currentBlockP->l[rowNumber];
+
+        diff += oddColDiff(macross, currentRow);
+
+        if (diff > bestSoFar)
+            return diff;
+    }
+    return diff;
+}
+
+
+/*===========================================================================*
+ *
+ * LumMotionErrorC
+ *
+ *  compute the motion error for the C subsampling pattern
+ *
+ * RETURNS: the error, or some number greater if it is worse
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+int32
+LumMotionErrorC(const LumBlock * const currentBlockP,
+                MpegFrame *      const prevFrame,
+                int              const by,
+                int              const bx,
+                vector           const m,
+                int32            const bestSoFar) {
+
+    int32 diff;        /* max value of diff is 255*256 = 65280 */
+    uint8 **prev;
+    int fy, fx;
+    unsigned int rowNumber;
+
+    computePrevFyFx(prevFrame, by, bx, m, &prev, &fy, &fx);
+
+    diff = 0;  /* initial value */
+    
+    for (rowNumber = 1; rowNumber < 16; rowNumber +=2) {
+        uint8 *       const macross    = &(prev[fy + rowNumber][fx]);
+        const int32 * const currentRow = currentBlockP->l[rowNumber];
+
+        diff += evenColDiff(macross, currentRow);
+
+        if (diff > bestSoFar)
+            return diff;
+    }
+    return diff;
+}
+
+
+/*===========================================================================*
+ *
+ * LumMotionErrorD
+ *
+ *  compute the motion error for the D subsampling pattern
+ *
+ * RETURNS: the error, or some number greater if it is worse
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+int32
+LumMotionErrorD(const LumBlock * const currentBlockP,
+                MpegFrame *      const prevFrame,
+                int              const by,
+                int              const bx,
+                vector           const m,
+                int32            const bestSoFar) {
+
+    int32 diff;     /* max value of diff is 255*256 = 65280 */
+    uint8 ** prev;
+    int fy, fx;
+    unsigned int rowNumber;
+
+    computePrevFyFx(prevFrame, by, bx, m, &prev, &fy, &fx);
+
+    diff = 0;  /* initial value */
+
+    for (rowNumber = 1; rowNumber < 16; rowNumber +=2) {
+        uint8 *       const macross    = &(prev[fy + rowNumber][fx]);
+        const int32 * const currentRow = currentBlockP->l[rowNumber];
+
+        diff += oddColDiff(macross, currentRow);
+
+        if (diff > bestSoFar)
+            return diff;
+    }
+    return diff;
+}
diff --git a/converter/ppm/ppmtompeg/tst/README b/converter/ppm/ppmtompeg/tst/README
new file mode 100644
index 00000000..4e35f6ba
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/README
@@ -0,0 +1,30 @@
+The test in this directory is derived from that in the Berkeley mpeg
+package, but we have trimmed it down for the purposes of the Netpbm
+package to save space.
+
+First of all, the Berkeley package had Berkeley YUV files for sample input
+(in the 'ts' directory).  But we converted them to JPEG to save a great 
+deal of space (even when the package is gzipped).
+
+We modified the ts.param file to work with the JPEG files and updated the
+expected results file to correspond to the JPEG input (the expected results
+are different after the JPEG conversion because JPEG conversion is lossy).
+
+We kept some of the other parameter files from the Berkeley package in
+case someone can get some use out of them, but did not update them to
+use the JPEG files.  You'll have to do that yourself.  And we removed
+the expected results files for them, since you can't generate those
+results with the inputs we have supplied.
+
+Note that JPEG input doesn't really exercise the most standard function
+of ppmtompeg.  As its name suggests, it's main purpose is to take PPM
+frames as input.  You can use jpegtoppm to create PPM input and then
+modify the parameter files if you want to test that.  We have not, however
+supplied an expected results file for that.
+
+The .mpg files are what ppmtojpeg output as the mpeg movie when we
+ran it on the jpeg files in the 'ts' directory.
+
+The .stat files are what ppmtojpeg output when we used the -stat
+option in generating the expected results.  As part of your test, use
+the -stat option and compare yours to the expected results.
diff --git a/converter/ppm/ppmtompeg/tst/gop.param b/converter/ppm/ppmtompeg/tst/gop.param
new file mode 100644
index 00000000..96900a3d
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/gop.param
@@ -0,0 +1,30 @@
+# test suite parameter file
+
+PATTERN		IBPBIBPBPB
+OUTPUT		/tmp/ts.mpg
+
+YUV_SIZE	352x240
+
+BASE_FILE_FORMAT	YUV
+INPUT_CONVERT	*
+GOP_SIZE	10
+SLICES_PER_FRAME  1
+
+INPUT_DIR	./tst/ts
+
+INPUT
+stennis.*.yuv	[30-39]
+stennis.*.yuv	[30-39]
+END_INPUT
+
+PIXEL		HALF
+RANGE		8
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	SIMPLE
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+REFERENCE_FRAME	ORIGINAL
diff --git a/converter/ppm/ppmtompeg/tst/par3.param b/converter/ppm/ppmtompeg/tst/par3.param
new file mode 100644
index 00000000..6ee6586e
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/par3.param
@@ -0,0 +1,43 @@
+# speed test parameter file
+
+PATTERN		IBBPBBPBBPBBPBB
+
+OUTPUT		/n/picasso/users/keving/encode/output/flowgard.mpg
+GOP_SIZE	30
+SLICES_PER_FRAME	15
+
+BASE_FILE_FORMAT	YUV
+YUV_SIZE	352x240
+
+INPUT_CONVERT	*
+
+INPUT_DIR	/n/picasso/users/keving/encode/input/flowg
+
+INPUT
+sflowg.*.yuv	[0-39]
+END_INPUT
+
+# quality parameters
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+# motion vector search parameters
+
+PIXEL		HALF
+
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+REFERENCE_FRAME	ORIGINAL
+
+PARALLEL_TEST_FRAMES	3
+PARALLEL_TIME_CHUNKS	30
+
+PARALLEL
+bugs-bunny	keving	~keving/encode/bin/sun/mpeg_encode
+linus		keving	~keving/encode/bin/sun/mpeg_encode
+END_PARALLEL
diff --git a/converter/ppm/ppmtompeg/tst/short.param b/converter/ppm/ppmtompeg/tst/short.param
new file mode 100644
index 00000000..9189e5d6
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/short.param
@@ -0,0 +1,31 @@
+# test suite parameter file
+
+PATTERN		IBBBPBBB
+OUTPUT		/tmp/ts.mpg
+
+YUV_SIZE	352x240
+
+BASE_FILE_FORMAT	YUV
+INPUT_CONVERT	*
+GOP_SIZE	30
+SLICES_PER_FRAME  1
+
+INPUT_DIR	../test/ts
+
+INPUT
+stennis.*.yuv	[30-37]
+END_INPUT
+
+PIXEL		HALF
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+REFERENCE_FRAME	ORIGINAL
+
+# FORCE_ENCODE_LAST_FRAME
diff --git a/converter/ppm/ppmtompeg/tst/test_all b/converter/ppm/ppmtompeg/tst/test_all
new file mode 100755
index 00000000..67b56c9c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/test_all
@@ -0,0 +1,19 @@
+#!/bin/csh -f
+cd ..
+echo "First we encode three mpegs... (note requires 5MB on /tmp)"
+rm -f /tmp/ts{,2,d}.{mpg,stat}
+./mpeg_encode -stat /tmp/ts.stat ./tst/ts.param
+./mpeg_encode -stat /tmp/ts2.stat ./tst/ts2.param
+./mpeg_encode -stat /tmp/tsd.stat ./tst/tsd.param
+
+cd tst
+
+echo "Test one - tst/ts.param"
+csh diffscript /tmp/ts.stat ts.stat /tmp/ts.mpg ts.mpg
+
+echo "Test two - tst/ts2.param (different pattern)"
+csh diffscript /tmp/ts2.stat ts2.stat /tmp/ts2.mpg ts2.mpg
+
+echo "Test three - tst/tsd.param (uses decoded frames)"
+csh diffscript /tmp/tsd.stat tsd.stat /tmp/tsd.mpg tsd.mpg
+
diff --git a/converter/ppm/ppmtompeg/tst/ts.mpg b/converter/ppm/ppmtompeg/tst/ts.mpg
new file mode 100644
index 00000000..2bc6ae6e
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts.mpg
Binary files differdiff --git a/converter/ppm/ppmtompeg/tst/ts.param b/converter/ppm/ppmtompeg/tst/ts.param
new file mode 100644
index 00000000..23e1092c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts.param
@@ -0,0 +1,29 @@
+# test suite parameter file
+
+PATTERN		IBBPBBPBBP
+OUTPUT		/tmp/ts.mpg
+
+BASE_FILE_FORMAT JPEG
+INPUT_CONVERT	*
+GOP_SIZE	30
+SLICES_PER_FRAME  1
+
+INPUT_DIR	./tst/ts
+
+INPUT
+stennis.*.jpg	[30-39]
+END_INPUT
+
+PIXEL		HALF
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+REFERENCE_FRAME	ORIGINAL
+# Default string has the date in it, bad for tests!
+USER_DATA  /dev/null
diff --git a/converter/ppm/ppmtompeg/tst/ts.stat b/converter/ppm/ppmtompeg/tst/ts.stat
new file mode 100644
index 00000000..e13be8a3
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts.stat
@@ -0,0 +1,52 @@
+MPEG ENCODER STATS (1.5b)
+------------------------
+TIME STARTED:  Thu Mar 30 19:50:43 2000
+MACHINE:  unknown
+FIRST FILE:  ./tst/ts/stennis.30.jpg
+LAST FILE:  ./tst/ts/stennis.39.jpg
+PATTERN:  ibbpbbpbbp
+GOP_SIZE:  30
+SLICES PER FRAME:  1
+RANGE:  +/-10
+PIXEL SEARCH:  HALF
+PSEARCH:  LOGARITHMIC
+BSEARCH:  CROSS2
+QSCALE:  8 10 25
+REFERENCE FRAME:  ORIGINAL
+TIME COMPLETED:  Thu Mar 30 19:50:50 2000
+Total time:  7 seconds
+
+-------------------------
+*****I FRAME SUMMARY*****
+-------------------------
+  Blocks:      330     (105385 bits)     (  319 bpb)
+  Frames:        1     (105488 bits)     (105488 bpf)     (32.2% of total)
+  Compression:   19:1     (   1.2487 bpp)
+  Seconds:          0     (   7.6923 fps)  (   649846 pps)  (     2538 mps)
+-------------------------
+*****P FRAME SUMMARY*****
+-------------------------
+  I Blocks:    110     ( 27261 bits)     (  247 bpb)
+  P Blocks:    872     (147211 bits)     (  168 bpb)
+  Skipped:       8
+  Frames:        3     (174816 bits)     (58272 bpf)     (53.3% of total)
+  Compression:   34:1     (   0.6898 bpp)
+  Seconds:          0     (   3.2967 fps)  (   278505 pps)  (     1087 mps)
+-------------------------
+*****B FRAME SUMMARY*****
+-------------------------
+  I Blocks:      1     (   156 bits)     (  156 bpb)
+  B Blocks:   1979     ( 46497 bits)     (   23 bpb)
+  B types:     227     (  21 bpb) forw    484 (  16 bpb) back    1268 (  26 bpb) bi
+  Skipped:       0
+  Frames:        6     ( 47328 bits)     ( 7888 bpf)     (14.4% of total)
+  Compression:  257:1     (   0.0934 bpp)
+  Seconds:          5     (   1.1952 fps)  (   100972 pps)  (      394 mps)
+---------------------------------------------
+Total Compression:   61:1     (   0.3880 bpp)
+Total Frames Per Second:  1.428571 (471 mps)
+CPU Time:  1.650165 fps     (544 mps)
+Total Output Bit Rate (30 fps):  983472 bits/sec
+MPEG file created in :  /tmp/ts.mpg
+
+
diff --git a/converter/ppm/ppmtompeg/tst/ts/stennis.30.jpg b/converter/ppm/ppmtompeg/tst/ts/stennis.30.jpg
new file mode 100644
index 00000000..f70e014e
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts/stennis.30.jpg
Binary files differdiff --git a/converter/ppm/ppmtompeg/tst/ts/stennis.31.jpg b/converter/ppm/ppmtompeg/tst/ts/stennis.31.jpg
new file mode 100644
index 00000000..cc88a285
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts/stennis.31.jpg
Binary files differdiff --git a/converter/ppm/ppmtompeg/tst/ts/stennis.32.jpg b/converter/ppm/ppmtompeg/tst/ts/stennis.32.jpg
new file mode 100644
index 00000000..221e22c9
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts/stennis.32.jpg
Binary files differdiff --git a/converter/ppm/ppmtompeg/tst/ts/stennis.33.jpg b/converter/ppm/ppmtompeg/tst/ts/stennis.33.jpg
new file mode 100644
index 00000000..c702a620
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts/stennis.33.jpg
Binary files differdiff --git a/converter/ppm/ppmtompeg/tst/ts/stennis.34.jpg b/converter/ppm/ppmtompeg/tst/ts/stennis.34.jpg
new file mode 100644
index 00000000..47c86806
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts/stennis.34.jpg
Binary files differdiff --git a/converter/ppm/ppmtompeg/tst/ts/stennis.35.jpg b/converter/ppm/ppmtompeg/tst/ts/stennis.35.jpg
new file mode 100644
index 00000000..ed21c16b
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts/stennis.35.jpg
Binary files differdiff --git a/converter/ppm/ppmtompeg/tst/ts/stennis.36.jpg b/converter/ppm/ppmtompeg/tst/ts/stennis.36.jpg
new file mode 100644
index 00000000..6fb7506b
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts/stennis.36.jpg
Binary files differdiff --git a/converter/ppm/ppmtompeg/tst/ts/stennis.37.jpg b/converter/ppm/ppmtompeg/tst/ts/stennis.37.jpg
new file mode 100644
index 00000000..561bd3b2
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts/stennis.37.jpg
Binary files differdiff --git a/converter/ppm/ppmtompeg/tst/ts/stennis.38.jpg b/converter/ppm/ppmtompeg/tst/ts/stennis.38.jpg
new file mode 100644
index 00000000..25ba097e
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts/stennis.38.jpg
Binary files differdiff --git a/converter/ppm/ppmtompeg/tst/ts/stennis.39.jpg b/converter/ppm/ppmtompeg/tst/ts/stennis.39.jpg
new file mode 100644
index 00000000..02b15aff
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts/stennis.39.jpg
Binary files differdiff --git a/converter/ppm/ppmtompeg/tst/ts2.param b/converter/ppm/ppmtompeg/tst/ts2.param
new file mode 100644
index 00000000..f72ba150
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts2.param
@@ -0,0 +1,33 @@
+# test suite parameter file
+
+PATTERN		IBBPBBPBB
+
+OUTPUT		/tmp/ts2.mpg
+
+YUV_SIZE	352x240
+
+BASE_FILE_FORMAT	YUV
+YUV_FORMAT	UCB
+INPUT_CONVERT	*
+GOP_SIZE	30
+SLICES_PER_FRAME  1
+
+INPUT_DIR	./tst/ts
+
+INPUT
+stennis.*.yuv	[30-39]
+END_INPUT
+
+PIXEL		HALF
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+REFERENCE_FRAME	ORIGINAL
+# Default string has the date in it, bad for tests!
+USER_DATA  /dev/null
diff --git a/converter/ppm/ppmtompeg/tst/ts2.stat b/converter/ppm/ppmtompeg/tst/ts2.stat
new file mode 100644
index 00000000..5a14ef79
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts2.stat
@@ -0,0 +1,52 @@
+MPEG ENCODER STATS (1.5)
+------------------------
+TIME STARTED:  Mon Jun 26 14:22:56 1995
+MACHINE:  odie.CS.Berkeley.EDU
+FIRST FILE:  ./tst/ts/stennis.30.yuv
+LAST FILE:  ./tst/ts/stennis.39.yuv
+PATTERN:  ibbpbbpbb
+GOP_SIZE:  30
+SLICES PER FRAME:  1
+RANGE:  +/-10
+FULL SEARCH:  0
+PSEARCH:  LOGARITHMIC
+BSEARCH:  CROSS2
+QSCALE:  8 10 25
+REFERENCE FRAME:  ORIGINAL
+TIME COMPLETED:  Mon Jun 26 14:23:11 1995
+Total time:  15 seconds
+
+-------------------------
+*****I FRAME SUMMARY*****
+-------------------------
+  Blocks:      660     (171792 bits)     (  260 bpb)
+  Frames:        2     (172008 bits)     (86004 bpf)     (60.0% of total)
+  Compression:   23:1     (   1.0180 bpp)
+  Seconds:          0     (   2.9851 fps)  (   252179 pps)  (      985 mps)
+-------------------------
+*****P FRAME SUMMARY*****
+-------------------------
+  I Blocks:     54     ( 13161 bits)     (  243 bpb)
+  P Blocks:    598     ( 69956 bits)     (  116 bpb)
+  Skipped:       8
+  Frames:        2     ( 83344 bits)     (41672 bpf)     (29.1% of total)
+  Compression:   48:1     (   0.4933 bpp)
+  Seconds:          1     (   1.2821 fps)  (   108307 pps)  (      423 mps)
+-------------------------
+*****B FRAME SUMMARY*****
+-------------------------
+  I Blocks:      5     (   391 bits)     (   78 bpb)
+  B Blocks:   1733     ( 29897 bits)     (   17 bpb)
+  B types:     250     (  11 bpb) forw    483 (  12 bpb) back    1000 (  20 bpb) bi
+  Skipped:     491
+  Frames:        6     ( 30960 bits)     ( 5160 bpf)     (10.8% of total)
+  Compression:  392:1     (   0.0611 bpp)
+  Seconds:         11     (   0.5029 fps)  (    42487 pps)  (      165 mps)
+---------------------------------------------
+Total Compression:   70:1     (   0.3392 bpp)
+Total Frames Per Second:  0.666667 (220 mps)
+CPU Time:  0.706215 fps     (233 mps)
+Total Output Bit Rate (30 fps):  859608 bits/sec
+MPEG file created in :  /tmp/ts2.mpg
+
+
diff --git a/converter/ppm/ppmtompeg/tst/ts3.param b/converter/ppm/ppmtompeg/tst/ts3.param
new file mode 100644
index 00000000..5f7b9efa
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts3.param
@@ -0,0 +1,32 @@
+# test suite parameter file
+
+PATTERN		IBPB
+OUTPUT		/tmp/ts.mpg
+
+YUV_SIZE	352x240
+
+BASE_FILE_FORMAT	YUV
+YUV_FORMAT	UCB
+INPUT_CONVERT	*
+GOP_SIZE	3
+SLICES_PER_FRAME  1
+
+INPUT_DIR	./tst/ts
+
+INPUT
+stennis.*.yuv	[30-39]
+END_INPUT
+
+PIXEL		HALF
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+REFERENCE_FRAME	ORIGINAL
+# Default string has the date in it, bad for tests!
+USER_DATA  /dev/null
diff --git a/converter/ppm/ppmtompeg/tst/ts4.param b/converter/ppm/ppmtompeg/tst/ts4.param
new file mode 100644
index 00000000..c8ba161c
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/ts4.param
@@ -0,0 +1,56 @@
+# test suite parameter file
+
+PATTERN		IBBBPBBBBP
+OUTPUT		/tmp/ts.mpg
+
+YUV_SIZE	352x240
+
+BASE_FILE_FORMAT	YUV
+YUV_FORMAT	UCB
+INPUT_CONVERT	*
+GOP_SIZE	30
+SLICES_PER_FRAME  1
+
+INPUT_DIR	./tst/ts
+
+INPUT
+stennis.*.yuv	[30-39]
+END_INPUT
+
+PIXEL		HALF
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+FRAME_RATE	25.000
+
+ASPECT_RATIO	1.0
+
+REFERENCE_FRAME	ORIGINAL
+
+IQTABLE
+	8	16	19	22	26	27	29	34
+	9	10	11	12	13	14	15	16
+	17	18	19	20	21	22	23	24
+	25	26	27	28	29	30	31	32
+	33	34	35	36	37	38	39	40
+	41	42	43	44	45	46	47	48
+	49	50	51	52	53	54	55	56
+	57	58	59	60	61	62	63	64
+
+NIQTABLE
+	8	16	19	22	26	27	29	34
+	9	10	11	12	13	14	15	16
+	17	18	19	20	21	22	23	24
+	25	26	27	28	29	30	31	32
+	33	34	35	36	37	38	39	40
+	41	42	43	44	45	46	47	48
+	49	50	51	52	53	54	55	56
+	57	58	59	60	61	62	63	64
+# Default string has the date in it, bad for tests!
+USER_DATA  /dev/null
diff --git a/converter/ppm/ppmtompeg/tst/tsd.param b/converter/ppm/ppmtompeg/tst/tsd.param
new file mode 100644
index 00000000..330a7a2a
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/tsd.param
@@ -0,0 +1,32 @@
+# test suite parameter file
+
+PATTERN		IBBBPBBBBP
+OUTPUT		/tmp/tsd.mpg
+
+YUV_SIZE	352x240
+
+BASE_FILE_FORMAT	YUV
+YUV_FORMAT	UCB
+INPUT_CONVERT	*
+GOP_SIZE	30
+SLICES_PER_FRAME  1
+
+INPUT_DIR	./tst/ts
+
+INPUT
+stennis.*.yuv	[30-39]
+END_INPUT
+
+PIXEL		HALF
+RANGE		10
+
+PSEARCH_ALG	LOGARITHMIC
+BSEARCH_ALG	CROSS2
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+REFERENCE_FRAME	DECODED
+# Default string has the date in it, bad for tests!
+USER_DATA  /dev/null
diff --git a/converter/ppm/ppmtompeg/tst/tsd.stat b/converter/ppm/ppmtompeg/tst/tsd.stat
new file mode 100644
index 00000000..b130e089
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/tsd.stat
@@ -0,0 +1,52 @@
+MPEG ENCODER STATS (1.5b)
+------------------------
+TIME STARTED:  Tue Aug 15 12:31:11 1995
+MACHINE:  odie.CS.Berkeley.EDU
+FIRST FILE:  ./tst/ts/stennis.30.yuv
+LAST FILE:  ./tst/ts/stennis.39.yuv
+PATTERN:  ibbbpbbbbp
+GOP_SIZE:  30
+SLICES PER FRAME:  1
+RANGE:  +/-10
+PIXEL SEARCH:  HALF
+PSEARCH:  LOGARITHMIC
+BSEARCH:  CROSS2
+QSCALE:  8 10 25
+REFERENCE FRAME:  DECODED
+TIME COMPLETED:  Tue Aug 15 12:31:31 1995
+Total time:  20 seconds
+
+-------------------------
+*****I FRAME SUMMARY*****
+-------------------------
+  Blocks:      330     ( 94083 bits)     (  285 bpb)
+  Frames:        1     ( 94192 bits)     (94192 bpf)     (37.0% of total)
+  Compression:   21:1     (   1.1150 bpp)
+  Seconds:          0     (   2.4390 fps)  (   206048 pps)  (      804 mps)
+-------------------------
+*****P FRAME SUMMARY*****
+-------------------------
+  I Blocks:     97     ( 20894 bits)     (  215 bpb)
+  P Blocks:    560     ( 83390 bits)     (  148 bpb)
+  Skipped:       3
+  Frames:        2     (104512 bits)     (52256 bpf)     (41.1% of total)
+  Compression:   38:1     (   0.6186 bpp)
+  Seconds:          1     (   1.0811 fps)  (    91329 pps)  (      356 mps)
+-------------------------
+*****B FRAME SUMMARY*****
+-------------------------
+  I Blocks:      4     (   588 bits)     (  147 bpb)
+  B Blocks:   2306     ( 54124 bits)     (   23 bpb)
+  B types:     283     (  14 bpb) forw    419 (  16 bpb) back    1604 (  26 bpb) bi
+  Skipped:       0
+  Frames:        7     ( 55504 bits)     ( 7929 bpf)     (21.8% of total)
+  Compression:  255:1     (   0.0939 bpp)
+  Seconds:         16     (   0.4182 fps)  (    35326 pps)  (      137 mps)
+---------------------------------------------
+Total Compression:   79:1     (   0.3011 bpp)
+Total Frames Per Second:  0.500000 (165 mps)
+CPU Time:  0.526316 fps     (173 mps)
+Total Output Bit Rate (30 fps):  763200 bits/sec
+MPEG file created in :  /tmp/tsd.mpg
+
+
diff --git a/converter/ppm/ppmtompeg/tst/tstl.param b/converter/ppm/ppmtompeg/tst/tstl.param
new file mode 100644
index 00000000..2b9c1718
--- /dev/null
+++ b/converter/ppm/ppmtompeg/tst/tstl.param
@@ -0,0 +1,31 @@
+# test suite parameter file
+
+PATTERN		IBBBPBBBBP
+OUTPUT		/tmp/ts.mpg
+
+YUV_SIZE	352x240
+
+BASE_FILE_FORMAT	YUV
+INPUT_CONVERT	*
+GOP_SIZE	30
+SLICES_PER_FRAME  1
+
+INPUT_DIR	../test/ts
+
+INPUT
+stennis.*.yuv	[30-39]
+END_INPUT
+
+PIXEL		HALF
+RANGE		10
+
+PSEARCH_ALG	TWOLEVEL
+BSEARCH_ALG	CROSS2
+
+IQSCALE		8
+PQSCALE		10
+BQSCALE		25
+
+REFERENCE_FRAME	ORIGINAL
+# Default string has the date in it, bad for tests!
+USER_DATA  /dev/null
diff --git a/converter/ppm/ppmtoneo.c b/converter/ppm/ppmtoneo.c
new file mode 100644
index 00000000..5703c12a
--- /dev/null
+++ b/converter/ppm/ppmtoneo.c
@@ -0,0 +1,123 @@
+/* ppmtoneo.c - read a portable pixmap and write a Neochrome NEO file
+**
+** Copyright (C) 2001 by Teemu Hukkanen <tjhukkan@iki.fi>
+**  based on ppmtopi1 by Steve Belczyk and Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+
+#define COLS 320
+#define ROWS 200
+#define MAXVAL 7
+#define MAXCOLORS 16
+
+static short screen[ROWS*COLS/4];  /* simulate the ST's video RAM */
+
+int
+main(int argc, char *argv[] ) {
+
+    FILE* ifp;
+    pixel** pixels;
+    colorhist_vector chv;
+    colorhash_table cht;
+    int rows, cols;
+    int colors;
+    int i;
+    int row;
+    pixval maxval;
+
+    ppm_init( &argc, argv );
+
+    if ( argc > 2 )
+        pm_usage( "[ppmfile]" );
+
+    if ( argc == 2 )
+        ifp = pm_openr( argv[1] );
+    else
+        ifp = stdin;
+
+    pixels = ppm_readppm( ifp, &cols, &rows, &maxval );
+    pm_close( ifp );
+    if ( (cols > COLS) || (rows > ROWS) )
+        pm_error( "image is larger than %dx%d - sorry", COLS, ROWS );
+
+    pm_message( "computing colormap..." );
+    chv = ppm_computecolorhist( pixels, cols, rows, MAXCOLORS, &colors );
+    if ( chv == (colorhist_vector) 0 ) {
+        pm_error(
+            "too many colors - try doing a 'pnmquant %d'", MAXCOLORS );
+    }
+    pm_message( "%d colors found", colors );
+
+    /* Write NEO header */
+    /* Flag */
+    pm_writebigshort( stdout, (short) 0);
+
+    /* resolution */
+    pm_writebigshort( stdout, (short) 0 );       /* low resolution */
+
+    /* palette */
+    for ( i = 0; i < 16; ++i ) {
+        short w;
+
+        if ( i < colors ) {
+            pixel p;
+
+            p = chv[i].color;
+            if ( maxval != MAXVAL )
+                PPM_DEPTH( p, p, maxval, MAXVAL );
+            w  = ( (int) PPM_GETR( p ) ) << 8;
+            w |= ( (int) PPM_GETG( p ) ) << 4;
+            w |= ( (int) PPM_GETB( p ) );
+        } else
+            w = 0;
+        pm_writebigshort( stdout, w );
+    }
+    if ( maxval > MAXVAL )
+        pm_message(
+            "maxval is not %d - automatically rescaling colors", MAXVAL );
+
+    /* Convert color vector to color hash table, for fast lookup. */
+    cht = ppm_colorhisttocolorhash( chv, colors );
+    ppm_freecolorhist( chv );
+
+    /* Skip rest of header */
+    fseek(stdout, 128, SEEK_SET);
+
+    /* Clear the screen buffer. */
+    for ( i = 0; i < ROWS*COLS/4; ++i )
+        screen[i] = 0;
+
+    /* Convert. */
+    for ( row = 0; row < rows; ++row ) {
+        int col;
+        for ( col = 0; col < cols; ++col) {
+            int color, ind, b, plane;
+            pixel const p = pixels[row][col];
+
+            color = ppm_lookupcolor( cht, &p );
+            if ( color == -1 )
+                pm_error(
+                    "color not found?!?  row=%d col=%d  r=%d g=%d b=%d",
+                    row, col, PPM_GETR(p), PPM_GETG(p), PPM_GETB(p) );
+            ind = 80 * row + ( ( col >> 4 ) << 2 );
+            b = 0x8000 >> (col & 0xf);
+            for ( plane = 0; plane < 4; ++plane )
+                if ( color & (1 << plane) )
+                    screen[ind+plane] |= b;
+            }
+        }
+
+    /* And write out the screen buffer. */
+    for ( i = 0; i < ROWS*COLS/4; ++i )
+        (void) pm_writebigshort( stdout, screen[i] );
+
+    exit( 0 );
+}
diff --git a/converter/ppm/ppmtopcx.c b/converter/ppm/ppmtopcx.c
new file mode 100644
index 00000000..bdcfc5c7
--- /dev/null
+++ b/converter/ppm/ppmtopcx.c
@@ -0,0 +1,906 @@
+/* ppmtopcx.c - convert a portable pixmap to PCX
+**
+** Copyright (C) 1994 by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
+** based on ppmtopcx.c by Michael Davidson
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** 11/Dec/94: first version
+** 12/Dec/94: added handling of "packed" format (16 colors or less)
+*/
+#include <assert.h>
+
+#include "ppm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+#define MAXCOLORS       256
+
+#define PCX_MAGIC       0x0a            /* PCX magic number             */
+#define PCX_256_COLORS  0x0c            /* magic number for 256 colors  */
+#define PCX_MAXVAL      (pixval)255
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* '-' if stdin */
+    unsigned int truecolor;   /* -24bit option */
+    unsigned int use_8_bit; /* -8bit option */
+    unsigned int planes;    /* zero means minimum */
+    unsigned int packed;
+    unsigned int verbose;
+    unsigned int stdpalette;
+    const char * palette;   /* NULL means none */
+    int xpos;
+    int ypos;
+};
+
+
+
+struct pcxCmapEntry {
+    unsigned char r;
+    unsigned char g;
+    unsigned char b;
+};
+
+static struct pcxCmapEntry
+pcxCmapEntryFromPixel(pixel const colorPixel) {
+
+    struct pcxCmapEntry retval;
+
+    retval.r = PPM_GETR(colorPixel);
+    retval.g = PPM_GETG(colorPixel);
+    retval.b = PPM_GETB(colorPixel);
+
+    return retval;
+}
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int planesSpec, xposSpec, yposSpec, paletteSpec;
+
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "24bit",     OPT_FLAG,   NULL,                  
+            &cmdlineP->truecolor,    0);
+    OPTENT3(0, "8bit",      OPT_FLAG,   NULL,    
+            &cmdlineP->use_8_bit,    0);
+    OPTENT3(0, "planes",    OPT_UINT,   &cmdlineP->planes, 
+            &planesSpec,             0);
+    OPTENT3(0, "packed",    OPT_FLAG,   NULL,                  
+            &cmdlineP->packed,       0);
+    OPTENT3(0, "verbose",   OPT_FLAG,   NULL,                  
+            &cmdlineP->verbose,      0);
+    OPTENT3(0, "stdpalette", OPT_FLAG,  NULL,
+            &cmdlineP->stdpalette,   0);
+    OPTENT3(0, "palette",    OPT_STRING, &cmdlineP->palette,
+            &paletteSpec,   0);
+    OPTENT3(0, "xpos",  OPT_INT, &cmdlineP->xpos, &xposSpec,   0);
+    OPTENT3(0, "ypos",  OPT_INT, &cmdlineP->ypos, &yposSpec,   0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (!xposSpec)
+        cmdlineP->xpos = 0;
+    else if (cmdlineP->xpos < -32767 || cmdlineP->xpos > 32768)
+        pm_error("-xpos value (%d) is outside acceptable range "
+                 "(-32767, 32768)", cmdlineP->xpos);
+
+    if (!yposSpec)
+        cmdlineP->ypos = 0;
+    else if (cmdlineP->ypos < -32767 || cmdlineP->ypos > 32768)
+        pm_error("-ypos value (%d) is outside acceptable range "
+                 "(-32767, 32768)", cmdlineP->ypos);
+
+    if (!planesSpec)
+        cmdlineP->planes = 0;  /* 0 means minimum */
+
+    if (planesSpec) {
+        if (cmdlineP->planes > 4 || cmdlineP->planes < 1)
+            pm_error("The only possible numbers of planes are 1-4.  "
+                     "You specified %u", cmdlineP->planes);
+        if (cmdlineP->packed)
+            pm_error("-planes is meaningless with -packed.");
+        if (cmdlineP->truecolor)
+            pm_error("-planes is meaningless with -24bit");
+        if (cmdlineP->use_8_bit)
+            pm_error("-planes is meaningless with -8bit");
+    }
+    
+    if (paletteSpec && cmdlineP->stdpalette)
+        pm_error("You can't specify both -palette and -stdpalette");
+
+    if (!paletteSpec)
+        cmdlineP->palette = NULL;
+
+    if (cmdlineP->use_8_bit && cmdlineP->truecolor) 
+        pm_error("You cannot specify both -8bit and -truecolor");
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        pm_error("Program takes at most one argument "
+                 "(input file specification).  You specified %d",
+                 argc-1);
+}
+
+
+
+/*
+ * Write out a two-byte little-endian word to the PCX file
+ */
+static void
+Putword(int    const w, 
+        FILE * const fp) {
+
+    int rc;
+    
+    rc = pm_writelittleshort(fp, w);
+
+    if (rc != 0)
+        pm_error("Error writing integer to output file");
+}
+
+
+
+/*
+ * Write out a byte to the PCX file
+ */
+static void
+Putbyte(int    const b, 
+        FILE * const fp) {
+
+    int rc;
+
+    rc = fputc(b & 0xff, fp);
+    if (rc == EOF)
+        pm_error("Error writing byte to output file.");
+}
+
+
+
+static const unsigned char bitmask[] = {1, 2, 4, 8, 16, 32, 64, 128};
+
+
+static void
+extractPlane(unsigned char * const rawrow, 
+             int             const cols, 
+             unsigned char * const buf, 
+             int             const plane) {
+/*----------------------------------------------------------------------------
+   From the image row 'rawrow', which is an array of 'cols' palette indices
+   (as unsigned 8 bit integers), extract plane number 'plane' and return
+   it at 'buf'.
+
+   E.g. Plane 2 is all the 2nd bits from the palette indices, packed so
+   that each byte represents 8 columns.
+-----------------------------------------------------------------------------*/
+    unsigned int const planeMask = 1 << plane;
+
+    unsigned int col;
+    int cbit;  /* Significance of bit representing current column in output */
+    unsigned char *cp;  /* Ptr to next output byte to fill */
+    unsigned char byteUnderConstruction;
+    
+    cp = buf;  /* initial value */
+
+    cbit = 7;
+    byteUnderConstruction = 0x00;
+    for (col = 0; col < cols; ++col) {
+        if (rawrow[col] & planeMask)
+            byteUnderConstruction |= (1 << cbit);
+
+        --cbit;
+        if (cbit < 0) {
+            /* We've filled a byte.  Output it and start the next */
+            *cp++ = byteUnderConstruction;
+            cbit = 7;
+            byteUnderConstruction = 0x00;
+        }
+    }
+    if (cbit < 7)
+        /* A byte was partially built when we ran out of columns (the number
+           of columns is not a multiple of 8.  Output the partial byte.
+        */
+        *cp++ = byteUnderConstruction;
+}
+
+
+
+static void
+PackBits(unsigned char * const rawrow, 
+         int             const width, 
+         unsigned char * const buf, 
+         int             const bits) {
+
+    int x, i, shift;
+
+    shift = i = -1;
+
+    for (x = 0; x < width; ++x) {
+        if (shift < 0) {
+            shift = 8 - bits;
+            buf[++i] = 0;
+        }
+
+        buf[i] |= (rawrow[x] << shift);
+        shift -= bits;
+    }
+}
+
+
+
+static void
+write_header(FILE *              const fp, 
+             int                 const cols, 
+             int                 const rows, 
+             int                 const BitsPerPixel, 
+             int                 const Planes, 
+             struct pcxCmapEntry const cmap16[],
+             unsigned int        const xPos, 
+             unsigned int        const yPos) {
+
+    int i, BytesPerLine;
+
+    Putbyte(PCX_MAGIC, fp);        /* .PCX magic number            */
+    Putbyte(0x05, fp);             /* PC Paintbrush version        */
+    Putbyte(0x01, fp);             /* .PCX run length encoding     */
+    Putbyte(BitsPerPixel, fp);     /* bits per pixel               */
+    
+    Putword(xPos, fp);             /* x1   - image left            */
+    Putword(yPos, fp);             /* y1   - image top             */
+    Putword(xPos+cols-1, fp);      /* x2   - image right           */
+    Putword(yPos+rows-1, fp);      /* y2   - image bottom          */
+
+    Putword(cols, fp);             /* horizontal resolution        */
+    Putword(rows, fp);             /* vertical resolution          */
+
+    /* Write out the Color Map for images with 16 colors or less */
+    if (cmap16)
+        for (i = 0; i < 16; ++i) {
+            Putbyte(cmap16[i].r, fp);
+            Putbyte(cmap16[i].g, fp);
+            Putbyte(cmap16[i].b, fp);
+        }
+    else {
+        unsigned int i;
+        for (i = 0; i < 16; ++i) {
+            Putbyte(0, fp);
+            Putbyte(0, fp);
+            Putbyte(0, fp);
+        }
+    }
+    Putbyte(0, fp);                /* reserved byte                */
+    Putbyte(Planes, fp);           /* number of color planes       */
+
+    BytesPerLine = ((cols * BitsPerPixel) + 7) / 8;
+    Putword(BytesPerLine, fp);    /* number of bytes per scanline */
+
+    Putword(1, fp);                /* palette info                 */
+
+    {
+        unsigned int i;
+        for (i = 0; i < 58; ++i)        /* fill to end of header        */
+            Putbyte(0, fp);
+    }
+}
+
+
+
+static void
+PCXEncode(FILE *                const fp, 
+          const unsigned char * const buf, 
+          int                   const Size) {
+
+    const unsigned char * const end = buf + Size;
+
+    const unsigned char * currentP;
+    int previous, count;
+
+    currentP = buf;
+    previous = *currentP++;
+    count    = 1;
+
+    while (currentP < end) {
+        unsigned char const c = *currentP++;
+        if (c == previous && count < 63)
+            ++count;
+        else {
+            if (count > 1 || (previous & 0xc0) == 0xc0) {
+                count |= 0xc0;
+                Putbyte ( count , fp );
+            }
+            Putbyte(previous, fp);
+            previous = c;
+            count   = 1;
+        }
+    }
+
+    if (count > 1 || (previous & 0xc0) == 0xc0) {
+        count |= 0xc0;
+        Putbyte ( count , fp );
+    }
+    Putbyte(previous, fp);
+}
+
+
+
+static unsigned int
+indexOfColor(colorhash_table const cht,
+             pixel           const color) {
+/*----------------------------------------------------------------------------
+   Return the index in the palette described by 'cht' of the color 'color'.
+
+   Abort program with error message if the color is not in the palette.
+-----------------------------------------------------------------------------*/
+
+    int const rc = ppm_lookupcolor(cht, &color);
+            
+    if (rc < 0)
+        pm_error("Image contains color which is not "
+                 "in the palette: %u/%u/%u", 
+                 PPM_GETR(color), PPM_GETG(color), PPM_GETB(color));
+
+    return rc;
+}
+
+
+
+static void
+ppmTo16ColorPcx(pixel **            const pixels, 
+                int                 const cols, 
+                int                 const rows, 
+                struct pcxCmapEntry const pcxcmap[], 
+                int                 const colors, 
+                colorhash_table     const cht, 
+                bool                const packbits,
+                unsigned int        const planesRequested,
+                unsigned int        const xPos,
+                unsigned int        const yPos) {
+
+    int Planes, BytesPerLine, BitsPerPixel;
+    unsigned char *indexRow;  /* malloc'ed */
+        /* indexRow[x] is the palette index of the pixel at column x of
+           the row currently being processed
+        */
+    unsigned char *planesrow; /* malloc'ed */
+        /* This is the input for a single row to the compressor */
+    int row;
+
+    if (packbits) {
+        Planes = 1;
+        if (colors > 4)        BitsPerPixel = 4;
+        else if (colors > 2)   BitsPerPixel = 2;
+        else                   BitsPerPixel = 1;
+    } else {
+        BitsPerPixel = 1;
+        if (planesRequested)
+            Planes = planesRequested;
+        else {
+            if (colors > 8)        Planes = 4;
+            else if (colors > 4)   Planes = 3;
+            else if (colors > 2)   Planes = 2;
+            else                   Planes = 1;
+        }
+    }
+    BytesPerLine = ((cols * BitsPerPixel) + 7) / 8;
+    MALLOCARRAY_NOFAIL(indexRow, cols);
+    MALLOCARRAY_NOFAIL(planesrow, BytesPerLine);
+
+    write_header(stdout, cols, rows, BitsPerPixel, Planes, pcxcmap, 
+                 xPos, yPos);
+    for (row = 0; row < rows; ++row) {
+        int col;
+        for (col = 0; col < cols; ++col)
+            indexRow[col] = indexOfColor(cht, pixels[row][col]);
+
+        if (packbits) {
+            PackBits(indexRow, cols, planesrow, BitsPerPixel);
+            PCXEncode(stdout, planesrow, BytesPerLine);
+        } else {
+            unsigned int plane;
+            for (plane = 0; plane < Planes; ++plane) {
+                extractPlane(indexRow, cols, planesrow, plane);
+                PCXEncode(stdout, planesrow, BytesPerLine);
+            }
+        }
+    }
+    free(planesrow);
+    free(indexRow);
+}
+
+
+
+static void
+ppmTo256ColorPcx(pixel **            const pixels, 
+                 int                 const cols, 
+                 int                 const rows, 
+                 struct pcxCmapEntry const pcxcmap[], 
+                 int                 const colors, 
+                 colorhash_table     const cht,
+                 unsigned int        const xPos, 
+                 unsigned int        const yPos) {
+
+    int row;
+    unsigned int i;
+    unsigned char *rawrow;
+
+    rawrow = (unsigned char *)pm_allocrow(cols, sizeof(unsigned char));
+
+    /* 8 bits per pixel, 1 plane */
+    write_header(stdout, cols, rows, 8, 1, NULL, xPos, yPos);
+    for (row = 0; row < rows; ++row) {
+        int col;
+        for (col = 0; col < cols; ++col)
+            rawrow[col] = indexOfColor(cht, pixels[row][col]);
+        PCXEncode(stdout, rawrow, cols);
+    }
+    Putbyte(PCX_256_COLORS, stdout);
+    for (i = 0; i < MAXCOLORS; ++i) {
+        Putbyte(pcxcmap[i].r, stdout);
+        Putbyte(pcxcmap[i].g, stdout);
+        Putbyte(pcxcmap[i].b, stdout);
+    }
+    pm_freerow((void*)rawrow);
+}
+
+
+
+static void
+ppmToTruecolorPcx(pixel **     const pixels, 
+                  int          const cols, 
+                  int          const rows, 
+                  pixval       const maxval,
+                  unsigned int const xPos, 
+                  unsigned int const yPos) {
+
+    unsigned char *redrow, *greenrow, *bluerow;
+    int col, row;
+
+    redrow   = (unsigned char *)pm_allocrow(cols, sizeof(unsigned char));
+    greenrow = (unsigned char *)pm_allocrow(cols, sizeof(unsigned char));
+    bluerow  = (unsigned char *)pm_allocrow(cols, sizeof(unsigned char));
+
+    /* 8 bits per pixel, 3 planes */
+    write_header(stdout, cols, rows, 8, 3, NULL, xPos, yPos);
+    for( row = 0; row < rows; row++ ) {
+        register pixel *pP = pixels[row];
+        for( col = 0; col < cols; col++, pP++ ) {
+            if( maxval != PCX_MAXVAL ) {
+                redrow[col]   = (long)PPM_GETR(*pP) * PCX_MAXVAL / maxval;
+                greenrow[col] = (long)PPM_GETG(*pP) * PCX_MAXVAL / maxval;
+                bluerow[col]  = (long)PPM_GETB(*pP) * PCX_MAXVAL / maxval;
+            }
+            else {
+                redrow[col]   = PPM_GETR(*pP);
+                greenrow[col] = PPM_GETG(*pP);
+                bluerow[col]  = PPM_GETB(*pP);
+            }
+        }
+        PCXEncode(stdout, redrow, cols);
+        PCXEncode(stdout, greenrow, cols);
+        PCXEncode(stdout, bluerow, cols);
+    }
+    pm_freerow((void*)bluerow);
+    pm_freerow((void*)greenrow);
+    pm_freerow((void*)redrow);
+}
+
+
+
+static const struct pcxCmapEntry 
+stdPalette[] = {
+    {   0,   0,   0 },
+    {   0,   0, 170 },
+    {   0, 170,   0 },
+    {   0, 170, 170 },
+    { 170,   0,   0 },
+    { 170,   0, 170 },
+    { 170, 170,   0 },
+    { 170, 170, 170 },
+    {  85,  85,  85 },
+    {  85,  85, 255 },
+    {  85, 255,  85 },
+    {  85, 255, 255 },
+    { 255,  85,  85 },
+    { 255,  85, 255 },
+    { 255, 255,  85 },
+    { 255, 255, 255 }
+};
+
+
+
+static void
+putPcxColorInHash(colorhash_table const cht,
+                  pixel           const newPcxColor,
+                  unsigned int    const newColorIndex,
+                  pixval          const maxval) {
+
+    pixel ppmColor;
+        /* Same color as 'newPcxColor', but at the PPM image's color
+           resolution: 'maxval'
+        */
+    int rc;
+
+    PPM_DEPTH(ppmColor, newPcxColor, PCX_MAXVAL, maxval);
+        
+    rc = ppm_lookupcolor(cht, &ppmColor);
+
+    if (rc == -1)
+        /* This color is not in the hash yet, so we just add it */
+        ppm_addtocolorhash(cht, &ppmColor, newColorIndex);
+    else {
+        /* This color is already in the hash.  That's because the
+           subject image has less color resolution than PCX (i.e.
+           'maxval' is less than PCX_MAXVAL), and two distinct
+           colors in the standard palette are indistinguishable at
+           subject image color resolution.
+           
+           So we have to figure out wether color 'newPcxColor' or
+           'existingPcxColor' is a better match for 'ppmColor'.
+        */
+
+        unsigned int const existingColorIndex = rc;
+
+        pixel idealPcxColor;
+        pixel existingPcxColor;
+
+        PPM_DEPTH(idealPcxColor, ppmColor, maxval, PCX_MAXVAL);
+        
+        PPM_ASSIGN(existingPcxColor, 
+                   stdPalette[existingColorIndex].r,
+                   stdPalette[existingColorIndex].g,
+                   stdPalette[existingColorIndex].b);
+
+        if (PPM_DISTANCE(newPcxColor, idealPcxColor) <
+            PPM_DISTANCE(existingPcxColor, idealPcxColor)) {
+            /* The new PCX color is a better match.  Make it the new
+               translation of image color 'ppmColor'.
+            */
+            ppm_delfromcolorhash(cht, &ppmColor);
+            ppm_addtocolorhash(cht, &ppmColor, newColorIndex);
+        }
+    }
+}
+
+
+
+static void
+generateStandardPalette(struct pcxCmapEntry ** const pcxcmapP,
+                        pixval                 const maxval,
+                        colorhash_table *      const chtP,
+                        int *                  const colorsP) {
+
+    unsigned int const stdPaletteSize = 16;
+    unsigned int colorIndex;
+    struct pcxCmapEntry * pcxcmap;
+    colorhash_table cht;
+
+    MALLOCARRAY_NOFAIL(pcxcmap, MAXCOLORS);
+    
+    *pcxcmapP = pcxcmap;
+
+    cht = ppm_alloccolorhash();
+
+    for (colorIndex = 0; colorIndex < stdPaletteSize; ++colorIndex) {
+        pixel pcxColor;
+            /* The color of this colormap entry, in PCX resolution */
+
+        pcxcmap[colorIndex] = stdPalette[colorIndex];
+        
+        PPM_ASSIGN(pcxColor, 
+                   stdPalette[colorIndex].r,
+                   stdPalette[colorIndex].g,
+                   stdPalette[colorIndex].b);
+
+        putPcxColorInHash(cht, pcxColor, colorIndex, maxval);
+    }
+
+    *chtP = cht;
+    *colorsP = stdPaletteSize;
+}
+    
+
+
+static void
+readPpmPalette(const char *   const paletteFileName,
+               pixel       (* const ppmPaletteP)[], 
+               unsigned int * const paletteSizeP) {
+
+    FILE * pfP;
+    pixel ** pixels;
+    int cols, rows;
+    pixval maxval;
+    
+    pfP = pm_openr(paletteFileName);
+
+    pixels = ppm_readppm(pfP, &cols, &rows, &maxval);
+
+    pm_close(pfP);
+    
+    *paletteSizeP = rows * cols;
+    if (*paletteSizeP > MAXCOLORS) 
+        pm_error("ordered palette image contains %d pixels.  Maximum is %d",
+                 *paletteSizeP, MAXCOLORS);
+
+    {
+        int j;
+        int row;
+        j = 0;  /* initial value */
+        for (row = 0; row < rows; ++row) {
+            int col;
+            for (col = 0; col < cols; ++col) 
+                (*ppmPaletteP)[j++] = pixels[row][col];
+        }
+    }
+    ppm_freearray(pixels, rows);
+}        
+
+
+
+static void
+readPaletteFromFile(struct pcxCmapEntry ** const pcxcmapP,
+                    const char *           const paletteFileName,
+                    pixval                 const maxval,
+                    colorhash_table *      const chtP,
+                    int *                  const colorsP) {
+
+    unsigned int colorIndex;
+    pixel ppmPalette[MAXCOLORS];
+    unsigned int paletteSize;
+    struct pcxCmapEntry * pcxcmap;
+    colorhash_table cht;
+
+    readPpmPalette(paletteFileName, &ppmPalette, &paletteSize);
+
+    MALLOCARRAY_NOFAIL(pcxcmap, MAXCOLORS);
+    
+    *pcxcmapP = pcxcmap;
+
+    cht = ppm_alloccolorhash();
+
+    for (colorIndex = 0; colorIndex < paletteSize; ++colorIndex) {
+        pixel pcxColor;
+            /* The color of this colormap entry, in PCX resolution */
+
+        pcxcmap[colorIndex] = pcxCmapEntryFromPixel(ppmPalette[colorIndex]);
+        
+        PPM_ASSIGN(pcxColor, 
+                   ppmPalette[colorIndex].r,
+                   ppmPalette[colorIndex].g,
+                   ppmPalette[colorIndex].b);
+
+        putPcxColorInHash(cht, pcxColor, colorIndex, maxval);
+    }
+
+    *chtP = cht;
+    *colorsP = paletteSize;
+}
+    
+
+
+static void
+moveBlackToIndex0(colorhist_vector const chv,
+                  int              const colors) {
+/*----------------------------------------------------------------------------
+   If black is in the palette, make it at Index 0.
+-----------------------------------------------------------------------------*/
+    pixel blackPixel;
+    unsigned int i;
+    bool blackPresent;
+
+    PPM_ASSIGN(blackPixel, 0, 0, 0);
+
+    blackPresent = FALSE;  /* initial assumption */
+
+    for (i = 0; i < colors; ++i)
+        if (PPM_EQUAL(chv[i].color, blackPixel))
+            blackPresent = TRUE;
+            
+    if (blackPresent) {
+        /* We use a trick here.  ppm_addtocolorhist() always adds to the
+           beginning of the table and if the color is already elsewhere in
+           the table, removes it.
+        */
+        int colors2;
+        colors2 = colors;
+        ppm_addtocolorhist(chv, &colors2, MAXCOLORS, &blackPixel, 0, 0);
+        assert(colors2 == colors);
+    }
+}
+
+
+
+static void
+makePcxColormapFromImage(pixel **               const pixels,
+                         int                    const cols,
+                         int                    const rows,
+                         pixval                 const maxval,
+                         struct pcxCmapEntry ** const pcxcmapP,
+                         colorhash_table *      const chtP,
+                         int *                  const colorsP,
+                         bool *                 const tooManyColorsP) {
+/*----------------------------------------------------------------------------
+   Make a colormap (palette) for the PCX header that can be used
+   for the image described by 'pixels', 'cols', 'rows', and 'maxval'.
+
+   Return it in newly malloc'ed storage and return its address as
+   *pcxcmapP.
+
+   Also return a lookup hash to relate a color in the image to the
+   appropriate index in *pcxcmapP.  Return that in newly malloc'ed 
+   storage as *chtP.
+
+   Iff there are too many colors to do that (i.e. more than 256), 
+   return *tooManyColorsP == TRUE.
+-----------------------------------------------------------------------------*/
+    int colors;
+    colorhist_vector chv;
+
+    pm_message("computing colormap...");
+
+    chv = ppm_computecolorhist(pixels, cols, rows, MAXCOLORS, &colors);
+    if (chv == NULL)
+        *tooManyColorsP = TRUE;
+    else {
+        int i;
+        struct pcxCmapEntry * pcxcmap;
+
+        *tooManyColorsP = FALSE;
+
+        pm_message("%d colors found", colors);
+        
+        moveBlackToIndex0(chv, colors);
+
+        MALLOCARRAY_NOFAIL(pcxcmap, MAXCOLORS);
+
+        *pcxcmapP = pcxcmap;
+
+        for (i = 0; i < colors; ++i) {
+            pixel p;
+
+            PPM_DEPTH(p, chv[i].color, maxval, PCX_MAXVAL);
+
+            pcxcmap[i].r = PPM_GETR(p);
+            pcxcmap[i].g = PPM_GETG(p);
+            pcxcmap[i].b = PPM_GETB(p);
+        }
+
+        /* Fill it out with black */
+        for ( ; i < MAXCOLORS; ++i) {
+            pcxcmap[i].r = 0;
+            pcxcmap[i].g = 0;
+            pcxcmap[i].b = 0;
+        }
+
+        *chtP = ppm_colorhisttocolorhash(chv, colors);
+
+        *colorsP = colors;
+
+        ppm_freecolorhist(chv);
+    }
+}
+
+
+
+static void 
+ppmToPalettePcx(pixel **            const pixels, 
+                int                 const cols, 
+                int                 const rows,
+                pixval              const maxval,
+                unsigned int        const xPos, 
+                unsigned int        const yPos,
+                struct pcxCmapEntry const pcxcmap[],
+                colorhash_table     const cht,
+                int                 const colors,
+                bool                const packbits,
+                unsigned int        const planes,
+                bool                const use_8_bit) {
+    
+    /* convert image */
+    if( colors <= 16 && !use_8_bit )
+        ppmTo16ColorPcx(pixels, cols, rows, pcxcmap, colors, cht, 
+                        packbits, planes, xPos, yPos);
+    else
+        ppmTo256ColorPcx(pixels, cols, rows, pcxcmap, colors, cht,
+                         xPos, yPos);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE* ifP;
+    int rows, cols;
+    pixval maxval;
+    pixel **pixels;
+    struct pcxCmapEntry * pcxcmap;
+    colorhash_table cht;
+    bool truecolor;
+    int colors;
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+    pixels = ppm_readppm(ifP, &cols, &rows, &maxval);
+    pm_close(ifP);
+
+    if (cmdline.truecolor)
+        truecolor = TRUE;
+    else {
+        if (cmdline.stdpalette) {
+            truecolor = FALSE;
+            generateStandardPalette(&pcxcmap, maxval, &cht, &colors);
+        } else if (cmdline.palette) {
+            truecolor = FALSE;
+            readPaletteFromFile(&pcxcmap, cmdline.palette, maxval, 
+                                &cht, &colors);
+        } else {
+            bool tooManyColors;
+            makePcxColormapFromImage(pixels, cols, rows, maxval,
+                                     &pcxcmap, &cht, &colors,
+                                     &tooManyColors);
+            
+            if (tooManyColors) {
+                pm_message("too many colors - writing a 24bit PCX file");
+                pm_message("if you want a non-24bit file, "
+                           " a 'pnmquant %d'", MAXCOLORS);
+                truecolor = TRUE;
+            } else
+                truecolor = FALSE;
+        }
+    }
+
+    if (truecolor)
+        ppmToTruecolorPcx(pixels, cols, rows, maxval, 
+                          cmdline.xpos, cmdline.ypos);
+    else {
+        ppmToPalettePcx(pixels, cols, rows, maxval, 
+                        cmdline.xpos, cmdline.ypos,
+                        pcxcmap, cht, colors, cmdline.packed, 
+                        cmdline.planes, cmdline.use_8_bit);
+        
+        ppm_freecolorhash(cht);
+        free(pcxcmap);
+    }
+    return 0;
+}
diff --git a/converter/ppm/ppmtopi1.c b/converter/ppm/ppmtopi1.c
new file mode 100644
index 00000000..64f836c7
--- /dev/null
+++ b/converter/ppm/ppmtopi1.c
@@ -0,0 +1,120 @@
+/* ppmtopi1.c - read a portable pixmap and write a Degas PI1 file
+**
+** Copyright (C) 1991 by Steve Belczyk and Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+
+#define COLS 320
+#define ROWS 200
+#define MAXVAL 7
+#define MAXCOLORS 16
+
+static short screen[ROWS*COLS/4];  /* simulate the ST's video RAM */
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    pixel** pixels;
+    register pixel *pP;
+    colorhist_vector chv;
+    colorhash_table cht;
+    int rows, cols, row, colors, i;
+    register int col;
+    pixval maxval;
+
+
+    ppm_init( &argc, argv );
+
+    if ( argc > 2 )
+        pm_usage( "[ppmfile]" );
+
+    if ( argc == 2 )
+        ifp = pm_openr( argv[1] );
+    else
+        ifp = stdin;
+
+    pixels = ppm_readppm( ifp, &cols, &rows, &maxval );
+    pm_close( ifp );
+    if ( (cols > COLS) || (rows > ROWS) )
+        pm_error( "image is larger than %dx%d - sorry", COLS, ROWS );
+
+    pm_message( "computing colormap..." );
+    chv = ppm_computecolorhist( pixels, cols, rows, MAXCOLORS, &colors );
+    if ( chv == (colorhist_vector) 0 )
+        {
+        pm_message(
+            "too many colors - try doing a 'pnmquant %d'", MAXCOLORS );
+        exit( 1 );
+        }
+    pm_message( "%d colors found", colors );
+
+    /* Write PI1 header - resolution and palette. */
+    (void) pm_writebigshort( stdout, (short) 0 );       /* low resolution */
+    for ( i = 0; i < 16; ++i )
+        {
+        short w;
+
+        if ( i < colors )
+            {
+            pixel p;
+
+            p = chv[i].color;
+            if ( maxval != MAXVAL )
+                PPM_DEPTH( p, p, maxval, MAXVAL );
+            w  = ( (int) PPM_GETR( p ) ) << 8;
+            w |= ( (int) PPM_GETG( p ) ) << 4;
+            w |= ( (int) PPM_GETB( p ) );
+            }
+        else
+            w = 0;
+        (void) pm_writebigshort( stdout, w );
+        }
+    if ( maxval > MAXVAL )
+        pm_message(
+            "maxval is not %d - automatically rescaling colors", MAXVAL );
+
+    /* Convert color vector to color hash table, for fast lookup. */
+    cht = ppm_colorhisttocolorhash( chv, colors );
+    ppm_freecolorhist( chv );
+
+    /* Clear the screen buffer. */
+    for ( i = 0; i < ROWS*COLS/4; ++i )
+        screen[i] = 0;
+
+    /* Convert. */
+    for ( row = 0; row < rows; ++row )
+        {
+        for ( col = 0, pP = pixels[row]; col < cols; ++col, ++pP )
+            {
+            register int color, ind, b, plane;
+
+            color = ppm_lookupcolor( cht, pP );
+            if ( color == -1 )
+                pm_error(
+                    "color not found?!?  row=%d col=%d  r=%d g=%d b=%d",
+                    row, col, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP) );
+            ind = 80 * row + ( ( col >> 4 ) << 2 );
+            b = 0x8000 >> (col & 0xf);
+            for ( plane = 0; plane < 4; ++plane )
+                if ( color & (1 << plane) )
+                    screen[ind+plane] |= b;
+            }
+        }
+
+    /* And write out the screen buffer. */
+    for ( i = 0; i < ROWS*COLS/4; ++i )
+        (void) pm_writebigshort( stdout, screen[i] );
+
+    exit( 0 );
+    }
diff --git a/converter/ppm/ppmtopict.c b/converter/ppm/ppmtopict.c
new file mode 100644
index 00000000..e2428fb6
--- /dev/null
+++ b/converter/ppm/ppmtopict.c
@@ -0,0 +1,481 @@
+/*
+** ppmtopict.c - read a portable pixmap and produce a Macintosh PICT2 file.
+**
+** Copyright (C) 1990 by Ken Yap <ken@cs.rochester.edu>.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+
+#define HEADER_SIZE		512
+
+#define	RUN_THRESH		3
+#define	MAX_RUN			128		/* 0xff = 2, 0xfe = 3, etc */
+#define	MAX_COUNT		128		/* 0x00 = 1, 0x01 = 2, etc */
+
+/* Opcodes */
+#define PICT_NOP		0x00
+#define PICT_clipRgn		0x01
+#define PICT_bkPat		0x02
+#define PICT_txFont		0x03
+#define PICT_txFace		0x04
+#define PICT_txMode		0x05
+#define PICT_spExtra		0x06
+#define PICT_pnSize		0x07
+#define PICT_pnMode		0x08
+#define PICT_pnPat		0x09
+#define PICT_thePat		0x0A
+#define PICT_ovSize		0x0B
+#define PICT_origin		0x0C
+#define PICT_txSize		0x0D
+#define PICT_fgColor		0x0E
+#define PICT_bkColor		0x0F
+#define PICT_txRatio		0x10
+#define PICT_picVersion		0x11
+#define	PICT_blPixPat		0x12
+#define	PICT_pnPixPat		0x13
+#define	PICT_fillPixPat		0x14
+#define	PICT_pnLocHFrac		0x15
+#define	PICT_chExtra		0x16
+#define	PICT_rgbFgCol		0x1A
+#define	PICT_rgbBkCol		0x1B
+#define	PICT_hiliteMode		0x1C
+#define	PICT_hiliteColor	0x1D
+#define	PICT_defHilite		0x1E
+#define	PICT_opColor		0x1F
+#define PICT_line		0x20
+#define PICT_line_from		0x21
+#define PICT_short_line		0x22
+#define PICT_short_line_from	0x23
+#define PICT_long_text		0x28
+#define PICT_DH_text		0x29
+#define PICT_DV_text		0x2A
+#define PICT_DHDV_text		0x2B
+#define PICT_frameRect		0x30
+#define PICT_paintRect		0x31
+#define PICT_eraseRect		0x32
+#define PICT_invertRect		0x33
+#define PICT_fillRect		0x34
+#define PICT_frameSameRect	0x38
+#define PICT_paintSameRect	0x39
+#define PICT_eraseSameRect	0x3A
+#define PICT_invertSameRect	0x3B
+#define PICT_fillSameRect	0x3C
+#define PICT_frameRRect		0x40
+#define PICT_paintRRect		0x41
+#define PICT_eraseRRect		0x42
+#define PICT_invertRRect	0x43
+#define PICT_fillRRect		0x44
+#define PICT_frameSameRRect	0x48
+#define PICT_paintSameRRect	0x49
+#define PICT_eraseSameRRect	0x4A
+#define PICT_invertSameRRect	0x4B
+#define PICT_fillSameRRect	0x4C
+#define PICT_frameOval		0x50
+#define PICT_paintOval		0x51
+#define PICT_eraseOval		0x52
+#define PICT_invertOval		0x53
+#define PICT_fillOval		0x54
+#define PICT_frameSameOval	0x58
+#define PICT_paintSameOval	0x59
+#define PICT_eraseSameOval	0x5A
+#define PICT_invertSameOval	0x5B
+#define PICT_fillSameOval	0x5C
+#define PICT_frameArc		0x60
+#define PICT_paintArc		0x61
+#define PICT_eraseArc		0x62
+#define PICT_invertArc		0x63
+#define PICT_fillArc		0x64
+#define PICT_frameSameArc	0x68
+#define PICT_paintSameArc	0x69
+#define PICT_eraseSameArc	0x6A
+#define PICT_invertSameArc	0x6B
+#define PICT_fillSameArc	0x6C
+#define PICT_framePoly		0x70
+#define PICT_paintPoly		0x71
+#define PICT_erasePoly		0x72
+#define PICT_invertPoly		0x73
+#define PICT_fillPoly		0x74
+#define PICT_frameSamePoly	0x78
+#define PICT_paintSamePoly	0x79
+#define PICT_eraseSamePoly	0x7A
+#define PICT_invertSamePoly	0x7B
+#define PICT_fillSamePoly	0x7C
+#define PICT_frameRgn		0x80
+#define PICT_paintRgn		0x81
+#define PICT_eraseRgn		0x82
+#define PICT_invertRgn		0x83
+#define PICT_fillRgn		0x84
+#define PICT_frameSameRgn	0x88
+#define PICT_paintSameRgn	0x89
+#define PICT_eraseSameRgn	0x8A
+#define PICT_invertSameRgn	0x8B
+#define PICT_fillSameRgn	0x8C
+#define PICT_BitsRect		0x90
+#define PICT_BitsRgn		0x91
+#define PICT_PackBitsRect	0x98
+#define PICT_PackBitsRgn	0x99
+#define PICT_shortComment	0xA0
+#define PICT_longComment	0xA1
+#define PICT_EndOfPicture	0xFF
+#define	PICT_headerOp		0x0C00
+
+static void putFill ARGS(( FILE *fd, int n ));
+static void putShort ARGS(( FILE *fd, int i ));
+static void putLong ARGS(( FILE *fd, long i ));
+static void putFixed ARGS(( FILE *fd, int in, int frac ));
+static void putRect ARGS(( FILE *fd, int x1, int x2, int y1, int y2 ));
+static int putRow ARGS(( FILE *fd, int row, int cols, pixel *rowpixels, char *packed ));
+
+#define MAXCOLORS 256
+static colorhash_table cht;
+
+int
+main(argc, argv)
+int argc;
+char *argv[];
+{
+	FILE *ifp;
+	int argn, rows, cols, colors, i, row, oc;
+	register pixel **pixels;
+	char *packed;
+	pixval maxval;
+	long lmaxval, rval, gval, bval;
+	colorhist_vector chv;
+
+
+	ppm_init( &argc, argv );
+
+	argn = 1;
+	if (argn < argc)
+	{
+		ifp = pm_openr(argv[1]);
+		argn++;
+	}
+	else
+		ifp = stdin;
+	if (argn != argc)
+		pm_usage("[ppmfile]");
+
+	pixels = ppm_readppm(ifp, &cols, &rows, &maxval);
+	if (cols < 8)
+		pm_error("ppm input too narrow, must be >= 8 pixels wide" );
+	lmaxval = (long)maxval;
+	pm_close(ifp);
+
+	/* Figure out the colormap. */
+	pm_message("computing colormap..." );
+	chv = ppm_computecolorhist(pixels, cols, rows, MAXCOLORS, &colors);
+	if (chv == (colorhist_vector) 0)
+		pm_error("too many colors - try doing a 'pnmquant %d'", MAXCOLORS);
+	pm_message("%d colors found", colors );
+
+	/* Make a hash table for fast color lookup. */
+	cht = ppm_colorhisttocolorhash(chv, colors);
+
+	/* write the header */
+	putFill(stdout, HEADER_SIZE);
+
+	/* write picSize and picFrame */
+	putShort(stdout, 0);
+	putRect(stdout, 0, 0, rows, cols);
+
+	/* write version op and version */
+	putShort(stdout, PICT_picVersion);
+	putShort(stdout, 0x02FF);
+	putShort(stdout, PICT_headerOp);
+	putLong(stdout, -1L);
+	putFixed(stdout, 0, 0);
+	putFixed(stdout, 0, 0);
+	putFixed(stdout, cols, 0);
+	putFixed(stdout, rows, 0);
+	putFill(stdout, 4);
+
+	/* seems to be needed by many PICT2 programs */
+	putShort(stdout, PICT_clipRgn);
+	putShort(stdout, 10);
+	putRect(stdout, 0, 0, rows, cols);
+
+	/* write picture */
+	putShort(stdout, PICT_PackBitsRect);
+	putShort(stdout, cols | 0x8000);
+	putRect(stdout, 0, 0, rows, cols);
+	putShort(stdout, 0);	/* pmVersion */
+	putShort(stdout, 0);	/* packType */
+	putLong(stdout, 0L);	/* packSize */
+	putFixed(stdout, 72, 0);	/* hRes */
+	putFixed(stdout, 72, 0);	/* vRes */
+	putShort(stdout, 0);	/* pixelType */
+	putShort(stdout, 8);	/* pixelSize */
+	putShort(stdout, 1);	/* cmpCount */
+	putShort(stdout, 8);	/* cmpSize */
+	putLong(stdout, 0L);	/* planeBytes */
+	putLong(stdout, 0L);	/* pmTable */
+	putLong(stdout, 0L);	/* pmReserved */
+	putLong(stdout, 0L);	/* ctSeed */
+	putShort(stdout, 0);	/* ctFlags */
+	putShort(stdout, colors-1);	/* ctSize */
+
+	/* Write out the colormap. */
+	for (i = 0; i < colors; i++)
+	{
+		putShort(stdout, i);
+		rval = PPM_GETR(chv[i].color);
+		gval = PPM_GETG(chv[i].color);
+		bval = PPM_GETB(chv[i].color);
+		if (lmaxval != 65535L)
+		{
+			rval = rval * 65535L / lmaxval;
+			gval = gval * 65535L / lmaxval;
+			bval = bval * 65535L / lmaxval;
+		}
+		putShort(stdout, (short)rval);
+		putShort(stdout, (short)gval);
+		putShort(stdout, (short)bval);
+	}
+
+	putRect(stdout, 0, 0, rows, cols);	/* srcRect */
+	putRect(stdout, 0, 0, rows, cols);	/* dstRect */
+	putShort(stdout, 0);			/* mode */
+
+	/* Finally, write out the data. */
+	packed = (char*) malloc((unsigned)(cols+cols/MAX_COUNT+1));
+	oc = 0;
+	for (row = 0; row < rows; row++)
+		oc += putRow(stdout, row, cols, pixels[row], packed);
+
+	/* if we wrote an odd number of pixdata bytes, pad */
+	if (oc & 1)
+		(void) putc(0, stdout);
+	putShort(stdout, PICT_EndOfPicture);
+
+	lmaxval = ftell(stdout) - HEADER_SIZE;
+	if (fseek(stdout, (long)HEADER_SIZE, 0) >= 0)
+		putShort(stdout, (short)(lmaxval & 0xffff));
+
+	exit(0);
+}
+
+static void
+putFill(fd, n)
+FILE *fd;
+int n;
+{
+	register int i;
+
+	for (i = 0; i < n; i++)
+		(void) putc(0, fd);
+}
+
+static void
+putShort(fd, i)
+FILE *fd;
+int i;
+{
+	(void) putc((i >> 8) & 0xff, fd);
+	(void) putc(i & 0xff, fd);
+}
+
+#if __STDC__
+static void
+putLong( FILE *fd, long i )
+#else /*__STDC__*/
+static void
+putLong(fd, i)
+FILE *fd;
+long i;
+#endif /*__STDC__*/
+{
+	(void) putc((int)((i >> 24) & 0xff), fd);
+	(void) putc(((int)(i >> 16) & 0xff), fd);
+	(void) putc(((int)(i >> 8) & 0xff), fd);
+	(void) putc((int)(i & 0xff), fd);
+}
+
+static void
+putFixed(fd, in, frac)
+FILE *fd;
+int in, frac;
+{
+	putShort(fd, in);
+	putShort(fd, frac);
+}
+
+static void
+putRect(fd, x1, x2, y1, y2)
+FILE *fd;
+int x1, x2, y1, y2;
+{
+	putShort(fd, x1);
+	putShort(fd, x2);
+	putShort(fd, y1);
+	putShort(fd, y2);
+}
+
+#define	RUNLENGTH
+#ifdef	RUNLENGTH
+
+#define		runtochar(c)	(257-(c))
+#define		counttochar(c)	((c)-1)
+
+static int
+putRow(fd, row, cols, rowpixels, packed)
+FILE *fd;
+int row, cols;
+pixel *rowpixels;
+char *packed;
+{
+	register int i;
+	int packcols, count, run, rep, oc;
+	register pixel *pP;
+	pixel lastp;
+	register char *p;
+
+	run = count = 0;
+	for (cols--, i = cols, pP = rowpixels + cols, p = packed, lastp = *pP;
+		i >= 0; i--, lastp = *pP, pP--)
+	{
+		if (PPM_EQUAL(lastp, *pP))
+			run++;
+		else if (run < RUN_THRESH)
+		{
+			while (run > 0)
+			{
+				*p++ = ppm_lookupcolor(cht, &lastp);
+				run--;
+				count++;
+				if (count == MAX_COUNT)
+				{
+					*p++ = counttochar(MAX_COUNT);
+					count -= MAX_COUNT;
+				}
+			}
+			run = 1;
+		}
+		else
+		{
+			if (count > 0)
+				*p++ = counttochar(count);
+			count = 0;
+			while (run > 0)
+			{
+				rep = run > MAX_RUN ? MAX_RUN : run;
+				*p++ = ppm_lookupcolor(cht, &lastp);
+				*p++ = runtochar(rep);
+				run -= rep;
+			}
+			run = 1;
+		}
+	}
+	if (run < RUN_THRESH)
+	{
+		while (run > 0)
+		{
+			*p++ = ppm_lookupcolor(cht, &lastp);
+			run--;
+			count++;
+			if (count == MAX_COUNT)
+			{
+				*p++ = counttochar(MAX_COUNT);
+				count -= MAX_COUNT;
+			}
+		}
+	}
+	else
+	{
+		if (count > 0)
+			*p++ = counttochar(count);
+		count = 0;
+		while (run > 0)
+		{
+			rep = run > MAX_RUN ? MAX_RUN : run;
+			*p++ = ppm_lookupcolor(cht, &lastp);
+			*p++ = runtochar(rep);
+				run -= rep;
+		}
+		run = 1;
+	}
+	if (count > 0)
+		*p++ = counttochar(count);
+
+	packcols = p - packed;		/* how many did we write? */
+	if (cols > 250)
+	{
+		putShort(fd, packcols);
+		oc = packcols + 2;
+	}
+	else
+	{
+		(void) putc(packcols, fd);
+		oc = packcols + 1;
+	}
+
+	/* now write out the packed row */
+	while(p != packed)
+	{
+		--p;
+		(void) putc(*p, fd);
+	}
+
+	return (oc);
+}
+
+#else	/* RUNLENGTH */
+
+/* real dumb putRow with no compression */
+static int
+putRow(fd, row, cols, rowpixels, packed)
+FILE *fd;
+int row, cols;
+pixel *rowpixels;
+char *packed;
+{
+	register int i, j, bc, oc;
+	register pixel *pP;
+
+#if notdef
+	bzero(aux, cols); /* aux?? */
+#endif /*notdef*/
+	bc = cols + (cols + MAX_COUNT - 1) / MAX_COUNT;
+	if (bc > 250)
+	{
+		putShort(fd, bc);
+		oc = bc + 2;
+	}
+	else
+	{
+		(void) putc(bc, fd);
+		oc = bc + 1;
+	}
+	for (i = 0, pP = rowpixels; i < cols;)
+	{
+		if (cols - i > MAX_COUNT)
+		{
+			(void) putc(MAX_COUNT - 1, fd);
+			for (j = 0; j < MAX_COUNT; j++)
+			{
+				(void) putc(ppm_lookupcolor(cht, pP), fd);
+				pP++;
+			}
+			i += MAX_COUNT;
+		}
+		else
+		{
+			(void) putc(cols - i - 1, fd);
+			for (j = 0; j < cols - i; j++)
+			{
+				(void) putc(ppm_lookupcolor(cht, pP), fd);
+				pP++;
+			}
+			i = cols;
+		}
+	}
+	return (oc);
+}
+#endif	/* RUNLENGTH */
diff --git a/converter/ppm/ppmtopj.c b/converter/ppm/ppmtopj.c
new file mode 100644
index 00000000..5d449f7a
--- /dev/null
+++ b/converter/ppm/ppmtopj.c
@@ -0,0 +1,256 @@
+/* ppmtopj.c - convert a portable pixmap to an HP PainJetXL image
+**
+** Copyright (C) 1990 by Christos Zoulas (christos@ee.cornell.edu)
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "nstring.h"
+#include "ppm.h"
+
+static int compress_row ARGS((unsigned char *op, unsigned char *oe, unsigned char *cp));
+/*
+ * XXX: Only 8.5 x 11 paper
+ */
+#define WIDTH	  8.5
+#define HEIGHT	  11.0
+#define DPI	  180
+#define XPIX	  ((int) ((DPI * WIDTH + 7) / 8) << 3)
+#define YPIX	  ((int) ((DPI * HEIGHT + 7) / 8) << 3)
+
+#define C_RESET 			"\033E"
+#define C_RENDER 			"\033*t%dJ"
+# define C_RENDER_NONE			0
+# define C_RENDER_SNAP			1
+# define C_RENDER_BW			2
+# define C_RENDER_DITHER		3
+# define C_RENDER_DIFFUSE		4
+# define C_RENDER_MONODITHER		5
+# define C_RENDER_MONODIFFUSE		6
+# define C_RENDER_MONO_CL_DITHER	5
+# define C_RENDER_MONO_CL_DIFFUSE	6
+#define C_BACK_SCALE			"\033*t%dK"
+# define C_BACK_SCALE_LIGHT		0
+# define C_BACK_SCALE_DARK		1
+#define C_GAMMA				"\033*t%dI"
+#define C_IMAGE_WIDTH			"\033*r%dS"
+#define C_IMAGE_HEIGHT			"\033*r%dT"
+#define C_DATA_PLANES			"\033*r%dU"
+#define C_TRANS_MODE			"\033*b%dM"
+# define C_TRANS_MODE_STD		0
+# define C_TRANS_MODE_RLE		1
+# define C_TRANS_MODE_TIFF		2
+#define C_SEND_PLANE			"\033*b%dV"
+#define C_LAST_PLANE			"\033*b%dW"
+#define C_BEGIN_RASTER			"\033*r%dA"
+# define C_BEGIN_RASTER_MARGIN		0
+# define C_BEGIN_RASTER_ACTIVE		1
+# define C_BEGIN_RASTER_NOSCALE		0
+# define C_BEGIN_RASTER_SCALE		2
+#define C_END_RASTER			"\033*r%dC"
+# define C_END_RASTER_UNUSED		0
+#define C_RESOLUTION			"\033*t%dR"
+# define C_RESOLUTION_90DPI		90
+# define C_RESOLUTION_180DPI		180
+#define C_MOVE_X			"\033*p+%dX"
+#define C_MOVE_Y			"\033*p+%dY"
+
+static const char * const rmode[] = { 
+    "none", "snap", "bw", "dither", "diffuse", 
+    "monodither", "monodiffuse", "clusterdither", 
+    "monoclusterdither", NULL 
+};
+
+/*
+ * Run-length encoding for the PaintJet. We have pairs of <instances>
+ * <value>, where instances goes from 0 (meaning one instance) to 255
+ * If we are unlucky we can double the size of the image.
+ */
+static int
+compress_row(op, oe, cp)
+unsigned char *op, *oe, *cp;
+{
+    unsigned char *ce = cp;
+    while ( op < oe ) {	
+	unsigned char px = *op++;
+	unsigned char *pr = op;
+	while ( op < oe && *op == px && op - pr < 255) op++;
+	*ce++ = op - pr;
+	*ce++ = px;
+    }
+    return ce - cp;
+}
+
+int main(argc, argv)
+int argc;
+char *argv[];
+{
+	pixel **pixels;
+	FILE *ifp;
+	int argn, rows, cols, r, c, k, p;
+	pixval maxval;
+	unsigned char *obuf, *op, *cbuf;
+	int render_mode = C_RENDER_NONE;
+	int back_scale = C_BACK_SCALE_DARK;
+	int gamma = 0;
+	int mode = C_TRANS_MODE_STD;
+	int center = 0;
+	int xoff = 0, yoff = 0;
+	/*
+	 * XXX: Someday we could make this command line options.
+	 */
+	int posscale = C_BEGIN_RASTER_MARGIN | C_BEGIN_RASTER_NOSCALE;
+	int resolution = C_RESOLUTION_180DPI;
+
+	const char * const usage = "[-center] [-xpos <pos>] [-ypos <pos>] [-gamma <val>] [-back <dark|lite>] [-rle] [-render <none|snap|bw|dither|diffuse|monodither|monodiffuse|clusterdither|monoclusterdither>] [ppmfile]";
+
+
+	ppm_init( &argc, argv );
+
+	argn = 1;
+	while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+	    {
+	    if ( pm_keymatch(argv[argn],"-render",2) && argn + 1 < argc )
+		{
+		++argn;
+		for (r = 0; rmode[r] != NULL; r++)
+		     if (STREQ(rmode[r], argv[argn]))
+			 break;
+		if (rmode[r] != NULL)
+		    render_mode = r;
+		else
+		    pm_usage(usage);
+		}
+	    else if ( pm_keymatch(argv[argn],"-back",2) && argn + 1 < argc )
+		{
+		++argn;
+		if (STREQ(argv[argn], "dark"))
+		    back_scale = C_BACK_SCALE_DARK;
+		else if (STREQ(argv[argn], "lite"))
+		    back_scale = C_BACK_SCALE_LIGHT;
+		else
+		    pm_usage(usage);
+		}
+	    else if ( pm_keymatch(argv[argn],"-gamma",2) && argn + 1 < argc )
+		{
+		++argn;
+		if ( sscanf( argv[argn], "%d",&gamma ) != 1 )
+		    pm_usage( usage );
+		}
+	    else if ( pm_keymatch(argv[argn],"-xpos",2) && argn + 1 < argc )
+		{
+		++argn;
+		if ( sscanf( argv[argn], "%d",&xoff ) != 1 )
+		    pm_usage( usage );
+		}
+	    else if ( pm_keymatch(argv[argn],"-ypos",2) && argn + 1 < argc )
+		{
+		++argn;
+		if ( sscanf( argv[argn], "%d",&yoff ) != 1 )
+		    pm_usage( usage );
+		}
+	    else if (pm_keymatch(argv[argn],"-rle",2))
+		mode = C_TRANS_MODE_RLE;
+	    else if (pm_keymatch(argv[argn],"-center",2))
+		center = 1;
+	    else
+		pm_usage( usage );
+	    ++argn;
+	    }
+
+	if ( argn < argc )
+	    {
+	    ifp = pm_openr( argv[argn] );
+	    ++argn;
+	    }
+	else
+	    ifp = stdin;
+
+	if ( argn != argc )
+	    pm_usage( usage );
+
+	pixels = ppm_readppm( ifp, &cols, &rows, &maxval );
+
+	pm_close( ifp );
+	obuf = (unsigned char *) pm_allocrow(cols, sizeof(unsigned char));
+	cbuf = (unsigned char *) pm_allocrow(cols * 2, sizeof(unsigned char));
+
+        if (cols > XPIX || rows > YPIX)
+	    pm_message("image too large for page");
+        if (center) {
+	    if (xoff || yoff)
+		pm_error("cannot specify both center and position");
+	    xoff = (XPIX - cols) / 2;
+	    yoff = (YPIX - rows) / 2;
+	}
+
+	(void) printf(C_RESET);
+	/*
+	 * Set the resolution before begin raster otherwise it
+	 * does not work.
+	 */
+	(void) printf(C_RESOLUTION, resolution);
+	(void) printf(C_BEGIN_RASTER, posscale);
+	if (xoff)
+	    (void) printf(C_MOVE_X, xoff);
+	if (yoff)
+	    (void) printf(C_MOVE_Y, yoff);
+	(void) printf(C_TRANS_MODE, mode);
+	(void) printf(C_RENDER, render_mode);
+	(void) printf(C_BACK_SCALE, back_scale);
+	(void) printf(C_GAMMA,	 gamma);
+	(void) printf(C_IMAGE_WIDTH, cols);
+	(void) printf(C_IMAGE_HEIGHT, rows);
+	(void) printf(C_DATA_PLANES, 3);
+
+        for (r = 0; r < rows; r++)
+	    /* for each primary */
+	    for (p = 0; p < 3; p++) {
+		switch (p) {
+		case 0:
+		    for (c = 0, op = &obuf[-1]; c < cols; c++) {
+			if ((k = (c & 7)) == 0)
+			    *++op = 0;
+			if (PPM_GETR(pixels[r][c]) > maxval / 2)
+			    *op |= 1 << (7 - k);
+		    }
+		    break;
+		case 1:
+		    for (c = 0, op = &obuf[-1]; c < cols; c++) {
+			if ((k = (c & 7)) == 0)
+			    *++op = 0;
+			if (PPM_GETG(pixels[r][c]) > maxval / 2)
+			    *op |= 1 << (7 - k);
+		    }
+		    break;
+		case 2:
+		    for (c = 0, op = &obuf[-1]; c < cols; c++) {
+			if ((k = (c & 7)) == 0)
+			    *++op = 0;
+			if (PPM_GETB(pixels[r][c]) > maxval / 2)
+			    *op |= 1 << (7 - k);
+		    }
+		    break;
+		}
+		++op;
+		if (mode == C_TRANS_MODE_RLE) {
+		    k = compress_row(obuf, op, cbuf);
+		    op = cbuf;
+		}
+		else {
+		    k = op - obuf;
+		    op = obuf;
+		}
+		(void) printf(p == 2 ? C_LAST_PLANE : C_SEND_PLANE, k);
+		(void) fwrite(op, 1, k, stdout);
+	    }
+	(void) printf(C_END_RASTER, C_END_RASTER_UNUSED);
+	exit(0);
+}
diff --git a/converter/ppm/ppmtopjxl.c b/converter/ppm/ppmtopjxl.c
new file mode 100644
index 00000000..72d299fd
--- /dev/null
+++ b/converter/ppm/ppmtopjxl.c
@@ -0,0 +1,423 @@
+/* ppmtopcl.c - convert portable pixmap into PCL language for HP PaintJet and
+ *              PaintJet XL color printers
+ * AJCD 12/3/91
+ * 
+ * usage:
+ *       ppmtopcl [-nopack] [-gamma <n>] [-presentation] [-dark]
+ *          [-diffuse] [-cluster] [-dither]
+ *          [-xshift <s>] [-yshift <s>]
+ *          [-xshift <s>] [-yshift <s>]
+ *          [-xsize|-width|-xscale <s>] [-ysize|-height|-yscale <s>]
+ *          [ppmfile]
+ *
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "nstring.h"
+
+#define MAXCOLORS 1024
+
+const char * const usage="[-nopack] [-gamma <n>] [-presentation] [-dark]\n\
+            [-diffuse] [-cluster] [-dither]\n\
+            [-xshift <s>] [-yshift <s>]\n\
+            [-xshift <s>] [-yshift <s>]\n\
+            [-xsize|-width|-xscale <s>] [-ysize|-height|-yscale <s>]\n\
+            [ppmfile]";
+
+#define PCL_MAXWIDTH 2048
+#define PCL_MAXHEIGHT 32767
+#define PCL_MAXVAL 255
+
+static int nopack = 0;
+static int dark = 0;
+static int diffuse = 0;
+static int dither = 0;
+static int cluster = 0;
+static int xsize = 0;
+static int ysize = 0;
+static int xshift = 0;
+static int yshift = 0;
+static int quality = 0;
+static double xscale = 0.0;
+static double yscale = 0.0;
+static double gamma_val = 0.0;
+
+/* argument types */
+#define DIM 0
+#define REAL 1
+#define BOOL 2
+static const struct options {
+    const char *name;
+    int type;
+    void *value;
+} options[] = {
+   {"-gamma",        REAL, &gamma_val },
+   {"-presentation", BOOL, &quality },
+   {"-width",        DIM,  &xsize },
+   {"-xsize",        DIM,  &xsize },
+   {"-height",       DIM,  &ysize },
+   {"-ysize",        DIM,  &ysize },
+   {"-xscale",       REAL, &xscale },
+   {"-yscale",       REAL, &yscale },
+   {"-xshift",       DIM,  &xshift },
+   {"-yshift",       DIM,  &yshift },
+   {"-dark",         BOOL, &dark },
+   {"-diffuse",      BOOL, &diffuse },
+   {"-dither",       BOOL, &dither },
+   {"-cluster",      BOOL, &cluster },
+   {"-nopack",       BOOL, &nopack },
+};
+
+#define putword(w) (putchar(((w)>>8) & 255), putchar((w) & 255))
+
+static int 
+bitsperpixel(int v) {
+   int bpp = 0;
+   while (v > 0) {  /* calculate # bits for value */
+      ++bpp;
+      v>>=1;
+   }
+   return (bpp);
+}
+
+
+
+static char *inrow = NULL;
+static char *outrow = NULL;
+/* "signed" was commented out below, but it caused warnings on an SGI 
+   compiler, which defaulted to unsigned character.  2001.03.30 BJH */
+static signed char *runcnt = NULL;
+
+static void 
+putbits(b, n) {
+    /* put #n bits in b out, packing into bytes; n=0 flushes bits */
+    /* n should never be > 8 */
+
+   static int out = 0;
+   static int cnt = 0;
+   static int num = 0;
+   static int pack = 0;
+   if (n) {
+      int xo = 0;
+      int xc = 0;
+      if (cnt+n > 8) {  /* overflowing current byte? */
+	 xc = cnt + n - 8;
+	 xo = (b & ~(-1 << xc)) << (8-xc);
+	 n -= xc;
+	 b >>= xc;
+      }
+      cnt += n;
+      out |= (b & ~(-1 << n)) << (8-cnt);
+      if (cnt >= 8) {
+	 inrow[num++] = out;
+	 out = xo;
+	 cnt = xc;
+      }
+   } else { /* flush row */
+      int i;
+      if (cnt) {
+	 inrow[num++] = out;
+	 out = cnt = 0;
+      }
+      for (; num > 0 && inrow[num-1] == 0; num--); /* remove trailing zeros */
+      printf("\033*b"); 
+      if (num && !nopack) {            /* TIFF 4.0 packbits encoding */
+	 int start = 0;
+	 int next;
+	 runcnt[start] = 0;
+	 for (i = 1; i < num; i++) {
+	    if (inrow[i] == inrow[i-1]) {
+	       if (runcnt[start] <= 0 && runcnt[start] > -127)
+		  runcnt[start]--;
+	       else
+		  runcnt[start = i] = 0;
+	    } else {
+	       if (runcnt[start] >= 0 && runcnt[start] < 127)
+		  runcnt[start]++;
+	       else
+		  runcnt[start = i] = 0;
+	    }
+	 }
+	 start = 0;
+	 for (i = 0; i < num; i = next) {
+	    int count = runcnt[i];
+	    int from = i;
+	    if (count >= 0) { /* merge two-byte runs */
+	       for (;;) {
+		  next = i+1+runcnt[i];
+		  if(next >= num || runcnt[next] < 0 ||
+		     count+runcnt[next]+1 > 127)
+		     break;
+		  count += runcnt[next]+1;
+		  i = next;
+	       }
+	    }
+	    next =  i + 1 + ((runcnt[i] < 0) ? -runcnt[i] : runcnt[i]);
+	    if (next < num && count > 0 &&
+		runcnt[next] < 0 && runcnt[next] > -127) {
+	       count--;
+	       next--;
+	       runcnt[next] = runcnt[next+1]-1;
+	    }
+	    outrow[start++] = count;
+	    if (count >= 0) {
+	       while (count-- >= 0)
+		  outrow[start++] = inrow[from++];
+	    } else
+	       outrow[start++] = inrow[from];
+	 }
+	 if (start < num) {
+	    num = start;
+	    if (!pack) {
+	       printf("2m");
+	       pack = 1;
+	    }
+	 } else {
+	    if (pack) {
+	       printf("0m");
+	       pack = 0;
+	    }
+	 }
+      }
+      printf("%dW", num);
+      for (i = 0; i < num; i++)
+	 putchar(pack ? outrow[i] : inrow[i]);
+      num = 0; /* new row */
+   }
+}
+
+int
+main(argc, argv)
+     int argc;
+     char *argv[];
+{
+   FILE *ifd;
+   register pixel **pixels, *pixrow;
+   register int  row, col, bpp, i;
+   int rows, cols;
+   pixval maxval;
+   int bpr, bpg, bpb;
+   int render;
+   int colors, pclindex;
+   colorhist_vector chv;
+   colorhash_table cht;
+   
+   ppm_init( &argc, argv );
+
+   while (argc > 1 && argv[1][0] == '-') {
+      char *c;
+      for (i = 0; i < sizeof(options)/sizeof(struct options); i++) {
+	 if (pm_keymatch(argv[1], options[i].name,
+			 MIN(strlen(argv[1]), strlen(options[i].name)))) {
+	    switch (options[i].type) {
+	    case DIM:
+	       if (++argv, --argc == 1)
+		  pm_usage(usage);
+	       for (c = argv[1]; ISDIGIT(*c); c++);
+	       if (c[0] == 'p' && c[1] == 't') /* points */
+		  *(int *)(options[i].value) = atoi(argv[1])*10;
+	       else if (c[0] == 'd' && c[1] == 'p') /* decipoints */
+		  *(int *)(options[i].value) = atoi(argv[1]);
+	       else if (c[0] == 'i' && c[1] == 'n') /* inches */
+		  *(int *)(options[i].value) = atoi(argv[1])*720;
+	       else if (c[0] == 'c' && c[1] == 'm') /* centimetres */
+		  *(int *)(options[i].value) = atoi(argv[1])*283.46457;
+	       else if (!c[0]) /* dots */
+		  *(int *)(options[i].value) = atoi(argv[1])*4;
+	       else
+		  pm_error("illegal unit of measure %s", c);
+	       break;
+	    case REAL:
+	       if (++argv, --argc == 1)
+		  pm_usage(usage);
+	       *(double *)(options[i].value) = atof(argv[1]);
+	       break;
+	    case BOOL:
+	       *(int *)(options[i].value) = 1;
+	       break;
+	    }
+	    break;
+	 }
+      }
+      if (i >= sizeof(options)/sizeof(struct options))
+	 pm_usage(usage);
+      argv++; argc--;
+   }
+   if (argc > 2)
+      pm_usage(usage);
+   else if (argc == 2)
+      ifd = pm_openr(argv[1]);
+   else
+      ifd = stdin ;
+
+   /* validate arguments */
+   if (diffuse+cluster+dither > 1)
+      pm_error("only one of -diffuse, -dither and -cluster may be used");
+   render = diffuse ? 4 : dither ? 3 : cluster ? 7 : 0;
+
+   if (xsize != 0.0 && xscale != 0.0)
+      pm_error("only one of -xsize and -xscale may be used");
+
+   if (ysize != 0.0 && yscale != 0.0)
+      pm_error("only one of -ysize and -yscale may be used");
+
+   pixels = ppm_readppm( ifd, &cols, &rows, &maxval );
+   pm_close( ifd );
+
+   /* limit checks */
+   if (cols > PCL_MAXWIDTH || rows > PCL_MAXHEIGHT)
+      pm_error("image too large; reduce with ppmscale");
+   if (maxval > PCL_MAXVAL)
+      pm_error("color range too large; reduce with ppmcscale");
+
+   /* Figure out the colormap. */
+   fprintf( stderr, "(Computing colormap..." ); fflush( stderr );
+   chv = ppm_computecolorhist( pixels, cols, rows, MAXCOLORS, &colors );
+   if ( chv == (colorhist_vector) 0 )
+      pm_error("too many colors; reduce with pnmquant");
+   fprintf( stderr, "  Done.  %d colors found.)\n", colors );
+
+   /* And make a hash table for fast lookup. */
+   cht = ppm_colorhisttocolorhash( chv, colors );
+
+   /* work out color downloading mode */
+   pclindex = bitsperpixel(colors);
+   if (pclindex > 8) /* can't use indexed mode */
+      pclindex = 0;
+   else
+      switch (pclindex) { /* round up to 1,2,4,8 */
+      case 0: /* direct mode (no palette) */
+	 bpp = bitsperpixel(maxval); /* bits per pixel */
+	 bpg = bpp; bpb = bpp;
+	 bpp = (bpp*3+7)>>3;     /* bytes per pixel now */
+	 bpr = (bpp<<3)-bpg-bpb; 
+	 bpp *= cols;            /* bytes per row now */
+	 break;
+      case 5:         pclindex++;
+      case 6:         pclindex++;
+      case 3: case 7: pclindex++;
+      default:
+	 bpp = 8/pclindex;
+	 bpp = (cols+bpp-1)/bpp;      /* bytes per row */
+      }
+
+   if ((inrow = (char *)malloc((unsigned)bpp)) == NULL ||
+       (outrow = (char *)malloc((unsigned)bpp*2)) == NULL ||
+       (runcnt = (signed char *)malloc((unsigned)bpp)) == NULL)
+      pm_error("can't allocate space for row");
+
+   /* set up image details */
+   if (xscale != 0.0)
+      xsize = cols * xscale * 4;
+   if (yscale != 0.0)
+      ysize = rows * yscale * 4;
+
+#ifdef DEBUG
+   fprintf(stderr, "dark    =%d\n", dark);
+   fprintf(stderr, "diffuse =%d\n", diffuse);
+   fprintf(stderr, "dither  =%d\n", dither);
+   fprintf(stderr, "cluster =%d\n", cluster);
+   fprintf(stderr, "quality =%d\n", quality);
+   fprintf(stderr, "xsize   =%d\n", xsize);
+   fprintf(stderr, "ysize   =%d\n", ysize);
+   fprintf(stderr, "xshift  =%d\n", xshift);
+   fprintf(stderr, "yshift  =%d\n", yshift);
+   fprintf(stderr, "xscale  =%lf\n", xscale);
+   fprintf(stderr, "yscale  =%lf\n", yscale);
+   fprintf(stderr, "gamma   =%lf\n", gamma_val);
+   fprintf(stderr, "pclindex   =%d\n", pclindex);
+   fprintf(stderr, "nopack  =%d\n", nopack);
+#endif
+
+   /* write PCL header */
+#if 0
+   printf("\033&l26A");                         /* paper size */
+#endif
+   printf("\033*r%ds%dT", cols, rows);          /* source width, height */
+   if (xshift != 0 || yshift != 0)
+      printf("\033&a%+dh%+dV", xshift, yshift); /* xshift, yshift */
+   if (quality)
+      printf("\033*o%dQ", quality);             /* print quality */
+   printf("\033*t");
+   if (xsize == 0 && ysize == 0)
+      printf("180r");                   /* resolution */
+   else {                               /* destination width, height */
+      if (xsize != 0)
+	 printf("%dh", xsize);
+      if (ysize != 0)
+	 printf("%dv", ysize);
+   }
+   if (gamma_val != 0)
+      printf("%.3fi", gamma_val);                    /* gamma correction */
+   if (dark)
+      printf("%dk", dark);              /* scaling algorithms */
+   printf("%dJ", render);               /* rendering algorithm */
+   printf("\033*v18W");                           /* configure image data */
+      putchar(0); /* relative colors */
+      putchar(pclindex ? 1 : 3); /* index/direct pixel mode */
+      putchar(pclindex); /* ignored in direct pixel mode */
+      if (pclindex) {
+	 putchar(0);
+	 putchar(0);
+	 putchar(0);
+      } else {
+	 putchar(bpr); /* bits per red */
+	 putchar(bpg); /* bits per green */
+	 putchar(bpb); /* bits per blue */
+      }
+      putword(maxval); /* max red reference */
+      putword(maxval); /* max green reference */
+      putword(maxval); /* max blue reference */
+      putword(0); /* min red reference */
+      putword(0); /* min green reference */
+      putword(0); /* min blue reference */
+   if (pclindex) {                        /* set palette */
+      for (i = 0; i < colors; i++) {
+	 int r, g, b;
+	 r = PPM_GETR( chv[i].color );
+	 g = PPM_GETG( chv[i].color );
+	 b = PPM_GETB( chv[i].color );
+	 if (i == 0)
+	    printf("\033*v");
+	 if (r)
+	    printf("%da", r);
+	 if (g)
+	    printf("%db", g);
+	 if (b)
+	    printf("%dc", b);
+	 if (i == colors-1)
+	    printf("%dI", i);    /* assign color index */
+	 else
+	    printf("%di", i);    /* assign color index */
+      }
+   }
+   ppm_freecolorhist( chv );
+
+   /* start raster graphics at CAP */
+   printf("\033*r%dA", (xsize != 0 || ysize != 0) ? 3 : 1);
+
+   for (row = 0; row < rows; row++) {
+      if (pclindex) { /* indexed color mode */
+	 int out, cnt;
+	 out = cnt = 0;
+	 for (col = 0, pixrow=pixels[row]; col < cols; col++, pixrow++) {
+	    putbits(ppm_lookupcolor( cht, pixrow ), pclindex);
+	 }
+	 putbits(0, 0); /* flush row */
+      } else { /* direct color mode */
+	 for (col = 0, pixrow=pixels[row]; col < cols; col++, pixrow++) {
+	    putbits(PPM_GETR( *pixrow ), bpr);
+	    putbits(PPM_GETG( *pixrow ), bpg);
+	    putbits(PPM_GETB( *pixrow ), bpb); /* don't need to flush */
+	 }
+	 putbits(0, 0); /* flush row */
+      }
+   }
+   printf("\033*rC"); /* end raster graphics */
+   exit(0);
+}
diff --git a/converter/ppm/ppmtoppm.c b/converter/ppm/ppmtoppm.c
new file mode 100644
index 00000000..500c9856
--- /dev/null
+++ b/converter/ppm/ppmtoppm.c
@@ -0,0 +1,44 @@
+/*----------------------------------------------------------------------------
+                               ppmtoppm
+------------------------------------------------------------------------------
+  Part of the Netpbm package.
+
+  Copy PPM image from Standard Input to Standard Output
+
+
+  By Bryan Henderson, San Jose CA 2002.09.07
+
+  Contributed to the public domain by its author 2002.09.07
+-----------------------------------------------------------------------------*/
+
+#include "ppm.h"
+
+int
+main(int argc, char *argv[]) {
+    int format;
+    int rows, cols;
+    pixval maxval;
+    int row;
+    pixel* pixelrow;
+    
+    ppm_init(&argc, argv);
+
+    if (argc-1 != 0)
+        pm_error("Program takes no arguments.  Input is from Standard Input");
+
+    ppm_readppminit(stdin, &cols, &rows, &maxval, &format);
+
+    ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+    pixelrow = ppm_allocrow(cols);
+
+    for (row = 0; row < rows; row++) {
+        ppm_readppmrow(stdin, pixelrow, cols, maxval, format);
+        ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
+    }
+    ppm_freerow(pixelrow);
+
+    pm_close(stdin);
+
+    exit(0);
+}
diff --git a/converter/ppm/ppmtopuzz.c b/converter/ppm/ppmtopuzz.c
new file mode 100644
index 00000000..1277cc20
--- /dev/null
+++ b/converter/ppm/ppmtopuzz.c
@@ -0,0 +1,96 @@
+/* ppmtopuzz.c - read a portable pixmap and write an X11 "puzzle" file
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+
+#define MAXVAL 255
+#define MAXCOLORS 256
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    pixel** pixels;
+    register pixel* pP;
+    colorhist_vector chv;
+    colorhash_table cht;
+    int rows, cols, row, colors, i;
+    register int col;
+    pixval maxval;
+
+
+    ppm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[ppmfile]" );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    pixels = ppm_readppm( ifp, &cols, &rows, &maxval );
+    pm_close( ifp );
+
+    pm_message( "computing colormap..." );
+    chv = ppm_computecolorhist( pixels, cols, rows, MAXCOLORS, &colors );
+    if ( chv == (colorhist_vector) 0 )
+	{
+	pm_message(
+	    "too many colors - try doing a 'pnmquant %d'", MAXCOLORS );
+	exit( 1 );
+	}
+    pm_message( "%d colors found", colors );
+
+    /* Write puzzle header. */
+    (void) pm_writebiglong( stdout, cols );
+    (void) pm_writebiglong( stdout, rows );
+    (void) putchar( (unsigned char) colors );
+    if ( maxval > MAXVAL )
+	pm_message(
+	    "maxval is not %d - automatically rescaling colors", MAXVAL );
+    for ( i = 0; i < colors; ++i )
+	{
+	pixel p;
+
+	p = chv[i].color;
+	if ( maxval != MAXVAL )
+	    PPM_DEPTH( p, p, maxval, MAXVAL );
+	(void) putchar( (unsigned char) PPM_GETR( p ) );
+	(void) putchar( (unsigned char) PPM_GETG( p ) );
+	(void) putchar( (unsigned char) PPM_GETB( p ) );
+	}
+
+    /* Convert color vector to color hash table, for fast lookup. */
+    cht = ppm_colorhisttocolorhash( chv, colors );
+    ppm_freecolorhist( chv );
+
+    /* And write out the data. */
+    for ( row = 0; row < rows; ++row )
+	{
+	for ( col = 0, pP = pixels[row]; col < cols; ++col, ++pP )
+	    {
+	    register int color;
+
+	    color = ppm_lookupcolor( cht, pP );
+	    if ( color == -1 )
+		pm_error(
+		    "color not found?!?  row=%d col=%d  r=%d g=%d b=%d",
+		    row, col, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP) );
+	    (void) putchar( (unsigned char) color );
+	    }
+	}
+
+    exit( 0 );
+    }
diff --git a/converter/ppm/ppmtorgb3.c b/converter/ppm/ppmtorgb3.c
new file mode 100644
index 00000000..21d3346a
--- /dev/null
+++ b/converter/ppm/ppmtorgb3.c
@@ -0,0 +1,99 @@
+/* ppmtorgb3.c - separate a portable pixmap into three portable graymaps
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+#include "ppm.h"
+#include "pgm.h"
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    FILE* redfile;
+    FILE* grnfile;
+    FILE* blufile;
+    const char* basename;
+    char filename[100];
+    char* cp;
+    pixel* pixelrow;
+    register pixel* pP;
+    gray* grayrow;
+    register gray* gP;
+    int rows, cols, format, row;
+    register int col;
+    pixval maxval;
+
+
+    ppm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[ppmfile]" );
+
+    if ( argc == 2 )
+	{
+	ifp = pm_openr( argv[1] );
+	basename = argv[1];
+	cp = strrchr( basename, '.' );
+	if ( cp != NULL )
+	    *cp = '\0';
+	}
+    else
+	{
+	ifp = stdin;
+	basename = "noname";
+	}
+
+    ppm_readppminit( ifp, &cols, &rows, &maxval, &format );
+    pixelrow = ppm_allocrow( cols );
+    (void) strcpy( filename, basename );
+    (void) strcat( filename, ".red" );
+    redfile = pm_openw( filename );
+    pgm_writepgminit( redfile, cols, rows, (gray) maxval, 0 );
+    (void) strcpy( filename, basename );
+    (void) strcat( filename, ".grn" );
+    grnfile = pm_openw( filename );
+    pgm_writepgminit( grnfile, cols, rows, (gray) maxval, 0 );
+    (void) strcpy( filename, basename );
+    (void) strcat( filename, ".blu" );
+    blufile = pm_openw( filename );
+    pgm_writepgminit( blufile, cols, rows, (gray) maxval, 0 );
+    grayrow = pgm_allocrow( cols );
+
+    for ( row = 0; row < rows; ++row )
+	{
+	ppm_readppmrow( ifp, pixelrow, cols, maxval, format );
+
+	for ( col = 0, pP = pixelrow, gP = grayrow; col < cols;
+	      ++col, ++pP, ++gP )
+	    *gP = (gray) PPM_GETR( *pP );
+	pgm_writepgmrow( redfile, grayrow, cols, maxval, 0 );
+
+	for ( col = 0, pP = pixelrow, gP = grayrow; col < cols;
+	      ++col, ++pP, ++gP )
+	    *gP = (gray) PPM_GETG( *pP );
+	pgm_writepgmrow( grnfile, grayrow, cols, maxval, 0 );
+
+	for ( col = 0, pP = pixelrow, gP = grayrow; col < cols;
+	      ++col, ++pP, ++gP )
+	    *gP = (gray) PPM_GETB( *pP );
+	pgm_writepgmrow( blufile, grayrow, cols, maxval, 0 );
+	}
+
+    pm_close( ifp );
+    pm_close( redfile );
+    pm_close( blufile );
+    pm_close( grnfile );
+
+    exit( 0 );
+    }
diff --git a/converter/ppm/ppmtosixel.c b/converter/ppm/ppmtosixel.c
new file mode 100644
index 00000000..5f361e45
--- /dev/null
+++ b/converter/ppm/ppmtosixel.c
@@ -0,0 +1,215 @@
+/* ppmtosix.c - read a portable pixmap and produce a color sixel file
+**
+** Copyright (C) 1991 by Rick Vinci.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+
+static void WriteHeader ARGS((void));
+static void WriteColorMap 
+  ARGS((colorhist_vector chv, int colors, pixval maxval));
+static void WriteRawImage ARGS((colorhash_table cht, int rows, int cols));
+static void WritePackedImage ARGS((colorhash_table cht, int rows, int cols));
+static void WriteEnd ARGS((void));
+#define MAXVAL 100
+#define MAXCOLORS 256
+
+#define DCS '\220'   /* Device Control String */
+#define ST  '\234'   /* String Terminator */
+#define CSI '\233'   /* Control String Introducer */
+#define ESC '\033'   /* Escape character */
+
+static pixel** pixels;   /* stored ppm pixmap input */
+static colorhash_table cht;
+int margin;
+
+
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+{
+    FILE* ifp;
+    int argn, rows, cols, colors;
+    int raw;
+    pixval maxval;
+    colorhist_vector chv;
+    const char* const usage = "[-raw] [-margin] [ppmfile]";
+
+
+    ppm_init( &argc, argv );
+
+    argn = 1;
+    raw = 0;
+    margin = 0;
+
+    /* Parse args. */
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+	{
+	if ( pm_keymatch( argv[argn], "-raw", 2 ) )
+	    raw = 1;
+	else if ( pm_keymatch( argv[argn], "-margin", 2 ) )
+	    margin = 1;
+	else
+	    pm_usage( usage );
+	++argn;
+	}
+
+    if ( argn < argc )
+	{
+	ifp = pm_openr( argv[argn] );
+	++argn;
+	}
+    else
+	ifp = stdin;
+
+    if ( argn != argc )
+	pm_usage( usage );
+
+    /* Read in the whole ppmfile. */
+    pixels = ppm_readppm( ifp, &cols, &rows, &maxval );
+    pm_close( ifp );
+
+    /* Print a warning if we could lose accuracy when rescaling colors. */
+    if ( maxval > MAXVAL )
+	pm_message(
+	    "maxval is not %d - automatically rescaling colors", MAXVAL );
+
+    /* Figure out the colormap. */
+    pm_message( "computing colormap..." );
+    chv = ppm_computecolorhist( pixels, cols, rows, MAXCOLORS, &colors );
+    if ( chv == (colorhist_vector) 0 )
+	pm_error( "too many colors - try doing a 'pnmquant %d'", MAXCOLORS );
+    pm_message( "%d colors found", colors );
+
+    /* Make a hash table for fast color lookup. */
+    cht = ppm_colorhisttocolorhash( chv, colors );
+
+    WriteHeader();
+    WriteColorMap( chv, colors, maxval );
+    if ( raw == 1 )
+	WriteRawImage( cht, rows, cols );
+    else
+	WritePackedImage( cht, rows, cols );
+    WriteEnd();
+
+    /* If the program failed, it previously aborted with nonzero completion
+       code, via various function calls.
+    */
+    return 0;
+}
+
+
+
+static void
+WriteHeader()
+    {
+    if ( margin == 1 )
+	printf( "%c%d;%ds", CSI, 14, 72 );
+    printf( "%c", DCS );  /* start with Device Control String */
+    printf( "0;0;8q" );   /* Horizontal Grid Size at 1/90" and graphics On */
+    printf( "\"1;1\n" );  /* set aspect ratio 1:1 */
+    }
+
+
+
+static void
+WriteColorMap( colorhist_vector chv, int colors, pixval maxval )
+    {
+    register int colornum;
+    pixel p;
+
+    for ( colornum = 0; colornum < colors ; ++colornum )
+	{
+	p = chv[colornum].color;
+	if ( maxval != MAXVAL )
+	    PPM_DEPTH( p, p, maxval, MAXVAL );
+	printf( "#%d;2;%d;%d;%d", colornum,
+	    (int) PPM_GETR( p ), (int) PPM_GETG( p ), (int) PPM_GETB( p ) );
+	}
+    printf( "\n" );
+    }
+
+
+
+static void
+WriteRawImage( cht, rows, cols )
+    colorhash_table cht;
+    int rows, cols;
+    {
+    int rownum, colnum, b;
+    const char* sixel = "@ACGO_";
+    register pixel* pP;
+
+    for ( rownum = 0; rownum < rows; ++rownum )
+	{
+	b = rownum % 6;
+	for ( colnum = 0, pP = pixels[rownum]; colnum < cols; ++colnum, ++pP )
+	    printf( "#%d%c", ppm_lookupcolor(cht, pP), sixel[b] );
+	printf( "$\n" );   /* Carriage Return */
+	if ( b == 5 )
+	    printf( "-\n" );   /* Line Feed (one sixel height) */
+	}
+    }
+
+
+
+static void
+WritePackedImage( cht, rows, cols )
+    colorhash_table cht;
+    int rows, cols;
+    {
+    int rownum, colnum, b, repeat, thiscolor, nextcolor;
+    const char* sixel = "@ACGO_";
+    register pixel* pP;
+
+    for ( rownum = 0; rownum < rows; ++rownum )
+	{
+	b = rownum % 6;
+	repeat = 1;
+	for ( colnum = 0, pP = pixels[rownum]; colnum < cols; ++colnum, ++pP )
+	    {
+	    thiscolor = ppm_lookupcolor(cht, pP);
+	    if ( colnum == cols -1 )   /* last pixel in row */
+		if ( repeat == 1 )
+		    printf( "#%d%c", thiscolor, sixel[b] );
+		else
+		    printf( "#%d!%d%c", thiscolor, repeat, sixel[b] );
+	    else   /* not last pixel in row */
+		{
+		nextcolor =  ppm_lookupcolor(cht, pP+1);
+		if ( thiscolor == nextcolor )
+		    ++repeat;
+		else
+		    if ( repeat == 1 )
+			printf( "#%d%c", thiscolor, sixel[b] );
+		    else
+		    {
+		    printf( "#%d!%d%c", thiscolor, repeat, sixel[b] );
+		    repeat = 1;
+		    }
+		}
+	    }   /* end column loop */
+	printf( "$\n" );   /* Carriage Return */
+	if ( b == 5 )
+	    printf( "-\n" );   /* Line Feed (one sixel height) */
+	}
+    }
+
+
+
+static void
+WriteEnd()
+    {
+    if ( margin == 1 )
+	printf ( "%c%d;%ds", CSI, 1, 80 );
+    printf( "%c\n", ST );
+    }
diff --git a/converter/ppm/ppmtoterm.c b/converter/ppm/ppmtoterm.c
new file mode 100644
index 00000000..72105705
--- /dev/null
+++ b/converter/ppm/ppmtoterm.c
@@ -0,0 +1,173 @@
+/* ppmtoterm.c - convert a portable pixmap into an ISO 6429 (ANSI) color ascii
+** text.
+**
+**  Copyright (C) 2002 by Ero Carrera (ecarrera@lightforge.com)
+**  Partially based on,
+**      ppmtoyuv.c by Marc Boucher,
+**      ppmtolj.c by Jonathan Melvin and
+**      ppmtogif.c by Jef Poskanzer
+**
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** 14/Aug/2002: First version.
+**
+*/
+
+#include <string.h>
+
+#include "ppm.h"
+#include "shhopt.h"
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, char **argv,
+                 struct cmdlineInfo *cmdlineP) {
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions3 on how to parse our options */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "verbose", OPT_FLAG, NULL, &cmdlineP->verbose, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    switch (argc-1) {
+    case 0:
+        cmdlineP->inputFilespec = "-";
+        break;
+    case 1:
+        cmdlineP->inputFilespec = argv[1];
+        break;
+    case 2:
+        break;
+    }
+}
+
+
+#define ESC         "\x1B\x5B"
+#define NUM_COLORS      128
+#define MAX_ANSI_STR_LEN    16
+
+
+static int __inline__ sqr(const int x) {
+    return x*x;
+}
+
+/*
+    Generates some sort of color palette mixing the available
+    colors as different values of background, foreground & brightness.
+*/
+static int 
+generate_palette(unsigned char rgb[NUM_COLORS][3], 
+                 char ansi_code[NUM_COLORS][MAX_ANSI_STR_LEN]) {
+    int code, col=0, cd2=0;
+    
+    memset((void *)rgb, 0, NUM_COLORS*3);
+    memset((void *)ansi_code, 0, NUM_COLORS*MAX_ANSI_STR_LEN);
+
+    for(col=cd2=0; cd2<8; cd2++) {
+        unsigned int b;
+        for(b=0;b<2;b++) {
+            for(code=0; code<8; code++) {
+                unsigned int c;
+                for(c=0;c<3;c++) {
+                    if(code&(1<<c)) {
+                        rgb[col][c]=(192|(b?63:0));
+                    }
+                    if(cd2&(1<<c)) {
+                        rgb[col][c]|=(128);
+                    }
+                }
+                sprintf(ansi_code[col],
+                        ESC"%dm"ESC"3%dm"ESC"4%dm",
+                        b, code, cd2);
+                col++;
+            }
+        }
+    }
+    return col;
+}
+
+
+
+int main(int argc, char **argv)
+{
+    FILE            *ifp;
+    pixel           **pixels;
+    int             rows, row, cols, col,
+                    pal_len, i;
+    pixval          maxval;
+    struct cmdlineInfo
+                    cmdline;
+    unsigned char   rgb[NUM_COLORS][3];
+    char            ansi_code[NUM_COLORS][MAX_ANSI_STR_LEN];
+
+    
+    ppm_init(&argc, argv);    
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifp = pm_openr(cmdline.inputFilespec);
+    
+    pixels = ppm_readppm(ifp, &cols, &rows, &maxval);
+
+    pm_close(ifp);
+        
+    pal_len=generate_palette(rgb, ansi_code);
+    
+    for (row = 0; row < rows; ++row) {
+        for (col = 0; col < cols; col++) {
+            pixval const r=(int)PPM_GETR(pixels[row][col])*255/maxval;
+            pixval const g=(int)PPM_GETG(pixels[row][col])*255/maxval;
+            pixval const b=(int)PPM_GETB(pixels[row][col])*255/maxval;
+            int val, dist;
+            
+            /*
+            The following loop calculates the index that
+            corresponds to the minimum color distance
+            between the given RGB values and the values
+            available in the palette.
+            */
+            for(i=0, dist=sqr(255)*3, val=0; i<pal_len; i++) {
+                pixval const pr=rgb[i][0];
+                pixval const pg=rgb[i][1];
+                pixval const pb=rgb[i][2];
+                unsigned int j;
+                if( (j=sqr(r-pr)+sqr(b-pb)+sqr(g-pg))<dist ) {
+                    dist=j;
+                    val=i;
+                }
+            }
+            printf("%s%c", ansi_code[val],0xB1);
+        }
+        printf(ESC"\x30m\n");
+    }
+    printf(ESC"\x30m");
+
+    ppm_freearray(pixels, rows);
+    
+    exit(0);
+}
diff --git a/converter/ppm/ppmtowinicon.c b/converter/ppm/ppmtowinicon.c
new file mode 100644
index 00000000..bcaa0e08
--- /dev/null
+++ b/converter/ppm/ppmtowinicon.c
@@ -0,0 +1,881 @@
+/* ppmtowinicon.c - read portable pixmap file(s) and write a MS Windows .ico
+**
+** Copyright (C) 2000 by Lee Benfield - lee@benf.org
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <math.h>
+#include <string.h>
+
+#include "winico.h"
+#include "ppm.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "nstring.h"
+
+#define MAJVERSION 0
+#define MINVERSION 3
+
+#define MAXCOLORS 256
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    unsigned int iconCount;
+    const char **inputFilespec;  /* '-' if stdin; malloc'ed array */
+    const char **andpgmFilespec;    /* NULL if unspecified; malloc'ed array */
+    const char *output;     /* '-' if unspecified */
+    unsigned int truetransparent;
+    unsigned int verbose;
+};
+
+
+static bool verbose;
+
+static int      file_offset = 0; /* not actually used, but useful for debug. */
+static FILE *   ofp;
+
+static void
+parseCommandLine(int                 argc, 
+                 char **             argv,
+                 struct cmdlineInfo *cmdlineP ) {
+/*----------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int outputSpec, andpgms;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "output",     OPT_STRING, &cmdlineP->output,
+            &outputSpec,                   0);
+    OPTENT3(0, "andpgms",    OPT_FLAG,   NULL,
+            &andpgms,                      0);
+    OPTENT3(0, "truetransparent", OPT_FLAG,   NULL,
+            &cmdlineP->truetransparent,    0);
+    OPTENT3(0, "verbose",    OPT_STRING, NULL,
+            &cmdlineP->verbose,            0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!outputSpec)
+        cmdlineP->output = "-";
+
+
+    if (!andpgms) {
+        if (argc-1 == 0) {
+            cmdlineP->iconCount = 1;
+            MALLOCARRAY_NOFAIL(cmdlineP->inputFilespec, cmdlineP->iconCount);
+            cmdlineP->inputFilespec[0] = "-";
+        } else {
+            unsigned int iconIndex;
+
+            cmdlineP->iconCount = argc-1;
+            MALLOCARRAY_NOFAIL(cmdlineP->inputFilespec, cmdlineP->iconCount);
+            for (iconIndex = 0; iconIndex < cmdlineP->iconCount; ++iconIndex)
+                cmdlineP->inputFilespec[iconIndex] = argv[iconIndex+1];
+        }
+        {
+            unsigned int iconIndex;
+            MALLOCARRAY_NOFAIL(cmdlineP->andpgmFilespec, cmdlineP->iconCount);
+            for (iconIndex = 0; iconIndex < cmdlineP->iconCount; ++iconIndex)
+                cmdlineP->andpgmFilespec[iconIndex] = NULL;
+        }
+    } else {
+        if (argc-1 < 2)
+            pm_error("with -andpgms, you must specify at least two "
+                     "arguments: image file name and and mask file name.  "
+                     "You specified %d", argc-1);
+        else if ((argc-1)/2*2 != (argc-1))
+            pm_error("with -andpgms, you must specify an even number of "
+                     "arguments.  You specified %d", argc-1);
+        else {
+            unsigned int iconIndex;
+            cmdlineP->iconCount = (argc-1)/2;
+            MALLOCARRAY_NOFAIL(cmdlineP->inputFilespec, cmdlineP->iconCount);
+            MALLOCARRAY_NOFAIL(cmdlineP->andpgmFilespec, cmdlineP->iconCount);
+            for (iconIndex = 0; iconIndex < cmdlineP->iconCount; ++iconIndex) {
+                cmdlineP->inputFilespec[iconIndex] = argv[1 + iconIndex*2];
+                cmdlineP->andpgmFilespec[iconIndex] = argv[2 + iconIndex*2];
+            }
+        }
+    }
+
+}
+
+
+
+static void 
+PutByte(int const v) {
+   
+   if (putc(v, ofp) == EOF)
+       pm_error("Unable to write byte to output file.");
+}
+   
+
+
+static void 
+PutShort(short const v) {
+   
+   if (pm_writelittleshort(ofp, v) == -1)
+       pm_error("Unable to write short integer to output file");
+}
+   
+
+
+static void 
+PutLong(long const v) {
+   
+   if (pm_writelittlelong(ofp, v) == -1)
+       pm_error("Unable to write long integer to output file");
+}
+
+
+   
+/*
+ * These have no purpose but to wrapper the Byte, Short & Long 
+ * functions.
+ */
+static void 
+writeU1 (u1 const v) {
+   file_offset++;
+   PutByte(v);
+}
+
+static  void 
+writeU2 (u2 const v) {
+   file_offset +=2;
+   PutShort(v);
+}
+
+static void 
+writeU4 (u4 const v) {
+   file_offset += 4;
+   PutLong(v);
+}
+
+static MS_Ico 
+createIconFile (void) {
+   MS_Ico MSIconData;
+   
+   MALLOCVAR_NOFAIL(MSIconData);
+
+   MSIconData->reserved     = 0;
+   MSIconData->type         = 1;
+   MSIconData->count        = 0;
+   MSIconData->entries      = NULL;
+   return MSIconData;
+}
+
+
+/* create andBitmap from pgm */
+
+static ICON_bmp 
+createAndBitmap (gray ** const ba, int const cols, int const rows,
+                 gray const maxval) {
+   /*
+    * How wide should the u1 string for each row be?
+    * each byte is 8 pixels, but must be a multiple of 4 bytes.
+    */
+   ICON_bmp icBitmap;
+   int xBytes,y,x;
+   int wt = cols;
+   u1 ** rowData;
+
+   MALLOCVAR_NOFAIL(icBitmap);
+
+   wt >>= 3;
+   if (wt & 3) {
+      wt = (wt & ~3) + 4;
+   }
+   xBytes = wt;
+   MALLOCARRAY_NOFAIL(rowData, rows);
+   icBitmap->xBytes = xBytes;
+   icBitmap->data   = rowData;
+   icBitmap->size   = xBytes * rows;
+   for (y=0;y<rows;y++) {
+      u1 * row;
+      int byteOn = 0;
+      int bitOn = 128;
+
+      MALLOCARRAY_NOFAIL(row, xBytes);
+
+      memset (row, 0, xBytes);
+      rowData[rows-y-1] = row;
+      /* 
+       * Check there's a bit array, otherwise we're just faking this...
+       */
+      if (ba) {
+     for (x=0;x<cols;x++) {
+            /* Black (bit clear) is transparent in PGM alpha maps,
+             * in ICO bit *set* is transparent.
+             */
+            if (ba[y][x] <= maxval/2) row[byteOn] |= bitOn;
+
+        if (bitOn == 1) {
+           byteOn++;
+           bitOn = 128;
+        } else {
+           bitOn >>= 1;
+        }
+     }
+      }
+   }
+   return icBitmap;
+}
+
+
+/*
+ * Depending on if the image is stored as 1bpp, 4bpp or 8bpp, the 
+ * encoding mechanism is different.
+ * 
+ * I didn't re-use the code from ppmtobmp since I need to keep the
+ * bitmaps in memory till I've loaded all ppms.
+ * 
+ * 8bpp => 1 byte/palette index.
+ * 4bpp => High Nibble, Low Nibble
+ * 1bpp => 1 palette value per bit, high bit 1st.
+ */
+static ICON_bmp 
+create1Bitmap (pixel ** const pa, int const cols, int const rows, 
+               colorhash_table const cht) {
+   /*
+    * How wide should the u1 string for each row be?
+    * each byte is 8 pixels, but must be a multiple of 4 bytes.
+    */
+   ICON_bmp icBitmap;
+   int xBytes,y,x;
+   int wt = cols;
+   u1 ** rowData;
+
+   MALLOCVAR_NOFAIL(icBitmap);
+   
+   wt >>= 3;
+   if (wt & 3) {
+      wt = (wt & ~3) + 4;
+   }
+   xBytes = wt;
+   MALLOCARRAY_NOFAIL(rowData, rows);
+   icBitmap->xBytes = xBytes;
+   icBitmap->data   = rowData;
+   icBitmap->size   = xBytes * rows;
+   for (y=0;y<rows;y++) {
+      u1 * row;
+      int byteOn = 0;
+      int bitOn = 128;
+      int value;
+      
+      MALLOCARRAY_NOFAIL(row, xBytes);
+      memset (row, 0, xBytes);
+      rowData[rows-y-1] = row;
+      /* 
+       * Check there's a pixel array, otherwise we're just faking this...
+       */
+      if (pa) {
+     for (x=0;x<cols;x++) {
+        /*
+         * So we've got a colorhash_table with two colors in it.
+         * Which is black?!
+         * 
+         * Unless the hashing function changes, 0's black.
+         */
+        value = ppm_lookupcolor(cht, &pa[y][x]);
+        if (!value) {
+           /* leave black. */
+        } else {
+           row[byteOn] |= bitOn;
+        }
+        if (bitOn == 1) {
+           byteOn++;
+           bitOn = 128;
+        } else {
+           bitOn >>= 1;
+        }
+     }
+      }
+   }
+   return icBitmap;
+}
+
+
+static ICON_bmp 
+create4Bitmap (pixel ** const pa, int const cols, int const rows,
+               colorhash_table const cht) {
+   /*
+    * How wide should the u1 string for each row be?
+    * each byte is 8 pixels, but must be a multiple of 4 bytes.
+    */
+   ICON_bmp icBitmap;
+   int xBytes,y,x;
+   int wt = cols;
+   u1 ** rowData;
+
+   MALLOCVAR_NOFAIL(icBitmap);
+
+   wt >>= 1;
+   if (wt & 3) {
+      wt = (wt & ~3) + 4;
+   }
+   xBytes = wt;
+   MALLOCARRAY_NOFAIL(rowData, rows);
+   icBitmap->xBytes = xBytes;
+   icBitmap->data   = rowData;
+   icBitmap->size   = xBytes * rows;
+
+   for (y=0;y<rows;y++) {
+      u1 * row;
+      int byteOn = 0;
+      int nibble = 1;   /* high nibble = 1, low nibble = 0; */
+      int value;
+
+      MALLOCARRAY_NOFAIL(row, xBytes);
+
+      memset (row, 0, xBytes);
+      rowData[rows-y-1] = row;
+      /* 
+       * Check there's a pixel array, otherwise we're just faking this...
+       */
+      if (pa) {
+     for (x=0;x<cols;x++) {
+        value = ppm_lookupcolor(cht, &pa[y][x]);
+        /*
+         * Shift it, if we're putting it in the high nibble.
+         */
+        if (nibble) {
+           value <<= 4;
+        }
+        row[byteOn] |= value;
+        if (nibble) {
+           nibble = 0;
+        } else {
+           nibble = 1;
+           byteOn++;
+        }
+     }
+      }
+   }
+   return icBitmap;
+}
+
+
+
+static ICON_bmp 
+create8Bitmap (pixel ** const pa, int const cols, int const rows,
+               colorhash_table const cht) {
+   /*
+    * How wide should the u1 string for each row be?
+    * each byte is 8 pixels, but must be a multiple of 4 bytes.
+    */
+   ICON_bmp icBitmap;
+   int xBytes,y,x;
+   int wt = cols;
+   u1 ** rowData;
+
+   MALLOCVAR_NOFAIL(icBitmap);
+
+   if (wt & 3) {
+      wt = (wt & ~3) + 4;
+   }
+   xBytes = wt;
+   MALLOCARRAY_NOFAIL(rowData, rows);
+   icBitmap->xBytes = xBytes;
+   icBitmap->data   = rowData;
+   icBitmap->size   = xBytes * rows;
+
+   for (y=0;y<rows;y++) {
+      u1 * row;
+
+      MALLOCARRAY_NOFAIL(row, xBytes);
+      memset (row, 0, xBytes);
+      rowData[rows-y-1] = row;
+      /* 
+       * Check there's a pixel array, otherwise we're just faking this...
+       */
+      if (pa) {
+     for (x=0;x<cols;x++) {
+        row[x] = ppm_lookupcolor(cht, &pa[y][x]);
+     }
+      }
+   }
+   return icBitmap;
+}
+
+
+
+static IC_InfoHeader 
+createInfoHeader(IC_Entry const entry, ICON_bmp const xbmp,
+                 ICON_bmp const abmp) {
+   IC_InfoHeader ih;
+   
+   MALLOCVAR_NOFAIL(ih);
+
+   ih->size          = 40;
+   ih->width         = entry->width;
+   ih->height        = entry->height * 2;  
+   ih->planes        = 1;  
+   ih->bitcount      = entry->bitcount;
+   ih->compression   = 0;
+   ih->imagesize     = entry->width * entry->height * 8 / entry->bitcount;
+   ih->x_pixels_per_m= 0;
+   ih->y_pixels_per_m= 0;
+   ih->colors_used   = 1 << entry->bitcount;
+   ih->colors_important = 0;
+   return ih;
+}
+
+
+
+static IC_Palette 
+createCleanPalette(void) {
+   IC_Palette palette;
+   int x;
+   
+   MALLOCVAR_NOFAIL(palette);
+
+   MALLOCARRAY_NOFAIL(palette->colors, MAXCOLORS);
+   for (x=0;x<MAXCOLORS;x++ ){
+      palette->colors[x] = NULL;
+   }
+   return palette;
+}
+
+
+
+static void 
+addColorToPalette(IC_Palette const palette, int const i,
+                  int const r, int const g, int const b) {
+
+    MALLOCVAR_NOFAIL(palette->colors[i]);
+
+    palette->colors[i]->red      = r;
+    palette->colors[i]->green    = g;
+    palette->colors[i]->blue     = b;
+    palette->colors[i]->reserved = 0;
+}
+
+
+
+static ICON_bmp 
+createBitmap (int const bpp, pixel ** const pa, 
+              int const cols, int const rows, 
+              colorhash_table const cht) {
+    
+    ICON_bmp retval;
+    const int assumed_bpp = (pa == NULL) ? 1 : bpp;
+
+    switch (assumed_bpp) {
+    case 1:
+        retval = create1Bitmap (pa,cols,rows,cht);
+        break;
+    case 4:
+        retval = create4Bitmap (pa,cols,rows,cht);
+        break;
+    case 8:
+    default:
+        retval = create8Bitmap (pa,cols,rows,cht);
+        break;
+    }
+    return retval;
+}
+
+
+
+static void
+makePalette(pixel **          const xorPPMarray,
+            int               const xorCols,
+            int               const xorRows,
+            pixval            const xorMaxval,
+            IC_Palette *      const paletteP,
+            colorhash_table * const xorChtP,
+            int *             const colorsP,
+            const char **     const errorP) {
+   /*
+    * Figure out the colormap and turn it into the appropriate GIF
+    * colormap - this code's pretty much straight from ppmtobpm
+    */
+    colorhist_vector xorChv;
+    unsigned int i;
+    int colors;
+    IC_Palette palette = createCleanPalette();
+
+    if (verbose) pm_message("computing colormap...");
+    xorChv = ppm_computecolorhist(xorPPMarray, xorCols, xorRows, MAXCOLORS, 
+                                  &colors);
+    if (xorChv == NULL)
+        asprintfN(errorP,
+                  "image has too many colors - try doing a 'pnmquant %d'",
+                  MAXCOLORS);
+    else {
+        *errorP = NULL;
+
+        if (verbose) pm_message("%d colors found", colors);
+        
+        if (verbose && (xorMaxval > 255))
+            pm_message("maxval is not 255 - automatically rescaling colors");
+        for (i = 0; i < colors; ++i) {
+            if (xorMaxval == 255) {
+                addColorToPalette(palette,i,
+                                  PPM_GETR(xorChv[i].color),
+                                  PPM_GETG(xorChv[i].color),
+                                  PPM_GETB(xorChv[i].color));
+            } else {
+                addColorToPalette(palette,i,
+                                  PPM_GETR(xorChv[i].color) * 255 / xorMaxval,
+                                  PPM_GETG(xorChv[i].color) * 255 / xorMaxval,
+                                  PPM_GETB(xorChv[i].color) * 255 / xorMaxval);
+            }
+        }
+        
+        /* And make a hash table for fast lookup. */
+        *xorChtP = ppm_colorhisttocolorhash(xorChv, colors);
+        
+        ppm_freecolorhist(xorChv);
+        
+        *paletteP = palette;
+        *colorsP = colors;
+    }
+}
+
+
+
+static void
+getOrFakeAndMap(const char *      const andPgmFname,
+                int               const xorCols,
+                int               const xorRows,
+                gray ***          const andPGMarrayP,
+                pixval *          const andMaxvalP,
+                colorhash_table * const andChtP,
+                const char **     const errorP) {
+
+    int andRows, andCols;
+    
+    if (!andPgmFname) {
+        /* He's not supplying a bitmap for 'and'.  Fake the bitmap. */
+        *andPGMarrayP = NULL;
+        *andMaxvalP   = 1;
+        *andChtP      = NULL;
+        *errorP       = NULL;
+    } else {
+        FILE * andfile;
+        andfile = pm_openr(andPgmFname);
+        *andPGMarrayP = pgm_readpgm(andfile, &andCols, &andRows, andMaxvalP);
+        pm_close(andfile);
+
+        if ((andCols != xorCols) || (andRows != xorRows)) {
+            asprintfN(errorP,
+                      "And mask and image have different dimensions "
+                     "(%d x %d vs %d x %d).  Aborting.",
+                     andCols, xorCols, andRows, xorRows);
+        } else
+            *errorP = NULL;
+    }
+}
+
+
+
+static void
+blackenTransparentAreas(pixel ** const xorPPMarray,
+                        int      const cols,
+                        int      const rows,
+                        gray **  const andPGMarray,
+                        pixval   const andMaxval) {
+
+    unsigned int row;
+
+    if (verbose) pm_message("Setting transparent pixels to black");
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            if (andPGMarray[row][col] < andMaxval)
+                /* It's not opaque here; make it black */
+                PPM_ASSIGN(xorPPMarray[row][col], 0, 0, 0);
+        }
+    }
+}
+
+
+
+static void 
+addEntryToIcon(MS_Ico       const MSIconData, 
+               const char * const xorPpmFname,
+               const char * const andPgmFname,
+               bool         const trueTransparent) {
+
+    IC_Entry entry;
+    FILE * xorfile;
+    pixel ** xorPPMarray;
+    gray ** andPGMarray;
+    ICON_bmp xorBitmap;
+    ICON_bmp andBitmap;
+    int rows, cols;
+    int bpp, colors;
+    int entry_cols;
+    IC_Palette palette;
+    colorhash_table  xorCht;
+    colorhash_table  andCht; 
+    const char * error;
+   
+    pixval xorMaxval;
+    gray andMaxval;
+
+    MALLOCVAR_NOFAIL(entry);
+
+   /*
+    * Read the xor PPM.
+    */
+    xorfile = pm_openr(xorPpmFname);
+    xorPPMarray = ppm_readppm(xorfile, &cols, &rows, &xorMaxval);
+    pm_close(xorfile);
+    /*
+    * Since the entry uses 1 byte to hold the width and height of the icon, the
+    * image can't be more than 256 x 256.
+    */
+    if (rows > 255 || cols > 255) {
+        pm_error("Max size for a icon is 255 x 255 (1 byte fields).  "
+                 "%s is %d x %d", xorPpmFname, cols, rows);
+    }
+   
+    if (verbose) pm_message("read PPM: %dw x %dh, maxval = %d", 
+                            cols, rows, xorMaxval);
+
+    makePalette(xorPPMarray, cols, rows, xorMaxval, 
+                &palette, &xorCht, &colors, &error);
+
+    if (error)
+        pm_error("Unable to make palette for '%s'.  %s", xorPpmFname, error);
+   /*
+    * All the icons I found seemed to pad the palette to the max entries
+    * for that bitdepth.
+    * 
+    * The spec indicates this isn't neccessary, but I'll follow this behaviour
+    * just in case.
+    */
+    if (colors < 3) {
+        bpp = 1;
+        entry_cols = 2;
+    } else if (colors < 17) {
+        bpp = 4;
+        entry_cols = 16;
+    } else {
+        bpp = 8;
+        entry_cols = 256;
+    }
+
+    getOrFakeAndMap(andPgmFname, cols, rows,
+                    &andPGMarray, &andMaxval, &andCht, &error);
+    if (error)
+        pm_error("Error in and map for '%s'.  %s", xorPpmFname, error);
+
+    if (andPGMarray && trueTransparent)
+        blackenTransparentAreas(xorPPMarray, cols, rows, 
+                                andPGMarray, andMaxval);
+
+    xorBitmap = createBitmap(bpp, xorPPMarray, cols, rows, xorCht);
+    andBitmap = createAndBitmap(andPGMarray, cols, rows, andMaxval);
+    /*
+     * Fill in the entry data fields.
+    */
+    entry->width         = cols;
+    entry->height        = rows;
+    entry->color_count   = entry_cols;
+    entry->reserved      = 0;
+    entry->planes        = 1;
+    /* 
+    * all the icons I looked at ignored this value...
+    */
+    entry->bitcount      = bpp;
+    entry->ih            = createInfoHeader(entry, xorBitmap, andBitmap);
+    entry->colors        = palette->colors;
+    entry->size_in_bytes = 
+        xorBitmap->size + andBitmap->size + 40 + (4 * entry->color_count);
+    if (verbose) 
+        pm_message("entry->size_in_bytes = %d + %d + %d = %d",
+                   xorBitmap->size ,andBitmap->size, 
+                   40, entry->size_in_bytes );
+    /*
+    * We don't know the offset ATM, set to 0 for now.
+    * Have to calculate this at the end.
+    */
+    entry->file_offset   = 0;
+    entry->xorBitmapOut  = xorBitmap->data;
+    entry->andBitmapOut  = andBitmap->data;
+    entry->xBytesXor     = xorBitmap->xBytes;
+    entry->xBytesAnd     = andBitmap->xBytes;  
+    /*
+    * Add the entry to the entries array.
+    */
+    ++MSIconData->count;
+    /* Perhaps I should allocate ahead, and take fewer trips to the well. */
+    REALLOCARRAY(MSIconData->entries, MSIconData->count);
+    MSIconData->entries[MSIconData->count-1] = entry;
+}
+
+
+
+static void 
+writeIC_Entry (IC_Entry const entry) {
+   writeU1(entry->width);
+   writeU1(entry->height);
+   writeU1(entry->color_count); /* chops 256->0 on its own.. */
+   writeU1(entry->reserved);
+   writeU2(entry->planes);
+   writeU2(entry->bitcount);
+   writeU4(entry->size_in_bytes);
+   writeU4(entry->file_offset);
+}
+
+
+
+static void 
+writeIC_InfoHeader (IC_InfoHeader const ih) {
+   writeU4(ih->size);
+   writeU4(ih->width);
+   writeU4(ih->height);
+   writeU2(ih->planes);
+   writeU2(ih->bitcount);
+   writeU4(ih->compression);
+   writeU4(ih->imagesize);
+   writeU4(ih->x_pixels_per_m);
+   writeU4(ih->y_pixels_per_m);
+   writeU4(ih->colors_used);
+   writeU4(ih->colors_important);
+}
+
+
+
+static void 
+writeIC_Color (IC_Color const col) {
+   /* Since the ppm might not have as many colors in it as we'd like,
+    * (2, 16, 256), stick 0 in the gaps.
+    * 
+    * This means that we lose palette information, but that can't be
+    * helped.  
+    */
+   if (col == NULL) {
+      writeU1(0);
+      writeU1(0);
+      writeU1(0);
+      writeU1(0);
+   } else {
+      writeU1(col->blue);
+      writeU1(col->green);
+      writeU1(col->red);
+      writeU1(col->reserved);
+   }
+}
+
+
+
+static void
+writeBitmap(u1 ** const bitmap, int const xBytes, int const height) {
+   int y;
+   for (y = 0;y<height;y++) {
+      fwrite (bitmap[y],1,xBytes,ofp);
+      file_offset += xBytes;
+   }
+}
+
+
+
+static void 
+writeMS_Ico(MS_Ico       const MSIconData, 
+            const char * const outFname) {
+    int x,y;
+   
+    ofp = pm_openw(outFname);
+
+    writeU2(MSIconData->reserved);
+    writeU2(MSIconData->type);
+    writeU2(MSIconData->count);
+    for (x=0;x<MSIconData->count;x++) writeIC_Entry(MSIconData->entries[x]);
+    for (x=0;x<MSIconData->count;x++) {
+        writeIC_InfoHeader(MSIconData->entries[x]->ih);
+        for (y=0;y<(MSIconData->entries[x]->color_count);y++) {
+            writeIC_Color(MSIconData->entries[x]->colors[y]);
+        }
+        if (verbose) pm_message("writing xor bitmap");
+        writeBitmap(MSIconData->entries[x]->xorBitmapOut,
+                    MSIconData->entries[x]->xBytesXor,
+                    MSIconData->entries[x]->height);
+        if (verbose) pm_message("writing and bitmap");
+        writeBitmap(MSIconData->entries[x]->andBitmapOut,
+                    MSIconData->entries[x]->xBytesAnd,
+                    MSIconData->entries[x]->height);
+    }
+    fclose(ofp);
+}
+
+
+
+int 
+main(int argc, char ** argv) {
+
+    struct cmdlineInfo cmdline;
+
+    MS_Ico const MSIconDataP = createIconFile();
+    unsigned int iconIndex;
+    unsigned int offset;
+   
+    ppm_init (&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    verbose = cmdline.verbose;
+
+    for (iconIndex = 0; iconIndex < cmdline.iconCount; ++iconIndex) {
+        addEntryToIcon(MSIconDataP, cmdline.inputFilespec[iconIndex],
+                       cmdline.andpgmFilespec[iconIndex], 
+                       cmdline.truetransparent);
+    }
+    /*
+     * Now we have to go through and calculate the offsets.
+     * The first infoheader starts at 6 + count*16 bytes.
+     */
+    offset = (MSIconDataP->count * 16) + 6;
+    for (iconIndex = 0; iconIndex < MSIconDataP->count; ++iconIndex) {
+        IC_Entry entry = MSIconDataP->entries[iconIndex];
+        entry->file_offset = offset;
+        /* 
+         * Increase the offset by the size of this offset & data.
+         * this includes the size of the color data.
+         */
+        offset += entry->size_in_bytes;
+    }
+    /*
+     * And now, we have to actually SAVE the .ico!
+     */
+    writeMS_Ico(MSIconDataP, cmdline.output);
+
+    free(cmdline.inputFilespec);
+    free(cmdline.andpgmFilespec);
+
+    return 0;
+}
+
diff --git a/converter/ppm/ppmtoxpm.c b/converter/ppm/ppmtoxpm.c
new file mode 100644
index 00000000..853ae711
--- /dev/null
+++ b/converter/ppm/ppmtoxpm.c
@@ -0,0 +1,659 @@
+/* ppmtoxpm.c - read a portable pixmap and produce a (version 3) X11 pixmap
+**
+** Copyright (C) 1990 by Mark W. Snitily
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** This tool was developed for Schlumberger Technologies, ATE Division, and
+** with their permission is being made available to the public with the above
+** copyright notice and permission notice.
+**
+** Upgraded to XPM2 by
+**   Paul Breslaw, Mecasoft SA, Zurich, Switzerland (paul@mecazh.uu.ch)
+**   Thu Nov  8 16:01:17 1990
+**
+** Upgraded to XPM version 3 by
+**   Arnaud Le Hors (lehors@mirsa.inria.fr)
+**   Tue Apr 9 1991
+**
+** Rainer Sinkwitz sinkwitz@ifi.unizh.ch - 21 Nov 91:
+**  - Bug fix, should should malloc space for rgbn[j].name+1 in line 441
+**    caused segmentation faults
+**    
+**  - lowercase conversion of RGB names def'ed out,
+**    considered harmful.
+**
+** Michael Pall (pall@rz.uni-karlsruhe.de) - 29 Nov 93:
+**  - Use the algorithm from xpm-lib for pixel encoding
+**    (base 93 not base 28 -> saves a lot of space for colorful xpms)
+*/
+
+#define _BSD_SOURCE   /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include "ppm.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "mallocvar.h"
+
+/* Max number of entries we will put in the XPM color map 
+   Don't forget the one entry for transparency.
+
+   We don't use this anymore.  Ppmtoxpm now has no arbitrary limit on
+   the number of colors.
+
+   We're assuming this isn't in fact an XPM format requirement, because
+   we've seen it work with 257, and 257 seems to be common, because it's
+   the classic 256 colors, plus transparency.  The value was 256 for
+   ages before we added transparency capability to this program in May
+   2001.  At that time, we started failing with 256 color images.
+   Some limit was also necessary before then because ppm_computecolorhash()
+   required us to specify a maximum number of colors.  It doesn't anymore.
+
+   If we find out that some XPM processing programs choke on more than
+   256 colors, we'll have to readdress this issue.  - Bryan.  2001.05.13.
+*/
+#define MAXCOLORS    256
+
+#define MAXPRINTABLE 92         /* number of printable ascii chars
+                                 * minus \ and " for string compat
+                                 * and ? to avoid ANSI trigraphs. */
+
+static const char * const printable =
+" .XoO+@#$%&*=-;:>,<1234567890qwertyuipasdfghjklzxcvbnmMNBVCZ\
+ASDFGHJKLPIUYTREWQ!~^/()_`'][{}|";
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilename;
+    const char *name;
+    const char *rgb;
+    const char *alpha_filename;
+    unsigned int hexonly;
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    const char * nameOpt;
+    unsigned int nameSpec;
+
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "alphamask",   OPT_STRING, &cmdlineP->alpha_filename, 
+            NULL, 0);
+    OPTENT3(0,   "name",        OPT_STRING, &nameOpt,                   
+            &nameSpec, 0);
+    OPTENT3(0,   "rgb",         OPT_STRING, &cmdlineP->rgb,            
+            NULL, 0);
+    OPTENT3(0,   "hexonly",     OPT_FLAG, NULL,
+            &cmdlineP->hexonly, 0);
+    OPTENT3(0,   "verbose",     OPT_FLAG, NULL,
+            &cmdlineP->verbose, 0);
+
+    /* Set the defaults */
+    cmdlineP->alpha_filename = NULL;  /* no transparency */
+    cmdlineP->rgb = NULL;      /* no rgb file specified */
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 == 0) 
+        cmdlineP->inputFilename = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->inputFilename = argv[1];
+
+    /* If output filename not specified, use input filename as default. */
+    if (nameSpec)
+        cmdlineP->name = nameOpt;
+    else if (STREQ(cmdlineP->inputFilename, "-"))
+        cmdlineP->name = "noname";
+    else {
+        static char name[80+1];
+        char *cp;
+
+        STRSCPY(name, cmdlineP->inputFilename);
+        cp = strchr(name, '.');
+        if (cp)
+            *cp = '\0';     /* remove extension */
+        cmdlineP->name = name;
+    }
+}
+
+
+typedef struct {            /* rgb values and ascii names (from
+                             * rgb text file) */
+    int r, g, b;            /* rgb values, range of 0 -> 65535 */
+    char *name;             /* color mnemonic of rgb value */
+} rgb_names;
+
+typedef struct {            
+    /* Information for an XPM color map entry */
+    char *cixel;    
+       /* XPM color number, as might appear in the XPM raster */
+    const char *rgbname;  
+       /* color name, e.g. "blue" or "#01FF23" */
+} cixel_map;
+
+
+
+static char *
+genNumstr(unsigned int const input, int const digits) {
+/*---------------------------------------------------------------------------
+   Given a number and a base (MAXPRINTABLE), this routine prints the
+   number into a malloc'ed string and returns it.  The length of the
+   string is specified by "digits".  It is not printed in decimal or
+   any other number system you're used to.  Rather, each digit is from
+   the set printable[], which contains MAXPRINTABLE characters; the
+   character printable[n] has value n.
+
+   The string is printable[0] filled with high order zeroes.
+
+   Example:
+     Assume: 
+       printable[0] == 'q'
+       printable[1] == '%'
+       MAXPRINTABLE == 2 
+       digits == 5
+       input == 3 
+     Result is the malloc'ed string "qqq%%"
+---------------------------------------------------------------------------*/
+    char *str, *p;
+    int d;
+    unsigned int i;
+
+    /* Allocate memory for printed number.  Abort if error. */
+    if (!(str = (char *) malloc(digits + 1)))
+        pm_error("out of memory");
+
+    i = input;
+    /* Generate characters starting with least significant digit. */
+    p = str + digits;
+    *p-- = '\0';            /* nul terminate string */
+    while (p >= str) {
+        d = i % MAXPRINTABLE;
+        i /= MAXPRINTABLE;
+        *p-- = printable[d];
+    }
+
+    if (i != 0)
+        pm_error("Overflow converting %d to %d digits in base %d",
+                 input, digits, MAXPRINTABLE);
+
+    return str;
+}
+
+
+
+static unsigned int
+xpmMaxvalFromMaxval(pixval const maxval) {
+
+    unsigned int retval;
+
+    /*
+     * Determine how many hex digits we'll be normalizing to if the rgb
+     * value doesn't match a color mnemonic. 
+     */
+    if (maxval <= 0x000F)
+        retval = 0x000F;
+    else if (maxval <= 0x00FF)
+        retval = 0x00FF;
+    else if (maxval <= 0x0FFF)
+        retval = 0x0FFF;
+    else if (maxval <= 0xFFFF)
+        retval = 0xFFFF;
+    else
+        pm_error("Internal error - impossible maxval %x", maxval);
+
+    return retval;
+}
+    
+
+
+static unsigned int
+charsPerPixelForSize(unsigned int const cmapSize) { 
+/*----------------------------------------------------------------------------
+   Return the number of characters it will take to represent a pixel in
+   an XPM that has a colormap of size 'cmapSize'.  Each pixel in an XPM
+   represents an index into the colormap with a base-92 scheme where each
+   character is one of 92 printable characters.  Ergo, if the colormap
+   has at most 92 characters, each pixel will be represented by a single
+   character.  If it has more than 92, but at most 92*92, it will take 2, 
+   etc.
+
+   If cmapSize is zero, there's no such thing as an XPM pixel, so we
+   return an undefined value.
+-----------------------------------------------------------------------------*/
+    unsigned int charsPerPixel;
+
+    if (cmapSize > 0) {
+        unsigned int j;
+
+        for (charsPerPixel = 0, j = cmapSize-1; j > 0; ++charsPerPixel)
+            j /= MAXPRINTABLE;
+    }
+    return charsPerPixel;
+} 
+
+
+
+static void
+genCmap(colorhist_vector const chv, 
+        int              const ncolors, 
+        pixval           const maxval, 
+        colorhash_table  const colornameHash,
+        const char *     const colornames[],
+        bool             const includeTransparent,
+        cixel_map **     const cmapP, 
+        unsigned int *   const transIndexP,
+        unsigned int *   const cmapSizeP,
+        unsigned int *   const charsPerPixelP) {
+/*----------------------------------------------------------------------------
+   Generate the XPM color map in cixel_map format (which is just a step
+   away from the actual text that needs to be written the XPM file).  The
+   color map is defined by 'chv', which contains 'ncolors' colors which
+   have maxval 'maxval'.
+
+   Output is in newly malloc'ed storage, with its address returned as
+   *cmapP.  We return the number of entries in it as *cmapSizeP.
+
+   This map includes an entry for transparency, whether the raster uses
+   it or not.  We return its index as *transIndexP.
+
+   In the map, identify colors by the names given by 'colornameHash' and
+   colornames[].  'colornameHash' maps a color in 'pixel' form to an
+   index into colornames[]; colornames[] contains the text to identify the
+   color in the XPM format.  The colors in 'colornameHash' have maxval 255.
+   If a color is not in 'colornameHash', use hexadecimal notation in the
+   output colormap.
+
+   But if 'colornameHash' is null, don't use color names at all.  Just use
+   hexadecimal notation.
+
+   Return as *charsPerPixel the number of characters, or digits, that
+   will be needed in the XPM raster to form an index into this color map.
+-----------------------------------------------------------------------------*/
+    unsigned int const cmapSize = ncolors + (includeTransparent ? 1 : 0);
+
+    cixel_map * cmap;  /* malloc'ed */
+    unsigned int cmapIndex;
+    unsigned int charsPerPixel;
+    unsigned int xpmMaxval;
+    
+    MALLOCARRAY(cmap, cmapSize);
+    if (cmapP == NULL)
+        pm_error("Out of memory allocating %u bytes for a color map.",
+                 sizeof(cixel_map) * (ncolors+1));
+
+    xpmMaxval = xpmMaxvalFromMaxval(maxval);
+
+    charsPerPixel = charsPerPixelForSize(cmapSize);
+
+    /*
+     * Generate the character-pixel string and the rgb name for each
+     * colormap entry. 
+     */
+    for (cmapIndex = 0; cmapIndex < ncolors; ++cmapIndex) {
+        pixel const color = chv[cmapIndex].color;
+
+        pixel color255;
+            /* The color, scaled to maxval 255 */
+        const char * colorname;  /* malloc'ed */
+        /*
+         * The character-pixel string is simply a printed number in base
+         * MAXPRINTABLE where the digits of the number range from
+         * printable[0] .. printable[MAXPRINTABLE-1] and the printed length
+         * of the number is 'charsPerPixel'. 
+         */
+        cmap[cmapIndex].cixel = genNumstr(cmapIndex, charsPerPixel);
+        
+        PPM_DEPTH(color255, color, maxval, 255);
+
+        if (colornameHash == NULL)
+            colorname = NULL;
+        else {
+            int colornameIndex;
+            colornameIndex = ppm_lookupcolor(colornameHash, &color255);
+            if (colornameIndex >= 0)
+                colorname = strdup(colornames[colornameIndex]);
+            else
+                colorname = NULL;
+        }
+        if (colorname)
+            cmap[cmapIndex].rgbname = colorname;
+        else {
+            /* Color has no name; represent it in hexadecimal */
+
+            pixel scaledColor;
+            const char * hexString;  /* malloc'ed */
+
+            PPM_DEPTH(scaledColor, color, maxval, xpmMaxval);
+
+            asprintfN(&hexString, xpmMaxval == 0x000F ? "#%X%X%X" :
+                      xpmMaxval == 0x00FF ? "#%02X%02X%02X" :
+                      xpmMaxval == 0x0FFF ? "#%03X%03X%03X" :
+                      "#%04X%04X%04X", 
+                      PPM_GETR(scaledColor),
+                      PPM_GETG(scaledColor),
+                      PPM_GETB(scaledColor)
+                );
+
+            if (hexString == NULL)
+                pm_error("Unable to allocate storage for hex string");
+            cmap[cmapIndex].rgbname = hexString;
+        }
+    }
+
+    if (includeTransparent) {
+        /* Add the special transparency entry to the colormap */
+        unsigned int const transIndex = ncolors;
+        cmap[transIndex].rgbname = strdup("None");
+        cmap[transIndex].cixel = genNumstr(transIndex, charsPerPixel);
+        *transIndexP = transIndex;
+    }
+    *cmapP          = cmap;
+    *cmapSizeP      = cmapSize;
+    *charsPerPixelP = charsPerPixel;
+}
+
+
+
+static void
+destroyCmap(cixel_map *  const cmap, 
+            unsigned int const cmapSize) {
+
+    int i;
+    /* Free the real color entries */
+    for (i = 0; i < cmapSize; i++) {
+        strfree(cmap[i].rgbname);
+        free(cmap[i].cixel);
+    }
+    free(cmap);
+}
+
+
+
+static void
+writeXpmFile(FILE *          const outfile, 
+             pixel **        const pixels, 
+             gray **         const alpha, 
+             pixval          const alphamaxval,
+             char            const name[], 
+             int             const cols, 
+             int             const rows, 
+             unsigned int    const cmapSize,
+             unsigned int    const charsPerPixel, 
+             cixel_map       const cmap[],
+             colorhash_table const cht,
+             unsigned int    const transIndex) {
+/*----------------------------------------------------------------------------
+   Write the whole XPM file to the open stream 'outfile'.
+
+   'cmap' is the colormap to be placed in the XPM.  'cmapSize' is the
+   number of entries in it.  'cht' is a hash table that gives you an 
+   index into 'cmap' given a color.  'transIndex' is the index into cmap
+   of the transparent color, and is valid only if 'alpha' is non-null
+   (otherwise, cmap might not contain a transparent color).
+-----------------------------------------------------------------------------*/
+    /* First the header */
+    printf("/* XPM */\n");
+    fprintf(outfile, "static char *%s[] = {\n", name);
+    fprintf(outfile, "/* width height ncolors chars_per_pixel */\n");
+    fprintf(outfile, "\"%d %d %d %d\",\n", cols, rows, 
+            cmapSize, charsPerPixel);
+
+    {
+        int i;
+        /* Write out the colormap (part of header) */
+        fprintf(outfile, "/* colors */\n");
+        for (i = 0; i < cmapSize; i++) { 
+            fprintf(outfile, "\"%s c %s\",\n", cmap[i].cixel, cmap[i].rgbname);
+        }
+    }
+    {
+        int row;
+
+        /* And now the raster */
+        fprintf(outfile, "/* pixels */\n");
+        for (row = 0; row < rows; row++) {
+            int col;
+            fprintf(outfile, "\"");
+            for (col = 0; col < cols; col++) {
+                if (alpha && alpha[row][col] <= alphamaxval/2)
+                    /* It's a transparent pixel */
+                    fprintf(outfile, "%s", cmap[transIndex].cixel);
+                else 
+                    fprintf(outfile, "%s", 
+                            cmap[ppm_lookupcolor(cht, 
+                                                 &pixels[row][col])].cixel);
+            }
+            fprintf(outfile, "\"%s\n", (row == (rows - 1) ? "" : ","));
+        }
+    }
+    /* And close up */
+    fprintf(outfile, "};\n");
+}
+
+
+
+static void
+readAlpha(const char filespec[], gray *** const alphaP,
+          int const cols, int const rows, pixval * const alphamaxvalP) {
+
+    FILE * alpha_file;
+    int alphacols, alpharows;
+        
+    alpha_file = pm_openr(filespec);
+    *alphaP = pgm_readpgm(alpha_file, &alphacols, &alpharows, alphamaxvalP);
+    pm_close(alpha_file);
+    
+    if (cols != alphacols || rows != alpharows)
+        pm_error("Alpha mask is not the same dimensions as the "
+                 "image.  Image is %d by %d, while mask is %d x %d.",
+                 cols, rows, alphacols, alpharows);
+}
+    
+
+
+static void
+computecolorhash(pixel **          const pixels,
+                 gray **           const alpha,
+                 int               const cols,
+                 int               const rows,
+                 gray              const alphaMaxval,
+                 colorhash_table * const chtP,
+                 unsigned int *    const ncolorsP,
+                 bool *            const transparentSomewhereP) {
+/*----------------------------------------------------------------------------
+   Compute a colorhash_table with one entry for each color in 'pixels' that
+   is not mostly transparent according to alpha mask 'alpha' (which has
+   maxval 'alphaMaxval').  alpha == NULL means all pixels are opaque.
+
+   The value associated with the color in the hash we build is meaningless.
+
+   Return the colorhash_table as *chtP, and the number of colors in it
+   as *ncolorsP.  Return *transparentSomewhereP == TRUE iff the image has
+   at least one pixel that is mostly transparent.
+-----------------------------------------------------------------------------*/
+    colorhash_table cht;
+    int row;
+    
+    cht = ppm_alloccolorhash( );
+    *ncolorsP = 0;   /* initial value */
+    *transparentSomewhereP = FALSE;  /* initial assumption */
+
+    /* Go through the entire image, building a hash table of colors. */
+    for (row = 0; row < rows; ++row) {
+        int col;
+
+        for (col = 0; col < cols; ++col) {
+            if (!alpha || alpha[row][col] > alphaMaxval/2) {
+                /* It's mostly opaque, so add this color to the hash
+                   if it's not already there.  
+                */
+                pixel const color = pixels[row][col];
+                int const lookupRc = ppm_lookupcolor(cht, &color);
+ 
+                if (lookupRc < 0) {
+                    /* It's not in the hash yet, so add it */
+                    ppm_addtocolorhash(cht, &color, 0);
+                    ++(*ncolorsP);
+                }
+            } else
+                *transparentSomewhereP = TRUE;
+        }
+    }
+    *chtP = cht;
+}
+
+
+
+static void
+computeColormap(pixel **           const pixels, 
+                gray **            const alpha,
+                int                const cols, 
+                int                const rows,
+                gray               const alphaMaxval,
+                colorhist_vector * const chvP, 
+                colorhash_table *  const chtP,
+                unsigned int *     const ncolorsP,
+                bool *             const transparentSomewhereP) {
+/*----------------------------------------------------------------------------
+   Compute the color map for the image 'pixels', which is 'cols' by 'rows',
+   in Netpbm data structures (a colorhist_vector for index-to-color lookups
+   and a colorhash_table for color-to-index lookups).
+
+   Exclude pixels that alpha mask 'alpha' (which has maxval
+   'alphaMaxval') says are mostly transparent.  alpha == NULL means all
+   pixels are opaque.
+
+   We return as *chvP an array of the colors present in 'pixels',
+   excluding those that are mostly transparent.  We return as
+   *ncolorsP the number of such colors.  We return
+   *transparentSomewhereP == TRUE iff the image has at least one
+   pixel that is mostly transparent.
+-----------------------------------------------------------------------------*/
+    colorhash_table histCht;
+
+    pm_message("(Computing colormap...");
+    computecolorhash(pixels, alpha, cols, rows, alphaMaxval, 
+                     &histCht, ncolorsP, transparentSomewhereP);
+    pm_message("...Done.  %d colors found.)", *ncolorsP);
+    
+    *chvP = ppm_colorhashtocolorhist(histCht, *ncolorsP);
+    ppm_freecolorhash(histCht);
+    /* Despite the name, the following generates an index on *chvP,
+       with which given a color you can quickly find the entry number
+       in *chvP that contains that color.
+    */
+    *chtP = ppm_colorhisttocolorhash(*chvP, *ncolorsP);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE *ifp;
+    int rows, cols;
+    unsigned int ncolors;
+    bool transparentSomewhere;
+    pixval maxval, alphaMaxval;
+    colorhash_table cht;
+    colorhist_vector chv;
+
+    colorhash_table colornameHash;
+        /* Hash table to map colors to their names */
+    const char ** colornames;
+        /* Table of color names; 'colornameHash' yields an index into this
+           array.
+        */
+
+    pixel **pixels;
+    gray **alpha;
+
+    /* Used for rgb value -> character-pixel string mapping */
+    cixel_map *cmap;  /* malloc'ed */
+        /* The XPM colormap */
+    unsigned int cmapSize;
+        /* Number of entries in 'cmap' */
+    unsigned int transIndex;
+        /* Index into 'cmap' of the transparent color, if there is one */
+
+    unsigned int charsPerPixel;  
+
+    struct cmdlineInfo cmdline;
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifp = pm_openr(cmdline.inputFilename);
+    pixels = ppm_readppm(ifp, &cols, &rows, &maxval);
+    pm_close(ifp);
+
+    if (cmdline.alpha_filename) 
+        readAlpha(cmdline.alpha_filename, &alpha, cols, rows, &alphaMaxval);
+    else
+        alpha = NULL;
+
+    computeColormap(pixels, alpha, cols, rows, alphaMaxval,
+                    &chv, &cht, &ncolors, &transparentSomewhere);
+
+    if (cmdline.hexonly)
+        colornameHash = NULL;
+    else
+        ppm_readcolornamefile(cmdline.rgb, FALSE, &colornameHash, &colornames);
+
+    /* Now generate the character-pixel colormap table. */
+    genCmap(chv, ncolors, maxval, 
+            colornameHash, colornames, transparentSomewhere, 
+            &cmap, &transIndex, &cmapSize, &charsPerPixel);
+
+    writeXpmFile(stdout, pixels, alpha, alphaMaxval,
+                 cmdline.name, cols, rows, cmapSize,
+                 charsPerPixel, cmap, cht, transIndex);
+    
+    if (colornameHash) {
+        ppm_freecolorhash(colornameHash);
+        ppm_freecolornames(colornames);
+    }
+    destroyCmap(cmap, cmapSize);
+    ppm_freearray(pixels, rows);
+    if (alpha) pgm_freearray(alpha, rows);
+
+    return 0;
+}
+
diff --git a/converter/ppm/ppmtoyuv.c b/converter/ppm/ppmtoyuv.c
new file mode 100644
index 00000000..7d843cc0
--- /dev/null
+++ b/converter/ppm/ppmtoyuv.c
@@ -0,0 +1,97 @@
+/* ppmtoyuv.c - convert a portable pixmap into an Abekas YUV file
+**
+** by Marc Boucher
+** Internet: marc@PostImage.COM
+** 
+** Based on Example Conversion Program, A60/A64 Digital Video Interface
+** Manual, page 69.
+**
+** Copyright (C) 1991 by DHD PostImage Inc.
+** Copyright (C) 1987 by Abekas Video Systems Inc.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+
+int
+main(argc, argv)
+char **argv;
+{
+	FILE *ifp;
+	pixel          *pixelrow;
+	register pixel *pP;
+	int             rows, cols, format, row;
+	register int    col;
+	pixval          maxval;
+	unsigned long   y1, y2=0, u=0, v=0, u0=0, u1, u2, v0=0, v1, v2;
+	unsigned char  *yuvbuf;
+
+
+	ppm_init(&argc, argv);
+
+	if (argc > 2) pm_usage("[ppmfile]");
+
+	if (argc == 2) ifp = pm_openr(argv[1]);
+	else ifp = stdin;
+
+	ppm_readppminit(ifp, &cols, &rows, &maxval, &format);
+
+    if (cols % 2 != 0)
+        pm_error("Image must have even number of columns.\n"
+                 "This image is %d columns wide.  Try Pnmcut.", cols);
+
+	pixelrow = ((pixel*) pm_allocrow( cols, sizeof(pixel) ));
+	yuvbuf = (unsigned char *) pm_allocrow( cols, 2 );
+
+	for (row = 0; row < rows; ++row) {
+		unsigned char *yuvptr;
+
+		ppm_readppmrow(ifp, pixelrow, cols, maxval, format);
+
+		for (col = 0, pP = pixelrow, yuvptr=yuvbuf; col < cols; col += 2, ++pP) {
+			pixval r, g, b;
+
+			/* first pixel gives Y and 0.5 of chroma */
+			r = PPM_GETR(*pP);
+			g = PPM_GETG(*pP);
+			b = PPM_GETB(*pP);
+
+			y1 = 16829 * r + 33039 * g + 6416 * b + (0xffff & y2);
+			u1 = -4853 * r - 9530 * g + 14383 * b;
+			v1 = 14386 * r - 12046 * g - 2340 * b;
+
+			pP++;
+			/* second pixel just yields a Y and 0.25 U, 0.25 V */
+			r = PPM_GETR(*pP);
+			g = PPM_GETG(*pP);
+			b = PPM_GETB(*pP);
+
+			y2 = 16829 * r + 33039 * g + 6416 * b + (0xffff & y1);
+			u2 = -2426 * r - 4765 * g + 7191 * b;
+			v2 = 7193 * r - 6023 * g - 1170 * b;
+
+			/* filter the chroma */
+			u = u0 + u1 + u2 + (0xffff & u);
+			v = v0 + v1 + v2 + (0xffff & v);
+
+			u0 = u2;
+			v0 = v2;
+
+			*yuvptr++ = (u >> 16) + 128;
+			*yuvptr++ = (y1 >> 16) + 16;
+			*yuvptr++ = (v >> 16) + 128;
+			*yuvptr++ = (y2 >> 16) + 16;
+		}
+		fwrite(yuvbuf, cols*2, 1, stdout);
+	}
+
+	pm_close(ifp);
+
+	exit(0);
+}
diff --git a/converter/ppm/ppmtoyuvsplit.c b/converter/ppm/ppmtoyuvsplit.c
new file mode 100644
index 00000000..2dddebfc
--- /dev/null
+++ b/converter/ppm/ppmtoyuvsplit.c
@@ -0,0 +1,197 @@
+/* ppmtoyuvsplit.c - convert a portable pixmap into 3 raw files:
+** - basename.Y : The Luminance chunk at the size of the Image
+** - basename.U : The Chrominance chunk U at 1/4
+** - basename.V : The Chrominance chunk V at 1/4
+** The subsampled U and V values are made by arithmetic mean.
+**
+** If CCIR601 is defined, the produced YUV triples are scaled again
+** to fit into the smaller range of values for this standard.
+**
+** by A.Beck
+** Internet: Andre_Beck@IRS.Inf.TU-Dresden.de
+**
+** Based on ppmtoyuv.c
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* Wether to create YUV in JFIF(JPEG) or CCIR.601(MPEG) scale */
+#define CCIR601
+
+/* Wether to use pm_close() or fake it -- don't ask me why */
+/* #define ORIGINAL */
+
+/* ALPHA Kludge by Franky */
+
+#ifdef __alpha
+#define myLONG int
+#else
+#define myLONG long
+#endif
+
+#include <string.h>
+#include "ppm.h"
+
+int
+main(argc, argv)
+char **argv;
+{
+        FILE *ifp,*vf,*uf,*yf;
+        pixel          *pixelrow1,*pixelrow2;
+        register pixel *pP1,*pP2;
+        int             rows, cols, format, row;
+        register int    col;
+        pixval          maxval;
+        myLONG u,v,y0,y1,y2,y3,u0,u1,u2,u3,v0,v1,v2,v3;
+        unsigned char  *y1buf,*y2buf,*ubuf,*vbuf;
+        char            ufname[256],vfname[256],yfname[256];
+
+
+        ppm_init(&argc, argv);
+
+        if ((argc>3)||(argc<2)) pm_usage("basename [ppmfile]");
+
+        if (argc == 3) ifp = pm_openr(argv[2]);
+        else ifp = stdin;
+
+        strcpy(ufname,argv[1]);
+        strcpy(vfname,argv[1]);
+        strcpy(yfname,argv[1]);
+
+        strcat(ufname,".U");
+        strcat(vfname,".V");
+        strcat(yfname,".Y");
+
+        uf = fopen(ufname,"wb");
+        vf = fopen(vfname,"wb");
+        yf = fopen(yfname,"wb");
+
+        if(!(uf && vf && yf)) {
+         perror("error opening output files");
+         exit(0);
+        }
+        ppm_readppminit(ifp, &cols, &rows, &maxval, &format);
+
+        if(cols & 1) fprintf(stderr,
+                             "%s: Warning: odd columns count, exceed ignored\n",
+                             argv[0]);
+        if(rows & 1) fprintf(stderr,
+                             "%s: Warning: odd rows count, exceed ignored\n",
+                             argv[0]);
+
+        pixelrow1 = ((pixel*) pm_allocrow( cols, sizeof(pixel) ));
+        pixelrow2 = ((pixel*) pm_allocrow( cols, sizeof(pixel) ));
+
+        y1buf = (unsigned char *) pm_allocrow( cols, 1 );
+        y2buf = (unsigned char *) pm_allocrow( cols, 1 );
+        ubuf = (unsigned char *) pm_allocrow( cols, 1 );
+        vbuf = (unsigned char *) pm_allocrow( cols, 1 );
+
+        for (row = 0; row < (rows & ~1); row += 2) {
+                unsigned char *y1ptr,*y2ptr,*uptr,*vptr;
+
+                ppm_readppmrow(ifp, pixelrow1, cols, maxval, format);
+                ppm_readppmrow(ifp, pixelrow2, cols, maxval, format);
+
+                pP1 = pixelrow1; pP2 = pixelrow2;
+                y1ptr = y1buf; y2ptr = y2buf; vptr = vbuf; uptr = ubuf;
+
+                for (col = 0 ; col < (cols & ~1); col += 2) {
+                        pixval r0,g0,b0,r1,g1,b1,r2,g2,b2,r3,g3,b3;
+
+                        /* first pixel */
+                        r0 = PPM_GETR(*pP1);
+                        g0 = PPM_GETG(*pP1);
+                        b0 = PPM_GETB(*pP1);
+                        pP1++;
+                        /* 2nd pixel */
+                        r1 = PPM_GETR(*pP1);
+                        g1 = PPM_GETG(*pP1);
+                        b1 = PPM_GETB(*pP1);
+                        pP1++;
+                        /* 3rd pixel */
+                        r2 = PPM_GETR(*pP2);
+                        g2 = PPM_GETG(*pP2);
+                        b2 = PPM_GETB(*pP2);
+                        pP2++;
+                        /* 4th pixel */
+                        r3 = PPM_GETR(*pP2);
+                        g3 = PPM_GETG(*pP2);
+                        b3 = PPM_GETB(*pP2);
+                        pP2++;
+
+
+/* The JFIF RGB to YUV Matrix for $00010000 = 1.0
+
+[Y]   [19595   38469    7471][R]
+[U] = [-11056  -21712  32768][G]
+[V]   [32768   -27440  -5328][B]
+
+*/
+
+                        y0 =  19595 * r0 + 38469 * g0 +  7471 * b0;
+                        u0 = -11056 * r0 - 21712 * g0 + 32768 * b0;
+                        v0 =  32768 * r0 - 27440 * g0 -  5328 * b0;
+
+                        y1 =  19595 * r1 + 38469 * g1 +  7471 * b1;
+                        u1 = -11056 * r1 - 21712 * g1 + 32768 * b1;
+                        v1 =  32768 * r1 - 27440 * g1 -  5328 * b1;
+
+                        y2 =  19595 * r2 + 38469 * g2 +  7471 * b2;
+                        u2 = -11056 * r2 - 21712 * g2 + 32768 * b2;
+                        v2 =  32768 * r2 - 27440 * g2 -  5328 * b2;
+
+                        y3 =  19595 * r3 + 38469 * g3 +  7471 * b3;
+                        u3 = -11056 * r3 - 21712 * g3 + 32768 * b3;
+                        v3 =  32768 * r3 - 27440 * g3 -  5328 * b3;
+
+                        /* mean the chroma for subsampling */
+
+                        u  = (u0+u1+u2+u3)>>2;
+                        v  = (v0+v1+v2+v3)>>2;
+
+#ifdef CCIR601
+
+                        y0 = (y0 * 219)/255 + 1048576;
+                        y1 = (y1 * 219)/255 + 1048576;
+                        y2 = (y2 * 219)/255 + 1048576;
+                        y3 = (y3 * 219)/255 + 1048576;
+
+                        u  = (u * 224)/255 ;
+                        v  = (v * 224)/255 ;
+#endif
+
+
+                        *y1ptr++  = (y0 >> 16) ;
+                        *y1ptr++  = (y1 >> 16) ;
+                        *y2ptr++  = (y2 >> 16) ;
+                        *y2ptr++  = (y3 >> 16) ;
+
+
+                        *uptr++   = (u >> 16)+128 ;
+                        *vptr++   = (v >> 16)+128 ;
+
+                }
+                fwrite(y1buf, (cols & ~1), 1, yf);
+                fwrite(y2buf, (cols & ~1), 1, yf);
+                fwrite(ubuf, cols/2, 1, uf);
+                fwrite(vbuf, cols/2, 1, vf);
+        }
+
+/* I dunno why pm_close sees an error... get rid of it */
+
+#ifdef ORIGINAL
+        pm_close(ifp);
+#else
+        if(ifp != stdin) fclose(ifp);
+#endif
+        fclose(yf);
+        fclose(uf);
+        fclose(vf);
+        exit(0);
+}
diff --git a/converter/ppm/qrttoppm.c b/converter/ppm/qrttoppm.c
new file mode 100644
index 00000000..935463e7
--- /dev/null
+++ b/converter/ppm/qrttoppm.c
@@ -0,0 +1,69 @@
+/* qrttoppm.c - read a QRT ray-tracer output file and produce a portable pixmap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    register pixel* pixelrow;
+    int rows, cols, row, col;
+    pixval maxval;
+    unsigned char* buf;
+
+
+    ppm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[qrtfile]" );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    /* Read in the QRT file.  First the header. */
+    cols = getc( ifp );
+    cols += getc( ifp ) << 8;
+    rows = getc( ifp );
+    rows += getc( ifp ) << 8;
+
+    if ( cols <= 0 || rows <= 0 )
+	pm_error( "invalid size: %d %d", cols, rows );
+    maxval = 255;
+
+    ppm_writeppminit( stdout, cols, rows, maxval, 0 );
+    pixelrow = ppm_allocrow( cols );
+    buf = (unsigned char *) malloc( 3 * cols );
+    if ( buf == (unsigned char *) 0 )
+	pm_error( "out of memory" );
+
+    for ( row = 0; row < rows; row++ )
+	{
+        (void) getc( ifp );	/* discard */
+        (void) getc( ifp );	/* linenum */
+	if ( fread( buf, 3 * cols, 1, ifp ) != 1 )
+	    pm_error( "EOF / read error" );
+	for ( col = 0; col < cols; col++ )
+	    PPM_ASSIGN(
+		pixelrow[col], buf[col], buf[cols + col], buf[2 * cols + col] );
+	ppm_writeppmrow( stdout, pixelrow, cols, maxval, 0 );
+	}
+
+    pm_close( ifp );
+    pm_close( stdout );
+
+    exit( 0 );
+    }
diff --git a/converter/ppm/rawtoppm.c b/converter/ppm/rawtoppm.c
new file mode 100644
index 00000000..8d6c3b2c
--- /dev/null
+++ b/converter/ppm/rawtoppm.c
@@ -0,0 +1,244 @@
+/* rawtoppm.c - convert raw RGB bytes into a portable pixmap
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <math.h>
+#include "ppm.h"
+
+static void dorowskip ARGS(( FILE* ifp, int rowskip ));
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    pixel* pixrow;
+    register pixel* pP;
+    int argn, headerskip, rowskip, rows, cols, row, i;
+    register int col;
+    int order;
+#define ORD_RGB 1
+#define ORD_RBG 2
+#define ORD_GRB 3
+#define ORD_GBR 4
+#define ORD_BRG 5
+#define ORD_BGR 6
+int interleave;
+#define INT_PIX 1
+#define INT_ROW 2
+    int val1, val2, val3;
+    gray* grow1;
+    gray* grow2;
+    gray* grow3;
+    register gray* g1P;
+    register gray* g2P;
+    register gray* g3P;
+    const char* const usage = "[-headerskip N] [-rowskip N] [-rgb|-rbg|-grb|-gbr|-brg|-bgr] [-interpixel|-interrow] <width> <height> [rawfile]";
+
+
+    ppm_init( &argc, argv );
+
+    argn = 1;
+    headerskip = 0;
+    rowskip = 0;
+    order = ORD_RGB;
+    interleave = INT_PIX;
+
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+	{
+	if ( pm_keymatch( argv[argn], "-headerskip", 2 ) )
+	    {
+	    ++argn;
+	    if ( argn >= argc )
+		pm_usage( usage );
+	    headerskip = atoi( argv[argn] );
+	    }
+	else if ( pm_keymatch( argv[argn], "-rowskip", 3 ) )
+	    {
+	    ++argn;
+	    if ( argn >= argc )
+		pm_usage( usage );
+	    rowskip = atoi( argv[argn] );
+	    }
+	else if ( pm_keymatch( argv[argn], "-rgb", 3 ) )
+	    order = ORD_RGB;
+	else if ( pm_keymatch( argv[argn], "-rbg", 3 ) )
+	    order = ORD_RBG;
+	else if ( pm_keymatch( argv[argn], "-grb", 3 ) )
+	    order = ORD_GRB;
+	else if ( pm_keymatch( argv[argn], "-gbr", 3 ) )
+	    order = ORD_GBR;
+	else if ( pm_keymatch( argv[argn], "-brg", 3 ) )
+	    order = ORD_BRG;
+	else if ( pm_keymatch( argv[argn], "-bgr", 3 ) )
+	    order = ORD_BGR;
+	else if ( pm_keymatch( argv[argn], "-interpixel", 7 ) )
+	    interleave = INT_PIX;
+	else if ( pm_keymatch( argv[argn], "-interrow", 7 ) )
+	    interleave = INT_ROW;
+	else
+	    pm_usage( usage );
+	++argn;
+	}
+
+    if ( argn + 2 > argc )
+	pm_usage( usage );
+
+    cols = atoi( argv[argn++] );
+    rows = atoi( argv[argn++] );
+    if ( cols <= 0 || rows <= 0 )
+	pm_usage( usage );
+
+    if ( argn < argc )
+	{
+	ifp = pm_openr( argv[argn] );
+	++argn;
+	}
+    else
+	ifp = stdin;
+
+    if ( argn != argc )
+	pm_usage( usage );
+
+    ppm_writeppminit( stdout, cols, rows, (pixval) 255, 0 );
+    pixrow = ppm_allocrow( cols );
+
+    if ( interleave == INT_ROW )
+	{
+	grow1 = pgm_allocrow( cols );
+	grow2 = pgm_allocrow( cols );
+	grow3 = pgm_allocrow( cols );
+	}
+
+    for ( i = 0; i < headerskip; ++i )
+	{
+	val1 = getc( ifp );
+	if ( val1 == EOF )
+	    pm_error( "EOF / read error" );
+	}
+
+    for ( row = 0; row < rows; ++row)
+	{
+	switch ( interleave )
+	    {
+	    case INT_PIX:
+	    for ( col = 0, pP = pixrow; col < cols; ++col, ++pP )
+		{
+		val1 = getc( ifp );
+		if ( val1 == EOF )
+		    pm_error( "EOF / read error" );
+		val2 = getc( ifp );
+		if ( val2 == EOF )
+		    pm_error( "EOF / read error" );
+		val3 = getc( ifp );
+		if ( val3 == EOF )
+		    pm_error( "EOF / read error" );
+		switch ( order )
+		    {
+		    case ORD_RGB:
+		    PPM_ASSIGN( *pP, val1, val2, val3 );
+		    break;
+		    case ORD_RBG:
+		    PPM_ASSIGN( *pP, val1, val3, val2 );
+		    break;
+		    case ORD_GRB:
+		    PPM_ASSIGN( *pP, val2, val1, val3 );
+		    break;
+		    case ORD_GBR:
+		    PPM_ASSIGN( *pP, val3, val1, val2 );
+		    break;
+		    case ORD_BRG:
+		    PPM_ASSIGN( *pP, val2, val3, val1 );
+		    break;
+		    case ORD_BGR:
+		    PPM_ASSIGN( *pP, val3, val2, val1 );
+		    break;
+		    }
+		}
+	    dorowskip( ifp, rowskip );
+	    break;
+
+	    case INT_ROW:
+	    for ( col = 0, g1P = grow1; col < cols; ++col, ++g1P )
+		{
+		val1 = getc( ifp );
+		if ( val1 == EOF )
+		    pm_error( "EOF / read error" );
+		*g1P = val1;
+		}
+	    dorowskip( ifp, rowskip );
+	    for ( col = 0, g2P = grow2; col < cols; ++col, ++g2P )
+		{
+		val2 = getc( ifp );
+		if ( val2 == EOF )
+		    pm_error( "EOF / read error" );
+		*g2P = val2;
+		}
+	    dorowskip( ifp, rowskip );
+	    for ( col = 0, g3P = grow3; col < cols; ++col, ++g3P )
+		{
+		val3 = getc( ifp );
+		if ( val3 == EOF )
+		    pm_error( "EOF / read error" );
+		*g3P = val3;
+		}
+	    dorowskip( ifp, rowskip );
+	    for ( col = 0, pP = pixrow, g1P = grow1, g2P = grow2, g3P = grow3;
+		  col < cols; ++col, ++pP, ++g1P, ++g2P, ++g3P )
+		{
+		switch ( order )
+		    {
+		    case ORD_RGB:
+		    PPM_ASSIGN( *pP, *g1P, *g2P, *g3P );
+		    break;
+		    case ORD_RBG:
+		    PPM_ASSIGN( *pP, *g1P, *g3P, *g2P );
+		    break;
+		    case ORD_GRB:
+		    PPM_ASSIGN( *pP, *g2P, *g1P, *g3P );
+		    break;
+		    case ORD_GBR:
+		    PPM_ASSIGN( *pP, *g3P, *g1P, *g2P );
+		    break;
+		    case ORD_BRG:
+		    PPM_ASSIGN( *pP, *g2P, *g3P, *g1P );
+		    break;
+		    case ORD_BGR:
+		    PPM_ASSIGN( *pP, *g3P, *g2P, *g1P );
+		    break;
+		    }
+		}
+	    break;
+	    }
+	ppm_writeppmrow( stdout, pixrow, cols, (pixval) 255, 0 );
+	}
+
+    pm_close( ifp );
+    pm_close( stdout );
+
+    exit( 0 );
+    }
+
+static void
+dorowskip( ifp, rowskip )
+    FILE* ifp;
+    int rowskip;
+    {
+    int i, val;
+
+    for ( i = 0; i < rowskip; ++i )
+	{
+	val = getc( ifp );
+	if ( val == EOF )
+	    pm_error( "EOF / read error" );
+	}
+    }
diff --git a/converter/ppm/rgb3toppm.c b/converter/ppm/rgb3toppm.c
new file mode 100644
index 00000000..a666553c
--- /dev/null
+++ b/converter/ppm/rgb3toppm.c
@@ -0,0 +1,88 @@
+/* rgb3toppm - combine three portable graymaps into one portable pixmap
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* rfd;
+    FILE* gfd;
+    FILE* bfd;
+    gray* rrow;
+    gray* rP;
+    gray* grow;
+    gray* gP;
+    gray* brow;
+    gray* bP;
+    pixel* pixelrow;
+    register pixel* pP;
+    int rows, cols, trows, tcols, row, col;
+    gray rmaxval, gmaxval, bmaxval;
+    int rformat, gformat, bformat;
+    pixval pmaxval;
+
+
+    ppm_init( &argc, argv );
+
+    if ( argc != 4 )
+	pm_usage( "<red pgmfile> <green pgmfile> <blue pgmfile> " );
+
+    rfd = pm_openr( argv[1] );
+    gfd = pm_openr( argv[2] );
+    bfd = pm_openr( argv[3] );
+
+    pgm_readpgminit( rfd, &cols, &rows, &rmaxval, &rformat );
+    pgm_readpgminit( gfd, &tcols, &trows, &gmaxval, &gformat );
+    if ( tcols != cols || trows != rows )
+	pm_error( "all three graymaps must be the same size" );
+    pgm_readpgminit( bfd, &tcols, &trows, &bmaxval, &bformat );
+    if ( tcols != cols || trows != rows )
+	pm_error( "all three graymaps must be the same size" );
+
+    pmaxval = rmaxval;
+    if ( gmaxval > pmaxval ) pmaxval = gmaxval;
+    if ( bmaxval > pmaxval ) pmaxval = bmaxval;
+    rrow = pgm_allocrow( cols );
+    grow = pgm_allocrow( cols );
+    brow = pgm_allocrow( cols );
+
+    ppm_writeppminit( stdout, cols, rows, pmaxval, 0 );
+    pixelrow = ppm_allocrow( cols );
+
+    for ( row = 0; row < rows; row++ )
+	{
+	pgm_readpgmrow( rfd, rrow, cols, rmaxval, rformat );
+	pgm_readpgmrow( gfd, grow, cols, gmaxval, gformat );
+	pgm_readpgmrow( bfd, brow, cols, bmaxval, bformat );
+
+	for ( col = 0, rP = rrow, gP = grow, bP = brow, pP = pixelrow;
+	      col < cols;
+	      ++col, ++rP, ++gP, ++bP, ++pP )
+	    {
+	    if ( rmaxval != pmaxval ) *rP = (int) *rP * pmaxval / rmaxval;
+	    if ( gmaxval != pmaxval ) *gP = (int) *gP * pmaxval / gmaxval;
+	    if ( bmaxval != pmaxval ) *bP = (int) *bP * pmaxval / bmaxval;
+	    PPM_ASSIGN( *pP, *rP, *gP, *bP );
+	    }
+	ppm_writeppmrow( stdout, pixelrow, cols, pmaxval, 0 );
+	}
+
+    pm_close( rfd );
+    pm_close( gfd );
+    pm_close( bfd );
+    pm_close( stdout );
+
+    exit( 0 );
+    }
diff --git a/converter/ppm/sldtoppm.c b/converter/ppm/sldtoppm.c
new file mode 100644
index 00000000..7da6d592
--- /dev/null
+++ b/converter/ppm/sldtoppm.c
@@ -0,0 +1,650 @@
+/*
+
+      Convert an AutoCAD slide (.sld) file to PPM format
+
+    An AutoCAD slide is a compressed sequence of  vectors  and  filled
+    polygons.   The  ppmdraw  package  is  used  to scan convert these
+    geometrical objects into a portable pixmap.
+
+    Author:
+        John Walker
+        Autodesk SA
+        Avenue des Champs-Montants 14b
+        CH-2074 MARIN
+        Switzerland
+        Usenet: kelvin@Autodesk.com
+        Fax:    038/33 88 15
+        Voice:  038/33 76 33
+
+    Permission  to  use, copy, modify, and distribute this software and
+    its documentation  for  any  purpose  and  without  fee  is  hereby
+    granted,  without any conditions or restrictions.  This software is
+    provided "as is" without express or implied warranty.
+
+*/
+
+#include <string.h>
+#include <math.h>
+
+#include "ppm.h"
+#include "ppmdraw.h"
+#include "nstring.h"
+#ifdef DEBUG
+#include <assert.h>
+#else
+#define assert(x)
+#endif
+
+/*  Define a variable type accepting numbers -127 <= n <= 127.  But note
+    that we still expect it to act UNSIGNED. */
+
+#define smallint unsigned char        /* Small integers */
+
+#define TRUE     1
+#define FALSE    0
+
+#define EOS     '\0'
+
+/* Screen point */
+
+struct spoint {
+    int x, y;
+};
+
+/* Screen polygon */
+
+struct spolygon { 
+    int npoints,              /* Number of points in polygon */
+          fill;           /* Fill type */
+    struct spoint pt[11];         /* Actual points */
+};
+
+/* Screen vector */
+
+struct svector {
+    struct spoint f;          /* From point */
+    struct spoint t;          /* To point */
+};
+
+static int extend ARGS((smallint ch));
+static int sli ARGS((void));
+static int slib ARGS((void));
+static void vscale ARGS((int *px, int *py));
+static void slider ARGS((void (*slvec) ARGS((struct svector *vec, int color)),
+                    void (*slflood) ARGS((struct spolygon *poly, int color)) ));
+static void slidefind ARGS((char *sname, int dironly, int ucasen));
+static void draw ARGS((struct svector *vec, int color));
+static void flood ARGS((struct spolygon *poly, int color));
+
+static int ixdots, iydots;        /* Screen size in dots */
+static FILE *slfile;              /* Slide file descriptor */
+static int blither = FALSE;       /* Dump slide file information ? */
+static int info = FALSE;          /* Print header information */
+static pixel **pixels;            /* Pixel map */
+static int pixcols, pixrows;          /* Pixel map size */
+#define pixmaxval 255             /* Largest pixel value */
+static double uscale = -1;        /* Uniform scale factor */
+static int sxsize = -1, sysize = -1;  /* Scale to X, Y size ? */
+
+#include "autocad.h"                  /* AutoCAD standard color assignments */
+
+/*  Local variables  */
+
+struct slhead {
+    char slh[17];             /* Primate-readable header */
+    char sntype;              /* Machine type (for number compat) */
+    char slevel;              /* Format type */
+    short sxdots, sydots;         /* Display X, Y dots */
+    double sdsar;             /* Display aspect ratio */
+    short shwfill;            /* Display hardware fill type */
+    char spad;                /* Pad to even byte length */
+};
+
+static int adjust = FALSE;        /* Adjust to correct aspect ratio ? */
+static struct slhead slfrof;          /* Slide file header */
+static long xfac, yfac;           /* Aspect ratio scale factors */
+
+static int sdrawkcab;             /* Slide drawing kinematic conversion of
+                     ass-backwards data flag */
+
+/*  EXTEND  --  Turn a smallint into an int with sign extension, whether
+        or not that happens automatically.  */
+
+static int extend(smallint ch)
+{
+    return ((int) ((ch & 0x80) ? (ch | ~0xFF) : ch));
+}
+
+/*  SLI  --  Input word from slide file  */
+
+static int sli()
+{
+    short wd;
+
+    if (fread(&wd, sizeof wd, 1, slfile) != 1) {
+        pm_error("error reading slide file");
+    } else {
+    if (sdrawkcab) {
+        wd = ((wd >> 8) & 0xFF) | (wd << 8);
+    }
+    }
+    return wd;
+}
+
+/*  SLIB  --  Input byte from slide file  */
+
+static int slib()
+{
+    smallint ch = 0;
+
+    if (fread(&ch, sizeof ch, 1, slfile) != 1) {
+        pm_error("error reading slide file");
+    }
+    return extend(ch);
+}
+
+/*  VSCALE -- scale screen coordinates for mismatched display.  */
+
+static void vscale(px, py)
+  int *px, *py;
+{
+    *px = (((unsigned) *px) * xfac) >> 16;
+    *py = (((unsigned) *py) * yfac) >> 16;
+}
+
+/*  SLIDER  --  Read slide file.  This is called with the name of the
+        file to be read and function pointers to the routines
+        which process vectors and polygon fill requests
+        respectively.
+*/
+
+static void slider(slvec, slflood)
+  void (*slvec) ARGS((struct svector *vec, int color));
+  void (*slflood) ARGS((struct spolygon *poly, int color));
+{
+    int i, rescale;
+    unsigned char ubfr[4];        /* Utility character buffer */
+    int lx, ly;               /* Last x and y point */
+    int slx, sly;             /* Last x and y scaled screen point */
+    struct svector vec;           /* Screen vector */
+    struct spolygon poly;         /* Screen polygon */
+    unsigned short cw;            /* Control word */
+    double dsar;              /* Screen aspect ratio */
+    long ldsar;               /* Scaled long DSAR */
+    short rtest;              /* Value to test byte reversal */
+    short btest = 0x1234;         /* Value to test byte-reversal */
+    static struct slhead slhi =       /* Master slide header sample */
+        {"AutoCAD Slide\r\n\32", 86,2, 0,0, 0.0, 0};
+    int curcolor = 7;             /* Current vector color */
+    pixel rgbcolor;           /* Pixel used to clear pixmap */
+
+    lx = ly = 32000;
+
+    /* Process the header of the slide file.  */
+
+    sdrawkcab = FALSE;            /* Initially guess byte order is OK */
+    fread(slfrof.slh, 17, 1, slfile);
+    fread(&slfrof.sntype, sizeof(char), 1, slfile);
+    fread(&slfrof.slevel, sizeof(char), 1, slfile);
+    fread(&slfrof.sxdots, sizeof(short), 1, slfile);
+    fread(&slfrof.sydots, sizeof(short), 1, slfile);
+    fread(ubfr, 4, 1, slfile);
+    fread(&slfrof.shwfill, sizeof(short), 1, slfile);
+    fread(&rtest, sizeof rtest, 1, slfile);
+
+    /* Verify that slide format is compatible with this program. */
+
+    if (STREQ(slfrof.slh, slhi.slh)) {
+        pm_error("this is not an AutoCAD slide file.");
+    }
+
+    /* Verify that the number format and file level in the header  are
+       compatible.  All slides written by versions of AutoCAD released
+       since September of 1987 are compatible with this format.  */
+
+    if ((slfrof.sntype != slhi.sntype) || (slfrof.slevel != slhi.slevel)) {
+        pm_error("incompatible slide file format");
+    }
+
+    /* Build SDSAR value from long scaled version. */
+
+    ldsar = 0L;
+    for (i = 3; i >= 0; i--) {
+    ldsar = (ldsar << 8) | ubfr[i];
+    }
+    slfrof.sdsar = ((double) ldsar) / 1E7;
+
+    /* Examine the byte order test value.   If it's backwards, set the
+       byte-reversal flag and correct all of the values we've read  in
+       so far. */
+
+    if (btest != rtest) {
+    sdrawkcab = TRUE;
+#define rshort(x) x = ((x >> 8) & 0xFF) | (x << 8)
+    rshort(slfrof.sxdots);
+    rshort(slfrof.sydots);
+    rshort(slfrof.shwfill);
+#undef rshort
+    }
+
+    /* Dump the header if we're blithering. */
+
+    if (blither || info) {
+        pm_message("Slide file type %d, level %d, hwfill type %d.",
+        slfrof.sntype, slfrof.slevel, slfrof.shwfill);
+        pm_message("Original screen size %dx%d, aspect ratio %.3f.",
+        slfrof.sxdots + 1, slfrof.sydots + 1, slfrof.sdsar);
+        pm_message("Byte order is %s.",
+            sdrawkcab ? "being reversed" : "the same");
+    }
+
+    /* If the display aspect ratio indicates that the  pixels  on  the
+       sending  screen  were  not  square,  adjust  the  size  of  the
+       generated bitmap to correct the  aspect  ratio  to  square  the
+       pixels.
+
+       We  always  correct  the aspect ratio by adjusting the width of
+       the image.  This guarantees that output from the SHADE command,
+       which  is  essentially  scan-line  data written in vector form,
+       will not be corrupted. */
+
+    dsar = ((double) slfrof.sxdots) / slfrof.sydots;
+    if (fabs(slfrof.sdsar - dsar) > 0.0001) {
+    if (adjust) {
+        ixdots = slfrof.sxdots * (slfrof.sdsar / dsar) + 0.5;
+        iydots = slfrof.sydots;
+        dsar = ((double) ixdots) / iydots;
+    } else {
+            pm_message("Warning - pixels on source screen were non-square.");
+            pm_message("          Specifying -adjust will correct image width to compensate.");
+        ixdots = slfrof.sxdots;
+        iydots = slfrof.sydots;
+        dsar = slfrof.sdsar;
+    }
+    } else {
+    /* Source pixels were square. */
+    ixdots = slfrof.sxdots;
+    iydots = slfrof.sydots;
+    dsar = slfrof.sdsar;
+    adjust = FALSE;           /* Mark no adjustment needed */
+    }
+
+    /* If there's a uniform scale factor specified, apply it. */
+
+    if (uscale > 0) {
+    ixdots = (ixdots * uscale) + 0.5;
+    iydots = (iydots * uscale) + 0.5;
+    }
+
+    /* If the image is to be stretched  to  a  given  width,  set  the
+       output  image  sizes accordingly.  If only a height or width is
+       given, scale the other direction proportionally to preserve the
+       aspect ratio. */
+
+    if (sxsize > 0) {
+    if (sysize > 0) {
+        iydots = sysize - 1;
+    } else {
+        iydots = ((((long) iydots) * (sxsize - 1)) +
+              (iydots / 2)) / ixdots;
+    }
+    ixdots = sxsize - 1;
+    } else if (sysize > 0) {
+    if (sxsize > 0) {
+        ixdots = sxsize - 1;
+    } else {
+        ixdots = ((((long) ixdots) * (sysize - 1)) +
+              (ixdots / 2)) / iydots;
+    }
+    iydots = sysize - 1;
+    }
+
+    if (adjust) {
+    pm_message(
+            "Resized from %dx%d to %dx%d to correct pixel aspect ratio.",
+        slfrof.sxdots + 1, slfrof.sydots + 1, ixdots + 1, iydots + 1);
+    }
+
+    /* Allocate image buffer and clear it to black. */
+
+    pixels = ppm_allocarray(pixcols = ixdots + 1, pixrows = iydots + 1);
+    PPM_ASSIGN(rgbcolor, 0, 0, 0);
+    ppmd_filledrectangle(pixels, pixcols, pixrows, pixmaxval, 0, 0,
+                         pixcols, pixrows, PPMD_NULLDRAWPROC,
+                         (char *) &rgbcolor);
+
+    if ((rescale = slfrof.sxdots != ixdots ||
+    slfrof.sydots != iydots ||
+    slfrof.sdsar != dsar) != 0) {
+
+        /* Rescale all coords. so they'll look (more or less)
+       right on this display.  */
+
+    xfac = (ixdots + 1) * 0x10000L;
+    xfac /= (long) (slfrof.sxdots + 1);
+    yfac = (iydots + 1) * 0x10000L;
+    yfac /= (long) (slfrof.sydots + 1);
+    if (dsar < slfrof.sdsar) {
+        yfac = yfac * dsar / slfrof.sdsar;
+       } else {
+        xfac = xfac * slfrof.sdsar / dsar;
+    }
+    }
+
+    poly.npoints = 0;             /* No flood in progress. */
+
+    while ((cw = sli()) != 0xFC00) {
+    switch (cw & 0xFF00) {
+        case 0xFB00:          /*  Short vector compressed  */
+        vec.f.x = lx + extend(cw & 0xFF);
+        vec.f.y = ly + slib();
+        vec.t.x = lx + slib();
+        vec.t.y = ly + slib();
+        lx = vec.f.x;
+        ly = vec.f.y;
+        if (rescale) {
+            vscale(&vec.f.x, &vec.f.y);
+            vscale(&vec.t.x, &vec.t.y);
+        }
+        (*slvec)(&vec, curcolor);/* Draw vector on screen */
+        slx = vec.f.x;        /* Save scaled point */
+        sly = vec.f.y;
+        break;
+
+        case 0xFC00:          /*  End of file  */
+        break;
+
+        case 0xFD00:          /*  Flood command  */
+        vec.f.x = sli();
+        vec.f.y = sli();
+        if ((int) vec.f.y < 0) { /* start or end */
+            if (poly.npoints != 0) { /* end?  */
+            if (poly.npoints > 2 && poly.npoints < 11) {
+                (*slflood)(&poly, curcolor);
+            } else {
+                            pm_error("Bad polygon vertex count (%d)",
+                   poly.npoints);
+            }
+            poly.npoints = 0;
+            } else {
+            poly.fill = -vec.f.y;  /* Start */
+            }
+        } else {          /* Polygon vertex */
+            if (poly.npoints < 10) {
+            if (rescale) {
+                vscale(&vec.f.x, &vec.f.y);
+            }
+            poly.pt[poly.npoints].x = vec.f.x;
+            poly.pt[poly.npoints].y = vec.f.y;
+            }
+            poly.npoints++;
+        }
+        break;
+
+        case 0xFE00:          /*  Common endpoint compressed  */
+        vec.f.x = lx + extend(cw & 0xFF);
+        vec.f.y = ly + slib();
+        lx = vec.f.x;
+        ly = vec.f.y;
+        vec.t.x = slx;
+        vec.t.y = sly;
+        if (rescale) {
+            vscale(&vec.f.x, &vec.f.y);
+        }
+        (*slvec)(&vec, curcolor);/* Draw vector */
+        slx = vec.f.x;        /* Save scaled point */
+        sly = vec.f.y;
+        break;
+
+        case 0xFF00:          /*  Change color  */
+        curcolor = cw & 0xFF;
+        break;
+
+        default:              /*  Co-ordinates  */
+        lx = vec.f.x = cw;
+        ly = vec.f.y = sli();
+        vec.t.x = sli();
+        vec.t.y = sli();
+        if (rescale) {
+           vscale(&vec.f.x, &vec.f.y);
+           vscale(&vec.t.x, &vec.t.y);
+        }
+        (*slvec)(&vec, curcolor);
+        slx = vec.f.x;        /* Save scaled point */
+        sly = vec.f.y;
+        break;
+    }
+    }
+}
+
+/*  SLIDEFIND  --  Find  a  slide  in  a  library  or,  if  DIRONLY is
+           nonzero, print a directory listing of the  library.
+           If  UCASEN  is nonzero, the requested slide name is
+           converted to upper case. */
+
+static void slidefind(sname, dironly, ucasen)
+  char *sname;
+  int dironly, ucasen;
+{
+    char uname[32];
+    unsigned char libent[36];
+    long pos;
+
+    if (dironly) {
+        pm_message("Slides in library:");
+    } else {
+    int i;
+    char *ip = sname;
+
+    for (i = 0; i < 31; i++) {
+        char ch = *ip++;
+        if (ch == EOS) {
+        break;
+        }
+        if (ucasen && ISLOWER(ch)) {
+        ch = TOUPPER(ch);
+        }
+        uname[i] = ch;
+    }
+    uname[i] = EOS;
+    }
+
+    /* Read slide library header and verify. */
+
+    if ((fread(libent, 32, 1, slfile) != 1) ||
+        (!STREQ((char *)libent, "AutoCAD Slide Library 1.0\015\012\32"))) {
+        pm_error("not an AutoCAD slide library file.");
+    }
+    pos = 32;
+
+    /* Search for a slide with the requested name. */
+
+    while (TRUE) {
+    if ((fread(libent, 36, 1, slfile) != 1) ||
+        (strlen((char *)libent) == 0)) {
+        if (dironly) {
+        return;
+        }
+            pm_error("slide %s not in library.", sname);
+    }
+    pos += 36;
+    if (dironly) {
+            pm_message("  %s", libent);
+    } else if (STREQ((char *)libent, uname)) {
+        long dpos = (((((libent[35] << 8) | libent[34]) << 8) |
+                 libent[33]) << 8) | libent[32];
+        if ((slfile == stdin) || (fseek(slfile, dpos, 0) == -1)) {
+        dpos -= pos;
+
+        while (dpos-- > 0) {
+            (void) getc(slfile);
+        }
+        }
+        break;
+    }
+    }
+}
+
+/*  DRAW  --  Draw a vector in the given AutoCAD color.  */
+
+static void draw(vec, color)
+  struct svector *vec;
+  int color;
+{
+    pixel rgbcolor;
+
+    if (blither) {
+        pm_message("Vector (%d, %d) - (%d, %d)  Color %d",
+           vec->f.x, vec->f.y, vec->t.x, vec->t.y, color);
+    }
+    assert(vec->f.x >= 0 && vec->f.x < pixcols);
+    assert(vec->f.y >= 0 && vec->f.y < pixrows);
+    assert(vec->t.x >= 0 && vec->t.x < pixcols);
+    assert(vec->t.y >= 0 && vec->t.y < pixrows);
+    PPM_ASSIGN(rgbcolor,
+           acadcol[color][0], acadcol[color][1], acadcol[color][2]);
+    ppmd_line(pixels, pixcols, pixrows, pixmaxval,
+          vec->f.x, iydots - vec->f.y, vec->t.x, iydots - vec->t.y,
+          PPMD_NULLDRAWPROC,
+          (char *) &rgbcolor);
+}
+
+/*  FLOOD  --  Draw a filled polygon.  */
+
+static void
+flood(struct spolygon * const poly,
+      int               const color) {
+
+    int i;
+    struct fillobj * handle;
+    pixel rgbcolor;
+
+    handle = ppmd_fill_create();
+
+    if (blither) {
+        pm_message("Polygon: %d points, fill type %d, color %d",
+                   poly->npoints, poly->fill, color);
+        for (i = 0; i < poly->npoints; i++) {
+            pm_message("   Point %d:  (%d, %d)", i + 1,
+                       poly->pt[i].x, poly->pt[i].y);
+        }
+    }
+
+    PPM_ASSIGN(rgbcolor,
+               acadcol[color][0], acadcol[color][1], acadcol[color][2]);
+    for (i = 0; i < poly->npoints; i++) {
+        assert(poly->pt[i].x >= 0 && poly->pt[i].x < pixcols);
+        assert(poly->pt[i].y >= 0 && poly->pt[i].y < pixrows);
+        ppmd_line(pixels, pixcols, pixrows, pixmaxval,
+                  poly->pt[i].x, iydots - poly->pt[i].y, 
+                  poly->pt[(i + 1) % poly->npoints].x,
+                  iydots - poly->pt[(i + 1) % poly->npoints].y,
+                  ppmd_fill_drawproc, handle);
+    }
+    ppmd_fill(pixels, pixcols, pixrows, pixmaxval,
+              handle, PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+
+    ppmd_fill_destroy(handle);
+}
+
+/*  Main program. */
+
+int main(argc, argv)
+  int argc;
+  char *argv[];
+{
+    int argn;
+    const char * const usage = "[-verbose] [-info] [-adjust] [-scale <s>]\n\
+                 [-dir] [-lib|-Lib <name>]\n\
+                 [-xsize|-width <x>] [-ysize|-height <y>] [sldfile]";
+    int scalespec = FALSE, widspec = FALSE, hgtspec = FALSE, dironly = FALSE,
+    ucasen;
+    char *slobber = (char *) 0;       /* Slide library item */
+
+
+    ppm_init(&argc, argv);
+    argn = 1;
+
+    while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0') {
+        if (pm_keymatch(argv[argn], "-verbose", 2)) {
+        blither = TRUE;
+        } else if (pm_keymatch(argv[argn], "-adjust", 2)) {
+        adjust = TRUE;
+        } else if (pm_keymatch(argv[argn], "-dir", 2)) {
+        dironly = TRUE;
+        } else if (pm_keymatch(argv[argn], "-info", 2)) {
+        info = TRUE;
+        } else if (pm_keymatch(argv[argn], "-lib", 2)) {
+        if (slobber != (char *) 0) {
+                pm_error("already specified a library item");
+        }
+            ucasen = argv[argn][1] != 'L';
+        argn++;
+        if (argn == argc) {
+        pm_usage(usage);
+        }
+        slobber = argv[argn];
+        } else if (pm_keymatch(argv[argn], "-scale", 2)) {
+        if (scalespec) {
+                pm_error("already specified a scale factor");
+        }
+        argn++;
+            if ((argn == argc) || (sscanf(argv[argn], "%lf", &uscale) != 1))
+        pm_usage(usage);
+        if (uscale <= 0.0) {
+                pm_error("scale factor must be greater than 0");
+        }
+        scalespec = TRUE;
+        } else if (pm_keymatch(argv[argn], "-xsize", 2) ||
+                   pm_keymatch(argv[argn], "-width", 2)) {
+        if (widspec) {
+                pm_error("already specified a width/xsize");
+        }
+        argn++;
+            if ((argn == argc) || (sscanf(argv[argn], "%d", &sxsize) != 1))
+        pm_usage(usage);
+        widspec = TRUE;
+        } else if (pm_keymatch(argv[argn], "-ysize", 2) ||
+                   pm_keymatch(argv[argn], "-height", 2)) {
+        if (hgtspec) {
+                pm_error("already specified a height/ysize");
+        }
+        argn++;
+            if ((argn == argc) || (sscanf(argv[argn], "%d", &sysize) != 1))
+        pm_usage(usage);
+        hgtspec = TRUE;
+    } else {
+        pm_usage(usage);
+    }
+    argn++;
+    }
+
+    /* If a file name is specified, open it.  Otherwise read from
+       standard input. */
+
+    if (argn < argc) {
+    slfile = pm_openr(argv[argn]);
+    argn++;
+    } else {
+    slfile = stdin;
+    }
+
+    if (argn != argc) {           /* Extra bogus arguments ? */
+    pm_usage(usage);
+    }
+
+    /* If we're extracting an item from a slide library, position the
+       input stream to the start of the chosen slide. */
+
+    if (dironly || (slobber != (char *) 0)) {
+    slidefind(slobber, dironly, ucasen);
+    }
+
+    if (!dironly) {
+    slider(draw, flood);
+    ppm_writeppm(stdout, pixels, pixcols, pixrows, pixmaxval, FALSE);
+    }
+    pm_close(slfile);
+    pm_close(stdout);
+    exit(0);
+}
diff --git a/converter/ppm/spctoppm.c b/converter/ppm/spctoppm.c
new file mode 100644
index 00000000..3eea7821
--- /dev/null
+++ b/converter/ppm/spctoppm.c
@@ -0,0 +1,212 @@
+/* spctoppm.c - read a compressed Spectrum file and produce a portable pixmap
+**
+** Copyright (C) 1991 by Steve Belczyk and Jef Poskanzer
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+
+#define ROWS 200
+#define COLS 320
+#define MAXVAL 7
+
+static void DoBitmap ARGS(( FILE* ifp ));
+static void DoChar ARGS(( int n, char c ));
+static void DoColormap ARGS(( FILE* ifp ));
+
+static char screen[ROWS*COLS/2];
+static short sscreen[ROWS*COLS/4];
+static pixel pal[ROWS][48];
+static long colormap_length, bitmap_length;
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    char c1, c2;
+    pixel* pixelrow;
+    register pixel* pP;
+    int row, col;
+
+
+    ppm_init( &argc, argv );
+
+    /* Check args. */
+    if ( argc > 2 )
+	pm_usage( "[spcfile]" );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    /* Check SPC file header. */
+    c1 = getc( ifp );
+    c2 = getc( ifp );
+
+    if ( ( c1 != 'S' ) || ( c2 != 'P' ) )
+	pm_error( "not a Spectrum picture" );
+
+    /* Skip reserved bytes. */
+    getc( ifp );
+    getc( ifp );
+
+    /* Get length of bitmap data. */
+    (void) pm_readbiglong( ifp, &bitmap_length );
+
+    /* and colormap */
+    (void) pm_readbiglong( ifp, &colormap_length );
+
+    /* Process bitmap. */
+    DoBitmap( ifp );
+
+    /* Process colormap. */
+    DoColormap( ifp );
+
+    pm_close( ifp );
+
+    /* Write the PPM file. */
+    ppm_writeppminit( stdout, COLS, ROWS, (pixval) MAXVAL, 0 );
+    pixelrow = ppm_allocrow( COLS );
+
+    for ( row = 0; row < ROWS; ++row )
+	{
+	for ( col = 0, pP = pixelrow; col < COLS; ++col, ++pP )
+	    {
+	    int c, ind, b, plane, x1;
+
+	    /* Compute pixel value. */
+	    ind = ( 80 * row ) + ( ( col >> 4 ) << 2 );
+	    b = 0x8000 >> (col & 0xf);
+	    c = 0;
+	    for ( plane = 0; plane < 4; ++plane )
+		if ( b & sscreen[ind+plane] )
+		    c |= (1 << plane);
+
+	    /* Compute palette index. */
+	    x1 = 10 * c;
+	    if ( c & 1 )
+		x1 -= 5;
+	    else
+		++x1;
+	    if ( ( col >= x1 ) && ( col < ( x1 + 160 ) ) )
+		c += 16;
+	    if ( col >= ( x1 + 160 ) )
+		c += 32;
+
+	    /* Store the proper color. */
+	    *pP = pal[row][c];
+	    }
+	ppm_writeppmrow( stdout, pixelrow, COLS, (pixval) MAXVAL, 0 );
+	}
+
+    pm_close( stdout );
+
+    exit( 0 );
+    }
+
+static void
+DoBitmap( ifp )
+    FILE* ifp;
+    {
+    int i;
+    long count, data;
+    signed char h, c;
+
+    /* Zero out first scan line. */
+    for ( i = 0; i < 160; ++i )
+	screen[i] = 0;
+
+    /* 'count' counts number of input bytes. */
+    count = 0;
+
+    /* 'data' counts just data bytes. */
+    data = 0;
+
+    while ( count < bitmap_length )
+	{
+	/* Get next record header. */
+	h = getc( ifp );
+	++count;
+
+	if ( ( h >= 0 ) && ( count < bitmap_length ) )
+	    {
+	    for ( i = 0; i <= h; ++i )
+		{
+		c = getc( ifp );
+		++count;
+		DoChar( data, c );
+		++data;
+		}
+	    }
+	else if ( ( h < 0 ) && ( count < bitmap_length ) )
+	    {
+	    c = getc( ifp );
+	    ++count;
+
+	    for ( i = 0; i < ( 2 - h ); ++i )
+		{
+		DoChar( data, c );
+		++data;
+		}
+	    }
+    }
+
+    /* Convert the char version of the screen to short. */
+    for ( i = 0; i < ROWS*COLS/4; ++i )
+	sscreen[i] = ( screen[i<<1] << 8 ) + ( 0xff & screen[(i<<1)+1] );
+    }
+
+#if __STDC__
+static void
+DoChar( int n, char c )
+#else /*__STDC__*/
+static void
+DoChar( n, c )
+    int n;
+    char c;
+#endif /*__STDC__*/
+    {
+    int i;
+
+    /* Compute screen index. */
+    i = 160 + 2 * ( n / 7960 ) + 8 * ( ( n % 7960 ) / 2 ) + ( n & 1 );
+    screen[i] = c;
+    }
+
+static void
+DoColormap( ifp )
+    FILE* ifp;
+    {
+    int i, j, b;
+    short mask;
+
+    /* Clear first three palettes. */
+    for ( j = 0; j < 48; ++j )
+	PPM_ASSIGN( pal[0][j], 0, 0, 0 );
+
+    /* Read the palettes. */
+    for ( i = 1; i < ROWS; ++i )
+	for ( j = 0; j < 3; ++j )
+	    {
+	    (void) pm_readbigshort( ifp, &mask );
+	    for ( b = 0; b < 15; ++b )
+		if ( mask & ( 1 << b ) )
+		    {
+		    short k;
+		    (void) pm_readbigshort( ifp, &k );
+		    PPM_ASSIGN( pal[i][(j*16)+b],
+			( k & 0x700 ) >> 8,
+			( k & 0x070 ) >> 4,
+			( k & 0x007 ) );
+		    }
+	    }
+    }
diff --git a/converter/ppm/sputoppm.c b/converter/ppm/sputoppm.c
new file mode 100644
index 00000000..2671bffa
--- /dev/null
+++ b/converter/ppm/sputoppm.c
@@ -0,0 +1,108 @@
+/* sputoppm.c - read an uncompressed Spectrum file and produce a portable pixmap
+**
+** Copyright (C) 1991 by Steve Belczyk and Jef Poskanzer
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+
+#define ROWS 200
+#define COLS 320
+#define MAXVAL 7
+
+static pixel pal[ROWS][48];                /* Spectrum palettes, three per row */
+static short screen[ROWS*COLS/4];          /* simulates the Atari's video RAM */
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    int i, j;
+    pixel* pixelrow;
+    register pixel* pP;
+    int row, col;
+
+
+    ppm_init( &argc, argv );
+
+    /* Check args. */
+    if ( argc > 2 )
+        pm_usage( "[spufile]" );
+
+    if ( argc == 2 )
+        ifp = pm_openr( argv[1] );
+    else
+        ifp = stdin;
+
+    /* Read the SPU file */
+
+    /* Read the screen data. */
+    for ( i = 0; i < ROWS*COLS/4; ++i )
+        (void) pm_readbigshort( ifp, &screen[i] );
+
+    /* Clear the first palette line. */
+    for ( j = 0; j < 48; ++j )
+        PPM_ASSIGN( pal[0][j], 0, 0, 0 );
+
+    /* Read the palettes. */
+    for ( i = 1; i < ROWS; ++i )
+        for ( j = 0; j < 48; ++j )
+            {
+            short k;
+            (void) pm_readbigshort( ifp, &k );
+            PPM_ASSIGN( pal[i][j],
+                ( k & 0x700 ) >> 8,
+                ( k & 0x070 ) >> 4,
+                ( k & 0x007 ) );
+            }
+
+    pm_close( ifp );
+
+    /* Ok, get set for writing PPM. */
+    ppm_writeppminit( stdout, COLS, ROWS, (pixval) MAXVAL, 0 );
+    pixelrow = ppm_allocrow( COLS );
+
+    /* Now do the conversion. */
+    for ( row = 0; row < ROWS; ++row )
+        {
+        for ( col = 0, pP = pixelrow; col < COLS; ++col, ++pP )
+            {
+            int c, ind, b, plane, x1;
+
+            /* Compute pixel value. */
+            ind = 80 * row + ( ( col >> 4 ) << 2 );
+            b = 0x8000 >> (col & 0xf);
+            c = 0;
+            for ( plane = 0; plane < 4; ++plane )
+                if ( b & screen[ind+plane] )
+                    c |= (1 << plane);
+
+            /* Compute palette index. */
+            x1 = 10 * c;
+            if ( c & 1 )
+                x1 -= 5;
+            else
+                ++x1;
+            if ( ( col >= x1 ) && ( col < ( x1 + 160 ) ) )
+                c += 16;
+            if ( col >= ( x1 + 160 ) )
+                c += 32;
+
+            /* Store the proper color. */
+            *pP = pal[row][c];
+            }
+        ppm_writeppmrow( stdout, pixelrow, COLS, (pixval) MAXVAL, 0 );
+        }
+
+    pm_close( stdout );
+
+    exit( 0 );
+    }
diff --git a/converter/ppm/tgatoppm.c b/converter/ppm/tgatoppm.c
new file mode 100644
index 00000000..9f2bc4c0
--- /dev/null
+++ b/converter/ppm/tgatoppm.c
@@ -0,0 +1,462 @@
+/* tgatoppm.c - read a TrueVision Targa file and write a portable pixmap
+**
+** Partially based on tga2rast, version 1.0, by Ian MacPhedran.
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <string.h>
+#include "ppm.h"
+#include "tga.h"
+#include "shhopt.h"
+#include "nstring.h"
+
+#define MAXCOLORS 16384
+
+static int mapped, rlencoded;
+
+static pixel ColorMap[MAXCOLORS];
+static gray AlphaMap[MAXCOLORS];
+static int RLE_count = 0, RLE_flag = 0;
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filename;
+    unsigned int headerdump;
+    const char *alpha_filename;
+    unsigned int alpha_stdout;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                   struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that many of the strings that this function returns in the
+   *cmdlineP structure are actually in the supplied argv array.  And
+   sometimes, one of these strings is actually just a suffix of an entry
+   in argv!
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int alpha_spec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "headerdump", OPT_FLAG,   NULL, &cmdlineP->headerdump,   0);
+    OPTENT3(0, "debug",      OPT_FLAG,   NULL, &cmdlineP->headerdump,   0);
+    OPTENT3(0, "alphaout",   OPT_STRING, &cmdlineP->alpha_filename,
+            &alpha_spec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc - 1 == 0)
+        cmdlineP->input_filename = "=";  /* he wants stdin */
+    else if (argc - 1 == 1)
+        cmdlineP->input_filename = strdup(argv[1]);
+    else 
+        pm_error("Too many arguments.  The only argument accepted "
+                 "is the input file specification");
+
+    if (alpha_spec &&
+        STREQ(cmdlineP->alpha_filename, "-"))
+        cmdlineP->alpha_stdout = 1;
+    else 
+        cmdlineP->alpha_stdout = 0;
+
+    if (!alpha_spec)
+        cmdlineP->alpha_filename = NULL;
+}
+
+
+
+
+static unsigned char
+getbyte(FILE * const ifP) {
+
+    unsigned char c;
+
+    if ( fread( (char*) &c, 1, 1, ifP ) != 1 )
+        pm_error( "EOF / read error" );
+    
+    return c;
+}
+
+
+
+static void
+get_pixel(FILE * const ifP, pixel * dest, int Size, gray *alpha_p) {
+
+    static pixval Red, Grn, Blu;
+    static pixval Alpha;
+    unsigned char j, k;
+    static unsigned int l;
+
+    /* Check if run length encoded. */
+    if ( rlencoded )
+	{
+	if ( RLE_count == 0 )
+	    { /* Have to restart run. */
+	    unsigned char i;
+	    i = getbyte( ifP );
+	    RLE_flag = ( i & 0x80 );
+	    if ( RLE_flag == 0 )
+		/* Stream of unencoded pixels. */
+		RLE_count = i + 1;
+	    else
+		/* Single pixel replicated. */
+		RLE_count = i - 127;
+	    /* Decrement count & get pixel. */
+	    --RLE_count;
+	    }
+	else
+	    { /* Have already read count & (at least) first pixel. */
+	    --RLE_count;
+	    if ( RLE_flag != 0 )
+		/* Replicated pixels. */
+		goto PixEncode;
+	    }
+	}
+    /* Read appropriate number of bytes, break into RGB. */
+    switch ( Size )
+	{
+	case 8:				/* Grayscale, read and triplicate. */
+	Red = Grn = Blu = l = getbyte( ifP );
+    Alpha = 0;
+	break;
+
+	case 16:			/* 5 bits each of red green and blue. */
+	case 15:			/* Watch byte order. */
+	j = getbyte( ifP );
+	k = getbyte( ifP );
+	l = ( (unsigned int) k << 8 ) + j;
+	Red = ( k & 0x7C ) >> 2;
+	Grn = ( ( k & 0x03 ) << 3 ) + ( ( j & 0xE0 ) >> 5 );
+	Blu = j & 0x1F;
+    Alpha = 0;
+	break;
+
+	case 32:            /* 8 bits each of blue, green, red, and alpha */
+	case 24:			/* 8 bits each of blue, green, and red. */
+	Blu = getbyte( ifP );
+	Grn = getbyte( ifP );
+	Red = getbyte( ifP );
+	if ( Size == 32 )
+	    Alpha = getbyte( ifP );
+    else
+        Alpha = 0;
+	l = 0;
+	break;
+
+	default:
+	pm_error( "unknown pixel size (#2) - %d", Size );
+	}
+
+PixEncode:
+    if ( mapped ) {
+        *dest = ColorMap[l];
+        *alpha_p = AlphaMap[l];
+    } else {
+        PPM_ASSIGN( *dest, Red, Grn, Blu );
+        *alpha_p = Alpha;
+    }
+    }
+
+
+
+static void
+readtga(FILE * const ifP, struct ImageHeader * tgaP) {
+
+    unsigned char flags;
+    ImageIDField junk;
+
+    tgaP->IdLength = getbyte( ifP );
+    tgaP->CoMapType = getbyte( ifP );
+    tgaP->ImgType = getbyte( ifP );
+    tgaP->Index_lo = getbyte( ifP );
+    tgaP->Index_hi = getbyte( ifP );
+    tgaP->Length_lo = getbyte( ifP );
+    tgaP->Length_hi = getbyte( ifP );
+    tgaP->CoSize = getbyte( ifP );
+    tgaP->X_org_lo = getbyte( ifP );
+    tgaP->X_org_hi = getbyte( ifP );
+    tgaP->Y_org_lo = getbyte( ifP );
+    tgaP->Y_org_hi = getbyte( ifP );
+    tgaP->Width_lo = getbyte( ifP );
+    tgaP->Width_hi = getbyte( ifP );
+    tgaP->Height_lo = getbyte( ifP );
+    tgaP->Height_hi = getbyte( ifP );
+    tgaP->PixelSize = getbyte( ifP );
+    flags = getbyte( ifP );
+    tgaP->AttBits = flags & 0xf;
+    tgaP->Rsrvd = ( flags & 0x10 ) >> 4;
+    tgaP->OrgBit = ( flags & 0x20 ) >> 5;
+    tgaP->IntrLve = ( flags & 0xc0 ) >> 6;
+    
+    if ( tgaP->IdLength != 0 )
+        fread( junk, 1, (int) tgaP->IdLength, ifP );
+}
+
+
+
+static void
+get_map_entry(FILE * const ifP, pixel * Value, int Size, gray * Alpha) {
+
+    unsigned char j, k, r, g, b, a;
+
+    /* Read appropriate number of bytes, break into rgb & put in map. */
+    switch ( Size )
+	{
+	case 8:				/* Grayscale, read and triplicate. */
+        r = g = b = getbyte( ifP );
+        a = 0;
+        break;
+        
+	case 16:			/* 5 bits each of red green and blue. */
+	case 15:			/* Watch for byte order. */
+        j = getbyte( ifP );
+        k = getbyte( ifP );
+        r = ( k & 0x7C ) >> 2;
+        g = ( ( k & 0x03 ) << 3 ) + ( ( j & 0xE0 ) >> 5 );
+        b = j & 0x1F;
+        a = 0;
+        break;
+        
+	case 32:            /* 8 bits each of blue, green, red, and alpha */
+	case 24:			/* 8 bits each of blue green and red. */
+        b = getbyte( ifP );
+        g = getbyte( ifP );
+        r = getbyte( ifP );
+        if ( Size == 32 )
+            a = getbyte( ifP );
+        else 
+            a = 0;
+        break;
+        
+	default:
+        pm_error( "unknown colormap pixel size (#2) - %d", Size );
+	}
+    PPM_ASSIGN( *Value, r, g, b );
+    *Alpha = a;
+}
+
+
+
+static void
+dumpHeader(struct ImageHeader const tga_head) {
+    const char * imgTypeName;
+    switch(tga_head.ImgType) {
+    case TGA_Map:      imgTypeName = "TGA_Map";      break;
+    case TGA_RGB:      imgTypeName = "TGA_RGB";      break;
+    case TGA_Mono:     imgTypeName = "TGA_Mono";     break;
+    case TGA_RLEMap:   imgTypeName = "TGA_RLEMap";   break;
+    case TGA_RLERGB:   imgTypeName = "TGA_RLERGB";   break;
+    case TGA_RLEMono:  imgTypeName = "TGA_RLEMono";  break;
+    case TGA_CompMap:  imgTypeName = "TGA_CompMap";  break;
+    case TGA_CompMap4: imgTypeName = "TGA_CompMap4"; break;
+    default:           imgTypeName = "unknown";
+    }
+    pm_message( "IdLength = %d", (int) tga_head.IdLength );
+    pm_message( "CoMapType = %d", (int) tga_head.CoMapType );
+    pm_message( "ImgType = %d (%s)", (int) tga_head.ImgType, imgTypeName );
+    pm_message( "Index_lo = %d", (int) tga_head.Index_lo );
+    pm_message( "Index_hi = %d", (int) tga_head.Index_hi );
+    pm_message( "Length_lo = %d", (int) tga_head.Length_lo );
+    pm_message( "Length_hi = %d", (int) tga_head.Length_hi );
+    pm_message( "CoSize = %d", (int) tga_head.CoSize );
+    pm_message( "X_org_lo = %d", (int) tga_head.X_org_lo );
+    pm_message( "X_org_hi = %d", (int) tga_head.X_org_hi );
+    pm_message( "Y_org_lo = %d", (int) tga_head.Y_org_lo );
+    pm_message( "Y_org_hi = %d", (int) tga_head.Y_org_hi );
+    pm_message( "Width_lo = %d", (int) tga_head.Width_lo );
+    pm_message( "Width_hi = %d", (int) tga_head.Width_hi );
+    pm_message( "Height_lo = %d", (int) tga_head.Height_lo );
+    pm_message( "Height_hi = %d", (int) tga_head.Height_hi );
+    pm_message( "PixelSize = %d", (int) tga_head.PixelSize );
+    pm_message( "AttBits = %d", (int) tga_head.AttBits );
+    pm_message( "Rsrvd = %d", (int) tga_head.Rsrvd );
+    pm_message( "OrgBit = %d", (int) tga_head.OrgBit );
+    pm_message( "IntrLve = %d", (int) tga_head.IntrLve );
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    struct ImageHeader tga_head;
+    FILE* ifP;
+    FILE *imageout_file, *alpha_file;
+    int rows, cols, row, realrow, truerow, baserow;
+    int maxval;
+    pixel** pixels;   /* The image array in ppm format */
+    gray** alpha;     /* The alpha channel array in pgm format */
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.input_filename);
+
+    if (cmdline.alpha_stdout)
+        alpha_file = stdout;
+    else if (cmdline.alpha_filename == NULL) 
+        alpha_file = NULL;
+    else
+        alpha_file = pm_openw(cmdline.alpha_filename);
+    
+    if (cmdline.alpha_stdout) 
+        imageout_file = NULL;
+    else
+        imageout_file = stdout;
+    
+    /* Read the Targa file header. */
+    readtga(ifP, &tga_head);
+
+    if (cmdline.headerdump) 
+        dumpHeader(tga_head);
+
+    rows = ((int) tga_head.Height_lo) + ((int) tga_head.Height_hi) * 256;
+    cols = ((int) tga_head.Width_lo)  + ((int) tga_head.Width_hi)  * 256;
+
+    switch (tga_head.ImgType) {
+	case TGA_Map:
+	case TGA_RGB:
+	case TGA_Mono:
+	case TGA_RLEMap:
+	case TGA_RLERGB:
+	case TGA_RLEMono:
+        break;
+	default:
+        pm_error("unknown Targa image type %d", tga_head.ImgType);
+	}
+    
+    if (tga_head.ImgType == TGA_Map ||
+        tga_head.ImgType == TGA_RLEMap ||
+        tga_head.ImgType == TGA_CompMap ||
+        tga_head.ImgType == TGA_CompMap4)
+	{ /* Color-mapped image */
+        if (tga_head.CoMapType != 1)
+            pm_error( 
+                "mapped image (type %d) with color map type != 1",
+                tga_head.ImgType );
+        mapped = true;
+        /* Figure maxval from CoSize. */
+        switch (tga_head.CoSize) {
+	    case 8:
+	    case 24:
+	    case 32:
+            maxval = 255;
+            break;
+
+	    case 15:
+	    case 16:
+            maxval = 31;
+            break;
+
+	    default:
+	    pm_error(
+		"unknown colormap pixel size - %d", tga_head.CoSize );
+	    }
+	} else { 
+        /* Not colormap, so figure maxval from PixelSize. */
+        mapped = false;
+        switch ( tga_head.PixelSize ) {
+	    case 8:
+	    case 24:
+	    case 32:
+            maxval = 255;
+            break;
+            
+	    case 15:
+	    case 16:
+            maxval = 31;
+            break;
+            
+	    default:
+            pm_error("unknown pixel size - %d", tga_head.PixelSize);
+	    }
+	}
+    
+    /* If required, read the color map information. */
+    if ( tga_head.CoMapType != 0 ) {
+        unsigned int i;
+        unsigned int temp1, temp2;
+
+        temp1 = tga_head.Index_lo + tga_head.Index_hi * 256;
+        temp2 = tga_head.Length_lo + tga_head.Length_hi * 256;
+        if ((temp1 + temp2 + 1) >= MAXCOLORS)
+            pm_error("too many colors - %d", (temp1 + temp2 + 1));
+        for (i = temp1; i < (temp1 + temp2); ++i)
+            get_map_entry(ifP, &ColorMap[i], (int) tga_head.CoSize,
+                          &AlphaMap[i]);
+	}
+
+    /* Check run-length encoding. */
+    if (tga_head.ImgType == TGA_RLEMap ||
+        tga_head.ImgType == TGA_RLERGB ||
+        tga_head.ImgType == TGA_RLEMono)
+        rlencoded = 1;
+    else
+        rlencoded = 0;
+    
+    /* Read the Targa file body and convert to portable format. */
+    pixels = ppm_allocarray( cols, rows );
+    alpha = pgm_allocarray( cols, rows );
+    truerow = 0;
+    baserow = 0;
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+
+        realrow = truerow;
+        if (tga_head.OrgBit == 0)
+            realrow = rows - realrow - 1;
+        
+        for (col = 0; col < cols; ++col)
+            get_pixel(ifP, &(pixels[realrow][col]), (int) tga_head.PixelSize,
+                      &(alpha[realrow][col]));
+        if (tga_head.IntrLve == TGA_IL_Four)
+            truerow += 4;
+        else if (tga_head.IntrLve == TGA_IL_Two)
+            truerow += 2;
+        else
+            ++truerow;
+        if (truerow >= rows)
+            truerow = ++baserow;
+	}
+    pm_close(ifP);
+    
+    if (imageout_file) 
+        ppm_writeppm(imageout_file, pixels, cols, rows, (pixval) maxval, 0);
+    if (alpha_file)
+        pgm_writepgm(alpha_file, alpha, cols, rows, (pixval) maxval, 0);
+    if (imageout_file) 
+        pm_close(imageout_file);
+    if (alpha_file)
+        pm_close(alpha_file);
+
+    return 0;
+}
diff --git a/converter/ppm/vidtoppm.c b/converter/ppm/vidtoppm.c
new file mode 100644
index 00000000..f3c20404
--- /dev/null
+++ b/converter/ppm/vidtoppm.c
@@ -0,0 +1,270 @@
+/* Bryan got this from mm.ftp-cs.berkeley.edu from the package
+   mpeg-encode-1.5b-src under the name vidtoppm.c on March 30, 2000.  
+   The file was dated January 19, 1995.  
+
+   This program does not use the netpbm libraries, but generates its
+   output via the program rawtoppm.  If any work is ever done on it
+   (or, more to the point, any interest ever expressed in it), it
+   should be converted just to call ppmwrite(), etc. directly.
+
+   There was no attached documentation, but the program appears to 
+   convert from Parallax XVideo JPEG format to a sequence of PPM files.  It
+   does this conversion by putting each frame in a window and then 
+   reading it out of the window, using libXvid.
+
+   Because it requires special libraries and there is no known
+   requirement for it today, we are not including this program in the
+   standard netpbm build.  But the source code is here in case someone
+   is interested in it later.
+
+*/
+
+/*
+ * Copyright (c) 1995 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+/* gcc -o playone playone.c -lX11 -lXvid -I/n/picasso/project/mm/xvideo/include
+ */
+#include <stdio.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <XPlxExt.h>
+
+#include "ppm.h"
+
+usage (p)
+char *p;
+
+{
+    fprintf (stderr, "Usage: %s filename width height start end outbase [quality]\n", p);
+    exit (1);
+}
+
+static char buffer[300000];
+
+Visual *
+FindFullColorVisual (dpy, depth)
+    Display *dpy;
+    int *depth;
+{
+  XVisualInfo vinfo;
+  XVisualInfo *vinfo_ret;
+  int numitems, maxdepth;
+  
+  vinfo.class = TrueColor;
+  
+  vinfo_ret = XGetVisualInfo(dpy, VisualClassMask, &vinfo, &numitems);
+  
+  if (numitems == 0) return NULL;
+
+  maxdepth = 0;
+  while(numitems > 0) {
+    if (vinfo_ret[numitems-1].depth > maxdepth) {
+      maxdepth = vinfo_ret[numitems-1 ].depth;
+    }
+    numitems--;
+  }
+  XFree(vinfo_ret);
+
+  if (maxdepth < 24) return NULL;
+
+  if (XMatchVisualInfo(dpy, DefaultScreen(dpy), maxdepth, 
+		       TrueColor, &vinfo)) {
+    *depth = maxdepth;
+    return vinfo.visual;
+  }
+  
+  return NULL;
+}
+
+Window
+CreateFullColorWindow (dpy, x, y, w, h)
+    Display *dpy;
+    int x, y, w, h;
+{
+    int depth;
+    Visual *visual;
+    XSetWindowAttributes xswa;
+    unsigned int mask;
+    unsigned int class;
+    int screen;
+
+    screen = XDefaultScreen(dpy);
+    class = InputOutput;	/* Could be InputOnly */
+    visual = FindFullColorVisual (dpy, &depth);
+    if (visual == NULL) {
+	return 0;
+    }
+    mask = CWBackPixel | CWColormap | CWBorderPixel;
+    xswa.colormap = XCreateColormap(dpy, XRootWindow(dpy, screen),
+		    visual, AllocNone);
+    xswa.background_pixel = BlackPixel(dpy, DefaultScreen(dpy));
+    xswa.border_pixel = WhitePixel(dpy, DefaultScreen(dpy));
+
+    return XCreateWindow(dpy, RootWindow(dpy, screen), x, y, w, h,
+	1, depth, class, visual, mask, &xswa);
+}
+
+main (argc, argv)
+int argc;
+char **argv;
+
+{
+  char *filename;
+  Display *dpy;
+  int screen;
+  Window root;
+  XEvent event;
+  GC gc;
+  Window win;
+  XPlxCImage image;
+  int size;
+  char *qTable;
+  FILE *inFile;
+  FILE *outFile;
+  extern char *malloc();
+  int fd, r1, r2, i, j, r;
+  int quality;
+  int start, end;
+  XImage *ximage;
+  char *tdata;
+  char *obase;
+  char ofname[256];
+  int height, width;
+  char command[256];
+
+  ppm_init(&argc, argv);
+
+  if ((argc != 7) && (argc != 8))usage (argv[0]);
+  filename = argv[1];
+
+  width = atoi(argv[2]);
+  height = atoi(argv[3]);
+
+  start = atoi(argv[4]);
+  end = atoi(argv[5]);
+
+  if ((start < 1) || (end < start)) {
+    perror ("Bad start and end values.");
+    exit();
+  }
+
+  obase = argv[6];
+
+  quality = 100;
+  
+  if (argc > 7)
+    quality = atoi (argv[7]);
+  
+  dpy = XOpenDisplay (NULL);
+  screen = DefaultScreen(dpy);
+  root = DefaultRootWindow(dpy);
+/*  gc = DefaultGC(dpy, screen); */
+/*  win = XCreateSimpleWindow (dpy, root, 0, 0, width, height,
+			     0, NULL, NULL);
+*/
+  win = CreateFullColorWindow(dpy, 0, 0, width+4, height+4);
+  gc = XCreateGC(dpy, win, 0, NULL);
+
+  if (!win ) {
+    perror ("Unable to create window");
+    exit(1);
+  }
+
+  XMapWindow (dpy, win);
+  XSelectInput (dpy, win, ExposureMask |ButtonPressMask);
+
+  size = MakeQTables(quality, &qTable);
+  XPlxPutTable(dpy, win, gc, qTable, size, 0);
+  XPlxPutTable(dpy, win, gc, qTable, size, 1);
+  XPlxVideoTag (dpy, win, gc, PLX_VIDEO);
+  
+  inFile = fopen(filename, "rb");
+  if (inFile == NULL) {
+    perror (filename);
+    exit (1);
+  }
+  fd = fileno(inFile);
+  wait(2);
+
+  for (i=0; i<start; i++) {
+    if (read (fd, &image, sizeof(image)) != sizeof(image)) {
+      perror("End of file.");
+      exit();
+    }
+    image.data = buffer;
+    if (read (fd, buffer, image.size) != image.size) {
+      perror("End of file.");
+      exit();
+    }
+  }
+    
+  for (i=start; i<=end; i++) {
+    fprintf(stdout, "GRABBING FRAME %d\n", i);
+
+    if (read (fd, &image, sizeof(image)) != sizeof(image)) {
+      perror("End of file.");
+      exit();
+    }
+    image.data = buffer;
+    if (read (fd, buffer, image.size) != image.size) {
+      perror("End of file.");
+      exit();
+    }
+    
+    XPlxPutCImage (dpy, win, gc, &image, 0, 0, image.width,
+		   image.height, 0, 0, width+2, height+2, 1);
+
+    XFlush(dpy);
+
+    ximage = XGetImage(dpy, win, 0, 0, width, height, 0x00ffffff,
+		       ZPixmap);
+    
+    if (i == 0) {
+      fprintf(stderr, "Depth %d\n", ximage->depth);
+      fprintf(stderr, "Height: %d Width: %d\n", height, width );
+    }
+    tdata = ximage->data;
+
+
+    sprintf(ofname, "%s%d.ppm", obase, i);
+    outFile = fopen("/tmp/foobar", "wb");
+    if (!outFile) {
+      perror("Couldn't open output file.");
+    }
+
+    for (r=0; r<height; r++) {
+      for (j=0; j<width; j++) {
+	fputc(*(tdata+3), outFile);
+	fputc(*(tdata+2), outFile);
+	fputc(*(tdata+1), outFile);
+	tdata += 4;
+      }
+    }
+
+    fclose(outFile);
+
+    free(tdata);
+
+    sprintf(command, "rawtoppm %d %d < /tmp/foobar > %s",
+	    width, height, ofname);
+    system(command);
+  }
+}
diff --git a/converter/ppm/winico.h b/converter/ppm/winico.h
new file mode 100644
index 00000000..4b8ac38b
--- /dev/null
+++ b/converter/ppm/winico.h
@@ -0,0 +1,88 @@
+#ifndef WINICO_H_INCLUDED
+#define WINICO_H_INCLUDED
+
+/* A specification for the Windows icon format is at   (2000.06.08)
+
+   http://www.daubnet.com/formats/ICO.html
+
+*/
+
+typedef unsigned char      u1;
+typedef unsigned short int u2;
+typedef unsigned int       u4;
+
+typedef struct MS_Ico_ *          MS_Ico;
+typedef struct IC_Entry_ *        IC_Entry;
+typedef struct IC_InfoHeader_ *   IC_InfoHeader;
+typedef struct IC_Color_ *        IC_Color;
+/* Not part of the spec, but useful in constructing the icon. */
+typedef struct IC_Palette_ *      IC_Palette;
+typedef struct ICON_bmp_ *        ICON_bmp;
+
+struct MS_Ico_ {
+   u2 reserved;
+   u2 type;
+   u2 count;
+   IC_Entry * entries;
+};
+
+
+struct IC_Entry_ {
+   u1 width;
+   u1 height;
+   /*
+    * color_count is actually a byte (u1)... but 0 = 256, so I've used a short (u2).
+    */
+   u2 color_count;
+   u1 reserved;
+   u2 planes;
+   u2 bitcount;
+   u4 size_in_bytes;
+   u4 file_offset;
+   IC_InfoHeader ih;
+   IC_Color * colors;
+   /*
+    * Below here, I have useful fields which aren't in the spec, but 
+    * save having to keep stoopid amounts of global data.
+    */
+   u1 * andBitmap;        /* Used in reader. */
+   u1 * xorBitmap;
+   int xBytesXor;         /* Not used in reading, but saved for writing. */
+   int xBytesAnd;         /* Not used in reading, but saved for writing. */
+   u1 ** andBitmapOut;    /* it's just easier to use a 2d array in the code.*/
+   u1 ** xorBitmapOut;    /* Sorry! :) */
+};
+
+struct IC_InfoHeader_ {
+   u4 size;
+   u4 width;
+   u4 height;
+   u2 planes;
+   u2 bitcount;
+   u4 compression;
+   u4 imagesize;
+   u4 x_pixels_per_m;
+   u4 y_pixels_per_m;
+   u4 colors_used;
+   u4 colors_important;
+};
+
+struct IC_Color_ {
+   u1 red;
+   u1 green;
+   u1 blue;
+   u1 reserved;
+};
+
+struct IC_Palette_ {
+   u4 col_amount;
+   IC_Color * colors;
+};
+
+struct ICON_bmp_ {
+   int xBytes;
+   u4 size;    /* just col_amount * height, but save calculating too often. */
+   u1 ** data;
+};
+
+#endif
diff --git a/converter/ppm/winico.html b/converter/ppm/winico.html
new file mode 100644
index 00000000..6e33874b
--- /dev/null
+++ b/converter/ppm/winico.html
@@ -0,0 +1,701 @@
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+<BASE HREF="http://www.oreilly.com/centers/gff/formats/miccur/"><table border=1 width=100%><tr><td><table border=1 bgcolor=#ffffff cellpadding=10 cellspacing=0 width=100% color=#ffffff><tr><td><font face=arial,sans-serif color=black size=-1>This is <b><font color=#0039b6>G</font> <font color=#c41200>o</font> <font color=#f3c518>o</font> <font color=#0039b6>g</font> <font color=#30a72f>l</font> <font color=#c41200>e</font></b>'s <a href="http://www.google.com/help/features.html#cached"><font color=blue>cache</font></a> of <A HREF="http://www.oreilly.com/centers/gff/formats/miccur/"><font color=blue>http://www.oreilly.com/centers/gff/formats/miccur/</font></a>.<br>
+<b><font color=#0039b6>G</font> <font color=#c41200>o</font> <font color=#f3c518>o</font> <font color=#0039b6>g</font> <font color=#30a72f>l</font> <font color=#c41200>e</font></b>'s cache is the snapshot that we took of the page as we crawled the web.<br>
+The page may have changed since that time.  Click here for the <A HREF="http://www.oreilly.com/centers/gff/formats/miccur/"><font color=blue>current page</font></a> without highlighting.<br>To link to or bookmark this page, use the following url: <code>http://www.google.com/search?q=cache:dgpSCZ8_GJwC:www.oreilly.com/centers/gff/formats/miccur/+windows++icon+transparent&amp;hl=en&amp;ie=UTF-8</code></font><br><br><center><font size=-2><i>Google is not affiliated with the authors of this page nor responsible for its content.</i></font></center></td></tr>
+<tr><td>
+<table border=0 cellpadding=0 cellspacing=0><tr><td><font face=arial,sans-serif color=black size=-1>These search terms have been highlighted:&nbsp;</font></td><td bgcolor=#ffff66><B><font face=arial,sans-serif color=black size=-1>windows&nbsp;</font></B></td><td bgcolor=#A0FFFF><B><font face=arial,sans-serif color=black size=-1>icon&nbsp;</font></B></td><td bgcolor=#99ff99><B><font face=arial,sans-serif color=black size=-1>transparent&nbsp;</font></B></td></tr></table>
+</td></tr></table></td></tr></table>
+<hr>
+<html>
+<head>
+<title>GFF Format Summary: Microsoft Windows Cursor and Icon</title>
+</head>
+<body>
+<h1><A NAME="SPEC-MICCUR">Microsoft <B style="color:black;background-color:#ffff66">Windows</B> Cursor and <B style="color:black;background-color:#A0FFFF">Icon</B></A></h1>
+<p>
+<b>Also Known As:</b> CUR, ICO
+<p>
+<hr>
+<p>
+
+<p>
+<table border=0>
+<tr valign=top>
+  <td align=left><b>Type</b></td>
+  <td align=left>Bitmap</td>
+</tr>
+<tr valign=top>
+  <td align=left><b>Colors</b></td>
+  <td align=left>1-bit and 4-bit</td>
+</tr>
+<tr valign=top>
+  <td align=left><b>Compression</b></td>
+  <td align=left>None</td>
+</tr>
+<tr valign=top>
+  <td align=left><b>Maximum Image Size</b></td>
+  <td align=left>4Gx4G pixels</td>
+</tr>
+<tr valign=top>
+  <td align=left><b>Multiple Images Per File</b></td>
+  <td align=left>Yes</td>
+</tr>
+<tr valign=top>
+  <td align=left><b>Numerical Format</b></td>
+  <td align=left>Little-endian</td>
+</tr>
+<tr valign=top>
+  <td align=left><b>Originator</b></td>
+  <td align=left>Microsoft</td>
+</tr>
+<tr valign=top>
+  <td align=left><b>Platform</b></td>
+  <td align=left>Microsoft <B style="color:black;background-color:#ffff66">Windows</B></td>
+</tr>
+<tr valign=top>
+  <td align=left><b>Supporting Applications</b></td>
+  <td align=left>Microsoft <B style="color:black;background-color:#ffff66">Windows</B></td>
+</tr>
+<tr valign=top>
+  <td align=left><b>See Also</b></td>
+  <td align=left><A HREF="gffse:/format.micbmp">Microsoft Bitmap</A>, <A HREF="gffse:/format.os2bmp">OS/2 Bitmap</A>, <A HREF="gffse:/format.micriff">Microsoft RIFF</A></td>
+</tr>
+</table>
+<p>
+<b>Usage</b><br>
+The <B style="color:black;background-color:#ffff66">Windows</B> environment uses icons as graphical links to objects
+(data files and executable programs). Cursors are used by the pointing device
+as graphical indications of the current state of the <B style="color:black;background-color:#ffff66">Windows</B> environment.
+<p>
+<b>Comments</b><br>
+<B style="color:black;background-color:#ffff66">Windows</B> icons and cursors are almost identical in format.
+In fact, cursor and <B style="color:black;background-color:#A0FFFF">icon</B> bitmaps can be used interchangeably.
+<p>
+<hr>
+<p>
+<A NAME="MICCUR-DMYID.1"><!-- Overview anchor --></A>
+<p>
+In an object-oriented graphical user interface--as Microsoft
+<B style="color:black;background-color:#ffff66">Windows</B> is sometimes described--icons are small bitmaps containing an
+iconographic picture that makes an object accessible to the user. The
+"objects" made available by icons are: collections of data (files);
+executable programs (more files); and peripheral devices (the toys
+you have hooked up to your computer). Rather than typing a file or
+program name on a command line, applications are started and files
+manipulated by clicking on an <B style="color:black;background-color:#A0FFFF">icon</B>, or "dragging and dropping" an
+<B style="color:black;background-color:#A0FFFF">icon</B> on to a window or another <B style="color:black;background-color:#A0FFFF">icon</B>. 
+<p>
+A cursor, or pointer as they are also called, is a special type of
+<B style="color:black;background-color:#A0FFFF">icon</B> that is used to track the location of the pointing device on the
+user interface. The cursor changes its appearance to indicate the
+state of the system and the user interface. Such states include that
+a search is in progress, an application is starting or stopping, or a
+user-initiated action is being processed. The appearance of the
+cursor also indicates what action may be performed based on the
+current position of the pointer on the display. For example, actions
+such as resizing a window or entering text may only be performed at
+specific locations within a window.
+<p>
+Although a wide variety of physical pointing devices (mouse,
+joystick, trackball, touch pad, light pen, pointing stick, etc.) are
+typically supported by a user interface, the input received by
+<B style="color:black;background-color:#ffff66">Windows</B> is identical from all types of pointing devices.
+<p>
+<P>
+<B>Contents:</B><br>
+<A HREF="#MICCUR-DMYID.2">File Organization</A><br>
+<A HREF="#MICCUR-DMYID.3">File Details</A><br>
+<A HREF="#MICCUR-DMYID.4">For Further Information</A><br>
+<p>
+Icons and cursors themselves are types of resources available in the
+<B style="color:black;background-color:#ffff66">Windows</B> environment. When an application makes a request for <B style="color:black;background-color:#ffff66">Windows</B>
+to display an <B style="color:black;background-color:#A0FFFF">icon</B> or cursor, <B style="color:black;background-color:#ffff66">Windows</B> must locate the appropriate
+file and choose which of the bitmaps stored in the file best fits the
+resolution and color depth of the display. 
+<p>
+The <B style="color:black;background-color:#A0FFFF">icon</B> displayed is chosen based on its size and number of colors.
+For example, a two-color <B style="color:black;background-color:#A0FFFF">icon</B> 16x16 pixels in size might look good on
+a monochrome display at 640x480 resolution, but the same <B style="color:black;background-color:#A0FFFF">icon</B> bitmap
+will probably look terrible on a 256-color, 1024x768 display. For
+such a high-resolution display, the choice of the 16-color, 64x64
+pixel version of the <B style="color:black;background-color:#A0FFFF">icon</B> would look much better.  However, if the
+<B style="color:black;background-color:#A0FFFF">icon</B> file only contains one bitmap, then that is the <B style="color:black;background-color:#A0FFFF">icon</B> bitmap that
+will be displayed.
+<p>
+<B style="color:black;background-color:#A0FFFF">Icon</B> and cursor bitmaps are typically very small. Under the 16-bit
+<B style="color:black;background-color:#ffff66">Windows</B> environment (Win16) icons are traditionally square (16x16,
+32x32, or 64x64 pixels in size) and have 16 or fewer colors. The
+32-bit <B style="color:black;background-color:#ffff66">Windows</B> (Win32) environment allows icons to be larger and have
+a greater pixel depth, such as 72x72 pixels with 256 colors. Win32 is
+also tolerant of rectangular <B style="color:black;background-color:#A0FFFF">icon</B> formats.
+<p>
+Cursors under <B style="color:black;background-color:#ffff66">Windows</B> 3.<i>x</i> are two-color (1-bit) bitmaps. Under
+<B style="color:black;background-color:#ffff66">Windows</B> 4.<i>x</i> cursors with 8, 16, or more colors are possible. Cursors
+are also not limited to a specific range of sizes as are icons,
+although most cursors are 32x32 pixels in size.
+<p>
+Both cursor and <B style="color:black;background-color:#A0FFFF">icon</B> data may be stored in separate file formats
+(<i>*.ico</i> and <i>*.cur</i>) on disk, or be stored as a <B style="color:black;background-color:#ffff66">Windows</B>
+resource file (<i>*.res</i>) and embedded directly into a <B style="color:black;background-color:#ffff66">Windows</B>
+executable file (<i>*.exe</i>), Dynamic Link Library (<i>*.dll</i>),
+Visual Basic control (<i>*.vbx</i>), or OLE
+control (<i>*.ocx</i>). (This article only discusses the ICO and CUR file
+formats and will leave the <B style="color:black;background-color:#ffff66">Windows</B> Resource format to a future
+article.)
+<p>
+Simple animations may be created by the sequential display of several
+<B style="color:black;background-color:#A0FFFF">icon</B> or cursor bitmaps in a continuous loop. These "flipbook"
+animations are not directly supported by the Win16 environment, but
+they do appear in some <B style="color:black;background-color:#ffff66">Windows</B> applications.
+<p>
+A true animated cursor format was defined by Microsoft in 1992 as part
+of the RIFF multimedia specification. Animated cursors are the
+<i>*.ani</i> files you might have noticed on your hard drive if you
+use <B style="color:black;background-color:#ffff66">Windows</B> 95 or <B style="color:black;background-color:#ffff66">Windows</B> NT. They are actually little more than a
+collection of ICO files stored in a single ANI file. For more
+information on the format of <B style="color:black;background-color:#ffff66">Windows</B> animated icons, see the
+<a href="gffse:/format.micriff">Microsoft RIFF</a> article.
+<p>
+If you are interested in the <B style="color:black;background-color:#A0FFFF">icon</B> and pointer file formats used by
+OS/2, then have a look at the <a href="gffse:/format.os2bmp">OS/2
+Bitmap</a> article. OS/2 uses its own flavor of the BMP format to
+store <B style="color:black;background-color:#A0FFFF">icon</B> and pointer data.
+<p>
+It should also be noted that there is no formal specification for
+either the <B style="color:black;background-color:#ffff66">Windows </B><B style="color:black;background-color:#A0FFFF">icon</B> or cursor file formats. In fact, there are no
+definitions for these file formats in the <B style="color:black;background-color:#ffff66">Windows</B> Software Development
+Kit (SDK) header files. What information that can be found must be
+scraped up from <B style="color:black;background-color:#ffff66">Windows</B> SDK manuals, Knowledge Base articles, and
+several sample applications. See the <a href="#MICCUR-DMYID.4">For
+Further Information</a> section in this article for more details on
+additional reference material for <B style="color:black;background-color:#ffff66">Windows</B> icons and cursors.
+<p>
+
+<h2><A NAME="MICCUR-DMYID.2">File Organization</A></h2>
+<p>
+The <B style="color:black;background-color:#ffff66">Windows </B><B style="color:black;background-color:#A0FFFF">icon</B> (ICO) and cursor (CUR) file formats are identical.
+Only the interpretation of the file data differs slightly. In fact,
+<B style="color:black;background-color:#A0FFFF">icon</B> and cursor files may be used interchangeably by applications that
+realize this fact.
+<p>
+Every ICO and CUR file contains a header, a directory of bitmap
+entries, and one or more bitmaps that describes the appearance of an
+<B style="color:black;background-color:#A0FFFF">icon</B> or cursor (shown below).
+<p>
+<table border=1>
+<tr valign=center>
+  <td align=center>Header</td>
+</tr>
+<tr valign=center>
+  <td align=center>Bitmap Directory</td>
+</tr>
+<tr valign=center>
+  <td align=center>Bitmap 1</td>
+</tr>
+<tr valign=center>
+  <td align=center>Bitmap 2</td>
+</tr>
+<tr valign=center>
+  <td align=center>...</td>
+</tr>
+<tr valign=center>
+  <td align=center>Bitmap N</td>
+</tr>
+</table>
+<p>
+The header stores information that is used to determine how many
+bitmaps are in the file. ICO and CUR files will contain one or more
+uncompressed bitmaps. Each entry in the bitmap directory will contain
+information that describes one of the bitmaps stored in the file. The
+directory will contain one entry per bitmap.
+<p>
+
+<h3><A NAME="MICCUR-DMYID.3">File Details</A></h3>
+<p>
+As <B style="color:black;background-color:#ffff66">Windows</B> ICO and CUR files are nearly identical, we will first look
+at the ICO format and then discuss the differences between the ICO
+and CUR formats.
+<p>
+The ICO header contains only a <B style="color:black;background-color:#ffff66">Windows</B> resource identification value
+and the count of the number of icons stored in the file. The header
+is immediately followed by a directory that contains information for
+all the icons stored in the ICO file. The directory is followed by
+the <B style="color:black;background-color:#A0FFFF">icon</B> bitmaps themselves.
+<p>
+The following structure illustrates the entire format of an ICO
+(or CUR) file:
+<pre>
+typedef struct _IconFile
+{
+    WORD      Reserved;      /* Reserved (always 0) */
+    WORD      ResourceType;  /* Resource ID (always 1) */
+    WORD      IconCount;     /* Number of <B style="color:black;background-color:#A0FFFF">icon</B> bitmaps in file */
+    ICONENTRY IconDir[];     /* Directory of <B style="color:black;background-color:#A0FFFF">icon</B> entries */
+    ICONDATA  IconData[];    /* Listing of ICO bitmaps */
+} ICONFILE;
+</pre>
+<p>
+Reserved is a two-byte value that is always zero in all ICO files.
+<p>
+ResourceType is the <B style="color:black;background-color:#ffff66">Windows</B> resource identifier type value. For icons
+this value is always 1.
+<p>
+IconCount is the number of icons stored in the ICO file. It is also
+the number of elements in the Icons array.
+<p>
+IconDir is an array of directory entries that describe the icons stored
+in the ICO file. There will be one entry per <B style="color:black;background-color:#A0FFFF">icon</B> stored. The number
+of entries will also equal the value of the IconCount field. Each
+ICONENTRY element has the following format:
+<pre>
+typedef struct _IconEntry
+{
+    BYTE  Width;        /* Width of <B style="color:black;background-color:#A0FFFF">icon</B> in pixels */
+    BYTE  Height;       /* Height of <B style="color:black;background-color:#A0FFFF">icon</B> in pixels */
+    BYTE  NumColors;    /* Maximum number of colors */
+    BYTE  Reserved;     /* Not used (always 0) */
+    WORD  NumPlanes;    /* Not used (always 0) */
+    WORD  BitsPerPixel; /* Not used (always 0) */
+    DWORD DataSize;     /* Length of <B style="color:black;background-color:#A0FFFF">icon</B> bitmap in bytes */
+    DWORD DataOffset;   /* Offset position of <B style="color:black;background-color:#A0FFFF">icon</B> bitmap in file */
+} ICONENTRY;
+</pre>
+<p>
+Width and Height are the size of the <B style="color:black;background-color:#A0FFFF">icon</B> in pixels. Only the values
+of 16, 32, and 64 for these fields are accepted by Win16.  Other
+values are also accepted by Win32. And as square icons are the most
+common, both of these fields will typically store the same value
+(although 32x16 icons were commonly used for the now antiquated
+300x200 CGA display mode).
+<p>
+NumColors is the maximum number of colors that may appear in the
+<B style="color:black;background-color:#A0FFFF">icon</B>. The values 2, 8, and 16 are the only values accepted by Win16.
+Other values are accepted by Win32. If the bitmap contains 256 or
+more colors the value of NumColors will be 0.
+<p>
+Reserved is not used and is always zero. This field was probably
+included as an element-alignment padding structure.
+<p>
+NumPlanes and BitsPerPixel are not used and are always 0. Earlier
+revisions of the ICO format may have used these fields, but in the
+current revision of ICO this information is now stored in the <B style="color:black;background-color:#A0FFFF">icon</B>
+data itself.
+<p>
+DataSize is the length of the <B style="color:black;background-color:#A0FFFF">icon</B> data in bytes for this entry.
+This value is the total size of both bitmaps used to render
+the <B style="color:black;background-color:#A0FFFF">icon</B> (explained below).
+<p>
+DataOffset is the location of the <B style="color:black;background-color:#A0FFFF">icon</B> data for this entry. The
+offset is measured in bytes from the start of the ICO file.
+<p>
+Following the <B style="color:black;background-color:#A0FFFF">icon</B> directory is the data for the <B style="color:black;background-color:#A0FFFF">icon</B>(s) themselves
+(the IconData array in the ICONFILE structure). Each <B style="color:black;background-color:#A0FFFF">icon</B> stored in
+an ICO file is actually an independent file format in itself, and
+contains a header, a color palette, <B style="color:black;background-color:#A0FFFF">icon</B> bitmap data, and a display
+bit mask. 
+<p>
+The start of each section of <B style="color:black;background-color:#A0FFFF">icon</B> data is specified by the IconOffset
+field in each <B style="color:black;background-color:#A0FFFF">icon</B> directory entry. Each section of <B style="color:black;background-color:#A0FFFF">icon</B> data has the
+following format:
+<pre>
+typedef struct _IconData
+{
+    WIN3XBITMAPHEADER   Header;         /* Bitmap header data */
+    WIN3XPALETTEELEMENT Palette[];      /* Color palette */
+    BYTE                XorMap[];       /* <B style="color:black;background-color:#A0FFFF">Icon</B> bitmap */
+    BYTE                AndMap[];       /* Display bit mask */
+} ICONDATA;
+</pre>
+<p>
+Header is a 40-byte <B style="color:black;background-color:#ffff66">Windows</B> 3.<i>x</i> BMP file header structure. Only the
+Size, Width, Height, Planes, BitsPerPixel, and SizeOfBitmap fields of
+this header are actually used. All other fields in this structure
+(Compression, SizeOfBitmap, HorzResolution, VertResolution,
+ColorsUsed, and ColorsImportant) are set to zero. Refer to the
+<a href="gffse:/format.micbmp">Microsoft <B style="color:black;background-color:#ffff66">Windows</B> Bitmap</a> article
+for more information on this header structure.
+<p>
+Palette is the color palette for the data in the XorMap array. The
+BitsPerPixel field of the Header is used to determine the number of
+elements in the Palette array (BitsPerPixel >= 1). For two-color
+icons there will be two palette entries; for 8- and 16-color icons
+there will be 16 entries. Each palette element is a four-byte RGB
+structure as described in the <a href="gffse:/format.micbmp">Microsoft
+<B style="color:black;background-color:#ffff66">Windows</B> Bitmap</a> article.
+<p>
+XorMap contains the <B style="color:black;background-color:#A0FFFF">icon's</B> foreground bitmap. The size of the pixels
+is indicated by the BitsPerPixel values in the header. Two-color
+(monochrome) bitmaps are stored as one bit per pixel; 8- and 16-color
+bitmap data is stored as 4 bits per pixel. Each pixel value is
+actually an index into the Palette color map.
+<p>
+AndMap is the <B style="color:black;background-color:#A0FFFF">icon's</B> background bit mask. This is a 1-bit-per-pixel
+mask that is the same size (in pixels) as the XorMap. This mask is
+used to map the visible area of the <B style="color:black;background-color:#A0FFFF">icon</B> on the screen before the
+<B style="color:black;background-color:#A0FFFF">icon</B> is actually displayed.
+<p>
+At first it may seem redundant to have Height and Width fields in
+both the <B style="color:black;background-color:#A0FFFF">icon</B> entry (ICONENTRY) and the <B style="color:black;background-color:#A0FFFF">icon</B> header
+(WIN3XBITMAPHEADER) structures. In fact, the Height and Width values
+stored in the <B style="color:black;background-color:#A0FFFF">icon</B> header are the combined size of the XorMap and
+AndMap bitmaps.  The Width values in the two structures will be the
+same, but the Height value in the <B style="color:black;background-color:#A0FFFF">icon</B> header will be double that of
+the Height value in the <B style="color:black;background-color:#A0FFFF">icon</B> directory entry. The <B style="color:black;background-color:#A0FFFF">icon</B> directory
+specifies the actual size of the <B style="color:black;background-color:#A0FFFF">icon</B> as it appears on the display,
+and the <B style="color:black;background-color:#A0FFFF">icon</B> header specifies the size of the data used to create the
+<B style="color:black;background-color:#A0FFFF">icon</B>.
+<p>
+Under the <B style="color:black;background-color:#ffff66">Windows</B> environment an <B style="color:black;background-color:#A0FFFF">icon</B> is displayed by first looking
+through an ICO file and determining which <B style="color:black;background-color:#A0FFFF">icon</B> bitmap best matches
+the number of colors and resolution of the display. The AND bit mask
+for the chosen <B style="color:black;background-color:#A0FFFF">icon</B> is bitwise ANDed with the pixels on the display
+where the <B style="color:black;background-color:#A0FFFF">icon</B> will appear. This removes the pixels from the display
+and leaves a virtual "hole" in the display where the non-<B style="color:black;background-color:#99ff99">transparent</B>
+parts of the <B style="color:black;background-color:#A0FFFF">icon</B> will appear.  Finally, the XOR map is bitwise XORed
+to the same pixels on the display. This operation adds the <B style="color:black;background-color:#A0FFFF">icon's</B>
+color to the display.
+<p>
+Let's look at this process in more detail.  Assume we have an ICO
+file that contains several "happy face" <B style="color:black;background-color:#A0FFFF">icon</B> bitmaps.  The ICO file
+actually stores four different bitmap variations of the same happy
+face:
+<p>
+<table border=0>
+<tr>
+  <th align=left>Width</th>
+  <th align=left>Height</th>
+  <th align=left>Number of Colors</th>
+  <th align=left>BitsPerPixel</th>
+</tr>
+<tr>
+  <td align=left>8</td>
+  <td align=left>8</td>
+  <td align=left>16</td>
+  <td align=left>4</td>
+</tr>
+<tr>
+  <td align=left>16</td>
+  <td align=left>16</td>
+  <td align=left>16</td>
+  <td align=left>4</td>
+</tr>
+<tr>
+  <td align=left>32</td>
+  <td align=left>32</td>
+  <td align=left>16</td>
+  <td align=left>4</td>
+</tr>
+<tr>
+  <td align=left>48</td>
+  <td align=left>48</td>
+  <td align=left>64</td>
+  <td align=left>8</td>
+</tr>
+</table>
+<p>
+When <i>happyfac.ico</i> is loaded, one of the <B style="color:black;background-color:#A0FFFF">icon</B> bitmaps must be chosen 
+that matches resolution of the display. The ICO reader searches each
+ICO directory entry for a "best fit" <B style="color:black;background-color:#A0FFFF">icon</B> bitmap.  For a 320x240x16
+display the 8x8, 16-color "happy face" <B style="color:black;background-color:#A0FFFF">icon</B> would probably look best.
+The reader then seeks to the offset of the selected <B style="color:black;background-color:#A0FFFF">icon</B> data.
+<p>
+Next, the AndMap bitmap must be loaded and ANDed to proper location
+on the display. The AndMap data follows the XorMap data, so the ICO
+reader must skip past the XorMap data to read the AndMap data. It is
+important to note that the scan lines of data in the AndMap and
+XorMap bitmaps are stored from the bottom up (the origin is in the
+lower left-hand corner). That is, the first scan line of the bitmap
+is actually the last scan line of the <B style="color:black;background-color:#A0FFFF">icon</B>.
+<p>
+In the 8x8x16 bitmap chosen, the AndMap contains only eight bytes
+of data (8 rows x 8 columns):
+<pre>
+FF 99 99 E7 66 BD C3 FF
+</pre>
+<p>
+In this form the data looks meaningless.  Looking at the AndMap as an
+array of bits we can see our happy face more clearly:
+<pre>
+1 1 1 1 1 1 1 1
+1 0 0 1 1 0 0 1
+1 0 0 1 1 0 0 1
+1 1 1 0 0 1 1 1
+0 1 1 0 0 1 1 0
+1 0 1 1 1 1 0 1
+1 1 0 0 0 0 1 1
+1 1 1 1 1 1 1 1
+</pre>
+<p>
+If we ANDed this bitmap to a location on the display that was all the
+same color we would see this result:
+<pre>
+C C C C C C C C     1 1 1 1 1 1 1 1     C C C C C C C C
+C C C C C C C C     1 0 0 1 1 0 0 1     C 0 0 C C 0 0 C
+C C C C C C C C     1 0 0 1 1 0 0 1     C 0 0 C C 0 0 C
+C C C C C C C C  +  1 1 1 0 0 1 1 1  =  C C C 0 0 C C C
+C C C C C C C C     0 1 1 0 0 1 1 0     0 C C 0 0 C C 0
+C C C C C C C C     1 0 1 1 1 1 0 1     C 0 C C C C 0 C
+C C C C C C C C     1 1 0 0 0 0 1 1     C C 0 0 0 0 C C
+C C C C C C C C     1 1 1 1 1 1 1 1     C C C C C C C C
+   Display              AndMap           ANDed Display
+</pre>
+<p>
+Each 1 bit in the AndMap preserved a display pixel (the <B style="color:black;background-color:#99ff99">transparent</B>
+portion of the <B style="color:black;background-color:#A0FFFF">icon</B>) and each 0 bit removed a display pixel (the
+opaque portion of the <B style="color:black;background-color:#A0FFFF">icon</B>).
+<p>
+Finally, we need to XOR the XorMap <B style="color:black;background-color:#A0FFFF">icon</B> pixel values into the hole we
+punched into the display. Remember that the actual pixel color values
+are stored in the color palette, and the bitmap data is only an index
+map that indicates where the pixel colors should be written.
+<p>
+In our happy face <B style="color:black;background-color:#A0FFFF">icon</B> each pixel in the bitmap is four bits in size
+and packed two pixels per byte. The XorMap bitmap data appears as
+such:
+<pre>
+00 00 00 00
+04 40 04 40
+04 40 04 40
+00 0F F0 00
+90 0F F0 09
+09 00 00 90
+00 99 99 00
+00 00 00 00
+</pre>
+<p>
+In case you can't see the happy face, the eyes are the value 0x4,
+the nose 0xF, and the smile 0x9. We now need to map the bitmap
+index values to color values in the palette. Let's assume that
+index 0x04 maps to color value 0x1, 0xF to 0xF, and 0x9 to 0x07.
+<p>
+With the color values determined, we XOR the pixel values to the same
+region we applied the AndMap mask. The 0 bits on the display (the
+black regions) indicate the non-<B style="color:black;background-color:#99ff99">transparent</B> pixels of the <B style="color:black;background-color:#A0FFFF">icon</B>. It is
+the pixel represented by the 0 bits that will have their color
+changed to that of the <B style="color:black;background-color:#A0FFFF">icon</B>. The <B style="color:black;background-color:#99ff99">transparent</B> pixels will retain the
+original color of the display:
+<pre>
+C C C C C C C C     0 0 0 0 0 0 0 0     C C C C C C C C
+C 0 0 C C 0 0 C     0 1 1 0 0 1 1 0     C 1 1 C C 1 1 C
+C 0 0 C C 0 0 C     0 1 1 0 0 1 1 0     C 1 1 C C 1 1 C
+C C C 0 0 C C C  +  0 0 0 F F 0 0 0  =  C C C F F C C C
+0 C C 0 0 C C 0     7 0 0 F F 0 0 7     7 C C F F C C 7
+C 0 C C C C 0 C     0 7 0 0 0 0 7 0     C 7 C C C C 7 C
+C C 0 0 0 0 C C     0 0 7 7 7 7 0 0     C C 7 7 7 7 C C
+C C C C C C C C     0 0 0 0 0 0 0 0     C C C C C C C C
+ ANDed Display       XORed values       <B style="color:black;background-color:#A0FFFF">Icon</B> on Display
+</pre>
+<p>
+A monochrome (1-bit) <B style="color:black;background-color:#A0FFFF">icon</B> or cursor will contain only four possible 
+pixels values: black, white, <B style="color:black;background-color:#99ff99">transparent</B>, and inverted. A <B style="color:black;background-color:#99ff99">transparent</B>
+or inverted pixel may be either black or white in color. The color
+palette will contain only two colors, which are black (entry zero)
+and white (entry one). The <B style="color:black;background-color:#99ff99">transparent</B> color is the original color of
+the display pixels. Inverted is the inverse color of the display
+pixels. Inverted pixels are responsible for the shadowy or shimmering
+effect you may have noticed when some cursors are moved across the
+display.
+<p>
+The possible combined bitmaps values are shown in Table Microsoft
+<B style="color:black;background-color:#ffff66">Windows</B> Cursor and <B style="color:black;background-color:#A0FFFF">Icon</B>-1.
+<p>
+<table border=0>
+<caption>Table Microsoft <B style="color:black;background-color:#ffff66">Windows</B> and Cursor <B style="color:black;background-color:#A0FFFF">Icon</B>-1. Monochrome <B style="color:black;background-color:#A0FFFF">icon</B> and cursor mask value combinations</caption>
+<tr valign=center>
+  <th></th>
+  <th align=left>AndMap Value</th>
+  <th align=left>XorMap Value</th>
+  <th align=left>Display Pixel Value</th>
+  <th align=left>Resulting Color</th>
+</tr>
+<tr>
+  <td align=left>Black</td>
+  <td align=left>0</td>
+  <td align=left>0</td>
+  <td align=left>0 or 1</td>
+  <td align=left>0</td>
+</tr>
+<tr>
+  <td align=left>White</td>
+  <td align=left>0</td>
+  <td align=left>1</td>
+  <td align=left>0 or 1</td>
+  <td align=left>1</td>
+</tr>
+<tr>
+  <td align=left><B style="color:black;background-color:#99ff99">Transparent</B></td>
+  <td align=left>1</td>
+  <td align=left>0</td>
+  <td align=left>0</td>
+  <td align=left>0</td>
+</tr>
+<tr>
+  <td></td>
+  <td align=left>1</td>
+  <td align=left>0</td>
+  <td align=left>1</td>
+  <td align=left>1</td>
+</tr>
+<tr>
+  <td align=left>Inverted</td>
+  <td align=left>1</td>
+  <td align=left>1</td>
+  <td align=left>0</td>
+  <td align=left>1</td>
+</tr>
+<tr>
+  <td></td>
+  <td align=left>1</td>
+  <td align=left>1</td>
+  <td align=left>1</td>
+  <td align=left>0</td>
+</tr>
+</table>
+<p>
+
+<h3><A NAME="MICCUR-DMYID.3.1">CUR File Format</A></h3>
+<p>
+Everything we have covered for the ICO format also applies to the
+CUR format with only a few exceptions:
+<p>
+The value of the ResourceType field in the header (ICONFILE) is 2,
+indicating the file contains cursor bitmap data.
+<p>
+The cursor directory entry redefines two unused fields to store hot
+spot information and modifies the possible values of two additional
+fields:
+<pre>
+typedef struct _IconEntry
+{
+    BYTE  Width;        /* Width of cursor in pixels */
+    BYTE  Height;       /* Height of cursor in pixels */
+    BYTE  NumColors;    /* Maximum number of colors */
+    BYTE  Reserved;     /* Not used (always 0) */
+    WORD  XHotSpot;     /* X location of cursor's hot spot */
+    WORD  YHotSpot;     /* Y location of cursor's hot spot */
+    DWORD DataSize;     /* Length of cursor bitmap in bytes */
+    DWORD DataOffset;   /* Offset position of cursor bitmap in file */
+} ICONENTRY;
+</pre>
+<p>
+Width and Height values in the <B style="color:black;background-color:#A0FFFF">icon</B> directory may be any size,
+although 32x32 pixels is the most common size for <B style="color:black;background-color:#ffff66">Windows</B> cursors.
+<p>
+NumColors is always 2 (1-bit, black and white) in a Win16 cursor
+file. Win32 cursors have the same color ranges as icons.
+<p>
+XHotSpot and YHotSpot store the coordinates of the cursor's hot spot.
+The X coordinate is relative to the cursor bitmap's left edge; the Y
+coordinate is relative to the cursor's top edge. Both coordinates are
+measured in pixels.
+<p>
+The hot spot is a single pixel in size. When the user clicks the
+pointing device, the coordinates of the hot spot are sent to <B style="color:black;background-color:#ffff66">Windows</B>.
+<B style="color:black;background-color:#ffff66">Windows</B> then performs an action based on where the hot spot is on the
+display and on the current state of <B style="color:black;background-color:#ffff66">Windows</B> itself. 
+<p>
+And finally, ICO and CUR files do not contain any type of
+identification signature or "magic number". A file reader may assume
+that all <i>.ico</i> files are <B style="color:black;background-color:#ffff66">Windows</B> icons and all <i>.cur</i>
+files are <B style="color:black;background-color:#ffff66">Windows</B>
+cursors, but it would haphazard to do so. If we assume instead that
+all ICO files will never contain more than 256 bitmaps (a very safe
+assumption) then an ICO file will begin with the byte sequence 00 00
+01 00 XX 00, where XX may be any byte value. Making the same
+assumption for cursors, all CUR files will begin with the byte
+sequence 00 00 02 00 XX 00.
+<p>
+
+<h2><A NAME="MICCUR-DMYID.4">For Further Information</A></h2>
+<p>
+The primary sources of ICO and CUR information are the Microsoft
+Win16 and Win32 Software Development Kits (SDK) and the Microsoft
+Developer Network Library (MSDN) CD-ROMs.
+<p>
+The Win16 and Win32 SDKs are distributed with the Microsoft Visual
+C++ compiler and the MSDN CD-ROMs. The following SDK references and
+Knowledge Base articles discuss the ICO, CUR, and DIB bitmap formats:
+<p>
+<blockquote>
+<p>
+<i>Microsoft <B style="color:black;background-color:#ffff66">Windows</B> Software Development Kit, Programmers'
+Reference, Volume 4: Resources</i>
+<p>
+Win32 SDK on-line help for the BITMAPINFO structure
+<p>  
+Q81498 SAMPLE: DIBs and Their Uses
+<p>
+Q94326 SAMPLE: 16 and 32 Bits-Per-Pel Bitmap Formats
+<p>
+Specs: Icons in Win32
+<p>
+Technical Articles: Win32 Binary Resource Formats
+<p>
+</blockquote>
+<p>
+You can find the SDK documents on the MSDN Library CD-ROMs. The MSDN
+Library is only available by subscription. However, Microsoft has made
+the October 1995 MSDN library available at:
+<p>
+<blockquote>
+<p>
+<i>ftp://ftp.microsoft.com/developr/MSDN/OctCD/</i>
+</blockquote>
+<p>
+One other MSDN Library file of interest is:
+<p>
+<blockquote>
+MSDN Frequently Asked Questions (FAQ)<br>
+PSS ID Number: Q116437, 02-16-1996<br>
+<i>ftp://ftp.microsoft.com/developr/MSDN/kb/Q116/4/37.txt</i><br>
+<i>http://www.microsoft.com/msdn/msdnfaq.htm</i>
+</blockquote>
+<p>
+The Win32 SDK also contains two sample applications that are necessary
+for understanding icons and cursors. They are <i>IconPro</i> and
+<i>imagedit</i> (an <B style="color:black;background-color:#A0FFFF">icon</B> and cursor bitmap editor). Both of these
+sample applications are distributed with complete source code, but no
+compiled binaries. The binaries are only distributed with the <B style="color:black;background-color:#ffff66">Windows</B>
+95 and <B style="color:black;background-color:#ffff66">Windows</B> NT Resource Kits.
+<p>
+You can contact the Microsoft MSDN group at:
+<p>
+<blockquote>
+Microsoft Developers Network<br>
+Voice: 206-936-2490<br>
+Email: <i>msdn@microsoft.com</i><br>
+WWW: <i>http://www.microsoft.com/msdn/</i>
+</blockquote>
+<p>
+And here's the addresses of Microsoft. Try getting what you need from
+their FTP and web sites first:
+<p>
+<blockquote>
+Microsoft Corporation<br>
+One Microsoft Way<br>
+Redmond, WA 98052-6399<br>
+FTP: <i>ftp://ftp.microsoft.com/</i><br>
+WWW: <i>http://www.microsoft.com/</i><br>
+CIS: WINSDK and MSWIN32 forums
+</blockquote>
+<p>
+
+<hr>
+<p>
+<A HREF="gffse:/format.micclip"><img src="../../images/txtpreva.gif"></A>
+<A HREF="gffse:/format.micmeta"><img src="../../images/txtnexta.gif"></A>
+<A HREF="../book.htm"><img src="../../images/txtupa.gif"></A>
+<a href="../bookidx.htm"><img src="../../images/txttoidx.gif"></a>
+<br>
+<a href="../booktoc.htm"><img src="../../images/btntoc.gif"></a>
+<a href="../book/glossary.htm"><img src="../../images/btnglos.gif"></a>
+<a href="../main.htm"><img src="../../images/btnmain.gif"></a>
+<a href="gffse:/page.formats"><img src="../../images/btnfmt.gif"></a>
+<a href="../software.htm"><img src="../../images/btnsoft.gif"></a>
+<a href="../internet.htm"><img src="../../images/btninet.gif"></a>
+<a href="../book.htm"><img src="../../images/btnbook.gif"></a>
+<P>
+Copyright &copy; 1996, 1994 O'Reilly &amp; Associates, Inc.  All Rights Reserved.
+<p>
+</body>
+</html>
diff --git a/converter/ppm/winicontoppm.c b/converter/ppm/winicontoppm.c
new file mode 100644
index 00000000..2d9de567
--- /dev/null
+++ b/converter/ppm/winicontoppm.c
@@ -0,0 +1,899 @@
+/* winicontoppm.c - read a MS Windows .ico file and write portable pixmap(s)
+**
+** Copyright (C) 2000,2003 by Lee Benfield - lee@benf.org
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** Changes:
+** 
+** 03/2003 - Added 24+32 bpp capability.
+*/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <math.h>
+#include <string.h>
+#include <assert.h>
+
+#include "ppm.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "mallocvar.h"
+#include "winico.h"
+
+#define MAJVERSION 0
+#define MINVERSION 4
+
+static int file_offset = 0;    /* not actually used, but useful for debug */
+static const char     er_read[] = "%s: read error";
+static const char *   infname;
+static FILE *   ifp;
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilespec;
+    const char * outputFilespec;
+    unsigned int allicons;
+    unsigned int bestqual;
+    unsigned int writeands;
+    unsigned int multippm;
+    unsigned int verbose;
+};
+
+
+
+
+static void
+parseCommandLine ( int argc, char ** argv,
+                   struct cmdlineInfo *cmdlineP ) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "allicons",     OPT_FLAG,   NULL,                  
+            &cmdlineP->allicons,       0 );
+    OPTENT3(0, "bestqual",     OPT_FLAG,   NULL,                  
+            &cmdlineP->bestqual,       0 );
+    OPTENT3(0, "writeands",    OPT_FLAG,   NULL,                  
+            &cmdlineP->writeands,      0 );
+    OPTENT3(0, "multippm",     OPT_FLAG,   NULL,                  
+            &cmdlineP->multippm,       0 );
+    OPTENT3(0, "verbose",      OPT_FLAG,   NULL,                  
+            &cmdlineP->verbose,        0 );
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+
+    if (argc-1 < 1) 
+        cmdlineP->inputFilespec = "-";
+    else
+        cmdlineP->inputFilespec = argv[1];
+
+    if (argc-1 < 2) {
+        cmdlineP->outputFilespec = "-";
+        
+        if (cmdlineP->writeands || cmdlineP->allicons)
+            pm_error("If you specify the -writeands or -allicons option, "
+                     "you must also specify an output file name argument.");
+    } else
+        cmdlineP->outputFilespec = argv[2];
+
+    if (argc-1 > 2)
+        pm_error("Too many arguments (%d).  Input filespec and "
+                 "output filespec are the only possible arguments.",
+                 argc-1);
+}
+
+
+
+
+static int 
+GetByte(void) {
+    int v;
+   
+    if ((v = getc(ifp)) == EOF)
+    {
+        pm_error(er_read, infname);
+    }
+   
+    return v;
+}
+   
+static short 
+GetShort(void) {
+    short v;
+   
+    if (pm_readlittleshort(ifp, &v) == -1)
+    {
+        pm_error(er_read, infname);
+    }
+   
+    return v;
+}
+   
+static long 
+GetLong(void) {
+    long v;
+   
+    if (pm_readlittlelong(ifp, &v) == -1)
+    {
+        pm_error(er_read, infname);
+    }
+   
+    return v;
+}
+   
+
+
+/*
+ * These have no purpose but to wrapper the Byte, Short & Long 
+ * functions.
+ */
+static u1 
+readU1 (void) {
+    file_offset++;
+    return GetByte();
+}
+
+static u1 * 
+readU1String (int length)
+{
+   
+    u1 * string;
+    
+    MALLOCARRAY(string, length + 1);
+    if (string == NULL)
+        pm_error("out of memory");
+
+    fread(string,sizeof(u1),length,ifp);
+    string[length] = 0;
+    file_offset += length * sizeof(u1);
+    return string;
+}
+
+static u2 
+readU2 (void) {
+    file_offset +=2;
+    return GetShort();
+}
+
+static u4 
+readU4 (void) {
+    file_offset += 4;
+    return GetLong();
+}
+
+static IC_Entry 
+readICEntry (void) 
+{
+    IC_Entry entry;
+
+    MALLOCVAR(entry);
+
+    if (entry == NULL)
+        pm_error("Unable to allcoate memory for IC entry");
+
+    entry->width         = readU1();
+    entry->height        = readU1();
+    entry->color_count   = readU1();
+    entry->reserved      = readU1();
+    entry->planes        = readU2();
+    entry->bitcount      = readU2();
+    entry->size_in_bytes = readU4();
+    entry->file_offset   = readU4();
+    entry->colors        = NULL;
+    entry->ih            = NULL;
+    entry->xorBitmap     = NULL;
+    entry->andBitmap     = NULL;
+    
+    return entry;
+}
+
+
+
+static IC_InfoHeader 
+readInfoHeader (IC_Entry entry) 
+{
+    IC_InfoHeader ih;
+
+    MALLOCVAR(ih);
+    
+    if (ih == NULL)
+        pm_error("Unable to allocate memory for info header");
+
+    ih->size            = readU4();
+    ih->width           = readU4();
+    ih->height          = readU4();
+    ih->planes          = readU2();
+    ih->bitcount        = readU2();
+    ih->compression     = readU4();
+    ih->imagesize       = readU4();
+    ih->x_pixels_per_m  = readU4();
+    ih->y_pixels_per_m  = readU4();
+    ih->colors_used     = readU4();
+    ih->colors_important = readU4();
+    
+    if (!entry->bitcount) entry->bitcount = ih->bitcount;
+    if (entry->color_count == 0 && 
+        entry->bitcount <= 8) entry->color_count = 256;
+    if (ih->compression) {
+        pm_error("Can't handle compressed icons");
+    }
+    return ih;
+}
+
+/*
+ * I don't know why this isn't the same as the spec, it just <b>isn't</b>
+ * The colors honestly seem to be stored BGR.  Bizarre.
+ * 
+ * I've checked this in the BMP code for bmptoppm and the gimp.  Guess the
+ * spec I have is just plain wrong.
+ */
+static IC_Color 
+readICColor (void) 
+{
+    IC_Color col;
+
+    MALLOCVAR(col);
+
+    if (col == NULL)
+        pm_error("Unable to allocate memory for color");
+
+    col->blue     = readU1();
+    col->green    = readU1();
+    col->red      = readU1();
+    col->reserved = readU1();
+    return col;
+}
+   
+
+
+/*
+ * Depending on if the image is stored as 1bpp, 4bpp or 8bpp, the 
+ * encoding mechanism is different.
+ * 
+ * 8bpp => 1 byte/palette index.
+ * 4bpp => High Nibble, Low Nibble
+ * 1bpp => 1 palette value per bit, high bit 1st.
+ */
+static u1 * 
+read1Bitmap (int width, int height) 
+{
+    int tmp;
+    int xBytes;
+    u1 * bitmap;
+    int wt = width;
+
+    MALLOCARRAY(bitmap, width * height);
+    if (bitmap == NULL)
+        pm_error("out of memory");
+
+    wt >>= 3;
+    if (wt & 3) {
+        wt = (wt & ~3) + 4;
+    }
+    xBytes = wt;
+    for (tmp = 0; tmp<height; tmp++ ) {
+        int x;
+        int rowByte = 0;
+        int xOrVal = 128;
+        u1 * row = readU1String(xBytes);
+        for (x = 0; x< width; x++) {
+            *(bitmap+((height-tmp-1)*width) + (x)) = 
+                (row[rowByte] & xOrVal) / xOrVal;
+            if (xOrVal == 1) {
+                xOrVal = 128;
+                rowByte++;
+            } else {
+                xOrVal >>= 1;
+            }
+        }
+        free(row);
+    }
+    return bitmap;
+}
+
+
+   
+static u1 * 
+read4Bitmap (int width, int height) 
+{
+    int tmp;
+    u1 * bitmap;
+
+    int wt = width;
+    int xBytes;
+
+    MALLOCARRAY(bitmap, width * height);
+    if (bitmap == NULL)
+        pm_error("out of memory");
+
+
+    wt >>= 1;
+    if (wt & 3) {
+        wt = (wt & ~3) + 4;
+    }
+    xBytes = wt;
+    for (tmp = 0; tmp<height ; tmp++ ) {
+        int rowByte = 0;
+        int bottom = 1;
+        int x;
+        u1 * row = readU1String(xBytes);
+        for (x = 0; x< width; x++) {
+            /*
+             * 2 nibbles, 2 values.
+             */
+            if (bottom) {
+                *(bitmap+((height-tmp-1)*width) + (x)) = 
+                    (row[rowByte] & 0xF0) >> 4;
+            } else {
+                *(bitmap+((height-tmp-1)*width) + (x)) = (row[rowByte] & 0xF);
+                rowByte++;
+            }
+            bottom = !bottom;
+        }
+    free(row);
+    }
+    return bitmap;
+}
+
+
+   
+static u1 * 
+read8Bitmap (int width, int height) 
+{
+    int tmp;
+    unsigned int xBytes;
+    unsigned int wt = width;
+    u1 * bitmap;
+   
+    MALLOCARRAY(bitmap, width * height);
+    if (bitmap == NULL)
+        pm_error("out of memory");
+
+    if (wt & 3) {
+        wt = (wt & ~3) + 4;
+    }
+    xBytes = wt;
+    for (tmp = 0; tmp<height ; tmp++ ) {
+        int rowByte = 0;
+        int x;
+        u1 * row = readU1String(xBytes);
+        for ( x = 0; x< width; x++) {
+            *(bitmap+((height-tmp-1)*width) + (x)) = row[rowByte];
+            rowByte++;
+        }
+        free(row);
+    }
+    return bitmap;
+}
+
+
+
+/*
+ * Read a true color bitmap. (24/32 bits)
+ * 
+ * The output routine deplanarizes it for us, we keep it flat here.
+ */
+static u1 *
+readXBitmap (int const width, 
+             int const height, 
+             int const bpp) {
+    int          const bytes  = bpp >> 3;
+    unsigned int const xBytes = width * bytes;
+
+    u1 * bitmap;
+        /* remember - bmp (dib) stored upside down, so reverse */
+
+    MALLOCARRAY(bitmap, bytes * width * height);
+    if (bitmap == NULL)
+        pm_error("out of memory allocating bitmap array");
+
+    {
+        unsigned int i;
+        u1 * bitcurptr;
+
+        for (i = 0, bitcurptr = &bitmap[bytes * width * (height-1)];
+             i < height; 
+             ++i, bitcurptr -= xBytes) {
+
+            u1 * const row = readU1String(xBytes);
+            memcpy(bitcurptr, row, xBytes);
+            free(row);
+        }
+    }
+    return bitmap;
+}
+
+
+
+static MS_Ico 
+readIconFile (bool const verbose) {
+    int iter,iter2;
+
+    MS_Ico MSIconData;
+
+    MALLOCVAR(MSIconData);
+   
+    /*
+     * reserved - should equal 0.
+     */
+    MSIconData->reserved = readU2();
+    /*
+     * Type - should equal 1
+     */
+    MSIconData->type     = readU2();
+    /*
+     * count - no of icons in file..
+     */
+    MSIconData->count    = readU2();
+    /*
+     * Allocate "count" array of entries.
+     */
+    if (verbose) 
+        pm_message("Icon file contains %d icons.", MSIconData->count);
+
+    MALLOCARRAY(MSIconData->entries, MSIconData->count);
+    if (MSIconData->entries == NULL)
+        pm_error("out of memory");
+    /*
+     * Read in each of the entries
+     */
+    for (iter = 0;iter < MSIconData->count ; iter++ ) {
+        MSIconData->entries[iter] = readICEntry();
+    }
+    /* After that, we have to read in the infoheader, color map (if
+     * any) and the actual bit/pix maps for the icons.  
+     */
+    if (verbose) 
+        fprintf (stderr,"#\tColors\tBPP\tWidth\tHeight\n");
+    for (iter = 0;iter < MSIconData->count ; iter++ ) {
+        int bpp;
+        MSIconData->entries[iter]->ih =
+            readInfoHeader (MSIconData->entries[iter]);
+       
+        /* What's the bits per pixel? */
+        bpp = MSIconData->entries[iter]->bitcount; 
+        /* Read the palette, if appropriate */
+        switch (bpp) {
+        case 24:
+        case 32:
+            /* 24/32 bpp icon has no palette */
+            break;
+        default:
+            MALLOCARRAY(MSIconData->entries[iter]->colors, 
+                        MSIconData->entries[iter]->color_count);
+            if (MSIconData->entries[iter]->colors == NULL)
+                pm_error("out of memory");
+
+            for (iter2 = 0;
+                 iter2 < MSIconData->entries[iter]->color_count ; 
+                 iter2++ ) {
+                MSIconData->entries[iter]->colors[iter2] = readICColor();
+            }
+            break;
+        }
+        if (verbose) {
+            char cols_text[10];
+            sprintf (cols_text, "%d", MSIconData->entries[iter]->color_count);
+            fprintf (stderr,
+                     "%d\t%s\t%d\t%d\t%d\n", iter,
+                     MSIconData->entries[iter]->color_count ? 
+                     cols_text : "TRUE",
+                     bpp, MSIconData->entries[iter]->width, 
+                     MSIconData->entries[iter]->height);
+        }
+        /* Pixels are stored bottom-up, left-to-right. Pixel lines are
+         * padded with zeros to end on a 32bit (4byte) boundary. Every
+         * line will have the same number of bytes. Color indices are
+         * zero based, meaning a pixel color of 0 represents the first
+         * color table entry, a pixel color of 255 (if there are that
+         * many) represents the 256th entry.  
+         * 
+         * 24+32 bit (16 is an abomination, which I'll avoid, and expect
+         * no-one to mind) are stored 1byte/plane with a spare (alpha?)
+         * byte for 32 bit.
+         */
+        {
+            /*
+             * Read XOR Bitmap
+             */
+            switch (bpp) {
+            case 1:
+                MSIconData->entries[iter]->xorBitmap = 
+                    read1Bitmap(MSIconData->entries[iter]->width,
+                                MSIconData->entries[iter]->height);
+                break;
+            case 4:
+                MSIconData->entries[iter]->xorBitmap = 
+                    read4Bitmap(MSIconData->entries[iter]->width,
+                                MSIconData->entries[iter]->height);
+                break;
+            case 8:
+                MSIconData->entries[iter]->xorBitmap = 
+                    read8Bitmap(MSIconData->entries[iter]->width,
+                                MSIconData->entries[iter]->height);
+                break;
+            case 24:
+            case 32:
+                MSIconData->entries[iter]->xorBitmap = 
+                    readXBitmap(MSIconData->entries[iter]->width,
+                                MSIconData->entries[iter]->height,bpp);
+                break;
+            default:
+                pm_error("Uncatered bit depth %d",bpp);
+            }
+            /*
+             * Read AND Bitmap
+             */
+            MSIconData->entries[iter]->andBitmap = 
+                read1Bitmap(MSIconData->entries[iter]->width,
+                            MSIconData->entries[iter]->height);
+        }
+      
+    }
+    return MSIconData;
+}
+
+
+
+static char * 
+trimOutputName(const char inputName[])
+{
+    /*
+     * Just trim off the final ".ppm", if there is one, else return as is.
+     * oh, for =~ ... :)
+     */
+    char * outFile = strdup(inputName);
+    if (STREQ(outFile + (strlen (outFile) - 4), ".ppm")) {
+        *(outFile + (strlen (outFile) - 4)) = 0;
+    }
+    return outFile;
+
+}
+
+
+
+static int 
+getBestQualityIcon(MS_Ico MSIconData)
+{
+    int x,best,best_size,best_bpp,bpp,size;
+    IC_Entry entry;
+
+    best_size = best_bpp = 0;
+    for (x = 0; x < MSIconData->count; x++) {
+        entry =  MSIconData->entries[x];
+        size = entry->width * entry->height;
+        bpp  = entry->bitcount ? entry->bitcount : entry->ih->bitcount;
+        if (size > best_size) {
+            best = x;
+            best_size = size;
+        } else if (size == best_size && bpp > best_bpp) {
+            best = x;
+            best_bpp = bpp;
+        }
+    }
+    return best;
+}
+
+static void
+writeXors(FILE *   const multiOutF,
+          char           outputFileBase[], 
+          IC_Entry const entry,
+          int      const entryNum,
+          bool     const multiple, 
+          bool     const xor) {
+/*----------------------------------------------------------------------------
+   Write an "xor" image (i.e. the main image) out.
+
+   'multiple' means this is one of multiple images that are being written.
+   'entryNum' is the sequence number within the winicon file of the image
+   we are writing.
+
+   'xor' means to include "xor" in the output file name.
+
+   if 'multiOutF' is non-null, it is the stream descriptor of an open
+   stream to which we are to write the image.  If it is null, 
+   we are to open a file using outputFileBase[] and 'entryNum' and 'xor'
+   to derive its name, and close it afterward.
+-----------------------------------------------------------------------------*/
+    FILE * outF;
+    pixel ** ppm_array;
+    int row;
+    int pel_size;
+    const char *outputFile;
+    int maxval;
+    int forcetext;
+
+    if (multiOutF) {
+        outF = multiOutF;
+        outputFile = strdup("");
+    } else {
+        if (outputFileBase) {
+            if (multiple) {
+                asprintfN(&outputFile, "%s%s_%d.ppm",
+                          outputFileBase,(xor ? "_xor" : ""), entryNum);
+            } else { 
+                asprintfN(&outputFile, "%s%s.ppm",
+                          outputFileBase,(xor ? "_xor" : ""));
+            }
+        } else
+            outputFile = strdup("-");
+        
+        outF = pm_openw(outputFile);
+    }
+    /* 
+     * allocate an array to save the bmp data into.
+     * note that entry->height will be 1/2 entry->ih->height,
+     * as the latter adds "and" and "xor" height.
+     */
+    ppm_array = ppm_allocarray(entry->width, entry->height);
+    for (row=0; row < entry->height; row++) {
+        u1 * xorRow;
+        int col;
+        switch (entry->bitcount) {
+        case 24:
+        case 32:
+            pel_size = entry->bitcount >> 3;
+            xorRow = entry->xorBitmap + row * entry->width * pel_size;
+            for (col=0; col < entry->width*pel_size;col+=pel_size) {
+                PPM_ASSIGN(ppm_array[row][col/pel_size],
+                           xorRow[col+2],xorRow[col+1],xorRow[col]);
+            }
+            break;
+        default:
+            xorRow = entry->xorBitmap + row * entry->width;
+            for (col=0; col < entry->width; col++) {
+                int colorIndex;
+                IC_Color color;
+                colorIndex  = xorRow[col];
+                color = entry->colors[colorIndex];
+                PPM_ASSIGN(ppm_array[row][col],
+                           color->red,color->green,color->blue);
+            }
+            break;
+        }
+    }    
+    
+    maxval = 255;
+    forcetext = 0;
+
+    ppm_writeppm(outF,ppm_array,entry->width, entry->height, 
+                 (pixval) maxval, forcetext);
+    ppm_freearray(ppm_array,entry->height);
+
+    strfree(outputFile);
+    
+    if (!multiOutF) 
+        pm_close(outF);
+}
+            
+
+
+static void
+writeAnds(FILE * const multiOutF, 
+          char outputFileBase[], IC_Entry const entry, int const entryNum, 
+          bool multiple) {
+/*----------------------------------------------------------------------------
+   Write the "and" image (i.e. the alpha mask) of the image 'IC_Entry' out.
+
+   'multiple' means this is one of multiple images that are being written.
+   'entryNum' is the sequence number within the winicon file of the image
+   we are writing.
+
+   if 'multiOutF' is non-null, it is the stream descriptor of an open
+   stream to which we are to write the image.  If it is null, 
+   we are to open a file using outputFileBase[] and 'entryNum' and 'xor'
+   to derive its name, and close it afterward.
+-----------------------------------------------------------------------------*/
+    FILE * outF;
+    bit ** pbm_array;
+    u1 * andRow;
+    int row;
+
+    if (multiOutF)
+        outF = multiOutF;
+    else {
+        const char *outputFile;
+
+        assert(outputFileBase);
+
+        if (multiple) 
+            asprintfN(&outputFile, "%s_and_%d.pbm", outputFileBase, entryNum);
+        else 
+            asprintfN(&outputFile, "%s_and.pbm", outputFileBase);
+        outF = pm_openw(outputFile);
+        strfree(outputFile);
+    }
+    pbm_array = pbm_allocarray(entry->width, entry->height);
+    for (row=0; row < entry->height; row++) {
+        int col;
+        andRow = entry->andBitmap + row * entry->width;
+        for (col=0; col < entry->width; col++) {
+            /* Note: black is transparent in a Netpbm alpha mask */
+            pbm_array[row][col] = andRow[col] ? PBM_BLACK: PBM_WHITE;
+        }
+    }
+
+    pbm_writepbm(outF, pbm_array, entry->width, entry->height, 0);
+
+    pbm_freearray(pbm_array, entry->height);
+    if (!multiOutF)
+        pm_close (outF);     
+}
+
+
+
+static void
+openMultiXor(char          outputFileBase[], 
+             bool    const writeands,
+             FILE ** const multiOutFP) {
+
+    const char *outputFile;
+
+    if (outputFileBase) {
+        asprintfN(&outputFile, "%s%s.ppm",
+                  outputFileBase, (writeands ? "_xor" : ""));
+    } else
+        outputFile = strdup("-");
+
+    /*
+     * Open the output file now, it'll stay open the whole time.
+     */
+    *multiOutFP = pm_openw(outputFile);
+
+    strfree(outputFile);
+}
+
+
+
+static void
+openMultiAnd(char outputFileBase[], FILE ** const multiAndOutFP) {
+
+    const char *outputFile;
+
+    assert(outputFileBase);
+
+    asprintfN(&outputFile, "%s_and.pbm", outputFileBase);
+    
+    *multiAndOutFP = pm_openw(outputFile);
+
+    strfree(outputFile);
+}
+
+static void free_iconentry(IC_Entry entry) {
+    int x;
+    if (entry->colors && entry->color_count) {
+        for (x=0;x<entry->color_count;x++) free(entry->colors[x]);
+        free(entry->colors);
+    }
+    if (entry->andBitmap) free(entry->andBitmap);
+    if (entry->xorBitmap) free(entry->xorBitmap);
+    if (entry->ih) free(entry->ih);
+    free(entry);
+}
+
+static void free_icondata(MS_Ico MSIconData)
+{
+    int x;
+    for (x=0;x<MSIconData->count;x++) {
+    free_iconentry(MSIconData->entries[x]);
+    }
+    free(MSIconData);
+}
+
+
+int 
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    int startEntry, endEntry;
+    MS_Ico MSIconData;
+    char * outputFileBase;
+    FILE * multiOutF;
+    FILE * multiAndOutF;
+   
+    ppm_init (&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    if (cmdline.bestqual && cmdline.allicons)
+        pm_message("-bestqual doesn't make sense with -allicons.  "
+                   "Ignoring -bestqual.");
+   
+    if (STREQ(cmdline.outputFilespec, "-"))
+        outputFileBase = NULL;
+    else
+        outputFileBase = trimOutputName(cmdline.outputFilespec);
+
+    ifp = pm_openr(cmdline.inputFilespec);
+
+    infname = cmdline.inputFilespec;
+
+    MSIconData = readIconFile(cmdline.verbose);
+    /*
+     * Now we've read the icon file in (Hopefully! :)
+     * Go through each of the entries, and write out files of the
+     * form
+     * 
+     * fname_0_xor.ppm
+     * fname_0_and.ppm
+     * 
+     * (or to stdout, depending on args parsing above).
+     */
+    /*
+     * If allicons is set, we want everything, if not, just go through once.
+     */
+    startEntry = 0;
+    if (cmdline.allicons) {
+        endEntry = MSIconData->count;
+    } else {
+        endEntry = 1;
+    }
+    /*
+     * If bestqual is set, find the icon with highest size & bpp.
+     */
+    if (cmdline.bestqual) {
+        startEntry = getBestQualityIcon(MSIconData);
+        endEntry = startEntry+1;
+    }
+   
+    if (cmdline.multippm) 
+        openMultiXor(outputFileBase, cmdline.writeands, &multiOutF);
+    else
+        multiOutF = NULL;
+
+    if (cmdline.writeands && cmdline.multippm) 
+        openMultiAnd(outputFileBase, &multiAndOutF);
+    else
+        multiAndOutF = NULL;
+
+    {
+        int entryNum;
+
+        for (entryNum = startEntry ; entryNum < endEntry ; entryNum++ ) {
+            IC_Entry const entry = MSIconData->entries[entryNum];
+
+            writeXors(multiOutF, outputFileBase, entry, entryNum, 
+                      cmdline.allicons, cmdline.writeands);
+            if (cmdline.writeands)
+                writeAnds(multiAndOutF, outputFileBase, 
+                          entry, entryNum, cmdline.allicons);
+        }
+    }
+    if (multiOutF)
+        pm_close (multiOutF);    
+    if (multiAndOutF)
+        pm_close(multiAndOutF);
+    
+    /* free up the image data here. */
+    free_icondata(MSIconData);
+    return 0;
+}
diff --git a/converter/ppm/xim.h b/converter/ppm/xim.h
new file mode 100644
index 00000000..ffe60fb6
--- /dev/null
+++ b/converter/ppm/xim.h
@@ -0,0 +1,125 @@
+#ifndef XIM_H_INCLUDED
+#define XIM_H_INCLUDED
+
+/* xim.h - header file for Xim files
+**
+** Taken from the X.V11R4 version of XimHeader.h:
+**
+** Author: Philip R. Thompson
+** Address:  phils@athena.mit.edu, 9-526 
+** Note:  size of header should be 1024 (1K) bytes.
+** $Header: /mit/phils/X/RCS/XimHeader.h,v 1.7 89/11/09 17:26:54 phils Exp Locker: phils $
+** $Date: 89/11/09 17:26:54 $
+** $Source: /mit/phils/X/RCS/XimHeader.h,v $
+*/
+
+#define IMAGE_VERSION    3
+#ifndef _BYTE
+typedef unsigned char  byte;
+#define _BYTE  1
+#endif
+
+/* External ascii file format. */
+typedef struct ImageHeader {
+    char file_version[8];   /* header version */
+    char header_size[8];    /* Size of file header in bytes  */
+    char image_width[8];    /* Width of the raster image */
+    char image_height[8];   /* Height of the raster imgage */
+    char num_colors[8];     /* Actual number of entries in c_map */
+    char num_channels[3];   /* 0 or 1 = pixmap, 3 = RG&B buffers */
+    char bytes_per_line[5]; /* bytes per scanline */
+    char num_pictures[4];   /* Number of pictures in file */
+    char bits_per_channel[4]; /* usually 1 or 8 */
+    char alpha_channel[4];  /* Alpha channel flag */
+    char runlength[4];      /* Runlength encoded flag */
+    char author[48];        /* Name of who made it */
+    char date[32];          /* Date and time image was made */
+    char program[16];       /* Program that created this file */
+    char comment[96];       /* other viewing info. for this image */
+    unsigned char c_map[256][3]; /* RGB values of the pixmap indices */
+} ImageHeader, XimAsciiHeader;
+
+
+/* Internal binary format. */
+typedef struct Color {
+    byte pixel, red, grn, blu;
+} Color;
+
+typedef struct XimImage {
+    int width;             /* width of the image in pixels */
+    int height;            /* height of the image in pixels */
+    unsigned datasize;     /* size of one channel of data */
+    short nchannels;     /* number data channels in image */
+    short bits_channel;  /* usually 1 or 8 */
+    short bytes_per_line; /* bytes to hold one scanline */
+    byte* data;            /* pixmap or red channel data */
+    byte* grn_data;        /* green channel data */
+    byte* blu_data;        /* blue  channel data */
+    byte* other;           /* other (alpha) data */
+    unsigned alpha_flag :1; /* alpha channel flag */
+    unsigned packed_flag:1; /* data packed in one chunk of memory */
+    unsigned runlen_flag:1; /* runlength encoded data flag */
+    unsigned : 0;           /* future flags, word alignment */
+    short tpics, npics;    /* number of images, total & left in file */
+    short ncolors;         /*   "    "  colors in the color table */
+    Color* colors;         /* colortable, one byte per r/g/b & pixel */
+    char* author;         /* author credit, copyright, etc */
+    char* date;           /* date image was made, grabbed, etc. */
+    char* program;        /* program used to make this */
+    short ncomments;       /* number of comments strings */
+    char** comments;      /* pointers to null terminated strings */
+    char* offset;         /* original offset in machine memory */
+    float chroma_red[2];   /* x, y image chromacity coords */
+    float chroma_grn[2];
+    float chroma_blu[2];
+    float chroma_wht[2];
+    float gamma;           /* image storage gamma */
+} XimImage;
+
+/* Future external ascii variable length header - under review. */
+#if (IMAGE_VERSION == 4)
+typedef struct XimAsciiHeader {
+    char file_version[4];   /* header version */
+    char header_size[8];    /* Size of file header (fixed part only) */
+    char image_height[8];   /* Height of the raster imgage in pixels */
+    char image_width[8];    /* Width of the raster image in pixels */
+    char bytes_line[8];     /* Actual # of bytes separating scanlines */
+    char bits_channel[4];   /* Bits per channel (usually 1 or 8) */
+    char num_channels[4];   /* 1 = pixmap, 3 = RG&B buffers */
+    char alpha_channel[2];  /* Alpha channel flag */
+    char num_colors[4];     /* Number of entries in c_map (if any) */
+    char num_pictures[4];   /* Number of images in file */
+    char runlength_flag[2]; /* Runlength encoded flag */
+    char future_flags[8];
+    char author[48];        /* Name of who made it, from passwd entry */
+    char date[32];          /* Unix format date */
+    char program[32];       /* Program that created this */
+    char gamma[12];         /* image storage gamma */
+    char chroma_red[24];    /* image red primary chromaticity coords. */
+    char chroma_grn[24];    /*   "   green "          "         "     */
+    char chroma_blu[24];    /*   "   blue  "          "         "     */
+    char chroma_wht[24];    /*   "   white point      "         "     */
+    char comment_length[8]  /* Total length of comments */
+    /* char* comment;           Null separated comments  */
+    /* unsigned char c_map[];   RGB Colortable, (ncolors * 3 bytes) */
+}  XimAsciiHeader;
+#endif /*IMAGE_VERSION 4*/
+
+#ifndef rnd
+#define rnd(x)  ((int)((float)(x) + 0.5)) /* round a float to an int */
+#endif
+
+/* Note:
+* - All data is in char's in order to maintain easily portability
+*   across machines, and some human readibility.
+* - Images may be stored as pixmaps (8 bits/pixel) or as seperate
+*   red, green, blue channel data (24+ bits/pixel).
+* - An alpha channel is optional and is found after every num_channels
+*   of data.
+* - Pixmaps or RGB (and alpha) channel data are stored respectively
+*   after the header.
+* - If num_channels = 1, a pixmap is assumed and the colormap in the
+*   header is used.
+* - Data size = image_width * image_height.
+*/
+#endif
diff --git a/converter/ppm/ximtoppm.c b/converter/ppm/ximtoppm.c
new file mode 100644
index 00000000..96798707
--- /dev/null
+++ b/converter/ppm/ximtoppm.c
@@ -0,0 +1,438 @@
+/* ximtoppm.c - read an Xim file and produce a portable pixmap
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <string.h>
+#include "ppm.h"
+#include "xim.h"
+#include "shhopt.h"
+#include "nstring.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filename;
+    const char *alpha_filename;
+    bool alpha_stdout;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that many of the strings that this function returns in the
+   *cmdlineP structure are actually in the supplied argv array.  And
+   sometimes, one of these strings is actually just a suffix of an entry
+   in argv!
+-----------------------------------------------------------------------------*/
+    optEntry option_def[100];
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int alphaoutSpec;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "alphaout",   OPT_STRING, 
+            &cmdlineP->alpha_filename, &alphaoutSpec, 0);
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and all of *cmdlineP. */
+
+    if (!alphaoutSpec)
+        cmdlineP->alpha_filename = NULL;
+
+    if (argc - 1 == 0)
+        cmdlineP->input_filename = "-";  /* he wants stdin */
+    else if (argc - 1 == 1)
+        cmdlineP->input_filename = strdup(argv[1]);
+    else 
+        pm_error("Too many arguments.  The only argument accepted "
+                 "is the input file specification");
+
+    if (cmdlineP->alpha_filename && 
+        STREQ(cmdlineP->alpha_filename, "-"))
+        cmdlineP->alpha_stdout = TRUE;
+    else 
+        cmdlineP->alpha_stdout = FALSE;
+}
+
+
+
+/* The subroutines are excerpted and slightly modified from the
+   X.V11R4 version of xim_io.c. 
+*/
+
+static int
+ReadXimHeader(FILE *     const in_fp,
+              XimImage * const header) {
+
+    int  i;
+    char *cp;
+    XimAsciiHeader  a_head;
+
+    cp = (char *) header;
+    for (i = 0; i < sizeof(XimImage); ++i )
+    *cp++ = 0;
+    /* Read header and verify image file formats */
+    if (fread((char *)&a_head, sizeof(ImageHeader), 1, in_fp) != 1) {
+        pm_message("ReadXimHeader: unable to read file header" );
+        return(0);
+    }
+    if (atoi(a_head.header_size) != sizeof(ImageHeader)) {
+        pm_message("ReadXimHeader: header size mismatch" );
+        return(0);
+    }
+    if (atoi(a_head.file_version) != IMAGE_VERSION) {
+        pm_message("ReadXimHeader: incorrect Image_file version" );
+        return(0);
+    }
+    header->width = atoi(a_head.image_width);
+    header->height = atoi(a_head.image_height);
+    header->ncolors = atoi(a_head.num_colors);
+    header->nchannels = atoi(a_head.num_channels);
+    header->bytes_per_line = atoi(a_head.bytes_per_line);
+/*    header->npics = atoi(a_head.num_pictures);
+*/
+    header->bits_channel = atoi(a_head.bits_per_channel);
+    header->alpha_flag = atoi(a_head.alpha_channel);
+    if (strlen(a_head.author)) {
+        if (!(header->author = calloc((unsigned int)strlen(a_head.author)+1,
+                1))) {
+            pm_message("ReadXimHeader: can't calloc author string" );
+            return(0);
+        }
+    header->width = atoi(a_head.image_width);
+        strncpy(header->author, a_head.author, strlen(a_head.author));
+    }
+    if (strlen(a_head.date)) {
+        if (!(header->date =calloc((unsigned int)strlen(a_head.date)+1,1))){
+            pm_message("ReadXimHeader: can't calloc date string" );
+            return(0);
+        }
+    header->width = atoi(a_head.image_width);
+        strncpy(header->date, a_head.date, strlen(a_head.date));
+    }
+    if (strlen(a_head.program)) {
+        if (!(header->program = calloc(
+                    (unsigned int)strlen(a_head.program) + 1, 1))) {
+            pm_message("ReadXimHeader: can't calloc program string" );
+            return(0);
+        }
+    header->width = atoi(a_head.image_width);
+        strncpy(header->program, a_head.program,strlen(a_head.program));
+    }
+    /* Do double checking for bakwards compatibility */
+    if (header->npics == 0)
+        header->npics = 1;
+    if (header->bits_channel == 0)
+        header->bits_channel = 8;
+    else if (header->bits_channel == 24) {
+        header->nchannels = 3;
+        header->bits_channel = 8;
+    }
+    if ((int)header->bytes_per_line == 0)
+        header->bytes_per_line = 
+            (header->bits_channel == 1 && header->nchannels == 1) ?
+                (header->width + 7) / 8 :
+                header->width;
+    header->datasize =(unsigned int)header->bytes_per_line * header->height;
+    if (header->nchannels == 3 && header->bits_channel == 8)
+        header->ncolors = 0;
+    else if (header->nchannels == 1 && header->bits_channel == 8) {
+        header->colors = (Color *)calloc((unsigned int)header->ncolors,
+                sizeof(Color));
+        if (header->colors == NULL) {
+            pm_message("ReadXimHeader: can't calloc colors" );
+            return(0);
+        }
+        for (i=0; i < header->ncolors; i++) {
+            header->colors[i].red = a_head.c_map[i][0];
+            header->colors[i].grn = a_head.c_map[i][1];
+            header->colors[i].blu = a_head.c_map[i][2];
+        }
+    }
+    return(1);
+}
+
+
+
+static int
+ReadImageChannel(FILE *         const infp,
+                 byte *         const buf,
+                 unsigned int * const bufsize,
+                 int            const encoded) {
+
+    int  i, runlen, nbytes;
+    unsigned int  j;
+    byte *line;
+    long  marker;
+
+    if (!encoded)
+        j = fread((char *)buf, 1, (int)*bufsize, infp);
+    else {
+        if ((line=(byte *)malloc((unsigned int)BUFSIZ)) == NULL) {
+            pm_message("ReadImageChannel: can't malloc() fread string" );
+            return(0);
+        }
+        /* Unrunlength encode data */
+        marker = ftell(infp);
+        j = 0;
+        while (((nbytes=fread((char *)line, 1, BUFSIZ, infp)) > 0) &&
+            (j < *bufsize)) {
+            for (i=0; (i < nbytes) && (j < *bufsize); i++) {
+                runlen = (int)line[i]+1;
+                i++;
+                while (runlen--)
+                    buf[j++] = line[i];
+            }
+            marker += i;
+        }
+        /* return to the begining of the next image's bufffer */
+        if (fseek(infp, marker, 0) == -1) {
+            pm_message("ReadImageChannel: can't fseek to location in image buffer" );
+            return(0);
+        }
+        free((char *)line);
+    }
+    if (j != *bufsize) {
+        pm_message("unable to complete channel: %u / %u (%d%%)",
+            j, *bufsize, (int)(j*100.0 / *bufsize) );
+        *bufsize = j;
+    }
+    return(1);
+}
+
+
+
+static int
+ReadXimImage(FILE *     const in_fp,
+             XimImage * const xim) {
+
+    if (xim->data) {
+        free((char *)xim->data);
+        xim->data = (byte *)0;
+    }
+    if (xim->grn_data) {
+        free((char *)xim->grn_data);
+        xim->grn_data = (byte *)0;
+    }
+    if (xim->blu_data) {
+        free((char *)xim->blu_data);
+        xim->blu_data = (byte *)0;
+    }
+    if (xim->other) {
+        free((char *)xim->other);
+        xim->other = (byte *)0;
+    }
+    xim->npics = 0;
+    if (!(xim->data = (byte *)calloc(xim->datasize, 1))) {
+        pm_message("ReadXimImage: can't malloc pixmap data" );
+        return(0);
+    }
+    if (!ReadImageChannel(in_fp, xim->data, &xim->datasize, 0)) {
+        pm_message("ReadXimImage: end of the images" );
+        return(0);
+    }
+    if (xim->nchannels == 3) {
+        xim->grn_data = (byte *)malloc(xim->datasize);
+        xim->blu_data = (byte *)malloc(xim->datasize);
+        if (xim->grn_data == NULL || xim->blu_data == NULL) {
+            pm_message("ReadXimImage: can't malloc rgb channel data" );
+            free((char *)xim->data);
+            if (xim->grn_data)  free((char *)xim->grn_data);
+            if (xim->blu_data)  free((char *)xim->blu_data);
+            xim->data = xim->grn_data = xim->blu_data = (byte*)0;
+            return(0);
+        }
+        if (!ReadImageChannel(in_fp, xim->grn_data, &xim->datasize, 0))
+            return(0);
+        if (!ReadImageChannel(in_fp, xim->blu_data, &xim->datasize, 0))
+            return(0);
+    }
+    if (xim->nchannels > 3) {
+        /* In theory, this can be any fourth channel, but the only one we
+           know about is an Alpha channel, so we'll call it that, even
+           though we process it generically.
+           */
+        if ((xim->other = (byte *)malloc(xim->datasize)) == NULL) {
+            pm_message("ReadXimImage: can't malloc alpha data" );
+            return(0);
+        }
+        if (!ReadImageChannel(in_fp, xim->other, &xim->datasize, 0))
+            return(0);
+    }
+    xim->npics = 1;
+    return(1);
+}
+
+
+
+/***********************************************************************
+*  File:   xlib.c
+*  Author: Philip Thompson
+*  $Date: 89/11/01 10:14:23 $
+*  $Revision: 1.14 $
+*  Purpose: General xim libray of utililities
+*  Copyright (c) 1988  Philip R. Thompson
+*                Computer Resource Laboratory (CRL)
+*                Dept. of Architecture and Planning
+*                M.I.T., Rm 9-526
+*                Cambridge, MA  02139
+*   This  software and its documentation may be used, copied, modified,
+*   and distributed for any purpose without fee, provided:
+*       --  The above copyright notice appears in all copies.
+*       --  This disclaimer appears in all source code copies.
+*       --  The names of M.I.T. and the CRL are not used in advertising
+*           or publicity pertaining to distribution of the software
+*           without prior specific written permission from me or CRL.
+*   I provide this software freely as a public service.  It is NOT a
+*   commercial product, and therefore is not subject to an an implied
+*   warranty of merchantability or fitness for a particular purpose.  I
+*   provide it as is, without warranty.
+*   This software is furnished  only on the basis that any party who
+*   receives it indemnifies and holds harmless the parties who furnish
+*   it against any claims, demands, or liabilities connected with using
+*   it, furnishing it to others, or providing it to a third party.
+*
+*   Philip R. Thompson (phils@athena.mit.edu)
+***********************************************************************/
+
+static int
+ReadXim(in_fp, xim)
+    FILE *in_fp;
+    XimImage *xim;
+{
+    if (!ReadXimHeader(in_fp, xim)) {
+        pm_message("can't read xim header" );
+    return(0);
+    }
+    if (!ReadXimImage(in_fp, xim)) {
+        pm_message("can't read xim data" );
+    return(0);
+    }
+    return(1);
+}
+
+
+
+int
+main(int argc,
+     char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE *ifP, *imageout_file, *alpha_file;
+    XimImage xim;
+    pixel *pixelrow, colormap[256];
+    gray *alpharow;
+        /* The alpha channel of the row we're currently converting, in
+           pgm fmt
+        */
+    int rows, cols, row, mapped;
+    pixval maxval;
+    bool success;
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    ifP = pm_openr(cmdline.input_filename);
+    
+    if (cmdline.alpha_stdout)
+        alpha_file = stdout;
+    else if (cmdline.alpha_filename == NULL) 
+        alpha_file = NULL;
+    else
+        alpha_file = pm_openw(cmdline.alpha_filename);
+    
+    if (cmdline.alpha_stdout) 
+        imageout_file = NULL;
+    else
+        imageout_file = stdout;
+    
+    success = ReadXim(ifP, &xim);
+    if (!success)
+        pm_error("can't read Xim file");
+
+    rows = xim.height;
+    cols = xim.width;
+
+    if (xim.nchannels == 1 && xim.bits_channel == 8) {
+        int i;
+
+        mapped = true;
+        maxval = 255;
+        for (i = 0; i < xim.ncolors; ++i) {
+            PPM_ASSIGN(
+                colormap[i], xim.colors[i].red,
+                xim.colors[i].grn, xim.colors[i].blu );
+            /* Should be colormap[xim.colors[i].pixel], but Xim is broken. */
+        }
+    } else if (xim.nchannels == 3 || xim.nchannels == 4) {
+        mapped = false;
+        maxval = pm_bitstomaxval(xim.bits_channel);
+    } else
+        pm_error(
+            "unknown Xim file type, nchannels == %d, bits_channel == %d",
+            xim.nchannels, xim.bits_channel);
+
+    if (imageout_file) 
+        ppm_writeppminit(imageout_file, cols, rows, maxval, 0);
+    if (alpha_file)
+        pgm_writepgminit(alpha_file, cols, rows, maxval, 0);
+
+    pixelrow = ppm_allocrow(cols);
+    alpharow = pgm_allocrow(cols);
+
+    for (row = 0; row < rows; ++row) {
+        if (mapped) {
+            byte * const ximrow = xim.data + row * xim.bytes_per_line;
+            unsigned int col;
+            
+            for (col = 0; col < cols; ++col) 
+                pixelrow[col] = colormap[ximrow[col]];
+            alpharow[col] = 0;
+        } else {
+            byte * const redrow = xim.data     + row * xim.bytes_per_line;
+            byte * const grnrow = xim.grn_data + row * xim.bytes_per_line;
+            byte * const blurow = xim.blu_data + row * xim.bytes_per_line;
+            byte * const othrow = xim.other    + row * xim.bytes_per_line;
+
+            unsigned int col;
+
+            for (col = 0; col < cols; ++col) {
+                PPM_ASSIGN(pixelrow[col],
+                           redrow[col], grnrow[col], blurow[col]);
+                if (xim.nchannels > 3)
+                    alpharow[col] = othrow[col];
+                else 
+                    alpharow[col] = 0;
+            }
+        }
+        if (imageout_file) 
+            ppm_writeppmrow(imageout_file, pixelrow, cols, maxval, 0);
+        if (alpha_file)
+            pgm_writepgmrow(alpha_file, alpharow, cols, maxval, 0);
+    }
+    pm_close(ifP);
+    if (imageout_file)
+        pm_close(imageout_file);
+    if (alpha_file)
+        pm_close(alpha_file);
+
+    return 0;
+}
diff --git a/converter/ppm/xpmtoppm.README b/converter/ppm/xpmtoppm.README
new file mode 100644
index 00000000..b5e254fa
--- /dev/null
+++ b/converter/ppm/xpmtoppm.README
@@ -0,0 +1,69 @@
+PPM Stuff 
+Convert portable pixmap to X11 Pixmap format (version 3) and vice versa
+-----------------------------------------------------------------------
+
+The program ppmtoxpm is a modified version of one sent out by Mark Snitily
+(mark@zok.uucp) and upgraded to XPM version 2 by Paul Breslaw
+(paul@mecazh.uu.ch).
+
+It converts Jeff Poskanzer's (jef@well.sf.ca.us) portable pixmap format
+(PBMPlus) into the new X11 pixmap format: XPM version 3 distributed by Arnaud
+Le Hors (lehors@mirsa.inria.fr).
+
+It is built using the PBMPlus libraries in the same way as any of the
+ppm utilities in the PBMPlus package.
+
+Paul Breslaw - Thu Nov 22 09:55:31 MET 1990
+--
+Paul Breslaw, Mecasoft SA,          |  telephone :  41 1 362 2040
+Guggachstrasse 10, CH-8057 Zurich,  |  e-mail    :  paul@mecazh.uu.ch
+Switzerland.                        |               mcsun!chx400!mecazh!paul
+--
+
+The program xpmtoppm is a modified version of the one distributed in the
+PBMPlus package by Jeff Poskanzer's which converts XPM version 1 or 3 files
+into a portable pixmap format.
+
+Upgraded to XPM version 3 by
+  Arnaud LE HORS     BULL Research France -- Koala Project
+  lehors@sa.inria.fr  Phone:(33) 93 65 77 71  Fax:(33) 93 65 77 66
+  Inria Sophia Antipolis B.P.109 06561 Valbonne Cedex France
+
+
+Installation
+-----------
+You should copy The ppmtoxpm.c, ppmtoxpm.1 and xpmtoppm.c, xpmtoppm.1 into
+your .../pbmplus/ppm directory.
+
+
+Patches
+-------
+* Rainer Sinkwitz sinkwitz@ifi.unizh.ch - 21 Nov 91:
+
+xpmtoppm.c:
+  - Bug fix, no advance of read ptr, would not read 
+    colors like "ac c black" because it would find 
+    the "c" of "ac" and then had problems with "c"
+    as color.
+    
+  - Now understands multword X11 color names
+  
+  - Now reads multiple color keys. Takes the color
+    of the hightest available key. Lines no longer need
+    to begin with key 'c'.
+    
+  - expanded line buffer to from 500 to 2048 for bigger files
+    
+ppmtoxpm.c:
+  - Bug fix, should should malloc space for rgbn[j].name+1 in line 441
+    caused segmentation faults
+    
+  - lowercase conversion of RGB names def'ed out,
+    considered harmful.
+    
+Suggestions:
+  ppmtoxpm should read /usr/lib/X11/rgb.txt by default.
+  With the Imakefiles of pbmplus it even gets compiled 
+  with -DRGB_DB=\"/usr/lib/X11/rgb.txt\"
+
+
diff --git a/converter/ppm/xpmtoppm.c b/converter/ppm/xpmtoppm.c
new file mode 100644
index 00000000..9dddfd83
--- /dev/null
+++ b/converter/ppm/xpmtoppm.c
@@ -0,0 +1,832 @@
+/* xpmtoppm.c - read an X11 pixmap file and produce a portable pixmap
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** Upgraded to handle XPM version 3 by
+**   Arnaud Le Hors (lehors@mirsa.inria.fr)
+**   Tue Apr 9 1991
+**
+** Rainer Sinkwitz sinkwitz@ifi.unizh.ch - 21 Nov 91:
+**  - Bug fix, no advance of read ptr, would not read 
+**    colors like "ac c black" because it would find 
+**    the "c" of "ac" and then had problems with "c"
+**    as color.
+**    
+**  - Now understands multiword X11 color names
+**  
+**  - Now reads multiple color keys. Takes the color
+**    of the hightest available key. Lines no longer need
+**    to begin with key 'c'.
+**    
+**  - expanded line buffer to from 500 to 2048 for bigger files
+*/
+
+#define _BSD_SOURCE   /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <string.h>
+
+#include "ppm.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "mallocvar.h"
+
+#define MAX_LINE (8 * 1024)
+  /* The maximum size XPM input line we can handle. */
+
+/* number of xpmColorKeys */
+#define NKEYS 5
+
+const char *xpmColorKeys[] =
+{
+ "s",					/* key #1: symbol */
+ "m",					/* key #2: mono visual */
+ "g4",					/* key #3: 4 grays visual */
+ "g",					/* key #4: gray visual */
+ "c",					/* key #5: color visual */
+};
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    char *input_filespec;  /* Filespecs of input files */
+    char *alpha_filename;
+    int alpha_stdout;
+    int verbose;
+};
+
+
+static int verbose;
+
+
+static void
+parse_command_line(int argc, char ** argv,
+                   struct cmdline_info *cmdline_p) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct *option_def = malloc(100*sizeof(optStruct));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct2 opt;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENTRY(0,   "alphaout",   OPT_STRING, &cmdline_p->alpha_filename, 0);
+    OPTENTRY(0,   "verbose",    OPT_FLAG,   &cmdline_p->verbose,        0);
+
+    cmdline_p->alpha_filename = NULL;
+    cmdline_p->verbose = FALSE;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = TRUE;  /* We may have parms that are negative numbers */
+
+    optParseOptions2(&argc, argv, opt, 0);
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (argc - 1 == 0)
+        cmdline_p->input_filespec = NULL;  /* he wants stdin */
+    else if (argc - 1 == 1)
+        cmdline_p->input_filespec = strdup(argv[1]);
+    else 
+        pm_error("Too many arguments.  The only argument accepted\n"
+                 "is the input file specification");
+
+    if (cmdline_p->alpha_filename && 
+        STREQ(cmdline_p->alpha_filename, "-"))
+        cmdline_p->alpha_stdout = TRUE;
+    else 
+        cmdline_p->alpha_stdout = FALSE;
+
+}
+
+
+static char lastInputLine[MAX_LINE+1];
+    /* contents of line most recently read from input */
+static bool backup;
+    /* TRUE means next read should be a reread of the most recently read
+       line, i.e. lastInputLine, instead of a read from the input file.
+    */
+
+
+static void
+getline(char * const line,
+        size_t const size,
+        FILE * const stream) {
+/*----------------------------------------------------------------------------
+   Read the next line from the input file 'stream', through the one-line
+   buffer lastInputLine[].
+
+   If 'backup' is true, the "next line" is the previously read line, i.e.
+   the one in that one-line buffer.  Otherwise, the "next line" is the next
+   line from the real file.  After reading the backed up line, we reset 
+   'backup' to false.
+
+   Return the line as a null terminated string in *line, which is an
+   array of 'size' bytes.
+
+   Exit program if the line doesn't fit in the buffer.
+-----------------------------------------------------------------------------*/
+    if (size > sizeof(lastInputLine))
+        pm_error("INTERNAL ERROR: getline() received 'size' parameter "
+                 "which is out of bounds");
+
+    if (backup) {
+        strncpy(line, lastInputLine, size); 
+        backup = FALSE;
+    } else {
+        if (fgets(line, size, stream) == NULL)
+            pm_error("EOF or read error on input file");
+        if (strlen(line) == size - 1)
+            pm_error("Input file has line that is too long (longer than "
+                     "%u bytes).", (unsigned)size - 1);
+        STRSCPY(lastInputLine, line);
+    }
+}
+
+
+
+static unsigned int
+getNumber(char * const p, unsigned int const size) {
+
+    unsigned int retval;
+    char * q;
+    
+    retval = 0;
+    for (q = p; q < p+size; ++q)
+        retval = (retval << 8) + *q;
+
+    return retval;
+}
+
+
+
+static void
+getword(char * const output, char ** const cursorP) {
+
+    char *t1;
+    char *t2;
+
+    for (t1=*cursorP; ISSPACE(*t1); t1++); /* skip white space */
+    for (t2 = t1; !ISSPACE(*t2) && *t2 != '"' && *t2 != '\0'; t2++);
+        /* Move to next white space, ", or eol */
+    if (t2 > t1)
+        strncpy(output, t1, t2 - t1);
+    output[t2 - t1] = '\0';
+    *cursorP = t2;
+}    
+
+
+
+static void
+addToColorMap(unsigned int const seqNum,
+              unsigned int const colorNumber, 
+              pixel * const colors, int * const ptab, 
+              char colorspec[], int const isTransparent,
+              int * const transparentP) {
+/*----------------------------------------------------------------------------
+   Add the color named by colorspec[] to the colormap contained in
+   'colors' and 'ptab', as the color associated with XPM color number
+   'colorNumber', which is the seqNum'th color in the XPM color map.
+
+   Iff 'transparent', set *transparentP to the colormap index that 
+   corresponds to this color.
+-----------------------------------------------------------------------------*/
+    if (ptab == NULL) {
+        /* Index into table. */
+        colors[colorNumber] = ppm_parsecolor(colorspec,
+                                             (pixval) PPM_MAXMAXVAL);
+        if (isTransparent) 
+            *transparentP = colorNumber;
+    } else {
+        /* Set up linear search table. */
+        colors[seqNum] = ppm_parsecolor(colorspec,
+                                        (pixval) PPM_MAXMAXVAL);
+        ptab[seqNum] = colorNumber;
+        if (isTransparent)
+            *transparentP = seqNum;
+    }
+}
+
+
+
+static void
+interpretXpm3ColorTableLine(char line[], int const seqNum, 
+                            int const chars_per_pixel,
+                            pixel * const colors, int * const ptab,
+                            int * const transparentP) {
+/*----------------------------------------------------------------------------
+   Interpret one line of the color table in the XPM header.  'line' is
+   the line from the XPM file.  It is the seqNum'th color table entry in
+   the file.  The file uses 'chars_per_pixel' characters per pixel.
+
+   Add the information from this color table entry to the color table
+   'colors' and, if it isn't NULL, the corresponding lookup shadow table
+   'ptab' (see readXpm3ColorTable for a description of these data 
+   structures).
+
+   The line may include values for multiple kinds of color (grayscale,
+   color, etc.).  We take the highest of these (e.g. color over grayscale).
+
+   If a color table entry indicates transparency, set *transparentP
+   to the colormap index that corresponds to the indicated color.
+-----------------------------------------------------------------------------*/
+    /* Note: this code seems to allow for multi-word color specifications,
+       but I'm not aware that such are legal.  Ultimately, ppm_parsecolor()
+       interprets the name, and I believe it only takes single word 
+       color specifications.  -Bryan 2001.05.06.
+    */
+    char str2[MAX_LINE+1];    
+    char *t1;
+    char *t2;
+    int endOfEntry;   /* boolean */
+    
+    unsigned int curkey, key, highkey;	/* current color key */
+    unsigned int lastwaskey;	
+        /* The last token we processes was a key, and we have processed
+           at least one token.
+        */
+    char curbuf[BUFSIZ];		/* current buffer */
+    int isTransparent;
+    
+    int colorNumber;
+        /* A color number that appears in the raster */
+    /* read the chars */
+    t1 = strchr(line, '"');
+    if (t1 == NULL)
+        pm_error("A line that is supposed to be an entry in the color "
+                 "table does not start with a quote.  The line is '%s'.  "
+                 "It is the %dth entry in the color table.", 
+                 line, seqNum);
+    else
+        t1++;  /* Points now to first color number character */
+
+    colorNumber = getNumber(t1, chars_per_pixel);
+    t1 += chars_per_pixel;
+
+    /*
+     * read color keys and values 
+     */
+    curkey = 0; 
+    highkey = 1;
+    lastwaskey = FALSE;
+    t2 = t1;
+    endOfEntry = FALSE;
+    while ( !endOfEntry ) {
+        int isKey;   /* boolean */
+        getword(str2, &t2);
+        if (strlen(str2) == 0)
+            endOfEntry = TRUE;
+        else {
+            /* See if the word we got is a valid key (and get its key
+               number if so)
+            */
+            for (key = 1; 
+                 key <= NKEYS && !STREQ(xpmColorKeys[key - 1], str2); 
+                 key++);
+            isKey = (key <= NKEYS);
+
+            if (lastwaskey || !isKey) {
+                /* This word is a color specification (or "none" for
+                   transparent).
+                */
+                if (!curkey) 
+                    pm_error("Missing color key token in color table line "
+                             "'%s' before '%s'.", line, str2);
+                if (!lastwaskey) 
+                    strcat(curbuf, " ");		/* append space */
+                if ( (strncmp(str2, "None", 4) == 0) 
+                     || (strncmp(str2, "none", 4) == 0) ) {
+                    /* This entry identifies the transparent color number */
+                    strcat(curbuf, "#000000");  /* Make it black */
+                    isTransparent = TRUE;
+                } else 
+                    strcat(curbuf, str2);		/* append buf */
+                lastwaskey = 0;
+            } else { 
+                /* This word is a key.  So we've seen the last of the 
+                   info for the previous key, and we must either put it
+                   in the color map or ignore it if we already have a higher
+                   color form in the colormap for this colormap entry.
+                */
+                if (curkey > highkey) {	/* flush string */
+                    addToColorMap(seqNum, colorNumber, colors, ptab, curbuf,
+                                  isTransparent, transparentP);
+                    highkey = curkey;
+                }
+                curkey = key;			/* set new key  */
+                curbuf[0] = '\0';		/* reset curbuf */
+                isTransparent = FALSE;
+                lastwaskey = 1;
+            }
+            if (*t2 == '"') break;
+        }
+    }
+    /* Put the info for the last key in the line into the colormap (or
+       ignore it if there's already a higher color form for this colormap
+       entry in it)
+    */
+    if (curkey > highkey) {
+        addToColorMap(seqNum, colorNumber, colors, ptab, curbuf,
+                      isTransparent, transparentP);
+        highkey = curkey;
+    }
+    if (highkey == 1) 
+        pm_error("C error scanning color table");
+}
+
+
+
+static void
+readXpm3Header(FILE * const stream, int * const widthP, int * const heightP, 
+               int * const chars_per_pixelP, int * const ncolorsP,
+               pixel ** const colorsP, int ** const ptabP,
+               int * const transparentP) {
+/*----------------------------------------------------------------------------
+  Read the header of the XPM file on stream 'stream'.  Assume the
+  getline() stream is presently positioned to the beginning of the
+  file and it is a Version 3 XPM file.  Leave the stream positioned
+  after the header.
+
+  We have two ways to return the colormap, depending on the number of
+  characters per pixel in the XPM:  
+  
+  If it is 1 or 2 characters per pixel, we return the colormap as a
+  Netpbm 'pixel' array *colorsP (in newly malloc'ed storage), such
+  that if a color in the raster is identified by index N, then
+  (*colorsP)[N] is that color.  So this array is either 256 or 64K
+  pixels.  In this case, we return *ptabP = NULL.
+
+  If it is more than 2 characters per pixel, we return the colormap as
+  both a Netpbm 'pixel' array *colorsP and a lookup table *ptabP (both
+  in newly malloc'ed storage).
+
+  If a color in the raster is identified by index N, then for some I,
+  (*ptabP)[I] is N and (*colorsP)[I] is the color in question.  So 
+  you iterate through *ptabP looking for N and then look at the 
+  corresponding entry in *colorsP to get the color.
+
+  Return as *transColorNumberP the value of the XPM color number that
+  represents a transparent pixel, or -1 if no color number does.
+-----------------------------------------------------------------------------*/
+    char line[MAX_LINE+1];
+    const char * xpm3_signature = "/* XPM */";
+    
+    *widthP = *heightP = *ncolorsP = *chars_per_pixelP = -1;
+
+    /* Read the XPM signature comment */
+    getline(line, sizeof(line), stream);
+    if (strncmp(line, xpm3_signature, strlen(xpm3_signature)) != 0) 
+        pm_error("Apparent XPM 3 file does not start with '/* XPM */'.  "
+                 "First line is '%s'", xpm3_signature);
+
+    /* Read the assignment line */
+    getline(line, sizeof(line), stream);
+    if (strncmp(line, "static char", 11) != 0)
+        pm_error("Cannot find data structure declaration.  Expected a "
+                 "line starting with 'static char', but found the line "
+                 "'%s'.", line);
+
+	/* Read the hints line */
+    getline(line, sizeof(line), stream);
+    /* skip the comment line if any */
+    if (!strncmp(line, "/*", 2)) {
+        while (!strstr(line, "*/"))
+            getline(line, sizeof(line), stream);
+        getline(line, sizeof(line), stream);
+    }
+    if (sscanf(line, "\"%d %d %d %d\",", widthP, heightP,
+               ncolorsP, chars_per_pixelP) != 4)
+        pm_error("error scanning hints line");
+
+    if (verbose == 1) 
+    {
+        pm_message("Width x Height:  %d x %d", *widthP, *heightP);
+        pm_message("no. of colors:  %d", *ncolorsP);
+        pm_message("chars per pixel:  %d", *chars_per_pixelP);
+    }
+
+    /* Allocate space for color table. */
+    if (*chars_per_pixelP <= 2) {
+        /* Set up direct index (see above) */
+        *colorsP = ppm_allocrow(*chars_per_pixelP == 1 ? 256 : 256*256);
+        *ptabP = NULL;
+    } else {
+        /* Set up lookup table (see above) */
+        *colorsP = ppm_allocrow(*ncolorsP);
+        MALLOCARRAY(*ptabP, *ncolorsP);
+        if (*ptabP == NULL)
+            pm_error("Unable to allocate memory for %d colors", *ncolorsP);
+    }
+    
+    { 
+        /* Read the color table */
+        int seqNum;
+            /* Sequence number of entry within color table in XPM header */
+
+        *transparentP = -1;  /* initial value */
+
+        for (seqNum = 0; seqNum < *ncolorsP; seqNum++) {
+            getline(line, sizeof(line), stream);
+            /* skip the comment line if any */
+            if (!strncmp(line, "/*", 2))
+                getline(line, sizeof(line), stream);
+            
+            interpretXpm3ColorTableLine(line, seqNum, *chars_per_pixelP, 
+                                        *colorsP, *ptabP, transparentP);
+        }
+    }
+}
+
+
+static void
+readXpm1Header(FILE * const stream, int * const widthP, int * const heightP, 
+               int * const chars_per_pixelP, int * const ncolorsP, 
+               pixel ** const colorsP, int ** const ptabP) {
+/*----------------------------------------------------------------------------
+  Read the header of the XPM file on stream 'stream'.  Assume the
+  getline() stream is presently positioned to the beginning of the
+  file and it is a Version 1 XPM file.  Leave the stream positioned
+  after the header.
+  
+  Return the information from the header the same as for readXpm3Header.
+-----------------------------------------------------------------------------*/
+    char line[MAX_LINE+1], str1[MAX_LINE+1], str2[MAX_LINE+1];
+    char *t1;
+    char *t2;
+    int format, v;
+    int i, j;
+    bool processedStaticChar;  
+        /* We have read up to and interpreted the "static char..." line */
+
+    *widthP = *heightP = *ncolorsP = *chars_per_pixelP = format = -1;
+
+    /* Read the initial defines. */
+    processedStaticChar = FALSE;
+    while (!processedStaticChar) {
+        getline(line, sizeof(line), stream);
+
+        if (sscanf(line, "#define %s %d", str1, &v) == 2) {
+            char *t1;
+            if ((t1 = strrchr(str1, '_')) == NULL)
+                t1 = str1;
+            else
+                ++t1;
+            if (STREQ(t1, "format"))
+                format = v;
+            else if (STREQ(t1, "width"))
+                *widthP = v;
+            else if (STREQ(t1, "height"))
+                *heightP = v;
+            else if (STREQ(t1, "ncolors"))
+                *ncolorsP = v;
+            else if (STREQ(t1, "pixel"))
+                *chars_per_pixelP = v;
+        } else if (!strncmp(line, "static char", 11)) {
+            if ((t1 = strrchr(line, '_')) == NULL)
+                t1 = line;
+            else
+                ++t1;
+            processedStaticChar = TRUE;
+        }
+    }
+    /* File is positioned to "static char" line, which is in line[] and
+       t1 points to position of last "_" in the line, or the beginning of
+       the line if there is no "_"
+    */
+    if (format == -1)
+        pm_error("missing or invalid format");
+    if (format != 1)
+        pm_error("can't handle XPM version %d", format);
+    if (*widthP == -1)
+        pm_error("missing or invalid width");
+    if (*heightP == -1)
+        pm_error("missing or invalid height");
+    if (*ncolorsP == -1)
+        pm_error("missing or invalid ncolors");
+    if (*chars_per_pixelP == -1)
+        pm_error("missing or invalid *chars_per_pixelP");
+    if (*chars_per_pixelP > 2)
+        pm_message("WARNING: *chars_per_pixelP > 2 uses a lot of memory");
+
+    /* If there's a monochrome color table, skip it. */
+    if (!strncmp(t1, "mono", 4)) {
+        for (;;) {
+            getline(line, sizeof(line), stream);
+            if (!strncmp(line, "static char", 11))
+                break;
+        }
+    }
+    /* Allocate space for color table. */
+    if (*chars_per_pixelP <= 2) {
+        /* Up to two chars per pixel, we can use an indexed table. */
+        v = 1;
+        for (i = 0; i < *chars_per_pixelP; ++i)
+            v *= 256;
+        *colorsP = ppm_allocrow(v);
+        *ptabP = NULL;
+    } else {
+        /* Over two chars per pixel, we fall back on linear search. */
+        *colorsP = ppm_allocrow(*ncolorsP);
+        MALLOCARRAY(*ptabP, *ncolorsP);
+        if (*ptabP == NULL)
+            pm_error("Unable to allocate memory for %d colors", *ncolorsP);
+    }
+
+    /* Read color table. */
+    for (i = 0; i < *ncolorsP; ++i) {
+        getline(line, sizeof(line), stream);
+
+        if ((t1 = strchr(line, '"')) == NULL)
+            pm_error("D error scanning color table");
+        if ((t2 = strchr(t1 + 1, '"')) == NULL)
+            pm_error("E error scanning color table");
+        if (t2 - t1 - 1 != *chars_per_pixelP)
+            pm_error("wrong number of chars per pixel in color table");
+        strncpy(str1, t1 + 1, t2 - t1 - 1);
+        str1[t2 - t1 - 1] = '\0';
+
+        if ((t1 = strchr(t2 + 1, '"')) == NULL)
+            pm_error("F error scanning color table");
+        if ((t2 = strchr(t1 + 1, '"')) == NULL)
+            pm_error("G error scanning color table");
+        strncpy(str2, t1 + 1, t2 - t1 - 1);
+        str2[t2 - t1 - 1] = '\0';
+
+        v = 0;
+        for (j = 0; j < *chars_per_pixelP; ++j)
+            v = (v << 8) + str1[j];
+        if (*chars_per_pixelP <= 2)
+            /* Index into table. */
+            (*colorsP)[v] = ppm_parsecolor(str2,
+                                           (pixval) PPM_MAXMAXVAL);
+        else {
+            /* Set up linear search table. */
+            (*colorsP)[i] = ppm_parsecolor(str2,
+                                           (pixval) PPM_MAXMAXVAL);
+            (*ptabP)[i] = v;
+        }
+    }
+    /* Position to first line of raster (which is the line after
+       "static char ...").
+    */
+    for (;;) {
+        getline(line, sizeof(line), stream);
+        if (strncmp(line, "static char", 11) == 0)
+            break;
+    }
+}
+
+
+
+static void
+interpretXpmLine(char   const line[],
+                 int    const chars_per_pixel,
+                 int    const ncolors,
+                 int *  const ptab, 
+                 int ** const cursorP,
+                 int *  const maxCursor) {
+/*----------------------------------------------------------------------------
+   Interpret one line of XPM input.  The line is in 'line', and its
+   format is 'chars_per_pixel' characters per pixel.  'ptab' is the
+   color table that applies to the line, which table has 'ncolors'
+   colors.
+
+   Put the colormap indexes for the pixels represented in 'line' at
+   *cursorP, lined up in the order they are in 'line', and return
+   *cursorP positioned just after the last one.
+
+   If the line doesn't start with a quote (e.g. it is empty), we issue
+   a warning and just treat the line as one that describes no pixels.
+
+   Stop processing the line either at the end of the line or when
+   the output would go beyond maxCursor, whichever comes first.
+-----------------------------------------------------------------------------*/
+    char * lineCursor;
+
+    lineCursor = strchr(line, '"');  /* position to 1st quote in line */
+    if (lineCursor == NULL) {
+        /* We've seen a purported XPM that had a blank line in it.  Just
+           ignoring it was the right thing to do.  05.05.27.
+        */
+        pm_message("WARNING:  No opening quotation mark in XPM input "
+                   "line which is supposed to be a line of raster data: "
+                   "'%s'.  Ignoring this line.", line);
+    } else {
+        ++lineCursor; /* Skip to first character after quote */
+
+        /* Handle pixels until a close quote, eol, or we've returned all
+           the pixels Caller wants.
+        */
+        while (*lineCursor && *lineCursor != '"' && *cursorP <= maxCursor) {
+            int colorNumber;
+            int i;
+            colorNumber = 0;  /* initial value */
+            for (i = 0; i < chars_per_pixel; ++i)
+                colorNumber = (colorNumber << 8) + *(lineCursor++);
+            if (ptab == NULL)
+                /* colormap is indexed directly by XPM color number */
+                *(*cursorP)++ = colorNumber;
+            else {
+                /* colormap shadows ptab[].  Find this color # in ptab[] */
+                int i;
+                for (i = 0; i < ncolors && ptab[i] != colorNumber; ++i);
+                if (i < ncolors)
+                    *(*cursorP)++ = i;
+                else
+                    pm_error("Color number %d is in raster, but not in "
+                             "colormap.  Line it's in: '%s'",
+                             colorNumber, line);
+            }
+        }
+    }
+}
+
+
+
+static void
+ReadXPMFile(FILE * const stream, int * const widthP, int * const heightP, 
+            pixel ** const colorsP, int ** const dataP, 
+            int * const transparentP) {
+/*----------------------------------------------------------------------------
+   Read the XPM file from stream 'stream'.
+
+   Return the dimensions of the image as *widthP and *heightP.
+   Return the color map as *colorsP, which is an array of *ncolorsP
+   colors.
+
+   Return the raster in newly malloced storage, an array of *widthP by
+   *heightP integers, each of which is an index into the colormap
+   *colorsP (and therefore less than *ncolorsP).  Return the address
+   of the array as *dataP.
+
+   In the colormap, put black for the transparent color, if the XPM 
+   image contains one.
+-----------------------------------------------------------------------------*/
+    char line[MAX_LINE+1], str1[MAX_LINE+1];
+    int totalpixels;
+    int *cursor;  /* cursor into *dataP */
+    int *maxcursor;  /* value of above cursor for last pixel in image */
+    int *ptab;   /* colormap - malloc'ed */
+    int rc;
+    int ncolors;
+    int chars_per_pixel;
+
+    backup = FALSE;
+
+    /* Read the header line */
+    getline(line, sizeof(line), stream);
+    backup = TRUE;  /* back up so next read reads this line again */
+    
+    rc = sscanf(line, "/* %s */", str1);
+    if (rc == 1 && strncmp(str1, "XPM", 3) == 0) {
+        /* It's an XPM version 3 file */
+        readXpm3Header(stream, widthP, heightP, &chars_per_pixel,
+                       &ncolors, colorsP, &ptab, transparentP);
+    } else {				/* try as an XPM version 1 file */
+        /* Assume it's an XPM version 1 file */
+        readXpm1Header(stream, widthP, heightP, &chars_per_pixel, 
+                       &ncolors, colorsP, &ptab);
+        *transparentP = -1;  /* No transparency in version 1 */
+    }
+    totalpixels = *widthP * *heightP;
+    MALLOCARRAY(*dataP, totalpixels);
+    if (*dataP == NULL)
+        pm_error("Could not get %d bytes of memory for image", totalpixels);
+    cursor = *dataP;
+    maxcursor = *dataP + totalpixels - 1;
+	getline(line, sizeof(line), stream); 
+        /* read next line (first line may not always start with comment) */
+    while (cursor <= maxcursor) {
+        if (strncmp(line, "/*", 2) == 0) {
+            /* It's a comment.  Ignore it. */
+        } else {
+            interpretXpmLine(line, chars_per_pixel, 
+                             ncolors, ptab, &cursor, maxcursor);
+        }
+        if (cursor <= maxcursor)
+            getline(line, sizeof(line), stream);
+    }
+    if (ptab) free(ptab);
+}
+ 
+
+
+static void
+writeOutput(FILE * const imageout_file,
+            FILE * const alpha_file,
+            int const cols, int const rows, 
+            pixel * const colors, int * const data,
+            int transparent) {
+/*----------------------------------------------------------------------------
+   Write the image in 'data' to open PPM file stream 'imageout_file',
+   and the alpha mask for it to open PBM file stream 'alpha_file',
+   except if either is NULL, skip it.
+
+   'data' is an array of cols * rows integers, each one being an index
+   into the colormap 'colors'.
+
+   Where the index 'transparent' occurs in 'data', the pixel is supposed
+   to be transparent.  If 'transparent' < 0, no pixels are transparent.
+-----------------------------------------------------------------------------*/
+    int row;
+    pixel *pixrow;
+    bit * alpharow;
+
+    if (imageout_file)
+        ppm_writeppminit(imageout_file, cols, rows, PPM_MAXMAXVAL, 0);
+    if (alpha_file)
+        pbm_writepbminit(alpha_file, cols, rows, 0);
+
+    pixrow = ppm_allocrow(cols);
+    alpharow = pbm_allocrow(cols);
+
+    for (row = 0; row < rows; ++row ) {
+        int col;
+        int * const datarow = data+(row*cols);
+
+        for (col = 0; col < cols; ++col) {
+            pixrow[col] = colors[datarow[col]];
+            if (datarow[col] == transparent)
+                alpharow[col] = PBM_BLACK;
+            else
+                alpharow[col] = PBM_WHITE;
+        }
+        if (imageout_file)
+            ppm_writeppmrow(imageout_file, 
+                            pixrow, cols, (pixval) PPM_MAXMAXVAL, 0);
+        if (alpha_file)
+            pbm_writepbmrow(alpha_file, alpharow, cols, 0);
+    }
+    ppm_freerow(pixrow);
+    pbm_freerow(alpharow);
+
+    if (imageout_file)
+        pm_close(imageout_file);
+    if (alpha_file)
+        pm_close(alpha_file);
+}    
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE *ifp;
+    FILE *alpha_file, *imageout_file;
+    pixel *colormap;
+    int cols, rows;
+    int transparent;  /* value of 'data' that means transparent */
+    int *data;
+        /* The image as an array of width * height integers, each one
+           being an index int colormap[].
+        */
+
+    struct cmdline_info cmdline;
+
+    ppm_init(&argc, argv);
+
+    parse_command_line(argc, argv, &cmdline);
+
+    verbose = cmdline.verbose;
+
+    if ( cmdline.input_filespec != NULL ) 
+        ifp = pm_openr( cmdline.input_filespec);
+    else
+        ifp = stdin;
+
+    if (cmdline.alpha_stdout)
+        alpha_file = stdout;
+    else if (cmdline.alpha_filename == NULL) 
+        alpha_file = NULL;
+    else {
+        alpha_file = pm_openw(cmdline.alpha_filename);
+    }
+
+    if (cmdline.alpha_stdout) 
+        imageout_file = NULL;
+    else
+        imageout_file = stdout;
+
+    ReadXPMFile(ifp, &cols, &rows, &colormap, &data, &transparent);
+    
+    pm_close(ifp);
+
+    writeOutput(imageout_file, alpha_file, cols, rows, colormap, data,
+                transparent);
+
+    free(colormap);
+    
+    return 0;
+}
diff --git a/converter/ppm/xvminitoppm.c b/converter/ppm/xvminitoppm.c
new file mode 100644
index 00000000..dfc76fcf
--- /dev/null
+++ b/converter/ppm/xvminitoppm.c
@@ -0,0 +1,207 @@
+/*=============================================================================
+                                    xvminitoppm
+===============================================================================
+   Convert XV mini thumbnail image to PPM.
+
+   This replaces the program of the same name by Ingo Wilken
+   (Ingo.Wilken@informatik.uni-oldenburg.de), 1993.
+
+   Written by Bryan Henderson in April 2006 and contributed to the public
+   domain.
+=============================================================================*/
+
+#include <assert.h>
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "nstring.h"
+#include "ppm.h"
+
+#define BUFSIZE 256
+
+
+typedef struct xvPalette {
+    unsigned int red[256];
+    unsigned int grn[256];
+    unsigned int blu[256];
+} xvPalette;
+
+
+struct cmdlineInfo {
+    const char * inputFileName;
+};
+
+
+
+static void
+parseCommandLine(int const argc,
+                 char *    argv[],
+                 struct cmdlineInfo * const cmdlineP) {
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else {
+        cmdlineP->inputFileName = argv[1];
+
+        if (argc-1 > 1)
+            pm_error("Too many arguments: %u.  Only argument is optional "
+                     "input file name.", argc-1);
+    }
+}
+
+
+
+static void
+getline(FILE * const ifP,
+        char * const buf,
+        size_t const size) {
+
+    char * rc;
+
+    rc = fgets(buf, size, ifP);
+    if (rc == NULL) {
+        if (ferror(ifP))
+            pm_perror("read error");
+        else
+            pm_error("unexpected EOF");
+    }
+}
+
+
+
+static void
+makeXvPalette(xvPalette * const xvPaletteP) {
+
+    unsigned int paletteIndex;
+    unsigned int r;
+
+    paletteIndex = 0;
+
+    for (r = 0; r < 8; ++r) {
+        unsigned int g;
+        for (g = 0; g < 8; ++g) {
+            unsigned int b;
+            for (b = 0; b < 4; ++b) {
+                xvPaletteP->red[paletteIndex] = (r*255)/7;
+                xvPaletteP->grn[paletteIndex] = (g*255)/7;
+                xvPaletteP->blu[paletteIndex] = (b*255)/3;
+                ++paletteIndex;
+            }
+        }
+    }
+
+}
+
+
+
+static void
+readXvHeader(FILE *         const ifP,
+             unsigned int * const colsP,
+             unsigned int * const rowsP,
+             unsigned int * const maxvalP) {
+           
+    char buf[256];
+    unsigned int cols, rows, maxval;
+    int rc;
+    bool endOfComments;
+    
+    getline(ifP, buf, sizeof(buf));
+
+    if (!STRNEQ(buf, "P7 332", 6))
+        pm_error("Input is not a XV thumbnail picture.  It does not "
+                 "begin with the characters 'P7 332'.");
+
+    endOfComments = FALSE;
+    while (!endOfComments) {
+        getline(ifP, buf, sizeof(buf));
+        if (STRNEQ(buf, "#END_OF_COMMENTS", 16))
+            endOfComments = TRUE;
+        else if (STRNEQ(buf, "#BUILTIN", 8))
+            pm_error("This program does not know how to "
+                     "convert builtin XV thumbnail pictures");
+    }
+    getline(ifP, buf, sizeof(buf));
+    rc = sscanf(buf, "%u %u %u", &cols, &rows, &maxval);
+    if (rc != 3)
+        pm_error("error parsing dimension info '%s'.  "
+                 "It does not consist of 3 decimal numbers.", buf);
+    if (maxval != 255)
+        pm_error("bogus XV thumbnail maxval %u.  Should be 255", maxval);
+}
+
+
+
+static void
+writePpm(FILE *             const ifP,
+         const xvPalette *  const xvPaletteP,
+         unsigned int       const cols,
+         unsigned int       const rows,
+         pixval             const maxval,
+         FILE *             const ofP) {
+/*----------------------------------------------------------------------------
+   Write out the PPM image, from the XV-mini input file ifP, which is
+   positioned to the raster.
+
+   The raster contains indices into the palette *xvPaletteP.
+-----------------------------------------------------------------------------*/
+    pixel * pixrow;
+    unsigned int row;
+
+    pixrow = ppm_allocrow(cols);
+
+    ppm_writeppminit(ofP, cols, rows, maxval, 0);
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            int byte;
+            byte = fgetc(ifP);
+            if (byte == EOF)
+                pm_error("unexpected EOF");
+            else {
+                unsigned int const paletteIndex = byte;
+                assert(byte > 0);
+                
+                PPM_ASSIGN(pixrow[col],
+                           xvPaletteP->red[paletteIndex],
+                           xvPaletteP->grn[paletteIndex],
+                           xvPaletteP->blu[paletteIndex]);
+            }
+        }
+        ppm_writeppmrow(ofP, pixrow, cols, maxval, 0);
+    }
+
+    ppm_freerow(pixrow);
+}
+
+
+
+int 
+main(int    argc,
+     char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    unsigned int cols, rows;
+    pixval maxval;
+    xvPalette xvPalette;
+ 
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    makeXvPalette(&xvPalette);
+
+    readXvHeader(ifP, &cols, &rows, &maxval);
+
+    writePpm(ifP, &xvPalette, cols, rows, maxval, stdout);
+
+    pm_close(ifP);
+
+    return 0;
+}
+
+
+
diff --git a/converter/ppm/yuvsplittoppm.c b/converter/ppm/yuvsplittoppm.c
new file mode 100644
index 00000000..0f5e19a3
--- /dev/null
+++ b/converter/ppm/yuvsplittoppm.c
@@ -0,0 +1,251 @@
+/* yuvsplittoppm.c - construct a portable pixmap from 3 raw files:
+** - basename.Y : The Luminance chunk at the size of the Image
+** - basename.U : The Chrominance chunk U at 1/4
+** - basename.V : The Chrominance chunk V at 1/4
+** The subsampled U and V values are made by arithmetic mean.
+**
+** If ccir601 is defined, the produced YUV triples have been scaled again
+** to fit into the smaller range of values for this standard.
+**
+** by Marcel Wijkstra <wijkstra@fwi.uva.nl>
+**
+** Based on ppmtoyuvsplit.c
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+#include "ppm.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+/* x must be signed for the following to work correctly */
+#define limit(x) (((x>0xffffff)?0xff0000:((x<=0xffff)?0:x&0xff0000))>>16)
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *filenameBase;
+    unsigned int width;
+    unsigned int height;
+    unsigned int ccir601;
+        /* Whether to create YUV in JFIF(JPEG) or CCIR.601(MPEG) scale */
+};
+
+
+static void
+parseCommandLine(int                 argc, 
+                 char **             argv,
+                 struct cmdlineInfo *cmdlineP ) {
+/*----------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "ccir601",     OPT_FLAG,   NULL,                  
+            &cmdlineP->ccir601,       0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+
+    if (argc-1 !=3)
+        pm_error("You must specify 3 arguments.  "
+                 "You specified %d", argc-1);
+    else {
+        int width, height;
+        cmdlineP->filenameBase = argv[1];
+        width = atoi(argv[2]);
+        if (width < 1)
+            pm_error("Width must be at least 1.  You specified %d", width);
+        height = atoi(argv[3]);
+        if (height < 1)
+            pm_error("Height must be at least 1.  You specified %d", height);
+        cmdlineP->width  = width;
+        cmdlineP->height = height;
+    }
+}
+
+
+
+static void
+computeTwoOutputRows(int             const cols,
+                     bool            const ccir601,
+                     unsigned char * const y1buf,
+                     unsigned char * const y2buf,
+                     unsigned char * const ubuf,
+                     unsigned char * const vbuf,
+                     pixel *         const pixelrow1,
+                     pixel *         const pixelrow2) {
+                     
+    int col;
+
+    for (col = 0; col < cols; col += 2) {
+        long int r0,g0,b0,r1,g1,b1,r2,g2,b2,r3,g3,b3;
+        long int  u,v,y0,y1,y2,y3,u0,u1,u2,u3,v0,v1,v2,v3;
+        
+        y0 = y1buf[col];
+        y1 = y1buf[col+1];
+        y2 = y2buf[col];
+        y3 = y2buf[col+1];
+
+        u = ubuf[col/2] - 128;
+        v = vbuf[col/2] - 128;
+        
+        if (ccir601) {
+            y0 = ((y0-16)*255)/219;
+            y1 = ((y1-16)*255)/219;
+            y2 = ((y2-16)*255)/219;
+            y3 = ((y3-16)*255)/219;
+
+            u  = (u*255)/224 ;
+            v  = (v*255)/224 ;
+        }
+        /* mean the chroma for subsampling */
+        
+        u0=u1=u2=u3=u;
+        v0=v1=v2=v3=v;
+
+
+        /* The inverse of the JFIF RGB to YUV Matrix for $00010000 = 1.0
+
+           [Y]   [65496        0   91880] [R]
+           [U] = [65533   -22580  -46799] [G]
+           [V]   [65537   116128      -8] [B]
+           
+        */
+
+        r0 = 65536 * y0               + 91880 * v0;
+        g0 = 65536 * y0 -  22580 * u0 - 46799 * v0;
+        b0 = 65536 * y0 + 116128 * u0             ;
+        
+        r1 = 65536 * y1               + 91880 * v1;
+        g1 = 65536 * y1 -  22580 * u1 - 46799 * v1;
+        b1 = 65536 * y1 + 116128 * u1             ;
+        
+        r2 = 65536 * y2               + 91880 * v2;
+        g2 = 65536 * y2 -  22580 * u2 - 46799 * v2;
+        b2 = 65536 * y2 + 116128 * u2             ;
+        
+        r3 = 65536 * y3               + 91880 * v3;
+        g3 = 65536 * y3 -  22580 * u3 - 46799 * v3;
+        b3 = 65536 * y3 + 116128 * u3             ;
+        
+        r0 = limit(r0);
+        r1 = limit(r1);
+        r2 = limit(r2);
+        r3 = limit(r3);
+        g0 = limit(g0);
+        g1 = limit(g1);
+        g2 = limit(g2);
+        g3 = limit(g3);
+        b0 = limit(b0);
+        b1 = limit(b1);
+        b2 = limit(b2);
+        b3 = limit(b3);
+
+        PPM_ASSIGN(pixelrow1[col],   (pixval)r0, (pixval)g0, (pixval)b0);
+        PPM_ASSIGN(pixelrow1[col+1], (pixval)r1, (pixval)g1, (pixval)b1);
+        PPM_ASSIGN(pixelrow2[col],   (pixval)r2, (pixval)g2, (pixval)b2);
+        PPM_ASSIGN(pixelrow2[col+1], (pixval)r3, (pixval)g3, (pixval)b3);
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE *vf,*uf,*yf;
+    int cols, rows;
+    pixel *pixelrow1,*pixelrow2;
+    int row;
+    unsigned char  *y1buf,*y2buf,*ubuf,*vbuf;
+    const char * ufname;
+    const char * vfname;
+    const char * yfname;
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+        
+    asprintfN(&ufname, "%s.U", cmdline.filenameBase);
+    asprintfN(&vfname, "%s.V", cmdline.filenameBase);
+    asprintfN(&yfname, "%s.Y", cmdline.filenameBase);
+
+    uf = pm_openr(ufname);
+    vf = pm_openr(vfname);
+    yf = pm_openr(yfname);
+
+    ppm_writeppminit(stdout, cmdline.width, cmdline.height, 255, 0);
+
+    if (cmdline.width % 2 != 0) {
+        pm_message("Warning: odd width; last column ignored");
+        cols = cmdline.width - 1;
+    } else
+        cols = cmdline.width;
+
+    if (cmdline.height % 2 != 0) {
+        pm_message("Warning: odd height; last row ignored");
+        rows = cmdline.height - 1;
+    } else 
+        rows = cmdline.height;
+
+    pixelrow1 = ppm_allocrow(cols);
+    pixelrow2 = ppm_allocrow(cols);
+
+    MALLOCARRAY_NOFAIL(y1buf, cmdline.width);
+    MALLOCARRAY_NOFAIL(y2buf, cmdline.width);
+    MALLOCARRAY_NOFAIL(ubuf,  cmdline.width/2);
+    MALLOCARRAY_NOFAIL(vbuf,  cmdline.width/2);
+
+    for (row = 0; row < rows; row += 2) {
+        fread(y1buf, cmdline.width,   1, yf);
+        fread(y2buf, cmdline.width,   1, yf);
+        fread(ubuf,  cmdline.width/2, 1, uf);
+        fread(vbuf,  cmdline.width/2, 1, vf);
+
+        computeTwoOutputRows(cols, cmdline.ccir601,
+                             y1buf, y2buf, ubuf, vbuf,
+                             pixelrow1, pixelrow2);
+
+        ppm_writeppmrow(stdout, pixelrow1, cols, (pixval) 255, 0);
+        ppm_writeppmrow(stdout, pixelrow2, cols, (pixval) 255, 0);
+    }
+    pm_close(stdout);
+
+    strfree(yfname);
+    strfree(vfname);
+    strfree(ufname);
+
+    pm_close(yf);
+    pm_close(uf);
+    pm_close(vf);
+
+    exit(0);
+}
diff --git a/converter/ppm/yuvtoppm.c b/converter/ppm/yuvtoppm.c
new file mode 100644
index 00000000..2b44c65f
--- /dev/null
+++ b/converter/ppm/yuvtoppm.c
@@ -0,0 +1,115 @@
+/* yuvtoppm.c - convert Abekas YUV bytes into a portable pixmap
+**
+** by Marc Boucher
+** Internet: marc@PostImage.COM
+** 
+** Based on Example Conversion Program, A60/A64 Digital Video Interface
+** Manual, page 69
+**
+** Uses integer arithmetic rather than floating point for better performance
+**
+** Copyright (C) 1991 by DHD PostImage Inc.
+** Copyright (C) 1987 by Abekas Video Systems Inc.
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+#include "mallocvar.h"
+
+/* x must be signed for the following to work correctly */
+#define limit(x) ((((x)>0xffffff)?0xff0000:(((x)<=0xffff)?0:(x)&0xff0000))>>16)
+
+int
+main(argc, argv)
+	char          **argv;
+{
+	FILE           *ifp;
+	pixel          *pixrow;
+	int             argn, rows, cols, row;
+	const char     * const usage = "<width> <height> [yuvfile]";
+    struct yuv {
+        /* This is an element of a YUV file.  It describes two 
+           side-by-side pixels.
+        */
+        unsigned char u;
+        unsigned char y1;
+        unsigned char v;
+        unsigned char y2;
+    } *yuvbuf;
+
+	ppm_init(&argc, argv);
+
+	argn = 1;
+
+	if (argn + 2 > argc)
+		pm_usage(usage);
+
+	cols = atoi(argv[argn++]);
+	rows = atoi(argv[argn++]);
+	if (cols <= 0 || rows <= 0)
+		pm_usage(usage);
+
+	if (argn < argc) {
+		ifp = pm_openr(argv[argn]);
+		++argn;
+	} else
+		ifp = stdin;
+
+	if (argn != argc)
+		pm_usage(usage);
+
+    if (cols % 2 != 0) {
+        pm_error("Number of columns (%d) is odd.  A YUV image must have an "
+                 "even number of columns.", cols);
+    }
+
+	ppm_writeppminit(stdout, cols, rows, (pixval) 255, 0);
+	pixrow = ppm_allocrow(cols);
+    MALLOCARRAY(yuvbuf, (cols+1)/2);
+    if (yuvbuf == NULL)
+        pm_error("Unable to allocate YUV buffer for %d columns.", cols);
+
+	for (row = 0; row < rows; ++row) {
+		int    col;
+
+		fread(yuvbuf, cols * 2, 1, ifp);
+
+		for (col = 0; col < cols; col += 2) {
+            /* Produce two pixels in pixrow[] */
+            int y1, u, v, y2, r, g, b;
+
+			u = yuvbuf[col/2].u-128;
+
+            y1 = yuvbuf[col/2].y1 - 16;
+			if (y1 < 0) y1 = 0;
+
+            v = yuvbuf[col/2].v - 128;
+
+            y2 = yuvbuf[col/2].y2 - 16;
+			if (y2 < 0) y2 = 0;
+
+			r = 104635 * v;
+			g = -25690 * u + -53294 * v;
+			b = 132278 * u;
+
+			y1*=76310; y2*=76310;
+
+			PPM_ASSIGN(pixrow[col], limit(r+y1), limit(g+y1), limit(b+y1));
+			PPM_ASSIGN(pixrow[col+1], limit(r+y2), limit(g+y2), limit(b+y2));
+		}
+		ppm_writeppmrow(stdout, pixrow, cols, (pixval) 255, 0);
+	}
+    free(yuvbuf);
+    ppm_freerow(pixrow);
+	pm_close(ifp);
+	pm_close(stdout);
+
+	exit(0);
+}
diff --git a/converter/tga.h b/converter/tga.h
new file mode 100644
index 00000000..fdbe4047
--- /dev/null
+++ b/converter/tga.h
@@ -0,0 +1,46 @@
+/* tga.h - header file for Targa files
+*/
+ 
+/* Header definition. */
+struct ImageHeader {
+    unsigned char IdLength;     /* length of Identifier String */
+    unsigned char CoMapType;        /* 0 = no map */
+    unsigned char ImgType;      /* image type (see below for values) */
+    unsigned char Index_lo, Index_hi;   /* index of first color map entry */
+    unsigned char Length_lo, Length_hi; /* number of entries in color map */
+    unsigned char CoSize;       /* size of color map entry (15,16,24,32) */
+    unsigned char X_org_lo, X_org_hi;   /* x origin of image */
+    unsigned char Y_org_lo, Y_org_hi;   /* y origin of image */
+    unsigned char Width_lo, Width_hi;   /* width of image */
+    unsigned char Height_lo, Height_hi; /* height of image */
+    unsigned char PixelSize;        /* pixel size (8,16,24,32) */
+    unsigned char AttBits;  /* 4 bits, number of attribute bits per pixel */
+    unsigned char Rsrvd;    /* 1 bit, reserved */
+    unsigned char OrgBit;   /* 1 bit, origin: 0=lower left, 1=upper left */
+    unsigned char IntrLve;  /* 2 bits, interleaving flag */
+    const char * Id;     /* Identifier String.  Length is IDLength, above */
+};
+
+#define IMAGEIDFIELDMAXSIZE 256
+
+typedef char ImageIDField[IMAGEIDFIELDMAXSIZE];
+
+#define TGA_MAXVAL 255
+
+/* Definitions for image types. */
+#define TGA_Null 0
+#define TGA_Map 1
+#define TGA_RGB 2
+#define TGA_Mono 3
+#define TGA_RLEMap 9
+#define TGA_RLERGB 10
+#define TGA_RLEMono 11
+#define TGA_CompMap 32
+#define TGA_CompMap4 33
+
+/* Definitions for interleave flag. */
+#define TGA_IL_None 0
+#define TGA_IL_Two 1
+#define TGA_IL_Four 2
+
+enum TGAbaseImageType {TGA_MAP_TYPE, TGA_MONO_TYPE, TGA_RGB_TYPE};
diff --git a/doc/COPYRIGHT.PATENT b/doc/COPYRIGHT.PATENT
new file mode 100644
index 00000000..fe3c242a
--- /dev/null
+++ b/doc/COPYRIGHT.PATENT
@@ -0,0 +1,125 @@
+Netpbm consists of code contributed by many authors.  In most of the
+source code files, there is a copyright notice and license, telling
+you what you may or may not do with the code.  All authors have granted
+you the right to use and distribute their code without having to pay
+them, as long as you meet some simple requirements.
+
+Most of the components require you to include a copy of their
+copyright notices and warranty disclaimers in any copies or
+derivations you distribute.
+
+Another restriction that some of the software has is that in order to
+have permission to copy it (which includes writing anything derived
+from it), you must distribute source code for your copy or derivation
+and propagate the same restriction to people who would copy your
+derivation.  In other words, the price the author wants for the use of
+his proprietary work is your contribution to the free software cause.
+
+One component prohibits you from selling it or using it in a
+commercial way: hpcdtoppm.
+
+Some components are contributed to the public domain.  
+
+The copyrights on individual components of this package are detailed 
+at appropriate places within the package.  A slightly out of date
+summary of all the copyrights is in the file 'doc/copyright_summary'
+in the Netpbm source tree.
+
+As with most public open source software, no one really knows for sure
+where the code came from.  It is possible that a contributor copied it
+without license to do so.  That might mean any user of the code owes
+someone royalties.  The Netpbm maintainer in particular has received
+no warranties regarding any of the code in the package.  So consider
+all the above to be modified by "to the best of the Netpbm
+maintainer's knowledge."
+
+
+
+PATENTS
+-------
+
+These are the patents the Netpbm maintainer knows about that relate to
+Netpbm.  It is basically just information the maintainer has stumbled
+over at some point -- no search has been done.
+
+No licenses have been granted by patent owners to the maintainer of
+Netpbm.  Therefore, if you need a patent to use something in Netpbm,
+you need your own license.
+
+A note about patents in general: A patent gives an inventor the
+exclusive right to make, sell, or use the invention.  If you
+independently invent something without knowing that the patent holder
+already did, that makes no difference -- the patent holder still has
+the exclusive right.  It makes no difference if you give the original
+inventor credit.  The patent applies to a method, not its expression,
+so writing a program from scratch to implement a certain method is
+still a patent infringement.  Infringing a patent is not a crime per
+se, but to the extent that it costs the patent holder money, the
+infringer has to make it up.
+
+The original purpose of patents is probably perverted when patents are
+applied to things you implement in computer programs.  This is one of
+the Free Software Foundation's causes.  See 
+<http://www.gnu.org/philosophy.html#laws>.
+
+
+Unisys owns patents on LZW compression, which is used by Ppmtogif, and
+maybe on LZW decompression, which is used by Giftopnm.  IBM also owns
+a patent that may cover the GIF tools.  Unisys offers a license of the
+patent for trivial use for $5000.  Its U.S. patent (Number 4,558,302)
+EXPIRED June 20, 2003.  In most of Europe, the patent expires June 18,
+2004.  In Japan, it's June 20, 2004 and in Canada, July 7, 2004.
+IBM's U.S. patent expirs August 11, 2006.
+
+Neither company has ever enforced the patent against trivial users of
+it.  <http://news.cnet.com/news/0-1005-200-1713278.html> is an article
+dated April 18, 2000 on the issue.
+http://www.unisys.com/about__unisys/lzw/> is Unisys' view of the
+matter.  For information from another perspective, see
+<http://burnallgifs.org>.
+
+The following Netpbm components may be restricted by these patents:
+Ppmtogif, Giftopnm.
+
+A good substitute for GIF if the patents are a problem is PNG (see
+pngtopnm, pnmtopng), which was developed with a primary purpose of not
+using any patented technology.
+
+You can also use the -nolzw option on ppmtogif to avoid using the LZW
+patent.  The images so generated are larger than traditional
+LZW-compressed GIFs, but any GIF decoder can decode them just the
+same.
+
+I repeat: The Unisys U.S. patent has expired.  It is not an issue
+for any future use of Netpbm.
+
+
+The Pnmtojpeg and possibly Jpegtopnm programs in some cases may use
+the arithmetic coding patents owned by IBM, AT&T, and Mitsubishi.
+There is difference of opinion on whether they do.
+
+Forgent owns a patent it believes covers JPEG compression.  This
+patent was virtually unknown before July 2002, when Forgent began to
+enforce it.  It has successfully enforced it against two companies
+(Sony and an unnamed Japanese digital camera maker), but without court
+ruling.  This patent, U.S. Patent No. 4,698,672, expires in 2006.
+
+Philips and Lucent Technologies also own patents they claim cover
+JPEG.
+
+The following Netpbm components may be restricted by these patents:
+Jpegtopnm, Pnmtojpeg, Ppmtompeg, Tifftopnm, Pnmtotiff.  These all
+do their JPEG work via a JPEG library not distributed with Netpbm.
+Your JPEG-related liability for using Netpbm is limited to your 
+liability for using your JPEG library.
+
+The next best alternative to JPEG is probably PNG and maybe JBIG for
+bilevel (black and white) images.
+
+http://burnalljpegs.org contains information on this issue.
+
+
+The Jbigtopnm and Pnmtojbig programs use arithmetic coding patents and
+other patents covering various aspects of the "front end."
+
+
diff --git a/doc/GPL_LICENSE.txt b/doc/GPL_LICENSE.txt
new file mode 100644
index 00000000..c3c7a9ea
--- /dev/null
+++ b/doc/GPL_LICENSE.txt
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/doc/HISTORY b/doc/HISTORY
new file mode 100644
index 00000000..7098e887
--- /dev/null
+++ b/doc/HISTORY
@@ -0,0 +1,3385 @@
+Jef Poskanzer (jef@acme.com) invented the PBM format in the 1980s to
+provide the computer graphics world with a common, trouble-free
+format, a lingua-franca, for bitmaps.  The format was designed to be
+simple enough that it could transmitted within an email message
+without any special encapsulating and survive any translations and
+recoding that an email message might go through and be easily
+extractable on the other end.
+
+In 1988, Jef distributed the forerunner of Netpbm, Pbmplus, which
+gathered together the various tools he had developed to work with PBM
+files.  These were mainly tools to convert between PBM and other
+existing graphics formats, making it possible to deal with the Tower
+Of Babel situation that had arisen with the proliferation of graphics
+formats.
+
+By the end of 1988, Jef had added the PGM and PPM formats and lots more
+tools to Pbmplus.
+
+In 1991, Jef added a variety of programs and code contributed by the
+user community, and then stopped maintaining Pbmplus.  Jef never
+formally renounced support for it, but simply didn't get around to
+distributing any updates for over a year following his final December
+10, 1991 release.
+
+In 1993, Netpbm was developed to replace Pbmplus.  Netpbm was nothing
+more than a new release of Pbmplus, and named for the fact that people
+all over the world would maintain the package by submitting fixes and
+enhancements over the Net.  This was a time when such worldwide
+collaboration was still novel.
+
+Then Netpbm apparently fell into neglect with the last release by its
+regular maintainer in March 1994.  In September of 1995 Anthony
+Thyssen released an unofficial update of it called Netpbm-1mar1994.p1,
+while disclaiming any responsibity for ongoing upkeep.
+
+The documentation in that 1995 release mentioned two different official
+mailing lists concerning the package, but by April 1999, neither
+existed anymore.
+
+In September 1999, Bryan Henderson needed some minor bugs fixed, and
+wanted to clean up the mess of unsupported versions and outdated
+documentation of these tools.  After checking around to see if anyone
+had any claims of ownership of the package, and finding none, Bryan
+assumed control and responsibility for Netpbm.  
+
+In November 1999, Thorbjoern Ravn Anderson <ravn@mip.sdu.dk> did the
+same thing, for the same reason (apparently unaware of Bryan's work)
+and made available an updated version (based on the 1994 release) as
+described by http://sunsite.auc.dk/netpbm.  Shortly thereafter, he
+discontinued his effort in deference to the new Pbmplus effort
+mentioned below.  Bryan did not know about Anderson's work until March
+2000.
+
+In August 2000, the PAM format appeared.  But it took a long time
+after that for there to be enough programs and library routines to
+make it useful.  It still is barely known and used; PAM is more of a
+direction or philosophy than a practical tool.  Along with PAM came a
+whole new suite of library routines for processing both classic PNM
+and PAM format images.  The new "pam" routines are easier to use.
+
+In parallel, and unknown to Bryan, Jef was working on a new release
+under the original name Pbmplus and claiming again (or maybe still) to
+be maintaining Pbmplus.  He did a limited release of a beta version of
+it in November 1999.  A mailing list pbmplus@acme.com was active.
+Bryan learned of this effort's existence in March 2000.  No widespread
+distribution ever resulted from this.
+
+Pbmplus and Netpbm were once part of the body of X/Windows contributed
+software, distributed on ftp.x.org (directory contrib/utilities).
+Bryan didn't receive a response to an inquiry to the ftp.x.org owner
+as to the prospect of cleaning up the Netpbm related files there.  So
+Bryan instead made Metalab the new home of the source code.  But
+Metalab had pretty low quality too, and in April 2000 Bryan moved the
+package's home to Sourceforge.
+
+In June 2002, Bryan reorganized the package.  Before, it was organized to
+reflect the evolution of the package:  first PBM only, then PGM was added,
+then PPM, and then the multi-format PNM programs.  There were four separate
+subroutine libraries and each of the PBM, PGM, PPM, and PNM components was
+independent of the components invented later.  Documentation was in the form
+of traditional man pages.  In Release 10, the package was integrated into a
+single monolithic package.  There was one subroutine library and the source
+tree was divided according to program function rather than what generation
+of Netpbm it related to.  All documentation was converted to HTML.
+
+In January 2004, Netpbm started to get the linear/gamma-adjusted
+sample value thing right.  Many programs had always used the gamma
+adjusted values from Netpbm images as if they were linear, which
+produced incorrect and rather terrible results.  Netpbm 10.20 brought
+the pm_[un]gamma709() library routines and pnm_readpamrown() etc.  for
+manipulating Netpbm images in floating point 0..1 sample values, both
+linear and gamma-adjusted.
+
+
+CHANGE HISTORY 
+--------------
+
+06.08.19 BJH  Release 10.35
+
+              Add pgmdeshadow.
+
+              giftopnm: add -quitearly.
+
+              pamfile: add -comments.
+
+              ppmdraw: remove limitation on size of script.
+
+              pnm_readpaminit(), pnm_writepaminit(): Add comment control.
+
+              ppmtogif: do one row at a time.
+
+              Pnmtopng: improve validation of -modtime option.
+
+              Look in /usr/share/X11 instead of /usr/openwin/lib for
+              rgb.txt.
+
+              ppmtompeg: die properly when frame is less than 16x16.
+
+              ppmdraw: fix bug with semicolon in script not followed by
+              white space.
+
+              libppmd (ppmdraw, ppmlabel): don't crash with horizontal
+              or vertical line that is entirely out of frame.
+
+              picttoppm: Fix 32 bit per pixel conversion, broken in 10.34.
+
+              pamthreshold: fix read from pipe.
+
+              Fix typo in make file that makes svgtopam get built
+              when Libxml2 is not available.
+
+              pnm_computetuplefreqhash(), pnm_computetuplefreqtable2():
+              Don't crash on error without pm_setjmp().
+
+              Change memmem() to memmem_internal() in cameratopam/identify.c
+              to avoid collision with system library.
+
+              Use __MINGW32__ to determine HAVE_MKSTEMP (instead of manual).
+
+              Eliminate use of network functions for endianness computations.
+
+              Use "inttypes_netpbm.h" on a system that doesn't have
+              the int_fast32_t, etc. types.  Like Solaris 8.
+
+              configure, installnetpbm: autoflush stdout.
+
+              Build: Use libpng-config if it exists.
+
+              Build: assume no Lex program if neither 'flex' or 'lex' shell
+              command verb exists.
+
+              Remove test source file ppmdtexttest.  Ppmdraw should be
+              sufficient now.
+              
+06.06.18 BJH  Release 10.34
+
+              Add pamthreshold.  Thanks Erik Auerswald
+              <auerswal@unix-ag.uni-kl.de>.
+
+              Add pamx.
+
+              Add pamtoxvmini.
+              
+              pammasksharpen: Add -threshold.
+
+              pnmtopng: make "N colors found" message verbose-only.
+
+              pnmtopng: make "no room in palette" message non-verbose.
+
+              picttoppm: Tolerate various PICT file corruptions.
+
+              picttoppm: Don't issue warning message when file named
+              'fontdir' doesn't exist.
+
+              libnetpbm: Add ppm_bk_color_from_color(),
+              ppm_bk_color_from_name(), ppm_name_from_bk_color().
+              Thanks "Kenan Kalajdzic" <kalajdzic@gmail.com>.
+
+              libnetpbm: Add ppmd_fill_path().
+
+              ppmtobmp: Fix for PBM input.
+
+              bmptopnm: Don't crash on BMP with no color map.
+
+              bmptopnm: Fix wrong file name in error messages.
+
+              ppmtogif: fix bug: always produces garbage output.
+
+              ppmtompeg: fix input from Standard Input.
+
+              pnmflip: fix bug: -rotate90, -rotate180, and -rotate270
+              (and synonyms) don't work when followed by other rotation
+              options.
+
+              ppmtoilbm: Fig bug: generates more planes than necessary.
+
+              pamtofits: fix buffer overflow in asembling header.
+
+              picttoppm: fix bug - interprets some images wrong because of
+              bogus "rowBytes" value.
+
+              Redo asprintfN(), etc. so as not to use va_list in a way
+              that doesn't work on some machines.
+
+              cameratopam: remove definition of memmem() so it doesn't collide
+              with same in some C libraries.  Add memmemN() and MEMEQ to
+              libnetpbm.
+
+              Fix build of filename.o.
+
+              Build: Use local version of mkstemp() based on mktemp() when
+              mkstemp() isn't available.  No automatic determination that
+              mkstemp() isn't available yet.
+
+              Build: Include dummy pm_system() on a system that doesn't
+              have regular Unix process management.
+
+              Add -Wundef to Gcc compile options.
+
+06.03.26 BJH  Release 10.33
+
+              Add pamtosvg.
+
+              g3topbm: Add -width, -paper_size.
+
+              libnetpbm / most newer programs: Fix bug that produces
+              plain format output when it should be raw because
+              pnm_readpaminit() does not set 'plainformat' and most
+              programs just copy the input pam to the output pam.
+
+              pamflip: fix bug with left/right flip of PBM that has 
+              width an even multiple of 8 plus something less than 8.
+
+              pnmquant: turn on autoflush when creating seekable file.
+
+              install: fix symbolic link pnmdepth -> pamdepth.
+              
+              build: fix some importinc dependencies.
+
+06.02.25 BJH  Release 10.32
+
+              Add rlatopam.  Thanks Simon Walton <simonw@matteworld.com>.
+
+              Add pgmmake.
+
+              bmptopnm: Understands RLE4/RLE8 compressed BMP.  Thanks
+              Prophet of the Way <afu@wta.att.ne.jp>.
+
+              pnmgamma: Add -bt709tosrgb -srgbtobt709, -bt709tolinear,
+              -lineartobt709, -gamma, -rgamma, -ggamma, -bgamma (gammma
+              values were formerly specified as parameters).
+
+              pnmgamma: Rename -cieramp to -bt709ramp.
+
+              pnmnorm: add -maxexpand option.
+
+              ppmdraw: work on multi-image streams.              
+
+              anytopnm: add mime time image/x-ms-bmp for BMP
+
+              pamchannel: works on multi-image streams.
+
+              pamstack: works on multi-image streams.
+
+              Convert pnmdepth to pamdepth.  Add multi-image stream
+              capability.
+
+              pamcut: works on multi-image streams.
+
+              pnmtops: allow -flate and -rle together.
+
+              pnmtops: overhaul of -psfilter output.
+              Thanks Chapman Flack <chap@anastigmatix.net>.
+
+              pnmrotate: fine adjustment to arithmetic (rounding,
+              pixel quantization).
+
+              pbmtopsg3: Put currentfile ... in exec block.  Thanks
+              Chapman Flack <chap@anastigmatix.net>
+
+              pbmtopsg3: Add missing /EndOfBlock .  Thanks
+              Chapman Flack <chap@anastigmatix.net>
+
+              pnmtops: Don't claim EPSF if using setpagedevice.
+              Thanks Chapman Flack <chap@anastigmatix.net>
+
+              giftopnm: do "reading image sequence N" message only if
+              being verbose.
+
+              ppmtobmp, bmptopnm: major speed improvement for PBM.  Thanks
+              Prophet of the Way <afu@wta.att.ne.jp>.
+
+              pbmtog3: Use new GCC features instead of assembly language
+              for superspeed operations.  Thanks
+              Prophet of the Way <afu@wta.att.ne.jp>.
+
+              pm_make_tempfile(): Use TEMP and TMP environment variables if
+              TMPDIR not set.
+
+              pm_make_tempfile(): improve error message.
+
+              libpam: pnm_writepamrowmult() respects pam.plainformat.
+
+              libpam: pnm_writepaminit() checks -plain option for PAM format
+              (recognizes the error).
+
+              ppmtoglobe: Fix wild pointer bug.
+
+              Fix wild pointer in REALLOCARRAY(). Affects ppmdraw,
+              escp2topbm, ppmtowinicon, ppmtompeg, pnmtopalm,
+              pnmtopng.  Thanks Steve Summit <scs@eskimo.com>.
+
+              pnmtopng: fix bug with undefined cmdline.modtimeSpec.
+              Thanks Mike Frysinger <vapier@gentoo.org>.
+
+              pnmquant: Use Perl sysseek() instead of seek() to avoid
+              a mispositioned file disaster on some platforms.
+
+              pamditherbw, pgmtopbm: fix bug: overly dark with cluster
+              methods because only 1/4 of the dither matrix is used.
+              Thanks Mark Williams <mark@aziraphale.homeip.net>.
+
+              pnmrotate: fix bug: garbage in lower right corner of background
+              with -noantialias.
+
+              pnmsmooth: change to regular Netpbm syntax.  Replace -size
+              with -width and -height.
+
+              pnmsmooth: fix bug: free of temp file name even when there
+              is no temp file (-dump option).
+
+              pnm_scaletuplerow() fix bug: does nothing when old maxval ==
+              new maxval.  Affects pamedge only.
+
+              ppmtompeg: Fix array/pointer degeneration mess with LumBlock
+              arguments; fix crash.
+
+              pamstereogram: Fix crippling bugs.  Thanks Scott Pakin
+              <scott@pakin.org>.
+
+              giftopnm: Fix bug with interlaced GIF < 5 rows.
+
+              giftopnm: Handle case of a clear code at the end of a block.
+
+              ppmtogif: Fix bug with interlaced GIF < 5 rows.
+
+              cameratopam: Fix segmentation fault from undefined 'ifp'.
+              Thanks Bernard Hatt <bmh@arkady.demon.co.uk>.
+
+              xwdtopnm: Fix bug from 10.31: LSB-first XWDs convert to all
+              black.
+
+              ppmglobe: fix bug: always says stripcount is zero
+              because it looks at the wrong argument.  Thanks Urs Enke
+              <urs.enke@rwth-aachen.de>.
+
+              pnmtotiffcmyk: Fix inttypes conflict on AIX.
+
+              Stop exporting stripeq().
+         
+              Add -lm to link of libnetpbm shared library, to express the
+              fact that code in libnetpbm requires it.  Helps GNU Ld
+              --as-needed.
+
+              Build: move symbolic links to header files into
+              'importinc' directories to keep directories cleaner.  Fully
+              populate 'importinc' in every directory so we don't have
+              to maintain a list of header files for each.
+
+05.12.23 BJH  Release 10.31
+
+              Add pamgradient.
+
+              libnetpbm/everything: speed up ppm_readppmrow() and
+              pgm_readpgmrow() by doing one fread per row instead of a
+              pgm_readrawsample() per sample.  Thanks "Ariel Berkman"
+              <aberkm1@uic.edu>.
+
+              libnetpbm: Make PGM, PPM, and PNM routines read suitable
+              PAM images.
+
+              pnmsplit: upgrade to pamsplit.
+
+              xwdtopnm: Add ability to work with bits per pixel >
+              bits per item.  Replace whole pixel reader.
+
+              pamtotiff: Use TIFFDefaultStripSize() for ROWSPERSTRIP default.
+
+              pamtotiff: add -tag option.  Thanks Gary Gorbet
+              <ggorbet@sdicgm.com>.
+
+              ppmglobe: add -background, -closeok, filename argument.
+
+              pnmcolormap: Work on multi-image stream (one map for all).
+
+              pnmtopclxl: read/convert/write one row at a time.
+              Thanks Martin Buck <m@rtin-buck.de>.
+
+              ppmtolj: read/convert/write one row at a time.
+              Thanks Martin Buck <m@rtin-buck.de>.
+
+              ppmtoxpm: fix bug: produced incorrect output when number
+              of colors is a power of 92.  Thanks Mark Weyer.
+
+              pnmcolormap: fix bug: crashes with input depth > 3.
+
+              pambayer: fix bug: doesn't actually read input file.
+
+              ppmtompeg build: add missing const to work around TRU64
+              compile failure.
+
+              xwdtopnm: Fix handling of padding at end of XWD row.
+
+              pamtotiff: fix bug with PAMs with > 3 planes.
+
+              Remove PPM_PACKCOLORS.
+
+              pnmremap: fix arithmetic overflow bug with maxval > 255 that
+              caused random pixels.
+
+              remove global "cmdline" variables from all programs (4 had them).
+
+              pnmtops: remove extra %%%%Page from 10.27.
+
+              mkinstalldirs: remove chmod 755 to match Automake's
+              mkinstalldirs.
+
+              libopt: add -quiet option
+
+              ppmtojpeg: fix prototype mismatch when building without
+              the JPEG function.
+
+              libnetpbm: fix: PPM_DISTANCE uses red twice instead of and green.
+
+              pnmnlfilt: fix alpha ranges.
+         
+              cameratopam: replace setenv with putenv so it works on Solaris.
+
+              cameratopam: put in workaround for Solaris header file bug.
+
+              pngtopnm: fix bug: grayscale PNG produces PPM.  s/b PGM.
+
+              anytopnm: Use sed instead of Awk -- more universal.
+
+              anytopnm: fix "INTERNAL ERROR" crash due to "filetype" variable
+              not set.
+
+              rletopnm: fix crash when input file is empty or
+              there are various other problems reading its header.
+
+              rletopnm: fix universal crash in option parser.
+
+              tifftopnm: fix crash due to uninitialized variable.
+
+              libnetpbm: Fix bug from 10.30: erroneously says a PAM header
+              line is missing.
+
+05.10.16 BJH  Release 10.30
+
+              Add pambayer.
+              
+              Add pamrgbatopng.
+                            
+              Add pamtilt.  Thanks Gregg Townsend <gmt@cs.arizona.edu>.
+              
+              pamtotiff: create from pnmtotiff.
+
+              pamtofits: create from pnmtofits.
+
+              pamaddnoise: create from pnmaddnoise.
+         
+              pamarith: Add -divide.
+
+              pammasksharpen: Add -sharpness.
+
+              pnmtopng: add -comp_mem_level, -comp_strategy, -comp_method,
+              -comp_window_bits, -comp_buffer_size.
+
+              pnmtopng: use Shhopt command line processor; replace
+              -chroma with -rgb, -phys with -size, -time with -modtime.
+
+              pnmremap: Handle multiple image stream.
+
+              xpmtoppm: Expand capacity from 2K character input lines to
+              8K character input lines.
+
+              libnetpbm: improved messages for "color xxx cannot be
+              represented precisely with maxval M"
+
+              libnetpbm color specification parsing:  Improve warning message
+              about rounding.
+
+              libnetpbm/everything: speed up ppm_writeppmrow() and
+              pgm_writepgmrow() by doing one fwrite per row instead of
+              a pgm_writerawsample() per sample.
+
+              pnmtopng: fix "meaningful bits" optimization of PGM so it
+              optimizes all the way instead of stopping at 4 bits.
+
+              pnmtopng: fix crash when no -alpha option, due to dereferencing
+              of undefined alpha_mask.
+
+              pnmtopng: fix bug: arbitrary color made transparent in
+              paletted PNG when nothing should be transparent.
+
+              pnmtops: bug: selects wrong Postscript level.
+
+              ppmtowinicon: use REALLOCARRAY instead of arithmetic-overflowing
+              realloc().
+
+              anytopnm: fix bug: fails when 'file' database doesn't
+              have mime type but does have human-readable type.
+              
+              manweb: recognize directory as not executable in search of PATH.
+
+              pgm_allocrow(), ppm_allocrow(), pnm_allocrow(): cure
+              arithmetic overflow.
+
+              libnetpbm: On plain formats, check for sample value exceeding
+              maxval.
+
+              libnetpbm: reject maxval > 65535 in "pam" functions. 
+
+              pamarith: Use normalized libnetpbm facilities to get
+              proper accuracy.  Thanks Thomas Henlich
+              <http://sourceforge.net/users/thenlich/>.
+
+              pamarith: fix bug: shift functions don't work when maxvals
+              are not identical.
+
+              pamarith: fix rounding error on multiply.
+
+              pgmtexture: correct calculation of normalizing constants
+              for d > 1.  Thanks Marc Breithecker
+              <Marc.Breithecker@informatik.uni-erlangen.de>.
+
+              pnmnorm: fix divide by zero crash.
+
+              pnmnorm: fix bug that doesn't let you specify
+              -luminosity, -colorvalue, or -saturation.
+
+              ilbmtoppm: fix variable used before set.
+
+              pnmtofits: fix 16 bit sample values > 2**15.
+
+              pnmtopsnr: minor adjustments to messages.
+
+              pnmnorm: don't crash when wvalue == bvalue.  Thanks Thomas
+              Henlich <http://sourceforge.net/users/thenlich/>.
+
+              pnmnorm: fix rounding of output values.  Thanks Thomas
+              Henlich <http://sourceforge.net/users/thenlich/>.
+
+              pnmhisteq: Fix external map to have width maxval + 1 instead
+              of maxval, so full intensity input pixels map properly.
+
+              pnmhisteq: Fix -rmap.
+
+              Fix typos in error messages in various programs.
+
+              pgmmedian: change syntax to Netpbm standard.
+
+              pammasksharpen: fix signedness bug that caused random bright
+              pixels at edges.
+
+              pammasksharpen: fix bug with maxval != 255.
+
+              Clean up a bunch of function prototypes so some broken
+              compilers don't complain about missing "const" in them.
+
+              Remove dependency of pnmtops on basename().  It isn't in
+              some C libraries and we don't want to mess with linking
+              extra libraries.
+
+              Build of ppmsvgalib: work properly when LINUXSVGALIB is
+              a "default path" value such as "libvga.so".
+
+05.08.15 BJH  Release 10.29
+
+              Add ppmdraw.
+
+              Add pammixinterlace.  Thanks Bruce Guenter <bruceg@em.ca>.
+              
+              Add pgmmedian.
+
+              Add pnmaddnoise.
+
+              pnmtopclxl: add -rendergray option.
+
+              pnmtopclxl: add -jobsetup option.
+              
+              pstopnm: add -dSAFER Ghostscript option.
+
+              pnmcrop: add -margin option.
+
+              pnmcrop: add -borderfile option.
+
+              pnmnorm: add -luminosity (which was already default),
+              -colorvalue, and -saturation.
+         
+              pnmtopng: Don't include any alpha info if supplied alpha mask
+              is all opaque.
+
+              ppmcie: Make white point mark more visible.
+
+              pamdice: Remove restriction of 100 slices in each direction.
+
+              pnmtopng: Fix bug: erroneous transparent pixels when supplied
+              alpha mask is all opaque.
+              
+              Fix bytesToWordInt(): converts incorrectly on a machine that
+              has pointer size != word size and uses the general case code.
+
+              libnetpbm: fix basic pm_readlittleshort() bug introduced
+              in 10.27.  Affects mdatopbm, rawtopgm, lispmtopgm,
+              pcxtoppm, winicontoppm, bmptopnm, sirtopnm, xwdtopnm,
+              cameratopam.
+
+              pamlookup: fix universal crash.  Thanks "Colley, Anthony
+              W." <Anthony.Colley@ngc.com>
+
+              pbmtolj: fix bug from 10.28: crashes with a line which is
+              a multiple of 8 plus 7 pixels wide not counting white right
+              margin.
+
+              ppmtompeg: fix uname() rc test (sometimes > 0 is success).
+
+05.06.06 BJH  Release 10.28
+
+              Add cameratopam (Dave Coffin's dcraw).
+
+              ppmtoxpm: ignore empty or similar malformed XPM line instead
+              of giving up.
+
+              pbmtolj: do multi-image stream.
+
+              pnmindex: rewrite in C.
+
+              Fix Gcc 3 warnings.
+
+              pamcomp: fix out of memory problem due to uninitialized
+              pam.allocation_depth.
+
+              anytopnm: remove apparently redundant traps of signals.
+
+              pnm_alloctupletable(): fix bug - returns random value.
+              (affects pnmcolormap, pnmremap, pnmquant, others).
+         
+              jpeg2ktopam: fix array bounds violation on 64 bit machines.
+              Thanks Frederic Devernay <Frederic.Devernay@inrialpes.fr>.
+
+              rle_open: remove extraneous errno declaration.  Thanks
+              Joerg Sonnenberger <joerg@britannica.bec.de>.
+
+              pm_tmpfile, pm_make_tmpfile: rearrange code.
+
+              ppmtompeg: fix insecure temp file.  Thanks Alexey
+              Tourbin <at@altlinux.ru>.
+
+              libnetpbm: fix basic readlittlelong() bug introduced in 10.27.
+              Affects xwdtopnm, bmptopnm, winicontoppm, lispmtopgm.
+
+              pbmtext/libnetpbm: fix crash with -builtin=fixed.
+
+              pbmtoppa: fix floating point exception always due to
+              undefined DPI.
+
+              fix jpeg2000 build failure on system without inttypes.h,
+              etc.  Build now excludes jpeg2000 if you don't have 64
+              bit capability, which practically means you have
+              inttypes.h, etc.
+
+              jpeg2000: don't do unsafe temporary files.
+
+              ppmshadow: make it work with old Perl.
+
+              pnmquant: remove use of Fcntl::seek, not available in
+              Perl 5.00503.
+
+              pnmquant: make -quiet option work.
+
+              pnmgamma: Correct math in -srgbramp.  Add proper rounding.
+              Thanks Horst J. Wobig (hjw.fsw@wobig.de).
+
+              ppmshadow: fix blurring of non-translucent shadow.
+
+              ppmtoxpm: fix use of uninitialized variable that results
+              in invalid color map.
+         
+              Fix backward pnmtopnm - pamtopnm symlink.
+
+05.03.28 BJH  Release 10.27
+
+              tifftopnm: do multi-image streams.
+
+              pnmtotiff: add -append option.
+
+              pnmtotiff: do multi-image streams.
+
+              pnmtopclxl: fix bug with invalid palette in
+              SetColorSpace command.
+
+              pnmtopng: don't accept -background without -mix.
+
+              Eliminate pnmtopnm program -- install 'pnmtopnm' as an
+              alternate name for 'pamtopnm' instead.
+
+              pamscale: refilter as part of resampling for better
+              quality downsampling.
+
+              ppmcolormask: fix segfault with stdin input.
+
+              Add pm_setjmpbuf().
+
+              pnmindex, ppmquantall: use pnmquant instead of ppmquant.
+
+              pnmquant: add -quiet, -plain options
+
+              ppmfade, pnmshadow, pnmindex, pamlookup: Use pamscale
+              instead of pnmscale.
+
+              ppmtoxpm: Don't include superfluous entries for colors
+              of transparent pixels, or for transparency when there are
+              all colors are opaque, in the XPM color map.
+              
+              pnmquant: remove dependency on File::Temp, not available
+              before Perl 5.6.1.
+
+              pamfunc: use multiplication instead of division for -divisor,
+              for speed.
+
+              pamfunc: fix bug in -adder and -subtractor with signed
+              arithmetic.  Thanks David Jones <drj@pobox.com>.
+
+              pamscale: fix bug in converting negative floats to integers.
+              Thanks David Jones <drj@pobox.com>.
+
+              pamscale: fix bug with -filter: scanbuf too small, thus
+              some output rows/columns can never be created and output raster
+              is invalid.
+
+              build: fix build errors in ppmtompeg with OMIT_NETWORK.
+
+              jpeg2ktopam: fix uninitialized memory problem in allocating
+              jasperCmptNo.
+
+              pbmclean: fix -minneighbors so it isn't really -minneighbor.
+
+              pnmremap: allow RGB map and BLACKANDWHITE/GRAYSCALE input.
+
+              pamstereogram: fix bug with -eyesep (float/double).
+
+              pamtojpeg2k: fix bug with -compression (float/double).
+
+              pamtojpeg2k/jpeg2ktopam: work on 64 bit machine; fix
+              definition of int32fast_t, etc. in libjasper.
+
+              libnetpbm: Respect plainness of specified format when writing.
+
+              pnmremap: fix handling of arithmetic overflow in color
+              distance calculation.
+
+              pnmremap: fix bug with palette of different depth from image.
+              
+              libnetpbm: fix 2 byte per pixel pnm_pamwriterow(),
+              pnm_formatpamrow().
+
+              pbmmake: fix bug: requires color option.  (Introduced ca.
+              10.23).
+
+              -plain works with PAM write functions.
+
+              common -plain option causes failure instead of wrong output
+              when used with PAM output.
+
+              pnmremap: fix rounding error where map maxval is small and
+              unequal to image maxval.
+
+              Add pnm_normalizeRow(), pnm_unnormalizeRow(),
+              pnm_creategammatransform(), pnm_createungammatransform(),
+              pnm_freegammatransform(), pnm_freeungammatransform().
+              Idea and prototype from "Scott T. Smith" <scott@gelatinous.com>.
+
+              pamscale: Use pnm_noramlizeRow() to speed up gamma adjustments.
+
+              palmtopnm: Don't assume Palm Bitmap is valid.
+              Thanks Paul Bolle <pebolle@tiscali.nl>.
+
+              giftopnm: fix crash with input that has bogus color indices
+              in the raster.
+
+              giftopnm: fix crash with input that has a bogus minimum code
+              length field.  Thanks deekoo@tentacle.net (Deekoo L.).
+
+              pnm_readpamrown(): use pnm_readpamrow() instead of
+              pm_getraw() for speed.  Similar for pnm_writepamrown().
+
+              pnm_readpamrown(): use multiplication instead of division
+              for speed.  Thanks "Scott T. Smith" <scott@gelatinous.com>.
+         
+              pm_readbiglong(), etc.: pm_error() in case of failure.
+
+              Add pm_readchar(), pm_writechar(), pm_readcharu(),
+              pm_writecharu().
+
+              pnmtopalm: Add packbits compression.
+
+              pnmtopalm: Add version 3 capability (-density, -withdummy).
+
+              pnmtopalm: fix bug: incorrect between-image pad amount.
+
+              pnmtopalm: fix compressed images.  use 0 compressed size
+              field - not totally correct, but passable.
+
+              palmtopnm: fix incorrect handling of some combinations of
+              PALM_IS_COMPRESSED flag and compression type field.
+              Thanks Paul Bolle <pebolle@tiscali.nl>.
+
+              palmtopnm: fix totally broken RLE and scanline compression.
+              Thanks Paul Bolle <pebolle@tiscali.nl>.
+
+              palmtopnm: handle PackBits compression.  Thanks Paul
+              Bolle <pebolle@tiscali.nl>.
+
+              palmtopnm: handle Version 3 encoding.  Thanks Paul Bolle
+              <pebolle@tiscali.nl>.
+
+              Make makeman put in missing newline.  Thanks Eric S. Raymond
+              <esr@thyrsus.com>.
+
+              Include libz in build of programs that use TIFF library.
+
+              palmtopnm: fix wrong check for disallowed combination of
+              -transparent and -rendition.
+
+              pnmtops: Add -psfilter, -flate, -ascii85, -level options.
+
+              pnmtops: Add -dict, -vmreclaim options.
+
+              pnmtops: improve %%Title
+
+              pamcomp: fix bug that duplicates last row of overlay image.
+              Thanks achurch@achurch.org (Andrew Church).
+
+              pamcut: fix bug that fills the rightmost column with black.
+              Thanks achurch@achurch.org (Andrew Church).
+
+              pnmtops: Add -noshowpage option.
+
+              pnmtops: Add %%%%Page. (whoops, this actually added a second
+              %%%%Page.  Removed in 10.31).
+
+              pnmtopalm: fix bug with -offset option.
+
+              xwdtopnm: recognize TrueColor visual class in header dump.
+
+              ppmforge: fix bug: crash due to wild pointer with -night.
+              Thanks John Walker <kelvin@fourmilab.ch>.
+
+05.01.01 BJH  Release 10.26
+
+              pnmhistmap: Add -dots, -nmax, -red, -green, -blue, -lval,
+              -rval, -height, and -width options.
+              Thanks "John H. DuBois III" <spcecdt@armory.com>.
+
+              pnmnorm: accept -wpercent and -wvalue (or -bpercent and -bvalue)
+              together.  Thanks Gregg Townsend <gmt@CS.Arizona.EDU>.
+         
+              xwdtopnm: Add -headerdump option.
+
+              pnmtoxwd: Include 256 color color maps in direct color instead
+              of zero.  Zero has a weird effect.
+
+              pamtotga: recognize RGB_ALPHA instead of RGBA.
+
+              palmtopnm: fix bug in determination of compression type.
+
+              palmtopnm: fix bug in handling of multi-image streams.
+              Thanks Paul Bolle <pebolle@tiscali.nl>.
+
+              pnmtopalm: fix bugs in computation of nextDepthOffset.
+              Thanks Paul Bolle <pebolle@tiscali.nl>.
+
+              pamtotga: fix TGA pixel size for TGA with alpha.  Don't do
+              colormap if input has transparency.
+
+              pamtotga: fix crash due to uninitialized "id" field in TGA 
+              header.
+
+              pbm_writepbmrow(): Fix bug in MMX/SSE code that causes
+              segmentation fault.
+
+              pnmhistmap: Fix bug: doesn't work with maxval != 255.
+              Thanks "John H. DuBois III" <spcecdt@armory.com>.
+
+              pbmtext: fix crash with code point > 127.
+
+              ppmtompeg: major cleanup.
+
+              pnmtopng: fix "internal error" and crash bug with -transparent.
+
+              pnmtoxwd: fix endianness on littleendian machines.
+
+              pnmtoxwd: fix bug: doesn't work if you specify an option.
+              
+              jpegtopnm: fix bug: -dumpexif all wrong.
+
+              jpegtopnm: fix bug: newline written to stdout instead of stderr
+              with -dumpexif.
+
+              Add missing 'col' declaration so -msse compile works.
+
+              pcdovtoppm: fix syntax error
+
+              pnmtojbig/jbigtopnm: add newline to "usage" message.
+
+              pamperspective: fix bugs with -frame_include, etc.
+
+              build: Jpeg2k programs build properly with newer Jasper library.
+
+              build: Fix bug: "no rule to build xxx.so" with external
+              Jasper or Jbig library.
+
+              Add NAME and DESCRIPTION section to pointer man pages.
+
+04.10.16 BJH  Release 10.25
+
+              Add pcxstd.ppm.
+
+              Add pbmtoibm23xx.  Thanks Jo Fahlke <jorrit@jorrit.de>.
+
+              Add pamedge, replacement for pnmedge.
+         
+              pamperspective: add -frame_include, -*margin, -include.
+
+              pamcomp: Handle RGB_ALPHA and GRAYSCALE_ALPHA images,
+              using the opacity plane.
+              
+              pamscale: do transparency pixel mixing properly.
+
+              ppmtompeg: cleanups and additional verbosity.
+
+              anytopnm: Use 'nawk' instead of 'awk' where available.
+
+              anytopnm: Don't use -e.  (not available everywhere).
+
+              tifftopnm: Add warning when Whole Image method reduces
+              resolution to 8 bit.
+
+              pamtopnm: accept extra planes (ignore them).
+
+              pamcut: major speedup.  Thanks Prophet of the Way
+              <afu@wta.att.ne.jp> (Akira Urushibata ("Douso")).
+
+              Add pnm_getopacity().
+
+              libnetpbm: "pam" read and write routines much more
+              efficient.  Thanks Prophet of the Way
+              <afu@wta.att.ne.jp> (Akira Urushibata ("Douso")).
+
+              Add pnm_formatpamrow(), pnm_allocrowimage(),
+              pnm_freerowimage().  Thanks Thanks Prophet of the Way
+              <afu@wta.att.ne.jp> (Akira Urushibata ("Douso")).
+
+              pnm_readpamrow(): Add option to have "tuplerow" argument
+              NULL and not get the row's contents.  Thanks Thanks
+              Prophet of the Way <afu@wta.att.ne.jp> (Akira Urushibata
+              ("Douso")).
+
+              Add pnm_writepamrowmult().  Thanks Thanks Prophet of the
+              Way <afu@wta.att.ne.jp> (Akira Urushibata ("Douso")).
+
+              libnetpbm: format plain format images more nicely.
+              Thanks Thanks Prophet of the Way <afu@wta.att.ne.jp>
+              (Akira Urushibata ("Douso")).  
+
+              pamcut: fix bug: Blows up instead of failing cleanly
+              when you try to cut beyond the bounds of the image.
+              Thanks Thanks Prophet of the Way <afu@wta.att.ne.jp>
+              (Akira Urushibata ("Douso")).
+
+              fix bug: MMX/SSE fast PBM writing (with gcc -msse) all wrong.
+
+              pamscale; fix bug: -linear option sense reversed.
+
+              pamscale: fix bug: everything too dark when using resampling
+              to enlarge.
+
+              Speed up pbm_writepbmrow() (and all PBM output programs)
+              by going a byte at a time.  Thanks Prophet of the Way
+              <afu@wta.att.ne.jp>.
+
+              pamperspective: fix bug that can cause memory corruption
+              and crashes.  Thanks Mark Weyer
+              <Mark.Weyer@math.uni-freiburg.de>.
+
+              ppmtompeg: put in proper marshalling/unmarshalling so as not
+              to depend on a 32 bit integer type.
+
+              ppmtopcx: add -palette option.
+
+              pnmconvol: fix bug: green and blue convolution matrices
+              exchanged, gray exchanged with random.
+
+              ppmtowinicon: fix bug: crashes on 32 bit images.
+         
+              Use <inttypes.h> or <types.h> where possible, dynamically
+              built "inttypes.h" where not.  Eliminate uint32n.
+
+              Handle BITS_PER_WORD=64 in wordaccess.h so it builds on 
+              64 bit platforms.
+
+04.08.11 BJH  Release 10.24
+
+              g3topbm: Add -stop_error.
+
+              Add makeman to build tools.  Thanks Eric S. Raymond
+              <esr@thyrsus.com>.
+
+              pamflip: Large speed, memory improvements for
+              non-diagonal flips.  Thanks Prophet of the Way
+              <afu@wta.att.ne.jp>.
+
+              jbigtopnm, pnmtojbig: Use packed PBM functions to speed up
+              greatly.  Thanks Prophet of the Way <afu@wta.att.ne.jp>.
+
+              g3topbm: Don't fail on premature EOF; produce partial
+              output instead.
+
+              add pnm_maketuplergb(), pnm_makerowrgb(), pnm_makearrayrgb().
+
+              Fix a bunch of programs that, starting with 10.23, have too
+              large a "len" field in the pam structure, causing storage
+              overwrites.
+
+              pbmtext: fix free of unallocated storage bug with stdin
+              input text.
+
+              pnm_writepaminit(): deal correctly with pam structure that
+              doesn't contain tuple_type member.
+
+              Fix pamP->allocation_depth == 0.
+
+              pamstretch-gen: use pamscale instead of pnmscale.
+
+              Generate pm_config.h at make time.  Determine endianness
+              and word size stuff dynamically.  Use uint32n instead of
+              uint32_t in infotopam.c.
+         
+              fitstopnm: fix MIN/MAX transposition in maxval bounding.
+         
+              Fix bug: "wordn" in place of "wordint" in wordaccess.h.
+
+              Fix bug: -plain option on "pam" programs generated plain
+              header, but raw raster.
+
+04.07.17 BJH  Release 10.23
+
+              Add pamgauss.
+
+              Add pammasksharpen.
+
+              Replace pnmtoplainpnm with pnmtopnm.
+
+              pnmconvol: Add -nooffset.
+
+              pamdice: add -hoverlap, -voverlap.  Thanks Geoff
+              Kuenning <geoff@cs.hmc.edu>.
+         
+              pbmtoepson: Add -dpi, -adjacent, -noadjacent, and -protocol
+              options.
+
+              pstopnm: recognize Encapsulated Postscript by presence of 
+              "EPSF-" in the header instead of a whole specific header line.
+              Thanks Philip Hallstrom <philip.hallstrom@gilbarco.com>.
+
+              pnmpad: Allow -left/-right along with -width,
+              -bottom/-top along with -height.  Thanks David Jones 
+              <drj@pobox.com>.
+
+              pnmpad: round to nearest instead of lowest in interpretation
+              of -halign, -valign.
+
+              pnmsplit: Add -padname option.
+
+              pnmenlarge: work on multi-image streams.
+              
+              libnetpbm: add allocation_depth to PAM structure.  Add
+              maketuplergb(), makerowrgb(), makearrayrgb().
+
+              pnminvert: much faster for PBMs.  Thanks 
+              Prophet of the Way <afu@wta.att.ne.jp>.
+
+              pbmmake: use packed bits to speed up.  Thanks Prophet of
+              the Way <afu@wta.att.ne.jp> (Akira Urushibata ("Douso")).
+
+              pbmtog3: speedups.  Thanks Prophet of the Way
+              <afu@wta.att.ne.jp> (Akira Urushibata ("Douso")).
+
+              Fix gamma value (from .45 to 2.2) in pm_gamma709(), 
+              pm_ungamma709().
+
+              pgmramp: Fix signedness bug with -rectangle, -ellipse.
+
+              pnmtojpeg: fix bug with large exif files. (negative malloc)
+
+              pnmtopng: Fix estimation of whether it's better to use a
+              colormap or not.
+         
+              Add pamditherbw, replacement for pgmtopbm.
+
+              pamperspective: Fix bug.  Incorrect output.  Thanks
+              <Mark.Weyer@math.uni-freiburg.de>.
+
+              ppmtompeg: fix crash with input that is not a multiple of
+              16 pixels high and wide.
+
+              pamtotga: fix crash due to freeing nonexistent color table.
+
+              pamtotga: fix bug: invalid TGA when you use -rgb with a
+              non-color input.
+
+              pgmtopbm: fix calculations that divide by maxval+1; should
+              divide by maxval.  Thanks David Jones <drj@pobox.com>.
+
+04.05.01 BJH  Release 10.22
+
+              Add pamperspective.  Thanks Mark Weyer
+              <Mark.Weyer@math.uni-freiburg.de>.
+
+              Add pamstereogram.  Thanks Scott Pakin <scott@pakin.org>.
+
+              Add pc1toppm.  Thanks Roine Gustafsson
+              <roine@users.sourceforge.net>.
+
+              Add pbmtodjvurle.  Thanks Scott Pakin <scott@pakin.org>.
+
+              Add infotopam.  Thanks Rich Griswold <griswold@acm.org>.
+
+              Add pamtopfm, pfmtopam.
+
+              ppmtowinicon: Add -truetransparent.
+
+              ppmtowinicon: remove old, wrong, undocumented -andppm synonym
+              of -andpgm.
+
+              ppmtopcx: Add -stdpalette option.
+
+              pamsumm: Add -brief, -normalize.
+
+              All programs that interpret color specifiers (ppmchange, etc.):
+              warn if the specified color can't be represented, to the 
+              precision specified, in the operative maxval.
+
+              ppmchange: fail if the specified color can't be represented in
+              the maxval of the input image.
+
+              pcxtoppm: Imply standard palette when palette in PCX header is
+              all black.  This function apparently got lost some time since
+              it was added in 1994.
+              
+              pcxtoppm: Add the color palette to -verbose output.
+
+              ppmtopcx: Fix bug: wrong 256 color PCX output.  From
+              10.21.  Thanks Timothy Borgeaud
+              <timothy@borgeaud.freeserve.co.uk>.
+
+              Convert ppmtodjvurle into pamtodjvurle.
+
+              pngtopnm: Dump additional info about PNG with -verbose.
+
+              pngtopnm: Use Netpbm command line parser.
+
+              pbm_readpbmrowpacked(): Fix bug: ORs bits into return value
+              instead of setting them.  Affects pbmtoescp2.
+
+              ppmtopcx: Fix bug: don't add black to a palette if it's not
+              already there.
+
+              pnm_tuplehashtotable(): fix allocsize (maxsize) = 0 case.
+
+              pamflip: fix bug with random memory accesses due to
+              signed/unsigned comparison.
+
+04.03.28 BJH  Release 10.21
+
+              Add pamsumm.
+
+              Add pamsummcol.
+
+              Add pamsharpness, pamsharpmap.
+
+              Add ppmtodjvurle.  Thanks Scott Pakin <scott@pakin.org>.
+
+              pstopnm: add -dpi option.
+
+              ppmtopcx: add -planes option.
+         
+              pbmtext: add -width option.
+
+              replace pnmcomp with pamcomp, do transparency right wrt gamma.
+
+              tifftopnm: Use better CMYK->RGB algorithm: y=1-(b+k) instead
+              of y=(1-k)*(1-b) in Row By Row mode.
+
+              pbmtext/libnetpbm: Allow pbmfont fonts to have 96 characters
+              (32-128) instead of 95.
+
+              pnmtopng: fix bug with -transparent.
+
+              pcxtoppm: remove debugging footsteps.
+
+              pnmflip: pass through -plain, etc. to pamflip.
+
+              Include pm_system.h, pm_gamma.h in package.
+
+              ppmtomap: Change #/bin/sh to #!/bin/sh.
+
+              ppmtogif: Don't violate array bounds when GIF color map is
+              is larger than internal color map.
+
+              yuvsplittoppm: Fix -ccir601 option; clean up.
+
+              ppmtobmp: Fix bug: Generates invalid bits per pixel
+              (e.g. 3) when input image has small number of colors.
+              Thanks David Jones <drj@pobox.com>.
+
+              pbmtog3: fix buffer overrun when image > 1728 columns.
+              Thanks Prophet of the Way <afu@wta.att.ne.jp>.
+
+              pnmsvgalib: Correct error message - too wide -> too tall.
+
+              pnmtotiffcmyk:  Correct abs to fabs so -theta works.
+
+              sldtoppm: Correct abs to fabs.
+
+              pgmramp: Correct abs to fabs so -rectangle and -ellipse work.
+
+              pgmramp: Don't crash with -rectangle and height or width = 1.
+
+              ppmforge: Correct abs to fabs so the planet is more than
+              2 pixels wide.  Also recode much of program in high level C.
+
+              pnmtojpeg: interpret "length" field in exif data as unsigned
+              16 bit instead of signed.
+
+              pnmrotate: fix bug: always crashes in memory free
+
+              pnmrotate: remove debugging messages.
+
+              Declare _XOPEN_SOURCE=500 in programs that use strdup() so
+              C libraries that don't have _BSD_SOURCE will declare it.
+
+              Use FTELLO macro so things compile on a system that
+              doesn't have ftello.  Don't assume _LARGE_FILE_API means
+              ftello is available -- _LARGE_FILE_API is input to libc, not
+              output from it.
+
+04.01.30 BJH  Release 10.20
+
+              pamscale: derive from pnmscale.
+
+              pamscale: Add resampling/convolution code.  Thanks
+              Michael Reinelt <m.reinelt@steinhilberschwehr.at>.
+
+              pamscale: Do math in linear intensities instead of
+              gamma-adjusted.
+
+              pamscale: Add -xyfill.
+
+              pnmtopng: Remove global variables.  Solves a problem with 
+              conflicting name with libc: "gamma".
+                       
+              pbmtomda: make program identifier 'MicroDesignPCW'.  Thanks
+              John Elliott <jce@seasip.demon.co.uk>.
+
+              fitstopnm: Produce maxval 1 instead of 0 when input is all one
+              value.  Use maxval 255 when samples are floating point.
+
+              pamstretch-gen: be robust to invalid PNM input.
+
+              anytopnm: do safe temporary files.
+
+              ppmquantall: do safe temporary files.
+
+              pnmindex: do safe temporary files.
+
+              pnmmargin: do safe temporary files.
+
+              pamstretch-gen: do safe temporary files.  Thanks 
+              Stefan Nordhausen <nordhaus@informatik.hu-berlin.de>.
+              
+              tifftopnm: Do better validation of number of channels.
+              
+              libnetpbm: Add "normalized" (floating point) read/write
+              routines:  pnm_readpamrown(), pnm_writepamrown(), 
+              pnm_allocpamrown(), pnm_freepamrown().
+
+              libnetpbm: Add pm_gamma709, pm_ungamma709().
+
+              libnetpbm: rename createBlackTuple() to pnm_createBlackTuple().
+
+              pm_openr_seekable(): use TMPFILE environment variable.
+              Thanks Kai Strieder <ska@pixelboxx.de>.
+
+              libnetpbm: Fail when input maxval is 0.  This wreaks havoc
+              with such things as rescaling to a new maxval.
+
+              pnmquant: Fix bug wherein all options are rejected.
+
+              ppmshadow: Fix crash in all invocations.
+
+              pnmtopng: fix bug with array bounds violation with -alpha.
+              Usual symptom: "internal inconsistency" message.
+
+              anytopnm: Remove "function" so it works on FreeBSD.
+
+              configure: Do a better job of choosing between -R and -rpath.
+
+              configure: Add test compile for PNG, Z libraries.
+
+03.11.15 BJH  Release 10.19
+
+              ppmhist: Add -float option.
+
+              ppmhist: speed up -colorname by reading color dictionary only
+              once.
+
+              bmptopnm: fix bug with BMPs that end prematurely.
+
+              giftopnm: Properly generate alpha mask when the same
+              color is both transparent and opaque in the GIF.  Thanks
+              Karl Zilles <zilles@1969.ws>.
+
+              rgb.txt: move D65 to the end, so "white" is preferred.
+
+              xwdtopnm: change interpretation of bitmap_pad and bitmap_unit
+              to accomodate Xfree86 direct color 24 bit xwd.
+
+              pbmtextps: fix bug where intermediate file gets truncated.
+
+              pstopnm: fix bug in which Standard Input never works.
+
+              ppmtobmp: change 'char' to 'unsigned char' to work around a
+              Solaris C library bug.
+
+              ppmtobmp: handle maxval properly for maxval != 256 truecolor BMP.
+              Thanks Michael Buchholz <michael.buchholz@deu.mci.com>.
+
+              ppmquant: replace with wrapper for pnmquant, because ppmquant
+              had some really hard bug in computing the new color set.
+
+              Fix stack corruption in pXm_check() on systems that have
+              both 32 and 64 bit file offsets.
+
+              Add 'makecat' build tool and description to USERDOC of
+              using lynx -dump to get man-able documentation.
+
+              Make AIX shared library build work, with -qmkshrobj.
+
+              configure: Add intelligence to default location for zlib,
+              libpng headers.
+
+              configure: Make shared libraries work on AIX.
+
+              Add netpbm-config.
+
+              installnetpbm: don't crash on mkdir on old Perl.
+
+03.09.19 BJH  Release 10.18
+
+              Add escp2topbm, pbmtoescp2.  Thanks Ulrich Walcher
+              <u.walcher@gmx.de>
+
+              Add pbmtomatrixorbital.
+
+              Add ddbugtopbm by Russell Marks.
+
+              bmptopnm: Handle transparency, arbitrary pixel format (rgba
+              masks in BMP header).
+
+              bmptopnm: Handle top-down BMP.
+
+              bmptopnm: Handle 32 bit BMP.  Thanks Aaron Orenstein
+              <aorenste@grandvirtual.com>.
+
+              ppmtopcx: add -8bit option.  Thanks Holger Schemel
+              <holger.schemel@telefonica.de>.
+
+              ilbmtoppm: recognize (and ignore) DPPS and TINY chunks.
+
+              pbmpage: Add solid perimeter box to Pattern 1.
+
+              anytopnm: use 'file --mime' where available to determine file
+              type more robustly.
+
+              manweb: make 'webdir' a list of directories instead of just one.
+
+              pnmpsnr: fix bug: Cr and Cb interchanged.  Thanks
+              "Uwe Utecht" <uwe.utecht@bbc.co.uk>.
+
+              pnmstitch: fix crash.  Thanks "Paul" <pwilkins@lashwhip.com>.
+
+              manweb: add ability to read info files.
+
+              configure: missing lineends for Solaris.
+
+              anytopnm, ppmquantall: remove non-POSIX tail -N and egrep.
+
+              pgmtexture: Fix Measure of Correlation-2 so small values don't
+              turn into zero (replace abs() with fabs()).
+         
+              Add and use MALLOCVAR and MALLOCARRAY utility macros.
+
+              Allocate rows individually if can't get all rows in a single
+              memory block.
+
+              Remove hpcdtoppm from the main package because it isn't
+              licensed properly to be distributed on Sourceforge.
+              
+              Remove "magic" file -- the version that comes with Christos
+              Zoulas' 'file' package now contains everything that file had.
+
+              configure: put space after -o in test compiles.
+
+              Explicitly set SCRIPTS make variable to null so environment
+              variable of the same name doesn't screw things up.
+
+              Remove -DALL_SOURCE from AIX compile; define it in files that
+              require it.
+
+              Add -ffast-math option to recommended Gcc options.
+
+              stamp_date: don't use LOGNAME as a variable (on AIX, you can't).
+
+03.07.05 BJH  Release 10.17
+
+              Various code cleanups based on TRU64 compiler warnings.
+
+              Configure: print message when $OSNAME not recognized.
+
+              Fix Makefile.common so LIBS and LOADLIBES work.
+
+              Fix make file for Ppmglobe so it links the math library.
+
+              Fix bug in xxx_check(): function/prototype mismatch
+              causes bogus wrong-filesize error on systems where off_t
+              is sometimes not 64 bits.
+
+03.07.03 BJH  Release 10.16
+
+              Add ppmglobe.  Thanks Max Gensthaler <Max@Gensthaler.de>.
+
+              bmptopnm: Add ability to convert 16 bits per pixel Windows BMP.
+
+              giftopnm: add -image=all.
+
+              pnmtiff: Add -resolutionunit option.
+
+              pnmtopng: fix bugs, remove limitations with -text.
+
+              pgmabel: Declare internal subroutines 'static'.
+              
+              pstopnm: fix bug: encapsulated postscript confused with 
+              regular postscript, so all pages get overlaid as one page.
+
+              pnmrotate: Use smaller real memory working set.
+
+              winicontoppm: use standard option parser.
+
+              pnmflip: fix bug causing -r180 and -r270 to be ignored.
+              Thanks YANO Hirokuni <hyano@ya-no.com>.
+
+              configure: fix bug with missing space when using CFLAGS
+              environment variable.
+
+              Add CPPFLAGS and LOADLIBES to rules and 'configure' test
+              compiles.
+
+              Issue error message if you do 'make package' before 'make'.
+
+              Add VERSION file to package.
+
+              Use explicit library file name on a link instead of -L/-l
+              where possible.
+
+03.04.27 BJH  Release 10.15
+
+              Add pamtohtmltbl.  Thanks Alexander B. Ivanov <ssh@S-and-B.ru>.
+
+              Add pampop9.  Thanks Robert Tinsley, 
+              <http://www.thepoacher.net/contact>.
+
+              winicontoppm: Handle 24 bit direct color winicons.  Thanks
+              Lee Benfield <lee@benf.org>.
+
+              pnmrotate: add -background option.
+
+              pnmtojpeg: Add -density option.
+
+              pamtotga: Add alpha capability, via "RGBA" tuple type.
+
+              libnetpbm: Add ppm_readcolornamefile().
+
+              Make library and programs work with files > 2GB.  At least 
+              on GNU and AIX.
+
+              pbmtext: Add -momargin option.  Thanks "Slaven Rezic"
+              <eserte@web.de>.
+
+              pbmtoepsi: fix bug: non six-digit lines between 
+              %%BeginPreview: ... and %%EndImage .  Thanks
+              "K.Nakano" <ksk@seeds-man.com>.
+
+              pnmrotate: fix bug: background color computed wrong.
+              
+              pnmrotate: fix bug: PBMs without -noantialias not properly
+              promoted to PGM.
+
+              pnmtops: fix bug: 1 bit per sample output when maxval is 2
+              or 3.  Should be 4 bits per sample.
+
+              pnmtops: fix bug: everything too dark when input maxval less
+              than Postscript maxval.
+
+              pm_nextimage(): fix bug: doesn't read past white space at the
+              end of a plain-format raster.
+
+              pnmremap: fix performance bug: don't put same color into hash
+              twice.  Thanks Hannu Koivisto <Hannu.Koivisto@ionific.com>.
+
+              ppmtoxpm: Add -hexonly.  Default to system color dictionary.
+              Use standard library routines to access color dictionary.
+
+              pnmcolormap: Keep only one row at a time in memory.
+
+              bmptopnm: fix buffer overrun with wild ColorsUsed value.
+
+              Change ordering of include files in parallel.c so it compiles
+              on AIX 5.
+
+              configure: change -O2 to -O0 for broken gcc compiler.
+
+              One more chop at the -R/-rpath issue: new defaults for Solaris.
+
+03.02.13 BJH  Release 10.14
+
+              Add pamedge, adapted from pnmedge by Peter Kirchgessner, 1995.
+
+              Add ppmwheel, adapted from ppmcirc by Peter Kirchgessner, 1995.
+
+              Add ppmtoarbtxt, renamed from ppmtotxt by Peter Kirchgessner,
+              1995.
+              
+              pnmflip: add pnmflip directory to search path for pamflip.
+
+              picttoppm: Recognize DirectBitsRgn opcode.  Thanks
+              <kabe@sra-tohoku.co.jp>.
+
+              ppmbrighten: -normalize works with nonseekable input.
+
+              jpegtopnm: Update exif stuff to match Jhead 1.9.
+
+              bmptopnm: fix for the case of ColorsUsed != 0 in 
+              non-colormapped (24 bit) image.
+
+              pbmtopgm: limit the maxval to legal values.
+         
+              pamarith: minimum output maxval of 2 for -compare.
+
+              rgb.txt: remove redundant lines, clean up.
+
+              pnmgamma: fix bug in -ungamma -cieramp. 1/gamma vs gamma.
+
+              pnmtopng: fix bug: background color = transparent color in
+              colormapped image causes transparent color not to be
+              transparent.  Also make bilevel images with background color
+              use 1 bit per pixel instead of 2.
+
+              pngtopnm: Fix bug: wrong exit code.
+
+              Fix broken ISUPPER; affects sbigtopgm and color name functions
+
+              pamflip: Use bundled strsepN() instead of strsep().
+
+              giftopnm: fix bug with GIFs that (invalidly) contain translation
+              cycles.  Clean up LZW decompression so it is readable and more
+              robust.
+              
+              Add strsepN() to libnetpbm, because strsep() is not available
+              everywhere.
+
+              Change --rpath to -rpath.
+
+              Change LINKERISGCC to LINKERISCOMPILER and default to Y.
+
+              configure: Handle explicit "none" response to svgalib prompt.
+
+              Add merge.o (vs mergelist) method of doing merge build.
+         
+              Fix bug: jbig converters missing from merge build.
+
+03.01.09 BJH  Release 10.13
+
+              Add pamlookup.
+              
+              Add pamflip: replacement of pnmflip.  Does PAM images and has
+              -xform option.
+
+              Add pm_system().
+
+              Ppmrainbow:  Add -norepeat option.
+
+              tifftopnm: handle 32 bits per sample -- Use upper 16 bits.
+
+              tifftopnm: add -byrow.
+
+              pamarith:  Add -compare operator.
+
+              ppmtompeg: Check for missing YUV_SIZE option with 
+              -combine_frames and -combine_gops.
+
+              ppmtompeg: fix buffer overrun with invalid message received
+              by slave processor.
+
+              pamseq: Fix crash/memory corruption.
+
+              pamseq: Remove debugging statement.
+
+              Add some protection against Netpbm images that indicate
+              dimensions too big to process without arithmetic overflow.
+
+              Replace isdigit() with ISDIGIT(), etc.
+
+              pnmnorm: malloc histogram array instead of using stack
+              variable to avoid stack overflow.
+
+              thinkjettopbm: works with Solaris Lex.
+              
+              Add PKGDIR_DEFAULT Makefile.config variable.
+
+              Configure: Test for old JPEG library without jpeg_marker_struct.
+
+              Configure: Add -I/usr/local/include and -I/usr/local/lib to
+              OpenBSD and NetBSD compiles and links.
+
+              Change make variable from INCLUDE to INCLUDES to avoid collision
+              with Cygwin environment variable.
+
+02.11.09 BJH  Release 10.12
+              
+              Add pamtojpeg2k, jpeg2ktopam.
+
+              pnmtotiff: add test for seekable output.
+
+              pnmflip: use packed PBM format to save memory.  Thanks
+              Ivan Karski <karski@nym.alias.net>.
+
+              thinkjettopbm: replace getopt with shhopt to avoid header file
+              problem (getopt.h) on some systems.
+
+              fix multiple definition problem with pm_plain_output.
+
+              pgmminkowski, pgmmorphconv: fix bug: programs missing.  These
+              were formally added in 10.0, but the code never actually went in.
+
+              pbmtomda: fix crash with input image not multiple of 4 rows.
+
+              tgatoppm: fix segfault in option processing from recent
+              option processor change.
+
+              Slight correction to coefficients in pnm_YCbCrtuple.
+
+              Add pnm_YCbCr_to_rgbtuple() to libnetpbm.
+
+              ppmtompeg: fix build failure with NETWORKLD=NONE.
+
+              ppmtompeg: fix build error due to broken C library header files
+              on SunOS.
+
+              Fix missing alias symlinks in merge build.
+
+02.10.17 BJH  Release 10.11
+         
+              Fix nstring.h, ppmcmap.h dependency in analyzer/.
+
+              pnmtojpeg: Add -rgb option.
+
+              jpegtopnm: Handle a stream of consecutive JPEGs.
+
+              tifftopnm: use TIFFRGBAImageGet() so it works on compressed
+              images.
+
+              'make distclean' deletes all symbolic links, even if I forget
+              to put them in HEADERLINKS so that 'make clean' gets them.
+
+02.10.10 BJH  Release 10.10
+
+              Add -plain common option.  Thanks "John H. DuBois III"
+              <spcecdt@armory.com>.
+
+              ppmhist: Add -colorname option
+
+              pnmcomp: Add -align=beyondleft, -align=beyondright,
+              -valign=above, -valign=below and make -xoff and -yoff work
+              in conjunction with -align and -valign.  Idea from 
+              "John H. DuBois III" <spcecdt@armory.com>.
+
+              manweb: Add ability to search through PATH for documentation
+              so you can just say 'manweb bmptopnm'.
+
+              bmptopnm: fix bug from 10.9: 24 bit truecolor should be
+              BGR, not RGB.
+
+              ppmtobmp: fix bug from 10.9: 24 bit truecolor should be
+              BGR, not RGB.
+
+              pamarith: Fix bug with mixed depth inputs.
+         
+              pamarith: Fix bug with -subtract of larger # from smaller.
+
+              pnmstitch: fix bug causing segfault at end.
+
+              pgmtopgm, ppmtoppm: Issue error message if arguments given.
+
+              giftopnm: remove debugging messages.
+
+              Fix line number in color dictionary error messages.
+
+              Fix build errors on systems that don't do "echo -n" by using
+              cat /dev/null instead.
+
+              Fix build errors that cause the data files not to get installed.
+              
+              Merge build: fix install of other/ programs and add NETWORKLD
+              to the link.
+
+              Merge build: add missing backward compatibility 'pnmfile' link.
+
+              Add SCO OpenServer to configure dialog.  Thanks
+              John H. DuBois III <spcecdt@armory.com>
+
+              Add FreeBSD to the configure dialog.
+
+02.09.21 BJH  Release 10.9
+
+              Add pgmtopgm.
+
+              Add ppmtoppm.
+
+              Add ppmtoterm.  Thanks Ero Carrera <ecarrera@lightforge.com>.
+
+              Add ppmrough.  Thanks Eckard Specht
+              <specht@hydra.nat.uni-magdeburg.de>.
+
+              Add rgb.txt
+
+              pnmquant: make it work with pipe input; fix crash due to 
+              half-finished work accidentally released a few releases ago.
+
+              bmptopnm: Use way less memory by inverting image while still
+              in the BMP raster format (as little as 1 bit per pixel), 
+              instead of in the libnetpbm format (96 bits per pixel).
+
+              ppmhist: Reads image into memory one row at a time instead
+              of entire image in memory at once.
+
+              ppmshadow: make it work with non-PPM input.
+
+              ppmshadow: Don't run a shell for every Netpbm program.
+
+              xwdtopnm, pnmtoxwd: make them work on 64-bit-long machines.
+              
+              ppmdither: Fix subscript out of bounds bug which caused
+              random intensity values for the most intense pixels (and
+              very slightly too-intense values everywhere).
+
+              ppmtobmp: Fix bug in 24 bit truecolor: was writing B, G, R
+              but standard appears to be R, G, B (per bmptopnm and xli).
+
+              pamarith: restore pnmarith capability of having one of the
+              inputs be depth 1 while the other is not.
+
+              pnmtopclxl: fine tuning of PBM input case.
+
+              remove pnmfile; obsoleted by pamfile.
+
+              ppmfade, ppmshadow: Change #!/bin/perl to #!/usr/bin/perl.
+
+              Add a bunch of missing #include <string.h> and fix scanf type
+              mismatches, per gcc 3.2 compiler warnings.
+
+              Add some missing -R options to links.
+
+02.08.16 BJH  Release 10.8
+  
+              Build: Fix Configure bug with JPEG headers in default path.
+
+              Pnmtopclxl: fix bug with PBM input not multiple of 8 across.
+
+02.08.15 BJH  Release 10.7
+
+              Pnmtopclxl: remove padBytesMultiple attribute, which doesn't
+              exist on old printers.
+
+              Pnmtopclxl: fix MediaDestination parameter value.
+
+              Pnmtopclxl: make options parsed by standard Netpbm
+              option parser; remove unconventional (and broken)
+              --output option.  Fix crash with Standard Input input.
+              Check whole length given for option values.
+
+              pnmflip: Add -memsize and -pagesize.  Make other options
+              conventional - only one transformation allowed.
+
+              Make 'configure' a Bourne Shell program that calls configure.pl.
+
+              Add pnmstitch.  Thanks Mark Salyzyn <salyzyn@cfl.rr.com>.
+
+              Fix -R stuff to fix Solaris build.
+              
+              Add lots of consts, remove dead code, with the help of 
+              GNU compiler warnings.
+
+02.07.29 BJH  Release 10.6
+
+              Add pnmtopclxl.  Thanks Jochen Karrer
+              <cip307@cip.physik.uni-wuerzburg.de>.
+
+              Add pnmstitch code; not working yet.
+
+              pnmmontage: add -data option.  Thanks Ben
+              <bem@mad.scientist.com>.
+
+              pnmtotga: put "image ID" in TGA output.
+
+              ppmtotga: Correct name to pnmtotga.
+
+              pnmcomp:  Add -opacity option.
+
+              pbmtog3: Add -nofixedwidth option.  Vladimir Nadvornik
+              <nadvornik@suse.cz>
+
+              ppmtopgm: upgrade to handle multi-image PPM file.
+
+              icontopbm: fix bugs with invalid input formats.  Thanks
+              Vladimir Nadvornik <nadvornik@suse.cz>
+
+              ppmtompeg: fix crash with logarithmic psearch algorithm and
+              non-power-of-2 search range.
+
+              configure: fix use of compiler other than cc.  Thanks
+              Ben <bem@mad.scientist.com>.
+
+              Fix Cygwin library build for default DLLVER.
+
+              Fix file handle problem with Configure on old Perl.
+
+              Don't use .defs file/run dlltool (Windows).
+
+02.07.14 BJH  Release 10.5
+         
+              Make library link on Solaris with GNU Ld work.  Thanks
+              Russel Winder <russel@russel.org.uk>.
+
+              Make Configure not depend on File::Temp.
+
+02.07.09 BJH  Release 10.4
+
+              Fix space after -I bug in Makefile.common, lib/Makefile.
+
+              Change a bunch of macros to upper case; avoid name collision
+              of round() with math library.
+
+              Configure detects broken gcc, sets -O2.
+
+              Configure detects jpeg headers not in default search path.
+
+02.07.02 BJH  Release 10.3
+
+              Add pamfunc.
+
+              Add pgmabel.  Thanks Volker Schmidt <lefti@voyager.boerde.de>.
+
+              pamslice: add -xmgr option.
+                       
+              pgmslice: upgrade to pamslice.
+         
+              pamarith: add -mean option.
+
+              pnmarith: upgrade to pamarith.
+
+              pnmtopng: add -palette option.  Functional copy from
+              "Pnmtopng" package.
+
+              pbmtoepsi: Add -dpi option.  Thanks Bill Cheng
+              <bill.cheng@acm.org>
+
+              tifftopnm: make -respectfillorder work.
+
+              pnmtopng: fix bugs in handling of text comment files.
+              Taken from "Pnmtopng" package.
+
+              pnmtopng: fix -hist option.
+
+              Cygwin build accomodations.  Thanks Charles Wilson 
+              <cwilson@ece.gatech.edu>.
+
+              Fix ppmtompeg build failure when JPEGLIB = NONE
+
+              Fix missing -R link options.
+
+02.06.14 BJH  Release 10.2
+         
+              Works with separate build directory.
+
+              pnmnorm: fix precision error causes whites to become black
+
+              Fix bug: Pbmpage needs math library
+
+02.06.07 BJH  Release 10.1
+
+              ppmmake: add -maxval option
+
+              pgmramp: add -maxval option
+
+              Add pamseq.
+
+              ppmcolors: base on pamseq.
+
+              pnmtotiff: Fix bug in -indexbits.
+
+              Fix runtime library paths for Darwin, Netbsd
+
+              installnetpbm: Fix infinite loop when you refuse manweb.conf.
+
+              installnetpbm: Add mode to mkdir for older Perl.
+
+              installnetpbm: Use BSD option on Cp if not GNU.
+
+02.06.01 BJH  Release 10.0
+         
+              Man pages replaced by web pages -- either private copies
+              or straight from Netpbm Central.  Documentation distributed
+              via Worldwide Web, not in the Netpbm source code tarball.
+
+              Reorganize source tree.  Combine 4 Netpbm libraries into 1.
+              Make merge build merge ALL programs into one.
+
+              Add pamendian.
+
+              Add pamstack.
+
+              Add pamtohdiff, hdifftopam.
+         
+              Add pbmtextps.  Thanks James McCann <jmccann@eskimo.com>.
+
+              Add pgmminkowski, pgmmorphconv.  Thanks Luuk van Dijk
+              <lvd@mndmttr.nl>
+
+              pnmtotiff: Add -indexbits option to allow fewer than 8 bits
+              per sample in apletted image.  Thanks Thomas Henlich.
+              <http://sourceforge.net/users/thenlich/>.
+
+              pnmtotiff: put zeroes in unused colormap entries.  Thanks
+              Thomas Henlich <http://sourceforge.net/users/thenlich/>.
+         
+              pbmtext:  Add -lspace option.  Thanks Denis <denism@cyberus.ca>.
+
+              ppmqvga: remove; appears to be obsoleted by Pnmquant.
+
+              pnmtops: remove setpagedevice by default.  Add -setpage option.
+
+              ppmtouil: rename to pamtouil, add transparency.
+
+              pamchannel: Add -tupletype option.
+
+              pstopnm: Add a showpage in case the EPS source doesn't have it.
+              Thanks "J. Milgram" <milgram@cgpp.com>.
+
+              pstopnm: Run Ghostscript directly instead of via a shell.
+              Some Windows environments don't have a Bourne-compatible shell.
+
+              pnmtotiff: Don't put bogus colormap in grayscale TIFF.
+              Thanks Thomas Henlich  <http://sourceforge.net/users/thenlich/>.
+
+              picttoppm, ppmtompeg, rletoppm: fix bug with invalid parameter
+              ("b" modes) on popen().
+
+              pnmtopng: fix bug with 'mayscale'.
+
+              pnmnorm: Fix bug with bvalue == 0.  Thanks "Blake, Chris" 
+              <chris.blake@windriver.com>
+
+              pnmtopng: restructure palette generation to make it
+              easier to read.
+
+              configure: Add --help.
+
+              Add Windows DLL versioning.  Thanks Charles Wilson 
+              <cwilson@ece.gatech.edu>.
+         
+              Add Nstring library, so Netpbm programs can use asprintf(), etc.
+
+02.03.15 BJH  Release 9.25
+
+              Add pamdice.
+         
+              pnmgamma: add -srgbramp.
+
+              ppmnorm, pgmnorm: replace with pnmnorm
+
+              ppmnorm: Read only a row at a time into memory.
+         
+              ppmnorm: Add -keephues option.
+
+              pnmtotiff: Add -miniswhite option.
+
+              pnmtopsnr: Read only a row at a time into memory.
+
+              Convert bmptoppm to bmptopnm.
+
+              ppmtopcx: Add -xpos, -ypos options.
+
+              pcxtoppm: Don't require seekable input.
+
+              pcxtoppm: Improve messages; add some PCX header validation.
+
+              ppmtowinicon: Use standard alpha mask (PGM image) instead of
+              PPM image (black/not black) for and file.
+
+              pnmpad: Add -width, -height, -halign, -valign options.
+              Thanks M. van Beilen <mvb@iradis.org>.
+
+              ppmhist: remove 100,000 color limit.
+
+              pnmnlfilt: works with multi-image streams.  Thanks 
+              "Steven M. Schultz" <sms@2BSD.COM>.
+
+              pcxtoppm: Fix bug with negative image position.
+
+              pnmtopsnr: fix unsigned integer bug giving wrong results on PGMs.
+
+              libpnm: fix bug: PAM functions write 16 bit samples wrong.
+
+              Tifftopnm: fix bug: transparency mask all wrong.  Thanks
+              Heikki Suonsivu <hsu@bbnetworks.net>.
+
+              Makefile improvements for cross compiling.  Thanks
+              "Maciej W. Rozycki" <macro@ds2.pg.gda.pl>.
+ 
+              Allow build with existing JBIG library instead of the
+              bundled one.  Thanks "Maciej W. Rozycki"
+              <macro@ds2.pg.gda.pl>.
+
+              ppmtompeg Makefile: include -R option where needed.
+              Thanks Hans Werner Strube <strube@physik3.gwdg.de>.
+
+              Add BSD/OS to configure program.  
+              Thanks seebs@plethora.net (Peter Seebach)
+
+              install uses supplied install.sh instead of an 'install program
+              it finds on your system.
+
+              configure: Fix missing newline bug for IRIX.
+
+02.01.27 BJH  Release 9.24
+
+              Add ppmsvgalib.
+              
+              pnmremap: Use full color resolution of input instead of that
+              of color map to do Floyd-Steinberg dithering.
+         
+              pnmremap: fix bug where Floyd-Steinberg dithering of very 
+              dark areas creates spots of maximal brightness.
+
+              ppmquant: fix same bug as pnmremap.
+
+              pnmscale: add -nomix option
+
+              rawtopgm: fix bug.  Thanks Joe Krahn <krahn@niehs.nih.gov>.
+              
+              pnmquant: replace with a Perl program that calls
+              pnmcolormap and pnmremap.
+
+              Fix bug: PAM library routines fail to read 2-byte samples.
+         
+              fix bug: install-dev doesn't install mappam.h.
+
+              configure sets up install paths.
+
+              make install does Ldconfig where required.
+         
+              pnmtops: Add "XxY" form of -dpi option.  Thanks Vladimir 
+              Nadvornik <nadvornik@suse.cz>
+
+              ppmtomap: rewrite to call the new pnmcolormap.
+
+              pnmscale: clean up code a little; now it appears to produce
+              somewhat better results -- don't know why.
+
+              exif.h: remove C++ comments
+
+              various fixes to please Irix compiler.
+
+              Change some "long" to "uint32n" so it works on 64 bit 
+              machines.
+
+02.01.04 BJH  Release 9.23
+
+              tifftopnm: fix bug from 9.22: always crashes.
+
+              xpmtoppm: fix bug from 9.21: XPM 3 files not recognized because
+              /* XPM */ comment read twice.
+                         
+              xwdtopnm: Use bitmap_pad instead of bitmap_unit on X11 pixmap
+              files with depth > 1.  Thanks "Ben Kelley" <ben_kelley@ubsw.com>.
+
+              pnmtopalm: fix bug: crashes with -colormap and non-color input.
+
+              ppmforge: fix bug: array subscripts out of bounds.  Thanks
+              Rob Menke <robmenke@mac.com>.
+
+              giftopnm: fix bug with extraneous "bogus character 0x00" message
+
+              tifftopnm: add -respectfillorder option.  Ignore FILLORDER
+              by default.
+
+              make install-dev: install shared library stub files (symlinks).
+              
+              ppmtogif: Fix bug: color resolution in wrong bits in logical
+              screen descriptor.  Thanks Elmue <elmue@gmx.de>.
+
+              Add -lz to Tiff programs; libtiff requires it now.
+
+              Add ppmcolors.
+
+              Add pnmremap.
+
+              Add pnmcolormap.
+
+              pnmtops: Include setpagedevice directive in Postscript output.
+              Thanks A Braunsdorf <ab@eas.purdue.edu>.
+
+              pnmtops: turn-to-fit logic works properly where paper is 
+              wider than tall.  Thanks A Braunsdorf <ab@eas.purdue.edu>.
+
+              giftopnm: cleanup
+         
+01.12.10 BJH  Release 9.22
+
+              Fix bug: pnm_luminosity_factor must be "extern" in pam.h
+
+              pbmtonokia: can create picture messages.  -txt, -net options.
+
+              tifftopnm: Accept invalid FILLORDER value - assume msb-to-lsb.
+
+01.12.01 BJH  Release 9.21
+
+              Add pamdeinterlace.
+
+              Add pnmquant.
+
+              pnmtops: Accept maxval > 255 input.
+         
+              Rename pnminterp to the more informative pnmstretch.
+
+              pnmstretch: convert from pnm to pam - pamstretch.
+
+              pamstretch: add -xscale, -yscale options
+
+              pbmtolj: Add -copies option
+
+              ppmtogif: Add secret -h option to make Latex2html configure
+              program work.
+
+              ppmquant: output maxval controlled by colormap maxval,
+              not input maxval.
+
+              pbmclean: Add -black and -white options.  Thanks
+              Michael Sternberg <sternberg@phys.uni-paderborn.de>.
+
+              xpmtoppm: Don't require input to start with a comment.
+              Thanks MINAMI <minami@chem.s.u-tokyo.ac.jp>.
+
+              xpmtoppm: Fix bug introduced with 9.20 with XPM 1 files.
+
+              pnmcut, pamcut: fix bug with cut that begins past right edge
+              of image.
+         
+              pamcut: Same fix as in pnmcut in 9.9.  Also fix black fill.  
+
+              pnmtotiff:  Process one row at a time instead of reading
+              entire image into memory.  Add -color, -truecolor.
+
+01.10.05 BJH  Release 9.20
+
+              ppmtogif: Add -nolzw option.
+
+              ppmntsc: do multiple images
+
+              pnmtojpeg: fix bug from 9.19 causing crash on most images.
+
+              ilbmtoppm: Correct cmyk -> rgb transformation.
+
+              ppmtompeg: Fix bug due to missing "breaks".  Thanks
+              Wolfgang Mueller <muellerw@pc7143.unige.ch>    
+
+              eyuvtoppm: fix memory leak, speed up by allocating working
+              storage only once.
+
+              libopt.c: replace strtok_r() with strtok() so it compiles on
+              Mac OS X.
+         
+01.09.18 BJH  Release 9.19
+
+              pnmtotiff: fix bug from 9.17 causing compile to fail
+              with old Tiff library
+
+              pnmtojpeg: Add -exif option.
+
+              ppmtojpeg: Change to pnmtojpeg; PBM/PGM input causes grayscale
+              output.
+
+              pnmtotiff: Handle LSB2MSB FILLORDER.
+
+              pnmtotiff: Fix -msb2lsb, -lsb2msb.  Used to do nothing but 
+              set FILLORDER tag value.
+
+              Make maxval of a PBM image read as a PGM image 255 instead of 1.
+              
+              libpbm: eliminate external data symbol pm_show_version
+              so it works with Windows.
+
+              Use subplatform-specific prefixes on Windows shared libraries.
+              Thanks Charles Wilson <cwilson@ece.gatech.edu>.
+
+01.09.04 BJH  Release 9.18
+
+              Fix bug from 9.17: -lm now needed in jpegtopnm link.
+
+              jpegtopnm: Add -dumpexif and -exif options.
+
+              pnmtotiff: Fix bug: 16 bit samples byte-swapped on little-
+              endian machines.
+
+              Remove C++ comments from exif.c
+
+01.09.03 BJH  Release 9.17
+
+              Add ppmtoneo, neotoppm.  Thanks Teemu Hukkanen <tjhukkan@iki.fi>.
+
+              pnmtotiff:  Add -flate, -adobeflate.  Thanks Peter Creath.
+
+              pnmindex: Fix bug with -size option.  Thanks 
+              André Majorel <amajorel@teaser.fr>
+
+              pnmflip: Fix memory leak, plus make special case for PBM
+              images that uses 12 times less memory.  Thanks
+              Stefan Roellin <stefan.roellin@switzerland.org>.
+          
+              tifftopnm: Fix incorrect interpretation of FILLORDER tag
+              causing incorrect output with 16 bit samples and
+              FILLORDER not msb2lsb.  Thanks Don Reid
+              <donr@cvs.agilent.com>.
+
+              tifftopnm: Fix PHOTOMETRIC_MIN_IS_WHITE case.
+              Thanks Don Reid <donr@cvs.agilent.com>.
+
+              giftopnm: Fix bug with malformed GIFs that end prematurely.
+
+              pbmtonokia: Convert C++ style comments to C style so Sun C
+              compiler can handle them.  Thanks Jon Parise <jon@csh.rit.edu>.
+
+              Install development package (header files, static libraries)
+              by default.  Add ability to build both static and shared
+              libraries.
+
+              Add merge/nonmerge to Makefile.config/configure, in place
+              of special make targets.
+
+              Fix Mac OSX build for gcc 3.0.  Thanks m-sekino@mb.kcom.ne.jp 
+              (Masanori Sekino)
+
+              Fix make install.hdr.  Thanks m-sekino@mb.kcom.ne.jp 
+              (Masanori Sekino)
+
+01.07.24 BJH  Release 9.16
+
+              Builds on Darwin/Mac OS.  Thanks m-sekino@mb.kcom.ne.jp 
+              (Masanori Sekino)
+
+              Add pbmtopsg3.  (Postscript G3 fax) Thanks Kristof Koehler 
+              <kristof@fachschaft.physik.uni-karlsruhe.de>
+
+              Add pbmtonokia.  Thanks Tim.Ruehsen@OpenMediaSystem.de.
+
+              Add ppmrainbow.  Thanks Arjen Bax <arjen.bax@cmg.nl>.
+
+              Add pamoil, as replacement for pgmoil.  Based on ppmoil by
+              Chris <cesheppa@midnightsun.uwaterloo.ca>.
+
+              ppmtogif:  Add -alpha option.
+
+              ppmtojpeg: fix bug: option takes as input filename.
+
+              xpmtoppm: fix bug: Bogus EOF failure.  Thanks 
+              "Dr. Larry Pyeatt" <pyeatt@redwood.cs.ttu.edu>.
+              
+              pnmtopng/pngtopnm: update to Release 2.37.5 of the pnmtopng
+              package.  Make -transparent option work like ppmtogif --
+              you can add an "=" before the color to specify "exact".
+              fix bug with background color and maxval = 65535.  Fix a 
+              raft of bugs in pnmtopng from Netpbm 9.15.
+
+              Remove Tiff library from the package.
+
+              Proper naming of AIX and HP-UX shared libraries.
+
+              giftopnm: Add color index to transparency message.
+
+              Add some -R link options in make files.
+
+              ppm3d: fix bug: ignores offset argument.
+
+              tifftopnm: fix bug: shifted right one pixel.
+         
+              pnmshear: remove sscanf so it works better with Cygwin,
+              convert to shhopt, clean up.
+
+              pgmcrater: Remove black vertical line at right
+              edge. Make craters wrap around the image (enables tiling
+              of image).  Thanks Arjen Bax.
+              
+              pgmtopbm: remove sscanf so it works better with Cygwin.
+              Thanks Arjen Bax.
+
+              Treat all white space, including DOS carriage returns,    
+              in color name db file as white space;  Thanks Arjen Bax.
+
+01.06.24 BJH  Release 9.15
+
+              Change format specs to state that sample values are
+              nonlinear instead of linear.
+
+              pnmtopalm/palmtopnm: Handle PalmOS 4.0 16 bit direct
+              color format.  Thanks  Bill Janssen  <janssen@parc.xerox.com>.
+
+              pnmgamma: Add CIE Rec 709 gamma correction option.
+
+              pnmgamma: Add -ungamma option
+              
+              ppmcie: Add -rec709 color system option as default so
+              that it produces true PPM output (per the new spec) and
+              carefully document what the program generates.
+              
+              ppmcie (work done by Andrew Hamilton
+              <Andrew.Hamilton@colorado.edu> in May 1999 but not
+              distributed):
+
+                 1. Corrected the XYZ -> RGB transformation;
+                 2. Eliminated the -interpwp option, a fudge which had been
+                    necessitated by the incorrect XYZ -> RGB transformation;
+                 3. Added a Gamma correction, missing in the original;
+                 4. Added the option to plot u' v' chromaticity instead of x y;
+                 5. Added options to suppress labels and/or axes.
+
+              pnmscale, pnmscalefixed:  Fix bug with unwanted -verbose
+              behavior.
+
+              pnmscale, pnmscalefixed: Add -reduce option.  Thanks
+              Christopher W. Boyd <cboyd@pobox.com>
+
+              ppmtoeyuv: Speed up by not freeing/reallocating storage for
+              each image.  Thanks "Steven M. Schultz" <sms@moe.2bsd.com>
+
+              ppmtoeyuv: Fix memory leak.
+
+              Eliminate pgm_pbmmaxval, ppm_pbmmaxval, and pnm_pbmmaxval
+              from libpgm, libppm, and libpnm.  'maxval' argument to 
+              pgm_readpgmrow() and ppm_readppmrow() already accomplishes
+              this.  pnm_readpnmrow() changed to do same.  external data
+              symbols like this do not work with Mingw.
+         
+              various changes to accomodate Mingw (native Windows)
+              and DLLs with Cygwin (Windows).
+
+              eyuvtoppm: rewrite.  Uses Netpbm libraries now.
+
+              pstopnm: Remove gratuitous 5% margin.  Add -equalpixels,
+              -imageheight, and -imagewidth options.
+
+              pnmtopng: Read only one row at a time, not the whole image,
+              into memory.  Do multiple passes through the (hopefully
+              fully cached) file.  This saves memory because pnmtopng's
+              internal format may use 96 times as much space per pixel 
+              as the file.
+
+              libpbm: Add pm_openr_seekable(), pm_seek(), pm_tell().
+
+              libppm: Add ppm_computecolorhash2(), ppm_computecolorhist2().
+
+              configure: fix bug with Solaris/Sun compiler.
+
+              Split pbmplus.h into pm_config.h and pbm/pm.h to prepare
+              for automatic generation of pm_config.h.  Move pm_*()
+              subroutines from libpbm1.c to new libpm.c for clarity.
+
+              Remove malloc.h from 411toppm.  malloc is included by
+              pm_config.h, in platform-dependent way.
+
+01.05.13 BJH  Release 9.14
+
+              fix bug in shhopt causing wild branches.
+
+              winicontoppm: Make alpha mask ("and") output PBM instead
+              of PPM; correct sense so white is opaque, black transparent.
+
+              ppmtoxpm: Add --alphamask option.  Remove 256 color limit.
+         
+              xpmtoppm: Add --alphaout option
+         
+              ppmtogif: Add "=" to -transparent option to specify exact
+              color.
+
+              configure: change /bin/perl to /usr/bin/perl.  Red Hat Linux
+              doesn't have /bin/perl.
+
+              pnm/Makefile: correct bugs in link.  Thanks J Scott Berg 
+              <jsberg@bnl.gov>
+
+              Make it build on Unixware.
+
+              Make it build on OpenBSD.
+
+              document color indexing and histogram functions.
+
+01.04.22 BJH  Release 9.13
+
+              ppmtogif: fix divide by zero
+
+              move much of README file into a new master man page, netpbm.1.
+
+              pnmcomp: add -align and -valign options.
+              
+              Add thinkjettopbm.  Thanks Eric Norum <eric.norum@usask.ca>.
+
+              Builds with Sun compiler.
+
+01.03.25 BJH  Release 9.12
+
+              Add 411toppm.  Thanks Steve Allen <sla@alumni.caltech.edu>.
+
+              pnmcrop:  add -sides option (does what was default
+              behavior until July 2000)
+
+              ppmtoeyuv: Use netpbm libraries, deal with multi-image
+              input.
+
+              Add color row color map and Floyd-Steinberg functions from
+              ilbm package to libppm.
+
+              ilbmtoppm, ppmtoilbm: replace with Ingo Wilken's version from
+              April 13, 1995.
+
+              jpegtopnm: fix crash with B&W JPEG input.
+
+              pnmcomp: fix bug: invalid output image when overlay image
+              is "higher" format than underlying image.
+
+              ppmchange: fix bug: wouldn't work with maxval != 255.
+
+              pnmtojbig: fix bug: it always says invalid maxval. 
+              Thanks Bill Davidsen.
+
+              pbmtext: fix bug: output is sometimes "not implemented"
+              because of uninitialized variable.
+
+              pnmpsnr: rewritten so as not to use Fiasco library.
+              Correctly handles maxval != 255.  Works with PAM.
+
+              Add IRIX to the configure program.  Thanks Neil Franklin 
+              <franklin@arch.ethz.ch>.
+
+              make files: add ZLIB_DIR and ZHDR_DIR variables to
+              Makefile.config.  Thanks Bill Janssen.  
+              <janssen@parc.xerox.com>
+
+              pcdindex: convert from csh to sh.  Thanks Steve McIntyre.
+
+01.02.20 BJH  Release 9.11
+
+              pnmtopalm: Handle 16 bit direct color format.
+
+              pnmtotiff: Add -xresolution and -yresolution options.
+              Thanks Tim Ruehsen.
+
+              Add error message to GNUMakefile for when GNU Make is too old.
+
+              pnmpsnr: fix bug: PGM files always compared equal.
+
+              libpbm: improve --version to reflect that libraries are
+              usually dynamically linked now, so it's the version of
+              libpbm, not the program itself.
+
+              pbmtext: add -space option.
+
+              asciitopgm: fix crash due to memcpy that should be memset.
+              Thanks Philipp Knirsch <pknirsch@redhat.com>.
+
+              pktopbm: fix numerous bugs.  Copied from Red Hat.  By
+              jcn <janneke@gnu.org> 1998.09.22.
+
+              pstopnm.csh: fix bomb when xres=yres.  This program is
+              obsolete (use pstopnm.c instead), but the patch was free,
+              so why not?  Copied from Red Hat.  By Nalin Dahyabhai
+              <nalin@redhat.com> 2000.02.14.
+
+              pnmtotiff: Make photometric for G3/G4 fax MINISWHITE 
+              instead of MINISBLACK.  Add -minisblack option.  Thanks
+              Eric Smith <eric@brouhaha.com>.
+
+              libppm/ppm_parsecolor: fix rounding error for very small
+              maxvals (e.g. PBM files -- maxval = 1).
+         
+              pnmtopalm, palmtopnm: minor updates
+              
+              bmptoppm: handle BMPs that use ColorsUsed instead of
+              bits per pixel to determine color map size.
+
+              bmptoppm: add -verbose option.
+
+              ppmquant: fix bug with maxval > 255 causing arithmetic
+              overflow and arbitrary colors in output.
+
+01.01.10 BJH  Release 9.10
+
+              giftopnm: add -alphaout.
+
+              ppmchange: Add -remainder option.
+
+              Add pnmtopalm, palmtopnm.  Thanks Bill Janssen 
+              <bill@janssen.org>.
+
+              Add pnmmontage.  Thanks Ben Olmstead <bem@mad.scientist.com>.
+
+              ppmtogif: Add -comment option.
+
+              ppmtogif: fix bug: created GIF89 when it should have created
+              GIF87.
+
+              giftopnm: fix bug in displaying of comment extensions.
+
+              jpegtopnm: Add -comments option.
+
+              ppmtojpeg: Add -comment option.
+
+              ppmtompeg: fix crash.  Thanks  Roger Southwick 
+              <rogers@mediaforte.com>
+
+              More work on separating source and build directories.
+              GNU Make 3.77 or better now required.
+
+00.11.20 BJH  Release 9.9
+
+              add ppmtolj: color HP Laserjet graphics (PCL).  Thanks
+              Jonathan.Melvin@heywood.co.uk.
+
+              ppmfade: add -mix option.
+
+              ppmhist: add -noheader option.
+
+              rawtopgm: Add --bpp, --maxval, and --littleendian options.
+
+              pnmindex: Add -noquant, -title options.
+
+              pnmtotiff: create 16 bits-per-sample Tiff files when 
+              maxval > 255 instead of crash.
+
+              tifftopnm: handle 16 bits-per-sample Tiff files.
+
+              pnmscale: Use floating point arithmetic instead of 12 bit
+              fixed point to reduce distortion at right and bottom edge of
+              large images with weird scaling factors.  Add pnmscalefixed,
+              which is the old fixed point pnmscale, which goes faster.  But
+              also fix bug in the fixed point version so the distortion 
+              isn't as bad.  Add -verbose option.
+
+              pnmcut: fix bug: right edge wrong or subscript out of bounds.
+              Thanks MURAKAMI Masahiko <muramasa@np.catv.ne.jp>,
+              Sven Over <sven.over@web.de>, Frederic Vivien 
+              <vivien@lcs.mit.edu>, Pete Weisz <pete@pw34.resnet.cornell.edu>.
+
+              xpmtoppm: fix reading of 4-character color codes; handles
+              "NONE" color (transparent background); add -v.  Thanks
+              Martin Vermeer <martin.vermeer@hut.fi>.
+
+              ppm/Makefile: fix bug: ppmtompeg, hpcdtoppm not installed.
+              Thanks Mike Castle <dalgoda@ix.netcom.com>.
+
+              ppmpspread: fix bug: invalid memory reference crash.  Thanks
+              digger <jfk666@poczta.onet.pl>, john joseph iii casey 
+              <jjcasey@midway.uchicago.edu>.
+
+              ppmchange: fix bug: every other argument pair ignored.
+              Thanks Sven Over <sven.over@web.de>.
+
+              Fix error message in pXmmerge (no pm_error()).  Thanks
+              Pete Weisz <pete@pw34.resnet.cornell.edu>, 
+              <g031w503@soft.iwate-pu.ac.jp>
+
+              libppm: add /usr/X11R6/lib/X11/rgb.txt as default rgb.txt file.
+              Thanks ceder@lysator.liu.se (Per Cederqvist).
+
+              Replace hpcdtoppm version 0.3 (1992) with 0.6 (1994).  Add
+              pcdindex.
+
+              Build fixes for Tru64.  Thanks Phil Benchoff <benchoff@vt.edu>.
+
+              Makefile.config: option to not strip binaries on install.
+
+00.09.01 BJH  Release 9.8
+
+              jpegtopnm: Add -adobe, -notadobe options.
+
+              ppmchange:  Add -closeness.
+
+              giftopnm: report transparency information.
+
+              jpegtopnm: Accept single-hyphen options.
+
+              Make it build on DJGPP.
+
+00.08.12 BJH  Release 9.7
+
+              Add PAM format, Pamchannel, Pamtopnm.
+
+              sgitopnm: add -channel option to access more than 3 channels.
+              Thanks Smarasderagd.
+         
+              pnmcut: Add -pad option
+
+              ppmtobmp: change default from -os2 to -windows.
+
+              pnmcut: fix bug: height argument misinterpreted.
+
+              ppmquantall: fix bug: don't crop white borders off images
+
+              yuvtoppm: fix bugs: don't depend on bigendian representation
+              of integers.  reject odd-width images.
+
+              ppmtoyuv: fix bug: reject odd-width images.
+
+              anytopnm: fix bug: required nonstandard shell feature.
+
+              giftopnm: fix bug: when image consists of two gray colors,
+              program converted them to black or white and created PBM
+              file.  Thanks Smarasderagd.  Also fix memory leak.
+
+              Make changes since 9.2 build on Cygwin.
+
+00.07.12 BJH  Release 9.6
+
+              Add pnmtofiasco, fiascotopnm, psnpsnr.  Thanks to Ullrich
+              Hafner.
+
+              Make Pnmcrop use a temporary file instead of huge amounts
+              of memory.
+
+              bmptoppm: fix crash when OS/2 BMP file has > 8 bits per pixel.
+
+              anytopnm: fix bug (from 9.0) wherein program almost
+              always crashed.  Add gzip, bzip, bzip2 capability.
+              Thanks Charles Howes.
+
+              parallel.c: Work around SunOS libc problem.
+
+              Define some newer libtiff macros so it compiles against
+              older libtiff.
+
+00.07.01 BJH  Release 9.5
+
+              Extend formats to allow multiple images per file.  Add
+              -allimages option to pnmfile.  Add pnmsplit.  Extend
+              pnmtops.
+
+              Add pbmtowbmp, wbmptopbm.  Thanks Terje Sannum.
+
+              ppmtobmp: Add 24 bit (truecolor) capability.  Remove Release 8.3
+              colormap size update.
+
+              pnmcut: easier, more expressive syntax for specifying what
+              to cut.
+
+              pnminterp: handle maxval != 255, convert to use Netpbm
+              libraries.
+
+              Translate pstopnm from Csh to C, eliminate dependency
+              on the 'bc' program.
+
+              Fix bug in tiff library build which caused null soname.
+
+              Add optParseopt2() to shhopt.a so you can use long options
+              with one hyphen instead of two.
+
+              giftopnm: fix bug with variable used before set.  Fix bug
+              (coding error -- 0=>i).  Unknown impact.
+
+              bmptoppm: fix bug with 24 bit (truecolor) Windows BMPs.
+
+              xwdtopnm: fix one more bug with bits_per_item <> bits_per_pixel.
+
+              Fix ppmtompeg build bug with static libraries.
+
+              Make build clean with gcc -ansi (albeit with some files
+              that need extensions declaring the _BSD_SOURCE etc.).
+
+00.06.04 BJH  Release 9.4
+
+              Rebase pnmtopng, pngtopnm on Pnmtopng 2.37.4.
+
+              pbmtolj: fix bug where blank lines get discarded.  Thanks
+              Charles Howes.
+
+              ppmtompeg: fix bug with "PNM" format files with maxval != 255.
+              Get rid of built-in PPM file parsing.
+
+              Fix 'make install' where mkinstalldirs is not found.
+
+              Fix bug where rle_global.c would not compile with GNU C 
+              Library 2.
+
+00.06.01 BJH  Release 9.3
+
+              pnmfile: check for file size error.
+
+              ppmhist: handle larger maxvals, image sizes
+
+              xwdtopnm: Use color map with DirectColor to fix wrong color
+              bug on some DirectColor xwds.
+
+              xwdtopnm: Correct bug with 24/32 DirectColor LSBfirst xwds.
+
+              Add pXmcheck() library function.
+
+              Add leaftoppm and ppmtoleaf.  Thanks Bill O'Donnell.
+
+              Add winicontoppm and ppmtowinicon.  Thanks Lee Benfield 
+              <lee@benf.org>.
+
+              Add pgmslice.  Thanks to Jos Dingjan.
+              
+              Include subset of RLE library in the package.
+
+              stamp-date doesn't rely on whoami.
+
+              Make file fixes to accomodate more install programs.
+         
+              Replace tmpnam() with mkstemp().
+
+              Add pXm_init() into all programs that didn't have it.
+
+00.05.15 BJH  Release 9.2
+
+              Shared libraries now build properly for Solaris, SunOS, NetBSD.
+
+              Add jbigtopnm, pnmtojbig.  Thanks to Markus Kuhn.
+
+              Add pnminterp, pnminterp-gen by Russell Marks.
+
+              Add pbm_writepbmrow_packed() and pbm_readpbmrow_packed() to
+              libpbm.
+
+              ppmdither: fix bug with input maxval != 255.  Make output
+              maxval the LCM of the requested numbers of primary levels.
+
+              xwdtopnm: works with files where there aren't an integeral
+              number of pixels per storage unit (e.g. 24 bits per pixel)
+              
+              Add some missing pXm_init()
+
+              Make pXmmerge source code automatically generated.
+
+00.05.06 BJH  Release 9.1
+
+              Add pbmtoppa (renamed from pbm2ppa, renamed from print-pbm).
+              Thanks Tim Norman.
+
+              Add pbmpage.  Thanks Tim Norman.
+
+              Add pbmtomda, mdatopbm.  Thanks John Elliott.
+
+              Replace gemtopbm with gemtopnm.  Thanks to John Elliott.
+
+              Add ppmntsc: change colors to those acceptable for ntsc or pal.
+
+              Rename old ppmntsc to ppmtv.
+
+              pbmtolj: Add compression (-delta, -packbits, -compress) by
+              Dave Platt
+
+              Look for both /usr/lib/X11/rgb.txt and /usr/openwin/lib/rgb.txt
+              by default.  Improved error messages if file not found.
+
+              pbmtext: renders character codes 0x80-0xff and 0x00-0x1f as
+              whatever the font says, rather than always blank.  Thanks
+              Helge Oldach.
+
+              Fix bug in parsing of rgb.txt file in ppm_colorname().
+
+              Add optional static library build to make files.
+
+              Fix a bunch of build bugs for Solaris, SunOS.  Still not all 
+              the way there.  Thanks Richard Curnow, Philippe Brieu, 
+              Benjamin Kuit.
+
+              Make it build on Cygwin.  Thanks Pierre Humblet.
+
+              Use rm -f;ls instead of ls -sf in make files; some systems don't
+              have ln -f.
+              
+00.04.15 BJH  Release 9.0
+
+              Add the 16-bit-per-sample format for maxval > 255.
+              Change size of samples in the library interface from 8
+              bits to 32 bits.  Library write routines now create the
+              new 16 bit format instead of plain format when you
+              request a maxval > 255 and not 'forceplain'.  Make all
+              programs read the new format, and all programs that
+              convert to PNM from a >8 bit input generate it instead
+              of failing or normalizing.  Make pnmdepth generate it.
+
+              pnm_readpnminit(), etc. fails if you request a maxval >
+              65535 and not 'forceplain'.  It used to generate plain
+              format in that case, but you couldn't actually specify    
+              a sample value > 255.
+
+              Add pnmtorle and rletopnm from the Army High Performance 
+              Computing Research Center.
+
+              add ppmcolormask - creates a mask of areas of a certain 
+              color in an image.
+
+              anytopnm: fix infinite loop
+              
+              pbmtext:  Ignore non-8-bit characters in bdf files instead
+              of crashing.
+
+              ppmdither: fix crash with large dithering matrix dimensions.
+
+              Rename ppmtompeg/headers/search.h to motion_search.h to avoid
+              possible confusion with the OSF system file search.h
+         
+              Fix erroneous shhopt.h dependency in build.
+              
+00.04.03 BJH  Release 8.4
+
+              Add ppmtompeg, adapted from Berkeley's mpeg_encode.
+         
+              Add eyuvtoppm and ppmtoeyuv, and vidtoppm, from Berkeley
+              mpeg tools.
+
+              Add ppmfade, adapted from Wesley C. Barris' pbmfade. 
+
+              Add ability to extract the alpha channel to tifftopnm,
+              tgatoppm, and ximtoppm.  (It's already in pngtoppm,
+              and still missing from ilbmtoppm).
+
+              pnmtotiff: Change default compression to none, due to
+              removal of LZW capability from Tiff library.
+
+              Make merge build use symbolic links instead of hard links.
+
+              Include dependent libraries in the link of the
+              libraries.  This is necessary on some systems, and a
+              good idea on others.
+
+              Use strerror() instead of sys_errlist[] everywhere.  If
+              some systems do not have the former, we need to do some
+              work, because strerror() was already used in some
+              places.  We do have a report of a BeOS system with no
+              sys_errlist[].
+
+00.03.24 BJH  Release 8.3
+
+              Add ppmshadow, by John Walker <http://www.fourmilab.ch/>,
+              dated 1997.08.15.
+
+              Add ppmlabel, by John Walker, dated June 1995.
+
+              Add ppmcie by John Walker, dated September 1994.
+
+              Add character drawing routines by John Walker to libppm.
+
+              Add sbigtopgm by John Walker, dated January 1998.
+
+              Handle BMP file color map size parameter in ppmtobmp, bmptoppm.
+              Thanks Marc Moorcroft.
+
+              Rewrite make files, fix lots of make install bugs.
+
+00.03.20 BJH  Release 8.2
+
+              Add ppmtojpeg and jpegtopnm.
+
+              Rename pnmnoraw to pnmtoplainpnm.
+
+              Add CMYK capability to tifftopnm.
+ 
+              Major make file rewrite, especially making install work.
+              Pnmmerge is no longer the default.  Shared libraries are.
+ 
+              Add pnmtotiffcmyk.  Written by Andrew Cooke  (Jara Software)
+              jara@andrewcooke.free-online.co.uk
+
+00.03.02 BJH  Release 8.1
+
+00.03.02 BJH  Add pnmtopng and pngtopnm.  I got these from 
+              ftp://ftp.au.netbsd.org/pub/NetBSD/packages/distfiles
+              today.  Release 2.37.3.  I added an "unsigned" and
+              changed the type of 'scaleval' to quiet compiler warnings.
+
+00.03.01 BJH  Fix xwdtopnm interpretation of 16 bit TrueColor files.
+              Thanks to Martin Kroeker, mk@daveg.com.
+
+99.09.23 BJH  Update xbmtopbm to recognize newer xbm format.
+
+
+
+
+CHANGES TO NETPBM THROUGH MARCH 1994
+------------------------------------
+
+Functional changes to Netpbm since 13 October 1993.
+Minor bug fixes and compatibility fixes are not documented in this file.
+
+PBM
+
+libpbm1.c	strstr() added to libpbm1.c.
+libpbm5.c	BDF font support added.
+pbmtext		BDF font support added.
+pbmto4425	New filter.
+pbmtoln03	Command line parsing changed to Pbmplus standard.
+
+
+PGM
+
+pgmnoise	New filter.
+
+
+PPM
+
+picttoppm	Updated
+ppm3d		New facility.
+ppmchange	New filter.
+ppmdim		New filter.
+ppmflash	New filter.
+ppmmix		New filter.
+ppmntsc		New filter.
+ppmqvga		Option parsing changed to Pbmplus standard.
+ppmshift	New filter.
+ppmspread	New filter.
+ppmtoxpm	Prototypes added.
+xpmtoppm	Prototypes added.
+ilbmtoppm	Updated.
+ppmtoilbm	Updated.
+
+
+PNM
+
+pnmtoddif	New filter.
+pnmhistmap	New facility.
+pnmtops		New option (-nocenter) added.
+
+
+Functional changes to Netpbm since 7 December 1993.
+Minor bug fixes and compatibility fixes are not documented in this file.
+
+PGM
+
+asciitopgm	New filter.
+fitstopgm	Replaced by fitstopnm.
+pgmtofits	Replaced by pnmtofits.
+pgmtopbm	Upgraded.
+pgmkernel	New filter.
+
+PPM
+
+ppmchange	Upgraded.
+xvminitoppm	New filter.
+
+PNM
+
+pnmalias	New filter.
+pnmtofits	Replacement for pgmtofits.
+fitstopnm	Replacement for fitstopgm.
+pnmtosgi	New filter.
+sgitopnm	New filter.
+pstopnm		New filter.
+
+
+
+CHANGES BETWEEN PBMPLUS AND THE ORIGINAL NETPBM
+-----------------------------------------------
+
+The following is new in Netpbm (compared to Pbmplus):
+
+PBM
+
+pbmtext		BDF font support added.
+
+pbmto4425	Display on an AT&T 4425 Ascii terminal.
+
+pbmtoascii	A new improved version.
+
+pbmtoln03	Convert to DEC LN03+.
+
+pbmtolps	Fast PostScript creator.
+
+pbmtopk		Conversion to/from a packed (PK) format font.
+pktopbm
+
+pbmclean	Flip isolated pixels.
+
+pbmpscale	Enlarge pbm image with edge smoothing.
+
+
+PGM
+
+asciitopgm	Convert an ascii image into pgm.
+
+pbmtopgm	Convert pbm to pgm by averaging areas.
+
+rawtopgm	Handles input files without specification of the file size,
+		assuming the input image is quadratic. It also supports a
+		-tb (top bottom flip) option.
+
+bioradtopgm	Conversion utility for files created by Biorad confocal
+		microscopes.
+
+spottopgm	Convert SPOT satellite images to pgm.
+
+pgmkernel	Generate a convolution kernel.
+
+pgmnoise	Create a pgm file with random pixels.
+
+
+PPM
+
+bmptoppm	Conversion to/from windows bitmap format.
+ppmtobmp
+
+ppmtogif	Updated version.
+giftoppm	Removed (see giftopnm).
+
+ppmtoilbm	Updated version.
+ilbmtoppm
+
+picttoppm	Updated version.
+ppmtopict
+
+ppmtoxpm	Updated version, which supports xpm version 3.
+xpmtoppm
+
+ppmtomap	Extract all colors from a ppm file.
+
+ppmtomitsu	Convert to Mitsubishi S340-10 printer format.
+
+xvminitoppm	Convert an XV thumbnail picture to ppm.
+
+ppmtoyuvsplit	Conversion to/from YUV triplets. (MPEG / JPEG).
+yuvsplittoppm
+
+ppm3d		Create a red/blue stereo image.
+
+ppmbrighten	Change image saturation and value on an HSV map.
+
+ppmchange	Change all pixels of one color to another in a portable pixmap
+
+ppmdim		Dim a ppm file down to total blackness.
+
+ppmdist		Simplistic grayscale assignment for machine generated
+		color images.
+
+ppmflash	Brighten a picture up to complete white-out
+
+ppmmix		Blend together two portable pixmaps.
+
+ppmnorm		Normalize the contrast in a portable pixmap.
+
+ppmntsc		Make a portable pixmap look like taken from an American TV.
+
+ppmqvga		Eight plane quantization.
+
+ppmshift	Shift lines of a portable pixmap left or right by a random amount.
+
+ppmspread	Displace a portable pixmap's pixels by a random amount.
+
+ppmtopjxl	Convert a ppm file into an HP PaintJet XL PCL file.
+
+
+PNM
+
+pnmtops		New option (-nocenter) added.
+
+pnmtofits	Replacement for pgmtofits/fitstopgm
+fitstopnm
+
+pnmtosgi	Conversion to/from sgi image format.
+sgitopnm
+
+pnmtosir	Conversion to/from Solitaire image recorder format.
+sirtopnm
+
+giftopnm	Replaces giftoppm. Examines the input image and produces
+		a pbm, pgm, or ppm output.
+
+pstopnm		Convert PostScript to pnm. Requires Ghostscript.
+
+zeisstopnm	Conversion utility for files created by Zeiss confocal
+		microscopes (the old standard).
+
+pnmalias	Anti aliasing filter.
+
+pnmcomp		Composite two portable anymaps together.
+
+pnmcrop		New options added.
+
+pnmpad		Add borders to anymap.
+
+
+LIBTIFF
+
+A new release of libtiff is included. Please read its supporting
+documentation.
+
+
+CHANGE LOG FROM PBMPLUS
+-----------------------
+
+Changes since the 30oct91 patch version:
+
+    Fixed uninitialized variable in ppmtotga.  (John Walker)
+    Added pgmcrater, ppmforge, ppmtoacad, sldtoppm.  (John Walker)
+    Slight change to the p?mmerge.c front-ends to allow for main programs
+      that return instead of exitting.
+    Minor clarifications to the pnmconvol man page.
+    Fixed xwdtopnm to read some 16-bit True/Direct files. (David Elliott)
+    Fixed uninitialized variables in pnmtotiff.  (Larry Rosenstein, Bayles Holt)
+    Added a couple of checks for \r while reading whitespace. (Larry Rosenstein)
+    Removed all the BROKENPUTC stuff.  Now we ignore all return values from
+      putc(), and check ferror() in pm_close().  Added pm_close() calls to
+      many of the filters.
+    Fixed pnmdepth to check for too-large newmaxvals.  Improved rounding
+      in pnmdepth and PPM_DEPTH macro.  (Tom Lane)
+    Minor fix to ppmtouil.  (Mohsen Banan)
+    Added new Imakefiles.  (Rainer Klute)
+
+Changes since the 05oct91 X11R5 contrib tape version:
+
+    Fixed minor SysV config error in pbmplus.h.  (Tom Lane)
+    Fixed tifftopnm so that BITSPERSAMPLE and SAMPLESPERPIXEL default
+      correctly.  Fixed possible bug in tgatoppm line-interleaving code.
+      (Arthur David Olson)
+    Fixed tifftopnm so that colormapped files are read correctly.
+      (PauL Drews, Mike Wade)
+    Corrected use of DefaultRGBDatabase / RGB_DB for imake sites.
+      (Randal L. Schwartz)
+    Bugfix to pnmtops color PostScript in -rle mode.  (Angus Duggan)
+    Added auto-turning to pnmtops.
+    Added run-length encoding to ppmtotga.
+    Space optimization to pgmoil.
+
+Changes since the 27sep91 comp.sources.misc distribution:
+
+    Fixed spelling error in giftoppm.  Fixed ppmrelief to not shrink the
+      image by 2 rows and columns.  Minor fix to pgmramp.  Fixed off-by-one
+      error in pnmtoxwd.  Man page fix for pnmgamma.  (Arthur David Olson)
+    Converted ANSI trigraphs in ppmtosixel into good old octal.  (Jeff Glover)
+    Fix to 24/32 bit case in rasttopnm.  (Behr de Ruiter)
+    Float/double portability fix to libppm4.  (Bruce Holmer, Ronald Khoo)
+    Fixed typo in compat.ksh.  (Larry Virden)
+    Fixed int/short incompatibility in tifftopnm.  (Salik Rafiq)
+
+Changes during the extended beta test period, starting on 15jan91:
+
+    Lots of fixes from: Anthony A. Datri, Arthur David Olson, David Brooks,
+      David Elliott, Doug Claar, Duncan Sinclair, Francois Pinard, Gerard
+      Leurs, Jim Hanko, Ken Laprade, Klaus U. Schallhorn, Markus Bolz, Mike
+      Hench, Philip Gladstone, R C Smith, Selden E. Ball, Jr., Stephen Uitti,
+      Steve Allen, Tom Lane, update.kpj-jaakkola@athena.dsv.su.se,
+      Charles Karney, Unmesh Agarwala, Ed Pendzik, Juha Sarlin, Tom Tulinsky,
+      Phillip Smith, Lai-King Mau, David Koblas, Mark Donovan.
+    Added a global -version flag.
+    Added bunches of statics and prototypes.  Now compiles with zero
+      warnings under gcc -ansi -pedantic.
+    Changed #ifdef __STDC__ to #if __STDC__, since some non-compliant
+      compilers define it as 0.
+    Changed pm_message and pm_error to be varargs routines.  Added a
+      portable version of vfprintf for those systems which don't have it.
+    Removed the option of not compiling the pgm and ppm parts.  Very few
+      people used it, and it added amazing complexity to the pnm programs,
+      turning them into maintenance nightmares.
+    Merged pbmpaste into pnmpaste.
+    Merged pgmtops and ppmtops into pnmtops.
+    Added auto-scaling and dpi / page size flags to pnmtops.
+    Changed the interpretation of bits in pbmlife to conform with other
+      tools.
+    Changed xwdtopnm to ignore the pixel number in the xwd color structure.
+    Added a -pseudodepth flag to pnmtoxwd.
+    Updated tifftopnm for libtiff 2.4.
+    Added many option flags to pnmtotiff.  (J.T. Conklin)
+    Added support for X11R5's new color specifiers rgb: and rgbi:.
+    Added pgmtexture.  (James Darrell McCauley)
+    Added ppmtopj, pjtoppm, and ppmdither.  (Christos Zoulas)
+    Added ppmtotga.  (Mark Shand)
+    Added ppmtosixel.  (Rick Vinci)
+    Added pbmtoatk and atktopbm.  (Bill Janssen)
+    Added ppmtoyuv and yuvtoppm.  (Marc Boucher)
+    Fixes to picttoppm.  (George Phillips)
+    Added 24-bit support to ilbmtoppm.  (Mark Thompson)
+
+Changes since the X.V11R4 / comp.sources.misc distribution of 22nov89:
+
+    Added pgmramp, pgmedge, pgmtoppm, rgb3toppm, ppmtoxpm, pnmgamma,
+      ximtoppm, pgmtofs, picttoppm, ppmtopict, ppmquantall, anytopnm,
+      pi1toppm, ppmtopi1, sputoppm, spctoppm, pbmto10x, ppmtoicr, ppmmake,
+      xpmtoppm, ppmtopuzz, ppmtouil, ybmtopbm, pbmtoybm, lispmtopgm,
+      pgmtolispm, pbmtogem, pi3topbm, pbmtopi3, pbmtoepson, pbmtoplot,
+      pbmtozinc, pbmtext, pnmnoraw, pnmmargin, pnmfile, pnmindex,
+      ppmtorgb3, gouldtoppm, pgmbentley, pgmoil, ppmrelief, pnmtotiff,
+      ppmtopcx.
+    Merged some filters:
+      rasttopbm and rasttoppm into rasttopnm;
+      pbmtorast and ppmtorast into pnmtorast;
+      xwdtopbm and xwdtoppm into xwdtopnm;
+      pbmtoxwd and ppmtoxwd into pnmtoxwd.
+    Promoted some filters:
+      pcxtopbm to pcxtoppm;
+      ppmarith to pnmarith;
+      ppmconvol to pnmconvol;
+      ppmcscale to pnmdepth;
+      ppmrotate to pnmrotate;
+      ppmscale to pnmscale;
+      ppmshear to pnmshear;
+      ppmsmooth to pnmsmooth;
+      tifftopgm to tifftopnm - new version based on Sam Leffler's libtiff.
+    Bugfixes to pbmtoicon, ppmtops.
+    The Makefiles now have a "merge" option.
+    All flags are now case-insensitive.
+    Added $(MAKE) stuff to Makefile.
+    Changed pnmsmooth from a csh script to a sh script.
+    Made macro use in pbmtox10bm and pbmtoxbm more portable.
+    Moved compataliases to compat.csh, and added compat.ksh.
+    Made ppmtoilbm less Amiga-specific.
+    Added -headerskip and -rowskip flags to rawtopgm.
+    Enhanced rasttopnm to interpret 8-bit rasters with no colormap as grayscale.
+    Changed sscanf %g to %f - some systems can't handle %g on input.
+    Added -expand flag to pbmmask.
+    Speedup to pnmflip - don't buffer if possible.
+    Added color-name-to-value routine to ppm - uses X11's rgb.txt if present.
+    Updated Imakefile support to reflect X.V11R4.
+    Removed picttopbm.
+    Improved pnmcut argument syntax so that negative coords work like pnmpaste.
+    Added "magic" file, for use with the "file" program.
+    40% speedup for pgmnorm from Robert Stockton (rgs@cs.cmu.edu).
+    Fixed long-standing bug involving colormaps on SPARCstations when
+      compiled with gcc -- had to do with passing structs by value.
+    Removed the -x flag from pnmtorast -- it's not really needed.
+    Fixed subtle bug in the pnm reading code that caused pnmcat to blow
+      it on images that differed greatly in width or height.
+    New version of giftoppm that handles the GIF89a standard, and doesn't
+      use fseek.
+    Fixed fitstopgm to handle three-axis images, such as the Hubble pix.
+    Xwdtopnm and pnmtoxwd finally handle byte-order properly.
+    Added -xysize flag to pnmscale.
+    Added conditional ANSI function prototypes to library routines.
+    Added -noantialias flag to pnmrotate and pnmshear.
+    Removed the TIPS file.  No one ever sent in any new tips, so I just
+      moved the few I had into the relevant man pages.  That's probably
+      where they belonged in the first place.
+    Added justification flags to pnmcat.
+    Added -map flag to ppmquant - user-specifiable colormap.  Also, the
+      Floyd-Steinberg error diffusion finally works right.
+    Added -map flag to pgmtoppm.
+    Added DirectColor support to xwdtopnm and pnmtoxwd.
+    Speedup to pgmtolj from Arthur David Olson: avoid sending whitespace.
+    Fix to pbmtogo from Bo Thide': 2D compression now works.
+
+Patch 1 to the X.V11R4 / comp.sources.misc distribution of 22nov89:
+
+    Fixed bug in pgmtops -rle.
+
+Changes since the alt.sources distribution of 13sep89:
+
+    Small corrections to ppmtorast, pgmtops.
+    Moved pbm/tifftopbm to pgm/tifftopgm - it now handles grayscale TIFF files.
+    Fixed tifftopgm to handle non-native byte order.
+    Changes to tifftopgm to handle bogus AppleScan TIFF files, to have
+      better command syntax, and to use stdio.
+    Optimizations to xbmtopbm, pbmtoxbm, and pbmtox10bm, courtesy of
+      Juha Sarlin, to make them go about three times as fast.
+    Optimization to pgmtops and ppmtops to make them go three times as fast.
+    Optimization to pnmcrop.
+    Added PBMPLUS_BROKENPUTC defines in pbmplus.h to handle systems (such as
+      ULTRIX) which have broken putc() macros.
+    Rewrote ppmscale, pnmcat, and pgmhist to operate line-by-line, instead
+      of reading in the whole image.
+    Rewrote pnmflip to keep only one copy of the image in memory.
+    Added pgmtofits, courtesy of Wilson H. Bent, plus bugfixes to fitstopgm
+      and a patch to giftoppm to handle black&white GIF files.
+    Added picttopbm and rawtopgm.
+    Fixes to xwdtoppm and ppmtoxwd so they compile with SunOS cc as well as gcc.
+    Another small change to ppmtoxwd having to do with colormap size.
+    Changed macptopbm's -headersize flag to be -extraskip.
+    Changed tgatoppm to read color values as BGRA instead of ARGB; the Targa
+      documentation is apparently wrong about the order.
+    Some changes to the Makefiles, partially to work around bugs in gnumake.
+    Got g3topbm working, and added pbmtog3, courtesy of Paul Haeberli.
+    Added some pixrect work-alike code so that rasttopbm, pbmtorast, rasttoppm,
+      and ppmtorast can be used on non-Sun systems.  This also provides a
+      final solution to the persistent byte- and bit-order problems on 386's.
+    Moved the SYSV-checking #ifdefs to *after* the include of pbm.h, which
+      defines SYSV.
+    Made all the #else's and #endif's ANSI-compliant.
+    Added manual pages for libpbm, libpgm, libppm, and libpnm, courtesy
+      of Tony Hansen.
+    Changed man page installation so that pages from different sections
+      can go in different directories.
+    Fixed Imakefiles.
+
+Changes since the expo.lcs.mit.edu FTP distribution of 06sep89:
+
+    Added #ifdefs to pnm/libpnm3.c to allow the PBM-PNM-only configuration.
+    Small corrections to TIPS, pnm/Makefile, pnm/Imakefile, ppm/ppmrotate.1,
+      ppm/ppmshear.1, ppm/ppmtoilbm.c, pbm/xwdtopbm.c, ppm/xwdtoppm.c,
+      ppm/ppmtoxwd.c, ppm/ppmtoxwd.1, pbm/x11wd.h.
+
+Changes since the comp.sources.misc distribution of 31oct88:
+
+    Added pbmreduce, pbmlife, pbmmask, and pbmupc.
+    Added gemtopbm, tifftopbm, pcxtopbm, pbmtogo, mgrtopbm, pbmtomgr,
+      cmuwmtopbm, pbmtocmuwm, g3topbm, and pbmtobg.
+    Minor bugfix to pbmtolj.
+    Slight restructuring of most of the programs to use vastly less memory.
+    Various other minor optimizations.
+    Fixed pbmtorast and rasttopbm to handle byte-swapped big-endian 386 boxes.
+    Slight changes to argument syntax of pbmcrop, pbmmake, pbmreduce.
+    Moved to the new PGM package: pbmtops (which now produces Conforming PS).
+    Moved to the new PPM package: giftopbm.
+    Moved to the new PNM package: pbmcrop pbmcut pbmenlarge pbminvert.
+    Consolidated into a single pnmflip tool: pbmfliplr pbmfliptb pbmtrnspos.
+    Consolidated into a single pnmcat tool: pbmcatlr pbmcattb.
+    Added compataliases script for upward compatability with changed tools.
+    Removed xxxtopbm.
+    Added a -headersize flag to macptopbm, to help get around annoying
+      problems in MacPaint file format.
+    Added the RAWBITS compilation-time option, to use a more compact and
+      much faster (but less portable) external format.
+    Removed the CBM format - use compress(1) and / or RAWBITS instead.
+    Pbmpaste (and the new pnmpaste) now accepts negative x and y coords,
+      which are interpreted relative to the right and bottom sides.
+    Changed all programs to accept a "-" file argument as meaning standard
+      input.
+    Removed pbmtox10wd, since it was never very useful (X10 doesn't have xwud).
+    Added Imakefiles, for X11 types to use.
+
+Changes since the X.V11R3 distribution of 31aug88:
+
+    The cbm format has been revised to support run-length encoding.
+    Pbmtops now does run-length encoding.
+
+Major changes since the X.V11R2 distribution of 28mar88:
+
+    The pbm format now has a "magic number".
+    New conversion filters: brushtopbm, giftopbm, pbmtolj, pbmtomacp,
+      pbmtoxwd, and pbmtox10wd.
+    Icontopbm converter has a better parser -- it knows to skip over
+      any extraneous comments at the beginning of the icon file.
+    Pbmtops generates a different PostScript wrapper program -- it should
+      handle huge bitmaps better.
+    Xwdtopbm now handles byte-swapping correctly.
+    Pbmmake takes a flag to specify the color of the new bitmap.
+    Pbmpaste now implements 'or', 'and', and 'xor' operations as well
+      as the default 'replace'.
diff --git a/doc/INSTALL b/doc/INSTALL
new file mode 100644
index 00000000..78cf1ebf
--- /dev/null
+++ b/doc/INSTALL
@@ -0,0 +1,248 @@
+HOW TO INSTALL NETPBM
+---------------------
+
+For most typical platforms, you can just do
+
+    configure
+
+followed by
+
+    make
+
+To build all the programs.  Then
+
+    make package
+
+to gather all the installable parts into a specified directory, and 
+finally
+
+    installnetpbm
+
+to install it into your system.  
+
+If you're building Latest Netpbm (as opposed to Stable Netpbm), there
+are probably known bugs.  Some may even prevent Netpbm from building.
+These are listed in the release notes for the release on Sourceforge.
+Check it out.
+
+The 'configure' program is not GNU Autoconf -- it is a simple program
+specific to Netpbm that prompts you for information about your system.
+If your system is not typical enough, you'll have to do a little more
+work, as described below under "custom installation."
+
+You need to use GNU Make even if nothing else on your system is GNU,
+because the Netpbm make files exploit many advanced features of GNU
+Make.  Often, systems have both GNU Make and a native Make.  In this
+case, GNU Make is named 'gmake'.  If you don't have it yet, see
+www.gnu.org/software.  GNU Make is free, easy to install, and works
+just about anywhere.
+
+The only tricky part about installing is setting up the shared
+libraries that are part of Netpbm.  Simply putting the library files
+in place may not be enough.  If you get mysterious "file not found"
+kinds of errors and are not an expert with shared libraries, see the
+section "SHARED LIBRARIES" below.
+
+The --keep-going option to Make is handy, because it causes Make to
+make anything it can, as opposed to quitting as soon as something goes
+wrong.  With so many parts having so many requirements, it's not
+unusual for a few things to fail to build, but that doesn't affect
+everything else.  You can work on the failed parts and repeat the make
+and it will attempt to build whatever it hasn't successfully built
+yet.
+
+
+AUTOMATING THE BUILD
+--------------------
+
+The build is already as automatic as Netpbm developers know how to make
+it without sacrificing your ability to build it a wide variety of ways.
+But if you're building for a specific range of applications, e.g. a
+particular standard system configuration, then you may want to write 
+programs to automate the build further.
+
+PLEASE don't do this by writing a program that invokes Netpbm's Configure
+program and tries to maintain a dialogue with Configure.  This is more work
+than you need to do and you will be disappointed with Configure's 
+unpredictability, especially from one release to the next.  Configure is
+specifically intended to talk to an intelligent human being.
+
+Rather, just write a program to generate the file Makefile.config.  That's
+all Configure does in the end anyway.  Like Configure, your program can
+simply copy Makefile.config.in and add overrides to the bottom.  Or you
+can just package up a complete Makefile.config and not run any program at
+all at build time.  Comments in Makefile.config.in explain the entire
+contents.  You can also run Configure interactively and use its output
+as an example.
+
+
+
+INSTALLATION - WINDOWS
+----------------------
+
+For notes on building Netpbm on Windows using Cygwin, see the file
+README.CYGWIN.  With Cygwin, you can build Netpbm programs that use
+Cygwin or Netpbm programs that use Mingw.
+
+For notes on building Netpbm on Windows using Djgpp, see the file
+README.DJGPP.
+
+See also the general installation instructions above.
+
+
+
+INSTALLATION - MAKING ONLY THE PARTS YOU NEED
+---------------------------------------------
+
+If you don't need the whole package, but just want one tool in it that you
+heard about, you can make just that one.  For example, to make Pnmtojpeg,
+just do
+
+  configure
+  cd converter/other
+  make pnmtojpeg
+
+It will build Pnmtojpeg and any of its dependencies, but nothing else.
+You have to install it manually.  
+
+When you build just one program, you should request static libraries
+in the configure step, because for just one program, the shared
+libraries would be pure masochism.
+
+
+CUSTOM BUILDING
+---------------
+
+This section explains how to customize the installation in the case
+that your platform is, or your requirements are, not among the simple
+cases that 'configure' understands.  'configure' is really just a
+convenient way to build the Makefile.config file, and in the most
+general case, you build that file manually.
+
+Makefile.config contains settings for various things that vary from
+one system to the next, like file paths.  Start with the distributed
+file Makefile.config.in.  Copy it as Makefile.config, then edit it.
+Search for your platform name (Solaris, SunOS, NetBSD, Cygwin, BeOS,
+and Tru64 are among those mentioned) to see recommended settings for
+your platform.
+
+If your system is even too exotic to accomodate with settings in
+Makefile.config, you may need to modify things in the main make files
+or pm_config.h.
+
+If you figure out how to install on other platforms, contact the
+Netpbm maintainer to have the 'configure' program or these
+instructions improved for the next person.
+
+
+CUSTOM INSTALLATION
+-------------------
+
+If the Installnetpbm program doesn't install the way you want, it is
+easy to install it manually using the package that 'make package'
+generates.  That package is just a directory full of files, and you
+should be able to tell by inspection what to do with those files (copy
+to /bin, etc).  If not, there will be a README file in the package to
+explain everything.
+
+
+INSTALLATION - SHARED LIBRARIES
+-------------------------------
+
+There are over 240 programs in the Netpbm package and they do a lot of
+common things.  In order to avoid the expense of copying the code for
+those common things into every program, Netpbm places them in a shared
+library: libnetpbm.  When you invoke a Netpbm program, your system
+notices that it needs this library and accesses it too.
+
+The tricky part of installing the shared (runtime) library is telling
+your system where to find it in the event that you invoke a Netpbm
+program.  And that varies from one system to the next.
+
+On a GNU libc system (essentially, any Linux system), if you put the
+Netpbm shared library in a conventional spot (say, /lib) and reboot
+your system, chances are you will have no trouble running Netpbm
+programs.  But if you want to fine tune things, read up on ld-linux.so
+(GNU libc's dynamic linker) and Ldconfig and consider the
+/etc/ld.so.conf file and LD_LIBRARY_PATH environment variables.  Use
+'ldd' to see if you have a shared library issue.  If it shows any
+shared library as "not found", you've got library trouble.
+
+On a Solaris system, use 'crle' to set the default search path for
+shared libraries (which is kept in /var/ld/ld.config).  Make sure that
+path includes the directory in which you installed the Netpbm shared
+library.  You can also use the LD_LIBRARY_PATH environment variable.
+
+Besides the Netpbm shared library, libnetpbm, several of the converter
+programs, e.g. Jpegtopnm, use separately distributed libraries that
+understand the graphics format involved.  You need to make sure your
+system knows how to find those libraries at run time too (or cause the
+Netpbm build to statically bind the libraries into the Netpbm programs).
+
+Another thing you can do is forget about library search paths and just
+build into each Netpbm executable the location of the Netpbm shared
+library.  (I'm talking about the classic -R linker option) You set
+this up with variables in Makefile.config.  If you use Configure to
+build Makefile.config, then for some platforms where this method is
+common, the Configure dialog asks you what directory, if any, you want
+built into Netpbm executables.
+
+One final note: New Netpbm executables often can run OK with an old
+Netpbm shared library.  This means if you don't correctly install
+the new library, you may run what you think is a new Netpbm program,
+but in actuality be accessing the old Netpbm library, and you may not
+even notice a problem right away.  But eventually, you may find some
+things not working the way they should.  Again, 'ldd' will give you 
+peace of mind.
+
+
+INSTALLATION WITHOUT SHARED LIBRARIES
+-------------------------------------
+
+Since shared libraries can be such a pain, and in fact some systems
+don't even have them, you can build Netpbm with a static library
+instead.  Just answer "static" to the static/shared question when you
+run 'configure' (if you don't use 'configure', set NETPBMLIBTYPE as
+directed in Makefile.config.in).
+
+If you do this, you probably want to do a merge build instead of the
+normal build (there's a question for that in the Configure program).
+See below.
+
+
+MERGE BUILD
+-----------
+
+There are two ways to build Netpbm: the standard or nonmerge build,
+and the merge build.  There are different make file targets for them
+and which one is default is controlled by the DEFAULT_TARGET make
+variable in Makefile.config, and its value is one of the choices you
+make via the Configure dialog.
+
+The standard build is the conventional one.  The merge build is a way
+to reduce disk space and other resource usage in some configurations.
+
+In the standard build, hundreds of separate programs get built: ppmtogif,
+pamcomp, etc.
+
+But the merge build creates a single large program called 'netpbm'
+instead.  That single 'netpbm' program contains the functions of all
+the individual programs that the standard build builds, and selects
+one of them to execute based on what command name you use to invoke
+'netpbm'.
+
+For example, if you install 'netpbm' with a symbolic link to it named
+'ppmtogif' and you invoke the program by typing 'ppmtogif' at a shell
+prompt, 'netpbm' executes Ppmtogif.
+
+Hence, 'make package' creates a package containing the one 'netpbm'
+program and lots of symbolic links to it.  With all these links
+properly installed, a typical user cannot tell the merge build from the
+standard build.
+
+
+DOCUMENTATION
+-------------
+
+Documentation is not packaged with the program source code.  See the
+file doc/USERDOC for information on installing documentation.
diff --git a/doc/Netpbm.programming b/doc/Netpbm.programming
new file mode 100644
index 00000000..14de8b08
--- /dev/null
+++ b/doc/Netpbm.programming
@@ -0,0 +1,358 @@
+This file is an attempt to give some basic guidelines for those who
+wish to write new Netpbm programs.  The guidelines ensure:
+
+  A) Your program functions consistently with the rest of the package.
+
+  B) Your program works in myriad environments on which you can't test it.
+
+  C) You don't miss an important detail of the Netpbm formats.
+
+  D) Your program is immune to small changes in the Netpbm formats.
+
+The easiest way to write your own Netpbm program is to take an
+existing one, similar to the one you want to write, and to modify
+it.  This saves a lot of time, and ensures conformity with these rules.
+But pick a recent one (check the file modification date and the
+doc/HISTORY file), because many things you see in old programs are
+grandfathered in.  Pamtopnm is good example of a thoroughly modern
+Netpbm program.  Pamcut is another good one.
+
+
+COLLECTION PARAMETERS
+---------------------
+
+The philosophy that guides what goes into Netpbm and what doesn't is
+one of maximum distribution of function.  Contributions to Netpbm are
+refused very rarely, and usually because there is already some better
+way to do what the contribution attempts to do, so that the
+contribution would just make the package more confusing and thus
+harder to use.  The second most common reason for declining a
+contribution is that it falls well outside Netpbm's scope.  That too
+would make the package more confusing and thus harder to use.
+
+"Nobody will use that" is not a reason for leaving something out of
+Netpbm.
+
+"That's different from how other Netpbm programs work" is similarly 
+not an excuse for refusing distribution of a feature.  (But it's a
+good reason to accept a change to make a feature consistent!)
+
+The standard for adding something to Netpbm isn't that it be perfect,
+or even a net improvement.  The standard is that it not make Netpbm
+worse.  Poor quality additions are made all the time, because a
+program that doesn't work well is usually no worse than no program at
+all.
+
+
+DEVELOPMENT PROCESS
+-------------------
+
+Just email your code to the Netpbm maintainer.  Base it on the most recent
+Latest Netpbm release if possible.  It's a good idea to ask the maintainer
+before starting a big change to a file to make sure it doesn't conflict
+with some other work that has been done since the last release.
+
+The preferred form for changes to existing files is a unified diff patch --
+the conventional Unix means of communicating code changes.
+
+You should update or create documentation too.  The source files for
+the documentation are the HTML files at
+http://netpbm.sourceforge.net.doc/ .  But if you don't, the Netpbm
+maintainer will do the update before releasing your code.
+
+There are some automated tests in the package - shell scripts in files
+named such as "pbmtog3.test".  You can use those to verify your
+changes.  You should also add to these tests and create new ones.  But
+most developers don't.
+
+
+CODING GUIDELINES
+-----------------
+
+The Netpbm maintainer accepts programs that do not meet these guidelines,
+so don't feel you need to hold back a contribution because you wrote it
+before you read these.
+
+* See the Netpbm library documentation to see what library functions
+  you should be using and how.  You can find it at:
+
+  http://netpbm.sourceforge.net/doc/libnetpbm.html
+
+* See the specifications of the Netpbm formats at:
+
+  http://netpbm.sourceforge.net/doc/pbm.html
+  http://netpbm.sourceforge.net/doc/pgm.html
+  http://netpbm.sourceforge.net/doc/ppm.html
+  http://netpbm.sourceforge.net/doc/pam.html
+
+  but don't depend very much on these; use the library functions
+  instead to read and write the image files.
+
+* Your new program must belong to one of the four Netpbm classes,
+  which are loosely based on the Netpbm formats.  They are defined as
+  follows:
+
+  pbm: These programs take PBM (bitmap - pixels are black or white)
+       files as input or output or both.  They never have PGM or PPM 
+       primary input or output.  They use the libpbm Netpbm
+       library.  They have "pbm" in their names.
+
+  pgm: These programs take PBM or PGM (grayscale) files as input, or
+       produce PGM files as output, or both.  They treat PBM input
+       as if it were the equivalent PGM input.  They never produce PBM
+       primary output and never have PPM primary input or output.
+       They use the libpbm and libpgm Netpbm libraries.  They have
+       "pgm" in their names.
+
+  ppm: These programs take PBM or PGM or PPM (color) files as input,
+       or produce PPM files as output, or both.  They treat PBM and
+       PGM input as if it were the equivalent PPM input.  They never
+       produce PBM or PGM primary output.  They use the libpbm,
+       libpgm, and libppm Netpbm libraries.  They have "ppm" in their
+       names.
+
+  pnm: These are the most general programs.  They handle all four
+       Netpbm formats (PBM, PGM, PPM, and PAM).  They use all Netpbm
+       formats as input or output or both.  They recognize the
+       difference between PBM, PGM, and PPM input, so a PBM input
+       might produce a different result than the equivalent PGM input.
+       These programs use the libpbm, libpgm, libppm, and libpnm
+       Netpbm libraries.  They have "pnm" or "pam" in their names.
+
+  Decide which one of these classes your program belongs to.  Your choice
+  determines the proper naming of the program and which set of library
+  subroutines you should use.
+
+* If your program involves transparency (alpha masks), you have two
+  alternatives.  The older method is to use a separate PGM file for the
+  alpha mask, in the style of Pngtopnm/Pnmtopng and Giftopnm/Pnmtogif,
+  and use command syntax like those programs.  See the PGM format spec
+  for details.
+  
+  A newer method involves a PAM image with an alpha plane (tuple type
+  RGB_ALPHA, etc.).  This is preferred because it's easier for users
+  to pipe stuff around.  Pamtotga is an example of this.
+
+* Declare all your symbols except 'main' as static so that they won't
+  cause problems to other programs when you do a "merge build" of 
+  Netpbm.
+
+* Declare main() with return type 'int', not 'void'.  Some systems won't
+  compile void main().
+
+* Always start the code in main() with a call to p?m_init().
+
+* Use shhopt for option processing.  i.e. call optParseOptions3().
+  This is really easy if you just copy the parseCommandLine() function
+  and struct cmdlineInfo declaration from pamcut.c and adapt it to 
+  your program. 
+
+  When you do this, you get a command line syntax consistent with all the
+  other Netpbm programs, you save coding time and debugging time, and it
+  is trivial to add options later on.
+
+  Do not use shhopt's short option alternative unless you need to be
+  compatible with another program that has short options.  Short
+  options are traditional one-character Unix options, which can be
+  stacked up like "foo -cderx myfile", and they are far too unfriendly
+  to be accepted by the Netpbm philosophy.  Note that long options in
+  shhopt can always be abbreviated to the shortest unique prefix, even
+  one character.
+
+  In messages and examples in documentation, always refer to an option
+  by its full name, not the abbreviation you usually use.  E.g. if you have
+  a "-width" option which can be abbreviated "-w", don't use -w in 
+  documentation.  -width is far clearer.
+  
+* Use pm_error() and pm_message() for error messages and other messages.
+  Note that the universal -quiet option (processed by p?m_init()) causes
+  messages issued via pm_message() to be suppressed.  And that's what
+  Netpbm's architecture requires.
+
+* The argument to pm_error() and pm_message() is a string of text, not
+  a formatted message.  Don't put newlines or other formatting characters
+  in it.  These subroutines are designed to be flexible in how they issue
+  the messages.
+
+* Use MALLOCARRAY() to allocate space for an array and MALLOCVAR to allocate
+  space for a non-array variable.  In fact, you usually want to save some
+  programming tedium and use the NOFAIL versions of these (they never fail
+  because they abort the program if memory is not available).  These avoid
+  array bounds violations.
+
+* Use pm_tmpfile() to make a temporary file.  This avoids races that can
+  be used to compromise security.
+
+* Properly use maxvals.  As explained in the format specifications, every
+  sample value in an image must be interpreted relative to the image's
+  maxval.  For example, a pixel with value 24 in an image with maxval 24
+  is the same brightness as a pixel with value 255 in an image with a
+  maxval of 255.
+
+  255 is a popular maxval (use the PPM_MAXMAXVAL etc. macros) because it
+  makes samples fit in a single byte and at one time was the maximum 
+  possible maxval in the format.
+
+  Note that the Pnmdepth program converts an image from one maxval to
+  another.
+
+* Don't include extra function.  If you can already do something by 
+  piping the input or output of your program through another Netpbm
+  program, don't make an option on your program to do it.  That's the
+  Netpbm philosophy -- simple building blocks.
+
+  Similarly, if your program does two things which would be useful
+  separately, write two programs and advise users to pipe them
+  together.  Or add a third program that simply calls the first two.
+
+* Your program should, if appropriate, allow the user to use Standard
+  Input and Output for images.  This is because Netpbm users commonly
+  use pipes.
+
+* Don't forget to write a proper html documentation page.  Get an
+  example to use as a template from
+  <http://netpbm.sourceforge.net/doc/directory.html>.
+
+* No Netpbm source code may contain tab characters.  If you
+  generate such a file, the Netpbm maintainer will convert it to spaces
+  and possibly change all your indenting at the same time.  Tabs are not
+  appropriate for code that multiple people must edit because they don't
+  necessarily look the same in every editing environment, and in most
+  editing environments, you can't even tell which spaces are actually in
+  the file and which came from the editor, expanding tabs.  Spaces, on
+  the other hand, look the same for everyone.  Modern editors let you
+  compose code just as easily with spaces as with tabs.
+
+
+
+
+DISCONTINUED CODING GUIDELINES
+------------------------------
+
+Here are some things you will see in old Netpbm programs, but they are
+obsolete and you shouldn't propagate them into a new program:
+
+* Tolerating non-standard C libraries.  You may assume all users have
+  ANSI and POSIX compliant C libraries.  E.g. use strrchr() and forget
+  about rindex().
+
+* pm_keymatch() for option processing.  Use shhopt instead, as described
+  above.
+
+* optParseOptions() and optParseOptions2().  These are obsoleted
+  by optParseOptions3(), which is easier to use and more powerful.
+
+* K&R C function declarations.  Always use ANSI function declarations
+  exclusively (e.g. use 
+
+      void foo(int arg1){} 
+
+  instead of 
+
+       void foo(arg1)
+           int arg1;
+           {}
+
+* for (col=0, xP = row; col < cols; col++, xP++)
+      foo(*xP);
+
+  This was done in the days before optimizing compilers because it ran faster
+  than the more readable:
+
+    for (col=0; col < cols; col++)
+        foo(row[col]);
+
+  Now, just use the latter.
+
+
+CODING STYLE
+------------
+
+We do not generally mandate any basic coding style in these
+guidelines.  Where you put your braces is a matter of personal style
+and other people working with your code will just have to live with
+it.  However, people working with your code might just recode it into
+another style if it makes it easier for them to read the code and
+have confidence in their changes.
+
+But if you have no preference, the following is what the Netpbm
+maintainer currently prefers.  Essentially, it is clean, elegant
+computer science-type code rather than brute force engineering-type
+code.  Modular and structured above all.
+
+* No gotos.  This includes all variations of goto:  break, continue, leave,
+  mid-function return.  An exception: aborting the entire program in the 
+  middle of something is allowed.
+
+* No functions with side effects.  This is a tough one, since
+  functions with side effects is highly traditional C.  In fact, the
+  creators of C didn't even have a concept of a subroutine.  However,
+  the average human brain, especially one trained in math, cannot
+  easily follow code where a function both computes a value and does
+  other stuff.
+  
+  For the purpose of this discussion, we say that a C function whose return
+  type is void is not a "function," but a "subroutine."
+
+  So a function should never change anything in the program.  All it does
+  is compute a value.
+
+  Where you have to call an external function that has side effects (virtually
+  anything in the standard C library, for example), put it in a simple
+  assignment function that captures its return value and otherwise consider
+  it a subroutine:
+
+    rc = fopen(...)
+    if (rc ...)
+
+* No reuse of variables.  Most variables should be set at most once.
+  Don't say "A is 5" and then later on, "no, A is 6."  A reader
+  should be able to take that first "A is 5" as a statement of fact
+  and not hunt for all the places it might be contradicted.  A
+  variable that represents the state of execution of an algorithm is an
+  obvious exception.
+
+  Use "const" everywhere you possibly can.
+
+  Especially never modify the argument of a function.  All function arguments
+  should be "const".
+
+  Don't use initializers except for constants.  If you're going to set 
+  a variable twice, do it out in the open; don't hide one in the declaration
+  of the variable.
+
+* Avoid global variables.  Sometimes, a value is truly global and
+  passing it as a parameter just muddies the code.  But most of the
+  time, any external value to which a function refers should be one of
+  its arguments.  
+
+  Declare a variable in the most local scope possible.
+
+* Keep subroutines small.  Generally under 50 lines.  But if the
+  routine is a long sequence of simple, similar things, it's OK for it
+  run on ad infinitem.
+
+* Use the type "bool" for boolean variables.  "bool" is defined in Netpbm
+  headers.  Use TRUE and FALSE as its values.
+
+* Do not say "if (a)" when you mean "if (a != 0)".  Use "if (a)" only if
+  a is a boolean variable.  Or where it's defined so that a zero value 
+  means ("doesn't exist").
+
+* Do multiword variable names like this:  "multiWordName".  Underscores waste
+  valuable screen real estate.
+
+* If a variable's value is the address of something (a pointer to something),
+  it's name should reflect that, as opposed to lying and saying the value is
+  the thing pointed to.  A "P" on the end is the conventional way to say
+  "address of."  E.g. if a variable's value is the address of a color value,
+  name it "colorP", not "color".
+
+* Put "const" as close as possible to the thing that is constant.
+  "int const width", not "const int width".  When pointers to
+  constants are involved, it makes it much easier to read.
+
+* Free something in the same subroutine that allocates it.  Have exactly 
+  one free per allocate (this comes naturally if you eliminate gotos).
+
+
diff --git a/doc/README.CYGWIN b/doc/README.CYGWIN
new file mode 100644
index 00000000..6c11ff35
--- /dev/null
+++ b/doc/README.CYGWIN
@@ -0,0 +1,79 @@
+Cygwin is a software package that sets up a Unix-like platform on a
+Windows (win32) system.  Here are some specific things you need in
+that environment to build and use Netpbm.  All these programs and
+libraries are optional parts of the cygwin package.
+
+   Programs: 
+    1) gcc suite 
+    2) binutils
+    3) bash
+    4) dlltool
+    5) flex & byacc
+    6) patch
+    7) install
+    8) rm, ln, cp and other file utilities
+   Libraries:
+    1) libtiff (cygtiff3.dll)
+    2) libpng  (cygpng2.dll)
+    3) libjpeg6b (cygjpeg6b.dll)
+    4) libz (cygz.dll)
+
+Find Cygwin at http://sources.redhat.com/cygwin/ .
+
+One problem special to Windows is the common existence of directories
+with space in their names (e.g. Windows 2000's "Documents and
+Settings" directory.  (Such filenames are possible on non-Windows
+systems, but are highly unconventional).  Don't try to build Netpbm in
+such a directory or with files in such a directory.  It ought to work,
+but it just doesn't.  And the error messages are far from helpful,
+since those spaces completely change the nature of the commands that
+include them.
+
+One way to deal with this is to use the Cygwin "mount" facility to map
+the Windows path "c:/Documents and Settings/aaa/bbb/cccc/Distributions" to
+something short and friendly, such as /Distributions.
+
+
+MINGW
+------
+
+You can use Cygwin on Windows to build Netpbm to run on Windows, but
+have the Netpbm programs use Mingw code instead of Cygwin code.
+
+Two reasons to do this:
+
+  - We don't appear to have a way to statically link Cygwin library
+    routines into Netpbm, so you have to have a Cygwin library (DLL)
+    installed on a system as well as Netpbm to run Netpbm.
+
+  - If you link Netpbm with a Cygwin library (some believe even if the
+    program links itself to a Cygwin library at run time), you cannot
+    distribute the resulting Netpbm, or any extension of your own,
+    unless you distribute it under GPL.  That's because Cygwin is
+    publicly distributed under GPL, so you probably got your
+    permission to distribute Cygwin as part of Netpbm from GPL.  A
+    condition of that permission was that you distribute everything you
+    wrap around Cygwin under GPL as well.
+
+Mingw is a small POSIX emulation library for Windows.  See 
+<http://mingw.org>.
+
+Mingw comes with tools you can use to build Netpbm with the Mingw
+library, but you can just use the Cygwin tools if you prefer.
+<http://www.delorie.com/howto/cygwin/mno-cygwin-howto.html> tells how.
+There isn't much to it -- it's just a matter of telling the compiler to
+search for include files and link libraries in a different place,
+which is basically just a -mno-cygwin option and -I and -L options.
+
+You can easily add the compiler options you need at the bottom of
+Makefile.config after you create it with the automatic configurator
+('configure').
+
+Mingw does not have the Unix process management facilities (fork/wait,
+etc.), so Netpbm will build without some of its function if you use
+Mingw.  Areas of Netpbm that require those facilities are so arcane you
+probably will not miss them.
+
+
+
+
diff --git a/doc/README.DJGPP b/doc/README.DJGPP
new file mode 100644
index 00000000..773a22e7
--- /dev/null
+++ b/doc/README.DJGPP
@@ -0,0 +1,65 @@
+Warning: The Netpbm distribution uses long filenames, so you can only
+build the package in a W9x environment, with LFN support.
+
+To build Netpbm with DJGPP v2.0x you need:
+
+        - djgpp: gcc, make, and binutils
+        - bash 
+        - fileutils
+        - sed
+        - groff (to convert man pages to cat format)
+        - man and less (to read the converted man pages)
+
+You can fetch all packages on Simtelnet in the DJGPP tree:
+ftp://ftp.simtel.net/pub/simtelnet/gnu/djgpp/
+
+You need too four external libraries to add tiff, png and jpeg support
+to Netpbm (you can build without them, modifying Makefile.config.djgpp
+but I think it makes Netpbm useless).
+
+        - Libtiff (www.libtiff.org)
+        - Libjpeg (www.ijg.org)
+        - Libpng  (www.libpng.org/pub/png/libpng.html)
+        - Libz    (www.info-zip.org/pub/infozip)
+
+All libraries come with guidelines to compile ok with DJGPP. At this time
+you can get binary packages of libjpeg, libpng and libz on Simtelnet.
+
+
+To build the Netpbm programs do the following:
+
+  Copy Makefile.config.djgpp to Makefile.config
+
+  Run "touch Makefile.config" to prevent Make from updating the file.
+
+  Read the Makefile.config and make any changes you want
+
+  Do "make merge"
+
+  Do "make install-merge" to install binaries and man pages.
+
+  Makefile.config.djgpp is set up to install these in TMPDIR/netpbm
+  (normally /dgjpp/tmp/netpbm).  If everything worked OK, you can
+  move the contents of this directory to your DJGPP production tree.
+
+
+WARNING
+
+There are some problems that have to be addressed if using the
+binaries in pure DOS OS, without long file names, but it works OK
+using "netpbm <utility>" (<utility> is the real Netpbm program to be
+used, for example: 
+
+      netpbm ppmtobmp <testimg.ppm >testimg.bmp
+
+instead of the usual
+
+      ppmtobmp <testimg.ppm >testimg.bmp).
+
+
+
+This procedure and the code to adapt Netpbm to DJGPP was contributed by
+M.Alvarez <malfer@teleline.es> in August 2001.  You can find his 
+current information on using Netpbm with DJGPP and prebuilt binaries
+at <http://www.terra.es/personal/malfer/netpbm/netpbm.htm>.
+
diff --git a/doc/USERDOC b/doc/USERDOC
new file mode 100644
index 00000000..3cd2b383
--- /dev/null
+++ b/doc/USERDOC
@@ -0,0 +1,155 @@
+Since May 2002, Netpbm does not have traditional man pages for
+documentation.  BUT YOU CAN CONFIGURE NETPBM, IF YOU WANT, SO YOU GET
+ESSENTIALLY THE SAME 'MAN' FUNCTION AS WITH A TRADITIONAL UNIX PACKAGE.
+
+Netpbm's maintainer believes man pages are obsolete and too limiting,
+and doesn't have time to maintain the documentation in multiple
+formats.  So instead of classic nroff man page format, the Netpbm
+documentation is available as HTML, with one HTML file per program,
+plus some others.  The current user manual is accessible on the World
+Wide Web at <http://netpbm.sourceforge.net/doc>, and if it's practical
+for you, you should access it there instead of making a local copy.
+This manual is always up to date.  It is not maintained on a release
+schedule like the source code is, but rather updated continuously.
+The user manual describes past Netpbm function as well as the present,
+so you can use the current manual with old Netpbm code.
+
+
+INSTALLING A LOCAL COPY OF DOCUMENTATION
+----------------------------------------
+
+If accessing the manual on the World Wide Web is not convenient for
+you (for example, if you want to access it from a computer that is not
+always connected to the Internet), just make a local copy of the web
+site files using GNU Wget:
+
+  wget --recursive --relative http://netpbm.sourceforge.net/doc/
+
+The above copies all the HTML files from the web site into your
+current directory, under a subdirectory 'netpbm.sourceforge.net/doc'
+that it creates.  You can browse those files directly with a web
+browser.  If you don't have Wget, get it from
+ftp://ftp.gnu.org/gnu/wget.  It is very useful.
+
+
+GETTING COMMAND HELP WITH A "MAN" COMMAND
+-----------------------------------------
+
+You can get the same quick access to program documentation with this
+HTML setup as with traditional man pages, using the Manweb program.
+This works whether you use the www copy or a local copy of the HTML
+files.  Manweb is distributed with Netpbm.  With Manweb and Netpbm
+installed and configured appropriately (see below), you can type
+
+  man netpbm
+
+and get the top level page of the Netpbm user manual (with hyperlinks to
+all the other pages), or
+
+  man netpbm ppmtogif
+
+or 
+
+  man ppmtogif
+
+to go straight to the Ppmtogif documentation.
+
+Installnetpbm normally installs Manweb and the netpbm.url file that
+Manweb needs to find the Netpbm documentation.  Through the Configure
+dialog, or editing Makefile.config, you determine whether Manweb
+accesses the master web copy or a local copy you installed.
+
+Installnetpbm installs the program as 'manweb'.  If you want to invoke
+it as 'man', you'll have to set that up yourself.  Perhaps with a
+symbolic link from 'man' to 'manweb'.  Note that 'manweb' is mostly
+backward compatible with 'man' so that this is a reasonable thing to
+do.  Manweb can find documentation on the web, in HTML files, in GNU
+info files, and in traditional man pages.
+
+
+In a standard installation of Netpbm, Installnetpbm also creates a
+traditional man page for every Netpbm program it installs, but the man
+page just tells you to go to the HTML file.  This way, even if the "man"
+program isn't capable of reading the HTML documentation and even if the
+user doesn't know specifically where Netpbm documentation lives, he isn't
+stranded without information.
+
+
+VIEWING NETPBM DOC WITH TRADITIONAL MAN PROGRAM
+-----------------------------------------------
+
+Some people want to be able to access the Netpbm documentation with an
+existing man program that doesn't know HTML.  You can install the
+documentation that way, with some loss of quality.  There are two
+ways:
+
+  1) convert the HTML to troff with the 'makeman' program in the
+     'buildtools' directory of the Netpbm source tree.  This is a 
+     Python program.
+
+  2) convert the HTML to formatted plain text (suitable as man "cat"
+     pages) with the 'makecat' program in the 'buildtools' directory
+     of the Netpbm source tree.  This program just does a 
+     'lynx -dump'.
+
+The "loss of quality" mentioned above is because:
+
+  - The classic Unix manual format isn't as expressive as the
+    worldwide web format; you can't convert down losslessly.
+
+  - There is less maintenance effort put into maintaining the
+    secondary non-web format.  It requires certain idioms to be
+    followed in the HTML source and lists of man pages to be
+    separately maintained.  This maintenance is essentially done on a
+    fault basis -- when someone notices the Unix man pages aren't
+    right, he fixes something.
+    
+    Bear in mind that the person who writes most of the Netpbm
+    documentation updates never sees the troff versions; he uses
+    Manweb, which renders directly from the HTML.
+
+Also, these methods require manual effort, and technical
+understanding, on your part to set up.  Setting it up is too complex
+for an automated process to do it for you with any significant
+integrity.  The examples are guidelines and you shouldn't expect them
+to work literally in your situtation.
+
+Here is an example of making troff pages:
+
+  mkdir netpbmdoc
+  cd netpbmdoc
+  wget --recursive --relative http://netpbm.sourceforge.net/doc/
+  cd netpbm.sourceforge.net/doc
+  make MAKEMAN=/usr/src/netpbm/buildtools/makeman \
+    -f /usr/src/netpbm/buildtools/Makefile.manpage manpages
+  make -f /usr/src/netpbm/buildtools/Makefile.manpage manpages
+  make -f /usr/src/netpbm/buildtools/Makefile.manpage installman
+  cd ../../..
+  rm -r netpbmdoc
+
+  man ppmtogif
+
+Here is an example of making "cat" pages:
+
+  mkdir netpbmdoc
+  cd netpbmdoc
+  wget --recursive --relative http://netpbm.sourceforge.net/doc/
+  cd netpbm.sourceforge.net/doc
+  /usr/src/netpbm/buildtools/makecat *.html
+  cp *.1 /usr/man/cat1/
+  cd ../../..
+  rm -r netpbmdoc
+
+  man ppmtogif
+
+
+DOCBOOK
+-------
+
+You can turn the Netpbm user manual into Docbook XML pages using
+Doclifter.  Because Doclifter works on troff pages, you have to
+convert the documentation down to troff first, which means the Docbook
+pages are of lower quality than the main HTML documentation.
+
+To create Docbook XML, follow the example above for creating troff
+pages, and use 'make xmlpages' instead of 'make manpages'.
diff --git a/doc/copyright_summary b/doc/copyright_summary
new file mode 100644
index 00000000..9ab3a784
--- /dev/null
+++ b/doc/copyright_summary
@@ -0,0 +1,382 @@
+Here is an analysys of Netpbm copyrights done by Steve McIntyre
+<stevem@chiark.greenend.org.uk> finishing on October 7, 2001.
+
+It is based on Netpbm 9.20, so it doesn't cover material added since
+then.
+
+
+
+Copyright:
+==========
+
+Netpbm has a very complicated licensing setup, as it is a collection
+of hundreds of small utility programs, each with an individual
+copyright/license. From inspection of the source and documentation in
+the package, I believe the following programs are DFSG-free. They have
+a variety of licenses, which I have summarised below for convenience.
+
+A:  The most common netpbm license - clearly the original from Jef Poskanzer:
+
+    Permission to use, copy, modify, and distribute this software and its
+    documentation for any purpose and without fee is hereby granted, provided
+    that the above copyright notice appear in all copies and that both that
+    copyright notice and this permission notice appear in supporting
+    documentation.  This software is provided "as is" without express or
+    implied warranty.
+
+The core libp[gbnp]m library code all also seems to follow this license.
+
+B:  The standard BSD license. See /usr/share/common-licenses/BSD
+
+C:  GNU General Public License v2. See /usr/share/common-licenses/GPL.
+
+D:  The Independent JPEG Group license:
+
+    Permission is hereby granted to use, copy, modify, and distribute
+    this software (or portions thereof) for any purpose, without fee,
+    subject to these conditions: (1) If any part of the source code
+    for this software is distributed, then this README file must be
+    included, with this copyright and no-warranty notice unaltered;
+    and any additions, deletions, or changes to the original files
+    must be clearly indicated in accompanying documentation.  (2) If
+    only executable code is distributed, then the accompanying
+    documentation must state that "this software is based in part on
+    the work of the Independent JPEG Group".  (3) Permission for use
+    of this software is granted only if the user accepts full
+    responsibility for any undesirable consequences; the authors
+    accept NO LIABILITY for damages of any kind.
+
+    These conditions apply to any software derived from or based on
+    the IJG code, not just to the unmodified library.  If you use our
+    work, you ought to acknowledge us.
+
+    Permission is NOT granted for the use of any IJG author's name or
+    company name in advertising or publicity relating to this software
+    or products derived from it.  This software may be referred to
+    only as "the Independent JPEG Group's software".
+
+    We specifically permit and encourage the use of this software as
+    the basis of commercial products, provided that all warranty or
+    liability claims are assumed by the product vendor.
+
+E:  This software is copyrighted as noted below.  It may be freely copied,
+    modified, and redistributed, provided that the copyright notice is
+    preserved on all copies.
+
+    There is no warranty or other guarantee of fitness for this software,
+    it is provided solely "as is".  Bug reports or fixes may be sent
+    to the author, who may or may not act on them as he desires.
+
+    You may not include this software in a program or other software product
+    without supplying the source, or without informing the end-user that the
+    source is available for no extra charge.
+    
+    If you modify this software, you should include a notice giving the
+    name of the person performing the modification, the date of modification,
+    and the reason for such modification.
+
+F:  The program and documentation may be freely distributed by anyone in source
+    or binary format. Please clearly note any changes.
+
+G:  Public domain:
+
+    This software is in the public domain.  Permission to use, copy,
+    modify, and distribute this software and its documentation for any
+    purpose and without fee is hereby granted, without any conditions or
+    restrictions.
+
+H:  Public domain, slightly different wording:
+
+    Permission  to  use, copy, modify, and distribute this software and
+    its documentation  for  any  purpose  and  without  fee  is  hereby
+    granted,  without any conditions or restrictions.  This software is
+    provided "as is" without express or implied warranty.
+
+I:  A variation on the BSD license
+
+    Permission to use, copy, modify, distribute, and  sell  this
+    software  and  its  documentation  for any purpose is hereby
+    granted without  fee,  provided  that  the  above  copyright
+    notice  appear  in  all  copies and that both that copyright
+    notice and this permission notice appear in supporting documentation,
+    and  that  the  name  of the  Smithsonian Astrophysical
+    Observatory not be used in advertising or publicity
+    pertaining to distribution of the software without specific,
+    written  prior  permission.   The Smithsonian  Astrophysical
+    Observatory makes no representations about  the  suitability
+    of  this  software for any purpose.  It is provided  "as is"
+    without express or implied warranty.
+    THE  SMITHSONIAN  ASTROPHYSICAL  OBSERVATORY  DISCLAIMS  ALL
+    WARRANTIES  WITH  REGARD  TO  THIS  SOFTWARE,  INCLUDING ALL
+    IMPLIED  WARRANTIES  OF  MERCHANTABILITY AND FITNESS, IN  NO
+    EVENT SHALL THE  SMITHSONIAN  ASTROPHYSICAL  OBSERVATORY  BE
+    LIABLE FOR  ANY SPECIAL, INDIRECT  OR  CONSEQUENTIAL DAMAGES
+    OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS  OF USE,  DATA
+    OR  PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+    OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION  WITH
+    THE  USE OR PERFORMANCE OF THIS SOFTWARE.
+
+411toppm:      (B) 2001      Steve Allen <sla@alumni.caltech.edu>
+anytopnm:      (A) 1991      Jef Poskanzer
+asciitopgm:    (A) 1989      Wilson H. Bent, Jr
+atktopbm:      (A) 1991      Bill Janssen
+bioradtopgm:   (A) 1993      Oliver Trepte <oliver@fysik4.kth.se>
+bmptoppm:      (A) 1992      David W. Sanderson
+brushtopbm:    (A) 1988      Jef Poskanzer
+cmuwmtopbm:    (A) 1989      Jef Poskanzer
+eyuvtoppm:     (B) 1995      The Regents of the University of California
+fiascotopnm:   (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+fitstopnm:     (A) 1989      Jef Poskanzer
+fstopgm:       (A) 1989      Jef Poskanzer
+g3topbm:       (A) 1989      Paul Haeberli <paul@manray.sgi.com>
+gemtopbm:      (A) 1988      Diomidis D. Spinellis
+gemtopnm:      (A) 1988      Diomidis D. Spinellis
+giftopnm:      (A) 1990-1993 David Koblas <koblas@netcom.com>
+gouldtoppm:    (A) 1990      Stephen P. Lesniewski
+hipstopgm:     (A) 1989      Jef Poskanzer
+icontopbm:     (A) 1988      Jef Poskanzer
+ilbmtoppm:     (A) 1989      Jef Poskanzer
+imgtoppm:      (A) 1989      Jef Poskanzer
+jbigtopnm:     (C) 2000      Markus Kuhn <mkuhn@acm.org>
+jpegtopnm:     (D) 1991-2000 Thomas G. Lane, Bryan Henderson
+leaftoppm:     (A) 1994      Bill O'Donnell
+lispmtopgm:    (A) 1988      Jef Poskanzer, Jamie Zawinski
+macptopbm:     (A) 1988      Jef Poskanzer
+mdatopbm:      (C) 1999      John Elliott <jce@seasip.demon.co.uk>
+mgrtopbm:      (A) 1989      Jef Poskanzer
+mtvtoppm:      (A) 1989      Jef Poskanzer
+neotoppm:      (D) 2001      Teemu Hukkanen <tjhukkan@iki.fi>
+palmtopnm:     (A) 1995-2000 Ian Goldberg, Bill Janssen
+pamcut:        (A) 1989      Jef Poskanzer
+pamfile:       (A) 1991      Jef Poskanzer
+pamoil:        (A) 1990      Wilson Bent <whb@hoh-2.att.com>
+pbmclean:      (A) 1989-1990 Jef Poskanzer, Angus Duggan
+pbmlife:       (A) 1988-1991 Jef Poskanzer
+pbmmake:       (A) 1988      Jef Poskanzer
+pbmmask:       (A) 1989-1991 Jef Poskanzer
+pbmpage:       (C) 1998      Tim Norman
+pbmpscale:     (A) 1989-1990 Jef Poskanzer, Angus Duggan
+pbmreduce:     (A) 1989      Jef Poskanzer
+pbmtext:       (A) 1991      Jef Poskanzer
+pbmto10x:      (A) 1990-1994 Ken Yap
+pbmtoascii:    (A) 1988-1992 Jef Poskanzer
+pbmtoatk:      (A) 1991      Bill Janssen
+pbmtobbnbg:    (A) 1989      Mike Parker
+pbmtocmuwm:    (A) 1989      Jef Poskanzer
+pbmtoepsi:     (A) 1988      Jef Poskanzer
+pbmtoepson:    (A) 1990      Jef Poskanzer, John Tiller <tiller@galois.msfc.nasa.gov>
+pbmtog3:       (A) 1989      Paul Haeberli <paul@manray.sgi.com>
+pbmtogem:      (A) 1988      Jef Poskanzer, David Beckemeyer
+pbmtogo:       (A) 1988-1989 Jef Poskanzer, Michael Haberler, Bo Thide'
+pbmtoicon:     (A) 1988      Jef Poskanzer
+pbmtolj:       (A) 1988      Jef Poskanzer, Michael Haberler
+pbmtomacp:     (A) 1988      Douwe vand der Schaaf
+pbmtomda:      (C) 1999      John Elliott <jce@seasip.demon.co.uk>
+pbmtomgr:      (A) 1989      Jef Poskanzer
+pbmtonokia:    (A) 2001      OMS Open Media System GmbH, Tim R<FC>hsen <tim.ruehsen@openmediasystem.de>
+pbmtopgm:      (A) 1989-1990 Jef Poskanzer, Angus Duggan
+pbmtopi3:      (A) 1988      David Beckemeyer and Jef Poskanzer
+pbmtoplot:     (A) 1990      Arthur David Olson
+pbmtoppa:      (C) 1998      Tim Norman
+pbmtopsg3:     (C) 2001      Kristof Koehler <kristof@fachschaft.physik.uni-karlsruhe.de>
+pbmtoptx:      (A) 1988      Jef Poskanzer
+pbmtowbmp:     (A) 1999      Terje Sannum <terje@looplab.com>
+pbmtox10bm:    (A) 1988      Jef Poskanzer
+pbmtoxbm:      (A) 1988      Jef Poskanzer
+pbmtoybm:      (A) 1991      Jamie Zawinski and Jef Poskanzer
+pbmtozinc:     (A) 1988      James Darrell McCauley <jdm5548@diamond.tamu.edu>, Jef Poskanzer
+pbmupc:        (A) 1988      Jef Poskanzer
+pcxtoppm:      (A) 1990      Michael Davidson
+pgmbentley:    (A) 1990      Wilson Bent <whb@hoh-2.att.com>
+pgmcrater:     (H) 1989      John Walker <kelvin@autodesk.com>
+pgmedge:       (A) 1989      Jef Poskanzer
+pgmenhance:    (A) 1989-1991 Jef Poskanzer
+pgmhist:       (A) 1989      Jef Poskanzer
+pgmkernel:     (A) 1992      Alberto Accomazzi, Smithsonian Astrophysical Observatory
+pgmnoise:      (A) 1993      Frank Neumann
+pgmnorm:       (A) 1989-1991 Jef Poskanzer
+pgmramp:       (A) 1989      Jef Poskanzer
+pgmslice:      (C) 2000      Jos Dingjan <jos@tuatha.org>
+pgmtexture:    (A) 1991      Texas Agricultural Experiment Station, James Darrell McCauley
+pgmtofs:       (A) 1991      Jef Poskanzer
+pgmtolispm:    (A) 1991      Jamie Zawinski and Jef Poskanzer
+pgmtopbm:      (A) 1989      Jef Poskanzer
+pgmtoppm:      (A) 1991      Jef Poskanzer
+pi1toppm:      (A) 1991      Steve Belczyk <seb3@gte.com> and Jef Poskanzer
+pi3topbm:      (A) 1988      David Beckemeyer and Diomidis D. Spinellis
+picttoppm:     (A) 1989-1993 George Phillips <phillips@cs.ubc.ca>
+pjtoppm:       (A) 1990      Christos Zoulas <christos@ee.cornell.edu>
+pngtopnm:      (A) 1995-1998 Alexander Lehmann <alex@hal.rhein-main.de>, Willem van Schaik <willem@schaik.com>
+pnmalias:      (A) 1992      Alberto Accomazzi, Smithsonian Astrophysical Observatory
+pnmarith:      (A) 1989-1991 Jef Poskanzer
+pnmcat:        (A) 1989-1991 Jef Poskanzer
+pnmcomp:       (A) 1992      David Koblas
+pnmconvol:     (A) 1989-1995 Jef Poskanzer, Mike Burns <burns@chem.psu.edu>
+pnmcrop:       (A) 1988      Jef Poskanzer
+pnmcut:        (A) 1989      Jef Poskanzer
+pnmdepth:      (A) 1989-1991 Jef Poskanzer
+pnmenlarge:    (A) 1989      Jef Poskanzer
+pnmfile:       (A) 1991      Jef Poskanzer
+pnmflip:       (A) 1989      Jef Poskanzer
+pnmgamma:      (A) 1991      Jef Poskanzer and Bill Davidson
+pnmhisteq:     (H) 1995      John Walker <kelvin@fourmalib.ch>
+pnmhistmap:    (A) 1993      Wilson H. Bent, Jr <whb@usc.edu>
+pnmindex:      (A) 1991      Jef Poskanzer
+pnminterp:     (C) 1998-2000 Russell Marks <russell.marks@ntlworld.com>
+pnminterp-gen: (C) 1998-2000 Russell Marks <russell.marks@ntlworld.com>
+pnminvert:     (A) 1989      Jef Poskanzer
+pnmmargin:     (A) 1991      Jef Poskanzer
+pnmmontage:    (A) 2000      Ben Olmstead
+pnmnlfilt:     (A) 1993      Graeme W. Gill <graeme@labtam.oz.au>
+pnmnoraw:      (A) 1991      Jef Poskanzer
+pnmpad:        (A) 1989-1990 Jef Poskanzer, Angus Duggan
+pnmpaste:      (A) 1989      Jef Poskanzer
+pnmpsnr:       (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+pnmrotate:     (A) 1989-1991 Jef Poskanzer
+pnmscale:      (A) 1989-1991 Jef Poskanzer
+pnmscalefixed: (A) 1989-1991 Jef Poskanzer
+pnmshear:      (A) 1989-1991 Jef Poskanzer
+pnmsmooth:     (A) 1984      Mike Burns <burns@chem.psu.edu>
+pnmsplit:      (?) 2000      Bryan Henderson <bryanh@giraffe-data.com>
+pnmtile:       (A) 1989      Jef Poskanzer
+pnmtoddif:     (A) 1992      Digital Equipment Corporation, Burkhard Neidecker-Lutz
+pnmtofiasco:   (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+pnmtofits:     (C) 1989      Wilson H. Bent <whb@hoh-2.att.com>
+pnmtojbig:     (C) 2000      Markus Kuhn <mkuhn@acm.org>
+pnmtojpeg:     (D) 1991-2001 Thomas G. Lane, Bryan Henderson
+pnmtopalm:     (A) 1995-2000 Ian Goldberg, Bill Janssen
+pnmtoplainpnm: (A) 1991      Jef Poskanzer
+pnmtopng:      (A) 1995-1998 Alexander Lehmann <alex@hal.rhein-main.de>, Willem van Schaik <willem@schaik.com>
+pnmtops:       (A) 1989      Jef Poskanzer
+pnmtorast:     (A) 1989-1991 Jef Poskanzer
+pnmtorle:      (E) 1994      Minnesota Supercomputer Center, Inc, Wes Barris <wes@msc.edu>
+pnmtosgi:      (A) 1994      Ingo Wilken <Ingo.Wilken@informatik.uni-oldenburg.de>
+pnmtosir:      (A) 1991      Marvin Landis
+pnmtotiff:     (A) 1990      Sun Microsystems, Inc, Jef Poskanzer
+pnmtotiffcmyk: (C) 1999      Andrew Cooke (Jara Software) <jara@andrewcooke.free-online.co.uk>
+pnmtoxwd:      (A) 1989-1991 Jef Poskanzer
+ppm3d:         (A) 1989      Jef Poskanzer
+ppmbrighten:   (A) 1989-1990 Jef Poskanzer, Brian Moffet
+ppmchange:     (A) 1991      Wilson H. Bent, Jr
+ppmcie:        (H) 1995      John Walker <kelvin@fourmilab.ch>
+ppmcolormask:  (?) 2000      Bryan Henderson <bryanh@giraffe-data.com>
+ppmdim:        (A) 1993      Frank Neumann
+ppmdist:       (A) 1993      Dan Stromberg
+ppmdither:     (A) 1991      Christos Zoulas
+ppmfade:       (?) 1994      Minnesota Supercomputer Center, Inc, Wes Barris <wes@msc.edu>
+ppmflash:      (A) 1993      Frank Neumann
+ppmforge:      (H) 1989      John Walker <kelvin@autodesk.com>
+ppmhist:       (A) 1989      Jef Poskanzer
+ppmlabel:      (H) 1995      John Walker <kelvin@fourmilab.ch>
+ppmmake:       (A) 1989      Jef Poskanzer
+ppmmix:        (A) 1993      Frank Neumann
+ppmnorm:       (A) 1989-1991 Jef Poskanzer, Wilson H. Bent, Jr <whb@usc.edu>
+ppmntsc:       (E) 1993      Minnesota Supercomputer Center, Inc, Wes Barris <wes@msc.edu>
+ppmpat:        (A) 1989-1991 Jef Poskanzer
+ppmquant:      (A) 1989-1991 Jef Poskanzer
+ppmquantall:   (A) 1991      Jef Poskanzer
+ppmqvga:       (F) 1991-1992 Bill Davidsen, Lyle Rains <lrains@netcom.com>
+ppmrainbow:	   (A) 2001      Arjen Bax, Bryan Henderson
+ppmrelief:     (A) 1990      Wilson H. Bent, Jr
+ppmshadow:     (G) 1997      John Walker <kelvin@fourmilab.ch>
+ppmshift:      (A) 1993      Frank Neumann
+ppmspread:     (A) 1993      Frank Neumann
+ppmtoacad:     (H) 1991      John Walker <kelvin@autodesk.com>
+ppmtobmp:      (A) 1992      David W. Sanderson
+ppmtoeyuv:     (B) 1995      The Regents of the University of California
+ppmtoicr:      (A) 1990      Kanthan Pillay <svpillay@Princeton.EDU>
+ppmtoilbm:     (A) 1989      Jef Poskanzer
+ppmtoleaf:     (A) 1994      Bill O'Donnell
+ppmtolj:       (A) 2000      Jonathan Melvin <jonathan.melvin@heywood.co.uk>
+ppmtomap:      (A) 1989      Jef Poskanzer
+ppmtomitsu:    (A) 1992-1993 S.Petra Zeidler
+ppmtompeg:     (B) 1995      The Regents of the University of California
+ppmtoneo:      (D) 2001      Teemu Hukkanen <tjhukkan@iki.fi>
+ppmtopcx:      (B) 1994      Ingo Wilken <Ingo.Wilken@informatik.uni-oldenburg.de>
+ppmtopgm:      (A) 1989      Jef Poskanzer
+ppmtopi1:      (A) 1991      Jef Poskanzer and Steve Belczyk
+ppmtopict:     (A) 1990      Ken Yap <ken@cs.rochester.edu>
+ppmtopj:       (A) 1990      Christos Zoulas <christos@ee.cornell.edu>
+ppmtopuzz:     (A) 1991      Jef Poskanzer
+ppmtorgb3:     (A) 1991      Jef Poskanzer
+ppmtosixel:    (A) 1991      Rick Vinci
+ppmtotga:      (A) 1989-1991 Mark Shand and Jef Poskanzer
+ppmtouil:      (A) 1990      Mark W. Snitily, Jef Poskanzer
+ppmtowinicon:  (A) 2000      Lee Benfield <lee@recoil.org>
+ppmtoxpm:      (A) 1990      Mark W. Snitily
+ppmtoyuv:      (A) 1987-1991 Abekas Video Systems Inc, DHD PostImage Inc
+ppmtoyuvsplit: (A) 1993      Andre Beck <Andre_Beck@IRS.Inf.TU-Dresden.de>
+ppmtv:         (A) 1993      Frank Neumann
+psidtopgm:     (A) 1989      Jef Poskanzer
+pstopnm:       (I) 1992      Smithsonian Astrophysical Observatory, Alberto Accomazzi
+qrttoppm:      (A) 1989      Jef Poskanzer
+rasttopnm:     (A) 1989-1991 Jef Poskanzer
+rawtopgm:      (A) 1989      Jef Poskanzer
+rawtoppm:      (A) 1991      Jef Poskanzer
+rgb3toppm:     (A) 1991      Jef Poskanzer
+rletopnm:      (E) 1994      Minnesota Supercomputer Center, Inc, Wes Barris <wes@msc.edu>
+sbigtopgm:     (A) 1998      John Walker
+sgitopnm:      (A) 1994      Ingo Wilken <Ingo.Wilken@informatik.uni-oldenburg.de>
+sirtopnm:      (A) 1991      Marvin Landis
+sldtoppm:      (H) 1991      John Walker <kelvin@autodesk.com>
+spctoppm:      (A) 1991      Jef Poskanzer and Steve Belczyk
+sputoppm:      (A) 1991      Jef Poskanzer and Steve Belczyk
+tgatoppm:      (A) 1989      Jef Poskanzer
+tifftopnm:     (A) 1990      Sun Microsystems, Inc, Jef Poskanzer
+thinkjettopbm: (A) 2001      W. Eric Norum <eric.norum@usask.ca>
+wbmptopbm:     (A) 1999      Terje Sannum <terje@looplab.com>
+winicontoppm:  (A) 2000      Lee Benfield <lee@recoil.org>
+xbmtopbm:      (A) 1988      Jef Poskanzer
+ximtoppm:      (A) 1991      Jef Poskanzer
+xpmtoppm:      (A) 1991      Jef Poskanzer
+xvminitoppm:   (A) 1993      Ingo Wilken <Ingo.Wilken@informatik.uni-oldenburg.de>
+xwdtopnm:      (A) 1989-1991 Jef Poskanzer
+ybmtopbm:      (A) 1988      Jamie Zawinski and Jef Poskanzer
+yuvsplittoppm: (A) 1993      Marcel Wijkstra <wijkstra@fwi.uva.nl>
+yuvtoppm:      (A) 1987-1991 Jef Poskanzer, Abekas Video Systems Inc., DHD PostImage Inc.
+zeisstopnm:    (A) 1993      Oliver Trepte <oliver@fysik4.kth.se>
+
+==============================================================================
+NON-FREE:
+
+The following programs have restrictions on their use or source:
+
+hpcdtoppm package:
+==================
+hpcdtoppm pcdindex
+
+*  Copyright (c) 1992, 1993, 1994 by Hadmut Danisch (danisch@ira.uka.de).
+*  Permission to use and distribute this software and its
+*  documentation for noncommercial use and without fee is hereby granted,
+*  provided that the above copyright notice appear in all copies and that
+*  both that copyright notice and this permission notice appear in
+*  supporting documentation. It is not allowed to sell this software in 
+*  any way. This software is not public domain.
+
+Note that Hpcdtoppm and Pcdindex are not distributed in the main Netpbm
+package on Sourceforge, as Sourceforge does not offer distribution services
+for software licensed in this way.  These programs are distributed in the
+supplemental Netpbm package on Ibiblio.
+
+filter to create GIFs - patent restricted. (http://www.unisys.com/unisys/lzw/)
+ppmtogif:      (A) 1989      Jef Poskanzer
+
+The Unisys patent, at least in the US, expired in 2003.
+
+=============================================================================
+UNKNOWN, SO NOT DISTRIBUTED:
+
+The following programs are in the upstream source package, but do not
+have sufficient copyright and license information to be redistributed:
+
+pamchannel:  No copyright / license text found
+pamtopnm:    No copyright / license text found
+pbmto4425:   No copyright / license text found
+pbmtoln03:   No copyright / license text found
+pbmtolps:    No copyright / license text found
+pbmtopk:     No copyright / license text found
+pktopbm:     No copyright / license text found
+ppmtopjxl:   No copyright / license text found
+spottopgm:   No copyright / license text found
+
+
diff --git a/doc/lgpl_v21.txt b/doc/lgpl_v21.txt
new file mode 100644
index 00000000..b1e3f5a2
--- /dev/null
+++ b/doc/lgpl_v21.txt
@@ -0,0 +1,504 @@
+		  GNU LESSER GENERAL PUBLIC LICENSE
+		       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+		  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+			    NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/doc/netpbm.1 b/doc/netpbm.1
new file mode 100644
index 00000000..5bed86a9
--- /dev/null
+++ b/doc/netpbm.1
@@ -0,0 +1,4 @@
+Are you looking for the man pages?
+
+Netpbm does not have man pages in the conventional Unix form.  Please see 
+the file doc/USERDOC for a complete explanation of this.
diff --git a/doc/netpbm.html b/doc/netpbm.html
new file mode 100644
index 00000000..471c1d54
--- /dev/null
+++ b/doc/netpbm.html
@@ -0,0 +1,3 @@
+Are you looking for the Netpbm user manual?
+
+It's not in the source code package.  Please see the file doc/USERDOC.
diff --git a/editor/Makefile b/editor/Makefile
new file mode 100644
index 00000000..18165666
--- /dev/null
+++ b/editor/Makefile
@@ -0,0 +1,86 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = editor
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+# We tend to separate out the build targets so that we don't have
+# any more dependencies for a given target than it really needs.
+# That way, if there is a problem with a dependency, we can still
+# successfully build all the stuff that doesn't depend upon it.
+# This package is so big, it's useful even when some parts won't 
+# build.
+
+PORTBINARIES = pamaddnoise pamcomp pamcut \
+	       pamdeinterlace pamdice pamditherbw pamedge \
+	       pamenlarge \
+	       pamflip pamfunc pammasksharpen pammixinterlace \
+	       pamoil pamperspective pampop9 \
+	       pamscale pamstretch pamthreshold \
+	       pbmclean pbmlife pbmmask pbmpscale pbmreduce \
+	       pgmabel pgmbentley pgmdeshadow pgmenhance \
+	       pgmmedian pgmmorphconv \
+	       pnmalias pnmcat pnmcomp pnmconvol pnmcrop pnmcut \
+	       pnmgamma \
+	       pnmhisteq pnmindex pnminvert pnmmontage \
+	       pnmnlfilt pnmnorm pnmpad pnmpaste \
+	       pnmremap pnmrotate \
+	       pnmscale pnmscalefixed pnmshear pnmsmooth pnmstitch pnmtile \
+	       ppm3d ppmbrighten ppmchange ppmcolormask \
+	       ppmdim ppmdist ppmdither ppmdraw \
+	       ppmflash ppmglobe ppmlabel ppmmix \
+	       ppmntsc ppmrelief ppmshift ppmspread ppmtv
+
+# We don't include programs that have special library dependencies in the
+# merge scheme, because we don't want those dependencies to prevent us
+# from building all the other programs.
+
+NOMERGEBINARIES = 
+MERGEBINARIES = $(PORTBINARIES)
+
+
+BINARIES = $(MERGEBINARIES) $(NOMERGEBINARIES)
+SCRIPTS = pnmflip ppmfade ppmquant ppmquantall ppmshadow \
+	  pamstretch-gen pnmmargin pnmquant 
+
+OBJECTS = $(BINARIES:%=%.o)
+
+MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2)
+
+.PHONY: all
+all: $(BINARIES)
+
+include $(SRCDIR)/Makefile.common
+
+install.bin: install.bin.local
+
+.PHONY: install.bin.local
+install.bin.local: $(PKGDIR)/bin
+# Remember that $(SYMLINK) might just be a copy command.
+# backward compatibility: program used to be named pnmnoraw
+# backward compatibility: program used to be pnminterp
+	cd $(PKGDIR)/bin ; \
+	rm -f pnminterp; \
+	$(SYMLINK) pamstretch$(EXE) pnminterp
+# pamoil replaced pgmoil in June 2001.
+	cd $(PKGDIR)/bin ; \
+	rm -f pgmoil ; \
+	$(SYMLINK) pamoil$(EXE) pgmoil
+# In March 2002, pnmnorm replaced ppmnorm and pgmnorm
+	cd $(PKGDIR)/bin ; \
+	rm -f ppmnorm ; \
+	$(SYMLINK) pnmnorm$(EXE) ppmnorm 
+	cd $(PKGDIR)/bin ; \
+	rm -f pgmnorm ; \
+	$(SYMLINK) pnmnorm$(EXE) pgmnorm
+# In March 2003, pamedge replaced pgmedge
+	cd $(PKGDIR)/bin ; \
+	rm -f pgmedge ; \
+	$(SYMLINK) pamedge$(EXE) pgmedge
+# In October 2004, pamenlarge replaced pnmenlarge
+	cd $(PKGDIR)/bin ; \
+	rm -f pnmenlarge ; \
+	$(SYMLINK) pamenlarge$(EXE) pnmenlarge
diff --git a/editor/dithers.h b/editor/dithers.h
new file mode 100644
index 00000000..24a9fb39
--- /dev/null
+++ b/editor/dithers.h
@@ -0,0 +1,87 @@
+/*
+** dithers.h
+**
+** Here are some dithering matrices.  They are all taken from "Digital
+** Halftoning" by Robert Ulichney, MIT Press, ISBN 0-262-21009-6.
+*/
+
+
+#if 0
+/*
+** Order-6 ordered dithering matrix.  Note that smaller ordered dithers
+** have no advantage over larger ones, so use dither8 instead.
+*/
+static int const dither6[8][8] = {
+  {  1, 59, 15, 55,  2, 56, 12, 52 },
+  { 33, 17, 47, 31, 34, 18, 44, 28 },
+  {  9, 49,  5, 63, 10, 50,  6, 60 },
+  { 41, 25, 37, 21, 42, 26, 38, 22 },
+  {  3, 57, 13, 53,  0, 58, 14, 54 },
+  { 35, 19, 45, 29, 32, 16, 46, 30 },
+  { 11, 51,  7, 61,  8, 48,  4, 62 },
+  { 43, 27, 39, 23, 40, 24, 36, 20 }
+  };
+#endif
+
+/* Order-8 ordered dithering matrix. */
+static int const dither8[16][16] = {
+  {   1,235, 59,219, 15,231, 55,215,  2,232, 56,216, 12,228, 52,212},
+  { 129, 65,187,123,143, 79,183,119,130, 66,184,120,140, 76,180,116},
+  {  33,193, 17,251, 47,207, 31,247, 34,194, 18,248, 44,204, 28,244},
+  { 161, 97,145, 81,175,111,159, 95,162, 98,146, 82,172,108,156, 92},
+  {   9,225, 49,209,  5,239, 63,223, 10,226, 50,210,  6,236, 60,220},
+  { 137, 73,177,113,133, 69,191,127,138, 74,178,114,134, 70,188,124},
+  {  41,201, 25,241, 37,197, 21,255, 42,202, 26,242, 38,198, 22,252},
+  { 169,105,153, 89,165,101,149, 85,170,106,154, 90,166,102,150, 86},
+  {   3,233, 57,217, 13,229, 53,213,  0,234, 58,218, 14,230, 54,214},
+  { 131, 67,185,121,141, 77,181,117,128, 64,186,122,142, 78,182,118},
+  {  35,195, 19,249, 45,205, 29,245, 32,192, 16,250, 46,206, 30,246},
+  { 163, 99,147, 83,173,109,157, 93,160, 96,144, 80,174,110,158, 94},
+  {  11,227, 51,211,  7,237, 61,221,  8,224, 48,208,  4,238, 62,222},
+  { 139, 75,179,115,135, 71,189,125,136, 72,176,112,132, 68,190,126},
+  {  43,203, 27,243, 39,199, 23,253, 40,200, 24,240, 36,196, 20,254},
+  { 171,107,155, 91,167,103,151, 87,168,104,152, 88,164,100,148, 84} 
+};
+
+/* Order-3 clustered dithering matrix. */
+static int const cluster3[6][6] = {
+  {  9,11,10, 8, 6, 7},
+  { 12,17,16, 5, 0, 1},
+  { 13,14,15, 4, 3, 2},
+  {  8, 6, 7, 9,11,10},
+  {  5, 0, 1,12,17,16},
+  {  4, 3, 2,13,14,15}
+};
+
+/* Order-4 clustered dithering matrix. */
+static int const cluster4[8][8] = {
+  { 18,20,19,16,13,11,12,15},
+  { 27,28,29,22, 4, 3, 2, 9},
+  { 26,31,30,21, 5, 0, 1,10},
+  { 23,25,24,17, 8, 6, 7,14},
+  { 13,11,12,15,18,20,19,16},
+  {  4, 3, 2, 9,27,28,29,22},
+  {  5, 0, 1,10,26,31,30,21},
+  {  8, 6, 7,14,23,25,24,17}
+};
+
+/* Order-8 clustered dithering matrix. */
+static int const cluster8[16][16] = {
+   { 64, 69, 77, 87, 86, 76, 68, 67, 63, 58, 50, 40, 41, 51, 59, 60},
+   { 70, 94,100,109,108, 99, 93, 75, 57, 33, 27, 18, 19, 28, 34, 52},
+   { 78,101,114,116,115,112, 98, 83, 49, 26, 13, 11, 12, 15, 29, 44},
+   { 88,110,123,124,125,118,107, 85, 39, 17,  4,  3,  2,  9, 20, 42},
+   { 89,111,122,127,126,117,106, 84, 38, 16,  5,  0,  1, 10, 21, 43},
+   { 79,102,119,121,120,113, 97, 82, 48, 25,  8,  6,  7, 14, 30, 45},
+   { 71, 95,103,104,105, 96, 92, 74, 56, 32, 24, 23, 22, 31, 35, 53},
+   { 65, 72, 80, 90, 91, 81, 73, 66, 62, 55, 47, 37, 36, 46, 54, 61},
+   { 63, 58, 50, 40, 41, 51, 59, 60, 64, 69, 77, 87, 86, 76, 68, 67},
+   { 57, 33, 27, 18, 19, 28, 34, 52, 70, 94,100,109,108, 99, 93, 75},
+   { 49, 26, 13, 11, 12, 15, 29, 44, 78,101,114,116,115,112, 98, 83},
+   { 39, 17,  4,  3,  2,  9, 20, 42, 88,110,123,124,125,118,107, 85},
+   { 38, 16,  5,  0,  1, 10, 21, 43, 89,111,122,127,126,117,106, 84},
+   { 48, 25,  8,  6,  7, 14, 30, 45, 79,102,119,121,120,113, 97, 82},
+   { 56, 32, 24, 23, 22, 31, 35, 53, 71, 95,103,104,105, 96, 92, 74},
+   { 62, 55, 47, 37, 36, 46, 54, 61, 65, 72, 80, 90, 91, 81, 73, 66}
+};
+
diff --git a/editor/pamaddnoise.c b/editor/pamaddnoise.c
new file mode 100644
index 00000000..9c2d12f7
--- /dev/null
+++ b/editor/pamaddnoise.c
@@ -0,0 +1,486 @@
+/*
+** 
+** Add gaussian, multiplicative gaussian, impulse, laplacian or 
+** poisson noise to a portable anymap.
+** 
+** Version 1.0  November 1995
+**
+** Copyright (C) 1995 by Mike Burns (burns@cac.psu.edu)
+**
+** Adapted to Netpbm 2005.08.09, by Bryan Henderson
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* References
+** ----------
+** "Adaptive Image Restoration in Signal-Dependent Noise" by R. Kasturi
+** Institute for Electronic Science, Texas Tech University  1982
+**
+** "Digital Image Processing Algorithms" by Ioannis Pitas
+** Prentice Hall, 1993  ISBN 0-13-145814-0
+*/
+
+#define _XOPEN_SOURCE   /* get M_PI in math.h */
+
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "pam.h"
+
+#define RANDOM_MASK 0x7FFF  /* only compare lower 15 bits.  Stupid PCs. */
+
+static double const EPSILON = 1.0e-5;
+static double const arand = 32767.0;      /* 2^15-1 in case stoopid computer */
+
+enum noiseType {
+    GAUSSIAN,
+    IMPULSE,  /* aka salt and pepper noise */
+    LAPLACIAN,
+    MULTIPLICATIVE_GAUSSIAN,
+    POISSON,
+    MAX_NOISE_TYPES
+};
+
+
+
+static void
+gaussian_noise(sample   const maxval,
+               sample   const origSample,
+               sample * const newSampleP,
+               float    const sigma1,
+               float    const sigma2) {
+/*----------------------------------------------------------------------------
+   Add Gaussian noise.
+
+   Based on Kasturi/Algorithms of the ACM
+-----------------------------------------------------------------------------*/
+
+    double x1, x2, xn, yn;
+    double rawNewSample;
+
+    x1 = (rand() & RANDOM_MASK) / arand; 
+
+    if (x1 == 0.0)
+        x1 = 1.0;
+    x2 = (rand() & RANDOM_MASK) / arand;
+    xn = sqrt(-2.0 * log(x1)) * cos(2.0 * M_PI * x2);
+    yn = sqrt(-2.0 * log(x1)) * sin(2.0 * M_PI * x2);
+    
+    rawNewSample =
+        origSample + (sqrt((double) origSample) * sigma1 * xn) + (sigma2 * yn);
+
+    *newSampleP = MAX(MIN((int)rawNewSample, maxval), 0);
+}
+
+
+
+static void
+impulse_noise(sample   const maxval,
+              sample   const origSample,
+              sample * const newSampleP,
+              float    const tolerance) {
+/*----------------------------------------------------------------------------
+   Add impulse (salt and pepper) noise
+-----------------------------------------------------------------------------*/
+
+    double const low_tol  = tolerance / 2.0;
+    double const high_tol = 1.0 - (tolerance / 2.0);
+    double const sap = (rand() & RANDOM_MASK) / arand; 
+
+    if (sap < low_tol) 
+        *newSampleP = 0;
+    else if ( sap >= high_tol )
+        *newSampleP = maxval;
+}
+
+
+
+static void
+laplacian_noise(sample   const maxval,
+                double   const infinity,
+                sample   const origSample,
+                sample * const newSampleP,
+                float    const lsigma) {
+/*----------------------------------------------------------------------------
+   Add Laplacian noise
+
+   From Pitas' book.
+-----------------------------------------------------------------------------*/
+    double const u = (rand() & RANDOM_MASK) / arand; 
+                
+    double rawNewSample;
+
+    if (u <= 0.5) {
+        if (u <= EPSILON)
+            rawNewSample = origSample - infinity;
+        else
+            rawNewSample = origSample + lsigma * log(2.0 * u);
+    } else {
+        double const u1 = 1.0 - u;
+        if (u1 <= 0.5 * EPSILON)
+            rawNewSample = origSample + infinity;
+        else
+            rawNewSample = origSample - lsigma * log(2.0 * u1);
+    }
+    *newSampleP = MIN(MAX((int)rawNewSample, 0), maxval);
+}
+
+
+
+static void
+multiplicative_gaussian_noise(sample   const maxval,
+                              double   const infinity,
+                              sample   const origSample,
+                              sample * const newSampleP,
+                              float    const mgsigma) {
+/*----------------------------------------------------------------------------
+   Add multiplicative Gaussian noise
+
+   From Pitas' book.
+-----------------------------------------------------------------------------*/
+    double rayleigh, gauss;
+    double rawNewSample;
+
+    {
+        double const uniform = (rand() & RANDOM_MASK) / arand; 
+        if (uniform <= EPSILON)
+            rayleigh = infinity;
+        else
+            rayleigh = sqrt(-2.0 * log( uniform));
+    }
+    {
+        double const uniform = (rand() & RANDOM_MASK) / arand; 
+        gauss = rayleigh * cos(2.0 * M_PI * uniform);
+    }
+    rawNewSample = origSample + (origSample * mgsigma * gauss);
+
+    *newSampleP = MIN(MAX((int)rawNewSample, 0), maxval);
+}
+
+
+
+static void
+poisson_noise(sample   const maxval,
+              sample   const origSample,
+              sample * const newSampleP,
+              float    const lambda) {
+/*----------------------------------------------------------------------------
+   Add Poisson noise
+-----------------------------------------------------------------------------*/
+    double const x  = lambda * origSample;
+    double const x1 = exp(-x);
+
+    double rawNewSample;
+    float rr;
+    unsigned int k;
+
+    rr = 1.0;  /* initial value */
+    k = 0;     /* initial value */
+    rr = rr * ((rand() & RANDOM_MASK) / arand);
+    while (rr > x1) {
+        ++k;
+        rr = rr * ((rand() & RANDOM_MASK) / arand);
+    }
+    rawNewSample = k / lambda;
+
+    *newSampleP = MIN(MAX((int)rawNewSample, 0), maxval);
+}
+
+
+
+int 
+main(int argc, char * argv[]) {
+
+    FILE * ifP;
+    struct pam inpam;
+    struct pam outpam;
+    tuple * tuplerow;
+    const tuple * newtuplerow;
+    unsigned int row;
+    double infinity;
+
+    int argn;
+    const char * inputFilename;
+    int noise_type;
+    int seed;
+    int i;
+    const char * const usage = "[-type noise_type] [-lsigma x] [-mgsigma x] "
+        "[-sigma1 x] [-sigma2 x] [-lambda x] [-seed n] "
+        "[-tolerance ratio] [pgmfile]";
+
+    const char * const noise_name[] = { 
+        "gaussian",
+        "impulse",
+        "laplacian",
+        "multiplicative_gaussian",
+        "poisson"
+    };
+    int const noise_id[] = { 
+        GAUSSIAN,
+        IMPULSE,
+        LAPLACIAN,
+        MULTIPLICATIVE_GAUSSIAN,
+        POISSON
+    };
+    /* minimum number of characters to match noise name for pm_keymatch() */
+    int const noise_compare[] = {
+        1,
+        1,
+        1,
+        1,
+        1
+    };
+
+    /* define default values for configurable options */
+    float lambda = 0.05;        
+    float lsigma = 10.0;
+    float mgsigma = 0.5;
+    float sigma1 = 4.0;
+    float sigma2 = 20.0;
+    float tolerance = 0.10;
+
+    pnm_init(&argc, argv);
+
+    seed = time(NULL) ^ getpid();
+    noise_type = GAUSSIAN;
+
+    argn = 1;
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+    {
+        if ( pm_keymatch( argv[argn], "-lambda", 3 ) )
+        {
+            ++argn;
+            if ( argn >= argc )
+            {
+                pm_message( 
+                    "incorrect number of arguments for -lambda option" );
+                pm_usage( usage );
+            }
+            else if ( argv[argn][0] == '-' )
+            {
+                pm_message( "invalid argument to -lambda option: %s", 
+                            argv[argn] );
+                pm_usage( usage );
+            }
+            lambda = atof( argv[argn] );
+        }
+        else if ( pm_keymatch( argv[argn], "-lsigma", 3 ) )
+        {
+            ++argn;
+            if ( argn >= argc )
+            {
+                pm_message( 
+                    "incorrect number of arguments for -lsigma option" );
+                pm_usage( usage );
+            }
+            else if ( argv[argn][0] == '-' )
+            {
+                pm_message( "invalid argument to -lsigma option: %s", 
+                            argv[argn] );
+                pm_usage( usage );
+            }
+            lsigma = atof( argv[argn] );
+        }
+        else if ( pm_keymatch( argv[argn], "-mgsigma", 2 ) )
+        {
+            ++argn;
+            if ( argn >= argc )
+            {
+                pm_message( 
+                    "incorrect number of arguments for -mgsigma option" );
+                pm_usage( usage );
+            }
+            else if ( argv[argn][0] == '-' )
+            {
+                pm_message( "invalid argument to -mgsigma option: %s", 
+                            argv[argn] );
+                pm_usage( usage );
+            }
+            mgsigma = atof( argv[argn] );
+        }
+        else if ( pm_keymatch( argv[argn], "-seed", 3 ) )
+        {
+            ++argn;
+            if ( argn >= argc )
+            {
+                pm_message( "incorrect number of arguments for -seed option" );
+                pm_usage( usage );
+            }
+            else if ( argv[argn][0] == '-' )
+            {
+                pm_message( "invalid argument to -seed option: %s", 
+                            argv[argn] );
+                pm_usage( usage );
+            }
+            seed = atoi(argv[argn]);
+        }
+        else if ( pm_keymatch( argv[argn], "-sigma1", 7 ) ||
+                  pm_keymatch( argv[argn], "-s1", 3 ) )
+        {
+            ++argn;
+            if ( argn >= argc )
+            {
+                pm_message( 
+                    "incorrect number of arguments for -sigma1 option" );
+                pm_usage( usage );
+            }
+            else if ( argv[argn][0] == '-' )
+            {
+                pm_message( "invalid argument to -sigma1 option: %s", 
+                            argv[argn] );
+                pm_usage( usage );
+            }
+            sigma1 = atof( argv[argn] );
+        }
+        else if ( pm_keymatch( argv[argn], "-sigma2", 7 ) ||
+                  pm_keymatch( argv[argn], "-s2", 3 ) )
+        {
+            ++argn;
+            if ( argn >= argc )
+            {
+                pm_message( 
+                    "incorrect number of arguments for -sigma2 option" );
+                pm_usage( usage );
+            }
+            else if ( argv[argn][0] == '-' )
+            {
+                pm_message( "invalid argument to -sigma2 option: %s", 
+                            argv[argn] );
+                pm_usage( usage );
+            }
+            sigma2 = atof( argv[argn] );
+        }
+        else if ( pm_keymatch( argv[argn], "-tolerance", 3 ) )
+        {
+            ++argn;
+            if ( argn >= argc )
+            {
+                pm_message( 
+                    "incorrect number of arguments for -tolerance option" );
+                pm_usage( usage );
+            }
+            else if ( argv[argn][0] == '-' )
+            {
+                pm_message( "invalid argument to -tolerance option: %s", 
+                            argv[argn] );
+                pm_usage( usage );
+            }
+            tolerance = atof( argv[argn] );
+        }
+        else if ( pm_keymatch( argv[argn], "-type", 3 ) )
+        {
+            ++argn;
+            if ( argn >= argc )
+            {
+                pm_message( "incorrect number of arguments for -type option" );
+                pm_usage( usage );
+            }
+            else if ( argv[argn][0] == '-' )
+            {
+                pm_message( "invalid argument to -type option: %s", 
+                            argv[argn] );
+                pm_usage( usage );
+            }
+            /* search through list of valid noise types and compare */
+            i = 0;
+            while ( ( i < MAX_NOISE_TYPES ) && 
+                    !pm_keymatch( argv[argn], 
+                                  noise_name[i], noise_compare[i] ) )
+                ++i;
+            if ( i >= MAX_NOISE_TYPES )
+            {
+                pm_message( "invalid argument to -type option: %s", 
+                            argv[argn] );
+                pm_usage( usage );
+            }
+            noise_type = noise_id[i];
+        }
+        else
+            pm_usage( usage );
+        ++argn;
+    }
+
+    if ( argn < argc )
+    {
+        inputFilename = argv[argn];
+        argn++;
+    }
+    else
+        inputFilename = "-";
+
+    if ( argn != argc )
+        pm_usage( usage );
+
+    srand(seed);
+
+    ifP = pm_openr(inputFilename);
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    outpam = inpam;
+    outpam.file = stdout;
+
+    pnm_writepaminit(&outpam);
+
+    tuplerow = pnm_allocpamrow(&inpam);
+    newtuplerow = pnm_allocpamrow(&inpam);
+    infinity = (double) inpam.maxval;
+    
+    for (row = 0; row < inpam.height; ++row) {
+        unsigned int col;
+        pnm_readpamrow(&inpam, tuplerow);
+        for (col = 0; col < inpam.width; ++col) {
+            unsigned int plane;
+            for (plane = 0; plane < inpam.depth; ++plane) {
+                switch (noise_type) {
+                case GAUSSIAN:
+                    gaussian_noise(inpam.maxval,
+                                   tuplerow[col][plane],
+                                   &newtuplerow[col][plane],
+                                   sigma1, sigma2);
+                    break;
+                    
+                case IMPULSE:
+                    impulse_noise(inpam.maxval,
+                                  tuplerow[col][plane],
+                                  &newtuplerow[col][plane],
+                                  tolerance);
+                   break;
+                    
+                case LAPLACIAN:
+                    laplacian_noise(inpam.maxval, infinity,
+                                    tuplerow[col][plane],
+                                    &newtuplerow[col][plane],
+                                    lsigma);
+                    break;
+                    
+                case MULTIPLICATIVE_GAUSSIAN:
+                    multiplicative_gaussian_noise(inpam.maxval, infinity,
+                                                  tuplerow[col][plane],
+                                                  &newtuplerow[col][plane],
+                                                  mgsigma);
+                    break;
+                    
+                case POISSON:
+                    poisson_noise(inpam.maxval,
+                                  tuplerow[col][plane],
+                                  &newtuplerow[col][plane],
+                                  lambda);
+                    break;
+
+                }
+            }
+        }
+        pnm_writepamrow(&outpam, newtuplerow);
+    }
+    pnm_freepamrow(newtuplerow);
+    pnm_freepamrow(tuplerow);
+
+    return 0;
+}
diff --git a/editor/pamcomp.c b/editor/pamcomp.c
new file mode 100644
index 00000000..871267b2
--- /dev/null
+++ b/editor/pamcomp.c
@@ -0,0 +1,619 @@
+/*----------------------------------------------------------------------------
+                              pamcomp
+-----------------------------------------------------------------------------
+   This program composes two images together, with optional translucence.
+
+   This program is derived from (and replaces) Pnmcomp, whose origin is
+   as follows:
+
+       Copyright 1992, David Koblas.                                    
+         Permission to use, copy, modify, and distribute this software  
+         and its documentation for any purpose and without fee is hereby
+         granted, provided that the above copyright notice appear in all
+         copies and that both that copyright notice and this permission 
+         notice appear in supporting documentation.  This software is   
+         provided "as is" without express or implied warranty.          
+
+   No code from the original remains in the present version.  The
+   January 2004 version was coded entirely by Bryan Henderson.
+   Bryan has contributed his work to the public domain.
+
+   The current version is derived from the January 2004 version, with
+   additional work by multiple authors.
+-----------------------------------------------------------------------------*/
+
+#define _BSD_SOURCE    /* Make sure strcasecmp() is in string.h */
+#include <string.h>
+#include <math.h>
+
+#include "pam.h"
+#include "pm_gamma.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+enum horizPos {BEYONDLEFT, LEFT, CENTER, RIGHT, BEYONDRIGHT};
+enum vertPos {ABOVE, TOP, MIDDLE, BOTTOM, BELOW};
+
+
+enum sampleScale {INTENSITY_SAMPLE, GAMMA_SAMPLE};
+/* This indicates a scale for a PAM sample value.  INTENSITY_SAMPLE means
+   the value is proportional to light intensity; GAMMA_SAMPLE means the 
+   value is gamma-adjusted as defined in the PGM/PPM spec.  In both
+   scales, the values are continuous and normalized to the range 0..1.
+
+   This scale has no meaning if the PAM is not a visual image.  
+*/
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *underlyingFilespec;  /* '-' if stdin */
+    const char *overlayFilespec;
+    const char *alphaFilespec;
+    const char *outputFilespec;  /* '-' if stdout */
+    int xoff, yoff;   /* value of xoff, yoff options */
+    float opacity;
+    unsigned int alphaInvert;
+    enum horizPos align;
+    enum vertPos valign;
+    unsigned int linear;
+};
+
+
+
+static void
+parseCommandLine(int                        argc, 
+                 char **                    argv,
+                 struct cmdlineInfo * const cmdlineP ) {
+/*----------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    char *align, *valign;
+    unsigned int xoffSpec, yoffSpec, alignSpec, valignSpec, opacitySpec,
+        alphaSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "invert",     OPT_FLAG,   NULL,                  
+            &cmdlineP->alphaInvert,       0);
+    OPTENT3(0, "xoff",       OPT_INT,    &cmdlineP->xoff,       
+            &xoffSpec,                    0);
+    OPTENT3(0, "yoff",       OPT_INT,    &cmdlineP->yoff,       
+            &yoffSpec,                    0);
+    OPTENT3(0, "opacity",    OPT_FLOAT, &cmdlineP->opacity,
+            &opacitySpec,                 0);
+    OPTENT3(0, "alpha",      OPT_STRING, &cmdlineP->alphaFilespec,
+            &alphaSpec,                   0);
+    OPTENT3(0, "align",      OPT_STRING, &align,
+            &alignSpec,                   0);
+    OPTENT3(0, "valign",     OPT_STRING, &valign,
+            &valignSpec,                  0);
+    OPTENT3(0, "linear",     OPT_FLAG,    NULL,       
+            &cmdlineP->linear,            0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+
+    if (!xoffSpec)
+        cmdlineP->xoff = 0;
+    if (!yoffSpec)
+        cmdlineP->yoff = 0;
+    if (!alphaSpec)
+        cmdlineP->alphaFilespec = NULL;
+
+    if (alignSpec) {
+        if (strcasecmp(align, "BEYONDLEFT") == 0)
+            cmdlineP->align = BEYONDLEFT;
+        else if (strcasecmp(align, "LEFT") == 0)
+            cmdlineP->align = LEFT;
+        else if (strcasecmp(align, "CENTER") == 0)
+            cmdlineP->align = CENTER;
+        else if (strcasecmp(align, "RIGHT") == 0)
+            cmdlineP->align = RIGHT;
+        else if (strcasecmp(align, "BEYONDRIGHT") == 0)
+            cmdlineP->align = BEYONDRIGHT;
+        else
+            pm_error("Invalid value for align option: '%s'.  Only LEFT, "
+                     "RIGHT, CENTER, BEYONDLEFT, and BEYONDRIGHT are valid.", 
+                     align);
+    } else 
+        cmdlineP->align = LEFT;
+
+    if (valignSpec) {
+        if (strcasecmp(valign, "ABOVE") == 0)
+            cmdlineP->valign = ABOVE;
+        else if (strcasecmp(valign, "TOP") == 0)
+            cmdlineP->valign = TOP;
+        else if (strcasecmp(valign, "MIDDLE") == 0)
+            cmdlineP->valign = MIDDLE;
+        else if (strcasecmp(valign, "BOTTOM") == 0)
+            cmdlineP->valign = BOTTOM;
+        else if (strcasecmp(valign, "BELOW") == 0)
+            cmdlineP->valign = BELOW;
+        else
+            pm_error("Invalid value for valign option: '%s'.  Only TOP, "
+                     "BOTTOM, MIDDLE, ABOVE, and BELOW are valid.", 
+                     align);
+    } else 
+        cmdlineP->valign = TOP;
+
+    if (!opacitySpec) 
+        cmdlineP->opacity = 1.0;
+
+    if (argc-1 < 1)
+        pm_error("Need at least one argument: file specification of the "
+                 "overlay image.");
+
+    cmdlineP->overlayFilespec = argv[1];
+
+    if (argc-1 >= 2)
+        cmdlineP->underlyingFilespec = argv[2];
+    else
+        cmdlineP->underlyingFilespec = "-";
+
+    if (argc-1 >= 3)
+        cmdlineP->outputFilespec = argv[3];
+    else
+        cmdlineP->outputFilespec = "-";
+
+    if (argc-1 > 3)
+        pm_error("Too many arguments.  Only acceptable arguments are: "
+                 "overlay image, underlying image, output image");
+}
+
+
+
+
+static int
+commonFormat(int const formatA,
+             int const formatB) {
+/*----------------------------------------------------------------------------
+   Return a viable format for the result of composing the two formats
+   'formatA' and 'formatB'.
+-----------------------------------------------------------------------------*/
+    int retval;
+
+    int const typeA = PAM_FORMAT_TYPE(formatA);
+    int const typeB = PAM_FORMAT_TYPE(formatB);
+    
+    if (typeA == PAM_TYPE || typeB == PAM_TYPE)
+        retval = PAM_FORMAT;
+    else if (typeA == PPM_TYPE || typeB == PPM_TYPE)
+        retval = PPM_FORMAT;
+    else if (typeA == PGM_TYPE || typeB == PGM_TYPE)
+        retval = PGM_FORMAT;
+    else if (typeA == PBM_TYPE || typeB == PBM_TYPE)
+        retval = PBM_FORMAT;
+    else {
+        /* Results are undefined for this case, so we do a hail Mary. */
+        retval = formatA;
+    }
+    return retval;
+}
+
+
+
+static void
+commonTupletype(const char * const tupletypeA, 
+                const char * const tupletypeB, 
+                char *       const tupletypeOut,
+                unsigned int const size) {
+
+    if (strncmp(tupletypeA, "RGB", 3) == 0 ||
+        strncmp(tupletypeB, "RGB", 3) == 0)
+        strncpy(tupletypeOut, "RGB", size);
+    else if (strncmp(tupletypeA, "GRAYSCALE", 9) == 0 ||
+        strncmp(tupletypeB, "GRAYSCALE", 9) == 0)
+        strncpy(tupletypeOut, "GRAYSCALE", size);
+    else if (strncmp(tupletypeA, "BLACKANDWHITE", 13) == 0 ||
+        strncmp(tupletypeB, "BLACKANDWHITE", 13) == 0)
+        strncpy(tupletypeOut, "BLACKANDWHITE", size);
+    else
+        /* Results are undefined for this case, so we do a hail Mary. */
+        strncpy(tupletypeOut, tupletypeA, size);
+}
+
+
+
+static void
+determineOutputType(struct pam * const composedPamP,
+                    struct pam * const underlayPamP,
+                    struct pam * const overlayPamP) {
+
+    composedPamP->height = underlayPamP->height;
+    composedPamP->width = underlayPamP->width;
+
+    composedPamP->format = commonFormat(underlayPamP->format, 
+                                        overlayPamP->format);
+    commonTupletype(underlayPamP->tuple_type, overlayPamP->tuple_type,
+                    composedPamP->tuple_type, 
+                    sizeof(composedPamP->tuple_type));
+
+    composedPamP->maxval = pm_lcm(underlayPamP->maxval, overlayPamP->maxval, 
+                                  1, PNM_OVERALLMAXVAL);
+
+    if (strcmp(composedPamP->tuple_type, "RGB") == 0)
+        composedPamP->depth = 3;
+    else if (strcmp(composedPamP->tuple_type, "GRAYSCALE") == 0)
+        composedPamP->depth = 1;
+    else if (strcmp(composedPamP->tuple_type, "BLACKANDWHITE") == 0)
+        composedPamP->depth = 1;
+    else
+        /* Results are undefined for this case, so we just do something safe */
+        composedPamP->depth = MIN(underlayPamP->depth, overlayPamP->depth);
+}
+
+
+
+static void
+warnOutOfFrame( int const originLeft,
+                int const originTop, 
+                int const overCols,
+                int const overRows,
+                int const underCols,
+                int const underRows ) {
+
+    if (originLeft >= underCols)
+        pm_message("WARNING: the overlay is entirely off the right edge "
+                   "of the underlying image.  "
+                   "It will not be visible in the result.  The horizontal "
+                   "overlay position you selected is %d, "
+                   "and the underlying image "
+                   "is only %d pixels wide.", originLeft, underCols );
+    else if (originLeft + overCols <= 0)
+        pm_message("WARNING: the overlay is entirely off the left edge "
+                   "of the underlying image.  "
+                   "It will not be visible in the result.  The horizontal "
+                   "overlay position you selected is %d and the overlay is "
+                   "only %d pixels wide.", originLeft, overCols);
+    else if (originTop >= underRows)
+        pm_message("WARNING: the overlay is entirely off the bottom edge "
+                   "of the underlying image.  "
+                   "It will not be visible in the result.  The vertical "
+                   "overlay position you selected is %d, "
+                   "and the underlying image "
+                   "is only %d pixels high.", originTop, underRows );
+    else if (originTop + overRows <= 0)
+        pm_message("WARNING: the overlay is entirely off the top edge "
+                   "of the underlying image.  "
+                   "It will not be visible in the result.  The vertical "
+                   "overlay position you selected is %d and the overlay is "
+                   "only %d pixels high.", originTop, overRows);
+}
+
+
+
+static void
+computeOverlayPosition(int                const underCols, 
+                       int                const underRows,
+                       int                const overCols, 
+                       int                const overRows,
+                       struct cmdlineInfo const cmdline, 
+                       int *              const originLeftP,
+                       int *              const originTopP) {
+/*----------------------------------------------------------------------------
+   Determine where to overlay the overlay image, based on the options the
+   user specified and the realities of the image dimensions.
+
+   The origin may be outside the underlying image (so e.g. *originLeftP may
+   be negative or > image width).  That means not all of the overlay image
+   actually gets used.  In fact, there may be no overlap at all.
+-----------------------------------------------------------------------------*/
+    int xalign, yalign;
+
+    switch (cmdline.align) {
+    case BEYONDLEFT:  xalign = -overCols;              break;
+    case LEFT:        xalign = 0;                      break;
+    case CENTER:      xalign = (underCols-overCols)/2; break;
+    case RIGHT:       xalign = underCols - overCols;   break;
+    case BEYONDRIGHT: xalign = underCols;              break;
+    }
+    switch (cmdline.valign) {
+    case ABOVE:       yalign = -overRows;              break;
+    case TOP:         yalign = 0;                      break;
+    case MIDDLE:      yalign = (underRows-overRows)/2; break;
+    case BOTTOM:      yalign = underRows - overRows;   break;
+    case BELOW:       yalign = underRows;              break;
+    }
+    *originLeftP = xalign + cmdline.xoff;
+    *originTopP  = yalign + cmdline.yoff;
+
+    warnOutOfFrame(*originLeftP, *originTopP, 
+                   overCols, overRows, underCols, underRows);    
+}
+
+
+
+static sample
+composeComponents(sample           const compA, 
+                  sample           const compB,
+                  float            const distrib,
+                  sample           const maxval,
+                  enum sampleScale const sampleScale) {
+/*----------------------------------------------------------------------------
+  Compose a single component of each of two pixels, with 'distrib' being
+  the fraction of 'compA' in the result, 1-distrib the fraction of 'compB'.
+  
+  The inputs and result are based on a maxval of 'maxval'.
+  
+  Note that while 'distrib' in the straightforward case is always in
+  [0,1], it can in fact be negative or greater than 1.  We clip the
+  result as required to return a legal sample value.
+-----------------------------------------------------------------------------*/
+    sample retval;
+
+    if (fabs(1.0-distrib) < .001)
+        /* Fast path for common case */
+        retval = compA;
+    else {
+        if (sampleScale == INTENSITY_SAMPLE) {
+            sample const mix = 
+                ROUNDU(compA * distrib + compB * (1.0 - distrib));
+            retval = MIN(maxval, MAX(0, mix));
+        } else {
+            float const compANormalized = (float)compA/maxval;
+            float const compBNormalized = (float)compB/maxval;
+            float const compALinear = pm_ungamma709(compANormalized);
+            float const compBLinear = pm_ungamma709(compBNormalized);
+            float const mix = 
+                compALinear * distrib + compBLinear * (1.0 - distrib);
+            sample const sampleValue = ROUNDU(pm_gamma709(mix) * maxval);
+            retval = MIN(maxval, MAX(0, sampleValue));
+        }
+    }
+    return retval;
+}
+
+
+
+static void
+overlayPixel(tuple            const overlayTuple,
+             struct pam *     const overlayPamP,
+             tuple            const underlayTuple,
+             struct pam *     const underlayPamP,
+             tuplen           const alphaTuplen,
+             bool             const invertAlpha,
+             bool             const overlayHasOpacity,
+             unsigned int     const opacityPlane,
+             tuple            const composedTuple,
+             struct pam *     const composedPamP,
+             float            const masterOpacity,
+             enum sampleScale const sampleScale) {
+
+    float overlayWeight;
+
+    overlayWeight = masterOpacity;  /* initial value */
+    
+    if (overlayHasOpacity)
+        overlayWeight *= (float)
+            overlayTuple[opacityPlane] / overlayPamP->maxval;
+    
+    if (alphaTuplen) {
+        float const alphaval = 
+            invertAlpha ? (1.0 - alphaTuplen[0]) : alphaTuplen[0];
+        overlayWeight *= alphaval;
+    }
+
+    {
+        unsigned int plane;
+        
+        for (plane = 0; plane < composedPamP->depth; ++plane)
+            composedTuple[plane] = 
+                composeComponents(overlayTuple[plane], underlayTuple[plane], 
+                                  overlayWeight,
+                                  composedPamP->maxval,
+                                  sampleScale);
+    }
+}
+
+
+
+static void
+adaptRowToOutputFormat(struct pam * const inpamP,
+                       tuple *      const tuplerow,
+                       struct pam * const outpamP) {
+/*----------------------------------------------------------------------------
+   Convert the row in 'tuplerow', which is in a format described by
+   *inpamP, to the format described by *outpamP.
+
+   'tuplerow' must have enough allocated depth to do this.
+-----------------------------------------------------------------------------*/
+    pnm_scaletuplerow(inpamP, tuplerow, tuplerow, outpamP->maxval);
+
+    if (strncmp(outpamP->tuple_type, "RGB", 3) == 0)
+        pnm_makerowrgb(inpamP, tuplerow);
+}
+
+
+
+static void
+composite(int          const originleft, 
+          int          const origintop, 
+          struct pam * const underlayPamP,
+          struct pam * const overlayPamP,
+          struct pam * const alphaPamP,
+          bool         const invertAlpha,
+          float        const masterOpacity,
+          struct pam * const composedPamP,
+          bool         const assumeLinear) {
+/*----------------------------------------------------------------------------
+   Overlay the overlay image in the array 'overlayImage', described by
+   *overlayPamP, onto the underlying image from the input image file
+   as described by *underlayPamP, output the composite to the image
+   file as described by *composedPamP.
+
+   Apply the overlay image with transparency described by the array
+   'alpha' and *alphaPamP.
+
+   The underlying image is positioned after its header.
+
+   'originleft' and 'origintop' are the coordinates in the underlying
+   image plane where the top left corner of the overlay image is to
+   go.  It is not necessarily inside the underlying image (in fact,
+   may be negative).  Only the part of the overlay that actually
+   intersects the underlying image, if any, gets into the output.
+-----------------------------------------------------------------------------*/
+    enum sampleScale const sampleScale = 
+        assumeLinear ? INTENSITY_SAMPLE : GAMMA_SAMPLE;
+
+    int underlayRow;  /* NB may be negative */
+    int overlayRow;   /* NB may be negative */
+    tuple * composedTuplerow;
+    tuple * underlayTuplerow;
+    tuple * overlayTuplerow;
+    tuplen * alphaTuplerown;
+    bool overlayHasOpacity;
+    unsigned int opacityPlane;
+
+    pnm_getopacity(overlayPamP, &overlayHasOpacity, &opacityPlane);
+
+    composedTuplerow = pnm_allocpamrow(composedPamP);
+    underlayTuplerow = pnm_allocpamrow(underlayPamP);
+    overlayTuplerow  = pnm_allocpamrow(overlayPamP);
+    if (alphaPamP)
+        alphaTuplerown = pnm_allocpamrown(alphaPamP);
+
+    pnm_writepaminit(composedPamP);
+
+    for (underlayRow = MIN(0, origintop), overlayRow = MIN(0, -origintop);
+         underlayRow < MAX(underlayPamP->height, 
+                           origintop + overlayPamP->height);
+         ++underlayRow, ++overlayRow) {
+
+        if (overlayRow >= 0 && overlayRow < overlayPamP->height) {
+            pnm_readpamrow(overlayPamP, overlayTuplerow);
+            adaptRowToOutputFormat(overlayPamP, overlayTuplerow, composedPamP);
+            if (alphaPamP)
+                pnm_readpamrown(alphaPamP, alphaTuplerown);
+        }
+        if (underlayRow >= 0 && underlayRow < underlayPamP->height) {
+            pnm_readpamrow(underlayPamP, underlayTuplerow);
+            adaptRowToOutputFormat(underlayPamP, underlayTuplerow, 
+                                   composedPamP);
+
+            if (underlayRow < origintop || 
+                underlayRow >= origintop + overlayPamP->height) {
+            
+                /* Overlay image does not touch this underlay row. */
+
+                pnm_writepamrow(composedPamP, underlayTuplerow);
+            } else {
+                unsigned int col;
+                for (col = 0; col < composedPamP->width; ++col) {
+                    int const ovlcol = col - originleft;
+
+                    if (ovlcol >= 0 && ovlcol < overlayPamP->width) {
+                        tuplen const alphaTuplen = 
+                            alphaPamP ? alphaTuplerown[ovlcol] : NULL;
+
+                        overlayPixel(overlayTuplerow[ovlcol], overlayPamP,
+                                     underlayTuplerow[col], underlayPamP,
+                                     alphaTuplen, invertAlpha,
+                                     overlayHasOpacity, opacityPlane,
+                                     composedTuplerow[col], composedPamP,
+                                     masterOpacity, sampleScale);
+                    } else
+                        /* Overlay image does not touch this column. */
+                        pnm_assigntuple(composedPamP, composedTuplerow[col],
+                                        underlayTuplerow[col]);
+                }
+                pnm_writepamrow(composedPamP, composedTuplerow);
+            }
+        }
+    }
+    pnm_freepamrow(composedTuplerow);
+    pnm_freepamrow(underlayTuplerow);
+    pnm_freepamrow(overlayTuplerow);
+    if (alphaPamP)
+        pnm_freepamrown(alphaTuplerown);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * underlayFileP;
+    FILE * overlayFileP;
+    FILE * alphaFileP;
+    struct pam underlayPam;
+    struct pam overlayPam;
+    struct pam alphaPam;
+    struct pam composedPam;
+    int originLeft, originTop;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    overlayFileP = pm_openr(cmdline.overlayFilespec);
+    pnm_readpaminit(overlayFileP, &overlayPam, 
+                    PAM_STRUCT_SIZE(allocation_depth));
+    if (cmdline.alphaFilespec) {
+        alphaFileP = pm_openr(cmdline.alphaFilespec);
+        pnm_readpaminit(alphaFileP, &alphaPam, 
+                        PAM_STRUCT_SIZE(allocation_depth));
+
+        if (overlayPam.width != alphaPam.width || 
+            overlayPam.height != overlayPam.height)
+            pm_error("Opacity map and overlay image are not the same size");
+    } else
+        alphaFileP = NULL;
+
+    underlayFileP = pm_openr(cmdline.underlyingFilespec);
+
+    pnm_readpaminit(underlayFileP, &underlayPam, 
+                    PAM_STRUCT_SIZE(allocation_depth));
+
+    computeOverlayPosition(underlayPam.width, underlayPam.height, 
+                           overlayPam.width,  overlayPam.height,
+                           cmdline, &originLeft, &originTop);
+
+    composedPam.size             = sizeof(composedPam);
+    composedPam.len              = PAM_STRUCT_SIZE(allocation_depth);
+    composedPam.allocation_depth = 0;
+    composedPam.file             = pm_openw(cmdline.outputFilespec);
+
+    determineOutputType(&composedPam, &underlayPam, &overlayPam);
+
+    pnm_setminallocationdepth(&underlayPam, composedPam.depth);
+    pnm_setminallocationdepth(&overlayPam,  composedPam.depth);
+    
+    composite(originLeft, originTop,
+              &underlayPam, &overlayPam, alphaFileP ? &alphaPam : NULL,
+              cmdline.alphaInvert, cmdline.opacity,
+              &composedPam, cmdline.linear);
+
+    if (alphaFileP)
+        pm_close(alphaFileP);
+    pm_close(overlayFileP);
+    pm_close(underlayFileP);
+    pm_close(composedPam.file);
+
+    /* If the program failed, it previously aborted with nonzero completion
+       code, via various function calls.
+    */
+    return 0;
+}
diff --git a/editor/pamcut.c b/editor/pamcut.c
new file mode 100644
index 00000000..d5de45fb
--- /dev/null
+++ b/editor/pamcut.c
@@ -0,0 +1,573 @@
+/*============================================================================ 
+                                pamcut
+==============================================================================
+  Cut a rectangle out of a Netpbm image
+
+  This is inspired by and intended as a replacement for Pnmcut by 
+  Jef Poskanzer, 1989.
+
+  By Bryan Henderson, San Jose CA.  Contributed to the public domain
+  by its author.
+============================================================================*/
+
+#include <limits.h>
+#include <assert.h>
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+#define UNSPEC INT_MAX
+    /* UNSPEC is the value we use for an argument that is not specified
+       by the user.  Theoretically, the user could specify this value,
+       but we hope not.
+       */
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+
+    /* The following describe the rectangle the user wants to cut out. 
+       the value UNSPEC for any of them indicates that value was not
+       specified.  A negative value means relative to the far edge.
+       'width' and 'height' are not negative.  These specifications 
+       do not necessarily describe a valid rectangle; they are just
+       what the user said.
+       */
+    int left;
+    int right;
+    int top;
+    int bottom;
+    int width;
+    int height;
+    unsigned int pad;
+
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "left",       OPT_INT,    &cmdlineP->left,     NULL,      0);
+    OPTENT3(0,   "right",      OPT_INT,    &cmdlineP->right,    NULL,      0);
+    OPTENT3(0,   "top",        OPT_INT,    &cmdlineP->top,      NULL,      0);
+    OPTENT3(0,   "bottom",     OPT_INT,    &cmdlineP->bottom,   NULL,      0);
+    OPTENT3(0,   "width",      OPT_INT,    &cmdlineP->width,    NULL,      0);
+    OPTENT3(0,   "height",     OPT_INT,    &cmdlineP->height,   NULL,      0);
+    OPTENT3(0,   "pad",        OPT_FLAG,   NULL, &cmdlineP->pad,           0);
+    OPTENT3(0,   "verbose",    OPT_FLAG,   NULL, &cmdlineP->verbose,       0);
+
+    /* Set the defaults */
+    cmdlineP->left = UNSPEC;
+    cmdlineP->right = UNSPEC;
+    cmdlineP->top = UNSPEC;
+    cmdlineP->bottom = UNSPEC;
+    cmdlineP->width = UNSPEC;
+    cmdlineP->height = UNSPEC;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = TRUE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (cmdlineP->width < 0)
+        pm_error("-width may not be negative.");
+    if (cmdlineP->height < 0)
+        pm_error("-height may not be negative.");
+
+    if ((argc-1) != 0 && (argc-1) != 1 && (argc-1) != 4 && (argc-1) != 5)
+        pm_error("Wrong number of arguments.  "
+                 "Must be 0, 1, 4, or 5 arguments.");
+
+    switch (argc-1) {
+    case 0:
+        cmdlineP->inputFilespec = "-";
+        break;
+    case 1:
+        cmdlineP->inputFilespec = argv[1];
+        break;
+    case 4:
+    case 5: {
+        int warg, harg;  /* The "width" and "height" command line arguments */
+
+        if (sscanf(argv[1], "%d", &cmdlineP->left) != 1)
+            pm_error("Invalid number for left column argument");
+        if (sscanf(argv[2], "%d", &cmdlineP->top) != 1)
+            pm_error("Invalid number for right column argument");
+        if (sscanf(argv[3], "%d", &warg) != 1)
+            pm_error("Invalid number for width argument");
+        if (sscanf(argv[4], "%d", &harg) != 1)
+            pm_error("Invalid number for height argument");
+
+        if (warg > 0) {
+            cmdlineP->width = warg;
+            cmdlineP->right = UNSPEC;
+        } else {
+            cmdlineP->width = UNSPEC;
+            cmdlineP->right = warg -1;
+        }
+        if (harg > 0) {
+            cmdlineP->height = harg;
+            cmdlineP->bottom = UNSPEC;
+        } else {
+            cmdlineP->height = UNSPEC;
+            cmdlineP->bottom = harg - 1;
+        }
+
+        if (argc-1 == 4)
+            cmdlineP->inputFilespec = "-";
+        else
+            cmdlineP->inputFilespec = argv[5];
+        break;
+    }
+    }
+}
+
+
+
+static void
+computeCutBounds(const int cols, const int rows,
+                 const int leftarg, const int rightarg, 
+                 const int toparg, const int bottomarg,
+                 const int widtharg, const int heightarg,
+                 int * const leftcolP, int * const rightcolP,
+                 int * const toprowP, int * const bottomrowP) {
+/*----------------------------------------------------------------------------
+   From the values given on the command line 'leftarg', 'rightarg',
+   'toparg', 'bottomarg', 'widtharg', and 'heightarg', determine what
+   rectangle the user wants cut out.
+
+   Any of these arguments may be UNSPEC to indicate "not specified".
+   Any except 'widtharg' and 'heightarg' may be negative to indicate
+   relative to the far edge.  'widtharg' and 'heightarg' are positive.
+
+   Return the location of the rectangle as *leftcolP, *rightcolP,
+   *toprowP, and *bottomrowP.  
+-----------------------------------------------------------------------------*/
+
+    int leftcol, rightcol, toprow, bottomrow;
+        /* The left and right column numbers and top and bottom row numbers
+           specified by the user, except with negative values translated
+           into the actual values.
+
+           Note that these may very well be negative themselves, such
+           as when the user says "column -10" and there are only 5 columns
+           in the image.
+           */
+
+    /* Translate negative column and row into real column and row */
+    /* Exploit the fact that UNSPEC is a positive number */
+
+    if (leftarg >= 0)
+        leftcol = leftarg;
+    else
+        leftcol = cols + leftarg;
+    if (rightarg >= 0)
+        rightcol = rightarg;
+    else
+        rightcol = cols + rightarg;
+    if (toparg >= 0)
+        toprow = toparg;
+    else
+        toprow = rows + toparg;
+    if (bottomarg >= 0)
+        bottomrow = bottomarg;
+    else
+        bottomrow = rows + bottomarg;
+
+    /* Sort out left, right, and width specifications */
+
+    if (leftcol == UNSPEC && rightcol == UNSPEC && widtharg == UNSPEC) {
+        *leftcolP = 0;
+        *rightcolP = cols - 1;
+    }
+    if (leftcol == UNSPEC && rightcol == UNSPEC && widtharg != UNSPEC) {
+        *leftcolP = 0;
+        *rightcolP = 0 + widtharg - 1;
+    }
+    if (leftcol == UNSPEC && rightcol != UNSPEC && widtharg == UNSPEC) {
+        *leftcolP = 0;
+        *rightcolP = rightcol;
+    }
+    if (leftcol == UNSPEC && rightcol != UNSPEC && widtharg != UNSPEC) {
+        *leftcolP = rightcol - widtharg + 1;
+        *rightcolP = rightcol;
+    }
+    if (leftcol != UNSPEC && rightcol == UNSPEC && widtharg == UNSPEC) {
+        *leftcolP = leftcol;
+        *rightcolP = cols - 1;
+    }
+    if (leftcol != UNSPEC && rightcol == UNSPEC && widtharg != UNSPEC) {
+        *leftcolP = leftcol;
+        *rightcolP = leftcol + widtharg - 1;
+    }
+    if (leftcol != UNSPEC && rightcol != UNSPEC && widtharg == UNSPEC) {
+        *leftcolP = leftcol;
+        *rightcolP = rightcol;
+    }
+    if (leftcol != UNSPEC && rightcol != UNSPEC && widtharg != UNSPEC) {
+        pm_error("You may not specify left, right, and width.\n"
+                 "Choose at most two of these.");
+    }
+
+
+    /* Sort out top, bottom, and height specifications */
+
+    if (toprow == UNSPEC && bottomrow == UNSPEC && heightarg == UNSPEC) {
+        *toprowP = 0;
+        *bottomrowP = rows - 1;
+    }
+    if (toprow == UNSPEC && bottomrow == UNSPEC && heightarg != UNSPEC) {
+        *toprowP = 0;
+        *bottomrowP = 0 + heightarg - 1;
+    }
+    if (toprow == UNSPEC && bottomrow != UNSPEC && heightarg == UNSPEC) {
+        *toprowP = 0;
+        *bottomrowP = bottomrow;
+    }
+    if (toprow == UNSPEC && bottomrow != UNSPEC && heightarg != UNSPEC) {
+        *toprowP = bottomrow - heightarg + 1;
+        *bottomrowP = bottomrow;
+    }
+    if (toprow != UNSPEC && bottomrow == UNSPEC && heightarg == UNSPEC) {
+        *toprowP = toprow;
+        *bottomrowP = rows - 1;
+    }
+    if (toprow != UNSPEC && bottomrow == UNSPEC && heightarg != UNSPEC) {
+        *toprowP = toprow;
+        *bottomrowP = toprow + heightarg - 1;
+    }
+    if (toprow != UNSPEC && bottomrow != UNSPEC && heightarg == UNSPEC) {
+        *toprowP = toprow;
+        *bottomrowP = bottomrow;
+    }
+    if (toprow != UNSPEC && bottomrow != UNSPEC && heightarg != UNSPEC) {
+        pm_error("You may not specify top, bottom, and height.\n"
+                 "Choose at most two of these.");
+    }
+
+}
+
+
+
+static void
+rejectOutOfBounds(const int cols, const int rows, 
+                  const int leftcol, const int rightcol, 
+                  const int toprow, const int bottomrow) {
+
+    /* Reject coordinates off the edge */
+
+    if (leftcol < 0)
+        pm_error("You have specified a left edge (%d) that is beyond\n"
+                 "the left edge of the image (0)", leftcol);
+    if (leftcol > cols-1)
+        pm_error("You have specified a left edge (%d) that is beyond\n"
+                 "the right edge of the image (%d)", leftcol, cols-1);
+    if (rightcol < 0)
+        pm_error("You have specified a right edge (%d) that is beyond\n"
+                 "the left edge of the image (0)", rightcol);
+    if (rightcol > cols-1)
+        pm_error("You have specified a right edge (%d) that is beyond\n"
+                 "the right edge of the image (%d)", rightcol, cols-1);
+    if (leftcol > rightcol) 
+        pm_error("You have specified a left edge (%d) that is to the right\n"
+                 "of the right edge you specified (%d)", 
+                 leftcol, rightcol);
+    
+    if (toprow < 0)
+        pm_error("You have specified a top edge (%d) that is above the top "
+                 "edge of the image (0)", toprow);
+    if (toprow > rows-1)
+        pm_error("You have specified a top edge (%d) that is below the\n"
+                 "bottom edge of the image (%d)", toprow, rows-1);
+    if (bottomrow < 0)
+        pm_error("You have specified a bottom edge (%d) that is above the\n"
+                 "top edge of the image (0)", bottomrow);
+    if (bottomrow > rows-1)
+        pm_error("You have specified a bottom edge (%d) that is below the\n"
+                 "bottom edge of the image (%d)", bottomrow, rows-1);
+    if (toprow > bottomrow) 
+        pm_error("You have specified a top edge (%d) that is below\n"
+                 "the bottom edge you specified (%d)", 
+                 toprow, bottomrow);
+}
+
+
+
+static void
+writeBlackRows(const struct pam * const outpamP, 
+               int                const rows) {
+/*----------------------------------------------------------------------------
+   Write out 'rows' rows of black tuples of the image described by *outpamP.
+
+   Unless our input image is PBM, PGM, or PPM, or PAM equivalent, we
+   don't really know what "black" means, so this is just something
+   arbitrary in that case.
+-----------------------------------------------------------------------------*/
+    tuple blackTuple;
+    tuple * blackRow;
+    int col;
+    
+    pnm_createBlackTuple(outpamP, &blackTuple);
+
+    MALLOCARRAY_NOFAIL(blackRow, outpamP->width);
+    
+    for (col = 0; col < outpamP->width; ++col)
+        blackRow[col] = blackTuple;
+
+    pnm_writepamrowmult(outpamP, blackRow, rows);
+
+    free(blackRow);
+
+    pnm_freepamtuple(blackTuple);
+}
+
+
+
+struct rowCutter {
+/*----------------------------------------------------------------------------
+   This is an object that gives you pointers you can use to effect the
+   horizontal cutting and padding of a row just by doing one
+   pnm_readpamrow() and one pnm_writepamrow().  It works like this:
+
+   The array inputPointers[] contains an element for each pixel in an input
+   row.  If it's a pixel that gets discarded in the cutting process, 
+   inputPointers[] points to a special "discard" tuple.  All thrown away
+   pixels have the same discard tuple to save CPU cache space.  If it's
+   a pixel that gets copied to the output, inputPointers[] points to some
+   tuple to which outputPointers[] also points.
+
+   The array outputPointers[] contains an element for each pixel in an
+   output row.  If the pixel is one that gets copied from the input, 
+   outputPointers[] points to some tuple to which inputPointers[] also
+   points.  If it's a pixel that gets padded with black, outputPointers[]
+   points to a constant black tuple.  All padded pixels have the same
+   constant black tuple to save CPU cache space.
+
+   For example, if you have a three pixel input row and are cutting
+   off the right two pixels, inputPointers[0] points to copyTuples[0]
+   and inputPointers[1] and inputPointers[2] point to discardTuple.
+   outputPointers[0] points to copyTuples[0].
+
+   We arrange to have the padded parts of the output row filled with
+   black tuples.  Unless the input image is PBM, PGM, or PPM, or PAM
+   equivalent, we don't really know what "black" means, so we fill with
+   something arbitrary in that case.
+-----------------------------------------------------------------------------*/
+    tuple * inputPointers;
+    tuple * outputPointers;
+
+    unsigned int inputWidth;
+    unsigned int outputWidth;
+
+    /* The following are the tuples to which inputPointers[] and
+       outputPointers[] may point.
+    */
+    tuple * copyTuples;
+    tuple blackTuple;
+    tuple discardTuple;
+};
+
+
+
+/* In a typical multi-image stream, all the images have the same
+   dimensions, so this program creates and destroys identical row
+   cutters for each image in the stream.  If that turns out to take a
+   significant amount of resource to do, we should create a cache:
+   keep the last row cutter made, tagged by the parameters used to
+   create it.  If the parameters are the same for the next image, we
+   just use that cached row cutter; otherwise, we discard it and
+   create a new one then.
+*/
+
+static void
+createRowCutter(struct pam *        const inpamP,
+                struct pam *        const outpamP,
+                int                 const leftcol,
+                int                 const rightcol,
+                struct rowCutter ** const rowCutterPP) {
+    
+    struct rowCutter * rowCutterP;
+    tuple * inputPointers;
+    tuple * outputPointers;
+    tuple * copyTuples;
+    tuple blackTuple;
+    tuple discardTuple;
+    int col;
+    
+    assert(inpamP->depth >= outpamP->depth);
+        /* Entry condition.  If this weren't true, we could not simply
+           treat an input tuple as an output tuple.
+        */
+
+    copyTuples   = pnm_allocpamrow(outpamP);
+    discardTuple = pnm_allocpamtuple(inpamP);
+    pnm_createBlackTuple(outpamP, &blackTuple);
+
+    MALLOCARRAY_NOFAIL(inputPointers,  inpamP->width);
+    MALLOCARRAY_NOFAIL(outputPointers, outpamP->width);
+
+    /* Put in left padding */
+    for (col = leftcol; col < 0; ++col)
+        outputPointers[col-leftcol] = blackTuple;
+ 
+    /* Put in extracted columns */
+    for (col = MAX(leftcol, 0); 
+         col <= MIN(rightcol, inpamP->width-1); 
+         ++col) {
+        int const outcol = col - leftcol;
+
+        inputPointers[col] = outputPointers[outcol] = copyTuples[outcol];
+    }
+
+    /* Put in right padding */
+    for (col = MIN(rightcol, inpamP->width-1) + 1; col <= rightcol; ++col)
+        outputPointers[col-leftcol] = blackTuple;
+    
+    /* Direct input pixels that are getting cut off to the discard tuple */
+
+    for (col = 0; col < leftcol; ++col)
+        inputPointers[col] = discardTuple;
+
+    for (col = rightcol + 1; col < inpamP->width; ++col)
+        inputPointers[col] = discardTuple;
+
+    MALLOCVAR_NOFAIL(rowCutterP);
+
+    rowCutterP->inputWidth     = inpamP->width;
+    rowCutterP->outputWidth    = outpamP->width;
+    rowCutterP->inputPointers  = inputPointers;
+    rowCutterP->outputPointers = outputPointers;
+    rowCutterP->copyTuples     = copyTuples;
+    rowCutterP->discardTuple   = discardTuple;
+    rowCutterP->blackTuple     = blackTuple;
+
+    *rowCutterPP = rowCutterP;
+}
+
+
+
+static void
+destroyRowCutter(struct rowCutter * const rowCutterP) {
+
+    pnm_freepamrow(rowCutterP->copyTuples);
+    pnm_freepamtuple(rowCutterP->blackTuple);
+    pnm_freepamtuple(rowCutterP->discardTuple);
+    free(rowCutterP->inputPointers);
+    free(rowCutterP->outputPointers);
+    
+    free(rowCutterP);
+}
+
+
+
+static void
+cutOneImage(FILE *             const ifP,
+            struct cmdlineInfo const cmdline,
+            FILE *             const ofP) {
+
+    int row;
+    int leftcol, rightcol, toprow, bottomrow;
+    struct pam inpam;   /* Input PAM image */
+    struct pam outpam;  /* Output PAM image */
+    struct rowCutter * rowCutterP;
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+    
+    computeCutBounds(inpam.width, inpam.height, 
+                     cmdline.left, cmdline.right, 
+                     cmdline.top, cmdline.bottom, 
+                     cmdline.width, cmdline.height, 
+                     &leftcol, &rightcol, &toprow, &bottomrow);
+
+    if (!cmdline.pad)
+        rejectOutOfBounds(inpam.width, inpam.height, leftcol, rightcol, 
+                          toprow, bottomrow);
+
+    if (cmdline.verbose) {
+        pm_message("Image goes from Row 0, Column 0 through Row %d, Column %d",
+                   inpam.height-1, inpam.width-1);
+        pm_message("Cutting from Row %d, Column %d through Row %d Column %d",
+                   toprow, leftcol, bottomrow, rightcol);
+    }
+
+    outpam = inpam;    /* Initial value -- most fields should be same */
+    outpam.file   = ofP;
+    outpam.width  = rightcol-leftcol+1;
+    outpam.height = bottomrow-toprow+1;
+
+    pnm_writepaminit(&outpam);
+
+    /* Write out top padding */
+    if (0 - toprow > 0)
+        writeBlackRows(&outpam, 0 - toprow);
+
+    createRowCutter(&inpam, &outpam, leftcol, rightcol, &rowCutterP);
+
+    /* Read input and write out rows extracted from it */
+    for (row = 0; row < inpam.height; ++row) {
+        if (row >= toprow && row <= bottomrow){
+            pnm_readpamrow(&inpam, rowCutterP->inputPointers);
+            pnm_writepamrow(&outpam, rowCutterP->outputPointers);
+        } else  /* row < toprow || row > bottomrow */
+            pnm_readpamrow(&inpam, NULL);
+        
+        /* Note that we may be tempted just to quit after reaching the bottom
+           of the extracted image, but that would cause a broken pipe problem
+           for the process that's feeding us the image.
+        */
+    }
+
+    destroyRowCutter(rowCutterP);
+    
+    /* Write out bottom padding */
+    if ((bottomrow - (inpam.height-1)) > 0)
+        writeBlackRows(&outpam, bottomrow - (inpam.height-1));
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE * const ofP = stdout;
+
+    struct cmdlineInfo cmdline;
+    FILE* ifP;
+    bool eof;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    eof = FALSE;
+    while (!eof) {
+        cutOneImage(ifP, cmdline, ofP);
+        pnm_nextimage(ifP, &eof);
+    }
+
+    pm_close(ifP);
+    pm_close(ofP);
+    
+    return 0;
+}
diff --git a/editor/pamcut.test b/editor/pamcut.test
new file mode 100644
index 00000000..be70f1fd
--- /dev/null
+++ b/editor/pamcut.test
@@ -0,0 +1,10 @@
+echo Test 1.  Should print 2958909756 124815
+./pamcut -top 0 -left 0 -width 260 -height 160 -pad ../testimg.ppm | cksum
+echo Test 2.  Should print 1550940962 10933
+./pamcut -top 200 -left 120 -width 40 -height 40 -pad ../testimg.ppm | cksum
+echo Test 3.  Should print 708474423 14
+./pamcut -top 5 -left 5 -bottom 5 -right 5 ../testimg.ppm | cksum
+echo Test 3.  Should print 3412257956 129
+pbmmake -g 50 50 | ./pamcut 5 5 30 30 | cksum
+
+
diff --git a/editor/pamdeinterlace.c b/editor/pamdeinterlace.c
new file mode 100644
index 00000000..9ed1d8eb
--- /dev/null
+++ b/editor/pamdeinterlace.c
@@ -0,0 +1,132 @@
+/******************************************************************************
+                             pamdeinterlace
+*******************************************************************************
+  De-interlace an image, i.e. select every 2nd row.
+   
+  By Bryan Henderson, San Jose, CA 2001.11.11.
+
+  Contributed to the public domain.
+******************************************************************************/
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+enum evenodd {EVEN, ODD};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespecs of input files */
+    enum evenodd rowsToTake;
+};
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry *option_def;
+    unsigned int option_def_index;
+
+    unsigned int takeeven, takeodd;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "takeeven", OPT_FLAG, NULL, &takeeven, 0);
+    OPTENT3(0,   "takeodd",  OPT_FLAG, NULL, &takeodd,  0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (takeeven && takeodd)
+        pm_error("You cannot specify both -takeeven and -takeodd options.");
+
+    if (takeodd)
+        cmdlineP->rowsToTake = ODD;
+    else
+        cmdlineP->rowsToTake = EVEN;
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        pm_error("You specified too many arguments (%d).  The only "
+                 "argument is the optional input file specification.",
+                 argc-1);
+}
+
+
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE * ifP;
+    tuple * tuplerow;   /* Row from input image */
+    unsigned int row;
+    struct cmdlineInfo cmdline;
+    struct pam inpam;  
+    struct pam outpam;
+
+    pnm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+    
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    tuplerow = pnm_allocpamrow(&inpam);
+
+    outpam = inpam;    /* Initial value -- most fields should be same */
+    outpam.file = stdout;
+    if (inpam.height % 2 == 0)
+        outpam.height = inpam.height / 2;
+    else {
+        if (cmdline.rowsToTake == ODD)
+            outpam.height = inpam.height / 2;
+        else
+            outpam.height = inpam.height / 2 + 1;
+    }
+
+    pnm_writepaminit(&outpam);
+
+    {
+        unsigned int modulusToTake;
+            /* The row number mod 2 of the rows that are supposed to go into
+               the output.
+            */
+
+        switch (cmdline.rowsToTake) {
+        case EVEN: modulusToTake = 0; break;
+        case ODD:  modulusToTake = 1; break;
+        default: pm_error("INTERNAL ERROR: invalid rowsToTake");
+        }
+
+        /* Read input and write out rows extracted from it */
+        for (row = 0; row < inpam.height; row++) {
+            pnm_readpamrow(&inpam, tuplerow);
+            if (row % 2 == modulusToTake)
+                pnm_writepamrow(&outpam, tuplerow);
+        }
+    }
+    pnm_freepamrow(tuplerow);
+    pm_close(inpam.file);
+    pm_close(outpam.file);
+    
+    return 0;
+}
+
diff --git a/editor/pamdice.c b/editor/pamdice.c
new file mode 100644
index 00000000..062e05e3
--- /dev/null
+++ b/editor/pamdice.c
@@ -0,0 +1,494 @@
+/*****************************************************************************
+                                  pamdice
+******************************************************************************
+  Slice a Netpbm image vertically and/or horizontally into multiple images.
+
+  By Bryan Henderson, San Jose CA 2001.01.31
+
+  Contributed to the public domain.
+
+******************************************************************************/
+
+#include <string.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "mallocvar.h"
+
+#define MAXFILENAMELEN 80
+    /* Maximum number of characters we accept in filenames */
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilespec;  /* '-' if stdin */
+    char * outstem; 
+        /* null-terminated string, max MAXFILENAMELEN-10 characters */
+    unsigned int sliceVertically;    /* boolean */
+    unsigned int sliceHorizontally;  /* boolean */
+    unsigned int width;    /* Meaningless if !sliceVertically */
+    unsigned int height;   /* Meaningless if !sliceHorizontally */
+    unsigned int hoverlap; 
+        /* Meaningless if !sliceVertically.  Guaranteed < width */
+    unsigned int voverlap; 
+        /* Meaningless if !sliceHorizontally.  Guaranteed < height */
+    unsigned int verbose;
+};
+
+
+static void
+parseCommandLine ( int argc, char ** argv,
+                   struct cmdlineInfo * const cmdlineP ) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+    
+    unsigned int outstemSpec, hoverlapSpec, voverlapSpec;
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "width",       OPT_UINT,    &cmdlineP->width,       
+            &cmdlineP->sliceVertically,       0 );
+    OPTENT3(0, "height",      OPT_UINT,    &cmdlineP->height,
+            &cmdlineP->sliceHorizontally,     0 );
+    OPTENT3(0, "hoverlap",    OPT_UINT,    &cmdlineP->hoverlap,
+            &hoverlapSpec,                    0 );
+    OPTENT3(0, "voverlap",    OPT_UINT,    &cmdlineP->voverlap,
+            &voverlapSpec,                    0 );
+    OPTENT3(0, "outstem",     OPT_STRING,  &cmdlineP->outstem,
+            &outstemSpec,                     0 );
+    OPTENT3(0, "verbose",     OPT_FLAG,    NULL,
+            &cmdlineP->verbose,               0 );
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (cmdlineP->sliceVertically) {
+        if (hoverlapSpec) {
+            if (cmdlineP->hoverlap > cmdlineP->width - 1)
+                pm_error("-hoverlap value must be less than -width (%u).  "
+                         "You specified %u.",
+                         cmdlineP->width, cmdlineP->hoverlap);
+        } else
+            cmdlineP->hoverlap = 0;
+    }
+    if (cmdlineP->sliceHorizontally) {
+        if (voverlapSpec) {
+            if (cmdlineP->voverlap > cmdlineP->height - 1)
+                pm_error("-voverlap value must be less than -height (%u).  "
+                         "You specified %u.",
+                         cmdlineP->height, cmdlineP->voverlap);
+        } else
+            cmdlineP->voverlap = 0;
+    }
+
+    if (!outstemSpec)
+        pm_error("You must specify the -outstem option to indicate where to "
+                 "put the output images.");
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else 
+        pm_error("Progam takes at most 1 parameter: the file specification.  "
+                 "You specified %d", argc-1);
+}
+
+
+
+static unsigned int
+divup(unsigned int const dividend,
+      unsigned int const divisor) {
+/*----------------------------------------------------------------------------
+   Divide 'dividend' by 'divisor' and round up to the next whole number.
+-----------------------------------------------------------------------------*/
+    return (dividend + divisor - 1) / divisor;
+}
+
+
+
+static void
+computeSliceGeometry(struct cmdlineInfo const cmdline,
+                     struct pam         const inpam,
+                     bool               const verbose,
+                     unsigned int *     const nHorizSliceP,
+                     unsigned int *     const sliceHeightP,
+                     unsigned int *     const bottomSliceHeightP,
+                     unsigned int *     const nVertSliceP,
+                     unsigned int *     const sliceWidthP,
+                     unsigned int *     const rightSliceWidthP
+                     ) {
+/*----------------------------------------------------------------------------
+   Compute the geometry of the slices, both common slices and possibly
+   smaller remainder slices at the top and right.
+-----------------------------------------------------------------------------*/
+    if (cmdline.sliceHorizontally) {
+        if (cmdline.height >= inpam.height)
+            *nHorizSliceP = 1;
+        else
+            *nHorizSliceP = 1 + divup(inpam.height - cmdline.height, 
+                                      cmdline.height - cmdline.voverlap);
+        *sliceHeightP = cmdline.height;
+    } else {
+        *nHorizSliceP = 1;
+        *sliceHeightP = inpam.height;
+    }
+
+    *bottomSliceHeightP = 
+        inpam.height - (*nHorizSliceP-1) * (cmdline.height - cmdline.voverlap);
+
+    if (cmdline.sliceVertically) {
+        if (cmdline.width >= inpam.width)
+            *nVertSliceP = 1;
+        else
+            *nVertSliceP = 1 + divup(inpam.width - cmdline.width, 
+                                     cmdline.width - cmdline.hoverlap);
+        *sliceWidthP = cmdline.width;
+    } else {
+        *nVertSliceP = 1;
+        *sliceWidthP = inpam.width;
+    }
+
+    *rightSliceWidthP = 
+        inpam.width - (*nVertSliceP-1) * (cmdline.width - cmdline.hoverlap);
+
+    if (verbose) {
+        pm_message("Creating %u images, %u across by %u down; "
+                   "each %u w x %u h",
+                   *nVertSliceP * *nHorizSliceP,
+                   *nVertSliceP, *nHorizSliceP,
+                   *sliceWidthP, *sliceHeightP);
+        if (*rightSliceWidthP != *sliceWidthP)
+            pm_message("Right vertical slice is only %u wide", 
+                       *rightSliceWidthP);
+        if (*bottomSliceHeightP != *sliceHeightP)
+            pm_message("Bottom horizontal slice is only %u high",
+                       *bottomSliceHeightP);
+    }
+}
+
+
+
+static unsigned int
+ndigits(unsigned int const arg) {
+/*----------------------------------------------------------------------------
+   Return the minimum number of digits it takes to represent the number
+   'arg' in decimal.
+-----------------------------------------------------------------------------*/
+    unsigned int leftover;
+    unsigned int i;
+
+    for (leftover = arg, i = 0; leftover > 0; leftover /= 10, ++i);
+
+    return MAX(1, i);
+}
+
+
+
+static void
+computeOutputFilenameFormat(int           const format, 
+                            char          const outstem[],
+                            unsigned int  const nHorizSlice,
+                            unsigned int  const nVertSlice,
+                            const char ** const filenameFormatP) {
+
+    const char * filenameSuffix;
+
+    switch(PNM_FORMAT_TYPE(format)) {
+    case PPM_TYPE: filenameSuffix = "ppm"; break;
+    case PGM_TYPE: filenameSuffix = "pgm"; break;
+    case PBM_TYPE: filenameSuffix = "pbm"; break;
+    case PAM_TYPE: filenameSuffix = "pam"; break;
+    default:       filenameSuffix = "";    break;
+    }
+    
+    asprintfN(filenameFormatP, "%s_%%0%uu_%%0%uu.%s",
+              outstem, ndigits(nHorizSlice), ndigits(nVertSlice),
+              filenameSuffix);
+
+    if (*filenameFormatP == NULL)
+        pm_error("Unable to allocate memory for filename format string");
+}
+
+
+
+static void
+openOutStreams(struct pam   const inpam, 
+               struct pam         outpam[],
+               unsigned int const horizSlice, 
+               unsigned int const nHorizSlice,
+               unsigned int const nVertSlice,
+               unsigned int const sliceHeight, 
+               unsigned int const sliceWidth,
+               unsigned int const rightSliceWidth,
+               unsigned int const hOverlap,
+               char         const outstem[]) {
+/*----------------------------------------------------------------------------
+   Open the output files for a single horizontal slice (there's one file
+   for each vertical slice) and write the Netpbm headers to them.  Also
+   compute the pam structures to control each.
+-----------------------------------------------------------------------------*/
+    const char * filenameFormat;
+    unsigned int vertSlice;
+
+    computeOutputFilenameFormat(inpam.format, outstem, nHorizSlice, nVertSlice,
+                                &filenameFormat);
+
+    for (vertSlice = 0; vertSlice < nVertSlice; ++vertSlice) {
+        const char * filename;
+
+        asprintfN(&filename, filenameFormat, horizSlice, vertSlice);
+
+        if (filename == NULL)
+            pm_error("Unable to allocate memory for output filename");
+        else {
+            outpam[vertSlice] = inpam;
+            outpam[vertSlice].file = pm_openw(filename);
+            
+            outpam[vertSlice].width = 
+                vertSlice < nVertSlice-1 ? sliceWidth : rightSliceWidth;
+            
+            outpam[vertSlice].height = sliceHeight;
+            
+            pnm_writepaminit(&outpam[vertSlice]);
+
+            strfree(filename);
+        }
+    }        
+    strfree(filenameFormat);
+}
+
+
+
+static void
+closeOutFiles(struct pam pam[], unsigned int const nVertSlice) {
+
+    unsigned int vertSlice;
+    
+    for (vertSlice = 0; vertSlice < nVertSlice; ++vertSlice)
+        pm_close(pam[vertSlice].file);
+}
+
+static void
+sliceRow(tuple              inputRow[], 
+         struct pam         outpam[], 
+         unsigned int const nVertSlice,
+         unsigned int const hOverlap) {
+/*----------------------------------------------------------------------------
+   Distribute the row inputRow[] across the 'nVerticalSlice' output
+   files described by outpam[].  Each outpam[x] tells how many columns
+   of inputRow[] to take and what their composition is.
+
+   'hOverlap', which is meaningful only when nVertSlice is greater than 1,
+   is the amount by which slices overlap each other.
+-----------------------------------------------------------------------------*/
+    tuple * outputRow;
+    unsigned int vertSlice;
+    unsigned int const sliceWidth = outpam[0].width;
+    unsigned int const stride = 
+        nVertSlice > 1 ? sliceWidth - hOverlap : sliceWidth;
+
+    for (vertSlice = 0, outputRow = inputRow; 
+         vertSlice < nVertSlice; 
+         outputRow += stride, ++vertSlice) {
+        pnm_writepamrow(&outpam[vertSlice], outputRow);
+    }
+}
+
+
+/*----------------------------------------------------------------------------
+   The input reader.  This just reads the input image row by row, except
+   that it lets us back up up to a predefined amount (the window size).
+   When we're overlapping horizontal slices, that's useful.  It's not as
+   simple as just reading the entire image into memory at once, but uses
+   a lot less memory.
+-----------------------------------------------------------------------------*/
+
+struct inputWindow {
+    unsigned int windowSize;
+    unsigned int firstRowInWindow;
+    struct pam pam;
+    tuple ** rows;
+};
+
+static void
+initInput(struct inputWindow * const inputWindowP,
+          struct pam *         const pamP,
+          unsigned int         const windowSize) {
+    
+    struct pam allocPam;  /* Just for allocating the window array */
+    unsigned int i;
+
+    inputWindowP->pam = *pamP;
+    inputWindowP->windowSize = windowSize;
+
+    allocPam = *pamP;
+    allocPam.height = windowSize;
+    
+    inputWindowP->rows = pnm_allocpamarray(&allocPam);
+
+    inputWindowP->firstRowInWindow = 0;
+
+    /* Fill the window with the beginning of the image */
+    for (i = 0; i < windowSize && i < pamP->height; ++i)
+        pnm_readpamrow(&inputWindowP->pam, inputWindowP->rows[i]);
+}
+
+static void
+termInputWindow(struct inputWindow * const inputWindowP) {
+
+    struct pam freePam;  /* Just for freeing window array */
+
+    freePam = inputWindowP->pam;
+    freePam.height = inputWindowP->windowSize;
+
+    pnm_freepamarray(inputWindowP->rows, &freePam);
+}
+
+static tuple *
+getInputRow(struct inputWindow * const inputWindowP,
+            unsigned int         const row) {
+
+    if (row < inputWindowP->firstRowInWindow)
+        pm_error("INTERNAL ERROR: attempt to back up too far with "
+                 "getInputRow() (row %u)", row);
+    if (row >= inputWindowP->pam.height)
+        pm_error("INTERNAL ERROR: attempt to read beyond bottom of "
+                 "input image (row %u)", row);
+
+    while (row >= inputWindowP->firstRowInWindow + inputWindowP->windowSize) {
+        tuple * const oldRow0 = inputWindowP->rows[0];
+        unsigned int i;
+        /* Slide the window down one row */
+        for (i = 0; i < inputWindowP->windowSize - 1; ++i)
+            inputWindowP->rows[i] = inputWindowP->rows[i+1];
+        ++inputWindowP->firstRowInWindow;
+
+        /* Read in the new last row in the window */
+        inputWindowP->rows[i] = oldRow0;  /* Reuse the memory */
+        pnm_readpamrow(&inputWindowP->pam, inputWindowP->rows[i]);
+    }        
+
+    return inputWindowP->rows[row - inputWindowP->firstRowInWindow];
+}
+
+/*-----  end of input reader ----------------------------------------------*/
+
+
+
+static void
+allocOutpam(unsigned int  const nVertSlice,
+            struct pam ** const outpamArrayP) {
+
+    struct pam * outpamArray;
+
+    MALLOCARRAY(outpamArray, nVertSlice);
+
+    if (outpamArray == NULL)
+        pm_error("Unable to allocate array for %u output pam structures.",
+                 nVertSlice);
+
+    *outpamArrayP = outpamArray;
+}
+
+
+
+int
+main(int argc, char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE    *ifP;
+    struct pam inpam;
+    unsigned int horizSlice;
+        /* Number of the current horizontal slice.  Slices are numbered
+           sequentially starting at 0.
+        */
+    unsigned int sliceWidth;
+        /* Width in pam columns of each vertical slice, except
+           the rightmost slice, which may be narrower.  If we aren't slicing
+           vertically, that means one slice, i.e. the slice width
+           is the image width.  
+        */
+    unsigned int rightSliceWidth;
+        /* Width in pam columns of the rightmost vertical slice. */
+    unsigned int sliceHeight;
+        /* Height in pam rows of each horizontal slice, except
+           the bottom slice, which may be shorter.  If we aren't slicing
+           horizontally, that means one slice, i.e. the slice height
+           is the image height.  
+        */
+    unsigned int bottomSliceHeight;
+        /* Height in pam rows of the bottom horizontal slice. */
+    unsigned int nHorizSlice;
+    unsigned int nVertSlice;
+    struct inputWindow inputWindow;
+    
+    struct pam * outpam;
+        /* malloc'ed.  outpam[x] is the pam structure that controls
+           the current horizontal slice of vertical slice x.
+        */
+
+    pnm_init(&argc, argv);
+    
+    parseCommandLine(argc, argv, &cmdline);
+        
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    computeSliceGeometry(cmdline, inpam, !!cmdline.verbose,
+                         &nHorizSlice, &sliceHeight, &bottomSliceHeight,
+                         &nVertSlice, &sliceWidth, &rightSliceWidth);
+
+    allocOutpam(nVertSlice, &outpam);
+    
+    initInput(&inputWindow, &inpam, 
+              nHorizSlice > 1 ? cmdline.voverlap + 1 : 1);
+
+    for (horizSlice = 0; horizSlice < nHorizSlice; ++horizSlice) {
+        unsigned int const thisSliceFirstRow = 
+            horizSlice * (sliceHeight - cmdline.voverlap);
+        unsigned int const thisSliceHeight = 
+            horizSlice < nHorizSlice-1 ? sliceHeight : bottomSliceHeight;
+
+        unsigned int row;
+
+        openOutStreams(inpam, outpam, horizSlice, nHorizSlice, nVertSlice, 
+                       thisSliceHeight, sliceWidth, rightSliceWidth,
+                       cmdline.hoverlap, cmdline.outstem);
+
+        for (row = 0; row < thisSliceHeight; ++row) {
+            tuple * const inputRow =
+                getInputRow(&inputWindow, thisSliceFirstRow + row);
+            sliceRow(inputRow, outpam, nVertSlice, cmdline.hoverlap);
+        }
+        closeOutFiles(outpam, nVertSlice);
+    }
+
+    termInputWindow(&inputWindow);
+
+    free(outpam);
+
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/editor/pamdither.c b/editor/pamdither.c
new file mode 100644
index 00000000..5eb931a6
--- /dev/null
+++ b/editor/pamdither.c
@@ -0,0 +1,319 @@
+/*=============================================================================
+                                 pamdither
+===============================================================================
+  By Bryan Henderson, July 2006.
+
+  Contributed to the public domain.
+
+  This is meant to replace Ppmdither by Christos Zoulas, 1991.
+=============================================================================*/
+
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "pam.h"
+
+/* Besides having to have enough memory available, the limiting factor
+   in the dithering matrix power is the size of the dithering value.
+   We need 2*dith_power bits in an unsigned int.  We also reserve
+   one bit to give headroom to do calculations with these numbers.
+*/
+#define MAX_DITH_POWER ((sizeof(unsigned int)*8 - 1) / 2)
+
+
+/* COLOR():
+ *	returns the index in the colormap for the
+ *      r, g, b values specified.
+ */
+#define COLOR(r,g,b) (((r) * dith_ng + (g)) * dith_nb + (b))
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  /* File name of input file */
+    const char * mapFileName;    /* File name of colormap file */
+    unsigned int dim;
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine (int argc, char ** argv,
+                  struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int dimSpec, mapfileSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+    
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "dim",      OPT_UINT, 
+            &cmdlineP->dim,    &dimSpec, 0);
+    OPTENT3(0,   "mapfile",      OPT_STRING, 
+            &cmdlineP->mapFilespec,    &mapfileSpec, 0);
+    OPTENT3(0, "verbose",        OPT_FLAG,   NULL,                  
+            &cmdlineP->verbose,        0 );
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (!dimSpec)
+        cmdlineP->dim = 4;
+
+    if (cmdlineP->dim > MAX_DITH_POWER)
+        pm_error("Dithering matrix power %u (-dim) is too large.  "
+                 "Must be <= %d",
+                 dithPower, MAX_DITH_POWER);
+        
+    if (!mapfileSpec)
+        pm_error("You must specify the -mapfile option.");
+
+    if (argc-1 > 1)
+        pm_error("Program takes at most one argument: the input file "
+                 "specification.  "
+                 "You specified %d arguments.", argc-1);
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+static unsigned int
+dither(sample       const p,
+       sample       const maxval,
+       unsigned int const d,
+       unsigned int const ditheredMaxval,
+       unsigned int const ditherMatrixArea) {
+/*----------------------------------------------------------------------------
+  Return the dithered brightness for a component of a pixel whose real 
+  brightness for that component is 'p' based on a maxval of 'maxval'.
+  The returned brightness is based on a maxval of ditheredMaxval.
+
+  'd' is the entry in the dithering matrix for the position of this pixel
+  within the dithered square.
+
+  'ditherMatrixArea' is the area (number of pixels in) the dithered square.
+-----------------------------------------------------------------------------*/
+    unsigned int const ditherSquareMaxval = ditheredMaxval * ditherMatrixArea;
+        /* This is the maxval for an intensity that an entire dithered
+           square can represent.
+        */
+    pixval const pScaled = ditherSquareMaxval * p / maxval;
+        /* This is the input intensity P expressed with a maxval of
+           'ditherSquareMaxval'
+        */
+    
+    /* Now we scale the intensity back down to the 'ditheredMaxval', and
+       as that will involve rounding, we round up or down based on the position
+       in the dithered square, as determined by 'd'
+    */
+
+    return (pScaled + d) / ditherMatrixArea;
+}
+
+
+
+static unsigned int
+dithValue(unsigned int const y,
+          unsigned int const x,
+          unsigned int const dithPower) { 
+/*----------------------------------------------------------------------------
+  Return the value of a dither matrix which is 2 ** dithPower elements
+  square at Row x, Column y.
+  [graphics gems, p. 714]
+-----------------------------------------------------------------------------*/
+    unsigned int d;
+        /*
+          Think of d as the density. At every iteration, d is shifted
+          left one and a new bit is put in the low bit based on x and y.
+          If x is odd and y is even, or visa versa, then a bit is shifted in.
+          This generates the checkerboard pattern seen in dithering.
+          This quantity is shifted again and the low bit of y is added in.
+          This whole thing interleaves a checkerboard pattern and y's bits
+          which is what you want.
+        */
+    unsigned int i;
+
+    for (i = 0, d = 0; i < dithPower; i++, x >>= 1, y >>= 1)
+        d = (d << 2) | (((x & 1) ^ (y & 1)) << 1) | (y & 1);
+
+    return(d);
+}
+
+
+
+static unsigned int **
+dithMatrix(unsigned int const dithPower) {
+/*----------------------------------------------------------------------------
+   Create the dithering matrix for dimension 'dithDim'.
+
+   Return it in newly malloc'ed storage.
+
+   Note that we assume 'dith_dim' is small enough that the dith_mat_sz
+   computed within fits in an int.  Otherwise, results are undefined.
+-----------------------------------------------------------------------------*/
+    unsigned int const dithDim = 1 << dithPower;
+
+    unsigned int ** dithMat;
+
+    assert(dithPower < sizeof(unsigned int) * 8);
+
+    {
+        unsigned int const dithMatSize = 
+            (dithDim * sizeof(*dithMat)) + /* pointers */
+            (dithDim * dithDim * sizeof(**dithMat)); /* data */
+        
+        dithMat = malloc(dithMatSize);
+        
+        if (dithMat == NULL) 
+            pm_error("Out of memory.  "
+                     "Cannot allocate %d bytes for dithering matrix.",
+                     dithMatSize);
+    }
+    {
+        unsigned int * const rowStorage = (unsigned int *)&dithMat[dithDim];
+        unsigned int y;
+        for (y = 0; y < dithDim; ++y)
+            dithMat[y] = &rowStorage[y * dithDim];
+    }
+    {
+        unsigned int y;
+        for (y = 0; y < dithDim; ++y) {
+            unsigned int x;
+            for (x = 0; x < dithDim; ++x)
+                dithMat[y][x] = dithValue(y, x, dithPower);
+        }
+    }
+    return dithMat;
+}
+
+    
+
+static void
+ditherImage(struct pam   const inpam,
+            tuple *      const colormap, 
+            unsigned int const dithPower,
+            struct pam   const outpam;
+            tuple **     const inTuples,
+            tuple ***    const outTuplesP) {
+
+    unsigned int const dithDim = 1 << dithPower;
+    unsigned int const ditherMatrixArea = SQR(dithDim);
+
+    unsigned int const modMask = (dithDim - 1);
+       /* And this into N to compute N % dithDim cheaply, since we
+          know (though the compiler doesn't) that dithDim is a power of 2
+       */
+    unsigned int ** const ditherMatrix = dithMatrix(dithPower);
+
+    tuple ** ouputTuples;
+    unsigned int row; 
+
+    assert(dithPower < sizeof(unsigned int) * 8);
+    assert(UINT_MAX / dithDim >= dithDim);
+
+    outTuples = ppm_allocpamarray(outpam);
+
+    for (row = 0; row < inpam.height; ++row) {
+        unsigned int col;
+        for (col = 0; col < inpam.width; ++col) {
+            unsigned int const d =
+                ditherMatrix[row & modMask][(width-col-1) & modMask];
+            tuple const inputTuple = inTuples[row][col];
+            unsigned int dithered[3];
+
+            unsigned int plane;
+
+            assert(inpam.depth >= 3);
+
+            for (plane = 0; plane < 3; ++plane)
+                dithered[plane] =
+                    dither(inputTuple[plane], inpam.maxval, d, outpam.maxval,
+                           ditherMatrixArea);
+
+            outTuples[row][col] = 
+                colormap[COLOR(dithered[RED_PLANE],
+                               dithered[GRN_PLANE],
+                               dithered[BLU_PLANE])];
+        }
+    }
+    free(ditherMatrix);
+    *outTuplesP = outTuples;
+}
+
+
+
+static void
+getColormap(const char * const mapFileName,
+            tuple **     const colormapP) {
+
+    TODO("write this");
+
+}
+
+
+
+int
+main(int argc,
+     char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    tuple ** inTuples;        /* Input image */
+    tuple ** outTuples;        /* Output image */
+    tuple * colormap;
+    int cols, rows;
+    pixval maxval;  /* Maxval of the input image */
+
+    struct pam outpamCommon;
+        /* Describes the output images.  Width and height fields are
+           not meaningful, because different output images might have
+           different dimensions.  The rest of the information is common
+           across all output images.
+        */
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(&argc, &argv);
+
+    pm_openr(cmdline.inputFileName);
+
+    inTuples = pnm_readpam(ifP, &inpam, PAM_STRUCT_SIZE(allocation_depth));
+    pm_close(ifP);
+
+    getColormap(cmdline.mapFileName, &colormap);
+
+    ditherImage(inpam, colormap, dithPower, inTuples, &outTuples);
+
+    ppm_writeppm(stdout, opixels, cols, rows, outputMaxval, 0);
+    pm_close(stdout);
+
+    free(colormap);
+
+    pnm_freepamarray(inTuples, &inpam);
+    pnm_freepamarray(outTuples, &outpam);
+
+    return 0;
+}
diff --git a/editor/pamditherbw.c b/editor/pamditherbw.c
new file mode 100644
index 00000000..61c23103
--- /dev/null
+++ b/editor/pamditherbw.c
@@ -0,0 +1,743 @@
+/*=============================================================================
+                           pamditherbw
+===============================================================================
+   Dither a grayscale PAM to a black and white PAM.
+
+   By Bryan Henderson, San Jose CA.  June 2004.
+
+   Contributed to the public domain by its author.
+
+   Based on ideas from Pgmtopbm by Jef Poskanzer, 1989.
+=============================================================================*/
+
+#include <assert.h>
+#include <string.h>
+
+#include "pam.h"
+#include "dithers.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "pm_gamma.h"
+
+enum halftone {QT_FS, QT_THRESH, QT_DITHER8, QT_CLUSTER, QT_HILBERT};
+
+enum ditherType {DT_REGULAR, DT_CLUSTER};
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *  inputFilespec;
+    enum halftone halftone;
+    unsigned int  clumpSize;
+        /* Defined only for halftone == QT_HILBERT */
+    unsigned int  clusterRadius;  
+        /* Defined only for halftone == QT_CLUSTER */
+    float         threshval;
+};
+
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int floydOpt, hilbertOpt, thresholdOpt, dither8Opt,
+        cluster3Opt, cluster4Opt, cluster8Opt;
+    unsigned int valueSpec, clumpSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "floyd",     OPT_FLAG,  NULL, &floydOpt,     0);
+    OPTENT3(0, "fs",        OPT_FLAG,  NULL, &floydOpt,     0);
+    OPTENT3(0, "threshold", OPT_FLAG,  NULL, &thresholdOpt, 0);
+    OPTENT3(0, "hilbert",   OPT_FLAG,  NULL, &hilbertOpt,   0);
+    OPTENT3(0, "dither8",   OPT_FLAG,  NULL, &dither8Opt,   0);
+    OPTENT3(0, "d8",        OPT_FLAG,  NULL, &dither8Opt,   0);
+    OPTENT3(0, "cluster3",  OPT_FLAG,  NULL, &cluster3Opt,  0);
+    OPTENT3(0, "c3",        OPT_FLAG,  NULL, &cluster3Opt,  0);
+    OPTENT3(0, "cluster4",  OPT_FLAG,  NULL, &cluster4Opt,  0);
+    OPTENT3(0, "c4",        OPT_FLAG,  NULL, &cluster4Opt,  0);
+    OPTENT3(0, "cluster8",  OPT_FLAG,  NULL, &cluster8Opt,  0);
+    OPTENT3(0, "c8",        OPT_FLAG,  NULL, &cluster8Opt,  0);
+    OPTENT3(0, "value",     OPT_FLOAT, &cmdlineP->threshval, 
+            &valueSpec, 0);
+    OPTENT3(0, "clump",     OPT_UINT,  &cmdlineP->clumpSize, 
+            &clumpSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (floydOpt + thresholdOpt + hilbertOpt + dither8Opt + 
+        cluster3Opt + cluster4Opt + cluster8Opt == 0)
+        cmdlineP->halftone = QT_FS;
+    else if (floydOpt + thresholdOpt + dither8Opt + 
+        cluster3Opt + cluster4Opt + cluster8Opt > 1)
+        pm_error("No cannot specify more than one halftoning type");
+    else {
+        if (floydOpt)
+            cmdlineP->halftone = QT_FS;
+        else if (thresholdOpt)
+            cmdlineP->halftone = QT_THRESH;
+        else if (hilbertOpt) {
+            cmdlineP->halftone = QT_HILBERT;
+
+            if (!clumpSpec)
+                cmdlineP->clumpSize = 5;
+            else {
+                if (cmdlineP->clumpSize < 2)
+                    pm_error("-clump must be at least 2.  You specified %u",
+                             cmdlineP->clumpSize);
+            }
+        } else if (dither8Opt)
+            cmdlineP->halftone = QT_DITHER8;
+        else if (cluster3Opt) {
+            cmdlineP->halftone = QT_CLUSTER;
+            cmdlineP->clusterRadius = 3;
+        } else if (cluster4Opt) {
+            cmdlineP->halftone = QT_CLUSTER;
+            cmdlineP->clusterRadius = 4;
+        } else if (cluster8Opt) {
+            cmdlineP->halftone = QT_CLUSTER;
+            cmdlineP->clusterRadius = 8;
+        } else 
+            pm_error("INTERNAL ERROR.  No halftone option");
+    }
+
+    if (!valueSpec)
+        cmdlineP->threshval = 0.5;
+    else {
+        if (cmdlineP->threshval < 0.0)
+            pm_error("-value cannot be negative.  You specified %f",
+                     cmdlineP->threshval);
+        if (cmdlineP->threshval > 1.0)
+            pm_error("-value cannot be greater than one.  You specified %f",
+                     cmdlineP->threshval);
+    }
+
+    if (clumpSpec && cmdlineP->halftone != QT_HILBERT)
+        pm_error("-clump is not valid without -hilbert");
+
+    if (argc-1 > 1)
+        pm_error("Too many arguments (%d).  There is at most one "
+                 "non-option argument:  the file name",
+                 argc-1);
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        cmdlineP->inputFilespec = "-";
+}
+
+
+static struct pam
+makeOutputPam(unsigned int const width,
+              unsigned int const height) {
+
+    struct pam outpam;
+
+    outpam.size = sizeof(outpam);
+    outpam.len = PAM_STRUCT_SIZE(tuple_type);
+    outpam.file = stdout;
+    outpam.format = PAM_FORMAT;
+    outpam.plainformat = 0;
+    outpam.height = height;
+    outpam.width = width;
+    outpam.depth = 1;
+    outpam.maxval = 1;
+    outpam.bytes_per_sample = 1;
+    strcpy(outpam.tuple_type, "BLACKANDWHITE");
+
+    return outpam;
+}
+
+
+
+/* Hilbert curve tracer */
+
+#define MAXORD 18
+
+static int hil_order,hil_ord;
+static int hil_turn;
+static int hil_dx,hil_dy;
+static int hil_x,hil_y;
+static int hil_stage[MAXORD];
+static int hil_width,hil_height;
+
+static void 
+initHilbert(int const w, 
+            int const h) {
+/*----------------------------------------------------------------------------
+  Initialize the Hilbert curve tracer 
+-----------------------------------------------------------------------------*/
+    int big,ber;
+    hil_width = w;
+    hil_height = h;
+    big = w > h ? w : h;
+    for (ber = 2, hil_order = 1; ber < big; ber <<= 1, hil_order++);
+    if (hil_order > MAXORD)
+        pm_error("Sorry, hilbert order is too large");
+    hil_ord = hil_order;
+    hil_order--;
+}
+
+
+
+static int 
+hilbert(int * const px, int * const py) {
+/*----------------------------------------------------------------------------
+  Return non-zero if got another point
+-----------------------------------------------------------------------------*/
+    int temp;
+    if (hil_ord > hil_order) {
+        /* have to do first point */
+
+        hil_ord--;
+        hil_stage[hil_ord] = 0;
+        hil_turn = -1;
+        hil_dy = 1;
+        hil_dx = hil_x = hil_y = 0;
+        *px = *py = 0;
+        return 1;
+    }
+
+    /* Operate the state machine */
+    for(;;)  {
+        switch (hil_stage[hil_ord]) {
+        case 0:
+            hil_turn = -hil_turn;
+            temp = hil_dy;
+            hil_dy = -hil_turn * hil_dx;
+            hil_dx = hil_turn * temp;
+            if (hil_ord > 0) {
+                hil_stage[hil_ord] = 1;
+                hil_ord--;
+                hil_stage[hil_ord]=0;
+                continue;
+            }
+        case 1:
+            hil_x += hil_dx;
+            hil_y += hil_dy;
+            if (hil_x < hil_width && hil_y < hil_height) {
+                hil_stage[hil_ord] = 2;
+                *px = hil_x;
+                *py = hil_y;
+                return 1;
+            }
+        case 2:
+            hil_turn = -hil_turn;
+            temp = hil_dy;
+            hil_dy = -hil_turn * hil_dx;
+            hil_dx = hil_turn * temp;
+            if (hil_ord > 0) { 
+                /* recurse */
+
+                hil_stage[hil_ord] = 3;
+                hil_ord--;
+                hil_stage[hil_ord]=0;
+                continue;
+            }
+        case 3:
+            hil_x += hil_dx;
+            hil_y += hil_dy;
+            if (hil_x < hil_width && hil_y < hil_height) {
+                hil_stage[hil_ord] = 4;
+                *px = hil_x;
+                *py = hil_y;
+                return 1;
+            }
+        case 4:
+            if (hil_ord > 0) {
+                /* recurse */
+                hil_stage[hil_ord] = 5;
+                hil_ord--;
+                hil_stage[hil_ord]=0;
+                continue;
+            }
+        case 5:
+            temp = hil_dy;
+            hil_dy = -hil_turn * hil_dx;
+            hil_dx = hil_turn * temp;
+            hil_turn = -hil_turn;
+            hil_x += hil_dx;
+            hil_y += hil_dy;
+            if (hil_x < hil_width && hil_y < hil_height) {
+                hil_stage[hil_ord] = 6;
+                *px = hil_x;
+                *py = hil_y;
+                return 1;
+            }
+        case 6:
+            if (hil_ord > 0) {
+                /* recurse */
+                hil_stage[hil_ord] = 7;
+                hil_ord--;
+                hil_stage[hil_ord]=0;
+                continue;
+            }
+        case 7:
+            temp = hil_dy;
+            hil_dy = -hil_turn * hil_dx;
+            hil_dx = hil_turn * temp;
+            hil_turn = -hil_turn;
+            /* Return from a recursion */
+            if (hil_ord < hil_order)
+                hil_ord++;
+            else
+                return 0;
+        }
+    }
+}
+
+
+
+static void 
+doHilbert(FILE *       const ifP,
+          unsigned int const clumpSize) {
+/*----------------------------------------------------------------------------
+  Use hilbert space filling curve dithering
+-----------------------------------------------------------------------------*/
+    /*
+     * This is taken from the article "Digital Halftoning with
+     * Space Filling Curves" by Luiz Velho, proceedings of
+     * SIGRAPH '91, page 81.
+     *
+     * This is not a terribly efficient or quick version of
+     * this algorithm, but it seems to work. - Graeme Gill.
+     * graeme@labtam.labtam.OZ.AU
+     *
+     */
+    struct pam graypam;
+    struct pam bitpam;
+    tuple ** grays;
+    tuple ** bits;
+
+    int end;
+    int *x,*y;
+    int sum;
+
+    grays = pnm_readpam(ifP, &graypam, sizeof(graypam));
+
+    bitpam = makeOutputPam(graypam.width, graypam.height);
+
+    bits = pnm_allocpamarray(&bitpam);
+
+    MALLOCARRAY(x, clumpSize);
+    MALLOCARRAY(y, clumpSize);
+    if (x == NULL  || y == NULL)
+        pm_error("out of memory");
+    initHilbert(graypam.width, graypam.height);
+
+    sum = 0;
+    end = clumpSize;
+
+    while (end == clumpSize) {
+        unsigned int i;
+        /* compute the next cluster co-ordinates along hilbert path */
+        for (i = 0; i < end; i++) {
+            if (hilbert(&x[i],&y[i])==0)
+                end = i;    /* we reached the end */
+        }
+        /* sum levels */
+        for (i = 0; i < end; i++)
+            sum += grays[y[i]][x[i]][0];
+        /* dither half and half along path */
+        for (i = 0; i < end; i++) {
+            unsigned int const row = y[i];
+            unsigned int const col = x[i];
+            if (sum >= graypam.maxval) {
+                bits[row][col][0] = 1;
+                sum -= graypam.maxval;
+            } else
+                bits[row][col][0] = 0;
+        }
+    }
+    pnm_writepam(&bitpam, bits);
+
+    pnm_freepamarray(bits, &bitpam);
+    pnm_freepamarray(grays, &graypam);
+}
+
+
+
+struct converter {
+    void (*convertRow)(struct converter * const converterP,
+                       unsigned int       const row,
+                       tuplen                   grayrow[], 
+                       tuple                    bitrow[]);
+    void (*destroy)(struct converter * const converterP);
+    unsigned int cols;
+    void * stateP;
+};
+
+
+
+struct fsState {
+    float * thiserr;
+    float * nexterr;
+    bool fs_forward;
+    samplen threshval;
+        /* The power value we consider to be half white */
+};
+
+
+static void
+fsConvertRow(struct converter * const converterP,
+             unsigned int       const row,
+             tuplen                   grayrow[],
+             tuple                    bitrow[]) {
+
+    struct fsState * const stateP = converterP->stateP;
+
+    samplen * const thiserr = stateP->thiserr;
+    samplen * const nexterr = stateP->nexterr;
+
+    unsigned int limitcol;
+    unsigned int col;
+    
+    for (col = 0; col < converterP->cols + 2; ++col)
+        nexterr[col] = 0.0;
+
+    if (stateP->fs_forward) {
+        col = 0;
+        limitcol = converterP->cols;
+    } else {
+        col = converterP->cols - 1;
+        limitcol = -1;
+    }
+
+    do {
+        samplen sum;
+
+        sum = pm_ungamma709(grayrow[col][0]) + thiserr[col + 1];
+        if (sum >= stateP->threshval) {
+            /* We've accumulated enough light to justify a white output
+               pixel.
+            */
+            bitrow[col][0] = PAM_BW_WHITE;
+            /* Remove from sum the power of the white output pixel */
+            sum -= 2*stateP->threshval;
+        } else
+            bitrow[col][0] = PAM_BLACK;
+        
+        /* Forward the power from current input pixel and the power
+           forwarded from previous input pixels to the current pixel,
+           to future output pixels, but subtract out any power we put
+           into the current output pixel.  
+        */
+        if (stateP->fs_forward) {
+            thiserr[col + 2] += (sum * 7) / 16;
+            nexterr[col    ] += (sum * 3) / 16;
+            nexterr[col + 1] += (sum * 5) / 16;
+            nexterr[col + 2] += (sum    ) / 16;
+            
+            ++col;
+        } else {
+            thiserr[col    ] += (sum * 7) / 16;
+            nexterr[col + 2] += (sum * 3) / 16;
+            nexterr[col + 1] += (sum * 5) / 16;
+            nexterr[col    ] += (sum    ) / 16;
+            
+            --col;
+        }
+    } while (col != limitcol);
+    
+    stateP->thiserr = nexterr;
+    stateP->nexterr = thiserr;
+    stateP->fs_forward = ! stateP->fs_forward;
+}
+
+
+
+static void
+fsDestroy(struct converter * const converterP) {
+    free(converterP->stateP);
+}
+
+
+
+static struct converter
+createFsConverter(struct pam * const graypamP,
+                  float        const threshFraction) {
+
+    struct fsState * stateP;
+    struct converter converter;
+
+    converter.cols       = graypamP->width;
+    converter.convertRow = &fsConvertRow;
+    converter.destroy    = &fsDestroy;
+
+    MALLOCVAR_NOFAIL(stateP);
+
+    /* Initialize Floyd-Steinberg error vectors. */
+    MALLOCARRAY_NOFAIL(stateP->thiserr, graypamP->width + 2);
+    MALLOCARRAY_NOFAIL(stateP->nexterr, graypamP->width + 2);
+    srand((int)(time(NULL) ^ getpid()));
+
+    {
+        /* (random errors in [-1/8 .. 1/8]) */
+        unsigned int col;
+        for (col = 0; col < graypamP->width + 2; ++col)
+            stateP->thiserr[col] = ((float)rand()/RAND_MAX - 0.5) / 4;
+    }
+
+    stateP->threshval  = threshFraction;
+
+    stateP->fs_forward = TRUE;
+
+    converter.stateP = stateP;
+
+    return converter;
+}
+
+
+
+struct threshState {
+    samplen threshval;
+};
+
+
+static void
+threshConvertRow(struct converter * const converterP,
+                 unsigned int       const row,
+                 tuplen                   grayrow[],
+                 tuple                    bitrow[]) {
+    
+    struct threshState * const stateP = converterP->stateP;
+
+    unsigned int col;
+    for (col = 0; col < converterP->cols; ++col)
+        bitrow[col][0] =
+            grayrow[col][0] >= stateP->threshval ? PAM_BW_WHITE : PAM_BLACK;
+}
+
+
+
+static void
+threshDestroy(struct converter * const converterP) {
+    free(converterP->stateP);
+}
+
+
+
+static struct converter
+createThreshConverter(struct pam * const graypamP,
+                      float        const threshFraction) {
+
+    struct threshState * stateP;
+    struct converter converter;
+
+    MALLOCVAR_NOFAIL(stateP);
+
+    converter.cols       = graypamP->width;
+    converter.convertRow = &threshConvertRow;
+    converter.destroy    = &threshDestroy;
+    
+    stateP->threshval    = threshFraction;
+    converter.stateP     = stateP;
+
+    return converter;
+}
+
+
+
+struct clusterState {
+    unsigned int radius;
+    float ** clusterMatrix;
+};
+
+
+
+static void
+clusterConvertRow(struct converter * const converterP,
+                  unsigned int       const row,
+                  tuplen                   grayrow[],
+                  tuple                    bitrow[]) {
+
+    struct clusterState * const stateP = converterP->stateP;
+    unsigned int const diameter = 2 * stateP->radius;
+
+    unsigned int col;
+
+    for (col = 0; col < converterP->cols; ++col) {
+        float const threshold = 
+            stateP->clusterMatrix[row % diameter][col % diameter];
+        bitrow[col][0] = 
+            grayrow[col][0] > threshold ? PAM_BW_WHITE : PAM_BLACK;
+    }
+}
+
+
+
+static void
+clusterDestroy(struct converter * const converterP) {
+
+    struct clusterState * const stateP = converterP->stateP;
+    unsigned int const diameter = 2 * stateP->radius;
+
+    unsigned int row;
+
+    for (row = 0; row < diameter; ++row)
+        free(stateP->clusterMatrix[row]);
+
+    free(stateP->clusterMatrix);
+    
+    free(stateP);
+}
+
+
+
+static struct converter
+createClusterConverter(struct pam *    const graypamP,
+                       enum ditherType const ditherType,
+                       unsigned int    const radius) {
+    
+    /* TODO: We create a floating point normalized, gamma-adjusted
+       dither matrix from the old integer dither matrices that were 
+       developed for use with integer arithmetic.  We really should
+       just change the literal values in dither.h instead of computing
+       the matrix from the integer literal values here.
+    */
+    
+    int const clusterNormalizer = radius * radius * 2;
+    unsigned int const diameter = 2 * radius;
+
+    struct converter converter;
+    struct clusterState * stateP;
+    unsigned int row;
+
+    converter.cols       = graypamP->width;
+    converter.convertRow = &clusterConvertRow;
+    converter.destroy    = &clusterDestroy;
+
+    MALLOCVAR_NOFAIL(stateP);
+
+    stateP->radius = radius;
+
+    MALLOCARRAY_NOFAIL(stateP->clusterMatrix, diameter);
+    for (row = 0; row < diameter; ++row) {
+        unsigned int col;
+
+        MALLOCARRAY_NOFAIL(stateP->clusterMatrix[row], diameter);
+        
+        for (col = 0; col < diameter; ++col) {
+            switch (ditherType) {
+            case DT_REGULAR: 
+                switch (radius) {
+                case 8: 
+                    stateP->clusterMatrix[row][col] = 
+                        pm_gamma709((float)dither8[row][col] / 256);
+                    break;
+                default: 
+                    pm_error("INTERNAL ERROR: invalid radius");
+                }
+                break;
+            case DT_CLUSTER: {
+                int val;
+                switch (radius) {
+                case 3: val = cluster3[row][col]; break;
+                case 4: val = cluster4[row][col]; break;
+                case 8: val = cluster8[row][col]; break;
+                default:
+                    pm_error("INTERNAL ERROR: invalid radius");
+                }
+                stateP->clusterMatrix[row][col] = 
+                    pm_gamma709((float)val / clusterNormalizer);
+            }
+            break;
+            }
+        }
+    }            
+
+    converter.stateP = stateP;
+
+    return converter;
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE* ifP;
+
+    pgm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    if (cmdline.halftone == QT_HILBERT)
+        doHilbert(ifP, cmdline.clumpSize);
+    else {
+        struct converter converter;
+        struct pam graypam;
+        struct pam bitpam;
+        tuplen * grayrow;
+        tuple * bitrow;
+        int row;
+
+        pnm_readpaminit(ifP, &graypam, sizeof(graypam));
+
+        bitpam = makeOutputPam(graypam.width, graypam.height);
+        
+        pnm_writepaminit(&bitpam);
+
+        switch (cmdline.halftone) {
+        case QT_FS:
+            converter = createFsConverter(&graypam, cmdline.threshval);
+            break;
+        case QT_THRESH:
+            converter = createThreshConverter(&graypam, cmdline.threshval);
+            break;
+        case QT_DITHER8: 
+            converter = createClusterConverter(&graypam, DT_REGULAR, 8); 
+            break;
+        case QT_CLUSTER: 
+            converter = createClusterConverter(&graypam, 
+                                               DT_CLUSTER, 
+                                               cmdline.clusterRadius);
+            break;
+        case QT_HILBERT: 
+                pm_error("INTERNAL ERROR: halftone is QT_HILBERT where it "
+                         "shouldn't be.");
+                break;
+        }
+
+        grayrow = pnm_allocpamrown(&graypam);
+        bitrow  = pnm_allocpamrow(&bitpam);
+
+        for (row = 0; row < graypam.height; ++row) {
+            pnm_readpamrown(&graypam, grayrow);
+
+            converter.convertRow(&converter, row, grayrow, bitrow);
+            
+            pnm_writepamrow(&bitpam, bitrow);
+        }
+        pnm_freepamrow(bitrow);
+        pnm_freepamrow(grayrow);
+
+        if (converter.destroy)
+            converter.destroy(&converter);
+    }
+
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/editor/pamedge.c b/editor/pamedge.c
new file mode 100644
index 00000000..e73c9d17
--- /dev/null
+++ b/editor/pamedge.c
@@ -0,0 +1,203 @@
+/* pnmedge.c - edge-detection
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**   modified for pnm by Peter Kirchgessner, 1995.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "pam.h"
+
+
+
+static void
+writeBlackRow(struct pam * const pamP) {
+
+    tuple * const tuplerow = pnm_allocpamrow(pamP);
+
+    unsigned int col;
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            tuplerow[col][plane] = 0;
+    }
+    pnm_writepamrow(pamP, tuplerow);
+} 
+
+
+
+static void
+rotateRows(tuple ** const row0P,
+           tuple ** const row1P,
+           tuple ** const row2P) {
+    /* Rotate rows. */
+    tuple * const formerRow0 = *row0P;
+    *row0P = *row1P;
+    *row1P = *row2P;
+    *row2P = formerRow0;
+}
+
+
+
+static long
+horizGradient(tuple *      const tuplerow,
+              unsigned int const col,
+              unsigned int const plane) {
+
+    return (long)tuplerow[col+1][plane] - (long)tuplerow[col-1][plane];
+}
+
+
+
+static long
+horizAvg(tuple *      const tuplerow,
+         unsigned int const col,
+         unsigned int const plane) {
+
+    return
+        1 * (long)tuplerow[col-1][plane] +
+        2 * (long)tuplerow[col  ][plane] +
+        1 * (long)tuplerow[col+1][plane];
+
+}
+
+
+
+static void
+computeOneRow(struct pam * const inpamP,
+              struct pam * const outpamP,
+              tuple *      const row0,
+              tuple *      const row1,
+              tuple *      const row2,
+              tuple *      const orow) {
+/*----------------------------------------------------------------------------
+   Compute an output row from 3 input rows.
+
+   The input rows must have the same maxval as the output row.
+-----------------------------------------------------------------------------*/
+    unsigned int plane;
+
+    for (plane = 0; plane < inpamP->depth; ++plane) {
+        unsigned int col;
+
+        /* Left column is black */
+        orow[0][plane] = 0;
+
+        for (col = 1; col < inpamP->width - 1; ++col) {
+            double const grad1 = 
+                1 * horizGradient(row0, col, plane) +
+                2 * horizGradient(row1, col, plane) +
+                1 * horizGradient(row2, col, plane);
+
+            double const grad2 = 
+                horizAvg(row2, col, plane) - horizAvg(row0, col, plane);
+
+            double const gradient = sqrt(SQR(grad1) + SQR(grad2));
+
+            /* apply arbitrary scaling factor and maxval clipping */
+            orow[col][plane] = MIN(outpamP->maxval, (long)(gradient / 1.8));
+
+            /* Right column is black */
+            orow[inpamP->width - 1][plane] = 0;
+        }
+    }
+}
+
+
+
+static void
+writeMiddleRows(struct pam * const inpamP,
+                struct pam * const outpamP) {
+
+    tuple *row0, *row1, *row2;
+    tuple *orow, *irow;
+    unsigned int row;
+
+    irow = pnm_allocpamrow(inpamP);
+    orow = pnm_allocpamrow(outpamP);
+    row0 = pnm_allocpamrow(outpamP);
+    row1 = pnm_allocpamrow(outpamP);
+    row2 = pnm_allocpamrow(outpamP);
+
+    /* Read in the first two rows. */
+    pnm_readpamrow(inpamP, irow);
+    pnm_scaletuplerow(inpamP, row0, irow, outpamP->maxval);
+    pnm_readpamrow(inpamP, irow);
+    pnm_scaletuplerow(inpamP, row1, irow, outpamP->maxval);
+
+    pm_message("row1[0][0]=%lu", row1[0][0]);
+
+    for (row = 1; row < inpamP->height - 1; ++row) {
+        /* Read in the next row and write out the current row.  */
+
+        pnm_readpamrow(inpamP, irow);
+        pnm_scaletuplerow(inpamP, row2, irow, outpamP->maxval);
+
+        computeOneRow(inpamP, outpamP, row0, row1, row2, orow);
+
+        pnm_writepamrow(outpamP, orow);
+
+        rotateRows(&row0, &row1, &row2);
+    }
+    pnm_freepamrow(orow);
+    pnm_freepamrow(row2);
+    pnm_freepamrow(row1);
+    pnm_freepamrow(row0);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+    FILE *ifP;
+    struct pam inpam, outpam;
+
+    pnm_init( &argc, argv );
+
+    if (argc-1 == 1) 
+        ifP = pm_openr(argv[1]);
+    else if (argc-1 == 0)
+        ifP = stdin;
+    else
+        pm_error("Too many arguments.  Program takes at most 1 argument: "
+                 "input file name");
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+    if (inpam.width < 3)
+        pm_error("Image is %u columns wide.  It must be at least 3.",
+                 inpam.width);
+    if (inpam.height < 3)
+        pm_error("Image is %u rows high.  It must be at least 3.",
+                 inpam.height);
+
+    outpam = inpam;
+    outpam.file = stdout;
+    if (PAM_FORMAT_TYPE(inpam.format) == PBM_TYPE) {
+        outpam.format = PGM_FORMAT;
+        outpam.maxval = 255;
+    }
+
+    pnm_writepaminit(&outpam);
+
+    /* First row is black: */
+    writeBlackRow(&outpam      );
+
+    writeMiddleRows(&inpam, &outpam);
+
+    pm_close(ifP);
+
+    /* Last row is black: */
+    writeBlackRow(&outpam);
+
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/editor/pamenlarge.c b/editor/pamenlarge.c
new file mode 100644
index 00000000..15b91b4f
--- /dev/null
+++ b/editor/pamenlarge.c
@@ -0,0 +1,117 @@
+/*=============================================================================
+                             pamenlarge
+===============================================================================
+  By Bryan Henderson 2004.09.26.  Contributed to the public domain by its
+  author.
+=============================================================================*/
+
+#include "pam.h"
+#include "mallocvar.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  
+    unsigned int scaleFactor;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    if (argc-1 < 1)
+        pm_error("You must specify at least one argument:  The scale factor");
+    else {
+        cmdlineP->scaleFactor = atoi(argv[1]);
+        
+        if (cmdlineP->scaleFactor < 1)
+            pm_error("Scale factor must be an integer at least 1.  "
+                     "You specified '%s'", argv[1]);
+
+        if (argc-1 >= 2)
+            cmdlineP->inputFilespec = argv[2];
+        else
+            cmdlineP->inputFilespec = "-";
+    }
+}        
+
+
+
+static void
+makeOutputRowMap(tuple **     const outTupleRowP,
+                 struct pam * const outpamP,
+                 struct pam * const inpamP,
+                 tuple *      const inTuplerow) {
+/*----------------------------------------------------------------------------
+   Create a tuple *outTupleRowP which is actually a row of pointers into
+   inTupleRow[], so as to map input pixels to output pixels by stretching.
+-----------------------------------------------------------------------------*/
+    tuple * newtuplerow;
+    int col;
+
+    MALLOCARRAY_NOFAIL(newtuplerow, outpamP->width);
+
+    for (col = 0 ; col < inpamP->width; ++col) {
+        unsigned int const scaleFactor = outpamP->width / inpamP->width;
+        unsigned int subcol;
+
+        for (subcol = 0; subcol < scaleFactor; ++subcol)
+            newtuplerow[col * scaleFactor + subcol] = inTuplerow[col];
+    }
+    *outTupleRowP = newtuplerow;
+}
+
+
+
+int
+main(int    argc, 
+     char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    struct pam inpam;
+    struct pam outpam; 
+    tuple * tuplerow;
+    tuple * newtuplerow;
+    int row;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+ 
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    outpam = inpam; 
+    outpam.file   = stdout;
+    outpam.width  = inpam.width * cmdline.scaleFactor;
+    outpam.height = inpam.height * cmdline.scaleFactor; 
+
+    pnm_writepaminit(&outpam);
+
+    tuplerow = pnm_allocpamrow(&inpam);
+
+    makeOutputRowMap(&newtuplerow, &outpam, &inpam, tuplerow);
+
+    for (row = 0; row < inpam.height; ++row) {
+        pnm_readpamrow(&inpam, tuplerow);
+        pnm_writepamrowmult(&outpam, newtuplerow, cmdline.scaleFactor);
+    }
+
+    free(newtuplerow);
+
+    pnm_freepamrow(tuplerow);
+
+    pm_close(ifP);
+    pm_close(stdout);
+
+    return 0;
+}
+
diff --git a/editor/pamenlarge.test b/editor/pamenlarge.test
new file mode 100644
index 00000000..a2221d4d
--- /dev/null
+++ b/editor/pamenlarge.test
@@ -0,0 +1,8 @@
+echo Test 1.  Should print 3424505894 913236
+./pamenlarge 3 ../testimg.ppm | cksum
+echo Test 2.  Should print 2940246561 304422
+ppmtopgm ../testimg.ppm | ./pamenlarge 3 | cksum
+echo Test 3.  Should print 3342398172 297
+./pamenlarge 3 ../testgrid.pbm | cksum
+echo Test 4.  Should print 237488670 3133413
+./pamenlarge 3 -plain ../testimg.ppm | cksum
diff --git a/editor/pamflip.c b/editor/pamflip.c
new file mode 100644
index 00000000..59b60b56
--- /dev/null
+++ b/editor/pamflip.c
@@ -0,0 +1,910 @@
+/* pamflip.c - perform one or more flip operations on a Netpbm image
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/*
+   transformGen() is the general transformation function.
+   
+   The following are enhancements for specific cases:
+   
+     transformRowByRowPbm()
+     transformRowsBottomTopPbm()
+     transformRowByRowNonPbm()
+     transformRowsBottomTopNonPbm()
+     transformPbm()
+   
+   Although we use transformGen() only when none of the enhancement
+   functions apply, it is capable of handling all cases.  (Only that it
+   is slow, and uses more memory.)  In the same manner, transformPbm() is
+   capable of handling all pbm transformations and transformRowByRowNonPbm()
+   transformRowsBottomTomNonPbm() are capable of handling pbm.
+
+
+   There is some fancy virtual memory management in transformGen() to avoid
+   page thrashing when you flip a large image in a columns-for-rows
+   way (e.g. -transpose).
+   
+   The page thrashing we're trying to avoid could happen because the
+   output of the transformation is stored in an array of tuples in
+   virtual memory.  A tuple array is stored in column-first order,
+   meaning that all the columns of particular row are contiguous, the
+   next row is next to that, etc.  If you fill up that array by
+   filling in Column 0 sequentially in every row from top to bottom,
+   you will touch a lot of different virtual memory pages, and every
+   one has to be paged in as you touch it.
+
+   If the number of virtual memory pages you touch exceeds the amount
+   of real memory the process can get, then by the time you hit the bottom
+   of the tuple array, the pages that hold the top are already paged out.
+   So if you go back and do Column 1 from top to bottom, you will again
+   touch lots of pages and have to page in every one of them.  Do this 
+   for 100 columns, and you might page in every page in the array 100 times
+   each, putting a few bytes in the page each time.
+
+   That is very expensive.  Instead, you'd like to keep the same pages in
+   real memory as long as possible and fill them up as much as you can 
+   before paging them out and working on a new set of pages.  You can do
+   that by doing Column 0 from top to say Row 10, then Column 1 from top
+   to Row 10, etc. all the way across the image.  Assuming 10 rows fits
+   in real memory, you will keep the virtual memory for the first 10 rows
+   of the tuple array in real memory until you've filled them in completely.
+   Now you go back and do Column 0 from Row 11 to Row 20, then Column 1
+   from Row 11 to Row 20, and so on.
+
+   So why are we even trying to fill in column by column instead of just
+   filling in row by row?  Because we're reading an input image row by row
+   and transforming it in such a way that a row of the input becomes
+   a column of the output.  In order to fill in a whole row of the output,
+   we'd have to read a whole column of the input, and then we have the same
+   page thrashing problem in the input.
+
+   So the optimal procedure is to do N output rows in each pass, where
+   N is the largest number of rows we can fit in real memory.  In each
+   pass, we read certain columns of every row of the input and output
+   every column of certain rows of the output.  The output area for
+   the rows in the pass gets paged in once during the pass and then
+   never again.  Note that some pages of every row of the input get
+   paged in once in each pass too.  As each input page is referenced
+   only in one burst, input pages do not compete with output pages for
+   real memory -- the working set is the output pages, which get referenced
+   cyclically.
+
+   This all worked when we used the pnm xel format, but now that we
+   use the pam tuple format, there's an extra memory reference that
+   could be causing trouble.  Because tuples have varying depth, a pam
+   row is an array of pointers to the tuples.  To access a tuple, we
+   access the tuple pointer, then the tuple.  We could probably do better,
+   because the samples are normally in the memory immediately following
+   the tuple pointers, so we could compute where a tuple's samples live
+   without actually loading the tuple address from memory.  I.e. the 
+   address of the tuple for Column 5 of Row 9 of a 3-deep 100-wide
+   image is (void*)tuples[9] + 100 * sizeof(tuple*) + 5*(3*sizeof(sample)).
+*/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <limits.h>
+#include <string.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "bitreverse.h"
+
+enum xformType {LEFTRIGHT, TOPBOTTOM, TRANSPOSE};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+    unsigned int xformCount;
+        /* Number of transforms in the 'xformType' array */
+    enum xformType xformList[10];
+        /* Array of transforms to be applied, in order */
+    unsigned int availableMemory;
+    unsigned int pageSize;
+    unsigned int verbose;
+};
+
+
+
+static void
+parseXformOpt(const char *     const xformOpt,
+              unsigned int  *  const xformCountP,
+              enum xformType * const xformList) {
+/*----------------------------------------------------------------------------
+   Translate the -xform option string into an array of transform types.
+
+   Return the array as xformList[], which is preallocated for at least
+   10 elements.
+-----------------------------------------------------------------------------*/
+    unsigned int xformCount;
+    char * xformOptWork;
+    char * cursor;
+    bool eol;
+    
+    xformOptWork = strdup(xformOpt);
+    cursor = &xformOptWork[0];
+    
+    eol = FALSE;    /* initial value */
+    xformCount = 0; /* initial value */
+    while (!eol && xformCount < 10) {
+        const char * token;
+        token = strsepN(&cursor, ",");
+        if (token) {
+            if (streq(token, "leftright"))
+                xformList[xformCount++] = LEFTRIGHT;
+            else if (streq(token, "topbottom"))
+                xformList[xformCount++] = TOPBOTTOM;
+            else if (streq(token, "transpose"))
+                xformList[xformCount++] = TRANSPOSE;
+            else if (streq(token, ""))
+            { /* ignore it */}
+            else
+                pm_error("Invalid transform type in -xform option: '%s'",
+                         token );
+        } else
+            eol = TRUE;
+    }
+    free(xformOptWork);
+
+    *xformCountP = xformCount;
+}
+
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int lr, tb, xy, r90, r270, r180, null;
+    unsigned int memsizeSpec, pagesizeSpec, xformSpec;
+    unsigned int memsizeOpt;
+    const char *xformOpt;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "lr",        OPT_FLAG,    NULL, &lr,      0);
+    OPTENT3(0, "leftright", OPT_FLAG,    NULL, &lr,      0);
+    OPTENT3(0, "tb",        OPT_FLAG,    NULL, &tb,      0);
+    OPTENT3(0, "topbottom", OPT_FLAG,    NULL, &tb,      0);
+    OPTENT3(0, "xy",        OPT_FLAG,    NULL, &xy,      0);
+    OPTENT3(0, "transpose", OPT_FLAG,    NULL, &xy,      0);
+    OPTENT3(0, "r90",       OPT_FLAG,    NULL, &r90,     0);
+    OPTENT3(0, "rotate90",  OPT_FLAG,    NULL, &r90,     0);
+    OPTENT3(0, "ccw",       OPT_FLAG,    NULL, &r90,     0);
+    OPTENT3(0, "r180",      OPT_FLAG,    NULL, &r180,    0);
+    OPTENT3(0, "rotate180", OPT_FLAG,    NULL, &r180,    0);
+    OPTENT3(0, "r270",      OPT_FLAG,    NULL, &r270,    0);
+    OPTENT3(0, "rotate270", OPT_FLAG,    NULL, &r270,    0);
+    OPTENT3(0, "cw",        OPT_FLAG,    NULL, &r270,    0);
+    OPTENT3(0, "null",      OPT_FLAG,    NULL, &null,    0);
+    OPTENT3(0, "verbose",   OPT_FLAG,    NULL, &cmdlineP->verbose,       0);
+    OPTENT3(0, "memsize",   OPT_UINT,    &memsizeOpt, 
+            &memsizeSpec,       0);
+    OPTENT3(0, "pagesize",  OPT_UINT,    &cmdlineP->pageSize,
+            &pagesizeSpec,      0);
+    OPTENT3(0, "xform",     OPT_STRING,  &xformOpt, 
+            &xformSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We don't parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (lr + tb + xy + r90 + r180 + r270 + null > 1)
+        pm_error("You may specify only one type of flip.");
+    if (lr + tb + xy + r90 + r180 + r270 + null == 1) {
+        if (lr) {
+            cmdlineP->xformCount = 1;
+            cmdlineP->xformList[0] = LEFTRIGHT;
+        } else if (tb) {
+            cmdlineP->xformCount = 1;
+            cmdlineP->xformList[0] = TOPBOTTOM;
+        } else if (xy) {
+            cmdlineP->xformCount = 1;
+            cmdlineP->xformList[0] = TRANSPOSE;
+        } else if (r90) {
+            cmdlineP->xformCount = 2;
+            cmdlineP->xformList[0] = TRANSPOSE;
+            cmdlineP->xformList[1] = TOPBOTTOM;
+        } else if (r180) {
+            cmdlineP->xformCount = 2;
+            cmdlineP->xformList[0] = LEFTRIGHT;
+            cmdlineP->xformList[1] = TOPBOTTOM;
+        } else if (r270) {
+            cmdlineP->xformCount = 2;
+            cmdlineP->xformList[0] = TRANSPOSE;
+            cmdlineP->xformList[1] = LEFTRIGHT;
+        } else if (null) {
+            cmdlineP->xformCount = 0;
+        }
+    } else if (xformSpec) 
+        parseXformOpt(xformOpt, &cmdlineP->xformCount, cmdlineP->xformList);
+    else
+        pm_error("You must specify an option such as -topbottom to indicate "
+                 "what kind of flip you want.");
+
+    if (memsizeSpec) {
+        if (memsizeOpt > UINT_MAX / 1024 / 1024)
+            pm_error("-memsize value too large: %u MiB.  Maximum this program "
+                     "can handle is %u MiB", 
+                     memsizeOpt, UINT_MAX / 1024 / 1024);
+        cmdlineP->availableMemory = memsizeOpt * 1024 *1024;
+    } else
+        cmdlineP->availableMemory = UINT_MAX;
+
+    if (!pagesizeSpec)
+        cmdlineP->pageSize = 4*1024;         
+
+    if (argc-1 == 0) 
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+struct xformMatrix {
+    int a;
+    int b;
+    int c;
+    int d;
+    int e;
+    int f;
+};
+
+
+
+static void
+leftright(struct xformMatrix * const xformP) {
+    xformP->a = - xformP->a;
+    xformP->c = - xformP->c;
+    xformP->e = - xformP->e + 1;
+}
+
+
+
+static void
+topbottom(struct xformMatrix * const xformP) {
+    xformP->b = - xformP->b;
+    xformP->d = - xformP->d;
+    xformP->f = - xformP->f + 1;
+}
+
+
+
+static void
+swap(int * const xP, int * const yP) {
+
+    int const t = *xP;
+
+    *xP = *yP;
+    *yP = t;
+}
+
+
+
+static void
+transpose(struct xformMatrix * const xformP) {
+    swap(&xformP->a, &xformP->b);
+    swap(&xformP->c, &xformP->d);
+    swap(&xformP->e, &xformP->f);
+}
+
+
+
+static void
+computeXformMatrix(struct xformMatrix * const xformP, 
+                   unsigned int         const xformCount,
+                   enum xformType             xformType[]) {
+
+    struct xformMatrix const nullTransform = {1, 0, 0, 1, 0, 0};
+
+    unsigned int i;
+
+    *xformP = nullTransform;   /* initial value */
+
+    for (i = 0; i < xformCount; ++i) {
+        switch (xformType[i]) {
+        case LEFTRIGHT: 
+            leftright(xformP);
+            break;
+        case TOPBOTTOM:
+            topbottom(xformP);
+            break;
+        case TRANSPOSE:
+            transpose(xformP);
+            break;
+        }
+    }
+}
+
+
+
+static void
+bitOrderReverse(unsigned char * const bitrow, 
+                unsigned int    const cols) {
+/*----------------------------------------------------------------------------
+  Reverse the bits in a packed pbm row (1 bit per pixel).  I.e. the leftmost
+  bit becomes the rightmost, etc.
+-----------------------------------------------------------------------------*/
+    unsigned int const lastfullByteIdx = cols/8 - 1;
+
+    if (cols == 0 || bitrow == NULL )
+        pm_error("Invalid arguments passed to bitOrderReverse");
+
+    if (cols <= 8)
+        bitrow[0] = bitreverse[bitrow[0]] << (8-cols);
+    else if (cols % 8 == 0) {
+        unsigned int i, j;
+        for (i = 0, j = lastfullByteIdx; i <= j; ++i, --j) {
+            unsigned char const t = bitreverse[bitrow[j]]; 
+            bitrow[j] = bitreverse[bitrow[i]];
+            bitrow[i] = t;
+        }
+    } else {
+        unsigned int const m = cols % 8; 
+
+        unsigned int i, j;
+            /* Cursors into bitrow[].  i moves from left to center;
+               j moves from right to center as bits of bitrow[] are exchanged.
+            */
+        unsigned char th, tl;  /* 16 bit temp ( th << 8 | tl ) */
+        tl = 0;
+        for (i = 0, j = lastfullByteIdx+1; i <= lastfullByteIdx/2; ++i, --j) {
+            th = bitreverse[bitrow[i]];
+            bitrow[i] =
+                bitreverse[0xff & ((bitrow[j-1] << 8 | bitrow[j]) >> (8-m))];
+            bitrow[j] = 0xff & ((th << 8 | tl) >> m);
+            tl = th;
+        }
+        if (i == j) 
+            /* bitrow[] has an odd number of bytes (an even number of
+               full bytes; lastfullByteIdx is odd), so we did all but
+               the center byte above.  We do the center byte now.
+            */
+            bitrow[j] = 0xff & ((bitreverse[bitrow[i]] << 8 | tl) >> m);
+    }
+}
+
+
+
+static void
+transformRowByRowPbm(struct pam * const inpamP, 
+                     struct pam * const outpamP,
+                     bool         const reverse) {
+/*----------------------------------------------------------------------------
+  Transform a PBM image either by flipping it left for right, or just leaving
+  it alone, as indicated by 'reverse'.
+
+  Process the image one row at a time and use fast packed PBM bit
+  reverse algorithm (where required).
+-----------------------------------------------------------------------------*/
+    unsigned char * bitrow;
+    unsigned int row;
+
+    bitrow = pbm_allocrow_packed(outpamP->width); 
+
+    pbm_writepbminit(outpamP->file, outpamP->width, outpamP->height, 0);
+
+    for (row = 0; row < inpamP->height; ++row) {
+        pbm_readpbmrow_packed(inpamP->file,  bitrow, inpamP->width,
+                              inpamP->format);
+
+        if (reverse)
+            bitOrderReverse(bitrow, inpamP->width);
+
+        pbm_writepbmrow_packed(outpamP->file, bitrow, outpamP->width, 0);
+    }
+    pbm_freerow_packed(bitrow);
+}
+
+
+
+static void
+transformRowByRowNonPbm(struct pam * const inpamP, 
+                        struct pam * const outpamP,
+                        bool         const reverse) {
+/*----------------------------------------------------------------------------
+  Flip an image left for right or leave it alone.
+
+  Process one row at a time.
+
+  This works on any image, but is slower and uses more memory than the
+  PBM-only transformRowByRowPbm().
+-----------------------------------------------------------------------------*/
+    tuple * tuplerow;
+    tuple * newtuplerow;
+        /* This is not a full tuple row.  It is either an array of pointers
+           to the tuples in 'tuplerow' (in reverse order) or just 'tuplerow'
+           itself.
+        */
+    tuple * scratchTuplerow;
+    
+    unsigned int row;
+    
+    tuplerow = pnm_allocpamrow(inpamP);
+    
+    if (reverse) {
+        /* Set up newtuplerow[] to point to the tuples of tuplerow[] in
+           reverse order.
+        */
+        unsigned int col;
+        
+        MALLOCARRAY_NOFAIL(scratchTuplerow, inpamP->width);
+
+        for (col = 0; col < inpamP->width; ++col) 
+            scratchTuplerow[col] = tuplerow[inpamP->width - col - 1];
+        newtuplerow = scratchTuplerow;
+    } else {
+        scratchTuplerow = NULL;
+        newtuplerow = tuplerow;
+    }
+    pnm_writepaminit(outpamP);
+
+    for (row = 0; row < inpamP->height ; ++row) {
+        pnm_readpamrow(inpamP, tuplerow);
+        pnm_writepamrow(outpamP, newtuplerow);
+    }
+    
+    if (scratchTuplerow)
+        free(scratchTuplerow);
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+static void
+transformRowByRow(struct pam * const inpamP,
+                  struct pam * const outpamP,
+                  bool         const reverse,
+                  bool         const verbose) {
+
+    if (verbose)
+        pm_message("Transforming row by row, top to bottom");
+
+    switch (PNM_FORMAT_TYPE(inpamP->format)) {
+    case PBM_TYPE:
+        transformRowByRowPbm(inpamP, outpamP, reverse);
+        break;
+    default:
+        transformRowByRowNonPbm(inpamP, outpamP, reverse);
+        break;
+    }
+} 
+
+
+
+static void
+transformRowsBottomTopPbm(struct pam * const inpamP,
+                          struct pam * const outpamP,
+                          bool         const reverse) { 
+/*----------------------------------------------------------------------------
+  Flip a PBM image top for bottom.  Iff 'reverse', also flip it left for right.
+
+  Read complete image into memory in packed PBM format; Use fast
+  packed PBM bit reverse algorithm (where required).
+-----------------------------------------------------------------------------*/
+    unsigned int const rows=inpamP->height;
+
+    unsigned char ** bitrow;
+    int row;
+        
+    bitrow = pbm_allocarray_packed(outpamP->width, outpamP->height);
+        
+    for (row = 0; row < rows; ++row)
+        pbm_readpbmrow_packed(inpamP->file, bitrow[row], 
+                              inpamP->width, inpamP->format);
+
+    pbm_writepbminit(outpamP->file, outpamP->width, outpamP->height, 0);
+
+    for (row = 0; row < rows; ++row) {
+        if (reverse) 
+            bitOrderReverse(bitrow[rows-row-1], inpamP->width);
+
+        pbm_writepbmrow_packed(outpamP->file, bitrow[rows - row - 1],
+                               outpamP->width, 0);
+    }
+    pbm_freearray_packed(bitrow, outpamP->height);
+}
+
+
+
+static void
+transformRowsBottomTopNonPbm(struct pam * const inpamP, 
+                             struct pam * const outpamP,
+                             bool         const reverse) {
+/*----------------------------------------------------------------------------
+  Read complete image into memory as a tuple array.
+
+  This can do any transformation except a column-for-row transformation,
+  on any type of image, but is slower and uses more memory than the 
+  PBM-only transformRowsBottomTopPbm().
+-----------------------------------------------------------------------------*/
+    tuple** tuplerows;
+    tuple * scratchTuplerow;
+        /* This is not a full tuple row -- just an array of pointers to
+           the tuples in 'tuplerows'.
+        */
+    unsigned int row;
+
+    if (reverse)
+        MALLOCARRAY_NOFAIL(scratchTuplerow, inpamP->width);
+    else
+        scratchTuplerow = NULL;
+  
+    tuplerows = pnm_allocpamarray(outpamP);
+
+    for (row = 0; row < inpamP->height ; ++row)
+        pnm_readpamrow(inpamP, tuplerows[row]);
+
+    pnm_writepaminit(outpamP);
+
+    for (row = 0; row < inpamP->height ; ++row) {
+        tuple * newtuplerow;
+        tuple * const tuplerow = tuplerows[inpamP->height - row - 1];
+        if (reverse) {
+            unsigned int col;
+            newtuplerow = scratchTuplerow;
+            for (col = 0; col < inpamP->width; ++col) 
+                newtuplerow[col] = tuplerow[inpamP->width - col - 1];
+        } else
+            newtuplerow = tuplerow;
+        pnm_writepamrow(outpamP, newtuplerow);
+    }
+
+    if (scratchTuplerow)
+        free(scratchTuplerow);
+    
+    pnm_freepamarray(tuplerows, outpamP);
+}
+
+
+
+static void
+transformRowsBottomTop(struct pam * const inpamP,
+                       struct pam * const outpamP,
+                       bool         const reverse,
+                       bool         const verbose) {
+
+    if (PNM_FORMAT_TYPE(inpamP->format) == PBM_TYPE) {
+        if (verbose)
+            pm_message("Transforming PBM row by row, bottom to top");
+        transformRowsBottomTopPbm(inpamP, outpamP, reverse);
+    } else {
+        if (verbose)
+            pm_message("Transforming non-PBM row by row, bottom to top");
+        transformRowsBottomTopNonPbm(inpamP, outpamP, reverse);
+    }
+}
+
+
+
+static void __inline__
+transformPoint(int                const col, 
+               int                const newcols,
+               int                const row, 
+               int                const newrows, 
+               struct xformMatrix const xform, 
+               int *              const newcolP, 
+               int *              const newrowP ) {
+/*----------------------------------------------------------------------------
+   Compute the location in the output of a pixel that is at row 'row',
+   column 'col' in the input.  Assume the output image is 'newcols' by
+   'newrows' and the transformation is as described by 'xform'.
+
+   Return the output image location of the pixel as *newcolP and *newrowP.
+-----------------------------------------------------------------------------*/
+    /* The transformation is:
+     
+                 [ a b 0 ]
+       [ x y 1 ] [ c d 0 ] = [ x2 y2 1 ]
+                 [ e f 1 ]
+    */
+    *newcolP = xform.a * col + xform.c * row + xform.e * (newcols - 1);
+    *newrowP = xform.b * col + xform.d * row + xform.f * (newrows - 1);
+}
+
+
+
+static void
+transformPbm(struct pam *       const inpamP,
+             struct pam *       const outpamP,
+             struct xformMatrix const xform) { 
+/*----------------------------------------------------------------------------
+   This is the same as transformGen, except that it uses less 
+   memory, since the PBM buffer format uses one bit per pixel instead
+   of twelve bytes + pointer space
+
+   This can do any PBM transformation, but is slower and uses more
+   memory than the more restricted transformRowByRowPbm() and
+   transformRowsBottomTopPbm().
+-----------------------------------------------------------------------------*/
+    bit* bitrow;
+    bit** newbits;
+    int row;
+            
+    bitrow = pbm_allocrow(inpamP->width);
+    newbits = pbm_allocarray(pbm_packed_bytes(outpamP->width), 
+                             outpamP->height);
+            
+    /* Initialize entire array to zeroes.  One bits will be or'ed in later */
+    for (row = 0; row < outpamP->height; ++row) {
+        int col;
+        for (col = 0; col < pbm_packed_bytes(outpamP->width); ++col) 
+             newbits[row][col] = 0; 
+    }
+    
+    for (row = 0; row < inpamP->height; ++row) {
+        int col;
+        pbm_readpbmrow(inpamP->file, bitrow, inpamP->width, inpamP->format);
+        for (col = 0; col < inpamP->width; ++col) {
+            int newcol, newrow;
+            transformPoint(col, outpamP->width, row, outpamP->height, xform,
+                           &newcol, &newrow);
+            newbits[newrow][newcol/8] |= bitrow[col] << (7 - newcol % 8);
+                /* Use of "|=" patterned after pbm_readpbmrow_packed. */
+         }
+    }
+    
+    pbm_writepbminit(outpamP->file, outpamP->width, outpamP->height, 0);
+    for (row = 0; row < outpamP->height; ++row)
+        pbm_writepbmrow_packed(outpamP->file, newbits[row], outpamP->width, 
+                               0);
+    
+    pbm_freearray(newbits, outpamP->height);
+    pbm_freerow(bitrow);
+}
+
+
+
+static unsigned int 
+optimalSegmentSize(struct xformMatrix  const xform, 
+                   struct pam *        const pamP,
+                   unsigned int        const availableMemory,
+                   unsigned int        const pageSize) {
+/*----------------------------------------------------------------------------
+   Compute the maximum number of columns that can be transformed, one row
+   at a time, without causing page thrashing.
+
+   See comments at the top of this file for an explanation of the kind
+   of page thrashing using segments avoids.
+
+   'availableMemory' is the amount of real memory in bytes that this
+   process should expect to be able to use.
+
+   'pageSize' is the size of a page in bytes.  A page means the unit that
+   is paged in or out.
+   
+   'pamP' describes the storage required to represent a row of the
+   output array.
+-----------------------------------------------------------------------------*/
+    unsigned int segmentSize;
+
+    if (xform.b == 0)
+        segmentSize = pamP->width;
+    else {
+        unsigned int const otherNeeds = 200*1024;
+            /* A wild guess at how much real memory is needed by the program
+               for purposes other than the output tuple array.
+            */
+        if (otherNeeds > availableMemory)
+            segmentSize = pamP->width;  /* Can't prevent thrashing */
+        else {
+            unsigned int const availablePages = 
+                (availableMemory - otherNeeds) / pageSize;
+            if (availablePages <= 1)
+                segmentSize = pamP->width; /* Can't prevent thrashing */
+            else {
+                unsigned int const bytesPerRow = 
+                    pamP->width * pamP->depth * pamP->bytes_per_sample;
+                unsigned int rowsPerPage = 
+                    MAX(1, (pageSize + (pageSize/2)) / bytesPerRow);
+                    /* This is how many consecutive rows we can touch
+                       on average while staying within the same page.  
+                    */
+                segmentSize = availablePages * rowsPerPage;
+            }
+        }
+    }    
+    return segmentSize;
+}
+
+
+
+static void
+transformNonPbm(struct pam *       const inpamP,
+                struct pam *       const outpamP,
+                struct xformMatrix const xform,
+                unsigned int       const segmentSize,
+                bool               const verbose) {
+/*----------------------------------------------------------------------------
+  Do the transform using "pam" library functions, as opposed to "pbm"
+  ones.
+
+  Assume input file is positioned to the raster (just after the
+  header).
+
+  'segmentSize' is the number of columns we are to process in each
+  pass.  We do each segment going from left to right.  For each
+  segment, we do each row, going from top to bottom.  For each row of
+  the segment, we do each column, going from left to right.  (The
+  reason Caller wants it done by segments is to improve virtual memory
+  reference locality.  See comments at the top of this file).
+
+  if 'segmentSize' is less than the whole image, ifP must be a seekable
+  file.
+
+  This can do any transformation, but is slower and uses more memory
+  than the PBM-only transformPbm().
+-----------------------------------------------------------------------------*/
+    pm_filepos imagepos;
+        /* The input file position of the raster.  But defined only if
+           segment size is less than whole image.
+        */
+    tuple* tuplerow;
+    tuple** newtuples;
+    unsigned int startCol;
+
+    tuplerow = pnm_allocpamrow(inpamP);
+    newtuples = pnm_allocpamarray(outpamP);
+    
+    if (segmentSize < inpamP->width)
+        pm_tell2(inpamP->file, &imagepos, sizeof(imagepos));
+
+    for (startCol = 0; startCol < inpamP->width; startCol += segmentSize) {
+        /* Do one set of columns which is small enough not to cause
+           page thrashing.
+        */
+        unsigned int const endCol = MIN(inpamP->width, startCol + segmentSize);
+        unsigned int row;
+
+        if (verbose)
+            pm_message("Transforming Columns %u up to %u", 
+                       startCol, endCol);
+
+        if (startCol > 0)
+            /* Go back to read from Row 0 again */
+            pm_seek2(inpamP->file, &imagepos, sizeof(imagepos));
+
+        for (row = 0; row < inpamP->height; ++row) {
+            unsigned int col;
+            pnm_readpamrow(inpamP, tuplerow);
+
+            for (col = startCol; col < endCol; ++col) {
+                int newcol, newrow;
+                transformPoint(col, outpamP->width, row, outpamP->height, 
+                               xform,
+                               &newcol, &newrow);
+                pnm_assigntuple(inpamP, newtuples[newrow][newcol],
+                                tuplerow[col]);
+            }
+        }
+    }
+    
+    pnm_writepam(outpamP, newtuples);
+    
+    pnm_freepamarray(newtuples, outpamP);
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+static void
+transformGen(struct pam *       const inpamP,
+             struct pam *       const outpamP,
+             struct xformMatrix const xform,
+             unsigned int       const availableMemory,
+             unsigned int       const pageSize,
+             bool               const verbose) {
+/*----------------------------------------------------------------------------
+  Produce the transformed output on Standard Output.
+
+  Assume input file is positioned to the raster (just after the
+  header).
+
+  This can transform any image in any way, but is slower and uses more
+  memory than the more restricted transformRowByRow() and
+  transformRowsBottomTop().
+-----------------------------------------------------------------------------*/
+    unsigned int const segmentSize = 
+        optimalSegmentSize(xform, outpamP, availableMemory, pageSize);
+    
+    switch (PNM_FORMAT_TYPE(inpamP->format)) {
+    case PBM_TYPE: 
+        transformPbm(inpamP, outpamP, xform);
+        break;
+    default:
+        if (segmentSize < outpamP->width) {
+            if (verbose && xform.b !=0)
+                pm_message("Transforming %u columns of %u total at a time", 
+                           segmentSize, outpamP->width);
+            else
+                pm_message("Transforming entire image at once");
+        }
+        transformNonPbm(inpamP, outpamP, xform, segmentSize, verbose);
+        break;
+    }
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+    struct cmdlineInfo cmdline;
+    struct pam inpam;
+    struct pam outpam;
+    FILE* ifP;
+    struct xformMatrix xform;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    if (cmdline.availableMemory < UINT_MAX)
+        ifP = pm_openr_seekable(cmdline.inputFilespec);
+    else
+        ifP = pm_openr(cmdline.inputFilespec);
+    
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    computeXformMatrix(&xform, cmdline.xformCount, cmdline.xformList);
+
+    outpam = inpam;  /* initial value */
+
+    outpam.file = stdout;
+    outpam.width  = abs(xform.a) * inpam.width + abs(xform.c) * inpam.height;
+    outpam.height = abs(xform.b) * inpam.width + abs(xform.d) * inpam.height;
+    
+    if (xform.b == 0 && xform.d == 1 && xform.f == 0)
+        /* In this case Row N of the output is based only on Row N of
+           the input, so we can transform row by row and avoid
+           in-memory buffering altogether.  
+        */
+        transformRowByRow(&inpam, &outpam, xform.a == -1, cmdline.verbose);
+    else if (xform.b == 0 && xform.c == 0) 
+        /* In this case, Row N of the output is based only on Row ~N of the
+           input.  We need all the rows in memory, but have to pass
+           through them only twice, so there is no page thrashing concern.
+        */
+        transformRowsBottomTop(&inpam, &outpam, xform.a == -1, 
+                               cmdline.verbose);
+    else
+        /* This is a colum-for-row type of transformation, which requires
+           complex traversal of an in-memory image.
+        */
+        transformGen(&inpam, &outpam, xform,
+                     cmdline.availableMemory, cmdline.pageSize, 
+                     cmdline.verbose);
+
+    pm_close(inpam.file);
+    pm_close(outpam.file);
+    
+    return 0;
+}
diff --git a/editor/pamflip.test b/editor/pamflip.test
new file mode 100644
index 00000000..96e889ea
--- /dev/null
+++ b/editor/pamflip.test
@@ -0,0 +1,12 @@
+echo Test 1.  Should print 2116496681 101484
+./pamflip -lr ../testimg.ppm | cksum 
+echo Test 2.  Should print 217037000 101484
+./pamflip -cw ../testimg.ppm | cksum
+echo Test 3.  Should print 2052917888 101484
+./pamflip -tb ../testimg.ppm | cksum
+echo Test 4.  Should print 3375384165 41
+./pamflip -lr ../testgrid.pbm | cksum
+echo Test 5.  Should print 604323149 41
+./pamflip -tb ../testgrid.pbm | cksum
+echo Test 6.  Should print 490797850 37
+./pamflip -cw ../testgrid.pbm | cksum
diff --git a/editor/pamfunc.c b/editor/pamfunc.c
new file mode 100644
index 00000000..dbb1ca70
--- /dev/null
+++ b/editor/pamfunc.c
@@ -0,0 +1,221 @@
+/******************************************************************************
+                               pamfunc
+*******************************************************************************
+  Apply one of various functions to each sample in a PAM image
+
+  By Bryan Henderson, San Jose CA 2002.06.16.
+
+  Contributed to the public domain
+
+  ENHANCEMENT IDEAS:
+
+  1) speed up by doing integer arithmetic instead of floating point for
+  multiply/divide where possible.  Especially when multiplying by an 
+  integer.
+
+  2) For multiply/divide, give option of simply changing the maxval and
+  leaving the raster alone.
+
+******************************************************************************/
+
+#include "pam.h"
+#include "shhopt.h"
+
+enum function {FN_MULTIPLY, FN_DIVIDE, FN_ADD, FN_SUBTRACT, FN_MIN, FN_MAX};
+
+/* Note that when the user specifies a minimum, that means he's requesting
+   a "max" function.
+*/
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+    enum function function;
+    union {
+        float multiplier;
+        float divisor;
+        int adder;
+        int subtractor;
+        unsigned int max;
+        unsigned int min;
+    } u;
+    unsigned int verbose;
+};
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int multiplierSpec, divisorSpec, adderSpec, subtractorSpec;
+    unsigned int maxSpec, minSpec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "multiplier", OPT_FLOAT,  &cmdlineP->u.multiplier, 
+            &multiplierSpec, 0);
+    OPTENT3(0,   "divisor",    OPT_FLOAT,  &cmdlineP->u.divisor,
+            &divisorSpec, 0);
+    OPTENT3(0,   "adder",      OPT_INT,    &cmdlineP->u.adder,
+            &adderSpec, 0);
+    OPTENT3(0,   "subtractor", OPT_INT,    &cmdlineP->u.subtractor,
+            &subtractorSpec, 0);
+    OPTENT3(0,   "min",        OPT_UINT,   &cmdlineP->u.min,
+            &minSpec, 0);
+    OPTENT3(0,   "max",        OPT_UINT,   &cmdlineP->u.max,
+            &maxSpec, 0);
+    OPTENT3(0,   "verbose",    OPT_FLAG,   NULL, &cmdlineP->verbose,       0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (multiplierSpec + divisorSpec + adderSpec + subtractorSpec +
+        minSpec + maxSpec > 1)
+        pm_error("You may specify at most one of -multiplier, -divisor,"
+                 "-adder, -subtractor, -min, and -max");
+
+    if (multiplierSpec) {
+        cmdlineP->function = FN_MULTIPLY;
+        if (cmdlineP->u.multiplier < 0)
+            pm_error("Multiplier must be nonnegative.  You specified %f", 
+                     cmdlineP->u.multiplier);
+    } else if (divisorSpec) {
+        cmdlineP->function = FN_DIVIDE;
+        if (cmdlineP->u.divisor < 0)
+            pm_error("Divisor must be nonnegative.  You specified %f", 
+                     cmdlineP->u.divisor);
+    } else if (adderSpec) {
+        cmdlineP->function = FN_ADD;
+    } else if (subtractorSpec) {
+        cmdlineP->function = FN_SUBTRACT;
+    } else if (minSpec) {
+        cmdlineP->function = FN_MAX;
+    } else if (maxSpec) {
+        cmdlineP->function = FN_MIN;
+    } else 
+        pm_error("You must specify one of -multiplier, -divisor, "
+                 "-adder, -subtractor, -min, or -max");
+        
+    if (argc-1 > 1)
+        pm_error("Too many arguments (%d).  File spec is the only argument.",
+                 argc-1);
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else 
+        cmdlineP->inputFilespec = argv[1];
+    
+}
+
+
+
+static void
+applyFunction(struct cmdlineInfo const cmdline,
+              struct pam         const inpam,
+              struct pam         const outpam,
+              tuple *            const inputRow,
+              tuple *            const outputRow) {
+
+    float const oneOverDivisor = 1/cmdline.u.divisor;
+        /* In my experiments, the compiler couldn't figure out that
+           1/cmdline.u.divisor is a constant and instead recomputed it
+           for each and every pixel.  division is slower than
+           multiplication, so we want to multiply by
+           1/cmdline.u.divisor instead of divide by cmdline.u.divisor,
+           so we compute that here.  Note that if the function isn't
+           divide, both cmdline.u.divisor and oneOverDivisor are
+           meaningless.  
+        */
+    int col;
+
+    for (col = 0; col < inpam.width; ++col) {
+        int plane;
+        for (plane = 0; plane < inpam.depth; ++plane) {
+            sample const inSample = inputRow[col][plane];
+            sample outSample;  /* Could be > maxval  */
+
+            switch (cmdline.function) {
+            case FN_MULTIPLY:
+                outSample = ROUNDU(inSample * cmdline.u.multiplier);
+                break;
+            case FN_DIVIDE:
+                outSample = ROUNDU(inSample * oneOverDivisor);
+                break;
+            case FN_ADD:
+                outSample = MAX(0, (long)inSample + cmdline.u.adder);
+                break;
+            case FN_SUBTRACT:
+                outSample = MAX(0, (long)inSample - cmdline.u.subtractor);
+                break;
+            case FN_MAX:
+                outSample = MAX(inSample, cmdline.u.min);
+                break;
+            case FN_MIN:
+                outSample = MIN(inSample, cmdline.u.max);
+                break;
+            }
+            outputRow[col][plane] = MIN(outpam.maxval, outSample);
+        }
+    }
+}                
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE* ifP;
+    tuple* inputRow;   /* Row from input image */
+    tuple* outputRow;  /* Row of output image */
+    int row;
+    struct cmdlineInfo cmdline;
+    struct pam inpam;   /* Input PAM image */
+    struct pam outpam;  /* Output PAM image */
+
+    pnm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    inputRow = pnm_allocpamrow(&inpam);
+
+    outpam = inpam;    /* Initial value -- most fields should be same */
+    outpam.file = stdout;
+
+    pnm_writepaminit(&outpam);
+
+    outputRow = pnm_allocpamrow(&outpam);
+
+    for (row = 0; row < inpam.height; row++) {
+        pnm_readpamrow(&inpam, inputRow);
+
+        applyFunction(cmdline, inpam, outpam, inputRow, outputRow);
+
+        pnm_writepamrow(&outpam, outputRow);
+    }
+    pnm_freepamrow(outputRow);
+    pnm_freepamrow(inputRow);
+    pm_close(inpam.file);
+    pm_close(outpam.file);
+    
+    exit(0);
+}
+
diff --git a/editor/pammasksharpen.c b/editor/pammasksharpen.c
new file mode 100644
index 00000000..87b928be
--- /dev/null
+++ b/editor/pammasksharpen.c
@@ -0,0 +1,192 @@
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilespec;  
+    const char * maskFilespec;  
+    unsigned int verbose;
+    float        sharpness;
+    float        threshold;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int sharpSpec, thresholdSpec;
+    
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "sharpness",       OPT_FLOAT,  &cmdlineP->sharpness,
+            &sharpSpec,           0);
+    OPTENT3(0, "threshold",       OPT_FLOAT,  &cmdlineP->threshold,
+            &thresholdSpec,       0);
+    OPTENT3(0, "verbose",         OPT_FLAG,   NULL,
+            &cmdlineP->verbose,   0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (sharpSpec) {
+        if (cmdlineP->sharpness < 0)
+            pm_error("-sharpness less than zero doesn't make sense.  "
+                     "You specified %f", cmdlineP->sharpness);
+    } else
+        cmdlineP->sharpness = 1.0;
+
+    if (thresholdSpec) {
+        if (cmdlineP->threshold < 0)
+            pm_error("-threshold less than zero doesn't make sense.  "
+                     "You specified %f", cmdlineP->threshold);
+        if (cmdlineP->threshold > 1.0)
+            pm_error("-threshold greater than unity doesn't make sense.  "
+                     "You specified %f", cmdlineP->threshold);
+        
+    } else
+        cmdlineP->threshold = 0.0;
+
+    if (argc-1 < 1)
+        pm_error("You must specify at least one argument:  The name "
+                 "of the mask image file");
+    else {
+        cmdlineP->maskFilespec = argv[1];
+        if (argc-1 < 2)
+            cmdlineP->inputFilespec = "-";
+        else {
+            cmdlineP->inputFilespec = argv[2];
+        
+            if (argc-1 > 2)
+                pm_error("There are at most two arguments:  mask file name "
+                         "and input file name.  You specified %d", argc-1);
+        }
+    }
+}        
+
+
+
+static sample
+sharpened(sample const inputSample,
+          sample const maskSample,
+          float  const sharpness,
+          sample const threshold,
+          sample const maxval) {
+
+    int const edgeness = inputSample - maskSample;
+
+    sample retval;
+
+    if (abs(edgeness) > threshold) {
+        float const rawResult = inputSample + edgeness * sharpness;
+        
+        retval = MIN(maxval, (unsigned)MAX(0, (int)(rawResult+0.5)));
+    } else
+        retval = inputSample;
+
+    return retval;
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    struct pam inpam;
+    struct pam maskpam;
+    struct pam outpam;
+    FILE * ifP;
+    FILE * maskfP;
+    tuple * inputTuplerow;
+    tuple * maskTuplerow;
+    tuple * outputTuplerow;
+    unsigned int row;
+    sample threshold;
+        /* Magnitude of difference between image and unsharp mask below
+           which they will be considered identical.
+        */
+    
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+    maskfP = pm_openr(cmdline.maskFilespec);
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+    pnm_readpaminit(maskfP, &maskpam, PAM_STRUCT_SIZE(tuple_type));
+
+    if (inpam.width  != maskpam.width || 
+        inpam.height != maskpam.height ||
+        inpam.depth  != maskpam.depth)
+        pm_error("The mask image must be the same dimensions as the "
+                 "input image.  The mask is %dx%dx%d, but the input is "
+                 "%dx%dx%d.",
+                 maskpam.width, maskpam.height, maskpam.depth,
+                 inpam.width,   inpam.height,   inpam.depth);
+    if (inpam.maxval != maskpam.maxval)
+        pm_error("The mask image must have the same maxval as the "
+                 "input image.  The input image has maxval %u, "
+                 "but the mask image has maxval %u",
+                 (unsigned)inpam.maxval, (unsigned)maskpam.maxval);
+
+    threshold = (float)cmdline.threshold / inpam.maxval;
+
+    outpam = inpam;
+    outpam.file = stdout;
+
+    inputTuplerow  = pnm_allocpamrow(&inpam);
+    maskTuplerow   = pnm_allocpamrow(&maskpam);
+    outputTuplerow = pnm_allocpamrow(&outpam);
+
+    pnm_writepaminit(&outpam);
+
+    for (row = 0; row < outpam.height; ++row) {
+        unsigned int col;
+        pnm_readpamrow(&inpam,   inputTuplerow);
+        pnm_readpamrow(&maskpam, maskTuplerow);
+        
+        for (col = 0; col < outpam.width; ++col) {
+            unsigned int plane;
+            
+            for (plane = 0; plane < outpam.depth; ++plane) {
+                outputTuplerow[col][plane] =
+                    sharpened(inputTuplerow[col][plane],
+                              maskTuplerow[col][plane],
+                              cmdline.sharpness,
+                              threshold,
+                              outpam.maxval);
+            }
+        }
+        pnm_writepamrow(&outpam, outputTuplerow);
+    }
+
+    pm_close(ifP);
+    pm_close(maskfP);
+
+    pnm_freepamrow(inputTuplerow);
+    pnm_freepamrow(maskTuplerow);
+    pnm_freepamrow(outputTuplerow);
+    
+    return 0;
+}
diff --git a/editor/pammixinterlace.c b/editor/pammixinterlace.c
new file mode 100644
index 00000000..1421c7a2
--- /dev/null
+++ b/editor/pammixinterlace.c
@@ -0,0 +1,173 @@
+/******************************************************************************
+                             pammixinterlace
+*******************************************************************************
+  De-interlace an image by merging adjacent rows.
+   
+  Copyright (C) 2005 Bruce Guenter, FutureQuest, Inc.
+
+  Permission to use, copy, modify, and distribute this software and its
+  documentation for any purpose and without fee is hereby granted,
+  provided that the above copyright notice appear in all copies and that
+  both that copyright notice and this permission notice appear in
+  supporting documentation.  This software is provided "as is" without
+  express or implied warranty.
+
+******************************************************************************/
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespecs of input files */
+};
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry *option_def;
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        pm_error("You specified too many arguments (%d).  The only "
+                 "argument is the optional input file specification.",
+                 argc-1);
+}
+
+
+
+static void
+allocateRowWindowBuffer(struct pam * const pamP,
+                        tuple **     const tuplerow) {
+
+    unsigned int row;
+
+    for (row = 0; row < 3; ++row)
+        tuplerow[row] = pnm_allocpamrow(pamP);
+}
+
+
+
+static void
+freeRowWindowBuffer(tuple ** const tuplerow) {
+
+    unsigned int row;
+
+    for (row = 0; row < 3; ++row)
+        pnm_freepamrow(tuplerow[row]);
+
+}
+
+
+
+static void
+slideWindowDown(tuple ** const tuplerow) {
+/*----------------------------------------------------------------------------
+  Slide the 3-line tuple row window tuplerow[] down one row by moving
+  pointers.
+
+  tuplerow[2] ends up an uninitialized buffer.
+-----------------------------------------------------------------------------*/
+    tuple * const oldrow0 = tuplerow[0];
+    tuplerow[0] = tuplerow[1];
+    tuplerow[1] = tuplerow[2];
+    tuplerow[2] = oldrow0;
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE * ifP;
+    struct cmdlineInfo cmdline;
+    struct pam inpam;  
+    struct pam outpam;
+    tuple * tuplerow[3];
+    tuple * outputrow;
+    
+    pnm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+    
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    outpam = inpam;    /* Initial value -- most fields should be same */
+    outpam.file = stdout;
+
+    pnm_writepaminit(&outpam);
+
+    allocateRowWindowBuffer(&inpam, tuplerow);
+    outputrow = pnm_allocpamrow(&outpam);
+
+    if (inpam.height < 3) {
+        unsigned int row;
+        pm_message("WARNING: Image height less than 3.  No mixing done.");
+        for (row = 0; row < inpam.height; ++row) {
+            pnm_readpamrow(&inpam, tuplerow[0]);
+            pnm_writepamrow(&outpam, tuplerow[0]);
+        }
+    } else {
+        unsigned int row;
+
+        pnm_readpamrow(&inpam, tuplerow[0]);
+        pnm_readpamrow(&inpam, tuplerow[1]);
+
+        /* Pass through first row */
+        pnm_writepamrow(&outpam, tuplerow[0]);
+
+        for (row = 2; row < inpam.height; ++row) {
+            unsigned int col;
+            pnm_readpamrow(&inpam, tuplerow[2]);
+            for (col = 0; col < inpam.width; ++col) {
+                unsigned int plane;
+
+                for (plane = 0; plane < inpam.depth; ++plane) {
+                    outputrow[col][plane] =
+                        (tuplerow[0][col][plane]
+                         + tuplerow[1][col][plane] * 2
+                         + tuplerow[2][col][plane]) / 4;
+                }
+            }
+            pnm_writepamrow(&outpam, outputrow);
+            
+            slideWindowDown(tuplerow);
+        }
+
+        /* Pass through last row */
+        pnm_writepamrow(&outpam, tuplerow[1]);
+    }
+
+    freeRowWindowBuffer(tuplerow);
+    pnm_freepamrow(outputrow);
+    pm_close(inpam.file);
+    pm_close(outpam.file);
+    
+    return 0;
+}
diff --git a/editor/pamoil.c b/editor/pamoil.c
new file mode 100644
index 00000000..6cb8d3ac
--- /dev/null
+++ b/editor/pamoil.c
@@ -0,0 +1,137 @@
+/* pgmoil.c - read a portable pixmap and turn into an oil painting
+**
+** Copyright (C) 1990 by Wilson Bent (whb@hoh-2.att.com)
+** Shamelessly butchered into a color version by Chris Sheppard
+** 2001
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "pam.h"
+#include "mallocvar.h"
+
+static void 
+convertRow(struct pam const inpam, tuple ** const tuples,
+           tuple * const tuplerow, int const row, int const smearFactor,
+           int * const hist) {
+
+    int sample;
+    for (sample = 0; sample < inpam.depth; sample++) {
+        int col;
+        for (col = 0; col < inpam.width; ++col)  {
+            int i;
+            int drow;
+            int modalval;
+                /* The sample value that occurs most often in the neighborhood
+                   of the pixel being examined
+                */
+
+            /* Compute hist[] - frequencies, in the neighborhood, of each 
+               sample value
+            */
+            for (i = 0; i <= inpam.maxval; ++i) hist[i] = 0;
+
+            for (drow = row - smearFactor; drow <= row + smearFactor; ++drow) {
+                if (drow >= 0 && drow < inpam.height) {
+                    int dcol;
+                    for (dcol = col - smearFactor; 
+                         dcol <= col + smearFactor; 
+                         ++dcol) {
+                        if ( dcol >= 0 && dcol < inpam.width )
+                            ++hist[tuples[drow][dcol][sample]];
+                    }
+                }
+            }
+            {
+                /* Compute modalval */
+                int sampleval;
+                int maxfreq;
+
+                maxfreq = 0;
+                modalval = 0;
+
+                for (sampleval = 0; sampleval <= inpam.maxval; ++sampleval) {
+                    if (hist[sampleval] > maxfreq) {
+                        maxfreq = hist[sampleval];
+                        modalval = sampleval;
+                    }
+                }
+            }
+            tuplerow[col][sample] = modalval;
+        }
+    }
+}
+
+
+
+int
+main(int argc, char *argv[] ) {
+    struct pam inpam, outpam;
+    FILE* ifp;
+    tuple ** tuples;
+    tuple * tuplerow;
+    int * hist;
+        /* A buffer for the convertRow subroutine to use */
+    int argn;
+    int row;
+    int smearFactor;
+    const char* const usage = "[-n <n>] [ppmfile]";
+
+    ppm_init( &argc, argv );
+
+    argn = 1;
+    smearFactor = 3;       /* DEFAULT VALUE */
+
+    /* Check for options. */
+    if ( argn < argc && argv[argn][0] == '-' ) {
+        if ( argv[argn][1] == 'n' ) {
+            ++argn;
+            if ( argn == argc || sscanf(argv[argn], "%d", &smearFactor) != 1 )
+                pm_usage( usage );
+        } else
+            pm_usage( usage );
+        ++argn;
+    }
+    if ( argn < argc ) {
+        ifp = pm_openr( argv[argn] );
+        ++argn;
+    } else
+        ifp = stdin;
+
+    if ( argn != argc )
+        pm_usage( usage );
+
+    tuples = pnm_readpam(ifp, &inpam, PAM_STRUCT_SIZE(tuple_type));
+    pm_close(ifp);
+
+    MALLOCARRAY(hist, inpam.maxval + 1);
+    if (hist == NULL)
+        pm_error("Unable to allocate memory for histogram.");
+
+    outpam = inpam; outpam.file = stdout;
+
+    pnm_writepaminit(&outpam);
+
+    tuplerow = pnm_allocpamrow(&inpam);
+
+    for (row = 0; row < inpam.height; ++row) {
+        convertRow(inpam, tuples, tuplerow, row, smearFactor, hist);
+        pnm_writepamrow(&outpam, tuplerow);
+    }
+
+    pnm_freepamrow(tuplerow);
+    free(hist);
+    pnm_freepamarray(tuples, &inpam);
+
+    pm_close(stdout);
+    exit(0);
+}
+
diff --git a/editor/pamperspective.c b/editor/pamperspective.c
new file mode 100644
index 00000000..fdf446c7
--- /dev/null
+++ b/editor/pamperspective.c
@@ -0,0 +1,1331 @@
+/*
+    pamperspective -- a reverse scanline renderer
+
+    Copyright (C) 2004 by Mark Weyer
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#define _BSD_SOURCE   /* Make sure strdup is int string.h */
+
+#include <math.h>
+#include <string.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+typedef double number;
+
+/* There was no reason for exactly this value of eps.
+   For compatibility it should only be decreased in future versions.
+*/
+
+#define eps 0.0001
+
+
+/* Multiple choice types for the command line */
+
+typedef enum {image, pixel_u} unit;
+const char *const unit_token[3] = {"image", "pixel", NULL};
+
+typedef enum {lattice, pixel_s} coord_system;
+const char *const system_token[3] = {"lattice", "pixel", NULL};
+
+typedef enum {nearest, linear} interpolation;
+const char *const interpolation_token[3] = {"nearest", "linear", NULL};
+
+typedef enum {free_, fixed} proportion;
+const char *const proportion_token[3] = {"free", "fixed", NULL};
+
+const char *const bool_token[7] = {"yes", "true", "on",
+                                    "no", "false", "off", NULL};
+#define first_false_bool_token 3
+
+/* All command line options that have float (actually number) values.
+   We use our own parsing technique for these, to handle width/height
+   ratios like 4/3
+*/
+
+#define num_float_options 15
+const char *const float_option_name[num_float_options][3] = {
+  {"upper left x", "upper_left_x", "ulx"},
+  {"upper left y", "upper_left_y", "uly"},
+  {"upper right x", "upper_right_x", "urx"},
+  {"upper right y", "upper_right_y", "ury"},
+  {"lower left x", "lower_left_x", "llx"},
+  {"lower left y", "lower_left_y", "lly"},
+  {"lower right x", "lower_right_x", "lrx"},
+  {"lower right y", "lower_right_y", "lry"},
+  {NULL, "detail", NULL},
+  {NULL, "ratio", NULL},
+  {NULL, "margin", NULL},
+  {NULL, "top_margin", "tmargin"},
+  {NULL, "bottom_margin", "bmargin"},
+  {NULL, "left_margin", "lmargin"},
+  {NULL, "right_margin", "rmargin"}
+};
+
+/* All command line options that have multiple choice values (except bools). */
+
+#define num_enum_options 5
+const char *const enum_option_name[num_enum_options] = {
+  "input_system", "output_system", "input_unit", "interpolation", "proportion"
+};
+const char *const *const enum_option_type[num_enum_options] = {
+  system_token, system_token, unit_token, interpolation_token, proportion_token
+};
+
+/* All command line options that have bool values */
+
+#define num_bool_options 1
+const char *const bool_option_name[num_bool_options] = {
+  "frame_include"
+};
+
+
+/* A linked list node for --include points */
+
+typedef struct include_point_tag {
+
+  /* How the point is given on the command line (for error messages) */
+
+  const char* specification;
+
+  /* coordinates */
+
+  number xi,yi;
+
+  /* link */
+
+  struct include_point_tag* next;
+
+} include_point;
+
+
+/* The collection of command line options */
+
+typedef struct {
+
+  /* float options */
+
+  number floats[num_float_options];
+
+  /* enum options */
+
+  int enums[num_enum_options];
+
+  /* bool options */
+
+  bool bools[num_bool_options];
+
+  /* flags */
+
+  unsigned int width_spec, height_spec,
+    top_margin_spec, left_margin_spec, right_margin_spec, bottom_margin_spec;
+
+  /* Other stuff */
+
+  int width, height;
+  const char* infilename;
+  include_point* include_points;
+
+} option;
+
+
+/* The collection of properties that correspond to the four specified
+   vertices 
+*/
+
+typedef struct {
+
+  /* 2d (image) coordinates of the 4 vertices */
+
+  number xi_ul, yi_ul,  xi_ur, yi_ur,  xi_ll, yi_ll,  xi_lr, yi_lr;
+
+  /* 3d (world) coordinates of the 4 vertices */
+
+  number xw_ul, yw_ul, zw_ul,  xw_ur, yw_ur, zw_ur,
+         xw_ll, yw_ll, zw_ll,  xw_lr, yw_lr, zw_lr;
+
+  /* Originally I planned to include the possibility to move the
+     centre of projection, that is the pixel the camera "looks at".  It
+     turned out, maybe surprisingly, that this does not have any
+     effect. So now this centre is moved to (0,0).
+     
+     Another original plan was to correct the output parameters
+     depending on the lengths of the paralellograms sides or its
+     angles.  This is, however, not possible without knowing something
+     like the camera angle or focal length (in pixels).
+  */
+  
+  /* The coefficients for the map from output to world coordinates.
+
+     The actual mapping is
+     u,v -> (ax+bx*u+cx*v, ay+by*u+cy*v, az+bz*u+cz*v)
+  */     
+  number ax,bx,cx, ay,by,cy, az,bz,cz;
+
+} world_data;
+
+
+/*
+  Internal infile buffer
+
+  This is a cyclic in random access out buffer, just large enough
+  to store all input lines that are still in use.
+*/
+
+typedef struct {
+
+  int num_rows, last_physical, last_logical;
+  tuple** rows;
+  const struct pam* inpam;
+
+} buffer;
+
+
+
+/*
+  The following are like MALLOCARRAY_NOFAIL and MALLOCVAR_NOFAIL,
+  but issue an error message instead of aborting.
+*/
+
+#define MALLOCARRAY_SAFE(handle,length) \
+{ \
+  MALLOCARRAY(handle,length); \
+  if (handle==NULL) \
+    pm_error ("Out of memory."); \
+}
+
+#define MALLOCVAR_SAFE(handle) \
+{ \
+  MALLOCVAR(handle); \
+  if (handle==NULL) \
+    pm_error ("Out of memory."); \
+}
+
+
+
+static void set_command_line_defaults (option *const options)
+{
+  options->infilename = "-";
+  options->include_points = NULL;
+  options->floats[8] = 1.0;             /* --detail               */
+  options->floats[9] = 1.0;             /* --ratio                */
+  options->floats[10] = 0.0;            /* --margin               */
+  options->floats[11] = 0.0;            /* --top_margin           */
+  options->floats[12] = 0.0;            /* --bottom_margin        */
+  options->floats[13] = 0.0;            /* --left_margin          */
+  options->floats[14] = 0.0;            /* --right_margin         */
+  options->enums[0] = lattice;          /* --input_system         */
+  options->enums[1] = lattice;          /* --output_system        */
+  options->enums[2] = pixel_u;          /* --input_unit           */
+  options->enums[3] = nearest;          /* --interpolation        */
+  options->enums[4] = free_;            /* --proportion           */
+  options->bools[0] = TRUE;             /* --frame_include        */
+}
+
+
+
+static int parse_enum (const char *const text,
+                       const char *const *const tokens, const char *const name)
+/*----------------------------------------------------------------------------
+  Parse an argument given to a multiple choice command line option
+-----------------------------------------------------------------------------*/
+{
+  bool found;
+  const char *const * cur_token;
+  char* tokenlist;
+  int tokenlistlen;
+  int value;
+  int num_spaces;
+  int i;
+
+  /* We find out, whether ^text occurs in ^tokens */
+
+  found = FALSE;
+  value = 0;
+  while (tokens[value] && !found) {
+    if (strcmp (text, tokens[value]))
+      value++;
+    else
+      found = TRUE;
+  };
+
+  /* otherwise issue an error */
+
+  if (!found) {
+    /* For the error message we want to list the allowed tokens.
+       First we have to determine, how much memory we need for that.
+    */
+    num_spaces = 2;
+
+    tokenlistlen = 0;
+    cur_token = tokens;
+    while (*cur_token) {
+      tokenlistlen += (strlen(*cur_token) + num_spaces);
+      cur_token++;
+    };
+    /* Then we create that list */
+    MALLOCARRAY_SAFE(tokenlist, tokenlistlen);
+    *tokenlist = 0;
+    cur_token = tokens;
+    while (*cur_token) {
+      for (i=0; i<num_spaces; i++)
+    strcat (tokenlist, " ");
+      strcat (tokenlist, *cur_token);
+      cur_token++;
+    };
+    /* Finally we issue the error */
+    pm_error ("'%s' is not a valid value for --%s.  "
+              "Valid values are:  %s", text, name, tokenlist);
+    /* pm_error() aborts the program, so there is no memory freeing here. */
+  };
+
+  /* If all went well, we return the value associated with the token,
+     which happens to be the index where we found the token
+  */
+  return value;
+}
+
+
+
+static number parse_float (char *const text)
+/*----------------------------------------------------------------------------
+  Parse an argument given to a float command line option.  We cannot
+  just call strtod, because we want to parse fractions like "5/3"
+-----------------------------------------------------------------------------*/
+{
+  bool error;
+  char* end;
+  char* denstart;
+  number num,den;
+
+  error = FALSE;
+  num = strtod (text, &end);    /* try strtod anyway */
+  switch (*end) {
+  case 0:           /* It is a plain number */
+    break;
+  case '/':         /* It might be a fraction */
+    /* (Try to) parse the numerator */
+    *end = 0;
+    num = strtod (text, &end);
+    error = (*end) != 0;
+    if (!error) {
+      /* Undo the above change */
+      *end = '/';
+      /* (Try to) parse the denominator */
+      denstart = end+1;
+      den = strtod (denstart, &end);
+      error = (fabs(den)<eps) || ((*end) != 0);
+      if (!error)
+    num /= den;
+    };
+    break;
+  default:          /* It is no number format we know */
+    error = TRUE;
+  };
+  if (error)
+    pm_error ("Invalid number format: %s", text);
+
+  return num;
+}
+
+
+
+static void parse_include_point(char * specification,
+                                include_point ** const include_pointsP)
+/*----------------------------------------------------------------------------
+  Add one point to the front of the linked list of include points
+  headed by include_pointsP.
+
+  The point is described by the asciiz string at 'specification'.
+----------------------------------------------------------------------------*/
+{
+  include_point* new_point;
+  char* comma_seek;
+
+  MALLOCVAR_SAFE(new_point);
+  new_point->specification = specification;
+  new_point->next = *include_pointsP;
+  *include_pointsP = new_point;
+
+  /* Now we parse the specification */
+
+  for (comma_seek = specification; (*comma_seek != ',') && (*comma_seek != 0);
+       comma_seek++);
+  if (*comma_seek == 0)
+    pm_error ("Invalid format for --include point: '%s'", specification);
+  *comma_seek = 0;      /* separate the two parts for parsing purposes */
+  new_point->xi = (number) parse_float(specification);
+  new_point->yi = (number) parse_float(comma_seek+1);
+  *comma_seek = ',';
+}
+
+
+static void parse_include_points(const char * const include_opt,
+                                 include_point ** const include_pointsP)
+/*----------------------------------------------------------------------------
+  Process the --include option value include_opt by making a linked list
+  of the points it describes (in reverse order).
+
+  Return a pointer to the first element of that linked list as
+  *include_pointsP.
+----------------------------------------------------------------------------*/
+{
+    char * cursor;
+    char * optWork;
+        /* Same as include_opt, except we replace delimiters with nulls
+           as we work.
+        */
+
+    optWork = strdup(include_opt);
+    if (optWork == NULL)
+        pm_error("out of memory");
+
+    cursor = &optWork[0];
+    while (*cursor != '\0') {
+        bool hit_end;
+        char * sem_seek;
+
+        for (sem_seek = cursor;
+             (*sem_seek != ';') && (*sem_seek != 0);
+             sem_seek++);
+
+        hit_end = (*sem_seek == '\0');
+            
+        *sem_seek = '\0';
+        parse_include_point(cursor, include_pointsP);
+
+        if (hit_end)
+            cursor = sem_seek;
+        else
+            cursor = sem_seek+1;
+    }
+    free(optWork);
+}
+
+
+static void parse_command_line (int argc, char* argv[], option *const options)
+{
+  char* float_text[num_float_options];
+  unsigned int float_spec[num_float_options];
+  char* enum_text[num_enum_options];
+  unsigned int enum_spec[num_enum_options];
+  char* bool_text[num_bool_options];
+  unsigned int bool_spec[num_bool_options];
+  char * include_opt;
+  unsigned int include_spec;
+  int i,j;
+  optStruct3 opt;
+  unsigned int option_def_index;
+  optEntry* option_def;
+
+  /* Let shhopt try its best */
+
+  option_def_index = 0;
+  MALLOCARRAY_SAFE(option_def,
+    (2*num_float_options + num_enum_options + num_bool_options + 3));
+  for (i=0; i<num_float_options; i++)
+    for (j=1; j<3; j++)
+      if (float_option_name[i][j])
+        OPTENT3(0, float_option_name[i][j], OPT_STRING,
+                &(float_text[i]), &(float_spec[i]), 0);
+  for (i=0; i<num_enum_options; i++)
+    OPTENT3(0, enum_option_name[i], OPT_STRING,
+            &(enum_text[i]), &(enum_spec[i]), 0);
+  for (i=0; i<num_bool_options; i++)
+    OPTENT3(0, bool_option_name[i], OPT_STRING,
+            &(bool_text[i]), &(bool_spec[i]), 0);
+  OPTENT3(0, "width", OPT_INT, &(options->width), &(options->width_spec), 0);
+  OPTENT3(0, "height", OPT_INT, &(options->height), &(options->height_spec),
+          0);
+  OPTENT3(0, "include", OPT_STRING, &include_opt, &include_spec, 0);
+  opt.opt_table = option_def;
+  opt.short_allowed = FALSE;
+  opt.allowNegNum = TRUE;
+  optParseOptions3 (&argc, argv, opt, sizeof(opt), 0);
+
+  /* The non-option arguments are optionally all eight coordinates
+     and optionally the input filename
+  */
+
+  switch (argc-1) {
+  case 1:
+    options->infilename = argv[1];
+  case 0:
+    for (i=0; i<8; i++)
+      if (!float_spec[i])
+        pm_error ("The %s-coordinate was not specified",
+                  float_option_name[i][0]);
+    break;
+  case 9:
+    options->infilename = argv[9];
+  case 8:
+    for (i=0; i<8; i++) {
+      float_text[i] = argv[i+1];
+      float_spec[i] = 1;
+    };
+    break;
+  default: pm_error ("Wrong (number of) command line arguments");
+  };
+
+  if (include_spec)
+      parse_include_points(include_opt, &options->include_points);
+
+  /* Parse float options -- shhopt retrieved them as strings */
+
+  for (i=0; i<num_float_options; i++)
+    if (float_spec[i])
+      options->floats[i] = parse_float (float_text[i]);
+
+  /* Parse enum options -- shhopt retrieved them as strings */
+
+  for (i=0; i<num_enum_options; i++)
+    if (enum_spec[i])
+      options->enums[i] = parse_enum (enum_text[i],enum_option_type[i],
+                                      enum_option_name[i]);
+
+  /* Parse bool options -- shhopt retrieved them as strings */
+
+  for (i=0; i<num_bool_options; i++)
+    if (bool_spec[i])
+      options->bools[i] = (first_false_bool_token >
+                           parse_enum (bool_text[i], bool_token,
+                                       bool_option_name[i]));
+
+  /* Propagate values where neccessary */
+
+  if (float_spec[10])           /* --margin */
+    for (i=11; i<15; i++)       /* --top_margin through --right_margin */
+      if (!(float_spec[i])) {
+        options->floats[i] = options->floats[10];
+        float_spec[i]=1;
+      };
+  options->top_margin_spec = float_spec[11];
+  options->bottom_margin_spec = float_spec[12];
+  options->left_margin_spec = float_spec[13];
+  options->right_margin_spec = float_spec[14];
+
+  /* Clean up */
+
+  free(option_def);
+}
+
+
+
+static void free_option (option *const options)
+{
+  include_point* current;
+  include_point* dispose;
+
+  current = options->include_points;
+  while (current != NULL) {
+    dispose = current;
+    current = current->next;
+    free(dispose);
+  };
+}
+
+
+
+static void init_world (option *const options,
+                        const struct pam *const inpam, world_data *const world)
+{
+  /* constructs xi_ul,...,yi_lr
+
+     Internally we use a pixel coordinate system with pixel units
+
+     This also translates the --include points' coordinates
+     into the internal system
+  */
+
+  number mult_x, mult_y, add_after;
+  int add_before;
+  include_point* current_include;
+
+  switch (options->enums[0]) {  /* --input_system */
+  case lattice:
+    add_after = -0.5;
+    add_before = 0;
+    break;
+  case pixel_s:
+    add_after = 0.0;
+    add_before = -1;
+    break;
+  };
+  switch (options->enums[2]) {  /* --input_unit */
+  case image:
+    mult_x = (number)((inpam->width) + add_before);
+    mult_y = (number)((inpam->height) + add_before);
+    break;
+  case pixel_u:
+    mult_x = 1.0;
+    mult_y = 1.0;
+    break;
+  };
+
+  world->xi_ul = ((number) options->floats[0]) * mult_x + add_after;
+  world->yi_ul = ((number) options->floats[1]) * mult_y + add_after;
+  world->xi_ur = ((number) options->floats[2]) * mult_x + add_after;
+  world->yi_ur = ((number) options->floats[3]) * mult_y + add_after;
+  world->xi_ll = ((number) options->floats[4]) * mult_x + add_after;
+  world->yi_ll = ((number) options->floats[5]) * mult_y + add_after;
+  world->xi_lr = ((number) options->floats[6]) * mult_x + add_after;
+  world->yi_lr = ((number) options->floats[7]) * mult_y + add_after;
+
+  for (current_include = options->include_points; current_include != NULL;
+       current_include = current_include->next) {
+    current_include->xi = current_include->xi * mult_x + add_after;
+    current_include->yi = current_include->yi * mult_y + add_after;
+  };
+}
+
+
+
+static bool solve_3_linear_equations (number* x1, number* x2, number* x3,
+                                      number const a11, number const a12,
+                                      number const a13, number const b1,
+                                      number const a21, number const a22,
+                                      number const a23, number const b2,
+                                      number const a31, number const a32,
+                                      number const a33, number const b3)
+/*----------------------------------------------------------------------------
+  The three equations are
+    a11*x1 + a12*x2 + a13*x3 = b1
+    a21*x1 + a22*x2 + a23*x3 = b2
+    a31*x1 + a32*x2 + a33*x3 = b3
+  The return value is wether the system is solvable
+----------------------------------------------------------------------------*/
+{
+  number c11,c12,d1,c21,c22,d2,e,f;
+  int pivot;
+
+  /* We do Gaussian elimination.
+     Whenever we find the system to be unsolvable, we just return FALSE.
+     In this specific case it makes the code clearer.
+  */
+
+  if (fabs(a11)<fabs(a21))
+    if (fabs(a21)<fabs(a31))
+      pivot=3;
+    else
+      pivot=2;
+  else
+    if (fabs(a11)<fabs(a31))
+      pivot=3;
+    else
+      pivot=1;
+
+  switch (pivot) {
+  case 1:
+    if (fabs(a11)<eps) return FALSE;
+    c11 = a22-a12*a21/a11;
+    c12 = a23-a13*a21/a11;
+    d1 =   b2- b1*a21/a11;
+    c21 = a32-a12*a31/a11;
+    c22 = a33-a13*a31/a11;
+    d2 =   b3- b1*a31/a11;
+    break;
+  case 2:
+    if (fabs(a21)<eps) return FALSE;
+    c11 = a12-a22*a11/a21;
+    c12 = a13-a23*a11/a21;
+    d1 =   b1- b2*a11/a21;
+    c21 = a32-a22*a31/a21;
+    c22 = a33-a23*a31/a21;
+    d2 =   b3- b2*a31/a21;
+    break;
+  case 3:
+    if (fabs(a31)<eps) return FALSE;
+    c11 = a12-a32*a11/a31;
+    c12 = a13-a33*a11/a31;
+    d1 =   b1- b3*a11/a31;
+    c21 = a22-a32*a21/a31;
+    c22 = a23-a33*a21/a31;
+    d2 =   b2- b3*a21/a31;
+    break;
+  }
+
+  /* Now we have a subsystem:
+       c11*x2 + c12*x3 = d1
+       c21*x2 + c22*x3 = d2
+  */
+
+  if (fabs(c11)>fabs(c21)) {
+    if (fabs(c11)<eps) return FALSE;
+    e = c22-c12*c21/c11;
+    f =  d2- d1*c21/c11;
+    /* Now we have a single equation e*x3=f */
+    if (fabs(e)<eps) return FALSE;
+    *x3 = f/e;
+    *x2 = (d1-c12*(*x3))/c11;
+  }
+  else {
+    if (fabs(c21)<eps) return FALSE;
+    e = c12-c22*c11/c21;
+    f =  d1- d2*c11/c21;
+    /* Now we have a single equation e*x3=f */
+    if (fabs(e)<eps) return FALSE;
+    *x3 = f/e;
+    *x2 = (d2-c22*(*x3))/c21;
+  };
+
+  switch (pivot) {
+  case 1:
+    *x1 = (b1-a13*(*x3)-a12*(*x2))/a11;
+    break;
+  case 2:
+    *x1 = (b2-a23*(*x3)-a22*(*x2))/a21;
+    break;
+  case 3:
+    *x1 = (b3-a33*(*x3)-a32*(*x2))/a31;
+    break;
+  };
+
+  return TRUE;
+}
+
+
+static void determine_world_parallelogram (world_data *const world,
+                                           const option *const options)
+/*----------------------------------------------------------------------------
+  constructs xw_ul,...,zw_lr from xi_ul,...,yi_lr
+     
+  Actually this is a solution of a linear equation system.
+  
+  We first solve 4 variables (the 4 z-coordinates) against 4
+  equations: Each z-coordinate determines the corresponding x- and
+  y-coordinates in a linear fashion, where the coefficients are taken
+  from the image coordinates. This corresponds to the fact that a
+  point of an image determines a line in the world.
+  
+  3 equations state that the 4 points form a parallelogram.  The 4th
+  equation is for normalization and states, that the centre of the
+  parallelogram has a z-coordinate of 1.
+-----------------------------------------------------------------------------*/
+{
+  number dx1,dx2,dx3,dx4,dx5, dy1,dy2,dy3,dy4,dy5;
+  number det;
+  number xw_ul,yw_ul,zw_ul, xw_ur,yw_ur,zw_ur,
+         xw_ll,yw_ll,zw_ll, xw_lr,yw_lr,zw_lr;
+  number top_margin, left_margin, right_margin, bottom_margin;
+  include_point* current_include;
+  number include_xo, include_yo, include_zw;
+  bool solvable, margin_spec;
+
+  dx1 = world->xi_lr - world->xi_ul;  /* d1 is the image diagonal ul -> lr */
+  dy1 = world->yi_lr - world->yi_ul;
+  dx2 = world->xi_ur - world->xi_ll;  /* d2 is the image diagonal ll -> ur */
+  dy2 = world->yi_ur - world->yi_ll;
+  dx3 = world->xi_ur - world->xi_ul;  /* d3 is the image side ul -> ur */
+  dy3 = world->yi_ur - world->yi_ul;
+  dx4 = world->xi_ul - world->xi_ll;  /* d4 is the image side ll -> ul */
+  dy4 = world->yi_ul - world->yi_ll;
+  dx5 = world->xi_ur - world->xi_lr;  /* d5 is the image side lr -> ur */
+  dy5 = world->yi_ur - world->yi_lr;
+
+  det = dx2*dy1 - dx1*dy2;
+
+  /* A determinant of 0 is really bad: It means that that diagonals in the
+     image are parallel (or of zero length)
+  */
+
+  if ((-eps<det) && (det<eps))
+    pm_error ("The specified vertices are degenerated.  "
+              "Maybe they were given in the wrong order?");
+
+  zw_ul = 2.0*(dx5*dy2-dx2*dy5)/det;
+  zw_ur = 2.0*(dx4*dy1-dx1*dy4)/det;
+  zw_ll = 2.0*(dx3*dy1-dx1*dy3)/det;
+  zw_lr = 2.0*(dx2*dy3-dx3*dy2)/det;
+
+  /* A zero or negative value for some z means that three of the points
+     lie on a line in the image or that the four points do not define
+     a convex shape.  We have to forbid this in order to prevent divisions
+     by zero later on
+  */
+
+  if ((zw_ul<eps) || (zw_ur<eps) || (zw_ll<eps) || (zw_lr<eps))
+    pm_error ("The specified vertices are degenerated.  "
+              "Maybe they were given in the wrong order?");
+
+  xw_ul = world->xi_ul * zw_ul;
+  yw_ul = world->yi_ul * zw_ul;
+  xw_ur = world->xi_ur * zw_ur;
+  yw_ur = world->yi_ur * zw_ur;
+  xw_ll = world->xi_ll * zw_ll;
+  yw_ll = world->yi_ll * zw_ll;
+  xw_lr = world->xi_lr * zw_lr;
+  yw_lr = world->yi_lr * zw_lr;
+
+  /* Now we introduce the margin. There are several ways the margin can be
+     defined. margin_spec keeps track of wether one of them has yet been
+     used. As long as margin_spec==FALSE, the variables top_margin to
+     bottom_margin are not initialized! */
+
+  if (options->bools[0]) {      /* --frame_include */
+    top_margin = 0.0;
+    left_margin = 0.0;
+    right_margin = 0.0;
+    bottom_margin = 0.0;
+    margin_spec = TRUE;
+  } else
+    margin_spec = FALSE;
+
+  for (current_include = options->include_points; current_include != NULL;
+       current_include = current_include->next) {
+    solvable = solve_3_linear_equations(&include_xo, &include_yo, &include_zw,
+      xw_ul-xw_ur, xw_ul-xw_ll, current_include->xi, xw_ul,
+      yw_ul-yw_ur, yw_ul-yw_ll, current_include->yi, yw_ul,
+      zw_ul-zw_ur, zw_ul-zw_ll, 1.0, zw_ul);
+    if (!solvable)
+      pm_error ("The --include point %s lies on the horizon.",
+                current_include->specification);
+    if (include_zw<0.0)
+      pm_error ("The --include point %s lies beyond the horizon.",
+                current_include->specification);
+    if (margin_spec) {
+      top_margin = MAX(top_margin, -include_yo);
+      left_margin = MAX(left_margin, -include_xo);
+      right_margin = MAX(right_margin, include_xo-1.0);
+      bottom_margin = MAX(bottom_margin, include_yo-1.0);
+    } else {
+      top_margin = -include_yo;
+      left_margin = -include_xo;
+      right_margin = include_xo-1.0;
+      bottom_margin = include_yo-1.0;
+      margin_spec = TRUE;
+    };
+  }
+
+  if (margin_spec) {    /* the margin is there. --top_margin and such can
+                           still enlarge it */
+    if (options->top_margin_spec)
+      top_margin = MAX(top_margin, options->floats[11]);
+    if (options->left_margin_spec)
+      left_margin = MAX(left_margin, options->floats[13]);
+    if (options->right_margin_spec)
+      right_margin = MAX(right_margin, options->floats[14]);
+    if (options->bottom_margin_spec)
+      bottom_margin = MAX(bottom_margin, options->floats[12]);
+  } else                /* the margin is not yet there. --top_margin and
+                           such can remedy this only if all of them are
+                           given */
+    if ((options->top_margin_spec) && (options->left_margin_spec) &&
+        (options->right_margin_spec) && (options->bottom_margin_spec)) {
+      top_margin = options->floats[11];
+      left_margin = options->floats[13];
+      right_margin = options->floats[14];
+      bottom_margin = options->floats[12];
+    } else              /* the margin finally is not there */
+      pm_error ("No frame specified. "
+                "Use --frame_include=yes or --include or --margin.");
+
+  world->xw_ul = xw_ul
+    - top_margin * (xw_ll-xw_ul)
+    - left_margin * (xw_ur-xw_ul);
+  world->yw_ul = yw_ul
+    - top_margin * (yw_ll-yw_ul)
+    - left_margin * (yw_ur-yw_ul);
+  world->zw_ul = zw_ul
+    - top_margin * (zw_ll-zw_ul)
+    - left_margin * (zw_ur-zw_ul);
+  world->xw_ur = xw_ur
+    - top_margin * (xw_lr-xw_ur)
+    - right_margin * (xw_ul-xw_ur);
+  world->yw_ur = yw_ur
+    - top_margin * (yw_lr-yw_ur)
+    - right_margin * (yw_ul-yw_ur);
+  world->zw_ur = zw_ur
+    - top_margin * (zw_lr-zw_ur)
+    - right_margin * (zw_ul-zw_ur);
+  world->xw_ll = xw_ll
+    - bottom_margin * (xw_ul-xw_ll)
+    - left_margin * (xw_lr-xw_ll);
+  world->yw_ll = yw_ll
+    - bottom_margin * (yw_ul-yw_ll)
+    - left_margin * (yw_lr-yw_ll);
+  world->zw_ll = zw_ll
+    - bottom_margin * (zw_ul-zw_ll)
+    - left_margin * (zw_lr-zw_ll);
+  world->xw_lr = xw_lr
+    - bottom_margin * (xw_ur-xw_lr)
+    - right_margin * (xw_ll-xw_lr);
+  world->yw_lr = yw_lr
+    - bottom_margin * (yw_ur-yw_lr)
+    - right_margin * (yw_ll-yw_lr);
+  world->zw_lr = zw_lr
+    - bottom_margin * (zw_ur-zw_lr)
+    - right_margin * (zw_ll-zw_lr);
+
+  /* Again we have to forbid nonpositive z */
+
+  if ((world->zw_ul<eps) || (world->zw_ur<eps) ||
+       (world->zw_ll<eps) || (world->zw_lr<eps))
+    pm_error ("The specified margin is too large.");
+
+}
+
+
+
+static int diff (int const a, int const b)
+{
+  return MAX (b-a, a-b);
+}
+
+
+
+static number norm_vector (number const x1, number const y1, number const z1,
+                           number const x2, number const y2, number const z2)
+/*----------------------------------------------------------------------------
+  Two 3D vertices p1 and p2 are given by their coordinates.
+  A linear movement from p1 to p2, parameterized by the interval [0,1],
+  is projected to the input image. The function returns the norm of
+  the derivative of this overall movement at time 0, that is at p1.
+  The norm uses the max metric.
+-----------------------------------------------------------------------------*/
+{
+  number dx,dy;
+
+  dx = (x2-x1)/z1 - (z2-z1)*x1/(z1*z1);
+  dy = (y2-y1)/z1 - (z2-z1)*y1/(z1*z1);
+
+  return MAX (fabs(dx), fabs(dy));
+}
+
+
+
+static number norm_side (number const x1, number const y1, number const z1,
+                         number const x2, number const y2, number const z2)
+/*----------------------------------------------------------------------------
+  This is similar to norm_vector. But now the norm of the derivative
+  is computed at both endpoints of the movement and the maximum is
+  returned.
+  
+  Why do we do this? The return value n is in fact the maximum of the
+  norm of the derivative ALONG the movement. So we know that if we
+  divide the movement into at least n steps, we will encounter every
+  x- and every y-coordinate of the input image between the two points.
+  This is our notion of losslessness with --detail=1.
+-----------------------------------------------------------------------------*/
+{
+  return MAX (norm_vector(x1,y1,z1,x2,y2,z2),
+              norm_vector(x2,y2,z2,x1,y1,z1));
+}
+
+
+
+static void determine_output_width_and_height (const world_data *const world,
+                                               option *const options)
+{
+  number du,dv;
+  int xsteps,ysteps,width,height;
+
+  /* Determine the number of steps for losslessness */
+
+  du = MAX (norm_side(world->xw_ul, world->yw_ul, world->zw_ul,
+                      world->xw_ur, world->yw_ur, world->zw_ur),
+            norm_side(world->xw_ll, world->yw_ll, world->zw_ll,
+                      world->xw_lr, world->yw_lr, world->zw_lr));
+  dv = MAX (norm_side(world->xw_ul, world->yw_ul, world->zw_ul,
+                      world->xw_ll, world->yw_ll, world->zw_ll),
+            norm_side(world->xw_ur, world->yw_ur, world->zw_ur,
+                      world->xw_lr, world->yw_lr, world->zw_lr));
+  xsteps = ceil(du*options->floats[8]);  /* option->floats[8] is --detail */
+  ysteps = ceil(dv*options->floats[8]);
+
+  /* Turn the numbers of steps into width and height */
+
+  switch (options->enums[1]) {  /* --output_system */
+  case lattice:
+    width = xsteps;
+    height = ysteps;
+    break;
+  case pixel_s:
+    width = xsteps+1;
+    height = ysteps+1;
+    break;
+  };
+
+  /* Correct the proportion of width and height by increasing one of them */
+
+  switch (options->enums[4]) {  /* --proportion */
+  case free_:   /* no correction at all */
+    break;
+  case fixed:   /* correction now */
+    /* options->floats[9] is --ratio */
+    width = MAX (floor(0.5 + ((number)height) * options->floats[9]),
+                 width);
+    height = MAX (floor(0.5 + ((number)width) / options->floats[9]),
+                  height);
+    break;
+  };
+
+  /* Override anything we have by the specified width and height */
+
+  if (!(options->width_spec))
+    options->width=width;
+  if (!(options->height_spec))
+    options->height=height;
+}
+
+
+
+static void determine_coefficients_lattice (world_data *const world,
+                                            const option *const options)
+/*----------------------------------------------------------------------------
+  Constructs ax,...,cz from xw_ul,...,zw_lr
+     
+  The calculations assume lattice coordinates, that is the point ul
+  corresponds to the upper left corner of the pixel (0,0) and the
+  point lr corresponds to the lower left corner of the pixel
+  (width-1,height-1)
+-----------------------------------------------------------------------------*/
+{
+  number width,height;
+
+  width = (number) options->width;
+  height = (number) options->height;
+
+  world->bx = (world->xw_ur - world->xw_ul)/width;
+  world->cx = (world->xw_ll - world->xw_ul)/height;
+  world->by = (world->yw_ur - world->yw_ul)/width;
+  world->cy = (world->yw_ll - world->yw_ul)/height;
+  world->bz = (world->zw_ur - world->zw_ul)/width;
+  world->cz = (world->zw_ll - world->zw_ul)/height;
+
+  world->ax = world->xw_ul + world->bx/2.0 + world->cx/2.0;
+  world->ay = world->yw_ul + world->by/2.0 + world->cy/2.0;
+  world->az = world->zw_ul + world->bz/2.0 + world->cz/2.0;
+}
+
+
+
+static void determine_coefficients_pixel (world_data *const world,
+                                          const option *const options)
+/*----------------------------------------------------------------------------
+  Constructs ax,...,cz from xw_ul,...,zw_lr
+     
+  The calculations assume pixel coordinates, that is the point ul
+  corresponds to the centre of the pixel (0,0) and the point lr
+  corresponds to the centre of the pixel (width-1,height-1)
+-----------------------------------------------------------------------------*/
+{
+  number width,height;
+
+  if (options->width == 1)
+    pm_error ("You specified 'pixel' as output coordinate model "
+              "and a width of 1.  These things don't mix.");
+  if (options->height == 1)
+    pm_error ("You specified 'pixel' as output coordinate model "
+              "and a height of 1.  These things don't mix.");
+
+  width = (number) (options->width-1);
+  height = (number) (options->height-1);
+
+  world->bx = (world->xw_ur - world->xw_ul)/width;
+  world->cx = (world->xw_ll - world->xw_ul)/height;
+  world->by = (world->yw_ur - world->yw_ul)/width;
+  world->cy = (world->yw_ll - world->yw_ul)/height;
+  world->bz = (world->zw_ur - world->zw_ul)/width;
+  world->cz = (world->zw_ll - world->zw_ul)/height;
+
+  world->ax = world->xw_ul;
+  world->ay = world->yw_ul;
+  world->az = world->zw_ul;
+}
+
+
+
+static void outpixel_to_inpixel (int const xo, int const yo, 
+                                 number* const xi, number* const yi,
+                                 const world_data *const world)
+{
+  number xof,yof,xw,yw,zw;
+
+  xof = (number) xo;
+  yof = (number) yo;
+  xw = world->ax + world->bx*xof + world->cx*yof;
+  yw = world->ay + world->by*xof + world->cy*yof;
+  zw = world->az + world->bz*xof + world->cz*yof;
+  *xi = xw/zw;
+  *yi = yw/zw;
+}
+
+static int outpixel_to_iny (int xo, int yo, const world_data *const world)
+{
+  number xi,yi;
+
+  outpixel_to_inpixel (xo,yo,&xi,&yi,world);
+
+  return (int) yi;
+}
+
+static int clean_y (int const y,  const struct pam *const outpam)
+{
+  return MIN(MAX(0, y), outpam->height-1);
+}
+
+static void init_buffer (buffer *const b, const world_data *const world,
+                         const option *const options,
+                         const struct pam *const inpam,
+                         const struct pam *const outpam)
+{
+  int yul, yur, yll, ylr, y_min;
+  int i, num_rows;
+
+  yul = outpixel_to_iny (0,0,world);
+  yur = outpixel_to_iny (outpam->width-1,0,world);
+  yll = outpixel_to_iny (0,outpam->height-1,world);
+  ylr = outpixel_to_iny (outpam->width-1,outpam->height-1,world);
+
+  y_min = MIN (MIN (yul,yur), MIN (yll,ylr));
+  num_rows = MAX (MAX (diff (yul, yur),
+                       diff (yll, ylr)),
+                  MAX (diff (clean_y(yul,outpam), clean_y(y_min,outpam)),
+                       diff (clean_y(yur,outpam), clean_y(y_min,outpam))))
+    + 2;
+  switch (options->enums[3]) {  /* --interpolation */
+  case nearest:
+    break;
+  case linear:
+    num_rows += 1;
+    break;
+  };
+  if (num_rows > inpam->height)
+    num_rows = inpam->height;
+
+  b->num_rows = num_rows;
+  MALLOCARRAY_SAFE (b->rows, num_rows);
+  for (i=0; i<num_rows; i++) {
+    b->rows[i] = pnm_allocpamrow (inpam);
+    pnm_readpamrow (inpam, b->rows[i]);
+  };
+  b->last_physical = num_rows-1;
+  b->last_logical = num_rows-1;
+  b->inpam = inpam;
+}
+
+static tuple* read_buffer (buffer *const b, int const logical_y)
+{
+  int y;
+
+  while (logical_y > b->last_logical) {
+    b->last_physical++;
+    if (b->last_physical == b->num_rows)
+      b->last_physical = 0;
+    pnm_readpamrow (b->inpam, b->rows[b->last_physical]);
+    b->last_logical++;
+  }
+
+  y = logical_y - b->last_logical + b->last_physical;
+  if (y<0)
+    y += b->num_rows;
+
+  return b->rows[y];
+}
+
+static void free_buffer (buffer *const b)
+{
+  int i;
+
+  for (i=0; i<b->num_rows; i++)
+    pnm_freepamrow (b->rows[i]);
+  free (b->rows);
+}
+
+
+
+
+/* The following variables are global for speed reasons.
+   In this way they do not have to be passed to each call of the
+   interpolation functions
+
+   Think of this as Sch&ouml;nfinkeling (aka Currying).
+*/
+
+static tuple background;
+static buffer* indata;
+static int width,height,depth;
+
+static void init_interpolation_global_vars (buffer* const inbuffer,
+                                            const struct pam *const inpam,
+                                            const struct pam *const outpam)
+{
+  pnm_createBlackTuple (outpam, &background);
+  indata = inbuffer;
+  width = inpam->width;
+  height = inpam->height;
+  depth = outpam->depth;
+}
+
+
+
+static void clean_interpolation_global_vars (void)
+{
+  free (background);
+}
+
+
+
+/* These functions perform the interpolation */
+
+static tuple attempt_read (int const x, int const y)
+{
+  if ((x<0) || (x>=width) || (y<0) || (y>=height))
+    return background;
+  else
+    return read_buffer(indata, y)[x];
+}
+
+
+
+static void take_nearest (tuple const dest, number const x, number const y)
+{
+  int xx,yy,entry;
+  tuple p;
+
+  xx = (int)floor(x+0.5);
+  yy = (int)floor(y+0.5);
+  p = attempt_read (xx, yy);
+  for (entry=0; entry<depth; entry++) {
+    dest[entry]=p[entry];
+  }
+}
+
+
+
+static void linear_interpolation (tuple const dest, 
+                                  number const x, number const y)
+{
+  int xx,yy,entry;
+  number xf,yf,a,b,c,d;
+  tuple p1,p2,p3,p4;
+
+  xx = (int)floor(x);
+  yy = (int)floor(y);
+  xf = x-(number)xx;
+  yf = y-(number)yy;
+  p1 = attempt_read (xx, yy);
+  p2 = attempt_read (xx+1, yy);
+  p3 = attempt_read (xx, yy+1);
+  p4 = attempt_read (xx+1, yy+1);
+  a = (1.0-xf)*(1.0-yf);
+  b = xf*(1.0-yf);
+  c = (1.0-xf)*yf;
+  d = xf*yf;
+  for (entry=0; entry<depth; entry++) {
+    dest[entry]=(sample) floor(
+      a*((number) p1[entry]) +
+      b*((number) p2[entry]) +
+      c*((number) p3[entry]) +
+      d*((number) p4[entry]) +
+      0.5);
+  }
+}
+
+
+
+int main (int argc, char* argv[])
+{
+  FILE* infp;
+  struct pam inpam;
+  buffer inbuffer;
+  FILE* outfp;
+  struct pam outpam;
+  tuple* outrow;
+  option options;
+  world_data world;
+  int row,col;
+  number xi,yi;
+  void (*interpolate) (tuple, number, number);
+
+  /* The usual initializations */
+
+  pnm_init (&argc, argv);
+  set_command_line_defaults (&options);
+  parse_command_line (argc, argv, &options);
+  infp = pm_openr (options.infilename);
+  pnm_readpaminit (infp, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+  /* Our own initializations */
+
+  init_world (&options, &inpam, &world);
+  determine_world_parallelogram (&world, &options);
+  determine_output_width_and_height (&world, &options);
+  switch (options.enums[1]) {   /* --output_system */
+  case lattice:
+    determine_coefficients_lattice (&world, &options);
+    break;
+  case pixel_s:
+    determine_coefficients_pixel (&world, &options);
+    break;
+  };
+
+  /* Initialize outpam */
+
+  outfp = pm_openw ("-");
+  outpam.size = sizeof (outpam);
+  outpam.len = PAM_STRUCT_SIZE(bytes_per_sample);
+  outpam.file = outfp;
+  outpam.format = inpam.format;
+  outpam.plainformat = inpam.plainformat;
+  outpam.height = options.height;
+  outpam.width = options.width;
+  outpam.depth = inpam.depth;
+  outpam.maxval = inpam.maxval;
+  outpam.bytes_per_sample = inpam.bytes_per_sample;
+  pnm_writepaminit (&outpam);
+
+  /* Initialize the actual calculation */
+
+  init_buffer (&inbuffer, &world, &options, &inpam, &outpam);
+  outrow = pnm_allocpamrow (&outpam);
+  init_interpolation_global_vars (&inbuffer,&inpam,&outpam);
+  switch (options.enums[3]) {   /* --interpolation */
+  case nearest:
+    interpolate = take_nearest;
+    break;
+  case linear:
+    interpolate = linear_interpolation;
+    break;
+  };
+
+  /* Perform the actual calculation */
+
+  for (row=0; row<outpam.height; row++) {
+    for (col=0; col<outpam.width; col++) {
+      outpixel_to_inpixel (col,row,&xi,&yi,&world);
+      interpolate(outrow[col],xi,yi);
+    }
+    pnm_writepamrow (&outpam, outrow);
+  }
+
+  /* Close everything down nicely */
+
+  clean_interpolation_global_vars ();
+  free_buffer (&inbuffer);
+  pnm_freepamrow (outrow);
+  free_option (&options);
+  pm_close (infp);
+  pm_close (outfp);
+  return 0;
+}
+
+
+
+
diff --git a/editor/pampop9.c b/editor/pampop9.c
new file mode 100644
index 00000000..d6c61e4f
--- /dev/null
+++ b/editor/pampop9.c
@@ -0,0 +1,108 @@
+/* 
+ *
+ * (c) Robert Tinsley, 2003 (http://www.thepoacher.net/contact)
+ *
+ * Released under the GPL (http://www.fsf.org/licenses/gpl.txt)
+ *
+ *
+ * CHANGES
+ *
+ * v1.00 2003-02-28 Original version
+ *
+ * v1.10 2003-03-02
+ *  + changed to use pam_* routines rather than ppm_*
+ *  + changed to use pm_* rather than fopen()/fclose()
+ *  + renamed from ppmgrid to pampup9
+ *  + wrote a man-page (actually, html)
+ *
+ * Changes by Bryan Henderson for inclusion in the Netpbm package (fully
+ * exploiting Netpbm library).  Renamed to Pampop9.  March 2003.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h> /* atoi() */
+
+#include "pam.h"
+
+static const char * const copyright = 
+  "(c) Robert Tinsley 2003 (http://www.thepoacher.net/contact)";
+
+static const char *usagestr = "pnmfile|- xtiles ytiles xdelta ydelta";
+
+int main(int argc, char *argv[])
+{
+    const char *filename = "-";
+    int xtiles, ytiles, xdelta, ydelta;
+    FILE *fp;
+
+    struct pam spam, dpam;
+    tuple **spix, *dpix;
+    int xtilesize, ytilesize, sx, sy, dx, dy, p;
+
+
+
+    pnm_init(&argc, argv);
+
+    if (argc-1 != 5) {
+        pm_error("Wrong number of arguments.  Program requires 5 arguments; "
+                 "you supplied %d.  Usage: %s", argc-1, usagestr);
+    }
+
+    filename = argv[1];
+    xtiles = atoi(argv[2]);
+    ytiles = atoi(argv[3]);
+    xdelta = atoi(argv[4]);
+    ydelta = atoi(argv[5]);
+
+    if (filename == NULL || *filename == '\0'
+        || xtiles <= 0 || ytiles <= 0 || xdelta < 0 || ydelta < 0) 
+        pm_error("invalid argument");
+
+    /* read src pam */
+
+    fp = pm_openr(filename);
+
+    spix = pnm_readpam(fp, &spam, PAM_STRUCT_SIZE(tuple_type));
+
+    pm_close(fp);
+
+    /* init dst pam */
+
+    xtilesize = spam.width  - (xtiles - 1) * xdelta;
+    ytilesize = spam.height - (ytiles - 1) * ydelta;
+
+    if (xtilesize <= 0)
+        pm_error("xtilesize must be positive.  You specified %d", xtilesize);
+    if (ytilesize <= 0)
+        pm_error("ytilesize must be positive.  You specified %d", ytilesize);
+
+    dpam = spam;
+    dpam.file = stdout;
+    dpam.width  = xtiles * xtilesize;
+    dpam.height = ytiles * ytilesize;
+
+    dpix = pnm_allocpamrow(&dpam);
+
+    pnm_writepaminit(&dpam);
+
+    /* generate dst pam */
+
+    for (dy = 0; dy < dpam.height; dy++) {
+        sy = ((int) (dy / ytilesize)) * ydelta + (dy % ytilesize);
+        for (dx = 0; dx < dpam.width; dx++) {
+            sx = ((int) (dx / xtilesize)) * xdelta + (dx % xtilesize);
+                for (p = 0; p < spam.depth; ++p) {
+                    dpix[dx][p] = spix[sy][sx][p];
+            }
+        }
+        pnm_writepamrow(&dpam, dpix);
+    }
+
+    /* all done */
+
+    pnm_freepamarray(spix, &spam);
+    pnm_freepamrow(dpix);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/editor/pamscale.c b/editor/pamscale.c
new file mode 100644
index 00000000..229fce42
--- /dev/null
+++ b/editor/pamscale.c
@@ -0,0 +1,2149 @@
+/* pamscale.c - rescale (resample) a PNM image
+
+   This program evolved out of Jef Poskanzer's program Pnmscale from
+   his Pbmplus package (which was derived from Poskanzer's 1989
+   Ppmscale).  The resampling logic was taken from Michael Reinelt's
+   program Pnmresample, somewhat recoded to follow Netpbm conventions.
+   Michael submitted that for inclusion in Netpbm in December 2003.
+   The frame of the program is by Bryan Henderson, and the old scaling
+   algorithm is based on that in Jef Poskanzer's Pnmscale, but
+   completely rewritten by Bryan Henderson ca. 2000.  Plenty of other
+   people contributed code changes over the years.
+
+   Copyright (C) 2003 by Michael Reinelt <reinelt@eunet.at>
+  
+   Copyright (C) 1989, 1991 by Jef Poskanzer.
+  
+   Permission to use, copy, modify, and distribute this software and its
+   documentation for any purpose and without fee is hereby granted, provided
+   that the above copyright notice appear in all copies and that both that
+   copyright notice and this permission notice appear in supporting
+   documentation.  This software is provided "as is" without express or
+   implied warranty.
+*/
+
+#define _XOPEN_SOURCE   /* get M_PI in math.h */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <assert.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+
+/****************************/
+/****************************/
+/********* filters **********/
+/****************************/
+/****************************/
+
+/* Most of the filters are FIR (finite impulse respone), but some
+** (sinc, bessel) are IIR (infinite impulse respone).
+** They should be windowed with hanning, hamming, blackman or
+** kaiser window.
+** For sinc and bessel the blackman window will be used per default.
+*/
+
+#define EPSILON 1e-7
+
+
+/* x^2 and x^3 helper functions */
+static __inline__ double 
+pow2 (double x)
+{
+  return x*x;
+}
+
+static __inline__ double 
+pow3 (double x) 
+{
+  return x*x*x;
+}
+
+
+/* box, pulse, Fourier window, */
+/* box function also know as rectangle function */
+/* 1st order (constant) b-spline */
+
+#define radius_point (0.0)
+#define radius_box (0.5)
+
+static double 
+filter_box (double x)
+{
+    if (x <  0.0) x = -x;
+    if (x <= 0.5) return 1.0;
+    return 0.0;
+}
+
+
+/* triangle, Bartlett window, */
+/* triangle function also known as lambda function */
+/* 2nd order (linear) b-spline */
+
+#define radius_triangle (1.0)
+
+static double 
+filter_triangle (double x)
+{
+    if (x <  0.0) x = -x;
+    if (x < 1.0) return 1.0-x;
+    return 0.0;
+}
+
+
+/* 3rd order (quadratic) b-spline */
+
+#define radius_quadratic (1.5)
+
+static double 
+filter_quadratic(double x)
+{
+    if (x <  0.0) x = -x;
+    if (x < 0.5) return 0.75-pow2(x);
+    if (x < 1.5) return 0.50*pow2(x-1.5);
+    return 0.0;
+}
+
+
+/* 4th order (cubic) b-spline */
+
+#define radius_cubic (2.0)
+
+static double 
+filter_cubic(double x)
+{
+    if (x <  0.0) x = -x;
+    if (x < 1.0) return 0.5*pow3(x) - pow2(x) + 2.0/3.0;
+    if (x < 2.0) return pow3(2.0-x)/6.0;
+    return 0.0;
+}
+
+
+/* Catmull-Rom spline, Overhauser spline */
+
+#define radius_catrom (2.0)
+
+static double 
+filter_catrom(double x)
+{
+    if (x <  0.0) x = -x;
+    if (x < 1.0) return  1.5*pow3(x) - 2.5*pow2(x)         + 1.0;
+    if (x < 2.0) return -0.5*pow3(x) + 2.5*pow2(x) - 4.0*x + 2.0;
+    return 0.0;
+}
+
+
+/* Mitchell & Netravali's two-param cubic */
+/* see Mitchell&Netravali,  */
+/* "Reconstruction Filters in Computer Graphics", SIGGRAPH 88 */
+
+#define radius_mitchell (2.0)
+
+static double 
+filter_mitchell(double x)
+{
+
+    double b = 1.0/3.0;
+    double c = 1.0/3.0;
+
+    double p0 = (  6.0 -  2.0*b         ) / 6.0;
+    double p2 = (-18.0 + 12.0*b +  6.0*c) / 6.0;
+    double p3 = ( 12.0 -  9.0*b -  6.0*c) / 6.0;
+    double q0 = (         8.0*b + 24.0*c) / 6.0;
+    double q1 = (      - 12.0*b - 48.0*c) / 6.0;
+    double q2 = (         6.0*b + 30.0*c) / 6.0;
+    double q3 = (      -      b -  6.0*c) / 6.0;
+
+    if (x <  0.0) x = -x;
+    if (x <  1.0) return p3*pow3(x) + p2*pow2(x)        + p0;
+    if (x < 2.0) return q3*pow3(x) + q2*pow2(x) + q1*x + q0;
+    return 0.0;
+}
+
+
+/* Gaussian filter (infinite) */
+
+#define radius_gauss (1.25)
+
+static double 
+filter_gauss(double x)
+{
+    return exp(-2.0*pow2(x)) * sqrt(2.0/M_PI);
+}
+
+
+/* sinc, perfect lowpass filter (infinite) */
+
+#define radius_sinc (4.0)
+
+static double 
+filter_sinc(double x)
+{
+    /* Note: Some people say sinc(x) is sin(x)/x.  Others say it's
+       sin(PI*x)/(PI*x), a horizontal compression of the former which is
+       zero at integer values.  We use the latter, whose Fourier transform
+       is a canonical rectangle function (edges at -1/2, +1/2, height 1).
+    */
+    if (x == 0.0) return 1.0;
+    return sin(M_PI*x)/(M_PI*x);
+}
+
+
+/* Bessel (for circularly symm. 2-d filt, infinite) */
+/* See Pratt "Digital Image Processing" p. 97 for Bessel functions */
+
+#define radius_bessel (3.2383)
+
+static double 
+filter_bessel(double x)
+{
+    if (x == 0.0) return M_PI/4.0;
+    return j1(M_PI*x)/(2.0*x);
+}
+
+
+/* Hanning window (infinite) */
+
+#define radius_hanning (1.0)
+
+static double 
+filter_hanning(double x)
+{
+    return 0.5*cos(M_PI*x) + 0.5;
+}
+
+
+/* Hamming window (infinite) */
+
+#define radius_hamming (1.0)
+
+static double 
+filter_hamming(double x)
+{
+  return 0.46*cos(M_PI*x) + 0.54;
+}
+
+
+/* Blackman window (infinite) */
+
+#define radius_blackman (1.0)
+
+static double 
+filter_blackman(double x)
+{
+    return 0.5*cos(M_PI*x) + 0.08*cos(2.0*M_PI*x) + 0.42;
+}
+
+
+/* parameterized Kaiser window (infinite) */
+/* from Oppenheim & Schafer, Hamming */
+
+#define radius_kaiser (1.0)
+
+/* modified zeroth order Bessel function of the first kind. */
+static double 
+bessel_i0(double x)
+{
+  
+    int i;
+    double sum, y, t;
+  
+    sum = 1.0;
+    y = pow2(x)/4.0;
+    t = y;
+    for (i=2; t>EPSILON; i++) {
+        sum += t;
+        t   *= (double)y/pow2(i);
+    }
+    return sum;
+}
+
+static double 
+filter_kaiser(double x)
+{
+    /* typically 4<a<9 */
+    /* param a trades off main lobe width (sharpness) */
+    /* for side lobe amplitude (ringing) */
+  
+    double a   = 6.5;
+    double i0a = 1.0/bessel_i0(a);
+  
+    return i0a*bessel_i0(a*sqrt(1.0-pow2(x)));
+}
+
+
+/* normal distribution (infinite) */
+/* Normal(x) = Gaussian(x/2)/2 */
+
+#define radius_normal (1.0)
+
+static double 
+filter_normal(double x)
+{
+    return exp(-pow2(x)/2.0) / sqrt(2.0*M_PI);
+    return 0.0;
+}
+
+
+/* Hermite filter */
+
+#define radius_hermite  (1.0)
+
+static double 
+filter_hermite(double x)
+{
+    /* f(x) = 2|x|^3 - 3|x|^2 + 1, -1 <= x <= 1 */
+    if (x <  0.0) x = -x;
+    if (x <  1.0) return 2.0*pow3(x) - 3.0*pow2(x) + 1.0;
+    return 0.0;
+}
+
+
+/* Lanczos filter */
+
+#define radius_lanczos (3.0)
+
+static double 
+filter_lanczos(double x)
+{
+    if (x <  0.0) x = -x;
+    if (x <  3.0) return filter_sinc(x) * filter_sinc(x/3.0);
+    return(0.0);
+}
+
+
+
+typedef struct {
+    const char *name;
+    double (*function)(double);
+    double radius;
+        /* This is how far from the Y axis (on either side) the
+           function has significant value.  (You can use this to limit
+           how much of your domain you bother to compute the function
+           over).  
+        */
+    bool windowed;
+} filter;
+
+
+static filter Filters[] = {
+    { "point",     filter_box,       radius_point,     FALSE },
+    { "box",       filter_box,       radius_box,       FALSE },
+    { "triangle",  filter_triangle,  radius_triangle,  FALSE },
+    { "quadratic", filter_quadratic, radius_quadratic, FALSE },
+    { "cubic",     filter_cubic,     radius_cubic,     FALSE },
+    { "catrom",    filter_catrom,    radius_catrom,    FALSE },
+    { "mitchell",  filter_mitchell,  radius_mitchell,  FALSE },
+    { "gauss",     filter_gauss,     radius_gauss,     FALSE },
+    { "sinc",      filter_sinc,      radius_sinc,      TRUE  },
+    { "bessel",    filter_bessel,    radius_bessel,    TRUE  },
+    { "hanning",   filter_hanning,   radius_hanning,   FALSE },
+    { "hamming",   filter_hamming,   radius_hamming,   FALSE },
+    { "blackman",  filter_blackman,  radius_blackman,  FALSE },
+    { "kaiser",    filter_kaiser,    radius_kaiser,    FALSE },
+    { "normal",    filter_normal,    radius_normal,    FALSE },
+    { "hermite",   filter_hermite,   radius_hermite,   FALSE },
+    { "lanczos",   filter_lanczos,   radius_lanczos,   FALSE },
+   { NULL },
+};
+
+
+typedef double (*basicFunction_t)(double);
+
+
+/****************************/
+/****************************/
+/****** end of filters ******/
+/****************************/
+/****************************/
+
+
+
+enum scaleType {SCALE_SEPARATE, SCALE_BOXFIT, SCALE_BOXFILL, SCALE_PIXELMAX};
+    /* This is a way of specifying the output dimensions.
+
+       SCALE_SEPARATE means specify the horizontal and vertical scaling
+       separately.  One or both may be unspecified.
+
+       SCALE_BOXFIT means specify height and width of a box, and the
+       image must be scaled, preserving aspect ratio, to the largest
+       size that will fit in the box.  Some of the box may be empty.
+
+       SCALE_BOXFILL means specify height and width of a box, and the
+       image must be scaled, preserving aspect ratio, to the smallest
+       size that completely fills the box.  Some of the image may be
+       outside the box.
+
+       SCALE_PIXELMAX means specify the maximum number of pixels the result
+       should have and scale preserving aspect ratio and maximizing image
+       size.
+    */
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+     * in a form easy for the program to use.
+     */
+    const char * inputFileName;  /* Filespec of input file */
+    unsigned int nomix;
+    basicFunction_t filterFunction; /* NULL if not using resample method */
+    basicFunction_t windowFunction;
+        /* Meaningful only when filterFunction != NULL */
+    double filterRadius;           
+        /* Meaningful only when filterFunction != NULL */
+    enum scaleType scaleType;
+    /* 'xsize' and 'ysize' are numbers of pixels.  Their meaning depends upon
+       'scaleType'.  for SCALE_BOXFIT and SCALE_BOXFILL, they are the box 
+       dimensions.  For SCALE_SEPARATE, they are the separate dimensions, or
+       zero to indicate unspecified.  For SCALE_PIXELMAX, they are
+       meaningless.
+    */
+    unsigned int xsize;
+    unsigned int ysize;
+    /* 'xscale' and 'yscale' are meaningful only for scaleType == 
+       SCALE_SEPARATE and only where the corresponding xsize/ysize is
+       unspecified.  0.0 means unspecified.
+    */
+    float xscale;
+    float yscale;
+    /* 'pixels' is meaningful only for scaleType == SCALE_PIXELMAX */
+    unsigned int pixels; 
+    unsigned int linear;
+    unsigned int verbose;
+};
+
+
+
+static void
+lookupFilterByName(const char * const filtername,
+                   filter *     const filterP) {
+
+    unsigned int i;
+    bool found;
+
+    found = FALSE;  /* initial assumption */
+
+    for (i=0; Filters[i].name; ++i) {
+        if (strcmp(filtername, Filters[i].name) == 0) {
+            *filterP = Filters[i];
+            found = TRUE;
+        }
+    }
+    if (!found) {
+        unsigned int i;
+        char known_filters[1024];
+        strcpy(known_filters, "");
+        for (i = 0; Filters[i].name; ++i) {
+            const char * const name = Filters[i].name;
+            if (strlen(known_filters) + strlen(name) + 1 + 1 < 
+                sizeof(known_filters)) {
+                strcat(known_filters, name);
+                strcat(known_filters, " ");
+            }
+        }
+        pm_error("No such filter as '%s'.  Known filter names are: %s",
+                 filtername, known_filters);
+    }
+}
+
+
+
+static void
+processFilterOptions(unsigned int const         filterSpec,
+                     const char                 filterOpt[],
+                     unsigned int const         windowSpec,
+                     const char                 windowOpt[],
+                     struct cmdlineInfo * const cmdlineP) {
+
+    if (filterSpec) {
+        filter baseFilter;
+        lookupFilterByName(filterOpt, &baseFilter);
+        cmdlineP->filterFunction = baseFilter.function; 
+        cmdlineP->filterRadius   = baseFilter.radius;
+
+        if (windowSpec) {
+            filter windowFilter;
+            lookupFilterByName(windowOpt, &windowFilter);
+            
+            if (cmdlineP->windowFunction == filter_box)
+                cmdlineP->windowFunction = NULL;
+            else
+                cmdlineP->windowFunction = windowFilter.function;
+        } else {
+            /* Default for most filters is no window.  Those that _require_
+               a window function get Blackman.
+               */
+            if (baseFilter.windowed)
+                cmdlineP->windowFunction = filter_blackman;
+            else
+                cmdlineP->windowFunction = NULL;
+        }
+    } else
+        cmdlineP->filterFunction = NULL;
+}
+
+
+
+static void
+parseXyParms(int                  const argc, 
+             char **              const argv,
+             struct cmdlineInfo * const cmdlineP) {
+
+    /* parameters are box width (columns), box height (rows), and
+       optional filespec 
+    */
+    if (argc-1 < 2)
+        pm_error("You must supply at least two parameters with "
+                 "-xyfit/xyfill/xysize: "
+                 "x and y dimensions of the bounding box.");
+    else if (argc-1 > 3)
+        pm_error("Too many arguments.  With -xyfit/xyfill/xysize, "
+                 "you need 2 or 3 arguments.");
+    else {
+        char * endptr;
+        cmdlineP->xsize = strtol(argv[1], &endptr, 10);
+        if (strlen(argv[1]) > 0 && *endptr != '\0')
+            pm_error("horizontal size argument not an integer: '%s'", 
+                     argv[1]);
+        if (cmdlineP->xsize <= 0)
+            pm_error("horizontal size argument is not positive: %d", 
+                     cmdlineP->xsize);
+        
+        cmdlineP->ysize = strtol(argv[2], &endptr, 10);
+        if (strlen(argv[2]) > 0 && *endptr != '\0')
+            pm_error("vertical size argument not an integer: '%s'", 
+                     argv[2]);
+        if (cmdlineP->ysize <= 0)
+            pm_error("vertical size argument is not positive: %d", 
+                     cmdlineP->ysize);
+        
+        if (argc-1 < 3)
+            cmdlineP->inputFileName = "-";
+        else
+            cmdlineP->inputFileName = argv[3];
+    }
+}
+
+
+
+static void
+parseScaleParms(int                   const argc, 
+                char **               const argv,
+                struct cmdlineInfo  * const cmdlineP) {
+
+    /* parameters are scale factor and optional filespec */
+    if (argc-1 < 1)
+        pm_error("With no dimension options, you must supply at least "
+                 "one parameter: the scale factor.");
+    else {
+        cmdlineP->xscale = cmdlineP->yscale = atof(argv[1]);
+        
+        if (cmdlineP->xscale == 0.0)
+            pm_error("The scale parameter %s is not a positive number.",
+                     argv[1]);
+        else {
+            if (argc-1 < 2)
+                cmdlineP->inputFileName = "-";
+            else
+                cmdlineP->inputFileName = argv[2];
+        }
+    }
+}
+
+
+
+static void
+parseFilespecOnlyParms(int                   const argc, 
+                       char **               const argv,
+                       struct cmdlineInfo  * const cmdlineP) {
+
+    /* Only parameter allowed is optional filespec */
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else
+        cmdlineP->inputFileName = argv[1];
+}
+
+
+static void 
+parseCommandLine(int argc, 
+                 char ** argv, 
+                 struct cmdlineInfo  * const cmdlineP) {
+/* --------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+--------------------------------------------------------------------------*/
+    optEntry *option_def;
+    /* Instructions to optParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+  
+    unsigned int option_def_index;
+    unsigned int xyfit, xyfill;
+    int xsize, ysize, pixels;
+    int reduce;
+    float xscale, yscale;
+    const char *filterOpt, *window;
+    unsigned int filterSpec, windowSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "xsize",     OPT_UINT,    &xsize,     NULL,                 0);
+    OPTENT3(0, "width",     OPT_UINT,    &xsize,     NULL,                 0);
+    OPTENT3(0, "ysize",     OPT_UINT,    &ysize,     NULL,                 0);
+    OPTENT3(0, "height",    OPT_UINT,    &ysize,     NULL,                 0);
+    OPTENT3(0, "xscale",    OPT_FLOAT,   &xscale,    NULL,                 0);
+    OPTENT3(0, "yscale",    OPT_FLOAT,   &yscale,    NULL,                 0);
+    OPTENT3(0, "pixels",    OPT_UINT,    &pixels,    NULL,                 0);
+    OPTENT3(0, "reduce",    OPT_UINT,    &reduce,    NULL,                 0);
+    OPTENT3(0, "xysize",    OPT_FLAG,    NULL,       &xyfit,               0);
+    OPTENT3(0, "xyfit",     OPT_FLAG,    NULL,       &xyfit,               0);
+    OPTENT3(0, "xyfill",    OPT_FLAG,    NULL,       &xyfill,              0);
+    OPTENT3(0, "verbose",   OPT_FLAG,    NULL,       &cmdlineP->verbose,  0);
+    OPTENT3(0, "filter",    OPT_STRING,  &filterOpt, &filterSpec,          0);
+    OPTENT3(0, "window",    OPT_STRING,  &window,    &windowSpec,          0);
+    OPTENT3(0, "nomix",     OPT_FLAG,    NULL,       &cmdlineP->nomix,    0);
+    OPTENT3(0, "linear",    OPT_FLAG,    NULL,       &cmdlineP->linear,   0);
+  
+    /* Set the defaults. -1 = unspecified */
+
+    /* (Now that we're using ParseOptions3, we don't have to do this -1
+     * nonsense, but we don't want to risk screwing these complex 
+     * option compatibilities up, so we'll convert that later.
+     */
+    xsize = -1;
+    ysize = -1;
+    xscale = -1.0;
+    yscale = -1.0;
+    pixels = -1;
+    reduce = -1;
+    
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;   /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+    /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (cmdlineP->nomix && filterSpec) 
+        pm_error("You cannot specify both -nomix and -filter.");
+
+    processFilterOptions(filterSpec, filterOpt, windowSpec, window,
+                         cmdlineP);
+
+    if (xsize == 0)
+        pm_error("-xsize/width must be greater than zero.");
+    if (ysize == 0)
+        pm_error("-ysize/height must be greater than zero.");
+    if (xscale != -1.0 && xscale <= 0.0)
+        pm_error("-xscale must be greater than zero.");
+    if (yscale != -1.0 && yscale <= 0.0)
+        pm_error("-yscale must be greater than zero.");
+    if (reduce <= 0 && reduce != -1)
+        pm_error("-reduce must be greater than zero.");
+
+    if (xsize != -1 && xscale != -1)
+        pm_error("Cannot specify both -xsize/width and -xscale.");
+    if (ysize != -1 && yscale != -1)
+        pm_error("Cannot specify both -ysize/height and -yscale.");
+    
+    if ((xyfit || xyfill) &&
+        (xsize != -1 || xscale != -1 || ysize != -1 || yscale != -1 || 
+         reduce != -1 || pixels != -1) )
+        pm_error("Cannot specify -xyfit/xyfill/xysize with other "
+                 "dimension options.");
+    if (xyfit && xyfill)
+        pm_error("Cannot specify both -xyfit and -xyfill");
+    if (pixels != -1 && 
+        (xsize != -1 || xscale != -1 || ysize != -1 || yscale != -1 ||
+         reduce != -1) )
+        pm_error("Cannot specify -pixels with other dimension options.");
+    if (reduce != -1 && 
+        (xsize != -1 || xscale != -1 || ysize != -1 || yscale != -1) )
+        pm_error("Cannot specify -reduce with other dimension options.");
+
+    if (pixels == 0)
+        pm_error("-pixels must be greater than zero");
+
+    /* Get the program parameters */
+
+    if (xyfit || xyfill) {
+        cmdlineP->scaleType = xyfit ? SCALE_BOXFIT : SCALE_BOXFILL;
+        parseXyParms(argc, argv, cmdlineP);
+    } else if (reduce != -1) {
+        cmdlineP->scaleType = SCALE_SEPARATE;
+        parseFilespecOnlyParms(argc, argv, cmdlineP);
+        cmdlineP->xscale = cmdlineP->yscale = 
+            ((double) 1.0) / ((double) reduce);
+        pm_message("reducing by %d gives scale factor of %f.", 
+                   reduce, cmdlineP->xscale);
+    } else if (pixels != -1) {
+        cmdlineP->scaleType = SCALE_PIXELMAX;
+        parseFilespecOnlyParms(argc, argv, cmdlineP);
+        cmdlineP->pixels = pixels;
+    } else if (xsize == -1 && xscale == -1 && ysize == -1 && yscale == -1
+               && pixels == -1 && reduce == -1) {
+        cmdlineP->scaleType = SCALE_SEPARATE;
+        parseScaleParms(argc, argv, cmdlineP);
+        cmdlineP->xsize = cmdlineP->ysize = 0;
+    } else {
+        cmdlineP->scaleType = SCALE_SEPARATE;
+        parseFilespecOnlyParms(argc, argv, cmdlineP);
+        cmdlineP->xsize = xsize == -1 ? 0 : xsize;
+        cmdlineP->ysize = ysize == -1 ? 0 : ysize;
+        cmdlineP->xscale = xscale == -1.0 ? 0.0 : xscale;
+        cmdlineP->yscale = yscale == -1.0 ? 0.0 : yscale;
+    }
+}
+
+
+
+static void 
+computeOutputDimensions(struct cmdlineInfo  const cmdline, 
+                        int                 const rows, 
+                        int                 const cols, 
+                        int *               const newrowsP, 
+                        int *               const newcolsP) {
+
+    switch(cmdline.scaleType) {
+    case SCALE_PIXELMAX: {
+        if (rows * cols <= cmdline.pixels) {
+            *newrowsP = rows;
+            *newcolsP = cols;
+        } else {
+            const double scale =
+                sqrt( (float) cmdline.pixels / ((float) cols * (float) rows));
+            *newrowsP = rows * scale;
+            *newcolsP = cols * scale;
+        }
+    } break;
+    case SCALE_BOXFIT:
+    case SCALE_BOXFILL: {
+        double const aspect_ratio = (float) cols / (float) rows;
+        double const box_aspect_ratio = 
+            (float) cmdline.xsize / (float) cmdline.ysize;
+        
+        if ((box_aspect_ratio > aspect_ratio && 
+             cmdline.scaleType == SCALE_BOXFIT) ||
+            (box_aspect_ratio < aspect_ratio &&
+             cmdline.scaleType == SCALE_BOXFILL)) {
+            *newrowsP = cmdline.ysize;
+            *newcolsP = *newrowsP * aspect_ratio + 0.5;
+        } else {
+            *newcolsP = cmdline.xsize;
+            *newrowsP = *newcolsP / aspect_ratio + 0.5;
+        }
+    } break;
+    case SCALE_SEPARATE: {
+        if (cmdline.xsize)
+            *newcolsP = cmdline.xsize;
+        else if (cmdline.xscale)
+            *newcolsP = cmdline.xscale * cols + .5;
+        else if (cmdline.ysize)
+            *newcolsP = cols * ((float) cmdline.ysize/rows) +.5;
+        else
+            *newcolsP = cols;
+
+        if (cmdline.ysize)
+            *newrowsP = cmdline.ysize;
+        else if (cmdline.yscale)
+            *newrowsP = cmdline.yscale * rows +.5;
+        else if (cmdline.xsize)
+            *newrowsP = rows * ((float) cmdline.xsize/cols) +.5;
+        else
+            *newrowsP = rows;
+    }
+    }
+
+    /* If the calculations above yielded (due to rounding) a zero 
+     * dimension, we fudge it up to 1.  We do this rather than considering
+     * it a specification error (and dying) because it's friendlier to 
+     * automated processes that work on arbitrary input.  It saves them
+     * having to check their numbers to avoid catastrophe.
+     */
+  
+    if (*newcolsP < 1) *newcolsP = 1;
+    if (*newrowsP < 1) *newrowsP = 1;
+}
+
+
+
+
+/****************************/
+/****************************/
+/******* resampling *********/
+/****************************/
+/****************************/
+
+/* The resample code was inspired by Paul Heckbert's zoom program.
+** http://www.cs.cmu.edu/~ph/zoom
+*/
+
+struct filterFunction {
+/*----------------------------------------------------------------------------
+   A function to convolve with the samples.
+-----------------------------------------------------------------------------*/
+    basicFunction_t basicFunction;
+        /* The basic shape of the function.  Its horizontal scale is
+           designed to filter out frequencies greater than 1.
+        */
+    basicFunction_t windowFunction;
+        /* A function to multiply by basicFunction().  NULL if none. */
+    double windowScaler;
+        /* Factor by which to compress windowFunction() horizontally */
+    double horizontalScaler;
+        /* Factor by which to compress basicFunction() *
+           windowFunction horizontally.  Note that compressing
+           horizontally in the sample domain is equivalent to
+           expanding horizontally (and shrinking vertically) in the
+           frequency domain.  I.e. values less than unity have the
+           effect of chopping out high frequencies.
+        */
+    double radius;
+        /* A final filter.  filterFunction(x) is zero for |x| > radius
+           regardless of what the rest of the members say.
+
+           Implementation note:  This is important because windowFunction(),
+           out of laziness, doesn't do the whole job of windowing.  It is
+           not zero beyond the cutoff points as it should be.  If not for
+           that, radius would only be a hint to describe what the other
+           members already do, so the convolver knows where to stop.
+        */
+};
+
+typedef struct {
+    /* A term of the linear combination of input rows that makes up an
+       output row.  I.e. an input row and its weight.
+
+       Alternatively, the analogous thing for a column.
+    */
+    int    position;    /* Row/column number in the input image */
+    double weight;      /* Weight to be given to that row/col.  In [0, 1]. */
+} WEIGHT;
+
+typedef struct {
+    /* A description of the linear combination of input rows that
+       generates a particular output row.  An output row is a weighted
+       average of some input rows.  E.g. Row 2 of the output might be
+       composed of 50% of Row 2 of the input and 50% of Row 3 of the
+       input.
+
+       Alternatively, the analogous thing for columns.
+    */
+    unsigned int nWeight;
+        /* Number of elements in 'Weight'.  They're consecutive, starting
+           at index 0.
+        */
+    unsigned int allocWeight;
+        /* Number of allocated frames in 'Weight' */ 
+    WEIGHT *Weight;
+        /* The terms of the linear combination.  Has 'nWeight' elements. 
+           The coefficients (weights) of each add up to unity.
+        */
+} WLIST;
+
+typedef struct {
+    /* This identifies a row of the input image. */
+    int rowNumber;
+        /* The row number in the input image of the row.
+           -1 means no row.
+        */
+    tuple *tuplerow;  
+        /* The tuples of the row.
+           If rowNumber = -1, these are arbitrary, but allocated, tuples.
+        */
+} SCANLINE;
+
+typedef struct {
+    /* A vertical window of a raster */
+    int width;    /* Width of the window, in columns */
+    int height;   /* Height of the window, in rows */
+    SCANLINE *line;
+        /* An array of 'height' elements, malloced.
+           This identifies the lines of the input image that compose the
+           window.  The index order is NOT the order of the rows in the
+           image.  E.g. line[0] isn't always the topmost row of the window.
+           Rather, the rows are arranged in a cycle and you have to know
+           indpendently where the topmost one is.  E.g. the rows of a 5
+           line window with topmost row at index 3 might be:
+
+              line[0] = Row 24
+              line[1] = Row 25
+              line[2] = Row 26
+              line[3] = Row 22
+              line[4] = Row 23
+        */
+} SCAN;
+
+
+
+static int
+appendWeight(WLIST * const WList,
+             int     const index,
+             double  const weight) {
+/*----------------------------------------------------------------------------
+   Add a weighting of 'weight' for index 'index' to the weight list
+   'WList'.
+-----------------------------------------------------------------------------*/
+    if (weight == 0.0) {
+        /* A weight of 0 in the list is redundant, so we don't add it.
+           A weight entry says "Add W fraction of the pixel at index I,"
+           so where W is 0, it's the same as not having the entry at all.
+        */
+    } else {
+        unsigned int const n = WList->nWeight;
+
+        assert(WList->allocWeight >= n+1);
+        
+        WList->Weight[n].position = index;
+        WList->Weight[n].weight   = weight;
+        ++WList->nWeight;
+    }
+    return 0;
+}
+
+
+
+static sample
+floatToSample(double const value,
+              sample const maxval) {
+
+    /* Take care here, the conversion of any floating point value <=
+       -1.0 to an unsigned type is _undefined_.  See ISO 9899:1999
+       section 6.3.1.4.  Not only is it undefined it also does the
+       wrong thing in actual practice, EG on Darwin PowerPC (my iBook
+       running OS X) negative values clamp to maxval.  We get negative
+       values because some of the filters (EG catrom) have negative
+       weights.  
+    */
+
+    return MIN(maxval, (sample)(MAX(0.0, (value + 0.5))));
+}
+
+
+
+static void
+initWeightList(WLIST *      const weightListP,
+               unsigned int const maxWeights) {
+
+    weightListP->nWeight = 0;
+    weightListP->allocWeight = maxWeights;
+    MALLOCARRAY(weightListP->Weight, maxWeights);
+    if (weightListP->Weight == NULL)
+        pm_error("Out of memory allocating a %u-element weight list.",
+                 maxWeights);
+}
+
+
+
+static void
+createWeightList(unsigned int          const targetPos,
+                 unsigned int          const sourceSize,
+                 double                const scale,
+                 struct filterFunction       filter,
+                 WLIST *               const weightListP) {
+/*----------------------------------------------------------------------------
+   Create a weight list for computing target pixel number 'targetPos' from
+   a set of source pixels.  These pixels are a line of pixels either 
+   horizontally or vertically.  The weight list is a list of weights to give
+   each source pixel in the set.
+   
+   The source pixel set is a window of source pixels centered on some
+   point.  The weights are defined by the function 'filter' of
+   the position within the window, and normalized to add up to 1.0.
+   Technically, the window is infinite, but we know that the filter
+   function is zero beyond a certain distance from the center of the
+   window.
+
+   For example, assume 'targetPos' is 5.  That means we're computing weights
+   for either Column 5 or Row 5 of the target image.  Assume it's Column 5.
+   Assume 'radius' is 1.  That means a window of two pixels' worth of a
+   source row determines the color of the Column 5 pixel of a target
+   row.  Assume 'filter' is a triangle function -- 1 at 0, sloping
+   down to 0 at -1 and 1.
+
+   Now assume that the scale factor is 2 -- the target image will be
+   twice the size of the source image.  That means the two-pixel-wide
+   window of the source row that affects Column 5 of the target row
+   (centered at target position 5.5) goes from position 1.75 to
+   3.75, centered at 2.75.  That means the window covers 1/4 of
+   Column 1, all of Column 2, and 3/4 of Column 3 of the source row.
+
+   We want to calculate 3 weights, one to be applied to each source pixel
+   in computing the target pixel.  Ideally, we would compute the average
+   height of the filter function over each source pixel region.  But 
+   that's too hard.  So we approximate by assuming that the filter function
+   is constant within each region, at the value the function has at the
+   _center_ of the region.
+
+   So for the Column 1 region, which goes from 1.75 to 2.00, centered
+   -.875 from the center of the window, we assume a constant function
+   value of triangle(-.875), which equals .125.  For the 2.00-3.00
+   region, we get triangle(-.25) = .75.  For the 3.00-3.75 region, we
+   get triangle(.125) = .875.  So the weights for the 3 regions, which
+   we get by multiplying this constant function value by the width of
+   the region and normalizing so they add up to 1 are:
+
+      Source Column 1:  .125*.25 / 1.4375 = .022
+      Source Column 2:  .75*1.00 / 1.4375 = .521
+      Source Column 3:  .875*.75 / 1.4375 = .457
+
+   These are the weights we return.  Thus, if we assume that the source
+   pixel 1 has value 10, source pixel 2 has value 20, and source pixel 3
+   has value 30, Caller would compute target pixel 5's value as
+
+      10*.022 + 20*.521 + 30*.457 = 24
+
+-----------------------------------------------------------------------------*/
+    /* 'windowCenter', is the continous position within the source of
+       the center of the window that will influence target pixel
+       'targetPos'.  'left' and 'right' are the edges of the window.
+       'leftPixel' and 'rightPixel' are the pixel positions of the
+       pixels at the edges of that window.  Note that if we're
+       doing vertical weights, "left" and "right" mean top and
+       bottom.  
+    */
+    double const windowCenter = ((double)targetPos + 0.5) / scale;
+    double left = MAX(0.0, windowCenter - filter.radius - EPSILON);
+    unsigned int const leftPixel = floor(left);
+    double right = MIN((double)sourceSize - EPSILON, 
+                       windowCenter + filter.radius + EPSILON);
+    unsigned int const rightPixel = floor(right);
+
+    double norm;
+    unsigned int j;
+
+    initWeightList(weightListP, rightPixel - leftPixel + 1);
+
+    /* calculate weights */
+    norm = 0.0;  /* initial value */
+
+    for (j = leftPixel; j <= rightPixel; ++j) {
+        /* Calculate the weight that source pixel 'j' will have in the 
+           value of target pixel 'targetPos'.
+        */
+        double const regionLeft   = MAX(left, (double)j);
+        double const regionRight  = MIN(right, (double)(j + 1));
+        double const regionWidth  = regionRight - regionLeft;
+        double const regionCenter = (regionRight + regionLeft) / 2;
+        double const dist         = regionCenter - windowCenter;
+        double weight;
+
+        weight = filter.basicFunction(filter.horizontalScaler * dist);
+        if (filter.windowFunction)
+            weight *= filter.windowFunction(
+                filter.horizontalScaler * filter.windowScaler * dist);
+
+        assert(regionWidth <= 1.0);
+        weight *= regionWidth;
+        norm += weight;
+        appendWeight(weightListP, j, weight);
+    }
+
+    if (norm == 0.0)
+        pm_error("INTERNAL ERROR: No source pixels contribute to target "
+                 "pixel %u", targetPos);
+
+    /* normalize the weights so they add up to 1.0 */
+    if (norm != 1.0) {
+        unsigned int n;
+        for (n = 0; n < weightListP->nWeight; ++n) {
+            weightListP->Weight[n].weight /= norm;
+        }
+    }
+}
+
+
+
+static void
+createWeightListSet(unsigned int          const sourceSize,
+                    unsigned int          const targetSize,
+                    struct filterFunction const filterFunction,
+                    WLIST **              const weightListSetP) {
+/*----------------------------------------------------------------------------
+   Create the set of weight lists that will effect the resample.
+
+   This is where the actual work of resampling gets done.
+
+   The weight list set is a bunch of factors one can multiply by the
+   pixels in a region to effect a resampling.  Multiplying by these
+   factors effects all of the following transformations on the
+   original pixels:
+   
+   1) Filter out any frequencies that are artifacts of the
+      original sampling.  We assume a perfect sampling was done,
+      which means the original continuous dataset had a maximum
+      frequency of 1/2 of the original sample rate and anything
+      above that is an artifact of the sampling.  So we filter out
+      anything above 1/2 of the original sample rate (sample rate
+      == pixel resolution).
+      
+   2) Filter out any frequencies that are too high to be captured
+      by the new sampling -- i.e. frequencies above 1/2 the new
+      sample rate.  This is the information we must lose due to low
+      sample rate.
+      
+   3) Sample the result at the new sample rate.
+
+   We do all three of these steps in a single convolution of the
+   original pixels.  Steps (1) and (2) can be combined into a
+   single frequency domain rectangle function.  A frequency domain
+   rectangle function is a pixel domain sinc function, which is
+   what we assume 'filterFunction' is.  We get Step 3 by computing
+   the convolution only at the new sample points.
+   
+   I don't know what any of this means when 'filterFunction' is
+   not sinc.  Maybe it just means some approximation or additional
+   filtering steps are happening.
+-----------------------------------------------------------------------------*/
+    double const scale = (double)targetSize / sourceSize;
+    WLIST *weightListSet;  /* malloc'ed */
+    unsigned int targetPos;
+
+    MALLOCARRAY_NOFAIL(weightListSet, targetSize);
+    
+    for (targetPos = 0; targetPos < targetSize; ++targetPos)
+        createWeightList(targetPos, sourceSize, scale, filterFunction,
+                         &weightListSet[targetPos]);
+
+    *weightListSetP = weightListSet;
+}
+
+
+
+static struct filterFunction
+makeFilterFunction(double          const scale,
+                   basicFunction_t       basicFunction,
+                   double          const basicRadius,
+                   basicFunction_t       windowFunction) {
+/*----------------------------------------------------------------------------
+   Create a function to convolve with the samples (so it isn't actually
+   a filter function, but the Fourier transform of a filter function.
+   A filter function is something you multiply by in the frequency domain)
+   to create a function from which one can resample.
+
+   Convolving with this function will achieve two goals:
+
+   1) filter out high frequencies that are artifacts of the original
+      sampling (i.e. the turning of a continuous function into a staircase
+      function);
+   2) filter out frequencies higher than half the resample rate, so that
+      the resample will be a perfect sampling of it, and not have aliasing.
+
+
+   To make the calculation even more efficient, we take advantage
+   of the fact that the weight list doesn't depend on the
+   particular old and new sample rates at all except -- all that's
+   important is their ratio (which is 'scale').  So we assume the
+   original sample rate is 1 and the new sample rate is 'scale'.
+
+-----------------------------------------------------------------------------*/
+    double const freqLimit = MIN(1.0, scale);
+        /* We're going to cut out any frequencies above this, to accomplish
+           Steps (1) and (2) above.
+        */
+
+    struct filterFunction retval;
+
+    retval.basicFunction = basicFunction;
+    retval.windowFunction = windowFunction;
+    
+    retval.horizontalScaler = freqLimit;
+
+    /* Our 'windowFunction' argument is a function normalized to the
+       domain (-1, 1).  We need to scale it horizontally to fit the
+       basic filter function.  We assume the radius of the filter
+       function is the area to which the window should fit (i.e. zero
+       beyond the radius, nonzero inside the radius).  But that's
+       really a misuse of radius, because radius is supposed to be
+       just the distance beyond which we can assume for convenience
+       that the filter function is zero, possibly giving up some
+       precision.
+
+       But note that 'windowFunction' isn't zero outside (-1, 1), even
+       though the actual window function is supposed to be.  Hence,
+       scaling the window function exactly to the radius stops our
+       calculations from noticing the wrong values outside (-1, 1) --
+       we'll never use them.
+    */
+    retval.windowScaler = 1/basicRadius;
+
+    retval.radius = basicRadius / retval.horizontalScaler;
+
+    return retval;
+}
+                   
+
+                   
+static void
+destroyWeightListSet(WLIST *      const weightListSet, 
+                     unsigned int const size) {
+
+    unsigned int i;
+
+    for (i = 0; i < size; ++i)
+        free(weightListSet[i].Weight);
+
+    free(weightListSet);
+}
+
+
+
+static void
+createScanBuf(struct pam * const pamP,
+              double       const maxRowWeights,
+              bool         const verbose,
+              SCAN *       const scanbufP) {
+
+    SCAN scanbuf;
+    unsigned int lineNumber;
+
+    scanbuf.width = pamP->width;
+    scanbuf.height = maxRowWeights;
+    MALLOCARRAY_NOFAIL(scanbuf.line, scanbuf.height);
+  
+    for (lineNumber = 0; lineNumber < scanbuf.height; ++lineNumber) {
+        scanbuf.line[lineNumber].rowNumber = -1;
+        scanbuf.line[lineNumber].tuplerow = pnm_allocpamrow(pamP);
+    }
+  
+    if (verbose)
+        pm_message("scanline buffer: %d lines of %d pixels", 
+                   scanbuf.height, scanbuf.width);
+
+    *scanbufP = scanbuf;
+}
+
+
+
+static void
+destroyScanbuf(SCAN const scanbuf) {
+
+    unsigned int lineNumber;
+
+    for (lineNumber = 0; lineNumber < scanbuf.height; ++lineNumber)
+        pnm_freepamrow(scanbuf.line[lineNumber].tuplerow);
+
+    free(scanbuf.line);
+}
+
+
+
+static void
+resampleDimensionMessage(struct pam * const inpamP,
+                         struct pam * const outpamP) {
+
+    pm_message ("resampling from %d*%d to %d*%d (%f, %f)", 
+                inpamP->width, inpamP->height, 
+                outpamP->width, outpamP->height,
+                (double)outpamP->width/inpamP->width, 
+                (double)outpamP->height/inpamP->height);
+}
+
+
+
+static void
+addInPixel(const struct pam * const pamP,
+           tuple              const tuple,
+           float              const weight,
+           bool               const haveOpacity,
+           unsigned int       const opacityPlane,
+           double *           const accum) {
+/*----------------------------------------------------------------------------
+  Add into *accum the values from the tuple 'tuple', weighted by
+  'weight'.
+
+  Iff 'haveOpacity', Plane 'opacityPlane' of the tuple is an opacity
+  (alpha, transparency) plane.
+-----------------------------------------------------------------------------*/
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        sample adjustedForOpacity;
+        
+        if (haveOpacity && plane != opacityPlane) {
+            float const opacity = (float)tuple[opacityPlane]/pamP->maxval;
+            float const unadjusted = (float)tuple[plane]/pamP->maxval;
+
+            adjustedForOpacity = 
+                floatToSample(unadjusted * opacity, pamP->maxval);
+        } else
+            adjustedForOpacity = tuple[plane];
+        
+        accum[plane] += (double)adjustedForOpacity * weight;
+    }
+}
+
+
+
+static void
+generateOutputTuple(const struct pam * const pamP,
+                    double             const accum[],
+                    bool               const haveOpacity, 
+                    unsigned int       const opacityPlane,
+                    tuple *            const tupleP) {
+/*----------------------------------------------------------------------------
+  Convert the values accum[] accumulated for a pixel by
+  outputOneResampledRow() to a bona fide PAM tuple as *tupleP,
+  as described by *pamP.
+-----------------------------------------------------------------------------*/
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        float opacityAdjustedSample;
+
+        if (haveOpacity && plane != opacityPlane) {
+            if (accum[opacityPlane] < EPSILON) {
+                assert(accum[plane] < EPSILON);
+                opacityAdjustedSample = 0.0;
+            } else 
+                opacityAdjustedSample = accum[plane] / accum[opacityPlane];
+        } else
+            opacityAdjustedSample = accum[plane];
+
+        (*tupleP)[plane] = floatToSample(opacityAdjustedSample, pamP->maxval);
+    }
+}
+
+
+
+static void
+outputOneResampledRow(const struct pam * const outpamP,
+                      SCAN               const scanbuf,
+                      WLIST              const YW,
+                      const WLIST *      const XWeight,
+                      tuple *            const line,
+                      double *           const accum) {
+/*----------------------------------------------------------------------------
+   From the data in 'scanbuf' and weights in 'YW' and 'XWeight', 
+   generate one output row for the image described by *outpamP and
+   output it.
+
+   An output pixel is a weighted average of the pixels in a certain
+   rectangle of the input.  'YW' and 'XWeight' describe those weights
+   for each column of the row we are to output.
+
+   'line' and 'accum' are just working space that Caller provides us
+   with to save us the time of allocating it.  'line' is at least big
+   enough to hold an output row; 'weight' is at least outpamP->depth
+   big.
+-----------------------------------------------------------------------------*/
+    unsigned int col;
+
+    bool haveOpacity;           /* There is an opacity plane */
+    unsigned int opacityPlane;  /* Plane number of opacity plane, if any */
+
+    pnm_getopacity(outpamP, &haveOpacity, &opacityPlane);
+
+    for (col = 0; col < outpamP->width; ++col) {
+        WLIST const XW = XWeight[col];
+
+        unsigned int i;
+        {
+            unsigned int plane;
+            for (plane = 0; plane < outpamP->depth; ++plane)
+                accum[plane] = 0.0;
+        }
+        
+        for (i = 0; i < YW.nWeight; ++i) {
+            int   const yp   = YW.Weight[i].position;
+            float const yw   = YW.Weight[i].weight;
+            int   const slot = yp % scanbuf.height;
+
+            unsigned int j;
+            
+            for (j = 0; j < XW.nWeight; ++j) {
+                int   const xp    = XW.Weight[j].position;
+                tuple const tuple = scanbuf.line[slot].tuplerow[xp];
+                
+                addInPixel(outpamP, tuple, yw * XW.Weight[j].weight, 
+                           haveOpacity, opacityPlane,
+                           accum);
+            }
+        }
+        generateOutputTuple(outpamP, accum, haveOpacity, opacityPlane, 
+                            &line[col]);
+    }
+    pnm_writepamrow(outpamP, line);
+}
+
+
+
+static bool
+scanbufContainsTheRows(SCAN  const scanbuf,
+                       WLIST const rowWeights) {
+/*----------------------------------------------------------------------------
+   Return TRUE iff scanbuf 'scanbuf' contains every row mentioned in
+   'rowWeights'.
+
+   It might contain additional rows besides.
+-----------------------------------------------------------------------------*/
+    bool missingRow;
+    unsigned int i;
+    
+    for (i = 0, missingRow = FALSE;
+         i < rowWeights.nWeight && !missingRow;
+        ++i) {
+        unsigned int const inputRow = rowWeights.Weight[i].position;
+        unsigned int const slot = inputRow % scanbuf.height;
+            /* This is the number of the slot in the scanbuf that would
+               have the input row in question if the scanbuf has the
+               row at all.
+            */
+        if (scanbuf.line[slot].rowNumber != inputRow) {
+            /* Nope, this slot has some other row or no row at all.
+               So the row we're looking for isn't in the scanbuf.
+            */
+            missingRow = TRUE;
+        }
+    }
+    return !missingRow;
+}
+
+
+
+static void
+createWeightLists(struct pam *     const inpamP,
+                  struct pam *     const outpamP,
+                  basicFunction_t  const filterFunction,
+                  double           const filterRadius,
+                  basicFunction_t  const windowFunction,
+                  WLIST **         const horizWeightP,
+                  WLIST **         const vertWeightP,
+                  unsigned int *   const maxRowWeightsP) {
+/*----------------------------------------------------------------------------
+   This is the function that actually does the resampling.  Note that it
+   does it without ever looking at the source or target pixels!  It produces
+   a simple set of numbers that Caller can blindly apply to the source 
+   pixels to get target pixels.
+-----------------------------------------------------------------------------*/
+    struct filterFunction horizFilter, vertFilter;
+
+    horizFilter = makeFilterFunction(
+        (double)outpamP->width/inpamP->width,
+        filterFunction, filterRadius, windowFunction);
+
+    createWeightListSet(inpamP->width, outpamP->width, horizFilter, 
+                        horizWeightP);
+    
+    vertFilter = makeFilterFunction(
+        (double)outpamP->height/inpamP->height,
+        filterFunction, filterRadius, windowFunction);
+
+    createWeightListSet(inpamP->height, outpamP->height, vertFilter, 
+                        vertWeightP);
+
+    *maxRowWeightsP = ceil(2.0*(vertFilter.radius+EPSILON) + 1 + EPSILON);
+}
+
+
+
+static void
+resample(struct pam *     const inpamP,
+         struct pam *     const outpamP,
+         basicFunction_t  const filterFunction,
+         double           const filterRadius,
+         basicFunction_t  const windowFunction,
+         bool             const verbose,
+         bool             const linear) {
+/*---------------------------------------------------------------------------
+  Resample the image in the input file, described by *inpamP,
+  so as to create the image in the output file, described by *outpamP.
+  
+  Input and output differ by height, width, and maxval only.
+
+  Use the resampling filter function 'filterFunction', applied over
+  radius 'filterRadius'.
+  
+  The input file is positioned past the header, to the beginning of the
+  raster.  The output file is too.
+---------------------------------------------------------------------------*/
+    int inputRow, outputRow;
+    WLIST * horizWeight;
+    WLIST * vertWeight;
+    SCAN scanbuf;
+    unsigned int maxRowWeights;
+
+    tuple * line;
+        /* This is just work space for outputOneSampleRow() */
+    double * weight;
+        /* This is just work space for outputOneSampleRow() */
+
+    if (linear)
+        pm_error("You cannot use the resampling scaling method on "
+                 "linear input.");
+  
+    createWeightLists(inpamP, outpamP, filterFunction, filterRadius,
+                      windowFunction, &horizWeight, &vertWeight,
+                      &maxRowWeights);
+
+    createScanBuf(inpamP, maxRowWeights, verbose, &scanbuf);
+
+    if (verbose)
+        resampleDimensionMessage(inpamP, outpamP);
+
+    line = pnm_allocpamrow(outpamP);
+    MALLOCARRAY_NOFAIL(weight, outpamP->depth);
+
+    outputRow = 0;
+    for (inputRow = 0; inputRow < inpamP->height; ++inputRow) {
+        bool needMoreInput;
+            /* We've output as much as we can using the rows that are in
+               the scanbuf; it's time to move the window.  Or fill it in
+               the first place.
+            */
+        unsigned int scanbufSlot;
+
+        /* Read source row; add it to the scanbuf */
+        scanbufSlot = inputRow % scanbuf.height;
+        scanbuf.line[scanbufSlot].rowNumber = inputRow;
+        pnm_readpamrow(inpamP, scanbuf.line[scanbufSlot].tuplerow);
+
+        /* Output all the rows we can make out of the current contents of
+           the scanbuf.  Might be none.
+        */
+        needMoreInput = FALSE;  /* initial assumption */
+        while (outputRow < outpamP->height && !needMoreInput) {
+            WLIST const rowWeights = vertWeight[outputRow];
+                /* The description of what makes up our current output row;
+                   i.e. what fractions of which input rows combine to create
+                   this output row.
+                */
+            assert(rowWeights.nWeight <= scanbuf.height); 
+
+            if (scanbufContainsTheRows(scanbuf, rowWeights)) {
+                outputOneResampledRow(outpamP, scanbuf, rowWeights, 
+                                      horizWeight, line, weight);
+                ++outputRow;
+            } else
+                needMoreInput = TRUE;
+        }
+    }
+
+    if (outputRow != outpamP->height)
+        pm_error("INTERNAL ERROR: assembled only %u of the required %u "
+                 "output rows.", outputRow, outpamP->height);
+
+    pnm_freepamrow(line);
+    destroyScanbuf(scanbuf);
+    destroyWeightListSet(horizWeight, outpamP->width);
+    destroyWeightListSet(vertWeight, outpamP->height);
+}
+
+
+/****************************/
+/****************************/
+/**** end of resampling *****/
+/****************************/
+/****************************/
+
+
+
+static void
+zeroNewRow(struct pam * const pamP,
+           tuplen *     const tuplenrow) {
+    
+    unsigned int col;
+
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+
+        for (plane = 0; plane < pamP->depth; ++plane)
+            tuplenrow[col][plane] = 0.0;
+    }
+}
+
+
+
+static void
+accumOutputCol(struct pam * const pamP,
+               tuplen       const intuplen,
+               float        const fraction, 
+               tuplen       const accumulator) {
+/*----------------------------------------------------------------------------
+   Add fraction 'fraction' of the pixel indicated by 'intuplen' to the
+   pixel accumulator 'accumulator'.
+
+   'intuplen' and 'accumulator' are not a standard libnetpbm tuplen.
+   It is proportional to light intensity.  The foreground color
+   component samples are proportional to light intensity, and have
+   opacity factored in.
+-----------------------------------------------------------------------------*/
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane)
+        accumulator[plane] += fraction * intuplen[plane];
+}
+
+
+
+static void
+horizontalScale(tuplen *     const inputtuplenrow, 
+                tuplen *     const newtuplenrow,
+                struct pam * const inpamP,
+                struct pam * const outpamP,
+                float        const xscale,
+                float *      const stretchP) {
+/*----------------------------------------------------------------------------
+  Take the input row 'inputtuplenrow', decribed by *inpamP, and scale
+  it by a factor of 'xscale', to create the output row 'newtuplenrow',
+  described by *outpamP.
+
+  Due to arithmetic imprecision, we may have to stretch slightly the
+  contents of the last pixel of the output row to make a full pixel.
+  Return as *stretchP the fraction of a pixel by which we had to
+  stretch in this way.
+
+  Assume maxval and depth of input and output are the same.
+-----------------------------------------------------------------------------*/
+    float fraccoltofill, fraccolleft;
+    unsigned int col;
+    unsigned int newcol;
+
+    newcol = 0;
+    fraccoltofill = 1.0;  /* Output column is "empty" now */
+
+    zeroNewRow(outpamP, newtuplenrow);
+
+    for (col = 0; col < inpamP->width; ++col) {
+        /* Process one tuple from input ('inputtuplenrow') */
+        fraccolleft = xscale;
+        /* Output all columns, if any, that can be filled using information
+           from this input column, in addition to what's already in the output
+           column.
+        */
+        while (fraccolleft >= fraccoltofill) {
+            /* Generate one output pixel in 'newtuplerow'.  It will
+               consist of anything accumulated from prior input pixels
+               in accumulator[], plus a fraction of the current input
+               pixel.  
+            */
+            assert(newcol < outpamP->width);
+            accumOutputCol(inpamP, inputtuplenrow[col], fraccoltofill,
+                           newtuplenrow[newcol]);
+
+            fraccolleft -= fraccoltofill;
+
+            /* Set up to start filling next output column */
+            ++newcol;
+            fraccoltofill = 1.0;
+        }
+        /* There's not enough left in the current input pixel to fill up 
+           a whole output column, so just accumulate the remainder of the
+           pixel into the current output column.  Due to rounding, we may
+           have a tiny bit of pixel left and have run out of output pixels.
+           In that case, we throw away what's left.
+        */
+        if (fraccolleft > 0.0 && newcol < outpamP->width) {
+            accumOutputCol(inpamP, inputtuplenrow[col], fraccolleft,
+                           newtuplenrow[newcol]);
+            fraccoltofill -= fraccolleft;
+        }
+    }
+
+    if (newcol < outpamP->width-1 || newcol > outpamP->width)
+        pm_error("Internal error: last column filled is %d, but %d "
+                 "is the rightmost output column.",
+                 newcol, outpamP->width-1);
+
+    if (newcol < outpamP->width) {
+        /* We were still working on the last output column when we 
+           ran out of input columns.  This would be because of rounding
+           down, and we should be missing only a tiny fraction of that
+           last output column.  Just fill in the missing color with the
+           color of the rightmost input pixel.
+        */
+        accumOutputCol(inpamP, inputtuplenrow[inpamP->width-1], 
+                       fraccoltofill, newtuplenrow[newcol]);
+        
+        *stretchP = fraccoltofill;
+    } else 
+        *stretchP = 0.0;
+}
+
+
+
+static void
+zeroAccum(struct pam * const pamP,
+          tuplen *     const accumulator) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        unsigned int col;
+        for (col = 0; col < pamP->width; ++col)
+            accumulator[col][plane] = 0.0;
+    }
+}
+
+
+
+static void
+accumOutputRow(struct pam * const pamP,
+               tuplen *     const tuplenrow, 
+               float        const fraction, 
+               tuplen *     const accumulator) {
+/*----------------------------------------------------------------------------
+   Take 'fraction' times the samples in row 'tuplenrow' and add it to 
+   'accumulator' in the same way as accumOutputCol().
+
+   'fraction' is less than 1.0.
+-----------------------------------------------------------------------------*/
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        unsigned int col;
+        for (col = 0; col < pamP->width; ++col)
+            accumulator[col][plane] += fraction * tuplenrow[col][plane];
+    }
+}
+
+
+
+static void
+readARow(struct pam *             const pamP,
+         tuplen *                 const tuplenRow,
+         const pnm_transformMap * const transform) {
+/*----------------------------------------------------------------------------
+  Read a row from the input file described by *pamP, as values (for the
+  foreground color) proportional to light intensity, with opacity
+  included.
+
+  By contrast, a simple libnetpbm read would give the same numbers you
+  find in a PAM: gamma-adjusted values for the foreground color
+  component and scaled as if opaque.  The latter means that full red
+  would have a red intensity of 1.0 even if the pixel is only 75%
+  opaque.  We, on the other hand, would return red intensity of .75 in
+  that case.
+
+  The opacity plane we return is the same as a simple libnetpbm read
+  would return.
+
+  We ASSUME that the transform 'transform' is that necessary to effect
+  the conversion to intensity-linear values and normalize.  If it is
+  NULL, we ASSUME that they already are intensity-proportional and just
+  need to be normalized.
+-----------------------------------------------------------------------------*/
+    tuple * tupleRow;
+
+    tupleRow = pnm_allocpamrow(pamP);
+
+    pnm_readpamrow(pamP, tupleRow);
+
+    pnm_normalizeRow(pamP, tupleRow, transform, tuplenRow);
+
+    pnm_applyopacityrown(pamP, tuplenRow);
+
+    pnm_freepamrow(tupleRow);
+}
+
+
+
+static void
+writeARow(struct pam *             const pamP,
+          tuplen *                 const tuplenRow,
+          const pnm_transformMap * const transform) {
+/*----------------------------------------------------------------------------
+  Write a row to the output file described by *pamP, from values
+  proportional to light intensity with opacity included (i.e. the same
+  kind of number you would get form readARow()).
+
+  We ASSUME that the transform 'transform' is that necessary to effect
+  the conversion to brightness-linear unnormalized values.  If it is
+  NULL, we ASSUME that they already are brightness-proportional and just
+  need to be unnormalized.
+
+  We destroy *tuplenRow in the process.
+-----------------------------------------------------------------------------*/
+    tuple * tupleRow;
+
+    tupleRow = pnm_allocpamrow(pamP);
+
+    pnm_unapplyopacityrown(pamP, tuplenRow);
+
+    pnm_unnormalizeRow(pamP, tuplenRow, transform, tupleRow);
+
+    pnm_writepamrow(pamP, tupleRow);
+
+    pnm_freepamrow(tupleRow);
+}
+
+
+
+static void
+issueStretchWarning(bool   const verbose, 
+                    double const fracrowtofill) {
+
+    /* We need another input row to fill up this
+       output row, but there aren't any more.
+       That's because of rounding down on our
+       scaling arithmetic.  So we go ahead with
+       the data from the last row we read, which
+       amounts to stretching out the last output
+       row.  
+    */
+    if (verbose)
+        pm_message("%f of bottom row stretched due to "
+                   "arithmetic imprecision", 
+                   fracrowtofill);
+}
+
+
+
+static void
+scaleHorizontallyAndOutputRow(struct pam *             const inpamP,
+                              struct pam *             const outpamP,
+                              tuplen *                 const rowAccumulator,
+                              const pnm_transformMap * const transform,
+                              tuplen *                 const newtuplenrow,
+                              float                    const xscale,
+                              unsigned int             const row,
+                              bool                     const verbose) {
+/*----------------------------------------------------------------------------
+   Scale the row in 'rowAccumulator' horizontally by factor 'xscale'
+   and output it.
+
+   'newtuplenrow' is work space Caller provides us.  It is at least
+   wide enough to hold one output row.
+-----------------------------------------------------------------------------*/
+    if (outpamP->width == inpamP->width)    
+        /* shortcut X scaling */
+        writeARow(outpamP, rowAccumulator, transform);
+            /* This destroys 'rowAccumulator' */
+    else {
+        float stretch;
+            
+        horizontalScale(rowAccumulator, newtuplenrow, inpamP, outpamP,
+                        xscale, &stretch);
+            
+        if (verbose && row == 0)
+            pm_message("%f of right column stretched due to "
+                       "arithmetic imprecision", 
+                       stretch);
+            
+        writeARow(outpamP, newtuplenrow, transform);
+            /* This destroys 'newtuplenrow' */
+    }
+}
+
+
+
+static void
+createTransforms(struct pam *              const inpamP,
+                 struct pam *              const outpamP,
+                 bool                      const assumeLinear,
+                 const pnm_transformMap ** const inputTransformP,
+                 const pnm_transformMap ** const outputTransformP) {
+
+    if (assumeLinear) {
+        *inputTransformP  = NULL;
+        *outputTransformP = NULL;
+    } else {
+        *inputTransformP  = pnm_createungammatransform(inpamP);
+        *outputTransformP = pnm_creategammatransform(outpamP);
+    }
+}
+
+
+
+static void
+destroyTransforms(const pnm_transformMap * const inputTransform,
+                  const pnm_transformMap * const outputTransform) {
+
+    if (inputTransform)
+        free((void*)inputTransform);
+    
+    if (outputTransform)
+        free((void*)outputTransform);
+}
+
+
+
+static void
+scaleWithMixing(struct pam * const inpamP,
+                struct pam * const outpamP,
+                float        const xscale, 
+                float        const yscale,
+                bool         const assumeLinear,
+                bool         const verbose) {
+/*----------------------------------------------------------------------------
+  Scale the image described by *inpamP by xscale horizontally and
+  yscale vertically and write the result as the image described by
+  *outpamP.
+
+  The input file is positioned past the header, to the beginning of the
+  raster.  The output file is too.
+
+  Mix colors from input rows together in the output rows.
+
+  'assumeLinear' means to assume that the sample values in the input
+  image vary from standard PAM in that they are proportional to
+  intensity, (This makes the computation a lot faster, so you might
+  use this even if the samples are actually standard PAM, to get
+  approximate but fast results).
+
+-----------------------------------------------------------------------------*/
+    /* Here's how we think of the color mixing scaling operation:  
+       
+       First, I'll describe scaling in one dimension.  Assume we have
+       a one row image.  A raster row is ordinarily a sequence of
+       discrete pixels which have no width and no distance between
+       them -- only a sequence.  Instead, think of the raster row as a
+       bunch of pixels 1 unit wide adjacent to each other.  For
+       example, we are going to scale a 100 pixel row to a 150 pixel
+       row.  Imagine placing the input row right above the output row
+       and stretching it so it is the same size as the output row.  It
+       still contains 100 pixels, but they are 1.5 units wide each.
+       Our goal is to make the output row look as much as possible
+       like the stretched input row, while observing that a pixel can
+       be only one color.
+
+       Output Pixel 0 is completely covered by Input Pixel 0, so we
+       make Output Pixel 0 the same color as Input Pixel 0.  Output
+       Pixel 1 is covered half by Input Pixel 0 and half by Input
+       Pixel 1.  So we make Output Pixel 1 a 50/50 mix of Input Pixels
+       0 and 1.  If you stand back far enough, input and output will
+       look the same.
+
+       This works for all scale factors, both scaling up and scaling down.
+       
+       For images with an opacity plane, imagine Input Pixel 0's
+       foreground is fully opaque red (1,0,0,1), and Input Pixel 1 is
+       fully transparent (foreground irrelevant) (0,0,0,0).  We make
+       Output Pixel 0's foreground fully opaque red as before.  Output
+       Pixel 1 is covered half by Input Pixel 0 and half by Input
+       Pixel 1, so it is 50% opaque; but its foreground color is still
+       red: (1,0,0,0.5).  The output foreground color is the opacity
+       and coverage weighted average of the input foreground colors,
+       and the output opacity is the coverage weighted average of the
+       input opacities.
+
+       This program always stretches or squeezes the input row to be the
+       same length as the output row; The output row's pixels are always
+       1 unit wide.
+
+       The same thing works in the vertical direction.  We think of
+       rows as stacked strips of 1 unit height.  We conceptually
+       stretch the image vertically first (same process as above, but
+       in place of a single-color pixels, we have a vector of colors).
+       Then we take each row this vertical stretching generates and
+       stretch it horizontally.  
+    */
+
+    tuplen * tuplenrow;     /* An input row */
+    tuplen * newtuplenrow;  /* Working space */
+    float rowsleft;
+    /* The number of rows of output that need to be formed from the
+       current input row (the one in tuplerow[]), less the number that 
+       have already been formed (either in accumulator[]
+       or output to the file).  This can be fractional because of the
+       way we define rows as having height.
+    */
+    float fracrowtofill;
+        /* The fraction of the current output row (the one in vertScaledRow[])
+           that hasn't yet been filled in from an input row.
+        */
+    tuplen * rowAccumulator;
+        /* The red, green, and blue color intensities so far accumulated
+           from input rows for the current output row.  The ultimate value
+           of this is an output row after vertical scaling, but before
+           horizontal scaling.
+        */
+    int rowsread;
+        /* Number of rows of the input file that have been read */
+    int row;
+    const pnm_transformMap * inputTransform;
+    const pnm_transformMap * outputTransform;
+    
+    tuplenrow = pnm_allocpamrown(inpamP); 
+    rowAccumulator = pnm_allocpamrown(inpamP);
+
+    rowsread = 0;
+    rowsleft = 0.0;
+    fracrowtofill = 1.0;
+
+    newtuplenrow = pnm_allocpamrown(outpamP);
+
+    createTransforms(inpamP, outpamP, assumeLinear,
+                     &inputTransform, &outputTransform);
+
+    for (row = 0; row < outpamP->height; ++row) {
+        /* First scale Y from tuplerow[] into rowAccumulator[]. */
+
+        zeroAccum(inpamP, rowAccumulator);
+
+        if (outpamP->height == inpamP->height) {
+            /* shortcut Y scaling */
+            readARow(inpamP, rowAccumulator, inputTransform);
+        } else {
+            while (fracrowtofill > 0) {
+                if (rowsleft <= 0.0) {
+                    if (rowsread < inpamP->height) {
+                        readARow(inpamP, tuplenrow, inputTransform);
+                        ++rowsread;
+                    } else
+                        issueStretchWarning(verbose, fracrowtofill);
+                    rowsleft = yscale;
+                }
+                if (rowsleft < fracrowtofill) {
+                    accumOutputRow(inpamP, tuplenrow, rowsleft,
+                                   rowAccumulator);
+                    fracrowtofill -= rowsleft;
+                    rowsleft = 0.0;
+                } else {
+                    accumOutputRow(inpamP, tuplenrow, fracrowtofill,
+                                   rowAccumulator);
+                    rowsleft = rowsleft - fracrowtofill;
+                    fracrowtofill = 0.0;
+                }
+            }
+            fracrowtofill = 1.0;
+        }
+        /* 'rowAccumulator' now contains the contents of a single
+           output row, but not yet horizontally scaled.  Scale it now
+           horizontally and write it out.
+        */
+        scaleHorizontallyAndOutputRow(inpamP, outpamP, rowAccumulator,
+                                      outputTransform, newtuplenrow, xscale,
+                                      row, verbose);
+            /* Destroys rowAccumulator */
+
+    }
+    destroyTransforms(inputTransform, outputTransform);
+    pnm_freepamrown(rowAccumulator);
+    pnm_freepamrown(newtuplenrow);
+    pnm_freepamrown(tuplenrow);
+}
+
+
+
+static void
+scaleWithoutMixing(const struct pam * const inpamP,
+                   const struct pam * const outpamP,
+                   float              const xscale, 
+                   float              const yscale) {
+/*----------------------------------------------------------------------------
+  Scale the image described by *inpamP by xscale horizontally and
+  yscale vertically and write the result as the image described by
+  *outpamP.
+
+  The input file is positioned past the header, to the beginning of the
+  raster.  The output file is too.
+  
+  Don't mix colors from different input pixels together in the output
+  pixels.  Each output pixel is an exact copy of some corresponding 
+  input pixel.
+-----------------------------------------------------------------------------*/
+    tuple * tuplerow;  /* An input row */
+    tuple * newtuplerow;
+    int row;
+    int rowInInput;
+
+    tuplerow = pnm_allocpamrow(inpamP); 
+    rowInInput = -1;
+
+    newtuplerow = pnm_allocpamrow(outpamP);
+
+    for (row = 0; row < outpamP->height; ++row) {
+        int col;
+        
+        int const inputRow = (int) (row / yscale);
+
+        for (; rowInInput < inputRow; ++rowInInput) 
+            pnm_readpamrow(inpamP, tuplerow);
+        
+        for (col = 0; col < outpamP->width; ++col) {
+            int const inputCol = (int) (col / xscale);
+            
+            pnm_assigntuple(inpamP, newtuplerow[col], tuplerow[inputCol]);
+        }
+
+        pnm_writepamrow(outpamP, newtuplerow);
+    }
+    pnm_freepamrow(tuplerow);
+    pnm_freepamrow(newtuplerow);
+}
+
+
+
+int
+main(int argc, char **argv ) {
+
+    struct cmdlineInfo cmdline;
+    FILE* ifP;
+    struct pam inpam, outpam;
+    float xscale, yscale;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    outpam = inpam;  /* initial value */
+    outpam.file = stdout;
+
+    if (PNM_FORMAT_TYPE(inpam.format) == PBM_TYPE) {
+        outpam.format = PGM_TYPE;
+        outpam.maxval = PGM_MAXMAXVAL;
+        pm_message("promoting from PBM to PGM");
+    } else {
+        outpam.format = inpam.format;
+        outpam.maxval = inpam.maxval;
+    }
+
+    computeOutputDimensions(cmdline, inpam.height, inpam.width,
+                            &outpam.height, &outpam.width);
+
+    xscale = (float) outpam.width / inpam.width;
+    yscale = (float) outpam.height / inpam.height;
+
+    if (cmdline.verbose) {
+        pm_message("Scaling by %f horizontally to %d columns.", 
+                   xscale, outpam.width);
+        pm_message("Scaling by %f vertically to %d rows.", 
+                   yscale, outpam.height);
+    }
+
+    if (xscale * inpam.width < outpam.width - 1 ||
+        yscale * inpam.height < outpam.height - 1) 
+        pm_error("Arithmetic precision of this program is inadequate to "
+                 "do the specified scaling.  Use a smaller input image "
+                 "or a slightly different scale factor.");
+
+    pnm_writepaminit(&outpam);
+
+    if (cmdline.nomix) {
+        if (cmdline.verbose)
+            pm_message("Using nomix method");
+        scaleWithoutMixing(&inpam, &outpam, xscale, yscale);
+    } else if (!cmdline.filterFunction) {
+        if (cmdline.verbose)
+            pm_message("Using regular rescaling method");
+        scaleWithMixing(&inpam, &outpam, xscale, yscale, 
+                        cmdline.linear, cmdline.verbose);
+    } else {
+        if (cmdline.verbose)
+            pm_message("Using general filter method");
+        resample(&inpam, &outpam,
+                 cmdline.filterFunction, cmdline.filterRadius,
+                 cmdline.windowFunction, cmdline.verbose,
+                 cmdline.linear);
+    }
+    pm_close(ifP);
+    pm_close(stdout);
+    
+    return 0;
+}
diff --git a/editor/pamstretch-gen b/editor/pamstretch-gen
new file mode 100755
index 00000000..cd59a36b
--- /dev/null
+++ b/editor/pamstretch-gen
@@ -0,0 +1,80 @@
+#!/bin/sh
+#
+# pamstretch-gen - a shell script which acts a little like a general
+# form of pamstretch, by scaling up with pamstretch then scaling
+# down with pamscale.
+#
+# it also copes with N<1, but then it just uses pamscale. :-)
+#
+# Formerly named 'pnminterp-gen' and 'pnmstretch-gen'.
+#
+# Copyright (C) 1998,2000 Russell Marks.
+# 
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or (at
+# your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 
+
+
+if [ "$1" = "" ]; then
+  echo 'usage: pamstretch-gen N [pnmfile]'
+  exit 1
+fi
+
+tempdir="${TMPDIR-/tmp}/pamstretch-gen.$$"
+mkdir $tempdir || { echo "Could not create temporary file. Exiting."; exit 1;}
+chmod 700 $tempdir
+tempfile=$tempdir/pnmig
+
+trap 'rm -rf $tempdir' 0 1 3 15
+
+if ! cat $2 >$tempfile 2>/dev/null; then
+  echo 'pamstretch-gen: error reading file' 1>&2
+  exit 1
+fi
+
+if ! pnmfile $tempfile 1>/dev/null 2>/dev/null; then
+  echo 'Not valid pnm input'
+  exit 1
+fi
+
+# we use the width as indication of how much to scale; width and
+# height are being scaled equally, so this should be ok.
+width=`pnmfile $tempfile 2>/dev/null|cut -d " " -f 3`
+
+if [ "$width" = "" ]; then
+  echo 'pamstretch-gen: not a PNM file' 1>&2
+  exit 1
+fi
+
+# should really use dc for maths, but awk is less painful :-)
+target_width=`awk 'BEGIN{printf("%d",'0.5+"$width"*"$1"')}'`
+
+# work out how far we have to scale it up with pamstretch so that the
+# new width is >= the target width.
+int_scale=`awk '
+BEGIN {
+int_scale=1;int_width='"$width"'
+while(int_width<'"$target_width"')
+  {
+  int_scale++
+  int_width+='"$width"'
+  }
+print int_scale
+}'`
+
+if [ "$int_scale" -eq 1 ]; then
+  pamscale "$1" $tempfile
+else
+  pamstretch "$int_scale" $tempfile | pnmscale -xsi "$target_width"
+fi
diff --git a/editor/pamstretch.c b/editor/pamstretch.c
new file mode 100644
index 00000000..0e9e6abf
--- /dev/null
+++ b/editor/pamstretch.c
@@ -0,0 +1,408 @@
+/* pamstretch - scale up portable anymap by interpolating between pixels.
+ * 
+ * This program is based on 'pnminterp' by Russell Marks, rename
+ * pnmstretch for inclusion in Netpbm, then rewritten and renamed to
+ * pamstretch by Bryan Henderson in December 2001.
+ *
+ * Copyright (C) 1998,2000 Russell Marks.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include "pam.h"
+#include "shhopt.h"
+
+enum an_edge_mode {
+    EDGE_DROP,
+        /* drop one (source) pixel at right/bottom edges. */
+    EDGE_INTERP_TO_BLACK,
+        /* interpolate right/bottom edge pixels to black. */
+    EDGE_NON_INTERP
+        /* don't interpolate right/bottom edge pixels 
+           (default, and what zgv does). */
+};
+
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec;  /* Filespecs of input files */
+    enum an_edge_mode edge_mode;
+    unsigned int xscale;
+    unsigned int yscale;
+};
+
+
+
+tuple blackTuple;  
+   /* A "black" tuple.  Unless our input image is PBM, PGM, or PPM, we
+      don't really know what "black" means, so this is just something
+      arbitrary in that case.
+      */
+
+
+static void
+parse_command_line(int argc, char ** argv,
+                   struct cmdline_info *cmdline_p) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+    unsigned int option_def_index;
+
+    unsigned int blackedge;
+    unsigned int dropedge;
+    unsigned int xscale_spec;
+    unsigned int yscale_spec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3('b', "blackedge",    OPT_FLAG, NULL, &blackedge,            0);
+    OPTENT3('d', "dropedge",     OPT_FLAG, NULL, &dropedge,             0);
+    OPTENT3(0,   "xscale",       OPT_UINT, 
+            &cmdline_p->xscale, &xscale_spec, 0);
+    OPTENT3(0,   "yscale",       OPT_UINT, 
+            &cmdline_p->yscale, &yscale_spec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE; /* We have some short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (blackedge && dropedge) 
+        pm_error("Can't specify both -blackedge and -dropedge options.");
+    else if (blackedge)
+        cmdline_p->edge_mode = EDGE_INTERP_TO_BLACK;
+    else if (dropedge)
+        cmdline_p->edge_mode = EDGE_DROP;
+    else
+        cmdline_p->edge_mode = EDGE_NON_INTERP;
+
+    if (xscale_spec && cmdline_p->xscale == 0)
+        pm_error("You specified zero for the X scale factor.");
+    if (yscale_spec && cmdline_p->yscale == 0)
+        pm_error("You specified zero for the Y scale factor.");
+
+    if (xscale_spec && !yscale_spec)
+        cmdline_p->yscale = 1;
+    if (yscale_spec && !xscale_spec)
+        cmdline_p->xscale = 1;
+
+    if (!(xscale_spec || yscale_spec)) {
+        /* scale must be specified in an argument */
+        if ((argc-1) != 1 && (argc-1) != 2)
+            pm_error("Wrong number of arguments (%d).  Without scale options, "
+                     "you must supply 1 or 2 arguments:  scale and "
+                     "optional file specification", argc-1);
+        
+        {
+            char *endptr;   /* ptr to 1st invalid character in scale arg */
+            unsigned int scale;
+            
+            scale = strtol(argv[1], &endptr, 10);
+            if (*argv[1] == '\0') 
+                pm_error("Scale argument is a null string.  "
+                         "Must be a number.");
+            else if (*endptr != '\0')
+                pm_error("Scale argument contains non-numeric character '%c'.",
+                         *endptr);
+            else if (scale < 2)
+                pm_error("Scale argument must be at least 2.  "
+                         "You specified %d", scale);
+            cmdline_p->xscale = scale;
+            cmdline_p->yscale = scale;
+        }
+        if (argc-1 > 1) 
+            cmdline_p->input_filespec = argv[2];
+        else
+            cmdline_p->input_filespec = "-";
+    } else {
+        /* No scale argument allowed */
+        if ((argc-1) > 1)
+            pm_error("Too many arguments (%d).  With a scale option, "
+                     "the only argument is the "
+                     "optional file specification", argc-1);
+        if (argc-1 > 0) 
+            cmdline_p->input_filespec = argv[1];
+        else
+            cmdline_p->input_filespec = "-";
+    }
+}
+
+
+
+static void
+stretch_line(struct pam * const inpamP, 
+             const tuple * const line, const tuple * const line_stretched, 
+             unsigned int const scale, enum an_edge_mode const edge_mode) {
+/*----------------------------------------------------------------------------
+   Stretch the line of tuples 'line' into the output buffer 'line_stretched',
+   by factor 'scale'.
+-----------------------------------------------------------------------------*/
+    int scaleincr;
+    int sisize;   
+        /* normalizing factor to make fractions representable as integers.
+           E.g. if sisize = 100, one half is represented as 50.
+        */
+    unsigned int col;
+    unsigned int outcol;
+    
+    sisize=0;
+    while (sisize<256) 
+        sisize += scale;
+    scaleincr = sisize/scale;  /* (1/scale, normalized) */
+
+    outcol = 0;  /* initial value */
+
+    for (col = 0; col < inpamP->width; ++col) {
+        unsigned int pos;
+            /* The fraction of the way we are from curline to nextline,
+               normalized by sisize.
+            */
+        if (col >= inpamP->width-1) {
+            /* We're at the edge.  There is no column to the right with which
+               to interpolate.
+            */
+            switch(edge_mode) {
+            case EDGE_DROP:
+                /* No output column needed for this input column */
+                break;
+            case EDGE_INTERP_TO_BLACK: {
+                unsigned int pos;
+                for (pos = 0; pos < sisize; pos += scaleincr) {
+                    unsigned int plane;
+                    for (plane = 0; plane < inpamP->depth; ++plane)
+                        line_stretched[outcol][plane] = 
+                            (line[col][plane] * (sisize-pos)) / sisize;
+                    ++outcol;
+                }
+            }
+            break;
+            case EDGE_NON_INTERP: {
+                unsigned int pos;
+                for (pos = 0; pos < sisize; pos += scaleincr) {
+                    unsigned int plane;
+                    for (plane = 0; plane < inpamP->depth; ++plane)
+                        line_stretched[outcol][plane] = line[col][plane];
+                    ++outcol;
+                }
+            }
+            break;
+            default: 
+                pm_error("INTERNAL ERROR: invalid value for edge_mode");
+            }
+        } else {
+            /* Interpolate with the next input column to the right */
+            for (pos = 0; pos < sisize; pos += scaleincr) {
+                unsigned int plane;
+                for (plane = 0; plane < inpamP->depth; ++plane)
+                    line_stretched[outcol][plane] = 
+                        (line[col][plane] * (sisize-pos) 
+                         +  line[col+1][plane] * pos) / sisize;
+                ++outcol;
+            }
+        }
+    }
+}
+
+
+
+static void 
+write_interp_rows(struct pam *      const outpamP,
+                  const tuple *     const curline,
+                  const tuple *     const nextline, 
+                  tuple *           const outbuf,
+                  int               const scale) {
+/*----------------------------------------------------------------------------
+   Write out 'scale' rows, being 'curline' followed by rows that are 
+   interpolated between 'curline' and 'nextline'.
+-----------------------------------------------------------------------------*/
+    unsigned int scaleincr;
+    unsigned int sisize;
+    unsigned int pos;
+
+    sisize=0;
+    while(sisize<256) sisize+=scale;
+    scaleincr=sisize/scale;
+
+    for (pos = 0; pos < sisize; pos += scaleincr) {
+        unsigned int col;
+        for (col = 0; col < outpamP->width; ++col) {
+            unsigned int plane;
+            for (plane = 0; plane < outpamP->depth; ++plane) 
+                outbuf[col][plane] = (curline[col][plane] * (sisize-pos)
+                    + nextline[col][plane] * pos) / sisize;
+        }
+        pnm_writepamrow(outpamP, outbuf);
+    }
+}
+
+
+
+static void
+swap_buffers(tuple ** const buffer1P, tuple ** const buffer2P) {
+    /* Advance "next" line to "current" line by switching
+       line buffers 
+    */
+    tuple *tmp;
+
+    tmp = *buffer1P;
+    *buffer1P = *buffer2P;
+    *buffer2P = tmp;
+}
+
+
+static void 
+stretch(struct pam * const inpamP, struct pam * const outpamP,
+        int const xscale, int const yscale,
+        enum an_edge_mode const edge_mode) {
+
+    tuple *linebuf1, *linebuf2;  /* Input buffers for two rows at a time */
+    tuple *curline, *nextline;   /* Pointers to one of the two above buffers */
+    /* And the stretched versions: */
+    tuple *stretched_linebuf1, *stretched_linebuf2;
+    tuple *curline_stretched, *nextline_stretched;
+
+    tuple *outbuf;   /* One-row output buffer */
+    unsigned int row;
+    unsigned int rowsToStretch;
+    
+    linebuf1 =           pnm_allocpamrow(inpamP);
+    linebuf2 =           pnm_allocpamrow(inpamP);
+    stretched_linebuf1 = pnm_allocpamrow(outpamP);
+    stretched_linebuf2 = pnm_allocpamrow(outpamP);
+    outbuf =             pnm_allocpamrow(outpamP);
+
+    curline = linebuf1;
+    curline_stretched = stretched_linebuf1;
+    nextline = linebuf2;
+    nextline_stretched = stretched_linebuf2;
+
+    pnm_readpamrow(inpamP, curline);
+    stretch_line(inpamP, curline, curline_stretched, xscale, edge_mode);
+
+    if (edge_mode == EDGE_DROP) 
+        rowsToStretch = inpamP->height - 1;
+    else
+        rowsToStretch = inpamP->height;
+    
+    for (row = 0; row < rowsToStretch; row++) {
+        if (row == inpamP->height-1) {
+            /* last line is about to be output. there is no further
+             * `next line'.  if EDGE_DROP, we stop here, with output
+             * of rows-1 rows.  if EDGE_INTERP_TO_BLACK we make next
+             * line black.  if EDGE_NON_INTERP (default) we make it a
+             * copy of the current line.  
+             */
+            switch (edge_mode) {
+            case EDGE_INTERP_TO_BLACK: {
+                int col;
+                for (col = 0; col < outpamP->width; col++)
+                    nextline_stretched[col] = blackTuple;
+            } 
+            break;
+            case EDGE_NON_INTERP: {
+                /* EDGE_NON_INTERP */
+                int col;
+                for (col = 0; col < outpamP->width; col++)
+                    nextline_stretched[col] = curline_stretched[col];
+            }
+            break;
+            case EDGE_DROP: 
+                pm_error("INTERNAL ERROR: processing last row, but "
+                         "edge_mode is EDGE_DROP.");
+            }
+        } else {
+            pnm_readpamrow(inpamP, nextline);
+            stretch_line(inpamP, nextline, nextline_stretched, xscale,
+                         edge_mode);
+        }
+        
+        /* interpolate curline towards nextline into outbuf */
+        write_interp_rows(outpamP, curline_stretched, nextline_stretched,
+                          outbuf, yscale);
+
+        swap_buffers(&curline, &nextline);
+        swap_buffers(&curline_stretched, &nextline_stretched);
+    }
+    pnm_freerow(outbuf);
+    pnm_freerow(stretched_linebuf2);
+    pnm_freerow(stretched_linebuf1);
+    pnm_freerow(linebuf2);
+    pnm_freerow(linebuf1);
+}
+
+
+
+int 
+main(int argc,char *argv[]) {
+
+    FILE *ifp;
+
+    struct cmdline_info cmdline; 
+    struct pam inpam, outpam;
+    
+    pnm_init(&argc, argv);
+
+    parse_command_line(argc, argv, &cmdline);
+
+    ifp = pm_openr(cmdline.input_filespec);
+
+    pnm_readpaminit(ifp, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    if (inpam.width < 2)
+        pm_error("Image is too narrow.  Must be at least 2 columns.");
+    if (inpam.height < 2)
+        pm_error("Image is too short.  Must be at least 2 lines.");
+
+
+    outpam = inpam;  /* initial value */
+    outpam.file = stdout;
+
+    if (PNM_FORMAT_TYPE(inpam.format) == PBM_TYPE) {
+        outpam.format = PGM_TYPE;
+        /* usual filter message when reading PBM but writing PGM: */
+        pm_message("promoting from PBM to PGM");
+    } else {
+        outpam.format = inpam.format;
+    }
+    {
+        unsigned int const dropped = cmdline.edge_mode == EDGE_DROP ? 1 : 0;
+
+        outpam.width = (inpam.width - dropped) * cmdline.xscale;
+        outpam.height = (inpam.height - dropped) * cmdline.yscale;
+
+        pnm_writepaminit(&outpam);
+    }
+
+    pnm_createBlackTuple(&outpam, &blackTuple);
+
+    stretch(&inpam, &outpam, 
+            cmdline.xscale, cmdline.yscale, cmdline.edge_mode);
+
+    pm_close(ifp);
+
+    exit(0);
+}
+
+
+
diff --git a/editor/pamthreshold.c b/editor/pamthreshold.c
new file mode 100644
index 00000000..40260e79
--- /dev/null
+++ b/editor/pamthreshold.c
@@ -0,0 +1,623 @@
+/* pamthreshold - convert a Netpbm image to black and white by thresholding  */
+
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* Copyright (C) 2006 Erik Auerswald
+ * auerswal@unix-ag.uni-kl.de */
+
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "pam.h"
+
+#define MAX_ITERATIONS 100             /* stop after at most 100 iterations */
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;
+    unsigned int simple;
+    float        threshold;
+    bool         local;
+    bool         dual;
+    float        contrast;
+    unsigned int width, height;
+        /* geometry of local subimage.  Defined only if 'local' or 'dual'
+           is true.
+        */
+};
+
+
+
+struct range {
+    /* A range of sample values, normalized to [0, 1] */
+    samplen min;
+    samplen max;
+};
+
+
+
+static void
+initRange(struct range * const rangeP) {
+
+    /* Initialize to "undefined" state */
+    rangeP->min = 1.0;
+    rangeP->max = 0.0;
+}
+
+          
+
+static void
+addToRange(struct range * const rangeP,
+           samplen        const newSample) {
+
+    rangeP->min = MIN(newSample, rangeP->min);
+    rangeP->max = MAX(newSample, rangeP->max);
+}
+
+
+
+static float
+spread(struct range const range) {
+
+    assert(range.max >= range.min);
+    
+    return range.max - range.min;
+}
+
+
+
+static void
+parseGeometry(const char *   const wxl,
+              unsigned int * const widthP,
+              unsigned int * const heightP,
+              const char **  const errorP) {
+
+    char * const xPos = strchr(wxl, 'x');
+    if (!xPos)
+        asprintfN(errorP, "There is no 'x'.  It should be WIDTHxHEIGHT");
+    else {
+        *widthP  = atoi(wxl);
+        *heightP = atoi(xPos + 1);
+
+        if (*widthP == 0)
+            asprintfN(errorP, "Width is zero.");
+        else if (*heightP == 0)
+            asprintfN(errorP, "Height is zero.");
+        else
+            *errorP = NULL;
+    }
+}
+
+
+
+static void
+parseCommandLine(int                 argc, 
+                 char **             argv,
+                 struct cmdlineInfo *cmdlineP ) {
+/*----------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    /* vars for the option parser */
+    optEntry * option_def;
+    optStruct3 opt;
+    unsigned int option_def_index = 0;   /* incremented by OPTENT3 */
+
+    unsigned int thresholdSpec, localSpec, dualSpec, contrastSpec;
+    const char * localOpt;
+    const char * dualOpt;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    /* define the options */
+    OPTENT3(0, "simple",    OPT_FLAG,   NULL,               
+            &cmdlineP->simple,      0);
+    OPTENT3(0, "local",     OPT_STRING, &localOpt,
+            &localSpec,             0);
+    OPTENT3(0, "dual",      OPT_STRING, &dualOpt,
+            &dualSpec,              0);
+    OPTENT3(0, "threshold", OPT_FLOAT,  &cmdlineP->threshold,
+            &thresholdSpec,         0);
+    OPTENT3(0, "contrast",  OPT_FLOAT,  &cmdlineP->contrast,
+            &contrastSpec,          0);
+
+    /* set the defaults */
+    cmdlineP->width = cmdlineP->height = 0U;
+
+    /* set the option description for optParseOptions3 */
+    opt.opt_table     = option_def;
+    opt.short_allowed = FALSE;           /* long options only */
+    opt.allowNegNum   = FALSE;           /* we have no numbers at all */
+
+    /* parse commandline, change argc, argv, and *cmdlineP */
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+    if (cmdlineP->simple + localSpec + dualSpec > 1)
+        pm_error("You may specify only one of -simple, -local, and -dual");
+
+    if (!thresholdSpec)
+        cmdlineP->threshold = 0.5;
+
+    /* 0 <= threshold <= 1 */
+    if ((cmdlineP->threshold < 0.0) || (cmdlineP->threshold > 1.0))
+        pm_error("threshold must be in [0,1]");
+
+    if (!contrastSpec)
+        cmdlineP->contrast = 0.05;
+
+    /* 0 <= contrast <= 1 */
+    if ((cmdlineP->contrast < 0.0) || (cmdlineP->contrast > 1.0))
+        pm_error("contrast must be in [0,1]");
+
+    if (localSpec) {
+        const char * error;
+        cmdlineP->local = TRUE;
+
+        parseGeometry(localOpt, &cmdlineP->width, &cmdlineP->height, &error);
+
+        if (error) {
+            pm_error("Invalid -local value '%s'.  %s", localOpt, error);
+            strfree(error);
+        }
+    } else
+        cmdlineP->local = FALSE;
+
+    if (dualSpec) {
+        const char * error;
+        cmdlineP->dual = TRUE;
+
+        parseGeometry(dualOpt, &cmdlineP->width, &cmdlineP->height, &error);
+
+        if (error) {
+            pm_error("Invalid -dual value '%s'.  %s", dualOpt, error);
+            strfree(error);
+        }
+    } else
+        cmdlineP->dual = FALSE;
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFileName = argv[1];
+    else 
+        pm_error("Progam takes at most 1 parameter: the file name.  "
+                 "You specified %d", argc-1);
+}
+
+
+
+/* simple thresholding (the same as in pamditherbw) */
+
+static void
+thresholdSimple(struct pam * const inpamP,
+                struct pam * const outpamP,
+                float        const threshold) {
+
+    tuplen * inrow;    /* normalized input row */
+    tuple * outrow;    /* raw output row */
+    unsigned int row;  /* number of the current row */
+
+    inrow  = pnm_allocpamrown(inpamP);
+    outrow = pnm_allocpamrow(outpamP);
+
+    /* do the simple thresholding */
+    for (row = 0; row < inpamP->height; ++row) {
+        unsigned int col;
+        pnm_readpamrown(inpamP, inrow);
+        for (col = 0; col < inpamP->width; ++col)
+            outrow[col][0] =
+                inrow[col][0] >= threshold ? PAM_BW_WHITE : PAM_BLACK;
+        pnm_writepamrow(outpamP, outrow);
+    }
+
+    pnm_freepamrow(inrow);
+    pnm_freepamrow(outrow);
+}
+
+
+
+static void
+analyzeDistribution(struct pam *          const inpamP,
+                    const unsigned int ** const histogramP,
+                    struct range *        const rangeP) {
+/*----------------------------------------------------------------------------
+   Find the distribution of the sample values -- minimum, maximum, and
+   how many of each value -- in input image *inpamP, whose file is
+   positioned to the raster.
+
+   Return the minimum and maximum as *rangeP and the frequency
+   distribution as *histogramP, an array such that histogram[i] is the
+   number of pixels that have sample value i.
+
+   Leave the file positioned to the raster.
+-----------------------------------------------------------------------------*/
+    unsigned int row;
+    tuple * inrow;
+    tuplen * inrown;
+    unsigned int * histogram;  /* malloced array */
+    unsigned int i;
+
+    pm_filepos rasterPos;      /* Position in input file of the raster */
+
+    pm_tell2(inpamP->file, &rasterPos, sizeof(rasterPos));
+
+    inrow = pnm_allocpamrow(inpamP);
+    inrown = pnm_allocpamrown(inpamP);
+    MALLOCARRAY(histogram, inpamP->maxval+1);
+    if (histogram == NULL)
+        pm_error("Unable to allocate space for %lu-entry histogram",
+                 inpamP->maxval+1);
+
+    /* Initialize histogram -- zero occurences of everything */
+    for (i = 0; i <= inpamP->maxval; ++i)
+        histogram[i] = 0;
+
+    initRange(rangeP);
+
+    for (row = 0; row < inpamP->height; ++row) {
+        unsigned int col;
+        pnm_readpamrow(inpamP, inrow);
+        pnm_normalizeRow(inpamP, inrow, NULL, inrown);
+        for (col = 0; col < inpamP->width; ++col) {
+            ++histogram[inrow[col][0]];
+            addToRange(rangeP, inrown[col][0]);
+        }
+    }
+    *histogramP = histogram;
+
+    pnm_freepamrow(inrow);
+    pnm_freepamrown(inrown);
+
+    pm_seek2(inpamP->file, &rasterPos, sizeof(rasterPos));
+}
+
+
+
+static void
+getLocalThreshold(tuplen **    const inrows,
+                  unsigned int const windowWidth,
+                  unsigned int const x,
+                  unsigned int const localWidth,
+                  unsigned int const localHeight,
+                  float        const darkness,
+                  float        const minSpread,
+                  samplen      const defaultThreshold,
+                  samplen *    const thresholdP) {
+/*----------------------------------------------------------------------------
+  Find a suitable threshold in local area around one pixel.
+
+  inrows[][] is a an array of 'windowWidth' pixels by 'localHeight'.
+
+  'x' is a column number within the window.
+
+  We look at the rectangle consisting of the 'localWidth' columns
+  surrounding x, all rows.  If x is near the left or right edge, we truncate
+  the window as needed.
+
+  We base the threshold on the local spread (difference between minimum
+  and maximum sample values in the local areas) and the 'darkness'
+  factor.  A higher 'darkness' gets a higher threshold.
+
+  If the spread is less than 'minSpread', we return 'defaultThreshold' and
+  'darkness' is irrelevant.
+
+  'localWidth' must be odd.
+-----------------------------------------------------------------------------*/
+    unsigned int const startCol = x >= localWidth/2 ? x - localWidth/2 : 0;
+
+    unsigned int col;
+    struct range localRange;
+
+    assert(localWidth % 2 == 1);  /* entry condition */
+
+    initRange(&localRange);
+
+    for (col = startCol; col <= x + localWidth/2 && col < windowWidth; ++col) {
+        unsigned int row;
+
+        for (row = 0; row < localHeight; ++row)
+            addToRange(&localRange, inrows[row][col][0]);
+    }
+
+    if (spread(localRange) < minSpread)
+        *thresholdP = defaultThreshold;
+    else
+        *thresholdP = localRange.min + darkness * spread(localRange);
+}
+
+
+
+static void
+thresholdLocalRow(struct pam *       const inpamP,
+                  tuplen **          const inrows,
+                  unsigned int       const localWidth,
+                  unsigned int       const windowHeight,
+                  unsigned int       const row,
+                  struct cmdlineInfo const cmdline,
+                  struct range       const globalRange,
+                  samplen            const globalThreshold,
+                  tuple *            const outrow) {
+
+    tuplen * const inrow = inrows[row % windowHeight];
+
+    float minSpread;
+    unsigned int col;
+
+    if (cmdline.dual)
+        minSpread = cmdline.contrast * spread(globalRange);
+    else
+        minSpread = 0.0;
+
+    for (col = 0; col < inpamP->width; ++col) {
+        samplen threshold;
+
+        getLocalThreshold(inrows, inpamP->width, col, localWidth, windowHeight,
+                          cmdline.threshold, minSpread, globalThreshold,
+                          &threshold);
+        
+        outrow[col][0] = inrow[col][0] >= threshold ? PAM_BW_WHITE : PAM_BLACK;
+    }
+}
+
+
+
+static void
+computeGlobalThreshold(struct pam *         const inpamP,
+                       const unsigned int * const histogram,
+                       struct range         const globalRange,
+                       float *              const thresholdP) {
+/*----------------------------------------------------------------------------
+   Compute the proper threshold to use for the image described by
+   *inpamP, whose file is positioned to the raster.
+
+   For our convenience:
+
+     'histogram' describes the frequency of occurence of the various sample
+     values in the image.
+
+     'globalRange' describes the range (minimum, maximum) of sample values
+     in the image.
+
+   Return the threshold (scaled to [0, 1]) as *thresholdP.
+
+   Leave the file positioned to the raster.
+-----------------------------------------------------------------------------*/
+    /* Found this algo in the wikipedia article "Thresholding (image
+       processing)"
+    */
+
+    float threshold;           /* threshold is iteratively determined */
+    float oldthreshold;        /* stop if oldthreshold==threshold */
+    unsigned int iter;         /* count of done iterations */
+
+    /* Use middle value (halfway between min and max) as initial threshold */
+    threshold = (globalRange.min + globalRange.max) / 2.0;
+
+    oldthreshold = -1.0;  /* initial value */
+    iter = 0; /* initial value */
+
+    /* adjust threshold to image */
+    while (fabs(oldthreshold - threshold) > 0.01 && iter < MAX_ITERATIONS) {
+        unsigned long white, black;   /* number of white, black pixels */
+        unsigned int row;
+
+        ++iter;
+
+        /* count black and white pixels */
+
+        for (row = 0, white = 0, black = 0; row < inpamP->height; ++row) {
+            unsigned int col;
+
+            for(col = 0; col < threshold * inpamP->maxval; ++col)
+                black += histogram[col];
+            for(; col <= inpamP->maxval; ++col)
+                white += histogram[col];
+        }
+
+        oldthreshold = threshold;
+
+        /* Use the weighted average of black and white pixels to calculate new
+           threshold
+        */
+        threshold =
+            (black * (globalRange.min + threshold) / 2.0 +
+             white * (threshold + globalRange.max) / 2.0) /
+            (black + white);
+    }
+
+    *thresholdP = threshold;
+}
+
+
+
+static void
+thresholdLocal(struct pam *       const inpamP,
+               struct pam *       const outpamP,
+               struct cmdlineInfo const cmdline) {
+/*----------------------------------------------------------------------------
+  Threshold the image described by *inpamP, whose file is positioned to the
+  raster, and output the resulting raster to the image described by
+  *outpamP.
+
+  Use local adaptive thresholding aka dynamic thresholding or dual
+  thresholding (global for low contrast areas, LAT otherwise)
+-----------------------------------------------------------------------------*/
+    struct range globalRange; /* Range of sample values in entire image */
+    tuplen ** inrows;
+        /* vertical window of image containing the local area.  This is
+           a ring of 'windowHeight' rows.  Row R of the image, when it is
+           in the window, is inrows[R % windowHeight].
+        */
+    unsigned int windowHeight;  /* size of 'inrows' window */
+    unsigned int nextRowToRead;
+        /* Number of the next row to be read from the file into the inrows[]
+           buffer.
+        */
+    tuple * outrow;           /* raw output row */
+    unsigned int row;
+        /* Number of the current row.  The current row is normally the
+           one in the center of the inrows[] buffer (which has an actual
+           center row because it is of odd height), but when near the top
+           and bottom edge of the image, it is not.
+        */
+    const unsigned int * histogram;
+    samplen globalThreshold;
+        /* This is a threshold based on the entire image, to use in areas
+           where the contrast is too small to use a locally-derived threshold.
+        */
+    unsigned int oddLocalWidth;
+    unsigned int oddLocalHeight;
+    unsigned int i;
+    
+    /* use a subimage with odd width and height to have a middle pixel */
+
+    if (cmdline.width % 2 == 0)
+        oddLocalWidth = cmdline.width + 1;
+    else 
+        oddLocalWidth = cmdline.width;
+    if (cmdline.height % 2 == 0)
+        oddLocalHeight = cmdline.height + 1;
+    else
+        oddLocalHeight = cmdline.height;
+
+    windowHeight = MIN(oddLocalHeight, inpamP->height);
+
+    analyzeDistribution(inpamP, &histogram, &globalRange);
+
+    computeGlobalThreshold(inpamP, histogram, globalRange, &globalThreshold);
+
+    outrow = pnm_allocpamrow(outpamP);
+
+    MALLOCARRAY(inrows, windowHeight);
+
+    if (inrows == NULL)
+        pm_error("Unable to allocate memory for a %u-row array", windowHeight);
+
+    for (i = 0; i < windowHeight; ++i)
+        inrows[i] = pnm_allocpamrown(inpamP);
+
+    /* Fill the vertical window buffer */
+    nextRowToRead = 0;
+
+    while (nextRowToRead < windowHeight)
+        pnm_readpamrown(inpamP, inrows[nextRowToRead++ % windowHeight]);
+
+    for (row = 0; row < inpamP->height; ++row) {
+        thresholdLocalRow(inpamP, inrows, oddLocalWidth, windowHeight, row,
+                          cmdline, globalRange, globalThreshold, outrow);
+
+        pnm_writepamrow(outpamP, outrow);
+        
+        /* read next image line if available and necessary */
+        if (row + windowHeight / 2 >= nextRowToRead &&
+            nextRowToRead < inpamP->height)
+            pnm_readpamrown(inpamP, inrows[nextRowToRead++ % windowHeight]);
+    }
+
+    free((void*)histogram);
+    for (i = 0; i < windowHeight; ++i)
+        pnm_freepamrow(inrows[i]);
+    free(inrows);
+    pnm_freepamrow(outrow);
+}
+
+
+
+static void
+thresholdIterative(struct pam * const inpamP,
+                   struct pam * const outpamP) {
+
+    const unsigned int * histogram;
+    struct range globalRange;
+    samplen threshold;
+
+    analyzeDistribution(inpamP, &histogram, &globalRange);
+
+    computeGlobalThreshold(inpamP, histogram, globalRange, &threshold);
+
+    pm_message("using global threshold %4.2f", threshold);
+
+    thresholdSimple(inpamP, outpamP, threshold);
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    FILE * ifP; 
+    struct cmdlineInfo cmdline;
+    struct pam inpam, outpam;
+    bool eof;  /* No more images in input stream */
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    if (cmdline.simple)
+        ifP = pm_openr(cmdline.inputFileName);
+    else
+        ifP = pm_openr_seekable(cmdline.inputFileName);
+
+    /* threshold each image in the PAM file */
+    eof = FALSE;
+    while (!eof) {
+        pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+        /* set output image parameters for a bilevel image */
+        outpam.size        = sizeof(outpam);
+        outpam.len         = PAM_STRUCT_SIZE(tuple_type);
+        outpam.file        = stdout;
+        outpam.format      = PAM_FORMAT;
+        outpam.plainformat = 0;
+        outpam.height      = inpam.height;
+        outpam.width       = inpam.width;
+        outpam.depth       = 1;
+        outpam.maxval      = 1;
+        outpam.bytes_per_sample = 1;
+        strcpy(outpam.tuple_type, "BLACKANDWHITE");
+
+        pnm_writepaminit(&outpam);
+
+        /* do the thresholding */
+
+        if (cmdline.simple)
+            thresholdSimple(&inpam, &outpam, cmdline.threshold);
+        else if (cmdline.local || cmdline.dual)
+            thresholdLocal(&inpam, &outpam, cmdline);
+        else
+            thresholdIterative(&inpam, &outpam);
+
+        pnm_nextimage(ifP, &eof);
+    }
+
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/editor/pbmclean.c b/editor/pbmclean.c
new file mode 100644
index 00000000..3ae3acfc
--- /dev/null
+++ b/editor/pbmclean.c
@@ -0,0 +1,239 @@
+/* pbmclean.c - pixel cleaning. Remove pixel if less than n connected
+ *              identical neighbours, n=1 default.
+ * AJCD 20/9/90
+ * stern, Fri Oct 19 00:10:38 MET DST 2001
+ *     add '-white/-black' flags to restrict operation to given blobs
+ */
+
+#include <stdio.h>
+#include "pbm.h"
+#include "shhopt.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespecs of input files */
+    bool flipWhite;
+    bool flipBlack;
+    unsigned int connect;
+    unsigned int verbose;
+};
+
+#define PBM_INVERT(p) ((p) == PBM_WHITE ? PBM_BLACK : PBM_WHITE)
+
+/* input bitmap size and storage */
+static bit *inrow[3] ;
+
+#define THISROW (1)
+
+enum compass_heading {
+    WEST=0,
+    NORTHWEST=1,
+    NORTH=2,
+    NORTHEAST=3,
+    EAST=4,
+    SOUTHEAST=5,
+    SOUTH=6,
+    SOUTHWEST=7
+};
+/* compass directions from west clockwise.  Indexed by enum compass_heading */
+int const xd[] = { -1, -1,  0,  1, 1, 1, 0, -1 } ;
+int const yd[] = {  0, -1, -1, -1, 0, 1, 1,  1 } ;
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+    unsigned int option_def_index;
+
+    unsigned int black, white;
+    unsigned int minneighborsSpec;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "verbose", OPT_FLAG, NULL, &cmdlineP->verbose, 0);
+    OPTENT3(0,   "black", OPT_FLAG, NULL, &black, 0);
+    OPTENT3(0,   "white", OPT_FLAG, NULL, &white, 0);
+    OPTENT3(0,   "minneighbors", OPT_UINT, &cmdlineP->connect, 
+            &minneighborsSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = TRUE;  /* We sort of allow negative numbers as parms */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!black && !white) {
+        cmdlineP->flipBlack = TRUE;
+        cmdlineP->flipWhite = TRUE;
+    } else {
+        cmdlineP->flipBlack = !!black;
+        cmdlineP->flipWhite = !!white;
+    }    
+
+
+    if (!minneighborsSpec) {
+        /* Now we do a sleazy tour through the parameters to see if
+           one is -N where N is a positive integer.  That's for
+           backward compatibility, since Pbmclean used to have
+           unconventional syntax where a -N option was used instead of
+           the current -minneighbors option.  The only reason -N didn't
+           get processed by pm_optParseOptions3() is that it looked
+           like a negative number parameter instead of an option.  
+           If we find a -N, we make like it was a -minneighbors=N option.
+        */
+        int i;
+        bool foundNegative;
+
+        cmdlineP->connect = 1;  /* default */
+        foundNegative = FALSE;
+
+        for (i = 1; i < argc; ++i) {
+            if (foundNegative)
+                argv[i-1] = argv[i];
+            else {
+                if (atoi(argv[i]) < 0) {
+                    cmdlineP->connect = - atoi(argv[i]);
+                    foundNegative = TRUE;
+                }
+            }
+        }
+        if (foundNegative)
+            --argc;
+    }
+
+    if (argc-1 < 1) 
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        pm_error("You specified too many arguments (%d).  The only "
+                 "argument is the optional input file specification.",
+                 argc-1);
+}
+
+
+
+
+
+static void 
+nextrow(FILE * const ifd,
+        int    const row,
+        int    const cols,
+        int    const rows,
+        int    const format) {
+/*----------------------------------------------------------------------------
+   Advance one row in the input.
+
+   'row' is the row number that will be the current row.
+-----------------------------------------------------------------------------*/
+    bit * shuffle;
+
+    /* First, get the "next" row in inrow[2] if this is the very first
+       call to nextrow().
+    */
+    if (inrow[2] == NULL && row < rows) {
+        inrow[2] = pbm_allocrow(cols);
+        pbm_readpbmrow(ifd, inrow[2], cols, format);
+    }
+    /* Now advance the inrow[] window, rotating the buffer that now holds
+       the "previous" row to use it for the new "next" row.
+    */
+    shuffle = inrow[0];
+
+    inrow[0] = inrow[1];
+    inrow[1] = inrow[2];
+    inrow[2] = shuffle ;
+    if (row+1 < rows) {
+        /* Read the "next" row in from the file.  Allocate buffer if needed */
+        if (inrow[2] == NULL)
+            inrow[2] = pbm_allocrow(cols);
+        pbm_readpbmrow(ifd, inrow[2], cols, format);
+    } else {
+        /* There is no next row */
+        if (inrow[2]) {
+            pbm_freerow(inrow[2]);
+            inrow[2] = NULL; 
+        }
+    }
+}
+
+
+
+static unsigned int
+likeNeighbors(bit *        const inrow[3], 
+              unsigned int const col, 
+              unsigned int const cols) {
+    
+    int const point = inrow[THISROW][col];
+    enum compass_heading heading;
+    int joined;
+
+    joined = 0;  /* initial value */
+    for (heading = WEST; heading <= SOUTHWEST; ++heading) {
+        int x = col + xd[heading] ;
+        int y = THISROW + yd[heading] ;
+        if (x < 0 || x >= cols || !inrow[y]) {
+            if (point == PBM_WHITE) joined++;
+        } else if (inrow[y][x] == point) joined++ ;
+    }
+    return joined;
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE *ifp;
+    bit *outrow;
+    int cols, rows, format;
+    unsigned int row;
+    unsigned int nFlipped;  /* Number of pixels we have flipped so far */
+
+    pbm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifp = pm_openr(cmdline.inputFilespec);
+
+    inrow[0] = inrow[1] = inrow[2] = NULL;
+    pbm_readpbminit(ifp, &cols, &rows, &format);
+
+    outrow = pbm_allocrow(cols);
+
+    pbm_writepbminit(stdout, cols, rows, 0) ;
+
+    nFlipped = 0;  /* No pixels flipped yet */
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        nextrow(ifp, row, cols, rows, format);
+        for (col = 0; col < cols; ++col) {
+            bit const thispoint = inrow[THISROW][col];
+            if ((cmdline.flipWhite && thispoint == PBM_WHITE) ||
+                (cmdline.flipBlack && thispoint == PBM_BLACK)) {
+                if (likeNeighbors(inrow, col, cols) < cmdline.connect) {
+                    outrow[col] = PBM_INVERT(thispoint);
+                    ++nFlipped;
+                } else
+                    outrow[col] = thispoint;
+            } else 
+                outrow[col] = thispoint;
+        }
+        pbm_writepbmrow(stdout, outrow, cols, 0) ;
+    }
+    pbm_freerow(outrow);
+    pm_close(ifp);
+
+    if (cmdline.verbose)
+        pm_message("%d pixels flipped", nFlipped);
+
+    return 0;
+}
diff --git a/editor/pbmlife.c b/editor/pbmlife.c
new file mode 100644
index 00000000..be34cc69
--- /dev/null
+++ b/editor/pbmlife.c
@@ -0,0 +1,114 @@
+/* pbmlife.c - read a portable bitmap and apply Conway's rules of Life to it
+**
+** Copyright (C) 1988,1 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+
+int
+main( argc, argv )
+int argc;
+char* argv[];
+    {
+    FILE* ifp;
+    bit* prevrow;
+    bit* thisrow;
+    bit* nextrow;
+    bit* temprow;
+    register bit* newrow;
+    int rows, cols, row;
+    register int col, count;
+    int format;
+
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[pbmfile]" );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    pbm_readpbminit( ifp, &cols, &rows, &format );
+    prevrow = pbm_allocrow( cols );
+    thisrow = pbm_allocrow( cols );
+    nextrow = pbm_allocrow( cols );
+
+    pbm_writepbminit( stdout, cols, rows, 0 );
+    newrow = pbm_allocrow( cols );
+
+    pbm_readpbmrow( ifp, nextrow, cols, format );
+
+    for ( row = 0; row < rows; ++row )
+	{
+	temprow = prevrow;
+	prevrow = thisrow;
+	thisrow = nextrow;
+	nextrow = temprow;
+	if ( row < rows - 1 )
+	    pbm_readpbmrow( ifp, nextrow, cols, format );
+
+        for ( col = 0; col < cols; ++col )
+	    {
+	    /* Check the neighborhood, with an unrolled double loop. */
+	    count = 0;
+	    if ( row > 0 )
+		{
+		/* upper left */
+		if ( col > 0 && prevrow[col - 1] == PBM_WHITE )
+		    ++count;
+		/* upper center */
+		if ( prevrow[col] == PBM_WHITE )
+		    ++count;
+		/* upper right */
+		if ( col < cols - 1 && prevrow[col + 1] == PBM_WHITE )
+		    ++count;
+		}
+	    /* left */
+	    if ( col > 0 && thisrow[col - 1] == PBM_WHITE )
+		++count;
+	    /* right */
+	    if ( col < cols - 1 && thisrow[col + 1] == PBM_WHITE )
+		++count;
+	    if ( row < rows - 1 )
+		{
+		/* lower left */
+		if ( col > 0 && nextrow[col - 1] == PBM_WHITE )
+		    ++count;
+		/* lower center */
+		if ( nextrow[col] == PBM_WHITE )
+		    ++count;
+		/* lower right */
+		if ( col < cols - 1 && nextrow[col + 1] == PBM_WHITE )
+		    ++count;
+		}
+
+	    /* And compute the new value. */
+	    if ( thisrow[col] == PBM_WHITE )
+		if ( count == 2 || count == 3 )
+		    newrow[col] = PBM_WHITE;
+		else
+		    newrow[col] = PBM_BLACK;
+	    else
+		if ( count == 3 )
+		    newrow[col] = PBM_WHITE;
+		else
+		    newrow[col] = PBM_BLACK;
+	    }
+	pbm_writepbmrow( stdout, newrow, cols, 0 );
+	}
+
+    pm_close( ifp );
+    pm_close( stdout );
+
+    exit( 0 );
+    }
diff --git a/editor/pbmmask.c b/editor/pbmmask.c
new file mode 100644
index 00000000..21ada6b9
--- /dev/null
+++ b/editor/pbmmask.c
@@ -0,0 +1,222 @@
+/* pbmmask.c - create a mask bitmap from a portable bitmap
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+#include "mallocvar.h"
+
+static bit ** bits;
+static bit ** mask;
+static bit backcolor;
+static int rows, cols;
+
+
+
+static short * fcols;
+static short * frows;
+static int fstacksize = 0;
+static int fstackp = 0;
+
+
+
+static void
+addflood(int const col,
+         int const row) {
+
+    if ( bits[row][col] == backcolor && mask[row][col] == PBM_BLACK ) {
+        if ( fstackp >= fstacksize ) {
+            if ( fstacksize == 0 ) {
+                fstacksize = 1000;
+                MALLOCARRAY(fcols, fstacksize);
+                MALLOCARRAY(frows, fstacksize);
+                if ( fcols == NULL || frows == NULL )
+                    pm_error( "out of memory" );
+            } else {
+                fstacksize *= 2;
+                fcols = (short*) realloc(
+                    (char*) fcols, fstacksize * sizeof(short) );
+                frows = (short*) realloc(
+                    (char*) frows, fstacksize * sizeof(short) );
+                if ( fcols == (short*) 0 || frows == (short*) 0 )
+                    pm_error( "out of memory" );
+            }
+        }
+        fcols[fstackp] = col;
+        frows[fstackp] = row;
+        ++fstackp;
+    }
+}
+
+
+
+static void
+flood(void) {
+
+    while ( fstackp > 0 ) {
+        int col, row;
+        --fstackp;
+        col = fcols[fstackp];
+        row = frows[fstackp];
+        if ( bits[row][col] == backcolor && mask[row][col] == PBM_BLACK ) {
+            int c;
+            mask[row][col] = PBM_WHITE;
+            if ( row - 1 >= 0 )
+                addflood( col, row - 1 );
+            if ( row + 1 < rows )
+                addflood( col, row + 1 );
+            for ( c = col + 1; c < cols; ++c ) {
+                if ( bits[row][c] == backcolor && mask[row][c] == PBM_BLACK ) {
+                    mask[row][c] = PBM_WHITE;
+                    if ( row - 1 >= 0 && 
+                         ( bits[row - 1][c - 1] != backcolor || 
+                           mask[row - 1][c - 1] != PBM_BLACK ) )
+                        addflood( c, row - 1 );
+                    if ( row + 1 < rows && 
+                         ( bits[row + 1][c - 1] != backcolor || 
+                           mask[row + 1][c - 1] != PBM_BLACK ) )
+                        addflood( c, row + 1 );
+                }
+                else
+                    break;
+            }
+            for ( c = col - 1; c >= 0; --c ) {
+                if ( bits[row][c] == backcolor && mask[row][c] == PBM_BLACK ) {
+                    mask[row][c] = PBM_WHITE;
+                    if ( row - 1 >= 0 && 
+                         ( bits[row - 1][c + 1] != backcolor || 
+                           mask[row - 1][c + 1] != PBM_BLACK ) )
+                        addflood( c, row - 1 );
+                    if ( row + 1 < rows && 
+                         ( bits[row + 1][c + 1] != backcolor || 
+                           mask[row + 1][c + 1] != PBM_BLACK ) )
+                        addflood( c, row + 1 );
+                } else
+                    break;
+            }
+        }
+    }
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    FILE* ifp;
+    int argn, expand, wcount;
+    register int row, col;
+    const char* const usage = "[-expand] [pbmfile]";
+
+    pbm_init( &argc, argv );
+
+    argn = 1;
+    expand = 0;
+
+    if ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+    {
+        if ( pm_keymatch( argv[argn], "-expand", 2 ) )
+            expand = 1;
+        else if ( pm_keymatch( argv[argn], "-noexpand", 2 ) )
+            expand = 0;
+        else
+            pm_usage( usage );
+        ++argn;
+    }
+
+    if ( argn == argc )
+        ifp = stdin;
+    else
+    {
+        ifp = pm_openr( argv[argn] );
+        ++argn;
+    }
+
+    if ( argn != argc )
+        pm_usage( usage );
+
+    bits = pbm_readpbm( ifp, &cols, &rows );
+    pm_close( ifp );
+    mask = pbm_allocarray( cols, rows );
+
+    /* Clear out the mask. */
+    for ( row = 0; row < rows; ++row )
+        for ( col = 0; col < cols; ++col )
+            mask[row][col] = PBM_BLACK;
+
+    /* Figure out the background color, by counting along the edge. */
+    wcount = 0;
+    for ( row = 0; row < rows; ++row ) {
+        if ( bits[row][0] == PBM_WHITE )
+            ++wcount;
+        if ( bits[row][cols - 1] == PBM_WHITE )
+            ++wcount;
+    }
+    for ( col = 1; col < cols - 1; ++col ) {
+        if ( bits[0][col] == PBM_WHITE )
+            ++wcount;
+        if ( bits[rows - 1][col] == PBM_WHITE )
+            ++wcount;
+    }
+    if ( wcount >= rows + cols - 2 )
+        backcolor = PBM_WHITE;
+    else
+        backcolor = PBM_BLACK;
+
+    /* Flood the entire edge.  Probably the first call will be enough, but
+       might as well be sure.
+    */
+    for ( col = cols - 3; col >= 2; col -= 2 ) {
+        addflood( col, rows - 1 );
+        addflood( col, 0 );
+    }
+    for ( row = rows - 1; row >= 0; row -= 2 ) {
+        addflood( cols - 1, row );
+        addflood( 0, row );
+    }
+    flood( );
+
+    if ( ! expand )
+        /* Done. */
+        pbm_writepbm( stdout, mask, cols, rows, 0 );
+    else {
+        /* Expand by one pixel. */
+        int srow, scol;
+        unsigned int row;
+        bit ** emask;
+
+        emask = pbm_allocarray( cols, rows );
+
+        for ( row = 0; row < rows; ++row ) {
+            unsigned int col;
+            for ( col = 0; col < cols; ++col )
+                if ( mask[row][col] == PBM_BLACK )
+                    emask[row][col] = PBM_BLACK;
+                else {
+                    emask[row][col] = PBM_WHITE;
+                    for ( srow = row - 1; srow <= row + 1; ++srow )
+                        for ( scol = col - 1; scol <= col + 1; ++scol )
+                            if ( srow >= 0 && srow < rows &&
+                                 scol >= 0 && scol < cols &&
+                                 mask[srow][scol] == PBM_BLACK ) {
+
+                                emask[row][col] = PBM_BLACK;
+                                break;
+                            }
+                }
+        }
+        pbm_writepbm( stdout, emask, cols, rows, 0 );
+    }
+
+    pm_close( stdout );
+
+    return 0;
+}
+
diff --git a/editor/pbmpscale.c b/editor/pbmpscale.c
new file mode 100644
index 00000000..63f203ed
--- /dev/null
+++ b/editor/pbmpscale.c
@@ -0,0 +1,199 @@
+/* pbmpscale.c - pixel scaling with jagged edge smoothing.
+ * AJCD 13/8/90
+ */
+
+#include <stdio.h>
+#include "pbm.h"
+#include "mallocvar.h"
+
+/* prototypes */
+void nextrow_pscale ARGS((FILE *ifd, int row));
+int corner ARGS((int pat));
+
+/* input bitmap size and storage */
+int rows, columns, format ;
+bit *inrow[3] ;
+
+#define thisrow (1)
+
+/* compass directions from west clockwise */
+int xd_pscale[] = { -1, -1,  0,  1, 1, 1, 0, -1 } ;
+int yd_pscale[] = {  0, -1, -1, -1, 0, 1, 1,  1 } ;
+
+/* starting positions for corners */
+#define NE(f) ((f) & 3)
+#define SE(f) (((f) >> 2) & 3)
+#define SW(f) (((f) >> 4) & 3)
+#define NW(f) (((f) >> 6) & 3)
+
+typedef unsigned short sixteenbits ;
+
+/* list of corner patterns; bit 7 is current color, bits 0-6 are squares
+ * around (excluding square behind), going clockwise.
+ * The high byte of the patterns is a mask, which determines which bits are
+ * not ignored.
+ */
+
+sixteenbits patterns[] = { 0x0000, 0xd555,         /* no corner */
+                           0x0001, 0xffc1, 0xd514, /* normal corner */
+                           0x0002, 0xd554, 0xd515, /* reduced corners */
+                           0xbea2, 0xdfc0, 0xfd81,
+                           0xfd80, 0xdf80,
+                           0x0003, 0xbfa1, 0xfec2 /* reduced if > 1 */
+                           };
+
+/* search for corner patterns, return type of corner found:
+ *  0 = no corner,
+ *  1 = normal corner,
+ *  2 = reduced corner,
+ *  3 = reduced if cutoff > 1
+ */
+
+int corner(pat)
+     int pat;
+{
+   register int i, r=0;
+   for (i = 0; i < sizeof(patterns)/sizeof(sixteenbits); i++)
+      if (patterns[i] < 0x100)
+         r = patterns[i];
+      else if ((pat & (patterns[i] >> 8)) ==
+               (patterns[i] & (patterns[i] >> 8)))
+         return r;
+   return 0;
+}
+
+/* get a new row
+ */
+
+void nextrow_pscale(ifd, row)
+     FILE *ifd;
+     int row;
+{
+   bit *shuffle = inrow[0] ;
+   inrow[0] = inrow[1];
+   inrow[1] = inrow[2];
+   inrow[2] = shuffle ;
+   if (row < rows) {
+      if (shuffle == NULL)
+         inrow[2] = shuffle = pbm_allocrow(columns);
+      pbm_readpbmrow(ifd, inrow[2], columns, format) ;
+   } else inrow[2] = NULL; /* discard storage */
+
+}
+
+int
+main(argc, argv)
+     int argc;
+     char *argv[];
+{
+   FILE *ifd;
+   register bit *outrow;
+   register int row, col, i, k;
+   int scale, cutoff, ucutoff ;
+   unsigned char *flags;
+
+   pbm_init( &argc, argv );
+
+   if (argc < 2)
+      pm_usage("scale [pbmfile]");
+
+   scale = atoi(argv[1]);
+   if (scale < 1)
+      pm_perror("bad scale (< 1)");
+
+   if (argc == 3)
+      ifd = pm_openr(argv[2]);
+   else
+      ifd = stdin ;
+
+   inrow[0] = inrow[1] = inrow[2] = NULL;
+   pbm_readpbminit(ifd, &columns, &rows, &format) ;
+
+   outrow = pbm_allocrow(columns*scale) ;
+   MALLOCARRAY(flags, columns);
+   if (flags == NULL) 
+       pm_error("out of memory") ;
+
+   pbm_writepbminit(stdout, columns*scale, rows*scale, 0) ;
+
+   cutoff = scale / 2;
+   ucutoff = scale - 1 - cutoff;
+   nextrow_pscale(ifd, 0);
+   for (row = 0; row < rows; row++) {
+      nextrow_pscale(ifd, row+1);
+      for (col = 0; col < columns; col++) {
+         flags[col] = 0 ;
+         for (i = 0; i != 8; i += 2) {
+            int vec = inrow[thisrow][col] != PBM_WHITE;
+            for (k = 0; k < 7; k++) {
+               int x = col + xd_pscale[(k+i)&7] ;
+               int y = thisrow + yd_pscale[(k+i)&7] ;
+               vec <<= 1;
+               if (x >=0 && x < columns && inrow[y])
+                  vec |= (inrow[y][x] != PBM_WHITE) ;
+            }
+            flags[col] |= corner(vec)<<i ;
+         }
+      }
+      for (i = 0; i < scale; i++) {
+         bit *ptr = outrow ;
+         int zone = (i > ucutoff) - (i < cutoff) ;
+         int cut = (zone < 0) ? (cutoff - i) :
+                   (zone > 0) ? (i - ucutoff) : 0 ;
+
+         for (col = 0; col < columns; col++) {
+            int pix = inrow[thisrow][col] ;
+            int flag = flags[col] ;
+            int cutl, cutr ;
+
+            switch (zone) {
+            case -1:
+               switch (NW(flag)) {
+               case 0: cutl = 0; break;
+               case 1: cutl = cut; break;
+               case 2: cutl = cut ? cut-1 : 0; break;
+               case 3: cutl = (cut && cutoff > 1) ? cut-1 : cut; break;
+               default: cutl = 0;  /* Should never reach here */
+               }
+               switch (NE(flag)) {
+               case 0: cutr = 0; break;
+               case 1: cutr = cut; break;
+               case 2: cutr = cut ? cut-1 : 0; break;
+               case 3: cutr = (cut && cutoff > 1) ? cut-1 : cut; break;
+               default: cutr = 0;  /* Should never reach here */
+              }
+               break;
+            case 0:
+               cutl = cutr = 0;
+               break ;
+            case 1:
+               switch (SW(flag)) {
+               case 0: cutl = 0; break;
+               case 1: cutl = cut; break;
+               case 2: cutl = cut ? cut-1 : 0; break;
+               case 3: cutl = (cut && cutoff > 1) ? cut-1 : cut; break;
+               default: cutl = 0;  /* should never reach here */
+               }
+               switch (SE(flag)) {
+               case 0: cutr = 0; break;
+               case 1: cutr = cut; break;
+               case 2: cutr = cut ? cut-1 : 0; break;
+               case 3: cutr = (cut && cutoff > 1) ? cut-1 : cut; break;
+               default: cutr = 0;  /* should never reach here */
+               }
+               break;
+             default: cutl = 0; cutr = 0;  /* Should never reach here */
+            }
+            for (k = 0; k < cutl; k++) /* left part */
+               *ptr++ = !pix ;
+            for (k = 0; k < scale-cutl-cutr; k++)  /* centre part */
+               *ptr++ = pix ;
+            for (k = 0; k < cutr; k++) /* right part */
+               *ptr++ = !pix ;
+         }
+         pbm_writepbmrow(stdout, outrow, scale*columns, 0) ;
+      }
+   }
+   pm_close(ifd);
+   exit(0);
+}
diff --git a/editor/pbmreduce.c b/editor/pbmreduce.c
new file mode 100644
index 00000000..15ec2a1b
--- /dev/null
+++ b/editor/pbmreduce.c
@@ -0,0 +1,208 @@
+/* pbmreduce.c - read a portable bitmap and reduce it N times
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+#include "mallocvar.h"
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    register bit** bitslice;
+    register bit* newbitrow;
+    register bit* nbP;
+    int argn, n, rows, cols, format, newrows, newcols;
+    int row, col, limitcol, subrow, subcol, count, direction;
+    const char* const usage = "[-floyd|-fs | -threshold] [-value <val>] N [pbmfile]";
+    int halftone;
+#define QT_FS 1
+#define QT_THRESH 2
+#define SCALE 1024
+#define HALFSCALE 512
+    long threshval, sum;
+    long* thiserr;  /* used for Floyd-Steinberg stuff */
+    long* nexterr;  /* used for Floyd-Steinberg stuff */
+    long* temperr;
+
+
+    pbm_init( &argc, argv );
+
+    argn = 1;
+    halftone = QT_FS;
+    threshval = HALFSCALE;
+
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+	{
+	if ( pm_keymatch( argv[argn], "-fs", 2 ) ||
+	     pm_keymatch( argv[argn], "-floyd", 2 ) )
+	    halftone = QT_FS;
+	else if ( pm_keymatch( argv[argn], "-threshold", 2 ) )
+	    halftone = QT_THRESH;
+	else if ( pm_keymatch( argv[argn], "-value", 2 ) )
+	    {
+	    float f;
+
+	    ++argn;
+	    if ( argn == argc || sscanf( argv[argn], "%f", &f ) != 1 ||
+		 f < 0.0 || f > 1.0 )
+		pm_usage( usage );
+	    threshval = f * SCALE;
+	    }
+	else
+	    pm_usage( usage );
+	++argn;
+	}
+
+    if ( argn == argc )
+	pm_usage( usage );
+    if ( sscanf( argv[argn], "%d", &n ) != 1 )
+	pm_usage( usage );
+    if ( n < 2 )
+	pm_error( "N must be greater than 1" );
+    ++argn;
+
+    if ( argn == argc )
+	ifp = stdin;
+    else
+	{
+	ifp = pm_openr( argv[argn] );
+	++argn;
+	}
+
+    if ( argn != argc )
+	pm_usage( usage );
+
+    pbm_readpbminit( ifp, &cols, &rows, &format );
+    bitslice = pbm_allocarray( cols, n );
+
+    newrows = rows / n;
+    newcols = cols / n;
+    pbm_writepbminit( stdout, newcols, newrows, 0 );
+    newbitrow = pbm_allocrow( newcols );
+
+    if ( halftone == QT_FS ) {
+        /* Initialize Floyd-Steinberg. */
+        MALLOCARRAY(thiserr, newcols + 2);
+        MALLOCARRAY(nexterr, newcols + 2);
+        if ( thiserr == NULL || nexterr == NULL )
+          pm_error( "out of memory" );
+
+        srand( (int) ( time( 0 ) ^ getpid( ) ) );
+        for ( col = 0; col < newcols + 2; ++col )
+          thiserr[col] = ( rand( ) % SCALE - HALFSCALE ) / 4;
+	    /* (random errors in [-SCALE/8 .. SCALE/8]) */
+	} else {
+        /* These variables are meaningless in this case, and the values
+           should never be used.
+           */
+        thiserr = NULL;
+        nexterr = NULL;
+    }
+    direction = 1;
+
+    for ( row = 0; row < newrows; ++row )
+	{
+	for ( subrow = 0; subrow < n; ++subrow )
+	    pbm_readpbmrow( ifp, bitslice[subrow], cols, format );
+
+	if ( halftone == QT_FS )
+	    for ( col = 0; col < newcols + 2; ++col )
+		nexterr[col] = 0;
+	if ( direction )
+	    {
+	    col = 0;
+	    limitcol = newcols;
+	    nbP = newbitrow;
+	    }
+	else
+	    {
+	    col = newcols - 1;
+	    limitcol = -1;
+	    nbP = &(newbitrow[col]);
+	    }
+
+	do
+	    {
+	    sum = 0;
+	    count = 0;
+	    for ( subrow = 0; subrow < n; ++subrow )
+		for ( subcol = 0; subcol < n; ++subcol )
+		    if ( row * n + subrow < rows && col * n + subcol < cols )
+			{
+			count += 1;
+			if ( bitslice[subrow][col * n + subcol] == PBM_WHITE )
+			    sum += 1;
+			}
+	    sum = ( sum * SCALE ) / count;
+
+	    if ( halftone == QT_FS )
+		sum += thiserr[col + 1];
+
+	    if ( sum >= threshval )
+		{
+		*nbP = PBM_WHITE;
+		if ( halftone == QT_FS )
+		    sum = sum - threshval - HALFSCALE;
+		}
+	    else
+		*nbP = PBM_BLACK;
+
+	    if ( halftone == QT_FS )
+		{
+		if ( direction )
+		    {
+		    thiserr[col + 2] += ( sum * 7 ) / 16;
+		    nexterr[col    ] += ( sum * 3 ) / 16;
+		    nexterr[col + 1] += ( sum * 5 ) / 16;
+		    nexterr[col + 2] += ( sum     ) / 16;
+		    }
+		else
+		    {
+		    thiserr[col    ] += ( sum * 7 ) / 16;
+		    nexterr[col + 2] += ( sum * 3 ) / 16;
+		    nexterr[col + 1] += ( sum * 5 ) / 16;
+		    nexterr[col    ] += ( sum     ) / 16;
+		    }
+		}
+	    if ( direction )
+		{
+		++col;
+		++nbP;
+		}
+	    else
+		{
+		--col;
+		--nbP;
+		}
+	    }
+	while ( col != limitcol );
+
+	pbm_writepbmrow( stdout, newbitrow, newcols, 0 );
+
+	if ( halftone == QT_FS )
+	    {
+	    temperr = thiserr;
+	    thiserr = nexterr;
+	    nexterr = temperr;
+	    direction = ! direction;
+	    }
+	}
+
+    pm_close( ifp );
+    pm_close( stdout );
+
+    exit( 0 );
+    }
+
+
diff --git a/editor/pgmabel.c b/editor/pgmabel.c
new file mode 100644
index 00000000..4914c4be
--- /dev/null
+++ b/editor/pgmabel.c
@@ -0,0 +1,316 @@
+/* pgmabel.c - read a portable graymap and making the deconvolution
+**
+**      Deconvolution of an axial-symmetric image of an rotation symmetrical
+**      process by solving the linear equation system with y-Axis as
+**      symmetry-line
+**
+** Copyright (C) 1997-2006 by German Aerospace Research establishment
+**
+** Author: Volker Schmidt
+**         lefti@voyager.boerde.de
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** $HISTORY:
+**
+** 24 Jan 2002 : 001.009 :  some optimzization
+** 22 Jan 2002 : 001.008 :  some stupid calculations changed
+** 08 Aug 2001 : 001.007 :  new usage (netpbm-conform)
+** 27 Jul 1998 : 001.006 :  First try of error correction
+** 26 Mar 1998 : 001.005 :  Calculating the dl's before transformation
+** 06 Feb 1998 : 001.004 :  Include of a logo in the upper left edge
+** 26 Nov 1997 : 001.003 :  Some bug fixes and reading only lines
+** 25 Nov 1997 : 001.002 :  include of pixsize for getting scale invariant
+** 03 Sep 1997 : 001.001 :  only define for PID2
+** 03 Sep 1997 : 001.000 :  First public release
+** 21 Aug 1997 : 000.909 :  Recalculate the streching-factor
+** 20 Aug 1997 : 000.908 :  -left and -right for calculating only one side
+** 20 Aug 1997 : 000.906 :  correction of divisor, include of -factor
+** 15 Aug 1997 : 000.905 :  Include of -help and -axis
+*/
+
+static const char* const version="$VER: pgmabel 1.009 (24 Jan 2002)";
+
+#include <math.h>
+#include <stdlib.h>   /* for calloc */
+#include "pgm.h"
+#include "mallocvar.h"
+
+#ifndef PID2          /*  PI/2 (on AMIGA always defined) */
+#define PID2    1.57079632679489661923  
+#endif
+
+#define TRUE 1
+#define FALSE 0
+
+/* some global variables */
+static double *aldl, *ardl;                /* pointer for weighting factors */
+
+/* ----------------------------------------------------------------------------
+** procedure for calculating the sum of the calculated surfaces with the
+** weight of the surface
+**      n     <-  index of end point of the summation
+**      N     <-  width of the calculated row
+**      xr    <-  array of the calculated elements of the row
+**      adl   <-  pre-calculated surface coefficient for each segment
+*/
+static double 
+Sum ( int n, double *xr, int N, double *adl)
+{
+    int k;
+    double result=0.0;
+
+    if (n==0) return(0.0);             /* outer ring  is 0 per definition    */
+    for (k=0 ; k<=(n-1) ; k++)
+    {
+         result += xr[k] * ( adl[k*N+n] - adl[(k+1)*N+n]);
+/*       result += xr[k] * ( dr(k,n+0.5,N) - dr(k+1,n+0.5,N));   */
+    }
+    return(result);
+}
+
+/* ----------------------------------------------------------------------------
+** procedure for calculating the surface coefficient for the Integration
+**      R, N  <-  indizes of the coefficient
+**      r     <-  radial position of the center of the surface
+*/
+static double 
+dr ( int R, double r,  int N)
+{
+    double a;
+    double b;
+    a=(double) N-R ;
+    b=(double) N-r ;
+    return(sqrt(a*a-b*b));
+}
+
+/* ----------------------------------------------------------------------------
+** procedure for making the Abel integration for deconvolution of the image
+**        y    <-> array with values for deconvolution and results
+**        N    <-  width of the array
+**        adl  <-  array with pre-calculated weighting factors
+*/
+static void 
+abel ( float *y, int N, double *adl)
+{
+    register int n;
+    double *rho, *rhop;       /* results and new index                       */
+    float  *yp;               /* new indizes for the y-array                 */
+
+    MALLOCARRAY(rho, N);
+    if( !rho )
+        pm_error( "out of memory" );
+    rhop = rho;
+    yp  = y;
+
+    for (n=0 ; n<N ; n++)
+    {
+        *(rhop++) = ((*yp++) - Sum(n,rho,N,adl))/(adl[n*N+n]);
+/*    *(rhop++) = ((*yp++) - Sum(n,rho,N))/(dr(n,n+0.5,N));  old version */
+        if ( *rhop < 0.0 ) *rhop = 0.0;         /*  error correction !       */
+/*   if (n > 2) rhop[n-1] = (rho[n-2]+rho[n-1]+rho[n])/3.0;  stabilization*/
+    }
+    for (n=0 ; n<N ; n++)
+        {
+            if (( n>=1 )&&( n<N-1 ))
+	       (*y++) = ((rho[n-1]*0.5+rho[n]+rho[n+1]*0.5)/2.0);/*1D median filter*/
+            else (*y++) = rho[n];
+        }
+    free(rho);
+}
+
+/* ----------------------------------------------------------------------------
+** printing a help message if Option -h(elp) is chosen
+*/
+static void 
+help()
+{
+    pm_message("-----------------------------------------------------------------");
+    pm_message("| pgmabel                                                       |");
+    pm_message("| make a deconvolution with vertical axis as symmetry-line      |");
+    pm_message("| usage:                                                        |");
+    pm_message("| pgmabel [-help] [-axis N] [-factor N] [-left|-right]          |");
+    pm_message("|         [-pixsize] [-verbose] [pgmfile]                       |");
+    pm_message("|   axis    : horizontal position of the axis                   |");
+    pm_message("|   factor  : user defines stretch-factor for the gray levels   |");
+    pm_message("|   pixsize : size of one pixel in mm (default = 0.1)           |");
+    pm_message("|   left    : calculating only the left (or right) side         |");
+    pm_message("|   verbose : output of useful data                             |");
+    pm_message("|   pgmfile : Name of a pgmfile (optional)                      |");
+    pm_message("|                                                               |");
+    pm_message("| for further information please contact the manpage            |"); 
+    pm_message("-----------------------------------------------------------------");
+    pm_message("%s",version);     /* telling the version      */
+    exit(-1);                     /* retur-code for no result */
+}
+
+
+
+
+
+/* ----------------------------------------------------------------------------
+** main program
+*/
+int main( argc, argv )
+    int    argc;
+    char*  argv[];
+{
+    FILE*  ifp;
+    gray maxval;                            /* maximum gray-level            */
+    gray* grayorig;
+    gray* grayrow;                          /* one line in the image         */
+    int argn, rows, cols, row, format;
+    int col, midcol=0, temp, tc;
+    float *trow;                          /* temporary row for deconvolution */
+    float l_div, r_div, fac=1.0, cfac=4.0;  /* factor for scaling gray-level */
+    float pixsize=0.1;
+    /* no verbose, calculating both sides                                */
+    int verb = FALSE, left = TRUE, right = TRUE;
+    int nologo = FALSE;
+    const char* const usage = "[-help] [-axis N] [-factor N] [-pixsize N] [-left|-right] [-verbose] [pgmfile]";
+
+    pgm_init( &argc, argv );
+    argn = 1;
+
+    /* Check for flags. */
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+        {
+        if ( pm_keymatch( argv[argn], "-help", 1 ) ) help();
+        else if ( pm_keymatch( argv[argn], "-axis", 1 ) )
+            {
+            ++argn;
+            if ( argn == argc || sscanf( argv[argn], "%i", &midcol ) !=1 )
+                pm_usage( usage );
+            }
+        else if ( pm_keymatch( argv[argn], "-factor", 1 ) )
+            {
+            ++argn;
+            if ( argn == argc || sscanf( argv[argn], "%f", &fac ) !=1 )
+                pm_usage( usage );
+            }
+        else if ( pm_keymatch( argv[argn], "-pixsize", 1 ) )
+            {
+            ++argn;
+            if ( argn == argc || sscanf( argv[argn], "%f", &pixsize ) !=1 )
+                pm_usage( usage );
+            }
+        else if ( pm_keymatch( argv[argn], "-verbose", 1 ) )
+            {
+                verb = TRUE;
+            }
+        else if ( pm_keymatch( argv[argn], "-left", 1 ) )
+            {
+                if ( left ) right = FALSE;
+                else pm_usage( usage );
+            }
+        else if ( pm_keymatch( argv[argn], "-right", 1 ) )
+           {
+                if ( right ) left = FALSE;
+                else pm_usage( usage );
+            }
+        else if ( pm_keymatch( argv[argn], "-nologo", 4 ) )
+            {
+                nologo = TRUE;
+            }
+        else
+            pm_usage( usage );
+        ++ argn;
+        }
+    if ( argn < argc )
+        {
+        ifp = pm_openr( argv[argn] );                    /* open the picture */
+        ++argn;
+        }
+    else
+        ifp = stdin;                                /* or reading from STDIN */
+    if ( argn != argc )
+        pm_usage( usage );
+
+    pgm_readpgminit( ifp, &cols, &rows, &maxval, &format );  /* read picture  */
+    pgm_writepgminit( stdout, cols, rows, maxval, 0 );  /* write the header  */
+    grayorig = pgm_allocrow(cols);
+    grayrow = pgm_allocrow( cols );                     /* allocate a row    */
+
+    if (midcol == 0) midcol = cols/2;     /* if no axis set take the center */
+    if (left ) l_div = (float)(PID2*pixsize)/(cfac*fac);
+    else l_div=1.0;                              /* weighting the left side  */
+    if (right) r_div = (float)(PID2*pixsize)/(cfac*fac);
+    else r_div=1.0;                              /* weighting the right side */
+
+    if (verb)
+    {
+        pm_message("%s",version);
+        pm_message("Calculating a portable graymap with %i rows and %i cols",rows,cols);
+        pm_message("  resuming a pixelsize of %f mm",pixsize);
+        if ( !right ) pm_message("     only the left side!");
+        if ( !left ) pm_message("     only the right side!");
+        pm_message("  axis = %i, stretching factor = %f",midcol,cfac*fac);
+        if ( left ) pm_message("  left side weighting = %f",l_div);
+        if ( right ) pm_message(" right side weighting = %f",r_div);
+    }
+
+    /* allocating the memory for the arrays aldl and ardl                    */
+    aldl = calloc ( midcol*midcol, sizeof(double));
+    if( !aldl )
+        pm_error( "out of memory" );
+    ardl = calloc ( (cols-midcol)*(cols-midcol), sizeof(double));
+    if( !ardl )
+        pm_error( "out of memory" );
+
+    MALLOCARRAY(trow, cols);
+    if( !trow )
+        pm_error( "out of memory" );
+
+    /* now precalculating the weighting-factors for the abel-transformation  */
+    for (col = 0; col < midcol; ++col)             /* factors for left side  */
+    {
+        for (tc = 0; tc < midcol; ++tc) aldl[col*midcol+tc] = dr(col,tc+0.5,midcol);
+    }
+    for (col = 0; col < (cols-midcol); ++col)      /* factors for right side */
+    {
+        for (tc = 0; tc < (cols-midcol); ++tc) 
+            ardl[col*(cols-midcol)+tc] = dr(col,tc+0.5,cols-midcol);
+    }
+
+    /* abel-transformation for each row splitted in right and left side      */
+    for ( row = 0; row < rows ; ++row )
+    {
+        pgm_readpgmrow( ifp, grayorig, cols, maxval, format );
+        for ( col = 0; col < midcol; ++col)          /* left side            */
+        {
+            trow[col] = (float) (grayorig[col]);
+        }
+        if (left ) abel(trow, midcol, aldl);         /* deconvolution        */
+        for ( col = 0; col < midcol; ++col)          /* writing left side    */
+        {
+            temp = (int)(trow[col]/l_div);
+            grayrow[col] = (temp>0?temp:0);
+        }
+        for ( col = midcol; col < cols; ++col )      /* right side           */
+        {
+            trow[cols-col-1] = (float) (grayorig[col]);
+        }
+        if ( right ) abel(trow,(cols-midcol),ardl);  /* deconvolution        */
+        for ( col = midcol; col < cols; ++col)       /* writing right side   */
+        {
+            temp = (int)(trow[cols-col-1]/r_div);
+            temp = (temp>0?temp:0);
+            grayrow[col] = temp;
+        }
+        pgm_writepgmrow( stdout, grayrow, cols, maxval, 0 );  /* saving row  */
+    }
+    pm_close( ifp );
+    pm_close( stdout );               /* closing output                      */
+    free( trow );                     /* deconvolution is done, clear memory */
+    pgm_freerow( grayorig );
+    pgm_freerow( grayrow );
+    free(aldl);
+    free(ardl);                      /* all used memory freed (i hope)       */
+    exit( 0 );                       /* end of procedure                     */
+}
+
diff --git a/editor/pgmbentley.c b/editor/pgmbentley.c
new file mode 100644
index 00000000..9cc86a91
--- /dev/null
+++ b/editor/pgmbentley.c
@@ -0,0 +1,64 @@
+/* pgmbentley.c - read a portable graymap and smear it according to brightness
+**
+** Copyright (C) 1990 by Wilson Bent (whb@hoh-2.att.com)
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <stdio.h>
+#include "pgm.h"
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    gray maxval;
+    gray** gin;
+    gray** gout;
+    int argn, rows, cols, row;
+    register int brow, col;
+    const char* const usage = "[pgmfile]";
+
+
+    pgm_init( &argc, argv );
+
+    argn = 1;
+
+    if ( argn < argc )
+	{
+	ifp = pm_openr( argv[argn] );
+	++argn;
+	}
+    else
+	ifp = stdin;
+
+    if ( argn != argc )
+	pm_usage( usage );
+
+    gin = pgm_readpgm( ifp, &cols, &rows, &maxval );
+    pm_close( ifp );
+    gout = pgm_allocarray( cols, rows );
+
+#define N 4
+    for ( row = 0; row < rows; ++row )
+	for ( col = 0; col < cols; ++col )
+	    {
+	    brow = row + (int) (gin[row][col]) / N;
+	    if ( brow >= rows )
+		brow = rows - 1;
+	    gout[brow][col] = gin[row][col];
+	    }
+
+    pgm_writepgm( stdout, gout, cols, rows, maxval, 0 );
+    pm_close( stdout );
+    pgm_freearray( gout, rows );
+
+    exit( 0 );
+    }
diff --git a/editor/pgmdeshadow.c b/editor/pgmdeshadow.c
new file mode 100644
index 00000000..482c6661
--- /dev/null
+++ b/editor/pgmdeshadow.c
@@ -0,0 +1,343 @@
+/*============================================================================
+                        pgmdeshadow
+==============================================================================
+   Read PGM containing scanned black/white text, deshadow, write PGM.
+============================================================================*/
+/*
+    This code is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This code is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this code; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*/
+
+/*
+ * Algorithm reference: Luc Vincent, "Morphological Grayscale Reruction
+ * in Image Analysis: Applications and Efficient Algorithms," IEEE
+ * Transactions on Image Processing, vol. 2, no. 2, April 1993, pp. 176-201.
+ *
+ * The algorithm used here is "fast hybrid grayscale reruction,"
+ * described as follows on pp. 198-199:
+ *
+ * I: mask image (binary or grayscale)
+ * J: marker image, defined on domain D_I, J <= I.
+ *    Reruction is determined directly in J.
+ *
+ * Scan D_I in raster order:
+ *   Let p be the current pixel;
+ *   J(p) <- (max{J(q),q member_of N_G_plus(p) union {p}}) ^ I(p)
+ *       [Note that ^ here refers to "pointwise minimum.]
+ *
+ * Scan D_I in antiraster order:
+ *   Let p be the current pixel;
+ *   J(p) <- (max{J(q),q member_of N_G_minus(p) union {p}}) ^ I(p)
+ *       [Note that ^ here refers to "pointwise minimum.]
+ *   If there exists q member_of N_G_minus(p) such that J(q) < J(p) and
+ *       J(q) < I(q), then fifo_add(p)
+ *
+ * Propagation step:
+ *   While fifo_empty() is false
+ *   p <- fifo_first()
+ *   For every pixel q member_of N_G(p):
+ *     If J(q) < J(p) and I(q) ~= J(q), then
+ *       J(q) <- min{J(p),I(q)}
+ *       fifo_add(q)
+ */
+
+#include <stdio.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "pgm.h"
+
+
+struct cmdlineInfo {
+    const char * inputFileName;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else {
+        cmdlineP->inputFileName = argv[1];
+        if (argc-1 > 1)
+            pm_error ("Too many arguments.  The only argument is "
+                      "the optional input file name");
+    }
+}
+
+
+
+static void
+initializeDeshadowMarker(gray **      const inputPixels,
+                         gray **      const markerPixels,
+                         unsigned int const cols,
+                         unsigned int const rows,
+                         gray         const maxval) {
+/*----------------------------------------------------------------------------
+  Fill the image with maxval and then copy 1-pixel-wide borders
+-----------------------------------------------------------------------------*/
+    { /* Make middle white */
+        unsigned int row;
+        
+        for (row = 1; row < rows-1; ++row) {
+            unsigned int col;
+            for (col = 1; col < cols-1; ++col)
+                markerPixels[row][col] = maxval;
+        }
+    }
+    { /* Copy top edge */
+        unsigned int col;
+        for (col = 0; col < cols; ++col)
+            markerPixels[0][col] = inputPixels[0][col];
+    }
+    { /* Copy bottom edge */
+        unsigned int col;
+        for (col = 0; col < cols; ++col)
+            markerPixels[rows-1][col] = inputPixels[rows-1][col];
+    }
+    { /* Copy left edge */
+        unsigned int row;
+        for (row = 0; row < rows; ++row)
+            markerPixels[row][0] = inputPixels[row][0];
+    }
+    { /* Copy right edge */
+        unsigned int row;
+        for (row = 0; row < rows; ++row)
+            markerPixels[row][cols-1] = inputPixels[row][cols-1];
+    }
+}
+
+
+
+static gray
+min5(gray const a,
+     gray const b,
+     gray const c,
+     gray const d,
+     gray const e) {
+    
+    return MIN(a,MIN(b,MIN(c,MIN(d,e))));
+}
+
+
+
+static gray
+minNortheastPixel(gray **      const pixels,
+                  unsigned int const col,
+                  unsigned int const row) {
+/*----------------------------------------------------------------------------
+  Return the minimum pixel value from among the immediate north-east
+  neighbors of (col, row) in pixels[][].
+-----------------------------------------------------------------------------*/
+    return min5(pixels[row][col],
+                pixels[row][col-1],
+                pixels[row-1][col-1],
+                pixels[row-1][col],
+                pixels[row-1][col+1]);
+}
+
+
+
+static gray
+minSouthwestPixel(gray **      const pixels,
+                  unsigned int const col,
+                  unsigned int const row) {
+/*----------------------------------------------------------------------------
+  Return the minimum pixel value from among the immediate south-west
+  neighbors of (col, row) in pixels[][].
+-----------------------------------------------------------------------------*/
+    return min5(pixels[row][col],
+                pixels[row][col+1],
+                pixels[row+1][col-1],
+                pixels[row+1][col],
+                pixels[row+1][col+1]);
+}
+
+
+
+static void
+estimateBackground(gray **      const inputPixels,
+                   gray **      const markerPixels,
+                   unsigned int const cols,
+                   unsigned int const rows,
+                   gray         const maxval) {
+/*----------------------------------------------------------------------------
+   Update markerPixels[].
+-----------------------------------------------------------------------------*/
+    unsigned int const passes = 2;
+        /* make only two passes since the image is not really complicated
+           (otherwise could go up to 10)
+        */
+
+    unsigned int pass;
+    bool stable;
+
+    for (pass = 0, stable = FALSE; pass < passes && !stable; ++pass) {
+        int row;
+
+        stable = TRUE;  /* initial assumption */
+
+        /* scan in raster order */
+
+        for (row = 1; row < rows; ++row) {
+            unsigned int col;
+            for (col = 1; col < cols-1; ++col) {
+                gray const minpixel =
+                    minNortheastPixel(markerPixels, col, row);
+
+                if (minpixel > inputPixels[row][col]) {
+                    markerPixels[row][col] = minpixel;
+                    stable = FALSE;
+                } else
+                    markerPixels[row][col] = inputPixels[row][col];
+            }       
+        }
+        /* scan in anti-raster order */
+        
+        for (row = rows-2; row >= 0; --row) {
+            int col;
+            for (col = cols-2; col > 0; --col) {
+                gray const minpixel =
+                    minSouthwestPixel(markerPixels, col, row);
+                
+                if (minpixel > inputPixels[row][col]) {
+                    markerPixels[row][col] = minpixel;
+                    stable = FALSE;
+                } else
+                    markerPixels[row][col] = inputPixels[row][col];
+            }
+        }
+    }
+}
+
+
+
+static void
+divide(gray **      const dividendPixels,
+       gray **      const divisorPixels,
+       unsigned int const cols,
+       unsigned int const rows,
+       gray         const maxval) {
+/*----------------------------------------------------------------------------
+   Divide each pixel of dividendPixels[][] by the corresponding pixel
+   in divisorPixels[], replacing the dividendPixels[][] pixel with the
+   quotient.
+
+   But leave a one-pixel border around dividendPixels[][] unmodified.
+
+   Make sure the results are reasonable and not larger than maxval.
+-----------------------------------------------------------------------------*/
+    unsigned int row;
+
+    for (row = 1; row < rows-1; ++row) {
+        unsigned int col;
+        for (col = 1; col < cols-1; ++col) {
+            gray const divisor  = divisorPixels[row][col];
+            gray const dividend = dividendPixels[row][col];
+
+            gray quotient;
+
+            if (divisor == 0)
+                quotient = maxval;
+            else {
+                if (25 * divisor < 3 * maxval && 25 * dividend < 3 * maxval)
+                    quotient = maxval;
+                else
+                    quotient =
+                        MIN(maxval,
+                            maxval * (dividend + dividend/2) / divisor);
+            }        
+            dividendPixels[row][col] = quotient;
+        }
+    }
+}
+
+
+
+static void
+deshadow(gray **      const inputPixels,
+         unsigned int const cols,
+         unsigned int const rows,
+         gray         const maxval) {
+/*----------------------------------------------------------------------------
+   Deshadow the image described by inputPixels[], 'cols', 'rows', and
+   'maxval'.  (Modify inputPixels[][]).
+-----------------------------------------------------------------------------*/
+    gray ** markerPixels;
+
+    markerPixels = pgm_allocarray(cols, rows);
+
+    initializeDeshadowMarker(inputPixels, markerPixels, cols, rows, maxval);
+    
+    estimateBackground(inputPixels, markerPixels, cols, rows, maxval);
+    
+    divide(inputPixels, markerPixels, cols, rows, maxval);
+
+    pgm_freearray(markerPixels, rows);
+}
+
+
+
+int
+main(int argc, char* argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    gray maxval;
+    gray ** pixels;
+    int cols, rows;
+
+    pgm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+    
+    pixels = pgm_readpgm(ifP, &cols, &rows, &maxval);
+    pm_close(ifP);
+    
+    deshadow(pixels, cols, rows, maxval);
+    
+    pgm_writepgm(stdout, pixels, cols, rows, maxval, 0);
+
+    pgm_freearray(pixels, rows);
+
+    return 0;
+}
diff --git a/editor/pgmenhance.c b/editor/pgmenhance.c
new file mode 100644
index 00000000..83670568
--- /dev/null
+++ b/editor/pgmenhance.c
@@ -0,0 +1,112 @@
+/* pgmenhance.c - edge-enhance a portable graymap
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pgm.h"
+
+int
+main(int argc, char * argv[] ) {
+    FILE* ifp;
+    gray* prevrow;
+    gray* thisrow;
+    gray* nextrow;
+    gray* temprow;
+    gray* newrow;
+    int argn, n, rows, cols, row, col;
+    float phi, omphi;
+    gray maxval;
+    int format;
+    const char* const usage = "[-N] [pgmfile]  ( 1 <= N <= 9, default = 9 )";
+
+    pgm_init( &argc, argv );
+
+    argn = 1;
+    n = 9;
+
+    if ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) {
+        if ( sscanf( &(argv[argn][1]), "%d", &n ) != 1 )
+            pm_usage( usage );
+        if ( n < 1 || n > 9 )
+            pm_usage( usage );
+        ++argn;
+    }
+
+    if ( argn != argc ) {
+        ifp = pm_openr( argv[argn] );
+        ++argn;
+    } else
+        ifp = stdin;
+
+    if ( argn != argc )
+        pm_usage( usage );
+
+    pgm_readpgminit( ifp, &cols, &rows, &maxval, &format );
+    prevrow = pgm_allocrow( cols );
+    thisrow = pgm_allocrow( cols );
+    nextrow = pgm_allocrow( cols );
+
+    pgm_writepgminit( stdout, cols, rows, maxval, 0 );
+    newrow = pgm_allocrow( cols );
+
+    /* The edge enhancing technique is taken from Philip R. Thompson's "xim"
+    ** program, which in turn took it from section 6 of "Digital Halftones by
+    ** Dot Diffusion", D. E. Knuth, ACM Transaction on Graphics Vol. 6, No. 4,
+    ** October 1987, which in turn got it from two 1976 papers by J. F. Jarvis
+    ** et. al.
+    */
+    phi = n / 10.0;
+    omphi = 1.0 - phi;
+
+    /* First row. */
+    pgm_readpgmrow( ifp, thisrow, cols, maxval, format );
+    pgm_writepgmrow( stdout, thisrow, cols, maxval, 0 );
+    pgm_readpgmrow( ifp, nextrow, cols, maxval, format );
+
+    /* Other rows. */
+    for ( row = 1; row < rows - 1; row++ ) {
+        temprow = prevrow;
+        prevrow = thisrow;
+        thisrow = nextrow;
+        nextrow = temprow;
+        pgm_readpgmrow( ifp, nextrow, cols, maxval, format );
+        
+        newrow[0] = thisrow[0];
+        for (col = 1; col < cols - 1; col++) {
+            /* Compute the sum of the neighborhood. */
+            long sum, newval;
+            sum =
+                (long) prevrow[col-1] + (long) prevrow[col] +
+                (long) prevrow[col+1] +
+                (long) thisrow[col-1] + (long) thisrow[col] +
+                (long) thisrow[col+1] +
+                (long) nextrow[col-1] + (long) nextrow[col] +
+                (long) nextrow[col+1];
+            /* Now figure new value. */
+            newval = ( ( thisrow[col] - phi * sum / 9 ) / omphi + 0.5 );
+            if ( newval < 0 )
+                newrow[col] = 0;
+            else if ( newval > maxval )
+                newrow[col] = maxval;
+            else
+                newrow[col] = newval;
+        }
+        newrow[cols - 1] = thisrow[cols - 1];
+        pgm_writepgmrow( stdout, newrow, cols, maxval, 0 );
+    }
+    pm_close( ifp );
+    
+    /* Last row. */
+    pgm_writepgmrow( stdout, nextrow, cols, maxval, 0 );
+
+    pm_close( stdout );
+
+    exit( 0 );
+}
diff --git a/editor/pgmmedian.c b/editor/pgmmedian.c
new file mode 100644
index 00000000..5878b1e7
--- /dev/null
+++ b/editor/pgmmedian.c
@@ -0,0 +1,462 @@
+/* 
+** Version 1.0  September 28, 1996
+**
+** Copyright (C) 1996 by Mike Burns <burns@cac.psu.edu>
+**
+** Adapted to Netpbm 2005.08.10 by Bryan Henderson
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* References
+** ----------
+** The select k'th value implementation is based on Algorithm 489 by 
+** Robert W. Floyd from the "Collected Algorithms from ACM" Volume II.
+**
+** The histogram sort is based is described in the paper "A Fast Two-
+** Dimensional Median Filtering Algorithm" in "IEEE Transactions on 
+** Acoustics, Speech, and Signal Processing" Vol. ASSP-27, No. 1, February
+** 1979.  The algorithm I more closely followed is found in "Digital
+** Image Processing Algorithms" by Ioannis Pitas.
+*/
+
+
+#include "pgm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+enum medianMethod {MEDIAN_UNSPECIFIED, SELECT_MEDIAN, HISTOGRAM_SORT_MEDIAN};
+#define MAX_MEDIAN_TYPES      2
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;
+    unsigned int width;
+    unsigned int height;
+    unsigned int cutoff;
+    enum medianMethod type;
+};
+
+
+/* Global variables common to each median sort routine. */
+static int const forceplain = 0;
+static int format;
+static gray maxval;
+static gray **grays;
+static gray *grayrow;
+static gray **rowptr;
+static int ccolso2, crowso2;
+static int row;
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int widthSpec, heightSpec, cutoffSpec, typeSpec;
+    const char * type;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "width",     OPT_UINT, &cmdlineP->width,
+            &widthSpec, 0);
+    OPTENT3(0, "height",    OPT_UINT, &cmdlineP->height,
+            &heightSpec, 0);
+    OPTENT3(0, "cutoff",    OPT_UINT, &cmdlineP->cutoff,
+            &cutoffSpec, 0);
+    OPTENT3(0, "type",    OPT_STRING, &type,
+            &typeSpec, 0);
+
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!widthSpec)
+        cmdlineP->width = 3;
+    if (!heightSpec)
+        cmdlineP->height = 3;
+    if (!cutoffSpec)
+        cmdlineP->cutoff = 250;
+
+    if (typeSpec) {
+        if (STREQ(type, "histogram_sort"))
+            cmdlineP->type = HISTOGRAM_SORT_MEDIAN;
+        else if (STREQ(type, "select"))
+            cmdlineP->type = SELECT_MEDIAN;
+        else
+            pm_error("Invalid value '%s' for -type.  Valid values are "
+                     "'histogram_sort' and 'select'", type);
+    } else
+        cmdlineP->type = MEDIAN_UNSPECIFIED;
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else {
+        cmdlineP->inputFileName = argv[1];
+        if (argc-1 > 1)
+            pm_error ("Too many arguments.  The only argument is "
+                      "the optional input file name");
+    }
+}
+
+
+
+static void
+select_489(gray * const a,
+           int *  const parray,
+           int    const n,
+           int    const k) {
+
+    gray t;
+    int i, j, l, r;
+    int ptmp, ttmp;
+
+    l = 0;
+    r = n - 1;
+    while ( r > l ) {
+        t = a[parray[k]];
+        ttmp = parray[k];
+        i = l;
+        j = r;
+        ptmp = parray[l];
+        parray[l] = parray[k];
+        parray[k] = ptmp;
+        if ( a[parray[r]] > t ) {
+            ptmp = parray[r];
+            parray[r] = parray[l];
+            parray[l] = ptmp;
+        }
+        while ( i < j ) {
+            ptmp = parray[i];
+            parray[i] = parray[j];
+            parray[j] = ptmp;
+            ++i;
+            --j;
+            while ( a[parray[i]] < t )
+                ++i;
+            while ( a[parray[j]] > t )
+                --j;
+        }
+        if ( a[parray[l]] == t ) {
+            ptmp = parray[l];
+            parray[l] = parray[j];
+            parray[j] = ptmp;
+        } else {
+            ++j;
+            ptmp = parray[j];
+            parray[j] = parray[r];
+            parray[r] = ptmp;
+        }
+        if ( j <= k )
+            l = j + 1;
+        if ( k <= j )
+            r = j - 1;
+    }
+}
+
+
+
+static void
+select_median(FILE * const ifp,
+              int    const ccols,
+              int    const crows,
+              int    const cols,
+              int    const rows,
+              int    const median) {
+
+    int ccol, col;
+    int crow;
+    int rownum, irow, temprow;
+    gray *temprptr;
+    int i, leftcol;
+    int num_values;
+    gray *garray;
+
+    int *parray;
+    int addcol;
+    int *subcol;
+    int tsum;
+
+    /* Allocate storage for array of the current gray values. */
+    garray = pgm_allocrow( crows * ccols );
+
+    num_values = crows * ccols;
+
+    parray = (int *) pm_allocrow( crows * ccols, sizeof(int) );
+    subcol = (int *) pm_allocrow( cols, sizeof(int) );
+
+    for ( i = 0; i < cols; ++i )
+        subcol[i] = ( i - (ccolso2 + 1) ) % ccols;
+
+    /* Apply median to main part of image. */
+    for ( ; row < rows; ++row ) {
+        temprow = row % crows;
+        pgm_readpgmrow( ifp, grays[temprow], cols, maxval, format );
+
+        /* Rotate pointers to rows, so rows can be accessed in order. */
+        temprow = ( row + 1 ) % crows;
+        rownum = 0;
+        for ( irow = temprow; irow < crows; ++rownum, ++irow )
+            rowptr[rownum] = grays[irow];
+        for ( irow = 0; irow < temprow; ++rownum, ++irow )
+            rowptr[rownum] = grays[irow];
+
+        for ( col = 0; col < cols; ++col ) {
+            if ( col < ccolso2 || col >= cols - ccolso2 ) {
+                grayrow[col] = rowptr[crowso2][col];
+            } else if ( col == ccolso2 ) {
+                leftcol = col - ccolso2;
+                i = 0;
+                for ( crow = 0; crow < crows; ++crow ) {
+                    temprptr = rowptr[crow] + leftcol;
+                    for ( ccol = 0; ccol < ccols; ++ccol ) {
+                        garray[i] = *( temprptr + ccol );
+                        parray[i] = i;
+                        ++i;
+                    }
+                }
+                select_489( garray, parray, num_values, median );
+                grayrow[col] = garray[parray[median]];
+            } else {
+                addcol = col + ccolso2;
+                for (crow = 0, tsum = 0; crow < crows; ++crow, tsum += ccols)
+                    garray[tsum + subcol[col]] = *(rowptr[crow] + addcol );
+                select_489( garray, parray, num_values, median );
+                grayrow[col] = garray[parray[median]];
+            }
+        }
+        pgm_writepgmrow( stdout, grayrow, cols, maxval, forceplain );
+    }
+
+    /* Write out remaining unchanged rows. */
+    for ( irow = crowso2 + 1; irow < crows; ++irow )
+        pgm_writepgmrow( stdout, rowptr[irow], cols, maxval, forceplain );
+
+    pgm_freerow( garray );
+    pm_freerow( (char *) parray );
+    pm_freerow( (char *) subcol );
+}
+
+
+
+static void
+histogram_sort_median(FILE * const ifp,
+                      int    const ccols,
+                      int    const crows,
+                      int    const cols,
+                      int    const rows,
+                      int    const median) {
+
+    int const histmax = maxval + 1;
+
+    int *hist;
+    int mdn, ltmdn;
+    gray *left_col, *right_col;
+
+    hist = (int *) pm_allocrow( histmax, sizeof( int ) );
+    left_col = pgm_allocrow( crows );
+    right_col = pgm_allocrow( crows );
+
+    /* Apply median to main part of image. */
+    for ( ; row < rows; ++row ) {
+        int col;
+        int temprow;
+        int rownum;
+        int irow;
+        int i;
+        /* initialize hist[] */
+        for ( i = 0; i < histmax; ++i )
+            hist[i] = 0;
+
+        temprow = row % crows;
+        pgm_readpgmrow( ifp, grays[temprow], cols, maxval, format );
+
+        /* Rotate pointers to rows, so rows can be accessed in order. */
+        temprow = ( row + 1 ) % crows;
+        rownum = 0;
+        for ( irow = temprow; irow < crows; ++rownum, ++irow )
+            rowptr[rownum] = grays[irow];
+        for ( irow = 0; irow < temprow; ++rownum, ++irow )
+            rowptr[rownum] = grays[irow];
+
+        for ( col = 0; col < cols; ++col ) {
+            if ( col < ccolso2 || col >= cols - ccolso2 )
+                grayrow[col] = rowptr[crowso2][col];
+            else if ( col == ccolso2 ) {
+                int crow;
+                int const leftcol = col - ccolso2;
+                i = 0;
+                for ( crow = 0; crow < crows; ++crow ) {
+                    int ccol;
+                    gray * const temprptr = rowptr[crow] + leftcol;
+                    for ( ccol = 0; ccol < ccols; ++ccol ) {
+                        gray const g = *( temprptr + ccol );
+                        ++hist[g];
+                        ++i;
+                    }
+                }
+                ltmdn = 0;
+                for ( mdn = 0; ltmdn <= median; ++mdn )
+                    ltmdn += hist[mdn];
+                mdn--;
+                if ( ltmdn > median ) 
+                    ltmdn -= hist[mdn];
+
+                grayrow[col] = mdn;
+            } else {
+                int crow;
+                int const subcol = col - ( ccolso2 + 1 );
+                int const addcol = col + ccolso2;
+                for ( crow = 0; crow < crows; ++crow ) {
+                    left_col[crow] = *( rowptr[crow] + subcol );
+                    right_col[crow] = *( rowptr[crow] + addcol );
+                }
+                for ( crow = 0; crow < crows; ++crow ) {
+                    {
+                        gray const g = left_col[crow];
+                        hist[(int) g]--;
+                        if ( (int) g < mdn )
+                            ltmdn--;
+                    }
+                    {
+                        gray const g = right_col[crow];
+                        hist[(int) g]++;
+                        if ( (int) g < mdn )
+                            ltmdn++;
+                    }
+                }
+                if ( ltmdn > median )
+                    do {
+                        mdn--;
+                        ltmdn -= hist[mdn];
+                    } while ( ltmdn > median );
+                else {
+                    /* This one change from Pitas algorithm can reduce run
+                    ** time by up to 10%.
+                    */
+                    while ( ltmdn <= median ) {
+                        ltmdn += hist[mdn];
+                        mdn++;
+                    }
+                    mdn--;
+                    if ( ltmdn > median ) 
+                        ltmdn -= hist[mdn];
+                }
+                grayrow[col] = mdn;
+            }
+        }
+        pgm_writepgmrow( stdout, grayrow, cols, maxval, forceplain );
+    }
+
+    {
+        /* Write out remaining unchanged rows. */
+        int irow;
+        for ( irow = crowso2 + 1; irow < crows; ++irow )
+            pgm_writepgmrow( stdout, rowptr[irow], cols, maxval, forceplain );
+    }
+    pm_freerow( (char *) hist );
+    pgm_freerow( left_col );
+    pgm_freerow( right_col );
+}
+
+
+
+int
+main(int    argc,
+     char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    int cols, rows;
+    int median;
+    enum medianMethod medianMethod;
+
+    pgm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    ifP = pm_openr(cmdline.inputFileName);
+
+    ccolso2 = cmdline.width / 2;
+    crowso2 = cmdline.height / 2;
+
+    pgm_readpgminit(ifP, &cols, &rows, &maxval, &format);
+    pgm_writepgminit(stdout, cols, rows, maxval, forceplain);
+
+    /* Allocate space for number of rows in mask size. */
+    grays = pgm_allocarray(cols, cmdline.height);
+    grayrow = pgm_allocrow(cols);
+
+    /* Allocate pointers to mask row buffer. */
+    rowptr = pgm_allocarray(1, cmdline.height);
+
+    /* Read in and write out initial rows that won't get changed. */
+    for (row = 0; row < cmdline.height - 1; ++row) {
+        pgm_readpgmrow(ifP, grays[row], cols, maxval, format);
+        /* Write out the unchanged row. */
+        if (row < crowso2)
+            pgm_writepgmrow(stdout, grays[row], cols, maxval, forceplain);
+    }
+
+    median = (cmdline.height * cmdline.width) / 2;
+
+    /* Choose which sort to run. */
+    if (cmdline.type == MEDIAN_UNSPECIFIED) {
+        if ((maxval / ((cmdline.width * cmdline.height) - 1)) < cmdline.cutoff)
+            medianMethod = HISTOGRAM_SORT_MEDIAN;
+        else
+            medianMethod = SELECT_MEDIAN;
+    } else
+        medianMethod = cmdline.type;
+
+    switch (medianMethod) {
+    case SELECT_MEDIAN:
+        select_median(ifP, cmdline.width, cmdline.height, cols, rows, median);
+        break;
+        
+    case HISTOGRAM_SORT_MEDIAN:
+        histogram_sort_median(ifP, cmdline.width, cmdline.height,
+                              cols, rows, median);
+        break;
+    case MEDIAN_UNSPECIFIED:
+        pm_error("INTERNAL ERROR: median unspecified");
+    }
+    
+    pm_close(ifP);
+    pm_close(stdout);
+
+    pgm_freearray(grays, cmdline.height);
+    pgm_freerow(grayrow);
+    pgm_freearray(rowptr, cmdline.height);
+
+    return 0;
+}
+
+
+
+
+
+
diff --git a/editor/pgmmorphconv.c b/editor/pgmmorphconv.c
new file mode 100644
index 00000000..abc4e718
--- /dev/null
+++ b/editor/pgmmorphconv.c
@@ -0,0 +1,253 @@
+/* pgmmorphconv.c - morphological convolutions on a graymap: dilation and 
+** erosion
+**
+** Copyright (C) 2000 by Luuk van Dijk/Mind over Matter
+**
+** Based on 
+** pnmconvol.c - general MxN convolution on a portable anymap
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pm_c_util.h"
+#include "pgm.h"
+
+
+/************************************************************
+ * Dilate 
+ ************************************************************/
+
+static int 
+dilate( bit** template, int trowso2, int tcolso2, 
+        gray** in_image, gray** out_image, 
+        int rows, int cols ){
+
+  int c, r, tc, tr;
+  int templatecount;
+  gray source;
+
+  for( c=0; c<cols; ++c)
+    for( r=0; r<rows; ++r )
+      out_image[r][c] = 0;   /* only difference with erode is here and below */
+  
+  /* 
+   *  for each non-black pixel of the template
+   *  add in to out
+   */
+
+  templatecount=0;
+
+  for( tr=-trowso2; tr<=trowso2; ++tr ){
+    for( tc=-tcolso2; tc<=tcolso2; ++tc ){
+
+      if( template[trowso2+tr][tcolso2+tc] == PBM_BLACK ) continue;
+
+      ++templatecount;
+
+      for( r= ((tr>0)?0:-tr) ; r< ((tr>0)?(rows-tr):rows) ; ++r ){
+        for( c= ((tc>0)?0:-tc) ; c< ((tc>0)?(cols-tc):cols) ; ++c ){
+          source = in_image[r+tr][c+tc];
+          out_image[r][c] = MAX(source, out_image[r][c]);
+        } /* for c */
+      } /* for r */
+    } /* for tr */
+  } /* for tc */
+
+  return templatecount;
+
+} /* dilate */
+
+
+
+/************************************************************
+ * Erode: same as dilate except !!!!
+ ************************************************************/
+
+static int 
+erode( bit** template, int trowso2, int tcolso2, 
+       gray** in_image, gray** out_image, 
+       int rows, int cols ){
+
+  int c, r, tc, tr;
+  int templatecount;
+  gray source;
+
+  for( c=0; c<cols; ++c)
+    for( r=0; r<rows; ++r )
+      out_image[r][c] = PGM_MAXMAXVAL; /* !!!! */
+  
+  /* 
+   *  for each non-black pixel of the template
+   *  add in to out
+   */
+
+  templatecount=0;
+
+  for( tr=-trowso2; tr<=trowso2; ++tr ){
+    for( tc=-tcolso2; tc<=tcolso2; ++tc ){
+
+      if( template[trowso2+tr][tcolso2+tc] == PBM_BLACK ) continue;
+
+      ++templatecount;
+
+      for( r= ((tr>0)?0:-tr) ; r< ((tr>0)?(rows-tr):rows) ; ++r ){
+    for( c= ((tc>0)?0:-tc) ; c< ((tc>0)?(cols-tc):cols) ; ++c ){
+
+      source = in_image[r+tr][c+tc];
+      out_image[r][c] = MIN(source, out_image[r][c]);
+      
+    } /* for c */
+      } /* for r */
+
+
+
+    } /* for tr */
+  } /* for tc */
+
+  return templatecount;
+
+} /* erode */
+
+
+
+/************************************************************
+ *  Main
+ ************************************************************/
+
+
+int main( int argc, char* argv[] ){
+
+  int argn;
+  char operation;
+  const char* usage = "-dilate|-erode|-open|-close <templatefile> [pgmfile]";
+
+  FILE* tifp;   /* template */
+  int tcols, trows;
+  int tcolso2, trowso2;
+  bit** template;
+
+
+  FILE*  ifp;   /* input image */
+  int cols, rows;
+  gray maxval;
+
+  gray** in_image;
+  gray** out_image;
+
+  int templatecount=0;
+
+  pgm_init( &argc, argv );
+
+  /*
+   *  parse arguments
+   */ 
+  
+  ifp = stdin;
+  operation = 'd';
+
+  argn=1;
+  
+  if( argn == argc ) pm_usage( usage );
+  
+  if( pm_keymatch( argv[argn], "-erode", 2  )) { operation='e'; argn++; }
+  else
+  if( pm_keymatch( argv[argn], "-dilate", 2 )) { operation='d'; argn++; }
+  else
+  if( pm_keymatch( argv[argn], "-open", 2   )) { operation='o'; argn++; }
+  else
+  if( pm_keymatch( argv[argn], "-close", 2  )) { operation='c'; argn++; }
+  
+  if( argn == argc ) pm_usage( usage );
+  
+  tifp = pm_openr( argv[argn++] );
+  
+  if( argn != argc ) ifp = pm_openr( argv[argn++] );
+
+  if( argn != argc ) pm_usage( usage );
+
+  
+  /* 
+   * Read in the template matrix.
+   */
+
+  template = pbm_readpbm( tifp, &tcols, &trows );
+  pm_close( tifp );
+
+  if( tcols % 2 != 1 || trows % 2 != 1 )
+    pm_error("the template matrix must have an odd number of "
+             "rows and columns" );
+
+  /* the reason is that we want the middle pixel to be the origin */
+  tcolso2 = tcols / 2; /* template coords run from -tcols/2 .. 0 .. +tcols/2 */
+  trowso2 = trows / 2;
+
+#if 0
+  fprintf(stderr, "template: %d  x %d\n", trows, tcols);
+  fprintf(stderr, "half: %d  x %d\n", trowso2, tcolso2);
+#endif
+
+  /*
+   * Read in the image
+   */
+  
+  in_image = pgm_readpgm( ifp, &cols, &rows, &maxval);
+
+  if( cols < tcols || rows < trows )
+    pm_error("the image is smaller than the convolution matrix" );
+  
+#if 0
+  fprintf(stderr, "image: %d  x %d (%d)\n", rows, cols, maxval);
+#endif
+
+  /* 
+   * Allocate  output buffer and initialize with min or max value 
+   */
+
+  out_image = pgm_allocarray( cols, rows );
+  
+  if( operation == 'd' ){
+    templatecount = dilate(template, trowso2, tcolso2, 
+               in_image, out_image, rows, cols);
+  } 
+  else if( operation == 'e' ){
+    templatecount = erode(template, trowso2, tcolso2, 
+              in_image, out_image, rows, cols);
+  }
+  else if( operation == 'o' ){
+    gray ** eroded_image;
+    eroded_image = pgm_allocarray( cols, rows );
+    templatecount = erode(template, trowso2, tcolso2, 
+                          in_image, eroded_image, rows, cols);
+    templatecount = dilate(template, trowso2, tcolso2, 
+                           eroded_image, out_image, rows, cols);
+    pgm_freearray( eroded_image, rows );
+  }
+  else if( operation == 'c' ){
+    gray ** dilated_image;
+    dilated_image = pgm_allocarray( cols, rows );
+    templatecount = dilate(template, trowso2, tcolso2, 
+                           in_image, dilated_image, rows, cols);
+    templatecount = erode(template, trowso2, tcolso2, 
+                          dilated_image, out_image, rows, cols);
+    pgm_freearray( dilated_image, rows );
+  }
+  
+  if(templatecount == 0 ) pm_error( "The template was empty!" );
+
+  pgm_writepgm( stdout, out_image, cols, rows, maxval, 1 );
+
+  pgm_freearray( out_image, rows );
+  pgm_freearray( in_image, rows );
+  pm_close( ifp );
+
+  exit( 0 );
+
+} /* main */
+
diff --git a/editor/pnmalias.c b/editor/pnmalias.c
new file mode 100644
index 00000000..36b41ce4
--- /dev/null
+++ b/editor/pnmalias.c
@@ -0,0 +1,250 @@
+/* pnmmalias.c - antialias a portable anymap.
+**
+** Copyright (C) 1992 by Alberto Accomazzi, Smithsonian Astrophysical
+** Observatory.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pnm.h"
+
+int
+main(int argc, char * argv[] ) {
+    FILE* ifp;
+    xel* xelrow[3];
+    xel* newxelrow;
+    pixel bgcolorppm, fgcolorppm;
+    register xel* xpP;
+    register xel* xP;
+    register xel* xnP;
+    register xel* nxP;
+    xel bgcolor, fgcolor;
+    int argn, rows, cols, format, newformat, bgonly, fgonly;
+    int bgalias, fgalias;
+    int row;
+    double fmask[9], weight;
+    xelval maxval;
+    xelval newmaxval;
+    const char* const usage = "[-bgcolor <color>] [-fgcolor <color>] [-bonly] [-fonly] [-balias] [-falias] [-weight <w>] [pnmfile]";
+
+    pnm_init( &argc, argv );
+
+    bgonly = fgonly = 0;
+    bgalias = fgalias = 0;
+    weight = 1./3.;
+    argn = 1;
+    PPM_ASSIGN( bgcolorppm, 0, 0, 0);
+    PPM_ASSIGN( fgcolorppm, 0, 0, 0);
+
+    while ( argn < argc && argv[argn][0] == '-' )
+        {
+        if ( pm_keymatch( argv[argn], "-fgcolor", 3 ) ) 
+        {
+        if ( ++argn >= argc ) 
+        pm_usage( usage );
+        else 
+        fgcolorppm = ppm_parsecolor( argv[argn], PPM_MAXMAXVAL );
+        }
+        else if ( pm_keymatch( argv[argn], "-bgcolor", 3 ) ) 
+        {
+        if ( ++argn >= argc ) 
+        pm_usage( usage );
+        else 
+        bgcolorppm = ppm_parsecolor( argv[argn], PPM_MAXMAXVAL );
+        }
+        else if ( pm_keymatch( argv[argn], "-weight", 2 ) ) 
+        {
+        if ( ++argn >= argc ) 
+        pm_usage( usage );
+        else if ( sscanf( argv[argn], "%lf", &weight ) != 1 )
+            pm_usage( usage );
+        else if ( weight >= 1. || weight <= 0. )
+        {
+        pm_message( "weight factor w must be 0.0 < w < 1.0" );
+        pm_usage( usage );
+        }
+        }
+    else if ( pm_keymatch( argv[argn], "-bonly", 3 ) )
+        bgonly = 1;
+    else if ( pm_keymatch( argv[argn], "-fonly", 3 ) )
+        fgonly = 1;
+    else if ( pm_keymatch( argv[argn], "-balias", 3 ) )
+        bgalias = 1;
+    else if ( pm_keymatch( argv[argn], "-falias", 3 ) )
+        fgalias = 1;
+    else if ( pm_keymatch( argv[argn], "-bfalias", 3 ) )
+        bgalias = fgalias = 0;
+    else if ( pm_keymatch( argv[argn], "-fbalias", 3 ) )
+        bgalias = fgalias = 0;
+        else
+            pm_usage( usage );
+        ++argn;
+        }
+
+    if ( argn != argc )
+    {
+    ifp = pm_openr( argv[argn] );
+    ++argn;
+    }
+    else
+    ifp = stdin;
+
+    if ( argn != argc )
+    pm_usage( usage );
+
+    /* normalize mask elements */
+    fmask[4] = weight;
+    fmask[0] = fmask[1] = fmask[2] = fmask[3] = ( 1.0 - weight ) / 8.0;
+    fmask[5] = fmask[6] = fmask[7] = fmask[8] = ( 1.0 - weight ) / 8.0;
+
+    pnm_readpnminit( ifp, &cols, &rows, &maxval, &format );
+   
+    xelrow[0] = pnm_allocrow( cols );
+    xelrow[1] = pnm_allocrow( cols );
+    xelrow[2] = pnm_allocrow( cols );
+    newxelrow = pnm_allocrow( cols );
+
+    /* Promote PBM files to PGM. */
+    if ( PNM_FORMAT_TYPE(format) == PBM_TYPE ) {
+        newformat = PGM_TYPE;
+        newmaxval = PGM_MAXMAXVAL;
+        pm_message( "promoting from PBM to PGM" );
+    } else {
+        newformat = format;
+        newmaxval = maxval;
+    }
+
+    /* Figure out foreground pixel value if none was given */
+    if (PPM_GETR(fgcolorppm) == 0 && PPM_GETG(fgcolorppm) == 0 && 
+        PPM_GETB(fgcolorppm) == 0 ) {
+        if ( PNM_FORMAT_TYPE(newformat) == PGM_TYPE )
+            PNM_ASSIGN1( fgcolor, newmaxval );
+        else 
+            PPM_ASSIGN( fgcolor, newmaxval, newmaxval, newmaxval );
+    } else {
+        if ( PNM_FORMAT_TYPE(newformat) == PGM_TYPE )
+            PNM_ASSIGN1( fgcolor, PPM_GETR( fgcolorppm ) );
+        else 
+            fgcolor = fgcolorppm;
+    }
+
+    if (PPM_GETR(bgcolorppm) != 0 || PPM_GETG(bgcolorppm) != 0 || 
+        PPM_GETB(bgcolorppm) != 0 ) {
+        if ( PNM_FORMAT_TYPE(newformat) == PGM_TYPE )
+            PNM_ASSIGN1( bgcolor, PPM_GETR( bgcolorppm) );
+        else 
+            bgcolor = bgcolorppm;
+    } else {
+        if ( PNM_FORMAT_TYPE(newformat) == PGM_TYPE )
+            PNM_ASSIGN1( bgcolor, 0 );
+        else 
+            PPM_ASSIGN( bgcolor, 0, 0, 0 );
+    }
+
+
+    pnm_readpnmrow( ifp, xelrow[0], cols, newmaxval, format );
+    pnm_readpnmrow( ifp, xelrow[1], cols, newmaxval, format );
+    pnm_writepnminit( stdout, cols, rows, newmaxval, newformat, 0 );
+    pnm_writepnmrow( stdout, xelrow[0], cols, newmaxval, newformat, 0 );
+
+    for ( row = 1; row < rows - 1; ++row ) {
+        int col;
+        int value, valuer, valueg, valueb;
+
+        pnm_readpnmrow( ifp, xelrow[(row+1)%3], cols, newmaxval, format );
+        newxelrow[0] = xelrow[row%3][0];
+        
+        for ( col = 1, xpP = (xelrow[(row-1)%3] + 1), xP = (xelrow[row%3] + 1),
+                  xnP = (xelrow[(row+1)%3] + 1), nxP = (newxelrow+1); 
+              col < cols - 1; ++col, ++xpP, ++xP, ++xnP, ++nxP ) {
+
+            int fgflag, bgflag;
+
+            /* Reset flags if anti-aliasing is to be done on foreground
+             * or background pixels only */
+            if ( ( bgonly && PNM_EQUAL( *xP, fgcolor ) ) ||
+                 ( fgonly && PNM_EQUAL( *xP, bgcolor ) ) ) 
+                bgflag = fgflag = 0;
+            else {
+                /* Do anti-aliasing here: see if pixel is at the border of a
+                 * background or foreground stepwise side */
+                bgflag = 
+                    (PNM_EQUAL(*xpP,bgcolor) && PNM_EQUAL(*(xP+1),bgcolor)) ||
+                    (PNM_EQUAL(*(xP+1),bgcolor) && PNM_EQUAL(*xnP,bgcolor)) ||
+                    (PNM_EQUAL(*xnP,bgcolor) && PNM_EQUAL(*(xP-1),bgcolor)) ||
+                    (PNM_EQUAL(*(xP-1),bgcolor) && PNM_EQUAL(*xpP,bgcolor));
+                fgflag = 
+                    (PNM_EQUAL(*xpP,fgcolor) && PNM_EQUAL(*(xP+1),fgcolor)) ||
+                    (PNM_EQUAL(*(xP+1),fgcolor) && PNM_EQUAL(*xnP,fgcolor)) ||
+                    (PNM_EQUAL(*xnP,fgcolor) && PNM_EQUAL(*(xP-1),fgcolor)) ||
+                    (PNM_EQUAL(*(xP-1),fgcolor) && PNM_EQUAL(*xpP,fgcolor)); 
+            }
+            if ( ( bgflag && bgalias ) || ( fgflag && fgalias ) || 
+                 ( bgflag && fgflag ) )
+                switch( PNM_FORMAT_TYPE( newformat ) ) {   
+                case PGM_TYPE:
+                    value = PNM_GET1(*(xpP-1)) * fmask[0] +
+                        PNM_GET1(*(xpP  )) * fmask[1] + 
+                        PNM_GET1(*(xpP+1)) * fmask[2] +
+                        PNM_GET1(*(xP -1)) * fmask[3] +
+                        PNM_GET1(*(xP   )) * fmask[4] +
+                        PNM_GET1(*(xP +1)) * fmask[5] +
+                        PNM_GET1(*(xnP-1)) * fmask[6] +
+                        PNM_GET1(*(xnP  )) * fmask[7] +
+                        PNM_GET1(*(xnP+1)) * fmask[8] +
+                        0.5;
+                    PNM_ASSIGN1( *nxP, value );
+                    break;
+                default:
+                    valuer= PPM_GETR(*(xpP-1)) * fmask[0] +
+                        PPM_GETR(*(xpP  )) * fmask[1] + 
+                        PPM_GETR(*(xpP+1)) * fmask[2] +
+                        PPM_GETR(*(xP -1)) * fmask[3] +
+                        PPM_GETR(*(xP   )) * fmask[4] +
+                        PPM_GETR(*(xP +1)) * fmask[5] +
+                        PPM_GETR(*(xnP-1)) * fmask[6] +
+                        PPM_GETR(*(xnP  )) * fmask[7] +
+                        PPM_GETR(*(xnP+1)) * fmask[8] +
+                        0.5;
+                    valueg= PPM_GETG(*(xpP-1)) * fmask[0] +
+                        PPM_GETG(*(xpP  )) * fmask[1] + 
+                        PPM_GETG(*(xpP+1)) * fmask[2] +
+                        PPM_GETG(*(xP -1)) * fmask[3] +
+                        PPM_GETG(*(xP   )) * fmask[4] +
+                        PPM_GETG(*(xP +1)) * fmask[5] +
+                        PPM_GETG(*(xnP-1)) * fmask[6] +
+                        PPM_GETG(*(xnP  )) * fmask[7] +
+                        PPM_GETG(*(xnP+1)) * fmask[8] +
+                        0.5;
+                    valueb= PPM_GETB(*(xpP-1)) * fmask[0] +
+                        PPM_GETB(*(xpP  )) * fmask[1] + 
+                        PPM_GETB(*(xpP+1)) * fmask[2] +
+                        PPM_GETB(*(xP -1)) * fmask[3] +
+                        PPM_GETB(*(xP   )) * fmask[4] +
+                        PPM_GETB(*(xP +1)) * fmask[5] +
+                        PPM_GETB(*(xnP-1)) * fmask[6] +
+                        PPM_GETB(*(xnP  )) * fmask[7] +
+                        PPM_GETB(*(xnP+1)) * fmask[8] +
+                        0.5;
+                    PPM_ASSIGN( *nxP, valuer, valueg, valueb );
+                    break;
+                }
+            else
+                *nxP = *xP;
+        }
+
+        newxelrow[cols-1] = xelrow[row%3][cols-1];
+        pnm_writepnmrow( stdout, newxelrow, cols, newmaxval, newformat, 0 );
+    }
+        
+    pnm_writepnmrow( stdout, xelrow[row%3], cols, newmaxval, newformat, 0 );
+    
+    pm_close( ifp );
+    exit ( 0 );
+}
+
diff --git a/editor/pnmcat.c b/editor/pnmcat.c
new file mode 100644
index 00000000..20dbf34d
--- /dev/null
+++ b/editor/pnmcat.c
@@ -0,0 +1,427 @@
+/* pnmcat.c - concatenate portable anymaps
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pnm.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+
+
+enum backcolor {BACK_BLACK, BACK_WHITE, BACK_AUTO};
+
+enum orientation {TOPBOTTOM, LEFTRIGHT};
+
+enum justification {JUST_CENTER, JUST_MIN, JUST_MAX};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char **inputFilespec;  
+    unsigned int nfiles;
+    enum backcolor backcolor;
+    enum orientation orientation;
+    enum justification justification;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    
+    unsigned int leftright, topbottom, black, white, jtop, jbottom,
+        jleft, jright, jcenter;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "leftright",  OPT_FLAG,   NULL, &leftright,   0);
+    OPTENT3(0, "lr",         OPT_FLAG,   NULL, &leftright,   0);
+    OPTENT3(0, "topbottom",  OPT_FLAG,   NULL, &topbottom,   0);
+    OPTENT3(0, "tb",         OPT_FLAG,   NULL, &topbottom,   0);
+    OPTENT3(0, "black",      OPT_FLAG,   NULL, &black,       0);
+    OPTENT3(0, "white",      OPT_FLAG,   NULL, &white,       0);
+    OPTENT3(0, "jtop",       OPT_FLAG,   NULL, &jtop,        0);
+    OPTENT3(0, "jbottom",    OPT_FLAG,   NULL, &jbottom,     0);
+    OPTENT3(0, "jleft",      OPT_FLAG,   NULL, &jleft,       0);
+    OPTENT3(0, "jright",     OPT_FLAG,   NULL, &jright,      0);
+    OPTENT3(0, "jcenter",    OPT_FLAG,   NULL, &jcenter,     0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (leftright + topbottom > 1)
+        pm_error("You may specify only one of -topbottom (-tb) and "
+                 "-leftright (-lr)");
+    else if (leftright)
+        cmdlineP->orientation = LEFTRIGHT;
+    else if (topbottom)
+        cmdlineP->orientation = TOPBOTTOM;
+    else
+        pm_error("You must specify either -leftright or -topbottom");
+
+    if (black + white > 1)
+        pm_error("You may specify only one of -black and -white");
+    else if (black)
+        cmdlineP->backcolor = BACK_BLACK;
+    else if (white)
+        cmdlineP->backcolor = BACK_WHITE;
+    else
+        cmdlineP->backcolor = BACK_AUTO;
+
+    if (jtop + jbottom + jleft + jright + jcenter > 1)
+        pm_error("You may specify onlyone of -jtop, -jbottom, "
+                 "-jleft, and -jright");
+    else {
+        switch (cmdlineP->orientation) {
+        case LEFTRIGHT:
+            if (jleft)
+                pm_error("-jleft is invalid with -leftright");
+            if (jright)
+                pm_error("-jright is invalid with -leftright");
+            if (jtop)
+                cmdlineP->justification = JUST_MIN;
+            else if (jbottom)
+                cmdlineP->justification = JUST_MAX;
+            else if (jcenter)
+                cmdlineP->justification = JUST_CENTER;
+            else
+                cmdlineP->justification = JUST_CENTER;
+            break;
+        case TOPBOTTOM:
+            if (jtop)
+                pm_error("-jtop is invalid with -topbottom");
+            if (jbottom)
+                pm_error("-jbottom is invalid with -topbottom");
+            if (jleft)
+                cmdlineP->justification = JUST_MIN;
+            else if (jright)
+                cmdlineP->justification = JUST_MAX;
+            else if (jcenter)
+                cmdlineP->justification = JUST_CENTER;
+            else
+                cmdlineP->justification = JUST_CENTER;
+            break;
+        }
+    }
+
+    if (argc-1 < 1) {
+        MALLOCARRAY_NOFAIL(cmdlineP->inputFilespec, 1);
+        cmdlineP->inputFilespec[0] = "-";
+        cmdlineP->nfiles = 1;
+    } else {
+        unsigned int i;
+
+        MALLOCARRAY_NOFAIL(cmdlineP->inputFilespec, argc-1);
+        
+        for (i = 0; i < argc-1; ++i)
+            cmdlineP->inputFilespec[i] = argv[1+i];
+        cmdlineP->nfiles = argc-1;
+    }
+}        
+
+
+
+static void
+computeOutputParms(unsigned int     const nfiles,
+                   enum orientation const orientation,
+                   int                    cols[], 
+                   int                    rows[],
+                   xelval                 maxval[],
+                   int                    format[],
+                   int *            const newcolsP,
+                   int *            const newrowsP,
+                   xelval *         const newmaxvalP,
+                   int *            const newformatP) {
+
+    int newcols, newrows;
+    int newformat;
+    xelval newmaxval;
+
+    unsigned int i;
+
+    newcols = 0;
+    newrows = 0;
+
+    for (i = 0; i < nfiles; ++i)	{
+        if (i == 0) {
+            newmaxval = maxval[i];
+            newformat = format[i];
+	    } else {
+            if (PNM_FORMAT_TYPE(format[i]) > PNM_FORMAT_TYPE(newformat))
+                newformat = format[i];
+            if (maxval[i] > newmaxval)
+                newmaxval = maxval[i];
+	    }
+        switch (orientation) {
+        case LEFTRIGHT:
+            newcols += cols[i];
+            if (rows[i] > newrows)
+                newrows = rows[i];
+            break;
+        case TOPBOTTOM:
+            newrows += rows[i];
+            if (cols[i] > newcols)
+                newcols = cols[i];
+            break;
+	    }
+	}
+    *newrowsP   = newrows;
+    *newcolsP   = newcols;
+    *newmaxvalP = newmaxval;
+    *newformatP = newformat;
+}
+
+
+
+static void
+concatenateLeftRight(FILE *             const ofp,
+                     unsigned int       const nfiles,
+                     int                const newcols,
+                     int                const newrows,
+                     xelval             const newmaxval,
+                     int                const newformat,
+                     enum justification const justification,
+                     FILE *                   ifp[],
+                     int                      cols[],
+                     int                      rows[],
+                     xelval                   maxval[],
+                     int                      format[],
+                     xel *                    xelrow[],
+                     xel                      background[]) {
+
+    unsigned int row;
+    
+    xel * const newxelrow = pnm_allocrow(newcols);
+
+    for (row = 0; row < newrows; ++row) {
+        unsigned int new;
+        unsigned int i;
+
+        new = 0;
+        for (i = 0; i < nfiles; ++i) {
+            int padtop;
+
+            switch (justification) {
+            case JUST_MIN:
+                padtop = 0;
+                break;
+            case JUST_MAX:
+                padtop = newrows - rows[i];
+                break;
+            case JUST_CENTER:
+                padtop = ( newrows - rows[i] ) / 2;
+                break;
+            }
+            if (row < padtop || row >= padtop + rows[i]) {
+                unsigned int col;
+                for (col = 0; col < cols[i]; ++col)
+                    newxelrow[new+col] = background[i];
+            } else {
+                if (row != padtop) {
+                    /* first row already read */
+                    pnm_readpnmrow(
+                        ifp[i], xelrow[i], cols[i], maxval[i], format[i] );
+                    pnm_promoteformatrow(
+                        xelrow[i], cols[i], maxval[i], format[i],
+                        newmaxval, newformat );
+                }
+                {
+                    unsigned int col;
+                    for (col = 0; col < cols[i]; ++col)
+                        newxelrow[new+col] = xelrow[i][col];
+                }
+            }
+            new += cols[i];
+        }
+        pnm_writepnmrow(ofp, newxelrow, newcols, newmaxval, newformat, 0);
+    }
+}
+
+
+
+static void
+concatenateTopBottom(FILE *             const ofp,
+                     unsigned int       const nfiles,
+                     int                const newcols,
+                     int                const newrows,
+                     xelval             const newmaxval,
+                     int                const newformat,
+                     enum justification const justification,
+                     FILE *                   ifp[],
+                     int                      cols[],
+                     int                      rows[],
+                     xelval                   maxval[],
+                     int                      format[],
+                     xel *                    xelrow[],
+                     xel                      background[]) {
+
+    int new;
+    xel * const newxelrow = pnm_allocrow(newcols);
+    int padleft;
+    unsigned int i;
+    unsigned int row;
+    
+    i = 0;
+    switch (justification) {
+    case JUST_MIN:
+        padleft = 0;
+        break;
+    case JUST_MAX:
+        padleft = newcols - cols[i];
+        break;
+    case JUST_CENTER:
+        padleft = (newcols - cols[i]) / 2;
+        break;
+    }
+
+    new = 0;
+
+    for (row = 0; row < newrows; ++row) {
+        if (row - new >= rows[i]) {
+            new += rows[i];
+            ++i;
+            if (i >= nfiles)
+                pm_error("INTERNAL ERROR: i > nfiles");
+            switch (justification) {
+            case JUST_MIN:
+                padleft = 0;
+                break;
+            case JUST_MAX:
+                padleft = newcols - cols[i];
+                break;
+            case JUST_CENTER:
+                padleft = (newcols - cols[i]) / 2;
+                break;
+            }
+        }
+        if (row - new > 0) {
+            pnm_readpnmrow(
+                ifp[i], xelrow[i], cols[i], maxval[i], format[i]);
+            pnm_promoteformatrow(
+                xelrow[i], cols[i], maxval[i], format[i],
+                newmaxval, newformat);
+        }
+        {
+            unsigned int col;
+
+            for (col = 0; col < padleft; ++col)
+                newxelrow[col] = background[i];
+            for (col = 0; col < cols[i]; ++col)
+                newxelrow[padleft+col] = xelrow[i][col];
+            for (col = padleft + cols[i]; col < newcols; ++col)
+                newxelrow[col] = background[i];
+        }
+        pnm_writepnmrow(ofp,
+                        newxelrow, newcols, newmaxval, newformat, 0);
+	}
+}
+
+
+
+int
+main(int argc, char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE** ifp;
+    xel** xelrow;
+    xel* background;
+    xelval* maxval;
+    xelval newmaxval;
+    int* rows;
+    int* cols;
+    int* format;
+    int newformat;
+    unsigned int i;
+    int newrows, newcols;
+
+    pnm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    MALLOCARRAY_NOFAIL(ifp,        cmdline.nfiles);
+    MALLOCARRAY_NOFAIL(xelrow,     cmdline.nfiles);
+    MALLOCARRAY_NOFAIL(background, cmdline.nfiles);
+    MALLOCARRAY_NOFAIL(maxval,     cmdline.nfiles);
+    MALLOCARRAY_NOFAIL(rows,       cmdline.nfiles);
+    MALLOCARRAY_NOFAIL(cols,       cmdline.nfiles);
+    MALLOCARRAY_NOFAIL(format,     cmdline.nfiles);
+
+    for (i = 0; i < cmdline.nfiles; ++i) {
+        ifp[i] = pm_openr(cmdline.inputFilespec[i]);
+        pnm_readpnminit(ifp[i], &cols[i], &rows[i], &maxval[i], &format[i]);
+        xelrow[i] = pnm_allocrow(cols[i]);
+    }
+
+    computeOutputParms(cmdline.nfiles, cmdline.orientation,
+                       cols, rows, maxval, format,
+                       &newcols, &newrows, &newmaxval, &newformat);
+
+    for (i = 0; i < cmdline.nfiles; ++i) {
+        /* Read first row just to get a good guess at the background. */
+        pnm_readpnmrow(ifp[i], xelrow[i], cols[i], maxval[i], format[i]);
+        pnm_promoteformatrow(
+            xelrow[i], cols[i], maxval[i], format[i], newmaxval, newformat);
+        switch (cmdline.backcolor) {
+        case BACK_AUTO:
+            background[i] =
+                pnm_backgroundxelrow(
+                    xelrow[i], cols[i], newmaxval, newformat);
+            break;
+        case BACK_BLACK:
+            background[i] = pnm_blackxel(newmaxval, newformat);
+            break;
+        case BACK_WHITE:
+            background[i] = pnm_whitexel(newmaxval, newformat);
+            break;
+        }
+	}
+
+    pnm_writepnminit(stdout, newcols, newrows, newmaxval, newformat, 0);
+
+    switch (cmdline.orientation) {
+    case LEFTRIGHT:
+        concatenateLeftRight(stdout, cmdline.nfiles,
+                             newcols, newrows, newmaxval, newformat,
+                             cmdline.justification,
+                             ifp, cols, rows, maxval, format, xelrow,
+                             background);
+        break;
+    case TOPBOTTOM:
+        concatenateTopBottom(stdout, cmdline.nfiles,
+                             newcols, newrows, newmaxval, newformat,
+                             cmdline.justification,
+                             ifp, cols, rows, maxval, format, xelrow,
+                             background);
+        break;
+    }
+    free(cmdline.inputFilespec);
+
+    for (i = 0; i < cmdline.nfiles; ++i)
+        pm_close(ifp[i]);
+
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/editor/pnmcomp.c b/editor/pnmcomp.c
new file mode 100644
index 00000000..5a8b1d55
--- /dev/null
+++ b/editor/pnmcomp.c
@@ -0,0 +1,459 @@
+/* +-------------------------------------------------------------------+ */
+/* | Copyright 1992, David Koblas.                                     | */
+/* |   Permission to use, copy, modify, and distribute this software   | */
+/* |   and its documentation for any purpose and without fee is hereby | */
+/* |   granted, provided that the above copyright notice appear in all | */
+/* |   copies and that both that copyright notice and this permission  | */
+/* |   notice appear in supporting documentation.  This software is    | */
+/* |   provided "as is" without express or implied warranty.           | */
+/* +-------------------------------------------------------------------+ */
+
+/*
+
+    DON'T ADD NEW FUNCTION TO THIS PROGRAM.  ADD IT TO pamcomp.c INSTEAD.
+
+*/
+
+
+
+#define _BSD_SOURCE    /* Make sure strcasecmp() is in string.h */
+#include <string.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+enum horizPos {BEYONDLEFT, LEFT, CENTER, RIGHT, BEYONDRIGHT};
+enum vertPos {ABOVE, TOP, MIDDLE, BOTTOM, BELOW};
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *underlyingFilespec;  /* '-' if stdin */
+    const char *overlayFilespec;
+    const char *alphaFilespec;
+    const char *outputFilespec;  /* '-' if stdout */
+    int xoff, yoff;   /* value of xoff, yoff options */
+    float opacity;
+    unsigned int alphaInvert;
+    enum horizPos align;
+    enum vertPos valign;
+};
+
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    char *align, *valign;
+    unsigned int xoffSpec, yoffSpec, alignSpec, valignSpec, opacitySpec,
+        alphaSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "invert",     OPT_FLAG,   NULL,                  
+            &cmdlineP->alphaInvert,       0 );
+    OPTENT3(0, "xoff",       OPT_INT,    &cmdlineP->xoff,       
+            &xoffSpec,       0 );
+    OPTENT3(0, "yoff",       OPT_INT,    &cmdlineP->yoff,       
+            &yoffSpec,       0 );
+    OPTENT3(0, "opacity",    OPT_FLOAT, &cmdlineP->opacity,
+            &opacitySpec,       0 );
+    OPTENT3(0, "alpha",      OPT_STRING, &cmdlineP->alphaFilespec,
+            &alphaSpec,  0 );
+    OPTENT3(0, "align",      OPT_STRING, &align,
+            &alignSpec,  0 );
+    OPTENT3(0, "valign",     OPT_STRING, &valign,
+            &valignSpec,  0 );
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+
+    if (!xoffSpec)
+        cmdlineP->xoff = 0;
+    if (!yoffSpec)
+        cmdlineP->yoff = 0;
+    if (!alphaSpec)
+        cmdlineP->alphaFilespec = NULL;
+
+    if (alignSpec) {
+        if (strcasecmp(align, "BEYONDLEFT") == 0)
+            cmdlineP->align = BEYONDLEFT;
+        else if (strcasecmp(align, "LEFT") == 0)
+            cmdlineP->align = LEFT;
+        else if (strcasecmp(align, "CENTER") == 0)
+            cmdlineP->align = CENTER;
+        else if (strcasecmp(align, "RIGHT") == 0)
+            cmdlineP->align = RIGHT;
+        else if (strcasecmp(align, "BEYONDRIGHT") == 0)
+            cmdlineP->align = BEYONDRIGHT;
+        else
+            pm_error("Invalid value for align option: '%s'.  Only LEFT, "
+                     "RIGHT, CENTER, BEYONDLEFT, and BEYONDRIGHT are valid.", 
+                     align);
+    } else 
+        cmdlineP->align = LEFT;
+
+    if (valignSpec) {
+        if (strcasecmp(valign, "ABOVE") == 0)
+            cmdlineP->valign = ABOVE;
+        else if (strcasecmp(valign, "TOP") == 0)
+            cmdlineP->valign = TOP;
+        else if (strcasecmp(valign, "MIDDLE") == 0)
+            cmdlineP->valign = MIDDLE;
+        else if (strcasecmp(valign, "BOTTOM") == 0)
+            cmdlineP->valign = BOTTOM;
+        else if (strcasecmp(valign, "BELOW") == 0)
+            cmdlineP->valign = BELOW;
+        else
+            pm_error("Invalid value for valign option: '%s'.  Only TOP, "
+                     "BOTTOM, MIDDLE, ABOVE, and BELOW are valid.", 
+                     align);
+    } else 
+        cmdlineP->valign = TOP;
+
+    if (!opacitySpec) 
+        cmdlineP->opacity = 1.0;
+
+    if (argc-1 < 1)
+        pm_error("Need at least one argument: file specification of the "
+                 "overlay image.");
+
+    cmdlineP->overlayFilespec = argv[1];
+
+    if (argc-1 >= 2)
+        cmdlineP->underlyingFilespec = argv[2];
+    else
+        cmdlineP->underlyingFilespec = "-";
+
+    if (argc-1 >= 3)
+        cmdlineP->outputFilespec = argv[3];
+    else
+        cmdlineP->outputFilespec = "-";
+
+    if (argc-1 > 3)
+        pm_error("Too many arguments.  Only acceptable arguments are: "
+                 "overlay image, underlying image, output image");
+}
+
+
+
+
+static void
+warnOutOfFrame( int const originLeft,
+                int const originTop, 
+                int const overCols,
+                int const overRows,
+                int const underCols,
+                int const underRows ) {
+    if (originLeft >= underCols)
+        pm_message("WARNING: the overlay is entirely off the right edge "
+                   "of the underlying image.  "
+                   "It will not be visible in the result.  The horizontal "
+                   "overlay position you selected is %d, "
+                   "and the underlying image "
+                   "is only %d pixels wide.", originLeft, underCols );
+    else if (originLeft + overCols <= 0)
+        pm_message("WARNING: the overlay is entirely off the left edge "
+                   "of the underlying image.  "
+                   "It will not be visible in the result.  The horizontal "
+                   "overlay position you selected is %d and the overlay is "
+                   "only %d pixels wide.", originLeft, overCols);
+    else if (originTop >= underRows)
+        pm_message("WARNING: the overlay is entirely off the bottom edge "
+                   "of the underlying image.  "
+                   "It will not be visible in the result.  The vertical "
+                   "overlay position you selected is %d, "
+                   "and the underlying image "
+                   "is only %d pixels high.", originTop, underRows );
+    else if (originTop + overRows <= 0)
+        pm_message("WARNING: the overlay is entirely off the top edge "
+                   "of the underlying image.  "
+                   "It will not be visible in the result.  The vertical "
+                   "overlay position you selected is %d and the overlay is "
+                   "only %d pixels high.", originTop, overRows);
+}
+
+
+
+static void
+computeOverlayPosition(const int underCols, const int underRows,
+                       const int overCols, const int overRows,
+                       const struct cmdlineInfo cmdline, 
+                       int * const originLeftP,
+                       int * const originTopP) {
+/*----------------------------------------------------------------------------
+   Determine where to overlay the overlay image, based on the options the
+   user specified and the realities of the image dimensions.
+
+   The origin may be outside the underlying image (so e.g. *originLeftP may
+   be negative or > image width).  That means not all of the overlay image
+   actually gets used.  In fact, there may be no overlap at all.
+-----------------------------------------------------------------------------*/
+    int xalign, yalign;
+
+    switch (cmdline.align) {
+    case BEYONDLEFT:  xalign = -overCols;              break;
+    case LEFT:        xalign = 0;                      break;
+    case CENTER:      xalign = (underCols-overCols)/2; break;
+    case RIGHT:       xalign = underCols - overCols;   break;
+    case BEYONDRIGHT: xalign = underCols;              break;
+    }
+    switch (cmdline.valign) {
+    case ABOVE:       yalign = -overRows;              break;
+    case TOP:         yalign = 0;                      break;
+    case MIDDLE:      yalign = (underRows-overRows)/2; break;
+    case BOTTOM:      yalign = underRows - overRows;   break;
+    case BELOW:       yalign = underRows;              break;
+    }
+    *originLeftP = xalign + cmdline.xoff;
+    *originTopP  = yalign + cmdline.yoff;
+
+    warnOutOfFrame( *originLeftP, *originTopP, 
+                    overCols, overRows, underCols, underRows );    
+}
+
+
+
+static pixval
+composeComponents(pixval const compA, 
+                  pixval const compB,
+                  float  const distrib,
+                  pixval const maxval) {
+/*----------------------------------------------------------------------------
+  Compose a single component of each of two pixels, with 'distrib' being
+  the fraction of 'compA' in the result, 1-distrib the fraction of 'compB'.
+  
+  Both inputs are based on a maxval of 'maxval', and so is our result.
+  
+  Note that while 'distrib' in the straightforward case is always in
+  [0,1], it can in fact be negative or greater than 1.  We clip the
+  result as required to return a legal pixval.
+-----------------------------------------------------------------------------*/
+    return MIN(maxval, MAX(0, (int)compA * distrib +
+                              (int)compB * (1.0 - distrib) + 
+                              0.5
+                          )
+              );
+}
+
+
+
+static pixel
+composePixels(pixel  const pixelA,
+              pixel  const pixelB,
+              float  const distrib,
+              pixval const maxval) {
+/*----------------------------------------------------------------------------
+  Compose two pixels 'pixelA' and 'pixelB', with 'distrib' being the
+  fraction of 'pixelA' in the result, 1-distrib the fraction of 'pixelB'.
+
+  Both inputs are based on a maxval of 'maxval', and so is our result.
+  
+  Note that while 'distrib' in the straightforward case is always in
+  [0,1], it can in fact be negative or greater than 1.  We clip the
+  result as required to return a legal pixval.
+-----------------------------------------------------------------------------*/
+    pixel retval;
+
+    pixval const red = 
+        composeComponents(PPM_GETR(pixelA), PPM_GETR(pixelB), distrib, maxval);
+    pixval const grn =
+        composeComponents(PPM_GETG(pixelA), PPM_GETG(pixelB), distrib, maxval);
+    pixval const blu = 
+        composeComponents(PPM_GETB(pixelA), PPM_GETB(pixelB), distrib, maxval);
+
+    PPM_ASSIGN(retval, red, grn, blu);
+
+    return retval;
+}
+
+
+
+static void
+composite(int      const originleft, 
+          int      const origintop, 
+          pixel ** const overlayImage, 
+          int      const overlayCols, 
+          int      const overlayRows,
+          xelval   const overlayMaxval, 
+          int      const overlayType,
+          int      const cols, 
+          int      const rows, 
+          xelval   const maxval, 
+          int      const type,
+          gray **  const alpha, 
+          gray     const alphaMax, 
+          bool     const invertAlpha,
+          float    const opacity,
+          FILE *   const ifp, 
+          FILE *   const ofp) {
+/*----------------------------------------------------------------------------
+   Overlay the overlay image 'overlayImage' onto the underlying image
+   which is in file 'ifp', and output the composite to file 'ofp'.
+
+   The underlying image file 'ifp' is positioned after its header.  The
+   width, height, format, and maxval of the underlying image are 'cols',
+   'rows', 'type', and 'maxval'.
+
+   The width, height, format, and maxval of the overlay image are
+   overlayCols, overlayRows, overlayType and overlayMaxval.
+
+   'originleft' and 'origintop' are the coordinates in the underlying
+   image plane where the top left corner of the overlay image is
+   to go.  It is not necessarily inside the underlying image (in fact,
+   may be negative).  Only the part of the overlay that actually intersects
+   the underlying image, if any, gets into the output.
+
+   Note that we modify the overlay image 'overlayImage' to change its
+   format and maxval to the format and maxval of the output.
+-----------------------------------------------------------------------------*/
+    /* otype and oxmaxv are the type and maxval for the composed (output)
+       image, and are derived from that of the underlying and overlay
+       images.
+    */
+    int    const otype = (overlayType < type) ? type : overlayType;
+    xelval const omaxv = pm_lcm(maxval, overlayMaxval, 1, PNM_OVERALLMAXVAL);
+
+    int     row;
+    xel     *pixelrow;
+
+    pixelrow = pnm_allocrow(cols);
+
+    if (overlayType != otype || overlayMaxval != omaxv) {
+        pnm_promoteformat(overlayImage, overlayCols, overlayRows,
+                          overlayMaxval, overlayType, omaxv, otype);
+    }
+
+    pnm_writepnminit(ofp, cols, rows, omaxv, otype, 0);
+
+    for (row = 0; row < rows; ++row) {
+        int col;
+
+        /* Read a row and convert it to the output type */
+        pnm_readpnmrow(ifp, pixelrow, cols, maxval, type);
+
+        if (type != otype || maxval != omaxv)
+            pnm_promoteformatrow(pixelrow, cols, maxval, type, omaxv, otype);
+
+        /* Now overlay the overlay with alpha (if defined) */
+        for (col = 0; col < cols; ++col) {
+            int const ovlcol = col - originleft;
+            int const ovlrow = row - origintop;
+
+            double overlayWeight;
+
+            if (ovlcol >= 0 && ovlcol < overlayCols &&
+                ovlrow >= 0 && ovlrow < overlayRows) {
+
+                if (alpha == NULL) {
+                    overlayWeight = opacity;
+                } else {
+                    double alphaval;
+                    alphaval = 
+                        (double)alpha[ovlrow][ovlcol] / (double)alphaMax;
+                    if (invertAlpha)
+                        alphaval = 1.0 - alphaval;
+                    overlayWeight = alphaval * opacity;
+                }
+
+                pixelrow[col] = composePixels(overlayImage[ovlrow][ovlcol],
+                                              pixelrow[col], 
+                                              overlayWeight, omaxv);
+            }
+        }
+        pnm_writepnmrow(ofp, pixelrow, cols, omaxv, otype, 0);
+    }
+    pnm_freerow(pixelrow);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE    *ifp, *ofp;
+    pixel   **image;
+    int     imageCols, imageRows, imageType;
+    xelval  imageMax;
+    int     cols, rows, type;
+    xelval  maxval;
+    gray    **alpha;
+    int     alphaCols, alphaRows;
+    xelval  alphaMax;
+    struct cmdlineInfo cmdline;
+    int originLeft, originTop;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+        
+    { /* Read the overlay image into 'image' */
+        FILE *fp;
+        fp = pm_openr(cmdline.overlayFilespec);
+        image = 
+            pnm_readpnm(fp, &imageCols, &imageRows, &imageMax, &imageType);
+        pm_close(fp);
+    }
+    if (cmdline.alphaFilespec) {
+        /* Read the alpha mask file into 'alpha' */
+        FILE *fp = pm_openr(cmdline.alphaFilespec);
+        alpha = pgm_readpgm(fp, &alphaCols, &alphaRows, &alphaMax);
+        pm_close(fp);
+            
+        if (imageCols != alphaCols || imageRows != alphaRows)
+            pm_error("Alpha map and overlay image are not the same size");
+    } else
+        alpha = NULL;
+
+    ifp = pm_openr(cmdline.underlyingFilespec);
+
+    ofp = pm_openw(cmdline.outputFilespec);
+
+    pnm_readpnminit(ifp, &cols, &rows, &maxval, &type);
+
+    computeOverlayPosition(cols, rows, imageCols, imageRows, 
+                           cmdline, &originLeft, &originTop);
+
+    composite(originLeft, originTop,
+              image, imageCols, imageRows, imageMax, imageType, 
+              cols, rows, maxval, type, 
+              alpha, alphaMax, cmdline.alphaInvert, cmdline.opacity,
+              ifp, ofp);
+
+    pm_close(ifp);
+    pm_close(ofp);
+
+    /* If the program failed, it previously aborted with nonzero completion
+       code, via various function calls.
+    */
+    return 0;
+}
+
+
diff --git a/editor/pnmconvol.c b/editor/pnmconvol.c
new file mode 100644
index 00000000..0bf44ce3
--- /dev/null
+++ b/editor/pnmconvol.c
@@ -0,0 +1,1989 @@
+/* pnmconvol.c - general MxN convolution on a PNM image
+**
+** Version 2.0.1 January 30, 1995
+**
+** Major rewriting by Mike Burns
+** Copyright (C) 1994, 1995 by Mike Burns (burns@chem.psu.edu)
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* A change history is at the bottom */
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* '-' if stdin */
+    const char *kernelFilespec;
+    unsigned int nooffset;
+};
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "nooffset",     OPT_FLAG,   NULL,                  
+            &cmdlineP->nooffset,       0 );
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 < 1)
+        pm_error("Need at least one argument: file specification of the "
+                 "convolution kernel image.");
+
+    cmdlineP->kernelFilespec = argv[1];
+
+    if (argc-1 >= 2)
+        cmdlineP->inputFilespec = argv[2];
+    else
+        cmdlineP->inputFilespec = "-";
+
+    if (argc-1 > 2)
+        pm_error("Too many arguments.  Only acceptable arguments are: "
+                 "convolution file name and input file name");
+}
+
+
+/* Macros to verify that r,g,b values are within proper range */
+
+#define CHECK_GRAY \
+    if ( tempgsum < 0L ) g = 0; \
+    else if ( tempgsum > maxval ) g = maxval; \
+    else g = tempgsum;
+
+#define CHECK_RED \
+    if ( temprsum < 0L ) r = 0; \
+    else if ( temprsum > maxval ) r = maxval; \
+    else r = temprsum;
+
+#define CHECK_GREEN \
+    if ( tempgsum < 0L ) g = 0; \
+    else if ( tempgsum > maxval ) g = maxval; \
+    else g = tempgsum;
+
+#define CHECK_BLUE \
+    if ( tempbsum < 0L ) b = 0; \
+    else if ( tempbsum > maxval ) b = maxval; \
+    else b = tempbsum;
+
+struct convolveType {
+    void (*ppmConvolver)(const float ** const rweights,
+                         const float ** const gweights,
+                         const float ** const bweights);
+    void (*pgmConvolver)(const float ** const weights);
+};
+
+static FILE* ifp;
+static int crows, ccols, ccolso2, crowso2;
+static int cols, rows;
+static xelval maxval;
+static int format, newformat;
+
+
+
+static void
+computeWeights(xel * const *   const cxels, 
+               int             const ccols, 
+               int             const crows,
+               int             const cformat, 
+               xelval          const cmaxval,
+               bool            const offsetPgm,
+               float ***       const rweightsP,
+               float ***       const gweightsP,
+               float ***       const bweightsP) {
+/*----------------------------------------------------------------------------
+   Compute the convolution matrix in normalized form from the PGM
+   form.  Each element of the output matrix is the actual weight we give an
+   input pixel -- i.e. the thing by which we multiple a value from the
+   input image.
+
+   'offsetPgm' means the PGM convolution matrix is defined in offset form so
+   that it can represent negative values.  E.g. with maxval 100, 50 means
+   0, 100 means 50, and 0 means -50.  If 'offsetPgm' is false, 0 means 0
+   and there are no negative weights.
+-----------------------------------------------------------------------------*/
+    double const scale = (offsetPgm ? 2.0 : 1.0) / cmaxval;
+    double const offset = offsetPgm ? - 1.0 : 0.0;
+
+    float** rweights;
+    float** gweights;
+    float** bweights;
+
+    float rsum, gsum, bsum;
+
+    unsigned int crow;
+
+    /* Set up the normalized weights. */
+    rweights = (float**) pm_allocarray(ccols, crows, sizeof(float));
+    gweights = (float**) pm_allocarray(ccols, crows, sizeof(float));
+    bweights = (float**) pm_allocarray(ccols, crows, sizeof(float));
+
+    rsum = gsum = bsum = 0.0;  /* initial value */
+    
+    for (crow = 0; crow < crows; ++crow) {
+        unsigned int ccol;
+        for (ccol = 0; ccol < ccols; ++ccol) {
+            switch (PNM_FORMAT_TYPE(cformat)) {
+            case PPM_TYPE:
+                rsum += rweights[crow][ccol] =
+                    (PPM_GETR(cxels[crow][ccol]) * scale + offset);
+                gsum += gweights[crow][ccol] =
+                    (PPM_GETG(cxels[crow][ccol]) * scale + offset);
+                bsum += bweights[crow][ccol] =
+                    (PPM_GETB(cxels[crow][ccol]) * scale + offset);
+                break;
+                
+            default:
+                gsum += gweights[crow][ccol] =
+                    (PNM_GET1(cxels[crow][ccol]) * scale + offset);
+                break;
+            }
+        }
+    }
+    *rweightsP = rweights;
+    *gweightsP = gweights;
+    *bweightsP = bweights;
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PPM_TYPE:
+        if (rsum < 0.9 || rsum > 1.1 || gsum < 0.9 || gsum > 1.1 ||
+            bsum < 0.9 || bsum > 1.1) {
+            pm_message("WARNING - this convolution matrix is biased.  " 
+                       "red, green, and blue average weights: %f, %f, %f "
+                       "(unbiased would be 1).",
+                       rsum, gsum, bsum);
+
+            if (rsum < 0 && gsum < 0 && bsum < 0)
+                pm_message("Maybe you want the -nooffset option?");
+        }
+        break;
+
+    default:
+        if (gsum < 0.9 || gsum > 1.1)
+            pm_message("WARNING - this convolution matrix is biased.  "
+                       "average weight = %f (unbiased would be 1)",
+                       gsum);
+        break;
+    }
+}
+
+
+
+/* General PGM Convolution
+**
+** No useful redundancy in convolution matrix.
+*/
+
+static void
+pgm_general_convolve(const float ** const weights) {
+    xel** xelbuf;
+    xel* outputrow;
+    xelval g;
+    int row;
+    xel **rowptr, *temprptr;
+    int toprow, temprow;
+    int i, irow;
+    long tempgsum;
+
+    /* Allocate space for one convolution-matrix's worth of rows, plus
+       a row output buffer.
+    */
+    xelbuf = pnm_allocarray(cols, crows);
+    outputrow = pnm_allocrow(cols);
+
+    /* Allocate array of pointers to xelbuf */
+    rowptr = (xel **) pnm_allocarray(1, crows);
+
+    pnm_writepnminit(stdout, cols, rows, maxval, newformat, 0);
+
+    /* Read in one convolution-matrix's worth of image, less one row. */
+    for (row = 0; row < crows - 1; ++row) {
+        pnm_readpnmrow(ifp, xelbuf[row], cols, maxval, format);
+        if (PNM_FORMAT_TYPE(format) != newformat)
+            pnm_promoteformatrow(xelbuf[row], cols, maxval, format, 
+                                 maxval, newformat);
+        /* Write out just the part we're not going to convolve. */
+        if (row < crowso2)
+            pnm_writepnmrow(stdout, xelbuf[row], cols, maxval, newformat, 0);
+    }
+
+    /* Now the rest of the image - read in the row at the end of
+       xelbuf, and convolve and write out the row in the middle.
+    */
+    for (; row < rows; ++row) {
+        int col;
+        toprow = row + 1;
+        temprow = row % crows;
+        pnm_readpnmrow(ifp, xelbuf[temprow], cols, maxval, format);
+        if (PNM_FORMAT_TYPE(format) != newformat)
+            pnm_promoteformatrow(xelbuf[temprow], cols, maxval, format, 
+                                 maxval, newformat);
+
+        /* Arrange rowptr to eliminate the use of mod function to determine
+           which row of xelbuf is 0...crows.  Mod function can be very costly.
+        */
+        temprow = toprow % crows;
+        i = 0;
+        for (irow = temprow; irow < crows; ++i, ++irow)
+            rowptr[i] = xelbuf[irow];
+        for (irow = 0; irow < temprow; ++irow, ++i)
+            rowptr[i] = xelbuf[irow];
+
+        for (col = 0; col < cols; ++col) {
+            if (col < ccolso2 || col >= cols - ccolso2)
+                outputrow[col] = rowptr[crowso2][col];
+            else {
+                int const leftcol = col - ccolso2;
+                int crow;
+                float gsum;
+                gsum = 0.0;
+                for (crow = 0; crow < crows; ++crow) {
+                    int ccol;
+                    temprptr = rowptr[crow] + leftcol;
+                    for (ccol = 0; ccol < ccols; ++ccol)
+                        gsum += PNM_GET1(*(temprptr + ccol))
+                            * weights[crow][ccol];
+                }
+                tempgsum = gsum + 0.5;
+            CHECK_GRAY;
+            PNM_ASSIGN1( outputrow[col], g );
+            }
+        }
+        pnm_writepnmrow(stdout, outputrow, cols, maxval, newformat, 0);
+    }
+
+    /* Now write out the remaining unconvolved rows in xelbuf. */
+    for (irow = crowso2 + 1; irow < crows; ++irow)
+        pnm_writepnmrow(stdout, rowptr[irow], cols, maxval, newformat, 0 );
+}
+
+
+
+/* PGM Mean Convolution
+**
+** This is the common case where you just want the target pixel replaced with
+** the average value of its neighbors.  This can work much faster than the
+** general case because you can reduce the number of floating point operations
+** that are required since all the weights are the same.  You will only need
+** to multiply by the weight once, not for every pixel in the convolution
+** matrix.
+**
+** This algorithm works by creating sums for each column of crows height for
+** the whole width of the image.  Then add ccols column sums together to obtain
+** the total sum of the neighbors and multiply that sum by the weight.  As you
+** move right to left to calculate the next pixel, take the total sum you just
+** generated, add in the value of the next column and subtract the value of the
+** leftmost column.  Multiply that by the weight and that's it.  As you move
+** down a row, calculate new column sums by using previous sum for that column
+** and adding in pixel on current row and subtracting pixel in top row.
+**
+*/
+
+
+static void
+pgm_mean_convolve(const float ** const weights) {
+    float const gmeanweight = weights[0][0];
+
+    int ccol, col;
+    xel** xelbuf;
+    xel* outputrow;
+    xelval g;
+    int row, crow;
+    xel **rowptr, *temprptr;
+    int leftcol;
+    int i, irow;
+    int toprow, temprow;
+    int subrow, addrow;
+    int subcol, addcol;
+    long gisum;
+    int tempcol, crowsp1;
+    long tempgsum;
+    long *gcolumnsum;
+
+    /* Allocate space for one convolution-matrix's worth of rows, plus
+    ** a row output buffer.  MEAN uses an extra row. */
+    xelbuf = pnm_allocarray( cols, crows + 1 );
+    outputrow = pnm_allocrow( cols );
+
+    /* Allocate array of pointers to xelbuf. MEAN uses an extra row. */
+    rowptr = (xel **) pnm_allocarray( 1, crows + 1);
+
+    /* Allocate space for intermediate column sums */
+    gcolumnsum = (long *) pm_allocrow( cols, sizeof(long) );
+    for ( col = 0; col < cols; ++col )
+    gcolumnsum[col] = 0L;
+
+    pnm_writepnminit( stdout, cols, rows, maxval, newformat, 0 );
+
+    /* Read in one convolution-matrix's worth of image, less one row. */
+    for ( row = 0; row < crows - 1; ++row )
+    {
+    pnm_readpnmrow( ifp, xelbuf[row], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+        pnm_promoteformatrow(
+        xelbuf[row], cols, maxval, format, maxval, newformat );
+    /* Write out just the part we're not going to convolve. */
+    if ( row < crowso2 )
+        pnm_writepnmrow( stdout, xelbuf[row], cols, maxval, newformat, 0 );
+    }
+
+    /* Do first real row only */
+    subrow = crows;
+    addrow = crows - 1;
+    toprow = row + 1;
+    temprow = row % crows;
+    pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+    pnm_promoteformatrow(
+        xelbuf[temprow], cols, maxval, format, maxval, newformat );
+
+    temprow = toprow % crows;
+    i = 0;
+    for (irow = temprow; irow < crows; ++i, ++irow)
+    rowptr[i] = xelbuf[irow];
+    for (irow = 0; irow < temprow; ++irow, ++i)
+    rowptr[i] = xelbuf[irow];
+
+    gisum = 0L;
+    for ( col = 0; col < cols; ++col )
+    {
+    if ( col < ccolso2 || col >= cols - ccolso2 )
+        outputrow[col] = rowptr[crowso2][col];
+    else if ( col == ccolso2 )
+        {
+        leftcol = col - ccolso2;
+        for ( crow = 0; crow < crows; ++crow )
+        {
+        temprptr = rowptr[crow] + leftcol;
+        for ( ccol = 0; ccol < ccols; ++ccol )
+            gcolumnsum[leftcol + ccol] += 
+            PNM_GET1( *(temprptr + ccol) );
+        }
+        for ( ccol = 0; ccol < ccols; ++ccol)
+        gisum += gcolumnsum[leftcol + ccol];
+        tempgsum = (float) gisum * gmeanweight + 0.5;
+        CHECK_GRAY;
+        PNM_ASSIGN1( outputrow[col], g );
+        }
+    else
+        {
+        /* Column numbers to subtract or add to isum */
+        subcol = col - ccolso2 - 1;
+        addcol = col + ccolso2;  
+        for ( crow = 0; crow < crows; ++crow )
+        gcolumnsum[addcol] += PNM_GET1( rowptr[crow][addcol] );
+        gisum = gisum - gcolumnsum[subcol] + gcolumnsum[addcol];
+        tempgsum = (float) gisum * gmeanweight + 0.5;
+        CHECK_GRAY;
+        PNM_ASSIGN1( outputrow[col], g );
+        }
+    }
+    pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
+
+    ++row;
+    /* For all subsequent rows do it this way as the columnsums have been
+    ** generated.  Now we can use them to reduce further calculations.
+    */
+    crowsp1 = crows + 1;
+    for ( ; row < rows; ++row )
+    {
+    toprow = row + 1;
+    temprow = row % (crows + 1);
+    pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+        pnm_promoteformatrow(
+        xelbuf[temprow], cols, maxval, format, maxval, newformat );
+
+    /* This rearrangement using crows+1 rowptrs and xelbufs will cause
+    ** rowptr[0..crows-1] to always hold active xelbufs and for 
+    ** rowptr[crows] to always hold the oldest (top most) xelbuf.
+    */
+    temprow = (toprow + 1) % crowsp1;
+    i = 0;
+    for (irow = temprow; irow < crowsp1; ++i, ++irow)
+        rowptr[i] = xelbuf[irow];
+    for (irow = 0; irow < temprow; ++irow, ++i)
+        rowptr[i] = xelbuf[irow];
+
+    gisum = 0L;
+    for ( col = 0; col < cols; ++col )
+        {
+        if ( col < ccolso2 || col >= cols - ccolso2 )
+        outputrow[col] = rowptr[crowso2][col];
+        else if ( col == ccolso2 )
+        {
+        leftcol = col - ccolso2;
+        for ( ccol = 0; ccol < ccols; ++ccol )
+            {
+            tempcol = leftcol + ccol;
+            gcolumnsum[tempcol] = gcolumnsum[tempcol]
+            - PNM_GET1( rowptr[subrow][ccol] )
+            + PNM_GET1( rowptr[addrow][ccol] );
+            gisum += gcolumnsum[tempcol];
+            }
+        tempgsum = (float) gisum * gmeanweight + 0.5;
+        CHECK_GRAY;
+        PNM_ASSIGN1( outputrow[col], g );
+        }
+        else
+        {
+        /* Column numbers to subtract or add to isum */
+        subcol = col - ccolso2 - 1;
+        addcol = col + ccolso2;  
+        gcolumnsum[addcol] = gcolumnsum[addcol]
+            - PNM_GET1( rowptr[subrow][addcol] )
+            + PNM_GET1( rowptr[addrow][addcol] );
+        gisum = gisum - gcolumnsum[subcol] + gcolumnsum[addcol];
+        tempgsum = (float) gisum * gmeanweight + 0.5;
+        CHECK_GRAY;
+        PNM_ASSIGN1( outputrow[col], g );
+        }
+        }
+    pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
+    }
+
+    /* Now write out the remaining unconvolved rows in xelbuf. */
+    for ( irow = crowso2 + 1; irow < crows; ++irow )
+    pnm_writepnmrow(
+            stdout, rowptr[irow], cols, maxval, newformat, 0 );
+
+    }
+
+
+/* PGM Horizontal Convolution
+**
+** Similar idea to using columnsums of the Mean and Vertical convolution,
+** but uses temporary sums of row values.  Need to multiply by weights crows
+** number of times.  Each time a new line is started, must recalculate the
+** initials rowsums for the newest row only.  Uses queue to still access
+** previous row sums.
+**
+*/
+
+static void
+pgm_horizontal_convolve(const float ** const weights) {
+    int ccol, col;
+    xel** xelbuf;
+    xel* outputrow;
+    xelval g;
+    int row, crow;
+    xel **rowptr, *temprptr;
+    int leftcol;
+    int i, irow;
+    int temprow;
+    int subcol, addcol;
+    float gsum;
+    int addrow, subrow;
+    long **growsum, **growsumptr;
+    int crowsp1;
+    long tempgsum;
+
+    /* Allocate space for one convolution-matrix's worth of rows, plus
+    ** a row output buffer. */
+    xelbuf = pnm_allocarray( cols, crows + 1 );
+    outputrow = pnm_allocrow( cols );
+
+    /* Allocate array of pointers to xelbuf */
+    rowptr = (xel **) pnm_allocarray( 1, crows + 1);
+
+    /* Allocate intermediate row sums.  HORIZONTAL uses an extra row. */
+    /* crows current rows and 1 extra for newest added row.           */
+    growsum = (long **) pm_allocarray( cols, crows + 1, sizeof(long) );
+    growsumptr = (long **) pnm_allocarray( 1, crows + 1);
+
+    pnm_writepnminit( stdout, cols, rows, maxval, newformat, 0 );
+
+    /* Read in one convolution-matrix's worth of image, less one row. */
+    for ( row = 0; row < crows - 1; ++row )
+    {
+    pnm_readpnmrow( ifp, xelbuf[row], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+        pnm_promoteformatrow(
+        xelbuf[row], cols, maxval, format, maxval, newformat );
+    /* Write out just the part we're not going to convolve. */
+    if ( row < crowso2 )
+        pnm_writepnmrow( stdout, xelbuf[row], cols, maxval, newformat, 0 );
+    }
+
+    /* First row only */
+    temprow = row % crows;
+    pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+    pnm_promoteformatrow(
+        xelbuf[temprow], cols, maxval, format, maxval, newformat );
+
+    temprow = (row + 1) % crows;
+    i = 0;
+    for (irow = temprow; irow < crows; ++i, ++irow)
+    rowptr[i] = xelbuf[irow];
+    for (irow = 0; irow < temprow; ++irow, ++i)
+    rowptr[i] = xelbuf[irow];
+
+    for ( crow = 0; crow < crows; ++crow )
+    growsumptr[crow] = growsum[crow];
+ 
+    for ( col = 0; col < cols; ++col )
+    {
+    if ( col < ccolso2 || col >= cols - ccolso2 )
+        outputrow[col] = rowptr[crowso2][col];
+    else if ( col == ccolso2 )
+        {
+        leftcol = col - ccolso2;
+        gsum = 0.0;
+        for ( crow = 0; crow < crows; ++crow )
+        {
+        temprptr = rowptr[crow] + leftcol;
+        growsumptr[crow][leftcol] = 0L;
+        for ( ccol = 0; ccol < ccols; ++ccol )
+            growsumptr[crow][leftcol] += 
+                PNM_GET1( *(temprptr + ccol) );
+        gsum += growsumptr[crow][leftcol] * weights[crow][0];
+        }
+        tempgsum = gsum + 0.5;
+        CHECK_GRAY;
+        PNM_ASSIGN1( outputrow[col], g );
+        }
+    else
+        {
+        gsum = 0.0;
+        leftcol = col - ccolso2;
+        subcol = col - ccolso2 - 1;
+        addcol = col + ccolso2;
+        for ( crow = 0; crow < crows; ++crow )
+        {
+        growsumptr[crow][leftcol] = growsumptr[crow][subcol]
+            - PNM_GET1( rowptr[crow][subcol] )
+            + PNM_GET1( rowptr[crow][addcol] );
+        gsum += growsumptr[crow][leftcol] * weights[crow][0];
+        }
+        tempgsum = gsum + 0.5;
+        CHECK_GRAY;
+        PNM_ASSIGN1( outputrow[col], g );
+        }
+        }
+    pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
+
+
+    /* For all subsequent rows */
+
+    subrow = crows;
+    addrow = crows - 1;
+    crowsp1 = crows + 1;
+    ++row;
+    for ( ; row < rows; ++row )
+    {
+    temprow = row % crowsp1;
+    pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+        pnm_promoteformatrow(
+        xelbuf[temprow], cols, maxval, format, maxval, newformat );
+
+    temprow = (row + 2) % crowsp1;
+    i = 0;
+    for (irow = temprow; irow < crowsp1; ++i, ++irow)
+        {
+        rowptr[i] = xelbuf[irow];
+        growsumptr[i] = growsum[irow];
+        }
+    for (irow = 0; irow < temprow; ++irow, ++i)
+        {
+        rowptr[i] = xelbuf[irow];
+        growsumptr[i] = growsum[irow];
+        }
+
+    for ( col = 0; col < cols; ++col )
+        {
+        if ( col < ccolso2 || col >= cols - ccolso2 )
+        outputrow[col] = rowptr[crowso2][col];
+        else if ( col == ccolso2 )
+        {
+        gsum = 0.0;
+        leftcol = col - ccolso2;
+        growsumptr[addrow][leftcol] = 0L;
+        for ( ccol = 0; ccol < ccols; ++ccol )
+            growsumptr[addrow][leftcol] += 
+            PNM_GET1( rowptr[addrow][leftcol + ccol] );
+        for ( crow = 0; crow < crows; ++crow )
+            gsum += growsumptr[crow][leftcol] * weights[crow][0];
+        tempgsum = gsum + 0.5;
+        CHECK_GRAY;
+        PNM_ASSIGN1( outputrow[col], g );
+        }
+        else
+        {
+        gsum = 0.0;
+        leftcol = col - ccolso2;
+        subcol = col - ccolso2 - 1;
+        addcol = col + ccolso2;  
+        growsumptr[addrow][leftcol] = growsumptr[addrow][subcol]
+            - PNM_GET1( rowptr[addrow][subcol] )
+            + PNM_GET1( rowptr[addrow][addcol] );
+        for ( crow = 0; crow < crows; ++crow )
+            gsum += growsumptr[crow][leftcol] * weights[crow][0];
+        tempgsum = gsum + 0.5;
+        CHECK_GRAY;
+        PNM_ASSIGN1( outputrow[col], g );
+        }
+        }
+    pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
+    }
+
+    /* Now write out the remaining unconvolved rows in xelbuf. */
+    for ( irow = crowso2 + 1; irow < crows; ++irow )
+    pnm_writepnmrow(
+            stdout, rowptr[irow], cols, maxval, newformat, 0 );
+
+    }
+
+
+/* PGM Vertical Convolution
+**
+** Uses column sums as in Mean Convolution.
+**
+*/
+
+
+static void
+pgm_vertical_convolve(const float ** const weights) {
+    int ccol, col;
+    xel** xelbuf;
+    xel* outputrow;
+    xelval g;
+    int row, crow;
+    xel **rowptr, *temprptr;
+    int leftcol;
+    int i, irow;
+    int toprow, temprow;
+    int subrow, addrow;
+    int tempcol;
+    float gsum;
+    long *gcolumnsum;
+    int crowsp1;
+    int addcol;
+    long tempgsum;
+
+    /* Allocate space for one convolution-matrix's worth of rows, plus
+    ** a row output buffer. VERTICAL uses an extra row. */
+    xelbuf = pnm_allocarray( cols, crows + 1 );
+    outputrow = pnm_allocrow( cols );
+
+    /* Allocate array of pointers to xelbuf */
+    rowptr = (xel **) pnm_allocarray( 1, crows + 1 );
+
+    /* Allocate space for intermediate column sums */
+    gcolumnsum = (long *) pm_allocrow( cols, sizeof(long) );
+    for ( col = 0; col < cols; ++col )
+    gcolumnsum[col] = 0L;
+
+    pnm_writepnminit( stdout, cols, rows, maxval, newformat, 0 );
+
+    /* Read in one convolution-matrix's worth of image, less one row. */
+    for ( row = 0; row < crows - 1; ++row )
+    {
+    pnm_readpnmrow( ifp, xelbuf[row], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+        pnm_promoteformatrow(
+        xelbuf[row], cols, maxval, format, maxval, newformat );
+    /* Write out just the part we're not going to convolve. */
+    if ( row < crowso2 )
+        pnm_writepnmrow( stdout, xelbuf[row], cols, maxval, newformat, 0 );
+    }
+
+    /* Now the rest of the image - read in the row at the end of
+    ** xelbuf, and convolve and write out the row in the middle.
+    */
+    /* For first row only */
+
+    toprow = row + 1;
+    temprow = row % crows;
+    pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+    pnm_promoteformatrow(
+        xelbuf[temprow], cols, maxval, format, maxval, newformat );
+
+    /* Arrange rowptr to eliminate the use of mod function to determine
+    ** which row of xelbuf is 0...crows.  Mod function can be very costly.
+    */
+    temprow = toprow % crows;
+    i = 0;
+    for (irow = temprow; irow < crows; ++i, ++irow)
+    rowptr[i] = xelbuf[irow];
+    for (irow = 0; irow < temprow; ++irow, ++i)
+    rowptr[i] = xelbuf[irow];
+
+    for ( col = 0; col < cols; ++col )
+    {
+    if ( col < ccolso2 || col >= cols - ccolso2 )
+        outputrow[col] = rowptr[crowso2][col];
+    else if ( col == ccolso2 )
+        {
+        gsum = 0.0;
+        leftcol = col - ccolso2;
+        for ( crow = 0; crow < crows; ++crow )
+        {
+        temprptr = rowptr[crow] + leftcol;
+        for ( ccol = 0; ccol < ccols; ++ccol )
+            gcolumnsum[leftcol + ccol] += 
+            PNM_GET1( *(temprptr + ccol) );
+        }
+        for ( ccol = 0; ccol < ccols; ++ccol)
+        gsum += gcolumnsum[leftcol + ccol] * weights[0][ccol];
+        tempgsum = gsum + 0.5;
+        CHECK_GRAY;
+        PNM_ASSIGN1( outputrow[col], g );
+        }
+    else
+        {
+        gsum = 0.0;
+        leftcol = col - ccolso2;
+        addcol = col + ccolso2;  
+        for ( crow = 0; crow < crows; ++crow )
+        gcolumnsum[addcol] += PNM_GET1( rowptr[crow][addcol] );
+        for ( ccol = 0; ccol < ccols; ++ccol )
+        gsum += gcolumnsum[leftcol + ccol] * weights[0][ccol];
+        tempgsum = gsum + 0.5;
+        CHECK_GRAY;
+        PNM_ASSIGN1( outputrow[col], g );
+        }
+    }
+    pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
+
+    /* For all subsequent rows */
+    subrow = crows;
+    addrow = crows - 1;
+    crowsp1 = crows + 1;
+    ++row;
+    for ( ; row < rows; ++row )
+    {
+    toprow = row + 1;
+    temprow = row % (crows +1);
+    pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+        pnm_promoteformatrow(
+        xelbuf[temprow], cols, maxval, format, maxval, newformat );
+
+    /* Arrange rowptr to eliminate the use of mod function to determine
+    ** which row of xelbuf is 0...crows.  Mod function can be very costly.
+    */
+    temprow = (toprow + 1) % crowsp1;
+    i = 0;
+    for (irow = temprow; irow < crowsp1; ++i, ++irow)
+        rowptr[i] = xelbuf[irow];
+    for (irow = 0; irow < temprow; ++irow, ++i)
+        rowptr[i] = xelbuf[irow];
+
+    for ( col = 0; col < cols; ++col )
+        {
+        if ( col < ccolso2 || col >= cols - ccolso2 )
+        outputrow[col] = rowptr[crowso2][col];
+        else if ( col == ccolso2 )
+        {
+        gsum = 0.0;
+        leftcol = col - ccolso2;
+        for ( ccol = 0; ccol < ccols; ++ccol )
+            {
+            tempcol = leftcol + ccol;
+            gcolumnsum[tempcol] = gcolumnsum[tempcol] 
+            - PNM_GET1( rowptr[subrow][ccol] )
+            + PNM_GET1( rowptr[addrow][ccol] );
+            gsum = gsum + gcolumnsum[tempcol] * weights[0][ccol];
+            }
+        tempgsum = gsum + 0.5;
+        CHECK_GRAY;
+        PNM_ASSIGN1( outputrow[col], g );
+        }
+        else
+        {
+        gsum = 0.0;
+        leftcol = col - ccolso2;
+        addcol = col + ccolso2;
+        gcolumnsum[addcol] = gcolumnsum[addcol]
+            - PNM_GET1( rowptr[subrow][addcol] )
+            + PNM_GET1( rowptr[addrow][addcol] );
+        for ( ccol = 0; ccol < ccols; ++ccol )
+            gsum += gcolumnsum[leftcol + ccol] * weights[0][ccol];
+        tempgsum = gsum + 0.5;
+        CHECK_GRAY;
+        PNM_ASSIGN1( outputrow[col], g );
+        }
+        }
+    pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
+    }
+
+    /* Now write out the remaining unconvolved rows in xelbuf. */
+    for ( irow = crowso2 + 1; irow < crows; ++irow )
+    pnm_writepnmrow(
+            stdout, rowptr[irow], cols, maxval, newformat, 0 );
+
+    }
+
+
+
+
+/* PPM General Convolution Algorithm
+**
+** No redundancy in convolution matrix.  Just use brute force.
+** See pgm_general_convolve() for more details.
+*/
+
+static void
+ppm_general_convolve(const float ** const rweights,
+                     const float ** const gweights,
+                     const float ** const bweights) {
+    int ccol, col;
+    xel** xelbuf;
+    xel* outputrow;
+    xelval r, g, b;
+    int row, crow;
+    float rsum, gsum, bsum;
+    xel **rowptr, *temprptr;
+    int toprow, temprow;
+    int i, irow;
+    int leftcol;
+    long temprsum, tempgsum, tempbsum;
+
+    /* Allocate space for one convolution-matrix's worth of rows, plus
+    ** a row output buffer. */
+    xelbuf = pnm_allocarray( cols, crows );
+    outputrow = pnm_allocrow( cols );
+
+    /* Allocate array of pointers to xelbuf */
+    rowptr = (xel **) pnm_allocarray( 1, crows );
+
+    pnm_writepnminit( stdout, cols, rows, maxval, newformat, 0 );
+
+    /* Read in one convolution-matrix's worth of image, less one row. */
+    for ( row = 0; row < crows - 1; ++row )
+    {
+    pnm_readpnmrow( ifp, xelbuf[row], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+        pnm_promoteformatrow(
+        xelbuf[row], cols, maxval, format, maxval, newformat );
+    /* Write out just the part we're not going to convolve. */
+    if ( row < crowso2 )
+        pnm_writepnmrow( stdout, xelbuf[row], cols, maxval, newformat, 0 );
+    }
+
+    /* Now the rest of the image - read in the row at the end of
+    ** xelbuf, and convolve and write out the row in the middle.
+    */
+    for ( ; row < rows; ++row )
+    {
+    toprow = row + 1;
+    temprow = row % crows;
+    pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+        pnm_promoteformatrow(
+        xelbuf[temprow], cols, maxval, format, maxval, newformat );
+
+    /* Arrange rowptr to eliminate the use of mod function to determine
+    ** which row of xelbuf is 0...crows.  Mod function can be very costly.
+    */
+    temprow = toprow % crows;
+    i = 0;
+    for (irow = temprow; irow < crows; ++i, ++irow)
+        rowptr[i] = xelbuf[irow];
+    for (irow = 0; irow < temprow; ++irow, ++i)
+        rowptr[i] = xelbuf[irow];
+
+    for ( col = 0; col < cols; ++col )
+        {
+        if ( col < ccolso2 || col >= cols - ccolso2 )
+        outputrow[col] = rowptr[crowso2][col];
+        else
+        {
+        leftcol = col - ccolso2;
+        rsum = gsum = bsum = 0.0;
+        for ( crow = 0; crow < crows; ++crow )
+            {
+            temprptr = rowptr[crow] + leftcol;
+            for ( ccol = 0; ccol < ccols; ++ccol )
+            {
+            rsum += PPM_GETR( *(temprptr + ccol) )
+                * rweights[crow][ccol];
+            gsum += PPM_GETG( *(temprptr + ccol) )
+                * gweights[crow][ccol];
+            bsum += PPM_GETB( *(temprptr + ccol) )
+                * bweights[crow][ccol];
+            }
+            }
+            temprsum = rsum + 0.5;
+            tempgsum = gsum + 0.5;
+            tempbsum = bsum + 0.5;
+            CHECK_RED;
+            CHECK_GREEN;
+            CHECK_BLUE;
+            PPM_ASSIGN( outputrow[col], r, g, b );
+        }
+        }
+    pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
+    }
+
+    /* Now write out the remaining unconvolved rows in xelbuf. */
+    for ( irow = crowso2 + 1; irow < crows; ++irow )
+    pnm_writepnmrow(
+            stdout, rowptr[irow], cols, maxval, newformat, 0 );
+
+    }
+
+
+/* PPM Mean Convolution
+**
+** Same as pgm_mean_convolve() but for PPM.
+**
+*/
+
+static void
+ppm_mean_convolve(const float ** const rweights,
+                  const float ** const gweights,
+                  const float ** const bweights) {
+    /* All weights of a single color are the same so just grab any one
+       of them.  
+    */
+    float const rmeanweight = rweights[0][0];
+    float const gmeanweight = gweights[0][0];
+    float const bmeanweight = bweights[0][0];
+
+    int ccol, col;
+    xel** xelbuf;
+    xel* outputrow;
+    xelval r, g, b;
+    int row, crow;
+    xel **rowptr, *temprptr;
+    int leftcol;
+    int i, irow;
+    int toprow, temprow;
+    int subrow, addrow;
+    int subcol, addcol;
+    long risum, gisum, bisum;
+    long temprsum, tempgsum, tempbsum;
+    int tempcol, crowsp1;
+    long *rcolumnsum, *gcolumnsum, *bcolumnsum;
+
+
+
+    /* Allocate space for one convolution-matrix's worth of rows, plus
+    ** a row output buffer.  MEAN uses an extra row. */
+    xelbuf = pnm_allocarray( cols, crows + 1 );
+    outputrow = pnm_allocrow( cols );
+
+    /* Allocate array of pointers to xelbuf. MEAN uses an extra row. */
+    rowptr = (xel **) pnm_allocarray( 1, crows + 1);
+
+    /* Allocate space for intermediate column sums */
+    rcolumnsum = (long *) pm_allocrow( cols, sizeof(long) );
+    gcolumnsum = (long *) pm_allocrow( cols, sizeof(long) );
+    bcolumnsum = (long *) pm_allocrow( cols, sizeof(long) );
+    for ( col = 0; col < cols; ++col )
+    {
+    rcolumnsum[col] = 0L;
+    gcolumnsum[col] = 0L;
+    bcolumnsum[col] = 0L;
+    }
+
+    pnm_writepnminit( stdout, cols, rows, maxval, newformat, 0 );
+
+    /* Read in one convolution-matrix's worth of image, less one row. */
+    for ( row = 0; row < crows - 1; ++row )
+    {
+    pnm_readpnmrow( ifp, xelbuf[row], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+        pnm_promoteformatrow(
+        xelbuf[row], cols, maxval, format, maxval, newformat );
+    /* Write out just the part we're not going to convolve. */
+    if ( row < crowso2 )
+        pnm_writepnmrow( stdout, xelbuf[row], cols, maxval, newformat, 0 );
+    }
+
+    /* Do first real row only */
+    subrow = crows;
+    addrow = crows - 1;
+    toprow = row + 1;
+    temprow = row % crows;
+    pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+    pnm_promoteformatrow(
+        xelbuf[temprow], cols, maxval, format, maxval, newformat );
+
+    temprow = toprow % crows;
+    i = 0;
+    for (irow = temprow; irow < crows; ++i, ++irow)
+    rowptr[i] = xelbuf[irow];
+    for (irow = 0; irow < temprow; ++irow, ++i)
+    rowptr[i] = xelbuf[irow];
+
+    risum = 0L;
+    gisum = 0L;
+    bisum = 0L;
+    for ( col = 0; col < cols; ++col )
+    {
+    if ( col < ccolso2 || col >= cols - ccolso2 )
+        outputrow[col] = rowptr[crowso2][col];
+    else if ( col == ccolso2 )
+        {
+        leftcol = col - ccolso2;
+        for ( crow = 0; crow < crows; ++crow )
+        {
+        temprptr = rowptr[crow] + leftcol;
+        for ( ccol = 0; ccol < ccols; ++ccol )
+            {
+            rcolumnsum[leftcol + ccol] += 
+            PPM_GETR( *(temprptr + ccol) );
+            gcolumnsum[leftcol + ccol] += 
+            PPM_GETG( *(temprptr + ccol) );
+            bcolumnsum[leftcol + ccol] += 
+            PPM_GETB( *(temprptr + ccol) );
+            }
+        }
+        for ( ccol = 0; ccol < ccols; ++ccol)
+        {
+        risum += rcolumnsum[leftcol + ccol];
+        gisum += gcolumnsum[leftcol + ccol];
+        bisum += bcolumnsum[leftcol + ccol];
+        }
+        temprsum = (float) risum * rmeanweight + 0.5;
+        tempgsum = (float) gisum * gmeanweight + 0.5;
+        tempbsum = (float) bisum * bmeanweight + 0.5;
+        CHECK_RED;
+        CHECK_GREEN;
+        CHECK_BLUE;
+        PPM_ASSIGN( outputrow[col], r, g, b );
+        }
+    else
+        {
+        /* Column numbers to subtract or add to isum */
+        subcol = col - ccolso2 - 1;
+        addcol = col + ccolso2;  
+        for ( crow = 0; crow < crows; ++crow )
+        {
+        rcolumnsum[addcol] += PPM_GETR( rowptr[crow][addcol] );
+        gcolumnsum[addcol] += PPM_GETG( rowptr[crow][addcol] );
+        bcolumnsum[addcol] += PPM_GETB( rowptr[crow][addcol] );
+        }
+        risum = risum - rcolumnsum[subcol] + rcolumnsum[addcol];
+        gisum = gisum - gcolumnsum[subcol] + gcolumnsum[addcol];
+        bisum = bisum - bcolumnsum[subcol] + bcolumnsum[addcol];
+        temprsum = (float) risum * rmeanweight + 0.5;
+        tempgsum = (float) gisum * gmeanweight + 0.5;
+        tempbsum = (float) bisum * bmeanweight + 0.5;
+        CHECK_RED;
+        CHECK_GREEN;
+        CHECK_BLUE;
+        PPM_ASSIGN( outputrow[col], r, g, b );
+        }
+    }
+    pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
+
+    ++row;
+    /* For all subsequent rows do it this way as the columnsums have been
+    ** generated.  Now we can use them to reduce further calculations.
+    */
+    crowsp1 = crows + 1;
+    for ( ; row < rows; ++row )
+    {
+    toprow = row + 1;
+    temprow = row % (crows + 1);
+    pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+        pnm_promoteformatrow(
+        xelbuf[temprow], cols, maxval, format, maxval, newformat );
+
+    /* This rearrangement using crows+1 rowptrs and xelbufs will cause
+    ** rowptr[0..crows-1] to always hold active xelbufs and for 
+    ** rowptr[crows] to always hold the oldest (top most) xelbuf.
+    */
+    temprow = (toprow + 1) % crowsp1;
+    i = 0;
+    for (irow = temprow; irow < crowsp1; ++i, ++irow)
+        rowptr[i] = xelbuf[irow];
+    for (irow = 0; irow < temprow; ++irow, ++i)
+        rowptr[i] = xelbuf[irow];
+
+    risum = 0L;
+    gisum = 0L;
+    bisum = 0L;
+    for ( col = 0; col < cols; ++col )
+        {
+        if ( col < ccolso2 || col >= cols - ccolso2 )
+        outputrow[col] = rowptr[crowso2][col];
+        else if ( col == ccolso2 )
+        {
+        leftcol = col - ccolso2;
+        for ( ccol = 0; ccol < ccols; ++ccol )
+            {
+            tempcol = leftcol + ccol;
+            rcolumnsum[tempcol] = rcolumnsum[tempcol]
+            - PPM_GETR( rowptr[subrow][ccol] )
+            + PPM_GETR( rowptr[addrow][ccol] );
+            risum += rcolumnsum[tempcol];
+            gcolumnsum[tempcol] = gcolumnsum[tempcol]
+            - PPM_GETG( rowptr[subrow][ccol] )
+            + PPM_GETG( rowptr[addrow][ccol] );
+            gisum += gcolumnsum[tempcol];
+            bcolumnsum[tempcol] = bcolumnsum[tempcol]
+            - PPM_GETB( rowptr[subrow][ccol] )
+            + PPM_GETB( rowptr[addrow][ccol] );
+            bisum += bcolumnsum[tempcol];
+            }
+        temprsum = (float) risum * rmeanweight + 0.5;
+        tempgsum = (float) gisum * gmeanweight + 0.5;
+        tempbsum = (float) bisum * bmeanweight + 0.5;
+        CHECK_RED;
+        CHECK_GREEN;
+        CHECK_BLUE;
+        PPM_ASSIGN( outputrow[col], r, g, b );
+        }
+        else
+        {
+        /* Column numbers to subtract or add to isum */
+        subcol = col - ccolso2 - 1;
+        addcol = col + ccolso2;  
+        rcolumnsum[addcol] = rcolumnsum[addcol]
+            - PPM_GETR( rowptr[subrow][addcol] )
+            + PPM_GETR( rowptr[addrow][addcol] );
+        risum = risum - rcolumnsum[subcol] + rcolumnsum[addcol];
+        gcolumnsum[addcol] = gcolumnsum[addcol]
+            - PPM_GETG( rowptr[subrow][addcol] )
+            + PPM_GETG( rowptr[addrow][addcol] );
+        gisum = gisum - gcolumnsum[subcol] + gcolumnsum[addcol];
+        bcolumnsum[addcol] = bcolumnsum[addcol]
+            - PPM_GETB( rowptr[subrow][addcol] )
+            + PPM_GETB( rowptr[addrow][addcol] );
+        bisum = bisum - bcolumnsum[subcol] + bcolumnsum[addcol];
+        temprsum = (float) risum * rmeanweight + 0.5;
+        tempgsum = (float) gisum * gmeanweight + 0.5;
+        tempbsum = (float) bisum * bmeanweight + 0.5;
+        CHECK_RED;
+        CHECK_GREEN;
+        CHECK_BLUE;
+        PPM_ASSIGN( outputrow[col], r, g, b );
+        }
+        }
+    pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
+    }
+
+    /* Now write out the remaining unconvolved rows in xelbuf. */
+    for ( irow = crowso2 + 1; irow < crows; ++irow )
+    pnm_writepnmrow(
+            stdout, rowptr[irow], cols, maxval, newformat, 0 );
+
+    }
+
+
+/* PPM Horizontal Convolution
+**
+** Same as pgm_horizontal_convolve()
+**
+**/
+
+static void
+ppm_horizontal_convolve(const float ** const rweights,
+                        const float ** const gweights,
+                        const float ** const bweights) {
+    int ccol, col;
+    xel** xelbuf;
+    xel* outputrow;
+    xelval r, g, b;
+    int row, crow;
+    xel **rowptr, *temprptr;
+    int leftcol;
+    int i, irow;
+    int temprow;
+    int subcol, addcol;
+    float rsum, gsum, bsum;
+    int addrow, subrow;
+    long **rrowsum, **rrowsumptr;
+    long **growsum, **growsumptr;
+    long **browsum, **browsumptr;
+    int crowsp1;
+    long temprsum, tempgsum, tempbsum;
+
+    /* Allocate space for one convolution-matrix's worth of rows, plus
+    ** a row output buffer. */
+    xelbuf = pnm_allocarray( cols, crows + 1 );
+    outputrow = pnm_allocrow( cols );
+
+    /* Allocate array of pointers to xelbuf */
+    rowptr = (xel **) pnm_allocarray( 1, crows + 1);
+
+    /* Allocate intermediate row sums.  HORIZONTAL uses an extra row */
+    rrowsum = (long **) pm_allocarray( cols, crows + 1, sizeof(long) );
+    rrowsumptr = (long **) pnm_allocarray( 1, crows + 1);
+    growsum = (long **) pm_allocarray( cols, crows + 1, sizeof(long) );
+    growsumptr = (long **) pnm_allocarray( 1, crows + 1);
+    browsum = (long **) pm_allocarray( cols, crows + 1, sizeof(long) );
+    browsumptr = (long **) pnm_allocarray( 1, crows + 1);
+
+    pnm_writepnminit( stdout, cols, rows, maxval, newformat, 0 );
+
+    /* Read in one convolution-matrix's worth of image, less one row. */
+    for ( row = 0; row < crows - 1; ++row )
+    {
+    pnm_readpnmrow( ifp, xelbuf[row], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+        pnm_promoteformatrow(
+        xelbuf[row], cols, maxval, format, maxval, newformat );
+    /* Write out just the part we're not going to convolve. */
+    if ( row < crowso2 )
+        pnm_writepnmrow( stdout, xelbuf[row], cols, maxval, newformat, 0 );
+    }
+
+    /* First row only */
+    temprow = row % crows;
+    pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+    pnm_promoteformatrow(
+        xelbuf[temprow], cols, maxval, format, maxval, newformat );
+
+    temprow = (row + 1) % crows;
+    i = 0;
+    for (irow = temprow; irow < crows; ++i, ++irow)
+    rowptr[i] = xelbuf[irow];
+    for (irow = 0; irow < temprow; ++irow, ++i)
+    rowptr[i] = xelbuf[irow];
+
+    for ( crow = 0; crow < crows; ++crow )
+    {
+    rrowsumptr[crow] = rrowsum[crow];
+    growsumptr[crow] = growsum[crow];
+    browsumptr[crow] = browsum[crow];
+    }
+ 
+    for ( col = 0; col < cols; ++col )
+    {
+    if ( col < ccolso2 || col >= cols - ccolso2 )
+        outputrow[col] = rowptr[crowso2][col];
+    else if ( col == ccolso2 )
+        {
+        leftcol = col - ccolso2;
+        rsum = 0.0;
+        gsum = 0.0;
+        bsum = 0.0;
+        for ( crow = 0; crow < crows; ++crow )
+        {
+        temprptr = rowptr[crow] + leftcol;
+        rrowsumptr[crow][leftcol] = 0L;
+        growsumptr[crow][leftcol] = 0L;
+        browsumptr[crow][leftcol] = 0L;
+        for ( ccol = 0; ccol < ccols; ++ccol )
+            {
+            rrowsumptr[crow][leftcol] += 
+                PPM_GETR( *(temprptr + ccol) );
+            growsumptr[crow][leftcol] += 
+                PPM_GETG( *(temprptr + ccol) );
+            browsumptr[crow][leftcol] += 
+                PPM_GETB( *(temprptr + ccol) );
+            }
+        rsum += rrowsumptr[crow][leftcol] * rweights[crow][0];
+        gsum += growsumptr[crow][leftcol] * gweights[crow][0];
+        bsum += browsumptr[crow][leftcol] * bweights[crow][0];
+        }
+        temprsum = rsum + 0.5;
+        tempgsum = gsum + 0.5;
+        tempbsum = bsum + 0.5;
+        CHECK_RED;
+        CHECK_GREEN;
+        CHECK_BLUE;
+        PPM_ASSIGN( outputrow[col], r, g, b );
+        }
+    else
+        {
+        rsum = 0.0;
+        gsum = 0.0;
+        bsum = 0.0;
+        leftcol = col - ccolso2;
+        subcol = col - ccolso2 - 1;
+        addcol = col + ccolso2;
+        for ( crow = 0; crow < crows; ++crow )
+        {
+        rrowsumptr[crow][leftcol] = rrowsumptr[crow][subcol]
+            - PPM_GETR( rowptr[crow][subcol] )
+            + PPM_GETR( rowptr[crow][addcol] );
+        rsum += rrowsumptr[crow][leftcol] * rweights[crow][0];
+        growsumptr[crow][leftcol] = growsumptr[crow][subcol]
+            - PPM_GETG( rowptr[crow][subcol] )
+            + PPM_GETG( rowptr[crow][addcol] );
+        gsum += growsumptr[crow][leftcol] * gweights[crow][0];
+        browsumptr[crow][leftcol] = browsumptr[crow][subcol]
+            - PPM_GETB( rowptr[crow][subcol] )
+            + PPM_GETB( rowptr[crow][addcol] );
+        bsum += browsumptr[crow][leftcol] * bweights[crow][0];
+        }
+        temprsum = rsum + 0.5;
+        tempgsum = gsum + 0.5;
+        tempbsum = bsum + 0.5;
+        CHECK_RED;
+        CHECK_GREEN;
+        CHECK_BLUE;
+        PPM_ASSIGN( outputrow[col], r, g, b );
+        }
+        }
+    pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
+
+
+    /* For all subsequent rows */
+
+    subrow = crows;
+    addrow = crows - 1;
+    crowsp1 = crows + 1;
+    ++row;
+    for ( ; row < rows; ++row )
+    {
+    temprow = row % crowsp1;
+    pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
+    if ( PNM_FORMAT_TYPE(format) != newformat )
+        pnm_promoteformatrow(
+        xelbuf[temprow], cols, maxval, format, maxval, newformat );
+
+    temprow = (row + 2) % crowsp1;
+    i = 0;
+    for (irow = temprow; irow < crowsp1; ++i, ++irow)
+        {
+        rowptr[i] = xelbuf[irow];
+        rrowsumptr[i] = rrowsum[irow];
+        growsumptr[i] = growsum[irow];
+        browsumptr[i] = browsum[irow];
+        }
+    for (irow = 0; irow < temprow; ++irow, ++i)
+        {
+        rowptr[i] = xelbuf[irow];
+        rrowsumptr[i] = rrowsum[irow];
+        growsumptr[i] = growsum[irow];
+        browsumptr[i] = browsum[irow];
+        }
+
+    for ( col = 0; col < cols; ++col )
+        {
+        if ( col < ccolso2 || col >= cols - ccolso2 )
+        outputrow[col] = rowptr[crowso2][col];
+        else if ( col == ccolso2 )
+        {
+        rsum = 0.0;
+        gsum = 0.0;
+        bsum = 0.0;
+        leftcol = col - ccolso2;
+        rrowsumptr[addrow][leftcol] = 0L;
+        growsumptr[addrow][leftcol] = 0L;
+        browsumptr[addrow][leftcol] = 0L;
+        for ( ccol = 0; ccol < ccols; ++ccol )
+            {
+            rrowsumptr[addrow][leftcol] += 
+            PPM_GETR( rowptr[addrow][leftcol + ccol] );
+            growsumptr[addrow][leftcol] += 
+            PPM_GETG( rowptr[addrow][leftcol + ccol] );
+            browsumptr[addrow][leftcol] += 
+            PPM_GETB( rowptr[addrow][leftcol + ccol] );
+            }
+        for ( crow = 0; crow < crows; ++crow )
+            {
+            rsum += rrowsumptr[crow][leftcol] * rweights[crow][0];
+            gsum += growsumptr[crow][leftcol] * gweights[crow][0];
+            bsum += browsumptr[crow][leftcol] * bweights[crow][0];
+            }
+        temprsum = rsum + 0.5;
+        tempgsum = gsum + 0.5;
+        tempbsum = bsum + 0.5;
+        CHECK_RED;
+        CHECK_GREEN;
+        CHECK_BLUE;
+        PPM_ASSIGN( outputrow[col], r, g, b );
+        }
+        else
+        {
+        rsum = 0.0;
+        gsum = 0.0;
+        bsum = 0.0;
+        leftcol = col - ccolso2;
+        subcol = col - ccolso2 - 1;
+        addcol = col + ccolso2;  
+        rrowsumptr[addrow][leftcol] = rrowsumptr[addrow][subcol]
+            - PPM_GETR( rowptr[addrow][subcol] )
+            + PPM_GETR( rowptr[addrow][addcol] );
+        growsumptr[addrow][leftcol] = growsumptr[addrow][subcol]
+            - PPM_GETG( rowptr[addrow][subcol] )
+            + PPM_GETG( rowptr[addrow][addcol] );
+        browsumptr[addrow][leftcol] = browsumptr[addrow][subcol]
+            - PPM_GETB( rowptr[addrow][subcol] )
+            + PPM_GETB( rowptr[addrow][addcol] );
+        for ( crow = 0; crow < crows; ++crow )
+            {
+            rsum += rrowsumptr[crow][leftcol] * rweights[crow][0];
+            gsum += growsumptr[crow][leftcol] * gweights[crow][0];
+            bsum += browsumptr[crow][leftcol] * bweights[crow][0];
+            }
+        temprsum = rsum + 0.5;
+        tempgsum = gsum + 0.5;
+        tempbsum = bsum + 0.5;
+        CHECK_RED;
+        CHECK_GREEN;
+        CHECK_BLUE;
+        PPM_ASSIGN( outputrow[col], r, g, b );
+        }
+        }
+    pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
+    }
+
+    /* Now write out the remaining unconvolved rows in xelbuf. */
+    for ( irow = crowso2 + 1; irow < crows; ++irow )
+    pnm_writepnmrow(
+            stdout, rowptr[irow], cols, maxval, newformat, 0 );
+
+    }
+
+
+/* PPM Vertical Convolution
+**
+** Same as pgm_vertical_convolve()
+**
+*/
+
+static void
+ppm_vertical_convolve(const float ** const rweights,
+                      const float ** const gweights,
+                      const float ** const bweights) {
+    int ccol, col;
+    xel** xelbuf;
+    xel* outputrow;
+    xelval r, g, b;
+    int row, crow;
+    xel **rowptr, *temprptr;
+    int i, irow;
+    int toprow, temprow;
+    int subrow, addrow;
+    int tempcol;
+    long *rcolumnsum, *gcolumnsum, *bcolumnsum;
+    int crowsp1;
+    int addcol;
+    long temprsum, tempgsum, tempbsum;
+
+    /* Allocate space for one convolution-matrix's worth of rows, plus
+    ** a row output buffer. VERTICAL uses an extra row. */
+    xelbuf = pnm_allocarray(cols, crows + 1);
+    outputrow = pnm_allocrow(cols);
+
+    /* Allocate array of pointers to xelbuf */
+    rowptr = (xel **) pnm_allocarray(1, crows + 1);
+
+    /* Allocate space for intermediate column sums */
+    MALLOCARRAY_NOFAIL(rcolumnsum, cols);
+    MALLOCARRAY_NOFAIL(gcolumnsum, cols);
+    MALLOCARRAY_NOFAIL(bcolumnsum, cols);
+
+    for (col = 0; col < cols; ++col) {
+        rcolumnsum[col] = 0L;
+        gcolumnsum[col] = 0L;
+        bcolumnsum[col] = 0L;
+    }
+
+    pnm_writepnminit(stdout, cols, rows, maxval, newformat, 0);
+
+    /* Read in one convolution-matrix's worth of image, less one row. */
+    for (row = 0; row < crows - 1; ++row) {
+        pnm_readpnmrow(ifp, xelbuf[row], cols, maxval, format);
+        if (PNM_FORMAT_TYPE(format) != newformat)
+            pnm_promoteformatrow(xelbuf[row], cols, maxval, format, 
+                                 maxval, newformat);
+        /* Write out just the part we're not going to convolve. */
+        if (row < crowso2)
+            pnm_writepnmrow(stdout, xelbuf[row], cols, maxval, newformat, 0);
+    }
+
+    /* Now the rest of the image - read in the row at the end of
+    ** xelbuf, and convolve and write out the row in the middle.
+    */
+    /* For first row only */
+
+    toprow = row + 1;
+    temprow = row % crows;
+    pnm_readpnmrow(ifp, xelbuf[temprow], cols, maxval, format);
+    if (PNM_FORMAT_TYPE(format) != newformat)
+        pnm_promoteformatrow(xelbuf[temprow], cols, maxval, format, maxval, 
+                             newformat);
+
+    /* Arrange rowptr to eliminate the use of mod function to determine
+    ** which row of xelbuf is 0...crows.  Mod function can be very costly.
+    */
+    temprow = toprow % crows;
+    i = 0;
+    for (irow = temprow; irow < crows; ++i, ++irow)
+        rowptr[i] = xelbuf[irow];
+    for (irow = 0; irow < temprow; ++irow, ++i)
+        rowptr[i] = xelbuf[irow];
+
+    for (col = 0; col < cols; ++col) {
+        if (col < ccolso2 || col >= cols - ccolso2)
+            outputrow[col] = rowptr[crowso2][col];
+        else if (col == ccolso2) {
+            int const leftcol = col - ccolso2;
+            float rsum, gsum, bsum;
+            rsum = 0.0;
+            gsum = 0.0;
+            bsum = 0.0;
+            for (crow = 0; crow < crows; ++crow) {
+                temprptr = rowptr[crow] + leftcol;
+                for (ccol = 0; ccol < ccols; ++ccol) {
+                    rcolumnsum[leftcol + ccol] += 
+                        PPM_GETR(*(temprptr + ccol));
+                    gcolumnsum[leftcol + ccol] += 
+                        PPM_GETG(*(temprptr + ccol));
+                    bcolumnsum[leftcol + ccol] += 
+                        PPM_GETB(*(temprptr + ccol));
+                }
+            }
+            for (ccol = 0; ccol < ccols; ++ccol) {
+                rsum += rcolumnsum[leftcol + ccol] * rweights[0][ccol];
+                gsum += gcolumnsum[leftcol + ccol] * gweights[0][ccol];
+                bsum += bcolumnsum[leftcol + ccol] * bweights[0][ccol];
+            }
+            temprsum = rsum + 0.5;
+            tempgsum = gsum + 0.5;
+            tempbsum = bsum + 0.5;
+            CHECK_RED;
+            CHECK_GREEN;
+            CHECK_BLUE;
+            PPM_ASSIGN(outputrow[col], r, g, b);
+        } else {
+            int const leftcol = col - ccolso2;
+            float rsum, gsum, bsum;
+            rsum = 0.0;
+            gsum = 0.0;
+            bsum = 0.0;
+            addcol = col + ccolso2;  
+            for (crow = 0; crow < crows; ++crow) {
+                rcolumnsum[addcol] += PPM_GETR( rowptr[crow][addcol]);
+                gcolumnsum[addcol] += PPM_GETG( rowptr[crow][addcol]);
+                bcolumnsum[addcol] += PPM_GETB( rowptr[crow][addcol]);
+            }
+            for (ccol = 0; ccol < ccols; ++ccol) {
+                rsum += rcolumnsum[leftcol + ccol] * rweights[0][ccol];
+                gsum += gcolumnsum[leftcol + ccol] * gweights[0][ccol];
+                bsum += bcolumnsum[leftcol + ccol] * bweights[0][ccol];
+            }
+            temprsum = rsum + 0.5;
+            tempgsum = gsum + 0.5;
+            tempbsum = bsum + 0.5;
+            CHECK_RED;
+            CHECK_GREEN;
+            CHECK_BLUE;
+            PPM_ASSIGN(outputrow[col], r, g, b);
+        }
+    }
+    pnm_writepnmrow(stdout, outputrow, cols, maxval, newformat, 0);
+    
+    /* For all subsequent rows */
+    subrow = crows;
+    addrow = crows - 1;
+    crowsp1 = crows + 1;
+    ++row;
+    for (; row < rows; ++row) {
+        toprow = row + 1;
+        temprow = row % (crows +1);
+        pnm_readpnmrow(ifp, xelbuf[temprow], cols, maxval, format);
+        if (PNM_FORMAT_TYPE(format) != newformat)
+            pnm_promoteformatrow(xelbuf[temprow], cols, maxval, format, 
+                                 maxval, newformat);
+
+        /* Arrange rowptr to eliminate the use of mod function to determine
+        ** which row of xelbuf is 0...crows.  Mod function can be very costly.
+        */
+        temprow = (toprow + 1) % crowsp1;
+        i = 0;
+        for (irow = temprow; irow < crowsp1; ++i, ++irow)
+            rowptr[i] = xelbuf[irow];
+        for (irow = 0; irow < temprow; ++irow, ++i)
+            rowptr[i] = xelbuf[irow];
+
+        for (col = 0; col < cols; ++col) {
+            if (col < ccolso2 || col >= cols - ccolso2)
+                outputrow[col] = rowptr[crowso2][col];
+            else if (col == ccolso2) {
+                int const leftcol = col - ccolso2;
+                float rsum, gsum, bsum;
+                rsum = 0.0;
+                gsum = 0.0;
+                bsum = 0.0;
+
+                for (ccol = 0; ccol < ccols; ++ccol) {
+                    tempcol = leftcol + ccol;
+                    rcolumnsum[tempcol] = rcolumnsum[tempcol] 
+                        - PPM_GETR(rowptr[subrow][ccol])
+                        + PPM_GETR(rowptr[addrow][ccol]);
+                    rsum = rsum + rcolumnsum[tempcol] * rweights[0][ccol];
+                    gcolumnsum[tempcol] = gcolumnsum[tempcol] 
+                        - PPM_GETG(rowptr[subrow][ccol])
+                        + PPM_GETG(rowptr[addrow][ccol]);
+                    gsum = gsum + gcolumnsum[tempcol] * gweights[0][ccol];
+                    bcolumnsum[tempcol] = bcolumnsum[tempcol] 
+                        - PPM_GETB(rowptr[subrow][ccol])
+                        + PPM_GETB(rowptr[addrow][ccol]);
+                    bsum = bsum + bcolumnsum[tempcol] * bweights[0][ccol];
+                }
+                temprsum = rsum + 0.5;
+                tempgsum = gsum + 0.5;
+                tempbsum = bsum + 0.5;
+                CHECK_RED;
+                CHECK_GREEN;
+                CHECK_BLUE;
+                PPM_ASSIGN(outputrow[col], r, g, b);
+            } else {
+                int const leftcol = col - ccolso2;
+                float rsum, gsum, bsum;
+                rsum = 0.0;
+                gsum = 0.0;
+                bsum = 0.0;
+                addcol = col + ccolso2;
+                rcolumnsum[addcol] = rcolumnsum[addcol]
+                    - PPM_GETR(rowptr[subrow][addcol])
+                    + PPM_GETR(rowptr[addrow][addcol]);
+                gcolumnsum[addcol] = gcolumnsum[addcol]
+                    - PPM_GETG(rowptr[subrow][addcol])
+                    + PPM_GETG(rowptr[addrow][addcol]);
+                bcolumnsum[addcol] = bcolumnsum[addcol]
+                    - PPM_GETB(rowptr[subrow][addcol])
+                    + PPM_GETB(rowptr[addrow][addcol]);
+                for (ccol = 0; ccol < ccols; ++ccol) {
+                    rsum += rcolumnsum[leftcol + ccol] * rweights[0][ccol];
+                    gsum += gcolumnsum[leftcol + ccol] * gweights[0][ccol];
+                    bsum += bcolumnsum[leftcol + ccol] * bweights[0][ccol];
+                }
+                temprsum = rsum + 0.5;
+                tempgsum = gsum + 0.5;
+                tempbsum = bsum + 0.5;
+                CHECK_RED;
+                CHECK_GREEN;
+                CHECK_BLUE;
+                PPM_ASSIGN(outputrow[col], r, g, b);
+            }
+        }
+        pnm_writepnmrow(stdout, outputrow, cols, maxval, newformat, 0);
+    }
+
+    /* Now write out the remaining unconvolved rows in xelbuf. */
+    for (irow = crowso2 + 1; irow < crows; ++irow)
+        pnm_writepnmrow(stdout, rowptr[irow], cols, maxval, newformat, 0);
+
+}
+
+
+
+static void
+determineConvolveType(xel * const *         const cxels,
+                      struct convolveType * const typeP) {
+/*----------------------------------------------------------------------------
+   Determine which form of convolution is best.  The general form always
+   works, but with some special case convolution matrices, faster forms
+   of convolution are possible.
+
+   We don't check for the case that one of the PPM colors can have 
+   differing types.  We handle only cases where all PPMs are of the same
+   special case.
+-----------------------------------------------------------------------------*/
+    int horizontal, vertical;
+    int tempcxel, rtempcxel, gtempcxel, btempcxel;
+    int crow, ccol;
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PPM_TYPE:
+        horizontal = TRUE;  /* initial assumption */
+        crow = 0;
+        while (horizontal && (crow < crows)) {
+            ccol = 1;
+            rtempcxel = PPM_GETR(cxels[crow][0]);
+            gtempcxel = PPM_GETG(cxels[crow][0]);
+            btempcxel = PPM_GETB(cxels[crow][0]);
+            while (horizontal && (ccol < ccols)) {
+                if ((PPM_GETR(cxels[crow][ccol]) != rtempcxel) ||
+                    (PPM_GETG(cxels[crow][ccol]) != gtempcxel) ||
+                    (PPM_GETB(cxels[crow][ccol]) != btempcxel)) 
+                    horizontal = FALSE;
+                ++ccol;
+            }
+            ++crow;
+        }
+
+        vertical = TRUE;   /* initial assumption */
+        ccol = 0;
+        while (vertical && (ccol < ccols)) {
+            crow = 1;
+            rtempcxel = PPM_GETR(cxels[0][ccol]);
+            gtempcxel = PPM_GETG(cxels[0][ccol]);
+            btempcxel = PPM_GETB(cxels[0][ccol]);
+            while (vertical && (crow < crows)) {
+                if ((PPM_GETR(cxels[crow][ccol]) != rtempcxel) |
+                    (PPM_GETG(cxels[crow][ccol]) != gtempcxel) |
+                    (PPM_GETB(cxels[crow][ccol]) != btempcxel))
+                    vertical = FALSE;
+                ++crow;
+            }
+            ++ccol;
+        }
+        break;
+        
+    default:
+        horizontal = TRUE; /* initial assumption */
+        crow = 0;
+        while (horizontal && (crow < crows)) {
+            ccol = 1;
+            tempcxel = PNM_GET1(cxels[crow][0]);
+            while (horizontal && (ccol < ccols)) {
+                if (PNM_GET1(cxels[crow][ccol]) != tempcxel)
+                    horizontal = FALSE;
+                ++ccol;
+            }
+            ++crow;
+        }
+        
+        vertical = TRUE;  /* initial assumption */
+        ccol = 0;
+        while (vertical && (ccol < ccols)) {
+            crow = 1;
+            tempcxel = PNM_GET1(cxels[0][ccol]);
+            while (vertical && (crow < crows)) {
+                if (PNM_GET1(cxels[crow][ccol]) != tempcxel)
+                    vertical = FALSE;
+                ++crow;
+            }
+            ++ccol;
+        }
+        break;
+    }
+    
+    /* Which type do we have? */
+    if (horizontal && vertical) {
+        typeP->ppmConvolver = ppm_mean_convolve;
+        typeP->pgmConvolver = pgm_mean_convolve;
+    } else if (horizontal) {
+        typeP->ppmConvolver = ppm_horizontal_convolve;
+        typeP->pgmConvolver = pgm_horizontal_convolve;
+    } else if (vertical) {
+        typeP->ppmConvolver = ppm_vertical_convolve;
+        typeP->pgmConvolver = pgm_vertical_convolve;
+    } else {
+        typeP->ppmConvolver = ppm_general_convolve;
+        typeP->pgmConvolver = pgm_general_convolve;
+    }
+}
+
+
+
+static void
+convolveIt(int                 const format,
+           struct convolveType const convolveType,
+           const float**       const rweights,
+           const float**       const gweights,
+           const float**       const bweights) {
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PPM_TYPE:
+        convolveType.ppmConvolver(rweights, gweights, bweights);
+        break;
+
+    default:
+        convolveType.pgmConvolver(gweights);
+    }
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE* cifp;
+    xel** cxels;
+    int cformat;
+    xelval cmaxval;
+    struct convolveType convolveType;
+    float ** rweights;
+    float ** gweights;
+    float ** bweights;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    cifp = pm_openr(cmdline.kernelFilespec);
+
+    /* Read in the convolution matrix. */
+    cxels = pnm_readpnm(cifp, &ccols, &crows, &cmaxval, &cformat);
+    pm_close(cifp);
+
+    if (ccols % 2 != 1 || crows % 2 != 1)
+        pm_error("the convolution matrix must have an odd number of "
+                 "rows and columns" );
+
+    ccolso2 = ccols / 2;
+    crowso2 = crows / 2;
+
+    ifp = pm_openr(cmdline.inputFilespec);
+
+    pnm_readpnminit(ifp, &cols, &rows, &maxval, &format);
+    if (cols < ccols || rows < crows)
+        pm_error("the image is smaller than the convolution matrix" );
+
+    newformat = MAX(PNM_FORMAT_TYPE(cformat), PNM_FORMAT_TYPE(format));
+    if (PNM_FORMAT_TYPE(cformat) != newformat)
+        pnm_promoteformat(cxels, ccols, crows, cmaxval, cformat, 
+                          cmaxval, newformat);
+    if (PNM_FORMAT_TYPE(format) != newformat) {
+        switch (PNM_FORMAT_TYPE(newformat)) {
+        case PPM_TYPE:
+            if (PNM_FORMAT_TYPE(format) != newformat)
+                pm_message("promoting to PPM");
+            break;
+        case PGM_TYPE:
+            if (PNM_FORMAT_TYPE(format) != newformat)
+                pm_message("promoting to PGM");
+            break;
+        }
+    }
+
+    computeWeights(cxels, ccols, crows, newformat, cmaxval, !cmdline.nooffset,
+                   &rweights, &gweights, &bweights);
+
+    /* Handle certain special cases when runtime can be improved. */
+
+    determineConvolveType(cxels, &convolveType);
+
+    convolveIt(format, convolveType, 
+               (const float **)rweights, 
+               (const float **)gweights, 
+               (const float **)bweights);
+
+    pm_close(stdout);
+    pm_close(ifp);
+    return 0;
+}
+
+
+
+/******************************************************************************
+                            SOME CHANGE HISTORY
+*******************************************************************************
+
+ Version 2.0.1 Changes
+ ---------------------
+ Fixed four lines that were improperly allocated as sizeof( float ) when they
+ should have been sizeof( long ).
+
+ Version 2.0 Changes
+ -------------------
+
+ Version 2.0 was written by Mike Burns (derived from Jef Poskanzer's
+ original) in January 1995.
+
+ Reduce run time by general optimizations and handling special cases of
+ convolution matrices.  Program automatically determines if convolution 
+ matrix is one of the types it can make use of so no extra command line
+ arguments are necessary.
+
+ Examples of convolution matrices for the special cases are
+
+    Mean       Horizontal    Vertical
+    x x x        x x x        x y z
+    x x x        y y y        x y z
+    x x x        z z z        x y z
+
+ I don't know if the horizontal and vertical ones are of much use, but
+ after working on the mean convolution, it gave me ideas for the other two.
+
+ Some other compiler dependent optimizations
+ -------------------------------------------
+ Created separate functions as code was getting too large to put keep both
+ PGM and PPM cases in same function and also because SWITCH statement in
+ inner loop can take progressively more time the larger the size of the 
+ convolution matrix.  GCC is affected this way.
+
+ Removed use of MOD (%) operator from innermost loop by modifying manner in
+ which the current xelbuf[] is chosen.
+
+ This is from the file pnmconvol.README, dated August 1995, extracted in
+ April 2000, which was in the March 1994 Netpbm release:
+
+ ----------------------------------------------------------------------------- 
+ This is a faster version of the pnmconvol.c program that comes with netpbm.
+ There are no changes to the command line arguments, so this program can be
+ dropped in without affecting the way you currently run it.  An updated man
+ page is also included.
+ 
+ My original intention was to improve the running time of applying a
+ neighborhood averaging convolution matrix to an image by using a different
+ algorithm, but I also improved the run time of performing the general
+ convolution by optimizing that code.  The general convolution runs in 1/4 to
+ 1/2 of the original time and neighborhood averaging runs in near constant
+ time for the convolution masks I tested (3x3, 5x5, 7x7, 9x9).
+ 
+ Sample times for two computers are below.  Times are in seconds as reported
+ by /bin/time for a 512x512 pgm image.
+ 
+ Matrix                  IBM RS6000      SUN IPC
+ Size & Type                220
+ 
+ 3x3
+ original pnmconvol         6.3            18.4
+ new general case           3.1             6.0
+ new average case           1.8             2.6
+ 
+ 5x5
+ original pnmconvol        11.9            44.4
+ new general case           5.6            11.9
+ new average case           1.8             2.6
+ 
+ 7x7
+ original pnmconvol        20.3            82.9
+ new general case           9.4            20.7
+ new average case           1.8             2.6
+ 
+ 9x9
+ original pnmconvol        30.9           132.4
+ new general case          14.4            31.8
+ new average case           1.8             2.6
+ 
+ 
+ Send all questions/comments/bugs to me at burns@chem.psu.edu.
+ 
+ - Mike
+ 
+ ----------------------------------------------------------------------------
+ Mike Burns                                              System Administrator
+ burns@chem.psu.edu                                   Department of Chemistry
+ (814) 863-2123                             The Pennsylvania State University
+ ----------------------------------------------------------------------------
+
+*/
diff --git a/editor/pnmcrop.c b/editor/pnmcrop.c
new file mode 100644
index 00000000..5fafbaac
--- /dev/null
+++ b/editor/pnmcrop.c
@@ -0,0 +1,613 @@
+/* pnmcrop.c - crop a portable anymap
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* IDEA FOR EFFICIENCY IMPROVEMENT:
+
+   If we have to read the input into a regular file because it is not
+   seekable (a pipe), find the borders as we do the copy, so that we
+   do 2 passes through the file instead of 3.  Also find the background
+   color in that pass to save yet another pass with -sides.
+*/
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+enum bg_choice {BG_BLACK, BG_WHITE, BG_DEFAULT, BG_SIDES};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilespec;
+    enum bg_choice background;
+    unsigned int left, right, top, bottom;
+    unsigned int verbose;
+    unsigned int margin;
+    const char * borderfile;  /* NULL if none */
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int blackOpt, whiteOpt, sidesOpt;
+    unsigned int marginSpec, borderfileSpec;
+    
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "black",      OPT_FLAG, NULL, &blackOpt,            0);
+    OPTENT3(0, "white",      OPT_FLAG, NULL, &whiteOpt,            0);
+    OPTENT3(0, "sides",      OPT_FLAG, NULL, &sidesOpt,            0);
+    OPTENT3(0, "left",       OPT_FLAG, NULL, &cmdlineP->left,      0);
+    OPTENT3(0, "right",      OPT_FLAG, NULL, &cmdlineP->right,     0);
+    OPTENT3(0, "top",        OPT_FLAG, NULL, &cmdlineP->top,       0);
+    OPTENT3(0, "bottom",     OPT_FLAG, NULL, &cmdlineP->bottom,    0);
+    OPTENT3(0, "verbose",    OPT_FLAG, NULL, &cmdlineP->verbose,   0);
+    OPTENT3(0, "margin",     OPT_UINT,   &cmdlineP->margin,    
+            &marginSpec,     0);
+    OPTENT3(0, "borderfile", OPT_STRING, &cmdlineP->borderfile,
+            &borderfileSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 == 0)
+        cmdlineP->inputFilespec = "-";  /* stdin */
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else 
+        pm_error("Too many arguments (%d).  "
+                 "Only need one: the input filespec", argc-1);
+
+    if (blackOpt && whiteOpt)
+        pm_error("You cannot specify both -black and -white");
+    else if (sidesOpt &&( blackOpt || whiteOpt ))
+        pm_error("You cannot specify both -sides and either -black or -white");
+    else if (blackOpt)
+        cmdlineP->background = BG_BLACK;
+    else if (whiteOpt)
+        cmdlineP->background = BG_WHITE;
+    else if (sidesOpt)
+        cmdlineP->background = BG_SIDES;
+    else
+        cmdlineP->background = BG_DEFAULT;
+
+    if (!cmdlineP->left && !cmdlineP->right && !cmdlineP->top
+        && !cmdlineP->bottom) {
+        cmdlineP->left = cmdlineP->right = cmdlineP->top 
+            = cmdlineP->bottom = TRUE;
+    }
+
+    if (!marginSpec)
+        cmdlineP->margin = 0;
+
+    if (!borderfileSpec)
+        cmdlineP->borderfile = NULL;
+}
+
+
+
+static xel
+background3Corners(FILE * const ifP,
+                   int    const rows,
+                   int    const cols,
+                   pixval const maxval,
+                   int    const format) {
+/*----------------------------------------------------------------------------
+  Read in the whole image, and check all the corners to determine the
+  background color.  This is a quite reliable way to determine the
+  background color.
+
+  Expect the file to be positioned to the start of the raster, and leave
+  it positioned arbitrarily.
+----------------------------------------------------------------------------*/
+    int row;
+    xel ** xels;
+    xel background;   /* our return value */
+
+    xels = pnm_allocarray(cols, rows);
+
+    for (row = 0; row < rows; ++row)
+        pnm_readpnmrow( ifP, xels[row], cols, maxval, format );
+
+    background = pnm_backgroundxel(xels, cols, rows, maxval, format);
+
+    pnm_freearray(xels, rows);
+
+    return background;
+}
+
+
+
+static xel
+background2Corners(FILE * const ifP,
+                   int    const cols,
+                   pixval const maxval,
+                   int    const format) {
+/*----------------------------------------------------------------------------
+  Look at just the top row of pixels and determine the background
+  color from the top corners; often this is enough to accurately
+  determine the background color.
+
+  Expect the file to be positioned to the start of the raster, and leave
+  it positioned arbitrarily.
+----------------------------------------------------------------------------*/
+    xel *xelrow;
+    xel background;   /* our return value */
+    
+    xelrow = pnm_allocrow(cols);
+
+    pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+
+    background = pnm_backgroundxelrow(xelrow, cols, maxval, format);
+
+    pnm_freerow(xelrow);
+
+    return background;
+}
+
+
+
+static xel
+computeBackground(FILE *         const ifP,
+                  int            const cols,
+                  int            const rows,
+                  xelval         const maxval,
+                  int            const format,
+                  enum bg_choice const backgroundChoice,
+                  int            const verbose) {
+/*----------------------------------------------------------------------------
+   Determine what color is the background color of the image in file
+   *ifP, which is described by 'cols', 'rows', 'maxval', and 'format'.
+
+   'backgroundChoice' is the method we are to use in determining the
+   background color.
+   
+   Expect the file to be positioned to the start of the raster, and leave
+   it positioned arbitrarily.
+-----------------------------------------------------------------------------*/
+    xel background;  /* Our return value */
+    
+    switch (backgroundChoice) {
+    case BG_WHITE:
+	    background = pnm_whitexel(maxval, format);
+        break;
+    case BG_BLACK:
+	    background = pnm_blackxel(maxval, format);
+        break;
+    case BG_SIDES: 
+        background = 
+            background3Corners(ifP, rows, cols, maxval, format);
+        break;
+    case BG_DEFAULT: 
+        background = 
+            background2Corners(ifP, cols, maxval, format);
+        break;
+    }
+
+    if (verbose)
+        pm_message("Background color is %s", 
+                   ppm_colorname(&background, maxval, TRUE /*hexok*/));
+
+    return(background);
+}
+
+
+
+static void
+findBordersInImage(FILE *         const ifP,
+                   unsigned int   const cols,
+                   unsigned int   const rows,
+                   xelval         const maxval,
+                   int            const format,
+                   xel            const backgroundColor,
+                   bool           const verbose, 
+                   bool *         const hasBordersP,
+                   unsigned int * const leftP,
+                   unsigned int * const rightP, 
+                   unsigned int * const topP,
+                   unsigned int * const bottomP) {
+/*----------------------------------------------------------------------------
+   Find the left, right, top, and bottom borders in the image 'ifP'.
+   Return their sizes in pixels as *leftP, *rightP, *topP, and *bottomP.
+   
+   Iff the image is all background, *hasBordersP == FALSE.
+
+   Expect the input file to be positioned to the beginning of the
+   image raster and leave it positioned arbitrarily.
+-----------------------------------------------------------------------------*/
+    xel* xelrow;        /* A row of the input image */
+    int row;
+    bool gottop;
+    int left, right, bottom, top;
+        /* leftmost, etc. nonbackground pixel found so far; -1 for none */
+
+    xelrow = pnm_allocrow(cols);
+    
+    left   = cols;  /* initial value */
+    right  = -1;   /* initial value */
+    top    = rows;   /* initial value */
+    bottom = -1;  /* initial value */
+
+    gottop = FALSE;
+    for (row = 0; row < rows; ++row) {
+        int col;
+        int thisRowLeft;
+        int thisRowRight;
+
+        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+        
+        col = 0;
+        while (PNM_EQUAL(xelrow[col], backgroundColor) && col < cols)
+            ++col;
+        thisRowLeft = col;
+
+        col = cols-1;
+        while (col >= thisRowLeft && PNM_EQUAL(xelrow[col], backgroundColor))
+            --col;
+        thisRowRight = col + 1;
+
+        if (thisRowLeft < cols) {
+            /* This row is not entirely background */
+            
+            left  = MIN(thisRowLeft,  left);
+            right = MAX(thisRowRight, right);
+
+            if (!gottop) {
+                gottop = TRUE;
+                top = row;
+            }
+            bottom = row + 1;   /* New candidate */
+        }
+    }
+
+    if (right == -1)
+        *hasBordersP = FALSE;
+    else {
+        *hasBordersP = TRUE;
+        assert(right <= cols); assert(bottom <= rows);
+        *leftP       = left - 0;
+        *rightP      = cols - right;
+        *topP        = top - 0;
+        *bottomP     = rows - bottom;
+    }
+}
+
+
+
+static void
+findBordersInFile(const char *   const borderFileName,
+                  xel            const backgroundColor,
+                  bool           const verbose, 
+                  bool *         const hasBordersP,
+                  unsigned int * const leftP,
+                  unsigned int * const rightP, 
+                  unsigned int * const topP,
+                  unsigned int * const bottomP) {
+
+    FILE * borderFileP;
+    int cols;
+    int rows;
+    xelval maxval;
+    int format;
+    
+    borderFileP = pm_openr(borderFileName);
+    
+    pnm_readpnminit(borderFileP, &cols, &rows, &maxval, &format);
+    
+    findBordersInImage(borderFileP, cols, rows, maxval, format, 
+                       backgroundColor, verbose, hasBordersP,
+                       leftP, rightP, topP, bottomP);
+
+    pm_close(borderFileP);
+} 
+
+
+
+static void
+reportOneEdge(unsigned int const oldBorderSize,
+              unsigned int const newBorderSize,
+              const char * const place) {
+
+#define ending(n) (((n) > 1) ? "s" : "")
+
+    if (newBorderSize > oldBorderSize)
+        pm_message("Adding %u pixel%s to the %u-pixel %s border",
+                   newBorderSize - oldBorderSize,
+                   ending(newBorderSize - oldBorderSize),
+                   oldBorderSize, place);
+    else if (newBorderSize < oldBorderSize)
+        pm_message("Cropping %u pixel%s from the %u-pixel %s border",
+                   oldBorderSize - newBorderSize,
+                   ending(oldBorderSize - newBorderSize),
+                   oldBorderSize, place);
+    else
+        pm_message("Leaving %s border unchanged at %u pixel%s",
+                   place, oldBorderSize, ending(oldBorderSize));
+}        
+
+
+
+static void
+reportCroppingParameters(unsigned int const oldLeftBorderSize,
+                         unsigned int const oldRightBorderSize,
+                         unsigned int const oldTopBorderSize,
+                         unsigned int const oldBottomBorderSize,
+                         unsigned int const newLeftBorderSize,
+                         unsigned int const newRightBorderSize,
+                         unsigned int const newTopBorderSize,
+                         unsigned int const newBottomBorderSize) {
+
+    if (oldLeftBorderSize == 0 && oldRightBorderSize == 0 &&
+        oldTopBorderSize == 0 && oldBottomBorderSize == 0)
+        pm_message("No Border found.");
+
+    reportOneEdge(oldLeftBorderSize,   newLeftBorderSize,   "left"   );
+    reportOneEdge(oldRightBorderSize,  newRightBorderSize,  "right"  );
+    reportOneEdge(oldTopBorderSize,    newTopBorderSize,    "top"    );
+    reportOneEdge(oldBottomBorderSize, newBottomBorderSize, "bottom" );
+}
+
+
+
+
+static void
+fillRow(xel *        const xelrow,
+        unsigned int const cols,
+        xel          const color) {
+
+    unsigned int col;
+
+    for (col = 0; col < cols; ++col)
+        xelrow[col] = color;
+}
+
+
+
+static void
+writeCropped(FILE *       const ifP,
+             unsigned int const cols,
+             unsigned int const rows,
+             xelval       const maxval,
+             int          const format,
+             unsigned int const oldLeftBorder,
+             unsigned int const oldRightBorder,
+             unsigned int const oldTopBorder,
+             unsigned int const oldBottomBorder,
+             unsigned int const newLeftBorder,
+             unsigned int const newRightBorder,
+             unsigned int const newTopBorder,
+             unsigned int const newBottomBorder,
+             xel          const backgroundColor,
+             FILE *       const ofP) {
+
+    /* In order to do cropping, padding or both at the same time, we have
+       a rather complicated row buffer:
+
+       xelrow[] is both the input and the output buffer.  So it contains
+       the foreground pixels, the original border pixels, and the new
+       border pixels.
+
+       The foreground pixels are in the center of the
+       buffer, starting at Column 'foregroundLeft' and going to
+       'foregroundRight'.
+
+       There is space to the left of that for the larger of the input
+       left border and the output left border.
+
+       Similarly, there is space to the right of the foreground pixels
+       for the larger of the input right border and the output right
+       border.
+
+       We have to read an entire row, including the pixels we'll be
+       leaving out of the output, so we pick a starting location in
+       the buffer that lines up the first foreground pixel at
+       'foregroundLeft'.
+
+       When we output the row, we pick a starting location in the 
+       buffer that includes the proper number of left border pixels
+       before 'foregroundLeft'.
+
+       That's for the middle rows.  For the top and bottom, we just use
+       the left portion of xelrow[], starting at 0.
+    */
+
+    unsigned int const foregroundCols =
+        cols - oldLeftBorder - oldRightBorder;
+    unsigned int const outputCols     = 
+        foregroundCols + newLeftBorder + newRightBorder;
+    unsigned int const foregroundRows =
+        rows - oldTopBorder - oldBottomBorder;
+    unsigned int const outputRows     =
+        foregroundRows + newTopBorder + newBottomBorder;
+
+    unsigned int const foregroundLeft  = MAX(oldLeftBorder, newLeftBorder);
+        /* Index into xelrow[] of leftmost pixel of foreground */
+    unsigned int const foregroundRight = foregroundLeft + foregroundCols;
+        /* Index into xelrow[] just past rightmost pixel of foreground */
+
+    unsigned int const allocCols =
+        foregroundRight + MAX(oldRightBorder, newRightBorder);
+
+    xel *xelrow;
+    unsigned int i;
+
+    assert(outputCols == newLeftBorder + foregroundCols + newRightBorder);
+    assert(outputRows == newTopBorder + foregroundRows + newBottomBorder);
+    
+    pnm_writepnminit(ofP, outputCols, outputRows, maxval, format, 0);
+
+    xelrow = pnm_allocrow(allocCols);
+
+    /* Read off existing top border */
+    for (i = 0; i < oldTopBorder; ++i)
+        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+
+
+    /* Output new top border */
+    fillRow(xelrow, outputCols, backgroundColor);
+    for (i = 0; i < newTopBorder; ++i)
+        pnm_writepnmrow(ofP, xelrow, outputCols, maxval, format, 0);
+
+
+    /* Read and output foreground rows */
+    for (i = 0; i < foregroundRows; ++i) {
+        /* Set left border pixels */
+        fillRow(&xelrow[foregroundLeft - newLeftBorder], newLeftBorder,
+                backgroundColor);
+
+        /* Read foreground pixels */
+        pnm_readpnmrow(ifP, &(xelrow[foregroundLeft - oldLeftBorder]), cols,
+                       maxval, format);
+
+        /* Set right border pixels */
+        fillRow(&xelrow[foregroundRight], newRightBorder, backgroundColor);
+        
+        pnm_writepnmrow(ofP,
+                        &(xelrow[foregroundLeft - newLeftBorder]), outputCols,
+                        maxval, format, 0);
+    }
+
+    /* Read off existing bottom border */
+    for (i = 0; i < oldBottomBorder; ++i)
+        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+
+    /* Output new bottom border */
+    fillRow(xelrow, outputCols, backgroundColor);
+    for (i = 0; i < newBottomBorder; ++i)
+        pnm_writepnmrow(ofP, xelrow, outputCols, maxval, format, 0);
+
+    pnm_freerow(xelrow);
+}
+
+
+
+static void
+determineNewBorders(struct cmdlineInfo const cmdline,
+                    unsigned int       const leftBorderSize,
+                    unsigned int       const rightBorderSize,
+                    unsigned int       const topBorderSize,
+                    unsigned int       const bottomBorderSize,
+                    unsigned int *     const newLeftSizeP,
+                    unsigned int *     const newRightSizeP,
+                    unsigned int *     const newTopSizeP,
+                    unsigned int *     const newBottomSizeP) {
+
+    *newLeftSizeP   = cmdline.left   ? cmdline.margin : leftBorderSize   ;
+    *newRightSizeP  = cmdline.right  ? cmdline.margin : rightBorderSize  ;
+    *newTopSizeP    = cmdline.top    ? cmdline.margin : topBorderSize    ;
+    *newBottomSizeP = cmdline.bottom ? cmdline.margin : bottomBorderSize ;
+}
+        
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;   
+        /* The program's regular input file.  Could be a seekable copy of it
+           in a temporary file.
+        */
+
+    xelval maxval;
+    int format;
+    int rows, cols;   /* dimensions of input image */
+    bool hasBorders;
+    unsigned int oldLeftBorder, oldRightBorder, oldTopBorder, oldBottomBorder;
+        /* The sizes of the borders in the input image */
+    unsigned int newLeftBorder, newRightBorder, newTopBorder, newBottomBorder;
+        /* The sizes of the borders in the output image */
+    xel background;
+    pm_filepos rasterpos;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr_seekable(cmdline.inputFilespec);
+
+    pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
+
+    pm_tell2(ifP, &rasterpos, sizeof(rasterpos));
+
+    background = computeBackground(ifP, cols, rows, maxval, format,
+                                   cmdline.background, cmdline.verbose);
+
+    if (cmdline.borderfile) {
+        findBordersInFile(cmdline.borderfile,
+                          background, cmdline.verbose, &hasBorders,
+                          &oldLeftBorder, &oldRightBorder,
+                          &oldTopBorder,  &oldBottomBorder);
+    } else {
+        pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
+
+        findBordersInImage(ifP, cols, rows, maxval, format, 
+                           background, cmdline.verbose, &hasBorders,
+                           &oldLeftBorder, &oldRightBorder,
+                           &oldTopBorder,  &oldBottomBorder);
+    }
+    if (!hasBorders)
+        pm_error("The image is entirely background; "
+                 "there is nothing to crop.");
+
+    determineNewBorders(cmdline, 
+                        oldLeftBorder, oldRightBorder,
+                        oldTopBorder,  oldBottomBorder,
+                        &newLeftBorder, &newRightBorder,
+                        &newTopBorder,  &newBottomBorder);
+
+    if (cmdline.verbose) 
+        reportCroppingParameters(oldLeftBorder, oldRightBorder,
+                                 oldTopBorder,  oldBottomBorder,
+                                 newLeftBorder, newRightBorder,
+                                 newTopBorder,  newBottomBorder);
+
+    pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
+
+    writeCropped(ifP, cols, rows, maxval, format,
+                 oldLeftBorder, oldRightBorder,
+                 oldTopBorder,  oldBottomBorder,
+                 newLeftBorder, newRightBorder,
+                 newTopBorder,  newBottomBorder,
+                 background, stdout);
+
+    pm_close(stdout);
+    pm_close(ifP);
+    
+    return 0;
+}
diff --git a/editor/pnmcut.c b/editor/pnmcut.c
new file mode 100644
index 00000000..a21fcffb
--- /dev/null
+++ b/editor/pnmcut.c
@@ -0,0 +1,427 @@
+ /* pnmcut.c - cut a rectangle out of a portable anymap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <limits.h>
+#include "pnm.h"
+#include "shhopt.h"
+
+#define UNSPEC INT_MAX
+    /* UNSPEC is the value we use for an argument that is not specified
+       by the user.  Theoretically, the user could specify this value,
+       but we hope not.
+       */
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec;  /* Filespecs of input files */
+
+    /* The following describe the rectangle the user wants to cut out. 
+       the value UNSPEC for any of them indicates that value was not
+       specified.  A negative value means relative to the far edge.
+       'width' and 'height' are not negative.  These specifications 
+       do not necessarily describe a valid rectangle; they are just
+       what the user said.
+       */
+    int left;
+    int right;
+    int top;
+    int bottom;
+    int width;
+    int height;
+    int pad;
+
+    int verbose;
+};
+
+
+
+static xel black_xel;  /* A black xel */
+
+
+static void
+parse_command_line(int argc, char ** argv,
+                   struct cmdline_info *cmdline_p) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct *option_def = malloc(100*sizeof(optStruct));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct2 opt;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENTRY(0,   "left",       OPT_INT,    &cmdline_p->left,           0);
+    OPTENTRY(0,   "right",      OPT_INT,    &cmdline_p->right,          0);
+    OPTENTRY(0,   "top",        OPT_INT,    &cmdline_p->top,            0);
+    OPTENTRY(0,   "bottom",     OPT_INT,    &cmdline_p->bottom,         0);
+    OPTENTRY(0,   "width",      OPT_INT,    &cmdline_p->width,          0);
+    OPTENTRY(0,   "height",     OPT_INT,    &cmdline_p->height,         0);
+    OPTENTRY(0,   "pad",        OPT_FLAG,   &cmdline_p->pad,            0);
+    OPTENTRY(0,   "verbose",    OPT_FLAG,   &cmdline_p->verbose,        0);
+
+    /* Set the defaults */
+    cmdline_p->left = UNSPEC;
+    cmdline_p->right = UNSPEC;
+    cmdline_p->top = UNSPEC;
+    cmdline_p->bottom = UNSPEC;
+    cmdline_p->width = UNSPEC;
+    cmdline_p->height = UNSPEC;
+    cmdline_p->pad = FALSE;
+    cmdline_p->verbose = FALSE;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = TRUE;  /* We may have parms that are negative numbers */
+
+    optParseOptions2(&argc, argv, opt, 0);
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (cmdline_p->width < 0)
+        pm_error("-width may not be negative.");
+    if (cmdline_p->height < 0)
+        pm_error("-height may not be negative.");
+
+    if ((argc-1) != 0 && (argc-1) != 1 && (argc-1) != 4 && (argc-1) != 5)
+        pm_error("Wrong number of arguments.  "
+                 "Must be 0, 1, 4, or 5 arguments.");
+
+    switch (argc-1) {
+    case 0:
+        cmdline_p->input_filespec = "-";
+        break;
+    case 1:
+        cmdline_p->input_filespec = argv[1];
+        break;
+    case 4:
+    case 5: {
+        int warg, harg;  /* The "width" and "height" command line arguments */
+
+        if (sscanf(argv[1], "%d", &cmdline_p->left) != 1)
+            pm_error("Invalid number for left column argument");
+        if (sscanf(argv[2], "%d", &cmdline_p->top) != 1)
+            pm_error("Invalid number for top row argument");
+        if (sscanf(argv[3], "%d", &warg) != 1)
+            pm_error("Invalid number for width argument");
+        if (sscanf(argv[4], "%d", &harg) != 1)
+            pm_error("Invalid number for height argument");
+
+        if (warg > 0) {
+            cmdline_p->width = warg;
+            cmdline_p->right = UNSPEC;
+        } else {
+            cmdline_p->width = UNSPEC;
+            cmdline_p->right = warg -1;
+        }
+        if (harg > 0) {
+            cmdline_p->height = harg;
+            cmdline_p->bottom = UNSPEC;
+        } else {
+            cmdline_p->height = UNSPEC;
+            cmdline_p->bottom = harg - 1;
+        }
+
+        if (argc-1 == 4)
+            cmdline_p->input_filespec = "-";
+        else
+            cmdline_p->input_filespec = argv[5];
+        break;
+    }
+    }
+}
+
+
+
+static void
+compute_cut_bounds(const int cols, const int rows,
+                   const int leftarg, const int rightarg, 
+                   const int toparg, const int bottomarg,
+                   const int widtharg, const int heightarg,
+                   int * const leftcol_p, int * const rightcol_p,
+                   int * const toprow_p, int * const bottomrow_p) {
+/*----------------------------------------------------------------------------
+   From the values given on the command line 'leftarg', 'rightarg',
+   'toparg', 'bottomarg', 'widtharg', and 'heightarg', determine what
+   rectangle the user wants cut out.
+
+   Any of these arguments may be UNSPEC to indicate "not specified".
+   Any except 'widtharg' and 'heightarg' may be negative to indicate
+   relative to the far edge.  'widtharg' and 'heightarg' are positive.
+
+   Return the location of the rectangle as *leftcol_p, *rightcol_p,
+   *toprow_p, and *bottomrow_p.  
+-----------------------------------------------------------------------------*/
+
+    int leftcol, rightcol, toprow, bottomrow;
+        /* The left and right column numbers and top and bottom row numbers
+           specified by the user, except with negative values translated
+           into the actual values.
+
+           Note that these may very well be negative themselves, such
+           as when the user says "column -10" and there are only 5 columns
+           in the image.
+           */
+
+    /* Translate negative column and row into real column and row */
+    /* Exploit the fact that UNSPEC is a positive number */
+
+    if (leftarg >= 0)
+        leftcol = leftarg;
+    else
+        leftcol = cols + leftarg;
+    if (rightarg >= 0)
+        rightcol = rightarg;
+    else
+        rightcol = cols + rightarg;
+    if (toparg >= 0)
+        toprow = toparg;
+    else
+        toprow = rows + toparg;
+    if (bottomarg >= 0)
+        bottomrow = bottomarg;
+    else
+        bottomrow = rows + bottomarg;
+
+    /* Sort out left, right, and width specifications */
+
+    if (leftcol == UNSPEC && rightcol == UNSPEC && widtharg == UNSPEC) {
+        *leftcol_p = 0;
+        *rightcol_p = cols - 1;
+    }
+    if (leftcol == UNSPEC && rightcol == UNSPEC && widtharg != UNSPEC) {
+        *leftcol_p = 0;
+        *rightcol_p = 0 + widtharg - 1;
+    }
+    if (leftcol == UNSPEC && rightcol != UNSPEC && widtharg == UNSPEC) {
+        *leftcol_p = 0;
+        *rightcol_p = rightcol;
+    }
+    if (leftcol == UNSPEC && rightcol != UNSPEC && widtharg != UNSPEC) {
+        *leftcol_p = rightcol - widtharg + 1;
+        *rightcol_p = rightcol;
+    }
+    if (leftcol != UNSPEC && rightcol == UNSPEC && widtharg == UNSPEC) {
+        *leftcol_p = leftcol;
+        *rightcol_p = cols - 1;
+    }
+    if (leftcol != UNSPEC && rightcol == UNSPEC && widtharg != UNSPEC) {
+        *leftcol_p = leftcol;
+        *rightcol_p = leftcol + widtharg - 1;
+    }
+    if (leftcol != UNSPEC && rightcol != UNSPEC && widtharg == UNSPEC) {
+        *leftcol_p = leftcol;
+        *rightcol_p = rightcol;
+    }
+    if (leftcol != UNSPEC && rightcol != UNSPEC && widtharg != UNSPEC) {
+        pm_error("You may not specify left, right, and width.\n"
+                 "Choose at most two of these.");
+    }
+
+
+    /* Sort out top, bottom, and height specifications */
+
+    if (toprow == UNSPEC && bottomrow == UNSPEC && heightarg == UNSPEC) {
+        *toprow_p = 0;
+        *bottomrow_p = rows - 1;
+    }
+    if (toprow == UNSPEC && bottomrow == UNSPEC && heightarg != UNSPEC) {
+        *toprow_p = 0;
+        *bottomrow_p = 0 + heightarg - 1;
+    }
+    if (toprow == UNSPEC && bottomrow != UNSPEC && heightarg == UNSPEC) {
+        *toprow_p = 0;
+        *bottomrow_p = bottomrow;
+    }
+    if (toprow == UNSPEC && bottomrow != UNSPEC && heightarg != UNSPEC) {
+        *toprow_p = bottomrow - heightarg + 1;
+        *bottomrow_p = bottomrow;
+    }
+    if (toprow != UNSPEC && bottomrow == UNSPEC && heightarg == UNSPEC) {
+        *toprow_p = toprow;
+        *bottomrow_p = rows - 1;
+    }
+    if (toprow != UNSPEC && bottomrow == UNSPEC && heightarg != UNSPEC) {
+        *toprow_p = toprow;
+        *bottomrow_p = toprow + heightarg - 1;
+    }
+    if (toprow != UNSPEC && bottomrow != UNSPEC && heightarg == UNSPEC) {
+        *toprow_p = toprow;
+        *bottomrow_p = bottomrow;
+    }
+    if (toprow != UNSPEC && bottomrow != UNSPEC && heightarg != UNSPEC) {
+        pm_error("You may not specify top, bottom, and height.\n"
+                 "Choose at most two of these.");
+    }
+
+}
+
+
+
+static void
+reject_out_of_bounds(const int cols, const int rows, 
+                     const int leftcol, const int rightcol, 
+                     const int toprow, const int bottomrow) {
+
+    /* Reject coordinates off the edge */
+
+    if (leftcol < 0)
+        pm_error("You have specified a left edge (%d) that is beyond\n"
+                 "the left edge of the image (0)", leftcol);
+    if (rightcol > cols-1)
+        pm_error("You have specified a right edge (%d) that is beyond\n"
+                 "the right edge of the image (%d)", rightcol, cols-1);
+    if (rightcol < 0)
+        pm_error("You have specified a right edge (%d) that is beyond\n"
+                 "the left edge of the image (0)", rightcol);
+    if (rightcol > cols-1)
+        pm_error("You have specified a right edge (%d) that is beyond\n"
+                 "the right edge of the image (%d)", rightcol, cols-1);
+    if (leftcol > rightcol) 
+        pm_error("You have specified a left edge (%d) that is to the right\n"
+                 "of the right edge you specified (%d)", 
+                 leftcol, rightcol);
+    
+    if (toprow < 0)
+        pm_error("You have specified a top edge (%d) that is above the top "
+                 "edge of the image (0)", toprow);
+    if (bottomrow > rows-1)
+        pm_error("You have specified a bottom edge (%d) that is below the\n"
+                 "bottom edge of the image (%d)", bottomrow, rows-1);
+    if (bottomrow < 0)
+        pm_error("You have specified a bottom edge (%d) that is above the\n"
+                 "top edge of the image (0)", bottomrow);
+    if (bottomrow > rows-1)
+        pm_error("You have specified a bottom edge (%d) that is below the\n"
+                 "bottom edge of the image (%d)", bottomrow, rows-1);
+    if (toprow > bottomrow) 
+        pm_error("You have specified a top edge (%d) that is below\n"
+                 "the bottom edge you specified (%d)", 
+                 toprow, bottomrow);
+}
+
+
+
+static void
+write_black_rows(FILE *outfile, const int rows, const int cols, 
+                 xel * const output_row,
+                 const pixval maxval, const int format) {
+/*----------------------------------------------------------------------------
+   Write out to file 'outfile' 'rows' rows of 'cols' black xels each,
+   part of an image of format 'format' with maxval 'maxval'.  
+
+   Use *output_row as a buffer.  It is at least 'cols' xels wide.
+-----------------------------------------------------------------------------*/
+    int row;
+    for (row = 0; row < rows; row++) {
+        int col;
+        for (col = 0; col < cols; col++) output_row[col] = black_xel;
+        pnm_writepnmrow(outfile, output_row, cols, maxval, format, 0);
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE* ifp;
+    xel* xelrow;  /* Row from input image */
+    xel* output_row;  /* Row of output image */
+    xelval maxval;
+    int rows, cols, format, row;
+    int leftcol, rightcol, toprow, bottomrow;
+    int output_cols;  /* Width of output image */
+    struct cmdline_info cmdline;
+
+    pnm_init( &argc, argv );
+
+    parse_command_line(argc, argv, &cmdline);
+
+    ifp = pm_openr(cmdline.input_filespec);
+
+    pnm_readpnminit(ifp, &cols, &rows, &maxval, &format);
+    xelrow = pnm_allocrow(cols);
+
+    black_xel = pnm_blackxel(maxval, format);
+
+    compute_cut_bounds(cols, rows, 
+                       cmdline.left, cmdline.right, 
+                       cmdline.top, cmdline.bottom, 
+                       cmdline.width, cmdline.height, 
+                       &leftcol, &rightcol, &toprow, &bottomrow);
+
+    if (!cmdline.pad)
+        reject_out_of_bounds(cols, rows, leftcol, rightcol, toprow, bottomrow);
+
+    if (cmdline.verbose) {
+        pm_message("Image goes from Row 0, Column 0 through Row %d, Column %d",
+                   rows-1, cols-1);
+        pm_message("Cutting from Row %d, Column %d through Row %d Column %d",
+                   toprow, leftcol, bottomrow, rightcol);
+    }
+
+    output_cols = rightcol-leftcol+1;
+    output_row = pnm_allocrow(output_cols);
+    
+    pnm_writepnminit(stdout, output_cols, bottomrow-toprow+1, 
+                     maxval, format, 0 );
+
+    /* Implementation note:  If speed is ever an issue, we can probably
+       speed up significantly the non-padding case by writing a special
+       case loop here for the case cmdline.pad == FALSE.
+       */
+
+    /* Write out top padding */
+    write_black_rows(stdout, 0 - toprow, output_cols, output_row, 
+                     maxval, format);
+    
+    /* Read input and write out rows extracted from it */
+    for (row = 0; row < rows; row++) {
+        pnm_readpnmrow(ifp, xelrow, cols, maxval, format);
+        if (row >= toprow && row <= bottomrow) {
+            int col;
+            /* Put in left padding */
+            for (col = leftcol; col < 0; col++) { 
+                output_row[col-leftcol] = black_xel;
+            }
+            /* Put in extracted columns */
+            for (col = MAX(leftcol, 0); col <= MIN(rightcol, cols-1); col++) {
+                output_row[col-leftcol] = xelrow[col];
+            }
+            /* Put in right padding */
+            for (col = MAX(cols, leftcol); col <= rightcol; col++) {
+                output_row[col-leftcol] = black_xel;
+            }
+            pnm_writepnmrow(stdout, output_row, output_cols, 
+                            maxval, format, 0);
+        }
+    }
+    /* Note that we may be tempted just to quit after reaching the bottom
+       of the extracted image, but that would cause a broken pipe problem
+       for the process that's feeding us the image.
+       */
+    /* Write out bottom padding */
+    write_black_rows(stdout, bottomrow - (rows-1), output_cols, output_row, 
+                     maxval, format);
+
+    pnm_freerow(output_row);
+    pnm_freerow(xelrow);
+    pm_close(ifp);
+    pm_close(stdout);
+    
+    exit( 0 );
+}
+
diff --git a/editor/pnmflip b/editor/pnmflip
new file mode 100755
index 00000000..6149aaa2
--- /dev/null
+++ b/editor/pnmflip
@@ -0,0 +1,80 @@
+#!/usr/bin/perl -w
+
+#============================================================================
+#  This is a compatibility interface to Pamflip.
+#
+#  It exists so existing programs and procedures that rely on Pnmflip
+#  syntax continue to work.  You should not make new use of Pnmflip and
+#  if you modify an old use, you should upgrade it to use Pamflip.
+#
+#  The one way that Pamflip is not backward compatible with Pnmflip is
+#  that with Pnmflip, you can do this:
+#
+#     pnmflip -xy -tb
+#
+#  and that causes pnmflip to do both transformations (i.e. the same thing
+#  as -r270).  With Pamflip, you can't specify multiple (or zero) flip
+#  type options.  Instead, you would use the -xform option:
+#
+#    pamflip -xform=transpose,topbottom
+#
+#============================================================================
+
+use strict;
+use File::Basename;
+use Cwd 'abs_path';
+
+my $xformOpt;
+my @miscOptions;
+my $infile;
+
+$xformOpt = '-xform=';  # initial value 
+
+@miscOptions = ();
+
+foreach (@ARGV) {
+    if (/^-/) {
+        # It's an option
+        if (/^-lr$/ || /^-le.*$/) {
+            $xformOpt .= "leftright,";
+        } elsif (/^-tb$/ || /^-to.*$/) {
+            $xformOpt .= "topbottom,";
+        } elsif (/^-x.*$/ || /^-tr.*$/) {
+            $xformOpt .= "transpose,";
+        } elsif (/^-r9.*$/ || /^-rotate9.*$/ || /^-cc.*$/) {
+            $xformOpt .= "transpose,topbottom,";
+        } elsif (/^-r1.*$/ || /^-rotate1.*$/) {
+            $xformOpt .= "leftright,topbottom,";
+        } elsif (/^-r2.*$/ || /^-rotate2.*$/ || /^-cw$/) {
+            $xformOpt .= "transpose,leftright,";
+        } else {
+            # It's not a transformation option; could be a Netpbm common
+            # option.
+            push(@miscOptions, $_);
+        }
+    } else {
+        # It's a parameter
+        if (defined($infile)) {
+            print(STDERR
+                  "You may specify at most one non-option parameter.\n");
+        } else {
+            $infile = $_;
+        }
+    }
+}
+
+# Finish off the -xform option by removing any trailing comma
+
+$/ = ',';
+chomp($xformOpt);
+
+my $infileParm = defined($infile) ? $infile : "-";
+
+# We want to get Pamflip from the same directory we came from if
+# it's there.  Frequently, the directory containing Netpbm programs is
+# not in the PATH and we were invoked by absolute path.
+
+my $my_directory = abs_path(dirname($0));
+$ENV{"PATH"} = $my_directory . ":" . $ENV{"PATH"};
+
+exec("pamflip", @miscOptions, $xformOpt, $infileParm);
diff --git a/editor/pnmgamma.c b/editor/pnmgamma.c
new file mode 100644
index 00000000..e13075ff
--- /dev/null
+++ b/editor/pnmgamma.c
@@ -0,0 +1,753 @@
+/* pnmgamma.c - perform gamma correction on a PNM image
+**
+** Copyright (C) 1991 by Bill Davidson and Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <math.h>
+#include <ctype.h>
+
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "pnm.h"
+
+enum transferFunction {
+    XF_EXP,
+    XF_EXP_INVERSE,
+    XF_BT709RAMP,
+    XF_BT709RAMP_INVERSE,
+    XF_SRGBRAMP,
+    XF_SRGBRAMP_INVERSE,
+    XF_BT709_TO_SRGB,
+    XF_SRGB_TO_BT709
+};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *filespec;  /* '-' if stdin */
+    enum transferFunction transferFunction;
+    float rgamma, ggamma, bgamma;
+    unsigned int maxval;
+    unsigned int makeNewMaxval;
+};
+
+
+
+static void
+interpretOldArguments(int                  const argc,
+                      char **              const argv,
+                      float                const defaultGamma,
+                      struct cmdlineInfo * const cmdlineP) {
+
+    /* Use the old syntax wherein the gamma values come from arguments.
+       If there is one argument, it's a gamma value for all three
+       components.  If 3 arguments, it's separate gamma values.  If
+       2, it's a single gamma value plus a file name.  If 4, it's
+       separate gamma values plus a file name.
+    */
+    if (argc-1 == 0) {
+        cmdlineP->rgamma = defaultGamma;
+        cmdlineP->ggamma = defaultGamma;
+        cmdlineP->bgamma = defaultGamma;
+        cmdlineP->filespec = "-";
+    } else if (argc-1 == 1) {
+        cmdlineP->rgamma = atof(argv[1]);
+        cmdlineP->ggamma = atof(argv[1]);
+        cmdlineP->bgamma = atof(argv[1]);
+        cmdlineP->filespec = "-";
+    } else if (argc-1 == 2) {
+        cmdlineP->rgamma = atof(argv[1]);
+        cmdlineP->ggamma = atof(argv[1]);
+        cmdlineP->bgamma = atof(argv[1]);
+        cmdlineP->filespec = argv[2];
+    } else if (argc-1 == 3) {
+        cmdlineP->rgamma = atof(argv[1]);
+        cmdlineP->ggamma = atof(argv[2]);
+        cmdlineP->bgamma = atof(argv[3]);
+        cmdlineP->filespec = "-";
+    } else if (argc-1 == 4) {
+        cmdlineP->rgamma = atof(argv[1]);
+        cmdlineP->ggamma = atof(argv[2]);
+        cmdlineP->bgamma = atof(argv[3]);
+        cmdlineP->filespec = argv[4];
+    } else 
+        pm_error("Wrong number of arguments.  "
+                 "You may have 0, 1, or 3 gamma values "
+                 "plus zero or one filename");
+        
+    if (cmdlineP->rgamma <= 0.0 || 
+        cmdlineP->ggamma <= 0.0 || 
+        cmdlineP->bgamma <= 0.0 )
+        pm_error("Invalid gamma value.  Must be positive floating point "
+                 "number.");
+}
+
+
+
+static void
+getGammaFromOpts(struct cmdlineInfo * const cmdlineP,
+                 bool                 const gammaSpec,
+                 float                const gammaOpt,
+                 bool                 const rgammaSpec,
+                 bool                 const ggammaSpec,
+                 bool                 const bgammaSpec,
+                 float                const defaultGamma) {
+
+    if (gammaSpec)
+        if (gammaOpt < 0.0)
+            pm_error("Invalid gamma value.  "
+                         "Must be positive floating point number.");
+    
+    if (rgammaSpec) {
+        if (cmdlineP->rgamma < 0.0)
+            pm_error("Invalid gamma value.  "
+                     "Must be positive floating point number.");
+    } else {
+        if (gammaSpec)
+            cmdlineP->rgamma = gammaOpt;
+        else 
+            cmdlineP->rgamma = defaultGamma;
+    }
+    if (ggammaSpec) {
+        if (cmdlineP->ggamma < 0.0) 
+            pm_error("Invalid gamma value.  "
+                     "Must be positive floating point number.");
+    } else {
+        if (gammaSpec)
+            cmdlineP->ggamma = gammaOpt;
+        else 
+            cmdlineP->ggamma = defaultGamma;
+    }
+    if (bgammaSpec) {
+        if (cmdlineP->bgamma < 0.0)
+            pm_error("Invalid gamma value.  "
+                     "Must be positive floating point number.");
+    } else {
+        if (gammaSpec)
+            cmdlineP->bgamma = gammaOpt;
+        else
+            cmdlineP->bgamma = defaultGamma;
+    }
+}
+
+
+
+static void
+parseCommandLine(int argc, char ** argv, 
+                 struct cmdlineInfo * const cmdlineP) {
+
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int bt709ramp, srgbramp, ungamma, bt709tosrgb, srgbtobt709;
+    unsigned int bt709tolinear, lineartobt709;
+    unsigned int gammaSpec, rgammaSpec, ggammaSpec, bgammaSpec;
+    float gammaOpt;
+    float defaultGamma;
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "ungamma",       OPT_FLAG,   NULL,
+            &ungamma,                 0);
+    OPTENT3(0, "bt709tolinear", OPT_FLAG,   NULL,
+            &bt709tolinear,           0);
+    OPTENT3(0, "lineartobt709", OPT_FLAG,   NULL,
+            &lineartobt709,           0);
+    OPTENT3(0, "bt709ramp",     OPT_FLAG,   NULL,
+            &bt709ramp,               0);
+    OPTENT3(0, "cieramp",       OPT_FLAG,   NULL,
+            &bt709ramp,               0);
+    OPTENT3(0, "srgbramp",      OPT_FLAG,   NULL,
+            &srgbramp,                0);
+    OPTENT3(0, "bt709tosrgb",   OPT_FLAG,   NULL,
+            &bt709tosrgb,             0);
+    OPTENT3(0, "srgbtobt709",   OPT_FLAG,   NULL,
+            &srgbtobt709,             0);
+    OPTENT3(0, "maxval",        OPT_UINT,   &cmdlineP->maxval,
+            &cmdlineP->makeNewMaxval, 0);
+    OPTENT3(0, "gamma",         OPT_FLOAT,  &gammaOpt,
+            &gammaSpec,               0);
+    OPTENT3(0, "rgamma",        OPT_FLOAT,  &cmdlineP->rgamma,
+            &rgammaSpec,              0);
+    OPTENT3(0, "ggamma",        OPT_FLOAT,  &cmdlineP->ggamma,
+            &ggammaSpec,              0);
+    OPTENT3(0, "bgamma",        OPT_FLOAT,  &cmdlineP->bgamma,
+            &bgammaSpec,              0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = TRUE; 
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (bt709tolinear + lineartobt709 + bt709ramp + srgbramp +
+        bt709tosrgb + srgbtobt709 > 1)
+        pm_error("You may specify only one function option");
+    else {
+        if (bt709tolinear) {
+            if (ungamma)
+                pm_error("You cannot specify -ungamma with -bt709tolinear");
+            else
+                cmdlineP->transferFunction = XF_BT709RAMP_INVERSE;
+        } else if (lineartobt709) {
+            if (ungamma)
+                pm_error("You cannot specify -ungamma with -lineartobt709");
+            else
+                cmdlineP->transferFunction = XF_BT709RAMP;
+        } else if (bt709tosrgb) {
+            if (ungamma)
+                pm_error("You cannot specify -ungamma with -bt709tosrgb");
+            else
+                cmdlineP->transferFunction = XF_BT709_TO_SRGB;
+        } else if (srgbtobt709) {
+            if (ungamma)
+                pm_error("You cannot specify -ungamma with -srgbtobt709");
+            else
+                cmdlineP->transferFunction = XF_SRGB_TO_BT709;
+        } else if (bt709ramp) {
+            if (ungamma)
+                cmdlineP->transferFunction = XF_BT709RAMP_INVERSE;
+            else
+                cmdlineP->transferFunction = XF_BT709RAMP;
+        } else if (srgbramp) {
+            if (ungamma)
+                cmdlineP->transferFunction = XF_SRGBRAMP_INVERSE;
+            else
+                cmdlineP->transferFunction = XF_SRGBRAMP;
+        } else {
+            if (ungamma)
+                cmdlineP->transferFunction = XF_EXP_INVERSE;
+            else
+                cmdlineP->transferFunction = XF_EXP;
+        }
+    }
+
+    if (cmdlineP->makeNewMaxval) {
+        if (cmdlineP->maxval > PNM_OVERALLMAXVAL)
+            pm_error("Largest possible maxval is %u.  You specified %u",
+                     PNM_OVERALLMAXVAL, cmdlineP->maxval);
+    }
+
+    switch (cmdlineP->transferFunction) {
+    case XF_BT709RAMP:
+    case XF_BT709RAMP_INVERSE:
+    case XF_SRGB_TO_BT709:
+        defaultGamma = 1.0/0.45;
+        break;
+    case XF_SRGBRAMP:
+    case XF_SRGBRAMP_INVERSE:
+    case XF_BT709_TO_SRGB:
+        /* The whole function is often approximated with
+           exponent 2.2 and no linear piece.  We do the linear
+           piece, so we use the real exponent of 2.4.
+        */
+        defaultGamma = 2.4;
+        break;
+    case XF_EXP:
+    case XF_EXP_INVERSE:
+        defaultGamma = 2.2;
+        break;
+    }
+
+    if (bt709tolinear || lineartobt709 || bt709tosrgb || srgbtobt709) {
+        /* Use the new syntax wherein the gamma values come from options,
+           not arguments.  So if there's an argument, it's a file name.
+        */
+        getGammaFromOpts(cmdlineP, gammaSpec, gammaOpt,
+                         rgammaSpec, ggammaSpec, bgammaSpec, defaultGamma);
+
+        if (argc-1 < 1)
+            cmdlineP->filespec = "-";
+        else {
+            cmdlineP->filespec = argv[1];
+            if (argc-1 > 1)
+                pm_error("Too many arguments (%u).  With this function, there "
+                         "is at most one argument:  the file name", argc-1);
+        }
+    } else {
+        if (gammaSpec || rgammaSpec || ggammaSpec || bgammaSpec)
+            pm_error("With this function, you specify the gamma values in "
+                     "arguments, not with the -gamma, etc.");
+        interpretOldArguments(argc, argv, defaultGamma, cmdlineP);
+    }
+}
+
+
+
+static void
+buildPowGamma(xelval       table[],
+              xelval const maxval,
+              xelval const newMaxval,
+              double const gamma) {
+/*----------------------------------------------------------------------------
+   Build a gamma table of size maxval+1 for the given gamma value.
+  
+   This function depends on pow(3m).  If you don't have it, you can
+   simulate it with '#define pow(x,y) exp((y)*log(x))' provided that
+   you have the exponential function exp(3m) and the natural logarithm
+   function log(3m).  I can't believe I actually remembered my log
+   identities.
+-----------------------------------------------------------------------------*/
+    xelval i;
+    double const oneOverGamma = 1.0 / gamma;
+
+    for (i = 0 ; i <= maxval; ++i) {
+        double const normalized = ((double) i) / maxval;
+            /* Xel sample value normalized to 0..1 */
+        double const v = pow(normalized, oneOverGamma);
+        table[i] = MIN((xelval)(v * newMaxval + 0.5), newMaxval);  
+            /* denormalize, round and clip */
+    }
+}
+
+
+
+static void
+buildBt709Gamma(xelval       table[],
+                xelval const maxval,
+                xelval const newMaxval,
+                double const gamma) {
+/*----------------------------------------------------------------------------
+   Build a gamma table of size maxval+1 for the ITU Recommendation
+   BT.709 gamma transfer function.
+
+   'gamma' must be 1/0.45 for true Rec. 709.
+-----------------------------------------------------------------------------*/
+    double const oneOverGamma = 1.0 / gamma;
+    xelval i;
+
+    /* This transfer function is linear for sample values 0
+       .. maxval*.018 and an exponential for larger sample values.
+       The exponential is slightly stretched and translated, though,
+       unlike the popular pure exponential gamma transfer function.
+    */
+    xelval const linearCutoff = (xelval) (maxval * 0.018 + 0.5);
+    double const linearExpansion = 
+        (1.099 * pow(0.018, oneOverGamma) - 0.099) / 0.018;
+    double const maxvalScaler = (double)newMaxval/maxval;
+
+    for (i = 0; i <= linearCutoff; ++i) 
+        table[i] = i * linearExpansion * maxvalScaler + 0.5;
+    for (; i <= maxval; ++i) {
+        double const normalized = ((double) i) / maxval;
+            /* Xel sample value normalized to 0..1 */
+        double const v = 1.099 * pow(normalized, oneOverGamma) - 0.099;
+        table[i] = MIN((xelval)(v * newMaxval + 0.5), newMaxval);  
+            /* denormalize, round, and clip */
+    }
+}
+
+
+
+static void
+buildBt709GammaInverse(xelval       table[],
+                       xelval const maxval,
+                       xelval const newMaxval,
+                       double const gamma) {
+/*----------------------------------------------------------------------------
+   Build a gamma table of size maxval+1 for the Inverse of the ITU
+   Rec. BT.709 gamma transfer function.
+
+   'gamma' must be 1/0.45 for true Rec. 709.
+-----------------------------------------------------------------------------*/
+    double const oneOverGamma = 1.0 / gamma;
+    xelval i;
+
+    /* This transfer function is linear for sample values 0
+       .. maxval*.018 and an exponential for larger sample values.
+       The exponential is slightly stretched and translated, though,
+       unlike the popular pure exponential gamma transfer function.
+    */
+
+    xelval const linearCutoff = (xelval) (maxval * 0.018 + 0.5);
+    double const linearCompression = 
+        0.018 / (1.099 * pow(0.018, oneOverGamma) - 0.099);
+    double const maxvalScaler = (double)newMaxval/maxval;
+
+    for (i = 0; i <= linearCutoff / linearCompression; ++i) 
+        table[i] = i * linearCompression * maxvalScaler + 0.5;
+
+    for (; i <= maxval; ++i) {
+        double const normalized = ((double) i) / maxval;
+            /* Xel sample value normalized to 0..1 */
+        double const v = pow((normalized + 0.099) / 1.099, gamma);
+        table[i] = MIN((xelval)(v * newMaxval + 0.5), newMaxval);  
+            /* denormalize, round, and clip */
+    }
+}
+
+
+
+static void
+buildSrgbGamma(xelval       table[],
+               xelval const maxval,
+               xelval const newMaxval,
+               double const gamma) {
+/*----------------------------------------------------------------------------
+   Build a gamma table of size maxval+1 for the IEC SRGB gamma
+   transfer function (Standard IEC 61966-2-1).
+
+   'gamma' must be 2.4 for true SRGB
+-----------------------------------------------------------------------------*/
+    double const oneOverGamma = 1.0 / gamma;
+    xelval i;
+
+    /* This transfer function is linear for sample values 0
+       .. maxval*.040405 and an exponential for larger sample values.
+       The exponential is slightly stretched and translated, though,
+       unlike the popular pure exponential gamma transfer function.
+    */
+    xelval const linearCutoff = (xelval) maxval * 0.0031308 + 0.5;
+    double const linearExpansion = 
+        (1.055 * pow(0.0031308, oneOverGamma) - 0.055) / 0.0031308;
+    double const maxvalScaler = (double)newMaxval/maxval;
+
+    for (i = 0; i <= linearCutoff; ++i) 
+        table[i] = i * linearExpansion * maxvalScaler + 0.5;
+    for (; i <= maxval; ++i) {
+        double const normalized = ((double) i) / maxval;
+            /* Xel sample value normalized to 0..1 */
+        double const v = 1.055 * pow(normalized, oneOverGamma) - 0.055;
+        table[i] = MIN((xelval)(v * newMaxval + 0.5), newMaxval);  
+            /* denormalize, round, and clip */
+    }
+}
+
+
+
+static void
+buildSrgbGammaInverse(xelval       table[],
+                      xelval const maxval,
+                      xelval const newMaxval,
+                      double const gamma) {
+/*----------------------------------------------------------------------------
+   Build a gamma table of size maxval+1 for the Inverse of the IEC SRGB gamma
+   transfer function (Standard IEC 61966-2-1).
+
+   'gamma' must be 2.4 for true SRGB
+-----------------------------------------------------------------------------*/
+    double const oneOverGamma = 1.0 / gamma;
+    xelval i;
+
+    /* This transfer function is linear for sample values 0
+       .. maxval*.040405 and an exponential for larger sample values.
+       The exponential is slightly stretched and translated, though,
+       unlike the popular pure exponential gamma transfer function.
+    */
+    xelval const linearCutoff = (xelval) maxval * 0.0031308 + 0.5;
+    double const linearCompression = 
+        0.0031308 / (1.055 * pow(0.0031308, oneOverGamma) - 0.055);
+    double const maxvalScaler = (double)newMaxval/maxval;
+
+    for (i = 0; i <= linearCutoff / linearCompression; ++i) 
+        table[i] = i * linearCompression * maxvalScaler + 0.5;
+    for (; i <= maxval; ++i) {
+        double const normalized = ((double) i) / maxval;
+            /* Xel sample value normalized to 0..1 */
+        double const v = pow((normalized + 0.055) / 1.055, gamma);
+        table[i] = MIN((xelval)(v * newMaxval + 0.5), newMaxval);  
+            /* denormalize, round, and clip */
+    }
+}
+
+
+
+static void
+buildBt709ToSrgbGamma(xelval       table[],
+                      xelval const maxval,
+                      xelval const newMaxval,
+                      double const gammaSrgb) {
+/*----------------------------------------------------------------------------
+   Build a gamma table of size maxval+1 for the combination of the
+   inverse of ITU Rec BT.709 and the forward SRGB gamma transfer
+   functions.  I.e. this converts from Rec 709 to SRGB.
+
+   'gammaSrgb' must be 2.4 for true SRGB.
+-----------------------------------------------------------------------------*/
+    double const oneOverGamma709  = 0.45;
+    double const gamma709         = 1.0 / oneOverGamma709;
+    double const oneOverGammaSrgb = 1.0 / gammaSrgb;
+    double const normalizer       = 1.0 / maxval;
+
+    /* This transfer function is linear for sample values 0
+       .. maxval*.018 and an exponential for larger sample values.
+       The exponential is slightly stretched and translated, though,
+       unlike the popular pure exponential gamma transfer function.
+    */
+
+    xelval const linearCutoff709 = (xelval) (maxval * 0.018 + 0.5);
+    double const linearCompression709 = 
+        0.018 / (1.099 * pow(0.018, oneOverGamma709) - 0.099);
+
+    double const linearCutoffSrgb = 0.0031308;
+    double const linearExpansionSrgb = 
+        (1.055 * pow(0.0031308, oneOverGammaSrgb) - 0.055) / 0.0031308;
+
+    xelval i;
+
+    for (i = 0; i <= maxval; ++i) {
+        double const normalized = i * normalizer;
+            /* Xel sample value normalized to 0..1 */
+        double radiance;
+        double srgb;
+
+        if (i < linearCutoff709 / linearCompression709)
+            radiance = normalized * linearCompression709;
+        else
+            radiance = pow((normalized + 0.099) / 1.099, gamma709);
+
+        if (radiance < linearCutoffSrgb)
+            srgb = radiance * linearExpansionSrgb;
+        else
+            srgb = 1.055 * pow(normalized, oneOverGammaSrgb) - 0.055;
+
+        table[i] = srgb * newMaxval + 0.5;
+    }
+}
+
+
+
+static void
+buildSrgbToBt709Gamma(xelval       table[],
+                      xelval const maxval,
+                      xelval const newMaxval,
+                      double const gamma709) {
+/*----------------------------------------------------------------------------
+   Build a gamma table of size maxval+1 for the combination of the
+   inverse of SRGB and the forward ITU Rec BT.709 gamma transfer
+   functions.  I.e. this converts from SRGB to Rec 709.
+
+   'gamma709' must be 1/0.45 for true Rec. 709.
+-----------------------------------------------------------------------------*/
+    double const oneOverGamma709  = 1.0 / gamma709;
+    double const gammaSrgb        = 2.4;
+    double const oneOverGammaSrgb = 1.0 / gammaSrgb;
+    double const normalizer       = 1.0 / maxval;
+
+    /* This transfer function is linear for sample values 0
+       .. maxval*.040405 and an exponential for larger sample values.
+       The exponential is slightly stretched and translated, though,
+       unlike the popular pure exponential gamma transfer function.
+    */
+    xelval const linearCutoffSrgb = (xelval) maxval * 0.0031308 + 0.5;
+    double const linearCompressionSrgb = 
+        0.0031308 / (1.055 * pow(0.0031308, oneOverGammaSrgb) - 0.055);
+
+    xelval const linearCutoff709 = (xelval) (maxval * 0.018 + 0.5);
+    double const linearExpansion709 = 
+        (1.099 * pow(0.018, oneOverGamma709) - 0.099) / 0.018;
+
+    xelval i;
+
+    for (i = 0; i <= maxval; ++i) {
+        double const normalized = i * normalizer;
+            /* Xel sample value normalized to 0..1 */
+        double radiance;
+        double bt709;
+
+        if (i < linearCutoffSrgb / linearCompressionSrgb)
+            radiance = normalized * linearCompressionSrgb;
+        else
+            radiance = pow((normalized + 0.099) / 1.099, gammaSrgb);
+
+        if (radiance < linearCutoff709)
+            bt709 = radiance * linearExpansion709;
+        else
+            bt709 = 1.055 * pow(normalized, oneOverGamma709) - 0.055;
+
+        table[i] = bt709 * newMaxval + 0.5;
+    }
+}
+
+
+
+static void
+createGammaTables(enum transferFunction const transferFunction,
+                  xelval                const maxval,
+                  xelval                const newMaxval,
+                  double                const rgamma, 
+                  double                const ggamma, 
+                  double                const bgamma,
+                  xelval **             const rtableP,
+                  xelval **             const gtableP,
+                  xelval **             const btableP) {
+
+    /* Allocate space for the tables. */
+    MALLOCARRAY(*rtableP, maxval+1);
+    MALLOCARRAY(*gtableP, maxval+1);
+    MALLOCARRAY(*btableP, maxval+1);
+    if (*rtableP == NULL || *gtableP == NULL || *btableP == NULL)
+        pm_error("Can't get memory to make gamma transfer tables");
+
+    /* Build the gamma corection tables. */
+    switch (transferFunction) {
+    case XF_BT709RAMP: {
+        buildBt709Gamma(*rtableP, maxval, newMaxval, rgamma);
+        buildBt709Gamma(*gtableP, maxval, newMaxval, ggamma);
+        buildBt709Gamma(*btableP, maxval, newMaxval, bgamma);
+    } break;
+
+    case XF_BT709RAMP_INVERSE: {
+        buildBt709GammaInverse(*rtableP, maxval, newMaxval, rgamma);
+        buildBt709GammaInverse(*gtableP, maxval, newMaxval, ggamma);
+        buildBt709GammaInverse(*btableP, maxval, newMaxval, bgamma);
+    } break;
+
+    case XF_SRGBRAMP: {
+        buildSrgbGamma(*rtableP, maxval, newMaxval, rgamma);
+        buildSrgbGamma(*gtableP, maxval, newMaxval, ggamma);
+        buildSrgbGamma(*btableP, maxval, newMaxval, bgamma);
+    } break;
+
+    case XF_SRGBRAMP_INVERSE: {
+        buildSrgbGammaInverse(*rtableP, maxval, newMaxval, rgamma);
+        buildSrgbGammaInverse(*gtableP, maxval, newMaxval, ggamma);
+        buildSrgbGammaInverse(*btableP, maxval, newMaxval, bgamma);
+    } break;
+
+    case XF_EXP: {
+        buildPowGamma(*rtableP, maxval, newMaxval, rgamma);
+        buildPowGamma(*gtableP, maxval, newMaxval, ggamma);
+        buildPowGamma(*btableP, maxval, newMaxval, bgamma);
+    } break;
+
+    case XF_EXP_INVERSE: {
+        buildPowGamma(*rtableP, maxval, newMaxval, 1.0/rgamma);
+        buildPowGamma(*gtableP, maxval, newMaxval, 1.0/ggamma);
+        buildPowGamma(*btableP, maxval, newMaxval, 1.0/bgamma);
+    } break;
+
+    case XF_BT709_TO_SRGB: {
+        buildBt709ToSrgbGamma(*rtableP, maxval, newMaxval, rgamma);
+        buildBt709ToSrgbGamma(*gtableP, maxval, newMaxval, ggamma);
+        buildBt709ToSrgbGamma(*btableP, maxval, newMaxval, bgamma);
+    } break;
+
+    case XF_SRGB_TO_BT709: {
+        buildSrgbToBt709Gamma(*rtableP, maxval, newMaxval, rgamma);
+        buildSrgbToBt709Gamma(*gtableP, maxval, newMaxval, ggamma);
+        buildSrgbToBt709Gamma(*btableP, maxval, newMaxval, bgamma);
+    } break;
+    }
+}
+
+
+
+static void
+convertRaster(FILE *   const ifP,
+              FILE *   const ofP,
+              int      const cols,
+              int      const rows,
+              xelval   const maxval,
+              int      const format,
+              xelval   const outputMaxval,
+              int      const outputFormat,
+              xelval * const rtable,
+              xelval * const gtable,
+              xelval * const btable) {
+
+    xel * xelrow;
+    unsigned int row;
+
+    xelrow = pnm_allocrow(cols);
+
+    for (row = 0; row < rows; ++row) {
+        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+
+        pnm_promoteformatrow(xelrow, cols, maxval, format, 
+                             maxval, outputFormat);
+
+        switch (PNM_FORMAT_TYPE(outputFormat)) {
+        case PPM_TYPE: {
+            unsigned int col;
+            for (col = 0; col < cols; ++col) {
+                xelval const r = PPM_GETR(xelrow[col]);
+                xelval const g = PPM_GETG(xelrow[col]);
+                xelval const b = PPM_GETB(xelrow[col]);
+                PPM_ASSIGN(xelrow[col], rtable[r], gtable[g], btable[b]);
+            }
+        } break;
+
+        case PGM_TYPE: {
+            unsigned int col;
+            for (col = 0; col < cols; ++col) {
+                xelval const xel = PNM_GET1(xelrow[col]);
+                PNM_ASSIGN1(xelrow[col], gtable[xel]);
+            }
+        } break;
+        default:
+            pm_error("Internal error.  Impossible format type");
+        }
+        pnm_writepnmrow(ofP, xelrow, cols, outputMaxval, outputFormat, 0);
+    }
+    pnm_freerow(xelrow);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    xelval maxval;
+    int rows, cols, format;
+    xelval outputMaxval;
+    int outputFormat;
+    xelval * rtable;
+    xelval * gtable;
+    xelval * btable;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.filespec);
+
+    pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
+
+    if (PNM_FORMAT_TYPE(format) == PPM_TYPE)
+        outputFormat = PPM_TYPE;
+    else if (cmdline.rgamma != cmdline.ggamma 
+             || cmdline.ggamma != cmdline.bgamma) 
+        outputFormat = PPM_TYPE;
+    else 
+        outputFormat = PGM_TYPE;
+
+    if (PNM_FORMAT_TYPE(format) != outputFormat) {
+        if (outputFormat == PPM_TYPE)
+            pm_message("Promoting to PPM");
+        if (outputFormat == PGM_TYPE)
+            pm_message("Promoting to PGM");
+    }
+
+    outputMaxval = cmdline.makeNewMaxval ? cmdline.maxval : maxval;
+
+    createGammaTables(cmdline.transferFunction, maxval,
+                      outputMaxval,
+                      cmdline.rgamma, cmdline.ggamma, cmdline.bgamma,
+                      &rtable, &gtable, &btable);
+
+    pnm_writepnminit(stdout, cols, rows, outputMaxval, outputFormat, 0);
+
+    convertRaster(ifP, stdout, cols, rows, maxval, format,
+                  outputMaxval, outputFormat,
+                  rtable, gtable, btable);
+
+    pm_close(ifP);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/editor/pnmhisteq.c b/editor/pnmhisteq.c
new file mode 100644
index 00000000..2c6893bd
--- /dev/null
+++ b/editor/pnmhisteq.c
@@ -0,0 +1,416 @@
+/*
+                 pnmhisteq.c
+
+           Equalize histogram for a PNM image
+
+  By Bryan Henderson 2005.09.10, based on ideas from the program of
+  the same name by John Walker (kelvin@fourmilab.ch) -- March MVM.
+  WWW home page: http://www.fourmilab.ch/ in 1995.
+
+  This program is contributed to the public domain by its author.
+*/
+
+#include <string.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;
+    unsigned int gray;
+    const char * wmap;
+    const char * rmap;
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int rmapSpec, wmapSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "rmap",     OPT_STRING, &cmdlineP->rmap, 
+            &rmapSpec,          0);
+    OPTENT3(0, "wmap",     OPT_STRING, &cmdlineP->wmap, 
+            &wmapSpec,          0);
+    OPTENT3(0, "gray",     OPT_FLAG,   NULL,
+            &cmdlineP->gray,    0);
+    OPTENT3(0, "verbose",  OPT_FLAG,   NULL,
+            &cmdlineP->verbose, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+
+    if (!wmapSpec)
+        cmdlineP->wmap = NULL;
+    if (!rmapSpec)
+        cmdlineP->rmap = NULL;
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else {
+        cmdlineP->inputFileName = argv[1];
+        if (argc-1 > 1)
+            pm_error("Too many arguments (%d).  The only argument is the "
+                     "input file name.", argc-1);
+    }
+}
+
+
+
+static void
+computeLuminosityHistogram(xel * const *   const xels,
+                           unsigned int    const rows,
+                           unsigned int    const cols,
+                           xelval          const maxval,
+                           int             const format,
+                           bool            const monoOnly,
+                           unsigned int ** const lumahistP,
+                           xelval *        const lminP,
+                           xelval *        const lmaxP,
+                           unsigned int *  const pixelCountP) {
+/*----------------------------------------------------------------------------
+  Scan the image and build the luminosity histogram.  If the input is
+  a PPM, we calculate the luminosity of each pixel from its RGB
+  components.
+-----------------------------------------------------------------------------*/
+    xelval lmin, lmax;
+    unsigned int pixelCount;
+    unsigned int * lumahist;
+
+    MALLOCARRAY(lumahist, maxval + 1);
+    if (lumahist == NULL)
+        pm_error("Out of storage allocating array for %u histogram elements",
+                 maxval + 1);
+
+    {
+        unsigned int i;
+        /* Initialize histogram to zeroes everywhere */
+        for (i = 0; i <= maxval; ++i)
+            lumahist[i] = 0;
+    }
+    lmin = maxval;  /* initial value */
+    lmax = 0;       /* initial value */
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PGM_TYPE:
+    case PBM_TYPE: {
+        /* Compute intensity histogram */
+
+        unsigned int row;
+
+        pixelCount = rows * cols;
+        for (row = 0; row < rows; ++row) {
+            unsigned int col;
+            for (col = 0; col < cols; ++col) {
+                xelval const l = PNM_GET1(xels[row][col]);
+                lmin = MIN(lmin, l);
+                lmax = MAX(lmax, l);
+                ++lumahist[l];
+            }
+        }
+    }
+    break;
+    case PPM_TYPE: {
+        unsigned int row;
+
+        for (row = 0, pixelCount = 0; row < rows; ++row) {
+            unsigned int col;
+            for (col = 0; col < cols; ++col) {
+                xel const thisXel = xels[row][col];
+                if (!monoOnly || PPM_ISGRAY(thisXel)) {
+                    xelval const l = PPM_LUMIN(thisXel);
+
+                    lmin = MIN(lmin, l);
+                    lmax = MAX(lmax, l);
+
+                    ++lumahist[l];
+                    ++pixelCount;
+                }
+            }
+        }
+    }
+    break;
+    default:
+        pm_error("invalid input format format");
+    }
+
+    *lumahistP = lumahist;
+    *pixelCountP = pixelCount;
+    *lminP = lmin;
+    *lmaxP = lmax;
+}
+
+
+
+static void
+findMaxLuma(const xelval * const lumahist,
+            xelval         const maxval,
+            xelval *       const maxLumaP) {
+
+    xelval maxluma;
+    unsigned int i;
+
+    for (i = 0, maxluma = 0; i <= maxval; ++i)
+        if (lumahist[i] > 0)
+            maxluma = i;
+
+    *maxLumaP = maxluma;
+}
+
+
+
+static void
+readMapFile(const char * const rmapFileName,
+            xelval       const maxval,
+            gray *       const lumamap) {
+
+    int rmcols, rmrows; 
+    gray rmmaxv;
+    int rmformat;
+    FILE * rmapfP;
+        
+    rmapfP = pm_openr(rmapFileName);
+    pgm_readpgminit(rmapfP, &rmcols, &rmrows, &rmmaxv, &rmformat);
+    
+    if (rmmaxv != maxval)
+        pm_error("maxval in map file (%u) different from input (%u)",
+                 rmmaxv, maxval);
+    
+    if (rmrows != 1)
+        pm_error("Map must have 1 row.  Yours has %u", rmrows);
+    
+    if (rmcols != maxval + 1)
+        pm_error("Map must have maxval + 1 (%u) columns.  Yours has %u",
+                 maxval + 1, rmcols);
+    
+    pgm_readpgmrow(rmapfP, lumamap, maxval+1, rmmaxv, rmformat);
+    
+    pm_close(rmapfP);
+}
+
+
+
+static void
+computeMap(const unsigned int * const lumahist,
+           xelval               const maxval,
+           unsigned int         const pixelCount,
+           gray *               const lumamap) {
+
+    /* Calculate initial histogram equalization curve. */
+    
+    unsigned int i;
+    unsigned int pixsum;
+    xelval maxluma;
+
+    for (i = 0, pixsum = 0; i <= maxval; ++i) {
+            
+        /* With 16 bit grays, the following calculation can
+           overflow a 32 bit long.  So, we do it in floating
+           point.
+        */
+
+        lumamap[i] = ROUNDU((((double) pixsum * maxval)) / pixelCount);
+
+        pixsum += lumahist[i];
+    }
+
+    findMaxLuma(lumahist, maxval, &maxluma);
+
+    {
+        double const lscale = (double)maxval /
+            ((lumahist[maxluma] > 0) ?
+             (double) lumamap[maxluma] : (double) maxval);
+
+        unsigned int i;
+
+        /* Normalize so that the brightest pixels are set to maxval. */
+
+        for (i = 0; i <= maxval; ++i)
+            lumamap[i] = MIN(maxval, ROUNDU(lumamap[i] * lscale));
+    }
+}
+
+
+
+static void
+getMapping(const char *         const rmapFileName,
+           const unsigned int * const lumahist,
+           xelval               const maxval,
+           unsigned int         const pixelCount,
+           gray **              const lumamapP) {
+/*----------------------------------------------------------------------------
+  Calculate the luminosity mapping table which gives the
+  histogram-equalized luminosity for each original luminosity.
+-----------------------------------------------------------------------------*/
+    gray * lumamap;
+
+    lumamap = pgm_allocrow(maxval+1);
+
+    if (rmapFileName)
+        readMapFile(rmapFileName, maxval, lumamap);
+    else
+        computeMap(lumahist, maxval, pixelCount, lumamap);
+
+    *lumamapP = lumamap;
+}
+
+
+
+static void
+reportMap(const unsigned int * const lumahist,
+          xelval               const maxval,
+          const gray *         const lumamap) {
+
+    unsigned int i;
+
+    fprintf(stderr, "  Luminosity map    Number of\n");
+    fprintf(stderr, " Original    New     Pixels  \n");
+
+    for (i = 0; i <= maxval; ++i) {
+        if (lumahist[i] > 0) {
+            fprintf(stderr,"%6d -> %6d  %8u\n", i,
+                    lumamap[i], lumahist[i]);
+        }
+    }
+}
+
+
+
+static void
+remap(xel **       const xels,
+      unsigned int const cols,
+      unsigned int const rows,
+      xelval       const maxval,
+      int          const format,
+      bool         const monoOnly,
+      const gray * const lumamap) {
+/*----------------------------------------------------------------------------
+   Update the array 'xels' to have the new intensities.
+-----------------------------------------------------------------------------*/
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PPM_TYPE: {
+        unsigned int row;
+        for (row = 0; row < rows; ++row) {
+            unsigned int col;
+            for (col = 0; col < cols; ++col) {
+                xel const thisXel = xels[row][col];
+                if (monoOnly && PPM_ISGRAY(thisXel)) {
+                    /* Leave this pixel alone */
+                } else {
+                    struct hsv hsv;
+                    xelval iv;
+
+                    hsv = ppm_hsv_from_color(thisXel, maxval);
+                    iv = MIN(maxval, ROUNDU(hsv.v * maxval));
+                    
+                    hsv.v = MIN(1.0, 
+                                ((double) lumamap[iv]) / ((double) maxval));
+
+                    xels[row][col] = ppm_color_from_hsv(hsv, maxval);
+                }
+            }
+        }
+    }
+    break;
+
+    case PBM_TYPE:
+    case PGM_TYPE: {
+        unsigned int row;
+        for (row = 0; row < rows; ++row) {
+            unsigned int col;
+            for (col = 0; col < cols; ++col)
+                PNM_ASSIGN1(xels[row][col],
+                            lumamap[PNM_GET1(xels[row][col])]);
+        }
+    }
+    break;
+    }
+}
+
+
+
+static void
+writeMap(const char * const wmapFileName,
+         const gray * const lumamap,
+         xelval       const maxval) {
+
+    FILE * const wmapfP = pm_openw(wmapFileName);
+
+    pgm_writepgminit(wmapfP, maxval+1, 1, maxval, 0);
+
+    pgm_writepgmrow(wmapfP, lumamap, maxval+1, maxval, 0);
+
+    pm_close(wmapfP);
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    xelval lmin, lmax;
+    gray * lumamap;           /* Luminosity map */
+    unsigned int * lumahist;  /* Histogram of luminosity values */
+    int rows, cols;           /* Rows, columns of input image */
+    xelval maxval;            /* Maxval of input image */
+    int format;               /* Format indicator (PBM/PGM/PPM) */
+    xel ** xels;              /* Pixel array */
+    unsigned int pixelCount;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    xels = pnm_readpnm(ifP, &cols, &rows, &maxval, &format);
+
+    pm_close(ifP);
+
+    computeLuminosityHistogram(xels, rows, cols, maxval, format,
+                               cmdline.gray, &lumahist, &lmin, &lmax,
+                               &pixelCount);
+
+    getMapping(cmdline.rmap, lumahist, maxval, pixelCount, &lumamap);
+
+    if (cmdline.verbose)
+        reportMap(lumahist, maxval, lumamap);
+
+    remap(xels, cols, rows, maxval, format, !!cmdline.gray, lumamap);
+
+    pnm_writepnm(stdout, xels, cols, rows, maxval, format, 0);
+
+    if (cmdline.wmap)
+        writeMap(cmdline.wmap, lumamap, maxval);
+
+    pgm_freerow(lumamap);
+
+    return 0;
+}
diff --git a/editor/pnmindex.c b/editor/pnmindex.c
new file mode 100644
index 00000000..cb7d3702
--- /dev/null
+++ b/editor/pnmindex.c
@@ -0,0 +1,638 @@
+/*============================================================================
+                              pnmindex   
+==============================================================================
+
+  build a visual index of a bunch of PNM images
+
+  This used to be a C shell program, and then a BASH program.  Neither
+  were portable enough, and the program is too complex for either of
+  those languages anyway, so now it's in C.
+
+  By Bryan Henderson 2005.04.24.
+
+  Contributed to the public domain by its author.
+
+============================================================================*/
+
+#define _BSD_SOURCE   /* Make sure strdup is in string.h */
+
+#include <assert.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    unsigned int  inputFileCount;
+    const char ** inputFileName;
+    unsigned int  size;
+    unsigned int  across;
+    unsigned int  colors;
+    unsigned int  black;
+    unsigned int  noquant;
+    const char *  title;
+    unsigned int  verbose;
+};
+
+static bool verbose;
+
+
+
+static void PM_GNU_PRINTF_ATTR(1,2)
+systemf(const char * const fmt,
+        ...) {
+
+    va_list varargs;
+    
+    size_t dryRunLen;
+    
+    va_start(varargs, fmt);
+    
+    vsnprintfN(NULL, 0, fmt, varargs, &dryRunLen);
+
+    va_end(varargs);
+
+    if (dryRunLen + 1 < dryRunLen)
+        /* arithmetic overflow */
+        pm_error("Command way too long");
+    else {
+        size_t const allocSize = dryRunLen + 1;
+        char * shellCommand;
+        shellCommand = malloc(allocSize);
+        if (shellCommand == NULL)
+            pm_error("Can't get storage for %u-character command",
+                     allocSize);
+        else {
+            va_list varargs;
+            size_t realLen;
+            int rc;
+
+            va_start(varargs, fmt);
+
+            vsnprintfN(shellCommand, allocSize, fmt, varargs, &realLen);
+                
+            assert(realLen == dryRunLen);
+            va_end(varargs);
+
+            if (verbose)
+                pm_message("shell cmd: %s", shellCommand);
+
+            rc = system(shellCommand);
+            if (rc != 0)
+                pm_error("shell command '%s' failed.  rc %d",
+                         shellCommand, rc);
+            
+            strfree(shellCommand);
+        }
+    }
+}
+        
+
+
+static void
+parseCommandLine(int argc, char ** argv, 
+                 struct cmdlineInfo * const cmdlineP) {
+
+    unsigned int option_def_index;
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int quant;
+    unsigned int sizeSpec, colorsSpec, acrossSpec, titleSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "black",       OPT_FLAG,   NULL,                  
+            &cmdlineP->black,         0);
+    OPTENT3(0, "noquant",     OPT_FLAG,   NULL,                  
+            &cmdlineP->noquant,       0);
+    OPTENT3(0, "quant",       OPT_FLAG,   NULL,                  
+            &quant,                   0);
+    OPTENT3(0, "verbose",     OPT_FLAG,   NULL,                  
+            &cmdlineP->verbose,       0);
+    OPTENT3(0, "size",        OPT_UINT,   &cmdlineP->size,
+            &sizeSpec,                0);
+    OPTENT3(0, "colors",      OPT_UINT,   &cmdlineP->colors,
+            &colorsSpec,              0);
+    OPTENT3(0, "across",      OPT_UINT,   &cmdlineP->across,
+            &acrossSpec,              0);
+    OPTENT3(0, "title",       OPT_STRING, &cmdlineP->title,
+            &titleSpec,               0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE; 
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (quant && cmdlineP->noquant)
+        pm_error("You can't specify both -quant and -noquat");
+
+    if (!colorsSpec)
+        cmdlineP->colors = 256;
+    
+    if (!sizeSpec)
+        cmdlineP->size = 100;
+
+    if (!acrossSpec)
+        cmdlineP->across = 6;
+
+    if (!titleSpec)
+        cmdlineP->title = NULL;
+
+    if (colorsSpec && cmdlineP->noquant)
+        pm_error("-colors doesn't make any sense with -noquant");
+
+    if (argc-1 < 1)
+        pm_error("You must name at least one file that contains an image "
+                 "to go into the index");
+
+    cmdlineP->inputFileCount = argc-1;
+
+    MALLOCARRAY_NOFAIL(cmdlineP->inputFileName, cmdlineP->inputFileCount);
+
+    {
+        unsigned int i;
+        for (i = 0; i < cmdlineP->inputFileCount; ++i) {
+            cmdlineP->inputFileName[i] = strdup(argv[i+1]);
+            if (cmdlineP->inputFileName[i] == NULL)
+                pm_error("Unable to allocate memory for a file name");
+        }
+    }
+}
+
+
+
+static void
+freeCmdline(struct cmdlineInfo const cmdline) {
+
+    unsigned int i;
+
+    for (i = 0; i < cmdline.inputFileCount; ++i)
+        strfree(cmdline.inputFileName[i]);
+
+    free(cmdline.inputFileName);
+
+}
+
+
+
+static void
+makeTempDir(const char ** const tempDirP) {
+
+    const char * const tmpdir = getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp";
+
+    const char * mytmpdir;
+    int rc;
+
+    asprintfN(&mytmpdir, "%s/pnmindex_%d", tmpdir, getpid());
+
+    rc = mkdir(mytmpdir, 0700);
+    if (rc != 0)
+        pm_error("Unable to create temporary file directory '%s'.  mkdir() "
+                 "fails with errno %d (%s)",
+                 mytmpdir, errno, strerror(errno));
+
+    *tempDirP = mytmpdir;
+}
+
+
+
+static void
+removeTempDir(const char * const tempDir) {
+
+    int rc;
+
+    rc = rmdir(tempDir);
+    if (rc != 0)
+        pm_error("Failed to remove temporary file directory '%s'.  "
+                 "rmdir() fails with errno %d (%s)",
+                 tempDir, errno, strerror(errno));
+}
+
+
+static const char *
+rowFileName(const char * const dirName,
+            unsigned int const row) {
+
+    const char * fileName;
+    
+    asprintfN(&fileName, "%s/pi.%u", dirName, row);
+    
+    return fileName;
+}
+
+
+
+static void
+makeTitle(const char * const title,
+          unsigned int const rowNumber,
+          bool         const blackBackground,
+          const char * const tempDir) {
+
+    const char * const invertStage = blackBackground ? "| pnminvert " : "";
+
+    const char * fileName;
+
+    fileName = rowFileName(tempDir, rowNumber);
+
+    /* This quoting is not adequate.  We really should do this without
+       a shell at all.
+    */
+    systemf("pbmtext \"%s\" "
+            "%s"
+            "> %s", 
+            title, invertStage, fileName);
+
+    strfree(fileName);
+}
+
+
+
+static void
+copyImage(const char * const inputFileName,
+          const char * const outputFileName) {
+
+    systemf("cat %s > %s", inputFileName, outputFileName);
+} 
+
+
+
+static void
+copyScaleQuantImage(const char * const inputFileName,
+                    const char * const outputFileName,
+                    int          const format,
+                    unsigned int const size,
+                    unsigned int const quant,
+                    unsigned int const colors) {
+
+    const char * scaleCommand;
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PBM_TYPE:
+        asprintfN(&scaleCommand, 
+                  "pamscale -quiet -xysize %u %u %s "
+                  "| pgmtopbm > %s",
+                  size, size, inputFileName, outputFileName);
+        break;
+        
+    case PGM_TYPE:
+        asprintfN(&scaleCommand, 
+                  "pamscale -quiet -xysize %u %u %s >%s",
+                  size, size, inputFileName, outputFileName);
+        break;
+        
+    case PPM_TYPE:
+        if (quant)
+            asprintfN(&scaleCommand, 
+                      "pamscale -quiet -xysize %u %u %s "
+                      "| pnmquant -quiet %u > %s",
+                      size, size, inputFileName, colors, outputFileName);
+        else
+            asprintfN(&scaleCommand, 
+                      "pamscale -quiet -xysize %u %u %s >%s",
+                      size, size, inputFileName, outputFileName);
+        break;
+    default:
+        pm_error("Unrecognized Netpbm format: %d", format);
+    }
+
+    systemf("%s", scaleCommand);
+
+    strfree(scaleCommand);
+}
+
+
+
+static int
+formatTypeMax(int const typeA,
+              int const typeB) {
+
+    if (typeA == PPM_TYPE || typeB == PPM_TYPE)
+        return PPM_TYPE; 
+    else if (typeA == PGM_TYPE || typeB == PGM_TYPE)
+        return PGM_TYPE;
+    else
+        return PBM_TYPE;
+}
+
+
+
+static const char *
+thumbnailFileName(const char * const dirName,
+                  unsigned int const row,
+                  unsigned int const col) {
+
+    const char * fileName;
+    
+    asprintfN(&fileName, "%s/pi.%u.%u", dirName, row, col);
+    
+    return fileName;
+}
+
+
+
+static const char *
+thumbnailFileList(const char * const dirName,
+                  unsigned int const row,
+                  unsigned int const cols) {
+
+    unsigned int const maxListSize = 4096;
+
+    char * list;
+    unsigned int col;
+
+    list = malloc(maxListSize);
+    if (list == NULL)
+        pm_error("Unable to allocate %u bytes for file list", maxListSize);
+
+    list[0] = '\0';
+    
+    for (col = 0; col < cols; ++col) {
+        const char * const fileName = thumbnailFileName(dirName, row, col);
+
+        if (strlen(list) + strlen(fileName) + 1 > maxListSize - 1)
+            pm_error("File name list too long for this program to handle.");
+        else {
+            strcat(list, " ");
+            strcat(list, fileName);
+        }
+        strfree(fileName);
+    }
+
+    return list;
+}
+
+
+
+static void
+makeImageFile(const char * const thumbnailFileName,
+              const char * const inputFileName,
+              bool         const blackBackground,
+              const char * const outputFileName) {
+
+    const char * const blackWhiteOpt = blackBackground ? "-black" : "-white";
+    const char * const invertStage = blackBackground ? "| pnminvert " : "";
+
+    systemf("pbmtext \"%s\" "
+            "%s"
+            "| pnmcat %s -topbottom %s - "
+            "> %s",
+            inputFileName, invertStage, blackWhiteOpt, 
+            thumbnailFileName, outputFileName);
+}    
+
+
+
+static void
+makeThumbnail(const char *  const inputFileName,
+              unsigned int  const size,
+              bool          const black,
+              bool          const quant,
+              unsigned int  const colors,
+              const char *  const tempDir,
+              unsigned int  const row,
+              unsigned int  const col,
+              int *         const formatP) {
+
+    FILE * ifP;
+    int imageCols, imageRows, format;
+    xelval maxval;
+    const char * tmpfile;
+    const char * fileName;
+        
+    ifP = pm_openr(inputFileName);
+    pnm_readpnminit(ifP, &imageCols, &imageRows, &maxval, &format);
+    pm_close(ifP);
+    
+    asprintfN(&tmpfile, "%s/pi.tmp", tempDir);
+
+    if (imageCols < size && imageRows < size)
+        copyImage(inputFileName, tmpfile);
+    else
+        copyScaleQuantImage(inputFileName, tmpfile, format, 
+                            size, quant, colors);
+
+    fileName = thumbnailFileName(tempDir, row, col);
+        
+    makeImageFile(tmpfile, inputFileName, black, fileName);
+
+    unlink(tmpfile);
+
+    strfree(fileName);
+    strfree(tmpfile);
+
+    *formatP = format;
+}
+        
+
+
+static void
+unlinkThumbnailFiles(const char * const dirName,
+                     unsigned int const row,
+                     unsigned int const cols) {
+
+    unsigned int col;
+    
+    for (col = 0; col < cols; ++col) {
+        const char * const fileName = thumbnailFileName(dirName, row, col);
+
+        unlink(fileName);
+
+        strfree(fileName);
+    }
+}
+
+
+
+static void
+unlinkRowFiles(const char * const dirName,
+               unsigned int const rows) {
+
+    unsigned int row;
+    
+    for (row = 0; row < rows; ++row) {
+        const char * const fileName = rowFileName(dirName, row);
+
+        unlink(fileName);
+
+        strfree(fileName);
+    }
+}
+
+
+
+static void
+combineIntoRowAndDelete(unsigned int const row,
+                        unsigned int const cols,
+                        int          const maxFormatType,
+                        bool         const blackBackground,
+                        bool         const quant,
+                        unsigned int const colors,
+                        const char * const tempDir) {
+
+    const char * const blackWhiteOpt = blackBackground ? "-black" : "-white";
+
+    const char * fileName;
+    const char * quantStage;
+    const char * fileList;
+    
+    fileName = rowFileName(tempDir, row);
+
+    unlink(fileName);
+
+    if (maxFormatType == PPM_TYPE && quant)
+        asprintfN(&quantStage, "| pnmquant -quiet %u ", colors);
+    else
+        quantStage = strdup("");
+
+    fileList = thumbnailFileList(tempDir, row, cols);
+
+    systemf("pnmcat %s -leftright -jbottom %s "
+            "%s"
+            ">%s",
+            blackWhiteOpt, fileList, quantStage, fileName);
+
+    strfree(fileList);
+    strfree(quantStage);
+    strfree(fileName);
+
+    unlinkThumbnailFiles(tempDir, row, cols);
+}
+
+
+
+static const char *
+rowFileList(const char * const dirName,
+            unsigned int const rows) {
+
+    unsigned int const maxListSize = 4096;
+
+    unsigned int row;
+    char * list;
+
+    list = malloc(maxListSize);
+    if (list == NULL)
+        pm_error("Unable to allocate %u bytes for file list", maxListSize);
+
+    list[0] = '\0';
+
+    for (row = 0; row < rows; ++row) {
+        const char * const fileName = rowFileName(dirName, row);
+
+        if (strlen(list) + strlen(fileName) + 1 > maxListSize - 1)
+            pm_error("File name list too long for this program to handle.");
+        
+        else {
+            strcat(list, " ");
+            strcat(list, fileName);
+        }
+        strfree(fileName);
+    }
+
+    return list;
+}
+
+
+
+static void
+writeRowsAndDelete(unsigned int const rows,
+                   int          const maxFormatType,
+                   bool         const blackBackground,
+                   bool         const quant,
+                   unsigned int const colors,
+                   const char * const tempDir) {
+
+    const char * const blackWhiteOpt = blackBackground ? "-black" : "-white";
+
+    const char * quantStage;
+    const char * fileList;
+    
+    if (maxFormatType == PPM_TYPE && quant)
+        asprintfN(&quantStage, "| pnmquant -quiet %u ", colors);
+    else
+        quantStage = strdup("");
+
+    fileList = rowFileList(tempDir, rows);
+
+    systemf("pnmcat %s -topbottom %s %s",
+            blackWhiteOpt, fileList, quantStage);
+
+    strfree(fileList);
+    strfree(quantStage);
+
+    unlinkRowFiles(tempDir, rows);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+    struct cmdlineInfo cmdline;
+    const char * tempDir;
+    int maxFormatType;
+    unsigned int colsInRow;
+    unsigned int rowsDone;
+    unsigned int i;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    verbose = cmdline.verbose;
+    
+    makeTempDir(&tempDir);
+
+    maxFormatType = PBM_TYPE;
+    colsInRow = 0;
+    rowsDone = 0;
+
+    if (cmdline.title)
+        makeTitle(cmdline.title, rowsDone++, cmdline.black, tempDir);
+
+    for (i = 0; i < cmdline.inputFileCount; ++i) {
+        const char * const inputFileName = cmdline.inputFileName[i];
+
+        int format;
+
+        makeThumbnail(inputFileName, cmdline.size, cmdline.black, 
+                      !cmdline.noquant, cmdline.colors, tempDir,
+                      rowsDone, colsInRow, &format);
+
+        maxFormatType = formatTypeMax(maxFormatType, PNM_FORMAT_TYPE(format));
+
+        ++colsInRow;
+        if (colsInRow >= cmdline.across || i == cmdline.inputFileCount-1) {
+            combineIntoRowAndDelete(
+                rowsDone, colsInRow, maxFormatType,
+                cmdline.black, !cmdline.noquant, cmdline.colors,
+                tempDir);
+            ++rowsDone;
+            colsInRow = 0;
+        }
+    }
+
+    writeRowsAndDelete(rowsDone, maxFormatType, cmdline.black,
+                       !cmdline.noquant, cmdline.colors, tempDir);
+
+    removeTempDir(tempDir);
+
+    freeCmdline(cmdline);
+
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/editor/pnmindex.csh b/editor/pnmindex.csh
new file mode 100755
index 00000000..c6f1e844
--- /dev/null
+++ b/editor/pnmindex.csh
@@ -0,0 +1,189 @@
+#!/bin/csh -f
+#
+# pnmindex - build a visual index of a bunch of anymaps
+#
+# Copyright (C) 1991 by Jef Poskanzer.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose and without fee is hereby granted, provided
+# that the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation.  This software is provided "as is" without express or
+# implied warranty.
+
+# -title and -quant added by John Heidemann 13-Sep-00.
+
+set size=100		# make the images about this big
+set across=6		# show this many images per row
+set colors=256		# quantize results to this many colors
+set back="-white"	# default background color
+set doquant=true	# quantize or not
+set title=""		# default title (none)
+
+while ( 1 )
+    switch ( "$1" )
+
+	case -s*:
+	if ( $#argv < 2 ) goto usage
+	set size="$2"
+	shift
+	shift
+	breaksw
+
+	case -a*:
+	if ( $#argv < 2 ) goto usage
+	set across="$2"
+	shift
+	shift
+	breaksw
+
+	case -t*:
+	if ( $#argv < 2 ) goto usage
+	set title="$2"
+	shift
+	shift
+	breaksw
+
+	case -c*:
+	set colors="$2"
+	shift
+	shift
+	breaksw
+
+	case -noq*:
+	set doquant=false
+	shift
+	breaksw
+
+	case -q*:
+	set doquant=true
+	shift
+	breaksw
+
+	case -b*:
+	set back="-black"
+	shift
+	breaksw
+
+	case -w*:
+	set back="-white"
+	shift
+	breaksw
+
+	case -*:
+	goto usage
+	breaksw
+
+	default:
+	break
+	breaksw
+
+    endsw
+end
+
+if ( $#argv == 0 ) then
+    goto usage
+endif
+
+set tmpfile=/tmp/pi.tmp.$$
+rm -f $tmpfile
+set maxformat=PBM
+
+set rowfiles=()
+set imagefiles=()
+@ row = 1
+@ col = 1
+
+if ( "$title" != "" ) then
+    set rowfile=/tmp/pi.${row}.$$
+    rm -f $rowfile
+    pbmtext "$title" > $rowfile
+    set rowfiles=( $rowfiles $rowfile )
+    @ row += 1
+endif
+
+foreach i ( $argv )
+
+    set description=`pnmfile $i`
+    if ( $description[4] <= $size && $description[6] <= $size ) then
+	cat $i > $tmpfile
+    else
+	switch ( $description[2] )
+	    case PBM:
+	    pnmscale -quiet -xysize $size $size $i | pgmtopbm > $tmpfile
+	    breaksw
+
+	    case PGM:
+	    pnmscale -quiet -xysize $size $size $i > $tmpfile
+	    if ( $maxformat == PBM ) then
+		set maxformat=PGM
+	    endif
+	    breaksw
+
+	    default:
+	    if ( $doquant == false ) then
+	        pnmscale -quiet -xysize $size $size $i > $tmpfile
+	    else
+	        pnmscale -quiet -xysize $size $size $i | ppmquant -quiet $colors > $tmpfile
+	    endif
+	    set maxformat=PPM
+	    breaksw
+	endsw
+    endif
+    set imagefile=/tmp/pi.${row}.${col}.$$
+    rm -f $imagefile
+    if ( "$back" == "-white" ) then
+	pbmtext "$i" | pnmcat $back -tb $tmpfile - > $imagefile
+    else
+	pbmtext "$i" | pnminvert | pnmcat $back -tb $tmpfile - > $imagefile
+    endif
+    rm -f $tmpfile
+    set imagefiles=( $imagefiles $imagefile )
+
+    if ( $col >= $across ) then
+	set rowfile=/tmp/pi.${row}.$$
+	rm -f $rowfile
+	if ( $maxformat != PPM || $doquant == false ) then
+	    pnmcat $back -lr -jbottom $imagefiles > $rowfile
+	else
+	    pnmcat $back -lr -jbottom $imagefiles | ppmquant -quiet $colors > $rowfile
+	endif
+	rm -f $imagefiles
+	set imagefiles=()
+	set rowfiles=( $rowfiles $rowfile )
+	@ col = 1
+	@ row += 1
+    else
+	@ col += 1
+    endif
+
+end
+
+if ( $#imagefiles > 0 ) then
+    set rowfile=/tmp/pi.${row}.$$
+    rm -f $rowfile
+    if ( $maxformat != PPM || $doquant == false ) then
+	pnmcat $back -lr -jbottom $imagefiles > $rowfile
+    else
+	pnmcat $back -lr -jbottom $imagefiles | ppmquant -quiet $colors > $rowfile
+    endif
+    rm -f $imagefiles
+    set rowfiles=( $rowfiles $rowfile )
+endif
+
+if ( $#rowfiles == 1 ) then
+    cat $rowfiles
+else
+    if ( $maxformat != PPM || $doquant == false ) then
+	pnmcat $back -tb $rowfiles
+    else
+	pnmcat $back -tb $rowfiles | ppmquant -quiet $colors
+    endif
+endif
+rm -f $rowfiles
+
+exit 0
+
+usage:
+echo "usage: $0 [-size N] [-across N] [-colors N] [-black] pnmfile ..."
+exit 1
diff --git a/editor/pnmindex.sh b/editor/pnmindex.sh
new file mode 100755
index 00000000..15ba1abd
--- /dev/null
+++ b/editor/pnmindex.sh
@@ -0,0 +1,215 @@
+#!/bin/sh
+#
+# pnmindex - build a visual index of a bunch of PNM images
+#
+# Copyright (C) 1991 by Jef Poskanzer.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose and without fee is hereby granted, provided
+# that the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation.  This software is provided "as is" without express or
+# implied warranty.
+
+size=100        # make the images about this big
+across=6        # show this many images per row
+colors=256      # quantize results to this many colors
+back="-white"   # default background color
+doquant=true    # quantize or not
+title=""        # default title (none)
+
+usage ()
+{
+  echo "usage: $0 [-size N] [-across N] [-colors N] [-black] pnmfile ..."
+  exit 1
+}
+
+while :; do
+    case "$1" in
+
+    -s*)
+        if [ $# -lt 2 ]; then usage; fi
+        size="$2"
+        shift
+        shift
+    ;;
+
+    -a*)
+        if [ $# -lt 2 ]; then usage; fi
+        across="$2"
+        shift
+        shift
+    ;;
+
+    -t*)
+        if [ $# -lt 2 ]; then usage; fi
+        title="$2"
+        shift
+        shift
+    ;;
+
+    -c*)
+        if [ $# -lt 2 ]; then usage; fi
+        colors="$2"
+        shift
+        shift
+    ;;
+
+    -b*)
+        back="-black"
+        shift
+    ;;
+
+    -w*)
+        back="-white"
+        shift
+    ;;
+
+    -noq*)
+        doquant=false
+        shift
+    ;;
+
+    -q*)
+        doquant=true
+        shift
+    ;;
+
+    -*)
+        usage
+    ;;
+
+    *)
+        break
+    ;;
+    esac
+done
+
+if [ $# -eq 0 ]; then
+    usage
+fi
+
+tempdir="${TMPDIR-/tmp}/pnmindex.$$"
+mkdir $tempdir || { echo "Could not create temporary file. Exiting."; exit 1;}
+chmod 700 $tempdir
+
+trap 'rm -rf $tempdir' 0 1 3 15
+
+tmpfile=$tempdir/pi.tmp
+maxformat=PBM
+
+rowfiles=()
+imagefiles=()
+row=1
+col=1
+
+if [ "$title"x != ""x ] ; then
+#    rowfile=`tempfile -p pirow -m 600`
+    rowfile=$tempdir/pi.${row}
+    pbmtext "$title" > $rowfile
+    rowfiles=(${rowfiles[*]} $rowfile )
+    row=$(($row + 1))
+fi
+
+for i in "$@"; do
+
+    description=(`pnmfile $i`)
+
+    format=${description[1]}
+    width=${description[3]}
+    height=${description[5]}
+
+    if [ $? -ne 0 ]; then
+        echo pnmfile returned an error
+        exit $?
+    fi
+
+    if [ $width -le $size ] && \
+       [ $height -le $size ]; then
+        cat $i > $tmpfile
+    else
+        case $format in
+
+        PBM) 
+            pamscale -quiet -xysize $size $size $i | pgmtopbm > $tmpfile
+        ;;
+
+        PGM)
+            pamscale -quiet -xysize $size $size $i > $tmpfile
+            if [ $maxformat = PBM ]; then
+                maxformat=PGM
+            fi
+        ;;
+
+        *) 
+            if [ "$doquant" = "true" ] ; then
+                pamscale -quiet -xysize $size $size $i | \
+                pnmquant -quiet $colors > $tmpfile
+            else
+                pamscale -quiet -xysize $size $size $i > $tmpfile
+            fi
+            maxformat=PPM
+        ;;
+        esac
+    fi
+
+    imagefile=$tempdir/pi.${row}.${col}
+    rm -f $imagefile
+    if [ "$back" = "-white" ]; then
+        pbmtext "$i" | pnmcat $back -tb $tmpfile - > $imagefile
+    else
+        pbmtext "$i" | pnminvert | pnmcat $back -tb $tmpfile - > $imagefile
+    fi
+    imagefiles=( ${imagefiles[*]} $imagefile )
+
+    if [ $col -ge $across ]; then
+        rowfile=$tempdir/pi.${row}
+        rm -f $rowfile
+
+        if [ $maxformat != PPM -o "$doquant" = "false" ]; then
+            pnmcat $back -lr -jbottom ${imagefiles[*]} > $rowfile
+        else
+            pnmcat $back -lr -jbottom ${imagefiles[*]} | \
+            pnmquant -quiet $colors > $rowfile
+        fi
+
+        rm -f ${imagefiles[*]}
+        unset imagefiles
+        imagefiles=()
+        rowfiles=( ${rowfiles[*]} $rowfile )
+        col=1
+        row=$(($row + 1))
+    else
+        col=$(($col + 1))
+    fi
+done
+
+# All the full rows have been put in row files.  
+# Now put the final partial row in its row file.
+
+if [ ${#imagefiles[*]} -gt 0 ]; then
+    rowfile=$tempdir/pi.${row}
+    rm -f $rowfile
+    if [ $maxformat != PPM -o "$doquant" = "false" ]; then
+        pnmcat $back -lr -jbottom ${imagefiles[*]} > $rowfile
+    else
+        pnmcat $back -lr -jbottom ${imagefiles[*]} | \
+        pnmquant -quiet $colors > $rowfile
+    fi
+    rm -f ${imagefiles[*]}
+    rowfiles=( ${rowfiles[*]} $rowfile )
+fi
+
+if [ ${#rowfiles[*]} -eq 1 ]; then
+    cat $rowfiles
+else
+    if [ $maxformat != PPM -o "$doquant" = "false" ]; then
+        pnmcat $back -tb ${rowfiles[*]}
+    else
+        pnmcat $back -tb ${rowfiles[*]} | pnmquant -quiet $colors
+    fi
+fi
+rm -f ${rowfiles[*]}
+
+exit 0
+
diff --git a/editor/pnminvert.c b/editor/pnminvert.c
new file mode 100644
index 00000000..40fee9be
--- /dev/null
+++ b/editor/pnminvert.c
@@ -0,0 +1,115 @@
+/* pnminvert.c - read a portable anymap and invert it
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pnm.h"
+
+#define CHARBITS (sizeof(unsigned char)*8)
+
+
+
+static void
+invertPbm(FILE * const ifP,
+          FILE * const ofP,
+          int    const cols,
+          int    const rows,
+          int    const format) {
+/*----------------------------------------------------------------------------
+   Invert a PBM image.  Use the "packed" PBM functions for speed.
+-----------------------------------------------------------------------------*/
+    /* We could make this faster by inverting whole words at a time,
+       using libnetpbm's wordaccess.h facility.
+    */
+    int const colChars = pbm_packed_bytes(cols);
+
+    unsigned char * bitrow; 
+    unsigned int row;
+    
+    bitrow = pbm_allocrow_packed(cols);
+    
+    for (row = 0; row < rows; ++row) {
+        unsigned int colChar;
+        
+        pbm_readpbmrow_packed(ifP, bitrow, cols, format);
+        for (colChar = 0; colChar < colChars; ++colChar)
+            bitrow[colChar] = ~ bitrow[colChar];
+        
+        /* Clean off remainder of fractional last character */
+        if (cols % CHARBITS > 0) {
+            bitrow[colChars-1] >>= CHARBITS - cols % CHARBITS;
+            bitrow[colChars-1] <<= CHARBITS - cols % CHARBITS;
+        }
+        pbm_writepbmrow_packed(ofP, bitrow, cols, 0);
+    }
+    pbm_freerow_packed(bitrow);
+}
+
+
+
+static void
+invertPnm(FILE * const ifP,
+          FILE * const ofP,
+          int    const cols,
+          int    const rows,
+          xelval const maxval,
+          int    const format) {
+
+    xel * xelrow;
+    unsigned int row;
+    
+    xelrow = pnm_allocrow(cols);
+    
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+        for (col = 0; col < cols; ++col)
+            pnm_invertxel(&xelrow[col], maxval, format);
+        
+        pnm_writepnmrow(ofP, xelrow, cols, maxval, format, 0);
+    }
+    pnm_freerow(xelrow);
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+    FILE* ifP;
+    xelval maxval;
+    int rows, cols, format;
+
+    pnm_init(&argc, argv);
+
+    if (argc-1 > 1)
+        pm_error("There is at most 1 argument - the input file name.  "
+                 "You specified %d", argc-1);
+    if (argc-1 == 1)
+        ifP = pm_openr(argv[1]);
+    else
+        ifP = stdin;
+
+    pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
+    pnm_writepnminit(stdout, cols, rows, maxval, format, 0);
+    
+    if (PNM_FORMAT_TYPE(format) == PBM_TYPE)
+        /* Take fast path */
+        invertPbm(ifP, stdout, cols, rows, format);
+    else
+        /* PPM , PGM  (logic also works for PBM) */
+        invertPnm(ifP, stdout, cols, rows, maxval, format);
+
+    pm_close(ifP);
+    pm_close(stdout);
+    
+    return 0;
+}
+
+
diff --git a/editor/pnminvert.test b/editor/pnminvert.test
new file mode 100644
index 00000000..606e4e5c
--- /dev/null
+++ b/editor/pnminvert.test
@@ -0,0 +1,15 @@
+echo Test 1.  Should print 1240379484 41
+./pnminvert ../testgrid.pbm | cksum
+echo Test 2.  Should print 1416115901 101484
+./pnminvert ../testimg.ppm | cksum
+echo Test 3.  Should print 4215652354 33838
+ppmtopgm ../testimg.ppm | ./pnminvert | cksum
+echo Test 4.  Should print 2595564405 14
+pbmmake -w 7 7 | ./pnminvert | cksum
+echo Test 5.  Should print 2595564405 14
+pbmmake -b 7 7 | cksum
+echo Test 6.  Should print 2595564405 14
+pbmmake -b 7 7 | ./pnminvert | ./pnminvert | cksum
+echo Test 7.  Should print 2896726098 15
+pbmmake -g 8 8 | ./pnminvert | cksum
+
diff --git a/editor/pnmmargin b/editor/pnmmargin
new file mode 100755
index 00000000..31420f99
--- /dev/null
+++ b/editor/pnmmargin
@@ -0,0 +1,88 @@
+#!/bin/sh
+#
+# ppmmargin - add a margin to a PNM image
+#
+# Copyright (C) 1991 by Jef Poskanzer.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose and without fee is hereby granted, provided
+# that the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation.  This software is provided "as is" without express or
+# implied warranty.
+
+tempdir="${TMPDIR-/tmp}/pnmmargin.$$"
+mkdir $tempdir || { echo "Could not create temporary file. Exiting."; exit 1;}
+chmod 700 $tempdir
+
+trap 'rm -rf $tempdir' 0 1 3 15
+
+tmp1=$tempdir/pnmm1
+tmp2=$tempdir/pnmm2
+tmp3=$tempdir/pnmm3
+tmp4=$tempdir/pnmm4
+
+color="-gofigure"
+
+# Parse args.
+while true ; do
+    case "$1" in
+	-w* )
+	color="-white"
+	shift
+	;;
+	-b* )
+	color="-black"
+	shift
+	;;
+	-c* )
+	shift
+	if [ ! ${1-""} ] ; then
+	    echo "usage: $0 [-white|-black|-color <colorspec>] <size> [pnmfile]" 1>&2
+	    exit 1
+	fi
+	color="$1"
+	shift
+	;;
+	-* )
+	echo "usage: $0 [-white|-black|-color <colorspec>] <size> [pnmfile]" 1>&2
+	exit 1
+	;;
+	* )
+	break
+	;;
+    esac
+done
+
+if [ ! ${1-""} ] ; then
+    echo "usage: $0 [-white|-black|-color <colorspec>] <size> [pnmfile]" 1>&2
+    exit 1
+fi
+size="$1"
+shift
+
+if [ ${2-""} ] ; then
+    echo "usage: $0 [-white|-black|-color <colorspec>] <size> [pnmfile]" 1>&2
+    exit 1
+fi
+
+# Capture input file in a tmp file, in case it's a pipe.
+cat $@ > $tmp1
+
+# Construct spacer files.
+case "$color" in
+    -gofigure )
+    pnmcut 0 0 1 1 $tmp1 | pnmtile $size 1 > $tmp2
+    ;;
+    -white | -black )
+    pbmmake $color $size 1 > $tmp2
+    ;;
+    * )
+    ppmmake $color $size 1 > $tmp2
+    ;;
+esac
+pamflip -rotate90 $tmp2 > $tmp3
+
+# Cat things together.
+pnmcat -lr $tmp2 $tmp1 $tmp2 > $tmp4
+pnmcat -tb $tmp3 $tmp4 $tmp3
diff --git a/editor/pnmmontage.c b/editor/pnmmontage.c
new file mode 100644
index 00000000..9eb2d7be
--- /dev/null
+++ b/editor/pnmmontage.c
@@ -0,0 +1,439 @@
+/* pnmmontage.c - build a montage of portable anymaps
+ *
+ * Copyright 2000 Ben Olmstead.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  This software is provided "as is" without express or
+ * implied warranty.
+ */
+
+#include <limits.h>
+#include <string.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "mallocvar.h"
+
+typedef struct { int f[sizeof(int) * 8 + 1]; } factorset;
+typedef struct { int x; int y; } coord;
+
+static int qfactor = 200;
+static int quality = 5;
+
+static factorset 
+factor(int n)
+{
+  int i, j;
+  factorset f;
+  for (i = 0; i < sizeof(int) * 8 + 1; ++i)
+    f.f[i] = 0;
+  for (i = 2, j = 0; n > 1; ++i)
+  {
+    if (n % i == 0)
+      f.f[j++] = i, n /= i, --i;
+  }
+  return (f);
+}
+
+static int 
+gcd(int n, int m)
+{
+  factorset nf, mf;
+  int i, j;
+  int g;
+
+  nf = factor(n);
+  mf = factor(m);
+
+  i = j = 0;
+  g = 1;
+  while (nf.f[i] && mf.f[j])
+  {
+    if (nf.f[i] == mf.f[j])
+      g *= nf.f[i], ++i, ++j;
+    else if (nf.f[i] < mf.f[j])
+      ++i;
+    else
+      ++j;
+  }
+  return (g);
+}
+
+static __inline__ int imax(int n, int m) { return (n > m ? n : m); }
+
+static int 
+checkcollision(coord *locs, coord *szs, coord *cloc, coord *csz, int n)
+{
+  int i;
+  for (i = 0; i < n; ++i)
+  {
+    if ((locs[i].x < cloc->x + csz->x) &&
+        (locs[i].y < cloc->y + csz->y) &&
+        (locs[i].x + szs[i].x > cloc->x) &&
+        (locs[i].y + szs[i].y > cloc->y))
+      return (1);
+  }
+  return (0);
+}
+
+static void 
+recursefindpack(coord *current, coord currentsz, coord *set, 
+                coord *best, int minarea, int *maxarea, 
+                int depth, int n, int xinc, int yinc)
+{
+  coord c;
+  if (depth == n)
+  {
+    if (currentsz.x * currentsz.y < *maxarea)
+    {
+      memcpy(best, current, sizeof(coord) * n);
+      *maxarea = currentsz.x * currentsz.y;
+    }
+    return;
+  }
+
+  for (current[depth].x = 0; 
+       imax(current[depth].x + set[depth].x, currentsz.x) * 
+           imax(currentsz.y, set[depth].y) < *maxarea; 
+       current[depth].x += xinc)
+  {
+    for (current[depth].y = 0; 
+         imax(current[depth].x + set[depth].x, currentsz.x) * 
+             imax(currentsz.y, current[depth].y + set[depth].y) < *maxarea; 
+         current[depth].y += yinc)
+    {
+      c.x = imax(current[depth].x + set[depth].x, currentsz.x);
+      c.y = imax(current[depth].y + set[depth].y, currentsz.y);
+      if (!checkcollision(current, set, &current[depth], &set[depth], depth))
+      {
+        recursefindpack(current, c, set, best, minarea, maxarea, 
+                        depth + 1, n, xinc, yinc);
+        if (*maxarea <= minarea)
+          return;
+      }
+    }
+  }
+}
+
+static void 
+findpack(struct pam *imgs, int n, coord *coords)
+{
+  int minarea;
+  int i;
+  int rdiv;
+  int cdiv;
+  int minx = -1;
+  int miny = -1;
+  coord *current;
+  coord *set;
+  int z = INT_MAX;
+  coord c = { 0, 0 };
+
+  if (quality > 1)
+  {
+    for (minarea = i = 0; i < n; ++i)
+      minarea += imgs[i].height * imgs[i].width,
+      minx = imax(minx, imgs[i].width),
+      miny = imax(miny, imgs[i].height);
+
+    minarea = minarea * qfactor / 100;
+  }
+  else
+  {
+    minarea = INT_MAX - 1;
+  }
+
+  /* It's relatively easy to show that, if all the images
+   * are multiples of a particular size, then a best
+   * packing will always align the images on a grid of
+   * that size.
+   *
+   * This speeds computation immensely.
+   */
+  for (rdiv = imgs[0].height, i = 1; i < n; ++i)
+    rdiv = gcd(imgs[i].height, rdiv);
+
+  for (cdiv = imgs[0].width, i = 1; i < n; ++i)
+    cdiv = gcd(imgs[i].width, cdiv);
+
+  MALLOCARRAY(current, n);
+  MALLOCARRAY(set, n);
+  for (i = 0; i < n; ++i)
+    set[i].x = imgs[i].width,
+    set[i].y = imgs[i].height;
+  recursefindpack(current, c, set, coords, minarea, &z, 0, n, cdiv, rdiv);
+}
+
+
+
+static void 
+adjustDepth(tuple *            const tuplerow,
+            const struct pam * const inpamP,
+            const struct pam * const outpamP,
+            coord              const coord) {
+
+    if (inpamP->depth < outpamP->depth) {
+        unsigned int i;
+        for (i = coord.x; i < coord.x + inpamP->width; ++i) {
+            int j;
+            for (j = inpamP->depth; j < outpamP->depth; ++j)
+                tuplerow[i][j] = tuplerow[i][inpamP->depth - 1];
+        }
+    }
+}
+
+
+
+static void 
+adjustMaxval(tuple *            const tuplerow,
+             const struct pam * const inpamP,
+             const struct pam * const outpamP,
+             coord              const coord) {
+
+    if (inpamP->maxval < outpamP->maxval) {
+        int i;
+        for (i = coord.x; i < coord.x + inpamP->width; ++i) {
+            int j;
+            for (j = 0; j < outpamP->depth; ++j)
+                tuplerow[i][j] *= outpamP->maxval / inpamP->maxval;
+        }
+    }
+}
+
+
+
+static void
+writePam(struct pam *       const outpamP,
+         unsigned int       const nfiles,
+         const coord *      const coords,
+         const struct pam * const imgs) {
+
+    tuple *tuplerow;
+    int i;
+  
+    pnm_writepaminit(outpamP);
+
+    tuplerow = pnm_allocpamrow(outpamP);
+
+    for (i = 0; i < outpamP->height; ++i) {
+        int j;
+        for (j = 0; j < nfiles; ++j) {
+            if (coords[j].y <= i && i < coords[j].y + imgs[j].height) {
+                pnm_readpamrow(&imgs[j], &tuplerow[coords[j].x]);
+                adjustDepth(tuplerow, &imgs[j], outpamP, coords[j]);
+
+                adjustMaxval(tuplerow, &imgs[j], outpamP, coords[j]);
+
+            }
+        }
+        pnm_writepamrow(outpamP, tuplerow);
+    }
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+int 
+main(int argc, char **argv)
+{
+  struct pam *imgs;
+  struct pam outimg;
+  struct pam p;
+  int nfiles;
+  int i, j;
+  unsigned int q[10];
+  coord *coords;
+  const char *headfname = NULL;
+  const char *datafname = NULL;
+  const char *prefix = "";
+  FILE *header;
+  FILE *data;
+  char **names;
+  char *c;
+
+  optEntry *option_def = malloc(100*sizeof(optEntry));
+      /* Instructions to OptParseOptions3 on how to parse our options.
+       */
+  optStruct3 opt;
+
+  unsigned int option_def_index;
+
+  option_def_index = 0;   /* incremented by OPTENTRY */
+  OPTENT3( 0,  "data",    OPT_STRING, &datafname, NULL, 0);
+  OPTENT3( 0,  "header",  OPT_STRING, &headfname, NULL, 0);
+  OPTENT3('q', "quality", OPT_UINT,   &qfactor,   NULL, 0);
+  OPTENT3('p', "prefix",  OPT_STRING, &prefix,    NULL, 0);
+  OPTENT3('0', "0",       OPT_FLAG,   NULL, &q[0],      0);
+  OPTENT3('1', "1",       OPT_FLAG,   NULL, &q[1],      0);
+  OPTENT3('2', "2",       OPT_FLAG,   NULL, &q[2],      0);
+  OPTENT3('3', "3",       OPT_FLAG,   NULL, &q[3],      0);
+  OPTENT3('4', "4",       OPT_FLAG,   NULL, &q[4],      0);
+  OPTENT3('5', "5",       OPT_FLAG,   NULL, &q[5],      0);
+  OPTENT3('6', "6",       OPT_FLAG,   NULL, &q[6],      0);
+  OPTENT3('7', "7",       OPT_FLAG,   NULL, &q[7],      0);
+  OPTENT3('8', "8",       OPT_FLAG,   NULL, &q[8],      0);
+  OPTENT3('9', "9",       OPT_FLAG,   NULL, &q[9],      0);
+
+  opt.opt_table = option_def;
+  opt.short_allowed = FALSE;
+  opt.allowNegNum = FALSE;
+
+  pnm_init(&argc, argv);
+
+  /* Check for flags. */
+  optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+  if (headfname)
+    header = pm_openw(headfname);
+
+  if (datafname)
+    data = pm_openw(datafname);
+
+  for (i = 0; i < 10; ++i)
+  {
+    if (q[i])
+    {
+      quality = i;
+      switch (quality)
+      {
+        case 0: case 1: break;
+        case 2: case 3: case 4: case 5: case 6: 
+            qfactor = 100 * (8 - quality); 
+            break;
+        case 7: qfactor = 150; break;
+        case 8: qfactor = 125; break;
+        case 9: qfactor = 100; break;
+      }
+    }
+  }
+
+  if (1 < argc)
+    nfiles = argc - 1;
+  else
+    nfiles = 1;
+
+  MALLOCARRAY(imgs, nfiles);
+  MALLOCARRAY(coords, nfiles);
+  MALLOCARRAY(names, nfiles);
+  
+  if (!imgs || !coords || !names)
+    pm_error("out of memory");
+
+  if (1 < argc)
+  {
+    for (i = 0; i < nfiles; ++i)
+    {
+      if (strchr(argv[i+1], ':'))
+      {
+        imgs[i].file = pm_openr(strchr(argv[i+1], ':') + 1);
+        *strchr(argv[i+1], ':') = 0;
+        names[i] = argv[i+1];
+      }
+      else
+      {
+        imgs[i].file = pm_openr(argv[i+1]);
+        names[i] = argv[i+1];
+      }
+    }
+  }
+  else
+  {
+    imgs[0].file = stdin;
+  }
+
+  pnm_readpaminit(imgs[0].file, &imgs[0], PAM_STRUCT_SIZE(tuple_type));
+  outimg.maxval = imgs[0].maxval;
+  outimg.format = imgs[0].format;
+  memcpy(outimg.tuple_type, imgs[0].tuple_type, sizeof(imgs[0].tuple_type));
+  outimg.depth = imgs[0].depth;
+
+  for (i = 1; i < nfiles; ++i)
+  {
+    pnm_readpaminit(imgs[i].file, &imgs[i], PAM_STRUCT_SIZE(tuple_type));
+    if (PAM_FORMAT_TYPE(imgs[i].format) > PAM_FORMAT_TYPE(outimg.format))
+      outimg.format = imgs[i].format,
+      memcpy(outimg.tuple_type, imgs[i].tuple_type, 
+             sizeof(imgs[i].tuple_type));
+    outimg.maxval = imax(imgs[i].maxval, outimg.maxval);
+    outimg.depth = imax(imgs[i].depth, outimg.depth);
+  }
+
+  for (i = 0; i < nfiles - 1; ++i)
+    for (j = i + 1; j < nfiles; ++j)
+      if (imgs[j].width * imgs[j].height > imgs[i].width * imgs[i].height)
+        p = imgs[i], imgs[i] = imgs[j], imgs[j] = p,
+        c = names[i], names[i] = names[j], names[j] = c;
+
+  findpack(imgs, nfiles, coords);
+
+  outimg.height = outimg.width = 0;
+  for (i = 0; i < nfiles; ++i)
+  {
+    outimg.width = imax(outimg.width, imgs[i].width + coords[i].x);
+    outimg.height = imax(outimg.height, imgs[i].height + coords[i].y);
+  }
+
+  outimg.size = sizeof(outimg);
+  outimg.len = sizeof(outimg);
+  outimg.file = stdout;
+  outimg.bytes_per_sample = 0;
+  for (i = outimg.maxval; i; i >>= 8)
+    ++outimg.bytes_per_sample;
+
+  writePam(&outimg, nfiles, coords, imgs);
+
+  if (datafname)
+  {
+    fprintf(data, ":0:0:%u:%u\n", outimg.width, outimg.height);
+
+    for (i = 0; i < nfiles; ++i)
+    {
+      fprintf(data, "%s:%u:%u:%u:%u\n", names[i], coords[i].x,
+          coords[i].y, imgs[i].width, imgs[i].height);
+    }
+  }
+
+  if (headfname)
+  {
+    fprintf(header, "#define %sOVERALLX %u\n"
+                    "#define %sOVERALLY %u\n"
+                    "\n",
+                    prefix, outimg.width,
+                    prefix, outimg.height);
+
+    for (i = 0; i < nfiles; ++i)
+    {
+      *strchr(names[i], '.') = 0;
+      for (j = 0; names[i][j]; ++j)
+      {
+        if (ISLOWER(names[i][j]))
+          names[i][j] = TOUPPER(names[i][j]);
+      }
+      fprintf(header, "#define %s%sX %u\n"
+                      "#define %s%sY %u\n"
+                      "#define %s%sSZX %u\n"
+                      "#define %s%sSZY %u\n"
+                      "\n",
+                      prefix, names[i], coords[i].x,
+                      prefix, names[i], coords[i].y,
+                      prefix, names[i], imgs[i].width,
+                      prefix, names[i], imgs[i].height);
+    }
+  }
+
+  for (i = 0; i < nfiles; ++i)
+    pm_close(imgs[i].file);
+  pm_close(stdout);
+
+  if (headfname)
+    pm_close(header);
+
+  if (datafname)
+    pm_close(data);
+
+  return 0;
+}
diff --git a/editor/pnmnlfilt.c b/editor/pnmnlfilt.c
new file mode 100644
index 00000000..20705f82
--- /dev/null
+++ b/editor/pnmnlfilt.c
@@ -0,0 +1,1028 @@
+/* pnmnlfilt.c - 4 in 1 (2 non-linear) filter
+**             - smooth an anyimage
+**             - do alpha trimmed mean filtering on an anyimage
+**             - do optimal estimation smoothing on an anyimage
+**             - do edge enhancement on an anyimage
+**
+** Version 1.0
+**
+** The implementation of an alpha-trimmed mean filter
+** is based on the description in IEEE CG&A May 1990
+** Page 23 by Mark E. Lee and Richard A. Redner.
+**
+** The paper recommends using a hexagon sampling region around each
+** pixel being processed, allowing an effective sub pixel radius to be
+** specified. The hexagon values are sythesized by area sampling the
+** rectangular pixels with a hexagon grid. The seven hexagon values
+** obtained from the 3x3 pixel grid are used to compute the alpha
+** trimmed mean. Note that an alpha value of 0.0 gives a conventional
+** mean filter (where the radius controls the contribution of
+** surrounding pixels), while a value of 0.5 gives a median filter.
+** Although there are only seven values to trim from before finding
+** the mean, the algorithm has been extended from that described in
+** CG&A by using interpolation, to allow a continuous selection of
+** alpha value between and including 0.0 to 0.5  The useful values
+** for radius are between 0.3333333 (where the filter will have no
+** effect because only one pixel is sampled), to 1.0, where all
+** pixels in the 3x3 grid are sampled.
+**
+** The optimal estimation filter is taken from an article "Converting Dithered
+** Images Back to Gray Scale" by Allen Stenger, Dr Dobb's Journal, November
+** 1992, and this article references "Digital Image Enhancement andNoise Filtering by
+** Use of Local Statistics", Jong-Sen Lee, IEEE Transactions on Pattern Analysis and
+** Machine Intelligence, March 1980.
+**
+** Also borrow the  technique used in pgmenhance(1) to allow edge
+** enhancement if the alpha value is negative.
+**
+** Author:
+**         Graeme W. Gill, 30th Jan 1993
+**         graeme@labtam.oz.au
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "pnm.h"
+
+/* MXIVAL is the maximum input sample value we can handle.
+   It is limited by our willingness to allocate storage in various arrays
+   that are indexed by sample values.
+
+   We use PPM_MAXMAXVAL because that used to be the maximum possible
+   sample value in the format, and most images still limit themselves to
+   this value.
+*/
+
+#define MXIVAL PPM_MAXMAXVAL   
+
+xelval omaxval; 
+    /* global so that pixel processing code can get at it quickly */
+int noisevariance;      
+    /* global so that pixel processing code can get at it quickly */
+
+/*
+ * Declared static here rather than passing a jillion options in the call to
+ * do_one_frame().   Also it makes a huge amount of sense to only malloc the
+ * row buffers once instead of for each frame (with the corresponding free'ing
+ * of course).
+*/
+static  xel *irows[3];
+static  xel *irow0, *irow1, *irow2, *orow;
+static  double radius=0.0,alpha= -1.0;
+static  int rows, cols, format, oformat, row, col;
+static  int (*atfunc)(int *);
+static  xelval maxval;
+
+#define NOIVAL (MXIVAL + 1)             /* number of possible input values */
+
+#define SCALEB 8                                /* scale bits */
+#define SCALE (1 << SCALEB)     /* scale factor */
+#define MXSVAL (MXIVAL * SCALE) /* maximum scaled values */
+
+#define CSCALEB 2                               /* coarse scale bits */
+#define CSCALE (1 << CSCALEB)   /* coarse scale factor */
+#define MXCSVAL (MXIVAL * CSCALE)       /* maximum coarse scaled values */
+#define NOCSVAL (MXCSVAL + 1)   /* number of coarse scaled values */
+#define SCTOCSC(x) ((x) >> (SCALEB - CSCALEB))  /*  scaled to coarse scaled */
+#define CSCTOSC(x) ((x) << (SCALEB - CSCALEB))  /*  course scaled to scaled */
+
+#ifndef MAXINT
+# define MAXINT 0x7fffffff      /* assume this is a 32 bit machine */
+#endif
+
+/* round and scale floating point to scaled integer */
+#define ROUNDSCALE(x) ((int)(((x) * (double)SCALE) + 0.5))
+/* round and un-scale scaled integer value */
+#define RUNSCALE(x) (((x) + (1 << (SCALEB-1))) >> SCALEB) 
+/* rounded un-scale */
+#define UNSCALE(x) ((x) >> SCALEB)
+
+static double
+sqr(double const arg) {
+    return arg * arg;
+}
+
+
+
+/* We restrict radius to the values: 0.333333 <= radius <= 1.0 */
+/* so that no fewer and no more than a 3x3 grid of pixels around */
+/* the pixel in question needs to be read. Given this, we only */
+/* need 3 or 4 weightings per hexagon, as follows: */
+/*                  _ _                         */
+/* Vertical hex:   |_|_|  1 2                   */
+/*                 |X|_|  0 3                   */
+/*                                       _      */
+/*              _                      _|_|   1 */
+/* Middle hex: |_| 1  Horizontal hex: |X|_| 0 2 */
+/*             |X| 0                    |_|   3 */
+/*             |_| 2                            */
+
+/* all filters */
+int V0[NOIVAL],V1[NOIVAL],V2[NOIVAL],V3[NOIVAL];        /* vertical hex */
+int M0[NOIVAL],M1[NOIVAL],M2[NOIVAL];                   /* middle hex */
+int H0[NOIVAL],H1[NOIVAL],H2[NOIVAL],H3[NOIVAL];        /* horizontal hex */
+
+/* alpha trimmed and edge enhancement only */
+int ALFRAC[NOIVAL * 8];                 /* fractional alpha divider table */
+
+/* optimal estimation only */
+int AVEDIV[7 * NOCSVAL];                /* divide by 7 to give average value */
+int SQUARE[2 * NOCSVAL];                /* scaled square lookup table */
+
+/* ************************************************** *
+   Hexagon intersecting square area functions 
+   Compute the area of the intersection of a triangle 
+   and a rectangle 
+   ************************************************** */
+
+/* Triangle orientation is per geometric axes (not graphical axies) */
+
+#define NW 0    /* North west triangle /| */
+#define NE 1    /* North east triangle |\ */
+#define SW 2    /* South west triangle \| */
+#define SE 3    /* South east triangle |/ */
+#define STH 2
+#define EST 1
+
+#define SWAPI(a,b) (t = a, a = -b, b = -t)
+
+static double 
+triang_area(double rx0, double ry0, double rx1, double ry1,
+            double tx0, double ty0, double tx1, double ty1,
+            int tt) {
+/* rx0,ry0,rx1,ry1:       rectangle boundaries */
+/* tx0,ty0,tx1,ty1:       triangle boundaries */
+/* tt:                    triangle type */
+
+    double a,b,c,d;
+    double lx0,ly0,lx1,ly1;
+
+    /* Convert everything to a NW triangle */
+    if (tt & STH) {
+        double t;
+        SWAPI(ry0,ry1);
+        SWAPI(ty0,ty1);
+    }
+    if (tt & EST) {
+        double t;
+        SWAPI(rx0,rx1);
+        SWAPI(tx0,tx1);
+    }
+    /* Compute overlapping box */
+    if (tx0 > rx0)
+        rx0 = tx0;
+    if (ty0 > ry0)
+        ry0 = ty0;
+    if (tx1 < rx1)
+        rx1 = tx1;
+    if (ty1 < ry1)
+        ry1 = ty1;
+    if (rx1 <= rx0 || ry1 <= ry0)
+        return 0.0;
+
+    /* Need to compute diagonal line intersection with the box */
+    /* First compute co-efficients to formulas x = a + by and y = c + dx */
+    b = (tx1 - tx0)/(ty1 - ty0);
+    a = tx0 - b * ty0;
+    d = (ty1 - ty0)/(tx1 - tx0);
+    c = ty0 - d * tx0;
+    
+    /* compute top or right intersection */
+    tt = 0;
+    ly1 = ry1;
+    lx1 = a + b * ly1;
+    if (lx1 <= rx0)
+        return (rx1 - rx0) * (ry1 - ry0);
+    else if (lx1 > rx1) {
+        /* could be right hand side */
+        lx1 = rx1;
+        ly1 = c + d * lx1;
+        if (ly1 <= ry0)
+            return (rx1 - rx0) * (ry1 - ry0);
+        tt = 1; /* right hand side intersection */
+    }
+    /* compute left or bottom intersection */
+    lx0 = rx0;
+    ly0 = c + d * lx0;
+    if (ly0 >= ry1)
+        return (rx1 - rx0) * (ry1 - ry0);
+    else if (ly0 < ry0) {
+        /* could be right hand side */
+        ly0 = ry0;
+        lx0 = a + b * ly0;
+        if (lx0 >= rx1)
+            return (rx1 - rx0) * (ry1 - ry0);
+        tt |= 2;        /* bottom intersection */
+    }
+    
+    if (tt == 0) {
+        /* top and left intersection */
+        /* rectangle minus triangle */
+        return ((rx1 - rx0) * (ry1 - ry0))
+            - (0.5 * (lx1 - rx0) * (ry1 - ly0));
+    } else if (tt == 1) {
+        /* right and left intersection */
+        return ((rx1 - rx0) * (ly0 - ry0))
+            + (0.5 * (rx1 - rx0) * (ly1 - ly0));
+    } else if (tt == 2) {
+        /* top and bottom intersection */
+        return ((rx1 - lx1) * (ry1 - ry0))
+            + (0.5 * (lx1 - lx0) * (ry1 - ry0));
+    } else {
+        /* tt == 3 */ 
+        /* right and bottom intersection */
+        /* triangle */
+        return (0.5 * (rx1 - lx0) * (ly1 - ry0));
+    }
+}
+
+
+
+static double
+rectang_area(double rx0, double ry0, double rx1, double ry1, 
+             double tx0, double ty0, double tx1, double ty1) {
+/* Compute rectangle area */
+/* rx0,ry0,rx1,ry1:  rectangle boundaries */
+/* tx0,ty0,tx1,ty1:  rectangle boundaries */
+
+    /* Compute overlapping box */
+    if (tx0 > rx0)
+        rx0 = tx0;
+    if (ty0 > ry0)
+        ry0 = ty0;
+    if (tx1 < rx1)
+        rx1 = tx1;
+    if (ty1 < ry1)
+        ry1 = ty1;
+    if (rx1 <= rx0 || ry1 <= ry0)
+        return 0.0;
+    return (rx1 - rx0) * (ry1 - ry0);
+}
+
+
+
+
+static double 
+hex_area(double sx, double sy, double hx, double hy, double d) {
+/* compute the area of overlap of a hexagon diameter d, */
+/* centered at hx,hy, with a unit square of center sx,sy. */
+/* sx,sy:    square center */
+/* hx,hy,d:  hexagon center and diameter */
+
+    double hx0,hx1,hx2,hy0,hy1,hy2,hy3;
+    double sx0,sx1,sy0,sy1;
+
+    /* compute square co-ordinates */
+    sx0 = sx - 0.5;
+    sy0 = sy - 0.5;
+    sx1 = sx + 0.5;
+    sy1 = sy + 0.5;
+
+    /* compute hexagon co-ordinates */
+    hx0 = hx - d/2.0;
+    hx1 = hx;
+    hx2 = hx + d/2.0;
+    hy0 = hy - 0.5773502692 * d;    /* d / sqrt(3) */
+    hy1 = hy - 0.2886751346 * d;    /* d / sqrt(12) */
+    hy2 = hy + 0.2886751346 * d;    /* d / sqrt(12) */
+    hy3 = hy + 0.5773502692 * d;    /* d / sqrt(3) */
+
+    return
+        triang_area(sx0,sy0,sx1,sy1,hx0,hy2,hx1,hy3,NW) +
+        triang_area(sx0,sy0,sx1,sy1,hx1,hy2,hx2,hy3,NE) +
+        rectang_area(sx0,sy0,sx1,sy1,hx0,hy1,hx2,hy2) +
+        triang_area(sx0,sy0,sx1,sy1,hx0,hy0,hx1,hy1,SW) +
+        triang_area(sx0,sy0,sx1,sy1,hx1,hy0,hx2,hy1,SE);
+}
+
+
+
+
+static void
+setupAvediv(void) {
+
+    unsigned int i;
+
+    for (i=0; i < (7 * NOCSVAL); ++i) {
+        /* divide scaled value by 7 lookup */
+        AVEDIV[i] = CSCTOSC(i)/7;       /* scaled divide by 7 */
+    }
+
+}
+
+
+
+
+static void
+setupSquare(void) {
+
+    unsigned int i;
+
+    for (i=0; i < (2 * NOCSVAL); ++i) {
+        /* compute square and rescale by (val >> (2 * SCALEB + 2)) table */
+        int const val = CSCTOSC(i - NOCSVAL); 
+        /* NOCSVAL offset to cope with -ve input values */
+        SQUARE[i] = (val * val) >> (2 * SCALEB + 2);
+    }
+}
+
+
+
+
+static void
+setup1(double   const alpha,
+       double   const radius,
+       double   const maxscale,
+       int *    const alpharangeP,
+       double * const meanscaleP,
+       double * const mmeanscaleP,
+       double * const alphafractionP,
+       int *    const noisevarianceP) {
+
+
+    setupAvediv();
+    setupSquare();
+
+    if (alpha >= 0.0 && alpha <= 0.5) {
+        /* alpha trimmed mean */
+        double const noinmean =  ((0.5 - alpha) * 12.0) + 1.0;
+            /* number of elements (out of a possible 7) used in the mean */
+
+        *mmeanscaleP = *meanscaleP = maxscale/noinmean;
+        if (alpha == 0.0) {
+            /* mean filter */ 
+            *alpharangeP = 0;
+            *alphafractionP = 0.0;            /* not used */
+        } else if (alpha < (1.0/6.0)) {
+            /* mean of 5 to 7 middle values */
+            *alpharangeP = 1;
+            *alphafractionP = (7.0 - noinmean)/2.0;
+        } else if (alpha < (1.0/3.0)) {
+            /* mean of 3 to 5 middle values */
+            *alpharangeP = 2;
+            *alphafractionP = (5.0 - noinmean)/2.0;
+        } else {
+            /* mean of 1 to 3 middle values */
+            /* alpha == 0.5 == median filter */
+            *alpharangeP = 3;
+            *alphafractionP = (3.0 - noinmean)/2.0;
+        }
+    } else if (alpha >= 1.0 && alpha <= 2.0) {
+        /* optimal estimation - alpha controls noise variance threshold. */
+        double const alphaNormalized = alpha - 1.0;
+            /* normalize it to 0.0 -> 1.0 */
+        double const noinmean = 7.0;
+        *alpharangeP = 5;                 /* edge enhancement function */
+        *mmeanscaleP = *meanscaleP = maxscale;  /* compute scaled hex values */
+        *alphafractionP = 1.0/noinmean;   
+            /* Set up 1:1 division lookup - not used */
+        *noisevarianceP = sqr(alphaNormalized * omaxval) / 8.0;    
+            /* estimate of noise variance */
+    } else if (alpha >= -0.9 && alpha <= -0.1) {
+        /* edge enhancement function */
+        double const posAlpha = -alpha;
+            /* positive alpha value */
+        *alpharangeP = 4;                 /* edge enhancement function */
+        *meanscaleP = maxscale * (-posAlpha/((1.0 - posAlpha) * 7.0));
+            /* mean of 7 and scaled by -posAlpha/(1-posAlpha) */
+        *mmeanscaleP = maxscale * (1.0/(1.0 - posAlpha) + *meanscaleP);    
+            /* middle pixel has 1/(1-posAlpha) as well */
+        *alphafractionP = 0.0;    /* not used */
+    } else {
+        /* An entry condition on 'alpha' makes this impossible */
+        pm_error("INTERNAL ERROR: impossible alpha value: %f", alpha);
+    }
+}
+
+
+
+
+static void
+setupAlfrac(double const alphafraction) {
+    /* set up alpha fraction lookup table used on big/small */
+
+    unsigned int i;
+
+    for (i=0; i < (NOIVAL * 8); ++i) {
+        ALFRAC[i] = ROUNDSCALE(i * alphafraction);
+    }
+}
+
+
+
+
+static void
+setupPixelWeightingTables(double const radius,
+                          double const meanscale,
+                          double const mmeanscale) {
+
+    /* Setup pixel weighting tables - note we pre-compute mean
+       division here too. 
+    */
+    double const hexhoff = radius/2;      
+        /* horizontal offset of vertical hex centers */
+    double const hexvoff = 3.0 * radius/sqrt(12.0); 
+        /* vertical offset of vertical hex centers */
+
+    double const tabscale  = meanscale  / (radius * hexvoff);
+    double const mtabscale = mmeanscale / (radius * hexvoff);
+
+    /* scale tables to normalize by hexagon area, and number of
+       hexes used in mean 
+    */
+    double const v0 =
+        hex_area(0.0,  0.0, hexhoff, hexvoff, radius) * tabscale;
+    double const v1 =
+        hex_area(0.0,  1.0, hexhoff, hexvoff, radius) * tabscale;
+    double const v2 =
+        hex_area(1.0,  1.0, hexhoff, hexvoff, radius) * tabscale;
+    double const v3 =
+        hex_area(1.0,  0.0, hexhoff, hexvoff, radius) * tabscale;
+    double const m0 =
+        hex_area(0.0,  0.0, 0.0,     0.0,     radius) * mtabscale;
+    double const m1 =
+        hex_area(0.0,  1.0, 0.0,     0.0,     radius) * mtabscale;
+    double const m2 =
+        hex_area(0.0, -1.0, 0.0,     0.0,     radius) * mtabscale;
+    double const h0 =
+        hex_area(0.0,  0.0, radius,  0.0,     radius) * tabscale;
+    double const h1 =
+        hex_area(1.0,  1.0, radius,  0.0,     radius) * tabscale;
+    double const h2 =
+        hex_area(1.0,  0.0, radius,  0.0,     radius) * tabscale;
+    double const h3 =
+        hex_area(1.0, -1.0, radius,  0.0,     radius) * tabscale;
+
+    unsigned int i;
+
+    for (i=0; i <= MXIVAL; ++i) {
+        V0[i] = ROUNDSCALE(i * v0);
+        V1[i] = ROUNDSCALE(i * v1);
+        V2[i] = ROUNDSCALE(i * v2);
+        V3[i] = ROUNDSCALE(i * v3);
+        M0[i] = ROUNDSCALE(i * m0);
+        M1[i] = ROUNDSCALE(i * m1);
+        M2[i] = ROUNDSCALE(i * m2);
+        H0[i] = ROUNDSCALE(i * h0);
+        H1[i] = ROUNDSCALE(i * h1);
+        H2[i] = ROUNDSCALE(i * h2);
+        H3[i] = ROUNDSCALE(i * h3);
+    }
+}
+
+
+
+
+/* Table initialization function - return alpha range */
+static int 
+atfilt_setup(double const alpha,
+             double const radius,
+             double const maxscale) {
+
+    int alpharange;                 /* alpha range value 0 - 5 */
+    double meanscale;               /* scale for finding mean */
+    double mmeanscale;              /* scale for finding mean - midle hex */
+    double alphafraction;   
+        /* fraction of next largest/smallest to subtract from sum */
+
+    setup1(alpha, radius, maxscale,
+           &alpharange, &meanscale, &mmeanscale, &alphafraction,
+           &noisevariance);
+
+    setupAlfrac(alphafraction);
+
+    setupPixelWeightingTables(radius, meanscale, mmeanscale);
+
+    return alpharange;
+}
+
+
+
+static int 
+atfilt0(int * p) {
+/* Core pixel processing function - hand it 3x3 pixels and return result. */
+/* Mean filter */
+    /* 'p' is 9 pixel values from 3x3 neighbors */
+    int retv;
+    /* map to scaled hexagon values */
+    retv = M0[p[0]] + M1[p[3]] + M2[p[7]];
+    retv += H0[p[0]] + H1[p[2]] + H2[p[1]] + H3[p[8]];
+    retv += V0[p[0]] + V1[p[3]] + V2[p[2]] + V3[p[1]];
+    retv += V0[p[0]] + V1[p[3]] + V2[p[4]] + V3[p[5]];
+    retv += H0[p[0]] + H1[p[4]] + H2[p[5]] + H3[p[6]];
+    retv += V0[p[0]] + V1[p[7]] + V2[p[6]] + V3[p[5]];
+    retv += V0[p[0]] + V1[p[7]] + V2[p[8]] + V3[p[1]];
+    return UNSCALE(retv);
+}
+
+#define CHECK(xx) {\
+        h0 += xx; \
+        if (xx > big) \
+            big = xx; \
+        else if (xx < small) \
+            small = xx; }
+
+static int 
+atfilt1(int * p) {
+/* Mean of 5 - 7 middle values */
+/* 'p' is 9 pixel values from 3x3 neighbors */
+
+    int h0,h1,h2,h3,h4,h5,h6;       /* hexagon values    2 3   */
+                                    /*                  1 0 4  */
+                                    /*                   6 5   */
+    int big,small;
+    /* map to scaled hexagon values */
+    h0 = M0[p[0]] + M1[p[3]] + M2[p[7]];
+    h1 = H0[p[0]] + H1[p[2]] + H2[p[1]] + H3[p[8]];
+    h2 = V0[p[0]] + V1[p[3]] + V2[p[2]] + V3[p[1]];
+    h3 = V0[p[0]] + V1[p[3]] + V2[p[4]] + V3[p[5]];
+    h4 = H0[p[0]] + H1[p[4]] + H2[p[5]] + H3[p[6]];
+    h5 = V0[p[0]] + V1[p[7]] + V2[p[6]] + V3[p[5]];
+    h6 = V0[p[0]] + V1[p[7]] + V2[p[8]] + V3[p[1]];
+    /* sum values and also discover the largest and smallest */
+    big = small = h0;
+    CHECK(h1);
+    CHECK(h2);
+    CHECK(h3);
+    CHECK(h4);
+    CHECK(h5);
+    CHECK(h6);
+    /* Compute mean of middle 5-7 values */
+    return UNSCALE(h0 -ALFRAC[(big + small)>>SCALEB]);
+}
+#undef CHECK
+
+#define CHECK(xx) {\
+        h0 += xx; \
+        if (xx > big1) {\
+            if (xx > big0) {\
+                big1 = big0; \
+                big0 = xx; \
+            } else \
+                big1 = xx; \
+        } \
+        if (xx < small1) {\
+            if (xx < small0) {\
+                small1 = small0; \
+                small0 = xx; \
+                } else \
+                    small1 = xx; \
+        }\
+    }
+
+
+static int 
+atfilt2(int *p) {
+/* Mean of 3 - 5 middle values */
+/* 'p' is 9 pixel values from 3x3 neighbors */
+    int h0,h1,h2,h3,h4,h5,h6;       /* hexagon values    2 3   */
+                                    /*                  1 0 4  */
+                                    /*                   6 5   */
+    int big0,big1,small0,small1;
+    /* map to scaled hexagon values */
+    h0 = M0[p[0]] + M1[p[3]] + M2[p[7]];
+    h1 = H0[p[0]] + H1[p[2]] + H2[p[1]] + H3[p[8]];
+    h2 = V0[p[0]] + V1[p[3]] + V2[p[2]] + V3[p[1]];
+    h3 = V0[p[0]] + V1[p[3]] + V2[p[4]] + V3[p[5]];
+    h4 = H0[p[0]] + H1[p[4]] + H2[p[5]] + H3[p[6]];
+    h5 = V0[p[0]] + V1[p[7]] + V2[p[6]] + V3[p[5]];
+    h6 = V0[p[0]] + V1[p[7]] + V2[p[8]] + V3[p[1]];
+    /* sum values and also discover the 2 largest and 2 smallest */
+    big0 = small0 = h0;
+    small1 = MAXINT;
+    big1 = 0;
+    CHECK(h1);
+    CHECK(h2);
+    CHECK(h3);
+    CHECK(h4);
+    CHECK(h5);
+    CHECK(h6);
+    /* Compute mean of middle 3-5 values */
+    return UNSCALE(h0 -big0 -small0 -ALFRAC[(big1 + small1)>>SCALEB]);
+}
+
+#undef CHECK
+
+#define CHECK(xx) {\
+        h0 += xx; \
+        if (xx > big2) \
+                { \
+                if (xx > big1) \
+                        { \
+                        if (xx > big0) \
+                                { \
+                                big2 = big1; \
+                                big1 = big0; \
+                                big0 = xx; \
+                                } \
+                        else \
+                                { \
+                                big2 = big1; \
+                                big1 = xx; \
+                                } \
+                        } \
+                else \
+                        big2 = xx; \
+                } \
+        if (xx < small2) \
+                { \
+                if (xx < small1) \
+                        { \
+                        if (xx < small0) \
+                                { \
+                                small2 = small1; \
+                                small1 = small0; \
+                                small0 = xx; \
+                                } \
+                        else \
+                                { \
+                                small2 = small1; \
+                                small1 = xx; \
+                                } \
+                        } \
+                else \
+                        small2 = xx; \
+                                         }}
+
+static int 
+atfilt3(int *p) {
+/* Mean of 1 - 3 middle values. If only 1 value, then this is a median
+   filter. 
+*/
+/* 'p' is pixel values from 3x3 neighbors */
+    int h0,h1,h2,h3,h4,h5,h6;       /* hexagon values    2 3   */
+                                    /*                  1 0 4  */
+                                    /*                   6 5   */
+    int big0,big1,big2,small0,small1,small2;
+    /* map to scaled hexagon values */
+    h0 = M0[p[0]] + M1[p[3]] + M2[p[7]];
+    h1 = H0[p[0]] + H1[p[2]] + H2[p[1]] + H3[p[8]];
+    h2 = V0[p[0]] + V1[p[3]] + V2[p[2]] + V3[p[1]];
+    h3 = V0[p[0]] + V1[p[3]] + V2[p[4]] + V3[p[5]];
+    h4 = H0[p[0]] + H1[p[4]] + H2[p[5]] + H3[p[6]];
+    h5 = V0[p[0]] + V1[p[7]] + V2[p[6]] + V3[p[5]];
+    h6 = V0[p[0]] + V1[p[7]] + V2[p[8]] + V3[p[1]];
+    /* sum values and also discover the 3 largest and 3 smallest */
+    big0 = small0 = h0;
+    small1 = small2 = MAXINT;
+    big1 = big2 = 0;
+    CHECK(h1);
+    CHECK(h2);
+    CHECK(h3);
+    CHECK(h4);
+    CHECK(h5);
+    CHECK(h6);
+    /* Compute mean of middle 1-3 values */
+    return  UNSCALE(h0 -big0 -big1 -small0 -small1 
+                    -ALFRAC[(big2 + small2)>>SCALEB]);
+}
+#undef CHECK
+
+static int 
+atfilt4(int *p) {
+/* Edge enhancement */
+/* notice we use the global omaxval */
+/* 'p' is 9 pixel values from 3x3 neighbors */
+
+    int hav;
+    /* map to scaled hexagon values and compute enhance value */
+    hav = M0[p[0]] + M1[p[3]] + M2[p[7]];
+    hav += H0[p[0]] + H1[p[2]] + H2[p[1]] + H3[p[8]];
+    hav += V0[p[0]] + V1[p[3]] + V2[p[2]] + V3[p[1]];
+    hav += V0[p[0]] + V1[p[3]] + V2[p[4]] + V3[p[5]];
+    hav += H0[p[0]] + H1[p[4]] + H2[p[5]] + H3[p[6]];
+    hav += V0[p[0]] + V1[p[7]] + V2[p[6]] + V3[p[5]];
+    hav += V0[p[0]] + V1[p[7]] + V2[p[8]] + V3[p[1]];
+    if (hav < 0)
+        hav = 0;
+    hav = UNSCALE(hav);
+    if (hav > omaxval)
+        hav = omaxval;
+    return hav;
+}
+
+static int 
+atfilt5(int *p) {
+/* Optimal estimation - do smoothing in inverse proportion */
+/* to the local variance. */
+/* notice we use the globals noisevariance and omaxval*/
+/* 'p' is 9 pixel values from 3x3 neighbors */
+
+    int mean,variance,temp;
+    int h0,h1,h2,h3,h4,h5,h6;       /* hexagon values    2 3   */
+                                    /*                  1 0 4  */
+                                    /*                   6 5   */
+    /* map to scaled hexagon values */
+    h0 = M0[p[0]] + M1[p[3]] + M2[p[7]];
+    h1 = H0[p[0]] + H1[p[2]] + H2[p[1]] + H3[p[8]];
+    h2 = V0[p[0]] + V1[p[3]] + V2[p[2]] + V3[p[1]];
+    h3 = V0[p[0]] + V1[p[3]] + V2[p[4]] + V3[p[5]];
+    h4 = H0[p[0]] + H1[p[4]] + H2[p[5]] + H3[p[6]];
+    h5 = V0[p[0]] + V1[p[7]] + V2[p[6]] + V3[p[5]];
+    h6 = V0[p[0]] + V1[p[7]] + V2[p[8]] + V3[p[1]];
+    mean = h0 + h1 + h2 + h3 + h4 + h5 + h6;
+    mean = AVEDIV[SCTOCSC(mean)];   /* compute scaled mean by dividing by 7 */
+    temp = (h1 - mean); variance = SQUARE[NOCSVAL + SCTOCSC(temp)];  
+        /* compute scaled variance */
+    temp = (h2 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)]; 
+        /* and rescale to keep */
+    temp = (h3 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)]; 
+        /* within 32 bit limits */
+    temp = (h4 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)];
+    temp = (h5 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)];
+    temp = (h6 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)];
+    temp = (h0 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)];   
+    /* (temp = h0 - mean) */
+    if (variance != 0)      /* avoid possible divide by 0 */
+        temp = mean + (variance * temp) / (variance + noisevariance);   
+            /* optimal estimate */
+    else temp = h0;
+    if (temp < 0)
+        temp = 0;
+    temp = RUNSCALE(temp);
+    if (temp > omaxval)
+        temp = omaxval;
+    return temp;
+}
+
+
+
+static void 
+do_one_frame(FILE *ifp) {
+
+    pnm_writepnminit( stdout, cols, rows, omaxval, oformat, 0 );
+    
+    if ( PNM_FORMAT_TYPE(oformat) == PPM_TYPE ) {
+        int pr[9],pg[9],pb[9];          /* 3x3 neighbor pixel values */
+        int r,g,b;
+
+        for ( row = 0; row < rows; row++ ) {
+            int po,no;           /* offsets for left and right colums in 3x3 */
+            xel *ip0, *ip1, *ip2, *op;
+
+            if (row == 0) {
+                irow0 = irow1;
+                pnm_readpnmrow( ifp, irow1, cols, maxval, format );
+            }
+            if (row == (rows-1))
+                irow2 = irow1;
+            else
+                pnm_readpnmrow( ifp, irow2, cols, maxval, format );
+
+            for (col = cols-1,po= col>0?1:0,no=0,
+                     ip0=irow0,ip1=irow1,ip2=irow2,op=orow;
+                 col >= 0;
+                 col--,ip0++,ip1++,ip2++,op++, no |= 1,po = col!= 0 ? po : 0) {
+                                /* grab 3x3 pixel values */
+                pr[0] = PPM_GETR( *ip1 );
+                pg[0] = PPM_GETG( *ip1 );
+                pb[0] = PPM_GETB( *ip1 );
+                pr[1] = PPM_GETR( *(ip1-no) );
+                pg[1] = PPM_GETG( *(ip1-no) );
+                pb[1] = PPM_GETB( *(ip1-no) );
+                pr[5] = PPM_GETR( *(ip1+po) );
+                pg[5] = PPM_GETG( *(ip1+po) );
+                pb[5] = PPM_GETB( *(ip1+po) );
+                pr[3] = PPM_GETR( *(ip2) );
+                pg[3] = PPM_GETG( *(ip2) );
+                pb[3] = PPM_GETB( *(ip2) );
+                pr[2] = PPM_GETR( *(ip2-no) );
+                pg[2] = PPM_GETG( *(ip2-no) );
+                pb[2] = PPM_GETB( *(ip2-no) );
+                pr[4] = PPM_GETR( *(ip2+po) );
+                pg[4] = PPM_GETG( *(ip2+po) );
+                pb[4] = PPM_GETB( *(ip2+po) );
+                pr[6] = PPM_GETR( *(ip0+po) );
+                pg[6] = PPM_GETG( *(ip0+po) );
+                pb[6] = PPM_GETB( *(ip0+po) );
+                pr[8] = PPM_GETR( *(ip0-no) );
+                pg[8] = PPM_GETG( *(ip0-no) );
+                pb[8] = PPM_GETB( *(ip0-no) );
+                pr[7] = PPM_GETR( *(ip0) );
+                pg[7] = PPM_GETG( *(ip0) );
+                pb[7] = PPM_GETB( *(ip0) );
+                r = (*atfunc)(pr);
+                g = (*atfunc)(pg);
+                b = (*atfunc)(pb);
+                PPM_ASSIGN( *op, r, g, b );
+            }
+            pnm_writepnmrow( stdout, orow, cols, omaxval, oformat, 0 );
+            if (irow1 == irows[2]) {
+                irow1 = irows[0];
+                irow2 = irows[1];
+                irow0 = irows[2];
+            } else if (irow1 == irows[1]) {
+                irow2 = irows[0];
+                irow0 = irows[1];
+                irow1 = irows[2];
+            }
+            else    /* must be at irows[0] */
+            {
+                irow0 = irows[0];
+                irow1 = irows[1];
+                irow2 = irows[2];
+            }
+        }
+    } else {
+        /* Else must be PGM */
+        int p[9];               /* 3x3 neighbor pixel values */
+        int pv;
+        int promote;
+
+        /* we scale maxval to omaxval */
+        promote = ( PNM_FORMAT_TYPE(format) != PNM_FORMAT_TYPE(oformat) );
+
+        for ( row = 0; row < rows; row++ ) {
+            int po,no;          /* offsets for left and right colums in 3x3 */
+            xel *ip0, *ip1, *ip2, *op;
+
+            if (row == 0) {
+                irow0 = irow1;
+                pnm_readpnmrow( ifp, irow1, cols, maxval, format );
+                if ( promote )
+                    pnm_promoteformatrow( irow1, cols, maxval, 
+                                          format, maxval, oformat );
+            }
+            if (row == (rows-1))
+                irow2 = irow1;
+            else {
+                pnm_readpnmrow( ifp, irow2, cols, maxval, format );
+                if ( promote )
+                    pnm_promoteformatrow( irow2, cols, maxval, 
+                                          format, maxval, oformat );
+            }
+
+            for (col = cols-1,po= col>0?1:0,no=0,
+                     ip0=irow0,ip1=irow1,ip2=irow2,op=orow;
+                 col >= 0;
+                 col--,ip0++,ip1++,ip2++,op++, no |= 1,po = col!= 0 ? po : 0) {
+                /* grab 3x3 pixel values */
+                p[0] = PNM_GET1( *ip1 );
+                p[1] = PNM_GET1( *(ip1-no) );
+                p[5] = PNM_GET1( *(ip1+po) );
+                p[3] = PNM_GET1( *(ip2) );
+                p[2] = PNM_GET1( *(ip2-no) );
+                p[4] = PNM_GET1( *(ip2+po) );
+                p[6] = PNM_GET1( *(ip0+po) );
+                p[8] = PNM_GET1( *(ip0-no) );
+                p[7] = PNM_GET1( *(ip0) );
+                pv = (*atfunc)(p);
+                PNM_ASSIGN1( *op, pv );
+            }
+            pnm_writepnmrow( stdout, orow, cols, omaxval, oformat, 0 );
+            if (irow1 == irows[2]) {
+                irow1 = irows[0];
+                irow2 = irows[1];
+                irow0 = irows[2];
+            } else if (irow1 == irows[1]) {
+                irow2 = irows[0];
+                irow0 = irows[1];
+                irow1 = irows[2];
+            } else {
+                /* must be at irows[0] */
+                irow0 = irows[0];
+                irow1 = irows[1];
+                irow2 = irows[2];
+            }
+        }
+    }
+}
+
+
+
+static void
+verifySame(unsigned int const imageSeq, 
+           int const imageCols, int const imageRows,
+           xelval const imageMaxval, int const imageFormat,
+           int const cols, int const rows,
+           xelval const maxval, int const format) {
+/*----------------------------------------------------------------------------
+   Issue error message and exit the program if the imageXXX arguments don't
+   match the XXX arguments.
+-----------------------------------------------------------------------------*/
+    if (imageCols != cols)
+        pm_error("Width of Image %u (%d) is not the same as Image 0 (%d)",
+                 imageSeq, imageCols, cols);
+    if (imageRows != rows)
+        pm_error("Height of Image %u (%d) is not the same as Image 0 (%d)",
+                 imageSeq, imageRows, rows);
+    if (imageMaxval != maxval)
+        pm_error("Maxval of Image %u (%u) is not the same as Image 0 (%u)",
+                 imageSeq, imageMaxval, maxval);
+    if (imageFormat != format)
+        pm_error("Format of Image %u is not the same as Image 0",
+                 imageSeq);
+}
+
+
+
+int (*atfuncs[6]) (int *) = {atfilt0,atfilt1,atfilt2,atfilt3,atfilt4,atfilt5};
+
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE * ifp;
+	bool eof;  /* We've hit the end of the input stream */
+    unsigned int imageSeq;  /* Sequence number of image, starting from 0 */
+
+    const char* const usage = "alpha radius pnmfile\n"
+        "0.0 <= alpha <= 0.5 for alpha trimmed mean -or- \n"
+        "1.0 <= alpha <= 2.0 for optimal estimation -or- \n"
+        "-0.1 >= alpha >= -0.9 for edge enhancement\n"
+        "0.3333 <= radius <= 1.0 specify effective radius\n";
+
+    pnm_init( &argc, argv );
+
+    if ( argc < 3 || argc > 4 )
+        pm_usage( usage );
+
+    if ( sscanf( argv[1], "%lf", &alpha ) != 1 )
+        pm_usage( usage );
+    if ( sscanf( argv[2], "%lf", &radius ) != 1 )
+        pm_usage( usage );
+        
+    if ((alpha > -0.1 && alpha < 0.0) || (alpha > 0.5 && alpha < 1.0))
+        pm_error( "Alpha must be in range 0.0 <= alpha <= 0.5 "
+                  "for alpha trimmed mean" );
+    if (alpha > 2.0)
+        pm_error( "Alpha must be in range 1.0 <= alpha <= 2.0 "
+                  "for optimal estimation" );
+    if (alpha < -0.9 || (alpha > -0.1 && alpha < 0.0))
+        pm_error( "Alpha must be in range -0.9 <= alpha <= -0.1 "
+                  "for edge enhancement" );
+    if (radius < 0.333 || radius > 1.0)
+        pm_error( "Radius must be in range 0.333333333 <= radius <= 1.0" );
+
+    if ( argc == 4 )
+        ifp = pm_openr( argv[3] );
+    else
+        ifp = stdin;
+        
+    pnm_readpnminit( ifp, &cols, &rows, &maxval, &format );
+        
+    if (maxval > MXIVAL) 
+        pm_error("The maxval of the input image (%d) is too large.\n"
+                 "This program's limit is %d.", 
+                 maxval, MXIVAL);
+        
+    oformat = PNM_FORMAT_TYPE(format);
+    /* force output to max precision without forcing new 2-byte format */
+    omaxval = MIN(maxval, PPM_MAXMAXVAL);
+        
+    atfunc = atfuncs[atfilt_setup(alpha, radius,
+                                  (double)omaxval/(double)maxval)];
+
+    if ( oformat < PGM_TYPE ) {
+        oformat = RPGM_FORMAT;
+        pm_message( "promoting file to PGM" );
+    }
+
+    orow = pnm_allocrow(cols);
+    irows[0] = pnm_allocrow(cols);
+    irows[1] = pnm_allocrow(cols);
+    irows[2] = pnm_allocrow(cols);
+    irow0 = irows[0];
+    irow1 = irows[1];
+    irow2 = irows[2];
+
+    eof = FALSE;  /* We're already in the middle of the first image */
+    imageSeq = 0;
+    while (!eof) {
+        do_one_frame(ifp);
+        pm_nextimage(ifp, &eof);
+        if (!eof) {
+            /* Read and validate header of next image */
+            int imageCols, imageRows;
+            xelval imageMaxval;
+            int imageFormat;
+
+            ++imageSeq;
+            pnm_readpnminit(ifp, &imageCols, &imageRows, 
+                            &imageMaxval, &imageFormat);
+            verifySame(imageSeq,
+                       imageCols, imageRows, imageMaxval, imageFormat,
+                       cols, rows, maxval, format);
+        }
+    }
+
+    pnm_freerow(irow0);
+    pnm_freerow(irow1);
+    pnm_freerow(irow2);
+    pnm_freerow(orow);
+    pm_close(ifp);
+
+    return 0;
+}
+
+
diff --git a/editor/pnmnorm.c b/editor/pnmnorm.c
new file mode 100644
index 00000000..51d954a8
--- /dev/null
+++ b/editor/pnmnorm.c
@@ -0,0 +1,620 @@
+/******************************************************************************
+                                  Pnmnorm
+*******************************************************************************
+
+  This program normalizes the contrast in a Netpbm image.
+
+  by Bryan Henderson bryanh@giraffe-data.com San Jose CA March 2002.
+  Adapted from Ppmnorm.
+
+  Ppmnorm is by Wilson H. Bent, Jr. (whb@usc.edu)
+  Extensively hacked from pgmnorm.c, which carries the following note:
+  
+  Copyright (C) 1989, 1991 by Jef Poskanzer.
+  
+  Permission to use, copy, modify, and distribute this software and its
+  documentation for any purpose and without fee is hereby granted, provided
+  that the above copyright notice appear in all copies and that both that
+  copyright notice and this permission notice appear in supporting
+  documentation.  This software is provided "as is" without express or
+  implied warranty.
+  
+  (End of note from pgmnorm.c)
+
+  Pgmnorm's man page also said:
+  
+  Partially based on the fbnorm filter in Michael Mauldin's "Fuzzy Pixmap"
+  package.
+*****************************************************************************/
+
+#include <assert.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+enum brightMethod {BRIGHT_LUMINOSITY, BRIGHT_COLORVALUE, BRIGHT_SATURATION};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+    unsigned int bvalueSpec;
+    xelval bvalue;
+    unsigned int bpercentSpec;
+    float bpercent;
+    unsigned int wvalueSpec;
+    xelval wvalue;
+    unsigned int wpercentSpec;
+    float wpercent;
+    enum brightMethod brightMethod;
+    unsigned int keephues;
+    float maxExpansion;
+        /* The maximum allowed expansion factor for expansion specified
+           by per centile.  This is a factor, not a per cent increase.
+           E.g. 50% increase means a factor of 1.50.
+        */
+};
+
+
+
+static void
+parseCommandLine (int argc, char ** argv,
+                  struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int luminosity, colorvalue, saturation;
+    unsigned int maxexpandSpec;
+    float maxexpand;
+    
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "bpercent",      OPT_FLOAT,   
+            &cmdlineP->bpercent,   &cmdlineP->bpercentSpec, 0);
+    OPTENT3(0,   "wpercent",      OPT_FLOAT,   
+            &cmdlineP->wpercent,   &cmdlineP->wpercentSpec, 0);
+    OPTENT3(0,   "bvalue",        OPT_UINT,   
+            &cmdlineP->bvalue,     &cmdlineP->bvalueSpec, 0);
+    OPTENT3(0,   "wvalue",        OPT_UINT,   
+            &cmdlineP->wvalue,     &cmdlineP->wvalueSpec, 0);
+    OPTENT3(0,   "maxexpand",     OPT_FLOAT,   
+            &maxexpand,            &maxexpandSpec, 0);
+    OPTENT3(0,   "keephues",      OPT_FLAG,   
+            NULL,                  &cmdlineP->keephues, 0);
+    OPTENT3(0,   "luminosity",    OPT_FLAG,   
+            NULL,                  &luminosity, 0);
+    OPTENT3(0,   "colorvalue",    OPT_FLAG,   
+            NULL,                  &colorvalue, 0);
+    OPTENT3(0,   "saturation",    OPT_FLAG,   
+            NULL,                  &saturation, 0);
+    OPTENT3(0,   "brightmax",     OPT_FLAG,   
+            NULL,                  &colorvalue, 0);
+
+    /* Note: -brightmax was documented and accepted long before it was
+       actually implemented.  By the time we implemented it, we
+       decided -colorvalue was a better name for it.
+    */
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (!cmdlineP->wpercentSpec)
+        cmdlineP->wpercent = 1.0;
+    if (!cmdlineP->bpercentSpec)
+        cmdlineP->bpercent = 2.0;
+
+    if (cmdlineP->wpercent < 0.0)
+        pm_error("You specified a negative value for wpercent: %f",
+                 cmdlineP->wpercent);
+    if (cmdlineP->bpercent < 0.0)
+        pm_error("You specified a negative value for bpercent: %f",
+                 cmdlineP->bpercent);
+    if (cmdlineP->wpercent > 100.0)
+        pm_error("You specified a per centage > 100 for wpercent: %f",
+                 cmdlineP->wpercent);
+    if (cmdlineP->bpercent > 100.0)
+        pm_error("You specified a per centage > 100 for bpercent: %f",
+                 cmdlineP->bpercent);
+
+    if (luminosity + colorvalue + saturation > 1)
+        pm_error("You can specify only one of "
+                 "-luminosity, -colorvalue, and -saturation");
+    else {
+        if (colorvalue)
+            cmdlineP->brightMethod = BRIGHT_COLORVALUE;
+        else if (saturation)
+            cmdlineP->brightMethod = BRIGHT_SATURATION;
+        else
+            cmdlineP->brightMethod = BRIGHT_LUMINOSITY;
+    }
+    if (maxexpandSpec) {
+        if (maxexpand < 0)
+            pm_error("-maxexpand must be positive.  You specified %f",
+                     maxexpand);
+        else
+            cmdlineP->maxExpansion = 1 + (float)maxexpand/100;
+    } else
+        cmdlineP->maxExpansion = 1e6;  /* essentially infinite */
+
+    if (argc-1 > 1)
+        pm_error("Program takes at most one argument: the input file "
+                 "specification.  "
+                 "You specified %d arguments.", argc-1);
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+static void
+buildHistogram(FILE *   const ifp, 
+               int      const cols,
+               int      const rows,
+               xelval   const maxval,
+               int      const format,
+               unsigned int   hist[],
+               enum brightMethod const brightMethod) {
+/*----------------------------------------------------------------------------
+   Build the histogram of brightness values for the image that is in file
+   'ifp', which is positioned just after the header (at the raster).
+
+   The histogram is the array hist[] such that hist[x] is the number
+   of xels in the image that have brightness x.  That brightness is
+   either the color value (intensity of most intense component) of the
+   xel or it is the luminosity of the xel, depending on
+   'brightMethod'.  In either case, it is based on the same maxval as
+   the image, which is 'maxval'.  The image is 'cols' columns wide by
+   'rows' rows high.
+
+   Leave the file positioned arbitrarily.
+-----------------------------------------------------------------------------*/
+    int row;
+    xel * xelrow;
+    
+    xelrow = pnm_allocrow(cols);
+
+    {
+        unsigned int i;
+        for (i = 0; i <= maxval; ++i)
+            hist[i] = 0;
+    }
+    for (row = 0; row < rows; ++row) {
+        int col;
+        pnm_readpnmrow(ifp, xelrow, cols, maxval, format);
+        for (col = 0; col < cols; ++col) {
+            xelval brightness;
+            xel const p = xelrow[col];
+            if (PNM_FORMAT_TYPE(format) == PPM_TYPE) {
+                switch(brightMethod) {
+                case BRIGHT_LUMINOSITY:
+                    brightness = PPM_LUMIN(p);
+                    break;
+                case BRIGHT_COLORVALUE:
+                    brightness = ppm_colorvalue(p);
+                    break;
+                case BRIGHT_SATURATION:
+                    brightness = ppm_saturation(p, maxval);
+                    break;
+                }
+            } else
+                brightness = PNM_GET1(p);
+            ++hist[brightness];
+        }
+    }
+    pnm_freerow(xelrow);
+}
+
+
+
+static void
+computeBottomPercentile(unsigned int         hist[], 
+                        unsigned int   const highest,
+                        unsigned int   const total,
+                        float          const percent, 
+                        unsigned int * const percentileP) {
+/*----------------------------------------------------------------------------
+   Compute the lowest index of hist[] such that the sum of the hist[]
+   values with that index and lower represent at least 'percent' per cent of
+   'n' (which is assumed to be the sum of all the values in hist[],
+   given to us to save us the time of computing it).
+-----------------------------------------------------------------------------*/
+    unsigned int cutoff = total * percent / 100.0;
+    unsigned int count;
+    unsigned int percentile;
+
+    percentile = 0; /* initial value */
+    count = hist[0];  /* initial value */
+
+    while (count < cutoff) {
+        if (percentile == highest)
+            pm_error("Internal error: computeBottomPercentile() received"
+                     "a 'total' value greater than the sum of the hist[]"
+                     "values");
+        ++percentile;
+        count += hist[percentile];
+    }        
+    *percentileP = percentile;
+}
+
+
+
+static void
+computeTopPercentile(unsigned int         hist[], 
+                     unsigned int   const highest, 
+                     unsigned int   const total,
+                     float          const percent, 
+                     unsigned int * const percentileP) {
+/*----------------------------------------------------------------------------
+   Compute the highest index of hist[] such that the sum of the hist[]
+   values with that index and higher represent 'percent' per cent of
+   'n' (which is assumed to be the sum of all the values in hist[],
+   given to us to save us the time of computing it).
+-----------------------------------------------------------------------------*/
+    unsigned int cutoff = total * percent / 100.0;
+    unsigned int count;
+    unsigned int percentile;
+
+    percentile = highest; /* initial value */
+    count = hist[highest];
+
+    while (count < cutoff) {
+        --percentile;
+        count += hist[percentile];
+    }
+    *percentileP = percentile;
+}
+
+
+
+static void
+computeAdjustmentForExpansionLimit(xelval   const maxval,
+                                   xelval   const unlBvalue,
+                                   xelval   const unlWvalue,
+                                   float    const maxExpansion,
+                                   xelval * const bLowerP,
+                                   xelval * const wRaiseP) {
+/*----------------------------------------------------------------------------
+   Assuming 'unlBvalue' and 'unlWvalue' are the appropriate bvalue and
+   wvalue to normalize the image to 0 .. maxval, compute the amount
+   by which the bvalue must be raised and the wvalue lowered from that
+   in order to cap the expansion factor at 'maxExpansion'.
+
+   E.g. if 'maxval' is 100, 'unlBvalue' is 20 and 'unlWvalue' is 70, that
+   implies an expansion factor of 100/50 (because the range goes from
+   70-20, which is 50, to 100 - 0, which is 100).  If 'maxEpansion' is
+   1.333, these values are unacceptable.  To get down to the desired 1.333
+   factor, we need the span of bvalue to wvalue to be 75, not 50.  So
+   we need to raise the bvalue and lower the wvalue by a total of 25.
+   We apportion that adjustment to bvalue and wvalue in proportion to
+   how close each is already to it's end (which we call the margin).
+   'unlBvalue' is 20 from its end, while 'unlWvalue' is 30 from its end,
+   so we want to lower the bvalue by 10 and raise the wvalue by 15.
+   Ergo we return *bLowerP = 10 and *wRaise = 15.
+-----------------------------------------------------------------------------*/
+    unsigned int const newRange = maxval - 0;
+        /* The range of sample values after normalization, if we used
+           the unlimited bvalue and wvalue
+        */
+    unsigned int const oldRange = unlWvalue - unlBvalue;
+        /* The range of sample values in the original image that normalize
+           to 0 .. maxval, if we used the unlimited bvalue and wvalue
+        */
+    float const unlExpansion = (float)newRange/oldRange;
+    
+    if (unlExpansion <= maxExpansion) {
+        /* No capping is necessary.  Unlimited values are already within
+           range.
+           */
+        *bLowerP = 0;
+        *wRaiseP = 0;
+    } else {
+        unsigned int const totalWidening = newRange/maxExpansion - oldRange;
+            /* Amount by which the (bvalue, wvalue) range must be widened
+               to limit expansion to 'maxExpansion'
+            */
+        unsigned int const bMargin = unlBvalue - 0;
+        unsigned int const wMargin = maxval - unlWvalue;
+
+        /* Apportion 'totalWidening' between the black and and the
+           white end
+        */
+        *bLowerP =
+            ROUNDU((float)bMargin / (bMargin + wMargin) * totalWidening);
+        *wRaiseP =
+            ROUNDU((float)wMargin / (bMargin + wMargin) * totalWidening);
+
+        pm_message("limiting expansion of %.1f%% to %.1f%%",
+                   (unlExpansion - 1) * 100, (maxExpansion -1) * 100);
+    }
+}
+
+
+
+static void
+computeEndValues(FILE *             const ifp,
+                 int                const cols,
+                 int                const rows,
+                 xelval             const maxval,
+                 int                const format,
+                 struct cmdlineInfo const cmdline,
+                 xelval *           const bvalueP,
+                 xelval *           const wvalueP) {
+/*----------------------------------------------------------------------------
+   Figure out what original values will be translated to full white and
+   full black -- thus defining to what all the other values get translated.
+
+   This may involve looking at the image.  The image is in the file
+   'ifp', which is positioned just past the header (at the raster).
+   Leave it positioned arbitrarily.
+-----------------------------------------------------------------------------*/
+    unsigned int * hist;  /* malloc'ed */
+
+    MALLOCARRAY(hist, PNM_OVERALLMAXVAL+1);
+
+    if (hist == NULL)
+        pm_error("Unable to allocate storage for intensity histogram.");
+    else {
+        xelval unlimitedBvalue, unlimitedWvalue;
+        unsigned int bLower, wRaise;
+
+        buildHistogram(ifp, cols, rows, maxval, format, hist,
+                       cmdline.brightMethod);
+
+        if (cmdline.bvalueSpec && !cmdline.bpercentSpec) {
+            unlimitedBvalue = cmdline.bvalue;
+        } else {
+            xelval percentBvalue;
+            computeBottomPercentile(hist, maxval, cols*rows, cmdline.bpercent, 
+                                    &percentBvalue);
+            if (cmdline.bvalueSpec)
+                unlimitedBvalue = MIN(percentBvalue, cmdline.bvalue);
+            else
+                unlimitedBvalue = percentBvalue;
+        }
+
+        if (cmdline.wvalueSpec && !cmdline.wpercentSpec) {
+            unlimitedWvalue = cmdline.wvalue;
+        } else {
+            xelval percentWvalue;
+            computeTopPercentile(hist, maxval, cols*rows, cmdline.wpercent, 
+                                 &percentWvalue);
+            if (cmdline.wvalueSpec)
+                unlimitedWvalue = MIN(percentWvalue, cmdline.wvalue);
+            else
+                unlimitedWvalue = percentWvalue;
+        }
+
+        computeAdjustmentForExpansionLimit(
+            maxval, unlimitedBvalue, unlimitedWvalue, cmdline.maxExpansion,
+            &bLower, &wRaise);
+
+        *bvalueP = unlimitedBvalue - bLower;
+        *wvalueP = unlimitedWvalue + wRaise;
+
+        free(hist);
+    }
+}
+
+
+
+static void
+computeTransferFunction(xelval            const bvalue, 
+                        xelval            const wvalue,
+                        xelval            const maxval,
+                        xelval **         const newBrightnessP) {
+/*----------------------------------------------------------------------------
+   Compute the transfer function, i.e. the array *newBrightnessP such that
+   (*newBrightnessP)[x] is the brightness of the xel that should replace a
+   xel with brightness x.  Brightness in this case means either luminosity
+   or color value (and it doesn't matter to us which).
+
+   'bvalue' is the highest brightness that should map to zero brightness;
+   'wvalue' is the lowest brightness that should map to full brightness.
+   brightnesses in between should be stretched linearly.  (That stretching
+   could conceivably result in more brightnesses mapping to zero and full
+   brightness, due to rounding).
+
+   Define function only for values 0..maxval.
+-----------------------------------------------------------------------------*/
+    xelval * newBrightness;
+    xelval i;
+
+    MALLOCARRAY(newBrightness, maxval+1);
+
+    if (newBrightness == NULL)
+        pm_error("Unable to allocate memory for transfer function.");
+
+    /* Clip the lowest brightnesses to zero */
+    if (bvalue > 0) 
+        for (i = 0; i < bvalue; ++i)
+            newBrightness[i] = 0;
+
+    /* Map the middle brightnesses linearly onto 0..maxval */
+    {
+        unsigned int const range = wvalue - bvalue;
+        unsigned int val;
+        /* The following for loop is a hand optimization of this one:
+           for (i = bvalue; i <= wvalue; ++i)
+             newBrightness[i] = (i-bvalue)*maxval/range);
+           (with proper rounding)
+        */
+        for (i = bvalue, val = range/2; 
+             i <= wvalue; 
+             ++i, val += maxval)
+            newBrightness[i] = MIN(val / range, maxval);
+
+        assert(newBrightness[bvalue] == 0);
+        assert(newBrightness[wvalue] == maxval);
+    }
+
+    /* Clip the highest brightnesses to maxval */
+    for (i = wvalue+1; i <= maxval; ++i)
+        newBrightness[i] = maxval;
+
+    *newBrightnessP = newBrightness;
+}
+            
+
+
+static float
+brightScaler(xel               const p,
+             pixval            const maxval,
+             xelval            const newBrightness[],
+             enum brightMethod const brightMethod) {
+/*----------------------------------------------------------------------------
+  Return the multiple by which the brightness pixel of color 'p' (based
+  on maxval 'maxval') should be changed according to the transfer
+  function newBrightness[], using the 'brightMethod' measure of
+  brightness.
+
+  For example, if 'brightMethod' is BRIGHT_LUMINOSITY, p is has
+  luminosity 50, and newBrightness[50] is 75, we would return 1.5.
+-----------------------------------------------------------------------------*/
+    xelval oldBrightness;
+    float scaler;
+             
+    switch (brightMethod) {
+    case BRIGHT_LUMINOSITY:
+        oldBrightness = PPM_LUMIN(p);
+        break;
+    case BRIGHT_COLORVALUE:
+        oldBrightness = ppm_colorvalue(p);
+        break;
+    case BRIGHT_SATURATION:
+        oldBrightness = ppm_saturation(p, maxval);
+        break;
+    }
+    if (oldBrightness == 0) {
+        assert(newBrightness[oldBrightness] == 0);
+        /* Doesn't matter what we scale by.  zero times anything is zero. */
+        scaler = 1.0;
+    } else
+        scaler = (float)newBrightness[oldBrightness]/oldBrightness;
+
+    return scaler;
+}
+            
+
+
+static void
+writeRowNormalized(xel *             const xelrow,
+                   int               const cols,
+                   xelval            const maxval,
+                   int               const format,
+                   enum brightMethod const brightMethod,
+                   bool              const keephues,
+                   xelval            const newBrightness[],
+                   xel *             const rowbuf) {
+/*----------------------------------------------------------------------------
+   Write to Standard Output a normalized version of the xel row 
+   'xelrow'.  Normalize it via the transfer function newBrightness[].
+
+   Use 'rowbuf' as a work buffer.  It is at least 'cols' columns wide.
+-----------------------------------------------------------------------------*/
+    xel * const outrow = rowbuf;
+                
+    unsigned int col;
+    for (col = 0; col < cols; ++col) {
+        xel const p = xelrow[col];
+
+        if (PPM_FORMAT_TYPE(format) == PPM_TYPE) {
+            if (keephues) {
+                float const scaler =
+                    brightScaler(p, maxval, newBrightness, brightMethod);
+
+                xelval const r = MIN((int)(PPM_GETR(p)*scaler+0.5), maxval);
+                xelval const g = MIN((int)(PPM_GETG(p)*scaler+0.5), maxval);
+                xelval const b = MIN((int)(PPM_GETB(p)*scaler+0.5), maxval);
+                PNM_ASSIGN(outrow[col], r, g, b);
+            } else 
+                PNM_ASSIGN(outrow[col], 
+                           newBrightness[PPM_GETR(p)], 
+                           newBrightness[PPM_GETG(p)], 
+                           newBrightness[PPM_GETB(p)]);
+        } else 
+            PNM_ASSIGN1(outrow[col], newBrightness[PNM_GET1(p)]);
+    }
+    pnm_writepnmrow(stdout, outrow, cols, maxval, format, 0);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE *ifP;
+    pm_filepos imagePos;
+    xelval maxval;
+    int rows, cols, format;
+    xelval bvalue, wvalue;
+    
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr_seekable(cmdline.inputFilespec);
+
+    /* Rescale so that bvalue maps to 0, wvalue maps to maxval. */
+    pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
+    pm_tell2(ifP, &imagePos, sizeof(imagePos));
+
+    computeEndValues(ifP, cols, rows, maxval, format, cmdline, 
+                     &bvalue, &wvalue);
+        
+    if (wvalue <= bvalue)
+        pm_error("The colors which become black would overlap the "
+                 "colors which become white.");
+    else {
+        xelval * newBrightness;
+        int row;
+        xel * xelrow;
+        xel * rowbuf;
+        
+        xelrow = pnm_allocrow(cols);
+
+        pm_message("remapping %d..%d to %d..%d", bvalue, wvalue, 0, maxval);
+
+        computeTransferFunction(bvalue, wvalue, maxval, &newBrightness);
+
+        pm_seek2(ifP, &imagePos, sizeof(imagePos));
+        pnm_writepnminit(stdout, cols, rows, maxval, format, 0);
+
+        rowbuf = pnm_allocrow(cols);
+
+        for (row = 0; row < rows; ++row) {
+            pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+            writeRowNormalized(xelrow, cols, maxval, format,
+                               cmdline.keephues, cmdline.brightMethod,
+                               newBrightness, rowbuf);
+        }
+        free(newBrightness);
+        pnm_freerow(rowbuf);
+        pnm_freerow(xelrow);
+    }
+    pm_close(ifP);
+    return 0;
+}
diff --git a/editor/pnmpad.c b/editor/pnmpad.c
new file mode 100644
index 00000000..e1fbdaec
--- /dev/null
+++ b/editor/pnmpad.c
@@ -0,0 +1,387 @@
+/* pnmpad.c - add border to sides of a portable anymap
+ ** AJCD 4/9/90
+ */
+
+/*
+ * Changelog
+ *
+ * 2002/01/25 - Rewrote options parsing code.
+ *      Added pad-to-width and pad-to-height with custom
+ *      alignment.  MVB.
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec;  /* Filespecs of input files */
+    unsigned int xsize;
+    unsigned int xsizeSpec;
+    unsigned int ysize;
+    unsigned int ysizeSpec;
+    unsigned int left;
+    unsigned int right;
+    unsigned int top;
+    unsigned int bottom;
+    unsigned int leftSpec;
+    unsigned int rightSpec;
+    unsigned int topSpec;
+    unsigned int bottomSpec;
+    float xalign;
+    float yalign;
+    unsigned int white;     /* >0: pad white; 0: pad black */
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int blackOpt;
+    unsigned int xalignSpec, yalignSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "xsize",     OPT_UINT,    &cmdlineP->xsize,       
+            &cmdlineP->xsizeSpec, 0);
+    OPTENT3(0,   "width",     OPT_UINT,    &cmdlineP->xsize,
+            &cmdlineP->xsizeSpec, 0);
+    OPTENT3(0,   "ysize",     OPT_UINT,    &cmdlineP->ysize,
+            &cmdlineP->ysizeSpec, 0);
+    OPTENT3(0,   "height",    OPT_UINT,    &cmdlineP->ysize,
+            &cmdlineP->ysizeSpec, 0);
+    OPTENT3(0,   "left",      OPT_UINT,    &cmdlineP->left, 
+            &cmdlineP->leftSpec, 0);
+    OPTENT3(0,   "right",     OPT_UINT,    &cmdlineP->right, 
+            &cmdlineP->rightSpec, 0);
+    OPTENT3(0,   "top",       OPT_UINT,    &cmdlineP->top, 
+            &cmdlineP->topSpec, 0);
+    OPTENT3(0,   "bottom",    OPT_UINT,    &cmdlineP->bottom, 
+            &cmdlineP->bottomSpec, 0);
+    OPTENT3(0,   "xalign",    OPT_FLOAT,   &cmdlineP->xalign,
+            &xalignSpec,           0);
+    OPTENT3(0,   "halign",    OPT_FLOAT,   &cmdlineP->xalign,
+            &xalignSpec,           0);
+    OPTENT3(0,   "yalign",    OPT_FLOAT,   &cmdlineP->yalign,
+            &yalignSpec,           0);
+    OPTENT3(0,   "valign",    OPT_FLOAT,   &cmdlineP->yalign,
+            &yalignSpec,           0);
+    OPTENT3(0,   "black",     OPT_FLAG,    NULL, 
+            &blackOpt,           0);
+    OPTENT3(0,   "white",     OPT_FLAG,    NULL,
+            &cmdlineP->white,    0);
+    OPTENT3(0,   "verbose",   OPT_FLAG,    NULL,
+            &cmdlineP->verbose,  0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof opt, 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (blackOpt && cmdlineP->white)
+        pm_error("You cannot specify both -black and -white");
+
+    if (xalignSpec && (cmdlineP->leftSpec || cmdlineP->rightSpec))
+        pm_error("You cannot specify both -xalign and -left or -right");
+
+    if (yalignSpec && (cmdlineP->topSpec || cmdlineP->bottomSpec))
+        pm_error("You cannot specify both -yalign and -top or -bottom");
+
+    if (xalignSpec && !cmdlineP->xsizeSpec)
+        pm_error("-xalign is meaningless without -width");
+
+    if (yalignSpec && !cmdlineP->ysizeSpec)
+        pm_error("-yalign is meaningless without -height");
+
+    if (xalignSpec) {
+        if (cmdlineP->xalign < 0)
+            pm_error("You have specified a negative -halign value (%f)", 
+                     cmdlineP->xalign);
+        if (cmdlineP->xalign > 1)
+            pm_error("You have specified a -halign value (%f) greater than 1", 
+                     cmdlineP->xalign);
+    } else
+        cmdlineP->xalign = 0.5;
+
+    if (yalignSpec) {
+        if (cmdlineP->yalign < 0)
+            pm_error("You have specified a negative -halign value (%f)", 
+                     cmdlineP->yalign);
+        if (cmdlineP->yalign > 1)
+            pm_error("You have specified a -valign value (%f) greater than 1", 
+                     cmdlineP->yalign);
+    } else
+        cmdlineP->yalign = 0.5;
+
+    /* get optional input filename */
+    if (argc-1 > 1)
+        pm_error("This program takes at most 1 parameter.  You specified %d",
+                 argc-1);
+    else if (argc-1 == 1) 
+        cmdlineP->input_filespec = argv[1];
+    else 
+        cmdlineP->input_filespec = "-";
+}
+
+
+
+static void
+parseCommandLineOld(int argc, char ** argv,
+                    struct cmdlineInfo * const cmdlineP) {
+
+    /* This syntax was abandonned in February 2002. */
+    pm_message("Warning: old style options are deprecated!");
+
+    cmdlineP->xsize = cmdlineP->ysize = 0;
+    cmdlineP->left = cmdlineP->right = cmdlineP->top = cmdlineP->bottom = 0;
+    cmdlineP->xalign = cmdlineP->yalign = 0.5;
+    cmdlineP->white = cmdlineP->verbose = FALSE;
+
+    while (argc >= 2 && argv[1][0] == '-') {
+        if (strcmp(argv[1]+1,"black") == 0) cmdlineP->white = FALSE;
+        else if (strcmp(argv[1]+1,"white") == 0) cmdlineP->white = TRUE;
+        else switch (argv[1][1]) {
+        case 'l':
+            if (atoi(argv[1]+2) < 0)
+                pm_error("left border too small");
+            else
+                cmdlineP->left = atoi(argv[1]+2);
+            break;
+        case 'r':
+            if (atoi(argv[1]+2) < 0)
+                pm_error("right border too small");
+            else
+                cmdlineP->right = atoi(argv[1]+2);
+            break;
+        case 'b':
+            if (atoi(argv[1]+2) < 0)
+                pm_error("bottom border too small");
+            else
+                cmdlineP->bottom = atoi(argv[1]+2);
+            break;
+        case 't':
+            if (atoi(argv[1]+2) < 0)
+                pm_error("top border too small");
+            else
+                cmdlineP->top = atoi(argv[1]+2);
+            break;
+        default:
+            pm_usage("[-white|-black] [-l#] [-r#] [-t#] [-b#] [pnmfile]");
+        }
+        argc--, argv++;
+    }
+
+    cmdlineP->xsizeSpec = (cmdlineP->xsize > 0);
+    cmdlineP->ysizeSpec = (cmdlineP->ysize > 0);
+
+    if (argc > 2)
+        pm_usage("[-white|-black] [-l#] [-r#] [-t#] [-b#] [pnmfile]");
+
+    if (argc == 2)
+        cmdlineP->input_filespec = argv[1];
+    else
+        cmdlineP->input_filespec = "-";
+}
+
+
+
+static void
+computeHorizontalPadSizes(struct cmdlineInfo const cmdline,
+                          int                const cols,
+                          unsigned int *     const lpadP,
+                          unsigned int *     const rpadP) {
+
+    if (cmdline.xsizeSpec) {
+        if (cmdline.leftSpec && cmdline.rightSpec) {
+            if (cmdline.left + cols + cmdline.right < cmdline.xsize) {
+                pm_error("Left padding (%u), and right "
+                         "padding (%u) are insufficient to bring the "
+                         "image width of %d up to %u.",
+                         cmdline.left, cmdline.right, cols, cmdline.xsize);
+            } else {
+                *lpadP = cmdline.left;
+                *rpadP = cmdline.right;
+            }
+        } else if (cmdline.leftSpec) {
+            *lpadP = cmdline.left;
+            *rpadP = MAX(cmdline.xsize, cmdline.left + cols) - 
+                (cmdline.left + cols);
+        } else if (cmdline.rightSpec) {
+            *rpadP = cmdline.right;
+            *lpadP = MAX(cmdline.xsize, cols + cmdline.right) -
+                (cols + cmdline.right);
+        } else {
+            if (cmdline.xsize > cols) {
+                *lpadP = ROUNDU((cmdline.xsize - cols) * cmdline.xalign);
+                *rpadP = cmdline.xsize - cols - *lpadP;
+            } else {
+                *lpadP = 0;
+                *rpadP = 0;
+            }
+        }
+    } else {
+        *lpadP = cmdline.leftSpec ? cmdline.left : 0;
+        *rpadP = cmdline.rightSpec ? cmdline.right : 0;
+    }
+}
+
+
+
+static void
+computeVerticalPadSizes(struct cmdlineInfo const cmdline,
+                        int                const rows,
+                        unsigned int *     const tpadP,
+                        unsigned int *     const bpadP) {
+
+    if (cmdline.ysizeSpec) {
+        if (cmdline.topSpec && cmdline.bottomSpec) {
+            if (cmdline.bottom + rows + cmdline.top < cmdline.ysize) {
+                pm_error("Top padding (%u), and bottom "
+                         "padding (%u) are insufficient to bring the "
+                         "image height of %d up to %u.",
+                         cmdline.top, cmdline.bottom, rows, cmdline.ysize);
+            } else {
+                *tpadP = cmdline.top;
+                *bpadP = cmdline.bottom;
+            }
+        } else if (cmdline.topSpec) {
+            *tpadP = cmdline.top;
+            *bpadP = MAX(cmdline.ysize, cmdline.top + rows) - 
+                (cmdline.top + rows);
+        } else if (cmdline.bottomSpec) {
+            *bpadP = cmdline.bottom;
+            *tpadP = MAX(cmdline.ysize, rows + cmdline.bottom) - 
+                (rows + cmdline.bottom);
+        } else {
+            if (cmdline.ysize > rows) {
+                *bpadP = ROUNDU((cmdline.ysize - rows) * cmdline.yalign);
+                *tpadP = cmdline.ysize - rows - *bpadP;
+            } else {
+                *bpadP = 0;
+                *tpadP = 0;
+            }
+        }
+    } else {
+        *bpadP = cmdline.bottomSpec ? cmdline.bottom : 0;
+        *tpadP = cmdline.topSpec ? cmdline.top : 0;
+    }
+}
+
+
+
+static void
+computePadSizes(struct cmdlineInfo const cmdline,
+                int                const cols,
+                int                const rows,
+                unsigned int *     const lpadP,
+                unsigned int *     const rpadP,
+                unsigned int *     const tpadP,
+                unsigned int *     const bpadP) {
+
+    computeHorizontalPadSizes(cmdline, cols, lpadP, rpadP);
+
+    computeVerticalPadSizes(cmdline, rows, tpadP, bpadP);
+
+    if (cmdline.verbose)
+        pm_message("Padding: left: %u; right: %u; top: %u; bottom: %u",
+                   *lpadP, *rpadP, *tpadP, *bpadP);
+}
+
+
+
+int
+main(int argc, char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE *ifP;
+    xel *xelrow, *bgrow, background;
+    xelval maxval;
+    int rows, cols, newcols, row, col, format;
+    bool depr_cmd; /* use deprecated commandline interface */
+    unsigned int lpad, rpad, tpad, bpad;
+
+    pnm_init( &argc, argv );
+
+    /* detect deprecated options */
+    depr_cmd = FALSE;  /* initial assumption */
+    if (argc > 1 && argv[1][0] == '-') {
+        if (argv[1][1] == 't' || argv[1][1] == 'b'
+            || argv[1][1] == 'l' || argv[1][1] == 'r') {
+            if (argv[1][2] >= '0' && argv[1][2] <= '9')
+                depr_cmd = TRUE;
+        }
+    } 
+    if (argc > 2 && argv[2][0] == '-') {
+        if (argv[2][1] == 't' || argv[2][1] == 'b'
+            || argv[2][1] == 'l' || argv[2][1] == 'r') {
+            if (argv[2][2] >= '0' && argv[2][2] <= '9')
+                depr_cmd = TRUE;
+        }
+    }
+
+    if (depr_cmd) 
+        parseCommandLineOld(argc, argv, &cmdline);
+    else 
+        parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.input_filespec);
+
+    pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
+    if (cmdline.white)
+        background = pnm_whitexel(maxval, format);
+    else
+        background = pnm_blackxel(maxval, format);
+
+    if (cmdline.verbose) pm_message("image WxH = %dx%d", cols, rows);
+
+    computePadSizes(cmdline, cols, rows, &lpad, &rpad, &tpad, &bpad);
+
+    newcols = cols + lpad + rpad;
+    xelrow = pnm_allocrow(newcols);
+    bgrow = pnm_allocrow(newcols);
+
+    for (col = 0; col < newcols; col++)
+        xelrow[col] = bgrow[col] = background;
+
+    pnm_writepnminit(stdout, newcols, rows + tpad + bpad, maxval, format, 0);
+
+    for (row = 0; row < tpad; row++)
+        pnm_writepnmrow(stdout, bgrow, newcols, maxval, format, 0);
+
+    for (row = 0; row < rows; row++) {
+        pnm_readpnmrow(ifP, &xelrow[lpad], cols, maxval, format);
+        pnm_writepnmrow(stdout, xelrow, newcols, maxval, format, 0);
+    }
+
+    for (row = 0; row < bpad; row++)
+        pnm_writepnmrow(stdout, bgrow, newcols, maxval, format, 0);
+
+    pnm_freerow(xelrow);
+    pnm_freerow(bgrow);
+
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/editor/pnmpaste.c b/editor/pnmpaste.c
new file mode 100644
index 00000000..38b316c6
--- /dev/null
+++ b/editor/pnmpaste.c
@@ -0,0 +1,185 @@
+/* pnmpaste.c - paste a rectangle into a portable anymap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pm_c_util.h"
+#include "pnm.h"
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp1;
+    FILE* ifp2;
+    register xel* xelrow1;
+    register xel* xelrow2;
+    register xel* x1P;
+    register xel* x2P;
+    xelval maxval1, maxval2, newmaxval;
+    int argn, rows1, cols1, format1, x, y;
+    int rows2, cols2, format2, newformat, row;
+    register int col;
+    char function;
+    const char* const usage = "[-replace|-or|-and|-xor] frompnmfile x y [intopnmfile]";
+
+    pnm_init( &argc, argv );
+
+    argn = 1;
+    function = 'r';
+
+    /* Check for flags. */
+    if ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+	{
+	if ( pm_keymatch( argv[argn], "-replace", 2 ) )
+	    function = 'r';
+	else if ( pm_keymatch( argv[argn], "-or", 2 ) )
+	    function = 'o';
+	else if ( pm_keymatch( argv[argn], "-and", 2 ) )
+	    function = 'a';
+	else if ( pm_keymatch( argv[argn], "-xor", 2 ) )
+	    function = 'x';
+	else
+	    pm_usage( usage );
+	++argn;
+	}
+
+    if ( argn == argc )
+	pm_usage( usage );
+    ifp1 = pm_openr( argv[argn] );
+    ++argn;
+
+    if ( argn == argc )
+	pm_usage( usage );
+    if ( sscanf( argv[argn], "%d", &x ) != 1 )
+	pm_usage( usage );
+    ++argn;
+    if ( argn == argc )
+	pm_usage( usage );
+    if ( sscanf( argv[argn], "%d", &y ) != 1 )
+	pm_usage( usage );
+    ++argn;
+
+    if ( argn != argc )
+	{
+	ifp2 = pm_openr( argv[argn] );
+	++argn;
+	}
+    else
+	ifp2 = stdin;
+
+    if ( argn != argc )
+	pm_usage( usage );
+
+    pnm_readpnminit( ifp1, &cols1, &rows1, &maxval1, &format1 );
+    xelrow1 = pnm_allocrow(cols1);
+    pnm_readpnminit( ifp2, &cols2, &rows2, &maxval2, &format2 );
+    xelrow2 = pnm_allocrow(cols2);
+
+    if ( x <= -cols2 )
+	pm_error(
+	    "x is too negative -- the second anymap has only %d cols",
+	    cols2 );
+    else if ( x >= cols2 )
+	pm_error(
+	    "x is too large -- the second anymap has only %d cols",
+	    cols2 );
+    if ( y <= -rows2 )
+	pm_error(
+	    "y is too negative -- the second anymap has only %d rows",
+	    rows2 );
+    else if ( y >= rows2 )
+	pm_error(
+	    "y is too large -- the second anymap has only %d rows",
+	    rows2 );
+
+    if ( x < 0 )
+	x += cols2;
+    if ( y < 0 )
+	y += rows2;
+
+    if ( x + cols1 > cols2 )
+	pm_error( "x + width is too large by %d pixels", x + cols1 - cols2 );
+    if ( y + rows1 > rows2 )
+	pm_error( "y + height is too large by %d pixels", y + rows1 - rows2 );
+
+    newformat = MAX( PNM_FORMAT_TYPE(format1), PNM_FORMAT_TYPE(format2) );
+    newmaxval = MAX( maxval1, maxval2 );
+
+    if ( function != 'r' && newformat != PBM_TYPE )
+	pm_error( "no logical operations allowed for non-bitmaps" );
+
+    pnm_writepnminit( stdout, cols2, rows2, newmaxval, newformat, 0 );
+
+    for ( row = 0; row < rows2; ++row )
+	{
+	pnm_readpnmrow( ifp2, xelrow2, cols2, maxval2, format2 );
+	pnm_promoteformatrow( xelrow2, cols2, maxval2, format2,
+	    newmaxval, newformat );
+
+	if ( row >= y && row < y + rows1 )
+	    {
+	    pnm_readpnmrow( ifp1, xelrow1, cols1, maxval1, format1 );
+	    pnm_promoteformatrow( xelrow1, cols1, maxval1, format1,
+		newmaxval, newformat );
+	    for ( col = 0, x1P = xelrow1, x2P = &(xelrow2[x]);
+		  col < cols1; ++col, ++x1P, ++x2P )
+		{
+		register xelval b1, b2;
+
+		switch ( function )
+		    {
+		    case 'r':
+		    *x2P = *x1P;
+		    break;
+
+		    case 'o':
+		    b1 = PNM_GET1( *x1P );
+		    b2 = PNM_GET1( *x2P );
+		    if ( b1 != 0 || b2 != 0 )
+			PNM_ASSIGN1( *x2P, newmaxval );
+		    else
+			PNM_ASSIGN1( *x2P, 0 );
+		    break;
+
+		    case 'a':
+		    b1 = PNM_GET1( *x1P );
+		    b2 = PNM_GET1( *x2P );
+		    if ( b1 != 0 && b2 != 0 )
+			PNM_ASSIGN1( *x2P, newmaxval );
+		    else
+			PNM_ASSIGN1( *x2P, 0 );
+		    break;
+
+		    case 'x':
+		    b1 = PNM_GET1( *x1P );
+		    b2 = PNM_GET1( *x2P );
+		    if ( ( b1 != 0 && b2 == 0 ) || ( b1 == 0 && b2 != 0 ) )
+			PNM_ASSIGN1( *x2P, newmaxval );
+		    else
+			PNM_ASSIGN1( *x2P, 0 );
+		    break;
+
+		    default:
+		    pm_error( "can't happen" );
+		    }
+		}
+	    }
+
+	pnm_writepnmrow( stdout, xelrow2, cols2, newmaxval, newformat, 0 );
+	}
+    
+    pm_close( ifp1 );
+    pm_close( ifp2 );
+    pm_close( stdout );
+
+    exit( 0 );
+    }
diff --git a/editor/pnmquant b/editor/pnmquant
new file mode 100755
index 00000000..175f6906
--- /dev/null
+++ b/editor/pnmquant
@@ -0,0 +1,275 @@
+#!/usr/bin/perl -w
+
+##############################################################################
+#                         pnmquant 
+##############################################################################
+#  By Bryan Henderson, San Jose CA; December 2001.
+#
+#  Contributed to the public domain by its author.
+##############################################################################
+
+use strict;
+use English;
+use Getopt::Long;
+#use File::Temp "tempfile";  # not available before Perl 5.6.1
+use File::Spec;
+#use Fcntl ":seek";  # not available in Perl 5.00503
+use Fcntl;  # gets open flags
+
+my ($TRUE, $FALSE) = (1,0);
+
+my ($SEEK_SET, $SEEK_CUR, $SEEK_END) = (0, 1, 2);
+
+sub tempFile($) {
+
+# Here's what we'd do if we could expect Perl 5.6.1 or later, instead
+# of calling this subroutine:
+#    my ($file, $filename) = tempfile("pnmquant_XXXX", 
+#                                     SUFFIX=>".pnm", 
+#                                     DIR=>File::Spec->tmpdir())
+#                                     UNLINK=>$TRUE);
+    my ($suffix) = @_;
+    my $fileName;
+    local *file;  # For some inexplicable reason, must be local, not my
+    my $i;
+    $i = 0;
+    do {
+        $fileName = File::Spec->tmpdir() . "/pnmquant_" . $i++ . $suffix;
+    } until sysopen(*file, $fileName, O_RDWR|O_CREAT|O_EXCL);
+
+    return(*file, $fileName);
+}
+
+
+
+sub parseCommandLine(@) {
+
+    local @ARGV = @_;  # GetOptions takes input from @ARGV only
+
+    my %cmdline;
+
+    my $validOptions = GetOptions(\%cmdline,
+                                  "center",
+                                  "meancolor",
+                                  "meanpixel",
+                                  "spreadbrightness",
+                                  "spreadluminosity",
+                                  "floyd|fs!",
+                                  "quiet",
+                                  "plain");
+
+    if (!$validOptions) {
+        print(STDERR "Invalid option syntax.\n");
+        exit(1);
+    }
+    if (@ARGV > 2) {
+        print(STDERR "This program takes at most 2 arguments.  You specified ",
+              scalar(@ARGV), "\n");
+        exit(1);
+    } 
+    if (@ARGV < 1) {
+        print(STDERR 
+              "You must specify the number of colors as an argument.\n");
+        exit(1);
+    }
+    my $infile;
+    $cmdline{ncolors} = $ARGV[0];
+    
+    if (!($cmdline{ncolors} =~ m{ ^[[:digit:]]+$ }x ) || 
+        $cmdline{ncolors} == 0) {
+        print(STDERR 
+              "Number of colors argument '$cmdline{ncolors}' " .
+              "is not a positive integer.\n");
+        exit(1);
+    }
+
+    if (@ARGV > 1) {
+        $cmdline{infile} = $ARGV[1];
+    } else {
+        $cmdline{infile} = "-";
+    }
+
+    return(\%cmdline);
+}
+
+
+
+sub setAutoflush($) {
+    my ($fh) = @_;
+
+    # Better would be $fh->autoflush() (with use IO:Handle), but older Perl
+    # doesn't have it.
+
+    my $oldFh = select($fh);
+    $OUTPUT_AUTOFLUSH = $TRUE;
+    select ($oldFh);
+}
+
+
+
+sub openSeekableAsStdin($) {
+    my ($infile) = @_;
+
+    # Open the input file $infile and connect it to Standard Input
+    # (assuming Standard Input is now open as the Perl file handle STDIN).
+    # If $infile is "-", that means just leave Standard Input alone.  But if
+    # Standard Input is not seekable, copy it to a temporary regular
+    # file and return a file handle for that.  I.e. the file handle we
+    # return is guaranteed to be seekable.
+
+    # Note: this all works only because STDIN is already set up on file
+    # descriptor 0.  Otherwise, open(STDIN, ...) would just create a brand
+    # new file handle named STDIN on some arbitrary file descriptor.
+    
+    if ($infile eq "-") {
+        my $seekWorked = sysseek(STDIN, 0, $SEEK_SET);
+        if ($seekWorked) {
+            # STDIN is already as we need it.
+        } else {
+            # It isn't seekable, so we must copy it to a temporary regular file
+            # and return that as the input file.
+            
+            my ($inFh, $inFilename) = tempFile(".pnm");
+            if (!defined($inFh)) {
+                die("Unable to create temporary file.  Errno=$ERRNO");
+            }
+            unlink($inFilename) or
+                die("Unable to unlink temporary file.  Errno=$ERRNO");
+
+            setAutoflush($inFh);
+
+            while (<STDIN>) {
+                print($inFh $_);
+            }
+            sysseek($inFh, 0, $SEEK_SET) 
+                or die("Seek of temporary input file failed!  " .
+                       "Errno = $ERRNO");
+            *INFH = *$inFh;  # Because open() rejects '<&$inFh' 
+            open(STDIN, "<&INFH");
+            tell(INFH);  # Avoids bogus "INFH is not referenced" warning
+        }
+    } else {
+        open(STDIN, "<", $infile) 
+            or die("Unable to open input file '$infile'.  Errno=$ERRNO");
+    }
+}
+
+
+
+sub makeColormap($$$$$) {
+
+    my ($ncolors, $opt_meanpixel, $opt_meancolor, $opt_spreadluminosity,
+        $opt_quiet) = @_;
+
+    # Make a colormap of $ncolors colors from the image on Standard Input.
+    # Put it in a temporary file and return its name.
+
+    my ($mapfileFh, $mapfileSpec) = tempFile(".pnm");
+
+    if (!defined($mapfileFh)) {
+        print(STDERR "Unable to create temporary file for colormap.  " .
+              "errno = $ERRNO\n");
+        exit(1);
+    }
+
+    my $averageOpt;
+    if (defined($opt_meanpixel)) {
+        $averageOpt = "-meanpixel";
+    } elsif (defined($opt_meancolor)) {
+        $averageOpt = "-meancolor";
+    } else {
+        $averageOpt = "-center";
+    }
+
+    my $spreadOpt;
+    if (defined($opt_spreadluminosity)) {
+        $spreadOpt = "-spreadluminosity";
+    } else {
+        $spreadOpt = "-spreadbrightness";
+    }
+
+    my @options;
+    @options = ($averageOpt, $spreadOpt);
+    if (defined($opt_quiet)) {
+        push(@options, '-quiet');
+    }
+
+    open(STDOUT, ">", $mapfileSpec);
+
+    my $maprc = system("pnmcolormap", $ncolors, @options);
+
+    if ($maprc != 0) {
+        print(STDERR "pnmcolormap failed, rc=$maprc\n");
+        exit(1);
+    } 
+    return $mapfileSpec;
+}
+
+
+
+sub remap($$$$) {
+
+    my ($mapfileSpec, $opt_floyd, $opt_plain, $opt_quiet) = @_;
+
+    # Remap the image on Standard Input to Standard Output, using the colors
+    # from the colormap file named $mapfileSpec.
+
+    my @options;
+    @options = ();  # initial value
+
+    if ($opt_floyd) {
+        push(@options, "-floyd");
+    }
+    if ($opt_plain) {
+        push(@options, "-plain");
+    }
+    if ($opt_quiet) {
+        push(@options, "-quiet");
+    }
+
+    my $remaprc = system("pnmremap", "-mapfile=$mapfileSpec", @options);
+    
+    if ($remaprc != 0) {
+        print(STDERR "pnmremap failed, rc=$remaprc\n");
+        exit(1);
+    }
+}
+
+
+
+##############################################################################
+#                              MAIN PROGRAM
+##############################################################################
+
+my $cmdlineR = parseCommandLine(@ARGV);
+
+openSeekableAsStdin($cmdlineR->{infile}); 
+
+# Save Standard Output for our eventual output
+open(OLDOUT, ">&STDOUT");
+select(OLDOUT);  # avoids Perl bug where it says we never use STDOUT 
+
+
+my $mapfileSpec = makeColormap($cmdlineR->{ncolors}, 
+                               $cmdlineR->{meanpixel}, 
+                               $cmdlineR->{meancolor}, 
+                               $cmdlineR->{spreadluminosity},
+                               $cmdlineR->{quiet});
+
+# Note that we use sysseek() instead of seek(), because we're positioning
+# the file to be read by our non-Perl child process, rather than for reading
+# through the Perl I/O layer.
+
+sysseek(STDIN, 0, $SEEK_SET) 
+    or die("seek back to zero on input file failed.");
+
+
+open(STDOUT, ">&OLDOUT");
+
+remap($mapfileSpec, 
+      $cmdlineR->{floyd}, 
+      $cmdlineR->{plain},
+      $cmdlineR->{quiet});
+
+unlink($mapfileSpec) or
+    die("Unable to unlink map file.  Errno=$ERRNO");
diff --git a/editor/pnmremap.c b/editor/pnmremap.c
new file mode 100644
index 00000000..2102fe63
--- /dev/null
+++ b/editor/pnmremap.c
@@ -0,0 +1,872 @@
+/******************************************************************************
+                               pnmremap.c
+*******************************************************************************
+
+  Replace colors in an input image with colors from a given colormap image.
+
+  For PGM input, do the equivalent.
+
+  By Bryan Henderson, San Jose, CA 2001.12.17
+
+  Derived from ppmquant, originally by Jef Poskanzer.
+
+  Copyright (C) 1989, 1991 by Jef Poskanzer.
+  Copyright (C) 2001 by Bryan Henderson.
+
+  Permission to use, copy, modify, and distribute this software and its
+  documentation for any purpose and without fee is hereby granted, provided
+  that the above copyright notice appear in all copies and that both that
+  copyright notice and this permission notice appear in supporting
+  documentation.  This software is provided "as is" without express or
+  implied warranty.
+******************************************************************************/
+
+#include <limits.h>
+#include <math.h>
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "pam.h"
+#include "pammap.h"
+
+#define MAXCOLORS 32767u
+
+enum missingMethod {
+    MISSING_FIRST,
+    MISSING_SPECIFIED,
+    MISSING_CLOSE
+};
+
+#define FS_SCALE 1024
+
+struct fserr {
+    long** thiserr;
+    long** nexterr;
+    bool fsForward;
+};
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilespec;  /* Filespec of input file */
+    const char * mapFilespec;    /* Filespec of colormap file */
+    unsigned int floyd;   /* Boolean: -floyd/-fs option */
+    enum missingMethod missingMethod;
+    char * missingcolor;      
+        /* -missingcolor value.  Null if not specified */
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine (int argc, char ** argv,
+                  struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int nofloyd, firstisdefault;
+    unsigned int missingSpec, mapfileSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+    
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "floyd",        OPT_FLAG,   
+            NULL,                       &cmdlineP->floyd, 0);
+    OPTENT3(0,   "fs",           OPT_FLAG,   
+            NULL,                       &cmdlineP->floyd, 0);
+    OPTENT3(0,   "nofloyd",      OPT_FLAG,   
+            NULL,                       &nofloyd, 0);
+    OPTENT3(0,   "nofs",         OPT_FLAG,   
+            NULL,                       &nofloyd, 0);
+    OPTENT3(0,   "firstisdefault", OPT_FLAG,   
+            NULL,                       &firstisdefault, 0);
+    OPTENT3(0,   "mapfile",      OPT_STRING, 
+            &cmdlineP->mapFilespec,    &mapfileSpec, 0);
+    OPTENT3(0,   "missingcolor", OPT_STRING, 
+            &cmdlineP->missingcolor,   &missingSpec, 0);
+    OPTENT3(0, "verbose",        OPT_FLAG,   NULL,                  
+            &cmdlineP->verbose,        0 );
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    cmdlineP->missingcolor = NULL;  /* default value */
+    
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (cmdlineP->floyd && nofloyd)
+        pm_error("You cannot specify both -floyd and -nofloyd options.");
+
+    if (firstisdefault && missingSpec)
+        pm_error("You cannot specify both -missing and -firstisdefault.");
+
+    if (firstisdefault)
+        cmdlineP->missingMethod = MISSING_FIRST;
+    else if (missingSpec)
+        cmdlineP->missingMethod = MISSING_SPECIFIED;
+    else
+        cmdlineP->missingMethod = MISSING_CLOSE;
+
+    if (!mapfileSpec)
+        pm_error("You must specify the -mapfile option.");
+
+    if (argc-1 > 1)
+        pm_error("Program takes at most one argument: the input file "
+                 "specification.  "
+                 "You specified %d arguments.", argc-1);
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+static void
+rgbToDepth1(const struct pam * const pamP,
+            tuple *            const tupleRow) {
+    
+    unsigned int col;
+
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        double grayvalue;
+        grayvalue = 0.0;  /* initial value */
+        for (plane = 0; plane < pamP->depth; ++plane)
+            grayvalue += pnm_lumin_factor[plane] * tupleRow[col][plane];
+        tupleRow[col][0] = (sample) (grayvalue + 0.5);
+    }
+}
+
+
+
+static void
+grayscaleToDepth3(const struct pam * const pamP,
+                  tuple *            const tupleRow) {
+    
+    unsigned int col;
+
+    assert(pamP->allocation_depth >= 3);
+
+    for (col = 0; col < pamP->width; ++col) {
+        tupleRow[col][1] = tupleRow[col][0];
+        tupleRow[col][2] = tupleRow[col][0];
+    }
+}
+
+
+
+static void
+adjustDepth(const struct pam * const pamP,
+            tuple *            const tupleRow,
+            unsigned int       const newDepth) {
+/*----------------------------------------------------------------------------
+   Change the depth of the raster row tupleRow[] of the image
+   described by 'pamP' to newDepth.
+
+   We don't change the memory allocation; tupleRow[] must already have
+   space allocated for at least 'newDepth' planes.  When we're done,
+   all but the first 'newDepth' planes are meaningless, but the space is
+   still there.
+
+   The only depth changes we know how to do are:
+
+     - from tuple type RGB, depth 3 to depth 1 
+
+       We change it to grayscale or black and white.
+
+     - from tuple type GRAYSCALE or BLACKANDWHITE depth 1 to depth 3.
+
+       We change it to RGB.
+
+   For any other depth change request, we issue an error message and abort
+   the program.
+-----------------------------------------------------------------------------*/
+    if (newDepth != pamP->depth) {
+
+        if (stripeq(pamP->tuple_type, "RGB")) {
+            if (newDepth != 1) {
+                pm_error("Map image depth of %u differs from input image "
+                         "depth of %u, and the tuple type is RGB.  "
+                         "The only depth to which I know how to convert "
+                         "an RGB tuple is 1.",
+                         newDepth, pamP->depth);
+            } else
+                rgbToDepth1(pamP, tupleRow);
+        } else if (stripeq(pamP->tuple_type, "GRAYSCALE") ||
+                   stripeq(pamP->tuple_type, "BLACKANDWHITE")) {
+            if (newDepth != 3) {
+                pm_error("Map image depth of %u differs from input image "
+                         "depth of %u, and the tuple type is GRAYSCALE "
+                         "or BLACKANDWHITE.  "
+                         "The only depth to which I know how to convert "
+                         "a GRAYSCALE or BLACKANDWHITE tuple is 3.",
+                         newDepth, pamP->depth);
+            } else
+                grayscaleToDepth3(pamP, tupleRow);
+        } else {
+            pm_error("Map image depth of %u differs from input image depth "
+                     "of %u, and the input image does not have a tuple type "
+                     "that I know how to convert to the map depth.  "
+                     "I can convert RGB, GRAYSCALE, and BLACKANDWHITE.  "
+                     "The input image is '%.*s'.",
+                     newDepth, pamP->depth, 
+                     (int)sizeof(pamP->tuple_type), pamP->tuple_type);
+        }
+    }
+}
+
+
+
+
+static void
+computeColorMapFromMap(struct pam *   const mappamP, 
+                       tuple **       const maptuples, 
+                       tupletable *   const colormapP,
+                       unsigned int * const newcolorsP) {
+/*----------------------------------------------------------------------------
+   Produce a colormap containing the colors that we will use in the output.
+
+   Make it include exactly those colors that are in the image
+   described by *mappamP and maptuples[][].
+
+   Return the number of colors in the returned colormap as *newcolorsP.
+-----------------------------------------------------------------------------*/
+    unsigned int colors; 
+
+    if (mappamP->width == 0 || mappamP->height == 0)
+        pm_error("colormap file contains no pixels");
+
+    *colormapP = 
+        pnm_computetuplefreqtable(mappamP, maptuples, MAXCOLORS, &colors);
+    if (*colormapP == NULL)
+        pm_error("too many colors in colormap!");
+    pm_message("%d colors found in colormap", colors);
+    *newcolorsP = colors;
+}
+
+
+
+static void
+initFserr(struct pam *   const pamP,
+          struct fserr * const fserrP) {
+/*----------------------------------------------------------------------------
+   Initialize the Floyd-Steinberg error vectors
+-----------------------------------------------------------------------------*/
+    unsigned int plane;
+
+    unsigned int const fserrSize = pamP->width + 2;
+
+    MALLOCARRAY(fserrP->thiserr, pamP->depth);
+    if (fserrP->thiserr == NULL)
+        pm_error("Out of memory allocating Floyd-Steinberg structures "
+                 "for depth %u", pamP->depth);
+    MALLOCARRAY(fserrP->nexterr, pamP->depth);
+    if (fserrP->nexterr == NULL)
+        pm_error("Out of memory allocating Floyd-Steinberg structures "
+                 "for depth %u", pamP->depth);
+    
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        MALLOCARRAY(fserrP->thiserr[plane], fserrSize);
+        if (fserrP->thiserr[plane] == NULL)
+            pm_error("Out of memory allocating Floyd-Steinberg structures "
+                     "for Plane %u, size %u", plane, fserrSize);
+        MALLOCARRAY(fserrP->nexterr[plane], fserrSize);
+        if (fserrP->nexterr[plane] == NULL)
+            pm_error("Out of memory allocating Floyd-Steinberg structures "
+                     "for Plane %u, size %u", plane, fserrSize);
+    }
+
+    srand((int)(time(0) ^ getpid()));
+
+    {
+        int col;
+
+        for (col = 0; col < fserrSize; ++col) {
+            unsigned int plane;
+            for (plane = 0; plane < pamP->depth; ++plane) 
+                fserrP->thiserr[plane][col] = 
+                    rand() % (FS_SCALE * 2) - FS_SCALE;
+                    /* (random errors in [-1 .. 1]) */
+        }
+    }
+    fserrP->fsForward = TRUE;
+}
+
+
+
+static void
+floydInitRow(struct pam * const pamP, struct fserr * const fserrP) {
+
+    int col;
+    
+    for (col = 0; col < pamP->width + 2; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane) 
+            fserrP->nexterr[plane][col] = 0;
+    }
+}
+
+
+
+static void
+floydAdjustColor(struct pam *   const pamP, 
+                 tuple          const tuple, 
+                 struct fserr * const fserrP, 
+                 int            const col) {
+/*----------------------------------------------------------------------------
+  Use Floyd-Steinberg errors to adjust actual color.
+-----------------------------------------------------------------------------*/
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        long int const s =
+            tuple[plane] + fserrP->thiserr[plane][col+1] / FS_SCALE;
+        tuple[plane] = MIN(pamP->maxval, MAX(0,s));
+    }
+}
+
+
+
+static void
+floydPropagateErr(struct pam *   const pamP, 
+                  struct fserr * const fserrP, 
+                  int            const col, 
+                  tuple          const oldtuple, 
+                  tuple          const newtuple) {
+/*----------------------------------------------------------------------------
+  Propagate Floyd-Steinberg error terms.
+
+  The error is due to substituting the tuple value 'newtuple' for the
+  tuple value 'oldtuple' (both described by *pamP).  The error terms
+  are meant to be used to introduce a compensating error into the
+  future selection of tuples nearby in the image.
+-----------------------------------------------------------------------------*/
+    unsigned int plane;
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        long const newSample = newtuple[plane];
+        long const oldSample = oldtuple[plane];
+        long const err = (oldSample - newSample) * FS_SCALE;
+            
+        if (fserrP->fsForward) {
+            fserrP->thiserr[plane][col + 2] += ( err * 7 ) / 16;
+            fserrP->nexterr[plane][col    ] += ( err * 3 ) / 16;
+            fserrP->nexterr[plane][col + 1] += ( err * 5 ) / 16;
+            fserrP->nexterr[plane][col + 2] += ( err     ) / 16;
+        } else {
+            fserrP->thiserr[plane][col    ] += ( err * 7 ) / 16;
+            fserrP->nexterr[plane][col + 2] += ( err * 3 ) / 16;
+            fserrP->nexterr[plane][col + 1] += ( err * 5 ) / 16;
+            fserrP->nexterr[plane][col    ] += ( err     ) / 16;
+        }
+    }
+}
+
+
+
+static void
+floydSwitchDir(struct pam * const pamP, struct fserr * const fserrP) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        long * const temperr = fserrP->thiserr[plane];
+        fserrP->thiserr[plane] = fserrP->nexterr[plane];
+        fserrP->nexterr[plane] = temperr;
+    }
+    fserrP->fsForward = ! fserrP->fsForward;
+}
+
+
+
+struct colormapFinder {
+/*----------------------------------------------------------------------------
+   This is an object that finds a color in a colormap.  The methods
+   'searchColormapClose' and 'searchColormapExact' belong to it.
+
+   This object ought to encompass the hash table as well some day and
+   possibly encapsulate the color map altogether and just be an object
+   that opaquely maps input colors to output colors.
+-----------------------------------------------------------------------------*/
+    tupletable colormap;
+    unsigned int colors;
+        /* Number of colors in 'colormap'.  At least 1 */
+    unsigned int distanceDivider;
+        /* The value by which our intermediate distance calculations
+           have to be divided to make sure we don't overflow our
+           unsigned int data structure.
+           
+           To the extent 'distanceDivider' is greater than 1, closest
+           color results will be approximate -- there could
+           conceivably be a closer one that we miss.
+        */
+};
+
+
+
+static void
+createColormapFinder(struct pam *             const pamP,
+                     tupletable               const colormap,
+                     unsigned int             const colors,
+                     struct colormapFinder ** const colormapFinderPP) {
+
+    struct colormapFinder * colormapFinderP;
+
+    MALLOCVAR_NOFAIL(colormapFinderP);
+
+    colormapFinderP->colormap = colormap;
+    colormapFinderP->colors = colors;
+
+    {
+        unsigned int const maxHandleableSqrDiff = 
+            (unsigned int)UINT_MAX / pamP->depth;
+        
+        if (SQR(pamP->maxval) > maxHandleableSqrDiff)
+            colormapFinderP->distanceDivider = (unsigned int)
+                (SQR(pamP->maxval) / maxHandleableSqrDiff + 0.1 + 1.0);
+                /* The 0.1 is a fudge factor to keep us out of rounding 
+                   trouble.  The 1.0 effects a round-up.
+                */
+        else
+            colormapFinderP->distanceDivider = 1;
+    }
+    *colormapFinderPP = colormapFinderP;
+}
+
+
+
+static void
+destroyColormapFinder(struct colormapFinder * const colormapFinderP) {
+
+    free(colormapFinderP);
+}
+
+
+
+static void
+searchColormapClose(struct pam *            const pamP,
+                    tuple                   const tuple,
+                    struct colormapFinder * const colorFinderP,
+                    int *                   const colormapIndexP) {
+/*----------------------------------------------------------------------------
+   Search the colormap indicated by *colorFinderP for the color closest to
+   that of tuple 'tuple'.  Return its index as *colormapIndexP.
+
+   *pamP describes the tuple 'tuple' and *colorFinderP has to be
+   compatible with it (i.e. the tuples in the color map must also be
+   described by *pamP).
+
+   We compute distance between colors simply as the cartesian distance
+   between them in the RGB space.  An alternative would be to look at
+   the chromaticities and luminosities of the colors.  In experiments
+   in 2003, we found that this was actually worse in many cases.  One
+   might think that two colors are closer if they have similar hues
+   than when they are simply different brightnesses of the same hue.
+   Human subjects asked to compare two colors normally say so.  But
+   when replacing the color of a pixel in an image, the luminosity is
+   much more important, because you need to retain the luminosity
+   relationship between adjacent pixels.  If you replace a pixel with
+   one that has the same chromaticity as the original, but much
+   darker, it may stand out among its neighbors in a way the original
+   pixel did not.  In fact, on an image with blurred edges, we saw
+   ugly effects at the edges when we substituted colors using a
+   chromaticity-first color closeness formula.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+    unsigned int dist;
+        /* The closest distance we've found so far between the value of
+           tuple 'tuple' and a tuple in the colormap.  This is measured as
+           the square of the cartesian distance between the tuples, except
+           that it's divided by 'distanceDivider' to make sure it will fit
+           in an unsigned int.
+        */
+
+    dist = UINT_MAX;  /* initial value */
+
+    assert(colorFinderP->colors > 0);
+
+    for (i = 0; i < colorFinderP->colors; ++i) {
+        unsigned int newdist;  /* candidate for new 'dist' value */
+        unsigned int plane;
+
+        newdist = 0;
+
+        for (plane=0; plane < pamP->depth; ++plane) {
+            newdist += 
+                SQR(tuple[plane] - colorFinderP->colormap[i]->tuple[plane]) 
+                / colorFinderP->distanceDivider;
+        }
+        if (newdist < dist) {
+            *colormapIndexP = i;
+            dist = newdist;
+        }
+    }
+}
+
+
+
+static void
+searchColormapExact(struct pam *            const pamP,
+                    struct colormapFinder * const colorFinderP,
+                    tuple                   const tuple,
+                    int *                   const colormapIndexP,
+                    bool *                  const foundP) {
+/*----------------------------------------------------------------------------
+   Search the colormap indicated by *colorFinderP for the color of
+   tuple 'tuple'.  If it's in the map, return its index as
+   *colormapIndexP and return *foundP == TRUE.  Otherwise, return
+   *foundP = FALSE.
+
+   *pamP describes the tuple 'tuple' and *colorFinderP has to be
+   compatible with it (i.e. the tuples in the color map must also be
+   described by *pamP).
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+    bool found;
+    
+    found = FALSE;  /* initial value */
+    for (i = 0; i < colorFinderP->colors && !found; ++i) {
+        unsigned int plane;
+        found = TRUE;  /* initial assumption */
+        for (plane=0; plane < pamP->depth; ++plane) 
+            if (tuple[plane] != colorFinderP->colormap[i]->tuple[plane]) 
+                found = FALSE;
+        if (found) 
+            *colormapIndexP = i;
+    }
+    *foundP = found;
+}
+
+
+
+static void
+lookupThroughHash(struct pam *            const pamP, 
+                  tuple                   const tuple, 
+                  bool                    const needExactMatch,
+                  struct colormapFinder * const colorFinderP,
+                  tuplehash               const colorhash,       
+                  int *                   const colormapIndexP,
+                  bool *                  const usehashP) {
+/*----------------------------------------------------------------------------
+   Look up the color of tuple 'tuple' in the color map indicated by
+   'colorFinderP' and, if it's in there, return its index as
+   *colormapIndexP.  If not, return *colormapIndexP == -1.
+
+   Both the tuple 'tuple' and the colors in color map 'colormap' are
+   described by *pamP.
+
+   If 'needExactMatch' isn't true, we find the closest color in the color map,
+   and never return *colormapIndex == -1.
+
+   lookaside at the hash table 'colorhash' to possibly avoid the cost of
+   a full lookup.  If we do a full lookup, we add the result to 'colorhash'
+   unless *usehashP is false, and if that makes 'colorhash' full, we set
+   *usehashP false.
+-----------------------------------------------------------------------------*/
+    int found;
+
+    /* Check hash table to see if we have already matched this color. */
+    pnm_lookuptuple(pamP, colorhash, tuple, &found, colormapIndexP);
+    if (!found) {
+        /* No, have to do a full lookup */
+        if (needExactMatch) {
+            bool found;
+
+            searchColormapExact(pamP, colorFinderP, tuple,
+                                colormapIndexP, &found);
+            if (!found)
+                *colormapIndexP = -1;
+        } else 
+            searchColormapClose(pamP, tuple, colorFinderP, colormapIndexP);
+        if (*usehashP) {
+            bool fits;
+            pnm_addtotuplehash(pamP, colorhash, tuple, *colormapIndexP, 
+                               &fits);
+            if (!fits) {
+                pm_message("out of memory adding to hash table; "
+                           "proceeding without it");
+                *usehashP = FALSE;
+            }
+        }
+    }
+}
+
+
+
+static void
+convertRow(struct pam *            const pamP, 
+           tuple                         tuplerow[],
+           tupletable              const colormap,
+           struct colormapFinder * const colorFinderP,
+           tuplehash               const colorhash, 
+           bool *                  const usehashP,
+           bool                    const floyd, 
+           enum missingMethod      const missingMethod, 
+           tuple                   const defaultColor,
+           struct fserr *          const fserrP,
+           unsigned int *          const missingCountP) {
+/*----------------------------------------------------------------------------
+  Replace the colors in row tuplerow[] (described by *pamP) with the
+  new colors.
+
+  Use and update the Floyd-Steinberg state *fserrP.
+
+  Return the number of pixels that were not matched in the color map as
+  *missingCountP.
+
+  *colorFinderP is a color finder based on 'colormap' -- it tells us what
+  index of 'colormap' corresponds to a certain color.
+-----------------------------------------------------------------------------*/
+    int col;
+    int limitcol;
+        /* The column at which to stop processing the row.  If we're scanning
+           forwards, this is the rightmost column.  If we're scanning 
+           backward, this is the leftmost column.
+        */
+    
+    if (floyd)
+        floydInitRow(pamP, fserrP);
+
+    *missingCountP = 0;  /* initial value */
+    
+    if ((!floyd) || fserrP->fsForward) {
+        col = 0;
+        limitcol = pamP->width;
+    } else {
+        col = pamP->width - 1;
+        limitcol = -1;
+    }
+    do {
+        int colormapIndex;
+            /* Index into the colormap of the replacement color, or -1 if
+               there is no usable color in the color map.
+            */
+
+        if (floyd) 
+            floydAdjustColor(pamP, tuplerow[col], fserrP, col);
+
+        lookupThroughHash(pamP, tuplerow[col], 
+                          missingMethod != MISSING_CLOSE, colorFinderP, 
+                          colorhash, &colormapIndex, usehashP);
+        if (floyd)
+            floydPropagateErr(pamP, fserrP, col, tuplerow[col], 
+                              colormap[colormapIndex]->tuple);
+
+        if (colormapIndex == -1) {
+            ++*missingCountP;
+            switch (missingMethod) {
+            case MISSING_SPECIFIED:
+                pnm_assigntuple(pamP, tuplerow[col], defaultColor);
+                break;
+            case MISSING_FIRST:
+                pnm_assigntuple(pamP, tuplerow[col], colormap[0]->tuple);
+                break;
+            default:
+                pm_error("Internal error: invalid value of missingMethod");
+            }
+        } else 
+            pnm_assigntuple(pamP, tuplerow[col], 
+                            colormap[colormapIndex]->tuple);
+
+        if (floyd && !fserrP->fsForward) 
+            --col;
+        else
+            ++col;
+    } while (col != limitcol);
+
+    if (floyd)
+        floydSwitchDir(pamP, fserrP);
+}
+
+
+
+static void
+copyRaster(struct pam *   const inpamP, 
+           struct pam *   const outpamP,
+           tupletable     const colormap, 
+           unsigned int   const colormapSize,
+           bool           const floyd, 
+           enum missingMethod const missingMethod,
+           tuple          const defaultColor, 
+           unsigned int * const missingCountP) {
+
+    tuplehash const colorhash = pnm_createtuplehash();
+    struct colormapFinder * colorFinderP;
+    bool usehash;
+    struct fserr fserr;
+    tuple * tuplerow = pnm_allocpamrow(inpamP);
+    int row;
+
+    if (outpamP->maxval != inpamP->maxval && missingMethod != MISSING_CLOSE)
+        pm_error("The maxval of the colormap (%u) is not equal to the "
+                 "maxval of the input image (%u).  This is allowable only "
+                 "if you are doing an approximate mapping (i.e. you don't "
+                 "specify -firstisdefault or -missingcolor)",
+                 (unsigned int)outpamP->maxval, (unsigned int)inpamP->maxval);
+
+    usehash = TRUE;
+
+    createColormapFinder(outpamP, colormap, colormapSize, &colorFinderP);
+
+    if (floyd)
+        initFserr(inpamP, &fserr);
+
+    *missingCountP = 0;  /* initial value */
+
+    for (row = 0; row < inpamP->height; ++row) {
+        unsigned int missingCount;
+
+        pnm_readpamrow(inpamP, tuplerow);
+
+        /* The following modify tuplerow, to make it consistent with
+           *outpamP instead of *inpamP.
+        */
+        adjustDepth(inpamP, tuplerow, outpamP->depth); 
+        pnm_scaletuplerow(inpamP, tuplerow, tuplerow, outpamP->maxval);
+
+        /* The following both consults and adds to 'colorhash' and
+           its associated 'usehash'.  It modifies 'tuplerow' too.
+        */
+        convertRow(outpamP, tuplerow, colormap, colorFinderP, colorhash, 
+                   &usehash,
+                   floyd, missingMethod, defaultColor, &fserr, &missingCount);
+        
+        *missingCountP += missingCount;
+        
+        pnm_writepamrow(outpamP, tuplerow);
+    }
+    destroyColormapFinder(colorFinderP);
+    pnm_freepamrow(tuplerow);
+    pnm_destroytuplehash(colorhash);
+}
+
+
+
+
+static void
+remap(FILE * const ifP,
+      const struct pam * const outpamCommonP,
+      tupletable         const colormap, 
+      unsigned int       const colormapSize,
+      bool               const floyd,
+      enum missingMethod const missingMethod,
+      tuple              const defaultColor,
+      bool               const verbose) {
+
+    bool eof;
+    eof = FALSE;
+    while (!eof) {
+        struct pam inpam, outpam;
+        unsigned int missingCount;
+            /* Number of pixels that were not matched in the color map (where
+               missingMethod is MISSING_CLOSE, this is always zero).
+            */
+
+        pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(allocation_depth));
+    
+        outpam = *outpamCommonP;
+        outpam.width  = inpam.width;
+        outpam.height = inpam.height;
+
+        pnm_writepaminit(&outpam);
+
+        /* Set up so input buffers have extra space as needed to
+           convert the input to the output depth.
+        */
+        pnm_setminallocationdepth(&inpam, outpam.depth);
+    
+        copyRaster(&inpam, &outpam, colormap, colormapSize, floyd,
+                   missingMethod, defaultColor, &missingCount);
+        
+        if (verbose)
+            pm_message("%d pixels not matched in color map", missingCount);
+        
+        pnm_nextimage(ifP, &eof);
+    }
+}
+
+
+
+
+int
+main(int argc, char * argv[] ) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    struct pam outpamCommon;
+        /* Describes the output images.  Width and height fields are
+           not meaningful, because different output images might have
+           different dimensions.  The rest of the information is common
+           across all output images.
+        */
+    tupletable colormap;
+    unsigned int colormapSize;
+    tuple defaultColor;
+        /* A tuple of the color that should replace any input color that is 
+           not in the colormap, if we're doing MISSING_SPECIFIED.
+        */
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+    {
+        FILE * mapfile;
+        struct pam mappam;
+        tuple ** maptuples;
+
+        mapfile = pm_openr(cmdline.mapFilespec);
+        maptuples = pnm_readpam(mapfile, &mappam, PAM_STRUCT_SIZE(tuple_type));
+        pm_close(mapfile);
+
+        computeColorMapFromMap(&mappam, maptuples, &colormap, &colormapSize);
+        pnm_freepamarray(maptuples, &mappam);
+
+        outpamCommon = mappam; 
+        outpamCommon.file = stdout;
+    }
+
+    defaultColor = pnm_allocpamtuple(&outpamCommon);
+    if (cmdline.missingcolor && outpamCommon.depth == 3) {
+        pixel const color = 
+            ppm_parsecolor(cmdline.missingcolor, outpamCommon.maxval);
+        defaultColor[PAM_RED_PLANE] = PPM_GETR(color);
+        defaultColor[PAM_GRN_PLANE] = PPM_GETG(color);
+        defaultColor[PAM_BLU_PLANE] = PPM_GETB(color);
+    }
+
+    remap(ifP, &outpamCommon, colormap, colormapSize, 
+          cmdline.floyd, cmdline.missingMethod, defaultColor,
+          cmdline.verbose);
+
+    pnm_freepamtuple(defaultColor);
+
+    pm_close(stdout);
+
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/editor/pnmrotate.c b/editor/pnmrotate.c
new file mode 100644
index 00000000..64c69e2a
--- /dev/null
+++ b/editor/pnmrotate.c
@@ -0,0 +1,808 @@
+/* pnmrotate.c - read a portable anymap and rotate it by some angle
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _XOPEN_SOURCE   /* get M_PI in math.h */
+
+#include <math.h>
+#include <assert.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+#define SCALE 4096
+#define HALFSCALE 2048
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilespec;  /* Filespecs of input file */
+    float angle;                /* Angle to rotate, in radians */
+    unsigned int noantialias;
+    const char * background;  /* NULL if none */
+    unsigned int keeptemp;  /* For debugging */
+    unsigned int verbose;
+};
+
+
+enum rotationDirection {CLOCKWISE, COUNTERCLOCKWISE};
+
+struct shearParm {
+    /* These numbers tell how to shear a pixel, but I haven't figured out 
+       yet exactly what each means.
+    */
+    long fracnew0;
+    long omfracnew0;
+    unsigned int shiftWhole;
+    unsigned int shiftUnits;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int backgroundSpec;
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "background",  OPT_STRING, &cmdlineP->background, 
+            &backgroundSpec,        0);
+    OPTENT3(0, "noantialias", OPT_FLAG,   NULL, 
+            &cmdlineP->noantialias, 0);
+    OPTENT3(0, "keeptemp",    OPT_FLAG,   NULL, 
+            &cmdlineP->keeptemp,    0);
+    OPTENT3(0, "verbose",     OPT_FLAG,   NULL, 
+            &cmdlineP->verbose,     0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = TRUE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!backgroundSpec)
+        cmdlineP->background = NULL;
+
+    if (argc-1 < 1)
+        pm_error("You must specify at least one argument:  the angle "
+                 "to rotate.");
+    else {
+        int rc;
+        float angleArg;
+
+        rc = sscanf(argv[1], "%f", &angleArg);
+
+        if (rc != 1)
+            pm_error("Invalid angle argument: '%s'.  Must be a floating point "
+                     "number of degrees.", argv[1]);
+        else if (angleArg < -90.0 || angleArg > 90.0)
+            pm_error("angle must be between -90 and 90, inclusive.  "
+                     "You specified %f.  "
+                     "Use 'pamflip' for other rotations.", angleArg);
+        else {
+            /* Convert to radians */
+            cmdlineP->angle = angleArg * M_PI / 180.0;
+
+            if (argc-1 < 2)
+                cmdlineP->inputFilespec = "-";
+            else {
+                cmdlineP->inputFilespec = argv[2];
+                
+                if (argc-1 > 2)
+                    pm_error("Program takes at most two arguments "
+                             "(angle and filename).  You specified %d",
+                             argc-1);
+            }
+        }
+    }
+}
+
+
+
+static void
+storeImage(const char * const fileName,
+           xel **       const xels,
+           unsigned int const cols,
+           unsigned int const rows,
+           xelval       const maxval,
+           int          const format) {
+
+    FILE * ofP;
+
+    ofP = pm_openw(fileName);
+
+    pnm_writepnm(ofP, xels, cols, rows, maxval, format, 0);
+
+    pm_close(ofP);
+}
+
+  
+
+static void
+computeNewFormat(bool     const antialias, 
+                 int      const format,
+                 xelval   const maxval,
+                 int *    const newformatP,
+                 xelval * const newmaxvalP) {
+
+    if (antialias && PNM_FORMAT_TYPE(format) == PBM_TYPE) {
+        *newformatP = PGM_TYPE;
+        *newmaxvalP = PGM_MAXMAXVAL;
+        pm_message("promoting from PBM to PGM - "
+                   "use -noantialias to avoid this");
+    } else {
+        *newformatP = format;
+        *newmaxvalP = maxval;
+    }
+}
+
+
+
+static xel
+backgroundColor(const char * const backgroundColorName,
+                xel *        const topRow,
+                int          const cols,
+                xelval       const maxval,
+                int          const format) {
+
+    xel retval;
+
+    if (backgroundColorName) {
+        retval = ppm_parsecolor(backgroundColorName, maxval);
+
+        switch(PNM_FORMAT_TYPE(format)) {
+        case PGM_TYPE:
+            if (!PPM_ISGRAY(retval))
+                pm_error("Image is PGM (grayscale), "
+                         "but you specified a non-gray "
+                         "background color '%s'", backgroundColorName);
+
+            break;
+        case PBM_TYPE:
+            if (!PNM_EQUAL(retval, pnm_whitexel(maxval, format)) &&
+                !PNM_EQUAL(retval, pnm_blackxel(maxval, format)))
+                pm_error("Image is PBM (black and white), "
+                         "but you specified '%s', which is neither black "
+                         "nor white, as background color", 
+                         backgroundColorName);
+            break;
+        }
+    } else 
+        retval = pnm_backgroundxelrow(topRow, cols, maxval, format);
+
+    return retval;
+}
+
+
+
+static void
+reportBackground(xel const bgColor) {
+
+    pm_message("Background color %u/%u/%u",
+               PPM_GETR(bgColor), PPM_GETG(bgColor), PPM_GETB(bgColor));
+}
+
+
+
+static void
+shearX(xel * const inRow, 
+       xel * const outRow, 
+       int   const cols, 
+       int   const format,
+       xel   const bgxel,
+       bool  const antialias,
+       float const shiftAmount,
+       int   const newcols) {
+/*----------------------------------------------------------------------------
+   Shift a the row inRow[] right by 'shiftAmount' pixels and return the
+   result as outRow[].
+
+   The input row is 'cols' columns wide, whereas the output row is
+   'newcols'.
+
+   The format of the input row is 'format'.
+
+   We shift the row on a background of color 'bgxel'.
+
+   The output row has the same format and maxval as the input.
+   
+   'shiftAmount' may not be negative.
+   
+   'shiftAmount' can be fractional, so we either just go by the
+   nearest integer value or mix pixels to achieve the shift, depending
+   on 'antialias'.
+-----------------------------------------------------------------------------*/
+    assert(shiftAmount >= 0.0);
+
+    if (antialias) {
+        unsigned int const shiftWhole = (unsigned int) shiftAmount;
+        long const fracShift = (shiftAmount - shiftWhole) * SCALE;
+        long const omfracShift = SCALE - fracShift;
+
+        unsigned int col;
+        xel * nxP;
+        xel prevxel;
+
+        for (col = 0; col < newcols; ++col)
+            outRow[col] = bgxel;
+            
+        prevxel = bgxel;
+        for (col = 0, nxP = &(outRow[shiftWhole]);
+             col < cols; ++col, ++nxP) {
+
+            xel const p = inRow[col];
+
+            switch (PNM_FORMAT_TYPE(format)) {
+            case PPM_TYPE:
+                PPM_ASSIGN(*nxP,
+                           (fracShift * PPM_GETR(prevxel) 
+                            + omfracShift * PPM_GETR(p) 
+                            + HALFSCALE) / SCALE,
+                           (fracShift * PPM_GETG(prevxel) 
+                            + omfracShift * PPM_GETG(p) 
+                            + HALFSCALE) / SCALE,
+                           (fracShift * PPM_GETB(prevxel) 
+                            + omfracShift * PPM_GETB(p) 
+                            + HALFSCALE) / SCALE );
+                break;
+                
+            default:
+                PNM_ASSIGN1(*nxP,
+                            (fracShift * PNM_GET1(prevxel) 
+                             + omfracShift * PNM_GET1(p) 
+                             + HALFSCALE) / SCALE );
+                break;
+            }
+            prevxel = p;
+        }
+        if (fracShift> 0 && shiftWhole + cols < newcols) {
+            switch (PNM_FORMAT_TYPE(format)) {
+            case PPM_TYPE:
+                PPM_ASSIGN(*nxP,
+                           (fracShift * PPM_GETR(prevxel) 
+                            + omfracShift * PPM_GETR(bgxel) 
+                            + HALFSCALE) / SCALE,
+                           (fracShift * PPM_GETG(prevxel) 
+                            + omfracShift * PPM_GETG(bgxel) 
+                            + HALFSCALE) / SCALE,
+                           (fracShift * PPM_GETB(prevxel) 
+                            + omfracShift * PPM_GETB(bgxel) 
+                            + HALFSCALE) / SCALE );
+                break;
+                    
+            default:
+                PNM_ASSIGN1(*nxP,
+                            (fracShift * PNM_GET1(prevxel) 
+                             + omfracShift * PNM_GET1(bgxel) 
+                             + HALFSCALE) / SCALE );
+                break;
+            }
+        }
+    } else {
+        unsigned int const shiftCols = (unsigned int) (shiftAmount + 0.5);
+        unsigned int col;
+        unsigned int outcol;
+
+        outcol = 0;  /* initial value */
+        
+        for (col = 0; col < shiftCols; ++col)
+            outRow[outcol++] = bgxel;
+        for (col = 0; col < cols; ++col)
+            outRow[outcol++] = inRow[col];
+        for (col = shiftCols + cols; col < newcols; ++col)
+            outRow[outcol++] = bgxel;
+        
+        assert(outcol == newcols);
+    }
+}
+
+
+
+static void
+shearXFromInputFile(FILE *                 const ifP,
+                    unsigned int           const cols,
+                    unsigned int           const rows,
+                    xelval                 const maxval,
+                    int                    const format,
+                    enum rotationDirection const direction,
+                    float                  const xshearfac,
+                    xelval                 const newmaxval,
+                    int                    const newformat,
+                    bool                   const antialias,
+                    const char *           const background,
+                    xel ***                const shearedXelsP,
+                    unsigned int *         const newcolsP,
+                    xel *                  const bgColorP) {
+/*----------------------------------------------------------------------------
+   Shear X from input file into newly malloced xel array.  Return that
+   array as *shearedColsP, and its width as *tempColsP.  Everything else
+   about the sheared image is the same as for the input image.
+
+   The input image on file 'ifP' is described by 'cols', 'rows',
+   'maxval', and 'format'.
+
+   Along the way, figure out what the background color of the output should
+   be based on the contents of the file and the user's directive
+   'background' and return that as *bgColorP.
+-----------------------------------------------------------------------------*/
+    unsigned int const maxShear = (rows - 0.5) * xshearfac + 0.5;
+    unsigned int const newcols = cols + maxShear;
+    
+    xel ** shearedXels;
+    xel * xelrow;
+    xel bgColor;
+    unsigned int row;
+
+    shearedXels = pnm_allocarray(newcols, rows);
+
+    xelrow = pnm_allocrow(cols);
+
+    for (row = 0; row < rows; ++row) {
+        /* The shear factor is designed to shear over the entire width
+           from the left edge of of the left pixel to the right edge of
+           the right pixel.  We use the distance of the center of this
+           pixel from the relevant edge to compute shift amount:
+        */
+        float const xDistance = 
+            (direction == COUNTERCLOCKWISE ? row + 0.5 : (rows-0.5 - row));
+        float const shiftAmount = xshearfac * xDistance;
+
+        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+
+        pnm_promoteformatrow(xelrow, cols, maxval, format, 
+                             newmaxval, newformat);
+
+        if (row == 0)
+            bgColor =
+                backgroundColor(background, xelrow, cols, newmaxval, format);
+
+        shearX(xelrow, shearedXels[row], cols, newformat, bgColor,
+               antialias, shiftAmount, newcols);
+    }
+    pnm_freerow(xelrow);
+
+    *shearedXelsP = shearedXels;
+    *newcolsP = newcols;
+
+    assert(rows >= 1);  /* Ergo, bgColor is defined */
+    *bgColorP = bgColor;
+}
+
+
+
+static void 
+shearYNoAntialias(xel **           const inxels,
+                  xel **           const outxels,
+                  int              const cols,
+                  int              const inrows,
+                  int              const outrows,
+                  int              const format,
+                  xel              const bgColor,
+                  struct shearParm const shearParm[]) {
+/*----------------------------------------------------------------------------
+   Shear the image in 'inxels' ('cols' x 'inrows') vertically into
+   'outxels' ('cols' x 'outrows'), both format 'format'.  shearParm[X]
+   tells how much to shear pixels in Column X (clipped to Rows 0
+   through 'outrow' -1) and 'bgColor' is what to use for background
+   where there is none of the input in the output.
+
+   We do not do any antialiasing.  We simply move whole pixels.
+
+   We go row by row instead of column by column to save real memory.  Going
+   row by row, the working set is only a few pages, whereas going column by
+   column, it would be one page per output row plus one page per input row.
+-----------------------------------------------------------------------------*/
+    unsigned int inrow;
+    unsigned int outrow;
+
+    /* Fill the output with background */
+    for (outrow = 0; outrow < outrows; ++outrow) {
+        unsigned int col;
+        for (col = 0; col < cols; ++col)
+            outxels[outrow][col] = bgColor;
+    }
+
+    /* Overlay that background with sheared image */
+    for (inrow = 0; inrow < inrows; ++inrow) {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            int const outrow = inrow + shearParm[col].shiftUnits;
+            if (outrow >= 0 && outrow < outrows)
+                outxels[outrow][col] = inxels[inrow][col];
+        }
+    }
+}
+
+
+
+static void
+shearYColAntialias(xel ** const inxels, 
+                   xel ** const outxels,
+                   int    const col,
+                   int    const inrows,
+                   int    const outrows,
+                   int    const format,
+                   xel    const bgxel,
+                   struct shearParm shearParm[]) {
+/*-----------------------------------------------------------------------------
+  Shear a column vertically.
+-----------------------------------------------------------------------------*/
+    long const fracnew0   = shearParm[col].fracnew0;
+    long const omfracnew0 = shearParm[col].omfracnew0;
+    int  const shiftWhole = shearParm[col].shiftWhole;
+        
+    int outrow;
+
+    xel prevxel;
+    int inrow;
+        
+    /* Initialize everything to background color */
+    for (outrow = 0; outrow < outrows; ++outrow)
+        outxels[outrow][col] = bgxel;
+
+    prevxel = bgxel;
+    for (inrow = 0; inrow < inrows; ++inrow) {
+        int const outrow = inrow + shiftWhole;
+
+        if (outrow >= 0 && outrow < outrows) {
+            xel * const nxP = &(outxels[outrow][col]);
+            xel const x = inxels[inrow][col];
+            switch ( PNM_FORMAT_TYPE(format) ) {
+            case PPM_TYPE:
+                PPM_ASSIGN(*nxP,
+                           (fracnew0 * PPM_GETR(prevxel) 
+                            + omfracnew0 * PPM_GETR(x) 
+                            + HALFSCALE) / SCALE,
+                           (fracnew0 * PPM_GETG(prevxel) 
+                            + omfracnew0 * PPM_GETG(x) 
+                            + HALFSCALE) / SCALE,
+                           (fracnew0 * PPM_GETB(prevxel) 
+                            + omfracnew0 * PPM_GETB(x) 
+                            + HALFSCALE) / SCALE );
+                break;
+                        
+            default:
+                PNM_ASSIGN1(*nxP,
+                            (fracnew0 * PNM_GET1(prevxel) 
+                             + omfracnew0 * PNM_GET1(x) 
+                             + HALFSCALE) / SCALE );
+                break;
+            }
+            prevxel = x;
+        }
+    }
+    if (fracnew0 > 0 && shiftWhole + inrows < outrows) {
+        xel * const nxP = &(outxels[shiftWhole + inrows][col]);
+        switch (PNM_FORMAT_TYPE(format)) {
+        case PPM_TYPE:
+            PPM_ASSIGN(*nxP,
+                       (fracnew0 * PPM_GETR(prevxel) 
+                        + omfracnew0 * PPM_GETR(bgxel) 
+                        + HALFSCALE) / SCALE,
+                       (fracnew0 * PPM_GETG(prevxel) 
+                        + omfracnew0 * PPM_GETG(bgxel) 
+                        + HALFSCALE) / SCALE,
+                       (fracnew0 * PPM_GETB(prevxel) 
+                        + omfracnew0 * PPM_GETB(bgxel) 
+                        + HALFSCALE) / SCALE);
+            break;
+                
+        default:
+            PNM_ASSIGN1(*nxP,
+                        (fracnew0 * PNM_GET1(prevxel) 
+                         + omfracnew0 * PNM_GET1(bgxel) 
+                         + HALFSCALE) / SCALE);
+            break;
+        }
+    }
+} 
+
+
+
+static void
+shearImageY(xel **                 const inxels,
+            int                    const cols,
+            int                    const inrows,
+            int                    const format,
+            xel                    const bgxel,
+            bool                   const antialias,
+            enum rotationDirection const direction,
+            float                  const yshearfac,
+            int                    const yshearjunk,
+            xel ***                const outxelsP,
+            unsigned int *         const outrowsP) {
+    
+    unsigned int const maxShear = (cols - 0.5) * yshearfac + 0.5;
+    unsigned int const outrows = inrows + maxShear - 2 * yshearjunk;
+
+    struct shearParm * shearParm;  /* malloc'ed */
+    int col;
+    xel ** outxels;
+    
+    outxels = pnm_allocarray(cols, outrows);
+
+    MALLOCARRAY(shearParm, cols);
+    if (shearParm == NULL)
+        pm_error("Unable to allocate memory for shearParm");
+
+    for (col = 0; col < cols; ++col) {
+        /* The shear factor is designed to shear over the entire height
+           from the top edge of of the top pixel to the bottom edge of
+           the bottom pixel.  We use the distance of the center of this
+           pixel from the relevant edge to compute shift amount:
+        */
+        float const yDistance = 
+            (direction == CLOCKWISE ? col + 0.5 : (cols-0.5 - col));
+        float const shiftAmount = yshearfac * yDistance;
+
+        shearParm[col].fracnew0   = (shiftAmount - (int)shiftAmount) * SCALE;
+        shearParm[col].omfracnew0 = SCALE - shearParm[col].fracnew0;
+        shearParm[col].shiftWhole = (int)shiftAmount - yshearjunk;
+        shearParm[col].shiftUnits = (int)(shiftAmount + 0.5) - yshearjunk;
+    }
+    if (!antialias)
+        shearYNoAntialias(inxels, outxels, cols, inrows, outrows, format,
+                          bgxel, shearParm);
+    else {
+        /* TODO: do this row-by-row, same as for noantialias, to save
+           real memory.
+        */
+        for (col = 0; col < cols; ++col) 
+            shearYColAntialias(inxels, outxels, col, inrows, outrows, format, 
+                               bgxel, shearParm);
+    }
+    free(shearParm);
+    
+    *outxelsP = outxels;
+    *outrowsP = outrows;
+}
+
+
+
+static void
+shearFinal(xel * const inRow, 
+           xel * const outRow, 
+           int   const incols, 
+           int   const outcols,
+           int   const format,
+           xel   const bgxel,
+           bool  const antialias,
+           float const shiftAmount,
+           int   const x2shearjunk) {
+
+
+    assert(shiftAmount >= 0.0);
+
+    {
+        unsigned int col;
+        for (col = 0; col < outcols; ++col)
+            outRow[col] = bgxel;
+    }
+
+    if (antialias) {
+        long const fracnew0   = (shiftAmount - (int) shiftAmount) * SCALE; 
+        long const omfracnew0 = SCALE - fracnew0; 
+        unsigned int const shiftWhole = (int)shiftAmount - x2shearjunk;
+
+        xel prevxel;
+        unsigned int col;
+
+        prevxel = bgxel;
+        for (col = 0; col < incols; ++col) {
+            int const new = shiftWhole + col;
+            if (new >= 0 && new < outcols) {
+                xel * const nxP = &(outRow[new]);
+                xel const x = inRow[col];
+                switch (PNM_FORMAT_TYPE(format)) {
+                case PPM_TYPE:
+                    PPM_ASSIGN(*nxP,
+                               (fracnew0 * PPM_GETR(prevxel) 
+                                + omfracnew0 * PPM_GETR(x) 
+                                + HALFSCALE) / SCALE,
+                               (fracnew0 * PPM_GETG(prevxel) 
+                                + omfracnew0 * PPM_GETG(x) 
+                                + HALFSCALE) / SCALE,
+                               (fracnew0 * PPM_GETB(prevxel) 
+                                + omfracnew0 * PPM_GETB(x) 
+                                + HALFSCALE) / SCALE);
+                    break;
+                    
+                default:
+                    PNM_ASSIGN1(*nxP,
+                                (fracnew0 * PNM_GET1(prevxel) 
+                                 + omfracnew0 * PNM_GET1(x) 
+                                 + HALFSCALE) / SCALE );
+                    break;
+                }
+                prevxel = x;
+            }
+        }
+        if (fracnew0 > 0 && shiftWhole + incols < outcols) {
+            xel * const nxP = &(outRow[shiftWhole + incols]);
+            switch (PNM_FORMAT_TYPE(format)) {
+            case PPM_TYPE:
+                PPM_ASSIGN(*nxP,
+                           (fracnew0 * PPM_GETR(prevxel) 
+                            + omfracnew0 * PPM_GETR(bgxel) 
+                            + HALFSCALE) / SCALE,
+                           (fracnew0 * PPM_GETG(prevxel) 
+                            + omfracnew0 * PPM_GETG(bgxel) 
+                            + HALFSCALE) / SCALE,
+                           (fracnew0 * PPM_GETB(prevxel) 
+                            + omfracnew0 * PPM_GETB(bgxel) 
+                            + HALFSCALE) / SCALE);
+                break;
+                
+            default:
+                PNM_ASSIGN1(*nxP,
+                            (fracnew0 * PNM_GET1(prevxel) 
+                             + omfracnew0 * PNM_GET1(bgxel) 
+                             + HALFSCALE) / SCALE );
+                break;
+            }
+        }
+    } else {
+        unsigned int const shiftCols =
+            (unsigned int)(shiftAmount + 0.5) - x2shearjunk;
+
+        unsigned int col;
+        for (col = 0; col < incols; ++col) {
+            unsigned int const outcol = shiftCols + col;
+            if (outcol >= 0 && outcol < outcols)
+                outRow[outcol] = inRow[col];
+        }
+    }
+}
+
+
+
+static void
+shearXToOutputFile(FILE *                 const ofP,
+                   xel **                 const xels,
+                   unsigned int           const cols, 
+                   unsigned int           const rows,
+                   xelval                 const maxval,
+                   int                    const format,
+                   enum rotationDirection const direction,
+                   float                  const xshearfac,
+                   int                    const x2shearjunk,
+                   xel                    const bgColor,
+                   bool                   const antialias) {
+/*----------------------------------------------------------------------------
+   Shear horizontally the image in 'xels' and write the result to file
+   'ofP'.  'cols', 'rows', 'maxval', and 'format' describe the image in
+   'xels'.  They also describe the output image, except that it will be
+   wider as dictated by the shearing parameters.
+
+   Shear over background color 'bgColor'.
+
+   Do a smooth pixel-mixing shear iff 'antialias' is true.
+-----------------------------------------------------------------------------*/
+    unsigned int const maxShear = (rows - 0.5) * xshearfac + 0.5;
+    unsigned int const newcols = cols + maxShear - 2 * x2shearjunk;
+
+    unsigned int row;
+    xel * xelrow;
+    
+    pnm_writepnminit(stdout, newcols, rows, maxval, format, 0);
+
+    xelrow = pnm_allocrow(newcols);
+
+    for (row = 0; row < rows; ++row) {
+        /* The shear factor is designed to shear over the entire width
+           from the left edge of of the left pixel to the right edge of
+           the right pixel.  We use the distance of the center of this
+           pixel from the relevant edge to compute shift amount:
+        */
+        float const xDistance = 
+            (direction == COUNTERCLOCKWISE ? row + 0.5 : (rows-0.5 - row));
+        float const shiftAmount = xshearfac * xDistance;
+
+        shearFinal(xels[row], xelrow, cols, newcols, format, 
+                   bgColor, antialias, shiftAmount, x2shearjunk);
+
+        pnm_writepnmrow(stdout, xelrow, newcols, maxval, format, 0);
+    }
+    pnm_freerow(xelrow);
+}
+
+
+
+int
+main(int argc, char *argv[]) { 
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    xel ** shear1xels;
+    xel ** shear2xels;
+    xel bgColor;
+    int rows, cols, format;
+    int newformat;
+    unsigned int newrows;
+    int newRowsWithJunk;
+    unsigned int shear1Cols;
+    int yshearjunk, x2shearjunk;
+    xelval maxval, newmaxval;
+    float xshearfac, yshearfac;
+    enum rotationDirection direction;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
+    
+    computeNewFormat(!cmdline.noantialias, format, maxval, 
+                     &newformat, &newmaxval);
+
+    xshearfac = fabs(tan(cmdline.angle / 2.0));
+    yshearfac = fabs(sin(cmdline.angle));
+    direction = cmdline.angle > 0 ? COUNTERCLOCKWISE : CLOCKWISE;
+
+    /* The algorithm we use, for maximum speed, is 3 simple shears:
+       A horizontal, a vertical, and another horizontal.
+    */
+
+    shearXFromInputFile(ifP, cols, rows, maxval, format,
+                        direction, xshearfac,
+                        newmaxval, newformat,
+                        !cmdline.noantialias, cmdline.background,
+                        &shear1xels, &shear1Cols, &bgColor);
+    
+    pm_close(ifP);
+
+    if (cmdline.verbose)
+        reportBackground(bgColor);
+
+    if (cmdline.keeptemp)
+        storeImage("pnmrotate_stage1.pnm", shear1xels, shear1Cols, rows,
+                   newmaxval, newformat);
+
+    yshearjunk = (shear1Cols - cols) * yshearfac;
+    newRowsWithJunk = (shear1Cols - 1) * yshearfac + rows + 0.999999;
+    x2shearjunk = (newRowsWithJunk - rows - yshearjunk - 1) * xshearfac;
+
+    shearImageY(shear1xels, shear1Cols, rows, newformat,
+                bgColor, !cmdline.noantialias, direction,
+                yshearfac, yshearjunk,
+                &shear2xels, &newrows);
+
+    pnm_freearray(shear1xels, rows);
+
+    if (cmdline.keeptemp)
+        storeImage("pnmrotate_stage2.pnm", shear2xels, shear1Cols, newrows, 
+                   newmaxval, newformat);
+
+    shearXToOutputFile(stdout, shear2xels, shear1Cols, newrows,
+                       newmaxval, newformat,
+                       direction, xshearfac, x2shearjunk, 
+                       bgColor, !cmdline.noantialias);
+
+    pnm_freearray(shear2xels, newrows);
+    pm_close(stdout);
+    
+    return 0;
+}
diff --git a/editor/pnmscale.c b/editor/pnmscale.c
new file mode 100644
index 00000000..f75f440c
--- /dev/null
+++ b/editor/pnmscale.c
@@ -0,0 +1,748 @@
+/* pnmscale.c - read a portable anymap and scale it
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+*/
+
+/* 
+
+      DON'T ADD NEW FUNCTION TO THIS PROGRAM.  ADD IT TO pamscale.c
+      INSTEAD.
+
+*/
+
+ 
+#include <math.h>
+#include <string.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+
+/* The pnm library allows us to code this program without branching cases
+   for PGM and PPM, but we do the branch anyway to speed up processing of 
+   PGM images.
+*/
+
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec;  /* Filespecs of input files */
+    unsigned int xsize;
+    unsigned int ysize;
+    float xscale;
+    float yscale;
+    unsigned int xbox;
+    unsigned int ybox;
+    unsigned int pixels;
+    unsigned int verbose;
+    unsigned int nomix;
+};
+
+
+static void
+parse_command_line(int argc, char ** argv,
+                   struct cmdline_info *cmdline_p) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc( 100*sizeof( optEntry ) );
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int xysize;
+    int xsize, ysize, pixels;
+    int reduce;
+    float xscale, yscale, scale_parm;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "xsize",     OPT_UINT,    &xsize,               NULL, 0);
+    OPTENT3(0, "width",     OPT_UINT,    &xsize,               NULL, 0);
+    OPTENT3(0, "ysize",     OPT_UINT,    &ysize,               NULL, 0);
+    OPTENT3(0, "height",    OPT_UINT,    &ysize,               NULL, 0);
+    OPTENT3(0, "xscale",    OPT_FLOAT,   &xscale,              NULL, 0);
+    OPTENT3(0, "yscale",    OPT_FLOAT,   &yscale,              NULL, 0);
+    OPTENT3(0, "pixels",    OPT_UINT,    &pixels,              NULL, 0);
+    OPTENT3(0, "reduce",    OPT_UINT,    &reduce,              NULL, 0);
+    OPTENT3(0, "xysize",    OPT_FLAG,    NULL, &xysize,              0);
+    OPTENT3(0, "verbose",   OPT_FLAG,    NULL, &cmdline_p->verbose,  0);
+    OPTENT3(0, "nomix",     OPT_FLAG,    NULL, &cmdline_p->nomix,    0);
+
+    /* Set the defaults. -1 = unspecified */
+    /* (Now that we're using ParseOptions3, we don't have to do this -1
+       nonsense, but we don't want to risk screwing these complex 
+       option compatibilities up, so we'll convert that later.
+    */
+    xsize = -1;
+    ysize = -1;
+    xscale = -1.0;
+    yscale = -1.0;
+    pixels = -1;
+    reduce = -1;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (xsize == 0)
+        pm_error("-xsize/width must be greater than zero.");
+    if (ysize == 0)
+        pm_error("-ysize/height must be greater than zero.");
+    if (xscale != -1.0 && xscale <= 0.0)
+        pm_error("-xscale must be greater than zero.");
+    if (yscale != -1.0 && yscale <= 0.0)
+        pm_error("-yscale must be greater than zero.");
+    if (reduce <= 0 && reduce != -1)
+        pm_error("-reduce must be greater than zero.");
+
+    if (xsize != -1 && xscale != -1)
+        pm_error("Cannot specify both -xsize/width and -xscale.");
+    if (ysize != -1 && yscale != -1)
+        pm_error("Cannot specify both -ysize/height and -yscale.");
+    
+    if (xysize && 
+        (xsize != -1 || xscale != -1 || ysize != -1 || yscale != -1 || 
+         reduce != -1 || pixels != -1) )
+        pm_error("Cannot specify -xysize with other dimension options.");
+    if (pixels != -1 && 
+        (xsize != -1 || xscale != -1 || ysize != -1 || yscale != -1 ||
+         reduce != -1) )
+        pm_error("Cannot specify -pixels with other dimension options.");
+    if (reduce != -1 && 
+        (xsize != -1 || xscale != -1 || ysize != -1 || yscale != -1) )
+        pm_error("Cannot specify -reduce with other dimension options.");
+
+    if (pixels == 0)
+        pm_error("-pixels must be greater than zero");
+
+    /* Get the program parameters */
+
+    if (xysize) {
+        /* parameters are xbox, ybox, and optional filespec */
+        scale_parm = 0.0;
+        if (argc-1 < 2)
+            pm_error("You must supply at least two parameters with -xysize:\n "
+                     "x and y dimensions of the bounding box.");
+        else if (argc-1 > 3)
+            pm_error("Too many arguments.  With -xysize, you need 2 or 3 "
+                     "arguments.");
+        else {
+            char * endptr;
+            cmdline_p->xbox = strtol(argv[1], &endptr, 10);
+            if (strlen(argv[1]) > 0 && *endptr != '\0')
+                pm_error("horizontal xysize not an integer: '%s'", argv[1]);
+            if (cmdline_p->xbox <= 0)
+                pm_error("horizontal size is not positive: %d", 
+                         cmdline_p->xbox);
+
+            cmdline_p->ybox = strtol(argv[2], &endptr, 10);
+            if (strlen(argv[2]) > 0 && *endptr != '\0')
+                pm_error("vertical xysize not an integer: '%s'", argv[2]);
+            if (cmdline_p->ybox <= 0)
+                pm_error("vertical size is not positive: %d", 
+                         cmdline_p->ybox);
+            
+            if (argc-1 < 3)
+                cmdline_p->input_filespec = "-";
+            else
+                cmdline_p->input_filespec = argv[3];
+        }
+    } else {
+        cmdline_p->xbox = 0;
+        cmdline_p->ybox = 0;
+        
+        if (xsize == -1 && xscale == -1 && ysize == -1 && yscale == -1
+            && pixels == -1 && reduce == -1) {
+            /* parameters are scale factor and optional filespec */
+            if (argc-1 < 1)
+                pm_error("With no dimension options, you must supply at least "
+                         "one parameter: \nthe scale factor.");
+            else {
+                scale_parm = atof(argv[1]);
+
+                if (scale_parm == 0.0)
+                    pm_error("The scale parameter %s is not "
+                             "a positive number.",
+                             argv[1]);
+                else {
+                    if (argc-1 < 2)
+                        cmdline_p->input_filespec = "-";
+                    else
+                        cmdline_p->input_filespec = argv[2];
+                }
+            }
+        } else {
+            /* Only parameter allowed is optional filespec */
+            if (argc-1 < 1)
+                cmdline_p->input_filespec = "-";
+            else
+                cmdline_p->input_filespec = argv[1];
+
+            if (reduce != -1) {
+                scale_parm = ((double) 1.0) / ((double) reduce);
+                pm_message("reducing by %d gives scale factor of %f.", 
+                           reduce, scale_parm);
+            } else
+                scale_parm = 0.0;
+        }
+    }
+
+    cmdline_p->xsize = xsize == -1 ? 0 : xsize;
+    cmdline_p->ysize = ysize == -1 ? 0 : ysize;
+    cmdline_p->pixels = pixels == -1 ? 0 : pixels;
+
+    if (scale_parm) {
+        cmdline_p->xscale = scale_parm;
+        cmdline_p->yscale = scale_parm;
+    } else {
+        cmdline_p->xscale = xscale == -1.0 ? 0.0 : xscale;
+        cmdline_p->yscale = yscale == -1.0 ? 0.0 : yscale;
+    }
+}
+
+
+
+static void
+compute_output_dimensions(const struct cmdline_info cmdline, 
+                          const int rows, const int cols,
+                          int * newrowsP, int * newcolsP) {
+
+    if (cmdline.pixels) {
+        if (rows * cols <= cmdline.pixels) {
+            *newrowsP = rows;
+            *newcolsP = cols;
+        } else {
+            const double scale =
+                sqrt( (float) cmdline.pixels / ((float) cols * (float) rows));
+            *newrowsP = rows * scale;
+            *newcolsP = cols * scale;
+        }
+    } else if (cmdline.xbox) {
+        const double aspect_ratio = (float) cols / (float) rows;
+        const double box_aspect_ratio = 
+            (float) cmdline.xbox / (float) cmdline.ybox;
+        
+        if (box_aspect_ratio > aspect_ratio) {
+            *newrowsP = cmdline.ybox;
+            *newcolsP = *newrowsP * aspect_ratio + 0.5;
+        } else {
+            *newcolsP = cmdline.xbox;
+            *newrowsP = *newcolsP / aspect_ratio + 0.5;
+        }
+    } else {
+        if (cmdline.xsize)
+            *newcolsP = cmdline.xsize;
+        else if (cmdline.xscale)
+            *newcolsP = cmdline.xscale * cols + .5;
+        else if (cmdline.ysize)
+            *newcolsP = cols * ((float) cmdline.ysize/rows) +.5;
+        else
+            *newcolsP = cols;
+
+        if (cmdline.ysize)
+            *newrowsP = cmdline.ysize;
+        else if (cmdline.yscale)
+            *newrowsP = cmdline.yscale * rows +.5;
+        else if (cmdline.xsize)
+            *newrowsP = rows * ((float) cmdline.xsize/cols) +.5;
+        else
+            *newrowsP = rows;
+    }    
+
+    /* If the calculations above yielded (due to rounding) a zero 
+       dimension, we fudge it up to 1.  We do this rather than considering
+       it a specification error (and dying) because it's friendlier to 
+       automated processes that work on arbitrary input.  It saves them
+       having to check their numbers to avoid catastrophe.
+    */
+
+    if (*newcolsP < 1) *newcolsP = 1;
+    if (*newrowsP < 1) *newrowsP = 1;
+}        
+
+
+
+static void
+horizontal_scale(const xel inputxelrow[], xel newxelrow[], 
+                 const int cols, const int newcols, const float xscale, 
+                 const int format, const xelval maxval,
+                 float * const stretchP) {
+/*----------------------------------------------------------------------------
+   Take the input row inputxelrow[], which is 'cols' columns wide, and
+   scale it by a factor of 'xscale', to create
+   the output row newxelrow[], which is 'newcols' columns wide.
+
+   'format' and 'maxval' describe the Netpbm format of the both input and
+   output rows.
+-----------------------------------------------------------------------------*/
+    float r, g, b;
+    float fraccoltofill, fraccolleft;
+    unsigned int col;
+    unsigned int newcol;
+    
+    newcol = 0;
+    fraccoltofill = 1.0;  /* Output column is "empty" now */
+    r = g = b = 0;          /* initial value */
+    for (col = 0; col < cols; ++col) {
+        /* Process one pixel from input ('inputxelrow') */
+        fraccolleft = xscale;
+        /* Output all columns, if any, that can be filled using information
+           from this input column, in addition to what's already in the output
+           column.
+        */
+        while (fraccolleft >= fraccoltofill) {
+            /* Generate one output pixel in 'newxelrow'.  It will consist
+               of anything accumulated from prior input pixels in 'r','g', 
+               and 'b', plus a fraction of the current input pixel.
+            */
+            switch (PNM_FORMAT_TYPE(format)) {
+            case PPM_TYPE:
+                r += fraccoltofill * PPM_GETR(inputxelrow[col]);
+                g += fraccoltofill * PPM_GETG(inputxelrow[col]);
+                b += fraccoltofill * PPM_GETB(inputxelrow[col]);
+                PPM_ASSIGN( newxelrow[newcol], 
+                            MIN(maxval, (int) (r + 0.5)), 
+                            MIN(maxval, (int) (g + 0.5)), 
+                            MIN(maxval, (int) (b + 0.5))
+                    );
+                break;
+
+            default:
+                g += fraccoltofill * PNM_GET1(inputxelrow[col]);
+                PNM_ASSIGN1( newxelrow[newcol], MIN(maxval, (int) (g + 0.5)));
+                break;
+            }
+            fraccolleft -= fraccoltofill;
+            /* Set up to start filling next output column */
+            newcol++;
+            fraccoltofill = 1.0;
+            r = g = b = 0.0;
+        }
+        /* There's not enough left in the current input pixel to fill up 
+           a whole output column, so just accumulate the remainder of the
+           pixel into the current output column.
+        */
+        if (fraccolleft > 0.0) {
+            switch (PNM_FORMAT_TYPE(format)) {
+            case PPM_TYPE:
+                r += fraccolleft * PPM_GETR(inputxelrow[col]);
+                g += fraccolleft * PPM_GETG(inputxelrow[col]);
+                b += fraccolleft * PPM_GETB(inputxelrow[col]);
+                break;
+                    
+            default:
+                g += fraccolleft * PNM_GET1(inputxelrow[col]);
+                break;
+            }
+            fraccoltofill -= fraccolleft;
+        }
+    }
+
+    if (newcol < newcols-1 || newcol > newcols)
+        pm_error("Internal error: last column filled is %d, but %d "
+                 "is the rightmost output column.",
+                 newcol, newcols-1);
+
+    if (newcol < newcols ) {
+        /* We were still working on the last output column when we 
+           ran out of input columns.  This would be because of rounding
+           down, and we should be missing only a tiny fraction of that
+           last output column.
+        */
+
+        *stretchP = fraccoltofill;
+
+        switch (PNM_FORMAT_TYPE(format)) {
+        case PPM_TYPE:
+            r += fraccoltofill * PPM_GETR(inputxelrow[cols-1]);
+            g += fraccoltofill * PPM_GETG(inputxelrow[cols-1]);
+            b += fraccoltofill * PPM_GETB(inputxelrow[cols-1]);
+
+            PPM_ASSIGN(newxelrow[newcol], 
+                       MIN(maxval, (int) (r + 0.5)), 
+                       MIN(maxval, (int) (g + 0.5)), 
+                       MIN(maxval, (int) (b + 0.5))
+                );
+            break;
+                
+        default:
+            g += fraccoltofill * PNM_GET1(inputxelrow[cols-1]);
+            PNM_ASSIGN1( newxelrow[newcol], MIN(maxval, (int) (g + 0.5)));
+            break;
+        }
+    } else 
+        *stretchP = 0;
+}
+
+
+
+static void
+zeroAccum(int const cols, int const format, 
+          float rs[], float gs[], float bs[]) {
+
+    int col;
+
+    for ( col = 0; col < cols; ++col )
+        rs[col] = gs[col] = bs[col] = 0.0;
+}
+
+
+
+static void
+accumOutputRow(xel * const xelrow, float const fraction, 
+               float rs[], float gs[], float bs[], 
+               int const cols, int const format) {
+/*----------------------------------------------------------------------------
+   Take 'fraction' times the color in row xelrow and add it to 
+   rs/gs/bs.  'fraction' is less than 1.0.
+-----------------------------------------------------------------------------*/
+    int col;
+
+    switch ( PNM_FORMAT_TYPE(format) ) {
+    case PPM_TYPE:
+        for ( col = 0; col < cols; ++col ) {
+            rs[col] += fraction * PPM_GETR(xelrow[col]);
+            gs[col] += fraction * PPM_GETG(xelrow[col]);
+            bs[col] += fraction * PPM_GETB(xelrow[col]);
+        }
+        break;
+
+    default:
+        for ( col = 0; col < cols; ++col)
+            gs[col] += fraction * PNM_GET1(xelrow[col]);
+        break;
+    }
+}
+
+
+
+static void
+makeRow(xel * const xelrow, float rs[], float gs[], float bs[],
+        int const cols, xelval const maxval, int const format) {
+/*----------------------------------------------------------------------------
+   Make an xel row at 'xelrow' with format 'format' and
+   maxval 'maxval' out of the color values in 
+   rs[], gs[], and bs[].
+-----------------------------------------------------------------------------*/
+    int col;
+
+    switch ( PNM_FORMAT_TYPE(format) ) {
+    case PPM_TYPE:
+        for ( col = 0; col < cols; ++col) {
+            PPM_ASSIGN(xelrow[col], 
+                       MIN(maxval, (int) (rs[col] + 0.5)), 
+                       MIN(maxval, (int) (gs[col] + 0.5)), 
+                       MIN(maxval, (int) (bs[col] + 0.5))
+                );
+        }
+        break;
+
+    default:
+        for ( col = 0; col < cols; ++col ) {
+            PNM_ASSIGN1(xelrow[col], 
+                        MIN(maxval, (int) (gs[col] + 0.5)));
+        }
+        break;
+    }
+}
+
+
+
+static void
+scaleWithMixing(FILE * const ifP,
+                int const cols, int const rows,
+                xelval const maxval, int const format,
+                int const newcols, int const newrows,
+                xelval const newmaxval, int const newformat,
+                float const xscale, float const yscale,
+                bool const verbose) {
+/*----------------------------------------------------------------------------
+   Scale the image on input file 'ifP' (which is described by 
+   'cols', 'rows', 'format', and 'maxval') by xscale horizontally and
+   yscale vertically and write the result to standard output as format
+   'newformat' and with maxval 'newmaxval'.
+
+   The input file is positioned past the header, to the beginning of the
+   raster.  The output file is too.
+
+   Mix colors from input rows together in the output rows.
+-----------------------------------------------------------------------------*/
+    /* Here's how we think of the color mixing scaling operation:  
+       
+       First, I'll describe scaling in one dimension.  Assume we have
+       a one row image.  A raster row is ordinarily a sequence of
+       discrete pixels which have no width and no distance between
+       them -- only a sequence.  Instead, think of the raster row as a
+       bunch of pixels 1 unit wide adjacent to each other.  For
+       example, we are going to scale a 100 pixel row to a 150 pixel
+       row.  Imagine placing the input row right above the output row
+       and stretching it so it is the same size as the output row.  It
+       still contains 100 pixels, but they are 1.5 units wide each.
+       Our goal is to make the output row look as much as possible
+       like the input row, while observing that a pixel can be only
+       one color.
+
+       Output Pixel 0 is completely covered by Input Pixel 0, so we
+       make Output Pixel 0 the same color as Input Pixel 0.  Output
+       Pixel 1 is covered half by Input Pixel 0 and half by Input
+       Pixel 1.  So we make Output Pixel 1 a 50/50 mix of Input Pixels
+       0 and 1.  If you stand back far enough, input and output will
+       look the same.
+
+       This works for all scale factors, both scaling up and scaling down.
+       
+       This program always stretches or squeezes the input row to be the
+       same length as the output row; The output row's pixels are always
+       1 unit wide.
+
+       The same thing works in the vertical direction.  We think of
+       rows as stacked strips of 1 unit height.  We conceptually
+       stretch the image vertically first (same process as above, but
+       in place of a single-color pixels, we have a vector of colors).
+       Then we take each row this vertical stretching generates and
+       stretch it horizontally.  
+    */
+
+    xel* xelrow;  /* An input row */
+    xel* vertScaledRow;
+        /* An output row after vertical scaling, but before horizontal
+           scaling
+        */
+    xel* newxelrow;
+    float rowsleft;
+        /* The number of rows of output that need to be formed from the
+           current input row (the one in xelrow[]), less the number that 
+           have already been formed (either in the rs/gs/bs accumulators
+           or output to the file).  This can be fractional because of the
+           way we define rows as having height.
+        */
+    float fracrowtofill;
+        /* The fraction of the current output row (the one in vertScaledRow[])
+           that hasn't yet been filled in from an input row.
+        */
+    float *rs, *gs, *bs;
+        /* The red, green, and blue color intensities so far accumulated
+           from input rows for the current output row.
+        */
+    int rowsread;
+        /* Number of rows of the input file that have been read */
+    int row;
+    
+    xelrow = pnm_allocrow(cols); 
+    vertScaledRow = pnm_allocrow(cols);
+    rs = (float*) pm_allocrow( cols, sizeof(float) );
+    gs = (float*) pm_allocrow( cols, sizeof(float) );
+    bs = (float*) pm_allocrow( cols, sizeof(float) );
+    rowsread = 0;
+    rowsleft = 0.0;
+    zeroAccum(cols, format, rs, gs, bs);
+    fracrowtofill = 1.0;
+
+    newxelrow = pnm_allocrow( newcols );
+
+    for ( row = 0; row < newrows; ++row ) {
+        /* First scale Y from xelrow[] into vertScaledRow[]. */
+
+        if ( newrows == rows ) { /* shortcut Y scaling if possible */
+            pnm_readpnmrow( ifP, vertScaledRow, cols, newmaxval, format );
+	    } else {
+            while (fracrowtofill > 0) {
+                if (rowsleft <= 0.0) {
+                    if (rowsread < rows) {
+                        pnm_readpnmrow(ifP, xelrow, cols, newmaxval, format);
+                        ++rowsread;
+                    } else {
+                        /* We need another input row to fill up this
+                           output row, but there aren't any more.
+                           That's because of rounding down on our
+                           scaling arithmetic.  So we go ahead with
+                           the data from the last row we read, which
+                           amounts to stretching out the last output
+                           row.  
+                        */
+                        if (verbose)
+                            pm_message("%f of bottom row stretched due to "
+                                       "arithmetic imprecision", 
+                                       fracrowtofill);
+                    }
+                    rowsleft = yscale;
+                }
+                if (rowsleft < fracrowtofill) {
+                    accumOutputRow(xelrow, rowsleft, rs, gs, bs, 
+                                   cols, format);
+                    fracrowtofill -= rowsleft;
+                    rowsleft = 0.0;
+                } else {
+                    accumOutputRow(xelrow, fracrowtofill, rs, gs, bs,
+                                   cols, format);
+                    rowsleft = rowsleft - fracrowtofill;
+                    fracrowtofill = 0.0;
+                }
+            }
+            makeRow(vertScaledRow, rs, gs, bs, cols, newmaxval, format);
+            zeroAccum(cols, format, rs, gs, bs);
+            fracrowtofill = 1.0;
+	    }
+
+        /* Now scale vertScaledRow horizontally into newxelrow and write
+           it out. 
+        */
+
+        if (newcols == cols)	/* shortcut X scaling if possible */
+            pnm_writepnmrow(stdout, vertScaledRow, newcols, 
+                            newmaxval, newformat, 0);
+        else {
+            float stretch;
+
+            horizontal_scale(vertScaledRow, newxelrow, cols, newcols, xscale, 
+                             format, newmaxval, &stretch);
+            
+            if (verbose && row == 0)
+                pm_message("%f of right column stretched due to "
+                           "arithmetic imprecision", 
+                           stretch);
+            
+            pnm_writepnmrow(stdout, newxelrow, newcols, 
+                            newmaxval, newformat, 0 );
+        }
+	}
+    pnm_freerow(newxelrow);
+    pnm_freerow(xelrow);
+    pnm_freerow(vertScaledRow);
+}
+
+
+
+static void
+scaleWithoutMixing(FILE * const ifP,
+                   int const cols, int const rows,
+                   xelval const maxval, int const format,
+                   int const newcols, int const newrows,
+                   xelval const newmaxval, int const newformat,
+                   float const xscale, float const yscale) {
+/*----------------------------------------------------------------------------
+   Scale the image on input file 'ifP' (which is described by 
+   'cols', 'rows', 'format', and 'maxval') by xscale horizontally and
+   yscale vertically and write the result to standard output as format
+   'newformat' and with maxval 'newmaxval'.
+
+   The input file is positioned past the header, to the beginning of the
+   raster.  The output file is too.
+
+   Don't mix colors from different input pixels together in the output
+   pixels.  Each output pixel is an exact copy of some corresponding 
+   input pixel.
+-----------------------------------------------------------------------------*/
+    xel* xelrow;  /* An input row */
+    xel* newxelrow;
+    int row;
+    int rowInXelrow;
+
+    xelrow = pnm_allocrow(cols); 
+    rowInXelrow = -1;
+
+    newxelrow = pnm_allocrow(newcols);
+
+    for (row = 0; row < newrows; ++row) {
+        int col;
+        
+        int const inputRow = (int) (row / yscale);
+
+        for (; rowInXelrow < inputRow; ++rowInXelrow) 
+            pnm_readpnmrow(ifP, xelrow, cols, newmaxval, format);
+        
+
+        for (col = 0; col < newcols; ++col) {
+            int const inputCol = (int) (col / xscale);
+            
+            newxelrow[col] = xelrow[inputCol];
+        }
+
+        pnm_writepnmrow(stdout, newxelrow, newcols, 
+                        newmaxval, newformat, 0 );
+	}
+    pnm_freerow(xelrow);
+    pnm_freerow(newxelrow);
+}
+
+
+
+int
+main(int argc, char **argv ) {
+
+    struct cmdline_info cmdline;
+    FILE* ifP;
+    int rows, cols, format, newformat, newrows, newcols;
+    xelval maxval, newmaxval;
+    float xscale, yscale;
+
+    pnm_init( &argc, argv );
+
+    parse_command_line(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.input_filespec);
+
+    pnm_readpnminit( ifP, &cols, &rows, &maxval, &format );
+
+    /* Promote PBM files to PGM. */
+    if ( PNM_FORMAT_TYPE(format) == PBM_TYPE ) {
+        newformat = PGM_TYPE;
+        newmaxval = PGM_MAXMAXVAL;
+        pm_message( "promoting from PBM to PGM" );
+	} else {
+        newformat = format;
+        newmaxval = maxval;
+    }
+    compute_output_dimensions(cmdline, rows, cols, &newrows, &newcols);
+
+    /* We round the scale factor down so that we never fill up the 
+       output while (a fractional pixel of) input remains unused.  Instead,
+       we will run out of input while (a fractional pixel of) output is 
+       unfilled -- which is easier for our algorithm to handle.
+       */
+    xscale = (float) newcols / cols;
+    yscale = (float) newrows / rows;
+
+    if (cmdline.verbose) {
+        pm_message("Scaling by %f horizontally to %d columns.", 
+                   xscale, newcols );
+        pm_message("Scaling by %f vertically to %d rows.", 
+                   yscale, newrows);
+    }
+
+    if (xscale * cols < newcols - 1 ||
+        yscale * rows < newrows - 1) 
+        pm_error("Arithmetic precision of this program is inadequate to "
+                 "do the specified scaling.  Use a smaller input image "
+                 "or a slightly different scale factor.");
+
+    pnm_writepnminit(stdout, newcols, newrows, newmaxval, newformat, 0);
+
+    if (cmdline.nomix) 
+        scaleWithoutMixing(ifP, cols, rows, maxval, format,
+                           newcols, newrows, newmaxval, newformat, 
+                           xscale, yscale);
+    else
+        scaleWithMixing(ifP, cols, rows, maxval, format,
+                        newcols, newrows, newmaxval, newformat, 
+                        xscale, yscale, cmdline.verbose);
+
+    pm_close(ifP);
+    pm_close(stdout);
+    
+    exit(0);
+}
diff --git a/editor/pnmscalefixed.c b/editor/pnmscalefixed.c
new file mode 100644
index 00000000..d562c670
--- /dev/null
+++ b/editor/pnmscalefixed.c
@@ -0,0 +1,590 @@
+/* pnmscale.c - read a portable anymap and scale it
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** Modified:
+**
+** June 6, 2001: Christopher W. Boyd <cboyd@pobox.com>
+**               - added -reduce N to allow scaling by integer value
+**                 in this case, scale_comp becomes 1/N and x/yscale
+**                 get set as they should
+**    
+**
+*/
+ 
+#include <math.h>
+#include "pnm.h"
+#include "shhopt.h"
+
+/* The pnm library allows us to code this program without branching cases
+   for PGM and PPM, but we do the branch anyway to speed up processing of 
+   PGM images.
+*/
+
+/* We do all our arithmetic in integers.  In order not to get killed by the
+   rounding, we scale every number up by the factor SCALE, do the 
+   arithmetic, then scale it back down.
+   */
+#define SCALE 4096
+#define HALFSCALE 2048
+
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec;  /* Filespecs of input files */
+    unsigned int xsize;
+    unsigned int ysize;
+    float xscale;
+    float yscale;
+    unsigned int xbox;
+    unsigned int ybox;
+    unsigned int pixels;
+    unsigned int verbose;
+};
+
+
+static void
+parse_command_line(int argc, char ** argv,
+                   struct cmdline_info *cmdline_p) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct *option_def = malloc(100*sizeof(optStruct));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct2 opt;
+
+    unsigned int option_def_index;
+    int xysize, xsize, ysize, pixels;
+    int reduce;
+    float xscale, yscale, scale_parm;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENTRY(0,   "xsize",     OPT_UINT,    &xsize,         0);
+    OPTENTRY(0,   "width",     OPT_UINT,    &xsize,         0);
+    OPTENTRY(0,   "ysize",     OPT_UINT,    &ysize,         0);
+    OPTENTRY(0,   "height",    OPT_UINT,    &ysize,         0);
+    OPTENTRY(0,   "xscale",    OPT_FLOAT,   &xscale,        0);
+    OPTENTRY(0,   "yscale",    OPT_FLOAT,   &yscale,        0);
+    OPTENTRY(0,   "pixels",    OPT_UINT,    &pixels,        0);
+    OPTENTRY(0,   "xysize",    OPT_FLAG,    &xysize,        0);
+    OPTENTRY(0,   "verbose",   OPT_FLAG,    &cmdline_p->verbose,        0);
+    OPTENTRY(0,   "reduce",    OPT_UINT,    &reduce,        0);
+
+    /* Set the defaults. -1 = unspecified */
+    xsize = -1;
+    ysize = -1;
+    xscale = -1.0;
+    yscale = -1.0;
+    pixels = -1;
+    xysize = 0;
+    reduce = -1;
+    cmdline_p->verbose = FALSE;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions2(&argc, argv, opt, 0);
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (xsize == 0)
+        pm_error("-xsize/width must be greater than zero.");
+    if (ysize == 0)
+        pm_error("-ysize/height must be greater than zero.");
+    if (xscale != -1.0 && xscale <= 0.0)
+        pm_error("-xscale must be greater than zero.");
+    if (yscale != -1.0 && yscale <= 0.0)
+        pm_error("-yscale must be greater than zero.");
+    if (reduce <= 0 && reduce != -1)
+        pm_error("-reduce must be greater than zero.");
+
+    if (xsize != -1 && xscale != -1)
+        pm_error("Cannot specify both -xsize/width and -xscale.");
+    if (ysize != -1 && yscale != -1)
+        pm_error("Cannot specify both -ysize/height and -yscale.");
+    
+    if (xysize && 
+        (xsize != -1 || xscale != -1 || ysize != -1 || yscale != -1 || 
+         reduce != -1 || pixels != -1) )
+        pm_error("Cannot specify -xysize with other dimension options.");
+    if (pixels != -1 && 
+        (xsize != -1 || xscale != -1 || ysize != -1 || yscale != -1 ||
+         reduce != -1) )
+        pm_error("Cannot specify -pixels with other dimension options.");
+    if (reduce != -1 && 
+        (xsize != -1 || xscale != -1 || ysize != -1 || yscale != -1) )
+        pm_error("Cannot specify -reduce with other dimension options.");
+
+    if (pixels == 0)
+        pm_error("-pixels must be greater than zero");
+
+    /* Get the program parameters */
+
+    if (xysize) {
+        /* parameters are xbox, ybox, and optional filespec */
+        scale_parm = 0.0;
+        if (argc-1 < 2)
+            pm_error("You must supply at least two parameters with -xysize:\n "
+                     "x and y dimensions of the bounding box.");
+        else if (argc-1 > 3)
+            pm_error("Too many arguments.  With -xysize, you need 2 or 3 "
+                     "arguments.");
+        else {
+            cmdline_p->xbox = atoi(argv[1]);
+            cmdline_p->ybox = atoi(argv[2]);
+            
+            if (argc-1 < 3)
+                cmdline_p->input_filespec = "-";
+            else
+                cmdline_p->input_filespec = argv[3];
+        }
+    } else {
+        cmdline_p->xbox = 0;
+        cmdline_p->ybox = 0;
+        
+        if (xsize == -1 && xscale == -1 && ysize == -1 && yscale == -1
+            && pixels == -1 && reduce == -1) {
+            /* parameters are scale factor and optional filespec */
+            if (argc-1 < 1)
+                pm_error("With no dimension options, you must supply at least "
+                         "one parameter: \nthe scale factor.");
+            else {
+                scale_parm = atof(argv[1]);
+
+                if (scale_parm == 0.0)
+                    pm_error("The scale parameter %s is not "
+                             "a positive number.",
+                             argv[1]);
+                else {
+                    if (argc-1 < 2)
+                        cmdline_p->input_filespec = "-";
+                    else
+                        cmdline_p->input_filespec = argv[2];
+                }
+            }
+        } else {
+            /* Only parameter allowed is optional filespec */
+            if (argc-1 < 1)
+                cmdline_p->input_filespec = "-";
+            else
+                cmdline_p->input_filespec = argv[1];
+
+            if (reduce != -1) {
+                scale_parm = ((double) 1.0) / ((double) reduce);
+                pm_message("reducing by %d gives scale factor of %f.", 
+                           reduce, scale_parm);
+            } else
+                scale_parm = 0.0;
+        }
+    }
+
+    cmdline_p->xsize = xsize == -1 ? 0 : xsize;
+    cmdline_p->ysize = ysize == -1 ? 0 : ysize;
+    cmdline_p->pixels = pixels == -1 ? 0 : pixels;
+
+    if (scale_parm) {
+        cmdline_p->xscale = scale_parm;
+        cmdline_p->yscale = scale_parm;
+    } else {
+        cmdline_p->xscale = xscale == -1.0 ? 0.0 : xscale;
+        cmdline_p->yscale = yscale == -1.0 ? 0.0 : yscale;
+    }
+}
+
+
+
+static void
+compute_output_dimensions(const struct cmdline_info cmdline, 
+                          const int rows, const int cols,
+                          int * newrowsP, int * newcolsP) {
+
+    if (cmdline.pixels) {
+        if (rows * cols <= cmdline.pixels) {
+            *newrowsP = rows;
+            *newcolsP = cols;
+        } else {
+            const double scale =
+                sqrt( (float) cmdline.pixels / ((float) cols * (float) rows));
+            *newrowsP = rows * scale;
+            *newcolsP = cols * scale;
+        }
+    } else if (cmdline.xbox) {
+        const double aspect_ratio = (float) cols / (float) rows;
+        const double box_aspect_ratio = 
+            (float) cmdline.xbox / (float) cmdline.ybox;
+        
+        if (box_aspect_ratio > aspect_ratio) {
+            *newrowsP = cmdline.ybox;
+            *newcolsP = *newrowsP * aspect_ratio + 0.5;
+        } else {
+            *newcolsP = cmdline.xbox;
+            *newrowsP = *newcolsP / aspect_ratio + 0.5;
+        }
+    } else {
+        if (cmdline.xsize)
+            *newcolsP = cmdline.xsize;
+        else if (cmdline.xscale)
+            *newcolsP = cmdline.xscale * cols + .5;
+        else if (cmdline.ysize)
+            *newcolsP = cols * ((float) cmdline.ysize/rows) +.5;
+        else
+            *newcolsP = cols;
+
+        if (cmdline.ysize)
+            *newrowsP = cmdline.ysize;
+        else if (cmdline.yscale)
+            *newrowsP = cmdline.yscale * rows +.5;
+        else if (cmdline.xsize)
+            *newrowsP = rows * ((float) cmdline.xsize/cols) +.5;
+        else
+            *newrowsP = rows;
+    }    
+
+    /* If the calculations above yielded (due to rounding) a zero 
+       dimension, we fudge it up to 1.  We do this rather than considering
+       it a specification error (and dying) because it's friendlier to 
+       automated processes that work on arbitrary input.  It saves them
+       having to check their numbers to avoid catastrophe.
+    */
+
+    if (*newcolsP < 1) *newcolsP = 1;
+    if (*newrowsP < 1) *newrowsP = 1;
+}        
+
+
+
+static void
+horizontal_scale(const xel inputxelrow[], xel newxelrow[], 
+                 const int cols, const int newcols, const long sxscale, 
+                 const int format, const xelval maxval,
+                 int * stretchP) {
+/*----------------------------------------------------------------------------
+   Take the input row inputxelrow[], which is 'cols' columns wide, and
+   scale it by a factor of 'sxcale', which is in SCALEths to create
+   the output row newxelrow[], which is 'newcols' columns wide.
+
+   'format' and 'maxval' describe the Netpbm format of the both input and
+   output rows.
+
+   *stretchP is the number of columns (could be fractional) on the right 
+   that we had to fill by stretching due to rounding problems.
+-----------------------------------------------------------------------------*/
+    long r, g, b;
+    long fraccoltofill, fraccolleft;
+    unsigned int col;
+    unsigned int newcol;
+    
+    newcol = 0;
+    fraccoltofill = SCALE;  /* Output column is "empty" now */
+    r = g = b = 0;          /* initial value */
+    for (col = 0; col < cols; ++col) {
+        /* Process one pixel from input ('inputxelrow') */
+        fraccolleft = sxscale;
+        /* Output all columns, if any, that can be filled using information
+           from this input column, in addition what's already in the output
+           column.
+        */
+        while (fraccolleft >= fraccoltofill) {
+            /* Generate one output pixel in 'newxelrow'.  It will consist
+               of anything accumulated from prior input pixels in 'r','g', 
+               and 'b', plus a fraction of the current input pixel.
+            */
+            switch (PNM_FORMAT_TYPE(format)) {
+            case PPM_TYPE:
+                r += fraccoltofill * PPM_GETR(inputxelrow[col]);
+                g += fraccoltofill * PPM_GETG(inputxelrow[col]);
+                b += fraccoltofill * PPM_GETB(inputxelrow[col]);
+                r /= SCALE;
+                if ( r > maxval ) r = maxval;
+                g /= SCALE;
+                if ( g > maxval ) g = maxval;
+                b /= SCALE;
+                if ( b > maxval ) b = maxval;
+                PPM_ASSIGN( newxelrow[newcol], r, g, b );
+                break;
+
+            default:
+                g += fraccoltofill * PNM_GET1(inputxelrow[col]);
+                g /= SCALE;
+                if ( g > maxval ) g = maxval;
+                PNM_ASSIGN1( newxelrow[newcol], g );
+                break;
+            }
+            fraccolleft -= fraccoltofill;
+            /* Set up to start filling next output column */
+            newcol++;
+            fraccoltofill = SCALE;
+            r = g = b = 0;
+        }
+        /* There's not enough left in the current input pixel to fill up 
+           a whole output column, so just accumulate the remainder of the
+           pixel into the current output column.
+        */
+        if (fraccolleft > 0) {
+            switch (PNM_FORMAT_TYPE(format)) {
+            case PPM_TYPE:
+                r += fraccolleft * PPM_GETR(inputxelrow[col]);
+                g += fraccolleft * PPM_GETG(inputxelrow[col]);
+                b += fraccolleft * PPM_GETB(inputxelrow[col]);
+                break;
+                    
+            default:
+                g += fraccolleft * PNM_GET1(inputxelrow[col]);
+                break;
+            }
+            fraccoltofill -= fraccolleft;
+        }
+    }
+
+    *stretchP = 0;   /* initial value */
+    while (newcol < newcols) {
+        /* We ran out of input columns before we filled up the output
+           columns.  This would be because of rounding down.  For small
+           images, we're probably missing only a tiny fraction of a column, 
+           but for large images, it could be multiple columns.
+
+           So we fake the remaining output columns by copying the rightmost
+           legitimate pixel.  We call this stretching.
+           */
+
+        *stretchP += fraccoltofill;
+
+        switch (PNM_FORMAT_TYPE(format)) {
+        case PPM_TYPE:
+            r += fraccoltofill * PPM_GETR(inputxelrow[cols-1]);
+            g += fraccoltofill * PPM_GETG(inputxelrow[cols-1]);
+            b += fraccoltofill * PPM_GETB(inputxelrow[cols-1]);
+
+            r += HALFSCALE;  /* for rounding */
+            r /= SCALE;
+            if ( r > maxval ) r = maxval;
+            g += HALFSCALE;  /* for rounding */
+            g /= SCALE;
+            if ( g > maxval ) g = maxval;
+            b += HALFSCALE;  /* for rounding */
+            b /= SCALE;
+            if ( b > maxval ) b = maxval;
+            PPM_ASSIGN(newxelrow[newcol], r, g, b );
+            break;
+                
+        default:
+            g += fraccoltofill * PNM_GET1(inputxelrow[cols-1]);
+            g += HALFSCALE;  /* for rounding */
+            g /= SCALE;
+            if ( g > maxval ) g = maxval;
+            PNM_ASSIGN1(newxelrow[newcol], g );
+            break;
+        }
+        newcol++;
+        fraccoltofill = SCALE;
+    }
+}
+
+
+int
+main(int argc, char **argv ) {
+
+    struct cmdline_info cmdline;
+    FILE* ifp;
+    xel* xelrow;
+    xel* tempxelrow;
+    xel* newxelrow;
+    xel* xP;
+    xel* nxP;
+    int rows, cols, format, newformat, rowsread, newrows, newcols;
+    int row, col, needtoreadrow;
+    xelval maxval, newmaxval;
+    long sxscale, syscale;
+    long fracrowtofill, fracrowleft;
+    long* rs;
+    long* gs;
+    long* bs;
+    int vertical_stretch;
+        /* The number of rows we had to fill by stretching because of 
+           rounding error, which made us run out of input rows before we
+           had filled up the output rows.
+           */
+
+    pnm_init( &argc, argv );
+
+    parse_command_line(argc, argv, &cmdline);
+
+    ifp = pm_openr(cmdline.input_filespec);
+
+    pnm_readpnminit( ifp, &cols, &rows, &maxval, &format );
+
+    /* Promote PBM files to PGM. */
+    if ( PNM_FORMAT_TYPE(format) == PBM_TYPE ) {
+        newformat = PGM_TYPE;
+        newmaxval = PGM_MAXMAXVAL;
+        pm_message( "promoting from PBM to PGM" );
+	}  else {
+        newformat = format;
+        newmaxval = maxval;
+    }
+    compute_output_dimensions(cmdline, rows, cols, &newrows, &newcols);
+
+    /* We round the scale factor down so that we never fill up the
+       output while (a fractional pixel of) input remains unused.
+       Instead, we will run out of input while some of the output is
+       unfilled.  We can address that by stretching, whereas the other
+       case would require throwing away some of the input.
+    */
+    sxscale = SCALE * newcols / cols;
+    syscale = SCALE * newrows / rows;
+
+    if (cmdline.verbose) {
+        pm_message("Scaling by %ld/%d = %f horizontally to %d columns.", 
+                   sxscale, SCALE, (float) sxscale/SCALE, newcols );
+        pm_message("Scaling by %ld/%d = %f vertically to %d rows.", 
+                   syscale, SCALE, (float) syscale/SCALE, newrows);
+    }
+
+    xelrow = pnm_allocrow(cols);
+    if (newrows == rows)	/* shortcut Y scaling if possible */
+        tempxelrow = xelrow;
+    else
+        tempxelrow = pnm_allocrow( cols );
+    rs = (long*) pm_allocrow( cols, sizeof(long) );
+    gs = (long*) pm_allocrow( cols, sizeof(long) );
+    bs = (long*) pm_allocrow( cols, sizeof(long) );
+    rowsread = 0;
+    fracrowleft = syscale;
+    needtoreadrow = 1;
+    for ( col = 0; col < cols; ++col )
+	rs[col] = gs[col] = bs[col] = HALFSCALE;
+    fracrowtofill = SCALE;
+    vertical_stretch = 0;
+    
+    pnm_writepnminit( stdout, newcols, newrows, newmaxval, newformat, 0 );
+    newxelrow = pnm_allocrow( newcols );
+    
+    for ( row = 0; row < newrows; ++row ) {
+        /* First scale vertically from xelrow into tempxelrow. */
+        if ( newrows == rows ) { /* shortcut vertical scaling if possible */
+            pnm_readpnmrow( ifp, xelrow, cols, newmaxval, format );
+	    } else {
+            while ( fracrowleft < fracrowtofill ) {
+                if ( needtoreadrow )
+                    if ( rowsread < rows ) {
+                        pnm_readpnmrow( ifp, xelrow, cols, newmaxval, format );
+                        ++rowsread;
+                    }
+                switch ( PNM_FORMAT_TYPE(format) ) {
+                case PPM_TYPE:
+                    for ( col = 0, xP = xelrow; col < cols; ++col, ++xP ) {
+                        rs[col] += fracrowleft * PPM_GETR( *xP );
+                        gs[col] += fracrowleft * PPM_GETG( *xP );
+                        bs[col] += fracrowleft * PPM_GETB( *xP );
+                    }
+                    break;
+
+                default:
+                    for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+                        gs[col] += fracrowleft * PNM_GET1( *xP );
+                    break;
+                }
+                fracrowtofill -= fracrowleft;
+                fracrowleft = syscale;
+                needtoreadrow = 1;
+            }
+            /* Now fracrowleft is >= fracrowtofill, so we can produce a row. */
+            if ( needtoreadrow ) {
+                if ( rowsread < rows ) {
+                    pnm_readpnmrow( ifp, xelrow, cols, newmaxval, format );
+                    ++rowsread;
+                    needtoreadrow = 0;
+                } else {
+                    /* We need another input row to fill up this output row,
+                       but there aren't any more.  That's because of rounding
+                       down on our scaling arithmetic.  So we go ahead with 
+                       the data from the last row we read, which amounts to 
+                       stretching out the last output row.
+                    */
+                    vertical_stretch += fracrowtofill;
+                }
+            }
+            switch ( PNM_FORMAT_TYPE(format) ) {
+            case PPM_TYPE:
+                for ( col = 0, xP = xelrow, nxP = tempxelrow;
+                      col < cols; ++col, ++xP, ++nxP ) {
+                    register long r, g, b;
+
+                    r = rs[col] + fracrowtofill * PPM_GETR( *xP );
+                    g = gs[col] + fracrowtofill * PPM_GETG( *xP );
+                    b = bs[col] + fracrowtofill * PPM_GETB( *xP );
+                    r /= SCALE;
+                    if ( r > newmaxval ) r = newmaxval;
+                    g /= SCALE;
+                    if ( g > newmaxval ) g = newmaxval;
+                    b /= SCALE;
+                    if ( b > newmaxval ) b = newmaxval;
+                    PPM_ASSIGN( *nxP, r, g, b );
+                    rs[col] = gs[col] = bs[col] = HALFSCALE;
+                }
+                break;
+
+            default:
+                for ( col = 0, xP = xelrow, nxP = tempxelrow;
+                      col < cols; ++col, ++xP, ++nxP ) {
+                    register long g;
+                    
+                    g = gs[col] + fracrowtofill * PNM_GET1( *xP );
+                    g /= SCALE;
+                    if ( g > newmaxval ) g = newmaxval;
+                    PNM_ASSIGN1( *nxP, g );
+                    gs[col] = HALFSCALE;
+                }
+                break;
+            }
+            fracrowleft -= fracrowtofill;
+            if ( fracrowleft == 0 ) {
+                fracrowleft = syscale;
+                needtoreadrow = 1;
+            }
+            fracrowtofill = SCALE;
+	    }
+
+        /* Now scale tempxelrow horizontally into newxelrow & write it out. */
+
+        if (newcols == cols)	/* shortcut X scaling if possible */
+            pnm_writepnmrow(stdout, tempxelrow, newcols, 
+                            newmaxval, newformat, 0);
+        else {
+            int stretch;
+
+            horizontal_scale(tempxelrow, newxelrow, cols, newcols, sxscale, 
+                             format, newmaxval, &stretch);
+            
+            if (cmdline.verbose && row == 0 && stretch != 0)
+                pm_message("%d/%d = %f right columns filled by stretching "
+                           "due to arithmetic imprecision", 
+                           stretch, SCALE, (float) stretch/SCALE);
+            
+            pnm_writepnmrow(stdout, newxelrow, newcols, 
+                            newmaxval, newformat, 0 );
+        }
+	}
+
+    if (cmdline.verbose && vertical_stretch != 0)
+        pm_message("%d/%d = %f bottom rows filled by stretching due to "
+                   "arithmetic imprecision", 
+                   vertical_stretch, SCALE, 
+                   (float) vertical_stretch/SCALE);
+    
+    pm_close( ifp );
+    pm_close( stdout );
+    
+    exit( 0 );
+}
diff --git a/editor/pnmshear.c b/editor/pnmshear.c
new file mode 100644
index 00000000..1b2d36a8
--- /dev/null
+++ b/editor/pnmshear.c
@@ -0,0 +1,227 @@
+/* pnmshear.c - read a portable anymap and shear it by some angle
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _XOPEN_SOURCE   /* get M_PI in math.h */
+
+#include <math.h>
+#include <string.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+
+#define SCALE 4096
+#define HALFSCALE 2048
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *       input_filespec;  /* Filespec of input file */
+    double       angle;           /* requested shear angle, in radians */
+    unsigned int noantialias;     /* -noantialias option */
+};
+
+
+
+static void
+parse_command_line(int argc, char ** argv,
+                   struct cmdline_info *cmdlineP) {
+
+    optStruct3 opt;
+    unsigned int option_def_index = 0;
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+
+    OPTENT3(0, "noantialias",      OPT_FLAG,  NULL, &cmdlineP->noantialias, 0);
+    
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;
+    opt.allowNegNum = TRUE;
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    
+    if (argc-1 < 1)
+        pm_error("Need an argument:  the shear angle.\n");
+    else {
+        char *endptr;
+        cmdlineP->angle = strtod(argv[1], &endptr) * M_PI / 180;
+        if (*endptr != '\0' || strlen(argv[1]) == 0)
+            pm_error("Angle argument is not a valid floating point number: "
+                     "'%s'", argv[1]);
+        if (argc-1 < 2)
+            cmdlineP->input_filespec = "-";
+        else {
+            cmdlineP->input_filespec = argv[2];
+            if (argc-1 > 2)
+                pm_error("too many arguments (%d).  "
+                         "The only arguments are shear angle and filespec.",
+                         argc-1);
+        }
+    }
+}
+
+
+static void
+makeNewXel(xel * const outputXelP, xel const curXel, xel const prevXel,
+           double const fracnew0, double const omfracnew0, int const format) {
+/*----------------------------------------------------------------------------
+   Create an output xel as *outputXel, which is part curXel and part
+   prevXel, the part given by the fractions omfracnew0 and fracnew0,
+   respectively.  These fraction values are the numerator of a fraction
+   whose denominator is SCALE.
+
+   The format of the pixel is 'format'.
+-----------------------------------------------------------------------------*/
+
+    switch ( PNM_FORMAT_TYPE(format) ) {
+    case PPM_TYPE:
+        PPM_ASSIGN( *outputXelP,
+                    ( fracnew0 * PPM_GETR(prevXel) 
+                      + omfracnew0 * PPM_GETR(curXel) 
+                      + HALFSCALE ) / SCALE,
+                    ( fracnew0 * PPM_GETG(prevXel) 
+                      + omfracnew0 * PPM_GETG(curXel) 
+                      + HALFSCALE ) / SCALE,
+                    ( fracnew0 * PPM_GETB(prevXel) 
+                      + omfracnew0 * PPM_GETB(curXel) 
+                      + HALFSCALE ) / SCALE );
+        break;
+        
+    default:
+        PNM_ASSIGN1( *outputXelP,
+                     ( fracnew0 * PNM_GET1(prevXel) 
+                       + omfracnew0 * PNM_GET1(curXel) 
+                       + HALFSCALE ) / SCALE );
+        break;
+    }
+}
+
+
+static void
+shear_row(xel * const xelrow, int const cols, 
+          xel * const newxelrow, int const newcols, 
+          double const shearCols,
+          int const format, xel const bgxel, bool const antialias) {
+/*----------------------------------------------------------------------------
+   Shear the row 'xelrow' by 'shearCols' columns, and return the result as
+   'newxelrow'.  They are 'cols' and 'newcols' columns wide, respectively.
+   
+   Fill in the part of the output row that doesn't contain image data with
+   'bgxel'.
+
+   Use antialiasing iff 'antialias'.
+
+   The format of the input xels (which implies something about the
+   output xels too) is 'format'.
+-----------------------------------------------------------------------------*/
+    int const intShearCols = (int) shearCols;
+        
+    if ( antialias ) {
+        const long fracnew0 = ( shearCols - intShearCols ) * SCALE;
+        const long omfracnew0 = SCALE - fracnew0;
+
+        int col;
+        xel prevXel;
+            
+        for ( col = 0; col < newcols; ++col )
+            newxelrow[col] = bgxel;
+
+        prevXel = bgxel;
+        for ( col = 0; col < cols; ++col){
+            makeNewXel(&newxelrow[intShearCols + col],
+                       xelrow[col], prevXel, fracnew0, omfracnew0,
+                       format);
+            prevXel = xelrow[col];
+        }
+        if ( fracnew0 > 0 ) 
+            /* Need to add a column for what's left over */
+            makeNewXel(&newxelrow[intShearCols + cols],
+                       bgxel, prevXel, fracnew0, omfracnew0, format);
+    } else {
+        int col;
+        for ( col = 0; col < intShearCols; ++col )
+            newxelrow[col] = bgxel;
+        for ( col = 0; col < cols; ++col )
+            newxelrow[intShearCols+col] = xelrow[col];
+        for ( col = intShearCols + cols; col < newcols; ++col )
+            newxelrow[col] = bgxel;
+    }
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+    FILE* ifp;
+    xel* xelrow;
+    xel* newxelrow;
+    xel bgxel;
+    int rows, cols, format; 
+    int newformat, newcols; 
+    int row;
+    xelval maxval, newmaxval;
+    double shearfac;
+
+    struct cmdline_info cmdline;
+
+    pnm_init( &argc, argv );
+
+    parse_command_line( argc, argv, &cmdline );
+
+    ifp = pm_openr( cmdline.input_filespec );
+
+    pnm_readpnminit( ifp, &cols, &rows, &maxval, &format );
+    xelrow = pnm_allocrow( cols );
+
+    /* Promote PBM files to PGM. */
+    if ( !cmdline.noantialias && PNM_FORMAT_TYPE(format) == PBM_TYPE ) {
+        newformat = PGM_TYPE;
+        newmaxval = PGM_MAXMAXVAL;
+        pm_message( "promoting from PBM to PGM - "
+                    "use -noantialias to avoid this" );
+    } else {
+        newformat = format;
+        newmaxval = maxval;
+    }
+
+    shearfac = tan( cmdline.angle );
+    if ( shearfac < 0.0 )
+        shearfac = -shearfac;
+
+    newcols = rows * shearfac + cols + 0.999999;
+
+    pnm_writepnminit( stdout, newcols, rows, newmaxval, newformat, 0 );
+    newxelrow = pnm_allocrow( newcols );
+
+    bgxel = pnm_backgroundxelrow( xelrow, cols, newmaxval, format );
+
+    for ( row = 0; row < rows; ++row ) {
+        double shearCols;
+
+        pnm_readpnmrow( ifp, xelrow, cols, newmaxval, format );
+
+        if ( cmdline.angle > 0.0 )
+            shearCols = row * shearfac;
+        else
+            shearCols = ( rows - row ) * shearfac;
+
+        shear_row(xelrow, cols, newxelrow, newcols, 
+                  shearCols, format, bgxel, !cmdline.noantialias);
+
+        pnm_writepnmrow( stdout, newxelrow, newcols, newmaxval, newformat, 0 );
+    }
+
+    pm_close( ifp );
+    pm_close( stdout );
+
+    exit( 0 );
+}
+
diff --git a/editor/pnmsmooth.README b/editor/pnmsmooth.README
new file mode 100644
index 00000000..fc5329bd
--- /dev/null
+++ b/editor/pnmsmooth.README
@@ -0,0 +1,21 @@
+README for pnmsmooth.c 2.0
+
+This is a replacement for the pnmsmooth script that is distributed with
+pbmplus/netpbm.  This version of pnmsmooth is written as a C program rather
+than a shell script.  It accepts command line arguments to specify a size 
+other than 3x3 for the convolution matrix and an argument for dumping the 
+resultant convolution matrix as a PGM file.  By default it uses the same 3x3
+matrix size as the pnmsmooth script and can be used as a direct replacement.
+
+Also included are an updated man page and a patch file to update the Imakefile
+in the pnm directory.  You may want to apply the patches by hand if you have
+already modified the Imakefile to add other new programs.   You will then
+have to remake the Makefiles and then type 'make all' in the pnm directory.
+
+- Mike
+
+----------------------------------------------------------------------------
+Mike Burns                                              System Administrator
+burns@chem.psu.edu                                   Department of Chemistry
+(814) 863-2123                             The Pennsylvania State University
+
diff --git a/editor/pnmsmooth.c b/editor/pnmsmooth.c
new file mode 100644
index 00000000..a18511c7
--- /dev/null
+++ b/editor/pnmsmooth.c
@@ -0,0 +1,241 @@
+/* pnmsmooth.c - smooth out an image by replacing each pixel with the 
+**               average of its width x height neighbors.
+**
+** Version 2.0   December 5, 1994
+**
+** Copyright (C) 1994 by Mike Burns (burns@chem.psu.edu)
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/*
+  Written by Mike Burns December 5, 1994 and called Version 2.0.
+  Based on ideas from a shell script by Jef Poskanzer, 1989, 1991.
+  The shell script had no options.
+*/
+
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "pnm.h"
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+    unsigned int width;
+    unsigned int height;
+    const char * dump;
+};
+
+
+
+static void
+parseCommandLine (int argc, char ** argv,
+                  struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int widthSpec, heightSpec, dumpSpec, sizeSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+    
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "dump",          OPT_STRING,   
+            &cmdlineP->dump,            &dumpSpec, 0);
+    OPTENT3(0,   "width",         OPT_UINT,
+            &cmdlineP->width,           &widthSpec, 0);
+    OPTENT3(0,   "height",        OPT_UINT,
+            &cmdlineP->height,          &heightSpec, 0);
+    OPTENT3(0,   "size",          OPT_FLAG,
+            NULL,                       &sizeSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (!widthSpec)
+        cmdlineP->width = 3;
+
+    if (!heightSpec)
+        cmdlineP->height = 3;
+
+    if (!dumpSpec)
+        cmdlineP->dump = NULL;
+
+    if (sizeSpec) {
+        /* -size is strictly for backward compatibility.  This program
+           used to use a different command line processor and had
+           irregular syntax in which the -size option had two values,
+           e.g. "-size <width> <height>" And the options had to go
+           before the arguments.  So an old pnmsmooth command looks to us
+           like a command with the -size flag option and the first two
+           arguments being the width and height.
+        */
+
+        if (widthSpec || heightSpec)
+            pm_error("-size is obsolete.  Use -width and -height instead");
+
+        if (argc-1 > 3)
+            pm_error("Too many arguments.  With -size, there are at most "
+                     "3 arguments.");
+        else if (argc-1 < 2)
+            pm_error("Not enough arguments.  With -size, the first two "
+                     "arguments are width and height");
+        else {
+            cmdlineP->width  = atoi(argv[1]);
+            cmdlineP->height = atoi(argv[2]);
+            
+            if (argc-1 < 3)
+                cmdlineP->inputFilespec = "-";
+            else
+                cmdlineP->inputFilespec = argv[3];
+        }
+    } else {
+        if (argc-1 > 1)
+            pm_error("Program takes at most one argument: the input file "
+                     "specification.  "
+                     "You specified %d arguments.", argc-1);
+        if (argc-1 < 1)
+            cmdlineP->inputFilespec = "-";
+        else
+            cmdlineP->inputFilespec = argv[1];
+    }
+    if (cmdlineP->width % 2 != 1)
+        pm_error("The convolution matrix must have an odd number of columns.  "
+                 "You specified %u", cmdlineP->width);
+
+    if (cmdlineP->height % 2 != 1)
+        pm_error("The convolution matrix must have an odd number of rows.  "
+                 "You specified %u", cmdlineP->height);
+}
+
+
+
+static void
+writeConvolutionImage(FILE *       const cofp,
+                      unsigned int const cols,
+                      unsigned int const rows,
+                      int          const format) {
+
+    xelval const convmaxval = rows * cols * 2;
+        /* normalizing factor for our convolution matrix */
+    xelval const g = rows * cols + 1;
+        /* weight of all pixels in our convolution matrix */
+    int row;
+    xel *outputrow;
+
+    if (convmaxval > PNM_OVERALLMAXVAL)
+        pm_error("The convolution matrix is too large.  "
+                 "Width x Height x 2\n"
+                 "must not exceed %d and it is %d.",
+                 PNM_OVERALLMAXVAL, convmaxval);
+
+    pnm_writepnminit(cofp, cols, rows, convmaxval, format, 0);
+    outputrow = pnm_allocrow(cols);
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cols; ++col)
+            PNM_ASSIGN1(outputrow[col], g);
+        pnm_writepnmrow(cofp, outputrow, cols, convmaxval, format, 0);
+    }
+    pnm_freerow(outputrow);
+}
+
+
+
+static void
+runPnmconvol(const char * const inputFilespec,
+             const char * const convolutionImageFilespec) {
+
+    /* fork a Pnmconvol process */
+    pid_t rc;
+
+    rc = fork();
+    if (rc < 0)
+        pm_error("fork() failed.  errno=%d (%s)", errno, strerror(errno));
+    else if (rc == 0) {
+        /* child process executes following code */
+
+        execlp("pnmconvol",
+               "pnmconvol", convolutionImageFilespec, inputFilespec,
+               NULL);
+
+        pm_error("error executing pnmconvol command.  errno=%d (%s)",
+                 errno, strerror(errno));
+    } else {
+        /* This is the parent */
+        pid_t const childPid = rc;
+
+        int status;
+
+        /* wait for child to finish */
+        while (wait(&status) != childPid);
+    }
+}
+
+
+
+int
+main(int argc, char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE * convFileP;
+    const char * tempfileName;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    if (cmdline.dump)
+        convFileP = pm_openw(cmdline.dump);
+    else
+        pm_make_tmpfile(&convFileP, &tempfileName);
+        
+    writeConvolutionImage(convFileP, cmdline.width, cmdline.height,
+                          PGM_FORMAT);
+
+    pm_close(convFileP);
+
+    if (cmdline.dump) {
+        /* We're done.  Convolution image is in user's file */
+    } else {
+        runPnmconvol(cmdline.inputFilespec, tempfileName);
+
+        unlink(tempfileName);
+        strfree(tempfileName);
+    }
+    return 0;
+}
diff --git a/editor/pnmstitch.c b/editor/pnmstitch.c
new file mode 100644
index 00000000..61f02a04
--- /dev/null
+++ b/editor/pnmstitch.c
@@ -0,0 +1,2408 @@
+/*
+ * Copyright (c) 2002 Mark Salyzyn
+ * All rights reserved.
+ *
+ * TERMS AND CONDITIONS OF USE
+ *
+ * Redistribution and use in source form, with or without modification, are
+ * permitted provided that redistributions of source code must retain the
+ * above copyright notice, this list of conditions and the following
+ * disclaimer.
+ *
+ * This software is provided `as is' by Mark Salyzyn and any express or implied
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose, are disclaimed. In no
+ * event shall Mark Salyzyn be liable for any direct, indirect, incidental,
+ * special, exemplary or consequential damages (including, but not limited to,
+ * procurement of substitute goods or services; loss of use, data, or profits;
+ * or business interruptions) however caused and on any theory of liability,
+ * whether in contract, strict liability, or tort (including negligence or
+ * otherwise) arising in any way out of the use of this software, even if
+ * advised of the possibility of such damage.
+ *
+ * Any restrictions or encumberances added to this source code or derivitives,
+ * is prohibited.
+ *
+ *  Name: pnmstitch.c
+ *  Description: Automated panoramic stitcher.
+ *      Many digital Cameras have a panorama mode where they hold on to
+ *    the right hand side of an image, shifted to the left hand side of their
+ *    view screen for subsequent pictures, facilitating the manual stitching
+ *    up of a panoramic shot. These same cameras are shipped with software
+ *    to manually or automatically stitch images together into the composite
+ *    image. However, these programs are dedicated for a specific OS.
+ *    In the pnmstitch program, it analyzes the match between the images,
+ *    generates a transform, processes the transform on the images, and
+ *    blends the overlapping regions. In addition, there is an output filter
+ *    to process automatic cropping of the resultant image.
+ *      The stitching software here works by constraining the right
+ *    hand side of the right hand image as `fixed' per-se, after offset
+ *    evaluation and only the left hand side of the right hand image is
+ *    mangled. Thus, the algorithm is optimized for stitching a right hand
+ *    image to the right hand half (half being a loose term) of the left hand
+ *    image.
+ *  Author: Mark Salyzyn <mark@bohica.net>  June 2002
+ *  Version: 0.0.4
+ *
+ *  Modifications: 0.0.4  July 31 2002 Mark Salyzyn <mark@bohica.net>
+ *                                  &  Bryan Henderson <bryanh@giraffe-data.com>
+ *      - FreeBSD port.
+ *      - merge changes to incorporate into netpbm tree.
+ *  Modifications: 0.0.3  July 27 2002  Mark Salyzyn <mark@bohica.net>
+ *                                  &   "George M. Sipe" <geo@sipe.org>
+ *      - Deal with subtle differences between BSD and GNU getopt
+ *        facilitating the Linux port.
+ *  Modifications: 0.0.2  July 25 2002  Mark Salyzyn <mark@bohica.net>
+ *      - RotateSliver needs to use higher resolution match.
+ *      - RotateCrop code interfered with StraightThrough code
+ *        resulting in an incorrect pnm image output.
+ *  Modifications: 0.0.1  July 18 2002  Mark Salyzyn <mark@bohica.net>
+ *      - Added BiLinearSliver, RotateSliver and HorizontalCrop
+ *
+ *  ToDo:
+ *      - Split this into multiple files ... nah, keep it in one to
+ *        keep it from polluting the netpbm tree.
+ *      - Add and refine the videorbits algorithm. One piece of public
+ *        domain software that is pnm aware is called videorbits. It
+ *        needs considerably more overlap than the Digital Cameras set
+ *        up (of course, anyone can to a panorama with more overlap
+ *        with our without the feature) as it uses what is called video
+ *        flow to generate the match. It is designed more for a series
+ *        of images from a video camera. videorbits has three programs,
+ *        one generates the transform, the next processes the
+ *        transform, and the final blends the images together much as
+ *        this one piece program does.
+ *      - speedups in matching algorithm
+ *      - refinement in accuracy of matching algorithm
+ *      - Add RotateCrop filter algorithm (in-memory copy of image,
+ *        detect least loss horizontal crop on a rotated image).
+ *      - pnmstitch should be generalized to handle transformation
+ *        occuring on the left image, currently it blends assuming
+ *        that there is no transformation effects on the left image.
+ *      - user selectable blending algorithms?
+ */
+
+#define _BSD_SOURCE 1   /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "mallocvar.h"
+#include "pam.h"
+
+/*
+ *  Structures
+ */
+
+/*
+ *  Image structure
+ */
+typedef struct {
+    const char * name;  /* File Name                */
+    struct pam   pam;   /* netpbm image description */
+    tuple **     tuple; /* in-memory copy of image  */
+} Image;
+
+/*
+ *  Output class
+ *  The following methods and data allocations are used for output filter.
+ */
+typedef struct output {
+    /* name */
+    const char * Name;
+    /* methods */
+    bool      (* Alloc)(struct output * me, const char * file,
+                        unsigned int width, unsigned int height,
+                        struct pam * prototype);
+    void      (* DeAlloc)(struct output * me);
+    tuple    *(* Row)(struct output * me, unsigned row);
+    void      (* FlushRow)(struct output * me, unsigned row);
+    void      (* FlushImage)(struct output * me);
+    /* data */
+    Image      * image;
+    void       * extra;
+} Output;
+
+extern Output OutputMethods[];
+
+/*
+ *  Stitching class
+ *  The following methods and data allocations are used for operations
+ *  surrounding stitching of an image.
+ */
+typedef struct stitcher {
+    /* name */
+    const char * Name;
+    /* methods */
+    bool      (* Alloc)(struct stitcher *me);
+    void      (* DeAlloc)(struct stitcher *me);
+    void      (* Constrain)(struct stitcher *me, int x, int y,
+                            int width, int height);
+        /* Set transformation parameter constraints.  This affects the
+           function of a future 'Match' method execution.
+        */
+    bool      (* Match)(struct stitcher *me, Image * Left, Image * Right);
+        /* Determine the transformation parameters for the stitching.
+           I.e. determine the parameters that affect future invocations
+           of the transformation methods below.  You must execute a 
+           'Match' before executing any of the transformation methods.
+        */
+    /*-----------------------------------------------------------------------
+      The transformation methods answer the question, "Which pixel in the left
+      image and which pixel in the right image contribute to the pixel at
+      Column X, Column Y of the output?
+
+      If there is no pixel in the left image that contributes to the output
+      pixel in question, the methods return column or row numbers outside
+      the bounds of the left image (possibly negative).  Likewise for the 
+      right image.
+    */
+    float     (* XLeft)(struct stitcher *me, int x, int y);
+        /* column number of the pixel from the left image */
+    float     (* YLeft)(struct stitcher *me, int x, int y);
+        /* row number of the pixel from the left image */
+    float     (* XRight)(struct stitcher *me, int x, int y);
+        /* column number of the pixel from the right image */
+    float     (* YRight)(struct stitcher *me, int x, int y);
+        /* row number of the pixel from the left image */
+    /*----------------------------------------------------------------------*/
+    /* Output methods */
+    void      (* Output)(struct stitcher *me, FILE * fp);
+    /* private data */
+    int          x, y, width, height;
+    /* For a Linear Sliver stitcher, 'x' and 'y' are simply the offset you
+       add to an output location to get the location in the right image of the
+       pixel that corresponds to that output pixel.
+    */
+    float      * parms;
+} Stitcher;
+
+extern Stitcher StitcherMethods[];
+
+/*
+ *  Prototypes
+ */
+static int pnmstitch(const char * const left,
+                     const char * const right,
+                     const char * const out,
+                     int          const x,
+                     int          const y,
+                     int          const width,
+                     int          const height,
+                     const char * const stitcher,
+                     const char * const filter);
+
+struct cmdlineInfo {
+    /*
+     * All the information the user supplied in the command line,
+     * in a form easy for the program to use.
+     */
+    const char * leftFilespec;   /* '-' if stdin */
+    const char * rightFilespec;  /* '-' if stdin */
+    const char * outputFilespec; /* '-' if stdout */
+    const char * stitcher;
+    const char * filter;
+    int          width;
+    int          height;
+    int          xrightpos;
+    int          yrightpos;
+    unsigned int verbose;
+};
+
+static char minus[] = "-";
+
+static void
+parseCommandLine ( int argc, char ** argv,
+                   struct cmdlineInfo *cmdlineP )
+{
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc( 100*sizeof( optEntry ) );
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    char *outputOpt;
+    unsigned int widthSpec, heightSpec, outputSpec, 
+        xrightposSpec, yrightposSpec, stitcherSpec, filterSpec;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "width",       OPT_UINT,   &cmdlineP->width, 
+            &widthSpec,         0);
+    OPTENT3(0, "height",      OPT_UINT,   &cmdlineP->height, 
+            &heightSpec,        0);
+    OPTENT3(0, "verbose",     OPT_FLAG,   NULL,                  
+            &cmdlineP->verbose, 0 );
+    OPTENT3(0, "output",      OPT_STRING, &outputOpt, 
+            &outputSpec,        0);
+    OPTENT3(0, "xrightpos",   OPT_UINT,   &cmdlineP->xrightpos, 
+            &xrightposSpec,     0);
+    OPTENT3(0, "yrightpos",   OPT_UINT,   &cmdlineP->yrightpos, 
+            &yrightposSpec,     0);
+    OPTENT3(0, "stitcher",    OPT_STRING, &cmdlineP->stitcher, 
+            &stitcherSpec,      0);
+    OPTENT3(0, "filter",      OPT_STRING, &cmdlineP->filter, 
+            &filterSpec,        0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!widthSpec) {
+        cmdlineP->width = INT_MAX;
+    }
+    if (!heightSpec) {
+        cmdlineP->height = INT_MAX;
+    }
+    if (!xrightposSpec) {
+        cmdlineP->xrightpos = INT_MAX;
+    }
+    if (!yrightposSpec) {
+        cmdlineP->yrightpos = INT_MAX;
+    }
+    if (!stitcherSpec) {
+        cmdlineP->stitcher = "BiLinearSliver";
+    }
+    if (!filterSpec) {
+        cmdlineP->filter = "StraightThrough";
+    }
+
+    if (argc-1 > 3) {
+        pm_error("Program takes at most three arguments: left, right, and "
+                 "output file specifications.  You specified %d", argc-1);
+        /* NOTREACHED */
+    } else {
+        if (argc-1 == 0) {
+            cmdlineP->leftFilespec = minus;
+            cmdlineP->rightFilespec = minus;
+        } else if (argc-1 == 1) {
+            cmdlineP->leftFilespec = minus;
+            cmdlineP->rightFilespec = argv[1];
+        } else {
+            cmdlineP->leftFilespec = argv[1];
+            cmdlineP->rightFilespec = argv[2];
+        }
+        if (argc-1 == 3 && outputSpec) {
+            pm_error("You cannot specify --output and also name the "
+                     "output file with the 3rd argument.");
+            /* NOTREACHED */
+        } else if (argc-1 == 3) {
+            cmdlineP->outputFilespec = argv[3];
+        } else if (outputSpec) {
+            cmdlineP->outputFilespec = outputOpt;
+        } else {
+            cmdlineP->outputFilespec = minus;
+        }
+    }
+} /* parseCommandLine() - end */
+
+static int  verbose;
+
+/*
+ *  Parse the command line, call pnmstitch to perform work.
+ */
+int
+main (int argc, char **argv)
+{
+    struct cmdlineInfo cmdline;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    verbose = cmdline.verbose;
+
+    return pnmstitch (cmdline.leftFilespec, 
+                      cmdline.rightFilespec, 
+                      cmdline.outputFilespec, 
+                      cmdline.xrightpos, 
+                      cmdline.yrightpos, 
+                      cmdline.width, 
+                      cmdline.height, 
+                      cmdline.stitcher, 
+                      cmdline.filter);
+} /* main() - end */
+
+
+
+/*
+ *  allocate a clear image structure.
+ */
+static Image *
+allocate_image(void)
+{
+    Image * retVal;
+    
+    MALLOCVAR(retVal);
+
+    if (retVal != NULL) 
+        memset (retVal, 0, (unsigned)sizeof(Image));
+
+    return retVal;
+}
+
+
+
+/*
+ *  free an image structure.
+ */
+static void
+free_image(Image * image)
+{
+    if (image->name) {
+        strfree(image->name);
+        image->name = NULL;     
+    }
+    if (image->tuple) {
+        pnm_freepamarray(image->tuple, &image->pam);
+        image->tuple = NULL; 
+    }
+    if (image->pam.file) {
+        fclose (image->pam.file);
+        image->pam.file = NULL;
+    }
+    free (image);
+}
+
+
+
+static void
+openWithPossibleExtension(const char *  const baseName,
+                          FILE **       const ifPP,
+                          const char ** const filenameP) {
+
+    /* list of possible extensions for input file */
+    const char * const extlist[] = { 
+        "", ".pnm", ".pam", ".pgm", ".pbm", ".ppm" 
+    };
+
+    FILE * ifP;
+    unsigned int extIndex;
+
+    ifP = NULL;   /* initial value -- no file opened yet */
+
+    for (extIndex = 0; extIndex < ARRAY_SIZE(extlist) && !ifP; ++extIndex) {
+        
+        const char * trialName;
+        
+        asprintfN(&trialName, "%s%s", baseName, extlist[extIndex]);
+        
+        ifP = fopen(trialName, "rb");
+        
+        if (ifP)
+            *filenameP = trialName;
+        else
+            strfree(trialName);
+    }
+    if (!ifP) 
+        pm_error ("Failed to open input file named '%s' "
+                  "or '%s' with one of various common extensions.", 
+                  baseName, baseName);
+    
+    *ifPP = ifP;
+}
+
+
+
+/*
+ *  Create an image object for the PNM/PAM image in the file name 'name'
+ *  This includes reading the entire image.
+ */
+static Image *
+readinit(const char * const name)
+{
+    Image * const image = allocate_image();
+    Image * retVal;
+
+    if (image == NULL) 
+        retVal = NULL;
+    else {
+        FILE * ifP;
+
+        if (strcmp(name,minus) == 0) {
+            ifP = stdin;
+            image->name = strdup("<stdin>");
+        } else {
+            openWithPossibleExtension(name, &ifP, &image->name);
+        }
+        image->tuple = pnm_readpam(ifP, &(image->pam), 
+                                   PAM_STRUCT_SIZE(tuple_type));
+        fclose (ifP);
+        image->pam.file = NULL;
+
+        if (image->tuple == NULL) {
+            free_image(image);
+            retVal = NULL;
+        } else
+            retVal = image;
+    }
+    return retVal;
+} /* readinit() - end */
+
+/*
+ *  Prepare an image to be output.
+ *      Too bad we can't help them and add a .pnm on the filename,
+ *      since this would be `bad'. on readinit we can check if the .pnm
+ *      needs to be added ...
+ */
+static bool
+writeinit(Image * image)
+{
+    if (strcmp(image->name,minus) == 0) {
+        image->pam.file = stdout;
+        strfree(image->name);
+        image->name = strdup("<stdout>");
+    } else {
+        image->pam.file = pm_openw(image->name);
+    }
+    return TRUE;
+} /* writeinit() - end */
+
+/*
+ *  Compare a subimage to an image.
+ *  The most time consuming actions surround this subroutine.
+ *  Return the magnitude of the difference between the specified region
+ *  in image 'left' and the specified region in image 'right'.
+ *  The magnitude is defined as the sum of the squares of the differences
+ *  between intensities of each of the 3 colors over all the pixels.
+ *
+ *  The region in the left image is the rectangle with top left corner at
+ *  Column 'lx', Row 'ly', with dimensions 'width' columns by 'height'
+ *  rows.  The region in the right image is a rectangle the same dimensions
+ *  with upper left corner at Column 'rx', Row 'ry'.
+ *
+ *  Caller must ensure that the regions indicated are entirely within the
+ *  their respective images.
+ */
+static unsigned long
+regionDifference(Image * left,
+                 int     lx,
+                 int     ly,
+                 Image * right,
+                 int     rx,
+                 int     ry,
+                 int     width,
+                 int     height)
+{
+    unsigned long total;
+    unsigned      row;
+    
+    total = 0;  /* initial value */
+
+    for (row = 0; row < height; ++row) {
+        unsigned column;
+
+        for (column = 0; column < width; ++column) {
+            unsigned plane;
+
+            for (plane = 0; plane < left->pam.depth; ++plane) {
+                sample const leftSample = left->tuple[row][column][plane];
+                sample const rightSample = right->tuple[row][column][plane];
+                total += SQR(leftSample - rightSample);
+            }
+        }
+    }
+    return total;
+}
+
+
+
+/*
+ *  Generate a recent sorted histogram of best matches.
+ */
+typedef struct {
+    unsigned long total;
+    int           x;
+    int           y;
+} Best;
+/* Arbitrary, except 9 points surrounding a hot spot plus one layer more */
+#define NUM_BEST 9
+
+/*
+ *  Allocate the Best structure.
+ */
+static Best *
+allocate_best(void)
+{
+    Best * retVal;
+
+    MALLOCARRAY(retVal, NUM_BEST);
+
+    if (retVal != NULL) {
+        unsigned int    i;
+        for (i = 0; i < NUM_BEST; ++i) {
+            retVal[i].total = ULONG_MAX;
+            retVal[i].x = INT_MAX;
+            retVal[i].y = INT_MAX;
+        }
+    }
+    return retVal;
+}
+
+/*
+ *  Free the Best structure
+ */
+#define free_best(best) free(best)
+
+/*
+ *  Placement helper for the Best structure.
+ */
+static void
+update_best(Best * best, unsigned long total, int x, int y)
+{
+    int i;
+    if (best[NUM_BEST-1].total <= total) {
+        return;
+    }
+    for (i = NUM_BEST - 1; i > 0; --i) {
+        if (best[i-1].total < total) {
+            break;
+        }
+        best[i] = best[i-1];
+    }
+    best[i].total = total;
+    best[i].x = x;
+    best[i].y = y;
+}
+
+/*
+ *  Print helper for the Best structure.
+ */
+static void
+pr_best(Best * const best)
+{
+    int i;
+    if (best != (Best *)NULL)
+    for (i = 0; i < NUM_BEST; ++i) {
+        fprintf (stderr, " (%d,%d)%lu",
+          best[i].x, best[i].y, best[i].total);
+    }
+} /* pr_best() - end */
+
+static void *
+findObject(const char * const name, void * start, unsigned size)
+{
+    char  ** object;
+    char  ** best;
+    unsigned length;
+
+    if (name == (char *)NULL) {
+        return start;
+    }
+    for (length = 0, best = (char **)NULL, object = (char **)start;
+      *object != (char *)NULL;
+      object = (char **)(((char *)object) + size)) {
+        const char * np      = name;
+        char       * op      = *object;
+        unsigned     matched = 0;
+
+        /* case insensitive match */
+        while ((*np != '\0') && (*op != '\0')
+         && ((*np == *op) || (tolower(*np) == tolower(*op)))) {
+            ++np;
+            ++op;
+            ++matched;
+        }
+        if ((*np == '\0') && (*op == '\0')) {
+            break;
+        }
+        if ((matched >= length) && (*np == '\0')) {
+            if (matched == length) {
+                best = (char **)NULL;
+            } else {
+                best = object;
+            }
+            length = matched;
+        }
+    }
+    if (*object == (char *)NULL) {
+        object = best;
+    }
+    if (object == (char **)NULL) {
+        fprintf (stderr,
+          "Unknown driver \"%s\". available drivers are:\n", name);
+        for (object = (char **)start;
+          *object != (char *)NULL;
+          object = (char **)(((char *)object) + size)) {
+            fprintf (stderr, "\t%s%s\n", *object,
+              (object == (char **)start) ? " (default)" : "");
+        }
+    }
+    return object;
+}
+
+/*
+ *  The general wrapper for both the Output and the Stitcher algorithms.
+ */
+
+/* Determine the mask corners for both Left and Right images */
+static void
+determineMaskCorners(Stitcher * Stitch,
+                     Image    * Left,
+                     Image    * Right,
+                     int        xp[],
+                     int        yp[])
+{
+    int i;
+
+    xp[0] = xp[1] = xp[4] = xp[5] = 0;
+    yp[0] = yp[2] = yp[4] = yp[6] = 0;
+    xp[2] = xp[3] = Left->pam.width;
+    yp[1] = yp[3] = Left->pam.height;
+    xp[6] = xp[7] = Right->pam.width;
+    yp[5] = yp[7] = Right->pam.height;
+    for (i = 0; i < 8; ++i) {
+        int     x, y, xx, yy, count = 65536; /* max iterations */
+        float (*X)(Stitcher *me, int x, int y);
+        float (*Y)(Stitcher *me, int x, int y);
+
+        if (i < 4) {
+            X = Stitch->XLeft;
+            Y = Stitch->YLeft;
+        } else {
+            X = Stitch->XRight;
+            Y = Stitch->YRight;
+        }
+        x = xp[i];
+        y = yp[i];
+        /* will not work if rotated 90o or if gain > 10 */
+        do {
+            xx = ((*X)(Stitch, xp[i], yp[i]) + 0.5) - x;
+            if (xx < 0) {
+                if (xx > -100) {
+                    ++xp[i];
+                } else {
+                    xp[i] -= xx / 10;
+                }
+            } else if (xx > 0) {
+                if (xx < 100) {
+                    --xp[i];
+                } else {
+                    xp[i] -= xx / 10;
+                }
+            }
+            yy = ((*Y)(Stitch, xp[i], yp[i]) + 0.5) - y;
+            if (yy < 0) {
+                if (yy > -100) {
+                    ++yp[i];
+                } else {
+                    yp[i] -= yy / 10;
+                }
+            } else if (yy > 0) {
+                if (yy < 100) {
+                    --yp[i];
+                } else {
+                    yp[i] -= yy / 10;
+                }
+            }
+        } while (((xx != 0) || (yy != 0)) && (--count != 0));
+    }
+    if (verbose) {
+        (*(Stitch->Output))(Stitch, stderr);
+        if (verbose > 2) {
+            static char quotes[] = "'\0\0\"\0\0'\"\0\"\"";
+            fprintf (stderr, " Left:");
+            for (i = 0; i < 8; ++i) {
+                if (i == 4) {
+                    fprintf (stderr, "\n Right:");
+                }
+                fprintf (stderr, " x%s,y%s=%d,%d",
+                  &quotes[(i%4)*3], &quotes[(i%4)*3],
+                  xp[i], yp[i]);
+            }
+        }
+    }
+} /* determineMaskCorners() - end */
+
+static void
+calculateXyWidthHeight(int         xp[],
+                       int         yp[],
+                       int * const xP,
+                       int * const yP,
+                       int * const widthP,
+                       int * const heightP)
+{
+    int x, y, width, height, i;
+
+    /* Calculate generic x,y left top corner, and the width and height */
+    x = xp[0];
+    y = yp[0];
+    width = height = 0;
+    for (i = 1; i < 8; ++i) {
+        if (xp[i] < x) {
+            width += x - xp[i];
+            x = xp[i];
+        } else if ((x + width) < xp[i]) {
+            width = xp[i] - x;
+        }
+        if (yp[i] < y) {
+            height += y - yp[i];
+            y = yp[i];
+        } else if ((y + height) < yp[i]) {
+            height = yp[i] - y;
+        }
+    }
+    *xP = x; *yP = y;
+    *widthP = width; *heightP = height;
+} /* calculateXyWidthHeight() - end */
+
+static void
+printPlan(int xp[], int yp[], Image * Left, Image * Right)
+{
+    /* Calculate Left image transformed bounds */
+    int X, Y, W, H, i;
+
+    X = xp[0];
+    Y = yp[0];
+    W = H = 0;
+    for (i = 1; i < 4; ++i) {
+        if (xp[i] < X) {
+            W += X - xp[i];
+            X = xp[i];
+        } else if ((X + W) < xp[i]) {
+            W = xp[i] - X;
+        }
+        if (yp[i] < Y) {
+            H += Y - yp[i];
+            Y = yp[i];
+        } else if ((Y + H) < yp[i]) {
+            H = yp[i] - Y;
+        }
+    }
+    fprintf (stderr,
+      "%s[%u,%u=>%d,%d](%d,%d)",
+      Left->name, Left->pam.width, Left->pam.height,
+      W, H, X, Y);
+    X = xp[i];
+    Y = yp[i];
+    W = H = 0;
+    for (++i; i < 8; ++i) {
+        if (xp[i] < X) {
+            W += X - xp[i];
+            X = xp[i];
+        } else if ((X + W) < xp[i]) {
+            W = xp[i] - X;
+        }
+        if (yp[i] < Y) {
+            H += Y - yp[i];
+            Y = yp[i];
+        } else if ((Y + H) < yp[i]) {
+            H = yp[i] - Y;
+        }
+    }
+    fprintf (stderr,
+      "+%s[%u,%u=>%d,%d](%d,%d)",
+      Right->name, Right->pam.width, Right->pam.height,
+      W, H, X, Y);
+} /* printPlan() - end */
+
+
+
+static void
+stitchOnePixel(Image *    const Left,
+               Image *    const Right,
+               struct pam const outpam,
+               int        const row,
+               int        const column,
+               int        const y,
+               int        const right_row,
+               int        const right_column,
+               unsigned * const firstRightP,
+               tuple      const outPixel) {
+               
+    unsigned plane;
+
+    for (plane = 0; plane < outpam.depth; ++plane) {
+        sample leftPixel, rightPixel;
+        /* Left `mix' is easy to find */
+        leftPixel = (column < Left->pam.width)
+                    ? (y < 0)
+                        ? ((row < -y) || (row >= (Left->pam.height - y)))
+                           ? 0
+                           : Left->tuple[row + y][column][plane]
+                        : (row < Left->pam.height)
+                          ? Left->tuple[row][column][plane]
+                          : 0
+                     : 0;
+        rightPixel = 0;
+        if (right_column >= 0) {
+            rightPixel = Right->tuple[right_row][right_column][plane];
+            if ((rightPixel > 0) && (*firstRightP == 0)) 
+                *firstRightP = column;
+        }
+        if (leftPixel == 0) {
+            leftPixel = rightPixel;
+        } else if ((*firstRightP <= column)
+                   && (column < Left->pam.width)
+                   && (rightPixel > 0)) {
+            /* blend 7/8 over half of stitch */
+            int const w = Left->pam.width - *firstRightP;
+            if (column < (*firstRightP + w/2)) {
+                int const v = (w * 4) / 7;
+                leftPixel = (sample)(
+                    ((leftPixel
+                      * (unsigned long)(*firstRightP + v - column))
+                     + (rightPixel
+                        * (unsigned long)(column - *firstRightP)))
+                    / (unsigned long)v);
+            } else {
+                int const v = w * 4;
+                leftPixel = (sample)(
+                    ((leftPixel 
+                      * (unsigned long)(Left->pam.width - column))
+                     + (rightPixel
+                        * (unsigned long)(column - Left->pam.width + v)))
+                    / (unsigned long)v);
+            }
+        }
+        outPixel[plane] = leftPixel;
+    }
+}
+
+
+
+static void
+stitchOneRow(Image *    const Left,
+             Image *    const Right,
+             Output *   const Out,
+             Stitcher * const Stitch,
+             int        const row,
+             int        const y) {
+
+    /*
+     *  We scale the overlap of the left and right images, we need to
+     * discover and hold on to the left edge of the right image to
+     * determine the rate at which we blend. Most (7/8) of the blending
+     * occurs in the first half of the overlap to reduce the occurences
+     * of blending artifacts. If there is no overlap, the image present
+     * has no blending activity, this is determined by the black
+     * background and is not through an alpha layer to help reduce
+     * storage needs. The algorithm below is complicated most by
+     * the blending determinations, overlapping a left untransformed
+     * image with a right transformed image with a black background is
+     * all that remains.
+     */
+    /*
+     * Normalize transformation against origin, the
+     * transformation algorithm was in reference to the right
+     * hand side of the left hand image before.
+     */
+    tuple * const Row = (*(Out->Row))(Out,row);
+
+    unsigned column, firstRight;
+
+    firstRight = 0;  /* initial value */
+
+    for (column = 0; column < Out->image->pam.width; ++column) {
+        int right_row, right_column;
+
+        right_row = -1;
+        right_column = (*(Stitch->XRight))(Stitch, column,
+                                           (y < 0) ? (row + y) : row) + 0.5;
+        if ((0 <= right_column)
+            && (right_column < Right->pam.width)) {
+            right_row = (*(Stitch->YRight))(Stitch, column,
+                                            (y < 0) ? (row + y) : row) + 0.5;
+            if ((right_row < 0)
+                || (Right->pam.height <= right_row)) {
+                right_column = -1;
+                right_row    = -1;
+            }
+        } else 
+            right_column = -1;
+
+        /* Create the pixel at column 'column' of row 'row' of the
+           output 'Out': Row[column].
+        */
+        stitchOnePixel(Left, Right, Out->image->pam, row, column, y, 
+                       right_row, right_column, &firstRight, Row[column]);
+    }
+}
+
+
+
+static void 
+stitchit(Image *      const Left, 
+         Image *      const Right, 
+         const char * const outfilename,
+         const char * const filter,
+         Stitcher *   const Stitch,
+         int *        const retvalP) {
+
+    Output * const Out = findObject(filter, &OutputMethods[0],
+                                    sizeof(OutputMethods[0]));
+    unsigned   row;
+    int        xp[8], yp[8], x, y, width, height;
+    
+    if ((Out == (Output *)NULL) || (Out->Name == (char *)NULL)) 
+        *retvalP = -2;
+    else {
+        if (verbose)
+            fprintf (stderr, "Selected %s output filter algorithm\n",
+                     Out->Name);
+
+        /* Determine the mask corners for both Left and Right images */
+        determineMaskCorners(Stitch, Left, Right, xp, yp);
+
+        /* Output the combined images */
+                
+        /* Calculate generic x,y left top corner, and the width and height */
+        calculateXyWidthHeight(xp, yp, &x, &y, &width, &height);
+                
+        if (verbose) 
+            printPlan(xp, yp, Left, Right);
+    
+        if (!(*(Out->Alloc))(Out, outfilename, width, height, &Left->pam))
+            *retvalP = -9;
+        else {
+            if (verbose) {
+                fprintf (stderr,
+                         "=%s[%u,%u=>%d,%d](%d,%d)\n",
+                         Out->image->name, Out->image->pam.width,
+                         Out->image->pam.height, width, height, x, y);
+            }
+            for (row = 0; row < Out->image->pam.height; row++) {
+                /* Generate row number 'row' of the output image 'Out' */
+                stitchOneRow(Left, Right, Out, Stitch, row, y);
+                (*(Out->FlushRow))(Out,row);
+            }
+            (*(Out->FlushImage))(Out);
+            (*(Out->DeAlloc))(Out);
+        
+            *retvalP = 0;
+        }
+    }
+}
+
+
+
+static int
+pnmstitch(const char * const leftfilename,
+          const char * const rightfilename,
+          const char * const outfilename,
+          int          const reqx,
+          int          const reqy,
+          int          const reqWidth,
+          int          const reqHeight,
+          const char * const stitcher,
+          const char * const filter)
+{
+    Stitcher * const Stitch = findObject(stitcher, &StitcherMethods[0],
+                                         sizeof(StitcherMethods[0]));
+    Image    * Left;
+    Image    * Right;
+    int        retval;
+
+    if ((Stitch == (Stitcher *)NULL) || (Stitch->Name == (char *)NULL)) 
+        retval = -1;
+    else {
+        if (verbose) 
+            fprintf (stderr, "Selected %s stitcher algorithm\n",
+                     Stitch->Name);
+
+        /* Left hand image read into memory */
+        Left = readinit(leftfilename);
+        if (Left == NULL)
+            retval = -3;
+        else {
+            /* Right hand image read into memory */
+            Right = readinit(rightfilename);
+            if (Right == NULL)
+                retval = -4;
+            else {
+                if (Left->pam.depth != Right->pam.depth) {
+                    fprintf(stderr, "Images should have matching depth.  "
+                            "The left image has depth %d, "
+                            "while the right has depth %d.", 
+                            Left->pam.depth, Right->pam.depth);
+                    retval = -5;
+                } else if (Left->pam.maxval != Right->pam.maxval) {
+                    fprintf (stderr,
+                             "Images should have matching maxval.  "
+                             "The left image has maxval %u, "
+                             "while the right has maxval %u.",
+                             (unsigned)Left->pam.maxval, 
+                             (unsigned)Right->pam.maxval);
+                    retval = -6;
+                } else if ((*(Stitch->Alloc))(Stitch) == FALSE) 
+                    retval = -7;
+                else {
+                    (*(Stitch->Constrain))(Stitch, reqx, reqy, 
+                                           reqWidth, reqHeight);
+
+                    if ((*(Stitch->Match))(Stitch, Left, Right) == FALSE) 
+                        retval = -8;
+                    else 
+                        stitchit(Left, Right, outfilename, filter, Stitch, 
+                                 &retval);
+                }
+                free_image(Right);
+            }
+            free_image(Left);
+        }
+    }
+    return retval;
+}
+
+
+
+/* Output Methods */
+
+/* Helper methods */
+
+static void
+OutputDeAlloc(Output * me)
+{
+    if (me->image != (Image *)NULL) {
+        /* Free up resources */
+        free_image (me->image);
+        me->image = (Image *)NULL;
+    }
+    if (me->extra != (void *)NULL) {
+        free (me->extra);
+        me->extra = (void *)NULL;
+    }
+} /* OutputDeAlloc() - end */
+
+static bool
+OutputAlloc(Output     * const me,
+            const char * const file,
+            unsigned int const width,
+            unsigned int const height,
+            struct pam * const prototype)
+{
+    /* Output the combined images */
+    me->extra = (void *)NULL;
+    me->image = allocate_image();
+    if (me->image == (Image *)NULL) {
+        return FALSE;
+    }
+    me->image->pam = *prototype;
+    me->image->pam.width = width;
+    me->image->pam.height = height;
+    /* Give the output a name */
+    me->image->name = strdup(file);
+    /* Initialize output arrays */
+    if (writeinit(me->image) == FALSE) {
+        OutputDeAlloc(me);
+        return FALSE;
+    }
+    return TRUE;
+} /* OutputAlloc() - end */
+
+/* StraightThrough output method */
+
+static void
+StraightThroughDeAlloc(Output * me)
+{
+    /* Trick the proper freeing of resouces on the Output Image */
+    me->image->pam.height = 1;
+    OutputDeAlloc(me);
+} /* StraightThroughDeAlloc() - end */
+
+static bool
+StraightThroughAlloc(Output     * const me,
+                     const char * const file,
+                     unsigned int const width,
+                     unsigned int const height,
+                     struct pam * const prototype)
+{
+    if (OutputAlloc(me, file, width, height, prototype) == FALSE) {
+        StraightThroughDeAlloc(me);
+    }
+    /* Trick the proper allocation of resouces on the Output Image */
+    me->image->pam.height = 1;
+    me->image->tuple = pnm_allocpamarray(&me->image->pam);
+    if (me->image->tuple == (tuple **)NULL) {
+        StraightThroughDeAlloc(me);
+        return FALSE;
+    }
+    me->image->pam.height = height;
+    pnm_writepaminit(&me->image->pam);
+    return TRUE;
+} /* StraightThroughAlloc() - end */
+
+static tuple *
+StraightThroughRow(Output * me, unsigned row)
+{
+    UNREFERENCED_PARAMETER(row);
+    return me->image->tuple[0];
+} /* StraightThroughRow() - end */
+
+static void
+StraightThroughFlushRow(Output * me, unsigned row)
+{
+    UNREFERENCED_PARAMETER(row);
+    if (me->image != (Image *)NULL) {
+        pnm_writepamrow(&me->image->pam, me->image->tuple[0]);
+    }
+} /* StraightThroughFlushRow() - end */
+
+static void
+StraightThroughFlushImage(Output * me)
+{
+    UNREFERENCED_PARAMETER(me);
+} /* StraightThroughFlushImage() - end */
+
+/* Horizontal Crop output method */
+
+#define HorizontalCropDeAlloc StraightThroughDeAlloc
+
+typedef struct {
+    int state;
+    int lostInSpace;
+} HorizontalCropExtra;
+
+static bool
+HorizontalCropAlloc(Output     * const me,
+                    const char * const file,
+                    unsigned int const width,
+                    unsigned int const height,
+                    struct pam * const prototype)
+{
+    unsigned long pos;
+
+    if (StraightThroughAlloc(me, file, width, height, prototype) == FALSE) {
+        return FALSE;
+    }
+    me->extra = (void *)malloc(sizeof(HorizontalCropExtra));
+    if (me->extra == (void *)NULL) {
+        HorizontalCropDeAlloc(me);
+        return FALSE;
+    }
+    memset (me->extra, 0, sizeof(HorizontalCropExtra));
+    /* Test if we can seek, important since we rewrite the header */
+    pos = ftell(me->image->pam.file);
+    if ((fseek(me->image->pam.file, 1L, SEEK_SET) != 0)
+     || (ftell(me->image->pam.file) != 1L)) {
+        fprintf (stderr, "%s needs to output to a seekable entity\n",
+          me->Name);
+    }
+    (void)fseek(me->image->pam.file, pos, SEEK_SET);
+    return TRUE;
+} /* HorizontalCropAlloc() - end */
+
+#define HorizontalCropRow StraightThroughRow
+
+static void
+HorizontalCropFlushRow(Output * me, unsigned row)
+{
+    unsigned column;
+    unsigned threshold;
+#   define HorizontalCropThreshold 4
+    UNREFERENCED_PARAMETER(row);
+
+    if (me->image == (Image *)NULL) {
+        return;
+    }
+    if (((HorizontalCropExtra *)(me->extra))->state == 2) {
+        ((HorizontalCropExtra *)(me->extra))->lostInSpace++;
+        return;
+    }
+    /* Any pitch black pixels? */
+    threshold = HorizontalCropThreshold;
+    for (column = 0; column < me->image->pam.width; ++column) {
+        unsigned plane = 0;
+        while (me->image->tuple[0][column][plane] == (sample)0) {
+            if (++plane >= me->image->pam.depth) {
+                if (--threshold == 0) {
+                    if (((HorizontalCropExtra *)(me->extra))->state == 1) {
+                        ((HorizontalCropExtra *)(me->extra))->state = 2;
+                    }
+                    ((HorizontalCropExtra *)(me->extra))->lostInSpace++;
+                    return;
+                }
+            }
+        }
+        if (plane < me->image->pam.depth) {
+            threshold = HorizontalCropThreshold;
+        }
+    }
+    ((HorizontalCropExtra *)(me->extra))->state = 1;
+    pnm_writepamrow(&me->image->pam, me->image->tuple[0]);
+} /* HorizontalCropFlushRow() - end */
+
+static void
+HorizontalCropFlushImage(Output * me)
+{
+    me->image->pam.height -= ((HorizontalCropExtra *)(me->extra))->lostInSpace;
+    if (verbose) {
+        fprintf (stderr, "%s has set image size to %d x %d\n",
+          me->Name, me->image->pam.width, me->image->pam.height);
+    }
+    if (fseek(me->image->pam.file, 0L, SEEK_SET) == 0) {
+        pnm_writepaminit(&me->image->pam);
+    } else {
+        fprintf (stderr,
+          "%s failed to seek to beginning to rewrite the header\n",
+          me->Name);
+    }
+} /* HorizontalCropFlushImage() - end */
+
+/* Rotate Crop output method */
+
+#define RotateCropDeAlloc OutputDeAlloc
+
+static bool
+RotateCropAlloc(Output     * const me,
+                const char * const file,
+                unsigned int const width,
+                unsigned int const height,
+                struct pam * const prototype)
+{
+    if (OutputAlloc(me, file, width, height, prototype) == FALSE) {
+        RotateCropDeAlloc(me);
+    }
+    me->image->tuple = pnm_allocpamarray(&me->image->pam);
+    if (me->image->tuple == (tuple **)NULL) {
+        RotateCropDeAlloc(me);
+        return FALSE;
+    }
+    return TRUE;
+} /* RotateCropAlloc() - end */
+
+static tuple *
+RotateCropRow(Output * me, unsigned row)
+{
+    return me->image->tuple[row];
+} /* RotateCropRow() - end */
+
+static void
+RotateCropFlushRow(Output * me, unsigned row)
+{
+    UNREFERENCED_PARAMETER(me);
+    UNREFERENCED_PARAMETER(row);
+} /* RotateCropFlushRow() - end */
+
+/*
+ *  Algorithm under construction.
+ *
+ */
+static void
+RotateCropFlushImage(Output * me)
+{
+    /* Cop Out for now ... */
+    pnm_writepam(&me->image->pam, me->image->tuple);
+} /* RotateCropFlushImage() - end */
+
+/* Output Method Table */
+
+Output OutputMethods[] = {
+    { "StraightThrough", StraightThroughAlloc, StraightThroughDeAlloc,
+      StraightThroughRow, StraightThroughFlushRow,
+      StraightThroughFlushImage },
+    { "HorizontalCrop", HorizontalCropAlloc, HorizontalCropDeAlloc,
+      HorizontalCropRow, HorizontalCropFlushRow, HorizontalCropFlushImage },
+    { "RotateCrop (unimplemented)", RotateCropAlloc, RotateCropDeAlloc,
+      RotateCropRow, RotateCropFlushRow, RotateCropFlushImage },
+    { (char *)NULL }
+};
+
+/* Stitcher Methods */
+
+/* These names are for the 8 parameters of a stitch, in any of the 3
+   methods this program presently implements.  Each is a subscript in
+   the parms[] array for the Stitcher object that represents a linear
+   stitching method.  
+   
+   There are also other sets of names for the 8 parameters, such as
+   Rotate_a.  I don't know why.  Maybe historical.
+*/
+
+#define Sliver_A   0
+#define Sliver_B   1
+#define Sliver_C   2
+#define Sliver_D   3
+#define Sliver_xp  4
+#define Sliver_yp  5
+#define Sliver_xpp 6
+#define Sliver_ypp 7
+
+/* Linear Stitcher Methods */
+
+static void
+LinearDeAlloc(Stitcher * me)
+{
+    if (me->parms != (float *)NULL) {
+        free (me->parms);
+        me->parms = (float *)NULL;
+    }
+} /* LinearDeAlloc() - end */
+
+static bool
+LinearAlloc(Stitcher * me)
+{
+    bool retval;
+
+    MALLOCARRAY(me->parms, 8);
+    if (me->parms == NULL) 
+        retval = FALSE;
+    else {
+        /* Constraints unset */
+        me->x = INT_MAX;
+        me->y = INT_MAX;
+        me->width = INT_MAX;
+        me->height = INT_MAX;
+        /* Unity transform matrix */
+        me->parms[Sliver_A]   = 1.0;
+        me->parms[Sliver_B]   = 0.0;
+        me->parms[Sliver_C]   = 0.0;
+        me->parms[Sliver_D]   = 0.0;
+        me->parms[Sliver_xp]  = 0.0;
+        me->parms[Sliver_yp]  = 1.0;
+        me->parms[Sliver_xpp] = 0.0;
+        me->parms[Sliver_ypp] = 0.0;
+        retval = TRUE;
+    }
+    return retval;
+}
+
+
+
+static void
+LinearConstrain(Stitcher * me, int x, int y, int width, int height)
+{
+    me->x = x;
+    me->y = y;
+    me->width = width;
+    me->height = height;
+} /* LinearConstrain() - end */
+
+/*
+ *  First pass is to find an approximate match. To do so, we take a
+ *  width sliver of the left hand side of the right image and compare
+ *  the sample to the left hand image. Accuracy is honored over speed.
+ *  The image overlap is expected between 7/16 to 1/16 in the horizontal
+ *  position, and a minumum of 5/8 in the vertical dimension.
+ *
+ *  Blind alleys:
+ *      - reduced resolution can match in totally wrong regions,
+ *        as such it can not be used to improve the speed by
+ *        getting close, then fine tuning at full resolution.
+ *      - vector (color) average of sample matched to running
+ *        vector average on left image in an attempt to improve
+ *        positional accuracy of a reduced resolution image
+ *        produced even more artifacts.
+ *      - A complete boxed sliver did not find a minima, as for
+ *        too large or too small of a square sample. heuristics
+ *        show that it works between 1/128 to 1/16 of the total
+ *        image dimension. Smaller, of course, improves speed,
+ *        but has the possibility of less accuracy.
+ *
+ *  Transformation parameters
+ *      x=x.+a
+ *      y=y'+b
+ *  Where x,y represents the original point, and x.,y.
+ *  represents the transformed point. Thus:
+ *
+ * Transformed image:
+ * ((x'+x")/2,(y'+y"-H)/2)               ((x'+x"+2W)/2,(y'+y"-H)/2)
+ * ((x'+x")/2,(y'+y"+H)/2)               ((x'+x"+2W)/2,(y'+y"+H)/2
+ *
+ * Corresponding to Original (dot) image:
+ * (0,0)                 (Right->pam.width,0)
+ * (0,Right->pam.height) (Right->pam.width,Right->pam.height)
+ *
+ *  Our matching data points are centered on x.=width/2, and
+ * scan for transformation results with a variety of y. values:
+ *  x=a*width/2+by.+c*width*y./2+d
+ *  y=e*width/2+fy.+g*width*y./2+h
+ * we set:
+ *  A=b+c*width/2
+ *  B=a*width/2+d
+ *  C=f+g*width/2
+ *  D=e*width/2+h
+ * thus simplifying to:
+ *  x=Ay.+B
+ *  y=Cy.+D
+ * adding in a weighting factor of w, the error equation is:
+ *   2                       2           2
+ *  E(A,B,C,D)=w * ((Ay.+B-x) + (Cy.+D-y))
+ * thus
+ *    2
+ *  dE(A)=2wy.(Ay.+B-x) => 0=A{wy.y. + B{wy. - {wy.x
+ *    2
+ *      dE(B)=2w(Ay.+B-x)   => 0=A{wy.   + B{w   - {wx
+ *      A=({wy.x{w-{wx{wy.)/({wy.y.{w-{wy.{wy.)
+ *      B=({wx-A{wy.)/{w
+ * and
+ *    2
+ *      dE(C)=2wy.(Cy.+D-y) => 0=C{wy.y. + D{wy. - {wy.y
+ *    2
+ *      dE(D)=2w(Cy.+D-y)   => 0=C{wy.   + D{w   - {wy
+ *      C=({wy.y{w-{wy{wy.)/({wy.y.{w-{wy.{wy.)
+ *      D=({wy-C{wy.)/{w
+ * requiring us to collect:
+ *   {wy.x=sumydotx
+ *     {wx=sumx
+ *        {wy.=sumydot
+ *      {wy.y.=sumydotydot
+ *          {w=sum
+ *       {wy.y=sumydoty
+ *         {wy=sumy
+ * Once we have A, B, C and D, we can calculate the x',y' and x",y"
+ * values as follows (based on geometric interpolation from the above
+ * constraints):
+ *  x'=AH/2+B-AHW/(2W-width)
+ *  y'=CH/2+D-CHW/(2W-width)
+ *  x"=AH/2+B+AHW/(2W-width)
+ *  y"=CH/2+D+CHW/(2W-width)
+ * These two points can be used either in the Linear or the BiLinear to
+ * establish a transform.
+ */
+
+#define IMAGE_PORTION 64
+#define SKIP_SLIVER   1
+
+/* Following global variables are for use by SliverMatch() */
+static unsigned long starPeriod;
+    /* The number of events between printing of a "*" progress
+       indicator.  
+    */
+static unsigned long starCount;
+    /* The number of events until the next * progress indicator needs to be
+       printed.
+    */
+
+static void
+starEvent() {
+    
+    if (--starCount == 0) {
+        starCount = starPeriod;
+        fprintf (stderr, "*");
+    }
+}
+
+
+static void
+starInit(unsigned long const period) {
+    starPeriod = period;
+    starCount = period;
+}
+
+
+static void
+starResetPeriod(unsigned long const period) {
+    starPeriod = period;
+    if (starCount > period)
+        starCount = period;
+}
+
+static void
+findBestMatches(Image *  const Left,
+                Image *  const Right,
+                int      const x,
+                int      const y,
+                int      const width,
+                int      const height,
+                int      const offY,
+                unsigned const Xmin,
+                unsigned const Xmax,
+                int      const Ymin,
+                int      const Ymax,
+                Best           best[NUM_BEST]) { 
+/*----------------------------------------------------------------------------
+  Compare the rectangle 'width' columns by 'height' rows with upper
+  left corner at Column 'x', Row y+offY in image 'Right' to a bunch of
+  rectangles of the same size in image 'Left' and generate a list of the
+  rectangles in 'Left' that best match the one in 'Right'.
+
+  The specific rectangles in 'Left' we examine are those with upper left
+  corner (X,Y+offY) where X is in [Xmin, Xmax) and Y is in [Ymin, Ymax).
+
+  We return the ordered list of best matches as best[].
+
+  Caller must ensure that each of the rectangles in question is fully
+  contained with its respective image.
+-----------------------------------------------------------------------------*/
+    unsigned X, Y;
+    /* Exhaustively find the best match */
+    for (X = Xmin; X < Xmax; ++X) {
+        int const widthOfOverlap = X - Left->pam.width;
+        for (Y = Ymin; Y < Ymax; ++Y) {
+            unsigned long difference = regionDifference(
+                Left, X, Y + offY,
+                Right, x, y + offY,
+                width, height);
+            update_best(best, difference, widthOfOverlap, Y + offY);
+            starEvent();
+        }
+    }
+}
+
+
+
+static void
+allocate_best_array(Best *** const bestP, unsigned const bestSize) {
+
+    Best ** best;
+    unsigned int i;
+
+    MALLOCARRAY(best, bestSize);
+    if (best == NULL)
+        pm_error("No memory for Best array");
+    
+    for (i = 0; i < bestSize; ++i) 
+        best[i] = allocate_best();
+    *bestP = best;
+}
+
+
+
+static void determineXYRange(Stitcher * const me,
+                             Image *    const Left,
+                             Image *    const Right,
+                             unsigned * const XminP,
+                             unsigned * const XmaxP,
+                             int *      const YminP,
+                             int *      const YmaxP) {
+    
+    if (me->x == INT_MAX) {
+        *XmaxP = Left->pam.width - me->width;
+        /* I can't bring myself to go half way */
+        *XminP = Left->pam.width - (7 * Right->pam.width / 16);
+    } else {
+        *XminP = me->x;
+        *XmaxP = me->x + 1;
+    }
+    if (me->y == INT_MAX) {
+        /* Middle 1/4 */
+        *YminP = Left->pam.height * 3 / 8;
+        *YmaxP = Left->pam.height - (*YminP) - me->height;
+    } else {
+        *YminP = me->y;
+        *YmaxP = me->y + 1;
+    }
+    if (verbose) 
+        pm_message("Test %d<x<%d %d<y<%d", *XminP, *XmaxP, *YminP, *YmaxP);
+}
+
+
+
+/*
+ *  Find the weighted best line fit using the left hand margin of the
+ * right hand image.
+ */
+static bool
+SliverMatch(Stitcher * me, Image * Left, Image * Right,
+            unsigned image_portion, unsigned skip_sliver)
+{
+    /* up/down 3/10, make sure has an odd number of members */
+    unsigned const bestSize = 
+        1 + 2 * ((image_portion * 3) / (10 * skip_sliver));
+    Best       ** best; /* malloc'ed array of Best * */
+    float         sumydotx, sumx, sumydot, sum;
+    float         sumydoty, sumy, sumydotydot;
+    int           yDiff;
+    unsigned      X, Xmin, Xmax, num, xmin, xmax;
+    int           x, y, Y, Ymin, Ymax, in, ymin, ymax;
+
+    /* Harry Sticks Geeses */
+    if (me->width == INT_MAX) {
+        me->width = Right->pam.width / image_portion;
+    }
+    if ((me->width > (Right->pam.width/2))
+     || (me->width > (Left->pam.width/2))) {
+        pm_error ("stitch sample too wide %d\n", me->width);
+        /* NOTREACHED */
+    }
+    if (me->height == INT_MAX) {
+        me->height = Right->pam.height / image_portion;
+    }
+    if ((me->height > Right->pam.height)
+     || (me->height > Left->pam.height)) {
+        pm_error ("stitch sample too high %d\n", me->height);
+        /* NOTREACHED */
+    }
+    yDiff = (Right->pam.height * skip_sliver) / image_portion;
+    starInit((unsigned long)-1L);
+
+    allocate_best_array(&best, bestSize);
+
+    determineXYRange(me, Left, Right, &Xmin, &Xmax, &Ymin, &Ymax);
+
+    /* Find the best */
+    if ((verbose == 1) || (verbose == 2)) {
+        fprintf (stderr, "%79s|\r|", "");
+        starInit((unsigned long)
+                 (
+                     (unsigned long)(Xmax - Xmin)
+                     * (unsigned long)(Ymax - Ymin)
+                     ) * (unsigned long)bestSize
+                 / 78L);
+    }
+
+    /* A point in the middle of the right image */
+    x = 0;
+    y = (Right->pam.height - me->height) / 2;
+    /*
+     *  Exhaustively search for the best match, improvements
+     * in the algorithm here, if any, would give us the best
+     * bang for the buck when it comes to improving performance.
+         */
+        /*
+         *  First pass through the right hand images to determine
+         * which are good candidate (top 90 percentile) for content of
+         * features that we may have a chance of testing with.
+         */
+    {
+        float   minf, maxf;
+        float * features;
+
+        MALLOCARRAY(features, bestSize);
+        minf = maxf = 0.0;
+        for (in = 0; in < bestSize; ++in) {
+            int const offY = yDiff * (in - (bestSize/2));
+            float SUM[3], SUMSQ[3];
+            int plane;
+            for (plane = 0; plane < MIN(Right->pam.depth,3); ++plane) {
+                SUM[plane] = SUMSQ[plane] = 0.0;
+            }
+            for (X = x; X < (x + me->width); ++X) {
+                for (Y = y + offY; Y < (y + offY + me->height); ++Y) {
+                    for (plane = 0; 
+                         plane < MIN(Right->pam.depth,3); 
+                         ++plane) {
+                        sample point = Right->tuple[Y][X][plane];
+                        SUM[plane]   += point;
+                        SUMSQ[plane] += point * point;
+                    }
+                }
+            }
+            /* How many features */
+            features[in] = 0.0;
+            for (plane = 0; plane < MIN(Right->pam.depth,3); ++plane) {
+                features[in] += SUMSQ[plane] - 
+                    (SUM[plane]*SUM[plane]/(float)(me->width*me->height));
+            }
+            if ((minf == 0.0) || (features[in] < minf)) {
+                minf = features[in];
+            }
+            if ((maxf == 0.0) || (features[in] > maxf)) {
+                maxf = features[in];
+            }
+        }
+        /* Select 90% in the contrast range */
+        minf = (minf + maxf) / 10;
+        for (in = 0; in < bestSize; ++in) {
+            if (features[in] < minf) {
+                free_best(best[in]);
+                best[in] = (Best *)NULL;
+            }
+        }
+    }
+    /* Loop through the constraints to find the best match */
+    sumydotx=sumx=sumydot=sumydotydot=sum=sumydoty=sumy=0.0;
+    xmin = UINT_MAX;
+    xmax = 0;
+    ymin = INT_MAX;
+    ymax = INT_MIN;
+    in = num = 0;
+    for (;;) {
+        float w;
+        int offY = yDiff * (in - (bestSize/2));
+        /* See if this one to be skipped because of too few features */
+        if (best[in] == (Best *)NULL) {
+            if (in > (bestSize/2)) {
+                in = bestSize - in;
+            } else if (in < (bestSize/2)) {
+                in = (bestSize-1) - in;
+            } else {
+                break;
+            }
+            if ((verbose == 1) || (verbose == 2))
+                for (X = Xmin; X < Xmax; ++X) {
+                    for (Y = Ymin; Y < Ymax; ++Y) 
+                        starEvent();
+                }
+            continue;
+        }
+        findBestMatches(Left, Right, x, y, me->width, me->height, 
+                        offY, Xmin, Xmax, Ymin, Ymax,
+                        best[in]);
+        /* slop (noise in NUM_BEST) */
+        {
+            float SUMx, SUMxx, SUMy, SUMyy, SUMw;
+            unsigned i;
+            SUMx = SUMxx = SUMy = SUMyy = SUMw = 0.0;
+            for (i = 0; i < NUM_BEST; ++i) {
+                /* best[in][i] describes the ith closest region in the right
+                   image to the region in the left image whose top corner is
+                   at (x, y+offY).
+                */
+                float const w2 = (best[in][i].total > 0)
+                    ? (1.0 / (float)best[in][i].total)
+                    : 1.0;
+                SUMx  += w2 * best[in][i].x;
+                SUMy  += w2 * best[in][i].y;
+                SUMxx += w2 * (best[in][i].x * best[in][i].x);
+                SUMyy += w2 * (best[in][i].y * best[in][i].y);
+                SUMw  += w2;
+            }
+            /* Find our weighted error */
+            w = SUMw
+                / ((SUMxx - (SUMx*SUMx)/SUMw) + (SUMyy - (SUMy*SUMy)/SUMw));
+        }
+        /* magnify slop */
+        w *= w;
+        Y = y + offY;
+        sumy        += w * best[in][0].y;
+        sumx        += w * best[in][0].x;
+        sum         += w;
+        sumydot     += w * Y;
+        sumydotydot += w * Y * Y;
+        sumydoty    += w * Y * best[in][0].y;
+        sumydotx    += w * Y * best[in][0].x;
+        /* Calculate the best fit line for these matches */
+        me->parms[Sliver_C] = ((sumydotydot * sum)
+                               - (sumydot * sumydot));
+        if (me->parms[Sliver_C] == 0.0) {
+            me->parms[Sliver_A] = 0.0;
+        } else {
+            me->parms[Sliver_A] = ((sumydotx * sum)
+                                   - (sumx * sumydot))
+                / me->parms[Sliver_C];
+            me->parms[Sliver_C] = ((sumydoty * sum)
+                                   - (sumy * sumydot))
+                / me->parms[Sliver_C];
+        }
+        if (sum == 0.0) {
+            me->parms[Sliver_B] = me->parms[Sliver_D] = 0;
+        } else {
+            me->parms[Sliver_B] = (sumx
+                                   - (me->parms[Sliver_A] * sumydot))
+                / sum;
+            me->parms[Sliver_D] = (sumy
+                                   - (me->parms[Sliver_C] * sumydot))
+                / sum;
+        }
+        if (verbose > 2) {
+            fprintf (stderr, "%.4g*(%d,%d)@(%d,%d)\n",
+                     w, best[in][0].x + Left->pam.width, best[in][0].y,
+                     me->width / 2, Y);
+        }
+        /* Record history of limits */
+        if ((best[in][0].x + Left->pam.width) < xmin) {
+            xmin = best[in][0].x + Left->pam.width;
+        }
+        if (xmax < (best[in][0].x + Left->pam.width)) {
+            xmax = best[in][0].x + Left->pam.width;
+        }
+        if ((best[in][0].y - offY) < ymin) {
+            ymin = best[in][0].y - offY;
+        }
+        if (ymax < (best[in][0].y - offY)) {
+            ymax = best[in][0].y - offY;
+        }
+        /* Lets restrict the search a bit now */
+        if (++num > 1) {
+            if (me->x == INT_MAX) {
+                int newXmin, newXmax, hold;
+                /* Use the formula to determine the bounds */
+                newXmin = (int)(me->parms[Sliver_B] + 0.5)
+                    + Left->pam.width;
+                newXmax = (int)((me->parms[Sliver_A]
+                                 * (float)Left->pam.height)
+                                + me->parms[Sliver_B] + 0.5)
+                    + Left->pam.width;
+                if (newXmax < newXmin) {
+                    hold = newXmin;
+                    newXmin = newXmax;
+                    newXmax = hold;
+                }
+                /* Trust little ... */
+                hold = (3 * newXmin - newXmax) / 2;
+                newXmax = (3 * newXmax - newXmin) / 2;
+                newXmin = hold;
+                /* Don't go inside history */
+                if (xmin < newXmin) {
+                    newXmin = xmin + Left->pam.width;
+                }
+                if (newXmax < xmax) {
+                    newXmax = xmax + Left->pam.width;
+                }
+                /* If it is `wacky' drop it */
+                if ((newXmax - Xmax) < (Right->pam.width / 3)) {
+                    /* Now upgrade new minimum and maximum */
+                    hold = Xmin;
+                    if ((Xmin < newXmin) && (newXmin < Xmax)) {
+                        hold = newXmin;
+                    }
+                    if ((Xmin < newXmax) && (newXmax < Xmax)) {
+                        Xmax = newXmax;
+                    }
+                    Xmin = hold;
+                }
+            }
+            if (me->y == INT_MAX) {
+                int newYmin, newYmax, hold;
+                float tmp;
+                /* Use the formula to determine the bounds */
+                newYmin = tmp = me->parms[Sliver_D]
+                    + ((float)(Left->pam.height + 1))
+                    / 2;
+                newYmax = (int)((me->parms[Sliver_C]
+                                 * (float)Left->pam.height)
+                                + tmp) - Left->pam.height;
+                if (newYmax < newYmin) {
+                    hold = newYmin;
+                    newYmin = newYmax;
+                    newYmax = hold;
+                }
+                /* Trust little ... */
+                hold = (3 * newYmin - newYmax) / 2;
+                newYmax = (3 * newYmax - newYmin) / 2;
+                newYmin = hold;
+                /* Don't go inside history */
+                if (ymin < newYmin) {
+                    newYmin = ymin;
+                }
+                if (newYmax < ymax) {
+                    newYmax = ymax;
+                }
+                /* Now upgrade new minimum and maximum */
+                hold = Ymin;
+                if ((Ymin < newYmin) && (newYmin < Ymax)) {
+                    hold = newYmin;
+                }
+                if ((Ymin < newYmax) && (newYmax < Ymax)) {
+                    Ymax = newYmax;
+                }
+                Ymin = hold;
+            }
+            if ((verbose == 1) || (verbose == 2)) {
+                starResetPeriod((unsigned long)(
+                    ((unsigned long)(Xmax - Xmin)
+                    * (unsigned long)(Ymax - Ymin))
+                                * (unsigned long)bestSize
+                                / 78L));
+            }
+        }
+        if (in > (bestSize/2)) {
+            in = bestSize - in;
+        } else if (in < (bestSize/2)) {
+            in = (bestSize-1) - in;
+        } else {
+            break;
+        }
+    }
+    if ((verbose == 1) || (verbose == 2)) {
+        fprintf (stderr, "\n");
+    }
+    if (verbose > 2) {
+        fprintf (stderr, "Up  ");
+        pr_best(best[bestSize-1]);
+        fprintf (stderr, "\nMid ");
+        pr_best(best[bestSize/2]);
+        fprintf (stderr, "\nDown");
+        pr_best(best[0]);
+        fprintf (stderr, "\n");
+    }
+
+    if (verbose) {
+        if (verbose > 1) {
+            fprintf (stderr,
+                     "[y=%.4g [x=%.4g [=%.4g [y.=%.4g "
+                     "[y.y.=%.4g [y.y=%.4g [y.x=%.4g\n",
+                     sumy, sumx, sum, sumydot, sumydotydot,
+                     sumydoty, sumydotx);
+        }
+        fprintf (stderr, "x=%.4gY%+.4g\ny=%.4gY%+.4g\n",
+                 me->parms[Sliver_A], me->parms[Sliver_B],
+                 me->parms[Sliver_C], me->parms[Sliver_D]);
+    }
+    /*
+         *  Free up resources
+         */
+    for (in = 0; in < bestSize; ++in) {
+        if (best[in] != (Best *)NULL) {
+            free_best (best[in]);
+            best[in] = (Best *)NULL;
+        }
+    }
+    free(best);
+
+        /* Calculate x',y' and x",y" from best fit line formula */
+    sum = (float)(Right->pam.width * Right->pam.height)
+        / (float)(2 * Right->pam.width - me->width);
+    me->parms[Sliver_xpp] = me->parms[Sliver_A] * sum;
+    me->parms[Sliver_ypp] = me->parms[Sliver_C] * sum;
+    sumx = me->parms[Sliver_A] * (Right->pam.height / 2)
+        + me->parms[Sliver_B];
+    sumx += Left->pam.width;
+    sumy = me->parms[Sliver_C] * (Right->pam.height / 2)
+        + me->parms[Sliver_D];
+    me->parms[Sliver_xp] = sumx - me->parms[Sliver_xpp];
+    me->parms[Sliver_yp] = sumy - me->parms[Sliver_ypp];
+    me->parms[Sliver_xpp] += sumx;
+    me->parms[Sliver_ypp] += sumy;
+    if (verbose > 1) {
+        fprintf (stderr, "x',y'=%.4g,%.4g x\",y\"=%.4g,%.4g\n",
+                 me->parms[Sliver_xp], me->parms[Sliver_yp],
+                 me->parms[Sliver_xpp], me->parms[Sliver_ypp]);
+    }
+    return TRUE;
+} /* SliverMatch() - end */
+
+/* These are not used.  Perhaps they are forerunners of the more
+   expressive Sliver_A, etc. names.
+*/
+#define Linear_a   0
+#define Linear_b   1
+#define Linear_c   2
+#define Linear_d   3
+#define Linear_e   4
+#define Linear_f   5
+#define Linear_g   6
+#define Linear_h   7
+
+static bool
+LinearMatch(Stitcher * me, Image * Left, Image * Right)
+{
+    if (SliverMatch(me, Left, Right, IMAGE_PORTION, SKIP_SLIVER * 8) 
+        == FALSE) {
+        return FALSE;
+    }
+
+    me->x = - (me->parms[Sliver_xp] + me->parms[Sliver_xpp] + 1) / 2;
+    me->y = - (me->parms[Sliver_yp] + me->parms[Sliver_ypp]
+          + (1 - Left->pam.height)) / 2;
+
+    if (verbose) 
+        pm_message("LinearMatch translation parameters are (%d,%d)",
+                   me->x, me->y);
+
+    return TRUE;
+} /* LinearMatch() - end */
+
+/*
+ *  Transformation parameters
+ *      left  x' = x
+ *      left  y' = y
+ *      right x' = x + me->x
+ *      right y' = y + me->y
+ */
+static float
+LinearXLeft(Stitcher * me, int x, int y)
+{
+    UNREFERENCED_PARAMETER(y);
+    return x;
+} /* LinearXLeft() - end */
+
+static float
+LinearYLeft(Stitcher * me, int x, int y)
+{
+    UNREFERENCED_PARAMETER(x);
+    return y;
+} /* LinearYLeft() - end */
+
+static float
+LinearXRight(Stitcher * me, int x, int y)
+{
+    UNREFERENCED_PARAMETER(y);
+    return (x + me->x);
+} /* LinearXRight() - end */
+
+static float
+LinearYRight(Stitcher * me, int x, int y)
+{
+    UNREFERENCED_PARAMETER(x);
+    return (y + me->y);
+} /* LinearYRight() - end */
+
+static void
+LinearOutput(Stitcher * me, FILE * fp)
+{
+    fprintf (fp, "x'=x%+d\ny'=y%+d\n", me->x, me->y);
+} /* LinearOutput() - end */
+
+/* BiLinear Stitcher Methods */
+
+static void
+BiLinearDeAlloc(Stitcher * me)
+{
+    LinearDeAlloc(me);
+}
+
+
+
+static bool
+BiLinearAlloc(Stitcher * me)
+{
+    return LinearAlloc(me);
+}
+
+
+
+static void
+BiLinearConstrain(Stitcher * me, int x, int y, int width, int height)
+{
+    LinearConstrain(me, x, y, width, height);
+    if (x != INT_MAX) {
+        me->parms[3] -= x;
+    }
+    if (y != INT_MAX) {
+        me->parms[7] -= y;
+    }
+} /* BiLinearConstrain() - end */
+
+/*
+ *  First pass is to find an approximate match. To do so, we take a
+ *  width sliver of the left hand side of the right image and compare
+ *  the sample to the left hand image. Accuracy is honored over speed.
+ *  The image overlap is expected between 7/16 to 1/16 in the horizontal
+ *  position, and a minumum of 5/8 in the vertical dimension.
+ *
+ *  Blind alleys:
+ *      - Tried a simpler constraint for right side to be `back'
+ *        to image, twisted too much sometimes:
+ *         . . .
+ *         W=aW+bH+cWH+d
+ *         H=eW+fH+gWH+h
+ *         W=aW+d
+ *         0=eW+h
+ *        Solve the equations resulted in:
+ *         a = W/(W-x") - cy"
+ *         b = -Wc
+ *         c = W/((x"-W)(y'-y"))
+ *         d = (1-a)W
+ *         e = y'(y"x'+W-Hx'-x"y")/(x'y"x"-Wx'y"-Wy'x"-WWy'+x'y'x"-Wx'y'-W)
+ *         f = 1 - Wg
+ *         g = (e + (H-y")/(x"-W))/y"
+ *         h = -We
+ *        Results left here for historical reasons.
+ *
+ *  Transformation parameters
+ *      x=ax.+by.+cx.y.+d
+ *      y=ex.+fy.+gx.y.+h
+ *  Where x,y represents the original point, and x.,y.
+ *  represents the transformed point. Thus:
+ *
+ * Transformed image:
+ * (x',y')               (x'",y'")
+ * (x",y")               (x"",y"")
+ *
+ * Corresponding to Original (dot) image:
+ * (0,0)                 (Right->pam.width,0)
+ * (0,Right->pam.height) (Right->pam.width,Right->pam.height)
+ *
+ * Define:
+ *      H=Right->pam.height
+ *      W=Right->pam.width
+ * Given that I want a flat presentation that both reduces the distortion
+ * necessary on an image, reduces the cropping losses, and flattens out the
+ * spherical or orbit distortions; it was chosen to constrain the right side
+ * in the middle horizontal, and pivot the left side in that middle (hopefully
+ * minimally) and to allow the image only vertical and horizontal location
+ * placement. Rotating the entire image could increase cropping losses
+ * especially if the focus was not down the center of the image on a
+ * graduated field causing the distortion to accumulate in subsequent
+ * images. Trapezoidal would cause the distortion to accumulate in subsequent
+ * images as well, resetting to `square' gradually towards the right would
+ * allow the next image to restart a match placing the distortions mainly
+ * in the stitching zone where averaging and the slight expectation of
+ * artifacts would minimize the effects. These constraints can be explained
+ * mathematically as the following:
+ *  x'" + x"" - 2W = x' + x"
+ *       y'" + y"" = y' + y"
+ *                 x'" = x""
+ *         y'" + H = y""
+ * resulting in the right side of the image being completely explained by the
+ * placement of the left hand side:
+ *  x'"=(x'+x"+2W)/2
+ *  y'"=(y'+y"-H)/2
+ *  x""=(x'+x"+2W)/2
+ *  y""=(y'+y"+H)/2
+ *
+ * Describing the `X' polygon using geometry and ratios:
+ *  X=A(y-(y'+y")/2)(x-(x'+x"+2W)/2) + (x - (x'+x")/2))
+ *    A=2(x"-x')/((y'-y")(x'-x"-2W))
+ *  a=2(y'(x'-x")-W(y'-y"))/((y'-y")(x'-x"-2W))
+ *  b=(x'-x")(x'+x"+2W)/((y'-y")(x'-x"-2W))
+ *  c=2(x"-x')/((y'-y")(x'-x"-2W))
+ *  d=(2W(x"y'-x'y")+y'(x"x"-x'x'))/((y'-y")(x'-x"-2W))
+ *
+ * Describing the `Y' polygon using geometry and ratios (note use of X rather
+ * than x, this has the effect of linearalizing the polygon).
+ *  Y=((y'-y"+H)/W(y'-y"))(y-(y'+y")/2)(X-HW/(y'-y"+H)) + H/2
+ *  e=(y'+y")(y'-y"+H)/2W(y"-y')
+ *  f=H/(y"-y')
+ *  g=(y'-y"+H)/W(y'-y")
+ *  h=Hy'/(y'-y")
+ *
+ * FYI: Reverse transform using the same formula style is:
+ *  a=(x"-x'+2W)/2W
+ *  b=(x"-x')/H
+ *  c=(x'-x")/WH
+ *  d=x'
+ *  e=(y"-y'-H)/2W
+ *  f=(y"-y')/H
+ *  g=(y'-y"+H)/WH
+ *  h=y'
+ */
+
+#define BiLinear_a   0
+#define BiLinear_b   1
+#define BiLinear_c   2
+#define BiLinear_d   3
+#define BiLinear_e   4
+#define BiLinear_f   5
+#define BiLinear_g   6
+#define BiLinear_h   7
+
+static bool
+BiLinearMatch(Stitcher * me, Image * Left, Image * Right)
+{
+    float xp, yp, xpp, ypp;
+
+    if (SliverMatch(me, Left, Right, IMAGE_PORTION, SKIP_SLIVER) == FALSE) {
+        return FALSE;
+    }
+    /* If too wacky, flatten out */
+    xp  = me->parms[Sliver_xp];
+    yp  = me->parms[Sliver_yp];
+    xpp = me->parms[Sliver_xpp];
+    ypp = me->parms[Sliver_ypp];
+    if ((me->parms[Sliver_A] < -0.3)
+     || (0.3 < me->parms[Sliver_A])) {
+        xp = xpp = (xp + xpp) / 2;
+    }
+    if ((me->parms[Sliver_C] < 0.6)
+     || (1.5 < me->parms[Sliver_D])) {
+        yp = (yp + ypp - (float)Right->pam.height) / 2;
+        ypp = yp + Right->pam.height;
+    }
+
+    /*
+     *  Calculate any necessary transformations on the
+     * right image to improve the stitching match. We have Done a
+     * weighted best fit line on the points we have collected
+     * thus far, now translate this to the constrained
+     * transformation equations.
+     */
+    /* a = y"-y' */
+    me->parms[BiLinear_a] = ypp-yp;
+    /* c = x'-x" */
+    me->parms[BiLinear_c] = xp-xpp;
+    /* d = (y"-y')(x"-x'+2W) = (y'-y")(x'-x"-2W) */
+    me->parms[BiLinear_d] = me->parms[BiLinear_a]
+                          * ((float)
+                             (2*Right->pam.width)-me->parms[BiLinear_c]);
+    /* a = 2(y'(x'-x")+W(y"-y'))/((y'-y")(x'-x"-2W)) */
+    me->parms[BiLinear_a] = 2*(yp*me->parms[BiLinear_c]
+                          + me->parms[BiLinear_a]*(float)(Right->pam.width))
+                          / me->parms[BiLinear_d];
+    /* b = (x'-x")(x'+x"+2W)/((y'-y")(x'-x"-2W)) */
+    me->parms[BiLinear_b] = me->parms[BiLinear_c]
+                          * (xp+xpp+(float)(2*Right->pam.width))
+                          / me->parms[BiLinear_d];
+    /* c = -2(x'-x")/((y'-y")(x'-x"-2W)) */
+    me->parms[BiLinear_c]*= -2/me->parms[BiLinear_d];
+    /* d = (2W(x"y'-x'y")+y'(x"x"-x'x'))/((y'-y")(x'-x"-2W)) */
+    me->parms[BiLinear_d] = ((xpp*yp-xp*ypp)*(float)(2*Right->pam.width)
+                          + yp*(xpp*xpp-xp*xp))
+                          / me->parms[BiLinear_d];
+
+    /* f = y"-y' */
+    me->parms[BiLinear_f] = ypp-yp;
+    /* g = (y"-y'-H)/W(y"-y') */
+    me->parms[BiLinear_g] = (me->parms[BiLinear_f]-(float)Right->pam.height)
+                          / me->parms[BiLinear_f]
+                          / (float)Right->pam.width;
+    /* e = (y'+y")(y'-y"+H)/2W(y"-y') = -g(y'+y")/2 */
+    me->parms[BiLinear_e] = (yp+ypp)*me->parms[BiLinear_g]/-2;
+    /* f=H/(y"-y') */
+    me->parms[BiLinear_f] = ((float)Right->pam.height)
+                          / me->parms[BiLinear_f];
+    /* h = Hy'/(y'-y") = -fy' */
+    me->parms[BiLinear_h] = -yp*me->parms[BiLinear_f];
+
+    return TRUE;
+} /* BiLinearMatch() - end */
+
+/*
+ *  Transformation parameters
+ *      x`=x
+ *      y`=y
+ */
+#define BiLinearXLeft LinearXLeft
+#define BiLinearYLeft LinearYLeft
+
+/*
+ *  Transformation parameters
+ *      x`=ax+by+cxy+d
+ *      y`=ex`+fy+gx`y+h
+ */
+static float
+BiLinearXRight(Stitcher * me, int x, int y)
+{
+    return (me->parms[BiLinear_a] * x) + (me->parms[BiLinear_b] * y)
+         + (me->parms[BiLinear_c] * (x * y)) + me->parms[BiLinear_d];
+} /* BiLinearXRight() - end */
+
+static float
+BiLinearYRight(Stitcher * me, int x, int y)
+{
+    /* A little trick I learned from a biker */
+    float X = BiLinearXRight(me, x, y);
+    return (me->parms[BiLinear_e] * X) + (me->parms[BiLinear_f] * y)
+         + (me->parms[BiLinear_g] * (X * y)) + me->parms[BiLinear_h];
+} /* BiLinearYRight() - end */
+
+static void
+BiLinearOutput(Stitcher * me, FILE * fp)
+{
+    fprintf (fp,
+      "x'=%.6gx%+.6gy%+.6gxy%+.6g\ny'=%.6gx'%+.6gy%+.6gx'y%+.6g\n",
+      me->parms[BiLinear_a], me->parms[BiLinear_b], me->parms[BiLinear_c],
+      me->parms[BiLinear_d], me->parms[BiLinear_e], me->parms[BiLinear_f],
+      me->parms[BiLinear_g], me->parms[BiLinear_h]);
+} /* BiLinearOutput() - end */
+
+/* Rotate Stitcher Methods */
+
+#define RotateDeAlloc   BiLinearDeAlloc
+
+#define RotateAlloc     BiLinearAlloc
+
+#define RotateConstrain BiLinearConstrain
+
+/*
+ *  First pass is to utilize the SliverMatch.
+ *
+ *  Transformation parameters
+ *      x=ax.+by.+d
+ *      y=ex.+fy.+h
+ *  Where x,y represents the original point, and x.,y.
+ *  represents the transformed point. Thus:
+ *
+ * Transformed image:
+ * (x',y')               (x'",y'")
+ * (x",y")               (x"",y"")
+ *
+ * Corresponding to Original (dot) image:
+ * (0,0)                 (Right->pam.width,0)
+ * (0,Right->pam.height) (Right->pam.width,Right->pam.height)
+ *
+ * Define:
+ *      H=Right->pam.height
+ *      W=Right->pam.width
+ *
+ */
+
+#define Rotate_a   0
+#define Rotate_b   1
+#define Rotate_c   2
+#define Rotate_d   3
+#define Rotate_e   4
+#define Rotate_f   5
+#define Rotate_g   6
+#define Rotate_h   7
+
+static bool
+RotateMatch(Stitcher * me, Image * Left, Image * Right)
+{
+    float xp, yp, xpp, ypp;
+
+    if (SliverMatch(me, Left, Right, IMAGE_PORTION, SKIP_SLIVER) == FALSE) {
+        return FALSE;
+    }
+    xp  = me->parms[Sliver_xp];
+    yp  = me->parms[Sliver_yp];
+    xpp = me->parms[Sliver_xpp];
+    ypp = me->parms[Sliver_ypp];
+
+    me->parms[Rotate_c] = (xp - xpp);
+    me->parms[Rotate_c]*= me->parms[Rotate_c];
+    me->parms[Rotate_g] = (yp - ypp);
+    me->parms[Rotate_g]*= me->parms[Rotate_g];
+    me->parms[Rotate_a] = me->parms[Rotate_f] = sqrt(me->parms[Rotate_g]
+                                              / (me->parms[Rotate_c]
+                                                + me->parms[Rotate_g]));
+    me->parms[Rotate_b] = me->parms[Rotate_e] = sqrt(me->parms[Rotate_c]
+                                              / (me->parms[Rotate_c]
+                                                + me->parms[Rotate_g]));
+    if (xp < xpp) {
+        me->parms[Rotate_b] = -me->parms[Rotate_b];
+    } else {
+        me->parms[Rotate_e] = -me->parms[Rotate_e];
+    }
+    /* negative (for reverse transform below) xp & yp set for unity gain */
+    xp = ((me->parms[Rotate_b] * (float)Right->pam.height) + xp + xpp) / -2;
+    yp = ((me->parms[Rotate_f] * (float)Right->pam.height) - yp - ypp) / 2;
+    me->parms[Rotate_d] = xp * me->parms[Rotate_a] + yp * me->parms[Rotate_b];
+    me->parms[Rotate_h] = xp * me->parms[Rotate_e] + yp * me->parms[Rotate_f];
+    return TRUE;
+} /* RotateMatch() - end */
+
+/*
+ *  Transformation parameters
+ *      x`=x
+ *      y`=y
+ */
+#define RotateXLeft BiLinearXLeft
+#define RotateYLeft BiLinearYLeft
+
+/*
+ *  Transformation parameters
+ *      x`=ax+by+d
+ *      y`=ex+fy+h
+ */
+
+static float
+RotateXRight(Stitcher * me, int x, int y)
+{
+    return (me->parms[Rotate_a] * x) + (me->parms[Rotate_b] * y)
+          + me->parms[Rotate_d];
+} /* RotateXRight() - end */
+
+static float
+RotateYRight(Stitcher * me, int x, int y)
+{
+    return (me->parms[Rotate_e] * x) + (me->parms[Rotate_f] * y)
+          + me->parms[Rotate_h];
+} /* RotateYRight() - end */
+
+static void
+RotateOutput(Stitcher * me, FILE * fp)
+{
+    fprintf (fp,
+      "x'=%.6gx%+.6gy%+.6g\ny'=%.6gx%+.6gy%+.6g\n",
+      me->parms[Rotate_a], me->parms[Rotate_b], me->parms[Rotate_d],
+      me->parms[Rotate_e], me->parms[Rotate_f], me->parms[Rotate_h]);
+} /* RotateOutput() - end */
+
+/* Stitcher Method Table */
+
+Stitcher StitcherMethods[] = {
+    { "RotateSliver", RotateAlloc, RotateDeAlloc, RotateConstrain,
+      RotateMatch, RotateXLeft, RotateYLeft, RotateXRight,
+      RotateYRight, RotateOutput },
+    { "BiLinearSliver", BiLinearAlloc, BiLinearDeAlloc, BiLinearConstrain,
+      BiLinearMatch, BiLinearXLeft, BiLinearYLeft, BiLinearXRight,
+      BiLinearYRight, BiLinearOutput },
+    { "LinearSliver", LinearAlloc, LinearDeAlloc, LinearConstrain,
+      LinearMatch, LinearXLeft, LinearYLeft, LinearXRight,
+      LinearYRight, LinearOutput },
+    { (char *)NULL }
+};
diff --git a/editor/pnmtile.c b/editor/pnmtile.c
new file mode 100644
index 00000000..96bf658d
--- /dev/null
+++ b/editor/pnmtile.c
@@ -0,0 +1,63 @@
+/* pnmtile.c - replicate a portable anymap into a specified size
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pnm.h"
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    xel** xels;
+    register xel* xelrow;
+    xelval maxval;
+    int rows, cols, format, width, height, row, col;
+    const char* const usage = "width height [pnmfile]";
+
+    pnm_init( &argc, argv );
+
+    if ( argc < 3 || argc > 4 )
+	pm_usage( usage );
+
+    if ( sscanf( argv[1], "%d", &width ) != 1 )
+	pm_usage( usage );
+    if ( sscanf( argv[2], "%d", &height ) != 1 )
+	pm_usage( usage );
+
+    if ( width < 1 )
+	pm_error( "width is less than 1" );
+    if ( height < 1 )
+	pm_error( "height is less than 1" );
+
+    if ( argc == 4 )
+	ifp = pm_openr( argv[3] );
+    else
+	ifp = stdin;
+
+    xels = pnm_readpnm( ifp, &cols, &rows, &maxval, &format );
+    pm_close( ifp );
+
+    xelrow = pnm_allocrow( width );
+
+    pnm_writepnminit( stdout, width, height, maxval, format, 0 );
+    for ( row = 0; row < height; ++row )
+	{
+	for ( col = 0; col < width; ++col )
+	    xelrow[col] = xels[row % rows][col % cols];
+	pnm_writepnmrow( stdout, xelrow, width, maxval, format, 0 );
+	}
+
+    pm_close( stdout );
+
+    exit( 0 );
+    }
diff --git a/editor/ppm3d.c b/editor/ppm3d.c
new file mode 100644
index 00000000..c37ceeb1
--- /dev/null
+++ b/editor/ppm3d.c
@@ -0,0 +1,138 @@
+/* ppmto3d.c - convert a portable pixmap to a portable graymap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+#include "lum.h"
+
+static void
+computeGrayscaleRow(const pixel * const inputRow,
+                    gray *        const outputRow,
+                    pixval        const maxval,
+                    unsigned int  const cols) {
+
+    if (maxval <= 255) {
+        unsigned int col;
+        /* Use fast approximation to 0.299 r + 0.587 g + 0.114 b. */
+        for (col = 0; col < cols; ++col)
+            outputRow[col] = ppm_fastlumin(inputRow[col]);
+    } else {
+        unsigned int col;
+        /* Can't use fast approximation, so fall back on floats. */
+        for (col = 0; col < cols; ++col)
+            outputRow[col] = PPM_LUMIN(inputRow[col]) + 0.5;
+    }
+}
+
+
+
+int
+main (int argc, char *argv[]) {
+
+    int offset; 
+    int cols, rows, row;
+    pixel* pixelrow;
+    pixval maxval;
+
+    FILE* Lifp;
+    pixel* Lpixelrow;
+    gray* Lgrayrow;
+    int Lrows, Lcols, Lformat;
+    pixval Lmaxval;
+   
+    FILE* Rifp;
+    pixel* Rpixelrow;
+    gray* Rgrayrow;
+    int Rrows, Rcols, Rformat;
+    pixval Rmaxval;
+   
+    ppm_init (&argc, argv);
+
+    if (argc-1 > 3 || argc-1 < 2) 
+        pm_error("Wrong number of arguments (%d).  Arguments are "
+                 "leftppmfile rightppmfile [horizontal_offset]", argc-1);
+
+    Lifp = pm_openr (argv[1]);
+    Rifp = pm_openr (argv[2]);
+
+    if (argc-1 >= 3) 
+        offset = atoi (argv[3]);
+    else
+        offset = 30;
+
+    ppm_readppminit (Lifp, &Lcols, &Lrows, &Lmaxval, &Lformat);
+    ppm_readppminit (Rifp, &Rcols, &Rrows, &Rmaxval, &Rformat);
+    
+    if ((Lcols != Rcols) || (Lrows != Rrows) || 
+        (Lmaxval != Rmaxval) || 
+        (PPM_FORMAT_TYPE(Lformat) != PPM_FORMAT_TYPE(Rformat)))
+        pm_error ("Pictures are not of same size and format");
+    
+    cols = Lcols;
+    rows = Lrows;
+    maxval = Lmaxval;
+   
+    ppm_writeppminit (stdout, cols, rows, maxval, 0);
+    Lpixelrow = ppm_allocrow (cols);
+    Lgrayrow = pgm_allocrow (cols);
+    Rpixelrow = ppm_allocrow (cols);
+    Rgrayrow = pgm_allocrow (cols);
+    pixelrow = ppm_allocrow (cols);
+
+    for (row = 0; row < rows; ++row) {
+        ppm_readppmrow(Lifp, Lpixelrow, cols, maxval, Lformat);
+        ppm_readppmrow(Rifp, Rpixelrow, cols, maxval, Rformat);
+
+        computeGrayscaleRow(Lpixelrow, Lgrayrow, maxval, cols);
+        computeGrayscaleRow(Rpixelrow, Rgrayrow, maxval, cols);
+        {
+            int col;
+            gray* LgP;
+            gray* RgP;
+            pixel* pP;
+            for (col = 0, pP = pixelrow, LgP = Lgrayrow, RgP = Rgrayrow;
+                 col < cols + offset;
+                 ++col) {
+            
+                if (col < offset/2)
+                    ++LgP;
+                else if (col >= offset/2 && col < offset) {
+                    const pixval Blue = (pixval) (float) *LgP;
+                    const pixval Red = (pixval) 0;
+                    PPM_ASSIGN (*pP, Red, Blue, Blue);
+                    ++LgP;
+                    ++pP;
+                } else if (col >= offset && col < cols) {
+                    const pixval Red = (pixval) (float) *RgP;
+                    const pixval Blue = (pixval) (float) *LgP;
+                    PPM_ASSIGN (*pP, Red, Blue, Blue);
+                    ++LgP;
+                    ++RgP;
+                    ++pP;
+                } else if (col >= cols && col < cols + offset/2) {
+                    const pixval Blue = (pixval) 0;
+                    const pixval Red = (pixval) (float) *RgP;
+                    PPM_ASSIGN (*pP, Red, Blue, Blue);
+                    ++RgP;
+                    ++pP;
+                } else
+                    ++RgP;
+            }
+        }    
+        ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
+    }
+
+    pm_close(Lifp);
+    pm_close(Rifp);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/editor/ppmbrighten.c b/editor/ppmbrighten.c
new file mode 100644
index 00000000..93649082
--- /dev/null
+++ b/editor/ppmbrighten.c
@@ -0,0 +1,337 @@
+/* ppmbrighten.c - allow user control over Value and Saturation of PPM file
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+** Copyright (C) 1990 by Brian Moffet.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+#define MULTI   1000
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* '-' if stdin */
+    float saturation;
+    float value;
+    unsigned int normalize;
+};
+
+
+
+
+static void
+parseCommandLine (int argc, char ** argv,
+                  struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int saturationSpec, valueSpec;
+    int saturationOpt, valueOpt;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "saturation",  OPT_INT,    &saturationOpt,
+            &saturationSpec,      0 );
+    OPTENT3(0, "value",       OPT_INT,    &valueOpt,
+            &valueSpec,           0 );
+    OPTENT3(0, "normalize",   OPT_FLAG,   NULL,
+            &cmdlineP->normalize, 0 );
+
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+    
+    if (saturationSpec) {
+        if (saturationOpt < -100)
+            pm_error("Saturation reduction cannot be more than 100%%.  "
+                     "You specified %d", saturationOpt);
+        else
+            cmdlineP->saturation = 1.0 + (float)saturationOpt / 100;
+    } else
+        cmdlineP->saturation = 1.0;
+
+    if (valueSpec) {
+        if (valueOpt < -100)
+            pm_error("Value reduction cannot be more than 100%%.  "
+                     "You specified %d", valueOpt);
+        else
+            cmdlineP->value = 1.0 + (float)valueOpt / 100;
+    } else
+        cmdlineP->value = 1.0;
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        pm_error("Program takes at most one argument:  file specification");
+}
+
+
+
+static __inline__ unsigned int
+mod(int const dividend, unsigned int const divisor) {
+
+    int remainder = dividend % divisor;
+
+    if (remainder < 0)
+        return divisor + remainder;
+    else 
+        return (unsigned int) remainder;
+}
+
+
+
+static void 
+RGBtoHSV(pixel          const color,
+         pixval         const maxval,
+         unsigned int * const hP, 
+         unsigned int * const sP, 
+         unsigned int * const vP) {
+
+    unsigned int const R = (MULTI * PPM_GETR(color) + maxval - 1) / maxval;
+    unsigned int const G = (MULTI * PPM_GETG(color) + maxval - 1) / maxval;
+    unsigned int const B = (MULTI * PPM_GETB(color) + maxval - 1) / maxval;
+
+    unsigned int s, v;
+    unsigned int t;
+    unsigned int sector;
+
+    v = MAX(R, MAX(G, B));
+
+    t = MIN(R, MIN(G, B));
+
+    if (v == 0)
+        s = 0;
+    else
+        s = ((v - t)*MULTI)/v;
+
+    if (s == 0)
+        sector = 0;
+    else {
+        unsigned int const cr = (MULTI * (v - R))/(v - t);
+        unsigned int const cg = (MULTI * (v - G))/(v - t);
+        unsigned int const cb = (MULTI * (v - B))/(v - t);
+
+        if (R == v)
+            sector = mod((int)(cb - cg), 6*MULTI);
+        else if (G == v)
+            sector = mod((int)((2*MULTI) + cr - cb), 6*MULTI);
+        else if (B == v)
+            sector = mod((int)((4*MULTI) + cg - cr), 6*MULTI);
+        else
+            pm_error("Internal error: neither r, g, nor b is maximum");
+    }
+
+    *hP = sector * 60;
+    *sP = s;
+    *vP = v;
+}
+
+
+
+static void
+HSVtoRGB(unsigned int   const h, 
+         unsigned int   const s, 
+         unsigned int   const v, 
+         pixval         const maxval,
+         pixel *        const colorP) {
+    
+    unsigned int R, G, B;
+
+    if (s == 0) {
+        R = v;
+        G = v;
+        B = v;
+    } else {
+        unsigned int const sectorSize = 60 * MULTI;
+            /* Color wheel is divided into six 60 degree sectors. */
+        unsigned int const sector = (h/sectorSize);
+            /* The sector in which our color resides.  Value is in 0..5 */
+        unsigned int const f = (h - sector*sectorSize)/60;
+            /* The fraction of the way the color is from one side of
+               our sector to the other side, going clockwise.  Value is
+               in [0, MULTI).
+            */
+        unsigned int const m = (v * (MULTI - s)) / MULTI;
+        unsigned int const n = (v * (MULTI - (s * f)/MULTI)) / MULTI;
+        unsigned int const k = (v * (MULTI - (s * (MULTI - f))/MULTI)) / MULTI;
+
+        switch (sector) {
+        case 0:
+            R = v;
+            G = k;
+            B = m;
+            break;
+        case 1:
+            R = n;
+            G = v;
+            B = m;
+            break;
+        case 2:
+            R = m;
+            G = v;
+            B = k;
+            break;
+        case 3:
+            R = m;
+            G = n;
+            B = v;
+            break;
+        case 4:
+            R = k;
+            G = m;
+            B = v;
+            break;
+        case 5:
+            R = v;
+            G = m;
+            B = n;
+            break;
+        default:
+            pm_error("Invalid H value passed to HSVtoRGB: %u/%u", h, MULTI);
+        }
+    }
+    PPM_ASSIGN(*colorP, 
+               (R * maxval) / MULTI,
+               (G * maxval) / MULTI,
+               (B * maxval) / MULTI);
+}
+
+
+
+static void
+getMinMax(FILE *         const ifP,
+          int            const cols,
+          int            const rows,
+          pixval         const maxval,
+          int            const format,
+          unsigned int * const minValueP,
+          unsigned int * const maxValueP) {
+
+    pixel * pixelrow;
+    unsigned int minValue, maxValue;
+    int row;
+
+    pixelrow = ppm_allocrow(cols);
+
+    maxValue = 0;
+    minValue = MULTI;
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        ppm_readppmrow(ifP, pixelrow, cols, maxval, format);
+        for (col = 0; col < cols; ++col) {
+            unsigned int H, S, V;
+
+            RGBtoHSV(pixelrow[col], maxval, &H, &S, &V);
+            maxValue = MAX(maxValue, V);
+            minValue = MIN(minValue, V);
+        }
+    }
+    ppm_freerow(pixelrow);
+
+    *minValueP = minValue;
+    *maxValueP = maxValue;
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE *ifP;
+    pixval minValue, maxValue;
+    pixel *pixelrow;
+    pixval maxval;
+    int rows, cols, format, row;
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    if (cmdline.normalize)
+        ifP = pm_openr_seekable(cmdline.inputFilespec);
+    else
+        ifP = pm_openr(cmdline.inputFilespec);
+
+    ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
+
+    if (cmdline.normalize) {
+        pm_filepos rasterPos;
+        pm_tell2(ifP, &rasterPos, sizeof(rasterPos));
+        getMinMax(ifP, cols, rows, maxval, format, &minValue, &maxValue);
+        pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
+        pm_message("Minimum value %u%% of full intensity "
+                   "being remapped to zero.",
+                   (minValue*100+MULTI/2)/MULTI);
+        pm_message("Maximum value %u%% of full intensity "
+                   "being remapped to full.",
+                   (maxValue*100+MULTI/2)/MULTI);
+    }
+
+    pixelrow = ppm_allocrow(cols);
+
+    ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        ppm_readppmrow(ifP, pixelrow, cols, maxval, format);
+        for (col = 0; col < cols; ++col) {
+            unsigned int H, S, V;
+
+            RGBtoHSV(pixelrow[col], maxval, &H, &S, &V);
+            
+            if (cmdline.normalize) {
+                V -= minValue;
+                V = (V * MULTI) /
+                    (MULTI - (minValue+MULTI-maxValue));
+            }
+
+            S = MIN(MULTI, (unsigned int) (S * cmdline.saturation + 0.5));
+            V = MIN(MULTI, (unsigned int) (V * cmdline.value + 0.5));
+
+            HSVtoRGB(H, S, V, maxval, &pixelrow[col]);
+        }
+
+        ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
+    }
+    ppm_freerow(pixelrow);
+
+    pm_close(ifP);
+
+    /* If the program failed, it previously aborted with nonzero completion
+       code, via various function calls.
+    */
+    return 0;
+}
diff --git a/editor/ppmchange.c b/editor/ppmchange.c
new file mode 100644
index 00000000..92d55527
--- /dev/null
+++ b/editor/ppmchange.c
@@ -0,0 +1,232 @@
+/* ppmchange.c - change a given color to another
+**
+** Copyright (C) 1991 by Wilson H. Bent, Jr.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** Modified by Alberto Accomazzi (alberto@cfa.harvard.edu).
+**     28 Jan 94 -  Added multiple color substitution function.
+*/
+
+#include "ppm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+#define TCOLS 256
+#define SQRT3 1.73205080756887729352
+    /* The square root of 3 */
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec;  /* Filespecs of input files */
+    int ncolors;      /* Number of valid entries in color0[], color1[] */
+    char * oldcolorname[TCOLS];  /* colors user wants replaced */
+    char * newcolorname[TCOLS];  /* colors with which he wants them replaced */
+    int closeness;    
+       /* -closeness option value.  Zero if no -closeness option */
+    char * remainder_colorname;  
+      /* Color user specified for -remainder.  Null pointer if he didn't
+         specify -remainder.
+      */
+    unsigned int closeok;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int closenessSpec, remainderSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "closeness",     OPT_UINT,
+            &cmdlineP->closeness,           &closenessSpec,     0);
+    OPTENT3(0, "remainder",     OPT_STRING,
+            &cmdlineP->remainder_colorname, &remainderSpec,     0);
+    OPTENT3(0, "closeok",       OPT_FLAG,
+            NULL,                           &cmdlineP->closeok, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!closenessSpec)
+        cmdlineP->remainder_colorname = NULL;
+
+    if (!closenessSpec)
+        cmdlineP->closeness = 0;
+
+    if ((argc-1) % 2 == 0) 
+        cmdlineP->input_filespec = "-";
+    else
+        cmdlineP->input_filespec = argv[argc-1];
+
+    {
+        int argn;
+        cmdlineP->ncolors = 0;  /* initial value */
+        for (argn = 1; 
+             argn+1 < argc && cmdlineP->ncolors < TCOLS; 
+             argn += 2) {
+            cmdlineP->oldcolorname[cmdlineP->ncolors] = argv[argn];
+            cmdlineP->newcolorname[cmdlineP->ncolors] = argv[argn+1];
+            cmdlineP->ncolors++;
+        }
+    }
+}
+
+
+
+static double
+sqrf(float const F) {
+    return F*F;
+}
+
+
+
+static int 
+colormatch(pixel const comparand, 
+           pixel const comparator, 
+           float const closeness) {
+/*----------------------------------------------------------------------------
+   Return true iff 'comparand' matches 'comparator' in color within the
+   fuzz factor 'closeness'.
+-----------------------------------------------------------------------------*/
+    /* Fast path for usual case */
+    if (closeness == 0)
+        return PPM_EQUAL(comparand, comparator);
+
+    return PPM_DISTANCE(comparand, comparator) <= sqrf(closeness);
+}
+
+
+
+static void
+changeRow(const pixel * const inrow, 
+          pixel *       const outrow, 
+          int           const cols,
+          int           const ncolors, 
+          const pixel         colorfrom[], 
+          const pixel         colorto[],
+          bool          const remainder_specified, 
+          pixel         const remainder_color, 
+          float         const closeness) {
+/*----------------------------------------------------------------------------
+   Replace the colors in a single row.  There are 'ncolors' colors to 
+   replace.  The to-replace colors are in the array colorfrom[], and the
+   replace-with colors are in corresponding elements of colorto[].
+   Iff 'remainder_specified' is true, replace all colors not mentioned
+   in colorfrom[] with 'remainder_color'.  Use the closeness factor
+   'closeness' in determining if something in the input row matches
+   a color in colorfrom[].
+
+   The input row is 'inrow'.  The output is returned as 'outrow', in
+   storage which must be already allocated.  Both are 'cols' columns wide.
+-----------------------------------------------------------------------------*/
+    int col;
+
+    for (col = 0; col < cols; ++col) {
+        int i;
+        int have_match; /* logical: It's a color user said to change */
+        pixel newcolor;  
+        /* Color to which we must change current pixel.  Undefined unless
+           'have_match' is true.
+        */
+
+        have_match = FALSE;  /* haven't found a match yet */
+        for (i = 0; i < ncolors && !have_match; ++i) {
+            have_match = colormatch(inrow[col], colorfrom[i], closeness);
+            newcolor = colorto[i];
+        }
+        if (have_match)
+            outrow[col] = newcolor;
+        else if (remainder_specified)
+            outrow[col] = remainder_color;
+        else
+            outrow[col] = inrow[col];
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    int format;
+    int rows, cols;
+    pixval maxval;
+    float closeness;
+    int row;
+    pixel* inrow;
+    pixel* outrow;
+
+    pixel oldcolor[TCOLS];  /* colors user wants replaced */
+    pixel newcolor[TCOLS];  /* colors with which he wants them replaced */
+    pixel remainder_color;
+      /* Color user specified for -remainder.  Undefined if he didn't
+         specify -remainder.
+      */
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    ifP = pm_openr(cmdline.input_filespec);
+
+    ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
+
+    if (cmdline.remainder_colorname)
+        remainder_color = ppm_parsecolor2(cmdline.remainder_colorname, maxval,
+                                          cmdline.closeok);
+    { 
+        int i;
+        for (i = 0; i < cmdline.ncolors; ++i) {
+            oldcolor[i] = ppm_parsecolor2(cmdline.oldcolorname[i], maxval,
+                                          cmdline.closeok);
+            newcolor[i] = ppm_parsecolor2(cmdline.newcolorname[i], maxval,
+                                          cmdline.closeok);
+        }
+    }
+    closeness = SQRT3 * maxval * cmdline.closeness/100;
+
+    ppm_writeppminit( stdout, cols, rows, maxval, 0 );
+    inrow = ppm_allocrow(cols);
+    outrow = ppm_allocrow(cols);
+
+    /* Scan for the desired color */
+    for (row = 0; row < rows; row++) {
+        ppm_readppmrow(ifP, inrow, cols, maxval, format);
+
+        changeRow(inrow, outrow, cols, cmdline.ncolors, oldcolor, newcolor,
+                  cmdline.remainder_colorname != NULL,
+                  remainder_color, closeness);
+
+        ppm_writeppmrow(stdout, outrow, cols, maxval, 0);
+    }
+
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/editor/ppmcolormask.c b/editor/ppmcolormask.c
new file mode 100644
index 00000000..57e5c825
--- /dev/null
+++ b/editor/ppmcolormask.c
@@ -0,0 +1,245 @@
+/*=========================================================================
+                             ppmcolormask
+===========================================================================
+
+  This program produces a PBM mask of areas containing a certain color.
+
+  By Bryan Henderson, Olympia WA; April 2000.
+
+  Contributed to the public domain by its author.
+=========================================================================*/
+
+#include <assert.h>
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "ppm.h"
+#include "pbm.h"
+
+enum matchType {
+    MATCH_EXACT,
+    MATCH_BK
+};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilename;
+    unsigned int colorCount;
+    struct {
+        enum matchType matchType;
+        union {
+            pixel    color;   /* matchType == MATCH_EXACT */
+            bk_color bkColor; /* matchType == MATCH_BK */
+        } u;
+    } maskColor[16];
+    unsigned int verbose;
+};
+
+
+
+static void
+parseColorOpt(const char *         const colorOpt,
+              struct cmdlineInfo * const cmdlineP) {
+
+    unsigned int colorCount;
+    char * colorOptWork;
+    char * cursor;
+    bool eol;
+    
+    colorOptWork = strdup(colorOpt);
+    cursor = &colorOptWork[0];
+    
+    eol = FALSE;    /* initial value */
+    colorCount = 0; /* initial value */
+    while (!eol && colorCount < ARRAY_SIZE(cmdlineP->maskColor)) {
+        const char * token;
+        token = strsepN(&cursor, ",");
+        if (token) {
+            if (STRNEQ(token, "bk:", 3)) {
+                cmdlineP->maskColor[colorCount].matchType = MATCH_BK;
+                cmdlineP->maskColor[colorCount].u.bkColor =
+                    ppm_bk_color_from_name(&token[3]);
+            } else {
+                cmdlineP->maskColor[colorCount].matchType = MATCH_EXACT;
+                cmdlineP->maskColor[colorCount].u.color =
+                    ppm_parsecolor(token, PPM_MAXMAXVAL);
+            }
+            ++colorCount;
+        } else
+            eol = TRUE;
+    }
+    free(colorOptWork);
+
+    cmdlineP->colorCount = colorCount;
+}
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that many of the strings that this function returns in the
+   *cmdlineP structure are actually in the supplied argv array.  And
+   sometimes, one of these strings is actually just a suffix of an entry
+   in argv!
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    const char * colorOpt;
+    unsigned int colorSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "color",      OPT_STRING, &colorOpt, &colorSpec,           0);
+    OPTENT3(0, "verbose",    OPT_FLAG,   NULL, &cmdlineP->verbose,        0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and all of *cmdlineP. */
+
+    if (colorSpec)
+        parseColorOpt(colorOpt, cmdlineP);
+
+    if (colorSpec) {
+        if (argc-1 < 1)
+            cmdlineP->inputFilename = "-";  /* he wants stdin */
+        else if (argc-1 == 1)
+            cmdlineP->inputFilename = argv[1];
+        else
+            pm_error("Too many arguments.  When you specify -color, "
+                     "the only argument accepted is the optional input "
+                     "file name.");
+    } else {
+        if (argc-1 < 1)
+            pm_error("You must specify the -color option.");
+        else {
+            cmdlineP->colorCount = 1;
+            cmdlineP->maskColor[0].matchType = MATCH_EXACT;
+            cmdlineP->maskColor[0].u.color =
+                ppm_parsecolor(argv[1], PPM_MAXMAXVAL);
+
+            if (argc - 1 < 2)
+                cmdlineP->inputFilename = "-";  /* he wants stdin */
+            else if (argc-1 == 2)
+                cmdlineP->inputFilename = argv[2];
+            else 
+                pm_error("Too many arguments.  The only arguments accepted "
+                         "are the mask color and optional input file name");
+        }
+    }
+}
+
+
+
+static bool
+isBkColor(pixel    const comparator,
+          pixval   const maxval,
+          bk_color const comparand) {
+
+    /* TODO: keep a cache of the bk color for each color in
+       a colorhash_table.
+    */
+    
+    bk_color const comparatorBk = ppm_bk_color_from_color(comparator, maxval);
+
+    return comparatorBk == comparand;
+}
+
+
+
+static bool
+colorIsInSet(pixel              const color,
+             pixval             const maxval,
+             struct cmdlineInfo const cmdline) {
+
+    bool isInSet;
+    unsigned int i;
+
+    for (i = 0, isInSet = FALSE;
+         i < cmdline.colorCount && !isInSet; ++i) {
+
+        assert(i < ARRAY_SIZE(cmdline.maskColor));
+
+        switch(cmdline.maskColor[i].matchType) {
+        case MATCH_EXACT:
+            if (PPM_EQUAL(color, cmdline.maskColor[i].u.color))
+                isInSet = TRUE;
+            break;
+        case MATCH_BK:
+            if (isBkColor(color, maxval, cmdline.maskColor[i].u.bkColor))
+                isInSet = TRUE;
+            break;
+        }
+    }
+    return isInSet;
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+
+    FILE * ifP;
+
+    /* Parameters of input image: */
+    int rows, cols;
+    pixval maxval;
+    int format;
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilename);
+
+    ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
+    pbm_writepbminit(stdout, cols, rows, 0);
+    {
+        pixel * const inputRow = ppm_allocrow(cols);
+        bit *   const maskRow  = pbm_allocrow(cols);
+
+        unsigned int numPixelsMasked;
+
+        unsigned int row;
+        for (row = 0, numPixelsMasked = 0; row < rows; ++row) {
+            int col;
+            ppm_readppmrow(ifP, inputRow, cols, maxval, format);
+            for (col = 0; col < cols; ++col) {
+                if (colorIsInSet(inputRow[col], maxval, cmdline)) {
+                    maskRow[col] = PBM_BLACK;
+                    ++numPixelsMasked;
+                } else 
+                    maskRow[col] = PBM_WHITE;
+            }
+            pbm_writepbmrow(stdout, maskRow, cols, 0);
+        }
+
+        if (cmdline.verbose)
+            pm_message("%u pixels found matching %u requested colors",
+                       numPixelsMasked, cmdline.colorCount);
+
+        pbm_freerow(maskRow);
+        ppm_freerow(inputRow);
+    }
+    pm_close(ifP);
+
+    return 0;
+}
+
+
+
diff --git a/editor/ppmdim.c b/editor/ppmdim.c
new file mode 100644
index 00000000..4e64965a
--- /dev/null
+++ b/editor/ppmdim.c
@@ -0,0 +1,112 @@
+
+/*********************************************************************/
+/* ppmdim -  dim a picture down to total blackness                   */
+/* Frank Neumann, October 1993                                       */
+/* V1.4 16.11.1993                                                   */
+/*                                                                   */
+/* version history:                                                  */
+/* V1.0 ~ 15.August 1993    first version                            */
+/* V1.1 03.09.1993          uses ppm libs & header files             */
+/* V1.2 03.09.1993          integer arithmetics instead of float     */
+/*                          (gains about 50 % speed up)              */
+/* V1.3 10.10.1993          reads only one line at a time - this     */
+/*                          saves LOTS of memory on big pictures     */
+/* V1.4 16.11.1993          Rewritten to be NetPBM.programming con-  */
+/*                          forming                                  */
+/*********************************************************************/
+
+#include "ppm.h"
+
+/* global variables */
+#ifdef AMIGA
+static char *version = "$VER: ppmdim 1.4 (16.11.93)"; /* Amiga version identification */
+#endif
+
+/**************************/
+/* start of main function */
+/**************************/
+int main(argc, argv)
+int argc;
+char *argv[];
+{
+	FILE* ifp;
+	int argn, rows, cols, format, i = 0, j = 0;
+	pixel *srcrow, *destrow;
+	pixel *pP = NULL, *pP2 = NULL;
+	pixval maxval;
+	double dimfactor;
+	long longfactor;
+	const char * const usage = "dimfactor [ppmfile]\n        dimfactor: 0.0 = total blackness, 1.0 = original picture\n";
+
+	/* parse in 'default' parameters */
+	ppm_init(&argc, argv);
+
+	argn = 1;
+
+	/* parse in dim factor */
+	if (argn == argc)
+		pm_usage(usage);
+	if (sscanf(argv[argn], "%lf", &dimfactor) != 1)
+		pm_usage(usage);
+	if (dimfactor < 0.0 || dimfactor > 1.0)
+		pm_error("dim factor must be in the range from 0.0 to 1.0 ");
+	++argn;
+
+	/* parse in filename (if present, stdin otherwise) */
+	if (argn != argc)
+	{
+		ifp = pm_openr(argv[argn]);
+		++argn;
+	}
+	else
+		ifp = stdin;
+
+	if (argn != argc)
+		pm_usage(usage);
+
+	/* read first data from file */
+	ppm_readppminit(ifp, &cols, &rows, &maxval, &format);
+
+	/* no error checking required here, ppmlib does it all for us */
+	srcrow = ppm_allocrow(cols);
+
+	longfactor = (long)(dimfactor * 65536);
+
+	/* allocate a row of pixel data for the new pixels */
+	destrow = ppm_allocrow(cols);
+
+	ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+	/** now do the dim'ing **/
+	/* the 'float' parameter for dimming is sort of faked - in fact, we */
+	/* convert it to a range from 0 to 65536 for integer math. Shouldn't */
+	/* be something you'll have to worry about, though. */
+
+	for (i = 0; i < rows; i++)
+	{
+		ppm_readppmrow(ifp, srcrow, cols, maxval, format);
+
+		pP = srcrow;
+		pP2 = destrow;
+
+		for (j = 0; j < cols; j++)
+		{
+			PPM_ASSIGN(*pP2, (PPM_GETR(*pP) * longfactor) >> 16,
+							 (PPM_GETG(*pP) * longfactor) >> 16,
+							 (PPM_GETB(*pP) * longfactor) >> 16);
+
+			pP++;
+			pP2++;
+		}
+
+		/* write out one line of graphic data */
+		ppm_writeppmrow(stdout, destrow, cols, maxval, 0);
+	}
+
+	pm_close(ifp);
+	ppm_freerow(srcrow);
+	ppm_freerow(destrow);
+
+	exit(0);
+}
+
diff --git a/editor/ppmdist.c b/editor/ppmdist.c
new file mode 100644
index 00000000..90c2e3d3
--- /dev/null
+++ b/editor/ppmdist.c
@@ -0,0 +1,170 @@
+#include "ppm.h"
+#include "mallocvar.h"
+
+
+/*
+ * Yep, it's a very simple algorithm, but it was something I wanted to have
+ * available.
+ */
+
+struct colorToGrayEntry {
+    pixel           color;
+    gray            gray;
+    int             frequency;
+};
+
+/*
+ * BUG: This number was chosen pretty arbitrarily.  The program is * probably
+ * only useful for a very small numbers of colors - and that's * not only
+ * because of the O(n) search that's used.  The idea lends * itself primarily
+ * to low color (read: simple, machine generated) images.
+ */
+#define MAXCOLORS 255
+
+
+static gray
+newGrayValue(pixel *pix, struct colorToGrayEntry *colorToGrayMap, int colors) {
+
+    int color;
+    /*
+     * Allowing this to be O(n), since the program is intended for small
+     * n.  Later, perhaps sort by color (r, then g, then b) and bsearch.
+     */
+    for (color = 0; color < colors; color++) {
+        if (PPM_EQUAL(*pix, colorToGrayMap[color].color))
+            return colorToGrayMap[color].gray;
+    }
+    pm_error("This should never happen - contact the maintainer");
+    return (-1);
+}
+
+
+
+static int
+cmpColorToGrayEntryByIntensity(const void *entry1, const void *entry2) {
+
+    return ((struct colorToGrayEntry *) entry1)->gray -
+        ((struct colorToGrayEntry *) entry2)->gray;
+}
+
+
+
+static int
+cmpColorToGrayEntryByFrequency(const void * entry1, const void * entry2) {
+
+    return ((struct colorToGrayEntry *) entry1)->frequency -
+        ((struct colorToGrayEntry *) entry2)->frequency;
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE           *ifp;
+    int             col, cols, row, rows, color, colors, argn;
+    int             frequency;
+    pixval          maxval;
+    pixel         **pixels;
+    pixel          *pP;
+    colorhist_vector hist;
+    gray           *grayrow;
+    gray           *gP;
+    struct colorToGrayEntry *colorToGrayMap;
+
+
+    ppm_init(&argc, argv);
+
+    argn = 1;
+    /* Default is to sort colors by intensity */
+    frequency = 0;
+
+    while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0') {
+        if (pm_keymatch(argv[argn], "-frequency", 2))
+            frequency = 1;
+        else if (pm_keymatch(argv[argn], "-intensity", 2))
+            frequency = 0;
+        else
+            pm_usage( "[-frequency|-intensity] [ppmfile]" );
+        ++argn;
+    }
+
+    if (argn < argc) {
+        ifp = pm_openr(argv[argn]);
+        ++argn;
+    } else
+        ifp = stdin;
+
+    pixels = ppm_readppm(ifp, &cols, &rows, &maxval);
+    pm_close(ifp);
+    /* all done with the input file - it's entirely in memory */
+
+    /*
+     * Compute a histogram of the colors in the input.  This is good for
+     * both frequency, and indirectly the intensity, of a color.
+     */
+    hist = ppm_computecolorhist(pixels, cols, rows, MAXCOLORS, &colors);
+
+    if (hist == (colorhist_vector) 0)
+        /*
+         * BUG: This perhaps should use an exponential backoff, in
+         * the number of colors, until success - cf pnmcolormap's
+         * approach.  The results are then more what's expected, but
+         * not necessarily very useful.
+         */
+        pm_error("Too many colors - Try reducing with pnmquant");
+
+    /* copy the colors into another structure for sorting */
+    MALLOCARRAY(colorToGrayMap, colors);
+    for (color = 0; color < colors; color++) {
+        colorToGrayMap[color].color = hist[color].color;
+        colorToGrayMap[color].frequency = hist[color].value;
+        /*
+         * This next is derivable, of course, but it's far faster to
+         * store it precomputed.  This can be skipped, when sorting
+         * by frequency - but again, for a small number of colors
+         * it's a small matter.
+         */
+        colorToGrayMap[color].gray = PPM_LUMIN(hist[color].color);
+    }
+
+    /*
+     * sort by intensity - sorting by frequency (in the histogram) is
+     * worth considering as a future addition.
+     */
+    if (frequency)
+        qsort(colorToGrayMap, colors, sizeof(struct colorToGrayEntry),
+              &cmpColorToGrayEntryByFrequency);
+    else
+        qsort(colorToGrayMap, colors, sizeof(struct colorToGrayEntry),
+              &cmpColorToGrayEntryByIntensity);
+
+    /*
+     * create mapping between the n colors in input, to n evenly spaced
+     * grayscale intensities.  This is done by overwriting the neatly
+     * formed gray values corresponding to the input-colors, with a new
+     * set of evenly spaced gray values.  Since maxval can be changed on
+     * a lark, we just use gray levels 0..colors-1, and adjust maxval
+     * accordingly
+     */
+    maxval = colors - 1;
+    for (color = 0; color < colors; color++)
+        colorToGrayMap[color].gray = color;
+
+    /* write pgm file, mapping colors to intensities */
+    pgm_writepgminit(stdout, cols, rows, maxval, 0);
+
+    grayrow = pgm_allocrow(cols);
+
+    for (row = 0; row < rows; row++) {
+        for (col = 0, pP = pixels[row], gP = grayrow; col < cols;
+             col++, pP++, gP++)
+            *gP = newGrayValue(pP, colorToGrayMap, colors);
+        pgm_writepgmrow(stdout, grayrow, cols, maxval, 0);
+    }
+
+    pm_close(stdout);
+
+    exit(0);
+}
+
diff --git a/editor/ppmdither.c b/editor/ppmdither.c
new file mode 100644
index 00000000..beb45e2f
--- /dev/null
+++ b/editor/ppmdither.c
@@ -0,0 +1,309 @@
+/* ppmdither.c - Ordered dithering of a color ppm file to a specified number
+**               of primary shades.
+**
+** Copyright (C) 1991 by Christos Zoulas.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+#include "mallocvar.h"
+
+/* Besides having to have enough memory available, the limiting factor
+   in the dithering matrix power is the size of the dithering value.
+   We need 2*dith_power bits in an unsigned int.  We also reserve
+   one bit to give headroom to do calculations with these numbers.
+*/
+#define MAX_DITH_POWER ((sizeof(unsigned int)*8 - 1) / 2)
+
+typedef unsigned char ubyte;
+
+static unsigned int dith_power;     /* base 2 log of dither matrix dimension */
+static unsigned int dith_dim;      	/* dimension of the dither matrix	*/
+static unsigned int dith_dm2;      	/* dith_dim squared				*/
+static unsigned int **dith_mat; 	/* the dithering matrix			*/
+static int debug;
+
+/* COLOR():
+ *	returns the index in the colormap for the
+ *      r, g, b values specified.
+ */
+#define COLOR(r,g,b) (((r) * dith_ng + (g)) * dith_nb + (b))
+
+
+
+static unsigned int
+dither(pixval const p,
+       pixval const maxval,
+       unsigned int const d,
+       unsigned int const ditheredMaxval) {
+/*----------------------------------------------------------------------------
+  Return the dithered intensity for a component of a pixel whose real 
+  intensity for that component is 'p' based on a maxval of 'maxval'.
+  The returned intensity is based on a maxval of ditheredMaxval.
+
+  'd' is the entry in the dithering matrix for the position of this pixel
+  within the dithered square.
+-----------------------------------------------------------------------------*/
+    unsigned int const ditherSquareMaxval = ditheredMaxval * dith_dm2;
+        /* This is the maxval for an intensity that an entire dithered
+           square can represent.
+        */
+    pixval const pScaled = ditherSquareMaxval * p / maxval;
+        /* This is the input intensity P expressed with a maxval of
+           'ditherSquareMaxval'
+        */
+    
+    /* Now we scale the intensity back down to the 'ditheredMaxval', and
+       as that will involve rounding, we round up or down based on the position
+       in the dithered square, as determined by 'd'
+    */
+
+    return (pScaled + d) / dith_dm2;
+}
+
+
+/* 
+ *	Return the value of a dither matrix which is 2**dith_power elements
+ *  square at Row x, Column y.
+ *	[graphics gems, p. 714]
+ */
+static unsigned int
+dith_value(unsigned int y, unsigned int x, const unsigned int dith_power) { 
+
+    unsigned int d;
+
+    /*
+     * Think of d as the density. At every iteration, d is shifted
+     * left one and a new bit is put in the low bit based on x and y.
+     * If x is odd and y is even, or visa versa, then a bit is shifted in.
+     * This generates the checkerboard pattern seen in dithering.
+     * This quantity is shifted again and the low bit of y is added in.
+     * This whole thing interleaves a checkerboard pattern and y's bits
+     * which is what you want.
+     */
+    int i;
+    for (i = 0, d = 0; i < dith_power; i++, x >>= 1, y >>= 1)
+        d = (d << 2) | (((x & 1) ^ (y & 1)) << 1) | (y & 1);
+    return(d);
+} /* end dith_value */
+
+
+
+static unsigned int **
+dith_matrix(unsigned int const dith_dim) {
+/*----------------------------------------------------------------------------
+   Create the dithering matrix for dimension 'dith_dim'.
+
+   Return it in newly malloc'ed storage.
+
+   Note that we assume 'dith_dim' is small enough that the dith_mat_sz
+   computed within fits in an int.  Otherwise, results are undefined.
+-----------------------------------------------------------------------------*/
+    unsigned int ** dith_mat;
+    {
+        unsigned int const dith_mat_sz = 
+            (dith_dim * sizeof(int *)) + /* pointers */
+            (dith_dim * dith_dim * sizeof(int)); /* data */
+
+        dith_mat = (unsigned int **) malloc(dith_mat_sz);
+
+        if (dith_mat == NULL) 
+            pm_error("Out of memory.  "
+                     "Cannot allocate %d bytes for dithering matrix.",
+                     dith_mat_sz);
+    }
+    {
+        unsigned int * const dat = (unsigned int *) &dith_mat[dith_dim];
+        unsigned int y;
+        for (y = 0; y < dith_dim; y++)
+            dith_mat[y] = &dat[y * dith_dim];
+    }
+    {
+        unsigned int y;
+        for (y = 0; y < dith_dim; y++) {
+            unsigned int x;
+            for (x = 0; x < dith_dim; x++) {
+                dith_mat[y][x] = dith_value(y, x, dith_power);
+                if (debug)
+                    (void) fprintf(stderr, "%4d ", dith_mat[y][x]);
+            }
+            if (debug)
+                (void) fprintf(stderr, "\n");
+        }
+    }
+    return dith_mat;
+}
+
+    
+
+static void
+dith_setup(const unsigned int dith_power, 
+           const unsigned int dith_nr, 
+           const unsigned int dith_ng, 
+           const unsigned int dith_nb, 
+           const pixval output_maxval,
+           pixel ** const colormapP) {
+/*----------------------------------------------------------------------------
+   Set up the dithering parameters, color map (lookup table) and
+   dithering matrix.
+
+   Return the colormap in newly malloc'ed storage and return its address
+   as *colormapP.
+-----------------------------------------------------------------------------*/
+    unsigned int r, g, b;
+
+    if (dith_nr < 2) 
+        pm_error("too few shades for red, minimum of 2");
+    if (dith_ng < 2) 
+        pm_error("too few shades for green, minimum of 2");
+    if (dith_nb < 2) 
+        pm_error("too few shades for blue, minimum of 2");
+
+    MALLOCARRAY(*colormapP, dith_nr * dith_ng * dith_nb);
+    if (*colormapP == NULL) 
+        pm_error("Unable to allocate space for the color lookup table "
+                 "(%d by %d by %d pixels).", dith_nr, dith_ng, dith_nb);
+    
+    for (r = 0; r < dith_nr; r++) 
+        for (g = 0; g < dith_ng; g++) 
+            for (b = 0; b < dith_nb; b++) {
+                PPM_ASSIGN((*colormapP)[COLOR(r,g,b)], 
+                           (r * output_maxval / (dith_nr - 1)),
+                           (g * output_maxval / (dith_ng - 1)),
+                           (b * output_maxval / (dith_nb - 1)));
+            }
+    
+    if (dith_power > MAX_DITH_POWER) {
+        pm_error("Dithering matrix power %d is too large.  Must be <= %d",
+                 dith_power, MAX_DITH_POWER);
+    } else {
+        dith_dim = (1 << dith_power);
+        dith_dm2 = dith_dim * dith_dim;
+    }
+
+    dith_mat = dith_matrix(dith_dim);
+} /* end dith_setup */
+
+
+/* 
+ *  Dither whole image
+ */
+static void
+dith_dither(const unsigned int width, const unsigned int height, 
+            const pixval maxval,
+            const pixel * const colormap, 
+            pixel ** const input, pixel ** const output,
+            const unsigned int dith_nr,
+            const unsigned int dith_ng,
+            const unsigned int dith_nb, 
+            const pixval output_maxval
+            ) {
+
+    const unsigned int dm = (dith_dim - 1);  /* A mask */
+    unsigned int row, col; 
+
+    for (row = 0; row < height; row++)
+        for (col = 0; col < width; col++) {
+            unsigned int const d = dith_mat[row & dm][(width-col-1) & dm];
+            pixel const input_pixel = input[row][col];
+            unsigned int const dithered_r = 
+                dither(PPM_GETR(input_pixel), maxval, d, dith_nr-1);
+            unsigned int const dithered_g = 
+                dither(PPM_GETG(input_pixel), maxval, d, dith_ng-1);
+            unsigned int const dithered_b = 
+                dither(PPM_GETB(input_pixel), maxval, d, dith_nb-1);
+            output[row][col] = 
+                colormap[COLOR(dithered_r, dithered_g, dithered_b)];
+        }
+}
+
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    FILE* ifp;
+    pixel *colormap;    /* malloc'd */
+    pixel **ipixels;        /* Input image */
+    pixel **opixels;        /* Output image */
+    int cols, rows;
+    pixval maxval;  /* Maxval of the input image */
+    pixval output_maxval;  /* Maxval in the dithered output image */
+    unsigned int argn;
+    const char* const usage = 
+	"[-dim <num>] [-red <num>] [-green <num>] [-blue <num>] [ppmfile]";
+    unsigned int dith_nr; /* number of red shades in output */
+    unsigned int dith_ng; /* number of green shades	in output */
+    unsigned int dith_nb; /* number of blue shades in output */
+
+
+    ppm_init( &argc, argv );
+
+    dith_nr = 5;  /* default */
+    dith_ng = 9;  /* default */
+    dith_nb = 5;  /* default */
+
+    dith_power = 4;  /* default */
+    debug = 0; /* default */
+    argn = 1;
+
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+	{
+	if ( pm_keymatch( argv[argn], "-dim", 1) &&  argn + 1 < argc ) {
+	    argn++;
+	    if (sscanf(argv[argn], "%u", &dith_power) != 1)
+		pm_usage( usage );
+	}
+	else if ( pm_keymatch( argv[argn], "-red", 1 ) && argn + 1 < argc ) {
+	    argn++;
+	    if (sscanf(argv[argn], "%u", &dith_nr) != 1)
+		pm_usage( usage );
+	}
+	else if ( pm_keymatch( argv[argn], "-green", 1 ) && argn + 1 < argc ) {
+	    argn++;
+	    if (sscanf(argv[argn], "%u", &dith_ng) != 1)
+		pm_usage( usage );
+	}
+	else if ( pm_keymatch( argv[argn], "-blue", 1 ) && argn + 1 < argc ) {
+	    argn++;
+	    if (sscanf(argv[argn], "%u", &dith_nb) != 1)
+		pm_usage( usage );
+	}
+	else if ( pm_keymatch( argv[argn], "-debug", 6 )) {
+        debug = 1;
+	}
+	else
+	    pm_usage( usage );
+	++argn;
+	}
+
+    if ( argn != argc )
+	{
+	ifp = pm_openr( argv[argn] );
+	++argn;
+	}
+    else
+	ifp = stdin;
+
+    if ( argn != argc )
+	pm_usage( usage );
+
+    ipixels = ppm_readppm( ifp, &cols, &rows, &maxval );
+    pm_close( ifp );
+    opixels = ppm_allocarray(cols, rows);
+    output_maxval = pm_lcm(dith_nr-1, dith_ng-1, dith_nb-1, PPM_MAXMAXVAL);
+    dith_setup(dith_power, dith_nr, dith_ng, dith_nb, output_maxval, 
+               &colormap);
+    dith_dither(cols, rows, maxval, colormap, ipixels, opixels,
+                dith_nr, dith_ng, dith_nb, output_maxval);
+    ppm_writeppm(stdout, opixels, cols, rows, output_maxval, 0);
+    pm_close(stdout);
+    exit(0);
+}
diff --git a/editor/ppmdraw.c b/editor/ppmdraw.c
new file mode 100644
index 00000000..5a4be96b
--- /dev/null
+++ b/editor/ppmdraw.c
@@ -0,0 +1,885 @@
+#define _XOPEN_SOURCE    /* Make sure M_PI is in math.h */
+#define _BSD_SOURCE      /* Make sure strdup is in string.h */
+
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "ppm.h"
+#include "ppmdraw.h"
+#include "ppmdfont.h"
+
+static bool verbose;
+
+
+static double
+sindeg(double const angle) {
+
+    return sin((double)angle / 360 * 2 * M_PI);
+}
+
+
+static double
+cosdeg(double const angle) {
+
+    return cos((double)angle / 360 * 2 * M_PI);
+}
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilename;  /* '-' if stdin */
+    const char * scriptfile;     /* NULL means none. '-' means stdin */
+    const char * script;         /* NULL means none */
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine (int argc, char ** argv,
+                  struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int scriptSpec, scriptfileSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "script",      OPT_STRING,    &cmdlineP->script,
+            &scriptSpec,      0);
+    OPTENT3(0, "scriptfile",  OPT_STRING,    &cmdlineP->scriptfile,
+            &scriptfileSpec,  0);
+    OPTENT3(0, "verbose",     OPT_FLAG,      NULL,
+            &cmdlineP->verbose, 0);
+
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+    
+    if (!scriptSpec && !scriptfileSpec)
+        pm_error("You must specify either -script or -scriptfile");
+
+    if (scriptSpec && scriptfileSpec)
+        pm_error("You may not specify both -script and -scriptfile");
+
+    if (!scriptSpec)
+        cmdlineP->script = NULL;
+    if (!scriptfileSpec)
+        cmdlineP->scriptfile = NULL;
+
+    if (argc-1 < 1) {
+        if (cmdlineP->scriptfile && strcmp(cmdlineP->scriptfile, "-") == 0)
+            pm_error("You can't specify Standard Input for both the "
+                     "input image and the script file");
+        else
+            cmdlineP->inputFilename = "-";
+    }
+    else if (argc-1 == 1)
+        cmdlineP->inputFilename = argv[1];
+    else
+        pm_error("Program takes at most one argument:  input file name");
+}
+
+
+struct pos {
+    unsigned int x;
+    unsigned int y;
+};
+
+struct drawState {
+    struct pos currentPos;
+    pixel color;
+};
+
+
+
+static void
+initDrawState(struct drawState * const drawStateP,
+              pixval             const maxval) {
+
+    drawStateP->currentPos.x = 0;
+    drawStateP->currentPos.y = 0;
+    PPM_ASSIGN(drawStateP->color, maxval, maxval, maxval);
+}
+
+
+
+static void
+readScriptFile(const char *  const scriptFileName,
+               const char ** const scriptP) {
+
+    FILE * scriptFileP;
+    char * script;
+    size_t scriptAllocation;
+    size_t bytesReadSoFar;
+
+    scriptAllocation = 4096;
+
+    MALLOCARRAY(script, scriptAllocation);
+
+    if (script == NULL)
+        pm_error("out of memory reading script from file");
+
+    scriptFileP = pm_openr(scriptFileName);
+
+    bytesReadSoFar = 0;
+    while (!feof(scriptFileP)) {
+        size_t bytesRead;
+
+        if (scriptAllocation - bytesReadSoFar < 2) {
+            scriptAllocation += 4096;
+            REALLOCARRAY(script, scriptAllocation);
+            if (script == NULL)
+                pm_error("out of memory reading script from file");
+        }
+        bytesRead = fread(script + bytesReadSoFar, 1,
+                          scriptAllocation - bytesReadSoFar - 1, scriptFileP);
+        bytesReadSoFar += bytesRead;
+    }
+    pm_close(scriptFileP);
+
+    {
+        unsigned int i;
+        for (i = 0; i < bytesReadSoFar; ++i)
+            if (!isprint(script[i]) && !isspace(script[i]))
+                pm_error("Script contains byte that is not printable ASCII "
+                         "character: 0x%02x", script[i]);
+    }
+    script[bytesReadSoFar] = '\0';  /* terminating NUL */
+
+    *scriptP = script;
+}
+
+
+
+enum drawVerb {
+    VERB_SETPOS,
+    VERB_SETLINETYPE,
+    VERB_SETLINECLIP,
+    VERB_SETCOLOR,
+    VERB_SETFONT,
+    VERB_LINE,
+    VERB_LINE_HERE,
+    VERB_SPLINE3,
+    VERB_CIRCLE,
+    VERB_FILLEDRECTANGLE,
+    VERB_TEXT,
+    VERB_TEXT_HERE
+};
+
+struct setposArg {
+    int x;
+    int y;
+};
+
+struct setlinetypeArg {
+    int type;
+};
+
+struct setlineclipArg {
+    unsigned int clip;
+};
+
+struct setcolorArg {
+    const char * colorName;
+};
+
+struct setfontArg {
+    const char * fontFileName;
+};
+
+struct lineArg {
+    int x0;
+    int y0;
+    int x1;
+    int y1;
+};
+
+struct lineHereArg {
+    int right;
+    int down;
+};
+
+struct spline3Arg {
+    int x0;
+    int y0;
+    int x1;
+    int y1;
+    int x2;
+    int y2;
+};
+
+struct circleArg {
+    int cx;
+    int cy;
+    unsigned int radius;
+};
+
+struct filledrectangleArg {
+    int x;
+    int y;
+    unsigned int width;
+    unsigned int height;
+};
+
+struct textArg {
+    int xpos;
+    int ypos;
+    unsigned int height;
+    int angle;
+    const char * text;
+};
+
+
+struct drawCommand {
+    enum drawVerb verb;
+    union {
+        struct setposArg          setposArg;
+        struct setlinetypeArg     setlinetypeArg;
+        struct setlineclipArg     setlineclipArg;
+        struct setcolorArg        setcolorArg;
+        struct setfontArg         setfontArg;
+        struct lineArg            lineArg;
+        struct lineHereArg        lineHereArg;
+        struct spline3Arg         spline3Arg;
+        struct circleArg          circleArg;
+        struct filledrectangleArg filledrectangleArg;
+        struct textArg            textArg;
+    } u;
+};
+
+
+
+static void
+freeDrawCommand(const struct drawCommand * const commandP) {
+    
+    switch (commandP->verb) {
+    case VERB_SETPOS:
+        break;
+    case VERB_SETLINETYPE:
+        break;
+    case VERB_SETLINECLIP:
+        break;
+    case VERB_SETCOLOR:
+        strfree(commandP->u.setcolorArg.colorName);
+        break;
+    case VERB_SETFONT:
+        strfree(commandP->u.setfontArg.fontFileName);
+        break;
+    case VERB_LINE:
+        break;
+    case VERB_LINE_HERE:
+        break;
+    case VERB_SPLINE3:
+        break;
+    case VERB_CIRCLE:
+        break;
+    case VERB_FILLEDRECTANGLE:
+        break;
+    case VERB_TEXT:
+    case VERB_TEXT_HERE:
+        strfree(commandP->u.textArg.text);
+        break;
+    }
+    
+
+    free((void *) commandP);
+}
+
+
+
+struct commandListElt {
+    struct commandListElt * nextP;
+    const struct drawCommand * commandP;
+};
+
+
+struct script {
+    struct commandListElt * commandListHeadP;
+    struct commandListElt * commandListTailP;
+};
+
+
+
+static void
+freeScript(struct script * const scriptP) {
+
+    struct commandListElt * p;
+
+    for (p = scriptP->commandListHeadP; p; p = p->nextP) {
+        freeDrawCommand(p->commandP);
+        free(p);
+    }
+
+    free(scriptP);
+}
+
+
+
+static void
+doTextHere(pixel **                   const pixels,
+           unsigned int               const cols,
+           unsigned int               const rows,
+           pixval                     const maxval,
+           const struct drawCommand * const commandP,
+           struct drawState *         const drawStateP) {
+    
+    ppmd_text(pixels, cols, rows, maxval,
+              drawStateP->currentPos.x,
+              drawStateP->currentPos.y,
+              commandP->u.textArg.height,
+              commandP->u.textArg.angle,
+              commandP->u.textArg.text,
+              PPMD_NULLDRAWPROC,
+              &drawStateP->color);
+    
+    {
+        int left, top, right, bottom;
+        
+        ppmd_text_box(commandP->u.textArg.height, 0,
+                      commandP->u.textArg.text,
+                      &left, &top, &right, &bottom);
+        
+
+        drawStateP->currentPos.x +=
+            ROUND((right-left) * cosdeg(commandP->u.textArg.angle));
+        drawStateP->currentPos.y -=
+            ROUND((right-left) * sindeg(commandP->u.textArg.angle));
+    }
+}
+
+
+
+static void
+executeScript(struct script * const scriptP,
+              pixel **        const pixels,
+              unsigned int    const cols,
+              unsigned int    const rows,
+              pixval          const maxval) {
+
+    struct drawState drawState;
+    unsigned int seq;
+        /* Sequence number of current command (0 = first, etc.) */
+    struct commandListElt * p;
+        /* Pointer to current element in command list */
+
+    initDrawState(&drawState, maxval);
+
+    for (p = scriptP->commandListHeadP, seq = 0; p; p = p->nextP, ++seq) {
+        const struct drawCommand * const commandP = p->commandP;
+
+        if (verbose)
+            pm_message("Command %u: %u", seq, commandP->verb);
+
+        switch (commandP->verb) {
+        case VERB_SETPOS:
+            drawState.currentPos.x = commandP->u.setposArg.x;
+            drawState.currentPos.y = commandP->u.setposArg.y;
+            break;
+        case VERB_SETLINETYPE:
+            ppmd_setlinetype(commandP->u.setlinetypeArg.type);
+            break;
+        case VERB_SETLINECLIP:
+            ppmd_setlineclip(commandP->u.setlineclipArg.clip);
+            break;
+        case VERB_SETCOLOR:
+            drawState.color =
+                ppm_parsecolor2(commandP->u.setcolorArg.colorName,
+                                maxval, TRUE);
+            break;
+        case VERB_SETFONT: {
+            FILE * ifP;
+            const struct ppmd_font * fontP;
+            ifP = pm_openr(commandP->u.setfontArg.fontFileName);
+            ppmd_read_font(ifP, &fontP);
+            ppmd_set_font(fontP);
+            pm_close(ifP);
+        } break;
+        case VERB_LINE:
+            ppmd_line(pixels, cols, rows, maxval,
+                      commandP->u.lineArg.x0, commandP->u.lineArg.y0,
+                      commandP->u.lineArg.x1, commandP->u.lineArg.y1,
+                      PPMD_NULLDRAWPROC,
+                      &drawState.color);
+            break;
+        case VERB_LINE_HERE: {
+            struct pos endPos;
+
+            endPos.x = drawState.currentPos.x + commandP->u.lineHereArg.right;
+            endPos.y = drawState.currentPos.y + commandP->u.lineHereArg.down;
+
+            ppmd_line(pixels, cols, rows, maxval,
+                      drawState.currentPos.x, drawState.currentPos.y,
+                      endPos.x, endPos.y,
+                      PPMD_NULLDRAWPROC,
+                      &drawState.color);
+            drawState.currentPos = endPos;
+        } break;
+        case VERB_SPLINE3:
+            ppmd_spline3(pixels, cols, rows, maxval,
+                         commandP->u.spline3Arg.x0,
+                         commandP->u.spline3Arg.y0,
+                         commandP->u.spline3Arg.x1,
+                         commandP->u.spline3Arg.y1,
+                         commandP->u.spline3Arg.x2,
+                         commandP->u.spline3Arg.y2,
+                         PPMD_NULLDRAWPROC,
+                         &drawState.color);
+            break;
+        case VERB_CIRCLE:
+            ppmd_circle(pixels, cols, rows, maxval,
+                        commandP->u.circleArg.cx,
+                        commandP->u.circleArg.cy,
+                        commandP->u.circleArg.radius,
+                        PPMD_NULLDRAWPROC,
+                        &drawState.color);
+            break;
+        case VERB_FILLEDRECTANGLE:
+            ppmd_filledrectangle(pixels, cols, rows, maxval,
+                                 commandP->u.filledrectangleArg.x,
+                                 commandP->u.filledrectangleArg.y,
+                                 commandP->u.filledrectangleArg.width,
+                                 commandP->u.filledrectangleArg.height,
+                                 PPMD_NULLDRAWPROC,
+                                 &drawState.color);
+            break;
+        case VERB_TEXT:
+            ppmd_text(pixels, cols, rows, maxval,
+                      commandP->u.textArg.xpos,
+                      commandP->u.textArg.ypos,
+                      commandP->u.textArg.height,
+                      commandP->u.textArg.angle,
+                      commandP->u.textArg.text,
+                      PPMD_NULLDRAWPROC,
+                      &drawState.color);
+            break;
+        case VERB_TEXT_HERE:
+            doTextHere(pixels, cols, rows, maxval, commandP, &drawState);
+            break;
+        }
+    }
+}
+
+
+
+struct tokenSet {
+    
+    const char * token[10];
+    unsigned int count;
+    
+};
+
+
+
+static void
+parseDrawCommand(struct tokenSet             const commandTokens,
+                 const struct drawCommand ** const drawCommandPP) {
+
+    struct drawCommand * drawCommandP;
+
+    if (commandTokens.count < 1)
+        pm_error("No tokens in command.");
+    else {
+        const char * const verb = commandTokens.token[0];
+
+        MALLOCVAR(drawCommandP);
+        if (drawCommandP == NULL)
+            pm_error("Out of memory to parse '%s' command", verb);
+
+        if (STREQ(verb, "setpos")) {
+            drawCommandP->verb = VERB_SETPOS;
+            if (commandTokens.count < 3)
+                pm_error("Not enough tokens for a 'setpos' command.  "
+                         "Need %u.  Got %u", 3, commandTokens.count);
+            else {
+                drawCommandP->u.setposArg.x = atoi(commandTokens.token[1]);
+                drawCommandP->u.setposArg.y = atoi(commandTokens.token[2]);
+            }
+        } else if (STREQ(verb, "setlinetype")) {
+            drawCommandP->verb = VERB_SETLINETYPE;
+            if (commandTokens.count < 2)
+                pm_error("Not enough tokens for a 'setlinetype' command.  "
+                         "Need %u.  Got %u", 2, commandTokens.count);
+            else {
+                const char * const typeArg = commandTokens.token[1];
+                if (STREQ(typeArg, "normal"))
+                    drawCommandP->u.setlinetypeArg.type = PPMD_LINETYPE_NORMAL;
+                else if (STREQ(typeArg, "normal"))
+                    drawCommandP->u.setlinetypeArg.type = 
+                        PPMD_LINETYPE_NODIAGS;
+                else
+                    pm_error("Invalid type");
+            }
+        } else if (STREQ(verb, "setlineclip")) {
+            drawCommandP->verb = VERB_SETLINECLIP;
+            if (commandTokens.count < 2)
+                pm_error("Not enough tokens for a 'setlineclip' command.  "
+                         "Need %u.  Got %u", 2, commandTokens.count);
+            else
+                drawCommandP->u.setlineclipArg.clip =
+                    atoi(commandTokens.token[1]);
+        } else if (STREQ(verb, "setcolor")) {
+            drawCommandP->verb = VERB_SETCOLOR;
+            if (commandTokens.count < 2)
+                pm_error("Not enough tokens for a 'setcolor' command.  "
+                         "Need %u.  Got %u", 2, commandTokens.count);
+            else
+                drawCommandP->u.setcolorArg.colorName =
+                    strdup(commandTokens.token[1]);
+        } else if (STREQ(verb, "setfont")) {
+            drawCommandP->verb = VERB_SETFONT;
+            if (commandTokens.count < 2)
+                pm_error("Not enough tokens for a 'setfont' command.  "
+                         "Need %u.  Got %u", 2, commandTokens.count);
+            else
+                drawCommandP->u.setfontArg.fontFileName =
+                    strdup(commandTokens.token[1]);
+        } else if (STREQ(verb, "line")) {
+            drawCommandP->verb = VERB_LINE;
+            if (commandTokens.count < 5)
+                pm_error("Not enough tokens for a 'line' command.  "
+                         "Need %u.  Got %u", 5, commandTokens.count);
+            else {
+                drawCommandP->u.lineArg.x0 = atoi(commandTokens.token[1]);
+                drawCommandP->u.lineArg.y0 = atoi(commandTokens.token[2]);
+                drawCommandP->u.lineArg.x1 = atoi(commandTokens.token[3]);
+                drawCommandP->u.lineArg.y1 = atoi(commandTokens.token[4]);
+            } 
+        } else if (STREQ(verb, "line_here")) {
+            drawCommandP->verb = VERB_LINE_HERE;
+            if (commandTokens.count < 3)
+                pm_error("Not enough tokens for a 'line_here' command.  "
+                         "Need %u.  Got %u", 3, commandTokens.count);
+            else {
+                struct lineHereArg * const argP =
+                    &drawCommandP->u.lineHereArg;
+                argP->right = atoi(commandTokens.token[1]);
+                argP->down = atoi(commandTokens.token[2]);
+            } 
+       } else if (STREQ(verb, "spline3")) {
+            drawCommandP->verb = VERB_SPLINE3;
+            if (commandTokens.count < 7)
+                pm_error("Not enough tokens for a 'spline3' command.  "
+                         "Need %u.  Got %u", 7, commandTokens.count);
+            else {
+                struct spline3Arg * const argP =
+                    &drawCommandP->u.spline3Arg;
+                argP->x0 = atoi(commandTokens.token[1]);
+                argP->y0 = atoi(commandTokens.token[2]);
+                argP->x1 = atoi(commandTokens.token[3]);
+                argP->y1 = atoi(commandTokens.token[4]);
+                argP->x2 = atoi(commandTokens.token[5]);
+                argP->y2 = atoi(commandTokens.token[6]);
+            } 
+        } else if (STREQ(verb, "circle")) {
+            drawCommandP->verb = VERB_CIRCLE;
+            if (commandTokens.count < 4)
+                pm_error("Not enough tokens for a 'circle' command.  "
+                         "Need %u.  Got %u", 4, commandTokens.count);
+            else {
+                struct circleArg * const argP = &drawCommandP->u.circleArg;
+                argP->cx     = atoi(commandTokens.token[1]);
+                argP->cy     = atoi(commandTokens.token[2]);
+                argP->radius = atoi(commandTokens.token[3]);
+            } 
+        } else if (STREQ(verb, "filledrectangle")) {
+            drawCommandP->verb = VERB_FILLEDRECTANGLE;
+            if (commandTokens.count < 5)
+                pm_error("Not enough tokens for a 'filledrectangle' command.  "
+                         "Need %u.  Got %u", 4, commandTokens.count);
+            else {
+                struct filledrectangleArg * const argP =
+                    &drawCommandP->u.filledrectangleArg;
+                argP->x      = atoi(commandTokens.token[1]);
+                argP->y      = atoi(commandTokens.token[2]);
+                argP->width  = atoi(commandTokens.token[3]);
+                argP->height = atoi(commandTokens.token[4]);
+            } 
+        } else if (STREQ(verb, "text")) {
+            drawCommandP->verb = VERB_TEXT;
+            if (commandTokens.count < 6)
+                pm_error("Not enough tokens for a 'text' command.  "
+                         "Need %u.  Got %u", 6, commandTokens.count);
+            else {
+                drawCommandP->u.textArg.xpos  = atoi(commandTokens.token[1]);
+                drawCommandP->u.textArg.ypos  = atoi(commandTokens.token[2]);
+                drawCommandP->u.textArg.height= atoi(commandTokens.token[3]);
+                drawCommandP->u.textArg.angle = atoi(commandTokens.token[4]);
+                drawCommandP->u.textArg.text  = strdup(commandTokens.token[5]);
+                if (drawCommandP->u.textArg.text == NULL)
+                    pm_error("Out of storage parsing 'text' command");
+            }
+        } else if (STREQ(verb, "text_here")) {
+            drawCommandP->verb = VERB_TEXT_HERE;
+            if (commandTokens.count < 4)
+                pm_error("Not enough tokens for a 'text_here' command.  "
+                         "Need %u.  Got %u", 4, commandTokens.count);
+            else {
+                drawCommandP->u.textArg.height= atoi(commandTokens.token[1]);
+                drawCommandP->u.textArg.angle = atoi(commandTokens.token[2]);
+                drawCommandP->u.textArg.text  = strdup(commandTokens.token[3]);
+                if (drawCommandP->u.textArg.text == NULL)
+                    pm_error("Out of storage parsing 'text_here' command");
+            }
+        } else
+            pm_error("Unrecognized verb '%s'", verb);
+    }
+    *drawCommandPP = drawCommandP;
+}
+
+
+
+static void
+disposeOfCommandTokens(struct tokenSet * const tokenSetP,
+                       struct script *   const scriptP) {
+
+    /* We've got a whole command in 'tokenSet'.  Parse it into *scriptP
+       and reset tokenSet to empty.
+    */
+    
+    struct commandListElt * commandListEltP;
+    
+    MALLOCVAR(commandListEltP);
+    if (commandListEltP == NULL)
+        pm_error("Out of memory allocating command list element frame");
+
+    parseDrawCommand(*tokenSetP, &commandListEltP->commandP);
+
+    {
+        unsigned int i;
+        for (i = 0; i < tokenSetP->count; ++i)
+            strfree(tokenSetP->token[i]);
+        tokenSetP->count = 0;
+    }
+    /* Put the list element for this command at the tail of the list */
+    commandListEltP->nextP = NULL;
+    if (scriptP->commandListTailP)
+        scriptP->commandListTailP->nextP = commandListEltP;
+    else
+        scriptP->commandListHeadP = commandListEltP;
+
+    scriptP->commandListTailP = commandListEltP;
+}
+
+
+
+static void
+processToken(const char *      const scriptText,
+             unsigned int      const cursor,
+             unsigned int      const tokenStart,
+             struct script *   const scriptP,
+             struct tokenSet * const tokenSetP) {
+
+    char * token;
+    unsigned int const tokenLength = cursor - tokenStart;
+    MALLOCARRAY_NOFAIL(token, tokenLength + 1);
+    memcpy(token, &scriptText[tokenStart], tokenLength);
+    token[tokenLength] = '\0';
+    
+    if (STREQ(token, ";")) {
+        disposeOfCommandTokens(tokenSetP, scriptP);
+        free(token);
+    } else {
+        if (tokenSetP->count >= ARRAY_SIZE(tokenSetP->token))
+            pm_error("too many tokens");
+        else
+            tokenSetP->token[tokenSetP->count++] = token;
+    }
+}
+
+
+
+static void
+parseScript(const char *     const scriptText,
+            struct script ** const scriptPP) {
+
+    struct script * scriptP;
+    unsigned int cursor;  /* cursor in scriptText[] */
+    bool intoken;      /* Cursor is inside token */
+    unsigned int tokenStart;
+        /* Position in 'scriptText' where current token starts.
+           Meaningless if 'intoken' is false.
+        */
+    bool quotedToken;
+        /* Current token is a quoted string.  Meaningless if 'intoken'
+           is false 
+        */
+    struct tokenSet tokenSet;
+
+    MALLOCVAR_NOFAIL(scriptP);
+
+    scriptP->commandListHeadP = NULL;
+    scriptP->commandListTailP = NULL;
+
+    /* A token begins with a non-whitespace character.  A token ends before
+       a whitespace character or semicolon or end of script, except that if
+       the token starts with a double quote, whitespace and semicolon don't
+       end it and another double quote does.
+
+       Semicolon (unquoted) is a token by itself.
+    */
+
+    tokenSet.count = 0;
+    intoken = FALSE;
+    tokenStart = 0;
+    cursor = 0;
+
+    while (scriptText[cursor] != '\0') {
+        char const scriptChar = scriptText[cursor];
+        
+        if (intoken) {
+            if ((quotedToken && scriptChar == '"') ||
+                (!quotedToken && (isspace(scriptChar) || scriptChar == ';'))) {
+                /* We've passed a token. */
+
+                processToken(scriptText, cursor, tokenStart, scriptP,
+                             &tokenSet);
+
+                intoken = FALSE;
+                if (scriptChar != ';')
+                    ++cursor;
+            } else
+                ++cursor;
+        } else {
+            if (!isspace(scriptChar)) {
+                /* A token starts here */
+
+                if (scriptChar == ';')
+                    /* It ends here too -- semicolon is token by itself */
+                    processToken(scriptText, cursor+1, cursor, scriptP,
+                                 &tokenSet);
+                else {
+                    intoken = TRUE;
+                    quotedToken = (scriptChar == '"');
+                    if (quotedToken)
+                        tokenStart = cursor + 1;
+                    else
+                        tokenStart = cursor;
+                }
+            }
+            ++cursor;
+        }            
+    }
+
+    if (intoken) {
+        /* Parse the last token, which was terminated by end of string */
+        if (quotedToken)
+            pm_error("Script ends in the middle of a quoted string");
+        processToken(scriptText, cursor, tokenStart, scriptP, &tokenSet);
+    }
+
+    if (tokenSet.count > 0) {
+        /* Parse the last command, which was not terminated with a semicolon.
+         */
+        disposeOfCommandTokens(&tokenSet, scriptP);
+    }
+
+    *scriptPP = scriptP;
+}
+
+
+
+static void
+getScript(struct cmdlineInfo const cmdline,
+          struct script **   const scriptPP) {
+
+    const char * scriptText;
+
+    if (cmdline.script) {
+        scriptText = strdup(cmdline.script);
+        if (scriptText == NULL)
+            pm_error("Out of memory creating script");
+    } else if (cmdline.scriptfile)
+        readScriptFile(cmdline.scriptfile, &scriptText);
+    else
+        pm_error("INTERNAL ERROR: no script");
+
+    if (verbose)
+        pm_message("Executing script '%s'", scriptText);
+
+    parseScript(scriptText, scriptPP);
+
+    strfree(scriptText);
+}
+
+          
+
+static void
+doOneImage(FILE *          const ifP,
+           struct script * const scriptP)  {
+
+    pixel ** pixels;
+    pixval maxval;
+    int rows, cols;
+    
+    pixels = ppm_readppm(ifP, &cols, &rows, &maxval);
+    
+    executeScript(scriptP, pixels, cols, rows, maxval);
+    
+    ppm_writeppm(stdout, pixels, cols, rows, maxval, 0);
+    
+    ppm_freearray(pixels, rows);
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    struct script * scriptP;
+    bool eof;
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    verbose = cmdline.verbose;
+
+    ifP = pm_openr(cmdline.inputFilename);
+
+    getScript(cmdline, &scriptP);
+
+    eof = FALSE;
+    while (!eof) {
+        doOneImage(ifP, scriptP);
+        ppm_nextimage(ifP, &eof);
+    }
+
+    freeScript(scriptP);
+
+    pm_close(ifP);
+
+    /* If the program failed, it previously aborted with nonzero completion
+       code, via various function calls.
+    */
+    return 0;
+}
diff --git a/editor/ppmfade b/editor/ppmfade
new file mode 100755
index 00000000..2507eaf2
--- /dev/null
+++ b/editor/ppmfade
@@ -0,0 +1,309 @@
+#!/usr/bin/perl -w
+#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+#
+#  This program creates a fade (a sequence of frames) between two images.
+#
+#  By Bryan Henderson, Olympia WA; March 2000
+#
+#  Contributed to the public domain by its author.
+#
+#  Inspired by the program Pbmfade by Wesley C. Barris of AHPCRC,
+#  Minnesota Supercomputer Center, Inc. January 7, 1994.  Pbmfade does
+#  much the same thing, but handles non-Netpbm formats too, and is 
+#  implemented in a more primitive language.
+#
+#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+use strict;
+
+my $SPREAD =  1;
+my $SHIFT =   2;
+my $RELIEF =  3;
+my $OIL =     4;
+my $EDGE =    5;
+my $BENTLEY = 6;
+my $BLOCK =   7;
+my $MIX =     8;
+#
+#  Set some defaults.
+#
+my $nframes = 30;			# total number of files created (1 sec)
+my $first_file = "undefined";
+my $last_file = "undefined";
+my $base_name = "fade";		# default base name of output files
+my $image = "ppm";		# default output storage format
+my $mode = $SPREAD;		# default fading mode
+#
+#  Check those command line args.
+#
+if (@ARGV == 0) {
+    usage();
+}
+
+my $n;  # argument number
+
+for ($n = 0; $n < @ARGV; $n++) {
+    if ("$ARGV[$n]" eq "-f") {
+        $n++;
+        $first_file = $ARGV[$n];
+        if (-e $first_file) {
+        } else {
+            print "I can't find $first_file\n";
+            exit 20;
+        }
+    } elsif ($ARGV[$n] eq "-l") {
+        $n++;
+        $last_file = $ARGV[$n];
+        if (-e $last_file) {
+        } else {
+            print "I can't find $last_file\n";
+            exit 20;
+        }
+    } elsif ($ARGV[$n] eq "-base") {
+        $n++;
+        $base_name = $ARGV[$n];
+    } elsif ($ARGV[$n] eq "-spread") {
+        $mode = $SPREAD;
+    } elsif ($ARGV[$n] eq "-shift") {
+        $mode = $SHIFT;
+    } elsif ($ARGV[$n] eq "-relief") {
+        $mode = $RELIEF;
+    } elsif ($ARGV[$n] eq "-oil") {
+        $mode = $OIL;
+    } elsif ("$ARGV[$n]" eq "-edge") {
+        $mode = $EDGE;
+    } elsif ("$ARGV[$n]" eq "-bentley") {
+        $mode = $BENTLEY;
+    } elsif ("$ARGV[$n]" eq "-block") {
+        $mode = $BLOCK;
+    } elsif ("$ARGV[$n]" eq "-mix") {
+        $mode = $MIX;
+    } elsif ($ARGV[$n] eq "-help" || $ARGV[$n] eq "-h") {
+        usage();
+    } else {
+        print "Unknown argument: $ARGV[$n]\n";
+        exit 100;
+    } 
+}
+#
+#  Define a couple linear ramps.
+#
+# We don't use element 0 of these arrays.
+my @spline10 = (0, 0, 0.11, 0.22, 0.33, 0.44, 0.55, 0.66, 0.77, 0.88, 1.0);
+my @spline20 = (0, 0, 0.05, 0.11, 0.16, 0.21, 0.26, 0.32, 0.37, 0.42, 0.47, 
+                0.53, 0.58, 0.63, 0.69, 0.74, 0.79, 0.84, 0.89, 0.95, 1.0);
+#
+#  Just what are we supposed to do?
+#
+my ($height, $width);    # width and height of our frames
+if ($first_file ne "undefined") {
+    if ((`pnmfile $first_file` =~ m{\b(\d+)\sby\s(\d+)} )) { 
+        $width = $1; $height = $2;
+    } else {
+        print("Unrecognized results from pnmfile on $first_file.\n");
+        exit(50);
+    }
+} elsif ($last_file ne "undefined") {
+    if ((`pnmfile $last_file` =~ m{\b(\d+)\sby\s(\d+)} )) { 
+        $width = $1; $height = $2;
+    } else {
+        print("Unrecognized results from pnmfile on $first_file.\n");
+        exit(50);
+    }
+} else {
+    print("ppmfade:  You must specify -f or -l (or both)\n");
+    exit(90);
+}
+
+print("Frames are " . $width . "W x " . $height . "H\n");
+
+if ($first_file eq "undefined") {
+    print "Fading from black to ";
+    system("ppmmake \\#000 $width $height >junk1$$.ppm");
+} else {
+    print "Fading from $first_file to ";
+    system("cp", $first_file, "junk1$$.ppm");
+}
+
+if ($last_file eq "undefined") {
+    print "black.\n";
+    system("ppmmake \\#000 $width $height >junk2$$.ppm");
+} else {
+    print "$last_file\n";
+    system("cp", $last_file, "junk2$$.ppm");
+}
+
+#
+#  Perform the fade.
+#
+
+# Here's what our temporary files are:
+#   junk1$$.ppm: The original (fade-from) image
+#   junk2$$.ppm: The target (fade-from) image
+#   junk3$$.ppm: The frame of the fade for the current iteration of the 
+#                the for loop.
+#   junk1a$$.ppm: If the fade involves a ppmmix sequence from one intermediate
+#                 image to another, this is the first frame of that 
+#                 sequence.
+#   junk2a$$.ppm: This is the last frame of the above-mentioned ppmmix sequence
+
+my $i;    # Frame number
+for ($i = 1; $i <= $nframes; $i++) {
+    print("Creating $i of $nframes...\n");
+    if ($mode eq $SPREAD) {
+        if ($i <= 10) {
+            my $n = $spline20[$i] * 100;
+            system("ppmspread $n junk1$$.ppm >junk3$$.ppm");
+        } elsif ($i <= 20) {
+            my $n;
+            $n = $spline20[$i] * 100;
+            system("ppmspread $n junk1$$.ppm >junk1a$$.ppm");
+            $n = (1-$spline20[$i-10]) * 100;
+            system("ppmspread $n junk2$$.ppm >junk2a$$.ppm");
+            $n = $spline10[$i-10];
+            system("ppmmix $n junk1a$$.ppm junk2a$$.ppm >junk3$$.ppm");
+        } else {
+            my $n = (1-$spline20[$i-10])*100;
+            system("ppmspread $n junk2$$.ppm >junk3$$.ppm");
+        }
+    } elsif ($mode eq $SHIFT) {
+        if ($i <= 10) {
+            my $n = $spline20[$i] * 100;
+            system("ppmshift $n junk1$$.ppm >junk3$$.ppm");
+        } elsif ($i <= 20) {
+            my $n;
+            $n = $spline20[$i] * 100;
+            system("ppmshift $n junk1$$.ppm >junk1a$$.ppm");
+            $n = (1-$spline20[$i-10])*100;
+            system("ppmshift $n junk2$$.ppm >junk2a$$.ppm");
+            $n = $spline10[$i-10];
+            system("ppmmix $n junk1a$$.ppm junk2a$$.ppm >junk3$$.ppm");
+        } else {
+            my $n = (1-$spline20[$i-10]) * 100;
+            system("ppmshift $n junk2$$.ppm >junk3$$.ppm");
+        }
+    } elsif ($mode eq $RELIEF) {
+        if ($i == 1) {
+            system("ppmrelief junk1$$.ppm >junk1r$$.ppm");
+        }
+        if ($i <= 10) {
+            my $n = $spline10[$i];
+            system("ppmmix $n junk1$$.ppm junk1r$$.ppm >junk3$$.ppm");
+        } elsif ($i <= 20) {
+            my $n = $spline10[$i-10];
+            system("ppmmix $n junk1r$$.ppm junk2r$$.ppm >junk3$$.ppm");
+        } else {
+            my $n = $spline10[$i-20];
+            system("ppmmix $n junk2r$$.ppm junk2$$.ppm >junk3$$.ppm");
+        }
+        if ($i == 10) {
+            system("ppmrelief junk2$$.ppm >junk2r$$.ppm");
+        }
+    } elsif ($mode eq $OIL) {
+        if ($i == 1) {
+            system("ppmtopgm junk1$$.ppm | pgmoil >junko$$.ppm");
+            system("rgb3toppm junko$$.ppm junko$$.ppm junko$$.ppm " .
+                   ">junk1o$$.ppm");
+        }
+        if ($i <= 10) {
+            my $n = $spline10[$i];
+            system("ppmmix $n junk1$$.ppm junk1o$$.ppm >junk3$$.ppm");
+        } elsif ($i <= 20) {
+            my $n = $spline10[$i-10];
+            system("ppmmix $n junk1o$$.ppm junk2o$$.ppm >junk3$$.ppm");
+        } else {
+            my $n = $spline10[$i-20];
+            system("ppmmix $n junk2o$$.ppm junk2$$.ppm >junk3$$.ppm");
+        }
+        if ($i == 10) {
+            system("ppmtopgm junk2$$.ppm | pgmoil >junko$$.ppm");
+            system("rgb3toppm junko$$.ppm junko$$.ppm junko$$.ppm " .
+                   ">junk2o$$.ppm");
+        }
+    } elsif ($mode eq $EDGE) {
+        if ($i == 1) {
+            system("ppmtopgm junk1$$.ppm | pgmedge >junko$$.ppm");
+            system("rgb3toppm junko$$.ppm junko$$.ppm junko$$.ppm " .
+                   ">junk1o$$.ppm");
+        }
+        if ($i <= 10) {
+            my $n = $spline10[$i];
+            system("ppmmix $n junk1$$.ppm junk1o$$.ppm >junk3$$.ppm");
+        } elsif ($i <= 20) {
+            my $n = $spline10[$i-10];
+            system("ppmmix $n junk1o$$.ppm junk2o$$.ppm >junk3$$.ppm");
+        } else {
+            my $n = $spline10[$i-20];
+            system("ppmmix $n junk2o$$.ppm junk2$$.ppm >junk3$$.ppm");
+        }
+        if ($i == 10) {
+            system("ppmtopgm junk2$$.ppm | pgmedge >junko$$.ppm");
+            system("rgb3toppm junko$$.ppm junko$$.ppm junko$$.ppm " .
+                   ">junk2o$$.ppm");
+        } 
+    } elsif ($mode eq $BENTLEY) {
+        if ($i == 1) {
+            system("ppmtopgm junk1$$.ppm | pgmbentley >junko$$.ppm");
+            system("rgb3toppm junko$$.ppm junko$$.ppm junko$$.ppm " .
+                   ">junk1o$$.ppm");
+        }
+        if ($i <= 10) {
+            my $n = $spline10[$i];
+            system("ppmmix $n junk1$$.ppm junk1o$$.ppm >junk3$$.ppm");
+        } elsif ($i <= 20) {
+            my $n = $spline10[$i-10];
+            system("ppmmix $n junk1o$$.ppm junk2o$$.ppm >junk3$$.ppm");
+        } else {
+            my $n = $spline10[$i-20];
+            system("ppmmix $n junk2o$$.ppm junk2$$.ppm >junk3$$.ppm");
+        }
+        if ($i == 10) {
+            system("ppmtopgm junk2$$.ppm | pgmbentley >junko$$.ppm");
+            system("rgb3toppm junko$$.ppm junko$$.ppm junko$$.ppm " .
+                   ">junk2o$$.ppm");
+        }
+    } elsif ($mode eq $BLOCK) {
+        if ($i <= 10) {
+            my $n = 1 - 1.9*$spline20[$i];
+            system("pamscale $n junk1$$.ppm | " .
+                   "pamscale -width $width -height $height >junk3$$.ppm");
+        } elsif ($i <= 20) {
+            my $n = $spline10[$i-10];
+            system("ppmmix $n junk1a$$.ppm junk2a$$.ppm >junk3$$.ppm");
+        } else {
+            my $n = 1 - 1.9*$spline20[31-$i];
+            system("pamscale $n junk2$$.ppm | " .
+                   "pamscale -width $width -height $height >junk3$$.ppm");
+        }
+        if ($i == 10) {
+            system("cp", "junk3$$.ppm", "junk1a$$.ppm");
+            system("pamscale $n junk2$$.ppm | " .
+                   "pamscale -width $width -height $height >junk2a$$.ppm");
+        }    
+    } elsif ($mode eq $MIX) {
+        my $fade_factor = sqrt(1/($nframes-$i+1));
+        system("ppmmix $fade_factor junk1$$.ppm junk2$$.ppm >junk3$$.ppm");
+    } else {
+        print("Internal error: impossible mode value '$mode'\n");
+    }
+
+    my $outfile = sprintf("%s.%04d.ppm", $base_name, $i);
+    system("cp", "junk3$$.ppm", $outfile);
+}
+
+#
+#  Clean up shop.
+#
+system("rm junk*$$.ppm");
+
+exit(0);
+
+
+
+sub usage() {
+   print "Usage: ppmfade [-f first_file] [-l last_file]\n";
+   print "               [-spread|-relief|-oil|-edge|-bentley|-block]\n";
+   print "               [-base basename]\n";
+   print "Notes: Default base: fade\n";
+   print "       The resulting image files will be named fade.NNNN.ppm.\n";
+   exit(100);
+}
diff --git a/editor/ppmflash.c b/editor/ppmflash.c
new file mode 100644
index 00000000..d1d048df
--- /dev/null
+++ b/editor/ppmflash.c
@@ -0,0 +1,114 @@
+
+/*********************************************************************/
+/* ppmflash -  brighten a picture up to total whiteout               */
+/* Frank Neumann, August 1993                                        */
+/* V1.4 16.11.1993                                                   */
+/*                                                                   */
+/* version history:                                                  */
+/* V1.0 ~ 15.August 1993    first version                            */
+/* V1.1 03.09.1993          uses ppm libs & header files             */
+/* V1.2 03.09.1993          integer arithmetics instead of float     */
+/*                          (gains about 50 % speed up)              */
+/* V1.3 11.10.1993          reads only one line at a time - this     */
+/*                          saves LOTS of memory on big picturs      */
+/* V1.4 16.11.1993          Rewritten to be NetPBM.programming con-  */
+/*                          forming                                  */
+/*********************************************************************/
+
+#include "ppm.h"
+
+/* global variables */
+#ifdef AMIGA
+static char *version = "$VER: ppmflash 1.4 (16.11.93)";
+#endif
+
+/**************************/
+/* start of main function */
+/**************************/
+int main(argc, argv)
+    int argc;
+    char *argv[];
+{
+    FILE* ifp;
+    int argn, rows, cols, i, j, format;
+    pixel *srcrow, *destrow;
+    pixel *pP, *pP2;
+    pixval maxval;
+    double flashfactor;
+    long longfactor;
+    const char* const usage = "flashfactor [ppmfile]\n        flashfactor: 0.0 = original picture, 1.0 = total whiteout\n";
+
+    /* parse in 'default' parameters */
+    ppm_init( &argc, argv );
+
+    argn = 1;
+
+    /* parse in flash factor */
+    if (argn == argc)
+        pm_usage(usage);
+    if (sscanf(argv[argn], "%lf", &flashfactor) != 1)
+        pm_usage(usage);
+    if (flashfactor < 0.0 || flashfactor > 1.0)
+        pm_error("flash factor must be in the range from 0.0 to 1.0 ");
+    ++argn;
+
+    /* parse in filename (if present, stdin otherwise) */
+    if (argn != argc)
+    {
+        ifp = pm_openr(argv[argn]);
+        ++argn;
+    }
+    else
+        ifp = stdin;
+
+    if (argn != argc)
+        pm_usage(usage);
+
+    /* read first data from file */
+    ppm_readppminit(ifp, &cols, &rows, &maxval, &format);
+
+    /* no error checking required here, ppmlib does it all for us */
+    srcrow = ppm_allocrow(cols);
+
+    longfactor = (long)(flashfactor * 65536);
+
+    /* allocate a row of pixel data for the new pixels */
+    destrow = ppm_allocrow(cols);
+
+    ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+    /** now do the flashing **/
+    /* the 'float' parameter for flashing is sort of faked - in fact, we */
+    /* convert it to a range from 0 to 65536 for integer math. Shouldn't */
+    /* be something you'll have to worry about, though. */
+
+    for (i = 0; i < rows; i++) {
+        ppm_readppmrow(ifp, srcrow, cols, maxval, format);
+
+        pP = srcrow;
+        pP2 = destrow;
+
+        for (j = 0; j < cols; j++) {
+            PPM_ASSIGN(*pP2, 
+                       PPM_GETR(*pP) + 
+                       (((maxval - PPM_GETR(*pP)) * longfactor) >> 16),
+                       PPM_GETG(*pP) + 
+                       (((maxval - PPM_GETG(*pP)) * longfactor) >> 16),
+                       PPM_GETB(*pP) + 
+                       (((maxval - PPM_GETB(*pP)) * longfactor) >> 16));
+
+            pP++;
+            pP2++;
+        }
+
+        /* write out one line of graphic data */
+        ppm_writeppmrow(stdout, destrow, cols, maxval, 0);
+    }
+
+    pm_close(ifp);
+    ppm_freerow(srcrow);
+    ppm_freerow(destrow);
+
+    exit(0);
+}
+
diff --git a/editor/ppmglobe.c b/editor/ppmglobe.c
new file mode 100644
index 00000000..ee1a57c3
--- /dev/null
+++ b/editor/ppmglobe.c
@@ -0,0 +1,172 @@
+/*
+ * This code written 2003
+ * by Max Gensthaler <Max@Gensthaler.de>
+ * Distributed under the Gnu Public License (GPL)
+ *
+ * Gensthaler called it 'ppmglobemap'.
+ *
+ * Translations of comments and C dialect by Bryan Henderson May 2003.
+ */
+
+
+#define _XOPEN_SOURCE  /* get M_PI in math.h */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <math.h>
+
+#include "ppm.h"
+#include "colorname.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  /* Filename of input files */
+    unsigned int stripcount;
+    const char * background;
+    unsigned int closeok;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int backgroundSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "background",     OPT_STRING, &cmdlineP->background, 
+            &backgroundSpec, 0);
+    OPTENT3(0, "closeok",        OPT_FLAG, NULL,
+            &cmdlineP->closeok, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!backgroundSpec)
+        cmdlineP->background = NULL;
+
+    if (argc - 1 < 1) 
+        pm_error("You must specify at least one argument:  the strip count");
+    else {
+        int const stripcount = atoi(argv[1]);
+        if (stripcount <= 0)
+            pm_error("The strip count must be positive.  You specified %d",
+                     stripcount);
+            
+        cmdlineP->stripcount = stripcount;
+
+        if (argc-1 < 2)
+            cmdlineP->inputFileName = "-";
+        else
+            cmdlineP->inputFileName = argv[2];
+    
+        if (argc - 1 > 2)
+            pm_error("There are at most two arguments: strip count "
+                     "and input file name.  "
+                     "You specified %u", argc-1);
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    pixel ** srcPixels;
+    pixel ** dstPixels;
+    int srcCols, srcRows;
+    unsigned int dstCols, dstRows;
+    pixval srcMaxval, dstMaxval;
+    unsigned int stripWidth;
+        /* Width in pixels of each strip.  In the output image, this means
+           the rectangular strip in which the lens-shaped foreground strip
+           is placed..
+        */
+    unsigned int row;
+    pixel backgroundColor;
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    ifP = pm_openr(cmdline.inputFileName);
+    
+    srcPixels = ppm_readppm(ifP, &srcCols, &srcRows, &srcMaxval);
+
+    pm_close(ifP);
+
+    stripWidth = srcCols / cmdline.stripcount;
+
+    if (stripWidth < 1)
+        pm_error("You asked for %u strips, but the image is only "
+                 "%u pixels wide, so that is impossible.",
+                 cmdline.stripcount, srcCols);
+
+    dstCols   = stripWidth * cmdline.stripcount;
+    dstRows   = srcRows;
+    dstMaxval = srcMaxval;
+
+    if (cmdline.background == NULL)
+        PPM_ASSIGN(backgroundColor, 0, 0, 0);
+    else
+        pm_parse_dictionary_name(cmdline.background,
+                                 dstMaxval, cmdline.closeok,
+                                 &backgroundColor);
+
+    dstPixels = ppm_allocarray(dstCols, dstRows);
+    
+    for (row = 0; row < dstRows; ++row) {
+        double const factor = sin(M_PI * row / dstRows);
+            /* Amount by which we squeeze the foreground image of each
+               strip in this row.
+            */
+        int const stripBorder = (int)((stripWidth*(1.0-factor)/2.0) + 0.5);
+            /* Distance from the edge (either one) of a strip to the
+               foreground image within that strip -- i.e. number of pixels
+               of background color, which User will cut out with scissors
+               after he prints the image.
+            */
+        unsigned int dstCol;
+
+        for (dstCol = 0; dstCol < dstCols; ++dstCol) {
+            if (dstCol % stripWidth < stripBorder
+                || dstCol % stripWidth >= stripWidth - stripBorder)
+                dstPixels[row][dstCol] = backgroundColor;
+            else {
+                unsigned int const leftEdge =
+                    (dstCol / stripWidth) * stripWidth;
+                unsigned int const srcCol = leftEdge +
+                    (int)((dstCol % stripWidth - stripBorder) / factor + 0.5);
+                dstPixels[row][dstCol] = srcPixels[row][srcCol];
+            }
+        }
+    }
+
+    ppm_writeppm(stdout, dstPixels, dstCols, dstRows, dstMaxval, 0);
+
+    return 0;
+}
diff --git a/editor/ppmlabel.c b/editor/ppmlabel.c
new file mode 100644
index 00000000..885d7d36
--- /dev/null
+++ b/editor/ppmlabel.c
@@ -0,0 +1,212 @@
+/*
+
+           Add text labels to a PPM image
+
+           by John Walker  --  kelvin@fourmilab.ch
+           WWW home page: http://www.fourmilab.ch/
+                  June 1995
+*/
+
+#define _XOPEN_SOURCE   /* get M_PI in math.h */
+
+#include <math.h>
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "ppmdraw.h"
+
+#define dtr(x)  (((x) * M_PI) / 180.0)
+
+static int argn, rows, cols, x, y, size, angle, transparent;
+static pixel **pixels;
+static pixval maxval;
+static pixel rgbcolor, backcolor;
+
+/*  DRAWTEXT  --  Draw text at current location and advance to
+          start of next line.  */
+
+static void 
+drawtext(const char * const text) {
+
+    if (!transparent && strlen(text) > 0) {
+        struct fillobj * handle;
+
+        int left, top, right, bottom;
+        int lx, ly;
+        int p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y;
+        double sina, cosa;
+
+        handle = ppmd_fill_create();
+        
+        ppmd_text_box(size, 0, text, &left, &top, &right, &bottom);
+
+        /* Displacement vector */ 
+
+        lx = right;
+        ly = -(top - bottom);
+
+        /* Sine and cosine */
+
+        sina = sin(dtr(angle));
+        cosa = cos(dtr(angle));
+
+        /* Rotated extent box corners */
+
+        p1x = (int) ((x + left * cosa + bottom * sina) + 0.5);
+        p1y = (int) ((y + bottom * cosa + -left * sina) + 0.5);
+
+#define WERF    ppmd_fill_drawproc, handle
+
+        p2x = (int) (p1x - sina * ly + 0.5);
+        p2y = (int) ((p1y - cosa * ly) + 0.5);
+
+        p3x = (int) (p1x + cosa * lx + -sina * ly + 0.5);
+        p3y = (int) ((p1y - (cosa * ly + sina * lx)) + 0.5);
+
+        p4x = (int) (p1x + cosa * lx + 0.5);
+        p4y = (int) ((p1y - sina * lx) + 0.5);
+
+        ppmd_line(pixels, cols, rows, maxval,
+                  p1x, p1y, p2x, p2y,
+                  WERF);
+        ppmd_line(pixels, cols, rows, maxval,
+                  p2x, p2y, p3x, p3y,
+                  WERF);
+        ppmd_line(pixels, cols, rows, maxval,
+                  p3x, p3y, p4x, p4y,
+                  WERF);
+        ppmd_line(pixels, cols, rows, maxval,
+                  p4x, p4y, p1x, p1y,
+                  WERF);
+
+
+        ppmd_fill(pixels, cols, rows, maxval,
+                  handle, PPMD_NULLDRAWPROC, (char *) &backcolor);
+
+        ppmd_fill_destroy(handle);
+    }
+    ppmd_text(pixels, cols, rows, maxval,
+              x, y, size, angle, text,
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+
+    /* For convenience, simulate a carriage return to the next line.
+       This allows multiple "-text" specifications or multiple lines
+       in a -file input to write consecutive lines of text in a
+       generally reasonable fashion.
+    */
+
+    x += (int) ((cos(dtr(angle + 270)) * size * 1.75) + 0.5);
+    y -= (int) ((sin(dtr(angle + 270)) * size * 1.75) + 0.5);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE *ifP;
+
+    /* Process standard command line arguments */
+
+    ppm_init(&argc, argv);
+
+    argn = 1;
+
+    /* Check for explicit input file specification,  Note that
+       we count on the fact that every command line switch
+       takes a single argument.  If this becomes untrue due
+       to a change in the future, you'll have to make this
+       test smarter.
+    */
+
+    if ((argn != argc) && (argc == 2 || argv[argc - 2][0] != '-')) {
+        ifP = pm_openr(argv[argc - 1]);
+        argc--;
+    } else
+        ifP = stdin;
+
+    /* Load input image */
+
+    pixels = ppm_readppm(ifP, &cols, &rows, &maxval);
+    pm_close(ifP);
+
+    /* Set initial defaults */
+
+    x = 0;
+    y = rows / 2;
+    size = 12;
+    angle = 0;
+    PPM_ASSIGN(rgbcolor, maxval, maxval, maxval);
+    PPM_ASSIGN(backcolor, 0, 0, 0);
+    transparent = TRUE;
+
+    while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0') {
+
+        if (pm_keymatch(argv[argn], "-angle", 1)) {
+            argn++;
+            if ((argn == argc) || (sscanf(argv[argn], "%d", &angle) != 1))
+                pm_error("-angle doesn't have a value");
+
+        } else if (pm_keymatch(argv[argn], "-background", 1)) {
+            argn++;
+            if (strcmp(argv[argn], "transparent") == 0) {
+                transparent = TRUE;
+            } else {
+                transparent = FALSE;
+                backcolor = ppm_parsecolor(argv[argn], maxval);
+            }
+
+        } else if (pm_keymatch(argv[argn], "-color", 1)
+                   || pm_keymatch(argv[argn], "-colour", 1)) {
+            argn++;
+            rgbcolor = ppm_parsecolor(argv[argn], maxval);
+
+        } else if (pm_keymatch(argv[argn], "-file", 1)) {
+            char s[512];
+
+            argn++;
+            ifP = pm_openr(argv[argn]);
+            while (fgets(s, sizeof s, ifP) != NULL) {
+                while (s[0] != 0 && s[strlen(s) - 1] < ' ') {
+                    s[strlen(s) - 1] = 0;
+                }
+                drawtext(s);
+            }
+            pm_close(ifP);
+
+        } else if (pm_keymatch(argv[argn], "-size", 1)) {
+            argn++;
+            if ((argn == argc) || (sscanf(argv[argn], "%d", &size) != 1))
+                pm_error("-size doesn't have a value");
+        } else if (pm_keymatch(argv[argn], "-text", 1)) {
+            argn++;
+            drawtext(argv[argn]);
+
+        } else if (pm_keymatch(argv[argn], "-u", 1)) {
+            pm_error("-u doesn't have a value");
+
+        } else if (pm_keymatch(argv[argn], "-x", 1)) {
+            argn++;
+            if ((argn == argc) || (sscanf(argv[argn], "%d", &x) != 1))
+                pm_error("-x doesn't have a value");
+
+        } else if (pm_keymatch(argv[argn], "-y", 1)) {
+            argn++;
+            if ((argn == argc) || (sscanf(argv[argn], "%d", &y) != 1))
+                pm_error("-y doesn't have a value");
+
+        } else
+            pm_error("Unrecognized option: '%s'", argv[argn]);
+        argn++;
+    }
+
+    if (argn != argc)
+        pm_error("Extraneous arguments");
+
+    ppm_writeppm(stdout, pixels, cols, rows, maxval, 0);
+
+    ppm_freearray(pixels, rows);
+
+    return 0;
+}
diff --git a/editor/ppmmix.c b/editor/ppmmix.c
new file mode 100644
index 00000000..5306d1cf
--- /dev/null
+++ b/editor/ppmmix.c
@@ -0,0 +1,131 @@
+
+/*********************************************************************/
+/* ppmmix -  mix together two pictures like with a fader             */
+/* Frank Neumann, October 1993                                       */
+/* V1.2 16.11.1993                                                   */
+/*                                                                   */
+/* version history:                                                  */
+/* V1.0 Aug   1993    first version                                  */
+/* V1.1 12.10.1993    uses ppm libs&headers, integer math, cleanups  */
+/* V1.2 16.11.1993    Rewritten to be NetPBM.programming conforming  */
+/*********************************************************************/
+
+#include "ppm.h"
+
+/* global variables */
+#ifdef AMIGA
+static char *version = "$VER: ppmmix 1.2 (16.11.93)"; /* Amiga version identification */
+#endif
+
+/**************************/
+/* start of main function */
+/**************************/
+int main(argc, argv)
+int argc;
+char *argv[];
+{
+	FILE *ifp1, *ifp2;
+	int argn, rows, cols, format, i = 0, j = 0;
+	int rows2, cols2, format2;
+	pixel *srcrow1, *srcrow2, *destrow;
+	pixel *pP1, *pP2, *pP3;
+	pixval maxval, maxval2;
+	pixval r1, r2, r3, g1, g2, g3, b1, b2, b3;
+	double fadefactor;
+	long longfactor;
+	const char * const usage = "fadefactor ppmfile1 ppmfile2\n        fadefactor: 0.0 = only ppmfile1, 1.0 = only ppmfile2\n";
+
+	/* parse in 'default' parameters */
+	ppm_init(&argc, argv);
+
+	argn = 1;
+
+	/* parse in dim factor */
+	if (argn == argc)
+		pm_usage(usage);
+	if (sscanf(argv[argn], "%lf", &fadefactor) != 1)
+		pm_usage(usage);
+	if (fadefactor < 0.0 || fadefactor > 1.0)
+		pm_error("fade factor must be in the range from 0.0 to 1.0 ");
+	++argn;
+
+	/* parse in filenames and open files (cannot be stdin-filters, sorry..) */
+	if (argn == argc-2)
+	{
+		ifp1 = pm_openr(argv[argn]);
+		++argn;
+		ifp2 = pm_openr(argv[argn]);
+	}
+	else
+		pm_usage(usage);
+
+	/* read first data from both files and compare sizes etc. */
+	ppm_readppminit(ifp1, &cols, &rows, &maxval, &format);
+	ppm_readppminit(ifp2, &cols2, &rows2, &maxval2, &format2);
+
+    if ( (cols != cols2) || (rows != rows2) )
+        pm_error("image sizes are different!");
+
+    if ( maxval != maxval2)
+		pm_error("images have different maxvalues");
+
+	if (format != format2)
+	{
+		pm_error("images have different PxM types");
+	}
+
+	/* no error checking required here, ppmlib does it all for us */
+	srcrow1 = ppm_allocrow(cols);
+	srcrow2 = ppm_allocrow(cols);
+
+	longfactor = (long)(fadefactor * 65536);
+
+	/* allocate a row of pixel data for the new pixels */
+	destrow = ppm_allocrow(cols);
+
+	ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+	for (i = 0; i < rows; i++)
+	{
+		ppm_readppmrow(ifp1, srcrow1, cols, maxval, format);
+		ppm_readppmrow(ifp2, srcrow2, cols, maxval, format);
+
+		pP1 = srcrow1;
+		pP2 = srcrow2;
+        pP3 = destrow;
+
+		for (j = 0; j < cols; j++)
+		{
+			r1 = PPM_GETR(*pP1);
+			g1 = PPM_GETG(*pP1);
+			b1 = PPM_GETB(*pP1);
+
+			r2 = PPM_GETR(*pP2);
+			g2 = PPM_GETG(*pP2);
+			b2 = PPM_GETB(*pP2);
+
+			r3 = r1 + (((r2 - r1) * longfactor) >> 16);
+			g3 = g1 + (((g2 - g1) * longfactor) >> 16);
+			b3 = b1 + (((b2 - b1) * longfactor) >> 16);
+
+
+			PPM_ASSIGN(*pP3, r3, g3, b3);
+
+			pP1++;
+			pP2++;
+			pP3++;
+		}
+
+		/* write out one line of graphic data */
+		ppm_writeppmrow(stdout, destrow, cols, maxval, 0);
+	}
+
+	pm_close(ifp1);
+	pm_close(ifp2);
+	ppm_freerow(srcrow1);
+	ppm_freerow(srcrow2);
+	ppm_freerow(destrow);
+
+	exit(0);
+}
+
diff --git a/editor/ppmntsc.c b/editor/ppmntsc.c
new file mode 100644
index 00000000..b9f2ac2f
--- /dev/null
+++ b/editor/ppmntsc.c
@@ -0,0 +1,499 @@
+/* This is ppmntsc.c, a program to adjust saturation values in an image
+   so they are legal for NTSC or PAL.
+
+   It is derived from the program rlelegal.c, dated June 5, 1995,
+   which is described below and propagates that program's copyright.
+   The derivation was done by Bryan Henderson on 2000.04.21 to convert
+   it from operating on the RLE format to operating on the PPM format
+   and to rewrite it in a cleaner style, taking advantage of modern C
+   compiler technology.  
+*/
+
+
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ */
+
+/* 
+ * rlelegal.c - Make RGB colors legal in the YIQ or YUV color systems.
+ * 
+ * Author:	Wes Barris
+ * 		Minnesota Supercomputer Center, Inc.
+ * Date:	Fri Oct 15, 1993
+ * @Copyright, Research Equipment Inc., d/b/a Minnesota Supercomputer
+ * Center, Inc., 1993
+
+ */
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include "ppm.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+
+#define TRUE 1
+#define FALSE 0
+
+enum legalize {RAISE_SAT, LOWER_SAT, ALREADY_LEGAL};
+   /* The actions that make a legal pixel */
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilename;
+    unsigned int verbose;
+    unsigned int debug;
+    unsigned int pal;
+    enum {ALL, LEGAL_ONLY, ILLEGAL_ONLY, CORRECTED_ONLY} output;
+};
+
+
+
+
+static void 
+rgbtoyiq(const int r, const int g, const int b, 
+         double * const y_p, 
+         double * const i_p, 
+         double * const q_p) {
+    
+    *y_p = .299*(r/255.0) + .587*(g/255.0) + .114*(b/255.0);
+    *i_p = .596*(r/255.0) - .274*(g/255.0) - .322*(b/255.0);
+    *q_p = .211*(r/255.0) - .523*(g/255.0) + .312*(b/255.0);
+}
+
+
+
+static void 
+yiqtorgb(const double y, const double i, const double q, 
+         int * const r_p, int * const g_p, int * const b_p) {
+    *r_p = 255.0*(1.00*y + .9562*i + .6214*q);
+    *g_p = 255.0*(1.00*y - .2727*i - .6468*q);
+    *b_p = 255.0*(1.00*y -1.1037*i +1.7006*q);
+}
+
+
+
+static void 
+rgbtoyuv(const int r, const int g, const int b, 
+         double * const y_p, 
+         double * const u_p, 
+         double * const v_p) {
+    *y_p =  .299*(r/255.0) + .587*(g/255.0) + .114*(b/255.0);
+    *u_p = -.147*(r/255.0) - .289*(g/255.0) + .437*(b/255.0);
+    *v_p =  .615*(r/255.0) - .515*(g/255.0) - .100*(b/255.0);
+}
+
+
+
+static void 
+yuvtorgb(const double y, const double u, const double v, 
+         int * const r_p, int * const g_p, int * const b_p) {
+    
+    *r_p = 255.0*(1.00*y + .0000*u +1.1398*v);
+    *g_p = 255.0*(1.00*y - .3938*u - .5805*v);
+    *b_p = 255.0*(1.00*y +2.0279*u + .0000*v);
+}
+
+
+
+static void
+make_legal_yiq(const double y, const double i, const double q, 
+               double * const y_new_p, 
+               double * const i_new_p, 
+               double * const q_new_p,
+               enum legalize * const action_p
+    ) {
+    
+    double sat_old, sat_new;
+    /*
+     * I and Q are legs of a right triangle.  Saturation is the hypotenuse.
+     */
+    sat_old = sqrt(i*i + q*q);
+    if (y+sat_old > 1.0) {
+        const double diff = 0.5*((y+sat_old) - 1.0);
+        *y_new_p = y - diff;
+        sat_new = 1.0 - *y_new_p;
+        *i_new_p = i*(sat_new/sat_old);
+        *q_new_p = q*(sat_new/sat_old);
+        *action_p = LOWER_SAT;
+    } else if (y-sat_old <= -0.251) {
+        const double diff = 0.5*((sat_old-y) - 0.251);
+        *y_new_p = y + diff;
+        sat_new = 0.250 + *y_new_p;
+        *i_new_p = i*(sat_new/sat_old);
+        *q_new_p = q*(sat_new/sat_old);
+        *action_p = RAISE_SAT;
+    } else {
+        *y_new_p = y;
+        *i_new_p = i;
+        *q_new_p = q;
+        *action_p = ALREADY_LEGAL;
+    }
+    return;
+}
+
+
+
+static void
+make_legal_yuv(const double y, const double u, const double v, 
+               double * const y_new_p, 
+               double * const u_new_p, 
+               double * const v_new_p,
+               enum legalize * const action_p
+    ) {
+    
+    double sat_old, sat_new;
+    /*
+     * U and V are legs of a right triangle.  Saturation is the hypotenuse.
+     */
+    sat_old = sqrt(u*u + v*v);
+    if (y+sat_old >= 1.334) {
+        const double diff = 0.5*((y+sat_old) - 1.334);
+        *y_new_p = y - diff;
+        sat_new = 1.333 - *y_new_p;
+        *u_new_p = u*(sat_new/sat_old);
+        *v_new_p = v*(sat_new/sat_old);
+        *action_p = LOWER_SAT;
+    } else if (y-sat_old <= -0.339) {
+        const double diff = 0.5*((sat_old-y) - 0.339);
+        *y_new_p = y + diff;
+        sat_new = 0.338 + *y_new_p;
+        *u_new_p = u*(sat_new/sat_old);
+        *v_new_p = v*(sat_new/sat_old);
+        *action_p = RAISE_SAT;
+    } else {
+        *u_new_p = u;
+        *v_new_p = v;
+        *action_p = ALREADY_LEGAL;
+    }
+    return;
+}
+
+
+
+static void
+make_legal_yiq_i(const int r_in, const int g_in, const int b_in, 
+                 int * const r_out_p, 
+                 int * const g_out_p, 
+                 int * const b_out_p,
+                 enum legalize * const action_p
+    ) {
+    
+    double y, i, q;
+    double y_new, i_new, q_new;
+    /*
+     * Convert to YIQ and compute the new saturation.
+     */
+    rgbtoyiq(r_in, g_in, b_in, &y, &i, &q);
+    make_legal_yiq(y, i, q, &y_new, &i_new, &q_new, action_p);
+    if (*action_p != ALREADY_LEGAL)
+        /*
+         * Given the new I and Q, compute new RGB values.
+        */
+        yiqtorgb(y_new, i_new, q_new, r_out_p, g_out_p, b_out_p);
+    else {
+        *r_out_p = r_in;
+        *g_out_p = g_in;
+        *b_out_p = b_in;
+      }
+    return;
+}
+
+
+
+static void
+make_legal_yuv_i(const int r_in, const int g_in, const int b_in, 
+                 int * const r_out_p, 
+                 int * const g_out_p, 
+                 int * const b_out_p,
+                 enum legalize * const action_p
+    ){
+    
+    double y, u, v;
+    double y_new, u_new, v_new;  
+    /*
+     * Convert to YUV and compute the new saturation.
+     */
+    rgbtoyuv(r_in, g_in, b_in, &y, &u, &v);
+    make_legal_yuv(y, u, v, &y_new, &u_new, &v_new, action_p);
+    if (*action_p != ALREADY_LEGAL)
+        /*
+         * Given the new U and V, compute new RGB values.
+         */
+        yuvtorgb(y_new, u_new, v_new, r_out_p, g_out_p, b_out_p);
+    else {
+        *r_out_p = r_in;
+        *g_out_p = g_in;
+        *b_out_p = b_in;
+    }
+    return;
+}
+
+
+
+static void 
+make_legal_yiq_b(const pixel input,
+                 pixel * const output_p,
+                 enum legalize * const action_p) {
+
+
+    int ir_in, ig_in, ib_in;
+    int ir_out, ig_out, ib_out;
+    
+    ir_in = (int)PPM_GETR(input);
+    ig_in = (int)PPM_GETG(input);
+    ib_in = (int)PPM_GETB(input);
+
+    make_legal_yiq_i(ir_in, ig_in, ib_in, &ir_out, &ig_out, &ib_out, action_p);
+
+    PPM_ASSIGN(*output_p, ir_out, ig_out, ib_out);
+
+    return;
+}
+
+
+
+static void 
+make_legal_yuv_b(const pixel input,
+                 pixel * const output_p,
+                 enum legalize * const action_p) {
+
+    int ir_in, ig_in, ib_in;
+    int ir_out, ig_out, ib_out;
+    
+    ir_in = (int)PPM_GETR(input);
+    ig_in = (int)PPM_GETG(input);
+    ib_in = (int)PPM_GETB(input);
+    make_legal_yuv_i(ir_in, ig_in, ib_in, &ir_out, &ig_out, &ib_out, action_p);
+
+    PPM_ASSIGN(*output_p, ir_out, ig_out, ib_out);
+
+    return;
+}
+
+
+
+static void 
+report_mapping(const pixel old_pixel, const pixel new_pixel) {
+/*----------------------------------------------------------------------------
+  Assuming old_pixel and new_pixel are input and output pixels,
+  tell the user that we changed a pixel to make it legal, if in fact we
+  did and it isn't the same change that we just reported.
+-----------------------------------------------------------------------------*/
+    static pixel last_changed_pixel;
+    static int first_time = TRUE;
+
+    if (!PPM_EQUAL(old_pixel, new_pixel) && 
+        (first_time || PPM_EQUAL(old_pixel, last_changed_pixel))) {
+        pm_message("Mapping %d %d %d -> %d %d %d\n",
+                   PPM_GETR(old_pixel),
+                   PPM_GETG(old_pixel),
+                   PPM_GETB(old_pixel),
+                   PPM_GETR(new_pixel),
+                   PPM_GETG(new_pixel),
+                   PPM_GETB(new_pixel)
+            );
+
+        last_changed_pixel = old_pixel;
+        first_time = FALSE;
+    }    
+}
+
+
+
+static void
+convert_one_image(FILE * const ifp, struct cmdlineInfo const cmdline, 
+                  bool * const eofP, 
+                  int * const hicountP, int * const locountP) {
+
+    /* Parameters of input image: */
+    int rows, cols;
+    pixval maxval;
+    int format;
+
+    ppm_readppminit(ifp, &cols, &rows, &maxval, &format);
+    ppm_writeppminit(stdout, cols, rows, maxval, FALSE);
+    {
+        pixel* const input_row = ppm_allocrow(cols);
+        pixel* const output_row = ppm_allocrow(cols);
+        pixel last_illegal_pixel;
+        /* Value of the illegal pixel we most recently processed */
+        pixel black;
+        /* A constant - black pixel */
+
+        PPM_ASSIGN(black, 0, 0, 0);
+
+        PPM_ASSIGN(last_illegal_pixel, 0, 0, 0);  /* initial value */
+        {
+            int row;
+
+            *hicountP = 0; *locountP = 0;  /* initial values */
+
+            for (row = 0; row < rows; ++row) {
+                int col;
+                ppm_readppmrow(ifp, input_row, cols, maxval, format);
+                for (col = 0; col < cols; ++col) {
+                    pixel corrected;
+                    /* Corrected or would-be corrected value for pixel */
+                    enum legalize action;
+                    /* What action was used to make pixel legal */
+                    if (cmdline.pal)
+                        make_legal_yuv_b(input_row[col],
+                                         &corrected,
+                                         &action);
+                    else
+                        make_legal_yiq_b(input_row[col],
+                                         &corrected,
+                                         &action);
+                        
+                    if (action == LOWER_SAT) 
+                        (*hicountP)++;
+                    if (action == RAISE_SAT)
+                        (*locountP)++;
+                    if (cmdline.debug) report_mapping(input_row[col],
+                                                      corrected);
+                    switch (cmdline.output) {
+                    case ALL:
+                        output_row[col] = corrected;
+                        break;
+                    case LEGAL_ONLY:
+                        output_row[col] = (action == ALREADY_LEGAL) ?
+                            input_row[col] : black;
+                        break;
+                    case ILLEGAL_ONLY:
+                        output_row[col] = (action != ALREADY_LEGAL) ?
+                            input_row[col] : black;
+                        break;
+                    case CORRECTED_ONLY:
+                        output_row[col] = (action != ALREADY_LEGAL) ?
+                            corrected : black;
+                        break;
+                    }
+                }
+                ppm_writeppmrow(stdout, output_row, cols, maxval, FALSE);
+            }
+        }
+        ppm_freerow(output_row);
+        ppm_freerow(input_row);
+    }
+}
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that many of the strings that this function returns in the
+   *cmdlineP structure are actually in the supplied argv array.  And
+   sometimes, one of these strings is actually just a suffix of an entry
+   in argv!
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;
+    optEntry *option_def;
+        /* Instructions to OptParseOptions on how to parse our options.
+         */
+    unsigned int option_def_index;
+    unsigned int legalonly, illegalonly, correctedonly;
+
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3('v', "verbose",        OPT_FLAG, NULL,  &cmdlineP->verbose,  0);
+    OPTENT3('V', "debug",          OPT_FLAG, NULL,  &cmdlineP->debug,    0);
+    OPTENT3('p', "pal",            OPT_FLAG, NULL,  &cmdlineP->pal,      0);
+    OPTENT3('l', "legalonly",      OPT_FLAG, NULL,  &legalonly,           0);
+    OPTENT3('i', "illegalonly",    OPT_FLAG, NULL,  &illegalonly,         0);
+    OPTENT3('c', "correctedonly",  OPT_FLAG, NULL,  &correctedonly,       0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = TRUE;
+    opt.allowNegNum = FALSE;
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+    if (argc - 1 == 0)
+        cmdlineP->inputFilename = "-";  /* he wants stdin */
+    else if (argc - 1 == 1)
+        cmdlineP->inputFilename = argv[1];
+    else 
+        pm_error("Too many arguments.  The only arguments accepted "
+                 "are the mask color and optional input file specification");
+
+    if (legalonly + illegalonly + correctedonly > 1)
+        pm_error("--legalonly, --illegalonly, and --correctedonly are "
+                 "conflicting options.  Specify at most one of these.");
+        
+    if (legalonly) 
+        cmdlineP->output = LEGAL_ONLY;
+    else if (illegalonly) 
+        cmdlineP->output = ILLEGAL_ONLY;
+    else if (correctedonly) 
+        cmdlineP->output = CORRECTED_ONLY;
+    else 
+        cmdlineP->output = ALL;
+}
+
+
+
+int
+main(int argc, char **argv) {
+    
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    int total_hicount, total_locount;
+    int image_count;
+
+    bool eof;
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilename);
+
+    image_count = 0;    /* initial value */
+    total_hicount = 0;  /* initial value */
+    total_locount = 0;  /* initial value */
+
+    eof = FALSE;
+    while (!eof) {
+        int hicount, locount;
+        convert_one_image(ifP, cmdline, &eof, &hicount, &locount);
+        image_count++;
+        total_hicount += hicount;
+        total_locount += locount;
+        ppm_nextimage(ifP, &eof);
+    }
+
+
+	if (cmdline.verbose) {
+        pm_message("%d images processed.", image_count);
+        pm_message("%d pixels were above the saturation limit.", 
+                   total_hicount);
+        pm_message("%d pixels were below the saturation limit.", 
+                   total_locount);
+    }
+    
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/editor/ppmquant b/editor/ppmquant
new file mode 100755
index 00000000..11bce6d2
--- /dev/null
+++ b/editor/ppmquant
@@ -0,0 +1,30 @@
+#!/usr/bin/perl -w
+##############################################################################
+#  This is nothing but a compatibility interface for Pnmquant.
+#  An old program coded to call Ppmquant will continue working because
+#  this interface exists.  All new (or newly modified) programs should
+#  call Pnmquant or Pnmremap instead.
+#
+#  In days past, Pnmquant and Pnmremap did not exist.  Ppmquant did
+#  the job of both Pnmremap and Pnmquant, but only on PPM images.
+##############################################################################
+
+use strict;
+
+use Getopt::Long;
+
+my $TRUE=1; my $FALSE = 0;
+
+my @ppmquantArgv = @ARGV;
+
+Getopt::Long::Configure('pass_through');
+
+my $validOptions = GetOptions('mapfile' => \my $mapfileopt);
+
+my $mapfileOptionPresent = ($validOptions && $mapfileopt);
+
+if ($mapfileOptionPresent) {
+    system('pnmremap', @ppmquantArgv);
+} else {
+    system('pnmquant', @ppmquantArgv);
+}
diff --git a/editor/ppmquantall b/editor/ppmquantall
new file mode 100755
index 00000000..af1ce22c
--- /dev/null
+++ b/editor/ppmquantall
@@ -0,0 +1,97 @@
+#!/bin/sh
+#
+# ppmquantall - run ppmquant on a bunch of files all at once, so they share
+#               a common colormap
+#
+# WARNING: overwrites the source files with the results!!!
+#
+# Verbose explanation: Let's say you've got a dozen pixmaps that you want
+# to display on the screen all at the same time.  Your screen can only
+# display 256 different colors, but the pixmaps have a total of a thousand
+# or so different colors.  For a single pixmap you solve this problem with
+# pnmquant; this script solves it for multiple pixmaps.  All it does is
+# concatenate them together into one big pixmap, run pnmquant on that, and
+# then split it up into little pixmaps again.
+#
+# IMPLEMENTATION NOTE:  Now that Pnmcolormap can compute a single colormap
+# for a whole stream of images, this program could be implemented more
+# simply.  Today, it concatenates a bunch of images into one image, uses
+# Pnmquant to quantize that, then splits the result back into multiple
+# images.  It could instead just run Pnmcolormap over all the images,
+# then run Pnmremap on each input image using the one colormap for all.
+
+usage()
+{
+    echo "usage: $0 [-ext extension] <newcolors> <ppmfile> ..."
+    exit 1
+}
+
+ext=
+
+while :; do
+
+    case "$1" in
+    -ext*)
+        if [ $# -lt 2 ]; then
+            usage
+        fi
+        ext=".$2"
+        shift
+        shift
+    ;;
+
+    *)  
+        break
+    ;;
+
+    esac
+done
+
+if [ $# -lt 2 ]; then
+    usage
+fi
+
+newcolors=$1
+shift
+nfiles=$#
+files=($@)
+
+# Extract the width and height of each of the images.
+# Here, we make the assumption that the width and height are on the
+# second line, even though the PPM format doesn't require that.
+# To be robust, we need to use Pnmfile to get that information, or 
+# Put this program in C and use ppm_readppminit().
+
+set widths=()
+set heights=()
+
+for i in ${files[@]}; do
+    widths=(${widths[*]} `grep -v '^#' $i | sed '1d; s/ .*//; 2q'`)
+    heights=(${heights[*]} `grep -v '^#' $i | sed '1d; s/.* //; 2q'`)
+done
+
+tempdir="${TMPDIR-/tmp}/ppmquantall.$$"
+mkdir $tempdir || { echo "Could not create temporary file. Exiting."; exit 1;}
+chmod 700 $tempdir
+
+trap 'rm -rf $tempdir' 0 1 3 15
+
+all=$tempdir/pqa.all.$$
+
+pnmcat -topbottom -jleft -white ${files[@]} | pnmquant $newcolors > $all
+if [ $? != 0 ]; then
+    exit $?
+fi
+
+y=0
+i=0
+
+while [ $i -lt $nfiles ]; do
+    pamcut -left 0 -top $y -width ${widths[$i]} -height ${heights[$i]} $all \
+        > ${files[$i]}$ext
+    if [ $? != 0 ]; then
+        exit $?
+    fi
+    y=$(($y + ${heights[$i]}))
+    i=$(($i + 1))
+done
diff --git a/editor/ppmquantall.csh b/editor/ppmquantall.csh
new file mode 100644
index 00000000..9a89bca0
--- /dev/null
+++ b/editor/ppmquantall.csh
@@ -0,0 +1,57 @@
+#!/bin/csh -f
+#
+# ppmquantall - run ppmquant on a bunch of files all at once, so they share
+#               a common colormap
+#
+# WARNING: overwrites the source files with the results!!!
+#
+# Verbose explanation: Let's say you've got a dozen pixmaps that you want
+# to display on the screen all at the same time.  Your screen can only
+# display 256 different colors, but the pixmaps have a total of a thousand
+# or so different colors.  For a single pixmap you solve this problem with
+# ppmquant; this script solves it for multiple pixmaps.  All it does is
+# concatenate them together into one big pixmap, run ppmquant on that, and
+# then split it up into little pixmaps again.
+
+if ( $#argv < 3 ) then
+    echo "usage:  ppmquantall <newcolors> <ppmfile> <ppmfile> ..."
+    exit 1
+endif
+
+set newcolors=$argv[1]
+set files=( $argv[2-] )
+
+# Extract the width and height of each of the images.
+# Here, we make the assumption that the width and height are on the
+# second line, even though the PPM format doesn't require that.
+# To be robust, we need to use Pnmfile to get that information, or 
+# Put this program in C and use ppm_readppminit().
+
+set widths=()
+set heights=()
+foreach i ( $files )
+    set widths=( $widths `sed '1d; s/ .*//; 2q' $i` )
+    set heights=( $heights `sed '1d; s/.* //; 2q' $i` )
+end
+
+set all=/tmp/pqa.all.$$
+rm -f $all
+pnmcat -topbottom -jleft -white $files | ppmquant -quiet $newcolors > $all
+if ( $status != 0 ) exit $status
+
+@ y = 0
+@ i = 1
+while ( $i <= $#files )
+    pnmcut -left 0 -top $y -width $widths[$i] -height $heights[$i] $all \
+       > $files[$i]
+    if ( $status != 0 ) exit $status
+    @ y = $y + $heights[$i]
+    @ i++
+end
+
+rm -f $all
+
+
+
+
+
diff --git a/editor/ppmrelief.c b/editor/ppmrelief.c
new file mode 100644
index 00000000..5e0669c3
--- /dev/null
+++ b/editor/ppmrelief.c
@@ -0,0 +1,90 @@
+/* ppmrelief.c - generate a relief map of a portable pixmap
+**
+** Copyright (C) 1990 by Wilson H. Bent, Jr.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <stdio.h>
+#include "ppm.h"
+
+int
+main(int argc, char * argv[]) {
+
+    FILE* ifp;
+    pixel** inputbuf;
+    pixel* outputrow;
+    int argn, rows, cols, format, row;
+    register int col;
+    pixval maxval, mv2;
+    const char* const usage = "[ppmfile]";
+
+    ppm_init( &argc, argv );
+
+    argn = 1;
+
+    if ( argn != argc ) {
+        ifp = pm_openr( argv[argn] );
+        ++argn;
+    } else
+        ifp = stdin;
+
+    if ( argn != argc )
+        pm_usage( usage );
+    
+    ppm_readppminit( ifp, &cols, &rows, &maxval, &format );
+    mv2 = maxval / 2;
+
+    /* Allocate space for 3 input rows, plus an output row. */
+    inputbuf = ppm_allocarray( cols, 3 );
+    outputrow = ppm_allocrow( cols );
+
+    ppm_writeppminit( stdout, cols, rows, maxval, 0 );
+
+    /* Read in the first two rows. */
+    ppm_readppmrow( ifp, inputbuf[0], cols, maxval, format );
+    ppm_readppmrow( ifp, inputbuf[1], cols, maxval, format );
+
+    /* Write out the first row, all zeros. */
+    for ( col = 0; col < cols; ++col )
+        PPM_ASSIGN( outputrow[col], 0, 0, 0 );
+    ppm_writeppmrow( stdout, outputrow, cols, maxval, 0 );
+
+    /* Now the rest of the image - read in the 3rd row of inputbuf,
+    ** and convolve with the first row into the output buffer.
+    */
+    for ( row = 2 ; row < rows; ++row ) {
+        pixval r, g, b;
+        int rowa, rowb;
+
+        rowa = row % 3;
+        rowb = (row + 2) % 3;
+        ppm_readppmrow( ifp, inputbuf[rowa], cols, maxval, format );
+        
+        for ( col = 0; col < cols - 2; ++col ) {
+            r = PPM_GETR( inputbuf[rowa][col] ) +
+                ( mv2 - PPM_GETR( inputbuf[rowb][col + 2] ) );
+            g = PPM_GETG( inputbuf[rowa][col] ) +
+                ( mv2 - PPM_GETG( inputbuf[rowb][col + 2] ) );
+            b = PPM_GETB( inputbuf[rowa][col] ) +
+                ( mv2 - PPM_GETB( inputbuf[rowb][col + 2] ) );
+            PPM_ASSIGN( outputrow[col + 1], r, g, b );
+        }
+        ppm_writeppmrow( stdout, outputrow, cols, maxval, 0 );
+    }
+
+    /* And write the last row, zeros again. */
+    for ( col = 0; col < cols; ++col )
+        PPM_ASSIGN( outputrow[col], 0, 0, 0 );
+    ppm_writeppmrow( stdout, outputrow, cols, maxval, 0 );
+
+    pm_close( ifp );
+    pm_close( stdout );
+
+    exit( 0 );
+}
diff --git a/editor/ppmshadow b/editor/ppmshadow
new file mode 100755
index 00000000..2a32fca0
--- /dev/null
+++ b/editor/ppmshadow
@@ -0,0 +1,273 @@
+#!/usr/bin/perl -w
+
+#                         P P M S H A D O W
+
+#            by John Walker  --  http://www.fourmilab.ch/
+#                          version = 1.2;
+#   --> with minor changes by Bryan Henderson to adapt to Netbpm.  
+#   See above web site for the real John Walker work, named pnmshadow.
+
+#   Bryan Henderson later made some major style changes (use strict, etc) and
+#   eliminated most use of shells.  See Netbpm HISTORY file.
+
+#   Pnmshadow is a brutal sledgehammer implemented in Perl which
+#   adds attractive shadows to images, as often seen in titles
+#   of World-Wide Web pages.  This program does not actually
+#   *do* any image processing--it simply invokes components of
+#   Jef Poskanzer's PBMplus package (which must be present on
+#   the path when this script is run) to bludgeon the source
+#   image into a plausible result.
+#
+#               This program is in the public domain.
+#
+#
+
+use strict;
+require 5.0;
+#  The good open() syntax, with the mode separate from the file name,
+#  came after 5.0.  So did mkdir() with default mode.
+
+my $true=1; my $false=0;
+
+
+sub getDimensions($) {
+    my ($fileName) = @_;
+#-----------------------------------------------------------------------------
+#  Return the dimensions of the Netpbm image in the named file
+#-----------------------------------------------------------------------------
+    my ($width, $height);
+    my $pamfileOutput = `pamfile $fileName`;
+    if ($pamfileOutput =~ m/.*\sP[BGP]M\s.*,\s*(\d*)\sby\s(\d*)/) {
+        ($width, $height) = ($1, $2);
+    } else {
+        die("Unrecognized output from 'pamfile' shell command");
+    }
+    return ($width, $height);
+}    
+
+
+sub makeConvolutionKernel($$) {
+    my ($convkernelfile, $ckern) = @_;
+
+    #   Create convolution kernel file to generate shadow
+    
+    open(OF, ">$convkernelfile") or die();
+    printf(OF "P2\n$ckern $ckern\n%d\n", $ckern * $ckern * 2);
+    my $a = ($ckern * $ckern) + 1;
+    my $i;
+    for ($i = 0; $i < $ckern; $i++) {
+        my $j;
+        for ($j = 0; $j < $ckern; $j++) {
+            printf(OF "%d%s", $a, ($j < ($ckern - 1)) ? " " : "\n");
+        }
+    }
+    close(OF);
+}
+
+
+
+##############################################################################
+#                           MAINLINE
+##############################################################################
+
+
+my $tmpdir = $ENV{TMPDIR} || "/tmp";
+my $ourtmp = "$tmpdir/ppmshadow$$";
+mkdir($ourtmp, 0777) or
+    die("Unable to create directory for temporary files '$ourtmp");
+
+#   Process command line options
+
+
+my $ifile; # Input file name
+my ($xoffset, $yoffset);
+
+my $convolve = 11;                   # Default blur convolution kernel size
+my $keeptemp = $false;               # Don't preserve intermediate files
+my $translucent = $false;            # Default not translucent
+
+while (@ARGV) {
+    my $arg = shift;
+    if ((substr($arg, 0, 1) eq '-') && (length($arg) > 1)) {
+        my $opt;
+        $opt = substr($arg, 1, 1);
+        $opt =~ tr/A-Z/a-z/;
+        if ($opt eq 'b') {        # -B n  --  Blur size
+            if (!defined($convolve = shift)) {
+                die("Argument missing after -b option\n");
+            }
+            if (($convolve < 11) && (($convolve & 1) == 0)) {
+                $convolve++;      # Round up even kernel specification
+            }
+        } elsif ($opt eq 'k') {   # -K  --  Keep temporary files
+            $keeptemp = $true;
+        } elsif ($opt eq 't') {   # -T  --  Translucent image
+            $translucent = $true;
+        } elsif ($opt eq 'x') {   # -X n  --  X offset
+            if (!defined($xoffset = shift)) {
+                die("Argument missing after -x option\n");
+            }
+            if ($xoffset < 0) {
+                $xoffset = -$xoffset;
+            }
+        } elsif ($opt eq 'y') {   # -Y n  --  Y offset
+            if (!defined($yoffset = shift)) {
+                die("Argument missing after -x option\n");
+            }
+            if ($yoffset < 0) {
+                $yoffset = -$xoffset;
+            }
+        }
+    } else {
+        if (defined $ifile) {
+            die("Duplicate input file specification.");
+        }
+        $ifile = $arg;   
+    }
+}
+
+#   Apply defaults for arguments not specified
+
+if (!(defined $xoffset)) {
+    #   Xoffset defaults to half the blur distance
+    $xoffset = int($convolve / 2);
+}
+
+if (!(defined $yoffset)) {
+    #   Yoffset defaults to Xoffset, however specified
+    $yoffset = $xoffset;
+}
+
+# Save the Standard Output open instance so we can use the STDOUT
+# file descriptor to pass files to our children.
+open(OLDOUT, ">&STDOUT");
+select(OLDOUT);  # avoids Perl bug where it says we never use STDOUT 
+
+my $infile = "$ourtmp/infile.ppm";
+
+if (defined($ifile) && $ifile ne "-") {
+    open(STDIN, "<$ifile") or die();
+}
+open(STDOUT, ">$infile") or die("Unable to open '$infile' as STDOUT");
+system("ppmtoppm");
+
+# You would think we could and should close stdin and stdout now, but if
+# we do that, system() pipelines later on fail mysteriously.  They don't
+# seem to be able to open stdin and stdout pipes properly if stdin and 
+# stdout didn't already exist.  2002.09.07 BJH
+
+my ($sourceImageWidth, $sourceImageHeight) = getDimensions($infile);
+
+#   Create an all-background-color image (same size as original image)
+
+my $backgroundfile = "$ourtmp/background.ppm";
+system("pamcut -left=0 -top=0 -width=1 -height=1 $infile | " .
+       "pamscale -xsize=$sourceImageWidth " .
+       "-ysize=$sourceImageHeight >$backgroundfile");
+
+#   Create mask file for background.  It is white wherever there is background
+#   image in the input.
+
+my $bgmaskfile = "$ourtmp/bgmask.pbm";
+system("pamarith -difference $infile $backgroundfile | pnminvert | ppmtopgm " .
+       "| pgmtopbm -thresh -value 1.0 >$bgmaskfile");
+
+my $ckern = $convolve <= 11 ? $convolve : 11;
+
+my $convkernelfile = "$ourtmp/convkernel.pgm";
+
+makeConvolutionKernel($convkernelfile, $ckern);
+
+if ($translucent) {
+
+    #   Convolve the input color image with the kernel
+    #   to create a translucent shadow image.
+
+    system("pnmconvol $convkernelfile $infile >$ourtmp/blurred.ppm");
+    unlink("$convkernelfile") unless $keeptemp;
+    while ($ckern < $convolve) {
+        system("pnmsmooth $ourtmp/blurred.ppm >$ourtmp/convolvedx.ppm");
+        rename("$ourtmp/convolvedx.ppm", "$ourtmp/blurred.ppm");
+        ++$ckern;
+    }
+} else {
+
+    #   Convolve the positive mask with the kernel to create shadow
+ 
+    my $blurredblackshadfile = "$ourtmp/blurredblackshad.pgm";
+    system("pamdepth -quiet 255 $bgmaskfile | " .
+           "pnmconvol $convkernelfile >$blurredblackshadfile");
+    unlink($convkernelfile) unless $keeptemp;
+
+    while ($ckern < $convolve) {
+        my $smoothedfile = "$ourtmp/smoothed.pgm";
+        system("pnmsmooth $blurredblackshadfile >$smoothedfile");
+        rename($smoothedfile, $blurredblackshadfile);
+        ++$ckern;
+    }
+
+    #   Multiply the shadow by the background color
+
+    system("pamarith -multiply $blurredblackshadfile $backgroundfile " .
+           ">$ourtmp/blurred.ppm");
+    unlink($blurredblackshadfile) unless $keeptemp;
+}
+
+#   Cut shadow image down to size of our frame.
+
+my $shadowfile = "$ourtmp/shadow.ppm";
+{
+    my $width = $sourceImageWidth - $xoffset;
+    my $height = $sourceImageHeight - $yoffset;
+    open(STDIN, "<$ourtmp/blurred.ppm") or die();
+    open(STDOUT, ">$shadowfile") or die();
+    system("pamcut", "-left=0", "-top=0", 
+           "-width=$width", "-height=$height");
+}
+unlink("$ourtmp/blurred.ppm") unless $keeptemp;
+
+#   Make mask for foreground
+
+my $fgmaskfile = "$ourtmp/fgmask.pbm";
+open(STDIN, "<$bgmaskfile") or die();
+open(STDOUT, ">$fgmaskfile") or die();
+system("pnminvert");
+
+#   Make image which is just foreground; rest is black.
+
+my $justfgfile = "$ourtmp/justfg.ppm";
+open(STDOUT, ">$justfgfile") or die();
+system("pamarith", "-multiply", $infile, $fgmaskfile);
+
+unlink($fgmaskfile) unless $keeptemp;
+unlink($infile) unless $keeptemp;
+
+#   Paste shadow onto background.
+
+my $shadbackfile = "$ourtmp/shadback.ppm";
+open(STDOUT, ">$shadbackfile") or die();
+system("pnmpaste", "-replace", $shadowfile, $xoffset, $yoffset,
+       $backgroundfile);
+unlink($shadowfile) unless $keeptemp;
+unlink($backgroundfile) unless $keeptemp;
+
+#   Knock out (make black) foreground area
+
+my $allbutfgfile = "$ourtmp/allbutfg.ppm";
+open(STDOUT, ">$allbutfgfile") or die();
+system("pamarith", "-multiply", $shadbackfile, $bgmaskfile);
+
+unlink($shadbackfile) unless $keeptemp;
+unlink($bgmaskfile) unless $keeptemp;
+
+#   Place foreground in blacked out area, send to original Standard Output.
+
+open(STDOUT, ">&OLDOUT");
+
+system("pamarith", "-add", $justfgfile, $allbutfgfile);
+unlink($justfgfile) unless $keeptemp;
+unlink($allbutfgfile) unless $keeptemp;
+
+if (!$keeptemp) {
+    rmdir($ourtmp) or die ("Unable to remove temporary directory '$ourtmp'");
+}
diff --git a/editor/ppmshadow.doc b/editor/ppmshadow.doc
new file mode 100644
index 00000000..1539c708
--- /dev/null
+++ b/editor/ppmshadow.doc
@@ -0,0 +1,627 @@
+<html>
+<head>
+<title>pnmshadow: How it Works</title>
+
+</head>
+
+<body>
+
+<center>
+<h1><img src="figures/how_title.jpg" width=417 height=116 alt="pnmshadow: How it Works"></h1>
+</center>
+
+<hr>
+<p>
+
+This document describes the process, including PBMplus commands
+and the intermediate images they create, by
+which <b><a href="./">pnmshadow</a></b>
+adds black shadows to source images.
+A <a href="how-t.html">companion document</a>
+describes how translucent shadows are created when the
+<b>-t</b> option is specified.
+
+<h3>The Starting Point</h3>
+
+Let's start with the following source image, 536 pixels wide and 141
+pixels high.  We convert the image from whatever form in which
+it was originally created (GIF, JPEG, etc.) to a PPM file before
+processing it with <b>pnmshadow</b>.
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadin.gif" width=536 height=141 alt="Input image">
+</table>
+</center>
+
+<h3>The Blank Background</h3>
+
+We start by determining the size of the input image with
+<b>pnmfile</b> and then constructing an image with the same
+size as the input image consisting entirely of the background
+color, which is defined as the color of the pixel at the upper
+left corner of the source image.  This is performed by the
+command:
+
+<p>
+<pre>
+    pnmcut 0 0 1 1 <em>ifile</em> | pnmscale -xsize <em>xsize</em> -ysize <em>ysize</em> &gt;<em>fname</em>-5.ppm
+</pre>
+<p>
+
+yielding the image:
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt5.gif" width=536 height=141 alt="Blank background image">
+</table>
+</center>
+
+<h3>The Positive Mask</h3>
+
+A positive mask image is created in which all pixels of the background
+color are set to white and all other pixels are black.  This is accomplished
+by subtracting the blank background image from the input (using the
+<tt>-difference</tt> option on <b>pnmarith</b> to avoid clipping at
+zero or the maximum pixel value), then inverting the result and
+thresholding it to a monochrome bitmap.
+
+<p>
+<pre>
+    pnmarith -difference <em>ifile</em> <em>fname</em>-5.ppm | pnminvert | ppmtopgm | pgmtopbm -thresh -value 1.0 &gt;<em>fname</em>-1.ppm
+</pre>
+<p>
+
+This produces the following mask image.
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt1.gif" width=536 height=141 alt="Positive mask image">
+</table>
+</center>
+
+<h3>The Blurred Image</h3>
+
+Since we wish to simulate a shadow from a nearby extended
+light source rather than a sharp shadow as cast by the
+Sun, we need to prepare a blurred version of the original
+image.  If the <b>-t</b> option is not specified on
+<b>pnmshadow</b> the shadow cast by an object of any color
+is always black, so the positive mask serves as the source image
+when preparing the shadow.  A convolution kernel which averages
+the number of pixels specified by the <b>-b</b> option
+(default 11), written into the temporary file <tt><em>fname</em>-2.ppm</tt>
+in ASCII PGM format, and then the blurred image is created with
+the command:
+
+<p>
+<pre>
+    pnmconvol <em>fname</em>-2.ppm <em>fname</em>-1.ppm &gt;<em>fname</em>-3.ppm
+</pre>
+<p>
+
+With the default blur setting of 11 pixels, the blurred image
+below is generated.
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt3.jpg" width=536 height=141 alt="Blurred shadow">
+</table>
+</center>
+
+<h3>Shadow on Background Color</h3>
+
+Having generated the blurred shadow from the monochrome mask image,
+it will consist of pixels ranging from white to black on a white
+background.  In order to preserve the background color in the
+original image, we multiply the shadow by the blank background color
+image created previously.  White pixels take on the background color
+and pixels belonging to the shadow are scaled to be relative to the
+background.
+
+<p>
+<pre>
+    pnmarith -multiply <em>fname</em>-3.ppm <em>fname</em>-5.ppm &gt;<em>fname</em>-10.ppm
+</pre>
+<p>
+
+This yields the following shadow, with the background of the
+original image.
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt10.jpg" width=536 height=141 alt="Shadow with background color">
+</table>
+</center>
+
+<h3>Offset Shadow Clip</h3>
+
+Shadows, even bogus ones like we're generating, usually look best when
+cast by a light source diagonally displaced from the centre of the
+shadow-casting object.  To achieve this effect, we first cut a
+rectangle from the blurred shadow image reduced in size by the
+the number of pixels specified by the <b>-b</b> option
+which default to half the blur (<b>-b</b>) setting.  The
+<em>xsize</em> and <em>ysize</em> arguments in the following
+command are the size of the input image in pixels less the shadow
+displacement in the respective axis.
+
+<p>
+<pre>
+    pnmcut 0 0 <em>xsize</em> <em>ysize</em> <em>fname</em>-10.ppm &gt;<em>fname</em>-4.ppm
+</pre>
+<p>
+
+The shadow clip is the identical to the shadow on background color, but
+smaller by the offset in each direction.
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt4.jpg" width=531 height=136 alt="Offset shadow clip">
+</table>
+</center>
+
+<h3>Offset Shadow</h3>
+
+Now we're ready to assemble the shadow offset by the specified number
+of pixels.  We do this by pasting the image cut in the previous step
+into the blank background, yielding an image the same size as the
+source image with the blurred shadow displaced to the right and
+down.
+
+<p>
+<pre>
+    pnmpaste -replace <em>fname</em>-4.ppm <em>xoffset</em> <em>yoffset</em> <em>fname</em>-5.ppm &gt;<em>fname</em>-6.ppm
+</pre>
+<p>
+
+This gives the following result:
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt6.jpg" width=536 height=141 alt="Offset shadow">
+</table>
+</center>
+
+<h3>Inverse Mask</h3>
+
+In order to stitch everything together, we need an inverse of the
+mask prepared earlier--one where black pixels represent the background
+and all other material is white.  This is easily accomplished by
+running the positive mask through <b>pnminvert</b>:
+
+<p>
+<pre>
+    pnminvert <em>fname</em>-1.ppm &gt;<em>fname</em>-7.ppm
+</pre>
+<p>
+
+yielding:
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt7.gif" width=536 height=141 alt="Inverse mask">
+</table>
+</center>
+
+<h3>Masked Input Image</h3>
+
+Now we use the inverse mask prepared in the previous step to create
+an image containing all non-background pixels from the source image,
+with background pixels set to black.  We simply multiply the
+inverse mask by the source image:
+
+<p>
+<pre>
+    pnmarith -multiply <em>ifile</em> <em>fname</em>-7.ppm &gt;<em>fname</em>-8.ppm
+</pre>
+<p>
+
+<em>et voilà:</em>
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt8.gif" width=536 height=141 alt="Masked Input Image">
+</table>
+</center>
+
+<h3>Shadow with Source Masked</h3>
+
+Our last intermediate step before joining the image with its
+shadow is preparing a shadow image with all non-background pixels
+in the source image set to black.  This ensures that when we add
+the image and the shadow, the shadow will not override any pixel
+in the source image.
+
+<p>
+<pre>
+    pnmarith -multiply <em>fname</em>-6.ppm <em>fname</em>-1.ppm &gt;<em>fname</em>-9.ppm
+</pre>
+<p>
+
+This is accomplished by multiplying the shadow by the positive
+mask image, which sets all non-background pixels in the source
+image to black:
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt9.jpg" width=536 height=141 alt="Shadow with Source Masked">
+</table>
+</center>
+
+<h3>The Final Product</h3>
+
+At long last, we're ready to put together the pieces and deliver
+the result to our ever-patient user.  This amounts simply to 
+adding the masked input image (consisting solely of non-background
+pixels from the original image) to the shadow with source masked
+(in which all source pixels are black):
+
+<p>
+<pre>
+    pnmarith -add <em>fname</em>-8.ppm <em>fname</em>-9.ppm
+</pre>
+<p>
+
+The resulting image, with shadow, is as follows:
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadout.jpg" width=536 height=141 alt="Output: Image with shadow">
+</table>
+</center>
+
+<h3>Smooth Operator</h3>
+
+Since many computer graphics programs create sharp edges on
+text, it's often best to create an image at a greater resolution
+than that used for presentation, then scale it to the final
+resolution with a tool which resamples the image, thus
+minimising jagged edges by averaging adjacent
+pixels.  Using the output of <b>pnmshadow</b> as the starting
+point and scaling to half size with <b>pnmscale</b>, we arrive at
+the following smoothed image, with shadow, ready to adorn a
+Web page:
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadout2.jpg" width=268 height=71 alt="Half scale image with shadow">
+</table>
+</center>
+
+<h4><a href="how-t.html">How it works with translucent shadows</a></h4>
+<h4><a href="./"><b>pnmshadow</b> main page</a></h4>
+
+<p>
+<hr>
+<p>
+<address>
+by <a href="/">John Walker</a><br>
+August 8th, 1997
+</address>
+
+</body>
+</html>
+<html>
+<head>
+<title>pnmshadow: How it Works in Translucent Mode</title>
+
+</head>
+
+<body>
+
+<center>
+<h1><img src="figures/how_title-t.jpg" width=421 height=159 alt="pnmshadow: How it Works in Translucent Mode"></h1>
+</center>
+
+<hr>
+
+<p>
+
+This document describes the process, including PBMplus commands
+and the intermediate images they create, by
+which <b><a href="./">pnmshadow</a></b>
+adds translucent shadows when the <b>-t</b> command line
+option is specified.  A <a href="how.html">companion document</a>
+describes how the default black shadows are generated.
+
+<h3>The Starting Point</h3>
+
+Let's start with the following source image, 536 pixels wide and 141
+pixels high.  We convert the image from whatever form in which
+it was originally created (GIF, JPEG, etc.) to a PPM file before
+processing it with <b>pnmshadow</b>.
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadin.gif" width=536 height=141 alt="Input image">
+</table>
+</center>
+
+<h3>The Blank Background</h3>
+
+We start by determining the size of the input image with
+<b>pnmfile</b> and then constructing an image with the same
+size as the input image consisting entirely of the background
+color, which is defined as the color of the pixel at the upper
+left corner of the source image.  This is performed by the
+command:
+
+<p>
+<pre>
+    pnmcut 0 0 1 1 <em>ifile</em> | pnmscale -xsize <em>xsize</em> -ysize <em>ysize</em> &gt;<em>fname</em>-5.ppm
+</pre>
+<p>
+
+yielding the image:
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt5.gif" width=536 height=141 alt="Blank background image">
+</table>
+</center>
+
+<h3>The Positive Mask</h3>
+
+A positive mask image is created in which all pixels of the background
+color are set to white and all other pixels are black.  This is accomplished
+by subtracting the blank background image from the input (using the
+<tt>-difference</tt> option on <b>pnmarith</b> to avoid clipping at
+zero or the maximum pixel value), then inverting the result and
+thresholding it to a monochrome bitmap.
+
+<p>
+<pre>
+    pnmarith -difference <em>ifile</em> <em>fname</em>-5.ppm | pnminvert | ppmtopgm | pgmtopbm -thresh -value 1.0 &gt;<em>fname</em>-1.ppm
+</pre>
+<p>
+
+This produces the following mask image.
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt1.gif" width=536 height=141 alt="Positive mask image">
+</table>
+</center>
+
+<h3>The Blurred Image</h3>
+
+Since we wish to simulate a shadow from a nearby extended
+light source rather than a sharp shadow as cast by the
+Sun, we need to prepare a blurred version of the original
+image.
+A convolution kernel which averages
+the number of pixels specified by the <b>-b</b> option
+(default 11), written into the temporary file <tt><em>fname</em>-2.ppm</tt>
+in ASCII PGM format, and then the blurred image is created with
+the command:
+
+<p>
+<pre>
+    pnmconvol <em>fname</em>-2.ppm <em>ifile</em> &gt;<em>fname</em>-10.ppm
+</pre>
+<p>
+
+With the default blur setting of 11 pixels, the blurred image
+below is generated.
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt10-t.jpg" width=536 height=141 alt="Blurred shadow">
+</table>
+</center>
+
+<h3>Offset Shadow Clip</h3>
+
+Shadows, even bogus ones like we're generating, usually look best when
+cast by a light source diagonally displaced from the centre of the
+shadow-casting object.  To achieve this effect, we first cut a
+rectangle from the blurred shadow image reduced in size by the
+the number of pixels specified by the <b>-b</b> option
+which default to half the blur (<b>-b</b>) setting.  The
+<em>xsize</em> and <em>ysize</em> arguments in the following
+command are the size of the input image in pixels less the shadow
+displacement in the respective axis.
+
+<p>
+<pre>
+    pnmcut 0 0 <em>xsize</em> <em>ysize</em> <em>fname</em>-10.ppm &gt;<em>fname</em>-4.ppm
+</pre>
+<p>
+
+The shadow clip is the identical to the shadow on background color, but
+smaller by the offset in each direction.
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt4-t.jpg" width=531 height=136 alt="Offset shadow clip">
+</table>
+</center>
+
+<h3>Offset Shadow</h3>
+
+Now we're ready to assemble the shadow offset by the specified number
+of pixels.  We do this by pasting the image cut in the previous step
+into the blank background, yielding an image the same size as the
+source image with the blurred shadow displaced to the right and
+down.
+
+<p>
+<pre>
+    pnmpaste -replace <em>fname</em>-4.ppm <em>xoffset</em> <em>yoffset</em> <em>fname</em>-5.ppm &gt;<em>fname</em>-6.ppm
+</pre>
+<p>
+
+This gives the following result:
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt6-t.jpg" width=536 height=141 alt="Offset shadow">
+</table>
+</center>
+
+<h3>Inverse Mask</h3>
+
+In order to stitch everything together, we need an inverse of the
+mask prepared earlier--one where black pixels represent the background
+and all other material is white.  This is easily accomplished by
+running the positive mask through <b>pnminvert</b>:
+
+<p>
+<pre>
+    pnminvert <em>fname</em>-1.ppm &gt;<em>fname</em>-7.ppm
+</pre>
+<p>
+
+yielding:
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt7.gif" width=536 height=141 alt="Inverse mask">
+</table>
+</center>
+
+<h3>Masked Input Image</h3>
+
+Now we use the inverse mask prepared in the previous step to create
+an image containing all non-background pixels from the source image,
+with background pixels set to black.  We simply multiply the
+inverse mask by the source image:
+
+<p>
+<pre>
+    pnmarith -multiply <em>ifile</em> <em>fname</em>-7.ppm &gt;<em>fname</em>-8.ppm
+</pre>
+<p>
+
+<em>et voilà:</em>
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt8.gif" width=536 height=141 alt="Masked Input Image">
+</table>
+</center>
+
+<h3>Shadow with Source Masked</h3>
+
+Our last intermediate step before joining the image with its
+shadow is preparing a shadow image with all non-background pixels
+in the source image set to black.  This ensures that when we add
+the image and the shadow, the shadow will not override any pixel
+in the source image.
+
+<p>
+<pre>
+    pnmarith -multiply <em>fname</em>-6.ppm <em>fname</em>-1.ppm &gt;<em>fname</em>-9.ppm
+</pre>
+<p>
+
+This is accomplished by multiplying the shadow by the positive
+mask image, which sets all non-background pixels in the source
+image to black:
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadt9-t.jpg" width=536 height=141 alt="Shadow with Source Masked">
+</table>
+</center>
+
+<h3>The Final Product</h3>
+
+At long last, we're ready to put together the pieces and deliver
+the image to our ever-patient user.  This amounts simply to 
+adding the masked input image (consisting solely of non-background
+pixels from the original image) to the shadow with source masked
+(in which all source pixels are black):
+
+<p>
+<pre>
+    pnmarith -add <em>fname</em>-8.ppm <em>fname</em>-9.ppm
+</pre>
+<p>
+
+The resulting image, with shadow, is as follows:
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadout-t.jpg" width=536 height=141 alt="Output: Image with translucent shadow">
+</table>
+</center>
+
+<h3>Smooth Operator</h3>
+
+Since many computer graphics programs create sharp edges on
+text, it's often best to create an image at a greater resolution
+than that used for presentation, then scale it to the final
+resolution with a tool which resamples the image, thus
+minimising jagged edges by averaging adjacent
+pixels.  Using the output of <b>pnmshadow</b> as the starting
+point and scaling to half size with <b>pnmscale</b>, we arrive at
+the following smoothed image, with shadow, ready to adorn a
+Web page:
+
+<p>
+<center>
+<table border=5>
+<tr><td>
+<img src="figures/shadout2-t.jpg" width=268 height=71 alt="Half scale image with translucent shadow">
+</table>
+</center>
+
+<h4><a href="how.html">How it works with black shadows</a></h4>
+<h4><a href="./"><b>pnmshadow</b> main page</a></h4>
+
+<p>
+<hr>
+<p>
+<address>
+by <a href="/">John Walker</a><br>
+August 8th, 1997
+</address>
+
+</body>
+</html>
diff --git a/editor/ppmshift.c b/editor/ppmshift.c
new file mode 100644
index 00000000..1f8a599b
--- /dev/null
+++ b/editor/ppmshift.c
@@ -0,0 +1,137 @@
+
+/*********************************************************************/
+/* ppmshift -  shift lines of a picture left or right by x pixels    */
+/* Frank Neumann, October 1993                                       */
+/* V1.1 16.11.1993                                                   */
+/*                                                                   */
+/* version history:                                                  */
+/* V1.0    11.10.1993  first version                                 */
+/* V1.1    16.11.1993  Rewritten to be NetPBM.programming conforming */
+/*********************************************************************/
+
+#include "ppm.h"
+
+/* global variables */
+#ifdef AMIGA
+static char *version = "$VER: ppmshift 1.1 (16.11.93)"; /* Amiga version identification */
+#endif
+
+/**************************/
+/* start of main function */
+/**************************/
+int main(argc, argv)
+int argc;
+char *argv[];
+{
+	FILE* ifp;
+	time_t timenow;
+	int argn, rows, cols, format, i = 0, j = 0;
+	pixel *srcrow, *destrow;
+	pixel *pP = NULL, *pP2 = NULL;
+	pixval maxval;
+	int shift, nowshift;
+	const char * const usage = "shift [ppmfile]\n        shift: maximum number of pixels to shift a line by\n";
+
+	/* parse in 'default' parameters */
+	ppm_init(&argc, argv);
+
+	argn = 1;
+
+	/* parse in shift number */
+	if (argn == argc)
+		pm_usage(usage);
+	if (sscanf(argv[argn], "%d", &shift) != 1)
+		pm_usage(usage);
+	if (shift < 0)
+		pm_error("shift factor must be 0 or more");
+	++argn;
+
+	/* parse in filename (if present, stdin otherwise) */
+	if (argn != argc)
+	{
+		ifp = pm_openr(argv[argn]);
+		++argn;
+	}
+	else
+		ifp = stdin;
+
+	if (argn != argc)
+		pm_usage(usage);
+
+	/* read first data from file */
+	ppm_readppminit(ifp, &cols, &rows, &maxval, &format);
+
+	if (shift > cols)
+    {
+		shift = cols;
+        pm_message("shift amount is larger than picture width - reset to %d", shift);
+    }
+
+	/* no error checking required here, ppmlib does it all for us */
+	srcrow = ppm_allocrow(cols);
+
+	/* allocate a row of pixel data for the new pixels */
+	destrow = ppm_allocrow(cols);
+
+	ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+	/* get time of day to feed the random number generator */
+	timenow = time(NULL);
+	srand(timenow);
+
+	/** now do the shifting **/
+	/* the range by which a line is shifted lays in the range from */
+	/* -shift/2 .. +shift/2 pixels; however, within this range it is */
+    /* randomly chosen */
+	for (i = 0; i < rows; i++)
+	{
+		if (shift != 0)
+			nowshift = (rand() % (shift+1)) - ((shift+1) / 2);
+		else
+			nowshift = 0;
+
+		ppm_readppmrow(ifp, srcrow, cols, maxval, format);
+
+		pP = srcrow;
+		pP2 = destrow;
+
+		/* if the shift value is less than zero, we take the original pixel line and */
+		/* copy it into the destination line translated to the left by x pixels. The */
+        /* empty pixels on the right end of the destination line are filled up with  */
+		/* the pixel that is the right-most in the original pixel line.              */
+		if (nowshift < 0)
+		{
+			pP+= abs(nowshift);
+			for (j = 0; j < cols; j++)
+			{
+				PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
+				pP2++;
+                if (j < (cols+nowshift)-1)
+					pP++;
+			}
+		}
+		/* if the shift value is 0 or positive, the first <nowshift> pixels of the */
+		/* destination line are filled with the first pixel from the source line,  */
+		/* and the rest of the source line is copied to the dest line              */
+		else
+		{
+			for (j = 0; j < cols; j++)
+			{
+				PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
+				pP2++;
+                if (j >= nowshift)
+					pP++;
+			}
+		}
+
+		/* write out one line of graphic data */
+		ppm_writeppmrow(stdout, destrow, cols, maxval, 0);
+	}
+
+	pm_close(ifp);
+	ppm_freerow(srcrow);
+	ppm_freerow(destrow);
+
+	exit(0);
+}
+
diff --git a/editor/ppmspread.c b/editor/ppmspread.c
new file mode 100644
index 00000000..569d1266
--- /dev/null
+++ b/editor/ppmspread.c
@@ -0,0 +1,127 @@
+/*********************************************************************/
+/* ppmspread -  randomly displace a PPM's pixels by a certain amount */
+/* Frank Neumann, October 1993                                       */
+/* V1.1 16.11.1993                                                   */
+/*                                                                   */
+/* version history:                                                  */
+/* V1.0 12.10.1993    first version                                  */
+/* V1.1 16.11.1993    Rewritten to be NetPBM.programming conforming  */
+/*********************************************************************/
+
+#include <string.h>
+#include "ppm.h"
+
+/* global variables */
+#ifdef AMIGA
+static char *version = "$VER: ppmspread 1.1 (16.11.93)"; /* Amiga version identification */
+#endif
+
+/**************************/
+/* start of main function */
+/**************************/
+int main(argc, argv)
+int argc;
+char *argv[];
+{
+	FILE* ifp;
+	int argn, rows, cols, i, j;
+	int xdis, ydis, xnew, ynew;
+	pixel **destarray, **srcarray;
+	pixel *pP, *pP2;
+	pixval maxval;
+	pixval r1, g1, b1;
+	int amount;
+	time_t timenow;
+	const char * const usage = "amount [ppmfile]\n        amount: # of pixels to displace a pixel by at most\n";
+
+	/* parse in 'default' parameters */
+	ppm_init(&argc, argv);
+
+	argn = 1;
+
+	/* parse in amount & seed */
+	if (argn == argc)
+		pm_usage(usage);
+	if (sscanf(argv[argn], "%d", &amount) != 1)
+		pm_usage(usage);
+	if (amount < 0)
+		pm_error("amount should be a positive number");
+	++argn;
+
+	/* parse in filename (if present, stdin otherwise) */
+	if (argn != argc)
+	{
+		ifp = pm_openr(argv[argn]);
+		++argn;
+	}
+	else
+		ifp = stdin;
+
+	if (argn != argc)
+		pm_usage(usage);
+
+	/* read entire picture into buffer */
+	srcarray = ppm_readppm(ifp, &cols, &rows, &maxval);
+
+	/* allocate an entire picture buffer for dest picture */
+	destarray = ppm_allocarray(cols, rows);
+
+	/* clear out the buffer */
+	for (i=0; i < rows; i++)
+		memset(destarray[i], 0, cols * sizeof(pixel));
+
+	/* set seed for random number generator */
+	/* get time of day to feed the random number generator */
+	timenow = time(NULL);
+	srand(timenow);
+
+	/* start displacing pixels */
+	for (i = 0; i < rows; i++)
+	{
+		pP = srcarray[i];
+
+		for (j = 0; j < cols; j++)
+		{
+			xdis = (rand() % (amount+1)) - ((amount+1) / 2);
+			ydis = (rand() % (amount+1)) - ((amount+1) / 2);
+
+			xnew = j + xdis;
+			ynew = i + ydis;
+
+			/* only set the displaced pixel if it's within the bounds of the image */
+			if (xnew >= 0 && xnew < cols && ynew >= 0 && ynew < rows)
+			{
+				/* displacing a pixel is accomplished by swapping it with another */
+				/* pixel in its vicinity - so, first store other pixel's RGB      */
+                pP2 = srcarray[ynew] + xnew;
+				r1 = PPM_GETR(*pP2);
+				g1 = PPM_GETG(*pP2);
+				b1 = PPM_GETB(*pP2);
+				/* set second pixel to new value */
+				pP2 = destarray[ynew] + xnew;
+				PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
+
+				/* now, set first pixel to (old) value of second */
+				pP2 = destarray[i] + j;
+				PPM_ASSIGN(*pP2, r1, g1, b1);
+			}
+			else
+			{
+                /* displaced pixel is out of bounds; leave the old pixel there */
+                pP2 = destarray[i] + j;
+				PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
+			}
+			pP++;
+		}
+	}
+
+	/* write out entire dest picture in one go */
+	ppm_writeppm(stdout, destarray, cols, rows, maxval, 0);
+
+	pm_close(ifp);
+	ppm_freearray(srcarray, rows);
+	ppm_freearray(destarray, rows);
+
+	exit(0);
+}
+
diff --git a/editor/ppmtv.c b/editor/ppmtv.c
new file mode 100644
index 00000000..da25102a
--- /dev/null
+++ b/editor/ppmtv.c
@@ -0,0 +1,105 @@
+
+/*********************************************************************/
+/* ppmtv -  make a 'look-alike ntsc' picture from a PPM file       */
+/* Frank Neumann, October 1993                                       */
+/* V1.1 16.11.1993                                                   */
+/*                                                                   */
+/* version history:                                                  */
+/* V1.0 12.10.1993    first version                                  */
+/* V1.1 16.11.1993    Rewritten to be NetPBM.programming conforming  */
+/*********************************************************************/
+
+#include "ppm.h"
+
+/**************************/
+/* start of main function */
+/**************************/
+int main(argc, argv)
+int argc;
+char *argv[];
+{
+	FILE* ifp;
+	int argn, rows, cols, format, i = 0, j = 0;
+	pixel *srcrow, *destrow;
+	pixel *pP = NULL, *pP2 = NULL;
+	pixval maxval;
+	double dimfactor;
+	long longfactor;
+	const char * const usage = "dimfactor [ppmfile]\n        dimfactor: 0.0 = total blackness, 1.0 = original picture\n";
+
+	/* parse in 'default' parameters */
+	ppm_init(&argc, argv);
+
+	argn = 1;
+
+	/* parse in dim factor */
+	if (argn == argc)
+		pm_usage(usage);
+	if (sscanf(argv[argn], "%lf", &dimfactor) != 1)
+		pm_usage(usage);
+	if (dimfactor < 0.0 || dimfactor > 1.0)
+		pm_error("dim factor must be in the range from 0.0 to 1.0 ");
+	++argn;
+
+	/* parse in filename (if present, stdin otherwise) */
+	if (argn != argc)
+	{
+		ifp = pm_openr(argv[argn]);
+		++argn;
+	}
+	else
+		ifp = stdin;
+
+	if (argn != argc)
+		pm_usage(usage);
+
+	/* read first data from file */
+	ppm_readppminit(ifp, &cols, &rows, &maxval, &format);
+
+	/* no error checking required here, ppmlib does it all for us */
+	srcrow = ppm_allocrow(cols);
+
+	longfactor = (long)(dimfactor * 65536);
+
+	/* allocate a row of pixel data for the new pixels */
+	destrow = ppm_allocrow(cols);
+
+	ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+	/** now do the ntsc'ing (actually very similar to ppmdim) **/
+	for (i = 0; i < rows; i++)
+	{
+		ppm_readppmrow(ifp, srcrow, cols, maxval, format);
+
+		pP = srcrow;
+		pP2 = destrow;
+
+		for (j = 0; j < cols; j++)
+		{
+			/* every alternating row is left in unchanged condition */
+			if (i & 1)
+			{
+				PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
+			}
+			/* and the other lines are dimmed to the specified factor */
+			else
+			{
+				PPM_ASSIGN(*pP2, (PPM_GETR(*pP) * longfactor) >> 16,
+								 (PPM_GETG(*pP) * longfactor) >> 16,
+								 (PPM_GETB(*pP) * longfactor) >> 16);
+			}
+			pP++;
+			pP2++;
+		}
+
+		/* write out one line of graphic data */
+		ppm_writeppmrow(stdout, destrow, cols, maxval, 0);
+	}
+
+	pm_close(ifp);
+	ppm_freerow(srcrow);
+	ppm_freerow(destrow);
+
+	exit(0);
+}
+
diff --git a/generator/Makefile b/generator/Makefile
new file mode 100644
index 00000000..52de9b10
--- /dev/null
+++ b/generator/Makefile
@@ -0,0 +1,40 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = generator
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+# We tend to separate out the build targets so that we don't have
+# any more dependencies for a given target than it really needs.
+# That way, if there is a problem with a dependency, we can still
+# successfully build all the stuff that doesn't depend upon it.
+# This package is so big, it's useful even when some parts won't 
+# build.
+
+PORTBINARIES = pamgauss pamgradient pamseq pamstereogram \
+	       pbmpage pbmmake pbmtext pbmtextps pbmupc \
+	       pgmcrater pgmkernel pgmmake pgmnoise pgmramp \
+	       ppmcie ppmcolors ppmforge ppmmake ppmpat ppmrough ppmwheel \
+
+# We don't include programs that have special library dependencies in the
+# merge scheme, because we don't want those dependencies to prevent us
+# from building all the other programs.
+
+NOMERGEBINARIES = 
+MERGEBINARIES = $(PORTBINARIES)
+
+
+BINARIES = $(MERGEBINARIES) $(NOMERGEBINARIES)
+SCRIPTS = ppmrainbow
+
+OBJECTS = $(BINARIES:%=%.o)
+
+MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2)
+
+.PHONY: all
+all: $(BINARIES)
+
+include $(SRCDIR)/Makefile.common
diff --git a/generator/pamgauss.c b/generator/pamgauss.c
new file mode 100644
index 00000000..eb5fa3fe
--- /dev/null
+++ b/generator/pamgauss.c
@@ -0,0 +1,207 @@
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "pam.h"
+
+#define true (1)
+#define false (0)
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    unsigned int width;
+    unsigned int height;
+    sample maxval;
+    float sigma;
+    const char * tupletype;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+  Convert program invocation arguments (argc,argv) into a format the 
+  program can use easily, struct cmdlineInfo.  Validate arguments along
+  the way and exit program with message if invalid.
+
+  Note that some string information we return as *cmdlineP is in the storage 
+  argv[] points to.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int tupletypeSpec, maxvalSpec, sigmaSpec;
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "tupletype",  OPT_STRING, &cmdlineP->tupletype, 
+            &tupletypeSpec,     0);
+    OPTENT3(0,   "maxval",     OPT_UINT,   &cmdlineP->maxval, 
+            &maxvalSpec,        0);
+    OPTENT3(0,   "sigma",      OPT_FLOAT,  &cmdlineP->sigma, 
+            &sigmaSpec,        0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!tupletypeSpec)
+        cmdlineP->tupletype = "";
+    else {
+        struct pam pam;
+        if (strlen(cmdlineP->tupletype)+1 > sizeof(pam.tuple_type))
+            pm_error("The tuple type you specified is too long.  "
+                     "Maximum %d characters.", sizeof(pam.tuple_type)-1);
+    }        
+
+    if (!sigmaSpec)
+        pm_error("You must specify the -sigma option.");
+    else if (cmdlineP->sigma <= 0.0)
+        pm_error("-sigma must be positive.  You specified %f", 
+                 cmdlineP->sigma);
+
+    if (!maxvalSpec)
+        cmdlineP->maxval = PNM_MAXMAXVAL;
+    else {
+        if (cmdlineP->maxval > PNM_OVERALLMAXVAL)
+            pm_error("The maxval you specified (%u) is too big.  "
+                     "Maximum is %u", (unsigned int) cmdlineP->maxval, 
+                     PNM_OVERALLMAXVAL);
+        if (cmdlineP->maxval < 1)
+            pm_error("-maxval must be at least 1");
+    }    
+
+    if (argc-1 < 2)
+        pm_error("Need two arguments: width and height.");
+    else if (argc-1 > 2)
+        pm_error("Only two argumeents allowed: with and height.  "
+                 "You specified %d", argc-1);
+    else {
+        cmdlineP->width = atoi(argv[1]);
+        cmdlineP->height = atoi(argv[2]);
+        if (cmdlineP->width <= 0)
+            pm_error("width argument must be a positive number.  You "
+                     "specified '%s'", argv[1]);
+        if (cmdlineP->height <= 0)
+            pm_error("height argument must be a positive number.  You "
+                     "specified '%s'", argv[2]);
+    }
+}
+
+
+
+static double
+distFromCenter(struct pam * const pamP,
+               int          const col,
+               int          const row) {
+
+    return sqrt(SQR(col - pamP->width/2) + SQR(row - pamP->height/2));
+}
+
+
+
+static double
+gauss(double const arg,
+      double const sigma) {
+/*----------------------------------------------------------------------------
+   Compute the value of the gaussian function with sigma parameter 'sigma'
+   and mu parameter zero of argument 'arg'.
+-----------------------------------------------------------------------------*/
+    double const pi = 3.14159;
+    double const coefficient = 1 / (sigma * sqrt(2*pi));
+    double const exponent = - SQR(arg-0) / (2 * SQR(sigma));
+
+    return coefficient * exp(exponent);
+}
+
+
+
+static double
+imageNormalizer(struct pam * const pamP,
+                double       const sigma) {
+/*----------------------------------------------------------------------------
+   Compute the value that has to be multiplied by the value of the 
+   one-dimensional gaussian function of the distance from center in
+   order to get the value for a normalized two-dimensional gaussian
+   function.  Normalized here means that the volume under the whole
+   curve is 1, just as the area under a whole one-dimensional gaussian
+   function is 1.
+-----------------------------------------------------------------------------*/
+    double volume;
+
+    unsigned int row;
+
+    volume = 0.0;   /* initial value */
+
+    for (row = 0; row < pamP->height; ++row) {
+        unsigned int col;
+        for (col = 0; col < pamP->width; ++col)
+            volume += gauss(distFromCenter(pamP, col, row), sigma);
+    }
+    return 1.0 / volume;
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    struct cmdlineInfo cmdline;
+    struct pam pam;
+    int row;
+    double normalizer;
+    tuplen * tuplerown;
+    
+    pnm_init(&argc, argv);
+   
+    parseCommandLine(argc, argv, &cmdline);
+
+    pam.size        = sizeof(pam);
+    pam.len         = PAM_STRUCT_SIZE(tuple_type);
+    pam.file        = stdout;
+    pam.format      = PAM_FORMAT;
+    pam.plainformat = 0;
+    pam.width       = cmdline.width;
+    pam.height      = cmdline.height;
+    pam.depth       = 1;
+    pam.maxval      = cmdline.maxval;
+    strcpy(pam.tuple_type, cmdline.tupletype);
+
+    normalizer = imageNormalizer(&pam, cmdline.sigma);
+    
+    pnm_writepaminit(&pam);
+   
+    tuplerown = pnm_allocpamrown(&pam);
+
+    for (row = 0; row < pam.height; ++row) {
+        int col;
+        for (col = 0; col < pam.width; ++col) {
+            double const gauss1 = gauss(distFromCenter(&pam, col, row),
+                                        cmdline.sigma);
+
+            tuplerown[col][0] = gauss1 * normalizer;
+        }
+        pnm_writepamrown(&pam, tuplerown);
+    }
+    
+    pnm_freepamrown(tuplerown);
+
+    return 0;
+}
diff --git a/generator/pamgradient.c b/generator/pamgradient.c
new file mode 100644
index 00000000..7717bf4b
--- /dev/null
+++ b/generator/pamgradient.c
@@ -0,0 +1,215 @@
+#include <string.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+
+
+struct cmdlineInfo {
+    tuple colorTopLeft;
+    tuple colorTopRight;
+    tuple colorBottomLeft;
+    tuple colorBottomRight;
+    unsigned depth;
+    unsigned int cols;
+    unsigned int rows;
+    unsigned int maxval;
+};
+
+static void
+parseCommandLine(int argc, char **argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+  Convert program invocation arguments (argc,argv) into a format the 
+  program can use easily, struct cmdlineInfo.  Validate arguments along
+  the way and exit program with message if invalid.
+
+  Note that some string information we return as *cmdlineP is in the storage 
+  argv[] points to.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int maxvalSpec;
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;
+    OPTENT3(0, "maxval", OPT_UINT, &cmdlineP->maxval, &maxvalSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!maxvalSpec)
+        cmdlineP->maxval = 255;
+    else {
+        if (cmdlineP->maxval > PAM_OVERALL_MAXVAL)
+            pm_error("The value you specified for -maxval (%u) is too big.  "
+                     "Max allowed is %u", cmdlineP->maxval,
+                     PAM_OVERALL_MAXVAL);
+        
+        if (cmdlineP->maxval < 1)
+            pm_error("You cannot specify 0 for -maxval");
+    }    
+
+    if (argc-1 != 6) {
+        pm_error("Need 6 arguments: colorTopLeft, colorTopRight, "
+                 "colorBottomLeft, colorBottomRight, width, height"); 
+    } else {
+        cmdlineP->colorTopLeft     = pnm_parsecolor(argv[1], cmdlineP->maxval);
+        cmdlineP->colorTopRight    = pnm_parsecolor(argv[2], cmdlineP->maxval);
+        cmdlineP->colorBottomLeft  = pnm_parsecolor(argv[3], cmdlineP->maxval);
+        cmdlineP->colorBottomRight = pnm_parsecolor(argv[4], cmdlineP->maxval);
+        cmdlineP->cols = atoi(argv[5]);
+        cmdlineP->rows = atoi(argv[6]);
+        if (cmdlineP->cols <= 0)
+            pm_error("width argument must be a positive number.  You "
+                     "specified '%s'", argv[5]);
+        if (cmdlineP->rows <= 0)
+            pm_error("height argument must be a positive number.  You "
+                     "specified '%s'", argv[6]);
+    }
+}
+
+
+
+static void
+freeCmdline(struct cmdlineInfo const cmdline) {
+
+    pnm_freepamtuple(cmdline.colorTopLeft);
+    pnm_freepamtuple(cmdline.colorTopRight);
+    pnm_freepamtuple(cmdline.colorBottomLeft);
+    pnm_freepamtuple(cmdline.colorBottomRight);
+}
+
+
+
+static void
+interpolate(struct pam * const pamP,
+            tuple *      const tuplerow,
+            tuple        const first,
+            tuple        const last) {
+
+    unsigned int plane;
+    
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        int const spread = last[plane] - first[plane];
+
+        int col;
+
+        if (INT_MAX / pamP->width < abs(spread))
+            pm_error("Arithmetic overflow.  You must reduce the width of "
+                     "the image (now %u) or the range of color values "
+                     "(%u in plane %u) so that their "
+                     "product is less than %d",
+                     pamP->width, abs(spread), plane, INT_MAX);
+
+        for (col = 0; col < pamP->width; ++col)
+            tuplerow[col][plane] =
+                first[plane] + (spread * col / (int)pamP->width);
+    }
+}
+
+
+
+static int
+isgray(struct pam * const pamP,
+       tuple        const color) {
+
+    return (pamP->depth == 1)
+        || ((pamP->depth == 3)
+        && (color[PAM_RED_PLANE] == color[PAM_GRN_PLANE])
+        && (color[PAM_RED_PLANE] == color[PAM_BLU_PLANE])); 
+}
+
+
+
+static tuple *
+createEdge(const struct pam * const pamP,
+           tuple              const topColor,
+           tuple              const bottomColor) {
+/*----------------------------------------------------------------------------
+   Create a left or right edge, interpolating from top to bottom.
+-----------------------------------------------------------------------------*/
+    struct pam interpPam;
+    tuple * tupleRow;
+
+    interpPam = *pamP;  /* initial value */
+    interpPam.width = pamP->height;
+    interpPam.height = 1;
+
+    tupleRow = pnm_allocpamrow(&interpPam);
+
+    interpolate(&interpPam, tupleRow, topColor, bottomColor);
+
+    return tupleRow;
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    struct pam pam;
+    tuple * tupleRow;
+    tuple * leftEdge;
+    tuple * rightEdge;
+    unsigned int row;
+    
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    pam.size             = sizeof pam;
+    pam.len              = PAM_STRUCT_SIZE(tuple_type);
+    pam.file             = stdout;
+    pam.plainformat      = 0;
+    pam.width            = cmdline.cols;
+    pam.height           = cmdline.rows;
+    pam.maxval           = cmdline.maxval;
+    pam.bytes_per_sample = pnm_bytespersample(pam.maxval);
+    pam.format           = PAM_FORMAT;
+    if (isgray(&pam, cmdline.colorTopLeft)
+            && isgray(&pam, cmdline.colorTopRight)
+            && isgray(&pam, cmdline.colorBottomLeft)
+            && isgray(&pam, cmdline.colorBottomRight)) {
+        pam.depth = 1;
+        strcpy(pam.tuple_type, PAM_PGM_TUPLETYPE);
+    } else {
+        pam.depth = 3;
+        strcpy(pam.tuple_type, PAM_PPM_TUPLETYPE);
+    }
+
+    pnm_writepaminit(&pam);
+    
+    tupleRow = pnm_allocpamrow(&pam);
+
+    leftEdge  = createEdge(&pam,
+                           cmdline.colorTopLeft, cmdline.colorBottomLeft);
+    rightEdge = createEdge(&pam,
+                           cmdline.colorTopRight, cmdline.colorBottomRight);
+
+    /* interpolate each row between the left edge and the right edge */
+    for (row = 0; row < pam.height; ++row) {
+        interpolate(&pam, tupleRow, leftEdge[row], rightEdge[row]);
+        pnm_writepamrow(&pam, tupleRow); 
+    }
+
+    pm_close(stdout);
+    pnm_freepamrow(rightEdge);
+    pnm_freepamrow(leftEdge);
+    pnm_freepamrow(tupleRow);
+
+    freeCmdline(cmdline);
+
+    return 0;
+}
diff --git a/generator/pamseq.c b/generator/pamseq.c
new file mode 100644
index 00000000..58419d12
--- /dev/null
+++ b/generator/pamseq.c
@@ -0,0 +1,202 @@
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "pam.h"
+#include "shhopt.h"
+
+#define true (1)
+#define false (0)
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    unsigned int depth;
+    sample maxval;
+    const char * tupletype;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+  Convert program invocation arguments (argc,argv) into a format the 
+  program can use easily, struct cmdlineInfo.  Validate arguments along
+  the way and exit program with message if invalid.
+
+  Note that some string information we return as *cmdlineP is in the storage 
+  argv[] points to.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int tupletypeSpec;
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "tupletype",  OPT_STRING, &cmdlineP->tupletype, 
+            &tupletypeSpec,     0);
+
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!tupletypeSpec)
+        cmdlineP->tupletype = "";
+    else {
+        struct pam pam;
+        if (strlen(cmdlineP->tupletype)+1 > sizeof(pam.tuple_type))
+            pm_error("The tuple type you specified is too long.  "
+                     "Maximum %d characters.", sizeof(pam.tuple_type)-1);
+    }        
+
+    if (argc-1 < 2)
+        pm_error("Need two arguments: depth and maxval.");
+    else if (argc-1 > 2)
+        pm_error("Only two argumeents allowed: depth and maxval.  "
+                 "You specified %d", argc-1);
+    else {
+        cmdlineP->depth = atoi(argv[1]);
+        cmdlineP->maxval = atoi(argv[2]);
+        if (cmdlineP->depth <= 0)
+            pm_error("depth argument must be a positive number.  You "
+                     "specified '%s'", argv[1]);
+        if (cmdlineP->maxval <= 0)
+            pm_error("maxval argument must be a positive number.  You "
+                     "specified '%s'", argv[2]);
+        if (cmdlineP->maxval > PNM_OVERALLMAXVAL)
+            pm_error("The maxval you specified (%u) is too big.  "
+                     "Maximum is %u", (unsigned int) cmdlineP->maxval, 
+                     PNM_OVERALLMAXVAL);
+        if (pm_maxvaltobits(cmdlineP->maxval) + 
+            pm_maxvaltobits(cmdlineP->depth-1) > sizeof(unsigned int)*8)
+            pm_error("The maxval (%u) and depth (%u) you specified result "
+                     "in a larger number of tuples than this program can "
+                     "handle (roughly %u)", 
+                     (unsigned int) cmdlineP->maxval, cmdlineP->depth,
+                     (unsigned int) -1);
+    }
+}
+
+
+
+static unsigned int
+powint(unsigned int base, unsigned int exponent) {
+/*----------------------------------------------------------------------------
+   This is standard pow(), but for integers and optimized for small
+   exponents.
+-----------------------------------------------------------------------------*/
+    unsigned int result;
+    unsigned int i;
+
+    result = 1;  /* initial value */
+    for (i = 0; i < exponent; ++i) 
+        result *= base;
+
+    return(result);
+}
+
+
+static void
+permuteHigherPlanes(struct pam const pam, int const nextplane, 
+                    tuple * const tuplerow, int * const colP, 
+                    tuple const lowerPlanes) {
+/*----------------------------------------------------------------------------
+   Create all the possible permutations of tuples whose lower-numbered planes
+   contain the values from 'lowerPlanes'.  I.e. vary the higher-numbered
+   planes between zero and maxval.
+
+   Write them sequentially into *tuplerow, starting at *colP.  Adjust
+   *colP to next the column after the ones we write.
+
+   lower-numbered means with plane numbers less than 'nextplane'.
+
+   We modify 'lowerPlanes' in the higher planes to undefined values.
+-----------------------------------------------------------------------------*/
+    if (nextplane == pam.depth - 1) {
+        sample value;
+        for (value = 0; value <= pam.maxval; ++value) {
+            unsigned int plane;
+            for (plane = 0; plane < nextplane; ++plane)
+                tuplerow[*colP][plane] = lowerPlanes[plane];
+            tuplerow[*colP][nextplane] = value;
+            ++(*colP);
+        }
+    } else {
+        sample value;
+
+        for (value = 0; value <= pam.maxval; ++value) {
+            /* We do something sleazy here and use Caller's lowerPlanes[]
+               variable as a local variable, modifying it in the higher
+               plane positions.  That's just for speed.
+            */
+            lowerPlanes[nextplane] = value;
+
+            permuteHigherPlanes(pam, nextplane+1, tuplerow, colP, lowerPlanes);
+        }
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    struct cmdlineInfo cmdline;
+    struct pam pam;
+    int col;
+    tuple lowerPlanes;
+        /* This is working storage passed to permuteHigherPlanes(),
+           which we call.  Note that because we always pass zero as the
+           "planes" argument to permuteHigherPlanes(), none of the 
+           "lower planes" value is defined as an input to 
+           permuteHigherPlanes().
+        */
+    tuple * tuplerow;
+    
+    pnm_init(&argc, argv);
+   
+    parseCommandLine(argc, argv, &cmdline);
+
+    pam.size = sizeof(pam);
+    pam.len = PAM_STRUCT_SIZE(tuple_type);
+    pam.file = stdout;
+    pam.format = PAM_FORMAT;
+    pam.plainformat = 0;
+    pam.width = powint(cmdline.maxval+1, cmdline.depth);
+    pam.height = 1;
+    pam.depth = cmdline.depth;
+    pam.maxval = cmdline.maxval;
+    strcpy(pam.tuple_type, cmdline.tupletype);
+
+    pnm_writepaminit(&pam);
+   
+    tuplerow = pnm_allocpamrow(&pam);
+
+    lowerPlanes = pnm_allocpamtuple(&pam);
+
+    col = 0;
+
+    permuteHigherPlanes(pam, 0, tuplerow, &col, lowerPlanes);
+
+    if (col != pam.width)
+        pm_error("INTERNAL ERROR: Wrote %d columns; should have written %d.",
+                 col, pam.width);
+
+    pnm_writepamrow(&pam, tuplerow);
+    
+    pnm_freepamrow(tuplerow);
+
+    return 0;
+}
diff --git a/generator/pamstereogram.c b/generator/pamstereogram.c
new file mode 100644
index 00000000..e9e58677
--- /dev/null
+++ b/generator/pamstereogram.c
@@ -0,0 +1,885 @@
+/* ----------------------------------------------------------------------
+ *
+ * Create a single image stereogram from a height map.
+ * by Scott Pakin <scott+pbm@pakin.org>
+ * Adapted to Netbpm conventions by Bryan Henderson.
+ * Revised by Scott Pakin.
+ *
+ * The core of this program is a simple adaptation of the code in
+ * "Displaying 3D Images: Algorithms for Single Image Random Dot
+ * Stereograms" by Harold W. Thimbleby, Stuart Inglis, and Ian
+ * H. Witten in IEEE Computer, 27(10):38-48, October 1994.  See that
+ * paper for a thorough explanation of what's going on here.
+ *
+ * ----------------------------------------------------------------------
+ *
+ * Copyright (C) 2006 Scott Pakin <scott+pbm@pakin.org>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ----------------------------------------------------------------------
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+/* Define a few helper macros. */
+#define round2int(X) ((int)((X)+0.5))      /* Nonnegative numbers only */
+
+enum outputType {OUTPUT_BW, OUTPUT_GRAYSCALE, OUTPUT_COLOR};
+
+/* ---------------------------------------------------------------------- */
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilespec;  /* '-' if stdin */
+    unsigned int verbose;        /* -verbose option */
+    unsigned int crosseyed;      /* -crosseyed option */
+    unsigned int makemask;       /* -makemask option */
+    unsigned int dpi;            /* -dpi option */
+    float eyesep;                /* -eyesep option */
+    float depth;                 /* -depth option */
+    unsigned int maxvalSpec;     /* -maxval option count */
+    unsigned int maxval;         /* -maxval option value x*/
+    int guidesize;               /* -guidesize option */
+    unsigned int magnifypat;     /* -magnifypat option */
+    unsigned int xshift;         /* -xshift option */
+    unsigned int yshift;         /* -yshift option */
+    const char * patFilespec;    /* -patfile option.  Null if none */
+    unsigned int randomseed;     /* -randomseed option */
+    enum outputType outputType;  /* Type of output file */
+};
+
+
+
+static void
+parseCommandLine(int                 argc,
+                 char **             argv,
+                 struct cmdlineInfo *cmdlineP ) {
+/*----------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int patfileSpec, dpiSpec, eyesepSpec, depthSpec,
+        guidesizeSpec, magnifypatSpec, xshiftSpec, yshiftSpec, randomseedSpec;
+
+    unsigned int blackandwhite, grayscale, color;
+
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "verbose",         OPT_FLAG,   NULL,
+            (unsigned int *)&cmdlineP->verbose,       0);
+    OPTENT3(0, "crosseyed",       OPT_FLAG,   NULL,
+            &cmdlineP->crosseyed,     0);
+    OPTENT3(0, "makemask",        OPT_FLAG,   NULL,
+            &cmdlineP->makemask,      0);
+    OPTENT3(0, "blackandwhite",   OPT_FLAG,   NULL,
+            &blackandwhite,           0);
+    OPTENT3(0, "grayscale",       OPT_FLAG,   NULL,
+            &grayscale,               0);
+    OPTENT3(0, "color",           OPT_FLAG,   NULL,
+            &color,                   0);
+    OPTENT3(0, "dpi",             OPT_UINT,   &cmdlineP->dpi,
+            &dpiSpec,                 0);
+    OPTENT3(0, "eyesep",          OPT_FLOAT,  &cmdlineP->eyesep,
+            &eyesepSpec,              0);
+    OPTENT3(0, "depth",           OPT_FLOAT,  &cmdlineP->depth,
+            &depthSpec,               0);
+    OPTENT3(0, "maxval",          OPT_UINT,   &cmdlineP->maxval,
+            &cmdlineP->maxvalSpec,    0);
+    OPTENT3(0, "guidesize",       OPT_INT,    &cmdlineP->guidesize,
+            &guidesizeSpec,           0);
+    OPTENT3(0, "magnifypat",      OPT_UINT,   &cmdlineP->magnifypat,
+            &magnifypatSpec,          0);
+    OPTENT3(0, "xshift",          OPT_UINT,   &cmdlineP->xshift,
+            &xshiftSpec,              0);
+    OPTENT3(0, "yshift",          OPT_UINT,   &cmdlineP->yshift,
+            &yshiftSpec,              0);
+    OPTENT3(0, "patfile",         OPT_STRING, &cmdlineP->patFilespec,
+            &patfileSpec,             0);
+    OPTENT3(0, "randomseed",      OPT_UINT,   &cmdlineP->randomseed,
+            &randomseedSpec,          0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+
+    if (blackandwhite + grayscale + color == 0)
+        cmdlineP->outputType = OUTPUT_BW;
+    else if (blackandwhite + grayscale + color > 1)
+        pm_error("You may specify only one of -blackandwhite, -grayscale, "
+                 "and -color");
+    else {
+        if (blackandwhite)
+            cmdlineP->outputType = OUTPUT_BW;
+        else if (grayscale)
+            cmdlineP->outputType = OUTPUT_GRAYSCALE;
+        else {
+            assert(color);
+            cmdlineP->outputType = OUTPUT_COLOR;
+        }
+    }
+    if (!patfileSpec)
+        cmdlineP->patFilespec = NULL;
+
+    if (!dpiSpec)
+        cmdlineP->dpi = 96;
+    else if (cmdlineP->dpi < 1)
+        pm_error("The argument to -dpi must be a positive integer");
+
+    if (!eyesepSpec)
+        cmdlineP->eyesep = 2.5;
+    else if (cmdlineP->eyesep <= 0.0)
+        pm_error("The argument to -eyesep must be a positive number");
+
+    if (!depthSpec)
+        cmdlineP->depth = (1.0/3.0);
+    else if (cmdlineP->depth < 0.0 || cmdlineP->depth > 1.0)
+        pm_error("The argument to -depth must be a number from 0.0 to 1.0");
+
+    if (cmdlineP->maxvalSpec) {
+        if (cmdlineP->maxval < 1)
+            pm_error("-maxval must be at least 1");
+        else if (cmdlineP->maxval > PNM_OVERALLMAXVAL)
+            pm_error("-maxval must be at most %u.  You specified %u",
+                     PNM_OVERALLMAXVAL, cmdlineP->maxval);
+    }
+
+    if (!guidesizeSpec)
+        cmdlineP->guidesize = 0;
+
+    if (!magnifypatSpec)
+        cmdlineP->magnifypat = 1;
+    else if (cmdlineP->magnifypat < 1)
+        pm_error("The argument to -magnifypat must be a positive integer");
+
+    if (!xshiftSpec)
+        cmdlineP->xshift = 0;
+
+    if (!yshiftSpec)
+        cmdlineP->yshift = 0;
+
+    if (!randomseedSpec)
+        cmdlineP->randomseed = time(NULL);
+
+    if (xshiftSpec && !cmdlineP->patFilespec)
+        pm_error("-xshift is valid only with -patfile");
+    if (yshiftSpec && !cmdlineP->patFilespec)
+        pm_error("-yshift is valid only with -patfile");
+
+    if (cmdlineP->makemask && cmdlineP->patFilespec)
+        pm_error("You may not specify both -makemask and -patfile");
+
+    if (cmdlineP->patFilespec && blackandwhite)
+        pm_error("-blackandwhite is not valid with -patfile");
+    if (cmdlineP->patFilespec && grayscale)
+        pm_error("-grayscale is not valid with -patfile");
+    if (cmdlineP->patFilespec && color)
+        pm_error("-color is not valid with -patfile");
+    if (cmdlineP->patFilespec && cmdlineP->maxvalSpec)
+        pm_error("-maxval is not valid with -patfile");
+
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        pm_error("Too many non-option arguments: %d.  Only argument is "
+                 "input file name", argc-1);
+}
+
+
+
+static int
+separation(double             const dist,
+           double             const eyesep,
+           unsigned int       const dpi,
+           double             const dof /* depth of field */
+    ) {
+/*----------------------------------------------------------------------------
+  Return a separation in pixels which corresponds to a 3-D distance
+  between the viewer's eyes and a point on an object.
+-----------------------------------------------------------------------------*/
+    int const pixelEyesep = round2int(eyesep * dpi);
+
+    return round2int((1.0 - dof * dist) * pixelEyesep / (2.0 - dof * dist));
+}
+
+
+
+static void
+reportImageParameters(const char * const fileDesc,
+                      const struct pam * const pamP) {
+
+    pm_message("%s: tuple type '%s', %d wide x %d high x %d deep, maxval %lu",
+               fileDesc, pamP->tuple_type, pamP->width, pamP->height,
+               pamP->depth, pamP->maxval);
+}
+
+
+
+/*----------------------------------------------------------------------------
+   Background generators
+-----------------------------------------------------------------------------*/
+
+struct outGenerator;
+
+typedef tuple coord2Color(struct outGenerator *, int, int);
+    /* A type to use for functions that map a 2-D coordinate to a color. */
+typedef void outGenStateTerm(struct outGenerator *);
+
+
+typedef struct outGenerator {
+    struct pam pam;
+    coord2Color * getTuple;
+        /* Map from a height-map (x,y) coordinate to a tuple */
+    outGenStateTerm * terminateState;
+    void * stateP;
+} outGenerator;
+
+
+
+struct randomState {
+    /* The state of a randomColor generator. */
+    unsigned int magnifypat;
+    tuple *      currentRow;
+    unsigned int prevy;
+};
+
+
+static coord2Color randomColor;
+
+static tuple
+randomColor(outGenerator * const outGenP,
+            int            const x,
+            int            const y) {
+/*----------------------------------------------------------------------------
+   Return a random RGB value.
+-----------------------------------------------------------------------------*/
+    struct randomState * const stateP = outGenP->stateP;
+
+    /* Every time we start a new row, we select a new sequence of random
+       colors.
+    */
+    if (y/stateP->magnifypat != stateP->prevy/stateP->magnifypat) {
+        unsigned int const modulus = outGenP->pam.maxval + 1;
+        int col;
+
+        for (col = 0; col < outGenP->pam.width; ++col) {
+            tuple const thisTuple = stateP->currentRow[col];
+
+            unsigned int plane;
+
+            for (plane = 0; plane < outGenP->pam.depth; ++plane) {
+                unsigned int const randval = rand();
+                thisTuple[plane] = randval % modulus;
+            }
+        }
+    }
+
+    /* Return the appropriate column from the pregenerated color row. */
+    stateP->prevy = y;
+    return stateP->currentRow[x/stateP->magnifypat];
+}
+
+
+
+static outGenStateTerm termRandomColor;
+
+static void
+termRandomColor(outGenerator * const outGenP) {
+
+    struct randomState * const stateP = outGenP->stateP;
+
+    pnm_freepamrow(stateP->currentRow);
+}
+
+
+
+static void
+initRandomColor(outGenerator *     const outGenP,
+                const struct pam * const inPamP,
+                struct cmdlineInfo const cmdline) {
+
+    struct randomState * stateP;
+
+    outGenP->pam.format      = PAM_FORMAT;
+    outGenP->pam.plainformat = 0;
+
+    switch (cmdline.outputType) {
+    case OUTPUT_BW:
+        strcpy(outGenP->pam.tuple_type, PAM_PBM_TUPLETYPE);
+        outGenP->pam.maxval = 1;
+        outGenP->pam.depth = 1;
+        break;
+    case OUTPUT_GRAYSCALE:
+        strcpy(outGenP->pam.tuple_type, PAM_PGM_TUPLETYPE);
+        outGenP->pam.maxval =
+            cmdline.maxvalSpec ? cmdline.maxval : inPamP->maxval;
+        outGenP->pam.depth = 1;
+        break;
+    case OUTPUT_COLOR:
+        strcpy(outGenP->pam.tuple_type, PAM_PPM_TUPLETYPE);
+        outGenP->pam.maxval =
+            cmdline.maxvalSpec ? cmdline.maxval : inPamP->maxval;
+        outGenP->pam.depth = 3;
+        break;
+    }
+
+    MALLOCVAR_NOFAIL(stateP);
+
+    stateP->currentRow = pnm_allocpamrow(&outGenP->pam);
+    stateP->magnifypat = cmdline.magnifypat;
+    stateP->prevy      = (unsigned int)(-cmdline.magnifypat);
+
+    outGenP->stateP         = stateP;
+    outGenP->getTuple       = &randomColor;
+    outGenP->terminateState = &termRandomColor;
+}
+
+
+
+struct patternPixelState {
+    /* This is the state of a patternPixel generator.*/
+    struct pam   patPam;     /* Descriptor of pattern image */
+    tuple **     patTuples;  /* Entire image read from the pattern file */
+    unsigned int xshift;
+    unsigned int yshift;
+    unsigned int magnifypat;
+};
+
+
+
+static coord2Color patternPixel;
+
+static tuple
+patternPixel(outGenerator * const outGenP,
+             int            const x,
+             int            const y) {
+/*----------------------------------------------------------------------------
+  Return a pixel from the pattern file.
+-----------------------------------------------------------------------------*/
+    struct patternPixelState * const stateP = outGenP->stateP;
+    struct pam * const patPamP = &stateP->patPam;
+
+    int patx, paty;
+
+    paty = ((y - stateP->yshift) / stateP->magnifypat) % patPamP->height;
+
+    if (paty < 0)
+        paty += patPamP->height;
+
+    patx = ((x - stateP->xshift) / stateP->magnifypat) % patPamP->width;
+
+    if (patx < 0)
+        patx += patPamP->width;
+
+    return stateP->patTuples[paty][patx];
+}
+
+
+
+static outGenStateTerm termPatternPixel;
+
+static void
+termPatternPixel(outGenerator * const outGenP) {
+
+    struct patternPixelState * const stateP = outGenP->stateP;
+
+    pnm_freepamarray(stateP->patTuples, &stateP->patPam);
+}
+
+
+
+static void
+initPatternPixel(outGenerator *     const outGenP,
+                 struct cmdlineInfo const cmdline) {
+
+    struct patternPixelState * stateP;
+    FILE * patternFileP;
+
+    MALLOCVAR_NOFAIL(stateP);
+        
+    patternFileP = pm_openr(cmdline.patFilespec);
+
+    stateP->patTuples =
+        pnm_readpam(patternFileP,
+                    &stateP->patPam, PAM_STRUCT_SIZE(tuple_type));
+
+    pm_close(patternFileP);
+
+    stateP->xshift     = cmdline.xshift;
+    stateP->yshift     = cmdline.yshift;
+    stateP->magnifypat = cmdline.magnifypat;
+
+    outGenP->stateP          = stateP;
+    outGenP->getTuple        = &patternPixel;
+    outGenP->terminateState  = &termPatternPixel;
+    outGenP->pam.format      = stateP->patPam.format;
+    outGenP->pam.plainformat = stateP->patPam.plainformat;
+    outGenP->pam.depth       = stateP->patPam.depth;
+    outGenP->pam.maxval      = stateP->patPam.maxval;
+
+    if (cmdline.verbose)
+        reportImageParameters("Pattern file", &stateP->patPam);
+}
+
+
+
+static void
+createoutputGenerator(struct cmdlineInfo const cmdline,
+                      const struct pam * const inPamP,
+                      outGenerator **    const outputGeneratorPP) {
+
+    outGenerator * outGenP;
+    
+    MALLOCVAR_NOFAIL(outGenP);
+
+    outGenP->pam.size   = sizeof(struct pam);
+    outGenP->pam.len    = PAM_STRUCT_SIZE(tuple_type);
+    outGenP->pam.bytes_per_sample = pnm_bytespersample(outGenP->pam.maxval);
+    outGenP->pam.file   = stdout;
+    outGenP->pam.height = inPamP->height + 3 * abs(cmdline.guidesize);
+        /* Allow room for guides. */
+    outGenP->pam.width  = inPamP->width;
+
+    if (cmdline.patFilespec) {
+        /* Background pixels should come from the pattern file. */
+
+        initPatternPixel(outGenP, cmdline);
+    } else {
+        /* Background pixels should be generated randomly */
+
+        initRandomColor(outGenP, inPamP, cmdline);
+    }
+
+    *outputGeneratorPP = outGenP;
+}
+
+
+
+static void
+destroyoutputGenerator(outGenerator * const outputGeneratorP) {
+
+    outputGeneratorP->terminateState(outputGeneratorP);
+    free(outputGeneratorP);
+}
+
+
+/* End of background generators */
+
+/* ---------------------------------------------------------------------- */
+
+
+static void
+makeWhiteRow(const struct pam * const pamP,
+             tuple *            const tuplerow) {
+
+    int col;
+
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            tuplerow[col][plane] = pamP->maxval;
+    }
+}
+
+
+
+static void
+writeRowCopies(const struct pam *  const outPamP,
+               const tuple *       const outrow,
+               unsigned int        const copyCount) {
+
+    unsigned int i;
+    for (i = 0; i < copyCount; ++i)
+        pnm_writepamrow(outPamP, outrow);
+}
+
+
+
+/* Draw a pair of guide boxes. */
+static void
+drawguides(int                const guidesize,
+           const struct pam * const outPamP,
+           double             const eyesep,
+           unsigned int       const dpi,
+           double             const depthOfField) {
+
+    int const far = separation(0, eyesep, dpi, depthOfField);
+        /* Space between the two guide boxes. */
+    int const width = outPamP->width;    /* Width of the output image */
+
+    tuple *outrow;             /* One row of output data */
+    tuple blackTuple;
+    int col;
+
+    pnm_createBlackTuple(outPamP, &blackTuple);
+
+    outrow = pnm_allocpamrow(outPamP);
+
+    /* Leave some blank rows before the guides. */
+    makeWhiteRow(outPamP, outrow);
+    writeRowCopies(outPamP, outrow, guidesize);
+
+    /* Draw the guides. */
+    if ((width - far + guidesize)/2 < 0 ||
+        (width + far - guidesize)/2 >= width)
+        pm_message("warning: the guide boxes are completely out of bounds "
+                   "at %d DPI", dpi);
+    else if ((width - far - guidesize)/2 < 0 ||
+             (width + far + guidesize)/2 >= width)
+        pm_message("warning: the guide boxes are partially out of bounds "
+                   "at %d DPI", dpi);
+
+    for (col = (width - far - guidesize)/2;
+         col < (width - far + guidesize)/2;
+         ++col)
+        if (col >= 0 && col < width)
+            pnm_assigntuple(outPamP, outrow[col], blackTuple);
+
+    for (col = (width + far - guidesize)/2;
+         col < (width + far + guidesize)/2;
+         ++col)
+        if (col >= 0 && col < width)
+            pnm_assigntuple(outPamP, outrow[col], blackTuple);
+
+    writeRowCopies(outPamP,outrow, guidesize);
+
+    /* Leave some blank rows after the guides. */
+    makeWhiteRow(outPamP, outrow);
+    writeRowCopies(outPamP, outrow, guidesize);
+
+    pnm_freerow(outrow);
+}
+
+
+
+/* Do the bulk of the work.  See the paper cited above for code
+ * comments.  All I (Scott) did was transcribe the code and make
+ * minimal changes for Netpbm.  And some style changes by Bryan to
+ * match Netpbm style.
+ */
+static void
+makeStereoRow(const struct pam * const inPamP,
+              tuple *            const inRow,
+              int *              const same,
+              double             const depthOfField,
+              double             const eyesep,
+              unsigned int       const dpi) {
+
+#define Z(X) (1.0-inRow[X][0]/(double)inPamP->maxval)
+
+    int const width       = inPamP->width;
+    int const pixelEyesep = round2int(eyesep * dpi);
+        /* Separation in pixels between the viewer's eyes */
+
+    int col;
+
+    for (col = 0; col < width; ++col)
+        same[col] = col;
+
+    for (col = 0; col < width; ++col) {
+        int const s = separation(Z(col), eyesep, dpi, depthOfField);
+        int left, right;
+
+        left  = col - s/2;  /* initial value */
+        right = left + s;   /* initial value */
+
+        if (0 <= left && right < width) {
+            int visible;
+            int t;
+            double zt;
+
+            t = 1;  /* initial value */
+
+            do {
+                double const dof = depthOfField;
+                zt = Z(col) + 2.0*(2.0 - dof*Z(col))*t/(dof*pixelEyesep);
+                visible = Z(col-t) < zt && Z(col+t) < zt;
+                ++t;
+            } while (visible && zt < 1);
+            if (visible) {
+                int l;
+
+                l = same[left];
+                while (l != left && l != right) {
+                    if (l < right) {
+                        left = l;
+                        l = same[left];
+                    } else {
+                        same[left] = right;
+                        left = right;
+                        l = same[left];
+                        right = l;
+                    }
+                }
+                same[left] = right;
+            }
+        }
+    }
+}
+
+
+
+static void
+makeMaskRow(const struct pam * const outPamP,
+            const int *        const same,
+            const tuple *      const outRow) {
+    int col;
+
+    for (col = outPamP->width-1; col >= 0; --col) {
+        bool const duplicate = (same[col] != col);
+        unsigned int plane;
+
+        for (plane = 0; plane < outPamP->depth; ++plane)
+            outRow[col][plane] = duplicate ? outPamP->maxval : 0;
+    }
+}
+
+
+
+static void
+makeImageRow(outGenerator * const outGenP,
+             int            const row,
+             const int *    const same,
+             const tuple *  const outRow) {
+/*----------------------------------------------------------------------------
+  same[N] is one of two things:
+
+  same[N] == N means to generate a value for Column N independent of
+  other columns in the row.
+
+  same[N] > N means Column N should be identical to Column same[N].
+  
+  same[N] < N is not allowed.
+-----------------------------------------------------------------------------*/
+    int col;
+    for (col = outGenP->pam.width-1; col >= 0; --col) {
+        bool const duplicate = (same[col] != col);
+        
+        tuple newtuple;
+        unsigned int plane;
+
+        if (duplicate) {
+            assert(same[col] > col);
+            assert(same[col] < outGenP->pam.width);
+
+            newtuple = outRow[same[col]];
+        } else 
+            newtuple = outGenP->getTuple(outGenP, col, row);
+
+        for (plane = 0; plane < outGenP->pam.depth; ++plane)
+            outRow[col][plane] = newtuple[plane];
+    }
+}
+
+
+
+static void
+invertHeightRow(const struct pam * const heightPamP,
+                tuple *            const tupleRow) {
+
+    int col;
+
+    for (col = 0; col < heightPamP->width; ++col)
+        tupleRow[col][0] = heightPamP->maxval - tupleRow[col][0];
+}
+
+
+
+static void
+makeImageRows(const struct pam * const inPamP,
+              outGenerator *     const outputGeneratorP,
+              double             const depthOfField,
+              double             const eyesep,
+              unsigned int       const dpi,
+              bool               const crossEyed,
+              bool               const makeMask) {
+
+    tuple * inRow;     /* One row of pixels read from the height-map file */
+    tuple * outRow;    /* One row of pixels to write to the height-map file */
+    int * same;
+        /* Array: same[N] is the column number of a pixel to the right forced
+           to have the same color as the one in column N
+        */
+    int row;           /* Current row in the input and output files */
+
+    inRow = pnm_allocpamrow(inPamP);
+    outRow = pnm_allocpamrow(&outputGeneratorP->pam);
+    MALLOCARRAY(same, inPamP->width);
+    if (same == NULL)
+        pm_error("Unable to allocate space for \"same\" array.");
+
+    /* See the paper cited above for code comments.  All I (Scott) did was
+     * transcribe the code and make minimal changes for Netpbm.  And some
+     * style changes by Bryan to match Netpbm style.
+     */
+    for (row = 0; row < inPamP->height; ++row) {
+        pnm_readpamrow(inPamP, inRow);
+        if (crossEyed)
+            /* Invert heights for cross-eyed (as opposed to wall-eyed)
+               people.
+            */
+            invertHeightRow(inPamP, inRow);
+
+        /* Determine color constraints. */
+        makeStereoRow(inPamP, inRow, same, depthOfField, eyesep, dpi);
+
+        if (makeMask)
+            makeMaskRow(&outputGeneratorP->pam, same, outRow);
+        else
+            makeImageRow(outputGeneratorP, row, same, outRow);
+
+        /* Write the resulting row. */
+        pnm_writepamrow(&outputGeneratorP->pam, outRow);
+    }
+    free(same);
+    pnm_freepamrow(outRow);
+    pnm_freepamrow(inRow);
+}
+
+
+
+static void
+produceStereogram(FILE *             const ifP,
+                  struct cmdlineInfo const cmdline) {
+
+    struct pam inPam;    /* PAM information for the height-map file */
+    outGenerator * outputGeneratorP;
+        /* Handle of an object that generates background pixels */
+    
+    pnm_readpaminit(ifP, &inPam, PAM_STRUCT_SIZE(tuple_type));
+
+    createoutputGenerator(cmdline, &inPam, &outputGeneratorP);
+
+    if (cmdline.verbose) {
+        reportImageParameters("Input (height map) file", &inPam);
+        if (inPam.depth > 1)
+            pm_message("Ignoring all but the first plane of input.");
+        reportImageParameters("Output (stereogram) file",
+                              &outputGeneratorP->pam);
+    }
+
+    pnm_writepaminit(&outputGeneratorP->pam);
+
+    /* Draw guide boxes at the top, if desired. */
+    if (cmdline.guidesize < 0)
+        drawguides(-cmdline.guidesize, &outputGeneratorP->pam,
+                   cmdline.eyesep,
+                   cmdline.dpi, cmdline.depth);
+
+    makeImageRows(&inPam, outputGeneratorP,
+                  cmdline.depth, cmdline.eyesep, cmdline.dpi,
+                  cmdline.crosseyed, cmdline.makemask);
+
+    /* Draw guide boxes at the bottom, if desired. */
+    if (cmdline.guidesize > 0)
+        drawguides(cmdline.guidesize, &outputGeneratorP->pam,
+                   cmdline.eyesep, cmdline.dpi, cmdline.depth);
+
+    destroyoutputGenerator(outputGeneratorP);
+}
+
+
+
+static void
+reportParameters(struct cmdlineInfo const cmdline) {
+
+    unsigned int const pixelEyesep = round2int(cmdline.eyesep * cmdline.dpi);
+
+    pm_message("Eye separation: %.4g inch * %d DPI = %u pixels",
+               cmdline.eyesep, cmdline.dpi, pixelEyesep);
+
+    if (cmdline.magnifypat > 1)
+        pm_message("Background magnification: %uX * %uX",
+                   cmdline.magnifypat, cmdline.magnifypat);
+    pm_message("\"Optimal\" pattern width: %u / (%u * 2) = %u pixels",
+               pixelEyesep, cmdline.magnifypat,
+               pixelEyesep/(cmdline.magnifypat * 2));
+    pm_message("Unique 3-D depth levels possible: %u",
+               separation(0, cmdline.eyesep, cmdline.dpi, cmdline.depth) -
+               separation(1, cmdline.eyesep, cmdline.dpi, cmdline.depth) + 1);
+    if (cmdline.patFilespec && (cmdline.xshift || cmdline.yshift))
+        pm_message("Pattern shift: (%u, %u)", cmdline.xshift, cmdline.yshift);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;      /* Parsed command line */
+    FILE * ifP;
+    
+    /* Parse the command line. */
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    if (cmdline.verbose)
+        reportParameters(cmdline);
+    
+    srand(cmdline.randomseed);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+        
+    /* Produce a stereogram. */
+    produceStereogram(ifP, cmdline);
+
+    pm_close(ifP);
+    
+    return 0;
+}
diff --git a/generator/pamstereogram.test b/generator/pamstereogram.test
new file mode 100644
index 00000000..7eb01fff
--- /dev/null
+++ b/generator/pamstereogram.test
@@ -0,0 +1,70 @@
+# Make some input files
+pamdepth -quiet 255 ../testgrid.pbm >/tmp/testgrid.pgm
+
+
+# Random pattern
+
+echo Test 01.  Should print 610673698 293:
+./pamstereogram -randomseed=1 ../testgrid.pbm | cksum 
+echo Test 02.  Should print 610673698 293:
+./pamstereogram -randomseed=1 -blackandwhite ../testgrid.pbm | cksum 
+echo Test 03.  Should print 3439084201 170:
+pamseq -tupletype=GRAYSCALE 1 100 | ./pamstereogram -randomseed=1 | cksum 
+echo Test 04.  Should print 2484923390 1070:
+pamgauss 100 10 -maxval=10000 -sigma 20 | pamfunc -multiplier=500 | \
+  ./pamstereogram -randomseed=1 -dpi=10 | cksum
+
+# Makemask
+
+echo Test 10.  Should print 1266273778 293:
+./pamstereogram -randomseed=1 -makemask ../testgrid.pbm | cksum 
+
+echo Test 11.  Should print 3034751595 1070:
+./pamgauss 100 10 -maxval=10000 -sigma 20 | pamfunc -multiplier=500 | \
+  ./pamstereogram -randomseed=1 -dpi=10 -makemask | cksum
+
+# Grayscale
+
+echo Test 20.  Should print 2468969328 289:
+./pamstereogram -randomseed=1 -grayscale ../testgrid.pbm | cksum 
+echo Test 21.  Should print 1946982115 4068:
+./pamseq 1 100 | pnmtile 200 20 | \
+  ./pamstereogram -randomseed=1 -dpi=10 -grayscale | \
+  cksum
+echo Test 22.  Should print 2078013430 4068:
+./pamseq 1 100 | pnmtile 200 20 | \
+  ./pamstereogram -randomseed=1 -dpi=10 -grayscale -maxval 255 | \
+  cksum
+
+# Color
+
+echo Test 30.  Should print 1319392622 731:
+./pamstereogram -randomseed=1 -color ../testgrid.pbm | cksum 
+echo Test 31.  Should print 389886159 12062:
+./pamseq 1 100 | pnmtile 200 20 | \
+  ./pamstereogram -randomseed=1 -dpi=10 -color | \
+  cksum
+
+# Pattern file
+
+echo Test 40.  Should print 1834916830 660:
+pamgradient black gray50 white gray50 100 50 | \
+  ./pamstereogram -patfile ../testgrid.pbm -eyesep=.1 -crosseyed | cksum
+
+echo Test 41.  Should print 4016818756 5014:
+pamgradient black gray50 white gray50 100 50 | \
+  ./pamstereogram -patfile /tmp/testgrid.pgm -eyesep=.1 -crosseyed | cksum
+
+# drawguides
+
+echo Test 51.  Should print 2365956562 11071:
+pamgradient black gray50 white gray50 100 50 | \
+  ./pamstereogram -randomseed=1 -dpi 10 -guidesize=20 | cksum
+
+echo Test 51.  Should print 3502025270 1441:
+pamgradient black gray50 white gray50 100 50 | \
+  ./pamstereogram -patfile=../testgrid.pbm -dpi 10 -guidesize=20 | cksum
+
+
+# Clean up files we created
+rm /tmp/testgrid.pgm
diff --git a/generator/pbmmake.c b/generator/pbmmake.c
new file mode 100644
index 00000000..afe1dac3
--- /dev/null
+++ b/generator/pbmmake.c
@@ -0,0 +1,184 @@
+/* pbmmake.c - create a blank bitmap of a specified size
+ *
+ * Akira Urushibata ("Douso") wrote some of the core code that went
+ * into the Netpbm 10.23 (July 2004) version of this program and licenses
+ * that code to the public under GPL.
+ *
+ * Bryan Henderson wrote the rest of that version and contributed his
+ * work to the public domain.
+ *
+ * See doc/HISTORY for a full history of this program.
+**
+*/
+
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "pbm.h"
+
+enum color {COLOR_BLACK, COLOR_WHITE, COLOR_GRAY};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    unsigned int width;
+    unsigned int height;
+    enum color color;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int blackOpt, whiteOpt, grayOpt;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "black",     OPT_FLAG, NULL, &blackOpt, 0);
+    OPTENT3(0, "white",     OPT_FLAG, NULL, &whiteOpt, 0);
+    OPTENT3(0, "gray",      OPT_FLAG, NULL, &grayOpt,  0);
+    OPTENT3(0, "grey",      OPT_FLAG, NULL, &grayOpt,  0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (blackOpt + whiteOpt + grayOpt > 1)
+        pm_error("You can specify only one of -black, -white, and -gray");
+    
+    if (blackOpt)
+        cmdlineP->color = COLOR_BLACK;
+    else if (whiteOpt)
+        cmdlineP->color = COLOR_WHITE;
+    else if (grayOpt)
+        cmdlineP->color = COLOR_GRAY;
+    else
+        cmdlineP->color = COLOR_WHITE;
+
+    if (argc-1 != 2)
+        pm_error("Wrong number of arguments (%d).  There are two "
+                 "non-option arguments: width and height in pixels",
+                 argc-1);
+    else {
+        cmdlineP->width  = atoi(argv[1]);
+        cmdlineP->height = atoi(argv[2]);
+
+        if (cmdlineP->width < 1) 
+            pm_error("Width must be positive.  You specified %d.", 
+                     cmdlineP->width);
+
+        if (cmdlineP->height < 1) 
+            pm_error("Height must be positive.  You specified %d.",
+                     cmdlineP->height);
+    }
+}
+
+
+
+static void 
+writeGrayRaster(unsigned int const cols, 
+                unsigned int const rows,
+                FILE *       const ofP) {
+
+    unsigned int const lastCol = (cols-1)/8;
+
+    unsigned char * bitrow0, * bitrow1;
+    unsigned int i;
+
+    bitrow0 = pbm_allocrow_packed(cols);
+    bitrow1 = pbm_allocrow_packed(cols);
+
+    for (i=0; i <= lastCol; ++i) { 
+        bitrow0[i] = (PBM_WHITE*0xaa) | (PBM_BLACK*0x55);
+        bitrow1[i] = (PBM_WHITE*0x55) | (PBM_BLACK*0xaa);
+        /* 0xaa = 10101010 ; 0x55 = 01010101 */
+    }
+    if (cols % 8 > 0) { 
+        bitrow0[lastCol] >>= 8 - cols % 8;
+        bitrow0[lastCol] <<= 8 - cols % 8;
+        bitrow1[lastCol] >>= 8 - cols % 8;
+        bitrow1[lastCol] <<= 8 - cols % 8;
+    }
+    if (rows > 1) {
+        unsigned int row;
+        for (row = 1; row < rows; row += 2) {
+            pbm_writepbmrow_packed(ofP, bitrow0, cols, 0);
+            pbm_writepbmrow_packed(ofP, bitrow1, cols, 0);
+        }
+    }
+    if (rows % 2 == 1)
+        pbm_writepbmrow_packed(ofP, bitrow0, cols, 0);
+
+    pbm_freerow(bitrow0);
+    pbm_freerow(bitrow1);
+}
+
+    
+
+static void
+writeSingleColorRaster(unsigned int const cols,
+                       unsigned int const rows,
+                       bit          const color,
+                       FILE *       const ofP) {
+
+    unsigned int const lastCol = (cols-1)/8;
+
+    unsigned char * bitrow0;
+    unsigned int i;
+
+    bitrow0 = pbm_allocrow_packed(cols);
+
+    for (i = 0; i <= lastCol; ++i) 
+        bitrow0[i] = color*0xff;
+
+    if (cols % 8 > 0) { 
+        bitrow0[lastCol] >>= 8 - cols % 8;
+        bitrow0[lastCol] <<= 8 - cols % 8;
+        /* row end trimming, really not necessary with white */
+    }
+    {
+        unsigned int row;
+        for (row = 0; row < rows; ++row)
+            pbm_writepbmrow_packed(ofP, bitrow0, cols, 0);
+    }
+    pbm_freerow(bitrow0);
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+
+    pbm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    pbm_writepbminit(stdout, cmdline.width, cmdline.height, 0);
+    
+    if (cmdline.color == COLOR_GRAY)
+        writeGrayRaster(cmdline.width, cmdline.height, stdout);
+    else {
+        bit const color = cmdline.color == COLOR_WHITE ? PBM_WHITE : PBM_BLACK;
+        writeSingleColorRaster(cmdline.width, cmdline.height, color, stdout);
+    }
+    pm_close(stdout);
+    
+    return 0;
+}
diff --git a/generator/pbmmake.test b/generator/pbmmake.test
new file mode 100644
index 00000000..0fd99ccd
--- /dev/null
+++ b/generator/pbmmake.test
@@ -0,0 +1,9 @@
+echo Test 1.  Should print 3892756435 12
+./pbmmake -white 16 2 | cksum
+echo Test 2.  Should print 1576602925 8
+./pbmmake -black 1 1  | cksum
+echo Test 3.  Should print 4272952448 14
+./pbmmake -gray 7 7   | cksum
+echo Test 4.  Should print 1634688086 15
+./pbmmake -grey 8 8   | cksum
+echo Tests done.
diff --git a/generator/pbmpage.c b/generator/pbmpage.c
new file mode 100644
index 00000000..e10ee6d6
--- /dev/null
+++ b/generator/pbmpage.c
@@ -0,0 +1,291 @@
+/***************************************************************************
+                                pbmpage
+
+  This program produces a printed page test pattern in PBM format.
+
+  This was adapted from Tim Norman's 'pbmtpg' program, part of his
+  'pbm2ppa' package, by Bryan Henderson on 2000.05.01.  The only
+  change was to make it use the Netpbm libraries to generate the
+  output.
+
+  For copyright and licensing information, see the pbmtoppa program,
+  which was also derived from the same package.
+****************************************************************************/
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <stdio.h>
+
+#include "pbm.h"
+
+/* US is 8.5 in by 11 in */
+
+#define USWIDTH  (5100)
+#define USHEIGHT (6600)
+
+/* A4 is 210 mm by 297 mm == 8.27 in by 11.69 in */
+
+#define A4WIDTH  (4960)
+#define A4HEIGHT (7016)
+
+
+struct bitmap {
+    int Width;      /* width and height in 600ths of an inch */
+    int Height;
+    int Pwidth;     /* width in bytes */
+    char *bitmap;
+};
+
+static struct bitmap bitmap;
+
+
+
+static void
+setpixel(int const x,
+         int const y,
+         int const c) {
+
+    int const charidx = y * bitmap.Pwidth + x/8;
+    char const bitmask = 128 >> (x % 8);
+
+    if (x < 0 || x >= bitmap.Width)
+        return;
+    if (y < 0 || y >= bitmap.Height)
+        return;
+
+    if (c)
+        bitmap.bitmap[charidx] |= bitmask;
+    else
+        bitmap.bitmap[charidx] &= ~bitmask;
+}
+
+
+
+static void 
+setplus(int x,int y,int s)
+/*----------------------------------------------------------------------------
+   Draw a black plus sign centered at (x,y) with arms 's' pixels long.  
+   Leave the exact center of the plus white.
+-----------------------------------------------------------------------------*/
+{
+  int i;
+
+  for(i=0; i<s; i++)
+  {
+    setpixel(x+i,y,1);
+    setpixel(x-i,y,1);
+    setpixel(x,y+i,1);
+    setpixel(x,y-i,1);
+  }
+}
+
+
+
+static void 
+setblock(int x,int y,int s)
+{
+  int i,j;
+
+  for(i=0; i<s; i++)
+    for(j=0; j<s; j++)
+      setpixel(x+i,y+j,1);
+}
+
+
+
+static void 
+setchar(int x,int y,char c)
+{
+  int xo,yo;
+  static char charmap[10][5]= { { 0x3e, 0x41, 0x41, 0x41, 0x3e },
+				{ 0x00, 0x42, 0x7f, 0x40, 0x00 },
+				{ 0x42, 0x61, 0x51, 0x49, 0x46 },
+				{ 0x22, 0x41, 0x49, 0x49, 0x36 },
+				{ 0x18, 0x14, 0x12, 0x7f, 0x10 },
+				{ 0x27, 0x45, 0x45, 0x45, 0x39 },
+				{ 0x3e, 0x49, 0x49, 0x49, 0x32 },
+				{ 0x01, 0x01, 0x61, 0x19, 0x07 },
+				{ 0x36, 0x49, 0x49, 0x49, 0x36 },
+				{ 0x26, 0x49, 0x49, 0x49, 0x3e } };
+
+  if(c<='9' && c>='0')
+    for(xo=0; xo<5; xo++)
+      for(yo=0; yo<8; yo++)
+	if((charmap[c-'0'][xo]>>yo)&1)
+	  setblock(x+xo*3,y+yo*3,3);
+}
+
+
+
+static void 
+setstring(int x,int y,char* s)
+{
+  char* p;
+  int xo;
+
+  for(xo=0, p=s; *p; xo+=21, p++)
+    setchar(x+xo,y,*p);
+}
+
+
+
+static void 
+setCG(int x,int y)
+{
+  int xo,yo,zo;
+
+  for(xo=0; xo<=50; xo++)
+  {
+    yo=sqrt(50.0*50.0-xo*xo);
+    setpixel(x+xo,y+yo,1);
+    setpixel(x+yo,y+xo,1);
+    setpixel(x-1-xo,y-1-yo,1);
+    setpixel(x-1-yo,y-1-xo,1);
+    setpixel(x+xo,y-1-yo,1);
+    setpixel(x-1-xo,y+yo,1);
+    for(zo=0; zo<yo; zo++)
+    {
+      setpixel(x+xo,y-1-zo,1);
+      setpixel(x-1-xo,y+zo,1);
+    }
+  }
+}
+
+
+
+static void
+outputPbm(FILE *        const file,
+          struct bitmap const bitmap) {
+/*----------------------------------------------------------------------------
+  Create a pbm file containing the image from the global variable bitmap[].
+-----------------------------------------------------------------------------*/
+    int const forceplain = 0;
+    bit *pbmrow;
+    int row;
+    int bitmap_cursor;
+    
+    pbm_writepbminit(file, bitmap.Width, bitmap.Height, forceplain);
+  
+    /* We round the allocated row space up to a multiple of 8 so the ugly
+       fast code below can work.
+       */
+    pbmrow = pbm_allocrow(((bitmap.Width+7)/8)*8);
+    
+    bitmap_cursor = 0;
+    for (row = 0; row < bitmap.Height; row++) {
+        int col;
+        for (col = 0; col < bitmap.Width;) {
+            /* A little ugliness makes a big speed difference here. */
+            pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<7);
+            pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<6);
+            pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<5);
+            pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<4);
+            pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<3);
+            pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<2);
+            pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<1);
+            pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<0);
+                
+            bitmap_cursor++;
+        }
+        pbm_writepbmrow(file, pbmrow, bitmap.Width, forceplain); 
+    }
+    pbm_freerow(pbmrow);
+    pm_close(file);
+}
+
+
+static void
+framePerimeter(unsigned int const Width, 
+               unsigned int const Height) {
+
+    unsigned int x,y;
+
+    /* Top edge */
+    for (x = 0; x < Width; ++x)
+        setpixel(x, 0, 1);
+
+    /* Bottom edge */
+    for (x = 0; x < Width; ++x)
+        setpixel(x, Height-1, 1);
+
+    /* Left edge */
+    for (y = 0; y < Height; ++y)
+        setpixel(0, y, 1);
+
+    /* Right edge */
+    for (y = 0; y < Height; ++y)
+        setpixel(Width-1, y, 1);
+}
+
+
+
+int 
+main(int argc,char** argv) {
+
+    int TP=1;
+    int x,y;
+    char buf[128];
+    int Width;      /* width and height in 600ths of an inch */
+    int Height;
+
+    pbm_init(&argc, argv);
+
+    if (argc > 1 && strcmp(argv[1], "-a4") == 0) {
+        Width = A4WIDTH;
+        Height = A4HEIGHT;
+        argc--;
+        argv++;
+    } else {
+        Width = USWIDTH;
+        Height = USHEIGHT;
+    }
+
+    bitmap.Width = Width;
+    bitmap.Height = Height;
+    bitmap.Pwidth = (Width + 7) / 8;
+    bitmap.bitmap = malloc(bitmap.Pwidth * bitmap.Height);
+
+    if (argc>1)
+        TP = atoi(argv[1]);
+
+    switch (TP) {
+    case 1:
+        framePerimeter(Width, Height);
+        for (x = 0; x < Width; x += 100)
+            for(y = 0; y < Height; y += 100)
+                setplus(x, y, 4);
+        for(x = 0; x < Width; x += 100) {
+            sprintf(buf,"%d", x);
+            setstring(x + 3, (Height/200) * 100 + 3, buf);
+        }
+        for (y=0; y < Height; y += 100) {
+            sprintf(buf, "%d", y);
+            setstring((Width/200) * 100 + 3, y + 3, buf);
+        }
+        for (x = 0; x < Width; x += 10)
+            for (y = 0; y < Height; y += 100)
+                setplus(x, y, ((x%100) == 50) ? 2 : 1);
+        for (x=0; x < Width; x += 100)
+            for (y = 0; y < Height; y += 10)
+                setplus(x, y, ((y%100) == 50) ? 2 : 1);
+        setCG(Width/2, Height/2);
+        break;
+    case 2:
+        for (y = 0; y < 300; ++y)
+            setpixel(Width/2, Height/2-y, 1);
+        break;
+    case 3:
+        for (y = 0; y < 300; ++y) {
+            setpixel(y, y, 1);
+            setpixel(Width-1-y, Height-1-y, 1);
+        }
+        break;
+    default:
+        pm_error("unknown test pattern (%d)", TP);
+    }
+
+    outputPbm(stdout, bitmap);
+
+    return 0;
+}
diff --git a/generator/pbmtext.c b/generator/pbmtext.c
new file mode 100644
index 00000000..e1d132d0
--- /dev/null
+++ b/generator/pbmtext.c
@@ -0,0 +1,732 @@
+/* pbmtext.c - render text into a bitmap
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <string.h>
+
+#include "pbm.h"
+#include "pbmfont.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *text;    /* text from command line or NULL if none */
+    const char *font;    /* -font option value or NULL if none */
+    const char *builtin; /* -builtin option value or NULL if none */
+    unsigned int dump;   
+        /* undocumented dump option for installing a new built-in font */
+    float space;   /* -space option value or default */
+    unsigned int width;     /* -width option value or zero */
+    int lspace;    /* lspace option value or default */
+    unsigned int nomargins;     /* -nomargins */
+};
+
+
+
+
+static void
+parse_command_line(int argc, char ** argv,
+                   struct cmdline_info *cmdline_p) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "font",      OPT_STRING, &cmdline_p->font, NULL,        0);
+    OPTENT3(0, "builtin",   OPT_STRING, &cmdline_p->builtin, NULL,     0);
+    OPTENT3(0, "dump",      OPT_FLAG,   NULL, &cmdline_p->dump,        0);
+    OPTENT3(0, "space",     OPT_FLOAT,  &cmdline_p->space, NULL,       0);
+    OPTENT3(0, "width",     OPT_UINT,   &cmdline_p->width, NULL,       0);
+    OPTENT3(0, "lspace",    OPT_INT,    &cmdline_p->lspace, NULL,      0);
+    OPTENT3(0, "nomargins", OPT_FLAG,   NULL, &cmdline_p->nomargins,   0);
+
+    /* Set the defaults */
+    cmdline_p->font = NULL;
+    cmdline_p->builtin = NULL;
+    cmdline_p->space = 0.0;
+    cmdline_p->width = 0;
+    cmdline_p->lspace = 0;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (argc-1 == 0)
+        cmdline_p->text = NULL;
+    else {
+        char *text;
+        int i;
+        int totaltextsize;
+
+        totaltextsize = 1;  /* initial value */
+        
+        text = malloc(totaltextsize);  /* initial allocation */
+        text[0] = '\0';
+        
+        for (i = 1; i < argc; i++) {
+            if (i > 1) {
+                totaltextsize += 1;
+                text = realloc(text, totaltextsize);
+                if (text == NULL)
+                    pm_error("out of memory allocating space for input text");
+                strcat(text, " ");
+            } 
+            totaltextsize += strlen(argv[i]);
+            text = realloc(text, totaltextsize);
+            if (text == NULL)
+                pm_error("out of memory allocating space for input text");
+            strcat(text, argv[i]);
+        }
+        cmdline_p->text = text;
+    }
+}
+
+
+struct text {
+    char **      textArray;  /* malloc'ed */
+    unsigned int allocatedLineCount;
+    unsigned int lineCount;
+};
+
+
+static void
+allocTextArray(struct text * const textP,
+               unsigned int  const maxLineCount,
+               unsigned int  const maxColumnCount) {
+
+    unsigned int line;
+
+    textP->allocatedLineCount = maxLineCount;
+
+    MALLOCARRAY_NOFAIL(textP->textArray, maxLineCount);
+    
+    for (line = 0; line < maxLineCount; ++line)
+        MALLOCARRAY_NOFAIL(textP->textArray[line], maxColumnCount+1);
+
+    textP->lineCount = 0;
+}
+
+
+static void
+freeTextArray(struct text const text) {
+
+    unsigned int line;
+
+    for (line = 0; line < text.allocatedLineCount; ++line)
+        free((char **)text.textArray[line]);
+
+    free(text.textArray);
+}
+
+
+
+static void
+fix_control_chars(char * const buf, struct font * const fn) {
+
+    int i;
+
+    /* chop off terminating newline */
+    if (strlen(buf) >= 1 && buf[strlen(buf)-1] == '\n')
+        buf[strlen(buf)-1] = '\0';
+    
+    for ( i = 0; buf[i] != '\0'; ++i ) {
+        if ( buf[i] == '\t' ) { 
+            /* Turn tabs into the right number of spaces. */
+            int j, n, l;
+            n = ( i + 8 ) / 8 * 8;
+            l = strlen( buf );
+            for ( j = l; j > i; --j )
+                buf[j + n - i - 1] = buf[j];
+            for ( ; i < n; ++i )
+                buf[i] = ' ';
+            --i;
+        }
+        else if ( !fn->glyph[(unsigned char)buf[i]] )
+            /* Turn unknown chars into a single space. */
+            buf[i] = ' ';
+    }
+}
+
+
+
+static void
+fill_rect(bit** const bits, 
+          int   const row0, 
+          int   const col0, 
+          int   const height, 
+          int   const width, 
+          bit   const color) {
+
+    int row;
+
+    for (row = row0; row < row0 + height; ++row) {
+        int col;
+        for (col = col0; col < col0 + width; ++col)
+            bits[row][col] = color;
+    }
+}
+
+
+
+static void
+get_line_dimensions(const char line[], const struct font * const font_p, 
+                    const float intercharacter_space,
+                    int * const bwid_p, int * const backup_space_needed_p) {
+/*----------------------------------------------------------------------------
+   Determine the width in pixels of the line of text line[] in the font
+   *font_p, and return it as *bwid_p.  Also determine how much of this
+   width goes to the left of the nominal starting point of the line because
+   the first character in the line has a "backup" distance.  Return that
+   as *backup_space_needed_p.
+-----------------------------------------------------------------------------*/
+    int cursor;  /* cursor into the line of text */
+    float accumulated_ics;
+        /* accumulated intercharacter space so far in the line we are 
+           stepping through.  Because the intercharacter space might not be
+           an integer, we accumulate it here and realize full pixels whenever
+           we have more than one pixel.
+           */
+
+    int no_chars_yet; 
+        /* logical: we haven't seen any renderable characters yet in 
+           the line.
+        */
+    struct glyph * lastGlyphP;
+        /* Glyph of last character processed so far.  Undefined if
+           'no_chars_yet'.
+        */
+
+    no_chars_yet = TRUE;   /* initial value */
+    *bwid_p = 0;  /* initial value */
+    accumulated_ics = 0.0;  /* initial value */
+
+    for (cursor = 0; line[cursor] != '\0'; cursor++) {
+        struct glyph * const glyphP = 
+            font_p->glyph[(unsigned char)line[cursor]];
+
+        if (glyphP) {
+            if (no_chars_yet) {
+                no_chars_yet = FALSE;
+                if (glyphP->x < 0) 
+                    *backup_space_needed_p = -glyphP->x;
+                else {
+                    *backup_space_needed_p = 0;
+                    *bwid_p += glyphP->x;
+                }
+            } else {
+                /* handle extra intercharacter space (-space option) */
+                int full_pixels;  /* integer part of accumulated_ics */
+                accumulated_ics += intercharacter_space;
+                full_pixels = (int) accumulated_ics;
+                if (full_pixels > 0) {
+                    *bwid_p += full_pixels;
+                    accumulated_ics -= full_pixels;
+                }
+                lastGlyphP = glyphP;
+            }
+            *bwid_p += glyphP->xadd;
+        }
+    }
+    if (no_chars_yet)
+        /* Line has no renderable characters */
+        *backup_space_needed_p = 0;
+    else {
+        /* Line has at least one renderable character.
+           Recalculate width of last character in line so it ends
+           right at the right edge of the glyph (no extra space to
+           anticipate another character).
+        */
+        *bwid_p -= lastGlyphP->xadd;
+        *bwid_p += lastGlyphP->width + lastGlyphP->x;
+    }
+}
+
+
+
+static void
+insert_character(const struct glyph * const glyph, 
+                 int                  const toprow, 
+                 int                  const leftcol,
+                 bit **               const bits) {
+/*----------------------------------------------------------------------------
+   Insert one character (whose glyph is 'glyph') into the image bits[].
+   Its top left corner shall be row 'toprow', column 'leftcol'.
+-----------------------------------------------------------------------------*/
+
+    int glyph_y, glyph_x;  /* position within the glyph */
+
+    for (glyph_y = 0; glyph_y < glyph->height; glyph_y++) {
+        for (glyph_x = 0; glyph_x < glyph->width; glyph_x++) {
+            if (glyph->bmap[glyph_y * glyph->width + glyph_x])
+                bits[toprow+glyph_y][leftcol+glyph->x+glyph_x] = 
+                    PBM_BLACK;
+        }
+    }
+}    
+
+
+
+static void
+insert_characters(bit **        const bits, 
+                  struct text   const lp,
+                  struct font * const fontP, 
+                  int           const topmargin, 
+                  int           const leftmargin,
+                  float         const intercharacter_space,
+                  int           const lspace) {
+/*----------------------------------------------------------------------------
+   Render the text 'lp' into the image 'bits' using font *fontP and
+   putting 'intercharacter_space' pixels between characters and
+   'lspace' pixels between the lines.
+-----------------------------------------------------------------------------*/
+    int line;  /* Line number in input text */
+
+    for (line = 0; line < lp.lineCount; ++line) {
+        int row;  /* row in image of top of current typeline */
+        int leftcol;  /* Column in image of left edge of current glyph */
+        int cursor;  /* cursor into a line of input text */
+        float accumulated_ics;
+            /* accumulated intercharacter space so far in the line we
+               are building.  Because the intercharacter space might
+               not be an integer, we accumulate it here and realize
+               full pixels whenever we have more than one pixel. 
+            */
+
+        row = topmargin + line * (fontP->maxheight + lspace);
+        leftcol = leftmargin;
+        accumulated_ics = 0.0;  /* initial value */
+    
+        for (cursor = 0; lp.textArray[line][cursor] != '\0'; ++cursor) {
+            unsigned int const glyphIndex = 
+                (unsigned char)lp.textArray[line][cursor];
+            struct glyph* glyph;   /* the glyph for this character */
+
+            glyph = fontP->glyph[glyphIndex];
+            if (glyph != NULL) {
+                const int toprow = row + fontP->maxheight + fontP->y 
+                    - glyph->height - glyph->y;
+                    /* row number in image of top row in glyph */
+                
+                insert_character(glyph, toprow, leftcol, bits);
+
+                leftcol += glyph->xadd;
+                {
+                    /* handle extra intercharacter space (-space option) */
+                    int full_pixels;  /* integer part of accumulated_ics */
+                    accumulated_ics += intercharacter_space;
+                    full_pixels = (int) accumulated_ics;
+                    if (full_pixels > 0) {
+                        leftcol += full_pixels;
+                        accumulated_ics -= full_pixels;
+                    }
+                }
+            }
+        }
+    }
+}
+
+
+struct outputTextCursor {
+    struct text text;
+        /* The output text.  The lineCount field of this represents
+           the number of lines we have completed.  The line after that
+           is the one we are currently filling.
+        */
+    unsigned int maxWidth;
+        /* A line of output can't be wider than this many pixels */
+    float intercharacterSpace;
+        /* The amount of extra space, in characters, that should be added
+           between every two characters (Pbmtext -space option)
+        */
+    unsigned int columnNo;
+        /* The column Number (starting at 0) in the current line that we are
+           filling where the next character goes.
+        */
+    bool noCharsYet;
+        /* We haven't put any renderable characters yet in the
+           output line. 
+        */
+    unsigned int widthSoFar;
+        /* The accumulated width, in pixels, of all the characters now
+           in the current output line 
+        */
+    float accumulatedIcs;
+        /* accumulated intercharacter space so far in the line we
+           are stepping through.  Because the intercharacter space
+           might not be an integer, we accumulate it here and
+           realize full pixels whenever we have more than one
+           pixel.  
+        */
+};
+
+
+
+static void
+initializeFlowedOutputLine(struct outputTextCursor * const cursorP) {
+
+    cursorP->columnNo = 0;
+    cursorP->noCharsYet = TRUE;
+    cursorP->widthSoFar = 0.0;
+    cursorP->accumulatedIcs = 0.0;
+}
+
+
+
+static void
+initializeFlowedOutput(struct outputTextCursor * const cursorP,
+                       unsigned int              const maxLines,
+                       unsigned int              const maxWidth,
+                       float                     const intercharacterSpace) {
+    
+    allocTextArray(&cursorP->text, maxLines, maxWidth);
+    cursorP->maxWidth = maxWidth;
+    cursorP->intercharacterSpace = intercharacterSpace;
+    initializeFlowedOutputLine(cursorP);
+}
+
+
+
+static void
+finishOutputLine(struct outputTextCursor * const cursorP) {
+
+    if (cursorP->text.lineCount < cursorP->text.allocatedLineCount) {
+        char * const currentLine = 
+            cursorP->text.textArray[cursorP->text.lineCount];
+        currentLine[cursorP->columnNo++] = '\0';
+        ++cursorP->text.lineCount;
+    }
+}
+
+
+
+static void
+placeCharacterInOutput(char                      const lastch,
+                       struct font *             const fontP, 
+                       struct outputTextCursor * const cursorP) {
+/*----------------------------------------------------------------------------
+   Place a character of text in the text array at the position indicated
+   by *cursorP, keeping track of what space this character will occupy
+   when this text array is ultimately rendered using font *fontP.
+
+   Note that while we compute how much space the character will take when
+   rendered, we don't render it.
+-----------------------------------------------------------------------------*/
+    if (cursorP->text.lineCount < cursorP->text.allocatedLineCount) {
+        unsigned int const glyphIndex = (unsigned char)lastch;
+        if (fontP->glyph[glyphIndex]) {
+            if (cursorP->noCharsYet) {
+                cursorP->noCharsYet = FALSE;
+                if (fontP->glyph[glyphIndex]->x > 0) 
+                    cursorP->widthSoFar += fontP->glyph[glyphIndex]->x;
+            } else {
+                /* handle extra intercharacter space (-space option) */
+                unsigned int fullPixels;  /* integer part of accumulatedIcs */
+                cursorP->accumulatedIcs += cursorP->intercharacterSpace;
+                fullPixels = (int) cursorP->accumulatedIcs;
+                if (fullPixels > 0) {
+                    cursorP->widthSoFar += fullPixels;
+                    cursorP->accumulatedIcs -= fullPixels;
+                }
+            }
+            cursorP->widthSoFar += fontP->glyph[glyphIndex]->xadd;
+        }
+        if (cursorP->widthSoFar < cursorP->maxWidth) {
+            char * const currentLine = 
+                cursorP->text.textArray[cursorP->text.lineCount];
+            currentLine[cursorP->columnNo++] = lastch;
+        } else {
+            /* Line is full; finish it off, start the next one, and
+               place the character there.
+            */
+            /* TODO: We really should back up to the previous white space
+               character and move the rest of the line to the next line
+            */
+            finishOutputLine(cursorP);
+            initializeFlowedOutputLine(cursorP);
+            placeCharacterInOutput(lastch, fontP, cursorP);
+        }
+    }
+}
+
+
+
+static void
+flowText(struct text    const inputText,
+         int            const width, 
+         struct font *  const fontP, 
+         float          const intercharacterSpace,
+         struct text *  const outputTextP) {
+    
+    unsigned int const maxLineCount = 50;
+
+    unsigned int inputLine;  
+        /* Input line number on which we are currently working */
+    struct outputTextCursor outputCursor;
+
+    for (inputLine = 0; inputLine < inputText.lineCount; ++inputLine) {
+        unsigned int incursor;   /* cursor into the line we are reading */
+
+        initializeFlowedOutput(&outputCursor, maxLineCount,
+                               width, intercharacterSpace);
+        
+        for (incursor = 0; 
+             inputText.textArray[inputLine][incursor] != '\0'; 
+             ++incursor)
+            placeCharacterInOutput(inputText.textArray[inputLine][incursor],
+                                   fontP, &outputCursor);
+        finishOutputLine(&outputCursor);
+    }
+    *outputTextP = outputCursor.text;
+}
+
+
+
+static void
+truncateText(struct text   const inputText, 
+             unsigned int  const width, 
+             struct font * const fontP, 
+             float         const intercharacterSpace,
+             struct text * const outputTextP) {
+
+    struct text truncatedText;
+    int line;  /* Line number on which we are currently working */
+
+    allocTextArray(&truncatedText, inputText.lineCount, width);
+
+    for (line = 0; line < inputText.lineCount; ++line){
+        int cursor;  /* cursor into the line of text */
+        unsigned char lastch;  /* line[cursor] */
+        int widthSoFar;
+            /* How long the line we've built, in pixels, is so far */
+        float accumulatedIcs;
+        /* accumulated intercharacter space so far in the line we are 
+           stepping through.  Because the intercharacter space might not be
+           an integer, we accumulate it here and realize full pixels whenever
+           we have more than one pixel.
+           */
+
+        int noCharsYet; 
+        /* logical: we haven't seen any renderable characters yet in 
+           the line.
+        */
+        noCharsYet = TRUE;   /* initial value */
+        widthSoFar = 0;  /* initial value */
+        accumulatedIcs = 0.0;  /* initial value */
+
+        truncatedText.textArray[line][0] = '\0';  /* Start with empty line */
+
+        for (cursor = 0; 
+             inputText.textArray[line][cursor] != '\0' && widthSoFar < width; 
+             cursor++) {
+            lastch = inputText.textArray[line][cursor];
+            if (fontP->glyph[(unsigned char)lastch]) {
+                if (noCharsYet) {
+                    noCharsYet = FALSE;
+                    if (fontP->glyph[lastch]->x > 0) 
+                        widthSoFar += fontP->glyph[lastch]->x;
+                } else {
+                    /* handle extra intercharacter space (-space option) */
+                    int fullPixels;  /* integer part of accumulatedIcs */
+                    accumulatedIcs += intercharacterSpace;
+                    fullPixels = (int) intercharacterSpace;
+                    if (fullPixels > 0) {
+                        widthSoFar += fullPixels;
+                        accumulatedIcs -= fullPixels;
+                    }
+                }
+                widthSoFar += fontP->glyph[lastch]->xadd;
+            }
+            if (widthSoFar < width) {
+                truncatedText.textArray[line][cursor] = 
+                    inputText.textArray[line][cursor];
+                truncatedText.textArray[line][cursor+1] = '\0';
+            }
+        }
+    }
+    truncatedText.lineCount = inputText.lineCount;
+    *outputTextP = truncatedText;
+}
+
+
+
+static void
+getText(const char          cmdline_text[], 
+        struct font * const fn, 
+        struct text * const input_textP) {
+
+    struct text input_text;
+
+    if (cmdline_text) {
+        allocTextArray(&input_text, 1, strlen(cmdline_text));
+        strcpy(input_text.textArray[0], cmdline_text);
+        fix_control_chars(input_text.textArray[0], fn);
+        input_text.lineCount = 1;
+    } else {
+        /* Read text from stdin. */
+
+        unsigned int maxlines;  
+            /* Maximum number of lines for which we presently have space
+               in the text array 
+            */
+        char buf[5000];
+        char ** text_array;
+        unsigned int lineCount;
+
+        maxlines = 50;  /* initial value */
+        MALLOCARRAY_NOFAIL(text_array, maxlines);
+        
+        lineCount = 0;  /* initial value */
+        while (fgets(buf, sizeof(buf), stdin) != NULL) {
+            fix_control_chars(buf, fn);
+            if (lineCount >= maxlines) {
+                maxlines *= 2;
+                text_array = (char**) realloc((char*) text_array, 
+                                              maxlines * sizeof(char*));
+                if (text_array == NULL)
+                    pm_error("out of memory");
+            }
+            text_array[lineCount] = strdup(buf);
+            if (text_array[lineCount] == NULL)
+                pm_error("out of memory");
+            ++lineCount;
+        }
+        input_text.textArray = text_array;
+        input_text.lineCount = lineCount;
+        input_text.allocatedLineCount = lineCount;
+    }
+    *input_textP = input_text;
+}
+
+
+
+static void
+compute_image_width(struct text         const lp, 
+                    const struct font * const fn,
+                    float               const intercharacter_space,
+                    int *               const maxwidthP, 
+                    int *               const maxleftbP) {
+    int line;
+    
+    *maxwidthP = 0;  /* initial value */
+    *maxleftbP = 0;  /* initial value */
+    for (line = 0; line < lp.lineCount; ++line) {
+        int bwid, backup_space_needed;
+        
+        get_line_dimensions(lp.textArray[line], fn, intercharacter_space,
+                            &bwid, &backup_space_needed);
+        
+        *maxwidthP = MAX(*maxwidthP, bwid);
+        *maxleftbP = MAX(*maxleftbP, backup_space_needed);
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdline_info cmdline;
+    bit** bits;
+    int rows, cols;
+    struct font* fontP;
+    int vmargin, hmargin;
+    struct text inputText;
+    struct text formattedText;
+    int maxwidth;
+        /* width in pixels of the longest line of text in the image */
+    int maxleftb;
+
+    pbm_init( &argc, argv );
+
+    parse_command_line(argc, argv, &cmdline);
+    
+    if (cmdline.font)
+        fontP = pbm_loadfont(cmdline.font);
+    else {
+        if (cmdline.builtin)
+            fontP = pbm_defaultfont(cmdline.builtin);
+        else
+            fontP = pbm_defaultfont("bdf");
+    }
+
+    if (cmdline.dump) {
+        pbm_dumpfont(fontP);
+        exit(0);
+    }
+
+    getText(cmdline.text, fontP, &inputText);
+    
+    if (cmdline.nomargins) {
+        vmargin = 0;
+        hmargin = 0;
+    } else {
+        if (inputText.lineCount == 1) {
+            vmargin = fontP->maxheight / 2;
+            hmargin = fontP->maxwidth;
+        } else {
+            vmargin = fontP->maxheight;
+            hmargin = 2 * fontP->maxwidth;
+        }
+    }
+
+    if (cmdline.width > 0) {
+        /* Flow or truncate lines to meet user's width request */
+        if (inputText.lineCount == 1) 
+            flowText(inputText, cmdline.width, fontP, cmdline.space,
+                     &formattedText);
+        else
+            truncateText(inputText, cmdline.width, fontP, cmdline.space,
+                         &formattedText);
+        freeTextArray(inputText);
+    } else
+        formattedText = inputText;
+    
+    rows = 2 * vmargin + 
+        formattedText.lineCount * fontP->maxheight + 
+        (formattedText.lineCount-1) * cmdline.lspace;
+
+    compute_image_width(formattedText, fontP, cmdline.space,
+                        &maxwidth, &maxleftb);
+
+    cols = 2 * hmargin + maxwidth;
+    bits = pbm_allocarray(cols, rows);
+
+    /* Fill background with white */
+    fill_rect(bits, 0, 0, rows, cols, PBM_WHITE);
+
+    /* Put the text in  */
+    insert_characters(bits, formattedText, fontP, vmargin, hmargin + maxleftb, 
+                      cmdline.space, cmdline.lspace);
+
+    /* All done. */
+    pbm_writepbm(stdout, bits, cols, rows, 0);
+
+    freeTextArray(formattedText);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/generator/pbmtextps.c b/generator/pbmtextps.c
new file mode 100644
index 00000000..ef8ae008
--- /dev/null
+++ b/generator/pbmtextps.c
@@ -0,0 +1,377 @@
+/*
+ * pbmtextps.c -  render text into a bitmap using a postscript interpreter
+ *
+ * Copyright (C) 2002 by James McCann.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  This software is provided "as is" without express or
+ * implied warranty.
+ *
+ * PostScript is a registered trademark of Adobe Systems International.
+ *
+ * Additions by Bryan Henderson contributed to public domain by author.
+ *
+ */
+#define _XOPEN_SOURCE   /* Make sure popen() is in stdio.h */
+#define _BSD_SOURCE     /* Make sure stdrup() is in string.h */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "pbm.h"
+#include "nstring.h"
+#include "shhopt.h"
+
+
+#define BUFFER_SIZE 2048
+
+static const char *gs_exe_path = 
+#ifdef GHOSTSCRIPT_EXECUTABLE_PATH
+GHOSTSCRIPT_EXECUTABLE_PATH;
+#else
+0;
+#endif
+
+static const char *pnmcrop_exe_path = 
+#ifdef PNMCROP_EXECUTABLE_PATH
+PNMCROP_EXECUTABLE_PATH;
+#else
+0;
+#endif
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    int          res;         /* resolution, DPI */
+    int          fontsize;    /* Size of font in points */
+    const char * font;      /* Name of postscript font */
+    float        stroke;
+        /* Width of stroke in points (only for outline font) */
+    unsigned int verbose;
+    const char * text;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*---------------------------------------------------------------------------
+  Note that the file spec array we return is stored in the storage that
+  was passed to us as the argv array.
+---------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+    /* Instructions to OptParseOptions2 on how to parse our options.
+   */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    int i;
+    char * text;
+    int totaltextsize = 0;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "resolution", OPT_INT,    &cmdlineP->res,            NULL,  0);
+    OPTENT3(0, "font",       OPT_STRING, &cmdlineP->font,           NULL,  0);
+    OPTENT3(0, "fontsize",   OPT_INT,    &cmdlineP->fontsize,       NULL,  0);
+    OPTENT3(0, "stroke",     OPT_FLOAT,  &cmdlineP->stroke,         NULL,  0);
+    OPTENT3(0, "verbose",    OPT_FLAG,   NULL, &cmdlineP->verbose,         0);
+
+    /* Set the defaults */
+    cmdlineP->res = 150;
+    cmdlineP->fontsize = 24;
+    cmdlineP->font = "Times-Roman";
+    cmdlineP->stroke = -1;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+    text = NULL;
+
+    for (i = 1; i < argc; i++) {
+        if (i > 1) {
+            totaltextsize += 1;
+            text = realloc(text, totaltextsize);
+            if (text == NULL)
+                pm_error("out of memory");
+            strcat(text, " ");
+        } 
+        totaltextsize += strlen(argv[i]);
+        text = realloc(text, totaltextsize);
+        if (text == NULL)
+            pm_error("out of memory");
+        strcat(text, argv[i]);
+    }
+    cmdlineP->text = text;
+}
+
+
+
+static const char *
+construct_postscript(struct cmdlineInfo const cmdl) {
+
+    const char * retval;
+    const char * template;
+
+    if (cmdl.stroke <= 0) 
+        template = "/%s findfont\n%d scalefont\nsetfont\n12 36 moveto\n"
+            "(%s) show\nshowpage\n";
+    else 
+        template = "/%s findfont\n%d scalefont\nsetfont\n12 36 moveto\n"
+            "%f setlinewidth\n0 setgray\n"
+            "(%s) true charpath\nstroke\nshowpage\n";
+
+    if (cmdl.stroke < 0)
+        asprintfN(&retval, template, cmdl.font, cmdl.fontsize, 
+                  cmdl.text);
+    else
+        asprintfN(&retval, template, cmdl.font, cmdl.fontsize, 
+                  cmdl.stroke, cmdl.text);
+
+    return retval;
+}
+
+
+
+static const char *
+gs_executable_name()
+{
+    static char buffer[BUFFER_SIZE];
+    if(! gs_exe_path) {
+        const char * const which = "which gs";
+        FILE *f;
+        memset(buffer, 0, BUFFER_SIZE);
+        if(!(f = popen(which, "r")))
+            pm_error("Can't find ghostscript");
+        fread(buffer, 1, BUFFER_SIZE, f);
+        if(buffer[strlen(buffer) - 1] == '\n')
+            buffer[strlen(buffer) - 1] = 0;
+        pclose(f);
+        if(buffer[0] != '/' && buffer[0] != '.')
+            pm_error("Can't find ghostscript");
+    }
+    else
+        strcpy(buffer, gs_exe_path);
+
+    return buffer;
+}
+
+
+
+static const char *
+crop_executable_name()
+{
+    static char buffer[BUFFER_SIZE];
+    if(! pnmcrop_exe_path) {
+        const char * const which = "which pnmcrop";
+        FILE *f;
+        memset(buffer, 0, BUFFER_SIZE);
+        if(!(f = popen(which, "r"))) {
+            return 0;
+        }
+    
+        fread(buffer, 1, BUFFER_SIZE, f);
+        if(buffer[strlen(buffer) - 1] == '\n')
+            buffer[strlen(buffer) - 1] = 0;
+        pclose(f);
+        if(buffer[0] != '/' && buffer[0] != '.') {
+            buffer[0] = 0;
+            pm_message("Can't find pnmcrop");
+        }
+    }
+    else
+        strcpy(buffer, pnmcrop_exe_path);
+
+    return buffer;
+}
+
+
+
+static const char *
+gsCommand(const char *       const psFname,
+          const char *       const outputFilename, 
+          struct cmdlineInfo const cmdline) {
+
+    const char * retval;
+    int const x = cmdline.res * 11;
+    int const y = cmdline.res * (cmdline.fontsize * 2 + 72)  / 72.;
+    asprintfN(&retval, "%s -g%dx%d -r%d -sDEVICE=pbm "
+              "-sOutputFile=%s -q -dBATCH -dNOPAUSE %s </dev/null >/dev/null", 
+              gs_executable_name(), x, y, cmdline.res, 
+              outputFilename, psFname);
+    return retval;
+}
+
+
+
+static const char *
+cropCommand(const char * const inputFileName) {
+
+    const char * retval;
+    
+    if (crop_executable_name()) {
+        asprintfN(&retval, "%s -top -right %s", 
+                  crop_executable_name(), inputFileName);
+        if (retval == NULL)
+            pm_error("Unable to allocate memory");
+    } else
+        retval = NULL;
+
+    return retval;
+}
+
+
+
+static void
+writeProgram(const char *       const psFname,
+             struct cmdlineInfo const cmdline) {
+
+    const char *ps;
+    FILE * psfile;
+
+    psfile = fopen(psFname, "w");
+    if (psfile == NULL)
+        pm_error("Can't open temp file '%s'.  Errno=%d (%s)",
+                 psFname, errno, strerror(errno));
+
+    ps = construct_postscript(cmdline);
+
+    if (cmdline.verbose)
+        pm_message("Postscript program = '%s'", ps);
+        
+    if (fwrite(ps, 1, strlen(ps), psfile) != strlen(ps))
+        pm_error("Can't write postscript to temp file");
+
+    fclose(psfile);
+
+    strfree(ps);
+}
+
+
+
+static void
+executeProgram(const char *       const psFname, 
+               const char *       const outputFname,
+               struct cmdlineInfo const cmdline) {
+
+    const char * com;
+    int rc;
+
+    com = gsCommand(psFname, outputFname, cmdline);
+    if (com == NULL)
+        pm_error("Can't allocate memory for a 'ghostscript' command");
+    
+    if (cmdline.verbose)
+        pm_message("Running Postscript interpreter '%s'", com);
+
+    rc = system(com);
+    if (rc != 0)
+        pm_error("Failed to run Ghostscript process.  rc=%d", rc);
+
+    strfree(com);
+}
+
+
+
+static void
+cropToStdout(const char * const inputFileName,
+             bool         const verbose) {
+
+    const char * com;
+
+    com = cropCommand(inputFileName);
+    if (com == NULL) {
+        /* No pnmcrop.  So don't crop. */
+        pm_message("Can't find pnmcrop command, image will be large");
+        asprintfN(&com, "cat %s", inputFileName);
+        if (com == NULL) 
+            pm_error("Unable to allocate memory.");
+    } else {
+        FILE *pnmcrop;
+
+        if (verbose)
+            pm_message("Running crop command '%s'", com);
+        
+        pnmcrop = popen(com, "r");
+        if (pnmcrop == NULL)
+            pm_error("Can't run pnmcrop process");
+        else {
+            char buf[2048];
+            bool eof;
+
+            eof = FALSE;
+            
+            while (!eof) {
+                int bytesRead;
+
+                bytesRead = fread(buf, 1, sizeof(buf), pnmcrop);
+                if (bytesRead > 0) {
+                    int rc;
+                    rc = fwrite(buf, 1, bytesRead, stdout);
+                    if (rc != bytesRead)
+                        pm_error("Can't write to stdout");
+                } else if (bytesRead == 0)
+                    eof = TRUE;
+                else
+                    pm_error("Failed to read output of Pnmcrop process.  "
+                             "Errno=%d (%s)", errno, strerror(errno));
+            }
+            fclose(pnmcrop);
+        }
+    }
+    strfree(com);
+}
+
+
+
+static void
+createOutputFile(struct cmdlineInfo const cmdline) {
+
+    const char * const template = "./pstextpbm.%d.tmp.%s";
+    
+    const char * psFname;
+    const char * uncroppedPbmFname;
+
+    asprintfN(&psFname, template, getpid(), "ps");
+    if (psFname == NULL)
+        pm_error("Unable to allocate memory");
+ 
+    writeProgram(psFname, cmdline);
+
+    asprintfN(&uncroppedPbmFname, template, getpid(), "pbm");
+    if (uncroppedPbmFname == NULL)
+        pm_error("Unable to allocate memory");
+ 
+    executeProgram(psFname, uncroppedPbmFname, cmdline);
+
+    unlink(psFname);
+    strfree(psFname);
+
+    cropToStdout(uncroppedPbmFname, cmdline.verbose);
+
+    unlink(uncroppedPbmFname);
+    strfree(uncroppedPbmFname);
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+
+    pbm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    createOutputFile(cmdline);
+
+    return 0;
+}
diff --git a/generator/pbmupc.c b/generator/pbmupc.c
new file mode 100644
index 00000000..5f694a00
--- /dev/null
+++ b/generator/pbmupc.c
@@ -0,0 +1,544 @@
+/* pbmupc.c - create a Universal Product Code bitmap
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "pbm.h"
+
+#define MARGIN 20
+#define DIGIT_WIDTH 14
+#define DIGIT_HEIGHT 23
+#define LINE1_WIDTH 2
+
+#define LINE2_WIDTH ( 2 * LINE1_WIDTH )
+#define LINE3_WIDTH ( 3 * LINE1_WIDTH )
+#define LINE4_WIDTH ( 4 * LINE1_WIDTH )
+#define LINES_WIDTH ( 7 * LINE1_WIDTH )
+#define SHORT_HEIGHT ( 8 * LINES_WIDTH )
+#define TALL_HEIGHT ( SHORT_HEIGHT + DIGIT_HEIGHT / 2 )
+
+static int alldig ARGS(( char* cp ));
+static void putdigit ARGS(( int d, bit** bits, int row0, int col0 ));
+static int addlines ARGS(( int d, bit** bits, int row0, int col0, int height, bit color ));
+static int rect ARGS(( bit** bits, int row0, int col0, int height, int width, bit color ));
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    register bit** bits;
+    int argn, style, rows, cols, row, digrow, col, digcolofs;
+    char* typecode;
+    char* manufcode;
+    char* prodcode;
+    int sum, p, lc0, lc1, lc2, lc3, lc4, rc0, rc1, rc2, rc3, rc4;
+    const char* const usage = "[-s1|-s2] <type> <manufac> <product>";
+
+
+    pbm_init( &argc, argv );
+
+    argn = 1;
+    style = 1;
+
+    /* Check for flags. */
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+	{
+	if ( pm_keymatch( argv[argn], "-s1", 3 ) )
+	    style = 1;
+	else if ( pm_keymatch( argv[argn], "-s2", 3 ) )
+	    style = 2;
+	else
+	    pm_usage( usage );
+	argn++;
+	}
+
+    if ( argn + 3 < argc )
+	pm_usage( usage );
+    typecode = argv[argn];
+    manufcode = argv[argn + 1];
+    prodcode = argv[argn + 2];
+    argn += 3;
+
+    if ( argn != argc )
+	pm_usage( usage );
+
+    if ( strlen( typecode ) != 1 || ( ! alldig( typecode ) ) ||
+	 strlen( manufcode ) != 5 || ( ! alldig ( manufcode ) ) ||
+	 strlen( prodcode ) != 5 || ( ! alldig ( prodcode ) ) )
+	pm_error(
+	    "type code must be one digit, and\n    manufacturer and product codes must be five digits" );
+    p = typecode[0] - '0';
+    lc0 = manufcode[0] - '0';
+    lc1 = manufcode[1] - '0';
+    lc2 = manufcode[2] - '0';
+    lc3 = manufcode[3] - '0';
+    lc4 = manufcode[4] - '0';
+    rc0 = prodcode[0] - '0';
+    rc1 = prodcode[1] - '0';
+    rc2 = prodcode[2] - '0';
+    rc3 = prodcode[3] - '0';
+    rc4 = prodcode[4] - '0';
+    sum = ( 10 - ( ( ( p + lc1 + lc3 + rc0 + rc2 + rc4 ) * 3 + lc0 + lc2 + lc4 + rc1 + rc3 ) % 10 ) ) % 10;
+
+    rows = 2 * MARGIN + SHORT_HEIGHT + DIGIT_HEIGHT;
+    cols = 2 * MARGIN + 12 * LINES_WIDTH + 11 * LINE1_WIDTH;
+    bits = pbm_allocarray( cols, rows );
+
+    (void) rect( bits, 0, 0, rows, cols, PBM_WHITE );
+    
+    row = MARGIN;
+    digrow = row + SHORT_HEIGHT;
+    col = MARGIN;
+    digcolofs = ( LINES_WIDTH - DIGIT_WIDTH ) / 2;
+
+    if ( style == 1 )
+	putdigit( p, bits, digrow, col - DIGIT_WIDTH - LINE1_WIDTH );
+    else if ( style == 2 )
+	putdigit(
+	    p, bits, row + SHORT_HEIGHT / 2, col - DIGIT_WIDTH - LINE1_WIDTH );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_WHITE );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK );
+    col = addlines( p, bits, row, col, TALL_HEIGHT, PBM_WHITE );
+    putdigit( lc0, bits, digrow, col + digcolofs );
+    col = addlines( lc0, bits, row, col, SHORT_HEIGHT, PBM_WHITE );
+    putdigit( lc1, bits, digrow, col + digcolofs );
+    col = addlines( lc1, bits, row, col, SHORT_HEIGHT, PBM_WHITE );
+    putdigit( lc2, bits, digrow, col + digcolofs );
+    col = addlines( lc2, bits, row, col, SHORT_HEIGHT, PBM_WHITE );
+    putdigit( lc3, bits, digrow, col + digcolofs );
+    col = addlines( lc3, bits, row, col, SHORT_HEIGHT, PBM_WHITE );
+    putdigit( lc4, bits, digrow, col + digcolofs );
+    col = addlines( lc4, bits, row, col, SHORT_HEIGHT, PBM_WHITE );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_WHITE );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_WHITE );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_WHITE );
+    putdigit( rc0, bits, digrow, col + digcolofs );
+    col = addlines( rc0, bits, row, col, SHORT_HEIGHT, PBM_BLACK );
+    putdigit( rc1, bits, digrow, col + digcolofs );
+    col = addlines( rc1, bits, row, col, SHORT_HEIGHT, PBM_BLACK );
+    putdigit( rc2, bits, digrow, col + digcolofs );
+    col = addlines( rc2, bits, row, col, SHORT_HEIGHT, PBM_BLACK );
+    putdigit( rc3, bits, digrow, col + digcolofs );
+    col = addlines( rc3, bits, row, col, SHORT_HEIGHT, PBM_BLACK );
+    putdigit( rc4, bits, digrow, col + digcolofs );
+    col = addlines( rc4, bits, row, col, SHORT_HEIGHT, PBM_BLACK );
+    col = addlines( sum, bits, row, col, TALL_HEIGHT, PBM_BLACK );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_WHITE );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK );
+    if ( style == 1 )
+	putdigit( sum, bits, digrow, col + LINE1_WIDTH );
+
+    pbm_writepbm( stdout, bits, cols, rows, 0 );
+    pm_close( stdout );
+
+    exit( 0 );
+    }
+
+static int
+alldig( cp )
+    char* cp;
+    {
+    for ( ; *cp != '\0'; cp++ )
+	if ( *cp < '0' || *cp > '9' )
+	    return 0;
+    return 1;
+    }
+
+static void
+putdigit( d, bits, row0, col0 )
+    int d;
+    bit** bits;
+    int row0, col0;
+    {
+    int row, col;
+    static bit digits[10][DIGIT_HEIGHT][DIGIT_WIDTH] = {
+        /* 0 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,1,1,1,1,1,1,0,0,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,1,1,1,0,0,0,0,1,1,1,0,0},
+            {0,0,1,1,0,0,0,0,0,0,1,1,0,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,0,1,1,0,0,0,0,0,0,1,1,0,0},
+            {0,0,1,1,1,0,0,0,0,1,1,1,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,0,0,1,1,1,1,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        },
+        /* 1 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,0,0,0,0,0,0},
+            {0,0,0,0,1,1,1,1,0,0,0,0,0,0},
+            {0,0,0,1,1,1,1,1,0,0,0,0,0,0},
+            {0,0,1,1,1,0,1,1,0,0,0,0,0,0},
+            {0,0,1,1,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        },
+        /* 2 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,1,0,0,0,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,1,1,1,1,0,0,1,1,1,1,0,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,0,0},
+            {0,1,1,0,0,0,0,0,0,0,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,0,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,0,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,1,1,1,0,0},
+            {0,0,0,0,0,0,0,0,1,1,1,0,0,0},
+            {0,0,0,0,0,0,0,1,1,1,0,0,0,0},
+            {0,0,0,0,0,0,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,0,0,0,0,0,0},
+            {0,0,0,0,1,1,1,0,0,0,0,0,0,0},
+            {0,0,0,1,1,1,0,0,0,0,0,0,0,0},
+            {0,0,1,1,1,0,0,0,0,0,0,0,0,0},
+            {0,1,1,1,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        },
+        /* 3 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,1,1,1,0,0},
+            {0,0,0,0,0,0,0,0,1,1,1,0,0,0},
+            {0,0,0,0,0,0,0,1,1,1,0,0,0,0},
+            {0,0,0,0,0,0,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,1,1,1,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,1,1,0,0},
+            {0,0,0,0,0,0,0,0,0,0,1,1,0,0},
+            {0,0,0,0,0,0,0,0,0,0,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,0,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,1,1,1,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,0,0},
+            {0,0,1,1,1,1,0,0,1,1,1,1,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,0,0,0,1,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        },
+        /* 4 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,0,0,0,0,0,0,0},
+            {0,0,0,0,1,1,1,0,0,0,0,0,0,0},
+            {0,0,0,0,1,1,0,0,0,0,0,0,0,0},
+            {0,0,0,1,1,1,0,0,0,0,0,0,0,0},
+            {0,0,0,1,1,0,0,0,1,1,0,0,0,0},
+            {0,0,1,1,1,0,0,0,1,1,0,0,0,0},
+            {0,0,1,1,0,0,0,0,1,1,0,0,0,0},
+            {0,1,1,1,0,0,0,0,1,1,0,0,0,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,0,0,0,0,0,0,0,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        },
+        /* 5 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,0,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,0,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,0,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,0,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,1,1,1,1,1,1,0,0,0,0,0},
+            {0,1,1,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,1,1,0,0},
+            {0,0,0,0,0,0,0,0,0,0,1,1,0,0},
+            {0,0,0,0,0,0,0,0,0,0,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,0,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,1,1,1,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,0,0},
+            {0,0,1,1,1,1,0,0,1,1,1,1,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,0,0,0,1,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        },
+        /* 6 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,1,1,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,0,0,0,0,0,0},
+            {0,0,0,0,1,1,1,0,0,0,0,0,0,0},
+            {0,0,0,1,1,1,0,0,0,0,0,0,0,0},
+            {0,0,0,1,1,0,0,0,0,0,0,0,0,0},
+            {0,0,1,1,1,0,0,0,0,0,0,0,0,0},
+            {0,0,1,1,0,1,1,1,1,0,0,0,0,0},
+            {0,0,1,1,1,1,1,1,1,1,1,0,0,0},
+            {0,1,1,1,1,1,0,0,1,1,1,1,0,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,0,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,0,1,1,0,0,0,0,0,0,1,1,0,0},
+            {0,0,1,1,1,1,0,0,1,1,1,1,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,0,0,0,1,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        },
+        /* 7 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,1,1,0,0},
+            {0,0,0,0,0,0,0,0,0,1,1,1,0,0},
+            {0,0,0,0,0,0,0,0,0,1,1,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,1,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,1,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,1,1,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,0,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,0,0,0,0,0,0,0},
+            {0,0,0,0,1,1,1,0,0,0,0,0,0,0},
+            {0,0,0,0,1,1,0,0,0,0,0,0,0,0},
+            {0,0,0,0,1,1,0,0,0,0,0,0,0,0},
+            {0,0,0,0,1,1,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        },
+        /* 8 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,1,1,1,1,1,1,1,1,1,1,0,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,0,1,1,1,0,0,0,0,1,1,1,0,0},
+            {0,0,0,1,1,1,0,0,1,1,1,0,0,0},
+            {0,0,0,0,1,1,1,1,1,1,0,0,0,0},
+            {0,0,0,0,1,1,1,1,1,1,0,0,0,0},
+            {0,0,0,1,1,1,0,0,1,1,1,0,0,0},
+            {0,0,1,1,1,0,0,0,0,1,1,1,0,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,0,1,1,1,1,1,1,1,1,1,1,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        }, 
+        /* 9 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,1,0,0,0,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,1,1,1,1,0,0,1,1,1,1,0,0},
+            {0,0,1,1,0,0,0,0,0,0,1,1,0,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,0,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,0,1,1,1,1,0,0,1,1,1,1,1,0},
+            {0,0,0,1,1,1,1,1,1,1,1,1,0,0},
+            {0,0,0,0,0,1,1,1,1,0,1,1,0,0},
+            {0,0,0,0,0,0,0,0,0,1,1,1,0,0},
+            {0,0,0,0,0,0,0,0,0,1,1,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,1,0,0,0},
+            {0,0,0,0,0,0,0,1,1,1,0,0,0,0},
+            {0,0,0,0,0,0,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        } 
+    };
+
+    for ( row = 0; row < DIGIT_HEIGHT; row++ )
+	for ( col = 0; col < DIGIT_WIDTH; col++ )
+	    bits[row0 + row][col0 + col] = digits[d][row][col];
+    }
+
+#if __STDC__
+static int
+addlines( int d, bit** bits, int row0, int col0, int height, bit color )
+#else /*__STDC__*/
+static int
+addlines( d, bits, row0, col0, height, color )
+    int d;
+    bit** bits;
+    int row0, col0, height;
+    bit color;
+#endif /*__STDC__*/
+    {
+    switch ( d )
+	{
+	case 0:
+	col0 = rect( bits, row0, col0, height, LINE3_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color );
+	break;
+
+	case 1:
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color );
+	break;
+
+	case 2:
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color );
+	break;
+
+	case 3:
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE4_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color );
+	break;
+
+	case 4:
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE3_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color );
+	break;
+
+	case 5:
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE3_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color );
+	break;
+
+	case 6:
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE4_WIDTH, 1 - color );
+	break;
+
+	case 7:
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE3_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color );
+	break;
+
+	case 8:
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE3_WIDTH, 1 - color );
+	break;
+
+	case 9:
+	col0 = rect( bits, row0, col0, height, LINE3_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color );
+	break;
+
+	default:
+	pm_error( "can't happen" );
+	}
+
+    return col0;
+    }
+
+#if __STDC__
+static int
+rect( bit** bits, int row0, int col0, int height, int width, bit color )
+#else /*__STDC__*/
+static int
+rect( bits, row0, col0, height, width, color )
+    bit** bits;
+    int row0, col0, height, width;
+    bit color;
+#endif /*__STDC__*/
+    {
+    int row, col;
+
+    for ( row = row0; row < row0 + height; row++ )
+	for ( col = col0; col < col0 + width; col++ )
+	    bits[row][col] = color;
+    return col0 + width;
+    }
diff --git a/generator/pgmcrater.c b/generator/pgmcrater.c
new file mode 100644
index 00000000..1833e604
--- /dev/null
+++ b/generator/pgmcrater.c
@@ -0,0 +1,383 @@
+/*
+
+			  Fractal cratering
+
+	   Designed and implemented in November of 1989 by:
+
+	    John Walker
+	    Autodesk SA
+	    Avenue des Champs-Montants 14b
+	    CH-2074 MARIN
+	    Switzerland
+	    Usenet: kelvin@Autodesk.com
+	    Fax:    038/33 88 15
+	    Voice:  038/33 76 33
+
+    The  algorithm  used  to  determine crater size is as described on
+    pages 31 and 32 of:
+
+	Peitgen, H.-O., and Saupe, D. eds., The Science Of Fractal
+	    Images, New York: Springer Verlag, 1988.
+
+    The  mathematical  technique  used	to calculate crater radii that
+    obey the proper area law distribution from a uniformly distributed
+    pseudorandom sequence was developed by Rudy Rucker.
+
+    Permission	to  use, copy, modify, and distribute this software and
+    its documentation  for  any  purpose  and  without	fee  is  hereby
+    granted,  without any conditions or restrictions.  This software is
+    provided "as is" without express or implied warranty.
+
+				PLUGWARE!
+
+    If you like this kind of stuff, you may also enjoy "James  Gleick's
+    Chaos--The  Software"  for  MS-DOS,  available for $59.95 from your
+    local software store or directly from Autodesk, Inc., Attn: Science
+    Series,  2320  Marinship Way, Sausalito, CA 94965, USA.  Telephone:
+    (800) 688-2344 toll-free or, outside the  U.S. (415)  332-2344  Ext
+    4886.   Fax: (415) 289-4718.  "Chaos--The Software" includes a more
+    comprehensive   fractal    forgery	  generator    which	creates
+    three-dimensional  landscapes  as  well as clouds and planets, plus
+    five more modules which explore other aspects of Chaos.   The  user
+    guide  of  more  than  200	pages includes an introduction by James
+    Gleick and detailed explanations by Rudy Rucker of the  mathematics
+    and algorithms used by each program.
+
+*/
+
+/* Modifications by Arjen Bax, 2001-06-21: Remove black vertical line at right
+ * edge. Make craters wrap around the image (enables tiling of image).
+ */
+
+#define _XOPEN_SOURCE   /* get M_PI in math.h */
+
+#include <assert.h>
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "pgm.h"
+#include "mallocvar.h"
+
+static void gencraters ARGS((void));
+static void initseed ARGS((void));
+
+/* Definitions for obtaining random numbers. */
+
+#define Cast(low, high) ((low)+((high)-(low)) * ((rand() & 0x7FFF) / arand))
+
+/*  Data types	*/
+
+typedef int Boolean;
+#define FALSE 0
+#define TRUE 1
+
+#define V   (void)
+
+/*  Display parameters	*/
+
+#define SCRX	screenxsize	      /* Screen width */
+#define SCRY	screenysize	      /* Screen height */
+#define SCRGAMMA 1.0		      /* Display gamma */
+
+/*  Local variables  */
+
+#define ImageGamma  0.5 	      /* Inherent gamma of mapped image */
+
+static int screenxsize = 256;	      /* Screen X size */
+static int screenysize = 256;	      /* Screen Y size */
+static double dgamma = SCRGAMMA;      /* Display gamma */
+static double arand = 32767.0;	      /* Random number parameters */
+static long ncraters = 50000L;	      /* Number of craters to generate */
+static double CdepthPower = 1.5;      /* Crater depth power factor */
+static double DepthBias = 0.707107;   /* Depth bias */
+
+static int modulo(int t, int n)
+{
+    int m;
+    assert(n>0);
+    m = t % n;
+    while (m<0) {
+	m+=n;
+    }
+    return m;
+}
+
+/*  INITSEED  --  Generate initial random seed, if needed.  */
+
+static void initseed()
+{
+    int i;
+
+    i = time(NULL) * 0xF37C;
+    srand(i);
+    for (i = 0; i < 7; i++) 
+        V rand();
+}
+
+/*  GENCRATERS	--  Generate cratered terrain.	*/
+
+static void gencraters()
+{
+    int i, j, x, y;
+    long l;
+    unsigned short *aux;
+    int slopemin = -52, slopemax = 52;
+#define RGBQuant    255
+    unsigned char *slopemap;   /* Slope to pixel map */
+    gray *pixels;	       /* Pixel vector */
+
+#define Auxadr(x, y)  &aux[modulo(y, SCRY)*SCRX+modulo(x, SCRX)]
+
+    /* Acquire the elevation array and initialize it to mean
+       surface elevation. */
+
+    MALLOCARRAY(aux, SCRX * SCRY);
+    if (aux == NULL) 
+        pm_error("out of memory allocating elevation array");
+
+    /* Acquire the elevation buffer and initialize to mean
+       initial elevation. */
+
+    for (i = 0; i < SCRY; i++) {
+	unsigned short *zax = aux + (((long) SCRX) * i);
+
+	for (j = 0; j < SCRX; j++) {
+	    *zax++ = 32767;
+	}
+    }
+
+    /* Every time we go around this loop we plop another crater
+       on the surface.	*/
+
+    for (l = 0; l < ncraters; l++) {
+	double g;
+	int cx = Cast(0.0, ((double) SCRX - 1)),
+	    cy = Cast(0.0, ((double) SCRY - 1)),
+	    gx, gy, x, y;
+	unsigned long amptot = 0, axelev;
+	unsigned int npatch = 0;
+
+
+	/* Phase 1.  Compute the mean elevation of the impact
+		     area.  We assume the impact area is a
+		     fraction of the total crater size. */
+
+	/* Thanks, Rudy, for this equation  that maps the uniformly
+	   distributed	numbers  from	Cast   into   an   area-law
+	   distribution as observed on cratered bodies. */
+
+	g = sqrt(1 / (M_PI * (1 - Cast(0, 0.9999))));
+
+	/* If the crater is tiny, handle it specially. */
+
+#if 0
+	fprintf(stderr, "crater=%lu ", (unsigned long)l);
+	fprintf(stderr, "cx=%d ", cx);
+	fprintf(stderr, "cy=%d ", cy);
+	fprintf(stderr, "size=%g\n", g);
+#endif
+
+	if (g < 3) {
+
+	   /* Set pixel to the average of its Moore neighbourhood. */
+
+	    for (y = cy - 1; y <= cy + 1; y++) {
+		for (x = cx - 1; x <= cx + 1; x++) {
+		    amptot += *Auxadr(x, y);
+		    npatch++;
+		}
+	    }
+	    axelev = amptot / npatch;
+
+	    /* Perturb the mean elevation by a small random factor. */
+
+	    x = (g >= 1) ? ((rand() >> 8) & 3) - 1 : 0;
+	    *Auxadr(cx, cy) = axelev + x;
+
+	    /* Jam repaint sizes to correct patch. */
+
+	    gx = 1;
+	    gy = 0;
+
+	} else {
+
+	    /* Regular crater.	Generate an impact feature of the
+	       correct size and shape. */
+
+	    /* Determine mean elevation around the impact area. */
+
+	    gx = MAX(2, (g / 3));
+	    gy = MAX(2, g / 3);
+
+	    for (y = cy - gy; y <= cy + gy; y++) {
+		for (x = cx-gx; x <= cx + gx; x++) {
+		    amptot += *Auxadr(x,y);
+		    npatch++;
+		}
+	    }
+	    axelev = amptot / npatch;
+
+	    gy = MAX(2, g);
+	    g = gy;
+	    gx = MAX(2, g);
+
+	    for (y = cy - gy; y <= cy + gy; y++) {
+		double dy = (cy - y) / (double) gy,
+		       dysq = dy * dy;
+
+		for (x = cx - gx; x <= cx + gx; x++) {
+		    double dx = ((cx - x) / (double) gx),
+			   cd = (dx * dx) + dysq,
+			   cd2 = cd * 2.25,
+			   tcz = DepthBias - sqrt(fabs(1 - cd2)),
+			   cz = MAX((cd2 > 1) ? 0.0 : -10, tcz),
+			   roll, iroll;
+		    unsigned short av;
+
+		    cz *= pow(g, CdepthPower);
+		    if (dy == 0 && dx == 0 && ((int) cz) == 0) {
+		       cz = cz < 0 ? -1 : 1;
+		    }
+
+#define 	    rollmin 0.9
+		    roll = (((1 / (1 - MIN(rollmin, cd))) /
+			     (1 / (1 - rollmin))) - (1 - rollmin)) / rollmin;
+		    iroll = 1 - roll;
+
+		    av = (axelev + cz) * iroll + (*Auxadr(x,y) + cz) * roll;
+		    av = MAX(1000, MIN(64000, av));
+		    *Auxadr(x,y) = av;
+		}
+	    }
+	 }
+	if ((l % 5000) == 4999) {
+	    pm_message( "%ld craters generated of %ld (%ld%% done)",
+		l + 1, ncraters, ((l + 1) * 100) / ncraters);
+	}
+    }
+
+    i = MAX((slopemax - slopemin) + 1, 1);
+    MALLOCARRAY(slopemap, i);
+    if (slopemap == NULL)
+        pm_error("out of memory allocating slope map");
+
+    for (i = slopemin; i <= slopemax; i++) {
+
+        /* Confused?   OK,  we're using the  left-to-right slope to
+	   calculate a shade based on the sine of  the	angle  with
+	   respect  to the vertical (light incident from the left).
+	   Then, with one exponentiation, we account for  both	the
+	   inherent   gamma   of   the	 image	(ad-hoc),  and	the
+	   user-specified display gamma, using the identity:
+
+		 (x^y)^z = (x^(y*z))		     */
+
+	slopemap[i - slopemin] = i > 0 ?
+	    (128 + 127.0 *
+		pow(sin((M_PI / 2) * i / slopemax),
+		       dgamma * ImageGamma)) :
+	    (128 - 127.0 *
+		pow(sin((M_PI / 2) * i / slopemin),
+		       dgamma * ImageGamma));
+    }
+
+    /* Generate the screen image. */
+
+    pgm_writepgminit(stdout, SCRX, SCRY, RGBQuant, FALSE);
+    pixels = pgm_allocrow(SCRX);
+
+    for (y = 0; y < SCRY; y++) {
+	gray *pix = pixels;
+
+	for (x = 0; x < SCRX; x++) {
+	    int j = *Auxadr(x+1, y) - *Auxadr(x, y);
+	    j = MIN(MAX(slopemin, j), slopemax);
+	    *pix++ = slopemap[j - slopemin];
+	}
+	pgm_writepgmrow(stdout, pixels, SCRX, RGBQuant, FALSE);
+    }
+    pm_close(stdout);
+    pgm_freerow(pixels);
+
+#undef Auxadr
+#undef Scradr
+    free((char *) slopemap);
+    free((char *) aux);
+}
+
+/*  MAIN  --  Main program.  */
+
+int main(argc, argv)
+  int argc;
+  char *argv[];
+{
+    int i;
+    Boolean gammaspec = FALSE, numspec = FALSE,
+	    widspec = FALSE, hgtspec = FALSE;
+    const char * const usage = "[-number <n>] [-width|-xsize <w>]\n\
+                  [-height|-ysize <h>] [-gamma <f>]";
+
+    DepthBias = sqrt(0.5);	      /* Get exact value for depth bias */
+
+
+    pgm_init(&argc, argv);
+
+    i = 1;
+    while ((i < argc) && (argv[i][0] == '-') && (argv[i][1] != '\0')) {
+        if (pm_keymatch(argv[i], "-gamma", 2)) {
+	    if (gammaspec) {
+                pm_error("already specified gamma correction");
+	    }
+	    i++;
+            if ((i == argc) || (sscanf(argv[i], "%lf", &dgamma)  != 1))
+		pm_usage(usage);
+	    if (dgamma <= 0.0) {
+                pm_error("gamma correction must be greater than 0");
+	    }
+	    gammaspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-number", 2)) {
+	    if (numspec) {
+                pm_error("already specified number of craters");
+	    }
+	    i++;
+            if ((i == argc) || (sscanf(argv[i], "%ld", &ncraters) != 1))
+		pm_usage(usage);
+	    if (ncraters <= 0) {
+                pm_error("number of craters must be greater than 0!");
+	    }
+	    numspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-xsize", 2) ||
+                   pm_keymatch(argv[i], "-width", 2)) {
+	    if (widspec) {
+                pm_error("already specified a width/xsize");
+	    }
+	    i++;
+            if ((i == argc) || (sscanf(argv[i], "%d", &screenxsize) != 1))
+		pm_usage(usage);
+	    if (screenxsize <= 0) {
+                pm_error("screen width must be greater than 0");
+	    }
+	    widspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-ysize", 2) ||
+                   pm_keymatch(argv[i], "-height", 2)) {
+	    if (hgtspec) {
+                pm_error("already specified a height/ysize");
+	    }
+	    i++;
+            if ((i == argc) || (sscanf(argv[i], "%d", &screenysize) != 1))
+		pm_usage(usage);
+	    if (screenxsize <= 0) {
+                pm_error("screen height must be greater than 0");
+	    }
+	    hgtspec = TRUE;
+	} else {
+	    pm_usage(usage);
+	}
+	i++;
+    }
+
+    initseed();
+    gencraters();
+
+    exit(0);
+}
diff --git a/generator/pgmkernel.c b/generator/pgmkernel.c
new file mode 100644
index 00000000..b741d596
--- /dev/null
+++ b/generator/pgmkernel.c
@@ -0,0 +1,91 @@
+/* pgmkernel.c - generate a portable graymap convolution kernel
+**
+** Creates a Portable Graymap file containing a convolution filter
+** with max value = 255 and minimum value > 127 that can be used as a 
+** smoothing kernel for pnmconvol.
+**
+** Copyright (C) 1992 by Alberto Accomazzi, Smithsonian Astrophysical
+** Observatory.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <math.h>
+#include "pgm.h"
+#include "mallocvar.h"
+
+int
+main ( argc, argv )
+    int argc;
+    char *argv[];
+{
+    register    int i, j;
+    int     argn = 1, ixsize, iysize, maxval = 255;
+    double  fxsize = 0.0, fysize = 0.0, w = 6.0, kxcenter, kycenter, 
+        tmax = 0, *fkernel;
+    const char  *usage = "[-weight f] width [height]";
+
+    pgm_init( &argc, argv );
+
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+    {
+        if ( pm_keymatch( argv[argn], "-weight", 2 )) {
+            if (++argn >= argc)
+                pm_usage( usage );
+            else if (sscanf(argv[argn], "%lf", &w) != 1)
+                pm_usage( usage );
+        }
+        else
+            pm_usage( usage );
+        argn++;
+    }
+
+    if (argn == argc)
+        pm_usage( usage );
+    
+    if (sscanf(argv[argn], "%lf", &fxsize) != 1) 
+        pm_error( "error reading input kernel x size, (%s)\n", argv[argn]);
+
+    ++argn;
+    if (argn == argc - 1) {
+        if (sscanf(argv[argn], "%lf", &fysize) != 1)
+            pm_error( "error reading input kernel y size, (%s)\n", argv[argn]);
+    }
+    else if (argn == argc)
+        fysize = fxsize;
+    else
+        pm_usage( usage );
+
+    if (fxsize <= 1 || fysize <= 1)
+        pm_usage( usage );
+
+    kxcenter = (fxsize - 1) / 2.0;
+    kycenter = (fysize - 1) / 2.0;
+    ixsize = fxsize + 0.999;
+    iysize = fysize + 0.999;
+    MALLOCARRAY(fkernel, ixsize * iysize);
+    for (i = 0; i < iysize; i++) 
+        for (j = 0; j < ixsize; j++) {
+            fkernel[i*ixsize+j] = 1.0 / (1.0 + w * sqrt((double)
+                                                        (i-kycenter)*(i-kycenter)+
+                                                        (j-kxcenter)*(j-kxcenter)));
+            if (tmax < fkernel[i*ixsize+j])
+                tmax = fkernel[i*ixsize+j];
+        }
+
+    /* output PGM header + data (ASCII format only) */
+    printf("P2\n%d %d\n%d\n", ixsize, iysize, maxval);
+    
+    for (i = 0; i < iysize; i++, printf("\n"))
+        for (j = 0; j < ixsize; j++)
+            printf(" %3d", (int)(maxval * (fkernel[i*ixsize+j] / 
+                                           (2*tmax) + 0.5)));
+    
+    exit(0);
+}
+
diff --git a/generator/pgmmake.c b/generator/pgmmake.c
new file mode 100644
index 00000000..42d96581
--- /dev/null
+++ b/generator/pgmmake.c
@@ -0,0 +1,111 @@
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "pgm.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    gray grayLevel;
+    unsigned int cols;
+    unsigned int rows;
+    gray maxval;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+  Convert program invocation arguments (argc,argv) into a format the 
+  program can use easily, struct cmdlineInfo.  Validate arguments along
+  the way and exit program with message if invalid.
+
+  Note that some string information we return as *cmdlineP is in the storage 
+  argv[] points to.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int maxvalSpec;
+    unsigned int option_def_index;
+
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "maxval",    OPT_UINT, &cmdlineP->maxval, &maxvalSpec,    0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = false;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!maxvalSpec)
+        cmdlineP->maxval = PGM_MAXMAXVAL;
+    else {
+        if (cmdlineP->maxval > PGM_OVERALLMAXVAL)
+            pm_error("The value you specified for -maxval (%u) is too big.  "
+                     "Max allowed is %u", cmdlineP->maxval, PGM_OVERALLMAXVAL);
+        
+        if (cmdlineP->maxval < 1)
+            pm_error("You cannot specify 0 for -maxval");
+    }    
+
+    if (argc-1 < 3)
+        pm_error("Need 3 arguments: gray level, width, height.");
+    else if (argc-1 > 3)
+        pm_error("Only 3 arguments allowed: gray level, width, height.  "
+                 "You specified %d", argc-1);
+    else {
+        double const grayLevel = atof(argv[1]);
+        if (grayLevel < 0.0)
+            pm_error("You can't have a negative gray level (%f)", grayLevel);
+        if (grayLevel > 1.0)
+            pm_error("Gray level must be in the range [0.0, 1.0].  "
+                     "You specified %f", grayLevel);
+        cmdlineP->grayLevel = ROUNDU(grayLevel * cmdlineP->maxval);
+        cmdlineP->cols = atoi(argv[2]);
+        cmdlineP->rows = atoi(argv[3]);
+        if (cmdlineP->cols <= 0)
+            pm_error("width argument must be a positive number.  You "
+                     "specified '%s'", argv[2]);
+        if (cmdlineP->rows <= 0)
+            pm_error("height argument must be a positive number.  You "
+                     "specified '%s'", argv[3]);
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    gray * grayrow;
+    unsigned int row;
+
+    pgm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    pgm_writepgminit(stdout, cmdline.cols, cmdline.rows, cmdline.maxval, 0);
+    grayrow = pgm_allocrow(cmdline.cols);
+
+    for (row = 0; row < cmdline.rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cmdline.cols; ++col)
+            grayrow[col] = cmdline.grayLevel;
+        pgm_writepgmrow(stdout, grayrow, cmdline.cols, cmdline.maxval, 0);
+	}
+
+    pgm_freerow(grayrow);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/generator/pgmnoise.c b/generator/pgmnoise.c
new file mode 100644
index 00000000..3929759b
--- /dev/null
+++ b/generator/pgmnoise.c
@@ -0,0 +1,77 @@
+
+/*********************************************************************/
+/* pgmnoise -  create a portable graymap with white noise            */
+/* Frank Neumann, October 1993                                       */
+/* V1.1 16.11.1993                                                   */
+/*                                                                   */
+/* version history:                                                  */
+/* V1.0 12.10.1993  first version                                    */
+/* V1.1 16.11.1993  Rewritten to be NetPBM.programming conforming    */
+/*********************************************************************/
+
+#include "pgm.h"
+
+/* global variables */
+#ifdef AMIGA
+static char *version = "$VER: pgmnoise 1.1 (16.11.93)"; /* Amiga version identification */
+#endif
+
+/**************************/
+/* start of main function */
+/**************************/
+int main(argc, argv)
+int argc;
+char *argv[];
+{
+	int argn, rows, cols, i, j;
+	gray *destrow;
+	const char * const usage = "width height\n        width and height are picture dimensions in pixels\n";
+	time_t timenow;
+
+	/* parse in 'default' parameters */
+	pgm_init(&argc, argv);
+
+	argn = 1;
+
+	/* parse in dim factor */
+	if (argn == argc)
+		pm_usage(usage);
+	if (sscanf(argv[argn], "%d", &cols) != 1)
+		pm_usage(usage);
+	argn++;
+	if (argn == argc)
+		pm_usage(usage);
+	if (sscanf(argv[argn], "%d", &rows) != 1)
+		pm_usage(usage);
+
+	if (cols <= 0 || rows <= 0)
+		pm_error("picture dimensions should be positive numbers");
+	++argn;
+
+	if (argn != argc)
+		pm_usage(usage);
+
+	/* no error checking required here, ppmlib does it all for us */
+	destrow = pgm_allocrow(cols);
+
+	pgm_writepgminit(stdout, cols, rows, PGM_MAXMAXVAL, 0);
+
+	/* get time of day to feed the random number generator */
+	timenow = time(NULL);
+	srand(timenow);
+
+	/* create the (gray) noise */
+	for (i = 0; i < rows; i++)
+	{
+		for (j = 0; j < cols; j++)
+			destrow[j] = rand() % PGM_MAXMAXVAL;
+
+		/* write out one line of graphic data */
+		pgm_writepgmrow(stdout, destrow, cols, PGM_MAXMAXVAL, 0);
+	}
+
+	pgm_freerow(destrow);
+
+	exit(0);
+}
+
diff --git a/generator/pgmramp.c b/generator/pgmramp.c
new file mode 100644
index 00000000..b84ed345
--- /dev/null
+++ b/generator/pgmramp.c
@@ -0,0 +1,170 @@
+/* pgmramp.c - generate a grayscale ramp
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <math.h>
+#include "pgm.h"
+#include "shhopt.h"
+
+enum ramptype {RT_LR, RT_TB, RT_RECT, RT_ELLIP};
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    enum ramptype ramptype;
+    unsigned int cols;
+    unsigned int rows;
+    gray maxval;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+  Convert program invocation arguments (argc,argv) into a format the 
+  program can use easily, struct cmdlineInfo.  Validate arguments along
+  the way and exit program with message if invalid.
+
+  Note that some string information we return as *cmdlineP is in the storage 
+  argv[] points to.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int lrSpec, tbSpec, rectangleSpec, ellipseSpec;
+    unsigned int maxvalSpec;
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "lr",        OPT_FLAG, NULL,              &lrSpec,        0);
+    OPTENT3(0,   "tb",        OPT_FLAG, NULL,              &tbSpec,        0);
+    OPTENT3(0,   "rectangle", OPT_FLAG, NULL,              &rectangleSpec, 0);
+    OPTENT3(0,   "ellipse",   OPT_FLAG, NULL,              &ellipseSpec,   0);
+    OPTENT3(0,   "maxval",    OPT_UINT, &cmdlineP->maxval, &maxvalSpec,    0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (lrSpec + tbSpec + rectangleSpec + ellipseSpec == 0)
+        pm_error("You must specify one of -lr, -tb, -rectangle, or -ellipse");
+    if (lrSpec + tbSpec + rectangleSpec + ellipseSpec > 1)
+        pm_error("You may specify at most one of "
+                 "-lr, -tb, -rectangle, or -ellipse");
+    if (lrSpec)
+        cmdlineP->ramptype = RT_LR;
+    else if (tbSpec)
+        cmdlineP->ramptype = RT_TB;
+    else if (rectangleSpec)
+        cmdlineP->ramptype = RT_RECT;
+    else if (ellipseSpec)
+        cmdlineP->ramptype = RT_ELLIP;
+    else
+        pm_error("INTERNAL ERROR - no ramp type option found");
+
+    if (!maxvalSpec)
+        cmdlineP->maxval = PGM_MAXMAXVAL;
+    else {
+        if (cmdlineP->maxval > PGM_OVERALLMAXVAL)
+            pm_error("The value you specified for -maxval (%u) is too big.  "
+                     "Max allowed is %u", cmdlineP->maxval, PGM_OVERALLMAXVAL);
+        
+        if (cmdlineP->maxval < 1)
+            pm_error("You cannot specify 0 for -maxval");
+    }    
+
+    if (argc-1 < 2)
+        pm_error("Need two arguments: width and height.");
+    else if (argc-1 > 2)
+        pm_error("Only two arguments allowed: width and height.  "
+                 "You specified %d", argc-1);
+    else {
+        cmdlineP->cols = atoi(argv[1]);
+        cmdlineP->rows = atoi(argv[2]);
+        if (cmdlineP->cols <= 0)
+            pm_error("width argument must be a positive number.  You "
+                     "specified '%s'", argv[1]);
+        if (cmdlineP->rows <= 0)
+            pm_error("height argument must be a positive number.  You "
+                     "specified '%s'", argv[2]);
+    }
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    gray *grayrow;
+    int rowso2, colso2;
+    unsigned int row;
+
+    pgm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    colso2 = MAX(1, cmdline.cols / 2);
+    rowso2 = MAX(1, cmdline.rows / 2);
+    
+    pgm_writepgminit(stdout, cmdline.cols, cmdline.rows, cmdline.maxval, 0);
+    grayrow = pgm_allocrow(cmdline.cols);
+    
+    for (row = 0; row < cmdline.rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cmdline.cols; ++col) {
+            switch (cmdline.ramptype) {
+            case RT_LR:
+                grayrow[col] = 
+                    col * cmdline.maxval / MAX(cmdline.cols-1, 1);
+                break;
+            case RT_TB:
+                grayrow[col] = 
+                    row * cmdline.maxval / MAX(cmdline.rows-1, 1);
+                break;
+
+            case RT_RECT: {
+                float const r = fabs((int)(rowso2 - row)) / rowso2;
+                float const c = fabs((int)(colso2 - col)) / colso2;
+                grayrow[col] = 
+                    cmdline.maxval - (r + c) / 2.0 * cmdline.maxval;
+            }
+            break;
+            
+            case RT_ELLIP: {
+                float const r = fabs((int)(rowso2 - row)) / rowso2;
+                float const c = fabs((int)(colso2 - col)) / colso2;
+                float v;
+
+                v = r * r + c * c;
+                if ( v < 0.0 ) v = 0.0;
+                else if ( v > 1.0 ) v = 1.0;
+                grayrow[col] = cmdline.maxval - v * cmdline.maxval;
+            }
+            break;
+            }
+	    }
+        pgm_writepgmrow(stdout, grayrow, cmdline.cols, cmdline.maxval, 0);
+	}
+
+    pgm_freerow(grayrow);
+    pm_close(stdout);
+    return 0;
+}
diff --git a/generator/ppmcie.c b/generator/ppmcie.c
new file mode 100644
index 00000000..fda0ab7c
--- /dev/null
+++ b/generator/ppmcie.c
@@ -0,0 +1,1201 @@
+/*
+
+      Generate a PPM file representing a CIE color gamut chart
+
+               by John Walker  --  kelvin@@fourmilab.ch
+               WWW home page: http://www.fourmilab.ch/
+
+    Permission  to  use, copy, modify, and distribute this software and
+    its documentation  for  any  purpose  and  without  fee  is  hereby
+    granted,  without any conditions or restrictions.  This software is
+    provided "as is" without express or implied warranty.
+
+    This program was called cietoppm in Walker's original work.  
+    Because "cie" is not a graphics format, Bryan changed the name
+    when he integrated it into the Netpbm package in March 2000.
+*/
+
+/*
+  Modified by
+  Andrew J. S. Hamilton 21 May 1999
+  Andrew.Hamilton@Colorado.EDU
+  http://casa.colorado.edu/~ajsh/
+
+  Corrected XYZ -> RGB transform.
+  Introduced gamma correction.
+  Introduced option to plot 1976 u' v' chromaticities.
+*/
+
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "ppmdraw.h"
+#include "nstring.h"
+
+#define CLAMP(v, l, h)  ((v) < (l) ? (l) : (v) > (h) ? (h) : (v))
+#define TRUE    1
+#define FALSE   0
+
+#define Maxval  255                   /* Maxval to use in generated pixmaps */
+
+/* A  color  system is defined by the CIE x and y  coordinates of its
+   three primary illuminants and the x and y coordinates of the  white
+   point. */
+
+struct colorSystem {
+    const char *name;                       /* Color system name */
+    double xRed, yRed,                /* Red primary illuminant */
+           xGreen, yGreen,            /* Green primary illuminant */
+           xBlue, yBlue,              /* Blue primary illuminant */
+           xWhite, yWhite,            /* White point */
+           gamma;             /* gamma of nonlinear correction */
+};
+
+/* The  following  table  gives  the  CIE  color  matching  functions
+   \bar{x}(\lambda),  \bar{y}(\lambda),  and   \bar{z}(\lambda),   for
+   wavelengths  \lambda  at 5 nanometre increments from 380 nm through
+   780 nm.  This table is used in conjunction with  Planck's  law  for
+   the  energy spectrum of a black body at a given temperature to plot
+   the black body curve on the CIE chart. */
+
+static double cie_color_match[][3] = {
+    { 0.0014, 0.0000, 0.0065 },       /* 380 nm */
+    { 0.0022, 0.0001, 0.0105 },
+    { 0.0042, 0.0001, 0.0201 },
+    { 0.0076, 0.0002, 0.0362 },
+    { 0.0143, 0.0004, 0.0679 },
+    { 0.0232, 0.0006, 0.1102 },
+    { 0.0435, 0.0012, 0.2074 },
+    { 0.0776, 0.0022, 0.3713 },
+    { 0.1344, 0.0040, 0.6456 },
+    { 0.2148, 0.0073, 1.0391 },
+    { 0.2839, 0.0116, 1.3856 },
+    { 0.3285, 0.0168, 1.6230 },
+    { 0.3483, 0.0230, 1.7471 },
+    { 0.3481, 0.0298, 1.7826 },
+    { 0.3362, 0.0380, 1.7721 },
+    { 0.3187, 0.0480, 1.7441 },
+    { 0.2908, 0.0600, 1.6692 },
+    { 0.2511, 0.0739, 1.5281 },
+    { 0.1954, 0.0910, 1.2876 },
+    { 0.1421, 0.1126, 1.0419 },
+    { 0.0956, 0.1390, 0.8130 },
+    { 0.0580, 0.1693, 0.6162 },
+    { 0.0320, 0.2080, 0.4652 },
+    { 0.0147, 0.2586, 0.3533 },
+    { 0.0049, 0.3230, 0.2720 },
+    { 0.0024, 0.4073, 0.2123 },
+    { 0.0093, 0.5030, 0.1582 },
+    { 0.0291, 0.6082, 0.1117 },
+    { 0.0633, 0.7100, 0.0782 },
+    { 0.1096, 0.7932, 0.0573 },
+    { 0.1655, 0.8620, 0.0422 },
+    { 0.2257, 0.9149, 0.0298 },
+    { 0.2904, 0.9540, 0.0203 },
+    { 0.3597, 0.9803, 0.0134 },
+    { 0.4334, 0.9950, 0.0087 },
+    { 0.5121, 1.0000, 0.0057 },
+    { 0.5945, 0.9950, 0.0039 },
+    { 0.6784, 0.9786, 0.0027 },
+    { 0.7621, 0.9520, 0.0021 },
+    { 0.8425, 0.9154, 0.0018 },
+    { 0.9163, 0.8700, 0.0017 },
+    { 0.9786, 0.8163, 0.0014 },
+    { 1.0263, 0.7570, 0.0011 },
+    { 1.0567, 0.6949, 0.0010 },
+    { 1.0622, 0.6310, 0.0008 },
+    { 1.0456, 0.5668, 0.0006 },
+    { 1.0026, 0.5030, 0.0003 },
+    { 0.9384, 0.4412, 0.0002 },
+    { 0.8544, 0.3810, 0.0002 },
+    { 0.7514, 0.3210, 0.0001 },
+    { 0.6424, 0.2650, 0.0000 },
+    { 0.5419, 0.2170, 0.0000 },
+    { 0.4479, 0.1750, 0.0000 },
+    { 0.3608, 0.1382, 0.0000 },
+    { 0.2835, 0.1070, 0.0000 },
+    { 0.2187, 0.0816, 0.0000 },
+    { 0.1649, 0.0610, 0.0000 },
+    { 0.1212, 0.0446, 0.0000 },
+    { 0.0874, 0.0320, 0.0000 },
+    { 0.0636, 0.0232, 0.0000 },
+    { 0.0468, 0.0170, 0.0000 },
+    { 0.0329, 0.0119, 0.0000 },
+    { 0.0227, 0.0082, 0.0000 },
+    { 0.0158, 0.0057, 0.0000 },
+    { 0.0114, 0.0041, 0.0000 },
+    { 0.0081, 0.0029, 0.0000 },
+    { 0.0058, 0.0021, 0.0000 },
+    { 0.0041, 0.0015, 0.0000 },
+    { 0.0029, 0.0010, 0.0000 },
+    { 0.0020, 0.0007, 0.0000 },
+    { 0.0014, 0.0005, 0.0000 },
+    { 0.0010, 0.0004, 0.0000 },
+    { 0.0007, 0.0002, 0.0000 },
+    { 0.0005, 0.0002, 0.0000 },
+    { 0.0003, 0.0001, 0.0000 },
+    { 0.0002, 0.0001, 0.0000 },
+    { 0.0002, 0.0001, 0.0000 },
+    { 0.0001, 0.0000, 0.0000 },
+    { 0.0001, 0.0000, 0.0000 },
+    { 0.0001, 0.0000, 0.0000 },
+    { 0.0000, 0.0000, 0.0000 }        /* 780 nm */
+};
+
+/* The following table gives the  spectral  chromaticity  co-ordinates
+   x(\lambda) and y(\lambda) for wavelengths in 5 nanometre increments
+   from 380 nm through  780  nm.   These  co-ordinates  represent  the
+   position in the CIE x-y space of pure spectral colors of the given
+   wavelength, and  thus  define  the  outline  of  the  CIE  "tongue"
+   diagram. */
+
+static double spectral_chromaticity[81][3] = {
+    { 0.1741, 0.0050 },               /* 380 nm */
+    { 0.1740, 0.0050 },
+    { 0.1738, 0.0049 },
+    { 0.1736, 0.0049 },
+    { 0.1733, 0.0048 },
+    { 0.1730, 0.0048 },
+    { 0.1726, 0.0048 },
+    { 0.1721, 0.0048 },
+    { 0.1714, 0.0051 },
+    { 0.1703, 0.0058 },
+    { 0.1689, 0.0069 },
+    { 0.1669, 0.0086 },
+    { 0.1644, 0.0109 },
+    { 0.1611, 0.0138 },
+    { 0.1566, 0.0177 },
+    { 0.1510, 0.0227 },
+    { 0.1440, 0.0297 },
+    { 0.1355, 0.0399 },
+    { 0.1241, 0.0578 },
+    { 0.1096, 0.0868 },
+    { 0.0913, 0.1327 },
+    { 0.0687, 0.2007 },
+    { 0.0454, 0.2950 },
+    { 0.0235, 0.4127 },
+    { 0.0082, 0.5384 },
+    { 0.0039, 0.6548 },
+    { 0.0139, 0.7502 },
+    { 0.0389, 0.8120 },
+    { 0.0743, 0.8338 },
+    { 0.1142, 0.8262 },
+    { 0.1547, 0.8059 },
+    { 0.1929, 0.7816 },
+    { 0.2296, 0.7543 },
+    { 0.2658, 0.7243 },
+    { 0.3016, 0.6923 },
+    { 0.3373, 0.6589 },
+    { 0.3731, 0.6245 },
+    { 0.4087, 0.5896 },
+    { 0.4441, 0.5547 },
+    { 0.4788, 0.5202 },
+    { 0.5125, 0.4866 },
+    { 0.5448, 0.4544 },
+    { 0.5752, 0.4242 },
+    { 0.6029, 0.3965 },
+    { 0.6270, 0.3725 },
+    { 0.6482, 0.3514 },
+    { 0.6658, 0.3340 },
+    { 0.6801, 0.3197 },
+    { 0.6915, 0.3083 },
+    { 0.7006, 0.2993 },
+    { 0.7079, 0.2920 },
+    { 0.7140, 0.2859 },
+    { 0.7190, 0.2809 },
+    { 0.7230, 0.2770 },
+    { 0.7260, 0.2740 },
+    { 0.7283, 0.2717 },
+    { 0.7300, 0.2700 },
+    { 0.7311, 0.2689 },
+    { 0.7320, 0.2680 },
+    { 0.7327, 0.2673 },
+    { 0.7334, 0.2666 },
+    { 0.7340, 0.2660 },
+    { 0.7344, 0.2656 },
+    { 0.7346, 0.2654 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 }                /* 780 nm */
+};
+
+static pixel **pixels;                /* Pixel map */
+static int pixcols, pixrows;          /* Pixel map size */
+static int sxsize = 512, sysize = 512; /* X, Y size */
+
+/* Standard white point chromaticities. */
+
+#define IlluminantC     0.3101, 0.3162  /* For NTSC television */
+#define IlluminantD65   0.3127, 0.3291  /* For EBU and SMPTE */
+
+/* Gamma of nonlinear correction.
+   See Charles Poynton's ColorFAQ Item 45 and GammaFAQ Item 6 at
+   http://www.inforamp.net/~poynton/ColorFAQ.html
+   http://www.inforamp.net/~poynton/GammaFAQ.html
+*/
+
+#define GAMMA_REC709    0.      /* Rec. 709 */
+
+static struct colorSystem const
+    NTSCsystem = {
+        "NTSC",
+        0.67,  0.33,  0.21,  0.71,  0.14,  0.08,
+        IlluminantC,   GAMMA_REC709
+    },
+    EBUsystem = {
+        "EBU (PAL/SECAM)",
+        0.64,  0.33,  0.29,  0.60,  0.15,  0.06,
+        IlluminantD65, GAMMA_REC709
+    },
+    SMPTEsystem = {
+        "SMPTE",
+        0.630, 0.340, 0.310, 0.595, 0.155, 0.070,
+        IlluminantD65, GAMMA_REC709
+    },
+    HDTVsystem = {
+        "HDTV",
+        0.670, 0.330, 0.210, 0.710, 0.150, 0.060,
+        IlluminantD65,  GAMMA_REC709
+    },
+    /* Huh? -ajsh
+    CIEsystem = {
+        "CIE",
+        0.7355, 0.2645, 0.2658, 0.7243, 0.1669, 0.0085,
+        0.3894, 0.3324, GAMMA_REC709
+        },
+    */
+    CIEsystem = {
+        "CIE",
+        0.7355,0.2645,0.2658,0.7243,0.1669,0.0085, 0.33333333, 0.33333333,
+        GAMMA_REC709
+    },
+    Rec709system = {
+        "CIE REC 709",
+        0.64,  0.33,  0.30,  0.60,  0.15,  0.06,
+        IlluminantD65, GAMMA_REC709
+    };
+
+/* Customsystem  is a variable that is initialized to CIE Rec 709, but
+   we modify it with information specified by the user's options.
+*/
+static struct colorSystem Customsystem = {
+    "Custom",
+    0.64,  0.33,  0.30,  0.60,  0.15,  0.06,  
+    IlluminantD65, GAMMA_REC709
+};
+    
+
+
+static void
+upvp_to_xy(double   const up,
+           double   const vp,
+           double * const xc,
+           double * const yc) {
+/*----------------------------------------------------------------------------
+    Given 1976 coordinates u', v', determine 1931 chromaticities x, y
+-----------------------------------------------------------------------------*/
+    *xc = 9*up / (6*up - 16*vp + 12);
+    *yc = 4*vp / (6*up - 16*vp + 12);
+}
+
+
+
+static void
+xy_to_upvp(double   const xc,
+           double   const yc,
+           double * const up,
+           double * const vp) {
+/*----------------------------------------------------------------------------
+    Given 1931 chromaticities x, y, determine 1976 coordinates u', v'
+-----------------------------------------------------------------------------*/
+    *up = 4*xc / (- 2*xc + 12*yc + 3);
+    *vp = 9*yc / (- 2*xc + 12*yc + 3);
+}
+
+
+
+static void
+xyz_to_rgb(const struct colorSystem * const cs,
+           double                      const xc,
+           double                      const yc,
+           double                      const zc,
+           double *                    const r,
+           double *                    const g,
+           double *                    const b) {
+/*----------------------------------------------------------------------------
+    Given  an additive tricolor system CS, defined by the CIE x and y
+    chromaticities of its three primaries (z is derived  trivially  as
+    1-(x+y)),  and  a  desired chromaticity (XC, YC, ZC) in CIE space,
+    determine the contribution of each primary in a linear combination
+    which   sums  to  the  desired  chromaticity.   If  the  requested
+    chromaticity falls outside the  Maxwell  triangle  (color  gamut)
+    formed  by the three primaries, one of the r, g, or b weights will
+    be negative.  
+
+    Caller can use constrain_rgb() to desaturate an outside-gamut
+    color to the closest representation within the available
+    gamut. 
+-----------------------------------------------------------------------------*/
+    double xr, yr, zr, xg, yg, zg, xb, yb, zb;
+    double xw, yw, zw;
+    double rx, ry, rz, gx, gy, gz, bx, by, bz;
+    double rw, gw, bw;
+
+    xr = cs->xRed;    yr = cs->yRed;    zr = 1 - (xr + yr);
+    xg = cs->xGreen;  yg = cs->yGreen;  zg = 1 - (xg + yg);
+    xb = cs->xBlue;   yb = cs->yBlue;   zb = 1 - (xb + yb);
+
+    xw = cs->xWhite;  yw = cs->yWhite;  zw = 1 - (xw + yw);
+
+    /* xyz -> rgb matrix, before scaling to white. */
+    rx = yg*zb - yb*zg;  ry = xb*zg - xg*zb;  rz = xg*yb - xb*yg;
+    gx = yb*zr - yr*zb;  gy = xr*zb - xb*zr;  gz = xb*yr - xr*yb;
+    bx = yr*zg - yg*zr;  by = xg*zr - xr*zg;  bz = xr*yg - xg*yr;
+
+    /* White scaling factors.
+       Dividing by yw scales the white luminance to unity, as conventional. */
+    rw = (rx*xw + ry*yw + rz*zw) / yw;
+    gw = (gx*xw + gy*yw + gz*zw) / yw;
+    bw = (bx*xw + by*yw + bz*zw) / yw;
+
+    /* xyz -> rgb matrix, correctly scaled to white. */
+    rx = rx / rw;  ry = ry / rw;  rz = rz / rw;
+    gx = gx / gw;  gy = gy / gw;  gz = gz / gw;
+    bx = bx / bw;  by = by / bw;  bz = bz / bw;
+
+    /* rgb of the desired point */
+    *r = rx*xc + ry*yc + rz*zc;
+    *g = gx*xc + gy*yc + gz*zc;
+    *b = bx*xc + by*yc + bz*zc;
+}
+
+
+
+static int
+constrain_rgb(double * const r,
+              double * const g,
+              double * const b) {
+/*----------------------------------------------------------------------------
+    If  the  requested RGB shade contains a negative weight for one of
+    the primaries, it lies outside the color  gamut  accessible  from
+    the  given  triple  of  primaries.  Desaturate it by adding white,
+    equal quantities of R, G, and B, enough to make RGB all positive.
+-----------------------------------------------------------------------------*/
+    double w;
+
+    /* Amount of white needed is w = - min(0, *r, *g, *b) */
+    w = (0 < *r) ? 0 : *r;
+    w = (w < *g) ? w : *g;
+    w = (w < *b) ? w : *b;
+    w = - w;
+
+    /* Add just enough white to make r, g, b all positive. */
+    if (w > 0) {
+        *r += w;  *g += w; *b += w;
+
+        return 1;                     /* Color modified to fit RGB gamut */
+    }
+
+    return 0;                         /* Color within RGB gamut */
+}
+
+
+
+static void
+gamma_correct(const struct colorSystem * const cs,
+              double *                   const c) {
+/*----------------------------------------------------------------------------
+    Transform linear RGB values to nonlinear RGB values.
+
+    Rec. 709 is ITU-R Recommendation BT. 709 (1990)
+    ``Basic Parameter Values for the HDTV Standard for the Studio and for
+    International Programme Exchange'', formerly CCIR Rec. 709.
+
+    For details see
+       http://www.inforamp.net/~poynton/ColorFAQ.html
+       http://www.inforamp.net/~poynton/GammaFAQ.html
+-----------------------------------------------------------------------------*/
+    double gamma;
+
+    gamma = cs->gamma;
+
+    if (gamma == 0.) {
+    /* Rec. 709 gamma correction. */
+    double cc = 0.018;
+    if (*c < cc) {
+        *c *= (1.099 * pow(cc, 0.45) - 0.099) / cc;
+    } else {
+        *c = 1.099 * pow(*c, 0.45) - 0.099;
+    }
+    } else {
+    /* Nonlinear color = (Linear color)^(1/gamma) */
+    *c = pow(*c, 1./gamma);
+    }
+}
+
+
+
+static void
+gamma_correct_rgb(const struct colorSystem * const cs,
+                  double * const r,
+                  double * const g,
+                  double * const b) {
+    gamma_correct(cs, r);
+    gamma_correct(cs, g);
+    gamma_correct(cs, b);
+}
+
+
+
+#define Sz(x) (((x) * MIN(pixcols, pixrows)) / 512)
+#define B(x, y) ((x) + xBias), (y)
+#define Bixels(y, x) pixels[y][x + xBias]
+
+
+
+static void
+computeMonochromeColorLocation(
+    double                     const waveLength,
+    int                        const pxcols,
+    int                        const pxrows,
+    bool                       const upvp,
+    int *                      const xP,
+    int *                      const yP) {
+
+    int const ix = (waveLength - 380) / 5;
+    double const px = spectral_chromaticity[ix][0];
+    double const py = spectral_chromaticity[ix][1];
+
+    if (upvp) {
+        double up, vp;
+        xy_to_upvp(px, py, &up, &vp);
+        *xP = up * (pxcols - 1);
+        *yP = (pxrows - 1) - vp * (pxrows - 1);
+    } else {
+        *xP = px * (pxcols - 1);
+        *yP = (pxrows - 1) - py * (pxrows - 1);
+    }
+}
+
+
+
+static void
+makeAllBlack(pixel **     const pixels,
+             unsigned int const cols,
+             unsigned int const rows) {
+
+    unsigned int row;
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cols; ++col)
+            PPM_ASSIGN(pixels[row][col], 0, 0, 0);
+    }
+}
+
+
+
+static void
+drawTongueOutline(pixel ** const pixels,
+                  int    const pixcols,
+                  int    const pixrows,
+                  pixval const maxval,
+                  bool   const upvp,
+                  int    const xBias,
+                  int    const yBias) {
+
+    int const pxcols = pixcols - xBias;
+    int const pxrows = pixrows - yBias;
+
+    pixel rgbcolor;
+    int wavelength;
+    int lx, ly;
+    int fx, fy;
+
+    PPM_ASSIGN(rgbcolor, maxval, maxval, maxval);
+
+    for (wavelength = 380; wavelength <= 700; wavelength += 5) {
+        int icx, icy;
+
+        computeMonochromeColorLocation(wavelength, pxcols, pxrows, upvp,
+                                       &icx, &icy);
+        
+        if (wavelength > 380)
+            ppmd_line(pixels, pixcols, pixrows, Maxval,
+                      B(lx, ly), B(icx, icy),
+                      PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+        else {
+            fx = icx;
+            fy = icy;
+        }
+        lx = icx;
+        ly = icy;
+    }
+    ppmd_line(pixels, pixcols, pixrows, maxval,
+              B(lx, ly), B(fx, fy),
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+}
+
+
+
+static void
+findTongue(pixel ** const pixels,
+           int      const pxcols,
+           int      const xBias,
+           int      const row,
+           bool *   const presentP,
+           int *    const leftEdgeP,
+           int *    const rightEdgeP) {
+/*----------------------------------------------------------------------------
+  Find out if there is any tongue on row 'row' of image 'pixels', and if
+  so, where.
+
+  We assume the image consists of all black except a white outline of the
+  tongue.
+-----------------------------------------------------------------------------*/
+    int i;
+
+    for (i = 0;
+         i < pxcols && PPM_GETR(Bixels(row, i)) == 0;
+         ++i);
+
+    if (i >= pxcols)
+        *presentP = FALSE;
+    else {
+        int j;
+        int const leftEdge = i;
+
+        *presentP = TRUE;
+        
+        for (j = pxcols - 1;
+             j >= leftEdge && PPM_GETR(Bixels(row, j)) == 0;
+             --j);
+
+        *rightEdgeP = j;
+        *leftEdgeP = leftEdge;
+    }
+}
+
+
+
+static void
+fillInTongue(pixel **                   const pixels,
+             int                        const pixcols,
+             int                        const pixrows,
+             pixval                     const maxval,
+             const struct colorSystem * const cs,
+             bool                       const upvp,
+             int                        const xBias,
+             int                        const yBias,
+             bool                       const highlightGamut) {
+
+    int const pxcols = pixcols - xBias;
+    int const pxrows = pixrows - yBias;
+
+    int y;
+
+    /* Scan the image line by line and  fill  the  tongue  outline
+       with the RGB values determined by the color system for the x-y
+       co-ordinates within the tongue.
+    */
+
+    for (y = 0; y < pxrows; ++y) {
+        bool present;  /* There is some tongue on this line */
+        int leftEdge; /* x position of leftmost pixel in tongue on this line */
+        int rightEdge; /* same, but rightmost */
+
+        findTongue(pixels, pxcols, xBias, y, &present, &leftEdge, &rightEdge);
+
+        if (present) {
+            int x;
+
+            for (x = leftEdge; x <= rightEdge; ++x) {
+                double cx, cy, cz, jr, jg, jb, jmax;
+                int r, g, b, mx;
+
+                if (upvp) {
+                    double up, vp;
+                    up = ((double) x) / (pxcols - 1);
+                    vp = 1.0 - ((double) y) / (pxrows - 1);
+                    upvp_to_xy(up, vp, &cx, &cy);
+                    cz = 1.0 - (cx + cy);
+                } else {
+                    cx = ((double) x) / (pxcols - 1);
+                    cy = 1.0 - ((double) y) / (pxrows - 1);
+                    cz = 1.0 - (cx + cy);
+                }
+
+                xyz_to_rgb(cs, cx, cy, cz, &jr, &jg, &jb);
+
+                mx = Maxval;
+        
+                /* Check whether the requested color  is  within  the
+                   gamut  achievable with the given color system.  If
+                   not, draw it in a reduced  intensity,  interpolated
+                   by desaturation to the closest within-gamut color. */
+        
+                if (constrain_rgb(&jr, &jg, &jb)) {
+                    mx = highlightGamut ? Maxval : ((Maxval + 1) * 3) / 4;
+                }
+                /* Scale to max(rgb) = 1. */
+                jmax = MAX(jr, MAX(jg, jb));
+                if (jmax > 0) {
+                    jr = jr / jmax;
+                    jg = jg / jmax;
+                    jb = jb / jmax;
+                }
+                /* gamma correct from linear rgb to nonlinear rgb. */
+                gamma_correct_rgb(cs, &jr, &jg, &jb);
+                r = mx * jr;
+                g = mx * jg;
+                b = mx * jb;
+                PPM_ASSIGN(Bixels(y, x), (pixval) r, (pixval) g, (pixval) b);
+            }
+        }
+    }
+}
+
+
+
+static void
+drawAxes(pixel ** const pixels,
+         int    const pixcols,
+         int    const pixrows,
+         pixval const maxval,
+         bool   const upvp,
+         int    const xBias,
+         int    const yBias) {
+
+    int const pxcols = pixcols - xBias;
+    int const pxrows = pixrows - yBias;
+
+    pixel rgbcolor;  /* Color of axes */
+    int i;
+
+    PPM_ASSIGN(rgbcolor, maxval, maxval, maxval);
+    ppmd_line(pixels, pixcols, pixrows, maxval,
+              B(0, 0), B(0, pxrows - 1), PPMD_NULLDRAWPROC,
+              (char *) &rgbcolor);
+    ppmd_line(pixels, pixcols, pixrows, maxval,
+              B(0, pxrows - 1), B(pxcols - 1, pxrows - 1),
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+    
+    /* Draw tick marks on X and Y axes every 0.1 units.  Also
+       label axes.
+    */
+    
+    for (i = 1; i <= 9; i += 1) {
+        char s[20];
+
+        /* X axis tick */
+
+        sprintf(s, "0.%d", i);
+        ppmd_line(pixels, pixcols, pixrows, maxval,
+                  B((i * (pxcols - 1)) / 10, pxrows - Sz(1)),
+                  B((i * (pxcols - 1)) / 10, pxrows - Sz(4)),
+                  PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+        ppmd_text(pixels, pixcols, pixrows, maxval,
+                  B((i * (pxcols - 1)) / 10 - Sz(11), pxrows + Sz(12)),
+                  Sz(10), 0, s, PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+
+        /* Y axis tick */
+
+        sprintf(s, "0.%d", 10 - i);
+        ppmd_line(pixels, pixcols, pixrows, maxval,
+                  B(0, (i * (pxrows - 1)) / 10),
+                  B(Sz(3), (i * (pxrows - 1)) / 10),
+                  PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+
+        ppmd_text(pixels, pixcols, pixrows, maxval,
+                  B(Sz(-30), (i * (pxrows - 1)) / 10 + Sz(5)),
+                  Sz(10), 0, s,
+                  PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+    }
+    ppmd_text(pixels, pixcols, pixrows, maxval,
+              B((98 * (pxcols - 1)) / 100 - Sz(11), pxrows + Sz(12)),
+              Sz(10), 0, (upvp ? "u'" : "x"),
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+    ppmd_text(pixels,  pixcols, pixrows, maxval,
+              B(Sz(-22), (2 * (pxrows - 1)) / 100 + Sz(5)),
+              Sz(10), 0, (upvp ? "v'" : "y"),
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+}
+
+
+
+static void
+plotWhitePoint(pixel **                   const pixels,
+               int                        const pixcols,
+               int                        const pixrows,
+               pixval                     const maxval,
+               const struct colorSystem * const cs,
+               bool                       const upvp,
+               int                        const xBias,
+               int                        const yBias) {
+
+    int const pxcols = pixcols - xBias;
+    int const pxrows = pixrows - yBias;
+
+    int wx, wy;
+
+    pixel rgbcolor;  /* Color of the white point mark */
+
+    PPM_ASSIGN(rgbcolor, 0, 0, 0);
+
+    if (upvp) {
+        double wup, wvp;
+        xy_to_upvp(cs->xWhite, cs->yWhite, &wup, &wvp);
+        wx = wup;
+        wy = wvp;
+        wx = (pxcols - 1) * wup;
+        wy = (pxrows - 1) - ((int) ((pxrows - 1) * wvp));
+    } else {
+        wx = (pxcols - 1) * cs->xWhite;
+        wy = (pxrows - 1) - ((int) ((pxrows - 1) * cs->yWhite));
+    }
+
+    PPM_ASSIGN(rgbcolor, 0, 0, 0);
+    /* We draw the four arms of the cross separately so as to
+       leave the pixel representing the precise white point
+       undisturbed.
+    */
+    ppmd_line(pixels, pixcols, pixrows, maxval,
+              B(wx + Sz(3), wy), B(wx + Sz(10), wy),
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+    ppmd_line(pixels, pixcols, pixrows, maxval,
+              B(wx - Sz(3), wy), B(wx - Sz(10), wy),
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+    ppmd_line(pixels, pixcols, pixrows, maxval,
+              B(wx, wy + Sz(3)), B(wx, wy + Sz(10)),
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+    ppmd_line(pixels, pixcols, pixrows, maxval,
+              B(wx, wy - Sz(3)), B(wx, wy - Sz(10)),
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+}
+
+
+
+static void
+plotBlackBodyCurve(pixel **                   const pixels,
+                   int                        const pixcols,
+                   int                        const pixrows,
+                   pixval                     const maxval,
+                   bool                       const upvp,
+                   int                        const xBias,
+                   int                        const yBias) {
+
+    int const pxcols = pixcols - xBias;
+    int const pxrows = pixrows - yBias;
+
+    double t;  /* temperature of black body */
+    pixel rgbcolor;  /* color of the curve */
+    int lx, ly;  /* Last point we've drawn on curve so far */
+
+    PPM_ASSIGN(rgbcolor, 0, 0, 0);
+
+    /* Plot black body curve from 1000 to 30000 kelvins. */
+
+    for (t = 1000.0; t < 30000.0; t += 50.0) {
+        double lambda, X = 0, Y = 0, Z = 0;
+        int xb, yb;
+        int ix;
+
+        /* Determine X, Y, and Z for blackbody by summing color
+           match functions over the visual range. */
+
+        for (ix = 0, lambda = 380; lambda <= 780.0; ix++, lambda += 5) {
+            double Me;
+
+            /* Evaluate Planck's black body equation for the
+               power at this wavelength. */
+
+            Me = 3.74183e-16 * pow(lambda * 1e-9, -5.0) /
+                (exp(1.4388e-2/(lambda * 1e-9 * t)) - 1.0);
+            X += Me * cie_color_match[ix][0];
+            Y += Me * cie_color_match[ix][1];
+            Z += Me * cie_color_match[ix][2];
+        }
+        if (upvp) {
+            double up, vp;
+            xy_to_upvp(X / (X + Y + Z), Y / (X + Y + Z), &up, &vp);
+            xb = (pxcols - 1) * up;
+            yb = (pxrows - 1) - ((pxrows - 1) * vp);
+        } else {
+            xb = (pxcols - 1) * X / (X + Y + Z);
+            yb = (pxrows - 1) - ((pxrows - 1) * Y / (X + Y + Z));
+        }
+
+        if (t > 1000) {
+            ppmd_line(pixels, pixcols, pixrows, Maxval,
+                      B(lx, ly), B(xb, yb), PPMD_NULLDRAWPROC,
+                      (char *) &rgbcolor);
+
+            /* Draw tick mark every 1000 kelvins */
+
+            if ((((int) t) % 1000) == 0) {
+                ppmd_line(pixels, pixcols, pixrows, Maxval,
+                          B(lx, ly - Sz(2)), B(lx, ly + Sz(2)),
+                          PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+
+                /* Label selected tick marks with decreasing density. */
+
+                if (t <= 5000.1 || (t > 5000.0 && 
+                                    ((((int) t) % 5000) == 0) &&
+                                    t != 20000.0)) {
+                    char bb[20];
+
+                    sprintf(bb, "%g", t);
+                    ppmd_text(pixels, pixcols, pixrows, Maxval,
+                              B(lx - Sz(12), ly - Sz(4)), Sz(6), 0, bb,
+                              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+                }
+  
+            }
+        }
+        lx = xb;
+        ly = yb;
+    }
+}
+
+
+
+static bool
+overlappingLegend(bool const upvp,
+                  int  const waveLength) {
+
+    bool retval;
+
+    if (upvp)
+        retval = (waveLength == 430 || waveLength == 640);
+    else 
+        retval = (waveLength == 460 || waveLength == 630 || waveLength == 640);
+    return retval;
+}
+
+
+
+static void
+plotMonochromeWavelengths(
+    pixel **                   const pixels,
+    int                        const pixcols,
+    int                        const pixrows,
+    pixval                     const maxval,
+    const struct colorSystem * const cs,
+    bool                       const upvp,
+    int                        const xBias,
+    int                        const yBias) {
+
+    int const pxcols = pixcols - xBias;
+    int const pxrows = pixrows - yBias;
+
+    int x;  /* The wavelength we're plotting */
+
+    for (x = (upvp? 420 : 450);
+         x <= 650;
+         x += (upvp? 10 : (x > 470 && x < 600) ? 5 : 10)) {
+
+        pixel rgbcolor;
+
+        /* Ick.  Drop legends that overlap and twiddle position
+           so they appear at reasonable positions with respect to
+           the tongue.
+        */
+
+        if (!overlappingLegend(upvp, x)) {
+            double cx, cy, cz, jr, jg, jb, jmax;
+            char wl[20];
+            int bx = 0, by = 0, tx, ty, r, g, b;
+            int icx, icy;  /* Location of the color on the tongue */
+
+            if (x < 520) {
+                bx = Sz(-22);
+                by = Sz(2);
+            } else if (x < 535) {
+                bx = Sz(-8);
+                by = Sz(-6);
+            } else {
+                bx = Sz(4);
+            }
+
+            computeMonochromeColorLocation(x, pxcols, pxrows, upvp,
+                                           &icx, &icy);
+
+            /* Draw the tick mark */
+            PPM_ASSIGN(rgbcolor, maxval, maxval, maxval);
+            tx = icx + ((x < 520) ? Sz(-2) : ((x >= 535) ? Sz(2) : 0));
+            ty = icy + ((x < 520) ? 0 : ((x >= 535) ? Sz(-1) : Sz(-2))); 
+            ppmd_line(pixels, pixcols, pixrows, Maxval,
+                      B(icx, icy), B(tx, ty),
+                      PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+
+            /* The following flailing about sets the drawing color to
+               the hue corresponding to the pure wavelength (constrained
+               to the display gamut). */
+
+            if (upvp) {
+                double up, vp;
+                up = ((double) icx) / (pxcols - 1);
+                vp = 1.0 - ((double) icy) / (pxrows - 1);
+                upvp_to_xy(up, vp, &cx, &cy);
+                cz = 1.0 - (cx + cy);
+            } else {
+                cx = ((double) icx) / (pxcols - 1);
+                cy = 1.0 - ((double) icy) / (pxrows - 1);
+                cz = 1.0 - (cx + cy);
+            }
+
+            xyz_to_rgb(cs, cx, cy, cz, &jr, &jg, &jb);
+            (void) constrain_rgb(&jr, &jg, &jb);
+
+            /* Scale to max(rgb) = 1 */
+            jmax = MAX(jr, MAX(jg, jb));
+            if (jmax > 0) {
+                jr = jr / jmax;
+                jg = jg / jmax;
+                jb = jb / jmax;
+            }
+            /* gamma correct from linear rgb to nonlinear rgb. */
+            gamma_correct_rgb(cs, &jr, &jg, &jb);
+            r = Maxval * jr;
+            g = Maxval * jg;
+            b = Maxval * jb;
+            PPM_ASSIGN(rgbcolor, (pixval) r, (pixval) g, (pixval) b);
+
+            sprintf(wl, "%d", x);
+            ppmd_text(pixels, pixcols, pixrows, Maxval,
+                      B(icx + bx, icy + by), Sz(6), 0, wl,
+                      PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+        }
+    }
+}
+
+
+
+static void
+writeLabel(pixel **                   const pixels,
+           int                        const pixcols,
+           int                        const pixrows,
+           pixval                     const maxval,
+           const struct colorSystem * const cs) {
+
+    pixel rgbcolor;  /* color of text */
+    char sysdesc[256];
+
+    PPM_ASSIGN(rgbcolor, maxval, maxval, maxval);
+    
+    snprintfN(sysdesc, sizeof(sysdesc),
+              "System: %s\n"
+              "Primary illuminants (X, Y)\n"
+              "     Red:  %0.4f, %0.4f\n"
+              "     Green: %0.4f, %0.4f\n"
+              "     Blue:  %0.4f, %0.4f\n"
+              "White point (X, Y): %0.4f, %0.4f",
+              cs->name, cs->xRed, cs->yRed, cs->xGreen, cs->yGreen,
+              cs->xBlue, cs->yBlue, cs->xWhite, cs->yWhite);
+    sysdesc[sizeof(sysdesc)-1] = '\0';  /* for robustness */
+
+    ppmd_text(pixels, pixcols, pixrows, Maxval,
+              pixcols / 3, Sz(24), Sz(12), 0, sysdesc,
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+}
+
+
+
+int
+main(int argc,
+     char * argv[]) {
+
+    int argn;
+    const char * const usage = "[-[no]black] [-[no]wpoint] [-[no]label] [-no[axes]] [-full]\n\
+[-xy|-upvp] [-rec709|-ntsc|-ebu|-smpte|-hdtv|-cie]\n\
+[-red <x> <y>] [-green <x> <y>] [-blue <x> <y>]\n\
+[-white <x> <y>] [-gamma <g>]\n\
+[-size <s>] [-xsize|-width <x>] [-ysize|-height <y>]";
+    const struct colorSystem *cs;
+
+    int widspec = FALSE, hgtspec = FALSE;
+    int xBias, yBias;
+    int upvp = FALSE;             /* xy or u'v' color coordinates? */
+    int showWhite = TRUE;             /* Show white point ? */
+    int showBlack = TRUE;             /* Show black body curve ? */
+    int fullChart = FALSE;            /* Fill entire tongue ? */
+    int showLabel = TRUE;             /* Show labels ? */
+    int showAxes = TRUE;              /* Plot axes ? */
+
+    ppm_init(&argc, argv);
+    argn = 1;
+
+    cs = &Rec709system;  /* default */
+    while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0') {
+        if (pm_keymatch(argv[argn], "-xy", 2)) {
+            upvp = FALSE;
+        } else if (pm_keymatch(argv[argn], "-upvp", 1)) {
+            upvp = TRUE;
+        } else if (pm_keymatch(argv[argn], "-xsize", 1) ||
+                   pm_keymatch(argv[argn], "-width", 2)) {
+            if (widspec) {
+                pm_error("already specified a size/width/xsize");
+            }
+            argn++;
+            if ((argn == argc) || (sscanf(argv[argn], "%d", &sxsize) != 1))
+                pm_usage(usage);
+            widspec = TRUE;
+        } else if (pm_keymatch(argv[argn], "-ysize", 1) ||
+                   pm_keymatch(argv[argn], "-height", 2)) {
+            if (hgtspec) {
+                pm_error("already specified a size/height/ysize");
+            }
+            argn++;
+            if ((argn == argc) || (sscanf(argv[argn], "%d", &sysize) != 1))
+                pm_usage(usage);
+            hgtspec = TRUE;
+        } else if (pm_keymatch(argv[argn], "-size", 2)) {
+            if (hgtspec || widspec) {
+                pm_error("already specified a size/height/ysize");
+            }
+            argn++;
+            if ((argn == argc) || (sscanf(argv[argn], "%d", &sysize) != 1))
+                pm_usage(usage);
+            sxsize = sysize;
+            hgtspec = widspec = TRUE;
+        } else if (pm_keymatch(argv[argn], "-rec709", 1)) {
+            cs = &Rec709system;
+        } else if (pm_keymatch(argv[argn], "-ntsc", 1)) {
+            cs = &NTSCsystem;
+        } else if (pm_keymatch(argv[argn], "-ebu", 1)) {
+            cs = &EBUsystem;
+        } else if (pm_keymatch(argv[argn], "-smpte", 2)) {
+            cs = &SMPTEsystem;
+        } else if (pm_keymatch(argv[argn], "-hdtv", 2)) {
+            cs = &HDTVsystem;                 
+        } else if (pm_keymatch(argv[argn], "-cie", 1)) {
+            cs = &CIEsystem;                 
+        } else if (pm_keymatch(argv[argn], "-black", 3)) {
+            showBlack = TRUE;         /* Show black body curve */
+        } else if (pm_keymatch(argv[argn], "-wpoint", 2)) {
+            showWhite = TRUE;         /* Show white point of color system */
+        } else if (pm_keymatch(argv[argn], "-noblack", 3)) {
+            showBlack = FALSE;        /* Don't show black body curve */
+        } else if (pm_keymatch(argv[argn], "-nowpoint", 3)) {
+            showWhite = FALSE;        /* Don't show white point of system */
+        } else if (pm_keymatch(argv[argn], "-label", 1)) {
+            showLabel = TRUE;         /* Show labels. */
+        } else if (pm_keymatch(argv[argn], "-nolabel", 3)) {
+            showLabel = FALSE;        /* Don't show labels */
+        } else if (pm_keymatch(argv[argn], "-axes", 1)) {
+            showAxes = TRUE;          /* Show axes. */
+        } else if (pm_keymatch(argv[argn], "-noaxes", 3)) {
+            showAxes = FALSE;         /* Don't show axes */
+        } else if (pm_keymatch(argv[argn], "-full", 1)) {
+            fullChart = TRUE;         /* Fill whole tongue full-intensity */
+        } else if (pm_keymatch(argv[argn], "-gamma", 2)) {
+            cs = &Customsystem;
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.gamma) != 1))
+                pm_usage(usage);
+        } else if (pm_keymatch(argv[argn], "-red", 1)) {
+            cs = &Customsystem;
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.xRed) != 1))
+                pm_usage(usage);
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.yRed) != 1))
+                pm_usage(usage);
+        } else if (pm_keymatch(argv[argn], "-green", 1)) {
+            cs = &Customsystem;
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.xGreen) != 1))
+                pm_usage(usage);
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.yGreen) != 1))
+                pm_usage(usage);
+        } else if (pm_keymatch(argv[argn], "-blue", 1)) {
+            cs = &Customsystem;
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.xBlue) != 1))
+                pm_usage(usage);
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.yBlue) != 1))
+                pm_usage(usage);
+        } else if (pm_keymatch(argv[argn], "-white", 1)) {
+            cs = &Customsystem;
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.xWhite) != 1))
+                pm_usage(usage);
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.yWhite) != 1))
+                pm_usage(usage);
+        } else {
+            pm_usage(usage);
+        }
+        argn++;
+    }
+
+    if (argn != argc) {               /* Extra bogus arguments ? */
+        pm_usage(usage);
+    }
+
+    pixcols = sxsize;
+    pixrows = sysize;
+
+    pixels = ppm_allocarray(pixcols, pixrows);
+
+    /* Partition into plot area and axes and establish subwindow. */
+
+    xBias = Sz(32);
+    yBias = Sz(20);
+
+    makeAllBlack(pixels, pixcols, pixrows);
+
+    drawTongueOutline(pixels, pixcols, pixrows, Maxval, upvp, xBias, yBias);
+
+    fillInTongue(pixels, pixcols, pixrows, Maxval, cs, upvp, xBias, yBias,
+                 fullChart);
+
+    if (showAxes)
+        drawAxes(pixels, pixcols, pixrows, Maxval, upvp, xBias, yBias);
+
+    if (showWhite)
+        plotWhitePoint(pixels, pixcols, pixrows, Maxval,
+                       cs, upvp, xBias, yBias);
+
+    if (showBlack)
+        plotBlackBodyCurve(pixels, pixcols, pixrows, Maxval,
+                           upvp, xBias, yBias);
+
+    /* Plot wavelengths around periphery of the tongue. */
+
+    if (showAxes)
+        plotMonochromeWavelengths(pixels, pixcols, pixrows, Maxval,
+                                  cs, upvp, xBias, yBias);
+
+    if (showLabel)
+        writeLabel(pixels, pixcols, pixrows, Maxval, cs);
+
+    ppm_writeppm(stdout, pixels, pixcols, pixrows, Maxval, FALSE);
+
+    return 0;
+}
diff --git a/generator/ppmcolors.c b/generator/ppmcolors.c
new file mode 100644
index 00000000..9112e1b8
--- /dev/null
+++ b/generator/ppmcolors.c
@@ -0,0 +1,87 @@
+/******************************************************************************
+                                ppmcolors
+*******************************************************************************
+  Generate a color map containing all the colors representable with a certain
+  maxval.
+  
+  THIS PROGRAM IS OBSOLETE.  USE PAMSEQ INSTEAD.  WE KEEP THIS AROUND
+  FOR BACKWARD COMPATIBILITY.
+
+******************************************************************************/
+
+#include "ppm.h"
+#include "shhopt.h"
+#include "nstring.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    unsigned int maxval;   
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv, struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "maxval",       OPT_UINT,   
+            &cmdlineP->maxval,  NULL, 0);
+    
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    /* defaults */
+    cmdlineP->maxval = 5;
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (cmdlineP->maxval < 1)
+        pm_error("-maxval must be at least 1");
+    else if (cmdlineP->maxval > PPM_OVERALLMAXVAL)
+        pm_error("-maxval too high.  The maximum allowable value is %u",
+                 PPM_OVERALLMAXVAL);
+
+    if (argc-1 > 0)
+        pm_error("This program takes no arguments.  You specified %d",
+                 argc-1);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    const char * cmd;   /* malloc'ed */
+    int rc;
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    asprintfN(&cmd, "pamseq 3 %u -tupletype=RGB | pamtopnm", cmdline.maxval);
+
+    rc = system(cmd);
+
+    if (rc != 0) 
+        pm_error("pamseq|pamtopnm pipeline failed.  system() rc = %d", rc);
+
+    strfree(cmd);
+    exit(rc);
+}
+
+
+
+
diff --git a/generator/ppmforge.c b/generator/ppmforge.c
new file mode 100644
index 00000000..64b1ad79
--- /dev/null
+++ b/generator/ppmforge.c
@@ -0,0 +1,1182 @@
+/*
+
+        Fractal forgery generator for the PPM toolkit
+
+    Originally  designed  and  implemented  in December of 1989 by
+    John Walker as a stand-alone program for the  Sun  and  MS-DOS
+    under  Turbo C.  Adapted in September of 1991 for use with Jef
+        Poskanzer's raster toolkit.
+
+    References cited in the comments are:
+
+        Foley, J. D., and Van Dam, A., Fundamentals of Interactive
+        Computer  Graphics,  Reading,  Massachusetts:  Addison
+        Wesley, 1984.
+
+        Peitgen, H.-O., and Saupe, D. eds., The Science Of Fractal
+        Images, New York: Springer Verlag, 1988.
+
+        Press, W. H., Flannery, B. P., Teukolsky, S. A., Vetterling,
+        W. T., Numerical Recipes In C, New Rochelle: Cambridge
+        University Press, 1988.
+
+    Author:
+        John Walker
+        http://www.fourmilab.ch/
+
+    Permission  to  use, copy, modify, and distribute this software and
+    its documentation  for  any  purpose  and  without  fee  is  hereby
+    granted,  without any conditions or restrictions.  This software is
+    provided "as is" without express or implied warranty.
+
+*/
+
+#define _XOPEN_SOURCE   /* get M_PI in math.h */
+
+#include <math.h>
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "mallocvar.h"
+
+#ifdef VMS
+static double const hugeVal = HUGE_VAL;
+#else
+static double const hugeVal = 1e50;
+#endif
+
+/* Definitions used to address real and imaginary parts in a two-dimensional
+   array of complex numbers as stored by fourn(). */
+
+#define Real(v, x, y)  v[1 + (((x) * meshsize) + (y)) * 2]
+#define Imag(v, x, y)  v[2 + (((x) * meshsize) + (y)) * 2]
+
+/* Co-ordinate indices within arrays. */
+
+typedef struct {
+    double x;
+    double y;
+    double z; 
+} vector;
+
+/* Definition for obtaining random numbers. */
+
+#define nrand 4               /* Gauss() sample count */
+#define Cast(low, high) ((low)+(((high)-(low)) * ((rand() & 0x7FFF) / arand)))
+
+/* prototypes */
+static void fourn ARGS((float data[], int nn[], int ndim, int isign));
+static void initgauss ARGS((unsigned int seed));
+static double gauss ARGS((void));
+static void spectralsynth ARGS((float **x, unsigned int n, double h));
+static void temprgb ARGS((double temp, double *r, double *g, double *b));
+static void etoile ARGS((pixel *pix));
+/*  Local variables  */
+
+static double arand, gaussadd, gaussfac; /* Gaussian random parameters */
+static double fracdim;            /* Fractal dimension */
+static double powscale;           /* Power law scaling exponent */
+static int meshsize = 256;        /* FFT mesh size */
+static unsigned int seedarg;        /* Seed specified by user */
+static bool seedspec = FALSE;      /* Did the user specify a seed ? */
+static bool clouds = FALSE;        /* Just generate clouds */
+static bool stars = FALSE;         /* Just generate stars */
+static int screenxsize = 256;         /* Screen X size */
+static int screenysize = 256;         /* Screen Y size */
+static double inclangle, hourangle;   /* Star position relative to planet */
+static bool inclspec = FALSE;      /* No inclination specified yet */
+static bool hourspec = FALSE;      /* No hour specified yet */
+static double icelevel;           /* Ice cap theshold */
+static double glaciers;           /* Glacier level */
+static int starfraction;          /* Star fraction */
+static int starcolor;            /* Star color saturation */
+
+/*  FOURN  --  Multi-dimensional fast Fourier transform
+
+    Called with arguments:
+
+       data       A  one-dimensional  array  of  floats  (NOTE!!!   NOT
+              DOUBLES!!), indexed from one (NOTE!!!   NOT  ZERO!!),
+              containing  pairs of numbers representing the complex
+              valued samples.  The Fourier transformed results  are
+              returned in the same array.
+
+       nn         An  array specifying the edge size in each dimension.
+              THIS ARRAY IS INDEXED FROM  ONE,  AND  ALL  THE  EDGE
+              SIZES MUST BE POWERS OF TWO!!!
+
+       ndim       Number of dimensions of FFT to perform.  Set to 2 for
+              two dimensional FFT.
+
+       isign      If 1, a Fourier transform is done; if -1 the  inverse
+              transformation is performed.
+
+        This  function  is essentially as given in Press et al., "Numerical
+        Recipes In C", Section 12.11, pp.  467-470.
+*/
+
+static void fourn(data, nn, ndim, isign)
+    float data[];
+    int nn[], ndim, isign;
+{
+    register int i1, i2, i3;
+    int i2rev, i3rev, ip1, ip2, ip3, ifp1, ifp2;
+    int ibit, idim, k1, k2, n, nprev, nrem, ntot;
+    float tempi, tempr;
+    double theta, wi, wpi, wpr, wr, wtemp;
+
+#define SWAP(a,b) tempr=(a); (a) = (b); (b) = tempr
+
+    ntot = 1;
+    for (idim = 1; idim <= ndim; idim++)
+        ntot *= nn[idim];
+    nprev = 1;
+    for (idim = ndim; idim >= 1; idim--) {
+        n = nn[idim];
+        nrem = ntot / (n * nprev);
+        ip1 = nprev << 1;
+        ip2 = ip1 * n;
+        ip3 = ip2 * nrem;
+        i2rev = 1;
+        for (i2 = 1; i2 <= ip2; i2 += ip1) {
+            if (i2 < i2rev) {
+                for (i1 = i2; i1 <= i2 + ip1 - 2; i1 += 2) {
+                    for (i3 = i1; i3 <= ip3; i3 += ip2) {
+                        i3rev = i2rev + i3 - i2;
+                        SWAP(data[i3], data[i3rev]);
+                        SWAP(data[i3 + 1], data[i3rev + 1]);
+                    }
+                }
+            }
+            ibit = ip2 >> 1;
+            while (ibit >= ip1 && i2rev > ibit) {
+                i2rev -= ibit;
+                ibit >>= 1;
+            }
+            i2rev += ibit;
+        }
+        ifp1 = ip1;
+        while (ifp1 < ip2) {
+            ifp2 = ifp1 << 1;
+            theta = isign * (M_PI * 2) / (ifp2 / ip1);
+            wtemp = sin(0.5 * theta);
+            wpr = -2.0 * wtemp * wtemp;
+            wpi = sin(theta);
+            wr = 1.0;
+            wi = 0.0;
+            for (i3 = 1; i3 <= ifp1; i3 += ip1) {
+                for (i1 = i3; i1 <= i3 + ip1 - 2; i1 += 2) {
+                    for (i2 = i1; i2 <= ip3; i2 += ifp2) {
+                        k1 = i2;
+                        k2 = k1 + ifp1;
+                        tempr = wr * data[k2] - wi * data[k2 + 1];
+                        tempi = wr * data[k2 + 1] + wi * data[k2];
+                        data[k2] = data[k1] - tempr;
+                        data[k2 + 1] = data[k1 + 1] - tempi;
+                        data[k1] += tempr;
+                        data[k1 + 1] += tempi;
+                    }
+                }
+                wr = (wtemp = wr) * wpr - wi * wpi + wr;
+                wi = wi * wpr + wtemp * wpi + wi;
+            }
+            ifp1 = ifp2;
+        }
+        nprev *= n;
+    }
+}
+#undef SWAP
+
+/*  INITGAUSS  --  Initialize random number generators.  As given in
+           Peitgen & Saupe, page 77. */
+
+static void initgauss(seed)
+    unsigned int seed;
+{
+    /* Range of random generator */
+    arand = pow(2.0, 15.0) - 1.0;
+    gaussadd = sqrt(3.0 * nrand);
+    gaussfac = 2 * gaussadd / (nrand * arand);
+    srand(seed);
+}
+
+/*  GAUSS  --  Return a Gaussian random number.  As given in Peitgen
+           & Saupe, page 77. */
+
+static double gauss()
+{
+    int i;
+    double sum = 0.0;
+
+    for (i = 1; i <= nrand; i++) {
+        sum += (rand() & 0x7FFF);
+    }
+    return gaussfac * sum - gaussadd;
+}
+
+/*  SPECTRALSYNTH  --  Spectrally  synthesized  fractal  motion in two
+               dimensions.  This algorithm is given under  the
+               name   SpectralSynthesisFM2D  on  page  108  of
+               Peitgen & Saupe. */
+
+static void spectralsynth(x, n, h)
+    float **x;
+    unsigned int n;
+    double h;
+{
+    unsigned bl;
+    int i, j, i0, j0, nsize[3];
+    double rad, phase, rcos, rsin;
+    float *a;
+
+    bl = ((((unsigned long) n) * n) + 1) * 2 * sizeof(float);
+    a = (float *) calloc(bl, 1);
+    if (a == (float *) 0) {
+        pm_error("Cannot allocate %d x %d result array (% d bytes).",
+                 n, n, bl);
+    }
+    *x = a;
+
+    for (i = 0; i <= n / 2; i++) {
+        for (j = 0; j <= n / 2; j++) {
+            phase = 2 * M_PI * ((rand() & 0x7FFF) / arand);
+            if (i != 0 || j != 0) {
+                rad = pow((double) (i * i + j * j), -(h + 1) / 2) * gauss();
+            } else {
+                rad = 0;
+            }
+            rcos = rad * cos(phase);
+            rsin = rad * sin(phase);
+            Real(a, i, j) = rcos;
+            Imag(a, i, j) = rsin;
+            i0 = (i == 0) ? 0 : n - i;
+            j0 = (j == 0) ? 0 : n - j;
+            Real(a, i0, j0) = rcos;
+            Imag(a, i0, j0) = - rsin;
+        }
+    }
+    Imag(a, n / 2, 0) = 0;
+    Imag(a, 0, n / 2) = 0;
+    Imag(a, n / 2, n / 2) = 0;
+    for (i = 1; i <= n / 2 - 1; i++) {
+        for (j = 1; j <= n / 2 - 1; j++) {
+            phase = 2 * M_PI * ((rand() & 0x7FFF) / arand);
+            rad = pow((double) (i * i + j * j), -(h + 1) / 2) * gauss();
+            rcos = rad * cos(phase);
+            rsin = rad * sin(phase);
+            Real(a, i, n - j) = rcos;
+            Imag(a, i, n - j) = rsin;
+            Real(a, n - i, j) = rcos;
+            Imag(a, n - i, j) = - rsin;
+        }
+    }
+
+    nsize[0] = 0;
+    nsize[1] = nsize[2] = n;          /* Dimension of frequency domain array */
+    fourn(a, nsize, 2, -1);       /* Take inverse 2D Fourier transform */
+}
+
+
+static unsigned int
+initseed(void) {
+    /*  Generate initial random seed.  */
+
+    int i;
+
+    i = time(NULL) ^ 0xF37C;
+    srand(i);
+    for (i = 0; i < 7; ++i)
+        rand();
+    return rand();
+}
+
+
+
+/*  TEMPRGB  --  Calculate the relative R, G, and B components  for  a
+         black  body  emitting  light  at a given temperature.
+         The Planck radiation equation is solved directly  for
+         the R, G, and B wavelengths defined for the CIE  1931
+         Standard    Colorimetric    Observer.    The   color
+         temperature is specified in degrees Kelvin. */
+
+static void temprgb(temp, r, g, b)
+    double temp;
+    double *r, *g, *b;
+{
+    double c1 = 3.7403e10,
+        c2 = 14384.0,
+        er, eg, eb, es;
+
+/* Lambda is the wavelength in microns: 5500 angstroms is 0.55 microns. */
+
+#define Planck(lambda)  ((c1 * pow((double) lambda, -5.0)) /  \
+                         (pow(M_E, c2 / (lambda * temp)) - 1))
+
+        er = Planck(0.7000);
+        eg = Planck(0.5461);
+        eb = Planck(0.4358);
+#undef Planck
+
+        es = 1.0 / MAX(er, MAX(eg, eb));
+
+        *r = er * es;
+        *g = eg * es;
+        *b = eb * es;
+}
+
+/*  ETOILE  --  Set a pixel in the starry sky.  */
+
+static void etoile(pix)
+    pixel *pix;
+{
+    if ((rand() % 1000) < starfraction) {
+#define StarQuality 0.5       /* Brightness distribution exponent */
+#define StarIntensity   8         /* Brightness scale factor */
+#define StarTintExp 0.5       /* Tint distribution exponent */
+        double v = StarIntensity * pow(1 / (1 - Cast(0, 0.9999)),
+                                       (double) StarQuality),
+            temp,
+            r, g, b;
+
+        if (v > 255) {
+            v = 255;
+        }
+
+        /* We make a special case for star color  of zero in order to
+           prevent  floating  point  roundoff  which  would  otherwise
+           result  in  more  than  256 star colors.  We can guarantee
+           that if you specify no star color, you never get more than
+           256 shades in the image. */
+
+        if (starcolor == 0) {
+            int vi = v;
+
+            PPM_ASSIGN(*pix, vi, vi, vi);
+        } else {
+            temp = 5500 + starcolor *
+                pow(1 / (1 - Cast(0, 0.9999)), StarTintExp) *
+                ((rand() & 7) ? -1 : 1);
+            /* Constrain temperature to a reasonable value: >= 2600K 
+               (S Cephei/R Andromedae), <= 28,000 (Spica). */
+            temp = MAX(2600, MIN(28000, temp));
+            temprgb(temp, &r, &g, &b);
+            PPM_ASSIGN(*pix, (int) (r * v + 0.499),
+                       (int) (g * v + 0.499),
+                       (int) (b * v + 0.499));
+        }
+    } else {
+        PPM_ASSIGN(*pix, 0, 0, 0);
+    }
+}
+
+
+
+static double
+uprj(unsigned int const a,
+     unsigned int const size) {
+
+    return (double)a/(size-1);
+}
+
+
+
+static double
+atSat(double const x,
+      double const y,
+      double const dsat) {
+
+    return x*(1.0-dsat) + y*dsat;
+}
+
+
+
+static unsigned char *
+makeCp(float *      const a,
+       unsigned int const n,
+       pixval       const maxval) {
+
+    /* Prescale the grid points into intensities. */
+
+    unsigned char * cp;
+    unsigned char * ap;
+
+    if (UINT_MAX / n < n)
+        pm_error("arithmetic overflow squaring %u", n);
+    cp = malloc(n * n);
+    if (cp == NULL)
+        pm_error("Unable to allocate %u bytes for cp array", n);
+
+    ap = cp;
+    {
+        unsigned int i;
+        for (i = 0; i < n; i++) {
+            unsigned int j;
+            for (j = 0; j < n; j++)
+                *ap++ = ((double)maxval * (Real(a, i, j) + 1.0)) / 2.0;
+        }
+    }
+    return cp;
+}
+
+
+
+static void
+createPlanetStuff(float *          const a,
+                  unsigned int     const n,
+                  double **        const uP,
+                  double **        const u1P,
+                  unsigned int **  const bxfP,
+                  unsigned int **  const bxcP,
+                  unsigned char ** const cpP,
+                  vector *         const sunvecP,
+                  unsigned int     const cols,
+                  pixval           const maxval) {
+
+    double *u, *u1;
+    unsigned int *bxf, *bxc;
+    unsigned char * cp;
+    double shang, siang;
+    bool flipped;
+
+    /* Compute incident light direction vector. */
+
+    shang = hourspec ? hourangle : Cast(0, 2 * M_PI);
+    siang = inclspec ? inclangle : Cast(-M_PI * 0.12, M_PI * 0.12);
+
+    sunvecP->x = sin(shang) * cos(siang);
+    sunvecP->y = sin(siang);
+    sunvecP->z = cos(shang) * cos(siang);  /* initial value */
+
+    /* Allow only 25% of random pictures to be crescents */
+
+    if (!hourspec && ((rand() % 100) < 75)) {
+        flipped = (sunvecP->z < 0);
+        sunvecP->z = fabs(sunvecP->z);
+    } else
+        flipped = FALSE;
+
+    if (!clouds) {
+        pm_message(
+            "        -inclination %.0f -hour %d -ice %.2f -glaciers %.2f",
+            (siang * (180.0 / M_PI)),
+            (int) (((shang * (12.0 / M_PI)) + 12 +
+                    (flipped ? 12 : 0)) + 0.5) % 24, icelevel, glaciers);
+        pm_message("        -stars %d -saturation %d.",
+                   starfraction, starcolor);
+    }
+
+    cp = makeCp(a, n, maxval);
+
+    /* Fill the screen from the computed  intensity  grid  by  mapping
+       screen  points onto the grid, then calculating each pixel value
+       by bilinear interpolation from  the  surrounding  grid  points.
+       (N.b. the pictures would undoubtedly look better when generated
+       with small grids if  a  more  well-behaved  interpolation  were
+       used.)
+       
+       Also compute the line-level interpolation parameters that
+       caller will need every time around his inner loop.  
+    */
+
+    MALLOCARRAY(u,   cols);
+    MALLOCARRAY(u1,  cols);
+    MALLOCARRAY(bxf, cols);
+    MALLOCARRAY(bxc, cols);
+    
+    if (u == NULL || u1 == NULL || bxf == NULL || bxc == NULL) 
+        pm_error("Cannot allocate %d element interpolation tables.", cols);
+    {
+        unsigned int j;
+        for (j = 0; j < cols; j++) {
+            double const bx = (n - 1) * uprj(j, cols);
+            
+            bxf[j] = floor(bx);
+            bxc[j] = bxf[j] + 1;
+            u[j] = bx - bxf[j];
+            u1[j] = 1 - u[j];
+        }
+    }
+    *uP   = u;  *u1P  = u1;
+    *bxfP = bxf; *bxcP = bxc;
+    *cpP  = cp;
+}
+
+
+
+static void
+generateStarrySkyRow(pixel *      const pixels, 
+                     unsigned int const cols) {
+/*----------------------------------------------------------------------------
+  Generate a starry sky.  Note  that no FFT is performed;
+  the output is  generated  directly  from  a  power  law
+  mapping  of  a  pseudorandom sequence into intensities. 
+-----------------------------------------------------------------------------*/
+    unsigned int j;
+    
+    for (j = 0; j < cols; j++)
+        etoile(pixels + j);
+}
+
+
+
+static void
+generateCloudRow(pixel *         const pixels,
+                 unsigned int    const cols,
+                 double          const t,
+                 double          const t1,
+                 double *        const u,
+                 double *        const u1,
+                 unsigned char * const cp,
+                 unsigned int *  const bxc,
+                 unsigned int *  const bxf,
+                 int             const byc,
+                 int             const byf,
+                 pixval          const maxval) {
+
+    /* Render the FFT output as clouds. */
+
+    unsigned int j;
+
+    for (j = 0; j < cols; j++) {
+        double r;
+        pixval w;
+        
+        r = 0.0;  /* initial value */
+        /* Note that where t1 and t are zero, the cp[] element
+           referenced below does not exist.
+        */
+        if (t1 > 0.0)
+            r += t1 * u1[j] * cp[byf + bxf[j]] +
+                t1 * u[j]  * cp[byf + bxc[j]];
+        if (t > 0.0)
+            r += t * u1[j] * cp[byc + bxf[j]] +
+                t * u[j]  * cp[byc + bxc[j]];
+        
+        w = (r > 127.0) ? (maxval * ((r - 127.0) / 128.0)) : 0;
+        
+        PPM_ASSIGN(*(pixels + j), w, w, maxval);
+    }
+}
+
+
+
+static void
+makeLand(int *  const irP,
+         int *  const igP,
+         int *  const ibP,
+         double const r) {
+/*----------------------------------------------------------------------------
+  Land area.  Look up color based on elevation from precomputed
+  color map table.
+-----------------------------------------------------------------------------*/
+    static unsigned char pgnd[][3] = {
+        {206, 205, 0}, {208, 207, 0}, {211, 208, 0},
+        {214, 208, 0}, {217, 208, 0}, {220, 208, 0},
+        {222, 207, 0}, {225, 205, 0}, {227, 204, 0},
+        {229, 202, 0}, {231, 199, 0}, {232, 197, 0},
+        {233, 194, 0}, {234, 191, 0}, {234, 188, 0},
+        {233, 185, 0}, {232, 183, 0}, {231, 180, 0},
+        {229, 178, 0}, {227, 176, 0}, {225, 174, 0},
+        {223, 172, 0}, {221, 170, 0}, {219, 168, 0},
+        {216, 166, 0}, {214, 164, 0}, {212, 162, 0},
+        {210, 161, 0}, {207, 159, 0}, {205, 157, 0},
+        {203, 156, 0}, {200, 154, 0}, {198, 152, 0},
+        {195, 151, 0}, {193, 149, 0}, {190, 148, 0},
+        {188, 147, 0}, {185, 145, 0}, {183, 144, 0},
+        {180, 143, 0}, {177, 141, 0}, {175, 140, 0},
+        {172, 139, 0}, {169, 138, 0}, {167, 137, 0},
+        {164, 136, 0}, {161, 135, 0}, {158, 134, 0},
+        {156, 133, 0}, {153, 132, 0}, {150, 132, 0},
+        {147, 131, 0}, {145, 130, 0}, {142, 130, 0},
+        {139, 129, 0}, {136, 128, 0}, {133, 128, 0},
+        {130, 127, 0}, {127, 127, 0}, {125, 127, 0},
+        {122, 127, 0}, {119, 127, 0}, {116, 127, 0},
+        {113, 127, 0}, {110, 128, 0}, {107, 128, 0},
+        {104, 128, 0}, {102, 127, 0}, { 99, 126, 0},
+        { 97, 124, 0}, { 95, 122, 0}, { 93, 120, 0},
+        { 92, 117, 0}, { 92, 114, 0}, { 92, 111, 0},
+        { 93, 108, 0}, { 94, 106, 0}, { 96, 104, 0},
+        { 98, 102, 0}, {100, 100, 0}, {103,  99, 0},
+        {106,  99, 0}, {109,  99, 0}, {111, 100, 0},
+        {114, 101, 0}, {117, 102, 0}, {120, 103, 0},
+        {123, 102, 0}, {125, 102, 0}, {128, 100, 0},
+        {130,  98, 0}, {132,  96, 0}, {133,  94, 0},
+        {134,  91, 0}, {134,  88, 0}, {134,  85, 0},
+        {133,  82, 0}, {131,  80, 0}, {129,  78, 0}
+    };
+
+    unsigned int const ix = ((r - 128) * (ARRAY_SIZE(pgnd) - 1)) / 127;
+    
+    *irP = pgnd[ix][0];
+    *igP = pgnd[ix][1];
+    *ibP = pgnd[ix][2];
+} 
+
+
+
+static void
+makeWater(int *  const irP,
+          int *  const igP,
+          int *  const ibP,
+          double const r,
+          pixval const maxval) {
+
+    /* Water.  Generate clouds above water based on elevation.  */
+
+    *irP = *igP = r > 64 ? (r - 64) * 4 : 0;
+    *ibP = maxval;
+}
+
+
+
+static void
+addIce(int *  const irP,
+       int *  const igP,
+       int *  const ibP,
+       double const r,
+       double const azimuth,
+       double const icelevel,
+       double const glaciers,
+       pixval const maxval) {
+
+    /* Generate polar ice caps. */
+    
+    double const icet = pow(fabs(sin(azimuth)), 1.0 / icelevel) - 0.5;
+    double const ice = MAX(0.0, 
+                           (icet + glaciers * MAX(-0.5, (r - 128) / 128.0)));
+    if  (ice > 0.125) {
+        *irP = maxval;
+        *igP = maxval;
+        *ibP = maxval;
+    }
+}
+
+
+
+static void
+limbDarken(int *          const irP,
+           int *          const igP,
+           int *          const ibP,
+           unsigned int   const col,
+           unsigned int   const row,
+           unsigned int   const cols,
+           unsigned int   const rows,
+           vector         const sunvec,
+           pixval         const maxval) {
+
+    /* With Gcc 2.95.3 compiler optimization level > 1, I have seen this
+       function confuse all the variables and ultimately generate a 
+       completely black image.  Adding an extra reference to 'rows' seems
+       to put things back in order, and the assert() below does that.
+       Take it out, and the problem comes back!  04.02.21.
+    */
+
+    /* Apply limb darkening by cosine rule. */
+
+    double const atthick  = 1.03;
+    double const atSatFac = 1.0;
+    double const athfac   = sqrt(atthick * atthick - 1.0);
+        /* Atmosphere thickness as a percentage of planet's diameter */
+
+    double const dy = 2 * ((double)rows/2 - row) / rows;
+    double const dysq = dy * dy;
+    /* Note: we are in fact normalizing this horizontal position by the
+       vertical size of the picture.  And we know rows >= cols.
+    */
+    double const dx   = 2 * ((double)cols/2 - col) / rows;
+    double const dxsq = dx * dx;
+
+    double const ds = MIN(1.0, sqrt(dxsq + dysq));
+    
+    /* Calculate atmospheric absorption based on the thickness of
+       atmosphere traversed by light on its way to the surface.  
+    */
+    double const dsq = ds * ds;
+    double const dsat = atSatFac * ((sqrt(atthick * atthick - dsq) -
+                                     sqrt(1.0 * 1.0 - dsq)) / athfac);
+
+    assert(rows >= cols);  /* An input requirement */
+
+    *irP = atSat(*irP, maxval/2, dsat);
+    *igP = atSat(*igP, maxval/2, dsat);
+    *ibP = atSat(*ibP, maxval,   dsat);
+    {
+        double const PlanetAmbient = 0.05;
+
+        double const sqomdysq = sqrt(1.0 - dysq);
+        double const svx = sunvec.x;
+        double const svy = sunvec.y * dy;
+        double const svz = sunvec.z * sqomdysq;
+        double const di = 
+            MAX(0, MIN(1.0, svx * dx + svy + svz * sqrt(1.0 - dxsq)));
+        double const inx = PlanetAmbient * 1.0 + (1.0 - PlanetAmbient) * di;
+
+        *irP *= inx;
+        *igP *= inx;
+        *ibP *= inx;
+    }
+}
+
+
+
+static void
+generatePlanetRow(pixel *         const pixelrow,
+                  unsigned int    const row,
+                  unsigned int    const rows,
+                  unsigned int    const cols,
+                  double          const t,
+                  double          const t1,
+                  double *        const u,
+                  double *        const u1,
+                  unsigned char * const cp,
+                  unsigned int *  const bxc,
+                  unsigned int *  const bxf,
+                  int             const byc,
+                  int             const byf,
+                  vector          const sunvec,
+                  pixval          const maxval) {
+
+    unsigned int const StarClose = 2;
+
+    double const azimuth    = asin(((((double) row) / (rows - 1)) * 2) - 1);
+    unsigned int const lcos = (rows / 2) * fabs(cos(azimuth));
+
+    unsigned int col;
+
+    for (col = 0; col < cols; ++col)
+        PPM_ASSIGN(pixelrow[col], 0, 0, 0);
+
+    for (col = cols/2 - lcos; col <= cols/2 + lcos; ++col) {
+        double r;
+        int ir, ig, ib;
+
+        r = 0.0;   /* initial value */
+        
+        /* Note that where t1 and t are zero, the cp[] element
+           referenced below does not exist.  
+        */
+        if (t1 > 0.0)
+            r += t1 * u1[col] * cp[byf + bxf[col]] +
+                t1 * u[col]  * cp[byf + bxc[col]];
+        if (t > 0.0)
+            r += t * u1[col] * cp[byc + bxf[col]] +
+                t * u[col]  * cp[byc + bxc[col]];
+
+        if (r >= 128) 
+            makeLand(&ir, &ig, &ib, r);
+        else 
+            makeWater(&ir, &ig, &ib, r, maxval);
+
+        addIce(&ir, &ig, &ib, r, azimuth, icelevel, glaciers, maxval);
+
+        limbDarken(&ir, &ig, &ib, col, row, cols, rows, sunvec, maxval);
+
+        PPM_ASSIGN(pixelrow[col], ir, ig, ib);
+    }
+
+    /* Left stars */
+
+    for (col = 0; (int)col < (int)(cols/2 - (lcos + StarClose)); ++col)
+        etoile(&pixelrow[col]);
+
+    /* Right stars */
+
+    for (col = cols/2 + (lcos + StarClose); col < cols; ++col)
+        etoile(&pixelrow[col]);
+}
+
+
+
+static void 
+genplanet(bool         const stars,
+          bool         const clouds,
+          float *      const a, 
+          unsigned int const cols,
+          unsigned int const rows,
+          unsigned int const n,
+          unsigned int const rseed) {
+/*----------------------------------------------------------------------------
+  Generate planet from elevation array.
+
+  If 'stars' is true, a is undefined.  Otherwise, it is defined.
+-----------------------------------------------------------------------------*/
+    pixval const maxval = PPM_MAXMAXVAL;
+
+    unsigned char *cp;
+    double *u, *u1;
+    unsigned int *bxf, *bxc;
+
+    pixel *pixelrow;
+    unsigned int row;
+
+    vector sunvec;
+
+    ppm_writeppminit(stdout, cols, rows, maxval, FALSE);
+
+    if (stars) {
+        pm_message("night: -seed %d -stars %d -saturation %d.",
+                   rseed, starfraction, starcolor);
+        cp = NULL; 
+        u = NULL; u1 = NULL;
+        bxf = NULL; bxc = NULL;
+    } else {
+        pm_message("%s: -seed %d -dimension %.2f -power %.2f -mesh %d",
+                   clouds ? "clouds" : "planet",
+                   rseed, fracdim, powscale, meshsize);
+        createPlanetStuff(a, n, &u, &u1, &bxf, &bxc, &cp, &sunvec, 
+                          cols, maxval);
+    }
+
+    pixelrow = ppm_allocrow(cols);
+    for (row = 0; row < rows; ++row) {
+        if (stars)
+            generateStarrySkyRow(pixelrow, cols);
+        else {
+            double const by = (n - 1) * uprj(row, rows);
+            int    const byf = floor(by) * n;
+            int    const byc = byf + n;
+            double const t = by - floor(by);
+            double const t1 = 1 - t;
+
+            if (clouds)
+                generateCloudRow(pixelrow, cols,
+                                 t, t1, u, u1, cp, bxc, bxf, byc, byf,
+                                 maxval);
+            else 
+                generatePlanetRow(pixelrow, row, rows, cols,
+                                  t, t1, u, u1, cp, bxc, bxf, byc, byf,
+                                  sunvec,
+                                  maxval);
+        }
+        ppm_writeppmrow(stdout, pixelrow, cols, maxval, FALSE);
+    }
+    pm_close(stdout);
+
+    ppm_freerow(pixelrow);
+    if (cp)  free(cp);
+    if (u)   free(u);
+    if (u1)  free(u1);
+    if (bxf) free(bxf);
+    if (bxc) free(bxc);
+}
+
+
+
+static void
+applyPowerLawScaling(float * const a,
+                     int     const meshsize,
+                     double  const powscale) {
+
+    /* Apply power law scaling if non-unity scale is requested. */
+    
+    if (powscale != 1.0) {
+        unsigned int i;
+        for (i = 0; i < meshsize; i++) {
+            unsigned int j;
+            for (j = 0; j < meshsize; j++) {
+                double const r = Real(a, i, j);
+                if (r > 0)
+                    Real(a, i, j) = pow(r, powscale);
+            }
+        }
+    }
+}
+
+
+
+static void
+computeExtremeReal(const float * const a,
+                   int           const meshsize,
+                   double *      const rminP,
+                   double *      const rmaxP) {
+    
+    /* Compute extrema for autoscaling. */
+
+    double rmin, rmax;
+    unsigned int i;
+
+    rmin = hugeVal;
+    rmax = -hugeVal;
+
+    for (i = 0; i < meshsize; i++) {
+        unsigned int j;
+        for (j = 0; j < meshsize; j++) {
+            double r = Real(a, i, j);
+            
+            rmin = MIN(rmin, r);
+            rmax = MAX(rmax, r);
+        }
+    }
+    *rminP = rmin;
+    *rmaxP = rmax;
+}
+
+
+
+static void
+replaceWithSpread(float * const a,
+                  int     const meshsize) {
+/*----------------------------------------------------------------------------
+  Replace the real part of each element of the 'a' array with a
+  measure of how far the real is from the middle; sort of a standard
+  deviation.
+-----------------------------------------------------------------------------*/
+    double rmin, rmax;
+    double rmean, rrange;
+    unsigned int i;
+
+    computeExtremeReal(a, meshsize, &rmin, &rmax);
+
+    rmean = (rmin + rmax) / 2;
+    rrange = (rmax - rmin) / 2;
+
+    for (i = 0; i < meshsize; i++) {
+        unsigned int j;
+        for (j = 0; j < meshsize; j++) {
+            Real(a, i, j) = (Real(a, i, j) - rmean) / rrange;
+        }
+    }
+}
+
+
+
+static bool
+planet(unsigned int const cols,
+       unsigned int const rows,
+       bool         const stars,
+       bool         const clouds) {
+/*----------------------------------------------------------------------------
+   Make a planet.
+-----------------------------------------------------------------------------*/
+    float * a;
+    bool error;
+    unsigned int rseed;        /* Current random seed */
+    
+    if (seedspec)
+        rseed = seedarg;
+    else 
+        rseed = initseed();
+    
+    initgauss(rseed);
+    
+    if (stars) {
+        a = NULL;
+        error = FALSE;
+    } else {
+        spectralsynth(&a, meshsize, 3.0 - fracdim);
+        if (a == NULL) {
+            error = TRUE;
+        } else {
+            applyPowerLawScaling(a, meshsize, powscale);
+                
+            replaceWithSpread(a, meshsize);
+
+            error = FALSE;
+        }
+    }
+    if (!error)
+        genplanet(stars, clouds, a, cols, rows, meshsize, rseed);
+
+    if (a != NULL)
+        free(a);
+
+    return !error;
+}
+
+
+
+
+int 
+main(int argc, char ** argv) {
+
+    bool success;
+    int i;
+    const char * const usage = "\n\
+[-width|-xsize <x>] [-height|-ysize <y>] [-mesh <n>]\n\
+[-clouds] [-dimension <f>] [-power <f>] [-seed <n>]\n\
+[-hour <f>] [-inclination|-tilt <f>] [-ice <f>] [-glaciers <f>]\n\
+[-night] [-stars <n>] [-saturation <n>]";
+    bool dimspec = FALSE, meshspec = FALSE, powerspec = FALSE,
+        widspec = FALSE, hgtspec = FALSE, icespec = FALSE,
+        glacspec = FALSE, starspec = FALSE, starcspec = FALSE;
+
+    int cols, rows;     /* Dimensions of our output image */
+
+    ppm_init(&argc, argv);
+    i = 1;
+    
+    while ((i < argc) && (argv[i][0] == '-') && (argv[i][1] != '\0')) {
+
+        if (pm_keymatch(argv[i], "-clouds", 2)) {
+            clouds = TRUE;
+        } else if (pm_keymatch(argv[i], "-night", 2)) {
+            stars = TRUE;
+        } else if (pm_keymatch(argv[i], "-dimension", 2)) {
+            if (dimspec) {
+                pm_error("already specified a dimension");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%lf", &fracdim)  != 1))
+                pm_usage(usage);
+            if (fracdim <= 0.0) {
+                pm_error("fractal dimension must be greater than 0");
+            }
+            dimspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-hour", 3)) {
+            if (hourspec) {
+                pm_error("already specified an hour");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%lf", &hourangle) != 1))
+                pm_usage(usage);
+            hourangle = (M_PI / 12.0) * (hourangle + 12.0);
+            hourspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-inclination", 3) ||
+                   pm_keymatch(argv[i], "-tilt", 2)) {
+            if (inclspec) {
+                pm_error("already specified an inclination/tilt");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%lf", &inclangle) != 1))
+                pm_usage(usage);
+            inclangle = (M_PI / 180.0) * inclangle;
+            inclspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-mesh", 2)) {
+            unsigned int j;
+
+            if (meshspec) {
+                pm_error("already specified a mesh size");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%d", &meshsize) != 1))
+                pm_usage(usage);
+
+            /* Force FFT mesh to the next larger power of 2. */
+
+            for (j = meshsize; (j & 1) == 0; j >>= 1) ;
+
+            if (j != 1) {
+                for (j = 2; j < meshsize; j <<= 1) ;
+                meshsize = j;
+            }
+            meshspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-power", 2)) {
+            if (powerspec) {
+                pm_error("already specified a power factor");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%lf", &powscale) != 1))
+                pm_usage(usage);
+            if (powscale <= 0.0) {
+                pm_error("power factor must be greater than 0");
+            }
+            powerspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-ice", 3)) {
+            if (icespec) {
+                pm_error("already specified ice cap level");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%lf", &icelevel) != 1))
+                pm_usage(usage);
+            if (icelevel <= 0.0) {
+                pm_error("ice cap level must be greater than 0");
+            }
+            icespec = TRUE;
+        } else if (pm_keymatch(argv[i], "-glaciers", 2)) {
+            if (glacspec) {
+                pm_error("already specified glacier level");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%lf", &glaciers) != 1))
+                pm_usage(usage);
+            if (glaciers <= 0.0) {
+                pm_error("glacier level must be greater than 0");
+            }
+            glacspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-stars", 3)) {
+            if (starspec) {
+                pm_error("already specified a star fraction");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%d", &starfraction) != 1))
+                pm_usage(usage);
+            starspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-saturation", 3)) {
+            if (starcspec) {
+                pm_error("already specified a star color saturation");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%d", &starcolor) != 1))
+                pm_usage(usage);
+            starcspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-seed", 3)) {
+            if (seedspec) {
+                pm_error("already specified a random seed");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%u", &seedarg) != 1))
+                pm_usage(usage);
+            seedspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-xsize", 2) ||
+                   pm_keymatch(argv[i], "-width", 2)) {
+            if (widspec) {
+                pm_error("already specified a width/xsize");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%d", &screenxsize) != 1))
+                pm_usage(usage);
+            widspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-ysize", 2) ||
+                   pm_keymatch(argv[i], "-height", 3)) {
+            if (hgtspec) {
+                pm_error("already specified a height/ysize");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%d", &screenysize) != 1))
+                pm_usage(usage);
+            hgtspec = TRUE;
+        } else {
+            pm_usage(usage);
+        }
+        i++;
+    }
+
+
+    /* Set defaults when explicit specifications were not given.
+
+       The  default  fractal  dimension  and  power  scale depend upon
+       whether we are generating a planet or clouds. 
+    */
+    
+    if (!dimspec) {
+        fracdim = clouds ? 2.15 : 2.4;
+    }
+    if (!powerspec) {
+        powscale = clouds ? 0.75 : 1.2;
+    }
+    if (!icespec) {
+        icelevel = 0.4;
+    }
+    if (!glacspec) {
+        glaciers = 0.75;
+    }
+    if (!starspec) {
+        starfraction = 100;
+    }
+    if (!starcspec) {
+        starcolor = 125;
+    }
+
+    /* Force  screen to be at least  as wide as it is high.  Long,
+       skinny screens  cause  crashes  because  picture  width  is
+       calculated based on height.  
+    */
+
+    cols = (MAX(screenysize, screenxsize) + 1) & (~1);
+    rows = screenysize;
+
+    success = planet(cols, rows, stars, clouds);
+
+    exit(success ? 0 : 1);
+}
diff --git a/generator/ppmmake.c b/generator/ppmmake.c
new file mode 100644
index 00000000..eee32485
--- /dev/null
+++ b/generator/ppmmake.c
@@ -0,0 +1,117 @@
+/* ppmmake.c - create a pixmap of a specified color and size
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "ppm.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    pixel color;
+    unsigned int cols;
+    unsigned int rows;
+    pixval maxval;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+  Convert program invocation arguments (argc,argv) into a format the 
+  program can use easily, struct cmdlineInfo.  Validate arguments along
+  the way and exit program with message if invalid.
+
+  Note that some string information we return as *cmdlineP is in the storage 
+  argv[] points to.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int maxvalSpec;
+    unsigned int option_def_index;
+
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "maxval",    OPT_UINT, &cmdlineP->maxval, &maxvalSpec,    0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = false;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!maxvalSpec)
+        cmdlineP->maxval = PPM_MAXMAXVAL;
+    else {
+        if (cmdlineP->maxval > PPM_OVERALLMAXVAL)
+            pm_error("The value you specified for -maxval (%u) is too big.  "
+                     "Max allowed is %u", cmdlineP->maxval, PPM_OVERALLMAXVAL);
+        
+        if (cmdlineP->maxval < 1)
+            pm_error("You cannot specify 0 for -maxval");
+    }    
+
+    if (argc-1 < 3)
+        pm_error("Need 3 arguments: color, width, height.");
+    else if (argc-1 > 3)
+        pm_error("Only 3 arguments allowed: color, width, height.  "
+                 "You specified %d", argc-1);
+    else {
+        cmdlineP->color = ppm_parsecolor(argv[1], cmdlineP->maxval);
+        cmdlineP->cols = atoi(argv[2]);
+        cmdlineP->rows = atoi(argv[3]);
+        if (cmdlineP->cols <= 0)
+            pm_error("width argument must be a positive number.  You "
+                     "specified '%s'", argv[2]);
+        if (cmdlineP->rows <= 0)
+            pm_error("height argument must be a positive number.  You "
+                     "specified '%s'", argv[3]);
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    pixel * pixrow;
+    unsigned int row;
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ppm_writeppminit(stdout, cmdline.cols, cmdline.rows, cmdline.maxval, 0);
+    pixrow = ppm_allocrow(cmdline.cols);
+
+    for (row = 0; row < cmdline.rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cmdline.cols; ++col)
+            pixrow[col] = cmdline.color;
+        ppm_writeppmrow(stdout, pixrow, cmdline.cols, cmdline.maxval, 0);
+	}
+
+    ppm_freerow(pixrow);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/generator/ppmpat.c b/generator/ppmpat.c
new file mode 100644
index 00000000..343100d5
--- /dev/null
+++ b/generator/ppmpat.c
@@ -0,0 +1,1060 @@
+/* ppmpat.c - make a pixmap
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _XOPEN_SOURCE  /* get M_PI in math.h */
+
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "ppmdraw.h"
+
+
+
+
+static pixel
+random_anticamo_color(pixval const maxval) {
+
+    int v1, v2, v3;
+    pixel p;
+
+    v1 = (maxval + 1) / 4;
+    v2 = (maxval + 1) / 2;
+    v3 = 3 * v1;
+
+    switch (rand() % 15) {
+    case 0: case 1:
+        PPM_ASSIGN(p, rand() % v1 + v3, rand() % v2, rand() % v2);
+        break;
+
+    case 2:
+    case 3:
+        PPM_ASSIGN(p, rand() % v2, rand() % v1 + v3, rand() % v2);
+        break;
+        
+    case 4:
+    case 5:
+        PPM_ASSIGN(p, rand() % v2, rand() % v2, rand() % v1 + v3);
+        break;
+
+    case 6:
+    case 7:
+    case 8:
+        PPM_ASSIGN(p, rand() % v2, rand() % v1 + v3, rand() % v1 + v3);
+        break;
+
+    case 9:
+    case 10:
+    case 11:
+        PPM_ASSIGN(p, rand() % v1 + v3, rand() % v2, rand() % v1 + v3);
+        break;
+
+    case 12:
+    case 13:
+    case 14:
+        PPM_ASSIGN(p, rand() % v1 + v3, rand() % v1 + v3, rand() % v2);
+        break;
+    }
+    
+    return p;
+}
+
+
+
+/* Camouflage stuff. */
+
+static pixel
+random_camo_color(pixval const maxval) {
+
+    int v1, v2, v3;
+    pixel p;
+
+    v1 = (maxval + 1 ) / 8;
+    v2 = (maxval + 1 ) / 4;
+    v3 = (maxval + 1 ) / 2;
+
+    switch (rand() % 10) {
+    case 0:
+    case 1:
+    case 2:
+        /* light brown */
+        PPM_ASSIGN(p, rand() % v3 + v3, rand() % v3 + v2, rand() % v3 + v2);
+        break;
+
+    case 3:
+    case 4:
+    case 5:
+        /* dark green */
+        PPM_ASSIGN(p, rand() % v2, rand() % v2 + 3 * v1, rand() % v2);
+        break;
+    
+    case 6:
+    case 7:
+        /* brown */
+        PPM_ASSIGN(p, rand() % v2 + v2, rand() % v2, rand() % v2);
+        break;
+
+    case 8:
+    case 9:
+        /* dark brown */
+        PPM_ASSIGN(p, rand() % v1 + v1, rand() % v1, rand() % v1);
+        break;
+    }
+
+    return p;
+}
+
+
+
+static float
+rnduni(void) {
+    return rand() % 32767 / 32767.0;
+}
+
+
+
+#define BLOBRAD 50
+
+#define MIN_POINTS 7
+#define MAX_POINTS 13
+
+#define MIN_ELLIPSE_FACTOR 0.5
+#define MAX_ELLIPSE_FACTOR 2.0
+
+#define MIN_POINT_FACTOR 0.5
+#define MAX_POINT_FACTOR 2.0
+
+
+
+static void
+camo(pixel **     const pixels,
+     unsigned int const cols,
+     unsigned int const rows,
+     pixval       const maxval,
+     bool         const antiflag) {
+
+    pixel color;
+    int n, i, cx, cy;
+    struct fillobj * fh;
+
+    /* Clear background. */
+    if (antiflag)
+        color = random_anticamo_color( maxval );
+    else
+        color = random_camo_color( maxval );
+
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC,
+        &color);
+
+    n = (rows * cols) / (BLOBRAD * BLOBRAD) * 5;
+    for (i = 0; i < n; ++i) {
+        int points, p, xs[MAX_POINTS], ys[MAX_POINTS], x0, y0;
+        float a, b, c, theta, tang, tx, ty;
+        
+        cx = rand() % cols;
+        cy = rand() % rows;
+        
+        points = rand() % ( MAX_POINTS - MIN_POINTS + 1 ) + MIN_POINTS;
+        a = rnduni() * (MAX_ELLIPSE_FACTOR - MIN_ELLIPSE_FACTOR) +
+            MIN_ELLIPSE_FACTOR;
+        b = rnduni() * (MAX_ELLIPSE_FACTOR - MIN_ELLIPSE_FACTOR) +
+            MIN_ELLIPSE_FACTOR;
+        theta = rnduni() * 2.0 * M_PI;
+        for (p = 0; p < points; ++p) {
+            tx = a * sin(p * 2.0 * M_PI / points);
+            ty = b * cos(p * 2.0 * M_PI / points);
+            tang = atan2(ty, tx) + theta;
+            c = rnduni() * (MAX_POINT_FACTOR - MIN_POINT_FACTOR) +
+                MIN_POINT_FACTOR;
+            xs[p] = cx + BLOBRAD * c * sin(tang);
+            ys[p] = cy + BLOBRAD * c * cos(tang);
+        }
+        x0 = (xs[0] + xs[points - 1]) / 2;
+        y0 = (ys[0] + ys[points - 1]) / 2;
+
+        fh = ppmd_fill_create();
+
+        ppmd_polyspline(
+            pixels, cols, rows, maxval, x0, y0, points, xs, ys, x0, y0,
+            ppmd_fill_drawproc, fh );
+        
+        if (antiflag)
+            color = random_anticamo_color(maxval);
+        else
+            color = random_camo_color(maxval);
+        ppmd_fill(pixels, cols, rows, maxval, fh, PPMD_NULLDRAWPROC, &color);
+        
+        ppmd_fill_destroy(fh);
+    }
+}
+
+
+
+static pixel
+random_color(pixval const maxval) {
+
+    pixel p;
+
+    PPM_ASSIGN(p,
+               rand() % (maxval + 1),
+               rand() % (maxval + 1),
+               rand() % (maxval + 1)
+        );
+    
+    return p;
+}
+
+
+
+#define DARK_THRESH 0.25
+
+#if __STDC__
+static pixel
+random_bright_color( pixval maxval )
+#else /*__STDC__*/
+static pixel
+random_bright_color( maxval )
+    pixval maxval;
+#endif /*__STDC__*/
+    {
+    pixel p;
+
+    do
+    {
+    p = random_color( maxval );
+    }
+    while ( PPM_LUMIN( p ) <= maxval * DARK_THRESH );
+
+    return p;
+    }
+
+#if __STDC__
+static pixel
+random_dark_color( pixval maxval )
+#else /*__STDC__*/
+static pixel
+random_dark_color( maxval )
+    pixval maxval;
+#endif /*__STDC__*/
+    {
+    pixel p;
+
+    do
+    {
+    p = random_color( maxval );
+    }
+    while ( PPM_LUMIN( p ) > maxval * DARK_THRESH );
+
+    return p;
+    }
+
+static pixel
+average_two_colors( p1, p2 )
+pixel p1, p2;
+    {
+    pixel p;
+
+    PPM_ASSIGN(
+    p, ( (int) PPM_GETR(p1) + (int) PPM_GETR(p2) ) / 2,
+    ( (int) PPM_GETG(p1) + (int) PPM_GETG(p2) ) / 2,
+    ( (int) PPM_GETB(p1) + (int) PPM_GETB(p2) ) / 2 );
+
+    return p;
+    }
+
+/* Gingham stuff. */
+
+static void
+average_drawproc(pixel** const pixels, 
+                 int const cols, 
+                 int const rows, 
+                 pixval const maxval, 
+                 int const col, 
+                 int const row, 
+                 const void* const clientdata )
+{
+    if ( col >= 0 && col < cols && row >= 0 && row < rows )
+        pixels[row][col] =
+            average_two_colors( pixels[row][col], *( (pixel*) clientdata ) );
+}
+
+static void
+gingham2( pixel** pixels, int cols, int rows, pixval maxval )
+{
+    pixel const backcolor = random_dark_color( maxval );
+    pixel const forecolor = random_bright_color( maxval );
+    int const colso2 = cols / 2;
+    int const rowso2 = rows / 2;
+
+    /* Warp. */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, colso2, rows, PPMD_NULLDRAWPROC,
+        &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, colso2, 0, cols - colso2, rows,
+        PPMD_NULLDRAWPROC, &forecolor );
+
+    /* Woof. */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols, rowso2, average_drawproc,
+        &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rowso2, cols, rows - rowso2,
+        average_drawproc, &forecolor );
+    }
+
+#if __STDC__
+static void
+gingham3( pixel** pixels, int cols, int rows, pixval maxval )
+#else /*__STDC__*/
+static void
+gingham3( pixels, cols, rows, maxval )
+    pixel** pixels;
+    int cols, rows;
+    pixval maxval;
+#endif /*__STDC__*/
+    {
+    int colso4, rowso4;
+    pixel backcolor, fore1color, fore2color;
+
+    colso4 = cols / 4;
+    rowso4 = rows / 4;
+    backcolor = random_dark_color( maxval );
+    fore1color = random_bright_color( maxval );
+    fore2color = random_bright_color( maxval );
+
+    /* Warp. */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, colso4, rows, PPMD_NULLDRAWPROC,
+        &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, colso4, 0, colso4, rows, PPMD_NULLDRAWPROC,
+        &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 2 * colso4, 0, colso4, rows,
+        PPMD_NULLDRAWPROC, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 3 * colso4, 0, cols - colso4, rows,
+        PPMD_NULLDRAWPROC, &fore1color );
+
+    /* Woof. */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols, rowso4, average_drawproc,
+        &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rowso4, cols, rowso4, average_drawproc,
+        &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 2 * rowso4, cols, rowso4,
+        average_drawproc, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 3 * rowso4, cols, rows - rowso4,
+        average_drawproc, &fore1color );
+    }
+
+#if __STDC__
+static void
+madras( pixel** pixels, int cols, int rows, pixval maxval )
+#else /*__STDC__*/
+static void
+madras( pixels, cols, rows, maxval )
+    pixel** pixels;
+    int cols, rows;
+    pixval maxval;
+#endif /*__STDC__*/
+    {
+    int cols2, rows2, cols3, rows3, cols12, rows12, cols6a, rows6a, cols6b,
+    rows6b;
+    pixel backcolor, fore1color, fore2color;
+
+    cols2 = cols * 2 / 44;
+    rows2 = rows * 2 / 44;
+    cols3 = cols * 3 / 44;
+    rows3 = rows * 3 / 44;
+    cols12 = cols - 10 * cols2 - 4 * cols3;
+    rows12 = rows - 10 * rows2 - 4 * rows3;
+    cols6a = cols12 / 2;
+    rows6a = rows12 / 2;
+    cols6b = cols12 - cols6a;
+    rows6b = rows12 - rows6a;
+    backcolor = random_dark_color( maxval );
+    fore1color = random_bright_color( maxval );
+    fore2color = random_bright_color( maxval );
+
+    /* Warp. */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols2, rows, PPMD_NULLDRAWPROC,
+        &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols2, 0, cols3, rows, PPMD_NULLDRAWPROC,
+        &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols2 + cols3, 0, cols2, rows,
+        PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 2 * cols2 + cols3, 0, cols2, rows,
+        PPMD_NULLDRAWPROC, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 3 * cols2 + cols3, 0, cols2, rows,
+        PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 4 * cols2 + cols3, 0, cols6a, rows,
+        PPMD_NULLDRAWPROC, &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 4 * cols2 + cols3 + cols6a, 0, cols2, rows,
+        PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 5 * cols2 + cols3 + cols6a, 0, cols3, rows,
+        PPMD_NULLDRAWPROC, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 5 * cols2 + 2 * cols3 + cols6a, 0, cols2,
+        rows, PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 6 * cols2 + 2 * cols3 + cols6a, 0, cols3,
+        rows, PPMD_NULLDRAWPROC, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 6 * cols2 + 3 * cols3 + cols6a, 0, cols2,
+        rows, PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 7 * cols2 + 3 * cols3 + cols6a, 0, cols6b,
+        rows, PPMD_NULLDRAWPROC, &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 7 * cols2 + 3 * cols3 + cols6a + cols6b, 0,
+        cols2, rows, PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 8 * cols2 + 3 * cols3 + cols6a + cols6b, 0,
+        cols2, rows, PPMD_NULLDRAWPROC, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 9 * cols2 + 3 * cols3 + cols6a + cols6b, 0,
+        cols2, rows, PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 10 * cols2 + 3 * cols3 + cols6a + cols6b, 
+        0, cols3, rows, PPMD_NULLDRAWPROC, &fore1color );
+
+    /* Woof. */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols, rows2, average_drawproc,
+        &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows2, cols, rows3, average_drawproc,
+        &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows2 + rows3, cols, rows2,
+        average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 2 * rows2 + rows3, cols, rows2,
+        average_drawproc, &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 3 * rows2 + rows3, cols, rows2,
+        average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 4 * rows2 + rows3, cols, rows6a,
+        average_drawproc, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 4 * rows2 + rows3 + rows6a, cols, rows2,
+        average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 5 * rows2 + rows3 + rows6a, cols, rows3,
+        average_drawproc, &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 5 * rows2 + 2 * rows3 + rows6a, cols,
+        rows2, average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 6 * rows2 + 2 * rows3 + rows6a, cols,
+        rows3, average_drawproc, &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 6 * rows2 + 3 * rows3 + rows6a, cols,
+        rows2, average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 7 * rows2 + 3 * rows3 + rows6a, cols,
+        rows6b, average_drawproc, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 7 * rows2 + 3 * rows3 + rows6a + rows6b,
+        cols, rows2, average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 8 * rows2 + 3 * rows3 + rows6a + rows6b,
+        cols, rows2, average_drawproc, &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 9 * rows2 + 3 * rows3 + rows6a + rows6b,
+        cols, rows2, average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 
+        10 * rows2 + 3 * rows3 + rows6a + rows6b,
+        cols, rows3, average_drawproc, &fore2color );
+    }
+
+#if __STDC__
+static void
+tartan( pixel** pixels, int cols, int rows, pixval maxval )
+#else /*__STDC__*/
+static void
+tartan( pixels, cols, rows, maxval )
+    pixel** pixels;
+    int cols, rows;
+    pixval maxval;
+#endif /*__STDC__*/
+    {
+    int cols1, rows1, cols3, rows3, cols10, rows10, cols5a, rows5a, cols5b,
+    rows5b;
+    pixel backcolor, fore1color, fore2color;
+
+    cols1 = cols / 22;
+    rows1 = rows / 22;
+    cols3 = cols * 3 / 22;
+    rows3 = rows * 3 / 22;
+    cols10 = cols - 3 * cols1 - 3 * cols3;
+    rows10 = rows - 3 * rows1 - 3 * rows3;
+    cols5a = cols10 / 2;
+    rows5a = rows10 / 2;
+    cols5b = cols10 - cols5a;
+    rows5b = rows10 - rows5a;
+    backcolor = random_dark_color( maxval );
+    fore1color = random_bright_color( maxval );
+    fore2color = random_bright_color( maxval );
+
+    /* Warp. */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols5a, rows, PPMD_NULLDRAWPROC,
+        &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols5a, 0, cols1, rows, PPMD_NULLDRAWPROC,
+        &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols5a + cols1, 0, cols5b, rows,
+        PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols10 + cols1, 0, cols3, rows,
+        PPMD_NULLDRAWPROC, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols10 + cols1 + cols3, 0, cols1, rows,
+        PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols10 + 2 * cols1 + cols3, 0, cols3, rows,
+        PPMD_NULLDRAWPROC, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols10 + 2 * cols1 + 2 * cols3, 0, cols1,
+        rows, PPMD_NULLDRAWPROC, (char*) &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols10 + 3 * cols1 + 2 * cols3, 0, cols3,
+        rows, PPMD_NULLDRAWPROC, &fore2color );
+
+    /* Woof. */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols, rows5a, average_drawproc,
+        &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows5a, cols, rows1, average_drawproc,
+        &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows5a + rows1, cols, rows5b,
+        average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows10 + rows1, cols, rows3,
+        average_drawproc, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows10 + rows1 + rows3, cols, rows1,
+        average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows10 + 2 * rows1 + rows3, cols, rows3,
+        average_drawproc, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows10 + 2 * rows1 + 2 * rows3, cols,
+        rows1, average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows10 + 3 * rows1 + 2 * rows3, cols,
+        rows3, average_drawproc, &fore2color );
+    }
+
+/* Poles stuff. */
+
+#define MAXPOLES 500
+
+#if __STDC__
+static void
+poles( pixel** pixels, int cols, int rows, pixval maxval )
+#else /*__STDC__*/
+static void
+poles( pixels, cols, rows, maxval )
+    pixel** pixels;
+    int cols, rows;
+    pixval maxval;
+#endif /*__STDC__*/
+    {
+    int poles, i, xs[MAXPOLES], ys[MAXPOLES], col, row;
+    pixel colors[MAXPOLES];
+
+    poles = cols * rows / 30000;
+
+    /* Place and color poles randomly. */
+    for ( i = 0; i < poles; ++i )
+    {
+    xs[i] = rand() % cols;
+    ys[i] = rand() % rows;
+    colors[i] = random_bright_color( maxval );
+    }
+
+    /* Now interpolate points. */
+    for ( row = 0; row < rows; ++row )
+    for ( col = 0; col < cols; ++col )
+        {
+        register long dist1, dist2, newdist, r, g, b;
+        pixel color1, color2;
+
+        /* Find two closest poles. */
+        dist1 = dist2 = 2000000000;
+        for ( i = 0; i < poles; ++i )
+        {
+        newdist = ( col - xs[i] ) * ( col - xs[i] ) +
+              ( row - ys[i] ) * ( row - ys[i] );
+        if ( newdist < dist1 )
+            {
+            dist1 = newdist;
+            color1 = colors[i];
+            }
+        else if ( newdist < dist2 )
+            {
+            dist2 = newdist;
+            color2 = colors[i];
+            }
+        }
+
+        /* And assign interpolated color. */
+        newdist = dist1 + dist2;
+        r = PPM_GETR(color1)*dist2/newdist + PPM_GETR(color2)*dist1/newdist;
+        g = PPM_GETG(color1)*dist2/newdist + PPM_GETG(color2)*dist1/newdist;
+        b = PPM_GETB(color1)*dist2/newdist + PPM_GETB(color2)*dist1/newdist;
+        PPM_ASSIGN( pixels[row][col], r, g, b );
+        }
+    }
+
+/* Squig stuff. */
+
+#define SQUIGS 5
+#define SQ_POINTS 7
+#define SQ_MAXCIRCLE_POINTS 5000
+
+static int sq_radius, sq_circlecount;
+static pixel sq_colors[SQ_MAXCIRCLE_POINTS];
+static int sq_xoffs[SQ_MAXCIRCLE_POINTS], sq_yoffs[SQ_MAXCIRCLE_POINTS];
+
+static void
+sq_measurecircle_drawproc(pixel** const pixels, 
+                          int const cols, 
+                          int const rows, 
+                          pixval const maxval, 
+                          int const col, 
+                          int const row, 
+                          const void* const clientdata)
+{
+    sq_xoffs[sq_circlecount] = col;
+    sq_yoffs[sq_circlecount] = row;
+    ++sq_circlecount;
+}
+
+static void
+sq_rainbowcircle_drawproc(pixel** const pixels, 
+                          int const cols, 
+                          int const rows, 
+                          pixval const maxval, 
+                          int const col, 
+                          int const row, 
+                          const void* const clientdata )
+{
+    int i;
+
+    for ( i = 0; i < sq_circlecount; ++i )
+    ppmd_point_drawproc(
+        pixels, cols, rows, maxval, col + sq_xoffs[i], row + sq_yoffs[i],
+        &(sq_colors[i]) );
+    }
+
+
+
+static void
+sq_assign_colors(int     const circlecount,
+                 pixval  const maxval,
+                 pixel * const colors) {
+
+    pixel rc1, rc2, rc3;
+    float cco3;
+    unsigned int i;
+
+    rc1 = random_bright_color(maxval);
+    rc2 = random_bright_color(maxval);
+    rc3 = random_bright_color(maxval);
+    cco3 = (circlecount - 1) / 3.0;
+
+    for (i = 0; i < circlecount ; ++i) {
+        if (i < cco3) {
+            float const frac = (float)i/cco3;
+            PPM_ASSIGN(colors[i],
+                       (float) PPM_GETR(rc1) +
+                       ((float) PPM_GETR(rc2) - (float) PPM_GETR(rc1)) * frac,
+                       (float) PPM_GETG(rc1) +
+                       ((float) PPM_GETG(rc2) - (float) PPM_GETG(rc1)) * frac,
+                       (float) PPM_GETB(rc1) +
+                       ((float) PPM_GETB(rc2) - (float) PPM_GETB(rc1)) * frac
+                );
+        } else if (i < 2.0 * cco3) {
+            float const frac = (float)i/cco3 - 1.0;
+            PPM_ASSIGN(colors[i],
+                       (float) PPM_GETR(rc2) +
+                       ((float) PPM_GETR(rc3) - (float) PPM_GETR(rc2)) * frac,
+                       (float) PPM_GETG(rc2) +
+                       ((float) PPM_GETG(rc3) - (float) PPM_GETG(rc2)) * frac,
+                       (float) PPM_GETB(rc2) +
+                       ((float) PPM_GETB(rc3) - (float) PPM_GETB(rc2)) * frac
+                       );
+        } else {
+            float const frac = (float)i/cco3 - 2.0;
+            PPM_ASSIGN(colors[i],
+                       (float) PPM_GETR(rc3) +
+                       ((float) PPM_GETR(rc1) - (float) PPM_GETR(rc3)) * frac,
+                       (float) PPM_GETG(rc3) +
+                       ((float) PPM_GETG(rc1) - (float) PPM_GETG(rc3)) * frac,
+                       (float) PPM_GETB(rc3) +
+                       ((float) PPM_GETB(rc1) - (float) PPM_GETB(rc3)) * frac
+                );
+        }
+    }
+}
+
+
+#if __STDC__
+static void
+squig( pixel** pixels, int cols, int rows, pixval maxval )
+#else /*__STDC__*/
+static void
+squig( pixels, cols, rows, maxval )
+    pixel** pixels;
+    int cols, rows;
+    pixval maxval;
+#endif /*__STDC__*/
+    {
+    pixel color;
+    int i, j, xc[SQ_POINTS], yc[SQ_POINTS], x0, y0, x1, y1, x2, y2, x3, y3;
+
+    /* Clear image to black. */
+    PPM_ASSIGN( color, 0, 0, 0 );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC,
+        &color );
+
+    /* Draw the squigs. */
+    (void) ppmd_setlinetype( PPMD_LINETYPE_NODIAGS );
+    (void) ppmd_setlineclip( 0 );
+    for ( i = SQUIGS; i > 0; --i )
+    {
+    /* Measure circle. */
+    sq_radius = ( cols + rows ) / 2 / ( 25 + i * 2 );
+    sq_circlecount = 0;
+    ppmd_circle(
+        pixels, cols, rows, maxval, 0, 0, sq_radius,
+        sq_measurecircle_drawproc, NULL );
+    sq_assign_colors( sq_circlecount, maxval, sq_colors );
+
+    /* Choose wrap-around point. */
+    switch ( rand() % 4 )
+        {
+        case 0:
+        x1 = rand() % cols;
+        y1 = 0;
+        if ( x1 < cols / 2 )
+        xc[0] = rand() % ( x1 * 2 );
+        else
+        xc[0] = cols - 1 - rand() % ( ( cols - x1 ) * 2 );
+        yc[0] = rand() % rows;
+        x2 = x1;
+        y2 = rows - 1;
+        xc[SQ_POINTS - 1] = 2 * x2 - xc[0];
+        yc[SQ_POINTS - 1] = y2 - yc[0];
+        x0 = xc[SQ_POINTS - 1];
+        y0 = yc[SQ_POINTS - 1] - rows;
+        x3 = xc[0];
+        y3 = yc[0] + rows;
+        break;
+
+        case 1:
+        x2 = rand() % cols;
+        y2 = 0;
+        if ( x2 < cols / 2 )
+        xc[SQ_POINTS - 1] = rand() % ( x2 * 2 );
+        else
+        xc[SQ_POINTS - 1] = cols - 1 - rand() % ( ( cols - x2 ) * 2 );
+        yc[SQ_POINTS - 1] = rand() % rows;
+        x1 = x2;
+        y1 = rows - 1;
+        xc[0] = 2 * x1 - xc[SQ_POINTS - 1];
+        yc[0] = y1 - yc[SQ_POINTS - 1];
+        x0 = xc[SQ_POINTS - 1];
+        y0 = yc[SQ_POINTS - 1] + rows;
+        x3 = xc[0];
+        y3 = yc[0] - rows;
+        break;
+
+        case 2:
+        x1 = 0;
+        y1 = rand() % rows;
+        xc[0] = rand() % cols;
+        if ( y1 < rows / 2 )
+        yc[0] = rand() % ( y1 * 2 );
+        else
+        yc[0] = rows - 1 - rand() % ( ( rows - y1 ) * 2 );
+        x2 = cols - 1;
+        y2 = y1;
+        xc[SQ_POINTS - 1] = x2 - xc[0];
+        yc[SQ_POINTS - 1] = 2 * y2 - yc[0];
+        x0 = xc[SQ_POINTS - 1] - cols;
+        y0 = yc[SQ_POINTS - 1];
+        x3 = xc[0] + cols;
+        y3 = yc[0];
+        break;
+
+        case 3:
+        x2 = 0;
+        y2 = rand() % rows;
+        xc[SQ_POINTS - 1] = rand() % cols;
+        if ( y2 < rows / 2 )
+        yc[SQ_POINTS - 1] = rand() % ( y2 * 2 );
+        else
+        yc[SQ_POINTS - 1] = rows - 1 - rand() % ( ( rows - y2 ) * 2 );
+        x1 = cols - 1;
+        y1 = y2;
+        xc[0] = x1 - xc[SQ_POINTS - 1];
+        yc[0] = 2 * y1 - yc[SQ_POINTS - 1];
+        x0 = xc[SQ_POINTS - 1] + cols;
+        y0 = yc[SQ_POINTS - 1];
+        x3 = xc[0] - cols;
+        y3 = yc[0];
+        break;
+        }
+
+    for ( j = 1; j < SQ_POINTS - 1; ++j )
+        {
+        xc[j] = ( rand() % ( cols - 2 * sq_radius ) ) + sq_radius;
+        yc[j] = ( rand() % ( rows - 2 * sq_radius ) ) + sq_radius;
+        }
+
+    ppmd_line(
+        pixels, cols, rows, maxval, x0, y0, x1, y1,
+        sq_rainbowcircle_drawproc, NULL );
+    ppmd_polyspline(
+        pixels, cols, rows, maxval, x1, y1, SQ_POINTS, xc, yc, x2, y2,
+        sq_rainbowcircle_drawproc, NULL );
+    ppmd_line(
+        pixels, cols, rows, maxval, x2, y2, x3, y3,
+        sq_rainbowcircle_drawproc, NULL );
+    }
+    }
+
+
+
+/* Test pattern.  Just a place to put ppmdraw exercises. */
+
+static void
+test(pixel **     const pixels,
+     unsigned int const cols,
+     unsigned int const rows,
+     pixval       const maxval) {
+
+    pixel color;
+    struct fillobj * fh;
+
+    /* Clear image to black. */
+    PPM_ASSIGN( color, 0, 0, 0 );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC,
+        &color);
+
+    fh = ppmd_fill_create();
+
+    ppmd_line(pixels, cols, rows, maxval, 
+              cols/8, rows/8, cols/2, rows/4, ppmd_fill_drawproc, fh);
+    ppmd_line(pixels, cols, rows, maxval, 
+              cols/2, rows/4, cols-cols/8, rows/8, ppmd_fill_drawproc, fh);
+    ppmd_line(pixels, cols, rows, maxval, 
+              cols-cols/8, rows/8, cols/2, rows/2, ppmd_fill_drawproc, fh);
+    ppmd_spline3(pixels, cols, rows, maxval, 
+                 cols/2, rows/2, cols/2-cols/16, rows/2-rows/10, 
+                 cols/2-cols/8, rows/2, ppmd_fill_drawproc, fh);
+    ppmd_spline3(pixels, cols, rows, maxval, 
+                 cols/2-cols/8, rows/2, cols/4+cols/16, rows/2+rows/10, 
+                 cols/4, rows/2, ppmd_fill_drawproc, fh);
+    ppmd_line(pixels, cols, rows, maxval, 
+              cols/4, rows/2, cols/8, rows/2, ppmd_fill_drawproc, fh);
+    ppmd_line(pixels, cols, rows, maxval, 
+              cols/8, rows/2, cols/8, rows/8, ppmd_fill_drawproc, fh);
+
+    PPM_ASSIGN(color, maxval, maxval, maxval);
+    ppmd_fill(pixels, cols, rows, maxval, fh, PPMD_NULLDRAWPROC, &color);
+
+    ppmd_fill_destroy(fh);
+
+}
+
+
+
+int
+main(int argc, char ** argv) {
+
+    pixel** pixels;
+    int argn, pattern, cols, rows;
+#define PAT_NONE 0
+#define PAT_GINGHAM2 1
+#define PAT_GINGHAM3 2
+#define PAT_MADRAS 3
+#define PAT_TARTAN 4
+#define PAT_POLES 5
+#define PAT_SQUIG 6
+#define PAT_CAMO 7
+#define PAT_ANTICAMO 8
+#define PAT_TEST 9
+    const char* const usage = "-gingham|-g2|-gingham3|-g3|-madras|-tartan|-poles|-squig|-camo|-anticamo <width> <height>";
+
+
+    ppm_init(&argc, argv);
+
+    argn = 1;
+    pattern = PAT_NONE;
+
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+    {
+        if ( pm_keymatch( argv[argn], "-gingham2", 9 ) ||
+             pm_keymatch( argv[argn], "-g2", 3 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_GINGHAM2;
+        }
+        else if ( pm_keymatch( argv[argn], "-gingham3", 9 ) ||
+                  pm_keymatch( argv[argn], "-g3", 3 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_GINGHAM3;
+        }
+        else if ( pm_keymatch( argv[argn], "-madras", 2 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_MADRAS;
+        }
+        else if ( pm_keymatch( argv[argn], "-tartan", 2 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_TARTAN;
+        }
+        else if ( pm_keymatch( argv[argn], "-poles", 2 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_POLES;
+        }
+        else if ( pm_keymatch( argv[argn], "-squig", 2 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_SQUIG;
+        }
+        else if ( pm_keymatch( argv[argn], "-camo", 2 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_CAMO;
+        }
+        else if ( pm_keymatch( argv[argn], "-anticamo", 2 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_ANTICAMO;
+        }
+        else if ( pm_keymatch( argv[argn], "-test", 3 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_TEST;
+        }
+        else
+            pm_usage( usage );
+        ++argn;
+    }
+    if ( pattern == PAT_NONE )
+        pm_error( "a base pattern must be specified" );
+
+    if ( argn == argc )
+        pm_usage( usage);
+    if ( sscanf( argv[argn], "%d", &cols ) != 1 )
+        pm_usage( usage );
+    ++argn;
+    if ( argn == argc )
+        pm_usage( usage);
+    if ( sscanf( argv[argn], "%d", &rows ) != 1 )
+        pm_usage( usage );
+    ++argn;
+
+    if ( argn != argc )
+        pm_usage( usage);
+
+    srand( (int) ( time( 0 ) ^ getpid( ) ) );
+    pixels = ppm_allocarray( cols, rows );
+
+    switch ( pattern )
+    {
+    case PAT_GINGHAM2:
+        gingham2( pixels, cols, rows, PPM_MAXMAXVAL );
+        break;
+
+    case PAT_GINGHAM3:
+        gingham3( pixels, cols, rows, PPM_MAXMAXVAL );
+        break;
+
+    case PAT_MADRAS:
+        madras( pixels, cols, rows, PPM_MAXMAXVAL );
+        break;
+
+    case PAT_TARTAN:
+        tartan( pixels, cols, rows, PPM_MAXMAXVAL );
+        break;
+
+    case PAT_POLES:
+        poles( pixels, cols, rows, PPM_MAXMAXVAL );
+        break;
+
+    case PAT_SQUIG:
+        squig( pixels, cols, rows, PPM_MAXMAXVAL );
+        break;
+
+    case PAT_CAMO:
+        camo( pixels, cols, rows, PPM_MAXMAXVAL, 0 );
+        break;
+
+    case PAT_ANTICAMO:
+        camo( pixels, cols, rows, PPM_MAXMAXVAL, 1 );
+        break;
+
+    case PAT_TEST:
+        test( pixels, cols, rows, PPM_MAXMAXVAL );
+        break;
+
+    default:
+        pm_error( "can't happen!" );
+    }
+
+    /* All done, write it out. */
+    ppm_writeppm( stdout, pixels, cols, rows, PPM_MAXMAXVAL, 0 );
+    pm_close( stdout );
+
+    exit( 0 );
+}
+
diff --git a/generator/ppmrainbow b/generator/ppmrainbow
new file mode 100755
index 00000000..0effeecf
--- /dev/null
+++ b/generator/ppmrainbow
@@ -0,0 +1,74 @@
+#!/usr/bin/perl -wl
+use strict;
+use Getopt::Long;
+
+my ($FALSE, $TRUE) = (0,1);
+
+(my $myname = $0) =~ s#\A.*/##;
+
+my ($Twid, $Thgt, $tmpdir, $norepeat, $verbose);
+
+# set defaults
+$Twid = 600;
+$Thgt = 8;
+$tmpdir = $ENV{"TMPDIR"} || "/tmp";
+$norepeat = $FALSE;
+$verbose = $FALSE;
+
+GetOptions("width=i"   => \$Twid,
+           "height=i"  => \$Thgt,
+           "tmpdir=s"  => \$tmpdir,
+           "norepeat!" => \$norepeat,
+           "verbose!"  => \$verbose);
+
+die "invalid width and/or height\n" unless $Twid >= 1 && $Thgt >= 1;
+
+my $verboseCommand = $verbose ? "set -x;" : "";
+
+if (@ARGV < 1) {
+    die("You must specify at least one color as an argument");
+} elsif (@ARGV < 2 && $norepeat) {
+    die("With the -norepeat option, you must specify at least two colors " .
+        "as arguments.");
+}
+
+my @colorlist;
+
+@colorlist = @ARGV;
+if (!$norepeat) {
+    push @colorlist, $ARGV[0];
+}
+
+my $tmpprefix = $tmpdir . "/$myname.$$.";
+
+my $widthRemaining;
+my $n;
+my @outlist;
+
+$n = 0;
+$widthRemaining = $Twid;
+@outlist = ();
+
+while (@colorlist >= 2) {
+    my $outfile = sprintf("%s%03u.ppm", $tmpprefix, $n);
+    push(@outlist, $outfile);
+
+    my $w = int(($widthRemaining-1)/(@colorlist-1))+1;
+    my $rc = system("$verboseCommand pgmramp -lr $w $Thgt | " .
+                    "pgmtoppm \"$colorlist[0]-$colorlist[1]\" >$outfile");
+    if ($rc != 0) {
+        die("pgmramp|pgmtoppm failed.");
+    }
+    $widthRemaining -= $w;
+    $n++;
+    shift @colorlist;
+}
+
+0 == system qq{$verboseCommand pnmcat -lr @outlist}
+    or exit 1;
+
+exit 0;
+
+END {
+    unlink @outlist if @outlist;
+}
diff --git a/generator/ppmrough.c b/generator/ppmrough.c
new file mode 100644
index 00000000..f8823173
--- /dev/null
+++ b/generator/ppmrough.c
@@ -0,0 +1,292 @@
+/* ppmrough.c - create a PPM image containing two colors with a ragged
+   border between them
+**
+** Copyright (C) 2002 by Eckard Specht.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.  */
+
+#include <math.h>
+#include <sys/time.h>
+#include "ppm.h"
+#include "shhopt.h"
+
+static pixel** PIX;
+static pixval BG_RED, BG_GREEN, BG_BLUE;
+
+
+struct cmdlineInfo {
+  /* All the information the user supplied in the command line,
+     in a form easy for the program to use.
+  */
+  unsigned int left, right, top, bottom;
+  unsigned int width, height, var;
+  const char *bg_rgb;
+  const char *fg_rgb;
+  unsigned init;
+  unsigned int verbose;
+};
+
+
+static void
+/*-------------------------------------------------------------------------- */
+   parseCommandLine(int argc, char ** argv, struct cmdlineInfo *cmdlineP)
+/*-------------------------------------------------------------------------- */
+{
+  optEntry *option_def = malloc(100*sizeof(optEntry));
+    /* Instructions to OptParseOptions2 on how to parse our options.    */
+  optStruct3 opt;
+
+  unsigned int option_def_index;
+
+  option_def_index = 0;   /* incremented by OPTENTRY */
+  OPTENT3(0, "width",   OPT_UINT,   &cmdlineP->width,   NULL, 0);
+  OPTENT3(0, "height",  OPT_UINT,   &cmdlineP->height,  NULL, 0);
+  OPTENT3(0, "left",    OPT_UINT,   &cmdlineP->left,    NULL, 0);
+  OPTENT3(0, "right",   OPT_UINT,   &cmdlineP->right,   NULL, 0);
+  OPTENT3(0, "top",     OPT_UINT,   &cmdlineP->top,     NULL, 0);
+  OPTENT3(0, "bottom",  OPT_UINT,   &cmdlineP->bottom,  NULL, 0);
+  OPTENT3(0, "bg",      OPT_STRING, &cmdlineP->bg_rgb,  NULL, 0);
+  OPTENT3(0, "fg",      OPT_STRING, &cmdlineP->fg_rgb,  NULL, 0);
+  OPTENT3(0, "var",     OPT_UINT,   &cmdlineP->var,     NULL, 0);
+  OPTENT3(0, "init",    OPT_UINT,   &cmdlineP->init,    NULL, 0);
+  OPTENT3(0, "verbose", OPT_FLAG,   NULL, &cmdlineP->verbose, 0);
+
+  /* Set the defaults */
+  cmdlineP->width = 100;
+  cmdlineP->height = 100;
+  cmdlineP->left = cmdlineP->right = cmdlineP->top = cmdlineP->bottom = -1;
+  cmdlineP->bg_rgb = NULL;
+  cmdlineP->fg_rgb = NULL;
+  cmdlineP->var = 10;
+
+  opt.opt_table = option_def;
+  opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+  opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+  optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+  if (argc-1 != 0)
+    pm_error("There are no arguments.  You specified %d.", argc-1);
+}
+
+static void
+/* ----------------------------------------- */
+   proc_left(int const r1, int const r2, int const c1, int const c2, 
+             unsigned int const var)
+/* ----------------------------------------- */
+{
+  int cm, rm, c;
+
+  if (r1 + 1 == r2)  return;
+  rm = (r1 + r2) >> 1;
+  cm = (c1 + c2) >> 1;
+  cm += (int)floor(((float)rand() / RAND_MAX - 0.5) * var + 0.5);
+
+  for (c = 0; c < cm; c++)
+    PPM_ASSIGN(PIX[rm][c], BG_RED, BG_GREEN, BG_BLUE);
+
+  proc_left(r1, rm, c1, cm, var);
+  proc_left(rm, r2, cm, c2, var);
+}
+
+static void
+/* ------------------------------------------ */
+   proc_right(int const r1, int const r2, int const c1, int const c2,
+              unsigned int const width, unsigned int const var)
+/* ------------------------------------------ */
+{
+  int cm, rm, c;
+
+  if (r1 + 1 == r2)  return;
+  rm = (r1 + r2) >> 1;
+  cm = (c1 + c2) >> 1;
+  cm += (int)floor(((float)rand() / RAND_MAX - 0.5) * var + 0.5);
+
+  for (c = cm; c < width; c++)
+    PPM_ASSIGN(PIX[rm][c], BG_RED, BG_GREEN, BG_BLUE);
+
+  proc_right(r1, rm, c1, cm, width, var);
+  proc_right(rm, r2, cm, c2, width, var);
+}
+
+static void
+/* ---------------------------------------- */
+   proc_top(int const c1, int const c2, int const r1, int const r2,
+            unsigned int const var)
+/* ---------------------------------------- */
+{
+  int rm, cm, r;
+
+  if (c1 + 1 == c2)  return;
+  cm = (c1 + c2) >> 1;
+  rm = (r1 + r2) >> 1;
+  rm += (int)floor(((float)rand() / RAND_MAX - 0.5) * var + 0.5);
+
+  for (r = 0; r < rm; r++)
+    PPM_ASSIGN(PIX[r][cm], BG_RED, BG_GREEN, BG_BLUE);
+
+  proc_top(c1, cm, r1, rm, var);
+  proc_top(cm, c2, rm, r2, var);
+}
+
+static void
+/* ------------------------------------------- */
+   proc_bottom(int const c1, int const c2, int const r1, int const r2,
+               unsigned int const height, unsigned int const var)
+/* ------------------------------------------- */
+{
+  int rm, cm, r;
+
+  if (c1 + 1 == c2)  return;
+  cm = (c1 + c2) >> 1;
+  rm = (r1 + r2) >> 1;
+  rm += (int)floor(((float)rand() / RAND_MAX - 0.5) * var + 0.5);
+
+  for (r = rm; r < height; r++)
+    PPM_ASSIGN(PIX[r][cm], BG_RED, BG_GREEN, BG_BLUE);
+
+  proc_bottom(c1, cm, r1, rm, height, var);
+  proc_bottom(cm, c2, rm, r2, height, var);
+}
+
+
+int
+/* ============================ */
+   main(int argc, char* argv[])
+/* ============================ */
+{
+  struct cmdlineInfo cmdline;
+  pixel bgcolor, fgcolor;
+  pixval fg_red, fg_green, fg_blue;
+  int rows, cols, row;
+  int left, right, top, bottom;
+  int left_r1, left_r2, left_c1, left_c2;
+  int right_r1, right_r2, right_c1, right_c2;
+  int top_r1, top_r2, top_c1, top_c2;
+  int bottom_r1, bottom_r2, bottom_c1, bottom_c2;
+  unsigned init;
+  struct timeval tv;
+  int col;
+
+  ppm_init(&argc, argv);
+
+  parseCommandLine(argc, argv, &cmdline);
+
+  init = cmdline.init;
+  if (init == 0) {
+    gettimeofday(&tv, NULL);
+    srand((unsigned int)tv.tv_usec);
+  }
+  else
+    srand(init);
+
+  cols = cmdline.width;
+  rows = cmdline.height;
+  left = cmdline.left;
+  right = cmdline.right;
+  top = cmdline.top;
+  bottom = cmdline.bottom;
+
+  if (cmdline.bg_rgb)
+    bgcolor = ppm_parsecolor(cmdline.bg_rgb, PPM_MAXMAXVAL);
+  else
+    PPM_ASSIGN(bgcolor, 0, 0, 0);
+  BG_RED = PPM_GETR(bgcolor);
+  BG_GREEN = PPM_GETG(bgcolor);
+  BG_BLUE = PPM_GETB(bgcolor);
+
+  if (cmdline.fg_rgb)
+    fgcolor = ppm_parsecolor(cmdline.fg_rgb, PPM_MAXMAXVAL);
+  else
+    PPM_ASSIGN(fgcolor, PPM_MAXMAXVAL, PPM_MAXMAXVAL, PPM_MAXMAXVAL);
+  fg_red = PPM_GETR(fgcolor);
+  fg_green = PPM_GETG(fgcolor);
+  fg_blue = PPM_GETB(fgcolor);
+
+  if (cmdline.verbose) {
+    pm_message("width is %d, height is %d, variance is %d.", 
+               cols, rows, cmdline.var);
+    if (left >= 0) pm_message("ragged left border is required");
+    if (right >= 0) pm_message("ragged right border is required");
+    if (top >= 0) pm_message("ragged top border is required");
+    if (bottom >= 0) pm_message("ragged bottom border is required");
+    pm_message("background is %s", ppm_colorname(&bgcolor, PPM_MAXMAXVAL, 1));
+    pm_message("foreground is %s", ppm_colorname(&fgcolor, PPM_MAXMAXVAL, 1));
+    if (init >= 0) pm_message("srand() initialized with seed %u", init);
+  }
+
+  /* Allocate memory for the whole pixmap */
+  PIX = ppm_allocarray(cols, rows);
+
+  /* First, set all pixel to foreground color */
+  for (row = 0; row < rows; row++)
+    for (col = 0; col < cols; col++)
+      PPM_ASSIGN(PIX[row][col], fg_red, fg_green, fg_blue);
+
+  /* Make a ragged left border */
+  if (left >= 0) {
+    left_c1 = left_c2 = left;
+    left_r1 = 0;
+    left_r2 = rows - 1;
+    for (col = 0; col < left_c1; col++)
+      PPM_ASSIGN(PIX[left_r1][col], BG_RED, BG_GREEN, BG_BLUE);
+    for (col = 0; col < left_c2; col++)
+      PPM_ASSIGN(PIX[left_r2][col], BG_RED, BG_GREEN, BG_BLUE);
+
+    proc_left(left_r1, left_r2, left_c1, left_c2, cmdline.var);
+  }
+
+  /* Make a ragged right border */
+  if (right >= 0) {
+    right_c1 = right_c2 = cols - right - 1;
+    right_r1 = 0;
+    right_r2 = rows - 1;
+    for (col = right_c1; col < cols; col++)
+      PPM_ASSIGN(PIX[right_r1][col], BG_RED, BG_GREEN, BG_BLUE);
+    for (col = right_c2; col < cols; col++)
+      PPM_ASSIGN(PIX[right_r2][col], BG_RED, BG_GREEN, BG_BLUE);
+
+    proc_right(right_r1, right_r2, right_c1, right_c2, 
+               cmdline.width, cmdline.var);
+  }
+
+  /* Make a ragged top border */
+  if (top >= 0) {
+    top_r1 = top_r2 = top;
+    top_c1 = 0;
+    top_c2 = cols - 1;
+    for (row = 0; row < top_r1; row++)
+      PPM_ASSIGN(PIX[row][top_c1], BG_RED, BG_GREEN, BG_BLUE);
+    for (row = 0; row < top_r2; row++)
+      PPM_ASSIGN(PIX[row][top_c2], BG_RED, BG_GREEN, BG_BLUE);
+
+    proc_top(top_c1, top_c2, top_r1, top_r2, cmdline.var);
+  }
+
+  /* Make a ragged bottom border */
+  if (bottom >= 0) {
+    bottom_r1 = bottom_r2 = rows - bottom - 1;
+    bottom_c1 = 0;
+    bottom_c2 = cols - 1;
+    for (row = bottom_r1; row < rows; row++)
+      PPM_ASSIGN(PIX[row][bottom_c1], BG_RED, BG_GREEN, BG_BLUE);
+    for (row = bottom_r2; row < rows; row++)
+      PPM_ASSIGN(PIX[row][bottom_c2], BG_RED, BG_GREEN, BG_BLUE);
+
+    proc_bottom(bottom_c1, bottom_c2, bottom_r1, bottom_r2, 
+                cmdline.height, cmdline.var);
+  }
+
+  /* Write pixmap */
+  ppm_writeppm(stdout, PIX, cols, rows, PPM_MAXMAXVAL, 0);
+
+  ppm_freearray(PIX, rows);
+
+  pm_close(stdout);
+  exit(0);
+}
diff --git a/generator/ppmwheel.c b/generator/ppmwheel.c
new file mode 100644
index 00000000..ef5021f9
--- /dev/null
+++ b/generator/ppmwheel.c
@@ -0,0 +1,157 @@
+/* ppmwheel.c - create a color circle of a specified size
+**
+** This was adapted by Bryan Henderson in January 2003 from ppmcirc.c by
+** Peter Kirchgessner:
+**
+** Copyright (C) 1995 by Peter Kirchgessner.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+#include <math.h>
+
+#include "ppm.h"
+
+#ifndef PI
+#define PI  3.14159265358979323846
+#endif
+
+#ifndef ABS
+#define ABS(a) ((a) < 0 ? -(a) : (a))
+#endif
+
+static void 
+hsv_rgb(double const in_h, double const in_s, double const in_v, 
+        double * const r, double * const g, double * const b) {
+/*----------------------------------------------------------------------------
+   This is a stripped down hsv->rgb converter that works only for
+   Saturation of zero.
+-----------------------------------------------------------------------------*/
+    double h, s, v;
+
+    h = in_h < 0.0 ? 0.0 : in_h > 360.0 ? 360.0 : in_h;
+
+    v = in_v < 0.0 ? 0.0 : in_v > 1.0 ? 1.0 : in_v;
+
+    s = in_s < 0.0 ? 0.0 : in_s > 1.0 ? 1.0 : in_s;
+
+    if (s != 0.0)
+        pm_error("Internal error: non-zero saturation");
+
+    if (h <= 60.0) {          /* from red to yellow */
+        *r = 1.0;
+        *g = h / 60.0;
+        *b = 0.0;
+    } else if ( h <= 120.0 ) {   /* from yellow to green */
+        *r = 1.0 - (h - 60.0) / 60.0;
+        *g = 1.0;
+        *b = 0.0;
+    } else if ( h <= 180.0 ) {   /* from green to cyan */
+        *r = 0.0;
+        *g = 1.0;
+        *b = (h - 120.0) / 60.0;
+    } else if ( h <= 240.0 ) {    /* from cyan to blue */
+        *r = 0.0;
+        *g = 1.0 - (h - 180.0) / 60.0;
+        *b = 1.0;
+    } else if ( h <= 300.0) {    /* from blue to magenta */
+        *r = (h - 240.0) / 60.0;
+        *g = 0.0;
+        *b = 1.0;
+    } else {                      /* from magenta to red */
+        *r = 1.0;
+        *g = 0.0;
+        *b = 1.0 - (h - 300.0) / 60.0;
+    }
+
+    if ( v >= 0.5) {
+        v = 2.0 - 2.0 * v;
+        v = sqrt (v);
+        *r = 1.0 + v * (*r - 1.0);
+        *g = 1.0 + v * (*g - 1.0);
+        *b = 1.0 + v * (*b - 1.0);
+    } else {
+        v *= 2.0;
+        v = sqrt (sqrt ( sqrt (v)));
+        *r *= v;
+        *g *= v;
+        *b *= v;
+    }
+}
+
+
+int
+main(int argc, char *argv[]) {
+    pixel *orow;
+    int rows, cols;
+    pixval maxval;
+    unsigned int row;
+    unsigned int xcenter, ycenter, radius;
+    long diameter;
+    char * tailptr;
+
+    ppm_init( &argc, argv );
+
+    if (argc-1 != 1)
+        pm_error("Program takes one argument:  diameter of color wheel");
+
+    diameter = strtol(argv[1], &tailptr, 10);
+    if (strlen(argv[1]) == 0 || *tailptr != '\0')
+        pm_error("You specified an invalid diameter: '%s'", argv[1]);
+    if (diameter <= 0)
+        pm_error("Diameter must be positive.  You specified %ld.", diameter);
+    if (diameter < 4)
+        pm_error("Diameter must be at least 4.  You specified %ld", diameter);
+
+    cols = rows = diameter;
+    
+    orow = ppm_allocrow(cols);
+
+    maxval = PPM_MAXMAXVAL;
+    ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+    radius = diameter/2 - 1;
+
+    xcenter = cols / 2;
+    ycenter = rows / 2;
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            double const dx = (int)col - (int)xcenter;
+            double const dy = (int)row - (int)ycenter;
+            double const dist = sqrt(dx*dx + dy*dy);
+
+            pixval r, g, b;
+
+            if (dist > radius) {
+                r = g = b = maxval;
+            } else {
+                double hue, sat, val;
+                double dr, dg, db;
+
+                hue = atan2(dx, dy) / PI * 180.0;
+                if (hue < 0.0) 
+                    hue = 360.0 + hue;
+                sat = 0.0;
+                val = dist / radius;
+
+                hsv_rgb(hue, sat, val, &dr, &dg, &db);
+
+                r = (pixval)(maxval * dr);
+                g = (pixval)(maxval * dg);
+                b = (pixval)(maxval * db);
+            }
+            PPM_ASSIGN (orow[col], r, g, b );
+        }
+        ppm_writeppmrow(stdout, orow, cols, maxval, 0);
+    }
+    pm_close(stdout);
+    exit(0);
+}
diff --git a/installnetpbm b/installnetpbm
new file mode 100755
index 00000000..a84c2820
--- /dev/null
+++ b/installnetpbm
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+perl `dirname $0`/buildtools/installnetpbm.pl
diff --git a/inttypes_netpbm.h b/inttypes_netpbm.h
new file mode 100644
index 00000000..6852145c
--- /dev/null
+++ b/inttypes_netpbm.h
@@ -0,0 +1,8 @@
+/* This was generated by the program 'typegen' */
+#ifndef INTTYPES_H_NETPBM
+#define INTTYPES_H_NETPBM
+typedef unsigned int uint32_t;
+typedef signed int int32_t;
+typedef signed int int_fast32_t;
+typedef unsigned int uint_fast32_t;
+#endif
diff --git a/lib/Makefile b/lib/Makefile
new file mode 100644
index 00000000..bd8eccae
--- /dev/null
+++ b/lib/Makefile
@@ -0,0 +1,278 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = lib
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+DLLTOOL=dlltool
+include $(BUILDDIR)/Makefile.config
+
+ifeq ($(NETPBMLIBTYPE),unixstatic)
+LIBNETPBM = libnetpbm.$(STATICLIBSUFFIX)
+else
+LIBNETPBM = $(NETPBMSHLIBPREFIX)netpbm$(DLLVER).$(NETPBMLIBSUFFIX)
+endif
+
+ifeq ($(STATICLIB_TOO),y)
+EXTRA_STATICLIB = libnetpbm.$(STATICLIBSUFFIX)
+else
+EXTRA_STATICLIB =
+endif
+
+ifeq ($(DONT_HAVE_PROCESS_MGMT),Y)
+  LIBSYSTEM = libsystem_dummy.o
+else
+  LIBSYSTEM = libsystem.o
+endif
+
+LIBOBJECTS = libpm.o fileio.o bitio.o colorname.o \
+	libpbm1.o libpbm2.o libpbm3.o libpbmfont.o \
+	libpgm1.o libpgm2.o \
+	libppm1.o libppm2.o libppmcmap.o libppmcolor.o libppmfuzzy.o \
+	libppmd.o ppmdfont.o standardppmdfont.o path.o \
+	libppmfloyd.o \
+	libpnm1.o libpnm2.o libpnm3.o \
+	libpam.o libpamread.o libpamwrite.o \
+	libpamn.o libpammap.o libpamcolor.o \
+	$(LIBSYSTEM) \
+
+ifneq (${VMS}x,x)
+LIBOBJECTS += libpbmvms.o
+endif
+# Library objects to be linked but not built by Makefile.common:
+LIBOBJECTS_X = util/shhopt.o util/nstring.o util/filename.o
+
+MANUALS3 = libnetpbm
+MANUALS5 = pbm pgm ppm pnm pam
+
+INTERFACE_HEADERS =  pm.h pbm.h bitio.h pbmfont.h \
+	pgm.h ppm.h ppm.h ppmcmap.h ppmfloyd.h colorname.h \
+	pnm.h pam.h pammap.h util/shhopt.h util/nstring.h util/mallocvar.h \
+	pm_system.h pm_gamma.h
+
+DATAFILES = rgb.txt
+
+.PHONY: all
+all: libnetpbm extra_staticlib
+
+INCLUDES = -I$(SRCDIR)/$(SUBDIR) -I. -Iimportinc
+
+SUBDIRS = util
+SCRIPTS = 
+BINARIES = 
+
+OMIT_LIBRARY_RULE = 1
+include $(SRCDIR)/Makefile.common
+
+# The following must go after Makefile.common because $(LIBNETPBM) may 
+# contain a reference to $(NETPBM_MAJOR_RELEASE).
+.PHONY: libnetpbm
+libnetpbm: $(LIBNETPBM)
+
+.PHONY: extra_staticlib
+extra_staticlib: $(EXTRA_STATICLIB)
+
+#----------------------------------------------------------------------------
+# Following are rules for building shared libraries.
+# Note that the user may specify a shared library as his "main" library
+# type, but request a static library in addition.
+#----------------------------------------------------------------------------
+
+$(LIBOBJECTS): %.o: %.c importinc
+# Note that the user may have configured -I options into CFLAGS.
+	$(CC) -c $(INCLUDES) -DNDEBUG $(CFLAGS) $(CFLAGS_SHLIB) \
+	  $(CFLAGS_PERSONAL) $(CADD) -o $@ $<
+
+MAJ = $(NETPBM_MAJOR_RELEASE)
+MIN = $(NETPBM_MINOR_RELEASE)
+
+SONAME = libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ)
+
+ifeq ($(NETPBMLIBTYPE),irixshared)
+# The libxxx.so link is needed to link the executables.
+libnetpbm.$(NETPBMLIBSUFFIX): $(SONAME)
+	rm -f $@
+	$(SYMLINK) $< $@
+
+PERLPROG = print "sgi$(MAJ)." . join(":sgi$(MAJ) . ", (0..$(MIN))) . "\n"
+
+$(SONAME): \
+    $(LIBOBJECTS) $(LIBOBJECTS_X) 
+	$(LD) $(LDSHLIB) -o $@ $(LIBOBJECTS) $(LIBOBJECTS_X) \
+          -lc \
+	  -soname libnetpbm.$(NETPBMLIBSUFFIX) \
+	  -set_version $(shell perl -e '$(PERLPROG)') \
+	  $(LADD)
+endif
+
+ifeq ($(NETPBMLIBTYPE),unixshared)
+# The libxxx.so link is needed to link the executables.
+libnetpbm.$(NETPBMLIBSUFFIX): $(SONAME)
+	rm -f $@
+	$(SYMLINK) $< $@
+# The $(SONAME) link is needed only to test the programs without
+# installing the libraries (in that case, you also need to direct the 
+# dynamic linker to the source directories, e.g. set LD_LIBRARY_PATH).
+$(SONAME): libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ).$(MIN)
+	rm -f $@
+	$(SYMLINK) $< $@
+libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ).$(MIN): $(LIBOBJECTS) $(LIBOBJECTS_X)
+	$(LD) $(LDSHLIB) -o $@ $(LIBOBJECTS) $(LIBOBJECTS_X) \
+          $(SHLIB_CLIB) -lm $(LADD)
+endif
+
+ifeq ($(NETPBMLIBTYPE),dll)
+ifeq ($(STATICLIB_TOO),y)
+$(NETPBMSHLIBPREFIX)netpbm$(DLLVER).dll: $(LIBOBJECTS) $(LIBOBJECTS_X) libnetpbm.$(STATICLIBSUFFIX)
+else
+$(NETPBMSHLIBPREFIX)netpbm$(DLLVER).dll: $(LIBOBJECTS) $(LIBOBJECTS_X)
+endif
+	$(LD) $(LDSHLIB) -Wl,--export-all-symbols \
+            -Wl,-soname,$(NETPBMSHLIBPREFIX)netpbm$(DLLVER).dll \
+	    -Wl,--output-def,$(NETPBMSHLIBPREFIX)netpbm$(DLLVER).def \
+            -Wl,--out-implib,libnetpbm.dll.a -o $@ $(LDFLAGS) \
+            $(LIBOBJECTS) $(LIBOBJECTS_X) $(LDLIBS) $(LADD) 
+endif
+
+ifeq ($(NETPBMLIBTYPE),dylib)
+libnetpbm.dylib: libnetpbm.$(MAJ).dylib
+	rm -f $@
+	$(SYMLINK) $< $@
+
+libnetpbm.$(MAJ).dylib: libnetpbm.$(MAJ).$(MIN).dylib
+	rm -f $@
+	$(SYMLINK) $< $@
+
+libnetpbm.$(MAJ).$(MIN).dylib: $(LIBOBJECTS) $(LIBOBJECTS_X) 
+	$(LD) $(LDSHLIB) -o $@ $(LIBOBJECTS) $(LIBOBJECTS_X) \
+           -lc $(LADD)
+endif
+
+#--------------------------------------------------------------------------
+# The rule for building a static library is below (if needed).  This is
+# tricky because the user can be building the static library as his main
+# library or in addition to some other kind of main library.  In fact,
+# he may have specified it both as the main library type and an 
+# additional library type.  In that case, NETPBMLIBSUFFIX and 
+# STATICLIBSUFFIX are redundant -- we hope they're the same.
+# 
+# We must not include a rule for static libraries if he doesn't want us
+# to build any.  The library name we come up with might conflict with 
+# the name of the library he actually is building.  In fact, in that case
+# STATICLIB_SUFFIX may just be arbitrary.
+#-----------------------------------------------------------------------------
+ifeq ($(NETPBMLIBTYPE),unixstatic)
+  BUILD_STATICLIB = y
+else
+  ifeq ($(STATICLIB_TOO),y)
+    BUILD_STATICLIB = y
+  else
+    BUILD_STATICLIB = n
+  endif
+endif
+
+ifeq ($(BUILD_STATICLIB),y)
+libnetpbm.$(STATICLIBSUFFIX): $(LIBOBJECTS) $(LIBOBJECTS_X)
+	-rm -f $@
+	ar rc $@ $(LIBOBJECTS) $(LIBOBJECTS_X)
+	-$(RANLIB) $@
+endif
+
+
+# To avoid major hassles with having ppmdcfont available here, we just
+# ship a pre-made standardppmfont.c, so this rule will not normally be
+# used.
+standardppmdfont.c:standard.ppmdfont
+	ppmdcfont <$< >$@ || (rm $@ && false)
+
+# Note that we create a new compile.h only for the first make after a
+# make clean.  This is good enough.  We used to do stamp-date for
+# every build of "all" from the Netpbm top directory, but nowhere
+# else, so it was really sloppy.
+
+compile.h:
+	$(SRCDIR)/buildtools/stamp-date >$@ || rm $@
+
+$(LIBOBJECTS_X): FORCE
+	@if [ ! -d $(dir $@) ] ; then mkdir $(dir $@) ; fi
+	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
+		SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
+
+libpm.o: compile.h
+
+# Install a shared library
+#
+.PHONY: install.lib
+ifeq ($(NETPBMLIBTYPE),unixshared)
+# install a Unix-style shared library
+install.lib: $(PKGDIR)/lib $(PKGDIR)/link
+	cd $(PKGDIR)/lib ; rm -f libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ).*
+	$(INSTALL) -c -m $(INSTALL_PERM_LIBD) \
+	  libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ).$(MIN)  $(PKGDIR)/lib/
+	cd $(PKGDIR)/lib/ ; \
+          rm -f libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ); \
+          $(SYMLINK) libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ).$(MIN) $(SONAME)
+endif
+ifeq ($(NETPBMLIBTYPE),dll)
+#install a Windows DLL shared library
+#Note that unlike Unix libraries, a Windows DLL must go into a directory
+#that is in the PATH, so we use bin/ instead of lib/
+install.lib: $(PKGDIR)/bin
+	( cd $(PKGDIR)/bin ; rm -f $(NETPBMSHLIBPREFIX)netpbm$(DLLVER).dll )
+	$(INSTALL) -c $(STRIPFLAG) -m $(INSTALL_PERM_LIBD) \
+          $(NETPBMSHLIBPREFIX)netpbm$(DLLVER).dll $(PKGDIR)/bin/
+endif
+ifeq ($(NETPBMLIBTYPE),dylib)
+# install a Darwin-style shared library
+install.lib: $(PKGDIR)/lib
+	cd $(PKGDIR)/lib ; rm -f libnetpbm.*.dylib
+	$(INSTALL) -c -m $(INSTALL_PERM_LIBD) libnetpbm.$(MAJ).$(MIN).dylib \
+	   $(PKGDIR)/lib
+	cd $(PKGDIR)/lib ; \
+          rm -f libnetpbm.$(MAJ).dylib; \
+          $(SYMLINK) libnetpbm.$(MAJ).$(MIN).dylib libnetpbm.$(MAJ).dylib
+endif
+
+.PHONY: install.hdr
+install.hdr: $(INTERFACE_HEADERS:%=%_installhdr)
+# You need to install the interface header files only if you are going to
+# compile programs that use the Netpbm libraries.  Alternatively, you may
+# prefer not to "install" them, but just to access the Netpbm source
+# directory when you compile your programs.
+
+%_installhdr: $(PKGDIR)/include
+	$(INSTALL) -c -m $(INSTALL_PERM_HDR) \
+	  $(SRCDIR)/lib/$(@:%_installhdr=%) $(PKGDIR)/include/;
+
+.PHONY: install.staticlib
+install.staticlib: $(PKGDIR)/link
+	$(INSTALL) -c -m $(INSTALL_PERM_LIBS) libnetpbm.$(STATICLIBSUFFIX) \
+	  $(PKGDIR)/link
+
+# Install a shared library stub -- the ".so" file used at link time to
+# prepare a program for dynamically linking a library at run time 
+.PHONY: install.sharedlibstub
+install.sharedlibstub: $(PKGDIR)/link
+ifeq ($(NETPBMLIBTYPE),unixshared)
+# install the link-time (.so) links to the runtime libraries
+	cd $(PKGDIR)/link ; \
+          rm -f libnetpbm.$(NETPBMLIBSUFFIX); \
+          $(SYMLINK) ../lib/libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ) \
+            libnetpbm.$(NETPBMLIBSUFFIX)
+endif
+ifeq ($(NETPBMLIBTYPE),dll)
+	$(INSTALL) -c -m $(INSTALL_PERM_LIBS) libnetpbm.dll.a $(PKGDIR)/link
+endif
+ifeq ($(NETPBMLIBTYPE),dylib)
+	cd $(PKGDIR)/link/ ; \
+          rm -f libnetpbm.dylib; \
+	$(SYMLINK) ../lib/libnetpbm.$(MAJ).$(MIN).dylib libnetpbm.dylib
+endif
+
+clean: localclean
+
+.PHONY: localclean
+localclean:
+	rm -f compile.h
+
+FORCE:
diff --git a/lib/bitio.c b/lib/bitio.c
new file mode 100644
index 00000000..ca1b55f9
--- /dev/null
+++ b/lib/bitio.c
@@ -0,0 +1,207 @@
+/*\
+ * $Id: bitio.c,v 1.5 1992/11/24 19:36:46 dws Exp dws $
+ *
+ * bitio.c - bitstream I/O
+ *
+ * Works for (sizeof(unsigned long)-1)*8 bits.
+ *
+ * Copyright (C) 1992 by David W. Sanderson.
+ * 
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and
+ * that both that copyright notice and this permission notice appear
+ * in supporting documentation.  This software is provided "as is"
+ * without express or implied warranty.
+ *
+ * $Log: bitio.c,v $
+ * Revision 1.5  1992/11/24  19:36:46  dws
+ * Added copyright.
+ *
+ * Revision 1.4  1992/11/17  03:37:50  dws
+ * updated comment
+ *
+ * Revision 1.3  1992/11/10  23:15:16  dws
+ * Removed superfluous code.
+ *
+ * Revision 1.2  1992/11/10  23:11:22  dws
+ * Generalized to handle more than one bitstream at once.
+ *
+ * Revision 1.1  1992/11/10  18:33:21  dws
+ * Initial revision
+ *
+\*/
+
+#include <string.h>
+
+#include "bitio.h"
+
+struct bitstream
+{
+    FILE *
+        f;      /* bytestream */
+    unsigned long
+        bitbuf;     /* bit buffer */
+    int
+        nbitbuf;    /* number of bits in 'bitbuf' */
+    char
+        mode;
+};
+
+#define Mask(n)     ((1<<(n))-1)
+
+#define BitPut(b,ul,n)  ((b)->bitbuf = (((b)->bitbuf<<(n))  \
+                    |((ul)&Mask(n))),   \
+            (b)->nbitbuf += (n))
+
+#define BitGet(b,n) (((b)->bitbuf>>((b)->nbitbuf-=(n))) & Mask(n))
+
+/*
+ * pm_bitinit() - allocate and return a struct bitstream * for the
+ * given FILE*.
+ *
+ * mode must be one of "r" or "w", according to whether you will be
+ * reading from or writing to the struct bitstream *.
+ *
+ * Returns 0 on error.
+ */
+
+struct bitstream *
+pm_bitinit(FILE * const f, const char * const mode) {
+
+    struct bitstream * ans;
+
+    if (!f || !mode)
+        ans = NULL;
+    else if (strcmp(mode, "r") != 0 && strcmp(mode, "w") != 0)
+        ans = NULL;
+    else {
+        ans = (struct bitstream *)calloc(1, sizeof(struct bitstream));
+        if (ans != NULL) {
+            ans->f = f;
+            ans->mode = *mode;
+        }
+    }
+    return ans;
+}
+
+/*
+ * pm_bitfini() - deallocate the given struct bitstream *.
+ *
+ * You must call this after you are done with the struct bitstream *.
+ * 
+ * It may flush some bits left in the buffer.
+ *
+ * Returns the number of bytes written, -1 on error.
+ */
+
+int
+pm_bitfini(b)
+    struct bitstream *b;
+{
+    int     nbyte = 0;
+
+    if(!b)
+        return -1;
+
+    /* flush the output */
+    if(b->mode == 'w')
+    {
+        /* flush the bits */
+        if (b->nbitbuf < 0 || b->nbitbuf >= 8)
+        {
+            /* pm_bitwrite() didn't work */
+            return -1;
+        }
+
+        /*
+         * If we get to here, nbitbuf is 0..7
+         */
+        if(b->nbitbuf)
+        {
+            char    c;
+
+            BitPut(b, 0, (long)8-(b->nbitbuf));
+            c = (char) BitGet(b, (long)8);
+            if(putc(c, b->f) == EOF)
+            {
+                return -1;
+            }
+            nbyte++;
+        }
+    }
+
+    free(b);
+    return nbyte;
+}
+
+/*
+ * pm_bitread() - read the next nbits into *val from the given file.
+ * 
+ * The last pm_bitread() must be followed by a call to pm_bitfini().
+ * 
+ * Returns the number of bytes read, -1 on error.
+ */
+
+int 
+pm_bitread(b, nbits, val)
+    struct bitstream *b;
+    unsigned long   nbits;
+    unsigned long  *val;
+{
+    int     nbyte = 0;
+    int     c;
+
+    if(!b)
+        return -1;
+
+    while (b->nbitbuf < nbits)
+    {
+        if((c = getc(b->f)) == EOF)
+        {
+            return -1;
+        }
+        nbyte++;
+
+        BitPut(b, c, (long) 8);
+    }
+
+    *val = BitGet(b, nbits);
+    return nbyte;
+}
+
+/*
+ * pm_bitwrite() - write the low nbits of val to the given file.
+ * 
+ * The last pm_bitwrite() must be followed by a call to pm_bitfini().
+ * 
+ * Returns the number of bytes written, -1 on error.
+ */
+
+int
+pm_bitwrite(b, nbits, val)
+    struct bitstream *b;
+    unsigned long   nbits;
+    unsigned long   val;
+{
+    int     nbyte = 0;
+    char        c;
+
+    if(!b)
+        return -1;
+
+    BitPut(b, val, nbits);
+
+    while (b->nbitbuf >= 8)
+    {
+        c = (char) BitGet(b, (long)8);
+
+        if(putc(c, b->f) == EOF)
+        {
+            return -1;
+        }
+        nbyte++;
+    }
+
+    return nbyte;
+}
diff --git a/lib/bitio.h b/lib/bitio.h
new file mode 100644
index 00000000..15fe0e8a
--- /dev/null
+++ b/lib/bitio.h
@@ -0,0 +1,89 @@
+/*\
+ * $Id: bitio.h,v 1.4 1992/11/24 19:37:02 dws Exp dws $
+ *
+ * bitio.h - bitstream I/O
+ *
+ * Works for (sizeof(unsigned long)-1)*8 bits.
+ *
+ * Copyright (C) 1992 by David W. Sanderson.
+ * 
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and
+ * that both that copyright notice and this permission notice appear
+ * in supporting documentation.  This software is provided "as is"
+ * without express or implied warranty.
+ *
+ * $Log: bitio.h,v $
+ * Revision 1.4  1992/11/24  19:37:02  dws
+ * Added copyright
+ *
+ * Revision 1.3  1992/11/17  03:37:59  dws
+ * updated comment
+ *
+ * Revision 1.2  1992/11/10  23:10:22  dws
+ * Generalized to handle more than one bitstream at a time.
+ *
+ * Revision 1.1  1992/11/10  18:33:51  dws
+ * Initial revision
+ *
+\*/
+
+#ifndef _BITIO_H_
+#define _BITIO_H_
+
+#include "pm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+typedef struct bitstream	*BITSTREAM;
+
+struct bitstream *
+pm_bitinit(FILE * const f, const char * const mode);
+
+/*
+ * pm_bitfini() - deallocate the given BITSTREAM.
+ *
+ * You must call this after you are done with the BITSTREAM.
+ * 
+ * It may flush some bits left in the buffer.
+ *
+ * Returns the number of bytes written, -1 on error.
+ */
+
+int
+pm_bitfini(BITSTREAM b);
+
+/*
+ * pm_bitread() - read the next nbits into *val from the given file.
+ * 
+ * Returns the number of bytes read, -1 on error.
+ */
+
+int
+pm_bitread(BITSTREAM       b,
+           unsigned long   nbits,
+           unsigned long * val);
+
+/*
+ * pm_bitwrite() - write the low nbits of val to the given file.
+ * 
+ * The last pm_bitwrite() must be followed by a call to pm_bitflush().
+ * 
+ * Returns the number of bytes written, -1 on error.
+ */
+
+int
+pm_bitwrite(BITSTREAM     b,
+            unsigned long nbits,
+            unsigned long val);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _BITIO_H_ */
diff --git a/lib/colorname.c b/lib/colorname.c
new file mode 100644
index 00000000..dcda3ab8
--- /dev/null
+++ b/lib/colorname.c
@@ -0,0 +1,208 @@
+/* colorname.c - colorname routines, not dependent on Netpbm formats
+**
+** Taken from libppm4.c May 2002.
+
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include "pm_c_util.h"
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "nstring.h"
+#include "colorname.h"
+
+static int lineNo;
+
+void 
+pm_canonstr(char * const str) {
+
+    char * p;
+    for (p = str; *p; ) {
+        if (ISSPACE(*p)) {
+            strcpy(p, &(p[1]));
+        } else {
+            if (ISUPPER(*p))
+                *p = tolower(*p);
+            ++p;
+        }
+    }
+}
+
+
+
+FILE *
+pm_openColornameFile(const char * const fileName, const int must_open) {
+/*----------------------------------------------------------------------------
+   Open the colorname dictionary file.  Its file name is 'fileName', unless
+   'fileName' is NULL.  In that case, its file name is the value of the
+   environment variable whose name is RGB_ENV (e.g. "RGBDEF").  Except
+   if that environment variable is not set, it is RGB_DB1, RGB_DB2,
+   or RGB_DB3 (e.g. "/usr/lib/X11/rgb.txt"), whichever exists.
+   
+   'must_open' is a logical: we must get the file open or die.  If
+   'must_open' is true and we can't open the file (e.g. it doesn't
+   exist), exit the program with an error message.  If 'must_open' is
+   false and we can't open the file, just return a null pointer.
+-----------------------------------------------------------------------------*/
+    const char *rgbdef;
+    FILE *f;
+
+    if (fileName == NULL) {
+        if ((rgbdef = getenv(RGBENV))==NULL) {
+            /* The environment variable isn't set, so try the hardcoded
+               default color name dictionary locations.
+            */
+            if ((f = fopen(RGB_DB1, "r")) == NULL &&
+                (f = fopen(RGB_DB2, "r")) == NULL &&
+                (f = fopen(RGB_DB3, "r")) == NULL && must_open) {
+                pm_error("can't open color names dictionary file named "
+                         "%s, %s, or %s "
+                         "and Environment variable %s not set.  Set %s to "
+                         "the pathname of your rgb.txt file or don't use "
+                         "color names.", 
+                         RGB_DB1, RGB_DB2, RGB_DB3, RGBENV, RGBENV);
+            }
+        } else {            
+            /* The environment variable is set */
+            if ((f = fopen(rgbdef, "r")) == NULL && must_open)
+                pm_error("Can't open the color names dictionary file "
+                         "named %s, per the %s environment variable.  "
+                         "errno = %d (%s)",
+                         rgbdef, RGBENV, errno, strerror(errno));
+        }
+    } else {
+        f = fopen(fileName, "r");
+        if (f == NULL && must_open)
+            pm_error("Can't open the color names dictionary file '%s'.  "
+                     "errno = %d (%s)", fileName, errno, strerror(errno));
+        
+    }
+    lineNo = 0;
+    return(f);
+}
+
+
+
+struct colorfile_entry
+pm_colorget(FILE * const f) {
+/*----------------------------------------------------------------------------
+   Get next color entry from the color name dictionary file 'f'.
+
+   If eof or error, return a color entry with NULL for the color name.
+
+   Otherwise, return color name in static storage within.
+-----------------------------------------------------------------------------*/
+    char buf[200];
+    static char colorname[200];
+    bool gotOne;
+    bool eof;
+    struct colorfile_entry retval;
+    char * rc;
+    
+    gotOne = FALSE;  /* initial value */
+    eof = FALSE;
+    while (!gotOne && !eof) {
+        lineNo++;
+        rc = fgets(buf, sizeof(buf), f);
+        if (rc == NULL)
+            eof = TRUE;
+        else {
+            if (buf[0] != '#' && buf[0] != '\n' && buf[0] != '!' &&
+                buf[0] != '\0') {
+                if (sscanf(buf, "%ld %ld %ld %[^\n]", 
+                           &retval.r, &retval.g, &retval.b, colorname) 
+                    == 4 )
+                    gotOne = TRUE;
+                else {
+                    if (buf[strlen(buf)-1] == '\n')
+                        buf[strlen(buf)-1] = '\0';
+                    pm_message("can't parse color names dictionary Line %d:  "
+                               "'%s'", 
+                               lineNo, buf);
+                }
+            }
+        }
+    }
+    if (gotOne)
+        retval.colorname = colorname;
+    else
+        retval.colorname = NULL;
+    return retval;
+}
+
+
+
+void
+pm_parse_dictionary_name(char    const colorname[], 
+                         pixval  const maxval,
+                         int     const closeOk,
+                         pixel * const colorP) {
+
+    FILE* f;
+    bool gotit;
+    bool colorfileExhausted;
+    struct colorfile_entry colorfile_entry;
+    char * canoncolor;
+    pixval r,g,b;
+
+    f = pm_openColornameFile(NULL, TRUE);  /* exits if error */
+    canoncolor = strdup(colorname);
+    pm_canonstr(canoncolor);
+    gotit = FALSE;
+    colorfileExhausted = FALSE;
+    while (!gotit && !colorfileExhausted) {
+        colorfile_entry = pm_colorget(f);
+        if (colorfile_entry.colorname) {
+            pm_canonstr(colorfile_entry.colorname);
+            if (strcmp( canoncolor, colorfile_entry.colorname) == 0)
+                gotit = TRUE;
+        } else
+            colorfileExhausted = TRUE;
+    }
+    fclose(f);
+    
+    if (!gotit)
+        pm_error("unknown color '%s'", colorname);
+    
+    /* Rescale from [0..255] if necessary. */
+    if (maxval != 255) {
+        r = colorfile_entry.r * maxval / 255;
+        g = colorfile_entry.g * maxval / 255;
+        b = colorfile_entry.b * maxval / 255;
+
+        if (!closeOk) {
+            if (r * 255 / maxval != colorfile_entry.r ||
+                g * 255 / maxval != colorfile_entry.g ||
+                b * 255 / maxval != colorfile_entry.b)
+                pm_message("WARNING: color '%s' cannot be represented "
+                           "exactly with a maxval of %u.  "
+                           "Approximating as (%u,%u,%u).  "
+                           "The color dictionary uses maxval 255, so that "
+                           "maxval will always work.",
+                           colorname, maxval, r, g, b);
+        }
+    } else {
+        r = colorfile_entry.r;
+        g = colorfile_entry.g;
+        b = colorfile_entry.b;
+    }
+    free(canoncolor);
+
+    PPM_ASSIGN(*colorP, r, g, b);
+}
+
+
+
diff --git a/lib/colorname.h b/lib/colorname.h
new file mode 100644
index 00000000..d33980e4
--- /dev/null
+++ b/lib/colorname.h
@@ -0,0 +1,43 @@
+#ifndef COLORNAME_H
+#define COLORNAME_H
+
+#include <string.h>
+#include <stdio.h>
+#include "ppm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+enum colornameFormat {PAM_COLORNAME_ENGLISH = 0,
+                      PAM_COLORNAME_HEXOK   = 1};
+
+struct colorfile_entry {
+    long r, g, b;
+    char * colorname;
+};
+
+
+
+void 
+pm_canonstr(char * const str);
+
+FILE *
+pm_openColornameFile(const char * const fileName, const int must_open);
+
+struct colorfile_entry
+pm_colorget(FILE * const f);
+
+void
+pm_parse_dictionary_name(const char       colorname[], 
+                         pixval     const maxval,
+                         int        const closeOk,
+                         pixel *    const colorP);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/lib/compile.h b/lib/compile.h
new file mode 100644
index 00000000..4626f1ea
--- /dev/null
+++ b/lib/compile.h
@@ -0,0 +1,5 @@
+/* This file tells the package when it was compiled */
+/* DO NOT EDIT - THIS FILE IS MAINTAINED AUTOMATICALLY */
+/* Created by the program 'stamp-date'  */
+#define COMPILE_TIME "Thu Aug 17 03:34:22 GMT 2006"
+#define COMPILED_BY "bryanh"
diff --git a/lib/fileio.c b/lib/fileio.c
new file mode 100644
index 00000000..01243f9d
--- /dev/null
+++ b/lib/fileio.c
@@ -0,0 +1,170 @@
+/* fileio.c - routines to read elements from Netpbm image files
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <stdio.h>
+#include <limits.h>
+
+#include "pm.h"
+#include "fileio.h"
+
+char
+pm_getc(FILE * const file) {
+    int ich;
+    char ch;
+
+    ich = getc(file);
+    if (ich == EOF)
+        pm_error("EOF / read error reading a byte");
+    ch = (char) ich;
+    
+    if (ch == '#') {
+        do {
+	    ich = getc(file);
+	    if (ich == EOF)
+            pm_error("EOF / read error reading a byte");
+	    ch = (char) ich;
+	    } while (ch != '\n' && ch != '\r');
+	}
+    return ch;
+}
+
+
+
+/* This is useful for PBM files.  It used to be used for PGM and PPM files
+   too, since the sample size was always one byte.  Now, use pbm_getrawsample()
+   for PGM and PPM files.
+*/
+
+unsigned char
+pm_getrawbyte(FILE * const file) {
+    int iby;
+
+    iby = getc(file);
+    if (iby == EOF)
+        pm_error("EOF / read error reading a one-byte sample");
+    return (unsigned char) iby;
+}
+
+
+
+unsigned int
+pm_getuint(FILE * const ifP) {
+/*----------------------------------------------------------------------------
+   Read an unsigned integer in ASCII decimal from the file stream
+   represented by 'ifP' and return its value.
+
+   If there is nothing at the current position in the file stream that
+   can be interpreted as an unsigned integer, issue an error message
+   to stderr and abort the program.
+
+   If the number at the current position in the file stream is too
+   great to be represented by an 'int' (Yes, I said 'int', not
+   'unsigned int'), issue an error message to stderr and abort the
+   program.
+-----------------------------------------------------------------------------*/
+    char ch;
+    unsigned int i;
+
+    do {
+        ch = pm_getc(ifP);
+	} while (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r');
+
+    if (ch < '0' || ch > '9')
+        pm_error("junk in file where an unsigned integer should be");
+
+    i = 0;
+    do {
+        unsigned int const digitVal = ch - '0';
+
+        if (i > INT_MAX/10 - digitVal)
+            pm_error("ASCII decimal integer in file is "
+                     "too large to be processed.  ");
+        i = i * 10 + digitVal;
+        ch = pm_getc(ifP);
+    } while (ch >= '0' && ch <= '9');
+
+    return i;
+}
+
+
+
+unsigned int
+pm_getraw(FILE *       const file, 
+          unsigned int const bytes) {
+
+    unsigned int value;  /* our return value */
+
+    if (bytes == 1) {
+        /* Here's a speedup for the common 1-byte sample case: */
+        value = getc(file);
+        if (value == EOF)
+            pm_error("EOF/error reading 1 byte sample from file.");
+    } else {
+        /* This code works for bytes == 1..4 */
+        /* We could speed this up by exploiting knowledge of the format of
+           an unsigned integer (i.e. endianness).  Then we could just cast
+           the value as an array of characters instead of shifting and
+           masking.
+           */
+        int shift;
+        unsigned char inval[4];
+        int cursor;
+        int n_read;
+
+        n_read = fread(inval, bytes, 1, file);
+        if (n_read < 1) 
+            pm_error("EOF/error reading %d byte sample from file.", bytes);
+        value = 0;  /* initial value */
+        cursor = 0;
+        for (shift = (bytes-1)*8; shift >= 0; shift-=8) 
+            value += inval[cursor++] << shift;
+    }
+    return(value);
+}
+
+
+
+void
+pm_putraw(FILE *       const file, 
+          unsigned int const value, 
+          unsigned int const bytes) {
+
+    if (bytes == 1) {
+        /* Here's a speedup for the common 1-byte sample case: */
+        int rc;
+        rc = fputc(value, file);
+        if (rc == EOF)
+            pm_error("Error writing 1 byte sample to file.");
+    } else {
+        /* This code works for bytes == 1..4 */
+        /* We could speed this up by exploiting knowledge of the format of
+           an unsigned integer (i.e. endianness).  Then we could just cast
+           the value as an array of characters instead of shifting and
+           masking.
+           */
+        int shift;
+        unsigned char outval[4];
+        int cursor;
+        int n_written;
+
+        cursor = 0;
+        for (shift = (bytes-1)*8; shift >= 0; shift-=8) {
+            outval[cursor++] = (value >> shift) & 0xFF;
+        }
+        n_written = fwrite(&outval, bytes, 1, file);
+        if (n_written == 0) 
+            pm_error("Error writing %d byte sample to file.", bytes);
+    }
+}
+
+
+
diff --git a/lib/fileio.h b/lib/fileio.h
new file mode 100644
index 00000000..158da10a
--- /dev/null
+++ b/lib/fileio.h
@@ -0,0 +1,22 @@
+#ifndef _NETPBM_FILEIO_H_
+#define _NETPBM_FILEIO_H_
+
+char
+pm_getc(FILE * const file);
+
+unsigned char
+pm_getrawbyte(FILE * const file);
+
+unsigned int
+pm_getuint(FILE * const file);
+
+unsigned int
+pm_getraw(FILE *       const file, 
+          unsigned int const bytes);
+
+void
+pm_putraw(FILE *       const file, 
+          unsigned int const value, 
+          unsigned int const bytes);
+
+#endif
diff --git a/lib/libpam.c b/lib/libpam.c
new file mode 100644
index 00000000..bbfa7e32
--- /dev/null
+++ b/lib/libpam.c
@@ -0,0 +1,1098 @@
+/*----------------------------------------------------------------------------
+                                  libpam.c
+------------------------------------------------------------------------------
+   These are the library functions, which belong in the libnetpbm library,
+   that deal with the PAM (Portable Arbitrary Format) image format.
+-----------------------------------------------------------------------------*/
+
+/* See libpm.c for the complicated explanation of this 32/64 bit file
+   offset stuff.
+*/
+#define _FILE_OFFSET_BITS 64
+#define _LARGE_FILES  
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <string.h>
+#include <limits.h>
+#include <assert.h>
+#include <stdio.h>
+
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "pam.h"
+#include "ppm.h"
+#include "libpbm.h"
+#include "libpgm.h"
+#include "libppm.h"
+#include "colorname.h"
+#include "fileio.h"
+
+#include "libpam.h"
+
+
+
+static unsigned int
+allocationDepth(const struct pam * const pamP) {
+
+    unsigned int retval;
+
+    if (pamP->len >= PAM_STRUCT_SIZE(allocation_depth)) {
+        if (pamP->allocation_depth == 0)
+            retval = pamP->depth;
+        else {
+            if (pamP->depth > pamP->allocation_depth)
+                pm_error("'allocationDepth' (%u) is smaller than 'depth' (%u)",
+                         pamP->allocation_depth, pamP->depth);
+            retval = pamP->allocation_depth;
+        }
+    } else
+        retval = pamP->depth;
+    return retval;
+}
+
+
+
+static const char **
+pamCommentP(const struct pam * const pamP) {
+
+    const char ** retval;
+
+    if (pamP->len >= PAM_STRUCT_SIZE(comment_p))
+        retval = pamP->comment_p;
+    else
+        retval = NULL;
+
+    return retval;
+}
+
+
+static void
+validateComputableSize(struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   Validate that the dimensions of the image are such that it can be
+   processed in typical ways on this machine without worrying about
+   overflows.  Note that in C, arithmetic is always modulus arithmetic,
+   so if your values are too big, the result is not what you expect.
+   That failed expectation can be disastrous if you use it to allocate
+   memory.
+
+   It is very normal to allocate space for a tuplerow, so we make sure
+   the size of a tuple row, in bytes, can be represented by an 'int'.
+
+   Another common operation is adding 1 or 2 to the highest row, column,
+   or plane number in the image, so we make sure that's possible.
+-----------------------------------------------------------------------------*/
+    unsigned int const depth = allocationDepth(pamP);
+
+    if (depth > INT_MAX/sizeof(sample))
+        pm_error("image depth (%u) too large to be processed", depth);
+    else if (depth * sizeof(sample) > INT_MAX/pamP->width)
+        pm_error("image width and depth (%u, %u) too large "
+                 "to be processed.", pamP->width, depth);
+    else if (pamP->width * (depth * sizeof(sample)) >
+             INT_MAX - depth * sizeof(tuple *))
+        pm_error("image width and depth (%u, %u) too large "
+                 "to be processed.", pamP->width, depth);
+    
+    if (depth > INT_MAX - 2)
+        pm_error("image depth (%u) too large to be processed", depth);
+    if (pamP->width > INT_MAX - 2)
+        pm_error("image width (%u) too large to be processed", pamP->width);
+    if (pamP->height > INT_MAX - 2)
+        pm_error("image height (%u) too large to be processed", pamP->height);
+}
+
+
+
+tuple
+pnm_allocpamtuple(const struct pam * const pamP) {
+
+    tuple retval;
+
+    retval = malloc(allocationDepth(pamP) * sizeof(retval[0]));
+
+    if (retval == NULL)
+        pm_error("Out of memory allocating %u-plane tuple", 
+                 allocationDepth(pamP));
+
+    return retval;
+}
+
+
+
+int
+pnm_tupleequal(const struct pam * const pamP, 
+               tuple              const comparand, 
+               tuple              const comparator) {
+
+    unsigned int plane;
+    bool equal;
+
+    equal = TRUE;  /* initial value */
+    for (plane = 0; plane < pamP->depth; ++plane) 
+        if (comparand[plane] != comparator[plane])
+            equal = FALSE;
+
+    return equal;
+}
+
+
+
+
+void
+pnm_assigntuple(const struct pam * const pamP,
+                tuple              const dest,
+                tuple              const source) {
+
+    unsigned int plane;
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        dest[plane] = source[plane];
+    }
+}
+
+
+
+static void
+scaleTuple(const struct pam * const pamP,
+           tuple              const dest,
+           tuple              const source, 
+           sample             const newmaxval) {
+
+    unsigned int plane;
+    for (plane = 0; plane < pamP->depth; ++plane) 
+        dest[plane] = pnm_scalesample(source[plane], pamP->maxval, newmaxval);
+}
+
+
+
+void
+pnm_scaletuple(const struct pam * const pamP,
+               tuple              const dest,
+               tuple              const source, 
+               sample             const newmaxval) {
+
+    scaleTuple(pamP, dest, source, newmaxval);
+}
+
+
+
+void
+pnm_createBlackTuple(const struct pam * const pamP, 
+                     tuple *            const blackTupleP) {
+/*----------------------------------------------------------------------------
+   Create a "black" tuple.  By that we mean a tuple all of whose elements
+   are zero.  If it's an RGB, grayscale, or b&w pixel, that means it's black.
+-----------------------------------------------------------------------------*/
+    *blackTupleP = pnm_allocpamtuple(pamP);
+    if (pamP->format == PAM_FORMAT) {
+        /* In this format, we don't know the meaning of "black", so we
+           just punt.
+           */
+        int i;
+        for (i = 0; i < pamP->depth; i++) 
+            (*blackTupleP)[i] = 0;
+    } else {
+        xel black_xel;
+        black_xel = pnm_blackxel(pamP->maxval, pamP->format);
+        (*blackTupleP)[0] = PPM_GETR(black_xel);
+        (*blackTupleP)[1] = PPM_GETG(black_xel);
+        (*blackTupleP)[2] = PPM_GETB(black_xel);
+    }
+}
+
+
+
+void
+createBlackTuple(const struct pam * const pamP, 
+                 tuple *            const blackTupleP) {
+
+/* This is poorly named, because it lacks the "pnm" prefix.  But for some
+   reason, this is how we originally named this.  So to maintain backward
+   compatibility with binaries that refer to "createBlackTuple", we define
+   this.  The preferred name, pnm_createBlackTuple() was new in Netpbm 10.20,
+   January 2004.  We should eventually retire createBlackTuple().
+*/
+    pnm_createBlackTuple(pamP, blackTupleP);
+}
+
+
+
+static tuple *
+allocPamRow(const struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   We assume that the dimensions of the image are such that arithmetic
+   overflow will not occur in our calculations.  NOTE: pnm_readpaminit()
+   ensures this assumption is valid.
+-----------------------------------------------------------------------------*/
+    /* The tuple row data structure starts with 'width' pointers to
+       the tuples, immediately followed by the 'width' tuples
+       themselves.  Each tuple consists of 'depth' samples.  
+    */
+
+    int const bytesPerTuple = allocationDepth(pamP) * sizeof(sample);
+    tuple * tuplerow;
+
+    tuplerow = malloc(pamP->width * (sizeof(tuple *) + bytesPerTuple));
+                      
+    if (tuplerow != NULL) {
+        /* Now we initialize the pointers to the individual tuples
+           to make this a regulation C two dimensional array.  
+        */
+        char * p;
+        unsigned int col;
+        
+        p = (char*) (tuplerow + pamP->width);  /* location of Tuple 0 */
+        for (col = 0; col < pamP->width; ++col) {
+                tuplerow[col] = (tuple) p;
+                p += bytesPerTuple;
+        }
+    }
+    return tuplerow;
+}
+
+
+
+tuple *
+pnm_allocpamrow(const struct pam * const pamP) {
+
+
+    tuple * const tuplerow = allocPamRow(pamP);
+
+    if (tuplerow == NULL)
+        pm_error("Out of memory allocating space for a tuple row of\n"
+                 "%d tuples by %d samples per tuple by %d bytes per sample.",
+                 pamP->width, allocationDepth(pamP), sizeof(sample));
+
+    return tuplerow;
+}
+
+
+
+static unsigned int 
+rowimagesize(const struct pam * const pamP) {
+
+    /* If repeatedly calculating this turns out to be a significant
+       performance problem, we could keep this in struct pam like
+       bytes_per_sample.
+    */
+
+    if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE)
+        return pbm_packed_bytes(pamP->width);
+    else 
+        return (pamP->width * pamP->bytes_per_sample * pamP->depth);
+}
+
+
+
+unsigned char *
+pnm_allocrowimage(const struct pam * const pamP) {
+
+    unsigned int const rowsize = rowimagesize(pamP);
+    unsigned int const overrunSpaceNeeded = 8;
+        /* This is the number of extra bytes of space libnetpbm needs to have
+           at the end of the buffer so it can use fast, lazy algorithms.
+        */
+    unsigned int const size = rowsize + overrunSpaceNeeded;
+
+    unsigned char * retval;
+
+    retval = malloc(size);
+
+    if (retval == NULL)
+        pm_error("Unable to allocate %u bytes for a row image buffer",
+                 size);
+
+    return retval;
+}
+
+
+
+void
+pnm_freerowimage(unsigned char * const rowimage) {
+    free(rowimage);
+}
+
+
+
+void 
+pnm_scaletuplerow(const struct pam * const pamP,
+                  tuple *            const destRow,
+                  tuple *            const sourceRow,
+                  sample             const newMaxval) {
+
+    if (pamP->maxval == newMaxval) {
+        /* Fast path for common case: no scaling needed */
+        if (destRow != sourceRow) {
+            /* Fast path for common case: it's already what it needs to be */
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col)
+                pnm_assigntuple(pamP, destRow[col], sourceRow[col]);
+        }
+    } else {        
+        unsigned int col;
+        for (col = 0; col < pamP->width; ++col) 
+            scaleTuple(pamP, destRow[col], sourceRow[col], newMaxval);
+    }
+}
+
+ 
+
+tuple **
+pnm_allocpamarray(const struct pam * const pamP) {
+    
+    tuple **tuplearray;
+
+    /* If the speed of this is ever an issue, it might be sped up a little
+       by allocating one large chunk.
+    */
+    
+    MALLOCARRAY(tuplearray, pamP->height);
+    if (tuplearray == NULL) 
+        pm_error("Out of memory allocating the row pointer section of "
+                 "a %u row array", pamP->height);
+    else {
+        int row;
+        bool outOfMemory;
+
+        outOfMemory = FALSE;
+        for (row = 0; row < pamP->height && !outOfMemory; ++row) {
+            tuplearray[row] = allocPamRow(pamP);
+            if (tuplearray[row] == NULL) {
+                unsigned int freerow;
+                outOfMemory = TRUE;
+                
+                for (freerow = 0; freerow < row; ++freerow)
+                    pnm_freepamrow(tuplearray[row]);
+            }
+        }
+        if (outOfMemory) {
+            free(tuplearray);
+            
+            pm_error("Out of memory allocating the %u rows %u columns wide by "
+                     "%u planes deep", pamP->height, pamP->width,
+                     allocationDepth(pamP));
+        }
+    }
+    return tuplearray;
+}
+
+
+
+void
+pnm_freepamarray(tuple ** const tuplearray, const struct pam * const pamP) {
+
+    int row;
+    for (row = 0; row < pamP->height; row++)
+        pnm_freepamrow(tuplearray[row]);
+
+    free(tuplearray);
+}
+
+
+
+void 
+pnm_setminallocationdepth(struct pam * const pamP,
+                          unsigned int const allocationDepth) {
+
+    if (pamP->len < PAM_STRUCT_SIZE(allocation_depth))
+        pm_error("Can't set minimum allocation depth in pam structure, "
+                 "because the structure is only %u bytes long, and to "
+                 "have an allocation_depth field, it must bea at least %u",
+                 pamP->len, PAM_STRUCT_SIZE(allocation_depth));
+
+    pamP->allocation_depth = MAX(allocationDepth, pamP->depth);
+        
+    validateComputableSize(pamP);
+}
+
+
+
+void
+pnm_setpamrow(const struct pam * const pamP,
+              tuple *            const tuplerow, 
+              sample             const value) {
+
+    int col;
+    for (col = 0; col < pamP->width; ++col) {
+        int plane;
+        for (plane = 0; plane < pamP->depth; ++plane) 
+            tuplerow[col][plane] = value;
+    }
+}
+
+
+
+
+#define MAX_LABEL_LENGTH 8
+#define MAX_VALUE_LENGTH 255
+
+static void
+parse_header_line(const char buffer[], char label[MAX_LABEL_LENGTH+1], 
+                  char value[MAX_VALUE_LENGTH+1]) {
+    
+    int buffer_curs;
+
+    buffer_curs = 0;
+    /* Skip initial white space */
+    while (ISSPACE(buffer[buffer_curs])) buffer_curs++;
+
+    {
+        /* Read off label, put as much as will fit into label[] */
+        int label_curs;
+        label_curs = 0;
+        while (!ISSPACE(buffer[buffer_curs]) && buffer[buffer_curs] != '\0') {
+            if (label_curs < MAX_LABEL_LENGTH) 
+                label[label_curs++] = buffer[buffer_curs];
+            buffer_curs++;
+        }
+        label[label_curs] = '\0';  /* null terminate it */
+    }    
+
+    /* Skip white space between label and value */
+    while (ISSPACE(buffer[buffer_curs])) buffer_curs++;
+
+    /* copy value into value[] */
+    strncpy(value, buffer+buffer_curs, MAX_VALUE_LENGTH+1);
+
+    {
+        /* Remove trailing white space from value[] */
+        int value_curs;
+        value_curs = strlen(value)-1;
+        while (value_curs >= 0 && ISSPACE(value[value_curs])) 
+            value[value_curs--] = '\0';
+    }
+}
+
+
+
+struct headerSeen {
+/*----------------------------------------------------------------------------
+   This structure tells what we've seen so far in our progress through the
+   PAM header
+-----------------------------------------------------------------------------*/
+    bool width;
+    bool height;
+    bool depth;
+    bool maxval;
+    bool endhdr;
+};
+
+
+
+static void
+process_header_line(char                const buffer[],
+                    struct pam *        const pamP,
+                    struct headerSeen * const headerSeenP) {
+/*----------------------------------------------------------------------------
+   Process a line from the PAM header.  The line is buffer[], and it is not
+   a comment or blank.
+
+   Put the value that the line defines in *pamP (unless it's ENDHDR).
+
+   Update *headerSeenP with whatever we see.
+-----------------------------------------------------------------------------*/
+    char label[MAX_LABEL_LENGTH+1];
+    char value[MAX_VALUE_LENGTH+1];
+
+    parse_header_line(buffer, label, value);
+
+    if (strcmp(label, "ENDHDR") == 0)
+        headerSeenP->endhdr = TRUE;
+    else {
+        if (strcmp(label, "WIDTH") == 0 ||
+            strcmp(label, "HEIGHT") == 0 ||
+            strcmp(label, "DEPTH") == 0 ||
+            strcmp(label, "MAXVAL") == 0) {
+
+            if (strlen(value) == 0)
+                pm_error("Missing value for %s in PAM file header.",
+                         label);
+            else {
+                char *endptr;
+                long int numeric_value;
+                errno = 0;  /* Clear errno so we can detect strtol() failure */
+                numeric_value = strtol(value, &endptr, 10);
+                if (errno != 0)
+                    pm_error("Too-large value for %s in "
+                             "PAM file header: '%s'", label, value);
+                if (*endptr != '\0') 
+                    pm_error("Non-numeric value for %s in "
+                             "PAM file header: '%s'", label, value);
+                else if (numeric_value < 0) 
+                    pm_error("Negative value for %s in "
+                             "PAM file header: '%s'", label, value);
+            }
+        }
+    
+        if (strcmp(label, "WIDTH") == 0) {
+            pamP->width = atoi(value);
+            headerSeenP->width = TRUE;
+        } else if (strcmp(label, "HEIGHT") == 0) {
+            pamP->height = atoi(value);
+            headerSeenP->height = TRUE;
+        } else if (strcmp(label, "DEPTH") == 0) {
+            pamP->depth = atoi(value);
+            headerSeenP->depth = TRUE;
+        } else if (strcmp(label, "MAXVAL") == 0) {
+            pamP->maxval = atoi(value);
+            headerSeenP->maxval = TRUE;
+        } else if (strcmp(label, "TUPLTYPE") == 0) {
+            int len = strlen(pamP->tuple_type);
+            if (len + strlen(value) + 1 > sizeof(pamP->tuple_type)-1)
+                pm_error("TUPLTYPE value too long in PAM header");
+            if (len == 0)
+                strcpy(pamP->tuple_type, value);
+            else {
+                strcat(pamP->tuple_type, "\n");
+                strcat(pamP->tuple_type, value);
+            }
+            pamP->tuple_type[sizeof(pamP->tuple_type)-1] = '\0';
+        } else 
+            pm_error("Unrecognized header line: '%s'.  "
+                     "Possible missing ENDHDR line?", label);
+    }
+}
+
+
+
+static void
+appendComment(char **      const commentsP,
+              const char * const commentHeader) {
+
+    const char * const commentLine = &commentHeader[1];
+
+    size_t commentLen = strlen(*commentsP) + strlen(commentLine) + 1;
+
+    assert(commentHeader[0] == '#');
+
+    REALLOCARRAY(*commentsP, commentLen);
+
+    if (*commentsP == NULL)
+        pm_error("Couldn't get storage for %u characters of comments from "
+                 "the PAM header", commentLen);
+
+    strcat(*commentsP, commentLine);
+}
+
+
+
+static void
+disposeOfComments(const struct pam * const pamP,
+                  const char *       const comments) {
+
+    const char ** const retP = pamCommentP(pamP);
+
+    if (retP)
+        *retP = comments;
+    else
+        strfree(comments);
+}
+
+
+
+static void
+readpaminitrest(struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   Read the rest of the PAM header (after the first line -- the magic
+   number line).  Fill in all the information in *pamP.
+-----------------------------------------------------------------------------*/
+    struct headerSeen headerSeen;
+    char * comments;
+
+    headerSeen.width  = FALSE;
+    headerSeen.height = FALSE;
+    headerSeen.depth  = FALSE;
+    headerSeen.maxval = FALSE;
+    headerSeen.endhdr = FALSE;
+
+    pamP->tuple_type[0] = '\0';
+
+    comments = strdup("");
+
+    { 
+        int c;
+        /* Read off rest of 1st line -- probably just the newline after the 
+           magic number 
+        */
+        while ((c = getc(pamP->file)) != -1 && c != '\n');
+    }    
+
+    while (!headerSeen.endhdr) {
+        char buffer[256];
+        char * rc;
+        rc = fgets(buffer, sizeof(buffer), pamP->file);
+        if (rc == NULL)
+            pm_error("EOF or error reading file while trying to read the "
+                     "PAM header");
+        else {
+            buffer[256-1-1] = '\n';  /* In case fgets() truncated */
+            if (buffer[0] == '#')
+                appendComment(&comments, buffer);
+            else if (stripeq(buffer, ""));
+                /* Ignore it; it's a blank line */
+            else 
+                process_header_line(buffer, pamP, &headerSeen);
+        }
+    }
+
+    disposeOfComments(pamP, comments);
+
+    if (!headerSeen.height)
+        pm_error("No HEIGHT header line in PAM header");
+    if (!headerSeen.width)
+        pm_error("No WIDTH header line in PAM header");
+    if (!headerSeen.depth)
+        pm_error("No DEPTH header line in PAM header");
+    if (!headerSeen.maxval)
+        pm_error("No MAXVAL header line in PAM header");
+
+    if (pamP->height == 0) 
+        pm_error("HEIGHT value is zero in PAM header");
+    if (pamP->width == 0) 
+        pm_error("WIDTH value is zero in PAM header");
+    if (pamP->depth == 0) 
+        pm_error("DEPTH value is zero in PAM header");
+    if (pamP->maxval == 0) 
+        pm_error("MAXVAL value is zero in PAM header");
+    if (pamP->maxval > PAM_OVERALL_MAXVAL)
+        pm_error("MAXVAL value (%lu) in PAM header is greater than %u",
+                 pamP->maxval, PAM_OVERALL_MAXVAL);
+}
+
+
+
+void
+pnm_readpaminitrestaspnm(FILE * const fileP, 
+                         int *  const colsP, 
+                         int *  const rowsP, 
+                         gray * const maxvalP,
+                         int *  const formatP) {
+/*----------------------------------------------------------------------------
+   Read the rest of the PAM header (after the first line) and return
+   information as if it were PPM or PGM.
+
+   Die if it isn't a PAM of the sort we can treat as PPM or PGM.
+-----------------------------------------------------------------------------*/
+    struct pam pam;
+
+    pam.size        = PAM_STRUCT_SIZE(tuple_type);
+    pam.file        = fileP;
+    pam.len         = sizeof(struct pam);
+    pam.format      = PAM_FORMAT;
+
+    readpaminitrest(&pam);
+
+
+    /* A PAM raster of depth 1 is identical to a PGM raster.  A PAM
+       raster of depth 3 is identical to PPM raster.  So
+       ppm_readppmrow() will be able to read the PAM raster as long as
+       the format it thinks it is (PGM or PPM) corresponds to the PAM
+       depth.  Similar for pgm_readpgmrow().
+    */
+    switch (pam.depth) {
+    case 3:
+        *formatP = RPPM_FORMAT;
+        break;
+    case 1:
+        *formatP = RPGM_FORMAT;
+        break;
+    default: {
+        pm_error("Cannot treat PAM image as PPM or PGM, "
+                 "because its depth (%u) "
+                 "is not 1 or 3.", pam.depth);
+    }
+    }
+
+    *colsP   = pam.width;
+    *rowsP   = pam.height;
+    *maxvalP = (gray)pam.maxval;
+}
+
+
+                
+unsigned int
+pnm_bytespersample(sample const maxval) {
+
+    assert(sizeof(maxval) * 8 <= 32);
+
+    if      (maxval >>  8 == 0) return 1;
+    else if (maxval >> 16 == 0) return 2;
+    else if (maxval >> 24 == 0) return 3;
+    else                        return 4;
+}
+
+
+
+void 
+pnm_readpaminit(FILE *       const file, 
+                struct pam * const pamP, 
+                int          const size) {
+
+    if (size < PAM_STRUCT_SIZE(tuple_type)) 
+        pm_error("pam object passed to pnm_readpaminit() is too small.  "
+                 "It must be large\n"
+                 "enough to hold at least up to the "
+                 "'tuple_type' member, but according\n"
+                 "to the 'size' argument, it is only %d bytes long.", 
+                 size);
+
+    pamP->size = size;
+    pamP->file = file;
+    pamP->len = MIN(pamP->size, sizeof(struct pam));
+
+    if (size >= PAM_STRUCT_SIZE(allocation_depth))
+        pamP->allocation_depth = 0;
+
+    /* Get magic number. */
+    pamP->format = pm_readmagicnumber(file);
+
+    switch (PAM_FORMAT_TYPE(pamP->format)) {
+    case PAM_TYPE: 
+        readpaminitrest(pamP);
+        break;
+    case PPM_TYPE: {
+        pixval maxval;
+        ppm_readppminitrest(pamP->file, &pamP->width, &pamP->height, &maxval);
+        pamP->maxval = (sample) maxval;
+        pamP->depth = 3;
+        strcpy(pamP->tuple_type, PAM_PPM_TUPLETYPE);
+        if (pamCommentP(pamP))
+            *pamP->comment_p = strdup("");
+    } break;
+
+    case PGM_TYPE: {
+        gray maxval;
+        pgm_readpgminitrest(pamP->file, &pamP->width, &pamP->height, &maxval);
+        pamP->maxval = (sample) maxval;
+        pamP->depth = 1;
+        strcpy(pamP->tuple_type, PAM_PGM_TUPLETYPE);
+        if (pamCommentP(pamP))
+            *pamP->comment_p = strdup("");
+    } break;
+
+    case PBM_TYPE:
+        pbm_readpbminitrest(pamP->file, &pamP->width,&pamP->height);
+        pamP->maxval = (sample) 1;
+        pamP->depth = 1;
+        strcpy(pamP->tuple_type, PAM_PBM_TUPLETYPE);
+        if (pamCommentP(pamP))
+            *pamP->comment_p = strdup("");
+        break;
+        
+    default:
+        pm_error("bad magic number - not a PAM, PPM, PGM, or PBM file");
+    }
+    
+    pamP->bytes_per_sample = pnm_bytespersample(pamP->maxval);
+    pamP->plainformat = FALSE;
+        /* See below for complex explanation of why this is FALSE. */
+
+    validateComputableSize(pamP);
+}
+
+
+
+static void
+writeComments(const struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   Write comments for a PAM header, insofar as *pamP specifies comments.
+-----------------------------------------------------------------------------*/
+    const char ** const commentP = pamCommentP(pamP);
+
+    if (commentP) {
+        const char * const comment = *commentP;
+
+        const char * p;
+        bool startOfLine;
+
+        for (p = &comment[0], startOfLine = TRUE; *p; ++p) {
+            if (startOfLine)
+                fputc('#', pamP->file);
+                
+            fputc(*p, pamP->file);
+
+            if (*p == '\n')
+                startOfLine = TRUE;
+            else
+                startOfLine = FALSE;
+        }
+        if (!startOfLine)
+            fputc('\n', pamP->file);
+    }
+}
+
+
+
+/* About the 'plainformat' member on image input operations:
+
+   'plainformat' is meaningless when reading an image, but we always
+   set it FALSE anyway.
+
+   That's because it is common for a program to copy a pam structure
+   used for input as a pam structure for output, and just update the
+   few fields it cares about -- mainly 'file'.  We want a program like
+   that to write a raw format image, and 'plainformat' in an output
+   pam structure is what determines whether it is raw or plain.  So we
+   set it false here so that it is false in the copied output pam
+   structure.
+   
+   Before 10.32, we set 'plainformat' according to the
+   plainness of the input image, and thought it was a good
+   idea for a program that reads a plain format input image to
+   write a plain format output image.  But that is not what
+   the older libnetpbm facilities (e.g. pnm_writepnm()) do, so
+   for compatibility, the pam facility shouldn't either.  This
+   came to light as we converted programs from the pnm/pbm/ppm
+   facilities to pam.
+*/
+
+void 
+pnm_writepaminit(struct pam * const pamP) {
+
+    const char * tupleType;
+
+    if (pamP->size < pamP->len)
+        pm_error("pam object passed to pnm_writepaminit() is smaller "
+                 "(%d bytes, according to its 'size' element) "
+                 "than the amount of data in it "
+                 "(%d bytes, according to its 'len' element).",
+                 pamP->size, pamP->len);
+
+    if (pamP->len < PAM_STRUCT_SIZE(bytes_per_sample))
+        pm_error("pam object passed to pnm_writepaminit() is too small.  "
+                 "It must be large\n"
+                 "enough to hold at least up through the "
+                 "'bytes_per_sample' member, but according\n"
+                 "to its 'len' member, it is only %d bytes long.", 
+                 pamP->len);
+
+    if (pamP->maxval > PAM_OVERALL_MAXVAL)
+        pm_error("maxval (%lu) passed to pnm_writepaminit() "
+                 "is greater than %u", pamP->maxval, PAM_OVERALL_MAXVAL);
+
+    if (pamP->len < PAM_STRUCT_SIZE(tuple_type))
+        tupleType = "";
+    else
+        tupleType = pamP->tuple_type;
+
+    pamP->bytes_per_sample = pnm_bytespersample(pamP->maxval);
+    
+    switch (PAM_FORMAT_TYPE(pamP->format)) {
+    case PAM_TYPE:
+        if (pm_plain_output)
+            pm_error("There is no plain version of PAM.  -plain option "
+                     "is not allowed");
+        fprintf(pamP->file, "P7\n");
+        writeComments(pamP);
+        fprintf(pamP->file, "WIDTH %u\n",   (unsigned)pamP->width);
+        fprintf(pamP->file, "HEIGHT %u\n",  (unsigned)pamP->height);
+        fprintf(pamP->file, "DEPTH %u\n",   pamP->depth);
+        fprintf(pamP->file, "MAXVAL %lu\n", pamP->maxval);
+        if (!stripeq(tupleType, ""))
+            fprintf(pamP->file, "TUPLTYPE %s\n", pamP->tuple_type);
+        fprintf(pamP->file, "ENDHDR\n");
+        break;
+    case PPM_TYPE:
+        /* The depth must be exact, because pnm_writepamrow() is controlled
+           by it, without regard to format.
+        */
+        if (pamP->depth != 3)
+            pm_error("pnm_writepaminit() got PPM format, but depth = %d "
+                     "instead of 3, as required for PPM.", 
+                     pamP->depth);
+        if (pamP->maxval > PPM_OVERALLMAXVAL) 
+            pm_error("pnm_writepaminit() got PPM format, but maxval = %ld, "
+                     "which exceeds the maximum allowed for PPM: %d", 
+                     pamP->maxval, PPM_OVERALLMAXVAL);
+        ppm_writeppminit(pamP->file, pamP->width, pamP->height, 
+                         (pixval) pamP->maxval, pamP->plainformat);
+        break;
+
+    case PGM_TYPE:
+        if (pamP->depth != 1)
+            pm_error("pnm_writepaminit() got PGM format, but depth = %d "
+                     "instead of 1, as required for PGM.",
+                     pamP->depth);
+        if (pamP->maxval > PGM_OVERALLMAXVAL)
+            pm_error("pnm_writepaminit() got PGM format, but maxval = %ld, "
+                     "which exceeds the maximum allowed for PGM: %d", 
+                     pamP->maxval, PGM_OVERALLMAXVAL);
+        pgm_writepgminit(pamP->file, pamP->width, pamP->height, 
+                         (gray) pamP->maxval, pamP->plainformat);
+        break;
+
+    case PBM_TYPE:
+        if (pamP->depth != 1)
+            pm_error("pnm_writepaminit() got PBM format, but depth = %d "
+                     "instead of 1, as required for PBM.",
+                     pamP->depth);
+        if (pamP->maxval != 1) 
+            pm_error("pnm_writepaminit() got PBM format, but maxval = %ld "
+                     "instead of 1, as required for PBM.", pamP->maxval);
+        pbm_writepbminit(pamP->file, pamP->width, pamP->height, 
+                         pamP->plainformat);
+        break;
+
+    default:
+        pm_error("Invalid format passed to pnm_writepaminit(): %d",
+                 pamP->format);
+    }
+}
+
+
+
+void
+pnm_checkpam(const struct pam *   const pamP, 
+             enum pm_check_type   const checkType, 
+             enum pm_check_code * const retvalP) {
+
+    if (checkType != PM_CHECK_BASIC) {
+        if (retvalP) *retvalP = PM_CHECK_UNKNOWN_TYPE;
+    } else switch (PAM_FORMAT_TYPE(pamP->format)) {
+    case PAM_TYPE: {
+        pm_filepos const need_raster_size = 
+            pamP->width * pamP->height * pamP->depth * pamP->bytes_per_sample;
+        pm_check(pamP->file, checkType, need_raster_size, retvalP);
+    }
+        break;
+    case PPM_TYPE:
+        pgm_check(pamP->file, checkType, pamP->format, 
+                  pamP->width, pamP->height, pamP->maxval, retvalP);
+        break;
+    case PGM_TYPE:
+        pgm_check(pamP->file, checkType, pamP->format, 
+                  pamP->width, pamP->height, pamP->maxval, retvalP);
+        break;
+    case PBM_TYPE:
+        pbm_check(pamP->file, checkType, pamP->format, 
+                  pamP->width, pamP->height, retvalP);
+        break;
+    default:
+        if (retvalP) *retvalP = PM_CHECK_UNCHECKABLE;
+    }
+}
+
+
+
+void 
+pnm_maketuplergb(const struct pam * const pamP,
+                 tuple              const tuple) {
+
+    if (allocationDepth(pamP) < 3)
+        pm_error("allocation depth %u passed to pnm_maketuplergb().  "
+                 "Must be at least 3.", allocationDepth(pamP));
+
+    if (pamP->depth < 3)
+        tuple[2] = tuple[1] = tuple[0];
+}
+
+
+
+void 
+pnm_makerowrgb(const struct pam * const pamP,
+               tuple *            const tuplerow) {
+    
+    if (pamP->depth < 3) {
+        unsigned int col;
+
+        if (allocationDepth(pamP) < 3)
+            pm_error("allocation depth %u passed to pnm_makerowrgb().  "
+                     "Must be at least 3.", allocationDepth(pamP));
+        
+        if (strncmp(pamP->tuple_type, "RGB", 3) != 0) {
+            for (col = 0; col < pamP->width; ++col) {
+                tuple const thisTuple = tuplerow[col];
+                thisTuple[2] = thisTuple[1] = thisTuple[0];
+            }
+        }
+    }
+}
+
+
+
+void 
+pnm_makearrayrgb(const struct pam * const pamP,
+                 tuple **           const tuples) {
+
+    if (pamP->depth < 3) {
+        unsigned int row;
+        if (allocationDepth(pamP) < 3)
+            pm_error("allocation depth %u passed to pnm_makearrayrgb().  "
+                     "Must be at least 3.", allocationDepth(pamP));
+        
+        for (row = 0; row < pamP->height; ++row) {
+            tuple * const tuplerow = tuples[row];
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col) {
+                tuple const thisTuple = tuplerow[col];
+                thisTuple[2] = thisTuple[1] = thisTuple[0];
+            }
+        }
+    }
+}
+
+
+
+void
+pnm_getopacity(const struct pam * const pamP,
+               bool *             const haveOpacityP,
+               unsigned int *     const opacityPlaneP) {
+
+    /* Design note; If use of this information proliferates, we should
+       probably add it to struct pam as convenience values analogous to
+       bytes_per_sample.
+    */
+    if (strcmp(pamP->tuple_type, "RGB_ALPHA") == 0) {
+        *haveOpacityP = TRUE;
+        *opacityPlaneP = PAM_TRN_PLANE;
+    } else if (strcmp(pamP->tuple_type, "GRAYSCALE_ALPHA") == 0) {
+        *haveOpacityP = TRUE;
+        *opacityPlaneP = PAM_GRAY_TRN_PLANE;
+    } else
+        *haveOpacityP = FALSE;
+}
+
+
+
+/*=============================================================================
+   pm_system() Standard Input feeder and Standard Output accepter functions.   
+=============================================================================*/
+
+void
+pm_feed_from_pamtuples(int    const pipeToFeedFd,
+                       void * const feederParm) {
+
+    struct pamtuples * const inputTuplesP = feederParm;
+
+    struct pam outpam;
+
+    outpam = *inputTuplesP->pamP;
+    outpam.file = fdopen(pipeToFeedFd, "w");
+
+    /* The following signals (and normally kills) the process with
+       SIGPIPE if the pipe does not take all 'inputLength' bytes.
+    */
+    pnm_writepam(&outpam, *inputTuplesP->tuplesP);
+
+    pm_close(outpam.file);
+}
+
+
+
+void
+pm_accept_to_pamtuples(int    const pipeToSuckFd,
+                       void * const accepterParm ) {
+
+    struct pamtuples * const outputTuplesP = accepterParm;
+
+    struct pam inpam;
+
+    *outputTuplesP->tuplesP =
+        pnm_readpam(fdopen(pipeToSuckFd, "r"), &inpam, sizeof(inpam));
+
+    pm_close(inpam.file);
+}
diff --git a/lib/libpam.h b/lib/libpam.h
new file mode 100644
index 00000000..4b8e6693
--- /dev/null
+++ b/lib/libpam.h
@@ -0,0 +1,17 @@
+/* This is the intra-libnetpbm interface header file for libpam.c
+*/
+
+#ifndef LIBPAM_H_INCLUDED
+#define LIBPAM_H_INCLUDED
+
+#include "pam.h"
+
+void
+pnm_readpaminitrestaspnm(FILE * const fileP, 
+                         int *  const colsP, 
+                         int *  const rowsP, 
+                         gray * const maxvalP,
+                         int *  const formatP);
+
+#endif
+
diff --git a/lib/libpamcolor.c b/lib/libpamcolor.c
new file mode 100644
index 00000000..e11415ca
--- /dev/null
+++ b/lib/libpamcolor.c
@@ -0,0 +1,102 @@
+/*----------------------------------------------------------------------------
+                                  libpamcolor.c
+------------------------------------------------------------------------------
+   These are the library functions, which belong in the libnetpbm library,
+   that deal with colors in the PAM image format.
+-----------------------------------------------------------------------------*/
+
+/* See libpbm.c for the complicated explanation of this 32/64 bit file
+   offset stuff.
+*/
+#define _FILE_OFFSET_BITS 64
+#define _LARGE_FILES  
+
+#include <limits.h>
+
+#include "pm_c_util.h"
+#include "pam.h"
+#include "ppm.h"
+
+
+tuple
+pnm_parsecolor(const char * const colorname,
+               sample       const maxval) {
+
+    tuple retval;
+    pixel color;
+    struct pam pam;
+
+    pam.len = PAM_STRUCT_SIZE(bytes_per_sample);
+    pam.depth = 3;
+    pam.maxval = maxval;
+    pam.bytes_per_sample = pnm_bytespersample(maxval);
+
+    retval = pnm_allocpamtuple(&pam);
+
+    color = ppm_parsecolor(colorname, maxval);
+
+    retval[PAM_RED_PLANE] = PPM_GETR(color);
+    retval[PAM_GRN_PLANE] = PPM_GETG(color);
+    retval[PAM_BLU_PLANE] = PPM_GETB(color);
+
+    return retval;
+}
+
+
+
+double pnm_lumin_factor[3] = {PPM_LUMINR, PPM_LUMING, PPM_LUMINB};
+
+void
+pnm_YCbCrtuple(tuple    const tuple, 
+               double * const YP, 
+               double * const CbP, 
+               double * const CrP) {
+/*----------------------------------------------------------------------------
+   Assuming that the tuple 'tuple' is of tupletype RGB, return the 
+   Y/Cb/Cr representation of the color represented by the tuple.
+-----------------------------------------------------------------------------*/
+    int const red = (int)tuple[PAM_RED_PLANE];
+    int const grn = (int)tuple[PAM_GRN_PLANE];
+    int const blu = (int)tuple[PAM_BLU_PLANE];
+    
+    *YP  = (+ PPM_LUMINR * red + PPM_LUMING * grn + PPM_LUMINB * blu);
+    *CbP = (- 0.16874 * red - 0.33126 * grn + 0.50000 * blu);
+    *CrP = (+ 0.50000 * red - 0.41869 * grn - 0.08131 * blu);
+}
+
+
+
+void 
+pnm_YCbCr_to_rgbtuple(const struct pam * const pamP,
+                      tuple              const tuple,
+                      double             const Y,
+                      double             const Cb, 
+                      double             const Cr,
+                      int *              const overflowP) {
+
+    double rgb[3];
+    unsigned int plane;
+    bool overflow;
+
+    rgb[PAM_RED_PLANE] = Y + 1.4022 * Cr + .5;
+    rgb[PAM_GRN_PLANE] = Y - 0.7145 * Cr - 0.3456 * Cb + .5;
+    rgb[PAM_BLU_PLANE] = Y + 1.7710 * Cb + .5;
+
+    overflow = FALSE;  /* initial assumption */
+
+    for (plane = 0; plane < 3; ++plane) {
+        if (rgb[plane] > pamP->maxval) {
+            overflow = TRUE;
+            tuple[plane] = pamP->maxval;
+        } else if (rgb[plane] < 0.0) {
+            overflow = TRUE;
+            tuple[plane] = 0;
+        } else 
+            tuple[plane] = (sample)rgb[plane];
+    }
+    if (overflowP)
+        *overflowP = overflow;
+}
+
+
+
diff --git a/lib/libpammap.c b/lib/libpammap.c
new file mode 100644
index 00000000..9e90ade0
--- /dev/null
+++ b/lib/libpammap.c
@@ -0,0 +1,662 @@
+/******************************************************************************
+                                  libpammap.c
+*******************************************************************************
+
+  These are functions that deal with tuple hashes and tuple tables.
+
+  Both tuple hashes and tuple tables let you associate an arbitrary
+  integer with a tuple value.  A tuple hash lets you look up the one
+  integer value (if any) associated with a given tuple value, having
+  the low memory and execution time characteristics of a hash table.
+  A tuple table lets you scan all the values, being a table of elements
+  that consist of an ordered pair of a tuple value and integer.
+
+******************************************************************************/
+
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "pam.h"
+#include "pammap.h"
+
+
+#define HASH_SIZE 20023
+
+unsigned int
+pnm_hashtuple(struct pam * const pamP, tuple const tuple) {
+/*----------------------------------------------------------------------------
+   Return the hash value of the tuple 'tuple' -- i.e. an index into a hash
+   table.
+-----------------------------------------------------------------------------*/
+    int i;
+    unsigned int hash;
+    const unsigned int hash_factor[] = {33023, 30013, 27011};
+
+    hash = 0;  /* initial value */
+    for (i = 0; i < MIN(pamP->depth, 3); ++i) {
+        hash += tuple[i] * hash_factor[i];  /* May overflow */
+    }
+    hash %= HASH_SIZE;
+    return hash;
+}
+
+
+
+
+tuplehash
+pnm_createtuplehash(void) {
+/*----------------------------------------------------------------------------
+   Create an empty tuple hash -- i.e. a hash table of zero length hash chains.
+-----------------------------------------------------------------------------*/
+    tuplehash retval;
+    unsigned int i;
+
+    MALLOCARRAY(retval, HASH_SIZE);
+
+    if (retval == NULL)
+        pm_error("Out of memory allocating tuple hash of size %u",
+                 HASH_SIZE);
+
+    for (i = 0; i < HASH_SIZE; ++i) 
+        retval[i] = NULL;
+
+    return retval;
+}
+
+
+
+void
+pnm_destroytuplehash(tuplehash const tuplehash) {
+
+    int i;
+
+    /* Free the chains */
+
+    for (i = 0; i < HASH_SIZE; ++i) {
+        struct tupleint_list_item * p;
+        struct tupleint_list_item * next;
+        
+        /* Walk this chain, freeing each element */
+        for (p = tuplehash[i]; p; p = next) {
+            next = p->next;
+
+            free(p);
+        }            
+    }
+
+    /* Free the table of chains */
+    free(tuplehash);
+}
+
+
+
+
+static struct tupleint_list_item * 
+allocTupleIntListItem(struct pam * const pamP) {
+
+
+    /* This is complicated by the fact that the last element of a 
+       tupleint_list_item is of variable length, because the last element
+       of _it_ is of variable length 
+    */
+    struct tupleint_list_item * retval;
+
+    unsigned int const size = 
+        sizeof(*retval) - sizeof(retval->tupleint.tuple) 
+        + pamP->depth * sizeof(sample);
+
+    retval = (struct tupleint_list_item *) malloc(size);
+
+    return retval;
+}
+
+
+
+void
+pnm_addtotuplehash(struct pam *   const pamP,
+                   tuplehash      const tuplehash, 
+                   tuple          const tupletoadd,
+                   int            const value,
+                   int *          const fitsP) {
+/*----------------------------------------------------------------------------
+   Add a tuple value to the hash -- assume it isn't already there.
+
+   Allocate new space for the tuple value and the hash chain element.
+
+   If we can't allocate space for the new hash chain element, don't
+   change anything and return *fitsP = FALSE;
+-----------------------------------------------------------------------------*/
+    struct tupleint_list_item * const listItemP = allocTupleIntListItem(pamP);
+    if (listItemP == NULL)
+        *fitsP = FALSE;
+    else {
+        unsigned int const hashvalue = pnm_hashtuple(pamP, tupletoadd);
+    
+        *fitsP = TRUE;
+
+        pnm_assigntuple(pamP, listItemP->tupleint.tuple, tupletoadd);
+        listItemP->tupleint.value = value;
+        listItemP->next = tuplehash[hashvalue];
+        tuplehash[hashvalue] = listItemP;
+    }
+}
+
+
+
+void
+pnm_lookuptuple(struct pam *    const pamP, 
+                const tuplehash       tuplehash, 
+                const tuple           searchval, 
+                int *           const foundP, 
+                int *           const retvalP) {
+    
+    unsigned int const hashvalue = pnm_hashtuple(pamP, searchval);
+    struct tupleint_list_item * p;
+    struct tupleint_list_item * found;
+
+    found = NULL;  /* None found yet */
+    for (p = tuplehash[hashvalue]; p && !found; p = p->next)
+        if (pnm_tupleequal(pamP, p->tupleint.tuple, searchval)) {
+            found = p;
+        }
+
+    if (found) {
+        *foundP = TRUE;
+        *retvalP = found->tupleint.value;
+    } else
+        *foundP = FALSE;
+}
+
+
+
+static void
+addColorOccurrenceToHash(tuple          const color, 
+                         tuplehash      const tuplefreqhash,
+                         struct pam *   const pamP,
+                         unsigned int   const maxsize,
+                         unsigned int * const sizeP,
+                         bool *         const fullP) {
+               
+    unsigned int const hashvalue = pnm_hashtuple(pamP, color);
+            
+    struct tupleint_list_item *p;
+
+    for (p = tuplefreqhash[hashvalue]; 
+         p && !pnm_tupleequal(pamP, p->tupleint.tuple, color);
+         p = p->next);
+
+    if (p) {
+        /* It's in the hash; just tally one more occurence */
+        ++p->tupleint.value;
+        *fullP = FALSE;
+    } else {
+        /* It's not in the hash yet, so add it (if allowed) */
+        ++(*sizeP);
+        if (maxsize > 0 && *sizeP > maxsize) 
+            *fullP = TRUE;
+        else {
+            *fullP = FALSE;
+            p = allocTupleIntListItem(pamP);
+            if (p == NULL)
+                pm_error("out of memory computing hash table");
+            pnm_assigntuple(pamP, p->tupleint.tuple, color);
+            p->tupleint.value = 1;
+            p->next = tuplefreqhash[hashvalue];
+            tuplefreqhash[hashvalue] = p;
+        }
+    }
+}
+
+
+
+void
+pnm_addtuplefreqoccurrence(struct pam *   const pamP,
+                           tuple          const value,
+                           tuplehash      const tuplefreqhash,
+                           int *          const firstOccurrenceP) {
+
+    unsigned int const hashvalue = pnm_hashtuple(pamP, value);
+            
+    struct tupleint_list_item * p;
+
+    for (p = tuplefreqhash[hashvalue]; 
+         p && !pnm_tupleequal(pamP, p->tupleint.tuple, value);
+         p = p->next);
+
+    if (p) {
+        /* It's in the hash; just tally one more occurence */
+        ++p->tupleint.value;
+        *firstOccurrenceP = FALSE;
+    } else {
+        struct tupleint_list_item * p;
+
+        /* It's not in the hash yet, so add it */
+        *firstOccurrenceP = TRUE;
+
+        p = allocTupleIntListItem(pamP);
+        if (p == NULL)
+            pm_error("out of memory computing hash table");
+
+        pnm_assigntuple(pamP, p->tupleint.tuple, value);
+        p->tupleint.value = 1;
+        p->next = tuplefreqhash[hashvalue];
+        tuplefreqhash[hashvalue] = p;
+    }
+}
+
+
+
+static void
+computehashrecoverable(struct pam *   const pamP,
+                       tuple **       const tupleArray, 
+                       unsigned int   const maxsize, 
+                       sample         const newMaxval,
+                       unsigned int * const sizeP,
+                       tuplehash *    const tuplefreqhashP,
+                       tuple **       const rowbufferP,
+                       tuple *        const colorP) {
+/*----------------------------------------------------------------------------
+   This is computetuplefreqhash(), only it leaves a trail so that if it
+   happens to longjmp out because of a failed memory allocation, the
+   setjmp'er can cleanup whatever it had done so far.
+-----------------------------------------------------------------------------*/
+    unsigned int row;
+    struct pam freqPam;
+    bool full;
+
+    freqPam = *pamP;
+    freqPam.maxval = newMaxval;
+
+    *tuplefreqhashP = pnm_createtuplehash();
+    *sizeP = 0;   /* initial value */
+    
+    *rowbufferP = pnm_allocpamrow(pamP);
+    
+    *colorP = pnm_allocpamtuple(&freqPam);
+    
+    full = FALSE;  /* initial value */
+    
+    /* Go through the entire raster, building a hash table of
+       tuple values. 
+    */
+    for (row = 0; row < pamP->height && !full; ++row) {
+        int col;
+        const tuple * tuplerow;  /* The row of tuples we are processing */
+        
+        if (tupleArray)
+            tuplerow = tupleArray[row];
+        else {
+            pnm_readpamrow(pamP, *rowbufferP);
+            tuplerow = *rowbufferP;
+        }
+        for (col = 0; col < pamP->width && !full; ++col) {
+            pnm_scaletuple(pamP, *colorP, tuplerow[col], freqPam.maxval);
+            addColorOccurrenceToHash(
+                *colorP, *tuplefreqhashP, &freqPam, maxsize, sizeP, &full);
+        }
+    }
+
+    pnm_freepamtuple(*colorP); *colorP = NULL;
+    pnm_freepamrow(*rowbufferP); *rowbufferP = NULL;
+
+    if (full) {
+        pnm_destroytuplehash(*tuplefreqhashP);
+        *tuplefreqhashP = NULL;
+    }
+}
+
+
+
+static tuplehash
+computetuplefreqhash(struct pam *   const pamP,
+                     tuple **       const tupleArray, 
+                     unsigned int   const maxsize, 
+                     sample         const newMaxval,
+                     unsigned int * const sizeP) {
+/*----------------------------------------------------------------------------
+  Compute a tuple frequency hash from a PAM.  This is a hash that gives
+  you the number of times a given tuple value occurs in the PAM.  You can
+  supply the input PAM in one of two ways:
+
+  1) a two-dimensional array of tuples tupleArray[][];  In this case,
+     'tupleArray' is non-NULL.
+
+  2) an open PAM file, positioned to the raster.  In this case,
+     'tupleArray' is NULL.  *pamP contains the file descriptor.
+  
+     We return with the file still open and its position undefined.  
+
+  In either case, *pamP contains parameters of the tuple array.
+
+  Return the number of unique tuple values found as *sizeP.
+
+  However, if the number of unique tuple values is greater than 'maxsize', 
+  return a null return value and *sizeP undefined.
+
+  The tuple values that index the hash are scaled to a new maxval of
+  'newMaxval'.  E.g.  if the input has maxval 100 and 'newMaxval' is
+  50, and a particular tuple has sample value 50, it would be counted
+  as sample value 25 in the hash.
+-----------------------------------------------------------------------------*/
+    tuplehash tuplefreqhash;
+    tuple * rowbuffer;  /* malloc'ed */
+        /* Buffer for a row read from the input file; undefined (but still
+           allocated) if input is not from a file.
+        */
+    tuple color;  
+        /* The color currently being added, scaled to the new maxval */
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    
+    /* Initialize to "none" for purposes of error recovery */
+    tuplefreqhash = NULL;
+    rowbuffer = NULL;
+    color = NULL;
+
+    if (setjmp(jmpbuf) == 0) {
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+        computehashrecoverable(pamP, tupleArray, maxsize, newMaxval, sizeP,
+                               &tuplefreqhash, &rowbuffer, &color);
+        pm_setjmpbuf(origJmpbufP);
+    } else {
+        if (color) 
+            pnm_freepamtuple(color);
+        if (rowbuffer)
+            pnm_freepamrow(rowbuffer);
+        if (tuplefreqhash)
+            pnm_destroytuplehash(tuplefreqhash);
+        pm_longjmp();
+    }
+    return tuplefreqhash;
+}
+
+
+
+tuplehash
+pnm_computetuplefreqhash(struct pam *   const pamP,
+                         tuple **       const tupleArray,
+                         unsigned int   const maxsize,
+                         unsigned int * const sizeP) {
+/*----------------------------------------------------------------------------
+   Compute the tuple frequency hash for the tuple array tupleArray[][].
+-----------------------------------------------------------------------------*/
+    return computetuplefreqhash(pamP, tupleArray, maxsize, pamP->maxval, 
+                                sizeP);
+}
+
+
+
+tupletable
+pnm_alloctupletable(const struct pam * const pamP, 
+                    unsigned int       const size) {
+    
+    tupletable retval;
+
+    if (UINT_MAX / sizeof(struct tupleint) < size)
+        pm_error("size %u is too big for arithmetic", size);
+    else {
+        unsigned int const mainTableSize = size * sizeof(struct tupleint *);
+        unsigned int const tupleIntSize = 
+            sizeof(struct tupleint) - sizeof(sample) 
+            + pamP->depth * sizeof(sample);
+
+        /* To save the enormous amount of time it could take to allocate
+           each individual tuple, we do a trick here and allocate everything
+           as a single malloc block and suballocate internally.
+        */
+        if ((UINT_MAX - mainTableSize) / tupleIntSize < size)
+            pm_error("size %u is too big for arithmetic", size);
+        else {
+            void * pool;
+            unsigned int i;
+    
+            pool = malloc(mainTableSize + size * tupleIntSize);
+    
+            retval = (tupletable) pool;
+
+            for (i = 0; i < size; ++i)
+                retval[i] = (struct tupleint *)
+                    ((char*)pool + mainTableSize + i * tupleIntSize);
+        }
+    }
+    return retval;
+}
+
+
+
+void
+pnm_freetupletable(struct pam * const pamP,
+                   tupletable   const tupletable) {
+
+    /* Note that the address 'tupletable' is, to the operating system, 
+       the address of a larger block of memory that contains not only 
+       tupletable, but all the samples to which it points (e.g.
+       tupletable[0].tuple[0])
+    */
+
+    free(tupletable);
+}
+
+
+
+void
+pnm_freetupletable2(struct pam * const pamP,
+                    tupletable2  const tupletable) {
+
+    pnm_freetupletable(pamP, tupletable.table);
+}
+
+
+
+static tupletable
+tuplehashtotable(const struct pam * const pamP,
+                 tuplehash          const tuplehash,
+                 unsigned int       const allocsize) {
+/*----------------------------------------------------------------------------
+   Create a tuple table containing the info from a tuple hash.  Allocate
+   space in the table for 'allocsize' elements even if there aren't that
+   many tuple values in the input hash.  That's so the caller has room
+   for expansion.
+
+   Caller must ensure that 'allocsize' is at least as many tuple values
+   as there are in the input hash.
+
+   We allocate new space for all the table contents; there are no pointers
+   in the table to tuples or anything else in existing space.
+-----------------------------------------------------------------------------*/
+    tupletable tupletable;
+
+    tupletable = pnm_alloctupletable(pamP, allocsize);
+
+    if (tupletable != NULL) {
+        unsigned int i, j;
+        /* Loop through the hash table. */
+        j = 0;
+        for (i = 0; i < HASH_SIZE; ++i) {
+            /* Walk this hash chain */
+            struct tupleint_list_item * p;
+            for (p = tuplehash[i]; p; p = p->next) {
+                assert(j < allocsize);
+                tupletable[j]->value = p->tupleint.value;
+                pnm_assigntuple(pamP, tupletable[j]->tuple, p->tupleint.tuple);
+                ++j;
+            }
+        }
+    }
+    return tupletable;
+}
+
+
+
+tupletable
+pnm_tuplehashtotable(const struct pam * const pamP,
+                     tuplehash          const tuplehash,
+                     unsigned int       const allocsize) {
+
+    tupletable tupletable;
+
+    tupletable = tuplehashtotable(pamP, tuplehash, allocsize);
+
+    if (tupletable == NULL)
+        pm_error("out of memory generating tuple table");
+
+    return tupletable;
+}
+
+
+
+tuplehash
+pnm_computetupletablehash(struct pam * const pamP, 
+                          tupletable   const tupletable,
+                          unsigned int const tupletableSize) {
+/*----------------------------------------------------------------------------
+   Create a tuple hash containing indices into the tuple table
+   'tupletable'.  The hash index for the hash is the value of a tuple;
+   the hash value is the tuple table index for the element in the
+   tuple table that contains that tuple value.
+
+   Assume there are no duplicate tuple values in the tuple table.
+
+   We allocate space for the main hash table and all the elements of the
+   hash chains.
+-----------------------------------------------------------------------------*/
+    tuplehash tupletablehash;
+    unsigned int i;
+    bool fits;
+    
+    tupletablehash = pnm_createtuplehash();
+
+    fits = TRUE;  /* initial assumption */
+    for (i = 0; i < tupletableSize && fits; ++i) {
+        pnm_addtotuplehash(pamP, tupletablehash, 
+                           tupletable[i]->tuple, i, &fits);
+    }
+    if (!fits) {
+        pnm_destroytuplehash(tupletablehash);
+        pm_error("Out of memory computing tuple hash from tuple table");
+    }
+    return tupletablehash;
+}
+
+
+
+tupletable
+pnm_computetuplefreqtable2(struct pam *   const pamP,
+                           tuple **       const tupleArray,
+                           unsigned int   const maxsize,
+                           sample         const newMaxval,
+                           unsigned int * const countP) {
+/*----------------------------------------------------------------------------
+   Compute a tuple frequency table from a PAM image.  This is an
+   array that tells how many times each tuple value occurs in the
+   image.
+
+   Except for the format of the output, this function is the same as
+   computetuplefreqhash().
+
+   If there are more than 'maxsize' unique tuple values in tupleArray[][],
+   give up.
+
+   Return the array in newly malloc'ed storage.  Allocate space for
+   'maxsize' entries even if there aren't that many distinct tuple
+   values in tupleArray[].  That's so the caller has room for
+   expansion.
+
+   If 'maxsize' is zero, allocate exactly as much space as there are
+   distinct tuple values in tupleArray[], and don't give up no matter
+   how many tuple values we find (except, of course, we abort if we
+   can't get enough memory).
+
+   Return the number of unique tuple values in tupleArray[][] as
+   *countP.
+
+
+   Scale the tuple values to a new maxval of 'newMaxval' before
+   processing them.  E.g. if the input has maxval 100 and 'newMaxval'
+   is 50, and a particular tuple has sample value 50, it would be
+   listed as sample value 25 in the output table.  This makes the
+   output table smaller and the processing time less.
+-----------------------------------------------------------------------------*/
+    tuplehash tuplefreqhash;
+    tupletable tuplefreqtable;
+    unsigned int uniqueCount;
+
+    tuplefreqhash = computetuplefreqhash(pamP, tupleArray, maxsize, 
+                                         newMaxval, &uniqueCount);
+    if (tuplefreqhash == NULL)
+        tuplefreqtable = NULL;
+    else {
+        unsigned int tableSize = (maxsize == 0 ? uniqueCount : maxsize);
+        assert(tableSize >= uniqueCount);
+        tuplefreqtable = tuplehashtotable(pamP, tuplefreqhash, tableSize);
+        pnm_destroytuplehash(tuplefreqhash);
+        if (tuplefreqtable == NULL)
+            pm_error("Out of memory generating tuple table");
+    }
+    *countP = uniqueCount;
+
+    return tuplefreqtable;
+}
+
+
+
+tupletable
+pnm_computetuplefreqtable(struct pam *   const pamP,
+                          tuple **       const tupleArray,
+                          unsigned int   const maxsize,
+                          unsigned int * const sizeP) {
+
+    return pnm_computetuplefreqtable2(pamP, tupleArray, maxsize, pamP->maxval,
+                                      sizeP);
+}
+
+
+
+char*
+pam_colorname(struct pam *         const pamP, 
+              tuple                const color, 
+              enum colornameFormat const format) {
+
+    unsigned int r, g, b;
+    FILE* f;
+    static char colorname[200];
+
+    r = pnm_scalesample(color[PAM_RED_PLANE], pamP->maxval, 255);
+    g = pnm_scalesample(color[PAM_GRN_PLANE], pamP->maxval, 255);
+    b = pnm_scalesample(color[PAM_BLU_PLANE], pamP->maxval, 255);
+
+    f = pm_openColornameFile(NULL, format == PAM_COLORNAME_ENGLISH);
+    if (f != NULL) {
+        unsigned int best_diff;
+        bool done;
+
+        best_diff = 32767;
+        done = FALSE;
+        while (!done) {
+            struct colorfile_entry const ce = pm_colorget(f);
+            if (ce.colorname) {
+                unsigned int const this_diff = 
+                    abs((int)r - (int)ce.r) + 
+                    abs((int)g - (int)ce.g) + 
+                    abs((int)b - (int)ce.b);
+
+                if (this_diff < best_diff) {
+                    best_diff = this_diff;
+                    strcpy(colorname, ce.colorname);
+                }
+            } else
+                done = TRUE;
+        }
+        fclose(f);
+        if (best_diff != 32767 && 
+            (best_diff == 0 || format == PAM_COLORNAME_ENGLISH))
+            return colorname;
+    }
+
+    /* Color lookup failed, but caller is willing to take an X11-style
+       hex specifier, so return that.
+    */
+    sprintf(colorname, "#%02x%02x%02x", r, g, b);
+    return colorname;
+}
diff --git a/lib/libpamn.c b/lib/libpamn.c
new file mode 100644
index 00000000..c7610100
--- /dev/null
+++ b/lib/libpamn.c
@@ -0,0 +1,542 @@
+/*----------------------------------------------------------------------------
+                                  libpamn.c
+------------------------------------------------------------------------------
+   These are the library functions, which belong in the libnetpbm library,
+   that deal with the PAM image format via maxval-normalized, floating point
+   sample values.
+-----------------------------------------------------------------------------*/
+
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "pam.h"
+#include "fileio.h"
+#include "pm_gamma.h"
+
+#define EPSILON 1e-7
+
+
+
+tuplen *
+pnm_allocpamrown(const struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   We assume that the dimensions of the image are such that arithmetic
+   overflow will not occur in our calculations.  NOTE: pnm_readpaminit()
+   ensures this assumption is valid.
+-----------------------------------------------------------------------------*/
+    const int bytes_per_tuple = pamP->depth * sizeof(samplen);
+    tuplen * tuplerown;
+
+    /* The tuple row data structure starts with 'width' pointers to
+       the tuples, immediately followed by the 'width' tuples
+       themselves.  Each tuple consists of 'depth' samples.
+    */
+
+    tuplerown = malloc(pamP->width * (sizeof(tuplen *) + bytes_per_tuple));
+    if (tuplerown == NULL)
+        pm_error("Out of memory allocating space for a tuple row of\n"
+                 "%d tuples by %d samples per tuple by %d bytes per sample.",
+                 pamP->width, pamP->depth, sizeof(samplen));
+
+    {
+        /* Now we initialize the pointers to the individual tuples to make this
+           a regulation C two dimensional array.
+        */
+        
+        char *p;
+        int i;
+        
+        p = (char*) (tuplerown + pamP->width);  /* location of Tuple 0 */
+        for (i = 0; i < pamP->width; i++) {
+            tuplerown[i] = (tuplen) p;
+            p += bytes_per_tuple;
+        }
+    }
+    return(tuplerown);
+}
+
+
+
+void 
+pnm_readpamrown(const struct pam * const pamP, 
+                tuplen *           const tuplenrow) {
+
+    /* For speed, we don't check any of the inputs for consistency 
+       here (unless it's necessary to avoid crashing).  Any consistency
+       checking should have been done by a prior call to 
+       pnm_writepaminit().
+    */
+    assert(pamP->maxval != 0);
+
+    /* Need a special case for raw PBM because it has multiple tuples (8)
+       packed into one byte.
+    */
+    if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE) {
+        int col;
+        bit *bitrow;
+        if (pamP->depth != 1)
+            pm_error("Invalid pam structure passed to pnm_readpamrow().  "
+                     "It says PBM format, but 'depth' member is not 1.");
+        bitrow = pbm_allocrow(pamP->width);
+        pbm_readpbmrow(pamP->file, bitrow, pamP->width, pamP->format);
+        for (col = 0; col < pamP->width; col++)
+            tuplenrow[col][0] = 
+                bitrow[col] == PBM_BLACK ? 0.0 : 1.0;
+        pbm_freerow(bitrow);
+    } else {
+        float const scaler = 1.0 / pamP->maxval;
+            /* Note: multiplication is faster than division, so we divide
+               once here so we can multiply many times later.
+            */
+        int col;
+        tuple * tuplerow;
+
+        tuplerow = pnm_allocpamrow(pamP);
+
+        pnm_readpamrow(pamP, tuplerow);
+        for (col = 0; col < pamP->width; ++col) {
+            unsigned int plane;
+            for (plane = 0; plane < pamP->depth; ++plane)
+                tuplenrow[col][plane] = tuplerow[col][plane] * scaler;
+        }
+        pnm_freepamrow(tuplerow);
+    }
+}
+
+
+
+void 
+pnm_writepamrown(const struct pam * const pamP, 
+                 const tuplen *     const tuplenrow) {
+
+    /* For speed, we don't check any of the inputs for consistency 
+       here (unless it's necessary to avoid crashing).  Any consistency
+       checking should have been done by a prior call to 
+       pnm_writepaminit().
+    */
+    assert(pamP->maxval != 0);
+
+    /* Need a special case for raw PBM because it has multiple tuples (8)
+       packed into one byte.
+    */
+    if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE) {
+        int col;
+        bit *bitrow;
+        bitrow = pbm_allocrow(pamP->width);
+        for (col = 0; col < pamP->width; col++)
+            bitrow[col] = 
+                tuplenrow[col][0] < 0.5 ? PBM_BLACK : PBM_WHITE;
+        pbm_writepbmrow(pamP->file, bitrow, pamP->width, 
+                        pamP->format == PBM_FORMAT);
+        pbm_freerow(bitrow);
+    } else {
+        tuple * tuplerow;
+        int col;
+
+        tuplerow = pnm_allocpamrow(pamP);
+
+        for (col = 0; col < pamP->width; ++col) {
+            unsigned int plane;
+            for (plane = 0; plane < pamP->depth; ++plane)
+                tuplerow[col][plane] = (sample)
+                    (tuplenrow[col][plane] * pamP->maxval + 0.5);
+        }    
+        pnm_writepamrow(pamP, tuplerow);
+        pnm_freepamrow(tuplerow);
+    }
+}
+
+
+
+tuplen **
+pnm_allocpamarrayn(const struct pam * const pamP) {
+    
+    tuplen **tuplenarray;
+    int row;
+
+    /* If the speed of this is ever an issue, it might be sped up a little
+       by allocating one large chunk.
+    */
+    
+    MALLOCARRAY(tuplenarray, pamP->height);
+    if (tuplenarray == NULL) 
+        pm_error("Out of memory allocating the row pointer section of "
+                 "a %u row array", pamP->height);
+
+    for (row = 0; row < pamP->height; row++) {
+        tuplenarray[row] = pnm_allocpamrown(pamP);
+    }
+    return(tuplenarray);
+}
+
+
+
+void
+pnm_freepamarrayn(tuplen **          const tuplenarray, 
+                  const struct pam * const pamP) {
+
+    int row;
+    for (row = 0; row < pamP->height; row++)
+        pnm_freepamrown(tuplenarray[row]);
+
+    free(tuplenarray);
+}
+
+
+
+tuplen** 
+pnm_readpamn(FILE *       const file, 
+             struct pam * const pamP, 
+             int          const size) {
+
+    tuplen **tuplenarray;
+    int row;
+
+    pnm_readpaminit(file, pamP, size);
+    
+    tuplenarray = pnm_allocpamarrayn(pamP);
+    
+    for (row = 0; row < pamP->height; row++) 
+        pnm_readpamrown(pamP, tuplenarray[row]);
+
+    return(tuplenarray);
+}
+
+
+
+void 
+pnm_writepamn(struct pam * const pamP, 
+              tuplen **    const tuplenarray) {
+
+    int row;
+
+    pnm_writepaminit(pamP);
+    
+    for (row = 0; row < pamP->height; row++) 
+        pnm_writepamrown(pamP, tuplenarray[row]);
+}
+
+
+
+void
+pnm_normalizetuple(struct pam * const pamP,
+                   tuple        const tuple,
+                   tuplen       const tuplen) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane) 
+        tuplen[plane] = (samplen)tuple[plane] / pamP->maxval;
+}
+
+
+
+void
+pnm_unnormalizetuple(struct pam * const pamP,
+                     tuplen       const tuplen,
+                     tuple        const tuple) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane) 
+        tuple[plane] = tuplen[plane] * pamP->maxval + 0.5;
+}
+
+
+
+void
+pnm_normalizeRow(struct pam *             const pamP,
+                 const tuple *            const tuplerow,
+                 const pnm_transformMap * const transform,
+                 tuplen *                 const tuplenrow) {
+
+    float const scaler = 1.0 / pamP->maxval;
+        /* Note: multiplication is faster than division, so we divide
+           once here so we can multiply many times later.
+        */
+    unsigned int plane;
+    
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        if (transform && transform[plane]) {
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col) {
+                sample const sample = tuplerow[col][plane];
+                tuplenrow[col][plane] = transform[plane][sample];
+            }
+        } else {
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col)
+                tuplenrow[col][plane] = tuplerow[col][plane] * scaler;
+        }
+    }
+}
+
+
+
+static sample
+reversemap(samplen          const samplen,
+           pnm_transformMap const transformMap,
+           sample           const maxval) {
+/*----------------------------------------------------------------------------
+   Find the integer sample value that maps to the normalized samplen value
+   'samplen' through the map 'transformMap'.  We interpret the map as
+   mapping the value N+1 to all the values transformMap[N] through 
+   transformMap[N+1], and we expect transformMap[N+1] to be greater than
+   transformMap[N] for all N.
+-----------------------------------------------------------------------------*/
+    /* Do a binary search, since the values are in sorted (increasing)
+       order
+    */
+    
+    sample low, high;
+
+    low = 0; high = maxval;  /* Consider whole range to start */
+
+    while (low < high) {
+        unsigned int const middle = (low + high) / 2;
+
+        if (samplen < transformMap[middle])
+            /* Restrict  our consideration to the lower half of the range */
+            high = middle;
+        else
+            /* Restrict our consideration to the upper half of the range */
+            low = middle + 1;
+    }
+    return low;
+}
+
+
+
+void
+pnm_unnormalizeRow(struct pam *             const pamP,
+                   const tuplen *           const tuplenrow,
+                   const pnm_transformMap * const transform,
+                   tuple *                  const tuplerow) {
+
+    unsigned int plane;
+    
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        if (transform && transform[plane]) {
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col)
+                tuplerow[col][plane] = 
+                    reversemap(tuplenrow[col][plane], 
+                               transform[plane], pamP->maxval);
+        } else {
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col)
+                tuplerow[col][plane] = 
+                    tuplenrow[col][plane] * pamP->maxval + 0.5;
+        }
+    }
+}
+
+
+
+typedef samplen (*gammaFunction)(samplen);
+
+static void
+gammaCommon(struct pam *  const pamP,
+            tuplen *      const tuplenrow,
+            gammaFunction       gammafn) {
+
+    unsigned int plane;
+    unsigned int opacityPlane;
+    bool haveOpacity;
+    
+    pnm_getopacity(pamP, &haveOpacity, &opacityPlane);
+
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        if (haveOpacity && plane == opacityPlane) {
+            /* It's an opacity (alpha) plane, which means there is
+               no gamma adjustment in it.  
+            */
+        } else {
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col)
+                tuplenrow[col][plane] = gammafn(tuplenrow[col][plane]);
+        }
+    }
+}
+
+
+
+void
+pnm_gammarown(struct pam * const pamP,
+              tuplen *     const tuplenrow) {
+
+    gammaCommon(pamP, tuplenrow, &pm_gamma709);
+}
+
+
+
+void
+pnm_ungammarown(struct pam * const pamP,
+                tuplen *     const tuplenrow) {
+
+    gammaCommon(pamP, tuplenrow, &pm_ungamma709);
+}
+
+
+
+enum applyUnapply {OPACITY_APPLY, OPACITY_UNAPPLY};
+
+static void
+applyopacityCommon(enum applyUnapply const applyUnapply,
+                   struct pam *      const pamP,
+                   tuplen *          const tuplenrow) {
+
+    unsigned int opacityPlane;
+    bool haveOpacity;
+    
+    pnm_getopacity(pamP, &haveOpacity, &opacityPlane);
+
+    if (haveOpacity) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane) {
+            if (plane != opacityPlane) {
+                unsigned int col;
+                for (col = 0; col < pamP->width; ++col) {
+                    tuplen const thisTuple = tuplenrow[col];
+
+                    switch (applyUnapply) {
+                    case OPACITY_APPLY:
+                        thisTuple[plane] *= thisTuple[opacityPlane];
+                        break;
+                    case OPACITY_UNAPPLY:
+                        if (thisTuple[opacityPlane] < EPSILON) {
+                            /* There is no foreground here at all.  So
+                               the color plane values must be zero and
+                               as output it makes absolutely no
+                               difference what they are (they must be
+                               multiplied by the opacity -- zero -- to
+                               be used).
+                            */
+                            assert(thisTuple[plane] < EPSILON);
+                        } else
+                            thisTuple[plane] /= thisTuple[opacityPlane];
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}
+
+
+void
+pnm_applyopacityrown(struct pam * const pamP,
+                     tuplen *     const tuplenrow) {
+
+    applyopacityCommon(OPACITY_APPLY, pamP, tuplenrow);
+
+}
+
+
+
+void
+pnm_unapplyopacityrown(struct pam * const pamP,
+                       tuplen *     const tuplenrow) {
+
+    applyopacityCommon(OPACITY_UNAPPLY, pamP, tuplenrow);
+}
+
+
+
+static void
+fillInMap(pnm_transformMap const ungammaTransformMap,
+          sample           const maxval,
+          float            const offset) {
+
+    float const scaler = 1.0/maxval;  /* divide only once, it's slow */
+
+    sample sample;
+
+    /* Fill in the map */
+    for (sample = 0; sample <= maxval; ++sample) {
+        samplen const samplen = (sample + offset) * scaler;
+        ungammaTransformMap[sample] = pm_ungamma709(samplen);
+    }
+}
+
+
+
+static pnm_transformMap *
+createUngammaMapOffset(const struct pam * const pamP,
+                       float              const offset) {
+/*----------------------------------------------------------------------------
+   Create a transform table that computes ungamma(arg+offset) for arg
+   in [0..maxval]; So with offset == 0, you get a function that can be
+   used in converting integer sample values to normalized ungamma'ed
+   samplen values.  But with offset == 0.5, you get a function that
+   can be used in a reverse lookup to convert normalized ungamma'ed
+   samplen values to integer sample values.  The 0.5 effectively does
+   the rounding.
+-----------------------------------------------------------------------------*/
+    pnm_transformMap * retval;
+    pnm_transformMap ungammaTransformMap;
+
+    MALLOCARRAY(retval, pamP->depth);
+
+    if (retval != NULL) {
+        MALLOCARRAY(ungammaTransformMap, pamP->maxval+1);
+
+        if (ungammaTransformMap != NULL) {
+            bool haveOpacity;
+            unsigned int opacityPlane;
+            unsigned int plane;
+
+            pnm_getopacity(pamP, &haveOpacity, &opacityPlane);
+
+            for (plane = 0; plane < pamP->depth; ++plane) {
+                if (haveOpacity && plane == opacityPlane)
+                    retval[plane] = NULL;
+                else
+                    retval[plane] = ungammaTransformMap;
+            }            
+            fillInMap(ungammaTransformMap, pamP->maxval, offset);
+        } else {
+            free(retval);
+            retval = NULL;
+        }
+    }
+    return retval;
+}
+
+
+
+pnm_transformMap *
+pnm_createungammatransform(const struct pam * const pamP) {
+
+    return createUngammaMapOffset(pamP, 0.0);
+}
+
+
+
+pnm_transformMap *
+pnm_creategammatransform(const struct pam * const pamP) {
+
+    /* Since we're creating a map to be used backwards (you search for
+       the normalized value in the array, and the result is the array
+       index at which you found it), the gamma transform map is almost
+       identical to the ungamma transform map -- just with a 0.5 offset
+       to effect rounding.
+    */
+    return createUngammaMapOffset(pamP, 0.5);
+}
+
+
+
+void
+pnm_freegammatransform(const pnm_transformMap * const transform,
+                       const struct pam *       const pamP) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < pamP->depth; ++plane)
+        if (transform[plane])
+            free(transform[plane]);
+
+    free((void*)transform);
+}
diff --git a/lib/libpamread.c b/lib/libpamread.c
new file mode 100644
index 00000000..1b65745a
--- /dev/null
+++ b/lib/libpamread.c
@@ -0,0 +1,277 @@
+/*----------------------------------------------------------------------------
+                                  libpamread.c
+------------------------------------------------------------------------------
+   These are the library functions, which belong in the libnetpbm library,
+   that deal with reading the PAM (Portable Arbitrary Format) image format
+   raster (not the header).
+-----------------------------------------------------------------------------*/
+
+/* See libpm.c for the complicated explanation of this 32/64 bit file
+   offset stuff.
+*/
+#define _FILE_OFFSET_BITS 64
+#define _LARGE_FILES  
+
+#include <string.h>
+#include <limits.h>
+#include <assert.h>
+
+#include "pam.h"
+#include "fileio.h"
+
+
+static void
+readPbmRow(const struct pam * const pamP,
+           tuple *            const tuplerow) {
+
+    unsigned char *bitrow;
+    if (pamP->depth != 1)
+        pm_error("Invalid pam structure passed to pnm_readpamrow().  "
+                 "It says PBM format, but 'depth' member is not 1.");
+    
+    bitrow = (unsigned char *) pbm_allocrow(pbm_packed_bytes(pamP->width));
+    pbm_readpbmrow_packed(pamP->file, bitrow, pamP->width, pamP->format);
+    
+    if (tuplerow) {
+        int col;
+        for (col = 0; col < pamP->width; ++col) {
+            tuplerow[col][0] = 
+                ( ((bitrow[col/8] >> (7-col%8)) & 1 ) == PBM_BLACK)
+                ? PAM_PBM_BLACK : PAM_PBM_WHITE
+                ;
+        }
+    }   
+    pbm_freerow(bitrow);
+}
+
+
+
+static void
+readPlainNonPbmRow(const struct pam * const pamP,
+                   tuple *            const tuplerow) {
+
+    int col;
+
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            if (tuplerow)
+                tuplerow[col][plane] = pm_getuint(pamP->file);
+            else
+                pm_getuint(pamP->file);  /* read data and discard */
+    }
+}
+
+
+
+/* Though it is possible to simplify the bytesToSampleN() and
+   parsexNBpsRow() functions into a single routine that handles all
+   sample widths, we value efficiency higher here.  Earlier versions
+   of Netpbm (before 10.25) did that, with a loop, and performance
+   suffered visibly.
+*/
+
+static __inline__ sample
+bytes2ToSample(unsigned char buff[2]) {
+
+    return (buff[0] << 8) + buff[1];
+}
+
+
+
+static __inline__ sample
+bytes3ToSample(unsigned char buff[3]) {
+    return (buff[0] << 16) + (buff[1] << 8) + buff[2];
+}
+
+
+
+static __inline__ sample
+bytes4ToSample(unsigned char buff[4]) {
+
+    return (buff[0] << 24) + (buff[1] << 16) + (buff[2] << 8) + buff[3];
+}
+
+
+
+static void
+parse1BpsRow(const struct pam *    const pamP,
+             tuple *               const tuplerow,
+             const unsigned char * const inbuf) {
+
+    int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* initial value */
+
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            tuplerow[col][plane]= inbuf[bufferCursor++];
+    }
+}
+
+
+
+static void
+parse2BpsRow(const struct pam *    const pamP,
+             tuple *               const tuplerow,
+             const unsigned char * const inbuf) {
+
+    unsigned char (* const ib)[2] = (unsigned char (*)[2]) inbuf;
+
+    int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* initial value */
+
+    for (col=0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            tuplerow[col][plane] = bytes2ToSample(ib[bufferCursor++]);
+    }
+}
+
+
+
+static void
+parse3BpsRow(const struct pam *    const pamP,
+             tuple *               const tuplerow,
+             const unsigned char * const inbuf) {
+
+    unsigned char (* const ib)[3] = (unsigned char (*)[3]) inbuf;
+
+    int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* initial value */
+
+    for (col=0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            tuplerow[col][plane] = bytes3ToSample(ib[bufferCursor++]);
+    }
+}
+
+
+
+static void
+parse4BpsRow(const struct pam *    const pamP,
+             tuple *               const tuplerow,
+             const unsigned char * const inbuf) {
+
+    unsigned char (* const ib)[4] = (unsigned char (*)[4]) inbuf;
+
+    int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* initial value */
+
+    for (col=0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            tuplerow[col][plane] = bytes4ToSample(ib[bufferCursor++]);
+    }
+}
+
+
+
+static void
+readRawNonPbmRow(const struct pam * const pamP,
+                 tuple *            const tuplerow) {
+
+    unsigned int const rowImageSize = 
+        pamP->width * pamP->bytes_per_sample * pamP->depth;
+
+    unsigned char * inbuf;
+    size_t bytesRead;
+
+    inbuf = pnm_allocrowimage(pamP);
+    
+    bytesRead = fread(inbuf, 1, rowImageSize, pamP->file);
+
+    if (bytesRead != rowImageSize) {
+        if (feof(pamP->file))
+            pm_error("End of file encountered when trying to read a row from "
+                     "input file.");
+        else 
+            pm_error("Error reading a row from input file.  "
+                     "fread() fails with errno=%d (%s)",
+                     errno, strerror(errno));
+    }
+    if (tuplerow) {
+        switch (pamP->bytes_per_sample) {
+        case 1: parse1BpsRow(pamP, tuplerow, inbuf); break;
+        case 2: parse2BpsRow(pamP, tuplerow, inbuf); break;
+        case 3: parse3BpsRow(pamP, tuplerow, inbuf); break;
+        case 4: parse4BpsRow(pamP, tuplerow, inbuf); break;
+        default:
+            pm_error("invalid bytes per sample passed to "
+                     "pnm_formatpamrow(): %u",  pamP->bytes_per_sample);
+        }
+    }
+    pnm_freerowimage(inbuf);
+}
+
+
+
+void 
+pnm_readpamrow(const struct pam * const pamP, 
+               tuple *            const tuplerow) {
+/*----------------------------------------------------------------------------
+   Read a row from the Netpbm image file into tuplerow[], at the
+   current file position.  If 'tuplerow' is NULL, advance the file
+   pointer to the next row, but don't return the contents of the
+   current one.
+   
+   We assume the file is positioned to the beginning of a row of the
+   image's raster.
+-----------------------------------------------------------------------------*/
+    /* For speed, we don't check any of the inputs for consistency 
+       here (unless it's necessary to avoid crashing).  Any consistency
+       checking should have been done by a prior call to 
+       pnm_writepaminit().
+    */  
+
+    /* Need a special case for raw PBM because it has multiple tuples (8)
+       packed into one byte.
+    */
+
+    switch (pamP->format) {
+    case PAM_FORMAT:
+    case RPPM_FORMAT:
+    case RPGM_FORMAT:
+        readRawNonPbmRow(pamP, tuplerow);
+        break;
+    case PPM_FORMAT:
+    case PGM_FORMAT:
+        readPlainNonPbmRow(pamP, tuplerow);
+        break;
+    case RPBM_FORMAT:
+    case PBM_FORMAT:
+        readPbmRow(pamP, tuplerow);
+        break;
+    default:
+        pm_error("Invalid 'format' member in PAM structure: %u", pamP->format);
+    }
+}
+
+
+
+tuple ** 
+pnm_readpam(FILE *       const fileP,
+            struct pam * const pamP, 
+            int          const size) {
+
+    tuple **tuplearray;
+    int row;
+
+    pnm_readpaminit(fileP, pamP, size);
+    
+    tuplearray = pnm_allocpamarray(pamP);
+    
+    for (row = 0; row < pamP->height; row++) 
+        pnm_readpamrow(pamP, tuplearray[row]);
+
+    return tuplearray;
+}
diff --git a/lib/libpamwrite.c b/lib/libpamwrite.c
new file mode 100644
index 00000000..9184a4b5
--- /dev/null
+++ b/lib/libpamwrite.c
@@ -0,0 +1,397 @@
+/*----------------------------------------------------------------------------
+                                  libpamwrite.c
+------------------------------------------------------------------------------
+   These are the library functions, which belong in the libnetpbm library,
+   that deal with writing the PAM (Portable Arbitrary Format) image format
+   raster (not the header).
+-----------------------------------------------------------------------------*/
+
+/* See libpm.c for the complicated explanation of this 32/64 bit file
+   offset stuff.
+*/
+#define _FILE_OFFSET_BITS 64
+#define _LARGE_FILES  
+
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+#include <assert.h>
+
+#include <math.h>
+
+#include "pam.h"
+
+
+static __inline__ unsigned int
+samplesPerPlainLine(sample       const maxval, 
+                    unsigned int const depth, 
+                    unsigned int const lineLength) {
+/*----------------------------------------------------------------------------
+   Return the minimum number of samples that should go in a line
+   'lineLength' characters long in a plain format non-PBM PNM image
+   with depth 'depth' and maxval 'maxval'.
+
+   Note that this number is just for aesthetics; the Netpbm formats allow
+   any number of samples per line.
+-----------------------------------------------------------------------------*/
+    unsigned int const digitsForMaxval = (unsigned int)
+        (log(maxval + 0.1 ) / log(10.0));
+        /* Number of digits maxval has in decimal */
+        /* +0.1 is an adjustment to overcome precision problems */
+    unsigned int const fit = lineLength / (digitsForMaxval + 1);
+        /* Number of maxval-sized samples that fit in a line */
+    unsigned int const retval = (fit > depth) ? (fit - (fit % depth)) : fit;
+        /* 'fit', rounded down to a multiple of depth, if possible */
+
+    return retval;
+}
+
+
+
+static void
+writePamPlainPbmRow(const struct pam *  const pamP,
+                    const tuple *       const tuplerow) {
+
+    int col;
+    unsigned int const samplesPerLine = 70;
+
+    for (col = 0; col < pamP->width; ++col)
+        fprintf(pamP->file,  
+                ((col+1) % samplesPerLine == 0 || col == pamP->width-1)
+                    ? "%1u\n" : "%1u",
+                tuplerow[col][0] == PAM_PBM_BLACK ? PBM_BLACK : PBM_WHITE);
+}
+
+
+
+static void
+writePamPlainRow(const struct pam *  const pamP,
+                    const tuple *       const tuplerow) {
+
+    int const samplesPerLine = 
+        samplesPerPlainLine(pamP->maxval, pamP->depth, 79);
+
+    int col;
+    unsigned int samplesInCurrentLine;
+        /* number of samples written from start of line  */
+    
+    samplesInCurrentLine = 0;
+
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane){
+            fprintf(pamP->file, "%lu ",tuplerow[col][plane]);
+
+            ++samplesInCurrentLine;
+
+            if (samplesInCurrentLine >= samplesPerLine) {
+                fprintf(pamP->file, "\n");
+                samplesInCurrentLine = 0;
+            }            
+        }
+    }
+    fprintf(pamP->file, "\n");
+}
+
+
+
+static void
+formatPbmRow(const struct pam * const pamP,
+             const tuple *      const tuplerow,
+             unsigned char *    const outbuf,
+             unsigned int *     const rowSizeP) {
+
+    unsigned char accum;
+    int col;
+    
+    accum = 0;  /* initial value */
+    
+    for (col=0; col < pamP->width; ++col) {
+        accum |= 
+            (tuplerow[col][0] == PAM_PBM_BLACK ? PBM_BLACK : PBM_WHITE)
+                << (7-col%8);
+        if (col%8 == 7) {
+                outbuf[col/8] = accum;
+                accum = 0;
+        }
+    }
+    if (pamP->width % 8 != 0) {
+        unsigned int const lastByteIndex = pamP->width/8;
+        outbuf[lastByteIndex] = accum;
+        *rowSizeP = lastByteIndex + 1;
+    } else
+        *rowSizeP = pamP->width/8;
+}
+
+
+
+/* Though it is possible to simplify the sampleToBytesN() and
+   formatNBpsRow() functions into a single routine that handles all
+   sample widths, we value efficiency higher here.  Earlier versions
+   of Netpbm (before 10.25) did that, with a loop, and performance
+   suffered visibly.
+*/
+
+static __inline__ void
+sampleToBytes2(unsigned char       buf[2], 
+               sample        const sampleval) {
+
+    buf[0] = (sampleval >> 8) & 0xff;
+    buf[1] = (sampleval >> 0) & 0xff;
+}
+
+
+
+static __inline__ void
+sampleToBytes3(unsigned char       buf[3], 
+               sample        const sampleval) {
+
+    buf[0] = (sampleval >> 16) & 0xff;
+    buf[1] = (sampleval >>  8) & 0xff;
+    buf[2] = (sampleval >>  0) & 0xff;
+}
+
+
+
+static __inline__ void
+sampleToBytes4(unsigned char       buf[4], 
+               sample        const sampleval) {
+
+    buf[0] = (sampleval >> 24 ) & 0xff;
+    buf[1] = (sampleval >> 16 ) & 0xff;
+    buf[2] = (sampleval >>  8 ) & 0xff;
+    buf[3] = (sampleval >>  0 ) & 0xff;
+}
+
+
+
+static __inline__ void
+format1BpsRow(const struct pam * const pamP,
+              const tuple *      const tuplerow,
+              unsigned char *    const outbuf,
+              unsigned int *     const rowSizeP) {
+/*----------------------------------------------------------------------------
+   Create the image of a row in the raster of a raw format Netpbm
+   image that has one byte per sample (ergo not PBM).
+
+   Put the image at *outbuf; put the number of bytes of it at *rowSizeP.
+-----------------------------------------------------------------------------*/
+    int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* initial value */
+    
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane=0; plane < pamP->depth; ++plane)
+            outbuf[bufferCursor++] = (unsigned char)tuplerow[col][plane];
+    }
+    *rowSizeP = pamP->width * 1 * pamP->depth;
+}
+
+
+
+static __inline__ void
+format2BpsRow(const struct pam * const pamP,
+              const tuple *      const tuplerow,
+              unsigned char *    const outbuf,
+              unsigned int *     const rowSizeP) {
+/*----------------------------------------------------------------------------
+  Analogous to format1BpsRow().
+-----------------------------------------------------------------------------*/
+    unsigned char (* const ob)[2] = (unsigned char (*)[2]) outbuf;
+
+    int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* initial value */
+    
+    for (col=0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            sampleToBytes2(ob[bufferCursor++], tuplerow[col][plane]);
+    }
+
+    *rowSizeP = pamP->width * 2 * pamP->depth;
+}
+
+
+
+static __inline__ void
+format3BpsRow(const struct pam * const pamP,
+              const tuple *      const tuplerow,
+              unsigned char *    const outbuf,
+              unsigned int *     const rowSizeP) {
+/*----------------------------------------------------------------------------
+  Analogous to format1BpsRow().
+-----------------------------------------------------------------------------*/
+    unsigned char (* const ob)[3] = (unsigned char (*)[3]) outbuf;
+
+    int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* initial value */
+    
+    for (col=0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            sampleToBytes3(ob[bufferCursor++], tuplerow[col][plane]);
+    }
+
+    *rowSizeP = pamP->width * 3 * pamP->depth;
+}
+
+
+
+static __inline__ void
+format4BpsRow(const struct pam * const pamP,
+              const tuple *      const tuplerow,
+              unsigned char *    const outbuf,
+              unsigned int *     const rowSizeP) {
+/*----------------------------------------------------------------------------
+  Analogous to format1BpsRow().
+-----------------------------------------------------------------------------*/
+    unsigned char (* const ob)[4] = (unsigned char (*)[4]) outbuf;
+
+    int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;  /* initial value */
+    
+    for (col=0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            sampleToBytes4(ob[bufferCursor++], tuplerow[col][plane]);
+    }
+
+    *rowSizeP = pamP->width * 4 * pamP->depth;
+}
+
+
+
+void
+pnm_formatpamrow(const struct pam * const pamP,
+                 const tuple *      const tuplerow,
+                 unsigned char *    const outbuf,
+                 unsigned int *     const rowSizeP) {
+/*----------------------------------------------------------------------------
+   Create the image of a row in the raster of a raw (not plain) format
+   Netpbm image, as described by *pamP and tuplerow[].  Put the image
+   at *outbuf.
+
+   'outbuf' must be the address of space allocated with pnm_allocrowimage().
+   
+   We return as *rowSizeP the number of bytes in the row image.
+-----------------------------------------------------------------------------*/
+    if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE)
+        formatPbmRow(pamP, tuplerow, outbuf, rowSizeP);
+    else {
+        switch(pamP->bytes_per_sample){
+        case 1: format1BpsRow(pamP, tuplerow, outbuf, rowSizeP); break;
+        case 2: format2BpsRow(pamP, tuplerow, outbuf, rowSizeP); break;
+        case 3: format3BpsRow(pamP, tuplerow, outbuf, rowSizeP); break;
+        case 4: format4BpsRow(pamP, tuplerow, outbuf, rowSizeP); break;
+        default:
+            pm_error("invalid bytes per sample passed to "
+                     "pnm_formatpamrow(): %u",  pamP->bytes_per_sample);
+        }
+    }
+}
+
+
+
+static void
+writePamRawRow(const struct pam * const pamP,
+               const tuple *      const tuplerow,
+               unsigned int       const count) {
+/*----------------------------------------------------------------------------
+   Write mutiple ('count') copies of the same row ('tuplerow') to the file,
+   in raw (not plain) format.
+-----------------------------------------------------------------------------*/
+    unsigned int rowImageSize;
+
+    unsigned char * outbuf;  /* malloc'ed */
+    unsigned int i;
+
+    outbuf = pnm_allocrowimage(pamP);
+
+    pnm_formatpamrow(pamP, tuplerow, outbuf, &rowImageSize);
+
+    for (i = 0; i < count; ++i) {
+        size_t bytesWritten;
+
+        bytesWritten = fwrite(outbuf, 1, rowImageSize, pamP->file);
+        if (bytesWritten != rowImageSize)
+            pm_error("fwrite() failed to write an image row to the file.  "
+                     "errno=%d (%s)", errno, strerror(errno));
+    }
+    pnm_freerowimage(outbuf);
+}
+
+
+
+void 
+pnm_writepamrow(const struct pam * const pamP, 
+                const tuple *      const tuplerow) {
+
+    /* For speed, we don't check any of the inputs for consistency 
+       here (unless it's necessary to avoid crashing).  Any consistency
+       checking should have been done by a prior call to 
+       pnm_writepaminit().
+    */
+    
+    if (pm_plain_output || pamP->plainformat) {
+        switch (PAM_FORMAT_TYPE(pamP->format)) {
+        case PBM_TYPE:
+            writePamPlainPbmRow(pamP, tuplerow);
+            break;
+        case PGM_TYPE:
+        case PPM_TYPE:
+            writePamPlainRow(pamP, tuplerow);
+            break;
+        case PAM_TYPE:
+            /* pm_plain_output is impossible here due to assumption stated
+               above about pnm_writepaminit() having checked it.  The
+               pamP->plainformat is meaningless for PAM.
+            */
+            writePamRawRow(pamP, tuplerow, 1);
+            break;
+        default:
+            pm_error("Invalid 'format' value %u in pam structure", 
+                     pamP->format);
+        }
+    } else
+        writePamRawRow(pamP, tuplerow, 1);
+}
+
+
+
+void
+pnm_writepamrowmult(const struct pam * const pamP, 
+                    const tuple *      const tuplerow,
+                    unsigned int       const count) {
+/*----------------------------------------------------------------------------
+   Write mutiple ('count') copies of the same row ('tuplerow') to the file.
+-----------------------------------------------------------------------------*/
+   if (pm_plain_output || pamP->plainformat) {
+       unsigned int i;
+       for (i = 0; i < count; ++i)
+           pnm_writepamrow(pamP, tuplerow);
+   } else
+       /* Simple common case - use fastpath */
+       writePamRawRow(pamP, tuplerow, count);
+}
+
+
+
+void 
+pnm_writepam(struct pam * const pamP, 
+             tuple **     const tuplearray) {
+
+    int row;
+
+    pnm_writepaminit(pamP);
+    
+    for (row = 0; row < pamP->height; ++row) 
+        pnm_writepamrow(pamP, tuplearray[row]);
+}
diff --git a/lib/libpbm.h b/lib/libpbm.h
new file mode 100644
index 00000000..827c9d7e
--- /dev/null
+++ b/lib/libpbm.h
@@ -0,0 +1,12 @@
+/* This is the intra-libnetpbm interface header file for libpbm*.c
+*/
+
+#ifndef LIBPBM_H_INCLUDED
+#define LIBPBM_H_INCLUDED
+
+void
+pbm_readpbminitrest(FILE * file,
+                    int  * colsP,
+                    int *  rowsP);
+
+#endif
diff --git a/lib/libpbm1.c b/lib/libpbm1.c
new file mode 100644
index 00000000..8dd491a7
--- /dev/null
+++ b/lib/libpbm1.c
@@ -0,0 +1,62 @@
+/* libpbm1.c - pbm utility library part 1
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* See libpm.c for the complicated explanation of this 32/64 bit file
+   offset stuff.
+*/
+#define _FILE_OFFSET_BITS 64
+#define _LARGE_FILES  
+
+#include <stdio.h>
+#include "pbm.h"
+#include "libpbm.h"
+#include "shhopt.h"
+
+void
+pbm_init(int *argcP, char *argv[]) {
+    pm_proginit(argcP, argv);
+}
+
+
+
+void
+pbm_nextimage(FILE *file, int * const eofP) {
+    pm_nextimage(file, eofP);
+}
+
+
+
+void
+pbm_check(FILE * file, const enum pm_check_type check_type, 
+          const int format, const int cols, const int rows,
+          enum pm_check_code * const retval_p) {
+
+    if (rows < 0)
+        pm_error("Invalid number of rows passed to pbm_check(): %d", rows);
+    if (cols < 0)
+        pm_error("Invalid number of columns passed to pbm_check(): %d", cols);
+    
+    if (check_type != PM_CHECK_BASIC) {
+        if (retval_p) *retval_p = PM_CHECK_UNKNOWN_TYPE;
+    } else if (format != RPBM_FORMAT) {
+        if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
+    } else {        
+        pm_filepos const bytes_per_row = (cols+7)/8;
+        pm_filepos const need_raster_size = rows * bytes_per_row;
+#ifdef LARGEFILEDEBUG
+        pm_message("pm_filepos passed to pm_check() is %u bytes",
+                   sizeof(pm_filepos));
+#endif
+        pm_check(file, check_type, need_raster_size, retval_p);
+    }
+}
+
diff --git a/lib/libpbm2.c b/lib/libpbm2.c
new file mode 100644
index 00000000..d04328ef
--- /dev/null
+++ b/lib/libpbm2.c
@@ -0,0 +1,180 @@
+/* libpbm2.c - pbm utility library part 2
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+#include "libpbm.h"
+#include "fileio.h"
+
+static bit 
+getbit (FILE * const file) {
+    char ch;
+
+    do {
+        ch = pm_getc( file );
+    } while ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' );
+
+    if ( ch != '0' && ch != '1' )
+        pm_error( "junk in file where bits should be" );
+    
+    return ( ch == '1' ) ? 1 : 0;
+}
+
+
+
+void
+pbm_readpbminitrest( file, colsP, rowsP )
+    FILE* file;
+    int* colsP;
+    int* rowsP;
+    {
+    /* Read size. */
+    *colsP = (int)pm_getuint( file );
+    *rowsP = (int)pm_getuint( file );
+
+    /* *colsP and *rowsP really should be unsigned int, but they come
+       from the time before unsigned ints (or at least from a person
+       trained in that tradition), so they are int.  We could simply
+       consider negative numbers to mean values > INT_MAX/2 and much
+       code would just automatically work.  But some code would fail
+       miserably.  So we consider values that won't fit in an int to
+       be unprocessable.
+    */
+    if (*colsP < 0)
+        pm_error("Number of columns in header is too large.");
+    if (*rowsP < 0)
+        pm_error("Number of columns in header is too large.");
+    }
+
+void
+pbm_readpbminit( file, colsP, rowsP, formatP )
+    FILE* file;
+    int* colsP;
+    int* rowsP;
+    int* formatP;
+    {
+    /* Check magic number. */
+    *formatP = pm_readmagicnumber( file );
+    switch ( PBM_FORMAT_TYPE(*formatP) )
+    {
+        case PBM_TYPE:
+    pbm_readpbminitrest( file, colsP, rowsP );
+    break;
+
+    default:
+    pm_error( "bad magic number - not a pbm file" );
+    }
+    }
+
+void
+pbm_readpbmrow( file, bitrow, cols, format )
+    FILE* file;
+    bit* bitrow;
+    int cols, format;
+    {
+    register int col, bitshift;
+    register bit* bP;
+
+    switch ( format )
+    {
+    case PBM_FORMAT:
+    for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
+        *bP = getbit( file );
+    break;
+
+    case RPBM_FORMAT: {
+        register unsigned char item;
+        bitshift = -1;  item = 0;  /* item's value is meaningless here */
+        for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
+          {
+              if ( bitshift == -1 )
+                {
+                    item = pm_getrawbyte( file );
+                    bitshift = 7;
+                }
+              *bP = ( item >> bitshift ) & 1;
+              --bitshift;
+          }
+    }
+    break;
+
+    default:
+    pm_error( "can't happen" );
+    }
+    }
+
+
+
+void
+pbm_readpbmrow_packed(FILE *          const file, 
+                      unsigned char * const packed_bits,
+                      int             const cols, 
+                      int             const format) {
+
+    switch(format) {
+    case PBM_FORMAT: {
+        unsigned int col;
+        unsigned int byteIndex;
+
+        /* We first clear the return buffer, then set ones where needed */
+        for (byteIndex = 0; byteIndex < pbm_packed_bytes(cols); ++byteIndex)
+            packed_bits[byteIndex] = 0x00;
+
+        for (col = 0; col < cols; ++col) {
+            unsigned char mask;
+            mask = getbit(file) << (7 - col % 8);
+            packed_bits[col / 8] |= mask;
+        }
+    }
+    break;
+
+    case RPBM_FORMAT: {
+        int bytes_read;
+        bytes_read = fread(packed_bits, 1, pbm_packed_bytes(cols), file);
+             
+        if (bytes_read < pbm_packed_bytes(cols)) {
+            if (feof(file)) 
+                if (bytes_read == 0) 
+                    pm_error("Attempt to read a raw PBM image row, but "
+                             "no more rows left in file.");
+                else
+                    pm_error("EOF in the middle of a raw PBM row.");
+            else 
+                pm_error("I/O error reading raw PBM row");
+        }
+    }
+    break;
+    
+    default:
+        pm_error( "Internal error in pbm_readpbmrow_packed." );
+    }
+}
+
+
+
+bit**
+pbm_readpbm( file, colsP, rowsP )
+    FILE* file;
+    int* colsP;
+    int* rowsP;
+    {
+    register bit** bits;
+    int format, row;
+
+    pbm_readpbminit( file, colsP, rowsP, &format );
+
+    bits = pbm_allocarray( *colsP, *rowsP );
+
+    for ( row = 0; row < *rowsP; ++row )
+        pbm_readpbmrow( file, bits[row], *colsP, format );
+
+    return bits;
+    }
diff --git a/lib/libpbm3.c b/lib/libpbm3.c
new file mode 100644
index 00000000..f1717f37
--- /dev/null
+++ b/lib/libpbm3.c
@@ -0,0 +1,282 @@
+/* libpbm3.c - pbm utility library part 3
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+#include "libpbm.h"
+#include "bitreverse.h"
+
+#if defined(__GNUC__) && (__GNUC__ >=3) && (__GNUC_MINOR__ >=1) && defined (__SSE__)
+/* intel MMX-SSE enhancement for pbm_writepbmowraw() */
+/* GCC only.  Turn on with -msse */
+
+#define HAVE_MMX_SSE 1
+#else
+#define HAVE_MMX_SSE 0
+#endif
+
+
+void
+pbm_writepbminit(FILE * const fileP, 
+                 int    const cols, 
+                 int    const rows, 
+                 int    const forceplain) {
+
+    if (!forceplain && !pm_plain_output) {
+        fprintf(fileP, "%c%c\n%d %d\n", PBM_MAGIC1, RPBM_MAGIC2, cols, rows);
+#ifdef VMS
+        set_outfile_binary();
+#endif
+    } else
+        fprintf(fileP, "%c%c\n%d %d\n", PBM_MAGIC1, PBM_MAGIC2, cols, rows);
+}
+
+
+
+static void
+writePackedRawRow(FILE *                const fileP,
+                  const unsigned char * const packed_bits,
+                  int                   const cols) {
+
+    int bytesWritten;
+    bytesWritten = fwrite(packed_bits, 1, pbm_packed_bytes(cols), fileP);
+    if (bytesWritten < pbm_packed_bytes(cols)) 
+        pm_error("I/O error writing packed row to raw PBM file.");
+} 
+
+
+
+static void
+packBitsWithMmxSse(FILE *          const fileP,
+                   const bit *     const bitrow,
+                   unsigned char * const packedBits,
+                   int             const cols,
+                   int *           const nextColP) {
+/*----------------------------------------------------------------------------
+   Pack the bits of bitrow[] into bytes at 'packedBits'.  Going left to right,
+   stop when there aren't enough bits left to fill a whole byte.  Return
+   as *nextColP the number of the next column after the rightmost one we
+   packed.
+
+   Use the Pentium MMX and SSE facilities to pack the bits quickly, but
+   perform the exact same function as the simpler packBitsGeneric().
+-----------------------------------------------------------------------------*/
+#if HAVE_MMX_SSE
+    /*
+      We use MMX/SSE facilities that operate on 8 bytes at once to pack
+      the bits quickly.
+    
+      We use 2 MMX registers (no SSE registers).
+    
+      The key machine instructions are:
+    
+    
+      PCMPEQB  Packed CoMPare EQual Byte
+    
+        Compares 8 bytes in parallel
+        Result is x00 if equal, xFF if unequal for each byte       
+    
+      PMOVMSKB Packed MOVe MaSK Byte 
+    
+        Result is a byte of the MSBs of 8 bytes
+        x00 xFF x00 xFF xFF xFF x00 x00 --> 01011100B = 0x5C     
+    
+    
+      EMMS     Empty MMx State
+    
+        Free MMX registers  
+    
+    
+      Here's a one-statement version of the code in our foor loop.  It's harder 
+      to read, but if we find out this generates more efficient code, we could 
+      use this.
+    
+        packedBits[col/8] 
+          = bitreverse [ ~ (unsigned char) __builtin_ia32_pmovmskb (
+            __builtin_ia32_pcmpeqb ( *(v8qi*) (&bitrow[col]), *(v8qi*) &zero64)
+            ) ];
+    */
+
+    typedef int v8qi __attribute__ ((mode(V8QI)));
+    typedef int di __attribute__ ((mode(DI)));
+    int col;
+
+    di const zero64 = 0;        /* to clear with PXOR */
+
+    for (col = 0; col < cols-7; col += 8) {
+        v8qi const compare =
+            __builtin_ia32_pcmpeqb(*(v8qi*) (&bitrow[col]), *(v8qi*) &zero64);
+        unsigned char const backwardWhiteMask = (unsigned char)
+            __builtin_ia32_pmovmskb(compare);
+        unsigned char const backwardBlackMask = ~backwardWhiteMask;
+        unsigned char const blackMask = bitreverse[backwardBlackMask];
+
+        packedBits[col/8] = blackMask;
+    }
+    *nextColP = col;
+
+    __builtin_ia32_emms();
+
+#else
+    if (bitreverse == bitreverse) {}; /* avoid unused vbl compiler warning */
+#endif
+}
+
+
+
+static void
+packBitsGeneric(FILE *          const fileP,
+                const bit *     const bitrow,
+                unsigned char * const packedBits,
+                int             const cols,
+                int *           const nextColP) {
+/*----------------------------------------------------------------------------
+   Pack the bits of bitrow[] into byts at 'packedBits'.  Going left to right,
+   stop when there aren't enough bits left to fill a whole byte.  Return
+   as *nextColP the number of the next column after the rightmost one we
+   packed.
+
+   Don't use any special CPU facilities to do the packing.
+-----------------------------------------------------------------------------*/
+    int col;
+
+    #define iszero(x) ((x) == 0 ? 0 : 1)
+
+    for (col = 0; col < cols-7; col += 8)
+        packedBits[col/8] = (
+            iszero(bitrow[col+0]) << 7 |
+            iszero(bitrow[col+1]) << 6 |
+            iszero(bitrow[col+2]) << 5 |
+            iszero(bitrow[col+3]) << 4 |
+            iszero(bitrow[col+4]) << 3 |
+            iszero(bitrow[col+5]) << 2 |
+            iszero(bitrow[col+6]) << 1 |
+            iszero(bitrow[col+7]) << 0
+            );
+    *nextColP = col;
+}
+
+
+
+static void
+writePbmRowRaw(FILE *      const fileP,
+               const bit * const bitrow,
+               int         const cols) {
+
+    int nextCol;
+
+    unsigned char * const packedBits = pbm_allocrow_packed(cols);
+
+    if (HAVE_MMX_SSE)
+        packBitsWithMmxSse(fileP, bitrow, packedBits, cols, &nextCol);
+    else 
+        packBitsGeneric(fileP, bitrow, packedBits, cols, &nextCol);
+
+    /* routine for partial byte at the end of packed_bits[]
+       Prior to addition of the above enhancement,
+       this method was used for the entire process
+     */                   
+
+    if (cols % 8 > 0) {
+        int col;
+        int bitshift;
+        unsigned char item;
+
+        bitshift = 7;  /* initial value */
+        item = 0;      /* initial value */
+        for (col = nextCol; col < cols; ++col, --bitshift )
+            if (bitrow[col] !=0)
+                item |= 1 << bitshift
+                ;
+        
+        packedBits[col/8] = item;
+    }
+    
+    writePackedRawRow(fileP, packedBits, cols);
+    
+    pbm_freerow_packed(packedBits);
+}
+
+
+
+static void
+writePbmRowPlain(FILE * const fileP,
+                 bit *  const bitrow, 
+                 int    const cols) {
+    
+    int col, charcount;
+
+    charcount = 0;
+    for (col = 0; col < cols; ++col) {
+        if (charcount >= 70) {
+            putc('\n', fileP);
+            charcount = 0;
+        }
+        putc(bitrow[col] ? '1' : '0', fileP);
+        ++charcount;
+    }
+    putc('\n', fileP);
+}
+
+
+
+void
+pbm_writepbmrow(FILE * const fileP, 
+                bit *  const bitrow, 
+                int    const cols, 
+                int    const forceplain) {
+
+    if (!forceplain && !pm_plain_output)
+        writePbmRowRaw(fileP, bitrow, cols);
+    else
+        writePbmRowPlain(fileP, bitrow, cols);
+}
+
+
+
+void
+pbm_writepbmrow_packed(FILE *                const fileP, 
+                       const unsigned char * const packed_bits,
+                       int                   const cols, 
+                       int                   const forceplain) {
+
+    if (!forceplain && !pm_plain_output)
+        writePackedRawRow(fileP, packed_bits, cols);
+    else {
+        bit *bitrow;
+        int col;
+
+        bitrow = pbm_allocrow(cols);
+
+        for (col = 0; col < cols; ++col) 
+            bitrow[col] = 
+                packed_bits[col/8] & (0x80 >> (col%8)) ? PBM_BLACK : PBM_WHITE;
+        writePbmRowPlain(fileP, bitrow, cols);
+        pbm_freerow(bitrow);
+    }
+}
+
+
+
+void
+pbm_writepbm(FILE * const fileP, 
+             bit ** const bits, 
+             int    const cols, 
+             int    const rows, 
+             int    const forceplain) {
+
+    int row;
+
+    pbm_writepbminit(fileP, cols, rows, forceplain);
+    
+    for (row = 0; row < rows; ++row)
+        pbm_writepbmrow(fileP, bits[row], cols, forceplain);
+}
diff --git a/lib/libpbmfont.c b/lib/libpbmfont.c
new file mode 100644
index 00000000..c7b5a355
--- /dev/null
+++ b/lib/libpbmfont.c
@@ -0,0 +1,1136 @@
+/* libpbm5.c - pbm utility library part 5
+**
+** Font routines.
+**
+** BDF font code Copyright 1993 by George Phillips.
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "nstring.h"
+#include "pbm.h"
+#include "pbmfont.h"
+#include "mallocvar.h"
+
+static unsigned int const firstCodePoint = 32;
+    /* This is the code point of the first character in a pbmfont font.
+       In ASCII, it is a space.
+    */
+
+static unsigned int const nCharsInFont = 96;
+    /* The number of characters in a pbmfont font.  A pbmfont font defines
+       characters at position 32 (ASCII space) through 127, so that's 96.
+    */
+
+/* The default font, packed in hex so this source file doesn't get huge.
+** You can replace this with your own font using pbm_dumpfont().
+*/
+#define DEFAULTFONT_ROWS 155
+#define DEFAULTFONT_COLS 112
+static unsigned long defaultfont_bits[DEFAULTFONT_ROWS][(DEFAULTFONT_COLS+31)/32] = {
+    {0x00000000L,0x20000c00L,0x10000000L,0x00000000L},
+    {0xc600a000L,0x42000810L,0x00000002L,0x00000063L},
+    {0x6c00a000L,0x45000810L,0x00000002L,0x00000036L},
+    {0x6c00a000L,0x88800808L,0xf2e1dee2L,0x00000036L},
+    {0x54000000L,0x80000800L,0x11122442L,0x0000002aL},
+    {0x54000001L,0x00000800L,0x11122442L,0x0000002aL},
+    {0x54000001L,0x00000800L,0x11122282L,0x0000002aL},
+    {0x44000102L,0x00000800L,0x11122382L,0x00000022L},
+    {0xee000102L,0x00000800L,0x11e1e102L,0x00000077L},
+    {0x00000204L,0x00000800L,0x11002102L,0x00000000L},
+    {0x00000000L,0x00000c00L,0x11002102L,0x00000000L},
+    {0x00000000L,0x003f8000L,0xe3807600L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x02000080L,0x00040000L,0x00120000L,0x00000001L},
+    {0x04000082L,0x828e1838L,0x20210100L,0x00000002L},
+    {0x04000082L,0x82912448L,0x20210100L,0x00000002L},
+    {0x08000082L,0x8fd01940L,0x404087c2L,0x00000004L},
+    {0x08000080L,0x050c0622L,0x00408102L,0x00000004L},
+    {0x10000080L,0x05061874L,0x0040828fL,0x00008008L},
+    {0x10000080L,0x1f912688L,0x00408002L,0x00000008L},
+    {0x20000000L,0x0a11098cL,0x00408002L,0x00000010L},
+    {0x20000080L,0x0a0e0672L,0x00210000L,0x00000010L},
+    {0x40000000L,0x00040000L,0x00210000L,0x00000020L},
+    {0x00000000L,0x00000000L,0x00120000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x004e0838L,0x7023e1cfL,0x00008000L},
+    {0x00000000L,0x00913844L,0x88620208L,0x00008000L},
+    {0x08000000L,0x00910844L,0x08a20401L,0x00000004L},
+    {0x10000000L,0x01110844L,0x08a20401L,0x00000008L},
+    {0x20000000L,0x01110808L,0x3123c781L,0x00000010L},
+    {0x400003e0L,0x02110810L,0x0a202441L,0x00000020L},
+    {0x20000000L,0x02110820L,0x0bf02442L,0x00000010L},
+    {0x10008000L,0x04110844L,0x88242442L,0x00000008L},
+    {0x08008002L,0x040e3e7cL,0x7073c382L,0x00000004L},
+    {0x00010000L,0x08000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x0000e1c0L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00011220L,0x00000000L,0x70e38f87L,0x00000000L},
+    {0x20011220L,0x00020020L,0x89108448L,0x00008010L},
+    {0x10011220L,0x00040010L,0x09314448L,0x00008008L},
+    {0x0800e221L,0x02083e08L,0x11514788L,0x00000004L},
+    {0x040111e0L,0x00100004L,0x2153e448L,0x00000002L},
+    {0x08011020L,0x00083e08L,0x213a2448L,0x00008004L},
+    {0x10011040L,0x02040010L,0x01022448L,0x00008008L},
+    {0x2000e381L,0x02020020L,0x20e77f87L,0x00000010L},
+    {0x00000000L,0x04000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x3803e7efL,0xc73bbe3dL,0xdb863ce7L,0x0000001cL},
+    {0x44011224L,0x48910808L,0x91036648L,0x00008022L},
+    {0x4c011285L,0x48910808L,0xa1036648L,0x00008026L},
+    {0x54011387L,0x081f0808L,0xc102a548L,0x0000802aL},
+    {0x54011285L,0x09910808L,0xe102a548L,0x0000802aL},
+    {0x4e011204L,0x08910848L,0x9112a4c8L,0x00008027L},
+    {0x40011224L,0x08910848L,0x891224c8L,0x00008020L},
+    {0x3803e7efL,0x073bbe31L,0xcff77e47L,0x0000001cL},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000003L,0x00000000L},
+    {0x0003e1cfL,0x87bff7efL,0xdfbf77c2L,0x00000000L},
+    {0x00013224L,0x48a4a244L,0x89122442L,0x00000000L},
+    {0x00011224L,0x4824a244L,0xa8a14482L,0x00000000L},
+    {0x00013227L,0x8e04226cL,0xa8414102L,0x00000000L},
+    {0x0001e224L,0x83842228L,0xa8a08102L,0x00000000L},
+    {0x00010224L,0x40842228L,0xd8a08242L,0x00000000L},
+    {0x00010224L,0x48843638L,0x51108442L,0x00000000L},
+    {0x0003c1ceL,0x6f1f1c10L,0x53b9c7c2L,0x00000000L},
+    {0x00000060L,0x00000000L,0x00000002L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000003L,0x00000000L},
+    {0xfe000000L,0x00000000L,0x00000000L,0x0000007fL},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00010180L,0x000000c0L,0x003001c0L,0x00000000L},
+    {0x08008081L,0x00040040L,0x00100200L,0x00000004L},
+    {0x10008082L,0x80040040L,0x00100200L,0x00000008L},
+    {0x10004084L,0x40023c78L,0x70f1c7c7L,0x00004008L},
+    {0x10004080L,0x00000244L,0x89122208L,0x00008008L},
+    {0x20002080L,0x00001e44L,0x8113e208L,0x00008010L},
+    {0x10002080L,0x00002244L,0x81120208L,0x00008008L},
+    {0x10001080L,0x00002244L,0x89122208L,0x00008008L},
+    {0x10001080L,0x00001db8L,0x70e9c787L,0x00008008L},
+    {0x10000880L,0x00000000L,0x00000000L,0x00008008L},
+    {0x08000180L,0x00000000L,0x00000000L,0x00008004L},
+    {0x00000000L,0x1fc00000L,0x00000007L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00030080L,0x981c0000L,0x00000000L,0x00000000L},
+    {0x20010000L,0x08040000L,0x00000000L,0x00000010L},
+    {0x10010000L,0x08040000L,0x00000000L,0x00000008L},
+    {0x10016387L,0x898474b8L,0x72e1d5c7L,0x00000008L},
+    {0x10019080L,0x8a042a64L,0x89122208L,0x00008008L},
+    {0x08011080L,0x8c042a44L,0x89122207L,0x00000004L},
+    {0x10011080L,0x8a042a44L,0x89122200L,0x00008008L},
+    {0x10011080L,0x89042a44L,0x89122208L,0x00008008L},
+    {0x1003bbe0L,0x98dfebe6L,0x71e1e787L,0x00000008L},
+    {0x10000000L,0x80000000L,0x01002000L,0x00000008L},
+    {0x20000000L,0x80000000L,0x01002000L,0x00000010L},
+    {0x00000007L,0x00000000L,0x03807000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00008000L,0x00000000L,0x10410000L,0x00000000L},
+    {0x00008000L,0x00000000L,0x20408000L,0x00000000L},
+    {0x0001f66eL,0xfdfbf77cL,0x20408000L,0x00000000L},
+    {0x24008224L,0x488a2248L,0x20408240L,0x00000012L},
+    {0x54008224L,0x4a842210L,0x40404540L,0x0000002aL},
+    {0x48008222L,0x8a8a1420L,0x20408480L,0x00000024L},
+    {0x00008a23L,0x85111c44L,0x20408000L,0x00000000L},
+    {0x000071d1L,0x0531887cL,0x20408000L,0x00000000L},
+    {0x00000000L,0x00000800L,0x20408000L,0x00000000L},
+    {0x00000000L,0x00000800L,0x10410000L,0x00000000L},
+    {0x00000000L,0x00003000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x00000000L,0x00000000L,0x00000000L},
+    {0x00000000L,0x20000c00L,0x10000000L,0x00000000L},
+    {0xc600a000L,0x42000810L,0x00000002L,0x00000063L},
+    {0x6c00a000L,0x45000810L,0x00000002L,0x00000036L},
+    {0x6c00a000L,0x88800808L,0xf2e1dee2L,0x00000036L},
+    {0x54000000L,0x80000800L,0x11122442L,0x0000002aL},
+    {0x54000001L,0x00000800L,0x11122442L,0x0000002aL},
+    {0x54000001L,0x00000800L,0x11122282L,0x0000002aL},
+    {0x44000102L,0x00000800L,0x11122382L,0x00000022L},
+    {0xee000102L,0x00000800L,0x11e1e102L,0x00000077L},
+    {0x00000204L,0x00000800L,0x11002102L,0x00000000L},
+    {0x00000000L,0x00000c00L,0x11002102L,0x00000000L},
+    {0x00000000L,0x003f8000L,0xe3807600L,0x00000000L}
+    };
+
+/* A default BDF font */
+/* Not as nicely compacted as the PBM font, oh well. */
+
+static struct glyph _g[190] = {
+ { 1, 1, 0, 0, 3, "\0" },
+ { 1, 9, 1, 0, 3, "\1\1\1\1\1\1\1\0\1" },
+ { 3, 3, 1, 6, 5, "\1\0\1\1\0\1\1\0\1" },
+ { 5, 8, 0, 0, 6, "\0\1\0\1\0\0\1\0\1\0\1\1\1\1\1\0\1\0\1\0\0\1\0\1\0\1\1\1\1\1\0\1\0\1\0\0\1\0\1\0" },
+ { 5, 11, 0, -1, 6, "\0\0\1\0\0\0\1\1\1\0\1\0\1\0\1\1\0\1\0\0\0\1\1\0\0\0\0\1\1\0\0\0\1\0\1\0\0\1\0\1\1\0\1\0\1\0\1\1\1\0\0\0\1\0\0" },
+ { 8, 9, 0, 0, 9, "\0\1\1\0\0\0\1\1\1\0\0\1\1\1\1\0\1\0\0\1\0\1\0\0\0\1\1\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\1\0\1\1\0\0\0\1\0\1\0\0\1\0\1\0\0\1\0\0\1\0\1\0\0\0\1\1\0" },
+ { 9, 9, 0, 0, 10, "\0\0\0\1\1\0\0\0\0\0\0\1\0\0\1\0\0\0\0\0\1\0\0\1\0\0\0\0\0\0\1\1\0\1\1\1\0\1\1\1\1\0\0\1\0\1\1\0\0\1\1\1\0\0\1\0\0\0\0\1\0\0\0\1\1\0\0\1\1\1\0\1\0\1\1\1\1\0\1\1\0" },
+ { 2, 3, 1, 6, 4, "\1\1\0\1\1\0" },
+ { 3, 12, 1, -3, 5, "\0\0\1\0\1\0\0\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\0\1\0\0\1\0\0\0\1" },
+ { 3, 12, 0, -3, 5, "\1\0\0\0\1\0\0\1\0\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\0\0\1\0\1\0\0" },
+ { 5, 5, 0, 4, 6, "\0\0\1\0\0\1\0\1\0\1\0\1\1\1\0\1\0\1\0\1\0\0\1\0\0" },
+ { 5, 5, 1, 1, 7, "\0\0\1\0\0\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\0\1\0\0" },
+ { 2, 3, 0, -2, 3, "\0\1\0\1\1\0" },
+ { 5, 1, 1, 3, 8, "\1\1\1\1\1" },
+ { 1, 1, 1, 0, 3, "\1" },
+ { 3, 9, 0, 0, 3, "\0\0\1\0\0\1\0\0\1\0\1\0\0\1\0\0\1\0\1\0\0\1\0\0\1\0\0" },
+ { 5, 9, 0, 0, 6, "\0\1\1\1\0\1\1\0\1\1\1\0\0\0\1\1\0\0\0\1\1\0\0\0\1\1\0\0\0\1\1\0\0\0\1\1\1\0\1\1\0\1\1\1\0" },
+ { 4, 9, 0, 0, 6, "\0\0\1\0\0\1\1\0\1\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\1\1\1" },
+ { 5, 9, 0, 0, 6, "\0\1\1\1\0\1\0\0\0\1\0\0\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\1\1\1\1\1" },
+ { 5, 9, 0, 0, 6, "\0\1\1\1\0\1\0\0\0\1\0\0\0\0\1\0\0\0\1\0\0\1\1\1\0\0\0\0\0\1\0\0\0\0\1\1\0\0\0\1\0\1\1\1\0" },
+ { 5, 9, 0, 0, 6, "\0\0\0\1\0\0\0\1\1\0\0\0\1\1\0\0\1\0\1\0\0\1\0\1\0\1\0\0\1\0\1\1\1\1\1\0\0\0\1\0\0\0\0\1\0" },
+ { 5, 9, 0, 0, 6, "\0\0\1\1\1\0\1\0\0\0\0\1\0\0\0\0\1\1\1\0\0\0\0\1\1\0\0\0\0\1\0\0\0\0\1\1\0\0\1\1\0\1\1\1\0" },
+ { 5, 9, 0, 0, 6, "\0\0\0\1\1\0\1\1\0\0\0\1\0\0\0\1\1\1\1\0\1\0\0\1\1\1\0\0\0\1\1\0\0\0\1\1\1\0\0\1\0\1\1\1\0" },
+ { 5, 9, 0, 0, 6, "\1\1\1\1\1\1\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\0\0\1\0\0\0" },
+ { 5, 9, 0, 0, 6, "\0\1\1\1\0\1\0\0\0\1\1\0\0\0\1\1\1\0\0\1\0\1\1\1\0\1\0\0\1\1\1\0\0\0\1\1\0\0\0\1\0\1\1\1\0" },
+ { 5, 9, 0, 0, 6, "\0\1\1\1\0\1\0\0\1\1\1\0\0\0\1\1\0\0\0\1\1\1\0\0\1\0\1\1\1\1\0\0\0\1\0\0\0\1\1\0\1\1\0\0\0" },
+ { 1, 6, 1, 0, 3, "\1\0\0\0\0\1" },
+ { 2, 8, 0, -2, 3, "\0\1\0\0\0\0\0\0\0\0\0\1\0\1\1\0" },
+ { 6, 5, 0, 1, 8, "\0\0\0\0\1\1\0\0\1\1\0\0\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1" },
+ { 5, 3, 1, 2, 7, "\1\1\1\1\1\0\0\0\0\0\1\1\1\1\1" },
+ { 6, 5, 1, 1, 8, "\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1\0\0\1\1\0\0\1\1\0\0\0\0" },
+ { 4, 9, 0, 0, 5, "\0\1\1\0\1\0\0\1\0\0\0\1\0\0\1\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\0\0\1\0\0" },
+ { 10, 11, 1, -2, 11, "\0\0\0\0\1\1\1\1\0\0\0\0\1\1\0\0\0\0\1\0\0\1\1\0\0\0\0\0\0\1\0\1\0\0\1\1\0\1\0\1\1\0\0\1\0\0\1\0\0\1\1\0\1\0\0\0\1\0\0\1\1\0\1\0\0\1\0\0\1\0\1\0\1\0\0\1\0\0\1\0\1\0\0\1\1\0\1\1\0\0\0\1\0\0\0\0\0\0\0\0\0\0\1\1\1\1\1\0\0\0" },
+ { 9, 9, 0, 0, 9, "\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\0\1\1\1\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\1\1\0\0\0\1\1\1" },
+ { 7, 9, 0, 0, 8, "\1\1\1\1\1\1\0\0\1\0\0\0\1\1\0\1\0\0\0\0\1\0\1\0\0\0\1\1\0\1\1\1\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\1\0\1\0\0\0\1\1\1\1\1\1\1\1\0" },
+ { 7, 9, 0, 0, 8, "\0\0\1\1\1\0\1\0\1\1\0\0\1\1\0\1\0\0\0\0\1\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\1\0\1\1\0\0\1\1\0\0\1\1\1\1\0" },
+ { 8, 9, 0, 0, 9, "\1\1\1\1\1\1\0\0\0\1\0\0\0\1\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\0\0\0\1\1\0\1\1\1\1\1\1\0\0" },
+ { 7, 9, 0, 0, 8, "\1\1\1\1\1\1\1\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\1\0\0\1\0\0\0\0\0\0\1\0\0\0\0\1\1\1\1\1\1\1\1" },
+ { 7, 9, 0, 0, 8, "\1\1\1\1\1\1\1\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\1\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\1\1\1\1\0\0\0" },
+ { 8, 9, 0, 0, 9, "\0\0\1\1\1\0\1\0\0\1\1\0\0\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\0\1\0\0\0\0\1\1\1\1\0\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 8, 9, 0, 0, 9, "\1\1\1\0\0\1\1\1\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\1\1\1\1\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\1\1\1\0\0\1\1\1" },
+ { 3, 9, 0, 0, 4, "\1\1\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 4, 9, 0, 0, 4, "\0\1\1\1\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\1\0\1\0\1\1\0\0" },
+ { 8, 9, 0, 0, 8, "\1\1\1\0\1\1\1\0\0\1\0\0\0\1\0\0\0\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\1\1\1\0\0\0\0\0\1\0\1\1\0\0\0\0\1\0\0\1\1\0\0\0\1\0\0\0\1\1\0\1\1\1\0\0\1\1\1" },
+ { 6, 9, 0, 0, 7, "\1\1\1\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\1\1\1\1\1\1\1" },
+ { 11, 9, 0, 0, 11, "\1\1\0\0\0\0\0\0\0\1\1\0\1\1\0\0\0\0\0\1\1\0\0\1\1\0\0\0\0\0\1\1\0\0\1\0\1\0\0\0\1\0\1\0\0\1\0\1\0\0\0\1\0\1\0\0\1\0\0\1\0\1\0\0\1\0\0\1\0\0\1\0\1\0\0\1\0\0\1\0\0\0\1\0\0\0\1\0\1\1\1\0\0\1\0\0\1\1\1" },
+ { 9, 9, 0, 0, 9, "\1\1\0\0\0\0\1\1\1\0\1\1\0\0\0\0\1\0\0\1\1\0\0\0\0\1\0\0\1\0\1\0\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\0\1\0\1\0\0\1\0\0\0\0\1\1\0\1\1\1\0\0\0\0\1\0" },
+ { 8, 9, 0, 0, 9, "\0\0\1\1\1\1\0\0\0\1\1\0\0\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 7, 9, 0, 0, 7, "\1\1\1\1\1\1\0\0\1\0\0\0\1\1\0\1\0\0\0\0\1\0\1\0\0\0\1\1\0\1\1\1\1\1\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\1\1\1\0\0\0\0" },
+ { 8, 11, 0, -2, 9, "\0\0\1\1\1\1\0\0\0\1\1\0\0\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\0\0\1\1" },
+ { 8, 9, 0, 0, 8, "\1\1\1\1\1\1\0\0\0\1\0\0\0\1\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\1\1\0\0\1\1\1\1\1\0\0\0\1\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\1\0\1\1\1\0\0\0\1\1" },
+ { 6, 9, 0, 0, 7, "\0\1\1\1\0\1\1\0\0\0\1\1\1\0\0\0\0\1\0\1\1\0\0\0\0\0\1\1\1\0\0\0\0\0\1\1\1\0\0\0\0\1\1\1\0\0\1\1\1\0\1\1\1\0" },
+ { 7, 9, 0, 0, 7, "\1\1\1\1\1\1\1\1\0\0\1\0\0\1\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\1\1\1\0\0" },
+ { 8, 9, 0, 0, 8, "\1\1\1\0\0\1\1\1\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 9, 9, 0, 0, 9, "\1\1\1\0\0\0\1\1\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\1\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0" },
+ { 12, 9, 0, 0, 12, "\1\1\1\0\1\1\1\0\0\1\1\1\0\1\0\0\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\1\0\0\0\0\1\1\0\1\1\1\0\1\0\0\0\0\0\1\0\1\0\1\0\1\0\0\0\0\0\1\1\0\0\1\1\0\0\0\0\0\0\0\1\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\1\0\0\0" },
+ { 8, 9, 0, 0, 8, "\1\1\1\0\0\1\1\1\0\1\0\0\0\0\1\0\0\0\1\0\0\1\0\0\0\0\1\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\1\0\1\1\0\0\0\0\1\0\0\1\0\0\0\1\0\0\0\0\1\0\1\1\1\0\0\1\1\1" },
+ { 9, 9, 0, 0, 9, "\1\1\1\0\0\0\1\1\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\0\1\0\0\0\0\0\1\1\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\1\1\0\0\0" },
+ { 7, 9, 0, 0, 8, "\1\1\1\1\1\1\1\1\0\0\0\0\1\1\0\0\0\0\1\1\0\0\0\0\1\1\0\0\0\0\1\1\0\0\0\0\1\1\0\0\0\0\1\1\0\0\0\0\0\1\0\0\0\0\0\1\1\1\1\1\1\1\1" },
+ { 3, 12, 1, -3, 5, "\1\1\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\1\1" },
+ { 3, 9, 0, 0, 3, "\1\0\0\1\0\0\1\0\0\0\1\0\0\1\0\0\1\0\0\0\1\0\0\1\0\0\1" },
+ { 3, 12, 0, -3, 5, "\1\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\1\1\1" },
+ { 5, 5, 0, 4, 6, "\0\0\1\0\0\0\1\0\1\0\0\1\0\1\0\1\0\0\0\1\1\0\0\0\1" },
+ { 6, 1, 0, -3, 6, "\1\1\1\1\1\1" },
+ { 2, 3, 1, 6, 4, "\0\1\1\0\1\1" },
+ { 5, 6, 1, 0, 6, "\0\1\1\0\0\1\0\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\0\1" },
+ { 5, 9, 0, 0, 6, "\1\1\0\0\0\0\1\0\0\0\0\1\0\0\0\0\1\1\1\0\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\1\1\1\0" },
+ { 4, 6, 1, 0, 5, "\0\1\1\0\1\0\0\1\1\0\0\0\1\0\0\0\1\0\0\1\0\1\1\0" },
+ { 5, 9, 1, 0, 6, "\0\0\1\1\0\0\0\0\1\0\0\0\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\0\1" },
+ { 5, 6, 1, 0, 6, "\0\1\1\0\0\1\0\0\1\0\1\1\1\1\0\1\0\0\0\0\1\1\0\0\1\0\1\1\1\0" },
+ { 3, 9, 0, 0, 3, "\0\0\1\0\1\0\0\1\0\1\1\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0" },
+ { 5, 9, 1, -3, 6, "\0\1\1\1\1\1\0\0\1\0\1\0\0\1\0\1\1\1\0\0\0\1\0\0\0\0\1\1\1\0\1\0\0\0\1\1\0\0\0\1\0\1\1\1\0" },
+ { 6, 9, 0, 0, 6, "\1\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\1\1\1\0\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1\0\1\1" },
+ { 3, 9, 0, 0, 3, "\0\1\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 2, 12, 0, -3, 3, "\0\1\0\0\0\0\1\1\0\1\0\1\0\1\0\1\0\1\0\1\0\1\1\0" },
+ { 6, 9, 0, 0, 6, "\1\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\1\0\0\1\0\1\0\0\0\1\1\0\0\0\0\1\0\1\0\0\0\1\0\0\1\0\0\1\0\0\1\1" },
+ { 3, 9, 0, 0, 3, "\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 9, 6, 0, 0, 9, "\1\0\1\1\0\1\1\0\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1\0\1\1\0\1\1" },
+ { 6, 6, 0, 0, 6, "\1\0\1\1\0\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1\0\1\1" },
+ { 4, 6, 1, 0, 6, "\0\1\1\0\1\0\0\1\1\0\0\1\1\0\0\1\1\0\0\1\0\1\1\0" },
+ { 5, 9, 0, -3, 6, "\1\1\1\1\0\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\1\1\1\0\0\1\0\0\0\0\1\0\0\0\1\1\1\0\0" },
+ { 5, 9, 1, -3, 6, "\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\1\0\0\0\0\1\0\0\0\0\1\0\0\0\1\1\1" },
+ { 4, 6, 0, 0, 4, "\1\0\1\1\0\1\1\0\0\1\0\0\0\1\0\0\0\1\0\0\1\1\1\0" },
+ { 4, 6, 1, 0, 6, "\0\1\1\1\1\0\0\1\1\1\0\0\0\0\1\1\1\0\0\1\1\1\1\0" },
+ { 4, 7, 0, 0, 4, "\0\1\0\0\1\1\1\1\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\1\1" },
+ { 6, 6, 0, 0, 6, "\1\1\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\0\1\1\0\1" },
+ { 6, 6, 0, 0, 6, "\1\1\0\0\1\1\0\1\0\0\1\0\0\1\0\1\1\0\0\1\0\1\0\0\0\0\1\1\0\0\0\0\1\0\0\0" },
+ { 9, 6, 0, 0, 9, "\1\1\1\0\1\1\0\1\1\0\1\0\0\1\0\0\1\0\0\1\1\0\1\0\1\1\0\0\0\1\0\1\0\1\0\0\0\0\1\1\0\1\0\0\0\0\0\1\0\0\1\0\0\0" },
+ { 5, 6, 1, 0, 6, "\1\1\0\1\1\0\1\0\1\0\0\0\1\0\0\0\0\1\0\0\0\1\0\1\0\1\1\0\1\1" },
+ { 6, 9, 0, -3, 6, "\1\1\0\0\1\1\0\1\0\0\1\0\0\1\0\1\1\0\0\1\0\1\0\0\0\0\1\1\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\1\0\0\0\0\1\1\0\0\0\0" },
+ { 4, 6, 1, 0, 6, "\1\1\1\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\1\1\1\1" },
+ { 4, 12, 1, -3, 6, "\0\0\1\1\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\1\1" },
+ { 1, 9, 1, 0, 3, "\1\1\1\1\1\1\1\1\1" },
+ { 4, 12, 0, -3, 6, "\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\1\1\0\0" },
+ { 6, 2, 0, 3, 7, "\0\1\1\0\0\1\1\0\0\1\1\0" },
+ { 1, 9, 1, -3, 4, "\1\0\1\1\1\1\1\1\1" },
+ { 5, 8, 1, -1, 6, "\0\0\0\0\1\0\1\1\1\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\1\0\0\1\0\1\1\1\0\1\0\0\0\0" },
+ { 5, 9, 0, 0, 6, "\0\0\1\1\0\0\1\0\0\1\0\1\0\0\0\0\1\0\0\0\1\1\1\1\0\0\1\0\0\0\0\1\0\0\0\1\1\1\0\1\1\1\0\1\1" },
+ { 6, 7, 1, 1, 7, "\1\0\0\0\0\1\0\1\1\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\1\1\1\0\1\0\0\0\0\1" },
+ { 5, 9, 0, 0, 6, "\1\0\0\0\1\1\0\0\0\1\0\1\0\1\0\0\1\0\1\0\1\1\1\1\1\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\1\1\1\0" },
+ { 1, 9, 1, 0, 3, "\1\1\1\0\0\1\1\1\1" },
+ { 4, 12, 1, -3, 6, "\0\1\1\1\1\0\0\1\1\1\0\0\0\1\1\0\1\0\1\1\1\0\0\1\1\0\0\1\1\1\0\1\0\1\1\0\0\0\1\1\1\0\0\1\1\1\1\0" },
+ { 3, 1, 0, 7, 3, "\1\0\1" },
+ { 9, 9, 1, 0, 11, "\0\0\0\1\1\1\0\0\0\0\1\1\0\0\0\1\1\0\0\1\0\1\1\1\0\1\0\1\0\1\0\0\1\0\0\1\1\0\1\0\0\0\0\0\1\1\0\1\0\0\1\0\0\1\0\1\0\1\1\1\0\1\0\0\1\1\0\0\0\1\1\0\0\0\0\1\1\1\0\0\0" },
+ { 3, 6, 1, 3, 5, "\1\1\0\0\0\1\1\1\1\1\0\1\0\0\0\1\1\1" },
+ { 5, 5, 1, 0, 7, "\0\0\1\0\1\0\1\0\1\0\1\0\1\0\0\0\1\0\1\0\0\0\1\0\1" },
+ { 6, 4, 1, 1, 8, "\1\1\1\1\1\1\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\1" },
+ { 4, 1, 1, 3, 6, "\1\1\1\1" },
+ { 9, 9, 1, 0, 11, "\0\0\0\1\1\1\0\0\0\0\1\1\0\0\0\1\1\0\0\1\0\1\1\1\0\1\0\1\0\0\1\0\0\1\0\1\1\0\0\1\1\1\0\0\1\1\0\0\1\0\1\0\0\1\1\1\0\1\0\1\0\1\0\0\1\1\0\0\0\1\1\0\0\0\1\1\1\1\0\0\0" },
+ { 4, 1, 0, 7, 4, "\1\1\1\1" },
+ { 4, 4, 0, 5, 5, "\0\1\1\0\1\0\0\1\1\0\0\1\0\1\1\0" },
+ { 5, 7, 1, 0, 7, "\0\0\1\0\0\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\0\1\0\0\0\0\0\0\0\1\1\1\1\1" },
+ { 4, 5, 0, 4, 4, "\0\1\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\1\1\1" },
+ { 3, 5, 0, 4, 4, "\1\1\1\0\0\1\0\1\0\0\0\1\1\1\0" },
+ { 2, 2, 1, 7, 4, "\0\1\1\0" },
+ { 6, 9, 0, -3, 6, "\1\1\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\1\1\0\1\0\1\0\0\0\0\0\1\0\0\0\0\0\1\1\0\0\0" },
+ { 6, 12, 0, -3, 7, "\0\1\1\1\1\1\1\1\1\0\1\0\1\1\1\0\1\0\1\1\1\0\1\0\1\1\1\0\1\0\0\1\1\0\1\0\0\0\1\0\1\0\0\0\1\0\1\0\0\0\1\0\1\0\0\0\1\0\1\0\0\0\1\0\1\0\0\0\1\0\1\0" },
+ { 1, 1, 1, 3, 3, "\1" },
+ { 3, 3, 0, -3, 3, "\0\1\0\0\0\1\1\1\1" },
+ { 3, 5, 0, 4, 4, "\0\1\0\1\1\0\0\1\0\0\1\0\1\1\1" },
+ { 3, 6, 1, 3, 5, "\0\1\0\1\0\1\1\0\1\0\1\0\0\0\0\1\1\1" },
+ { 5, 5, 0, 0, 7, "\1\0\1\0\0\0\1\0\1\0\0\0\1\0\1\0\1\0\1\0\1\0\1\0\0" },
+ { 9, 9, 0, 0, 9, "\0\1\0\0\0\0\1\0\0\1\1\0\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\1\1\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\0\0\1\0\1\0\1\0\0\0\1\0\0\1\1\1\1\0\0\1\0\0\0\0\1\0" },
+ { 9, 9, 0, 0, 9, "\0\1\0\0\0\0\1\0\0\1\1\0\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\1\1\0\1\0\1\1\0\0\0\0\1\0\1\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\1\1\1\1" },
+ { 9, 9, 0, 0, 9, "\1\1\1\0\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\0\0\0\0\1\1\0\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\0\0\1\0\1\0\1\0\0\0\1\0\0\1\1\1\1\0\0\1\0\0\0\0\1\0" },
+ { 4, 9, 0, -3, 5, "\0\0\1\0\0\0\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\1\0\0\1\0\0\0\1\0\0\1\0\1\1\0" },
+ { 9, 12, 0, 0, 9, "\0\0\0\1\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\0\1\1\1\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\1\1\0\0\0\1\1\1" },
+ { 9, 12, 0, 0, 9, "\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\0\1\1\1\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\1\1\0\0\0\1\1\1" },
+ { 9, 12, 0, 0, 9, "\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\0\1\1\1\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\1\1\0\0\0\1\1\1" },
+ { 9, 12, 0, 0, 9, "\0\0\0\0\1\0\1\0\0\0\0\0\1\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\0\1\1\1\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\1\1\0\0\0\1\1\1" },
+ { 9, 11, 0, 0, 9, "\0\0\0\1\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\0\1\1\1\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\1\1\0\0\0\1\1\1" },
+ { 9, 12, 0, 0, 9, "\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\0\1\1\1\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\1\1\1\0\0\0\1\1\1" },
+ { 10, 9, 0, 0, 11, "\0\0\1\1\1\1\1\1\1\1\0\0\0\1\1\0\0\0\0\1\0\0\1\0\1\0\0\0\0\0\0\0\1\0\1\0\0\0\1\0\0\1\0\0\1\1\1\1\1\0\0\1\1\1\1\0\0\0\1\0\0\1\0\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\0\1\1\1\0\0\1\1\1\1\1\1" },
+ { 7, 12, 0, -3, 8, "\0\0\1\1\1\0\1\0\1\1\0\0\1\1\0\1\0\0\0\0\1\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\1\0\1\1\0\0\1\1\0\0\1\1\1\1\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\1\1\1\0\0" },
+ { 7, 12, 0, 0, 8, "\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\1\1\1\1\1\1\1\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\1\0\0\1\0\0\0\0\0\0\1\0\0\0\0\1\1\1\1\1\1\1\1" },
+ { 7, 12, 0, 0, 8, "\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\1\1\1\1\1\1\1\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\1\0\0\1\0\0\0\0\0\0\1\0\0\0\0\1\1\1\1\1\1\1\1" },
+ { 7, 12, 0, 0, 8, "\0\0\0\1\0\0\0\0\0\1\0\1\0\0\0\0\0\0\0\0\0\1\1\1\1\1\1\1\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\1\0\0\1\0\0\0\0\0\0\1\0\0\0\0\1\1\1\1\1\1\1\1" },
+ { 7, 11, 0, 0, 8, "\0\0\1\0\1\0\0\0\0\0\0\0\0\0\1\1\1\1\1\1\1\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\0\0\0\1\0\0\1\1\1\1\1\0\0\1\0\0\0\1\0\0\1\0\0\0\0\0\0\1\0\0\0\0\1\1\1\1\1\1\1\1" },
+ { 3, 12, 0, 0, 4, "\1\0\0\0\1\0\0\0\0\1\1\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 3, 12, 0, 0, 4, "\0\0\1\0\1\0\0\0\0\1\1\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 3, 12, 0, 0, 4, "\0\1\0\1\0\1\0\0\0\1\1\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 3, 11, 0, 0, 4, "\1\0\1\0\0\0\1\1\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 8, 9, 0, 0, 9, "\1\1\1\1\1\1\0\0\0\1\0\0\0\1\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\0\1\1\1\1\0\0\0\0\1\0\1\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\0\0\0\1\1\0\1\1\1\1\1\1\0\0" },
+ { 9, 12, 0, 0, 9, "\0\0\0\0\1\0\1\0\0\0\0\0\1\0\1\0\0\0\0\0\0\0\0\0\0\0\0\1\1\0\0\0\0\1\1\1\0\1\1\0\0\0\0\1\0\0\1\1\0\0\0\0\1\0\0\1\0\1\0\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\0\1\0\1\0\0\1\0\0\0\0\1\1\0\1\1\1\0\0\0\0\1\0" },
+ { 8, 12, 0, 0, 9, "\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1\1\0\0\0\1\1\0\0\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 8, 12, 0, 0, 9, "\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1\1\0\0\0\1\1\0\0\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 8, 12, 0, 0, 9, "\0\0\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1\1\0\0\0\1\1\0\0\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 8, 12, 0, 0, 9, "\0\0\0\1\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1\1\0\0\0\1\1\0\0\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 8, 11, 0, 0, 9, "\0\0\1\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1\1\0\0\0\1\1\0\0\1\1\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\1\0\0\0\0\0\0\1\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 5, 5, 1, 1, 7, "\1\0\0\0\1\0\1\0\1\0\0\0\1\0\0\0\1\0\1\0\1\0\0\0\1" },
+ { 9, 10, 0, 0, 9, "\0\0\0\0\0\0\0\0\1\0\0\1\1\1\1\0\1\0\0\1\1\0\0\1\1\0\0\0\1\0\0\0\1\1\0\0\1\0\0\0\1\0\0\1\0\1\0\0\0\1\0\0\1\0\1\0\0\1\0\0\0\1\0\0\1\1\0\0\0\1\0\0\0\1\1\0\0\1\1\0\0\1\0\1\1\1\1\0\0\0" },
+ { 8, 12, 0, 0, 8, "\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\1\1\1\0\0\1\1\1\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 8, 12, 0, 0, 8, "\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\1\1\1\0\0\1\1\1\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 8, 12, 0, 0, 8, "\0\0\0\1\0\0\0\0\0\0\1\0\1\0\0\0\0\0\0\0\0\0\0\0\1\1\1\0\0\1\1\1\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 8, 11, 0, 0, 8, "\0\0\1\0\1\0\0\0\0\0\0\0\0\0\0\0\1\1\1\0\0\1\1\1\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\1\1\0\0\0\1\1\1\1\0\0" },
+ { 9, 12, 0, 0, 9, "\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1\0\0\0\1\1\1\0\1\0\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\0\1\0\0\1\0\0\0\0\0\1\1\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\1\1\0\0\0" },
+ { 7, 9, 0, 0, 7, "\1\1\1\0\0\0\0\0\1\0\0\0\0\0\0\1\1\1\1\1\0\0\1\0\0\0\1\1\0\1\0\0\0\0\1\0\1\0\0\0\1\1\0\1\1\1\1\1\0\0\1\0\0\0\0\0\1\1\1\0\0\0\0" },
+ { 6, 9, 0, 0, 6, "\0\0\1\1\0\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\0\0\0\1\1\1\0\0\0\1\0\0\1\0\0\1\0\0\0\1\0\1\0\0\0\1\1\1\0\1\1\0" },
+ { 5, 9, 1, 0, 6, "\0\1\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\0\1" },
+ { 5, 9, 1, 0, 6, "\0\0\0\1\0\0\0\1\0\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\0\1" },
+ { 5, 9, 1, 0, 6, "\0\0\1\0\0\0\1\0\1\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\0\1" },
+ { 5, 9, 1, 0, 6, "\0\1\0\1\0\1\0\1\0\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\0\1" },
+ { 5, 8, 1, 0, 6, "\0\1\0\1\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\0\1" },
+ { 5, 9, 1, 0, 6, "\0\0\1\0\0\0\1\0\1\0\0\0\1\0\0\0\1\1\0\0\1\0\0\1\0\0\1\1\1\0\1\0\0\1\0\1\0\0\1\0\0\1\1\0\1" },
+ { 8, 6, 1, 0, 9, "\0\1\1\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\1\1\1\1\1\0\1\0\0\1\0\0\0\0\1\0\0\1\1\0\0\1\0\1\1\0\1\1\1\0" },
+ { 4, 9, 1, -3, 5, "\0\1\1\0\1\0\0\1\1\0\0\0\1\0\0\0\1\0\0\1\0\1\1\0\0\1\0\0\0\0\1\0\1\1\1\0" },
+ { 5, 9, 1, 0, 6, "\0\1\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\1\1\1\1\0\1\0\0\0\0\1\1\0\0\1\0\1\1\1\0" },
+ { 5, 9, 1, 0, 6, "\0\0\1\0\0\0\1\0\0\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\1\1\1\1\0\1\0\0\0\0\1\1\0\0\1\0\1\1\1\0" },
+ { 5, 9, 1, 0, 6, "\0\0\1\0\0\0\1\0\1\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\1\1\1\1\0\1\0\0\0\0\1\1\0\0\1\0\1\1\1\0" },
+ { 5, 8, 1, 0, 6, "\0\1\0\1\0\0\0\0\0\0\0\1\1\0\0\1\0\0\1\0\1\1\1\1\0\1\0\0\0\0\1\1\0\0\1\0\1\1\1\0" },
+ { 3, 9, 0, 0, 3, "\1\0\0\0\1\0\0\0\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 3, 9, 0, 0, 3, "\0\1\0\1\0\0\0\0\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 3, 9, 0, 0, 3, "\0\1\0\1\0\1\0\0\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 3, 8, 0, 0, 3, "\1\0\1\0\0\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1" },
+ { 4, 9, 1, 0, 6, "\0\1\0\0\0\1\1\1\1\0\1\0\0\1\1\1\1\0\0\1\1\0\0\1\1\0\0\1\1\0\0\1\0\1\1\0" },
+ { 6, 9, 0, 0, 6, "\0\0\1\0\1\0\0\1\0\1\0\0\0\0\0\0\0\0\1\0\1\1\0\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\1\1\1\0\1\1" },
+ { 4, 9, 1, 0, 6, "\0\1\0\0\0\0\1\0\0\0\0\0\0\1\1\0\1\0\0\1\1\0\0\1\1\0\0\1\1\0\0\1\0\1\1\0" },
+ { 4, 9, 1, 0, 6, "\0\0\1\0\0\1\0\0\0\0\0\0\0\1\1\0\1\0\0\1\1\0\0\1\1\0\0\1\1\0\0\1\0\1\1\0" },
+ { 4, 9, 1, 0, 6, "\0\0\1\0\0\1\0\1\0\0\0\0\0\1\1\0\1\0\0\1\1\0\0\1\1\0\0\1\1\0\0\1\0\1\1\0" },
+ { 4, 9, 1, 0, 6, "\0\1\0\1\1\0\1\0\0\0\0\0\0\1\1\0\1\0\0\1\1\0\0\1\1\0\0\1\1\0\0\1\0\1\1\0" },
+ { 4, 8, 1, 0, 6, "\1\0\1\0\0\0\0\0\0\1\1\0\1\0\0\1\1\0\0\1\1\0\0\1\1\0\0\1\0\1\1\0" },
+ { 5, 5, 1, 1, 7, "\0\0\1\0\0\0\0\0\0\0\1\1\1\1\1\0\0\0\0\0\0\0\1\0\0" },
+ { 6, 7, 0, -1, 6, "\0\0\1\1\0\1\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\1\1\0\0\1\0\0\0\0\0" },
+ { 6, 9, 0, 0, 6, "\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\1\1\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\0\1\1\0\1" },
+ { 6, 9, 0, 0, 6, "\0\0\0\1\0\0\0\0\1\0\0\0\0\0\0\0\0\0\1\1\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\0\1\1\0\1" },
+ { 6, 9, 0, 0, 6, "\0\0\1\0\0\0\0\1\0\1\0\0\0\0\0\0\0\0\1\1\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\0\1\1\0\1" },
+ { 6, 8, 0, 0, 6, "\0\1\0\1\0\0\0\0\0\0\0\0\1\1\0\1\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\1\0\0\0\1\1\0\1" },
+ { 6, 12, 0, -3, 6, "\0\0\0\0\1\0\0\0\0\1\0\0\0\0\0\0\0\0\1\1\0\0\1\1\0\1\0\0\1\0\0\1\0\1\1\0\0\1\0\1\0\0\0\0\1\1\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\1\0\0\0\0\1\1\0\0\0\0" },
+ { 5, 12, 0, -3, 6, "\1\1\0\0\0\0\1\0\0\0\0\1\0\0\0\0\1\1\1\0\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\1\0\0\1\0\1\1\1\0\0\1\0\0\0\0\1\0\0\0\1\1\1\0\0" },
+ { 6, 11, 0, -3, 6, "\0\1\0\0\1\0\0\0\0\0\0\0\1\1\0\0\1\1\0\1\0\0\1\0\0\1\0\1\1\0\0\1\0\1\0\0\0\0\1\1\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\1\0\0\0\0\1\1\0\0\0\0" }
+};
+
+static struct font default_bdffont = { 14, 15, -1, -3, {
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ _g + 0,
+ _g + 1,
+ _g + 2,
+ _g + 3,
+ _g + 4,
+ _g + 5,
+ _g + 6,
+ _g + 7,
+ _g + 8,
+ _g + 9,
+ _g + 10,
+ _g + 11,
+ _g + 12,
+ _g + 13,
+ _g + 14,
+ _g + 15,
+ _g + 16,
+ _g + 17,
+ _g + 18,
+ _g + 19,
+ _g + 20,
+ _g + 21,
+ _g + 22,
+ _g + 23,
+ _g + 24,
+ _g + 25,
+ _g + 26,
+ _g + 27,
+ _g + 28,
+ _g + 29,
+ _g + 30,
+ _g + 31,
+ _g + 32,
+ _g + 33,
+ _g + 34,
+ _g + 35,
+ _g + 36,
+ _g + 37,
+ _g + 38,
+ _g + 39,
+ _g + 40,
+ _g + 41,
+ _g + 42,
+ _g + 43,
+ _g + 44,
+ _g + 45,
+ _g + 46,
+ _g + 47,
+ _g + 48,
+ _g + 49,
+ _g + 50,
+ _g + 51,
+ _g + 52,
+ _g + 53,
+ _g + 54,
+ _g + 55,
+ _g + 56,
+ _g + 57,
+ _g + 58,
+ _g + 59,
+ _g + 60,
+ _g + 61,
+ _g + 62,
+ _g + 63,
+ _g + 64,
+ _g + 65,
+ _g + 66,
+ _g + 67,
+ _g + 68,
+ _g + 69,
+ _g + 70,
+ _g + 71,
+ _g + 72,
+ _g + 73,
+ _g + 74,
+ _g + 75,
+ _g + 76,
+ _g + 77,
+ _g + 78,
+ _g + 79,
+ _g + 80,
+ _g + 81,
+ _g + 82,
+ _g + 83,
+ _g + 84,
+ _g + 85,
+ _g + 86,
+ _g + 87,
+ _g + 88,
+ _g + 89,
+ _g + 90,
+ _g + 91,
+ _g + 92,
+ _g + 93,
+ _g + 94,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ _g + 95,
+ _g + 96,
+ _g + 97,
+ _g + 98,
+ _g + 99,
+ _g + 100,
+ _g + 101,
+ _g + 102,
+ _g + 103,
+ _g + 104,
+ _g + 105,
+ _g + 106,
+ _g + 107,
+ _g + 108,
+ _g + 109,
+ _g + 110,
+ _g + 111,
+ _g + 112,
+ _g + 113,
+ _g + 114,
+ _g + 115,
+ _g + 116,
+ _g + 117,
+ _g + 118,
+ _g + 119,
+ _g + 120,
+ _g + 121,
+ _g + 122,
+ _g + 123,
+ _g + 124,
+ _g + 125,
+ _g + 126,
+ _g + 127,
+ _g + 128,
+ _g + 129,
+ _g + 130,
+ _g + 131,
+ _g + 132,
+ _g + 133,
+ _g + 134,
+ _g + 135,
+ _g + 136,
+ _g + 137,
+ _g + 138,
+ _g + 139,
+ _g + 140,
+ _g + 141,
+ _g + 142,
+ _g + 143,
+ _g + 144,
+ _g + 145,
+ _g + 146,
+ _g + 147,
+ _g + 148,
+ _g + 149,
+ _g + 150,
+ _g + 151,
+ _g + 152,
+ _g + 153,
+ _g + 154,
+ _g + 155,
+ _g + 156,
+ _g + 157,
+ _g + 158,
+ _g + 159,
+ _g + 160,
+ _g + 161,
+ _g + 162,
+ _g + 163,
+ _g + 164,
+ _g + 165,
+ _g + 166,
+ _g + 167,
+ _g + 168,
+ _g + 169,
+ _g + 170,
+ _g + 171,
+ _g + 172,
+ _g + 173,
+ _g + 174,
+ _g + 175,
+ _g + 176,
+ _g + 177,
+ _g + 178,
+ _g + 179,
+ _g + 180,
+ _g + 181,
+ _g + 182,
+ _g + 183,
+ _g + 184,
+ _g + 185,
+ _g + 186,
+ _g + 187,
+ _g + 188,
+ _g + 189
+ }
+};
+
+
+
+struct font *
+pbm_defaultfont(const char * const name) {
+/*----------------------------------------------------------------------------
+   Generate the built-in font with name 'name'.
+-----------------------------------------------------------------------------*/
+    struct font * retval;
+
+    if (strcmp(name, "bdf") == 0)
+        retval = &default_bdffont;
+    else {
+        bit** defaultfont;
+        unsigned int row;
+
+        if (strcmp(name, "fixed") != 0)
+            pm_error( "built-in font name unknown, try 'bdf' or 'fixed'" );
+
+        defaultfont = pbm_allocarray( DEFAULTFONT_COLS, DEFAULTFONT_ROWS );
+        for (row =  0; row < DEFAULTFONT_ROWS; ++row) {
+            unsigned int col;
+            for (col = 0; col < DEFAULTFONT_COLS; col += 32) {
+                int scol;
+                unsigned long l;
+
+                l = defaultfont_bits[row][col / 32]; /* initial value */
+
+                for (scol = MIN( col + 32, DEFAULTFONT_COLS) - 1;
+                     scol >= (int)col; 
+                     --scol) {
+                    if (l & 0x01)
+                        defaultfont[row][scol] = 1;
+                    else
+                        defaultfont[row][scol] = 0;
+                    l >>= 1;
+                }
+            }
+        }
+
+        retval = 
+            pbm_dissectfont(defaultfont, DEFAULTFONT_ROWS, DEFAULTFONT_COLS);
+    }
+    return retval;
+}
+
+
+struct font*
+pbm_dissectfont(bit ** const font,
+                int    const frows,
+                int    const fcols) {
+    /*
+    ** This routine expects a font bitmap representing the following text:
+    **
+    ** (0,0)
+    **    M ",/^_[`jpqy| M
+    **
+    **    /  !"#$%&'()*+ /
+    **    < ,-./01234567 <
+    **    > 89:;<=>?@ABC >
+    **    @ DEFGHIJKLMNO @
+    **    _ PQRSTUVWXYZ[ _
+    **    { \]^_`abcdefg {
+    **    } hijklmnopqrs }
+    **    ~ tuvwxyz{|}~  ~
+    **
+    **    M ",/^_[`jpqy| M
+    **
+    ** The bitmap must be cropped exactly to the edges.
+    **
+    ** The border you see is irrelevant.  The 12 x 8 array in the center is
+    ** the font.  The top left character there belongs to code point 0, and
+    ** the code points increase in standard reading order, so the bottom
+    ** right character is code point 127.  You can't define code points 
+    ** < 32 or > 127 with this font format.
+    **
+    ** The dissection works by finding the first blank row and column; that
+    ** gives the height and width of the maximum-sized character, which is
+    ** not too useful.  But the distance from there to the opposite side is
+    ** an integral multiple of the cell size, and that's what we need.  Then
+    ** it's just a matter of filling in all the coordinates.
+    **
+    ** The difference between cell_height, cell_width and char_height,
+    ** char_width is that the first is the size of the cell including
+    ** spacing, while the second is just the actual maximum-size character.
+    */
+    int cell_width, cell_height, char_width, char_height;
+    int brow, bcol, row, col, d, ch, r, c, i;
+    struct font* fn;
+    struct glyph* glyph;
+    char* bmap;
+    bit b;
+
+    /* Find first blank row. */
+    for ( brow = 0; brow < frows / 6; ++brow ) {
+        b = font[brow][0];
+        for ( col = 1; col < fcols; ++col )
+            if ( font[brow][col] != b )
+                goto nextrow;
+        goto gotblankrow;
+    nextrow: ;
+    }
+    pm_error( "couldn't find blank row in font" );
+
+ gotblankrow:
+    /* Find first blank col. */
+    for ( bcol = 0; bcol < fcols / 8; ++bcol ) {
+        b = font[0][bcol];
+        for ( row = 1; row < frows; ++row )
+            if ( font[row][bcol] != b )
+                goto nextcol;
+        goto gotblankcol;
+    nextcol: ;
+    }
+    pm_error( "couldn't find blank col in font" );
+
+ gotblankcol:
+    /* Now compute character cell size. */
+    d = frows - brow;
+    cell_height = d / 11;
+    if ( cell_height * 11 != d )
+        pm_error( "problem computing character cell height" );
+    d = fcols - bcol;
+    cell_width = d / 15;
+    if ( cell_width * 15 != d )
+        pm_error( "problem computing character cell width" );
+    char_height = brow;
+    char_width = bcol;
+
+    /* Now convert to a general font */
+
+    MALLOCVAR(fn);
+    if ( fn == NULL )
+        pm_error( "out of memory allocating font structure" );
+
+    fn->maxwidth  = char_width;
+    fn->maxheight = char_height;
+    fn->x = fn->y = 0;
+
+    fn->oldfont = font;
+    fn->frows = frows;
+    fn->fcols = fcols;
+    
+    /* Initialize all character positions to "undefined."  Those that
+       are defined in the font will be filled in below.
+    */
+    for (i = 0; i < 256; i++)
+        fn->glyph[i] = NULL;
+
+    MALLOCARRAY(glyph, nCharsInFont);
+    if ( glyph == NULL )
+        pm_error( "out of memory allocating glyphs" );
+    
+    bmap = (char*) malloc( fn->maxwidth * fn->maxheight * nCharsInFont );
+    if ( bmap == (char*) 0)
+        pm_error( "out of memory allocating glyph data" );
+
+    /* Now fill in the 0,0 coords. */
+    row = cell_height * 2;
+    col = cell_width * 2;
+    for (i = 0; i < firstCodePoint; ++i)
+        fn->glyph[i] = NULL;
+
+    for ( ch = 0; ch < nCharsInFont; ++ch ) {
+        glyph[ch].width = fn->maxwidth;
+        glyph[ch].height = fn->maxheight;
+        glyph[ch].x = glyph[ch].y = 0;
+        glyph[ch].xadd = cell_width;
+
+        for ( r = 0; r < glyph[ch].height; ++r )
+            for ( c = 0; c < glyph[ch].width; ++c )
+                bmap[r * glyph[ch].width + c] = font[row + r][col + c];
+    
+        glyph[ch].bmap = bmap;
+        bmap += glyph[ch].width * glyph[ch].height;
+
+        fn->glyph[firstCodePoint + ch] = &glyph[ch];
+
+        col += cell_width;
+        if ( col >= cell_width * 14 ) {
+            col = cell_width * 2;
+            row += cell_height;
+        }
+    }
+    for (i = firstCodePoint + nCharsInFont; i < 256; ++i)
+        fn->glyph[i] = NULL;
+    
+    return fn;
+}
+
+
+
+struct font*
+pbm_loadfont(const char * const filename)
+{
+    FILE* fp;
+    struct font* fn;
+    char line[256];
+
+    fp = pm_openr( filename );
+    fgets(line, 256, fp);
+    pm_close( fp );
+
+    if (line[0] == PBM_MAGIC1 && 
+        (line[1] == PBM_MAGIC2 || line[1] == RPBM_MAGIC2)) {
+        return pbm_loadpbmfont( filename );
+    } else if (!strncmp(line, "STARTFONT", 9)) {
+        if (!(fn = pbm_loadbdffont( filename )))
+          pm_error( "could not load BDF font file" );
+        return fn;
+    } else {
+      pm_error( "font file not in a recognized format ");
+      return NULL;  /* should never reach here */
+  }
+}
+
+
+
+struct font* pbm_loadpbmfont(const char * const filename)
+{
+    FILE* ifp;
+    bit** font;
+    int fcols, frows;
+
+    ifp = pm_openr( filename );
+    font = pbm_readpbm( ifp, &fcols, &frows );
+    pm_close( ifp );
+    return pbm_dissectfont( font, frows, fcols );
+}
+
+void
+pbm_dumpfont( fn )
+    struct font* fn;
+{
+    /* Dump out font as C source code. */
+    int row, col, scol, lperrow;
+    unsigned long l;
+
+    if (fn->oldfont) {
+    printf( "#define DEFAULTFONT_ROWS %d\n", fn->frows );
+    printf( "#define DEFAULTFONT_COLS %d\n", fn->fcols );
+    printf( "static unsigned long defaultfont_bits[DEFAULTFONT_ROWS][(DEFAULTFONT_COLS+31)/32] = {\n" );
+    for ( row = 0; row < fn->frows; ++row )
+        {
+        lperrow = 0;
+        for ( col = 0; col < fn->fcols; col += 32 )
+        {
+        if ( lperrow == 0 )
+            printf( "    {" );
+        else if ( lperrow % 6 == 0 )
+            {
+            printf( ",\n     " );
+            lperrow = 0;
+            }
+        else
+            printf( "," );
+        l = 0;
+        for ( scol = col; scol < MIN( col + 32, fn->fcols ); ++scol )
+            {
+            l <<= 1;
+            if ( fn->oldfont[row][scol] )
+            l |= 1;
+            }
+        printf( "0x%08lxL", l );
+        ++lperrow;
+        }
+        printf( "}%s\n", row == fn->frows - 1 ? "" : "," );
+        }
+    printf( "    };\n" );
+    }
+    else {
+    struct glyph* glyph;
+    int i, j, ng;
+
+    ng = 0;
+    for (i = 0; i < 256; i++)
+        if (fn->glyph[i])
+            ng++;
+
+    printf("static struct glyph _g[%d] = {\n", ng);
+    for (i = 0; i < 256; i++) {
+        if (!(glyph = fn->glyph[i]))
+            continue;
+
+        printf(" { %d, %d, %d, %d, %d, \"", glyph->width, glyph->height,
+            glyph->x, glyph->y, glyph->xadd);
+
+        for (j = 0; j < glyph->width * glyph->height; j++)
+            if (glyph->bmap[j])
+                printf("\\1");
+            else
+                printf("\\0");
+        
+        ng--;
+        printf("\" }%s\n", ng ? "," : "");
+    }
+    printf("};\n");
+
+    printf("static struct font default_bdffont = { %d, %d, %d, %d, {\n",
+        fn->maxwidth, fn->maxheight, fn->x, fn->y);
+
+    for (i = 0; i < 256; i++) {
+        if (fn->glyph[i])
+            printf(" _g + %d", ng++);
+        else
+            printf(" 0");
+        
+        if (i != 255) printf(",");
+        printf("\n");
+    }
+
+    printf(" }\n};\n");
+    exit(0);
+
+    }
+
+}
+
+
+/* Routines for loading a BDF font file */
+
+static int readline ARGS((FILE* fp, char* buf, char* arg[]));
+
+#define expect(str) if (readline(fp, line, arg) < 0 || strcmp(arg[0], (str))) \
+    { fclose(fp); return 0; }
+
+struct font* pbm_loadbdffont(const char * const name)
+{
+    FILE* fp;
+    char line[1024], *arg[32], *hex;
+    char *b;
+    int n, numchar, hdig, encoding;
+    struct font* font;
+    struct glyph* glyph;
+
+    if (!(fp = fopen(name, "rb")))
+        return 0;
+
+    expect("STARTFONT");
+
+    MALLOCVAR(font);
+    if (font == NULL)
+        pm_error("no memory for font");
+    font->oldfont = 0;
+    { 
+        /* Initialize all characters to nonexistent; we will fill the ones we
+           find in the bdf file later.
+        */
+        int i;
+        for (i = 0; i < 256; i++) 
+            font->glyph[i] = NULL;
+    }
+
+    while (readline(fp, line, arg) >= 0) {
+        if (!strcmp(arg[0], "COMMENT"))
+            continue;
+        if (!strcmp(arg[0], "SIZE"))
+            continue;
+        
+        if (!strcmp(arg[0], "STARTPROPERTIES")) {
+            n = atoi(arg[1]);
+            for (; n > 0 && readline(fp, line, arg) >= 0; n--)
+                ;
+        }
+        else if (!strcmp(arg[0], "FONTBOUNDINGBOX")) {
+            font->maxwidth = atoi(arg[1]);
+            font->maxheight = atoi(arg[2]);
+            font->x = atoi(arg[3]);
+            font->y = atoi(arg[4]);
+        }
+        else if (!strcmp(arg[0], "ENDFONT")) {
+            fclose(fp);
+            return font;
+        }
+        else if (!strcmp(arg[0], "CHARS")) {
+            numchar = atoi(arg[1]);
+            while (numchar > 0) {
+                if (readline(fp, line, arg) < 0) { fclose(fp); return 0; }
+                if (!strcmp(arg[0], "COMMENT"))
+                    continue;
+                if (strcmp(arg[0], "STARTCHAR")) { fclose(fp); return 0; }
+                MALLOCVAR(glyph);
+                if (glyph == NULL)
+                    pm_error("no memory for font glyph");
+
+                expect("ENCODING");
+                if ((encoding = atoi(arg[1])) < 0) {
+                    if (arg[2])
+                        encoding = atoi(arg[2]);
+                    else {
+                        while (readline(fp, line, arg) >= 0)
+                            if (!strcmp(arg[0], "ENDCHAR"))
+                                break;
+                        
+                        numchar--;
+                        continue;
+                    }
+                }
+                expect("SWIDTH");
+                expect("DWIDTH");
+                glyph->xadd = atoi(arg[1]);
+                expect("BBX");
+                glyph->width = atoi(arg[1]);
+                glyph->height = atoi(arg[2]);
+                glyph->x = atoi(arg[3]);
+                glyph->y = atoi(arg[4]);
+
+                b = (char*)malloc(glyph->width * glyph->height * sizeof(char));
+                glyph->bmap = b;
+                if (!glyph->bmap)
+                    pm_error("no memory for font glyph byte map");
+
+                if (readline(fp, line, arg) < 0) { fclose(fp); return 0; }
+                if (!strcmp(arg[0], "ATTRIBUTES"))
+                    if (readline(fp, line, arg) < 0) { fclose(fp); return 0; }
+                
+                for (n = glyph->height; n > 0; n--) {
+                    int i;  /* dot counter */
+                    if (readline(fp, line, arg) < 0) { fclose(fp); return 0; }
+                    hex = line;
+                    for (i = glyph->width; i > 0; i -= 4) {
+                        hdig = *hex++;
+                        if (hdig >= '0' && hdig <= '9')
+                            hdig -= '0';
+                        else if (hdig >= 'a' && hdig <= 'f')
+                            hdig -= 'a' - 10;
+                        else if (hdig >= 'A' && hdig <= 'F')
+                            hdig -= 'A' - 10;
+                        
+                        *b++ = hdig & 8 ? 1 : 0;
+                        if (i > 1) *b++ = hdig & 4 ? 1 : 0;
+                        if (i > 2) *b++ = hdig & 2 ? 1 : 0;
+                        if (i > 3) *b++ = hdig & 1;
+                    }
+                }
+
+                expect("ENDCHAR");
+
+                if (encoding < 256)
+                    /* We ignore any characters with codes that don't fit 
+                       in 8 bits.  We may want to change this someday.
+                       */
+                    font->glyph[encoding] = glyph;
+
+                numchar--;
+            }
+        }
+    }
+    return font;
+}
+
+static int readline(fp, buf, arg)
+FILE* fp;
+char* buf;
+char* arg[];
+{
+    if (!fgets(buf, 1024, fp))
+        return -1;
+    
+    return mk_argvn(buf, arg, 32);
+}
+
+int mk_argvn(s, vec, mk_max)
+char* s;
+char* vec[];
+int mk_max;
+{
+    int n;
+
+    n = 0;
+    while (*s) {
+        if (ISSPACE(*s)) {
+            *s++ = '\0';
+            continue;
+        }
+        vec[n++] = s;
+        if (n >= mk_max)
+            break;
+        while (*s && !ISSPACE(*s))
+            s++;
+    }
+    vec[n] = 0;
+    return n;
+}
diff --git a/lib/libpbmvms.c b/lib/libpbmvms.c
new file mode 100644
index 00000000..8a2a54f8
--- /dev/null
+++ b/lib/libpbmvms.c
@@ -0,0 +1,734 @@
+/***************************************************************************
+  This file contains library routines needed to build Netpbm for VMS.
+  However, as of 2000.05.26, when these were split out of libpbm1.c
+  (where they were all switched by #ifdef VMS), there is no evidence
+  that anyone is building Netpbm for VMS.
+****************************************************************************/
+
+/*
+ * @(#)argproc.c 1.0 89/02/01		Mark Pizzolato (mark@infopiz.uucp)	
+ */
+
+#ifndef lint
+char argproc_version[] = "@(#)argproc.c VMS uucp Version infopiz-1.0";
+#endif
+
+#include "includes.h"		/* System include files, system dependent */
+
+
+/*
+ * getredirection() is intended to aid in porting C programs
+ * to VMS (Vax-11 C) which does not have '>' and '<'
+ * I/O redirection, along with a command line pipe mechanism
+ * using the '|' AND background command execution '&'.
+ * The piping mechanism will probably work with almost any 'filter' type
+ * of program.  With suitable modification, it may useful for other
+ * portability problems as well.
+ *
+ * Author:  Mark Pizzolato	mark@infopiz.UUCP
+ */
+struct list_item
+    {
+    struct list_item *next;
+    char *value;
+    };
+
+int
+getredirection(ac, av)
+int		*ac;
+char		***av;
+/*
+ * Process vms redirection arg's.  Exit if any error is seen.
+ * If getredirection() processes an argument, it is erased
+ * from the vector.  getredirection() returns a new argc and argv value.
+ * In the event that a background command is requested (by a trailing "&"),
+ * this routine creates a background subprocess, and simply exits the program.
+ *
+ * Warning: do not try to simplify the code for vms.  The code
+ * presupposes that getredirection() is called before any data is
+ * read from stdin or written to stdout.
+ *
+ * Normal usage is as follows:
+ *
+ *	main(argc, argv)
+ *	int		argc;
+ *    	char		*argv[];
+ *	{
+ *		getredirection(&argc, &argv);
+ *	}
+ */
+{
+    int			argc = *ac;	/* Argument Count	  */
+    char		**argv = *av;	/* Argument Vector	  */
+    char		*ap;   		/* Argument pointer	  */
+    int	       		j;		/* argv[] index		  */
+    extern int		errno;		/* Last vms i/o error 	  */
+    int			item_count = 0;	/* Count of Items in List */
+    int			new_file;	/* flag, true if '>' used */
+    struct list_item 	*list_head = 0;	/* First Item in List	    */
+    struct list_item	*list_tail;	/* Last Item in List	    */
+    char 		*in = NULL;	/* Input File Name	    */
+    char 		*out = NULL;	/* Output File Name	    */
+    char 		*outmode = "w";	/* Mode to Open Output File */
+    int			cmargc = 0;    	/* Piped Command Arg Count  */
+    char		**cmargv = NULL;/* Piped Command Arg Vector */
+    stat_t		statbuf;	/* fstat buffer		    */
+
+    /*
+     * First handle the case where the last thing on the line ends with
+     * a '&'.  This indicates the desire for the command to be run in a
+     * subprocess, so we satisfy that desire.
+     */
+    ap = argv[argc-1];
+    if (0 == strcmp("&", ap))
+	exit(background_process(--argc, argv));
+    if ('&' == ap[strlen(ap)-1])
+	{
+	ap[strlen(ap)-1] = '\0';
+	exit(background_process(argc, argv));
+	}
+    /*
+     * Now we handle the general redirection cases that involve '>', '>>',
+     * '<', and pipes '|'.
+     */
+    for (new_file = 0, j = 0; j < argc; ++j)
+	{
+	if (0 == strcmp("<", argv[j]))
+	    {
+	    if (j+1 >= argc)
+		{
+		errno = EINVAL;
+		perror("No input file");
+		exit(EXIT_ERR);
+		}
+	    in = argv[++j];
+	    continue;
+	    }
+	if ('<' == *(ap = argv[j]))
+	    {
+	    in = 1 + ap;
+	    continue;
+	    }
+	if (0 == strcmp(">", ap))
+	    {
+	    if (j+1 >= argc)
+		{
+		errno = EINVAL;
+		perror("No output file");
+		exit(EXIT_ERR);
+		}
+	    out = argv[++j];
+	    new_file = 1;
+	    continue;
+	    }
+	if ('>' == *ap)
+	    {
+	    if ('>' == ap[1])
+		{
+		outmode = "a";
+		if ('\0' == ap[2])
+		    out = argv[++j];
+		else
+		    out = 2 + ap;
+		}
+	    else
+		{ out = 1 + ap;  new_file = 1; }
+	    continue;
+	    }
+	if (0 == strcmp("|", argv[j]))
+	    {
+	    if (j+1 >= argc)
+		{
+		errno = EPIPE;
+		perror("No command to Pipe to");
+		exit(EXIT_ERR);
+		}
+	    cmargc = argc-(j+1);
+	    cmargv = &argv[j+1];
+	    argc = j;
+	    outmode = "wb";	/* pipes are binary mode devices */
+	    continue;
+	    }
+	if ('|' == *(ap = argv[j]))
+	    {
+	    ++argv[j];
+	    cmargc = argc-j;
+	    cmargv = &argv[j];
+	    argc = j;
+	    outmode = "wb";	/* pipes are binary mode devices */
+	    continue;
+	    }
+	expand_wild_cards(ap, &list_head, &list_tail, &item_count);
+	}
+    /*
+     * Allocate and fill in the new argument vector, Some Unix's terminate
+     * the list with an extra null pointer.
+     */
+    argv = *av = calloc(item_count+1, sizeof(char *));
+    for (j = 0; j < item_count; ++j, list_head = list_head->next)
+	argv[j] = list_head->value;
+    *ac = item_count;
+    if (cmargv != NULL)
+	{
+	char subcmd[1024];
+	static char *pipe_and_fork();
+
+	if (out != NULL)
+	    {
+	    errno = EINVAL;
+	    perror("Invalid '|' and '>' specified");
+	    exit(EXIT_ERR);
+	    }
+	strcpy(subcmd, cmargv[0]);
+	for (j = 1; j < cmargc; ++j)
+	    {
+	    strcat(subcmd, " \"");
+	    strcat(subcmd, cmargv[j]);
+	    strcat(subcmd, "\"");
+	    }
+	out = pipe_and_fork(subcmd);
+	outmode = "wb";
+	}
+	
+    /* Check for input from a pipe (mailbox) */
+
+    if(fstat(0, &statbuf) == 0){
+	if(strncmp(statbuf.st_dev, "MB", 2) == 0 || 
+	    strncmp(statbuf.st_dev, "_MB", 3) == 0){
+
+	    /* Input from a pipe, reopen it in binary mode to disable	*/
+	    /* carriage control processing.				*/
+
+	    if (in != NULL){
+		errno = EINVAL;
+		perror("Invalid '|' and '<' specified");
+		exit(EXIT_ERR);
+		}
+	    freopen(statbuf.st_dev, "rb", stdin);
+	    }
+	}
+    else {
+	perror("fstat failed");
+	exit(EXIT_ERR);
+	}
+
+#ifdef __ALPHA
+        /*, "mbc=32", "mbf=2"))) blows up on the ALPHA cbm 11/08/92 */
+    if ((in != NULL) && (NULL == freopen(in, "r", stdin)))
+        {
+#else
+    if ((in != NULL) && (NULL == freopen(in, "r", stdin, "mbc=32", "mbf=2")))
+	{
+#endif
+	perror(in);    	       	/* Can't find file		*/
+	exit(EXIT_ERR);		/* Is a fatal error		*/
+	}
+#ifdef __ALPHA
+    if ((out != NULL) && (NULL == freopen(out, outmode, stdout)))
+        {
+#else
+    if ((out != NULL) && (NULL == freopen(out, outmode, stdout, "mbc=32", "mbf=2")))
+	{	
+#endif
+	perror(ap);		/* Error, can't write or append	*/
+	exit(EXIT_ERR);		/* Is a fatal error		*/
+	}
+
+     if ( new_file ) {
+	/*
+	 * We are making an explicit output file, fstat the file and
+         * declare exit handler to be able change the file to fixed length
+	 * records if necessary. 
+	 */
+	char fname[256];
+	static int outfile_rundown(), check_outfile_filetype();
+	static stat_t ofile_stat;
+	static struct exit_control_block {
+    	    struct exit_control_block *flink;
+    	    int	(*exit_routine)();
+	    int arg_count;
+	    int *status_address;	/* arg 1 */
+	    stat_t *stat;		/* arg 2 */
+	    int exit_status;
+	    int skew[128];
+	} exit_block = 
+	    { 0, outfile_rundown, 2, &exit_block.exit_status, &ofile_stat, 0 };
+
+	if ( fstat ( fileno(stdout), &ofile_stat ) == 0 )
+	     sys$dclexh ( &exit_block );
+	else fprintf(stderr,"Error fstating stdout - %s\n",
+		strerror(errno,vaxc$errno) );
+
+	if ( fgetname ( stdout, fname, 0 ) ) check_outfile_filetype ( fname );
+     }
+#ifdef DEBUG
+    fprintf(stderr, "Arglist:\n");
+    for (j = 0; j < *ac;  ++j)
+	fprintf(stderr, "argv[%d] = '%s'\n", j, argv[j]);
+#endif
+}
+
+static int binary_outfile = 0;
+void set_outfile_binary() { binary_outfile = 1; return; }
+
+/*
+ * Check if output file should be set to binary (fixed 512) on exit based
+ * upon the filetype.
+ */
+static int check_outfile_filetype ( name )
+    char *name;
+{
+    char *binary_filetypes, *ext, *p, *t;
+    binary_filetypes = (char *) getenv ( "PBM_BINARY_FILETYPES" );
+    if ( binary_filetypes == NULL ) return 0;
+
+    ext = strchr ( name, '.' );  if ( ext == NULL ) return 0;
+    ext++;
+    t = strrchr ( name, '.' );   if ( t != NULL ) *t = '\0';
+
+    for ( p = binary_filetypes; *p != '\0'; p++ ) {
+	for ( t = p;
+	      (*p != '\0' ) && (strchr ( ", 	", *p ) == NULL); 
+	     p++ ) *p = toupper(*p);
+	*p = '\0';
+
+	if ( strcmp ( t, ext ) == 0 ) {
+	    binary_outfile = 1;
+	    break;
+	}
+    }
+    return binary_outfile;
+}
+
+
+/*
+ * Exit handler to set output file to binary on image exit.
+ */
+static int outfile_rundown ( reason, statbuf )
+    int *reason;
+    stat_t *statbuf;
+{
+    int code, channel, status, LIB$GETDVI(), sys$assign(), sys$qiow();
+    long int fib_desc[2], devchar;
+    short int iosb[4];
+    $DESCRIPTOR(device, statbuf->st_dev);
+    struct fibdef fib;		/* File information block (XQP) */
+    struct atrdef atr[2];
+    struct fat {
+      unsigned char rtype, ratattrib;
+      unsigned short int rsize, hiblk[2], efblk[2], ffbyte, maxrec, defext, gbc;
+      unsigned short int reserved[4], versions;
+    } rat;
+
+    if ( !binary_outfile ) return 1;	/* leave file alone */
+    /*
+     * Assign channel to device listed in stat block and test if it is
+     * a directory structured device, returning if not.
+     */
+    device.dsc$w_length = strlen ( statbuf->st_dev );
+    status = sys$assign ( &device, &channel, 0, 0 );
+    if ((status & 1) == 0) { fprintf(stderr, 
+	"assign error to %s (%d)\n", device.dsc$a_pointer, status);
+		return status; }
+    code = DVI$_DEVCHAR;
+    status = LIB$GETDVI ( &code, &channel, 0, &devchar );
+    if ((status & 1) == 0) { fprintf(stderr, "getdvi error: %d\n", status);
+		return status; }
+    if ( (devchar & DEV$M_DIR) == 0 ) return 1;
+    /*
+     * Build File Information Block and Atrribute block.
+     */
+#ifdef __ALPHA
+    fib.fib$w_fid[0] = statbuf->st_ino[0];
+    fib.fib$w_fid[1] = statbuf->st_ino[1];
+    fib.fib$w_fid[2] = statbuf->st_ino[2];
+#else
+    fib.fib$r_fid_overlay.fib$w_fid[0] = statbuf->st_ino[0];
+    fib.fib$r_fid_overlay.fib$w_fid[1] = statbuf->st_ino[1];
+    fib.fib$r_fid_overlay.fib$w_fid[2] = statbuf->st_ino[2];
+#endif
+
+    atr[0].atr$w_size = sizeof(rat);
+    atr[0].atr$w_type = ATR$C_RECATTR;
+    atr[0].atr$l_addr = &rat;
+    atr[1].atr$w_size = 0; atr[1].atr$w_type = 0;
+    /*
+     * Perform access function with read_attributes sub-function.
+     */
+    freopen ( "SYS$OUTPUT:", "a", stdout );
+    fib_desc[0] = 10; fib_desc[1] = (long int) &fib;
+#ifdef __ALPHA
+    fib.fib$l_acctl = FIB$M_WRITE;
+#else
+    fib.fib$r_acctl_overlay.fib$l_acctl = FIB$M_WRITE;
+#endif
+    status = sys$qiow ( 0, channel, IO$_ACCESS|IO$M_ACCESS,
+		 &iosb, 0, 0, &fib_desc, 0, 0, 0, &atr, 0 );
+    /*
+     * If status successful, update record byte and perform a MODIFY.
+     */
+    if ( (status&1) == 1 ) status = iosb[0];
+    if ( (status&1) == 1 ) {
+      rat.rtype = 1;		/* fixed length records */
+      rat.rsize = 512;  	/* 512 byte block recrods */
+      rat.ratattrib = 0;        /* Record attributes: none */
+
+     status = sys$qiow
+	( 0, channel, IO$_MODIFY, &iosb, 0, 0, &fib_desc, 0, 0, 0, &atr, 0 );
+       if ( (status&1) == 1 ) status = iosb[0];
+   }
+   sys$dassgn ( channel );
+   if ( (status & 1) == 0 ) fprintf ( stderr,
+	"Failed to convert output file to binary format, status: %d\n", status);
+   return status;
+}
+
+
+static add_item(head, tail, value, count)
+struct list_item **head;
+struct list_item **tail;
+char *value;
+int *count;
+{
+    if (*head == 0)
+	{
+	if (NULL == (*head = calloc(1, sizeof(**head))))
+	    {
+	    errno = ENOMEM;
+	    perror("");
+	    exit(EXIT_ERR);
+	    }
+	*tail = *head;
+	}
+    else
+	if (NULL == ((*tail)->next = calloc(1, sizeof(**head))))
+	    {
+	    errno = ENOMEM;
+	    perror("");
+	    exit(EXIT_ERR);
+	    }
+	else
+	    *tail = (*tail)->next;
+    (*tail)->value = value;
+    ++(*count);
+}
+
+static expand_wild_cards(item, head, tail, count)
+char *item;
+struct ltem_list **head;
+struct ltem_list **tail;
+int *count;
+{
+int expcount = 0;
+int context = 0;
+int status;
+int status_value;
+int had_version;
+$DESCRIPTOR(filespec, item);
+$DESCRIPTOR(defaultspec, "SYS$DISK:[]*.*;");
+$DESCRIPTOR(resultspec, "");
+
+    if (strcspn(item, "*%") == strlen(item))
+	{
+	add_item(head, tail, item, count);
+	return;
+	}
+    resultspec.dsc$b_dtype = DSC$K_DTYPE_T;
+    resultspec.dsc$b_class = DSC$K_CLASS_D;
+    resultspec.dsc$a_pointer = NULL;
+    filespec.dsc$w_length = strlen(item);
+    /*
+     * Only return version specs, if the caller specified a version
+     */
+    had_version = strchr(item, ';');
+    while (1 == (1&lib$find_file(&filespec, &resultspec, &context,
+    				 &defaultspec, 0, &status_value, &0)))
+	{
+	char *string;
+	char *c;
+
+	if (NULL == (string = calloc(1, resultspec.dsc$w_length+1)))
+	    {
+	    errno = ENOMEM;
+	    perror("");
+	    exit(EXIT_ERR);
+	    }
+	strncpy(string, resultspec.dsc$a_pointer, resultspec.dsc$w_length);
+	string[resultspec.dsc$w_length] = '\0';
+	if (NULL == had_version)
+	    *((char *)strrchr(string, ';')) = '\0';
+	/*
+	 * Be consistent with what the C RTL has already done to the rest of
+	 * the argv items and lowercase all of these names.
+	 */
+	for (c = string; *c; ++c)
+	    if (isupper(*c))
+		*c = tolower(*c);
+	add_item(head, tail, string, count);
+	++expcount;
+	}
+    if (expcount == 0)
+	add_item(head, tail, item, count);
+    lib$sfree1_dd(&resultspec);
+    lib$find_file_end(&context);
+}
+
+static int child_st[2];	/* Event Flag set when child process completes	*/
+
+static short child_chan;/* I/O Channel for Pipe Mailbox			*/
+
+static exit_handler(status)
+int *status;
+{
+short iosb[4];
+
+    if (0 == child_st[0])
+	{
+#ifdef DEBUG
+	fprintf(stderr, "Waiting for Child Process to Finnish . . .\n");
+#endif
+	fflush(stdout);	    /* Have to flush pipe for binary data to	*/
+			    /* terminate properly -- <tp@mccall.com>	*/
+#ifdef DEBUG
+	fprintf(stderr, "    stdout flushed. . .\n");
+#endif
+	sys$qio(0, child_chan, IO$_WRITEOF, iosb, 0, 0, 0, 0, 0, 0, 0, 0);
+#ifdef DEBUG
+	fprintf(stderr, "    Pipe terminated. . .\n");
+#endif
+	fclose(stdout);
+#ifdef DEBUG
+	fprintf(stderr, "    stdout closed. . .\n");
+#endif
+	sys$synch(0, child_st);
+	sys$dassgn(child_chan);
+	}
+#ifdef DEBUG
+	fprintf(stderr, "    sync done. . .\n");
+#endif
+}
+
+#include <syidef>		/* System Information Definitions	*/
+
+static sig_child(chan)
+int chan;
+{
+#ifdef DEBUG
+    fprintf(stderr, "Child Completion AST, st: %x\n", child_st[0] );
+#endif
+    if (child_st[0] == 0)
+	{ child_st[0] = 1; }
+    sys$setef ( 0 );
+}
+
+static struct exit_control_block
+    {
+    struct exit_control_block *flink;
+    int	(*exit_routine)();
+    int arg_count;
+    int *status_address;
+    int exit_status;
+    } exit_block =
+    {
+    0,
+    exit_handler,
+    1,
+    &exit_block.exit_status,
+    0
+    };
+
+static char *pipe_and_fork(cmd)
+char *cmd;
+{
+    $DESCRIPTOR(cmddsc, cmd);
+    static char mbxname[64], ef = 0;
+    $DESCRIPTOR(mbxdsc, mbxname);
+    short iosb[4];
+    int status;
+    int pid;
+    struct
+	{
+	short dna_buflen;
+	short dna_itmcod;
+	char *dna_buffer;
+	short *dna_retlen;
+	int listend;
+	} itmlst =
+	{
+	sizeof(mbxname),
+	DVI$_DEVNAM,
+	mbxname,
+	&mbxdsc.dsc$w_length,
+	0
+	};
+    int mbxsize;
+    struct
+	{
+	short mbf_buflen;
+	short mbf_itmcod;
+	int *mbf_maxbuf;
+	short *mbf_retlen;
+	int listend;
+	} syiitmlst =
+	{
+	sizeof(mbxsize),
+	SYI$_MAXBUF,
+	&mbxsize,
+	0,
+	0
+	};
+
+    cmddsc.dsc$w_length = strlen(cmd);
+    /*
+     * Get the SYSGEN parameter MAXBUF, and the smaller of it and 2048 as
+     * the size of the 'pipe' mailbox.
+     */
+    if (1 == (1&(vaxc$errno = sys$getsyiw(0, 0, 0, &syiitmlst, iosb, 0, 0, 0))))
+	vaxc$errno = iosb[0];
+    if (0 == (1&vaxc$errno))
+	{
+ 	errno = EVMSERR;
+	perror("Can't get SYSGEN parameter value for MAXBUF");
+	exit(EXIT_ERR);
+	}
+    if (mbxsize > 2048)
+	mbxsize = 2048;
+    if (0 == (1&(vaxc$errno = sys$crembx(0, &child_chan, mbxsize, mbxsize, 0, 0, 0))))
+	{
+	errno = EVMSERR;
+	perror("Can't create pipe mailbox");
+	exit(EXIT_ERR);
+	}
+    if (1 == (1&(vaxc$errno = sys$getdviw(0, child_chan, 0, &itmlst, iosb,
+    					  0, 0, 0))))
+	vaxc$errno = iosb[0];
+    if (0 == (1&vaxc$errno))
+	{
+ 	errno = EVMSERR;
+	perror("Can't get pipe mailbox device name");
+	exit(EXIT_ERR);
+	}
+    mbxname[mbxdsc.dsc$w_length] = '\0';
+#ifdef DEBUG
+    fprintf(stderr, "Pipe Mailbox Name = '%s'\n", mbxname);
+#endif
+    if (0 == (1&(vaxc$errno = lib$spawn(&cmddsc, &mbxdsc, 0, &1,
+    					0, &pid, child_st, &ef, sig_child,
+    					&child_chan))))
+	{
+	errno = EVMSERR;
+	perror("Can't spawn subprocess");
+	exit(EXIT_ERR);
+	}
+#ifdef DEBUG
+    fprintf(stderr, "Subprocess's Pid = %08X\n", pid);
+#endif
+    sys$dclexh(&exit_block);
+    return(mbxname);
+}
+
+background_process(argc, argv)
+int argc;
+char **argv;
+{
+char command[2048] = "$";
+$DESCRIPTOR(value, command);
+$DESCRIPTOR(cmd, "BACKGROUND$COMMAND");
+$DESCRIPTOR(null, "NLA0:");
+int pid;
+
+    strcat(command, argv[0]);
+    while (--argc)
+	{
+	strcat(command, " \"");
+	strcat(command, *(++argv));
+	strcat(command, "\"");
+	}
+    value.dsc$w_length = strlen(command);
+    if (0 == (1&(vaxc$errno = lib$set_symbol(&cmd, &value))))
+	{
+	errno = EVMSERR;
+	perror("Can't create symbol for subprocess command");
+	exit(EXIT_ERR);
+	}
+    if (0 == (1&(vaxc$errno = lib$spawn(&cmd, &null, 0, &17, 0, &pid))))
+	{
+	errno = EVMSERR;
+	perror("Can't spawn subprocess");
+	exit(EXIT_ERR);
+	}
+#ifdef DEBUG
+    fprintf(stderr, "%s\n", command);
+#endif
+    fprintf(stderr, "%08X\n", pid);
+    return(EXIT_OK);
+}
+
+/* got this off net.sources */
+
+#ifdef	VMS
+#define	index	strchr
+#endif	/*VMS*/
+
+/*
+ * get option letter from argument vector
+ */
+int	opterr = 1,		/* useless, never set or used */
+	optind = 1,		/* index into parent argv vector */
+	optopt;			/* character checked for validity */
+char	*optarg;		/* argument associated with option */
+
+#define BADCH	(int)'?'
+#define EMSG	""
+#define tell(s)	fputs(progname,stderr);fputs(s,stderr); \
+		fputc(optopt,stderr);fputc('\n',stderr);return(BADCH);
+
+getopt(nargc,nargv,ostr)
+int	nargc;
+char	**nargv,
+	*ostr;
+{
+	static char	*place = EMSG;	/* option letter processing */
+	register char	*oli;		/* option letter list index */
+	char	*index();
+	char *progname;
+
+	if(!*place) {			/* update scanning pointer */
+		if(optind >= nargc || *(place = nargv[optind]) != '-' || !*++place) return(EOF);
+		if (*place == '-') {	/* found "--" */
+			++optind;
+			return(EOF);
+		}
+	}				/* option letter okay? */
+	if ((optopt = (int)*place++) == (int)':' || !(oli = index(ostr,optopt))) {
+		if(!*place) ++optind;
+#ifdef VMS
+		progname = strrchr(nargv[0],']');
+#else
+		progname = rindex(nargv[0],'/');
+#endif
+		if (!progname) progname = nargv[0]; else progname++;
+		tell(": illegal option -- ");
+	}
+	if (*++oli != ':') {		/* don't need argument */
+		optarg = NULL;
+		if (!*place) ++optind;
+	}
+	else {				/* need an argument */
+		if (*place) optarg = place;	/* no white space */
+		else if (nargc <= ++optind) {	/* no arg */
+			place = EMSG;
+#ifdef VMS
+			progname = strrchr(nargv[0],']');
+#else
+			progname = rindex(nargv[0],'/');
+#endif
+			if (!progname) progname = nargv[0]; else progname++;
+			tell(": option requires an argument -- ");
+		}
+	 	else optarg = nargv[optind];	/* white space */
+		place = EMSG;
+		++optind;
+	}
+	return(optopt);			/* dump back option letter */
+}
diff --git a/lib/libpgm.h b/lib/libpgm.h
new file mode 100644
index 00000000..ba51f9a9
--- /dev/null
+++ b/lib/libpgm.h
@@ -0,0 +1,24 @@
+/* This is the intra-libnetpbm interface header file for libpgm*.c
+*/
+
+#ifndef LIBPGM_H_INCLUDED
+#define LIBPGM_H_INCLUDED
+
+#include "pgm.h"
+
+void
+pgm_readpgminitrest(FILE * const file, 
+                    int *  const colsP, 
+                    int *  const rowsP, 
+                    gray * const maxvalP);
+
+gray
+pgm_getrawsample(FILE * const file,
+                 gray   const maxval);
+
+void
+pgm_writerawsample(FILE * const fileP,
+                   gray   const val,
+                   gray   const maxval);
+
+#endif
diff --git a/lib/libpgm1.c b/lib/libpgm1.c
new file mode 100644
index 00000000..75fa365b
--- /dev/null
+++ b/lib/libpgm1.c
@@ -0,0 +1,324 @@
+/* libpgm1.c - pgm utility library part 1
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* See libpbm.c for the complicated explanation of this 32/64 bit file
+   offset stuff.
+*/
+#define _FILE_OFFSET_BITS 64
+#define _LARGE_FILES  
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "pgm.h"
+#include "libpgm.h"
+#include "pbm.h"
+#include "libpbm.h"
+#include "pam.h"
+#include "libpam.h"
+#include "fileio.h"
+#include "mallocvar.h"
+
+
+gray *
+pgm_allocrow(unsigned int const cols) {
+
+    gray * grayrow;
+
+    MALLOCARRAY(grayrow, cols);
+
+    if (grayrow == NULL)
+        pm_error("Unable to allocate space for a %u-column gray row", cols);
+
+    return grayrow;
+}
+
+
+
+void 
+pgm_init(int *   const argcP,
+         char ** const argv) {
+
+    pbm_init( argcP, argv );
+}
+
+
+
+void
+pgm_nextimage(FILE * const file, int * const eofP) {
+    pm_nextimage(file, eofP);
+}
+
+
+
+void
+pgm_readpgminitrest(FILE * const fileP, 
+                    int *  const colsP, 
+                    int *  const rowsP, 
+                    gray * const maxvalP) {
+
+    gray maxval;
+    
+    /* Read size. */
+    *colsP = (int)pm_getuint(fileP);
+    *rowsP = (int)pm_getuint(fileP);
+
+    /* Read maxval. */
+    maxval = pm_getuint(fileP);
+    if (maxval > PGM_OVERALLMAXVAL)
+        pm_error("maxval of input image (%u) is too large.  "
+                 "The maximum allowed by PGM is %u.", 
+                 maxval, PGM_OVERALLMAXVAL);
+    if (maxval == 0)
+        pm_error("maxval of input image is zero.");
+
+    *maxvalP = maxval;
+}
+
+
+
+static void
+validateComputableSize(unsigned int const cols,
+                       unsigned int const rows) {
+/*----------------------------------------------------------------------------
+   Validate that the dimensions of the image are such that it can be
+   processed in typical ways on this machine without worrying about
+   overflows.  Note that in C, arithmetic is always modulus
+   arithmetic, so if your values are too big, the result is not what
+   you expect.  That failed expectation can be disastrous if you use
+   it to allocate memory.
+
+   A common operation is adding 1 or 2 to the highest row or
+   column number in the image, so we make sure that's possible.
+-----------------------------------------------------------------------------*/
+    if (cols > INT_MAX - 2)
+        pm_error("image width (%u) too large to be processed", cols);
+    if (rows > INT_MAX - 2)
+        pm_error("image height (%u) too large to be processed", rows);
+}
+
+
+
+void
+pgm_readpgminit(FILE * const fileP,
+                int *  const colsP, 
+                int *  const rowsP,
+                gray * const maxvalP,
+                int *  const formatP) {
+
+    int realFormat;
+
+    /* Check magic number. */
+    realFormat = pm_readmagicnumber(fileP);
+    switch (PAM_FORMAT_TYPE(realFormat)) {
+    case PGM_TYPE:
+        *formatP = realFormat;
+        pgm_readpgminitrest(fileP, colsP, rowsP, maxvalP);
+        break;
+        
+    case PBM_TYPE:
+        *formatP = realFormat;
+        pbm_readpbminitrest(fileP, colsP, rowsP);
+        
+        /* Mathematically, it makes the most sense for the maxval of a PBM
+           file seen as a PGM to be 1.  But we tried this for a while and
+           found that it causes unexpected results and frequent need for a
+           Pnmdepth stage to convert the maxval to 255.  You see, when you
+           transform a PGM file in a way that causes interpolated gray shades,
+           there's no in-between value to use when maxval is 1.  It's really
+           hard even to discover that your lack of Pnmdepth is your problem.
+           So we pick 255, which is the most common PGM maxval, and the highest
+           resolution you can get without increasing the size of the PGM 
+           image.
+
+           So this means some programs that are capable of exploiting the
+           bi-level nature of a PBM file must be PNM programs instead of PGM
+           programs.
+        */
+        
+        *maxvalP = PGM_MAXMAXVAL;
+        break;
+
+    case PAM_TYPE:
+        pnm_readpaminitrestaspnm(fileP, colsP, rowsP, maxvalP, formatP);
+
+        if (PAM_FORMAT_TYPE(*formatP) != PGM_TYPE)
+            pm_error("Format of PAM input is not consistent with PGM");
+
+        break;
+
+    default:
+        pm_error("bad magic number - not a pgm or pbm file");
+    }
+    validateComputableSize(*colsP, *rowsP);
+}
+
+
+
+gray
+pgm_getrawsample(FILE * const file,
+                 gray   const maxval) {
+
+    if (maxval < 256) {
+        /* The sample is just one byte.  Read it. */
+        return(pm_getrawbyte(file));
+    } else {
+        /* The sample is two bytes.  Read both. */
+        unsigned char byte_pair[2];
+        size_t pairs_read;
+
+        pairs_read = fread(&byte_pair, 2, 1, file);
+        if (pairs_read == 0) 
+            pm_error("EOF /read error while reading a long sample");
+        /* This could be a few instructions faster if exploited the internal
+           format (i.e. endianness) of a pixval.  Then we might be able to
+           skip the shifting and oring.
+           */
+        return((byte_pair[0]<<8) | byte_pair[1]);
+    }
+}
+
+
+
+void
+pgm_readpgmrow(FILE * const file,
+               gray * const grayrow, 
+               int    const cols,
+               gray   const maxval,
+               int    const format) {
+
+    switch (format) {
+    case PGM_FORMAT: {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            grayrow[col] = pm_getuint(file);
+            if (grayrow[col] > maxval)
+                pm_error("value out of bounds (%u > %u)",
+                         grayrow[col], maxval);
+        }
+    }
+    break;
+        
+    case RPGM_FORMAT: {
+        unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
+        int          const bytesPerRow    = cols * bytesPerSample;
+
+        unsigned char * rowBuffer;
+        ssize_t rc;
+
+        MALLOCARRAY(rowBuffer, bytesPerRow);
+        if (rowBuffer == NULL)
+            pm_error("Unable to allocate memory for row buffer "
+                     "for %u columns", cols);
+
+        rc = fread(rowBuffer, 1, bytesPerRow, file);
+        if (rc == 0)
+            pm_error("Error reading row.  fread() errno=%d (%s)",
+                     errno, strerror(errno));
+        else if (rc != bytesPerRow)
+            pm_error("Error reading row.  Short read of %u bytes "
+                     "instead of %u", rc, bytesPerRow);
+
+        if (maxval < 256) {
+            unsigned int col;
+            for (col = 0; col < cols; ++col)
+                grayrow[col] = (gray)rowBuffer[col];
+        } else {
+            unsigned int bufferCursor;
+            unsigned int col;
+
+            bufferCursor = 0;  /* Start at beginning of rowBuffer[] */
+
+            for (col = 0; col < cols; ++col) {
+                gray g;
+
+                g = rowBuffer[bufferCursor++] << 8;
+                g |= rowBuffer[bufferCursor++];
+
+                grayrow[col] = g;
+            }
+        }
+        free(rowBuffer);
+    }
+        break;
+    
+    case PBM_FORMAT:
+    case RPBM_FORMAT: {
+        bit * bitrow;
+        int col;
+
+        bitrow = pbm_allocrow(cols);
+        pbm_readpbmrow( file, bitrow, cols, format );
+        for (col = 0; col < cols; ++col)
+            grayrow[col] = (bitrow[col] == PBM_WHITE ) ? maxval : 0;
+        pbm_freerow(bitrow);
+    }
+        break;
+        
+    default:
+        pm_error( "can't happen" );
+    }
+}
+
+
+
+gray **
+pgm_readpgm(FILE * const file,
+            int *  const colsP,
+            int *  const rowsP, 
+            gray * const maxvalP) {
+
+    gray** grays;
+    int row;
+    int format;
+
+    pgm_readpgminit( file, colsP, rowsP, maxvalP, &format );
+    
+    grays = pgm_allocarray( *colsP, *rowsP );
+    
+    for ( row = 0; row < *rowsP; ++row )
+        pgm_readpgmrow( file, grays[row], *colsP, *maxvalP, format );
+    
+    return grays;
+}
+
+
+
+void
+pgm_check(FILE *               const file, 
+          enum pm_check_type   const check_type, 
+          int                  const format, 
+          int                  const cols, 
+          int                  const rows, 
+          gray                 const maxval,
+          enum pm_check_code * const retval_p) {
+
+    if (rows < 0)
+        pm_error("Invalid number of rows passed to pgm_check(): %d", rows);
+    if (cols < 0)
+        pm_error("Invalid number of columns passed to pgm_check(): %d", cols);
+    
+    if (check_type != PM_CHECK_BASIC) {
+        if (retval_p) *retval_p = PM_CHECK_UNKNOWN_TYPE;
+    } else if (PGM_FORMAT_TYPE(format) == PBM_TYPE) {
+        pbm_check(file, check_type, format, cols, rows, retval_p);
+    } else if (format != RPGM_FORMAT) {
+        if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
+    } else {        
+        pm_filepos const bytes_per_row = cols * (maxval > 255 ? 2 : 1);
+        pm_filepos const need_raster_size = rows * bytes_per_row;
+        
+        pm_check(file, check_type, need_raster_size, retval_p);
+        
+    }
+}
diff --git a/lib/libpgm2.c b/lib/libpgm2.c
new file mode 100644
index 00000000..7a04f09b
--- /dev/null
+++ b/lib/libpgm2.c
@@ -0,0 +1,226 @@
+/* libpgm2.c - pgm utility library part 2
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+#include <errno.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "pgm.h"
+#include "libpgm.h"
+
+
+
+void
+pgm_writepgminit(FILE * const fileP, 
+                 int    const cols, 
+                 int    const rows, 
+                 gray   const maxval, 
+                 int    const forceplain) {
+
+    bool const plainFormat = forceplain || pm_plain_output;
+
+    if (maxval > PGM_OVERALLMAXVAL && !plainFormat) 
+        pm_error("too-large maxval passed to ppm_writepgminit(): %d.\n"
+                 "Maximum allowed by the PGM format is %d.",
+                 maxval, PGM_OVERALLMAXVAL);
+
+    fprintf(fileP, "%c%c\n%d %d\n%d\n", 
+            PGM_MAGIC1, 
+            plainFormat || maxval >= 1<<16 ? PGM_MAGIC2 : RPGM_MAGIC2, 
+            cols, rows, maxval );
+#ifdef VMS
+    if (!plainFormat)
+        set_outfile_binary();
+#endif
+}
+
+
+
+static void
+putus(unsigned short const n, 
+      FILE *         const fileP) {
+
+    if (n >= 10)
+        putus(n / 10, fileP);
+    putc('0' + n % 10, fileP);
+}
+
+
+
+void
+pgm_writerawsample(FILE * const fileP,
+                   gray   const val,
+                   gray   const maxval) {
+
+    if (maxval < 256) {
+        /* Samples fit in one byte, so write just one byte */
+        int rc;
+        rc = putc(val, fileP);
+        if (rc == EOF)
+            pm_error("Error writing single byte sample to file");
+    } else {
+        /* Samples are too big for one byte, so write two */
+        int n_written;
+        unsigned char outval[2];
+        /* We could save a few instructions if we exploited the internal
+           format of a gray, i.e. its endianness.  Then we might be able
+           to skip the shifting and anding.
+           */
+        outval[0] = val >> 8;
+        outval[1] = val & 0xFF;
+        n_written = fwrite(&outval, 2, 1, fileP);
+        if (n_written == 0) 
+            pm_error("Error writing double byte sample to file");
+    }
+}
+
+
+
+static void
+format1bpsRow(const gray *    const grayrow,
+              unsigned int    const cols,
+              unsigned char * const rowBuffer) {
+
+    /* single byte samples. */
+
+    unsigned int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;
+
+    for (col = 0; col < cols; ++col)
+        rowBuffer[bufferCursor++] = grayrow[col];
+}
+
+
+
+
+static void
+format2bpsRow(const gray    * const grayrow,
+              unsigned int    const cols,
+              unsigned char * const rowBuffer) {
+
+    /* two byte samples. */
+
+    unsigned int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;
+
+    for (col = 0; col < cols; ++col) {
+        gray const val = grayrow[col];
+        
+        rowBuffer[bufferCursor++] = val >> 8;
+        rowBuffer[bufferCursor++] = (unsigned char) val;
+    }
+}
+
+
+
+static void
+writepgmrowraw(FILE *       const fileP,
+               const gray * const grayrow,
+               unsigned int const cols,
+               gray         const maxval) {
+
+    unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
+    unsigned int const bytesPerRow    = cols * bytesPerSample;
+
+    unsigned char * rowBuffer;
+    ssize_t rc;
+
+    MALLOCARRAY(rowBuffer, bytesPerRow);
+
+    if (rowBuffer == NULL)
+        pm_error("Unable to allocate memory for row buffer "
+                 "for %u columns", cols);
+
+    if (maxval < 256)
+        format1bpsRow(grayrow, cols, rowBuffer);
+    else
+        format2bpsRow(grayrow, cols, rowBuffer);
+
+    rc = fwrite(rowBuffer, 1, bytesPerRow, fileP);
+
+    if (rc < 0)
+        pm_error("Error writing row.  fwrite() errno=%d (%s)",
+                 errno, strerror(errno));
+    else if (rc != bytesPerRow)
+        pm_error("Error writing row.  Short write of %u bytes "
+                 "instead of %u", rc, bytesPerRow);
+
+    free(rowBuffer);
+}
+
+
+
+static void
+writepgmrowplain(FILE *       const fileP,
+                 const gray * const grayrow, 
+                 unsigned int const cols, 
+                 gray         const maxval) {
+
+    int col, charcount;
+
+    charcount = 0;
+    for (col = 0; col < cols; ++col) {
+        if (charcount >= 65) {
+            putc('\n', fileP);
+            charcount = 0;
+        } else if (charcount > 0) {
+            putc(' ', fileP);
+            ++charcount;
+        }
+#ifdef DEBUG
+        if (grayrow[col] > maxval)
+            pm_error("value out of bounds (%u > %u)", grayrow[col], maxval);
+#endif /*DEBUG*/
+        putus((unsigned short)grayrow[col], fileP);
+        charcount += 3;
+    }
+    if (charcount > 0)
+        putc('\n', fileP);
+}
+
+
+
+void
+pgm_writepgmrow(FILE *       const fileP, 
+                const gray * const grayrow, 
+                int          const cols, 
+                gray         const maxval, 
+                int          const forceplain) {
+
+    if (forceplain || pm_plain_output || maxval >= 1<<16)
+        writepgmrowplain(fileP, grayrow, cols, maxval);
+    else
+        writepgmrowraw(fileP, grayrow, cols, maxval);
+}
+
+
+
+void
+pgm_writepgm(FILE *  const fileP,
+             gray ** const grays,
+             int     const cols,
+             int     const rows,
+             gray    const maxval,
+             int     const forceplain) {
+
+    unsigned int row;
+
+    pgm_writepgminit(fileP, cols, rows, maxval, forceplain);
+
+    for (row = 0; row < rows; ++row)
+        pgm_writepgmrow(fileP, grays[row], cols, maxval, forceplain);
+}
diff --git a/lib/libpm.c b/lib/libpm.c
new file mode 100644
index 00000000..2e563a09
--- /dev/null
+++ b/lib/libpm.c
@@ -0,0 +1,1494 @@
+/**************************************************************************
+                                  libpm.c
+***************************************************************************
+  This is the most fundamental Netpbm library.  It contains routines
+  not specific to any particular Netpbm format.
+
+  Some of the subroutines in this library are intended and documented
+  for use by Netpbm users, but most of them are just used by other
+  Netpbm library subroutines.
+
+  Before May 2001, this function was served by the libpbm library
+  (in addition to being the library for handling the PBM format).
+
+**************************************************************************/
+#define _SVID_SOURCE
+    /* Make sure P_tmpdir is defined in GNU libc 2.0.7 (_XOPEN_SOURCE 500
+       does it in other libc's).  pm_config.h defines TMPDIR as P_tmpdir
+       in some environments.
+    */
+#define _XOPEN_SOURCE 500    /* Make sure ftello, fseeko are defined */
+#define _LARGEFILE_SOURCE 1  /* Make sure ftello, fseeko are defined */
+#define _LARGEFILE64_SOURCE 1 
+#define _FILE_OFFSET_BITS 64
+    /* This means ftello() is really ftello64() and returns a 64 bit file
+       position.  Unless the C library doesn't have ftello64(), in which 
+       case ftello() is still just ftello().
+
+       Likewise for all the other C library file functions.
+
+       And off_t and fpos_t are 64 bit types instead of 32.  Consequently,
+       pm_filepos_t might be 64 bits instead of 32.
+    */
+#define _LARGE_FILES  
+    /* This does for AIX what _FILE_OFFSET_BITS=64 does for GNU */
+#define _LARGE_FILE_API
+    /* This makes the the x64() functions available on AIX */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <setjmp.h>
+#ifdef __DJGPP__
+  #include <io.h>
+#endif
+
+#include "pm_c_util.h"
+#include "version.h"
+#include "compile.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "pm.h"
+
+/* The following are set by pm_init(), then used by subsequent calls to other
+   pm_xxx() functions.
+   */
+static const char* pm_progname;
+static bool pm_showmessages;  
+    /* Programs should display informational messages (because the user didn't
+       specify the --quiet option).
+    */
+
+int pm_plain_output;
+    /* Boolean: programs should produce output in plain format */
+
+static jmp_buf * pm_jmpbufP = NULL;
+    /* A description of the point to which the program should hyperjump
+       if a libnetpbm function encounters an error (libnetpbm functions
+       don't normally return in that case).
+
+       User sets this to something in his own extra-library context.
+       Libnetpbm routines that have something that needs to be cleaned up
+       preempt it.
+
+       NULL, which is the default value, means when a libnetpbm function
+       encounters an error, it causes the process to exit.
+    */
+
+
+
+void
+pm_setjmpbuf(jmp_buf * const jmpbufP) {
+    pm_jmpbufP = jmpbufP;
+}
+
+
+
+void
+pm_setjmpbufsave(jmp_buf *  const jmpbufP,
+                 jmp_buf ** const oldJmpbufPP) {
+
+    *oldJmpbufPP = pm_jmpbufP;
+    pm_jmpbufP = jmpbufP;
+}
+
+
+
+void
+pm_longjmp(void) {
+
+    if (pm_jmpbufP)
+        longjmp(*pm_jmpbufP, 1);
+    else
+        exit(1);
+}
+
+
+
+void
+pm_usage(const char usage[]) {
+    pm_error("usage:  %s %s", pm_progname, usage);
+}
+
+
+
+void
+pm_perror(const char reason[] ) {
+
+    if (reason != NULL && strlen(reason) != 0)
+        pm_error("%s - errno=%d (%s)", reason, errno, strerror(errno));
+    else
+        pm_error("Something failed with errno=%d (%s)", 
+                 errno, strerror(errno));
+}
+
+
+
+void PM_GNU_PRINTF_ATTR(1,2)
+pm_message(const char format[], ...) {
+
+    va_list args;
+
+    va_start(args, format);
+
+    if (pm_showmessages) {
+        fprintf(stderr, "%s: ", pm_progname);
+        vfprintf(stderr, format, args);
+        fputc('\n', stderr);
+    }
+    va_end(args);
+}
+
+
+
+void PM_GNU_PRINTF_ATTR(1,2)
+pm_error(const char format[], ...) {
+    va_list args;
+
+    va_start(args, format);
+
+    fprintf(stderr, "%s: ", pm_progname);
+    vfprintf(stderr, format, args);
+    fputc('\n', stderr);
+    va_end(args);
+
+    pm_longjmp();
+}
+
+
+/* Variable-sized arrays. */
+
+char *
+pm_allocrow(unsigned int const cols,
+            unsigned int const size) {
+
+    char * itrow;
+
+    if (UINT_MAX / cols < size)
+        pm_error("Arithmetic overflow multiplying %u by %u to get the "
+                 "size of a row to allocate.", cols, size);
+
+    itrow = malloc(cols * size);
+    if (itrow == NULL)
+        pm_error("out of memory allocating a row");
+
+    return itrow;
+}
+
+
+
+void
+pm_freerow(char * const itrow) {
+    free(itrow);
+}
+
+
+
+char**
+pm_allocarray(int const cols, int const rows, int const size )  {
+/*----------------------------------------------------------------------------
+   Allocate an array of 'rows' rows of 'cols' columns each, with each
+   element 'size' bytes.
+
+   We use a special format where we tack on an extra element to the row
+   index to indicate the format of the array.
+
+   We have two ways of allocating the space: fragmented and
+   unfragmented.  In both, the row index (plus the extra element) is
+   in one block of memory.  In the fragmented format, each row is
+   also in an independent memory block, and the extra row pointer is
+   NULL.  In the unfragmented format, all the rows are in a single
+   block of memory called the row heap and the extra row pointer is
+   the address of that block.
+
+   We use unfragmented format if possible, but if the allocation of the
+   row heap fails, we fall back to fragmented.
+-----------------------------------------------------------------------------*/
+    char** rowIndex;
+    char * rowheap;
+
+    MALLOCARRAY(rowIndex, rows + 1);
+    if (rowIndex == NULL)
+        pm_error("out of memory allocating row index (%u rows) for an array",
+                 rows);
+    rowheap = malloc(rows * cols * size);
+    if (rowheap == NULL) {
+        /* We couldn't get the whole heap in one block, so try fragmented
+           format.
+        */
+        unsigned int row;
+        
+        rowIndex[rows] = NULL;   /* Declare it fragmented format */
+
+        for (row = 0; row < rows; ++row) {
+            rowIndex[row] = pm_allocrow(cols, size);
+            if (rowIndex[row] == NULL)
+                pm_error("out of memory allocating Row %u "
+                         "(%u columns, %u bytes per tuple) "
+                         "of an array", row, cols, size);
+        }
+    } else {
+        /* It's unfragmented format */
+        unsigned int row;
+        rowIndex[rows] = rowheap;  /* Declare it unfragmented format */
+
+        for (row = 0; row < rows; ++row)
+            rowIndex[row] = &(rowheap[row * cols * size]);
+    }
+    return rowIndex;
+}
+
+
+
+void
+pm_freearray(char ** const rowIndex, 
+             int     const rows) {
+
+    void * const rowheap = rowIndex[rows];
+
+    if (rowheap != NULL)
+        free(rowheap);
+    else {
+        unsigned int row;
+        for (row = 0; row < rows; ++row)
+            pm_freerow(rowIndex[row]);
+    }
+    free(rowIndex);
+}
+
+
+
+/* Case-insensitive keyword matcher. */
+
+int
+pm_keymatch(char *       const strarg, 
+            const char * const keywordarg, 
+            int          const minchars) {
+    int len;
+    const char *keyword;
+    char *str;
+
+    str = strarg;
+    keyword = keywordarg;
+
+    len = strlen( str );
+    if ( len < minchars )
+        return 0;
+    while ( --len >= 0 )
+        {
+        register char c1, c2;
+
+        c1 = *str++;
+        c2 = *keyword++;
+        if ( c2 == '\0' )
+            return 0;
+        if ( ISUPPER( c1 ) )
+            c1 = tolower( c1 );
+        if ( ISUPPER( c2 ) )
+            c2 = tolower( c2 );
+        if ( c1 != c2 )
+            return 0;
+        }
+    return 1;
+}
+
+
+/* Log base two hacks. */
+
+int
+pm_maxvaltobits(int const maxval) {
+    if ( maxval <= 1 )
+        return 1;
+    else if ( maxval <= 3 )
+        return 2;
+    else if ( maxval <= 7 )
+        return 3;
+    else if ( maxval <= 15 )
+        return 4;
+    else if ( maxval <= 31 )
+        return 5;
+    else if ( maxval <= 63 )
+        return 6;
+    else if ( maxval <= 127 )
+        return 7;
+    else if ( maxval <= 255 )
+        return 8;
+    else if ( maxval <= 511 )
+        return 9;
+    else if ( maxval <= 1023 )
+        return 10;
+    else if ( maxval <= 2047 )
+        return 11;
+    else if ( maxval <= 4095 )
+        return 12;
+    else if ( maxval <= 8191 )
+        return 13;
+    else if ( maxval <= 16383 )
+        return 14;
+    else if ( maxval <= 32767 )
+        return 15;
+    else if ( (long) maxval <= 65535L )
+        return 16;
+    else
+        pm_error( "maxval of %d is too large!", maxval );
+        return -1;  /* Should never come here */
+}
+
+int
+pm_bitstomaxval(int const bits) {
+    return ( 1 << bits ) - 1;
+}
+
+
+unsigned int PURE_FN_ATTR
+pm_lcm(unsigned int const x, 
+       unsigned int const y,
+       unsigned int const z,
+       unsigned int const limit) {
+/*----------------------------------------------------------------------------
+  Compute the least common multiple of 'x', 'y', and 'z'.  If it's bigger than
+  'limit', though, just return 'limit'.
+-----------------------------------------------------------------------------*/
+    unsigned int biggest;
+    unsigned int candidate;
+
+    if (x == 0 || y == 0 || z == 0)
+        pm_error("pm_lcm(): Least common multiple of zero taken.");
+
+    biggest = MAX(x, MAX(y,z));
+
+    candidate = biggest;
+    while (((candidate % x) != 0 ||       /* not a multiple of x */
+            (candidate % y) != 0 ||       /* not a multiple of y */
+            (candidate % z) != 0 ) &&     /* not a multiple of z */
+           candidate <= limit)
+        candidate += biggest;
+
+    if (candidate > limit) 
+        candidate = limit;
+
+    return candidate;
+}
+
+
+/* Initialization. */
+
+
+#ifdef VMS
+static const char *
+vmsProgname(int * const argcP, char * argv[]) {   
+    char **temp_argv = argv;
+    int old_argc = *argcP;
+    int i;
+    const char * retval;
+    
+    getredirection( argcP, &temp_argv );
+    if (*argcP > old_argc) {
+        /* Number of command line arguments has increased */
+        fprintf( stderr, "Sorry!! getredirection() for VMS has "
+                 "changed the argument list!!!\n");
+        fprintf( stderr, "This is intolerable at the present time, "
+                 "so we must stop!!!\n");
+        exit(1);
+    }
+    for (i=0; i<*argcP; i++)
+        argv[i] = temp_argv[i];
+    retval = strrchr( argv[0], '/');
+    if ( retval == NULL ) retval = rindex( argv[0], ']');
+    if ( retval == NULL ) retval = rindex( argv[0], '>');
+
+    return retval;
+}
+#endif
+
+
+
+void
+pm_init(const char * const progname, unsigned int const flags) {
+/*----------------------------------------------------------------------------
+   Initialize static variables that Netpbm library routines use.
+
+   Any user of Netpbm library routines is expected to call this at the
+   beginning of this program, before any other Netpbm library routines.
+
+   A program may call this via pm_proginit() instead, though.
+-----------------------------------------------------------------------------*/
+    pm_setMessage(FALSE, NULL);
+
+    pm_progname = progname;
+
+#ifdef O_BINARY
+#ifdef HAVE_SETMODE
+    /* Set the stdin and stdout mode to binary.  This means nothing on Unix,
+       but matters on Windows.
+       
+       Note that stdin and stdout aren't necessarily image files.  In
+       particular, stdout is sometimes text for human consumption,
+       typically printed on the terminal.  Binary mode isn't really
+       appropriate for that case.  We do this setting here without
+       any knowledge of how stdin and stdout are being used because it is
+       easy.  But we do make an exception for the case that we know the
+       file is a terminal, to get a little closer to doing the right
+       thing.  
+    */
+    if (!isatty(0)) setmode(0,O_BINARY);  /* Standard Input */
+    if (!isatty(1)) setmode(1,O_BINARY);  /* Standard Output */
+#endif /* HAVE_SETMODE */
+#endif /* O_BINARY */
+}
+
+
+
+static void
+showVersion(void) {
+    pm_message( "Using libnetpbm from Netpbm Version: %s", NETPBM_VERSION );
+#if defined(COMPILE_TIME) && defined(COMPILED_BY)
+    pm_message( "Compiled %s by user \"%s\"",
+                COMPILE_TIME, COMPILED_BY );
+#endif
+#ifdef BSD
+    pm_message( "BSD defined" );
+#endif /*BSD*/
+#ifdef SYSV
+#ifdef VMS
+    pm_message( "VMS & SYSV defined" );
+#else
+    pm_message( "SYSV defined" );
+#endif
+#endif /*SYSV*/
+#ifdef MSDOS
+    pm_message( "MSDOS defined" );
+#endif /*MSDOS*/
+#ifdef AMIGA
+    pm_message( "AMIGA defined" );
+#endif /* AMIGA */
+    {
+        const char * rgbdef;
+        pm_message( "RGB_ENV='%s'", RGBENV );
+        rgbdef = getenv(RGBENV);
+        if( rgbdef )
+            pm_message( "RGBENV= '%s' (env vbl set to '%s')", 
+                        RGBENV, rgbdef );
+        else
+            pm_message( "RGBENV= '%s' (env vbl is unset)", RGBENV);
+    }
+}
+
+
+
+static void
+showNetpbmHelp(const char progname[]) {
+/*----------------------------------------------------------------------------
+  Tell the user where to get help for this program, assuming it is a Netpbm
+  program (a program that comes with the Netpbm package, as opposed to a 
+  program that just uses the Netpbm libraries).
+
+  Tell him to go to the URL listed in the Netpbm configuration file.
+  The Netpbm configuration file is the file named by the NETPBM_CONF
+  environment variable, or /etc/netpbm if there is no such environment
+  variable.
+
+  If the configuration file doesn't exist or can't be read, or doesn't
+  contain a DOCURL value, tell him to go to a hardcoded source for
+  documentation.
+-----------------------------------------------------------------------------*/
+    const char * netpbmConfigFileName;
+    FILE * netpbmConfigFile;
+    char * docurl;
+
+    if (getenv("NETPBM_CONF"))
+        netpbmConfigFileName = getenv("NETPBM_CONF");
+    else 
+        netpbmConfigFileName = "/etc/netpbm";
+    
+    netpbmConfigFile = fopen(netpbmConfigFileName, "r");
+    if (netpbmConfigFile == NULL) {
+        pm_message("Unable to open Netpbm configuration file '%s'.  "
+                   "Errno = %d (%s).  "
+                   "Use the NETPBM_CONF environment variable "
+                   "to control the identity of the Netpbm configuration file.",
+                   netpbmConfigFileName,errno, strerror(errno));
+        docurl = NULL;
+    } else {
+        docurl = NULL;  /* default */
+        while (!feof(netpbmConfigFile) && !ferror(netpbmConfigFile)) {
+            char line[80+1];
+            fgets(line, sizeof(line), netpbmConfigFile);
+            if (line[0] != '#') {
+                sscanf(line, "docurl=%s", docurl);
+            }
+        }
+        if (docurl == NULL)
+            pm_message("No 'docurl=' line in Netpbm configuration file '%s'.",
+                       netpbmConfigFileName);
+    }
+    if (docurl == NULL)
+        pm_message("We have no reliable indication of where the Netpbm "
+                   "documentation is, but try "
+                   "http://netpbm.sourceforge.net or email "
+                   "Bryan Henderson (bryanh@giraffe-data.com) for help.");
+    else
+        pm_message("This program is part of the Netpbm package.  Find "
+                   "documentation for it at %s/%s\n", docurl, progname);
+}
+
+
+
+void
+pm_proginit(int * const argcP, char * argv[]) {
+/*----------------------------------------------------------------------------
+   Do various initialization things that all programs in the Netpbm package,
+   and programs that emulate such programs, should do.
+
+   This includes processing global options.
+
+   This includes calling pm_init() to initialize the Netpbm libraries.
+-----------------------------------------------------------------------------*/
+    int argn, i;
+    const char * progname;
+    bool showmessages;
+    bool show_version;
+        /* We're supposed to just show the version information, then exit the
+           program.
+        */
+    bool show_help;
+        /* We're supposed to just tell user where to get help, then exit the
+           program.
+        */
+    
+    /* Extract program name. */
+#ifdef VMS
+    progname = vmsProgname(argcP, argv);
+#else
+    progname = strrchr( argv[0], '/');
+#endif
+    if (progname == NULL)
+        progname = argv[0];
+    else
+        ++progname;
+
+    pm_init(progname, 0);
+
+    /* Check for any global args. */
+    showmessages = TRUE;
+    show_version = FALSE;
+    show_help = FALSE;
+    pm_plain_output = FALSE;
+    for (argn = 1; argn < *argcP; ++argn) {
+        if (pm_keymatch(argv[argn], "-quiet", 6) ||
+            pm_keymatch(argv[argn], "--quiet", 7)) 
+            showmessages = FALSE;
+        else if (pm_keymatch(argv[argn], "-version", 8) ||
+                   pm_keymatch(argv[argn], "--version", 9)) 
+            show_version = TRUE;
+        else if (pm_keymatch(argv[argn], "-help", 5) ||
+                 pm_keymatch(argv[argn], "--help", 6) ||
+                 pm_keymatch(argv[argn], "-?", 2)) 
+            show_help = TRUE;
+        else if (pm_keymatch(argv[argn], "-plain", 6) ||
+                 pm_keymatch(argv[argn], "--plain", 7))
+            pm_plain_output = TRUE;
+        else
+            continue;
+        for (i = argn + 1; i <= *argcP; ++i)
+            argv[i - 1] = argv[i];
+        --(*argcP);
+    }
+
+    pm_setMessage((unsigned int) showmessages, NULL);
+
+    if (show_version) {
+        showVersion();
+        exit( 0 );
+    } else if (show_help) {
+        pm_error("Use 'man %s' for help.", progname);
+        /* If we can figure out a way to distinguish Netpbm programs from 
+           other programs using the Netpbm libraries, we can do better here.
+        */
+        if (0)
+            showNetpbmHelp(progname);
+        exit(0);
+    }
+}
+
+
+void
+pm_setMessage(int const newState, int * const oldStateP) {
+    
+    if (oldStateP)
+        *oldStateP = pm_showmessages;
+
+    pm_showmessages = !!newState;
+}
+
+
+char *
+pm_arg0toprogname(const char arg0[]) {
+/*----------------------------------------------------------------------------
+   Given a value for argv[0] (a command name or file name passed to a 
+   program in the standard C calling sequence), return the name of the
+   Netpbm program to which is refers.
+
+   In the most ordinary case, this is simply the argument itself.
+
+   But if the argument contains a slash, it is the part of the argument 
+   after the last slash, and if there is a .exe on it (as there is for
+   DJGPP), that is removed.
+
+   The return value is in static storage within.  It is null-terminated,
+   but truncated at 64 characters.
+-----------------------------------------------------------------------------*/
+    static char retval[64+1];
+    char *slash_pos;
+
+    /* Chop any directories off the left end */
+    slash_pos = strrchr(arg0, '/');
+
+    if (slash_pos == NULL) {
+        strncpy(retval, arg0, sizeof(retval));
+        retval[sizeof(retval)-1] = '\0';
+    } else {
+        strncpy(retval, slash_pos +1, sizeof(retval));
+        retval[sizeof(retval)-1] = '\0';
+    }
+
+    /* Chop any .exe off the right end */
+    if (strlen(retval) >= 4 && strcmp(retval+strlen(retval)-4, ".exe") == 0)
+        retval[strlen(retval)-4] = 0;
+
+    return(retval);
+}
+
+
+
+/* File open/close that handles "-" as stdin/stdout and checks errors. */
+
+FILE*
+pm_openr(const char * const name) {
+    FILE* f;
+
+    if (strcmp(name, "-") == 0)
+        f = stdin;
+    else {
+#ifndef VMS
+        f = fopen(name, "rb");
+#else
+        f = fopen(name, "r", "ctx=stm");
+#endif
+        if (f == NULL) 
+            pm_error("Unable to open file '%s' for reading.  "
+                     "fopen() returns errno %d (%s)", 
+                     name, errno, strerror(errno));
+    }
+    return f;
+}
+
+
+
+FILE*
+pm_openw(const char * const name) {
+    FILE* f;
+
+    if (strcmp(name, "-") == 0)
+        f = stdout;
+    else {
+#ifndef VMS
+        f = fopen(name, "wb");
+#else
+        f = fopen(name, "w", "mbc=32", "mbf=2");  /* set buffer factors */
+#endif
+        if (f == NULL) 
+            pm_error("Unable to open file '%s' for writing.  "
+                     "fopen() returns errno %d (%s)", 
+                     name, errno, strerror(errno));
+    }
+    return f;
+}
+
+
+
+static const char *
+tmpDir(void) {
+/*----------------------------------------------------------------------------
+   Return the name of the directory in which we should create temporary
+   files.
+
+   The name is a constant in static storage.
+-----------------------------------------------------------------------------*/
+    const char * tmpdir;
+        /* running approximation of the result */
+
+    tmpdir = getenv("TMPDIR");   /* Unix convention */
+
+    if (!tmpdir || strlen(tmpdir) == 0)
+        tmpdir = getenv("TMP");  /* Windows convention */
+
+    if (!tmpdir || strlen(tmpdir) == 0)
+        tmpdir = getenv("TEMP"); /* Windows convention */
+
+    if (!tmpdir || strlen(tmpdir) == 0)
+        tmpdir = TMPDIR;
+
+    return tmpdir;
+}
+
+
+
+static int
+mkstempx(char * const filenameBuffer) {
+/*----------------------------------------------------------------------------
+  This is meant to be equivalent to POSIX mkstemp().
+
+  On some old systems, mktemp() is a security hazard that allows a hacker
+  to read or write our temporary file or cause us to read or write some
+  unintended file.  On other systems, mkstemp() does not exist.
+
+  A Windows/mingw environment is one which doesn't have mkstemp()
+  (2006.06.15).
+
+  We assume that if a system doesn't have mkstemp() that its mktemp()
+  is safe, or that the total situation is such that the problems of
+  mktemp() are not a problem for the user.
+-----------------------------------------------------------------------------*/
+    int retval;
+    int fd;
+    unsigned int attempts;
+    bool gotFile;
+    bool error;
+
+    for (attempts = 0, gotFile = FALSE, error = FALSE;
+         !gotFile && !error && attempts < 100;
+         ++attempts) {
+
+        char * rc;
+        rc = mktemp(filenameBuffer);
+
+        if (rc == NULL)
+            error = TRUE;
+        else {
+            int rc;
+
+            rc = open(filenameBuffer, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+
+            if (rc == 0) {
+                fd = rc;
+                gotFile = TRUE;
+            } else {
+                if (errno == EEXIST) {
+                    /* We'll just have to keep trying */
+                } else 
+                    error = TRUE;
+            }
+        }
+    }    
+    if (gotFile)
+        retval = fd;
+    else
+        retval = -1;
+
+    return retval;
+}
+
+
+
+static int
+mkstemp2(char * const filenameBuffer) {
+
+#if HAVE_MKSTEMP
+    if (0)
+        mkstempx(NULL);  /* defeat compiler unused function warning */
+    return mkstemp(filenameBuffer);
+#else
+    return mkstempx(filenameBuffer);
+#endif
+}
+
+
+
+void
+pm_make_tmpfile(FILE **       const filePP,
+                const char ** const filenameP) {
+
+    int fd;
+    FILE * fileP;
+    const char * filenameTemplate;
+    char * filenameBuffer;  /* malloc'ed */
+    unsigned int fnamelen;
+    const char * tmpdir;
+    const char * dirseparator;
+
+    fnamelen = strlen (pm_progname) + 10; /* "/" + "_XXXXXX\0" */
+
+    tmpdir = tmpDir();
+
+    if (tmpdir[strlen(tmpdir) - 1] == '/')
+        dirseparator = "";
+    else
+        dirseparator = "/";
+    
+    asprintfN(&filenameTemplate, "%s%s%s%s", 
+              tmpdir, dirseparator, pm_progname, "_XXXXXX");
+
+    if (filenameTemplate == NULL)
+        pm_error("Unable to allocate storage for temporary file name");
+
+    filenameBuffer = strdup(filenameTemplate);
+
+    fd = mkstemp2(filenameBuffer);
+
+    if (fd < 0)
+        pm_error("Unable to create temporary file according to name "
+                 "pattern '%s'.  mkstemp() failed with "
+                 "errno %d (%s)", filenameTemplate, errno, strerror(errno));
+    else {
+        fileP = fdopen(fd, "w+b");
+
+        if (fileP == NULL)
+            pm_error("Unable to create temporary file.  fdopen() failed "
+                     "with errno %d (%s)", errno, strerror(errno));
+    }
+    strfree(filenameTemplate);
+
+    *filenameP = filenameBuffer;
+    *filePP = fileP;
+}
+
+
+
+FILE * 
+pm_tmpfile(void) {
+
+    FILE * fileP;
+    const char * tmpfile;
+
+    pm_make_tmpfile(&fileP, &tmpfile);
+
+    unlink(tmpfile);
+
+    strfree(tmpfile);
+
+    return fileP;
+}
+
+
+
+FILE *
+pm_openr_seekable(const char name[]) {
+/*----------------------------------------------------------------------------
+  Open the file named by name[] such that it is seekable (i.e. it can be
+  rewound and read in multiple passes with fseek()).
+
+  If the file is actually seekable, this reduces to the same as
+  pm_openr().  If not, we copy the named file to a temporary file
+  and return that file's stream descriptor.
+
+  We use a file that the operating system recognizes as temporary, so
+  it picks the filename and deletes the file when Caller closes it.
+-----------------------------------------------------------------------------*/
+    int stat_rc;
+    int seekable;  /* logical: file is seekable */
+    struct stat statbuf;
+    FILE * original_file;
+    FILE * seekable_file;
+
+    original_file = pm_openr((char *) name);
+
+    /* I would use fseek() to determine if the file is seekable and 
+       be a little more general than checking the type of file, but I
+       don't have reliable information on how to do that.  I have seen
+       streams be partially seekable -- you can, for example seek to
+       0 if the file is positioned at 0 but you can't actually back up
+       to 0.  I have seen documentation that says the errno for an
+       unseekable stream is EBADF and in practice seen ESPIPE.
+
+       On the other hand, regular files are always seekable and even if
+       some other file is, it doesn't hurt much to assume it isn't.
+    */
+
+    stat_rc = fstat(fileno(original_file), &statbuf);
+    if (stat_rc == 0 && S_ISREG(statbuf.st_mode))
+        seekable = TRUE;
+    else 
+        seekable = FALSE;
+
+    if (seekable) {
+        seekable_file = original_file;
+    } else {
+        seekable_file = pm_tmpfile();
+        
+        /* Copy the input into the temporary seekable file */
+        while (!feof(original_file) && !ferror(original_file) 
+               && !ferror(seekable_file)) {
+            char buffer[4096];
+            int bytes_read;
+            bytes_read = fread(buffer, 1, sizeof(buffer), original_file);
+            fwrite(buffer, 1, bytes_read, seekable_file);
+        }
+        if (ferror(original_file))
+            pm_error("Error reading input file into temporary file.  "
+                     "Errno = %s (%d)", strerror(errno), errno);
+        if (ferror(seekable_file))
+            pm_error("Error writing input into temporary file.  "
+                     "Errno = %s (%d)", strerror(errno), errno);
+        pm_close(original_file);
+        {
+            int seek_rc;
+            seek_rc = fseek(seekable_file, 0, SEEK_SET);
+            if (seek_rc != 0)
+                pm_error("fseek() failed to rewind temporary file.  "
+                         "Errno = %s (%d)", strerror(errno), errno);
+        }
+    }
+    return seekable_file;
+}
+
+
+
+void
+pm_close(FILE * const f) {
+    fflush(f);
+    if (ferror(f))
+        pm_message("A file read or write error occurred at some point");
+    if (f != stdin)
+        if (fclose(f) != 0)
+            pm_error("close of file failed with errno %d (%s)",
+                     errno, strerror(errno));
+}
+
+
+
+/* The pnmtopng package uses pm_closer() and pm_closew() instead of 
+   pm_close(), apparently because the 1999 Pbmplus package has them.
+   I don't know what the difference is supposed to be.
+*/
+
+void
+pm_closer(FILE * const f) {
+    pm_close(f);
+}
+
+
+
+void
+pm_closew(FILE * const f) {
+    pm_close(f);
+}
+
+
+
+/* Endian I/O.
+
+   Before Netpbm 10.27 (March 2005), these would return failure on EOF
+   or I/O failure.  For backward compatibility, they still have the return
+   code, but it is always zero and the routines abort the program in case
+   of EOF or I/O failure.  A program that wants to handle failure differently
+   must use lower level (C library) interfaces.  But that level of detail
+   is uncharacteristic of a Netpbm program; the ease of programming that
+   comes with not checking a return code is more Netpbm.
+
+   It is also for historical reasons that these return signed values,
+   when clearly unsigned would make more sense.
+*/
+
+
+
+static void
+abortWithReadError(FILE * const ifP) {
+
+    if (feof(ifP))
+        pm_error("Unexpected end of input file");
+    else
+        pm_error("Error (not EOF) reading file.");
+}
+
+
+
+void
+pm_readchar(FILE * const ifP,
+            char * const cP) {
+    
+    int c;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+
+    *cP = c;
+}
+
+
+
+void
+pm_writechar(FILE * const ofP,
+             char   const c) {
+
+    putc(c, ofP);
+}
+
+
+
+int
+pm_readbigshort(FILE *  const ifP, 
+                short * const sP) {
+    int c;
+
+    unsigned short s;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    s = (c & 0xff) << 8;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    s |= c & 0xff;
+
+    *sP = s;
+
+    return 0;
+}
+
+
+
+int
+pm_writebigshort(FILE * const ofP, 
+                 short  const s) {
+
+    putc((s >> 8) & 0xff, ofP);
+    putc(s & 0xff, ofP);
+
+    return 0;
+}
+
+
+
+int
+pm_readbiglong(FILE * const ifP, 
+               long * const lP) {
+
+    int c;
+    unsigned long l;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l = c << 24;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c << 16;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c << 8;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c;
+
+    *lP = l;
+
+    return 0;
+}
+
+
+
+int
+pm_writebiglong(FILE * const ofP, 
+                long   const l) {
+
+    putc((l >> 24) & 0xff, ofP);
+    putc((l >> 16) & 0xff, ofP);
+    putc((l >>  8) & 0xff, ofP);
+    putc((l >>  0) & 0xff, ofP);
+
+    return 0;
+}
+
+
+
+int
+pm_readlittleshort(FILE *  const ifP, 
+                   short * const sP) {
+    int c;
+    unsigned short s;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    s = c & 0xff;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    s |= (c & 0xff) << 8;
+
+    *sP = s;
+
+    return 0;
+}
+
+
+
+int
+pm_writelittleshort(FILE * const ofP, 
+                    short  const s) {
+
+    putc((s >> 0) & 0xff, ofP);
+    putc((s >> 8) & 0xff, ofP);
+
+    return 0;
+}
+
+
+
+int
+pm_readlittlelong(FILE * const ifP, 
+                  long * const lP) {
+    int c;
+    unsigned long l;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l = c;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c << 8;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c << 16;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c << 24;
+
+    *lP = l;
+
+    return 0;
+}
+
+
+
+int
+pm_writelittlelong(FILE * const ofP, 
+                   long   const l) {
+
+    putc((l >>  0) & 0xff, ofP);
+    putc((l >>  8) & 0xff, ofP);
+    putc((l >> 16) & 0xff, ofP);
+    putc((l >> 24) & 0xff, ofP);
+
+    return 0;
+}
+
+
+
+int 
+pm_readmagicnumber(FILE * const ifP) {
+
+    int ich1, ich2;
+
+    ich1 = getc(ifP);
+    ich2 = getc(ifP);
+    if (ich1 == EOF || ich2 == EOF)
+        pm_error( "Error reading magic number from Netpbm image stream.  "
+                  "Most often, this "
+                  "means your input file is empty." );
+
+    return ich1 * 256 + ich2;
+}
+
+
+
+/* Read a file of unknown size to a buffer. Return the number of bytes
+   read. Allocate more memory as we need it. The calling routine has
+   to free() the buffer.
+
+   Oliver Trepte, oliver@fysik4.kth.se, 930613 */
+
+#define PM_BUF_SIZE 16384      /* First try this size of the buffer, then
+                                   double this until we reach PM_MAX_BUF_INC */
+#define PM_MAX_BUF_INC 65536   /* Don't allocate more memory in larger blocks
+                                   than this. */
+
+char *
+pm_read_unknown_size(FILE * const file, 
+                     long * const nread) {
+    long nalloc;
+    char * buf;
+    bool eof;
+
+    *nread = 0;
+    nalloc = PM_BUF_SIZE;
+    MALLOCARRAY(buf, nalloc);
+
+    eof = FALSE;  /* initial value */
+
+    while(!eof) {
+        int val;
+
+        if (*nread >= nalloc) { /* We need a larger buffer */
+            if (nalloc > PM_MAX_BUF_INC)
+                nalloc += PM_MAX_BUF_INC;
+            else
+                nalloc += nalloc;
+            REALLOCARRAY_NOFAIL(buf, nalloc);
+        }
+
+        val = getc(file);
+        if (val == EOF)
+            eof = TRUE;
+        else 
+            buf[(*nread)++] = val;
+    }
+    return buf;
+}
+
+
+
+union cheat {
+    uint32n l;
+    short s;
+    unsigned char c[4];
+};
+
+
+
+short
+pm_bs_short(short const s) {
+    union cheat u;
+    unsigned char t;
+
+    u.s = s;
+    t = u.c[0];
+    u.c[0] = u.c[1];
+    u.c[1] = t;
+    return u.s;
+}
+
+
+
+long
+pm_bs_long(long const l) {
+    union cheat u;
+    unsigned char t;
+
+    u.l = l;
+    t = u.c[0];
+    u.c[0] = u.c[3];
+    u.c[3] = t;
+    t = u.c[1];
+    u.c[1] = u.c[2];
+    u.c[2] = t;
+    return u.l;
+}
+
+
+
+void
+pm_tell2(FILE *       const fileP, 
+         void *       const fileposP,
+         unsigned int const fileposSize) {
+/*----------------------------------------------------------------------------
+   Return the current file position as *filePosP, which is a buffer
+   'fileposSize' bytes long.  Abort the program if error, including if
+   *fileP isn't a file that has a position.
+-----------------------------------------------------------------------------*/
+    /* Note: FTELLO() is either ftello() or ftell(), depending on the
+       capabilities of the underlying C library.  It is defined in
+       pm_config.h.  ftello(), in turn, may be either ftell() or
+       ftello64(), as implemented by the C library.
+    */
+    pm_filepos const filepos = FTELLO(fileP);
+    if (filepos < 0)
+        pm_error("ftello() to get current file position failed.  "
+                 "Errno = %s (%d)\n", strerror(errno), errno);
+
+    if (fileposSize == sizeof(pm_filepos)) {
+        pm_filepos * const fileposP_filepos = fileposP;
+        *fileposP_filepos = filepos;
+    } else if (fileposSize == sizeof(long)) {
+        if (sizeof(pm_filepos) > sizeof(long) &&
+            filepos >= (pm_filepos) 1 << (sizeof(long)*8))
+            pm_error("File size is too large to represent in the %u bytes "
+                     "that were provided to pm_tell2()", fileposSize);
+        else {
+            long * const fileposP_long = fileposP;
+            *fileposP_long = (long)filepos;
+        }
+    } else
+        pm_error("File position size passed to pm_tell() is invalid: %u.  "
+                 "Valid sizes are %u and %u", 
+                 fileposSize, sizeof(pm_filepos), sizeof(long));
+}
+
+
+
+unsigned int
+pm_tell(FILE * const fileP) {
+    
+    long filepos;
+
+    pm_tell2(fileP, &filepos, sizeof(filepos));
+
+    return filepos;
+}
+
+
+
+void
+pm_seek2(FILE *             const fileP, 
+         const pm_filepos * const fileposP,
+         unsigned int       const fileposSize) {
+/*----------------------------------------------------------------------------
+   Position file *fileP to position *fileposP.  Abort if error, including
+   if *fileP isn't a seekable file.
+-----------------------------------------------------------------------------*/
+    if (fileposSize == sizeof(pm_filepos)) 
+        /* Note: FSEEKO() is either fseeko() or fseek(), depending on the
+           capabilities of the underlying C library.  It is defined in
+           pm_config.h.  fseeko(), in turn, may be either fseek() or
+           fseeko64(), as implemented by the C library.
+        */
+        FSEEKO(fileP, *fileposP, SEEK_SET);
+    else if (fileposSize == sizeof(long)) {
+        long const fileposLong = *(long *)fileposP;
+        fseek(fileP, fileposLong, SEEK_SET);
+    } else
+        pm_error("File position size passed to pm_seek() is invalid: %u.  "
+                 "Valid sizes are %u and %u", 
+                 fileposSize, sizeof(pm_filepos), sizeof(long));
+}
+
+
+
+void
+pm_seek(FILE * const fileP, unsigned long filepos) {
+/*----------------------------------------------------------------------------
+-----------------------------------------------------------------------------*/
+
+    pm_filepos fileposBuff;
+
+    fileposBuff = filepos;
+
+    pm_seek2(fileP, &fileposBuff, sizeof(fileposBuff));
+}
+
+
+
+void
+pm_nextimage(FILE * const file, int * const eofP) {
+/*----------------------------------------------------------------------------
+   Position the file 'file' to the next image in the stream, assuming it is
+   now positioned just after the current image.  I.e. read off any white
+   space at the end of the current image's raster.  Note that the raw formats
+   don't permit such white space, but this routine tolerates it anyway, 
+   because the plain formats do permit white space after the raster.
+
+   Iff there is no next image, return *eofP == TRUE.
+
+   Note that in practice, we will not normally see white space here in
+   a plain PPM or plain PGM stream because the routine to read a
+   sample from the image reads one character of white space after the
+   sample in order to know where the sample ends.  There is not
+   normally more than one character of white space (a newline) after
+   the last sample in the raster.  But plain PBM is another story.  No white
+   space is required between samples of a plain PBM image.  But the raster
+   normally ends with a newline nonetheless.  Since the sample reading code
+   will not have read that newline, it is there for us to read now.
+-----------------------------------------------------------------------------*/
+    bool eof;
+    bool nonWhitespaceFound;
+
+    eof = FALSE;
+    nonWhitespaceFound = FALSE;
+
+    while (!eof && !nonWhitespaceFound) {
+        int c;
+        c = getc(file);
+        if (c == EOF) {
+            if (feof(file)) 
+                eof = TRUE;
+            else
+                pm_error("File error on getc() to position to image");
+        } else {
+            if (!isspace(c)) {
+                int rc;
+
+                nonWhitespaceFound = TRUE;
+
+                /* Have to put the non-whitespace character back in
+                   the stream -- it's part of the next image.  
+                */
+                rc = ungetc(c, file);
+                if (rc == EOF) 
+                    pm_error("File error doing ungetc() "
+                             "to position to image.");
+            }
+        }
+    }
+    *eofP = eof;
+}
+
+
+
+void
+pm_check(FILE *               const file, 
+         enum pm_check_type   const check_type, 
+         pm_filepos           const need_raster_size,
+         enum pm_check_code * const retval_p) {
+/*----------------------------------------------------------------------------
+   This is not defined for use outside of libnetpbm.
+-----------------------------------------------------------------------------*/
+    struct stat statbuf;
+    pm_filepos curpos;  /* Current position of file; -1 if none */
+    int rc;
+
+#ifdef LARGEFILEDEBUG
+    pm_message("pm_filepos received by pm_check() is %u bytes.",
+               sizeof(pm_filepos));
+#endif
+    /* Note: FTELLO() is either ftello() or ftell(), depending on the
+       capabilities of the underlying C library.  It is defined in
+       pm_config.h.  ftello(), in turn, may be either ftell() or
+       ftello64(), as implemented by the C library.
+    */
+    curpos = FTELLO(file);
+    if (curpos >= 0) {
+        /* This type of file has a current position */
+            
+        rc = fstat(fileno(file), &statbuf);
+        if (rc != 0) 
+            pm_error("fstat() failed to get size of file, though ftello() "
+                     "successfully identified\n"
+                     "the current position.  Errno=%s (%d)",
+                     strerror(errno), errno);
+        else if (!S_ISREG(statbuf.st_mode)) {
+            /* Not a regular file; we can't know its size */
+            if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
+        } else {
+            pm_filepos const have_raster_size = statbuf.st_size - curpos;
+            
+            if (have_raster_size < need_raster_size)
+                pm_error("File has invalid format.  The raster should "
+                         "contain %u bytes, but\n"
+                         "the file ends after only %u bytes.",
+                         (unsigned int) need_raster_size, 
+                         (unsigned int) have_raster_size);
+            else if (have_raster_size > need_raster_size) {
+                if (retval_p) *retval_p = PM_CHECK_TOO_LONG;
+            } else {
+                if (retval_p) *retval_p = PM_CHECK_OK;
+            }
+        }
+    } else
+        if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
+}
+
+
+
diff --git a/lib/libpnm1.c b/lib/libpnm1.c
new file mode 100644
index 00000000..82f99b93
--- /dev/null
+++ b/lib/libpnm1.c
@@ -0,0 +1,222 @@
+/* libpnm1.c - pnm utility library part 1
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+#include <errno.h>
+
+#include "pnm.h"
+
+#include "ppm.h"
+#include "libppm.h"
+
+#include "pgm.h"
+#include "libpgm.h"
+
+#include "pbm.h"
+#include "libpbm.h"
+
+#include "pam.h"
+#include "libpam.h"
+
+#include "mallocvar.h"
+
+
+
+xel *
+pnm_allocrow(unsigned int const cols) {
+
+    xel * xelrow;
+
+    MALLOCARRAY(xelrow, cols);
+
+    if (xelrow == NULL)
+        pm_error("Unable to allocate space for a %u-column xel row", cols);
+
+    return xelrow;
+}
+
+
+
+void
+pnm_init( argcP, argv )
+    int* argcP;
+    char* argv[];
+    {
+    ppm_init( argcP, argv );
+    }
+
+void
+pnm_nextimage(FILE *file, int * const eofP) {
+    pm_nextimage(file, eofP);
+}
+
+
+
+static void
+validateComputableSize(unsigned int const cols,
+                       unsigned int const rows) {
+/*----------------------------------------------------------------------------
+   Validate that the dimensions of the image are such that it can be
+   processed in typical ways on this machine without worrying about
+   overflows.  Note that in C, arithmetic is always modulus
+   arithmetic, so if your values are too big, the result is not what
+   you expect.  That failed expectation can be disastrous if you use
+   it to allocate memory.
+
+   A common operation is adding 1 or 2 to the highest row or
+   column number in the image, so we make sure that's possible.
+-----------------------------------------------------------------------------*/
+    if (cols > INT_MAX - 2)
+        pm_error("image width (%u) too large to be processed", cols);
+    if (rows > INT_MAX - 2)
+        pm_error("image height (%u) too large to be processed", rows);
+}
+
+
+
+void
+pnm_readpnminit(FILE *   const fileP,
+                int *    const colsP,
+                int *    const rowsP,
+                xelval * const maxvalP,
+                int *    const formatP) {
+
+    int realFormat;
+
+    /* Check magic number. */
+    realFormat = pm_readmagicnumber(fileP);
+    switch (PAM_FORMAT_TYPE(realFormat)) {
+    case PPM_TYPE: {
+        pixval maxval;
+        *formatP = realFormat;
+        ppm_readppminitrest(fileP, colsP, rowsP, &maxval);
+        *maxvalP = maxval;
+    }
+    break;
+
+    case PGM_TYPE: {
+        gray maxval;
+
+        *formatP = realFormat;
+        pgm_readpgminitrest(fileP, colsP, rowsP, &maxval);
+        *maxvalP = maxval;
+    }
+    break;
+
+    case PBM_TYPE:
+        *formatP = realFormat;
+        pbm_readpbminitrest(fileP, colsP, rowsP);
+        *maxvalP = 1;
+    break;
+
+    case PAM_TYPE: {
+        gray maxval;
+        pnm_readpaminitrestaspnm(fileP, colsP, rowsP, &maxval, formatP);
+        *maxvalP = maxval;
+    }
+    break;
+
+    default:
+        pm_error("bad magic number - not a ppm, pgm, or pbm file");
+    }
+    validateComputableSize(*colsP, *rowsP);
+}
+
+
+
+void
+pnm_readpnmrow(FILE * const fileP,
+               xel *  const xelrow,
+               int    const cols,
+               xelval const maxval,
+               int    const format) {
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PPM_TYPE:
+        ppm_readppmrow(fileP, (pixel*) xelrow, cols, (pixval) maxval, format);
+        break;
+
+    case PGM_TYPE: {
+        gray * grayrow;
+        unsigned int col;
+
+        grayrow = pgm_allocrow(cols);
+        pgm_readpgmrow(fileP, grayrow, cols, (gray) maxval, format);
+        for (col = 0; col < cols; ++col)
+            PNM_ASSIGN1(xelrow[col], grayrow[col]);
+        pgm_freerow(grayrow);
+    }
+    break;
+        
+    case PBM_TYPE: {
+        bit * bitrow;
+        unsigned int col;
+        bitrow = pbm_allocrow(cols);
+        pbm_readpbmrow(fileP, bitrow, cols, format);
+        for (col = 0; col < cols; ++col)
+            PNM_ASSIGN1(xelrow[col], bitrow[col] == PBM_BLACK ? 0: maxval);
+        pbm_freerow(bitrow);
+    }
+    break;
+
+    default:
+        pm_error("INTERNAL ERROR.  Impossible format.");
+    }
+}
+
+
+
+xel **
+pnm_readpnm(FILE *   const fileP,
+            int *    const colsP,
+            int *    const rowsP,
+            xelval * const maxvalP,
+            int *    const formatP) {
+
+    xel ** xels;
+    int row;
+
+    pnm_readpnminit(fileP, colsP, rowsP, maxvalP, formatP);
+
+    xels = pnm_allocarray(*colsP, *rowsP);
+
+    for (row = 0; row < *rowsP; ++row)
+        pnm_readpnmrow(fileP, xels[row], *colsP, *maxvalP, *formatP);
+
+    return xels;
+}
+
+
+
+void
+pnm_check(FILE *               const fileP,
+          enum pm_check_type   const check_type, 
+          int                  const format,
+          int                  const cols,
+          int                  const rows,
+          int                  const maxval,
+          enum pm_check_code * const retvalP) {
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PBM_TYPE:
+        pbm_check(fileP, check_type, format, cols, rows, retvalP);
+        break;
+    case PGM_TYPE: 
+        pgm_check(fileP, check_type, format, cols, rows, maxval, retvalP);
+        break;
+    case PPM_TYPE:
+        ppm_check(fileP, check_type, format, cols, rows, maxval, retvalP);
+        break;
+    default:
+        pm_error("pnm_check() called with invalid format parameter");
+    }
+}
diff --git a/lib/libpnm2.c b/lib/libpnm2.c
new file mode 100644
index 00000000..aae78d52
--- /dev/null
+++ b/lib/libpnm2.c
@@ -0,0 +1,128 @@
+/* libpnm2.c - pnm utility library part 2
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pm_c_util.h"
+
+#include "pnm.h"
+
+#include "ppm.h"
+#include "libppm.h"
+
+#include "pgm.h"
+#include "libpgm.h"
+
+#include "pbm.h"
+#include "libpbm.h"
+
+void
+pnm_writepnminit(FILE * const fileP, 
+                 int    const cols, 
+                 int    const rows, 
+                 xelval const maxval, 
+                 int    const format, 
+                 int    const forceplain) {
+
+    bool const plainFormat = forceplain || pm_plain_output;
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PPM_TYPE:
+        ppm_writeppminit(fileP, cols, rows, (pixval) maxval, plainFormat);
+        break;
+
+    case PGM_TYPE:
+        pgm_writepgminit(fileP, cols, rows, (gray) maxval, plainFormat);
+        break;
+
+    case PBM_TYPE:
+        pbm_writepbminit(fileP, cols, rows, plainFormat);
+    break;
+
+    default:
+        pm_error("invalid format argument received by pnm_writepnminit(): %d"
+                 "PNM_FORMAT_TYPE(format) must be %d, %d, or %d", 
+                 format, PBM_TYPE, PGM_TYPE, PPM_TYPE);
+    }
+}
+
+
+
+void
+pnm_writepnmrow(FILE * const fileP, 
+                xel *  const xelrow, 
+                int    const cols, 
+                xelval const maxval, 
+                int    const format, 
+                int    const forceplain) {
+
+    bool const plainFormat = forceplain || pm_plain_output;
+    
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PPM_TYPE:
+        ppm_writeppmrow(fileP, (pixel*) xelrow, cols, (pixval) maxval, 
+                        plainFormat);
+        break;
+
+    case PGM_TYPE: {
+        gray* grayrow;
+        unsigned int col;
+
+        grayrow = pgm_allocrow(cols);
+
+        for (col = 0; col < cols; ++col)
+            grayrow[col] = PNM_GET1(xelrow[col]);
+
+        pgm_writepgmrow(fileP, grayrow, cols, (gray) maxval, plainFormat);
+
+        pgm_freerow( grayrow );
+    }
+    break;
+
+    case PBM_TYPE: {
+        bit* bitrow;
+        unsigned int col;
+
+        bitrow = pbm_allocrow(cols);
+
+        for (col = 0; col < cols; ++col)
+            bitrow[col] = PNM_GET1(xelrow[col]) == 0 ? PBM_BLACK : PBM_WHITE;
+
+        pbm_writepbmrow(fileP, bitrow, cols, plainFormat);
+
+        pbm_freerow(bitrow);
+    }    
+    break;
+    
+    default:
+        pm_error("invalid format argument received by pnm_writepnmrow(): %d"
+                 "PNM_FORMAT_TYPE(format) must be %d, %d, or %d", 
+                 format, PBM_TYPE, PGM_TYPE, PPM_TYPE);
+    }
+}
+
+
+
+void
+pnm_writepnm(FILE * const fileP,
+             xel ** const xels,
+             int    const cols,
+             int    const rows,
+             xelval const maxval,
+             int    const format,
+             int    const forceplain) {
+
+    unsigned int row;
+
+    pnm_writepnminit(fileP, cols, rows, maxval, format, forceplain);
+    
+    for (row = 0; row < rows; ++row)
+        pnm_writepnmrow(fileP, xels[row], cols, maxval, format, forceplain);
+}
diff --git a/lib/libpnm3.c b/lib/libpnm3.c
new file mode 100644
index 00000000..c9c9a1b0
--- /dev/null
+++ b/lib/libpnm3.c
@@ -0,0 +1,393 @@
+/* libpnm3.c - pnm utility library part 3
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pnm.h"
+
+#include "ppm.h"
+#include "libppm.h"
+
+#include "pgm.h"
+#include "libpgm.h"
+
+#include "pbm.h"
+#include "libpbm.h"
+
+#if __STDC__
+xel
+pnm_backgroundxel( xel** xels, int cols, int rows, xelval maxval, int format )
+#else /*__STDC__*/
+xel
+pnm_backgroundxel( xels, cols, rows, maxval, format )
+    xel** xels;
+    int cols, rows, format;
+    xelval maxval;
+#endif /*__STDC__*/
+    {
+    xel bgxel, ul, ur, ll, lr;
+
+    /* Guess a good background value. */
+    ul = xels[0][0];
+    ur = xels[0][cols-1];
+    ll = xels[rows-1][0];
+    lr = xels[rows-1][cols-1];
+
+    /* First check for three corners equal. */
+    if ( PNM_EQUAL( ul, ur ) && PNM_EQUAL( ur, ll ) )
+    bgxel = ul;
+    else if ( PNM_EQUAL( ul, ur ) && PNM_EQUAL( ur, lr ) )
+    bgxel = ul;
+    else if ( PNM_EQUAL( ul, ll ) && PNM_EQUAL( ll, lr ) )
+    bgxel = ul;
+    else if ( PNM_EQUAL( ur, ll ) && PNM_EQUAL( ll, lr ) )
+    bgxel = ur;
+    /* Nope, check for two corners equal. */
+    else if ( PNM_EQUAL( ul, ur ) || PNM_EQUAL( ul, ll ) ||
+          PNM_EQUAL( ul, lr ) )
+    bgxel = ul;
+    else if ( PNM_EQUAL( ur, ll ) || PNM_EQUAL( ur, lr ) )
+    bgxel = ur;
+    else if ( PNM_EQUAL( ll, lr ) )
+    bgxel = ll;
+    else
+    {
+    /* Nope, we have to average the four corners.  This breaks the
+    ** rules of pnm, but oh well.  Let's try to do it portably. */
+    switch ( PNM_FORMAT_TYPE(format) )
+        {
+        case PPM_TYPE:
+        PPM_ASSIGN( bgxel,
+        PPM_GETR(ul) + PPM_GETR(ur) + PPM_GETR(ll) + PPM_GETR(lr) / 4,
+        PPM_GETG(ul) + PPM_GETG(ur) + PPM_GETG(ll) + PPM_GETG(lr) / 4,
+        PPM_GETB(ul) + PPM_GETB(ur) + PPM_GETB(ll) + PPM_GETB(lr) / 4 );
+        break;
+
+        case PGM_TYPE:
+        {
+        gray gul, gur, gll, glr;
+        gul = (gray) PNM_GET1( ul );
+        gur = (gray) PNM_GET1( ur );
+        gll = (gray) PNM_GET1( ll );
+        glr = (gray) PNM_GET1( lr );
+        PNM_ASSIGN1( bgxel, ( ( gul + gur + gll + glr ) / 4 ) );
+        break;
+        }
+
+        case PBM_TYPE:
+        pm_error(
+        "pnm_backgroundxel: four bits no two of which equal each other??" );
+
+        default:
+        pm_error( "Invalid format passed to pnm_backgroundxel()");
+        }
+    }
+
+    return bgxel;
+    }
+
+#if __STDC__
+xel
+pnm_backgroundxelrow( xel* xelrow, int cols, xelval maxval, int format )
+#else /*__STDC__*/
+xel
+pnm_backgroundxelrow( xelrow, cols, maxval, format )
+    xel* xelrow;
+    int cols, format;
+    xelval maxval;
+#endif /*__STDC__*/
+    {
+    xel bgxel, l, r;
+
+    /* Guess a good background value. */
+    l = xelrow[0];
+    r = xelrow[cols-1];
+
+    /* First check for both corners equal. */
+    if ( PNM_EQUAL( l, r ) )
+    bgxel = l;
+    else
+    {
+    /* Nope, we have to average the two corners.  This breaks the
+    ** rules of pnm, but oh well.  Let's try to do it portably. */
+    switch ( PNM_FORMAT_TYPE(format) )
+        {
+        case PPM_TYPE:
+        PPM_ASSIGN( bgxel, PPM_GETR(l) + PPM_GETR(r) / 2,
+        PPM_GETG(l) + PPM_GETG(r) / 2, PPM_GETB(l) + PPM_GETB(r) / 2 );
+        break;
+
+        case PGM_TYPE:
+        {
+        gray gl, gr;
+        gl = (gray) PNM_GET1( l );
+        gr = (gray) PNM_GET1( r );
+        PNM_ASSIGN1( bgxel, ( ( gl + gr ) / 2 ) );
+        break;
+        }
+
+        case PBM_TYPE:
+        {
+        int col, blacks;
+
+        /* One black, one white.  Gotta count. */
+        for ( col = 0, blacks = 0; col < cols; ++col )
+        {
+        if ( PNM_GET1( xelrow[col] ) == 0 )
+            ++blacks;
+        }
+        if ( blacks >= cols / 2 )
+        PNM_ASSIGN1( bgxel, 0 );
+        else
+        PNM_ASSIGN1( bgxel, maxval );
+        break;
+        }
+
+        default:
+        pm_error( "Invalid format passed to pnm_backgroundxelrow()");
+        }
+    }
+
+    return bgxel;
+    }
+
+#if __STDC__
+xel
+pnm_whitexel( xelval maxval, int format )
+#else /*__STDC__*/
+xel
+pnm_whitexel( maxval, format )
+    xelval maxval;
+    int format;
+#endif /*__STDC__*/
+    {
+    xel x;
+
+    switch ( PNM_FORMAT_TYPE(format) )
+    {
+    case PPM_TYPE:
+    PPM_ASSIGN( x, maxval, maxval, maxval );
+    break;
+
+    case PGM_TYPE:
+    PNM_ASSIGN1( x, maxval );
+    break;
+
+    case PBM_TYPE:
+    PNM_ASSIGN1( x, maxval );
+    break;
+
+    default:
+    pm_error( "Invalid format passed to pnm_whitexel()");
+    }
+
+    return x;
+    }
+
+#if __STDC__
+xel
+pnm_blackxel( xelval maxval, int format )
+#else /*__STDC__*/
+xel
+pnm_blackxel( maxval, format )
+    xelval maxval;
+    int format;
+#endif /*__STDC__*/
+    {
+    xel x;
+
+    switch ( PNM_FORMAT_TYPE(format) )
+    {
+    case PPM_TYPE:
+    PPM_ASSIGN( x, 0, 0, 0 );
+    break;
+
+    case PGM_TYPE:
+    PNM_ASSIGN1( x, (xelval) 0 );
+    break;
+
+    case PBM_TYPE:
+    PNM_ASSIGN1( x, (xelval) 0 );
+    break;
+
+    default:
+    pm_error( "Invalid format passed to pnm_blackxel(): %d", format);
+    }
+
+    return x;
+    }
+
+void
+pnm_invertxel(xel*   const xP, 
+              xelval const maxval, 
+              int    const format) {
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PPM_TYPE:
+        PPM_ASSIGN(*xP, 
+                   maxval - PPM_GETR(*xP),
+                   maxval - PPM_GETG(*xP), 
+                   maxval - PPM_GETB(*xP));
+        break;
+
+    case PGM_TYPE:
+        PNM_ASSIGN1(*xP, maxval - PNM_GET1(*xP));
+        break;
+
+    case PBM_TYPE:
+        PNM_ASSIGN1(*xP, (PNM_GET1(*xP) == 0) ? maxval : 0);
+        break;
+
+    default:
+        pm_error("Invalid format passed to pnm_invertxel()");
+    }
+}
+
+
+
+#if __STDC__
+void
+pnm_promoteformat( xel** xels, int cols, int rows, xelval maxval, int format, xelval newmaxval, int newformat )
+#else /*__STDC__*/
+void
+pnm_promoteformat( xels, cols, rows, maxval, format, newmaxval, newformat )
+    xel** xels;
+    xelval maxval, newmaxval;
+    int cols, rows, format, newformat;
+#endif /*__STDC__*/
+    {
+    int row;
+
+    for ( row = 0; row < rows; ++row )
+    pnm_promoteformatrow(
+        xels[row], cols, maxval, format, newmaxval, newformat );
+    }
+
+#if __STDC__
+void
+pnm_promoteformatrow( xel* xelrow, int cols, xelval maxval, int format, xelval newmaxval, int newformat )
+#else /*__STDC__*/
+void
+pnm_promoteformatrow( xelrow, cols, maxval, format, newmaxval, newformat )
+    xel* xelrow;
+    xelval maxval, newmaxval;
+    int cols, format, newformat;
+#endif /*__STDC__*/
+    {
+    register int col;
+    register xel* xP;
+
+    if ( ( PNM_FORMAT_TYPE(format) == PPM_TYPE &&
+       ( PNM_FORMAT_TYPE(newformat) == PGM_TYPE ||
+         PNM_FORMAT_TYPE(newformat) == PBM_TYPE ) ) ||
+     ( PNM_FORMAT_TYPE(format) == PGM_TYPE &&
+       PNM_FORMAT_TYPE(newformat) == PBM_TYPE ) )
+    pm_error( "pnm_promoteformatrow: can't promote downwards!" );
+
+    /* Are we promoting to the same type? */
+    if ( PNM_FORMAT_TYPE(format) == PNM_FORMAT_TYPE(newformat) )
+    {
+    if ( PNM_FORMAT_TYPE(format) == PBM_TYPE )
+        return;
+    if ( newmaxval < maxval )
+        pm_error(
+       "pnm_promoteformatrow: can't decrease maxval - try using pnmdepth" );
+    if ( newmaxval == maxval )
+        return;
+    /* Increase maxval. */
+    switch ( PNM_FORMAT_TYPE(format) )
+        {
+        case PGM_TYPE:
+        for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+        PNM_ASSIGN1(
+            *xP, (int) PNM_GET1(*xP) * newmaxval / maxval );
+        break;
+
+        case PPM_TYPE:
+        for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+        PPM_DEPTH( *xP, *xP, maxval, newmaxval );
+        break;
+
+        default:
+        pm_error( "Invalid old format passed to pnm_promoteformatrow()" );
+        }
+    return;
+    }
+
+    /* We must be promoting to a higher type. */
+    switch ( PNM_FORMAT_TYPE(format) )
+    {
+    case PBM_TYPE:
+    switch ( PNM_FORMAT_TYPE(newformat) )
+        {
+        case PGM_TYPE:
+        for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+        if ( PNM_GET1(*xP) == 0 )
+            PNM_ASSIGN1( *xP, 0 );
+        else
+            PNM_ASSIGN1( *xP, newmaxval );
+        break;
+
+        case PPM_TYPE:
+        for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+        if ( PNM_GET1(*xP) == 0 )
+            PPM_ASSIGN( *xP, 0, 0, 0 );
+        else
+            PPM_ASSIGN( *xP, newmaxval, newmaxval, newmaxval );
+        break;
+
+        default:
+        pm_error( "Invalid new format passed to pnm_promoteformatrow()" );
+        }
+    break;
+
+    case PGM_TYPE:
+    switch ( PNM_FORMAT_TYPE(newformat) )
+        {
+        case PPM_TYPE:
+        if ( newmaxval < maxval )
+        pm_error(
+       "pnm_promoteformatrow: can't decrease maxval - try using pnmdepth" );
+        if ( newmaxval == maxval )
+        {
+        for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+            PPM_ASSIGN(
+            *xP, PNM_GET1(*xP), PNM_GET1(*xP), PNM_GET1(*xP) );
+        }
+        else
+        { /* Increase maxval. */
+        for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
+            PPM_ASSIGN(
+            *xP, (int) PNM_GET1(*xP) * newmaxval / maxval,
+            (int) PNM_GET1(*xP) * newmaxval / maxval,
+            (int) PNM_GET1(*xP) * newmaxval / maxval );
+        }
+        break;
+
+        default:
+        pm_error( "Invalid new format passed to pnm_promoteformatrow()" );
+        }
+    break;
+
+    default:
+        pm_error( "Invalid old format passed to pnm_promoteformatrow()" );
+    }
+    }
+
+
+pixel
+xeltopixel(xel const inputxel) {
+    
+    pixel outputpixel;
+
+    PPM_ASSIGN(outputpixel, 
+               PNM_GET1(inputxel), PNM_GET1(inputxel), PNM_GET1(inputxel));
+    return outputpixel;
+}
diff --git a/lib/libppm.h b/lib/libppm.h
new file mode 100644
index 00000000..ec2a173d
--- /dev/null
+++ b/lib/libppm.h
@@ -0,0 +1,15 @@
+/* This is the intra-libnetpbm interface header file for libppm*.c
+*/
+
+#ifndef LIBPPM_H_INCLUDED
+#define LIBPPM_H_INCLUDED
+
+#include "ppm.h"
+
+void
+ppm_readppminitrest(FILE *   const file, 
+                    int *    const colsP, 
+                    int *    const rowsP, 
+                    pixval * const maxvalP);
+
+#endif
diff --git a/lib/libppm1.c b/lib/libppm1.c
new file mode 100644
index 00000000..57a1db7d
--- /dev/null
+++ b/lib/libppm1.c
@@ -0,0 +1,328 @@
+/* libppm1.c - ppm utility library part 1
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* See libpbm.c for the complicated explanation of this 32/64 bit file
+   offset stuff.
+*/
+#define _FILE_OFFSET_BITS 64
+#define _LARGE_FILES  
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include "ppm.h"
+#include "libppm.h"
+#include "pgm.h"
+#include "libpgm.h"
+#include "pbm.h"
+#include "libpbm.h"
+#include "pam.h"
+#include "libpam.h"
+#include "fileio.h"
+#include "mallocvar.h"
+
+
+pixel *
+ppm_allocrow(unsigned int const cols) {
+
+    pixel * pixelrow;
+
+    MALLOCARRAY(pixelrow, cols);
+
+    if (pixelrow == 0)
+        pm_error("Unable to allocate space for a %u-column pixel row", cols);
+
+    return pixelrow;
+}
+
+
+
+void
+ppm_init( argcP, argv )
+    int* argcP;
+    char* argv[];
+    {
+    pgm_init( argcP, argv );
+    }
+
+void
+ppm_nextimage(FILE * const fileP, 
+              int *  const eofP) {
+    pm_nextimage(fileP, eofP);
+}
+
+
+
+void
+ppm_readppminitrest(FILE *   const fileP, 
+                    int *    const colsP, 
+                    int *    const rowsP, 
+                    pixval * const maxvalP) {
+    unsigned int maxval;
+
+    /* Read size. */
+    *colsP = (int)pm_getuint(fileP);
+    *rowsP = (int)pm_getuint(fileP);
+
+    /* Read maxval. */
+    maxval = pm_getuint(fileP);
+    if (maxval > PPM_OVERALLMAXVAL)
+        pm_error("maxval of input image (%u) is too large.  "
+                 "The maximum allowed by the PPM is %u.",
+                 maxval, PPM_OVERALLMAXVAL); 
+    if (maxval == 0)
+        pm_error("maxval of input image is zero.");
+
+    *maxvalP = maxval;
+}
+
+
+
+static void
+validateComputableSize(unsigned int const cols,
+                       unsigned int const rows) {
+/*----------------------------------------------------------------------------
+   Validate that the dimensions of the image are such that it can be
+   processed in typical ways on this machine without worrying about
+   overflows.  Note that in C, arithmetic is always modulus
+   arithmetic, so if your values are too big, the result is not what
+   you expect.  That failed expectation can be disastrous if you use
+   it to allocate memory.
+
+   A common operation is adding 1 or 2 to the highest row or
+   column number in the image, so we make sure that's possible.
+-----------------------------------------------------------------------------*/
+    if (cols > INT_MAX - 2)
+        pm_error("image width (%u) too large to be processed", cols);
+    if (rows > INT_MAX - 2)
+        pm_error("image height (%u) too large to be processed", rows);
+}
+
+
+
+void
+ppm_readppminit(FILE *   const fileP, 
+                int *    const colsP, 
+                int *    const rowsP, 
+                pixval * const maxvalP, 
+                int *    const formatP) {
+
+    int realFormat;
+
+    /* Check magic number. */
+    realFormat = pm_readmagicnumber(fileP);
+    switch (PAM_FORMAT_TYPE(realFormat)) {
+    case PPM_TYPE:
+        *formatP = realFormat;
+        ppm_readppminitrest(fileP, colsP, rowsP, maxvalP);
+        break;
+
+    case PGM_TYPE:
+        *formatP = realFormat;
+        pgm_readpgminitrest(fileP, colsP, rowsP, maxvalP);
+        break;
+
+    case PBM_TYPE:
+        *formatP = realFormat;
+        *maxvalP = 1;
+        pbm_readpbminitrest(fileP, colsP, rowsP);
+        break;
+
+    case PAM_TYPE:
+        pnm_readpaminitrestaspnm(fileP, colsP, rowsP, maxvalP, formatP);
+        break;
+
+    default:
+        pm_error("bad magic number %d - not a PPM, PGM, PBM, or PAM file",
+                 PAM_FORMAT_TYPE(*formatP));
+    }
+    validateComputableSize(*colsP, *rowsP);
+}
+
+
+
+void
+ppm_readppmrow(FILE*  const fileP, 
+               pixel* const pixelrow, 
+               int    const cols, 
+               pixval const maxval, 
+               int    const format) {
+
+    switch (format) {
+    case PPM_FORMAT: {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            pixval const r = pm_getuint(fileP);
+            pixval const g = pm_getuint(fileP);
+            pixval const b = pm_getuint(fileP);
+
+            if (r > maxval)
+                pm_error("Red sample value %u is greater than maxval (%u)",
+                         r, maxval);
+            if (g > maxval)
+                pm_error("Green sample value %u is greater than maxval (%u)",
+                         g, maxval);
+            if (b > maxval)
+                pm_error("Blue sample value %u is greater than maxval (%u)",
+                         b, maxval);
+
+            PPM_ASSIGN(pixelrow[col], r, g, b);
+        }
+    }
+    break;
+
+    /* For PAM, we require a depth of 3, which means the raster format
+       is identical to Raw PPM!  How convenient.
+    */
+    case PAM_FORMAT:
+    case RPPM_FORMAT: {
+        unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
+        unsigned int const bytesPerRow    = cols * 3 * bytesPerSample;
+        
+        unsigned int bufferCursor;
+        unsigned char * rowBuffer;
+        ssize_t rc;
+
+        MALLOCARRAY(rowBuffer, bytesPerRow);
+        
+        if (rowBuffer == NULL)
+            pm_error("Unable to allocate memory for row buffer "
+                     "for %u columns", cols);
+
+        rc = fread(rowBuffer, 1, bytesPerRow, fileP);
+    
+        if (feof(fileP))
+            pm_error("Unexpected EOF reading row of PPM image.");
+        else if (ferror(fileP))
+            pm_error("Error reading row.  fread() errno=%d (%s)",
+                     errno, strerror(errno));
+        else if (rc != bytesPerRow)
+            pm_error("Error reading row.  Short read of %u bytes "
+                     "instead of %u", rc, bytesPerRow);
+    
+        bufferCursor = 0;  /* start at beginning of rowBuffer[] */
+        
+        if (bytesPerSample == 1) {
+            unsigned int col;
+            for (col = 0; col < cols; ++col) {
+                pixval const r = rowBuffer[bufferCursor++];
+                pixval const g = rowBuffer[bufferCursor++];
+                pixval const b = rowBuffer[bufferCursor++];
+                PPM_ASSIGN(pixelrow[col], r, g, b);
+            }
+        } else  {
+            /* two byte samples */
+            unsigned int col;
+            for (col = 0; col < cols; ++col) {
+                pixval r, g, b;
+
+                r = rowBuffer[bufferCursor++] << 8;
+                r |= rowBuffer[bufferCursor++];
+
+                g = rowBuffer[bufferCursor++] << 8;
+                g |= rowBuffer[bufferCursor++];
+
+                b = rowBuffer[bufferCursor++] << 8;
+                b |= rowBuffer[bufferCursor++];
+                
+                PPM_ASSIGN(pixelrow[col], r, g, b);
+            }
+        }
+        free(rowBuffer);
+    }
+    break;
+
+    case PGM_FORMAT:
+    case RPGM_FORMAT: {
+        gray * const grayrow = pgm_allocrow(cols);
+        unsigned int col;
+
+        pgm_readpgmrow(fileP, grayrow, cols, maxval, format);
+        for (col = 0; col < cols; ++col) {
+            pixval const g = grayrow[col];
+            PPM_ASSIGN(pixelrow[col], g, g, g);
+        }
+        pgm_freerow(grayrow);
+    }
+    break;
+
+    case PBM_FORMAT:
+    case RPBM_FORMAT: {
+        bit * const bitrow = pbm_allocrow(cols);
+        unsigned int col;
+
+        pbm_readpbmrow(fileP, bitrow, cols, format);
+        for (col = 0; col < cols; ++col) {
+            pixval const g = (bitrow[col] == PBM_WHITE) ? maxval : 0;
+            PPM_ASSIGN(pixelrow[col], g, g, g);
+        }
+        pbm_freerow(bitrow);
+    }
+    break;
+
+    default:
+        pm_error("Invalid format code");
+    }
+}
+
+
+
+pixel**
+ppm_readppm(FILE *   const fileP, 
+            int *    const colsP, 
+            int *    const rowsP, 
+            pixval * const maxvalP) {
+    pixel** pixels;
+    int row;
+    int format;
+
+    ppm_readppminit(fileP, colsP, rowsP, maxvalP, &format);
+
+    pixels = ppm_allocarray(*colsP, *rowsP);
+
+    for (row = 0; row < *rowsP; ++row)
+        ppm_readppmrow(fileP, pixels[row], *colsP, *maxvalP, format);
+
+    return pixels;
+}
+
+
+
+void
+ppm_check(FILE *               const fileP, 
+          enum pm_check_type   const check_type, 
+          int                  const format, 
+          int                  const cols, 
+          int                  const rows, 
+          pixval               const maxval,
+          enum pm_check_code * const retval_p) {
+
+    if (rows < 0)
+        pm_error("Invalid number of rows passed to ppm_check(): %d", rows);
+    if (cols < 0)
+        pm_error("Invalid number of columns passed to ppm_check(): %d", cols);
+    
+    if (check_type != PM_CHECK_BASIC) {
+        if (retval_p) *retval_p = PM_CHECK_UNKNOWN_TYPE;
+    } else if (PPM_FORMAT_TYPE(format) == PBM_TYPE) {
+        pbm_check(fileP, check_type, format, cols, rows, retval_p);
+    } else if (PPM_FORMAT_TYPE(format) == PGM_TYPE) {
+        pgm_check(fileP, check_type, format, cols, rows, maxval, retval_p);
+    } else if (format != RPPM_FORMAT) {
+        if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
+    } else {        
+        pm_filepos const bytes_per_row = cols * 3 * (maxval > 255 ? 2 : 1);
+        pm_filepos const need_raster_size = rows * bytes_per_row;
+        
+        pm_check(fileP, check_type, need_raster_size, retval_p);
+    }
+}
diff --git a/lib/libppm2.c b/lib/libppm2.c
new file mode 100644
index 00000000..52c4ab16
--- /dev/null
+++ b/lib/libppm2.c
@@ -0,0 +1,208 @@
+/* libppm2.c - ppm utility library part 2
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+#include <errno.h>
+
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "ppm.h"
+#include "libppm.h"
+
+void
+ppm_writeppminit(FILE*  const fileP, 
+                 int    const cols, 
+                 int    const rows, 
+                 pixval const maxval, 
+                 int    const forceplain) {
+
+    bool const plainFormat = forceplain || pm_plain_output;
+
+    if (maxval > PPM_OVERALLMAXVAL && !plainFormat) 
+        pm_error("too-large maxval passed to ppm_writeppminit(): %d."
+                 "Maximum allowed by the PPM format is %d.",
+                 maxval, PPM_OVERALLMAXVAL);
+
+    fprintf(fileP, "%c%c\n%d %d\n%d\n", 
+            PPM_MAGIC1, 
+            plainFormat || maxval >= 1<<16 ? PPM_MAGIC2 : RPPM_MAGIC2, 
+            cols, rows, maxval );
+#ifdef VMS
+    if (!plainFormat)
+        set_outfile_binary();
+#endif
+}
+
+
+
+static void
+putus(unsigned short const n,
+      FILE *         const fileP) {
+
+    if (n >= 10)
+        putus(n / 10, fileP);
+    putc('0' + n % 10, fileP);
+}
+
+
+
+static void
+format1bpsRow(const pixel *   const pixelrow,
+              unsigned int    const cols,
+              unsigned char * const rowBuffer) {
+
+    /* single byte samples. */
+
+    unsigned int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;
+
+    for (col = 0; col < cols; ++col) {
+        rowBuffer[bufferCursor++] = PPM_GETR(pixelrow[col]);
+        rowBuffer[bufferCursor++] = PPM_GETG(pixelrow[col]);
+        rowBuffer[bufferCursor++] = PPM_GETB(pixelrow[col]);
+    }
+}
+
+
+
+
+static void
+format2bpsRow(const pixel *   const pixelrow,
+              unsigned int    const cols,
+              unsigned char * const rowBuffer) {
+    
+    /* two byte samples. */
+
+    unsigned int col;
+    unsigned int bufferCursor;
+
+    bufferCursor = 0;
+
+    for (col = 0; col < cols; ++col) {
+        pixval const r = PPM_GETR(pixelrow[col]);
+        pixval const g = PPM_GETG(pixelrow[col]);
+        pixval const b = PPM_GETB(pixelrow[col]);
+        
+        rowBuffer[bufferCursor++] = r >> 8;
+        rowBuffer[bufferCursor++] = (unsigned char)r;
+        rowBuffer[bufferCursor++] = g >> 8;
+        rowBuffer[bufferCursor++] = (unsigned char)g;
+        rowBuffer[bufferCursor++] = b >> 8;
+        rowBuffer[bufferCursor++] = (unsigned char)b;
+    }
+}
+
+
+
+static void
+ppm_writeppmrowraw(FILE *        const fileP,
+                   const pixel * const pixelrow,
+                   unsigned int  const cols,
+                   pixval        const maxval ) {
+
+    unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
+    unsigned int const bytesPerRow    = cols * 3 * bytesPerSample;
+
+    unsigned char * rowBuffer;
+    ssize_t rc;
+
+    MALLOCARRAY(rowBuffer, bytesPerRow);
+
+    if (rowBuffer == NULL)
+        pm_error("Unable to allocate memory for row buffer "
+                 "for %u columns", cols);
+
+    if (maxval < 256)
+        format1bpsRow(pixelrow, cols, rowBuffer);
+    else
+        format2bpsRow(pixelrow, cols, rowBuffer);
+
+    rc = fwrite(rowBuffer, 1, bytesPerRow, fileP);
+
+    if (rc < 0)
+        pm_error("Error writing row.  fwrite() errno=%d (%s)",
+                 errno, strerror(errno));
+    else if (rc != bytesPerRow)
+        pm_error("Error writing row.  Short write of %u bytes "
+                 "instead of %u", rc, bytesPerRow);
+
+    free(rowBuffer);
+}
+
+
+
+
+static void
+ppm_writeppmrowplain(FILE *       const fileP,
+                     pixel *      const pixelrow,
+                     unsigned int const cols,
+                     pixval       const maxval) {
+
+    unsigned int col;
+    unsigned int charcount;
+
+    charcount = 0;
+
+    for (col = 0; col < cols; ++col) {
+        if (charcount >= 65) {
+            putc('\n', fileP);
+            charcount = 0;
+        } else if (charcount > 0) {
+            putc(' ', fileP);
+            putc(' ', fileP);
+            charcount += 2;
+        }
+        putus(PPM_GETR(pixelrow[col]), fileP);
+        putc(' ', fileP);
+        putus(PPM_GETG(pixelrow[col]), fileP);
+        putc(' ', fileP);
+        putus(PPM_GETB(pixelrow[col]), fileP);
+        charcount += 11;
+    }
+    if (charcount > 0)
+        putc('\n', fileP);
+}
+
+
+
+void
+ppm_writeppmrow(FILE *  const fileP, 
+                pixel * const pixelrow, 
+                int     const cols, 
+                pixval  const maxval, 
+                int     const forceplain) {
+
+    if (forceplain || pm_plain_output || maxval >= 1<<16) 
+        ppm_writeppmrowplain(fileP, pixelrow, cols, maxval);
+    else 
+        ppm_writeppmrowraw(fileP, pixelrow, cols, maxval);
+}
+
+
+
+void
+ppm_writeppm(FILE *  const file, 
+             pixel** const pixels, 
+             int     const cols, 
+             int     const rows, 
+             pixval  const maxval, 
+             int     const forceplain)  {
+    int row;
+    
+    ppm_writeppminit(file, cols, rows, maxval, forceplain);
+    
+    for (row = 0; row < rows; ++row)
+        ppm_writeppmrow(file, pixels[row], cols, maxval, forceplain);
+}
diff --git a/lib/libppmcmap.c b/lib/libppmcmap.c
new file mode 100644
index 00000000..a9efccbc
--- /dev/null
+++ b/lib/libppmcmap.c
@@ -0,0 +1,650 @@
+/* libppm3.c - ppm utility library part 3
+**
+** Colormap routines.
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+#include "libppm.h"
+#include "mallocvar.h"
+#include "ppmcmap.h"
+
+#define HASH_SIZE 20023
+
+#define ppm_hashpixel(p) ( ( ( (long) PPM_GETR(p) * 33023 + \
+                               (long) PPM_GETG(p) * 30013 + \
+                               (long) PPM_GETB(p) * 27011 ) \
+                             & 0x7fffffff ) % HASH_SIZE )
+
+colorhist_vector
+ppm_computecolorhist( pixel ** const pixels, 
+                      const int cols, const int rows, const int maxcolors, 
+                      int * const colorsP ) {
+/*----------------------------------------------------------------------------
+   Compute a color histogram for the image described by 'pixels',
+   'cols', and 'rows'.  I.e. a colorhist_vector containing an entry
+   for each color in the image and for each one the number of pixels
+   of that color (i.e. a color histogram).
+
+   If 'maxcolors' is zero, make the output have 5 spare slots at the end
+   for expansion.
+   
+   If 'maxcolors' is nonzero, make the output have 'maxcolors' slots in
+   it, and if there are more colors than that in the image, don't return
+   anything except a NULL pointer.
+-----------------------------------------------------------------------------*/
+    colorhash_table cht;
+    colorhist_vector chv;
+
+    cht = ppm_computecolorhash(pixels, cols, rows, maxcolors, colorsP);
+    if (cht == NULL)
+        chv = NULL;
+    else {
+        chv = ppm_colorhashtocolorhist(cht, maxcolors);
+        ppm_freecolorhash(cht);
+    }
+    return chv;
+}
+
+
+
+colorhist_vector
+ppm_computecolorhist2(FILE * const ifp,
+                      const int cols, const int rows, 
+                      const pixval maxval, const int format, 
+                      const int maxcolors, int * const colorsP ) {
+
+    colorhash_table cht;
+    colorhist_vector chv;
+
+    cht = ppm_computecolorhash2(ifp, cols, rows, maxval, format, 
+                                maxcolors, colorsP);
+    if (cht ==NULL)
+        return NULL;
+    chv = ppm_colorhashtocolorhist(cht, maxcolors);
+    ppm_freecolorhash(cht);
+    return chv;
+}
+
+
+
+void
+ppm_addtocolorhist( colorhist_vector chv, 
+                    int * const colorsP, const int maxcolors, 
+                    const pixel * const colorP, 
+                    const int value, const int position ) {
+    int i, j;
+
+    /* Search colorhist for the color. */
+    for ( i = 0; i < *colorsP; ++i )
+        if ( PPM_EQUAL( chv[i].color, *colorP ) ) {
+            /* Found it - move to new slot. */
+            if ( position > i ) {
+                for ( j = i; j < position; ++j )
+                    chv[j] = chv[j + 1];
+            } else if ( position < i ) {
+                for ( j = i; j > position; --j )
+                    chv[j] = chv[j - 1];
+            }
+            chv[position].color = *colorP;
+            chv[position].value = value;
+            return;
+        }
+    if ( *colorsP < maxcolors ) {
+        /* Didn't find it, but there's room to add it; so do so. */
+        for ( i = *colorsP; i > position; --i )
+            chv[i] = chv[i - 1];
+        chv[position].color = *colorP;
+        chv[position].value = value;
+        ++(*colorsP);
+    }
+}
+
+
+
+colorhash_table
+ppm_alloccolorhash(void)  {
+    colorhash_table cht;
+    int i;
+
+    MALLOCARRAY(cht, HASH_SIZE);
+    if (cht == NULL)
+        pm_error( "out of memory allocating hash table" );
+
+    for (i = 0; i < HASH_SIZE; ++i)
+        cht[i] = NULL;
+
+    return cht;
+}
+
+
+
+static colorhash_table
+computecolorhash(pixel ** const pixels, 
+                 const int cols, const int rows, 
+                 const int maxcolors, int * const colorsP,
+                 FILE * const ifp, pixval const maxval, int const format) {
+/*----------------------------------------------------------------------------
+   Compute a color histogram from an image.  The input is one of two types:
+
+   1) a two-dimensional array of pixels 'pixels';  In this case, 'pixels'
+      is non-NULL and 'ifp' is NULL.
+
+   2) an open file, positioned to the image data.  In this case,
+      'pixels' is NULL and 'ifp' is non-NULL.  ifp is the stream
+      descriptor for the input file, and 'maxval' and 'format' are
+      parameters of the image data in it.
+      
+      We return with the file still open and its position undefined.  
+
+   In either case, the image is 'cols' by 'rows'.
+
+   Return the number of colors found as *colorsP.
+
+   However, if 'maxcolors' is nonzero and the number of colors is
+   greater than 'maxcolors', return a null return value and *colorsP
+   undefined.
+-----------------------------------------------------------------------------*/
+    colorhash_table cht;
+    int row;
+    pixel * rowbuffer;  /* malloc'ed */
+        /* Buffer for a row read from the input file; undefined (but still
+           allocated) if input is not from a file.
+        */
+    
+    cht = ppm_alloccolorhash( );
+    *colorsP = 0;   /* initial value */
+
+    rowbuffer = ppm_allocrow(cols);
+
+    /* Go through the entire image, building a hash table of colors. */
+    for (row = 0; row < rows; ++row) {
+        int col;
+        pixel * pixelrow;  /* The row of pixels we are processing */
+
+        if (ifp) {
+            ppm_readppmrow(ifp, rowbuffer, cols, maxval, format);
+            pixelrow = rowbuffer;
+        } else 
+            pixelrow = pixels[row];
+
+        for (col = 0; col < cols; ++col) {
+            const pixel apixel = pixelrow[col];
+            const int hash = ppm_hashpixel(apixel);
+            colorhist_list chl; 
+
+            for (chl = cht[hash]; 
+                 chl != (colorhist_list) 0 && 
+                     !PPM_EQUAL(chl->ch.color, apixel);
+                 chl = chl->next);
+
+            if (chl)
+                chl->ch.value++;
+            else {
+                /* It's not in the hash yet, so add it (if allowed) */
+                ++(*colorsP);
+                if (maxcolors > 0 && *colorsP > maxcolors) {
+                    ppm_freecolorhash(cht);
+                    return NULL;
+                } else {
+                    MALLOCVAR(chl);
+                    if (chl == NULL)
+                        pm_error("out of memory computing hash table");
+                    chl->ch.color = apixel;
+                    chl->ch.value = 1;
+                    chl->next = cht[hash];
+                    cht[hash] = chl;
+                }
+            }
+        }
+    }
+    ppm_freerow(rowbuffer);
+    return cht;
+}
+
+
+
+colorhash_table
+ppm_computecolorhash(pixel ** const pixels, 
+                     const int cols, const int rows, 
+                     const int maxcolors, int * const colorsP) {
+
+    return computecolorhash(pixels, cols, rows, maxcolors, colorsP, 
+                            NULL, 0, 0);
+}
+
+
+
+colorhash_table
+ppm_computecolorhash2(FILE * const ifp,
+                      const int cols, const int rows, 
+                      const pixval maxval, const int format, 
+                      const int maxcolors, int * const colorsP ) {
+
+    return computecolorhash(NULL, cols, rows, maxcolors, colorsP,
+                            ifp, maxval, format);
+}
+
+
+
+int
+ppm_addtocolorhash(colorhash_table const cht, 
+                   const pixel *   const colorP, 
+                   int             const value) {
+/*----------------------------------------------------------------------------
+   Add color *colorP to the color hash 'cht' with associated value 'value'.
+
+   Assume the color is not already in the hash.
+-----------------------------------------------------------------------------*/
+    int retval;
+    colorhist_list chl;
+
+    MALLOCVAR(chl);
+    if (chl == NULL)
+        retval = -1;
+    else {
+        int const hash = ppm_hashpixel(*colorP);
+
+        chl->ch.color = *colorP;
+        chl->ch.value = value;
+        chl->next = cht[hash];
+        cht[hash] = chl;
+        retval = 0;
+    }
+    return retval;
+}
+
+
+
+void
+ppm_delfromcolorhash(colorhash_table const cht, 
+                     const pixel *   const colorP) {
+/*----------------------------------------------------------------------------
+   Delete the color *colorP from the colorhahs 'cht', if it's there.
+-----------------------------------------------------------------------------*/
+    int hash;
+    colorhist_list * chlP;
+
+    hash = ppm_hashpixel(*colorP);
+    for (chlP = &cht[hash]; *chlP; chlP = &(*chlP)->next) {
+        if (PPM_EQUAL((*chlP)->ch.color, *colorP)) {
+            /* chlP points to a pointer to the hash chain element we want
+               to remove.
+            */
+            colorhist_list const chl = *chlP;
+            *chlP = chl->next;
+            free(chl);
+            return;
+        }
+    }
+}
+
+
+
+static unsigned int
+colorHashSize(colorhash_table const cht) {
+/*----------------------------------------------------------------------------
+   Return the number of colors in the colorhash table 'cht'
+-----------------------------------------------------------------------------*/
+    unsigned int nColors;
+        /* Number of colors found so far */
+    int i;
+    /* Loop through the hash table. */
+    nColors = 0;
+    for (i = 0; i < HASH_SIZE; ++i) {
+        colorhist_list chl;
+        for (chl = cht[i]; chl; chl = chl->next) 
+            ++nColors;
+    }
+    return nColors;
+}
+
+
+
+colorhist_vector
+ppm_colorhashtocolorhist(colorhash_table const cht, int const maxcolors) {
+
+    colorhist_vector chv;
+    colorhist_list chl;
+    unsigned int chvSize;
+
+    if (maxcolors == 0)
+        /* We leave space for 5 more colors so caller can add in special
+           colors like background color and transparent color.
+        */
+        chvSize = colorHashSize(cht) + 5;
+    else
+        /* Caller is responsible for making sure there are no more
+           than 'maxcolors' colors in the colorhash table.  NOTE:
+           Before March 2002, the maxcolors == 0 invocation didn't
+           exist.  
+        */
+        chvSize = maxcolors;
+
+    /* Collate the hash table into a simple colorhist array. */
+    MALLOCARRAY(chv, chvSize);
+    if (chv == NULL)
+        pm_error("out of memory generating histogram");
+
+    {
+        int i, j;
+        /* Loop through the hash table. */
+        j = 0;
+        for (i = 0; i < HASH_SIZE; ++i)
+            for (chl = cht[i]; chl; chl = chl->next) {
+                /* Add the new entry. */
+                chv[j] = chl->ch;
+                ++j;
+            }
+    }
+    return chv;
+}
+
+
+
+colorhash_table
+ppm_colorhisttocolorhash(colorhist_vector const chv, 
+                         int              const colors) {
+    colorhash_table cht;
+    int i, hash;
+    pixel color;
+    colorhist_list chl;
+
+    cht = ppm_alloccolorhash( );  /* Initializes to NULLs */
+
+    for (i = 0; i < colors; ++i) {
+        color = chv[i].color;
+        hash = ppm_hashpixel(color);
+        for (chl = cht[hash]; chl != (colorhist_list) 0; chl = chl->next)
+            if (PPM_EQUAL(chl->ch.color, color))
+                pm_error(
+                    "same color found twice - %d %d %d", PPM_GETR(color),
+                    PPM_GETG(color), PPM_GETB(color) );
+        MALLOCVAR(chl);
+        if (chl == NULL)
+            pm_error("out of memory");
+        chl->ch.color = color;
+        chl->ch.value = i;
+        chl->next = cht[hash];
+        cht[hash] = chl;
+    }
+    return cht;
+}
+
+
+
+int
+ppm_lookupcolor(colorhash_table const cht, 
+                const pixel *   const colorP) {
+    int hash;
+    colorhist_list chl;
+
+    hash = ppm_hashpixel(*colorP);
+    for (chl = cht[hash]; chl; chl = chl->next)
+        if (PPM_EQUAL(chl->ch.color, *colorP))
+            return chl->ch.value;
+
+    return -1;
+}
+
+
+
+void
+ppm_freecolorhist(colorhist_vector const chv) {
+    free(chv);
+}
+
+
+
+void
+ppm_freecolorhash(colorhash_table const cht) {
+
+    int i;
+    colorhist_list chl, chlnext;
+
+    for (i = 0; i < HASH_SIZE; ++i)
+        for (chl = cht[i]; chl != (colorhist_list) 0; chl = chlnext) {
+            chlnext = chl->next;
+            free(chl);
+        }
+    free(cht);
+}
+
+
+/*****************************************************************************
+  The following "color row" routines are taken from Ingo Wilken's ilbm
+  package, dated December 1994.  Since they're only used by ppmtoilbm
+  and ilbmtoppm today, they aren't documented or well maintained, but
+  they seem pretty useful and ought to be used in other programs.
+
+  -Bryan 2001.03.10
+****************************************************************************/
+
+/* libcmap2.c - support routines for color rows
+**
+** Copyright (C) 1994 Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+colorhash_table
+ppm_colorrowtocolorhash(colorrow, ncolors)
+    pixel *colorrow;
+    int ncolors;
+{
+    colorhash_table cht;
+    int i;
+
+    cht = ppm_alloccolorhash();
+    for( i = 0; i < ncolors; i++ ) {
+        if( ppm_lookupcolor(cht, &colorrow[i]) < 0 ) {
+            if( ppm_addtocolorhash(cht, &colorrow[i], i) < 0 )
+                pm_error("out of memory adding to hash table");
+        }
+    }
+    return cht;
+}
+
+
+pixel *
+ppm_computecolorrow(pixels, cols, rows, maxcolors, ncolorsP)
+    pixel **pixels;
+    int cols, rows, maxcolors;
+    int *ncolorsP;
+{
+    int ncolors, row, col;
+    colorhash_table cht;
+    pixel *pixrow;
+
+    pixrow = ppm_allocrow(maxcolors);
+    cht = ppm_alloccolorhash(); ncolors = 0;
+    for( row = 0; row < rows; row++ ) {
+        for( col = 0; col < cols; col++ ) {
+            if( ppm_lookupcolor(cht, &pixels[row][col]) < 0 ) {
+                if( ncolors >= maxcolors ) {
+                    ppm_freerow(pixrow);
+                    pixrow = (pixel *)0;
+                    ncolors = -1;
+                    goto fail;
+                }
+                if( ppm_addtocolorhash(cht, &pixels[row][col], ncolors) < 0 )
+                    pm_error("out of memory adding to hash table");
+                pixrow[ncolors] = pixels[row][col];
+                ++ncolors;
+            }
+        }
+    }
+fail:
+    ppm_freecolorhash(cht);
+
+    *ncolorsP = ncolors;
+    return pixrow;
+}
+
+
+pixel *
+ppm_mapfiletocolorrow(file, maxcolors, ncolorsP, maxvalP)
+    FILE *file;
+    int maxcolors;
+    int *ncolorsP;
+    pixval *maxvalP;
+{
+    int cols, rows, format, row, col, ncolors;
+    pixel *pixrow, *temprow;
+    colorhash_table cht;
+
+    pixrow = ppm_allocrow(maxcolors);
+
+    ppm_readppminit(file, &cols, &rows, maxvalP, &format);
+    temprow = ppm_allocrow(cols);
+    cht = ppm_alloccolorhash(); ncolors = 0;
+    for( row = 0; row < rows; row++ ) {
+        ppm_readppmrow(file, temprow, cols, *maxvalP, format);
+        for( col = 0; col < cols; col++ ) {
+            if( ppm_lookupcolor(cht, &temprow[col]) < 0 ) {
+                if( ncolors >= maxcolors ) {
+                    ppm_freerow(pixrow);
+                    pixrow = (pixel *)0;
+                    ncolors = -1;
+                    goto fail;
+                }
+                if( ppm_addtocolorhash(cht, &temprow[col], ncolors) < 0 )
+                    pm_error("out of memory adding to hash table");
+                pixrow[ncolors] = temprow[col];
+                ++ncolors;
+            }
+        }
+    }
+fail:
+    ppm_freecolorhash(cht);
+    ppm_freerow(temprow);
+
+    *ncolorsP = ncolors;
+    return pixrow;
+}
+
+
+static int
+pixel_cmp(const void * const a, const void * const b) {
+    const pixel *p1 = (const pixel *)a, *p2 = (const pixel *)b;
+    int diff;
+
+    diff = PPM_GETR(*p1) - PPM_GETR(*p2);
+    if( diff == 0 ) {
+        diff = PPM_GETG(*p1) - PPM_GETG(*p2);
+        if( diff == 0 )
+            diff = PPM_GETB(*p1) - PPM_GETB(*p2);
+    }
+    return diff;
+}
+
+static int (*custom_cmp)(pixel *, pixel *);
+
+static int
+custom_stub(const void * const a, const void * const b) {
+    return (*custom_cmp)((pixel *)a, (pixel *)b);
+}
+
+
+void
+ppm_sortcolorrow(pixel * const colorrow, const int ncolors, 
+                 int (*cmpfunc)(pixel *, pixel *)) {
+
+    if( cmpfunc ) {
+        custom_cmp = cmpfunc;
+        qsort((void *)colorrow, ncolors, sizeof(pixel), custom_stub);
+    } else
+        qsort((void *)colorrow, ncolors, sizeof(pixel), pixel_cmp);
+}
+
+
+
+int
+ppm_addtocolorrow(colorrow, ncolorsP, maxcolors, pixelP)
+    pixel *colorrow;
+    int *ncolorsP;
+    int maxcolors;
+    pixel *pixelP;
+{
+    int i;
+    pixval r, g, b;
+
+    r = PPM_GETR(*pixelP);
+    g = PPM_GETG(*pixelP);
+    b = PPM_GETB(*pixelP);
+
+    for( i = 0; i < *ncolorsP; i++ ) {
+        if( PPM_GETR(colorrow[i]) == r &&
+            PPM_GETG(colorrow[i]) == g &&
+            PPM_GETB(colorrow[i]) == b )
+                return i;
+    }
+
+    i = *ncolorsP;
+    if( i >= maxcolors )
+        return -1;
+    colorrow[i] = *pixelP;
+    ++*ncolorsP;
+    return i;
+}
+
+
+int
+ppm_findclosestcolor(const pixel * const colormap, 
+                     int           const ncolors, 
+                     const pixel * const pP) {
+    
+    /* Search colormap for closest match.       */
+
+    int i;
+    int ind;
+    unsigned int bestDist;
+
+    bestDist = UINT_MAX;
+    ind = -1;
+
+    for(i = 0; i < ncolors && bestDist > 0; ++i) {
+        unsigned int const dist = PPM_DISTANCE(*pP, colormap[i]);
+        
+        if (dist < bestDist ) {
+            ind = i;
+            bestDist = dist;
+        }
+    }
+    return ind;
+}
+
+
+void
+#if __STDC__
+ppm_colorrowtomapfile(FILE *ofp, pixel *colormap, int ncolors, pixval maxval)
+#else
+ppm_colorrowtomapfile(ofp, colormap, ncolors, maxval)
+    FILE *ofp;
+    pixel *colormap;
+    int ncolors;
+    pixval maxval;
+#endif
+{
+    int i;
+
+    ppm_writeppminit(ofp, ncolors, 1, maxval, 1);
+    for( i = 0; i < ncolors; i++ )
+        ppm_writeppmrow(ofp, &colormap[i], 1, maxval, 1);
+}
+
+
+
diff --git a/lib/libppmcolor.c b/lib/libppmcolor.c
new file mode 100644
index 00000000..ff1a1e67
--- /dev/null
+++ b/lib/libppmcolor.c
@@ -0,0 +1,710 @@
+/*
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "ppm.h"
+#include "colorname.h"
+
+
+static void
+computeHexTable(int hexit[]) {
+    int i;
+    for ( i = 0; i < 256; ++i )
+        hexit[i] = 1234567890;
+    hexit['0'] = 0;
+    hexit['1'] = 1;
+    hexit['2'] = 2;
+    hexit['3'] = 3;
+    hexit['4'] = 4;
+    hexit['5'] = 5;
+    hexit['6'] = 6;
+    hexit['7'] = 7;
+    hexit['8'] = 8;
+    hexit['9'] = 9;
+    hexit['a'] = hexit['A'] = 10;
+    hexit['b'] = hexit['B'] = 11;
+    hexit['c'] = hexit['C'] = 12;
+    hexit['d'] = hexit['D'] = 13;
+    hexit['e'] = hexit['E'] = 14;
+    hexit['f'] = hexit['F'] = 15;
+}
+
+
+
+static long
+invRgbnorm(pixval       const rgb,
+           pixval       const maxval,
+           unsigned int const hexDigits) {
+/*----------------------------------------------------------------------------
+  This is the inverse of 'rgbnorm', below.
+-----------------------------------------------------------------------------*/
+    long retval;
+
+    switch (hexDigits) {
+    case 1:
+        retval = (long)((double) rgb * 15 / maxval + 0.5);
+        break;
+    case 2:
+        retval = (long) ((double) rgb * 255 / maxval + 0.5);
+        break;
+    case 3:
+        retval = (long) ((double) rgb * 4095 / maxval + 0.5);
+        break;
+    case 4:
+        retval = (long) ((double) rgb * 65535UL / maxval + 0.5);
+        break;
+    default:
+        pm_message("Internal error in invRgbnorm()");
+        abort();
+    }
+    return retval;
+}
+
+
+
+static pixval
+rgbnorm(long         const rgb, 
+        pixval       const maxval, 
+        unsigned int const hexDigitCount, 
+        bool         const closeOk,
+        const char * const colorname) {
+/*----------------------------------------------------------------------------
+   Normalize the color (r, g, or b) value 'rgb', which was specified
+   with 'hexDigitCount' digits, to a maxval of 'maxval'.  If the
+   number of digits isn't valid, issue an error message and identify
+   the complete color color specification in error as 'colorname'.
+
+   For example, if the user says 0ff/000/000 and the maxval is 100,
+   then rgb is 0xff, n is 3, and our result is 
+   0xff / (16**3-1) * 100 = 6.
+
+-----------------------------------------------------------------------------*/
+    pixval retval;
+
+    assert(hexDigitCount > 0);
+
+    switch (hexDigitCount) {
+    case 1:
+        retval = (pixval)((double) rgb * maxval / 15 + 0.5);
+        break;
+    case 2:
+        retval = (pixval) ((double) rgb * maxval / 255 + 0.5);
+        break;
+    case 3:
+        retval = (pixval) ((double) rgb * maxval / 4095 + 0.5);
+        break;
+    case 4:
+        retval = (pixval) ((double) rgb * maxval / 65535L + 0.5);
+        break;
+    default:
+        pm_error("color specifier '%s' has too many digits", colorname);
+    }
+
+    if (!closeOk) {
+        long const newrgb = invRgbnorm(retval, maxval, hexDigitCount);
+        if (newrgb != rgb)
+            pm_message("WARNING: Component 0x%lx of color '%s' "
+                       "cannot be represented precisely with maxval %u.  "
+                       "Approximating as %u.",
+                       rgb, colorname, maxval, retval);
+    }
+    return retval;
+}
+
+
+
+static void
+parseNewHexX11(const char       colorname[], 
+               pixval     const maxval,
+               bool       const closeOk,
+               pixel *    const colorP) {
+
+    int hexit[256];
+
+    const char* cp;
+    pixval r,g,b;
+    pixval rNorm, gNorm, bNorm;
+
+    int i;
+
+    computeHexTable(hexit);
+
+    cp = colorname + 4;
+
+    for (i = 0, r = 0; *cp != '/'; ++i, ++cp)
+        r = r * 16 + hexit[(int)*cp];
+    rNorm = rgbnorm(r, maxval, i, closeOk, colorname);
+
+    for (i = 0, g = 0,++cp; *cp != '/'; ++i, ++cp )
+        g = g * 16 + hexit[(int)*cp];
+    gNorm = rgbnorm(g, maxval, i, closeOk, colorname);
+
+    for (i = 0, b = 0, ++cp; *cp != '\0'; ++i, ++cp )
+        b = b * 16 + hexit[(int)*cp];
+    bNorm = rgbnorm(b, maxval, i, closeOk, colorname);
+
+    PPM_ASSIGN(*colorP, rNorm, gNorm, bNorm);
+}
+
+
+
+static void
+parseNewDecX11(char       const colorname[], 
+               pixval     const maxval,
+               bool       const closeOk,
+               pixel *    const colorP) {
+
+    float const epsilon = 1.0/65536.0;
+    float fr, fg, fb;
+    pixval rNorm, gNorm, bNorm;
+
+    if (sscanf( colorname, "rgbi:%f/%f/%f", &fr, &fg, &fb) != 3)
+        pm_error("invalid color specifier '%s'", colorname);
+    if (fr < 0.0 || fr > 1.0 || fg < 0.0 || fg > 1.0 
+        || fb < 0.0 || fb > 1.0)
+        pm_error("invalid color specifier '%s' - "
+                 "values must be between 0.0 and 1.0", colorname );
+
+    rNorm = fr * maxval + 0.5;
+    gNorm = fg * maxval + 0.5;
+    bNorm = fb * maxval + 0.5;
+
+    if (!closeOk) {
+        if (fabs((double)rNorm/maxval - fr) > epsilon ||
+            fabs((double)gNorm/maxval - fg) > epsilon ||
+            fabs((double)bNorm/maxval - fb) > epsilon)
+            pm_message("WARNING: Color '%s' cannot be represented "
+                       "precisely with maxval %u.  "
+                       "Approximating as (%u,%u,%u).",
+                       colorname, maxval, rNorm, gNorm, bNorm);
+    }
+    PPM_ASSIGN(*colorP, rNorm, gNorm, bNorm);
+}
+
+
+
+static void
+parseOldX11(const char       colorname[], 
+            pixval     const maxval,
+            bool       const closeOk,
+            pixel *    const colorP) {
+
+    int hexit[256];
+    long r,g,b;
+    pixval rNorm, gNorm, bNorm;
+    
+    computeHexTable(hexit);
+
+    switch (strlen(colorname) - 1 /* (Number of hex digits) */) {
+    case 3:
+        r = hexit[(int)colorname[1]];
+        g = hexit[(int)colorname[2]];
+        b = hexit[(int)colorname[3]];
+        rNorm = rgbnorm(r, maxval, 1, closeOk, colorname);
+        gNorm = rgbnorm(g, maxval, 1, closeOk, colorname);
+        bNorm = rgbnorm(b, maxval, 1, closeOk, colorname);
+        break;
+
+    case 6:
+        r = (hexit[(int)colorname[1]] << 4 ) + hexit[(int)colorname[2]];
+        g = (hexit[(int)colorname[3]] << 4 ) + hexit[(int)colorname[4]];
+        b = (hexit[(int)colorname[5]] << 4 ) + hexit[(int)colorname[6]];
+        rNorm = rgbnorm(r, maxval, 2, closeOk, colorname);
+        gNorm = rgbnorm(g, maxval, 2, closeOk, colorname);
+        bNorm = rgbnorm(b, maxval, 2, closeOk, colorname);
+        break;
+
+    case 9:
+        r = (hexit[(int)colorname[1]] << 8) +
+            (hexit[(int)colorname[2]] << 4) +
+            (hexit[(int)colorname[3]] << 0);
+        g = (hexit[(int)colorname[4]] << 8) + 
+            (hexit[(int)colorname[5]] << 4) +
+            (hexit[(int)colorname[6]] << 0);
+        b = (hexit[(int)colorname[7]] << 8) + 
+            (hexit[(int)colorname[8]] << 4) +
+            (hexit[(int)colorname[9]] << 0);
+        rNorm = rgbnorm(r, maxval, 3, closeOk, colorname);
+        gNorm = rgbnorm(g, maxval, 3, closeOk, colorname);
+        bNorm = rgbnorm(b, maxval, 3, closeOk, colorname);
+        break;
+
+    case 12:
+        r = (hexit[(int)colorname[1]] << 12) + 
+            (hexit[(int)colorname[2]] <<  8) +
+            (hexit[(int)colorname[3]] <<  4) + hexit[(int)colorname[4]];
+        g = (hexit[(int)colorname[5]] << 12) + 
+            (hexit[(int)colorname[6]] <<  8) +
+            (hexit[(int)colorname[7]] <<  4) + hexit[(int)colorname[8]];
+        b = (hexit[(int)colorname[9]] << 12) + 
+            (hexit[(int)colorname[10]] << 8) +
+            (hexit[(int)colorname[11]] << 4) + hexit[(int)colorname[12]];
+        rNorm = rgbnorm(r, maxval, 4, closeOk, colorname);
+        gNorm = rgbnorm(g, maxval, 4, closeOk, colorname);
+        bNorm = rgbnorm(b, maxval, 4, closeOk, colorname);
+        break;
+
+    default:
+        pm_error("invalid color specifier '%s'", colorname);
+    }
+    PPM_ASSIGN(*colorP, rNorm, gNorm, bNorm);
+}
+
+
+
+
+static void
+parseOldX11Dec(const char       colorname[], 
+               pixval     const maxval,
+               bool       const closeOk,
+               pixel *    const colorP) {
+
+    float const epsilon = 1.0/65536.0;
+
+    float fr, fg, fb;
+    pixval rNorm, gNorm, bNorm;
+
+    if (sscanf(colorname, "%f,%f,%f", &fr, &fg, &fb) != 3)
+        pm_error("invalid color specifier '%s'", colorname);
+    if (fr < 0.0 || fr > 1.0 || fg < 0.0 || fg > 1.0 
+        || fb < 0.0 || fb > 1.0)
+        pm_error("invalid color specifier '%s' - "
+                 "values must be between 0.0 and 1.0", colorname );
+
+    rNorm = fr * maxval + 0.5;
+    gNorm = fg * maxval + 0.5;
+    bNorm = fb * maxval + 0.5;
+
+    if (!closeOk) {
+        if (fabs((float)rNorm/maxval - fr) > epsilon ||
+            fabs((float)gNorm/maxval - fg) > epsilon ||
+            fabs((float)bNorm/maxval - fb) > epsilon)
+            pm_message("WARNING: Color '%s' cannot be represented "
+                       "precisely with maxval %u.  "
+                       "Approximating as (%u,%u,%u).",
+                       colorname, maxval, rNorm, gNorm, bNorm);
+    }
+    PPM_ASSIGN(*colorP, rNorm, gNorm, bNorm);
+}
+
+
+
+pixel
+ppm_parsecolor2(const char * const colorname,
+                pixval       const maxval,
+                int          const closeOk) {
+
+    pixel color;
+    
+    if (strncmp(colorname, "rgb:", 4) == 0)
+        /* It's a new-X11-style hexadecimal rgb specifier. */
+        parseNewHexX11(colorname, maxval, closeOk, &color);
+    else if (strncmp(colorname, "rgbi:", 5) == 0)
+        /* It's a new-X11-style decimal/float rgb specifier. */
+        parseNewDecX11(colorname, maxval, closeOk, &color);
+    else if (colorname[0] == '#')
+        /* It's an old-X11-style hexadecimal rgb specifier. */
+        parseOldX11(colorname, maxval, closeOk, &color);
+    else if ((colorname[0] >= '0' && colorname[0] <= '9') ||
+             colorname[0] == '.')
+        /* It's an old-style decimal/float rgb specifier. */
+        parseOldX11Dec(colorname, maxval, closeOk, &color);
+    else 
+        /* Must be a name from the X-style rgb file. */
+        pm_parse_dictionary_name(colorname, maxval, closeOk, &color);
+    
+    return color;
+}
+
+
+
+pixel
+ppm_parsecolor(const char * const colorname, 
+               pixval       const maxval) {
+
+    return ppm_parsecolor2(colorname, maxval, TRUE);
+}
+
+
+
+char*
+ppm_colorname(const pixel* const colorP, 
+              pixval       const maxval, 
+              int          const hexok)   {
+
+    int r, g, b;
+    FILE* f;
+    static char colorname[200];
+
+    if (maxval == 255) {
+        r = PPM_GETR(*colorP);
+        g = PPM_GETG(*colorP);
+        b = PPM_GETB(*colorP);
+    } else {
+        r = (int) PPM_GETR(*colorP) * 255 / (int) maxval;
+        g = (int) PPM_GETG(*colorP) * 255 / (int) maxval;
+        b = (int) PPM_GETB(*colorP) * 255 / (int) maxval;
+    }
+
+    f = pm_openColornameFile(NULL, !hexok);
+    if (f != NULL) {
+        int best_diff, this_diff;
+        bool eof;
+
+        best_diff = 32767;
+        eof = FALSE;
+        while (!eof && best_diff > 0 ) {
+            struct colorfile_entry const ce = pm_colorget(f);
+            if (ce.colorname)  {
+                this_diff = abs(r - ce.r) + abs(g - ce.g) + abs(b - ce.b);
+                if (this_diff < best_diff) {
+                    best_diff = this_diff;
+                    strcpy(colorname, ce.colorname);
+                }
+            } else
+                eof = TRUE;
+        }
+        fclose(f);
+        if (best_diff != 32767 && (best_diff == 0 || ! hexok))
+            return colorname;
+    }
+
+    /* Color lookup failed, but caller is willing to take an X11-style
+       hex specifier, so return that.
+    */
+    sprintf(colorname, "#%02x%02x%02x", r, g, b);
+    return colorname;
+}
+
+
+
+#define MAXCOLORNAMES 1000u
+
+static void
+processColorfileEntry(struct colorfile_entry const ce,
+                      colorhash_table        const cht,
+                      const char **          const colornames,
+                      pixel *                const colors,
+                      unsigned int *         const colornameIndexP) {
+
+    if (*colornameIndexP >= MAXCOLORNAMES)
+        pm_error("Too many colors in colorname dictionary.  "
+                 "Max allowed is %u", MAXCOLORNAMES);
+    else {
+        pixel color;
+
+        PPM_ASSIGN(color, ce.r, ce.g, ce.b);
+
+        if (ppm_lookupcolor(cht, &color) >= 0) {
+            /* The color is already in the hash, which means we saw it
+               earlier in the file.  We prefer the first name that the
+               file gives for each color, so we just ignore the
+               current entry.  
+            */
+        } else {
+            ppm_addtocolorhash(cht, &color, *colornameIndexP);
+            colornames[*colornameIndexP] = strdup(ce.colorname);
+            colors[*colornameIndexP] = color;
+            if (colornames[*colornameIndexP] == NULL)
+                pm_error("Unable to allocate space for color name");
+            ++(*colornameIndexP);
+        }
+    }
+}
+
+
+
+static void
+readcolordict(const char *    const fileName,
+              bool            const mustOpen,
+              unsigned int *  const nColorsP,
+              const char **   const colornames,
+              pixel * const   colors,
+              colorhash_table const cht) {
+
+    FILE * colorFile;
+
+    colorFile = pm_openColornameFile(fileName, mustOpen);
+
+    if (colorFile != NULL) {
+        unsigned int colornameIndex;
+        bool done;
+
+        colornameIndex = 0;  /* initial value */
+        done = FALSE;
+        while (!done) {
+            struct colorfile_entry const ce = pm_colorget(colorFile);
+
+            if (!ce.colorname)  
+                done = TRUE;
+            else 
+                processColorfileEntry(ce, cht, colornames, colors,
+                                      &colornameIndex);
+        }
+
+        *nColorsP = colornameIndex;
+
+        while (colornameIndex < MAXCOLORNAMES)
+            colornames[colornameIndex++] = NULL;
+
+        fclose(colorFile);
+    }
+}
+
+
+
+void
+ppm_readcolordict(const char *      const fileName,
+                  int               const mustOpen,
+                  unsigned int *    const nColorsP,
+                  const char ***    const colornamesP,
+                  pixel **          const colorsP,
+                  colorhash_table * const chtP) {
+
+    colorhash_table cht;
+    const char ** colornames;
+    pixel * colors;
+    unsigned int nColors;
+
+    cht = ppm_alloccolorhash();
+
+    MALLOCARRAY(colornames, MAXCOLORNAMES);
+
+    colors = ppm_allocrow(MAXCOLORNAMES);
+
+    if (colornames == NULL)
+        pm_error("Unable to allocate space for colorname table.");
+
+    readcolordict(fileName, mustOpen, &nColors, colornames, colors, cht);
+
+    if (chtP)
+        *chtP = cht;
+    else
+        ppm_freecolorhash(cht);
+    if (colornamesP)
+        *colornamesP = colornames;
+    else
+        ppm_freecolornames(colornames);
+    if (colorsP)
+        *colorsP = colors;
+    else
+        ppm_freerow(colors);
+    if (nColorsP)
+        *nColorsP = nColors;
+}
+
+
+
+void
+ppm_readcolornamefile(const char *      const fileName, 
+                      int               const mustOpen,
+                      colorhash_table * const chtP, 
+                      const char ***    const colornamesP) {
+
+    ppm_readcolordict(fileName, mustOpen, NULL, colornamesP, NULL, chtP);
+}
+
+
+
+void
+ppm_freecolornames(const char ** const colornames) {
+
+    unsigned int i;
+
+    for (i = 0; i < MAXCOLORNAMES; ++i)
+        if (colornames[i])
+            free((char *)colornames[i]);
+
+    free(colornames);
+}
+
+
+
+static unsigned int 
+nonnegative(unsigned int const arg) {
+
+    if ((int)(arg) < 0)
+        return 0;
+    else
+        return arg;
+}
+
+
+
+pixel
+ppm_color_from_ycbcr(unsigned int const y, 
+                     int          const cb, 
+                     int          const cr) {
+/*----------------------------------------------------------------------------
+   Return the color that has luminance 'y', blue chrominance 'cb', and
+   red chrominance 'cr'.
+
+   The 3 input values can be on any scale (as long as it's the same
+   scale for all 3) and the maxval of the returned pixel value is the
+   same as that for the input values.
+
+   Rounding may cause an output value to be greater than the maxval.
+-----------------------------------------------------------------------------*/
+    pixel retval;
+
+    PPM_ASSIGN(retval, 
+               y + 1.4022 * cr,
+               nonnegative(y - 0.7145 * cr - 0.3456 * cb),
+               y + 1.7710 * cb
+        );
+    
+    return retval;
+}
+
+
+
+pixel
+ppm_color_from_hsv(struct hsv const hsv,
+                   pixval     const maxval) {
+
+    pixel retval;
+    double R, G, B;
+
+    if (hsv.s == 0) {
+        R = hsv.v;
+        G = hsv.v;
+        B = hsv.v;
+    } else {
+        unsigned int const sectorSize = 60;
+            /* Color wheel is divided into six 60 degree sectors. */
+        unsigned int const sector = (hsv.h/sectorSize);
+            /* The sector in which our color resides.  Value is in 0..5 */
+        double const f = (hsv.h - sector*sectorSize)/60;
+            /* The fraction of the way the color is from one side of
+               our sector to the other side, going clockwise.  Value is
+               in [0, 1).
+            */
+        double const m = (hsv.v * (1 - hsv.s));
+        double const n = (hsv.v * (1 - (hsv.s * f)));
+        double const k = (hsv.v * (1 - (hsv.s * (1 - f))));
+
+        switch (sector) {
+        case 0:
+            R = hsv.v;
+            G = k;
+            B = m;
+            break;
+        case 1:
+            R = n;
+            G = hsv.v;
+            B = m;
+            break;
+        case 2:
+            R = m;
+            G = hsv.v;
+            B = k;
+            break;
+        case 3:
+            R = m;
+            G = n;
+            B = hsv.v;
+            break;
+        case 4:
+            R = k;
+            G = m;
+            B = hsv.v;
+            break;
+        case 5:
+            R = hsv.v;
+            G = m;
+            B = n;
+            break;
+        default:
+            pm_error("Invalid H value passed to color_from_HSV: %f", hsv.h);
+        }
+    }
+    PPM_ASSIGN(retval, 
+               ROUNDU(R * maxval),
+               ROUNDU(G * maxval),
+               ROUNDU(B * maxval));
+
+    return retval;
+}
+
+
+
+struct hsv
+ppm_hsv_from_color(pixel  const color,
+                   pixval const maxval) {
+
+    double const epsilon = 1e-5;
+
+    double const R = (double)PPM_GETR(color) / maxval;
+    double const G = (double)PPM_GETG(color) / maxval;
+    double const B = (double)PPM_GETB(color) / maxval;
+
+    enum hueSector {SECTOR_RED, SECTOR_GRN, SECTOR_BLU};
+    enum hueSector hueSector;
+
+    struct hsv retval;
+    double range;
+
+    if (R >= G) {
+        if (R >= B) {
+            hueSector = SECTOR_RED;
+            retval.v = R;
+        } else {
+            hueSector = SECTOR_BLU;
+            retval.v = B;
+        }
+    } else {
+        if (G >= B) {
+            hueSector = SECTOR_GRN;
+            retval.v = G;
+        } else {
+            hueSector = SECTOR_BLU;
+            retval.v = B;
+        }
+    }
+
+    range = retval.v - MIN(R, MIN(G, B));
+
+    if (retval.v < epsilon)
+        retval.s = 0.0;
+    else
+        retval.s = range/retval.v;
+
+    if (range < epsilon)
+        /* It's gray, so hue really has no meaning.  We arbitrarily pick 0 */
+        retval.h = 0.0;
+    else {
+        double const cr = (retval.v - R) / range;
+        double const cg = (retval.v - G) / range;
+        double const cb = (retval.v - B) / range;
+
+        double angle;  /* hue angle, in range -30 - +330 */
+
+        switch(hueSector) {
+        case SECTOR_RED: angle =   0.0 + 60.0 * (cb - cg); break;
+        case SECTOR_GRN: angle = 120.0 + 60.0 * (cr - cb); break;
+        case SECTOR_BLU: angle = 240.0 + 60.0 * (cg - cr); break;
+        }
+        retval.h = angle >= 0.0 ? angle : 360 + angle;
+    }
+
+    return retval;
+}
+
+
diff --git a/lib/libppmd.c b/lib/libppmd.c
new file mode 100644
index 00000000..6c764ca1
--- /dev/null
+++ b/lib/libppmd.c
@@ -0,0 +1,1177 @@
+/* 
+**
+** This library module contains the ppmdraw routines.
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+** 
+** The character drawing routines are by John Walker
+** Copyright (C) 1994 by John Walker, kelvin@fourmilab.ch
+*/
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "ppm.h"
+#include "ppmdfont.h"
+#include "ppmdraw.h"
+
+typedef int qsort_compare(const void *, const void *);
+    /* A compare function to pass to stdlib.h's qsort() */
+
+
+#define DDA_SCALE 8192
+
+struct penpos {
+    int x;
+    int y;
+};
+
+
+
+
+static void
+drawPoint(ppmd_drawproc       drawproc,
+          const void *  const clientdata,
+          pixel **      const pixels, 
+          int           const cols, 
+          int           const rows, 
+          pixval        const maxval, 
+          int           const x, 
+          int           const y) {
+/*----------------------------------------------------------------------------
+   Draw a single point, assuming that it is within the bounds of the
+   image.
+-----------------------------------------------------------------------------*/
+    if (drawproc == PPMD_NULLDRAWPROC) {
+        const pixel * const pixelP = clientdata;
+        
+        assert(x >= 0); assert(x < cols);
+        assert(y >= 0); assert(y < rows);
+
+        pixels[y][x] = *pixelP;
+    } else
+        drawproc(pixels, cols, rows, maxval, x, y, clientdata);
+}
+
+
+
+void
+ppmd_point_drawproc(pixel**     const pixels, 
+                    int         const cols, 
+                    int         const rows, 
+                    pixval      const maxval, 
+                    int         const x, 
+                    int         const y, 
+                    const void* const clientdata) {
+
+    if (x >= 0 && x < cols && y >= 0 && y < rows)
+        pixels[y][x] = *((pixel*)clientdata);
+}
+
+
+/* Simple fill routine. */
+
+void
+ppmd_filledrectangle(pixel **      const pixels, 
+                     int           const cols, 
+                     int           const rows, 
+                     pixval        const maxval, 
+                     int           const x, 
+                     int           const y, 
+                     int           const width, 
+                     int           const height, 
+                     ppmd_drawproc      drawProc,
+                     const void *  const clientdata) {
+
+    int cx, cy, cwidth, cheight, row;
+
+    /* Clip. */
+    cx = x;
+    cy = y;
+    cwidth = width;
+    cheight = height;
+
+    if (cx < 0) {
+        cx = 0;
+        cwidth += x;
+    }
+    if (cy < 0) {
+        cy = 0;
+        cheight += y;
+    }
+    if (cx + cwidth > cols)
+        cwidth = cols - cx;
+
+    if (cy + cheight > rows)
+        cheight = rows - cy;
+
+    /* Draw. */
+    for (row = cy; row < cy + cheight; ++row) {
+        unsigned int col;
+        for (col = cx; col < cx + cwidth; ++col)
+            drawPoint(drawProc, clientdata,
+                      pixels, cols, rows, maxval, col, row);
+    }
+}
+
+
+/* Outline drawing stuff. */
+
+static int linetype = PPMD_LINETYPE_NORMAL;
+
+int
+ppmd_setlinetype(int const type) {
+
+    int old;
+
+    old = linetype;
+    linetype = type;
+    return old;
+}
+
+
+
+static bool lineclip = TRUE;
+
+
+
+int
+ppmd_setlineclip(int const newSetting) {
+
+    bool previousSetting;
+
+    previousSetting = lineclip;
+
+    lineclip = newSetting;
+
+    return previousSetting;
+}
+
+
+
+static void
+clipEnd0(int    const x0,
+         int    const y0,
+         int    const x1,
+         int    const y1,
+         int    const cols,
+         int    const rows,
+         int *  const cx0P,
+         int *  const cy0P,
+         bool * const noLineP) {
+/*----------------------------------------------------------------------------
+   Given a line that goes from (x0, y0) to (x1, y1), where any of
+   these coordinates may be anywhere in space -- not just in the frame,
+   clip the (x0, y0) end to bring it into the frame.  
+   Return the clipped-to location as (*cx0P, *cy0P).
+
+   Iff this is not possible because the entire line described is
+   outside the frame, return *nolineP == true.
+
+   The frame is 'cols' columns starting at 0, by 'rows' rows starting
+   at 0.
+-----------------------------------------------------------------------------*/
+
+    int cx0, cy0;
+    bool noLine;
+
+    cx0 = x0;        /* initial value */
+    cy0 = y0;        /* initial value */
+    noLine = FALSE;  /* initial value */
+
+    /* Clip End 0 of the line horizontally */
+    if (cx0 < 0) {
+        if (x1 < 0)
+            noLine = TRUE;
+        else {
+            cy0 = cy0 + (y1 - cy0) * (-cx0) / (x1 - cx0);
+            cx0 = 0;
+        }
+    } else if (cx0 >= cols) {
+        if (x1 >= cols)
+            noLine = TRUE;
+        else {
+            cy0 = cy0 + (y1 - cy0) * (cols - 1 - cx0) / (x1 - cx0);
+            cx0 = cols - 1;
+        }
+    }
+
+    /* Clip End 0 of the line vertically */
+    if (cy0 < 0) {
+        if (y1 < 0)
+            noLine = TRUE;
+        else {
+            cx0 = cx0 + (x1 - cx0) * (-cy0) / (y1 - cy0);
+            cy0 = 0;
+        }
+    } else if (cy0 >= rows) {
+        if (y1 >= rows)
+            noLine = TRUE;
+        else {
+            cx0 = cx0 + (x1 - cx0) * (rows - 1 - cy0) / (y1 - cy0);
+            cy0 = rows - 1;
+        }
+    }
+
+    *cx0P = cx0;
+    *cy0P = cy0;
+    *noLineP = noLine;
+}
+
+
+
+static void
+clipEnd1(int    const x0,
+         int    const y0,
+         int    const x1,
+         int    const y1,
+         int    const cols,
+         int    const rows,
+         int *  const cx1P,
+         int *  const cy1P) {
+/*----------------------------------------------------------------------------
+   Given a line that goes from (x0, y0) to (x1, y1), where (x0, y0) is
+   within the frame, but (x1, y1) can be anywhere in space, clip the
+   (x1, y1) end to bring it into the frame.  Return the clipped-to
+   location as (*cx1P, *cy1P).
+
+   This is guaranteed to be possible, since we already know at least one
+   point (i.e. (x0, y0)) is in the frame.
+
+   The frame is 'cols' columns starting at 0, by 'rows' rows starting
+   at 0.
+-----------------------------------------------------------------------------*/
+    int cx1, cy1;
+
+    assert(x0 >= 0 && y0 < cols);
+    assert(y0 >= 0 && y0 < rows);
+    
+    /* Clip End 1 of the line horizontally */
+    cx1 = x1;  /* initial value */
+    cy1 = y1;  /* initial value */
+    
+    if (cx1 < 0) {
+        /* We know the line isn't vertical, since End 0 is in the frame
+           and End 1 is left of frame.
+        */
+        cy1 = cy1 + (y0 - cy1) * (-cx1) / (x0 - cx1);
+        cx1 = 0;
+    } else if (cx1 >= cols) {
+        /* We know the line isn't vertical, since End 0 is in the frame
+           and End 1 is right of frame.
+        */
+        cy1 = cy1 + (y0 - cy1) * (cols - 1 - cx1) / (x0 - cx1);
+        cx1 = cols - 1;
+    }
+    
+    /* Clip End 1 of the line vertically */
+    if (cy1 < 0) {
+        /* We know the line isn't horizontal, since End 0 is in the frame
+           and End 1 is above frame.
+        */
+        cx1 = cx1 + (x0 - cx1) * (-cy1) / (y0 - cy1);
+        cy1 = 0;
+    } else if (cy1 >= rows) {
+        /* We know the line isn't horizontal, since End 0 is in the frame
+           and End 1 is below frame.
+        */
+        cx1 = cx1 + (x0 - cx1) * (rows - 1 - cy1) / (y0 - cy1);
+        cy1 = rows - 1;
+    }
+
+    *cx1P = cx1;
+    *cy1P = cy1;
+}
+
+
+
+static void
+clipLine(int    const x0,
+         int    const y0,
+         int    const x1,
+         int    const y1,
+         int    const cols,
+         int    const rows,
+         int *  const cx0P,
+         int *  const cy0P,
+         int *  const cx1P,
+         int *  const cy1P,
+         bool * const noLineP) {
+/*----------------------------------------------------------------------------
+   Clip the line that goes from (x0, y0) to (x1, y1) so that none of it
+   is outside the boundaries of the raster with width 'cols' and height
+   'rows'
+
+   The clipped line goes from (*cx0P, *cy0P) to (*cx1P, *cy1P).
+
+   But if the entire line is outside the boundaries (i.e. we clip the
+   entire line), return *noLineP true and the other values undefined.
+-----------------------------------------------------------------------------*/
+    int cx0, cy0, cx1, cy1;
+        /* The line we successively modify.  Starts out as the input
+           line and ends up as the output line.
+        */
+    bool noLine;
+
+    clipEnd0(x0, y0, x1, y1, cols, rows, &cx0, &cy0, &noLine);
+
+    if (!noLine) {
+        /* (cx0, cy0) is in the frame: */
+        assert(cx0 >= 0 && cy0 < cols);
+        assert(cy0 >= 0 && cy0 < rows);
+
+        clipEnd1(cx0, cy0, x1, y1, cols, rows, &cx1, &cy1);
+    }
+
+    *cx0P = cx0;
+    *cy0P = cy0;
+    *cx1P = cx1;
+    *cy1P = cy1;
+    *noLineP = noLine;
+}
+
+
+
+static void
+drawShallowLine(ppmd_drawproc       drawProc,
+                const void *  const clientdata,
+                pixel **      const pixels, 
+                int           const cols, 
+                int           const rows, 
+                pixval        const maxval, 
+                int           const x0, 
+                int           const y0,
+                int           const x1,
+                int           const y1) {
+/*----------------------------------------------------------------------------
+   Draw a line that is more horizontal than vertical.
+
+   Don't clip.
+
+   Assume the line has distinct start and end points (i.e. it's at least
+   two points).
+-----------------------------------------------------------------------------*/
+
+    /* Loop over X domain. */
+    long dy, srow;
+    int dx, col, row, prevrow;
+
+    if (x1 > x0)
+        dx = 1;
+    else
+        dx = -1;
+    dy = (y1 - y0) * DDA_SCALE / abs(x1 - x0);
+    prevrow = row = y0;
+    srow = row * DDA_SCALE + DDA_SCALE / 2;
+    col = x0;
+    for ( ; ; ) {
+        if (linetype == PPMD_LINETYPE_NODIAGS && row != prevrow) {
+            drawPoint(drawProc, clientdata,
+                      pixels, cols, rows, maxval, col, prevrow);
+            prevrow = row;
+        }
+        drawPoint(drawProc, clientdata, pixels, cols, rows, maxval, col, row);
+        if (col == x1)
+            break;
+        srow += dy;
+        row = srow / DDA_SCALE;
+        col += dx;
+    }
+}
+
+
+
+static void
+drawSteepLine(ppmd_drawproc       drawProc,
+              const void *  const clientdata,
+              pixel **      const pixels, 
+              int           const cols, 
+              int           const rows, 
+              pixval        const maxval, 
+              int           const x0, 
+              int           const y0,
+              int           const x1,
+              int           const y1) {
+/*----------------------------------------------------------------------------
+   Draw a line that is more vertical than horizontal.
+
+   Don't clip.
+
+   Assume the line has distinct start and end points (i.e. it's at least
+   two points).
+-----------------------------------------------------------------------------*/
+    /* Loop over Y domain. */
+
+    long dx, scol;
+    int dy, col, row, prevcol;
+
+    if (y1 > y0)
+        dy = 1;
+    else
+        dy = -1;
+    dx = (x1 - x0) * DDA_SCALE / abs(y1 - y0);
+    row = y0;
+    prevcol = col = x0;
+    scol = col * DDA_SCALE + DDA_SCALE / 2;
+    for ( ; ; ) {
+        if (linetype == PPMD_LINETYPE_NODIAGS && col != prevcol) {
+            drawPoint(drawProc, clientdata,
+                      pixels, cols, rows, maxval, prevcol, row);
+            prevcol = col;
+        }
+        drawPoint(drawProc, clientdata, pixels, cols, rows, maxval, col, row);
+        if (row == y1)
+            break;
+        row += dy;
+        scol += dx;
+        col = scol / DDA_SCALE;
+    }
+}
+
+
+
+void
+ppmd_line(pixel **      const pixels, 
+          int           const cols, 
+          int           const rows, 
+          pixval        const maxval, 
+          int           const x0, 
+          int           const y0, 
+          int           const x1, 
+          int           const y1, 
+          ppmd_drawproc       drawProc,
+          const void *  const clientdata) {
+
+    int cx0, cy0, cx1, cy1;
+    bool noLine;  /* There's no line left after clipping */
+
+    if (lineclip) {
+        clipLine(x0, y0, x1, y1, cols, rows, &cx0, &cy0, &cx1, &cy1, &noLine);
+    } else {
+        cx0 = x0;
+        cy0 = y0;
+        cx1 = x1;
+        cy1 = y1;
+        noLine = FALSE;
+    }
+
+    if (noLine) {
+        /* Nothing to draw */
+    } else if (cx0 == cx1 && cy0 == cy1) {
+        /* This line is just a point.  Because there aren't two
+           distinct endpoints, we have a special case.
+        */
+        drawPoint(drawProc, clientdata, pixels, cols, rows, maxval, cx0, cy0);
+    } else {
+        /* Draw, using a simple DDA. */
+        if (abs(cx1 - cx0) > abs(cy1 - cy0))
+            drawShallowLine(drawProc, clientdata, pixels, cols, rows, maxval,
+                            cx0, cy0, cx1, cy1);
+        else
+            drawSteepLine(drawProc, clientdata, pixels, cols, rows, maxval,
+                          cx0, cy0, cx1, cy1);
+    }
+}
+
+
+
+#define SPLINE_THRESH 3
+void
+ppmd_spline3(pixel **      const pixels, 
+             int           const cols, 
+             int           const rows, 
+             pixval        const maxval, 
+             int           const x0, 
+             int           const y0, 
+             int           const x1, 
+             int           const y1, 
+             int           const x2, 
+             int           const y2, 
+             ppmd_drawproc       drawProc,
+             const void *  const clientdata) {
+
+    register int xa, ya, xb, yb, xc, yc, xp, yp;
+
+    xa = ( x0 + x1 ) / 2;
+    ya = ( y0 + y1 ) / 2;
+    xc = ( x1 + x2 ) / 2;
+    yc = ( y1 + y2 ) / 2;
+    xb = ( xa + xc ) / 2;
+    yb = ( ya + yc ) / 2;
+
+    xp = ( x0 + xb ) / 2;
+    yp = ( y0 + yb ) / 2;
+    if ( abs( xa - xp ) + abs( ya - yp ) > SPLINE_THRESH )
+        ppmd_spline3(
+            pixels, cols, rows, maxval, x0, y0, xa, ya, xb, yb, drawProc,
+            clientdata );
+    else
+        ppmd_line(
+            pixels, cols, rows, maxval, x0, y0, xb, yb, drawProc, clientdata);
+
+    xp = ( x2 + xb ) / 2;
+    yp = ( y2 + yb ) / 2;
+    if ( abs( xc - xp ) + abs( yc - yp ) > SPLINE_THRESH )
+        ppmd_spline3(
+            pixels, cols, rows, maxval, xb, yb, xc, yc, x2, y2, drawProc,
+            clientdata );
+    else
+        ppmd_line(
+            pixels, cols, rows, maxval, xb, yb, x2, y2,
+            drawProc, clientdata );
+}
+
+
+
+void
+ppmd_polyspline(pixel **      const pixels, 
+                int           const cols, 
+                int           const rows, 
+                pixval        const maxval, 
+                int           const x0, 
+                int           const y0, 
+                int           const nc, 
+                int *         const xc, 
+                int *         const yc, 
+                int           const x1, 
+                int           const y1, 
+                ppmd_drawproc       drawProc,
+                const void *  const clientdata) {
+
+    register int i, x, y, xn, yn;
+
+    x = x0;
+    y = y0;
+    for ( i = 0; i < nc - 1; ++i )
+    {
+        xn = ( xc[i] + xc[i + 1] ) / 2;
+        yn = ( yc[i] + yc[i + 1] ) / 2;
+        ppmd_spline3(
+            pixels, cols, rows, maxval, x, y, xc[i], yc[i], xn, yn, drawProc,
+            clientdata );
+        x = xn;
+        y = yn;
+    }
+    ppmd_spline3(
+        pixels, cols, rows, maxval, x, y, xc[nc - 1], yc[nc - 1], x1, y1,
+        drawProc, clientdata );
+}
+
+
+
+void
+ppmd_spline4(pixel **      const pixels, 
+             int           const cols, 
+             int           const rows, 
+             pixval        const maxval, 
+             int           const x0, 
+             int           const y0, 
+             int           const x1, 
+             int           const y1, 
+             int           const x2, 
+             int           const y2, 
+             int           const x3, 
+             int           const y3, 
+             ppmd_drawproc       drawproc,
+             const void *  const clientdata) {
+
+    pm_error("ppmd_spline4() has not been written yet!");
+
+}
+
+
+
+void
+ppmd_circle(pixel **      const pixels, 
+            int           const cols, 
+            int           const rows, 
+            pixval        const maxval, 
+            int           const cx, 
+            int           const cy, 
+            int           const radius, 
+            ppmd_drawproc       drawProc,
+            const void *  const clientdata) {
+
+    int x0, y0, x, y, prevx, prevy, nopointsyet;
+    long sx, sy, e;
+
+    x0 = x = radius;
+    y0 = y = 0;
+    sx = x * DDA_SCALE + DDA_SCALE / 2;
+    sy = y * DDA_SCALE + DDA_SCALE / 2;
+    e = DDA_SCALE / radius;
+    drawPoint(drawProc, clientdata,
+              pixels, cols, rows, maxval, x + cx, y + cy);
+    nopointsyet = 1;
+
+    do {
+        prevx = x;
+        prevy = y;
+        sx += e * sy / DDA_SCALE;
+        sy -= e * sx / DDA_SCALE;
+        x = sx / DDA_SCALE;
+        y = sy / DDA_SCALE;
+        if (x != prevx || y != prevy) {
+            nopointsyet = 0;
+            drawPoint(drawProc, clientdata,
+                      pixels, cols, rows, maxval, x + cx, y + cy);
+        }
+    }
+    while (nopointsyet || x != x0 || y != y0);
+}
+
+
+
+/* Arbitrary fill stuff. */
+
+typedef struct
+{
+    short x;
+    short y;
+    short edge;
+} coord;
+
+typedef struct fillobj {
+    int n;
+    int size;
+    int curedge;
+    int segstart;
+    int ydir;
+    int startydir;
+    coord * coords;
+} fillobj;
+
+#define SOME 1000
+
+static int oldclip;
+
+struct fillobj *
+ppmd_fill_create(void) {
+
+    fillobj * fillObjP;
+
+    MALLOCVAR(fillObjP);
+    if (fillObjP == NULL)
+        pm_error("out of memory allocating a fillhandle");
+    fillObjP->n = 0;
+    fillObjP->size = SOME;
+    MALLOCARRAY(fillObjP->coords, fillObjP->size);
+    if (fillObjP->coords == NULL)
+        pm_error("out of memory allocating a fillhandle");
+    fillObjP->curedge = 0;
+    
+    /* Turn off line clipping. */
+    /* UGGH! We must eliminate this global variable */
+    oldclip = ppmd_setlineclip(0);
+    
+    return fillObjP;
+}
+
+
+
+void
+ppmd_fill_destroy(struct fillobj * fillObjP) {
+
+    free(fillObjP->coords);
+    free(fillObjP);
+}
+
+
+void
+ppmd_fill_drawproc(pixel**      const pixels, 
+                   int          const cols, 
+                   int          const rows, 
+                   pixval       const maxval, 
+                   int          const x, 
+                   int          const y, 
+                   const void * const clientdata) {
+
+    fillobj * fh;
+    coord * cp;
+    coord * ocp;
+
+    fh = (fillobj*) clientdata;
+
+    if (fh->n > 0) {
+        /* If these are the same coords we saved last time, don't bother. */
+        ocp = &(fh->coords[fh->n - 1]);
+        if ( x == ocp->x && y == ocp->y )
+            return;
+    }
+
+    /* Ok, these are new; check if there's room for two more coords. */
+    if (fh->n + 1 >= fh->size) {
+        fh->size += SOME;
+        REALLOCARRAY(fh->coords, fh->size);
+        if (fh->coords == NULL)
+            pm_error( "out of memory enlarging a fillhandle" );
+    }
+
+    /* Check for extremum and set the edge number. */
+    if (fh->n == 0) {
+        /* Start first segment. */
+        fh->segstart = fh->n;
+        fh->ydir = 0;
+        fh->startydir = 0;
+    } else {
+        int dx, dy;
+
+        dx = x - ocp->x;
+        dy = y - ocp->y;
+        if (dx < -1 || dx > 1 || dy < -1 || dy > 1) {
+            /* Segment break.  Close off old one. */
+            if (fh->startydir != 0 && fh->ydir != 0)
+                if (fh->startydir == fh->ydir) {
+                    /* Oops, first edge and last edge are the same.
+                       Renumber the first edge in the old segment.
+                    */
+                    coord * fcp;
+                    int oldedge;
+
+                    fcp = &(fh->coords[fh->segstart]);
+                    oldedge = fcp->edge;
+                    for ( ; fcp->edge == oldedge; ++fcp )
+                        fcp->edge = ocp->edge;
+                }
+            /* And start new segment. */
+            ++fh->curedge;
+            fh->segstart = fh->n;
+            fh->ydir = 0;
+            fh->startydir = 0;
+        } else {
+            /* Segment continues. */
+            if (dy != 0) {
+                if (fh->ydir != 0 && fh->ydir != dy) {
+                    /* Direction changed.  Insert a fake coord, old
+                       position but new edge number.
+                    */
+                    ++fh->curedge;
+                    cp = &fh->coords[fh->n];
+                    cp->x = ocp->x;
+                    cp->y = ocp->y;
+                    cp->edge = fh->curedge;
+                    ++fh->n;
+                }
+                fh->ydir = dy;
+                if (fh->startydir == 0)
+                    fh->startydir = dy;
+            }
+        }
+    }
+
+    /* Save this coord. */
+    cp = &fh->coords[fh->n];
+    cp->x = x;
+    cp->y = y;
+    cp->edge = fh->curedge;
+    ++fh->n;
+}
+
+
+
+
+static qsort_compare yx_compare;
+
+static int
+yx_compare(const void * const c1Arg,
+           const void * const c2Arg) {
+
+    const coord * const c1P = c1Arg;
+    const coord * const c2P = c2Arg;
+
+    int retval;
+    
+    if (c1P->y > c2P->y)
+        retval = 1;
+    else if (c1P->y < c2P->y)
+        retval = -1;
+    else if (c1P->x > c2P->x)
+        retval = 1;
+    else if (c1P->x < c2P->x)
+        retval = -1;
+    else
+        retval = 0;
+
+    return retval;
+}
+
+
+
+void
+ppmd_fill(pixel **         const pixels, 
+          int              const cols, 
+          int              const rows, 
+          pixval           const maxval, 
+          struct fillobj * const fh,
+          ppmd_drawproc          drawProc,
+          const void *     const clientdata) {
+
+    int pedge;
+    int i, edge, lx, rx, py;
+    coord * cp;
+    bool eq;
+    bool leftside;
+
+    /* Close off final segment. */
+    if (fh->n > 0 && fh->startydir != 0 && fh->ydir != 0) {
+        if (fh->startydir == fh->ydir) {
+            /* Oops, first edge and last edge are the same. */
+            coord * fcp;
+            int lastedge, oldedge;
+
+            lastedge = fh->coords[fh->n - 1].edge;
+            fcp = &(fh->coords[fh->segstart]);
+            oldedge = fcp->edge;
+            for ( ; fcp->edge == oldedge; ++fcp )
+                fcp->edge = lastedge;
+        }
+    }
+    /* Restore clipping now. */
+    ppmd_setlineclip(oldclip);
+
+    /* Sort the coords by Y, secondarily by X. */
+    qsort((char*) fh->coords, fh->n, sizeof(coord), yx_compare);
+
+    /* Find equal coords with different edge numbers, and swap if necessary. */
+    edge = -1;
+    for (i = 0; i < fh->n; ++i) {
+        cp = &fh->coords[i];
+        if (i > 1 && eq && cp->edge != edge && cp->edge == pedge) {
+            /* Swap .-1 and .-2. */
+            coord t;
+
+            t = fh->coords[i-1];
+            fh->coords[i-1] = fh->coords[i-2];
+            fh->coords[i-2] = t;
+        }
+        if (i > 0) {
+            if (cp->x == lx && cp->y == py) {
+                eq = TRUE;
+                if (cp->edge != edge && cp->edge == pedge) {
+                    /* Swap . and .-1. */
+                    coord t;
+
+                    t = *cp;
+                    *cp = fh->coords[i-1];
+                    fh->coords[i-1] = t;
+                }
+            } else
+                eq = FALSE;
+        }
+        lx    = cp->x;
+        py    = cp->y;
+        pedge = edge;
+        edge  = cp->edge;
+    }
+
+    /* Ok, now run through the coords filling spans. */
+    for (i = 0; i < fh->n; ++i) {
+        cp = &fh->coords[i];
+        if (i == 0) {
+            lx       = rx = cp->x;
+            py       = cp->y;
+            edge     = cp->edge;
+            leftside = TRUE;
+        } else {
+            if (cp->y != py) {
+                /* Row changed.  Emit old span and start a new one. */
+                ppmd_filledrectangle(
+                    pixels, cols, rows, maxval, lx, py, rx - lx + 1, 1,
+                    drawProc, clientdata);
+                lx       = rx = cp->x;
+                py       = cp->y;
+                edge     = cp->edge;
+                leftside = TRUE;
+            } else {
+                if (cp->edge == edge) {
+                    /* Continuation of side. */
+                    rx = cp->x;
+                } else {
+                    /* Edge changed.  Is it a span? */
+                    if (leftside) {
+                        rx       = cp->x;
+                        leftside = FALSE;
+                    } else {
+                        /* Got a span to fill. */
+                        ppmd_filledrectangle(
+                            pixels, cols, rows, maxval, lx, py, rx - lx + 1,
+                            1, drawProc, clientdata);
+                        lx       = rx = cp->x;
+                        leftside = TRUE;
+                    }
+                    edge = cp->edge;
+                }
+            }
+        }
+    }
+}
+
+
+
+/* Table used to look up sine of angles from 0 through 90 degrees.
+   The value returned is the sine * 65536.  Symmetry is used to
+   obtain sine and cosine for arbitrary angles using this table. */
+
+static long sintab[] = {
+    0, 1143, 2287, 3429, 4571, 5711, 6850, 7986, 9120, 10252, 11380,
+    12504, 13625, 14742, 15854, 16961, 18064, 19160, 20251, 21336,
+    22414, 23486, 24550, 25606, 26655, 27696, 28729, 29752, 30767,
+    31772, 32768, 33753, 34728, 35693, 36647, 37589, 38521, 39440,
+    40347, 41243, 42125, 42995, 43852, 44695, 45525, 46340, 47142,
+    47929, 48702, 49460, 50203, 50931, 51643, 52339, 53019, 53683,
+    54331, 54963, 55577, 56175, 56755, 57319, 57864, 58393, 58903,
+    59395, 59870, 60326, 60763, 61183, 61583, 61965, 62328, 62672,
+    62997, 63302, 63589, 63856, 64103, 64331, 64540, 64729, 64898,
+    65047, 65176, 65286, 65376, 65446, 65496, 65526, 65536
+};
+
+static int extleft, exttop, extright, extbottom;  /* To accumulate extents */
+
+/* LINTLIBRARY */
+
+/*  ISIN  --  Return sine of an angle in integral degrees.  The
+          value returned is 65536 times the sine.  */
+
+#if __STDC__
+static long isin(int deg)
+#else
+    static long isin(deg)
+    int deg;
+#endif
+{
+    /* Domain reduce to 0 to 360 degrees. */
+
+    if (deg < 0) {
+        deg = (360 - ((- deg) % 360)) % 360;
+    } else if (deg >= 360) {
+        deg = deg % 360;
+    }
+
+    /* Now look up from table according to quadrant. */
+
+    if (deg <= 90) {
+        return sintab[deg];
+    } else if (deg <= 180) {
+        return sintab[180 - deg];
+    } else if (deg <= 270) {
+        return -sintab[deg - 180];
+    }
+    return -sintab[360 - deg];
+}
+
+/*  ICOS  --  Return cosine of an angle in integral degrees.  The
+          value returned is 65536 times the cosine.  */
+
+#if __STDC__
+static long icos(int deg)
+#else
+    static long icos(deg)
+    int deg;
+#endif
+{
+    return isin(deg + 90);
+}  
+
+#define SCHAR(x) (u = (x), (((u) & 0x80) ? ((u) | (-1 ^ 0xFF)) : (u)))
+
+#define Scalef 21       /* Font design size */
+#define Descend 9       /* Descender offset */
+
+
+
+static void
+drawGlyph(const struct ppmd_glyph * const glyphP,
+          int *                     const xP,
+          int                       const y,
+          pixel **                  const pixels,
+          unsigned int              const cols,
+          unsigned int              const rows,
+          pixval                    const maxval,
+          int                       const height,
+          int                       const xpos,
+          int                       const ypos,
+          long                      const rotcos,
+          long                      const rotsin,
+          ppmd_drawproc                   drawProc,
+          const void *              const clientdata
+          ) {
+/*----------------------------------------------------------------------------
+   *xP is the column number of the left side of the glyph in the
+   output upon entry, and we update it to the left side of the next
+   glyph.
+
+   'y' is the row number of either the top or the bottom of the glyph
+   (I can't tell which right now) in the output.
+-----------------------------------------------------------------------------*/
+    struct penpos penPos;
+    unsigned int commandNum;
+    int x;
+    int u;  /* Used by the SCHAR macro */
+
+    x = *xP;  /* initial value */
+
+    x -= SCHAR(glyphP->header.skipBefore);
+
+    penPos.x = x;
+    penPos.y = y;
+
+    for (commandNum = 0;
+         commandNum < glyphP->header.commandCount;
+         ++commandNum) {
+
+        const struct ppmd_glyphCommand * const commandP =
+            &glyphP->commandList[commandNum];
+
+        switch (commandP->verb) {
+        case CMD_NOOP:
+            break;
+        case CMD_DRAWLINE:
+        {
+            int const nx = x + SCHAR(commandP->x);
+            int const ny = y + SCHAR(commandP->y);
+
+            int mx1, my1, mx2, my2;
+            int tx1, ty1, tx2, ty2;
+
+            /* Note that up until this  moment  we've  been
+               working  in  an  arbitrary model co-ordinate
+               system with  fixed  size  and  no  rotation.
+               Before  drawing  the  stroke,  transform  to
+               viewing co-ordinates to  honour  the  height
+               and angle specifications.
+            */
+
+            mx1 = (penPos.x * height) / Scalef;
+            my1 = ((penPos.y - Descend) * height) / Scalef;
+            mx2 = (nx * height) / Scalef;
+            my2 = ((ny - Descend) * height) / Scalef;
+            tx1 = xpos + (mx1 * rotcos - my1 * rotsin) / 65536;
+            ty1 = ypos + (mx1 * rotsin + my1 * rotcos) / 65536;
+            tx2 = xpos + (mx2 * rotcos - my2 * rotsin) / 65536;
+            ty2 = ypos + (mx2 * rotsin + my2 * rotcos) / 65536;
+            
+            ppmd_line(pixels, cols, rows, maxval, tx1, ty1, tx2, ty2,
+                      drawProc, clientdata);
+
+            penPos.x = nx;
+            penPos.y = ny;
+        }
+            break;
+        case CMD_MOVEPEN:
+            penPos.x = x + SCHAR(commandP->x);
+            penPos.y = y + SCHAR(commandP->y);
+            break;
+        }
+    }
+    x += glyphP->header.skipAfter; 
+
+    *xP = x;
+}
+
+
+/* PPMD_TEXT  --  Draw the zero-terminated  string  s,  with  its  baseline
+          starting  at  point  (x, y), inclined by angle degrees to
+          the X axis, with letters height pixels  high  (descenders
+          will  extend below the baseline).  The supplied drawproc
+          and cliendata are passed to ppmd_line which performs  the
+          actual drawing. */
+
+void
+ppmd_text(pixel**       const pixels, 
+          int           const cols, 
+          int           const rows, 
+          pixval        const maxval, 
+          int           const xpos, 
+          int           const ypos, 
+          int           const height, 
+          int           const angle, 
+          const char *  const sArg, 
+          ppmd_drawproc       drawProc,
+          const void *  const clientdata) {
+
+    const struct ppmd_font * const fontP = ppmd_get_font();
+    long rotsin, rotcos;
+    int x, y;
+    const char * s;
+
+    x = y = 0;
+    rotsin = isin(-angle);
+    rotcos = icos(-angle);
+
+    s = sArg;
+    while (*s) {
+        unsigned char const ch = *s++;
+
+        if (ch >= fontP->header.firstCodePoint &&
+            ch < fontP->header.firstCodePoint + fontP->header.characterCount) {
+
+            const struct ppmd_glyph * const glyphP =
+                &fontP->glyphTable[ch - fontP->header.firstCodePoint];
+
+            drawGlyph(glyphP, &x, y, pixels, cols, rows, maxval,
+                      height, xpos, ypos, rotcos, rotsin,
+                      drawProc, clientdata);
+        } else if (ch == '\n') {
+            /* Move to the left edge of the next line down */
+            y += Scalef + Descend;
+            x = 0;
+        }
+    }
+}
+
+/* EXTENTS_DRAWPROC  --  Drawproc which just accumulates the extents
+             rectangle bounding the text. */
+
+static void 
+extents_drawproc (pixel**      const pixels, 
+                  int          const cols, 
+                  int          const rows,
+                  pixval       const maxval, 
+                  int          const x, 
+                  int          const y, 
+                  const void * const clientdata)
+{
+    extleft = MIN(extleft, x);
+    exttop = MIN(exttop, y);
+    extright = MAX(extright, x);
+    extbottom = MAX(extbottom, y);
+}
+
+
+/* PPMD_TEXT_BOX  --  Calculate  extents  rectangle for a given piece of
+   text.  For most  applications  where  extents  are
+   needed,   angle  should  be  zero  to  obtain  the
+   unrotated extents.  If you need  the  extents  box
+   for post-rotation text, however, you can set angle
+   nonzero and it will be calculated correctly.
+*/
+
+void
+ppmd_text_box(int const height, 
+              int const angle, 
+              const char * const s, 
+              int * const left, 
+              int * const top, 
+              int * const right, 
+              int * const bottom)
+{
+    extleft = 32767;
+    exttop = 32767;
+    extright = -32767;
+    extbottom = -32767;
+    ppmd_text(NULL, 32767, 32767, 255, 1000, 1000, height, angle, s, 
+              extents_drawproc, NULL);
+    *left = extleft - 1000; 
+    *top = exttop - 1000;
+    *right = extright - 1000;
+    *bottom = extbottom - 1000;
+}
diff --git a/lib/libppmfloyd.c b/lib/libppmfloyd.c
new file mode 100644
index 00000000..071c3c36
--- /dev/null
+++ b/lib/libppmfloyd.c
@@ -0,0 +1,270 @@
+/* 
+These functions were taken from Ingo Wilken's ilbm package by Bryan
+Henderson on 01.03.10.  Because ppmtoilbm and ilbmtoppm are the only
+programs that will use these in the foreseeable future, they remain
+lightly documented and tested. 
+
+But they look like they would be useful in other Netpbm programs that
+do Floyd-Steinberg.
+*/
+
+
+
+/* libfloyd.c - generic Floyd-Steinberg error distribution routines for PBMPlus
+**
+** Copyright (C) 1994 Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "ppm.h"
+#include "ppmfloyd.h"
+#include "mallocvar.h"
+
+
+
+static void
+fs_adjust(ppm_fs_info * const fi, 
+          int           const col) {
+
+    int     const errcol = col+1;
+    pixel * const pP     = &(fi->pixrow[col]);
+    pixval  const maxval = fi->maxval;
+
+    long r, g, b;
+
+    /* Use Floyd-Steinberg errors to adjust actual color. */
+    r = fi->thisrederr  [errcol]; if( r < 0 ) r -= 8; else r += 8; r /= 16;
+    g = fi->thisgreenerr[errcol]; if( g < 0 ) g -= 8; else g += 8; g /= 16;
+    b = fi->thisblueerr [errcol]; if( b < 0 ) b -= 8; else b += 8; b /= 16;
+
+    r += PPM_GETR(*pP); if ( r < 0 ) r = 0; else if ( r > maxval ) r = maxval;
+    g += PPM_GETG(*pP); if ( g < 0 ) g = 0; else if ( g > maxval ) g = maxval;
+    b += PPM_GETB(*pP); if ( b < 0 ) b = 0; else if ( b > maxval ) b = maxval;
+
+    PPM_ASSIGN(*pP, r, g, b);
+    fi->red = r; fi->green = g; fi->blue = b;
+}
+
+
+
+static ppm_fs_info *
+allocateFi(int const cols) {
+
+    ppm_fs_info * fi;
+
+    MALLOCVAR(fi);
+    
+    if (fi != NULL) {
+        MALLOCARRAY(fi->thisrederr  , cols + 2);
+        MALLOCARRAY(fi->thisgreenerr, cols + 2);
+        MALLOCARRAY(fi->thisblueerr , cols + 2);
+        MALLOCARRAY(fi->nextrederr  , cols + 2);
+        MALLOCARRAY(fi->nextgreenerr, cols + 2);
+        MALLOCARRAY(fi->nextblueerr , cols + 2);
+        
+        if (fi->thisrederr   == NULL || 
+            fi->thisgreenerr == NULL || 
+            fi->thisblueerr  == NULL ||
+            fi->nextrederr   == NULL || 
+            fi->nextgreenerr == NULL || 
+            fi->nextblueerr  == NULL)
+            pm_error("out of memory allocating "
+                     "Floyd-Steinberg control structure");
+    } else
+        pm_error("out of memory allocating Floyd-Steinberg control structure");
+
+    return(fi);
+}
+
+
+
+ppm_fs_info *
+ppm_fs_init(int cols, pixval maxval, int flags) {
+
+    ppm_fs_info *fi;
+    
+    fi = allocateFi(cols);
+
+    fi->lefttoright = 1;
+    fi->cols = cols;
+    fi->maxval = maxval;
+    fi->flags = flags;
+    
+    if( flags & FS_RANDOMINIT ) {
+        unsigned int i;
+        srand((int)(time(0) ^ getpid()));
+        for( i = 0; i < cols +2; i++ ) {
+            /* random errors in [-1..+1] */
+            fi->thisrederr[i]   = rand() % 32 - 16;
+            fi->thisgreenerr[i] = rand() % 32 - 16;
+            fi->thisblueerr[i]  = rand() % 32 - 16;
+        }
+    }
+    else {
+        unsigned int i;
+
+        for( i = 0; i < cols + 2; i++ )
+            fi->thisrederr[i] = fi->thisgreenerr[i] = 
+                fi->thisblueerr[i] = 0;
+    }
+    return fi;
+}
+
+
+
+void
+ppm_fs_free(fi)
+    ppm_fs_info *fi;
+{
+    if( fi ) {
+        free(fi->thisrederr); free(fi->thisgreenerr); free(fi->thisblueerr);
+        free(fi->nextrederr); free(fi->nextgreenerr); free(fi->nextblueerr);
+        free(fi);
+    }
+}
+
+
+int
+ppm_fs_startrow(fi, pixrow)
+    ppm_fs_info *fi;
+    pixel *pixrow;
+{
+    register int col;
+
+    if( !fi )
+        return 0;
+
+    fi->pixrow = pixrow;
+
+    for( col = 0; col < fi->cols + 2; col++ )
+        fi->nextrederr[col] = fi->nextgreenerr[col] = fi->nextblueerr[col] = 0;
+
+    if( fi->lefttoright ) {
+        fi->col_end = fi->cols;
+        col = 0;
+    }
+    else {
+        fi->col_end = -1;
+        col = fi->cols - 1;
+    }
+    fs_adjust(fi, col);
+    return col;
+}
+
+
+int
+ppm_fs_next(fi, col)
+    ppm_fs_info *fi;
+    int col;
+{
+    if( !fi )
+        ++col;
+    else {
+        if( fi->lefttoright )
+            ++col;
+        else
+            --col;
+        if( col == fi->col_end )
+            col = fi->cols;
+        else
+            fs_adjust(fi, col);
+    }
+    return col;
+}
+
+
+void
+ppm_fs_endrow(fi)
+    ppm_fs_info *fi;
+{
+    long *tmp;
+
+    if( fi ) {
+        tmp = fi->thisrederr;   fi->thisrederr   = fi->nextrederr;   fi->nextrederr   = tmp;
+        tmp = fi->thisgreenerr; fi->thisgreenerr = fi->nextgreenerr; fi->nextgreenerr = tmp;
+        tmp = fi->thisblueerr;  fi->thisblueerr  = fi->nextblueerr;  fi->nextblueerr  = tmp;
+        if( fi->flags & FS_ALTERNATE )
+            fi->lefttoright = !(fi->lefttoright);
+    }
+}
+
+
+void
+ppm_fs_update(fi, col, pP)
+    ppm_fs_info *fi;
+    int col;
+    pixel *pP;
+{
+    if( fi )
+        ppm_fs_update3(fi, col, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
+}
+
+
+void
+ppm_fs_update3(ppm_fs_info * const fi, 
+               int           const col, 
+               pixval        const r, 
+               pixval        const g, 
+               pixval        const b) {
+
+    int const errcol = col + 1;
+    long err;
+
+    if (fi) {
+        long const rerr = (long)(fi->red)   - (long)r;
+        long const gerr = (long)(fi->green) - (long)g;
+        long const berr = (long)(fi->blue)  - (long)b;
+    
+        if ( fi->lefttoright ) {
+            long two_err;
+
+            two_err = 2*rerr;
+            err = rerr;     fi->nextrederr[errcol+1] += err;    /* 1/16 */
+            err += two_err; fi->nextrederr[errcol-1] += err;    /* 3/16 */
+            err += two_err; fi->nextrederr[errcol  ] += err;    /* 5/16 */
+            err += two_err; fi->thisrederr[errcol+1] += err;    /* 7/16 */
+
+            two_err = 2*gerr;
+            err = gerr;     fi->nextgreenerr[errcol+1] += err;    /* 1/16 */
+            err += two_err; fi->nextgreenerr[errcol-1] += err;    /* 3/16 */
+            err += two_err; fi->nextgreenerr[errcol  ] += err;    /* 5/16 */
+            err += two_err; fi->thisgreenerr[errcol+1] += err;    /* 7/16 */
+
+            two_err = 2*berr;
+            err = berr;     fi->nextblueerr[errcol+1] += err;    /* 1/16 */
+            err += two_err; fi->nextblueerr[errcol-1] += err;    /* 3/16 */
+            err += two_err; fi->nextblueerr[errcol  ] += err;    /* 5/16 */
+            err += two_err; fi->thisblueerr[errcol+1] += err;    /* 7/16 */
+        }
+        else {
+            long two_err;
+
+            two_err = 2*rerr;
+            err = rerr;     fi->nextrederr[errcol-1] += err;    /* 1/16 */
+            err += two_err; fi->nextrederr[errcol+1] += err;    /* 3/16 */
+            err += two_err; fi->nextrederr[errcol  ] += err;    /* 5/16 */
+            err += two_err; fi->thisrederr[errcol-1] += err;    /* 7/16 */
+
+            two_err = 2*gerr;
+            err = gerr;     fi->nextgreenerr[errcol-1] += err;    /* 1/16 */
+            err += two_err; fi->nextgreenerr[errcol+1] += err;    /* 3/16 */
+            err += two_err; fi->nextgreenerr[errcol  ] += err;    /* 5/16 */
+            err += two_err; fi->thisgreenerr[errcol-1] += err;    /* 7/16 */
+
+            two_err = 2*berr;
+            err = berr;     fi->nextblueerr[errcol-1] += err;    /* 1/16 */
+            err += two_err; fi->nextblueerr[errcol+1] += err;    /* 3/16 */
+            err += two_err; fi->nextblueerr[errcol  ] += err;    /* 5/16 */
+            err += two_err; fi->thisblueerr[errcol-1] += err;    /* 7/16 */
+        }
+    }
+}
+
+
+
diff --git a/lib/libppmfuzzy.c b/lib/libppmfuzzy.c
new file mode 100644
index 00000000..6127d5d5
--- /dev/null
+++ b/lib/libppmfuzzy.c
@@ -0,0 +1,434 @@
+/*=============================================================================
+  This file contains fuzzy color matching code.
+
+  It is all based on algorithms and methods by Kenan Kalajdzic
+  <kenan@unix.ba> in 2006.
+=============================================================================*/
+
+#include "pm_c_util.h"
+#include "nstring.h"
+#include "ppm.h"
+
+typedef double fzLog;
+    /* fuzzy logic truth value */
+
+/*----------------------------------------------------------------------------
+   Member functions
+-----------------------------------------------------------------------------*/
+
+static fzLog
+memberS(fzLog const x1,
+        fzLog const x2,
+        fzLog const x) {
+
+    fzLog retval;
+
+    if (x <= x1)
+        retval = 0;
+    else if (x > x1 && x <= x2)
+        retval = (x - x1) / (x2 - x1);
+    else
+        retval = 1;
+
+    return retval;
+}
+
+
+
+static fzLog
+memberZ(fzLog const x1,
+        fzLog const x2,
+        fzLog const x) {
+
+    fzLog retval;
+
+    if (x <= x1)
+        retval = 1;
+    else if (x > x1 && x <= x2)
+        retval = (x2 - x) / (x2 - x1);
+    else
+        retval = 0;
+
+    return retval;
+}
+
+
+
+static fzLog
+memberTrapez(fzLog const x1,
+             fzLog const x2,
+             fzLog const x3,
+             fzLog const x4,
+             fzLog const x) {
+
+    fzLog retval;
+    
+    if (x <= x1 || x > x4)
+        retval = 0;
+    else if (x > x1 && x <= x2)
+        retval = (x - x1) / (x2 - x1);
+    else if (x > x2 && x <= x3)
+        retval = 1;
+    else
+        retval = (x4 - x) / (x4 - x3);
+
+    return retval;
+}
+
+
+
+/*----------------------------------------------------------------------------
+   Hue membership functions
+-----------------------------------------------------------------------------*/
+
+static fzLog
+hueIsAround000(double const hue) {
+
+    return memberZ(10, 30, hue);
+}
+
+
+
+static fzLog
+hueIsAround015(double const hue) {
+
+    return memberZ(30, 40, hue);
+}
+
+
+
+static fzLog
+hueIsAround030(double const hue) {
+
+    return memberTrapez(10, 30, 40, 60, hue);
+}
+
+
+
+static fzLog
+hueIsAround060(double const hue) {
+
+    return memberTrapez(40, 60, 60, 80, hue);
+}
+
+
+
+static fzLog
+hueIsAround120(double const hue) {
+
+    return memberTrapez(60, 80, 150, 180, hue);
+}
+
+
+
+static fzLog
+hueIsAround180(double const hue) {
+
+    return memberTrapez(150, 180, 240, 260, hue);
+}
+
+
+
+static fzLog
+hueIsAround270(double const hue) {
+
+    return memberTrapez(240, 260, 290, 310, hue);
+}
+
+
+
+static fzLog
+hueIsAround320(double const hue) {
+
+    return memberTrapez(290, 310, 320, 350, hue);
+}
+
+
+
+static fzLog
+hueIsAround360(double const hue) {
+
+    return memberS(320, 350, hue);
+}
+
+
+
+/*----------------------------------------------------------------------------
+   Saturation membership functions
+-----------------------------------------------------------------------------*/
+
+static fzLog
+satIsVeryLow(double const sat) {
+
+    return memberZ(0.02, 0.1, sat);
+}
+
+
+
+static fzLog
+satIsLow(double const sat) {
+    return memberTrapez(0.02, 0.1, 0.2, 0.3, sat);
+}
+
+
+
+static fzLog
+satIsMedium(double const sat) {
+
+    return memberTrapez(0.2, 0.3, 0.6, 0.7, sat);
+}
+
+
+
+static fzLog
+satIsHigh(double const sat) {
+
+    return memberS(0.6, 0.7, sat);
+}
+
+
+
+/*----------------------------------------------------------------------------
+   Value membership functions
+-----------------------------------------------------------------------------*/
+
+static fzLog
+valIsVeryLow(double const val) {
+
+    return memberZ(0.1, 0.2, val);
+}
+
+
+
+static fzLog
+valIsLow(double const val) {
+
+    return memberTrapez(0.1, 0.2, 0.3, 0.6, val);
+}
+
+
+
+static fzLog
+valIsMedium(double const val) {
+
+    return memberTrapez(0.3, 0.6, 0.7, 0.8, val);
+}
+
+
+
+static fzLog
+valIsHigh(double const val) {
+
+    return memberS(0.7, 0.8, val);
+}
+
+
+
+/*----------------------------------------------------------------------------
+   Fuzzy logic functions
+-----------------------------------------------------------------------------*/
+
+static fzLog
+fzAnd(fzLog const opLeft,
+      fzLog const opRight) {
+
+    return MIN(opLeft, opRight);
+}
+
+
+
+static fzLog
+fzOr(fzLog const opLeft,
+     fzLog const opRight) {
+
+    return MAX(opLeft, opRight);
+}
+
+
+
+static fzLog
+fzNot(fzLog const op) {
+    return 1.0 - op;
+}
+
+
+
+/*----------------------------------------------------------------------------
+   Fuzzy color matching
+-----------------------------------------------------------------------------*/
+
+static void
+matchBk(pixel     const color,
+        pixval    const maxval,
+        fzLog (* const bkMatchP)[BKCOLOR_COUNT]) {
+
+    struct hsv const hsv = ppm_hsv_from_color(color, maxval);
+
+    fzLog const satVeryLow = satIsVeryLow(hsv.s);
+    fzLog const satLow     = satIsLow(hsv.s);
+    fzLog const satMedium  = satIsMedium(hsv.s);
+    fzLog const satHigh    = satIsHigh(hsv.s);
+
+    fzLog const valVeryLow = valIsVeryLow(hsv.v);
+    fzLog const valLow     = valIsLow(hsv.v);
+    fzLog const valMedium  = valIsMedium(hsv.v);
+    fzLog const valHigh    = valIsHigh(hsv.v);
+
+    fzLog const hueAround000 = hueIsAround000(hsv.h);
+    fzLog const hueAround015 = hueIsAround015(hsv.h);
+    fzLog const hueAround030 = hueIsAround030(hsv.h);
+    fzLog const hueAround060 = hueIsAround060(hsv.h);
+    fzLog const hueAround120 = hueIsAround120(hsv.h);
+    fzLog const hueAround180 = hueIsAround180(hsv.h);
+    fzLog const hueAround270 = hueIsAround270(hsv.h);
+    fzLog const hueAround320 = hueIsAround320(hsv.h);
+    fzLog const hueAround360 = hueIsAround360(hsv.h);
+
+    (*bkMatchP)[BKCOLOR_BLACK]  =
+        fzAnd(fzOr(satVeryLow, satLow), valVeryLow);
+
+    (*bkMatchP)[BKCOLOR_GRAY]   =
+        fzAnd(fzOr(satVeryLow, satLow), fzOr(valLow, valMedium));
+
+    (*bkMatchP)[BKCOLOR_WHITE]  =
+        fzAnd(fzOr(satVeryLow, satLow), valHigh);
+    
+    (*bkMatchP)[BKCOLOR_RED]    =
+        fzAnd(fzAnd(fzOr(hueAround000, hueAround360), fzNot(satVeryLow)),
+              fzOr(valMedium, valHigh)
+             );
+
+    (*bkMatchP)[BKCOLOR_ORANGE] =
+        fzAnd(fzAnd(hueAround030, fzOr(satMedium, satHigh)),
+              fzOr(valMedium, valHigh)
+             );
+
+    (*bkMatchP)[BKCOLOR_YELLOW] =
+        fzAnd(fzAnd(hueAround060, fzOr(satMedium, satHigh)),
+              fzOr(valMedium, valHigh)
+             );
+
+    (*bkMatchP)[BKCOLOR_GREEN]  =
+        fzAnd(fzAnd(hueAround120, fzNot(satVeryLow)),
+              fzOr(valMedium, valHigh)
+             );
+
+    (*bkMatchP)[BKCOLOR_BLUE]   =
+        fzAnd(fzAnd(hueAround180, fzAnd(fzNot(satVeryLow), fzNot(satLow))),
+              fzOr(valMedium, valHigh)
+             );
+
+    (*bkMatchP)[BKCOLOR_VIOLET] =
+        fzAnd(fzAnd(hueAround270, fzNot(satVeryLow)),
+              fzOr(valMedium, valHigh)
+             );
+
+    (*bkMatchP)[BKCOLOR_PURPLE] =
+        fzAnd(fzAnd(hueAround320, fzNot(satVeryLow)),
+              fzOr(valMedium, valHigh)
+             );
+
+    (*bkMatchP)[BKCOLOR_BROWN]  =
+        fzAnd(fzOr(hueAround015, hueAround360),
+              fzAnd(fzNot(satVeryLow), fzNot(valHigh))
+             );
+}
+
+
+
+bk_color
+ppm_bk_color_from_color(pixel  const color,
+                        pixval const maxval) {
+
+    fzLog bkmatch[BKCOLOR_COUNT];
+    bk_color i;
+    bk_color bestSoFar;
+    fzLog bestMatch;
+
+    matchBk(color, maxval, &bkmatch);
+
+    for (i = 0, bestSoFar = 0, bestMatch = 0.0; i < BKCOLOR_COUNT; ++i) {
+        if (bkmatch[i] > bestMatch) {
+            bestSoFar = i;
+            bestMatch = bkmatch[i];
+        }
+    }
+    return bestSoFar;
+}
+
+
+
+static pixel const bkColorMap[BKCOLOR_COUNT] = {
+    {  0,   0,   0}, /* BKCOLOR_BLACK  */
+    {174, 174, 174}, /* BKCOLOR_GRAY   */
+    {255, 255, 255}, /* BKCOLOR_WHITE  */
+    {255,   0,   0}, /* BKCOLOR_RED    */
+    {255, 128,   0}, /* BKCOLOR_ORANGE */
+    {255, 255,   0}, /* BKCOLOR_YELLOW */
+    {  0, 255,   0}, /* BKCOLOR_GREEN  */
+    {  0,   0, 255}, /* BKCOLOR_BLUE   */
+    {143,  94, 153}, /* BKCOLOR_VIOLET */
+    {160,  32, 240}, /* BKCOLOR_PURPLE */
+    {128,  42,  42}  /* BKCOLOR_BROWN  */
+};
+
+
+
+pixel
+ppm_color_from_bk_color(bk_color const bkColor,
+                        pixval   const maxval) {
+
+    pixel const color255 = bkColorMap[bkColor];
+
+    pixel retval;
+
+    if (maxval != 255) {
+        PPM_DEPTH(retval, color255, 255, maxval);
+    } else
+        retval = color255;
+
+    return retval;
+}
+
+
+
+static const char * const bkColorNameMap[BKCOLOR_COUNT] = {
+    "black",
+    "gray",
+    "white",
+    "red",
+    "orange",
+    "yellow",
+    "green",
+    "blue",
+    "violet",
+    "purple",
+    "brown"
+};
+
+
+
+bk_color
+ppm_bk_color_from_name(const char * const name) {
+
+    bk_color i;
+
+    for (i = 0; i < BKCOLOR_COUNT; ++i) {
+        if (STREQ(name, bkColorNameMap[i]))
+            return i;
+    }
+    pm_error("Invalid Berlin-Kay color name: '%s'", name);
+    return 0;  /* quiet compiler warning */
+}
+
+
+
+const char *
+ppm_name_from_bk_color(bk_color const bkColor) {
+
+    if (bkColor >= BKCOLOR_COUNT)
+        pm_error("Invalid color passed to name_from_bk_color(): %u",
+                 bkColor);
+
+    return bkColorNameMap[bkColor];
+}
diff --git a/lib/libsystem.c b/lib/libsystem.c
new file mode 100644
index 00000000..e0d62178
--- /dev/null
+++ b/lib/libsystem.c
@@ -0,0 +1,319 @@
+/*=============================================================================
+                                 pm_system
+===============================================================================
+   This is the library subroutine pm_system().  It is just like Standard C
+   Library system(), except that you can supply routines for it to run to
+   generate the Standard Input for the executed shell command and to accept
+   the Standard Output from it.  system(), by contrast, always sets up the
+   current Standard Input and Standard Output as the Standard Input and
+   Standard Output of the shell command.
+
+   By Bryan Henderson, San Jose CA  2002.12.14.
+
+   Contributed to the public domain.
+=============================================================================*/
+#define _XOPEN_SOURCE
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/wait.h>
+
+#include "pm.h"
+#include "pm_system.h"
+
+#define STDIN 0
+#define STDOUT 1
+
+
+static void
+execProgram(const char * const shellCommand,
+            int          const inputPipeFd,
+            int          const outputPipeFd) {
+/*----------------------------------------------------------------------------
+   Run the shell command 'shellCommand', supplying to the shell
+   'inputPipeFd' as its Standard Input and 'outputPipeFd' as its 
+   Standard Output.
+
+   But leave Standard Input and Standard Output as we found them.
+-----------------------------------------------------------------------------*/
+    int stdinSaveFd, stdoutSaveFd;
+    int rc;
+
+    /* Make inputPipeFd Standard Input.
+       Make outputPipeFd Standard Output.
+    */
+    stdinSaveFd = dup(STDIN);
+    stdoutSaveFd = dup(STDOUT);
+    
+    close(STDIN);
+    close(STDOUT);
+
+    dup2(inputPipeFd, STDIN);
+    dup2(outputPipeFd, STDOUT);
+
+    rc = execl("/bin/sh", "sh", "-c", shellCommand, NULL);
+
+    close(STDIN);
+    close(STDOUT);
+    dup2(stdinSaveFd, STDIN);
+    dup2(stdoutSaveFd, STDOUT);
+    close(stdinSaveFd);
+    close(stdoutSaveFd);
+
+    if (rc < 0)
+        pm_error("Unable to exec the shell.  Errno=%d (%s)",
+                 errno, strerror(errno));
+    else
+        pm_error("INTERNAL ERROR.  execl() returns, but does not fail.");
+}
+
+
+
+static void
+createPipeFeeder(void          pipeFeederRtn(int, void *), 
+                 void *  const feederParm, 
+                 int *   const fdP,
+                 pid_t * const pidP) {
+/*----------------------------------------------------------------------------
+   Create a process and a pipe.  Have the process run program
+   'pipeFeederRtn' to fill the pipe and return the file descriptor of the
+   other end of the pipe as *fdP.
+-----------------------------------------------------------------------------*/
+    int pipeToFeed[2];
+    pid_t feederPid;
+
+    pipe(pipeToFeed);
+    feederPid = fork();
+    if (feederPid < 0) {
+        pm_error("fork() of stdin feeder failed.  errno=%d (%s)", 
+                 errno, strerror(errno));
+    } else if (feederPid == 0) {
+        /* This is the child -- the stdin feeder process */
+        close(pipeToFeed[0]);
+        (*pipeFeederRtn)(pipeToFeed[1], feederParm);
+        exit(0);
+    }
+    else {
+        /* This is the parent */
+        close(pipeToFeed[1]);
+        *fdP = pipeToFeed[0];
+        *pidP = feederPid;
+    }
+}
+
+
+
+static void
+spawnProcessor(const char * const shellCommand, 
+               int          const stdinFd,
+               int *        const stdoutFdP,
+               pid_t *      const pidP) {
+/*----------------------------------------------------------------------------
+   Create a process to run a shell that runs command 'shellCommand'.
+   Pass file descriptor 'stdinFd' to the shell as Standard Input.
+   Set up a pipe and pass it to the shell as Standard Output.  Return
+   as *stdoutFdP the file descriptor of the other end of that pipe,
+   from which Caller can suck the shell's Standard Output.
+-----------------------------------------------------------------------------*/
+    int stdoutpipe[2];
+    pid_t processorpid;
+        
+    pipe(stdoutpipe);
+
+    processorpid = fork();
+    if (processorpid < 0) {
+        pm_error("fork() of processor process failed.  errno=%d (%s)\n", 
+                 errno, strerror(errno));
+    } else if (processorpid == 0) {
+        /* The second child */
+        close(stdoutpipe[0]);
+
+        execProgram(shellCommand, stdinFd, stdoutpipe[1]);
+
+        close(stdinFd);
+        close(stdoutpipe[1]);
+        pm_error("INTERNAL ERROR: execProgram() returns.");
+    } else {
+        /* The parent */
+        close(stdoutpipe[1]);
+        *stdoutFdP = stdoutpipe[0];
+        *pidP = processorpid;
+    }
+}
+
+
+static void
+cleanupProcessorProcess(pid_t const processorPid) {
+
+    int status;
+    waitpid(processorPid, &status, 0);
+    if (status != 0) 
+        pm_message("Shell process ended abnormally.  "
+                   "completion code = %d", status);
+}
+
+
+
+static void
+cleanupFeederProcess(pid_t const feederPid) {
+    int status;
+
+    waitpid(feederPid, &status, 0);
+
+    if (WIFSIGNALED(status)) {
+        if (WTERMSIG(status) == SIGPIPE)
+            pm_message("WARNING: "
+                       "Standard Input feeder process was terminated by a "
+                       "SIGPIPE signal because the shell command closed its "
+                       "Standard Input before the Standard Input feeder was "
+                       "through feeding it.");
+        else
+            pm_message("WARNING: "
+                       "Standard Input feeder was terminated by a Signal %d.",
+                       WTERMSIG(status));
+    }
+    else if (WIFEXITED(status)) {
+        if (WEXITSTATUS(status) != 0)
+            pm_message("WARNING: "
+                       "Standard Input feeder process ended abnormally.  "
+                       "exit status = %d", WEXITSTATUS(status));
+    } else
+        pm_message("WARNING: "
+                   "Unrecognized process completion status from "
+                   "Standard Input feeder: %d", status);
+}
+
+
+
+void
+pm_system(void stdinFeeder(int, void *),
+          void *          const feederParm,
+          void stdoutAccepter(int, void *),
+          void *          const accepterParm,
+          const char *    const shellCommand) {
+/*----------------------------------------------------------------------------
+   Run a shell and have it run command 'shellCommand'.  Feed its
+   Standard Input with a pipe, which is fed by the routine
+   'stdinFeeder' with parameter 'feederParm'.  Process its Standard
+   Output with the routine 'stdoutAccepter' with parameter 'accepterParm'.
+
+   But if 'stdinFeeder' is NULL, just feed the shell our own Standard
+   Input.  And if 'stdoutFeeder' is NULL, just send its Standard Output
+   to our own Standard Output.
+-----------------------------------------------------------------------------*/
+
+    /* If 'stdinFeeder' is non-NULL, we create a child process to run
+       'stdinFeeder' and create a pipe between from that process as the
+       shell's Standard Input.
+
+       If 'stdoutFeeder' is non-NULL, we create a child process to run
+       the shell and create a pipe between the shell's Standard Output
+       and this process, and then this process runs 'stdoutAccepter'
+       to read the data from that pipe.
+       
+       But if 'stdoutFeeder' is NULL, we just run the shell in this
+       process.
+
+       So there can be 1, 2, or 3 processes involved depending on 
+       parameters.
+    */
+    
+    int shellStdinFd;
+    pid_t feederPid;
+
+    if (stdinFeeder) 
+        createPipeFeeder(stdinFeeder, feederParm, &shellStdinFd, &feederPid);
+    else {
+        shellStdinFd = STDIN;
+        feederPid = 0;
+    }
+
+    if (stdoutAccepter) {
+        int shellStdoutFd;
+        pid_t processorPid;
+
+        /* Make a child process to run the shell and pipe back to us its
+           Standard Output 
+        */
+        spawnProcessor(shellCommand, shellStdinFd, 
+                       &shellStdoutFd, &processorPid);
+
+        /* Dispose of the stdout from that shell */
+        (*stdoutAccepter)(shellStdoutFd, accepterParm);
+        close(shellStdoutFd);
+
+        cleanupProcessorProcess(processorPid);
+    } else {
+        /* Run a child process for the shell that sends its Standard Output
+           to our Standard Output
+        */
+        int const stdinSaveFd = dup(STDIN);
+        int rc;
+
+        dup2(shellStdinFd, STDIN);
+        
+        rc = system(shellCommand);
+
+        close(STDIN);
+        dup2(stdinSaveFd, STDIN);
+        
+        if (rc < 0)
+            pm_error("Unable to invoke the shell.  Errno=%d (%s)",
+                     errno, strerror(errno));
+        else if (rc != 0)
+            pm_message("WARNING: Shell process completion code = %d", rc);
+    }
+
+    if (feederPid) 
+        cleanupFeederProcess(feederPid);
+}
+
+
+
+
+void
+pm_feed_from_memory(int    const pipeToFeedFd,
+                    void * const feederParm) {
+
+    struct bufferDesc * const inputBufferP = feederParm;
+    
+    FILE * const outfile = fdopen(pipeToFeedFd, "w");
+    
+    int bytesTransferred;
+
+    /* The following signals (and normally kills) the process with
+       SIGPIPE if the pipe does not take all 'size' bytes.
+    */
+    bytesTransferred = 
+        fwrite(inputBufferP->buffer, 1, inputBufferP->size, outfile);
+
+    if (inputBufferP->bytesTransferredP)
+        *(inputBufferP->bytesTransferredP) = bytesTransferred;
+
+    fclose(outfile);
+}
+
+
+
+void
+pm_accept_to_memory(int             const pipetosuckFd,
+                    void *          const accepterParm ) {
+
+    struct bufferDesc * const outputBufferP = accepterParm;
+    
+    FILE * const infile = fdopen(pipetosuckFd, "r");
+
+    int bytesTransferred;
+
+    bytesTransferred =
+        fread(outputBufferP->buffer, 1, outputBufferP->size, infile);
+
+    fclose(infile);
+
+    if (outputBufferP->bytesTransferredP)
+        *(outputBufferP->bytesTransferredP) = bytesTransferred;
+}
diff --git a/lib/libsystem_dummy.c b/lib/libsystem_dummy.c
new file mode 100644
index 00000000..afe20dde
--- /dev/null
+++ b/lib/libsystem_dummy.c
@@ -0,0 +1,47 @@
+/*=============================================================================
+                             libsystem_dummy.c
+===============================================================================
+  This is a dummy version of libsystem.c, for use on systems that don't
+  have the kind of process control that libsystem.c needs.
+
+  With this module, program will build cleanly, but if a program actually
+  calls pm_system(), it will die with an error message saying that
+  the facility is not available.
+=============================================================================*/
+
+#include <assert.h>
+
+#include "pm.h"
+#include "pm_system.h"
+
+void
+pm_system(void                  stdinFeeder(int, void *),
+          void *          const feederParm,
+          void                  stdoutAccepter(int, void *),
+          void *          const accepterParm,
+          const char *    const shellCommand) {
+
+    pm_error("This program wants to run another program using pm_system() in "
+             "the libnetpbm library, but libnetpbm was built without "
+             "the pm_system() facility -- probably because this system "
+             "doesn't have the process management facilities pm_system() "
+             "requires.");
+}
+
+
+void
+pm_feed_from_memory(int    const pipeToFeedFd,
+                    void * const feederParm) {
+
+    assert(FALSE);  /* Can't ever run, since pm_system() is a dummy */
+}
+
+
+
+void
+pm_accept_to_memory(int    const pipetosuckFd,
+                    void * const accepterParm) {
+
+    assert(FALSE);  /* Can't ever run, since pm_system() is a dummy */
+}
+
diff --git a/lib/lum.h b/lib/lum.h
new file mode 100644
index 00000000..34d3866e
--- /dev/null
+++ b/lib/lum.h
@@ -0,0 +1,123 @@
+    /* Lookup tables for fast RGB -> luminance calculation. */
+    static int times77[256] = {
+	    0,    77,   154,   231,   308,   385,   462,   539,
+	  616,   693,   770,   847,   924,  1001,  1078,  1155,
+	 1232,  1309,  1386,  1463,  1540,  1617,  1694,  1771,
+	 1848,  1925,  2002,  2079,  2156,  2233,  2310,  2387,
+	 2464,  2541,  2618,  2695,  2772,  2849,  2926,  3003,
+	 3080,  3157,  3234,  3311,  3388,  3465,  3542,  3619,
+	 3696,  3773,  3850,  3927,  4004,  4081,  4158,  4235,
+	 4312,  4389,  4466,  4543,  4620,  4697,  4774,  4851,
+	 4928,  5005,  5082,  5159,  5236,  5313,  5390,  5467,
+	 5544,  5621,  5698,  5775,  5852,  5929,  6006,  6083,
+	 6160,  6237,  6314,  6391,  6468,  6545,  6622,  6699,
+	 6776,  6853,  6930,  7007,  7084,  7161,  7238,  7315,
+	 7392,  7469,  7546,  7623,  7700,  7777,  7854,  7931,
+	 8008,  8085,  8162,  8239,  8316,  8393,  8470,  8547,
+	 8624,  8701,  8778,  8855,  8932,  9009,  9086,  9163,
+	 9240,  9317,  9394,  9471,  9548,  9625,  9702,  9779,
+	 9856,  9933, 10010, 10087, 10164, 10241, 10318, 10395,
+	10472, 10549, 10626, 10703, 10780, 10857, 10934, 11011,
+	11088, 11165, 11242, 11319, 11396, 11473, 11550, 11627,
+	11704, 11781, 11858, 11935, 12012, 12089, 12166, 12243,
+	12320, 12397, 12474, 12551, 12628, 12705, 12782, 12859,
+	12936, 13013, 13090, 13167, 13244, 13321, 13398, 13475,
+	13552, 13629, 13706, 13783, 13860, 13937, 14014, 14091,
+	14168, 14245, 14322, 14399, 14476, 14553, 14630, 14707,
+	14784, 14861, 14938, 15015, 15092, 15169, 15246, 15323,
+	15400, 15477, 15554, 15631, 15708, 15785, 15862, 15939,
+	16016, 16093, 16170, 16247, 16324, 16401, 16478, 16555,
+	16632, 16709, 16786, 16863, 16940, 17017, 17094, 17171,
+	17248, 17325, 17402, 17479, 17556, 17633, 17710, 17787,
+	17864, 17941, 18018, 18095, 18172, 18249, 18326, 18403,
+	18480, 18557, 18634, 18711, 18788, 18865, 18942, 19019,
+	19096, 19173, 19250, 19327, 19404, 19481, 19558, 19635 };
+    static int times150[256] = {
+	    0,   150,   300,   450,   600,   750,   900,  1050,
+	 1200,  1350,  1500,  1650,  1800,  1950,  2100,  2250,
+	 2400,  2550,  2700,  2850,  3000,  3150,  3300,  3450,
+	 3600,  3750,  3900,  4050,  4200,  4350,  4500,  4650,
+	 4800,  4950,  5100,  5250,  5400,  5550,  5700,  5850,
+	 6000,  6150,  6300,  6450,  6600,  6750,  6900,  7050,
+	 7200,  7350,  7500,  7650,  7800,  7950,  8100,  8250,
+	 8400,  8550,  8700,  8850,  9000,  9150,  9300,  9450,
+	 9600,  9750,  9900, 10050, 10200, 10350, 10500, 10650,
+	10800, 10950, 11100, 11250, 11400, 11550, 11700, 11850,
+	12000, 12150, 12300, 12450, 12600, 12750, 12900, 13050,
+	13200, 13350, 13500, 13650, 13800, 13950, 14100, 14250,
+	14400, 14550, 14700, 14850, 15000, 15150, 15300, 15450,
+	15600, 15750, 15900, 16050, 16200, 16350, 16500, 16650,
+	16800, 16950, 17100, 17250, 17400, 17550, 17700, 17850,
+	18000, 18150, 18300, 18450, 18600, 18750, 18900, 19050,
+	19200, 19350, 19500, 19650, 19800, 19950, 20100, 20250,
+	20400, 20550, 20700, 20850, 21000, 21150, 21300, 21450,
+	21600, 21750, 21900, 22050, 22200, 22350, 22500, 22650,
+	22800, 22950, 23100, 23250, 23400, 23550, 23700, 23850,
+	24000, 24150, 24300, 24450, 24600, 24750, 24900, 25050,
+	25200, 25350, 25500, 25650, 25800, 25950, 26100, 26250,
+	26400, 26550, 26700, 26850, 27000, 27150, 27300, 27450,
+	27600, 27750, 27900, 28050, 28200, 28350, 28500, 28650,
+	28800, 28950, 29100, 29250, 29400, 29550, 29700, 29850,
+	30000, 30150, 30300, 30450, 30600, 30750, 30900, 31050,
+	31200, 31350, 31500, 31650, 31800, 31950, 32100, 32250,
+	32400, 32550, 32700, 32850, 33000, 33150, 33300, 33450,
+	33600, 33750, 33900, 34050, 34200, 34350, 34500, 34650,
+	34800, 34950, 35100, 35250, 35400, 35550, 35700, 35850,
+	36000, 36150, 36300, 36450, 36600, 36750, 36900, 37050,
+	37200, 37350, 37500, 37650, 37800, 37950, 38100, 38250 };
+    static int times29[256] = {
+	    0,    29,    58,    87,   116,   145,   174,   203,
+	  232,   261,   290,   319,   348,   377,   406,   435,
+	  464,   493,   522,   551,   580,   609,   638,   667,
+	  696,   725,   754,   783,   812,   841,   870,   899,
+	  928,   957,   986,  1015,  1044,  1073,  1102,  1131,
+	 1160,  1189,  1218,  1247,  1276,  1305,  1334,  1363,
+	 1392,  1421,  1450,  1479,  1508,  1537,  1566,  1595,
+	 1624,  1653,  1682,  1711,  1740,  1769,  1798,  1827,
+	 1856,  1885,  1914,  1943,  1972,  2001,  2030,  2059,
+	 2088,  2117,  2146,  2175,  2204,  2233,  2262,  2291,
+	 2320,  2349,  2378,  2407,  2436,  2465,  2494,  2523,
+	 2552,  2581,  2610,  2639,  2668,  2697,  2726,  2755,
+	 2784,  2813,  2842,  2871,  2900,  2929,  2958,  2987,
+	 3016,  3045,  3074,  3103,  3132,  3161,  3190,  3219,
+	 3248,  3277,  3306,  3335,  3364,  3393,  3422,  3451,
+	 3480,  3509,  3538,  3567,  3596,  3625,  3654,  3683,
+	 3712,  3741,  3770,  3799,  3828,  3857,  3886,  3915,
+	 3944,  3973,  4002,  4031,  4060,  4089,  4118,  4147,
+	 4176,  4205,  4234,  4263,  4292,  4321,  4350,  4379,
+	 4408,  4437,  4466,  4495,  4524,  4553,  4582,  4611,
+	 4640,  4669,  4698,  4727,  4756,  4785,  4814,  4843,
+	 4872,  4901,  4930,  4959,  4988,  5017,  5046,  5075,
+	 5104,  5133,  5162,  5191,  5220,  5249,  5278,  5307,
+	 5336,  5365,  5394,  5423,  5452,  5481,  5510,  5539,
+	 5568,  5597,  5626,  5655,  5684,  5713,  5742,  5771,
+	 5800,  5829,  5858,  5887,  5916,  5945,  5974,  6003,
+	 6032,  6061,  6090,  6119,  6148,  6177,  6206,  6235,
+	 6264,  6293,  6322,  6351,  6380,  6409,  6438,  6467,
+	 6496,  6525,  6554,  6583,  6612,  6641,  6670,  6699,
+	 6728,  6757,  6786,  6815,  6844,  6873,  6902,  6931,
+	 6960,  6989,  7018,  7047,  7076,  7105,  7134,  7163,
+	 7192,  7221,  7250,  7279,  7308,  7337,  7366,  7395 };
+
+/* The ppm_fastlumin() macro is a way to compute luminosity without
+   floating point arithmetic.  On modern computers, floating point often isn't
+   any slower, so you may prefer ppm_lumin() in ppm.h.
+
+   ppm_fastlumin() works only with maxval <= 255, because the multiplication
+   tables go up only that high.
+
+   In the following arithmetic, note that shifting right 8 bits is the same
+   as dividing by (77+150+29), but some compilers may generate faster code
+   for >>8 than for /(77+150+29).
+*/
+static __inline__ pixval
+ppm_fastlumin(pixel const p) {
+
+    return
+        (times77[PPM_GETR(p)] +
+         times150[PPM_GETG(p)] +
+         times29[PPM_GETB(p)] +
+         128)  /* round off */
+             >> 8;
+}
+
diff --git a/lib/mkstemp.c b/lib/mkstemp.c
new file mode 100644
index 00000000..cd9140c7
--- /dev/null
+++ b/lib/mkstemp.c
@@ -0,0 +1,8 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#include "pm_c_util.h"
+
+
+
diff --git a/lib/pam.h b/lib/pam.h
new file mode 100644
index 00000000..97d5b3cb
--- /dev/null
+++ b/lib/pam.h
@@ -0,0 +1,490 @@
+/*----------------------------------------------------------------------------
+   These are declarations for use with the Portable Arbitrary Map (PAM)
+   format and the Netpbm library functions specific to them.
+-----------------------------------------------------------------------------*/
+
+#ifndef PAM_H
+#define PAM_H
+
+#include "pm.h"
+#include "pnm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+typedef unsigned long sample;
+    /* Regardless of the capacity of "unsigned long", a sample is always
+       less than 1 << 16.  This is essential for some code to avoid
+       arithmetic overflows.
+    */
+
+struct pam {
+/* This structure describes an open PAM image file.  It consists
+   entirely of information that belongs in the header of a PAM image
+   and filesystem information.  It does not contain any state
+   information about the processing of that image.  
+
+   This is not considered to be an opaque object.  The user of Netbpm
+   libraries is free to access and set any of these fields whenever
+   appropriate.  The structure exists to make coding of function calls
+   easy.
+*/
+
+    /* 'size' and 'len' are necessary in order to provide forward and
+       backward compatibility between library functions and calling programs
+       as this structure grows.
+       */
+    unsigned int size;   
+        /* The storage size of this entire structure, in bytes */
+    unsigned int len;    
+        /* The length, in bytes, of the information in this structure.
+           The information starts in the first byte and is contiguous.  
+           This cannot be greater than 'size'
+           */
+    FILE * file;
+    int format;
+        /* The format code of the raw image.  This is PAM_FORMAT
+           unless the PAM image is really a view of a PBM, PGM, or PPM
+           image.  Then it's PBM_FORMAT, RPBM_FORMAT, etc.
+           */
+    unsigned int plainformat;
+        /* Logical: On output, use the plain version of the format type
+           indicated by 'format'.  Otherwise, use the raw version.
+           (i.e., on output, the plainness information in 'format' is
+           irrelevant).  Input functions set this to FALSE, for the
+           convenience of programs that copy an input pam structure for
+           use with output.
+
+           Before Netpbm 10.32, this was rather different.  It simply
+           described for convenience the plainness of the format indicated
+           by 'format'.
+        */
+    int height;  /* Height of image in rows */
+    int width;   
+        /* Width of image in number of columns (tuples per row) */
+    unsigned int depth;   
+        /* Depth of image (number of samples in each tuple). */
+    sample maxval;  /* Maximum defined value for a sample */
+    unsigned int bytes_per_sample;  
+        /* Number of bytes used to represent each sample in the image file.
+           Note that this is strictly a function of 'maxval'.  It is in a
+           a separate member for computational speed.
+        */
+    char tuple_type[256];
+        /* The tuple type string from the image header.  If the PAM image
+           is really a view of a PBM, PGM, or PPM image, the value is
+           PAM_PBM_TUPLETYPE, PAM_PGM_TUPLETYPE, or PAM_PPM_TUPLETYPE,
+           respectively.
+        */
+    unsigned int allocation_depth;
+        /* The number of samples for which memory is allocated for any
+           'tuple' type associated with this PAM structure.  This must
+           be at least as great as 'depth'.  Only the first 'depth' of
+           the samples of a tuple are meaningful.
+
+           The purpose of this is to make it possible for a program to
+           change the type of a tuple to one with more or fewer
+           planes.  
+
+           0 means the allocation depth is the same as the image depth.
+        */
+    const char ** comment_p;
+        /* Pointer to a pointer to a NUL-terminated ASCII string of
+           comments.  When reading an image, this contains the
+           comments from the image's PAM header; when writing, the
+           image gets these as comments, right after the magic number
+           line.  The individual comments are delimited by newlines
+           and are in the same order as in the PAM header.
+
+           On output, NULL means no comments.
+
+           On input, libnetpbm mallocs storage for the comments and placed
+           the pointer at *comment_p.  Caller must free it.  NULL means
+           libnetpbm does not return comments and does not allocate any
+           storage.
+        */
+};
+
+#define PAM_HAVE_ALLOCATION_DEPTH 1
+#define PAM_HAVE_COMMENT_P 1
+
+/* PAM_STRUCT_SIZE(x) tells you how big a struct pam is up through the 
+   member named x.  This is useful in conjunction with the 'len' value
+   to determine which fields are present in the structure.
+*/
+#define PAM_MEMBER_OFFSET(mbrname) \
+  ((unsigned int)(char*)&((struct pam *)0)->mbrname)
+#define PAM_MEMBER_SIZE(mbrname) \
+  sizeof(((struct pam *)0)->mbrname)
+#define PAM_STRUCT_SIZE(mbrname) \
+  (PAM_MEMBER_OFFSET(mbrname) + PAM_MEMBER_SIZE(mbrname))
+
+#define PAM_BLACK 0
+#define PAM_BW_WHITE 1
+
+#define PAM_PBM_TUPLETYPE "BLACKANDWHITE"
+#define PAM_PGM_TUPLETYPE "GRAYSCALE"
+#define PAM_PPM_TUPLETYPE "RGB"
+
+#define PAM_PBM_BLACK PAM_BLACK
+#define PAM_PBM_WHITE PAM_BW_WHITE
+    /* These are values of samples in a PAM image that represents a black
+       and white bitmap image.  They are the values of black and white,
+       respectively.  For example, if you use pnm_readpamrow() to read a
+       row from a PBM file, the black pixels get returned as 
+       PAM_PBM_BLACK.
+    */
+
+#define PAM_RED_PLANE 0
+#define PAM_GRN_PLANE 1
+#define PAM_BLU_PLANE 2
+    /* These are plane numbers for the 3 planes of a PAM image that
+       represents an RGB image (tuple type is "RGB").  So
+       if 'pixel' is a tuple returned by pnmreadpamrow(), then
+       pixel[PAM_GRN_PLANE] is the value of the green sample in that
+       pixel.
+       */
+#define PAM_TRN_PLANE 3
+    /* A PAM with "RGB_ALPHA" tuple type has this 4th plane
+       for transparency.  0 = transparent, maxval = opaque.
+    */
+#define PAM_GRAY_TRN_PLANE 1
+    /* For a "GRAYSCALE" tuple type, this is the transparency plane */
+
+typedef sample *tuple;  
+    /* A tuple in a PAM.  This is an array such that tuple[i-1] is the
+       ith sample (element) in the tuple.  It's dimension is the depth
+       of the image (see pam.depth above).
+    */
+
+#define PAM_OVERALL_MAXVAL 65535
+
+/* Note: xv uses the same "P7" signature for its thumbnail images (it
+   started using it years before PAM and unbeknownst to the designer
+   of PAM).  But these images are still easily distinguishable from
+   PAMs 
+*/
+#define PAM_MAGIC1 'P'
+#define PAM_MAGIC2 '7'
+#define PAM_FORMAT (PAM_MAGIC1 * 256 + PAM_MAGIC2)
+#define PAM_TYPE PAM_FORMAT
+
+/* Macro for turning a format number into a type number. */
+
+#define PAM_FORMAT_TYPE(f) ((f) == PAM_FORMAT ? PAM_TYPE : PPM_FORMAT_TYPE(f))
+
+struct pamtuples {
+    struct pam * pamP;
+    tuple ***    tuplesP;
+};
+
+
+typedef float * pnm_transformMap;
+    /* This is an array of transform maps.  transform[N] is the 
+       array that is the map for Plane N.
+   
+       Transform maps define a transformation between PAM sample value
+       to normalized libnetpbm "samplen" value, i.e. what you get back
+       from pnm_readpamrown() or pass to pnm_writepamrown().
+       Typically, it's a gamma transfer function generated by
+       pnm_creategammatransform() or pnm_createungammatransform().
+
+       NULL for any transform means just plain normalization -- divide
+       the PAM sample value by the maxval to get the samplen, multiply
+       samplen by the maxval and round to get PAM sample value.
+
+       NULL for map table, or 'transform' member not present (pam
+       structure is too small to contain it) means ALL transforms
+       are plain normalization.
+
+       Each transform map is an array indexed by a PAM sample
+       value, containing 'float' values.  So it must have 'maxval'
+       entries.  The sample -> samplen tranformation is just the
+       obvious table lookup.  The samplen -> sample transformation is
+       more complicated -- if the samplen value is between map[N]
+       and map[N+1], then the sample value is N.  And only transforms
+       where map[N+1] > map[N] are allowed.  
+    */
+
+/* Declarations of library functions. */
+
+/* We don't have a specific PAM function for init and nextimage, because
+   one can simply use pnm_init() and pnm_nextimage() from pnm.h.
+*/
+
+unsigned int
+pnm_bytespersample(sample const maxval);
+
+int
+pnm_tupleequal(const struct pam * const pamP, 
+               tuple              const comparand, 
+               tuple              const comparator);
+
+void
+pnm_assigntuple(const struct pam * const pamP,
+                tuple              const dest,
+                tuple              const source);
+
+static __inline__ sample
+pnm_scalesample(sample const source, 
+                sample const oldmaxval, 
+                sample const newmaxval) {
+
+    if (oldmaxval == newmaxval)
+        /* Fast path for common case */
+        return source;
+    else 
+        return (source * newmaxval + (oldmaxval/2)) / oldmaxval;
+}
+
+
+
+void
+pnm_scaletuple(const struct pam * const pamP,
+               tuple              const dest,
+               tuple              const source, 
+               sample             const newmaxval);
+
+void 
+pnm_scaletuplerow(const struct pam * const pamP,
+                  tuple *            const destRow,
+                  tuple *            const sourceRow,
+                  sample             const newMaxval);
+
+void 
+pnm_maketuplergb(const struct pam * const pamP,
+                 tuple              const tuple);
+
+void 
+pnm_makerowrgb(const struct pam * const pamP,
+               tuple *            const tuplerow);
+
+void 
+pnm_makearrayrgb(const struct pam * const pamP,
+                 tuple **           const tuples);
+
+void
+pnm_getopacity(const struct pam * const pamP,
+               int *              const haveOpacityP,
+               unsigned int *     const opacityPlaneP);
+
+void
+pnm_createBlackTuple(const struct pam * const pamP, tuple * const blackTupleP);
+
+void
+createBlackTuple(const struct pam * const pamP, tuple * const blackTupleP);
+
+
+tuple
+pnm_allocpamtuple(const struct pam * const pamP);
+
+#define pnm_freepamtuple(tuple) pm_freerow((char*) tuple)
+
+tuple *
+pnm_allocpamrow(const struct pam * const pamP);
+
+#define pnm_freepamrow(tuplerow) pm_freerow((char*) tuplerow)
+
+tuple **
+pnm_allocpamarray(const struct pam * const pamP);
+
+void
+pnm_freepamarray(tuple ** const tuplearray, const struct pam * const pamP);
+
+void 
+pnm_setminallocationdepth(struct pam * const pamP,
+                          unsigned int const allocationDepth);
+
+void
+pnm_setpamrow(const struct pam * const pam, 
+              tuple *            const tuplerow, 
+              sample             const value);
+
+unsigned char *
+pnm_allocrowimage(const struct pam * const pamP);
+
+void
+pnm_freerowimage(unsigned char * const rowimage);
+
+void 
+pnm_readpaminit(FILE *       const file, 
+                struct pam * const pamP, 
+                int          const size);
+
+void 
+pnm_readpamrow(const struct pam * const pamP, tuple* const tuplerow);
+
+tuple ** 
+pnm_readpam(FILE *       const file, 
+            struct pam * const pamP, 
+            int          const size);
+
+void 
+pnm_writepaminit(struct pam * const pamP);
+
+void
+pnm_formatpamrow(const struct pam * const pamP,
+                 const tuple *      const tuplerow,
+                 unsigned char *    const outbuf,
+                 unsigned int *     const rowSizeP);
+
+void 
+pnm_writepamrow(const struct pam * const pamP, const tuple * const tuplerow);
+
+void
+pnm_writepamrowmult(const struct pam * const pamP, 
+                    const tuple *      const tuplerow,
+                    unsigned int       const rptcnt);
+
+void 
+pnm_writepam(struct pam * const pamP, tuple ** const tuplearray);
+
+void
+pnm_checkpam(const struct pam *   const pamP, 
+             enum pm_check_type   const checkType,
+             enum pm_check_code * const retvalP);
+
+/*----------------------------------------------------------------------------
+   Facilities for working with maxval-normalized samples.  Such samples
+   are floating point quantities in the range 0..1.
+
+   This is just a working format; there is no Netpbm image format that
+   has normalized samples.
+-----------------------------------------------------------------------------*/
+typedef float samplen;
+
+typedef samplen *tuplen;
+    /* Same as 'tuple', except using normalized samples. */
+
+tuplen *
+pnm_allocpamrown(const struct pam * const pamP);
+
+#define pnm_freepamrown(tuplenrow) pm_freerow((char*) tuplenrow)
+
+tuplen *
+pnm_allocpamrown(const struct pam * const pamP);
+
+void 
+pnm_readpamrown(const struct pam * const pamP, 
+                tuplen *           const tuplenrow);
+
+void 
+pnm_writepamrown(const struct pam * const pamP, 
+                 const tuplen *     const tuplenrow);
+
+tuplen **
+pnm_allocpamarrayn(const struct pam * const pamP);
+
+void
+pnm_freepamarrayn(tuplen **          const tuplenarray, 
+                  const struct pam * const pamP);
+
+tuplen** 
+pnm_readpamn(FILE *       const file, 
+             struct pam * const pamP, 
+             int          const size);
+
+void 
+pnm_writepamn(struct pam * const pamP, 
+              tuplen **    const tuplenarray);
+
+
+void
+pnm_normalizetuple(struct pam * const pamP,
+                   tuple        const tuple,
+                   tuplen       const tuplen);
+
+void
+pnm_unnormalizetuple(struct pam * const pamP,
+                     tuplen       const tuplen,
+                     tuple        const tuple);
+
+void
+pnm_normalizeRow(struct pam *             const pamP,
+                 const tuple *            const tuplerow,
+                 const pnm_transformMap * const transform,
+                 tuplen *                 const tuplenrow);
+
+void
+pnm_unnormalizeRow(struct pam *             const pamP,
+                   const tuplen *           const tuplenrow,
+                   const pnm_transformMap * const transform,
+                   tuple *                  const tuplerow);
+
+/*----------------------------------------------------------------------------
+   Facilities for working with visual images in particular
+-----------------------------------------------------------------------------*/
+
+
+void
+pnm_gammarown(struct pam * const pamP,
+              tuplen *     const row);
+
+void
+pnm_ungammarown(struct pam * const pamP,
+                tuplen *     const row);
+
+void
+pnm_applyopacityrown(struct pam * const pamP,
+                     tuplen *     const tuplenrow);
+
+void
+pnm_unapplyopacityrown(struct pam * const pamP,
+                       tuplen *     const tuplenrow);
+
+pnm_transformMap *
+pnm_creategammatransform(const struct pam * const pamP);
+
+void
+pnm_freegammatransform(const pnm_transformMap * const transform,
+                       const struct pam *       const pamP);
+
+pnm_transformMap *
+pnm_createungammatransform(const struct pam * const pamP);
+
+#define pnm_freeungammatransform pnm_freegammatransform;
+
+tuple
+pnm_parsecolor(const char * const colorname,
+               sample       const maxval);
+
+extern double 
+pnm_lumin_factor[3];
+
+void
+pnm_YCbCrtuple(const tuple tuple, 
+               double * const YP, double * const CbP, double * const CrP);
+
+void 
+pnm_YCbCr_to_rgbtuple(const struct pam * const pamP,
+                      tuple              const tuple,
+                      double             const Y,
+                      double             const Cb, 
+                      double             const Cr,
+                      int *              const overflowP);
+
+#define pnm_rgbtupleisgray(tuple) \
+    ((tuple)[PAM_RED_PLANE] == (tuple)[PAM_GRN_PLANE] && \
+     (tuple)[PAM_RED_PLANE] == (tuple)[PAM_BLU_PLANE])
+
+/*----------------------------------------------------------------------------
+   These are meant for passing to pm_system() as Standard Input feeder
+   and Standard Output accepter.
+-----------------------------------------------------------------------------*/
+
+void
+pm_feed_from_pamtuples(int    const pipeToFeedFd,
+                       void * const feederParm);
+
+void
+pm_accept_to_pamtuples(int    const pipeToSuckFd,
+                       void * const accepterParm);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/lib/pammap.h b/lib/pammap.h
new file mode 100644
index 00000000..fa054deb
--- /dev/null
+++ b/lib/pammap.h
@@ -0,0 +1,124 @@
+/******************************************************************************
+                                pammap.h
+*******************************************************************************
+
+  Interface header file for hash table and lookup table pam functions
+  in libpnm.
+
+******************************************************************************/
+
+#ifndef PAMMAP_H
+#define PAMMAP_H
+#include "colorname.h"
+#include "pam.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+struct tupleint {
+    /* An ordered pair of a tuple value and an integer, such as you 
+       would find in a tuple table or tuple hash.
+
+       Note that this is a variable length structure.
+    */
+    int value;
+    sample tuple[1];  
+        /* This is actually a variable size array -- its size is the 
+           depth of the tuple in question.  Some compilers do not let us
+           declare a variable length array.
+        */
+};
+typedef struct tupleint ** tupletable;
+
+typedef struct {
+    unsigned int size;
+    tupletable table;
+} tupletable2;
+
+struct tupleint_list_item {
+    struct tupleint_list_item * next;
+    struct tupleint tupleint;
+};
+typedef struct tupleint_list_item * tupleint_list;
+
+typedef tupleint_list * tuplehash;
+
+unsigned int
+pnm_hashtuple(struct pam * const pamP, tuple const tuple);
+
+void
+pnm_addtotuplehash(struct pam *   const pamP,
+                   tuplehash      const tuplehash, 
+                   tuple          const tuple,
+                   int            const value,
+                   int *          const fitsP);
+
+void
+pnm_addtuplefreqoccurrence(struct pam *   const pamP,
+                           tuple          const value, 
+                           tuplehash      const tuplefreqhash,
+                           int *          const firstOccurrenceP);
+
+void
+pnm_lookuptuple(struct pam * const pamP, const tuplehash tuplehash, 
+                const tuple searchval, 
+                int * const foundP, int * const retvalP);
+
+tupletable
+pnm_alloctupletable(const struct pam * const pamP, unsigned int const size);
+
+void
+pnm_freetupletable(struct pam * const pamP, tupletable const tupletable);
+
+void
+pnm_freetupletable2(struct pam * const pamP, tupletable2 const tupletable);
+
+tuplehash
+pnm_createtuplehash(void);
+
+void
+pnm_destroytuplehash(tuplehash const tuplehash);
+
+tupletable
+pnm_computetuplefreqtable(struct pam *   const pamP,
+                          tuple **       const tupleArray,
+                          unsigned int   const maxsize,
+                          unsigned int * const sizeP);
+
+tupletable
+pnm_computetuplefreqtable2(struct pam *   const pamP,
+                           tuple **       const tupleArray,
+                           unsigned int   const maxsize,
+                           sample         const newMaxval,
+                           unsigned int * const sizeP);
+
+tuplehash
+pnm_computetuplefreqhash(struct pam *   const pamP,
+                         tuple **       const tupleArray,
+                         unsigned int   const maxsize,
+                         unsigned int * const sizeP);
+
+tuplehash
+pnm_computetupletablehash(struct pam * const pamP, 
+                          tupletable   const tupletable,
+                          unsigned int const tupletableSize);
+
+tupletable
+pnm_tuplehashtotable(const struct pam * const pamP,
+                     tuplehash          const tuplehash,
+                     unsigned int       const maxsize);
+
+char*
+pam_colorname(struct pam *         const pamP, 
+              tuple                const color, 
+              enum colornameFormat const format);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/path.c b/lib/path.c
new file mode 100644
index 00000000..79985109
--- /dev/null
+++ b/lib/path.c
@@ -0,0 +1,468 @@
+/* This library module contains "ppmdraw" routines for drawing paths.
+
+   By Bryan Henderson San Jose CA 06.05.24.
+   Contributed to the public domain.
+
+   I actually wrote this before I knew ppmd_fill() already existed.
+   ppmd_fill() is more general.  But path.c is probably faster and is
+   better code, so I'm keeping it for now.
+*/
+
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "ppm.h"
+#include "ppmdfont.h"
+#include "ppmdraw.h"
+
+
+
+/* This is the algorithm we use to fill a path.
+
+   A path is a set of points that form a closed figure.  The last point
+   and first point are identical.  But the path may cross itself other
+   places too.  Our job is to color the interior of that figure.
+
+   We do it with horizontal lines.  We follow the path around from
+   start to finish, visiting every point in it.  We remember in a
+   stack the points we've been to.  As long as we keep going in the
+   same vertical direction, that stack grows.  When we turn around and
+   visit a row that we've been to before, we drop a horizontal line of
+   fill color from where we are now to where we were the last time we
+   visited that row, and then remove that entry from the stack.  Note
+   that because we go one point at a time, the entry on the stack for
+   the row we're at now will always be on the top of stack.
+   
+   Note that the points on the stack always have consecutive row
+   numbers, monotonically increasing or decreasing, whichever is the
+   direction we started out in.
+
+   This goes on, with the stack alternately growing and shrinking as
+   the path turns to head up and down, until we get back to the row
+   where we started.  At this point, the stack becomes empty following
+   the algorithm in the previous paragraph.  If the path crosses over
+   and keeps going, we just start filling the stack again, with the
+   entries now going in the opposite direction from before -- e.g.
+   if the path started out heading downward, the points in the stack
+   mononotically increased in row number; after crossing back over,
+   the points in the stack monotonically decrease in row number.
+
+
+   It's probably more efficient to use vertical lines than horizontal
+   ones when the image is tall and narrow.  But we're not that
+   sophisticated.
+*/
+
+
+/* NOTE NOTE NOTE
+
+   In all the path logic below, we call the direction of increasing row
+   number "up" because we think of the raster as standard Cartesian
+   plane.  So visualize the image as upside down in the first quadrant
+   of the Cartesian plane -- the real top left corner of the image is at
+   the origin.
+*/
+
+
+
+static bool
+pointEqual(ppmd_point const comparator,
+           ppmd_point const comparand) {
+
+    return comparator.x == comparand.x && comparator.y == comparand.y;
+}
+
+
+
+static int
+vertDisp(ppmd_point const begPoint,
+         ppmd_point const endPoint) {
+/*----------------------------------------------------------------------------
+   Return the vertical displacement of 'endPoint' with respect to
+   'begPoint' -- How much higher 'endPoint' is than 'begPoint'.
+-----------------------------------------------------------------------------*/
+    return endPoint.y - begPoint.y;
+}
+
+
+
+static int
+horzDisp(ppmd_point const begPoint,
+         ppmd_point const endPoint) {
+/*----------------------------------------------------------------------------
+   Return the horizontal displacement of 'endPoint' with respect to
+   'begPoint' -- How much further right 'endPoint' is than 'begPoint'.
+-----------------------------------------------------------------------------*/
+    return endPoint.x - begPoint.x;
+}
+
+
+
+static bool
+isOnLineSeg(ppmd_point const here,
+            ppmd_point const begPoint,
+            ppmd_point const endPoint) {
+
+    return
+        here.y >= MIN(begPoint.y, endPoint.y) &&
+        here.y <= MAX(begPoint.y, endPoint.y) &&
+        here.x >= MIN(begPoint.x, endPoint.x) &&
+        here.x <= MAX(begPoint.x, endPoint.x);
+}    
+
+
+
+static double
+lineSlope(ppmd_point const begPoint,
+          ppmd_point const endPoint) {
+/*----------------------------------------------------------------------------
+   Return the slope of the line that begins at 'begPoint' and ends at
+   'endPoint'.
+
+   Positive slope means as row number increases, column number increases.
+-----------------------------------------------------------------------------*/
+    return (double)vertDisp(begPoint, endPoint) / horzDisp(begPoint, endPoint);
+}
+
+
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   A complicated structure for tracking the state of a the filling
+   algorithm.  See description of algorithm above.
+-----------------------------------------------------------------------------*/
+    ppmd_point * stack;
+        /* An array which holds the fundamental stack.  The bottom of the
+           stack is element 0.  It grows consecutively up.
+        */
+    unsigned int topOfStack;
+        /* Index in stack[] of the top of the entry which will hold next
+           _next_ element pushed onto the stack.
+        */
+    unsigned int stackSize;
+        /* Number of elements in stack[] -- i.e. maximum height of stack */
+    int step;
+        /* -1 or 1.  -1 means each new point pushed on the stack is one
+           unit below the previous one; 1 means each new point pushed on
+           the stack is one unit above the previous one.
+        */
+} fillStack;
+
+
+
+static void
+createStack(fillStack ** const stackPP) {
+/*----------------------------------------------------------------------------
+   Create an empty fill stack.
+-----------------------------------------------------------------------------*/
+    fillStack * stackP;
+
+    MALLOCVAR_NOFAIL(stackP);
+
+    stackP->stackSize = 1024;
+
+    MALLOCARRAY(stackP->stack, stackP->stackSize);
+
+    if (stackP->stack == NULL)
+        pm_error("Could not allocate memory for a fill stack of %u points",
+                 stackP->stackSize);
+
+    stackP->topOfStack = 0;
+
+    stackP->step = +1;  /* arbitrary choice */
+
+    *stackPP = stackP;
+}
+
+
+
+static void
+destroyStack(fillStack * const stackP) {
+
+    free(stackP->stack);
+
+    free(stackP);
+}
+
+
+
+static ppmd_point
+topOfStack(fillStack * const stackP) {
+
+    assert(stackP->topOfStack > 0);
+
+    return stackP->stack[stackP->topOfStack-1];
+}
+
+
+
+static bool
+stackIsEmpty(fillStack * const stackP) {
+
+    return stackP->topOfStack == 0;
+}
+
+
+
+static bool
+inStackDirection(fillStack * const stackP,
+                 ppmd_point  const point) {
+/*----------------------------------------------------------------------------
+   Return true iff the point 'point' is is a step in the current stack
+   direction from the point on the top of the stack.  I.e. we could push
+   this point onto the stack without violating the monotonic property.
+-----------------------------------------------------------------------------*/
+    if (stackIsEmpty(stackP))
+        return true;
+    else
+        return topOfStack(stackP).y + stackP->step == point.y;
+}
+
+
+
+static bool
+againstStackDirection(fillStack * const stackP,
+                      ppmd_point  const point) {
+/*----------------------------------------------------------------------------
+   Return true iff the point 'point' is a step in the other direction
+   form the current stack direction.
+-----------------------------------------------------------------------------*/
+    if (stackIsEmpty(stackP))
+        return false;
+    else
+        return topOfStack(stackP).y - stackP->step == point.y;
+}
+
+
+
+static bool
+isLateralFromTopOfStack(fillStack * const stackP,
+                        ppmd_point  const point) {
+/*----------------------------------------------------------------------------
+   Return true iff the point 'point' is laterally across from the point
+   on the top of the stack.
+-----------------------------------------------------------------------------*/
+    if (stackIsEmpty(stackP))
+        return false;
+    else
+        return point.y == topOfStack(stackP).y;
+}
+
+
+
+static void
+pushStack(fillStack * const stackP,
+          ppmd_point  const newPoint) {
+
+    assert(inStackDirection(stackP, newPoint));
+
+    if (stackP->topOfStack >= stackP->stackSize) {
+        stackP->stackSize *= 2;
+
+        REALLOCARRAY(stackP->stack, stackP->stackSize);
+
+        if (stackP->stack == NULL)
+            pm_error("Could not allocate memory for a fill stack of %u points",
+                     stackP->stackSize);
+    }
+    assert(stackP->topOfStack < stackP->stackSize);
+
+    stackP->stack[stackP->topOfStack++] = newPoint;
+pm_message("pushed (%u, %u) at %u", newPoint.x, newPoint.y, stackP->topOfStack-1);
+}
+
+
+
+static ppmd_point
+popStack(fillStack * const stackP) {
+
+    ppmd_point retval;
+
+    assert(stackP->topOfStack < stackP->stackSize);
+
+    retval = stackP->stack[--stackP->topOfStack];
+pm_message("popped (%u, %u) at %u", retval.x, retval.y, stackP->topOfStack);
+    return retval;
+}
+
+
+
+static void
+replaceTopOfStack(fillStack * const stackP,
+                  ppmd_point  const point) {
+
+    assert(stackP->topOfStack > 0);
+
+    stackP->stack[stackP->topOfStack-1] = point;
+}
+
+
+
+static void
+reverseStackDirection(fillStack * const stackP) {
+
+    stackP->step *= -1;
+}
+
+
+
+static void
+drawFillLine(ppmd_point const begPoint,
+             ppmd_point const endPoint,
+             pixel **   const pixels,
+             pixel      const color) {
+
+    unsigned int leftCol, rghtCol;
+    unsigned int col;
+    unsigned int row;
+
+    /* Fill lines are always horizontal */
+
+    assert(begPoint.y == endPoint.y);
+
+pm_message("filling from (%u, %u) to (%u, %u)", begPoint.x, begPoint.y, endPoint.x, endPoint.y);
+    row = begPoint.y;
+
+    if (begPoint.x <= endPoint.x) {
+        leftCol = begPoint.x;
+        rghtCol = endPoint.x;
+    } else {
+        leftCol = endPoint.x;
+        rghtCol = begPoint.x;
+    }
+
+    for (col = leftCol; col <= rghtCol; ++col)
+        pixels[row][col] = color;
+}
+
+
+
+static void
+fillPoint(fillStack * const stackP,
+          ppmd_point  const point,
+          pixel **    const pixels,
+          pixel       const color) {
+/*----------------------------------------------------------------------------
+   Follow the outline of the figure to the point 'point', which is adjacent
+   to the point most recently added.
+
+   Fill the image in 'pixels' with color 'color' and update *stackP as
+   required.
+-----------------------------------------------------------------------------*/
+pm_message("filling point (%u, %u)", point.x, point.y);
+    if (inStackDirection(stackP, point)) {
+        pushStack(stackP, point);
+        pixels[point.y][point.x] = color;
+    } else {
+        if (againstStackDirection(stackP, point))
+            popStack(stackP);
+        
+        if (stackIsEmpty(stackP)) {
+            reverseStackDirection(stackP);
+            pushStack(stackP, point);
+        } else {
+            assert(isLateralFromTopOfStack(stackP, point));
+            
+            drawFillLine(topOfStack(stackP), point, pixels, color);
+            replaceTopOfStack(stackP, point);
+        }
+    }
+}
+
+
+
+static void
+fillLeg(ppmd_point  const begPoint,
+        ppmd_point  const endPoint,
+        fillStack * const stackP,
+        pixel **    const pixels,
+        pixel       const color) {
+/*----------------------------------------------------------------------------
+   Follow the leg which is a straight line segment from 'begPoint'
+   through 'endPoint', filling the raster 'pixels' with color 'color',
+   according to *stackP as we go.
+
+   We update *stackP accordingly.
+
+   A leg starts where the leg before it ends, so we skip the first point
+   in the line segment.
+-----------------------------------------------------------------------------*/
+    assert(!stackIsEmpty(stackP));
+
+    if (endPoint.y == begPoint.y)
+        /* Line is horizontal; We need just the end point. */
+        fillPoint(stackP, endPoint, pixels, color);
+    else {
+        double const invSlope = 1/lineSlope(begPoint, endPoint);
+        int const step = endPoint.y > begPoint.y ? +1 : -1;
+
+        ppmd_point here;
+
+        here = begPoint;
+    
+        while (here.y != endPoint.y) {
+            here.y += step;
+            here.x = ROUNDU(begPoint.x + vertDisp(begPoint, here) * invSlope);
+
+            assert(isOnLineSeg(here, begPoint, endPoint));
+
+            fillPoint(stackP, here, pixels, color);
+        }
+    }
+}
+
+
+
+void
+ppmd_fill_path(pixel **      const pixels, 
+               int           const cols, 
+               int           const rows, 
+               pixval        const maxval,
+               ppmd_path *   const pathP,
+               pixel         const color) {
+/*----------------------------------------------------------------------------
+   Draw a path which defines a closed figure (or multiple closed figures)
+   and fill it in.
+
+   *pathP describes the path.  'color' is the color with which to fill.
+
+   'pixels' is the canvas on which to draw the figure; its dimensions are
+   'cols' x 'rows'.
+
+   'maxval' is the maxval for 'color' and for 'pixels'.
+
+   Fail (abort the program) if the path does not end on the same point at
+   which it began.
+-----------------------------------------------------------------------------*/
+    ppmd_point prevVertex;
+    fillStack * stackP;
+    unsigned int legNumber;
+
+    createStack(&stackP);
+
+    prevVertex = pathP->begPoint;
+    pushStack(stackP, pathP->begPoint);
+
+    for (legNumber = 0; legNumber < pathP->legCount; ++legNumber) {
+        ppmd_pathleg * const legP = &pathP->legs[legNumber];
+        ppmd_point const nextVertex = legP->u.linelegparms.end;
+
+        if (prevVertex.y >= rows || nextVertex.y >= rows)
+            pm_error("Path extends below the image.");
+        if (prevVertex.x >= cols || nextVertex.x >= cols)
+            pm_error("Path extends off the image to the right.");
+
+        fillLeg(prevVertex, nextVertex, stackP, pixels, color);
+
+        prevVertex = nextVertex;
+    }
+    if (!pointEqual(prevVertex, pathP->begPoint))
+        pm_error("Failed to fill a path -- the path is not closed "
+                 "(i.e. it doesn't end up at the same point where it began)");
+
+    assert(pointEqual(popStack(stackP), pathP->begPoint));
+    assert(stackIsEmpty(stackP));
+
+    destroyStack(stackP);
+}
diff --git a/lib/pbm.h b/lib/pbm.h
new file mode 100644
index 00000000..1591c77f
--- /dev/null
+++ b/lib/pbm.h
@@ -0,0 +1,97 @@
+#ifndef PBM_H_INCLUDED
+#define PBM_H_INCLUDED
+
+#include "pm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+typedef unsigned char bit;
+#define PBM_WHITE 0
+#define PBM_BLACK 1
+
+
+/* Magic constants. */
+
+#define PBM_MAGIC1 'P'
+#define PBM_MAGIC2 '1'
+#define RPBM_MAGIC2 '4'
+#define PBM_FORMAT (PBM_MAGIC1 * 256 + PBM_MAGIC2)
+#define RPBM_FORMAT (PBM_MAGIC1 * 256 + RPBM_MAGIC2)
+#define PBM_TYPE PBM_FORMAT
+
+
+/* Macro for turning a format number into a type number. */
+
+#define PBM_FORMAT_TYPE(f) \
+  ((f) == PBM_FORMAT || (f) == RPBM_FORMAT ? PBM_TYPE : -1)
+
+
+/* Declarations of routines. */
+
+void pbm_init ARGS(( int* argcP, char* argv[] ));
+void
+pbm_nextimage(FILE *file, int * const eofP);
+
+#define pbm_allocarray(cols, rows) \
+  ((bit**) pm_allocarray(cols, rows, sizeof(bit)))
+#define pbm_allocrow(cols) ((bit*) pm_allocrow(cols, sizeof(bit)))
+#define pbm_freearray(bits, rows) pm_freearray((char**) bits, rows)
+#define pbm_freerow(bitrow) pm_freerow((char*) bitrow)
+#define pbm_packed_bytes(cols) (((cols)+7)/8)
+#define pbm_allocrow_packed(cols) \
+    ((unsigned char *) pm_allocrow(pbm_packed_bytes(cols), \
+                                   sizeof(unsigned char)))
+#define pbm_freerow_packed(packed_bits) \
+    pm_freerow((char *) packed_bits)
+#define pbm_allocarray_packed(cols, rows) ((unsigned char **) \
+    pm_allocarray(pbm_packed_bytes(cols), rows, sizeof(unsigned char)))
+#define pbm_freearray_packed(packed_bits, rows) \
+    pm_freearray((char **) packed_bits, rows)
+
+bit** pbm_readpbm(FILE* file, int* colsP, int* rowsP);
+void pbm_readpbminit(FILE* file, int* colsP, int* rowsP, int* formatP);
+void pbm_readpbmrow(FILE* file, bit* bitrow, int cols, int format);
+void pbm_readpbmrow_packed(
+    FILE* const file, unsigned char * const packed_bits, 
+    const int cols, const int format);
+
+void
+pbm_writepbminit(FILE * const fileP, 
+                 int    const cols, 
+                 int    const rows, 
+                 int    const forceplain);
+
+void
+pbm_writepbm(FILE * const fileP, 
+             bit ** const bits, 
+             int    const cols, 
+             int    const rows, 
+             int    const forceplain);
+
+void
+pbm_writepbmrow(FILE * const fileP, 
+                bit *  const bitrow, 
+                int    const cols, 
+                int    const forceplain);
+
+void
+pbm_writepbmrow_packed(FILE *                const fileP, 
+                       const unsigned char * const packed_bits,
+                       int                   const cols, 
+                       int                   const forceplain);
+
+void
+pbm_check(FILE * file, const enum pm_check_type check_type, 
+          const int format, const int cols, const int rows,
+          enum pm_check_code * const retval_p);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/pbmfont.h b/lib/pbmfont.h
new file mode 100644
index 00000000..432aea3c
--- /dev/null
+++ b/lib/pbmfont.h
@@ -0,0 +1,75 @@
+/* pbmfont.h - header file for font routines in libpbm
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+struct glyph {
+    /* A glyph consists of white borders and the "central glyph" which
+       can be anything, but normally does not have white borders because
+       it's more efficient to represent white borders explicitly.
+    */
+	int width, height;
+        /* The dimensions of the central glyph, i.e. the 'bmap' array */
+	int x;
+        /* Width in pixels of the white left border of this glyph.
+           This can actually be negative to indicate that the central
+           glyph backs up over the previous character in the line.  In
+           that case, if there is no previous character in the line, it
+           is as if 'x' is 0.
+        */
+    int y;
+        /* Height in pixels of the white bottom border of this glyph */
+	int xadd;
+        /* Width of glyph -- white left border plus central glyph
+           plus white right border
+        */
+	const char * bmap;
+        /* The raster of the central glyph.  It is an 'width' by
+           'height' array in row major order, with each byte being 1
+           for black; 0 for white.  E.g. if 'width' is 20 pixels and
+           'height' is 40 pixels and it's a rectangle that is black on
+           the top half and white on the bottom, this is an array of
+           800 bytes, with the first 400 having value 0x01 and the
+           last 400 having value 0x00.
+        */
+};
+
+struct font {
+    /* This describes a combination of font and character set.  Given
+       an code point in the range 0..255, this structure describes the
+       glyph for that character.
+    */
+	int maxwidth, maxheight;
+	int x;
+        /* ?? Not used by Pbmtext */
+    int y;
+        /* Amount of white space that should be added between lines of
+           this font.
+        */
+	struct glyph * glyph[256];
+        /* glyph[i] is the glyph for code point i */
+	bit** oldfont;
+	    /* for compatibility with old pbmtext routines */
+	    /* oldfont is 0 if the font is BDF derived */
+	int fcols, frows;
+};
+
+struct font* pbm_defaultfont(const char* const which);
+struct font*
+pbm_dissectfont(bit ** const font,
+                int    const frows,
+                int    const fcols);
+struct font* pbm_loadfont(const char * const filename);
+struct font* pbm_loadpbmfont(const char * const filename);
+struct font* pbm_loadbdffont(const char * const filename);
+void pbm_dumpfont ARGS(( struct font* fn ));
+int mk_argvn ARGS(( char* s, char* vec[], int max ));
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/lib/pgm.h b/lib/pgm.h
new file mode 100644
index 00000000..86935307
--- /dev/null
+++ b/lib/pgm.h
@@ -0,0 +1,136 @@
+/* pgm.h - header file for libpgm portable graymap library
+*/
+
+#ifndef _PGM_H_
+#define _PGM_H_
+
+#include "pbm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+/* The following definition has nothing to do with the format of a PGM file */
+typedef unsigned int gray;
+
+/* Since April 2000, we are capable of reading and generating raw
+   (binary) PGM files with maxvals up to 65535.  However, before that
+   the maximum (as usually implemented) was 255, and people still want
+   to generate files with a maxval of no more than 255 in most cases
+   (because then old Netpbm programs can process them, and they're
+   only half as big).
+
+   So we keep PGM_MAXMAXVAL = 255, even though it's kind of a misnomer.  
+
+   Note that one could always write a file with maxval > PGM_MAXMAXVAL and
+   it would just go into plain (text) format instead of raw (binary) format.
+   Along with the expansion to 16 bit raw files, we took away that ability.
+   Unless you specify 'forceplain' on the pgm_writepgminit() call, it will
+   fail if you specify a maxval > PGM_OVERALLMAXVAL.  I made this design
+   decision because I don't think anyone really wants to get a plain format
+   file with samples larger than 65535 in it.  However, it should be possible
+   just to increase PGM_OVERALLMAXVAL and get that old function back for
+   maxvals that won't fit in 16 bits.  I think the only thing really
+   constraining PGM_OVERALLMAXVAL is the size of the 'gray' data structure,
+   which is generally 32 bits.
+*/
+
+#define PGM_OVERALLMAXVAL 65535
+#define PGM_MAXMAXVAL 255
+
+
+/* Magic constants. */
+
+#define PGM_MAGIC1 'P'
+#define PGM_MAGIC2 '2'
+#define RPGM_MAGIC2 '5'
+#define PGM_FORMAT (PGM_MAGIC1 * 256 + PGM_MAGIC2)
+#define RPGM_FORMAT (PGM_MAGIC1 * 256 + RPGM_MAGIC2)
+#define PGM_TYPE PGM_FORMAT
+
+/* For the alpha-mask variant of PGM: */
+#define PGM_TRANSPARENT 0
+
+/* Macro for turning a format number into a type number. */
+
+#define PGM_FORMAT_TYPE(f) ((f) == PGM_FORMAT || (f) == RPGM_FORMAT ? PGM_TYPE : PBM_FORMAT_TYPE(f))
+
+
+/* Declarations of routines. */
+
+void 
+pgm_init(int *   const argcP,
+         char ** const argv);
+
+gray *
+pgm_allocrow(unsigned int const cols);
+
+#define pgm_freerow(grayrow) free(grayrow)
+
+#define pgm_allocarray( cols, rows ) \
+  ((gray**) pm_allocarray( cols, rows, sizeof(gray) ))
+#define pgm_freearray( grays, rows ) pm_freearray( (char**) grays, rows )
+
+gray **
+pgm_readpgm(FILE * const file,
+            int *  const colsP,
+            int *  const rowsP, 
+            gray * const maxvalP);
+
+void
+pgm_readpgminit(FILE * const file,
+                int *  const colsP, 
+                int *  const rowsP,
+                gray * const maxvalP,
+                int *  const formatP);
+
+void
+pgm_readpgmrow(FILE * const file,
+               gray * const grayrow, 
+               int    const cols,
+               gray   const maxval,
+               int    const format);
+
+void
+pgm_writepgminit(FILE * const fileP, 
+                 int    const cols, 
+                 int    const rows, 
+                 gray   const maxval, 
+                 int    const forceplain);
+
+void
+pgm_writepgmrow(FILE *       const fileP, 
+                const gray * const grayrow, 
+                int          const cols, 
+                gray         const maxval, 
+                int          const forceplain);
+
+void
+pgm_writepgm(FILE *  const fileP,
+             gray ** const grays,
+             int     const cols,
+             int     const rows,
+             gray    const maxval,
+             int     const forceplain);
+
+void
+pgm_nextimage(FILE * const file, int * const eofP);
+
+void
+pgm_check(FILE *               const file, 
+          enum pm_check_type   const check_type, 
+          int                  const format, 
+          int                  const cols, 
+          int                  const rows, 
+          gray                 const maxval,
+          enum pm_check_code * const retval_p);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /*_PGM_H_*/
diff --git a/lib/pm.h b/lib/pm.h
new file mode 100644
index 00000000..040a6a4b
--- /dev/null
+++ b/lib/pm.h
@@ -0,0 +1,346 @@
+/* pm.h - interface to format-independent part of libpbm.
+**
+** Copyright (C) 1988, 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#ifndef PM_H_INCLUDED
+#define PM_H_INCLUDED
+
+#include "pm_config.h"
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <errno.h>
+#include <setjmp.h>
+#include <sys/stat.h>
+
+#ifdef VMS
+#include <perror.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+
+/* Definitions to make Netpbm programs work with either ANSI C or C
+   Classic.
+
+   This is obsolete, as all compilers recognize the ANSI syntax now.
+
+   We are slowly removing all the ARGS invocations from the programs
+   (and replacing them with explicit ANSI syntax), but we have a lot
+   of programs where we have removed ARGS from the definition but not
+   the prototype, and we have discovered that the Sun compiler
+   considers the resulting mismatch between definition and prototype
+   to be an error.  So we make ARGS create the ANSI syntax
+   unconditionally to avoid having to fix all those mismatches.  */
+
+#if 0
+#if __STDC__
+#define ARGS(alist) alist
+#else /*__STDC__*/
+#define ARGS(alist) ()
+#define const
+#endif /*__STDC__*/
+#endif
+#define ARGS(alist) alist
+
+
+/* PM_GNU_PRINTF_ATTR lets the GNU compiler check pm_message() and pm_error()
+   calls to be sure the arguments match the format string, thus preventing
+   runtime segmentation faults and incorrect messages.
+*/
+#ifdef __GNUC__
+#define PM_GNU_PRINTF_ATTR(a,b) __attribute__ ((format (printf, a, b)))
+#else
+#define PM_GNU_PRINTF_ATTR
+#endif
+
+
+/* PURE_FN_ATTR is the attribute you add to a function declaration
+   that indicates it's a true function -- has no side effects and return
+   value is not influenced by anything except its arguments.
+*/
+#ifdef __GNUC__
+#define PURE_FN_ATTR __attribute__ ((const))
+#else
+#define PURE_FN_ATTR
+#endif
+
+typedef struct {
+    /* Coordinates of a pixel within an image.  Row 0 is the top row.
+       Column 0 is the left column.
+    */
+    unsigned int row;
+    unsigned int col;
+} pm_pixelcoord;
+
+extern int pm_plain_output;
+    /* Output functions are to produce plain (as opposed to raw) format
+       regardless of their 'plainformat' arguments.
+    */
+
+void 
+pm_init(const char * const progname, unsigned int const flags);
+
+void 
+pm_proginit(int* const argcP, char* argv[]);
+
+void
+pm_setMessage(int const newState, int * const oldStateP);
+
+FILE * 
+pm_tmpfile(void);
+
+void
+pm_make_tmpfile(FILE **       const filePP,
+                const char ** const filenameP);
+
+void
+pm_nextimage(FILE * const file, int * const eofP);
+
+/* Variable-sized arrays definitions. */
+
+char** 
+pm_allocarray (int const cols, int const rows, int const size );
+
+char * 
+pm_allocrow(unsigned int const cols,
+            unsigned int const size);
+
+void 
+pm_freearray (char** const its, int const rows);
+
+void 
+pm_freerow(char* const itrow);
+
+
+/* Obsolete -- use shhopt instead */
+int 
+pm_keymatch (char* const str, const char* const keyword, int const minchars);
+
+
+int PURE_FN_ATTR
+pm_maxvaltobits(int const maxval);
+
+int PURE_FN_ATTR
+pm_bitstomaxval(int const bits);
+
+unsigned int PURE_FN_ATTR
+pm_lcm (unsigned int const x, 
+        unsigned int const y,
+        unsigned int const z,
+        unsigned int const limit);
+
+void
+pm_setjmpbuf(jmp_buf * const jmpbufP);
+
+void
+pm_setjmpbufsave(jmp_buf *  const jmpbufP,
+                 jmp_buf ** const oldJmpbufPP);
+
+void
+pm_longjmp(void);
+
+void PM_GNU_PRINTF_ATTR(1,2)
+pm_message (const char format[], ...);     
+
+void PM_GNU_PRINTF_ATTR(1,2)
+pm_error (const char reason[], ...);       
+
+/* Obsolete - use helpful error message instead */
+void
+pm_perror (const char reason[]);           
+
+/* Obsolete - use shhopt and user's manual instead */
+void 
+pm_usage (const char usage[]);             
+
+FILE* 
+pm_openr (const char* const name);
+         
+FILE*    
+pm_openw (const char* const name);
+         
+FILE *
+pm_openr_seekable(const char name[]);
+
+void     
+pm_close (FILE* const f);
+
+void 
+pm_closer (FILE* const f);
+          
+void      
+pm_closew (FILE* const f);
+
+
+
+void
+pm_readchar(FILE * const ifP,
+            char * const cP);
+
+static __inline__ void
+pm_readcharu(FILE *          const ifP,
+             unsigned char * const cP) {
+    pm_readchar(ifP, (char *) cP);
+}
+
+void
+pm_writechar(FILE * const ofP,
+             char   const c);
+
+static __inline__ void
+pm_writecharu(FILE *        const ofP,
+              unsigned char const c) {
+    pm_writechar(ofP, (char) c);
+}
+
+int
+pm_readbigshort(FILE *  const ifP, 
+                short * const sP);
+
+static __inline__ int
+pm_readbigshortu(FILE*            const ifP, 
+                 unsigned short * const sP) {
+    return pm_readbigshort(ifP, (short *) sP);
+}
+
+int
+pm_writebigshort(FILE * const ofP,
+                 short  const s);
+
+static __inline__ int
+pm_writebigshortu(FILE *          const ofP,
+                  unsigned short  const s) {
+    return pm_writebigshort(ofP, (short) s);
+}
+
+int
+pm_readbiglong(FILE * const ifP, 
+               long * const lP);
+
+static __inline__ int
+pm_readbiglongu(FILE *          const ifP,
+                unsigned long * const lP) {
+    return pm_readbiglong(ifP, (long *) lP);
+}
+
+int
+pm_writebiglong(FILE * const ofP,
+                long   const l);
+
+static __inline__ int
+pm_writebiglongu(FILE *        const ofP,
+                 unsigned long const l) {
+    return pm_writebiglong(ofP, (long) l);
+}
+
+int
+pm_readlittleshort(FILE  * const ifP,
+                   short * const sP);
+
+static __inline__ int
+pm_readlittleshortu(FILE  *          const ifP,
+                    unsigned short * const sP) {
+    return pm_readlittleshort(ifP, (short *) sP);
+}
+
+int
+pm_writelittleshort(FILE * const ofP,
+                    short  const s);
+
+static __inline__ int
+pm_writelittleshortu(FILE *          const ofP,
+                     unsigned short  const s) {
+    return pm_writelittleshort(ofP, (short) s);
+}
+
+int
+pm_readlittlelong(FILE * const ifP,
+                  long * const lP);
+
+static __inline__ int
+pm_readlittlelongu(FILE *          const ifP,
+                   unsigned long * const lP) {
+    return pm_readlittlelong(ifP, (long *) lP);
+}
+
+int
+pm_writelittlelong(FILE * const ofP,
+                   long   const l);
+
+static __inline__ int
+pm_writelittlelongu(FILE *        const ofP,
+                    unsigned long const l) {
+    return pm_writelittlelong(ofP, (long) l);
+}
+
+int 
+pm_readmagicnumber(FILE * const ifP);
+
+char* 
+pm_read_unknown_size(FILE * const ifP, 
+                     long * const buf);
+
+short
+pm_bs_short(short const s);
+
+long
+pm_bs_long(long const l);
+
+unsigned int
+pm_tell(FILE * const fileP);
+
+void
+pm_tell2(FILE *       const fileP, 
+         void *       const fileposP,
+         unsigned int const fileposSize);
+
+void
+pm_seek2(FILE *             const fileP, 
+         const pm_filepos * const fileposP,
+         unsigned int       const fileposSize);
+
+void
+pm_seek(FILE * const fileP, unsigned long filepos);
+
+enum pm_check_code {
+    PM_CHECK_OK,
+    PM_CHECK_UNKNOWN_TYPE,
+    PM_CHECK_TOO_LONG,
+    PM_CHECK_UNCHECKABLE,
+    PM_CHECK_TOO_SHORT
+};
+
+enum pm_check_type {
+    PM_CHECK_BASIC
+};
+
+void
+pm_check(FILE *               const file, 
+         enum pm_check_type   const check_type, 
+         pm_filepos           const need_raster_size,
+         enum pm_check_code * const retval_p);
+
+char *
+pm_arg0toprogname(const char arg0[]);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
diff --git a/lib/pm_gamma.h b/lib/pm_gamma.h
new file mode 100644
index 00000000..92b34145
--- /dev/null
+++ b/lib/pm_gamma.h
@@ -0,0 +1,68 @@
+#ifndef _PM_GAMMA_H_
+#define _PM_GAMMA_H_
+
+#include "pm_config.h"
+
+#include <math.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+static __inline__ float
+pm_gamma709(float const intensity) {
+
+    /* Here are parameters of the gamma transfer function
+       for the Netpbm formats.  This is CIE Rec 709.
+       
+       This transfer function is linear for sample values 0 .. .018 
+       and an exponential for larger sample values.
+       The exponential is slightly stretched and translated, though,
+       unlike the popular pure exponential gamma transfer function.
+    */
+    float const gamma = 2.2;
+    float const oneOverGamma = 1.0 / gamma;
+    float const linearCutoff = 0.018;
+    float const linearExpansion = 
+        (1.099 * pow(linearCutoff, oneOverGamma) - 0.099) / linearCutoff;
+
+    float brightness;
+
+    if (intensity < linearCutoff)
+        brightness = intensity * linearExpansion;
+    else
+        brightness = 1.099 * pow(intensity, oneOverGamma) - 0.099;
+
+    return brightness;
+}
+
+
+
+static __inline__ float
+pm_ungamma709(float const brightness) {
+
+    /* These are the same parameters as in pm_gamma, above */
+
+    float const gamma = 2.2;
+    float const oneOverGamma = 1.0 / gamma;
+    float const linearCutoff = 0.018;
+    float const linearExpansion = 
+        (1.099 * pow(linearCutoff, oneOverGamma) - 0.099) / linearCutoff;
+    
+    float intensity;
+
+    if (brightness < linearCutoff * linearExpansion)
+        intensity = brightness / linearExpansion;
+    else
+        intensity = pow((brightness + 0.099) / 1.099, gamma);
+
+    return intensity;
+}
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/lib/pm_system.h b/lib/pm_system.h
new file mode 100644
index 00000000..0605f888
--- /dev/null
+++ b/lib/pm_system.h
@@ -0,0 +1,43 @@
+#ifndef PM_SYSTEM_H_INCLUDED
+#define PM_SYSTEM_H_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+
+void
+pm_system(void                  stdinFeeder(int, void *),
+          void *          const feederParm,
+          void                  stdoutAccepter(int, void *),
+          void *          const accepterParm,
+          const char *    const shellCommand);
+
+
+struct bufferDesc {
+    /* This is just a parameter for the routines below */
+    unsigned int    size;
+    unsigned char * buffer;
+    unsigned int *  bytesTransferredP;
+};
+
+
+/* The following are a Standard Input feeder and a Standard Output accepter
+   for pm_system().  
+*/
+void
+pm_feed_from_memory(int    const pipeToFeedFd,
+                    void * const feederParm);
+
+void
+pm_accept_to_memory(int    const pipetosuckFd,
+                    void * const accepterParm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/pnm.h b/lib/pnm.h
new file mode 100644
index 00000000..d3b6f84f
--- /dev/null
+++ b/lib/pnm.h
@@ -0,0 +1,134 @@
+/* pnm.h - header file for libpnm portable anymap library
+*/
+
+#ifndef _PNM_H_
+#define _PNM_H_
+
+#include "ppm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+
+typedef pixel xel;
+typedef pixval xelval;
+#define PNM_OVERALLMAXVAL PPM_OVERALLMAXVAL
+#define PNM_MAXMAXVAL PPM_MAXMAXVAL
+#define PNM_GET1(x) PPM_GETB(x)
+#define PNM_ASSIGN1(x,v) PPM_ASSIGN(x,0,0,v)
+#define PNM_ASSIGN(x,r,g,b) PPM_ASSIGN(x,r,g,b)
+#define PNM_EQUAL(x,y) PPM_EQUAL(x,y)
+#define PNM_FORMAT_TYPE(f) PPM_FORMAT_TYPE(f)
+#define PNM_DEPTH(newp,p,oldmaxval,newmaxval) \
+    PPM_DEPTH(newp,p,oldmaxval,newmaxval)
+
+/* Declarations of routines. */
+
+void pnm_init ARGS(( int* argcP, char* argv[] ));
+
+void
+pnm_nextimage(FILE *file, int * const eofP);
+
+xel *
+pnm_allocrow(unsigned int const cols);
+
+#define pnm_freerow(xelrow) free(xelrow)
+
+#define pnm_allocarray( cols, rows ) \
+  ((xel**) pm_allocarray( cols, rows, sizeof(xel) ))
+#define pnm_freearray( xels, rows ) pm_freearray( (char**) xels, rows )
+
+
+void
+pnm_readpnminit(FILE *   const fileP,
+                int *    const colsP,
+                int *    const rowsP,
+                xelval * const maxvalP,
+                int *    const formatP);
+
+void
+pnm_readpnmrow(FILE * const fileP,
+               xel *  const xelrow,
+               int    const cols,
+               xelval const maxval,
+               int    const format);
+
+xel **
+pnm_readpnm(FILE *   const fileP,
+            int *    const colsP,
+            int *    const rowsP,
+            xelval * const maxvalP,
+            int *    const formatP);
+
+void
+pnm_check(FILE *               const fileP,
+          enum pm_check_type   const check_type, 
+          int                  const format,
+          int                  const cols,
+          int                  const rows,
+          int                  const maxval,
+          enum pm_check_code * const retvalP);
+
+
+void
+pnm_writepnminit(FILE * const fileP, 
+                 int    const cols, 
+                 int    const rows, 
+                 xelval const maxval, 
+                 int    const format, 
+                 int    const forceplain);
+
+void
+pnm_writepnmrow(FILE * const fileP, 
+                xel *  const xelrow, 
+                int    const cols, 
+                xelval const maxval, 
+                int    const format, 
+                int    const forceplain);
+
+void
+pnm_writepnm(FILE * const fileP,
+             xel ** const xels,
+             int    const cols,
+             int    const rows,
+             xelval const maxval,
+             int    const format,
+             int    const forceplain);
+
+xel 
+pnm_backgroundxel (xel** xels, int cols, int rows, xelval maxval, int format);
+
+xel 
+pnm_backgroundxelrow (xel* xelrow, int cols, xelval maxval, int format);
+
+xel 
+pnm_whitexel (xelval maxval, int format);
+
+xel 
+pnm_blackxel(xelval maxval, int format);
+
+void 
+pnm_invertxel(xel *  const x,
+              xelval const maxval,
+              int    const format);
+
+void 
+pnm_promoteformat(xel** xels, int cols, int rows, xelval maxval, int format, 
+                  xelval newmaxval, int newformat);
+void 
+pnm_promoteformatrow(xel* xelrow, int cols, xelval maxval, int format, 
+                     xelval newmaxval, int newformat);
+
+pixel
+xeltopixel(xel const inputxel);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /*_PNM_H_*/
diff --git a/lib/ppm.h b/lib/ppm.h
new file mode 100644
index 00000000..033330b9
--- /dev/null
+++ b/lib/ppm.h
@@ -0,0 +1,300 @@
+/* Interface header file for PPM-related functions in libnetpbm */
+
+#ifndef _PPM_H_
+#define _PPM_H_
+
+#include "pgm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+typedef gray pixval;
+
+/* These are just for use in this header file */
+#define PPM_MAX(a,b) ((a) > (b) ? (a) : (b))
+#define PPM_MIN(a,b) ((a) < (b) ? (a) : (b))
+
+
+#define PPM_OVERALLMAXVAL PGM_OVERALLMAXVAL
+#define PPM_MAXMAXVAL PGM_MAXMAXVAL
+typedef struct {
+    pixval r, g, b;
+} pixel;
+#define PPM_GETR(p) ((p).r)
+#define PPM_GETG(p) ((p).g)
+#define PPM_GETB(p) ((p).b)
+
+/************* added definitions *****************/
+#define PPM_PUTR(p,red) ((p).r = (red))
+#define PPM_PUTG(p,grn) ((p).g = (grn))
+#define PPM_PUTB(p,blu) ((p).b = (blu))
+/**************************************************/
+
+#define PPM_ASSIGN(p,red,grn,blu) \
+  do { (p).r = (red); (p).g = (grn); (p).b = (blu); } while (0)
+#define PPM_EQUAL(p,q) \
+  ( (p).r == (q).r && (p).g == (q).g && (p).b == (q).b )
+
+
+/* Magic constants. */
+
+#define PPM_MAGIC1 'P'
+#define PPM_MAGIC2 '3'
+#define RPPM_MAGIC2 '6'
+#define PPM_FORMAT (PPM_MAGIC1 * 256 + PPM_MAGIC2)
+#define RPPM_FORMAT (PPM_MAGIC1 * 256 + RPPM_MAGIC2)
+#define PPM_TYPE PPM_FORMAT
+
+
+#include "ppmcmap.h"
+
+/* Macro for turning a format number into a type number. */
+
+#define PPM_FORMAT_TYPE(f) \
+  ((f) == PPM_FORMAT || (f) == RPPM_FORMAT ? PPM_TYPE : PGM_FORMAT_TYPE(f))
+
+
+/* Declarations of routines. */
+
+void ppm_init(int * argcP, char* argv[]);
+
+#define ppm_allocarray(cols, rows) \
+  ((pixel**) pm_allocarray(cols, rows, sizeof(pixel)))
+
+pixel *
+ppm_allocrow(unsigned int const cols);
+
+#define ppm_freearray(pixels, rows) pm_freearray((char**) pixels, rows)
+
+#define ppm_freerow(pixelrow) free(pixelrow);
+
+pixel**
+ppm_readppm(FILE *   const fileP, 
+            int *    const colsP, 
+            int *    const rowsP, 
+            pixval * const maxvalP);
+
+void
+ppm_readppminit(FILE *   const fileP, 
+                int *    const colsP, 
+                int *    const rowsP, 
+                pixval * const maxvalP, 
+                int *    const formatP);
+
+void
+ppm_readppmrow(FILE*  const fileP, 
+               pixel* const pixelrow, 
+               int    const cols, 
+               pixval const maxval, 
+               int    const format);
+
+void
+ppm_writeppm(FILE *  const fileP, 
+             pixel** const pixels, 
+             int     const cols, 
+             int     const rows, 
+             pixval  const maxval, 
+             int     const forceplain);
+
+void
+ppm_writeppminit(FILE*  const fileP, 
+                 int    const cols, 
+                 int    const rows, 
+                 pixval const maxval, 
+                 int    const forceplain);
+
+void
+ppm_writeppmrow(FILE *  const fileP, 
+                pixel * const pixelrow, 
+                int     const cols, 
+                pixval  const maxval, 
+                int     const forceplain);
+
+void
+ppm_check(FILE *               const fileP, 
+          enum pm_check_type   const check_type, 
+          int                  const format, 
+          int                  const cols, 
+          int                  const rows, 
+          pixval               const maxval,
+          enum pm_check_code * const retval_p);
+
+void
+ppm_nextimage(FILE * const fileP, 
+              int *  const eofP);
+
+pixel 
+ppm_parsecolor(const char * const colorname,
+               pixval       const maxval);
+
+pixel
+ppm_parsecolor2(const char * const colorname,
+                pixval       const maxval,
+                int          const closeOk);
+
+char*
+ppm_colorname(const pixel* const colorP, 
+              pixval       const maxval, 
+              int          const hexok);
+
+void
+ppm_readcolordict(const char *      const fileName,
+                  int               const mustOpen,
+                  unsigned int *    const nColorsP,
+                  const char ***    const colornamesP,
+                  pixel **          const colorsP,
+                  colorhash_table * const chtP);
+
+void
+ppm_readcolornamefile(const char *      const fileName, 
+                      int               const mustOpen,
+                      colorhash_table * const chtP, 
+                      const char ***    const colornamesP);
+
+void
+ppm_freecolornames(const char ** const colornames);
+
+#define PPM_ISGRAY(pixel) \
+    (PPM_GETR(pixel) == PPM_GETG(pixel) && PPM_GETR(pixel) == PPM_GETB(pixel))
+
+/* Color scaling macro -- to make writing ppmtowhatever easier. */
+
+#define PPM_DEPTH(newp,p,oldmaxval,newmaxval) \
+    PPM_ASSIGN( (newp), \
+	( (int) PPM_GETR(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval), \
+	( (int) PPM_GETG(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval), \
+	( (int) PPM_GETB(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval) )
+
+#define PPM_SQR(x) (x)*(x)
+
+static __inline__ unsigned int
+PPM_DISTANCE(pixel const p1,
+             pixel const p2) {
+    return (
+        PPM_SQR(PPM_GETR(p1)-PPM_GETR(p2)) +
+        PPM_SQR(PPM_GETG(p1)-PPM_GETG(p2)) +
+        PPM_SQR(PPM_GETB(p1)-PPM_GETB(p2)) );
+}
+#undef PPM_SQR
+
+/* Note that because a sample can be at most 1 << 16 - 1, PPM_DISTANCE
+   is less than UINT_MAX.  That means you can use UINT_MAX as an infinite
+   distance in some applications.
+*/
+
+/* Luminance, Chrominance macros. */
+
+/* The following are weights of the red, green, and blue components
+   respectively in the luminance of a color.  Actually, it's "luma,"
+   not luminance, the difference being that luminance would be a linear
+   combination of intensities, whereas luma is a linear combination of
+   gamma-adjusted intensities, as you would find in a Netpbm image.
+
+   These are from ITU-R BT.601.5.  That means they probably aren't technically
+   right for use with PPM images, because the PPM spec says ITU-R BT.709.
+   The two are similar, though.
+*/
+#define PPM_LUMINR (0.2989)
+#define PPM_LUMING (0.5866)
+#define PPM_LUMINB (0.1145)
+
+#define PPM_LUMIN(p) ( PPM_LUMINR * PPM_GETR(p) \
+                       + PPM_LUMING * PPM_GETG(p) \
+                       + PPM_LUMINB * PPM_GETB(p) )
+#define PPM_CHROM_B(p) ( -0.16874 * PPM_GETR(p) \
+                         - 0.33126 * PPM_GETG(p) \
+                         + 0.5 * PPM_GETB(p) )
+#define PPM_CHROM_R(p) ( 0.5 * PPM_GETR(p) \
+                         - 0.41869 * PPM_GETG(p) \
+                         - 0.08131 * PPM_GETB(p) )
+
+pixel
+ppm_color_from_ycbcr(unsigned int const y, 
+                     int          const cb, 
+                     int          const cr);
+
+/* Hue/Saturation/Value calculations */
+
+struct hsv {
+    double h;  /* hue (degrees)  0..360 */
+    double s;  /* saturation (0-1) */
+    double v;  /* value (0-1) */
+};
+
+pixel
+ppm_color_from_hsv(struct hsv const hsv,
+                   pixval     const maxval);
+
+struct hsv
+ppm_hsv_from_color(pixel  const color,
+                   pixval const maxval);
+
+static __inline pixval
+ppm_colorvalue(pixel const p) {
+/*----------------------------------------------------------------------------
+  The color value (V is HSV) as a pixval
+-----------------------------------------------------------------------------*/
+    return PPM_MAX(PPM_GETR(p), PPM_MAX(PPM_GETG(p), PPM_GETB(p)));
+}
+
+static __inline pixval
+ppm_saturation(pixel const p,
+               pixval const maxval) {
+/*----------------------------------------------------------------------------
+  The saturation, as a pixval (i.e. if saturation is 50% and maxval
+  is 100, this is 50).
+-----------------------------------------------------------------------------*/
+    pixval const maxIntensity =
+        PPM_MAX(PPM_GETR(p), PPM_MAX(PPM_GETG(p), PPM_GETB(p)));
+    pixval const minIntensity =
+        PPM_MIN(PPM_GETR(p), PPM_MIN(PPM_GETG(p), PPM_GETB(p)));
+    pixval const range = maxIntensity - minIntensity;
+
+    return (pixval)((unsigned long)range * maxval / maxIntensity);
+}
+
+typedef enum {
+    /* A color from the set of universally understood colors developed
+       by Brent Berlin and Paul Kay
+    */
+    BKCOLOR_BLACK = 0,
+    BKCOLOR_GRAY,
+    BKCOLOR_WHITE,
+    BKCOLOR_RED,
+    BKCOLOR_ORANGE,
+    BKCOLOR_YELLOW,
+    BKCOLOR_GREEN,
+    BKCOLOR_BLUE,
+    BKCOLOR_VIOLET,
+    BKCOLOR_PURPLE,
+    BKCOLOR_BROWN
+} bk_color;
+
+#define BKCOLOR_COUNT (BKCOLOR_BROWN+1)
+
+bk_color
+ppm_bk_color_from_color(pixel  const color,
+                        pixval const maxval);
+
+pixel
+ppm_color_from_bk_color(bk_color const bkColor,
+                        pixval   const maxval);
+
+bk_color
+ppm_bk_color_from_name(const char * const name);
+
+const char *
+ppm_name_from_bk_color(bk_color const bkColor);
+
+#ifdef __cplusplus
+}
+#endif
+
+#undef PPM_MIN
+#undef PPM_MAX
+
+#endif /*_PPM_H_*/
diff --git a/lib/ppmcmap.h b/lib/ppmcmap.h
new file mode 100644
index 00000000..b44dcbea
--- /dev/null
+++ b/lib/ppmcmap.h
@@ -0,0 +1,111 @@
+#ifndef PPMCMAP_INCLUDED
+#define PPMCMAP_INCLUDED
+/* ppmcmap.h - header file for colormap routines in libppm
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+
+/* Color histogram stuff. */
+
+typedef struct colorhist_item* colorhist_vector;
+struct colorhist_item {
+    pixel color;
+    int value;
+};
+
+typedef struct colorhist_list_item* colorhist_list;
+struct colorhist_list_item {
+    struct colorhist_item ch;
+    colorhist_list next;
+};
+
+colorhist_vector
+ppm_computecolorhist( pixel ** const pixels, 
+                      const int cols, const int rows, const int maxcolors, 
+                      int * const colorsP );
+colorhist_vector
+ppm_computecolorhist2(FILE * const ifp,
+                      const int cols, const int rows, 
+                      const pixval maxval, const int format, 
+                      const int maxcolors, int * const colorsP );
+
+void
+ppm_addtocolorhist( colorhist_vector chv, 
+                    int * const colorsP, const int maxcolors, 
+                    const pixel * const colorP, 
+                    const int value, const int position );
+
+void
+ppm_freecolorhist(colorhist_vector const chv);
+
+
+/* Color hash table stuff. */
+
+typedef colorhist_list* colorhash_table;
+
+colorhash_table
+ppm_computecolorhash( pixel ** const pixels, 
+                      const int cols, const int rows, 
+                      const int maxcolors, int * const colorsP );
+
+colorhash_table
+ppm_computecolorhash2(FILE * const ifp,
+                      const int cols, const int rows, 
+                      const pixval maxval, const int format, 
+                      const int maxcolors, int * const colorsP);
+
+int
+ppm_lookupcolor(colorhash_table const cht, 
+                const pixel *   const colorP );
+
+colorhist_vector
+ppm_colorhashtocolorhist(colorhash_table const cht, 
+                         int             const maxcolors);
+
+colorhash_table
+ppm_colorhisttocolorhash(colorhist_vector const chv, 
+                         int              const colors);
+
+int
+ppm_addtocolorhash(colorhash_table const cht, 
+                   const pixel *   const colorP, 
+                   int             const value);
+
+void
+ppm_delfromcolorhash(colorhash_table const cht, 
+                     const pixel *   const colorP);
+
+
+colorhash_table
+ppm_alloccolorhash(void);
+
+void
+ppm_freecolorhash(colorhash_table const cht);
+
+
+colorhash_table ppm_colorrowtocolorhash ARGS((pixel *colorrow, int ncolors));
+pixel * ppm_computecolorrow ARGS((pixel **pixels, int cols, int rows, int maxcolors, int *ncolorsP));
+pixel * ppm_mapfiletocolorrow ARGS((FILE *file, int maxcolors, int *ncolorsP, pixval *maxvalP));
+void    ppm_colorrowtomapfile ARGS((FILE *ofp, pixel *colormap, int ncolors, pixval maxval));
+void    ppm_sortcolorrow (pixel * const colorrow, const int ncolors, 
+                          int (*cmpfunc)(pixel *, pixel *) );
+int     ppm_addtocolorrow ARGS((pixel *colorrow, int *ncolorsP, int maxcolors, pixel *pixelP));
+
+int
+ppm_findclosestcolor(const pixel * const colormap, 
+                     int           const ncolors, 
+                     const pixel * const pP);
+
+/* standard sort function for ppm_sortcolorrow() */
+#define PPM_STDSORT     (int (*)(pixel *, pixel *))0
+#endif
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/lib/ppmdfont.c b/lib/ppmdfont.c
new file mode 100644
index 00000000..a378f79c
--- /dev/null
+++ b/lib/ppmdfont.c
@@ -0,0 +1,133 @@
+#include <stdio.h>
+#include <assert.h>
+
+#include "pm.h"
+#include "mallocvar.h"
+#include "ppmdfont.h"
+
+
+extern struct ppmd_font ppmd_standardfont;
+
+static const struct ppmd_font * currentFontP = &ppmd_standardfont;
+
+static void
+readGlyphHeader(FILE *                    const ifP,
+                struct ppmd_glyphHeader * const glyphHeaderP) {
+
+    glyphHeaderP->commandCount = fgetc(ifP);
+    glyphHeaderP->skipBefore   = fgetc(ifP);
+    glyphHeaderP->skipAfter    = fgetc(ifP);
+}
+
+
+
+static void
+readGlyphCommand(FILE *                     const ifP,
+                 struct ppmd_glyphCommand * const glyphCommandP) {
+
+    glyphCommandP->verb = fgetc(ifP);
+    glyphCommandP->x    = fgetc(ifP);
+    glyphCommandP->y    = fgetc(ifP);
+}
+
+
+
+static void
+readCharacter(FILE *              const ifP,
+              struct ppmd_glyph * const glyphP) {
+
+    unsigned int commandNum;
+    struct ppmd_glyphCommand * commandList;
+
+    readGlyphHeader(ifP, &glyphP->header);
+
+    MALLOCARRAY(commandList, glyphP->header.commandCount);
+
+    if (commandList == NULL)
+        pm_error("Insufficient memory to create a %u-command "
+                 "command list.", glyphP->header.commandCount);
+
+    for (commandNum = 0;
+         commandNum < glyphP->header.commandCount;
+         ++commandNum) {
+
+        readGlyphCommand(ifP, &commandList[commandNum]);
+    }
+    glyphP->commandList = commandList;
+}
+
+
+
+static void
+readFontHeader(FILE *                   const ifP,
+               struct ppmd_fontHeader * const fontHeaderP) {
+    
+    fread(&fontHeaderP->signature, 1, sizeof(fontHeaderP->signature), ifP);
+    fontHeaderP->format         = fgetc(ifP);
+    fontHeaderP->characterCount = fgetc(ifP);
+    fontHeaderP->firstCodePoint = fgetc(ifP);
+}
+
+
+
+void
+ppmd_set_font(const struct ppmd_font * const newFontP) {
+
+    currentFontP = newFontP;
+}
+
+
+
+const struct ppmd_font *
+ppmd_get_font(void) {
+
+    return currentFontP;
+}
+
+
+
+void
+ppmd_read_font(FILE *                        const ifP,
+               const struct ppmd_font **     const fontPP) {
+
+    unsigned int relativeCodePoint;
+    struct ppmd_glyph * glyphTable;
+    struct ppmd_font * fontP;
+
+    MALLOCVAR(fontP);
+    if (fontP == NULL)
+        pm_error("Insufficient memory for font header");
+
+    readFontHeader(ifP, &fontP->header);
+
+    MALLOCARRAY(glyphTable, fontP->header.characterCount);
+    if (glyphTable == NULL)
+        pm_error("Insufficient memory to store %u characters",
+                 fontP->header.characterCount);
+
+    for (relativeCodePoint = 0;
+         relativeCodePoint < fontP->header.characterCount;
+         ++relativeCodePoint) {
+
+        readCharacter(ifP, &glyphTable[relativeCodePoint]);
+    }
+    fontP->glyphTable = glyphTable;
+    *fontPP = fontP;
+}
+
+
+
+void
+ppmd_free_font(const struct ppmd_font * const fontP) {
+
+    unsigned int relativeCodePoint;
+
+    for (relativeCodePoint = 0;
+         relativeCodePoint < fontP->header.characterCount;
+         ++relativeCodePoint) {
+        
+        free((void*)fontP->glyphTable[relativeCodePoint].commandList);
+    }
+    free((void*)fontP->glyphTable);
+    free((void*)fontP);
+}
diff --git a/lib/ppmdfont.h b/lib/ppmdfont.h
new file mode 100644
index 00000000..329386ff
--- /dev/null
+++ b/lib/ppmdfont.h
@@ -0,0 +1,74 @@
+#ifndef PPMDFONT_INCLUDED
+#define PPMDFONT_INCLUDED
+
+#include <stdio.h>
+
+/* A font file has the following format, with proper packing:
+
+    struct ppmd_fontHeader fontHeader;
+    struct {
+        struct ppmd_glyphHeader glyphHeader;
+        struct ppmd_glyphCommand glyphCommand[N];
+    } glyph[M]
+    
+    Where:
+        M is fontHeader.characterCount
+        N is glyphHeader.commandCount   
+
+    glyph[i] is the glyph for code point Q,
+      where i = Q - fontHeader.firstCodePoint
+
+*/
+
+struct ppmd_fontHeader {
+    char signature[8];             /* "ppmdfont" */
+    unsigned char format;          /* 0x01 */
+    unsigned char characterCount;
+        /* Number of characters in this font */
+    unsigned char firstCodePoint;
+        /* lowest code point in the font */
+};
+
+struct ppmd_glyphHeader {
+    unsigned char commandCount;
+        /* Number of struct glyphCommand that follow */
+    unsigned char skipBefore;
+    unsigned char skipAfter;
+};
+
+enum ppmd_glyphCommandVerb {CMD_NOOP     = 0,
+                            CMD_DRAWLINE = 1,
+                            CMD_MOVEPEN  = 2
+};
+
+struct ppmd_glyphCommand {
+    enum ppmd_glyphCommandVerb verb;
+    unsigned char x;
+    unsigned char y;
+};
+
+struct ppmd_glyph {
+    struct ppmd_glyphHeader header;
+    const struct ppmd_glyphCommand * commandList;
+};
+
+struct ppmd_font {
+    struct ppmd_fontHeader header;
+    const struct ppmd_glyph * glyphTable;
+};
+
+void
+ppmd_set_font(const struct ppmd_font * const newFontP);
+
+const struct ppmd_font *
+ppmd_get_font(void);
+
+void
+ppmd_read_font(FILE *                    const ifP,
+               const struct ppmd_font ** const fontPP);
+
+void
+ppmd_free_font(const struct ppmd_font * const fontP);
+
+
+#endif
diff --git a/lib/ppmdraw.h b/lib/ppmdraw.h
new file mode 100644
index 00000000..9efe51b9
--- /dev/null
+++ b/lib/ppmdraw.h
@@ -0,0 +1,303 @@
+/* ppmdraw.h - header file for simple drawing routines in libppm
+**
+** Simple, yes, and also fairly slow if the truth be told; but also very
+** flexible and powerful.
+**
+** The two basic concepts are the drawproc and clientdata.  All the drawing
+** routines take a drawproc that does the actual drawing.  A drawproc draws
+** a single point, and it looks like this:
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+
+typedef enum {
+    PPMD_PATHLEG_LINE
+} ppmd_pathlegtype;
+
+typedef struct {
+    unsigned int x;
+    unsigned int y;
+} ppmd_point;
+
+struct ppmd_linelegparms {
+    ppmd_point end;
+};
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   A leg of a ppmd_path.
+-----------------------------------------------------------------------------*/
+    ppmd_pathlegtype type;
+    union {
+        struct ppmd_linelegparms linelegparms;
+    } u;
+} ppmd_pathleg;
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   A closed path
+-----------------------------------------------------------------------------*/
+    unsigned int version;
+        /* Must be zero.  For future expansion. */
+    ppmd_point   begPoint;  
+    unsigned int legCount;
+        /* Number of legs in the path; i.e. size of 'legs' array */
+    size_t       legSize;
+        /* Size of storage occupied by one ppmd_pathleg.  I.e.
+           sizeof(ppmd_pathleg).  Used for
+           binary backward compatibility between callers and libppmd
+           as the definition of ppmd_pathleg changes.
+        */
+    ppmd_pathleg * legs;
+} ppmd_path;
+
+
+
+typedef void ppmd_drawproc(pixel **, int, int, pixval, int, int, const void *);
+
+ppmd_drawproc ppmd_point_drawproc;
+
+/*
+** So, you call a drawing routine, e.g. ppmd_line(), and pass it a drawproc;
+** it calls the drawproc for each point it wants to draw.  Why so complicated?
+** Because you can define your own drawprocs to do more interesting things than
+** simply draw the point.  For example, you could make one that calls back into
+** another drawing routine, say ppmd_circle() to draw a circle at each point
+** of a line.
+**
+** Slow?  Well sure, we're talking results here, not realtime.  You can do
+** tricks with this arrangement that you couldn't even think of before.
+** Still, to speed things up for the 90% case you can use this:
+*/
+#define PPMD_NULLDRAWPROC NULL
+/*
+** Just like ppmd_point_drawproc() it simply draws the point, but it's done
+** inline, and clipping is assumed to be handled at a higher level.
+**
+** Now, what about clientdata.  Well, it's an arbitrary pointer, and can
+** mean something different to each different drawproc.  For the above two
+** drawprocs, clientdata should be a pointer to a pixel holding the color
+** to be drawn.  Other drawprocs can use it to point to something else,
+** e.g. some structure to be modified, or they can ignore it.
+*/
+
+
+/* Outline drawing routines.  Lines, splines, circles, etc. */
+
+int 
+ppmd_setlinetype(int const type);
+
+#define PPMD_LINETYPE_NORMAL 0
+#define PPMD_LINETYPE_NODIAGS 1
+/* If you set NODIAGS, all pixels drawn by ppmd_line() will be 4-connected
+** instead of 8-connected; in other words, no diagonals.  This is useful
+** for some applications, for example when you draw many parallel lines
+** and you want them to fit together without gaps.
+*/
+
+int
+ppmd_setlineclip(int const clip);
+
+#define ppmd_setlineclipping(x)     ppmd_setlineclip(x)
+/* Normally, ppmd_line() clips to the edges of the pixmap.  You can use this
+** routine to disable the clipping, for example if you are using a drawproc
+** that wants to do its own clipping.
+*/
+
+void
+ppmd_line(pixel**       const pixels, 
+          int           const cols, 
+          int           const rows, 
+          pixval        const maxval, 
+          int           const x0, 
+          int           const y0, 
+          int           const x1, 
+          int           const y1, 
+          ppmd_drawproc       drawproc,
+          const void *  const clientdata);
+/* Draws a line from (x0, y0) to (x1, y1).
+*/
+
+void
+ppmd_spline3(pixel **      const pixels, 
+             int           const cols, 
+             int           const rows, 
+             pixval        const maxval, 
+             int           const x0, 
+             int           const y0, 
+             int           const x1, 
+             int           const y1, 
+             int           const x2, 
+             int           const y2, 
+             ppmd_drawproc       drawproc,
+             const void *  const clientdata);
+    /* Draws a three-point spline from (x0, y0) to (x2, y2), with (x1,
+       y1) as the control point.  All drawing is done via ppmd_line(),
+       so the routines that control it control ppmd_spline3() as well. 
+    */
+
+void
+ppmd_polyspline(pixel **     const pixels, 
+                int          const cols, 
+                int          const rows, 
+                pixval       const maxval, 
+                int          const x0, 
+                int          const y0, 
+                int          const nc, 
+                int *        const xc, 
+                int *        const yc, 
+                int          const x1, 
+                int          const y1, 
+                ppmd_drawproc      drawProc,
+                const void * const clientdata);
+    /* Draws a bunch of splines end to end.  (x0, y0) and (x1, y1) are
+       the initial and final points, and the xc and yc are the
+       intermediate control points.  nc is the number of these control
+       points.
+    */
+
+void
+ppmd_spline4(pixel **      const pixels, 
+             int           const cols, 
+             int           const rows, 
+             pixval        const maxval, 
+             int           const x0, 
+             int           const y0, 
+             int           const x1, 
+             int           const y1, 
+             int           const x2, 
+             int           const y2, 
+             int           const x3, 
+             int           const y3, 
+             ppmd_drawproc       drawproc,
+             const void *  const clientdata);
+    /* Draws a four-point spline from (x0, y0) to (x3, y3), with (x1,
+       y1) and (x2, y2) as the control points.  All drawing is done
+       via ppmd_line(), so the routines that control it control this
+       as well.
+    */
+
+void
+ppmd_circle(pixel **     const pixels, 
+            int          const cols, 
+            int          const rows, 
+            pixval       const maxval, 
+            int          const cx, 
+            int          const cy, 
+            int          const radius, 
+            ppmd_drawproc      drawProc,
+            const void * const clientdata);
+    /* Draws a circle centered at (cx, cy) with the specified radius. */
+
+
+/* Simple filling routines.  */
+
+void
+ppmd_filledrectangle(pixel **      const pixels, 
+                     int           const cols, 
+                     int           const rows, 
+                     pixval        const maxval, 
+                     int           const x, 
+                     int           const y, 
+                     int           const width, 
+                     int           const height, 
+                     ppmd_drawproc       drawproc,
+                     const void *  const clientdata );
+    /* Fills in the rectangle [x, y, width, height]. */
+
+
+void
+ppmd_fill_path(pixel **      const pixels, 
+               int           const cols, 
+               int           const rows, 
+               pixval        const maxval,
+               ppmd_path *   const pathP,
+               pixel         const color);
+    /* Fills in a closed path.  Not much different from ppmd_fill(),
+       but with a different interface.
+    */
+
+
+/* Arbitrary filling routines.  With these you can fill any outline that
+** you can draw with the outline routines.
+*/
+
+struct fillobj;
+
+struct fillobj *
+ppmd_fill_create(void);
+    /* Returns a blank fillhandle. */
+
+void
+ppmd_fill_destroy(struct fillobj * fillObjP);
+
+void
+ppmd_fill_drawproc(pixel ** const pixels, 
+                   int      const cols, 
+                   int      const rows, 
+                   pixval   const maxval, 
+                   int      const x, 
+                   int      const y, 
+                   const void * const clientdata);
+    /* Use this drawproc to trace the outline you want filled.  Use
+       the fillhandle as the clientdata.
+    */
+void
+ppmd_fill(pixel **         const pixels, 
+          int              const cols, 
+          int              const rows, 
+          pixval           const maxval, 
+          struct fillobj * const fh,
+          ppmd_drawproc          drawProc,
+          const void *     const clientdata);
+
+/* Once you've traced the outline, give the fillhandle to this routine to
+** do the actual drawing.  As usual, it takes a drawproc and clientdata;
+** you could define drawprocs to do stipple fills and such.
+*/
+
+/* Text drawing routines. */
+
+void
+ppmd_text(pixel**      const pixels, 
+          int          const cols, 
+          int          const rows, 
+          pixval       const maxval, 
+          int          const xpos, 
+          int          const ypos, 
+          int          const height, 
+          int          const angle, 
+          const char * const sArg, 
+          void (*drawprocP)(pixel**, int, int, pixval, int, int, const void*), 
+    const void*  const clientdata);
+/* Draws the null-terminated string 's' left justified at the point
+   ('x', 'y').  The text will be 'height' pixels high and will be aligned on a
+   baseline inclined 'angle' degrees with the X axis.  The supplied
+   drawproc and clientdata are passed to ppmd_line() which performs the
+   actual drawing.
+*/
+
+void
+ppmd_text_box(int          const height, 
+              int          const angle, 
+              const char * const s, 
+              int *        const leftP, 
+              int *        const topP, 
+              int *        const rightP, 
+              int *        const bottomP);
+/* Calculates the extents box for text drawn by ppm_text with the given
+   string, size, and orientation.  Most extent box calculations should use
+   an angle specification of zero to calculate the unrotated box enclosing
+   the text.  If you need the extents of rotated text, however, you can
+   call ppmd_text_box with a nonzero angle.
+*/
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/lib/ppmfloyd.h b/lib/ppmfloyd.h
new file mode 100644
index 00000000..e16ad651
--- /dev/null
+++ b/lib/ppmfloyd.h
@@ -0,0 +1,67 @@
+/* These declarations were supposed to be in the libfloyd.h file in the ilbm
+   package, but that file was missing, so I made them up myself.  
+   - Bryan 01.03.10.
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+struct ppm_fs_info {
+    /* thisXerr and nextXerr are dynamically allocated arrays each of whose
+       dimension is the width of the image plus 2
+       */
+    long *thisrederr;
+    long *thisgreenerr;
+    long *thisblueerr;
+    long *nextrederr;
+    long *nextgreenerr;
+    long *nextblueerr;
+    int lefttoright;
+    int cols;
+    pixval maxval;
+    int flags;
+    pixel *pixrow;
+    int col_end;
+    pixval red, green, blue;
+};
+
+typedef struct ppm_fs_info ppm_fs_info;
+
+/* Bitmasks for ppm_fs_info.flags */
+#define FS_RANDOMINIT 0x01
+#define FS_ALTERNATE  0x02
+
+ppm_fs_info *
+ppm_fs_init(int cols, pixval maxval, int flags);
+
+void
+ppm_fs_free(ppm_fs_info *fi);
+
+int
+ppm_fs_startrow(ppm_fs_info *fi, pixel *pixrow);
+
+int
+ppm_fs_next(ppm_fs_info *fi, int col);
+
+void
+ppm_fs_endrow(ppm_fs_info *fi);
+
+void
+ppm_fs_update(    ppm_fs_info *fi, int col, pixel *pP);
+
+
+void
+ppm_fs_update3(ppm_fs_info * const fi, 
+               int           const col, 
+               pixval        const r, 
+               pixval        const g, 
+               pixval        const b);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/lib/rgb.txt b/lib/rgb.txt
new file mode 100644
index 00000000..ad4d9d87
--- /dev/null
+++ b/lib/rgb.txt
@@ -0,0 +1,881 @@
+# The colors in this color dictionary are from the following sources, in
+# order.  Some color names are defined multiple times, and sometimes
+# with different colors.  Many colors have multiple names.
+
+# - Netpbm originals
+# - Crayola crayons, as determined by John Thomas at Tektronix
+# - Hollasch at Microsoft Research
+# - HTML 4.0
+# - Some HTML extension that Internet Explorer understands
+# - XFree86 rgb.txt ca. 2001, derived from MIT X11
+
+# More details on the sources are above each group.
+
+# The preferred form of names is The X Window System standard, all one
+# word with the initial letter of each word capitalized.  But programs that
+# look up in this dictionary should convert the target color name and each
+# name in this dictionary to all lower case and remove spaces before
+# comparing.
+#
+# Numbered colors (e.g. blue4) are deprecated, but many are here for
+# compatibility with X.
+
+# These are the 72 colors from Crayola crayons, as determined by
+# John Thomas at Tektronix
+# (taken from http://www.swiss.ai.mit.edu/~jaffer/Color/thomas.txt on
+# 2002.09.03, with all the grayN removed).
+
+  0   0   0 Black
+255 255 255 White
+255   0   0 Red
+  0 255   0 Green
+  0   0 255 Blue
+  0 255 255 Cyan
+255   0 211 Magenta
+255 255   0 Yellow
+255 138   0 Orange
+159 211   0 GreenYellow
+  0 255 159 Spring Green
+  0 138 255 SkyBlue
+148   0 211 Violet
+255   0 148 VioletRed
+105 105 105 DimGray
+174 174 174 Gray
+174 174 174 Grey
+211 211 211 LightGrey
+211 211 211 LightGray
+105 105 105 DimGrey
+199  21 133 MediumVioletRed
+114  33 188 BlueViolet
+218 107 212 Orchid
+172  77 166 MediumOrchid
+106  37 102 DarkOrchid
+103   7  72 Maroon
+ 76  46  87 Plum
+146  62 112 Thistle
+171 197 255 LightBlue
+ 61  98 208 MediumBlue
+100 149 237 CornflowerBlue
+  0   0 142 NavyBlue
+  0   0 142 Navy
+ 12  62  99 MidnightBlue
+ 72 209 204 Turquoise
+ 62 172 181 MediumTurquoise
+ 29 111 117 DarkTurquoise
+ 52 152 202 LightSteelBlue
+ 55 121 153 SteelBlue
+126 125 160 CadetBlue
+117 134 190 SlateBlue
+ 95 109 154 MediumSlateBlue
+ 51  62  99 DarkSlateBlue
+ 60  64  74 DarkSlateGrey
+ 60  64  74 DarkSlateGray
+  0  83   0 DarkGreen
+ 79  79  47 DarkOliveGreen
+ 85 192  52 ForestGreen
+107 142  35 MediumForestGreen
+ 46 155  28 LimeGreen
+ 60 141  35 MediumSpringGreen
+152 255 152 PaleGreen
+ 43 167 112 SeaGreen
+ 27 134  86 MediumSeaGreen
+ 41 171 151 Aquamarine
+ 21 135 118 MediumAquamarine
+ 75 211   0 YellowGreen
+254 197  68 Gold
+184 134  11 MediumGoldenrod
+218 165  32 Goldenrod
+229 199 117 Wheat
+189 167 107 Khaki
+176 155 125 Tan
+178 143  86 SandyBrown
+142 107  35 Sienna
+103  67   0 Brown
+101  46  46 IndianRed
+255 174 185 Pink
+248 137 117 Coral
+248 109 104 Salmon
+226  65  42 OrangeRed
+136  18  13 Firebrick
+
+# The following 192 colors are from
+# http://www.research.microsoft.com/~hollasch/cgindex/color/colors.txt
+# on 2002.09.02.  See examples of them at
+# http://www.swiss.ai.mit.edu/~jaffer/Color/hollasch.pdf
+
+250 235 215 AntiqueWhite
+240 255 255 Azure
+255 228 196 Bisque
+255 235 205 BlanchedAlmond
+255 248 220 Cornsilk
+252 230 201 Eggshell
+255 250 240 FloralWhite
+220 220 220 Gainsboro
+248 248 255 GhostWhite
+240 255 240 Honeydew
+255 255 240 Ivory
+230 230 250 Lavender
+255 240 245 LavenderBlush
+255 250 205 LemonChiffon
+250 240 230 Linen
+245 255 250 MintCream
+255 228 225 MistyRose
+255 228 181 Moccasin
+255 222 173 NavajoWhite
+253 245 230 OldLace
+255 239 213 PapayaWhip
+255 218 185 PeachPuff
+255 245 238 Seashell
+255 250 250 Snow
+216 191 216 Thistle
+252 255 240 TitaniumWhite
+245 222 179 Wheat
+255 255 255 White
+245 245 245 WhiteSmoke
+253 248 255 ZincWhite
+128 138 135 ColdGrey
+105 105 105 DimGrey
+192 192 192 Grey
+211 211 211 LightGrey
+112 128 144 SlateGrey
+ 47  79  79 SlateGreyDark
+119 136 153 SlateGreyLight
+128 128 105 WarmGrey
+  0   0   0 Black
+ 41  36  33 IvoryBlack
+ 46  71  59 Lamp Black
+227  38  54 AlizarinCrimson
+156 102  31 Brick
+227  23  13 CadmiumRedDeep
+255 127  80 Coral
+240 128 128 CoralLight
+255  20 147 DeepPink
+212  61  26 EnglishRed
+178  34  34 Firebrick
+227  18  48 GeraniumLake
+255 105 180 HotPink
+176  23  31 IndianRed
+255 160 122 LightSalmon
+227  46  48 MadderLakeDeep
+176  48  96 Maroon
+255 192 203 Pink
+255 182 193 PinkLight
+135  38  87 Raspberry
+255   0   0 Red
+227  54  56 RoseMadder
+250 128 114 Salmon
+255  99  71 Tomato
+212  26  31 VenetianRed
+163 148 128 Beige
+128  42  42 Brown
+219  41  41 BrownMadder
+135  66  31 BrownOchre
+222 184 135 Burlywood
+138  54  15 BurntSienna
+138  51  36 BurntUmber
+210 105  30 Chocolate
+115  61  26 DeepOchre
+255 125  64 Flesh
+255  87  33 FleshOchre
+199 120  38 GoldOchre
+255  61  13 GreenishUmber
+240 230 140 Khaki
+189 183 107 KhakiDark
+245 245 220 LightBeige
+205 133  63 Peru
+188 143 143 RosyBrown
+199  97  20 RawSienna
+115  74  18 RawUmber
+ 94  38  18 Sepia
+160  82  45 Sienna
+139  69  19 SaddleBrown
+244 164  96 SandyBrown
+210 180 140 Tan
+ 94  38   5 VanDykeBrown
+255  97   3 CadmiumOrange
+255   3  13 CadmiumRedLight
+237 145  33 Carrot
+255 140   0 DarkOrange
+150  69  20 MarsOrange
+227 112  26 MarsYellow
+255 128   0 Orange
+255  69   0 OrangeRed
+227 130  23 YellowOchre
+255 168  36 AureolineYellow
+227 207  87 Banana
+255 227   3 CadmiumLemon
+255 153  18 CadmiumYellow
+255 176  15 CadmiumYellowLight
+255 215   0 Gold
+218 165  32 Goldenrod
+184 134  11 GoldenrodDark
+250 250 210 GoldenrodLight
+238 232 170 GoldenrodPale
+238 221 130 LightGoldenrod
+227 168 105 Melon
+255 168  18 NaplesYellowDeep
+255 255   0 Yellow
+255 255 224 YellowLight
+127 255   0 Chartreuse
+102 128  20 ChromeOxideGreen
+ 97 179  41 CinnabarGreen
+ 61 145  64 CobaltGreen
+  0 201  87 EmeraldGreen
+ 34 139  34 ForestGreen
+  0 255   0 Green
+  0 100   0 GreenDark
+152 251 152 GreenPale
+173 255  47 GreenYellow
+124 252   0 LawnGreen
+ 50 205  50 LimeGreen
+189 252 201 Mint
+ 59  94  43 Olive
+107 142  35 OliveDrab
+ 85 107  47 OliveGreenDark
+ 10 201  43 PermanentGreen
+ 48 128  20 SapGreen
+ 46 139  87 SeaGreen
+143 188 143 SeaGreenDark
+ 60 179 113 SeaGreenMedium
+ 32 178 170 SeaGreenLight
+  0 255 127 SpringGreen
+  0 250 154 SpringGreenMedium
+ 56  94  15 TerreVerte
+110 255 112 ViridianLight
+154 205  50 YellowGreen
+127 255 212 Aquamarine
+102 205 170 AquamarineMedium
+  0 255 255 Cyan
+224 255 255 CyanWhite
+ 64 224 208 Turquoise
+  0 206 209 TurquoiseDark
+ 72 209 204 TurquoiseMedium
+175 238 238 TurquoisePale
+240 248 255 AliceBlue
+  0   0 255 Blue
+173 216 230 BlueLight
+  0   0 205 BlueMedium
+ 95 158 160 Cadet
+ 61  89 171 Cobalt
+100 149 237 Cornflower
+  5 184 204 Cerulean
+ 30 144 255 DodgerBlue
+ 46   8  84 Indigo
+  3 168 158 ManganeseBlue
+ 25  25 112 MidnightBlue
+  0   0 128 Navy
+ 51 161 201 Peacock
+176 224 230 PowderBlue
+ 65 105 225 RoyalBlue
+106  90 205 SlateBlue
+ 72  61 139 SlateBlueDark
+132 112 255 SlateBlueLight
+123 104 238 SlateBlueMedium
+135 206 235 SkyBlue
+  0 191 255 SkyBlueDeep
+135 206 250 SkyBlueLight
+ 70 130 180 SteelBlue
+176 196 222 SteelBlueLight
+  0 199 140 TurquoiseBlue
+ 18  10 143 Ultramarine
+138  43 226 BlueViolet
+145  33 158 CobaltVioletDeep
+255   0 255 Magenta
+218 112 214 Orchid
+153  50 204 OrchidDark
+186  85 211 OrchidMedium
+219  38  69 PermanentRedViolet
+221 160 221 Plum
+160  32 240 Purple
+147 112 219 PurpleMedium
+ 92  36 110 UltramarineViolet
+143  94 153 Violet
+148   0 211 VioletDark
+208  32 144 VioletRed
+199  21 133 VioletRedMedium
+219 112 147 VioletRedPale
+#
+# From HTML 4.0 specification.  These are colors you can specify by name
+# in HTML tags.
+#
+# Actually, these are the colors as if HTML's sRGB values were actually
+# Rec. 709.  E.g. HTML 4.0 says silver is r=192, g=192, b=192, in the
+# SRGB system.
+# This file, which by definition contains Rec. 709 values, shows the same
+# three numbers (192,192,192).  So this file defines a slightly different
+# color than HTML does.
+#
+# There's some extension to HTML, apparently not blessed by W3C, that adds
+# all the MIT colors (see above).  Internet Explorer can render all of them.
+
+000 000 000 Black
+192 192 192 Silver
+128 128 128 Gray
+255 255 255 White
+128 000 000 Maroon
+255 000 000 Red
+128 000 128 Purple
+255 000 255 Fuchsia
+000 128 000 Green
+000 255 000 Lime
+128 128 000 Olive
+255 255 000 Yellow
+000 000 128 Navy
+000 000 255 Blue
+000 128 128 Teal
+000 255 255 Aqua
+
+# These are from XFree86 rgb.txt ca. 2001.  We have extracted the color names
+# that aren't already included above and also converted all the names to the
+# standard form and eliminated duplicates.
+#
+# The original file started with this line:
+#
+#! $XConsortium: rgb.txt,v 10.41 94/02/20 18:39:36 rws Exp $
+112 128 144 SlateGray
+119 136 153 LightSlateGray
+119 136 153 LightSlateGrey
+211 211 211 light grey
+100 149 237 CornflowerBlue
+132 112 255 LightSlateBlue
+  0 191 255 DeepSkyBlue
+135 206 250 LightSkyBlue
+175 238 238 PaleTurquoise
+224 255 255 LightCyan
+143 188 143 DarkSeaGreen
+ 32 178 170 LightSeaGreen
+189 183 107 DarkKhaki
+238 232 170 PaleGoldenrod
+250 250 210 LightGoldenrodYellow
+255 255 224 LightYellow
+233 150 122 DarkSalmon
+240 128 128 LightCoral
+255 182 193 LightPink
+219 112 147 PaleVioletRed
+148   0 211 DarkViolet
+147 112 219 MediumPurple
+255 250 250 Snow1
+238 233 233 Snow2
+205 201 201 Snow3
+139 137 137 Snow4
+255 245 238 Seashell1
+238 229 222 Seashell2
+205 197 191 Seashell3
+139 134 130 Seashell4
+255 239 219 AntiqueWhite1
+238 223 204 AntiqueWhite2
+205 192 176 AntiqueWhite3
+139 131 120 AntiqueWhite4
+255 228 196 Bisque1
+238 213 183 Bisque2
+205 183 158 Bisque3
+139 125 107 Bisque4
+255 218 185 PeachPuff1
+238 203 173 PeachPuff2
+205 175 149 PeachPuff3
+139 119 101 PeachPuff4
+255 222 173 NavajoWhite1
+238 207 161 NavajoWhite2
+205 179 139 NavajoWhite3
+139 121	 94 NavajoWhite4
+255 250 205 LemonChiffon1
+238 233 191 LemonChiffon2
+205 201 165 LemonChiffon3
+139 137 112 LemonChiffon4
+255 248 220 Cornsilk1
+238 232 205 Cornsilk2
+205 200 177 Cornsilk3
+139 136 120 Cornsilk4
+255 255 240 Ivory1
+238 238 224 Ivory2
+205 205 193 Ivory3
+139 139 131 Ivory4
+240 255 240 Honeydew1
+224 238 224 Honeydew2
+193 205 193 Honeydew3
+131 139 131 Honeydew4
+255 240 245 LavenderBlush1
+238 224 229 LavenderBlush2
+205 193 197 LavenderBlush3
+139 131 134 LavenderBlush4
+255 228 225 MistyRose1
+238 213 210 MistyRose2
+205 183 181 MistyRose3
+139 125 123 MistyRose4
+240 255 255 Azure1
+224 238 238 Azure2
+193 205 205 Azure3
+131 139 139 Azure4
+131 111 255 SlateBlue1
+122 103 238 SlateBlue2
+105  89 205 SlateBlue3
+ 71  60 139 SlateBlue4
+ 72 118 255 RoyalBlue1
+ 67 110 238 RoyalBlue2
+ 58  95 205 RoyalBlue3
+ 39  64 139 RoyalBlue4
+  0   0 255 Blue1
+  0   0 238 Blue2
+  0   0 205 Blue3
+  0   0 139 Blue4
+ 30 144 255 DodgerBlue1
+ 28 134 238 DodgerBlue2
+ 24 116 205 DodgerBlue3
+ 16  78 139 DodgerBlue4
+ 99 184 255 SteelBlue1
+ 92 172 238 SteelBlue2
+ 79 148 205 SteelBlue3
+ 54 100 139 SteelBlue4
+  0 191 255 DeepSkyBlue1
+  0 178 238 DeepSkyBlue2
+  0 154 205 DeepSkyBlue3
+  0 104 139 DeepSkyBlue4
+135 206 255 SkyBlue1
+126 192 238 SkyBlue2
+108 166 205 SkyBlue3
+ 74 112 139 SkyBlue4
+176 226 255 LightSkyBlue1
+164 211 238 LightSkyBlue2
+141 182 205 LightSkyBlue3
+ 96 123 139 LightSkyBlue4
+198 226 255 SlateGray1
+185 211 238 SlateGray2
+159 182 205 SlateGray3
+108 123 139 SlateGray4
+202 225 255 LightSteelBlue1
+188 210 238 LightSteelBlue2
+162 181 205 LightSteelBlue3
+110 123 139 LightSteelBlue4
+191 239 255 LightBlue1
+178 223 238 LightBlue2
+154 192 205 LightBlue3
+104 131 139 LightBlue4
+224 255 255 LightCyan1
+209 238 238 LightCyan2
+180 205 205 LightCyan3
+122 139 139 LightCyan4
+187 255 255 PaleTurquoise1
+174 238 238 PaleTurquoise2
+150 205 205 PaleTurquoise3
+102 139 139 PaleTurquoise4
+152 245 255 CadetBlue1
+142 229 238 CadetBlue2
+122 197 205 CadetBlue3
+ 83 134 139 CadetBlue4
+  0 245 255 Turquoise1
+  0 229 238 Turquoise2
+  0 197 205 Turquoise3
+  0 134 139 Turquoise4
+  0 255 255 Cyan1
+  0 238 238 Cyan2
+  0 205 205 Cyan3
+  0 139 139 Cyan4
+151 255 255 DarkSlateGray1
+141 238 238 DarkSlateGray2
+121 205 205 DarkSlateGray3
+ 82 139 139 DarkSlateGray4
+127 255 212 Aquamarine1
+118 238 198 Aquamarine2
+102 205 170 Aquamarine3
+ 69 139 116 Aquamarine4
+193 255 193 DarkSeaGreen1
+180 238 180 DarkSeaGreen2
+155 205 155 DarkSeaGreen3
+105 139 105 DarkSeaGreen4
+ 84 255 159 SeaGreen1
+ 78 238 148 SeaGreen2
+ 67 205 128 SeaGreen3
+ 46 139	 87 SeaGreen4
+154 255 154 PaleGreen1
+144 238 144 PaleGreen2
+124 205 124 PaleGreen3
+ 84 139	 84 PaleGreen4
+  0 255 127 SpringGreen1
+  0 238 118 SpringGreen2
+  0 205 102 SpringGreen3
+  0 139	 69 SpringGreen4
+  0 255	  0 Green1
+  0 238	  0 Green2
+  0 205	  0 Green3
+  0 139	  0 Green4
+127 255	  0 Chartreuse1
+118 238	  0 Chartreuse2
+102 205	  0 Chartreuse3
+ 69 139	  0 Chartreuse4
+192 255	 62 OliveDrab1
+179 238	 58 OliveDrab2
+154 205	 50 OliveDrab3
+105 139	 34 OliveDrab4
+202 255 112 DarkOliveGreen1
+188 238 104 DarkOliveGreen2
+162 205	 90 DarkOliveGreen3
+110 139	 61 DarkOliveGreen4
+255 246 143 Khaki1
+238 230 133 Khaki2
+205 198 115 Khaki3
+139 134	 78 Khaki4
+255 236 139 LightGoldenrod1
+238 220 130 LightGoldenrod2
+205 190 112 LightGoldenrod3
+139 129	 76 LightGoldenrod4
+255 255 224 LightYellow1
+238 238 209 LightYellow2
+205 205 180 LightYellow3
+139 139 122 LightYellow4
+255 255	  0 Yellow1
+238 238	  0 Yellow2
+205 205	  0 Yellow3
+139 139	  0 Yellow4
+255 215	  0 Gold1
+238 201	  0 Gold2
+205 173	  0 Gold3
+139 117	  0 Gold4
+255 193	 37 Goldenrod1
+238 180	 34 Goldenrod2
+205 155	 29 Goldenrod3
+139 105	 20 Goldenrod4
+255 185	 15 DarkGoldenrod1
+238 173	 14 DarkGoldenrod2
+205 149	 12 DarkGoldenrod3
+139 101	  8 DarkGoldenrod4
+255 193 193 RosyBrown1
+238 180 180 RosyBrown2
+205 155 155 RosyBrown3
+139 105 105 RosyBrown4
+255 106 106 IndianRed1
+238  99	 99 IndianRed2
+205  85	 85 IndianRed3
+139  58	 58 IndianRed4
+255 130	 71 Sienna1
+238 121	 66 Sienna2
+205 104	 57 Sienna3
+139  71	 38 Sienna4
+255 211 155 Burlywood1
+238 197 145 Burlywood2
+205 170 125 Burlywood3
+139 115	 85 Burlywood4
+255 231 186 Wheat1
+238 216 174 Wheat2
+205 186 150 Wheat3
+139 126 102 Wheat4
+255 165	 79 Tan1
+238 154	 73 Tan2
+205 133	 63 Tan3
+139  90	 43 Tan4
+255 127	 36 Chocolate1
+238 118	 33 Chocolate2
+205 102	 29 Chocolate3
+139  69	 19 Chocolate4
+255  48	 48 Firebrick1
+238  44	 44 Firebrick2
+205  38	 38 Firebrick3
+139  26	 26 Firebrick4
+255  64	 64 Brown1
+238  59	 59 Brown2
+205  51	 51 Brown3
+139  35	 35 Brown4
+255 140 105 Salmon1
+238 130	 98 Salmon2
+205 112	 84 Salmon3
+139  76	 57 Salmon4
+255 160 122 LightSalmon1
+238 149 114 LightSalmon2
+205 129	 98 LightSalmon3
+139  87	 66 LightSalmon4
+255 165	  0 Orange1
+238 154	  0 Orange2
+205 133	  0 Orange3
+139  90	  0 Orange4
+255 127	  0 DarkOrange1
+238 118	  0 DarkOrange2
+205 102	  0 DarkOrange3
+139  69	  0 DarkOrange4
+255 114	 86 Coral1
+238 106	 80 Coral2
+205  91	 69 Coral3
+139  62	 47 Coral4
+255  99	 71 Tomato1
+238  92	 66 Tomato2
+205  79	 57 Tomato3
+139  54	 38 Tomato4
+255  69	  0 OrangeRed1
+238  64	  0 OrangeRed2
+205  55	  0 OrangeRed3
+139  37	  0 OrangeRed4
+255   0	  0 Red1
+238   0	  0 Red2
+205   0	  0 Red3
+139   0	  0 Red4
+255  20 147 DeepPink1
+238  18 137 DeepPink2
+205  16 118 DeepPink3
+139  10	 80 DeepPink4
+255 110 180 HotPink1
+238 106 167 HotPink2
+205  96 144 HotPink3
+139  58  98 HotPink4
+255 181 197 Pink1
+238 169 184 Pink2
+205 145 158 Pink3
+139  99 108 Pink4
+255 174 185 LightPink1
+238 162 173 LightPink2
+205 140 149 LightPink3
+139  95 101 LightPink4
+255 130 171 PaleVioletRed1
+238 121 159 PaleVioletRed2
+205 104 137 PaleVioletRed3
+139  71	 93 PaleVioletRed4
+255  52 179 Maroon1
+238  48 167 Maroon2
+205  41 144 Maroon3
+139  28	 98 Maroon4
+255  62 150 VioletRed1
+238  58 140 VioletRed2
+205  50 120 VioletRed3
+139  34	 82 VioletRed4
+255   0 255 Magenta1
+238   0 238 Magenta2
+205   0 205 Magenta3
+139   0 139 Magenta4
+255 131 250 Orchid1
+238 122 233 Orchid2
+205 105 201 Orchid3
+139  71 137 Orchid4
+255 187 255 Plum1
+238 174 238 Plum2
+205 150 205 Plum3
+139 102 139 Plum4
+224 102 255 MediumOrchid1
+209  95 238 MediumOrchid2
+180  82 205 MediumOrchid3
+122  55 139 MediumOrchid4
+191  62 255 DarkOrchid1
+178  58 238 DarkOrchid2
+154  50 205 DarkOrchid3
+104  34 139 DarkOrchid4
+155  48 255 Purple1
+145  44 238 Purple2
+125  38 205 Purple3
+ 85  26 139 Purple4
+171 130 255 MediumPurple1
+159 121 238 MediumPurple2
+137 104 205 MediumPurple3
+ 93  71 139 MediumPurple4
+255 225 255 Thistle1
+238 210 238 Thistle2
+205 181 205 Thistle3
+139 123 139 Thistle4
+  0   0   0 Gray0
+  0   0   0 Grey0
+  3   3   3 Gray1
+  3   3   3 Grey1
+  5   5   5 Gray2
+  5   5   5 Grey2
+  8   8   8 Gray3
+  8   8   8 Grey3
+ 10  10  10 Gray4
+ 10  10  10 Grey4
+ 13  13  13 Gray5
+ 13  13  13 Grey5
+ 15  15  15 Gray6
+ 15  15  15 Grey6
+ 18  18  18 Gray7
+ 18  18  18 Grey7
+ 20  20  20 Gray8
+ 20  20  20 Grey8
+ 23  23  23 Gray9
+ 23  23  23 Grey9
+ 26  26  26 Gray10
+ 26  26  26 Grey10
+ 28  28  28 Gray11
+ 28  28  28 Grey11
+ 31  31  31 Gray12
+ 31  31  31 Grey12
+ 33  33  33 Gray13
+ 33  33  33 Grey13
+ 36  36  36 Gray14
+ 36  36  36 Grey14
+ 38  38  38 Gray15
+ 38  38  38 Grey15
+ 41  41  41 Gray16
+ 41  41  41 Grey16
+ 43  43  43 Gray17
+ 43  43  43 Grey17
+ 46  46  46 Gray18
+ 46  46  46 Grey18
+ 48  48  48 Gray19
+ 48  48  48 Grey19
+ 51  51  51 Gray20
+ 51  51  51 Grey20
+ 54  54  54 Gray21
+ 54  54  54 Grey21
+ 56  56  56 Gray22
+ 56  56  56 Grey22
+ 59  59  59 Gray23
+ 59  59  59 Grey23
+ 61  61  61 Gray24
+ 61  61  61 Grey24
+ 64  64  64 Gray25
+ 64  64  64 Grey25
+ 66  66  66 Gray26
+ 66  66  66 Grey26
+ 69  69  69 Gray27
+ 69  69  69 Grey27
+ 71  71  71 Gray28
+ 71  71  71 Grey28
+ 74  74  74 Gray29
+ 74  74  74 Grey29
+ 77  77  77 Gray30
+ 77  77  77 Grey30
+ 79  79  79 Gray31
+ 79  79  79 Grey31
+ 82  82  82 Gray32
+ 82  82  82 Grey32
+ 84  84  84 Gray33
+ 84  84  84 Grey33
+ 87  87  87 Gray34
+ 87  87  87 Grey34
+ 89  89  89 Gray35
+ 89  89  89 Grey35
+ 92  92  92 Gray36
+ 92  92  92 Grey36
+ 94  94  94 Gray37
+ 94  94  94 Grey37
+ 97  97  97 Gray38
+ 97  97  97 Grey38
+ 99  99  99 Gray39
+ 99  99  99 Grey39
+102 102 102 Gray40
+102 102 102 Grey40
+105 105 105 Gray41
+105 105 105 Grey41
+107 107 107 Gray42
+107 107 107 Grey42
+110 110 110 Gray43
+110 110 110 Grey43
+112 112 112 Gray44
+112 112 112 Grey44
+115 115 115 Gray45
+115 115 115 Grey45
+117 117 117 Gray46
+117 117 117 Grey46
+120 120 120 Gray47
+120 120 120 Grey47
+122 122 122 Gray48
+122 122 122 Grey48
+125 125 125 Gray49
+125 125 125 Grey49
+127 127 127 Gray50
+127 127 127 Grey50
+130 130 130 Gray51
+130 130 130 Grey51
+133 133 133 Gray52
+133 133 133 Grey52
+135 135 135 Gray53
+135 135 135 Grey53
+138 138 138 Gray54
+138 138 138 Grey54
+140 140 140 Gray55
+140 140 140 Grey55
+143 143 143 Gray56
+143 143 143 Grey56
+145 145 145 Gray57
+145 145 145 Grey57
+148 148 148 Gray58
+148 148 148 Grey58
+150 150 150 Gray59
+150 150 150 Grey59
+153 153 153 Gray60
+153 153 153 Grey60
+156 156 156 Gray61
+156 156 156 Grey61
+158 158 158 Gray62
+158 158 158 Grey62
+161 161 161 Gray63
+161 161 161 Grey63
+163 163 163 Gray64
+163 163 163 Grey64
+166 166 166 Gray65
+166 166 166 Grey65
+168 168 168 Gray66
+168 168 168 Grey66
+171 171 171 Gray67
+171 171 171 Grey67
+173 173 173 Gray68
+173 173 173 Grey68
+176 176 176 Gray69
+176 176 176 Grey69
+179 179 179 Gray70
+179 179 179 Grey70
+181 181 181 Gray71
+181 181 181 Grey71
+184 184 184 Gray72
+184 184 184 Grey72
+186 186 186 Gray73
+186 186 186 Grey73
+189 189 189 Gray74
+189 189 189 Grey74
+191 191 191 Gray75
+191 191 191 Grey75
+194 194 194 Gray76
+194 194 194 Grey76
+196 196 196 Gray77
+196 196 196 Grey77
+199 199 199 Gray78
+199 199 199 Grey78
+201 201 201 Gray79
+201 201 201 Grey79
+204 204 204 Gray80
+204 204 204 Grey80
+207 207 207 Gray81
+207 207 207 Grey81
+209 209 209 Gray82
+209 209 209 Grey82
+212 212 212 Gray83
+212 212 212 Grey83
+214 214 214 Gray84
+214 214 214 Grey84
+217 217 217 Gray85
+217 217 217 Grey85
+219 219 219 Gray86
+219 219 219 Grey86
+222 222 222 Gray87
+222 222 222 Grey87
+224 224 224 Gray88
+224 224 224 Grey88
+227 227 227 Gray89
+227 227 227 Grey89
+229 229 229 Gray90
+229 229 229 Grey90
+232 232 232 Gray91
+232 232 232 Grey91
+235 235 235 Gray92
+235 235 235 Grey92
+237 237 237 Gray93
+237 237 237 Grey93
+240 240 240 Gray94
+240 240 240 Grey94
+242 242 242 Gray95
+242 242 242 Grey95
+245 245 245 Gray96
+245 245 245 Grey96
+247 247 247 Gray97
+247 247 247 Grey97
+250 250 250 Gray98
+250 250 250 Grey98
+252 252 252 Gray99
+252 252 252 Grey99
+255 255 255 Gray100
+255 255 255 Grey100
+169 169 169 DarkGrey
+169 169 169 DarkGray
+0     0 139 DarkBlue
+0   139 139 DarkCyan
+139   0 139 DarkMagenta
+139   0   0 DarkRed
+144 238 144 LightGreen
+
+
+# These were more or less invented for use with Netpbm:
+255 255 255  D65
diff --git a/lib/standard.ppmdfont b/lib/standard.ppmdfont
new file mode 100644
index 00000000..124e1d73
--- /dev/null
+++ b/lib/standard.ppmdfont
Binary files differdiff --git a/lib/standardppmdfont.c b/lib/standardppmdfont.c
new file mode 100644
index 00000000..aa4707e2
--- /dev/null
+++ b/lib/standardppmdfont.c
@@ -0,0 +1,3177 @@
+/* THIS FILE WAS GENERATED BY 'ppmdcfont' from a ppmfont file. */
+
+#include "ppmdfont.h"
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_33[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 2 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 7 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 7 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_34[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 243 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 249 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 10, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 243 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 10, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 10, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 249 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_35[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 1, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 16 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 240 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 16 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 253 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 253 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 3 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 3 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_36[26] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 254, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 13 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 2, 240 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 13 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 247 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_37[31] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 9, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 252, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 244 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 5, 2 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 2 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_38[34] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 10, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 10, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 246, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 246, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 10, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 10, 7 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_39[7] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 250 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_40[10] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 4, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 242 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 11 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 14 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_41[10] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 252, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 242 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 11 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 14 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_42[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 6 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 253 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 3 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 5, 253 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 3 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_43[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 247, 0 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 0 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_44[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 10 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 12 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 13 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_45[2] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 247, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 0 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_46[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 7 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_47[2] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 9, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_48[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 255, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 244 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_49[4] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 252, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_50[14] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_51[15] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 5 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_52[6] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 3, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 2 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 3, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_53[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 5, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 5 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_54[23] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 2 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_55[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 244 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_56[29] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_57[23] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_58[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 7 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 7 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_59[14] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 10 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 12 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 13 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_60[3] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 8, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_61[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 247, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 253 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 247, 3 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 3 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_62[3] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 248, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_63[20] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 2 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 7 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 7 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_64[55] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 5, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 1 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 255, 249 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 4 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 5, 249 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 10, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 11, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 11, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 10, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 9, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 246, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 246, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 6 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 249 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 4 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_65[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 2 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 2 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_66[23] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 254 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_67[18] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 8, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 4 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_68[15] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_69[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 254 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 9 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_70[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 254 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_71[22] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 8, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 1 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 3, 1 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 1 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_72[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 254 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_73[2] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_74[10] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 4, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 2 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_75[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 2 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 254, 253 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_76[3] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_77[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 248, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 248, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 8, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 8, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_78[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_79[21] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_80[13] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 255 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_81[24] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 1, 5 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 11 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_82[16] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 254 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_83[20] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_84[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 244 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_85[10] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 244 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_86[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 248, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 8, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_87[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 246, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 10, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_88[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_89[6] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 248, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 8, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 254 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_90[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 244 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 244 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 249, 9 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_91[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 253, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 16 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 254, 240 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 16 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 253, 240 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 240 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 253, 16 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_92[2] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 9, 16 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 247, 240 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_93[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 2, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 16 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 3, 240 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 16 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 252, 240 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 240 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 252, 16 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_94[7] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 248, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 2 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 248, 2 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 2 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_95[2] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 20, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_96[7] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 1, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 248 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_97[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_98[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_99[14] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_100[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_101[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_102[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 5, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 253, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 251 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_103[22] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 11 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 14 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 15 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 16 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 16 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 15 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_104[10] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 255 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_105[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 255, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 243 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 244 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_106[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 243 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 1, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 12 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 15 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 16 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_107[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 5, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 5 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 255, 1 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_108[2] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_109[18] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 245, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 245, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 245, 255 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 248, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 255 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 8, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 10, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 11, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 11, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_110[10] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 255 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_111[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_112[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 16 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_113[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 16 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_114[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 253, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 253, 1 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 251 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_115[17] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 6 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_116[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 253, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 251 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_117[10] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 5 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_118[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_119[11] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 248, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 8, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 4, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_120[5] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_121[9] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 250, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 13 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 252, 15 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 250, 16 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 249, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_122[8] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 251, 9 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 251 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 251 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 251, 9 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 6, 9 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_123[39] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 2, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 241 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 242 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 255 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 241 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 243 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 253, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 11 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 13 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 15 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 255, 1 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 10 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 12 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 14 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 15 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_124[2] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_125[39] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 254, 240 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 241 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 242 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 244 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 246 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 249 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 255 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 0, 241 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 243 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 245 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 247 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 248 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 250 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 4 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 6 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 9 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 11 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 13 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 15 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 1, 1 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 3 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 255, 5 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 7 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 8 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 10 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 2, 12 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 14 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 15 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 254, 16 }
+};
+
+struct ppmd_glyphCommand const ppmd_standardfont_glyphTable_cmd_126[23] = {
+  {/* glyphCommand */ CMD_MOVEPEN, 2, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 0, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 251 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 252 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 11, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 13, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 15, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 17, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 18, 254 }
+  ,
+  {/* glyphCommand */ CMD_MOVEPEN, 2, 0 }
+  ,
+  {/* glyphCommand */ CMD_NOOP, 0, 0 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 1, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 3, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 5, 253 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 7, 254 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 11, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 13, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 15, 2 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 17, 1 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 18, 255 }
+  ,
+  {/* glyphCommand */ CMD_DRAWLINE, 18, 252 }
+};
+
+struct ppmd_glyph const ppmd_standardfont_glyphTable[95] = {
+  { /* glyph */
+    { /* header */ 0, 0, 21}
+    ,
+    NULL
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 251, 5}
+    ,
+    ppmd_standardfont_glyphTable_cmd_33
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 253, 15}
+    ,
+    ppmd_standardfont_glyphTable_cmd_34
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 246, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_35
+  }
+  ,
+  { /* glyph */
+    { /* header */ 26, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_36
+  }
+  ,
+  { /* glyph */
+    { /* header */ 31, 244, 12}
+    ,
+    ppmd_standardfont_glyphTable_cmd_37
+  }
+  ,
+  { /* glyph */
+    { /* header */ 34, 243, 13}
+    ,
+    ppmd_standardfont_glyphTable_cmd_38
+  }
+  ,
+  { /* glyph */
+    { /* header */ 7, 251, 5}
+    ,
+    ppmd_standardfont_glyphTable_cmd_39
+  }
+  ,
+  { /* glyph */
+    { /* header */ 10, 249, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_40
+  }
+  ,
+  { /* glyph */
+    { /* header */ 10, 249, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_41
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 248, 8}
+    ,
+    ppmd_standardfont_glyphTable_cmd_42
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 243, 13}
+    ,
+    ppmd_standardfont_glyphTable_cmd_43
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 251, 5}
+    ,
+    ppmd_standardfont_glyphTable_cmd_44
+  }
+  ,
+  { /* glyph */
+    { /* header */ 2, 243, 13}
+    ,
+    ppmd_standardfont_glyphTable_cmd_45
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 251, 5}
+    ,
+    ppmd_standardfont_glyphTable_cmd_46
+  }
+  ,
+  { /* glyph */
+    { /* header */ 2, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_47
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_48
+  }
+  ,
+  { /* glyph */
+    { /* header */ 4, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_49
+  }
+  ,
+  { /* glyph */
+    { /* header */ 14, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_50
+  }
+  ,
+  { /* glyph */
+    { /* header */ 15, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_51
+  }
+  ,
+  { /* glyph */
+    { /* header */ 6, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_52
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_53
+  }
+  ,
+  { /* glyph */
+    { /* header */ 23, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_54
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_55
+  }
+  ,
+  { /* glyph */
+    { /* header */ 29, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_56
+  }
+  ,
+  { /* glyph */
+    { /* header */ 23, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_57
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 251, 5}
+    ,
+    ppmd_standardfont_glyphTable_cmd_58
+  }
+  ,
+  { /* glyph */
+    { /* header */ 14, 251, 5}
+    ,
+    ppmd_standardfont_glyphTable_cmd_59
+  }
+  ,
+  { /* glyph */
+    { /* header */ 3, 244, 12}
+    ,
+    ppmd_standardfont_glyphTable_cmd_60
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 243, 13}
+    ,
+    ppmd_standardfont_glyphTable_cmd_61
+  }
+  ,
+  { /* glyph */
+    { /* header */ 3, 244, 12}
+    ,
+    ppmd_standardfont_glyphTable_cmd_62
+  }
+  ,
+  { /* glyph */
+    { /* header */ 20, 247, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_63
+  }
+  ,
+  { /* glyph */
+    { /* header */ 55, 243, 14}
+    ,
+    ppmd_standardfont_glyphTable_cmd_64
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 247, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_65
+  }
+  ,
+  { /* glyph */
+    { /* header */ 23, 245, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_66
+  }
+  ,
+  { /* glyph */
+    { /* header */ 18, 246, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_67
+  }
+  ,
+  { /* glyph */
+    { /* header */ 15, 245, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_68
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 246, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_69
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 246, 8}
+    ,
+    ppmd_standardfont_glyphTable_cmd_70
+  }
+  ,
+  { /* glyph */
+    { /* header */ 22, 246, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_71
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_72
+  }
+  ,
+  { /* glyph */
+    { /* header */ 2, 252, 4}
+    ,
+    ppmd_standardfont_glyphTable_cmd_73
+  }
+  ,
+  { /* glyph */
+    { /* header */ 10, 248, 8}
+    ,
+    ppmd_standardfont_glyphTable_cmd_74
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 245, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_75
+  }
+  ,
+  { /* glyph */
+    { /* header */ 3, 246, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_76
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 244, 12}
+    ,
+    ppmd_standardfont_glyphTable_cmd_77
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_78
+  }
+  ,
+  { /* glyph */
+    { /* header */ 21, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_79
+  }
+  ,
+  { /* glyph */
+    { /* header */ 13, 245, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_80
+  }
+  ,
+  { /* glyph */
+    { /* header */ 24, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_81
+  }
+  ,
+  { /* glyph */
+    { /* header */ 16, 245, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_82
+  }
+  ,
+  { /* glyph */
+    { /* header */ 20, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_83
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 248, 8}
+    ,
+    ppmd_standardfont_glyphTable_cmd_84
+  }
+  ,
+  { /* glyph */
+    { /* header */ 10, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_85
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 247, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_86
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 244, 12}
+    ,
+    ppmd_standardfont_glyphTable_cmd_87
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_88
+  }
+  ,
+  { /* glyph */
+    { /* header */ 6, 247, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_89
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 246, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_90
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 249, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_91
+  }
+  ,
+  { /* glyph */
+    { /* header */ 2, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_92
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 249, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_93
+  }
+  ,
+  { /* glyph */
+    { /* header */ 7, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_94
+  }
+  ,
+  { /* glyph */
+    { /* header */ 2, 253, 22}
+    ,
+    ppmd_standardfont_glyphTable_cmd_95
+  }
+  ,
+  { /* glyph */
+    { /* header */ 7, 251, 5}
+    ,
+    ppmd_standardfont_glyphTable_cmd_96
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 247, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_97
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 246, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_98
+  }
+  ,
+  { /* glyph */
+    { /* header */ 14, 247, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_99
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 247, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_100
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 247, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_101
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 251, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_102
+  }
+  ,
+  { /* glyph */
+    { /* header */ 22, 247, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_103
+  }
+  ,
+  { /* glyph */
+    { /* header */ 10, 247, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_104
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 252, 4}
+    ,
+    ppmd_standardfont_glyphTable_cmd_105
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 251, 5}
+    ,
+    ppmd_standardfont_glyphTable_cmd_106
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 247, 8}
+    ,
+    ppmd_standardfont_glyphTable_cmd_107
+  }
+  ,
+  { /* glyph */
+    { /* header */ 2, 252, 4}
+    ,
+    ppmd_standardfont_glyphTable_cmd_108
+  }
+  ,
+  { /* glyph */
+    { /* header */ 18, 241, 15}
+    ,
+    ppmd_standardfont_glyphTable_cmd_109
+  }
+  ,
+  { /* glyph */
+    { /* header */ 10, 247, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_110
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 247, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_111
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 246, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_112
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 247, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_113
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 249, 6}
+    ,
+    ppmd_standardfont_glyphTable_cmd_114
+  }
+  ,
+  { /* glyph */
+    { /* header */ 17, 248, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_115
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 251, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_116
+  }
+  ,
+  { /* glyph */
+    { /* header */ 10, 247, 10}
+    ,
+    ppmd_standardfont_glyphTable_cmd_117
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 248, 8}
+    ,
+    ppmd_standardfont_glyphTable_cmd_118
+  }
+  ,
+  { /* glyph */
+    { /* header */ 11, 245, 11}
+    ,
+    ppmd_standardfont_glyphTable_cmd_119
+  }
+  ,
+  { /* glyph */
+    { /* header */ 5, 248, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_120
+  }
+  ,
+  { /* glyph */
+    { /* header */ 9, 248, 8}
+    ,
+    ppmd_standardfont_glyphTable_cmd_121
+  }
+  ,
+  { /* glyph */
+    { /* header */ 8, 248, 9}
+    ,
+    ppmd_standardfont_glyphTable_cmd_122
+  }
+  ,
+  { /* glyph */
+    { /* header */ 39, 249, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_123
+  }
+  ,
+  { /* glyph */
+    { /* header */ 2, 252, 4}
+    ,
+    ppmd_standardfont_glyphTable_cmd_124
+  }
+  ,
+  { /* glyph */
+    { /* header */ 39, 249, 7}
+    ,
+    ppmd_standardfont_glyphTable_cmd_125
+  }
+  ,
+  { /* glyph */
+    { /* header */ 23, 255, 21}
+    ,
+    ppmd_standardfont_glyphTable_cmd_126
+  }
+};
+
+
+struct ppmd_font const ppmd_standardfont = {
+  {/* .header */
+    {'p','p','m','d','f','o','n','t'},
+    0x01,
+    95,
+    32
+  }
+  ,
+  /* .glyphTable: */ ppmd_standardfont_glyphTable
+};
diff --git a/lib/util/LICENSE.txt b/lib/util/LICENSE.txt
new file mode 100644
index 00000000..aeb06a7f
--- /dev/null
+++ b/lib/util/LICENSE.txt
@@ -0,0 +1,121 @@
+The Frontier Artistic License Version 1.0
+Derived from the Artistic License at OpenSource.org.
+Submitted to OpenSource.org for Open Source Initiative certification.
+   
+Preamble
+
+The intent of this document is to state the conditions under which a
+Package may be copied, such that the Copyright Holder maintains some
+semblance of artistic control over the development of the package,
+while giving the users of the package the right to use and distribute
+the Package in a more-or-less customary fashion, plus the right to
+make reasonable modifications.
+   
+Definitions
+
+  "Package" refers to the script, suite, file, or collection of
+  scripts, suites, and/or files distributed by the Copyright Holder,
+  and to derivatives of that Package created through textual modification.
+
+  "Standard Version" refers to such a Package if it has not been
+  modified, or has been modified in accordance with the wishes of
+  the Copyright Holder.
+
+  "Copyright Holder" is whoever is named in the copyright statement
+  or statements for the package.
+
+  "You" is you, if you're thinking about copying or distributing
+  this Package.
+
+  "Reasonable copying fee" is whatever you can justify on the basis
+  of media cost, duplication charges, time of people involved, and
+  so on. (You will not be required to justify it to the Copyright
+  Holder, but only to the computing community at large as a market
+  that must bear the fee.)
+
+  "Freely Available" means that no fee is charged for the item
+  itself, though there may be fees involved in handling the item.
+  It also means that recipients of the item may redistribute it under
+  the same conditions they received it.
+       
+
+Terms
+
+1. You may make and give away verbatim copies of the source form of
+the Standard Version of this Package without restriction, provided
+that you duplicate all of the original copyright notices and
+associated disclaimers.
+   
+2. You may apply bug fixes, portability fixes, and other modifications
+derived from the Public Domain or from the Copyright Holder. A Package
+modified in such a way shall still be considered the Standard Version.
+   
+3. You may otherwise modify your copy of this Package in any way,
+provided that you insert a prominent notice in each changed script,
+suite, or file stating how and when you changed that script, suite,
+or file, and provided that you do at least ONE of the following:
+   
+  a) Use the modified Package only within your corporation or
+  organization, or retain the modified Package solely for personal use.
+     
+  b) Place your modifications in the Public Domain or otherwise make
+  them Freely Available, such as by posting said modifications to Usenet
+  or an equivalent medium, or placing the modifications on a major archive
+  site such as ftp.uu.net, or by allowing the Copyright Holder to include
+  your modifications in the Standard Version of the Package.
+     
+  c) Rename any non-standard executables so the names do not conflict
+  with standard executables, which must also be provided, and provide
+  a separate manual page (or equivalent) for each non-standard executable
+  that clearly documents how it differs from the Standard Version.
+     
+  d) Make other distribution arrangements with the Copyright Holder.
+     
+4. You may distribute the programs of this Package in object code or
+executable form, provided that you do at least ONE of the following:
+   
+  a) Distribute a Standard Version of the executables and library
+  files, together with instructions (in the manual page or
+  equivalent) on where to get the Standard Version.
+     
+  b) Accompany the distribution with the machine-readable source of
+  the Package with your modifications.
+     
+  c) Accompany any non-standard executables with their corresponding
+  Standard Version executables, give the non-standard executables
+  non-standard names, and clearly document the differences in manual
+  pages (or equivalent), together with instructions on where to get
+  the Standard Version.
+     
+  d) Make other distribution arrangements with the Copyright Holder.
+     
+5. You may charge a reasonable copying fee for any distribution of
+this Package. You may charge any fee you choose for support of this
+Package. You may not charge a fee for this Package itself. However,
+you may distribute this Package in aggregate with other (possibly
+commercial) programs as part of a larger (possibly commercial)
+software distribution provided that you do not advertise this Package
+as a product of your own.
+   
+6. The scripts and library files supplied as input to or produced as
+output from the programs of this Package do not automatically fall
+under the copyright of this Package, but belong to whomever generated
+them, and may be sold commercially, and may be aggregated with this
+Package.
+   
+7. Scripts, suites, or programs supplied by you that depend on or
+otherwise make use of this Package shall not be considered part of
+this Package.
+   
+8. The name of the Copyright Holder may not be used to endorse or
+promote products derived from this software without specific prior
+written permission.
+   
+9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+   
+                        The End
+
+
+http://www.spinwardstars.com/frontier/fal.html
diff --git a/lib/util/Makefile b/lib/util/Makefile
new file mode 100644
index 00000000..8f461f28
--- /dev/null
+++ b/lib/util/Makefile
@@ -0,0 +1,29 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = lib/util
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+INCLUDES = -I $(BUILDDIR) -I $(SRCDIR)/$(SUBDIR)/..
+
+# nstring is required for asprintf(), etc.  Also some systems don't have
+# snprintf(), e.g. Solaris 2.5.1.  2002.03.29.
+UTILOBJECTS = shhopt.o nstring.o filename.o
+
+MERGE_OBJECTS =
+
+all: $(UTILOBJECTS)
+
+include $(SRCDIR)/Makefile.common
+
+$(UTILOBJECTS):%.o:%.c importinc
+	$(CC) -c $(INCLUDES) -DNDEBUG $(CFLAGS) $(CFLAGS_SHLIB) \
+	  $(CFLAGS_PERSONAL) $(CADD) -o $@ $<
+
+testnstring: test.c nstring.h nstring.o
+	$(CC) $(CFLAGS) $(CADD) -o $@ nstring.o $<
+
+include Makefile.depend
diff --git a/lib/util/bitreverse.h b/lib/util/bitreverse.h
new file mode 100644
index 00000000..b3f0ea13
--- /dev/null
+++ b/lib/util/bitreverse.h
@@ -0,0 +1,46 @@
+/*
+** bitreverse.h
+**
+** This particular array seems to be useful in a lot of bitmap
+** conversion programs.  It's not used with standard libnetpbm PBM
+** processing because bits are stored one per byte (or per word), for
+** easier manipulation.  But if you wanted to write, for example, a
+** program to directly convert Sun raster format into X bitmaps, you
+** could use this.  It's also useful for the "packed PBM" libnetpbm
+** functions.
+**
+** Of course, you could also use this fairly slick chunk of code:
+**
+**     c = ((c >>  1) & 0x55) | ((c <<  1) & 0xaa);
+**     c = ((c >>  2) & 0x33) | ((c <<  2) & 0xcc);
+**     c = ((c >> 4) & 0x0f) | ((c << 4) & 0xf0); 
+*/
+
+#ifndef _BITR_H_
+#define _BITR_H_
+
+static unsigned char bitreverse[256] = {
+    0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0,
+    0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+    0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 0x84, 0x44, 0xc4,
+    0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+    0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc,
+    0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+    0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca,
+    0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+    0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6,
+    0x36, 0xb6, 0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+    0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1,
+    0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+    0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9,
+    0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+    0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 0x0d, 0x8d, 0x4d, 0xcd,
+    0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+    0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3,
+    0x33, 0xb3, 0x73, 0xf3, 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+    0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7,
+    0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+    0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf,
+    0x3f, 0xbf, 0x7f, 0xff};
+
+#endif /*_BITR_H_*/
diff --git a/lib/util/filename.c b/lib/util/filename.c
new file mode 100644
index 00000000..e3a9a89f
--- /dev/null
+++ b/lib/util/filename.c
@@ -0,0 +1,26 @@
+#include "nstring.h"
+
+#include "filename.h"
+
+const char *
+pm_basename(const char * const fileName) {
+/*----------------------------------------------------------------------------
+   Return the filename portion of a file name, e.g. "foo.ppm" from
+   "/home/bryanh/foo.ppm".
+
+   Return it as a malloc'ed string.
+-----------------------------------------------------------------------------*/
+    unsigned int basenameStart;
+    unsigned int i;
+    const char * retval;
+
+    basenameStart = 0;  /* initial assumption */
+
+    for (i = 0; fileName[i]; ++i) {
+        if (fileName[i] == '/')
+            basenameStart = i+1;
+    }
+    asprintfN(&retval, "%s", &fileName[basenameStart]);
+
+    return retval;
+}
diff --git a/lib/util/filename.h b/lib/util/filename.h
new file mode 100644
index 00000000..f598fec1
--- /dev/null
+++ b/lib/util/filename.h
@@ -0,0 +1,7 @@
+#ifndef FILENAME_H_INCLUDED
+#define FILENAME_H_INCLUDED
+
+const char *
+pm_basename(const char * const fileName);
+
+#endif
diff --git a/lib/util/intcode.h b/lib/util/intcode.h
new file mode 100644
index 00000000..4d9c83aa
--- /dev/null
+++ b/lib/util/intcode.h
@@ -0,0 +1,86 @@
+#ifndef INTCODE_H_INCLUDED
+#define INTCODE_H_INCLUDED
+
+#include "pm_config.h"  /* For uint32_t, BYTE_ORDER */
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   This is a big-endian representation of a 32 bit integer.  I.e.
+   bytes[0] is the most significant 8 bits; bytes[3] is the least
+   significant 8 bits of the number in pure binary.
+
+   On a big-endian machines, this is bit for bit identical to uint32_t.
+   On a little-endian machine, it isn't.
+
+   This is an important data type because decent file formats use
+   big-endian -- they don't care if some CPU happens to use some other
+   code for its own work.
+-----------------------------------------------------------------------------*/
+    unsigned char bytes[4];
+} bigend32;
+
+
+unsigned int const pm_byteOrder = BYTE_ORDER;
+
+
+static __inline__ uint32_t
+pm_uintFromBigend32(bigend32 const arg) {
+
+    uint32_t retval;
+
+    switch (pm_byteOrder) {
+    case BIG_ENDIAN: {
+        union {
+            bigend32 bigend;
+            uint32_t native;
+        } converter;
+        
+        converter.bigend = arg;
+        
+        retval = converter.native;
+    }; break;
+    case LITTLE_ENDIAN: {
+        retval =
+            (arg.bytes[0] << 24) |
+            (arg.bytes[1] << 16) |
+            (arg.bytes[2] <<  8) |
+            (arg.bytes[3] <<  0);
+    } break;
+    }
+    return retval;
+}
+
+
+
+static __inline__ bigend32
+pm_bigendFromUint32(uint32_t const arg) {
+
+    bigend32 retval;
+
+    switch (pm_byteOrder) {
+    case BIG_ENDIAN: {
+        union {
+            bigend32 bigend;
+            uint32_t native;
+        } converter;
+        
+        converter.native = arg;
+
+        retval = converter.bigend;
+    } break;
+    case LITTLE_ENDIAN: {
+        uint32_t shift;
+        shift = arg;
+        retval.bytes[3] = shift;  /* Takes lower 8 bits */
+        shift >>= 8;
+        retval.bytes[2] = shift;  /* Takes lower 8 bits */
+        shift >>= 8;
+        retval.bytes[1] = shift;  /* Takes lower 8 bits */
+        shift >>= 8;
+        retval.bytes[0] = shift;  /* Takes lower 8 bits */
+    } break;
+    }
+    return retval;
+}
+
+#endif
diff --git a/lib/util/lexheader b/lib/util/lexheader
new file mode 100644
index 00000000..c5691660
--- /dev/null
+++ b/lib/util/lexheader
@@ -0,0 +1,9 @@
+/* This is the file 'lexheader'.  It contains extra stuff needed by
+   parsers generated by lex.
+
+   GNU Flex generates a parser that refers to the non-ansi C library 
+   subroutine fileno().  In order to let it compile without warnings,
+   we define _XOPEN_SOURCE to declare that fact.
+*/
+#define _XOPEN_SOURCE
+/* END OF lexheader */
diff --git a/lib/util/mallocvar.h b/lib/util/mallocvar.h
new file mode 100644
index 00000000..a26d007b
--- /dev/null
+++ b/lib/util/mallocvar.h
@@ -0,0 +1,107 @@
+/* These are some dynamic memory allocation facilities.  They are essentially
+   an extension to C, as they do allocations with a cognizance of C 
+   variables.  You can use them to make C read more like a high level
+   language.
+
+   Before including this, you must define an __inline__ macro if your
+   compiler doesn't recognize it as a keyword.
+*/
+
+#ifndef MALLOCVAR_INCLUDED
+#define MALLOCVAR_INCLUDED
+
+#include "pm_config.h"
+
+#include <limits.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+static __inline__ void
+mallocProduct(void **      const resultP, 
+              unsigned int const factor1,
+              unsigned int const factor2) {
+/*----------------------------------------------------------------------------
+   malloc a space whose size in bytes is the product of 'factor1' and
+   'factor2'.  But if that size cannot be represented as an unsigned int,
+   return NULL without allocating anything.  Also return NULL if the malloc
+   fails.
+
+   If either factor is zero, malloc a single byte.
+
+   Note that malloc() actually takes a size_t size argument, so the
+   proper test would be whether the size can be represented by size_t,
+   not unsigned int.  But there is no reliable indication available to
+   us, like UINT_MAX, of what the limitations of size_t are.  We
+   assume size_t is at least as expressive as unsigned int and that
+   nobody really needs to allocate more than 4GB of memory.
+-----------------------------------------------------------------------------*/
+    if (factor1 == 0 || factor2 == 0)
+        *resultP = malloc(1);
+    else {
+        if (UINT_MAX / factor2 < factor1) 
+            *resultP = NULL;
+        else 
+            *resultP = malloc(factor1 * factor2); 
+    }
+}
+
+
+
+static __inline__ void
+reallocProduct(void **      const blockP,
+               unsigned int const factor1,
+               unsigned int const factor2) {
+    
+    if (UINT_MAX / factor2 < factor1) 
+        *blockP = NULL;
+    else 
+        *blockP = realloc(*blockP, factor1 * factor2); 
+}
+
+
+
+#define MALLOCARRAY(arrayName, nElements) do { \
+    void * array; \
+    mallocProduct(&array, nElements, sizeof(arrayName[0])); \
+    arrayName = array; \
+} while (0)
+
+#define REALLOCARRAY(arrayName, nElements) { \
+    void * array; \
+    array = arrayName; \
+    reallocProduct(&array, nElements, sizeof(arrayName[0])); \
+    arrayName = array; \
+} while (0)
+
+
+#define MALLOCARRAY_NOFAIL(arrayName, nElements) \
+do { \
+    MALLOCARRAY(arrayName, nElements); \
+    if ((arrayName) == NULL) \
+        abort(); \
+} while(0)
+
+#define REALLOCARRAY_NOFAIL(arrayName, nElements) \
+do { \
+    REALLOCARRAY(arrayName, nElements); \
+    if ((arrayName) == NULL) \
+        abort(); \
+} while(0)
+
+
+#define MALLOCVAR(varName) \
+    varName = malloc(sizeof(*varName))
+
+#define MALLOCVAR_NOFAIL(varName) \
+    do {if ((varName = malloc(sizeof(*varName))) == NULL) abort();} while(0)
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/lib/util/nstring.c b/lib/util/nstring.c
new file mode 100644
index 00000000..702a3c44
--- /dev/null
+++ b/lib/util/nstring.c
@@ -0,0 +1,906 @@
+/*
+ * snprintf.c - a portable implementation of snprintf
+ *
+
+   THIS MODULE WAS ADAPTED FOR NETPBM BY BRYAN HENDERSON ON 2002.03.24.
+   Bryan got the base from 
+   http://www.ijs.si/software/snprintf/snprintf-2.2.tar.gz, but made
+   a lot of changes and additions.
+
+ * AUTHOR
+ *   Mark Martinec <mark.martinec@ijs.si>, April 1999.
+ *
+ *   Copyright 1999, Mark Martinec. All rights reserved.
+ *
+ * TERMS AND CONDITIONS
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the "Frontier Artistic License" which comes
+ *   with this Kit.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty
+ *   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *   See the Frontier Artistic License for more details.
+ *
+ *   You should have received a copy of the Frontier Artistic License
+ *   with this Kit in the file named LICENSE.txt .
+ *   If not, I'll be glad to provide one.
+ *
+ * FEATURES
+ * - careful adherence to specs regarding flags, field width and precision;
+ * - good performance for large string handling (large format, large
+ *   argument or large paddings). Performance is similar to system's sprintf
+ *   and in several cases significantly better (make sure you compile with
+ *   optimizations turned on, tell the compiler the code is strict ANSI
+ *   if necessary to give it more freedom for optimizations);
+ * - return value semantics per ISO/IEC 9899:1999 ("ISO C99");
+ * - written in standard ISO/ANSI C - requires an ANSI C compiler.
+ *
+ * IMPLEMENTED CONVERSION SPECIFIERS AND DATA TYPES
+ *
+ * This snprintf implements only the following conversion specifiers:
+ * s, c, d, u, o, x, X, p  (and synonyms: i, D, U, O - see below)
+ * with flags: '-', '+', ' ', '0' and '#'.
+ * An asterisk is acceptable for field width as well as precision.
+ *
+ * Length modifiers 'h' (short int), 'l' (long int),
+ * and 'll' (long long int) are implemented.
+ *
+ * Conversion of numeric data (conversion specifiers d, u, o, x, X, p)
+ * with length modifiers (none or h, l, ll) is left to the system routine
+ * sprintf, but all handling of flags, field width and precision as well as
+ * c and s conversions is done very carefully by this portable routine.
+ * If a string precision (truncation) is specified (e.g. %.8s) it is
+ * guaranteed the string beyond the specified precision will not be referenced.
+ *
+ * Length modifiers h, l and ll are ignored for c and s conversions (you
+ * can't use data types wint_t and wchar_t).
+ *
+ * The following common synonyms for conversion characters are acceptable:
+ *   - i is a synonym for d
+ *   - D is a synonym for ld, explicit length modifiers are ignored
+ *   - U is a synonym for lu, explicit length modifiers are ignored
+ *   - O is a synonym for lo, explicit length modifiers are ignored
+ * The D, O and U conversion characters are nonstandard, they are accepted
+ * for backward compatibility only, and should not be used for new code.
+ *
+ * The following is specifically NOT implemented:
+ *   - flag ' (thousands' grouping character) is recognized but ignored
+ *   - numeric conversion specifiers: f, e, E, g, G and synonym F,
+ *     as well as the new a and A conversion specifiers
+ *   - length modifier 'L' (long double) and 'q' (quad - use 'll' instead)
+ *   - wide character/string conversions: lc, ls, and nonstandard
+ *     synonyms C and S
+ *   - writeback of converted string length: conversion character n
+ *   - the n$ specification for direct reference to n-th argument
+ *   - locales
+ *
+ * It is permitted for str_m to be zero, and it is permitted to specify NULL
+ * pointer for resulting string argument if str_m is zero (as per ISO C99).
+ *
+ * The return value is the number of characters which would be generated
+ * for the given input, excluding the trailing null. If this value
+ * is greater or equal to str_m, not all characters from the result
+ * have been stored in str, output bytes beyond the (str_m-1) -th character
+ * are discarded. If str_m is greater than zero it is guaranteed
+ * the resulting string will be null-terminated.
+ *
+ * NOTE that this matches the ISO C99, OpenBSD, and GNU C library 2.1,
+ * but is different from some older and vendor implementations,
+ * and is also different from XPG, XSH5, SUSv2 specifications.
+ * For historical discussion on changes in the semantics and standards
+ * of snprintf see printf(3) man page in the Linux programmers manual.
+ *
+ * Routines asprintf and vasprintf return a pointer (in the ptr argument)
+ * to a buffer sufficiently large to hold the resulting string. This pointer
+ * should be passed to free(3) to release the allocated storage when it is
+ * no longer needed. If sufficient space cannot be allocated, these functions
+ * will return -1 and set ptr to be a NULL pointer. These two routines are a
+ * GNU C library extensions (glibc).
+ *
+ * Routines asnprintf and vasnprintf are similar to asprintf and vasprintf,
+ * yet, like snprintf and vsnprintf counterparts, will write at most str_m-1
+ * characters into the allocated output string, the last character in the
+ * allocated buffer then gets the terminating null. If the formatted string
+ * length (the return value) is greater than or equal to the str_m argument,
+ * the resulting string was truncated and some of the formatted characters
+ * were discarded. These routines present a handy way to limit the amount
+ * of allocated memory to some sane value.
+ *
+ * AVAILABILITY
+ *   http://www.ijs.si/software/snprintf/
+ *
+ */
+
+
+#define PORTABLE_SNPRINTF_VERSION_MAJOR 2
+#define PORTABLE_SNPRINTF_VERSION_MINOR 2
+
+#include <sys/types.h>
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <errno.h>
+
+#include "pm.h"
+#include "pm_c_util.h"
+
+#include "nstring.h"
+
+#ifdef isdigit
+#undef isdigit
+#endif
+#define isdigit(c) ((c) >= '0' && (c) <= '9')
+
+/* For copying strings longer or equal to 'breakeven_point'
+ * it is more efficient to call memcpy() than to do it inline.
+ * The value depends mostly on the processor architecture,
+ * but also on the compiler and its optimization capabilities.
+ * The value is not critical, some small value greater than zero
+ * will be just fine if you don't care to squeeze every drop
+ * of performance out of the code.
+ *
+ * Small values favor memcpy, large values favor inline code.
+ */
+#if defined(__alpha__) || defined(__alpha)
+#  define breakeven_point   2	/* AXP (DEC Alpha)     - gcc or cc or egcs */
+#endif
+#if defined(__i386__)  || defined(__i386)
+#  define breakeven_point  12	/* Intel Pentium/Linux - gcc 2.96 */
+#endif
+#if defined(__hppa)
+#  define breakeven_point  10	/* HP-PA               - gcc */
+#endif
+#if defined(__sparc__) || defined(__sparc)
+#  define breakeven_point  33	/* Sun Sparc 5         - gcc 2.8.1 */
+#endif
+
+/* some other values of possible interest: */
+/* #define breakeven_point  8 */  /* VAX 4000          - vaxc */
+/* #define breakeven_point 19 */  /* VAX 4000          - gcc 2.7.0 */
+
+#ifndef breakeven_point
+#  define breakeven_point   6	/* some reasonable one-size-fits-all value */
+#endif
+
+#define fast_memcpy(d,s,n) \
+  { register size_t nn = (size_t)(n); \
+    if (nn >= breakeven_point) memcpy((d), (s), nn); \
+    else if (nn > 0) { /* proc call overhead is worth only for large strings*/\
+      register char *dd; register const char *ss; \
+      for (ss=(s), dd=(d); nn>0; nn--) *dd++ = *ss++; } }
+
+#define fast_memset(d,c,n) \
+  { register size_t nn = (size_t)(n); \
+    if (nn >= breakeven_point) memset((d), (int)(c), nn); \
+    else if (nn > 0) { /* proc call overhead is worth only for large strings*/\
+      register char *dd; register const int cc=(int)(c); \
+      for (dd=(d); nn>0; nn--) *dd++ = cc; } }
+
+/* declarations */
+
+static char credits[] = "\n\
+@(#)snprintf.c, v2.2: Mark Martinec, <mark.martinec@ijs.si>\n\
+@(#)snprintf.c, v2.2: Copyright 1999, Mark Martinec. Frontier Artistic License applies.\n\
+@(#)snprintf.c, v2.2: http://www.ijs.si/software/snprintf/\n";
+
+
+
+void
+vsnprintfN(char *       const str,
+           size_t       const str_m,
+           const char * const fmt,
+           va_list            ap,
+           size_t *     const sizeP) {
+
+    size_t str_l = 0;
+    const char *p = fmt;
+
+    /* In contrast with POSIX, the ISO C99 now says that str can be
+       NULL and str_m can be 0.  This is more useful than the old:
+       if (str_m < 1) return -1;
+    */
+
+    if (!p) p = "";
+    while (*p) {
+        if (*p != '%') {
+            /* if (str_l < str_m) str[str_l++] = *p++; -- this would
+               be sufficient but the following code achieves better
+               performance for cases * where format string is long and
+               contains few conversions
+            */
+            const char *q = strchr(p + 1,'%');
+            size_t n = !q ? strlen(p) : (q - p);
+            if (str_l < str_m) {
+                size_t avail = str_m - str_l;
+                fast_memcpy(str + str_l, p, (n > avail ? avail : n));
+            }
+            p += n; str_l += n;
+        } else {
+            const char *starting_p;
+            size_t min_field_width = 0, precision = 0;
+            int zero_padding = 0, precision_specified = 0, justify_left = 0;
+            int alternate_form = 0, force_sign = 0;
+            int space_for_positive = 1;
+                /* If both the ' ' and '+' flags appear,
+                   the ' ' flag should be ignored.
+                */
+            char length_modifier = '\0';  /* allowed values: \0, h, l, L */
+            char tmp[32];
+                /* temporary buffer for simple numeric->string conversion */
+
+            const char *str_arg;
+                /* string address in case of string argument */
+            size_t str_arg_l;
+                /* natural field width of arg without padding and sign */
+            unsigned char uchar_arg;
+                /* unsigned char argument value - only defined for c
+                   conversion.  N.B. standard explicitly states the char
+                   argument for the c conversion is unsigned.
+                */
+
+            size_t number_of_zeros_to_pad = 0;
+                /* number of zeros to be inserted for numeric
+                   conversions as required by the precision or minimal
+                   field width
+                */
+
+            size_t zero_padding_insertion_ind = 0;
+                /* index into tmp where zero padding is to be inserted */
+
+            char fmt_spec = '\0';
+                /* current conversion specifier character */
+
+            str_arg = credits;
+                /* just to make compiler happy (defined but not used) */
+            str_arg = NULL;
+            starting_p = p;
+            ++p;  /* skip '%' */
+
+            /* parse flags */
+            while (*p == '0' || *p == '-' || *p == '+' ||
+                   *p == ' ' || *p == '#' || *p == '\'') {
+                switch (*p) {
+                case '0': zero_padding = 1; break;
+                case '-': justify_left = 1; break;
+                case '+': force_sign = 1; space_for_positive = 0; break;
+                case ' ': force_sign = 1; break;
+                    /* If both the ' ' and '+' flags appear, the ' '
+                       flag should be ignored
+                    */
+                case '#': alternate_form = 1; break;
+                case '\'': break;
+                }
+                ++p;
+            }
+            /* If the '0' and '-' flags both appear, the '0' flag
+               should be ignored.
+            */
+
+            /* parse field width */
+            if (*p == '*') {
+                int j;
+                p++; j = va_arg(ap, int);
+                if (j >= 0) min_field_width = j;
+                else { min_field_width = -j; justify_left = 1; }
+            } else if (isdigit((int)(*p))) {
+                /* size_t could be wider than unsigned int; make sure
+                   we treat argument like common implementations do
+                */
+                unsigned int uj = *p++ - '0';
+                while (isdigit((int)(*p)))
+                    uj = 10*uj + (unsigned int)(*p++ - '0');
+                min_field_width = uj;
+            }
+            /* parse precision */
+            if (*p == '.') {
+                p++; precision_specified = 1;
+                if (*p == '*') {
+                    int j = va_arg(ap, int);
+                    p++;
+                    if (j >= 0) precision = j;
+                    else {
+                        precision_specified = 0; precision = 0;
+                        /* NOTE: Solaris 2.6 man page claims that in
+                           this case the precision should be set to 0.
+                           Digital Unix 4.0, HPUX 10 and BSD man page
+                           claim that this case should be treated as
+                           unspecified precision, which is what we do
+                           here.
+                        */
+                    }
+                } else if (isdigit((int)(*p))) {
+                    /* size_t could be wider than unsigned int; make
+                       sure we treat argument like common
+                       implementations do
+                    */
+                    unsigned int uj = *p++ - '0';
+                    while (isdigit((int)(*p)))
+                        uj = 10*uj + (unsigned int)(*p++ - '0');
+                    precision = uj;
+                }
+            }
+            /* parse 'h', 'l' and 'll' length modifiers */
+            if (*p == 'h' || *p == 'l') {
+                length_modifier = *p; p++;
+                if (length_modifier == 'l' && *p == 'l') {
+                    /* double l = long long */
+                    length_modifier = 'l';  /* treat it as a single 'l' */
+                    p++;
+                }
+            }
+            fmt_spec = *p;
+
+            /* common synonyms: */
+            switch (fmt_spec) {
+            case 'i': fmt_spec = 'd'; break;
+            case 'D': fmt_spec = 'd'; length_modifier = 'l'; break;
+            case 'U': fmt_spec = 'u'; length_modifier = 'l'; break;
+            case 'O': fmt_spec = 'o'; length_modifier = 'l'; break;
+            default: break;
+            }
+            /* get parameter value, do initial processing */
+            switch (fmt_spec) {
+            case '%':
+                /* % behaves similar to 's' regarding flags and field widths */
+            case 'c':
+                /* c behaves similar to 's' regarding flags and field widths */
+            case 's':
+                /* wint_t and wchar_t not handled */
+                length_modifier = '\0';
+                /* the result of zero padding flag with non-numeric
+                    conversion specifier is undefined. Solaris and
+                    HPUX 10 does zero padding in this case, Digital
+                    Unix and Linux does not.
+                */
+
+                zero_padding = 0;
+                    /* turn zero padding off for string conversions */
+                str_arg_l = 1;
+                switch (fmt_spec) {
+                case '%':
+                    str_arg = p; break;
+                case 'c': {
+                    int j = va_arg(ap, int);
+                    uchar_arg = (unsigned char) j;
+                        /* standard demands unsigned char */
+                    str_arg = (const char *) &uchar_arg;
+                    break;
+                }
+                case 's':
+                    str_arg = va_arg(ap, const char *);
+                    if (!str_arg)
+                        /* make sure not to address string beyond the
+                           specified precision !!!
+                        */
+                        str_arg_l = 0;
+                    else if (!precision_specified)
+                        /* truncate string if necessary as requested by
+                           precision 
+                        */
+                        str_arg_l = strlen(str_arg);
+                    else if (precision == 0)
+                        str_arg_l = 0;
+                    else {
+                        /* memchr on HP does not like n > 2^31  !!! */
+                        const char * q =
+                            memchr(str_arg, '\0',
+                                   precision <= 0x7fffffff ?
+                                   precision : 0x7fffffff);
+                        str_arg_l = !q ? precision : (q-str_arg);
+                    }
+                    break;
+                default: break;
+                }
+                break;
+            case 'd': case 'u': case 'o': case 'x': case 'X': case 'p': {
+                /* NOTE: the u, o, x, X and p conversion specifiers imply
+                   the value is unsigned;  d implies a signed value
+                */
+                int arg_sign = 0;
+                /* 0  if numeric argument is zero (or if pointer is NULL
+                      for 'p'),
+                   +1 if greater than zero (or nonzero for unsigned arguments),
+                   -1 if negative (unsigned argument is never negative)
+                */
+
+                int int_arg = 0;
+                unsigned int uint_arg = 0;
+                   /* defined only for length modifier h, or for no
+                      length modifiers
+                   */
+
+                long int long_arg = 0;  unsigned long int ulong_arg = 0;
+                /* only defined for length modifier l */
+
+                void *ptr_arg = NULL;
+                /* pointer argument value -only defined for p conversion */
+
+                if (fmt_spec == 'p') {
+                    /* HPUX 10: An l, h, ll or L before any other
+                        conversion character (other than d, i, u, o,
+                        x, or X) is ignored.
+
+                      Digital Unix: not specified, but seems to behave
+                      as HPUX does.
+
+                      Solaris: If an h, l, or L appears before any
+                      other conversion specifier (other than d, i, u,
+                      o, x, or X), the behavior is
+                      undefined. (Actually %hp converts only 16-bits
+                      of address and %llp treats address as 64-bit
+                      data which is incompatible with (void *)
+                      argument on a 32-bit system). 
+                    */
+
+                    length_modifier = '\0';
+                    ptr_arg = va_arg(ap, void *);
+                    if (ptr_arg != NULL) arg_sign = 1;
+                } else if (fmt_spec == 'd') {  /* signed */
+                    switch (length_modifier) {
+                    case '\0':
+                    case 'h':
+                        /* It is non-portable to specify a second
+                           argument of char or short to va_arg,
+                           because arguments seen by the called
+                           function are not char or short.  C converts
+                           char and short arguments to int before
+                           passing them to a function.
+                        */
+                        int_arg = va_arg(ap, int);
+                        if      (int_arg > 0) arg_sign =  1;
+                        else if (int_arg < 0) arg_sign = -1;
+                        break;
+                    case 'l':
+                        long_arg = va_arg(ap, long int);
+                        if      (long_arg > 0) arg_sign =  1;
+                        else if (long_arg < 0) arg_sign = -1;
+                        break;
+                    }
+                } else {  /* unsigned */
+                    switch (length_modifier) {
+                    case '\0':
+                    case 'h':
+                        uint_arg = va_arg(ap, unsigned int);
+                        if (uint_arg)
+                            arg_sign = 1;
+                        break;
+                    case 'l':
+                        ulong_arg = va_arg(ap, unsigned long int);
+                        if (ulong_arg)
+                            arg_sign = 1;
+                        break;
+                    }
+                }
+                str_arg = tmp; str_arg_l = 0;
+                /* NOTE: For d, i, u, o, x, and X conversions, if
+                   precision is specified, the '0' flag should be
+                   ignored. This is so with Solaris 2.6, Digital UNIX
+                   4.0, HPUX 10, Linux, FreeBSD, NetBSD; but not with
+                   Perl.
+                */
+                if (precision_specified)
+                    zero_padding = 0;
+                if (fmt_spec == 'd') {
+                    if (force_sign && arg_sign >= 0)
+                        tmp[str_arg_l++] = space_for_positive ? ' ' : '+';
+                    /* leave negative numbers for sprintf to handle,
+                       to avoid handling tricky cases like (short
+                       int)(-32768)
+                    */
+                } else if (alternate_form) {
+                    if (arg_sign != 0 && (fmt_spec == 'x' ||
+                                          fmt_spec == 'X')) {
+                        tmp[str_arg_l++] = '0';
+                        tmp[str_arg_l++] = fmt_spec;
+                    }
+                    /* alternate form should have no effect for p
+                       conversion, but ...
+                    */
+                }
+                zero_padding_insertion_ind = str_arg_l;
+                if (!precision_specified)
+                    precision = 1;   /* default precision is 1 */
+                if (precision == 0 && arg_sign == 0) {
+                    /* converted to null string */
+                    /* When zero value is formatted with an explicit
+                       precision 0, the resulting formatted string is
+                       empty (d, i, u, o, x, X, p).
+                    */
+                } else {
+                    char f[5]; int f_l = 0;
+                    f[f_l++] = '%';
+                        /* construct a simple format string for sprintf */
+                    if (!length_modifier) { }
+                    else if (length_modifier=='2') {
+                        f[f_l++] = 'l'; f[f_l++] = 'l';
+                    }
+                    else
+                        f[f_l++] = length_modifier;
+                    f[f_l++] = fmt_spec; f[f_l++] = '\0';
+                    if (fmt_spec == 'p')
+                        str_arg_l += sprintf(tmp+str_arg_l, f, ptr_arg);
+                    else if (fmt_spec == 'd') {  /* signed */
+                        switch (length_modifier) {
+                        case '\0':
+                        case 'h':
+                            str_arg_l+=sprintf(tmp+str_arg_l, f, int_arg);
+                            break;
+                        case 'l':
+                            str_arg_l+=sprintf(tmp+str_arg_l, f, long_arg);
+                            break;
+                        }
+                    } else {  /* unsigned */
+                        switch (length_modifier) {
+                        case '\0':
+                        case 'h':
+                            str_arg_l += sprintf(tmp+str_arg_l, f, uint_arg);
+                            break;
+                        case 'l':
+                            str_arg_l += sprintf(tmp+str_arg_l, f, ulong_arg);
+                            break;
+                        }
+                    }
+                    /* include the optional minus sign and possible "0x"
+                       in the region before the zero padding insertion point
+                    */
+                    if (zero_padding_insertion_ind < str_arg_l &&
+                        tmp[zero_padding_insertion_ind] == '-') {
+                        zero_padding_insertion_ind++;
+                    }
+                    if (zero_padding_insertion_ind+1 < str_arg_l &&
+                        tmp[zero_padding_insertion_ind]   == '0' &&
+                        (tmp[zero_padding_insertion_ind+1] == 'x' ||
+                         tmp[zero_padding_insertion_ind+1] == 'X') ) {
+                        zero_padding_insertion_ind += 2;
+                    }
+                }
+                {
+                    size_t num_of_digits =
+                        str_arg_l - zero_padding_insertion_ind;
+                    if (alternate_form && fmt_spec == 'o'
+                        /* unless zero is already the first character */
+                        && !(zero_padding_insertion_ind < str_arg_l
+                             && tmp[zero_padding_insertion_ind] == '0')) {
+                        /* assure leading zero for alternate-form
+                           octal numbers 
+                        */
+                        if (!precision_specified ||
+                            precision < num_of_digits+1) {
+                            /* precision is increased to force the
+                               first character to be zero, except if a
+                               zero value is formatted with an
+                               explicit precision of zero
+                            */
+                            precision = num_of_digits+1;
+                            precision_specified = 1;
+                        }
+                    }
+                    /* zero padding to specified precision? */
+                    if (num_of_digits < precision) 
+                        number_of_zeros_to_pad = precision - num_of_digits;
+                }
+                /* zero padding to specified minimal field width? */
+                if (!justify_left && zero_padding) {
+                    int n =
+                        min_field_width - (str_arg_l+number_of_zeros_to_pad);
+                    if (n > 0) number_of_zeros_to_pad += n;
+                }
+            } break;
+            default:
+                /* unrecognized conversion specifier, keep format
+                   string as-is
+                */
+                zero_padding = 0;
+                    /* turn zero padding off for non-numeric convers. */
+                /* reset flags */
+                justify_left = 1;
+                min_field_width = 0;
+                /* discard the unrecognized conversion, just keep the
+                   unrecognized conversion character
+                */
+                str_arg = p;
+                str_arg_l = 0;
+                if (*p)
+                    /* include invalid conversion specifier unchanged
+                       if not at end-of-string
+                    */
+                    ++str_arg_l;
+                break;
+            }
+            if (*p)
+                p++;  /* step over the just processed conversion specifier */
+            /* insert padding to the left as requested by
+               min_field_width; this does not include the zero padding
+               in case of numerical conversions
+            */
+
+            if (!justify_left) {
+                /* left padding with blank or zero */
+                int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
+                if (n > 0) {
+                    if (str_l < str_m) {
+                        size_t avail = str_m-str_l;
+                        fast_memset(str+str_l, (zero_padding ? '0' : ' '),
+                                    (n > avail ? avail : n));
+                    }
+                    str_l += n;
+                }
+            }
+            /* zero padding as requested by the precision or by the
+               minimal field width for numeric conversions required?
+            */
+            if (number_of_zeros_to_pad <= 0) {
+                /* will not copy first part of numeric right now,
+                   force it to be copied later in its entirety
+                */
+                zero_padding_insertion_ind = 0;
+            } else {
+                /* insert first part of numerics (sign or '0x') before
+                   zero padding
+                */
+                int n = zero_padding_insertion_ind;
+                if (n > 0) {
+                    if (str_l < str_m) {
+                        size_t avail = str_m-str_l;
+                        fast_memcpy(str+str_l, str_arg, (n>avail?avail:n));
+                    }
+                    str_l += n;
+                }
+                /* insert zero padding as requested by the precision
+                   or min field width
+                */
+                n = number_of_zeros_to_pad;
+                if (n > 0) {
+                    if (str_l < str_m) {
+                        size_t avail = str_m - str_l;
+                        fast_memset(str + str_l, '0', (n > avail ? avail : n));
+                    }
+                    str_l += n;
+                }
+            }
+            /* insert formatted string (or as-is conversion specifier
+               for unknown conversions)
+            */
+            {
+                int n = str_arg_l - zero_padding_insertion_ind;
+                if (n > 0) {
+                    if (str_l < str_m) {
+                        size_t avail = str_m-str_l;
+                        fast_memcpy(str + str_l,
+                                    str_arg + zero_padding_insertion_ind,
+                                    (n > avail ? avail : n));
+                    }
+                    str_l += n;
+                }
+            }
+            /* insert right padding */
+            if (justify_left) {
+                /* right blank padding to the field width */
+                int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
+                if (n > 0) {
+                    if (str_l < str_m) {
+                        size_t avail = str_m-str_l;
+                        fast_memset(str+str_l, ' ', (n>avail?avail:n));
+                    }
+                    str_l += n;
+                }
+            }
+        }
+    }
+    if (str_m > 0) {
+        /* make sure the string is null-terminated even at the expense
+           of overwriting the last character (shouldn't happen, but
+           just in case)
+        */
+        str[str_l <= str_m-1 ? str_l : str_m-1] = '\0';
+    }
+    *sizeP = str_l;
+}
+
+
+
+int
+snprintfN(char *       const dest,
+          size_t       const str_m,
+          const char * const fmt,
+          ...) {
+
+    size_t size;
+    va_list ap;
+
+    va_start(ap, fmt);
+    
+    vsnprintfN(dest, str_m, fmt, ap, &size);
+
+    va_end(ap);
+
+    assert(size <= INT_MAX);
+
+    return size;
+}
+
+
+
+/* When a function that is supposed to return a malloc'ed string cannot
+   get the memory for it, it should return 'strsol'.  That has a much
+   better effect on the caller, if the caller doesn't explicitly allow for
+   the out of memory case, than returning NULL.  Note that it is very
+   rare for the system not to have enough memory to return a small string,
+   so it's OK to have somewhat nonsensical behavior when it happens.  We
+   just don't want catastrophic behavior.
+
+   'strsol' is an external symbol, so if Caller wants to detect the
+   out-of-memory failure, he certainly can.
+*/
+const char * const strsol = "NO MEMORY TO CREATE STRING!";
+
+
+
+/* We would like to have vasprintfN(), but it is difficult because you
+   can't run through a va_list twice, which we would want to do: once
+   to measure the length; once actually to build the string.  On some
+   machines, you can simply make two copies of the va_list variable in
+   normal C fashion, but on others you need va_copy, which is a
+   relatively recent invention.  In particular, the simple va_list copy
+   failed on an AMD64 Gcc Linux system in March 2006.
+*/
+
+void PM_GNU_PRINTF_ATTR(2,3)
+asprintfN(const char ** const resultP,
+          const char *  const fmt, 
+          ...) {
+
+    va_list varargs;
+    
+    size_t dryRunLen;
+    
+    va_start(varargs, fmt);
+    
+    vsnprintfN(NULL, 0, fmt, varargs, &dryRunLen);
+
+    va_end(varargs);
+
+    if (dryRunLen + 1 < dryRunLen)
+        /* arithmetic overflow */
+        *resultP = strsol;
+    else {
+        size_t const allocSize = dryRunLen + 1;
+        char * result;
+        result = malloc(allocSize);
+        if (result == NULL)
+            *resultP = strsol;
+        else {
+            va_list varargs;
+            size_t realLen;
+
+            va_start(varargs, fmt);
+
+            vsnprintfN(result, allocSize, fmt, varargs, &realLen);
+                
+            assert(realLen == dryRunLen);
+            va_end(varargs);
+
+            *resultP = result;
+        }
+    }
+}
+
+
+
+void
+strfree(const char * const string) {
+
+    if (string != strsol)
+        free((void *) string);
+}
+
+
+
+const char *
+strsepN(char ** const stringP, const char * const delim) {
+    const char * retval;   
+
+    if (stringP == NULL || *stringP == NULL)
+        retval = NULL;
+    else {
+        char * p;
+
+        retval = *stringP;
+
+        for (p = *stringP; *p && strchr(delim, *p) == NULL; ++p);
+ 
+        if (*p) {
+            /* We hit a delimiter, not end-of-string.  So null out the 
+               delimiter and advance user's pointer to the next token
+            */
+            *p++ = '\0';
+            *stringP = p;
+        } else {
+            /* We ran out of string.  So the end-of-string delimiter is 
+               already there, and we set the user's pointer to NULL to 
+               indicate there are no more tokens.
+            */
+            *stringP = NULL;
+        }
+    }
+    return retval;
+}
+
+
+
+int
+stripeq(const char * const comparand,
+        const char * const comparator) {
+/*----------------------------------------------------------------------------
+  Compare two strings, ignoring leading and trailing white space.
+
+  Return 1 (true) if the strings are identical; 0 (false) otherwise.
+-----------------------------------------------------------------------------*/
+    char *p, *q, *px, *qx;
+    char equal;
+  
+    /* Make p and q point to the first non-blank character in each string.
+     If there are no non-blank characters, make them point to the terminating
+     NULL.
+     */
+
+    p = (char *) comparand;
+    while (ISSPACE(*p)) p++;
+    q = (char *) comparator;
+    while (ISSPACE(*q)) q++;
+
+    /* Make px and qx point to the last non-blank character in each string.
+       If there are no nonblank characters (which implies the string is
+       null), make them point to the terminating NULL.
+    */
+
+    if (*p == '\0') px = p;
+    else {
+        px = p + strlen(p) - 1;
+        while (ISSPACE(*px)) px--;
+    }
+
+    if (*q == '\0') qx = q;
+    else {
+        qx = q + strlen(q) - 1;
+        while (ISSPACE(*qx)) qx--;
+    }
+
+    equal = 1;   /* initial assumption */
+  
+    /* If the stripped strings aren't the same length, 
+       we know they aren't equal 
+     */
+    if (px - p != qx - q) equal = 0;
+
+
+    while (p <= px) {
+        if (*p != *q) equal = 0;
+        p++; q++;
+    }
+    return equal;
+}
+
+
+
+const char *
+memmemN(const char * const haystack,
+        size_t       const haystacklen,
+        const char * const needle,
+        size_t       const needlelen) {
+
+    /* This does the same as the function of the same name in the GNU
+       C library
+    */
+    const char * p;
+
+    for (p = haystack; p <= haystack + haystacklen - needlelen; ++p)
+        if (MEMEQ(p, needle, needlelen))
+            return p;
+
+    return NULL;
+}
diff --git a/lib/util/nstring.h b/lib/util/nstring.h
new file mode 100644
index 00000000..9ed20051
--- /dev/null
+++ b/lib/util/nstring.h
@@ -0,0 +1,157 @@
+#ifndef _NSTRING_H
+#define _NSTRING_H
+
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "pm.h"  /* For PM_GNU_PRINTF_ATTR, __inline__ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+/* Here is are string functions that respect the size of the array
+   into which you are copying -- E.g. STRSCPY truncates the source string as
+   required so that it fits, with the terminating null, in the destination
+   array.
+*/
+#define STRSCPY(A,B) \
+	(strncpy((A), (B), sizeof(A)), *((A)+sizeof(A)-1) = '\0')
+#define STRSCMP(A,B) \
+	(strncmp((A), (B), sizeof(A)))
+#define STRSCAT(A,B) \
+    (strncpy(A+strlen(A), B, sizeof(A)-strlen(A)), *((A)+sizeof(A)-1) = '\0')
+
+#define STREQ(A, B) \
+    (strcmp((A), (B)) == 0)
+#define STRNEQ(A, B, C) \
+    (strncmp((A), (B), (C)) == 0)
+#define STRCASEEQ(A, B) \
+    (strcasecmp((A), (B)) == 0)
+#define STRNCASEEQ(A, B, C) \
+    (strncasecmp((A), (B), (C)) == 0)
+#define STRSEQ(A, B) \
+	(strncmp((A), (B), sizeof(A)) == 0)
+
+#define MEMEQ(A, B, C) \
+    (memcmp((A), (B), (C)) == 0)
+#define MEMSZERO(A) \
+    bzero((A), sizeof(A))
+
+
+static __inline__ int
+streq(const char * const comparand,
+      const char * const comparator) {
+
+    return strcmp(comparand, comparator) == 0;
+}
+
+
+
+/* The standard C library routines isdigit(), for some weird 
+   historical reason, does not take a character (type 'char') as its
+   argument.  Instead it takes an integer.  When the integer is a whole
+   number, it represents a character in the obvious way using the local
+   character set encoding.  When the integer is negative, the results
+   are undefined.
+
+   Passing a character to isdigit(), which expects an integer, results in
+   isdigit() sometimes getting a negative number.
+
+   On some systems, when the integer is negative, it represents exactly
+   the character you want it to anyway (e.g. -1 is the character that is
+   encoded 0xFF).  But on others, it does not.
+
+   (The same is true of other routines like isdigit()).
+
+   Therefore, we have the substitutes for isdigit() etc. that take an
+   actual character (type 'char') as an argument.
+*/
+
+#define ISALNUM(C) (isalnum((unsigned char)(C)))
+#define ISALPHA(C) (isalpha((unsigned char)(C)))
+#define ISCNTRL(C) (iscntrl((unsigned char)(C)))
+#define ISDIGIT(C) (isdigit((unsigned char)(C)))
+#define ISGRAPH(C) (isgraph((unsigned char)(C)))
+#define ISLOWER(C) (islower((unsigned char)(C)))
+#define ISPRINT(C) (isprint((unsigned char)(C)))
+#define ISPUNCT(C) (ispunct((unsigned char)(C)))
+#define ISSPACE(C) (isspace((unsigned char)(C)))
+#define ISUPPER(C) (isupper((unsigned char)(C)))
+#define ISXDIGIT(C) (isxdigit((unsigned char)(C)))
+#define TOUPPER(C) ((char)toupper((unsigned char)(C)))
+
+
+/* These are all private versions of commonly available standard C
+   library subroutines whose names are the same except with the N at
+   the end.  Because not all standard C libraries have them all,
+   Netpbm must include them in its own libraries, and because some
+   standard C libraries have some of them, Netpbm must use different
+   names for them.
+   
+   The GNU C library has all of them.  All but the oldest standard C libraries
+   have snprintf().
+
+   There are slight differences between the asprintf() family and that
+   found in other libraries:
+
+     - There is no return value.
+
+     - The returned string is a const char * instead of a char *.  The
+       const is more correct.
+
+     - If the function can't get the memory, it returns 'strsol',
+       which is a string that is in static memory that contains text
+       indicating an out of memory failure has occurred, intead of
+       NULL.  This makes it much easier for programs to ignore this
+       possibility.
+
+   strfree() is strictly a Netpbm invention, to allow proper type checking
+   when freeing storage allocated by the Netpbm asprintfN().
+*/
+
+extern const char * const strsol;
+
+int
+snprintfN(char *       const dest,
+          size_t       const str_m,
+          const char * const fmt,
+          ...) PM_GNU_PRINTF_ATTR(3,4);
+
+void
+vsnprintfN(char *       const str,
+           size_t       const str_m,
+           const char * const fmt,
+           va_list            ap,
+           size_t *     const sizeP);
+
+void
+asprintfN(const char ** const resultP,
+          const char *  const fmt,
+          ...) PM_GNU_PRINTF_ATTR(2,3);
+
+void 
+strfree(const char * const string);
+
+const char *
+strsepN(char ** const stringP, const char * const delim);
+
+int
+stripeq(const char * const comparand,
+        const char * const comparator);
+
+const char *
+memmemN(const char * const haystack,
+        size_t       const haystacklen,
+        const char * const needle,
+        size_t       const needlelen);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/util/pm_c_util.h b/lib/util/pm_c_util.h
new file mode 100644
index 00000000..f21a2f82
--- /dev/null
+++ b/lib/util/pm_c_util.h
@@ -0,0 +1,62 @@
+#ifndef PM_C_UTIL_INCLUDED
+#define PM_C_UTIL_INCLUDED
+
+#undef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#undef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#undef ABS
+#define ABS(a) ((a) >= 0 ? (a) : -(a))
+#undef SGN
+#define SGN(a)		(((a)<0) ? -1 : 1)
+#undef ODD
+#define ODD(n) ((n) & 1)
+#undef ROUND
+#define ROUND(X) (((X) >= 0) ? (int)((X)+0.5) : (int)((X)-0.5))
+#undef ROUNDU
+#define ROUNDU(X) ((unsigned int)((X)+0.5))
+#undef SQR
+#define SQR(a) ((a)*(a))
+
+/* NOTE: do not use "bool" as a type in an external interface.  It could
+   have different definitions on either side of the interface.  Even if both
+   sides include this interface header file, the conditional compilation
+   here means one side may use the typedef below and the other side may
+   use some other definition.  For an external interface, be safe and just
+   use "int".
+*/
+
+/* We used to assume that if TRUE was defined, then bool was too.
+   However, we had a report on 2001.09.21 of a Tru64 system that had
+   TRUE but not bool and on 2002.03.21 of an AIX 4.3 system that was
+   likewise.  So now we define bool all the time, unless the macro
+   HAVE_BOOL is defined.  If someone is using the Netpbm libraries and
+   also another library that defines bool, he can either make the
+   other library define/respect HAVE_BOOL or just define HAVE_BOOL in
+   the file that includes pm_config.h or with a compiler option.  Note
+   that C++ always has bool.  
+
+   A preferred way of getting booleans is <stdbool.h>.  But it's not
+   available on all platforms, and it's easy to reproduce what it does
+   here.
+*/
+#ifndef TRUE
+  #define TRUE 1
+  #endif
+#ifndef FALSE
+  #define FALSE 0
+  #endif
+/* C++ has a bool type and false and true constants built in. */
+#ifndef __cplusplus
+  #ifndef HAVE_BOOL
+    #define HAVE_BOOL 1
+    typedef int bool;
+    #endif
+  #ifndef true
+    enum boolvalue {false=0, true=1};
+    #endif
+  #endif
+
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+
+#endif
diff --git a/lib/util/shhopt-1.1.6.lsm b/lib/util/shhopt-1.1.6.lsm
new file mode 100644
index 00000000..7fba316e
--- /dev/null
+++ b/lib/util/shhopt-1.1.6.lsm
@@ -0,0 +1,16 @@
+Begin3
+Title:		shhopt - library for parsing command line options.
+Version:	1.1.6
+Entered-date:	10MAR00
+Description:	C-functions for parsing command line options, both
+                traditional one-character options, and GNU'ish
+                --long-options.
+Keywords:	programming, library, lib, commandline, options
+Author: 	sverrehu@online.no (Sverre H. Huseby)
+Primary-site:	http://home.sol.no/~sverrehu/pub-unix/
+Alternate-site:	sunsite.unc.edu /pub/Linux/libs
+		shhopt-1.1.6.tar.gz
+Platforms:	Requires ANSI C-compiler.
+Copying-policy:	Artistic License
+		http://www.opensource.org/licenses/artistic-license.html
+End
diff --git a/lib/util/shhopt.README b/lib/util/shhopt.README
new file mode 100644
index 00000000..2d241edf
--- /dev/null
+++ b/lib/util/shhopt.README
@@ -0,0 +1,200 @@
+Shhopt was originally written by Sverre H. Huseby, and extended by
+Bryan Henderson starting in March 2000 for use in Netpbm.  
+
+Below is the README file from Huseby's package.
+
+The file LICENSE.TXT in this directory contains the license (the
+Artistic License) under which Bryan took and redistributed Shhopt and
+the license under which Bryan offers the modified Shhopt to others.
+
+Bryan made the following changes to shhopt for Netpbm.  It is fully
+backward compatible with the original.
+
+- OPT_FLOAT (floating point number) data type added
+
+- optParseOptions2() added.  Advantages over optParseOptions(): You
+  can have a syntax where there is no such thing as a short option
+  (e.g. -a.  Maybe stacked like -tanp).  Then the long options can
+  have either 1 or 2 dashes (e.g. -width or --width).  Of course, -w
+  could be an abbreviation of -width; that's not the same thing as a
+  short option.
+
+- optParseOptions3() added.  Advantages over optParseOptions2(): 
+  Tells you whether (how many times, actually) an option was
+  specified - no need to play games with defaults.  Also, no need
+  to initialize an option value variable.
+
+- optStruct longName changed from char * to const char * to avoid
+  compiler warnings (with -Wwrite-strings) when you assign a string
+  literal to it (which is the normal case).
+
+- OPTENTRY/OPTENT3 macros added for declaring the option definition
+  array.
+
+- replace isdigit() with ISDIGIT() from Netpbm nstring.h so weird 
+  8-bit characters don't cause incorrect results.
+
+------------------------------------------------------------------------------
+
+
+shhopt - library for parsing command line options.
+==================================================
+
+This is a set of functions for parsing command line options. Both
+traditional one-character options, and GNU-style --long-options are
+supported.
+
+
+What separates this from traditional getopt?
+--------------------------------------------
+
+This library does more of the parsing for you. You set up a special
+structure describing the names and types of the options you want your
+program to support. In the structure you also give addresses of
+variables to update or functions to call for the various
+options. By calling optParseOptions, all options in argv are parsed
+and removed from argv. What is left, are the non-optional arguments to
+your program.
+
+The down-side of this, is that you won't be able to make a program
+where the position of the options between the non-options are
+significant.
+
+shhopt is distributed under the "Artistic license" (aka. the Perl
+license), which IMHO gives more freedom than GPL or LGPL. For a copy
+of the Artistic license, see
+
+    http://www.opensource.org/licenses/artistic-license.html
+
+For more information on Open Source licenses, go to
+
+    http://www.opensource.org/licenses/
+
+
+Usage
+-----
+
+To see how to use this library, take a look at the sample program
+example.c.
+
+A brief explanation:
+
+To parse your command line, you need to create and initialize an array
+of optStruct's. Each element in the array describes a long and short
+version of an option and specifies what type of option it is and how
+to handle it.
+
+The structure fields (see also shhopt.h):
+
+  `shortName' is the short option name without the leading '-'.
+
+  `longName' is the long option name without the leading "--".
+
+  `type' specifies what type of option this is. (Does it expect an
+      argument? Is it a flag? If it takes an argument, what type
+      should it be?)
+
+  `arg' is either a function to be called with the argument from
+      the commandline, or a pointer to a location in which to store
+      the value.
+
+  `flags' indicates whether `arg' points to a function or a storage
+      location.
+
+The different argument types:
+
+  `OPT_END' flags this as the last element in the options array.
+
+  `OPT_FLAG' indicates an option that takes no arguments. If `arg' is
+      not a function pointer, the value of `arg' will be set to 1 if
+      this flag is found on the command line.
+
+  `OPT_STRING' expects a string argument.
+
+  `OPT_INT' expects an int argument.
+
+  `OPT_UINT' expects an unsigned int argument.
+
+  `OPT_LONG' expects a long argument.
+
+  `OPT_ULONG' expects an unsigned long argument.
+
+The different flag types:
+
+  `OPT_CALLFUNC' indicates that `arg' is a function pointer. If this
+      is not given, `arg' is taken as a pointer to a variable.
+
+
+Notes
+-----
+
+* A dash (`-') by itself is not taken as any kind of an option, as
+  several programs use this to indicate the special files stdin and
+  stdout. It is thus left as a normal argument to the program.
+
+* Two dashes (`--') as an argument, is taken to mean that the rest of
+  the arguments should not be scanned for options. This simplifies
+  giving names of files that start with a dash.
+
+* Short (one-character) options accept parameters in two ways, either
+  directly following the option in the same argv-entry, or in the next
+  argv-entry:
+
+	-sPARAMETER
+	-s PARAMETER
+
+* Long options accept parameters in two ways:
+
+	--long-option=PARAMETER
+	--long-option PARAMETER
+
+  To follow the GNU-tradition, your program documentation should use
+  the first form.
+
+* Several one-character options may be combined after a single
+  dash. If any of the options requires a parameter, the rest of the
+  string is taken as this parameter. If there is no "rest of the
+  string", the next argument is taken as the parameter.
+
+* There is no support for floating point (double) arguments to
+  options. This is to avoid unnecessary linking with the math
+  library. See example.c for how to get around this by writing a
+  function converting a string argument to a double (functions
+  strToDouble and doubleFunc).
+
+
+Portability
+-----------
+
+If your libc lacks strtoul, you will need to link with GNU's -liberty,
+that may be found by anonymous ftp to ftp://ftp.gnu.org/pub/gnu/
+
+The library has (more or less recently) been compiled and `tested' on
+the following systems:
+
+	IRIX Release 5.3 IP22
+	GNU/Linux 2.2.11
+	SunOS Release 4.1.3_U1 (-liberty needed)
+	ULTRIX V4.4 (Rev. 69)
+
+All compilations were done using GNU's gcc, and GNU's make.
+
+
+Author
+------
+
+The program is written by
+
+        Sverre H. Huseby        sverrehu@online.no
+        Lofthusvn. 11 B         http://home.sol.no/~sverrehu/
+        N-0587 Oslo
+        Norway
+
+You can use and copy this for _free_, but I would be very happy if you
+send me an E-mail and tell me that you use it. If you insist on paying
+something, please donate some money to an organization that strives to
+make the world a better place for everyone.
+
+I don't like bugs, so please help me removing them by reporting
+whatever you find!
+
diff --git a/lib/util/shhopt.c b/lib/util/shhopt.c
new file mode 100644
index 00000000..7722b5d5
--- /dev/null
+++ b/lib/util/shhopt.c
@@ -0,0 +1,939 @@
+/*------------------------------------------------------------------------
+ |  FILE            shhopt.c
+ |
+ |  DESCRIPTION     Functions for parsing command line arguments. Values
+ |                  of miscellaneous types may be stored in variables,
+ |                  or passed to functions as specified.
+ |
+ |  REQUIREMENTS    Some systems lack the ANSI C -function strtoul. If your
+ |                  system is one of those, you'll ned to write one yourself,
+ |                  or get the GNU liberty-library (from prep.ai.mit.edu).
+ |
+ |  WRITTEN BY      Sverre H. Huseby <sverrehu@online.no>
+ +----------------------------------------------------------------------*/
+
+/*************************************************************************
+  This is based on work by Sverre H. Huseby <sverrehu@online.no>.
+  These functions are backward compatible with the 'shhopt'
+  distributed by Huseby.
+
+  See the file README.shhopt for copy licensing information.
+*************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+
+/*-----------------------------------------------------------------------+
+|  PRIVATE DATA                                                          |
++-----------------------------------------------------------------------*/
+
+static void optFatalFunc(const char *, ...);
+static void (*optFatal)(const char *format, ...) = optFatalFunc;
+
+/*-----------------------------------------------------------------------+
+|  PRIVATE FUNCTIONS                                                     |
++-----------------------------------------------------------------------*/
+
+/*------------------------------------------------------------------------
+ |  NAME          optFatalFunc
+ |
+ |  FUNCTION      Show given message and abort the program.
+ |
+ |  INPUT         format, ...
+ |                        Arguments used as with printf().
+ |
+ |  RETURNS       Never returns. The program is aborted.
+ */
+static void
+optFatalFunc(const char *format, ...)
+{
+    va_list ap;
+
+    fflush(stdout);
+    va_start(ap, format);
+    vfprintf(stderr, format, ap);
+    va_end(ap);
+    fprintf(stderr, "\n");
+    exit(99);
+}
+
+/*------------------------------------------------------------------------
+ |  NAME          optStructCount
+ |
+ |  FUNCTION      Get number of options in a optStruct.
+ |
+ |  INPUT         opt     array of possible options.
+ |
+ |  RETURNS       Number of options in the given array.
+ |
+ |  DESCRIPTION   Count elements in an optStruct-array. The strcture must
+ |                be ended using an element of type OPT_END.
+ */
+static int
+optStructCount(const optEntry opt[])
+{
+    int ret = 0;
+
+    while (opt[ret].type != OPT_END && ret < 500)
+        ++ret;
+    return ret;
+}
+
+/*------------------------------------------------------------------------
+ |  NAME          optMatch
+ |
+ |  FUNCTION      Find a matching option.
+ |
+ |  INPUT         opt     array of possible options.
+ |                s       string to match, without `-' or `--'.
+ |                lng     match long option, otherwise short.
+ |
+ |  RETURNS       Index to the option if found, -1 if not found.
+ |
+ |  DESCRIPTION   Short options are matched from the first character in
+ |                the given string.
+ */
+static int
+optMatch(const optEntry opt[], const char *s, int lng)
+{
+    int        nopt, q, matchlen = 0;
+    const char *p;
+
+    nopt = optStructCount(opt);
+    if (lng) {
+        if ((p = strchr(s, '=')) != NULL)
+            matchlen = p - s;
+        else
+            matchlen = strlen(s);
+    }
+    for (q = 0; q < nopt; q++) {
+        if (lng) {
+            if (!opt[q].longName)
+                continue;
+            if (strncmp(s, opt[q].longName, matchlen) == 0)
+                return q;
+        } else {
+            if (!opt[q].shortName)
+                continue;
+            if (*s == opt[q].shortName)
+                return q;
+        }
+    }
+    return -1;
+}
+
+/*------------------------------------------------------------------------
+ |  NAME          optString
+ |
+ |  FUNCTION      Return a (static) string with the option name.
+ |
+ |  INPUT         opt     the option to stringify.
+ |                lng     is it a long option?
+ |
+ |  RETURNS       Pointer to static string.
+ */
+static char *
+optString(const optEntry opte, int lng)
+{
+    static char ret[31];
+
+    if (lng) {
+        strcpy(ret, "--");
+        strncpy(ret + 2, opte.longName, 28);
+    } else {
+        ret[0] = '-';
+        ret[1] = opte.shortName;
+        ret[2] = '\0';
+    }
+    return ret;
+}
+
+
+    
+static optEntry
+optStructToEntry(const optStruct opt) {
+/*----------------------------------------------------------------------------
+   Return the information in 'opt' (an optStruct type) as an optEntry type.
+   optEntry is newer and has an additional field.
+-----------------------------------------------------------------------------*/
+    optEntry opte;
+
+    opte.shortName = opt.shortName;
+    opte.longName  = opt.longName;
+    opte.type      = opt.type;
+    opte.arg       = opt.arg;
+    opte.specified = NULL;
+    opte.flags     = opt.flags;
+
+    return(opte);
+}
+
+
+
+static optEntry *
+optStructTblToEntryTbl(const optStruct optStructTable[]) {
+/*----------------------------------------------------------------------------
+   Return a table of optEntry types containing the information in the
+   input table of optStruct types.
+
+   Return it in newly malloc'ed storage.
+-----------------------------------------------------------------------------*/
+    int count;
+        /* Number of entries in input table, including OPT_END marker */
+    int i;
+
+    optEntry *optEntryTable;  /* malloc'ed array */
+    
+    /* Count the entries in optStructTable[] */
+    for (i = 0; optStructTable[i].type != OPT_END && i < 500; i++);
+    count = i+1;
+
+    optEntryTable = (optEntry *) malloc(count * sizeof(optEntry));
+    if (optEntryTable) {
+        int i;
+        for (i = 0; i < count; i++) 
+            optEntryTable[i] = optStructToEntry(optStructTable[i]);
+    }
+    return(optEntryTable);
+}
+
+
+
+
+/*------------------------------------------------------------------------
+ |  NAME          optNeedsArgument
+ |
+ |  FUNCTION      Check if an option requires an argument.
+ |
+ |  INPUT         opt     the option to check.
+ |
+ |  RETURNS       Boolean value.
+ */
+static int
+optNeedsArgument(const optEntry opt)
+{
+    return opt.type == OPT_STRING
+	|| opt.type == OPT_INT
+	|| opt.type == OPT_UINT
+	|| opt.type == OPT_LONG
+	|| opt.type == OPT_ULONG
+    || opt.type == OPT_FLOAT
+    || opt.type == OPT_NAMELIST
+        ;
+}
+
+/*------------------------------------------------------------------------
+ |  NAME          argvRemove
+ |
+ |  FUNCTION      Remove an entry from an argv-array.
+ |
+ |  INPUT         argc    pointer to number of options.
+ |                argv    array of option-/argument-strings.
+ |                i       index of option to remove.
+ |
+ |  OUTPUT        argc    new argument count.
+ |                argv    array with given argument removed.
+ */
+static void
+argvRemove(int *argc, char *argv[], int i)
+{
+    if (i >= *argc)
+        return;
+    while (i++ < *argc)
+        argv[i - 1] = argv[i];
+    --*argc;
+}
+
+
+
+static void
+getToken(const char *  const tokenStart,
+         char          const delimiter,
+         const char ** const tokenP,
+         const char ** const nextP) {
+/*----------------------------------------------------------------------------
+   Find the token starting at 'tokenStart' up to but not including
+   the first 'delimiter' character or end of string.  Return it in newly
+   malloced memory as *tokenP, NUL-terminated.
+
+   Make *nextP point just past the token, i.e. to the delimiter or
+   end of string NUL character.
+
+   Note that if the string is empty, or starts with the delimiter,
+   we return an empty string and *nextP == tokenStart, i.e. *nextP
+   doesn't necessarily advance.
+-----------------------------------------------------------------------------*/
+    char * token;
+    const char * cursor;
+    unsigned int charCount;
+
+    /* Run through the token, counting characters */
+
+    charCount = 0;
+    cursor = tokenStart;
+
+    while (*cursor != delimiter && *cursor != '\0') {
+        if (*cursor == '\\') {
+            ++cursor;
+            if (*cursor == '\0')
+                optFatal("string ends with an escape character (\\)");
+        }
+        ++cursor;
+        ++charCount;
+    }
+    
+    token = malloc(charCount + 1);
+    if (token == NULL)
+        optFatal("Could not allocate %u bytes of memory to parse a string",
+                 charCount + 1);
+
+    /* Go back and do it again, this time copying the characters */
+    charCount = 0;
+    cursor = tokenStart;
+
+    while (*cursor != delimiter && *cursor != '\0') {
+        if (*cursor == '\\')
+            ++cursor;
+
+        assert(*cursor != '\0');
+
+        token[charCount++] = *cursor++;
+    }
+    token[charCount] = '\0';
+
+    *tokenP = token;
+    *nextP = cursor;
+}
+
+
+
+static void
+parseNameList(const char *           const listText,
+              struct optNameValue ** const listP) {
+
+    unsigned int const maxOptionCount = 100;
+
+    const char * cursor;
+    unsigned int optionCount;
+    struct optNameValue * list;
+
+    MALLOCARRAY_NOFAIL(list, maxOptionCount+1);
+
+    cursor = &listText[0];  /* initial value */
+
+    optionCount = 0;  /* initial value */
+
+    while (optionCount < maxOptionCount && *cursor != '\0') {
+        const char * next;
+        struct optNameValue pair;
+
+        getToken(cursor, '=', &pair.name, &next);
+
+        cursor = next;
+
+        if (*cursor == '\0')
+            optFatal("name=value option value ends prematurely.  An equal "
+                     "sign was expected following name '%s'", pair.name);
+
+        assert(*cursor == '=');
+        ++cursor;
+
+        getToken(cursor, ',', &pair.value, &next);
+
+        cursor = next;
+
+        list[optionCount++] = pair;
+
+        if (*cursor != '\0') {
+            assert(*cursor == ',');
+            ++cursor;
+        }
+    }
+    list[optionCount].name  = NULL;
+    list[optionCount].value = NULL;
+
+    *listP = list;
+}
+
+
+
+/*------------------------------------------------------------------------
+ |  NAME          optExecute
+ |
+ |  FUNCTION      Perform the action of an option.
+ |
+ |  INPUT         opt     element in array of defined options that 
+ |                        applies to this option
+ |                arg     argument to option, if it applies.
+ |                lng     was the option given as a long option?
+ |
+ |  RETURNS       Nothing. Aborts in case of error.
+ */
+static void
+optExecute(optEntry  const opt, char *arg, int lng)
+{
+    if (opt.specified)
+        (*(opt.specified))++;
+
+    switch (opt.type) {
+    case OPT_FLAG:
+        if (opt.arg)
+            *((int *) opt.arg) = 1;
+        break;
+
+    case OPT_STRING:
+        if (opt.arg)
+            *((char **) opt.arg) = arg;
+        break;
+
+    case OPT_INT:
+    case OPT_LONG: {
+        long tmp;
+        char *e;
+	  
+        if (arg == NULL)
+            optFatal("internal error: optExecute() called with NULL argument "
+                     "'%s'", optString(opt, lng));
+        tmp = strtol(arg, &e, 10);
+        if (*e)
+            optFatal("invalid number `%s'", arg);
+        if (errno == ERANGE
+            || (opt.type == OPT_INT && (tmp > INT_MAX || tmp < INT_MIN)))
+            optFatal("number `%s' to `%s' out of range",
+                     arg, optString(opt, lng));
+        if (opt.type == OPT_INT) {
+            *((int *) opt.arg) = (int) tmp;
+        } else /* OPT_LONG */ {
+            if (opt.arg)
+                *((long *) opt.arg) = tmp;
+        }
+    } break;
+	
+    case OPT_UINT:
+    case OPT_ULONG: {
+        unsigned long tmp;
+        char *e;
+        
+        if (arg == NULL)
+            optFatal("internal error: optExecute() called with NULL argument "
+                     "'%s'", optString(opt, lng));
+        tmp = strtoul(arg, &e, 10);
+        if (*e)
+            optFatal("invalid number `%s'", arg);
+        if (errno == ERANGE
+            || (opt.type == OPT_UINT && tmp > UINT_MAX))
+            optFatal("number `%s' to `%s' out of range",
+                     arg, optString(opt, lng));
+        if (opt.type == OPT_UINT) {
+           if (opt.arg)
+               *((unsigned *) opt.arg) = (unsigned) tmp;
+        } else /* OPT_ULONG */ {
+            if (opt.arg)
+                *((unsigned long *) opt.arg) = tmp;
+        }
+    } break;
+    case OPT_FLOAT: {
+        float tmp;
+        char *e;
+	  
+        if (arg == NULL)
+            optFatal("internal error: optExecute() called with NULL argument "
+                     "'%s'", optString(opt, lng));
+        tmp = strtod(arg, &e);
+        if (*e)
+            optFatal("invalid floating point number `%s'", arg);
+        if (errno == ERANGE)
+            optFatal("floating point number `%s' to `%s' out of range",
+                     arg, optString(opt, lng));
+        if (opt.arg)
+            *((float *) opt.arg) = tmp;
+    } break;
+    case OPT_NAMELIST: {
+        if (arg == NULL)
+            optFatal("internal error: optExecute() called with NULL argument "
+                     "'%s'", optString(opt, lng));
+
+        if (opt.arg)
+            parseNameList(arg, (struct optNameValue **)opt.arg);
+
+    } break;
+    default:
+        break;
+    }
+}
+
+
+
+/*-----------------------------------------------------------------------+
+|  PUBLIC FUNCTIONS                                                      |
++-----------------------------------------------------------------------*/
+
+/*------------------------------------------------------------------------
+ |  NAME          optSetFatalFunc
+ |
+ |  FUNCTION      Set function used to display error message and exit.
+ |
+ |  SYNOPSIS      #include "shhopt.h"
+ |                void optSetFatalFunc(void (*f)(const char *, ...));
+ |
+ |  INPUT         f       function accepting printf()'like parameters,
+ |                        that _must_ abort the program.
+ */
+void
+optSetFatalFunc(void (*f)(const char *, ...))
+{
+    optFatal = f;
+}
+
+
+/*------------------------------------------------------------------------
+ |  NAME          optParseOptions
+ |
+ |  FUNCTION      Parse commandline options.
+ |
+ |  SYNOPSIS      #include "shhopt.h"
+ |                void optParseOptions(int *argc, char *argv[],
+ |                                     optStruct opt[], int allowNegNum);
+ |
+ |  INPUT         argc    Pointer to number of options.
+ |                argv    Array of option-/argument-strings.
+ |                opt     Array of possible options.
+ |                allowNegNum
+ |                        a negative number is not to be taken as
+ |                        an option.
+ |
+ |  OUTPUT        argc    new argument count.
+ |                argv    array with arguments removed.
+ |
+ |  RETURNS       Nothing. Aborts in case of error.
+ |
+ |  DESCRIPTION   This function checks each option in the argv-array
+ |                against strings in the opt-array, and `executes' any
+ |                matching action. Any arguments to the options are
+ |                extracted and stored in the variables or passed to
+ |                functions pointed to by entries in opt.
+ |
+ |                Options and arguments used are removed from the argv-
+ |                array, and argc is decreased accordingly.
+ |
+ |                Any error leads to program abortion.
+ */
+void
+optParseOptions(int *argc, char *argv[], optStruct opt[], int allowNegNum)
+{
+    int  ai,        /* argv index. */
+         optarg,    /* argv index of option argument, or -1 if none. */
+         mi,        /* Match index in opt. */
+         done;
+    char *arg,      /* Pointer to argument to an option. */
+         *o,        /* pointer to an option character */
+         *p;
+
+    optEntry *opt_table;  /* malloc'ed array */
+
+    opt_table = optStructTblToEntryTbl(opt);
+    if (opt_table == NULL)
+        optFatal("Memory allocation failed (trying to allocate space for "
+                 "new-format option table)");
+
+    /*
+     *  Loop through all arguments.
+     */
+    for (ai = 0; ai < *argc; ) {
+        /*
+         *  "--" indicates that the rest of the argv-array does not
+         *  contain options.
+         */
+        if (strcmp(argv[ai], "--") == 0) {
+            argvRemove(argc, argv, ai);
+            break;
+        }
+
+        if (allowNegNum && argv[ai][0] == '-' && ISDIGIT(argv[ai][1])) {
+            ++ai;
+            continue;
+        } else if (strncmp(argv[ai], "--", 2) == 0) {
+            /* long option */
+            /* find matching option */
+            if ((mi = optMatch(opt_table, argv[ai] + 2, 1)) < 0)
+                optFatal("unrecognized option `%s'", argv[ai]);
+
+            /* possibly locate the argument to this option. */
+            arg = NULL;
+            if ((p = strchr(argv[ai], '=')) != NULL)
+                arg = p + 1;
+	    
+            /* does this option take an argument? */
+            optarg = -1;
+            if (optNeedsArgument(opt_table[mi])) {
+                /* option needs an argument. find it. */
+                if (!arg) {
+                    if ((optarg = ai + 1) == *argc)
+                        optFatal("option `%s' requires an argument",
+                                 optString(opt_table[mi], 1));
+                    arg = argv[optarg];
+                }
+            } else {
+                if (arg)
+                    optFatal("option `%s' doesn't allow an argument",
+                             optString(opt_table[mi], 1));
+            }
+            optExecute(opt_table[mi], arg, 1);
+            /* remove option and any argument from the argv-array. */
+            if (optarg >= 0)
+                argvRemove(argc, argv, ai);
+            argvRemove(argc, argv, ai);
+        } else if (*argv[ai] == '-') {
+            /* A dash by itself is not considered an option. */
+            if (argv[ai][1] == '\0') {
+                ++ai;
+                continue;
+            }
+            /* Short option(s) following */
+            o = argv[ai] + 1;
+            done = 0;
+            optarg = -1;
+            while (*o && !done) {
+                /* find matching option */
+                if ((mi = optMatch(opt_table, o, 0)) < 0)
+                    optFatal("unrecognized option `-%c'", *o);
+
+                /* does this option take an argument? */
+                optarg = -1;
+                arg = NULL;
+                if (optNeedsArgument(opt_table[mi])) {
+                    /* option needs an argument. find it. */
+                    arg = o + 1;
+                    if (!*arg) {
+                        if ((optarg = ai + 1) == *argc)
+                            optFatal("option `%s' requires an argument",
+                                     optString(opt_table[mi], 0));
+                        arg = argv[optarg];
+                    }
+                    done = 1;
+                }
+                /* perform the action of this option. */
+                optExecute(opt_table[mi], arg, 0);
+                ++o;
+            }
+            /* remove option and any argument from the argv-array. */
+            if (optarg >= 0)
+                argvRemove(argc, argv, ai);
+            argvRemove(argc, argv, ai);
+        } else {
+            /* a non-option argument */
+            ++ai;
+        }
+    }
+    free(opt_table);
+}
+
+
+static void
+parse_short_option_token(char *argv[], const int argc, const int ai,
+                         const optEntry opt_table[], 
+                         int * const tokens_consumed_p) {
+
+    char *o;  /* A short option character */
+    char *arg;
+    int mi;   /* index into option table */
+    unsigned char processed_arg;  /* boolean */
+        /* We processed an argument to one of the one-character options. 
+           This necessarily means there are no more options in this token
+           to process.
+           */
+
+    *tokens_consumed_p = 1;  /* initial assumption */
+
+    o = argv[ai] + 1;
+    processed_arg = 0;  /* initial value */
+    while (*o && !processed_arg) {
+		/* find matching option */
+		if ((mi = optMatch(opt_table, o, 0)) < 0)
+		    optFatal("unrecognized option `-%c'", *o);
+
+		/* does this option take an argument? */
+		if (optNeedsArgument(opt_table[mi])) {
+		    /* option needs an argument. find it. */
+		    arg = o + 1;
+		    if (!*arg) {
+                if (ai + 1 >= argc)
+			    optFatal("option `%s' requires an argument",
+				     optString(opt_table[mi], 0));
+			arg = argv[ai+1];
+            (*tokens_consumed_p)++;
+		    }
+		    processed_arg = 1;
+		} else 
+            arg = NULL;
+		/* perform the action of this option. */
+		optExecute(opt_table[mi], arg, 0);
+		++o;
+    }
+}
+
+
+
+static void
+parse_long_option(char *argv[], const int argc, const int ai,
+                  const int namepos,
+                  const optEntry opt_table[], 
+                  int * const tokens_consumed_p) {
+
+    char *equals_arg;
+      /* The argument of an option, included in the same token, after a
+         "=".  NULL if no "=" in the token.
+         */
+    char *arg;     /* The argument of an option; NULL if none */
+    int mi;    /* index into option table */
+
+    /* The current token is an option, and its name starts at
+       Index 'namepos' in the argument.
+    */
+    *tokens_consumed_p = 1;  /* initial assumption */
+    /* find matching option */
+    if ((mi = optMatch(opt_table, &argv[ai][namepos], 1)) < 0)
+        optFatal("unrecognized option `%s'", argv[ai]);
+            
+    /* possibly locate the argument to this option. */
+    { 
+        char *p;
+        if ((p = strchr(argv[ai], '=')) != NULL)
+            equals_arg = p + 1;
+        else 
+            equals_arg = NULL;
+    }
+    /* does this option take an argument? */
+    if (optNeedsArgument(opt_table[mi])) {
+        /* option needs an argument. find it. */
+        if (equals_arg)
+            arg = equals_arg;
+        else {
+            if (ai + 1 == argc)
+                optFatal("option `%s' requires an argument",
+                         optString(opt_table[mi], 1));
+            arg = argv[ai+1];
+            (*tokens_consumed_p)++;
+        }
+    } else {
+        if (equals_arg)
+            optFatal("option `%s' doesn't allow an argument",
+                     optString(opt_table[mi], 1));
+        else 
+            arg = NULL;
+    }
+    /* perform the action of this option. */
+    optExecute(opt_table[mi], arg, 1);
+}
+
+
+
+/*------------------------------------------------------------------------
+ |  NAME          optParseOptions2
+ |
+ |  FUNCTION      Parse commandline options.
+ |
+ |  SYNOPSIS      #include "shhopt.h"
+ |                void optParseOptions2(int *argc, char *argv[],
+ |                                      optStruct2 opt, unsigned long flags);
+ |
+ |  INPUT         argc    Pointer to number of options.
+ |                argv    Array of option-/argument-strings.
+ |                opt     Structure describing option syntax.
+ |                flags   Result is undefined if not zero.
+ |                        For future expansion.
+ |
+ |  OUTPUT        argc    new argument count.
+ |                argv    array with arguments removed.
+ |
+ |  RETURNS       Nothing. Aborts in case of error.
+ |
+ |  DESCRIPTION   This function checks each option in the argv-array
+ |                against strings in the opt-array, and `executes' any
+ |                matching action. Any arguments to the options are
+ |                extracted and stored in the variables or passed to
+ |                functions pointed to by entries in opt.
+ |
+ |                This differs from optParseOptions in that it accepts
+ |                long options with just one hyphen and doesn't accept
+ |                any short options.  It also has accomodations for 
+ |                future expansion.
+ |
+ |                Options and arguments used are removed from the argv-
+ |                array, and argc is decreased accordingly.
+ |
+ |                Any error leads to program abortion.
+ */
+void
+optParseOptions2(int * const argc_p, char *argv[], const optStruct2 opt, 
+                 const unsigned long flags)
+/*----------------------------------------------------------------------------
+   This does the same thing as optParseOptions3(), except that there is no
+   "specified" return value.  
+
+   This function exists for backward compatibility.
+-----------------------------------------------------------------------------*/
+
+{
+    optStruct3 opt3;
+
+    opt3.short_allowed = opt.short_allowed;
+    opt3.allowNegNum   = opt.allowNegNum;
+    opt3.opt_table     = optStructTblToEntryTbl(opt.opt_table);
+
+    if (opt3.opt_table == NULL)
+        optFatal("Memory allocation failed (trying to allocate space for "
+                 "new-format option table)");
+    
+    optParseOptions3(argc_p, argv, opt3, sizeof(opt3), flags);
+
+    free(opt3.opt_table);
+}
+
+
+
+
+static void
+zero_specified(optEntry opt_table[]) {
+/*----------------------------------------------------------------------------
+   Set all the "number of times specified" return values identified in the
+   option table opt_table[] to zero.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+
+    for (i = 0; opt_table[i].type != OPT_END; i++) {
+        if (opt_table[i].specified)
+            *(opt_table[i].specified) = 0;
+    }
+}
+
+
+
+/*------------------------------------------------------------------------
+ |  NAME          optParseOptions3
+ |
+ |  FUNCTION      Parse commandline options.
+ |
+ |  INPUT         argc    Pointer to number of options.
+ |                argv    Array of option-/argument-strings.
+ |                opt     Structure describing option syntax.
+ |                optStructSize
+ |                        Size of "opt" (since the caller may be older
+ |                        than this function, it may be using a structure
+ |                        with fewer fields than exist today.  We use this
+ |                        parameter to handle those older callers). 
+ |                flags   Result is undefined if not zero.
+ |                        For future expansion.
+ |
+ |  OUTPUT        argc    new argument count.
+ |                argv    array with arguments removed.
+ |                
+ |                Areas pointed to by pointers in 'opt' get updated with
+ |                option values and counts.
+ |
+ |  RETURNS       Nothing. Aborts in case of error.
+ |
+ |  DESCRIPTION   This function checks each option in the argv-array
+ |                against strings in the opt-array, and `executes' any
+ |                matching action. Any arguments to the options are
+ |                extracted and stored in the variables or passed to
+ |                functions pointed to by entries in opt.
+ |
+ |                This differs from optParseOptions in that it accepts
+ |                long options with just one hyphen and doesn't accept
+ |                any short options.  It also has accomodations for 
+ |                future expansion.
+ |
+ |                Options and arguments used are removed from the argv-
+ |                array, and argc is decreased accordingly.
+ |
+ |                Any error leads to program abortion.
+ */
+void
+optParseOptions3(int * const argc_p, char *argv[], const optStruct3 opt, 
+                 const unsigned int optStructSize, const unsigned long flags)
+{
+    int  ai;        /* argv index. */
+    int tokens_consumed;
+    unsigned char no_more_options;  /* boolean */
+        /* We've encountered the "no more options" token */
+
+    zero_specified(opt.opt_table);
+
+    /*
+     *  Loop through all arguments.
+     */
+    no_more_options = 0;  /* initial value */
+    for (ai = 0; ai < *argc_p; ) {
+        if (no_more_options) 
+            /* Can't be an option -- there aren't any more */
+            ai++;
+        else if (argv[ai][0] != '-') 
+            /* Can't be an option -- doesn't start with a dash */
+            ai++;
+        else {
+            /* It starts with a dash -- could be an option */
+            if (argv[ai][1] == '\0') {
+                /* A dash by itself is not considered an option. */
+                ++ai;
+                tokens_consumed = 0;
+            } else if (opt.allowNegNum && ISDIGIT(argv[ai][1])) {
+                /* It's a negative number parameter, not an option */
+                ++ai;
+                tokens_consumed = 0;
+            } else if (argv[ai][1] == '-') {
+                /* It starts with -- */
+                if (argv[ai][2] == '\0') {
+                    /* The entire thing is "--".  That means no more options */
+                    tokens_consumed = 1;
+                    no_more_options = 1;
+                } else 
+                    /* It's an option that starts with "--" */
+                    parse_long_option(argv, *argc_p, ai, 2,
+                                      opt.opt_table, &tokens_consumed);
+            } else {
+                if (opt.short_allowed) {
+                    /* It's a cluster of (one or more) short options */
+                    parse_short_option_token(argv, *argc_p, ai,
+                                             opt.opt_table, &tokens_consumed);
+                } else {
+                    /* It's a long option that starts with "-" */
+                    parse_long_option(argv, *argc_p, ai, 1,
+                                      opt.opt_table, &tokens_consumed);
+                }
+            
+            }
+            /* remove option and any argument from the argv-array. */
+            {
+                int i;
+                for (i = 0; i < tokens_consumed; i++)
+                    argvRemove(argc_p, argv, ai);
+            }
+        }
+    }
+}
+
+
+
+void
+optDestroyNameValueList(struct optNameValue * const list) {
+
+    unsigned int i;
+
+    for (i = 0; list[i].name; ++i) {
+        strfree(list[i].name);
+        strfree(list[i].value);
+    }
+
+    free(list);
+}
diff --git a/lib/util/shhopt.h b/lib/util/shhopt.h
new file mode 100644
index 00000000..fd15b53c
--- /dev/null
+++ b/lib/util/shhopt.h
@@ -0,0 +1,240 @@
+/*==============================================================================
+HERE IS AN EXAMPLE OF THE USE OF SHHOPT:
+
+
+#include <shhopt.h>
+int 
+main ( int argc, char **argv ) {
+
+    int help_flag = 7;
+    unsigned int help_spec =7;
+    unsigned int height_spec =7;
+    unsigned int name_spec= 7;
+    char *name= "initial";
+    int height=7;
+    int verbose_flag=7;
+    int debug_flag=7;
+    struct optNameValue * optlist;
+    
+    optStruct3 opt;
+    unsigned int option_def_index = 0;
+    optEntry *option_def = malloc(100*sizeof(option_def[0]));
+
+    OPTENT3(0,   "help",     OPT_FLAG,     &help_flag,    &help_spec,   0);
+    OPTENT3(0,   "height",   OPT_INT,      &height,       &height_spec, 0);
+    OPTENT3('n', "name",     OPT_STRING,   &name,         &name_spec,   0);
+    OPTENT3('v', "verbose",  OPT_FLAG,     &verbose_flag, NULL,         0);
+    OPTENT3('g', "debug",    OPT_FLAG,     &debug_flag,   NULL,         0);
+    OPTENT3(0,   "options",  OPT_NAMELIST, &optlist,      NULL,         0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = 1;
+    opt.allowNegNum = 1;
+
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    
+
+    printf("argc=%d\n", argc);
+    printf("help_flag=%d\n", help_flag);
+    printf("help_spec=%d\n", help_spec);
+    printf("height=%d\n", height);
+    printf("height_spec=%d\n", height_spec);
+    printf("name='%s'\n", name);
+    printf("name_spec=%d\n", name_spec);
+    printf("verbose_flag=%d\n", verbose_flag);
+    printf("debug_flag=%d\n", verbose_flag);
+
+    if (optlist) {
+        unsigned int i;
+        while (optlist[i].name) {
+            printf("option '%s' = '%s'\n", optlist[i].name, optlist[i].value);
+            ++i;
+        }
+        optDestroyNameValueList(optlist);
+    } else
+        printf("No -options\n");
+}
+
+Now run this program with something like
+
+  myprog -vg --name=Bryan --hei 4 "My first argument" --help
+
+  or do it with opt.short_allowed=0 and
+
+  myprog -v /etc/passwd -name=Bryan --hei=4
+
+
+========================================================================*/
+
+
+#ifndef SHHOPT_H
+#define SHHOPT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if 0
+} /* to fake out automatic code indenters */
+#endif
+
+/* constants for recognized option types. */
+typedef enum {
+    OPT_END,               /* nothing. used as ending element. */
+    OPT_FLAG,              /* no argument following. sets variable to 1. */
+    OPT_STRING,            /* string argument. */
+    OPT_INT,               /* signed integer argument. */
+    OPT_UINT,              /* unsigned integer argument. */
+    OPT_LONG,              /* signed long integer argument. */
+    OPT_ULONG,             /* unsigned long integer argument. */
+    OPT_FLOAT,             /* floating point argument. */
+    OPT_NAMELIST           /* list like "key1=val1,key2=val2" */
+} optArgType;
+
+
+typedef struct {
+    /* This structure describes a single program option in a form for
+     use by the optParseOptions() or optParseOptions2() function.
+    */
+    char       shortName;  /* short option name. */
+    const char *longName;  /* long option name, not including '--'. */
+    optArgType type;       /* option type. */
+    void       *arg;       /* pointer to variable to fill with argument,
+                            * or pointer to function if type == OPT_FUNC. */
+    int        flags;      /* modifier flags. */
+} optStruct;
+    
+typedef struct {
+    /* This structure describes a single program option in a form for
+     use by the optParseOptions3() function.
+    */
+    char       shortName;  /* short option name. */
+    const char *longName;  /* long option name, not including '--' or '-' */
+    optArgType type;       /* option type. */
+    void       *arg;       
+        /* pointer to variable in which to return option's argument (or TRUE
+           if it's a flag option), or pointer to function if 
+           type == OPT_FUNC.  If the option is specified multiple times, only 
+           the rightmost one affects this return value.
+        */
+    unsigned int *specified;
+        /* pointer to variable in which to return the number of times that
+           the option was specified.  If NULL, don't return anything.
+        */
+    int        flags;      /* modifier flags. */
+} optEntry;
+    
+
+typedef struct {
+    /* This structure describes the options of a program in a form for
+       use with the optParseOptions2() function.
+       */
+    unsigned char short_allowed;  /* boolean */
+        /* The syntax may include short (i.e. one-character) options.
+           These options may be stacked within a single token (e.g.
+           -abc = -a -b -c).  If this value is not true, the short option
+           member of the option table entry is meaningless and long 
+           options may have either one or two dashes.
+           */
+    unsigned char allowNegNum;  /* boolean */
+        /* Anything that starts with - and then a digit is a numeric
+           parameter, not an option 
+           */
+    optStruct *opt_table;
+} optStruct2;
+
+typedef struct {
+    /* Same as optStruct2, but for optParseOptions3() */
+    unsigned char short_allowed;  
+    unsigned char allowNegNum;    
+    optEntry *opt_table;
+} optStruct3;
+
+
+/* You can use OPTENTRY to assign a value to a dynamically or automatically
+   allocated optStruct structure with minimal typing and easy readability.
+
+   Here is an example:
+
+       unsigned int option_def_index = 0;
+       optStruct *option_def = malloc(100*sizeof(optStruct));
+       OPTENTRY('h', "help",     OPT_FLAG, &help_flag, 0);
+       OPTENTRY(0,   "alphaout", OPT_STRING, &alpha_filename, 0);
+*/   
+
+/* If you name your variables option_def and option_def_index like in the 
+   example above, everything's easy.  If you want to use OPTENTRY with other
+   variables, define macros OPTION_DEF and OPTION_DEF_INDEX before calling
+   OPTENTRY.
+*/
+
+#ifndef OPTION_DEF
+#define OPTION_DEF option_def
+#endif
+#ifndef OPTION_DEF_INDEX
+#define OPTION_DEF_INDEX option_def_index
+#endif
+
+#define OPTENTRY(shortvalue,longvalue,typevalue,outputvalue,flagvalue) {\
+    OPTION_DEF[OPTION_DEF_INDEX].shortName = (shortvalue); \
+    OPTION_DEF[OPTION_DEF_INDEX].longName = (longvalue); \
+    OPTION_DEF[OPTION_DEF_INDEX].type = (typevalue); \
+    OPTION_DEF[OPTION_DEF_INDEX].arg = (outputvalue); \
+    OPTION_DEF[OPTION_DEF_INDEX].flags = (flagvalue); \
+    OPTION_DEF[++OPTION_DEF_INDEX].type = OPT_END; \
+    }
+
+/* OPTENT3 is the same as OPTENTRY except that it also sets the "specified"
+   element of the table entry (so it assumes OPTION_DEF is a table of
+   optEntry instead of optStruct).
+
+   Here is an example:
+
+       unsigned int option_def_index = 0;
+       optEntry *option_def = malloc(100*sizeof(optEntry));
+       OPTENT3('h', "help",     OPT_FLAG, &help_flag, 0);
+       OPTENT3(0,   "alphaout", OPT_STRING, &alpha_filename, 0);
+*/
+
+#define OPTENT3(shortvalue,longvalue,typevalue,outputvalue,specifiedvalue, \
+                flagvalue) {\
+    OPTION_DEF[OPTION_DEF_INDEX].specified = (specifiedvalue); \
+    OPTENTRY(shortvalue, longvalue, typevalue, outputvalue, flagvalue) \
+    }
+
+
+struct optNameValue {
+    const char * name;
+    const char * value;
+};
+
+
+        
+void optSetFatalFunc(void (*f)(const char *, ...));
+void optParseOptions(int *argc, char *argv[],
+		     optStruct opt[], int allowNegNum);
+void
+optParseOptions2(int * const argc_p, char *argv[], const optStruct2 opt, 
+                 const unsigned long flags);
+void
+optParseOptions3(int * const argc_p, char *argv[], const optStruct3 opt, 
+                 const unsigned int optStructSize, const unsigned long flags);
+
+void
+optDestroyNameValueList(struct optNameValue * const list);
+
+#ifdef __cplusplus
+}
+#endif
+
+/* Here's a hack to help us introduce pm_c_util.h.  That should be
+   #included in nearly every Netpbm program, but we're too lazy to insert
+   it into hundreds of files right now.  But shhopt.h is included into
+   most of those files, so we #include it here!
+
+   Before 2005.12.03, the stuff that is now in pm_c_util.h was in pm.h,
+   but that's a bad place for it because pm.h is an external header file.
+*/
+#include "pm_c_util.h"
+
+#endif
diff --git a/lib/util/testnstring.c b/lib/util/testnstring.c
new file mode 100644
index 00000000..87e6139e
--- /dev/null
+++ b/lib/util/testnstring.c
@@ -0,0 +1,30 @@
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "nstring.h"
+
+#define true (1)
+#define false (0)
+
+int
+main(int argc, char **argv) {
+
+    char snprintfNResult[80];
+    const char * asprintfNResult; 
+
+    printf("Hello world.\n");
+
+    snprintfN(snprintfNResult, 19, "snprintfN test truncated");
+
+    printf("snprintfN result='%s'\n", snprintfNResult);
+
+    asprintfN(&asprintfNResult, "asprintf'ed string %d %u %s", 
+              5, 89, "substring");
+
+    printf("asprintfN result='%s'\n", asprintfNResult);
+
+    strfree(asprintfNResult);
+    return 0;
+}
diff --git a/lib/util/wordaccess.h b/lib/util/wordaccess.h
new file mode 100644
index 00000000..28963aee
--- /dev/null
+++ b/lib/util/wordaccess.h
@@ -0,0 +1,65 @@
+#ifndef WORDACCESS_H_INCLUDED
+#define WORDACCESS_H_INCLUDED
+
+/* These are facilities for accessing data in C programs in ways that
+   exploit the way the machine defines words in order to squeeze out
+   speed and CPU efficiency.
+
+   In particular, routines in this file exploit the endianness of the
+   machine and use explicit machine instructions to access C
+   variables.
+
+   A word is the amount of data that fits in a register; the amount of
+   data that a single machine instruction can process.  For example,
+   on IA32, a word is 32 bits because a single load or store
+   instruction moves that many bits and a single add instruction
+   operates on that many bits.
+
+
+   These facilities revolve around two data types:  wordInt and
+   wordIntBytes.
+
+   wordint is an unsigned integer with precision (size) of one word.
+   It is just the number -- nothing is implied about how it is
+   represented in memory.
+
+   wordintBytes is an array of bytes that represent a word-sized
+   unsigned integer.  x[0] is the high order 8 digits of the binary
+   coding of the integer, x[1] the next highest 8 digits, etc.
+   Note that it has big-endian form, regardless of what endianness the
+   underlying machine uses.
+
+   The actual size of word differs by machine.  Usually it is 32 or 64
+   bits.  Logically it can be as small as one byte.  Fixed bit sequences
+   in each program impose a lower limit of word width.  For example, the
+   longest bit sequence in pbmtog3 has 13 bits, so an 8-bit word won't
+   work with that.
+
+   We also assume that a char is 8 bits.
+*/
+#if (!defined(WORDACCESS_GENERIC) \
+     && defined(__GNUC__) && defined(__GLIBC__) \
+     && (__GNUC__ * 100 + __GNUC_MINOR__ >= 304) )
+
+    #if BYTE_ORDER==BIG_ENDIAN    /* defined by GCC */
+
+        #include "wordaccess_gcc3_be.h"
+
+    #elif defined(__ia64__) || defined(__amd64__) || defined(__x86_64__)
+         /* all these macros are defined by GCC */
+
+        #include "wordaccess_64_le.h"
+
+    #else
+
+        #include "wordaccess_gcc3_le.h"
+
+    #endif
+
+#else
+
+    #include "wordaccess_generic.h"
+
+#endif
+
+#endif
diff --git a/lib/util/wordaccess_64_le.h b/lib/util/wordaccess_64_le.h
new file mode 100644
index 00000000..2343b6d4
--- /dev/null
+++ b/lib/util/wordaccess_64_le.h
@@ -0,0 +1,52 @@
+/*=============================================================================
+  This file is the part of wordaccess.h for use under these
+  conditions:
+
+  * GCC (>=3.4), GLIBC
+  * 64 bit Little-Endian machines (IA64, X86-64, AMD64)
+=============================================================================*/
+   
+/*  
+    64 bit hton and ntoh do not exist.  Here we use bswap_64.
+    
+    While bswap_64 works on 64 bit data, __builtin_clzl works on "long" which
+    may or may not be 64 bits.  Code provided to find the right data type and
+    file off any extra when necessary.
+*/
+
+#include <byteswap.h>  /* See note above on bswap_64 */
+ 
+typedef uint64_t wordint;
+typedef unsigned char wordintBytes[sizeof(wordint)];
+
+static __inline__ wordint
+bytesToWordint(wordintBytes bytes) {
+    return ((wordint) bswap_64(*(wordint *)bytes));
+}
+
+
+
+static __inline__ void
+wordintToBytes(wordintBytes * const bytesP,
+               wordint        const wordInt) {
+    *(wordint *)bytesP = bswap_64(wordInt);
+}
+
+
+
+static __inline__ unsigned int
+wordintClz(wordint const x){
+
+    unsigned int s;
+
+    if (x == 0)
+        return sizeof(wordint) * 8;
+
+    /* Find the data type closest to 64 bits, and file off any extra. */
+    else if ((s=sizeof(long int)) >= 8)
+        return (__builtin_clzl((int)x << (s - 8) * 8));
+    else if ((s=sizeof(long long int)) >= 8)
+        return (__builtin_clzll((long long int)x << (s - 8) * 8));
+    else
+        pm_error("Long long int is less than 64 bits on this machine"); 
+}
diff --git a/lib/util/wordaccess_gcc3_be.h b/lib/util/wordaccess_gcc3_be.h
new file mode 100644
index 00000000..6f5d86fc
--- /dev/null
+++ b/lib/util/wordaccess_gcc3_be.h
@@ -0,0 +1,40 @@
+/*=============================================================================
+  This file is the part of wordaccess.h for use under these
+  conditions:
+
+  * GCC (>=3.4), GLIBC
+  * Big-Endian machines
+  
+  __builtin_clz is available on GCC 3.4 and above
+     
+  Note that the clz scheme does not work and requires adjustment
+  if long type does not make use of all bits for data storage.
+  
+  This is unlikely.  According to GNU MP (http://www.swox.com/gmp/),
+  in rare cases such as Cray, there are smaller data types that take up
+  the same space as long, but leave the higher bits silent.  Currently,
+  there are no known such cases for data type long.
+*===========================================================================*/
+
+typedef unsigned long int wordint;
+typedef unsigned char wordintBytes[sizeof(wordint)];
+
+static __inline__ wordint
+bytesToWordint(wordintBytes bytes) {
+    return *((wordint *)bytes);
+}
+
+
+
+static __inline__ void
+wordintToBytes(wordintBytes * const bytesP,
+               wordint        const wordInt) {
+    *(wordint *)bytesP = wordInt;
+}
+
+
+
+static __inline__ unsigned int
+wordintClz(wordint const x) {
+    return (x==0 ? sizeof(wordint)*8 : __builtin_clzl(x));
+}
diff --git a/lib/util/wordaccess_gcc3_le.h b/lib/util/wordaccess_gcc3_le.h
new file mode 100644
index 00000000..7db218db
--- /dev/null
+++ b/lib/util/wordaccess_gcc3_le.h
@@ -0,0 +1,54 @@
+/*=============================================================================
+
+  This file is the part of wordaccess.h for use under these
+  conditions:
+
+  * GCC (>=3.4), GLIBC
+  * 32 bit Little-Endian machines (intel MPUs 80386, Pentium, etc.)
+  * Other non-Big-Endian machines (very rare)
+  
+=============================================================================*/
+
+typedef uint32_t wordint;
+typedef unsigned char wordintBytes[sizeof(wordint)];
+
+#include <sys/types.h>
+#include <netinet/in.h>
+
+/*
+  Here we use the more widely used functions htonl and ntohl instead of
+  bswap_32.  This makes possible the handling of weird byte ordering
+  (neither Big-Endian nor Little-Endian) schemes, if any.
+*/
+
+static __inline__ wordint
+bytesToWordint(wordintBytes const bytes) {
+    return (wordint) ntohl(*(wordint *)bytes);
+}
+
+
+
+static __inline__ void
+wordintToBytes(wordintBytes * const bytesP,
+               wordint        const wordInt) {
+
+    *(wordint *)bytesP = htonl(wordInt);
+}
+
+
+
+static __inline__ unsigned int
+wordintClz(wordint const x) {
+
+    /* Find the data type closest to 32 bits, and file off any extra.  */
+
+    if (x == 0)
+        return sizeof(wordint) * 8;
+    else if (sizeof(int) >= 4)
+        return __builtin_clz((int)x << (sizeof(int) - 4) * 8);
+    else if (sizeof(long int) >= 4)
+        return __builtin_clzl((long int)x << (sizeof(long int) - 4) * 8);
+    else
+        pm_error("Long int is less than 32 bits on this machine"); 
+}
+
diff --git a/lib/util/wordaccess_generic.h b/lib/util/wordaccess_generic.h
new file mode 100644
index 00000000..7f27ef74
--- /dev/null
+++ b/lib/util/wordaccess_generic.h
@@ -0,0 +1,89 @@
+/*=============================================================================
+
+  This file is the part of wordaccess.h for use under these
+  conditions:
+
+  * Compilers other than GCC
+  * GCC before version 3.4
+  * c libraries other than Glibc
+  * Specified by the user with WORDACCESS_GENERIC
+=============================================================================*/
+
+typedef uint32_t wordint;
+typedef unsigned char wordintBytes[sizeof(wordint)];
+    
+static __inline__ wordint
+bytesToWordint(wordintBytes const bytes) {
+    wordint retval;
+    unsigned int i;
+
+    /* Note that 'bytes' is a pointer, due to C array degeneration.
+       That means sizeof(bytes) isn't what you think it is.
+    */
+    
+    for (i = 1, retval = bytes[0]; i < sizeof(wordint); ++i) {
+        retval = (retval << 8) + bytes[i];
+    }
+    return retval;
+}
+
+
+
+static __inline__ void
+wordintToBytes(wordintBytes * const bytesP,
+               wordint        const wordInt) {
+
+    wordint buffer;
+    int i;
+
+    for (i = sizeof(*bytesP)-1, buffer = wordInt; i >= 0; --i) {
+        (*bytesP)[i] = buffer & 0xFF;
+        buffer >>= 8;
+    }
+}
+    
+static unsigned char const clz8[256]= {
+    8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4,
+    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 
+};
+
+
+
+static __inline__ unsigned int
+clz16(wordint const x) {
+    if (x >> 8  != 0)
+        return clz8[x >> 8];
+    else
+        return clz8[x] + 8;
+}
+
+
+
+static __inline__  unsigned int
+clz32(wordint const x) {
+    if (x >> 16  != 0)
+        return clz16(x >> 16);
+    else
+        return clz16(x) +16;
+}
+
+
+
+static __inline__  unsigned int
+wordintClz(wordint const x) {
+    return clz32(x);
+}
diff --git a/manweb b/manweb
new file mode 100755
index 00000000..988c8f68
--- /dev/null
+++ b/manweb
@@ -0,0 +1,427 @@
+#!/usr/bin/perl -w
+
+use strict;
+use English;
+#use File::stat;
+use Errno;
+use Fcntl ':mode';
+use Getopt::Long;
+
+my $FALSE = 0;
+my $TRUE = !$FALSE;
+
+our $debug;
+    
+
+sub giveHelp() {
+
+    print("Manweb is a replacement for Man.  It gets reference \n");
+    print("documentation from the Worldwide Web or a private web. \n");
+    print("Manweb is distributed with the Netpbm package \n");
+    print("(http://netpbm.sourceforge.net).\n");
+    print("\n");
+    print("Documentation of Manweb is at \n");
+    print("\n");
+    print("        http://netpbm.sourceforge.net/doc/manweb.html\n");
+    print("\n");
+    print("Or if you have it properly installed, just use the command \n");
+    print("\n");
+    print("        manweb manweb \n");
+}
+
+
+sub debug(@) {
+    if ($debug) {
+        print(STDERR @_, "\n");
+    }
+}
+
+
+sub findUrl($@);  # findUrl() is recursive.
+
+sub findUrl($@) {
+    my ($webdir, @topicList) = @_;
+#-----------------------------------------------------------------------------
+#  Starting in the directory $webdir, find the URL for the documentation
+#  of the topic identified by @topicList.  @topicList is a main topic
+#  followed by a subtopic of that topic, and so on.
+#
+#  If @topicList is an empty list, return the url that refers to the 
+#  directory $webdir itself.
+#-----------------------------------------------------------------------------
+    my $url;
+
+    if (@topicList == 0) {
+        # He's not specifying a topic; that means he just wants the index
+        # of the specified directory -- but only if it exists.
+
+        if (-d($webdir)) {
+            $url = directoryUrl($webdir);
+        } 
+    } else {
+        my $topic0 = shift(@topicList);
+
+        # First look for a .url file 
+
+        $url = doturl($webdir, $topic0, @topicList);
+        if (!defined($url)) {
+            # No .url file.  Look for directory.
+            
+            my $subwebdir = "$webdir/$topic0";
+            if (-d($subwebdir)) {
+                $url = findUrl($subwebdir, @topicList);
+            } else {
+                # No directory.  Look for html file.
+                my $htmlfilename = "$webdir/$topic0.html";
+            
+                if (-f($htmlfilename)) {
+                    if (@topicList > 0) {
+                        print(STDERR 
+                              "Ignoring subtopic chain '@topicList' because " .
+                              "There is an html file named " .
+                              "'$htmlfilename'.\n");
+                    } 
+                    $url = "file://$htmlfilename";
+                }
+            }
+        }
+    }
+    return($url);
+}
+
+
+
+sub findUrlInPath($@) {
+    my ($webdirR, @topicList) = @_;
+
+    my @webdirLeft = @$webdirR;
+
+    my $url;
+
+    for (my $webdir = shift(@webdirLeft);
+         defined($webdir) && !defined($url);
+         $webdir = shift(@webdirLeft)) {
+
+        $url = findUrl($webdir, @topicList);
+    }
+    return $url;
+}
+
+
+
+sub directoryUrl($$) {
+    # If this directory has an index file, that's the URL.  Otherwise
+    # it's just the directory itself.  Too bad the browser doesn't do
+    # this for us, like it does for HTTP URLs.
+
+    my ($webdir) = @_;
+    my ($dev, $ino, $mode, $rest) = stat("$webdir/index.html");
+
+    my $url;
+
+    if (defined($mode) && S_ISREG($mode)) {
+        $url = "file://$webdir/index.html";
+    } else {
+        my ($dev, $ino, $mode, $rest) = stat("$webdir/index.htm");
+        if (defined($mode) && S_ISREG($mode)) {
+            $url = "file://$webdir/index.htm";
+        } else {
+            $url = "file://$webdir";
+        }
+    }
+    return($url);
+}
+
+
+
+
+sub doturl($$) {
+    my ($webdir, $topic0, @topicList) = @_;
+#-----------------------------------------------------------------------------
+#  Handle a .url file.
+#
+#  If there is a file named "$topic0.url" in the directory $webdir,
+#  return the URL that gets to the proper web page for subtopic list
+#  @topiclist with respect to the URL in that .url file.
+#
+#  If there's no such .url file, though, return an undefined value.
+#-----------------------------------------------------------------------------
+    my $url;
+
+    my $urlfilename = "$webdir/$topic0.url";
+
+    my $openworked = open(URLFILE, "<$urlfilename");
+        
+    if ($openworked) {
+        my @url = <URLFILE>;
+        if (@url == 0) {
+            die("URL file '$urlfilename' is empty.");
+        } elsif (@url > 1) {
+            die("URL file '$urlfilename' contains more than one line.");
+        } else {
+            my $topUrl = $url[0];
+            chomp($topUrl);
+            if (@topicList > 0) {
+                if ($topUrl =~ m|.*[^/]$|) {
+                    print(STDERR 
+                          "Ignoring subtopic chain '@topicList' because " .
+                          "URL '$topUrl' is not a directory URL.\n");
+                }
+                $url = $topUrl . join("/", @topicList) . ".html";
+            } else {
+                $url = $topUrl;
+            }
+        }
+    }
+    return($url);
+}
+
+
+
+sub executablePathUrl($) {
+    my ($progName) = @_;
+#-----------------------------------------------------------------------------
+#  If $progName is the name of a program that would be found in the
+#  program search path (as defined by the PATH environment variable),
+#  and the directory in which the program resides contains a file
+#  .docurl, return the first line of that file, appended with
+#  "$progName.html" as the URL.  If the line from the file doesn't end
+#  with a slash, though, just return the line itself.
+#
+#  If $progName is not such a program name, or there is no .docurl,
+#  return undefined. 
+#-----------------------------------------------------------------------------
+    my $url;
+
+    my @path = split(/:/,$ENV{"PATH"});
+    
+    my $i;
+    my $progDir;
+    for ($i = 0; $i < @path && !$progDir; ++$i) {
+        my $testProgName = $path[$i] . "/" . $progName;
+        if (-x($testProgName) && -f($testProgName)) {
+            $progDir = $path[$i];
+        }
+    }
+
+    if ($progDir) {
+        debug("Found program '$progName' in directory '$progDir'");
+        my $urlfilename = "$progDir/doc.url";
+        if (-f($urlfilename)) {
+            debug("Looking at file '$urlfilename'");
+            my $openworked = open(URLFILE, "<$urlfilename");
+        
+            if ($openworked) {
+                my @url = <URLFILE>;
+                if (@url == 0) {
+                    die("URL file '$urlfilename' is empty.");
+                } elsif (@url > 1) {
+                    die("URL file '$urlfilename' contains more " .
+                        "than one line.");
+                } else {
+                    my $topUrl = $url[0];
+                    chomp($topUrl);
+                    debug("doc.url file contains URL '$topUrl'");
+                    if ($topUrl =~ m|.*[^/]$|) {
+                        $url = $topUrl;
+                    } else {
+                        $url = "$topUrl/$progName.html";
+                    }
+                }
+            } else {
+                die("Unable to open file '$urlfilename'.");
+            }
+        }
+    }
+
+    return($url);
+}
+
+
+
+sub infoTopicExists($) {
+    my ($searchtopic) = @_;
+
+    if (!defined($searchtopic)) {
+        die("no topic passed to infoTopicExists");
+    }
+    
+    my $infopath = ($ENV{"INFOPATH"} or "/usr/info");
+    
+    my @infopath = split(/:/, $infopath);
+    
+    my $found;
+    
+    $found = $FALSE;
+
+    for (my $infodir = shift(@infopath);
+         defined($infodir) && !$found; 
+         $infodir = shift(@infopath)) {
+
+        my $opened = open(my $dirfile, "<$infodir/dir");
+
+        if ($opened) {
+            while ((defined(my $dirfileline = <$dirfile>)) && !$found) {
+                if ($dirfileline =~ m{^\* (.*):}) {
+                    my $topic = $1;
+                    
+                    if (lc($topic) eq lc($searchtopic)) {
+                        $found = $TRUE;
+                    }
+                }
+            }
+            close($dirfile);
+        }
+    }
+    return $found;
+}
+
+
+sub validateWebdir($@) {
+    my ($confFile, @webdir) = @_;
+
+    foreach my $webdir (@webdir) {
+
+        if ($webdir =~ m{^[^/]}) {
+            die("webdir component '$webdir' " .
+                "in configuration file '$confFile' " .
+                "is not valid.  It must be an absolute path, and " .
+                "therefore start with a slash.");
+        } elsif ($webdir =~ m{^//}) {
+            # Two slashes would cause a unique problem when we try
+            # to make a file: URL out of it.
+            die("webdir component '$webdir' " .
+                "in configuration file '$confFile' " .
+                "is not valid.  It starts with two slashes.");
+        }
+    }
+}
+
+
+
+sub readConfFile($) {
+#-----------------------------------------------------------------------------
+#  Read the configuration file (/etc/manweb.conf or value of
+#  MANWEB_CONF_FILE or named by our argument).  Return values set in
+#  it, or defaults.
+#-----------------------------------------------------------------------------
+    my ($fileArg) = @_;
+    
+    my $confFile;
+
+    if (defined($fileArg)) {
+        $confFile = $fileArg;
+    } else {
+        my $envVblValue = $ENV{"MANWEB_CONF_FILE"};
+        if (defined($envVblValue)) {
+            $confFile = $envVblValue;
+        } else {
+            $confFile = "/etc/manweb.conf";
+        }
+    }
+
+    open(CONF, "<$confFile") or die("Can't open configuration file " .
+                                    "'$confFile'.  $ERRNO");
+    
+    my (@webdir, $browser);
+
+    while(<CONF>) {
+        chomp();
+        if (/^\s*#/) {
+            #It's comment - ignore
+        } elsif (/^\s*$/) {
+            #It's a blank line - ignore
+        } elsif (/\s*(\S+)\s*=\s*(\S+)/) {
+            #It looks like "keyword=value"
+            my ($keyword, $value) = ($1, $2);
+            if ($keyword eq "webdir") {
+                @webdir = split(/:/, $value);
+                validateWebdir($confFile, @webdir);
+            } elsif ($keyword eq "browser") {
+                $browser = $value;
+            } else {
+                die("Unrecognized keyword in configuration file '$confFile': " 
+                    . "'$keyword'");
+            }
+        } else {
+            die("Invalid syntax in configuration file line '$_'.  " .
+                "Must be keyword=value, #comment, or blank line");
+            }
+    }              
+    close(CONF);
+
+    if (!@webdir) {
+        @webdir = ("/usr/man/web");
+    }
+    if (!defined($browser)) {
+        $browser = $ENV{"BROWSER"} ? $ENV{"BROWSER"} : "lynx";
+    }
+    
+    return(\@webdir, $browser);
+}
+
+
+
+##############################################################################
+#                               MAINLINE
+##############################################################################
+
+my ($optConfig, $optHelp, $optDebug);
+
+my $validOptions = GetOptions("config=s" => \$optConfig,
+                              "help" => \$optHelp,
+                              "debug" => \$optDebug,
+                              );
+
+if (!$validOptions) { print(STDERR "Invalid syntax.\n"); exit(1); }
+
+if ($optHelp) { 
+    giveHelp(); 
+    exit(0);
+}
+
+$debug = $optDebug;
+
+my ($webdirR, $browser) = readConfFile($optConfig);
+
+my $url;
+
+my $directUrl = findUrlInPath($webdirR, @ARGV);
+
+if (defined($directUrl)) {
+    $url = $directUrl;
+    debug("Found URL in doc search path");
+} else {
+    if (@ARGV == 1) {
+        $url = executablePathUrl($ARGV[0]);
+        if (defined($url)) {debug("Found URL via executable path");}
+    }
+}
+
+if (defined($url)) {
+    print(STDERR "Browsing URL '$url'...\n");
+    system($browser, $url);
+} else {
+    if (@ARGV == 1) {
+        if (infoTopicExists($ARGV[0])) {
+            print(STDERR 
+                  "No web doc, but 'info' topic found.  Running 'info'...\n");
+            system("info", $ARGV[0]);
+        } else {
+            my $mantopic = $ARGV[0];
+            print(STDERR 
+                  "No web doc.  Running 'man' on topic '$mantopic'...\n");
+            system("man", $mantopic);
+        }
+    } elsif (@ARGV == 2 && $ARGV[0] =~ m{\d+}) {
+        my ($mansection, $mantopic) = @ARGV;
+        print(STDERR
+              "No web doc.  Running 'man ' on Section $mansection, " .
+              "Topic '$mantopic'...\n");
+        system("man", $mansection, $mantopic);
+    } else {
+        print(STDERR "No web documentation found for topic chain @ARGV " .
+              "and it isn't in the right form to try a man page\n");
+        exit(1);
+    }
+}
diff --git a/netpbm.c b/netpbm.c
new file mode 100644
index 00000000..c47cb37e
--- /dev/null
+++ b/netpbm.c
@@ -0,0 +1,80 @@
+/* netpbm.c - merge of all the Netpbm programs
+
+   Derived from pbmmerge.c, etc. by Bryan Henderson May 2002.  Copyright
+   notice from pbmmerge.c, etc:
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* Note: be careful using any Netpbm library functions in here, since
+   we don't call pnm_init()
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include "pam.h"
+
+#define TRY(s,m) { \
+    extern int m(int argc, char *argv[]); \
+    if (strcmp(cp, s) == 0) exit(m(argc, argv)); \
+}
+
+int
+main(int argc, char *argv[]) {
+
+    char* cp;
+    
+    if (strcmp(pm_arg0toprogname(argv[0]), "netpbm") == 0) {
+        ++argv;
+        --argc;
+        if (argc < 1 || !*argv)	{
+            fprintf(stderr, "Usage: netpbm netpbm_program_name [args ...]\n");
+            exit(1);
+		}
+	}
+
+    cp = pm_arg0toprogname(argv[0]);
+    
+    /* merge.h is an automatically generated file that generates code to
+       match the string 'cp' against the name of every program that is part
+       of this merge and, upon finding a match, invoke that program.
+    */
+
+/* The following inclusion is full of TRY macro invocations */
+
+#include "mergetrylist"
+
+    /* Add the obsolete names for some programs */
+    TRY("gemtopbm", main_gemtopnm);
+    TRY("pnminterp", main_pamstretch);
+    TRY("pgmoil", main_pamoil);
+
+    /* We don't do the ppmtojpeg alias because if user doesn't have a JPEG
+       library, there is no main_pnmtojpeg library.  The right way to do
+       this is to have these TRY's generated by the subdirectory makes,
+       which would know whether pnmtojpeg was built into the merged binary
+       or not.  But that's too much work.
+
+    TRY("ppmtojpeg", main_pnmtojpeg); 
+    */
+    TRY("bmptoppm", main_bmptopnm);
+    TRY("pgmnorm", main_pnmnorm);
+    TRY("ppmnorm", main_pnmnorm);
+    TRY("ppmtotga", main_pamtotga);
+    TRY("pnmarith", main_pamarith);
+    TRY("pnmfile", main_pamfile);
+    TRY("pgmedge", main_pamedge);
+
+    fprintf(stderr,"'%s' is an unknown Netpbm program name \n", cp );
+    exit(1);
+}
+
+
+
diff --git a/other/Makefile b/other/Makefile
new file mode 100644
index 00000000..87f92f96
--- /dev/null
+++ b/other/Makefile
@@ -0,0 +1,75 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = other
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+SUBDIRS = pamx
+
+ifneq ($(LINUXSVGALIB),NONE)
+  ifneq ($(LINUXSVGAHDR_DIR),)
+    INCLUDES += -I$(LINUXSVGAHDR_DIR)
+  endif
+endif
+
+# We tend to separate out the build targets so that we don't have
+# any more dependencies for a given target than it really needs.
+# That way, if there is a problem with a dependency, we can still
+# successfully build all the stuff that doesn't depend upon it.
+# This package is so big, it's useful even when some parts won't 
+# build.
+
+PORTBINARIES = pamarith pambayer pamchannel pamdepth \
+	pamendian pamlookup pampick pamsplit \
+	pamstack pamsummcol pnmcolormap \
+	ppmdcfont ppmddumpfont ppmdmkfont 
+
+BINARIES = $(PORTBINARIES)
+
+ifneq ($(LINUXSVGALIB),NONE)
+  BINARIES += ppmsvgalib
+endif
+
+SCRIPTS = ppmtomap
+
+OBJECTS = $(BINARIES:%=%.o)
+
+# We don't include programs that have special library dependencies in the
+# merge scheme, because we don't want those dependencies to prevent us
+# from building all the other programs.
+
+MERGEBINARIES = $(BINARIES)
+MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2)
+
+.PHONY: all
+all: $(BINARIES) $(SUBDIRS:%=%/all)
+
+include $(SRCDIR)/Makefile.common
+
+ppmsvgalib: %: %.o $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $< \
+	  $(shell $(LIBOPT) $(NETPBMLIB) $(LINUXSVGALIB)) \
+	  $(MATHLIB) $(LDLIBS) \
+	  $(LADD) 
+
+install.bin: install.bin.local
+.PHONY: install.bin.local
+install.bin.local: $(PKGDIR)/bin
+# Remember that $(SYMLINK) might just be a copy command.
+# In July 2002, pamarith replaced pnmarith
+	cd $(PKGDIR)/bin ; \
+	  rm -f pnmarith ; \
+	  $(SYMLINK) pamarith$(EXE) pnmarith
+# In December 2005, pamsplit replaced pnmsplit
+	cd $(PKGDIR)/bin ; \
+	  rm -f pnmsplit ; \
+	  $(SYMLINK) pamsplit$(EXE) pnmsplit
+# In February 2006, pamdepth replaced pnmdepth
+	cd $(PKGDIR)/bin ; \
+	  rm -f pnmdepth ; \
+	  $(SYMLINK) pamdepth$(EXE) pnmdepth
+
+FORCE:
diff --git a/other/pamarith.c b/other/pamarith.c
new file mode 100644
index 00000000..c1e7f1ed
--- /dev/null
+++ b/other/pamarith.c
@@ -0,0 +1,503 @@
+#include <assert.h>
+#include <string.h>
+
+#include "shhopt.h"
+#include "pam.h"
+
+enum function {FN_ADD, FN_SUBTRACT, FN_MULTIPLY, FN_DIVIDE, FN_DIFFERENCE,
+               FN_MINIMUM, FN_MAXIMUM, FN_MEAN, FN_COMPARE,
+               FN_AND, FN_OR, FN_NAND, FN_NOR, FN_XOR,
+               FN_SHIFTLEFT, FN_SHIFTRIGHT
+              };
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input1Filespec;  
+    const char *input2Filespec;  
+    enum function function;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    
+    unsigned int addSpec, subtractSpec, multiplySpec, divideSpec,
+        differenceSpec,
+        minimumSpec, maximumSpec, meanSpec, compareSpec,
+        andSpec, orSpec, nandSpec, norSpec, xorSpec,
+        shiftleftSpec, shiftrightSpec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "add",         OPT_FLAG,   NULL, &addSpec,        0);
+    OPTENT3(0, "subtract",    OPT_FLAG,   NULL, &subtractSpec,   0);
+    OPTENT3(0, "multiply",    OPT_FLAG,   NULL, &multiplySpec,   0);
+    OPTENT3(0, "divide",      OPT_FLAG,   NULL, &divideSpec,     0);
+    OPTENT3(0, "difference",  OPT_FLAG,   NULL, &differenceSpec, 0);
+    OPTENT3(0, "minimum",     OPT_FLAG,   NULL, &minimumSpec,    0);
+    OPTENT3(0, "maximum",     OPT_FLAG,   NULL, &maximumSpec,    0);
+    OPTENT3(0, "mean",        OPT_FLAG,   NULL, &meanSpec,       0);
+    OPTENT3(0, "compare",     OPT_FLAG,   NULL, &compareSpec,    0);
+    OPTENT3(0, "and",         OPT_FLAG,   NULL, &andSpec,        0);
+    OPTENT3(0, "or",          OPT_FLAG,   NULL, &orSpec,         0);
+    OPTENT3(0, "nand",        OPT_FLAG,   NULL, &nandSpec,       0);
+    OPTENT3(0, "nor",         OPT_FLAG,   NULL, &norSpec,        0);
+    OPTENT3(0, "xor",         OPT_FLAG,   NULL, &xorSpec,        0);
+    OPTENT3(0, "shiftleft",   OPT_FLAG,   NULL, &shiftleftSpec,  0);
+    OPTENT3(0, "shiftright",  OPT_FLAG,   NULL, &shiftrightSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (addSpec + subtractSpec + multiplySpec + divideSpec + differenceSpec +
+        minimumSpec + maximumSpec + meanSpec + compareSpec +
+        andSpec + orSpec + nandSpec + norSpec + xorSpec +
+        shiftleftSpec + shiftrightSpec > 1)
+        pm_error("You may specify only one function");
+
+    if (argc-1 != 2)
+        pm_error("You must specify two arguments:  the files which are "
+                 "the operands of the "
+                 "dyadic function.  You specified %d", argc-1);
+    else {
+        cmdlineP->input1Filespec = argv[1];
+        cmdlineP->input2Filespec = argv[2];
+    }
+
+    if (addSpec)
+        cmdlineP->function = FN_ADD;
+    else if (subtractSpec)
+        cmdlineP->function = FN_SUBTRACT;
+    else if (multiplySpec)
+        cmdlineP->function = FN_MULTIPLY;
+    else if (divideSpec)
+        cmdlineP->function = FN_DIVIDE;
+    else if (differenceSpec)
+        cmdlineP->function = FN_DIFFERENCE;
+    else if (minimumSpec)
+        cmdlineP->function = FN_MINIMUM;
+    else if (maximumSpec)
+        cmdlineP->function = FN_MAXIMUM;
+    else if (meanSpec)
+        cmdlineP->function = FN_MEAN;
+    else if (compareSpec)
+        cmdlineP->function = FN_COMPARE;
+    else if (andSpec)
+        cmdlineP->function = FN_AND;
+    else if (orSpec)
+        cmdlineP->function = FN_OR;
+    else if (nandSpec)
+        cmdlineP->function = FN_NAND;
+    else if (norSpec)
+        cmdlineP->function = FN_NOR;
+    else if (xorSpec)
+        cmdlineP->function = FN_XOR;
+    else if (shiftleftSpec)
+        cmdlineP->function = FN_SHIFTLEFT;
+    else if (shiftrightSpec)
+        cmdlineP->function = FN_SHIFTRIGHT;
+    else
+        pm_error("You must specify a function (e.g. '-add')");
+}        
+
+
+
+enum category {
+    CATEGORY_FRACTIONAL_ARITH,
+        /* Arithmetic in which each sample represents a the fraction
+           sample/maxval.
+        */
+    CATEGORY_BITSTRING,
+        /* And, Or, etc.  Maxval isn't a scale factor at all; it's a mask. */
+    CATEGORY_SHIFT
+        /* Left argument is a bit string, but right argument is a whole
+           number (left maxval is a mask; right maxval is meaningless).
+        */
+};
+
+
+
+static enum category
+functionCategory(enum function const function) {
+
+    enum category retval;
+    
+    switch (function) {
+    case FN_ADD:
+    case FN_SUBTRACT:
+    case FN_DIFFERENCE:
+    case FN_MINIMUM:
+    case FN_MAXIMUM:
+    case FN_MEAN:
+    case FN_COMPARE:
+    case FN_MULTIPLY:
+    case FN_DIVIDE:
+        retval = CATEGORY_FRACTIONAL_ARITH;
+        break;
+    case FN_AND:
+    case FN_OR:
+    case FN_NAND:
+    case FN_NOR:
+    case FN_XOR:
+        retval = CATEGORY_BITSTRING;
+        break;
+    case FN_SHIFTLEFT:
+    case FN_SHIFTRIGHT:
+        retval = CATEGORY_SHIFT;
+        break;
+    }
+    return retval;
+}
+
+
+
+static void
+computeOutputType(struct pam *  const outpamP,
+                  struct pam    const inpam1,
+                  struct pam    const inpam2,
+                  enum function const function) {
+
+    outpamP->size        = sizeof(struct pam);
+    outpamP->len         = PAM_STRUCT_SIZE(tuple_type);
+    outpamP->file        = stdout;
+    outpamP->format      = MAX(inpam1.format, inpam2.format);
+    outpamP->plainformat = FALSE;
+    outpamP->height      = inpam1.height;
+    outpamP->width       = inpam1.width;
+    outpamP->depth       = MAX(inpam1.depth, inpam2.depth);
+
+    switch (functionCategory(function)) {    
+    case CATEGORY_FRACTIONAL_ARITH:
+        if (function == FN_COMPARE)
+            outpamP->maxval = 2;
+        else
+            outpamP->maxval = MAX(inpam1.maxval, inpam2.maxval);
+        break;
+    case CATEGORY_BITSTRING:
+        if (inpam2.maxval != inpam1.maxval)
+            pm_error("For a bit string operation, the maxvals of the "
+                     "left and right image must be the same.  You have "
+                     "left=%u and right=%u", 
+                     (unsigned)inpam1.maxval, (unsigned)inpam2.maxval);
+
+        if (pm_bitstomaxval(pm_maxvaltobits(inpam1.maxval)) != inpam1.maxval)
+            pm_error("For a bit string operation, the maxvals of the inputs "
+                     "must be a full binary count, i.e. a power of two "
+                     "minus one such as 0xff.  You have 0x%x",
+                     (unsigned)inpam1.maxval);
+
+        outpamP->maxval = inpam1.maxval;
+        break;
+    case CATEGORY_SHIFT:
+        if (pm_bitstomaxval(pm_maxvaltobits(inpam1.maxval)) != inpam1.maxval)
+            pm_error("For a bit shift operation, the maxval of the left "
+                     "input image "
+                     "must be a full binary count, i.e. a power of two "
+                     "minus one such as 0xff.  You have 0x%x",
+                     (unsigned)inpam1.maxval);
+        outpamP->maxval = inpam1.maxval;
+    }
+    outpamP->bytes_per_sample = (pm_maxvaltobits(outpamP->maxval)+7)/8;
+    strcpy(outpamP->tuple_type, inpam1.tuple_type);
+}
+
+
+
+static samplen
+applyNormalizedFunction(enum function const function,
+                        samplen       const leftArg,
+                        samplen       const rightArg) {
+
+    samplen result;
+
+    switch (function) {
+    case FN_ADD:
+        result = MIN(1., leftArg + rightArg);
+        break;
+    case FN_SUBTRACT:
+        result = MAX(0., leftArg - rightArg);
+        break;
+    case FN_MULTIPLY:
+        result = leftArg * rightArg;
+        break;
+    case FN_DIVIDE:
+        result = (rightArg > leftArg) ?
+        leftArg / rightArg : 1.;
+        break;
+    case FN_DIFFERENCE:
+        result = leftArg > rightArg ? 
+            leftArg - rightArg : rightArg - leftArg;
+        break;
+    case FN_MINIMUM:
+        result = MIN(leftArg, rightArg);
+        break;
+    case FN_MAXIMUM:
+        result = MAX(leftArg, rightArg);
+        break;
+    case FN_MEAN:
+        result = (leftArg + rightArg) / 2.0;
+        break;
+    case FN_COMPARE:
+        result = 
+            leftArg > rightArg ? 1. : leftArg < rightArg ? 0. : .5;
+        break;
+    default:
+        pm_error("Internal error.  applyNormalizedFunction() called "
+                 "for a function it doesn't know how to do: %u", function);
+    }
+
+    return result;
+}
+
+
+
+static void
+doNormalizedArith(struct pam *  const inpam1P,
+                  struct pam *  const inpam2P,
+                  struct pam *  const outpamP,
+                  enum function const function) {
+
+    tuplen * tuplerown1;
+    tuplen * tuplerown2;
+    tuplen * tuplerownOut;
+    unsigned int row;
+
+    tuplerown1   = pnm_allocpamrown(inpam1P);
+    tuplerown2   = pnm_allocpamrown(inpam2P);
+    tuplerownOut = pnm_allocpamrown(outpamP);
+
+    for (row = 0; row < outpamP->height; ++row) {
+        unsigned int col;
+        pnm_readpamrown(inpam1P, tuplerown1);
+        pnm_readpamrown(inpam2P, tuplerown2);
+        
+        for (col = 0; col < outpamP->width; ++col) {
+            unsigned int outplane;
+            
+            for (outplane = 0; outplane < outpamP->depth; ++outplane) {
+                unsigned int const plane1 = MIN(outplane, inpam1P->depth-1);
+                unsigned int const plane2 = MIN(outplane, inpam2P->depth-1);
+
+                tuplerownOut[col][outplane] = 
+                    applyNormalizedFunction(function, 
+                                            tuplerown1[col][plane1], 
+                                            tuplerown2[col][plane2]);
+                assert(tuplerownOut[col][outplane] >= 0.);
+                assert(tuplerownOut[col][outplane] <= 1.);
+
+            }
+        }
+        pnm_writepamrown(outpamP, tuplerownOut);
+    }
+
+    pnm_freepamrown(tuplerown1);
+    pnm_freepamrown(tuplerown2);
+    pnm_freepamrown(tuplerownOut);
+}
+
+
+
+static sample
+applyUnNormalizedFunction(enum function const function,
+                          sample        const leftArg,
+                          sample        const rightArg,
+                          sample        const maxval) {
+/*----------------------------------------------------------------------------
+   Apply dyadic function 'function' to the arguments 'leftArg' and
+   'rightArg', assuming both are based on the same maxval 'maxval'.
+   Return a value which is also a fraction of 'maxval'.
+
+   Exception: for the shift operations, 'rightArg' is not based on any
+   maxval.  It is an absolute bit count.
+-----------------------------------------------------------------------------*/
+    sample result;
+
+    switch (function) {
+    case FN_ADD:
+        result = MIN(maxval, leftArg + rightArg);
+        break;
+    case FN_SUBTRACT:
+        result = MAX(0, (int)leftArg - (int)rightArg);
+        break;
+    case FN_DIFFERENCE:
+        result = leftArg > rightArg ? leftArg - rightArg : rightArg - leftArg;
+        break;
+    case FN_MINIMUM:
+        result = MIN(leftArg, rightArg);
+        break;
+    case FN_MAXIMUM:
+        result = MAX(leftArg, rightArg);
+        break;
+    case FN_MEAN:
+        result = (leftArg + rightArg + 1) / 2;
+        break;
+    case FN_COMPARE:
+        result = leftArg > rightArg ? 2 : leftArg < rightArg ? 0 : 1;
+        break;
+    case FN_MULTIPLY:
+        result = (leftArg * rightArg + maxval/2) / maxval;
+        break;
+    case FN_DIVIDE:
+        result = (rightArg > leftArg) ?
+            (leftArg * maxval + rightArg/2) / rightArg : maxval;
+        break;
+
+    case FN_AND:
+        result = leftArg & rightArg;
+        break;
+    case FN_OR:
+        result = leftArg | rightArg;
+        break;
+    case FN_NAND:
+        result = ~(leftArg & rightArg) & maxval;
+        break;
+    case FN_NOR:
+        result = ~(leftArg | rightArg) & maxval;
+        break;
+    case FN_XOR:
+        result = leftArg ^ rightArg;
+        break;
+    case FN_SHIFTLEFT:
+        result = (leftArg << rightArg) & maxval;
+        break;
+    case FN_SHIFTRIGHT:
+        result = leftArg >> rightArg;
+        break;
+    default:
+        pm_error("Internal error.  applyUnNormalizedFunction() called "
+                 "for a function it doesn't know how to do: %u", function);
+    }
+
+    return result;
+}
+
+
+
+static void
+doUnNormalizedArith(struct pam *  const inpam1P,
+                    struct pam *  const inpam2P,
+                    struct pam *  const outpamP,
+                    enum function const function) {
+/*----------------------------------------------------------------------------
+   Take advantage of the fact that both inputs and the output use the same
+   maxval to do the computation without time-consuming normalization of
+   sample values.
+-----------------------------------------------------------------------------*/
+    sample const maxval = outpamP->maxval;
+
+    tuple * tuplerow1;
+    tuple * tuplerow2;
+    tuple * tuplerowOut;
+    unsigned int row;
+
+    /* Input conditions: */
+    assert(inpam1P->maxval == maxval);
+    assert(inpam2P->maxval == maxval);
+    assert(outpamP->maxval == maxval);
+
+    tuplerow1   = pnm_allocpamrow(inpam1P);
+    tuplerow2   = pnm_allocpamrow(inpam2P);
+    tuplerowOut = pnm_allocpamrow(outpamP);
+
+    for (row = 0; row < outpamP->height; ++row) {
+        unsigned int col;
+        pnm_readpamrow(inpam1P, tuplerow1);
+        pnm_readpamrow(inpam2P, tuplerow2);
+        
+        for (col = 0; col < outpamP->width; ++col) {
+            unsigned int outplane;
+            
+            for (outplane = 0; outplane < outpamP->depth; ++outplane) {
+                unsigned int const plane1 = MIN(outplane, inpam1P->depth-1);
+                unsigned int const plane2 = MIN(outplane, inpam2P->depth-1);
+
+                tuplerowOut[col][outplane] = 
+                    applyUnNormalizedFunction(function, 
+                                              tuplerow1[col][plane1], 
+                                              tuplerow2[col][plane2],
+                                              maxval);
+
+                assert(tuplerowOut[col][outplane] >= 0);
+                assert(tuplerowOut[col][outplane] <= outpamP->maxval);
+            }
+        }
+        pnm_writepamrow(outpamP, tuplerowOut);
+    }
+
+    pnm_freepamrow(tuplerow1);
+    pnm_freepamrow(tuplerow2);
+    pnm_freepamrow(tuplerowOut);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    struct pam inpam1;
+    struct pam inpam2;
+    struct pam outpam;
+    FILE * if1P;
+    FILE * if2P;
+    
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    if1P = pm_openr(cmdline.input1Filespec);
+    if2P = pm_openr(cmdline.input2Filespec);
+
+    pnm_readpaminit(if1P, &inpam1, PAM_STRUCT_SIZE(tuple_type));
+    pnm_readpaminit(if2P, &inpam2, PAM_STRUCT_SIZE(tuple_type));
+
+    if (inpam1.width != inpam2.width || inpam1.height != inpam2.height)
+        pm_error("The two images must be the same width and height.  "
+                 "The first is %ux%ux%u, but the second is %ux%ux%u",
+                 inpam1.width, inpam1.height, inpam1.depth,
+                 inpam2.width, inpam2.height, inpam2.depth);
+
+    if (inpam1.depth != 1 && inpam2.depth != 1 && inpam1.depth != inpam2.depth)
+        pm_error("The two images must have the same depth or one of them "
+                 "must have depth 1.  The first has depth %u and the second "
+                 "has depth %u", inpam1.depth, inpam2.depth);
+
+    computeOutputType(&outpam, inpam1, inpam2, cmdline.function);
+
+    pnm_writepaminit(&outpam);
+
+    switch (functionCategory(cmdline.function)) {    
+    case CATEGORY_FRACTIONAL_ARITH:
+        if (inpam1.maxval == inpam2.maxval)
+            doUnNormalizedArith(&inpam1, &inpam2, &outpam, cmdline.function);
+        else
+            doNormalizedArith(&inpam1, &inpam2, &outpam, cmdline.function);
+        break;
+    case CATEGORY_BITSTRING:
+    case CATEGORY_SHIFT:
+        doUnNormalizedArith(&inpam1, &inpam2, &outpam, cmdline.function);
+        break;
+    }
+
+    pm_close(if1P);
+    pm_close(if2P);
+    
+    return 0;
+}
diff --git a/other/pambayer.c b/other/pambayer.c
new file mode 100644
index 00000000..aa4beef1
--- /dev/null
+++ b/other/pambayer.c
@@ -0,0 +1,297 @@
+/*
+
+  Bayer matrix conversion tool
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published
+  by the Free Software Foundation; either version 2 of the License,
+  or (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
+  USA
+
+  Copyright Alexandre Becoulet <diaxen AT free DOT fr>
+  
+  Completely rewritten for Netpbm by Bryan Henderson August 2005.
+*/
+
+#include <unistd.h>
+#include <stdio.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+
+enum bayerType {
+    BAYER1,
+    BAYER2,
+    BAYER3,
+    BAYER4
+};
+
+struct cmdlineInfo {
+    const char * inputFilespec;
+    enum bayerType bayerType;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int typeSpec;
+    unsigned int type;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "type",     OPT_UINT, &type,
+            &typeSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 > 1)
+        pm_error("There is at most one argument -- the input file.  "
+                 "You specified %u", argc-1);
+
+    if (!typeSpec)
+        pm_error("You must specify the -type option");
+    else {
+        switch (type) {
+        case 1: cmdlineP->bayerType = BAYER1; break;
+        case 2: cmdlineP->bayerType = BAYER2; break;
+        case 3: cmdlineP->bayerType = BAYER3; break;
+        case 4: cmdlineP->bayerType = BAYER4; break;
+        }
+    }
+}
+
+
+
+static void
+calc_4(const struct pam * const pamP,
+       tuple **           const intuples,
+       tuple **           const outtuples,
+       unsigned int       const plane,
+       unsigned int       const xoffset,
+       unsigned int       const yoffset) {
+/*----------------------------------------------------------------------------
+    X . X
+    . . .
+    X . X
+-----------------------------------------------------------------------------*/
+    unsigned int y;
+    
+    for (y = yoffset; y < pamP->height; y += 2) {
+        unsigned int x;
+        for (x = xoffset; x + 2 < pamP->width; x += 2) {
+            outtuples[y][x][plane] = intuples[y][x][0];
+            outtuples[y][x + 1][plane] =
+                (intuples[y][x][0] + intuples[y][x + 2][0]) / 2;
+        }
+    }
+    for (y = yoffset; y + 2 < pamP->height; y += 2) {
+        unsigned int x;
+        for (x = xoffset; x < pamP->width; ++x)
+            outtuples[y + 1][x][plane] =
+                (outtuples[y][x][plane] + outtuples[y + 2][x][plane]) / 2;
+    }
+}
+
+
+
+static void
+calc_5(const struct pam * const pamP,
+       tuple **           const intuples,
+       tuple **           const outtuples,
+       unsigned int       const plane,
+       unsigned int       const xoffset,
+       unsigned int       const yoffset) {
+/*----------------------------------------------------------------------------
+   . X .
+   X . X
+   . X .
+-----------------------------------------------------------------------------*/
+    unsigned int y;
+    unsigned int j;
+
+    j = 0;  /* initial value */
+
+    for (y = yoffset; y + 2 < pamP->height; ++y) {
+        unsigned int x;
+        for (x = xoffset + j; x + 2 < pamP->width; x += 2) {
+            outtuples[y][x + 1][plane] = intuples[y][x + 1][0];
+            outtuples[y + 1][x + 1][plane] = 
+                (intuples[y][x + 1][0] + intuples[y + 1][x][0] +
+                 intuples[y + 2][x + 1][0] + intuples[y + 1][x + 2][0]) / 4;
+        }
+        j = 1 - j;
+    }
+}
+
+
+
+struct compAction {
+    unsigned int xoffset;
+    unsigned int yoffset;
+    void (*calc)(const struct pam * const pamP,
+                 tuple **           const intuples,
+                 tuple **           const outtuples,
+                 unsigned int       const plane,
+                 unsigned int       const xoffset,
+                 unsigned int       const yoffset);
+};
+
+
+
+static struct compAction const comp_1[3] = {
+/*----------------------------------------------------------------------------
+  G B G B
+  R G R G
+  G B G B
+  R G R G
+-----------------------------------------------------------------------------*/
+
+    { 0, 1, calc_4 },
+    { 0, 1, calc_5 },
+    { 1, 0, calc_4 }
+};
+
+static struct compAction const comp_2[3] = {
+/*----------------------------------------------------------------------------
+  R G R G
+  G B G B
+  R G R G
+  G B G B
+-----------------------------------------------------------------------------*/
+    { 0, 0, calc_4 },
+    { 0, 0, calc_5 },
+    { 1, 1, calc_4 }
+};
+
+static struct compAction const comp_3[3] = {
+/*----------------------------------------------------------------------------
+  B G B G
+  G R G R
+  B G B G
+  G R G R
+-----------------------------------------------------------------------------*/
+    { 1, 1, calc_4 },
+    { 0, 0, calc_5 },
+    { 0, 0, calc_4 }
+};
+
+static struct compAction const comp_4[3] = {
+/*----------------------------------------------------------------------------
+  G R G R
+  B G B G
+  G R G R
+  B G B G
+-----------------------------------------------------------------------------*/
+    { 1, 0, calc_4 },
+    { 0, 1, calc_5 },
+    { 0, 1, calc_4 }
+};
+
+
+
+static void
+makeOutputPam(const struct pam * const inpamP,
+              struct pam *       const outpamP) {
+
+    outpamP->size   = sizeof(*outpamP);
+    outpamP->len    = PAM_STRUCT_SIZE(tuple_type);
+    outpamP->file   = stdout;
+    outpamP->format = PAM_FORMAT;
+    outpamP->plainformat = 0;
+    outpamP->width  = inpamP->width;
+    outpamP->height = inpamP->height;
+    outpamP->depth  = 3;
+    outpamP->maxval = inpamP->maxval;
+    outpamP->bytes_per_sample = inpamP->bytes_per_sample;
+    STRSCPY(outpamP->tuple_type, "RGB");
+}
+
+
+
+static const struct compAction *
+actionTableForType(enum bayerType const bayerType) {
+
+    const struct compAction * retval;
+
+    switch (bayerType) {
+    case BAYER1: retval = comp_1; break;
+    case BAYER2: retval = comp_2; break;
+    case BAYER3: retval = comp_3; break;
+    case BAYER4: retval = comp_4; break;
+    }
+    return retval;
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    struct pam inpam;
+    struct pam outpam;
+    tuple ** intuples;
+    tuple ** outtuples;
+    const struct compAction * compActionTable;
+    unsigned int plane;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    ifP = pm_openr(cmdline.inputFilespec);
+    
+    intuples = pnm_readpam(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    compActionTable = actionTableForType(cmdline.bayerType);
+
+    makeOutputPam(&inpam, &outpam);
+
+    outtuples = pnm_allocpamarray(&outpam);
+
+    for (plane = 0; plane < 3; ++plane) {
+        struct compAction const compAction = compActionTable[plane];
+
+        compAction.calc(&inpam, intuples, outtuples, plane,
+                        compAction.xoffset, compAction.yoffset);
+    }
+    pnm_writepam(&outpam, outtuples);
+
+    pnm_freepamarray(outtuples, &outpam);
+    pnm_freepamarray(intuples, &inpam);
+
+    return 0;
+}
diff --git a/other/pamchannel.c b/other/pamchannel.c
new file mode 100644
index 00000000..ac7bae65
--- /dev/null
+++ b/other/pamchannel.c
@@ -0,0 +1,199 @@
+/*----------------------------------------------------------------------------
+                               pamchannel
+------------------------------------------------------------------------------
+  Part of the Netpbm package.
+
+  Extract specified channels (planes) from a PAM image
+
+
+  By Bryan Henderson, San Jose CA 2000.08.05
+
+  Contributed to the public domain by its author 2000.08.05.
+-----------------------------------------------------------------------------*/
+
+#include <string.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+#define MAX_CHANNELS 16
+    /* The most channels we allow user to specify */
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  /* Filename of input files */
+    const char * tupletype;      /* Tuple type for output PAM */
+    int n_channel;
+        /* The number of channels to extract.  At least 1, at most 16. */
+    unsigned int channel_to_extract[MAX_CHANNELS];
+        /* The channel numbers to extract, in order. */
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+    extern struct pam pam;  /* Just so we can look at field sizes */
+
+    unsigned int option_def_index;
+    unsigned int infileSpec, tupletypeSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "infile",     OPT_STRING, &cmdlineP->inputFileName, 
+            &infileSpec, 0);
+    OPTENT3(0, "tupletype",  OPT_STRING, &cmdlineP->tupletype, 
+            &tupletypeSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!infileSpec)
+        cmdlineP->inputFileName = "-";
+
+    if (!tupletypeSpec)
+        cmdlineP->tupletype = "";
+    else
+        if (strlen(cmdlineP->tupletype)+1 > sizeof(pam.tuple_type))
+            pm_error("Tuple type name specified is too long.  Maximum of "
+                     "%u characters allowed.", sizeof(pam.tuple_type));
+
+    cmdlineP->n_channel = 0;  /* initial value */
+    { 
+        int argn;
+        for (argn = 1; argn < argc; argn++) {
+            int n;
+            char *endptr;
+
+            if (cmdlineP->n_channel >= MAX_CHANNELS) 
+                pm_error("You may not specify more than %d channels.",
+                         MAX_CHANNELS);
+            
+            n = strtol(argv[argn], &endptr, 10);
+            if (n < 0)
+                pm_error("Channel numbers cannot be negative.  "
+                         "You specified %d", n);
+            if (endptr == NULL)
+                pm_error("non-numeric channel number argument: '%s'",
+                         argv[argn]);
+            
+            cmdlineP->channel_to_extract[cmdlineP->n_channel++] = n;
+        }
+    }
+    if (cmdlineP->n_channel < 1)
+        pm_error("You must specify at least one channel to extract.");
+}
+
+
+
+static void
+validateChannels(int          const n_channel, 
+                 unsigned int const channels[], 
+                 int          const depth) {
+
+    int i;
+
+    for (i = 0; i < n_channel; i++) 
+        if (channels[i] > depth-1)
+            pm_error("You specified channel number %d.  The highest numbered\n"
+                     "channel in the input image is %d.",
+                     channels[i], depth-1);
+}
+
+
+
+static void
+doOneImage(FILE *       const ifP,
+           FILE *       const ofP,
+           unsigned int const nChannel,
+           unsigned int const channelToExtract[],
+           const char * const tupletype) {
+
+    struct pam inpam;   /* Input PAM image */
+    struct pam outpam;  /* Output PAM image */
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    validateChannels(nChannel, channelToExtract, inpam.depth);
+
+    outpam = inpam;     /* Initial value */
+    outpam.file   = ofP;
+    outpam.depth  = nChannel;
+    outpam.format = PAM_FORMAT;
+    strcpy(outpam.tuple_type, tupletype);
+    
+    pnm_writepaminit(&outpam);
+
+    {
+        tuple * inrow;
+        tuple * outrow;
+        
+        inrow  = pnm_allocpamrow(&inpam);      
+        outrow = pnm_allocpamrow(&outpam);
+        { 
+            unsigned int row;
+            
+            for (row = 0; row < inpam.height; ++row) {
+                unsigned int col;
+
+                pnm_readpamrow(&inpam, inrow);
+
+                for (col = 0; col < inpam.width; ++col) {
+                    unsigned int plane;
+                    for (plane = 0; plane < nChannel; ++plane) 
+                        outrow[col][plane] = 
+                            inrow[col][channelToExtract[plane]];
+                }
+                pnm_writepamrow(&outpam, outrow);
+            }
+        }
+        pnm_freepamrow(outrow);
+        pnm_freepamrow(inrow);        
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    bool eof;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    ifP = pm_openr(cmdline.inputFileName);
+
+    eof = FALSE;
+    while (!eof) {
+        doOneImage(ifP, stdout, cmdline.n_channel, cmdline.channel_to_extract,
+                   cmdline.tupletype);
+
+        pnm_nextimage(ifP, &eof);
+    }
+
+    pm_close(ifP);
+
+    return 0;
+}
+
diff --git a/other/pamdepth.c b/other/pamdepth.c
new file mode 100644
index 00000000..0c4490ed
--- /dev/null
+++ b/other/pamdepth.c
@@ -0,0 +1,175 @@
+/*=============================================================================
+                            pamdepth
+===============================================================================
+  Change the maxval in a Netpbm image.
+
+  This replaces Pnmdepth.
+
+  By Bryan Henderson January 2006.
+
+  Contributed to the public domain by its author.
+=============================================================================*/
+#include <assert.h>
+
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "pam.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;
+    unsigned int newMaxval;
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec strings we return are stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "verbose",  OPT_STRING, NULL, 
+            &cmdlineP->verbose, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 < 1)
+        pm_error("You must specify at least one argument -- the new maxval");
+    else {
+        int const intval = atoi(argv[1]);
+
+        if (intval < 1)
+            pm_error("New maxval must be at least 1.  You specified %d",
+                     intval);
+        else if (intval > PNM_OVERALLMAXVAL)
+            pm_error("newmaxval (%d) is too large.\n"
+                     "The maximum allowed by the PNM formats is %d.",
+                     intval, PNM_OVERALLMAXVAL);
+        else
+            cmdlineP->newMaxval = intval;
+
+        if (argc-1 < 2)
+            cmdlineP->inputFileName = "-";
+        else
+            cmdlineP->inputFileName = argv[2];
+    }
+}
+
+
+
+static void
+createSampleMap(sample   const oldMaxval,
+                sample   const newMaxval,
+                sample** const sampleMapP) {
+
+    unsigned int i;
+
+    sample * sampleMap;
+
+    MALLOCARRAY_NOFAIL(sampleMap, oldMaxval+1);
+
+    for (i = 0; i <= oldMaxval; ++i)
+        sampleMap[i] = (i * newMaxval + oldMaxval / 2) / oldMaxval;
+
+    *sampleMapP = sampleMap;
+}
+
+
+
+static void
+transformRaster(struct pam * const inpamP,
+                struct pam * const outpamP) {
+                
+    tuple * tuplerow;
+    unsigned int row;
+    sample * sampleMap;  /* malloc'ed */
+
+    createSampleMap(inpamP->maxval, outpamP->maxval, &sampleMap);
+
+    assert(inpamP->height == outpamP->height);
+    assert(inpamP->width  == outpamP->width);
+    assert(inpamP->depth  == outpamP->depth);
+
+    tuplerow = pnm_allocpamrow(inpamP);
+
+    for (row = 0; row < inpamP->height; ++row) {
+        unsigned int col;
+        pnm_readpamrow(inpamP, tuplerow);
+
+        for (col = 0; col < inpamP->width; ++col) {
+            unsigned int plane;
+            for (plane = 0; plane < inpamP->depth; ++plane)
+                tuplerow[col][plane] = sampleMap[tuplerow[col][plane]];
+        }
+        pnm_writepamrow(outpamP, tuplerow);
+	}
+
+    pnm_freepamrow(tuplerow);
+
+    free(sampleMap);
+}
+
+
+
+int
+main(int    argc,
+     char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    struct pam inpam;
+    struct pam outpam;
+    bool eof;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    eof = FALSE;
+    while (!eof) {
+        pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+        outpam = inpam;  /* initial value */
+        
+        outpam.file = stdout;
+        outpam.maxval = cmdline.newMaxval;
+        
+        if (PNM_FORMAT_TYPE(inpam.format) == PBM_TYPE) {
+            pm_message( "promoting from PBM to PGM" );
+            outpam.format = PGM_TYPE;
+        } else
+            outpam.format = inpam.format;
+        
+        pnm_writepaminit(&outpam);
+
+        transformRaster(&inpam, &outpam);
+
+        pnm_nextimage(ifP, &eof);
+    }
+    pm_close(ifP);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/other/pamendian.c b/other/pamendian.c
new file mode 100644
index 00000000..16e1fe56
--- /dev/null
+++ b/other/pamendian.c
@@ -0,0 +1,70 @@
+/******************************************************************************
+                              pnmendian
+*******************************************************************************
+
+  Reverse the endianness of multi-byte samples in a Netpbm stream.
+  I.e. convert between the true format and the little endian variation of
+  it.
+******************************************************************************/
+  
+#include "pam.h"
+
+
+static sample
+reverseSample(sample const insample, unsigned int const bytesPerSample) {
+/*----------------------------------------------------------------------------
+  Return a sample whose value is the least significant
+  'bytes_per_sample' bytes, in reverse order.
+-----------------------------------------------------------------------------*/
+    unsigned int bytePos;
+    sample shiftedInsample;
+    sample outsample;
+    shiftedInsample = insample;  /* initial value */
+    outsample = 0;  /* initial value */
+    for (bytePos = 0; bytePos < bytesPerSample; ++bytePos) {
+        outsample = outsample * 256 + (shiftedInsample & 0xff);
+        shiftedInsample >>= 8;
+    }
+    return outsample;
+}
+
+
+
+int main(int argc, char *argv[]) {
+
+    struct pam inpam, outpam;
+    tuple * intuplerow;
+    tuple * outtuplerow;
+    unsigned int row;
+
+    pnm_init(&argc, argv);
+
+    pnm_readpaminit(stdin, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    outpam = inpam;
+    outpam.file = stdout;
+
+    pnm_writepaminit(&outpam);
+
+    intuplerow = pnm_allocpamrow(&inpam);      
+    outtuplerow = pnm_allocpamrow(&outpam);
+
+    for (row = 0; row < inpam.height; row++) {
+        unsigned int col;
+        pnm_readpamrow(&inpam, intuplerow);
+        for (col = 0; col < inpam.width; col++) {
+            unsigned int plane;
+            for (plane = 0; plane < inpam.depth; plane++) 
+                outtuplerow[col][plane] = 
+                    reverseSample(intuplerow[col][plane], 
+                                  inpam.bytes_per_sample);
+        }
+        pnm_writepamrow(&outpam, outtuplerow);
+    }
+
+    pnm_freepamrow(outtuplerow);        
+    pnm_freepamrow(intuplerow);        
+
+    exit(0);
+}
+
diff --git a/other/pamlookup.c b/other/pamlookup.c
new file mode 100644
index 00000000..2651d596
--- /dev/null
+++ b/other/pamlookup.c
@@ -0,0 +1,299 @@
+/*============================================================================
+                               pamlookup
+==============================================================================
+
+  Look up integers or ordered pairs from an index image in a lookup table and
+  produce a corresponding image containing the results of the lookups.
+  
+  The index image and lookup table are PAM images.  The output image is
+  a PAM image with the width and height of the index image and tuples of
+  the kind in the lookup table.
+
+  By Bryan Henderson, San Jose CA 2002.11.10
+
+============================================================================*/
+
+#include "pam.h"
+#include "shhopt.h"
+#include "pm_system.h"
+#include "nstring.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *indexFilespec;  
+    char *lookupFilespec;
+    char *missingcolor;  /* -missingcolor value.  null if not specified */
+    unsigned int fit;  /* -fit option */
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    
+    unsigned int lookupfileSpec, missingcolorSpec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "lookupfile",     OPT_STRING, &cmdlineP->lookupFilespec,  
+            &lookupfileSpec, 0);
+    OPTENT3(0,   "missingcolor", OPT_STRING, 
+            &cmdlineP->missingcolor,   &missingcolorSpec, 0);
+    OPTENT3(0,   "fit", OPT_FLAG, 
+            NULL,   &cmdlineP->fit, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!lookupfileSpec)
+        pm_error("You must specify the -lookupfile option");
+
+    if (!missingcolorSpec)
+        cmdlineP->missingcolor = NULL;
+
+    if (argc-1 < 1)
+        cmdlineP->indexFilespec = "-";
+    else
+        cmdlineP->indexFilespec = argv[1];
+}        
+
+
+
+static void
+fitLookup(tuple **     const inputLookup, 
+          struct pam   const inputLookuppam,
+          tuple ***    const fitLookupP,
+          struct pam * const fitLookuppamP,
+          unsigned int const cols,
+          unsigned int const rows) {
+/*----------------------------------------------------------------------------
+  Scale the lookup table image so that it has dimensions 'cols' x 'rows'.
+-----------------------------------------------------------------------------*/
+    const char * pamscaleCommand;
+    struct pamtuples inPamtuples;
+    struct pamtuples outPamtuples;
+
+    *fitLookuppamP = inputLookuppam;
+    fitLookuppamP->width = cols;
+    fitLookuppamP->height = rows;
+
+    asprintfN(&pamscaleCommand, "pamscale -width=%u -height=%u", cols, rows);
+
+    inPamtuples.pamP = (struct pam *) &inputLookuppam;
+    inPamtuples.tuplesP = (tuple ***) &inputLookup;
+    outPamtuples.pamP = fitLookuppamP;
+    outPamtuples.tuplesP = fitLookupP;
+    
+    pm_system(&pm_feed_from_pamtuples, &inPamtuples,
+              &pm_accept_to_pamtuples, &outPamtuples,
+              pamscaleCommand);
+
+    strfree(pamscaleCommand);
+}
+
+
+
+static void
+getLookup(const char * const lookupFilespec, 
+          unsigned int const indexDegree,
+          unsigned int const indexMaxval,
+          tuple ***    const lookupP,
+          struct pam * const lookuppamP,
+          bool         const fit) {
+
+    FILE*  lookupfileP;
+
+    struct pam inputLookuppam;
+    tuple** inputLookup;
+
+    lookupfileP = pm_openr(lookupFilespec);
+    inputLookup = pnm_readpam(lookupfileP, 
+                              &inputLookuppam, PAM_STRUCT_SIZE(tuple_type));
+
+    pm_close(lookupfileP);
+    
+    if (fit) {
+        fitLookup(inputLookup, inputLookuppam, lookupP, lookuppamP,
+                  indexMaxval + 1, 
+                  indexDegree > 1 ? indexMaxval + 1 : 1);
+        pnm_freepamarray(inputLookup, &inputLookuppam);
+    } else {
+        *lookupP = inputLookup;
+        *lookuppamP = inputLookuppam;
+    }
+        
+    if (indexDegree == 1 && lookuppamP->height != 1)
+        pm_error("Your index image has integer indices, "
+                 "so the lookup table image must be one row.  "
+                 "Yours is %d rows.", 
+                 lookuppamP->height);
+
+    if (lookuppamP->width - 1 > indexMaxval)
+        pm_message("Warning:  your lookup table image is wider than "
+                   "the maxval of "
+                   "your index message, so the right end of the lookup "
+                   "table image will have no effect on the output.");
+    if (indexDegree == 2 && lookuppamP->height - 1 > indexMaxval)
+        pm_message("Warning: your lookup table image is taller than "
+                   "the maxval of "
+                   "your index message, so the bottom end of the lookup "
+                   "table image has no effect on the output.");
+}
+
+
+
+static void
+computeDefaultTuple(struct cmdlineInfo const cmdline, 
+                    tuple **           const lookup,
+                    struct pam *       const lookuppamP, 
+                    tuple *            const defaultTupleP) {
+
+    tuple retval;
+
+    retval = pnm_allocpamtuple(lookuppamP);
+
+    /* Note: "missing color" really makes sense only for a color
+       lookup, whereas this program allows an arbitrary PAM image as a
+       lookup table.  We should probably check here for a lookup file
+       that has a visual image tuple type, but we don't out of
+       laziness.  The program probably ought to have a generic
+       "missing tuple type" option too.  
+    */
+    if (cmdline.missingcolor) {
+        pixel const color = 
+            ppm_parsecolor(cmdline.missingcolor, lookuppamP->maxval);
+
+        if (lookuppamP->depth >= 3) {
+            retval[PAM_RED_PLANE] = PPM_GETR(color);
+            retval[PAM_GRN_PLANE] = PPM_GETG(color);
+            retval[PAM_BLU_PLANE] = PPM_GETB(color);
+        } else {
+            if (PPM_GETR(color) != PPM_GETG(color) ||
+                PPM_GETR(color) != PPM_GETB(color))
+                pm_error("You specified as a missing color something which "
+                         "is not monochrome, but your lookup table file, "
+                         "and thus your "
+                         "output file, can contain only monochrome values");
+            else
+                retval[0] = PPM_GETR(color);
+        }
+    } else 
+        pnm_assigntuple(lookuppamP, retval, lookup[0][0]);
+
+    *defaultTupleP = retval;
+}
+
+
+
+static void
+doLookup(struct pam const indexpam,
+         struct pam const outpamarg,
+         tuple      const defaultTuple,
+         tuple **   const lookup,
+         struct pam const lookuppam) {
+
+    struct pam outpam;
+    unsigned int row;
+
+    tuple* tuplerowIndex;
+    tuple* tuplerowOut;
+
+    outpam = outpamarg;
+
+    tuplerowIndex = pnm_allocpamrow(&indexpam);
+    tuplerowOut = pnm_allocpamrow(&outpam);
+
+    pnm_writepaminit(&outpam);
+
+    for (row = 0; row < outpam.height; ++row) {
+        unsigned int col;
+        pnm_readpamrow(&indexpam, tuplerowIndex);
+        
+        for (col = 0; col < outpam.width; ++col) {
+            unsigned int indexRow, indexCol;
+            tuple v;
+            
+            if (indexpam.depth < 2) {
+                indexRow = 0;
+                indexCol = tuplerowIndex[col][0];
+            } else {
+                indexRow = tuplerowIndex[col][0];
+                indexCol = tuplerowIndex[col][1];
+            }
+
+            if (indexRow >= lookuppam.height || indexCol >= lookuppam.width)
+                v = defaultTuple;
+            else
+                v = lookup[indexRow][indexCol];
+
+            pnm_assigntuple(&outpam, tuplerowOut[col], v);
+        }
+        pnm_writepamrow(&outpam, tuplerowOut);
+    }
+    pnm_freepamrow(tuplerowIndex);
+    pnm_freepamrow(tuplerowOut);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    struct pam indexpam;
+    struct pam outpam;
+    FILE*  ifP;
+    struct pam lookuppam;
+    tuple** lookup;
+
+    tuple defaultTuple;
+    
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.indexFilespec);
+
+    pnm_readpaminit(ifP, &indexpam, PAM_STRUCT_SIZE(tuple_type));
+
+    if (indexpam.depth != 1 && indexpam.depth != 2)
+        pm_error("The input (index) file must have depth 1 or 2.  "
+                 "Yours has depth %d",
+                 indexpam.depth);
+
+    getLookup(cmdline.lookupFilespec, indexpam.depth, indexpam.maxval, 
+              &lookup, &lookuppam, cmdline.fit);
+
+    computeDefaultTuple(cmdline, lookup, &lookuppam, &defaultTuple);
+
+    outpam = lookuppam;
+    outpam.height = indexpam.height;
+    outpam.width = indexpam.width;
+    outpam.file = stdout;
+    
+    doLookup(indexpam, outpam, defaultTuple, lookup, lookuppam);
+
+    pm_close(ifP);
+
+    pnm_freepamtuple(defaultTuple);
+    pnm_freepamarray(lookup, &lookuppam);
+    
+    exit(0);
+}
+
diff --git a/other/pampick.c b/other/pampick.c
new file mode 100644
index 00000000..7c2d4385
--- /dev/null
+++ b/other/pampick.c
@@ -0,0 +1,250 @@
+/******************************************************************************
+                               pampick
+*******************************************************************************
+  Select specified images from a Netpbm image stream and create a new
+  Netpbm image stream containing them.
+
+  By Bryan Henderson, San Jose CA; October 2005
+
+  Contributed to the public domain by its author.
+******************************************************************************/
+
+#include <string.h>
+#include <stdio.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "mallocvar.h"
+
+
+struct uintSet {
+    unsigned int allocSize;
+    unsigned int count;
+    unsigned int * list;
+};
+
+
+
+static void
+initUintSet(struct uintSet * const uintSetP,
+            unsigned int     const allocSize) {
+
+    uintSetP->allocSize = allocSize;
+    uintSetP->count     = 0;
+
+    MALLOCARRAY(uintSetP->list, allocSize);
+    if (uintSetP->list == NULL)
+        pm_error("Could not allocate memory for an array of %u numbers.",
+                 allocSize);
+}
+
+
+
+static void
+termUintSet(struct uintSet * const uintSetP) {
+
+    free(uintSetP->list);
+}
+
+
+
+static bool
+isMemberOfUintSet(const struct uintSet * const uintSetP,
+                  unsigned int           const searchValue) {
+
+    bool retval;
+    unsigned int i;
+
+    retval = FALSE;  /* initial assumption */
+    
+    for (i = 0; i < uintSetP->count; ++i) {
+        if (uintSetP->list[i] == searchValue)
+            retval = TRUE;
+    }
+    return retval;
+}
+
+
+
+static void
+addToUintSet(struct uintSet * const uintSetP,
+             unsigned int     const newValue) {
+
+    if (uintSetP->count >= uintSetP->allocSize)
+        pm_error("Overflow of number list");
+    else {
+        if (!isMemberOfUintSet(uintSetP, newValue))
+            uintSetP->list[uintSetP->count++] = newValue;
+    }
+}
+
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    struct uintSet imageSeqList;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the pointers we place into *cmdlineP are sometimes to storage
+   in the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+
+    /* We have no options.  We use the parser just to gripe if user
+       specifies an option.  But when we add an option in the future,
+       it will go right here with an OPTENT3() macro invocation.
+    */
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    initUintSet(&cmdlineP->imageSeqList, argc-1);
+
+    {
+        unsigned int i;
+
+        for (i = 0; i < argc-1; ++i) {
+            if (strlen(argv[i+1]) == 0)
+                pm_error("An image sequence argument is a null string!");
+            else {
+                char * endPtr;
+                int const strtolResult = strtol(argv[i+1], &endPtr, 10);
+
+                if (*endPtr != '\0')
+                    pm_error("Garbage in sequence number argument '%s': '%s'",
+                             argv[i+1], endPtr);
+                
+                if (strtolResult < 0)
+                    pm_error("Image sequence number cannot be negative.  "
+                             "You specified %d", strtolResult);
+
+                addToUintSet(&cmdlineP->imageSeqList, strtolResult);
+            }
+        }
+    }
+    free(option_def);
+}
+
+
+
+static void
+destroyCmdline(struct cmdlineInfo * const cmdlineP) {
+
+    termUintSet(&cmdlineP->imageSeqList);
+}
+
+
+
+static void
+extractOneImage(FILE * const infileP,
+                FILE * const outfileP) {
+/*----------------------------------------------------------------------------
+  Copy a complete image from input stream *infileP to output stream
+  *outfileP.
+
+  But if outfileP == NULL, just read the image and discard it.
+-----------------------------------------------------------------------------*/
+    struct pam inpam;
+    struct pam outpam;
+    enum pm_check_code checkRetval;
+    
+    unsigned int row;
+    tuple * tuplerow;
+
+    pnm_readpaminit(infileP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    pnm_checkpam(&inpam, PM_CHECK_BASIC, &checkRetval);
+
+    outpam = inpam;
+    outpam.file = outfileP;
+
+    if (outfileP)
+        pnm_writepaminit(&outpam);
+
+    tuplerow = pnm_allocpamrow(&inpam);
+    for (row = 0; row < inpam.height; ++row) {
+        pnm_readpamrow(&inpam, tuplerow);
+        if (outfileP)
+            pnm_writepamrow(&outpam, tuplerow);
+    }
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+static void
+failIfUnpickedImages(const struct uintSet * const uintSetP,
+                     unsigned int           const imageCount) {
+/*----------------------------------------------------------------------------
+   Abort the program (pm_error()) if there are any image sequence numbers
+   in the set *uintSetP that are greater than or equal to 'imageCount'.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+
+    for (i = 0; i < uintSetP->count; ++i) {
+        if (uintSetP->list[i] >= imageCount)
+            pm_error("You asked for image sequence number %u "
+                     "(relative to 0).  "
+                     "The number of images in the input stream is only %u.",
+                     uintSetP->list[i], imageCount);
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+
+    bool eof;  /* No more images in input */
+    unsigned int imageSeq;  
+        /* Sequence of current image in input file.  First = 0 */
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    eof = FALSE;
+    for (imageSeq = 0; !eof; ++imageSeq) {
+        if (isMemberOfUintSet(&cmdline.imageSeqList, imageSeq)) {
+            pm_message("Extracting Image #%u", imageSeq);
+
+            extractOneImage(stdin, stdout);
+        } else
+            extractOneImage(stdin, NULL);
+
+        pnm_nextimage(stdin, &eof);
+    }
+
+    failIfUnpickedImages(&cmdline.imageSeqList, imageSeq);
+
+    destroyCmdline(&cmdline);
+
+    pm_close(stdin);
+    pm_close(stdout);
+    
+    return 0;
+}
diff --git a/other/pamsplit.c b/other/pamsplit.c
new file mode 100644
index 00000000..b9ff6247
--- /dev/null
+++ b/other/pamsplit.c
@@ -0,0 +1,187 @@
+/******************************************************************************
+                               pamsplit
+*******************************************************************************
+  Split a Netpbm format input file into multiple Netpbm format output files
+  with one image per output file.
+
+  By Bryan Henderson, Olympia WA; June 2000
+
+  Contributed to the public domain by its author.
+******************************************************************************/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <string.h>
+#include <stdio.h>
+#include "pam.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "mallocvar.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;
+    const char * outputFilePattern;
+    unsigned int debug;
+    unsigned int padname;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the pointers we place into *cmdlineP are sometimes to storage
+   in the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int padnameSpec;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "debug",   OPT_FLAG, NULL, &cmdlineP->debug, 0);
+    OPTENT3(0,   "padname", OPT_UINT, &cmdlineP->padname, &padnameSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!padnameSpec)
+        cmdlineP->padname = 0;
+
+    if (argc - 1 < 1) 
+        cmdlineP->inputFileName = "-";
+    else
+        cmdlineP->inputFileName = argv[1];
+    if (argc -1 < 2)
+        cmdlineP->outputFilePattern = "image%d";
+    else
+        cmdlineP->outputFilePattern = argv[2];
+
+    if (!strstr(cmdlineP->outputFilePattern, "%d"))
+        pm_error("output file spec pattern parameter must include the "
+                 "string '%%d',\n"
+                 "to stand for the image sequence number.\n"
+                 "You specified '%s'.", cmdlineP->outputFilePattern);
+}
+
+
+
+static void
+extractOneImage(FILE * const infileP,
+                FILE * const outfileP) {
+
+    struct pam inpam;
+    struct pam outpam;
+    enum pm_check_code checkRetval;
+    
+    unsigned int row;
+    tuple * tuplerow;
+
+    pnm_readpaminit(infileP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    pnm_checkpam(&inpam, PM_CHECK_BASIC, &checkRetval);
+
+    outpam = inpam;
+    outpam.file = outfileP;
+
+    pnm_writepaminit(&outpam);
+
+    tuplerow = pnm_allocpamrow(&inpam);
+    for (row = 0; row < inpam.height; ++row) {
+        pnm_readpamrow(&inpam, tuplerow);
+        pnm_writepamrow(&outpam, tuplerow);
+    }
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+static void
+computeOutputName(char          const outputFilePattern[], 
+                  unsigned int  const padCount,
+                  unsigned int  const imageSeq,
+                  const char ** const outputNameP) {
+/*----------------------------------------------------------------------------
+   Compute the name of an output file given the pattern
+   outputFilePattern[] and the image sequence number 'imageSeq'.
+   outputFilePattern[] contains at least one instance of the string
+   "%d" and we substitute the ASCII decimal representation of
+   imageSeq for the firstone of them to generate the output file
+   name.  We add leading zeroes as necessary to bring the number up to
+   at least 'padCount' characters.
+-----------------------------------------------------------------------------*/
+    char * beforeSub;
+    const char * afterSub;
+    const char * filenameFormat;
+        /* A format string for asprintfN for the file name */
+
+    beforeSub = strdup(outputFilePattern);
+    *(strstr(beforeSub, "%d")) = '\0';
+
+    afterSub = strstr(outputFilePattern, "%d") + 2;
+
+    /* Make filenameFormat something like "%s%04u%s" */
+    asprintfN(&filenameFormat, "%%s%%0%ud%%s", padCount);
+
+    asprintfN(outputNameP, filenameFormat, beforeSub, imageSeq, afterSub);
+
+    strfree(filenameFormat);
+
+    free(beforeSub);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+
+    FILE * ifP;
+    bool eof;  /* No more images in input */
+    unsigned int imageSeq;  
+        /* Sequence of current image in input file.  First = 0 */
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    ifP = pm_openr(cmdline.inputFileName);
+
+    eof = FALSE;
+    for (imageSeq = 0; !eof; ++imageSeq) {
+        FILE * ofP;
+        const char * outputFileName;  /* malloc'ed */
+
+        computeOutputName(cmdline.outputFilePattern, cmdline.padname, 
+                          imageSeq,
+                          &outputFileName);
+        pm_message("WRITING %s", outputFileName);
+
+        ofP = pm_openw(outputFileName);
+        extractOneImage(ifP, ofP);
+
+        pm_close(ofP);
+        strfree(outputFileName);
+
+        pnm_nextimage(ifP, &eof);
+    }
+    pm_close(ifP);
+    
+    return 0;
+}
diff --git a/other/pamstack.c b/other/pamstack.c
new file mode 100644
index 00000000..4f8d9945
--- /dev/null
+++ b/other/pamstack.c
@@ -0,0 +1,240 @@
+/*----------------------------------------------------------------------------
+                               pamstack
+------------------------------------------------------------------------------
+  Part of the Netpbm package.
+
+  Combine the channels (stack the planes) of multiple PAM images to create
+  a single PAM image.
+
+
+  By Bryan Henderson, San Jose CA 2000.08.05
+
+  Contributed to the public domain by its author 2002.05.05.
+-----------------------------------------------------------------------------*/
+
+#include <string.h>
+
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "pam.h"
+
+#define MAX_INPUTS 16
+    /* The most input PAMs we allow user to specify */
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *tupletype;       /* Tuple type for output PAM */
+    unsigned int nInput;
+        /* The number of input PAMs.  At least 1, at most 16. */
+    const char * inputFileName[MAX_INPUTS];
+        /* The PAM files to combine, in order. */
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec strings we return are stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+    extern struct pam pam;  /* Just so we can look at field sizes */
+
+    unsigned int option_def_index;
+    unsigned int tupletypeSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+    
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "tupletype",  OPT_STRING, &cmdlineP->tupletype, 
+            &tupletypeSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!tupletypeSpec)
+        cmdlineP->tupletype = "";
+    else
+        if (strlen(cmdlineP->tupletype)+1 > sizeof(pam.tuple_type))
+            pm_error("Tuple type name specified is too long.  Maximum of "
+                     "%u characters allowed.", sizeof(pam.tuple_type));
+
+    cmdlineP->nInput = 0;  /* initial value */
+    { 
+        int argn;
+        for (argn = 1; argn < argc; argn++) {
+            if (cmdlineP->nInput >= MAX_INPUTS) 
+                pm_error("You may not specify more than %d input images.",
+                         MAX_INPUTS);
+            cmdlineP->inputFileName[cmdlineP->nInput++] = argv[argn];
+        }
+    }
+    if (cmdlineP->nInput < 1)
+        pm_error("You must specify at least one input PAM image.");
+}
+
+
+
+static void
+openAllStreams(unsigned int  const nInput,
+               const char ** const inputFileName,
+               FILE **       const ifP) {
+
+    unsigned int inputSeq;
+
+    for (inputSeq = 0; inputSeq < nInput; ++inputSeq)
+        ifP[inputSeq] = pm_openr(inputFileName[inputSeq]);
+}
+
+
+
+static void
+outputRaster(const struct pam       inpam[], 
+             unsigned int     const nInput,
+             struct pam             outpam) {
+
+    tuple *inrow;
+    tuple *outrow;
+        
+    outrow = pnm_allocpamrow(&outpam);
+    inrow = pnm_allocpamrow(&outpam);      
+
+    { 
+        int row;
+        
+        for (row = 0; row < outpam.height; row++) {
+            unsigned int inputSeq;
+            int outplane;
+            outplane = 0;  /* initial value */
+            for (inputSeq = 0; inputSeq < nInput; ++inputSeq) {
+                struct pam thisInpam = inpam[inputSeq];
+                int col;
+
+                pnm_readpamrow(&thisInpam, inrow);
+
+                for (col = 0; col < outpam.width; col ++) {
+                    int inplane;
+                    for (inplane = 0; inplane < thisInpam.depth; ++inplane) 
+                        outrow[col][outplane+inplane] = inrow[col][inplane];
+                }
+                outplane += thisInpam.depth;
+            }
+            pnm_writepamrow(&outpam, outrow);
+        }
+    }
+    pnm_freepamrow(outrow);
+    pnm_freepamrow(inrow);        
+}
+
+
+
+static void
+processOneImageInAllStreams(unsigned int const nInput,
+                            FILE *       const ifP[],
+                            FILE *       const ofP,
+                            const char * const tupletype) {
+
+    struct pam inpam[MAX_INPUTS];   /* Input PAM images */
+    struct pam outpam;  /* Output PAM image */
+
+    unsigned int inputSeq;
+        /* The horizontal sequence -- i.e. the sequence of the
+           input stream, not the sequence of an image within a
+           stream.
+        */
+
+    unsigned int outputDepth;
+    outputDepth = 0;  /* initial value */
+    
+    for (inputSeq = 0; inputSeq < nInput; ++inputSeq) {
+
+        pnm_readpaminit(ifP[inputSeq], &inpam[inputSeq], 
+                        PAM_STRUCT_SIZE(tuple_type));
+
+        if (inputSeq > 0) {
+            /* All images, including this one, must be compatible with the 
+               first image.
+            */
+            if (inpam[inputSeq].width != inpam[0].width)
+                pm_error("Image no. %u does not have the same width as "
+                         "Image 0.", inputSeq);
+            if (inpam[inputSeq].height != inpam[0].height)
+                pm_error("Image no. %u does not have the same height as "
+                         "Image 0.", inputSeq);
+            if (inpam[inputSeq].maxval != inpam[0].maxval)
+                pm_error("Image no. %u does not have the same maxval as "
+                         "Image 0.", inputSeq);
+        }
+        outputDepth += inpam[inputSeq].depth;
+    }
+
+    outpam        = inpam[0];     /* Initial value */
+    outpam.depth  = outputDepth;
+    outpam.file   = ofP;
+    outpam.format = PAM_FORMAT;
+    strcpy(outpam.tuple_type, tupletype);
+
+    pm_message("Writing %u channel PAM image", outpam.depth);
+
+    pnm_writepaminit(&outpam);
+
+    outputRaster(inpam, nInput, outpam);
+}
+
+
+
+static void
+nextImageAllStreams(unsigned int const nInput,
+                    FILE *       const ifP[],
+                    bool *       const eofP) {
+/*----------------------------------------------------------------------------
+   Advance all the streams ifP[] to the next image.
+
+   Return *eofP == TRUE iff at least one stream has no next image.
+-----------------------------------------------------------------------------*/
+    unsigned int inputSeq;
+
+    for (inputSeq = 0; inputSeq < nInput; ++inputSeq) {
+        bool eof;
+        pnm_nextimage(ifP[inputSeq], &eof);
+        if (eof)
+            *eofP = eof;
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP[MAX_INPUTS];
+    bool eof;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    openAllStreams(cmdline.nInput, cmdline.inputFileName, ifP);
+
+    eof = FALSE;
+    while (!eof) {
+        processOneImageInAllStreams(cmdline.nInput, ifP, stdout,
+                                    cmdline.tupletype);
+
+        nextImageAllStreams(cmdline.nInput, ifP, &eof);
+    }
+
+    return 0;
+}
diff --git a/other/pamsummcol.c b/other/pamsummcol.c
new file mode 100644
index 00000000..c2c3e46b
--- /dev/null
+++ b/other/pamsummcol.c
@@ -0,0 +1,257 @@
+/******************************************************************************
+                               pamsummcol
+*******************************************************************************
+  Summarize the columns of a PAM image with various functions.
+
+  By Bryan Henderson, San Jose CA 2004.02.07.
+
+  Contributed to the public domain
+
+
+******************************************************************************/
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+enum function {FN_ADD, FN_MEAN, FN_MIN, FN_MAX};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+    enum function function;
+    unsigned int verbose;
+};
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int sumSpec, meanSpec, minSpec, maxSpec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "sum",      OPT_FLAG,  NULL, &sumSpec,           0);
+    OPTENT3(0,   "mean",     OPT_FLAG,  NULL, &meanSpec,          0);
+    OPTENT3(0,   "min",      OPT_FLAG,  NULL, &minSpec,           0);
+    OPTENT3(0,   "max",      OPT_FLAG,  NULL, &maxSpec,           0);
+    OPTENT3(0,   "verbose",  OPT_FLAG,  NULL, &cmdlineP->verbose, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (sumSpec + minSpec + maxSpec > 1)
+        pm_error("You may specify at most one of -sum, -min, and -max");
+
+    if (sumSpec) {
+        cmdlineP->function = FN_ADD;
+    } else if (meanSpec) {
+        cmdlineP->function = FN_MEAN;
+    } else if (minSpec) {
+        cmdlineP->function = FN_MIN;
+    } else if (maxSpec) {
+        cmdlineP->function = FN_MAX;
+    } else 
+        pm_error("You must specify one of -sum, -min, or -max");
+        
+    if (argc-1 > 1)
+        pm_error("Too many arguments (%d).  File spec is the only argument.",
+                 argc-1);
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else 
+        cmdlineP->inputFilespec = argv[1];
+    
+}
+
+
+struct accum {
+    union {
+        unsigned int sum;
+        unsigned int min;
+        unsigned int max;
+    } u;
+};
+
+
+
+static void
+createAccumulator(enum function    const function,
+                  unsigned int     const cols,
+                  unsigned int     const planes,
+                  struct accum *** const accumulatorP) {
+    
+    struct accum ** accumulator;
+    unsigned int col;
+
+    MALLOCARRAY_NOFAIL(accumulator, cols);
+
+    for (col = 0; col < cols; ++col) {
+        unsigned int plane;
+
+        MALLOCARRAY_NOFAIL(accumulator[col], planes);
+
+        for (plane = 0; plane < planes; ++plane) {
+            switch(function) {
+            case FN_ADD:  accumulator[col][plane].u.sum = 0;        break;
+            case FN_MEAN: accumulator[col][plane].u.sum = 0;        break;
+            case FN_MIN:  accumulator[col][plane].u.min = UINT_MAX; break;
+            case FN_MAX:  accumulator[col][plane].u.max = 0;        break;
+            } 
+        }
+    }
+    *accumulatorP = accumulator;
+}
+
+
+
+static void
+destroyAccumulator(struct accum **    accumulator,
+                   unsigned int const cols) {
+
+    unsigned int col;
+    for (col = 0; col < cols; ++col)
+        free(accumulator[col]);
+
+    free(accumulator);
+}
+
+
+
+static void
+aggregate(struct pam *    const inpamP,
+          tuple *         const tupleRow,
+          enum function   const function,
+          struct accum ** const accumulator) {
+
+    unsigned int col;
+
+    for (col = 0; col < inpamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < inpamP->depth; ++plane) {
+            switch(function) {
+            case FN_ADD:  
+            case FN_MEAN: 
+                if (accumulator[col][plane].u.sum > 
+                    UINT_MAX - tupleRow[col][plane])
+                    pm_error("Numerical overflow in Column %u", col);
+                accumulator[col][plane].u.sum += tupleRow[col][plane];
+            break;
+            case FN_MIN:  
+                if (tupleRow[col][plane] < accumulator[col][plane].u.min)
+                    accumulator[col][plane].u.min = tupleRow[col][plane];
+                break;
+            case FN_MAX:
+                if (tupleRow[col][plane] > accumulator[col][plane].u.min)
+                    accumulator[col][plane].u.min = tupleRow[col][plane];
+                break;
+            } 
+        }
+    }
+}
+
+
+
+static void
+makeSummaryRow(struct accum ** const accumulator,
+               unsigned int  const   count,
+               struct pam *  const   pamP,
+               enum function const   function,
+               tuple *       const   tupleRow) {
+    
+    unsigned int col;
+
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane) {
+            switch(function) {
+            case FN_ADD:  
+                tupleRow[col][plane] = 
+                    MIN(accumulator[col][plane].u.sum, pamP->maxval);
+                break;
+            case FN_MEAN: 
+                tupleRow[col][plane] = 
+                    ROUNDU((double)accumulator[col][plane].u.sum / count);
+                break;
+            case FN_MIN:  
+                tupleRow[col][plane] = 
+                    accumulator[col][plane].u.min;
+                break;
+            case FN_MAX:
+                tupleRow[col][plane] = 
+                    accumulator[col][plane].u.max;
+                break;
+            }
+        } 
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE* ifP;
+    tuple* inputRow;   /* Row from input image */
+    tuple* outputRow;  /* Output row */
+    int row;
+    struct cmdlineInfo cmdline;
+    struct pam inpam;   /* Input PAM image */
+    struct pam outpam;  /* Output PAM image */
+    struct accum ** accumulator;  /* malloc'ed two-dimensional array */
+
+    pnm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    createAccumulator(cmdline.function, inpam.width, inpam.depth, 
+                      &accumulator);
+
+    inputRow = pnm_allocpamrow(&inpam);
+
+    outpam = inpam;    /* Initial value -- most fields should be same */
+    outpam.file = stdout;
+    outpam.height = 1;
+
+    pnm_writepaminit(&outpam);
+
+    outputRow = pnm_allocpamrow(&outpam);
+
+    for (row = 0; row < inpam.height; row++) {
+        pnm_readpamrow(&inpam, inputRow);
+
+        aggregate(&inpam, inputRow, cmdline.function, accumulator);
+    }
+    makeSummaryRow(accumulator, inpam.height, &outpam, cmdline.function, 
+                   outputRow);
+    pnm_writepamrow(&outpam, outputRow);
+
+    pnm_freepamrow(outputRow);
+    pnm_freepamrow(inputRow);
+    destroyAccumulator(accumulator, inpam.width);
+    pm_close(inpam.file);
+    pm_close(outpam.file);
+    
+    return 0;
+}
diff --git a/other/pamx/COPYRIGHT b/other/pamx/COPYRIGHT
new file mode 100644
index 00000000..3ad3b675
--- /dev/null
+++ b/other/pamx/COPYRIGHT
@@ -0,0 +1,72 @@
+Many of the source files for Pamx contain code written by or derived
+from code written by Jim Frost for his program Xloadimage.  The files
+in which Frost has copyright contain Frost's name at the top.  Frost
+licenses his copyright to the public as follows:
+
+    Copyright 1989, 1993 Jim Frost
+   
+    Permission to use, copy, modify, distribute, and sell this software
+    and its documentation for any purpose is hereby granted without fee,
+    provided that the above copyright notice appear in all copies and
+    that both that copyright notice and this permission notice appear
+    in supporting documentation.  The author makes no representations
+    about the suitability of this software for any purpose.  It is
+    provided "as is" without express or implied warranty.
+   
+    THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+    INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+    NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+    CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+    OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+    OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+    USE OR PERFORMANCE OF THIS SOFTWARE.
+
+
+The rest of the code was primarily written by Bryan Henderson,
+starting in 2006.  Bryan and other authors have contributed their work
+to the public domain.
+
+
+Here is a more or less complete list of people who contributed to
+the Xloadimage from which Pamx was derived.  The list is from the README
+file in the Xloadimage source code:
+
+Special thanks to the crew at the Boston University Graphics Lab for
+their assistance and sample images, and to bzs@std.com for his simple
+dithering algorithm (or what's left of it).  Real special thanks to
+Kirk L. Johnson (tuna@athena.mit.edu) for a very nice GIF loader and
+dithering routine, to Mark Snitily (zok!mark@apple.com) for 386/ix
+compatibility work, to Andreas Stolcke (stolcke@icsib12.berkeley.edu)
+for miscellaneous bug fixes, to Anthony A. Datri (datri@convex.com)
+for a number of things, to Mark Moraes (moraes@cs.toronto.edu) for
+the slideshow colormap fix, to Gregg Townsend (gmt@cs.arizona.edu) for
+a suggested dithering routine and other fixes, to Brian Frost
+(B1F5814@RIGEL.TAMU.EDU) for changes for VMS, to Chip Horstman for G3
+FAX support, to Deron Dann Johnson (dj@eng.sun.com) for fixing the
+RetainTemporary bug, to Tom Tatlow (tatlow@dash.enet.dec.com) for
+image rotation code, to Mark A. Horstman (mhorstm@sarek.sbc.com) for
+tilde expansion in .xloadimagerc files and virtual-root support in
+root.c, to Tim Roper (timr@labtam.labtam.oz.au), Graeme Gill
+(graeme@labtam.oz.au) for gamma correction and Utah RLE image support,
+Mark Majhor (uunet!sequent!markm) for FBM and MacPaint support, Ian
+MacPhedran (macphed@dvinci.usask.ca) for PGM and PPM support, Per
+Fogelstrom (pf@diab.se) for a fix to send.c, Hans J. Albertsson
+(hans@Sweden.Sun.COM) for cleaning up GIF aborting, Graham Hudspith
+(gwh@inmos.com) for a geometry patch, Glenn P. Davis
+(davis@unidata.ucar.edu) for McIDAS areafile support, Keith S. Pickens
+(maxwell.nde.swri.edu!ksp) for fixing the RLE loader to work with the
+updated zio package, Mike Douglas (douglas@wilbur.coyote.trw.com) for
+normalization, Rod Johnson (johnson@wrl.epi.com) for speedup
+suggestions, Hal Peterson (hrp@cray.com) for his Imakefile fix, Matt
+Caprile (Matthew.Caprile@ec.bull.fr) for slideshow delay code, Bob
+Deroy (rwd@bucrsb.bu.edu) for mondo 24-bit Sun Rasterfile images that
+broke everything, Christos S. Zoulas (christo@ee.cornell.edu) for a
+first-cut 24-bit implementation, Gerald James Barnes
+(gjb@oasis.icl.stc.co.uk) for a first-cut forced-visual
+implementation, Michael Campanella (campanella@cvg.enet.dec.com) for
+more VMS changes, Kee Hinckley (nazgul@alfalfa.com) for robustness
+changes to the g3 and MacPaint loaders and the ZIO package, Tim
+Northrup (tim@brspyr1.brs.com) for PC Paintbrush and GEM image
+formats, Richard Weidner (richard@elroy.jpl.nasa.gov) for lots of
+24-bit testing,  Eckhard Rueggeberg (erueg@cfgauss.uni-math.gwdg.de)
+for a better PCX loader, and any others whose names I've missed.
diff --git a/other/pamx/Makefile b/other/pamx/Makefile
new file mode 100644
index 00000000..a86a3331
--- /dev/null
+++ b/other/pamx/Makefile
@@ -0,0 +1,43 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = other/pamx
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+ifneq ($(X11LIB),NONE)
+  ifneq ($(X11HDR_DIR),)
+    INCLUDES += -I$(X11HDR_DIR)
+  endif
+endif
+
+ifneq ($(X11LIB),NONE)
+  BINARIES += pamx
+endif
+
+PAMX_OBJECTS = \
+	pamx.o \
+	image.o \
+	send.o \
+	window.o \
+
+MERGE_OBJECTS = \
+	pamx.o2 \
+	image.o \
+	send.o \
+	window.o \
+
+OBJECTS = $(PAMX_OBJECTS)
+
+MERGEBINARIES = $(BINARIES)
+
+all: $(BINARIES)
+
+include $(SRCDIR)/Makefile.common
+
+pamx: $(PAMX_OBJECTS) $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $(PAMX_OBJECTS) \
+	  $(shell $(LIBOPT) $(NETPBMLIB) $(X11LIB)) \
+	  $(LDLIBS) $(MATHLIB) $(RPATH) $(LADD)
diff --git a/other/pamx/Makefile2 b/other/pamx/Makefile2
new file mode 100644
index 00000000..f69e103f
--- /dev/null
+++ b/other/pamx/Makefile2
@@ -0,0 +1,51 @@
+# C compiler to use, including special flags.
+CC=gcc
+
+WARNINGS = -Wall -Wmissing-declarations -Wundef -Wimplicit -Wwrite-strings \
+	-Winline \
+	-Wstrict-prototypes -Wmissing-prototypes \
+	-Werror
+
+CFLAGS = $(WARNINGS) -fno-common -g
+INCLUDES = -I /home/bryanh/netpbm/other/importinc
+
+# X11 include and library information.
+X11_LIB_DIR=-L/subsysx/X11R6/lib
+X11_LIB_NAME=-lX11
+NETPBMLIB = /home/bryanh/netpbm/lib/libnetpbm.so
+
+LIBS=$(X11_LIB_DIR) $(X11_LIB_NAME) -lm
+
+default: pamx
+
+# files for the image library
+IMAGE_SRCS= image.c
+IMAGE_OBJS= ${IMAGE_SRCS:.c=.o}
+
+# files for the image processing library
+PROCESS_HDRS=
+# no image processing.
+PROCESS_SRCS= fill.c
+PROCESS_OBJS= ${PROCESS_SRCS:.c=.o}
+
+X_SRCS= send.c window.c pamx.c
+X_OBJS= ${X_SRCS:.c=.o}
+
+OBJS= $(IMAGE_OBJS) $(PROCESS_OBJS) $(X_OBJS) $(NETPBMLIB)
+
+.c.o: $*.c
+	$(CC) -c $(CFLAGS) $(INCLUDES) $*.c $(CADD)
+
+pamx: $(OBJS) $(OPTIONAL_LIBS)
+	$(CC) -o $@ $(OBJS) $(OPTIONAL_LIBS) $(LIBS)
+
+clean::
+	rm -f *.o pamx
+
+dep:
+	$(CC) -MM -MG $(INCLUDES) *.c >Makefile.depend
+
+include Makefile.depend
+
+Makefile.depend:
+	>$@
diff --git a/other/pamx/fill.c b/other/pamx/fill.c
new file mode 100644
index 00000000..13a2b21e
--- /dev/null
+++ b/other/pamx/fill.c
@@ -0,0 +1,82 @@
+/* 
+   fill an image area with a particular pixel value
+ 
+   By Jim Frost 1989.10.02, Bryan Henderson 2006.03.25.
+ 
+   See COPYRIGHT file for copyright information.
+*/
+
+#include "pm.h"
+#include "image.h"
+#include "valtomem.h"
+#include "fill.h"
+
+void
+fill(Image * const imageP,
+     unsigned int const fx,
+     unsigned int const fy,
+     unsigned int const fw,
+     unsigned int const fh,
+     Pixel        const pixval) {
+
+    assertGoodImage(imageP);
+    switch(imageP->type) {
+    case IBITMAP: {
+        unsigned int const linelen = (imageP->width +7)/ 8;
+        unsigned int const start = (fx +7) / 8;
+        unsigned char const startmask = 0x80 >> (fx % 8);
+
+        unsigned int y;
+        unsigned char * lineptr;
+
+        for (y = fy, lineptr = imageP->data + linelen * fy;
+             y < fy + fh;
+             ++y, lineptr += linelen) {
+
+            unsigned int x;
+            unsigned char mask;
+            unsigned char * pixptr;
+
+            mask = startmask;
+            pixptr = lineptr + start;
+
+            for (x = fx; x < fw; ++x) {
+                if (pixval)
+                    *pixptr |= mask;
+                else
+                    *pixptr &= ~mask;
+                if (!(mask >>= 1)) {
+                    mask = 0x80;
+                    ++pixptr;
+                }
+            }
+        }
+    } break;
+        
+  case IRGB:
+    case ITRUE: {
+        unsigned int const linelen= imageP->width * imageP->pixlen;
+        unsigned int const start = imageP->pixlen * fx;
+
+        unsigned int y;
+        unsigned char * lineptr;
+
+        for (y = fy, lineptr = imageP->data + (linelen * fy);
+             y < fy + fh;
+             ++y, lineptr += linelen) {
+
+            unsigned int x;
+            unsigned char * pixptr;
+
+            pixptr = lineptr + start;
+            for (x = fx, pixptr = lineptr + start;
+                 x < fw;
+                 ++x, pixptr += imageP->pixlen) {
+                valToMem(pixval, pixptr, imageP->pixlen);
+            }
+        }
+    } break;
+    default:
+        pm_error("INTERNAL ERROR: Impossible image type %u", imageP->type);
+    }
+}
diff --git a/other/pamx/fill.h b/other/pamx/fill.h
new file mode 100644
index 00000000..1f316d0b
--- /dev/null
+++ b/other/pamx/fill.h
@@ -0,0 +1,16 @@
+#ifndef FILL_H_INCLUDED
+#define FILL_H_INCLUDED
+
+#include "ximageinfo.h"
+
+struct Image;
+
+void
+fill(struct Image * const imageP,
+     unsigned int   const fx,
+     unsigned int   const fy,
+     unsigned int   const fw,
+     unsigned int   const fh,
+     Pixel          const pixval);
+
+#endif
diff --git a/other/pamx/image.c b/other/pamx/image.c
new file mode 100644
index 00000000..3aaa8478
--- /dev/null
+++ b/other/pamx/image.c
@@ -0,0 +1,331 @@
+/*
+   Functions to allocate and deallocate structures and structure data
+ 
+   By Jim Frost 1989.09.29, Bryan Henderson 2006.03.25.
+ 
+   See COPYRIGHT file for copyright information.
+*/
+
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "pam.h"
+#include "image.h"
+
+
+
+/* this table is useful for quick conversions between depth and ncolors */
+
+static unsigned long const DepthToColorsTable[] = {
+  /*  0 */ 1,
+  /*  1 */ 2,
+  /*  2 */ 4,
+  /*  3 */ 8,
+  /*  4 */ 16,
+  /*  5 */ 32,
+  /*  6 */ 64,
+  /*  7 */ 128,
+  /*  8 */ 256,
+  /*  9 */ 512,
+  /* 10 */ 1024,
+  /* 11 */ 2048,
+  /* 12 */ 4096,
+  /* 13 */ 8192,
+  /* 14 */ 16384,
+  /* 15 */ 32768,
+  /* 16 */ 65536,
+  /* 17 */ 131072,
+  /* 18 */ 262144,
+  /* 19 */ 524288,
+  /* 20 */ 1048576,
+  /* 21 */ 2097152,
+  /* 22 */ 4194304,
+  /* 23 */ 8388608,
+  /* 24 */ 16777216,
+  /* 25 */ 33554432,
+  /* 26 */ 67108864,
+  /* 27 */ 134217728,
+  /* 28 */ 268435456,
+  /* 29 */ 536870912,
+  /* 30 */ 1073741824,
+  /* 31 */ 2147483648u,
+  /* 32 */ 2147483648u /* bigger than unsigned int; this is good enough */
+};
+
+
+
+unsigned int
+depthToColors(unsigned int const depth) {
+    return DepthToColorsTable[MIN(depth,32)];
+}
+
+
+
+unsigned long
+colorsToDepth(unsigned long const ncolors) {
+
+    unsigned long a;
+
+    for (a = 0; (a < 32) && (DepthToColorsTable[a] < ncolors); ++a)
+        /* EMPTY */
+        ;
+    return a ;
+}
+
+
+
+void
+assertGoodImage(Image * const imageP) {
+
+    assert(imageP != NULL);
+
+    switch (imageP->type) {
+    case IBITMAP:
+    case IRGB:
+    case ITRUE:
+        break;
+    default:
+        assert(FALSE);  /* can't be */
+    }
+}
+
+
+
+static void
+newRGBMapData(RGBMap *     const rgbP,
+              unsigned int const size) {
+
+    rgbP->used = 0;
+    rgbP->size = size;
+    rgbP->compressed = FALSE;
+    MALLOCARRAY(rgbP->red, size);
+    MALLOCARRAY(rgbP->grn, size);
+    MALLOCARRAY(rgbP->blu, size);
+
+    if (rgbP->red == NULL || rgbP->grn == NULL || rgbP->blu == NULL)
+        pm_error("Out of memory allocating %u pixels", size);
+}
+
+
+
+static void
+freeRGBMapData(RGBMap * const rgbP) {
+
+    free(rgbP->red);
+    free(rgbP->grn);
+    free(rgbP->blu);
+}
+
+
+
+Image *
+newBitImage(unsigned int const width,
+            unsigned int const height) {
+
+    unsigned int const linelen = (width + 7) / 8;
+
+    Image * imageP;
+
+    MALLOCVAR_NOFAIL(imageP);
+
+    imageP->type = IBITMAP;
+    newRGBMapData(&imageP->rgb, 2);
+    imageP->rgb.red[0] = imageP->rgb.grn[0] = imageP->rgb.blu[0] = 65535;
+    imageP->rgb.red[1] = imageP->rgb.grn[1] = imageP->rgb.blu[1] = 0;
+    imageP->rgb.used = 2;
+    imageP->width = width;
+    imageP->height = height;
+    imageP->depth = 1;
+
+    if (UINT_MAX / linelen < height)
+        pm_error("Image dimensions too big to compute: %u x %u",
+                 linelen, height);
+    MALLOCARRAY(imageP->data, linelen * height);
+
+    if (imageP->data == NULL)
+        pm_error("Out of memory allocating array of %u x %u", linelen, height);
+
+    return imageP;
+}
+
+
+
+Image *
+newRGBImage(unsigned int const width,
+            unsigned int const height,
+            unsigned int const depth) {
+    
+    unsigned int const pixlen = pixlen > 0 ? (depth + 7) / 8 : 1;
+        /* Special case for "zero" depth image, which is sometimes
+           interpreted as "one color"
+        */
+    unsigned int const numcolors = depthToColors(depth);
+
+    Image * imageP;
+    
+    MALLOCVAR_NOFAIL(imageP);
+    imageP->type   = IRGB;
+    newRGBMapData(&imageP->rgb, numcolors);
+    imageP->width  = width;
+    imageP->height = height;
+    imageP->depth  = depth;
+    imageP->pixlen = pixlen;
+
+    if (UINT_MAX / width / height < pixlen)
+        pm_error("Image dimensions %u x %u x %u are too big to compute.",
+                 width, height, pixlen);
+    MALLOCARRAY(imageP->data, width * height * pixlen);
+    if (imageP->data == NULL)
+        pm_error("Unable to allocate %u x %u x %u raster array",
+                 width, height, pixlen);
+
+    return imageP;
+}
+
+
+
+Image *
+newTrueImage(unsigned int const width,
+             unsigned int const height) {
+
+    unsigned int const pixlen = 3;
+    
+    Image * imageP;
+
+    MALLOCVAR_NOFAIL(imageP);
+    imageP->type     = ITRUE;
+    imageP->rgb.used = 0;
+    imageP->rgb.size = 0;
+    imageP->width    = width;
+    imageP->height   = height;
+    imageP->depth    = 24;
+    imageP->pixlen   = 3;
+
+    if (UINT_MAX / width / height < pixlen)
+        pm_error("Image dimensions %u x %u x %u are too big to compute.",
+                 width, height, pixlen);
+    MALLOCARRAY(imageP->data, width * height * pixlen);
+    if (imageP->data == NULL)
+        pm_error("Unable to allocate %u x %u x %u raster array",
+                 width, height, pixlen);
+
+    return imageP;
+}
+
+
+
+static void
+freeImageData(Image * const imageP) {
+
+    if (!TRUEP(imageP))
+        freeRGBMapData(&imageP->rgb);
+    free(imageP->data);
+}
+
+
+
+void
+freeImage(Image * const imageP) {
+
+    assertGoodImage(imageP);
+
+    freeImageData(imageP);
+
+    imageP->type = IBAD;
+
+    free(imageP);
+}
+
+
+
+
+static void
+fillRow1(struct pam *     const pamP,
+         tuple *          const tuplerow,
+         unsigned char ** const pP) {
+
+    unsigned int col;
+    
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            *(*pP)++ =
+                pnm_scalesample(tuplerow[col][0], pamP->maxval, 255);
+    }
+}
+
+
+
+static void
+fillRow3(struct pam *     const pamP,
+         tuple *          const tuplerow,
+         unsigned char ** const pP) {
+
+    unsigned int col;
+    
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            *(*pP)++ =
+                pnm_scalesample(tuplerow[col][plane], pamP->maxval, 255);
+    }
+}
+
+
+
+Image *
+pbmLoad(const char * const fullname,
+        const char * const name,
+        bool         const verbose) {
+
+    FILE * ifP;
+    struct pam pam;
+    Image * imageP;
+    unsigned int row;
+    const char * filename;
+    tuple * tuplerow;
+    unsigned char * p;
+    enum {DEPTH_1, DEPTH_3} depth;
+
+    if (STREQ(fullname, "stdin"))
+        filename = "-";
+    else
+        filename = fullname;
+
+    ifP = pm_openr(filename);
+
+    pnm_readpaminit(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
+
+    if (strncmp(pam.tuple_type, "RGB", 3) == 0) {
+        depth = DEPTH_3;
+        if (pam.depth < 3)
+            pm_error("Invalid depth %u for RGB tuple type.", pam.depth);
+    } else
+        depth = DEPTH_1;
+
+    imageP = newTrueImage(pam.width, pam.height);
+
+    p = &imageP->data[0];  /* initial value */
+
+    tuplerow = pnm_allocpamrow(&pam);
+
+    for (row = 0; row < pam.height; ++row) {
+        pnm_readpamrow(&pam, tuplerow);
+        
+        switch (depth) {
+        case DEPTH_3:
+            fillRow3(&pam, tuplerow, &p);
+            break;
+        case DEPTH_1:
+            fillRow1(&pam, tuplerow, &p);
+            break;
+        }
+    }
+    pnm_freepamrow(tuplerow);
+    
+    pm_close(ifP);
+
+    return imageP;
+}
diff --git a/other/pamx/image.h b/other/pamx/image.h
new file mode 100644
index 00000000..9c9689ac
--- /dev/null
+++ b/other/pamx/image.h
@@ -0,0 +1,90 @@
+#ifndef IMAGE_H_INCLUDED
+#define IMAGE_H_INCLUDED
+#include <strings.h>
+
+#include "pm_c_util.h"
+
+#include "ximageinfo.h"
+
+typedef struct rgbmap {
+    unsigned int size;       /* size of RGB map */
+    unsigned int used;       /* number of colors used in RGB map */
+    bool         compressed; /* image uses colormap fully */
+    Intensity *  red;        /* color values in X style */
+    Intensity *  grn;
+    Intensity *  blu;
+} RGBMap;
+
+typedef struct Image {
+    unsigned int    type;   /* type of image */
+    RGBMap          rgb;    /* RGB map of image if IRGB type */
+    unsigned int    width;  /* width of image in pixels */
+    unsigned int    height; /* height of image in pixels */
+    unsigned int    depth;  /* depth of image in bits if IRGB type */
+    unsigned int    pixlen; /* length of pixel if IRGB type */
+    unsigned char * data;   /* data rounded to full byte for each row */
+} Image;
+
+#define IBAD    0 /* invalid image (used when freeing) */
+#define IBITMAP 1 /* image is a bitmap */
+#define IRGB    2 /* image is RGB */
+#define ITRUE   3 /* image is true color */
+
+#define BITMAPP(IMAGE) ((IMAGE)->type == IBITMAP)
+#define RGBP(IMAGE)    ((IMAGE)->type == IRGB)
+#define TRUEP(IMAGE)   ((IMAGE)->type == ITRUE)
+
+#define TRUE_RED(PIXVAL)   (((PIXVAL) & 0xff0000) >> 16)
+#define TRUE_GRN(PIXVAL) (((PIXVAL) & 0xff00) >> 8)
+#define TRUE_BLU(PIXVAL)  ((PIXVAL) & 0xff)
+#define RGB_TO_TRUE(R,G,B) \
+  (((unsigned int)((R) & 0xff00) << 8) | ((unsigned int)(G) & 0xff00) | \
+   ((unsigned int)(B) >> 8))
+
+unsigned long
+colorsToDepth(unsigned long const ncolors);
+
+void
+assertGoodImage(Image * const imageP);
+
+Image *
+newBitImage(unsigned int const width,
+            unsigned int const height);
+
+Image *
+newRGBImage(unsigned int const width,
+            unsigned int const height,
+            unsigned int const depth);
+
+Image *
+newTrueImage(unsigned int const width,
+             unsigned int const height);
+
+void
+freeImage(Image * const imageP);
+
+unsigned int
+depthToColors(unsigned int const depth);
+
+extern unsigned short RedIntensity[];
+extern unsigned short GreenIntensity[];
+extern unsigned short BlueIntensity[];
+
+static __inline__ unsigned int
+colorIntensity(unsigned int const red,
+               unsigned int const grn,
+               unsigned int const blu) {
+/*----------------------------------------------------------------------------
+  Return the (approximate) intensity of a color.
+-----------------------------------------------------------------------------*/
+    return (RedIntensity[red / 256] +
+            GreenIntensity[grn / 256] +
+            BlueIntensity[blu / 256]);
+}
+
+Image *
+pbmLoad(const char * const fullname,
+        const char * const name,
+        bool         const verbose);
+
+#endif
diff --git a/other/pamx/pamx.c b/other/pamx/pamx.c
new file mode 100644
index 00000000..8a64fec4
--- /dev/null
+++ b/other/pamx/pamx.c
@@ -0,0 +1,364 @@
+/* By Bryan Henderson 2006.03.25 
+
+   Copyright information is in the file COPYRIGHT
+*/
+
+#include <signal.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "filename.h"
+
+#include "ximageinfo.h"
+#include "image.h"
+#include "fill.h"
+#include "window.h"
+
+
+struct geometry {
+    bool specified;
+    const char * string;
+    unsigned int width;
+    unsigned int height;
+};
+
+struct cmdlineInfo {
+    const char * inputFileName;
+    const char * displayName;  /* NULL if none */
+    const char * display;
+    const char * title;
+    unsigned int fullscreen;
+    unsigned int verbose;
+    struct geometry geometry;
+    const char * foreground;
+    const char * background;
+    const char * border;
+    unsigned int install;
+    unsigned int private;
+    unsigned int pixmap;
+    unsigned int fit;
+    unsigned int visualSpec;
+    unsigned int visual;
+};
+
+
+
+static void 
+parseCommandLine(int argc, 
+                 char ** argv, 
+                 struct cmdlineInfo  * const cmdlineP) {
+/* --------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+--------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+  
+    unsigned int option_def_index;
+
+    unsigned int displaySpec, titleSpec, foregroundSpec, backgroundSpec,
+        borderSpec, geometrySpec;
+
+    const char * geometryOpt;
+    const char * visualOpt;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "fullscreen", OPT_FLAG,
+            NULL, &cmdlineP->fullscreen,     0);
+    OPTENT3(0, "install",    OPT_FLAG,
+            NULL, &cmdlineP->install,     0);
+    OPTENT3(0, "private",    OPT_FLAG,
+            NULL, &cmdlineP->private,     0);
+    OPTENT3(0, "fit",        OPT_FLAG,
+            NULL, &cmdlineP->fit,     0);
+    OPTENT3(0, "pixmap",     OPT_FLAG,
+            NULL, &cmdlineP->pixmap,     0);
+    OPTENT3(0, "verbose",    OPT_FLAG,
+            NULL, &cmdlineP->verbose,     0);
+    OPTENT3(0, "display",    OPT_STRING,
+            &cmdlineP->display, &displaySpec, 0);
+    OPTENT3(0, "title",      OPT_STRING,
+            &cmdlineP->title, &titleSpec,     0);
+    OPTENT3(0, "foreground", OPT_STRING,
+            &cmdlineP->foreground, &foregroundSpec,     0);
+    OPTENT3(0, "background", OPT_STRING,
+            &cmdlineP->background, &backgroundSpec,     0);
+    OPTENT3(0, "border",     OPT_STRING,
+            &cmdlineP->border, &borderSpec,     0);
+    OPTENT3(0, "geometry",   OPT_STRING,
+            &geometryOpt, &geometrySpec,     0);
+    OPTENT3(0, "visual",     OPT_STRING,
+            &visualOpt, &cmdlineP->visualSpec,     0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;   /* We have no parms that are negative numbers */
+    
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (geometrySpec) {
+        int rc;
+
+        cmdlineP->geometry.specified = true;
+        cmdlineP->geometry.string    = geometryOpt;
+
+        rc = sscanf(geometryOpt, "%ux%u",
+                    &cmdlineP->geometry.width, &cmdlineP->geometry.height);
+
+        if (rc != 2)
+            pm_error("Geometry value '%s' does not have the form WxH, "
+                     "where W and H are natural numbers", geometryOpt);
+
+        if (cmdlineP->geometry.width == 0)
+            pm_error("Width in -geometry option is zero");
+        if (cmdlineP->geometry.height == 0)
+            pm_error("Height in -geometry option is zero");
+    } else
+        cmdlineP->geometry.specified = false;
+
+    if (!displaySpec)
+        cmdlineP->display = NULL;
+
+    if (!titleSpec)
+        cmdlineP->title = NULL;
+
+    if (!foregroundSpec)
+        cmdlineP->foreground = NULL;
+
+    if (!backgroundSpec)
+        cmdlineP->background = NULL;
+
+    if (!borderSpec)
+        cmdlineP->border = NULL;
+
+    if (cmdlineP->visualSpec)
+        cmdlineP->visual = visualClassFromName(visualOpt);
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFileName = argv[1];
+    else
+        pm_error("Program takes at most one argument:  input file name");
+}
+
+
+
+static int
+errorHandler(Display *     const disp,
+             XErrorEvent * const error) {
+/*----------------------------------------------------------------------------
+   This is an X error handler
+-----------------------------------------------------------------------------*/
+    char errortext[BUFSIZ];
+
+    XGetErrorText(disp, error->error_code, errortext, sizeof(errortext));
+    pm_error("X Error: %s on resource 0x%x",
+             errortext, (unsigned)error->resourceid);
+    return 0;
+}
+
+
+
+static void
+fillRow1(struct pam *     const pamP,
+         tuple *          const tuplerow,
+         unsigned char ** const pP) {
+
+    unsigned int col;
+    
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            *(*pP)++ =
+                pnm_scalesample(tuplerow[col][0], pamP->maxval, 255);
+    }
+}
+
+
+
+static void
+fillRow3(struct pam *     const pamP,
+         tuple *          const tuplerow,
+         unsigned char ** const pP) {
+
+    unsigned int col;
+    
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            *(*pP)++ =
+                pnm_scalesample(tuplerow[col][plane], pamP->maxval, 255);
+    }
+}
+
+
+
+static void
+loadPamImage(FILE *   const ifP,
+             Image ** const imagePP) {
+
+    struct pam pam;
+    Image * imageP;
+    unsigned int row;
+    tuple * tuplerow;
+    unsigned char * p;
+    enum {DEPTH_1, DEPTH_3} depth;
+
+    pnm_readpaminit(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
+
+    if (strncmp(pam.tuple_type, "RGB", 3) == 0) {
+        depth = DEPTH_3;
+        if (pam.depth < 3)
+            pm_error("Invalid depth %u for RGB tuple type.", pam.depth);
+    } else
+        depth = DEPTH_1;
+
+    imageP = newTrueImage(pam.width, pam.height);
+
+    p = &imageP->data[0];  /* initial value */
+
+    tuplerow = pnm_allocpamrow(&pam);
+
+    for (row = 0; row < pam.height; ++row) {
+        pnm_readpamrow(&pam, tuplerow);
+        
+        switch (depth) {
+        case DEPTH_3:
+            fillRow3(&pam, tuplerow, &p);
+            break;
+        case DEPTH_1:
+            fillRow1(&pam, tuplerow, &p);
+            break;
+        }
+    }
+    pnm_freepamrow(tuplerow);
+
+    *imagePP = imageP;
+}
+
+
+#define BACKGROUND_IDX 0
+#define FOREGROUND_IDX 1
+
+
+static void
+processImage(Image *            const imageP,
+             struct cmdlineInfo const cmdline,
+             Display *          const dispP,
+             int                const scrn) {
+/*----------------------------------------------------------------------------
+   Modify image *imageP according to various command line options.
+-----------------------------------------------------------------------------*/
+    if (imageP->depth <= 1) {
+        if (cmdline.background) {
+            XColor color;
+            XParseColor(dispP, DefaultColormap(dispP, scrn),
+                        cmdline.background, &color);
+            imageP->rgb.red[BACKGROUND_IDX] = color.red;
+            imageP->rgb.grn[BACKGROUND_IDX] = color.green;
+            imageP->rgb.blu[BACKGROUND_IDX] = color.blue;
+        }
+        if (cmdline.foreground) {
+            XColor color;
+            XParseColor(dispP, DefaultColormap(dispP, scrn),
+                        cmdline.foreground, &color);
+            imageP->rgb.red[FOREGROUND_IDX] = color.red;
+            imageP->rgb.grn[FOREGROUND_IDX] = color.green;
+            imageP->rgb.blu[FOREGROUND_IDX] = color.blue;
+        }
+    }    
+}
+
+
+
+static void
+determineTitle(struct cmdlineInfo const cmdline,
+               const char **      const titleP) {
+    
+    const char * title;
+    
+    if (cmdline.title)
+        title = strdup(cmdline.title);
+    else {
+        if (STREQ(cmdline.inputFileName, "-"))
+            title = NULL;
+        else {
+            title = pm_basename(cmdline.inputFileName);
+        }
+    }
+    *titleP = title;
+}
+
+
+
+int
+main(int     argc,
+     char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE *       ifP;
+    Image *      imageP;
+    Display *    dispP;           /* display we're sending to */
+    int          scrn;           /* screen we're sending to */
+    int          retval;
+    const char * geometryString;
+    const char * title;
+    viewer * viewerP;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+    
+    dispP = XOpenDisplay(cmdline.display);
+    if (!dispP)
+        pm_error("Cannot open display '%s'", XDisplayName(cmdline.display));
+
+    scrn = DefaultScreen(dispP);
+    XSetErrorHandler(errorHandler);
+
+    geometryString =
+        cmdline.geometry.specified ? cmdline.geometry.string : NULL;
+
+    createViewer(&viewerP, dispP, scrn, geometryString, cmdline.fullscreen);
+
+    loadPamImage(ifP, &imageP);
+
+    processImage(imageP, cmdline, dispP, scrn);
+
+    determineTitle(cmdline, &title);
+
+    displayInViewer(viewerP, imageP,
+                    cmdline.install, cmdline.private, cmdline.fit,
+                    cmdline.pixmap, cmdline.visualSpec, cmdline.visual,
+                    title, cmdline.verbose,
+                    &retval);
+
+    freeImage(imageP);
+
+    destroyViewer(viewerP);
+
+    if (title)
+        strfree(title);
+
+    XCloseDisplay(dispP);
+
+    return retval;
+}
diff --git a/other/pamx/send.c b/other/pamx/send.c
new file mode 100644
index 00000000..4b1268a5
--- /dev/null
+++ b/other/pamx/send.c
@@ -0,0 +1,872 @@
+/*
+ 
+  Send an Image to an X pixmap
+
+
+  By Jim Frost 1989.10.02, Bryan Henderson 2006.03.25.
+ 
+  Copyright 1989, 1990, 1991 Jim Frost.
+  See COPYRIGHT file for copyright information.
+*/
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include "pm_c_util.h"
+#include "pm.h"
+#include "mallocvar.h"
+#include "ximageinfo.h"
+#include "valtomem.h"
+#include "image.h"
+#include "send.h"
+
+#define TRUE_TO_15BIT(PIXEL)     \
+    ((((PIXEL) & 0xf80000) >> 9) | \
+    (((PIXEL) & 0x00f800) >> 6) | \
+    (((PIXEL) & 0x0000f8) >> 3))
+
+#define RED_INTENSITY(P)   (((P) & 0x7c00) >> 10)
+#define GREEN_INTENSITY(P) (((P) & 0x03e0) >> 5)
+#define BLUE_INTENSITY(P)   ((P) & 0x001f)
+#define PM_SCALE(a, b, c) (long)((a) * (c))/(b)
+
+
+static bool GotError;
+
+static int
+pixmapErrorTrap(Display *     const disp,
+                XErrorEvent * const  pErrorEvent) {
+
+#define MAXERRORLEN 100
+    char buf[MAXERRORLEN+1];
+    GotError = 1;
+    XGetErrorText(disp, pErrorEvent->error_code, buf, MAXERRORLEN);
+    pm_message("serial #%ld (request code %d) Got Error '%s'",
+               pErrorEvent->serial,
+               pErrorEvent->request_code,
+               buf);
+    return 0;
+}
+
+
+
+Pixmap
+ximageToPixmap(Display *    const disp,
+               Window       const parent,
+               XImageInfo * const ximageinfoP) {
+
+    XErrorHandler old_handler;
+    Pixmap        pixmap;
+  
+    GotError = FALSE;
+    old_handler = XSetErrorHandler(pixmapErrorTrap);
+    XSync(disp, False);
+    pixmap= XCreatePixmap(disp, parent,
+                          ximageinfoP->ximageP->width,
+                          ximageinfoP->ximageP->height,
+                          ximageinfoP->depth);
+    XSetErrorHandler(old_handler);
+    if (GotError)
+        return None;
+    ximageinfoP->drawable = pixmap;
+    sendXImage(ximageinfoP, 0, 0, 0, 0,
+               ximageinfoP->ximageP->width, ximageinfoP->ximageP->height);
+
+    return pixmap;
+}
+
+
+
+/* find the best pixmap depth supported by the server for a particular
+ * visual and return that depth.
+ *
+ * this is complicated by R3's lack of XListPixmapFormats so we fake it
+ * by looking at the structure ourselves.
+ */
+
+static unsigned int
+bitsPerPixelAtDepth(Display *    const disp,
+                    int          const scrn,
+                    unsigned int const depth) {
+
+#if XlibSpecificationRelease < 4 /* the way things were */
+  unsigned int a;
+
+  for (a= 0; a < disp->nformats; a++)
+    if (disp->pixmap_format[a].depth == depth)
+      return(disp->pixmap_format[a].bits_per_pixel);
+
+#else /* the way things should be */
+  XPixmapFormatValues *xf;
+  unsigned int nxf, a;
+
+  xf = XListPixmapFormats(disp, (int *)&nxf);
+  for (a = 0; a < nxf; a++)
+    if (xf[a].depth == depth)
+      return(xf[a].bits_per_pixel);
+#endif
+
+  /* this should never happen; if it does, we're in trouble
+   */
+
+  fprintf(stderr, "bitsPerPixelAtDepth: Can't find pixmap depth info!\n");
+  exit(1);
+}
+     
+
+
+static Image *
+itrueToRGB(Image *      const imageP,
+           unsigned int const ddepth) {
+
+    int y, x, num_pixels, colors;
+    unsigned long pixel_counts[32786];
+    unsigned long pixel_array[32786];
+    Pixel pixval;
+    unsigned char * pixel;
+    unsigned char * dpixel;
+    Image * newImageP;
+    
+    newImageP = newRGBImage(imageP->width, imageP->height, ddepth);
+
+    colors = 1 << ddepth;
+  
+    bzero(pixel_counts, 32768 * sizeof(unsigned long));
+  
+    pixel= imageP->data;
+    for (y= 0; y < imageP->height; y++) {
+        unsigned int x;
+        for (x= 0; x < imageP->width; x++) {
+            unsigned int const z = TRUE_TO_15BIT(memToVal(pixel, 3));
+            pixel_counts[z]++;
+            pixel += 3;
+        }
+    }
+    num_pixels = 0;
+    for (x = 0; x < 32768; ++x) {
+        if (pixel_counts[x] > 0) {
+            unsigned long const red = RED_INTENSITY(x);
+            unsigned long const grn = GREEN_INTENSITY(x);
+            unsigned long const blu = BLUE_INTENSITY(x);
+            pixel_counts[x] = num_pixels;
+            *(newImageP->rgb.red + num_pixels) = red<<11;
+            *(newImageP->rgb.grn + num_pixels) = grn<<11; 
+            *(newImageP->rgb.blu + num_pixels) =  blu<<11;
+            pixel_array[num_pixels++] = (short)x;
+            if (num_pixels > colors)
+                break;
+        }
+    }    
+
+    pixel = imageP->data;
+    dpixel = newImageP->data;
+    
+    for (y = 0; y < imageP->height; ++y) {
+        unsigned int x;
+        for (x = 0; x < imageP->width; ++x) {
+            unsigned int const z = TRUE_TO_15BIT(memToVal(pixel, 3));
+            pixval = pixel_counts[z];
+            valToMem(pixval, dpixel, newImageP->pixlen);
+            pixel += 3;
+            dpixel += newImageP->pixlen;
+        }
+    }
+    newImageP->rgb.used = num_pixels;
+    newImageP->rgb.compressed = 1;
+
+    return newImageP;
+}
+
+
+
+static void
+makeUsableVisual(Image *      const origImageP,
+                 Visual *     const visualP,
+                 unsigned int const ddepth,
+                 Image **     const newImagePP) {
+
+    /* process image based on type of visual to which we're sending */
+
+    switch (origImageP->type) {
+    case ITRUE:
+        switch (visualP->class) {
+        case TrueColor:
+        case DirectColor:
+            /* goody goody */
+            *newImagePP = origImageP;
+            break;
+        case PseudoColor:
+            *newImagePP = itrueToRGB(origImageP, ddepth);
+            if (*newImagePP == NULL)
+                pm_error("Unable to convert for Pseudocolor.");
+            break;
+        default:
+            pm_error("INTERNAL ERROR: impossible visual class %u",
+                     visualP->class);
+        }
+        break;
+        
+    case IRGB:
+        switch(visualP->class) {
+        case TrueColor:
+        case DirectColor:
+            /* no problem, we handle this just fine */
+            *newImagePP = origImageP;
+            break;
+        default:
+            pm_error("INTERNAL ERROR: impossible visual class %u",
+                     visualP->class);
+        }
+        
+    case IBITMAP:
+        /* no processing ever needs to be done for bitmaps */
+        *newImagePP = origImageP;
+        break;
+    }
+}    
+
+
+
+static void
+makeColorMap1(Display *  const disp,
+              int        const scrn,
+              Visual *   const visualP,
+              Colormap * const cmapP,
+              Pixel **   const redvalueP,
+              Pixel **   const grnvalueP,
+              Pixel **   const bluvalueP) {
+    
+    Pixel * redvalue;
+    Pixel * grnvalue;
+    Pixel * bluvalue;
+    Pixel pixval;
+    unsigned int redcolors, grncolors, blucolors;
+    unsigned int redstep, grnstep, blustep;
+    unsigned int redbottom, grnbottom, blubottom;
+    unsigned int redtop, grntop, blutop;
+    unsigned int a;
+            
+    MALLOCARRAY_NOFAIL(redvalue, 256);
+    MALLOCARRAY_NOFAIL(grnvalue, 256);
+    MALLOCARRAY_NOFAIL(bluvalue, 256);
+            
+    if (visualP == DefaultVisual(disp, scrn))
+        *cmapP = DefaultColormap(disp, scrn);
+    else
+        *cmapP = XCreateColormap(disp, RootWindow(disp, scrn),
+                                 visualP, AllocNone);
+            
+ retry_direct: /* tag we hit if a DirectColor allocation fails on
+                * default colormap */
+            
+    /* calculate number of distinct colors in each band */
+            
+    redcolors = grncolors = blucolors = 1;
+    for (pixval = 1; pixval; pixval <<= 1) {
+        if (pixval & visualP->red_mask)
+            redcolors <<= 1;
+        if (pixval & visualP->green_mask)
+            grncolors <<= 1;
+        if (pixval & visualP->blue_mask)
+            blucolors <<= 1;
+    }
+            
+    /* sanity check */
+            
+    if ((redcolors > visualP->map_entries) ||
+        (grncolors > visualP->map_entries) ||
+        (blucolors > visualP->map_entries)) {
+        pm_message("Warning: inconsistency in color information "
+                   "(this may be ugly)");
+    }
+            
+    redstep= 256 / redcolors;
+    grnstep= 256 / grncolors;
+    blustep= 256 / blucolors;
+    redbottom = grnbottom = blubottom= 0;
+    for (a = 0; a < visualP->map_entries; ++a) {
+        XColor xcolor;
+        Status rc;
+        if (redbottom < 256)
+            redtop = redbottom + redstep;
+        if (grnbottom < 256)
+            grntop = grnbottom + grnstep;
+        if (blubottom < 256)
+            blutop = blubottom + blustep;
+                
+        xcolor.flags = DoRed | DoGreen | DoBlue;
+        xcolor.red   = (redtop - 1) << 8;
+        xcolor.green = (grntop - 1) << 8;
+        xcolor.blue  = (blutop - 1) << 8;
+        rc = XAllocColor(disp, *cmapP, &xcolor);
+        if (rc == 0) {
+            /* Allocation failed.  If it's for a DirectColor default
+               visual then we should create a private colormap
+               and try again.
+            */
+
+            if ((visualP->class == DirectColor) &&
+                (visualP == DefaultVisual(disp, scrn))) {
+                *cmapP = XCreateColormap(disp, RootWindow(disp, scrn),
+                                         visualP, AllocNone);
+                goto retry_direct;
+            }
+                    
+            /* something completely unexpected happened */
+                    
+            pm_error("INTERNAL ERROR: XAllocColor failed on a "
+                     "TrueColor/Directcolor visual");
+        }
+                
+        /* fill in pixel values for each band at this intensity */
+                
+        while ((redbottom < 256) && (redbottom < redtop))
+            redvalue[redbottom++] = xcolor.pixel & visualP->red_mask;
+        while ((grnbottom < 256) && (grnbottom < grntop))
+            grnvalue[grnbottom++] = xcolor.pixel & visualP->green_mask;
+        while ((blubottom < 256) && (blubottom < blutop))
+            bluvalue[blubottom++] = xcolor.pixel & visualP->blue_mask;
+    }
+    *redvalueP   = redvalue;
+    *grnvalueP   = grnvalue;
+    *bluvalueP   = bluvalue;
+}
+
+
+ 
+static void
+allocColorCells(Display *      const disp,
+                Colormap       const cmap,
+                Pixel *        const colorIndex,
+                unsigned int   const colorCount,
+                unsigned int * const cellCountP) {
+
+    bool outOfCells;
+    unsigned int cellCount;
+    
+    outOfCells = false;  /* initial value */
+    cellCount = 0;       /* initial value */
+    while (cellCount < colorCount && !outOfCells) {
+        Status rc;
+        rc = XAllocColorCells(disp, cmap, FALSE, NULL, 0,
+                              &colorIndex[cellCount++], 1);
+        if (rc == 0)
+            outOfCells = true;
+    }
+    *cellCountP = cellCount;
+}
+
+    
+
+
+static void
+makeColorMap2(Display *  const disp,
+              int        const scrn,
+              Visual *   const visualP,
+              RGBMap     const rgb,
+              bool       const userWantsPrivateCmap,
+              bool       const userWantsFit,
+              bool       const verbose,
+              Colormap * const cmapP,
+              Pixel **   const colorIndexP) {
+
+    bool privateCmap;
+    bool fit;
+    bool newmap;
+    Pixel * colorIndex;
+
+    MALLOCARRAY_NOFAIL(colorIndex, rgb.used);
+        
+    /* 'privateCmap' is invalid if not a dynamic visual */
+        
+    switch (visualP->class) {
+    case StaticColor:
+    case StaticGray:
+        privateCmap = TRUE;
+    default:
+        privateCmap = userWantsPrivateCmap;
+    }
+        
+    /* get the colormap to use. */
+        
+    if (privateCmap) { /* user asked us to use a private cmap */
+        newmap = TRUE;
+        fit = FALSE;
+    } else if ((visualP == DefaultVisual(disp, scrn)) ||
+               (visualP->class == StaticGray) ||
+               (visualP->class == StaticColor) ||
+               (visualP->class == TrueColor) ||
+               (visualP->class == DirectColor)) {
+            
+        unsigned int a;
+
+        fit = userWantsFit;
+
+        /* if we're using the default visual, try to alloc colors
+           shareable.  otherwise we're using a static visual and
+           should treat it accordingly.
+        */
+            
+        if (visualP == DefaultVisual(disp, scrn))
+            *cmapP = DefaultColormap(disp, scrn);
+        else
+            *cmapP = XCreateColormap(disp, RootWindow(disp, scrn),
+                                     visualP, AllocNone);
+        newmap = FALSE;
+            
+        /* allocate colors shareable (if we can) */
+            
+        for (a = 0; a < rgb.used; ++a) {
+            Status rc;
+            XColor  xcolor;
+
+            xcolor.flags = DoRed | DoGreen | DoBlue;
+            xcolor.red   = rgb.red[a];
+            xcolor.green = rgb.grn[a];
+            xcolor.blue  = rgb.blu[a];
+            rc = XAllocColor(disp, *cmapP, &xcolor);
+            if (rc == 0) {
+                if ((visualP->class == StaticColor) ||
+                    (visualP->class == StaticGray) ||
+                    (visualP->class == TrueColor) ||
+                    (visualP->class == DirectColor)) {
+                    pm_error("XAllocColor failed on a static visual");
+                } else {
+                    /* We can't allocate the colors shareable so
+                       free all the colors we had allocated and
+                       create a private colormap (or fit into the
+                       default cmap if `fit' is true).
+                    */
+                    XFreeColors(disp, *cmapP, colorIndex, a, 0);
+                    newmap = TRUE;
+                    break;
+                }
+            }
+            colorIndex[a] = xcolor.pixel;
+        }
+    } else {
+        newmap = TRUE;
+        fit    = FALSE;
+    }
+        
+    if (newmap) {
+        /* Either create a new colormap or fit the image into the
+           one we have.  To create a new one, we create a private
+           cmap and allocate the colors writable.  Fitting the
+           colors is harder; we have to:
+
+           1. grab the server so no one can goof with the colormap.
+           2. count the available colors using XAllocColorCells.
+           3. free the colors we just allocated.
+           4. reduce the depth of the image to fit.
+           5. allocate the colors again shareable.
+           6. ungrab the server and continue on our way.
+               
+           Someone should shoot the people who designed X color allocation.
+        */
+            
+        unsigned int a;
+
+        if (fit) {
+            if (verbose)
+                pm_message("Fitting image into default colormap");
+            XGrabServer(disp);
+        } else {
+            if (verbose)
+                pm_message("Using private colormap");
+                
+            /* create new colormap */
+                
+            *cmapP = XCreateColormap(disp, RootWindow(disp, scrn),
+                                     visualP, AllocNone);
+        }
+            
+        allocColorCells(disp, *cmapP, colorIndex, rgb.used, &a);
+
+        if (fit) {
+            if (a > 0)
+                XFreeColors(disp, *cmapP, colorIndex, a, 0);
+            if (a <= 2)
+                pm_error("Cannot fit into default colormap");
+        }
+            
+        if (a == 0)
+            pm_error("Color allocation failed!");
+            
+        if (fit) {
+            unsigned int a;
+            for (a = 0; a < rgb.used; ++a) {
+                XColor xcolor;
+                xcolor.flags = DoRed | DoGreen | DoBlue;
+                xcolor.red   = rgb.red[a];
+                xcolor.green = rgb.grn[a];
+                xcolor.blue  = rgb.blu[a];
+                
+                if (!XAllocColor(disp, *cmapP, &xcolor))
+                    pm_error("XAllocColor failed while fitting colormap!");
+                colorIndex[a] = xcolor.pixel;
+            }
+            XUngrabServer(disp);
+        } else {
+            unsigned int b;
+            for (b = 0; b < a; ++b) {
+                XColor xcolor;
+                xcolor.flags = DoRed | DoGreen | DoBlue;
+                xcolor.pixel = colorIndex[b];
+                xcolor.red   = rgb.red[b];
+                xcolor.green = rgb.grn[b];
+                xcolor.blue  = rgb.blu[b];
+                XStoreColor(disp, *cmapP, &xcolor);
+            }
+        }
+    }
+    *colorIndexP = colorIndex;
+}
+
+
+
+static void
+doColorAllocation(XImageInfo * const ximageinfoP,
+                  Display *    const disp,
+                  int          const scrn,
+                  Visual *     const visualP,
+                  Image *      const imageP,
+                  bool         const userWantsPrivateCmap,
+                  bool         const userWantsFit,
+                  bool         const verbose,
+                  Pixel **     const colorIndexP,
+                  Pixel **     const redvalP,
+                  Pixel **     const grnvalP,
+                  Pixel **     const bluvalP) {
+    
+    if ((visualP->class == TrueColor || visualP->class == DirectColor) &&
+        !BITMAPP(imageP)) {
+        makeColorMap1(disp, scrn, visualP, &ximageinfoP->cmap,
+                      redvalP, grnvalP, bluvalP);
+        *colorIndexP = NULL;
+    } else {
+        makeColorMap2(disp, scrn, visualP, imageP->rgb,
+                      userWantsPrivateCmap, userWantsFit, verbose,
+                      &ximageinfoP->cmap, colorIndexP);
+        
+        *redvalP = *grnvalP = *bluvalP = NULL;
+    }
+}
+    
+
+
+
+static void
+makeXImage(XImageInfo * const ximageinfoP,
+           Display *    const disp,
+           int          const scrn,
+           Visual *     const visualP,
+           unsigned int const ddepth,
+           Image *      const imageP,
+           Pixel        const colorIndex[],
+           Pixel        const redvalue[],
+           Pixel        const grnvalue[],
+           Pixel        const bluvalue[],
+           bool         const verbose) {
+/*----------------------------------------------------------------------------
+  Create an XImage and related colormap based on the image type we
+  have.
+-----------------------------------------------------------------------------*/
+    if (verbose)
+        pm_message("Building XImage...");
+
+    switch (imageP->type) {
+    case IBITMAP: {
+        unsigned int const byteCount =
+            (imageP->width + 7) / 8 * imageP->height;
+        unsigned char * data;
+
+        /* we copy the data to be more consistent */
+
+        MALLOCARRAY(data, byteCount);
+        if (data == NULL)
+            pm_error("Can't allocate space for %u byte image", byteCount);
+        bcopy(imageP->data, data, byteCount);
+
+        ximageinfoP->ximageP =
+            XCreateImage(disp, visualP, 1, XYBitmap,
+                         0, (char*)data, imageP->width, imageP->height, 8, 0);
+        ximageinfoP->depth = ddepth;
+        ximageinfoP->foreground = colorIndex[1];
+        ximageinfoP->background = colorIndex[0];
+        ximageinfoP->ximageP->bitmap_bit_order = MSBFirst;
+        ximageinfoP->ximageP->byte_order = MSBFirst;
+    } break;
+
+    case IRGB:
+    case ITRUE: {
+        /* Modify image data to match visual and colormap */
+        
+        unsigned int const dbits = bitsPerPixelAtDepth(disp, scrn, ddepth);
+        unsigned int const dpixlen = (dbits + 7) / 8;
+
+        ximageinfoP->depth = ddepth;
+        
+        switch (visualP->class) {
+        case DirectColor:
+        case TrueColor: {
+            unsigned char * data;
+            unsigned char * destptr;
+            unsigned char * srcptr;
+        
+            ximageinfoP->ximageP =
+                XCreateImage(disp, visualP, ddepth, ZPixmap, 0,
+                             NULL, imageP->width, imageP->height, 8, 0);
+            MALLOCARRAY(data, imageP->width * imageP->height * dpixlen);
+            if (data == NULL)
+                pm_error("Unable to allocate space for %u x %u x %u image",
+                         imageP->width, imageP->height, dpixlen);
+            ximageinfoP->ximageP->data = (char*)data;
+            destptr = data;
+            srcptr = imageP->data;
+            switch (imageP->type) {
+            case ITRUE: {
+                unsigned int y;
+                for (y= 0; y < imageP->height; ++y) {
+                    unsigned int x;
+                    for (x= 0; x < imageP->width; ++x) {
+                        Pixel const pixval = memToVal(srcptr, imageP->pixlen);
+                        Pixel const newpixval =
+                            redvalue[TRUE_RED(pixval)] |
+                            grnvalue[TRUE_GRN(pixval)] |
+                            bluvalue[TRUE_BLU(pixval)];
+                        valToMem(newpixval, destptr, dpixlen);
+                        srcptr += imageP->pixlen;
+                        destptr += dpixlen;
+                    }
+                }
+            } break;
+            case IRGB: {
+                unsigned int y;
+                for (y= 0; y < imageP->height; ++y) {
+                    unsigned int x;
+                    for (x = 0; x < imageP->width; ++x) {
+                        Pixel const pixval = memToVal(srcptr, imageP->pixlen);
+                        Pixel const newpixval =
+                            redvalue[imageP->rgb.red[pixval] >> 8] |
+                            grnvalue[imageP->rgb.grn[pixval] >> 8] |
+                            bluvalue[imageP->rgb.blu[pixval] >> 8];
+                        valToMem(newpixval, destptr, dpixlen);
+                        srcptr += imageP->pixlen;
+                        destptr += dpixlen;
+                    }
+                }
+            } break;
+            default: /* something's broken */
+                pm_error("INTERNAL ERROR: Unexpected image type %u for "
+                         "DirectColor/TrueColor visual!", imageP->type);
+            }
+            ximageinfoP->ximageP->byte_order = MSBFirst;
+                /* Trust me, I know what I'm talking about */
+        } break;
+
+        default: {
+            
+            /* only IRGB images make it this far. */
+
+            /* If our XImage doesn't have modulus 8 bits per pixel,
+               it's unclear how to pack bits so we instead use an
+               XYPixmap image.  This is slower.
+            */
+
+            if (dbits % 8) {
+                unsigned int const linelen = (imageP->width + 7) / 8;
+                unsigned int const size =
+                    imageP->width * imageP->height * dpixlen;
+                unsigned char * data;
+                unsigned char * destptr;
+                unsigned char * srcptr;
+                Pixel pixval;
+                unsigned int a;
+
+                ximageinfoP->ximageP =
+                    XCreateImage(disp, visualP, ddepth, XYPixmap, 0,
+                                 NULL, imageP->width, imageP->height, 8, 0);
+
+                MALLOCARRAY(data, size);
+                if (data == NULL)
+                    pm_error("Unable to allocate space for %u x %x x %u "
+                             "image", imageP->width, imageP->height, dpixlen);
+                ximageinfoP->ximageP->data = (char*)data;
+                bzero(data, size);
+                ximageinfoP->ximageP->bitmap_bit_order = MSBFirst;
+                ximageinfoP->ximageP->byte_order = MSBFirst;
+                for (a= 0; a < dbits; ++a) {
+                    Pixel const pixmask = 1 << a;
+                    unsigned char * const destdata = 
+                        data + ((ddepth - a - 1) * imageP->height * linelen);
+
+                    unsigned int y;
+
+                    srcptr = imageP->data;
+                    for (y= 0; y < imageP->height; ++y) {
+                        unsigned int x;
+                        unsigned char mask;
+                        destptr = destdata + (y * linelen);
+                        *destptr = 0;
+                        mask = 0x80;
+                        for (x= 0; x < imageP->width; ++x) {
+                            pixval = memToVal(srcptr, imageP->pixlen);
+                            srcptr += imageP->pixlen;
+                            if (colorIndex[pixval] & pixmask)
+                                *destptr |= mask;
+                            mask >>= 1;
+                            if (mask == 0) {
+                                mask = 0x80;
+                                ++destptr;
+                            }
+                        }
+                    }
+                }
+            } else {
+                unsigned int const dpixlen =
+                    (ximageinfoP->ximageP->bits_per_pixel + 7) / 8;
+
+                unsigned char * data;
+                unsigned char * srcptr;
+                unsigned char * destptr;
+                unsigned int y;
+
+                ximageinfoP->ximageP =
+                    XCreateImage(disp, visualP, ddepth, ZPixmap, 0,
+                                 NULL, imageP->width, imageP->height, 8, 0);
+
+                MALLOCARRAY(data, imageP->width * imageP->height * dpixlen);
+                if (data == NULL)
+                    pm_error("Failed to allocate space for %u x %u x %u image",
+                             imageP->width, imageP->height, dpixlen);
+                ximageinfoP->ximageP->data = (char*)data;
+                ximageinfoP->ximageP->byte_order= MSBFirst;
+                    /* Trust me, i know what I'm talking about */
+                srcptr = imageP->data;
+                destptr = data;
+                for (y= 0; y < imageP->height; ++y) {
+                    unsigned int x;
+                    for (x= 0; x < imageP->width; x++) {
+                        valToMem(colorIndex[memToVal(srcptr, imageP->pixlen)],
+                                 destptr, dpixlen);
+                        srcptr += imageP->pixlen;
+                        destptr += dpixlen;
+                    }
+                }
+            }
+        } break;
+        }   
+    } break;
+    }
+    if (verbose)
+        pm_message("done");
+}
+
+
+
+XImageInfo *
+imageToXImage(Display *    const disp,
+              int          const scrn,
+              Visual *     const visualP, /* visual to use */
+              unsigned int const ddepth, /* depth of the visual to use */
+              Image *      const origImageP,
+              bool         const userWantsPrivateCmap,
+              bool         const userWantsFit,
+              bool         const verbose) {
+
+    XImageInfo * ximageinfoP;
+    Image * imageP;
+    Pixel * colorIndex;
+    Pixel * redvalue;
+    Pixel * grnvalue;
+    Pixel * bluvalue;
+
+    assertGoodImage(origImageP);
+  
+    MALLOCVAR_NOFAIL(ximageinfoP);
+    ximageinfoP->disp = disp;
+    ximageinfoP->scrn = scrn;
+    ximageinfoP->depth = 0;
+    ximageinfoP->drawable = None;
+    ximageinfoP->foreground = ximageinfoP->background = 0;
+    ximageinfoP->gc = NULL;
+    ximageinfoP->ximageP = NULL;
+  
+    makeUsableVisual(origImageP, visualP, ddepth, &imageP);
+
+    assertGoodImage(imageP);
+
+    doColorAllocation(ximageinfoP, disp, scrn, visualP, imageP,
+                      userWantsPrivateCmap, userWantsFit, verbose,
+                      &colorIndex, &redvalue, &grnvalue, &bluvalue);
+
+    makeXImage(ximageinfoP, disp, scrn, visualP, ddepth, imageP,
+               colorIndex, redvalue, grnvalue, bluvalue, verbose);
+
+    if (colorIndex)
+        free(colorIndex);
+    if (redvalue) {
+        free(redvalue);
+        free(grnvalue);
+        free(bluvalue);
+    }
+    if (imageP != origImageP)
+        freeImage(imageP);
+    
+    return ximageinfoP;
+}
+
+
+
+void
+sendXImage(XImageInfo * const ximageinfoP,
+           int          const src_x,
+           int          const src_y,
+           int          const dst_x,
+           int          const dst_y,
+           unsigned int const w,
+           unsigned int const h) {
+/*----------------------------------------------------------------------------
+  Given an XImage and a drawable, move a rectangle from the Ximage
+  to the drawable.
+-----------------------------------------------------------------------------*/
+    XGCValues gcv;
+
+    /* build and cache the GC */
+    
+    if (!ximageinfoP->gc) {
+        gcv.function = GXcopy;
+        if (ximageinfoP->ximageP->depth == 1) {
+            gcv.foreground = ximageinfoP->foreground;
+            gcv.background= ximageinfoP->background;
+            ximageinfoP->gc =
+                XCreateGC(ximageinfoP->disp, ximageinfoP->drawable,
+                          GCFunction | GCForeground | GCBackground,
+                          &gcv);
+        }else
+            ximageinfoP->gc =
+                XCreateGC(ximageinfoP->disp, ximageinfoP->drawable,
+                          GCFunction, &gcv);
+    }
+    
+    XPutImage(ximageinfoP->disp, ximageinfoP->drawable, ximageinfoP->gc,
+              ximageinfoP->ximageP, src_x, src_y, dst_x, dst_y, w, h);
+}
+
+
+
+void
+freeXImage(Image *      const imageP,
+           XImageInfo * const ximageinfoP) {
+/*----------------------------------------------------------------------------
+  free up anything cached in the local Ximage structure.
+-----------------------------------------------------------------------------*/
+    if (ximageinfoP->gc)
+        XFreeGC(ximageinfoP->disp, ximageinfoP->gc);
+    free(ximageinfoP->ximageP->data);
+    ximageinfoP->ximageP->data = NULL;
+    XDestroyImage(ximageinfoP->ximageP);
+
+    free(ximageinfoP);
+}
diff --git a/other/pamx/send.h b/other/pamx/send.h
new file mode 100644
index 00000000..203b99c7
--- /dev/null
+++ b/other/pamx/send.h
@@ -0,0 +1,38 @@
+#ifndef SEND_H_INCLUDED
+#define SEND_H_INCLUDED
+
+#include <X11/Xlib.h>
+
+#include "pm_c_util.h"
+
+struct Image;
+
+void
+sendXImage(XImageInfo * const ximageinfoP,
+           int          const src_x,
+           int          const src_y,
+           int          const dst_x,
+           int          const dst_y,
+           unsigned int const w,
+           unsigned int const h);
+
+void
+freeXImage(struct Image * const imageP,
+           XImageInfo *   const ximageinfoP);
+
+XImageInfo *
+imageToXImage(Display *      const disp,
+              int            const scrn,
+              Visual *       const visualP, /* visual to use */
+              unsigned int   const ddepth, /* depth of the visual to use */
+              struct Image * const origImageP,
+              bool           const userWantsPrivateCmap,
+              bool           const userWantsFit,
+              bool           const verbose);
+
+Pixmap
+ximageToPixmap(Display *    const disp,
+               Window       const parent,
+               XImageInfo * const ximageinfo);
+
+#endif
diff --git a/other/pamx/valtomem.h b/other/pamx/valtomem.h
new file mode 100644
index 00000000..e27ce0df
--- /dev/null
+++ b/other/pamx/valtomem.h
@@ -0,0 +1,65 @@
+/*
+  By Jim Frost 1989.10.02.
+
+  Copyright 1989 Jim Frost.
+  See COPYRIGHT file for copyright information.
+*/
+#ifndef VALTOMEM_H_INCLUDED
+#define VALTOMEM_H_INCLUDED
+
+/* inline these functions for speed.  these only work for {len : 1,2,3,4}.
+ */
+
+#define memToVal(PTR,LEN) \
+  ((LEN) == 1 ? ((unsigned long)(*((unsigned char *)PTR))) : \
+   ((LEN) == 3 ? ((unsigned long) \
+          (*(unsigned char *)(PTR) << 16) | \
+          (*((unsigned char *)(PTR) + 1) << 8) | \
+          (*((unsigned char *)(PTR) + 2))) : \
+    ((LEN) == 2 ? ((unsigned long) \
+           (*(unsigned char *)(PTR) << 8) | \
+           (*((unsigned char *)(PTR) + 1))) : \
+     ((unsigned long)((*(unsigned char *)(PTR) << 24) | \
+              (*((unsigned char *)(PTR) + 1) << 16) | \
+              (*((unsigned char *)(PTR) + 2) << 8) | \
+              (*((unsigned char *)(PTR) + 3)))))))
+
+#define memToValLSB(PTR,LEN) \
+  ((LEN) == 1 ? ((unsigned long)(*(unsigned char *)(PTR))) : \
+   ((LEN) == 3 ? ((unsigned long) \
+          (*(unsigned char *)(PTR)) | \
+          (*((unsigned char *)(PTR) + 1) << 8) | \
+          (*((unsigned char *)(PTR) + 2) << 16)) : \
+    ((LEN) == 2 ? ((unsigned long) \
+           (*(unsigned char *)(PTR)) | (*((unsigned char *)(PTR) + 1) << 8)) : \
+     ((unsigned long)((*(unsigned char *)(PTR)) | \
+              (*((unsigned char *)(PTR) + 1) << 8) | \
+              (*((unsigned char *)(PTR) + 2) << 16) | \
+              (*((unsigned char *)(PTR) + 3) << 24))))))
+
+#define valToMem(VAL,PTR,LEN) \
+  ((LEN) == 1 ? (*(unsigned char *)(PTR) = ((unsigned int)(VAL) & 0xff)) : \
+   ((LEN) == 3 ? (((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff0000) >> 16), \
+          ((*((unsigned char *)(PTR) + 1)) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+          ((*((unsigned char *)(PTR) + 2)) = ((unsigned int)(VAL) & 0xff))) : \
+    ((LEN) == 2 ? (((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+           ((*((unsigned char *)(PTR) + 1)) = ((unsigned int)(VAL) & 0xff))) : \
+     (((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff000000) >> 24), \
+      ((*((unsigned char *)(PTR) + 1)) = ((unsigned int)(VAL) & 0xff0000) >> 16), \
+      ((*((unsigned char *)(PTR) + 2)) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+      ((*((unsigned char *)(PTR) + 3)) = ((unsigned int)(VAL) & 0xff))))))
+
+#define valToMemLSB(VAL,PTR,LEN) \
+  ((LEN) == 1 ? (*(unsigned char *)(PTR) = ((unsigned int)(VAL) & 0xff)) : \
+   ((LEN) == 3 ? (((*(unsigned char *)(PTR) + 2) = ((unsigned int)(VAL) & 0xff0000) >> 16), \
+          ((*((unsigned char *)(PTR) + 1)) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+          ((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff))) : \
+    ((LEN) == 2 ? (((*((unsigned char *)(PTR) + 1) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+            ((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff)))) : \
+     (((*((unsigned char *)(PTR) + 3)) = ((unsigned int)(VAL) & 0xff000000) >> 24), \
+      ((*((unsigned char *)(PTR) + 2)) = ((unsigned int)(VAL) & 0xff0000) >> 16), \
+      ((*((unsigned char *)(PTR) + 1)) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+      ((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff))))))
+
+
+#endif
diff --git a/other/pamx/window.c b/other/pamx/window.c
new file mode 100644
index 00000000..1a6e510b
--- /dev/null
+++ b/other/pamx/window.c
@@ -0,0 +1,1209 @@
+/*
+   Functions to allocate and deallocate structures and structure data
+ 
+   By Jim Frost 1989.10.03, Bryan Henderson 2006.03.25.
+ 
+   See COPYRIGHT file for copyright information.
+*/
+
+#include <assert.h>
+#include <ctype.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <X11/cursorfont.h>
+#include <X11/Xatom.h>
+#include <X11/Xdefs.h>  /* Needed by Xutil.h */
+#include <X11/X.h>      /* Needed by Xutil.h */
+#include <X11/Xlib.h>   /* Needed by Xutil.h */
+#include <X11/Xutil.h>
+
+#include "mallocvar.h"
+#include "nstring.h"
+#include "pm.h"
+#include "ximageinfo.h"
+#include "send.h"
+#include "image.h"
+#include "window.h"
+
+/* A viewer object is something in which you can display an image.  You
+   can display multiple images in the same viewer, sequentially.
+
+   The viewer has a permanent window, called the viewport.  When you display
+   an image, it has a second window, called the image window, which is a
+   child of the viewport.
+*/
+
+struct viewer {
+    Display *    dispP;
+    int          scrn;
+    Window       imageWin;
+    Window       viewportWin;
+    Colormap     imageColormap;
+    Cursor       cursor;
+    unsigned int xpos, ypos;
+    unsigned int width, height;
+    bool         fullscreen;
+    bool         userChoseGeometry;
+    Atom         deleteAtom;
+    bool         blank;
+        /* I'm just guessing, but it seems that a "paint" operation is
+           necessary the first time a viewport is used, but not when
+           displaying a new image in an old viewport.  I assume that's
+           because the new viewport is blank, and that's what this
+           value means.
+        */
+    Pixmap       pixmap;
+};
+
+
+
+static void
+setXloadimageClassHint(Display * const dispP,
+                       Window    const window) {
+
+    XClassHint classhint;
+
+    classhint.res_class = (char*)"Xloadimage";
+    classhint.res_name  = (char*)"xloadimage";
+
+    XSetClassHint(dispP, window, &classhint);
+}
+
+
+
+static void
+setDeleteWindow(viewer * const viewerP) {
+
+    Atom const protoAtom = XInternAtom(viewerP->dispP, "WM_PROTOCOLS", False);
+    
+    viewerP->deleteAtom = XInternAtom(viewerP->dispP,
+                                      "WM_DELETE_WINDOW", False);
+    
+    if ((protoAtom != None) && (viewerP->deleteAtom != None))
+        XChangeProperty(viewerP->dispP, viewerP->viewportWin,
+                        protoAtom, XA_ATOM, 32, PropModeReplace,
+                        (unsigned char *)&viewerP->deleteAtom, 1);
+}
+
+
+
+static void
+getInitialViewerGeometry(const char *   const geometryString,
+                         bool           const fullscreen,
+                         Display *      const dispP,
+                         int            const scrn,
+                         unsigned int * const xposP,
+                         unsigned int * const yposP,
+                         unsigned int * const widthP,
+                         unsigned int * const heightP,
+                         bool *         const userChoseP) {
+
+    unsigned int const defaultWidth  = 10;
+    unsigned int const defaultHeight = 10;
+
+    if (fullscreen) {
+        *widthP  = DisplayWidth(dispP, scrn);
+        *heightP = DisplayHeight(dispP, scrn);
+        *xposP   = 0;
+        *yposP   = 0;
+        *userChoseP = TRUE;
+    } else if (geometryString) {
+        const char * defGeom;
+        asprintfN(&defGeom, "%ux%u+0+0", defaultWidth, defaultHeight);
+        XGeometry(dispP, scrn, geometryString, defGeom, 0, 1, 1, 0, 0,
+                  (int *)xposP, (int *)yposP,
+                  (int *)widthP, (int *)heightP);
+        strfree(defGeom);
+        *userChoseP = TRUE;
+    } else {
+        *widthP     = defaultWidth;
+        *heightP    = defaultHeight;
+        *xposP      = 0;
+        *yposP      = 0;
+        *userChoseP = FALSE;
+    }
+}
+
+
+
+void
+createViewer(viewer **     const viewerPP,
+             Display *     const dispP,
+             int           const scrn,
+             const char *  const geometryString,
+             bool          const fullscreen) {
+
+    viewer * viewerP;
+    
+    XSetWindowAttributes  swa_view;
+
+    MALLOCVAR_NOFAIL(viewerP);
+
+    viewerP->dispP       = dispP;
+    viewerP->scrn        = scrn;
+    viewerP->fullscreen  = fullscreen;
+    viewerP->imageWin    = 0;
+    viewerP->viewportWin = 0;
+
+    getInitialViewerGeometry(geometryString, fullscreen, dispP, scrn,
+                             &viewerP->xpos, &viewerP->ypos,
+                             &viewerP->width, &viewerP->height,
+                             &viewerP->userChoseGeometry);
+
+    viewerP->cursor = XCreateFontCursor(dispP, XC_watch);
+
+    swa_view.background_pixel = WhitePixel(dispP, scrn);
+    swa_view.backing_store    = NotUseful;
+    swa_view.cursor           = viewerP->cursor;
+    swa_view.event_mask       =
+        ButtonPressMask | Button1MotionMask | KeyPressMask |
+        StructureNotifyMask | EnterWindowMask | LeaveWindowMask;
+    swa_view.save_under       = FALSE;
+    
+    viewerP->viewportWin =
+        XCreateWindow(dispP, RootWindow(dispP, scrn),
+                      viewerP->xpos, viewerP->ypos,
+                      viewerP->width, viewerP->height, 0,
+                      DefaultDepth(dispP, scrn), InputOutput,
+                      DefaultVisual(dispP, scrn),
+                      CWBackingStore | CWBackPixel | CWCursor |
+                      CWEventMask | CWSaveUnder,
+                      &swa_view);
+
+    setXloadimageClassHint(viewerP->dispP, viewerP->viewportWin);
+    
+    setDeleteWindow(viewerP);
+
+    viewerP->blank = TRUE;
+
+    *viewerPP = viewerP;
+}
+
+
+
+static void
+determineRepaintStrategy(viewer  *    const viewerP,
+                         bool         const userWantsPixmap,
+                         bool         const verbose,
+                         XImageInfo * const ximageinfoP,
+                         Pixmap *     const pixmapP) {
+                        
+    /* Decide how we're going to handle repaints.  We have three modes:
+       use backing-store, use background pixmap, and use exposures.
+       If the server supports backing-store, we enable it and use it.
+       This really helps servers which are memory constrained.  If the
+       server does not have backing-store, we try to send the image to
+       a pixmap and use that as backing-store.  If that fails, we use
+       exposures to blit the image (which is ugly but it works).
+       
+       'use_pixmap' forces background pixmap mode, which may improve
+       performance.
+    */
+
+    ximageinfoP->drawable = viewerP->imageWin;
+    if ((DoesBackingStore(ScreenOfDisplay(viewerP->dispP, viewerP->scrn)) ==
+         NotUseful) ||
+        userWantsPixmap) {
+        *pixmapP = ximageToPixmap(viewerP->dispP, viewerP->imageWin,
+                                  ximageinfoP);
+        if (*pixmapP == None && verbose)
+            pm_message("Cannot create image in server; "
+                       "repaints will be ugly!");
+    } else
+        *pixmapP = None;
+}
+
+
+
+static void
+setImageWindowAttr(Display * const dispP,
+                   int       const scrn,
+                   Window    const imageWindow,
+                   Colormap  const cmap,
+                   Pixmap    const pixmap) {
+
+    /* build window attributes for the image window */
+
+    XSetWindowAttributes swa_img;
+    unsigned int         wa_mask_img;
+
+    swa_img.bit_gravity  = NorthWestGravity;
+    swa_img.save_under   = FALSE;
+    swa_img.colormap     = cmap;
+    swa_img.border_pixel = 0;
+
+    wa_mask_img = 0;  /* initial value */
+
+    if (pixmap == None) {
+        /* No pixmap.  Must paint over the wire.  Ask for BackingStore
+           to cut down on the painting.  But, ask for Exposures so we can
+           paint both viewables and backingstore.
+        */
+
+        swa_img.background_pixel = WhitePixel(dispP,scrn);
+        wa_mask_img |= CWBackPixel;
+        swa_img.event_mask = ExposureMask;
+        wa_mask_img |= CWEventMask;
+        swa_img.backing_store = WhenMapped;
+        wa_mask_img |= CWBackingStore;
+    } else {
+        /* We have a pixmap so tile the window.  to move the image we only
+           have to move the window and the server should do the rest.
+         */
+
+        swa_img.background_pixmap = pixmap;
+        wa_mask_img |= CWBackPixmap;
+        swa_img.event_mask = 0; /* no exposures please */
+        wa_mask_img |= CWEventMask;
+        swa_img.backing_store = NotUseful;
+        wa_mask_img |= CWBackingStore;
+    }
+    XChangeWindowAttributes(dispP, imageWindow, wa_mask_img, &swa_img);
+}
+
+
+
+static void
+createImageWindow(viewer *      const viewerP,
+                  XImageInfo *  const ximageInfoP,
+                  Image *       const imageP,
+                  Visual *      const visualP,
+                  bool          const userWantsPixmap,
+                  bool          const verbose) {
+
+    XSetWindowAttributes  swa_img;
+
+    swa_img.bit_gravity  = NorthWestGravity;
+    swa_img.save_under   = FALSE;
+    swa_img.colormap     = ximageInfoP->cmap;
+    swa_img.border_pixel = 0;
+    viewerP->imageWin = XCreateWindow(viewerP->dispP, viewerP->viewportWin,
+                                      viewerP->xpos, viewerP->ypos,
+                                      imageP->width, imageP->height, 0,
+                                      ximageInfoP->depth, InputOutput, visualP,
+                                      CWBitGravity | CWColormap | CWSaveUnder |
+                                      CWBorderPixel, &swa_img);
+    viewerP->imageColormap = ximageInfoP->cmap;
+    setXloadimageClassHint(viewerP->dispP, viewerP->imageWin);
+
+    determineRepaintStrategy(viewerP, userWantsPixmap, verbose, ximageInfoP,
+                             &viewerP->pixmap);
+
+    setImageWindowAttr(viewerP->dispP, viewerP->scrn,
+                       viewerP->imageWin, ximageInfoP->cmap,
+                       viewerP->pixmap);
+}
+
+
+
+static void
+destroyImageWindow(viewer * const viewerP) {
+    
+    if (viewerP->imageWin) {
+        if (viewerP->imageColormap &&
+            (viewerP->imageColormap != DefaultColormap(viewerP->dispP, viewerP->scrn)))
+            XFreeColormap(viewerP->dispP, viewerP->imageColormap);
+        XDestroyWindow(viewerP->dispP, viewerP->imageWin);
+    }
+}
+                       
+
+
+static void
+changeCursor(viewer *      const viewerP,
+             unsigned int  const imageWidth,
+             unsigned int  const imageHeight) {
+
+    Cursor cursor;
+    XSetWindowAttributes swa;
+
+    if ((viewerP->width >= imageWidth) && (viewerP->height >= imageHeight))
+        cursor = XCreateFontCursor(viewerP->dispP, XC_icon);
+    else if ((viewerP->width < imageWidth) && (viewerP->height >= imageHeight))
+        cursor = XCreateFontCursor(viewerP->dispP, XC_sb_h_double_arrow);
+    else if ((viewerP->width >= imageWidth) && (viewerP->height < imageHeight))
+        cursor = XCreateFontCursor(viewerP->dispP, XC_sb_v_double_arrow);
+    else
+        cursor = XCreateFontCursor(viewerP->dispP, XC_fleur);
+
+    swa.cursor = cursor;
+    XChangeWindowAttributes(viewerP->dispP, viewerP->viewportWin,
+                            CWCursor, &swa);
+
+    XFreeCursor(viewerP->dispP, viewerP->cursor);
+
+    viewerP->cursor = cursor;
+}
+
+
+
+static void
+placeImage(viewer * const viewerP,
+           int      const width,
+           int      const height,
+           int *    const rxP,     /* input and output */
+           int *    const ryP) {   /* input and output */
+
+    int pixx, pixy;
+    
+    pixx = *rxP;
+    pixy = *ryP;
+    
+    if (viewerP->width > width)
+        pixx = (viewerP->width - width) / 2;
+    else {
+        if ((pixx < 0) && (pixx + width < viewerP->width))
+            pixx = viewerP->width - width;
+        if (pixx > 0)
+            pixx = 0;
+    }
+    if (viewerP->height > height)
+        pixy = (viewerP->height - height) / 2;
+    else {
+        if ((pixy < 0) && (pixy + height < viewerP->height))
+            pixy = viewerP->height - viewerP->height;
+        if (pixy > 0)
+            pixy = 0;
+    }
+    XMoveWindow(viewerP->dispP, viewerP->imageWin, pixx, pixy);
+
+    *rxP = pixx;
+    *ryP = pixy;
+}
+
+
+
+static void
+blitImage(XImageInfo * const ximageinfoP,
+          unsigned int const width,
+          unsigned int const height,
+          int          const xArg,
+          int          const yArg,
+          int          const wArg,
+          int          const hArg) {
+
+    int w, h;
+    int x, y;
+
+    w = MIN(wArg, width);
+    h = MIN(hArg, height);
+
+    x = xArg;
+    y = yArg;
+
+    if (x < 0) {
+        XClearArea(ximageinfoP->disp, ximageinfoP->drawable,
+                   x, y, -x, h, False);
+        w -= (0 - x);
+        x = 0;
+    }
+    if (y < 0) {
+        XClearArea(ximageinfoP->disp, ximageinfoP->drawable,
+                   x, y, w, -y, False);
+        h -= (0 - y);
+        y = 0;
+    }
+    if (x + w > width) {
+        XClearArea(ximageinfoP->disp, ximageinfoP->drawable,
+                   x + width, y, x + w - width, h, False);
+        w -= x + w - width;
+    }
+    if (y + h > height) {
+        XClearArea(ximageinfoP->disp, ximageinfoP->drawable,
+                   x, y + height, w, y + h - height, False);
+        h -= y + h - height;
+    }
+    sendXImage(ximageinfoP, x, y, x, y, w, h);
+}
+
+
+
+void
+destroyViewer(viewer * const viewerP) {
+/*----------------------------------------------------------------------------
+   Clean up static window.
+-----------------------------------------------------------------------------*/
+    if (viewerP->pixmap != None)
+        XFreePixmap(viewerP->dispP, viewerP->pixmap);
+
+    if (viewerP->imageWin)
+        XDestroyWindow(viewerP->dispP, viewerP->imageWin);
+    viewerP->imageWin = 0;
+    
+    if (viewerP->viewportWin)
+        XDestroyWindow(viewerP->dispP, viewerP->viewportWin);
+    
+    viewerP->viewportWin = 0;
+
+    XFreeCursor(viewerP->dispP, viewerP->cursor);
+
+    free(viewerP);
+}
+
+
+
+static void
+setViewportColormap(viewer *  const viewerP,
+                    Visual *  const visualP) {
+/*----------------------------------------------------------------------------
+  Set the colormap and WM_COLORMAP_WINDOWS properly for the viewport.
+-----------------------------------------------------------------------------*/
+    static Atom cmap_atom = None;
+    XSetWindowAttributes swa;
+    Window cmap_windows[2];
+
+    if (cmap_atom == None)
+        cmap_atom = XInternAtom(viewerP->dispP, "WM_COLORMAP_WINDOWS", False);
+    
+    /* If the visual we're using is the same as the default visual (used by
+       the viewport window) then we can set the viewport window to use the
+       image's colormap.  This keeps most window managers happy.
+    */
+    
+    if (visualP == DefaultVisual(viewerP->dispP, viewerP->scrn)) {
+        swa.colormap = viewerP->imageColormap;
+        XChangeWindowAttributes(viewerP->dispP, viewerP->viewportWin,
+                                CWColormap, &swa);
+        XDeleteProperty(viewerP->dispP, viewerP->viewportWin, cmap_atom);
+    } else {
+        /* Smart window managers can handle it when we use a different colormap
+           in our subwindow so long as we set the WM_COLORMAP_WINDOWS property
+           ala ICCCM.
+        */
+        cmap_windows[0] = viewerP->imageWin;
+        cmap_windows[1] = viewerP->viewportWin;
+        XChangeProperty(viewerP->dispP, viewerP->viewportWin,
+                        cmap_atom, XA_WINDOW, 32,
+                        PropModeReplace, (unsigned char *)cmap_windows, 2);
+    }
+}
+
+
+
+static const char *
+iconName(const char * const s) {
+/*----------------------------------------------------------------------------
+   Return an icon name suitable for an image titled 's'.
+
+   s == NULL means untitled.
+-----------------------------------------------------------------------------*/
+    const char * retval;
+    char buf[BUFSIZ];
+
+    if (!s)
+        STRSCPY(buf, "Unnamed");
+    else {
+        char * t;
+
+        STRSCPY(buf, s);
+        /* strip off stuff following 1st word.  This strips
+           info added by processing functions too.
+        */
+        t = index(buf, ' ');
+        if (t)
+            *t = '\0';
+    
+        /* Strip off leading path.  if you don't use unix-style paths,
+           You might want to change this.
+        */
+    
+        t= rindex(buf, '/');
+        if (t) {
+            char * p;
+            for (p = buf, ++t; *t; ++p, ++t)
+                *p = *t;
+            *p = '\0';
+        }
+        /* look for an extension and strip it off */
+        t = index(buf, '.');
+        if (t)
+            *t = '\0';
+    }
+
+    retval = strdup(buf);
+    if (retval == NULL)
+        pm_error("Out of memory");
+    return retval;
+}
+
+
+
+/* visual class to name table  */
+
+static struct visual_class_name {
+    int          class; /* numerical value of class */
+    const char * name;  /* actual name of class */
+} const VisualClassName[] = {
+    {TrueColor,   "TrueColor"   } ,
+    {DirectColor, "DirectColor" } ,
+    {PseudoColor, "PseudoColor" } ,
+    {StaticColor, "StaticColor" } ,
+    {GrayScale,   "GrayScale"   } ,
+    {StaticGray,  "StaticGray"  } ,
+    {StaticGray,  "StaticGrey"  } ,
+    {-1,          NULL          }
+};
+
+
+
+int
+visualClassFromName(const char * const name) {
+
+    unsigned int a;
+    int class;
+    bool found;
+    
+    for (a = 0, found = FALSE; VisualClassName[a].name; ++a) {
+        if (STRCASEEQ(VisualClassName[a].name, name)) {
+            /* Check for uniqueness.  We special-case StaticGray
+               because we have two spellings but they are unique if
+               we find either.
+            */
+            if (found && class != StaticGray)
+                pm_error("'%s' does not uniquely describe a visual class",
+                         name);
+
+            class = VisualClassName[a].class;
+            found = TRUE;
+        }
+    }
+    if (!found)
+        pm_error("'%s' is not a visual class name", name);
+
+    return class;
+}
+
+
+
+static const char *
+nameOfVisualClass(int const class) {
+
+    unsigned int a;
+    const char * name;
+    bool found;
+
+    for (a = 0, found = FALSE; VisualClassName[a].name; ++a) {
+        if (VisualClassName[a].class == class) {
+            name = VisualClassName[a].name;
+            found = TRUE;
+        }
+    }
+    if (found)
+        return name;
+    else
+        return "[Unknown Visual Class]";
+}
+
+
+
+/* find the best visual of a particular class with a particular depth
+ */
+
+static Visual *
+bestVisualOfClassAndDepth(Display *    const disp,
+                          int          const scrn,
+                          int          const class,
+                          unsigned int const depth) {
+
+    long const vinfoMask =
+        VisualScreenMask | VisualClassMask | VisualDepthMask;
+
+    Visual * best;
+    XVisualInfo template;
+    XVisualInfo * infoP;
+    unsigned int nvisuals;
+
+    best = NULL;  /* initial value */
+
+    template.screen = scrn;
+    template.class  = class;
+    template.depth  = depth;
+    infoP = XGetVisualInfo(disp, vinfoMask, &template, (int*)&nvisuals);
+    if (infoP) {
+        /* There are visuals with this depth */
+
+        /* Not sure what to do if this gives more than one visual of a
+           particular class and depth, so just return the first one.
+        */
+
+        best = infoP->visual;
+        XFree((char *)infoP);
+    }
+    return best;
+}
+
+
+
+static void
+bestVisual(Display *      const disp,
+           int            const scrn,
+           Image *        const imageP,
+           Visual **      const rvisualPP,
+           unsigned int * const rdepthP) {
+/*----------------------------------------------------------------------------
+  Try to determine the best available visual to use for a particular
+  image
+-----------------------------------------------------------------------------*/
+    unsigned int  depth, a;
+    Screen * screen;
+    Visual * visualP;
+    Visual * default_visualP;
+
+    /* Figure out the best depth the server supports.  note that some servers
+       (such as the HP 11.3 server) actually say they support some depths but
+       have no visuals that support that depth.  Seems silly to me ...
+    */
+
+    depth = 0;
+    screen = ScreenOfDisplay(disp, scrn);
+    for (a = 0; a < screen->ndepths; ++a) {
+        if (screen->depths[a].nvisuals &&
+            ((!depth ||
+              ((depth < imageP->depth) && (screen->depths[a].depth > depth)) ||
+              ((screen->depths[a].depth >= imageP->depth) &&
+               (screen->depths[a].depth < depth)))))
+            depth = screen->depths[a].depth;
+    }
+    if (!depth) {
+        /* this shouldn't happen */
+        pm_message("bestVisual: didn't find any depths?!?");
+        depth = DefaultDepth(disp, scrn);
+    }
+    
+    /* given this depth, find the best possible visual
+     */
+    
+    default_visualP = DefaultVisual(disp, scrn);
+    switch (imageP->type) {
+    case ITRUE: {
+        /* If the default visual is DirectColor or TrueColor prioritize such
+           that we use the default type if it exists at this depth
+        */
+
+        if (default_visualP->class == TrueColor) {
+            visualP = bestVisualOfClassAndDepth(disp, scrn, TrueColor, depth);
+            if (!visualP)
+                visualP = bestVisualOfClassAndDepth(disp, scrn,
+                                                    DirectColor, depth);
+        } else {
+            visualP = bestVisualOfClassAndDepth(disp, scrn, DirectColor,
+                                                depth);
+            if (!visualP)
+                visualP = bestVisualOfClassAndDepth(disp, scrn, TrueColor,
+                                                    depth);
+        }
+        
+        if (!visualP || ((depth <= 8) &&
+                         bestVisualOfClassAndDepth(disp, scrn, PseudoColor,
+                                                   depth)))
+            visualP = bestVisualOfClassAndDepth(disp, scrn, PseudoColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, GrayScale, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticGray, depth);
+    } break;
+        
+    case IRGB: {
+        /* if it's an RGB image, we want PseudoColor if we can get it */
+
+        visualP = bestVisualOfClassAndDepth(disp, scrn, PseudoColor, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, DirectColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, TrueColor, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, GrayScale, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticGray, depth);
+    } break;
+
+    case IBITMAP: {
+        visualP = bestVisualOfClassAndDepth(disp, scrn, PseudoColor, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, GrayScale, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticGray, depth);
+
+        /* It seems pretty wasteful to use a TrueColor or DirectColor visual
+           to display a bitmap (2-color) image, so we look for those last
+        */
+
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, DirectColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, TrueColor, depth);
+    } break;
+    }
+
+    if (!visualP) { /* this shouldn't happen */
+        pm_message("bestVisual: couldn't find one?!?");
+        depth = DefaultDepth(disp, scrn);
+        visualP = DefaultVisual(disp, scrn);
+    }
+    *rvisualPP = visualP;
+    *rdepthP   = depth;
+}
+
+
+
+static void
+bestVisualOfClass(Display *      const disp,
+                  int            const scrn,
+                  Image *        const image,
+                  int            const visual_class,
+                  Visual **      const rvisual,
+                  unsigned int * const rdepth) {
+/*----------------------------------------------------------------------------
+  Given a visual class, try to find the best visual of that class at
+  the best depth.  Return a null visual and depth if we couldn't find
+  any visual of that type at any depth.
+-----------------------------------------------------------------------------*/
+  Visual       *visual;
+  Screen       *screen;
+  unsigned int  a, b, depth;
+
+  /* loop through depths looking for a visual of a good depth which matches
+   * our visual class.
+   */
+
+  screen= ScreenOfDisplay(disp, scrn);
+  visual= (Visual *)NULL;
+  depth= 0;
+  for (a= 0; a < screen->ndepths; a++) {
+    for (b= 0; b < screen->depths[a].nvisuals; b++) {
+      if ((screen->depths[a].visuals[b].class == visual_class) &&
+      (!depth ||
+       ((depth < image->depth) && (screen->depths[a].depth > depth)) ||
+       ((screen->depths[a].depth >= image->depth) &&
+        (screen->depths[a].depth < depth)))) {
+    depth= screen->depths[a].depth;
+    visual= &(screen->depths[a].visuals[b]);
+      }
+    }
+  }
+  *rvisual= visual;
+  *rdepth= depth;
+}
+
+
+
+static void
+getImageDispDimensions(viewer *       const viewerP,
+                       Image *        const imageP,
+                       unsigned int * const widthP,
+                       unsigned int * const heightP) {
+
+    if (viewerP->userChoseGeometry) {
+        *widthP  = viewerP->width;
+        *heightP = viewerP->height;
+    } else {
+        unsigned int const displayWidth = 
+            DisplayWidth(viewerP->dispP, viewerP->scrn);
+        unsigned int const displayHeight =
+            DisplayHeight(viewerP->dispP, viewerP->scrn);
+
+        /* We don't use more than 90% of display real estate unless user
+           explicitly asked for it.
+        */
+        *widthP = MIN(imageP->width, (unsigned)(displayWidth * 0.9));
+        *heightP = MIN(imageP->height, (unsigned)(displayHeight * 0.9));
+    }
+}
+
+
+
+static void
+getVisualAndDepth(Image *        const imageP,
+                  Display *      const dispP,
+                  int            const scrn,
+                  bool           const fit,
+                  bool           const visualSpec,
+                  unsigned int   const visualClass,
+                  Visual **      const visualPP,
+                  unsigned int * const depthP) {
+
+
+    /* If the user told us to fit the colormap, we must use the default
+       visual.
+    */
+
+    if (fit) {
+        *visualPP = DefaultVisual(dispP, scrn);
+        *depthP   = DefaultDepth(dispP, scrn);
+    } else {
+        Visual * visualP;
+        unsigned int depth;
+
+        visualP = NULL;
+        if (!visualSpec) {
+            /* Try to pick the best visual for the image. */
+
+            bestVisual(dispP, scrn, imageP, &visualP, &depth);
+        } else {
+            /* Try to find a visual of the specified class */
+
+            bestVisualOfClass(dispP, scrn, imageP, visualClass,
+                              &visualP, &depth);
+            if (!visualP) {
+                bestVisual(dispP, scrn, imageP, &visualP, &depth);
+                pm_message("Server does not provide %s visual, using %s",
+                           nameOfVisualClass(visualClass),
+                           nameOfVisualClass(visualP->class));
+            }
+        }
+        *visualPP = visualP;
+        *depthP   = depth;
+    }
+}
+
+
+
+static void
+setNormalSizeHints(viewer *     const viewerP,
+                   Image *      const imageP) {
+    
+    XSizeHints sh;
+    
+    sh.width  = viewerP->width;
+    sh.height = viewerP->height;
+    if (viewerP->fullscreen) {
+        sh.min_width  = sh.max_width  = viewerP->width;
+        sh.min_height = sh.max_height = viewerP->height;
+    } else {
+        sh.min_width  = 1;
+        sh.min_height = 1;
+        sh.max_width  = imageP->width;
+        sh.max_height = imageP->height;
+    }
+    sh.width_inc  = 1;
+    sh.height_inc = 1;
+
+    sh.flags = PMinSize | PMaxSize | PResizeInc;
+    if (viewerP->userChoseGeometry)
+        sh.flags |= USSize;
+    else
+        sh.flags |= PSize;
+
+    if (viewerP->userChoseGeometry) {
+        sh.x = viewerP->xpos;
+        sh.y = viewerP->ypos;
+        sh.flags |= USPosition;
+    }
+    XSetNormalHints(viewerP->dispP, viewerP->viewportWin, &sh);
+
+    sh.min_width  = sh.max_width;
+    sh.min_height = sh.max_height;
+    XSetNormalHints(viewerP->dispP, viewerP->imageWin, &sh);
+        /* Image doesn't shrink */
+}
+
+
+
+static void
+setWMHints(viewer * const viewerP) {
+
+    XWMHints wmh;
+
+    wmh.input = TRUE;
+    wmh.flags = InputHint;
+    XSetWMHints(viewerP->dispP, viewerP->viewportWin, &wmh);
+}
+
+
+#define CTL_C '\003'
+
+typedef enum exitReason {
+    EXIT_NONE,
+    EXIT_QUIT,
+    EXIT_WM_KILL,
+    EXIT_DESTROYED
+} exitReason;
+
+
+
+static void
+run(viewer *     const viewerP,
+    Image *      const imageP,
+    XImageInfo * const ximageInfoP,
+    int          const initPixx,
+    int          const initPixy,
+    bool         const install,
+    exitReason * const exitReasonP) {
+
+    int lastx, lasty;
+    int pixx, pixy;
+    union {
+        XEvent              event;
+        XAnyEvent           any;
+        XButtonEvent        button;
+        XKeyEvent           key;
+        XConfigureEvent     configure;
+        XExposeEvent        expose;
+        XMotionEvent        motion;
+        XResizeRequestEvent resize;
+        XClientMessageEvent message;
+    } event;
+    exitReason exitReason;
+
+    lastx = lasty = -1;
+    pixx   = initPixx;
+    pixy   = initPixy;
+
+    exitReason = EXIT_NONE;  /* No reason to exit yet */
+
+    while (exitReason == EXIT_NONE) {
+        XNextEvent(viewerP->dispP, &event.event);
+
+        switch (event.any.type) {
+        case ButtonPress:
+            if (event.button.button == 1) {
+                lastx = event.button.x;
+                lasty = event.button.y;
+            }
+            break;
+
+        case KeyPress: {
+            char buf[128];
+            KeySym ks;
+            XComposeStatus status;
+            Status rc;
+            
+            rc = XLookupString(&event.key, buf, 128, &ks, &status);
+            if (rc == 1) {
+                char const ret = buf[0];
+                char const lowerRet = tolower(ret);
+
+                switch (lowerRet) {
+                case CTL_C:
+                case 'q':
+                    exitReason = EXIT_QUIT;
+                    break;
+                }
+            }
+        } break;
+
+        case MotionNotify: {
+            int mousex, mousey;
+
+            if (imageP->width  <= viewerP->width &&
+                imageP->height <= viewerP->height) {
+                /* we're AT&T */
+            } else {
+                mousex = event.button.x;
+                mousey = event.button.y;
+                while (XCheckTypedEvent(viewerP->dispP, MotionNotify,
+                                        (XEvent*)&event)) {
+                    mousex = event.button.x;
+                    mousey = event.button.y;
+                }
+                pixx -= (lastx - mousex);
+                pixy -= (lasty - mousey);
+                lastx = mousex;
+                lasty = mousey;
+                placeImage(viewerP, imageP->width, imageP->height,
+                           &pixx, &pixy);
+            }
+        } break;
+
+        case ConfigureNotify:
+            viewerP->width  = event.configure.width;
+            viewerP->height = event.configure.height;
+
+            placeImage(viewerP, imageP->width, imageP->height,
+                       &pixx, &pixy);
+
+            /* Configure the cursor to indicate which directions we can drag
+             */
+
+            changeCursor(viewerP, imageP->width, imageP->height);
+            break;
+
+        case DestroyNotify:
+            exitReason = EXIT_DESTROYED;
+            break;
+
+        case Expose:
+            blitImage(ximageInfoP, imageP->width, imageP->height,
+                      event.expose.x, event.expose.y,
+                      event.expose.width, event.expose.height);
+            break;
+
+        case EnterNotify:
+            if (install)
+                XInstallColormap(viewerP->dispP, ximageInfoP->cmap);
+            break;
+
+        case LeaveNotify:
+            if (install)
+                XUninstallColormap(viewerP->dispP, ximageInfoP->cmap);
+            break;
+
+        case ClientMessage:
+            /* If we get a client message for the viewport window
+               which has the value of the delete atom, it means the
+               window manager wants us to die.
+            */
+
+            if ((event.message.window == viewerP->viewportWin) &&
+                (event.message.data.l[0] == viewerP->deleteAtom)) {
+                exitReason = EXIT_WM_KILL;
+            }
+            break;
+        }
+    }
+    *exitReasonP = exitReason;
+}
+
+
+
+static int
+retvalueFromExitReason(exitReason const exitReason) {
+
+    int retval;
+
+    switch (exitReason) {
+    case EXIT_NONE:      assert(false); break;
+    case EXIT_QUIT:      retval =  0;     break;
+    case EXIT_WM_KILL:   retval = 10;     break;
+    case EXIT_DESTROYED: retval = 20;     break;
+    }
+    return retval;
+}
+
+
+
+static void
+resizeViewer(viewer *     const viewerP,
+             unsigned int const newWidth,
+             unsigned int const newHeight) {
+    
+    if (viewerP->width != newWidth || viewerP->height != newHeight) {
+        XResizeWindow(viewerP->dispP, viewerP->viewportWin,
+                      newWidth, newHeight);
+        viewerP->width  = newWidth;
+        viewerP->height = newHeight;
+    }
+}
+
+
+
+void
+displayInViewer(viewer *       const viewerP,
+                struct Image * const imageP,
+                bool           const install,
+                bool           const userWantsPrivateCmap,
+                bool           const fit,
+                bool           const userWantsPixmap,
+                bool           const visualSpec,
+                unsigned int   const visualClass,
+                const char *   const title,
+                bool           const verbose,
+                int *          const retvalP) {
+    
+    XImageInfo *     ximageInfoP;
+    Visual *         visual;
+    unsigned int     depth;
+    bool             privateCmap;
+    int              pixx, pixy;
+    exitReason       exitReason;
+    unsigned int     windowWidth, windowHeight;
+
+    pixx = -1; pixy = -1;  /* initial values */
+
+    getImageDispDimensions(viewerP, imageP, &windowWidth, &windowHeight);
+
+    resizeViewer(viewerP, windowWidth, windowHeight);
+
+    getVisualAndDepth(imageP, viewerP->dispP, viewerP->scrn,
+                      fit, visualSpec, visualClass,
+                      &visual, &depth);
+    
+    if (verbose && (visual != DefaultVisual(viewerP->dispP, viewerP->scrn)))
+        pm_message("Using %s visual", nameOfVisualClass(visual->class));
+
+    /* If we're in slideshow mode and the user told us to fit the colormap,
+       free it here.
+    */
+
+    if (viewerP->blank) {
+        /* For the first image we display we can use the default cmap.
+           subsequent images use a private colormap (unless they're
+           bitmaps) so we don't get color erosion when switching
+           images.
+        */
+
+        if (fit) {
+            XDestroyWindow(viewerP->dispP, viewerP->imageWin);
+            viewerP->imageWin = 0;
+            viewerP->imageColormap = 0;
+            privateCmap = userWantsPrivateCmap;
+        } else if (!BITMAPP(imageP))
+            privateCmap = TRUE;
+    } else
+        privateCmap = userWantsPrivateCmap;
+
+    ximageInfoP = imageToXImage(viewerP->dispP, viewerP->scrn, visual, depth,
+                                imageP, privateCmap, fit, verbose);
+    if (!ximageInfoP)
+        pm_error("INTERNAL ERROR: Cannot convert Image to XImage");
+
+    destroyImageWindow(viewerP);
+
+    createImageWindow(viewerP, ximageInfoP, imageP, visual,
+                      userWantsPixmap, verbose);
+
+    if (title)
+        XStoreName(viewerP->dispP, viewerP->viewportWin, title);
+    else
+        XStoreName(viewerP->dispP, viewerP->viewportWin, "Unnamed");
+
+    {
+        const char * const name = iconName(title);
+        XSetIconName(viewerP->dispP, viewerP->viewportWin, name);
+        strfree(name);
+    }
+    setNormalSizeHints(viewerP, imageP);
+
+    setWMHints(viewerP);
+
+    setViewportColormap(viewerP, visual);
+
+    /* Map (display) windows */
+
+    XMapWindow(viewerP->dispP, viewerP->imageWin);
+    XMapWindow(viewerP->dispP, viewerP->viewportWin);
+
+    /* Start displaying image */
+
+    placeImage(viewerP, imageP->width, imageP->height, &pixx, &pixy);
+    if (!viewerP->blank) {
+        XResizeWindow(viewerP->dispP, viewerP->imageWin,
+                      imageP->width, imageP->height);
+        /* Clear the image window.  Ask for exposure if there is no tile. */
+        XClearArea(viewerP->dispP, viewerP->imageWin, 0, 0, 0, 0,
+                   (viewerP->pixmap == None));
+        viewerP->blank = FALSE;
+    }
+
+    changeCursor(viewerP, imageP->width, imageP->height);
+
+    /* Process X events, continuously */
+    run(viewerP, imageP, ximageInfoP, pixx, pixy, install, &exitReason);
+
+    freeXImage(imageP, ximageInfoP);
+
+    *retvalP = retvalueFromExitReason(exitReason);
+}
diff --git a/other/pamx/window.h b/other/pamx/window.h
new file mode 100644
index 00000000..2efc54b5
--- /dev/null
+++ b/other/pamx/window.h
@@ -0,0 +1,38 @@
+#ifndef WINDOW_H_INCLUDED
+#define WINDOW_H_INCLUDED
+
+#include <X11/Xlib.h>
+
+#include "pm_c_util.h"
+
+struct Image;
+struct viewer;
+typedef struct viewer viewer;
+
+int
+visualClassFromName(const char * const name);
+
+void
+createViewer(viewer **     const viewerPP,
+             Display *     const dispP,
+             int           const scrn,
+             const char *  const geometryString,
+             bool          const fullscreen);
+
+void
+destroyViewer(viewer * const viewerP);
+
+void
+displayInViewer(viewer *       const viewerP,
+                struct Image * const imageP,
+                bool           const install,
+                bool           const userWantsPrivateCmap,
+                bool           const fit,
+                bool           const userWantsPixmap,
+                bool           const visualSpec,
+                unsigned int   const visualClass,
+                const char *   const title,
+                bool           const verbose,
+                int *          const retvalP);
+
+#endif
diff --git a/other/pamx/ximageinfo.h b/other/pamx/ximageinfo.h
new file mode 100644
index 00000000..b0b54ee1
--- /dev/null
+++ b/other/pamx/ximageinfo.h
@@ -0,0 +1,25 @@
+#ifndef XIMAGEINFO_H_INCLUDED
+#define XIMAGEINFO_H_INCLUDED
+
+#include <X11/Xlib.h>
+
+
+
+typedef unsigned long  Pixel;     /* what X thinks a pixel is */
+typedef unsigned short Intensity; /* what X thinks an RGB intensity is */
+
+typedef struct {
+    /* This struct holds the X-client side bits for a rendered image.  */
+    Display * disp;       /* destination display */
+    int       scrn;       /* destination screen */
+    int       depth;      /* depth of drawable we want/have */
+    Drawable  drawable;   /* drawable to send image to */
+    Pixel     foreground;
+        /* foreground and background pixels for mono images */
+    Pixel     background;
+    Colormap  cmap;       /* colormap used for image */
+    GC        gc;         /* cached gc for sending image */
+    XImage *  ximageP;     /* ximage structure */
+} XImageInfo;
+
+#endif
diff --git a/other/pnmcolormap.c b/other/pnmcolormap.c
new file mode 100644
index 00000000..c4776001
--- /dev/null
+++ b/other/pnmcolormap.c
@@ -0,0 +1,973 @@
+/******************************************************************************
+                               pnmcolormap.c
+*******************************************************************************
+
+  Create a colormap file (a PPM image containing one pixel of each of a set
+  of colors).  Base the set of colors on an input image.
+
+  For PGM input, do the equivalent for grayscale and produce a PGM graymap.
+
+  By Bryan Henderson, San Jose, CA 2001.12.17
+
+  Derived from ppmquant, originally by Jef Poskanzer.
+
+  Copyright (C) 1989, 1991 by Jef Poskanzer.
+  Copyright (C) 2001 by Bryan Henderson.
+
+  Permission to use, copy, modify, and distribute this software and its
+  documentation for any purpose and without fee is hereby granted, provided
+  that the above copyright notice appear in all copies and that both that
+  copyright notice and this permission notice appear in supporting
+  documentation.  This software is provided "as is" without express or
+  implied warranty.
+
+******************************************************************************/
+
+#include <math.h>
+
+#include "pam.h"
+#include "pammap.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+enum methodForLargest {LARGE_NORM, LARGE_LUM};
+
+enum methodForRep {REP_CENTER_BOX, REP_AVERAGE_COLORS, REP_AVERAGE_PIXELS};
+
+typedef struct box* boxVector;
+struct box {
+    int ind;
+    int colors;
+    int sum;
+};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+    unsigned int allcolors;  /* boolean: select all colors from the input */
+    unsigned int newcolors;    
+        /* Number of colors argument; meaningless if allcolors true */
+    enum methodForLargest methodForLargest; 
+        /* -spreadintensity/-spreadluminosity options */
+    enum methodForRep methodForRep;
+        /* -center/-meancolor/-meanpixel options */
+    unsigned int sort;
+    unsigned int square;
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine (int argc, char ** argv,
+                  struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int spreadbrightness, spreadluminosity;
+    unsigned int center, meancolor, meanpixel;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "spreadbrightness", OPT_FLAG,   
+            NULL,                       &spreadbrightness, 0);
+    OPTENT3(0,   "spreadluminosity", OPT_FLAG,   
+            NULL,                       &spreadluminosity, 0);
+    OPTENT3(0,   "center",           OPT_FLAG,   
+            NULL,                       &center,           0);
+    OPTENT3(0,   "meancolor",        OPT_FLAG,   
+            NULL,                       &meancolor,        0);
+    OPTENT3(0,   "meanpixel",        OPT_FLAG,   
+            NULL,                       &meanpixel,        0);
+    OPTENT3(0, "sort",     OPT_FLAG,   NULL,                  
+            &cmdlineP->sort,       0 );
+    OPTENT3(0, "square",   OPT_FLAG,   NULL,                  
+            &cmdlineP->square,       0 );
+    OPTENT3(0, "verbose",   OPT_FLAG,   NULL,                  
+            &cmdlineP->verbose,       0 );
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+
+    if (spreadbrightness && spreadluminosity) 
+        pm_error("You cannot specify both -spreadbrightness and "
+                 "spreadluminosity.");
+    if (spreadluminosity)
+        cmdlineP->methodForLargest = LARGE_LUM;
+    else
+        cmdlineP->methodForLargest = LARGE_NORM;
+
+    if (center + meancolor + meanpixel > 1)
+        pm_error("You can specify only one of -center, -meancolor, and "
+                 "-meanpixel.");
+    if (meancolor)
+        cmdlineP->methodForRep = REP_AVERAGE_COLORS;
+    else if (meanpixel) 
+        cmdlineP->methodForRep = REP_AVERAGE_PIXELS;
+    else
+        cmdlineP->methodForRep = REP_CENTER_BOX;
+
+    if (argc-1 > 2)
+        pm_error("Program takes at most two arguments: number of colors "
+                 "and input file specification.  "
+                 "You specified %d arguments.", argc-1);
+    else {
+        if (argc-1 < 2)
+            cmdlineP->inputFilespec = "-";
+        else
+            cmdlineP->inputFilespec = argv[2];
+
+        if (argc-1 < 1)
+            pm_error("You must specify the number of colors in the "
+                     "output as an argument.");
+        else {
+            if (strcmp(argv[1], "all") == 0)
+                cmdlineP->allcolors = TRUE;
+            else {
+                char * tail;
+                long int const newcolors = strtol(argv[1], &tail, 10);
+                if (*tail != '\0')
+                    pm_error("The number of colors argument '%s' is not "
+                             "a number or 'all'", argv[1]);
+                else if (newcolors < 1)
+                    pm_error("The number of colors must be positive");
+                else if (newcolors == 1)
+                    pm_error("The number of colors must be greater than 1.");
+                else {
+                    cmdlineP->newcolors = newcolors;
+                    cmdlineP->allcolors = FALSE;
+                }
+            }
+        }
+    }
+}
+
+
+typedef int qsort_comparison_fn(const void *, const void *);
+    /* A collation function to be used as argument to qsort() */
+
+static qsort_comparison_fn compareplane;
+
+static unsigned int compareplanePlane;
+    /* This is a parameter to compareplane().  We use this global variable
+       so that compareplane() can be called by qsort(), to compare two
+       tuples.  qsort() doesn't pass any arguments except the two tuples.
+    */
+static int
+compareplane(const void * const arg1, 
+             const void * const arg2) {
+
+    const struct tupleint * const * const comparandPP  = arg1;
+    const struct tupleint * const * const comparatorPP = arg2;
+
+    return (*comparandPP)->tuple[compareplanePlane] -
+        (*comparatorPP)->tuple[compareplanePlane];
+}
+
+
+
+static qsort_comparison_fn sumcompare;
+
+static int
+sumcompare(const void * const b1, const void * const b2) {
+    return(((boxVector)b2)->sum - ((boxVector)b1)->sum);
+}
+
+
+
+
+/*
+** Here is the fun part, the median-cut colormap generator.  This is based
+** on Paul Heckbert's paper "Color Image Quantization for Frame Buffer
+** Display", SIGGRAPH '82 Proceedings, page 297.
+*/
+
+static tupletable2
+newColorMap(unsigned int const newcolors,
+            unsigned int const depth) {
+
+    tupletable2 colormap;
+    unsigned int i;
+    struct pam pam;
+
+    pam.depth = depth;
+
+    colormap.table = pnm_alloctupletable(&pam, newcolors);
+
+    for (i = 0; i < newcolors; ++i) {
+        unsigned int plane;
+        for (plane = 0; plane < depth; ++plane) 
+            colormap.table[i]->tuple[plane] = 0;
+    }
+    colormap.size = newcolors;
+
+    return colormap;
+}
+
+
+
+static boxVector
+newBoxVector(int const colors, int const sum, int const newcolors) {
+
+    boxVector bv;
+    MALLOCARRAY(bv, newcolors);
+    if (bv == NULL)
+        pm_error("out of memory allocating box vector table");
+
+    /* Set up the initial box. */
+    bv[0].ind = 0;
+    bv[0].colors = colors;
+    bv[0].sum = sum;
+
+    return bv;
+}
+
+
+
+static void
+findBoxBoundaries(tupletable2  const colorfreqtable,
+                  unsigned int const depth,
+                  unsigned int const boxStart,
+                  unsigned int const boxSize,
+                  sample             minval[],
+                  sample             maxval[]) {
+/*----------------------------------------------------------------------------
+  Go through the box finding the minimum and maximum of each
+  component - the boundaries of the box.
+-----------------------------------------------------------------------------*/
+    unsigned int plane;
+    unsigned int i;
+
+    for (plane = 0; plane < depth; ++plane) {
+        minval[plane] = colorfreqtable.table[boxStart]->tuple[plane];
+        maxval[plane] = minval[plane];
+    }
+    
+    for (i = 1; i < boxSize; ++i) {
+        unsigned int plane;
+        for (plane = 0; plane < depth; ++plane) {
+            sample const v = colorfreqtable.table[boxStart + i]->tuple[plane];
+            if (v < minval[plane]) minval[plane] = v;
+            if (v > maxval[plane]) maxval[plane] = v;
+        } 
+    }
+}
+
+
+
+static unsigned int
+largestByNorm(sample minval[], sample maxval[], unsigned int const depth) {
+    
+    unsigned int largestDimension;
+    unsigned int plane;
+
+    sample largestSpreadSoFar = 0;  
+    largestDimension = 0;
+    for (plane = 0; plane < depth; ++plane) {
+        sample const spread = maxval[plane]-minval[plane];
+        if (spread > largestSpreadSoFar) {
+            largestDimension = plane;
+            largestSpreadSoFar = spread;
+        }
+    }
+    return largestDimension;
+}
+
+
+
+static unsigned int 
+largestByLuminosity(sample minval[], sample maxval[], 
+                    unsigned int const depth) {
+/*----------------------------------------------------------------------------
+   This subroutine presumes that the tuple type is either 
+   BLACKANDWHITE, GRAYSCALE, or RGB (which implies pamP->depth is 1 or 3).
+   To save time, we don't actually check it.
+-----------------------------------------------------------------------------*/
+    unsigned int retval;
+
+    if (depth == 1)
+        retval = 0;
+    else {
+        /* An RGB tuple */
+        unsigned int largestDimension;
+        unsigned int plane;
+        double largestSpreadSoFar;
+
+        largestSpreadSoFar = 0.0;
+
+        for (plane = 0; plane < 3; ++plane) {
+            double const spread = 
+                pnm_lumin_factor[plane] * (maxval[plane]-minval[plane]);
+            if (spread > largestSpreadSoFar) {
+                largestDimension = plane;
+                largestSpreadSoFar = spread;
+            }
+        }
+        retval = largestDimension;
+    }
+    return retval;
+}
+
+
+
+static void
+centerBox(int          const boxStart,
+          int          const boxSize,
+          tupletable2  const colorfreqtable, 
+          unsigned int const depth,
+          tuple        const newTuple) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < depth; ++plane) {
+        int minval, maxval;
+        unsigned int i;
+
+        minval = maxval = colorfreqtable.table[boxStart]->tuple[plane];
+        
+        for (i = 1; i < boxSize; ++i) {
+            int const v = colorfreqtable.table[boxStart + i]->tuple[plane];
+            minval = MIN( minval, v);
+            maxval = MAX( maxval, v);
+        }
+        newTuple[plane] = (minval + maxval) / 2;
+    }
+}
+
+
+
+static void
+averageColors(int          const boxStart,
+              int          const boxSize,
+              tupletable2  const colorfreqtable, 
+              unsigned int const depth,
+              tuple        const newTuple) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < depth; ++plane) {
+        sample sum;
+        int i;
+
+        sum = 0;
+
+        for (i = 0; i < boxSize; ++i) 
+            sum += colorfreqtable.table[boxStart+i]->tuple[plane];
+
+        newTuple[plane] = sum / boxSize;
+    }
+}
+
+
+
+static void
+averagePixels(int          const boxStart,
+              int          const boxSize,
+              tupletable2  const colorfreqtable, 
+              unsigned int const depth,
+              tuple        const newTuple) {
+
+    unsigned int n;
+        /* Number of tuples represented by the box */
+    unsigned int plane;
+    unsigned int i;
+
+    /* Count the tuples in question */
+    n = 0;  /* initial value */
+    for (i = 0; i < boxSize; ++i)
+        n += colorfreqtable.table[boxStart + i]->value;
+
+
+    for (plane = 0; plane < depth; ++plane) {
+        sample sum;
+        int i;
+
+        sum = 0;
+
+        for (i = 0; i < boxSize; ++i) 
+            sum += colorfreqtable.table[boxStart+i]->tuple[plane]
+                * colorfreqtable.table[boxStart+i]->value;
+
+        newTuple[plane] = sum / n;
+    }
+}
+
+
+
+static tupletable2
+colormapFromBv(unsigned int      const newcolors,
+               boxVector         const bv, 
+               unsigned int      const boxes,
+               tupletable2       const colorfreqtable, 
+               unsigned int      const depth,
+               enum methodForRep const methodForRep) {
+    /*
+    ** Ok, we've got enough boxes.  Now choose a representative color for
+    ** each box.  There are a number of possible ways to make this choice.
+    ** One would be to choose the center of the box; this ignores any structure
+    ** within the boxes.  Another method would be to average all the colors in
+    ** the box - this is the method specified in Heckbert's paper.  A third
+    ** method is to average all the pixels in the box. 
+    */
+    tupletable2 colormap;
+    unsigned int bi;
+
+    colormap = newColorMap(newcolors, depth);
+
+    for (bi = 0; bi < boxes; ++bi) {
+        switch (methodForRep) {
+        case REP_CENTER_BOX: 
+            centerBox(bv[bi].ind, bv[bi].colors, colorfreqtable, depth, 
+                      colormap.table[bi]->tuple);
+            break;
+        case REP_AVERAGE_COLORS:
+            averageColors(bv[bi].ind, bv[bi].colors, colorfreqtable, depth,
+                          colormap.table[bi]->tuple);
+            break;
+        case REP_AVERAGE_PIXELS:
+            averagePixels(bv[bi].ind, bv[bi].colors, colorfreqtable, depth,
+                          colormap.table[bi]->tuple);
+            break;
+        default:
+            pm_error("Internal error: invalid value of methodForRep: %d",
+                     methodForRep);
+        }
+    }
+    return colormap;
+}
+
+
+
+static unsigned int
+freqTotal(tupletable2 const freqtable) {
+    
+    unsigned int i;
+    unsigned int sum;
+
+    sum = 0;
+
+    for (i = 0; i < freqtable.size; ++i)
+        sum += freqtable.table[i]->value;
+
+    return sum;
+}
+
+
+static void
+splitBox(boxVector             const bv, 
+         unsigned int *        const boxesP, 
+         unsigned int          const bi,
+         tupletable2           const colorfreqtable, 
+         unsigned int          const depth,
+         enum methodForLargest const methodForLargest) {
+/*----------------------------------------------------------------------------
+   Split Box 'bi' in the box vector bv (so that bv contains one more box
+   than it did as input).  Split it so that each new box represents about
+   half of the pixels in the distribution given by 'colorfreqtable' for 
+   the colors in the original box, but with distinct colors in each of the
+   two new boxes.
+
+   Assume the box contains at least two colors.
+-----------------------------------------------------------------------------*/
+    unsigned int const boxStart = bv[bi].ind;
+    unsigned int const boxSize  = bv[bi].colors;
+    unsigned int const sm       = bv[bi].sum;
+
+    sample * minval;  /* malloc'ed array */
+    sample * maxval;  /* malloc'ed array */
+
+    unsigned int largestDimension;
+        /* number of the plane with the largest spread */
+    unsigned int medianIndex;
+    int lowersum;
+        /* Number of pixels whose value is "less than" the median */
+
+    MALLOCARRAY_NOFAIL(minval, depth);
+    MALLOCARRAY_NOFAIL(maxval, depth);
+
+    findBoxBoundaries(colorfreqtable, depth, boxStart, boxSize, 
+                      minval, maxval);
+
+    /* Find the largest dimension, and sort by that component.  I have
+       included two methods for determining the "largest" dimension;
+       first by simply comparing the range in RGB space, and second by
+       transforming into luminosities before the comparison.
+    */
+    switch (methodForLargest) {
+    case LARGE_NORM: 
+        largestDimension = largestByNorm(minval, maxval, depth);
+        break;
+    case LARGE_LUM: 
+        largestDimension = largestByLuminosity(minval, maxval, depth);
+        break;
+    }
+                                                    
+    /* TODO: I think this sort should go after creating a box,
+       not before splitting.  Because you need the sort to use
+       the REP_CENTER_BOX method of choosing a color to
+       represent the final boxes 
+    */
+
+    /* Set the gross global variable 'compareplanePlane' as a
+       parameter to compareplane(), which is called by qsort().
+    */
+    compareplanePlane = largestDimension;
+    qsort((char*) &colorfreqtable.table[boxStart], boxSize, 
+          sizeof(colorfreqtable.table[boxStart]), 
+          compareplane);
+            
+    {
+        /* Now find the median based on the counts, so that about half
+           the pixels (not colors, pixels) are in each subdivision.  */
+
+        unsigned int i;
+
+        lowersum = colorfreqtable.table[boxStart]->value; /* initial value */
+        for (i = 1; i < boxSize - 1 && lowersum < sm/2; ++i) {
+            lowersum += colorfreqtable.table[boxStart + i]->value;
+        }
+        medianIndex = i;
+    }
+    /* Split the box, and sort to bring the biggest boxes to the top.  */
+
+    bv[bi].colors = medianIndex;
+    bv[bi].sum = lowersum;
+    bv[*boxesP].ind = boxStart + medianIndex;
+    bv[*boxesP].colors = boxSize - medianIndex;
+    bv[*boxesP].sum = sm - lowersum;
+    ++(*boxesP);
+    qsort((char*) bv, *boxesP, sizeof(struct box), sumcompare);
+
+    free(minval); free(maxval);
+}
+
+
+
+static void
+mediancut(tupletable2           const colorfreqtable, 
+          unsigned int          const depth,
+          int                   const newcolors,
+          enum methodForLargest const methodForLargest,
+          enum methodForRep     const methodForRep,
+          tupletable2 *         const colormapP) {
+/*----------------------------------------------------------------------------
+   Compute a set of only 'newcolors' colors that best represent an
+   image whose pixels are summarized by the histogram
+   'colorfreqtable'.  Each tuple in that table has depth 'depth'.
+   colorfreqtable.table[i] tells the number of pixels in the subject image
+   have a particular color.
+
+   As a side effect, sort 'colorfreqtable'.
+-----------------------------------------------------------------------------*/
+    boxVector bv;
+    unsigned int bi;
+    unsigned int boxes;
+    bool multicolorBoxesExist;
+        /* There is at least one box that contains at least 2 colors; ergo,
+           there is more splitting we can do.
+        */
+
+    bv = newBoxVector(colorfreqtable.size, freqTotal(colorfreqtable),
+                      newcolors);
+    boxes = 1;
+    multicolorBoxesExist = (colorfreqtable.size > 1);
+
+    /* Main loop: split boxes until we have enough. */
+    while (boxes < newcolors && multicolorBoxesExist) {
+        /* Find the first splittable box. */
+        for (bi = 0; bi < boxes && bv[bi].colors < 2; ++bi);
+        if (bi >= boxes)
+            multicolorBoxesExist = FALSE;
+        else 
+            splitBox(bv, &boxes, bi, colorfreqtable, depth, methodForLargest);
+    }
+    *colormapP = colormapFromBv(newcolors, bv, boxes, colorfreqtable, depth,
+                                methodForRep);
+
+    free(bv);
+}
+
+
+
+
+static void
+validateCompatibleImage(struct pam * const inpamP,
+                        struct pam * const firstPamP,
+                        unsigned int const imageSeq) {
+
+    if (inpamP->depth != firstPamP->depth)
+        pm_error("Image %u depth (%u) is not the same as Image 0 (%u)",
+                 imageSeq, inpamP->depth, firstPamP->depth);
+    if (inpamP->maxval != firstPamP->maxval)
+        pm_error("Image %u maxval (%u) is not the same as Image 0 (%u)",
+                 imageSeq,
+                 (unsigned)inpamP->maxval, (unsigned)firstPamP->maxval);
+    if (inpamP->format != firstPamP->format)
+        pm_error("Image %u format (%d) is not the same as Image 0 (%d)",
+                 imageSeq, inpamP->format, firstPamP->format);
+    if (!STREQ(inpamP->tuple_type, firstPamP->tuple_type))
+        pm_error("Image %u tuple type (%s) is not the same as Image 0 (%s)",
+                 imageSeq, inpamP->tuple_type, firstPamP->tuple_type);
+}
+
+
+
+static void
+addImageColorsToHash(struct pam *   const pamP,
+                     tuplehash      const tuplehash,
+                     unsigned int * const colorCountP) {
+
+    tuple * tuplerow;
+    unsigned int row;
+    
+    tuplerow = pnm_allocpamrow(pamP);
+
+    for (row = 0; row < pamP->height; ++row) {
+        unsigned int col;
+
+        pnm_readpamrow(pamP, tuplerow);
+
+        for (col = 0; col < pamP->width; ++col) {
+            bool firstOccurrence;
+
+            pnm_addtuplefreqoccurrence(pamP, tuplerow[col], tuplehash,
+                                       &firstOccurrence);
+
+            if (firstOccurrence)
+                ++(*colorCountP);
+        }
+    }
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+static void
+computeHistogram(FILE *         const ifP,
+                 int *          const formatP,
+                 struct pam *   const freqPamP,
+                 tupletable2 *  const colorfreqtableP) {
+/*----------------------------------------------------------------------------
+  Make a histogram of the colors in the image stream in the file '*ifP'.
+  
+  Return as *freqPamP a description of the tuple values in the histogram.
+  Only the fields of *freqPamP that describe individual tuples are
+  meaningful (depth, maxval, tuple type);
+
+  As a fringe benefit, also return the format of the input file as
+  *formatP.
+----------------------------------------------------------------------------*/
+    unsigned int imageSeq;
+    struct pam firstPam;
+    tuplehash tuplehash;
+    unsigned int colorCount;
+    bool eof;
+    
+    pm_message("making histogram...");
+
+    tuplehash = pnm_createtuplehash();
+    colorCount = 0;
+
+    eof = FALSE;
+
+    for (imageSeq = 0; !eof; ++imageSeq) {
+        struct pam inpam;
+        
+        pm_message("Scanning image %u", imageSeq);
+
+        pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+        if (imageSeq == 0)
+            firstPam = inpam;
+        else
+            validateCompatibleImage(&inpam, &firstPam, imageSeq);
+    
+        addImageColorsToHash(&inpam, tuplehash, &colorCount);
+
+        pm_message("%u colors so far", colorCount);
+
+        pnm_nextimage(ifP, &eof);
+    }
+    colorfreqtableP->table =
+        pnm_tuplehashtotable(&firstPam, tuplehash, colorCount);
+    colorfreqtableP->size = colorCount;
+
+    pnm_destroytuplehash(tuplehash);
+
+    pm_message("%u colors found", colorfreqtableP->size);
+    
+    freqPamP->size   = sizeof(*freqPamP);
+    freqPamP->len    = PAM_STRUCT_SIZE(tuple_type);
+    freqPamP->maxval = firstPam.maxval;
+    freqPamP->bytes_per_sample = pnm_bytespersample(freqPamP->maxval);
+    freqPamP->depth  = firstPam.depth;
+    STRSCPY(freqPamP->tuple_type, firstPam.tuple_type);
+    
+    *formatP = firstPam.format;
+}
+
+
+
+static void
+computeColorMapFromInput(FILE *                const ifP,
+                         bool                  const allColors, 
+                         int                   const reqColors, 
+                         enum methodForLargest const methodForLargest,
+                         enum methodForRep     const methodForRep,
+                         int *                 const formatP,
+                         struct pam *          const freqPamP,
+                         tupletable2 *         const colormapP) {
+/*----------------------------------------------------------------------------
+   Produce a colormap containing the best colors to represent the
+   image stream in file 'ifP'.  Figure it out using the median cut
+   technique.
+
+   The colormap will have 'reqcolors' or fewer colors in it, unless
+   'allcolors' is true, in which case it will have all the colors that
+   are in the input.
+
+   The colormap has the same maxval as the input.
+
+   Put the colormap in newly allocated storage as a tupletable2 
+   and return its address as *colormapP.  Return the number of colors in
+   it as *colorsP and its maxval as *colormapMaxvalP.
+
+   Return the characteristics of the input file as
+   *formatP and *freqPamP.  (This information is not really
+   relevant to our colormap mission; just a fringe benefit).
+-----------------------------------------------------------------------------*/
+    tupletable2 colorfreqtable;
+
+    computeHistogram(ifP, formatP, freqPamP, &colorfreqtable);
+    
+    if (allColors) {
+        *colormapP = colorfreqtable;
+    } else {
+        if (colorfreqtable.size <= reqColors) {
+            pm_message("Image already has few enough colors (<=%d).  "
+                       "Keeping same colors.", reqColors);
+            *colormapP = colorfreqtable;
+        } else {
+            pm_message("choosing %d colors...", reqColors);
+            mediancut(colorfreqtable, freqPamP->depth,
+                      reqColors, methodForLargest, methodForRep,
+                      colormapP);
+            pnm_freetupletable2(freqPamP, colorfreqtable);
+        }
+    }
+}
+
+
+
+static void
+sortColormap(tupletable2  const colormap, 
+             sample       const depth) {
+/*----------------------------------------------------------------------------
+   Sort the colormap in place, in order of ascending Plane 0 value, 
+   the Plane 1 value, etc.
+
+   Use insertion sort.
+-----------------------------------------------------------------------------*/
+    int i;
+    
+    pm_message("Sorting %u colors...", colormap.size);
+
+    for (i = 0; i < colormap.size; ++i) {
+        int j;
+        for (j = i+1; j < colormap.size; ++j) {
+            unsigned int plane;
+            bool iIsGreater, iIsLess;
+
+            iIsGreater = FALSE; iIsLess = FALSE;
+            for (plane = 0; 
+                 plane < depth && !iIsGreater && !iIsLess; 
+                 ++plane) {
+                if (colormap.table[i]->tuple[plane] >
+                    colormap.table[j]->tuple[plane])
+                    iIsGreater = TRUE;
+                else if (colormap.table[i]->tuple[plane] <
+                         colormap.table[j]->tuple[plane])
+                    iIsLess = TRUE;
+            }            
+            if (iIsGreater) {
+                for (plane = 0; plane < depth; ++plane) {
+                    sample const temp = colormap.table[i]->tuple[plane];
+                    colormap.table[i]->tuple[plane] =
+                        colormap.table[j]->tuple[plane];
+                    colormap.table[j]->tuple[plane] = temp;
+                }
+            }
+        }    
+    }
+}
+
+
+
+static void 
+colormapToSquare(struct pam * const pamP,
+                 tupletable2  const colormap,
+                 tuple ***    const outputRasterP) {
+    {
+        unsigned int const intsqrt = (int)sqrt((float)colormap.size);
+        if (intsqrt * intsqrt == colormap.size) 
+            pamP->width = intsqrt;
+        else 
+            pamP->width = intsqrt + 1;
+    }
+    {
+        unsigned int const intQuotient = colormap.size / pamP->width;
+        if (pamP->width * intQuotient == colormap.size)
+            pamP->height = intQuotient;
+        else
+            pamP->height = intQuotient + 1;
+    }
+    {
+        tuple ** outputRaster;
+        unsigned int row;
+        unsigned int colormapIndex;
+        
+        outputRaster = pnm_allocpamarray(pamP);
+
+        colormapIndex = 0;  /* initial value */
+        
+        for (row = 0; row < pamP->height; ++row) {
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col) {
+                unsigned int plane;
+                for (plane = 0; plane < pamP->depth; ++plane) {
+                    outputRaster[row][col][plane] = 
+                        colormap.table[colormapIndex]->tuple[plane];
+                }
+                if (colormapIndex < colormap.size-1)
+                    ++colormapIndex;
+            }
+        }
+        *outputRasterP = outputRaster;
+    } 
+}
+
+
+
+static void 
+colormapToSingleRow(struct pam * const pamP,
+                    tupletable2  const colormap,
+                    tuple ***    const outputRasterP) {
+
+    tuple ** outputRaster;
+    unsigned int col;
+    
+    pamP->width = colormap.size;
+    pamP->height = 1;
+    
+    outputRaster = pnm_allocpamarray(pamP);
+    
+    for (col = 0; col < pamP->width; ++col) {
+        int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            outputRaster[0][col][plane] = colormap.table[col]->tuple[plane];
+    }
+    *outputRasterP = outputRaster;
+}
+
+
+
+static void
+colormapToImage(int                const format,
+                const struct pam * const colormapPamP,
+                tupletable2        const colormap,
+                bool               const sort,
+                bool               const square,
+                struct pam *       const outpamP, 
+                tuple ***          const outputRasterP) {
+/*----------------------------------------------------------------------------
+   Create a tuple array and pam structure for an image which includes
+   one pixel of each of the colors in the colormap 'colormap'.
+
+   May rearrange the contents of 'colormap'.
+-----------------------------------------------------------------------------*/
+    outpamP->size             = sizeof(*outpamP);
+    outpamP->len              = PAM_STRUCT_SIZE(tuple_type);
+    outpamP->format           = format,
+    outpamP->plainformat      = FALSE;
+    outpamP->depth            = colormapPamP->depth;
+    outpamP->maxval           = colormapPamP->maxval;
+    outpamP->bytes_per_sample = pnm_bytespersample(outpamP->maxval);
+    STRSCPY(outpamP->tuple_type, colormapPamP->tuple_type);
+
+    if (sort)
+        sortColormap(colormap, outpamP->depth);
+
+    if (square) 
+        colormapToSquare(outpamP, colormap, outputRasterP);
+    else 
+        colormapToSingleRow(outpamP, colormap, outputRasterP);
+}
+
+
+
+int
+main(int argc, char * argv[] ) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    int format;
+    struct pam colormapPam;
+    struct pam outpam;
+    tuple ** colormapRaster;
+    tupletable2 colormap;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    computeColorMapFromInput(ifP,
+                             cmdline.allcolors, cmdline.newcolors, 
+                             cmdline.methodForLargest, 
+                             cmdline.methodForRep,
+                             &format, &colormapPam, &colormap);
+
+    pm_close(ifP);
+
+    colormapToImage(format, &colormapPam, colormap,
+                    cmdline.sort, cmdline.square, &outpam, &colormapRaster);
+
+    if (cmdline.verbose)
+        pm_message("Generating %u x %u image", outpam.width, outpam.height);
+
+    outpam.file = stdout;
+    
+    pnm_writepam(&outpam, colormapRaster);
+    
+    pnm_freetupletable2(&colormapPam, colormap);
+
+    pnm_freepamarray(colormapRaster, &outpam);
+
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/other/ppmdcfont.c b/other/ppmdcfont.c
new file mode 100644
index 00000000..701277a9
--- /dev/null
+++ b/other/ppmdcfont.c
@@ -0,0 +1,200 @@
+#include <stdio.h>
+#include <assert.h>
+
+#include "ppm.h"
+#include "nstring.h"
+#include "ppmdfont.h"
+
+
+
+static void
+generateHeader(struct ppmd_fontHeader const fontHeader) {
+
+    fprintf(stdout, "  {/* .header */\n");
+    fprintf(stdout, "    {'p','p','m','d','f','o','n','t'},\n");
+    fprintf(stdout, "    0x%02x,\n", fontHeader.format);
+    fprintf(stdout, "    %u,\n", fontHeader.characterCount);
+    fprintf(stdout, "    %u\n", fontHeader.firstCodePoint);
+    fprintf(stdout, "  }\n");
+}
+
+
+
+static void
+generateGlyphCommand(struct ppmd_glyphCommand const glyphCommand) {
+
+    const char * verb;
+
+    switch (glyphCommand.verb) {
+    case CMD_NOOP:     verb = "CMD_NOOP";     break;
+    case CMD_DRAWLINE: verb = "CMD_DRAWLINE"; break;
+    case CMD_MOVEPEN:  verb = "CMD_MOVEPEN";  break;
+    }
+    
+    fprintf(stdout, "  {/* glyphCommand */ %s, %u, %u }\n",
+            verb, glyphCommand.x, glyphCommand.y);
+    
+}
+
+
+
+static void
+generateCommandTable(struct ppmd_glyph const glyph,
+                     const char *      const variableName) {
+
+    unsigned int commandNum;
+
+    fprintf(stdout, "struct ppmd_glyphCommand const %s[%u] = {\n",
+            variableName, glyph.header.commandCount);
+
+    for (commandNum = 0;
+         commandNum < glyph.header.commandCount;
+         ++commandNum) {
+
+        generateGlyphCommand(glyph.commandList[commandNum]);
+
+        if (commandNum < glyph.header.commandCount-1)
+            fprintf(stdout, "  ,\n");
+    }
+}
+
+
+
+static void
+generateCommandTables(const struct ppmd_font * const fontP,
+                      const char *             const glyphTableVariableName) {
+
+    unsigned int relativeCodePoint;
+
+    for (relativeCodePoint = 0;
+         relativeCodePoint < fontP->header.characterCount;
+         ++relativeCodePoint) {
+
+        if (fontP->glyphTable[relativeCodePoint].header.commandCount > 0) {
+            const char * commandTableVariableName;
+
+            asprintfN(&commandTableVariableName, "%s_cmd_%u",
+                      glyphTableVariableName,
+                      fontP->header.firstCodePoint + relativeCodePoint);
+            
+            generateCommandTable(fontP->glyphTable[relativeCodePoint],
+                                 commandTableVariableName);
+
+            strfree(commandTableVariableName);
+
+            fprintf(stdout, "};\n");
+            fprintf(stdout, "\n");
+        }
+    }
+}
+
+
+
+static void
+generateGlyph(
+    struct ppmd_glyph const glyph,
+    const char *      const commandTableVariableName) {
+
+    fprintf(stdout, "  { /* glyph */\n");
+    fprintf(stdout, "    { /* header */ %u, %u, %u}\n",
+            glyph.header.commandCount,
+            glyph.header.skipBefore,
+            glyph.header.skipAfter
+            );
+
+    fprintf(stdout, "    ,\n");
+    if (glyph.header.commandCount == 0)
+        fprintf(stdout, "    NULL\n");
+    else
+        fprintf(stdout, "    %s\n", commandTableVariableName);
+
+    fprintf(stdout, "  }\n");
+}
+
+
+
+static void
+generateGlyphTable(const struct ppmd_font * const fontP,
+                   const char *             const variableName) {
+
+    unsigned int relativeCodePoint;
+
+    generateCommandTables(fontP, variableName);
+
+    fprintf(stdout, "struct ppmd_glyph const %s[%u] = {\n",
+            variableName, fontP->header.characterCount);
+
+    for (relativeCodePoint = 0;
+         relativeCodePoint < fontP->header.characterCount;
+         ++relativeCodePoint) {
+
+        const char * commandTableVariableName;
+
+        asprintfN(&commandTableVariableName, "%s_cmd_%u",
+                  variableName,
+                  fontP->header.firstCodePoint + relativeCodePoint);
+        
+        generateGlyph(fontP->glyphTable[relativeCodePoint],
+                      commandTableVariableName);
+
+        strfree(commandTableVariableName);
+
+        if (relativeCodePoint < fontP->header.characterCount - 1)
+            fprintf(stdout, "  ,\n");
+    }
+    fprintf(stdout, "};\n");
+    fprintf(stdout, "\n");
+}
+
+
+
+static void
+generateFont(const struct ppmd_font * const fontP,
+             const char *             const fontVariableName,
+             const char *             const glyphTableVariableName) {
+
+    fprintf(stdout, "struct ppmd_font const %s = {\n", fontVariableName);
+    
+    generateHeader(fontP->header);
+
+    fprintf(stdout, "  ,\n");
+
+    fprintf(stdout, "  /* .glyphTable: */ %s\n", glyphTableVariableName);
+
+    fprintf(stdout, "};\n");
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    const char fontVariableName[] = "ppmd_standardfont";
+    const struct ppmd_font * fontP;
+    const char * glyphTableVariableName;
+
+    ppm_init(&argc, argv);
+
+    ppmd_read_font(stdin, &fontP);
+
+    fprintf(stdout, "/* THIS FILE WAS GENERATED BY 'ppmdcfont' from a "
+            "ppmdfont file. */\n");
+
+    fprintf(stdout, "\n");
+
+    fprintf(stdout, "#include \"ppmdfont.h\"\n\n");
+
+    asprintfN(&glyphTableVariableName, "%s_glyphTable", fontVariableName);
+
+    generateGlyphTable(fontP, glyphTableVariableName);
+
+    fprintf(stdout, "\n");
+        
+    generateFont(fontP, fontVariableName, glyphTableVariableName);
+
+    strfree(glyphTableVariableName);
+
+    ppmd_free_font(fontP);
+    
+    return 0;
+}
diff --git a/other/ppmddumpfont.c b/other/ppmddumpfont.c
new file mode 100644
index 00000000..3ab477ab
--- /dev/null
+++ b/other/ppmddumpfont.c
@@ -0,0 +1,89 @@
+#include <stdio.h>
+#include <assert.h>
+
+#include "ppm.h"
+#include "ppmdfont.h"
+
+
+
+static int
+untwos(unsigned char const arg) {
+
+    if (arg >= 128)
+        return arg - 256;
+    else
+        return arg;
+}
+
+
+
+static void
+dumpHeader(struct ppmd_fontHeader const fontHeader) {
+    
+    pm_message("Font has %u characters", fontHeader.characterCount);
+    pm_message("Font has code points %u through %u",
+               fontHeader.firstCodePoint,
+               fontHeader.firstCodePoint + fontHeader.characterCount - 1);
+}
+
+
+
+static void
+dumpGlyph(struct ppmd_glyph const glyph) {
+
+    unsigned int commandNum;
+
+    pm_message("  skip before: %u pixels; skip after: %u pixels; "
+               "%u commands:", 
+               glyph.header.skipBefore,
+               glyph.header.skipAfter,
+               glyph.header.commandCount);
+
+    for (commandNum = 0;
+         commandNum < glyph.header.commandCount;
+         ++commandNum) {
+         
+        struct ppmd_glyphCommand const glyphCommand =
+            glyph.commandList[commandNum];
+        
+        const char * verbDisp;
+
+        switch (glyphCommand.verb) {
+        case CMD_NOOP:     verbDisp = "NOOP";     break;
+        case CMD_DRAWLINE: verbDisp = "DRAWLINE"; break;
+        case CMD_MOVEPEN:  verbDisp = "MOVEPEN";  break;
+        }
+
+        pm_message("    %s %d %d",
+                   verbDisp, untwos(glyphCommand.x), untwos(glyphCommand.y));
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    const struct ppmd_font * fontP;
+    unsigned int relativeCodePoint;
+
+    ppm_init(&argc, argv);
+
+    ppmd_read_font(stdin, &fontP);
+
+    dumpHeader(fontP->header);
+
+    for (relativeCodePoint = 0;
+         relativeCodePoint < fontP->header.characterCount;
+         ++relativeCodePoint) {
+
+        pm_message("Code point %u:",
+                   fontP->header.firstCodePoint + relativeCodePoint);
+
+        dumpGlyph(fontP->glyphTable[relativeCodePoint]);
+    }
+
+    ppmd_free_font(fontP);
+    
+    return 0;
+}
diff --git a/other/ppmdmkfont.c b/other/ppmdmkfont.c
new file mode 100644
index 00000000..7cf1256f
--- /dev/null
+++ b/other/ppmdmkfont.c
@@ -0,0 +1,705 @@
+#include <stdio.h>
+#include <assert.h>
+
+#include "ppm.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "ppmdfont.h"
+
+
+/*        Stroke character definitions
+
+   The  following  character  definitions are derived from the (public
+   domain) Hershey plotter  font  database,  using  the  single-stroke
+   Roman font.
+
+   Each  character  definition  begins  with 3 bytes which specify the
+   number of X, Y plot pairs which follow, the negative  of  the  skip
+   before  starting  to  draw  the  characters, and the skip after the
+   character.  The first plot pair moves the pen to that location  and
+   subsequent  pairs  draw  to  the  location given.  A pair of 192, 0
+   raises the pen, moves to the location given by the following  pair,
+   and resumes drawing with the pair after that.
+
+   The  values  in  the  definition  tables are 8-bit two's complement
+   signed numbers.  We  declare  the  table  as  "unsigned  char"  and
+   manually  sign-extend  the  values because C compilers differ as to
+   whether the type "char" is signed or unsigned, and  some  compilers
+   don't  accept the qualifier "signed" which we would like to use for
+   these items.  We specify negative numbers as their  unsigned  two's
+   complements  to  avoid  complaints  from compilers which don't like
+   initialising unsigned data with signed values.  Ahhh,  portability.
+*/
+
+static unsigned char char32[] =
+{ 0, 0, 21 };
+
+static unsigned char char33[] =
+{ 8, 251, 5,
+  0, 244, 0, 2, 192, 0, 0, 7, 255, 8, 0, 9, 1, 8, 0, 7 };
+
+static unsigned char char34[] =
+{ 17, 253, 15,
+  2, 244, 1, 245, 0, 244, 1, 243, 2, 244, 2, 246, 1,
+  248, 0, 249, 192, 0, 10, 244, 9, 245, 8, 244, 9, 243, 10, 244,
+  10, 246, 9, 248, 8, 249, };
+
+static unsigned char char35[] =
+{ 11, 246, 11,
+  1, 240, 250, 16, 192, 0, 7, 240, 0, 16, 192, 0, 250,
+  253, 8, 253, 192, 0, 249, 3, 7, 3 };
+
+static unsigned char char36[] =
+{ 26, 246, 10,
+  254, 240, 254, 13, 192, 0, 2, 240, 2, 13, 192, 0, 7,
+  247, 5, 245, 2, 244, 254, 244, 251, 245, 249, 247, 249, 249, 250,
+  251, 251, 252, 253, 253, 3, 255, 5, 0, 6, 1, 7, 3, 7, 6, 5, 8, 2,
+  9, 254, 9, 251, 8, 249, 6 };
+
+static unsigned char char37[] =
+{ 31, 244, 12,
+  9, 244, 247, 9, 192, 0, 252, 244, 254, 246, 254,
+  248, 253, 250, 251, 251, 249, 251, 247, 249, 247, 247, 248, 245,
+  250, 244, 252, 244, 254, 245, 1, 246, 4, 246, 7, 245, 9, 244,
+  192, 0, 5, 2, 3, 3, 2, 5, 2, 7, 4, 9, 6, 9, 8, 8, 9, 6, 9, 4, 7,
+  2, 5, 2 };
+  
+static unsigned char char38[] =
+{ 34, 243, 13,
+  10, 253, 10, 252, 9, 251, 8, 251, 7, 252, 6, 254, 4,
+  3, 2, 6, 0, 8, 254, 9, 250, 9, 248, 8, 247, 7, 246, 5, 246, 3,
+  247, 1, 248, 0, 255, 252, 0, 251, 1, 249, 1, 247, 0, 245, 254,
+  244, 252, 245, 251, 247, 251, 249, 252, 252, 254, 255, 3, 6, 5,
+  8, 7, 9, 9, 9, 10, 8, 10, 7 };
+
+static unsigned char char39[] =
+{ 7, 251, 5,
+  0, 246, 255, 245, 0, 244, 1, 245, 1, 247, 0, 249, 255,
+  250 };
+
+static unsigned char char40[] =
+{ 10, 249, 7,
+  4, 240, 2, 242, 0, 245, 254, 249, 253, 254, 253, 2,
+  254, 7, 0, 11, 2, 14, 4, 16 };
+
+static unsigned char char41[] =
+{ 10, 249, 7,
+  252, 240, 254, 242, 0, 245, 2, 249, 3, 254, 3, 2, 2,
+  7, 0, 11, 254, 14, 252, 16 };
+
+static unsigned char char42[] =
+{ 8, 248, 8,
+  0, 250, 0, 6, 192, 0, 251, 253, 5, 3, 192, 0, 5, 253,
+  251, 3 };
+
+static unsigned char char43[] =
+{ 5, 243, 13,
+  0, 247, 0, 9, 192, 0, 247, 0, 9, 0 };
+
+static unsigned char char44[] =
+{ 8, 251, 5,
+  1, 8, 0, 9, 255, 8, 0, 7, 1, 8, 1, 10, 0, 12, 255, 13
+};
+
+static unsigned char char45[] =
+{ 2, 243, 13,
+  247, 0, 9, 0 };
+
+static unsigned char char46[] =
+{ 5, 251, 5,
+  0, 7, 255, 8, 0, 9, 1, 8, 0, 7 };
+
+static unsigned char char47[] =
+{ 2, 245, 11,
+  9, 240, 247, 16 };
+
+static unsigned char char48[] =
+{ 17, 246, 10,
+  255, 244, 252, 245, 250, 248, 249, 253, 249, 0, 250,
+  5, 252, 8, 255, 9, 1, 9, 4, 8, 6, 5, 7, 0, 7, 253, 6, 248, 4,
+  245, 1, 244, 255, 244 };
+
+static unsigned char char49[] =
+{ 4, 246, 10,
+  252, 248, 254, 247, 1, 244, 1, 9 };
+
+static unsigned char char50[] =
+{ 14, 246, 10,
+  250, 249, 250, 248, 251, 246, 252, 245, 254, 244, 2,
+  244, 4, 245, 5, 246, 6, 248, 6, 250, 5, 252, 3, 255, 249, 9, 7, 9
+};
+
+static unsigned char char51[] =
+{ 15, 246, 10,
+  251, 244, 6, 244, 0, 252, 3, 252, 5, 253, 6, 254, 7,
+  1, 7, 3, 6, 6, 4, 8, 1, 9, 254, 9, 251, 8, 250, 7, 249, 5 };
+
+
+static unsigned char char52[] =
+{ 6, 246, 10,
+  3, 244, 249, 2, 8, 2, 192, 0, 3, 244, 3, 9 };
+
+static unsigned char char53[] =
+{ 17, 246, 10,
+  5, 244, 251, 244, 250, 253, 251, 252, 254, 251, 1,
+  251, 4, 252, 6, 254, 7, 1, 7, 3, 6, 6, 4, 8, 1, 9, 254, 9, 251,
+  8, 250, 7, 249, 5 };
+
+static unsigned char char54[] =
+{ 23, 246, 10,
+  6, 247, 5, 245, 2, 244, 0, 244, 253, 245, 251, 248,
+  250, 253, 250, 2, 251, 6, 253, 8, 0, 9, 1, 9, 4, 8, 6, 6, 7, 3,
+  7, 2, 6, 255, 4, 253, 1, 252, 0, 252, 253, 253, 251, 255, 250, 2
+};
+
+static unsigned char char55[] =
+{ 5, 246, 10,
+  7, 244, 253, 9, 192, 0, 249, 244, 7, 244 };
+    
+static unsigned char char56[] =
+{ 29, 246, 10,
+  254, 244, 251, 245, 250, 247, 250, 249, 251, 251,
+  253, 252, 1, 253, 4, 254, 6, 0, 7, 2, 7, 5, 6, 7, 5, 8, 2, 9,
+  254, 9, 251, 8, 250, 7, 249, 5, 249, 2, 250, 0, 252, 254, 255,
+  253, 3, 252, 5, 251, 6, 249, 6, 247, 5, 245, 2, 244, 254, 244 };
+
+static unsigned char char57[] =
+{ 23, 246, 10,
+  6, 251, 5, 254, 3, 0, 0, 1, 255, 1, 252, 0, 250,
+  254, 249, 251, 249, 250, 250, 247, 252, 245, 255, 244, 0, 244, 3,
+  245, 5, 247, 6, 251, 6, 0, 5, 5, 3, 8, 0, 9, 254, 9, 251, 8, 250,
+  6 };
+      
+static unsigned char char58[] =
+{ 11, 251, 5,
+  0, 251, 255, 252, 0, 253, 1, 252, 0, 251, 192, 0, 0,
+  7, 255, 8, 0, 9, 1, 8, 0, 7 };
+                                
+static unsigned char char59[] =
+{ 14, 251, 5,
+  0, 251, 255, 252, 0, 253, 1, 252, 0, 251, 192, 0, 1,
+  8, 0, 9, 255, 8, 0, 7, 1, 8, 1, 10, 0, 12, 255, 13 };
+
+static unsigned char char60[] =
+{ 3, 244, 12,
+  8, 247, 248, 0, 8, 9 };
+
+static unsigned char char61[] =
+{ 5, 243, 13,
+  247, 253, 9, 253, 192, 0, 247, 3, 9, 3 };
+
+static unsigned char char62[] =
+{ 3, 244, 12,
+  248, 247, 8, 0, 248, 9 };
+
+static unsigned char char63[] =
+{ 20, 247, 9,
+  250, 249, 250, 248, 251, 246, 252, 245, 254, 244, 2,
+  244, 4, 245, 5, 246, 6, 248, 6, 250, 5, 252, 4, 253, 0, 255, 0,
+  2, 192, 0, 0, 7, 255, 8, 0, 9, 1, 8, 0, 7 };
+
+static unsigned char char64[] =
+{ 55, 243, 14,
+  5, 252, 4, 250, 2, 249, 255, 249, 253, 250, 252,
+  251, 251, 254, 251, 1, 252, 3, 254, 4, 1, 4, 3, 3, 4, 1, 192, 0,
+  255, 249, 253, 251, 252, 254, 252, 1, 253, 3, 254, 4, 192, 0, 5,
+  249, 4, 1, 4, 3, 6, 4, 8, 4, 10, 2, 11, 255, 11, 253, 10, 250, 9,
+  248, 7, 246, 5, 245, 2, 244, 255, 244, 252, 245, 250, 246, 248,
+  248, 247, 250, 246, 253, 246, 0, 247, 3, 248, 5, 250, 7, 252, 8,
+  255, 9, 2, 9, 5, 8, 7, 7, 8, 6, 192, 0, 6, 249, 5, 1, 5, 3, 6, 4
+};
+  
+static unsigned char char65[] =
+{ 8, 247, 9,
+  0, 244, 248, 9, 192, 0, 0, 244, 8, 9, 192, 0, 251, 2,
+  5, 2 };
+
+static unsigned char char66[] =
+{ 23, 245, 10,
+  249, 244, 249, 9, 192, 0, 249, 244, 2, 244, 5, 245,
+  6, 246, 7, 248, 7, 250, 6, 252, 5, 253, 2, 254, 192, 0, 249, 254,
+  2, 254, 5, 255, 6, 0, 7, 2, 7, 5, 6, 7, 5, 8, 2, 9, 249, 9 };
+
+static unsigned char char67[] =
+{ 18, 246, 11,
+  8, 249, 7, 247, 5, 245, 3, 244, 255, 244, 253, 245,
+  251, 247, 250, 249, 249, 252, 249, 1, 250, 4, 251, 6, 253, 8,
+  255, 9, 3, 9, 5, 8, 7, 6, 8, 4 };
+
+static unsigned char char68[] =
+{ 15, 245, 10,
+  249, 244, 249, 9, 192, 0, 249, 244, 0, 244, 3, 245,
+  5, 247, 6, 249, 7, 252, 7, 1, 6, 4, 5, 6, 3, 8, 0, 9, 249, 9 };
+
+static unsigned char char69[] =
+{ 11, 246, 9,
+  250, 244, 250, 9, 192, 0, 250, 244, 7, 244, 192, 0,
+  250, 254, 2, 254, 192, 0, 250, 9, 7, 9 };
+
+static unsigned char char70[] =
+{ 8, 246, 8,
+  250, 244, 250, 9, 192, 0, 250, 244, 7, 244, 192, 0,
+  250, 254, 2, 254 };
+
+static unsigned char char71[] =
+{ 22, 246, 11,
+  8, 249, 7, 247, 5, 245, 3, 244, 255, 244, 253, 245,
+  251, 247, 250, 249, 249, 252, 249, 1, 250, 4, 251, 6, 253, 8,
+  255, 9, 3, 9, 5, 8, 7, 6, 8, 4, 8, 1, 192, 0, 3, 1, 8, 1 };
+
+static unsigned char char72[] =
+{ 8, 245, 11,
+  249, 244, 249, 9, 192, 0, 7, 244, 7, 9, 192, 0, 249,
+  254, 7, 254 };
+
+static unsigned char char73[] =
+{ 2, 252, 4,
+  0, 244, 0, 9 };
+
+static unsigned char char74[] =
+{ 10, 248, 8,
+  4, 244, 4, 4, 3, 7, 2, 8, 0, 9, 254, 9, 252, 8, 251,
+  7, 250, 4, 250, 2 };
+
+static unsigned char char75[] =
+{ 8, 245, 10,
+  249, 244, 249, 9, 192, 0, 7, 244, 249, 2, 192, 0,
+  254, 253, 7, 9 };
+
+static unsigned char char76[] = 
+{ 3, 246, 7,
+  250, 244, 250, 9, 6, 9 };
+
+static unsigned char char77[] =
+{ 11, 244, 12,
+  248, 244, 248, 9, 192, 0, 248, 244, 0, 9, 192, 0, 8,
+  244, 0, 9, 192, 0, 8, 244, 8, 9 };
+                                               
+static unsigned char char78[] =
+{ 8, 245, 11,
+  249, 244, 249, 9, 192, 0, 249, 244, 7, 9, 192, 0, 7,
+  244, 7, 9 };
+                         
+static unsigned char char79[] =
+{ 21, 245, 11,
+  254, 244, 252, 245, 250, 247, 249, 249, 248, 252,
+  248, 1, 249, 4, 250, 6, 252, 8, 254, 9, 2, 9, 4, 8, 6, 6, 7, 4,
+  8, 1, 8, 252, 7, 249, 6, 247, 4, 245, 2, 244, 254, 244 };
+
+static unsigned char char80[] =
+{ 13, 245, 10,
+  249, 244, 249, 9, 192, 0, 249, 244, 2, 244, 5, 245,
+  6, 246, 7, 248, 7, 251, 6, 253, 5, 254, 2, 255, 249, 255 };
+
+static unsigned char char81[] =
+{ 24, 245, 11,
+  254, 244, 252, 245, 250, 247, 249, 249, 248, 252,
+  248, 1, 249, 4, 250, 6, 252, 8, 254, 9, 2, 9, 4, 8, 6, 6, 7, 4,
+  8, 1, 8, 252, 7, 249, 6, 247, 4, 245, 2, 244, 254, 244, 192, 0,
+  1, 5, 7, 11 };
+                           
+static unsigned char char82[] =
+{ 16, 245, 10,
+  249, 244, 249, 9, 192, 0, 249, 244, 2, 244, 5, 245,
+  6, 246, 7, 248, 7, 250, 6, 252, 5, 253, 2, 254, 249, 254, 192, 0,
+  0, 254, 7, 9 };
+
+static unsigned char char83[] =
+{ 20, 246, 10,
+  7, 247, 5, 245, 2, 244, 254, 244, 251, 245, 249,
+  247, 249, 249, 250, 251, 251, 252, 253, 253, 3, 255, 5, 0, 6, 1,
+  7, 3, 7, 6, 5, 8, 2, 9, 254, 9, 251, 8, 249, 6 };
+
+static unsigned char char84[] =
+{ 5, 248, 8,
+  0, 244, 0, 9, 192, 0, 249, 244, 7, 244 };
+
+static unsigned char char85[] =
+{ 10, 245, 11,
+  249, 244, 249, 3, 250, 6, 252, 8, 255, 9, 1, 9, 4,
+  8, 6, 6, 7, 3, 7, 244 };
+
+static unsigned char char86[] =
+{ 5, 247, 9,
+  248, 244, 0, 9, 192, 0, 8, 244, 0, 9 };
+
+static unsigned char char87[] =
+{ 11, 244, 12,
+  246, 244, 251, 9, 192, 0, 0, 244, 251, 9, 192, 0, 0,
+  244, 5, 9, 192, 0, 10, 244, 5, 9 };
+
+static unsigned char char88[] =
+{ 5, 246, 10,
+  249, 244, 7, 9, 192, 0, 7, 244, 249, 9 };
+
+static unsigned char char89[] =
+{ 6, 247, 9,
+  248, 244, 0, 254, 0, 9, 192, 0, 8, 244, 0, 254 };
+
+static unsigned char char90[] =
+{ 8, 246, 10,
+  7, 244, 249, 9, 192, 0, 249, 244, 7, 244, 192, 0,
+  249, 9, 7, 9 };
+
+static unsigned char char91[] =
+{ 11, 249, 7,
+  253, 240, 253, 16, 192, 0, 254, 240, 254, 16, 192, 0,
+  253, 240, 4, 240, 192, 0, 253, 16, 4, 16 };
+
+static unsigned char char92[] =
+{ 2, 245, 11,
+  9, 16, 247, 240 };
+
+static unsigned char char93[] =
+{ 11, 249, 7,
+  2, 240, 2, 16, 192, 0, 3, 240, 3, 16, 192, 0, 252,
+  240, 3, 240, 192, 0, 252, 16, 3, 16 };
+
+static unsigned char char94[] =
+{ 7, 245, 11,
+  248, 2, 0, 253, 8, 2, 192, 0, 248, 2, 0, 254, 8, 2 };
+
+static unsigned char char95[] =
+{ 2, 253, 22,
+  0, 9, 20, 9 };
+
+static unsigned char char96[] =
+{ 7, 251, 5,
+  1, 244, 0, 245, 255, 247, 255, 249, 0, 250, 1, 249, 0, 248 };
+
+static unsigned char char97[] =
+{ 17, 247, 10,
+  6, 251, 6, 9, 192, 0, 6, 254, 4, 252, 2, 251, 255,
+  251, 253, 252, 251, 254, 250, 1, 250, 3, 251, 6, 253, 8, 255, 9,
+  2, 9, 4, 8, 6, 6 };
+                                
+static unsigned char char98[] =
+{ 17, 246, 9,
+  250, 244, 250, 9, 192, 0, 250, 254, 252, 252, 254,
+  251, 1, 251, 3, 252, 5, 254, 6, 1, 6, 3, 5, 6, 3, 8, 1, 9, 254,
+  9, 252, 8, 250, 6 };
+                                 
+static unsigned char char99[] =
+{ 14, 247, 9,
+  6, 254, 4, 252, 2, 251, 255, 251, 253, 252, 251, 254,
+  250, 1, 250, 3, 251, 6, 253, 8, 255, 9, 2, 9, 4, 8, 6, 6 };
+
+static unsigned char char100[] =
+{ 17, 247, 10,
+  6, 244, 6, 9, 192, 0, 6, 254, 4, 252, 2, 251, 255,
+  251, 253, 252, 251, 254, 250, 1, 250, 3, 251, 6, 253, 8, 255, 9,
+  2, 9, 4, 8, 6, 6 };
+                                 
+static unsigned char char101[] =
+{ 17, 247, 9,
+  250, 1, 6, 1, 6, 255, 5, 253, 4, 252, 2, 251, 255,
+  251, 253, 252, 251, 254, 250, 1, 250, 3, 251, 6, 253, 8, 255, 9,
+  2, 9, 4, 8, 6, 6 };
+
+static unsigned char char102[] =
+{ 8, 251, 7,
+  5, 244, 3, 244, 1, 245, 0, 248, 0, 9, 192, 0, 253,
+  251, 4, 251 };
+
+static unsigned char char103[] =
+{ 22, 247, 10,
+  6, 251, 6, 11, 5, 14, 4, 15, 2, 16, 255, 16, 253,
+  15, 192, 0, 6, 254, 4, 252, 2, 251, 255, 251, 253, 252, 251,
+  254, 250, 1, 250, 3, 251, 6, 253, 8, 255, 9, 2, 9, 4, 8, 6, 6 };
+
+static unsigned char char104[] =
+{ 10, 247, 10,
+  251, 244, 251, 9, 192, 0, 251, 255, 254, 252, 0,
+  251, 3, 251, 5, 252, 6, 255, 6, 9 };
+
+static unsigned char char105[] =
+{ 8, 252, 4,
+  255, 244, 0, 245, 1, 244, 0, 243, 255, 244, 192, 0,
+  0, 251, 0, 9 };
+
+static unsigned char char106[] =
+{ 11, 251, 5,
+  0, 244, 1, 245, 2, 244, 1, 243, 0, 244, 192, 0, 1,
+  251, 1, 12, 0, 15, 254, 16, 252, 16 };
+
+static unsigned char char107[] =
+{ 8, 247, 8,
+  251, 244, 251, 9, 192, 0, 5, 251, 251, 5, 192, 0, 255, 1, 6, 9 };
+
+static unsigned char char108[] =
+{ 2, 252, 4,
+  0, 244, 0, 9 };
+                                        
+static unsigned char char109[] =
+{ 18, 241, 15,
+  245, 251, 245, 9, 192, 0, 245, 255, 248, 252, 250,
+  251, 253, 251, 255, 252, 0, 255, 0, 9, 192, 0, 0, 255, 3, 252,
+  5, 251, 8, 251, 10, 252, 11, 255, 11, 9 };
+
+static unsigned char char110[] =
+{ 10, 247, 10,
+  251, 251, 251, 9, 192, 0, 251, 255, 254, 252, 0,
+  251, 3, 251, 5, 252, 6, 255, 6, 9 };
+
+static unsigned char char111[] =
+{ 17, 247, 10,
+ 255, 251, 253, 252, 251, 254, 250, 1, 250, 3, 251,
+  6, 253, 8, 255, 9, 2, 9, 4, 8, 6, 6, 7, 3, 7, 1, 6, 254, 4, 252,
+  2, 251, 255, 251 };
+
+static unsigned char char112[] =
+{ 17, 246, 9,
+  250, 251, 250, 16, 192, 0, 250, 254, 252, 252, 254,
+  251, 1, 251, 3, 252, 5, 254, 6, 1, 6, 3, 5, 6, 3, 8, 1, 9, 254,
+  9, 252, 8, 250, 6 };
+
+static unsigned char char113[] =
+{ 17, 247, 10,
+  6, 251, 6, 16, 192, 0, 6, 254, 4, 252, 2, 251, 255,
+  251, 253, 252, 251, 254, 250, 1, 250, 3, 251, 6, 253, 8, 255, 9,
+  2, 9, 4, 8, 6, 6 };
+                                 
+static unsigned char char114[] =
+{ 8, 249, 6,
+  253, 251, 253, 9, 192, 0, 253, 1, 254, 254, 0, 252,
+  2, 251, 5, 251 };
+                               
+static unsigned char char115[] =
+{ 17, 248, 9,
+  6, 254, 5, 252, 2, 251, 255, 251, 252, 252, 251,
+  254, 252, 0, 254, 1, 3, 2, 5, 3, 6, 5, 6, 6, 5, 8, 2, 9, 255, 9,
+  252, 8, 251, 6 };
+
+static unsigned char char116[] =
+{ 8, 251, 7,
+  0, 244, 0, 5, 1, 8, 3, 9, 5, 9, 192, 0, 253, 251, 4, 251 };
+
+static unsigned char char117[] =
+{ 10, 247, 10,
+  251, 251, 251, 5, 252, 8, 254, 9, 1, 9, 3, 8, 6, 5,
+  192, 0, 6, 251, 6, 9 };
+
+static unsigned char char118[] =
+{ 5, 248, 8,
+  250, 251, 0, 9, 192, 0, 6, 251, 0, 9 };
+                                                                
+static unsigned char char119[] =
+{ 11, 245, 11,
+  248, 251, 252, 9, 192, 0, 0, 251, 252, 9, 192, 0,
+  0, 251, 4, 9, 192, 0, 8, 251, 4, 9 };
+
+static unsigned char char120[] =
+{ 5, 248, 9,
+  251, 251, 6, 9, 192, 0, 6, 251, 251, 9 };
+
+static unsigned char char121[] =
+{ 9, 248, 8,
+  250, 251, 0, 9, 192, 0, 6, 251, 0, 9, 254, 13, 252,
+  15, 250, 16, 249, 16 };
+                                     
+static unsigned char char122[] =
+{ 8, 248, 9,
+  6, 251, 251, 9, 192, 0, 251, 251, 6, 251, 192, 0,
+  251, 9, 6, 9 };
+
+static unsigned char char123[] =
+{ 39, 249, 7,
+  2, 240, 0, 241, 255, 242, 254, 244, 254, 246, 255,
+  248, 0, 249, 1, 251, 1, 253, 255, 255, 192, 0, 0, 241, 255, 243,
+  255, 245, 0, 247, 1, 248, 2, 250, 2, 252, 1, 254, 253, 0, 1, 2,
+  2, 4, 2, 6, 1, 8, 0, 9, 255, 11, 255, 13, 0, 15, 192, 0, 255, 1,
+  1, 3, 1, 5, 0, 7, 255, 8, 254, 10, 254, 12, 255, 14, 0, 15, 2, 16 };
+
+static unsigned char char124[] =
+{ 2, 252, 4,
+  0, 240, 0, 16 };
+
+static unsigned char char125[] =
+{ 39, 249, 7,
+  254, 240, 0, 241, 1, 242, 2, 244, 2, 246, 1, 248, 0,
+  249, 255, 251, 255, 253, 1, 255, 192, 0, 0, 241, 1, 243, 1, 245,
+  0, 247, 255, 248, 254, 250, 254, 252, 255, 254, 3, 0, 255, 2,
+  254, 4, 254, 6, 255, 8, 0, 9, 1, 11, 1, 13, 0, 15, 192, 0, 1, 1,
+  255, 3, 255, 5, 0, 7, 1, 8, 2, 10, 2, 12, 1, 14, 0, 15, 254, 16 };
+
+static unsigned char char126[] =
+{ 23, 255, 21,
+  2, 1, 0, 255, 1, 253, 3, 251, 5, 251, 7, 252,
+  11, 255, 13, 0, 15, 0, 17, 255, 18, 254, 192, 0, 2, 0, 1,
+  254, 3, 253, 5, 253, 7, 254, 11, 1, 13, 2, 15, 2, 17, 1, 18,
+  255, 18, 252 };
+
+/* Pointers to character definition tables. */
+
+static unsigned char * fontData[] = {
+    char32, char33, char34, char35, char36, char37, char38, char39, char40,
+    char41, char42, char43, char44, char45, char46, char47, char48, char49,
+    char50, char51, char52, char53, char54, char55, char56, char57, char58,
+    char59, char60, char61, char62, char63, char64, char65, char66, char67,
+    char68, char69, char70, char71, char72, char73, char74, char75, char76,
+    char77, char78, char79, char80, char81, char82, char83, char84, char85,
+    char86, char87, char88, char89, char90, char91, char92, char93, char94,
+    char95, char96, char97, char98, char99, char100, char101, char102,
+    char103, char104, char105, char106, char107, char108, char109, char110,
+    char111, char112, char113, char114, char115, char116, char117, char118,
+    char119, char120, char121, char122, char123, char124, char125, char126
+};
+
+
+
+static void
+writeGlyphCommand(FILE *                   const ofP,
+                  struct ppmd_glyphCommand const glyphCommand) {
+
+    fputc(glyphCommand.verb, ofP);
+    fputc(glyphCommand.x, ofP);
+    fputc(glyphCommand.y, ofP);
+}    
+
+
+
+static void
+writeMovePen(FILE *                const ofP,
+             const unsigned char * const glyphData) {
+
+    struct ppmd_glyphCommand glyphCommand;
+            
+    glyphCommand.verb = CMD_MOVEPEN;
+    glyphCommand.x = glyphData[0];
+    glyphCommand.y = glyphData[1];
+    
+    writeGlyphCommand(ofP, glyphCommand);
+}
+
+
+
+static void
+writeMovePenNoop(FILE *                const ofP,
+                 const unsigned char * const glyphData) {
+
+    struct ppmd_glyphCommand glyphCommand;
+            
+    glyphCommand.verb = CMD_MOVEPEN;
+    glyphCommand.x = glyphData[0];
+    glyphCommand.y = glyphData[1];
+    
+    writeGlyphCommand(ofP, glyphCommand);
+                
+    glyphCommand.verb = CMD_NOOP;
+    glyphCommand.x = 0;
+    glyphCommand.y = 0;
+
+    writeGlyphCommand(ofP, glyphCommand);
+}
+
+
+
+static void
+writeDrawLine(FILE *                const ofP,
+              const unsigned char * const glyphData) {
+
+    struct ppmd_glyphCommand glyphCommand;
+
+    glyphCommand.verb = CMD_DRAWLINE;
+    glyphCommand.x = glyphData[0];
+    glyphCommand.y = glyphData[1];
+    
+    writeGlyphCommand(ofP, glyphCommand);
+}
+            
+
+
+static void
+writeGlyphHeader(FILE *                  const ofP,
+                 struct ppmd_glyphHeader const glyphHeader) {
+
+    fputc(glyphHeader.commandCount, ofP);
+    fputc(glyphHeader.skipBefore, ofP);
+    fputc(glyphHeader.skipAfter, ofP);
+}    
+
+
+
+static void
+writeBuiltinCharacter(FILE *       const ofP,
+                      unsigned int const relativeCodePoint) {
+
+    const unsigned char * const glyphData = fontData[relativeCodePoint];
+
+    struct ppmd_glyphHeader glyphHeader;
+    unsigned int commandNum;
+
+    glyphHeader.commandCount = glyphData[0];
+    glyphHeader.skipBefore   = glyphData[1];
+    glyphHeader.skipAfter    = glyphData[2];
+
+    writeGlyphHeader(ofP, glyphHeader);
+
+    commandNum = 0;
+
+    while (commandNum < glyphHeader.commandCount) {
+            
+        if (commandNum == 0) {
+            writeMovePen(ofP, &glyphData[3 + commandNum * 2]);
+            commandNum += 1;
+        } else if (glyphData[3 + commandNum*2] == 192) {
+
+            assert(commandNum + 1 < glyphHeader.commandCount);
+
+            writeMovePenNoop(ofP, &glyphData[3 + (commandNum + 1) * 2]);
+
+            commandNum += 2;
+        } else {
+            writeDrawLine(ofP, &glyphData[3 + commandNum * 2]);
+            commandNum += 1;
+        }
+    }
+}
+
+
+
+static void
+writeFontHeader(FILE *                 const ofP,
+                struct ppmd_fontHeader const fontHeader) {
+
+    fwrite(fontHeader.signature, 1, sizeof(fontHeader.signature), ofP);
+    fputc(fontHeader.format, ofP);
+    fputc(fontHeader.characterCount, ofP);
+    fputc(fontHeader.firstCodePoint, ofP);
+}
+
+
+
+static void
+writeBuiltinFont(FILE * const ofP) {
+
+    unsigned int relativeCodePoint;
+
+    struct ppmd_fontHeader fontHeader;
+
+    memcpy(fontHeader.signature, "ppmdfont", sizeof(fontHeader.signature));
+    fontHeader.format         = 0x01;
+    fontHeader.characterCount = 95;
+    fontHeader.firstCodePoint = 32;
+
+    writeFontHeader(ofP, fontHeader);
+
+    for (relativeCodePoint = 0;
+         relativeCodePoint < fontHeader.characterCount;
+         ++relativeCodePoint) {
+
+        writeBuiltinCharacter(ofP,relativeCodePoint);
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    ppm_init(&argc, argv);
+
+    writeBuiltinFont(stdout);
+    
+    return 0;
+}
diff --git a/other/ppmsvgalib.c b/other/ppmsvgalib.c
new file mode 100644
index 00000000..67cc2b1a
--- /dev/null
+++ b/other/ppmsvgalib.c
@@ -0,0 +1,283 @@
+/******************************************************************************
+                               ppmsvgalib
+*******************************************************************************
+   Display a PPM image on a Linux console using Svgalib.
+
+   By Bryan Henderson, San Jose CA 2002.01.06.
+
+   Contributed to the public domain.
+   
+******************************************************************************/
+
+#define _XOPEN_SOURCE    /* Make sure modern signal stuff is in signal.h */
+#include <stdio.h>
+#include <vga.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <unistd.h>
+#include "ppm.h"
+#include "shhopt.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+    unsigned int mode;
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine (int argc, char ** argv,
+                  struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc( 100*sizeof( optEntry ) );
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int modeSpec;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "mode",         OPT_UINT,
+            &cmdlineP->mode,   &modeSpec, 0);
+    OPTENT3(0,   "verbose",      OPT_FLAG,
+            NULL,   &cmdlineP->verbose,   0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (!modeSpec)
+        pm_error("You must specify the -mode option.");
+
+    if (argc-1 > 1)
+        pm_error("Program takes at most one argument: the input file "
+                 "specification.  "
+                 "You specified %d arguments.", argc-1);
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+static void
+displayImage(FILE * const ifP, 
+             int    const cols, 
+             int    const rows,
+             pixval const maxval, 
+             int    const format,
+             int    const originCol,
+             int    const originRow) {
+/*----------------------------------------------------------------------------
+   Draw the PPM image which is in file 'ifP', which is positioned after the
+   PPM header on the screen with its upper left corner (originCol, originRow).
+
+   The image is 'cols' x 'rows' with maxval 'maxval' and PNM format 'format'.
+
+   Svgalib is initialized and the mode selected.
+
+   The image fits on the screen.
+-----------------------------------------------------------------------------*/
+    unsigned int svgalibMaxval = 255;
+        /* This is the maxval for intensity values passed to Svgalib */
+    unsigned int row;
+    pixel * pixelrow;
+
+    pixelrow = ppm_allocrow(cols);
+
+    /* Implementation note:  It might be faster to use 
+       vga_drawscansegment() instead of vga_drawpixel()
+    */
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        ppm_readppmrow(ifP, pixelrow, cols, maxval, format);
+        for (col = 0; col < cols; ++col) {
+            pixel const p = pixelrow[col];
+            int const red = PPM_GETR(p) * svgalibMaxval / maxval;
+            int const grn = PPM_GETG(p) * svgalibMaxval / maxval;
+            int const blu = PPM_GETB(p) * svgalibMaxval / maxval;
+
+            vga_setrgbcolor(red, grn, blu);
+            vga_drawpixel(originCol + col, originRow + row);
+        }
+    }
+    ppm_freerow(pixelrow);
+}
+
+
+
+static void
+sigintHandler(int const signal) {
+/*----------------------------------------------------------------------------
+   This is a signal handler for the SIGINT signal (Control-C).
+
+   It does nothing; The handler exists only to replace the default action,
+   which is to terminate the process.  Though the handler does nothing,
+   the signal still causes the wait() system call, assuming it's in progress,
+   to terminate so that this program can terminate.
+-----------------------------------------------------------------------------*/
+}
+
+
+
+static void 
+waitforSigint(void) {
+
+    struct sigaction oldsigaction;
+    struct sigaction newsigaction;
+    int rc;
+    
+    newsigaction.sa_handler = &sigintHandler;
+    sigemptyset(&newsigaction.sa_mask);
+    newsigaction.sa_flags = 0;
+    rc = sigaction(SIGINT, &newsigaction, &oldsigaction);
+    if (rc != 0)
+        pm_error("Unable to set up SIGINTR signal handler.  Errno=%d (%s)",
+                 errno, strerror(errno));
+
+    pause();  /* Wait for a signal, e.g. control-C */
+
+    sigaction(SIGINT, &oldsigaction, NULL);
+}
+
+
+
+static void
+display(FILE * const ifP, 
+        int    const cols, 
+        int    const rows, 
+        pixval const maxval, 
+        int    const format, 
+        int    const videoMode, 
+        bool   const verbose) {
+
+    int xmax, ymax;
+    vga_modeinfo *modeinfo;
+
+    modeinfo = vga_getmodeinfo(videoMode);
+    
+    if (verbose) {
+        pm_message("Screen Width: %d  Height: %d  Colors: %d",
+                   modeinfo->width,
+                   modeinfo->height,
+                   modeinfo->colors);
+        pm_message("DisplayStartRange: %xh  Maxpixels: %d  Blit: %s",
+                   modeinfo->startaddressrange,
+                   modeinfo->maxpixels,
+                   modeinfo->haveblit ? "YES" : "NO");
+    }
+
+    if (modeinfo->colors <= 256)
+        pm_error("This video mode has %d or fewer colors, which means "
+                 "it is colormapped (aka paletted, aka pseudocolor).  "
+                 "This program cannot drive colormapped modes.", 
+                 modeinfo->colors);
+
+    if (cols > modeinfo->width)
+        pm_error("Image is too wide (%d columns) for screen (%d columns).  "
+                 "Use Pamcut to select part to display.", 
+                 cols, modeinfo->width);
+    if (rows > modeinfo->height)
+        pm_error("Image is too tall (%d rows) for screen (%d rows).  "
+                 "Use Pamcut to select part to display.",
+                 rows, modeinfo->height);
+    
+    /* The program must not terminate after we set the video mode and before
+       we reset it to text mode.  Note that vga_setmode() sets up handlers
+       for signals such as SIGINT that attempt to restore modes and then exit
+       the program.
+    */
+
+    vga_setmode(videoMode);
+
+    vga_screenoff();
+
+    xmax = vga_getxdim() - 1;
+    ymax = vga_getydim() - 1;
+
+    /* Draw white border */
+
+    vga_setcolor(vga_white());
+    vga_drawline(0, 0, xmax, 0);
+    vga_drawline(xmax, 0, xmax, ymax);
+    vga_drawline(xmax, ymax, 0, ymax);
+    vga_drawline(0, ymax, 0, 0);
+
+    vga_screenon();
+
+    {
+        int const originCol = (modeinfo->width - cols) / 2;
+        int const originRow = (modeinfo->height - rows) / 2;
+        displayImage(ifP, cols, rows, maxval, format, originCol, originRow);
+    }
+
+    waitforSigint();
+
+    vga_setmode(TEXT);
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+
+    FILE * ifP;
+    struct cmdlineInfo cmdline;
+    int cols, rows;
+    pixval maxval;
+    int format;
+    int rc;
+
+    ppm_init( &argc, argv );
+
+    rc = vga_init();         /* Initialize. */
+    if (rc < 0)
+        pm_error("Svgalib unable to allocate a virtual console.");
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
+
+    {
+        enum pm_check_code checkResult;
+        ppm_check(ifP, PM_CHECK_BASIC, format, cols, rows, maxval, 
+                  &checkResult);
+    }
+
+    if (vga_hasmode(cmdline.mode))
+        display(ifP, cols, rows, maxval, format, 
+                cmdline.mode, cmdline.verbose);
+    else {
+        pm_error("Svgalib video mode #%d not available.  Either the "
+                 "video controller isn't capable of that mode or the "
+                 "Svgalib video driver doesn't know how to use it.",
+                 cmdline.mode);
+    }
+
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/other/ppmtomap b/other/ppmtomap
new file mode 100755
index 00000000..1d7ed940
--- /dev/null
+++ b/other/ppmtomap
@@ -0,0 +1,5 @@
+#! /bin/sh
+
+# This program exists for backward compatibility.
+
+pnmcolormap all $@
diff --git a/pm_config.h b/pm_config.h
new file mode 100644
index 00000000..bbe7a495
--- /dev/null
+++ b/pm_config.h
@@ -0,0 +1,302 @@
+/* pm_config.h GENERATED BY A MAKE RULE */
+#ifndef PM_CONFIG_H
+#define PM_CONFIG_H
+#include <inttypes.h>
+#define HAVE_INT64 1
+/* pm_config.h.in FOLLOWS ... */
+/**************************************************************************
+                               NETPBM
+                           pm_config.in.h
+***************************************************************************
+  This file provides platform-dependent definitions for all Netpbm
+  libraries and the programs that use them.
+
+  The make files generate pm_config.h by copying this file and adding
+  other stuff.  The Netpbm programs #include pm_config.h.
+
+  Wherever possible, Netpbm handles customization via the make files
+  instead of via this file.  However, Netpbm's make file philosophy
+  discourages lining up a bunch of -D options on every compile, so a 
+  #define here would be preferable to a -D compile option.
+
+**************************************************************************/
+
+#if defined(USG) || defined(SVR4) || defined(VMS) || defined(__SVR4)
+#define SYSV
+#endif
+#if !( defined(BSD) || defined(SYSV) || defined(MSDOS) || defined(__amigaos__))
+/* CONFIGURE: If your system is >= 4.2BSD, set the BSD option; if you're a
+** System V site, set the SYSV option; if you're IBM-compatible, set MSDOS;
+** and if you run on an Amiga, set AMIGA. If your compiler is ANSI C, you're
+** probably better off setting SYSV - all it affects is string handling.
+*/
+#define BSD
+/* #define SYSV */
+/* #define MSDOS */
+#endif
+
+/* Switch macros like _POSIX_SOURCE are supposed to add features from
+   the indicated standard to the C library.  A source file defines one
+   of these macros to declare that it uses features of that standard
+   as opposed to conflicting features of other standards (e.g. the
+   POSIX foo() subroutine might do something different from the X/Open
+   foo() subroutine).  Plus, this forces the coder to understand upon
+   what feature sets his program relies.
+
+   But some C library developers have misunderstood this and think of these
+   macros like the old __ansi__ macro, which tells the C library, "Don't 
+   have any features that aren't in the ANSI standard."  I.e. it's just
+   the opposite -- the macro subtracts features instead of adding them.
+
+   This means that on some platforms, Netpbm programs must define
+   _POSIX_SOURCE, and on others, it must not.  Netpbm's POSIX_IS_IMPLIED 
+   macro indicates that we're on a platform where we need not define
+   _POSIX_SOURCE (and probably must not).
+
+   The problematic C libraries treat _XOPEN_SOURCE the same way.
+*/
+#if defined(__OpenBSD__) || defined (__NetBSD__) || defined(__bsdi__) || defined(__APPLE__)
+#define POSIX_IS_IMPLIED
+#endif
+
+
+/* CONFIGURE: If you have an X11-style rgb color names file, define its
+** path here.  This is used by PPM to parse color names into rgb values.
+** If you don't have such a file, comment this out and use the alternative
+** hex and decimal forms to specify colors (see ppm/pgmtoppm.1 for details).  */
+/* There was some evidence before Netpbm 9.1 that the rgb database macros
+   might be already set right now.  I couldn't figure out how, so I changed
+   their meanings and they are now set unconditionally.  -Bryan 00.05.03.
+*/
+#ifdef VMS
+#define RGB_DB1 "PBMplus_Dir:RGB.TXT"
+#define RGB_DB2 "PBMplus_Dir:RGB.TXT"
+#define RGB_DB3 "PBMplus_Dir:RGB.TXT"
+#else
+#define RGB_DB1 "/usr/lib/X11/rgb.txt"
+#define RGB_DB2 "/usr/share/X11/rgb.txt"
+#define RGB_DB3 "/usr/X11R6/lib/X11/rgb.txt"
+#endif
+
+/* CONFIGURE: This is the name of an environment variable that tells
+** where the color names database is.  If the environment variable isn't
+** set, Netpbm tries the hardcoded defaults set above.
+*/
+#define RGBENV "RGBDEF"    /* name of env-var */
+
+#if (defined(SYSV) || defined(__amigaos__))
+
+#include <string.h>
+/* Before Netpbm 9.1, rand and srand were macros for random and
+   srandom here.  This caused a failure on a SunOS 5.6 system, which
+   is SYSV, but has both rand and random declared (with different
+   return types).  The macro caused the prototype for random to be a
+   second prototype for rand.  Before 9.1, Netpbm programs called
+   random() and on a SVID system, that was really a call to rand().
+   We assume all modern systems have rand() itself, so now Netpbm
+   always calls rand() and if we find a platform that doesn't have
+   rand(), we will add something here for that platform.  -Bryan 00.04.26
+#define random rand
+#define srandom(s) srand(s)
+extern void srand();
+extern int rand();
+*/
+/* Before Netpbm 9.15, there were macro definitions of index() and 
+   rindex() here, but there are no longer any invocations of those 
+   functions in Netpbm, except in the VMS-only code, so there's no
+   reason for them.
+*/
+
+#ifndef __SASC
+#ifndef _DCC    /* Amiga DICE Compiler */
+#define bzero(dst,len) memset(dst,0,len)
+#define bcopy(src,dst,len) memcpy(dst,src,len)
+#define bcmp memcmp
+#endif /* _DCC */
+#endif /* __SASC */
+
+#endif /*SYSV or Amiga*/
+
+/* We should change all of Netpbm to use uint32_t instead of uint32n,
+   because we now have a strategy for ensuring that uint32_t is defined.
+   But we're going to wait a while in case our uint32_t strategy doesn't
+   work.  04.08.24.
+*/
+typedef uint32_t uint32n;
+typedef int32_t int32n;
+
+#include <fcntl.h>
+#include <time.h>
+#include <stdlib.h>
+#include <unistd.h>
+/* 
+   Before Netpbm 9.0, atoi() and exit() were declared for everybody
+   except MSDOS and Amiga, and time() and write() were declared for
+   everybody except MSDOS, Amiga, and __osf__.  fcntl.h, time.h, and
+   stlib.h were included for MSDOS and Amiga, and unistd.h was included
+   for everyone except VMS, MSDOS, and Amiga.  With the netbsd patches,
+   atoi(), exit(), time(), and write() were not declared for __NetBSD__.
+
+   We're hoping that all current systems have the standard header
+   files, and will reinstate some of these explicit declarations if we
+   hear otherwise.  
+
+   If it turns out to be this easy, we should just move these inclusions
+   to the source files that actually need them.
+   
+   -Bryan 2000.04.13
+
+extern int atoi();
+extern void exit();
+extern long time();
+extern int write(); 
+*/
+
+/* CONFIGURE: On most BSD systems, malloc() gets declared in stdlib.h, on
+** system V, it gets declared in malloc.h. On some systems, malloc.h
+** doesn't declare these, so we have to do it here. On other systems,
+** for example HP/UX, it declares them incompatibly.  And some systems,
+** for example Dynix, don't have a malloc.h at all.  A sad situation.
+** If you have compilation problems that point here, feel free to tweak
+** or remove these declarations.
+*/
+#ifdef BSD
+#include <stdlib.h>
+#endif
+#if (defined(SYSV) && !defined(VMS))
+#include <malloc.h>
+#endif
+/* extern char* malloc(); */
+/* extern char* realloc(); */
+/* extern char* calloc(); */
+
+/* CONFIGURE: Some systems don't have vfprintf(), which we need for the
+** error-reporting routines.  If you compile and get a link error about
+** this routine, uncomment the first define, which gives you a vfprintf
+** that uses the theoretically non-portable but fairly common routine
+** _doprnt().  If you then get a link error about _doprnt, or
+** message-printing doesn't look like it's working, try the second
+** define instead.
+*/
+/* #define NEED_VFPRINTF1 */
+/* #define NEED_VFPRINTF2 */
+
+/* CONFIGURE: Some systems don't have strstr(), which some routines need.
+** If you compile and get a link error about this routine, uncomment the
+** define, which gives you a strstr.
+*/
+/* #define NEED_STRSTR */
+
+/* CONFIGURE: Set this option if your compiler uses strerror(errno)
+** instead of sys_errlist[errno] for error messages.
+*/
+#define A_STRERROR
+
+/* CONFIGURE: If your system has the setmode() function, set HAVE_SETMODE.
+** If you do, and also the O_BINARY file mode, pm_init() will set the mode
+** of stdin and stdout to binary for all Netpbm programs.
+** You need this with Cygwin (Windows).
+*/
+#ifdef __CYGWIN__
+#define HAVE_SETMODE
+#endif
+
+/* #define HAVE_SETMODE */
+
+#ifdef __amigaos__
+#include <clib/exec_protos.h>
+#define getpid() ((pid_t)FindTask(NULL))
+#endif
+
+#ifdef DJGPP
+#define HAVE_SETMODE
+#define lstat stat
+#endif /* DJGPP */
+
+/*  CONFIGURE: Netpbm uses __inline__ to declare functions that should
+    be compiled as inline code.  GNU C recognizes the __inline__ keyword.
+    If your compiler recognizes any other keyword for this, you can set
+    it here.
+*/
+#if !defined(__GNUC__)
+  #if (!defined(__inline__))
+    #if (defined(__sgi) || defined(_AIX))
+      #define __inline__ __inline
+    #else   
+      #define __inline__
+    #endif
+  #endif
+#endif
+
+/* CONFIGURE: Some systems seem to need more than standard program linkage
+   to get a data (as opposed to function) item out of a library.
+
+   On Windows mingw systems, it seems you have to #include <import_mingw.h>
+   and #define EXTERNDATA DLL_IMPORT  .  2001.05.19
+*/
+#define EXTERNDATA extern
+
+/* only Pnmstitch uses UNREFERENCED_PARAMETER today (and I'm not sure why),
+   but it might come in handy some day.
+*/
+#if (!defined(UNREFERENCED_PARAMETER))
+# if (defined(__GNUC__))
+#  define UNREFERENCED_PARAMETER(x)
+# elif (defined(__USLC__) || defined(_M_XENIX))
+#  define UNREFERENCED_PARAMETER(x) ((x)=(x))
+# else
+#  define UNREFERENCED_PARAMETER(x) (x)
+# endif
+#endif
+
+/* In GNU, _LFS_LARGEFILE means the "off_t" functions (ftello, etc.) are
+   available.  In AIX, _AIXVERSION_430 means it's AIX Version 4.3.0 or
+   better, which seems to mean the "off_t" functions are available.
+*/
+#if defined(_LFS_LARGEFILE) || defined(_AIXVERSION_430)
+typedef off_t pm_filepos;
+#define FTELLO ftello
+#define FSEEKO fseeko
+#else
+typedef long int pm_filepos;
+#define FTELLO ftell
+#define FSEEKO fseek
+#endif
+
+#if defined(_PLAN9)
+#define TMPDIR "/tmp"
+#else
+/* Use POSIX value P_tmpdir from libc */
+#define TMPDIR P_tmpdir
+#endif
+
+/* Note that if you _don't_ have mkstemp(), you'd better have a safe
+   mktemp() or otherwise not be concerned about its unsafety.  On some
+   systems, use of mktemp() makes it possible for a hacker to cause a
+   Netpbm program to access a file of the hacker's choosing when the
+   Netpbm program means to access its own temporary file.
+*/
+#ifdef __MINGW32__
+  #define HAVE_MKSTEMP 0
+#else
+  #define HAVE_MKSTEMP 1
+#endif
+
+/* This was generated by the program 'endiangen' */
+
+/* LITTLE_ENDIAN, BIG_ENDIAN, and BYTE_ORDER may come from the C library
+via ctype.h. */
+#include <ctype.h>
+#ifndef LITTLE_ENDIAN
+#define LITTLE_ENDIAN 1234
+#endif
+#ifndef BIG_ENDIAN
+#define BIG_ENDIAN 4321
+#endif
+
+#ifndef BYTE_ORDER
+#define BYTE_ORDER LITTLE_ENDIAN
+#endif
+
+#define BITS_PER_WORD 32
+#endif
diff --git a/pm_config.in.h b/pm_config.in.h
new file mode 100644
index 00000000..9fc55351
--- /dev/null
+++ b/pm_config.in.h
@@ -0,0 +1,278 @@
+/**************************************************************************
+                               NETPBM
+                           pm_config.in.h
+***************************************************************************
+  This file provides platform-dependent definitions for all Netpbm
+  libraries and the programs that use them.
+
+  The make files generate pm_config.h by copying this file and adding
+  other stuff.  The Netpbm programs #include pm_config.h.
+
+  Wherever possible, Netpbm handles customization via the make files
+  instead of via this file.  However, Netpbm's make file philosophy
+  discourages lining up a bunch of -D options on every compile, so a 
+  #define here would be preferable to a -D compile option.
+
+**************************************************************************/
+
+#if defined(USG) || defined(SVR4) || defined(VMS) || defined(__SVR4)
+#define SYSV
+#endif
+#if !( defined(BSD) || defined(SYSV) || defined(MSDOS) || defined(__amigaos__))
+/* CONFIGURE: If your system is >= 4.2BSD, set the BSD option; if you're a
+** System V site, set the SYSV option; if you're IBM-compatible, set MSDOS;
+** and if you run on an Amiga, set AMIGA. If your compiler is ANSI C, you're
+** probably better off setting SYSV - all it affects is string handling.
+*/
+#define BSD
+/* #define SYSV */
+/* #define MSDOS */
+#endif
+
+/* Switch macros like _POSIX_SOURCE are supposed to add features from
+   the indicated standard to the C library.  A source file defines one
+   of these macros to declare that it uses features of that standard
+   as opposed to conflicting features of other standards (e.g. the
+   POSIX foo() subroutine might do something different from the X/Open
+   foo() subroutine).  Plus, this forces the coder to understand upon
+   what feature sets his program relies.
+
+   But some C library developers have misunderstood this and think of these
+   macros like the old __ansi__ macro, which tells the C library, "Don't 
+   have any features that aren't in the ANSI standard."  I.e. it's just
+   the opposite -- the macro subtracts features instead of adding them.
+
+   This means that on some platforms, Netpbm programs must define
+   _POSIX_SOURCE, and on others, it must not.  Netpbm's POSIX_IS_IMPLIED 
+   macro indicates that we're on a platform where we need not define
+   _POSIX_SOURCE (and probably must not).
+
+   The problematic C libraries treat _XOPEN_SOURCE the same way.
+*/
+#if defined(__OpenBSD__) || defined (__NetBSD__) || defined(__bsdi__) || defined(__APPLE__)
+#define POSIX_IS_IMPLIED
+#endif
+
+
+/* CONFIGURE: If you have an X11-style rgb color names file, define its
+** path here.  This is used by PPM to parse color names into rgb values.
+** If you don't have such a file, comment this out and use the alternative
+** hex and decimal forms to specify colors (see ppm/pgmtoppm.1 for details).  */
+/* There was some evidence before Netpbm 9.1 that the rgb database macros
+   might be already set right now.  I couldn't figure out how, so I changed
+   their meanings and they are now set unconditionally.  -Bryan 00.05.03.
+*/
+#ifdef VMS
+#define RGB_DB1 "PBMplus_Dir:RGB.TXT"
+#define RGB_DB2 "PBMplus_Dir:RGB.TXT"
+#define RGB_DB3 "PBMplus_Dir:RGB.TXT"
+#else
+#define RGB_DB1 "/usr/lib/X11/rgb.txt"
+#define RGB_DB2 "/usr/share/X11/rgb.txt"
+#define RGB_DB3 "/usr/X11R6/lib/X11/rgb.txt"
+#endif
+
+/* CONFIGURE: This is the name of an environment variable that tells
+** where the color names database is.  If the environment variable isn't
+** set, Netpbm tries the hardcoded defaults set above.
+*/
+#define RGBENV "RGBDEF"    /* name of env-var */
+
+#if (defined(SYSV) || defined(__amigaos__))
+
+#include <string.h>
+/* Before Netpbm 9.1, rand and srand were macros for random and
+   srandom here.  This caused a failure on a SunOS 5.6 system, which
+   is SYSV, but has both rand and random declared (with different
+   return types).  The macro caused the prototype for random to be a
+   second prototype for rand.  Before 9.1, Netpbm programs called
+   random() and on a SVID system, that was really a call to rand().
+   We assume all modern systems have rand() itself, so now Netpbm
+   always calls rand() and if we find a platform that doesn't have
+   rand(), we will add something here for that platform.  -Bryan 00.04.26
+#define random rand
+#define srandom(s) srand(s)
+extern void srand();
+extern int rand();
+*/
+/* Before Netpbm 9.15, there were macro definitions of index() and 
+   rindex() here, but there are no longer any invocations of those 
+   functions in Netpbm, except in the VMS-only code, so there's no
+   reason for them.
+*/
+
+#ifndef __SASC
+#ifndef _DCC    /* Amiga DICE Compiler */
+#define bzero(dst,len) memset(dst,0,len)
+#define bcopy(src,dst,len) memcpy(dst,src,len)
+#define bcmp memcmp
+#endif /* _DCC */
+#endif /* __SASC */
+
+#endif /*SYSV or Amiga*/
+
+/* We should change all of Netpbm to use uint32_t instead of uint32n,
+   because we now have a strategy for ensuring that uint32_t is defined.
+   But we're going to wait a while in case our uint32_t strategy doesn't
+   work.  04.08.24.
+*/
+typedef uint32_t uint32n;
+typedef int32_t int32n;
+
+#include <fcntl.h>
+#include <time.h>
+#include <stdlib.h>
+#include <unistd.h>
+/* 
+   Before Netpbm 9.0, atoi() and exit() were declared for everybody
+   except MSDOS and Amiga, and time() and write() were declared for
+   everybody except MSDOS, Amiga, and __osf__.  fcntl.h, time.h, and
+   stlib.h were included for MSDOS and Amiga, and unistd.h was included
+   for everyone except VMS, MSDOS, and Amiga.  With the netbsd patches,
+   atoi(), exit(), time(), and write() were not declared for __NetBSD__.
+
+   We're hoping that all current systems have the standard header
+   files, and will reinstate some of these explicit declarations if we
+   hear otherwise.  
+
+   If it turns out to be this easy, we should just move these inclusions
+   to the source files that actually need them.
+   
+   -Bryan 2000.04.13
+
+extern int atoi();
+extern void exit();
+extern long time();
+extern int write(); 
+*/
+
+/* CONFIGURE: On most BSD systems, malloc() gets declared in stdlib.h, on
+** system V, it gets declared in malloc.h. On some systems, malloc.h
+** doesn't declare these, so we have to do it here. On other systems,
+** for example HP/UX, it declares them incompatibly.  And some systems,
+** for example Dynix, don't have a malloc.h at all.  A sad situation.
+** If you have compilation problems that point here, feel free to tweak
+** or remove these declarations.
+*/
+#ifdef BSD
+#include <stdlib.h>
+#endif
+#if (defined(SYSV) && !defined(VMS))
+#include <malloc.h>
+#endif
+/* extern char* malloc(); */
+/* extern char* realloc(); */
+/* extern char* calloc(); */
+
+/* CONFIGURE: Some systems don't have vfprintf(), which we need for the
+** error-reporting routines.  If you compile and get a link error about
+** this routine, uncomment the first define, which gives you a vfprintf
+** that uses the theoretically non-portable but fairly common routine
+** _doprnt().  If you then get a link error about _doprnt, or
+** message-printing doesn't look like it's working, try the second
+** define instead.
+*/
+/* #define NEED_VFPRINTF1 */
+/* #define NEED_VFPRINTF2 */
+
+/* CONFIGURE: Some systems don't have strstr(), which some routines need.
+** If you compile and get a link error about this routine, uncomment the
+** define, which gives you a strstr.
+*/
+/* #define NEED_STRSTR */
+
+/* CONFIGURE: Set this option if your compiler uses strerror(errno)
+** instead of sys_errlist[errno] for error messages.
+*/
+#define A_STRERROR
+
+/* CONFIGURE: If your system has the setmode() function, set HAVE_SETMODE.
+** If you do, and also the O_BINARY file mode, pm_init() will set the mode
+** of stdin and stdout to binary for all Netpbm programs.
+** You need this with Cygwin (Windows).
+*/
+#ifdef __CYGWIN__
+#define HAVE_SETMODE
+#endif
+
+/* #define HAVE_SETMODE */
+
+#ifdef __amigaos__
+#include <clib/exec_protos.h>
+#define getpid() ((pid_t)FindTask(NULL))
+#endif
+
+#ifdef DJGPP
+#define HAVE_SETMODE
+#define lstat stat
+#endif /* DJGPP */
+
+/*  CONFIGURE: Netpbm uses __inline__ to declare functions that should
+    be compiled as inline code.  GNU C recognizes the __inline__ keyword.
+    If your compiler recognizes any other keyword for this, you can set
+    it here.
+*/
+#if !defined(__GNUC__)
+  #if (!defined(__inline__))
+    #if (defined(__sgi) || defined(_AIX))
+      #define __inline__ __inline
+    #else   
+      #define __inline__
+    #endif
+  #endif
+#endif
+
+/* CONFIGURE: Some systems seem to need more than standard program linkage
+   to get a data (as opposed to function) item out of a library.
+
+   On Windows mingw systems, it seems you have to #include <import_mingw.h>
+   and #define EXTERNDATA DLL_IMPORT  .  2001.05.19
+*/
+#define EXTERNDATA extern
+
+/* only Pnmstitch uses UNREFERENCED_PARAMETER today (and I'm not sure why),
+   but it might come in handy some day.
+*/
+#if (!defined(UNREFERENCED_PARAMETER))
+# if (defined(__GNUC__))
+#  define UNREFERENCED_PARAMETER(x)
+# elif (defined(__USLC__) || defined(_M_XENIX))
+#  define UNREFERENCED_PARAMETER(x) ((x)=(x))
+# else
+#  define UNREFERENCED_PARAMETER(x) (x)
+# endif
+#endif
+
+/* In GNU, _LFS_LARGEFILE means the "off_t" functions (ftello, etc.) are
+   available.  In AIX, _AIXVERSION_430 means it's AIX Version 4.3.0 or
+   better, which seems to mean the "off_t" functions are available.
+*/
+#if defined(_LFS_LARGEFILE) || defined(_AIXVERSION_430)
+typedef off_t pm_filepos;
+#define FTELLO ftello
+#define FSEEKO fseeko
+#else
+typedef long int pm_filepos;
+#define FTELLO ftell
+#define FSEEKO fseek
+#endif
+
+#if defined(_PLAN9)
+#define TMPDIR "/tmp"
+#else
+/* Use POSIX value P_tmpdir from libc */
+#define TMPDIR P_tmpdir
+#endif
+
+/* Note that if you _don't_ have mkstemp(), you'd better have a safe
+   mktemp() or otherwise not be concerned about its unsafety.  On some
+   systems, use of mktemp() makes it possible for a hacker to cause a
+   Netpbm program to access a file of the hacker's choosing when the
+   Netpbm program means to access its own temporary file.
+*/
+#ifdef __MINGW32__
+  #define HAVE_MKSTEMP 0
+#else
+  #define HAVE_MKSTEMP 1
+#endif
+
diff --git a/testgrid.pbm b/testgrid.pbm
new file mode 100644
index 00000000..cc6a50aa
--- /dev/null
+++ b/testgrid.pbm
@@ -0,0 +1,3 @@
+P4
+14 16
+ª¨ÿüª¨ÿüª¨ÿüª¨ÿüª¨ÿüª¨ÿüª¨ÿüª¨ÿü
\ No newline at end of file
diff --git a/testimg.ppm b/testimg.ppm
new file mode 100644
index 00000000..061d5368
--- /dev/null
+++ b/testimg.ppm
@@ -0,0 +1,4 @@
+P6
+227 149
+255
+0/-0/-10.21/51.51.62/62/83/83/:3-:3-:3-:3-:3-:3-:2/91.91.80-80-91.91.:2/80-80-80-80-80-80-80-80-6.+6.+6.+5-*5-*4,)4,)4,)4,)4,)4,)4,)4,)4,)4,)2-)/*$/,%/,%0-&1.'2/(30)30)63,63,74-85.85.96/:70:7.A:0B<0D>2F@4IA4JB5KC6KC6MD5MD5OC3NB2OC3OC3PD4RE5R?1Y?2b@4nB5}E6‹H8™G9£F7¯G:¸G9¾E:ÅG;ÇG>ÊG?ËH@ÐE@çFLíCLëDKëEIîCIïBDñ>Bô=Aø;A÷:@ô:?ð<?é?@â@>×?<ËA7»=/µ@.µ@.´?-´?-³@-²?-¯@-­@,ªA,¦A-¢B,Ÿ@*›A)˜@*–A,”>-’?/’?/‘>.‘>,=+’<+’<+”?+”?+”=*”=*”=*•>+–?,–@/–?6•>5—=2Ÿ?1©B3³D3¼D4¿D4¹?0µA2¬F8žH;‡H9oA2T8*C3&=5295495473271160050-50-72/72/72/61.61-50,50,41,//-.0-//-//-0/-0/-2.-2.-5,-4+,4*+3)*7(+=.1E69P:<jBC|IHMM•OOŸW[ªdnªoƒŸt”{£‡®€†º~ˆ½sy­`a‘TKvPDhSJgOG^MH^TQbfdo|}‚‘™ž˜£©Ÿ¢¨šž “’{|lfgUXWEQNEUR[UQbUQb0/-0/-10.10.40-51.62/62/83/83/:3-:3-:3-:3-:3-:3-91.91.80-80-80-80-91.91.80-80-80-80-80-80-80-80-6.+6.+5-*5-*5-*4,)4,)4,)5-*5-*5-*5-*5-*5-*5-*3.*0+%0-&0-&1.'2/(30)41*41*63,63,74-74-85.96/:70:7.@9/A;/C=1E?3H@3IA4JB5JB5LC4LC4MA1MA1MA1NB2OC3QD4P>0U?1^A3jC4xD6„E4’E5œC3§C4¯A4µA4¼B7ÀD:ÄE<ÅF=ÍC@áEIçBIèCIêDHíDGïBDó@Cö?Cø;A÷:@ô:?ð<?é?@àA>Õ@<Ê@6¹>/µ@.´?-´?-´?-²?,°?-¯@-­@,©@+¦A-¡A+Ÿ@*›A)˜@*–A,”>-’?/‘>.‘>.‘>,=+’<+’<+”?+“>*”=*”=*”=*•>+–?,–@/”@5•>5˜>3 >1«A3µD4½C4¿D5»A2·C6¬F8œI;…G:l@3S9*B4)>63:6595484382271161.61.72/72/72/61.61-50,50,41,//-.0-//-//-0/-0/-2.-2.-3--5,-4*+3)*5)+<-0C47N8:d>=vEA†JINLšTV¤aj¥l}rŽ‘{¢†€®…¹{„»ou©[[QHuOCiOFeOG_PH_RN_[Yfnotƒ‡ˆ”™•™ž—š ”™‘ƒ~ojkY][LVSJXSZVRaXQa/.,/.,0/-10.40-40-51.51.72.72.72.72.92,92,92,92,91.80.7/-7/-7/-7/-80.91/80.80.80.80.80.80.80.80.6.,5-+5-+5-+4,*4,*4,*4,*5-+5-+5-+5-+5-+5-+5-+3.*2-'1.'2/(30)30)41*41*52+63,63,63,74-85.96/96/:7.?8.@:.B<0D>2G?4H@5H@3H@3I@1I@1K?1K?1K?/L@0MA1NB2MA1QA1YB2dC2qC3|C2‡B2’A0˜<- :+§;.¯=2µ@6ºD:¿F=ÅD>ÙCEá@FãBGçBFêDFðCEôADø?Dú;@ù:?õ;@ð=@è@@ÜA=Ñ@;Æ@5·=.³@-³@-²?,²?-°?-¯>,­@,ª?-§@-¥@,¡A+A,š?*˜@*•@+”>-‘>.‘>.‘>.=+=+=+=+‘>,‘>,’<+’<+“=,“=,”?+•?.•A6–?5š>3£>2¯A4¹C5¿D5ÁC5ÀD8¸F;®I=™J=G;h@4Q:,B5,?74=77<66:4494183072/72/62/62/62/51.52-41,41,21,.0-,1-.0-.0-//-//-0/-2.-5//4..5,-4*+4*+9-/>24I56[97l?9|E@†IDOM˜[`›fv”mˆŒwžƒ}­}‚¹u~·fm¤TV‰MEvLAkMAeOFcQHcMH^NK\[[eqty…‰ˆ‡Œ†Šˆ…†Š|xzlfiXZ[MVSLZU[ZT`[S`.-+/.,/.,0/-3/,40-40-40-61-61-61-61-81+81+81+81+7/-7/-6.,6.,6.,6.,7/-7/-80.80.80.80.80.80.80.80.5-+5-+5-+4,*4,*4,*3+)3+)6.,6.,6.,6.,6.,6.,6.,4/,30+30)30)41*41*52+52+52+52+52+63,74-85.85.96/96->7-?9-@:.B<0E=2E=2F>1F>1G=1G>/H<.I=/I=/J>.L@0JA0KD2NE4UD4^D3iD2sB1~A/†?-Œ9)”9'9*£<-¬@3³E8¸H<ÁF>ÒDCÚACÞBCâDCçCDìBCó@C÷?Aú;@ù:?õ;@î>@åA@ÚB=Í@9Â@3µ=-°@,°@,°@,¯>,®?,®?,¬?+©@-¦?,£@+ @*œ@+˜@*–@)”?*‘>,‘>.‘>.=-=+=+<*<*=+=+<*<*’<+‘>,”>-’?-•A6–?5œ>2¦@4²B6¼C8ÁC7ÂB7ÂF<ºJ?¬L@—K>|F:b@4L:.A7-@85>96=77<74:5294183083062/62/62/32.52-21,21,12--2.-2./1./1.00.00.10.10.5106005//5,-4+,6,-:01D22T71c;3qA7{E;‚HD‰RU_l‹i‚ƒs˜}y«x}µowµ`f¢QR‹LEyL@pL@hPEgQFfLC^GBVMLZ^^fjnquyxx}wz€vwzokoa`bTWYLTTL]WY]V]]V^------.-+/.,0/-10.3/,40-3/,3/,4/+4/+4/+4/+6/)6/)4/,4/,3.+3.+3.+3.+4/,4/,50-50-50-50-50-50-50-50-3.+3.+2-*2-*2-*1,)1,)1,)4/,4/,4/,4/,4/,4/,4/,4/,41,41,41,41,41,52-52-52-52-52-63.63.74/85096196/<5-=6,?8.@9/B:/C;0C;0C;.D:.D:.F:.G;-H<.I=/J>0I@1JG6MH5RG5YF5bE3jD1uB/}>,‚;)‹:)“:*š=,£B2¬F8²J=»I?ÌGBÔDCØDBÝEBâBBéAAð=@ô<>ù:?ø:<ô<>í?>áB>ÓC:ÅA5¹?0²?-®?,®?,®?,­>-¬>-¬>-ª?-¨>.¤?- ?,ž?+š?,—?+•>*“>*‘>,?.>->->-Ž=,Ž=,Ž=,Ž=,Ž=,Ž=,Ž=,<,>-‘>.?.”B4—A4@1¨@3¶A7¿C9ÅB8ÄA7¾C;·H?¦LCJ@tE;Z>2E9-<5+@93@85?75>63=52<4194083/62/43/43/23.32.12-12-02--2.,2.-2.-2./1./1.00.10.3205105104..2,,4+,7./=/.N5.Y9.e=1n@3sB;yKK€Zeƒg€p—zxªu{·ks´_d¦TT”OGƒLBwNAmNBhMAeJA`GBYGFXKKWMPU]cc`fbbia`f\Z`TW[MUXMXXP^YV`WX`WZ,,,,,,.-+.-+/.,0/-3/,3/,2.+2.+3.*3.*3.*3.*5.(5-*3.+3.+2-*1,)1,)2-*3.+3.+3.+3.+3.+3.+3.+3.+3.+3.+2-*2-*2-*2-*1,)1,)1,)0+(3.+3.+3.+3.+3.+3.+3.+3.+41,41,41,41,41,41,41,41,41,52-52-63.74/85085085.;4,<5+=6,>7-@7.A9.A9.A9.C9/C9-E9-F:.G;/H<.J>0HA1JG6IH6NG5VF6\E3dC2n@0v>-{<+‚;)Œ;*”=,œ@1£F5ªJ:´J=ÄH@ÌEAÑFAÖE@ÞCAä@>ì>?ñ:<÷;<ô:;ð<=é@=ÜC=ÍC8¾@2±>,®?,«@,ª?+ª?+ª?-©>,©>,¨?,¥>-¢?, ?,›>,—?+•>*“>)‘?*>+>->->-Ž=,Ž=,Ž=,<+Ž=,‹<+<+‹<+‹<-Œ=,>/Ž?.”B4—A2 @2¬B5¹C9ÂC:ÅB:ÂA;ºA9±I@£NGNEoG=S?4A;/96-@93A75?74>63<4194083/74/43/43/34/23.23.02-02-.3--3/-3/.3/.3/02/02/11/11/21/32032040/2.-1-,4..8.,G4-O4)X8+`<0e?6mGFyYd‚k…€uŸ||²w|¼nu»dh¯[[¡RMLB~OArL@hI=cH>`HB^ECX@BO;@FBGJDMJJQJJQIIQFKQEOUIVWO^YS`YS`XU++++++,,,---/.,/.,0/-0/-1-*1-*1-*1-*2-)2-)2-)2-)2-*2,,1++1++1++1++2,,2,,1++1++1++1++1++1++1++1++2,,2,,2,,1++1++1++0**0**3--3--3--3--3--3--3--3.+41,41,41,30+30+30+30+30+41,41,52-63.74/74/85085.:3+;4,<5-=6.?6/?6-?6-?7,B8.B8.E8/E9-G;/H<0J>2H@3HE6GF4KE5QD4XC2_B2f?.n=,v=,|:*…9)Œ;*“=.›B2¢F7¬F8»F<ÂF>ÉF>ÐE>ÙD@âC?ê@@ð>>ò::ñ;:ì<<äA<ÖC;ÆD6µ@/ª=)ª?-©@-©@-¨?,¨>.¨>.§=-¥>-£=. ?.ž?-š?-–?,“>*‘?*>)>+>->-Œ=,Œ=.Œ=.‹<-‹<-‹<-Š=-Š;,‰<,Š</‹>.‹=0Œ?/’C2˜B1¡A1®B6¼C:ÂC<ÄC=ÀC=¹FA²QJ¥XRXQsRIWI>CC7<?6>93@72>71=60:5/94.83/63.43.43.34/13.13..3-.3-.3--3/-3/-3/-3/.3/.3/02/02/00.11/22021/0/-/.,2.-4/,?0+D0)K3)T8-Z<4dFFu]jƒs‰€«…„¾~ƒÇtzÆmp½ce¯VSšLC‚K?qI<gG;cE>_FB]DBW?AN;?H:BE>HGDMHGQIGQHJRGNVKUXM^ZOaYNaXO++++++,,,,,,.,-/.,0/-0/-1-*1-*1-*1-*2-)2-)2-)2-*2,,1++1++0**0**1++1++2,,0**0**0**0**0**0**0**0**2,,2,,2,,1++1++0**0**0**2,,2,,2,,2,,2,,2,,2,,2,,3/,30+30+30+30+30+30+30+41,41,52-63.63.74/85085092,:3+;4,<5->5.>5.>5.>5,B8/B8.E8/E8/G:1I=1J>2I?3FC4FC4JB5OA4TA2\@2b>0j<-q<.w9*}8)…8(Œ:,•=/›B4¤B5²F:ºE;ÁF>ÊG?ÔG@ÞFAçCAîB@í;;ë;;ç>;ßB;ÑD:¿D4¯@-¤>(¦A-¦A-¥@,¥@.¥@.¤?-¤?-¤>/¢>.Ÿ@.œ?.˜?-•>+‘?*>)>+>->->-Œ=,Œ=.‹<-‹<-Š=-Š=-ˆ<.ˆ<.ˆ<.ˆ</‡>/ˆ>1‹?1‘D2–C1¢B4­C6ºC;ÁD>ÁD>»EA¹PL²[T¥e\‘f]u_T[UIGMACI?<92?82>71;6094.74-63.43.43.34.24/13./4..3-.3-.3--3/-3/-3/-3/.3/.3/02/02///-00.22022010.0/-0/-3/,8,,;,)C0*K70S<6^IHtbn‡z”Š¶ŒÆ„ˆÏz€ÌrxÆik¶WWŸID„E=nG<dD<aC>^CAY@CV@DP>EKGQRKWUQ^WU`XS_UR^TT^SY_S^[LaZJaZJ,-/,-/,-/,.-------.-+.-+/.,/.,1-*0,)0,)0,)/+(/+(/+*/+*/+*/+*/+*/+*/+*0,+/+*/+*/+*/+*/+*/+*/+*/+*/+*/+*/+*0,+0,+1-,1-,2.-1-,1-,1-,1-,1-,1-,1-,1-,0,)1-*2.+3/,3/,3/,3/,3/,3/,3/,3/,3/,40-51.62/74/80-92,:3-;4.=4/>5.>5.>5.?4.?5,B5-C6.D7/F90G:1F<2G?4H@5J@6P?5T>3X<1^90b6+m9.t8-|8+ƒ9,;/“=0˜?1ž>0§A3­A4µC8¾E:ÊG=ÔG>ÞE?åC@è@?êBAæDAÚE>ÈD8·B1ªA.¢B,¢A.¡@-¢?,¢>.¡=-¡=-¢>. ?/œ<,š=,˜<-•>-“=,=+Ž=*Œ>*‹<+‹<+Š=-‰<,‰<,‰<,ˆ:-‡;-‰=/†</†</‡=0ˆ>3ˆ@2‰A5‹A4‘E5—D4£E9±I>ºG@»D>»EA¸MG´ZR¯f_£qf‘sh~rdjj^V^SIQFLLBJF=B>5<8/95,74+63,33+43.34.14-14-02-/1,,1+,1+-2.-2.-2.-2./1./1./1./1.02/02/11/11/11/11/11/40/4+0;/3A32C4/J;6]OOymy‹…Ÿ“»”–Ï•Ùƒ‰ÓtzÆjn·_b§Z[”LItHBdA>]>>X?BUAIVLU\U`bbqnn}xv†|rulyoguh_k_W_P]\Hb\Fc]G,-/,-/,-/,-/------.,-.-+/.,.-+0,)0,)/+(/+(/+(/+(.*).*).*).*)/+*/+*/+*/+*/+*/+*/+*/+*/+*/+*/+*/+*/+*/+*/+*0,+0,+0,+1-,1-,1-,1-,1-,1-,1-,1-,1-,1-,0,+0,)1-*2.+3/,3/,2.+2.+2.+2.+2.+2.+3/,40-51.62/80.91.:2/;4.=4/>50>50=4-?4.?4.A4.B5-C6.E80G:2H;3H>5H>5L=6O>6Q=4V;2Z90_7/h70p7.w7-9-‰9.<1–<1›=1¢@3§A3¬B5´D8¾E:ÉF<ÔE=ÛD=ßD@àE@ÞFAÔG>ÄF8²C2¥B-žB-ŸC.žB-Ÿ@.ž?-ž?-ž?-ž>.ž>.š=,™>,–=-”=,=+>+Œ>*Œ=,‹<+Š=+‰<,‰<,‰<,‡;+‡;-…<-†</…=/…=1ƒ=1„>2†@6‡A7ˆB6“G9—E7£G<¯J@¸IB¸GA·LF³TN±d\ªqfŸ~oo|mmseZfZNXMLNAKI=EC7@=4=:188.44,11)23-23-03,/2+/1,.0+.0+.0+/1.-2./1./1./1./1./1./1.02/02/11/11/11/11/11/2015+49-7<23?53H?:^VTxr|Šˆž““¹”—Ê–Ô„‹ÏyÂqy¸kt­hnž\_€XZqSUjRWjT^hZgmfvvr‚tˆ~’‡ƒ•‰~Žv†yr€qfteZeT[ZE`Z@b\D,-/,-/,-/,-/,-/,.-------.-+.-+.-+-,*/+(.*'.*'.*',+),*+,*+,*++)*+)*+)*+)*-+,-+,-+,-+,-+,-+,-+,-+,,*+,*+-+,-+,-+,.,-.,-.,-.,-.,-.,-/-./-./-./-./.,0,+0,+1-,2.-2.-2.-2.-1-,1-,1-,1-,1-,2.-3/.40/51.80.91.:2/;30=31=4/=4/=4/?40?4.A4.A4.C60D71F93G:4H;5J;6K<7N=6P;6S:5V72[6.c60k6.t5,}7/‡9/;0”<0–<1?3 @4¢@3¨@3±C6ºD8ÅE:ÍD:ÕF@×H@ÔIBÌI?¾E:®C3¡B.œA.B/œA.œ?.›>-›>-›>-›>/›?0˜<-–=-”<.“=.>-Œ=,‹<+Š=+‰<*‰<*‰<,‡;+‡;-…<-„:-ƒ;-„<0‚<0‚<2‚>3ƒ?4…A8‡C:ˆD9”J=—H; H>¬KD²KF³LG²SM®`V­sg¦qŒz‘Ž{‚‰ws€ocqbXcUNRDMN@HI;DD8@@49;.46+/1&01)01)/0*/0*./*./*//-//-//-.0-//-//-//-//-//-//-00.00.00.00.00.00.00.1/26+97+98/4;63HE>_^Yzz|‹š”±’—¿•Ç„ŽÃ}‰»{‰¶|‹²}Œ«}ˆšwƒq~‡n}‚n~~o‚~yŽ…ƒ™‹¡”‘¨˜¥”ˆŠƒ–ƒ|{j{i\hTXX@]Y<_[@-.0-.0-.0-.0-.0-.0.......-+.-+-,*-,*.*'.*'.*'.*),*++)*+)*+)*+)**()*()*(),*+,*+,*+,*+,*+,*+,*+,*+,*+,*+,*+,*+,*+-+,-+,-+,-+,.,-.,-.,-.,-/-./-./-./+*0,+1-,1-,1-,1-,0,+0,+0,+0,+0,+0,+1-,2.-3/.40/91/:20;31<41=31=31=31=4/>3/>3/@2/@3-A4.C60D71E82F93H94I:5J;6L:6N94Q83T50^72e60o6/x8/‚90Š:/<1”>1™?4›?4›?2Ÿ?1¥A2®B5¸D7¿E:ÇG>ËH@ÊJAÃI>¶F:ªB5žA0™@.™A-™A-˜?-—>,—>,™>,™=.—>.•<,”=,“=.=-Ž=,Š=+Š=-‰<,ˆ;)ˆ;)‡;+‡;+„;,„;,ƒ;-€;,;/€<1€<1>5ƒ@7ƒC:‡D<ˆE<”L@–H<žG>§JC®LI®QL­]Vªj^§€o¡yšš‚›ƒ†•€z‹xm{jbm]SZJQUFKO@EI:@D6;=057,13(01)/0(./).-(.-).-)/.,0/-/.,/.,/.,/.,/.,..,/.,..,0/-//-0/-//-//-//-//-2-17,:6*83-1961HJ?bfX{€z‹““£–°Œ•¶ƒ²¯…™²¤¶’¨³“¨«¤£Œ ž‡ž˜‚™}˜‰œ‹ˆ£”°š˜±›”­—‹£…›„’|k|iXfOSV;ZV9^Z=+/2+/2+/2+/2-.0-.0......------.-+-,*-,*,+),+),+),*+,*+,*++)*+)**()*()*(),*+,*+,*+,*+,*+,*+,*+,*+,*+,*+,*+,*+,*++)*+)*+)*,*+-+,-+,.,-.,-/-./-./-./+*0,+0,+0,+0,+0,+/+*.*)/+*/+*/+*/+*0,+1-,3/.40/901:20;31<42=32=31<20<20=20=2.?1.?1.@2/A30B5/C60D63C84D95E:6G96H94K84N50X72_60i70r7/}:1†<1Œ>2>2—@6—?5—?5—?3œ@3£C5«C6³E8ºE;½G=¾H>¹G=°D8¦A5›?0—@/—@-—@-–?.•>-”=,•<,–=/–=/•<.“;-’</Ž<.‹<-‰<,‰<,ˆ<,‡;+†:*†:*†:*ƒ:+ƒ:+‚:,€:.€:0€<1€=4?6‚B9ƒD=‡F@ˆH?‘KA’H=šG?£LE¨PL¨XQ©f]¦vh¢Œw›™€•¤‡Œ£‡†ƒ~‘}t‚qjue\eRV_LNUCEL:?F6<@27:/68-01)00(.-(/,'/+(/+*1-,2.-1-,1-,1-,1-,1-,/.,1-,/.,2.-0/-2.-0/-0/-0/-0/-2-18,:5)51++66,GL8`hQzƒpˆ’‡—–š ™¥†–£‡›¢ª©›¹± À³˜·¨š¶§›·¨˜·¥‘±œ‰«’‰«±”’³–•´•­ˆ¢…„›{’xgzdTbIQT9VU9XW;,03,03,03,03./1./1//////....../.,.-+.-+.-+-,*-,*-+,-+,-+,,*++)**()*()*()+)*+)*+)*+)*+)*+)*+)*+)*,*+,*++)*+)*+)**()*()*(),*+,*+,*+-+,.,-.,-/-./-./+*/+*0,+0,+/+*/+*.*)-)(0,+0,+/+*0,+1-,2.-3/.40/:12:12;31<42=32<21<20;1/=20<1/>0->0-?1.@2/A4.?4.@51@72@93A:4B94C84F74H5/Q51X5/a6/l8-u9.€</†>0‹=1“?5•>5“?5“?4•B4šB4 C4¥D4¬B5°D8´E:±E;ªB7¡?4š>1–>0•?.”?+“=,“=,“<+“<+”<.”<.“;-’<-<.;-Š;,‰<,‡;+‡;+†:*†:*…9)ƒ:)ƒ:+9*9-9-€<1<3?6€A8‚C<…F?ˆIBŠICŽJAG>—HCŸNJ§VR§`Z©pe¤ƒrœ”}–¡ƒ¨Šˆ§ˆƒ „~–~yˆup{jdp\]iSR^JJS@BK:>E5:@29<134,22*1.)/,'/))0**1++2,,1++1++1++1++1++0,+0,+0,+1-,1-,1-,1-,/.,/.,/.,2,.8*75(13+(56&EK1\gGu‚d†“yŽ›Š‘ž”Ÿš‹ž˜¥——´ž¢Å¥©Î­¥É­¦Ê°§Ë±¤È¬—¾Ÿ‹³‘ˆ°²Ž²Ž²¬Š„¡‚€™{wŽrdx]Q_ENR7QQ5SR6,03,03,03,03./1./1./1./1/////////////.,/.,.-+.-+/-..,-.,--+,,*++)*+)**()+)*+)*+)*+)*+)*+)*+)*+)*+)*+)*+)**()*()*())'()'(+)*+)*,*+-+,.,-.,-/-./-./+*/+*/+*/+*/+*.*)-)(,('0,+0,+0,+0,+1-,2.-40/40/:12;23;23<34=32<21<21;10<1/<1/>0/=/.>0->0-?1.>3/=60;60;83<94=:5>93@72C60G4.O4+Y4+d5+n8,x:-;.…;.<4‘<5>3@3A2“B1—A2™@0 >1¦@4ªB7ªB9¥@6Ÿ>5—=2•?2’?-’?-‘>,=+‘;*‘;*’<-’<-‘;.‘;.Ž;-;-Š;.ˆ:-†:,„;,…9)…9)ƒ:)‚9(‚9*9*~8,~8,€<1€=4€@7B9„E>…IAˆKFŒMFŒIAŽH@˜JF¡SO¨]X©hb©{n¤Žy™œ’§†‰¬‹‚ªˆ€¡„™€~‹wxnjxadr[ZhQQ]IITCCL;>D6<?467/44,30+0+'1()1()2)*4+,2)*2)*2)*2)*2)*0**0**0**1++1++0,+0,+0,+0,+0,+1+-6)25)-4-%46!AH&Wc;q€W‚’mœ} †¢Œ‹¡Š¨‰•¸Ž È”¤Ð©Ó«¨Ò®§Ñ­¡Ë¥“½—…¯‡‚¬„‡°†ˆ®…Œ±ˆ‹«†Ÿ{{”tqˆk_sWM]BIO3JM0KK/./1,01./1./1.0/.0/.0////00.00.00.0/-0/-0/+/.,1-,2.-1-,1-,0,+/+,,*++)*+)*+)*+)*+)*+)*+)*+)*+),+)*+),+)*-(,*(),())'()'((&'*()+)*,*+,+)-+,.-+/.,/.,-,*-,*/+*/+*.*)-)(,(',('1-,2-*2-*2-*3.+4/,50-61.;31;31<42<42=31<20;1/;1/<1/<1/=/.=/.=/,>0-?1.=2.=4-=6.;819919:29:2;81?6/C2+J1,T2)^3*g7+o9-u=.z</‡=2‹=3‹?2Œ@2Ž@3@1’?1•=/˜</œ>2¢B6¡C7žB7™?4–>2”>1”>/“=.=-<,Ž=,Œ=,Œ=.>/Ž<.Ž<.‹</Š;.ˆ:-ˆ:-†:,„;,‚:+‚:+:*~;*~;+|:*}9,|:.€>2>5€@6‚C:ƒG=…JB‡NE‹OGŒICŽID“PJ˜\Tžg`uiž‡už˜€”¡…ª‰‰¬‹ƒ¨‡€¡„~›zytˆoi}bby]\pUUgOO\HGRAAH8=A388.85.7/,3+)2()2()3)*4*+0*,/+*0*,0**0*,0*,2),2),3*-1+-1+-1+-0,-0,-0,-1+/4)/4*+4-%46!?F%T`8m|Qg‰™tžz¡~ˆ¡zŠ§{‘¶‚›ÄˆŸËŸÉ™žÇÆš˜Á•Œ·Š‚­€…®‚Œµ‰„«‰®…‰©‚~œxvolƒfZnSJZ?GM3FJ1DF./0+.0+/0+01,01,01,12-21-32.43/43/62/51.41,3/,4/,50-50-4/,3.+2,,1-,0,+0,+.,-.,---/--/,,.++-*).))+.)/.)-/(/.)-/)-.)-.*+.*+/+*/+*-,*.-)--+-.)./*./*------.,-/-./.,0/-2.+2.+2-*4,)5-*6.+8/*:1,:1*;2+=4-=4->50>50>50=4/<3.;2-=2.<1-<1/;0.=/.>0/>0/@1.A0)@2);4*77-39-28.56.:3-?-)F*'L)'S*&Z/(`5,d<0k@0yA0@/†A1‹B3@3“=0“;/’8-“;/“=.”A1•B2”C2’A0‘>.‘;,—;0—:2’;1Œ=0ˆ?0„?/‚?.ƒ>.ˆ@1ˆ?0Š>1Š<0‰;/ˆ:.†:-ƒ;-{9+~@3w<.q7)w>-w>-v;-}?2{;/‚@4‡E9‰I?ˆLA†MB„PC‡NE‘KI—SP”bY‘paŒ}j‰‰q‰–|Šž‚¤‡¦‰Ž§Š§Œ‡¤ˆ~Ÿ‚uš{o—uiib„_[zXZsUZnSWeNPWEJK=C?6@93;0.6**3')3'+3'+1&*,*-)+***,*(),'+.(,1(-2'-3(.3(.3(02(00)10)1/*1/*0/)-1++0-(//#<?*V\@pzX‹gœu‹sŒ¡vŒ¦wŠ¨v‰ªuŽ´{™¿†ŸÁ¾–ºŠ³ƒˆ®}…«|ƒª{…ª~ƒ¨}ƒ¦~€ž|y”ur‰mh|cYkUMZFIQ<CH4?B//0*01+01+01+12,12,21,32-43.43.74/74/63.52-50,50,7/,7/,6.,6.,3.+2-*0,+0,+0,-/-./-0--/-,1+*/)*.)(-.)//(//(//(//)-/)-.*+.*+/+*/+*.-).-)./*./*./)./*.....0....../.,/.,1-*3.+5-*5.(7.)8/(:/);0*<2)=3*>5,>5,>5.>5.>5.=4-<3.<3.=2.<1-;0.;0.=/.=/.>0/@1.C0*C0)A2+>4+:6-95,:3-<1-?-+D*)I*(N+'T/)Z5-_:1e>/pA/xA-€A0ˆ@2?4‘=3“:2“;19.‹</‹>.Œ@0A1A1ŽA/‘?1•;2•;2<2‹=0†>0ƒ@/?/‚?.…@1†>/‰=0ˆ<.‡;.„;,‚:,€;,x8,x<1s9-n9+s>.s>.r8*u:,}=1ƒA5‰E:‹I=‰K@†NA„PC‡NE‘JH—SR’f]Œwf„†p€“wž„¥†ˆ§ˆ§Š§§Š¤‰Ÿƒwœ}o˜ve’i`‹`Y‚ZY{X\wXZnSSaJNUCFH;C@7<737/-3*+2)*1(+.(*,*+**,+),+),-(,/)-2(02(02(02(02(10)1/*1-*3-*3-*12-12.-0,)--%8:,SXBox]€‹i‡–o†™l†žnˆ£p†¤p„¥pŠ­w“¶€™¹‡–¶„“³Œ®{†«x‚¨w‚¨yƒ¨|ƒ¦|‚¤{x’uqˆnh{eYjWMZHEL<@D5;=/12,12,12,12,23-23-43.43.54/54/85085085074/72.61-80-80-7/,6.+4/,3.+2,,1-,1-./-.0.1/-0--/,,.+*/+)./(/1'//)-/)-/)-/)-.*+.*+0,+0,+/.*/.*/.)0/*0/*0/+//-///0./0./0/-/.,1-*2-*6.+70*90+:1*<1+=3*>4+?5,?6-?6-@7.@7.?6/>5.=4-=4-=2.<1-;0.;0.;0.;0.<1/?1.C2+E0+H/+K--L,/K+.I*/E+.A-,@.*A.(E.(M.)X1*b3-g5,j:,o;-w;0=4‡<6Œ<5:4:4Š<2‡=2†>2…?3†A2‰A3ŒB5C5Ž?2=/Œ>1Š>.‡>/…>,†=.ˆ<,‰;.‰<,ˆ:-†:*ƒ;,<)~=+{<+}=1z<1v:/u;/x@1x@1v<.v;-?5‚B8‰F=‹H?‰JA‡KA‡NCŠNF‘JH˜TQ–f\ve‡…n‚‘t{¤ƒ…¨‡ˆª‰ªŒ©ŒŽ¥‰ˆŸƒš}y˜xi–ma’e\‹a\†`]‚`[|]UrVPhPFYEBP?:D93:2.3,*/)*,)**(0'(1&*1&*1&*0'*1(+0*.0+//*.-+.,+0+,0)-0(-1(-1)-01/23/..*'**"57*PUAmv[|Ši€k~“j™jƒŸn€ n~ m€¥q‡¬x¯|‹­zˆªx„©v§v§u¦w¦y¤y€¢}}›yw‘tp‡mh|cYkUMZHDH9=?299-23-23-34.34.34.45/54/54/650761961:72:72961940940:2/91.91.80-50-4/,4..4..3/03/01/01/20.1..0--/-+.0)02).0*.0*.0*.0*,/+,/+*1-,1-,0/+0/+10+10+10+10,11/1111/010.10.2.+3.+3.*92,92,;2+<3,>4+@6-@6-A7.B8/A8/B90A8/A81@70>5.>5.=2.=2.<1/;0.;0.<1/=20=2.B3.E2.L..R+0V'/U&.P'/I).C/.<1+;2)?2)G0(R/)_.*d/)i9/k;/u<3}<6…;8‰;9Š;7ˆ:6‰>8†@8†B9…B9…C7†B7‡A7‰A5ˆ@1ˆA/‡@.‡@.‡>-ˆ<,ˆ;+‰:+Š8*ˆ9*‡:*ƒ:)<){=(x>(x>*‚>1{7.z7.z<1v;-w=/|A3{@2€B7ƒE:†H=ˆJ?†J@‡MBˆODŠQHŒSJŽ\QŽh[‹tb…g€‹m~–vž|ƒ§ƒ„©‡‡ª‰‹ªŠ‹¦‡‡ ‚ƒš~}˜yq•oi”ifgfŒeg‹gd†e_~_Zw[PhPK^JBP?8D62:/.4*,/(+*%2&&5%(4%(2&(1'(/)+/+,.,-+++*+-*+-(,-(,/',/',/*+-.*+1++0+'.+"88,PUAiqYv„c{Œhyh|–iœl}Ÿmyžkz m¥r‚§t€¥q}£p}£p}¥s~¥v~¥x}¤x|¡x}Ÿzz™wuqn‡je|bWkRN[GDF9?=1:8,45/45/45/560560560761761872872;83<94<94<94<73<73<41<41;30;3083072/61.61.5106216213122011/00./1-.2,04+.4+.4+.2,.2,,2,,2,,3/.3/,3/,3/,21,21,32-32.32032032032051051.61.61-;4.<5/=4-?6-A7.B8/E8/C9/D:1D:1E;2D:1C90B8/@5/@5/>3/>3/=2.=2.=20=20>31>31@51E31M02T,4X)3W(2R)1K,1B30:6-77+:6*B4)M2)X/)^/)f:1j;3s<7z=:‚<:†<;‡;;†::‚;7>8A8B9ƒC:ƒC:…@9„@7‚C2‚C1ƒB0„?/†=.ˆ<.‰:-‹9-‰7+‡8+…9+‚:+~=+x>*v?*x>*9-|/'‚8/„>4w4+s5*}A6}C7E:€G<‚I>ƒJ?„KB†MD‡QG…WJ|aNzjQ€pYu]‚|bƒ†i†“uˆ|ƒ£~§‚‚ª…‚¬†©„¥€{ž}z™wz’pzlxlwŒkumr‰lm„gkd`rX[iRR[HHL=@@4;7,70&5*$6('5''3''1'&.)&+*(++)+-*',('+*)+*)+***,+),+),-)*,#$2*'50*86*BC5UZFfpWn}^tˆeqŠbuex˜iw›ks™htšiwŸkz¢nx lwŸmx ny£s{¥u{¥w|£wyžuzœww–tsŽom†hd{_WkPN[GCC7>:195,560560671671782782872872983:94=:5>;6>;6>;6>95>95?74?74>63=52;63:5294194184184395484373243151240/6-06-.6-06-.4..4..4..4/,40-40-40-51.32-32-43.43.43/431542540841850940:5/=60>7/@7.A8/C90D:0G:1H;2F<3F<3F<3F<3E;2C90B71A60@51@51?40>3/>31?42@53?53@72C52I35P16T/6S.5P05J22C52=90<:-=:+C7)I6(Q3)W2)]2+d3,l50v64}77‚87ƒ77‚66~75}:4}<6}>5€?9ƒ@8†?9…A8€B3€C1B1ƒ@0…=/‡;.ˆ:.‡9-…9,ƒ9,‚:,<,|=,y>,x?,|=,‡5*‹2,¡LE§XQ‹A8|90‚F;€K=yH:zJ<{M>|N?}OBQE‡UJ‚_LrkOosR|uX‡w]yb˜iŸ‹s ”z––z|‹£ˆ¨…§‚€¤~{Ÿy~™vƒ‘p…ŽoƒŽp€pp{ŽpwŒms‡kj~bfv\_hSV[GOM>GA3@6*=0';,'9+(6+'3+(/,',-'+.').().(+-(-,*/+*3)*4(*7'*7'(3($<3,E>4IG8QR@]bKgqVjyZn‚]k„\l‰]p‘bq•eo•do—eršfuŸmrœjq›itžnx¢rz¤vy¢vyŸvvštw™vu”rokj…fc|^UlON\ECC7@91;4,671782782782893893983:94:94;:5>;6?<7?<7?<7@;7@;7B:7B:7A96@85=84=84<73<73<73<74<74<74;639529338308/09/.8/080.80.80.61.61-61-61-52-52-63.63.74/74/540540841952:63<94=84@93@70A8/C90D:0G:1H<0I=1I=1J=4J=4J=4I<3F<3D:1B8/A7.A81@70@72?61?61@72@72A83?74@85B86D97G96H96H96H94E80E8/E9-E9+G9,I9*K9+Q7*Z/&d/'n3-z63ƒ98‰;9‹;:‹=;‹A>‡@:‚=6<3:3‚<4ˆ=7‰@7ƒA5B3‚@2ƒ?2ƒ=1„<0;/€</~<.|=.{=.|>/|>/}>/=/†9/1+¢<8ÍlfÙ~y­ZTŒC<ˆLAN@tJ<qM=pQ?qR@tS@zWD‚[J~eOmsOo{U„y[˜u_©oc¶mf¾qk½wo»‚w±Œz§—~žŸ€–¡¡~…y…™vˆ‘r‹rˆ‘r„“r€–r|–sx“pt’pm‹ii„edx\]kQV^GMP;ED0B;+@3+?2,;0*70*30)00(./'./)/0*2/*6,+:*+>(+C&+E$+C&(F5-LC4VQ>[YD`bJgmQiwVj{Wl‚[g‚Wf†WlŽ\o”an”an–bršfsko›jo™irœnw ty¢xxžwu›vs—su—vs’rn‹li„cb{[TmMM]CGH:E<5@707827828938938939:4:94:94;:5<;6?<7@=8@=8@=8A<8A<8C;8C;8C;8B:7?:6>95>95=84>95>95>95>95=85<73:51;30:0.:0.91.91.91/91.91.72.61-61-63.63.63.74/74/74/540651952;83<94?:4B;5B;3A8/B:/C9/E;/H<0I=1J>2J>2K>5K>5J=4J=4F<3E;2C90B8/B92B92A83@72@72A83B94A:4?82@93B;5D=7F=6G<6K<5N;4M6.N6,Q6+Q6+Q7*P9+P9)V6'f6*r6,~;3‰@9•D@›HDŸJGŸLF QJ™LDŽD;…;0„7-…7-Œ91=4ˆ>5‡>7†=6…<5…<5=4}=3z>3x@3vA3x@3z>3<3ƒ:3ˆ73‘31˜(&³=;ì|zý•’Åhc–G@‰K@xH:nM<jQ=fT>hV>lX@t[E`L€hPysQ„wUžt\´l^É__ÓV\ÙQ[×T\äouÙzx̆|À~µ•€«•}£’xŸvœŠrœŠt™u•u‘‘u‹’s†‘s‚‘r|‹ny†ju{amoWgbN_TBUE5R</O4-M1-I0,D/*>/(9/&7.'6/'81):/)=.+A,+F)+H(+K'+H)'TB4YQ<d^FgeLilOnuVm{Xl~Xk„Zg„Vg‡Vm]p•bq–br™duit›lo™ko–jr™mwvyŸxwšyt–us”uu”usqoŠki„ea|[TmMM]BMN@KB9F=69:49:49:49:49:4:;5=<7=<7=<7=<7@=8@=8A>9B?:D?;D?;E@<E@<E@:D?9D?9C>8E>8D=7B;5B;5B;5B;5B:7B;5A:4A:4A83A83A83@72@64>71>71>71<71<71;60:5/85.85.74/74/961961961:70<71=82A:2B;1C:1D<1F<2J>2K?3L@2N@3N@5N@7N@7L?6K>5I<3H;2E;2E;2B90A8/@91?80?80?80@93@93<5/MD=M@:K:3T@9R62O0+a<6\3-`5.`4+^/'^/%f6,m=1q=0‰I=G;—I?ŸKAŸF>›>7š=6žD;¦ND¤PE¨VJ«YK¥OB™A5—;0›>6“98‘98Œ65†52†84„?8|@6r>1rB4oA2q=0v:0‚72Œ43’-1š',ÈHIèbaÅEDÍWUáyv«VO†F<„VFmR=i[AibFjeHj`En^D|dLjT’bL²m]ÑnhÛX]à;Lç-Eò+Hõ0Lö@YìI\ä[eånrâ{|Û€}Ûƒ؉‚Ãv½wº„x¶†x²ˆx­Šw¨‹y¥Œxš€o™~m˜xi“oabY„UM{IBxA>u:<i.2d02a11P&'G%#L0-M51G4.E2,D1-F1.F.,G+*K--Q6/YH4`W:f_BgdEkoNu|Zu„]oYlƒWl‡Zp]r’as•bu—dw™fv›hr–js˜os—qq•os•tw™xz˜|y–zx•ysrm‡jk„fi‚dazZTmMO_DNO?SK@PG>9:49:49:49:4:;5;<6>=8?>9>=8>=8A>9A>9B?:C@;D?;D?;FA=E@<E@:E@:D?9D?9E>8E>8E>8E>8E>8D=5C<6C<4D;4C:1B92B92B92B92A83A83@93@93>71<71;60;6096/85.74/74/96196/96/:70<71>:1A:0B<0C;0E;/I=1J>0L@2L@2N@3N@3N@5N@7M?6J=4I<3H;2E;2E;2C:1B90@91@91@91@91A:4B92D93I81L/+V.,j76u99{;;…ECƒD?„H@…IA†G>ŽIB™NH¡PL¢OG“D7”B4šB6ŸC8 >3œ7-œ7-Ÿ=0£C5›?0™@0 E3¤G6§F6®H:¶NE´LM¯HL¥@D™9;‘98>:ƒ@8x>3o>0zI;…LA„?8„,+’).³9DÒLWÜKNßMMÊ>=À@?Ð`\³YQ„@5‚TDyaIe[@^]?ihIslOvgJ~dK–cN¼dXÖ_[æUZëANò,Eþ%Dÿ&Hÿ'Iÿ.Nù3Nò<RðIZëP^åQ]ãS]àXbÛbiÕflÔjnÒmqÏqrÍutÉxwÇywÊ||ÉyzÈvxÇqtÆkrÅfnÅakÃ_kÅ`n³S_¥KUšGQDK†EIm69HT1-M0*H1+J6/K81K:2O>6UE8[O5cZ9gaAifEnrOz‚]z‰bt†^r‰_p‰_p‹`qŽ`u’dw”dw”dw”fs’ix–rz—xy–x~˜}ƒ„…‡ƒ›…™ƒz’zr‰om„hhd`y[TmMNaERSAVPBUOC8938938939:4;<6<=7?>9@?:@?:@?:C@;C@;C@;C@;D?;D?;FA;FA;FA;E@:E@:E@:G@8F?7JA:I@9H?8G>5F=6E<3F<3E;1D:1D:1D:1D:1D;4D;4D;4C<4?80?80<71;60:5/:5/94.94.96/96/:5/;7.>7/@9/B90C;0E;1F<0I=1K?1M?2M@0NA1M@0P?5M?4L>3K=2I<3H;2E;1D:0C:1C:1A:0A:0?;2@<3@<3D;4UD<R3.j23ŽAG­LWÃU`Ë]fÆ_b›B>—I?–LAœNB¬RJ¾URÌOSÄLK¥@4B0 @0¥?1¥;.£7+¤8+§=/«E6¡>+˜7$:'¥>-¬A/²C2¶D:§02¯:B¹DL½LRºMR­KL–A>‚71{8/v6,x2*„1-œ37¹=GÕGWèM[âFIÏ53Ð:9½31ÈNKÆc]”J?Œ[J}cLj_CgdEtoOpQhJhN°hYäbbõQZôDQö7Iý1Jÿ2Nÿ/Mÿ*Hÿ2Oü0K÷1Jõ5Lò4Mð2Kó3Ló7Pò@XïC[îF]íIaëNcêQeèUhèVkåSjäRiãOiäNiçNlëPoòSsóVuþh…ña|å[tÕUlÍYlÉdr¨R]r-2^&'Y0,W:4Q?5B:-:8)>B1LN9VO3aV6e_?heDquT†e~ŒiwˆdxŒiu‹er‹dr‹cvŒeyhzizj€•v„›~Šž… Š’£—¨–˜©™–¦™‘¢’‡˜†zypƒmg~b^wYTmMPcGSXDXUDYVG7827828939:4;<6=>8@?:A@;BA<BA<DA<DA<DA<DA<E@<E@<GB<GB<GB<FA;FA;FB9HA9HA9LC<KB9JA8I@7H>5G=3F<2E;1D:0D:0E;1E;2D;2E<3E<5E<5@91?80=82<71;60:5/:5/:5/96/96-;7.;7,?8.@:.C;0D</F<0F=.J>0K?/M@0M@0M@/M@0O?2O>4L>3K=2J<3G:1E;1D:0D;2D;2B;1@<1@<3A=4B>5F<3S81[*&‰:?¾T`ÙTeæUfãUcËJOŸ30–:/‘>0™A3®F=ÅIGÕBHÍ>@±B7§F5©B3¬@3¯?3°>3³B4´D6²G5©B/£<)¤;(©<(«:(®6&­3(¶97º9=½7>¼7<¿<BÆJLÄTS¾XS™?7Œ4*‰,%61ÃHKÜOWâAQÛ3@Þ:9Í.*Ó84È74ÆE@È]U©WKWFwW@veI~pSpQ„fJdK©o[ÕuiîSWüDP÷;Jø9Iþ=Nÿ@Qÿ:Lù4Hÿ?Sþ9Mú4Ký3Ký1Jÿ/Iÿ0Nÿ6Sÿ3Tý4Vý6Wú7Wø9Zø=\÷>^õ@aøCfö@fõ?eõ?gø?hüCmÿErÿIsÿOuÿQuþUvòSqçTnåaxÖfv·Wb}15j0/X1*P8,G>/>A.;D/?G/PK._T8g`CokN||`ˆŽr‡‘v}‹qzq|s|szozŠm}Œoƒ’uˆ—z•£Šœ©•£®¦±¡¬´§±¹®°·°«µ­¡«¢” ”ƒ‘‚s„qh|c]tXSlNPdHRZCWYDYZH671671782893:;5<=7?>9@?:BA<BA<DA<DA<DA<EB=FA=FA=FC<FC<GB<HC=HD;HD;JC;JC9LC:KB9KA8J@6J=4I=1H<0G;/E;/E;/E;/E;1E;1E;1D;2D;2@9/@9/@91?80<71;60;60:5/;7.;7.;7.<8-?8.A;/C;0D</G=1G>/K?1M@0M@0NA0O?/O?0O?2N=3N=3J<1I;0G;/D:0D:0D<1D<1B;1@<1A=2A?3B?6K=4\5.w32­LSÛ]iæM_å@Q×6E·&+¨1)7(’:&—<)¬@3ÃD=Ô>?Í;;±?5¨B4ª@2­?2²@5¸C9¼H;»J<¬>/ª?/¨=+¦;)ª9)°:,·;/¼<1ÊFAÎDAÐ>?Ð79Ö7<ÞBEâLNÝROÄC>ÆKCÍTLÔSMÙKJÛ?Bà5>á27Û4.Ú7.Ð1+Ï:4¿84³@9µ[P–UC{N9‚bI†kP„`F’[F­fTËrdçmhêEKò:Dê9Cç<DçBHçFKèGLêFMôJSòBMò9Iö5Hù2Gý/Fÿ2Lÿ8Rÿ9Vÿ9Xÿ:Xý:Zþ;]ÿ=_ÿ@bÿAeÿCiþDkþDmýBmüCoüCoüBqýBoÿGpÿ@hÿGmÿMpòIhéOkå[rÙcs½YcŽ?D`&$Q+"V@3VO=IL7@D-RJ3eYCujV€{gŽy™œ‹•œŒˆ“ƒ‚Ž€‰—ˆ›Œ™ˆ‡’‚Š“‚—žŽ¤¨™²¶§¸¼®¿Á¶ÀÁ¹ÂýÅÆÁÃÃþ¾¾°µ±¢©¢—Œy‡vi{e\sYTmORfJQ[BUZCW\F560560671782893:;5=<7>=8@?:@?:C@;DA<EB=EB=GB>GB>GD=GD=HC=ID>IE<IE<KD:LE;LC:KC8LB8KA5L@4K?3J>2I=1G=1G=1F<0F<0E;1E;1D;2C:1A:0A:0@91@91=82<71;60;60;7.;7.;7,<8-?9-A;/D</E=0H?0J>0L?/M@0NA0NA0O?/O?/O?2N=3M<2L;1I;0G;/F90C9/C;0B<0B;1@<1@>2A?3B?6N;4m84—FEÄY_ÛWbßCQÞ8FÔ3;½++­1'¡8%˜:!—9 ¤;&¶>.Ã:2¾71§7,¢:-¥9-§7,¯:1¸B8ºE;·E:®>2­?0§<,¤6'ª6)¶>0ÃD;ÊG=¾8-Î@6áD?ìBBô=Aó9>ë27Þ.0Û75âGBèSLåNGÜ=9Ù10ã/2ê67Ù1(Þ</Í,"Ð71¾3,°7/Ég\¸l\ŽWC‡ZCƒV?‰R=©\LÎlaágbäTTðEKñ=Fä;@Ù=>ÒA>ÒGBÝOMëWWíNRïDMð:Gö5F÷2Fø.Dû1Gþ7Nþ8Sý8Tý8Vþ9Wÿ;\ÿ>aÿAeÿCkþ;eý<hý>jý?mú>nø>oô=mö:jÿHrÿ;aÿ?dÿKnÿKn÷NmïUoãZnèr‚Âaj˜ILt:8\6-P9+PC2UM:^QAreUˆ|n•‚Ÿ‘¦§Ÿ¡¦Ÿ–œ˜•ž›ž§¤¦¯¬¦¬¨¢§£¦¨£µ´°ÂÁ¼ÌÉÀÑÎÅÕÐÌÕÐÍÔÎÎÕÏÑÑËÏËÆÊ¿½À°²¯˜Ÿ˜€Œ~m~k`t[WnRVjNS`FV`EX`H560560560560671893:94;:5=<7>=8A>9C@;DA<FC>HC?HC?GD=HE>ID>ID>JF=JF=MF<MF<ME:LD9MC9MC7NB6MA3N@3MA3JA2JA2I@1H?0G=1F<0D<1D<1C:1C:1A:2@91?82?82<71<71<8/<8-<8-<8-@:.A;/D</E=0H?0K?1M@0NA0P@0P@0O@-O?/O=1O=1M=0L</I;0F:.E8/C9/B:/A;/A:0?;0?=1@>2@@4P91r1/¨JKÁSV»>D½06Â03Á//º1)©1!£9#œ=!™;›< ¡=#¥;%¤6%ž6) 8-¢6,¥4,­81·@:¸C<²@6±B7­A4¨</¦8+¯:0¼C8ÃD=Å@7Ã>-Ï>-Ù5+á*&í"%ú%+ÿ*1ÿ.4í)*ç.+á3,ß4,à3,ã2,é0-ç2+Ú2%Ö6&Ð2&Ï7,Å6.½>7È`UÒq¦eSVBŠM:£WIÈf[ßd_çRTèCIõFMí@Dß??Ô@<É@8Æ@7ÑFAãMLïJNô@Kù9Hý7Hü5Hø3E÷6Gú;Mú8Pü7Rû6Rý6Uÿ6Xÿ9]ÿ;aÿ<gÿ>kÿ?mÿ@qÿBtÿBuþBuüBtùBpÿFmúAaþEeÿKjþKkÿVtÿ_zõ]tòj~æp~Óow®^aƒCAg6/hB7sUJye\Œ}v£–­£¡²®«¶¶¶²¶¹ª¯³°·½¶½Å½ÁÊÀÃÊÃÂÊÉÆÍÕÎÖÝ×ÛâÚØçÝÛéÝÝæÚÜåØßä×ààÔÞÙÏØÊÅ˺ºº¢§£ˆ’‡t‚qexb\sW[oS[jM\iK]gL561561561560561671872983<;6=<7@=8B?:DA<FC<GD=HE>HE>HE>HE<JF=JF;KG;KG;NH:MG;MG;ME:MD;MD;MD=NC?NC=MC7MD5KB3JA2H>2G=1E=2D;2C:1C:3A:2A:4@93?74<73<71>7/>7/<8/=90>:1A:2C<2D=3H@5I?3L@2MA1NA0P@0O@-O@-L?/L>1M=0L;1J91I81E80C90@9/>:/;;/<<0=>0>?1A?0S8-‡<7Ä\[ÊZY®86«.*²1+®.%­2#¬9&¬B*ªF,¦D'£A&£A&¢<#Ÿ9# 9*£;0¦:0¦7.­<4¶C<·E;°@5«=0¨</©;.¬<0»E9ÆLAÅE<¼8,¿6$Í:(Ý;.æ3,ñ+*ù(+ý&+û%'ø**õ.+ï2,ë4,è3*å3)ä2(à3%à:*Ñ2Ü?0Î6)È8-Â?5³A6Ñl`¿gY¥RB¨OAËcZçhbæRRêAFóBJë>Dá;=Ö<<Í@9Á>4¶8,¹6.Ä94âHHé@Eð;Dö:Hú:Iø8G÷;Iû?NþAUÿ?Vþ<Tþ9Tÿ8Uÿ9Zÿ:^ÿ;cÿ>iÿ>lÿ>oÿ>pý>qú@rø@t÷Aq÷CjÿMmÿSs÷Jhë@`ýTsÿg…ÿe‚÷]wï_xçh{Üp}Æqx§gg^X{ZQ—~wª™’¾°­Ä¼ºÆÂÃÈÇÌÇÇÏÃÃÏËÊØÌÊØÑÊÚ×ÎßßÔäæÛéêàëíãëïåæòèæôèèðäæîáèíàéêÝçäØâÑÊÑÁ¿Â««©‘–z†xl{hbu_`rXbpVboScmT21/320431651875984984984;:5<;6==5??7AA9CC9EE;EE;HH@HH>HH<JH;KJ8LK7ML8NK8MJ9JF:GD;JFCNIMNIPGDOD?ENE>LC4J@4KA5MC9LB8F=4B90E<5C:5@93?74@85?75<74<42C:5B94A96>95?:7>:7?;8@<9B?:D@7G@6J@4L@0O@-O@+L?,G@.F?/I;0K81M53K65J88F;9B?:6904</9D4:A/69$?=(bC1¼j^¹JA¦7,¥5)§7+¥5'¥7&¨=+£8$¥<&ª@*­@)¬=)¬9&®6%«6%¤6'¡7)¢8*¦<.ª@2¬B4¬B2¬A1©>,«=,­<,³=/½C4ÄF8À>1º2&Ç9+Ì8*Ð8+Ö8-Û7-â5.é3/î1-ñ.,ô.-ó0,ñ1,ê5*ã7)Ý:)Û:(Ü9&Ô3Ð3 Ï8'Æ7'¾6(ÂB5ÏSIÈNCÏSKá\Wî]ZðPRí>Cò9?÷?GêAFßCDÕAAÈ?9»<3±;/°</²<0ÊL@ÔKCÜGCá>?ë<A÷?Gý?Iù;GþDQüCQþDRÿDUý@Vü<Uÿ<ZÿBdÿFkÿAjú;gõ:gö<mùCsýIyÿKzÿMyÿMtøPsðOpçMiêMjüYxÿgˆÿ_ƒõGlöWwè_yÃ^n½{‡a^xp§‘„»§œÑÄ»ÝÖÐÝÜÚÜÛàÞÜçâÛëæ×ìëØîñÙñöÝó÷âó÷çôõëôôîòùôñùôðùñïöíîóèìîãééÞäæÝâÖÐÔÍÉÊ»º¸£¥ ‹‰yƒxr}op{k}†sxlv}k0./1/0320542653762873872:94;:5==5??7AA7CC9DD8EE;HH>HH>HH<JH9KJ6LK6MM5NM8KJ8KI=KJEPPRWU`YXjVTjRO`OIKMD?H?:F=8G>9H?:F=8B;5D:8A96?74?74@85@85=85;62=4/=4/=52<74=96>:9=<:>=9B?:D?9G@6J@4M@0O@-P?+L@*B?,B?.F<0H:1J65I56F35@65@<9:=6;>5@@4E<-J9)^B4€L>®QB«;- 2#¡6&£:'ž7$ž9%£>*¡;%§<(®?+³@-¶=,·9*¸6(µ7)ª9)¥:*¤9)¥;+¨>.«A1«B/«@.®?,«:(±<+¿E6ÆH9Á?1¼8+À6)Ë7+Ï7,Ð9.Ñ;-Ô</Ø:.à8/ç4-î1-ô.+ô.+ñ0+ê4)á7(×:'Õ:$Þ<'Ù6#Õ8%Ô=*Ì;*Á5&Ä:/ÏF<ÑF?ÚIDéOMóPQôGIð;@õ:AøCHåBEÚDCÒBAÅ>8·;1®:-ª<-«=.¶E5ÆL?ÔNEÜGCã?>ï?Bú?Fþ@JøCJ÷CLúDPÿFTýBSù>Sü>XÿBaÿFhÿCiü?iù@jùBpüFvþJzÿK{ÿJzÿHtõKpñQsõ\zù_{ûXwüNqýEkúDlïEiÚKgÈ_p±nukLI~t°–‰Ì²¥äÓÉìãÜëçæêéîêçòíâóöãùûáúÿâýÿçÿÿìÿÿòþÿöþÿùûþú÷ýúõü÷ôúõòøïðôëîñèíïæëæÝàÜÖØÊÆų²®ž¡šŽ“Œ‰…ˆƒ•‡‰€†Œ~/.,0/-10.21/43/540762761:94::2<<4>>6@@6BB8CC9DD:IF=IG;JH;LI8MJ7NL7NL7OL9KI:NKBTRS_^fihxmm…lkŠkhƒ`YiYQ\OGRH@KH>GG=EE<A@:<C:;?:7<74<73=82>93>:1=9083-94.;60<92=:3>;4?<5@<3E>4G?4I?3L@0O?0P?/P?-L?,<;&:=(?<+A;-B71A62>42;30=84B;5H94N2.Y,)l/.‡;= FE£;0¢2$Ÿ1 ¤9'¥<)ž7$ž8"¢<&¤;&ª=)±A-¶A/¹>.»<-¾:-¾</²<.¬<.©9+©9+«<+­>-­>-­<,²?-°8(¹>.ËL=ËG:¼6*º2&É9.Ð6,Ô6+Ô8,Ö:.Ø:/Ü8.ã6/è3,ð0-ô.+ô.+ð1)ç2'Ý6&Ô8"Ò7!Ý8$Ü5#Ú9'Ú>/Ó=.È6)Å9,ËA7É<5Í>8ÙEAåKIêJJéCCì?AëCCÞCAÕD?ÍB=Â=4¶:.®8*«:*¬;+¬8)ÀD8ÔNEÝIEà@@è>A÷BIÿHPôAGô@IùCOýGTþEUú@Uû?XþA_ÿDfÿCiÿCkÿFpÿIwÿK{ÿL|ÿJzÿFxÿHwÿKtúNtÿZ|ÿa€ýUvñCdûIkÿTvÜ?\ÍI`Ø{…¸||aC;|n®ŽÕ³§óÛÑûìåúòðù÷ú÷ôýôìûúëÿÿêÿÿìÿÿîÿÿóÿÿ÷ÿÿûÿÿýüÿþûÿýùÿúøÿøöÿõöýóôûðôùðóðçêåßáÓÏξ½¹­®¨¢¥ž£™£—£«žž¦—›£–/.,/.,0/-10,21-32.54/650880991;;3==5??5AA7BB8CC9HE<JF;KH9MJ9PK8OM8QL8OL;LJ>QPL^]bmlzzz’‚¡€‚¨€€¦{u—rkŠe_{YSmTLdMEZG@PB<HD?F@;?<87;63;60<8/=9.;8/:70991;;3>;2?=1@<1?;/A;-F?/H?.K?/M@/O?0O?/P>0L?/@=*?>,@=.?;/?;2>93=:5:94<94D95M51V-+j)-„28ž8C«>C¦7.¥7(¥:(ª?-¨?, 9&¡8#¤;&¥:&«<)³>,¹@/½>/Á=0Å=1Ä>2½?3¶=2²9.®8*¯9+°:,²:,³9*¶;,¹;-ÄB4ËE9È>3¾1'À2(Í9/Ö5+Ø4*Ú6-Û7-Þ7.á6,æ3,ì1*ó0,ô.+ô/)ï0(ä2&Û4$Ò6 Ð5×2Ú0Ú4$Ü<.Ö=/Ð9.Í=2ÒD:Ä5-Å60Ê;5ØD@âMIåKIÞC?Ö=8Ó@9ÎA8Ç>4¾</µ:+°8*¯9+±;-¬4&½?3ÑH@ÙHCÝB@å@DóFLýNUð?Eð?EöBMþHUþHWüBWú@XüA^ÿAcÿCfÿFmÿIrÿMyÿO|ÿL{ÿIyÿDvÿN~ÿR}þKrøMoÿVuÿUu÷MjðFcïKfÒ>VádtþŸ§ÓŽd92lJ>¥ynÐ¥œõÔËÿéãÿôòÿýÿÿüÿùóÿüïÿÿïÿÿðÿÿòÿÿôÿÿ÷ÿÿûýÿüûÿýúÿüøÿúøÿù÷ÿö÷ÿõöÿôøþó÷öëïìãäÚÕÒÉÆÁº¹´³´¬±´©°¶ª¶¾³°¸­¬´©10,10,0/+0/+10,21,43.54/77/880991;;3==3??5AA7DA8IE:LF:NH:PJ:RK9RM:SL:QK=OJDVTUfdqwx††¨¹“ÃÆŽ‹À‡ƒ¶{w©pmšid_[~UPnNJaKGXEBM?=B;7895296/85,85,671783891;;1></?<+B=*C<)G?,J?+K@,LA/M@0M?2L@4K>5L?6K>5F<3B92=82:946;47<59<5>:1H4-Y2-w78”?D¦>E§9<ª;0ª<+«@.¬A/©@-¥<)¦;'«>*§8$­:'µ<+¼=.Á<-Å;.Ê<0Ê=3Å?6¾>3º:/·7,·7,¸8+º8+º8+»7+ÇA5ÍC8Å8.Á1&Æ3)Ë7-Ï5+Û4+ß3)á4-â6,ã5,ç5+ì1*ð/*õ/,ö/*ò/)ì0'â2#Ù4!Ð5Ï4Ö1Ù/Ø2"Ø6)×9-Ô:0Ö?6ØE=ÖF>Í@7É<5Ë@9ÖKDÙNGÒE>Å;1Å=1Â<0À</º;,·9*´:+¶<-¹?0µ9-¿<2É@:ÒC?ÛCBãDHíJOöOVì?Eí>CòBLûIUÿJYýFXúCYûC]û@_ûBdÿElÿJsÿNzÿO|ÿLyþIvÿJwÿTÿT}ýJqùImÿStÿVtøNiëD^äF]êXkÿ–¢ÿµ¼óžŽHFk.)¦kcÍ–óÈÁÿãÞÿñðÿüýÿýÿü÷þþöÿÿöÿÿ÷ÿÿøÿÿúÿÿüÿÿýýÿýúÿüøÿûøÿùøÿø÷ÿöøÿõ÷ÿôøÿôøýñóóéêäÜÚÖÑÍÍÊÃÉÉ¿ÊÊÀÊÍÂÉÐȾȿ·Á¸65143.32.10+10+21,32-43.66.77/880::2<<2>>4@@6B@4JD8ME8OH8RK;TK:TM;SL<RKAQLIZW^li|~Œ¸–™Ì™žØšžÞ™˜Ú”‘ÔŒŠÉ†…¿~µxv§nk–fe‡]ZwVTiMKYDAJ><?;:8;74762555457664872<:.?<+C>*F@*G?*H@+IA,IB0IA4HB6HA9G@:K=<I;;C9:=77875384/83/917<574+C1']80~C=–GCž>?ž51§;/ª<+ª?-ª?-©>,©>*¬?+°?-«8%²9(¹:+¿9-Ã9,È:.Í:0Ï<4Ë>5Æ;4Â91Á8.Á8.Â9/Â8.Ã6,Å7-ÑC9Ð@7Ã0&Ã,#Ð7/Ö<4Ó2*à3,ä2(ç2+è3*ê3+í2+ð/*ó-*ö/*õ.)ñ0)ì1(á4$Ú5"Ñ6 Ð5Ø7#Ù3#Ö3$Õ3&Õ5)Ô8,Ô<1Ö?6ìYQáRJÓHAÌC;ÊE<ÌI?ÈE;À>1¼:*¼;(¼:*¹:)·:(¸:+¼>0¿A3ÁA6Á<3Å<6ÏA=×EEÝGHäIMëLPèBFê@CðCIùKTþNYüJZüF\üF_øA_ùBaýEiÿJpÿNwÿOzÿNxÿLvÿQ{ÿRyþOvýOtÿVyÿ]{ÿSoîD^úTlîQd÷dtÿ’žÿ‹•ôƒ‰Ç`d§MMµjgΊXÿÞÙÿïîÿúùÿüþýûÿýúÿþûÿÿûÿÿûÿÿüÿÿûýÿûûÿûøÿûøÿüùÿûúÿûúÿøúÿöøÿó÷ýñóùíïòææçÝÛßØÒÛ×ÎÝÛÏàÞÒßáÖÜãÛÏØÓÅÎÉ<94;8185052+41*41*52+63,74-85.96/;81=:1?<3A>5C?4JB7MC7PG8SJ;WK;UL=UK?SJCSJK]Wcnl‚‚‚¦‘•Åš ÚŸ¦ê¢©ñ¡¥ïž ë™›å•˜Ý”•Ö‘ʉ‰½‚ƒ±zy¡rq‘fc~XVkPN\IGRFCLCBJ??K==G;:@;9:<94?;/C=-E@,F@*FA+EB/EC4CC7BC;AC>@ACB<FD>LCANEEOCHNAIL>HI>GDCHAA=2L:.gF7ƒOAI<Ž?2”8)¥>/©>,«>*¬?+­@,¯@-°?-±<*±8'¶8)½9*Ã9,È8-Ì8.Ò91Ô;5Ï:4Í:3Ë81Ë81Ì92Í:2Ì70Ë6/Ó<5Õ>5Ò91Ì2(Î4*Ø;2Ú<3×3*ä1*é1'ì1*î2)ð1)ò/)ô-(ö,(÷-)ô/)ð1)é3(â5'Ù6%Ò7!Ï7 Ô9%Õ8%Ó6%Ñ4%Ò4(Ó7+Ò8.Ð7/ãNGèXPçZSÚQIÌG>ÄD9¿@7¹>/¹<*¹<&¹<(¹:'¸9(º;*À>0ÃA4ÉD;Æ@7É@:ÑFCÕIHÖHGØGJßIKæFHèBDíDIõLSüQZûO]ûL]ûK`öD^÷DaùFfýIlÿNuÿOxÿOxÿPw÷VxóUvôTvüYxÿ]{ÿZwûQkòI`ýWköUgêM^ö^mâKZæTaåTa×XaÁefȃ~嫧ÿÔÑÿëèÿóòÿøùÿþÿûüÿûüÿûüÿüüþüúýýùúý÷÷ü÷ôÿú÷ÿûøÿüûÿûúÿøøÿóóýîñúëîðáäéÝÝäÙÕâÙÒæßÕëçÛñíáññåìóìÛæâÎÙÕ@=6=:3:7074-52+52+52+63,74-74-96/;81=:1?<3@=4B>3JB7MC7RF8VJ:WK;XL<WK?TICSJM^Xfpmˆ‚„«‘—Ëœ¤ã£­õ§°ýª±ÿ¦¬ú¡§ó ¤îŸ£ê¡á™›Ö•–Ì’’ĉˆ´yyŸji‹`]|XUpRPhOMeNJcKG^FBS@=H?:>?:6@<1A>/C@/CB0BC3BD7?D=>D@<ED;BJ>CVCIaLRhU\o\br`dobbjd`afXWaJDlG>ƒSE•XF“J7‘@+™>+¨A.®@/®A-¯@-°A.´A/²=+°7&µ7(¼8+Â8+È8-Í6-Ñ7/Õ81Ø;4Ò72Ð72Ð72Ñ82Ô94Ô94Ó83Ó6/ÞA:Õ8/Ñ3*Ö8/Þ=5Þ=5Ú91Ü5,ç2)ë0'ï0(ñ0)ô/)÷-)÷,(÷,(÷-)ô/)î2)é4)á5'Ú7&Ô7$Ï8#Î7"Ï8%Î7$Ï6&Ð7)Ò9+Ñ7+Î4*Ë4+ãNGód\ë`YÕOFÄA7º;2µ9-¸>)¸>'¹<&¹;%º9&½9*À</Ä>2ÊD9ÊA9ÎE?ÕKHÓLIÎGDÍEEÖHGãIIæCDèEHòMSøSZùR\÷O^úOaôH^õG`øGdûIiÿNrÿQxÿRyÿTzðUuïZwù^}ý^|ùUpõMgùOiÿYnÿ[mÿctðM^÷TeôO`üUgõL_äR_À^_»vqל˜úÉÅÿåãÿíëÿôôÿÿýüÿÿûÿÿûÿÿûÿþýýýýüúþùöýøõÿøõÿùöÿúùÿúùÿööüððøéìôææëÝÝèÚÙäÙÓèÞÕðèÝùóåÿúìþþòôúöáëêÓÝÜB>5A=4@<3>:1<8/:6-84+73*62)62)73*84+;60>93A<6E>6I@7MC7RF8UI9WJ:XK;ZLA[NHTIMXR`gd|~§’Ê— ãžªö¥°ÿª³ÿ«²ÿ¬´ÿ¬²üª±ùª¯ó¨¬ì©«èŸ¡Ú™šÒÁ±tr¡he’]Z‡XR~XQzYQvVOnMH_C?M?:@?;:B?8>>4@B5BE:?D=<B>:CB>FH?LUDVnQfƒarŽisŽtvx„o€€^lŠ[c’WYžWU¦VM¦M?Ÿ@.œ9$ :$§<(¬=*®?,±@.³@-´?-¶=,¹;,º6'¾6(Ä6*Ë7-Ñ7-Ö8/Ú91Ú83×84Ø95Ù:6Ø93Ø61Ø61Ú83Ý:3æC<à=6Ú70ã@7þ[Rÿlcÿ]Tç?6å3)í2)ñ2*ô/)ô*&õ(%ø(&ø+(÷-+ó0,í2+ã1'Ú0#Ô1"Ô4$Ó:(Ì;&Ê<(Í<)Ï<*Ò:,Ò9+Ò8,Ñ7-Ú@8Ï81ÜGAôc^åXQËB<Å@;·4*·:&¶<$¹<&º<&½:(¿:+Ä<0Æ>2Ç>4ÌC;ÙPJßXRÔOJÃ?:À?:ÏHDÞHGâGEæJKïPTñRWïNVíKXñL\ùRføOf÷Ke÷JfüMlÿQsÿRvýRvôWvîXuõXuû[wÿ\xÿZsÿWnÿVkÿbuýVgÿ\mÿ[lñFXôDXÿQeîVeÆfgªieʋ網øÒÏÿëçýïîüø÷ùýüùÿÿùÿÿúÿþüþýÿþüÿûøþöôþùõÿû÷ÿùöÿöôÿööÿõõöèèæØØãÕÔÞÐÍáÖÐñçÞüôçþøèÿüéÿÿóúÿùèñðØáàC?6B>3A=4?;2=90;7.:6-95,73*73*73*84+:5/=82@;5D=5H?6LB6QE7TH8VI8WJ9YK>ZMEUJNWQ_eby{¤ŠÉ”⛦õ£®þ«´ÿ¬µÿ®¶ÿ¯·ÿ°·ÿ¯¶ü¯´ø¯²õ©ªë¢£ã˜˜ØŒŠÉ€»uq®ie c^˜`WŽ`Vˆ]TVPtMIbFCTBAIAAC<<:AB=FEACD?A?@DBEOMRWVdch†quš}¢‡}Ÿ’}œ {š¥pŠ¤`u¬Yi¶VaºQU¸HF°@4¬;)¬<&­='¨8$¨7%«8&®9(°8'²9(µ7(¸6(»3%Â4(Ê7-Ó:2Ù<3Þ=5ß<5ß<5á>9ß<7ß<7á>9åB=èC=çB<æA;å@:æB9ä@7Ü8/Õ1(Ø6+ëI>ÿ[Oñ@6î6,ê+#î)#ø-)ÿ/-þ,+ö((ú0.õ4/ï61ç6.Þ3)Ö2&Ô2%Ï6&Ë:'È;'Ê<(Í:(Ï9*Ð7)Ñ5)Ð4(Ó9/Ï5-Ô=6åPJåTQÛLHÍB?·1(¼;(»=&½<'¾;'¾9(À8(Â:,Ä:/È>4ËB8ÕOFÜWPÒRIÃD=¿C;ÌHCÛLHßJFåMLëSRíTWëPVéNVìOZ÷VføUhùSiúRküRmÿSqÿStÿRuòMmøUtÿ\zÿ_{ÿ\vÿUoûRiûReüUf÷RbÿZhÿZhóN^øScþYißS^¶`_—^W¶}Ù©¥îÈÅýáÞùééýøõúüùùÿÿøÿÿ÷ÿýúþýÿÿýÿûûÿ÷õýøôþùõþùõþöóÿ÷öÿõõöêêêÜÛäÖÓÝÐÊßÕÌïåÛüõåÿùçÿýéÿÿïúÿøèñîÛáßEA6EA5C?4A=2?;0=9.<8-;7,84+84+73*84+:5/=82?:4C<4G>5JB7ND8RF6TG6WG7YI<YJCWKKXP]b^ysuœ…ŠÂ™Ü˜¤ð «úª³þ­µþ¯·ÿ±¹ÿ³ºÿ´»ÿµ¹ÿµ¹ÿ¯²ù¬¬ô££ë™—àŽÓƒ€Çxu¼sm³k`¢i]›cZ‘]W…VRwNKhEDV@@L85<A<@KABL@BQ>BZEJpW]‚cs˜r—¥z§®«²{£¸vœÀp“ÂcƒÀTnÂG\ÐK\ÑHPÄ<<º5.¸9*µ<'°<%°;'°;)²:)´;*·<,»<-À<-Ä<.Ä6*Ë8.Ó<1Ù?5ßA8á>7à<3ß:4Ü71Ù4.Ø3-Þ93æA;ìE?éB<ä=5ã<4Û4,Ø4+Û7-Ô2'Ì, Ö6*éG:õK>ò@6ï4-ð-)ö,*ú,,ü,,ø*,ð*)ì/+è2.ã5.Ü4+Ø1(Õ1'Ñ3'Í7(Ë:)Í:(Ð:)Ò;*Ó:*Ô8+Ô8,Ñ4+Ö<4Õ:5Õ<7åONíYWÙEEÂ3/À;,¿<(À;*¿:)Á9)À8(À8*À8,Æ=3ÇA6ÏJAØUKÒRGÆG>ÀD:ÇG>ÖKDØICÝLIäSPåSTâPSâMSåNWòXdöZhý[pþZrüVnúRlûQlÿQpýGmÿPvÿZ}ÿ[{ÿVrýTkþWkÿ\mûYhøZhûamö`kí]gïfnìfmÉ\_ ]W{OF˜mfÁ˜”ݺ¶ðÔÑôàßÿõôùù÷ùÿýùÿÿ÷ÿýúþýÿÿÿÿýÿÿøùüùôüùòüùôýøôÿùöÿøöùîìïäâåÚÖÛÐÊÜÒÈîäØýöäÿûèÿýçÿÿíúýôêðìÞãßIE9HD8FB6D@4B>3@<1?;0>:/:6-95,95,95,:5/<71>93@;5E>6I@7LD7OF5RE4UE5WG8WI>XMKXNW^Zqpp–€†ºŒ–Ô•¡é©õ¨°ù©±ù­³ý°·ÿ±·ÿ²¹ÿ´¸ÿ´¸ÿ¯±ü«­ø¥§òŸžê—–⌋ׂÍ}yÅum¶pgªf`ž_ZYX„QQuGGcA>Q=5DH8BT=C`@EnCJ€OU–]d«fx¿kÉnšËo˜ÉiÈ`ƒÉWxÆIgÄ;UÆ2HÙAPàEMÑ;<Æ71Ä?0½@,±:$­6"­5$®5$³5&¶7(¼8+Ã;-È</Ó@6×@7Ú@6Ü?6Þ=5Þ93Ý6.Û4,Ó.(Ò-'Ó.(Ø3-á:4ã<4á81Ü5-Ü5-Ð,"Í)Ù7,âB6Ü?0Ð4%Ë.éF7óI<ýH?ü<7ô-*ñ#%õ')û/2ñ-.ë/.ä2.Þ3,Ú1*×0*Ö/)Ó0'Ó7*Ò9+Ö:+Ø<-Ú>/Û?0Ü@3Ü@3Ò6*æLBåJEÎ50ßGFóZ\ßIKÕA?È</Ä;+Ã:*Ã:*Â:*Â:*Á9+¾9*Â<0Â>2ÈF9ÏOBÎPDÆH<¿C7ÁC7ÎH?ÑF?ÕJEÛPKÝROÙMLÚKMÜKPëX`ó[güaqÿauúXmõOgõMgÿNkÿOuÿRyÿUwÿTqüTmý[pÿcuÿjxÿguúboõamídlãflÙhjÎghµfa^TeG<^U©ˆÍ«©åÇÅïÚÙÿóóùøöùýüøÿÿ÷ÿÿûÿÿÿþÿÿüÿÿùúþûöûûóûúõþûöÿýùÿûøþôòøíéêßÙÞÔËÝÓÉíæÖÿøåÿþèÿÿæÿÿëøúïêïèãæßNH:MG9LF8JD6HB6F@4D=3C<2?80>7/=6.=6.=60>71@93?:4C>8EA8KC8NE6QD3RE2VF6VH;ZMGVMR[Whlk‹~‚±Š“Î’Ÿãš¦î¦®÷©¯ù­°ý¯³ý°³ÿ°´þ°³ÿ¯³ý«®û¨«ø¤§ô ¢ïšœé“•âŠŒÙ†…уÉ{u»mk¬dež_a’Y[„RQsNHbWFY_CQmBLEM˜MT¬U]¼[bÊZhÙRpâRußTuÙPlÒJbÍBWÇ8JÅ.?Ô8EãCKæGKØ>>Í>6ÌF:ÃH6¶?+·>-¸=-º<-½>/Ã?0ÊB4ÐD7ÕE:ÙE9ÚB7Û>5Ù;2Ù6-Ù5,Ú3+×3*Ô1*×4-Ú7.Ý90ß80Þ7/Ý6.Ü5,Ö/&Ù5+×7+Ï2#Í1"Ò9)Õ>-Ô;)Ø<-æD7øJAýD?ù64õ*-ö*-÷/2÷67í55â30Ú1,×0*×/,Ù0-Ù0+ã81ä91â:1â;2à<0ß=0Ü?0Ú>1Ñ7+ïWLøaZÓ;6Ñ;:æPQÜFHæPQÏ<4Ê:/Æ8,Å9,Ä:-Ä<.Ã;-À;,¾:-¾<.ÃA3ÇH9ÇI;ÂD6¾@2¼>0ÉF<ÊE<ÎIBÕPI×RMÕNKÓKK×LOéZ`ð^hüfrÿhxü]qòPeóMeýNkÿVzÿTxÿRrøTmö]qûhxûjw÷erüetó_mì`kêkrßruÃjfªd\žla}fVXH9kXJ”|r½ œÞÂÁîÖÖþîïûõõûûûûÿÿùÿÿýþÿÿþÿÿûÿÿúýÿþùûþõûüöÿþùÿÿúÿþúÿûõÿ÷òñèáäÜÑáÙÌðé×ÿúäÿÿæÿþåÿÿê÷ùëíðçèéáQK=PJ<OI;MG9KE9IC7G@6G@6B;3A:2@91?80?82?82A:4@;5B?8EB9KE9MF6PE3RE2UF3UH7YLCUKLZScii…|«‰“È“žÞš¤ë¦¬ø©¬û¬­ý­°ÿ®±ÿ®±þ­°ý­°ý©¬ù§ª÷¤¨ò¢¦ðŸ£íšžè•™ã’”Þ’’Ú‰ŠÍ|€Àsx²ou©mpigŽj_}y_x‚WjŽP_£O\»S\ÍT]ÖPWÛHRå@Qè?RäCRÜDPÖCKÏ@DÊ9<É46äJLçIJáAAÕ74Î95ÎA8ÈD8¾>1ÃE6ÃE6ÅF7ÉE8ÎF8ÑG:×G<ÚG=Ö>3Õ;1Ö8/Õ4,Ö3*Ø4+Û4,Ú6-Û81Ý<4à=4ß<3Ý90Ü5-Ü5,Û7-Ò.$Ý;0Ú=.Í1"Æ-Î7&Ò=)Í:&Ì9'Ô8)â:/ó=9ÿ@@ÿ<>þ37ó,/ê,.á+*Ø*)Ô+(×/,Þ44ä88é99ï75ð74î73è71ã7-Ü6*×5(Ñ5&Í5(çPEÿmcÛHAÈ42×CCÓ>BêVVÙA<Ò>4Ë8.Æ6+Æ:-Æ</Ã>/Á=.»9+»<-¾?0ÀA2¿A2½@.¼=.»<-ÃC6ÄD9ÈH?ÐPGÓRLÐOIÒNLÕONé^cîagûitÿnzÿduõVjõQiÿUoÿWvÿVtúXpñ^pônyöw€íksÞXañ`mñ`mìboïs}숊Ì|ªth™yjskXON:_VGƒqg¯–’ÜÀ¿ïÕØüéëþôõýûüýþÿúþÿþýÿÿýÿÿûÿÿùþÿÿûùÿõúýöÿÿúÿÿúÿÿøÿý÷ÿþöùðçíåÚéáÔôïÜÿûåÿþåÿÿãÿÿèùúêòôçîðåSM=RL<QK=OI;MG9KE7JD8IC7E>4D=3B;3A:2@93A:4A96@;7A@;CC;IE9MG7OG2RF0UF1UH5WK?RHFXR^jg‚}§‰“Ä’žÚ˜¢ç¤§ö¦§ú«©ü«¬þ¬­ÿ­®þ¬®û¬®û¨¬ö¦ªô¤¨ñ£§ð¡¨î §í¤è›¢æ™žâ’šÛ‹“҆ʃ„ˆ·ƒ¨ˆyšmˆšcz©Xi»Q_ÏMYàIRèAIê=Aé=;ã>8ÝC9ÖH:ÎI8ÅF3ÂC0Å@1äTIàG?Ú;7Ø64Ø88Ø:;Ñ98È74À:/¾<.¿;.À:.Â8+Ã7*Æ4'Æ2&Î4*Ð3*Ñ3(Ô3)Ö3*Ù5,Ý6.Ü8/Þ=3Û=2Ü;1Û8/Û7-Ú6,Ø4*×3)Õ3&Ó3%Ð4%Ï6&Ò=)Ñ>*Ê9$À2È:&Ê4%Õ1'ç51ú<<ÿ=@ÿ7=÷37ê-1á-.Ù--×/.Ü43ã9:é;=ð9=ö26ø03ô01í1/ä1*Þ2(Õ1%Ï3$É1$Ñ=1új_äTLÈ95Ð@?Ë:=äRSäJHÜC=Ñ:3É6.Æ8.Å;0Â<0¿=/º;*¼?-½@.¼?-º;*º;(½;+¾<,¼>0¼>2ÀD8ÈLBÌPHÊNFÌLIÐNLèaeìaføhrÿo{ÿhx÷Zk÷UjþZrÿYsû]tîaræjtë}€ñ‡‰èwyÚ_dñgtþm|óeuëlwö‘•ì¢ŸÄ”Š¢|hjUHQ<WUFth\¤ŒˆÛ¿¾òÕÙùãæÿóöÿúüþþÿüýÿþûÿÿüÿÿúÿÿøýÿÿûùÿõ÷ýóýÿ÷ÿÿøÿýöÿý÷ÿÿöÿùïõïáòìÜúõáÿýçÿýäÿþâÿÿçýþìùúì÷÷ëVO?TN>SK>PJ<OG:LF8LD7JD8H@5F@4E<3B;1B92A:2C:3A<6C@9DD:JF:MG7OG2QF0TH2UH5UI=QGEWQ]jh€~‚¨Œ”Å“Ø™¢çŸ ò£ ÷¦£ú§§ý¨ªÿ©«þ©¬ý§­û¥«õ£ªò£§î¢§ë¤©í¦©î¥¨í£§î˜ è”¡å”Ÿß”Ÿ×•›Íš•¾ Œ¯ªƒ ¥e´]pÂQcÐIWÜBNå>Fë<Aì<<ê?8ãA4ÜE4ÔI4ÊI3ÂF.¿B,Â?-âSEÞD<Ý97æ>>êDFå@DÙ8=Î65ÍB;ÈD8ÉC8ÇA5Æ>2Ã9,Â6)Â2'Æ2(Ë3(Î4*Ó5,Ø5.Ù4.Û4.Û4,äB7Ý=1Ù7,Ú6,ß9-ß9-Û5)Õ1%Ø8(Í2 Ê1!Ï9(Î8'Ç4"Ê7%ÔC0È7&Î8*×6,Þ5.æ3/î53ö:9û?>ÿLKúHFòBBì>=ê<=ç7:å26æ,1ô+1ø)/ô,.ï/.è1+à4*Ù5)Ó7(Æ0!À.ôdYë]SÏ@:ÔDCË;;ßMMêRQàHEÓ;6É6/Æ8.Æ9/Ã;-¾:+¾<,Á@-ÀA.»<)·8%¸9&½<)¾?.µ7)³7+¸>1ÁG:ÅK@ÅIAÈIBËJEåa_æ^`ñdjþnwþkuó]iðXeø]mú^sõbtèdoãlrì~ù‹ö†…éqsøryÿy†õaqàXföˆ‘ÿ´´Ð®¢ ›‡]jPDR9QT?jbU›ˆÚÀ¿òØÙöàãÿô÷ÿúýÿþÿýüÿþüÿÿýÿÿûÿþùýûÿúôÿòòþòùÿôüÿöûýòÿýôÿÿôÿÿóûõç÷ñáþùåÿýçþüãþýáÿÿêÿÿñýýóüüòXO@WP@WN?UN>TK<RK;RI:PH;MC7KC6KA5H@3H>2G?2H>2F@4GC:GE9JG8NI6RJ5UJ4UJ4VK9XNDULMZTbkhƒ{~§‰Ã‘™×˜Ÿç£¤ö¥¢ù¢¢ü¡¢ü ¤ÿ¢¨ÿ£«ÿ¤­ü¡ªõ¡©ñ¡¨ì¥¨íª©ï­©ó±©ö¬©ø ©ø—§ò’¡â–ŸÖ¦£Î¶ž¾¹…œ¸gz¿L]ÑERß>Mç;Gç<Dæ=Bà@@à@@â>?ß<=ÝEBÉ:4ÊD9ÊF:º1)ÛJEèJIéCEë>Bè;?æ<?á=>Ú<=Ô<;ÏB;È?7ÊD;ÑMAÍK>¿=0¸6)½9,¿7+Ã6,È5-Ë2*Ñ/*Ô/+Û2/Þ5.çA5ß9+Ù1$Ý3&å9+ç;-ß8&Õ2Ï2Ì4Ê5!Ç4"Ç4"Ê4%Í5'Ï5)Ê0&Ï2)Ñ5)Ò6*Ò4(Õ3&×3'Ü4)à2)æ3.ë52ë33é/2ç-2è-4í.5ö-3ö+/ï+,ì/-ê5.ã9,Ù7(Ð4%Ã-Ä3"çXHéYNË;3ßNIÅ41ÚHHáOPÜJJÔC@Ì;6È80Æ8,Ç9+È<+Å<*Â;(¾9&¼;(»<)º=)º=+·<,´8,µ;0¹=1»?3»?3¾@4À@5ÄA7ïjaõpiêc_ômjûqqìadôgmÿpyÿlzûhxðdoébiëdjîlnîqoîqoûy{ÿ}…ùZlücwßbpû¬¯¶ªš}‘u]rQJY:LR8snZ¥•ˆË·°íÕÓÿîðÿö÷ÿ÷ûýøüüüþþÿÿþÿÿÿþÿûÿþðÿôåþéæûêïÿîøÿöüÿôþÿóÿþñÿýñÿûîÿúêÿùæÿùãÿùáÿùáüúåÿÿõÿÿûÿÿûYPAXO@XO@VM>UL=TK<SJ;RI:NE6MD5LC4KB3JA2JA2JA2IA4GC8HD9LF8OH6RJ5SK4VK7TK:XNEULMYUckhƒ{}¦†ŒÀ•Ó–šáŸžìŸñžžôžŸù¢ýŸ¦ÿ ªÿ¡¬û ¬ô «íŸ§è¢¥è¥¤ê©£í« î¤Ÿï §÷š¦ðœ¡á£œÐ±˜À¿ªÂq„ÄTbÑCOâ<Fì8Aï7?ê:=å<?Ü@AÛACà<CÚ9?ÚADË;;ÌB?Ê@=Á31åOPæCFè?Dê;Bç8?â8;Û89Õ:8Ï;7ÔE?Æ=5Å<4ÌG>ÌLAÁC5¸:,·9+¹7*¿7+Æ6-Ë4-Ñ2.Ø3/á53ä84ä>2á;-Þ6)ß5&á5'á5'Ú5"Ó4Ñ9$Ë9"Ç9%Ç:&É<+Ë<,Ì8,Î4*Õ3.Ö5-Ó7+Ï7)É6&Ç4"Ë4!Ï4"Ø2$Þ2&ã0+æ.,æ,-æ,/æ-2è.3ì+.í,-ì0.è2.à4*Û5'Õ8'Ò9'Õ?.»*ÖD5Ð>1ÙF>ëVPÚB?ØBAçUVâRRÜKHÔC>Í=5É9.È9+Ç8(È;*Ä;(¿:'½<)½>+º?-¹@/¶>.²:,´;0·=0¸>1º?0½?1ÁB3ÅC5ëi\òmdçb[ðkfõolèbañklütxÿq~ÿo|öirí]fêY`ñbf÷qpþzxûvwÿx~ùRdþ[pä]nö¦©©¦“jŒkZtON_;SZ;us\©ÖĸöáÜÿõñÿúúÿúûÿûüüüüúþýûÿþúþÿõÿúíÿóáÿéáýæéÿëóÿñøÿòýÿòÿÿñÿýïÿýíÿúéÿöåûóàúòÝüôßü÷äýúóüüúÿÿý\PB[OA[OAYM?XL>WK=VJ<VJ<SG9RF8QE7OC5OC5OC5OC5MC7JC9JF;MG9PI7SK6VK5VK7TK:WMCVMNZWbkiy{¡…‰¹‹‘Ë‘–Ö˜˜Þ™™ã˜™éšî› ô£÷Ÿ§ø ©ô ªï¨èœ¤ãœ¢àžŸàžá™ã›™â–›ßššÚ¤•Ð±ŒÀ¿‚¬Çr“ÉYqÇBS×ALß>Dã=?å==å<?ã=?á>Aá>Cà<C×8=Õ?AÏA@ÊC?Ã<8Ä96éWWàBCä>Bè;?ã9<Ü68Ö66Ñ96Ì;6ÕHAÄ;1¾5+ÅA5ÍK>ÆH:»=/·8)º6)¿7)Ç7,Ï6.×50Þ71æ95é=9×3*×5*Ú6*Ü6*Ý5*Ý7)Ú:*×>,Å4!¿4¹4!¹6"¼8)½8)½3(À-%ã@;å>8Þ=3Õ<.Ì9)È7$È7"Ì7#Ò7%Õ3$×/&Ù.'Û.*Ü.-Û//Ü./Û,)ß0+à5-Ü5,Õ3&Ð4%Ð9&Ñ>*ÔA/Â1 çSEÒ;0ÚA;ÞC?èJIðTUíUTçSQãOMÝJCÖC;Ï<2Ê8+Å6&Ä7&À7%¼7&¹:'¹<*·>-µ=,²=,°:,±;/´<.µ=/·=.¹?0¾C4ÃE7Ü\Qêg]äaYðlgöpmça`ìfgójqàR^ï^kõhqòemñ`gôeiùqqþxwútuÿnuóJ]øRhä[mñœ¡£œŠa€`UoHOd=W`Aww]­¥’áÑÂÿîæÿøñÿüøÿþúþÿúûÿüûÿýûÿýùÿûóÿöèÿîÜþãÚøÞáùáèûåïüèöýëýþìýúéÿúêÿ÷æúñàôèØòçÕöëÙøñáþúñýüøÿþû\PB\PB[OAZN@YM?YM?XL>XL>UI;TH:SG9RF8RF8RF8RF8PF:LE;KG<OI;RK9TL7WL6WL8WK;VLBUMK\V`jh~yy›‚…²ŠÂ”Γ•Ò•”Ö••Û–˜ã˜›è˜žìš¢í›£ëœ¥è˜¢ß–ŸÚ•›Õ”˜Õ“—Ö’“Ö‘Ò…„½‘ƒ¶¥}±·s¢ÈfÒVzÕGaÔ<KÞAJàBCÞCAàB?áAAä?Cç>Eç=Fã?FÖ:>Ñ?@ÑEDÆA<º61Å>:êZYÝABã@Cæ=@ã:=Û89Ó97Ð<8ÎA8ÏE;Â:.»2(À</ÇE7ÄE6½>/¸9(½8)Â9)Ë8.Ò:/Ù80à91è;7ë>8Ø1+Ù2*Ü3,Ý5,à5-ß7.Û9.Ö=/É7(Æ:)Á<+¾<,¿;.Á;0Å<6Î95ëC@í@<ä@7Ù=1Ï9*Ç9%È:&É;%Ï<(Ð7'Ï1%Ñ1%Ô1(Ö3,Ô1,Ñ/*Ó2*Ó5,Ó7+Ð7)Í5'Ê7%Ì;(Ì>*Ì;(Ï<,ô^PàF<Ò3/Ò.,æ@@ûWXÿusÿroÿjgö^YçPGÕA7Ç5(½. Ä8'À8(»8&¸;)·<,µ=-²=,°<-¯;.­<.°</°<-±;-¶>.¼B3ÀF7ËMAß`Wâa[ôpløtræ`aä]aæ]dÖHTçYeógrôgoöelükpþsvþvvûpsûenñDXôKbåXkë’– •ƒ_zYQlCSh?\gEy|_°ª”çÜÊÿõèÿùðÿûñþþôýÿ÷ûÿúùÿúöÿøòüóêûëÚøÞÎóÒÊëÌÏìÎ×îÒàðÖéóÛò÷áöõáûöãüõãõìÛíáÑéÝÍíáÑñèÙüõíü÷ñþùó\PB\PB[OA[OAZN@YM?YM?YM?WK=VJ<UI;UI;TH:UI;UI;SI=OG<OI=QI<SL:UM8XM7YL9XL<UKAUKI\U]hexut“|~¥…‡·ŒÂŽÃȒϔԒ•Ü“—à”˜ß•šÞ•Ü’›ÖŽ”ΉDž‹Å‡Ã}ƒÁ~¸}tŸŽp”¨gÁ\„ÖNvâAcè:Uê9KæAGàECÜGAÛGCßEEæAGî<Jí=JæBI×>@Ð@?ÐIE¿>8³2,ÈC<àUPÜDCáACã?@á>?Ú?=Ô@<ÑD;ÏF<Ç=2À8*½5)½9*¿=-¿=-½;+½<)Â:*Ç;*Ï;/Õ;/Û8/à8/ç83é:5æ95ä52ä20æ21ç32æ40Ý2+Ô0'Ë1'Ë7-È:0Ä7.Á4-Ã40Ì:;Ý?@ë8;í76ä71Ø5,Ì4&Æ5"Ä7#Å:%Ë=)Ë8&Ì4&Î5'Ô8,Ô:0Ó9/Ð8-Ð>1Ê;-Å8'Ä7%Æ9'Ç<)Ç<)Ç:(Í>-Í:*Ø>2áC:Ô/-ä::à24ß56×53Ö;6ÞC>åKCêQIêSHèTHäUGË?0Ä?.¾<,¸=-·>-´?.°?/®>0¬<.¬<.¬<.¬=,­<,±=.·A3ºD6ÆLAÞbXâc]ðnlôrrå`cå`eç`g÷kvûoz÷kví`hð_fýlqÿx{ÿy|úmsø^jóAWôC]èUh懣“ƒg]YtI^uIhwPˆf³²–èâÌÿúéÿýíÿþïýÿòûÿôøÿôôÿñíûêäòáØî×Åèǻ伺޺¿Þ¼ÇàÀÍâÃØæÌãëÓìîØôñÞ÷òßóêÙìàÒèÚÍêÜÏïáÖòèßñèáòéâ[OA[OA[OAZN@ZN@YM?YM?YM?XL>XL>WK=VJ<VJ<WK=WK=XL>QI>QI>SK>UL;XM9XM7YL9XM;WK?ULGYSWe^nnjƒut–~}¥„…±†‰´‡‰¹Š‹Á‹ÈŽÏŽŽÔÕӔӋʃˆÀ|¸u|²ov­io«ljœ€lˆ“cy®YvËPoãBdð7Vö4L÷8Hï@EåFBÝJCÛJEáGGèCJò=Nð>LäCI×CCÌA>ÎIDº;4°4,ÉI@ÕLFØBAÚ?=Û;;Ú<;Ø@=ÔE?ÍG<ËG:À8*Á8(À8*¾9(¼9'»8&½:(À;(Ä;)Ê=,Ò<.×;.Ü8.á6.ä6/è50é32è./é,0î02õ47õ77î45æ21Ú.*×4/Õ62Ñ32Ï/1Ò/4Û6=ê:Dí06î02æ3/Ü3,Ñ5)Ê7'È9(É<(Æ8$É6$Î6(Ó:,Ú>2ÝA5ÝA5ØA6ÓG8ÉA1Â;(À9&Ã<)Æ=*Å<*Å8&Ì:+Í7)Í0'æC<Û2/ï?Aã/2Ú*,Õ1/Ò5.Õ81Ö90Ò8.Ì5*Æ2&À1#ÎE5ÆA2¼=.µ:*±9)¯9+«:,ª:,«;/«=0¬>/¬<.«<+­<,²>/µA2ÉSGàg^àc_êjiînoæchðjqõoxúq{ýt~ömuîbködnÿrzÿx~ÿquúipøWfùAYô@[êQfá~ƒ¬—†ykmˆ]o‰ZyŠ`—r¶·˜ßÞÂùôÞüúåÿÿïûÿïõÿïïÿëçúäÜïÙÐãÍÅÞÁ°Ö­¨Ó¦ªÐ§°Ó«¶Ô®¼Ö±ÇÚºÒßÁàæÌèêÔòíÚòéØîâÔìÛÑêÙÏìÛÓéÚÓæÙÑåØÐ[N>[N>[N>[N>ZM=ZM=ZM=ZM=YL<YL<XK;XK;XK;YL<YL<YM?SI?TJ>UL=XL<YN:ZM:ZM:YN<YM?ULEXOR_Wbd_sjfsoyy|~¤~€©‚²ƒ„¼††Æˆ‡Ë‰ˆÌ‰ˆÊˆˆÈƒƒ¿|}µvw­pq§kl¤fg l`Ž‚`q•V^³N^ÒJ^ì@X÷6Kþ3Fþ9Có?BéD@áHBÞICâFIéBJò=Nï>NÝAEÔFDÈA=ÉHB´:/±7,ÎOFÉB<Î=8Ð64Ï10Ð51Ó>8ÏE;ÈF9ÃD5¾9(Ã:(Â:*¿:'½8%¾9&¿:'Á:&Ä9&Ë:)Ó:,Ù9+Ý7+á5+ä3+ê3-ë*+ñ*-ø-3ü/4ÿ17ÿ37ÿ38ý58ù8;õ8<ò9>ò9Aô9D÷7Fû6Hþ5Eô+5ô-2í12ã4/Ù6-Ð8*Î;+Î=,Ë5&Ñ8*Ù;/ß=2â>4ä=4å>5ßA6ÙJ<ÏG7ÉA1Ä<,Â9)Â9'Æ8*È9)É2'Ó9/á@8ëD>Þ0/à..ë46è66Ø3-Õ7.×90×:1Õ;1Ò;0Í:0É;/ÖL?ÌF:¿@1´9*°6)¬6(«7*©8*¬;-­=/®>0­=/«<+«<+­<.±=0ÀKAÚdZÞc^èkiðosêinõrzüvîfp÷oyùryöjsþlvÿwÿv}ùhoùcn÷RbþB[÷<YêKaÝt{µš‰žz}˜k{—f‚•h‘u¬²ŽËÌ­ààÄéìÑîöÞåóÙÙíÑÏæÉÅÞÀ¼Õ·´Ë®ªÈ¤šÁ”–™Ó¡È™§Ë¬Ë¡µÎ§ÁÒ°ÎÙ»ÙÝÄåãÎìåÕïáÖëÚÒçÒÍãÐÊàÏÈÚÌÃÕǾ[N>ZM=ZM=ZM=ZM=ZM=YL<YL<ZM=YL<YL<XK;XK;YL<ZM=ZL?VJ>VJ>XL>YN<ZM<ZM:ZM:ZM<[OAWMDWML\RZ^Xfb]qjfspwv–xxœ|{§~µ„¿…‚ň‚ȉ„Ç‚}¿y·zu­xq§vo¥tk¢pg za‹„QZ™HE³EHÓHOîBNø:Fþ6Aþ9@õ==îA=æD?ãEBäCHèAKî=Më?MØ?BÑGDÅA<ÃG?²9.³:/ÑUKÃ>5É83Ç/,Å*&È/*Í:3ÌC9ÃC6¼>/À;*Ä;)Ã<)Á:'Â;(Ã<)Â;'Ã8%Ä5$Ê7%Ô8)Ù7*Ý5*á3*å3)ê2*õ33ÿ58ÿ8<ÿ4:ÿ-2ÿ(-ÿ(-þ+1ü-3ö)0ò&1÷)6ÿ,?ÿ*Aü 9ó/ö"0ó(.í,1æ0/Ú1,Ô3+Ñ5)Ð6*Ô6+Ý90æ=6ë>7ë:4è71è50â92ßH=×K<ÓE7Ë=/Ä6(Â3%È6)Ï8-Î4*Ô6-æC<ß82à21Ò  ì89å63Õ1(Ò4)Ò4)Ï5)Î6+Ê6*È5+Å7+ãYNØRFÊH;¾@2·;/µ;.µ<1µ<1¯9-±;/±=0±=.¯;,­9*­9,®:-±<2ÐYQÜb]ìppôvyîmrõq|ör}ÿzƒÿ‰ÿy€ôhq÷epÿq|ÿuÿktø^jöOaÿA^÷8WéG^Ûntº‹™©„‚žn~šg—hœq¡©‚´º–ÈÍ­ÕܽÌÙ»ÁÔ´²Ì©§ÂŸž¼˜š¶–²Œ±†‰³†µ~¹„•À‹œÂŸÄ‘¨Å™²È¡¾ÎªÉÒµÚÙÄæßÍêÞÒéÖÏáÌÉÛÆÃÔÁ»Î½µÈ·°ZM=ZM=ZM=ZM=ZM=ZM=ZM=ZM=XK;XK;XK;YL<YL<ZM=ZM=ZM=ZL?ZL?ZM=[N=]M=^O<\O<\O>YM=WMCWLHXNOZPX]Ub`Yia\rjfokŽsp›vq§{u³ƒ~ÁŠƒÉŠƒÇˆÀŠ~¼ˆy²ƒr¨†r§t«‡l£†\„HL¤B9¶A:ÍC@Þ@?è:;ô<>ÿDE÷><ó=<ì>=ê@AëBIêCMëANãALÐ>>Å@9¾?6·>3°</²<0ÀD8ÒMDÇ61Ô;6Í2.Ì3.ÖC;Ç?3µ6'»>,º7%Á:'Ã<)Â;(À9%Â9&Å:'Ç:(Ë:'Ð9(Ö6(Ø2$Ú."à/%ì7.õ=5õ82÷40÷0-ø**û&(û%'û%'û%'û&*÷!)ø".ÿ'7ÿ(>ÿ!;ÿ6ý1ÿ'8ï#,æ#+ã+-Ý--Ô+(Ò-)Ü41â62è64î66ò65ò12ï./î,,å/,Ü92Ñ:1Ò91ìSK»"Ä+#ßF>È.&Í.(Þ<7èE@â:7Ø/*Ú.*â51å<7ß>4Õ<.Ï5)Ë3&Ì5*Í;.Ë;0Å8.ìbXàZOÈC:¹6,º:/¸8-±3'´6*°0%°2&®2&¯4%°6'²:*´<.³=1°:0ºC;Ö\Wìppñsvöx|ûyƒõq|ÿ|„ýw€út}üs{ÿr~ÿr}ÿmxÿgsü`nôI\ÿ>\ÿ>[äAVÔek´•€’¡zƒm}™f~”c‡—j”Ÿwž¦ ¨ƒœ¨„’£Š£|€Ÿvzšqyšo|r vƒ¤u‚­wƒ³y‹¸‘¼„“¾†—¾‡œ¾Œ¢½§¼“¯½š¾Â§Ï̹ßÓÇãÒÊÜÇÄÔ¿¼Ìº¶Á²«¹ª¥ZM=ZM=ZM=ZM=ZM=ZM=ZM=ZM=XK;XK;XK;YL<YL<ZM=ZM=ZM=ZL?ZM=\L=]M=]N;^O<\O<\O>]P@[OC[NFYNJZPQ\SX_U^_Wfd\tibƒng‘qjxp¬€y¼‡€ÄŠÄ€Á~ºt«Œn¢—q¤¡u¨šjž™W{=Aª5+±5+Â<3Ó@8Ý>8è?:óA=õ=;õ;<ô;@ñ>Bî@IèAIâ@KÚAFÇ<9½=4¶<1±;/«:,®:+½A5ÐJAÚG@ÜA=Ô63Ð51Õ@9ÍC8»9+²5#½8'Â;(Ä=*Ä=*Ä;(Æ;(Ç:(Ê9(Ï9(Ï6&Ô2%Ú2%ã5*ê8.ï80ð91ç1&ç/%ê.%ï,&ö+'û+)þ,+ÿ-.ü*-ú%+ú$.ÿ'7ÿ$;ÿ7ÿ3ü0õ0ö.9õ3<å*1Û(,Ü.0Þ02Û+.ñ:>ô7=÷4:ö26ö/4ø03ù25ô87Û2-Ú;5×82äE?äE?Î/+Ñ2.Á"Ô51á?:èE@à=8Ø3-Ù2,Ý60Ý:1Ô:.Ï9*É5'Ç5(È9+Ê<0Ê<2Æ9/ícYáXNÉ@8º4+Â91Ã:0¿6,Â9/¿5+¾4*½4*»5)¸6)µ5(´6(°6)´;0»B9ÓYTèljïqtöx|þ|„øv€þ|„üyùv~þuÿsÿp}ÿkwüdqý_nöI]ÿ>\ÿ>\éCYÓeh®Žw‰˜oz•bu’\xŽ]cŠ•k˜p‹•p…”mp‡]l‰]h‰\g^k_q•ey›izŸk¬t„±vŠ·|»}¼“¼‚–½†›»‰›µ†¡³‹­¶—¾½¨ÏÆ·ØÇ¿ÖÁ¼Ï¼¸Æ·´º¬©²¤£\L<\L<\L<\L<\L<\L<\L<\L<ZJ:ZJ:ZJ:[K;[K;\L<\L<\L<\L=\L=]K=]M=]N;^O<^O<\O<_RA]QA\PD[NFZOK[PN]QS]RZ_SgcXvh]…kb‘sk¤vµ†}¾‰}½—…Á™€·›v©žpž®u¢ºv¥µi˜±Rt¸<D¾5-º4+À<0ËC7ÓE9ÛC8ä?9ñ=<÷:>ù:Aö=Eï@GæAHÙ@EÏ@BÀ;6¸90±9+­9*©8(­9*»?3ÏF<ÙD>×96Ù74Ú;7ÜC=ÝOEÎH<µ3#¾9(Á:'Ä;)Æ=+É=,È<+É:)Ì9)Ï7)Ï2#Ó/#ß4*î=3ô?6ñ91é4+à4&ß5&â2%å/$ë,$î+%ñ+(ò+(ó+-ð(+ò&/ø(6þ%:ý7ü3ø3ô%7ò0;ó5Aò8Cð;Dð<Eé5>ä,6ð2<ó/;ö.9ö-7õ.3õ.1ó/1í42Ü0,â=9Ú64ß;9ÿmjåCAÑ/-Î/,Ù:7ß@<àA;Û<6Õ7.Ö5-Ö5+Ò6*È6'Ã6%Â6'Ä8)Å;.Æ<1Æ<1Å;1ë`YáTMÉ<3¿0(Ë;3Ñ>6Î;3Ò=6Ø=8×<7Ô=6Ï<2É;1Ã9.¼6*¶6)¹;/¼@6ÏTMãgeíorøz~ÿˆû|ƒû|ƒûyûx€ÿwÿsÿn}ÿgvû`pü[mõG^ÿ<Zÿ>\ìFZÎ`až~ev…Zi„Qh…OmƒRt…X{ˆ]}ˆ^x„\q‚X`zM_Pa…UeYn•`uœg} j~£mƒ¬r„²tˆ¶x‹¹y‹¹y¹z‘º~”º“±•¬€›©†©­’ºµ¢Ç¹®Ë¸²È¶´¸¬¬ª¡¢ —š[K;[K;[K;[K;[K;[K;[K;[K;ZJ:ZJ:ZJ:[K;[K;\L<\L<\L<]K=]K=]L<^M=^M;^O<^O<^O<^Q@^Q@]OB[OC\NE[NF\OI]OO^P_bTmdY{i^‰pgœ|t­…{·ˆ{³™…º¥…´¯€ª¸z¡ÆxŸÑuœÌcŒÈMlÜFRßA@ÕA=ÏE;ÌH;ÍG;ÓE9ÞA:î@Aõ<Aú=Dø?GðAHâAFÒ@AÆ?<¿?6¶<1¯9+«:*ª9)®8*¼>0ÎD:Õ<7Õ31à;9åA?áD?èSLßUJ¿:+Á9)¾7$À7%Ä;)É=,Ê=,Ë9*Î8)Í4&Ò2&Ù2)å9/ñ>7õ>6ð5.ä/&à6)Þ6)à4*ã1'ç.)ê-)ì*(ì**ê),è',ë'1ò)9ö&<÷!;û=û%Aû6Hä(6ç.<ÿP\ÿ`lÿP\ò<Ió9Gè)8í*8ñ-9ô0:ñ27ê01á+*×(%Ø0-Ú72Ô2/åC@ÿspõVSÝ>;åGDÛ=:Ú<9Õ:5Ò80Ñ7-Ð6*Í4&Æ3#¾5#¹6$»8&¿;,À</¿9-À:/Å<2åXQÜMGÊ70Ç0)×<7ÞA:Ú;5ß<5ç>9é=9æ?9á@8Ù?5Ð<2È:.Á9-»9,»=1ËNHàc_ìnoú|ÿ„ˆý~…øy€øy€üyÿx‚ÿt‚ÿn}ýetù^n÷VhóE\ý8Vÿ<ZîH\ÇYZŠlRcrG\tB\yCdzIl}PtVvƒXrXoWm‡ZlŽ\o•buh{¤l¨o„ªq…«p…­q…±tˆ´u‰·v‰·v‰·v‹¸w¹z‘´|«{’¤|›¤…­«–¾²¤È·¯Ç¸µ´«®¡ž¥—’™[K;[K;[K;[K;[K;[K;[K;[K;ZJ:ZJ:ZJ:[K;[K;\L<\L<\L<]L<^K<^K<^M=^M;_N<^O<^O<^O<^N>^N>\O?^NA]OB^PE^OJbNYbQdcTqdZ}le‘xr¢w¨ƒv¤”}§¬†«Â‰©Î‚žÚw”ál‰ÛXwÚD_óBTúDPïJPáIHÐE>ËE<ÐF<ÙD>èBBð@Cõ@GõBHíDGßCDÏA=Â?7¿@7µ=/®:+«:*ª9)®8*»;.Ì?6áFAâ=;îBBëAAÞ;6ãJBæXLÕK>È@2À8(¼3#Á8(È<-É:*Ë7)Ð8+Î0%×3)à8/é;4í:5í60ë0+å,'ã0,ã1-ç10ì31ó25ö37÷48÷6;ì-5ê,6í.=ñ0Có-Fö)Gý+Nÿ3Rô9Lå3?ð@Mÿ_lÿlzÿWfõCSõ>Pî3Dí3Aí3>é6<ã99Û83Î4*Ç/$Ë2*Ç.(Ð72òYTö\ZòXVÛA?âHFÜB@Ô<7Î70Í6-Ï8-Ï9+Ê7'Â5#¹8#³9$µ:(¸=-¹;,¶6)¼7.Æ=5ÛNGÙEAÎ50Ð1-á>9ç@:ã81ç51ë20î21ë52ç83á:2Ø:/Ð9.È:.¾6*º8+ÈIBÜ_Yënlû}€ÿ…‰ýƒõv}öw~üyÿx‚ÿsƒÿk}ýdvù^pôSeôF]ý8Vþ=ZïL]ÀTRz_BWh<Yq?[vCfyKn~QtVx…Zyˆ_wŒay•exšhz m|§qªr„«r…«p†©o‡­rˆ°t‰³tˆµt‡µt…·r‰¸t‹¸w¶{Ž¬xŽ£x™¤‚­­•À¹§ÎÀµÒÄÁ¼¹À¨ª¶œœ¨ZJ:ZJ:ZJ:ZJ:ZJ:ZJ:ZJ:ZJ:ZJ:ZJ:ZJ:[K;[K;\L<\L<]L<^K<^K<^K<_L=^M;_N<^O<^O<]N;]N;^O<_O?_O?`P@aQAbPFbNPcN]_Pe`Usga…to—{t}q—v–®ƒŸÌ‹¡Û‚–ær…ëduéPdé;Rù3Lÿ<QûERéDKÕ?@Î@>ÑC?ÖEBãEFèBDîAEíCFçDEÚEAËB:¿?4»?3±;-©8(¨9(ª9)°8*¼:-Ê;3èIEé??ë=>å78Ú2/ÞA:îZPôh[ÚPCÉA3¾5%Â6'Ç9+È9+Ë7+Ò8,Ñ0&Ú6-æ;4é;4ê40è/,ê-+ë--ó49õ3;ø3=ü3=ÿ1>þ0=ý1=ù1>õ0Aò1Bô4Kõ5Nõ0Nö-Oÿ1Xÿ>_æ3HùO\ÿanÿ_nÿWfûP`ôDXé8JóAQê:GÝ2:Ò/2Ê2-Â8-½;+¼:*Ä:/À2(ÙJBúkcÝLGáPKÏ;9Ï;9àLJÔ@<Ê70Ê7-Î</Í<+Ë:)Ä;(´;&­:%¯<)³>-±9)¯3'º:/ÊD;ÖGAØC=Ô72×2.ç;7ì:6è2.í2-ò,-ô,,ò./ï31è71â:1Ú<1Ò>2Â6)º4)ÅB:×XRçjhú|}ÿ†‰þ€„õv}÷xýz‚ÿx‚ÿqÿi{ûbtø]oòQc÷I`ÿ:Xý?[ðO_ºQNqX:Sf8]uEa|InSv†Y{ˆ]~‹`g‚—l€œl}Ÿm|¤o}¨p€©o‚ªnƒ©l…©lˆ«qˆ®qˆ°r†³r„³oƒµp…·r‰¸t‹µv‰¬tŽ¥wš¨„°´™ÈïØ̾ÚÑÌÂÂÌ«±Á›¡±ZK8ZK8ZK8ZK8ZK8ZK8ZK8ZK8ZK8ZK8ZK8[L9[L9\M:\M:]L:^K<`J<^K<_L=^M;_N<^O<^O<^O:_P;`O;aP>aP>aP>aP<bOAeMMdMW`O_aUmgb€uq’|v˜€s‘uŽ­€”͆”ß~‡îqyùclúP[ú;Mÿ*Fÿ2Qÿ<Sñ<Mà<E×@EÕCDÔBCÜCEàBCäADâBDÞDBÔC>É@8¾>3¸>1¯9+¨7'©:)­<*´<,Á=1Ï?7ß=:å99ã03â/2à42Û94æMEülaòh[ÙQCÆ</Æ:-È:.É7*Í6+Õ8/Õ4,Þ7/æ93è62é1/è/,î1/õ15ÿ2Cÿ0Eÿ-Fÿ*Bÿ%@ÿ#=ü 9ö 8÷'Aõ+Eõ.Mõ/Pó+Pô)Tÿ2`ÿAhóCXÿ`mÿhuøVeíKZñL\ëBUÚ2Cë@RÝ7CÉ-1º((±.$«8&¥?&¨@'¸?.¼:,ÝYMá]QÍG>ÍD>Á63É;7äUQÕF@É91È8-É:,È9(È9(Ä=)±?'¦<&¨;&¬=*«7(¬3(¼=4ÓLFÖGAÝE@Û96Ü30é73ì51ë0+ô1-ý,/ÿ+-ú./ô1/í4/æ81ß;1Ø>2É9.½3(À;2ÏNHáa^÷yzÿˆŠÿƒ‡ùzû|ƒÿ{†ÿx„ÿn~þew÷^põZlëL`õIaü9Wû=YîM]²JGhQ1Qd6\tDf~Nuˆ[Žc„h†’jˆ—n‰žsˆ¤t„¦t§r}¨p¨n©mƒ©l…©l…¨n‡«n…­o…¯o°l€²k‚¶n…·r‡³t‡­t§wœ¬…²¹šÈƯ×νÙÒÌ»¿Ê¤¬¿’š­[J8ZK8[J8ZK8[J8ZK8[J8ZK8[J8ZK8[J8[L9\K9\M:]L:]L:^K<^K<^K<^M=^M=^O<^O<\O<`Q>aR=aR?bQ?bQ?aP>aO;bL>hOKgNRbP\cWkjfuv”}|œx—‘z–«•Æ„’Ú}…ðuzþlmÿY[ýDLþ/Iÿ5Rÿ<Uó<Né?LãCKÝBHÖ=@ÛACÜ@AÞ@AÛA?ØC?ÐA9Ç>4½=0µ?1¬;+©8(­<,´?.»@1ÊA7ØC=Ù74á85Þ..â30æ:6Ó0'Ï5)ëUGÿwjë[PÑC7Ê<0Ë;0É7*Í6+Ô:0Ú70ß82å63ç32ê01í12ô36ý4<þ%6ÿ#9ÿ"9ÿ"9ÿ!8ÿ6ý5ø6ï2î9ð%Bï(Gî&Jñ'Mÿ3\ÿEhÿbuÿ`l÷WcñQ]òP]ïMZâ@MÔ2=Ú=FÌ7;»/.®/(¦5'¢;(›?&œ>%ª9'²8+ÒVJ»;2Ä?8¾41½2/ÎC@åWSÕHAÈ91Ä6*Å6(Ä5%Æ5$Ã:(±<(§:&¨7%­9*¬4&¯1%Â?5ÛRJÙIAáHBÞ;6Þ2.ê41í1/ï-+ü22þ,/ý+.ù+-ó++í-*æ1*ß4,Ö8,Ñ=1¿2(¼7.ÈIBÙ\Xówwÿ‰‹ÿ‡Šû€…þ†ÿ}ˆÿv„ÿj|þ_s÷XlöTiêD\õD^õ6Uñ:VàLZ¥HC^K-Mc5Uo?b}JtŒ\€–g‡˜lˆ›nŠ r‹¥vŒ¬z†«wªr~§m|¥i}¥g¥g‚¦i‚¥k‚¨mƒ«m‚¬l®h~°i³j„¶o‰¶s‰¯rŽ¬x™°„­»˜¿Å©ËʵËͶ½Åžªº‹—§]K7\K7]K7\K7]K7\K7]K7\K7^L8]L8^L8]L8^L8]L8^L8]L:]J<]K=^L>\L<\L<[N=]P?^SA`SB_R?`P@bQAcRBdQBdNAdLBjMIhKMdPYf[lnk†w{ž€…­‰ˆ²ƒ©¥‡©¾ˆ Ð‚’ây~ðsqújbÿa^ùJWñ>Që:Lî?NïCQê@Kæ<Gå>Fá<Bß<?Ú<=Ö<:Ï>9Ê=4Á;0¸:,§6&¯A0­<,¬6(¿A5ÌF=Ì=5Ð72Ú85Ü71Þ5.Þ6-Þ8*Ý<*×:'Ó6%Ù;0ðSLòZOÙE9Ç8*Í>.ÔB3Ò:-Ô3+ðGBá//î5:ó6=î,7ÿBMñ'3ú&2ÿ)2ÿ(2ù'ý"*ÿ'/þ'/ð&ð)ù+7ê .ì&7ñ-Aç#;ÿXrç-Dÿ]lÿ_iþZcùU\õPWëHMÝ?@Í84Ë>7ÄA7·?1¦7&™2!™6#š;'Ÿ<)§8-®7/ÂE?¿;7½31Ä64Å54ÔDCÛLHÚKEÑD;È:0Ä4)Ç5(Ë4)Å5*¼</³9,­/#«(³-$Ã9/ÒC;ØH?äPFßE=Ú70Ý0*ç0,ò21ù13ý14÷/1ö01ö01ó0.ò0.í2-æ5-Ý9/Õ=0¾2%À>1¹=3Ö_Yésqÿû„†û‡Šÿ„Œÿxˆÿh}ÿ_wÿ[uÿUoøLføEcñ:Yô;[æ?YÛ]i‹@;VF-L^6YuBg†Mw–]}œcg€¡jƒ¦n‚¨o€§n¦m~¥l}¥i|¤h}£f£e£f¥l¦m«l¬k~­g®f±gƒ³i‡µm‹´r°vŽ®|™´‰§½™±Á¤²Á®¦²°˜žz…‹]K7]K7]K7]K7]K7]K7]K7]K7^L8^L8^L8^L8^L8^L8^L8^K:\K;^L>_M?^N>[N=[N=\Q?^RB_SC]QA]OB_OB`NDbPFcOHeNHiMJgNQeS_g_tnny~¨€‡»‡ŠÁž•Ê£‹»«£¹xÓyƒê|{ôvjöi`ø]aóOZìFRòHSøIVõEPï?Jë;Eä9?à:<Ú::Õ=8Î?7Ç>4À</·<,«<+ª?-©8(²:,ÁA6Å<4Ç61Ô<7Ò50Ö5-×5*Ø4(Ù8&Û:&Û;%Ú9'Ý6.æA;ëMDáI<Ð>/Ç8(Ê8)Ó;.Þ;4öJFä01ð6;ö6Añ.<ÿ@Nï'4ó$,÷%(û),ý+,ü*+ø((ö((÷+,ï$'í%(ê%,ý;DÙ'ð4CüARØ&6ÿjuÿbjþZaüW]ôOSåBC×<8Ñ>6¾8,¿D5¹H6©>,™/)(’(ž-%°:6ÑTPÎHGÃ54Æ45Ñ==ëWUØGBÖG?ÐA9Ç9/Æ3)Ê6,Ð7/Î70Ç=3Â91Â5.Ã2-É4.Ñ83Ø?9ÞE=äJ@ÞA8Ú6-Ý0*ç0,ò21û03û03ô02ó12ô01ó/0ó0.î1-ç4-ß9-×=1¿3$¾</·=2Ó^Wévsÿù…ˆø…Šÿ‚Šÿt…ÿf|ÿ\vÿXtÿQoþIhü@aó:Zñ?_áI`ÁS\{?7RF.M_9ZvCe‡Ks•Yxœ_wb{¡f}¥i}¦j|¥i|¥i|¤h{£e{¡d{¡d}¡c~¢e|£j}¦l}©l}ªi|«e}­c¯c‚±c…°h‡²kˆ¯pˆ«s‹«|¬„ª‡Œ¢‹x‡€dppR^^]L8]L8]L8]L8]L8]L8]L8]L8]L8]L8]L8]L8]L8]L8]L8]L8^K:_N>aP@`P@]P?\O>[O?[RC[QE[QG\OG\OI_PMcQOfTTgUUgUUgV\h]nli†sv£|ƒº‚ŠËŠÔ˜‘× ŠÊª‚´¹}ŸÏ€“䄆îvósjûeg÷S\íGQðFOöHR÷EQòAKî=Eä7=à7:Ù99Ô<7Í@7ÅA4¾?0¶>-­>-ª<+«:,¸B6ÇH?À;2»0)Ê:2Í6/Ð6.Õ7,Ø8,Ù7(Ù8&Ü9&à:*Ý4-Û4.âA9éOCÞH:È5%È2#Ù@2â>5õHDè13ò5;ù7@ø4@ÿESø2?ð)0ë#&ì$'ù13õ-/ï''ì&%õ12ë)*ö8:è+1â)1ë3=ÿR^ï=Kÿ[gþ^fñV\îOTðMRêEIÝ:;Õ74Ô?9À6,Á?2¼B5³=1¬7-¥3)¡,#œ' š%©/*ÇECÂ::¼,,È35×ABô^]Ð?:ÒC;ÐA9Ë=3Ì9/Ô=4Ú@8Û@;Û@<Ø=;Ý>;ãA?ä>>à::ã?=ëIDàB9Ý<4Ù5,Ý2+ç1-ð31õ12ø02ô02ô02ô01ó/0ó0.î1-ç4-ß9-Ø>2À4%º8+µ;0Ð[Tízwÿ“’û‡Šúƒ‰ý~‰ÿr‚ÿdyÿ[tÿTqÿNmÿFgû=_ô=\îEbßTg¢AHp;3SH2Qa<]xEf‡Nr“Zu˜^tš_xžc{£g{£gy¢fy¢fy¡cx byŸby a| bz cz¢f{¤h{§h{¨e{©a{©`}«`®`‡²jŠµn‹²s‰¬t†¦w‚žuy“no†lSeYAPM2A>^M9^M9^M9^M9^M9^M9^M9^M9^M9^M9^M9^M9^M9^M9^M9^M9^L8`N:aP<`Q>]P?ZN>YOCXPEXOH[QO_UTbXYeX_iYcl\fk^glaildqok‚rtšy~µ†Ê…ŒÚŒŽáŽ‡×Ÿ‹Ó­ŠÄ³€©½yŽÌ{Þzò‚wÿsrÿcgùU\ôMTõGPôCMò>Gï<Bå6;à88Û97Ô<7Í@6ÅA4¼A1·?.«:*­>-±=0¼F:ÌRGÇH?º5.º1)È91Í81Ó;0Ù=1Û;-Ù6'Ü6&à8+à5-Û2+à=4ìNBãK=Ò:,Ñ8*àB6ß7.ð>:é/0ð16÷4<ü8BÿNZÿLUý>Eò38â$&í/1ê,,ð22è**ë/.ò::Ñæ37Ý,2è9@ðEMÿXcóMWØ=CÚADáBFá>AÞ9=Ü89Ø88Ó97É83Â91½:2ÀA:ÍNHÖVSÐNNÃC@²72¶95ÏKIÙMLãQRïYZêRQñYVÉ51Î>6ÒB9Ð@7Ó?5ÙB9àC<â@;é?@ç8=ì9=ò?Cï:?è58ì>=öMJÞ93Û60Ú3+Þ3,å4.ì30ò21ô01ô02ö/2ö01õ/0õ/.ð0-é4-à8-Ù?3Â6'·5(³9.ËVOï|yÿ“’üˆ‹ý‰þz‡ýoücwÿXqÿOlÿFhÿ@cû9\ô?^éKdØZh…35g;0WO8WgCa{Kj‰Ps’Yt•\u˜^wby¡ez¢fw dv awŸavž`xŸ`xŸ`{Ÿaz cz¢fz£gz§fz§bz¨`z¨]|«]~­_†²g‹´nŒ³tŠ­s†¦u€qtŽii€dQcUDSL8G@^M;^M;^M;^M;^M;^M;^M;^M;^M;^M;^M;^M;^M;^M;^M;^M9]K5_M5`P9_P;\O>ZN@XOFXQKYPQ`W\g`hnfsshysg{sg}qh}ol}pq†rw—v}«z‚Á‡Ò„ŠàŠ‹å“ãŒÚ „¿žuŸ l‚¬nq¼vlÒviòsmÿllÿgiþ^`ûRWõFMï<Bê7=æ5;á78Ý98Ö=7ÎA7ÆB5½B0¸@/­7)´@3¯:0°;1ÈRHÕ\QÉMC»;0¾90Á7-Ì9/Ö>3Û=1Ú6*Ü4'â6*à3,â70ä=5åE9âF9ÞB3ÞA2â>2Û0&ê72ì0/ï.1ñ.4ú7?ÿPXÿ]dÿZ_ÿOSá.1ã03à,-÷EEè66Ü,,Û-.ë>@Ü25Ù37ÿbhà=BË*2Ê-4Á&*Õ;=á@EÛ6:Ù37ß9=Ü7;Í/0Ì43Í;;ÕGFÞRSì`cõilôekê^aÄ@>ÆE@å^[øhgÿopÿjjåKK×=;É40Ñ>7×D<ÖC;Õ>5Õ<4Ø:1Ü41î5;ñ-7ð,6ò.8ï,4ë,3ó9<þJKß3/Ü3.Ú3+Ü3,ã5.ê40ï4/ò21ô02ö/2÷/1ö.0õ/.ð0-ê3-â7-Ù?3Æ8*´2%²8-ÃNGï|yÿ’‘ÿˆŒÿ‰ÿw…úl|øatûTnýHgÿ>bÿ:_ú6\óAaàNeÂWap/+_?0[W>^lIe}Mk‰St’\u–_v™_xžcy¡cx buŸ`uŸ`u_vž_wž_xŸ`{Ÿaz¡bz¢d{¥e|¦d|§`y§^y¨Z{ª\}¬^€¬a…¯g‡®m‡ªp…¥sƒ r{•nrŠjexe[k^RbU_N<_N<_N<_N<_N<_N<_N<_N<_N<_N<_N<_N<_N<_N<_N<_N:^L4_N4`N8^O:[N>YOEZSMZTT_ZahbprmzuŒ{u‘{q“zp“vp’qt“rxšt}¨w¶zƒÄ~†Ï„‰Ú‹ŒÞ–Ü™ŠÍ›…¶¢ƒ£ª„‘«~yŸl[šQ>¼MBÔQIâWRëWUñSRñKMí>Cå4:é9<æ9;à;9Ø=8Ó@8ÊB6ÀA0º?/³9,ºA6«5+£.$¾I?Ùg\ÙdZËUI¸<2¸6)À3)Î:0Ù;0Ú6,Þ3)ä6-ß1*ä71ã;2Ý9/Þ>0ãF5äB3ß9+ß1(é4-ð31ï-.í*.ô17ÿEJÿV[ÿ]bÿ\aâ9<ã9<Õ+,ôJKâ89×//Û57Ì()Ì'+øXZÝ>BÕ9=Ñ6:º#Ä(+Ü<>æAEÝ49Ù,2Þ17Ù05Í(.Ò37äKNöaeûhnùcl÷`iú`løbkÖLLÕNJê\ZêVTçMMãHFÏ42Î3/ÙA<àKDäOHßJCÙ@8×:3×6.Ü.-õ2:û-:ø*7õ'4õ)4ø0:ÿ>CÿJJà1.Þ3,Ü3,Ý5,á4-ç4/í4/ð3/ô02÷/2ù.1ø-0ö..ñ/-ë2-â7-Ù=1É;-³1$±7,ºC=ìyvÿÿˆÿ|ˆÿqöhxö_rùRlûDcÿ9^ÿ6]ú6\ðFcÑN`¢IMe5+VD0[Y@^lIe|NkˆRt‘[w•_x™`{ždz cvž`s›]rœ\s›\tœ]v^wž_{Ÿaz¡by¡bz¤b|§b{¦_x¦[x§Yz©Y{ªZ~ª_ƒ­e…¬k‡©m‰§s‰¥u„s|”ru‰pnlfwe_P=_P=_P=_P=_P=_P=_P=_P=^O<^O<^O<^O<^O<^O<^O<_N:aP6aP6aO9^O<[OA[RK\VV_Zakgvso†}z—‚~¡|¥}w£{u¥xv§sy©s}°x¸{„¿~†Ä„‰ÉŽÎ”“Íš“Ç •½«›µ¼¦²É­©Å¦”ªˆmœfJœE2¯A4¶@4ÂB9ÖHDèNNîJKì?Cë=?é;=ä<;ß=:Ù@8Ð@5È>1Á=0½:0¾?6¯4,§1'»I?ÑcVÖh[ÕeWÃOBº>2º4)Ç7,Ö90Ù5,ß2+ç60â0,ã4/â7/Þ7.à>1åC4ä>0Þ4%æ4*é0+ð3/ð..ï-.ò14ó78öBEöMRüY^éFKöQWØ37ëFJÝ8<æAEÚ7:Î.0Ñ05äHKÒ7;Ð7:¹#%Õ;=Û8;å;>ë<Aç6<â-6Ý)2á1;ç<DðLSý]eÿksÿjsú`jöZgùXh÷[fôddèZVèWTØ@=Ï10Ô44Ó33áC@ãJEéRKêSLáJAÙ?7Ø93Û81ã33ó.7ÿ0>ÿ0=ú*7ü.:ÿ;EÿCHûABã1/ß4-Ý4-Ý5,â4-æ3,í4/ñ40ô02÷/2ù.1ø-0ö..ñ/-ë2-ã7-Ø:/Í>0µ1%±7,²;5ésqÿŠŠÿ‡ÿv†þi}óató[p÷PjúCbÿ7`ÿ7aù;_ìLf¿O[~86]=0NF1VX@ZeEcxMj…RrŽ[v”^yša{ždxžas›]p˜Yp™WqšXqšXt›\uœ]y_xŸ`x¡_y¢^z¥^y¤\x¤Yx¤Wy¥V{§X©_‚¬d„«h†¨l‡¥o†¢q€šmx‘jq…ij~cbv]_P=_P=_P=_P=_P=_P=_P=_P=^O<^O<^O<^O<^O<^O<^O<_N:cQ9cQ9aP<]P@ZPG\TR_Zaa_mtrŠyx˜€¨ƒ°~~°yy¯yu°wwµw|¾x€Á}…Ä‚ˆÄ…ŠÁ¿—–¾ ¼¬§»¶¬µ¾³±È¸«Ð¾¦ÖÁ¢Ñ»–Ьˆ¯oT®\G¤G5¨>0ÂG?àWQïWVñNOé@Cê=?æ<=â=;ß=:×=5Ñ:1É90Á80Á<5¹:3¸=5ÁOEÇXMÇ]OÑeXÓcUÂL>¼:-Å8.Ò91×4-ß2,ç60æ40á2-à5.ã<3çC7æC4â:-ß3%ê5,æ.&ð0-ï/,ð31ñ54ç-.â24çBF÷X]öW\ÿouåDJëHMà<Cþ[`ÿouÞ=Bÿ}‚ÿmqêQTÔ>@Æ02Ö:=òHKî9>ð7?ø=Fò7Bè-8ô>Jÿ]gÿmwÿjrÿcn÷]gú^kÿaoÿ]nùYeò\]ãPIâJGÕ74Ò/0Ù54Ò0.Ø95ßD?ãJDâIAÙ@8Ó6/Ö4/Ü71è66ê'/ü0<þ2=÷+6ù0:ÿ>Eý?Aì44â3.à5.Þ6-ß4,á3*è3,ï4/ô41ö01÷/2ù.1ø-0ø..ò/-ë2-ã7-×9.Ñ?2¶0%²6,°61èpoÿ‡ˆÿ†Žÿp‚ücxñ\pôXn÷OiûBbÿ9aÿ<döAbäRg°QWd/'WD3GG/RT<U^?btLiQq‹[v’_z˜b|dxœ_r™Zn—Un—Un—Uo˜Vr™Zt›\w›]wž]wž[x¡[y£[w£Xv¢Wv¢Uw£Ty¥Xz¤Z~¦_¦c¡d~œf{—frŒ]i‚X`vRZoPRgH\L<^N>`P@`P@_O?_O?_O?aQAbRB_O?]M=]M=aQAcSCbRB`Q>dS?aP>]P@^QI[QPZSZeapsq‰yxšz|¥~€±€¹}ºxz·rs´lo¶twÈv{Ì€ƒÊ‹Æ–—䤾²°»¼¹´ËƳÓÌ°ÙϬÛϧÜÏ£ÝУÙ˜٘Ѩˆ¸€g ^FËzgÖue½H?×QN÷ccéKLå?Aâ89ä::â:9Ü75Ù61Ö;6Ë81À5.ÇB;»<3¿I?ÆXK¹OAÎdVÑcTÌXIÅG9Ä:/Í6-Ø7/á6/ã4/ß0-ß2,à70ä=4à<0Ý7)ß5(å7,ä/$ê/&ì/)î1-î20ë40è51â64×79æJNõY]íNS×8=×6<ùX`ÿ~„ÿ~†ÿouÿgoÿipÿflõ\añX[ûZ_ÿUZÿDKü8Bû7Aö2>î+9ì0>ñ=HÿS_ÿ`iÿgrÿepþbmÿcnÿ`oý]gíSSßF@Ö85Ö41×11×/.Ø31Û94áD=ÜB:Ù?7Ö<4×90Û81à93æ66ð7<ó4;ó4;ö4<õ6;ò59ë33ã1/Þ3,Þ5.ß7.à5+ä3+é2,ï2,õ20÷12ø03ú/2ù.1ù//ó0.ì3.å7.à?5Ì8,¿6,¯/&¸;7ÒXWÿ˜šþ{ƒÿm€ÿe|ø\rðPhôGeüCeÿ;dù9`õNlÓRd‰=?P, C;&CG.KM5Y_CZjEcxMqˆZx’bx–`t•\r–Yq˜Yl•Sm–Rn—So˜Tr™Xt›Zuœ]uœ[t›XtWv Xw¡Wv¢Wv¢Uu¡Tt S{¥[{£]zž^u—[m‹Ub~MXrCSj>AW0?U/<R,[K;^N>`P@`P@`P@_O?`P@aQAbRB`P@_O?`P@bRBcSCbRB`P@fVG_QF\OG^TSaZad`omkƒvw–wy¢{}®}¸x|¹quµkp´lo¶nq¾uvÐ|~Õ‰‰Ó˜˜Î¨§Ç¸¶ÁÉźÓαÛתâÛ¥çÞ¥æÝ¢äÛ¤âØ£ÛÑ ÔǛӼš»œ€©}dÅ‹wÊ}m´VJÒa[örpìZ[çKLã?@ç=>ë>@ç;;â66Û64Ö=8É83Ê?8¹6.¸>3½K@¶H;È]MÝo`Ô`QÆH:½5)Ä0&Ô6-Ü5-Û0)â51å95ä=7à<3Ü8.Û5)ß4*ã5*ì7.í5+í2+ë0+ç0,ã2,á4.Ý52Ö87Ø>@äJLóW[ú^bü\dþ]eÿaiôS[ïNVñQYÿ_gÿjqÿflõZ`óNTí4:ñ,5ô*6÷-9ñ'5å!-æ'6í7CþPZÿZdÿblÿdmÿenÿcmý[hõQZåEEÞ<7Û64Þ63ß55Ü30Ú2/Û62à>9Û>7Ø;4×:3Û81ß82å95ç:6é9;ê7:ë7:î79î79ì65å31ß2,Ü3,Ý6-ß7,â6,å2+ê1,ò1,õ20÷12÷03ú/2ù.1÷//ó0.ì3.å7.ß<3Ô=2Â8.¶1*·74ØZ[ÿ“ûs}ÿfzÿ^vùWo÷OiùFfû?bü<cô@cåOh¹KVx;8L2#<:#<B(EG/OS8VdA`rJnƒXu_v“]t“Xq“Vp”Tk’Ok”Pl•Qm–Rp—Vr™Xsš[sšYt›XuWuŸWuŸWtžTržQqRpœQn˜Pl”NhŒLbFXu?Ni6F]/@W+@W-@W-AX.YL<[N>^QA^QA^QA^QA_RB`SCaTD`SC_RB`SCaTDaTD`SC^PCbUM\QM[QRcYbkdtqm„vu•y{¤y{¬y~¶x|»nsµei°`g¯jn¸ruÆ}}ׇ„Ý—”Û©§Ø»¹ÏËÊÈÜØ¿åá´éäªíè¦ðé¥íå¤çá§âÛ§×Ï¡ËÃœ¸¯©››ƒk¨ƒp§o`œRGº]UÝmi÷uuï__äJLå@DëADì>@è8:â66ß=:Ô;6ÔA:Â70·7,·>3®=/¶F8Ûj\ßi[Ù[MÊB6Å2(Ñ4+Ù6-Ù2,Þ52éA>ìGCà=6Õ2)Õ1'Ú2'Þ0%æ4*è1)ç.)æ/)ã2,â7/á=4àB9Æ-(Ê43ÜDCðVXú^b÷ZaóS[ñQ[èFQêHSïOYü\fÿmvÿntôY_ã>Dé.5ô-4û2<ü2>õ+9í'4ö6EÿKW÷ISõPWøW_þ^fÿckÿ`i÷RYéBIÞ97Þ71ã75é<8ë;;æ95á51Ý60Ý:3Ù;2×90×90Ü71á83ç:6é<8ã99ã99å97ç98è88æ74á40Û2+Ú6-Û7-Þ8,â6,ç2)ë0)ó0,÷10õ12õ13ø02÷/1÷//ò0.î3.ç6.Ý90ÜD9Ç80»4.µ/.ä_bÿƒˆølwþbxþWsúPmþLlÿCfö:]ó;_ìIfÔSgDJl;4O>,=<':?(ED/FH0Q\<YkEg|Qr‰[tZqUmRkOiMi‘Ki’Lk”Pm–Tn—Up—Xq˜WuœYuWtœUrœTpšRm—Mk•Mj”LeGaˆE[AUt;Li3E`-AX*>U'B\/E_2Ic6WJ:ZM=\O?^QA]P@^QA_RBaTD`SC`SC`SCaTDaTD`SC_RB\PDZOMZQVbXcjcsqm†vu•xz£z|­w|´sy·mr´bi¯]d¬ag±lr¾x{ʉ‰ß•‘夡䶵áÇÄÙÕÑÎáÝÄçä¹ëæ¯îê­îè¬éâ«ãÛªÚÓ©Ë¡»¶™¥¥›™„“ˆv˜q™rc›bW³i`Ïrmû‹‰õutê[]æLNèCGí@Dì<?é;<ß:8Û<8ÞE?ÓC;Ã:0º:/°6)§1#¿I;ÛaRêk\ÛSEÉ:,Ì4'Ô6+×4-Ù42ñMKþZXíKFÙ80Ö3*Û4+Þ2(ß.&á,%á,%Þ-%Ü1)Ú6,Ù;0×?4Â/'Ä4,Î:6ÙCBßFHàEIäGNêMVñQ]öVbøXbù\eÿgpÿmuôYaÞ;@ê17ñ.4ò-6ð+4ë%2ì(4û=IÿS_í?IèAIêGLôQVÿY`ÿX]ôGMä68á53ä73ê;8ò@>ô@?ï=;æ95à72Ý82Ú91Ù80Ù80Ü71ã73è96é:7â:7à;9ã:7æ:8ç:6å84à5.Ú3+Ú6,Û7-Þ8,â6,ç2)ë0)ò/+ö0/ô22ô23÷12ö01ö0/ò0.î3.ç6.Ü8/àF<É91¾3.¶,,îfjÿy€øgtÿbyýTqþKkÿGjÿ>cò6Yë=^ãQhÄXeˆBBgC7[N;GF1AC-JH3EE-LU6Sc>btLlƒUp‹XnRkPiŽKgIhJh‘Kj“Mk”Pm–To–Wp—Vt›Xs›Uq™So—Pj“MgJeŽHdG`‡D]AWy=Rq8Mi6Lf6Ne7Of8Lf7Oi9Sm>UH8WJ:[N>\O?]P@]P@_RBaTD_RB`SCaTDaTD`SC_RB_RB^QIVMR_Xhnf{tp‰xt—xw¡xz«y{´sy·jr³`g­[c«_g°jr»v|Æ€„Γ•àžžä­®ç»½æËÉßÔÓØÝÛÎáßÆåá¾æâ¼äß¹à׶ÚеÎƱ¾µ¦­§›˜šŒŒŽ€‰…yŠ|q‘vk›si¨meºoi扄î~ósrñedñWYðMNîDEì@@ã:7à;7äB=ÞE?Ì<3Æ=3ÄB5¯1#®0"ÇI;ÜXKÖN@Ì=/Î8*Ð6*Ñ3*Ö42õUUÿkhú]XãD>Ü;3ß;2ã80â4-ã2,â1+Ý0)Ô0'Í/$Æ/$Â0#Å9,À4'Á3)Ë:5×CAÛEGÛBGÚ@HàDOëOZòVaòVaú^iÿgoóYaáBGê:<í57í38ï28ñ3=÷9CÿHSÿV_ä6?Ý4;ß6;ëADùJOüIMô;Aé13è51ë95ï<8ò<9ó;9ð:7é73â70ß;2Ü;1Û:0Ü90ß82ä73è64é75æ:8ä;8æ:8è:9è;7æ95á6/Ü3,Û7-Ü8.ß9-á7*å3)é1)ñ0)ô1-ó32ñ33ô22ô01ô1/ò0.î3.ç6.Þ7/àC:Ê70À1-À13÷jpÿqzúftû]túPmÿCfÿ=dÿ9cõ8^æEdÙZm«V[wE>dJ=aYFTO;KI4PI6GE.GP3L\8ZlFf|Nm…SmŠRlŒMhJfŽHgHg‘Ii’Lj“Ol•Sn•Vo–Ut™Vs˜Un•Rj’LfKcŒHaŠF_ˆFZBX|?St;Pn8Ok:Rl=Wn@ZqCUo?Vq>WqARE5UH8XK;ZM=[N>\O?^QA`SC`SCaTDaTDaTD`SC`SCbUEaVP^Vcje|yu}yšzy¡xy©vy°sw´jr³cj°[c«]e®hp¹v~LJшŒÓ™Ý£¥à¯³ã»¿åÆÇãÏÎÞÔÓØ×ÖÑÙ×ËÙÖÇ×ÑÅÒÉÀÌþŹ¹´¨¬£šŠ‹†x{tyvquleleogŽa[`[ÀsmØyuð{ü|yÿrpýccöSTðHGïC?ê>:á<6àA;Ñ:1ÓC:ÝTJÂ>2¸6)½9,Â=.È?/ÏB1×C5Ö@1Ñ7-Ð2/êLMú_]òWSßD?Ù:4Û81à70æ93ç92ç92â:1Ú9/Ñ9,Ê8+Ä8)°(¸3"È@2ÓI>ÒD@Ë;:Ì7;Ï:@Ñ7AâHRíV_ðYbõ^g÷`iêU[ÜAEå@>ì?;ñACúGJÿLTÿQYÿT]ÿV^á4:Ú/5Ü/3ç7:ô=Aø;?÷48ó12í42ï95ð86í42ë20ê20ç40á4.ã<4à=4ß<3Þ;2á83ä73è43è43è88ç98è88ë99ë97é75â5/Þ3,Û7-Ü8,Ý:+ß7*ã2(ç0(ï0(ò1,ñ42ð43ó32ó11ó1/ñ1.ì3.ç6.à91Ù<3Ì71Á0-Ñ@CÿnuýkuùbqðRiõHfÿ;aÿ4^ÿ6bû>däNiÊ\i‹GFjF:]L<]VCXQ>RJ7OG4ID.DJ.GU2Sc>^sHgMj‡OjŠKgŒGgŒFfŽGfHgJi’Nk”Rm”Um”Uq•Uo”Qj‘NgŽKcŒH`‰E_ˆF^†G\‚E[~DWxAVr?Uo@YpB]rG^uG[sCYtAXs@NB2QE5UI9WK;XL<ZN>\P@^RBaUEbVFbVFaUE_SCaUEfZJh]YngwupŽ|x™{y wx¦vw­pu¯jo¯ah¬^f®_g°em¶pxÀ}…ؙ͉ؗ Ö¡¨Öª±Û´¹ßº¿ßÀÁÝÃÃÛÇÆØÆÃÔÅÁÐÁ»É¼³Ä»°Áµ¨¼¦˜¯–‹œˆ…Œqqqtpopfewhe†nj„_Y•d_›ZT¹f`Ùsnðzvÿ{wÿwsÿhfüZWþRNõHBæ=6çD=Ø;4ÛG=ê\RÅ;0Ä<0½7+¾6(Ç>.ÑD3ÔB3ÔA1×A3Ò86Ø>>ÝCCÜB@Ö=7Õ81Ø7/Þ7/å:3æ93å:3â;3Û>5ÕA5ÑE8ÌG6®-¼;%ÍJ8ÑL=Æ=5»0+À00Ê9>×AJáKTëU`ðZeð]eìYaßLRÔ>@Ï4/Ø7/à;9ê@@òCHõDJñBIî?Fâ5;ß26ã36ï8<õ79ø14û/2ü14ï20ð95ð74ì0.è,*ç0,æ3.à3,ä=5à=4à=4à<3ã:5æ95ê65ì65ì57ì57í57î68î66ë54ä2.Þ1+Þ7.Ý9-Ý:+ß7*á4&æ1&í1(ñ1,ð42î53ñ42ò21ó1/ñ1.ì3.ç6.â;3Ò4+Ò:5Æ20çUXÿpxùcnòZiçI`óEfÿ8bÿ/]ÿ5bÿBhÜLe±PYj5/^G7XK:UN;[P>VI8M@0JC0@F,AO.K[6Xj@bxGfJhˆIf‹FeŠDc‹BcEeŽHgLh‘Oi‘Rk’Sn’RlPhNd‹JaŠH_ˆF_‡H_‡H_…H_‚H]~G]yF]wH`wKcxOczN]uC[vAZu@JC3MF6QH9TK<WK=YM?\O?^QAdWFeXGdXHbVF_UIaWMg^Ulcfwr‰yušxvžtsŸrr¤tu­lp¯bg©Za©_e¯fl¸ms¿v|ȇӓݗœÞ›¡Õ §Ó©­Ú®³Ý±´Ý²´Û´´Ú¶´Ûµ±Ö³­Ñ®§É«¡ÄªžÂ¤˜¼™‹¯ˆ~™|x†dbgiefcYZj[X{c_yVP’c]ŠNF¢UM½`XÖkcñwrÿzÿzwÿnjþa\÷TOéGBîOIÝB=ÞG@êWOº,"¾1'¾4'Ç>.ÑE6Ë>-Á2"Ê7'ÛE7ÞE?Ó97Ì3.Í50Ô;5×=5Ý<4á=4ä;4ä92Þ5.×4+Ï5)Ê8+Å<,Â?-ÔS>ÈG2½<)¾:-Ã=2Ä;5Â74Â43ÜKNÞMRáPUåTYèY]åVXÜMOÓD@É5+Ñ7+Ø7/Ý52â24å26æ18æ.6ì4>ì3;ó5?û9Bý4>ú+5ÿ(3ÿ/9ø-3ù57ù59ð./ë*+é0.è51â5/ã<4à=4à=4á=4ä;6ç:6ë76î66ì46ë35ì25í34í55ê41ã1-Û0(Þ8,Ý:+Ý:+Þ8(à4&ã3$é3&ì4*î50î52ð42ð3/ò2/ï2,ì4,ç6.ä=4Í/&×>9Ì35ùbgÿoy÷[iðNcêC]ùEhÿ:eÿ0]ÿ6aöGhÉJ]“CFR,#VI8SL:OH6[O?UI9F?-HF1>B'@J(IU1Sd:_rEe}Ig„JeˆHcˆCa‰BbŠCcEeŽHfMhPiQlPkMfJcŠIaˆG`‡F`‡H`†I^G^F_}G`|Ia{Kd{Mf|Nf~N]xC]z@^{CDB6FD8JF;PG>RH>WI>]M>_O?`Q>aR?`SB_VGbYRgb_ojnso~wq“zt |y¨yw©pp¦gg£`a¢\_¤`b­fhµopÀyzÌ„‚Ö‹ß–’眙栞ݡ¡×££Ù¤¤Ú¦£Ø¥¢×¥¡Ô¤ Ó¥žÑ£Í¢šÉ¢™ÈŸ–Ù»Ž‚°ƒ|žrn}igld^`fZZkYUrWP}WNŒWO•RI¨ZPµXPÁZSØkdìyrù‚|ÿ‹†ùuqÿusí_]ßPLÙEAïZTÌ71Å2*Ä6*Ã7(À7'Ã7&Æ9(Ê;+Ð<.Õ?1Ò8.Ö<2×?4Ó;0Í5(Í3'Ø:/â@5à90á90Þ7.×7+Ï7)É:*Æ?,ÄA/Â=.Æ@4ËE:ÍG<ÌC;Å<4»2,¶+&Ç<7ÐEBÙOLàVSãYVáXRØOIÐC:Ì;*Ó:(Ü8,â5.è./ì*2ð(3ô'6ú->ú+?û*?ý)?ÿ(?ÿ&?ÿ$<ÿ$<ÿ$8ÿ'9ÿ+<þ,9ó+5í.5è45ç;9Ý84Ý:3Þ;4à;5ã:5æ95é73ë54ë35ì46ë54é54ç53â5/Ý5,Û5)Ü8,Û9*Ü9(Þ9&ß8&â7%ã6%æ4&ì5-í4/ð50ñ4.ñ5,í5+ê5*å7,à90Ø7/Õ:6Û?BÿmwÿbsûSjõIcÿKjö6[ÿ6_ÿ<gø>cçNj¯IV^$"D, ?:'HE4PM<PI9EB1>C/?G/BH&EK%KS.S]8[kDavKd~NdƒJb†F`ˆBa‰@c‹BeFhJlPm‘Ql‘LiŽHgŒGf‹HfŠJe‰Ic…HaƒGdƒJdIbGc~GdJdHe~Ga}C[{<\=_‚B=?4?A6FC<JE?PE?TG>[K>]M=`O;`Q<^SA^VIc[Xhcinlyso†tp•vpžtp¢pm¢gfŸaaŸ]^¡\\¤ed°kjºtrÅ}{φ‚Ù‰à”Žæ˜“å›–Ùœ˜Õ™Öžš×ž˜Ö—Õœ–Ô›•Ñž–Ñœ”Íœ“Ê›’ǚē‰»ˆ®‚xtk|mdif]^f[YiZSoWM{UJŠVKšVK°\RºYRÁXRÓfaàqjãvoí~wý‡…ÿ‡…÷usîgdãUSëZUÇ2,Â/%Å7)Ã7&À7%Á8&Ä8'È;*Ï;-Ó=/Õ9,Õ9,Ó:,Ò:,Ñ9+Ñ9+Ô8+Ø8,ß;1à90Ü8.Ö8,Ï9*È9(Á:&¾9&¾6*Á80Æ=5É@8ÊA9È?7Å<4Â91¿4-Ç>6ÑH@ØOGÚQI×NFÌC;Ã7*Ê7%Ó8$Ý7)å4,í//ô+3ù)7ý(:ÿ+@ÿ*Aÿ)Aÿ&Aÿ#@ÿ!<ÿ=ÿ:ÿ:ÿ 9ÿ%;ÿ);÷-9ð19ê7:å;;Ü94Ü:5Þ;4à;5ã:5æ95é73ë52ë35ì46ë54è64ä71ß6/Ü5,Ù5)Û9*Û9*Ü9(Ü9&Þ9&ß8%á8%ã6%ç5+ê3+ë4,ë5*ë5*ê6(ä7)á7*ß;1Õ4,Ó54óTYÿesÿ[põGbÿIhÿAdÿ;`ý3[ù;aòMmÍLb‰8?S*$A5'69(;<,B@1DA0@?-=B.>G,=CKP'\b<eoJhvRezQd~Od‚LaƒFa†Aa‰@cŒ@gDiŽHkMl‘Nl‘KjGhHf‹FgŠJf‰If…JdƒHfƒKdIdHd€FeGeGfEb~A]€>_„?b‡B69.:<1??7DA:KB;OE<VH=YL<^O<\O<\SB^WMc]]helom{sq‰so”qk™jf˜c`•\[”ZZ˜\] \_¦hjµno¿wxÊ€Ò†„ØŠˆÜ‹à‘ŽÝ“Ó“‘ÑÑҔѓŽÑ“ŒÐ’‹Î•ŒÍ“‹Ç”‹Ä”‹À“‹¾Œ„³ƒz¥€r•|ewu^fi[[d[Vd[RfXKrVH„TH£ZQ½`YÉ\YÏYWÛcbákißokãvqûŠ„ÿŠ†ÿ…ÿ}y÷nhîaXÄ4+À2&Ä8)Á:'À9&À9%Â9&Æ;(Ì:+Ð:,Ö:-Ó5)Î5'Ð8*Ô>/Ô>/Î6(Ë/"Ú:.Ú8-Ù7,Ö8,Ð:+É:)Â9&¾7$½5)À6,Â8.Ä:0Ç=3Ê@6ËA7ÌB8Ç:1Ê@6ÏE;ÐF<ÒH>ÑG=Ê@6Å7+Ì6%Ö6&ß7,ç4/ñ03ö-5û+9ÿ*<ü';ü'=û%=ù#;û!:ú7ü7þ6ÿ8ÿ6ÿ$8ý*;÷2<ñ6=ë8;ä::Ü94Ü:5Þ;4à;5å95ç85é73ë52í36î47í55ê65å61á6/Ü5,Ø4(Ú8)Ù9)Ú9'Û8%Ý8%Þ7$à7$á6%á5'â4)ã5*ã6(ã6(á7(Þ6)Û7+Ý<2Ð1+Ø88ÿkrÿ]nþPiñ<[ÿHlÿ7]ÿ>eù5[î>bæVqª?Qa#(L/);7+07'37&9<)@=*A<(@>)BB&?FXa4s~T€Œdw‡`hSa}La€G`ƒC`…?a‰@cŒ@hŽEjJkMl‘Nn“Mk‘HhHgŒGh‹Kh‹KfˆLd†Je„KdƒJc€FdGf‚Hf‚GeFb€Bc†DeŠEhH25*58-;<4A>7G@:KB;RE<VJ<XL<YM=YQD^WOc^bigron€roŒrm•jg”`^WW‹TUŽWX˜\_¢ac­ln»rsÃ{|΂ƒÕˆ†ÙŠˆÛŒˆÜŠ×ŽÒΌώ‹Ð‰ÑŽˆÐŽ†Ï…Î…ÊŽ…Æ…Á‡¼‡¶‰‚¬€xoŒ€_p|Y`lXYd[T^_Q_\IjWF}SEžUL¾\YÐY[ÙX\äcgçkkáplåxsì}vízsþƒ~ÿ†ÿ{õkaÉ<2Ä8)Â;(¿:'¿;&¾:%Á:&Å:'È9)Î8*Ø:.Ô6*Î5'Î8)Ñ>.Ñ>.Ë8(Ç/!Ò6)Ó5)Ò4(Ò6)Ñ9+Í<+È=*Ä;)Ä:-Ä:/Æ90Æ9/Ç:1È;1É<3Ë=3ÓD<ÓE;ÐA9Ë=3Ë<4ÏA7Ï@8Î;1Î5'Ù5)â5.ë31ò/5÷+6ù)7ü):ù&9÷&;÷&;ø'<û&<ý%:ÿ$:ÿ#:ÿ"7þ#7ú%7ù,;÷5>ò9?è8:à87Ü94Ü:5Þ;4à;5å95ç85é73ë54î47î47î66ê65æ72á6/Ü5,×5*Ø8*Ø8(Ø8(Ù8&Ú7&Ü7$Ý6$Ý6$Û5%Ü6(Ý7'Ü9(Ü9(Û:(Ù9)×:+×:1Ð3.ëHKÿoyÿVjøD_ø<_ÿAgÿ3\ÿ8bø>cèMlÅOe€0=J C1-11'.6'4=,=B.B@+B<$E>$HE$U\0p~MŸr—¨|ƒ˜mj„W_{J]~E_ƒCa†@bŠAfCkJl‘Lm’Om’Oo”Nl’IiŽIhHiŒLiŒLhŠNg‰Mf…LdƒJdGe‚HgƒHh„If‚Gd‚DgŠHiŽIl‘L-3'36+891<;6B=9G@:MD=QG=SI=SK>UNDZUQa^eigumklkŠjhb`XVˆPP†QQXYš`cªfj´orÁuxÉ|Ђ…Ö†‡Ù‡ˆØŠ‰Ù‹ŠÖ‹ŒÏ‹ŠÌŠ‰Í‰‡Ð‰…ш„ÑŠƒÑ‰‚ÐŒ„Ï‹„È…Á†º†°‡¥~w–m…„Yj€SZnVTc\RZaOZ_HeZDxUB’LD¸TRÐSWÛRYå^dèejãklæuqäunâoh÷~vÿ‚{ÿ‡~ôl`É?2Á8(À;(¿='¿='À<'Â;'Å:'Ç8(Í7)Ø:/Ù8.Ò8,Ï9+Ì:+É:*È9)É7(Ï9+Ñ7+Ò8,Ñ7+Ï7*Ì8*Ë<,Ë>-Ê<0É=0Ê<2É;/È:0Æ8,Å7-Å5*Ð@7Ð@5Í=4É9.Ë80Î;1Í:2Ì5,Ð/%Ù0)ä20ì25ô/8ù-9û+;ü,<ü-?û.?ü/@ü/@þ/Aÿ/?ÿ-?ÿ.@ÿ)9ü)8÷+7õ0:ñ6=í8=ã77Ü43Ü94Ü:5Þ;6â:7å95ç85ê65ì44ñ48ñ48ï56í76æ72á6/Ú6,×5*Ø8*Ö9(Ö9(Ø9&Ø9&Ù8&Ú7&Ù8&Õ8%Õ:&Ö;'Õ=(Ô=(Ô=*Ô=,Ô<.Ñ7/Ú;8ÿ^eÿaqÿPi÷<[ÿAiÿ4_ÿ4`û1YôKlÛYq•>NZ%-C),:2/-0)08+7@/:B+<<"A<UH(`[5{…S£lª¾‹¨¾Ž¥wo‹[aK^F`„DcˆBeDj“Go”Np•Po“So“So”Nl’IiŽIgŒGiŒLiŒLi‹OhŠNf…Je„IdƒHe„Ig…Ih†Hg…Ge…DiŽIi‘Kl”N+1%.4*470893>:7B=:HA;KD<NH<NG=OJDWSR_^ffeugggf†ba‰[YŠQQ…NO‡RS“[^£ei³lo¾uwÊz|ÑÖƒ…؆‡Ù†‡×ˆ‡Õ‰‰Ó‡ˆË†‡È†…Ë…ƒÎ…€Ð…€Ò‡Ò‡€Ð…ÒŒ…Ë…ÁŒ…¸‹…«„{v|jz„VcPVnUQd^P[eMXbIb^EsXCOE´WRÐUZÚSZç\cèagågjèpoìyræqh÷|tósjþxmæ\QÄ;+¾5#Â;'Á=(Á=(Ã<(Ä;(È;)Ë9*Ï7*Ö5+Ø7-Ô:.Î:,Ç:)Ä8'Æ:)Ê=,Ï=.Ö@2ÙA4Ö>1Î8*È4&Ê8)Ë<,É:,È:,É;/É;-Ê:/È9+È5+Ç5(Ê6,Ï;/Ò>4Ó?3Ö?6Õ>3Î7.É,#Ò+%Ü-*ç02ð39ö1;ú0<ý0?ý0?ù0@÷1@÷1@ö0=÷/<÷-9ú,9ú*7ý/<ù/;õ0:ð3:í6;æ69ß55Ù42Ü94Ý:5ß:6â:7å95ç85ê65ì44ò59ò59ð67í76æ74ß6/Ú6-Ö6*Õ7+Ô8)Ô8)Ô9'Õ8'Õ8%×7'Ô9%Ð9$Î<%Ï=&Ï?'Î@(Î@*Ð?.Ò;0Ñ61ëHIÿfrÿOdÿHfû7[ÿAmý/]ÿ7aò4XãPj¼Ufm19E(*E697325406906=-3;#38CCja:…TŸ®w©Á‡µÎ—¯Ç“’®}v”be†Q`‚F`…BcˆBgFm–Jr˜Or—Rq•Up”To”Nl‘KhHf‹Fh‹KiŒLi‹Ni‹Og†Kf…Je„If…IhˆIi‰Hi‡Gf†CjJk“Mn–P(0#+1'/4-350764<85@=8C>8IE<HE<KHCRPQ[[eabtbb|``‚_^ˆZXŠTTŠTT\] gi³psÄvxÍ{Ø~‚Ûƒ„Þ„†Ý„…؃„Ö…„Ô†…у†Ë‚…ȃƒË‚̓€Ñ…€Ôˆ€Õˆ€Ó…Ò‹„È‹ƒ¾‹ƒ²‰‚¤|“wp€veo~U[|PQnUNf_O]eMZdIaaGq]E‰TF­]TÈZ[ÔVZâ[aæ]déaeîllõzsírj÷xoÞYPå[PÒD8Ã5'Ç:)Å:'Ä<&Å='È=(Ê;*Í:*Ð8*Ô6*Ó0'Ô3)Ò8,Í;,Ç;*Â;(À;(Ã<)È;*ÕC4áK=ÞH:Ó=/Ê4&É5'Í;,È9)È9)È9+È9)Ê8+Ê8)Ë7+Ë7)Í6+Ò<.×?4Ø@3Ú@6ÛA5×:1Ò1)Ü0.å14î49õ6>ù4>ù1>ú0@ø/?ò.<ï/<í/9í07ï-5ï,2ò+2ñ*1õ2:õ3;ò5;ì59å57á55Þ65Ü75Ü86Ý97ß:6ã:7æ87é77ê67ì46ô5:ô5:ñ7:î87æ74à70Ú6-Ô6*Ó7*Ñ8*Ð8*Ð9(Ð9(Ð9&Ñ8(Ð9&Ë9"È:"È>$Ç?%È@(È@*Ì=-Ï;1×96üU\ÿ^qÿGcÿ?aÿ:aÿ7gþ3bû8`íFdÅNa‰@IS12A64@:<:46?56<7179+4="9CSZ.‚‚P¢§q±ÅŠ¯ËŽ°Ì’¦Ã’®}{™egˆQ_E^ƒ@c‰@iDo•Js™Ps˜Sq•Uo“Sn“NkJgŒGeŠEgŠJh‹Ki‹Ni‹NfˆKd†Id†IeˆHgŠJh‹Ii‰Hf‰Ej’Ll–No™Q&.!(0%-2+13.331764:95<;6GD=DD<GFBOOQZZd_ap^`y\\~`_‰\ZŒYX‘\\šgg­qsÀz|р܄ႅ䅆ㄅჃۂՃ‚Òƒ‚΄‡Ìƒ†É„„̃‚Î…‚Ó‡‚ÖŠ‚׌ƒÔ…ÐŒƒÄ‹‚·Š‚«…›xˆsnuqbevWUtSLjVKf_MbfM_eIcbFp^FzQ?[M·ZSÅSR×VZâW\êY`òce÷rmñnf÷qhÑD;ÔA7Å1%È4&ÔC2Ç9%Ç:&É;'Í<)Ð:+Ó:,Ø8,Ù7,Ï,#Î0%Î6)Ë<,Ç@-Á>*¾;'¼7$À4#ÑB2ãOAäN@ØB4Ë7)Ë7)Ð>/Ê;+É:*Ç8(Æ7'È6'Ê6(Î8*Ð8+Ó;.Õ;/Õ9-Ò6*Õ7,Ú<1Ý<2Ü71è88ñ8=÷:Aû9Bú4Aõ/<ò,;ï,:í/;ê19ê38ë48í57ñ56ô36ó57ð37ï6;í9<é69â45Ý33ß76à;9Ý86Þ97á96ã:7æ87é77ê67ì46ô5:ô5:ñ7:ì89æ95ß82Ø7/Ó7+Ð8+Í:*Í9+Ì;*Í:*Í:(Í:*Ì;(Å9"Â:"Ã=$Â>%Ã?(Æ?+Ê<.Ï81ß<?ÿ\fÿQhÿIfÿ9]ÿ=hù.]ý<hé;\å\p¦LV^..G83BC><89?48E26C41=:)>E&FU*bs?›a­½±ËŒ«É‹¦Ã‹º„Ž¬z{™ef‡PZ|@^?a‡>hŽCo•Jr˜Or—Rp”Tn’Rn“NjIf‹Fd‰Df‰Ih‹Ki‹Ni‹NfˆKe‡Jd‡GeˆFgŠHh‹Gh‹GeŠDk“Lm—OpšR(0!'/"(-&).(.0-3317839:4==5?@8DE@JLKORYVXeZ\s]^}YX‚[Y‹^]–ee£nn¶wxÈØ„…⌌ðŠŠì†‡ä‚‚Ü€€Ö‚€Óƒ‚΀ƒÈ„ǃƒË„„ΆƒÐˆƒÓ‹„ÒŒ„ÏŠ€ÅŠ€¼‰€¯‡}Ÿ€xwozkegf]Xm[Qm[Mi\Lf^Kd_IeaHhaGo_FoP;…UA¥]O¿aYÑYXÝQTîS[ø\`ùheæYPÔD<Î70Ñ7/×:1Ö=/Ô;+Ï9(Í:&Ï9(Ñ:)Ô8)Ø8*Ü5,Ü5,Ð/%Í3)Ì8,Ê>/Å@/Á@-¾=*¼9'Â9)¾0"éWJßK?Ã/#Ñ=1Î</Ç8(Ê;+É:)È9(Ç8'È7&É6&Ë5&Ì4&Ð7)×;.Ü>2Ü<0Ù7,Ú8-â>4ìC<õBEò9?ð2<ö4?ö2@ñ-;í-:í2=ì7>æ5;ä68å78ë99ð:9ñ78ï77ò;=î<<ê::æ87á85ß74Ü75Ü75Þ97á98ã99æ::é9;ì9<í9<ï8<ô7;ó6:ð8:í9:ç;9à;5×90Ï7,Ê8+È9)Ç9+Æ:)Ç:)Ç:(È;*Ç<)Á9#Ä='¾:#¶4¼9%ÉD3Ï?6Ê1,ÿ^eÿVgÿIbÿ<\ø1X÷0Yû6cõBiçYo¬FQo33N3,B?6>C<@78@-1L/3K2.A9&DJ(Zn=~š_ž¸w¨Å‚¬ÊŒŸ¾‚˜¶€”²~‚ nf„RXvBY{?]€>c‡AiDn”Im•Ll”Nk’Ol‘NiŽIfŠDc†Bc†Bf‰Ih‹KfŠLdˆJg‹MhŒNg‹Kf‹HeŠEf‹EjIk“Jq›QržSt U*0")/#).').(-/,130561782;<4>?7AD=HJGOPTSUaXYkZ\u\Z^]‰db”kj¤utº~·‡ß‹Œéð‰‰ë…„ ؀~Ҁς‚Ì…†É†‡È‡ˆË‰ˆÎ‹ˆÏŒ†Î†ÌŽ…ÈŠ€¼‰±†|¡‚x‘{s€tknlb`e]Re`Me^Kf^Kh]Ki\Ij^Hk_Gn^Ew_G[C’YF­ZLÇVPÞRSõQXÿX_ðUSãLEÙ>9Õ60Ù6/Ü90Û9,Ö9(Ó8&Ñ8&Ñ8(Ô8)×7)Ü6*ß4,Ý5,Î0'É5)È9+Æ=-ÄA/Á@-¿<*¾9(Ä8)Ë<.äREÜH<È4(Ì:-È:,Â4&Ê=,Ë<+Ê;*Ê;*Ë:)Ì9)Î8)Ï6(Ó7*Ö8,Ù7,×3)Õ1'Ø1(Þ7.ç<5øDGñ8>í/9ï/:ð0=î.;ë1<ë6?ä5:Ü36Ø22ß:8îEBøIFøDCó?>é:7ç98å97â96á85à85á98á98à87â88å99ç9:é9;ì9<í9<î7;ó6:ò59î68ê88ä;8Ü:5Ó9/Ë7+Æ8*Ã:*Â:*À;*À;*Á<)Á<+Á<)½9$Á?)»:%³5¹:'Á;/Ê:2Ø<=ÿ^gÿPdÿA[ù8Wö6[õ;`ô>dãIe«?Lƒ?>_:2G;/:>08>2F<:R>=N0.S8/VK5ciEx[°pÂ}¡Æš¹}š¹€š¶ƒ©xrŽ^YuETp=\{B_@b†@iCm“Hl”Kk“LiMiMgŒGe‰Cc†Bc†DgŠJiŒLhŒNg‹MiOiMiŽKgŒGgŒFfŽEl’Im•LpœQoRt U-1"-1#,/&,.)//-11/34/45-9;0<>1@B7EHAKMLQRWUUaVWi\Yt^]fdŒpn zx·…ƒÎ‹ßêŽïŽŒíŠ‰ç‡„ßÓ|Ë}ȀƅLjˆÆŠŠÊŒŠÉŒŠÉŒˆÅŒ„¿‹‚·‰€­†|¡€v{p€ujpndbi_Vc]M_aL_aKc^Jg\Jl[In\Ho]Gq]EwaIxX?‡S=£VDÇXOãUSøOTþMSâ><Þ<7Ý84Þ71á6/á6.Ý7+Ú7(Ô7&Ó8$Ó8&Õ8'Ø6)Ý5*à3,Ý5,Ë3(Ä6(Â9)Á<+Â?-Â?-Á<+À8(Ã5'ÜJ=ÚF:ÕA5Î</Ä6(Ä;+¾6&Æ:)Ç:)Ç:)É:*Ê8)Ë7)Ì6(Í3'Õ9-Õ7,Ö3*×3*Ú3+ß6/å:3é<8ð<=í49ë07ì18î3<í4<ë7@ê=CÝ7;áAAìNKøYUýZUúQLïB>å84â62à72à72à72á85â96ä;8ä;8á77ä88æ8:è8;ë8<ë8<ë8<ì8;ï58î47ì57ç77â:7Ù:4Ï8/Ç7,Â8+¿:+½;+»<+»<+»<)»<+»<)¸9&½@*³9$±6$¶;+µ0'Ã40ìPTÿ[iÿJ`õ:Uð7Vñ@]ïHfçHdÍNa‚89e?6R@2DA09=,57)>6+K9/E-#R=,g]Bˆ_’«t—»{–Áz–¿{”³xœ¸…š´…€šm\uKHb5Nh8[xB_}?cƒ@hŒDl’Gj’Ih’JgLfKd‰Dd‡Cc†DeˆFh‹KkŽNkOjŽNiOiOiMhJgHh’Hl”Ko™Ms¡Vs£Wv¤Y24&04&01)01+12-23.34.46+9;-;>-?A3DF9IJDMONRQWSS]XVd\Zoda~nl”zw¬„‚ÃŒ‰Ö‘㔑ê“뎋≅قÌ|Ã|¿€~½„‚Á†…Á‰ˆÂŠÃ‹ˆ¿‰…¸‡®…}¤…{€vŽyn~rgmmaak^Vi[Pc\J]`K]aJc^Jh[JmZKqZJtZIv[FsWAxR;ŠQ=ªYFÍ[PãTPðFIñ>AÛ2/Ü5/á51ã60ã5.á5+à6)Û8)Ö7$Ó8$Ñ8&Ô9'Ø6)Ü6*à3,Ú6-È6)½8'¼7&½:(¿>+Â?-Ã;+Ä8)Æ4'éUIÓ<1Ì8,ÑB4¾5%À=+À=+À8(Ä8'Å9(Ç:)È9)Ë7)Ì6(Í3'Ò6*Ô3)Õ2)Û4,ã:3é>7ì?9ì=:è45ì59ï6;î5;ê2:æ39ä7=â<@óUVübbÿmjÿidóVQäA<ß63Ý4/ß61Þ71Þ71ß82á85â96å97æ:8å78æ89è8;é9<ë8<ë8<ë8<ì7<î5:î68ê67å97ß<7Ö<4Ì9/Å9,¾9*º;*¶;)µ<)µ<+´=)³;*³<(³:'·@,­8&­8'³;-«+"Ã54ý`gÿTdúG\ð<Uë=XçG_áOdÒO_´SZvGAXH9KD2FE1BE0=?*:8#;3?3PG*nkHŠ“f•¬t‘´tŽ·s‘ºxž»…¡¸Š§}l‚[K_:AW0Lc7Wr?_{@dƒ@iŠCk‘Fi‘Hg‘GdHeŽJb‡Bc†Bd‡EgŠHjMlOm‘Qn’Rj‘Pj‘Nj’Lh’Jg‘Gi“Gm—KpœOv¥Wv¦Zx¨\78(68*66,56.56056067/68+;>-=@-@C.DG4IK>MNFQPNRRRVUSYX]a_lkius›€}²ˆ†ÇŒŠÓ•’ᔑâÜ‹‡Ó„Ä}º|µ|³~·„º‡„¹‰…·Š„²†¨z›€xs‡znzrfjm_^j[Ti[PjZKfZJb_Lb_Le^Li\Kn[Lr[Kw\K{ZG~UA†R=›RA¶VFÎRHÚG@â88å33Ý1-ß3/â5/ã5.á3,ß3)Ý5(Ü9(Ó8$Ð9$Ï:&Ñ:'Ô8)Ù7*Ý5,Ø7-Ä;+¹:)µ6%·8'½;+Â=.Ç;.Ç7,Ð90õ\TÖ=5É5+ÏA5¸3$·:(¼?+¿:)Ã:*Æ:+È:,Ê;-Ì:-Ï8-Ñ7-Ð3*Ò1)Ö1+Þ71å<7ê>:è;7æ74é75ï;<ð<?ê69ä15ä5:èBFíMOÿxwÿqnñ`[ÝJCÑ83Ñ2.Ü73ç@:à93à91à72à72â62ã73å76å76æ68ç79ê7;ë8<ë8>ë8>è7=ê7;î7;î79ê7:ä::ß=:Ö=7Ë;2Ã;/º8*µ:*³;*¯<)¯<*¬<(¬;)¬;)ª;(¬=*¦8'§9*ª8-¬/)Ë@CÿhrôM^óH[íDYåH[ÙO^ÉT]·TWœXUdM?OL9LG4JE1FD-?B'=D#>H#KV.\g<xT‹™f‘¤m©n²r—¹}Ÿ¶ˆ’§€u†dRcC?P0BS1Oa9Vl=b{Be‚BiŠCjEi’Fe‘FbHcŽGb‡Bf†CgŠHiŒJkŽNmPn’Ro“Sj‘Nk’Oj“Mi“Ki“Ij–InšKržOv¥Wv§Xw¨Y?=.=>.==1==3==5;<4;=2;=/?B/@D-CG.FJ3KL:NOAQQIRRHYWJZZN_^\gerqn‰{y¡ƒ€·‡…Čьӊϊ…ǃº~y¯zw¦yv¥{v¬|y®€|®ƒ}©ƒ}£x—|t‹zrvksrffm_\j\Sk[NlZLl[Kk[Ki]Mg^Mi]Mj]Mn]Ms^Mz]MZI“`O›VG§N@¸J=Å@7Ï6.Ø1+Þ1+á51á6/â5.â4-à2)Þ4'Ü6&Ú;(Ñ9$Í;$Ì;&Î;'Ñ:)Õ9*Ù7,Ó9-Á>,´;(°5#²5#»9)Ä<.É;/Ë7-Ù?7ø[TãIAÎ:0Ì@3¸6&¬3 µ<)¼9'¿7'À7'Ã7(Å7)É7*Î7,Ñ7-Ù<3Û:2Þ93å<7ê>:ê=9ç85ã41è96ë<9ì::è8:ì>@øPPÿbcÿppùheâTPÊ=6À1+É40Ö;7ß=:à;7â;5á:2â94á83ã73ä73å55æ66é69é69ê7;ë8<ë8>ê7=è7=é6:ï8<ì89è8:ã;:Ý>:Ó>7É<2À</¶8)²:)®;)ª;(©<(¨;'¥:&¥:(¥<) 9( ;) 9*¢6*µ>8ÛTXûepîK\ïI]éJ\ÛM[ÉQZ´VV ZRŠ`RPI7HM9NI5H@+;567>O%Lf6lŠTw”\„›e‰šd›f£k”­t˜°|ƒ•mn}\Q`C<J09F,AP1O_;Wj=czDe‚Bj‹Dk‘Fi’Fe‘FaFcŽGcˆCi‰FjKlMlOlOm‘Qn’Rm”Qm•Om–Pl–Lk—Jm™JrŸNs¢Q{¬]{¬]z«\DA0DB3DB6CC9CC;CC;BB6BC3DF0EH-GJ-JM2ON9RQ?TREUSD\ZC\\D\]Oaaaihxrq{z¦~µ†‚¿ˆƒÃˆ„Á„º{®ys¡to—om•plžrn¡vpžwršwr’to…skzrinmb`l_Wj\Qk[Lm\Lo\Mo\Kq[Mq[No\Nm]Nl_Om`Os`Oz_N†ZMŸ_S¤PE®B8º;2Æ6-Î1(Ö2)Ý5,â70á6/á4-à3,à4*Ý5(Ø7%Ô9%Ï:"Ê="É=$Ê>'Í<)Ò;*Õ9,Ï;-¿@-±<(¬5!®3!º8(Ä<.Ê:/Î5-Ø93ðNIñTMÙB9ÌB5¾?.¦1³<(¾<,Á9+Á7*Â6)Å7+É9.Ï;1Ô;3áD=ãA<ä?;ç>;é=;é;:è88ç85ç85æ95æ74é;:ôJJÿ\[ÿdcøc_Ì=9Á82½4.Ã81ÕA=ÞE@ß=;Ù42â;5â;3â94â94ä84å84ç77é77ê69ë7:ë6;ë8<ë8>è7=è7=é6:ë7:é69æ89à;9Û>9Ñ>6Æ<1¾<.³8)®;)ª;(¦;'¤;(£:'£:' 9&¡>+–7%œ=+š9)š2'ÃPKíkmî\fìP^éJ\ßHWÍKS¹RS¤[R’cQ€jUJM8DL7JB/H9$B7FH#Up=g“V~³o‚³qƒ¥h}’YƒYŽ–c‰—d|Œ_XfCGT89E-6B*:D,=J0JX7Wh>e|Fh‚Cl‹ElFi“Gc‘Fa‘GaGf‹FlŒImNn‘OlOkŽNkOm‘Qp—Tq™Sp™SpšPo›NqNt£Rw¦U|­^z­^y¬]IC3JD6IE9IE:IG;IG;HF9HG5IH3JJ2LL2NN4RP;TQ>XRDYTA]Y>[Z>\YH^[Tdahnk~xs“}x {§ƒ}«…­ƒ|¦}wunoi…kg‚ieˆjf‹mhˆmi‚mh|lfrkbgj`^g]Th\Nh[Kk\In]Kq_Kq_Kp]Lp]Np]Op]Ns]Ow]N~]N‡\L’XL¡UH¤F<«9/º7-Ê7/Ó6-Ú6-Þ6-ß6/ß4-ß4,ß4,ß7,Ý7)Ø6'Ñ6$Ï:$Ì<$Ê<&Ë='Î;)Ñ;*Ó:,Î<-¾A/¯<'¨3!¬3 ¶9'Á=.É;/Ì8.Ì2*àA;ø[TàI@ÐD7ÊF7«2¹>,ÊF7ÌB5Ê@3Ë?2Í?3ÔA7ÜE<ãF?â@;â=9ã:7â64ã54ä65å97å<9ä?9à=6Ü:5ß@:êMHêQLÓ@9¸)#¸-&¾5-É>7Ñ@;Ö>9Ú;8ã;;ç==â96â96â96ä86å95ç:6é99ê::ê88ê88ë8;ë8;é9<è8;ç7:ç7:é6:æ68â88Þ:8Ö=8Ì=5Â<1¹;-®9(ª;*¦;)¢;( ;'ž;&Ÿ:(›:'˜?-Œ5"™=.˜8*—/&Í\X÷y|ßX_æXdÚQ[ÊKR¹LO©VPž`UgU€nVPW8?J*<;C=UQ+lvDƒ¢f”Á~‡¼v†¸s|žan‚Mu~Q‚‡^u}V[b@;D)4<%5=(=D2<E29C+CP2Ue>g{HiƒFl‹Gm‘Gj“Gf’GbGcŽGhJlMo’Pn“PlMiŽKjJj’Ls›TsUsSržQržOs¢Rw¦Uz«Zv¦Zu¥[s£YRK;RK;RJ=RJ=RJ=RJ=RJ=RK;UN<VO=WP>XQ?YR@[TD\UE\UEb[HaZHaYLbYRe][kaiqftuj{zn„|pˆs‹s‰|p„uj{mbre]jc`k`_g_\c_Z^^ZY^YUaZRbZOe[Og[Ki\Kk\Ik\Gm]Fm]Fj^HicMqfTqZJuOB‹WJœ\P£YL¬ZL¥N=¦E4§<,±7(Â8-Ñ80Ü41Ý1-ß6/Þ7.Þ7.Þ7.Þ7.Û7+Ø6)×7)Õ8)Ô8)Ò9)Ò9)Ò9+Ò9+Ô8+Í;,½;+¶>-®9(©1 «2!·9*À</Ã9.È5+Î5-Ô:0ÛC8ÝK>ÔH9Â=,·2#ÝPFØJ@ÔF<ÔD;ÚG?ÞG@àC>à<:à74æ87ì::ì::ç77â64à93Ü=7ÙF>ÏB9Ä:/¼4(·1&¹5)¿;/Ã?3Ã:0É<3Ò?7Ú?:â=;ç;9ì89í9:ç7:å8:å8:å99å97å97å95å95à40á51ä65å76æ89ç9:ç9:ç9;ë;>æ9;ß99Ü=:ÖA=ÍB;ÁA6·?1©:)£<+ =*›<(™:&–:%—8&‘8&A0‹<-‡/#‘/&¼LJämoçloÍVZ½LNµNOªQM¢UO›XO—]R•aTŠjSWZ/:M7FTa)~ŽP›°oŸ¼yœ½x’µs~ah€L\mCYdBT]BHP9?D0:=,@C2<@1:>0<C3:C0AN2Rd>]sBgƒHpKo“Kl’Gj’Ii“KgJlPm‘So“Sl“Pm’Mk“Jm–Jm˜IqœLqžMrŸNr¡Pt£Rv§UyªYzª^y¦cm™ZbŽOWN?WN?WN?WN?WN?WN?WN?WN?XO@YPAYPA[RC\SD]TE^UF_VGd[JcZIcYMcYOf[Uj_]nbdpdhreltgpvirvirsfmm`gg[_bXY^[VZZRYVOWTKXTIXTH\VH^WGcZKcZIf[Ig\Hi]Gj^Fk_Gh`IcbMngTv\MSFšVM«VO°RJ·QE¯H9¬C0¬=,±9)¾8,Ë80Õ62Ù40Ý6.Ü8.Ü8.Ü8.Û7-Ú8-Ù7,×7+×7+Õ7+Õ7+Õ7+Ô8+Ô8+Ô8+Î:,¾<.¶>.®9(¦1 §2 ²:)¹>.¾<.Æ:-Ç4*Ë4)Ò;0ÜE:áOBßQCÝOCÛF?Ø@;Ó<5Ò;4Ö=7Ú=8Ü86Ý33ç77ë78î79ë78ç77å97â=9ÛB:ÊA7¿?4¸:,±6'¯5&²8)¸=-¿@1ÅA4Í@6×@7ß=8ã:7é77ï56ñ7:é6:ç6<ç7:æ89æ87å95å95ã:5â92ã:3ã:5ä;6ä;8ä;8å99å99ä:;ã;;ß<=Ù?=ÐA;Ä?6·=2¬;-¥;+ =*œ=+—<)–;(’:&“8%Œ:%€9'€8)‰5*<5¿QP×eeÑ^a·MM¬NL¤TMYP˜\Q—^S—^S˜_VŒfQ`_/Sg*^r3zO™¯n¥¾|™²pˆ bj‚HauBTe;JX7FP7?H59?158-:</AB4=?4:=2=A3:C0@M3Rd>^tEgƒHqMp”Ln’Hk”Hk“Lh‘Km‘Qn’Tm”Sm”Qk“Lk”Hl—Hm˜HpJpJp Ls¢Qu¦Tx©Xx©Xx¨^m—Xb‹QY‚H]TE]TE]TE]TE]TE]TE]TE]TE[RC\SD]TE^UF_VG`WHaXIaXGe]Je]Hd[Jd[Le[Qf[Uh]Yh][i]]j^^k__k__i^\f[YbWS_VQZVMWTKUQHRNCQMBSOCWQCXRB^WG^WEaYFc[Fg\Hi^Hk`JhbLbaMngUy_PˆXN¢[U±VQ²IE¸E@¹@7¸>1·;/¸8+¼8+Ã9,É;/Ñ:/Ù8.Û7-Û7-Û9.Ú8-Ù7,Ù7,×7+×7+Õ7+Õ7+Ô8+Ô8+Ô8+Ò9+Í;,À>0¶>.­:(£2 £3ª:&³>,¹>.Ä?0Ä8+Å2(È4*Ð<0ØF9ÜM?âNDÙ:6Ø43Ô20Ö42Û97á=<æ<<é;<í9<î5:ë27é26è58å99á<:Ù@:¾8-µ:+±6'®4%­5%°8(µ<+»=.ÈD5ÏC6ÖB8Ü=7â94è64í55ï79ê69é69é69ç77ç77æ95å95å:3ã:3ä;4ä;6ã<6ã;8â:9á99Þ88Ý9:Ü<<Ú@>ÓB=È?9¹:1­7+¥7(¡;,<+™<+”;)“:(9&7%Š9&z6#y6%ˆ9, F=¹SOÀWT¸PO«IF¢MHœSL™YP—^S—aW—aW˜_V‹ePsrFzV“¦n¤¹€«À‡ž³{|[[o<?R$AS+AP/?L2:C06<.69058/9;.@A3=?49<1=A39B/@M3Qc=`vGi…Js’Or–Np”Jm–Jm•Nk”Np”Tq•Uo–Sn–Pl”Kl•Il—Hn™IpLpLpŸNu¤Sx©Xyª[u¦Wq X_‰JVEOx>aXIaXIaXIaXIaXIaXIaXIaXI_VG`WH`WHaXIbYJcZKd[Ld[Je]Hf^Gf^Ig_Lg^Of\Pf\Rf\Sh]Wh]Wh]Wh^Ug]Te[QdZPc[N]YMZVJVRFRNBPL@PM>TN@UO?WQAYTA[VC]XDaZGd]Jf_LfaMdcQleUv\OŠ[Q©b\¸ZX¶FD¹<:Ã<8Æ;6Ä:0Â8-½8)¼:*¾?.Ç?/Ó9-Ù7,Ú8-Ù9-Ú8-Ø8,Ø8,Õ7+Õ7+Ô8+Ô8+Ô8+Ô8+Ô8+Ò9+Í;,Á?1¶>.¬9'¢3 3¢8"¬=)µ@.¼?-¿:+Ã7*Å5*Ë7+Î:.Î</Ó:2Þ65à24Û12Ý34à88å;<ê=?ì<>î7<ì38é06æ25å58á77Ú65Î61·5(¯7'®6&¬7&¬7&®9(²:)µ:*ÃA1ÊB4ÔA7Ü?8â;5æ95ë76î87í68ë78ë78ê86ê86è94ç:4æ;4â92á:2ß:4à;7Þ:8Ü:8Û97Ù99Ö<:Õ@<ÒC=ÊA;»;2¯6+¤6'ž7(œ;*˜;)”;)’;(:&8$Œ7#‡9%€=*v5#5'™E:ªPH¬MG©LG¦QLPJ˜UL”ZO”^R•aV•aV—aWgT‰…_™©x­½Ž¦¸ˆ¡ts†YN`8/A->3B%:F.=F3;A3:=2:=4<=599-?@2<>39<1<@29B/?L2Pb<awHj†Kt“Ps—Or–Lo˜Lo—Pm—Op—Ts˜Up˜Ro—Pm–Jm–Hn™Jp›KqžMrŸNt£Sy¨X{¬]w§[mSe’M[…FS|BNw=cZKcZKcZKcZKcZKcZKcZKcZKcZKcZKd[Ld[Le\Mf]Nf]Nf]Lf^Gg_Hh`IiaLiaNi`Oh_Pg^Oj`Ti`Qi`Qh_Nh_Nh`Mh`MhaNd^N`ZL[UGVPBPM>NK:NK:NK:PM<RO>TQ@VS@XWC[ZF]\H^]Kb`Qf^QmWJ†[R¬ic¾c`ÁMMÈBCÐ;=Ô89Ó84Ì70Á9+º=)µC+¼B+Î;+×7)Ø8*×:+Ù9+Ö9*Ö9*Ô8)Ô8)Ô8)Ô8)Ò9)Ò9)Ò9)Ò9)Í;,Á?1¶>0«:(¡6"š4›7 £=&¯B-³;*º;,Â:,Ç9-Ë8.Î:0Ð<0Ù:4é9<ì4<æ39ã28â38á48â38â25é6<é49ç4:ç7:ã9<Û76Ð21À0(´6(­:(­:(«:(«:(«:(¬9&¯7&¸:+Á;/Î>3Ø?7à=8ä;8è96ì87î66î66î66í74ê84è:3ç:3ä<3á:2ß;2ß<5Ü=7Û=:Ù><×=;Ô>=Ï@<ËB<ÅB:¹=5­7-£5(ž7(š:*–;)“<)‘;*:(Š9&‰8%ˆ7$ƒ8%ƒ@-u4"{3%“G:¤QI¡NFžNG¡WN˜UL’XLZN\N]R”`U—aWhW‰f˜¥z©€Žj]kHDS42@&$2-:&0;*5=.9?3<?4=?4:;3::099->?1;=28;0;?18A.>K1Pb<bxIk‡Lu”Qt˜Ps—MqšNq™Ro™QršSršSršQp™Mm—Km˜Ip›LsžOt Qv¢Sx§Y{©^z¨_qŸVd’JY†C]†JW~GRyBe\Me\Me\Me\Me\Me\Me\Me\Me\Mf]Nf]Nf]Ng^Og^Oh_Ph_Ng_Jh`IiaLjbMjbOjbOjaPjaPjaPiaNh`Mh`Kh`Kh`IiaJjbKf_Mb]J^XHXRBSM=MJ9KH7IH6KJ8LK9LM;NO=PQ?QTASVESVE[YL^VIcQEzXN¡ha¶eaÄVUÖQRÙ@Cß:>Ü87Ô70Ç;.¼?+±C*µA(Ê;*Ô7(Õ8)Õ9*Ö9*Õ9*Õ9*Ô8)Ô8)Ò9)Ò9)Ò9)Ò9)Ò9)Ò9)Í;,Á?1µ=/«<)¢9$™6•5›;"§A*²B.¹@/¿;,Ã7*Å2(Ë4+Ò;0ß=8ê5:î3<ë6=é8>ç:>ä;>ã:=â9<ß58á7:â9>á=>Û=>Ñ96Ã2/¶0'¯9+«>*«>*©>*ª=)©<(©:'«8%¯6%¹7)Å;0Ð=3Ù<5ß<7ä;8è;7î87ï75ï75î85ë:4é;4ç<4ä=4â>5à?7ÞA:ÚA;ÙA>ÕA?Ñ@=ÍB?Ã?:»@8³=3ª8-¡5(›5'˜8(•<*‘<(<)Š<(ˆ:&†9'„7%ƒ6$6%}:)t3!~9*—OA£YNœRG•OE˜VJ•YN[MŽZMŒ[MŽ\Q“_T™`W‘gW~xXyƒ^r|ZU`B;E,/;%0;*0<.3=24<16<2<?6@A9?A6<<077+89+>?1:<17:/:>07@-=J0Oa;bxIk‡Lu”Qu™Qt˜NqšNršSpšRršQsœPr›Op™Kn™Jp›LsžOv¡Ry¥X{§Zz¨]x¦]qŸWg”O]ŠEX‚B`‡NY€ISzCg^Og^Og^Og^Og^Og^Og^Og^Og^Og^Og^Oh_Ph_Ph_Ph_Pi`OjaPjbOjbOjbOi`Oi`OiaNiaNiaNh`Kh`Kh`Ih`IhaGhaGhaGf_Le^Kb[I\WDWR?PM:LI8IH6IH6GH6GJ9GK:HL;IM<HO?JN?PQCXRF[OCiRDƒYM–VL°SLÔZYÛIJâ?Bá99Ú72Ï;1Â?-´B*³<$É:)Ó7(Ô8)Ó:*Õ9*Ó:*Ó:*Ó:*Ò9)Ñ:)Ñ:)Ñ:)Ñ:)Ñ:)Ñ:)Í;,Á=0µ=/¬=,£<)˜8 4•9 ¡A)­C-¶A/¿=/À6+Â/%Ç0'Ð6.Þ:8è59î6>ì;Aê@CæAEàBCÜ@AÙ??Í12Ð66Ó:<Ñ=;É;9¾71µ4.­4)ª<-¥>+¥>+¥@,¥>+¤>(¦;'©:'ª7%²7'¼8+Å8.Ì70Ô94Ü=9æ>;é:7ì95ì95ë:4é;4è;4å=4â>4àB7ÝC9ÚC<ÕB;ÏA=Ê?<Ç=;¿>9±<3©:/¢8+œ6(˜7'”8)‘:):(‹=)‰<*‡<)ƒ:'‚9(€7&6%}6$x3#x5%…B2˜UEWK”PCPD•ZL“YMZN\Q\Q’[T•\U˜[VŽbUkbEV`=JS4=G,6?*2=,4>35?66=68?8=B;CF=EG<CD6@>/;9*78*=>0:<17:/:>06?,=J0N`:awHk‡Lu”Qu™Qt˜Nr›OršSpšPq›OrNqœMp›LošKrNu¡Ty¥X«`~©az¥]qœUf‘L_‰G]‡E^ˆIb‰PY€ISzCh_Ph_Ph_Ph_Ph_Ph_Ph_Ph_Pg^Oh_Ph_Ph_Ph_Ph_Pi`Qi`QmdUlcTjaRi`Qh_Pg^Og^Mh`MiaNiaLiaLiaLh`Ih`IhaGh`IgaKf_Le^Kb[H\WDUR?PM<ML:EF4DG4DH7DH7BI9BK:BK:CJ:IK>SQDSPAXL<eK:tE5•H>ÂYSÙROàDEà::Ü75Õ;3Ê>/º=)µ8"É:)Ï8'Ð9(Ò;*Ò;*Ò;*Ò;*Ò;*Ñ:)Ñ:)Ñ:)Ñ:)Ñ:)Ñ:)Ñ:)Í;,Á;0¶:.­<,§>+™9#3“7Ÿ?'£9#°;)À>0Æ<1Ì8.Ð7/Ù<5ã?=ì=Bî?DéCEâDCØ@?Ê;7À50»1.¹/,¼51¾:6»<6³:2ª8.¤8,¡:+¡=-Ÿ>+ ?,¡@-¡@-¡A+£>*¦=*©;*­:(²7(¹5)¿5+É90Ô?9àC>ä=7è;5è;5ç<4å<5ä=4á>5Þ@5ÜD9ØD:ÒC;ÊA9Ä?:¼;6¸85°93¢7-™8(•5'“6'‘:)<*‹:)…8&ˆ=*…<+ƒ<*€<)~:'|8%{6&{6&y1#€8*ŒG8–SC’PB‹L=RE™_S‘WL’YN”[R–]T–]V—\V–YTŒ^QaX;EM(6@8A&>G2<E43=2-7.1817>6BE<GJ?HI;BC1?>*;:&78(=>09;069.:>06?,<I/N`:awHj†Ku”Qt˜Pt˜Nr›OršSpšPqœMqœLqœLp›Kp›LsžOw£V{§\‚­e€«dx£\j•P]‡EYƒA^ˆHePcŠQZJSzCi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi_Sj`Wj`Wj`Wj`Wj`Vj`Vj`TjaRjaRjaPjaPjbOjbOjbOjbMjbMg_Jg_Jf^Ie_Ib]IZXCSP=ML:HI7EH7AE6@D5?E7?G8?G8>F7GK=IK=AE4KJ6\Q;aE/ƒJ9Àj]Üg`ãUSäFEåA@àA=Õ>5É=0Æ=-Æ4%Ë5&Ì6'Í7(Í7(Î8)Ñ;,Ó=.Ò<-Ò<-Ò<-Ò<-Ñ;,Ñ;,Ñ;,Î<-»2(¶7.±;/¨:)š7"”5•7œ9"®A-³;+º6)Á4*Í6/×<7âC?êHEèDEèEHÝCCÉ:6»61´80®8.©4+«9/©9.¤8. 8-›9,–:+“<+’<+–=+˜=*™>+š?,›@-A, A-£@-¢;*¥:(«7(°6)¶6+¾8-Ä;3Ì<4Ú=6à=6â?8âA7âA9ÞB6Ú@6×@5ØH=ÒE;ÇA8»;2²5/ª3-¦1*Ÿ2+™9-’;*‘9+:*‹:)ˆ;)‡;+…<+€9'}8(|9(|9(|9({8'y6&x3$w- ‹A4—OC“MAŽL@‘QE“UJŽRG˜\R˜\R‘UMTL—ZU•XS”WT“gZ]T5IQ*AK(BK,>H/7@+2=-4?14<1HPCZ^PW[JMO:DF.AA'==%:;)>?1;=2:=2;?14=*;H.Pb<cyJlˆMu”Qs—Or–Lp™MršSq›QpLqžKrŸNrŸNsŸPv¢U{§\ªb‚­fx£^j“O`‰G^‡Ea‰JfŽPhTd‰S[€LTyEi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi_Sj_Yj_[j_[j_Yj_Yj`Wj`Vj`Tj`TjaRjaRjaRjaRjaRjaPjaPh`Mh`Kg_Jg_Jd]J^YEUR?ON:IJ:EH7BD6@D5?E7>F7>F7=E6@F8>E5>F1HL5MI0U?'Q:¼sbËcXà_ZëUTçGGâ@>àA=Ø?9Î:0Í9+Î=,Ï=.Ï>-Ï;-Î;+Í9+Í:*Î:,Ð:+Ð:,Ð:+Ð:,Ð:+Ð:,Í;.É?5ÅB:¼B7®=/Ÿ6#–1–1›2¨5#´6(Ã;/ÑA8ÞE?æIDéJFêKHÞCAÛEDÐB>¼<3¯9/©=0¥?1Ÿ=0Ÿ?1œ>2—>0”>/=/Š>.‡@.ˆ?,Ž=*‘;*’<+“=,”?+•>+—>,™>,Ÿ@.¡=-¤:,¨8,¬8+±8-¶:0¼9/Ê<2Ð<2Ó?5ÕA7×C9ÖC9ÔD9ÑE8ÊB6ÃA4¹=3°:0©6/¤5.Ÿ4,™7,’:.Œ=,Š;,ˆ;+†:*„;*ƒ<*<,~;*|;){9)y:)x9(w8'u6'w4$€4'D7˜NC‘K?ŒJ>QD’TIRG“SJ˜XO˜UO—VPœ]X”WRŽSOŠcT]W5Xc9_jBZfBIT66C)4@*<H4OXGYbQ_fTW\FJN7BE*??#<<"9:(=>0:<19<1<@25>+;H.Pb<dzKlˆMu”Qt˜Pr–LqšNršSq›Qs OrŸLpLqžMt Qx¤W|§_ªc{¦aošUcŒJ]†Da‰JeNeNc‹Od‰S[€LTyEi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`Qi_Uj_[j^^j^^j_]j_Yj`Wj`Vj`Tj`Tj`Tj`Tj`Tj`Tj`Tj`Vj`TlaOk`LiaNiaNf_La\HYVCSR>MK<HI9DF8AE6AE6?F6>D6<D5=F57@-@H1IK3F?%W?'‡ZC·s`¶VJÙbZðebìTSæFHëGHæCDØ;6Ë7+É:*É:,É:*Ë9,Ê8)È6)È6'Ê8+Ë7)Ë7+Ë7)Ë7+Ì8*Ì8,Ë8.Â3-¾5/¶6-­3(¡0"ž/ 1 §3$¼A2ÇC7ÖG?áLFçLGéJFåFBßD@×CAÏDAÁ@:±;1¤</¡A1œE4˜E3“D3‘D4B2ŠA0…@0‚A/~A.@.†:*‹9+‹:)Š;*‹<+<+Ž=*<*–?.–=-›;+9* 8+¥9-©;.®9/¸:.¾90Á;0Ä>3Å?3ÅA4ÃA3ÁA4¶;,²:,ª8-£7+ž6+›6,˜7.”:/;/ˆ<,‡;-„;,‚:+€;+€;+<,};+z;*z;,w9*v8)u7(t6)v4&ˆ;1”F<–NBŽJ=ˆH<OB‘SH’RI’OI™TO™SQšVSš]Z“YU[Tl[}X~‰^€‹alxRLX46D#=J.LZ@eqYeoW]eMPU>EI0?C(=@#:<$89';<.8:/9<1=A36?,<I/Pb<dzKm‰Nv•Rt˜Ps—MqšNršSpšPu¢Qp LnLpŸOu¤Vy§\}¨`|§`p™UfM\„E\„EeŒMiQfN_…HcˆRZLSxEi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`QjaRjaRjaRjaRjaRjaRjaRj`Vk`\k__k`^k`\k`ZkaXkaWkaUkaUkaUkaUkaUkaWkaWkaXkaWnbRmbPjbOjbOhaNe^K_ZGYVERO>MK<GH:DF8CE7BD6@D5<C3<H49B-FJ3LF0O:%qL9šcOª^N¡A5ÆSLçc_ð^^íSUëJOéDHáAAÌ91Ç9-Å7+Å7+È8-Ê:/Ë;0Í:0Ñ>4Ñ>4Ò>4Ò>4Ó?5Ó?5Ó?5Ó>7Ó?;ÑB>ËD>ÆF=ÂF<ÃI>ÇM@ÎPDÙSHÝPGâMGäIEäGBâC?àA=ÛB=ÕFBÉD?º?7«=0Ÿ>.šC0•G3”I6F4ŒE3ˆC3…B1€A0}@.zA.|?-‚:,†8,…9+†:,†:,‡;-‡;+ˆ;+‰:+‹:)Ž8)8*•9,™;/=/¢<0ª:/­8.¯9/°:.°:.°:,¯9+®8*¨7)¥7(ž6)›6*•7+’8-‘9/Ž<0ˆ<.…<-ƒ;,9*:*~9*}:*};+|:,z;,y;,w9*s8*q6(r7)u5)ŒD8’H=‘K?ŠH:‡I<‹OD“TK—TL–QL›TP˜QO”SO–\X_Xh^—~j””p…’fu‚WYh?CR+?O*P`<crQetU\iKNX=CL1?E+>B)=A(;='78(:;-79.9<1=A37@-<I/Oa;bxIlˆMv•Ru™Qt˜Nr›OršSo›Pq OožMožMs¢Rz¨]|ªaz¥^u [cŒJ^‡E[ƒD_‡HgŽOj‘PfL`‡Hb‡QY~KRwDi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`QjaRjaRjaRjaRjaRjaRjaRj`Tk`Zk`\k`ZkaXkaWkaUkbSkbSkbSkbSkbSkaUkaWkaWkaXkaWocUnbRkbQkbQkbQhaOd]M`ZJXRBRO>NK<IG8GE6DE5BC3>B1=I1?G/LG1S>+g=-•ZL­_Sœ>4ž3+¶?9ØXUóigödeëRUæJNèNPÙIAÓF<Í@6Ë>4Î@6ÒD:ÕG=×G>ÚJAÚJAÝJBÝJBÞKCÞKCàKDàKEëSRèTTãSRÜROØSLØSLÛULàUNàLHáIFãEBâC@äB@âC@äEBàFDÙJFÊE@¹>7¬=2¡?2˜B1’E3G4ŒE3ˆC3‡B3ƒ@0?1~?0z?/|>/€:.ƒ9.ƒ9.ƒ9.ƒ;-ƒ;-ƒ;-ƒ;-9*‚9*…9+‡:*‰:+;-<.“;/›;/ž9-Ÿ9- :,¡9,¢8+¢8*¢8*Ÿ8)œ8)˜8(”8)9*Œ:,‰:-‡;-„:-‚:,9+€8*}8)}7+|8+{9+z8,y9-x:-v8+q5*o5)q7+t9+ŒH;I<‰I=„I;…K?ŒRF“WM˜XO˜QMœUQ˜RP“VQ’`YˆbWƒg[†zdnrQZi@JY0AR(IZ0Wi?dvNj|VXiEL[:?L.:D)<D,>C-<A-:=,89+:;-68-8;0>B47@-;H.L^8_uFi…Jt“Pt˜Pt˜Nr›Os›TpœQnMmžLp¡Pw¨Y|ªaz¨`rXi“QZƒA\„EaˆIeŒMgŽMgŽKhJfŠLc†PY{IRtBi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`QkbSkbSkbSkbSkbSkbSkbSkaUlbYlbYlbYlbVlbVlcRlcRldQldQldQlcRlcTlbVlbXlbXlbXocUnbRkbQlcRlcRkbQg`Pd]M^WGYSCSM?MJ;KH9GE6EB3?B/;H.?H-OC-a=-ƒE:«XR´NJž,+²:9¹=;ÓSRòpnþvvñehëX^ð^_ãUQÚPFÔG@ÐC:ÑD=ÔG>ÖGAÖG?ÙHCÚJBÜHDÝJCÞJFÞKDàKGàKGâHHáGIÜHHÙGGÖHD×HDØGBÛFBàBAäABæBAèBBçCBäB@ßA>ÛA?ÛJGÊC?¹<6®>3¢@3˜A0A0C3ŠA2ˆ@1‡?1…=1‚<0€<1=1=1;1;1ƒ:3;1;1€</€<1=/€</€=-<-€;,‚:,9*‚8+ƒ7)Œ:.8.‘9-”:/—;.š=.ž</Ÿ>.™9)–9(“:*:+Š;,‡;+„;,‚:+‚:,9+7)~6({5){5){5)z6)x6*x8,w9,s8*p4)o5)r:-v<0‹M@‰K<„J<‚M=…QC‹VH’XM—WN“NI™TO—VR•\Ue\{^PfVF`^GEO->N)?O(J\2_rEk~QexKXj@M_7@Q-6E&7D*=F1=D4:>07;-8:,:;-68-8;0>B47@-9F,I[5[qBeFr‘Ns—Os—Mr›Os›TpœQmœLo Os¤Ux¨\z¨`s [i“Q`ŠHYB^†GeŒMgŽMfJeGgŒGhŒLa„NXzHQsAi`Qi`Qi`Qi`Qi`Qi`Qi`Qi`QkbSkbSkbSkbSkbSkbSkbSkbSlbXlbVlbVlcRlcRldOldOldMldMldMldOldQlcRlcTlbVlbVocUmaSkbSlcTmdUlcTi`Qf_Ob[K^WGXQARL<OI9JG6IC3AC.:G+@F*U@+xG9£PL´HH¶8<º39ÈAEÄ?@ÊJIßb`ñqpðnpçbeâZ\ÛQNÕLDÏD?ÍB;ÏD?ÐE>ÐB>Í@9ÒC?ÒC=ÔC@ÕD?×CAØD@ÙECÚDCÝADÝADÞDFÝGHßIHàJIãIIåGHçACêADìBEìBEçAAâ@>Û=:Õ=:ÚFFÉ=<¹83°;2¦@4˜?1‘>0@1Š</Š<0‰;1ˆ:0‡81†93…:4„;4;3€<3:4€<3<3}=1}=3|<0|<0|=.|=.{<-|:,{9)z8*{7*‚:.ƒ9.‡9-‰9.9.‘;.•<.–=-’9)‘:):*Š;*‡;+ƒ<*€;+~;*€8*€8,~6*}5)|4(z4(z4*y5*u2)v6,v8-r6+o3)o5*s:/x@3ŠPB†N?N=‚Q@…VF‰XIŽUJ‘RI’OI—TN“TO‘\VŠfZn[JPK7EK1AN0KZ9WgC`rJgyOdzLVl>G]/AV-7H$1@!6B*=F5<D98=67:39;.;<.68-9<1?C57@-7D*FX2Vl=a}BoŽKq•Ms—Mr›OtœUqRožPq¢Qu¦Wv¦Zs¡Yj—RbŒJ[…E^†H`ˆIcŠKeŒKfJfŽHf‹Fe‰I`ƒMWyGPr@i`Qi`Qi`Qi`Qi`Qi`Qi`Qi`QkbSkbSkbSkbSkbSkbSkbSkbSlbVlcTlcTlcRldQldOldOldMldMldOldOldQlcRlcTlcTlcTnbTmaSmaSmaSmdUlcTjaRg`Pd]M`YIZSCUN>PK8MH5ID1DC.@F*DD([B,ˆTG¯\X«>A¬/3ÎINÆ?EÀ;>»;<ÄFGØZ[ãefÛY[ÌGHÑJGÐEBÍB?ÐB>ÔFBÔFBÐB>É>7ÏD=ÏD=ÒD@ÔEAÖEBÙECÚDEÝDFÞ?Cß@DÞBEßEEáGGàFFàBCá>?èBDé@Cè>Aç=>ä>>ãA?áC@ßECÖBBÅ98¶50°;4¦?6š>3=/?2ˆ</‡:0‡:0‡81‡83‡83ˆ94ˆ;5‚92€:2€:2;2~<0~<0|<0|<0x8,y9-z:.z:.{<-{<-z;,z;,};/9/€7.7,„6*…5*†7*†7(Š8*ˆ9*ˆ;+…<-„<-€=,~<,};+}9,}7-|6,y5*z4*x4)x4)x5,r2(u5+v8-r6+n4)n5*t;0xB6ŠUG„PB€O@„SDˆWIŒVJŒRGNG•TN”UN‹RK„XMdSeZDGK2=J,IX9ZkIgxTcxQYnESh=Nc8J_4:L&0?,95>)=C5<B8:<79<5;=2;=079.9<1?C58?-8B)EU1Uh;azCnŠMr“Nr–NršQsSqRr Uu£Xv¤[t¢ZošUfN^ˆH[ƒDdŒNa‰K`‡HcŠIfJgŽKeŠEa…E`ƒMWyGPr@i`Qi`Qi`QjaRjaRkbSkbSkbSjaRjaRjaRjaRjaRjaRjaRjaRkbSkbSkbSkbSkbQkbQkbQkbQlcRlcRlcRlcRlcTlcTlcTnbTk]Pp`QtdWrdWnbVj`Th`ShbTgaSgaQc]M[VCVQ=SN8NH2GA)KG,I?$bI3‘gW¶xm»le¸YW¹OO¶BE½BEÂDGÈFHÍHKÐJKÒLMÓKMÏEEÐDEÓEDÖFEÕFBÒC=ÎC<ÏF<ÈD8ÉE9ÊE<ÌE?ÒD@×CCÚADÛ?CáBFâBDáCDàDEàDEáFDãEDäDDãCCäB@ä@?ã?>ã?>â@=â@=ÞC?ÓB?Å>:¶93ª70Ÿ:0—=2Ž>3†>2ƒ?2?3=2;3„93†93ˆ81ˆ92…;0„<.„<.ƒ;-;/€:.€:.€:.~:-~:-~:-~:-};/};/};/};/}90}90~80~8.€7.€7.€7.~8,€8,~8,}9,|8-{9-z8,x8,x8,x8.x8.w7-v8-v6,t6+s5*q5+m1'r6,n4)m3(o6+m4)q8-I=…SH…SJ†RGˆRH‹PHQJ”QK•RL“TMTK‰[N‚cQo_HYU:MV7O_;j~YbxQYoHUjCRg@Mb;IY5CR17D&4>#2:#6;'<>0@B5@@6<>358-39-4:.7:/9=/;?.=B,<E(S_;arFn†Rs‘Ur•QršSsSrUužZ~§e~§ep˜YbŠK^†G^†H^„G`†IbˆKdŠMfNeŒMcŠK`‡H^„G_‚LTvDKm;i`Qi`Qi`QjaRjaRkbSkbSkbSjaRjaRjaRjaRjaRjaRjaRjaRkbSkbSkbSkbSkbSkbSkbSkbSlcTlcTlcTlcTlcTlcTlcTnbTrbSqaRrbUqcVrfZogZmeXjdVgcWkhYoiYlgThcOc^H[U=RL4PJ0H>%Q?)kP=XG„NBˆE=“D?±WV¸TT»QQÀPOÀNMÁMMÂLJÃKJÐTTÈHGÂ=>Ä=:ÐB@ÓEAÏB;Ç>4ÊD9ÉE9ËF=ÍG>ÔFBØDDÜAEÝAEáBFáCDáCDâDEâDCáFDáFDáFDàEAßD@àB?ßA>àA=àA=àA=ÞC?ÔE?ÈC<º>6¬:0 90”:/Š<0‚<0€A2~@3~>4<3ƒ:3†91ˆ81‰90‡;.…<-„;,„;,ƒ;-‚:,‚:,‚:,9-9-9-9-~:/~:/~:/~:/}90}90}90}90}90}90|90|90|90z:0z:0y9/y9/y9/w9.w9.w8/w8/t8.s7-r6,r6,p6+p6+k1&q7,m4)l3(o6+l3(p7,~H>„RI…SLˆQJ‹PJQL”PM•QN”SOŠOG…SH€[K{ePsiPhkLfqQgxT^tMXnGPf?La:K_:K\:GV7CP4<F-8@)6;'7:)<=/?@2>>2;=04:03;05;16<0:</<?.>A,>D*Q[9^mDkRpŒRp“Sq™RsSsžVxŸ^|¢exžak‘T`†I]ƒF]ƒF\‚E`†Ia‡Jc‰Le‹NdŠMbˆK`†I^„I]LRtBIk9i`Qi`Qi`QjaRjaRkbSkbSkbSjaRjaRjaRjaRjaRjaRjaRjaRkbSkbSkbSkbSkbSkbSkbSkbSlcTlcTlcTlcTlcTlcTlcTnbTqdTo_Pm]Pm_RqeYog\ldYhbVfbWpl`{xi~{jyizubpiVe`JTT<FF.C>(MB.S@/U8(]7*l=3n70u91~;5‡@:’IB›PJ¥XP­ZR»^YºSN·GE¼B?ÈDBÑFCÓD@Ï@:ÐA;ÐC<ÒE>ÔG@ÖGCÚFFÛEFÞDFßCDßCDßCDßCDàDEßEEßECßECÞDBÞDBÜD?ÛC>ÜC=ÜC=ÜC=ÛC>ÖE@ÎE?ÃB<³>5¢:1”8-ˆ9,ƒ;-‚@2@1~>2=1ƒ:1†91‡81ˆ:0‡;.„;,„;,„;,‚:,‚:,€:.€:.9-9-~:/~:/~:/~:/~:/~:/}90}90}90}90|90{8/{8/{8/{;1{;1z:0y;0x:/x:/x:/x:/t8.t8.s7-s7-r6,q5+o5*o5*j1&o6+m4)k2'l6*i3'm7+}G=‡PI‡PI‹PJŽQL‘RM’SN”UP‘VPŒWO‡[P~^OtbNoiQorUlwYfwUQeBK_:EY6DU3EV6GV9GT8ER8?I0<D-9>*7;*:<.<>0;=/9;.6<25=26<26<0:</<>0=@-=C)MW5Zi@f|MlˆNn‘Qq™RuŸUtŸWz¡`w`n”Wc‰L\‚E\‚E\‚EZ€C_…Ha‡JbˆKdŠMc‰La‡J_…H]ƒH[}JPr@Gi7i`Qi`Qi`QjaRjaRkbSkbSkbSjaRjaRjaRjaRjaRjaRjaRjaRkbSkbSkbSkbSkbSkbSkbSkbSlcTlcTlcTlcTlcTlcTlcTnbTrdWoaTm_Rk_SkaWlcZle[jf]jf]xtiˆ„x‘•’Œ~‰‚r}zg`cNOT>DG2FE1HC0F=,L<,VC4P9+R6*V4*Y5)\6+a9/e=1m=1ŠKBSJ®UO¶NK¼FDÆABÒDCØFFÔ@>ÕA?ÖB>ÖE@ÙECÚFDÛEDÝEDÞDDßCDßCDÞDDÞDDÝEDÝEBÞFCÛFBÚEAÙD>ØE>×D<×D<ÙD=ØE>ÔD<ÓF?ÉF>ºA8§;1˜8,Œ9+…<-ƒ@0@1~?0=/ƒ:1„:/‡9/ˆ:.„:-„;,„;,ƒ:+‚:,‚:,9-9-9-9-~:/~:/~:/~:/~:/~:/}90|90}90{8/{8/{8/{8/x8.{;1y;0y;0y;0x:/x:/x:/v:/s7-s7-s7-r6,q5+o5*o5*m4)j1&o6+l3(h2&k5)h2&l6*|F<ŠOGŒOJPKRMSN‘VPWPŒZQ‰]R…aUy_PiZGd_IdhO\fKN]@EU8AQ4=M0<K.?M3CQ7FR:GS;BL4>G2:A/9=,9=.9=.8<-7;-7=36=56;46<2:<1;=/<?,<A*JS4Ve>bxIj†Ln‘Qs›Tw¡Wx¢Z{¢ar˜[e‹N\‚EZ€C\‚E\‚EZ€C_…H`†Ia‡JbˆKa‡J`†I^„G]ƒHY{HNp>Eg5haQhaQhaQibRibRjcSjcSjcSibRibRibRibRibRibRibRibRkbSkbSkbSkbSkbSkbSkbSkbSlcTlcTlcTlcTlcTlcTlcTlcTrfXth\sg[mcYjaXle]snhzwp~w† ”¨¥œ«§œ¨¤™£‘™–‡z{kcfSOR?GJ7EH5BE2BE2EH5KL<JH9GD5D>0A9,A7+@6*F4(S5*qB8ŒPHŸPK¯IG¿GIÌEIÓBEÖ@BÙ@BÚBAÛCBÚEAÚEAÜDAÜDAÞDDÞDFÞDFÞDFÞDDÝEDÝEDÚDCÚFB×FAÖF>ÔE=ÔE=ÓF=ÔE=ÕF>ÔA:ÓC;ÎE=ÁB9®>3ž9-‘;.Š</„?0?/€>.€=-;/„:-†:-‡;.„:-ƒ;-‚:,‚:,‚:,9+9-9-~:/~:/~:/~:/}:1}:1}:1}:1|91z:1|91y90y90x8/x8/v7.y:1y:1x90v:0v:0u9/u9/t:/r7/q6.q6.p5-o4,n3+n3+m4+j1(m7-j4*h2(j4*f2'j6+}D;NGNHQJTLUOŽWPŠYRƒ[Q{YMv\OkZJ]UBYXDY^HOYA?K3:H/7E.4B+5A+8D.=I3CL7EN9BK6?H5<E2:A/8?/7>.5<,4:,5<45<56;56;49;0:</;>-;@)GP1Ra:`vGi…Ko’RuVy£Yy£[xŸ`n‘W`ƒIZ}C[~D^G^G]€F`ƒIa„Jb…Kb…Ka„J`ƒI_‚H^IW|ILp@Bf6haQhaQhaQibRibRjcSjcSjcSibRibRibRibRibRibRibRibRkbSkbSkbSkbSkbSkbSkbSkbSlcTlcTlcTlcTlcTlcTlcTlcTkaUndZpf\lcZkd\rmg„|‘Œ§£ µ±®ÄÁ¼ÉÆÁÉľÅÀºÀ¹±¹²¨¤œˆ‚rfcTPQ?FI8>E3;D1:F28E38E38C2:B3<B4@C8CE:GE9>4(P:/e?6€EA¡PO¿X[ÊQVÊCIÕFJ×EFÚFFÛEDÝEBÞDBÝD?ÞCAÞDDÞDFÞDFÞDFÜDCÜDCÜDCÚDCÙEA×FAÕF@ÓF=ÒE<ÐF<ÑG=ÔE=Ô?8Õ@9ÐC:ÆC9¶A7¥=0–=/Œ=.†>/‚?/>.€=-<-ƒ;-„:-…;.‚:,‚:,‚:,‚:,9+:+9-~8,~:/~:/~:/~:/}:1}:1}:1}:1z:1z:1y90y90x8/v7.v7.t8.w8/u9/u9/s9.t8.s9.r8-r8-q6.q6.p5-o4,n3+m4+l3*j4*h2(l6,i3)f2'h4)e1&i5*{B9ŽMG’MH‘PJTLVO‡XNYO|\Qs\Nk[L`WFVSBPSBJQ?@I64@,3<+2;*09(09(2;*5>-9B1<E4?H7?H5>G4:F2:C25@/3>-1<,3:33954954928919;.;>-;@*CL/O^7]sDi„Mp“SwŸYy£Yw¡Yq˜Yf‰OZ}CX{A\E]€F]€F^G`ƒI`ƒI`ƒI`ƒI`ƒI_‚H^G]€JV{HJn@Ae7haQhaQhaQibRibRjcSjcSjcSibRibRibRibRibRibRibRibRkbSkbSkbSkbSkbSkbSkbSkbSlcTlcTlcTlcTlcTlcTlcTlbVj`VjaXkdZkg^upjˆ…€Ÿžš°°®ÃÂÀÓÒÐâáßçæäçãàãÞÚÞÖÓÙÎÈμ²¸¥——‡zym]_YIKL:@E1;D/>J6=H7=F5;C4;>5:;3983880==3C<2N71a84ˆHH¯]_Å`dÊWZÍQSÓNO×MKÛKJÞHGáFDãDAâBBßCDÞDFÞDFÝCEÝCEÜBDÜBBÛCBÚDCØD@ÕD?ÓF?ÒE<ÐF<ÐG=ÔE=Ù@:Ù@:ÑB:ÈC:¼C:¬@4™;/Œ9+‰=/ƒ>.>-€=,<,ƒ;,ƒ;,„<-‚:,‚:,‚:,9+9-9-~8.}9.~:/~:/}:1}:1}:1}:1}:1}:1z:1y:1x90x90w8/t8.s7-r8-t8.s9.r8-q8-r8-p7,p7,p7,o6-o6-n5,n5,m4+l3*k2)i3)g1'k7,h4)e1&h4)b0%f4)zA8MG’MH‘PJŽULˆXNYOxZOr\Nk^N^WGRPAKN=CJ:9B12:+08)17+17-06,/5+/5)08+2:+4<-9D4;F5=H7<I7;F56C13@,1>-.800621622717829;0:<.:?)?H+KY5[qChƒLq“Vwž[w¡YsœVgP^IUx@WzB[~F[~F[~F]€H_‚J_‚J_‚J_‚J^I^I]€H]€JUvIJk@@a6haQhaQhaQibRibRjcSjcSjcSibRibRibRibRibRibRibRibRkbSkbSkbSkbSkbSkbSkbSkbSlcTlcTlcTlcTlcTlcTlcTlbVpg^ng_lgatqj‡„¡ œ¼¼ºÎÐÏÏÏÏàààòòòúøùû÷ö÷óðñìéðâßèÐÆÝÁµÁ«¢‘}ub^\GKM7CH2AI2AI4@G5BD7AA7B=9C97?:69<5>?7B71J.+d66QR³giÆnmÀ\\ÇZWÐVS×QNÜLKâHHæCDåBCßCFÝDFÝDFÝCEÝCEÜBDÜBBÜBBÜDCÙCB×C?ÓD>ÒE>ÐF<ÐF<ÔE=ÜC=ÛA9ÒB:ÊD;¿E:°A6œ:-‹5&Š</…=.‚=-€=,<,<,ƒ<*„<-‚:,‚:,‚:,:+9-~8,}9.}9.~:/~:/}:1}:1}:1}:1}:1{;1y:1w;1x90u9/t8.s9.r8-q7,r8-q8-p7,n8,p7,m7+o6+o6+o6-o6-n5,m4+l3*j4*i3)h4)f2'k7,h4)c1&e3(b0%e3(xB8ŽMG’OIŽRJŠUMƒXOzZOq[Mh\LZTDIJ:?C4>D69A208+/7*6<247058157247025.06,08-19,6A3:E5=J9>K9<I77F32A,1>-,6..400511606718:/:<.:?+<E*IW4YoAg‚Mp’Vv\užXp™U`†IX{CSv>WzB[~FY|DY|D\G_‚J_‚J^I^I^I]€H]€H]LStIHhA?]7icSicSicSicSicSicSicSicSicSicSicSicSicSicSicSjcSkbSkbSkbSlcTlcTmdUmdUmdUmdUmdUmdUmdUmdUmdUmdUldWibZgd]vsn“’Ž¯¯­ÄÆÅÚÜÛêîïóôöö÷ùüüþÿÿÿÿÿýÿûúü÷ôýòì÷áÔòØÇàʵƶŸª ‡ˆ„ibbFEH-DH/CG0FE1GD5F<3C60F42H:9>=9>?:B:7I75cGF’jjÅ“’è­©Ò‹‡ÇtnÀ]XÆSPÕONßJLçFKéFKßEGßIKÚDFÔ;>Ø>@áGIàDGÖ:=Ú@@Ú@@ÙA@ÖB>ÔC>ÑB<ÐA;ÑA9ÞC>ÛA9Ó@9ÊA9¾B8°@5Ÿ:.8*Œ=0‡>/ƒ>.>-€=,<+=*<,:+9-9-~8,~8.|8-|8-|8-|90|90|90|90z:1z:1z:1z:1y=5w<4w;3u:2t91q8/p7.p7.o6-m7-m7-l8-m7-l8-m7-m7-i2+m6/p92o81k4-g2*g2*h3+e0(i7.d2)]-#`0&_/%b2(s>6ŒOJPKˆQJRJ}YMv_QfYITN>DE5>E5:C25@04</3;04:04:068378366446135016//6..6+.9+2=-6C2:G6:I68G44C.1?.*4+,2.-2..3-45/79.:</:?+6>&DR1WlCh‚Rs”]wž_r›Wk”R[~DXyDTu@Tu@WxCZ{F[|GZ{F^JbƒNbƒN^J\}H_€K`L^~MTsJFd@<W4icSicSicSicSicSicSicSicSicSicSicSicSicSicSicSicSkbSkbSkbSlcTlcTmdUmdUmdUmdUmdUmdUmdUmdUmdUmdUldYgd]onj†…ƒ¤¤¢ÁÃÂÖÚÛêîï÷ûü÷ûþøüÿüýÿþþþÿÿýÿþúÿüùÿúóÿðàûëÔíßÅ×ͲÀ»¤£„‚ƒcgjKLQ3GJ/EC.GB/H>2I:3L95K<9?:7BA?MHE]RPyjg£Ž‹Î²®èÅ¿ÿ×Ñ쳪͂|Àc^ÊVVØRSßHMÚADÖDEÕEEØHHÜJKÛEFÕ<>Ö<>ÛACÙ??Ù??ÙA@×A@ÖB>ÓB=ÒA<Ó@9ÜA<Û@;Ó@9ËB:¿C;±A6 ;1’8-Œ=0‡>/ƒ>.>-€=,<+<,<,:+9-9-~8,}9.|8-|8-|8-|90|90|90|90z:1{;2{;2z;2w<4u<3u:2s:1r90n8.o6-m7-m7-l8-l8-l8-l8-j8-l8-l8-k6.n70o81n91l7/i4,g2*e3*c1(d4*_/%^.$b2(^.$`0&p>5JE‡RL‡XR~XOrVKdRDQJ:>?/:A16A05@03>04<14<15;17:379477577557246116//6./7,.9+1<.4A08E49H58G44E24B1.8/.5..3--2+23+46+8:-9=,7?(DQ3WlEh‚Ut•`wbp˜YgPY|DWxCTu@Tu@VwBYzEYzEYzE[|G_€K_€K\}H[|G^J^J^{MSnKF_A9R4icSicSicSicSicSicSicSicSicSicSicSicSicSicSicSicSkbSkbSkbSlcTlcTmdUmdUmdUmdUmdUmdUmdUmdUneVneVlfZed_y{x—™˜²¶·ÍÑÒäéìôùüúÿÿûÿÿüÿÿýþÿþþþÿþüÿÿûÿÿúÿÿôþüçúùÝïðÑáåÄÒØ´¼ÄŸ ¨ƒ‡‘l`gEQX7DG,DC.JD4LD7KA8I>:C;9SJKia_wvœ’¾³¯ÙÎÈêÜÓþèÝÿóéÿäÛð©£ÃecµBEÇDIÚRVÛUTÑMKÊDAÍCAÔFE×EFØDDÙCDÖ=?×>@Ø@?Ö@?ÖB@ÓB?ÓB=ÔA:Ù@:Ù@:ÒB:ËE<ÀD<²B7¡<2“9.Ž<0‰=0ƒ>/>.=-~<,€=-€=-9-9-~:/}9.}9.|8-{8/{8/}:1}:1{;2{;2{;2{;2z;4x<4t;2q;1r90o9/n8.l8-l6,k7,l8-j8-j8-j8-j8-i9-j8-j8-n91m80m80m80m80i7.f4+c1(d2)a1']-#_0&c4*\-#_0&sD<}PJVP~YQpTI^J?OC7?<-46(2;*0;+1<.2=/4<14<15;17:379479668357257227007/08-.9+0;-2?.6C27E48G48G49F54>33:2/4-,2(/0(13(57*6:)6>'DQ5YmJk„Zw—fwœfl“Z_‡KWxCWuCTr@Tr@VtBXvDYwEYwEZxF]{I_}K]{I]{I_}K^|JZwKPgJAW@3I2icSicSicSicSicSicSicSicSicSicSicSicSicSicSicSicSjcSkbSkbSlcTlcTmdUmdUmdUmdUmdUmdUneVneVneVneVmg[jkfƒ‡ˆ£§¨¹¾ÁÐÕØåíïóûýõýÿûÿÿûÿÿüÿÿüþûýþùþýøÿþ÷ÿÿóøýæôýÞî÷ØæòÎÝéÃÎÚ´¶Á™¡«†€‡efmLMQ6FH0HJ5KI:HD9D@7OGDj`_‹€¤š™·¯¬ËÆÂÞÛÔêæÝúñèÿûñÿ÷íÿÝÕû¶±åŽÊcd°@?ÃPKÉTMÉPHÃF@ÈGBÔMJÖKHÐ@?Ò>>Ó??Õ?>ÕA?ÕA?ÓB?ÓB=ÓB=Ö>9Õ@:ÒC=ÌE?ÀE>²A9¢=5•;2Ž<1‰=0ƒ>/>.=/~<,€=-€=-9-9-~:/}9.}9.|8-{8/{8/~;2~;2|<3{;2{;2{;2z;4x<4r90o9/o9/m9.l8-k7,j6+h6+i7,i7,i7,h8,h8,h8,h8,h8,m;2l7/j5-i7.j8/j8/f4+a1'e5+a2(].$a2(b3)Y* a2(yNE\VvWRiNGWC:I<3@:.:;-8<.08)/:*3;.4</5=25;169069079468368349238139/19.19..9+/:,0=,2?.5B17E4;H6<I89C87?428.-3'.0%/1$24&48'4<'ER8]pPr‹d{šnwœifXU|ESsATr@Sq?Sq?Tr@UsAVtBVtBWuC[yG]{I\zH^|J_}K\zHWrIJ^E<M;.?-gdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSicSjcSkbSkbSlcTlcTmdUmdUmdUmdUmdUmdUneVneVofWofWng]qqo‡Œ¤©¬·¿ÂÍÕØãíïòüþôþÿøÿÿúÿÿûÿüýÿúüýõúüñùùíöøêöÿèóÿäðýáîùÙéôÒÞèÅÊÒ­¶¾™£}‚bY]BGL5FK7FJ9CG9AC8c^X…}z«¡ŸÁ·µÌÂÁ×ÏÍæáÝñîéú÷ðüõëÿñæÿòéÿôìÿåß騤·hc¦LAµPDÁYNÇ[QÆQH¿D=ÂA<ËDAË@=Î@>Ð@?ÑA@ÒC?ÑC?ÑC?ÒC=Ó?;ÒA<ÐE@ÉE@½C>¯@9 >5•=3Ž<1‰<2ƒ=1=0=1~<.=/=/~:/~:/~:/}9.|90{8/{8/{8/|<3|<3|<3{;2z;4y:3y:3w;3n70m80l7/l7/k6.h6-h6-h6-i7.h8.h8.g8.g8.g8.g8.h8.k92h6/f4-g5.i70h70e4-b1*c2+b3+_0(]1(]1(V*!b6-{ULtYRaNHN?8A7.=6,;9-9;.9<139-3;.5;/6<06<07:/58-47.69049238139/39/2:/19.19,.9+.9+/:,0;-3>.6C2:E5<G7=E8:B55;/06(02%/1$13%15$2:%GS;buWwk|›rq•g\‚QJp=Oo>Qn>Qn>Qn>Qn>Qn>Sp@Sp@WtD[xH]zJ]zJ_|L_|LZwGRlECU?6D7(6)gdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSicSjcSkbSkbSlcTlcTmdUmdUmdUmdUmdUneVneVofWpgXpgXng]lnmz‚…“š «µ·ÄÎÐÜæèí÷ùôþÿõþýöÿüúÿúûÿùûþóøúíõöèñôãôýêôýèôüåóùßòöÛéìÍØ×¹ÅÄ¥±°’ŽregOMP;GK:FM=GOBKQG{{sŸš”ž¸ØÎÌßÓÓçÛÛóëéü÷ôðïêüüôÿÿöÿýóÿùïÿóêÿçÞÿ×ËØ“ƒ¸eS£J:´TF½WIµF;·@8ÈKEÇC>ÊC?ÌB?ÎC@ÎC>ÎC>ÍD>ÍB=ÑB<ÒC?ÎDAÅD?¸A=ª=8ž;5•<4Ž;3‰<2ƒ=3=2=1~<0=1=/~:/~:/~:/}9.|90{8/{8/{8/|<3{;2{;2z:1y:3x92w81u91n70l7/l7/j8/i7.i7.h6-g7-g7-g7-f7-f7-f7-f7-f7-f7-h70h6/g5.g5.g6/h70g6/f5.a2*e6.^2)\0'`4+a5,i=4uSJXG@E>6<5/95,;8/8:/57,36+69.5;/7:/69.69.58-57,36+28,19,28.19,19.19,19,19,.9+.9+.9+/:,0;+3>.6A17B2<D5:C28?/5;-46)24'13%04%07%HT>ex\umt’lf‰_RwKBh9Kk<Nk;Pm=Pm=Nk;Nk;Ol<Qn>VsCZwG]zJ^{K_|L_|LVsCNg@<J9/:2$.&gdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSicSjcSkbSkbSlcTlcTmdUmdUmdUmdUmdUneVneVofWpgXqhYoh^hikmtz…Œ”£¬±¿ÈÍÓÞâåðòðüüòüûõþùøÿ÷úÿöüÿòúýìøùçö÷çööê÷÷ëú÷èüöæýõâ÷ìÖçØÃÕƯÀ±šž“}un[ZWFPQCSVK[bZeld•˜²²¨ÕÎÆèÝÙóããúêëÿñôÿ÷ùÿþûþýùùúôøúïüüðÿÿóÿÿñÿûêÿæÑñª”¼o[©VD®RCµPD»LA¿JAÂG?ÆE?ÉE@ÊE@ÊE>ÉD=ÈC<ÉB<ÑC?ÐE@ÌEAÂC=³=9¦;5œ;5•<6Œ<3‡=4…<5=4<3~<0=1=1~:/~:/~:/}9.|90{8/{8/{8/{;2{;2y:3x92w81v70v70s7/n70j8/j8/j8/j8/h8.h8.h8.g7-f7-f7-f7-d8-d8-d8-f7-g6/h70i81i81h70i81j;3l=5g80k<4b6-a5,oC:xLCtI@nMDC9056.45/8918;247.14+25,58-58-57,46+46+46+46)37)/7(.9)08+.9).9+.9)08)08)19*08)08+/7*08+19,3;.4<-9B1:C0;B0:A1:<.68*24&/3$29'IU?cu[m„gf„bWyTInE?d8Hg;Li;Nk=Nk=Li;Li;Mj<Ol>UrDYvH[xJ]zL^{M\yKQn@G_;6B4+4/#)%gdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSgdSicSjcSjcSjbUlbVlbVmcWmdUmdUmdUleUmfVngWogZnh\oh^mjekormtzƒŠ¥®³ÃÌÑÕßáæîñôüþôýú÷ýùùþ÷úÿôüÿòüÿñüÿîýþðûøïþúñÿùïÿúíÿùéÿòßïàËÝηÁ²›¢—|ubb_NXZL]cWmwn|†}¦¬¢¾ÀµÚÕÏîäâüîîÿôôÿô÷þôõÿùùûúøûýøüÿúøÿõôüñ÷ýñÿÿñÿùèÿôãÿÌ»½p›QD©SFµSH²C:ÀJ@ÄH@ÇG>ÉF>ÈE=ÇD<ÆC;ÆC;ÍG>ÌG>ÇG>¼C;­>5Ÿ:2—:3‘>6Š=5†=4ƒ=5=4€<3;0;1;1~:/~:/~:1}90|90{8/{8/{8/z:1z:1x92w81v70u6/u6/q6.m80k90j8/j8/i9/i9/i9/h8.g7-f7-g7-f7-f7-f7-f7-f7-f5.j81m;4k:3j92j:0m>4oC8l@5oD;d<2c=2zVJ†dZyWMbLA>:13814927<54:0/5+25,9<336+25*24)03(13(25*46+47,.6),7)/7*-8*/7*/7*/7*/7*19,08+08-/7,/7,/7,08-08+7?09B1<E4=D4;A38<.26'-4$2;*JVBcr[h|a]vXOmKFfAAa:Hd;Kh<Nk?Nk?Kh<Jg9Li=Nk=TqEWtHZwK[xL]zNZvMNjAC[;2>4*00!''heVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVjdVjdVjdXldYlcZmeZmeXmfVg`NjeRnkXol]he\feasqr‚†Š“–ž¡«³¶ÃÈËØÝàêîïö÷ùùûúüþýÿÿÿÿÿúýþöúýòùüñúýòûþóùúòúúòùùíüúëþüçù÷ÞéåÊÕÔ¶º¹›˜˜|qtY_dMbkXr}l„‚‘›´¹²ËÌÇãâÞòîë÷óòû÷öýùøüø÷ÿþüÿÿÿÿÿÿýÿúýÿúýÿúýÿúýÿúúü÷ÿÿúÿûöÿêåا £`X›D=µNE¿MCÂF<ÄD9ÊE<ÎI@ÎKAËH@ÇG<ÆG8ÃH8½J8³H8¦E5šB4‘B5‹C5†B5„B6‚@4‚>3‚<2„:1†91†9191~:1|91{80z7/z7/w7.v6-w7.w7.w8/w8/v7.u6-q5+n3+l7/i81i81h70h70g6/g6/g6/f5.g6/i70i81h70g6/e4-e3,h3-j5/m80k90j:0i;.i=0kA3gB2jI:dH:v_O~k\ŠxjŒoRJ=79.4:04:039/39/39/28.28.17-17-36-06,25,/5+14+/5+/4-.5-/4-.5-/4-/4-/4-/4-05./4-/4-.3,.3,/4-/4-/6.1;23=26@58B79D67B45@03>-4?.DQ=WeN[kQPbHEX<@T8BW6Ic>Ke>Kf=Kf=Je<Je:Lg>Oj?UpGYtK\vO]wP]wRWqNHb?;O6&1+&+.$),heVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVjcYkdZlc\md[meZmfVidQjhSmjWjk[gg_lll~„“š¨¯µ¸ÂÄÑÖÙâçêòóõûüþÿþÿÿþÿþýûÿþúÿÿúþÿùûüôøûòöüò÷ýó÷úñøúïøúí÷úéùûåòõÚßâÃÌЯ´¸—‘–vkqUYaI_kUtoŒ˜Š§œ¿Á¼ÓÓÑççåóóñ÷÷õûûùüüúûûùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûÿþõÿÿõÿÿþÿÿÿûúÿíêð¿ºÂ~uŸKA¯M@¹K>ÂL@ÅK>ÃE9¼=4¾?6ÃE9ÄF7ÃJ7½L:³J7¦F6™C2‘C6E7…C7‚B8A7>5‚<4ƒ:3†91„93~92}:2z:1z:1y90x8/v7.v7.u6-v7.t8.t8.t8.r6,o5*m4+l7/i81j81h70i70h70g6/g6/e3,e3,g5.h6/j81j81j81k92k60l71k90k;1k;/j>1j@0iD2gH6kP=gRAufSueŒ…s†ƒrKL<69.39/39/39/28.28.28.17-17-17-17-06,06,/5+/5+/5+/4./4./4./4./4./4./4./4.05//4..3-.3-.3-.3-/4./6/.80/:21=34@66B66B66B45B16C1CP>P]IR`IIW@AO6BP7EU:Jb@Jd?Ke@Ke>Ic<Hc:Id;Je<SnEXrK\vQ]wT[tTTmOC\>6I5&1-',0$)-heVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVjcYkdZlc\md[meZmfVmhUkiTmjWjk[kkcwww‘’—§¬²¿ÆÌÒÜÞìñô÷üÿþÿÿþÿÿÿþÿÿþÿþýûÿþúÿÿúýþøúûóõøïñ÷ëñ÷ëô÷ìõùëöøêõøåõ÷áîîÔÛÛ¿ÉÊ«°±’“–yuy`kpZt{iˆŸ§œ®µ­ÍÏÊÞÞÜïïíøøöûûùÿÿÿÿÿÿýýûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿþûÿÿûÿÿÿÿÿÿûùÿúôÿ÷íýÑÆØž”L=¥RB©L;§@1·G;ÏYMÏUJ¼@4ÄD7ÆH:ÃK=¹I;ªB7?3—@7“E;‰@9„A9A8?6~>5}=4<4}=4z;4x<4w;3w;3u:2t91t91t91r90r90r90r90o9/n8.l6,k5+l7/j81l71j81k60i70i70h6/g5.g5.f5.g6/h70i81k:3k:3l;4j;3i:0h<1k?2jC4iD2fE2fI7lVAjYGujV†m‘Ž{€mEF658-28.28.28.17-17-17-06,17-17-17-06,06,/5+/5+/5+/4./4./4./4./4./4./4./4./4./4..3--2,-2,.3-/4..5.+5-+6.-9//;12>24@46B47D38E3?L:ER>CQ:;I28F->L3DT9J_@Jb@LdBKc?Ia=G`9F_8G`9RkDXpL]uS^vVZqTPgK=T8.A.&1-',0%*.heVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVjcYkdZlc\md[meZmfVniVljUlkWlm]pqi€‚ž¡¦¹¾ÄÄËÑÛåçõúýûÿÿüýÿüýÿþüýüûùÿþüÿÿûÿþùûüôõöîîñæéïãçíßíñâðôåñôãñôßòñÜêêÐØؼÇÇ«²±•¡¡‰‘“}“€œž‘ª­¢¶»´ÀÅ¿ÜÜÚééçööôûûùýýûÿÿýÿÿýýýûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþûûýÿþÿÿþÿ÷òïüóìÿüóÿûíÿôäìñ¹p•P@¢O?¸XJ·M?µE9ÀG<ÁA6ÈE;ÅI?½G=¯@9¡<4š=6–A<?;‡@<@:}@;z?9y@9x?8x?8v=4v=4v=4u<3s=3r<2r<2q;1o;0o;0o;0n:/k9.j8-j8-i7.m82n72m61l71l71k60i70i70l:3j92h70f7/f7/f7/h91f:1j?6f>4e=1f>2hC3iE5fG3cG2cJ6hV@i\IskV…‚oŽzpq_9=,47,28.17-17-17-06,06,06,17-17-17-06,06,/5+/5+/5+.3-.3-.3-.3-.3-.3-.3-.3-.3-.3--2,-2,-2,-2,.3--4-*4,)4,*6,+7-.:.1=14@25A36C2:G5<I78E12?+2@)8F/>N4DX<E]=K`AJb@K`?G_;H^:F^:SiEXpN`uV^uXZnSLcI9M4):(%.+%*.$)-heVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVjcYkdZlc\md[meZmfVlgTkiTnmYop`tum…‡†¤§¬¿ÄÊÎÕÛæðòûÿÿûÿÿûüþýþÿÿþÿþýûÿÿýÿÿûþýø÷øðïðèçêßàæÚÞåÕçëÚêïÛíðÛìðÙîîÖèèÐÚØ¿Ìʱ½§·µ ±®¶´§Á¾µÉÈÃÒÓÎÙÙ×èèæòòðúúøüüúýýûÿÿýÿÿýüüúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþüÿüÿÿüÿÿûúüû÷ýÿùþÿøþÿôÿÿíÿþìøßËÅœŠbP–J:¥L<´P@¼L>¿@7ÆC;ÅF?¿D=±>;¦:7ž:8™>;‘=;Š?<„@=}@=xA<tB;rC;rC;q?6s>6s>6r=5r=5r=5p>5o=4n>4m=3l<2k;1j;1i:0i:0j:0n72p62p62m61m61l71i70i70l;4k:3h91e90e90e90e:1d<2fB6cA5cC4dD5dG5dI6bI3_I2^L6eV?jbMrmW„ƒo„‡r[`L3:(28,28.17-17-17-06,06,06,17-17-17-06,06,/5+/5+/5+.3-.3-.3-.3-.3-.3-.3-.3--2,-2,,1+,1+,1+,1+-2,,3,,6.+5-*4+*4++5,.8-0;-2=/2=-5@/6A05A-1=)1=)5A-8F/=O5@T8FY=H\@J]?I^=K^>K`?TgG[pQcvZauZYkSJ]G6G4&4%$-*$),"'*heVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVjcYkdZlc\md[meZlfVjgTkjUlnYorasvm…„Ÿ¤¨¸¿ÇÖßäí÷ùûÿÿûÿÿûüÿþÿÿÿþÿþýûÿÿûÿÿúüüôóõêëíâãçÙÝãÕÜãÑäéÓçìÕéíÖêìÔëëÓèæÏßÚÄÓθÓκÌƶËøÐÉÁ×ÒÎàÛØêæåðïíóòðùù÷ýýûýýûýýûÿÿýÿÿÿüüúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýüÿûüÿøùÿþýþÿýùÿýôÿøïÿõ÷ÿöð÷çÿÿíÿîÛͧ”¤kX¢[GªWE­L;¹D:ÀD<ÀE>¼E?´A>ª=:¢:9š;9•=<?=…?=}@;wB<rC;oD;oD;p?8q?6q?6q?6q?6p>5o?5o?5o@6n?5m>4k<2h<1h<1h<1l<2o83q62p62p62m61l71j81i81h70g80e90e:1d<2e?4f@5dB6^B4aG8cL:dM;cM8`K6]K3]M4]Q9bX?mhRss[€ƒnv{eFO:4=*39-28.28.28.17-17-17-06,17-17-17-06,06,/5+/5+/5+-2,-2,-2,-2,-2,-2,-2,-2,-2,,1++0*+0*+0*+0*,1+,3,.5.,6.*4+*4+*4++5,-7,.9+-8*0;+4?/5@/4?.3?+3>-3@,7F/9K3@O8BT:GW<H[?M]BM`DUeJ\oSdtZ`rZXgRGXE4B1$2%$-*$),"'*heVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVjdVjdVjcYkdZlc\md[lfZlfVliVlkVkmXkn]lrhy}• ®·¾ÒÛàêóøûÿÿùþÿùúþþÿÿÿþÿúù÷ÿÿûþýøùùññóèéëÞãçØßæÖàèÓäéÒçíÓèìÓçéÑèèÐçåÎàÛÇ×оÚÓÃÙÑÆÞÔËæÝØîäãóéêúñôÿùûúù÷ÿÿÿÿÿýýýûýýûÿÿýÿÿýüüúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýüÿùøÿúùÿþýüÿý÷ÿþîÿùñÿÿòÿýõÿúóùëÿýíÿúèôλ«wbQ:ª\H²N>¸J=¹I>¸I@´G@¯D>¥=:œ:7–=9>:ˆ?9€A:xA:tD:pF:pE<p?8q?8q?8q?8q?8q?8p?8p?8qB:pA9n?7l=5i=4j>5j>5n=6o83r73p62p62n72l71j81i81d8/d90c;1d>3d@4cA5cA5_C5ZE4_N<gVBhWCcS<^O8[O5\R7]V<]Y>nlUtv^|kcmU2>(6B.3;.39/39/39/28.28.28.17-17-17-17-06,06,/5+/5+/5+-2,-2,-2,-2,-2,-2,-2,-2,,1+,1++0**/)*/)+0*,1++2+-4-+5-*4,*4,+5,+5,,6+-7,,6+/:,2=/6A17B27B15@04?.2?-4C.:G3=L5AO8DT:JX?L\BTbI\kTcqZ^mXUbPDRA2>0#/%&/,&,,$**heVheVheVheVheVheVheVheVheVheVheVheVheVheVheVheVjdVjdVjcYkdZlc\md[lfZlfVolYmlWjlWgkZflbr{xŽ˜š¨±¸ÊÓØãìñøýÿ÷üÿúûÿþÿÿÿþÿúùõÿÿúýýõøøîðòåéëÝåéØãêØåíÖæìÒéíÒêíÒèèÎèæÏèãÏáÚÈØÐÃØÎÄÝÒÌêßÛøíëÿôöÿõùÿõûÿùüþüýÿÿýÿÿýÿÿÿÿÿÿÿÿýÿÿýýýûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþúÿýûÿýûýüúùýüøÿÿõÿÿóÿÿíýúóÿûûÿúÿÿôÿöæÿóßÿãÍ×­•©oY¨WD®Q@¬O>¯OA±OB­K@¤C<™<5•>7?6ˆ?8A7{B7uD6qE8qE8p?8q?8s>8q?8q?8q?8q?8p?8sB;qB:o@8m>6l=5j>5m>6o>7o83q73o83m82m82i81h91f:1f;2d>3d@4bB5`C5]A3\@2WB1TG4[S>f^Gi^HbX?ZS9ZS7\W:ZV;XW;kmUsw^u}fUaI&28D03;.4:04:039/39/39/28.28.17-17-17-06,06,/5+/5+/5+-2,-2,-2,-2,-2,-2,-2,-2,,1++0*+0**/)*/)+0*+0*,1++2+)3+*4,+5-+5,,6--7.-7,,6+.8-2<16A39D69D47B46A10;+2?-5B09F2<I5@N7FT=JX?R`I[hTanZ\iWQ^MBN@/;/",#+1-(.,&,,heVheVheVheVheVheVheVheVifWifWifWifWifWifWifWifWkeWkeWkdZle[md]ne\mg[mgWqn[kmWknYkr`jrgpyv‡‘“Ÿª°»ÄÉÐÙÞêïóöûþüýÿþÿÿÿþÿþýùÿÿúþþöúúðöøëôöèðôãêñßçïØðöÚíòÔëîÑììÒèæÑáÜÉÝÕÈÞÔÊáÔÎèÚÙòääúëîþòôÿ÷ûÿùÿÿüÿÿýþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿúÿþúÿþúÿÿÿýÿþûÿÿúÿÿúÿÿûÿÿþÿÿÿÿýÿûõÿûñÿüìÿûåÿóÛÿêѺ‚i¦bK—O9ŸT?¨ZF£RAJ: M?Ž<0‹=0†>2=/x=/t?/sB3tD6r=5r=7t=8s>8u@:u@:t?9q?8sA:q@9q@9q@9p?8n?7o>7o>7k92m82l:3l;4l;4h<3g<3c=2c?3^>1dG9cG9T=-N9(M8'@1WQ;PN7KI0OK2]Y>ieJeaDVU7XX<]`CosZz€fdlU=I1-9#2>*4</5;14:04:04:039/39/39/39/39/39/28.17-17-17-06,16016005/05//4./4./4./4.,1+,1+,1+,1+,1+,1++0*+0*.5.-4--4-,3,-4--4,.5-.5-+2*-4,07/5=29A6=E8?G<?J<7B25@/3>-1=)2>*9E/BN8IU?O[GVbN]hWYdTLWI<F;/9.)3*)/+(.*'-+heVheVheVheVheVheVheVheVifWifWifWifWifWifWifWifWkeWkeWkdZle[md]ne\mg[khWonZjlVknYkr`jtiq|x‰”–£®´¾ÇÌÒÛàëðô÷üÿýþÿþÿÿÿþüþýùÿÿúþþôûûï÷ùëô÷æðõáêòÝèíÖêïÑæëËäçÊææÌãáÌßÙÉÞÔÊßÔÎçÙØíßßöçêûïóÿôøÿ÷ûÿúþÿüÿÿþüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿüûÿúûÿúýÿüýþÿþýÿÿüÿÿüÿÿûÿÿüÿÿüÿÿûùÿüõÿþñÿÿíÿûåÿòÙÿëÑà±—§u\‘[C–ZBšYCšVC™R@ŽG5‹F6ˆE4ƒD3|C2wB0s@/o>/u@8s>8u>9u@:t?9o=6p>7sA:q?8p?8p?8n?7o>7m>6n=6m>6k<4l=5m>6k?6j?6gA6eA5bB5dG9[A2^G7^I8N=+F7$G8%>6!LL4GK2DG,DD*MM1\[?baC`aBYY=dgJsw^sy_X`I:D,/;%7C/6>16<26<26<25;15;15;15;15;15;15;14:04:039/39/39/27127127116016016005/05/.3-.3--2,-2,,1+,1++0*+0**1**1*)0))0))0)*1*+2*,3+.5--4,.5-07/4;39A6?F>BJ?;F8:E57B14?.3?+6B.<H2@L6LXDS_KYdTWbRLVK=G<1;2+5,+2+*0,)/+heVheVheVheVheVheVheVheVifWifWifWifWifWifWifWifWkeWkeWlcZmd[md]ne\mg[khWmlXimVjoYktaiuiq}yŠ˜™¥²¸¿ÊÐÔÝâíòö÷üÿüýÿýÿþÿþüþýøÿÿ÷ýýóúûí÷úéô÷äïôÞçïØåëÑâçÉÞáÂÝÝÁÞÞÆÞÛÈÝ×ËàÕÏäÙ×îââóçëúîòÿôøÿ÷ûÿùüÿúýÿýýÿþüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿüøÿüøÿüûÿþþþþÿüÿÿúÿÿùÿÿøÿÿùÿÿúÿÿúúÿýøÿÿöÿÿóýÿïÿúæÿôÜÿøàÿìÓ㿧¬‚j‡YB…Q;’[F—]IS?F2u>)r>)s@+tD0vE4q?4n<3q<4tB9sA8o?5qA7xH>n>4m>4m>4k?4l=3j>3l=3j>3g?5gA6gA6gC7eC7cC6`C5^D5bK;UB1ZI7`S@RG3C;&E=(FA+AE,BH.AE*<@%?B%NN2_`AijKaaEnqTvzagmSJO94<%4=(=F38>28>48>48>47=37=37=36<28>48>47=37=37=36<26<26<25:449349349338238238227105/05//4..3--2,,1++0*+0*).().().().().(*/)+0*,1+160/4.,1+,1+/4.6;4=B<AH@@K=>K:<I78E34A-2?+3A*5C,DQ=KXDQ^MQ^MHTH<H<2>4.8/-4--4-,3,gdUgdUheVheVheVheVifWifWifWifWifWifWifWifWifWifWkeWkeWlcZmd[md]mf\mg[jiWkmXimVjoYjs`hthm|wˆ˜˜¦³¹¿ÊÐÓÜãëðööûÿüýÿýÿþÿÿýÿþùþþöýýñúûí÷úçô÷âîóÜåíÕâèÎÛàÀ×Ú»ÖÖ¼Ù×ÂÝ×ÇÞ×ÍåÛÙìààøìðûðöÿõûÿùþÿúýÿûûÿûûÿýüÿþüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿüøÿþøÿþûÿþþþþÿüþÿúÿÿùÿÿøþÿúýÿùüÿùùÿýúÿÿøÿÿøûÿõüþðÿÿíÿüéÿñßÿðÝÿïÚñÒ½´z{R>tJ4uH3vI4zM8}P;yL7nA.e7'{M@sD:qB8sD:oC8g;0d8-g=1i?3i?3i?3g?3f>2f>2f>2e?2b@4bB5`C5_C5_C5[D4ZE4WD3XI6OB/\T?oiSc^HMK4IG0IK3>D*?H-@F*<B&<A#FI,XY:deFmmQxx\tv^[_F@C.6;%8@+<C19?39?59?58>48>47=37=37=39?58>48>48>48>48>48>48>47<67<67<66;56;56;55:45:438238216005//4.-2,,1+,1++0*+0**/)*/)+0*,1+-2,-2,05/.3-+0*+0*-2.2718=9;B:@K=@M;@M;=J68E13@,1?(0>'<I5BO;HUCIVEDPB;G;2>4.:0,6..5.-4-gdUgdUgdUheVheVifWifWifWifWifWifWifWifWifWifWifWkeWldWlcZmd[md]mf\mg[jiWkmXinWiqZgs_drejyt…••£²·½ÈÎÒÛâêïõôùýûüÿýÿþÿÿýÿÿúÿÿøÿÿóþÿñûþë÷úåðõÞæîÖâèÌÙÜ¿ÕÕ¹ÒйÔѾÚÔÈàÙÑêàßôéíþóùÿ÷üÿûÿÿýÿÿþÿÿþüÿþúÿþúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿþúÿÿúÿÿûÿÿþþþÿýþÿüüÿûüÿûúÿúøÿù÷ÿú÷ÿýøÿÿûÿÿûýÿúýþùùôîÿþöÿýôÿüóÿûîÿ÷çûæÕéÒÀÏ´¡¶š…”uawVCjI6lI6rM;uP>mF7iB3gB2jE5nI9nJ:oK;oK;eA1cB1cB1cB1cB1cB1bC1`C3]F6\G6\G6ZG6ZG6WH5TG4RG3PH3NH2fdM~~frrZVX@JL4GM3<E*<F+?H+AG+AF(DG*MN/TU6xw[{z^nnVUU=CB.?A,>A.:>-;>39?59?58>48>47=37=37=38>48>48>48>48>48>48>48>49>89>89>88=78=78=77<67<66;55:449338227105//4./4.-2.-2.,1-+0,+0,,1--2.-2.,1-,1-,1-,1-.210513764;4;G9=L9@O<@O:<K67F/4C,2A*6E.:I4@O<BP??M>8F71?2-9-,6--4,,3+fcTfcTgdUheVheVifWjgXjgXifWifWifWifWifWifWifWifWldWldWlcZmd[md]mf\mg[jiWkmXinWiqZgs_bpcgxr‚””£²·ÀËÑÔÝäëðöõúþûüÿýÿþÿÿýÿÿúÿÿøÿÿôÿÿóÿÿïúýèò÷àçðÕâèÌ×Ú½ÑѵÍË´Î˺ÖÏÅÞ×Ñìâã÷îóÿöþÿúÿÿýÿÿþÿÿÿýþÿúþÿùþÿúþÿúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþýþÿýþÿýþÿýÿþýÿüþÿúþÿùÿÿ÷ýýóýýõþýøþýùÿþüþþþÿýÿÿþÿþùÿÿüÿÿûüûöóÿøòÿýôÿþñÿýíÿúéÿòà÷äÓçÒ¿Ò»©¹ŸŽŸ„srb|]K}^LtUCaD2W:(Y<*[>,X=*`E2`E2`E2_F2`G3`G3`G3]H3\K7\M:\M:YL9XM9UM8RL6QL6NL5QQ9ikS|€gmqXSY?FL2>G,<F+<F+?H+DJ.EJ,DG*HI*LM.yx\tsWfdMTR;LI6KJ6FE3<=-<?49?59?59?58>48>48>48>47=37=38>48>48>48>49?59?5:?9:?9:?9:?99>89>89>88=78=78=77<66;55:4493382382/40.3/-2.,1-+0,+0,+0,+0,+0,,1--10.21/32/32/32-4-3?17F3=L7@O:?N7<K49H18G05D-8G0<K6>M:<J97E61?2-9-+5,-4,,3+fcTfcTgdUheVheVifWjgXjgXifWifWifWifWifWifWifWifWldWldWlcZmd[md]mf\mg[jiWjlWhmVhrZfs_cqdhys…——¥·»ÇÒØÚãêðõûøýÿüýÿýÿþÿÿýÿþùÿÿõÿÿòÿÿðüÿìøûæïôÝâëÐÝãÇÕÕ¹Î̳ÉÄ°ÊÄ´ÐÉ¿ÚÒÏéàãöíòÿ÷ÿÿúÿÿýÿÿþÿÿÿýýÿúüÿøüÿøýÿúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþÿüÿÿüÿþýÿýÿþúÿúøÿùøÿõøÿóøÿóøÿõüÿøýÿúÿýþÿüÿÿüÿÿûÿý÷ÿÿúÿÿüÿÿýÿÿþÿþýûûûóúøìÿÿñÿÿïÿÿíÿþíÿýíÿöçÿîàýêÛúãÑÿæÐãÊ´¥xt^GeO8bL5\H0^J2\J2\J2]K3]K3^L4^L4\M6YN8WO:WO:VP:UO9SN8PN7NN6MO7PT;`fLfoTU^CEO4AK0<F+?I.?I.CL/HN2HM/FI,OP1YX:qmRjfKa\F[V@YTATQ>MJ9BC3=@5;A7;A7;A7:@6:@6:@69?58>48>48>49?59?5:@6:@6:@6;@:;@:;@::?9:?99>89>89>8:?9:?99>89>88=78=77<67<6495273162/40-2.,1-+/.+/.,0/,0/-10.21.23.23-12+1-+9*/@-6H2;M7=O7<N4:L2:L25G/7I1:L6<M::K96G70@3-;.,6--4,,3+cdRcdRdeSefTgeVhfWigXigXhfWifWifWifWifWifWifWkeWkeWkeWldYle[md]mf\kg[jiWikVhmWhrZgt`dqgj{u‰›ª¼ÀÍØÞàçíôùÿúÿÿûÿÿýÿüþÿúþþöúúîúûíúûéøúåóöáêî×ßãÊÙÜÁÓѸÌDZž¬Æ¾±ÌüÖÎËæÝàôêòþ÷ÿÿúÿÿþÿÿÿÿþþþýÿúüÿúüÿúýÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþüÿþüÿþüÿýþÿüÿÿüÿÿýÿýÿþûÿüøÿù÷ÿ÷÷ÿõøÿöùÿöüÿøÿÿûÿþýÿüÿÿûÿÿúÿÿúÿÿûÿÿüÿÿþÿþýÿûýüüþûþÿúùúòúüñýûïþúîÿùìÿûìÿýïÿýìÿðÛÿûâìÜÞŽu`P7RB)XH/XH.[M3[M3ZN4ZN4[O5[O5\P6[Q8UO7TO9TO9QO8PN7NN6KM5IM4JN5LR8S\APY<@I,;F(AL.?J,DM0EN1JQ2MR4JM0LL0YY=kjNhdIb^E^ZA_ZD`[GZUAQO:KJ8AB4@B5@B5>A6>A6>A6=@7=@79?59?59?5:@6:@6:B7:B7:B7;@9;@9;@::?8:?99>79>89>7;@:;@9;@:;@9:?9:?8:?9:?98?87=94;4382160/4..3/.3/.21.21,2.,2.+2++2*,4))6%$6)=!2F+8L1;O6;M5;M5;M58J29K3;M5<N6<N87H51B0.<--7,.5--4,]eN^fO_gP`hQdiUfiVgjWijXfgUhgUheVheVjdVkeWkeYlfXlfXkhYmg[kg\mf^jf[ieYgeVghVfkUenYbo]dqhsƒ€–¨¬¹ÈÏÒÛââéïò÷û÷üÿùýüüþùüÿöüþñ÷øêùúêø÷åòñÝîíÙëêÖàßÊ×Ò¾×ϼËÀ®½´¥½³©Â·±ÌÂÁÛÒ×êàèúóûüöÿþûÿÿþÿþýÿüýÿüþýüþýþþþþþþþþþþþþþþþþþþÿþüÿþüÿþüÿþüþýùþýùÿüùÿýúÿþûÿþûÿþÿÿýþÿüýÿýþÿÿÿþÿýýÿüûýøÿÿûüý÷ÿýøÿþúÿýøÿúöÿ÷ôÿùùÿúÿÿüÿÿýÿÿÿÿÿÿÿýÿüýÿþüþýþÿÿÿÿÿÿþüÿþùÿþóÿþîÿýêÿýæúôÜÿúàçàƈlUN2WN1TK.SJ+WN1WN1UN1UN1TO2TO2TN4SO4QM2RN5QO6PP6MO7KO6HM6GL5JP6FO4AJ-=F'>H&DK*FM+EL*PV4PT3KN/HK,TT8bbFccIZZ@[Y@\ZA][B][B[Y@XV=US:SQ8HF1GD1DA0A?0@>1>>2??5>@5;>59?59?59A69A48C58C58C5:B79A69@88@58?79A69@8:B7:A9:B79@8:B7;B:<D9=D<>E=8B:8B:7A88@56>14</39/271.40/51-7/-9-.;)0@&6G'7M';V+:W+;U0;T4;R8=P:>O<>P::L6@R8EW=FY=DX=@T;5H2+<),6+,3++2*ZgM[hN\hP]iQbjSckTglXhkXghVghVifWifWkeWlfXmeZmgYmgYkhYkg[kg\jf]jf[hfZefVhiWglVfo\erajwnzŠ‰œ«²ºÈÑÐÙàáéìò÷ûøþþùþúúÿøûþóùýïøùë÷øæõôâíìØéæÓåâÏÜ×ÄÒ˹ʿ­Á´¤¹­¡¼¯¦Á¶²ËÁÀÛÐÖæßæûóþý÷ÿþûÿÿþÿþýÿüýÿüýÿüýÿþþþþþþþþþþþþþþþþþþÿþüÿþüÿþüÿþüÿýúÿüùÿüùÿýúÿýùÿþúÿþýÿýüÿüýÿýþÿþÿÿþÿÿýþýûüÿþýÿþûÿüúÿøóøíçòåÝöèßûðêÿúýÿüÿÿýþÿÿýÿÿûýÿüýÿþüýÿýþÿýþÿÿýÿÿþüÿþõÿÿîÿþèÿÿäúöÝýûâäàÇ‹‡lRM0SN0QK+QK+UO/UO/TO1TO1SO2SO2QP4QP4QM2PO3PN5NQ6MO7JP6HM6EM5HQ6EN1BK.?I'BI(FM+JP,KQ-NR/PT1VZ7^bA_bCY\?TW<UU=VV>WU>XV=YW>XV=XW;WV:XT9QK3OI3LG4ID1DA2B@3A?3>@5<=59?59?59A67B48C57D37D3:B79A69A68@58@59A69A6:B7:B7:B7:B7:B7;C8<D9=E:<F;=G?<H><F;;F6<D5:B39?38=67>74>63=40>-3C)9M*BY/Ga1Op;Ln;Li=Fa>BY?<R=;L::K8=O9DV<K^BM`BL`DH\@:Q71D.+5*+2*)0(ZgM[hN\hP^jRbjSdlUglXilYijXijXkhYkhYlfXmgYnf[nhZmgYkhYkg[jf[jf]ieZhfZfgWhiWejTdmZgtco|sŽ¬³¶ÄÍÍÖÝÞæéñöúøþþúþýùþøøûòôøêóôæðñßêé×áàÌÜÙÆ×ÔÁÐ˸ÇÀ®¿³£½° ¼°¤Ã¶®ËÀ¼ÕËÊãØÞëäëüôÿý÷ÿþûÿÿþÿþýÿüýÿüýÿýÿþþþþþþþþþþþþþþþþþþþÿþüÿþüÿþüÿþüÿýúÿýúÿüùÿüùÿüøÿüøÿýüÿüûÿûüÿüýÿþÿÿþÿÿýþþüýÿþýÿþûÿýûüóîêßÙâÕÍëÝÔ÷ìæÿúýÿüÿÿýþÿÿýÿÿûýÿüýÿþüýÿýþÿýþÿÿþÿÿÿýÿþõÿþíÿýçÿþãû÷ÞþüããßƉ…jPK.QL.QK+QK+TN.UO/TO1UP2TP3SO2QP4PO3QM2ON2PN5MP5MO7JP6IN7FN6HQ6FO2CL/CM+FM,JQ/OU1SY5QY2W_8jpJy[qvVY^@KO4MP5QQ9QQ9RP9SQ8TR9UT8YU:ZV;XR:VP:RM:NI6HE6DB5CA5?A6<=59?5:@69A67B47B47D37D3:B7:B79A69A69A69A6:B7:B7:B7:B7:B7:B7;C8<D9>F;<F;?IA>J@?I>>I9?G8>F7>D8=B;>E>9C;6@74B19I/BV3Pg=XrB^J[}JXuIMhECZ@9O:6G54E2<N8EW=NaESfHSgKNbF@W=6I3/9./6.-4,ZgM[hN\hP^jRblTemVhmYinZklZklZlj[liZnhZnhZog\oi[liZkhYjfZjf[ie\ieZhfZfgWfgUbgQajWerao|s~Ž–¥¬«¹ÂÇÐ×Úáçîó÷öüüøüûõúôóöíîòäéêÜãäÒÜÛÉÓÒ¾Î˸ÉƳþ«½¶¤Ã·§Ç¹¬ÍÁµÖÉÁßÔÐéßÞóèîøñøýõÿþøÿÿüÿþýÿýüÿüýÿüþýýÿþþþþþþþþþþþþþþþþþþþÿþüÿþüþýûÿþüÿýúÿýúÿýúÿüùÿû÷ÿû÷ÿýüÿüûþúûÿûüÿýþÿþÿÿýþþüýÿüûÿþûÿýûýôïíâÜæÙÑðâÙüñëÿúýÿüÿÿýþÿÿýÿÿûýÿúýÿþüýÿýþÿýþÿÿþÿÿÿýÿþõÿþíÿüæÿüáþùãÿþçäßÉŠ†mPJ0RM0SL/SM-TN.UO/UP2UP2TP3SO2PO3ON2PL1NM1OM4LO4LN6JP6JO8HP8JS8GP3GP3IS1MT3OV4V\8\d=grHtUˆ“i›s…lnuTZaBRV;LN6NN6MM5NL5OM6RP7WS:YU<ZT>XR<TO<QL9KH9GE8EC7@B7=>6:@6:@69A67B47B46C26C2;C8:B79A69A69A69A6:B7;C8;C8:B7:B7;C8;C8=E:>F;=G<=G?<H>>H=>I9?G8>F7>D8=B;=D=9C;6@74B19I/DX5Ri?[uEZ{FWyFTqEIdA=T:2H3/@.->+6H2@R8L_CReGSgKOcG@W=6I30:/07/-4,YgMZhN\hP^jRblTemVinZjo[mn\lm[mk\mj[nhZnhZoi]oi[liZkhYjfZieZie\ieZig[ghXghVchRajWerao|s{‹ŠŸ¦¢°¹ÁËÔÔÝäéñôòúüôúøñöðíðçèìÞâãÕÚÛÉÒÑ¿ÌɶÉıþ«¿¸¦¼³¢Ê¾®ÓŸÜÐÄæÙÑíâàöìíÿôúÿùÿÿ÷ÿÿúÿÿüÿþýÿýüÿûüþüþýþÿÿþþþþþþþþþþþþþþþþþþÿþüÿþüþýûþýûÿýúÿýúÿýúÿûøÿúöþùõÿýüÿûúýùúþúûþüýÿýþÿþÿÿýþÿüûÿýúÿüúÿøó÷ìæòåÝøêáÿôîÿúýÿüÿÿýþÿÿýÿÿûýÿúýÿþüýÿüýÿýþÿÿþÿÿÿýÿþõÿýìÿûãþúßÿüæÿÿéçâÌŒˆoRL2TO2TM0UN1TN.UO/VQ3VQ3UQ4SO2ON2NM1PL1NM1NL3KN3LN6KQ7KP9JR:LU:IR5JS6OY7SZ9T[9]c?fnG‚’c’¦sŸ±›ª’¡zˆ”pr{\\bFLP7KM5LK6KI4LJ5PK5TN8VP:WQ;VP:SN;QL9LI:IG:GE9CE:=>6:@6:@69A67B47B46C26C2;C8;C8:B79A69A6:B7;C8;C8;C8;C8;C8;C8<D9=E:>F;=G<=G?<H>=G<=H8>F7=E6=C7=B;:A:7A95?64B19I/BV3Ne;Uo?Tu@RtAPmAE`=9P6-C.+<*+<)1C-<N4H[?PcERfJNbF@W=5H2,6++2*'.&
\ No newline at end of file
diff --git a/urt/Makefile b/urt/Makefile
new file mode 100644
index 00000000..04dd2913
--- /dev/null
+++ b/urt/Makefile
@@ -0,0 +1,33 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = urt
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+LIBOBJECTS = Runput.o cmd_name.o \
+	rle_addhist.o rle_error.o rle_getcom.o rle_getrow.o rle_getskip.o \
+	rle_global.o rle_hdr.o rle_open_f.o rle_putcom.o rle_putrow.o \
+        rle_row_alc.o \
+        scanargs.o vaxshort.o     
+
+MERGE_OBJECTS =
+
+all: librle.a
+
+librle.a: $(LIBOBJECTS)
+	rm -f $@
+	ar rc $@ $^
+	$(RANLIB) $@
+
+# Rule for objects.
+$(LIBOBJECTS): %.o: %.c importinc
+	$(CC) -c $(CFLAGS) -I importinc -o $@ $< $(CFLAGS_PERSONAL) $(CADD)
+
+BINARIES =
+SCRIPTS =
+
+OMIT_URT_RULE = 1
+include $(SRCDIR)/Makefile.common
diff --git a/urt/README b/urt/README
new file mode 100644
index 00000000..dc68889d
--- /dev/null
+++ b/urt/README
@@ -0,0 +1,20 @@
+This directory contains a subset of the Utah Raster Toolkit library.
+
+The source files are taken directly from that library, but only the ones
+Netpbm needs, so the resulting librle.a is smaller than the original.
+
+The files were extracted by Bryan Henderson on 2000.05.18, from a
+package taken from ftp.iastate.edu/pub/utah-raster/ called urt-3.1b on
+2000.04.13.
+
+A user who has the original library installed can use it instead of
+this stripped down version by configuring the make files
+appropriately.
+
+The source files have been modified slightly to quiet compiler warnings.
+
+In rle_global.c, the global data structure rle_dflt_hdr had "stdout"
+in its initializer in the original.  But GNU C Library Version 2
+defines stdout as a variable, so that wouldn't compile.  So I changed
+it to NULL and added a line to rle_hdr_init to set that field to
+'stdout' dynamically.  2000.06.02 BJH.
diff --git a/urt/Runput.c b/urt/Runput.c
new file mode 100644
index 00000000..3bc562ac
--- /dev/null
+++ b/urt/Runput.c
@@ -0,0 +1,356 @@
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ *
+ *  Modified at BRL 16-May-88 by Mike Muuss to avoid Alliant STDC desire
+ *  to have all "void" functions so declared.
+ */
+/* 
+ * Runput.c - General purpose Run Length Encoding.
+ * 
+ * Author:	Spencer W. Thomas
+ * 		Computer Science Dept.
+ * 		University of Utah
+ * Date:	Mon Aug  9 1982
+ * Copyright (c) 1982,1986 Spencer W. Thomas
+ * 
+ * $Id: Runput.c,v 3.0.1.1 1992/01/28 18:17:40 spencer Exp $
+ * 
+ * Modified by:	Todd W. Fuqua
+ * 	Date:	Jul 22 1984
+ * convert to new RLE format to make room for larger frame buffers
+ */
+
+/* THIS IS WAY OUT OF DATE.  See rle.5.
+ * The output file format is:
+ * 
+ * Word 0:	A "magic" number.  The top byte of the word contains
+ *		the letter 'R' or the letter 'W'.  'W' indicates that
+ *		only black and white information was saved.  The bottom
+ *		byte is one of the following:
+ *	' ':	Means a straight "box" save, -S flag was given.
+ *	'B':	Image saved with background color, clear screen to
+ *		background before restoring image.
+ *	'O':	Image saved in overlay mode.
+ * 
+ * Words 1-6:	The structure
+ * {     short   xpos,			Lower left corner
+ *             ypos,
+ *             xsize,			Size of saved box
+ *             ysize;
+ *     char    rgb[3];			Background color
+ *     char    map;			flag for map presence
+ * }
+ * 
+ * If the map flag is non-zero, then the color map will follow as 
+ * 3*256 16 bit words, first the red map, then the green map, and
+ * finally the blue map.
+ * 
+ * Following the setup information is the Run Length Encoded image.
+ * Each instruction consists of a 4-bit opcode, a 12-bit datum and
+ * possibly one or more following words (all words are 16 bits).  The
+ * instruction opcodes are:
+ * 
+ * SkipLines (1):   The bottom 10 bits are an unsigned number to be added to
+ *		    current Y position.
+ * 
+ * SetColor (2):    The datum indicates which color is to be loaded with
+ * 		    the data described by the following ByteData and
+ * 		    RunData instructions.  0->red, 1->green, 2->blue.  The
+ * 		    operation also resets the X position to the initial
+ * 		    X (i.e. a carriage return operation is performed).
+ * 
+ * SkipPixels (3):  The bottom 10 bits are an unsigned number to be
+ * 		    added to the current X position.
+ * 
+ * ByteData (5):    The datum is one less than the number of bytes of
+ * 		    color data following.  If the number of bytes is
+ * 		    odd, a filler byte will be appended to the end of
+ * 		    the byte string to make an integral number of 16-bit
+ * 		    words.  The bytes are in PDP-11 order.  The X
+ * 		    position is incremented to follow the last byte of
+ * 		    data.
+ * 
+ * RunData (6):	    The datum is one less than the run length.  The
+ * 		    following word contains (in its lower 8 bits) the
+ * 		    color of the run.  The X position is incremented to
+ * 		    follow the last byte in the run.
+ */
+
+#include    <string.h>
+#include	<stdio.h>
+
+#include	"rle_put.h"
+#include	"rle.h"
+#include	"rle_code.h"
+#include    "vaxshort.h"
+#include    "Runput.h"
+
+#define UPPER 255			/* anything bigger ain't a byte */
+
+/*
+ * Macros to make writing instructions with correct byte order easier.
+ */
+/* Write a two-byte value in little_endian order. */
+#define	put16(a)    (putc((a)&0xff,rle_fd),putc((char)(((a)>>8)&0xff),rle_fd))
+
+/* short instructions */
+#define mk_short_1(oper,a1)		/* one argument short */ \
+    putc(oper,rle_fd), putc((char)a1,rle_fd)
+
+#define mk_short_2(oper,a1,a2)		/* two argument short */ \
+    putc(oper,rle_fd), putc((char)a1,rle_fd), put16(a2)
+
+/* long instructions */
+#define mk_long_1(oper,a1)		/* one argument long */ \
+    putc((char)(LONG|oper),rle_fd), putc('\0', rle_fd), put16(a1)
+
+#define mk_long_2(oper,a1,a2)		/* two argument long */ \
+    putc((char)(LONG|oper),rle_fd), putc('\0', rle_fd), \
+    put16(a1), put16(a2)
+
+/* choose between long and short format instructions */
+/* NOTE: these macros can only be used where a STATEMENT is legal */
+
+#define mk_inst_1(oper,a1)		/* one argument inst */ \
+    if (a1>UPPER) (mk_long_1(oper,a1)); else (mk_short_1(oper,a1))
+
+#define mk_inst_2(oper,a1,a2)		/* two argument inst */ \
+    if (a1>UPPER) (mk_long_2(oper,a1,a2)); else (mk_short_2(oper,a1,a2))
+
+/* 
+ * Opcode definitions
+ */
+#define	    RSkipLines(n)   	    mk_inst_1(RSkipLinesOp,(n))
+
+#define	    RSetColor(c)	    mk_short_1(RSetColorOp,(c))
+				    /* has side effect of performing */
+				    /* "carriage return" action */
+
+#define	    RSkipPixels(n)	    mk_inst_1(RSkipPixelsOp,(n))
+
+#define	    RNewLine		    RSkipLines(1)
+
+#define	    RByteData(n)	    mk_inst_1(RByteDataOp,n)
+					/* followed by ((n+1)/2)*2 bytes */
+					/* of data.  If n is odd, last */
+					/* byte will be ignored */
+					/* "cursor" is left at pixel */
+					/* following last pixel written */
+
+#define	    RRunData(n,c)	    mk_inst_2(RRunDataOp,(n),(c))
+					/* next word contains color data */
+					/* "cursor" is left at pixel after */
+					/* end of run */
+
+#define     REOF		    mk_inst_1(REOFOp,0)
+					/* Really opcode only */
+
+/*****************************************************************
+ * TAG( RunSetup )
+ * Put out initial setup data for RLE files.
+ */
+void
+RunSetup(rle_hdr * the_hdr)
+{
+    struct XtndRsetup setup;
+    register FILE * rle_fd = the_hdr->rle_file;
+
+    put16( RLE_MAGIC );
+
+    if ( the_hdr->background == 2 )
+	setup.h_flags = H_CLEARFIRST;
+    else if ( the_hdr->background == 0 )
+	setup.h_flags = H_NO_BACKGROUND;
+    else
+	setup.h_flags = 0;
+    if ( the_hdr->alpha )
+	setup.h_flags |= H_ALPHA;
+    if ( the_hdr->comments != NULL && *the_hdr->comments != NULL )
+	setup.h_flags |= H_COMMENT;
+
+    setup.h_ncolors = the_hdr->ncolors;
+    setup.h_pixelbits = 8;		/* Grinnell dependent */
+    if ( the_hdr->ncmap > 0 && the_hdr->cmap == NULL )
+    {
+	fprintf( stderr,
+       "%s: Color map of size %d*%d specified, but not supplied, writing %s\n",
+		 the_hdr->cmd, the_hdr->ncmap, (1 << the_hdr->cmaplen),
+		 the_hdr->file_name );
+	the_hdr->ncmap = 0;
+    }
+    setup.h_cmaplen = the_hdr->cmaplen;	/* log2 of color map size */
+    setup.h_ncmap = the_hdr->ncmap;	/* no of color channels */
+    vax_pshort(setup.hc_xpos,the_hdr->xmin);
+    vax_pshort(setup.hc_ypos,the_hdr->ymin);
+    vax_pshort(setup.hc_xlen,the_hdr->xmax - the_hdr->xmin + 1);
+    vax_pshort(setup.hc_ylen,the_hdr->ymax - the_hdr->ymin + 1);
+    fwrite((char *)&setup, SETUPSIZE, 1, rle_fd);
+    if ( the_hdr->background != 0 )
+    {
+	register int i;
+	register rle_pixel *background =
+	    (rle_pixel *)malloc( (unsigned)(the_hdr->ncolors + 1) );
+	register int *bg_color;
+	/* 
+	 * If even number of bg color bytes, put out one more to get to 
+	 * 16 bit boundary.
+	 */
+	bg_color = the_hdr->bg_color;
+	for ( i = 0; i < the_hdr->ncolors; i++ )
+	    background[i] =  *bg_color++;
+	/* Extra byte, if written, should be 0. */
+	background[i] = 0;
+	fwrite((char *)background, (the_hdr->ncolors / 2) * 2 + 1, 1, rle_fd);
+	free( background );
+    }
+    else
+	putc( '\0', rle_fd );
+    if (the_hdr->ncmap > 0)
+    {
+	/* Big-endian machines are harder */
+	register int i, nmap = (1 << the_hdr->cmaplen) *
+			       the_hdr->ncmap;
+	register char *h_cmap = (char *)malloc( nmap * 2 );
+	if ( h_cmap == NULL )
+	{
+	    fprintf( stderr,
+	     "%s: Malloc failed for color map of size %d, writing %s\n",
+		     the_hdr->cmd, nmap, the_hdr->file_name );
+	    exit( 1 );
+	}
+	for ( i = 0; i < nmap; i++ )
+	    vax_pshort( &h_cmap[i*2], the_hdr->cmap[i] );
+
+	fwrite( h_cmap, nmap, 2, rle_fd );
+	free( h_cmap );
+    }
+
+    /* Now write out comments if given */
+    if ( setup.h_flags & H_COMMENT )
+    {
+	int comlen;
+	register CONST_DECL char ** com_p;
+
+	/* Get the total length of comments */
+	comlen = 0;
+	for ( com_p = the_hdr->comments; *com_p != NULL; com_p++ )
+	    comlen += 1 + strlen( *com_p );
+
+	put16( comlen );
+	for ( com_p = the_hdr->comments; *com_p != NULL; com_p++ )
+	    fwrite( *com_p, 1, strlen( *com_p ) + 1, rle_fd );
+
+	if ( comlen & 1 )	/* if odd length, round up */
+	    putc( '\0', rle_fd );
+    }
+}
+
+/*****************************************************************
+ * TAG( RunSkipBlankLines )
+ * Skip one or more blank lines in the RLE file.
+ */
+void
+RunSkipBlankLines(int nblank, rle_hdr * the_hdr)
+{
+    register FILE * rle_fd = the_hdr->rle_file;
+    RSkipLines(nblank);
+}
+
+/*****************************************************************
+ * TAG( RunSetColor )
+ * Select a color and do carriage return.
+ * color: 0 = Red, 1 = Green, 2 = Blue.
+ */
+void
+RunSetColor(int c, rle_hdr * the_hdr)
+{
+    register FILE * rle_fd = the_hdr->rle_file;
+    RSetColor(c);
+}
+
+/*****************************************************************
+ * TAG( RunSkipPixels )
+ * Skip a run of background.
+ */
+
+/* ARGSUSED */
+void
+RunSkipPixels(int nskip, int last, int wasrun, rle_hdr * the_hdr)
+{
+    register FILE * rle_fd = the_hdr->rle_file;
+    if (! last && nskip > 0)
+    {
+	RSkipPixels(nskip);
+    }
+}
+
+/*****************************************************************
+ * TAG( RunNewScanLine )
+ * Perform a newline action.  Since CR is implied by the Set Color
+ * operation, only generate code if the newline flag is true.
+ */
+void
+RunNewScanLine(int flag, rle_hdr * the_hdr)
+{
+    register FILE * rle_fd = the_hdr->rle_file;
+    if (flag)
+    {
+	RNewLine;
+    }
+}
+
+/*****************************************************************
+ * TAG( Runputdata )
+ * Put one or more pixels of byte data into the output file.
+ */
+void
+Runputdata(rle_pixel * buf, int n, rle_hdr * the_hdr)
+{
+    register FILE * rle_fd = the_hdr->rle_file;
+    if (n == 0)
+	return;
+
+    RByteData(n-1);
+    fwrite((char *)buf, n, 1, rle_fd);
+    if ( n & 1 )
+	putc( 0, rle_fd );
+}
+
+/*****************************************************************
+ * TAG( Runputrun )
+ * Output a single color run.
+ */
+
+/* ARGSUSED */
+void
+Runputrun(int color, int n, int last, rle_hdr * the_hdr)
+{
+    register FILE * rle_fd = the_hdr->rle_file;
+    RRunData(n-1,color);
+}
+
+
+/*****************************************************************
+ * TAG( RunputEof )
+ * Output an EOF opcode
+ */
+void
+RunputEof(rle_hdr * the_hdr)
+{
+    register FILE * rle_fd = the_hdr->rle_file;
+    REOF;
+}
diff --git a/urt/Runput.h b/urt/Runput.h
new file mode 100644
index 00000000..776e3ec5
--- /dev/null
+++ b/urt/Runput.h
@@ -0,0 +1,23 @@
+void 
+RunSetup(rle_hdr * the_hdr);
+
+void
+RunSkipBlankLines(int nblank, rle_hdr * the_hdr);
+
+void
+RunSetColor(int c, rle_hdr * the_hdr);
+
+void
+RunSkipPixels(int nskip, int last, int wasrun, rle_hdr * the_hdr);
+
+void
+RunNewScanLine(int flag, rle_hdr * the_hdr);
+
+void
+Runputdata(rle_pixel * buf, int n, rle_hdr * the_hdr);
+
+void
+Runputrun(int color, int n, int last, rle_hdr * the_hdr);
+
+void
+RunputEof(rle_hdr * the_hdr);
diff --git a/urt/cmd_name.c b/urt/cmd_name.c
new file mode 100644
index 00000000..1f8f0edf
--- /dev/null
+++ b/urt/cmd_name.c
@@ -0,0 +1,54 @@
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ */
+/* 
+ * cmd_name.c - Extract command name from argv[0].
+ * 
+ * Author:	Spencer W. Thomas
+ * 		EECS Dept.
+ * 		University of Michigan
+ * Date:	Wed Jun 27 1990
+ * Copyright (c) 1990, University of Michigan
+ */
+
+#include "rle.h"
+static char no_name[] = "(no-name)";
+
+char *
+cmd_name( argv )
+char **argv;
+{
+    register char *cp, *a;
+
+    /* Be paranoid. */
+    if ( !argv || !(a = *argv) )
+	return no_name;
+
+    /* Find end of file name. */
+    for ( cp = a; *cp; cp++ )
+	;
+
+    /* Find last / or beginning of command name. */
+    for ( cp--; *cp != '/' && cp > a; cp-- )
+	;
+    
+    /* If it's a /, skip it. */
+    if ( *cp == '/' )
+	cp++;
+
+    return cp;
+}
diff --git a/urt/rle.h b/urt/rle.h
new file mode 100644
index 00000000..71f15d28
--- /dev/null
+++ b/urt/rle.h
@@ -0,0 +1,492 @@
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ */
+/* 
+ * rle.h - Global declarations for Utah Raster Toolkit RLE programs.
+ * 
+ * Author:  Todd W. Fuqua
+ *      Computer Science Dept.
+ *      University of Utah
+ * Date:    Sun Jul 29 1984
+ * Copyright (c) 1984 Todd W. Fuqua
+ * 
+ * $Id: rle.h,v 3.0.1.5 1992/04/30 14:05:56 spencer Exp $
+ */
+
+#ifndef RLE_H
+#define RLE_H
+
+#include "rle_config.h"     /* Configuration parameters. */
+
+#include <stdio.h>      /* Declare FILE. */
+
+#ifdef c_plusplus
+#define USE_PROTOTYPES
+#endif
+
+enum rle_dispatch {
+    NO_DISPATCH = -1,
+    RUN_DISPATCH = 0
+};
+
+/* ****************************************************************
+ * TAG( rle_pixel rle_map )
+ *
+ * Typedef for 8-bit (or less) pixel data.
+ *
+ * Typedef for 16-bit color map data.
+ */
+typedef unsigned char rle_pixel;
+typedef unsigned short rle_map;
+
+/*
+ * Defines for traditional channel numbers.
+ */
+#define RLE_RED     0   /* Red channel traditionally here. */
+#define RLE_GREEN   1   /* Green channel traditionally here. */
+#define RLE_BLUE    2   /* Blue channel traditionally here. */
+#define RLE_ALPHA      -1   /* Alpha channel here. */
+
+/*
+ * Return values from rle_get_setup.
+ */
+#define RLE_SUCCESS  0
+#define RLE_NOT_RLE -1
+#define RLE_NO_SPACE    -2
+#define RLE_EMPTY   -3
+#define RLE_EOF     -4
+
+/*
+ * "Magic" value for is_init field.  Pi * 2^29.
+ */
+#define RLE_INIT_MAGIC  0x6487ED51L
+
+/*****************************************************************
+ * TAG( RLE_CHECK_ALLOC )
+ *
+ * Test for allocation failure, scream and die if so.
+ */
+#define RLE_CHECK_ALLOC( pgm, ptr, name )               \
+    ( !(ptr) ?  rle_alloc_error( pgm, name ) : 0 )
+
+/*
+ * TAG( rle_hdr )
+ *
+ * Definition of header structure used by RLE routines.
+ */
+
+#ifndef c_plusplus
+typedef
+#endif
+    struct rle_hdr {
+        enum rle_dispatch dispatch;  /* Type of file to create. */
+        int       ncolors;    /* Number of color channels. */
+        int *     bg_color;   /* Pointer to bg color vector. */
+        int       alpha;      /* If !0, save alpha channel. */
+        int       background; /* 0->just save all pixels, */
+        /* 1->overlay, 2->clear to bg first. */
+        int       xmin;       /* Lower X bound (left.) */
+        int       xmax;       /* Upper X bound (right.) */
+        int       ymin;       /* Lower Y bound (bottom.) */
+        int       ymax;       /* Upper Y bound (top.) */
+        int       ncmap;      /* Number of color channels in color map. */
+        /* Map only saved if != 0. */
+        int       cmaplen;    /* Log2 of color map length. */
+        rle_map * cmap;       /* Pointer to color map array. */
+        const char ** comments; /* Pointer to array of pointers to comments. */
+        FILE *    rle_file;   /* Input or output file. */
+        /* 
+         * Bit map of channels to read/save.  Indexed by (channel mod 256).
+         * Alpha channel sets bit 255.
+         * 
+         * Indexing (0 <= c <= 255):
+         *      bits[c/8] & (1 << (c%8))
+         */
+#define RLE_SET_BIT(glob,bit) \
+        ((glob).bits[((bit)&0xff)/8] |= (1<<((bit)&0x7)))
+#define RLE_CLR_BIT(glob,bit) \
+            ((glob).bits[((bit)&0xff)/8] &= ~(1<<((bit)&0x7)))
+#define RLE_BIT(glob,bit) \
+            ((glob).bits[((bit)&0xff)/8] & (1<<((bit)&0x7)))
+            char    bits[256/8];
+            /* Set to magic pattern if following fields are initialized. */
+            /* This gives a 2^(-32) chance of missing. */
+            long int is_init;   
+            /* Command, file name and image number for error messages. */
+            const char *cmd;
+            const char *file_name;
+            int img_num;
+            /* 
+             * Local storage for rle_getrow & rle_putrow.
+             * rle_getrow has
+             *      scan_y  int     current Y scanline.
+             *      vert_skip   int     number of lines to skip.
+             * rle_putrow has
+             *      nblank  int     number of blank lines.
+             *      brun    short(*)[2] Array of background runs.
+             *      fileptr long        Position in output file.
+             */
+            union {
+                struct {
+                    int scan_y,
+                        vert_skip;
+                    char is_eof,    /* Set when EOF or EofOp encountered. */
+                        is_seek;    /* If true, can seek input file. */
+                } get;
+                struct {
+                    int nblank;
+                    short (*brun)[2];
+                    long fileptr;
+                } put;
+            } priv;
+    }
+#ifndef c_plusplus
+rle_hdr             /* End of typedef. */
+#endif
+;
+
+/* 
+ * TAG( rle_dflt_hdr )
+ *
+ * Global variable with possibly useful default values.
+ */
+extern rle_hdr rle_dflt_hdr;
+
+
+/* Declare RLE library routines. */
+
+/* From rle_error.c. */
+/*****************************************************************
+ * TAG( rle_alloc_error )
+ * 
+ * Print memory allocation error message and exit.
+ */
+extern int rle_alloc_error( const char *pgm,
+                            const char *name );
+
+/*****************************************************************
+ * TAG( rle_get_error )
+ *
+ * Print an error message based on the error code returned by
+ * rle_get_setup.
+ */
+extern int rle_get_error( int code,
+                          const char *pgmname,
+                          const char *fname );
+              
+/* From rle_getrow.c */
+
+/*****************************************************************
+ * TAG( rle_debug )
+ * 
+ * Turn RLE debugging on or off.
+ */
+extern void rle_debug( int on_off );
+
+int
+rle_get_setup(rle_hdr * const the_hdr);
+
+/*****************************************************************
+ * TAG( rle_get_setup_ok )
+ *
+ * Call rle_get_setup.  If it returns an error code, call
+ * rle_get_error to print the error message, then exit with the error
+ * code. 
+ */
+extern void rle_get_setup_ok( rle_hdr *the_hdr,
+                              const char *prog_name,
+                              const char *file_name);
+
+/*****************************************************************
+ * TAG( rle_getrow )
+ *
+ * Read a scanline worth of data from an RLE file.
+ */
+extern int rle_getrow( rle_hdr * the_hdr, 
+                       rle_pixel * scanline[] );
+
+/* From rle_getskip.c */
+
+/*****************************************************************
+ * TAG( rle_getskip )
+ * Skip a scanline, return the number of the next one.
+ */
+extern unsigned int rle_getskip( rle_hdr *the_hdr );
+
+/* From rle_hdr.c. */
+
+/*****************************************************************
+ * TAG( rle_names )
+ *
+ * Load the command and file names into the rle_hdr.
+ */
+extern void rle_names( rle_hdr *the_hdr,
+                       const char *pgmname,
+                       const char *fname,
+                       int img_num );
+
+/*****************************************************************
+ * TAG( rle_hdr_cp )
+ * 
+ * Make a "safe" copy of a rle_hdr structure.
+ */
+extern rle_hdr * rle_hdr_cp( rle_hdr *from_hdr,
+                             rle_hdr *to_hdr );
+
+/*****************************************************************
+ * TAG( rle_hdr_init )
+ * 
+ * Initialize a rle_hdr structure.
+ */
+extern rle_hdr * rle_hdr_init( rle_hdr *the_hdr );
+
+/*****************************************************************
+ * TAG( rle_hdr_clear )
+ * 
+ */
+extern void rle_hdr_clear( rle_hdr *the_hdr );
+
+/* From rle_putrow.c. */
+
+/*****************************************************************
+ * TAG( rgb_to_bw )
+ *
+ * Converts RGB data to gray data via the NTSC Y transform.
+ */
+extern void rgb_to_bw( rle_pixel *red_row,
+                       rle_pixel *green_row,
+                       rle_pixel *blue_row,
+                       rle_pixel *bw_row,
+                       int rowlen );
+
+/*****************************************************************
+ * TAG( rle_puteof )
+ *
+ * Write an End-of-image opcode to the RLE file.
+ */
+extern void rle_puteof( rle_hdr *the_hdr );
+
+/*****************************************************************
+ * TAG( rle_putrow )
+ *
+ * Write a scanline of data to the RLE file.
+ */
+extern void rle_putrow( rle_pixel *rows[], int rowlen, rle_hdr *the_hdr );
+
+/*****************************************************************
+ * TAG( rle_put_init )
+ *
+ * Initialize header for output, but don't write it to the file.
+ */
+extern void rle_put_init( rle_hdr * the_hdr );
+
+/*****************************************************************
+ * TAG( rle_put_setup )
+ *
+ * Write header information to a new RLE image file.
+ */
+extern void rle_put_setup( rle_hdr * the_hdr );
+
+/*****************************************************************
+ * TAG( rle_skiprow )
+ *
+ * Skip nrow scanlines in the output file.
+ */
+extern void rle_skiprow( rle_hdr *the_hdr, int nrow );
+
+/* From rle_cp.c */
+/*****************************************************************
+ * TAG( rle_cp )
+ * Copy image data from input to output with minimal interpretation.
+ */
+extern void rle_cp( rle_hdr *in_hdr, rle_hdr *out_hdr );
+
+/* From rle_row_alc.c. */
+/*****************************************************************
+ * TAG( rle_row_alloc )
+ *
+ * Allocate scanline memory for use by rle_getrow.
+ */
+extern int rle_row_alloc( rle_hdr * the_hdr,
+                          rle_pixel *** scanp );
+
+/*****************************************************************
+     * TAG( rle_row_free )
+     *
+     * Free the above.
+     */
+extern void rle_row_free( rle_hdr *the_hdr, rle_pixel **scanp );
+
+/* From buildmap.c. */
+/* 
+ * buildmap - build a more usable colormap from data in the_hdr struct.
+     */
+extern rle_pixel **buildmap( rle_hdr *the_hdr,
+                             int minmap,
+                             double orig_gamma,
+                             double new_gamma );
+
+/* From rle_getcom.c. */
+/*****************************************************************
+ * TAG( rle_getcom )
+ *
+ * Get a specific comment from the image comments.
+ */
+const char *
+rle_getcom(const char * const name,
+           rle_hdr *    const the_hdr);
+
+/* From rle_putcom.c. */
+
+/* Delete a specific comment from the image comments. */
+const char *
+rle_delcom(const char * const name,
+           rle_hdr *    const the_hdr);
+
+/* Put (or replace) a comment into the image comments. */
+const char *
+rle_putcom(const char * const value,
+           rle_hdr *    const the_hdr);
+
+/* From dither.c. */
+/*****************************************************************
+ * TAG( bwdithermap )
+ * Create a color map for ordered dithering in grays.
+ */
+extern void bwdithermap( int levels, double gamma, int bwmap[],
+                         int divN[256], int modN[256], int magic[16][16] );
+/*****************************************************************
+ * TAG( ditherbw )
+ * Dither a gray-scale value.
+ */
+extern int ditherbw( int x, int y, int val, 
+                     int divN[256], int modN[256], int magic[16][16] );
+/*****************************************************************
+ * TAG( dithergb )
+ * Dither a color value.
+ */
+extern int dithergb( int x, int y, int r, int g, int b,
+                     int divN[256], int modN[256], int magic[16][16] );
+/*****************************************************************
+ * TAG( dithermap )
+ * Create a color map for ordered dithering in color.
+ */
+extern void dithermap( int levels, double gamma, int rgbmap[][3],
+                       int divN[256], int modN[256], int magic[16][16] );
+/*****************************************************************
+ * TAG( make_square )
+ * Make a 16x16 magic square for ordered dithering.
+ */
+extern void make_square( double N, int divN[256], int modN[256],
+                         int magic[16][16] );
+
+/* From float_to_exp.c. */
+/*****************************************************************
+ * TAG( float_to_exp )
+ * Convert a list of floating point numbers to "exp" format.
+ */
+extern void float_to_exp( int count, float * floats, rle_pixel * pixels );
+
+/* From rle_open_f.c. */
+/*****************************************************************
+ * TAG( rle_open_f )
+ *
+ * Open an input/output file with default.
+ */
+FILE *
+rle_open_f(const char * prog_name, const char * file_name, 
+           const char * mode);
+
+/*****************************************************************
+ * TAG( rle_open_f_noexit )
+ *
+ * Open an input/output file with default.
+ */
+FILE *
+rle_open_f_noexit(const char * const prog_name,
+                  const char * const file_name, 
+                  const char * const mode);
+
+/*****************************************************************
+ * TAG( rle_close_f )
+ * 
+ * Close a file opened by rle_open_f.  If the file is stdin or stdout,
+ * it will not be closed.
+ */
+extern void 
+rle_close_f( FILE *fd );
+
+/* From colorquant.c. */
+/*****************************************************************
+ * TAG( colorquant )
+ * Compute a colormap for quantizing an image to a limited set of colors.
+ */
+extern int colorquant( rle_pixel *red, rle_pixel *green, rle_pixel *blue,
+                       unsigned long pixels, rle_pixel *colormap[3],
+                       int colors, int bits,
+                       rle_pixel *rgbmap, int fast, int otherimages );
+
+/* From rle_addhist.c. */
+
+/* Append history information to the HISTORY comment. */
+void
+rle_addhist(char *          argv[],
+            rle_hdr * const in_hdr,
+            rle_hdr * const out_hdr);
+
+/* From cmd_name.c. */
+/*****************************************************************
+ * TAG( cmd_name )
+ * Extract command name from argv.
+ */
+extern char *cmd_name( char **argv );
+
+/* From scanargs.c. */
+/*****************************************************************
+ * TAG( scanargs )
+ * Scan command argument list and parse arguments.
+ */
+extern int scanargs( int argc,
+                     char **argv,
+                     const char *format,
+                     ... );
+
+/* From hilbert.c */
+/*****************************************************************
+ * TAG( hilbert_i2c )
+ * Convert an index into a Hilbert curve to a set of coordinates.
+ */
+extern void hilbert_c2i( int n, int m, int a[], long int *r );
+
+/*****************************************************************
+ * TAG( hilbert_c2i )
+ * Convert coordinates of a point on a Hilbert curve to its index.
+ */
+extern void hilbert_i2c( int n, int m, long int r, int a[] );
+
+/* From inv_cmap.c */
+/*****************************************************************
+ * TAG( inv_cmap )
+ * Compute an inverse colormap efficiently.
+ */
+extern void inv_cmap( int colors,
+                      unsigned char *colormap[3],
+                      int bits,
+                      unsigned long *dist_buf,
+                      unsigned char *rgbmap );
+
+#endif /* RLE_H */
diff --git a/urt/rle_addhist.c b/urt/rle_addhist.c
new file mode 100644
index 00000000..04e26206
--- /dev/null
+++ b/urt/rle_addhist.c
@@ -0,0 +1,115 @@
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ */
+/* 
+ * rle_addhist.c - Add to the HISTORY comment in header
+ * 
+ * Author:  Andrew Marriott.
+ *      School of Computer Science 
+ *      Curtin University of Technology
+ * Date:    Mon Sept 10 1988
+ * Copyright (c) 1988, Curtin University of Technology
+ */
+
+#include "rle.h"
+
+#include <string.h>
+#include <stdio.h>
+
+#ifdef  USE_TIME_H
+#include <time.h>
+#else
+#include <sys/types.h>
+#include <sys/time.h>
+#endif
+
+#include "mallocvar.h"
+
+/*****************************************************************
+ * TAG( rle_addhist )
+ * 
+ * Put a history comment into the header struct.
+ * Inputs:
+ *  argv:       Command line history to add to comments.
+ *  in_hdr:     Incoming header struct to use.
+ * Outputs:
+ *  out_hdr:    Outgoing header struct to add to.
+ * Assumptions:
+ *  If no incoming struct then value is NULL.
+ * Algorithm:
+ *  Calculate length of all comment strings, malloc and then set via
+ *  rle_putcom.
+ */
+
+void
+rle_addhist(char *          argv[],
+            rle_hdr * const in_hdr,
+            rle_hdr * const out_hdr) {
+
+    const char * const histoire = "HISTORY";
+    const char * const padding = "\t";
+
+    int length;
+    int i;
+    time_t  temp;
+    /* padding must give number of characters in histoire   */
+    /*     plus one for "="                 */
+    char * timedate;
+    const char * old;
+    static char * newc;
+
+    if (getenv("NO_ADD_RLE_HISTORY"))
+        return;
+    
+    length = 0;
+    for (i = 0; argv[i]; ++i)
+        length += strlen(argv[i]) +1;   /* length of each arg plus space. */
+
+    time(&temp);
+    timedate = ctime(&temp);
+    length += strlen(timedate);        /* length of date and time in ASCII. */
+
+    length += strlen(padding) + 3 + strlen(histoire) + 1;
+        /* length of padding, "on "  and length of history name plus "="*/
+    if (in_hdr) /* if we are interested in the old comments... */
+        old = rle_getcom(histoire, in_hdr);     /* get old comment. */
+    else
+        old = NULL;
+    
+    if (old && *old)
+        length += strlen(old);       /* add length if there. */
+
+    ++length;                               /*Cater for the null. */
+
+    MALLOCARRAY(newc, length);
+
+    if (newc == NULL)
+        return;
+
+    strcpy(newc,histoire);(void)strcat(newc,"=");
+    if (old && *old)
+        strcat(newc, old); /* add old string if there. */
+    for (i=0;argv[i];i++) {
+        strcat(newc, argv[i]);
+        strcat(newc, " ");
+    }
+    strcat(newc,"on ");
+    strcat(newc,timedate);         /* \n supplied by time. */
+    strcat(newc,padding);          /* to line up multiple histories.*/
+    
+    rle_putcom(newc, out_hdr);
+}
diff --git a/urt/rle_code.h b/urt/rle_code.h
new file mode 100644
index 00000000..955e7d42
--- /dev/null
+++ b/urt/rle_code.h
@@ -0,0 +1,70 @@
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ */
+/* 
+ * rle_code.h - Definitions for Run Length Encoding.
+ * 
+ * Author:	Spencer W. Thomas
+ * 		Computer Science Dept.
+ * 		University of Utah
+ * Date:	Mon Aug  9 1982
+ * Copyright (c) 1982 Spencer W. Thomas
+ * 
+ * $Header: /usr/users/spencer/src/urt/include/RCS/rle_code.h,v 3.0 90/08/03 15:19:48 spencer Exp $
+ */
+
+#ifndef RLE_MAGIC
+
+/* 
+ * Opcode definitions
+ */
+
+#define     LONG                0x40
+#define	    RSkipLinesOp	1
+#define	    RSetColorOp		2
+#define	    RSkipPixelsOp	3
+#define	    RByteDataOp		5
+#define	    RRunDataOp		6
+#define	    REOFOp		7
+
+#define     H_CLEARFIRST        0x1	/* clear framebuffer flag */
+#define	    H_NO_BACKGROUND	0x2	/* if set, no bg color supplied */
+#define	    H_ALPHA		0x4   /* if set, alpha channel (-1) present */
+#define	    H_COMMENT		0x8	/* if set, comments present */
+
+struct XtndRsetup
+{
+    char    hc_xpos[2],
+            hc_ypos[2],
+            hc_xlen[2],
+            hc_ylen[2];
+    char    h_flags,
+            h_ncolors,
+	    h_pixelbits,
+	    h_ncmap,
+	    h_cmaplen;
+};
+#define	    SETUPSIZE	((4*2)+5)
+
+/* "Old" RLE format magic numbers */
+#define	    RMAGIC	('R' << 8)	/* top half of magic number */
+#define	    WMAGIC	('W' << 8)	/* black&white rle image */
+
+#define	    RLE_MAGIC	((short)0xcc52)	/* RLE file magic number */
+
+#endif /* RLE_MAGIC */
+
diff --git a/urt/rle_config.h b/urt/rle_config.h
new file mode 100644
index 00000000..f3fa5bbc
--- /dev/null
+++ b/urt/rle_config.h
@@ -0,0 +1,105 @@
+/* rle_config.h
+ * 
+ * Automatically generated by make-config-h script.
+ * DO NOT EDIT THIS FILE.
+ * Edit include/makefile.src and the configuration file instead.
+ */
+#if defined(WIN32) && !defined(__CYGWIN__)
+#define NO_OPEN_PIPES
+#endif
+
+#define ABEKASA60 ABEKASA60
+#define ABEKASA62 ABEKASA62
+#define ALIAS ALIAS
+#define CUBICOMP CUBICOMP
+#define GIF GIF
+#define GRAYFILES GRAYFILES
+#define MACPAINT MACPAINT
+#define POSTSCRIPT POSTSCRIPT
+#define TARGA TARGA
+#define TIFF2p4 TIFF2p4
+#define VICAR VICAR
+#define WASATCH WASATCH
+#define WAVEFRONT WAVEFRONT
+#define GCC GCC
+#define CONST_DECL CONST_DECL
+#define NO_MAKE_MAKEFILE NO_MAKE_MAKEFILE
+#define NO_TOOLS NO_TOOLS
+#define USE_TIME_H USE_TIME_H
+#define USE_PROTOTYPES USE_PROTOTYPES
+#define USE_RANDOM USE_RANDOM
+#define USE_STDARG USE_STDARG
+#define USE_STDLIB_H USE_STDLIB_H
+#define USE_UNISTD_H USE_UNISTD_H
+#define USE_STRING_H USE_STRING_H
+#define VOID_STAR VOID_STAR
+/* -*-C-*- */
+/***************** From rle_config.tlr *****************/
+
+/* CONST_DECL must be defined as 'const' or nothing. */
+#ifdef CONST_DECL
+#undef CONST_DECL
+#define CONST_DECL const
+
+#else
+#define CONST_DECL
+
+#endif
+
+/* A define for getx11. */
+#ifndef USE_XLIBINT_H
+#define XLIBINT_H_NOT_AVAILABLE
+#endif
+
+/* Typedef for void * so we can use it consistently. */
+#ifdef VOID_STAR
+typedef	void *void_star;
+#else
+typedef char *void_star;
+#endif
+
+#ifdef USE_STDLIB_H
+#include <stdlib.h>
+#else
+
+/* Some programs include files from other packages that also declare
+ * malloc.  Avoid double declaration by #define NO_DECLARE_MALLOC
+ * before including this file.
+ */
+#ifndef NO_DECLARE_MALLOC
+#ifdef USE_PROTOTYPES
+#   include <sys/types.h>	/* For size_t. */
+    extern void_star malloc( size_t );
+    extern void_star calloc( size_t, size_t );
+    extern void_star realloc( void_star, size_t );
+    extern void free( void_star );
+#else
+    extern void_star malloc();
+    extern void_star realloc();
+    extern void_star calloc();
+    extern void free();
+    extern void cfree();
+#endif /* USE_PROTOTYPES */
+#endif /* NO_DECLARE_MALLOC */
+
+#ifdef USE_PROTOTYPES
+extern char *getenv( CONST_DECL char *name );
+#else
+extern char *getenv();
+#endif
+
+#endif /* USE_STDLIB_H */
+
+#ifdef NEED_BSTRING
+    /* From bstring.c. */
+    /*****************************************************************
+     * TAG( bstring bzero )
+     * 'Byte string' functions.
+     */
+#   define bzero( _str, _n )		memset( _str, '\0', _n )
+#   define bcopy( _from, _to, _count )	memcpy( _to, _from, _count )
+#endif
+
+#ifdef NEED_SETLINEBUF
+#   define setlinebuf( _s )	setvbuf( (_s), NULL, _IOLBF, 0 )
+#endif
diff --git a/urt/rle_error.c b/urt/rle_error.c
new file mode 100644
index 00000000..acaca1a6
--- /dev/null
+++ b/urt/rle_error.c
@@ -0,0 +1,118 @@
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ */
+/* 
+ * rle_error.c - Error message stuff for URT.
+ * 
+ * Author:	Spencer W. Thomas
+ * 		EECS Dept.
+ * 		University of Michigan
+ * Date:	Mon Mar  2 1992
+ * Copyright (c) 1992, University of Michigan
+ */
+
+#include <string.h>
+
+#include "rle.h"
+
+/*****************************************************************
+ * TAG( rle_alloc_error )
+ * 
+ * Print memory allocation error message and exit.
+ * Inputs:
+ * 	pgm:		Name of this program.
+ * 	name:		Name of memory trying to be allocated.
+ * Outputs:
+ * 	Prints message and exits.
+ *
+ * Returns int because it's used in a conditional expression.
+ */
+int
+rle_alloc_error( pgm, name )
+CONST_DECL char *pgm, *name;
+{
+    if ( name )
+	fprintf( stderr, "%s: memory allocation failed.\n", pgm );
+    else
+	fprintf( stderr, "%s: memory allocation failed (no space for %s).\n",
+		 pgm, name );
+
+    exit( RLE_NO_SPACE );
+
+    /* Will some compilers bitch about this because they know exit
+     * doesn't return??
+     */
+    return 0;
+}
+
+/*****************************************************************
+ * TAG( rle_get_error )
+ * 
+ * Print an error message for the return code from rle_get_setup
+ * Inputs:
+ * 	code:		The return code from rle_get_setup.
+ *	pgmname:	Name of this program (argv[0]).
+ *	fname:		Name of the input file.
+ * Outputs:
+ * 	Prints an error message on standard output.
+ *	Returns code.
+ */
+int
+rle_get_error( code, pgmname, fname )
+int code;
+CONST_DECL char *pgmname;
+CONST_DECL char *fname;
+{
+    if (! fname || strcmp( fname, "-" ) == 0 )
+	fname = "Standard Input";
+
+    switch( code )
+    {
+    case RLE_SUCCESS:		/* success */
+	break;
+
+    case RLE_NOT_RLE:		/* Not an RLE file */
+	fprintf( stderr, "%s: %s is not an RLE file\n",
+		 pgmname, fname );
+	break;
+
+    case RLE_NO_SPACE:			/* malloc failed */
+	fprintf( stderr,
+		 "%s: Malloc failed reading header of file %s\n",
+		 pgmname, fname );
+	break;
+
+    case RLE_EMPTY:
+	fprintf( stderr, "%s: %s is an empty file\n",
+		 pgmname, fname );
+	break;
+
+    case RLE_EOF:
+	fprintf( stderr,
+		 "%s: RLE header of %s is incomplete (premature EOF)\n",
+		 pgmname, fname );
+	break;
+
+    default:
+	fprintf( stderr, "%s: Error encountered reading header of %s\n",
+		 pgmname, fname );
+	break;
+    }
+    return code;
+}
+
+
diff --git a/urt/rle_getcom.c b/urt/rle_getcom.c
new file mode 100644
index 00000000..20da56a9
--- /dev/null
+++ b/urt/rle_getcom.c
@@ -0,0 +1,99 @@
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ */
+/* 
+ * rle_getcom.c - Get specific comments from the_hdr structure.
+ * 
+ * Author:	Spencer W. Thomas
+ * 		Computer Science Dept.
+ * 		University of Utah
+ * Date:	Sun Jan 25 1987
+ * Copyright (c) 1987, University of Utah
+ */
+
+#include <stdio.h>
+#include "rle.h"
+
+/*****************************************************************
+ * TAG( match )
+ * 
+ * Match a name against a test string for "name=value" or "name".
+ * If it matches name=value, return pointer to value part, if just
+ * name, return pointer to NUL at end of string.  If no match, return NULL.
+ *
+ * Inputs:
+ * 	n:	Name to match.  May also be "name=value" to make it easier
+ *		to replace comments.
+ *	v:	Test string.
+ * Outputs:
+ * 	Returns pointer as above.
+ * Assumptions:
+ *	[None]
+ * Algorithm:
+ *	[None]
+ */
+static const char *
+match(const char * const nArg,
+      const char * const vArg) {
+
+    const char * n;
+    const char * v;
+
+    for (n = nArg, v = vArg; *n != '\0' && *n != '=' && *n == *v; n++, v++)
+        ;
+    if (*n == '\0' || *n == '=') {
+        if (*v == '\0')
+            return v;
+        else if (*v == '=')
+            return ++v;
+    }
+    return NULL;
+}
+
+
+
+/*****************************************************************
+ * TAG( rle_getcom )
+ * 
+ * Return a pointer to the value part of a name=value pair in the comments.
+ * Inputs:
+ * 	name:		Name part of the comment to search for.
+ *	the_hdr:	rle_dflt_hdr structure.
+ * Outputs:
+ * 	Returns pointer to value part of comment or NULL if no match.
+ * Assumptions:
+ *	[None]
+ * Algorithm:
+ *	[None]
+ */
+const char *
+rle_getcom(const char * const name,
+           rle_hdr *    const the_hdr) {
+
+    const char ** cp;
+
+    if (the_hdr->comments == NULL)
+        return NULL;
+
+    for (cp = the_hdr->comments; *cp; ++cp) {
+        const char * const v = match(name, *cp);
+        if (v != NULL )
+            return v;
+    }
+    return NULL;
+}
+
diff --git a/urt/rle_getrow.c b/urt/rle_getrow.c
new file mode 100644
index 00000000..fadf5441
--- /dev/null
+++ b/urt/rle_getrow.c
@@ -0,0 +1,562 @@
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ *
+ *  Modified at BRL 16-May-88 by Mike Muuss to avoid Alliant STDC desire
+ *  to have all "void" functions so declared.
+ */
+/* 
+ * rle_getrow.c - Read an RLE file in.
+ * 
+ * Author:  Spencer W. Thomas
+ *      Computer Science Dept.
+ *      University of Utah
+ * Date:    Wed Apr 10 1985
+ * Copyright (c) 1985 Spencer W. Thomas
+ * 
+ * $Id: rle_getrow.c,v 3.0.1.5 1992/03/04 19:33:08 spencer Exp spencer $
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include "pm.h"
+#include "mallocvar.h"
+
+#include "rle.h"
+#include "rle_code.h"
+#include "vaxshort.h"
+
+/* Read a two-byte "short" that started in VAX (LITTLE_ENDIAN) order */
+#define VAXSHORT( var, fp )\
+    { var = fgetc(fp)&0xFF; var |= (fgetc(fp)) << 8; }
+  
+/* Instruction format -- first byte is opcode, second is datum. */
+
+#define OPCODE(inst) (inst[0] & ~LONG)
+#define LONGP(inst) (inst[0] & LONG)
+#define DATUM(inst) (inst[1] & 0xff)    /* Make sure it's unsigned. */
+
+static int     debug_f;     /* If non-zero, print debug info. */
+
+/*****************************************************************
+ * TAG( rle_get_setup )
+ * 
+ * Read the initialization information from an RLE file.
+ * Inputs:
+ *  the_hdr:    Contains pointer to the input file.
+ * Outputs:
+ *  the_hdr:    Initialized with information from the
+ *          input file.
+ *  Returns 0 on success, -1 if the file is not an RLE file,
+ *  -2 if malloc of the color map failed, -3 if an immediate EOF
+ *  is hit (empty input file), and -4 if an EOF is encountered reading
+ *  the setup information.
+ * Assumptions:
+ *  infile points to the "magic" number in an RLE file (usually
+ * byte 0 in the file).
+ * Algorithm:
+ *  Read in the setup info and fill in the_hdr.
+ */
+int
+rle_get_setup(rle_hdr * const the_hdr) {
+    struct XtndRsetup setup;
+    short magic;
+    FILE * infile = the_hdr->rle_file;
+    int i;
+    char * comment_buf;
+    
+    /* Clear old stuff out of the header. */
+    rle_hdr_clear( the_hdr );
+    if ( the_hdr->is_init != RLE_INIT_MAGIC )
+        rle_names( the_hdr, "Urt", "some file", 0 );
+    the_hdr->img_num++;     /* Count images. */
+
+    VAXSHORT( magic, infile );
+    if ( feof( infile ) )
+        return RLE_EMPTY;
+    if ( magic != RLE_MAGIC )
+        return RLE_NOT_RLE;
+    fread( &setup, 1, SETUPSIZE, infile );  /* assume VAX packing */
+    if ( feof( infile ) )
+        return RLE_EOF;
+
+    /* Extract information from setup */
+    the_hdr->ncolors = setup.h_ncolors;
+    for ( i = 0; i < the_hdr->ncolors; i++ )
+        RLE_SET_BIT( *the_hdr, i );
+
+    if ( !(setup.h_flags & H_NO_BACKGROUND) && setup.h_ncolors > 0 )
+    {
+        rle_pixel * bg_color;
+
+        MALLOCARRAY(the_hdr->bg_color, setup.h_ncolors);
+        MALLOCARRAY(bg_color, 1 + (setup.h_ncolors / 2) * 2);
+        RLE_CHECK_ALLOC( the_hdr->cmd, the_hdr->bg_color && bg_color,
+                         "background color" );
+        fread( (char *)bg_color, 1, 1 + (setup.h_ncolors / 2) * 2, infile );
+        for ( i = 0; i < setup.h_ncolors; i++ )
+            the_hdr->bg_color[i] = bg_color[i];
+        free( bg_color );
+    }
+    else
+    {
+        (void)getc( infile );   /* skip filler byte */
+        the_hdr->bg_color = NULL;
+    }
+
+    if ( setup.h_flags & H_NO_BACKGROUND )
+        the_hdr->background = 0;
+    else if ( setup.h_flags & H_CLEARFIRST )
+        the_hdr->background = 2;
+    else
+        the_hdr->background = 1;
+    if ( setup.h_flags & H_ALPHA )
+    {
+        the_hdr->alpha = 1;
+        RLE_SET_BIT( *the_hdr, RLE_ALPHA );
+    }
+    else
+        the_hdr->alpha = 0;
+
+    the_hdr->xmin = vax_gshort( setup.hc_xpos );
+    the_hdr->ymin = vax_gshort( setup.hc_ypos );
+    the_hdr->xmax = the_hdr->xmin + vax_gshort( setup.hc_xlen ) - 1;
+    the_hdr->ymax = the_hdr->ymin + vax_gshort( setup.hc_ylen ) - 1;
+
+    the_hdr->ncmap = setup.h_ncmap;
+    the_hdr->cmaplen = setup.h_cmaplen;
+    if ( the_hdr->ncmap > 0 )
+    {
+        int const maplen = the_hdr->ncmap * (1 << the_hdr->cmaplen);
+        int i;
+        char *maptemp;
+
+        MALLOCARRAY(the_hdr->cmap, maplen);
+        MALLOCARRAY(maptemp, 2 * maplen);
+        if ( the_hdr->cmap == NULL || maptemp == NULL )
+        {
+            pm_error("Malloc failed for color map of size %d*%d "
+                     "in rle_get_setup, reading '%s'",
+                     the_hdr->ncmap, (1 << the_hdr->cmaplen),
+                     the_hdr->file_name );
+            return RLE_NO_SPACE;
+        }
+        fread( maptemp, 2, maplen, infile );
+        for ( i = 0; i < maplen; i++ )
+            the_hdr->cmap[i] = vax_gshort( &maptemp[i * 2] );
+        free( maptemp );
+    }
+
+    /* Check for comments */
+    if ( setup.h_flags & H_COMMENT )
+    {
+        short comlen, evenlen;
+        register char * cp;
+
+        VAXSHORT( comlen, infile ); /* get comment length */
+        evenlen = (comlen + 1) & ~1;    /* make it even */
+        if ( evenlen )
+        {
+            MALLOCARRAY(comment_buf, evenlen);
+    
+            if ( comment_buf == NULL )
+            {
+                pm_error("Malloc failed for comment buffer of size %d "
+                         "in rle_get_setup, reading '%s'",
+                         comlen, the_hdr->file_name );
+                return RLE_NO_SPACE;
+            }
+            fread( comment_buf, 1, evenlen, infile );
+            /* Count the comments */
+            for ( i = 0, cp = comment_buf; cp < comment_buf + comlen; cp++ )
+                if ( *cp == 0 )
+                    i++;
+            i++;            /* extra for NULL pointer at end */
+            /* Get space to put pointers to comments */
+            MALLOCARRAY(the_hdr->comments, i);
+            if ( the_hdr->comments == NULL )
+            {
+                pm_error("Malloc failed for %d comment pointers "
+                         "in rle_get_setup, reading '%s'",
+                         i, the_hdr->file_name );
+                return RLE_NO_SPACE;
+            }
+            /* Get pointers to the comments */
+            *the_hdr->comments = comment_buf;
+            for ( i = 1, cp = comment_buf + 1;
+                  cp < comment_buf + comlen;
+                  cp++ )
+                if ( *(cp - 1) == 0 )
+                    the_hdr->comments[i++] = cp;
+            the_hdr->comments[i] = NULL;
+        }
+        else
+            the_hdr->comments = NULL;
+    }
+    else
+        the_hdr->comments = NULL;
+
+    /* Initialize state for rle_getrow */
+    the_hdr->priv.get.scan_y = the_hdr->ymin;
+    the_hdr->priv.get.vert_skip = 0;
+    the_hdr->priv.get.is_eof = 0;
+    the_hdr->priv.get.is_seek = ftell( infile ) > 0;
+    debug_f = 0;
+
+    if ( !feof( infile ) )
+        return RLE_SUCCESS; /* success! */
+    else
+    {
+        the_hdr->priv.get.is_eof = 1;
+        return RLE_EOF;
+    }
+}
+
+
+/*****************************************************************
+ * TAG( rle_get_setup_ok )
+ * 
+ * Read the initialization information from an RLE file.
+ * Inputs:
+ *  the_hdr:    Contains pointer to the input file.
+ *  prog_name:  Program name to be printed in the error message.
+ *      file_name:  File name to be printed in the error message.
+ *                  If NULL, the string "stdin" is generated.
+ * Outputs:
+ *  the_hdr:    Initialized with information from the
+ *          input file.
+ *      If reading the header fails, it prints an error message
+ *  and exits with the appropriate status code.
+ * Algorithm:
+ *  rle_get_setup does all the work.
+ */
+void
+rle_get_setup_ok( the_hdr, prog_name, file_name )
+rle_hdr * the_hdr;
+const char *prog_name;
+const char *file_name;
+{
+    int code;
+
+    /* Backwards compatibility: if is_init is not properly set, 
+     * initialize the header.
+     */
+    if ( the_hdr->is_init != RLE_INIT_MAGIC )
+    {
+    FILE *f = the_hdr->rle_file;
+    rle_hdr_init( the_hdr );
+    the_hdr->rle_file = f;
+    rle_names( the_hdr, prog_name, file_name, 0 );
+    }
+
+    code = rle_get_error( rle_get_setup( the_hdr ),
+              the_hdr->cmd, the_hdr->file_name );
+    if (code)
+    exit( code );
+}
+
+
+/*****************************************************************
+ * TAG( rle_debug )
+ * 
+ * Turn RLE debugging on or off.
+ * Inputs:
+ *  on_off:     if 0, stop debugging, else start.
+ * Outputs:
+ *  Sets internal debug flag.
+ * Assumptions:
+ *  [None]
+ * Algorithm:
+ *  [None]
+ */
+void
+rle_debug( on_off )
+int on_off;
+{
+    debug_f = on_off;
+
+    /* Set line buffering on stderr.  Character buffering is the default, and
+     * it is SLOOWWW for large amounts of output.
+     */
+    setvbuf( stderr, NULL, _IOLBF, 0);
+/*
+    setlinebuf( stderr );
+*/
+}
+
+
+/*****************************************************************
+ * TAG( rle_getrow )
+ * 
+ * Get a scanline from the input file.
+ * Inputs:
+ *  the_hdr:    Header structure containing information about 
+ *          the input file.
+ * Outputs:
+ *  scanline:   an array of pointers to the individual color
+ *          scanlines.  Scanline is assumed to have
+ *          the_hdr->ncolors pointers to arrays of rle_pixel,
+ *          each of which is at least the_hdr->xmax+1 long.
+ *  Returns the current scanline number.
+ * Assumptions:
+ *  rle_get_setup has already been called.
+ * Algorithm:
+ *  If a vertical skip is being executed, and clear-to-background is
+ *  specified (the_hdr->background is true), just set the
+ *  scanlines to the background color.  If clear-to-background is
+ *  not set, just increment the scanline number and return.
+ * 
+ *  Otherwise, read input until a vertical skip is encountered,
+ *  decoding the instructions into scanline data.
+ *
+ *  If ymax is reached (or, somehow, passed), continue reading and
+ *  discarding input until end of image.
+ */
+int
+rle_getrow( the_hdr, scanline )
+rle_hdr * the_hdr;
+rle_pixel *scanline[];
+{
+    register rle_pixel * scanc;
+    register int nc;
+    register FILE *infile = the_hdr->rle_file;
+    int scan_x = the_hdr->xmin, /* current X position */
+        max_x = the_hdr->xmax,  /* End of the scanline */
+       channel = 0;         /* current color channel */
+    int ns;         /* Number to skip */
+    short word, long_data;
+    char inst[2];
+
+    /* Clear to background if specified */
+    if ( the_hdr->background != 1 )
+    {
+    if ( the_hdr->alpha && RLE_BIT( *the_hdr, -1 ) )
+        memset( (char *)scanline[-1] + the_hdr->xmin, 0,
+           the_hdr->xmax - the_hdr->xmin + 1 );
+    for ( nc = 0; nc < the_hdr->ncolors; nc++ )
+        if ( RLE_BIT( *the_hdr, nc ) ) {
+        /* Unless bg color given explicitly, use 0. */
+        if ( the_hdr->background != 2 || the_hdr->bg_color[nc] == 0 )
+            memset( (char *)scanline[nc] + the_hdr->xmin, 0,
+               the_hdr->xmax - the_hdr->xmin + 1 );
+        else
+            memset((char *)scanline[nc] + the_hdr->xmin,
+                   the_hdr->bg_color[nc],
+                   the_hdr->xmax - the_hdr->xmin + 1);
+        }
+    }
+
+    /* If skipping, then just return */
+    if ( the_hdr->priv.get.vert_skip > 0 )
+    {
+    the_hdr->priv.get.vert_skip--;
+    the_hdr->priv.get.scan_y++;
+    if ( the_hdr->priv.get.vert_skip > 0 ) {
+        if ( the_hdr->priv.get.scan_y >= the_hdr->ymax )
+        {
+        int y = the_hdr->priv.get.scan_y;
+        while ( rle_getskip( the_hdr ) != 32768 )
+            ;
+        return y;
+        }
+        else
+        return the_hdr->priv.get.scan_y;
+    }
+    }
+
+    /* If EOF has been encountered, return also */
+    if ( the_hdr->priv.get.is_eof )
+    return ++the_hdr->priv.get.scan_y;
+
+    /* Otherwise, read and interpret instructions until a skipLines
+     * instruction is encountered.
+     */
+    if ( RLE_BIT( *the_hdr, channel ) )
+    scanc = scanline[channel] + scan_x;
+    else
+    scanc = NULL;
+    for (;;)
+    {
+    inst[0] = getc( infile );
+    inst[1] = getc( infile );
+    if ( feof(infile) )
+    {
+        the_hdr->priv.get.is_eof = 1;
+        break;      /* <--- one of the exits */
+    }
+
+    switch( OPCODE(inst) )
+    {
+    case RSkipLinesOp:
+        if ( LONGP(inst) )
+        {
+        VAXSHORT( the_hdr->priv.get.vert_skip, infile );
+        }
+        else
+        the_hdr->priv.get.vert_skip = DATUM(inst);
+        if (debug_f)
+        fprintf(stderr, "Skip %d Lines (to %d)\n",
+            the_hdr->priv.get.vert_skip,
+            the_hdr->priv.get.scan_y +
+                the_hdr->priv.get.vert_skip );
+
+        break;          /* need to break for() here, too */
+
+    case RSetColorOp:
+        channel = DATUM(inst);  /* select color channel */
+        if ( channel == 255 )
+        channel = -1;
+        scan_x = the_hdr->xmin;
+        if ( RLE_BIT( *the_hdr, channel ) )
+        scanc = scanline[channel]+scan_x;
+        if ( debug_f )
+        fprintf( stderr, "Set color to %d (reset x to %d)\n",
+             channel, scan_x );
+        break;
+
+    case RSkipPixelsOp:
+        if ( LONGP(inst) )
+        {
+        VAXSHORT( long_data, infile );
+        scan_x += long_data;
+        scanc += long_data;
+        if ( debug_f )
+            fprintf( stderr, "Skip %d pixels (to %d)\n",
+                long_data, scan_x );
+        }
+        else
+        {
+        scan_x += DATUM(inst);
+        scanc += DATUM(inst);
+        if ( debug_f )
+            fprintf( stderr, "Skip %d pixels (to %d)\n",
+                DATUM(inst), scan_x );
+        }
+        break;
+
+    case RByteDataOp:
+        if ( LONGP(inst) )
+        {
+        VAXSHORT( nc, infile );
+        }
+        else
+        nc = DATUM(inst);
+        nc++;
+        if ( debug_f ) {
+        if ( RLE_BIT( *the_hdr, channel ) )
+            fprintf( stderr, "Pixel data %d (to %d):", nc, scan_x+nc );
+        else
+            fprintf( stderr, "Pixel data %d (to %d)\n", nc, scan_x+nc);
+        }
+        if ( RLE_BIT( *the_hdr, channel ) )
+        {
+        /* Don't fill past end of scanline! */
+        if ( scan_x + nc > max_x )
+        {
+            ns = scan_x + nc - max_x - 1;
+            nc -= ns;
+        }
+        else
+            ns = 0;
+        fread( (char *)scanc, 1, nc, infile );
+        while ( ns-- > 0 )
+            (void)getc( infile );
+        if ( nc & 1 )
+            (void)getc( infile );   /* throw away odd byte */
+        }
+        else
+        if ( the_hdr->priv.get.is_seek )
+            fseek( infile, ((nc + 1) / 2) * 2, 1 );
+        else
+        {
+            register int ii;
+            for ( ii = ((nc + 1) / 2) * 2; ii > 0; ii-- )
+            (void) getc( infile );  /* discard it */
+        }
+
+        scanc += nc;
+        scan_x += nc;
+        if ( debug_f && RLE_BIT( *the_hdr, channel ) )
+        {
+        rle_pixel * cp = scanc - nc;
+        for ( ; nc > 0; nc-- )
+            fprintf( stderr, "%02x", *cp++ );
+        putc( '\n', stderr );
+        }
+        break;
+
+    case RRunDataOp:
+        if ( LONGP(inst) )
+        {
+        VAXSHORT( nc, infile );
+        }
+        else
+        nc = DATUM(inst);
+        nc++;
+        scan_x += nc;
+
+        VAXSHORT( word, infile );
+        if ( debug_f )
+        fprintf( stderr, "Run length %d (to %d), data %02x\n",
+                nc, scan_x, word );
+        if ( RLE_BIT( *the_hdr, channel ) )
+        {
+        if ( scan_x > max_x )
+        {
+            ns = scan_x - max_x - 1;
+            nc -= ns;
+        } 
+        else
+            ns = 0;
+        if ( nc >= 10 )     /* break point for 785, anyway */
+        {
+            memset((char *)scanc, word, nc);
+            scanc += nc;
+        }
+        else
+            for ( nc--; nc >= 0; nc--, scanc++ )
+            *scanc = word;
+        }
+        break;
+
+    case REOFOp:
+        the_hdr->priv.get.is_eof = 1;
+        if ( debug_f )
+        fprintf( stderr, "End of Image\n" );
+        break;
+
+    default:
+        fprintf( stderr,
+             "%s: rle_getrow: Unrecognized opcode: %d, reading %s\n",
+             the_hdr->cmd, inst[0], the_hdr->file_name );
+        exit(1);
+    }
+    if ( OPCODE(inst) == RSkipLinesOp || OPCODE(inst) == REOFOp )
+        break;          /* <--- the other loop exit */
+    }
+
+    /* If at end, skip the rest of a malformed image. */
+    if ( the_hdr->priv.get.scan_y >= the_hdr->ymax )
+    {
+    int y = the_hdr->priv.get.scan_y;
+    while ( rle_getskip( the_hdr ) != 32768 )
+        ;
+    return y;
+    }
+
+    return the_hdr->priv.get.scan_y;
+}
diff --git a/urt/rle_getskip.c b/urt/rle_getskip.c
new file mode 100644
index 00000000..f1c333e7
--- /dev/null
+++ b/urt/rle_getskip.c
@@ -0,0 +1,162 @@
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ */
+/* 
+ * rle_getskip.c - Skip scanlines on input.
+ * 
+ * Author:	Spencer W. Thomas
+ * 		EECS Dept.
+ * 		University of Michigan
+ * Date:	Wed Jun 27 1990
+ * Copyright (c) 1990, University of Michigan
+ */
+
+#include "rle.h"
+#include "rle_code.h"
+
+/* Read a two-byte "short" that started in VAX (LITTLE_ENDIAN) order */
+#define VAXSHORT( var, fp )\
+	{ var = fgetc(fp)&0xFF; var |= (fgetc(fp)) << 8; }
+  
+/* Instruction format -- first byte is opcode, second is datum. */
+
+#define OPCODE(inst) (inst[0] & ~LONG)
+#define LONGP(inst) (inst[0] & LONG)
+#define DATUM(inst) (inst[1] & 0xff)	/* Make sure it's unsigned. */
+
+/*****************************************************************
+ * TAG( rle_getskip )
+ * 
+ * Skip the next scanline with data on it.
+ * Most useful for skipping to end-of-image.
+ * Inputs:
+ * 	the_hdr:	Describes input image.
+ * Outputs:
+ * 	Returns the number of the next scanline.  At EOF returns 32768.
+ * Assumptions:
+ * 	rle_get_setup has been called.
+ * Algorithm:
+ * 	Read input to the beginning of the next scanline, or to EOF or
+ * 	end of image.
+ */
+unsigned int
+rle_getskip( the_hdr )
+rle_hdr *the_hdr;
+{
+    unsigned char inst[2];
+    register FILE *infile = the_hdr->rle_file;
+    int nc;
+
+    /* Add in vertical skip from last scanline */
+    if ( the_hdr->priv.get.vert_skip > 0) 
+	the_hdr->priv.get.scan_y += the_hdr->priv.get.vert_skip;
+    the_hdr->priv.get.vert_skip = 0;
+
+    if ( the_hdr->priv.get.is_eof )
+	return 32768;		/* too big for 16 bits, signal EOF */
+    
+    /* Otherwise, read and interpret instructions until a skipLines
+     * instruction is encountered.
+     */
+    for (;;)
+    {
+        inst[0] = getc( infile );
+	inst[1] = getc( infile );
+	if ( feof(infile) )
+	{
+	    the_hdr->priv.get.is_eof = 1;
+	    break;		/* <--- one of the exits */
+	}
+
+	switch( OPCODE(inst) )
+	{
+	case RSkipLinesOp:
+	    if ( LONGP(inst) )
+	    {
+		VAXSHORT( the_hdr->priv.get.vert_skip, infile );
+	    }
+	    else
+		the_hdr->priv.get.vert_skip = DATUM(inst);
+	    break;			/* need to break for() here, too */
+
+	case RSetColorOp:
+	    /* No-op here. */
+	    break;
+
+	case RSkipPixelsOp:
+	    if ( LONGP(inst) )
+	    {
+		(void)getc( infile );
+		(void)getc( infile );
+	    }
+	    break;
+
+	case RByteDataOp:
+	    if ( LONGP(inst) )
+	    {
+	        VAXSHORT( nc, infile );
+	    }
+	    else
+		nc = DATUM(inst);
+	    nc++;
+	    if ( the_hdr->priv.get.is_seek )
+		fseek( infile, ((nc + 1) / 2) * 2, 1 );
+	    else
+	    {
+		register int ii;
+		for ( ii = ((nc + 1) / 2) * 2; ii > 0; ii-- )
+		    (void) getc( infile );	/* discard it */
+	    }
+
+	    break;
+
+	case RRunDataOp:
+	    if ( LONGP(inst) )
+	    {
+		(void)getc( infile );
+		(void)getc( infile );
+	    }
+	    (void)getc( infile );
+	    (void)getc( infile );
+	    break;
+
+	case REOFOp:
+	    the_hdr->priv.get.is_eof = 1;
+	    break;
+
+	default:
+	    fprintf( stderr,
+		     "%s: rle_getskip: Unrecognized opcode: %d, reading %s\n",
+		     the_hdr->cmd, OPCODE(inst), the_hdr->file_name );
+	    exit(1);
+	}
+	if ( OPCODE(inst) == REOFOp )
+	    break;			/* <--- the other loop exit */
+	if ( OPCODE(inst) == RSkipLinesOp )
+	    break;
+    }
+
+    /* Return the number of the NEXT scanline. */
+    the_hdr->priv.get.scan_y +=
+	the_hdr->priv.get.vert_skip;
+    the_hdr->priv.get.vert_skip = 0;
+
+    if ( the_hdr->priv.get.is_eof )
+	return 32768;		/* too big for 16 bits, signal EOF */
+    else
+	return the_hdr->priv.get.scan_y;
+}
diff --git a/urt/rle_global.c b/urt/rle_global.c
new file mode 100644
index 00000000..90d3f975
--- /dev/null
+++ b/urt/rle_global.c
@@ -0,0 +1,85 @@
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ *
+ *  Modified at BRL 16-May-88 by Mike Muuss to avoid Alliant STDC desire
+ *  to have all "void" functions so declared.
+ */
+/* 
+ * rle_global.c - Global variable initialization for rle routines.
+ * 
+ * Author:	Spencer W. Thomas
+ * 		Computer Science Dept.
+ * 		University of Utah
+ * Date:	Thu Apr 25 1985
+ * Copyright (c) 1985,1986 Spencer W. Thomas
+ * 
+ * $Id: rle_global.c,v 3.0.1.1 1992/01/28 18:23:03 spencer Exp $
+ */
+
+#include <stdio.h>
+#include "rle_put.h"
+#include "rle.h"
+#include "Runput.h"
+
+struct rle_dispatch_tab rle_DTable[] = {
+    {
+	" OB",
+	RunSetup,
+	RunSkipBlankLines,
+	RunSetColor,
+	RunSkipPixels,
+	RunNewScanLine,
+	Runputdata,
+	Runputrun,
+	DefaultBlockHook,
+	RunputEof
+    },
+};
+
+static int bg_color[3] = { 0, 0, 0 };
+
+rle_hdr rle_dflt_hdr = {
+    RUN_DISPATCH,		/* dispatch value */
+    3,				/* 3 colors */
+    bg_color,			/* background color */
+    0,				/* (alpha) if 1, save alpha channel */
+    2,				/* (background) 0->just save pixels, */
+				/* 1->overlay, 2->clear to bg first */
+    0, 511,			/* (xmin, xmax) X bounds to save */
+    0, 511,			/* (ymin, ymax) Y bounds to save */
+    0,				/* ncmap (if != 0, save color map) */
+    8,				/* cmaplen (log2 of length of color map) */
+    NULL,			/* pointer to color map */
+    NULL,			/* pointer to comment strings */
+    NULL,			/* output file -- must be set dynamically */
+    { 7 },			/* RGB channels only */
+    0L,				/* Can't free name and file fields. */
+    "Urt",			/* Default "program name". */
+    "no file",			/* No file name given. */
+    0				/* First image. */
+    /* Can't initialize the union */
+};
+
+#if 0
+/* ARGSUSED */
+void
+NullputEof(the_hdr)
+rle_hdr * the_hdr;
+{
+				/* do nothing */
+}
+#endif
diff --git a/urt/rle_hdr.c b/urt/rle_hdr.c
new file mode 100644
index 00000000..3cc0401d
--- /dev/null
+++ b/urt/rle_hdr.c
@@ -0,0 +1,297 @@
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ */
+/* 
+ * rle_hdr.c - Functions to manipulate rle_hdr structures.
+ * 
+ * Author:	Spencer W. Thomas
+ * 		EECS Dept.
+ * 		University of Michigan
+ * Date:	Mon May 20 1991
+ * Copyright (c) 1991, University of Michigan
+ */
+
+#include "rle.h"
+
+#include <string.h>
+
+/*****************************************************************
+ * TAG( rle_names )
+ *
+ * Load program and file names into header.
+ * Inputs:
+ * 	the_hdr:	Header to modify.
+ * 	pgmname:	The program name.
+ * 	fname:		The file name.
+ * 	img_num:	Number of the image within the file.
+ * Outputs:
+ * 	the_hdr:	Modified header.
+ * Algorithm:
+ * 	If values previously filled in (by testing is_init field),
+ * 	free them.  Make copies of file name and program name,
+ * 	modifying file name for standard i/o.  Set is_init field.
+ */
+void
+rle_names( the_hdr, pgmname, fname, img_num )
+rle_hdr *the_hdr;
+CONST_DECL char *pgmname;
+CONST_DECL char *fname;
+int img_num;
+{
+#if 0
+    /* Can't do this because people do hdr1 = hdr2, which copies
+       the pointers. */
+
+    /* If filled in, free previous values. */
+    if ( the_hdr->is_init == RLE_INIT_MAGIC &&
+	 the_hdr->cmd != NULL && the_hdr->file_name != NULL )
+    {
+	if ( pgmname != the_hdr->cmd )
+	    free( the_hdr->cmd );
+	if ( fname != the_hdr->file_name )
+	    free( the_hdr->file_name );
+    }
+#endif
+
+    /* Mark as filled in. */
+    the_hdr->is_init = RLE_INIT_MAGIC;
+
+    /* Default file name for stdin/stdout. */
+    if ( fname == NULL || strcmp( fname, "-" ) == 0 || *fname == '\0' )
+	fname = "Standard I/O";
+    if ( pgmname == NULL )
+	pgmname = rle_dflt_hdr.cmd;
+
+    /* Fill in with copies of the strings. */
+    if ( the_hdr->cmd != pgmname )
+    {
+	char *tmp = (char *)malloc( strlen( pgmname ) + 1 );
+	RLE_CHECK_ALLOC( pgmname, tmp, 0 );
+	strcpy( tmp, pgmname );
+	the_hdr->cmd = tmp;
+    }
+
+    if ( the_hdr->file_name != fname )
+    {
+	char *tmp = (char *)malloc( strlen( fname ) + 1 );
+	RLE_CHECK_ALLOC( pgmname, tmp, 0 );
+	strcpy( tmp, fname );
+	the_hdr->file_name = tmp;
+    }
+
+    the_hdr->img_num = img_num;
+}
+
+
+/* Used by rle_hdr_cp and rle_hdr_init to avoid recursion loops. */
+static int no_recurse = 0;
+
+/*****************************************************************
+ * TAG( rle_hdr_cp )
+ * 
+ * Make a "safe" copy of a rle_hdr structure.
+ * Inputs:
+ * 	from_hdr:	Header to be copied.
+ * Outputs:
+ * 	to_hdr:		Copy of from_hdr, with all memory referred to
+ * 			by pointers copied.  Also returned as function
+ * 			value.  If NULL, a static header is used.
+ * Assumptions:
+ * 	It is safe to call rle_hdr_init on to_hdr.
+ * Algorithm:
+ * 	Initialize to_hdr, copy from_hdr to it, then copy the memory
+ * 	referred to by all non-null pointers.
+ */
+rle_hdr *
+rle_hdr_cp( from_hdr, to_hdr )
+rle_hdr *from_hdr, *to_hdr;
+{
+    static rle_hdr dflt_hdr;
+    CONST_DECL char *cmd, *file;
+    int num;
+
+    /* Save command, file name, and image number if already initialized. */
+    if ( to_hdr &&  to_hdr->is_init == RLE_INIT_MAGIC )
+    {
+	cmd = to_hdr->cmd;
+	file = to_hdr->file_name;
+	num = to_hdr->img_num;
+    }
+    else
+    {
+	cmd = file = NULL;
+	num = 0;
+    }
+
+    if ( !no_recurse )
+    {
+	no_recurse++;
+	rle_hdr_init( to_hdr );
+	no_recurse--;
+    }
+
+    if ( to_hdr == NULL )
+	to_hdr = &dflt_hdr;
+
+    *to_hdr = *from_hdr;
+
+    if ( to_hdr->bg_color )
+    {
+	int size = to_hdr->ncolors * sizeof(int);
+	to_hdr->bg_color = (int *)malloc( size );
+	RLE_CHECK_ALLOC( to_hdr->cmd, to_hdr->bg_color, "background color" );
+	memcpy( to_hdr->bg_color, from_hdr->bg_color, size );
+    }
+
+    if ( to_hdr->cmap )
+    {
+	int size = to_hdr->ncmap * (1 << to_hdr->cmaplen) * sizeof(rle_map);
+	to_hdr->cmap = (rle_map *)malloc( size );
+	RLE_CHECK_ALLOC( to_hdr->cmd, to_hdr->cmap, "color map" );
+	memcpy( to_hdr->cmap, from_hdr->cmap, size );
+    }
+
+    /* Only copy array of pointers, as the original comment memory
+     * never gets overwritten.
+     */
+    if ( to_hdr->comments )
+    {
+	int size = 0;
+	CONST_DECL char **cp;
+	for ( cp=to_hdr->comments; *cp; cp++ )
+	    size++;		/* Count the comments. */
+	/* Check if there are really any comments. */
+	if ( size )
+	{
+	    size++;		/* Copy the NULL pointer, too. */
+	    size *= sizeof(char *);
+	    to_hdr->comments = (CONST_DECL char **)malloc( size );
+	    RLE_CHECK_ALLOC( to_hdr->cmd, to_hdr->comments, "comments" );
+	    memcpy( to_hdr->comments, from_hdr->comments, size );
+	}
+	else
+	    to_hdr->comments = NULL;	/* Blow off empty comment list. */
+    }
+
+    /* Restore the names to their original values. */
+    to_hdr->cmd = cmd;
+    to_hdr->file_name = file;
+
+    /* Lines above mean nothing much happens if cmd and file are != NULL. */
+    rle_names( to_hdr, to_hdr->cmd, to_hdr->file_name, num );
+
+    return to_hdr;
+}
+
+/*****************************************************************
+ * TAG( rle_hdr_clear )
+ * 
+ * Clear out the allocated memory pieces of a header.
+ *
+ * This routine is intended to be used internally by the library, to
+ * clear a header before putting new data into it.  It clears all the
+ * fields that would be set by reading in a new image header.
+ * Therefore, it does not clear the program and file names.
+ * 
+ * Inputs:
+ * 	the_hdr:	To be cleared.
+ * Outputs:
+ * 	the_hdr:	After clearing.
+ * Assumptions:
+ * 	If is_init field is RLE_INIT_MAGIC, the header has been
+ * 	properly initialized.  This will fail every 2^(-32) times, on
+ * 	average.
+ * Algorithm:
+ * 	Free memory and set to zero all pointers, except program and
+ * 	file name.
+ */
+void
+rle_hdr_clear( the_hdr )
+rle_hdr *the_hdr;
+{
+    /* Try to free memory.  Assume if is_init is properly set that this
+     * header has been previously initialized, therefore it is safe to
+     * free memory.
+     */
+    if ( the_hdr && the_hdr->is_init == RLE_INIT_MAGIC )
+    {
+	if ( the_hdr->bg_color )
+	    free( the_hdr->bg_color );
+	the_hdr->bg_color = 0;
+	if ( the_hdr->cmap )
+	    free( the_hdr->cmap );
+	the_hdr->cmap = 0;
+	/* Unfortunately, we don't know how to free the comment memory. */
+	if ( the_hdr->comments )
+	    free( the_hdr->comments );
+	the_hdr->comments = 0;
+    }
+}
+
+
+
+/*****************************************************************
+ * TAG( rle_hdr_init )
+ * 
+ * Initialize a rle_hdr structure.
+ * Inputs:
+ * 	the_hdr:	Header to be initialized.
+ * Outputs:
+ * 	the_hdr:	Initialized header.
+ * Assumptions:
+ * 	If the_hdr->is_init is RLE_INIT_MAGIC, the header has been
+ * 	previously initialized.
+ * 	If the_hdr is a copy of another rle_hdr structure, the copy
+ * 	was made with rle_hdr_cp.
+ * Algorithm:
+ *  Fill in fields of rle_dflt_hdr that could not be set by the loader
+ *	If the_hdr is rle_dflt_hdr, do nothing else
+ *  Else:
+ *	  If the_hdr is NULL, return a copy of rle_dflt_hdr in static storage
+ * 	  If the_hdr->is_init is RLE_INIT_MAGIC, free all memory
+ * 	     pointed to by non-null pointers.
+ *    If this is a recursive call to rle_hdr_init, clear *the_hdr and
+ *      return the_hdr.
+ *    Else make a copy of rle_dflt_hdr and return its address.  Make the
+ *      copy in static storage if the_hdr is NULL, and in the_hdr otherwise.
+ */
+rle_hdr *
+rle_hdr_init( the_hdr )
+rle_hdr *the_hdr;
+{
+    rle_hdr *ret_hdr;
+
+    rle_dflt_hdr.rle_file = stdout;
+    /* The rest of rle_dflt_hdr is set by the loader's data initialization */
+
+    if ( the_hdr == &rle_dflt_hdr )
+	return the_hdr;
+
+    rle_hdr_clear( the_hdr );
+
+    /* Only call rle_hdr_cp if not called from there. */
+    if ( !no_recurse )
+    {
+	no_recurse++;
+	ret_hdr = rle_hdr_cp( &rle_dflt_hdr, the_hdr );
+	no_recurse--;
+    }
+    else
+	ret_hdr = the_hdr;
+
+    return ret_hdr;
+}
diff --git a/urt/rle_open_f.c b/urt/rle_open_f.c
new file mode 100644
index 00000000..d2575c11
--- /dev/null
+++ b/urt/rle_open_f.c
@@ -0,0 +1,310 @@
+/* 
+ * rle_open_f.c - Open a file with defaults.
+ * 
+ * Author : 	Jerry Winters 
+ * 		EECS Dept.
+ * 		University of Michigan
+ * Date:	11/14/89
+ * Copyright (c) 1990, University of Michigan
+ */
+
+#define _XOPEN_SOURCE  /* Make sure fdopen() is in stdio.h */
+
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#ifndef NO_OPEN_PIPES
+/* Need to have a SIGCLD signal catcher. */
+#include <signal.h>
+#include <sys/wait.h>
+#include <errno.h>
+
+#include "rle.h"
+
+/* Count outstanding children.  Assume no more than 100 possible. */
+#define MAX_CHILDREN 100
+static int catching_children = 0;
+static int pids[MAX_CHILDREN];
+
+
+
+static FILE *
+my_popen(const char * const cmd, 
+         const char * const mode, 
+         int  *       const pid) {
+
+    FILE *retfile;
+    int thepid = 0;
+    int pipefd[2];
+    int i;
+
+    /* Check args. */
+    if ( *mode != 'r' && *mode != 'w' )
+    {
+	errno = EINVAL;
+	return NULL;
+    }
+
+    if ( pipe(pipefd) < 0 )
+	return NULL;
+    
+    /* Flush known files. */
+    fflush(stdout);
+    fflush(stderr);
+    if ( (thepid = fork()) < 0 )
+    {
+	close(pipefd[0]);
+	close(pipefd[1]);
+	return NULL;
+    }
+    else if (thepid == 0) {
+	/* In child. */
+	/* Rearrange file descriptors. */
+	if ( *mode == 'r' )
+	{
+	    /* Parent reads from pipe, so reset stdout. */
+	    close(1);
+	    dup2(pipefd[1],1);
+	} else {
+	    /* Parent writing to pipe. */
+	    close(0);
+	    dup2(pipefd[0],0);
+	}
+	/* Close anything above fd 2. (64 is an arbitrary magic number). */
+	for ( i = 3; i < 64; i++ )
+	    close(i);
+
+	/* Finally, invoke the program. */
+	if ( execl("/bin/sh", "sh", "-c", cmd, NULL) < 0 )
+	    exit(127);
+	/* NOTREACHED */
+    }	
+
+    /* Close file descriptors, and gen up a FILE ptr */
+    if ( *mode == 'r' )
+    {
+	/* Parent reads from pipe. */
+	close(pipefd[1]);
+	retfile = fdopen( pipefd[0], mode );
+    } else {
+	/* Parent writing to pipe. */
+	close(pipefd[0]);
+	retfile = fdopen( pipefd[1], mode );
+    }
+
+    /* Return the PID. */
+    *pid = thepid;
+
+    return retfile;
+}
+#endif  /* !NO_OPEN_PIPES */
+
+/* 
+ *  Purpose : Open a file for input or ouput as controlled by the mode
+ *  parameter.  If no file name is specified (ie. file_name is null) then
+ *  a pointer to stdin or stdout will be returned.  The calling routine may
+ *  call this routine with a file name of "-".  For this case rle_open_f
+ *  will return a pointer to stdin or stdout depending on the mode.
+ *    If the user specifies a non-null file name and an I/O error occurs
+ *  when trying to open the file, rle_open_f will terminate execution with
+ *  an appropiate error message.
+ *
+ *  parameters
+ *   input:
+ *     prog_name: 	name of the calling program.
+ *     file_name : 	name of the file to open
+ *     mode : 		either "r" for read or input file or "w" for write or
+ *            		output file
+ *
+ *   output:
+ *     a file pointer
+ * 
+ */
+FILE *
+rle_open_f_noexit(const char * const prog_name, 
+                  const char * const file_name, 
+                  const char * const mode ) {
+
+    FILE *fp;
+    void perror();
+    CONST_DECL char *err_str;
+    const char *cp;
+    char *combuf;
+
+    if ( *mode == 'w' || *mode == 'a' )
+        fp = stdout;     /* Set the default value */
+    else
+        fp = stdin;
+    
+    if ( file_name != NULL && strcmp( file_name, "-" ) != 0 )
+    {
+#ifndef NO_OPEN_PIPES
+        /* Check for dead children. */
+        if ( catching_children > 0 )
+        {
+            int i, j;
+
+            /* Check all children to see if any are dead, reap them if so. */
+            for ( i = 0; i < catching_children; i++ )
+            {
+                /* The assumption here is that if it's dead, the kill
+                 * will fail, but, because we haven't waited for
+                 * it yet, it's a zombie.
+                 */
+                if (kill(pids[i], 0) < 0) {
+                    int opid = pids[i], pid = 0;
+                    /* Wait for processes & delete them from the list,
+                     * until we get the one we know is dead.
+                     * When removing one earlier in the list than
+                     * the one we found, decrement our loop index.
+                     */
+                    while (pid != opid) {
+                        pid = wait( NULL );
+                        for ( j = 0;
+                              j < catching_children && pids[j] != pid;
+                              j++ )
+                            ;
+                        if ( pid < 0 )
+                            break;
+                        if ( j < catching_children ) {
+                            if ( i >= j )
+                                i--;
+                            for ( j++; j < catching_children; j++ )
+                                pids[j-1] = pids[j];
+                            catching_children--;
+                        }
+                    }
+                }
+            }
+        }
+
+        /*  Real file, not stdin or stdout.  If name ends in ".Z",
+         *  pipe from/to un/compress (depending on r/w mode).
+         *  
+         *  If it starts with "|", popen that command.
+         */
+	    
+        cp = file_name + strlen( (char*) file_name ) - 2;
+        /* Pipe case. */
+        if ( *file_name == '|' )
+        {
+            int thepid;		/* PID from my_popen */
+            if ( (fp = my_popen( file_name + 1, mode, &thepid )) == NULL )
+            {
+                err_str = "%s: can't invoke <<%s>> for %s: ";
+                goto err;
+            }
+            /* One more child to catch, eventually. */
+            if (catching_children < MAX_CHILDREN) {
+                pids[catching_children++] = thepid;
+            }
+        }
+
+        /* Compress case. */
+        else if ( cp > file_name && *cp == '.' && *(cp + 1) == 'Z' )
+        {
+            int thepid;		/* PID from my_popen. */
+            combuf = (char *)malloc( 20 + strlen( file_name ) );
+            if ( combuf == NULL )
+            {
+                err_str = "%s: out of memory opening (compressed) %s for %s";
+                goto err;
+            }
+
+            if ( *mode == 'w' )
+                sprintf( combuf, "compress > %s", file_name );
+            else if ( *mode == 'a' )
+                sprintf( combuf, "compress >> %s", file_name );
+            else
+                sprintf( combuf, "compress -d < %s", file_name );
+
+            fp = my_popen( combuf, mode, &thepid );
+            free( combuf );
+
+            if ( fp == NULL )
+            {
+                err_str =
+                    "%s: can't invoke 'compress' program, "
+                    "trying to open %s for %s";
+                goto err;
+            }
+            /* One more child to catch, eventually. */
+            if (catching_children < MAX_CHILDREN) {
+                pids[catching_children++] = thepid;
+            }
+        }
+        else
+#endif /* !NO_OPEN_PIPES */
+        {
+            /* Ordinary, boring file case. */
+            /* In the original code, the code to add the "b" was
+               conditionally included only if the macro STDIO_NEEDS_BINARY was
+               defined.  But for Netpbm, there is no need make a distinction;
+               we always add the "b".  -BJH 2000.07.20.  
+            */
+            char mode_string[32];	/* Should be enough. */
+
+            /* Concatenate a 'b' onto the mode. */
+            mode_string[0] = mode[0];
+            mode_string[1] = 'b';
+            strcpy( mode_string + 2, mode + 1 );
+        
+            if ( (fp = fopen(file_name, mode_string)) == NULL )
+            {
+                err_str = "%s: can't open %s for %s: ";
+                goto err;
+            }
+        }
+    }
+
+    return fp;
+
+err:
+	fprintf( stderr, err_str,
+             prog_name, file_name,
+             (*mode == 'w') ? "output" :
+             (*mode == 'a') ? "append" :
+             "input" );
+	perror( "" );
+	return NULL;
+
+}
+
+FILE *
+rle_open_f(const char * prog_name, const char * file_name, const char * mode)
+{
+    FILE *fp;
+
+    if ( (fp = rle_open_f_noexit( prog_name, file_name, mode )) == NULL )
+	exit( -1 );
+
+    return fp;
+}
+
+
+/*****************************************************************
+ * TAG( rle_close_f )
+ * 
+ * Close a file opened by rle_open_f.  If the file is stdin or stdout,
+ * it will not be closed.
+ * Inputs:
+ * 	fd:	File to close.
+ * Outputs:
+ * 	None.
+ * Assumptions:
+ * 	fd is open.
+ * Algorithm:
+ * 	If fd is NULL, just return.
+ * 	If fd is stdin or stdout, don't close it.  Otherwise, call fclose.
+ */
+void
+rle_close_f( fd )
+FILE *fd;
+{
+    if ( fd == NULL || fd == stdin || fd == stdout )
+	return;
+    else
+	fclose( fd );
+}
diff --git a/urt/rle_put.h b/urt/rle_put.h
new file mode 100644
index 00000000..d611b438
--- /dev/null
+++ b/urt/rle_put.h
@@ -0,0 +1,104 @@
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ */
+/* 
+ * rle_put.h - Definitions and a few global variables for rle_putrow/putraw.
+ * 
+ * Author:	Spencer W. Thomas
+ * 		Computer Science Dept.
+ * 		University of Utah
+ * Date:	Mon Aug  9 1982
+ * Copyright (c) 1982 Spencer W. Thomas
+ * 
+ * $Id: rle_put.h,v 3.0.1.2 1992/02/27 21:14:35 spencer Exp $
+ */
+
+#include "rle.h"
+
+/* ****************************************************************
+ * Dispatch table for different output types.
+ */
+#ifdef __cplusplus        /* Cfront 2.0  or g++ */
+#ifndef c_plusplus
+#define c_plusplus        
+#endif
+extern "C" {
+#endif
+
+
+#ifdef c_plusplus
+#define ARB_ARGS ...
+#else
+#define ARB_ARGS
+#endif
+
+typedef int rle_fn( ARB_ARGS );
+
+struct rle_dispatch_tab {
+    CONST_DECL char   *magic;   /* magic type flags */
+    void (*setup)(rle_hdr * the_hdr);          /* startup function */
+    void (*skipBlankLines)(int nblank, rle_hdr * the_hdr);
+    void(*setColor)(int c, rle_hdr * the_hdr);
+    void(*skipPixels)(int nskip, int last, int wasrun, rle_hdr * the_hdr);
+    void(*newScanLine)(int flag, rle_hdr * the_hdr);
+    void(*putdat)(rle_pixel * buf, int n, rle_hdr * the_hdr);
+        /* put a set of differing pixels */
+    void(*putrn)(int color, int n, int last, rle_hdr * the_hdr);
+        /* put a run all the same */
+    void (*blockHook)(rle_hdr * the_hdr);
+        /* hook called at start of new output block */
+    void(*putEof)(rle_hdr * the_hdr);     /* write EOF marker (if possible) */
+};
+
+extern struct rle_dispatch_tab rle_DTable[];
+
+/* 
+ * These definitions presume the existence of a variable called
+ * "fileptr", declared "long * fileptr".  *fileptr should be
+ * initialized to 0 before calling Setup().
+ * A pointer "the_hdr" declared "rle_hdr * the_hdr" is also
+ * presumed to exist.
+ */
+#define	    rle_magic		(rle_DTable[(int)the_hdr->dispatch].magic)
+#define	    Setup()		(*rle_DTable[(int)the_hdr->dispatch].setup)(the_hdr)
+#define	    SkipBlankLines(n)	(*rle_DTable[(int)the_hdr->dispatch].skipBlankLines)(n, the_hdr)
+#define	    SetColor(c)		(*rle_DTable[(int)the_hdr->dispatch].setColor)(c, the_hdr)
+#define	    SkipPixels(n, l, r)	(*rle_DTable[(int)the_hdr->dispatch].skipPixels)(n,l,r, the_hdr)
+#define	    NewScanLine(flag)	(*rle_DTable[(int)the_hdr->dispatch].newScanLine)(flag, the_hdr)
+#define	    putdata(buf, len)	(*rle_DTable[(int)the_hdr->dispatch].putdat)(buf, len, the_hdr)
+#define	    putrun(val, len, f)	(*rle_DTable[(int)the_hdr->dispatch].putrn)(val,len,f, the_hdr)
+#define	    BlockHook()		(*rle_DTable[(int)the_hdr->dispatch].blockHook)(the_hdr)
+#define	    PutEof()		(*rle_DTable[(int)the_hdr->dispatch].putEof)(the_hdr)
+
+void
+DefaultBlockHook(rle_hdr * the_hdr);
+/* 
+ * States for run detection
+ */
+#define	DATA	0
+#define	RUN1	1
+#define RUN2	2
+#define	RUN3	3
+#define RUN4	4
+#define RUN5	5
+#define RUN6	6
+#define RUN7	7
+#define	INRUN	-1
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/urt/rle_putcom.c b/urt/rle_putcom.c
new file mode 100644
index 00000000..b1215661
--- /dev/null
+++ b/urt/rle_putcom.c
@@ -0,0 +1,169 @@
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ */
+/* 
+ * rle_putcom.c - Add a picture comment to the header struct.
+ * 
+ * Author:  Spencer W. Thomas
+ *      Computer Science Dept.
+ *      University of Utah
+ * Date:    Mon Feb  2 1987
+ * Copyright (c) 1987, University of Utah
+ */
+
+#include <stdio.h>
+
+#include "mallocvar.h"
+#include "pm.h"
+#include "rle.h"
+
+/*****************************************************************
+ * TAG( match )
+ * 
+ * Match a name against a test string for "name=value" or "name".
+ * If it matches name=value, return pointer to value part, if just
+ * name, return pointer to NUL at end of string.  If no match, return NULL.
+ *
+ * Inputs:
+ *  n:  Name to match.  May also be "name=value" to make it easier
+ *      to replace comments.
+ *  v:  Test string.
+ * Outputs:
+ *  Returns pointer as above.
+ * Assumptions:
+ *  [None]
+ * Algorithm:
+ *  [None]
+ */
+static const char *
+match(const char * const nArg,
+      const char * const vArg) {
+
+    const char * n;
+    const char * v;
+
+    for (n = nArg, v = vArg; *n != '\0' && *n != '=' && *n == *v; ++n, ++v)
+        ;
+    if (*n == '\0' || *n == '=') {
+        if (*v == '\0')
+            return v;
+        else if (*v == '=')
+            return ++v;
+    }
+
+    return NULL;
+}
+
+
+
+/*****************************************************************
+ * TAG( rle_putcom )
+ * 
+ * Put a comment into the header struct.
+ * Inputs:
+ *  value:      Value to add to comments.
+ *  the_hdr:    Header struct to add to.
+ * Outputs:
+ *  the_hdr:    Modified header struct.
+ *  Returns previous value;
+ * Assumptions:
+ *  value pointer can be used as is (data is NOT copied).
+ * Algorithm:
+ *  Find match if any, else add at end (realloc to make bigger).
+ */
+const char *
+rle_putcom(const char * const value,
+           rle_hdr *    const the_hdr) {
+
+    if ( the_hdr->comments == NULL) {
+        MALLOCARRAY_NOFAIL(the_hdr->comments, 2);
+        the_hdr->comments[0] = value;
+        the_hdr->comments[1] = NULL;
+    } else {
+        const char ** cp;
+        const char * v;
+        const char ** old_comments;
+        int i;
+        for (i = 2, cp = the_hdr->comments; *cp != NULL; ++i, ++cp)
+            if (match(value, *cp) != NULL) {
+                v = *cp;
+                *cp = value;
+                return v;
+            }
+        /* Not found */
+        /* Can't realloc because somebody else might be pointing to this
+         * comments block.  Of course, if this were true, then the
+         * assignment above would change the comments for two headers.
+         * But at least, that won't crash the program.  Realloc will.
+         * This would work a lot better in C++, where hdr1 = hdr2
+         * could copy the pointers, too.
+         */
+        old_comments = the_hdr->comments;
+        MALLOCARRAY(the_hdr->comments, i);
+        if (the_hdr->comments == NULL)
+            pm_error("Unable to allocate memory for comments");
+        the_hdr->comments[--i] = NULL;
+        the_hdr->comments[--i] = value;
+        for (--i; i >= 0; --i)
+            the_hdr->comments[i] = old_comments[i];
+    }
+
+    return NULL;
+}
+
+
+
+/*****************************************************************
+ * TAG( rle_delcom )
+ * 
+ * Delete a comment from header struct.
+ * Inputs:
+ *  name:       Name of comment to delete.
+ *  the_hdr:    Header to delete comment from.
+ * Outputs:
+ *  the_hdr:    Modified header struct.
+ *  Returns original comment value.
+ * Assumptions:
+ *  [None]
+ * Algorithm:
+ *  [None]
+ */
+const char *
+rle_delcom(const char * const name,
+           rle_hdr *    const the_hdr) {
+
+    const char * v = NULL;
+
+    if (the_hdr->comments == NULL)
+        v = NULL;
+    else {
+        const char ** cp;
+
+        for (cp = the_hdr->comments; *cp != NULL; ++cp)
+            if (match(name, *cp) != NULL) {
+                v = *cp;
+                for ( ; *cp != NULL; ++cp)
+                    *cp = cp[1];
+                break;
+            }
+        /* Not found */
+        if (*the_hdr->comments == NULL)
+            the_hdr->comments = NULL;
+    }
+
+    return v;
+}
diff --git a/urt/rle_putrow.c b/urt/rle_putrow.c
new file mode 100644
index 00000000..230720f8
--- /dev/null
+++ b/urt/rle_putrow.c
@@ -0,0 +1,728 @@
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ *
+ *  Modified at BRL 16-May-88 by Mike Muuss to avoid Alliant STDC desire
+ *  to have all "void" functions so declared.
+ */
+/* 
+ * rle_putrow.c - Save a row of the fb to a file.
+ * 
+ * Author:	Spencer W. Thomas
+ * 		Computer Science Dept.
+ * 		University of Utah
+ * Date:	1 April 1981
+ * Copyright (c) 1981,1986 Spencer W. Thomas
+ *
+ * $Id: rle_putrow.c,v 3.0.1.2 1992/01/28 18:29:22 spencer Exp $
+ */
+ 
+#include <stdio.h>
+#include "rle_put.h"
+#include "rle.h"
+
+
+#define FASTRUNS		/* Faster run finding */
+#ifdef vax
+#define LOCC			/* Use vax instructions for more speed */
+#endif
+
+#define	FALSE	0
+#define	TRUE	1
+
+/* Save some typing. */
+#define PBRUN the_hdr->priv.put.brun
+
+/*****************************************************************
+ * TAG( findruns )
+ * 
+ * Find runs not a given color in the row.
+ * Inputs:
+ * 	row:		Row of pixel values
+ *	rowlen:		Number of pixels in the row.
+ *	color:		Color to compare against.
+ *	nrun:		Number of runs already found (in different colors).
+ *	brun:		Runs found in other color channels already.
+ * Outputs:
+ * 	brun:		Modified to reflect merging of runs in this color.
+ *	Returns number of runs in brun.
+ * Assumptions:
+ *
+ * Algorithm:
+ * 	Search for occurences of pixels not of the given color outside
+ *	the runs already found.  When some are found, add a new run or
+ *	extend an existing one.  Adjacent runs with fewer than two
+ *	pixels intervening are merged.
+ */
+static int
+findruns(rle_pixel * const row, 
+         int         const rowlen, 
+         int         const color, 
+         int         const nrunAlready,
+         short    (* const brun)[2]) {
+
+    int i = 0, lower, upper;
+    int s, j;
+    int nrun;
+
+#ifdef DEBUG
+    fprintf( stderr, "findruns( " );
+    for ( s = 0; s < rowlen; s++ )
+        fprintf( stderr, "%2x.%s", row[s], (s % 20 == 19) ? "\n\t" : "" );
+    if ( s % 20 != 0 )
+        fprintf( stderr, "\n\t" );
+    fprintf( stderr, "%d, %d, %d, \n\t", rowlen, color, nrun );
+    for ( j = 0; j < nrun; j++ )
+        fprintf( stderr, "(%3d,%3d) %s", brun[j][0], brun[j][1],
+                 (j % 6 == 5) ? "\n\t" : "" );
+    fprintf( stderr, ")\n" );
+#endif
+
+    nrun = nrunAlready;
+
+    while ( i <= nrun )
+    {
+        /* Assert: 0 <= i <= rowlen
+         * brun[i] is the run following the "blank" space being
+         * searched.  If i == rowlen, search after brun[i-1].
+	 */
+
+	/* get lower and upper bounds of search */
+
+        if ( i == 0 )
+            lower = 0;
+        else
+            lower = brun[i-1][1] + 1;
+
+        if ( i == nrun )
+            upper = rowlen - 1;
+        else
+            upper = brun[i][0] - 1;
+
+#ifdef DEBUG
+        fprintf( stderr, "Searching before run %d from %d to %d\n",
+                 i, lower, upper );
+#endif
+        /* Search for beginning of run != color */
+#if  defined(LOCC)&defined(vax)
+        s = upper - skpc( (char *)row + lower, upper - lower + 1, color ) + 1;
+#else
+        for ( s = lower; s <= upper; s++ )
+            if ( row[s] != color )
+                break;
+#endif
+
+        if ( s <= upper )	/* found a new run? */
+        {
+            if ( s > lower + 1 || i == 0 ) /* disjoint from preceding run? */
+            {
+#ifdef DEBUG
+                fprintf( stderr, "Found new run starting at %d\n", s );
+#endif
+                /* Shift following runs up */
+                for ( j = nrun; j > i; j-- )
+                {
+                    brun[j][0] = brun[j-1][0];
+                    brun[j][1] = brun[j-1][1];
+                }
+                brun[i][0] = s;
+                nrun++;
+            }
+            else
+            {
+                i--;		/* just add to preceding run */
+#ifdef DEBUG
+                fprintf( stderr, "Adding to previous run\n" );
+#endif
+            }
+
+#if defined(LOCC)&defined(vax)
+            s = upper - locc( (char *)row + s, upper - s + 1, color ) + 1;
+#else
+            for ( ; s <= upper; s++ )
+                if ( row[s] == color )
+                    break;
+#endif
+            brun[i][1] = s - 1;
+
+#ifdef DEBUG
+            fprintf( stderr, "Ends at %d", s - 1 );
+#endif
+            if ( s >= upper && i < nrun - 1 ) /* merge with following run */
+            {
+                brun[i][1] = brun[i+1][1];
+                /* move following runs back down */
+                for ( j = i + 2; j < nrun; j++ )
+                {
+                    brun[j-1][0] = brun[j][0];
+                    brun[j-1][1] = brun[j][1];
+                }
+                nrun--;
+#ifdef DEBUG
+                fprintf( stderr, ", add to next run" );
+#endif
+            }
+#ifdef DEBUG
+            putc( '\n', stderr );
+#endif
+        }
+	
+        /* Search in next space */
+        i++;
+    }
+
+    return nrun;
+}
+
+
+
+/*****************************************************************
+ * TAG( rle_putrow )
+ * Write a scanline to the output file.
+ * 
+ * Inputs:
+ *	rows:		Pointer to vector of pointers to
+ *			rle_pixel arrays containing the pixel information.
+ *			If NULL, rowlen scanlines are skipped.
+ *	rowlen:		The number of pixels in the scanline, or the
+ *			number of scanlines to skip (see above).
+ * Outputs:
+ * 	Run length encoded information is written to the_hdr.rle_file.
+ * Assumptions:
+ * 	I'm sure there are lots of assumptions in here.
+ * Algorithm:
+ * 	There are two parts:
+ * 		1. Find all "sufficiently long" runs of background
+ * 		   color.  These will not be saved at all.
+ * 		2. For each run of non-background, for each color
+ * 		   channel, find runs of identical pixel values
+ * 		   between "data" segments (of differing pixel
+ * 		   values).
+ * 	For part 1, "sufficiently long" is 2 pixels, if the following
+ * 	data is less than 256 pixels long, otherwise it is 4 pixels.
+ * 	This is enforced by a post-process merge.
+ *
+ * 	Part 1 can be done in two different ways, depending on whether
+ * 	FASTRUNS is defined or not.  With FASTRUNS defined, it finds
+ * 	runs of the background pixel value in each channel
+ * 	independently, and then merges the results.  With FASTRUNS not
+ * 	defined, it scans all channels in parallel.
+ *
+ * 	Part 2 uses a state machine.  For each run of non-background
+ * 	data, it searches for sufficiently long sequences of a single
+ * 	value (in each channel independently).  Sufficiently long is 4
+ * 	pixels if the following data is < 256 pixels, 6 pixels
+ * 	otherwise.  This is because the startup cost for the run is 2
+ * 	bytes, and the startup cost for a data segment is 2 bytes if
+ * 	it is < 256 pixels long, 4 bytes otherwise.  Thus a run
+ * 	shorter than 4 or 6 pixels (respectively) would actually make
+ * 	the output longer.  An additional pixel is required if the
+ * 	preceding data is an odd number of pixels long (because a
+ * 	filler byte will be output at the end of it.)
+ */
+
+void
+rle_putrow(rows, rowlen, the_hdr)
+register rle_pixel *rows[];
+int rowlen;
+register rle_hdr * the_hdr;
+{
+    register int i, j;
+    int nrun;
+    register rle_pixel *row;
+    int mask;
+    char bits[256];
+    short   state,		/* State of run-finding state machine. */
+	    dstart,		/* Starting point for current data segment. */
+    	    dend,		/* Ending point of current data segment. */
+	    rstart = 0,		/* Starting point of current run. */
+	    runval = 0;		/* Data value for current run. */
+
+    if (rows == NULL)
+    {
+	the_hdr->priv.put.nblank += rowlen;
+	return;
+    }
+    /* 
+     * If not done already, allocate space to remember runs of
+     * non-background color.  A run of bg color must be at least 2
+     * bytes long to count, so there can be at most rowlen/3 of them.
+     */
+    if ( PBRUN == NULL )
+    {
+	PBRUN = (short (*)[2])malloc(
+	    (unsigned)((rowlen/3 + 1) * 2 * sizeof(short)) );
+	if ( PBRUN == NULL )
+	{
+	    fprintf( stderr, "%s: Malloc failed in rle_putrow, writing %s\n",
+		     the_hdr->cmd, the_hdr->file_name );
+	    exit(1);
+	}
+    }
+    /* Unpack bitmask in the_hdr struct */
+    for ( i=0; i < the_hdr->ncolors; i++ )
+	bits[i] = RLE_BIT( *the_hdr, i );
+    bits[255] = RLE_BIT( *the_hdr, -1 );
+
+    /* 
+     * If saving only non-background pixels, find runs of them.  Note
+     * that the alpha channel is considered to be background iff it is
+     * zero.
+     */
+#ifdef	FASTRUNS
+    if ( the_hdr->background )
+    {
+	/* 
+	 * Find runs in each color individually, merging them as we go.
+	 */
+	nrun = 0;		/* start out with no runs */
+	/* Alpha channel first */
+	if ( the_hdr->alpha )
+	    nrun = findruns( rows[-1], rowlen, 0, nrun, PBRUN );
+	/* Now the color channels */
+	for ( i = 0; i < the_hdr->ncolors; i++ )
+	    if ( bits[i] )
+		nrun = findruns( rows[i], rowlen, the_hdr->bg_color[i],
+				 nrun, PBRUN );
+    }
+    else
+    {
+	PBRUN[0][0] = 0;
+	PBRUN[0][1] = rowlen-1;
+	nrun = 1;
+    }
+#else				/* FASTRUNS */
+    if (the_hdr->background)	/* find non-background runs */
+    {
+	j = 0;
+	for (i=0; i<rowlen; i++)
+	    if (!same_color( i, rows, the_hdr->bg_color,
+			     the_hdr->ncolors, bits ) ||
+		(the_hdr->alpha && rows[-1][i] != 0))
+	    {
+		if (j > 0 && i - PBRUN[j-1][1] <= 2)
+		    j--;
+		else
+		    PBRUN[j][0] = i; /* start of run */
+		for ( i++;
+		      i < rowlen && 
+			( !same_color( i, rows, the_hdr->bg_color,
+					 the_hdr->ncolors, bits ) ||
+			  (the_hdr->alpha && rows[-1][i] != 0) );
+		      i++)
+		    ;			/* find the end of this run */
+		PBRUN[j][1] = i-1;    /* last in run */
+		j++;
+	    }
+	nrun = j;
+    }
+    else
+    {
+	PBRUN[0][0] = 0;
+	PBRUN[0][1] = rowlen-1;
+	nrun = 1;
+    }
+#endif				/* FASTRUNS */
+    /* One final pass merges runs with fewer than 4 intervening pixels
+     * if the second run is longer than 255 pixels.  This is because
+     * the startup cost for such a run is 4 bytes.
+     */
+    if ( nrun > 1 )
+    {
+	for ( i = nrun - 1; i > 0; i-- )
+	{
+	    if ( PBRUN[i][1] - PBRUN[i][0] > 255 &&
+		 PBRUN[i-1][1] + 4 > PBRUN[i][0] )
+	    {
+		PBRUN[i-1][1] = PBRUN[i][1];
+		for ( j = i; j < nrun - 1; j++ )
+		{
+		    PBRUN[j][0] = PBRUN[j+1][0];
+		    PBRUN[j][1] = PBRUN[j+1][1];
+		}
+		nrun--;
+	    }
+	}
+    }
+
+    if (nrun > 0)
+    {
+	if (the_hdr->priv.put.nblank > 0)
+	{
+	    SkipBlankLines(the_hdr->priv.put.nblank);
+	    the_hdr->priv.put.nblank = 0;
+	}
+	for ( mask = (the_hdr->alpha ? -1 : 0);
+	      mask < the_hdr->ncolors;
+	      mask++)			/* do all colors */
+	{
+	    if ( ! bits[mask & 0xff] )
+	    {
+		continue;
+	    }
+	    row = rows[mask];
+	    SetColor(mask);
+	    if (PBRUN[0][0] > 0)
+	    {
+		SkipPixels(PBRUN[0][0], FALSE, FALSE);
+	    }
+	    for (j=0; j<nrun; j++)
+	    {
+		state = DATA;
+		dstart = PBRUN[j][0];
+		dend = PBRUN[j][1];
+		for (i=dstart; i<=dend; i++)
+		{
+		    switch(state)
+		    {
+		    case DATA:
+			if (i > dstart && runval == row[i])
+			{
+			    /* 2 in a row may be a run. */
+			    /* If odd data length, start with RUN1 */
+			    if ( ((i - dstart) % 2) == 0)
+				state = RUN1;
+			    else
+				state = RUN2;
+			}
+			else
+			{
+			    runval = row[i];	/* maybe a run starts here? */
+			    rstart = i;
+			}
+			break;
+	    
+		    case RUN4:
+			if (runval == row[i])
+			{
+			    /* If the following data might be longer
+			     * than 255 pixels then look for 8 in a
+			     * row, otherwise, 6 in a row is
+			     * sufficient.  Fake this by skipping to
+			     * state RUN5.
+			     */
+			    if ( dend - i > 255 )
+				state  = RUN5;	/* Need some more. */
+			    else
+				state = RUN7;	/* Next one makes a run. */
+			    
+			}
+			else
+			{
+			    state = DATA;	/* Nope, back to data */
+			    runval = row[i];	/* but maybe a new run here? */
+			    rstart = i;
+			}
+			break;
+
+		    case RUN1:
+		    case RUN2:
+		    case RUN3:
+		    case RUN5:
+		    case RUN6:
+			if (runval == row[i])
+			{
+			    /* Move to the next state. */
+			    state++;
+			}
+			else
+			{
+			    state = DATA;	/* Nope, back to data */
+			    runval = row[i];	/* but maybe a new run here? */
+			    rstart = i;
+			}
+			break;
+
+
+		    case RUN7:
+			if (runval == row[i])	/* enough in a row for a run */
+			{
+			    state = INRUN;
+			    putdata(row + dstart, rstart - dstart);
+#ifdef FASTRUNS
+#ifdef LOCC
+			    /* Shortcut to find end of run! */
+			    i = dend - skpc( (char *)row + i, dend + 1 - i,
+					     runval );
+#else
+			    while ( row[++i] == runval && i <= dend)
+				; /* not quite so good, but not bad */
+			    i--;
+#endif /* LOCC */
+#endif /* FASTRUNS */
+			}
+			else
+			{
+			    state = DATA;		/* not a run, */
+			    runval = row[i];	/* but may this starts one */
+			    rstart = i;
+			}
+			break;
+	    
+		    case INRUN:
+			if (runval != row[i])	/* if run out */
+			{
+			    state = DATA;
+			    putrun(runval, i - rstart, FALSE);
+			    runval = row[i];	/* who knows, might be more */
+			    rstart = i;
+			    dstart = i;	/* starting a new 'data' run */
+			}
+			break;
+		    }
+		}
+		if (state == INRUN)
+		    putrun(runval, i - rstart, TRUE);	/* last bit */
+		else
+		    putdata(row + dstart, i - dstart);
+
+		if (j < nrun-1)
+		    SkipPixels(
+			    PBRUN[j+1][0] - dend - 1,
+			    FALSE, state == INRUN);
+		else
+		{
+		    if (rowlen - dend > 0)
+			SkipPixels(
+			    rowlen - dend - 1,
+			    TRUE, state == INRUN);
+		}
+	    }
+
+	    if ( mask != the_hdr->ncolors - 1 )
+		NewScanLine(FALSE);
+	}
+    }
+
+    /* Increment to next scanline */
+    the_hdr->priv.put.nblank++;
+
+    /* flush every scanline */
+    fflush( the_hdr->rle_file );
+}
+
+
+/*****************************************************************
+ * TAG( rle_skiprow )
+ * 
+ * Skip rows in RLE file.
+ * Inputs:
+ * 	the_hdr:    	Header struct for RLE output file.
+ *  	nrow:	    	Number of rows to skip.
+ * Outputs:
+ * 	Increments the nblank field in the the_hdr struct, so that a Skiplines
+ *  	code will be output the next time rle_putrow or rle_putraw is called.
+ * Assumptions:
+ * 	Only effective when called between rle_putrow or rle_putraw calls (or
+ *  	some other routine that follows the same conventions.
+ * Algorithm:
+ *	[None]
+ */
+void
+rle_skiprow( the_hdr, nrow )
+rle_hdr *the_hdr;
+int nrow;
+{
+    the_hdr->priv.put.nblank += nrow;
+}
+
+
+/*****************************************************************
+ * TAG( rle_put_init )
+ * 
+ * Initialize the header structure for writing scanlines. 
+ * Inputs:
+ *	[None]
+ * Outputs:
+ * 	the_hdr:	Private portions initialized for output.
+ * Assumptions:
+ *	[None]
+ * Algorithm:
+ *	[None]
+ */
+void
+rle_put_init( the_hdr )
+register rle_hdr *the_hdr;
+{
+    the_hdr->dispatch = RUN_DISPATCH;
+
+    if ( the_hdr->is_init != RLE_INIT_MAGIC )
+    {
+	the_hdr->cmd = "Urt";
+	the_hdr->file_name = "some file";
+    }
+    the_hdr->priv.put.nblank = 0;	/* Reinit static vars */
+    /* Would like to be able to free previously allocated storage,
+     * but can't count on a non-NULL value being a valid pointer.
+     */
+    PBRUN = NULL;
+    the_hdr->priv.put.fileptr = 0;
+
+    /* Only save alpha if alpha AND alpha channel bit are set. */
+    if ( the_hdr->alpha )
+	the_hdr->alpha = (RLE_BIT( *the_hdr, -1 ) != 0);
+    else
+	RLE_CLR_BIT( *the_hdr, -1 );
+}
+
+/*****************************************************************
+ * TAG( rle_put_setup )
+ * 
+ * Initialize for writing RLE, and write header to output file.
+ * Inputs:
+ * 	the_hdr:	Describes output image.
+ * Outputs:
+ * 	the_hdr:	Initialized.
+ * Assumptions:
+ *	Lots of them.
+ * Algorithm:
+ *	[None]
+ */
+void
+rle_put_setup( the_hdr )
+register rle_hdr * the_hdr;
+{
+    rle_put_init( the_hdr );
+    the_hdr->img_num++;		/* Count output images. */
+    Setup();
+}
+
+void
+DefaultBlockHook(rle_hdr * the_hdr)
+{
+    					/* Do nothing */
+}
+
+/*****************************************************************
+ * TAG( rle_puteof )
+ * Write an EOF code into the output file.
+ */
+void
+rle_puteof( the_hdr )
+register rle_hdr * the_hdr;
+{
+    /* Don't puteof twice. */
+    if ( the_hdr->dispatch == NO_DISPATCH )
+	return;
+    PutEof();
+    fflush( the_hdr->rle_file );
+    /* Free storage allocated by rle_put_init. */
+    if ( PBRUN != NULL )
+    {
+	free( PBRUN );
+	PBRUN = NULL;
+    }
+    /* Signal that puteof has been called. */
+    the_hdr->dispatch = NO_DISPATCH;
+}
+
+#ifndef FASTRUNS
+/*****************************************************************
+ * TAG( same_color )
+ * 
+ * Determine if the color at the given index position in the scan rows
+ * is the same as the background color.
+ * Inputs:
+ * 	index:	    Index to the pixel position in each row.
+ *	rows:	    array of pointers to the scanlines
+ *	bg_color:   the background color
+ *	ncolors:    number of color elements/pixel
+ * Outputs:
+ * 	TRUE if the color at row[*][i] is the same as bg_color[*].
+ * Assumptions:
+ *	[None]
+ * Algorithm:
+ *	[None]
+ */
+static int
+same_color( index, rows, bg_color, ncolors, bits )
+register rle_pixel *rows[];
+register int bg_color[];
+char *bits;
+{
+    register int i;
+
+    for ( i = 0; i < ncolors; i++, bits++ )
+	if ( *bits &&
+	     rows[i][index] != bg_color[i] )
+	    return 0;
+    return 1;				/* all the same */
+}
+#endif /* !FASTRUNS */
+
+/*****************************************************************
+ * TAG( rgb_to_bw )
+ * 
+ * Perform the NTSC Y transform on RGB data to get B&W data.
+ * Inputs:
+ * 	red_row, green_row, blue_row:	Given RGB pixel data.
+ *	rowlen:	    Number of pixels in the rows.
+ * Outputs:
+ * 	bw_row:	    Output B&W data.  May coincide with one of the
+ *		    inputs.
+ * Assumptions:
+ *	[None]
+ * Algorithm:
+ * 	BW = .30*R + .59*G + .11*B
+ */
+void
+rgb_to_bw( red_row, green_row, blue_row, bw_row, rowlen )
+rle_pixel *red_row;
+rle_pixel *green_row;
+rle_pixel *blue_row;
+rle_pixel *bw_row;
+int rowlen;
+{
+    register int x, bw;
+
+    for (x=0; x<rowlen; x++)
+    {
+	/* 68000 won't store float > 127 into byte? */
+	/* HP compiler blows it */
+	bw = 0.5 + .30*red_row[x] + .59*green_row[x] + .11*blue_row[x];
+	bw_row[x] = bw;
+    }
+}
+
+#ifdef LOCC
+/*ARGSUSED*/
+locc( p, l, c )
+register char *p;
+register int l;
+register int c;
+{
+    asm( "locc	r9,r10,(r11)" );
+#ifdef lint
+    c = (int) p;		/* why doesn't ARGSUSED work? */
+    l = c;
+    return l;			/* Needs return value, at least */
+#endif
+}
+
+/*ARGSUSED*/
+skpc( p, l, c )
+register char *p;
+register int l;
+register int c;
+{
+    asm( "skpc r9,r10,(r11)" );
+#ifdef lint
+    c = (int) p;		/* why doesn't ARGSUSED work? */
+    l = c;
+    return l;			/* Needs return value, at least */
+#endif
+}
+#endif
diff --git a/urt/rle_row_alc.c b/urt/rle_row_alc.c
new file mode 100644
index 00000000..0f29523e
--- /dev/null
+++ b/urt/rle_row_alc.c
@@ -0,0 +1,129 @@
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ *
+ *  Modified at BRL 16-May-88 by Mike Muuss to avoid Alliant STDC desire
+ *  to have all "void" functions so declared.
+ */
+/* 
+ * rle_row_alc.c - Allocate buffers for rle_getrow/rle_putrow.
+ * 
+ * Author:	Spencer W. Thomas
+ * 		Computer Science Dept.
+ * 		University of Utah
+ * Date:	Fri Nov 14 1986
+ * Copyright (c) 1986, Spencer W. Thomas
+ */
+
+#include <stdio.h>
+#include "rle.h"
+
+/*****************************************************************
+ * TAG( rle_row_alloc )
+ * 
+ * Allocate buffer space for use by rle_getrow and rle_putrow.
+ * Inputs:
+ * 	the_hdr:	Header structure for RLE file to be read or
+ *			written.
+ * Outputs:
+ *	scanp:		Pointer to pointer to created scanline buffer.
+ *			This pointer is adjusted for the alpha channel,
+ *			if present.
+ *	Returns 0 for success, -1 if malloc failed.
+ * Assumptions:
+ * 	No input scanline will extend beyond the declared xmax endpoint.
+ * Algorithm:
+ *	Count number of channels actually used (check bitmap).
+ * 	Allocate nchan*rowlength pixels, allocate a buffer
+ *	to hold ncolors+alpha pointers, and give each channel
+ *	rowlength pixels.  Rowlength is xmax + 1, to allow for rle_getrow
+ *	usage.
+ */
+int
+rle_row_alloc( the_hdr, scanp )
+rle_hdr *the_hdr;
+rle_pixel ***scanp;
+{
+    rle_pixel ** scanbuf, * pixbuf;
+    int rowlen, nchan = 0, i, ncol;
+
+    rowlen = the_hdr->xmax + 1;
+    if ( the_hdr->alpha && RLE_BIT( *the_hdr, RLE_ALPHA ) )
+	nchan++;
+    for ( i = 0; i < the_hdr->ncolors; i++ )
+	if ( RLE_BIT( *the_hdr, i ) )
+	     nchan++;
+
+    ncol = the_hdr->ncolors + the_hdr->alpha;
+
+    if ( (scanbuf = (rle_pixel **)malloc( ncol * sizeof(rle_pixel *) )) == 0 )
+	return -1;
+    if ( (pixbuf = (rle_pixel *)malloc( nchan * rowlen *
+				       sizeof(rle_pixel) )) == 0 )
+    {
+	free( scanbuf );
+	return -1;
+    }
+
+    if ( the_hdr->alpha )
+	scanbuf++;
+
+    for ( i = -the_hdr->alpha; i < the_hdr->ncolors; i++ )
+	if ( RLE_BIT( *the_hdr, i ) )
+	{
+	    scanbuf[i] = pixbuf;
+	    pixbuf += rowlen;
+	}
+	else
+	    scanbuf[i] = 0;
+    *scanp = scanbuf;
+
+    return 0;
+}
+
+
+/*****************************************************************
+ * TAG( rle_row_free )
+ * 
+ * Free storage allocated by rle_row_alloc().
+ * Inputs:
+ * 	the_hdr:	Header structure as above.
+ *	scanp:		Pointer to scanbuf above.
+ * Outputs:
+ * 	Frees storage referenced by scanp and nrawp.
+ * Assumptions:
+ * 	Storage was allocated by rle_row_alloc, or by use of same
+ *	algorithm, at least.
+ * Algorithm:
+ * 	free scanp[0] and scanp.
+ */
+void
+rle_row_free( the_hdr, scanp )
+rle_hdr *the_hdr;
+rle_pixel **scanp;
+{
+    int i;
+
+    if ( the_hdr->alpha )
+	scanp--;
+    for ( i = 0; i < the_hdr->ncolors + the_hdr->alpha; i++ )
+	if ( scanp[i] != 0 )
+	{
+	    free( (char *)scanp[i] );
+	    break;
+	}
+    free( (char *)scanp );
+}
diff --git a/urt/scanargs.c b/urt/scanargs.c
new file mode 100644
index 00000000..416f2380
--- /dev/null
+++ b/urt/scanargs.c
@@ -0,0 +1,948 @@
+/* 
+ * $Id: scanargs.c,v 3.0.1.3 1992/02/27 21:18:14 spencer Exp $
+ * 		Version 7 compatible
+ * 	Argument scanner, scans argv style argument list.
+ * 
+ * 	Some stuff is a kludge because sscanf screws up
+ * 
+ * 	Gary Newman - 10/4/1979 - Ampex Corp. 
+ * 
+ * 	Modified by Spencer W. Thomas, Univ. of Utah, 5/81 to
+ * 	add args introduced by 	a flag, add qscanargs call,
+ * 	allow empty flags.
+ * 
+ * 	If you make improvements we'd like to get them too.
+ * 	Jay Lepreau	lepreau@utah-20, decvax!harpo!utah-cs!lepreau
+ * 	Spencer Thomas	thomas@utah-20, decvax!harpo!utah-cs!thomas 
+ * 
+ *	(I know the code is ugly, but it just grew, you see ...)
+ * 
+ * Modified by:	Spencer W. Thomas
+ * 	Date:	Feb 25 1983
+ * 1. Fixed scanning of optional args.  Now args introduced by a flag
+ *    must follow the flag which introduces them and precede any other
+ *    flag argument.  It is still possible for a flag introduced
+ *    argument to be mistaken for a "bare" argument which occurs
+ *    earlier in the format string.  This implies that flags may not
+ *    be conditional upon other flags, and a message will be generated
+ *    if this is attempted.
+ * 
+ * 2. Usage message can be formatted by inserting newlines, tabs and
+ *    spaces into the format string.  This is especially useful for
+ *    long argument lists.
+ * 
+ * 3. Added n/N types for "numeric" args.  These args are scanned
+ *    using the C language conventions - a number starting 0x is
+ *    hexadecimal, a number starting with 0 is octal, otherwise it is
+ *    decimal.
+ *
+ *  Modified at BRL 16-May-88 by Mike Muuss to avoid Alliant STDC desire
+ *  to have all "void" functions so declared.
+ */
+
+#include "rle.h"
+#include <stdio.h>
+#include <ctype.h>
+#ifndef USE_STDARG
+#include <varargs.h>
+#else
+#include <stdarg.h>
+#endif
+
+#include "nstring.h"
+
+typedef char bool;
+/* 
+ * An explicit assumption is made in this code that all pointers look
+ * alike, except possible char * pointers.
+ */
+typedef int *ptr;
+
+#define YES 1
+#define NO 0
+#define ERROR(msg)  {fprintf(stderr, "%s\n", msg); goto error; }
+
+/* 
+ * Storage allocation macros
+ */
+#define NEW( type, cnt )	(type *) malloc( (cnt) * sizeof( type ) )
+#define RENEW( type, ptr, cnt )	(type *) realloc( ptr, (cnt) * sizeof( type ) )
+
+#if defined(c_plusplus) && !defined(USE_PROTOTYPES)
+#define USE_PROTOTYPES
+#endif
+
+#ifndef USE_PROTOTYPES
+static char * prformat();
+static int isnum();
+static int	_do_scanargs();
+void		scan_usage();
+#else
+static CONST_DECL char * prformat( CONST_DECL char *, int );
+static int isnum( CONST_DECL char *, int, int );
+static int	_do_scanargs( int argc, char **argv, CONST_DECL char *format,
+			      va_list argl );
+void		scan_usage( char **, CONST_DECL char * );
+#endif
+
+/* 
+ * Argument list is (argc, argv, format, ... )
+ */
+int
+#ifndef USE_STDARG
+scanargs ( va_alist )
+va_dcl
+#else
+scanargs ( int argc, char **argv, CONST_DECL char *format, ... )
+#endif /* !USE_STDARG */
+{
+    va_list argl;
+    int retval;
+#ifndef USE_STDARG
+    int argc;
+    char ** argv;
+    CONST_DECL char *format;
+
+    va_start( argl );
+    argc = va_arg( argl, int );
+    argv = va_arg( argl, char ** );
+    format = va_arg( argl, CONST_DECL char * );
+#else
+    va_start( argl, format );
+#endif
+    retval = _do_scanargs( argc, argv, format, argl );
+    va_end( argl );
+    return retval;
+}
+    
+/* 
+ * This routine is necessary because of a pyramid compiler botch that
+ * uses parameter registers in a varargs routine.  The extra
+ * subroutine call isolates the args on the register stack so they
+ * don't get trashed.
+ */
+
+static int
+_do_scanargs( argc, argv, format, argl )
+int     argc;			/* Actual arguments */
+char  **argv;
+CONST_DECL char   *format;
+va_list argl;
+{
+
+    int    check;			/* check counter to be sure all argvs
+					   are processed */
+    register CONST_DECL char  *cp;
+    int    cnt;
+    int	    optarg = 0;			/* where optional args start */
+    int	    nopt = 0;
+    char    tmpflg,			/* temp flag */
+	    typchr;			/* type char from format string */
+    char    c;
+    bool  * arg_used;			/* array of flags */
+    ptr	    aptr = 0;			/* pointer to return loc */
+
+    bool    required;
+    int	    excnt;			/* which flag is set */
+    bool    exflag;			/* when set, one of a set of exclusive
+					   flags is set */
+
+    bool    list_of;			/* set if parsing off a list of args */
+    bool    comma_list;			/* set if AT&T style multiple args */
+    bool    no_usage;			/* If set, don't print usage msg. */
+    bool    help = NO;			/* If set, always print usage. */
+    int	  * cnt_arg = 0;		/* where to stuff list count */
+    int	    list_cnt;			/* how many in list */
+    /* These are used to build return lists */
+    char ** strlist = 0;
+    int   * intlist = 0;
+    long  * longlist = 0;
+    float * fltlist = 0;
+    double *dbllist = 0;
+    char  * argp;			/* Pointer to argument. */
+
+    CONST_DECL char   *ncp;		/* remember cp during flag scanning */
+    static char   cntrl[7] = "%  %1s";	/* control string for scanf's */
+    char    junk[2];			/* junk buffer for scanf's */
+
+    /* Set up for argument counting. */
+    arg_used = NEW( bool, argc );
+    if (arg_used == NULL)
+    {
+	fprintf(stderr, "malloc failed in scanargs, exiting\n");
+	exit(-1);
+    }
+    else
+    {
+	for (cnt=0; cnt<argc; cnt++)
+	    arg_used[cnt] = NO;
+    }
+    check = 0;
+
+    /* Scan for -help in arg list. */
+    for ( cnt=1; cnt<argc; cnt++ )
+	if ( strcmp( argv[cnt], "-help" ) == 0 )
+	{
+	    check += cnt;
+	    arg_used[cnt] = YES;
+	    if ( argc == 2 )
+	    {
+		scan_usage( argv, format );
+		return 0;
+	    }
+	    else
+		help = YES;
+	}
+
+    /* If format string ends in @, don't print a usage message. */
+    no_usage = *(format + strlen( format ) - 1) == '&';
+
+    cp = format;
+    /* 
+     * Skip program name
+     */
+    while ( *cp != ' ' && *cp != '\t' && *cp != '\n' && *cp != '\0' )
+	cp++;
+
+    while (*cp)
+    {
+	required = NO;			/* reset per-arg flags */
+	list_of = NO;
+	comma_list = NO;
+	list_cnt = 0;
+	switch (*(cp++))
+	{
+	    default: 			/* all other chars */
+		break;
+	    case ' ':			/* separators */
+	    case '\t':
+	    case '\n':
+		optarg = 0;		/* end of optional arg string */
+		break;
+
+	    case '(':			/* Surrounds a comment. */
+	    {
+		int depth = 1;		/* Count parenthesis depth. */
+		while ( *cp && depth > 0 )
+		    switch ( *(cp++) )
+		    {
+		    case '(':	depth++;		break;
+		    case ')':	depth--;		break;
+		    }
+		break;
+	    }
+
+	    case '!': 			/* required argument */
+		required = YES;
+	    case '%': 			/* not required argument */
+reswitch:				/* after finding '*' or ',' */
+		switch (typchr = *(cp++))
+		{
+		    case ',':		/* argument is AT&T list of things */
+			comma_list = YES;
+		    case '*':		/* argument is list of things */
+			list_of = YES;
+			list_cnt = 0;	/* none yet */
+			cnt_arg = va_arg( argl, int *);	/* item count * here */
+			goto reswitch;	/* try again */
+
+		    case '$':		/* "rest" of argument list */
+			while ( argc > 1 && !arg_used[argc-1] )
+			    argc--;	/* find last used argument */
+			*va_arg( argl, int * ) = argc;
+			break;
+
+		    case '&':		/* Return unused args. */
+			/* Count how many.  Always include argv[0]. */
+			for ( nopt = cnt = 1; cnt < argc; cnt++ )
+			    if ( !arg_used[cnt] )
+				nopt++;
+			if ( nopt == 1 )
+			    nopt = 0;	/* Special case for no args. */
+			if ( nopt > 0 )
+			{
+			    strlist = NEW( char *, nopt + 1 );
+			    /* Copy program name, for sure. */
+			    strlist[0] = argv[0];
+			    for ( nopt = cnt = 1; cnt < argc; cnt++ )
+				if ( !arg_used[cnt] )
+				{
+				    strlist[nopt++] = argv[cnt];
+				    check += cnt;
+				    arg_used[cnt] = 1;
+				}
+			    strlist[nopt] = NULL;
+			}
+			else
+			    strlist = NULL;	/* No args, return empty. */
+
+			/* Return count and arg list. */
+			*va_arg( argl, int * ) = nopt;
+			*va_arg( argl, char *** ) = strlist;
+			break;
+
+		    case '-': 		/* argument is flag */
+			if (optarg > 0)
+			    ERROR("Format error: flag conditional on flag not allowed");
+
+		    /* go back to label */
+			ncp = cp-1;	/* remember */
+			cp -= 3;
+			for (excnt = exflag = 0
+				; *cp != ' ' && !(*cp=='-' &&(cp[-1]=='!'||cp[-1]=='%'));
+				(--cp, excnt++))
+			{
+			    for (cnt = optarg+1; cnt < argc; cnt++)
+			    {
+			    /* flags all start with - */
+				if (*argv[cnt] == '-' && !arg_used[cnt] &&
+					!ISDIGIT(argv[cnt][1]))
+				    if (*(argv[cnt] + 1) == *cp)
+				    {
+					if (*(argv[cnt] + 2) != 0)
+					    ERROR ("extra flags ignored");
+					if (exflag)
+					    ERROR ("more than one exclusive flag chosen");
+					exflag++;
+					required = NO;
+					check += cnt;
+					arg_used[cnt] = 1;
+					nopt = cnt;
+					*va_arg( argl, int *) |= (1 << excnt);
+					break;
+				    }
+			    }
+			}
+			if (required)
+			    ERROR ("flag argument missing");
+			cp = ncp;
+			/* 
+			 * If none of these flags were found, skip any
+			 * optional arguments (in the varargs list, too).
+			 */
+			if (!exflag)
+			{
+			    (void)va_arg( argl, int * );/* skip the arg, too */
+			    while (*++cp && ! ISSPACE(*cp))
+				if (*cp == '!' || *cp == '%')
+				{
+				    if ( *++cp == '*' || *cp == ',' )
+				    {
+					cp++;
+					(void)va_arg( argl, int * );
+				    }
+				    /* 
+				     * Assume that char * might be a
+				     * different size, but that all
+				     * other pointers are same size.
+				     */
+				    if ( *cp == 's' )
+					(void)va_arg( argl, char * );
+				    else
+					(void)va_arg( argl, ptr );
+				}
+			}
+			else
+			{
+			    optarg = nopt;
+			    cp++;	/* skip over - */
+			}
+
+			break;
+
+		    case 's': 		/* char string */
+		    case 'd': 		/* decimal # */
+		    case 'o': 		/* octal # */
+		    case 'x': 		/* hexadecimal # */
+		    case 'n':		/* "number" in C syntax */
+		    case 'f': 		/* floating # */
+		    case 'D': 		/* long decimal # */
+		    case 'O': 		/* long octal # */
+		    case 'X': 		/* long hexadecimal # */
+		    case 'N':		/* long number in C syntax */
+		    case 'F': 		/* double precision floating # */
+#if defined(sgi) && !defined(mips)
+			/* Fix for broken SGI IRIS 2400/3000 floats */
+			if ( typchr == 'F' ) typchr = 'f';
+#endif /* sgi */
+			for (cnt = optarg+1; cnt < argc; cnt++)
+			{
+			    argp = argv[cnt];
+
+			    if ( isnum( argp, typchr, comma_list ) )
+			    {
+				;	/* it's ok, then */
+			    }
+			    else if ( *argp == '-' && argp[1] != '\0' )
+				if ( optarg > 0 ) /* end optional args? */
+				{
+				    /* Eat the arg, too, if necessary */
+				    if ( list_cnt == 0 ) {
+					if ( typchr == 's' )
+					    (void)va_arg( argl, char * );
+					else
+					    (void)va_arg( argl, ptr );
+                    }
+				    break;
+				}
+				else
+				    continue;
+			    else if ( typchr != 's' )
+				continue;	/* not number, keep looking */
+			    
+			    /* 
+			     * Otherwise usable argument may already
+			     * be used.  (Must check this after
+			     * checking for flag, though.)
+			     */
+			    if (arg_used[cnt]) continue;
+
+			    /* 
+			     * If it's a comma-and-or-space-separated
+			     * list then count how many, and separate
+			     * the list into an array of strings.
+			     */
+			    if ( comma_list )
+			    {
+				register char * s;
+				int pass;
+
+				/*
+				 * Copy the string so we remain nondestructive
+				 */
+				s = NEW( char, strlen(argp)+1 );
+				strcpy( s, argp );
+				argp = s;
+
+				/* 
+				 * On pass 0, just count them.  On
+				 * pass 1, null terminate each string 
+				 */
+				for ( pass = 0; pass <= 1; pass++ )
+				{
+				    for ( s = argp; *s != '\0'; )
+				    {
+					if ( pass )
+					    strlist[list_cnt] = s;
+					while ( (c = *s) != '\0' && c != ' ' &&
+						c != '\t' && c != ',' )
+					    s++;
+					if ( pass )
+					    *s = '\0';
+
+					list_cnt++;	/* count separators */
+					/* 
+					 * Two commas in a row give a null
+					 * string, but two spaces
+					 * don't.  Also skip spaces
+					 * after a comma.
+					 */
+					if ( c != '\0' )
+					    while ( *++s == ' ' || *s == '\t' )
+						;
+				    }
+				    if ( pass == 0 )
+				    {
+					strlist = NEW( char *, list_cnt );
+					list_cnt = 0;
+				    }
+				}
+			    }
+			    else if ( list_of )
+				list_cnt++;   /* getting them one at a time */
+			    /* 
+			     * If it's either type of list, then alloc
+			     * storage space for the returned values
+			     * (except that comma-separated string
+			     * lists already are done).
+			     */
+			    if ( list_of )
+			    {
+				if ( list_cnt == 1 || comma_list )
+				    switch( typchr )
+				    {
+					case 's':
+					    if ( !comma_list )
+						strlist = NEW( char *, 1 );
+					    aptr = (ptr) &strlist[0];
+					    break;
+					case 'n':
+					case 'd':
+					case 'o':
+					case 'x':
+					    intlist = NEW( int, list_cnt );
+					    aptr = (ptr) &intlist[0];
+					    break;
+					case 'N':
+					case 'D':
+					case 'O':
+					case 'X':
+					    longlist = NEW( long, list_cnt );
+					    aptr = (ptr) &longlist[0];
+					    break;
+					case 'f':
+					    fltlist = NEW( float, list_cnt );
+					    aptr = (ptr) &fltlist[0];
+					    break;
+					case 'F':
+					    dbllist = NEW( double, list_cnt );
+					    aptr = (ptr) &dbllist[0];
+					    break;
+				    }
+				else
+				    switch( typchr )
+				    {
+					case 's':
+					    strlist = RENEW( char *, strlist,
+							     list_cnt );
+					    aptr = (ptr) &strlist[list_cnt-1];
+					    break;
+					case 'n':
+					case 'd':
+					case 'o':
+					case 'x':
+					    intlist = RENEW( int, intlist,
+							     list_cnt );
+					    aptr = (ptr) &intlist[list_cnt-1];
+					    break;
+					case 'N':
+					case 'D':
+					case 'O':
+					case 'X':
+					    longlist = RENEW( long, longlist,
+							      list_cnt );
+					    aptr = (ptr) &longlist[list_cnt-1];
+					    break;
+					case 'f':
+					    fltlist = RENEW( float, fltlist,
+							     list_cnt );
+					    aptr = (ptr) &fltlist[list_cnt-1];
+					    break;
+					case 'F':
+					    dbllist = RENEW( double, dbllist,
+							     list_cnt );
+					    aptr = (ptr) &dbllist[list_cnt-1];
+					    break;
+				    }
+			    }
+			    else
+				aptr = va_arg( argl, ptr );
+
+			    if ( typchr == 's' )
+			    {
+				if ( ! comma_list )
+				    *(char **)aptr = argp;
+			    }
+			    else
+			    {
+				nopt = 0;
+				do {
+				    /* 
+				     * Need to update aptr if parsing
+				     * a comma list
+				     */
+				    if ( comma_list && nopt > 0 )
+				    {
+					argp = strlist[nopt];
+					switch( typchr )
+					{
+					    case 'n':
+					    case 'd':
+					    case 'o':
+					    case 'x':
+						aptr = (ptr) &intlist[nopt];
+						break;
+					    case 'N':
+					    case 'D':
+					    case 'O':
+					    case 'X':
+						aptr = (ptr) &longlist[nopt];
+						break;
+					    case 'f':
+						aptr = (ptr) &fltlist[nopt];
+						break;
+					    case 'F':
+						aptr = (ptr) &dbllist[nopt];
+						break;
+					}
+				    }
+				    /* 
+				     * Do conversion for n and N types
+				     */
+				    tmpflg = typchr;
+				    if (typchr == 'n' || typchr == 'N' ) {
+					if (*argp != '0')
+					    tmpflg = 'd';
+					else if (*(argp+1) == 'x' ||
+						 *(argp+1) == 'X')
+					{
+					    tmpflg = 'x';
+					    argp += 2;
+					}
+					else
+					    tmpflg = 'o';
+                    }
+				    if (typchr == 'N')
+					tmpflg = toupper( tmpflg );
+
+
+				    /* put in conversion */
+				    if ( isupper( tmpflg ) )
+				    {
+					cntrl[1] = 'l';
+					cntrl[2] = tolower( tmpflg );
+				    }
+				    else
+				    {
+					cntrl[1] = tmpflg;
+					cntrl[2] = ' ';
+				    }
+				    if (sscanf (argp, cntrl, aptr, junk) != 1)
+					ERROR ("Bad numeric argument");
+				} while ( comma_list && ++nopt < list_cnt );
+			    }
+			    check += cnt;
+			    arg_used[cnt] = 1;
+			    required = NO;
+			    /*
+			     * If not looking for multiple args,
+			     * then done, otherwise, keep looking.
+			     */
+			    if ( !( list_of && !comma_list ) )
+				break;
+			    else
+				continue;
+			}
+			if (required)
+			    switch (typchr)
+			    {
+				case 'x': 
+				case 'X': 
+				    ERROR ("missing hexadecimal argument");
+				case 's': 
+				    ERROR ("missing string argument");
+				case 'o': 
+				case 'O': 
+				    ERROR ("missing octal argument");
+				case 'd': 
+				case 'D': 
+				    ERROR ("missing decimal argument");
+				case 'f': 
+				case 'F': 
+				    ERROR ("missing floating argument");
+				case 'n':
+				case 'N':
+				    ERROR ("missing numeric argument");
+			    }
+			if ( list_cnt > 0 )
+			{
+			    *cnt_arg = list_cnt;
+			    switch ( typchr )
+			    {
+				case 's':
+				    *va_arg( argl, char *** ) = strlist;
+				    break;
+				case 'n':
+				case 'd':
+				case 'o':
+				case 'x':
+				    *va_arg( argl, int ** ) = intlist;
+				    break;
+				case 'N':
+				case 'D':
+				case 'O':
+				case 'X':
+				    *va_arg( argl, long ** ) = longlist;
+				    break;
+				case 'f':
+				    *va_arg( argl, float ** ) = fltlist;
+				    break;
+				case 'F':
+				    *va_arg( argl, double **) = dbllist;
+				    break;
+			    }
+			    if ( typchr != 's' && comma_list )
+				free( (char *) strlist );
+			}
+			else if ( cnt >= argc )
+			{
+			    /* Fell off end looking, so must eat the arg */
+			    if ( typchr == 's' )
+				(void)va_arg( argl, char * );
+			    else
+				(void)va_arg( argl, ptr );
+			}
+			break;
+		    default: 		/* error */
+			fprintf (stderr,
+				 "scanargs: Corrupt or invalid format spec\n");
+			return 0;
+		}
+	}
+    }
+
+    /*  Count up empty flags */
+    for (cnt=1; cnt<argc; cnt++)
+	if (argv[cnt][0] == '-' && argv[cnt][1] == '-' && argv[cnt][2] == 0
+	    && !arg_used[cnt] )
+	    check += cnt;
+
+    /* sum from 1 to N = n*(n+1)/2 used to count up checks */
+    if (check != (((argc - 1) * argc) / 2))
+	ERROR ("extra arguments not processed");
+
+    /* If -help, always print usage. */
+    if ( help )
+	scan_usage( argv, format );
+
+    free(arg_used);
+    return 1;
+
+error: 
+    if ( !no_usage )
+	scan_usage( argv, format );
+    free(arg_used);
+    return 0;
+}
+
+void
+scan_usage( argv, format )
+char ** argv;
+CONST_DECL char * format;
+{
+    register CONST_DECL char * cp;
+
+    fprintf (stderr, "usage : ");
+    if (*(cp = format) != ' ')
+    {
+	if ( *cp == '%' )
+	{
+	    /* 
+	     * This is bogus, but until everyone can agree on a name
+	     * for (rindex/strrchr) ....
+	     */
+	    for ( cp = argv[0]; *cp != '\0'; cp++ )
+		;			/* find the end of the string */
+	    for ( ; cp > argv[0] && *cp != '/'; cp-- )
+		;			/* find the last / */
+	    if ( *cp == '/' )
+		cp++;
+	    fprintf( stderr, "%s", cp );
+
+	    cp = format + 1;		/* reset to where it should be */
+	}
+	while (putc (*cp++, stderr) != ' ');
+    }
+    else
+	fprintf (stderr, "?? ");
+    while (*cp == ' ')
+	cp++;
+    (void)prformat (cp, NO);
+}
+
+static CONST_DECL char *
+prformat (format, recurse)
+CONST_DECL char   *format;
+int 	recurse;
+{
+    register CONST_DECL char  *cp;
+    bool    required, comma_list;
+    int    list_of, depth;
+
+    cp = format;
+    if (recurse)
+	putc (' ', stderr);
+
+    required = NO;
+    list_of = 0;
+    comma_list = NO;
+    while (*cp)
+    {
+	switch (*cp)
+	{
+	    default:
+	    	cp++;
+		break;
+	    case ' ':
+	    case '\n':
+	    case '\t':
+		/* allow annotations */
+		for ( ; format < cp; format++ )
+		    putc( *format, stderr );
+		putc(*cp, stderr);
+		format = ++cp;
+		break;
+
+	    case '(':
+		/* Parentheses surround an arbitrary (parenthesis
+		 * balanced) comment.
+		 */
+		for ( ; format < cp; format++ )
+		    putc( *format, stderr );
+		for ( cp++, depth = 1; *cp && depth > 0; )
+		{
+		    /* Don't print last close paren. */
+		    if ( *cp != ')' || depth > 1 )
+			putc( *cp, stderr );
+		    switch( *(cp++) )
+		    {
+		    case '(':	depth++;		break;
+		    case ')':	depth--;		break;
+		    }
+		}
+		format = cp;
+		break;
+
+	    case '!': 
+		required = YES;
+	    case '%': 
+reswitch:
+		switch (*++cp)
+		{
+		    case ',':
+			comma_list++;
+		    case '*':
+			list_of++;
+			goto reswitch;
+
+		    case '$':		/* "rest" of argument list */
+			if (!required)
+			    putc ('[', stderr);
+			for (; format < cp - 1 - list_of; format++)
+			    putc (*format, stderr);
+			fputs( " ...", stderr );
+			if ( !required )
+			    putc( ']', stderr );
+			break;
+
+		    case '-': 		/* flags */
+			if (!required)
+			    putc ('[', stderr);
+			putc ('-', stderr);
+
+			if (cp - format > 2 + list_of)
+			    putc ('{', stderr);
+			cp = format;
+			while (*cp != '%' && *cp != '!')
+			    putc (*cp++, stderr);
+			if (cp - format > 1 + list_of)
+			    putc ('}', stderr);
+			cp += 2;	/* skip !- or %- */
+			if (*cp && !ISSPACE(*cp))
+			    cp = prformat (cp, YES);
+					/* this is a recursive call */
+
+			cp--;	/* don't ignore next character */
+
+			if (!required)
+			    putc (']', stderr);
+			break;
+		    case 's': 		/* char string */
+		    case 'd': 		/* decimal # */
+		    case 'o': 		/* octal # */
+		    case 'x': 		/* hexadecimal # */
+		    case 'f': 		/* floating # */
+		    case 'D': 		/* long decimal # */
+		    case 'O': 		/* long octal # */
+		    case 'X': 		/* long hexadecimal # */
+		    case 'F': 		/* double precision floating # */
+		    case 'n':		/* numeric arg (C format) */
+		    case 'N':		/* long numeric arg */
+			if (!required)
+			    putc ('[', stderr);
+			for (; format < cp - 1 - list_of; format++)
+			    putc (*format, stderr);
+			if ( list_of != 0 )
+			{
+			    if ( comma_list )
+				putc( ',', stderr );
+			    else
+				putc( ' ', stderr );
+			    fputs( "...", stderr );
+			}
+			if (!required)
+			    putc (']', stderr);
+			break;
+		    default: 
+			break;
+		}
+		required = NO;
+		list_of = NO;
+		comma_list = NO;
+		if (*cp)		/* check for end of string */
+		    format = ++cp;
+		if (*cp && !ISSPACE(*cp))
+		    putc (' ', stderr);
+	}
+	if (recurse && ISSPACE(*cp))
+	    break;
+    }
+    if (!recurse)
+    {
+	for ( ; format < cp; format++ )
+	    putc( *format, stderr );
+	putc ('\n', stderr);
+    }
+    return (cp);
+}
+
+/* 
+ * isnum - determine whether a string MIGHT represent a number.
+ * typchr indicates the type of argument we are looking for, and
+ * determines the legal character set.  If comma_list is YES, then
+ * space and comma are also legal characters.
+ */
+static int
+isnum( str, typchr, comma_list )
+register CONST_DECL char * str;
+int typchr;
+int comma_list;
+{
+    register CONST_DECL char *allowed, *digits, *cp;
+    int hasdigit = NO;
+
+    switch( typchr )
+    {
+	case 'n':
+	case 'N':
+	    allowed = " \t,+-x0123456789abcdefABCDEF";
+	    break;
+	case 'd':
+	case 'D':
+	    allowed = " \t,+-0123456789";
+	    break;
+	case 'o':
+	case 'O':
+	    allowed = " \t,01234567";
+	    break;
+	case 'x':
+	case 'X':
+	    allowed = " \t,0123456789abcdefABCDEF";
+	    break;
+	case 'f':
+	case 'F':
+	    allowed = " \t,+-eE.0123456789";
+	    break;
+	case 's':			/* only throw out decimal numbers */
+	default:
+	    allowed = " \t,+-.0123456789";
+	    break;
+    }
+    digits = allowed;
+    while ( *digits != '0' )
+	digits++;
+    if ( ! comma_list )
+	allowed += 3;		      /* then don't allow space, tab, comma */
+
+    while ( *str != '\0' )
+    {
+    	for ( cp = allowed; *cp != '\0' && *cp != *str; cp++ )
+    	    ;
+    	if ( *cp == '\0' )
+	    return NO;		     /* if not in allowed chars, not number */
+	if ( cp - digits >= 0 )
+	    hasdigit = YES;
+	str++;
+    }
+    return hasdigit;
+}
diff --git a/urt/vaxshort.c b/urt/vaxshort.c
new file mode 100644
index 00000000..4b57b516
--- /dev/null
+++ b/urt/vaxshort.c
@@ -0,0 +1,50 @@
+/*
+ *			V A X S H O R T
+ *
+ *  Code to manipulate 16-bit integers in VAX order in a
+ *  machine independent manner.
+ *
+ *  (VAX is a trademark of Digital Equipment Corporation)
+ *
+ *  Author -
+ *	Michael John Muuss
+ *  
+ *  Source -
+ *	SECAD/VLD Computing Consortium, Bldg 394
+ *	The U. S. Army Ballistic Research Laboratory
+ *	Aberdeen Proving Ground, Maryland  21005-5066
+ *  
+ *  Distribution Status -
+ *	Public Domain, Distribution Unlimitied.
+ */
+
+#include "vaxshort.h"
+
+/*
+ *			V A X _ G S H O R T
+ *
+ *  Obtain a 16-bit signed integer from two adjacent characters,
+ *  stored in VAX order, regardless of word alignment.
+ */
+int
+vax_gshort(char *msgp)
+{
+	register unsigned char *p = (unsigned char *) msgp;
+	register int	i;
+
+	if( (i = (p[1] << 8) | p[0]) & 0x8000 )
+		return(i | ~0xFFFF);	/* Sign extend */
+	return(i);
+}
+
+/*
+ *			V A X _ P S H O R T
+ */
+char *
+vax_pshort(char *msgp, unsigned short s)
+{
+
+	msgp[0] = s & 0xFF;
+	msgp[1] = s >> 8;
+	return(msgp+2);
+}
diff --git a/urt/vaxshort.h b/urt/vaxshort.h
new file mode 100644
index 00000000..daeb856b
--- /dev/null
+++ b/urt/vaxshort.h
@@ -0,0 +1,5 @@
+int
+vax_gshort(char *msgp);
+
+char *
+vax_pshort(char *msgp, unsigned short s);
diff --git a/version.h b/version.h
new file mode 100644
index 00000000..b5229071
--- /dev/null
+++ b/version.h
@@ -0,0 +1 @@
+#define NETPBM_VERSION "Netpbm 10.35.0"
diff --git a/vms/Add_List.com b/vms/Add_List.com
new file mode 100755
index 00000000..830cfc96
--- /dev/null
+++ b/vms/Add_List.com
@@ -0,0 +1,59 @@
+$ VERIFY = F$Verify (0)
+$!
+$!     ADD_LIST.COM command procedure
+$!         Usage:
+$!             ADD_LIST library file_spec [logical_name_table]
+$!
+$!     Last Modified: 18-JAN-1991 Rick Dyson
+$!
+$!     Escape routes
+$ On Control_Y Then GoTo FINISH
+$ On Error     Then GoTo FINISH
+$ On Warning   Then GoTo FINISH
+$ On Severe    Then GoTo FINISH
+$!
+$!     We're out'a here if the calling parameter is null
+$ P2 = F$Edit (P2, "TRIM, UPCASE")
+$ If P2 .eqs. "" Then GoTo FINISH
+$!
+$!     Check logical name table argument and default if necessary.
+$!
+$ TABLE = F$Edit (P3, "UNCOMMENT, UPCASE, TRIM")
+$ If (TABLE .eqs. "PROCESS")
+$    Then
+$    Else If (TABLE .eqs. "GROUP")
+$            Then
+$            Else If (TABLE .eqs. "JOB")
+$                    Then
+$                    Else If (TABLE .eqs. "SYSTEM")
+$                            Then
+$                            Else
+$                                TABLE = "Process"
+$                         EndIf
+$                 EndIf
+$         EndIf
+$ EndIf
+$!
+$! Check the first value in the library list
+$ LIB = P1
+$ X = F$TrnLnm (LIB, "LNM$''TABLE'")
+$ If X .eqs. "" Then GoTo INSERT
+$ If X .eqs. P2 Then GoTo FINISH
+$!
+$! Find the first free logical to assign the library file to
+$ BASE = P1 + "_"
+$ N = 1
+$NEXTLIB:
+$   LIB := 'BASE''N'
+$   X = F$TrnLnm (LIB, "LNM$''TABLE'")
+$   If X .eqs. "" Then GoTo INSERT
+$   If X .eqs. P2 Then GoTo FINISH
+$   N = N + 1
+$   GoTo NEXTLIB
+$!
+$! Add the library file to the library file list
+$INSERT:
+$   Define /'TABLE' 'LIB' 'P2'
+$FINISH:
+$   VERIFY = F$Verify (VERIFY)
+$   Exit
diff --git a/vms/Make_PBMplus.com b/vms/Make_PBMplus.com
new file mode 100755
index 00000000..d3243d96
--- /dev/null
+++ b/vms/Make_PBMplus.com
@@ -0,0 +1,519 @@
+$ If F$Mode () .eqs. "INTERACTIVE"
+$    Then
+$        VERIFY = F$Verify (0)
+$    Else
+$        VERIFY = F$Verify (1)
+$ EndIf
+$ THIS_PATH = F$Element (0, "]", F$Environment ("PROCEDURE")) + "]"
+$ Set Default 'THIS_PATH'
+$!
+$ PBMPLUS_PATH = F$Element (0, "]", F$Environment ("DEFAULT")) + ".]"
+$ Define /NoLog /Translation_Attributes = Concealed PBMplus_Root "''PBMPLUS_PATH'"
+$ Define /NoLog PBMplus_Dir PBMplus_Root:[000000]
+$ Define /NoLog PBMplusShr PBMplus_Dir:PBMplusShr
+$ @ PBMplus_Dir:STAMP-DATE.COM
+$ Purge /NoLog /NoConfirm COMPILE.H
+$!
+$!            Make the Shareable Library
+$!
+$ Set Default PBMplus_Root:[pbm]
+$ PBMPLUS_PATH = F$Element (0, "]", F$Environment ("DEFAULT")) - ".PBM" + ".]"
+$ If F$TrnLnm ("PBMplus_Root") .eqs. "" Then 	Define /Translation_Attributes = Concealed PBMplus_Root "''PBMPLUS_PATH'"
+$ If F$TrnLnm ("PBMplus_Dir") .eqs. "" Then 	Define PBMplus_Dir PBMplus_Root:[000000]
+$ If F$TrnLnm ("Sys") .eqs. "" Then Define Sys Sys$Library
+$ CC /NOLIST/OBJECT=LIBPBM1.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] LIBPBM1.C
+$ CC /NOLIST/OBJECT=LIBPBM2.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] LIBPBM2.C
+$ CC /NOLIST/OBJECT=LIBPBM3.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] LIBPBM3.C
+$ CC /NOLIST/OBJECT=LIBPBM4.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] LIBPBM4.C
+$ CC /NOLIST/OBJECT=LIBPBM5.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] LIBPBM5.C
+$ Library /Create libpbm.olb libpbm%.obj
+$ Set Default PBMplus_Root:[pgm]
+$ PBMPLUS_PATH = F$Element (0, "]", F$Environment ("DEFAULT")) - ".PGM" + ".]"
+$ If F$TrnLnm ("PBMplus_Root") .eqs. "" Then 	Define /Translation_Attributes = Concealed PBMplus_Root "''PBMPLUS_PATH'"
+$ If F$TrnLnm ("PBMplus_Dir") .eqs. "" Then 	Define PBMplus_Dir PBMplus_Root:[000000]
+$ If F$TrnLnm ("Sys") .eqs. "" Then Define Sys Sys$Library
+$ CC /NOLIST/OBJECT=LIBPGM1.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) LIBPGM1.C
+$ CC /NOLIST/OBJECT=LIBPGM2.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) LIBPGM2.C
+$ Library /Create libpgm.olb libpgm%.obj
+$ Set Default PBMplus_Root:[ppm]
+$ PBMPLUS_PATH = F$Element (0, "]", F$Environment ("DEFAULT")) - ".PPM" + ".]"
+$ If F$TrnLnm ("PBMplus_Root") .eqs. "" Then 	Define /Translation_Attributes = Concealed PBMplus_Root "''PBMPLUS_PATH'"
+$ If F$TrnLnm ("PBMplus_Dir") .eqs. "" Then 	Define PBMplus_Dir PBMplus_Root:[000000]
+$ If F$TrnLnm ("Sys") .eqs. "" Then Define Sys Sys$Library
+$ CC /NOLIST/OBJECT=LIBPPM1.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) LIBPPM1.C
+$ CC /NOLIST/OBJECT=LIBPPM2.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) LIBPPM2.C
+$ CC /NOLIST/OBJECT=LIBPPM3.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) LIBPPM3.C
+$ CC /NOLIST/OBJECT=LIBPPM4.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) LIBPPM4.C
+$ CC /NOLIST/OBJECT=LIBPPM5.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) LIBPPM5.C
+$ CC /NOLIST/OBJECT=BITIO.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) BITIO.C
+$ Library /Create libppm.olb libppm%.obj,bitio.obj
+$ Set Default PBMplus_Root:[pnm]
+$ PBMPLUS_PATH = F$Element (0, "]", F$Environment ("DEFAULT")) - ".PNM" + ".]"
+$ If F$TrnLnm ("PBMplus_Root") .eqs. "" Then 	Define /Translation_Attributes = Concealed PBMplus_Root "''PBMPLUS_PATH'"
+$ If F$TrnLnm ("PBMplus_Dir") .eqs. "" Then 	Define PBMplus_Dir PBMplus_Root:[000000]
+$ If F$TrnLnm ("Sys") .eqs. "" Then Define Sys Sys$Library
+$ CC /NOLIST/OBJECT=LIBPNM1.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) LIBPNM1.C
+$ CC /NOLIST/OBJECT=LIBPNM2.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) LIBPNM2.C
+$ CC /NOLIST/OBJECT=LIBPNM3.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) LIBPNM3.C
+$ CC /NOLIST/OBJECT=LIBPNM4.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) LIBPNM4.C
+$ Library /Create libpnm.olb libpnm%.obj
+$ Set Default PBMplus_Dir
+$ @ PBMplus_Dir:MAKE_PBMplusShr.COM
+$!
+$!		PBM
+$!
+$ Set Default PBMplus_Root:[pbm]
+$ PBMPLUS_PATH = F$Element (0, "]", F$Environment ("DEFAULT")) - ".PBM" + ".]"
+$ If F$TrnLnm ("PBMplus_Root") .eqs. "" Then 	Define /Translation_Attributes = Concealed PBMplus_Root "''PBMPLUS_PATH'"
+$ If F$TrnLnm ("PBMplus_Dir") .eqs. "" Then 	Define PBMplus_Dir PBMplus_Root:[000000]
+$ If F$TrnLnm ("Sys") .eqs. "" Then Define Sys Sys$Library
+$ CC /NOLIST/OBJECT=ATKTOPBM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] ATKTOPBM.C
+$ LINK /TRACE/NOMAP/EXEC=ATKTOPBM.EXE ATKTOPBM.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=BRUSHTOPBM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] BRUSHTOPBM.C
+$ LINK /TRACE/NOMAP/EXEC=BRUSHTOPBM.EXE BRUSHTOPBM.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=CMUWMTOPBM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] CMUWMTOPBM.C
+$ LINK /TRACE/NOMAP/EXEC=CMUWMTOPBM.EXE CMUWMTOPBM.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=G3TOPBM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] G3TOPBM.C
+$ LINK /TRACE/NOMAP/EXEC=G3TOPBM.EXE G3TOPBM.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=GEMTOPBM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] GEMTOPBM.C
+$ LINK /TRACE/NOMAP/EXEC=GEMTOPBM.EXE GEMTOPBM.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=ICONTOPBM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] ICONTOPBM.C
+$ LINK /TRACE/NOMAP/EXEC=ICONTOPBM.EXE ICONTOPBM.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=MACPTOPBM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] MACPTOPBM.C
+$ LINK /TRACE/NOMAP/EXEC=MACPTOPBM.EXE MACPTOPBM.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=MGRTOPBM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] MGRTOPBM.C
+$ LINK /TRACE/NOMAP/EXEC=MGRTOPBM.EXE MGRTOPBM.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMLIFE.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMLIFE.C
+$ LINK /TRACE/NOMAP/EXEC=PBMLIFE.EXE PBMLIFE.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMMAKE.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMMAKE.C
+$ LINK /TRACE/NOMAP/EXEC=PBMMAKE.EXE PBMMAKE.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMMASK.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMMASK.C
+$ LINK /TRACE/NOMAP/EXEC=PBMMASK.EXE PBMMASK.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMREDUCE.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMREDUCE.C
+$ LINK /TRACE/NOMAP/EXEC=PBMREDUCE.EXE PBMREDUCE.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTEXT.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTEXT.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTEXT.EXE PBMTEXT.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTO10X.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTO10X.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTO10X.EXE PBMTO10X.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTO4425.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTO4425.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTO4425.EXE PBMTO4425.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOASCII.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOASCII.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOASCII.EXE PBMTOASCII.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOATK.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOATK.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOATK.EXE PBMTOATK.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOBBNBG.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOBBNBG.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOBBNBG.EXE PBMTOBBNBG.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOCMUWM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOCMUWM.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOCMUWM.EXE PBMTOCMUWM.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOEPSON.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOEPSON.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOEPSON.EXE PBMTOEPSON.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOG3.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOG3.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOG3.EXE PBMTOG3.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOGEM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOGEM.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOGEM.EXE PBMTOGEM.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOGO.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOGO.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOGO.EXE PBMTOGO.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOICON.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOICON.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOICON.EXE PBMTOICON.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOLJ.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOLJ.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOLJ.EXE PBMTOLJ.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOMACP.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOMACP.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOMACP.EXE PBMTOMACP.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOMGR.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOMGR.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOMGR.EXE PBMTOMGR.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOPI3.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOPI3.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOPI3.EXE PBMTOPI3.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOPLOT.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOPLOT.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOPLOT.EXE PBMTOPLOT.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOPTX.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOPTX.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOPTX.EXE PBMTOPTX.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOX10BM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOX10BM.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOX10BM.EXE PBMTOX10BM.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOXBM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOXBM.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOXBM.EXE PBMTOXBM.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOYBM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOYBM.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOYBM.EXE PBMTOYBM.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOZINC.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOZINC.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOZINC.EXE PBMTOZINC.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMUPC.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMUPC.C
+$ LINK /TRACE/NOMAP/EXEC=PBMUPC.EXE PBMUPC.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PI3TOPBM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PI3TOPBM.C
+$ LINK /TRACE/NOMAP/EXEC=PI3TOPBM.EXE PI3TOPBM.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=XBMTOPBM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] XBMTOPBM.C
+$ LINK /TRACE/NOMAP/EXEC=XBMTOPBM.EXE XBMTOPBM.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=YBMTOPBM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] YBMTOPBM.C
+$ LINK /TRACE/NOMAP/EXEC=YBMTOPBM.EXE YBMTOPBM.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOLN03.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOLN03.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOLN03.EXE PBMTOLN03.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMCLEAN.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMCLEAN.C
+$ LINK /TRACE/NOMAP/EXEC=PBMCLEAN.EXE PBMCLEAN.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMPSCALE.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMPSCALE.C
+$ LINK /TRACE/NOMAP/EXEC=PBMPSCALE.EXE PBMPSCALE.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOEPSI.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOEPSI.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOEPSI.EXE PBMTOEPSI.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOLPS.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOLPS.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOLPS.EXE PBMTOLPS.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PBMTOPK.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PBMTOPK.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOPK.EXE PBMTOPK.obj,PBMplusSHR/Option
+$ CC /NOLIST/OBJECT=PKTOPBM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = [-] PKTOPBM.C
+$ LINK /TRACE/NOMAP/EXEC=PKTOPBM.EXE PKTOPBM.obj,PBMplusSHR/Option
+$ Set Protection = (System:RWE, Owner:RWE, Group:RE, World:RE) *.exe
+$!
+$!		PGM
+$!
+$ Set Default PBMplus_Root:[pgm]
+$ PBMPLUS_PATH = F$Element (0, "]", F$Environment ("DEFAULT")) - ".PGM" + ".]"
+$ If F$TrnLnm ("PBMplus_Root") .eqs. "" Then 	Define /Translation_Attributes = Concealed PBMplus_Root "''PBMPLUS_PATH'"
+$ If F$TrnLnm ("PBMplus_Dir") .eqs. "" Then 	Define PBMplus_Dir PBMplus_Root:[000000]
+$ If F$TrnLnm ("Sys") .eqs. "" Then Define Sys Sys$Library
+$ CC /NOLIST/OBJECT=ASCIITOPGM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) ASCIITOPGM.C
+$ LINK /TRACE/NOMAP/EXEC=ASCIITOPGM.EXE ASCIITOPGM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=FSTOPGM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) FSTOPGM.C
+$ LINK /TRACE/NOMAP/EXEC=FSTOPGM.EXE FSTOPGM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=HIPSTOPGM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) HIPSTOPGM.C
+$ LINK /TRACE/NOMAP/EXEC=HIPSTOPGM.EXE HIPSTOPGM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=LISPMTOPGM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) LISPMTOPGM.C
+$ LINK /TRACE/NOMAP/EXEC=LISPMTOPGM.EXE LISPMTOPGM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PGMBENTLEY.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) PGMBENTLEY.C
+$ LINK /TRACE/NOMAP/EXEC=PGMBENTLEY.EXE PGMBENTLEY.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PGMCRATER.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) PGMCRATER.C
+$ LINK /TRACE/NOMAP/EXEC=PGMCRATER.EXE PGMCRATER.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PGMEDGE.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) PGMEDGE.C
+$ LINK /TRACE/NOMAP/EXEC=PGMEDGE.EXE PGMEDGE.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PGMENHANCE.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) PGMENHANCE.C
+$ LINK /TRACE/NOMAP/EXEC=PGMENHANCE.EXE PGMENHANCE.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PGMHIST.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) PGMHIST.C
+$ LINK /TRACE/NOMAP/EXEC=PGMHIST.EXE PGMHIST.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PGMKERNEL.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) PGMKERNEL.C
+$ LINK /TRACE/NOMAP/EXEC=PGMKERNEL.EXE PGMKERNEL.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PGMNOISE.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) PGMNOISE.C
+$ LINK /TRACE/NOMAP/EXEC=PGMNOISE.EXE PGMNOISE.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PGMNORM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) PGMNORM.C
+$ LINK /TRACE/NOMAP/EXEC=PGMNORM.EXE PGMNORM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PGMOIL.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) PGMOIL.C
+$ LINK /TRACE/NOMAP/EXEC=PGMOIL.EXE PGMOIL.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PGMRAMP.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) PGMRAMP.C
+$ LINK /TRACE/NOMAP/EXEC=PGMRAMP.EXE PGMRAMP.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PGMTOFS.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) PGMTOFS.C
+$ LINK /TRACE/NOMAP/EXEC=PGMTOFS.EXE PGMTOFS.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PGMTOLISPM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) PGMTOLISPM.C
+$ LINK /TRACE/NOMAP/EXEC=PGMTOLISPM.EXE PGMTOLISPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PGMTOPBM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) PGMTOPBM.C
+$ LINK /TRACE/NOMAP/EXEC=PGMTOPBM.EXE PGMTOPBM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PSIDTOPGM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) PSIDTOPGM.C
+$ LINK /TRACE/NOMAP/EXEC=PSIDTOPGM.EXE PSIDTOPGM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=RAWTOPGM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) RAWTOPGM.C
+$ LINK /TRACE/NOMAP/EXEC=RAWTOPGM.EXE RAWTOPGM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PGMTEXTURE.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) PGMTEXTURE.C
+$ LINK /TRACE/NOMAP/EXEC=PGMTEXTURE.EXE PGMTEXTURE.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=BIORADTOPGM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) BIORADTOPGM.C
+$ LINK /TRACE/NOMAP/EXEC=BIORADTOPGM.EXE BIORADTOPGM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PBMTOPGM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) PBMTOPGM.C
+$ LINK /TRACE/NOMAP/EXEC=PBMTOPGM.EXE PBMTOPGM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=SPOTTOPGM.OBJ /Define = (PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-.pbm],[-]) SPOTTOPGM.C
+$ LINK /TRACE/NOMAP/EXEC=SPOTTOPGM.EXE SPOTTOPGM.obj,[-]PBMplusSHR.OPT/Option
+$ Set Protection = (System:RWE, Owner:RWE, Group:RE, World:RE) *.exe
+$!
+$!		PPM
+$!
+$ Set Default PBMplus_Root:[ppm]
+$ PBMPLUS_PATH = F$Element (0, "]", F$Environment ("DEFAULT")) - ".PPM" + ".]"
+$ If F$TrnLnm ("PBMplus_Root") .eqs. "" Then 	Define /Translation_Attributes = Concealed PBMplus_Root "''PBMPLUS_PATH'"
+$ If F$TrnLnm ("PBMplus_Dir") .eqs. "" Then 	Define PBMplus_Dir PBMplus_Root:[000000]
+$ If F$TrnLnm ("Sys") .eqs. "" Then Define Sys Sys$Library
+$ CC /NOLIST/OBJECT=GOULDTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) GOULDTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=GOULDTOPPM.EXE GOULDTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=HPCDTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) HPCDTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=HPCDTOPPM.EXE HPCDTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=ILBMTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) ILBMTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=ILBMTOPPM.EXE ILBMTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=IMGTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) IMGTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=IMGTOPPM.EXE IMGTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=MTVTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) MTVTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=MTVTOPPM.EXE MTVTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PCXTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PCXTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=PCXTOPPM.EXE PCXTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PGMTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PGMTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=PGMTOPPM.EXE PGMTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PI1TOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PI1TOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=PI1TOPPM.EXE PI1TOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PICTTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PICTTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=PICTTOPPM.EXE PICTTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PJTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PJTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=PJTOPPM.EXE PJTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPM3D.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPM3D.C
+$ LINK /TRACE/NOMAP/EXEC=PPM3D.EXE PPM3D.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMCHANGE.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMCHANGE.C
+$ LINK /TRACE/NOMAP/EXEC=PPMCHANGE.EXE PPMCHANGE.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMDIM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMDIM.C
+$ LINK /TRACE/NOMAP/EXEC=PPMDIM.EXE PPMDIM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMDITHER.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMDITHER.C
+$ LINK /TRACE/NOMAP/EXEC=PPMDITHER.EXE PPMDITHER.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMFLASH.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMFLASH.C
+$ LINK /TRACE/NOMAP/EXEC=PPMFLASH.EXE PPMFLASH.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMFORGE.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMFORGE.C
+$ LINK /TRACE/NOMAP/EXEC=PPMFORGE.EXE PPMFORGE.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMHIST.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMHIST.C
+$ LINK /TRACE/NOMAP/EXEC=PPMHIST.EXE PPMHIST.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMMAKE.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMMAKE.C
+$ LINK /TRACE/NOMAP/EXEC=PPMMAKE.EXE PPMMAKE.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMMIX.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMMIX.C
+$ LINK /TRACE/NOMAP/EXEC=PPMMIX.EXE PPMMIX.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMQUANT.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMQUANT.C
+$ LINK /TRACE/NOMAP/EXEC=PPMQUANT.EXE PPMQUANT.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMRELIEF.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMRELIEF.C
+$ LINK /TRACE/NOMAP/EXEC=PPMRELIEF.EXE PPMRELIEF.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMSHIFT.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMSHIFT.C
+$ LINK /TRACE/NOMAP/EXEC=PPMSHIFT.EXE PPMSHIFT.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMSPREAD.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMSPREAD.C
+$ LINK /TRACE/NOMAP/EXEC=PPMSPREAD.EXE PPMSPREAD.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOACAD.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOACAD.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOACAD.EXE PPMTOACAD.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOGIF.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOGIF.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOGIF.EXE PPMTOGIF.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOICR.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOICR.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOICR.EXE PPMTOICR.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOILBM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOILBM.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOILBM.EXE PPMTOILBM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOMITSU.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOMITSU.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOMITSU.EXE PPMTOMITSU.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOPCX.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOPCX.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOPCX.EXE PPMTOPCX.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOPGM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOPGM.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOPGM.EXE PPMTOPGM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOPI1.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOPI1.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOPI1.EXE PPMTOPI1.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOPICT.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOPICT.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOPICT.EXE PPMTOPICT.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOPJ.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOPJ.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOPJ.EXE PPMTOPJ.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOPUZZ.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOPUZZ.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOPUZZ.EXE PPMTOPUZZ.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTORGB3.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTORGB3.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTORGB3.EXE PPMTORGB3.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOSIXEL.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOSIXEL.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOSIXEL.EXE PPMTOSIXEL.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOTGA.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOTGA.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOTGA.EXE PPMTOTGA.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOUIL.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOUIL.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOUIL.EXE PPMTOUIL.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOXPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOXPM.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOXPM.EXE PPMTOXPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOYUV.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOYUV.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOYUV.EXE PPMTOYUV.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=QRTTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) QRTTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=QRTTOPPM.EXE QRTTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=RAWTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) RAWTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=RAWTOPPM.EXE RAWTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=RGB3TOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) RGB3TOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=RGB3TOPPM.EXE RGB3TOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=SLDTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) SLDTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=SLDTOPPM.EXE SLDTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=SPCTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) SPCTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=SPCTOPPM.EXE SPCTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=SPUTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) SPUTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=SPUTOPPM.EXE SPUTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=TGATOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) TGATOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=TGATOPPM.EXE TGATOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=XIMTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) XIMTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=XIMTOPPM.EXE XIMTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=XPMTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) XPMTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=XPMTOPPM.EXE XPMTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=XVMINITOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) XVMINITOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=XVMINITOPPM.EXE XVMINITOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=YUVTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) YUVTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=YUVTOPPM.EXE YUVTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=BMPTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) BMPTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=BMPTOPPM.EXE BMPTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMBRIGHTEN.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMBRIGHTEN.C
+$ LINK /TRACE/NOMAP/EXEC=PPMBRIGHTEN.EXE PPMBRIGHTEN.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMDIST.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMDIST.C
+$ LINK /TRACE/NOMAP/EXEC=PPMDIST.EXE PPMDIST.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMQVGA.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMQVGA.C
+$ LINK /TRACE/NOMAP/EXEC=PPMQVGA.EXE PPMQVGA.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOBMP.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOBMP.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOBMP.EXE PPMTOBMP.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOMAP.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOMAP.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOMAP.EXE PPMTOMAP.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOPJXL.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOPJXL.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOPJXL.EXE PPMTOPJXL.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMTOYUVSPLIT.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMTOYUVSPLIT.C
+$ LINK /TRACE/NOMAP/EXEC=PPMTOYUVSPLIT.EXE PPMTOYUVSPLIT.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=YUVSPLITTOPPM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) YUVSPLITTOPPM.C
+$ LINK /TRACE/NOMAP/EXEC=YUVSPLITTOPPM.EXE YUVSPLITTOPPM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PPMPAT.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.pgm],[-.pbm]) PPMPAT.C
+$ LINK /TRACE/NOMAP/EXEC=PPMPAT.EXE PPMPAT.obj,[-]PBMplusSHR.OPT/Option
+$ Set Protection = (System:RWE, Owner:RWE, Group:RE, World:RE) *.exe
+$ Set Default PBMplus_Root:[libtiff]
+$ Define /NoLog Sys Sys$Library
+$ CC /NOLIST/OBJECT=MKG3STATES.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize mkg3states.c
+$ Define /User_Mode LNK$Library Sys$Library:VAXCRTL
+$ LINK /TRACE/NOMAP/EXEC=MKG3STATES.EXE mkg3states
+$ Define /User_Mode Sys$Output g3states.h
+$ Run mkg3states.exe
+$ CC /NOLIST/OBJECT=TIF_FAX3.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_FAX3.C
+$ CC /NOLIST/OBJECT=TIF_FAX4.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_FAX4.C
+$ CC /NOLIST/OBJECT=TIF_AUX.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_AUX.C
+$ CC /NOLIST/OBJECT=TIF_CCITTRLE.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_CCITTRLE.C
+$ CC /NOLIST/OBJECT=TIF_CLOSE.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_CLOSE.C
+$ CC /NOLIST/OBJECT=TIF_COMPRESS.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_COMPRESS.C
+$ CC /NOLIST/OBJECT=TIF_DIR.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_DIR.C
+$ CC /NOLIST/OBJECT=TIF_DIRINFO.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_DIRINFO.C
+$ CC /NOLIST/OBJECT=TIF_DIRREAD.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_DIRREAD.C
+$ CC /NOLIST/OBJECT=TIF_DIRWRITE.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_DIRWRITE.C
+$ CC /NOLIST/OBJECT=TIF_DUMPMODE.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_DUMPMODE.C
+$ CC /NOLIST/OBJECT=TIF_ERROR.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_ERROR.C
+$ CC /NOLIST/OBJECT=TIF_GETIMAGE.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_GETIMAGE.C
+$ CC /NOLIST/OBJECT=TIF_JPEG.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_JPEG.C
+$ CC /NOLIST/OBJECT=TIF_FLUSH.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_FLUSH.C
+$ CC /NOLIST/OBJECT=TIF_LZW.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_LZW.C
+$ CC /NOLIST/OBJECT=TIF_MACHDEP.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_MACHDEP.C
+$ CC /NOLIST/OBJECT=TIF_NEXT.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_NEXT.C
+$ CC /NOLIST/OBJECT=TIF_OPEN.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_OPEN.C
+$ CC /NOLIST/OBJECT=TIF_PACKBITS.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_PACKBITS.C
+$ CC /NOLIST/OBJECT=TIF_PRINT.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_PRINT.C
+$ CC /NOLIST/OBJECT=TIF_READ.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_READ.C
+$ CC /NOLIST/OBJECT=TIF_STRIP.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_STRIP.C
+$ CC /NOLIST/OBJECT=TIF_SWAB.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_SWAB.C
+$ CC /NOLIST/OBJECT=TIF_THUNDER.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_THUNDER.C
+$ CC /NOLIST/OBJECT=TIF_TILE.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_TILE.C
+$ CC /NOLIST/OBJECT=TIF_VERSION.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_VERSION.C
+$ CC /NOLIST/OBJECT=TIF_VMS.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_VMS.C
+$ CC /NOLIST/OBJECT=TIF_WARNING.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_WARNING.C
+$ CC /NOLIST/OBJECT=TIF_WRITE.OBJ /Include_Directory = ([]) /Define = ("BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /NoDebug /Optimize TIF_WRITE.C
+$ If "''F$Search ("libtiff.olb")'" .eqs. "" Then Library /Create libtiff.olb
+$ Delete /NoConfirm /NoLog mkg3states.obj;
+$ Library /Replace libtiff.olb *.obj
+$!
+$!		PNM
+$!
+$ Set Default PBMplus_Root:[pnm]
+$ PBMPLUS_PATH = F$Element (0, "]", F$Environment ("DEFAULT")) - ".PNM" + ".]"
+$ If F$TrnLnm ("PBMplus_Root") .eqs. "" Then 	Define /Translation_Attributes = Concealed PBMplus_Root "''PBMPLUS_PATH'"
+$ If F$TrnLnm ("PBMplus_Dir") .eqs. "" Then 	Define PBMplus_Dir PBMplus_Root:[000000]
+$ If F$TrnLnm ("Sys") .eqs. "" Then Define Sys Sys$Library
+$ CC /NOLIST/OBJECT=PNMALIAS.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMALIAS.C
+$ LINK /TRACE/NOMAP/EXEC=PNMALIAS.EXE PNMALIAS.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMARITH.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMARITH.C
+$ LINK /TRACE/NOMAP/EXEC=PNMARITH.EXE PNMARITH.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMCAT.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMCAT.C
+$ LINK /TRACE/NOMAP/EXEC=PNMCAT.EXE PNMCAT.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMCONVOL.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMCONVOL.C
+$ LINK /TRACE/NOMAP/EXEC=PNMCONVOL.EXE PNMCONVOL.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMCROP.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMCROP.C
+$ LINK /TRACE/NOMAP/EXEC=PNMCROP.EXE PNMCROP.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMCUT.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMCUT.C
+$ LINK /TRACE/NOMAP/EXEC=PNMCUT.EXE PNMCUT.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMDEPTH.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMDEPTH.C
+$ LINK /TRACE/NOMAP/EXEC=PNMDEPTH.EXE PNMDEPTH.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMENLARGE.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMENLARGE.C
+$ LINK /TRACE/NOMAP/EXEC=PNMENLARGE.EXE PNMENLARGE.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMFILE.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMFILE.C
+$ LINK /TRACE/NOMAP/EXEC=PNMFILE.EXE PNMFILE.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMFLIP.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMFLIP.C
+$ LINK /TRACE/NOMAP/EXEC=PNMFLIP.EXE PNMFLIP.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMHISTMAP.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMHISTMAP.C
+$ LINK /TRACE/NOMAP/EXEC=PNMHISTMAP.EXE PNMHISTMAP.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMINVERT.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMINVERT.C
+$ LINK /TRACE/NOMAP/EXEC=PNMINVERT.EXE PNMINVERT.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMNORAW.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMNORAW.C
+$ LINK /TRACE/NOMAP/EXEC=PNMNORAW.EXE PNMNORAW.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMNTSC.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMNTSC.C
+$ LINK /TRACE/NOMAP/EXEC=PNMNORAW.EXE PNMNORAW.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMNTSC.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMNTSC.C
+$ LINK /TRACE/NOMAP/EXEC=PNMNTSC.EXE PNMNTSC.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMPASTE.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMPASTE.C
+$ LINK /TRACE/NOMAP/EXEC=PNMPASTE.EXE PNMPASTE.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMSCALE.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMSCALE.C
+$ LINK /TRACE/NOMAP/EXEC=PNMSCALE.EXE PNMSCALE.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMTILE.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMTILE.C
+$ LINK /TRACE/NOMAP/EXEC=PNMTILE.EXE PNMTILE.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMTODDIF.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMTODDIF.C
+$ LINK /TRACE/NOMAP/EXEC=PNMTODDIF.EXE PNMTODDIF.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMTOFITS.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMTOFITS.C
+$ LINK /TRACE/NOMAP/EXEC=PNMTOFITS.EXE PNMTOFITS.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=FITSTOPNM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) FITSTOPNM.C
+$ LINK /TRACE/NOMAP/EXEC=FITSTOPNM.EXE FITSTOPNM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMTOPS.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMTOPS.C
+$ LINK /TRACE/NOMAP/EXEC=PNMTOPS.EXE PNMTOPS.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMTORAST.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMTORAST.C
+$ LINK /TRACE/NOMAP/EXEC=PNMTORAST.EXE PNMTORAST.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMTOXWD.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMTOXWD.C
+$ LINK /TRACE/NOMAP/EXEC=PNMTOXWD.EXE PNMTOXWD.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=RASTTOPNM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) RASTTOPNM.C
+$ LINK /TRACE/NOMAP/EXEC=RASTTOPNM.EXE RASTTOPNM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=XWDTOPNM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) XWDTOPNM.C
+$ LINK /TRACE/NOMAP/EXEC=XWDTOPNM.EXE XWDTOPNM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=GIFTOPNM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) GIFTOPNM.C
+$ LINK /TRACE/NOMAP/EXEC=GIFTOPNM.EXE GIFTOPNM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMCOMP.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMCOMP.C
+$ LINK /TRACE/NOMAP/EXEC=PNMCOMP.EXE PNMCOMP.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMTOSGI.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMTOSGI.C
+$ LINK /TRACE/NOMAP/EXEC=PNMTOSGI.EXE PNMTOSGI.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=SGITOPNM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) SGITOPNM.C
+$ LINK /TRACE/NOMAP/EXEC=SGITOPNM.EXE SGITOPNM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMTOSIR.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMTOSIR.C
+$ LINK /TRACE/NOMAP/EXEC=PNMTOSIR.EXE PNMTOSIR.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=SIRTOPNM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) SIRTOPNM.C
+$ LINK /TRACE/NOMAP/EXEC=SIRTOPNM.EXE SIRTOPNM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMNLFILT.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMNLFILT.C
+$ LINK /TRACE/NOMAP/EXEC=PNMNLFILT.EXE PNMNLFILT.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMPAD.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMPAD.C
+$ LINK /TRACE/NOMAP/EXEC=PNMPAD.EXE PNMPAD.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=ZEISSTOPNM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) ZEISSTOPNM.C
+$ LINK /TRACE/NOMAP/EXEC=ZEISSTOPNM.EXE ZEISSTOPNM.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMGAMMA.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMGAMMA.C
+$ LINK /TRACE/NOMAP/EXEC=PNMGAMMA.EXE PNMGAMMA.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMROTATE.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMROTATE.C
+$ LINK /TRACE/NOMAP/EXEC=PNMROTATE.EXE PNMROTATE.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMSHEAR.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) PNMSHEAR.C
+$ LINK /TRACE/NOMAP/EXEC=PNMSHEAR.EXE PNMSHEAR.obj,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=TIFFTOPNM.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF,"BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) tifftopnm.c
+$ LINK /TRACE/NOMAP/EXEC=TIFFTOPNM.EXE tifftopnm,[-.libtiff]libtiff.olb/Library,[-]PBMplusSHR.OPT/Option
+$ CC /NOLIST/OBJECT=PNMTOTIFF.OBJ /Define = (PPM,PGM,PBM,PBMPLUS_RAWBITS,LIBTIFF,"BSDTYPES=1","USE_VARARGS=1","USE_PROTOTYPES=0","USE_CONST",__STDC__) /Include_Directory = ([-],[-.ppm],[-.pgm],[-.pbm],[-.libtiff]) pnmtotiff.c
+$ LINK /TRACE/NOMAP/EXEC=PNMTOTIFF.EXE pnmtotiff,[-.libtiff]libtiff.olb/Library,[-]PBMplusSHR.OPT/Option
+$ Set Protection = (System:RWE, Owner:RWE, Group:RE, World:RE) *.exe
+$!
+$!		Install the binaries in a separate directory
+$!
+$ Set Default PBMplus_Dir
+$ If F$Parse ("PBMplus_Root:[Exe]") .eqs. "" Then Create /Directory PBMplus_Root:[Exe]
+$ Set Default PBMplus_Root:[PBM]
+$ PBMPLUS_PATH = F$Element (0, "]", F$Environment ("DEFAULT")) - ".PBM" + ".]"
+$ If F$TrnLnm ("PBMplus_Root") .eqs. "" Then 	Define /Translation_Attributes = Concealed PBMplus_Root "''PBMPLUS_PATH'"
+$ If F$TrnLnm ("PBMplus_Dir") .eqs. "" Then 	Define PBMplus_Dir PBMplus_Root:[000000]
+$ If F$TrnLnm ("Sys") .eqs. "" Then Define Sys Sys$Library
+$ Set Protection = (System:RWE, Owner:RWED, Group:RE, World:RE) *.exe
+$ Rename /Log *.exe PBMplus_Root:[Exe]
+$ Set Protection = Owner:RWE PBMplus_Root:[Exe]*.exe
+$ Set Default PBMplus_Root:[PGM]
+$ PBMPLUS_PATH = F$Element (0, "]", F$Environment ("DEFAULT")) - ".PGM" + ".]"
+$ If F$TrnLnm ("PBMplus_Root") .eqs. "" Then 	Define /Translation_Attributes = Concealed PBMplus_Root "''PBMPLUS_PATH'"
+$ If F$TrnLnm ("PBMplus_Dir") .eqs. "" Then 	Define PBMplus_Dir PBMplus_Root:[000000]
+$ If F$TrnLnm ("Sys") .eqs. "" Then Define Sys Sys$Library
+$ Set Protection = (System:RWE, Owner:RWED, Group:RE, World:RE) *.exe
+$ Rename /Log *.exe PBMplus_Root:[Exe]
+$ Set Protection = Owner:RWE PBMplus_Root:[Exe]*.exe
+$ Set Default PBMplus_Root:[PPM]
+$ PBMPLUS_PATH = F$Element (0, "]", F$Environment ("DEFAULT")) - ".PPM" + ".]"
+$ If F$TrnLnm ("PBMplus_Root") .eqs. "" Then 	Define /Translation_Attributes = Concealed PBMplus_Root "''PBMPLUS_PATH'"
+$ If F$TrnLnm ("PBMplus_Dir") .eqs. "" Then 	Define PBMplus_Dir PBMplus_Root:[000000]
+$ If F$TrnLnm ("Sys") .eqs. "" Then Define Sys Sys$Library
+$ Set Protection = (System:RWE, Owner:RWED, Group:RE, World:RE) *.exe
+$ Rename /Log *.exe PBMplus_Root:[Exe]
+$ Set Protection = Owner:RWE PBMplus_Root:[Exe]*.exe
+$ Set Default PBMplus_Root:[PNM]
+$ PBMPLUS_PATH = F$Element (0, "]", F$Environment ("DEFAULT")) - ".PNM" + ".]"
+$ If F$TrnLnm ("PBMplus_Root") .eqs. "" Then 	Define /Translation_Attributes = Concealed PBMplus_Root "''PBMPLUS_PATH'"
+$ If F$TrnLnm ("PBMplus_Dir") .eqs. "" Then 	Define PBMplus_Dir PBMplus_Root:[000000]
+$ If F$TrnLnm ("Sys") .eqs. "" Then Define Sys Sys$Library
+$ Set Protection = (System:RWE, Owner:RWED, Group:RE, World:RE) *.exe
+$ Rename /Log *.exe PBMplus_Root:[Exe]
+$ Set Protection = Owner:RWE PBMplus_Root:[Exe]*.exe
+$!
+$!		Build the VMS Help Library
+$!
+$ Set Default PBMplus_Dir
+$ Library /Create /Help PBMPLUS.HLB PBMPLUS.HLP
+$ Set File /Truncate PBMPLUS.HLB
+$ Exit
diff --git a/vms/Make_PBMplusShr.com b/vms/Make_PBMplusShr.com
new file mode 100755
index 00000000..8e43efb9
--- /dev/null
+++ b/vms/Make_PBMplusShr.com
@@ -0,0 +1,280 @@
+$!
+$! Make shareable image out of PBMPLUS libraries.  This command procedure
+$! takes no arguments, but must be placed in the top-level PBMPLUS directory.
+$!
+$! It uses the following input library files:
+$!
+$!	[.PBM]LIBPBM.OLB,[.PGM]LIBPGM.OLB,[.PPM]LIBPPM.OLB,[.PNM]LIBPNM.OLB
+$!
+$! This procedure generates the following files if missing or out-of-date:
+$!
+$!	TRANSVEC.OBJ	Object file containing transfer vector for PBMPLUSSHR.
+$!	PBMPLUSSHR.EXE	Shareable image file for PBM libraries.
+$!	PBMPLUSSHR.OPT	Linker options file for linking utility program against
+$!			the PBMPLUSSHR.EXE shareable image.
+$!
+$ instruct = 0
+$ proc = f$environment("PROCEDURE")
+$ proc_cdt = f$cvtime(f$file(proc,"CDT"))
+$ if f$search("TRANSVEC.OBJ") .EQS. "" THEN GOTO NEW_TRANSVEC
+$ if f$cvtime(f$file("TRANSVEC.OBJ","CDT")) .GTS. PROC_CDT THEN GOTO TRANSVEC_DONE
+$ NEW_TRANSVEC:
+$ instruct = 1
+$ Write SYS$Output "Making new transvec.obj..."
+$ Macro /NoList /Object = TRANSVEC.OBJ Sys$Input
+; PMBPLUS_TRANSFER_VECTOR
+; This routine defines a transfer vector for use in creating shareable image
+;
+; define macro to make transfer vector entry for a given routine.  Entry mask 
+; is obtained from routine we are transfering to.  Jump to word past entry 
+; since these are VAX procedures (written in FORTRAN).
+;
+	.MACRO TRANSFER_ENTRY routine
+;
+	.TRANSFER routine
+	.MASK	  routine
+	JMP	  routine + 2
+;
+	.ENDM TRANSFER_ENTRY
+;
+	.TITLE PBMPLUS_TRANSFER_VECTOR
+	.IDENT /01/
+	.PSECT PBMPLUS_XVEC PIC,USR,CON,REL,LCL,SHR,EXE,RD,NOWRT,NOVEC
+;
+;	Simply go through iap procedures and declare transfer vector
+;	entry points for them.  New procedure must be added to the END
+;	of this list.
+;
+TRANSFER_VECTOR:
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;Library LIBPBM
+;Module ARGPROC
+	TRANSFER_ENTRY BACKGROUND_PROCESS 
+	TRANSFER_ENTRY GETOPT
+	TRANSFER_ENTRY GETREDIRECTION
+	TRANSFER_ENTRY SET_OUTFILE_BINARY
+
+;Module LIBPBM1
+	TRANSFER_ENTRY PBM_INIT
+	TRANSFER_ENTRY PM_ALLOCARRAY
+	TRANSFER_ENTRY PM_ALLOCROW
+	TRANSFER_ENTRY PM_BITSTOMAXVAL
+	TRANSFER_ENTRY PM_CLOSE
+	TRANSFER_ENTRY PM_ERROR                         
+	TRANSFER_ENTRY PM_FREEARRAY                     
+	TRANSFER_ENTRY PM_FREEROW
+	TRANSFER_ENTRY PM_INIT                          
+	TRANSFER_ENTRY PM_KEYMATCH                      
+	TRANSFER_ENTRY PM_MAXVALTOBITS                  
+	TRANSFER_ENTRY PM_MESSAGE
+	TRANSFER_ENTRY PM_OPENR                         
+	TRANSFER_ENTRY PM_OPENW                         
+	TRANSFER_ENTRY PM_PERROR                        
+	TRANSFER_ENTRY PM_READBIGLONG
+	TRANSFER_ENTRY PM_READBIGSHORT                  
+	TRANSFER_ENTRY PM_READLITTLELONG                
+	TRANSFER_ENTRY PM_READLITTLESHORT               
+	TRANSFER_ENTRY PM_USAGE
+	TRANSFER_ENTRY PM_WRITEBIGLONG                  
+	TRANSFER_ENTRY PM_WRITEBIGSHORT                 
+	TRANSFER_ENTRY PM_WRITELITTLELONG               
+	TRANSFER_ENTRY PM_WRITELITTLESHORT
+	TRANSFER_ENTRY PM_READ_UNKNOWN_SIZE
+
+;Module LIBPBM2
+	TRANSFER_ENTRY PBM_READMAGICNUMBER              
+	TRANSFER_ENTRY PBM_READPBM                      
+	TRANSFER_ENTRY PBM_READPBMINIT                  
+	TRANSFER_ENTRY PBM_READPBMINITREST
+	TRANSFER_ENTRY PBM_READPBMROW
+
+;Module LIBPBM3
+	TRANSFER_ENTRY PBM_WRITEPBM                     
+	TRANSFER_ENTRY PBM_WRITEPBMINIT                 
+	TRANSFER_ENTRY PBM_WRITEPBMROW
+
+;Module LIBPBM4
+	TRANSFER_ENTRY PBM_GETC                         
+	TRANSFER_ENTRY PBM_GETINT                       
+	TRANSFER_ENTRY PBM_GETRAWBYTE
+
+;Module LIBPBM5
+	TRANSFER_ENTRY PBM_DEFAULTFONT                  
+	TRANSFER_ENTRY PBM_DISSECTFONT                  
+	TRANSFER_ENTRY PBM_DUMPFONT
+	TRANSFER_ENTRY PBM_LOADFONT
+	TRANSFER_ENTRY PBM_LOADBDFFONT
+	TRANSFER_ENTRY MK_ARGVN
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;Library LIBPGM
+;Module LIBPGM1
+	TRANSFER_ENTRY PGM_INIT                         
+	TRANSFER_ENTRY PGM_READPGM                      
+	TRANSFER_ENTRY PGM_READPGMINIT                  
+	TRANSFER_ENTRY PGM_READPGMINITREST
+	TRANSFER_ENTRY PGM_READPGMROW
+
+;Module LIBPGM2
+	TRANSFER_ENTRY PGM_WRITEPGM                     
+	TRANSFER_ENTRY PGM_WRITEPGMINIT                 
+	TRANSFER_ENTRY PGM_WRITEPGMROW
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Library LIBPPM
+;
+;Module LIBPPM1
+	TRANSFER_ENTRY PPM_INIT                         
+	TRANSFER_ENTRY PPM_READPPM                      
+	TRANSFER_ENTRY PPM_READPPMINIT                  
+	TRANSFER_ENTRY PPM_READPPMINITREST
+	TRANSFER_ENTRY PPM_READPPMROW
+
+;Module LIBPPM2
+	TRANSFER_ENTRY PPM_WRITEPPM                     
+	TRANSFER_ENTRY PPM_WRITEPPMINIT                 
+	TRANSFER_ENTRY PPM_WRITEPPMROW
+
+;Module LIBPPM3
+	TRANSFER_ENTRY PPM_ADDTOCOLORHASH               
+	TRANSFER_ENTRY PPM_ADDTOCOLORHIST               
+	TRANSFER_ENTRY PPM_ALLOCCOLORHASH               
+	TRANSFER_ENTRY PPM_COLORHASHTOCOLORHIST
+	TRANSFER_ENTRY PPM_COLORHISTTOCOLORHASH         
+	TRANSFER_ENTRY PPM_COMPUTECOLORHASH             
+	TRANSFER_ENTRY PPM_COMPUTECOLORHIST             
+	TRANSFER_ENTRY PPM_FREECOLORHASH
+	TRANSFER_ENTRY PPM_FREECOLORHIST                
+	TRANSFER_ENTRY PPM_LOOKUPCOLOR
+
+;Module LIBPPM4
+	TRANSFER_ENTRY PPM_COLORNAME                    
+	TRANSFER_ENTRY PPM_PARSECOLOR
+
+;Module LIBPPM5
+	TRANSFER_ENTRY PPMD_CIRCLE                      
+	TRANSFER_ENTRY PPMD_FILL                        
+	TRANSFER_ENTRY PPMD_FILLEDRECTANGLE             
+	TRANSFER_ENTRY PPMD_FILL_DRAWPROC
+	TRANSFER_ENTRY PPMD_FILL_INIT                   
+	TRANSFER_ENTRY PPMD_LINE                        
+	TRANSFER_ENTRY PPMD_POINT_DRAWPROC              
+	TRANSFER_ENTRY PPMD_POLYSPLINE
+	TRANSFER_ENTRY PPMD_SETLINECLIP                 
+	TRANSFER_ENTRY PPMD_SETLINETYPE                 
+	TRANSFER_ENTRY PPMD_SPLINE3
+
+;Module BITIO
+	TRANSFER_ENTRY PM_BITINIT
+	TRANSFER_ENTRY PM_BITFINI
+	TRANSFER_ENTRY PM_BITREAD
+	TRANSFER_ENTRY PM_BITWRITE
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Library LIBPNM
+;Module LIBPNM1
+	TRANSFER_ENTRY PNM_INIT                         
+	TRANSFER_ENTRY PNM_READPNM                      
+	TRANSFER_ENTRY PNM_READPNMINIT                  
+	TRANSFER_ENTRY PNM_READPNMROW
+
+;Module LIBPNM2
+	TRANSFER_ENTRY PNM_WRITEPNM                     
+	TRANSFER_ENTRY PNM_WRITEPNMINIT                 
+	TRANSFER_ENTRY PNM_WRITEPNMROW
+
+;Module LIBPNM3
+	TRANSFER_ENTRY PNM_BACKGROUNDXEL                
+	TRANSFER_ENTRY PNM_BACKGROUNDXELROW             
+	TRANSFER_ENTRY PNM_BLACKXEL                     
+	TRANSFER_ENTRY PNM_INVERTXEL
+	TRANSFER_ENTRY PNM_PROMOTEFORMAT                
+	TRANSFER_ENTRY PNM_PROMOTEFORMATROW             
+	TRANSFER_ENTRY PNM_WHITEXEL
+
+;Module LIBPNM4
+	TRANSFER_ENTRY MEM_CREATE                       
+	TRANSFER_ENTRY MEM_FREE                         
+	TRANSFER_ENTRY PR_DUMP                          
+	TRANSFER_ENTRY PR_LOAD_COLORMAP
+	TRANSFER_ENTRY PR_LOAD_HEADER                   
+	TRANSFER_ENTRY PR_LOAD_IMAGE
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;	allocate extra space to allow for code modifications without changing 
+;	the size of the shared image.
+;
+	.BLKB 2048-<.-TRANSFER_VECTOR>	; Reserve 4 pages.
+;
+	.END
+
+$ TRANSVEC_DONE:
+$!
+$!   Create new options file if needed.
+$!
+$ if f$search("PBMPLUSSHR.OPT") .EQS. "" THEN GOTO NEW_OPTFILE
+$ if f$cvtime(f$file("PBMPLUSSHR.OPT","CDT")) .GTS. PROC_CDT THEN GOTO OPTFILE_DONE
+$ NEW_OPTFILE:
+$ instruct = 1
+$ write sys$output "Making new pbmplusshr.opt..."
+$ CREATE PBMPLUSSHR.OPT
+PBMplusShr /Share
+Sys$Share:VAXCRTL /Share
+PSECT_ATTR = ARGPROC_VERSION,GBL,NOSHR
+PSECT_ATTR = OPTARG,GBL,NOSHR
+PSECT_ATTR = OPTERR,GBL,NOSHR
+PSECT_ATTR = OPTIND,GBL,NOSHR
+PSECT_ATTR = OPTOPT,GBL,NOSHR
+PSECT_ATTR = PGM_PBMMAXVAL,GBL,NOSHR
+PSECT_ATTR = PNM_PBMMAXVAL,GBL,NOSHR
+PSECT_ATTR = PPM_PBMMAXVAL,GBL,NOSHR
+$!
+$ OPTFILE_DONE:
+$!
+$ if f$search("PBMPLUSSHR.EXE") .EQS. "" THEN GOTO NEW_SHAREABLE
+$ EXE_CDT = f$cvtime(f$file_attributes("PBMPLUSSHR.EXE","CDT"))
+$ if EXE_CDT .LTS. PROC_CDT THEN GOTO NEW_SHAREABLE
+$ if f$cvtime(f$file("[.PBM]LIBPBM.OLB","RDT")) .GTS. EXE_CDT THEN GOTO NEW_SHAREABLE
+$ if f$cvtime(f$file("[.PGM]LIBPGM.OLB","RDT")) .GTS. EXE_CDT THEN GOTO NEW_SHAREABLE
+$ if f$cvtime(f$file("[.PPM]LIBPPM.OLB","RDT")) .GTS. EXE_CDT THEN GOTO NEW_SHAREABLE
+$ if f$cvtime(f$file("[.PNM]LIBPNM.OLB","RDT")) .GTS. EXE_CDT THEN GOTO NEW_SHAREABLE
+$ GOTO SHAREABLE_DONE
+$ NEW_SHAREABLE:
+$ instruct = 1
+$ write sys$output "Making new pbmplusshr.exe..."
+$ Link /Map = PBMPLUSHSR.MAP /Share = SYS$DISK:[]PBMPLUSSHR.EXE Sys$Input/Option
+COLLECT=FIRST,PBMPLUS_XVEC
+COLLECT=GLOBALS1,PGM_PBMMAXVAL,PNM_PBMMAXVAL,PPM_PBMMAXVAL
+COLLECT=GLOBALS2,ARGPROC_VERSION,OPTARG,OPTERR,OPTIND,OPTOPT
+
+TRANSVEC.OBJ
+[.PBM]LIBPBM/LIB,[.PGM]LIBPGM/LIB,[.PPM]LIBPPM/LIB
+[.PNM]LIBPNM/LIB,SYS$SHARE:VAXCRTL/SHARE
+
+
+UNSUPPORTED = 1			! force demand zero pages
+GSMATCH=LEQUAL,2,1		! Major ID = 2, minor ID = 2
+
+PSECT_ATTR = ARGPROC_VERSION,NOSHR
+PSECT_ATTR = OPTARG,NOSHR
+PSECT_ATTR = OPTERR,NOSHR
+PSECT_ATTR = OPTIND,NOSHR
+PSECT_ATTR = OPTOPT,NOSHR
+PSECT_ATTR = PGM_PBMMAXVAL,NOSHR
+PSECT_ATTR = PNM_PBMMAXVAL,NOSHR
+PSECT_ATTR = PPM_PBMMAXVAL,NOSHR
+!PSECT_ATTR = ,LCL,NOSHR
+$!
+$ SHAREABLE_DONE:
+$ if .NOT. instruct then write sys$output "All PBMPLUSSHR files up to date."
+$ if .NOT. instruct then exit $status
+$ create sys$output
+
+	Define the logical name PBMPLUSSHR as "disk:[dir]PBMPLUSSHR", where
+	disk and [dir] are the disk and directory containing the
+	shareable image PBMPLUSSHR.EXE and linker options file PBMPLUSSHR.OPT.
+
+	You can then link an executable against the image with the command
+
+	    LINK program.OBJ,PBMplusShr/Option
+
+$ exit $status
diff --git a/vms/NetPBM.TeX b/vms/NetPBM.TeX
new file mode 100644
index 00000000..86a9abc8
--- /dev/null
+++ b/vms/NetPBM.TeX
@@ -0,0 +1,12115 @@
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex (version 1.04, July 15, 1991)
+% on Thu Dec 12 09:12:27 1991
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+
+\documentstyle[troffman,twoside]{article}
+\begin{document}
+%
+% input file: pbmplus.1
+%
+\phead{PBMPLUS}{1L}{13 October 1993}{}{}
+
+\shead{NAME}
+pbmplus - enhanced portable bitmap toolkit
+
+\shead{DESCRIPTION}
+The {\it pbmplus} toolkit allows conversions between image files of
+different format.  By means of using common intermediate formats, only
+$2 \times N$ conversion filters are required to support $N$ distinct formats,
+instead of the $N^2$ which would be required to convert directly between
+any one format and any other.  The package also includes simple tools for
+manipulating portable bitmaps.
+
+The package consists of four upwardly compatible sections:
+\begin{TPlist}{pbm}
+\item[{pbm}]
+Supports monochrome bitmaps (1 bit per pixel).
+\item[{pgm}]
+Supports grayscale images.  Reads either {\it pbm} or {\it pgm} formats
+and writes {\it pgm} format.
+\item[{ppm}]
+Supports full-color images.  Reads either {\it pbm}, {\it pgm}, or {\it
+ppm} formats, writes {\it ppm} format.
+\item[{pnm}]
+Supports content-independent manipulations on any of the three formats
+listed above, as well as external formats having multiple types.  Reads
+either {\it pbm}, {\it pgm}, or {\it ppm} formats, and generally writes
+the same type as it read (whenever a {\it pnm} tool makes an exception
+and ``promotes'' a file to a higher format, it informs the user).
+\end{TPlist}
+
+\shead{DESCRIPTION OF CONTENTS}
+\begin{TPlist}{{\it cmuwmtopbm}}
+\item[{{\it atktopbm}}]
+convert Andrew Toolkit raster object to portable bitmap
+\item[{{\it brushtopbm}}]
+convert Xerox doodle brushes to portable bitmap
+\item[{{\it cmuwmtopbm}}]
+convert CMU window manager format to portable bitmap
+\item[{{\it g3topbm}}]
+convert Group 3 FAX to portable bitmap
+\item[{{\it icontopbm}}]
+convert Sun icon to portable bitmap
+\item[{{\it gemtopbm}}]
+convert GEM .img format to portable bitmap
+\item[{{\it macptopbm}}]
+convert MacPaint to portable bitmap
+\item[{{\it mgrtopbm}}]
+convert MGR format to portable bitmap
+\item[{{\it pbmmerge}}]
+merge wrapper routine
+\item[{{\it pbmto10x}}]
+convert portable bitmap to Gemini 10x printer graphics
+\item[{{\it pbmtoascii}}]
+convert portable bitmap to ASCII graphic form
+\item[{{\it pbmtoatk}}]
+convert portable bitmap to Andrew Toolkit raster object
+\item[{{\it pbmtobbnbg}}]
+convert portable bitmap to BBN BitGraph graphics
+\item[{{\it pbmtocmuwm}}]
+convert portable bitmap to CMU window manager format
+\item[{{\it pbmtoepson}}]
+convert portable bitmap to Epson printer graphics
+\item[{{\it pbmtog3}}]
+convert portable bitmap to Group 3 FAX
+\item[{{\it pbmtogem}}]
+convert portable bitmap into GEM .img file
+\item[{{\it pbmtogo}}]
+convert portable bitmap to GraphOn graphics
+\item[{{\it pbmtoicon}}]
+convert portable bitmap to Sun icon
+\item[{{\it pbmtolj}}]
+convert portable bitmap to HP LaserJet graphics
+\item[{{\it pbmtomacp}}]
+convert portable bitmap to MacPaint
+\item[{{\it pbmtomgr}}]
+convert portable bitmap to MGR format
+\item[{{\it pbmtopi3}}]
+convert portable bitmap to Atari Degas .pi3
+\item[{{\it pbmtoplot}}]
+convert portable bitmap into Unix plot(5) file
+\item[{{\it pbmtoptx}}]
+convert portable bitmap to Printronix graphics
+\item[{{\it pbmtoxbm}}]
+convert portable bitmap to X11 bitmap
+\item[{{\it pbmtox10bm}}]
+convert portable bitmap to X10 bitmap
+\item[{{\it pbmtoybm}}]
+convert portable bitmap into Bennet Yee ``face'' file
+\item[{{\it pbmtozinc}}]
+convert portable bitmap to Zinc Interface Library icon
+\item[{{\it pbmlife}}]
+apply Conway's rules of Life to a portable bitmap
+\item[{{\it pbmmake}}]
+create a blank bitmap of a specified size and color
+\item[{{\it pbmmask}}]
+create a mask bitmap from a regular bitmap
+\item[{{\it pbmreduce}}]
+reduce a portable bitmap N times, using Floyd-Steinberg
+\item[{{\it pbmtext}}]
+render text into a bitmap
+\item[{{\it pbmupc}}]
+create a Universal Product Code bitmap
+\item[{{\it pi3topbm}}]
+convert Atari Degas .pi3 to portable bitmap
+\item[{{\it xbmtopbm}}]
+convert X10 or X11 bitmap to portable bitmap
+\item[{{\it ybmtopbm}}]
+convert Bennet Yee ``face'' file into portable bitmap
+\item[{{\it asciitopgm}}]
+convert ASCII graphics into a portable graymap
+\item[{{\it fstopgm}}]
+convert Usenix FaceSaver$^{\rm tm}$ format to portable graymap
+\item[{{\it hipstopgm}}]
+convert HIPS format to portable graymap
+\item[{{\it lispmtopgm}}]
+convert a Lisp Machine bitmap file into pgm format
+\item[{{\it pgmbentley}}]
+Bentleyize a portable graymap
+\item[{{\it pgmcrater}}]
+create cratered terrain by fractal forgery
+\item[{{\it pgmedge}}]
+edge-detect a portable graymap
+\item[{{\it pgmenhance}}]
+edge-enhance a portable graymap
+\item[{{\it pgmhist}}]
+print a histogram of the values in a portable graymap
+\item[{{\it pgmkernel}}]
+generate a convolution kernel
+\item[{{\it pgmmerge}}]
+merge wrapper routine
+\item[{{\it pgmnoise}}]
+create a graymap made up of white noise
+\item[{{\it pgmnorm}}]
+normalize contrast in a portable graymap
+\item[{{\it pgmoil}}]
+turn a portable graymap into an oil painting
+\item[{{\it pgmramp}}]
+generate a grayscale ramp
+\item[{{\it pgmtexture}}]
+calculate textural features on a portable graymap
+\item[{{\it pgmtofits}}]
+convert portable graymap to FITS format
+\item[{{\it pgmtofs}}]
+convert portable graymap to Usenix FaceSaver$^{\rm tm}$ format
+\item[{{\it pgmtolispm}}]
+convert a portable graymap into Lisp Machine format
+\item[{{\it pgmtopbm}}]
+convert portable graymap to portable bitmap
+\item[{{\it psidtopgm}}]
+convert PostScript ``image'' data to portable graymap
+\item[{{\it rawtopgm}}]
+convert raw grayscale bytes to portable graymap
+\item[{{\it giftoppm}}]
+convert GIF to portable pixmap
+\item[{{\it gouldtoppm}}]
+convert Gould scanner file to portable pixmap
+\item[{{\it ilbmtoppm}}]
+convert IFF ILBM to portable pixmap
+\item[{{\it imgtoppm}}]
+convert Img-whatnot to portable pixmap
+\item[{{\it mtvtoppm}}]
+convert MTV ray-tracer output to portable pixmap
+\item[{{\it pcxtoppm}}]
+convert PC Paintbrush format to portable pixmap
+\item[{{\it pgmtoppm}}]
+colorize a portable graymap into a portable pixmap
+\item[{{\it pi1toppm}}]
+convert Atari Degas .pi1 to portable pixmap
+\item[{{\it picttoppm}}]
+convert Macintosh PICT to portable pixmap
+\item[{{\it pjtoppm}}]
+convert HP PaintJet file to portable pixmap
+\item[{{\it ppmchange}}]
+change all pixels of one color to another in a portable pixmap
+\item[{{\it ppmdim}}]
+dim a portable pixmap down to total blackness
+\item[{{\it ppmdist}}]
+simplistic grayscale assignment for machine generated, color images
+\item[{{\it ppmdither}}]
+ordered dither for color images
+\item[{{\it ppmflash}}]
+brighten a picture up to complete white-out
+\item[{{\it ppmforge}}]
+fractal forgeries of clouds, planets, and starry skies
+\item[{{\it ppmhist}}]
+print a histogram of a portable pixmap
+\item[{{\it ppmmake}}]
+create a pixmap of a specified size and color
+\item[{{\it ppmmerge}}]
+merge wrapper routine
+\item[{{\it ppmmix}}]
+blend together two portable pixmaps
+\item[{{\it ppmpat}}]
+create a pretty pixmap
+\item[{{\it ppmquant}}]
+quantize colors down to a specified number
+\item[{{\it ppmquantall}}]
+script to run ppmquant on a set of pixmaps
+\item[{{\it ppmrelief}}]
+run a Laplacian Relief filter on a portable pixmap
+\item[{{\it ppmshift}}]
+shift lines of a portable pixmap left or right by a random amount
+\item[{{\it ppmspread}}]
+displace a portable pixmap's pixels by a random amount
+\item[{{\it ppmtoacad}}]
+convert portable pixmap to AutoCAD database or slide
+\item[{{\it ppmtoicr}}]
+convert portable pixmap to NCSA ICR graphics
+\item[{{\it ppmtoilbm}}]
+convert portable pixmap to IFF ILBM
+\item[{{\it ppmtomitsu}}]
+convert a portable pixmap to a Mitsubishi S340-10 file
+\item[{{\it ppmtopcx}}]
+convert portable pixmap to PC Paintbrush format
+\item[{{\it ppmtopgm}}]
+convert portable pixmap to portable graymap
+\item[{{\it ppmtopi1}}]
+convert portable pixmap to Atari Degas .pi1
+\item[{{\it ppmtopict}}]
+convert portable pixmap to Macintosh PICT
+\item[{{\it ppmtopj}}]
+convert portable pixmap to HP PaintJet file
+\item[{{\it ppmtopuzz}}]
+convert portable pixmap to X11 ``puzzle'' file
+\item[{{\it ppmtorgb3}}]
+separate a portable pixmap into three portable graymaps
+\item[{{\it ppmtosixel}}]
+convert portable pixmap to DEC sixel format
+\item[{{\it ppmtotga}}]
+convert portable pixmap to TrueVision Targa file
+\item[{{\it ppmtouil}}]
+convert portable pixmap to Motif UIL icon file
+\item[{{\it ppmtoxpm}}]
+convert portable pixmap to XPM format
+\item[{{\it ppmtoyuv}}]
+convert portable pixmap to Abekas YUV format
+\item[{{\it qrttoppm}}]
+convert QRT ray-tracer output to portable pixmap
+\item[{{\it rawtoppm}}]
+convert raw RGB bytes to portable pixmap
+\item[{{\it rgb3toppm}}]
+combine three portable graymaps into one portable pixmap
+\item[{{\it sldtoppm}}]
+convert an AutoCAD slide file into a portable pixmap
+\item[{{\it spctoppm}}]
+convert Atari compressed Spectrum to portable pixmap
+\item[{{\it sputoppm}}]
+convert Atari uncompressed Spectrum to portable pixmap
+\item[{{\it tgatoppm}}]
+convert TrueVision Targa file to portable pixmap
+\item[{{\it ximtoppm}}]
+convert Xim to portable pixmap
+\item[{{\it xvminitoppm}}]
+convert a XV ``thumbnail'' picture to PPM
+\item[{{\it xpmtoppm}}]
+convert XPM format to portable pixmap
+\item[{{\it yuvtoppm}}]
+convert Abekas YUV format to portable pixmap
+\item[{{\it anytopnm}}]
+script to attempt to convert any format to P?M
+\item[{{\it pnmalias}}]
+antialias a portable anyumap.
+\item[{{\it pnmarith}}]
+perform arithmetic on two portable anymaps
+\item[{{\it pnmcat}}]
+concatenate portable anymaps
+\item[{{\it pnmconvol}}]
+general MxN convolution on a portable anymap
+\item[{{\it pnmcrop}}]
+crop all like-colored borders off a portable anymap
+\item[{{\it pnmcut}}]
+select a rectangular region from a portable anymap
+\item[{{\it pnmdepth}}]
+change the maxval in a portable anymap
+\item[{{\it pnmenlarge}}]
+enlarge a portable anymap N times
+\item[{{\it pnmfile}}]
+describe a portable anymap
+\item[{{\it pnmflip}}]
+perform one or more flip operations on a portable anymap
+\item[{{\it pnmgamma}}]
+perform gamma correction on a portable anymap
+\item[{{\it pnmindex}}]
+script to build a visual index of a bunch of anymaps
+\item[{{\it pnminvert}}]
+invert a portable anymap
+\item[{{\it pnmmargin}}]
+script to add a margin to a portable anymap
+\item[{{\it pnmmerge}}]
+merge wrapper routine
+\item[{{\it pnmnoraw}}]
+force a portable anymap into ASCII format
+\item[{{\it pnmpaste}}]
+paste a rectangle into a portable anymap
+\item[{{\it pnmrotate}}]
+rotate a portable anymap
+\item[{{\it pnmscale}}]
+scale a portable anymap
+\item[{{\it pnmshear}}]
+shear a portable anymap
+\item[{{\it pnmsmooth}}]
+script that uses pnmconvol to smooth a anymap
+\item[{{\it pnmtile}}]
+replicate a portable anymap into a specified size
+\item[{{\it pnmtofits}}]
+convert a portable anymap into FITS format
+\item[{{\it pnmtogif}}]
+convert portable anymap to GIF
+\item[{{\it pnmtops}}]
+convert portable anymap to PostScript
+\item[{{\it pnmtorast}}]
+convert portable anymap to Sun raster file
+\item[{{\it pnmtotiff}}]
+convert portable anymap to TIFF file
+\item[{{\it pnmtoxwd}}]
+convert portable anymap to X11 window dump
+\item[{{\it fitstopnm}}]
+fitstopnm - convert a FITS file into a portable anymap
+\item[{{\it pstopnm}}]
+script to convert PS to portable anymap with GhostScript
+\item[{{\it rasttopnm}}]
+convert Sun raster file to portable anymap
+\item[{{\it tifftopnm}}]
+convert TIFF file to portable anymap
+\item[{{\it xwdtopnm}}]
+convert X10 or X11 window dump to portable anymap
+\end{TPlist}
+
+\shead{SEE ALSO}
+There are a number of related image-manipulation tools:
+\begin{TPlist}{{\it Fuzzy Pixmap Manipulation}}
+\item[{{\it IM Raster Toolkit}}]
+A portable and efficient format toolkit.  The format supports pixels of
+arbitrary channels, components, and bit precisions, while allowing
+compression and machine byte-order independence.  Support for image
+manipulation, digital halftoning, and format conversion.  Previously
+distributed on tape c/o the University of Waterloo (an {\it ftp}
+version is to appear later).  Author: Alan Paeth
+(awpaeth@watcgl.uwaterloo.ca).
+\item[{{\it Utah RLE Toolkit}}]
+Conversion and manipulation package, similar to {\it pbmplus}.  Available
+via {\it ftp} as {\it cs.utah.edu: pub/toolkit-2.0.tar.Z} and {\it
+ucsd.edu: graphics\-/utah-raster-toolkit.\-tar.Z}.
+\item[{{\it Fuzzy Pixmap Manipulation}}]
+Conversion and manipulation package, similar to {\it pbmplus}.  Version
+1.0 available via {\it ftp} as {\it
+nl.cs.cmu.edu: /usr/mlm/ftp/fbm.tar.Z}, {\it
+uunet.uu.net: pub/fbm.tar.Z}, and {\it ucsd.edu: graphics/fbm.tar.Z}.
+Author: Michael Mauldin (mlm@nl.cs.cmu.edu).
+\item[{{\it Img Software Set}}]
+Reads and writes its own image format, displaying results on an X11
+screen, and does some image manipulations.  Version 1.3 is available via
+{\it ftp} as {\it ftp.x.org: contrib/img\_1.3.tar.Z}, and {\it
+venera.isi.edu: pub/img\_1.3.tar.Z}, along with a large collection of
+color images.  Author: Paul Raveling (raveling@venera.isi.edu).
+\item[{{\it Xim}}]
+Reads and writes its own image format, displays on an X11 screen, and
+does some image manipulations.  Available in your nearest X11R4 source
+tree as {\it contrib/clients/xim}.  A more recent version is available
+via ftp from {\it video.mit.edu}.  It uses X11R4 and the OSF/Motif
+toolkit to provide basic interactive image manipulation and reads/writes
+GIF, xwd, xbm, tiff, rle, xim, and other formats.  Author: Philip R.
+Thompson.
+\item[{{\it xloadimage}}]
+Reads in images in various formats and displays them on an X11 screen.
+Available via {\it ftp} as {\it
+ftp.x.org: contrib/xloadimage$\ast$}, and in your nearest {\it
+comp.sources.\-x} archive.  Author: Jim Frost (madd@std.\-com).
+\item[{{\it TIFF\ Software}}]
+Nice portable library for reading and writing TIFF files, plus a few
+tools for manipulating them and reading other formats.  Available via
+{\it ftp} as {\it sgi.com: graphics/tiff/$\ast$.tar.Z} or {\it
+uunet.uu.net: graphics/tiff.tar.Z}.  Author: Sam Leffler
+(sam@sgi.com).
+\item[{{\it ALV}}]
+A Sun-specific image toolkit.  Version 2.0.6 posted to {\it
+comp.sources.\-sun} on 11 December 1989.  Also available via email to {\it
+alv-users-request @cs.bris.ac.uk}.
+\item[{{\it popi}}]
+An image manipulation language.  Version 2.1 posted to {\it
+comp.\-sources.\-misc} on 12 December 1989.
+\item[{{\it ImageMagick,}}]
+X11 package for display and interactive manipulation of images.  Uses its
+own format (MIFF), and includes some converters.  Available via {\it
+ftp} as {\it ftp.x.org: contrib/ImageMagick.$\ast$.tar.Z}.
+\item[{{\it Khoros}}]
+A huge (\~{}100 meg) graphical development environment based on X11R4.
+Components include a visual programming language, code generators for
+extending the visual language and adding new application packages to the
+system, an interactive user interface editor, an interactive image
+display package, an extensive library of image and signal processing
+routines, and 2D/3D plotting packages.  Available via {\it ftp} as
+{\it pprg.unm.edu: pub/khoros/$\ast$}.
+\item[{JPEG package}]
+JPEG is a a standardized compression method for full-color and gray-scale
+images of ``real-world'' scenes; this experimental package includes
+programs to compress gif and ppm format files to JPEG format ({\it
+cjpeg(1L)),} and to decompress them ({\it djpeg(1L)).} Available by {\it
+ftp} as {\it uunet.uu.net: graphics/jpeg/jpegsrc.v4.tar.Z}.
+\end{TPlist}
+
+libpbm(3), libpgm(3), libpnm(3), libppm(3), pbm(5), pgm(5), pnm(5),
+ppm(5), rasterfile(1)
+
+\shead{AUTHOR}
+Distribution of 10 December 1991.  \copyright 1989, 1991 by Jef Poskanzer.
+
+Feedback and questions are welcome. Please send them to:
+\begin{IPlist}
+\IPitem{{}}
+{\nofill
+        jef@netcom.com
+        jef@well.sf.ca.us
+        apple!well!jef
+\fill}
+\end{IPlist}
+
+When sending bug reports, always include the output from running any
+pbmplus program with the -version flag, including descriptions of the
+type of system you are on, the compiler you use, and whether you are
+using Makefiles or Imakefiles.
+
+When suggesting new formats or features, please include whatever
+documentation you have, and a uuencoded sample.  The response time will
+depend upon my schedule and the complexity of the task;  if you need it
+right away, or it is a complicated job, you might consider paying me.
+
+The Usenet newsgroup {\it alt.graphics.pixutils} is a forum for
+discussion of image conversion and editing packages.  Posting queries
+there may be better than mailing them to me, since it allows other people
+to help provide answers.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted, provided
+that the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.  This software is provided ``as is'' without express or
+implied warranty.  Thus, you may do what you want with this software.
+Build it into your package, steal code from it, whatever.  Just be sure
+to let people know where it came from.
+
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Feb  4 14:35:11 1994
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: asciitopgm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: asciitopgm.1
+%
+\phead{asciitopgm}{1}{26 December 1994}{}{}
+
+%.IX asciitopgm
+\shead{NAME}
+asciitopgm - convert ASCII graphics into a portable graymap
+\shead{SYNOPSIS}
+{\bf asciitopgm}
+{\it [}{\rm -d}
+{\rm divisor}{\it ]}
+{\it height width}
+{\rm [}{\it asciifile}{\rm ]}
+\shead{DESCRIPTION}
+Reads ASCII data as input.
+Produces a portable graymap with pixel values which are an approximation
+of the ``brightness'' of the ASCII characters,
+assuming black-on-white printing.
+In other words, a capital M is very dark, a period is ver light,
+and a space is white.
+Input lines which are fewer than
+{\it width}
+characters are automatically padded with spaces.
+\par
+The
+{\it divisor}
+argument is a floating-point number by which the output pixels are
+divided; the default value is 1.0.
+This can be used to adjust the brightness of the graymap:
+for example, if the image is too dim, reduce the divisor.
+\par
+In keeping with (I believe) Fortran line-printer conventions,
+input lines beginning with a + (plus) character are assumed
+to ``overstrike'' the previous line, allowing a larger range of gray values.
+\par
+This tool contradicts the message in the
+{\it pbmtoascii}
+manual: ``Note that there is no asciitopbm tool - this
+transformation is one-way.''
+\shead{BUGS}
+The table of ASCII-to-grey values is subject to interpretation,
+and, of course, depends on the typeface intended for the input.
+\shead{SEE ALSO}
+pbmtoascii(1), pgm(5)
+\shead{AUTHOR}
+Wilson H. Bent. Jr. (whb@usc.edu)
+%
+% end of input file: asciitopgm.1
+%--------------------------------------------------
+
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:07:54 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: atktopbm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: atktopbm.1
+%
+\phead{atktopbm}{1}{26 September 1991}{}{}
+
+%.IX atktopbm
+\shead{NAME}
+atktopbm - convert Andrew Toolkit raster object to portable bitmap
+\shead{SYNOPSIS}
+{\bf atktopbm}
+{\rm [}{\it atkfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads an Andrew Toolkit raster object as input.
+%.IX "Andrew Toolkit raster object"
+Produces a portable bitmap as output.
+\shead{SEE ALSO}
+pbmtoatk(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1991 by Bill Janssen.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: atktopbm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:35 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: bioradtopgm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: bioradtopgm.1
+%
+\phead{bioradtopgm}{1}{28 June 1993}{}{}
+
+%.IX bioradtopgm
+\shead{NAME}
+bioradtopgm - convert a Biorad confocal file into a portable graymap
+\shead{SYNOPSIS}
+{\bf bioradtopgm}
+{\rm [}{\bf -image\#}{\rm ]}
+{\rm [}{\it imagedata}{\rm ]}
+\shead{DESCRIPTION}
+Reads a Biorad confocal file as input.
+Produces a portable graymap as output.
+If the resulting image is upside down, run it through
+{\bf pnmflip\ -tb .}
+%.IX pnmflip
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -image\#}}
+\item[{{\bf -image\#}}]
+A Biorad image file may contain more than one image. With this flag,
+you can specify which image to extract (only one at a time). The first
+image in the file has number zero. If no
+image number is supplied, only information about the image size and
+the number of images in the input is printed out. No output is produced.
+\end{TPlist}
+
+\shead{BUGS}
+A Biorad image may be in word format. If PbmPlus is not compiled with the
+``BIGGRAYS'' flag, word files can not be converted. See the Makefile.
+\shead{SEE ALSO}
+pgm(5), pnmflip(1)
+\shead{AUTHORS}
+\copyright 1993 by Oliver Trepte (oliver@fysik4.kth.se).
+\nwl
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: bioradtopgm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:07 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: bmptoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: bmptoppm.1
+%
+\phead{bmptoppm}{1}{26 Oct 1992}{}{}
+
+%.IX bmptoppm
+\shead{NAME}
+bmptoppm -- convert a BMP file into a portable pixmap
+\shead{SYNOPSIS}
+{\bf bmptoppm}
+{\rm [}{\it bmpfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a Microsoft Windows or OS/2 BMP file as input.
+%.IX BMP
+Produces a portable pixmap as output.
+\shead{SEE ALSO}
+ppmtobmp(1),
+ppm(5)
+\shead{AUTHOR}
+\copyright 1992 by David W. Sanderson.
+% Permission to use, copy, modify, and distribute this software and
+% its documentation for any purpose and without fee is hereby granted,
+% provided that the above copyright notice appear in all copies and
+% that both that copyright notice and this permission notice appear in
+% supporting documentation.  This software is provided "as is" without
+% express or implied warranty.
+%
+% end of input file: bmptoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:07:54 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: brushtopbm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: brushtopbm.1
+%
+\phead{brushtopbm}{1}{28 August 1988}{}{}
+
+%.IX brushtopbm
+\shead{NAME}
+brushtopbm - convert a doodle brush file into a portable bitmap
+\shead{SYNOPSIS}
+{\bf brushtopbm}
+{\rm [}{\it brushfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a Xerox doodle brush file as input.
+%.IX "Xerox doodle brush format"
+Produces a portable bitmap as output.
+\par
+Note that there is currently no pbmtobrush tool.
+\shead{SEE ALSO}
+pbm(5)
+\shead{AUTHOR}
+\copyright 1988 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: brushtopbm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:07:55 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: cmuwmtopbm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: cmuwmtopbm.1
+%
+\phead{cmuwmtopbm}{1}{15 April 1989}{}{}
+
+%.IX cmuwmtopbm
+\shead{NAME}
+cmuwmtopbm - convert a CMU window manager bitmap into a portable bitmap
+\shead{SYNOPSIS}
+{\bf cmuwmtopbm}
+{\rm [}{\it cmuwmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a CMU window manager bitmap as input.
+%.IX "CMU window manager bitmap"
+Produces a portable bitmap as output.
+\shead{SEE ALSO}
+pbmtocmuwm(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: cmuwmtopbm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Mon Feb  7 08:49:58 1994
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: fitstopnm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: fitstopnm.1
+%
+\phead{fitstopnm}{1}{20 September 89}{}{}
+
+\shead{NAME}
+fitstopnm - convert a FITS file into a portable anymap
+\shead{SYNOPSIS}
+{\bf fitstopnm}
+{\rm [}{\bf -image}
+{\it N}{\rm ]}
+{\rm [}{\bf -noraw}{\rm ]}
+{\rm [}{\bf -scanmax}{\rm ]}
+{\rm [}{\bf -printmax}{\rm ]}
+{\rm [}{\bf -min}
+{\it f}{\rm ]}
+{\rm [}{\bf -max}
+{\it f}{\rm ]}
+{\rm [}{\it FITSfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a FITS file as input.
+%.IX FITS
+Produces a portable pixmap if the FITS file consists of 3 image planes
+(NAXIS = 3 and NAXIS3 = 3), a portable graymap if the FITS file
+consists of 2 image planes (NAXIS = 2), or whenever the 
+{\bf --image}
+flag is specified.
+The results may need to be flipped top for bottom; if so, just
+pipe the output through
+{\bf pnmflip -tb.}
+%.IX pnmflip
+\shead{OPTIONS}
+\par
+The
+{\bf -image}
+option is for FITS files with three axes.
+The assumption is that the third axis is for multiple images,
+and this option lets you select which one you want.
+\par
+Flags 
+{\bf -min}
+and 
+{\bf -max}
+can be used to override the min and max values as read from the FITS
+header or the image data if no DATAMIN and DATAMAX keywords are found.
+Flag
+{\bf -scanmax}
+can be used to force the program to scan the data even when DATAMIN
+and DATAMAX are found in the header. If 
+{\bf -printmax}
+is specified, the program will just print the min and max values and
+quit.
+Flag
+{\bf -noraw}
+can be used to force 
+the program to produce an ASCII portable anymap.
+\par
+The program will tell what kind of anymap is writing.
+All flags can be abbreviated to their shortest unique prefix.
+\shead{REFERENCES}
+FITS stands for Flexible Image Transport System.  A full description
+can be found in Astronomy \& Astrophysics Supplement Series 44 (1981),
+page 363.
+\shead{SEE ALSO}
+pnmtofits(1), pgm(5), pnmflip(1)
+\shead{AUTHOR}
+Copyright (C) 1989 by Jef Poskanzer, with modifications by 
+Daniel Briggs (dbriggs@nrao.edu) and Alberto
+Accomazzi (alberto@cfa.harvard.edu).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: fitstopnm.1
+%--------------------------------------------------
+
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:24 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: fstopgm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: fstopgm.1
+%
+\phead{fstopgm}{1}{06 April 89}{}{}
+
+%.IX fstopgm
+\shead{NAME}
+fstopgm - convert a Usenix FaceSaver(tm) file into a portable graymap
+\shead{SYNOPSIS}
+{\bf fstopgm}
+{\rm [}{\it fsfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a Usenix FaceSaver(tm) file as input.
+%.IX FaceSaver
+Produces a portable graymap as output.
+\par
+FaceSaver(tm) files sometimes have rectangular pixels.
+While
+{\it fstopgm}
+won't re-scale them into square pixels for you,
+it will give you the precise
+{\it pnmscale}
+command that will do the job.
+Because of this, reading a FaceSaver(tm) image is a two-step process.
+First you do:
+\nofill
+  fstopgm $>$ /dev/null
+\fill
+This will tell you whether you need to use
+{\it pnmscale.}
+Then use one of the following pipelines:
+\nofill
+  fstopgm $|$ pgmnorm
+  fstopgm $|$ pnmscale -whatever $|$ pgmnorm
+\fill
+To go to PBM, you want something more like one of these:
+\nofill
+  fstopgm $|$ pnmenlarge 3 $|$ pgmnorm $|$ pgmtopbm
+  fstopgm $|$ pnmenlarge 3 $|$ pnmscale $<$whatever$>$ $|$ pgmnorm $|$ pgmtopbm
+\fill
+You want to enlarge when going to a bitmap because otherwise you lose
+information; but enlarging by more than 3 does not look good.
+\par
+FaceSaver is a registered trademark of Metron Computerware Ltd. of
+Oakland, CA.
+\shead{SEE ALSO}
+pgmtofs(1), pgm(5), pgmnorm(1), pnmenlarge(1), pnmscale(1), pgmtopbm(1)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: fstopgm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:07:55 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: g3topbm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: g3topbm.1
+%
+\phead{g3topbm}{1}{02 October 1989}{}{}
+
+%.IX g3topbm
+\shead{NAME}
+g3topbm - convert a Group 3 fax file into a portable bitmap
+\shead{SYNOPSIS}
+{\bf g3topbm}
+{\rm [}{\bf -kludge}{\rm ]}
+{\rm [}{\bf -reversebits}{\rm ]}
+{\rm [}{\bf -stretch}{\rm ]}
+{\rm [}{\it g3file}{\rm ]}
+\shead{DESCRIPTION}
+Reads a Group 3 fax file as input.
+%.IX "Group 3 fax"
+%.IX fax
+Produces a portable bitmap as output.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -kludge}}
+\item[{{\bf -kludge}}]
+Tells
+{\it g3topbm}
+to ignore the first few lines of the file;
+sometimes fax files have some junk at the beginning.
+\item[{{\bf -reversebits}}]
+Tells
+{\it g3topbm}
+to interpret bits least-significant
+first, instead of the default most-significant first.
+Apparently some fax modems do it one way and others do it the other way.
+If you get a whole bunch of ``bad code word'' messages, try using this
+flag.
+\item[{{\bf -stretch}}]
+Tells
+{\it g3topbm}
+to stretch the image vertically by
+duplicating each row.
+This is for the low-quality transmission mode.
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{REFERENCES}
+The standard for Group 3 fax is defined in CCITT Recommendation T.4.
+\shead{BUGS}
+Probably.
+\shead{SEE ALSO}
+pbmtog3(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1989 by Paul Haeberli (paul@manray.sgi.com).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: g3topbm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:07:56 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: gemtopbm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: gemtopbm.1
+%
+\phead{gemtopbm}{1}{3 December 1988}{}{}
+
+%.IX gemtopbm
+\shead{NAME}
+gemtopbm - convert a GEM .img file into a portable bitmap
+\shead{SYNOPSIS}
+{\bf gemtopbm}
+{\rm [}{\bf -d}{\rm ]}
+{\it gemfile}
+\shead{DESCRIPTION}
+Reads a GEM .img file as input.
+%.IX GEM
+Produces a portable bitmap as output.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -d}}
+\item[{{\bf -d}}]
+Produce output describing the contents of the .img file.
+\end{TPlist}
+
+\shead{BUGS}
+Does not support file containing more than one plane.
+Can't read from standard input.
+\shead{SEE ALSO}
+pbmtogem(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1988 Diomidis D. Spinellis (dds@cc.ic.ac.uk).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted,
+% provided that the above copyright notice appear in all copies and that
+% both that copyright notice and this permission notice appear in
+% supporting documentation.
+%
+% end of input file: gemtopbm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:27 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: giftopnm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: giftopnm.1
+%
+\phead{giftopnm}{1}{29 September 1993}{}{}
+
+%.IX giftopnm
+\shead{NAME}
+giftopnm - convert a GIF file into a portable anymap
+\shead{SYNOPSIS}
+{\bf giftopnm}
+{\rm [}{\bf -verbose}{\rm ]}
+{\rm [}{\bf -comments}{\rm ]}
+{\rm [}{\bf -image}
+{\it N}{\rm ]}
+{\rm [}{\it GIFfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a GIF file for input, and outputs portable anymap.
+%.IX GIF
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -verbose}}
+\item[{{\bf -verbose}}]
+Produces verbose output about the GIF file input.
+\item[{{\bf -comments}}]
+Only outputs GIF89 comment fields.
+\item[{{\bf -image}}]
+Output the specified gif image from the
+input gif archive (where
+{\it N}
+is '1', '2', '20'...).
+Normally there is only one image per file, so this option
+is not needed.
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{BUGS}
+This does not correctly handle the Plain Text Extension of the GIF89
+standard, since I did not have any example input files containing them.
+\shead{SEE ALSO}
+ppmtogif(1), ppm(5)
+\shead{AUTHOR}
+\copyright 1993 by David Koblas (koblas@netcom.com)
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted,
+% provided that the above copyright notice appear in all copies and that
+% both that copyright notice and this permission notice appear in
+% supporting documentation.
+%
+% end of input file: giftopnm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:40 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: gouldtoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: gouldtoppm.1
+%
+\phead{gouldtoppm}{1}{20 May 1990}{}{}
+
+%.IX gouldtoppm
+\shead{NAME}
+gouldtoppm - convert Gould scanner file into a portable pixmap
+\shead{SYNOPSIS}
+{\bf gouldtoppm}
+{\rm [}{\it gouldfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a file produced by the Gould scanner as input.
+%.IX "Gould scanner"
+Produces a portable pixmap as output.
+\shead{SEE ALSO}
+ppm(5)
+\shead{AUTHOR}
+\copyright 1990 by Stephen Paul Lesniewski.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: gouldtoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:25 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: hipstopgm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: hipstopgm.1
+%
+\phead{hipstopgm}{1}{24 August 89}{}{}
+
+%.IX hipstopgm
+\shead{NAME}
+hipstopgm - convert a HIPS file into a portable graymap
+\shead{SYNOPSIS}
+{\bf hipstopgm}
+{\rm [}{\it hipsfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a HIPS file as input.
+%.IX HIPS
+Produces a portable graymap as output.
+\par
+If the HIPS file contains more than one frame in sequence, hipstopgm
+will concatenate all the frames vertically.
+\par
+HIPS is a format developed at the Human Information Processing
+Laboratory, NYU.
+\shead{SEE ALSO}
+pgm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: hipstopgm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:07:57 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: icontopbm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: icontopbm.1
+%
+\phead{icontopbm}{1}{31 August 1988}{}{}
+
+%.IX icontopbm
+\shead{NAME}
+icontopbm - convert a Sun icon into a portable bitmap
+\shead{SYNOPSIS}
+{\bf icontopbm}
+{\rm [}{\it iconfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a Sun icon as input.
+%.IX Sun
+%.IX "Sun icon format"
+Produces a portable bitmap as output.
+\shead{SEE ALSO}
+pbmtoicon(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1988 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: icontopbm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:41 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ilbmtoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ilbmtoppm.1
+%
+\phead{ilbmtoppm}{1}{20 June 1993}{}{}
+
+%.IX ilbmtoppm
+\shead{NAME}
+ilbmtoppm - convert an ILBM file into a portable pixmap
+\shead{SYNOPSIS}
+{\bf ilbmtoppm}
+{\rm [}{\bf -verbose}{\rm ]}
+{\rm [}{\it ILBMfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads an IFF ILBM file as input.
+%.IX "IFF"
+%.IX "ILBM"
+Produces a portable pixmap as output.
+Supported ILBM types are:
+\begin{TPlist}{Normal ILBMs with 1-16 planes.}
+\item[{Normal ILBMs with 1-16 planes.}]
+\item[{Amiga Extra-Halfbrite (EHB)}]
+\item[{Amiga Hold-and-Modify (HAM) with 3-16 planes.}]
+%.IX "HAM"
+\item[{24 bit.}]
+\item[{Color map (BMHD + CMAP chunk only, nPlanes = 0).}]
+\item[{Unofficial direct color.}]
+1-16 planes for each color component.
+\item[{Chunks used:}]
+BMHD, CMAP, CAMG (only HAM \& EHB flags used), BODY
+unofficial DCOL chunk to identify direct color ILBM
+\item[{Chunks ignored:}]
+GRAB, DEST, SPRT, CRNG, CCRT, CLUT, DPPV, DRNG, EPSF
+\item[{Other chunks (ignored but displayed in verbose mode):}]
+NAME, AUTH, (c), ANNO, DPI
+\item[{Unknown chunks are skipped.}]
+\end{TPlist}
+
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -verbose}}
+\item[{{\bf -verbose}}]
+Give some informaton about the ILBM file.
+\end{TPlist}
+
+\shead{BUGS}
+Probably.
+\shead{REFERENCES}
+Amiga ROM Kernel Reference Manual - Devices (3rd Ed.)
+Addison Wesley, ISBN 0--201--56775--X
+\shead{SEE ALSO}
+ppm(5), ppmtoilbm(1)
+\shead{AUTHORS}
+\copyright 1989 by Jef Poskanzer.
+\nwl
+Modified June 1993 by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ilbmtoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:41 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: imgtoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: imgtoppm.1
+%
+\phead{imgtoppm}{1}{05 September 1989}{}{}
+
+%.IX imgtoppm
+\shead{NAME}
+imgtoppm - convert an Img-whatnot file into a portable pixmap
+\shead{SYNOPSIS}
+{\bf imgtoppm}
+{\rm [}{\it imgfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads an Img-whatnot file as input.
+Produces a portable pixmap as output.
+The Img-whatnot toolkit is available for FTP on venera.isi.edu,
+along with numerous images in this format.
+%.IX Img-whatnot
+\shead{SEE ALSO}
+ppm(5)
+\shead{AUTHOR}
+Based on a simple conversion program posted to comp.graphics by Ed Falk.
+
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: imgtoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:19 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: libpbm.3
+
+\newpage
+%--------------------------------------------------
+% start of input file: libpbm.3
+%
+\phead{libpbm}{3}{}{}{}
+
+\shead{NAME}
+libpbm - functions to support portable bitmap programs
+\shead{SYNOPSIS}
+\def\Ss{\par\vspace{1.0\baselineskip}%.ft CW
+\nofill
+}
+\def\Se{\fill
+%.ft P
+\par\vspace{1.0\baselineskip}}
+\Ss
+\#include $<$pbm.h$>$
+cc ... libpbm.a
+\Se
+\shead{DESCRIPTION - PACKAGE-WIDE ROUTINES}
+%.SS KEYWORD MATCHING
+\Ss
+int pm\_keymatch( char* str, char* keyword, int minchars )
+\Se
+Does a case-insensitive match of
+{\bf str}
+against
+{\bf keyword}{\rm .}
+{\bf str}
+can be a leading sunstring of
+{\bf keyword}{\rm ,}
+but at least
+{\bf minchars}
+must be present.
+%.SS LOG BASE TWO
+\Ss
+int pm\_maxvaltobits( int maxval )
+int pm\_bitstomaxval( int bits )
+\Se
+Convert between a maxval and the minimum number of bits required
+to hold it.
+%.SS MESSAGES AND ERRORS
+\Ss
+void pm\_message( char* fmt, ... )
+\Se
+{\bf printf()}
+style routine to write an informational message.
+\Ss
+void pm\_error( char* fmt, ... )
+\Se
+{\bf printf()}
+style routine to write an error message and abort.
+\Ss
+void pm\_usage( char* usage )
+\Se
+Write a usage message.
+The string should indicate what arguments are to be provided to the program.
+%.SS GENERIC FILE MANAGEMENT
+\Ss
+FILE* pm\_openr( char* name )
+\Se
+Open the given file for reading, with appropriate error checking.
+A filename of ``-'' is taken as equivalent to stdin.
+\Ss
+FILE* pm\_openw( char* name )
+\Se
+Open the given file for writing, with appropriate error checking.
+\Ss
+void pm\_close( FILE* fp )
+\Se
+Close the file descriptor, with appropriate error checking.
+%.SS ENDIAN I/O
+\Ss
+int pm\_readbigshort( FILE* in, short* sP )
+int pm\_writebigshort( FILE* out, short s )
+int pm\_readbiglong( FILE* in, long* lP )
+int pm\_writebiglong( FILE* out, long l )
+int pm\_readlittleshort( FILE* in, short* sP )
+int pm\_writelittleshort( FILE* out, short s )
+int pm\_readlittlelong( FILE* in, long* lP )
+int pm\_writelittlelong( FILE* out, long l )
+\Se
+Routines to read and write short and long ints in either big- or
+little-endian byte order.
+\shead{DESCRIPTION - PBM-SPECIFIC ROUTINES}
+%.SS TYPES AND CONSTANTS
+\Ss
+typedef ... bit;
+\#define PBM\_WHITE ...
+\#define PBM\_BLACK ...
+\Se
+each
+{\bf bit}
+should contain only the values of
+{\bf PBM\_WHITE}
+or
+{\bf PBM\_BLACK}{\rm .}
+\Ss
+\#define PBM\_FORMAT ...
+\#define RPBM\_FORMAT ...
+\#define PBM\_TYPE PBM\_FORMAT
+\#define PBM\_FORMAT\_TYPE(f) ...
+\Se
+For distinguishing different file formats and types.
+%.SS INITIALIZATION
+\Ss
+void pbm\_init( int* argcP, char* argv[] )
+\Se
+All PBM programs must call this routine.
+%.SS MEMORY MANAGEMENT
+\Ss
+bit** pbm\_allocarray( int cols, int rows )
+\Se
+Allocate an array of bits.
+\Ss
+bit* pbm\_allocrow( int cols )
+\Se
+Allocate a row of the given number of bits.
+\Ss
+void pbm\_freearray( bit** bits, int rows )
+\Se
+Free the array allocated with
+{\bf pbm\_allocarray()}
+containing the given number
+of rows.
+\Ss
+void pbm\_freerow( bit* bitrow )
+\Se
+Free a row of bits.
+%.SS READING FILES
+\Ss
+void pbm\_readpbminit( FILE* fp, int* colsP, int* rowsP, int* formatP )
+\Se
+Read the header from a PBM file, filling in the rows, cols and format
+variables.
+\Ss
+void pbm\_readpbmrow( FILE* fp, bit* bitrow, int cols, int format )
+\Se
+Read a row of bits into the bitrow array.
+Format and cols were filled in by
+{\bf pbm\_readpbminit()}{\rm .}
+\Ss
+bit** pbm\_readpbm( FILE* fp, int* colsP, int* rowsP )
+\Se
+Read an entire bitmap file into memory, returning the allocated array and
+filling in the rows and cols variables.
+This function combines
+{\bf pbm\_readpbminit()}{\rm ,}
+{\bf pbm\_allocarray()}
+and
+{\bf pbm\_readpbmrow()}{\rm .}
+\Ss
+char* pm\_read\_unknown\_size( FILE* fp, long* nread )
+\Se
+Read an entire file or input stream of unknown size to a buffer.
+Allocate memory more memory as needed. The calling routine has
+to free the allocated buffer with
+{\bf free()}{\rm .}
+{\bf pm\_read\_unknown\_size()}
+returns a pointer to the allocated buffer. The
+{\bf nread}
+argument returns the number of bytes read.
+%.SS WRITING FILES
+\Ss
+void pbm\_writepbminit( FILE* fp, int cols, int rows, int forceplain )
+\Se
+Write the header for a portable bitmap file.
+The forceplain flag forces a plain-format file to be written, as opposed
+to a raw-format one.
+\Ss
+void pbm\_writepbmrow( FILE* fp, bit* bitrow, int cols, int forceplain )
+\Se
+Write a row from a portable bitmap.
+\Ss
+void pbm\_writepbm( FILE* fp, bit** bits, int cols, int rows, int forceplain )
+\Se
+Write the header and all data for a portable bitmap.
+This function combines
+{\bf pbm\_writepbminit()}
+and
+{\bf pbm\_writepbmrow()}{\rm .}
+\shead{SEE ALSO}
+libpgm(3), libppm(3), libpnm(3)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Tony Hansen and Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: libpbm.3
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:37 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: libpgm.3
+
+\newpage
+%--------------------------------------------------
+% start of input file: libpgm.3
+%
+\phead{libpgm}{3}{}{}{}
+
+\shead{NAME}
+libpgm - functions to support portable graymap programs
+\shead{SYNOPSIS}
+\def\Ss{\par\vspace{1.0\baselineskip}%.ft CW
+\nofill
+}
+\def\Se{\fill
+%.ft P
+\par\vspace{1.0\baselineskip}}
+\Ss
+\#include $<$pgm.h$>$
+cc ... libpgm.a libpbm.a
+\Se
+\shead{DESCRIPTION}
+%.SS TYPES AND CONSTANTS
+\Ss
+typedef ... gray;
+\#define PGM\_MAXMAXVAL ...
+extern gray pgm\_pbmmaxval;
+\Se
+Each
+{\bf gray}
+should contain only the values between
+{\bf 0}
+and
+{\bf PGM\_MAXMAXVAL}{\rm .}
+{\bf pgm\_pbmmaxval}
+is the maxval used when a PGM program reads a PBM file.
+Normally it is 1; however, for some programs, a larger value gives better
+results.
+\Ss
+\#define PGM\_FORMAT ...
+\#define RPGM\_FORMAT ...
+\#define PGM\_TYPE PGM\_FORMAT
+int PGM\_FORMAT\_TYPE( int format )
+\Se
+For distinguishing different file formats and types.
+%.SS INITIALIZATION
+\Ss
+void pgm\_init( int* argcP, char* argv[] )
+\Se
+All PGM programs must call this routine.
+%.SS MEMORY MANAGEMENT
+\Ss
+gray** pgm\_allocarray( int cols, int rows )
+\Se
+Allocate an array of grays.
+\Ss
+gray* pgm\_allocrow( int cols )
+\Se
+Allocate a row of the given number of grays.
+\Ss
+void pgm\_freearray( gray** grays, int rows )
+\Se
+Free the array allocated with
+{\bf pgm\_allocarray()}
+containing the given number
+of rows.
+\Ss
+void pgm\_freerow( gray* grayrow )
+\Se
+Free a row of grays.
+%.SS READING FILES
+\Ss
+void pgm\_readpgminit( FILE* fp, int* colsP, int* rowsP, gray* maxvalP, int* formatP )
+\Se
+Read the header from a PGM file, filling in the rows, cols, maxval and format
+variables.
+\Ss
+void pgm\_readpgmrow( FILE* fp, gray* grayrow, int cols, gray maxval, int format )
+\Se
+Read a row of grays into the grayrow array.
+Format, cols, and maxval were filled in by
+{\bf pgm\_readpgminit()}{\rm .}
+\Ss
+gray** pgm\_readpgm( FILE* fp, int* colsP, int* rowsP, gray* maxvalP )
+\Se
+Read an entire graymap file into memory, returning the allocated array and
+filling in the rows, cols and maxval variables.
+This function combines
+{\bf pgm\_readpgminit()}{\rm ,}
+{\bf pgm\_allocarray()}
+and
+{\bf pgm\_readpgmrow()}{\rm .}
+%.SS WRITING FILES
+\Ss
+void pgm\_writepgminit( FILE* fp, int cols, int rows, gray maxval, int forceplain )
+\Se
+Write the header for a portable graymap file.
+The forceplain flag forces a plain-format file to be written, as opposed
+to a raw-format one.
+\Ss
+void pgm\_writepgmrow( FILE* fp, gray* grayrow, int cols, gray maxval, int forceplain )
+\Se
+Write a row from a portable graymap.
+\Ss
+void pgm\_writepgm( FILE* fp, gray** grays, int cols, int rows, gray maxval, int forceplain )
+\Se
+Write the header and all data for a portable graymap.
+This function combines
+{\bf pgm\_writepgminit()}
+and
+{\bf pgm\_writepgmrow()}{\rm .}
+\shead{SEE ALSO}
+libpbm(3), libppm(3), libpnm(3)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Tony Hansen and Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: libpgm.3
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:34 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: libpnm.3
+
+\newpage
+%--------------------------------------------------
+% start of input file: libpnm.3
+%
+\phead{libpnm}{3}{}{}{}
+
+\shead{NAME}
+libpnm - functions to support portable anymap programs
+\shead{SYNOPSIS}
+\def\Ss{\par\vspace{1.0\baselineskip}%.ft CW
+\nofill
+}
+\def\Se{\fill
+%.ft P
+\par\vspace{1.0\baselineskip}}
+\Ss
+\#include $<$pnm.h$>$
+cc ... libpnm.a libppm.a libpgm.a libpbm.a
+\Se
+\shead{DESCRIPTION}
+%.SS TYPES AND CONSTANTS
+\Ss
+typedef ... xel;
+typedef ... xelval;
+\#define PNM\_MAXMAXVAL ...
+extern xelval pnm\_pbmmaxval;
+\Se
+Each
+{\bf xel}
+contains three
+{\bf xelval}{\rm s,}
+each of which should contain only the values between
+{\bf 0}
+and
+{\bf PNM\_MAXMAXVAL}{\rm .}
+{\bf pnm\_pbmmaxval}
+is the maxval used when a PNM program reads a PBM file.
+Normally it is 1; however, for some programs, a larger value gives better
+results.
+%.SS XEL MANIPULATIONS
+\Ss
+xelval PNM\_GET1( xel x )
+\Se
+This macro extracts a single value from an xel, when you know it's
+from a PBM or PGM file.
+When it's from a PPM file, use
+{\bf PPM\_GETR()}{\rm ,}
+{\bf PPM\_GETG()}{\rm ,}
+and
+{\bf PPM\_GETB()}{\rm .}
+\Ss
+void PNM\_ASSIGN1( xel x, xelval v )
+\Se
+This macro assigns a single value to an xel, when you know it's
+from a PBM or PGM file.
+When it's from a PPM file, use
+{\bf PPM\_ASSIGN()}{\rm .}
+\Ss
+int PNM\_EQUAL( xel x, xel y )
+\Se
+This macro checks two xels for equality.
+\Ss
+int PNM\_FORMAT\_TYPE( int format )
+\Se
+For distinguishing different file types.
+%.SS INITIALIZATION
+\Ss
+void pnm\_init( int* argcP, char* argv[] )
+\Se
+All PNM programs must call this routine.
+%.SS MEMORY MANAGEMENT
+\Ss
+xel** pnm\_allocarray( int cols, int rows )
+\Se
+Allocate an array of xels.
+\Ss
+xel* pnm\_allocrow( int cols )
+\Se
+Allocate a row of the given number of xels.
+\Ss
+void pnm\_freearray( xel** xels, int rows )
+\Se
+Free the array allocated with
+{\bf pnm\_allocarray()}
+containing the given number
+of rows.
+\Ss
+void pnm\_freerow( xel* xelrow )
+\Se
+Free a row of xels.
+%.SS READING FILES
+\Ss
+void pnm\_readpnminit( FILE* fp, int* colsP, int* rowsP, xelval* maxvalP, int* formatP )
+\Se
+Read the header from a PNM file, filling in the rows, cols, maxval and format
+variables.
+\Ss
+void pnm\_readpnmrow( FILE* fp, xel* xelrow, int cols, xelval maxval, int format )
+\Se
+Read a row of xels into the xelrow array.
+Format, cols, and maxval were filled in by
+{\bf pnm\_readpnminit()}{\rm .}
+\Ss
+xel** pnm\_readpnm( FILE* fp, int* colsP, int* rowsP, xelval* maxvalP, int* formatP )
+\Se
+Read an entire anymap file into memory, returning the allocated array and
+filling in the rows, cols, maxval, and format variables.
+This function combines
+{\bf pnm\_readpnminit()}{\rm ,}
+{\bf pnm\_allocarray()}
+and
+{\bf pnm\_readpnmrow()}{\rm .}
+Unlike the equivalent functions in PBM, PGM, and PPM, it returns the format
+so you can tell what type the file is.
+%.SS WRITING FILES
+\Ss
+void pnm\_writepnminit( FILE* fp, int cols, int rows, xelval maxval, int format, int forceplain )
+\Se
+Write the header for a portable anymap file.
+Unlike the equivalent functions in PBM, PGM, and PPM, you have to specify
+the output type.
+The forceplain flag forces a plain-format file to be written, as opposed
+to a raw-format one.
+\Ss
+void pnm\_writepnmrow( FILE* fp, xel* xelrow, int cols, xelval maxval, int format, int forceplain )
+\Se
+Write a row from a portable anymap.
+\Ss
+void pnm\_writepnm( FILE* fp, xel** xels, int cols, int rows, xelval maxval, int format, int forceplain )
+\Se
+Write the header and all data for a portable anymap.
+This function combines
+{\bf pnm\_writepnminit()}
+and
+{\bf pnm\_writepnmrow()}{\rm .}
+%.SS FORMAT PROMOTION
+\Ss
+void pnm\_promoteformatrow( xel* xelrow, int cols, xelval maxval, int format, xelval newmaxval, int newformat )
+\Se
+Promote a row of xels from one maxval and format to a new set.
+Used when combining multiple anymaps of different types - just
+take the max of the maxvals and the max of the formats, and
+promote them all to that.
+\Ss
+void pnm\_promoteformat( xel** xels, int cols, int rows, xelval maxval, int format, xelval newmaxval, int newformat )
+\Se
+Promote an entire anymap.
+%.SS XEL MANIPULATION
+\Ss
+xel pnm\_whitexel( xelval maxval, int format )
+xel pnm\_blackxel( xelval maxval, int format )
+\Se
+Return a white or black xel for the given maxval and format.
+\Ss
+void pnm\_invertxel( xel* x, xelval maxval, int format )
+\Se
+Invert an xel.
+\Ss
+xel pnm\_backgroundxelrow( xel* xelrow, int cols, xelval maxval, int format )
+\Se
+Figure out an appropriate background xel based on this row.
+\Ss
+xel pnm\_backgroundxel( xel** xels, int cols, int rows, xelval maxval, int format )
+\Se
+Figure out a background xel based on an entire anymap.
+This can do a slightly better job than
+{\bf pnm\_backgroundxelrow()}{\rm .}
+\shead{SEE ALSO}
+pbm(3), pgm(3), ppm(3)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Tony Hansen and Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: libpnm.3
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:12 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: libppm.3
+
+\newpage
+%--------------------------------------------------
+% start of input file: libppm.3
+%
+\phead{libppm}{3}{}{}{}
+
+\shead{NAME}
+libppm - functions to support portable pixmap programs
+\shead{SYNOPSIS}
+\def\Ss{\par\vspace{1.0\baselineskip}%.ft CW
+\nofill
+}
+\def\Se{\fill
+%.ft P
+\par\vspace{1.0\baselineskip}}
+\Ss
+\#include $<$ppm.h$>$
+cc ... libppm.a libpgm.a libpbm.a
+\Se
+\shead{DESCRIPTION}
+%.SS TYPES AND CONSTANTS
+\Ss
+typedef ... pixel;
+typedef ... pixval;
+\#define PPM\_MAXMAXVAL ...
+extern pixval ppm\_pbmmaxval;
+\Se
+Each
+{\bf pixel}
+contains three
+{\bf pixval}{\rm s,}
+each of which should contain only the values between
+{\bf 0}
+and
+{\bf PPM\_MAXMAXVAL}{\rm .}
+{\bf ppm\_pbmmaxval}
+is the maxval used when a PPM program reads a PBM file.
+Normally it is 1; however, for some programs, a larger value gives better
+results.
+\Ss
+\#define PPM\_FORMAT ...
+\#define RPPM\_FORMAT ...
+\#define PPM\_TYPE PPM\_FORMAT
+int PPM\_FORMAT\_TYPE( int format )
+\Se
+For distinguishing different file formats and types.
+\Ss
+pixval PPM\_GETR( pixel p )
+pixval PPM\_GETG( pixel p )
+pixval PPM\_GETB( pixel p )
+\Se
+These three macros retrieve the red, green or blue value from the given
+pixel.
+\Ss
+void PPM\_ASSIGN( pixel p, pixval red, pixval grn, pixval blu )
+\Se
+This macro assigns the given red, green and blue values to the pixel.
+\Ss
+int PPM\_EQUAL( pixel p, pixel q )
+\Se
+This macro checks two pixels for equality.
+\Ss
+void PPM\_DEPTH( pixel newp, pixel p, pixval oldmaxval, pixval newmaxval )
+\Se
+This macro scales the colors of pixel
+{\bf p}
+according the old and new maximum values and assigns the new values to
+{\bf newp}{\rm .}
+It is intended to make writing ppmtowhatever easier.
+\Ss
+float PPM\_LUMIN( pixel p )
+\Se
+This macro determines the luminance of the pixel
+{\bf p}{\rm .}
+%.SS MEMORY MANAGEMENT
+\Ss
+pixel** ppm\_allocarray( int cols, int rows )
+\Se
+Allocate an array of pixels.
+\Ss
+pixel* ppm\_allocrow( int cols )
+\Se
+Allocate a row of the given number of pixels.
+\Ss
+void ppm\_freearray( pixel** pixels, int rows )
+\Se
+Free the array allocated with
+{\bf ppm\_allocarray()}
+containing the given number
+of rows.
+\Ss
+void pbm\_freerow( pixel* pixelrow )
+\Se
+Free a row of pixels.
+%.SS READING PBM FILES
+\Ss
+void ppm\_readppminit( FILE* fp, int* colsP, int* rowsP, pixval* maxvalP, int* formatP )
+\Se
+Read the header from a PPM file, filling in the rows, cols, maxval and format
+variables.
+\Ss
+void ppm\_readppmrow( FILE* fp, pixel* pixelrow, int cols, pixval maxval, int format )
+\Se
+Read a row of pixels into the pixelrow array.
+Format, cols, and maxval were filled in by
+{\bf ppm\_readppminit()}{\rm .}
+\Ss
+pixel** ppm\_readppm( FILE* fp, int* colsP, int* rowsP, pixval* maxvalP )
+\Se
+Read an entire pixmap file into memory, returning the allocated array and
+filling in the rows, cols and maxval variables.
+This function combines
+{\bf ppm\_readppminit()}{\rm ,}
+{\bf ppm\_allocarray()}
+and
+{\bf ppm\_readppmrow()}{\rm .}
+%.SS WRITING FILES
+\Ss
+void ppm\_writeppminit( FILE* fp, int cols, int rows, pixval maxval, int forceplain )
+\Se
+Write the header for a portable pixmap file.
+The forceplain flag forces a plain-format file to be written, as opposed
+to a raw-format one.
+\Ss
+void ppm\_writeppmrow( FILE* fp, pixel* pixelrow, int cols, pixval maxval, int forceplain )
+\Se
+Write a row from a portable pixmap.
+\Ss
+void ppm\_writeppm( FILE* fp, pixel** pixels, int cols, int rows, pixval maxval, int forceplain )
+\Se
+Write the header and all data for a portable pixmap.
+This function combines
+{\bf ppm\_writeppminit()}
+and
+{\bf ppm\_writeppmrow()}{\rm .}
+%.SS COLOR NAMES
+\Ss
+pixel ppm\_parsecolor( char* colorname, pixval maxval )
+\Se
+Parses an ASCII color name into a pixel.
+The color can be specified in three ways.  One, as a name, assuming
+that a pointer to an X11-style color names file was compiled in.  Two,
+as an X11-style hexadecimal number: \#rgb, \#rrggbb, \#rrrgggbbb, or
+\#rrrrggggbbbb.  Three, as a triplet of decimal floating point numbers
+separated by commas: r.r,g.g,b.b.
+\Ss
+char* ppm\_colorname( pixel* colorP, pixval maxval, int hexok )
+\Se
+Returns a pointer to a string describing the given color.
+If the X11 color names file is available and the color appears in
+it, that name is returned.
+Otherwise, if the hexok flag is true then a hexadecimal colorspec
+is returned; if hexok is false and the X11 color names file is
+available, then the closest matching color is returned;
+otherwise, it's an error.
+\shead{SEE ALSO}
+pbm(3), pgm(3)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Tony Hansen and Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: libppm.3
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:25 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: lispmtopgm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: lispmtopgm.1
+%
+\phead{lispmtopgm}{1}{06 March 1990}{}{}
+
+%.IX lispmtopgm
+\shead{NAME}
+lispmtopgm - convert a Lisp Machine bitmap file into pgm format
+\shead{SYNOPSIS}
+{\bf lispmtopgm}
+{\rm [}{\it lispmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a Lisp Machine bitmap as input.
+%.IX "Lisp Machine bitmap"
+Produces a portable graymap as output.
+\par
+This is the file format written by the tv:write-bit-array-file function on
+TI Explorer and Symbolics lisp machines.
+\par
+Multi-plane bitmaps on lisp machines are color; but the lispm image file
+format does not include a color map, so we must treat it as a graymap 
+instead.  This is unfortunate.
+\shead{SEE ALSO}
+pgmtolispm(1), pgm(5)
+\shead{BUGS}
+The Lispm bitmap file format is a bit quirky;  Usually the image in the file
+has its width rounded up to the next higher multiple of 32, but not always.
+If the width is not a multiple of 32, we don't deal with it properly, but 
+because of the Lispm microcode, such arrays are probably not image data 
+anyway.
+\par
+Also, the lispm code for saving bitmaps has a bug, in that if you are writing a
+bitmap which is not mod32 across, the file may be up to 7 bits too short!  They
+round down instead of up, and we don't handle this bug gracefully.
+\par
+No color.
+\shead{AUTHOR}
+\copyright 1991 by Jamie Zawinski and Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: lispmtopgm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:07:57 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: macptopbm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: macptopbm.1
+%
+\phead{macptopbm}{1}{29 March 1989}{}{}
+
+%.IX macptopbm
+\shead{NAME}
+macptopbm - convert a MacPaint file into a portable bitmap
+\shead{SYNOPSIS}
+{\bf macptopbm}
+{\rm [}{\bf -extraskip}
+{\it N}{\rm ]}
+{\rm [}{\it macpfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a MacPaint file as input.
+%.IX MacPaint
+%.IX Macintosh
+Produces a portable bitmap as output.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -extraskip}}
+\item[{{\bf -extraskip}}]
+This flag is to get around a problem with some methods
+of transferring files from the Mac world to the Unix world.
+Most of these methods leave the Mac files alone, but a few of
+them add the ``finderinfo'' data onto the front of the Unix file.
+This means an extra 128 bytes to skip over when reading the file.
+The symptom to watch for is that the resulting PBM file looks shifted
+to one side.
+If you get this, try
+{\bf -extraskip}
+128, and if that still doesn't look right try another value.
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+picttoppm(1), pbmtomacp(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1988 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+The MacPaint-reading code is \copyright 1987 by Patrick J. Naughton
+(naughton@wind.sun.com).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted,
+% provided that the above copyright notice appear in all copies and that
+% both that copyright notice and this permission notice appear in
+% supporting documentation. 
+%
+% end of input file: macptopbm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:07:58 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: mgrtopbm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: mgrtopbm.1
+%
+\phead{mgrtopbm}{1}{24 January 1989}{}{}
+
+%.IX mgrtopbm
+\shead{NAME}
+mgrtopbm - convert a MGR bitmap into a portable bitmap
+\shead{SYNOPSIS}
+{\bf mgrtopbm}
+{\rm [}{\it mgrfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a MGR bitmap as input.
+%.IX MGR
+Produces a portable bitmap as output.
+\shead{SEE ALSO}
+pbmtomgr(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: mgrtopbm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:42 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: mtvtoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: mtvtoppm.1
+%
+\phead{mtvtoppm}{1}{02 February 1989}{}{}
+
+%.IX mtvtoppm
+\shead{NAME}
+mtvtoppm - convert output from the MTV or PRT ray tracers into a portable pixmap
+\shead{SYNOPSIS}
+{\bf mtvtoppm}
+{\rm [}{\it mtvfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads an input file from Mark VanDeWettering's MTV ray tracer.
+%.IX "MTV raytracer"
+Produces a portable pixmap as output.
+\par
+The PRT raytracer also produces this format.
+%.IX "PRT raytracer"
+\shead{SEE ALSO}
+ppm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: mtvtoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:20 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbm.5
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbm.5
+%
+\phead{pbm}{5}{27 September 1991}{}{}
+
+\shead{NAME}
+pbm - portable bitmap file format
+\shead{DESCRIPTION}
+The portable bitmap format is a lowest common denominator monochrome
+file format.
+%.IX "PBM file format"
+It was originally designed to make it reasonable to mail bitmaps
+between different types of machines using the typical stupid network
+mailers we have today.
+Now it serves as the common language of a large family of bitmap
+conversion filters.
+The definition is as follows:
+\begin{IPlist}
+\IPitem{{-}}
+A ``magic number'' for identifying the file type.
+A pbm file's magic number is the two characters ``P1''.
+%.IX "magic numbers"
+\IPitem{{-}}
+Whitespace (blanks, TABs, CRs, LFs).
+\IPitem{{-}}
+A width, formatted as ASCII characters in decimal.
+\IPitem{{-}}
+Whitespace.
+\IPitem{{-}}
+A height, again in ASCII decimal.
+\IPitem{{-}}
+Whitespace.
+\IPitem{{-}}
+Width * height bits, each either '1' or '0', starting at the top-left
+corner of the bitmap, proceeding in normal English reading order.
+\IPitem{{-}}
+The character '1' means black, '0' means white.
+\IPitem{{-}}
+Whitespace in the bits section is ignored.
+\IPitem{{-}}
+Characters from a ``\#'' to the next end-of-line are ignored (comments).
+\IPitem{{-}}
+No line should be longer than 70 characters.
+\end{IPlist}
+
+\par
+Here is an example of a small bitmap in this format:
+\nofill
+P1
+\# feep.pbm
+24 7
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0
+0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0
+0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 1 0
+0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0
+0 1 0 0 0 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+\fill
+\par
+Programs that read this format should be as lenient as possible,
+accepting anything that looks remotely like a bitmap.
+\par
+There is also a variant on the format, available
+by setting the RAWBITS option at compile time.  This variant is
+%.IX RAWBITS
+different in the following ways:
+\begin{IPlist}
+\IPitem{{-}}
+The ``magic number'' is ``P4'' instead of ``P1''.
+\IPitem{{-}}
+The bits are stored eight per byte, high bit first low bit last.
+\IPitem{{-}}
+No whitespace is allowed in the bits section, and only a single character
+of whitespace (typically a newline) is allowed after the height.
+\IPitem{{-}}
+The files are eight times smaller and many times faster to read and write.
+\end{IPlist}
+
+\shead{SEE ALSO}
+atktopbm(1), brushtopbm(1), cmuwmtopbm(1), g3topbm(1),
+gemtopbm(1), icontopbm(1),
+macptopbm(1), mgrtopbm(1), pi3topbm(1), xbmtopbm(1),
+ybmtopbm(1),
+pbmto10x(1), pnmtoascii(1), pbmtoatk(1), pbmtobbnbg(1),
+pbmtocmuwm(1), pbmtoepson(1),
+pbmtog3(1), pbmtogem(1), pbmtogo(1), pbmtoicon(1), pbmtolj(1),
+pbmtomacp(1), pbmtomgr(1), pbmtopi3(1), pbmtoplot(1), pbmtoptx(1),
+pbmtox10bm(1), pbmtoxbm(1), pbmtoybm(1),
+pbmtozinc(1),
+pbmlife(1), pbmmake(1), pbmmask(1), pbmreduce(1),
+pbmtext(1), pbmupc(1),
+pnm(5), pgm(5), ppm(5)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbm.5
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:16 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmclean.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmclean.1
+%
+\phead{pbmclean}{12 Dec 1990}{}{}{}
+
+\shead{NAME}
+pbmclean - flip isolated pixels in portable bitmap
+\shead{SYNOPSIS}
+pbmclean [-connect] [pbmfile]
+\shead{DESCRIPTION}
+Reads a portable bitmap as input. Outputs a portable bitmap with every 
+pixel which has less than %
+\it connect %
+\rm identical neighbours inverted.
+Pbmclean can be used to clean up ``snow'' on bitmap images.
+\shead{SEE ALSO}
+pbm(5)
+\shead{AUTHOR}
+\copyright 1990 by Angus Duggan.
+\copyright 1989 by Jef Poskanzer.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted, provided
+that the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.  This software is provided "as is" without express or
+implied warranty.
+%
+% end of input file: pbmclean.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:07:58 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmlife.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmlife.1
+%
+\phead{pbmlife}{1}{21 February 1991}{}{}
+
+%.IX pbmlife
+\shead{NAME}
+pbmlife - apply Conway's rules of Life to a portable bitmap
+\shead{SYNOPSIS}
+{\bf pbmlife}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Applies the rules of Life to it for one generation,
+%.IX Life
+and produces a portable bitmap as output.
+\par
+A white pixel in the image is interpreted as a live beastie, and a
+black pixel as an empty space.
+\shead{SEE ALSO}
+pbm(5)
+\shead{AUTHOR}
+\copyright 1988, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmlife.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:07:59 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmmake.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmmake.1
+%
+\phead{pbmmake}{1}{22 February 1989}{}{}
+
+%.IX pbmmake
+\shead{NAME}
+pbmmake - create a blank bitmap of a specified size
+\shead{SYNOPSIS}
+{\bf pbmmake}
+{\rm [}{\bf -white}{\rm $|$}{\bf -black}{\rm $|$}{\bf -gray}
+{\rm ]}
+{\it width height}
+\shead{DESCRIPTION}
+Produces a portable bitmap of the specified width and height.
+%.IX "generating bitmaps"
+The color defaults to white.
+\shead{OPTIONS}
+\par
+In addition to the usual
+{\bf -white}
+and
+{\bf -black}{\rm ,}
+this program implements
+{\bf -gray}{\rm .}
+This gives a simple 50\% gray pattern with 1's and 0's alternating.
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+pbm(5), ppmmake(1)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmmake.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:00 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmmask.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmmask.1
+%
+\phead{pbmmask}{1}{08 August 1989}{}{}
+
+%.IX pbmmask
+\shead{NAME}
+pbmmask - create a mask bitmap from a regular bitmap
+\shead{SYNOPSIS}
+{\bf pbmmask}
+{\rm [}{\bf -expand}{\rm ]}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Creates a corresponding mask bitmap and writes it out.
+\par
+The color to be interpreted as ``background'' is determined automatically.
+Regardless of which color is background, the mask will be white where
+the background is and black where the figure is.
+\par
+This lets you do a masked paste like this, for objects with a black background:
+\nofill
+    pbmmask obj $>$ objmask
+    pnmpaste $<$ dest -and objmask $<$x$>$ $<$y$>$ $|$ pnmpaste -or obj $<$x$>$ $<$y$>$
+\fill
+%.IX pnmpaste
+For objects with a white background, you can either invert them or
+add a step:
+\nofill
+    pbmmask obj $>$ objmask
+    pnminvert objmask $|$ pnmpaste -and obj 0 0 $>$ blackback
+    pnmpaste $<$ dest -and objmask $<$x$>$ $<$y$>$ $|$ pnmpaste -or blackback $<$x$>$ $<$y$>$
+\fill
+%.IX pnminvert
+Note that this three-step version works for objects with black backgrounds
+too, if you don't care about the wasted time.
+\par
+You can also use masks with graymaps and pixmaps, using the
+{\it pnmarith}
+tool.  For instance:
+\nofill
+    ppmtopgm obj.ppm $|$ pgmtopbm -threshold $|$ pbmmask $>$ objmask.pbm
+    pnmarith -multiply dest.ppm objmask.pbm $>$ t1.ppm
+    pnminvert objmask.pbm $|$ pnmarith -multiply obj.ppm - $>$ t2.ppm
+    pnmarith -add t1.ppm t2.ppm
+\fill
+%.IX pnmarith
+An interesting variation on this is to pipe the mask through the
+{\it pnmsmooth}
+%.IX pnmsmooth
+script before using it.  This makes the boundary between the two images less
+sharp.
+%.OPTIONS
+\begin{TPlist}{{\bf -expand}}
+\item[{{\bf -expand}}]
+Expands the mask by one pixel out from the image.
+This is useful if you want a little white border around your image.
+(A better solution might be to turn the
+{\it pbmlife}
+tool into a general cellular automaton tool...)
+\end{TPlist}
+
+\shead{SEE ALSO}
+pnmpaste(1), pnminvert(1), pbm(5), pnmarith(1), pnmsmooth(1)
+\shead{AUTHOR}
+\copyright 1988 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmmask.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:16 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmpscale.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmpscale.1
+%
+\phead{pbmpscale}{12 Dec 1990}{}{}{}
+
+\shead{NAME}
+pbmpscale - enlarge a portable bitmap with edge smoothing
+\shead{SYNOPSIS}
+pbmpscale N [ pbmfile ]
+\shead{DESCRIPTION}
+Reads a portable bitmap as input, and outputs a portable bitmap
+enlarged N times. Enlargement is done by pixel replication,
+with some additional smoothing of corners and edges.
+\shead{SEE ALSO}
+pnmenlarge(1), ppmscale(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1990 by Angus Duggan.
+\copyright 1989 by Jef Poskanzer.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted, provided
+that the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.  This software is provided "as is" without express or
+implied warranty.
+\shead{NOTES}
+pbmpscale works best for enlargements of 2. Enlargements greater than 2
+should be done by as many enlargements of 2 as possible, followed by an
+enlargement by the remaining factor.
+%
+% end of input file: pbmpscale.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:00 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmreduce.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmreduce.1
+%
+\phead{pbmreduce}{1}{02 August 1989}{}{}
+
+%.IX pbmreduce
+\shead{NAME}
+pbmreduce - read a portable bitmap and reduce it N times
+\shead{SYNOPSIS}
+{\bf pbmreduce}
+{\rm [}{\bf -floyd}{\rm $|$}{\bf -fs}{\rm $|$}{\bf -threshold}
+{\rm ]}
+{\rm [}{\bf -value}
+{\it val}{\rm ]}
+{\it N}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Reduces it by a factor of
+{\it N}{\rm ,}
+and produces a portable bitmap as output.
+%.IX shrinking
+\par
+{\it pbmreduce}
+duplicates a lot of the functionality of
+{\it pgmtopbm;}
+%.IX pgmtopbm
+you could do something like
+{\bf pnmscale $|$ pgmtopbm,}
+%.IX pnmscale
+but
+{\it pbmreduce}
+is a lot faster.
+\par
+{\it pbmreduce}
+can be used to ``re-halftone'' an image.
+%.IX halftoning
+Let's say you have a scanner that only produces black\&white, not
+grayscale, and it does a terrible job of halftoning (most b\&w scanners
+fit this description).
+One way to fix the halftoning is to scan at the highest possible
+resolution, say 300 dpi, and then reduce by a factor of three or
+so using
+{\it pbmreduce}{\rm .}
+You can even correct the brightness of an image, by using the
+{\bf -value}
+flag.
+\shead{OPTIONS}
+\par
+By default, the halftoning after the reduction is done via
+boustrophedonic Floyd-Steinberg error diffusion; however, the
+%.IX Floyd-Steinberg
+%.IX "error diffusion"
+{\bf -threshold}
+flag can be used to specify simple thresholding.  This gives better
+%.IX thresholding
+results when reducing line drawings.
+\par
+The
+{\bf -value}
+flag alters the thresholding value for all quantizations.
+It should be a real number between 0 and 1.
+Above 0.5 means darker images; below 0.5 means lighter.
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+pnmenlarge(1), pnmscale(1), pgmtopbm(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1988 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmreduce.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:01 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtext.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtext.1
+%
+\phead{pbmtext}{1}{5 January 1991}{}{}
+
+%.IX pbmtext
+\shead{NAME}
+pbmtext - render text into a bitmap
+\shead{SYNOPSIS}
+{\bf pbmtext}
+{\rm [}{\bf -font}
+{\it fontfile}{\rm ]}
+{\rm [}{\it text}{\rm ]}
+\shead{DESCRIPTION}
+\par
+Takes the specified text, either a single line from the command
+line or multiple lines from standard input, and renders it
+into a bitmap.
+%.IX text
+\shead{OPTIONS}
+\par
+By default, pbmtext uses a built-in font.
+You can also specify your own font with the
+{\bf -font}
+flag.
+The
+{\it fontfile}
+is a pbm file, created in a very specific way.
+In your window system of choice, display the following text
+in the desired (fixed-width) font:
+\nofill
+
+    M ",/\^{}\_[`jpqy$|$ M
+
+    /  !"\#\$\%\&'()*+ /
+    $<$ ,-./01234567 $<$
+    $>$ 89:;$<$=$>$?@ABC $>$
+    @ DEFGHIJKLMNO @
+    \_ PQRSTUVWXYZ[ \_
+    \{ \bs ]\^{}\_`abcdefg \{
+    \} hijklmnopqrs \}
+    \~{} tuvwxyz\{$|$\}\~{}  \~{}
+
+    M ",/\^{}\_[`jpqy$|$ M
+
+\fill
+Do a screen grab or window dump of that text, using for instance
+{\it xwd}{\rm ,}
+{\it xgrabsc}{\rm ,}
+or
+{\it screendump}{\rm .}
+Convert the result into a pbm file.
+If necessary, use
+{\it pnmcut}
+to remove everything except the text.
+Finally, run it through
+{\it pnmcrop}
+%.IX pnmcrop
+to make sure the edges are right up against the text.
+{\it pbmtext}
+can figure out the sizes and spacings from that.
+\shead{SEE ALSO}
+pbm(5), pnmcut(1), pnmcrop(1)
+\shead{AUTHOR}
+\copyright 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtext.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:02 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmto10x.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmto10x.1
+%
+\phead{pbmto10x}{1}{1 January 1990}{}{}
+
+%.IX pbmto10x
+\shead{NAME}
+pbmto10x - convert a portable bitmap into Gemini 10X printer graphics
+\shead{SYNOPSIS}
+{\bf pbmto10x}
+{\rm [}{\bf -h}{\rm ]}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces a file of Gemini 10X printer graphics as output.
+%.IX "Gemini 10X printer graphics"
+The 10x's printer codes are alleged to be similar to the Epson codes.
+%.IX Epson
+\par
+Note that there is no 10xtopbm tool - this transformation is one way.
+\shead{OPTIONS}
+\par
+The resolution is normally 60H by 72V.
+If the
+{\bf -h}
+flag is specified, resolution is 120H by 144V.
+You may find it useful to rotate landscape images before printing.
+\shead{SEE ALSO}
+pbm(5)
+\shead{AUTHOR}
+\copyright 1990 by Ken Yap.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmto10x.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:02 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtoascii.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtoascii.1
+%
+\phead{pbmtoascii}{1}{20 March 1992}{}{}
+
+\shead{NAME}
+pbmtoascii - convert a portable bitmap into ASCII graphics
+\shead{SYNOPSIS}
+{\bf pbmtoascii}
+{\rm [}{\bf -1x2}{\rm $|$}{\bf -2x4}{\rm ]}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces a somewhat crude ASCII graphic as output.
+\par
+Note that there is no asciitopbm tool - this transformation is one-way.
+\shead{OPTIONS}
+The
+{\bf -1x2}
+and
+{\bf -2x4}
+flags give you two alternate ways for the bits to get mapped to characters.
+With
+{\bf 1x2}{\rm ,}
+the default, each character represents a group of 1 bit across by 2 bits down.
+With
+{\bf -2x4}{\rm ,}
+each character represents 2 bits across by 4 bits down.
+With the 1x2 mode you can see the individual bits, so it's useful for
+previewing small bitmaps on a non-graphics terminal.
+The 2x4 mode lets you display larger bitmaps on a standard 80-column display,
+but it obscures bit-level details.
+2x4 mode is also good for displaying
+graymaps - ``pnmscale -width 158 $|$ pgmnorm $|$ pgmtopbm -thresh''
+should give good results.
+\shead{SEE ALSO}
+pbm(5)
+\shead{AUTHOR}
+\copyright 1988, 1992 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtoascii.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:03 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtoatk.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtoatk.1
+%
+\phead{pbmtoatk}{1}{26 September 1991}{}{}
+
+%.IX pbmtoatk
+\shead{NAME}
+pbmtoatk - convert portable bitmap to Andrew Toolkit raster object
+\shead{SYNOPSIS}
+{\bf pbmtoatk}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces a Andrew Toolkit raster object as output.
+%.IX "Andrew Toolkit raster object"
+\shead{SEE ALSO}
+atktopbm(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1991 by Bill Janssen.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtoatk.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:03 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtobbnbg.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtobbnbg.1
+%
+\phead{pbmtobg}{1}{16 May 1989}{}{}
+
+%.IX pbmtobbnbg
+\shead{NAME}
+pbmtobg - convert a portable bitmap into BitGraph graphics
+\shead{SYNOPSIS}
+{\bf pbmtobg}
+{\rm [}{\it rasterop}{\rm ]}
+{\rm [}{\it x}
+{\it y}{\rm ]}
+$<$
+{\it pbmfile}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces BBN BitGraph terminal Display Pixel Data (DPD) sequence as output.
+%.IX "BBN BitGraph"
+\par
+The rasterop can be specified on the command line.  If this is omitted, 3
+(replace) will be used.  A position in (x,y) coordinates can also be
+specified.  If both are given, the rasterop comes first.  The portable bitmap
+is always taken from the standard input.
+\par
+Note that there is no bgtopbm tool.
+\shead{SEE ALSO}
+pbm(5)
+\shead{AUTHOR}
+Copyright 1989 by Mike Parker.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtobbnbg.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:04 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtocmuwm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtocmuwm.1
+%
+\phead{pbmtocmuwm}{1}{15 April 1989}{}{}
+
+%.IX pbmtocmuwm
+\shead{NAME}
+pbmtocmuwm - convert a portable bitmap into a CMU window manager bitmap
+\shead{SYNOPSIS}
+{\bf pbmtocmuwm}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces a CMU window manager bitmap as output.
+%.IX "CMU window manager bitmap"
+\shead{SEE ALSO}
+cmuwmtopbm(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtocmuwm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:17 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtoepsi.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtoepsi.1
+%
+\phead{pbmtoepsi}{1}{1992}{}{}
+
+%.IX pbmtoepsi
+\shead{NAME}
+pbmtoepsi - convert a portable bitmap into an encapsulated PostScript
+style preview bitmap
+\shead{SYNOPSIS}
+{\bf pbmtoepsi}
+{\rm [}{\bf -bbonly}{\rm ]}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produce an encapsulated Postscript style bitmap as output. The output
+is not a stand alone postscript file, it is only a preview bitmap,
+which can be included in an encapsulated PostScript file.
+Note that there is no epsitopbm tool - this transformation is one way.
+
+This utility is a part of the pstoepsi tool by Doug Crabill
+(dgc@cs.purdue.edu).
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -bbonly}}
+\item[{{\bf -bbonly}}]
+Only create a boundary box, don't fill it with the image.
+\end{TPlist}
+
+\shead{SEE ALSO}
+pbm(5), pnmtops(1), psidtopgm(1)
+\shead{AUTHOR}
+\copyright 1988 Jef Poskanzer
+\nwl
+modified by Doug Crabill 1992.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtoepsi.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:04 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtoepson.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtoepson.1
+%
+\phead{pbmtoepson}{1}{4 January 1991}{}{}
+
+%.IX pbmtoepson
+\shead{NAME}
+pbmtoepson - convert a portable bitmap into Epson printer graphics
+\shead{SYNOPSIS}
+{\bf pbmtoepson}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces a file of Epson printer graphics as output.
+%.IX Epson
+\par
+Note that there is no epsontopbm tool - this transformation is one way.
+\shead{SEE ALSO}
+pbm(5)
+\shead{AUTHOR}
+\copyright 1991 by John Tiller (tiller@galois.msfc.nasa.gov) and Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtoepson.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:05 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtog3.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtog3.1
+%
+\phead{pbmtog3}{1}{02 October 1989}{}{}
+
+%.IX pbmtog3
+\shead{NAME}
+pbmtog3 - convert a portable bitmap into a Group 3 fax file
+\shead{SYNOPSIS}
+{\bf pbmtog3}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as output.
+Produces a Group 3 fax file as input.
+%.IX "Group 3 fax"
+%.IX fax
+\shead{REFERENCES}
+The standard for Group 3 fax is defined in CCITT Recommendation T.4.
+\shead{BUGS}
+Probably.
+\shead{SEE ALSO}
+g3topbm(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1989 by Paul Haeberli (paul@manray.sgi.com).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtog3.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:05 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtogem.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtogem.1
+%
+\phead{pbmtogem}{1}{11 March 1990}{}{}
+
+%.IX pbmtogem
+\shead{NAME}
+pbmtogem - convert a portable bitmap into a GEM .img file
+\shead{SYNOPSIS}
+{\bf pbmtogem}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces a GEM .img file as output.
+%.IX GEM
+\shead{BUGS}
+It does not support compression of the data.
+\shead{SEE ALSO}
+gemtopbm(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1988 by David Beckemeyer (bdt!david) and Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted,
+% provided that the above copyright notice appear in all copies and that
+% both that copyright notice and this permission notice appear in
+% supporting documentation.
+%
+% end of input file: pbmtogem.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:06 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtogo.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtogo.1
+%
+\phead{pbmtogo}{1}{24 November 1989}{}{}
+
+%.IX pbmtogo
+\shead{NAME}
+pbmtogo - convert a portable bitmap into compressed GraphOn graphics
+\shead{SYNOPSIS}
+{\bf pbmtogo}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces 2D compressed GraphOn graphics as output.
+%.IX GraphOn
+Be sure to set up your GraphOn with the following modes: 8 bits / no parity;
+obeys no XON/XOFF; NULs are accepted.  These are all on the Comm menu.
+Also, remember to turn off tty post processing.
+Note that there is no gotopbm tool.
+\shead{SEE ALSO}
+pbm(5)
+\shead{AUTHOR}
+\copyright 1988, 1989 by Jef Poskanzer, Michael Haberler, and Bo Thide'.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtogo.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:06 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtoicon.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtoicon.1
+%
+\phead{pbmtoicon}{1}{31 August 1988}{}{}
+
+%.IX pbmtoicon
+\shead{NAME}
+pbmtoicon - convert a portable bitmap into a Sun icon
+\shead{SYNOPSIS}
+{\bf pbmtoicon}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces a Sun icon as output.
+%.IX Sun
+%.IX "Sun icon format"
+\shead{SEE ALSO}
+icontopbm(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1988 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtoicon.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:07 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtolj.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtolj.1
+%
+\phead{pbmtolj}{1}{29 August 1988}{}{}
+
+%.IX pbmtolj
+\shead{NAME}
+pbmtolj - convert a portable bitmap into HP LaserJet format
+\shead{SYNOPSIS}
+{\bf pbmtolj}
+{\rm [}{\bf -resolution}
+{\it N}{\rm ]}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces HP LaserJet data as output.
+%.IX "HP LaserJet"
+\par
+Note that there is no ljtopbm tool.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -resolution}}
+\item[{{\bf -resolution}}]
+Specifies the resolution of the output device, in dpi.
+Typical values are 75, 100, 150, 300.
+The default is 75.
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+pbm(5)
+\shead{AUTHOR}
+\copyright 1988 by Jef Poskanzer and Michael Haberler.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtolj.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:15 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtoln03.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtoln03.1
+%
+\phead{pbmtoln03}{1}{7 May 1993}{}{}
+
+%.IX pbmtoln03
+\shead{NAME}
+pbmtoln03 - convert protable bitmap to DEC LN03+ Sixel output
+\shead{SYNOPSIS}
+{\bf pbmtoln03}
+{\rm [}{\bf -rltbf}{\rm ]}
+{\it pbmfile}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+%.IX "DEC LN03+ Sixel"
+Produces a DEC LN03+ Sixel output file.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -l nn}}
+\item[{{\bf -l nn}}]
+Use ``nn'' as value for left margin (default 0).
+\item[{{\bf -r nn}}]
+Use ``nn'' as value for right margin (default 2400).
+\item[{{\bf -t nn}}]
+Use ``nn'' as value for top margin (default 0).
+\item[{{\bf -b nn}}]
+Use ``nn'' as value for bottom margin (default 3400).
+\item[{{\bf -f nn}}]
+Use ``nn'' as value for form length (default 3400).
+\end{TPlist}
+
+\shead{SEE ALSO}
+pbm(5)
+\shead{AUTHOR}
+Tim Cook, 26 Feb 1992
+%
+% end of input file: pbmtoln03.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:18 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtolps.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtolps.1
+%
+\phead{pbmtolps}{12 Dec 1990}{}{}{}
+
+\shead{NAME}
+pbmtolps - convert portable bitmap to PostScript
+\shead{SYNOPSIS}
+pbmtolps [ -dpi n ] [ pbmfile ]
+\shead{DESCRIPTION}
+Reads a portable bitmap as input, and outputs PostScript.
+The output Postscript uses lines instead of the image operator to
+generate a (device dependent) picture which will be imaged
+much faster.
+\par
+The Postscript path length is constrained to be less that 1000
+points so that no limits are overrun on the Apple Laserwriter
+and (presumably) no other printers.
+\shead{SEE ALSO}
+pgmtops(1), ppmtops(1), pbm(5)
+\shead{AUTHOR}
+\copyright George Phillips (phillips@cs.ubc.ca).
+%
+% end of input file: pbmtolps.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:08 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtomacp.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtomacp.1
+%
+\phead{pbmtomacp}{1}{31 August 1988}{}{}
+
+%.IX pbmtomacp
+\shead{NAME}
+pbmtomacp - convert a portable bitmap into a MacPaint file
+\shead{SYNOPSIS}
+{\bf pbmtomacp}
+{\rm [}{\bf -l}
+{\it left}{\rm ]}
+{\rm [}{\bf -r}
+{\it right}{\rm ]}
+{\rm [}{\bf -b}
+{\it bottom}{\rm ]}
+{\rm [}{\bf -t}
+{\it top}{\rm ]}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+If no input-file is given, standard input is assumed.
+Produces a MacPaint file as output.
+%.IX MacPaint
+%.IX Macintosh
+\par
+The generated file is only the data fork of a picture.
+You will need a program such as
+{\it mcvert}
+to generate a Macbinary or a BinHex file that contains the necessary
+information to identify the file as a PNTG file to MacOS.
+\shead{OPTIONS}
+\par
+Left, right, bottom \& top let you define a square into the pbm file,
+that must be converted.
+Default is the whole file.
+If the file is too large for a MacPaint-file, the bitmap is cut to fit
+from ( left, top ).
+\shead{BUGS}
+The source code contains comments in a language other than English.
+\shead{SEE ALSO}
+ppmtopict(1), macptopbm(1), pbm(5), mcvert(1)
+\shead{AUTHOR}
+\copyright 1988 by Douwe van der Schaaf ($\ldots$!mcvax!uvapsy!vdschaaf).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtomacp.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:08 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtomgr.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtomgr.1
+%
+\phead{pbmtomgr}{1}{24 January 1989}{}{}
+
+%.IX pbmtomgr
+\shead{NAME}
+pbmtomgr - convert a portable bitmap into a MGR bitmap
+\shead{SYNOPSIS}
+{\bf pbmtomgr}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces a MGR bitmap as output.
+%.IX MGR
+\shead{SEE ALSO}
+mgrtopbm(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtomgr.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:36 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtopgm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtopgm.1
+%
+\phead{pbmtopgm}{12 Dec 1990}{}{}{}
+
+\shead{NAME}
+pbmtopgm - convert portable bitmap to portable graymap by averaging areas
+\shead{SYNOPSIS}
+pbmtopgm $<$width$>$ $<$height$>$ [pbmfile]
+\shead{DESCRIPTION}
+Reads a portable bitmap as input. Outputs a portable graymap created by
+averaging the number of pixels within a sample area of
+%
+\it width %
+\rm by %
+\it height %
+\rm around each point. Pbmtopgm is similar to a
+special case of ppmconvol. A ppmsmooth step may be needed after pbmtopgm.
+\par
+Pbmtopgm has the effect of anti-aliasing bitmaps which contain distinct
+line features.
+\shead{SEE ALSO}
+pbm(5)
+\shead{AUTHOR}
+\copyright 1990 by Angus Duggan.
+\copyright 1989 by Jef Poskanzer.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted, provided
+that the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.  This software is provided "as is" without express or
+implied warranty.
+\shead{NOTES}
+Pbmtopgm works best with odd sample width and heights.
+%
+% end of input file: pbmtopgm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:09 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtopi3.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtopi3.1
+%
+\phead{pbmtopi3}{1}{11 March 1990}{}{}
+
+%.IX pbmtopi3
+\shead{NAME}
+pbmtopi3 - convert a portable bitmap into an Atari Degas .pi3 file 
+\shead{SYNOPSIS}
+{\bf pbmtopi3}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces an Atari Degas .pi3 file as output.
+%.IX Atari
+%.IX "Degas .pi3"
+\shead{SEE ALSO}
+pi3topbm(1), pbm(5), ppmtopi1(1), pi1toppm(1)
+\shead{AUTHOR}
+\copyright 1988 by David Beckemeyer (bdt!david) and Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted,
+% provided that the above copyright notice appear in all copies and that
+% both that copyright notice and this permission notice appear in
+% supporting documentation.
+%
+% end of input file: pbmtopi3.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:18 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtopk.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtopk.1
+%
+\phead{pbmtopk}{1}{6 August 1990}{}{}
+
+\shead{NAME}
+pbmtopk - convert a portable bitmap into a packed (PK) format font
+\shead{SYNOPSIS}
+pbmtopk pkfile[.pk] tfmfile[.tfm] resolution [-s designsize] [-p num param...]
+[-C codingscheme] [-F family] [-f optfile] [-c num]
+[-W width] [-H height] [-D depth]
+[-I ital] [-h horiz] [-v vert] [-x xoff] [-y yoff] [pbmfile]...
+\shead{DESCRIPTION}
+Reads portable bitmaps as input, and produces a packed (PK) font file and a
+TFM (TeX font metric) file as output. The resolution parameter indicates the
+resolution of the font, in dots per inch. If the filename ``-'' is used for any
+of the filenames, the standard input stream (or standard output where
+appropriate) will be used.
+\shead{OPTIONS}
+\begin{IPlist}
+\IPitem{{-s\ designsize}}
+Sets the design size of the font, in TeX's points (72.27pt to the inch). The
+default design size is 1. The TFM parameters are given as multiples of the
+design size.
+\IPitem{{-p\ num\ param...}}
+Sets the first num font parameters for the font. The first seven parameters
+are the slant,
+interword spacing, interword space stretchability, interword space
+shrinkability, x-height, quad width, and post-sentence extra space of the
+font. Math and symbol fonts may have more parameters; see The TeXbook for a
+list of these. Reasonable default values are chosen for parameters which are
+not specified.
+\IPitem{{-C\ codingscheme}}
+Sets the coding scheme comment in the TFM file.
+\IPitem{{-F\ family}}
+Sets the font family comment in the TFM file.
+\IPitem{{-f\ optfile}}
+Reads the file optfile, which should contain a lines of the form:
+\par\vspace{1.0\baselineskip}
+\nofill
+\raggedright
+   filename xoff yoff horiz vert width height depth ital
+\fill
+%.ad
+\par\vspace{1.0\baselineskip}
+The pbm files specified by the filename parameters are inserted consecutively
+in the font with the specified attributes. If any of the attributes are
+omitted, or replaced with ``*'', a default value will be calculated from the
+size of the bitmap. The settings of the -W, -H, -D, -I, -h, -v, -x, and -y
+options do not affected characters created in this way.
+The character number can be changed by including a line starting with
+``='',
+followed by the new number.
+Lines beginning with
+``\%'' or ``\#'' are ignored.
+\IPitem{{-c\ num}}
+Sets the character number of the next bitmap encountered to num.
+\IPitem{{-W\ width}}
+Sets the TFM width of the next character to width (in design size multiples).
+\IPitem{{-H\ height}}
+Sets the TFM height of the next character to height (in design size multiples).
+\IPitem{{-D\ depth}}
+Sets the TFM depth of the next character to depth (in design size multiples).
+\IPitem{{-I\ ital}}
+Sets the italic correction of the next character to
+ital (in design size multiples).
+\IPitem{{-h\ horiz}}
+Sets the horizontal escapement of the next character to horiz (in pixels).
+\IPitem{{-v\ vert}}
+Sets the vertical escapement of the next character to vert (in pixels).
+\IPitem{{-x\ xoff}}
+Sets the horizontal offset of the next character to xoff (in pixels).
+\IPitem{{-y\ yoff}}
+Sets the vertical offset of the next character to yoff (in pixels, from the
+top row).
+\end{IPlist}
+
+\shead{SEE ALSO}
+pktopbm(1), pbm(5)
+\shead{AUTHOR}
+Adapted from Tom Rokicki's pxtopk by Angus Duggan (ajcd@dcs.ed.ac.uk).
+
+%
+% end of input file: pbmtopk.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:09 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtoplot.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtoplot.1
+%
+\phead{pbmtoplot}{1}{1 September 1990}{}{}
+
+%.IX pbmtoplot
+\shead{NAME}
+pbmtoplot - convert a portable bitmap into a Unix plot(5) file
+\shead{SYNOPSIS}
+{\bf pbmtoplot}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces a Unix
+{\it plot}
+file.
+%.IX plot
+\par
+Note that there is no plottopbm tool - this transformation is one-way.
+\shead{SEE ALSO}
+pbm(5), plot(5)
+\shead{AUTHOR}
+\copyright 1990 by Arthur David Olson.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtoplot.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:10 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtoptx.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtoptx.1
+%
+\phead{pbmtoptx}{1}{31 August 1988}{}{}
+
+%.IX pbmtoptx
+\shead{NAME}
+pbmtoptx - convert a portable bitmap into Printronix printer graphics
+\shead{SYNOPSIS}
+{\bf pbmtoptx}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces a file of Printronix printer graphics as output.
+%.IX Printronix
+\par
+Note that there is no ptxtopbm tool - this transformation is one way.
+\shead{SEE ALSO}
+pbm(5)
+\shead{AUTHOR}
+\copyright 1988 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtoptx.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:10 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtox10bm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtox10bm.1
+%
+\phead{pbmtox10bm}{1}{31 August 1988}{}{}
+
+%.IX pbmtox10bm
+\shead{NAME}
+pbmtox10bm - convert a portable bitmap into an X10 bitmap
+\shead{SYNOPSIS}
+{\bf pbmtox10bm}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces an X10 bitmap as output.
+This older format is maintained for compatibility.
+%.IX "X bitmap"
+%.IX "X window system"
+\par
+Note that there is no x10bmtopbm tool, because
+{\it xbmtopbm}
+can read both X11 and X10 bitmaps.
+\shead{SEE ALSO}
+pbmtoxbm(1), xbmtopbm(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1988 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtox10bm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:11 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtoxbm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtoxbm.1
+%
+\phead{pbmtoxbm}{1}{31 August 1988}{}{}
+
+%.IX pbmtoxbm
+\shead{NAME}
+pbmtoxbm - convert a portable bitmap into an X11 bitmap
+\shead{SYNOPSIS}
+{\bf pbmtoxbm}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces an X11 bitmap as output.
+%.IX "X bitmap"
+%.IX "X window system"
+\shead{SEE ALSO}
+pbmtox10bm(1), xbmtopbm(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1988 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtoxbm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:12 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtoybm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtoybm.1
+%
+\phead{pbmtoybm}{1}{06 March 1990}{}{}
+
+%.IX pbmtoybm
+\shead{NAME}
+pgmtoybm - convert a portable bitmap into a Bennet Yee ``face'' file
+\shead{SYNOPSIS}
+{\bf pbmtoybm}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces as output a file acceptable to the
+{\it face}
+and
+{\it xbm}
+programs by Bennet Yee (bsy+@cs.cmu.edu).
+%.IX face
+\shead{SEE ALSO}
+ybmtopbm(1), pbm(5), face(1), face(5), xbm(1)
+\shead{AUTHOR}
+\copyright 1991 by Jamie Zawinski and Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmtoybm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:12 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmtozinc.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmtozinc.1
+%
+\phead{pbmtozinc}{l}{02 November 1990}{}{}
+
+%.IX pbmtozinc
+\shead{NAME}
+pbmtozinc - convert a portable bitmap into a Zinc bitmap
+\shead{SYNOPSIS}
+{\bf pbmtozinc}
+{\rm [}{\it pbmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable bitmap as input.
+Produces a bitmap in the format used by the Zinc Interface Library
+(ZIL) Version 1.0 as output.
+%.IX "Zinc Interface Library"
+\shead{SEE ALSO}
+pbm(5)
+\shead{AUTHOR}
+\copyright 1988 by James Darrell McCauley (jdm5548@diamond.tamu.edu)
+and Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% Zinc and Zinc Interface Library are trademarks of
+% Zinc Software Inc., Pleasant Grove, Utah.
+%
+% end of input file: pbmtozinc.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:13 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pbmupc.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pbmupc.1
+%
+\phead{pbmupc}{1}{14 March 1989}{}{}
+
+%.IX pbmupc
+\shead{NAME}
+pbmupc - create a Universal Product Code bitmap
+\shead{SYNOPSIS}
+{\bf pbmupc}
+{\rm [}{\bf -s1}{\rm $|$}{\bf -s2}{\rm ]}
+{\it type manufac product}
+\shead{DESCRIPTION}
+Generates a Universal Product Code symbol.
+%.IX "Universal Product Code"
+The three arguments are: a one digit product type, a five digit
+manufacturer code, and a five digit product code.
+For example, ``0 72890 00011'' is the code for Heineken.
+%.IX Heineken
+\par
+As presently configured,
+{\it pbmupc}
+produces a bitmap 230 bits wide and 175 bits high.
+The size can be altered by changing the defines at the beginning of
+the program, or by running the output through
+{\it pnmenlarge}
+or
+{\it pnmscale}{\rm .}
+\shead{OPTIONS}
+\par
+The
+{\bf -s1}
+and
+{\bf -s2}
+flags select the style of UPC to generate.
+The default,
+{\bf -s1}{\rm ,}
+looks more or less like this:
+\nofill
+ $|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$
+ $|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$
+ $|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$
+ $|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$
+0$|$$|$12345$|$$|$67890$|$$|$5
+\fill
+The other style,
+{\bf -s2}{\rm ,}
+puts the product type digit higher up, and
+doesn't display the checksum digit:
+\nofill
+ $|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$
+ $|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$
+0$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$
+ $|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$$|$
+ $|$$|$12345$|$$|$67890$|$$|$
+\fill
+\shead{SEE ALSO}
+pbm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pbmupc.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:42 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pcxtoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pcxtoppm.1
+%
+\phead{pcxtoppm}{1}{9 April 1990}{}{}
+
+%.IX pcxtoppm
+\shead{NAME}
+pcxtoppm - convert a PCX file into a portable pixmap
+\shead{SYNOPSIS}
+{\bf pcxtoppm}
+{\rm [}{\it pcxfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a PCX file as input.
+%.IX PCX
+Produces a portable pixmap as output.
+\shead{SEE ALSO}
+ppmtopcx(1), ppm(5)
+\shead{AUTHOR}
+\copyright 1990 by Michael Davidson.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pcxtoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:38 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgm.5
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgm.5
+%
+\phead{pgm}{5}{12 November 1991}{}{}
+
+\shead{NAME}
+pgm - portable graymap file format
+\shead{DESCRIPTION}
+The portable graymap format is a lowest common denominator grayscale
+file format.
+%.IX "PGM file format"
+The definition is as follows:
+\begin{IPlist}
+\IPitem{{-}}
+A ``magic number'' for identifying the file type.
+A pgm file's magic number is the two characters ``P2''.
+%.IX "magic numbers"
+\IPitem{{-}}
+Whitespace (blanks, TABs, CRs, LFs).
+\IPitem{{-}}
+A width, formatted as ASCII characters in decimal.
+\IPitem{{-}}
+Whitespace.
+\IPitem{{-}}
+A height, again in ASCII decimal.
+\IPitem{{-}}
+Whitespace.
+\IPitem{{-}}
+The maximum gray value, again in ASCII decimal.
+\IPitem{{-}}
+Whitespace.
+\IPitem{{-}}
+Width * height gray values, each in ASCII decimal, between 0 and the specified
+maximum value, separated by whitespace, starting at the top-left
+corner of the graymap, proceeding in normal English reading order.
+A value of 0 means black, and the maximum value means white.
+\IPitem{{-}}
+Characters from a ``\#'' to the next end-of-line are ignored (comments).
+\IPitem{{-}}
+No line should be longer than 70 characters.
+\end{IPlist}
+
+\par
+Here is an example of a small graymap in this format:
+\nofill
+P2
+\# feep.pgm
+24 7
+15
+0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
+0  3  3  3  3  0  0  7  7  7  7  0  0 11 11 11 11  0  0 15 15 15 15  0
+0  3  0  0  0  0  0  7  0  0  0  0  0 11  0  0  0  0  0 15  0  0 15  0
+0  3  3  3  0  0  0  7  7  7  0  0  0 11 11 11  0  0  0 15 15 15 15  0
+0  3  0  0  0  0  0  7  0  0  0  0  0 11  0  0  0  0  0 15  0  0  0  0
+0  3  0  0  0  0  0  7  7  7  7  0  0 11 11 11 11  0  0 15  0  0  0  0
+0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
+\fill
+\par
+Programs that read this format should be as lenient as possible,
+accepting anything that looks remotely like a graymap.
+\par
+There is also a variant on the format, available
+by setting the RAWBITS option at compile time.  This variant is
+different in the following ways:
+%.IX RAWBITS
+\begin{IPlist}
+\IPitem{{-}}
+The ``magic number'' is ``P5'' instead of ``P2''.
+\IPitem{{-}}
+The gray values are stored as plain bytes, instead of ASCII decimal.
+\IPitem{{-}}
+No whitespace is allowed in the grays section, and only a single character
+of whitespace (typically a newline) is allowed after the maxval.
+\IPitem{{-}}
+The files are smaller and many times faster to read and write.
+\end{IPlist}
+
+\par
+Note that this raw format can only be used for maxvals less than
+or equal to 255.
+If you use the
+{\it pgm}
+library and try to write a file with a larger maxval,
+it will automatically fall back on the slower but more general plain
+format.
+\shead{SEE ALSO}
+fitstopgm(1), fstopgm(1), hipstopgm(1), lispmtopgm(1), psidtopgm(1),
+rawtopgm(1),
+pgmbentley(1), pgmcrater(1), pgmedge(1), pgmenhance(1), pgmhist(1), pgmnorm(1),
+pgmoil(1), pgmramp(1), pgmtexture(1),
+pgmtofits(1), pgmtofs(1), pgmtolispm(1), pgmtopbm(1),
+pnm(5), pbm(5), ppm(5)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pgm.5
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:26 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgmbentley.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgmbentley.1
+%
+\phead{pgmbentley}{1}{11 January 1991}{}{}
+
+%.IX pgmbentley
+\shead{NAME}
+pgmbentley - Bentleyize a portable graymap
+\shead{SYNOPSIS}
+{\bf pgmbentley}
+{\rm [}{\it pgmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable graymap as input.
+Performs The Bentley Effect, and writes a portable graymap as output.
+%.IX "Bentley Effect"
+\par
+The Bentley Effect is described in ``Beyond Photography'' by Holzmann,
+chapter 4, photo 4.
+It's a vertical smearing based on brightness.
+\shead{SEE ALSO}
+pgmoil(1), ppmrelief(1), pgm(5)
+\shead{AUTHOR}
+\copyright 1990 by Wilson Bent (whb@hoh-2.att.com).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pgmbentley.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:27 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgmcrater.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgmcrater.1
+%
+\phead{pgmcrater}{1}{15 October 1991}{}{}
+
+%.IX pgmcrater
+%.IX fractals
+%.IX craters
+\shead{NAME}
+pgmcrater - create cratered terrain by fractal forgery
+\shead{SYNOPSIS}
+\raggedright
+{\bf pgmcrater}
+'ti 15
+{\rm [}{\bf -number}
+{\it n}{\rm ]}
+{\rm [}{\bf -height}{\rm $|$}{\bf -ysize}
+{\it s}{\rm ]}
+{\rm [}{\bf -width}{\rm $|$}{\bf -xsize}
+{\it s}{\rm ]}
+{\rm [}{\bf -gamma}
+{\it g}{\rm ]}
+%.ad
+\shead{DESCRIPTION}
+{\bf pgmcrater}
+creates a portable graymap which mimics cratered terrain.  The graymap
+is created by simulating the impact of a given number of craters with
+random position and size, then rendering the resulting terrain
+elevations based on a light source shining from one side of the
+screen.  The size distribution of the craters is based on a power law
+which results in many more small craters than large ones.  The number
+of craters of a given size varies as the reciprocal of the area as
+described on pages 31 and 32 of Peitgen and Saupe[1]; cratered bodies
+in the Solar System are observed to obey this relationship.  The
+formula used to obtain crater radii governed by this law from a
+uniformly distributed pseudorandom sequence was developed by Rudy
+Rucker.
+\par
+High resolution images with large numbers of craters often benefit
+from being piped through
+{\bf pnmsmooth}{\rm .}
+The averaging performed by this process eliminates some of the jagged
+pixels and lends a mellow ``telescopic image'' feel to the overall
+picture.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -number}{\it \ n}
+}
+\item[{{\bf -number}{\it \ n}
+}]
+Causes
+{\it n}
+craters to be generated.  If no
+{\bf -number}
+specification is given, 50000 craters will be generated.  Don't expect
+to see them all!  For every large crater there are many, many more
+tiny ones which tend simply to erode the landscape.  In general, the
+more craters you specify the more realistic the result; ideally you
+want the entire terrain to have been extensively turned over again and
+again by cratering.  High resolution images containing five to ten
+million craters are stunning but take quite a while to create.
+\item[{{\bf -height}{\it \ height}
+}]
+Sets the height of the generated image to
+{\it height}
+pixels.  The default height is 256 pixels.
+\item[{{\bf -width}{\it \ width}
+}]
+Sets the width of the generated image to
+{\it width}
+pixels.  The default width is 256 pixels.
+\item[{{\bf -xsize}{\it \ width}
+}]
+Sets the width of the generated image to
+{\it width}
+pixels.  The default width is 256 pixels.
+\item[{{\bf -ysize}{\it \ height}
+}]
+Sets the height of the generated image to
+{\it height}
+pixels.  The default height is 256 pixels.
+\item[{{\bf -gamma}{\it \ factor}
+}]
+The specified
+{\it factor}
+is used to gamma correct the graymap in the same manner as performed
+by
+{\bf pnmgamma}{\rm .}
+The default value is 1.0, which results in a medium contrast image.
+Values larger than 1 lighten the image and reduce contrast, while
+values less than 1 darken the image, increasing contrast.
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{BUGS}
+The
+{\bf -gamma}
+option isn't really necessary since you can achieve the same
+effect by piping the output from
+{\bf pgmcrater}
+through
+{\bf pnmgamma}{\rm .}
+However,
+{\bf pgmcrater}
+performs an internal gamma map anyway in the process of rendering the
+elevation array into a graymap, so there's no additional overhead in
+allowing a user-specified gamma.
+\par
+Real craters have two distinct morphologies.
+{\bf pgmcrater}
+simulates only small craters, which are hemispherical in shape
+(regardless of the incidence angle of the impacting body, as long as the
+velocity is sufficiently high).  Large craters, such as Copernicus and
+Tycho on the Moon, have a ``walled plain'' shape with a cross-section more
+like:
+\nofill
+%.ne 2
+%.cs R 18
+                /\bs                             /\bs 
+\nwl
+          \_\_\_\_\_/  \bs \_\_\_\_\_\_\_\_\_\_\_\_/\bs \_\_\_\_\_\_\_\_\_\_\_\_/  \bs \_\_\_\_\_
+%.cs R
+\fill
+%.ss 12
+Larger craters should really use this profile, including the central
+peak, and totally obliterate the pre-existing terrain.
+\shead{SEE ALSO}
+{\bf pgm}{\rm (5),}
+{\bf pnmgamma}{\rm (1),}
+{\bf pnmsmooth}{\rm (1)}
+\begin{TPlist}{[1]}
+\item[{[1]}]
+Peitgen, H.-O., and Saupe, D. eds., The Science Of Fractal Images,
+New York: Springer Verlag, 1988.
+%.ne 10
+\end{TPlist}
+
+\shead{AUTHOR}
+\ind{1\parindent}{\nofill
+    John Walker
+    Autodesk SA
+    Avenue des Champs-Montants 14b
+    CH-2074 MARIN
+    Suisse/Schweiz/Svizzera/Svizra/Switzerland
+\fill}
+\begin{TPlist}{Usenet:}
+\item[{Usenet:}]
+kelvin@Autodesk.com
+\item[{Fax:}]
+038/33 88 15
+\item[{Voice:}]
+038/33 76 33
+\end{TPlist}
+
+\par
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+without any conditions or restrictions.  This software is provided ``as
+is'' without express or implied warranty.
+\par
+{\bf PLUGWARE!}
+If you like this kind of stuff, you may also enjoy ``James Gleick's
+Chaos--The Software'' for MS-DOS, available for \$59.95 from your
+local software store or directly from Autodesk, Inc., Attn: Science
+Series, 2320 Marinship Way, Sausalito, CA 94965, USA.  Telephone:
+(800) 688-2344 toll-free or, outside the U.S. (415) 332-2344 Ext
+4886.  Fax: (415) 289-4718.  ``Chaos--The Software'' includes a more
+comprehensive fractal forgery generator which creates
+three-dimensional landscapes as well as clouds and planets, plus five
+more modules which explore other aspects of Chaos.  The user guide of
+more than 200 pages includes an introduction by James Gleick and
+detailed explanations by Rudy Rucker of the mathematics and algorithms
+used by each program.
+%
+% end of input file: pgmcrater.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:27 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgmedge.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgmedge.1
+%
+\phead{pgmedge}{1}{04 February 1990}{}{}
+
+%.IX pgmedge
+\shead{NAME}
+pgmedge - edge-detect a portable graymap
+\shead{SYNOPSIS}
+{\bf pgmedge}
+{\rm [}{\it pgmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable graymap as input.
+Outlines the edges, and writes a portable graymap as output.
+%.IX "edge detection"
+Piping the result through
+{\bf pgmtopbm -threshold}
+and playing with the
+threshold value will give a bitmap of the edges.
+%.IX thresholding
+\par
+The edge detection technique used is to take the Pythagorean sum of
+two Sobel gradient operators at 90 degrees to each other.
+For more details see ``Digital Image Processing'' by Gonzalez and Wintz,
+chapter 7.
+\shead{SEE ALSO}
+pgmenhance(1), pgmtopbm(1), pgm(5), pbm(5)
+\shead{AUTHOR}
+\copyright 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pgmedge.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:28 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgmenhance.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgmenhance.1
+%
+\phead{pgmenhance}{1}{13 January 1989}{}{}
+
+%.IX pgmenhance
+\shead{NAME}
+pgmenhance - edge-enhance a portable graymap
+\shead{SYNOPSIS}
+{\bf pgmenhance}
+{\rm [}{\it -N}{\rm ]}
+{\rm [}{\it pgmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable graymap as input.
+Enhances the edges, and writes a portable graymap as output.
+%.IX "edge enhancement"
+\par
+The edge enhancing technique is taken from Philip R. Thompson's ``xim''
+program, which in turn took it from section 6 of ``Digital Halftones by
+Dot Diffusion'', D. E. Knuth, ACM Transaction on Graphics Vol. 6, No. 4,
+October 1987, which in turn got it from two 1976 papers by J. F. Jarvis
+{\it et. al.}
+\shead{OPTIONS}
+\par
+The optional
+{\it -N}
+flag should be a digit from 1 to 9.
+1 is the lowest level of enhancement, 9 is the highest,
+The default is 9.
+\shead{SEE ALSO}
+pgmedge(1), pgm(5), pbm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pgmenhance.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:29 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgmhist.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgmhist.1
+%
+\phead{pgmhist}{1}{28 February 1989}{}{}
+
+%.IX pgmhist
+\shead{NAME}
+pgmhist - print a histogram of the values in a portable graymap
+\shead{SYNOPSIS}
+{\bf pgmhist}
+{\rm [}{\it pgmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable graymap as input.
+Prints a histogram of the gray values.
+\shead{SEE ALSO}
+pgmnorm(1), pgm(5), ppmhist(1)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pgmhist.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Feb  4 14:35:48 1994
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgmkernel.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgmkernel.1
+%
+\phead{pgmkernel}{1}{10 December 1992}{}{}
+
+%.IX pgmkernel
+\shead{NAME}
+pgmkernel - generate a convolution kernel
+\shead{SYNOPSIS}
+{\bf pgmkernel} [{\bf --weight}{\it w}] {\it width} [{\it height}]
+\shead{DESCRIPTION}
+Generates a portable graymap array of size
+{\it width} x {\it height} (or {\it width} x {\it width} if {\it height}
+is not specified) to be used as a convolution file by
+{\bf pnmconvol}{\rm .}
+The data in the convolution array K are computed according to the
+formula:
+\par
+K(i,j) = 1 / ( 1 + w * sqrt((i-width/2)\^{}2 + (j-height/2)\^{}2)) 
+\par
+where 
+{\it w}
+is a coefficient specified via the 
+{\it --weight}
+flag, and
+{\it width}
+and 
+{\it height}
+are the X and Y filter sizes.
+\par
+The output PGM file is always written out in ASCII format.
+\shead{OPTIONS}
+The optional 
+{\it -weight}
+flag should be a real number greater than -1.
+The default value is 6.0.
+\shead{BUGS}
+The computation time is proportional to 
+{\it width}
+* 
+{\it height}{\rm .}
+This increases rapidly with the increase of the kernel size.  
+A better approach could be using a FFT in these cases.
+\shead{SEE ALSO}
+pnmconvol(1), pnmsmooth(1)
+\shead{AUTHOR}
+Alberto Accomazzi (alberto@cfa.harvard.edu).
+%
+% end of input file: pgmkernel.1
+%--------------------------------------------------
+
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Mon Nov 29 13:21:08 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgmnoise.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgmnoise.1
+%
+\phead{pgmnoise}{1}{16 November 1993}{}{}
+
+%.IX pgmnoise
+\shead{NAME}
+pgmnoise - create a graymap made up of white noise
+\shead{SYNOPSIS}
+{\bf pgmnoise}
+{\it width height}
+\shead{DESCRIPTION}
+Creates a portable graymap that is made up of random pixels with
+gray values in the range of 0 to PGM\_MAXMAXVAL (depends on the compilation,
+either 255 or 65535). The graymap has a size of width * height pixels.
+\shead{SEE ALSO}
+pgm(5)
+\shead{AUTHOR}
+\copyright 1993 by Frank Neumann.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pgmnoise.1
+%--------------------------------------------------
+
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:29 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgmnorm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgmnorm.1
+%
+\phead{pgmnorm}{1}{28 February 1989}{}{}
+
+%.IX pgmnorm
+\shead{NAME}
+pgmnorm - normalize the contrast in a portable graymap
+\shead{SYNOPSIS}
+{\bf pgmnorm}
+{\rm [}{\bf -bpercent}
+{\it N}
+$|$
+{\bf -bvalue}
+{\it N}{\rm ]}
+{\rm [}{\bf -wpercent}
+{\it N}
+$|$
+{\bf -wvalue}
+{\it N}{\rm ]}
+{\rm [}{\it pgmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable graymap as input.
+Normalizes the contrast by forcing the lightest pixels to white, the
+%.IX "contrast normalization"
+darkest pixels to black, and linearly rescaling the ones in between;
+and produces a portable graymap as output.
+\shead{OPTIONS}
+\par
+By default, the darkest 2 percent of all pixels are mapped to black, and
+the lightest 1 percent are mapped to white.
+You can override these percentages by using the
+{\bf -bpercent}
+and
+{\bf -wpercent}
+flags,
+or you can specify the exact pixel values to be mapped by using the
+{\bf -bvalue}
+and
+{\bf -wvalue}
+flags.
+Appropriate numbers for the flags can be gotten from the
+{\it pgmhist}
+tool.
+%.IX pgmhist
+If you just want to enhance the contrast, then choose values at elbows in the
+histogram; {\it e.g.}, if value 29 represents 3\% of the image but value 30
+represents 20\%, choose 30 for
+{\it bvalue}{\rm .}
+If you want to lighten the
+image, then set
+{\it bvalue}
+to 0 and just fiddle with
+{\it wvalue}{\rm ;}
+similarly, to darken the image, set
+{\it wvalue}
+to maxval and play with
+{\it bvalue}{\rm .}
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+pgmhist(1), pgm(5)
+\shead{AUTHOR}
+Partially based on the fbnorm filter in Michael Mauldin's ``Fuzzy
+Pixmap''
+package.
+
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pgmnorm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:30 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgmoil.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgmoil.1
+%
+\phead{pgmoil}{1}{11 January 1991}{}{}
+
+%.IX pgmoil
+\shead{NAME}
+pgmoil - turn a portable graymap into an oil painting
+\shead{SYNOPSIS}
+{\bf pgmoil}
+{\rm [}{\bf -n}
+{\it N}{\rm ]}
+{\rm [}{\it pgmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable graymap as input.
+Does an ``oil transfer'', and writes a portable graymap as output.
+%.IX "oil transfer"
+\par
+The oil transfer is described in ``Beyond Photography'' by Holzmann,
+chapter 4, photo 7.
+It's a sort of localized smearing.
+\shead{OPTIONS}
+\par
+The optional
+{\bf -n}
+flag controls the size of the area smeared.  The default value is 3.
+\shead{BUGS}
+Takes a long time to run.
+\shead{SEE ALSO}
+pgmbentley(1), ppmrelief(1), pgm(5)
+\shead{AUTHOR}
+\copyright 1990 by Wilson Bent (whb@hoh-2.att.com).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pgmoil.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:30 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgmramp.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgmramp.1
+%
+\phead{pgmramp}{1}{24 November 1989}{}{}
+
+%.IX pgmramp
+\shead{NAME}
+pgmramp - generate a grayscale ramp
+\shead{SYNOPSIS}
+{\bf pgmramp}
+{\bf -lr}{\rm $|$}{\bf -tb}
+$|$
+{\bf -rectangle}{\rm $|$}{\bf -ellipse}
+{\it width height}
+\shead{DESCRIPTION}
+Generates a graymap of the specified size containing a black-to-white ramp.
+%.IX "generating graymaps"
+These ramps are useful for multiplying with other images, using the
+{\it pnmarith}
+tool.
+%.IX pnmarith
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -lr}}
+\item[{{\bf -lr}}]
+A left to right ramp.
+\item[{{\bf -tb}}]
+A top to bottom ramp.
+\item[{{\bf -rectangle}}]
+A rectangular ramp.
+\item[{{\bf -ellipse}}]
+An elliptical ramp.
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+pnmarith(1), pgm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pgmramp.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:35 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgmtexture.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgmtexture.1
+%
+\phead{pgmtexture}{1}{22 Aug 1991}{}{}
+
+%.IX pgmtexture
+\shead{NAME}
+pgmtexture - calculate textural features on a portable graymap
+\shead{SYNOPSIS}
+{\bf pgmtexture}
+{\rm [}{\bf -d}
+{\it d}{\rm ]}
+{\rm [}{\it pgmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable graymap as input.  Calculates textural features
+based on spatial dependence matrices at 0, 45, 90, and 135 degrees for
+a distance 
+{\it d}
+(default = 1).
+Textural features include:
+\begin{IPlist}
+\IPitem{{}}
+(1) Angular Second Moment,
+\nwl
+(2) Contrast,
+\nwl
+(3) Correlation,
+\nwl
+(4) Variance,          
+\nwl
+(5) Inverse Difference Moment,
+\nwl
+(6) Sum Average,
+\nwl
+(7) Sum Variance,
+\nwl
+(8) Sum Entropy,
+\nwl
+(9) Entropy,
+\nwl
+(10) Difference Variance,
+\nwl
+(11) Difference Entropy,
+\nwl
+(12, 13) Information Measures of Correlation, and
+\nwl
+(14) Maximal Correlation Coefficient.
+\end{IPlist}
+
+\par
+Algorithm taken from:
+\nwl
+Haralick, R.M., K. Shanmugam, and I. Dinstein. 1973. Textural features
+for image classification.  
+{\it IEEE Transactions on Systems, Man,}
+{\it and Cybertinetics,}
+SMC-3(6):610-621.
+\shead{BUGS}
+The program can run incredibly slow for large images (larger than 64 x 64)
+and command line options are limited.
+The method for finding (14) the maximal correlation coefficient, which
+requires finding the second largest eigenvalue of a matrix Q, does not
+always converge.
+\shead{REFERENCES}
+{\it IEEE Transactions on Systems, Man,}
+{\it and Cybertinetics,}
+SMC-3(6):610-621.
+\shead{SEE ALSO}
+pgm(5), pnmcut(1)
+\shead{AUTHOR}
+\copyright 1991 by Texas Agricultural Experiment Station, employer for
+hire of James Darrell McCauley. 
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% THE TEXAS AGRICULTURAL EXPERIMENT STATION (TAES) AND THE TEXAS A&M
+% UNIVERSITY SYSTEM (TAMUS) MAKE NO EXPRESS OR IMPLIED WARRANTIES
+% (INCLUDING BY WAY OF EXAMPLE, MERCHANTABILITY) WITH RESPECT TO ANY
+% ITEM, AND SHALL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL
+% OR CONSEQUENTAL DAMAGES ARISING OUT OF THE POSESSION OR USE OF
+% ANY SUCH ITEM. LICENSEE AND/OR USER AGREES TO INDEMNIFY AND HOLD
+% TAES AND TAMUS HARMLESS FROM ANY CLAIMS ARISING OUT OF THE USE OR
+% POSSESSION OF SUCH ITEMS.
+%
+% end of input file: pgmtexture.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:31 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgmtofits.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgmtofits.1
+%
+\phead{pgmtofits}{1}{20 September 1989}{}{}
+
+%.IX pgmtofits
+\shead{NAME}
+pgmtofits - convert a portable graymap into FITS format
+\shead{SYNOPSIS}
+{\bf pgmtofits}
+{\rm [}{\it pgmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable graymap as input.
+Produces a FITS file as output.
+%.IX FITS
+\par
+FITS stands for Flexible Image Transport System.  A full description
+can be found in Astronomy \& Astrophysics Supplement Series 44 (1981),
+page 363.
+\shead{SEE ALSO}
+fitstopgm(1), pgm(5)
+\shead{AUTHOR}
+\copyright 1989 by Wilson H. Bent (whb@hoh-2.att.com).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pgmtofits.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:31 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgmtofs.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgmtofs.1
+%
+\phead{pgmtofs}{1}{18 May 1990}{}{}
+
+%.IX pgmtofs
+\shead{NAME}
+pgmtofs - convert portable graymap to Usenix FaceSaver(tm) format
+\shead{SYNOPSIS}
+{\bf pgmtofs}
+{\rm [}{\it pgmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable graymap as input.
+Produces Usenix FaceSaver(tm) format as output.
+%.IX FaceSaver
+\par
+FaceSaver is a registered trademark of Metron Computerware Ltd. of
+Oakland, CA.
+\shead{SEE ALSO}
+fstopgm(1), pgm(5)
+\shead{AUTHOR}
+\copyright 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pgmtofs.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:32 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgmtolispm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgmtolispm.1
+%
+\phead{pgmtolispm}{1}{06 March 1990}{}{}
+
+%.IX pgmtolispm
+\shead{NAME}
+pgmtolispm - convert a portable graymap into Lisp Machine format
+\shead{SYNOPSIS}
+{\bf pgmtolispm}
+{\rm [}{\it pgmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable graymap as input.
+Produces a Lisp Machine bitmap as output.
+%.IX "Lisp Machine bitmap"
+\par
+This is the file format read by the tv:read-bit-array-file function on
+TI Explorer and Symbolics lisp machines.
+\par
+Given a pgm (instead of a pbm) a multi-plane image will be output.
+This is probably not useful unless you have a color lisp machine.
+\par
+Multi-plane bitmaps on lisp machines are color; but the lispm image file
+format does not include a color map, so we must treat it as a graymap 
+instead.  This is unfortunate.
+\shead{SEE ALSO}
+lispmtopgm(1), pgm(5)
+\shead{BUGS}
+Output width is always rounded up to the nearest multiple of 32; this might 
+not always be what you want, but it probably is (arrays which are not 
+modulo 32 cannot be passed to the Lispm BITBLT function, and thus cannot 
+easily be displayed on the screen).
+\par
+No color.
+\shead{AUTHOR}
+\copyright 1991 by Jamie Zawinski and Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pgmtolispm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:33 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgmtopbm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgmtopbm.1
+%
+\phead{pgmtopbm}{1}{26 July 1988}{}{}
+
+%.IX pgmtopbm
+\shead{NAME}
+pgmtopbm - convert a portable graymap into a portable bitmap
+\shead{SYNOPSIS}
+{\bf pgmtopbm}
+{\rm [}{\bf -floyd}{\rm $|$}{\bf -fs}{\rm $|$}{\bf -threshold}
+{\rm $|$}{\bf -dither8}{\rm $|$}{\bf -d8}{\rm $|$}{\bf -cluster3}
+{\rm $|$}{\bf -c3}{\rm $|$}{\bf -cluster4}{\rm $|$}{\bf -c4}
+{\rm $|$}{\bf -cluster8}{\rm $|$}{\bf -c8}{\rm ]}
+{\rm [}{\bf -value}
+{\it val}{\rm ]}
+{\rm [}{\it pgmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable graymap as input.
+Produces a portable bitmap as output.
+%.IX halftoning
+\par
+Note that there is no pbmtopgm converter, because any pgm program can
+read pbm files automagically.
+\shead{OPTIONS}
+\par
+The default quantization method is boustrophedonic Floyd-Steinberg error
+diffusion
+{\rm (}{\bf -floyd}
+or
+{\bf -fs}{\rm ).}
+%.IX Floyd-Steinberg
+%.IX "error diffusion"
+Also available are simple thresholding
+{\rm (}{\bf -threshold}{\rm );}
+%.IX thresholding
+Bayer's ordered dither
+{\rm (}{\bf -dither8}{\rm )}
+with a 16x16 matrix; and three different sizes of 45-degree clustered-dot dither
+{\rm (}{\bf -cluster3}{\rm ,}
+{\bf -cluster4}{\rm ,}
+{\bf -cluster8}{\rm ).}
+%.IX dithering
+\par
+Floyd-Steinberg will almost always give the best looking results; however,
+looking good is not always what you want.
+For instance, thresholding can be used in a pipeline with the
+{\it pnmconvol}
+%.IX pnmconvol
+tool, for tasks like edge and peak detection.
+And clustered-dot dithering gives a newspaper-ish look, a useful special effect.
+\par
+The
+{\bf -value}
+flag alters the thresholding value for Floyd-Steinberg and
+simple thresholding.
+It should be a real number between 0 and 1.
+Above 0.5 means darker images; below 0.5 means lighter.
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{REFERENCES}
+The only reference you need for this stuff is ``Digital Halftoning'' by
+Robert Ulichney, MIT Press, ISBN 0--262--21009--6.
+\shead{SEE ALSO}
+pbmreduce(1), pgm(5), pbm(5), pnmconvol(1)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pgmtopbm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:43 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pgmtoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pgmtoppm.1
+%
+\phead{pgmtoppm}{1}{11 January 1991}{}{}
+
+%.IX pgmtoppm
+\shead{NAME}
+pgmtoppm - colorize a portable graymap into a portable pixmap
+\shead{SYNOPSIS}
+{\bf pgmtoppm}
+{\it colorspec}
+{\rm [}{\it pgmfile}{\rm ]}
+\nwl
+{\bf pgmtoppm}
+{\it colorspec1}{\bf -}{\it colorspec2}
+{\rm [}{\it pgmfile}{\rm ]}
+\nwl
+{\bf pgmtoppm -map}
+{\it mapfile}
+{\rm [}{\it pgmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable graymap as input.
+Colorizes it by multiplying the the gray values by specified color or colors,
+and produces a portable pixmap as output.
+%.IX colorization
+\par
+If only one color is specified, black in the pgm file stays black and
+white in the pgm file turns into the specified color in the ppm file.
+If two colors (separated by a dash) are specified, then black gets mapped
+to the first color and white gets mapped to the second.
+\par
+The color can be specified in five ways:
+%.IX "specifying colors"
+\begin{TPlist}{o}
+\item[{o}]
+A name, assuming
+that a pointer to an X11-style color names file was compiled in.
+\item[{o}]
+An X11-style hexadecimal specifier: rgb:r/g/b, where r g and b are
+each 1- to 4-digit hexadecimal numbers.
+\item[{o}]
+An X11-style decimal specifier: rgbi:r/g/b, where r g and b are
+floating point numbers between 0 and 1.
+\item[{o}]
+For backwards compatibility, an old-X11-style hexadecimal
+number: \#rgb, \#rrggbb, \#rrrgggbbb, or \#rrrrggggbbbb.
+\item[{o}]
+For backwards compatibility, a triplet of numbers
+separated by commas: r,g,b, where r g and b are
+floating point numbers between 0 and 1.
+(This style was added before MIT came up with the similar rgbi style.)
+\end{TPlist}
+
+\par
+Also, the
+{\bf -map}
+flag lets you specify an entire colormap to be used.
+The mapfile is just a
+{\it ppm}
+file; it can be any shape, all that matters
+is the colors in it and their order.
+In this case, black gets mapped into the first
+color in the map file, and white gets mapped to the last.
+\shead{SEE ALSO}
+rgb3toppm(1), ppmtopgm(1), ppmtorgb3(1), ppm(5), pgm(5)
+\shead{AUTHOR}
+\copyright 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pgmtoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:43 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pi1toppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pi1toppm.1
+%
+\phead{pi1toppm}{1}{19 July 1990}{}{}
+
+%.IX pi1toppm
+\shead{NAME}
+pi1toppm - convert an Atari Degas .pi1 into a portable pixmap
+\shead{SYNOPSIS}
+{\bf pi1toppm}
+{\rm [}{\it pi1file}{\rm ]}
+\shead{DESCRIPTION}
+Reads an Atari Degas .pi1 file as input.
+%.IX Atari
+%.IX "Degas .pi1"
+Produces a portable pixmap as output.
+\shead{SEE ALSO}
+ppmtopi1(1), ppm(5), pi3topbm(1), pbmtopi3(1)
+\shead{AUTHOR}
+\copyright 1991 by Steve Belczyk (seb3@gte.com) and Jef Poskanzer.
+% Permission to use, copy, modify and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pi1toppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:13 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pi3topbm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pi3topbm.1
+%
+\phead{pi3topbm}{1}{11 March 1990}{}{}
+
+%.IX pi3topbm
+\shead{NAME}
+pi3topbm - convert an Atari Degas .pi3 file into a portable bitmap
+\shead{SYNOPSIS}
+{\bf pi3topbm}
+{\rm [}{\it pi3file}{\rm ]}
+\shead{DESCRIPTION}
+Reads an Atari Degas .pi3 file as input.
+%.IX Atari
+%.IX "Degas .pi3"
+Produces a portable bitmap as output.
+\shead{SEE ALSO}
+pbmtopi3(1), pbm(5), pi1toppm(1), ppmtopi1(1)
+\shead{AUTHOR}
+\copyright 1988 by David Beckemeyer (bdt!david) and Diomidis D. Spinellis.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted,
+% provided that the above copyright notice appear in all copies and that
+% both that copyright notice and this permission notice appear in
+% supporting documentation.
+%
+% end of input file: pi3topbm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:44 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: picttoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: picttoppm.1
+%
+\phead{picttoppm}{1}{29 November 1991}{}{}
+
+%.IX picttoppm
+\shead{NAME}
+picttoppm - convert a Macintosh PICT file into a portable pixmap
+\shead{SYNOPSIS}
+{\bf picttoppm}
+{\rm [}{\bf -verbose}{\rm ]}
+{\rm [}{\bf -fullres}{\rm ]}
+{\rm [}{\bf -noheader}{\rm ]}
+{\rm [}{\it pictfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a PICT file (version 1 or 2) and outputs a portable pixmap.
+%.IX PICT
+%.IX Macintosh
+Useful as the first step in converting a scanned image to something
+that can be displayed on Unix.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf --fullres}}
+\item[{{\bf --fullres}}]
+Force any images in the PICT file to be output with at least their
+full resolution.  A PICT file may indicate that a contained
+image is to be scaled down before output.  This option forces images
+to retain their sizes and prevent information loss.
+\item[{{\bf --noheader}}]
+Do not skip the 512 byte header that is present on all PICT files.
+This is useful when you have PICT data that was not stored in
+the data fork of a PICT file.
+\item[{{\bf --verbose}}]
+Turns on verbose mode which prints a 
+a whole bunch of information that only
+{\it picttoppm}
+hackers really care about.
+\end{TPlist}
+
+\shead{BUGS}
+The PICT file format is a general drawing format.
+{\it picttoppm}
+only supports a small subset of its operations but is still very useful for
+files produced by scanning software.  In particular, text added to a
+scanned image will be silently ignored.
+\shead{SEE ALSO}
+Inside Macintosh volume 5,
+ppmtopict(1),
+ppm(5)
+\shead{AUTHOR}
+\copyright 1989 George Phillips (phillips@cs.ubc.ca).
+% Permission is granted to freely distribute this program in whole or in
+% part provided you don't make money off it, you don't pretend that you
+% wrote it and that this notice accompanies the code.
+%
+% George Phillips <phillips@cs.ubc.ca>
+% Department of Computer Science
+% University of British Columbia
+%
+% end of input file: picttoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:44 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pjtoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pjtoppm.1
+%
+\phead{pjtoppm}{1}{14 July 1991}{}{}
+
+%.IX pjtoppm
+\shead{NAME}
+pjtoppm - convert an HP PaintJet file to a portable pixmap
+\shead{SYNOPSIS}
+{\bf pjtoppm}
+{\rm [}{\it paintjet}{\rm ]}
+\shead{DESCRIPTION}
+Reads an HP PaintJet file as input and converts it into a portable pixmap.
+This was a quick hack to save some trees, and it only handles a small
+subset of the paintjet commands.
+In particular, it will only handle 
+enough commands to convert most raster image files.
+\shead{REFERENCES}
+HP PaintJet XL Color Graphics Printer User's Guide
+\shead{SEE ALSO}
+ppmtopj(1)
+\shead{AUTHOR}
+\copyright 1991 by Christos Zoulas.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pjtoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:19 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pktopbm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pktopbm.1
+%
+\phead{pktopbm}{1}{6 August 1990}{}{}
+
+\shead{NAME}
+pktopbm - convert packed (PK) format font into portable bitmap(s)
+\shead{SYNOPSIS}
+pktopbm pkfile[.pk] [-c num] pbmfile ...
+\shead{DESCRIPTION}
+Reads a packed (PK) font file as input, and produces portable bitmaps as
+output. If the filename ``-'' is used for any
+of the filenames, the standard input stream (or standard output where
+appropriate) will be used.
+\shead{OPTIONS}
+\begin{IPlist}
+\IPitem{{-c\ num}}
+Sets the character number of the next bitmap written to num.
+\end{IPlist}
+
+\shead{SEE ALSO}
+pbmtopk(1), pbm(5)
+\shead{AUTHOR}
+Adapted from Tom Rokicki's pxtopk by Angus Duggan (ajcd@dcs.ed.ac.uk.
+
+%
+% end of input file: pktopbm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:35 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnm.5
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnm.5
+%
+\phead{pnm}{5}{27 September 1991}{}{}
+
+\shead{NAME}
+pnm - portable anymap file format
+\shead{DESCRIPTION}
+The
+{\it pnm}
+programs operate on portable bitmaps, graymaps, and pixmaps, produced by the
+{\it pbm, pgm,}
+and
+{\it ppm}
+segments.  There is no file format associated with
+{\it pnm}
+itself.
+\shead{SEE ALSO}
+anytopnm(1), rasttopnm(1), tifftopnm(1), xwdtopnm(1),
+pnmtops(1), pnmtorast(1), pnmtotiff(1), pnmtoxwd(1),
+pnmarith(1), pnmcat(1), pnmconvol(1), pnmcrop(1), pnmcut(1),
+pnmdepth(1), pnmenlarge(1), pnmfile(1), pnmflip(1), pnmgamma(1),
+pnmindex(1), pnminvert(1), pnmmargin(1), pnmnoraw(1), pnmpaste(1),
+pnmrotate(1), pnmscale(1), pnmshear(1), pnmsmooth(1), pnmtile(1),
+ppm(5), pgm(5), pbm(5)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnm.5
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Mon Feb  7 08:47:49 1994
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmalias.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmalias.1
+%
+\phead{pnmalias}{1}{30 April 1992}{}{}
+
+\shead{NAME}
+pnmalias - antialias a portable anyumap.
+\shead{SYNOPSIS}
+{\bf pnmalias}
+{\rm [}{\bf -bgcolor}
+{\it color}{\rm ]}
+{\rm [}{\bf -fgcolor}
+{\it color}{\rm ]}
+{\rm [}{\bf -bonly}{\rm ]}
+{\rm [}{\bf -fonly}{\rm ]}
+{\rm [}{\bf -balias}{\rm ]}
+{\rm [}{\bf -falias}{\rm ]}
+{\rm [}{\bf -weight}
+{\it w}{\rm ]}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input, and applies anti-aliasing to background and
+foreground pixels.
+If the input file is a portable bitmap, the 
+output anti-aliased image is promoted to a graymap, and a message is printed
+informing the user of the change in format.
+\shead{OPTIONS}
+\par
+{\bf --bgcolor}
+{\it colorb,}
+{\bf --fgcolor}
+{\it colorf}
+\ind{1\parindent}set the background color to 
+{\it colorb,}
+and the foreground to color to
+{\it colorf.}
+Pixels with these values will be anti-aliased. by default,
+the background color is taken to be black, and foreground color
+is assumed to be white.  
+The colors can be specified in five ways:
+\begin{TPlist}{{\bf o}}
+\item[{{\bf o}}]
+A name, assuming
+that a pointer to an X11-style color names file was compiled in.
+\item[{{\bf o}}]
+An X11-style hexadecimal specifier: rgb:r/g/b, where r g and b are
+each 1- to 4-digit hexadecimal numbers.
+\item[{{\bf o}}]
+An X11-style decimal specifier: rgbi:r/g/b, where r g and b are
+floating point numbers between 0 and 1.
+\item[{{\bf o}}]
+For backwards compatibility, an old-X11-style hexadecimal
+number: \#rgb, \#rrggbb, \#rrrgggbbb, or \#rrrrggggbbbb.
+\item[{{\bf o}}]
+For backwards compatibility, a triplet of numbers
+separated by commas: r,g,b, where r g and b are
+floating point numbers between 0 and 1.
+(This style was added before MIT came up with the similar rgbi style.)
+\end{TPlist}
+\par\noindent
+Note that even when dealing with graymaps, background and foreground
+colors need to be specified in the fashion described above.
+In this case, background and foreground pixel values are taken to be the
+value of the red component for the given color.
+\ind{1\parindent}
+\par
+{\bf --bonly}{\rm ,}
+{\bf --fonly}
+\ind{1\parindent}Apply anti-aliasing only to background 
+{\rm (}{\bf --bonly}{\rm ),}
+or foreground
+{\rm (}{\bf --fonly}{\rm )}
+pixels.
+\ind{1\parindent}
+\par
+{\bf --balias}{\rm ,}
+{\bf --falias}
+\ind{1\parindent}Apply anti-aliasing to all pixels surrounding background
+{\rm (}{\bf --balias}{\rm ),}
+or foreground
+{\rm (}{\bf --falias}{\rm )}
+pixels.  By default, anti-aliasing takes place only among neighboring
+background and foreground pixels.
+\ind{1\parindent}
+\par
+{\bf --weight}
+{\it w}
+\ind{1\parindent}Use 
+{\it w}
+as the central weight for the aliasing filter.
+{\it W}
+must be a real number in the range
+0 $<$ 
+{\it w}
+$<$ 1.
+The lower the value of 
+{\it w}
+is, the ``blurrier'' the output image is.  The default is w = 1/3.
+\shead{SEE ALSO}
+pbmtext(1), pnmsmooth(1), pnm(5)
+\shead{AUTHOR}
+Copyright (C) 1992 by Alberto Accomazzi, Smithsonian Astrophysical Observatory.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmalias.1
+%--------------------------------------------------
+
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:17 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmarith.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmarith.1
+%
+\phead{pnmarith}{1}{26 August 1993}{}{}
+
+%.IX pnmarith
+\shead{NAME}
+pnmarith - perform arithmetic on two portable anymaps
+\shead{SYNOPSIS}
+{\bf pnmarith}
+{\bf -add}{\rm $|$}{\bf -subtract}{\rm $|$}{\bf -multiply}{\rm $|$}{\bf -difference}
+{\it pnmfile1 pnmfile2}
+\shead{DESCRIPTION}
+Reads two portable anymaps as input.
+Performs the specified arithmetic operation,
+and produces a portable anymap as output.
+The two input anymaps must be the same width and height.
+\par
+The arithmetic is performed between corresponding pixels in the two
+anymaps, as if maxval was 1.0, black was 0.0, and a linear scale in between.
+Results that fall outside of [0..1) are truncated.
+\par
+The operator
+{\it -difference}
+calculates the absolute value of
+{\it pnmarith -subtract pnmfile1 pnmfile2,}
+{\it i.e.}, no truncation is done.
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+pbmmask(1), pnmpaste(1), pnminvert(1), pnm(5)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer..
+Lightly modified by Marcel Wijkstra (wijkstra@fwi.uva.nl).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmarith.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:17 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmcat.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmcat.1
+%
+\phead{pnmcat}{1}{12 March 1989}{}{}
+
+%.IX pnmcat
+\shead{NAME}
+pnmcat - concatenate portable anymaps
+\shead{SYNOPSIS}
+{\bf pnmcat}
+{\rm [}{\bf -white}{\rm $|$}{\bf -black}{\rm ]}
+{\bf -leftright}{\rm $|$}{\bf -lr}
+{\rm [}{\bf -jtop}{\rm $|$}{\bf -jbottom}{\rm ]}
+{\it pnmfile pnmfile}
+{\rm ...}
+\nwl
+{\bf pnmcat}
+{\rm [}{\bf -white}{\rm $|$}{\bf -black}{\rm ]}
+{\bf -topbottom}{\rm $|$}{\bf -tb}
+{\rm [}{\bf -jleft}{\rm $|$}{\bf -jright}{\rm ]}
+{\it pnmfile pnmfile}
+{\rm ...}
+\shead{DESCRIPTION}
+Reads portable anymaps as input.
+Concatenates them either left to right or top to bottom, and produces a
+portable anymap as output.
+%.IX concatenation
+\shead{OPTIONS}
+\par
+If the anymaps are not all the same height (left-right) or width (top-bottom),
+the smaller ones have to be justified with the largest.
+By default, they get centered, but you can specify one side or the other
+with one of the -j* flags.
+So,
+{\bf -topbottom -jleft}
+would stack the anymaps on top of each other, flush with the left edge.
+\par
+The
+{\bf -white}
+and
+{\bf -black}
+flags specify what color to use to fill in the extra space
+when doing this justification.
+If neither is specified, the program makes a guess.
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+pnm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmcat.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:31 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmcomp.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmcomp.1
+%
+\phead{pnmcomp}{1}{21 February 1989}{}{}
+
+%.IX pnmcomp
+\shead{NAME}
+pnmcomp - composite two portable anymap files together
+\shead{SYNOPSIS}
+{\bf pnmcomp}
+{\rm [}{\it -invert}{\rm ]}
+{\rm [}{\it -xoff}{\rm N}{\it ]}
+{\rm [}{\it -yoff}{\rm N}{\it ]}
+{\rm [}{\it -alpha}{\rm pgmfile}{\it ]}
+{\rm overlay}
+{\rm [}{\it pnm-input}{\rm ]}
+{\rm [}{\it pnm-output}{\rm ]}
+\shead{DESCRIPTION}
+Reads in a portable any map image and put a overlay upon it, with optional
+alpha mask.  The 
+{\it -alpha pgmfile}
+allows you to also add an alpha mask file to the compositing process, the
+range of max and min can be swapped by using the
+{\it -invert}
+option.
+The
+{\it -xoff}
+and
+{\it -yoff}
+arguments can be negative, allowing you to shift the overlay off the
+top corner of the screen.
+\shead{SEE ALSO}
+pnm(5)
+\shead{AUTHOR}
+\copyright 1992 by David Koblas (koblas@mips.com).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmcomp.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:18 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmconvol.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmconvol.1
+%
+\phead{pnmconvol}{1}{13 January 1991}{}{}
+
+%.IX pnmconvol
+\shead{NAME}
+pnmconvol - general MxN convolution on a portable anymap
+\shead{SYNOPSIS}
+{\bf pnmconvol}
+{\it convolutionfile}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads two portable anymaps as input.
+Convolves the second using the first,
+and writes a portable anymap as output.
+%.IX convolution
+\par
+Convolution means replacing each pixel with a weighted average of the
+nearby pixels.  The weights and the area to average are determined by
+the convolution matrix.
+The unsigned numbers in the convolution file are offset by -maxval/2 to
+make signed numbers, and then normalized, so the actual values in the
+convolution file are only relative.
+\par
+Here is a sample convolution file;
+it does a simple average of the nine immediate neighbors, resulting
+in a smoothed image:
+\nofill
+    P2
+    3 3
+    18
+    10 10 10
+    10 10 10
+    10 10 10
+\fill
+\par
+To see how this works, do the above-mentioned offset: 10 - 18/2 gives 1.
+The possible range of values is from 0 to 18, and after the offset
+that's -9 to 9.  The normalization step makes the range -1 to 1, and
+the values get scaled correspondingly so they become 1/9 - exactly what
+you want.
+The equivalent matrix for 5x5 smoothing would have maxval 50 and be
+filled with 26.
+\par
+The convolution file will usually be a graymap,
+so that the same convolution gets applied to each color component.
+However, if you want to use a pixmap and do a different convolution to
+different colors, you can certainly do that.
+\shead{SEE ALSO}
+pnmsmooth(1), pnm(5)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmconvol.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:18 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmcrop.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmcrop.1
+%
+\phead{pnmcrop}{1}{25 February 1989}{}{}
+
+%.IX pnmcrop
+\shead{NAME}
+pnmcrop - crop a portable anymap
+\shead{SYNOPSIS}
+{\bf pnmcrop}
+{\rm [}{\bf -white}{\rm $|$}{\bf -black}{\rm ]}
+{\rm [}{\bf -left}{\rm ]}
+{\rm [}{\bf -right}{\rm ]}
+{\rm [}{\bf -top}{\rm ]}
+{\rm [}{\bf -bottom}{\rm ]}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Removes edges that are the background color,
+and produces a portable anymap as output.
+%.IX cropping
+\shead{OPTIONS}
+\par
+By default, it makes a guess as to what the background color is.
+You can override the default with the
+{\bf -white}
+and
+{\bf -black}
+flags.
+\par
+The options
+{\bf -left, -right, -top}
+and
+{\bf -bottom}
+restrict cropping to the sides specified. The default is to crop all sides of
+the image.
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+pnmcut(1), pnm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmcrop.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:19 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmcut.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmcut.1
+%
+\phead{pnmcut}{1}{21 February 1989}{}{}
+
+%.IX pnmcut
+\shead{NAME}
+pnmcut - cut a rectangle out of a portable anymap
+\shead{SYNOPSIS}
+{\bf pnmcut}
+{\it x y width height}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Extracts the specified rectangle,
+and produces a portable anymap as output.
+%.IX cut
+The
+{\it x}
+and
+{\it y}
+can be negative, in which case they are interpreted
+relative to the right and bottom of the anymap, respectively.
+\shead{SEE ALSO}
+pnm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmcut.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:20 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmdepth.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmdepth.1
+%
+\phead{pnmdepth}{1}{12 January 1991}{}{}
+
+%.IX pnmdepth
+\shead{NAME}
+pnmdepth - change the maxval in a portable anymap
+\shead{SYNOPSIS}
+{\bf pnmdepth}
+{\it newmaxval}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Scales all the pixel values, and writes out the image with the new maxval.
+Scaling the colors down to a smaller maxval will result in some loss
+of information.
+\par
+Be careful of off-by-one errors when choosing the new maxval.
+For instance, if you want the color values to be five bits wide,
+use a maxval of 31, not 32.
+\shead{SEE ALSO}
+pnm(5), ppmquant(1), ppmdither(1)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmdepth.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:20 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmenlarge.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmenlarge.1
+%
+\phead{pnmenlarge}{1}{26 February 1989}{}{}
+
+%.IX pnmenlarge
+\shead{NAME}
+pnmenlarge - read a portable anymap and enlarge it N times
+\shead{SYNOPSIS}
+{\bf pnmenlarge}
+{\it N}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Replicates its pixels
+{\it N}
+times, and produces a portable anymap as output.
+%.IX enlarging
+\par
+{\it pnmenlarge}
+can only enlarge by integer factors.
+The slower but more general
+{\it pnmscale}
+%.IX pnmscale
+can enlarge or reduce by arbitrary
+factors, and
+{\it pbmreduce}
+%.IX pbmreduce
+can reduce by integer factors, but only for bitmaps.
+\par
+If you enlarge by a factor of 3 or more, you should probably add a
+{\it pnmsmooth}
+%.IX pnmsmooth
+step; otherwise, you can see the original pixels in the resulting image.
+\shead{SEE ALSO}
+pbmreduce(1), pnmscale(1), pnmsmooth(1), pnm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmenlarge.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:21 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmfile.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmfile.1
+%
+\phead{pnmfile}{1}{9 January 1991}{}{}
+
+%.IX pnmfile
+\shead{NAME}
+pnmfile - describe a portable anymap
+\shead{SYNOPSIS}
+{\bf pnmfile}
+{\rm [}{\it pnmfile}{\rm ]}
+{\rm ...}
+\shead{DESCRIPTION}
+Reads one or more portable anymaps as input.
+Writes out short descriptions of the image type, size, etc.
+This is mostly for use in shell scripts, so the format is not
+particularly pretty.
+\shead{SEE ALSO}
+pnm(5), file(1)
+\shead{AUTHOR}
+\copyright 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmfile.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:21 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmflip.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmflip.1
+%
+\phead{pnmflip}{1}{25 July 1989}{}{}
+
+%.IX pnmflip
+\shead{NAME}
+pnmflip - perform one or more flip operations on a portable anymap
+\shead{SYNOPSIS}
+{\bf pnmflip}
+{\rm [}{\bf -leftright}{\rm $|$}{\bf -lr}{\rm ]}
+{\rm [}{\bf -topbottom}{\rm $|$}{\bf -tb}{\rm ]}
+{\rm [}{\bf -transpose}{\rm $|$}{\bf -xy}{\rm ]}
+{\rm [}{\bf -rotate90}{\rm $|$}{\bf -r90}{\rm $|$}{\bf -ccw}
+{\rm ]}
+{\rm [}{\bf -rotate270}{\rm $|$}{\bf -r270}{\rm $|$}{\bf -cw}
+{\rm ]}
+{\rm [}{\bf -rotate180}{\rm $|$}{\bf -r180}{\rm ]}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Performs one or more flip operations, in the order specified, and
+writes out a portable anymap.
+%.IX rotation
+%.IX reflection
+%.IX transposition
+\shead{OPTIONS}
+\par
+The flip operations available are: left for right
+{\rm (}{\bf -leftright}
+or
+{\bf -lr}{\rm );}
+top for bottom
+{\rm (}{\bf -topbottom}
+or
+{\bf -tb}{\rm );}
+and transposition
+{\rm (}{\bf -transpose}
+or
+{\bf -xy}{\rm ).}
+In addition, some canned concatenations are available:
+{\bf -rotate90}
+or
+{\bf -ccw}
+is equivalent to
+{\bf -transpose}
+{\bf -topbottom}{\rm ;}
+{\bf -rotate270}
+or
+{\bf -cw}
+is equivalent to
+{\bf -transpose}
+{\bf -leftright}{\rm ;}
+and
+{\bf -rotate180}
+is equivalent to
+{\bf -leftright}
+{\bf -topbottom}{\rm .}
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+pnmrotate(1), pnm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmflip.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:29 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmgamma.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmgamma.1
+%
+\phead{pnmgamma}{1}{12 January 1991}{}{}
+
+%.IX pnmgamma
+\shead{NAME}
+pnmgamma - perform gamma correction on a portable anymap
+\shead{SYNOPSIS}
+{\bf pnmgamma}
+{\it value}
+{\rm [}{\it pnmfile}{\rm ]}
+\nwl
+{\bf pnmgamma}
+{\it redvalue greenvalue bluevalue}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Performs gamma correction,
+and produces a portable anymap as output.
+%.IX "gamma correction"
+\par
+The arguments specify what gamma value(s) to use.
+A value of 1.0 leaves the image alone, less than one darkens it,
+and greater than one lightens it.
+\shead{SEE ALSO}
+pnm(5)
+\shead{AUTHOR}
+\copyright 1991 by Bill Davidson and Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmgamma.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:22 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnminvert.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnminvert.1
+%
+\phead{pnminvert}{1}{08 August 1989}{}{}
+
+%.IX pnminvert
+\shead{NAME}
+pnminvert - invert a portable anymap
+\shead{SYNOPSIS}
+{\bf pnminvert}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Inverts it black for white and produces a portable anymap as output.
+\shead{SEE ALSO}
+pnm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnminvert.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:32 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmnlfilt.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmnlfilt.1
+%
+\phead{pnmnlfilt}{1}{5 February 1993}{}{}
+
+%.IX pnmnlfilt
+\shead{NAME}
+pnmnlfilt - non-linear filters: smooth, alpha trim mean, optimal
+estimation smoothing, edge enhancement.
+\shead{SYNOPSIS}
+{\bf pnmnlfilt}
+{\rm alpha}
+{\rm radius}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+%.IX smoothing
+%.IX dithering
+%.IX alpha trim
+%.IX mean filter
+%.IX median filter
+%.IX optimal estimation
+This is something of a swiss army knife filter. It has 3 distinct operating
+modes. In all of the modes each pixel in the image is examined and processed
+according to it and its surrounding pixels values. Rather than using the
+9 pixels in a 3x3 block, 7 hexagonal area samples are taken, the size of
+the hexagons being controlled by the radius parameter. A radius value of
+0.3333 means that the 7 hexagons exactly fit into the center pixel ({\it i.e.},
+there will be no filtering effect). A radius value of 1.0 means that
+the 7 hexagons exactly fit a 3x3 pixel array.
+\shead{Alpha trimmed mean filter.	(0.0 $<$= alpha $<$= 0.5)}
+\par
+The value of the center pixel will be
+replaced by the mean of the 7 hexagon values, but the 7 values are
+sorted by size and the top and bottom alpha portion of the 7 are
+excluded from the mean.  This implies that an alpha value of 0.0 gives
+the same sort of output as a normal convolution ({\it i.e.}, averaging or
+smoothing filter), where radius will determine the ``strength'' of the
+filter. A good value to start from for subtle filtering is alpha = 0.0, radius = 0.55
+For a more blatant effect, try alpha 0.0 and radius 1.0
+\par
+An alpha value of 0.5 will cause the median value of the
+7 hexagons to be used to replace the center pixel value. This sort
+of filter is good for eliminating ``pop'' or single pixel noise from
+an image without spreading the noise out or smudging features on
+the image. Judicious use of the radius parameter will fine tune the
+filtering. Intermediate values of alpha give effects somewhere
+between smoothing and ``pop'' noise reduction. For subtle filtering
+try starting with values of alpha = 0.4, radius = 0.6  For a more blatant
+effect try alpha = 0.5, radius = 1.0
+\shead{Optimal estimation smoothing. (1.0 $<$= alpha $<$= 2.0)}
+\par
+This type of filter applies a smoothing filter adaptively over the image.
+For each pixel the variance of the surrounding hexagon values is calculated,
+and the amount of smoothing is made inversely proportional to it. The idea
+is that if the variance is small then it is due to noise in the image, while
+if the variance is large, it is because of ``wanted'' image features. As usual
+the radius parameter controls the effective radius, but it probably advisable to
+leave the radius between 0.8 and 1.0 for the variance calculation to be meaningful.
+The alpha parameter sets the noise threshold, over which less smoothing will be done.
+This means that small values of alpha will give the most subtle filtering effect,
+while large values will tend to smooth all parts of the image. You could start
+with values like alpha = 1.2, radius = 1.0 and try increasing or decreasing the
+alpha parameter to get the desired effect. This type of filter is best for
+filtering out dithering noise in both bitmap and color images.
+\shead{Edge enhancement. (-0.1 $>$= alpha $>$= -0.9)}
+\par
+This is the opposite type of filter to the smoothing filter. It enhances
+edges. The alpha parameter controls the amount of edge enhancement, from
+subtle (-0.1) to blatant (-0.9). The radius parameter controls the effective
+radius as usual, but useful values are between 0.5 and 0.9. Try starting
+with values of alpha = 0.3, radius = 0.8
+\shead{Combination use.}
+\par
+The various modes of 
+{\bf pnmnlfilt}
+can be used one after the other to get the desired result. For instance to
+turn a monochrome dithered image into a grayscale image you could try
+one or two passes of the smoothing filter, followed by a pass of the optimal estimation
+filter, then some subtle edge enhancement. Note that using edge enhancement is
+only likely to be useful after one of the non-linear filters (alpha trimmed mean
+or optimal estimation filter), as edge enhancement is the direct opposite of
+smoothing.
+\par
+For reducing color quantization noise in images ({\it i.e.}, turning .gif files back into
+24 bit files) you could try a pass of the optimal estimation filter
+(alpha 1.2, radius 1.0), a pass of the median filter (alpha 0.5, radius 0.55),
+and possibly a pass of the edge enhancement filter.
+Several passes of the optimal estimation filter with declining alpha
+values are more effective than a single pass with a large alpha value.
+As usual, there is a tradeoff between filtering effectiveness and loosing
+detail. Experimentation is encouraged.
+\shead{References:}
+\par
+The alpha-trimmed mean filter is 
+based on the description in IEEE CG\&A May 1990 
+Page 23 by Mark E. Lee and Richard A. Redner,
+and has been enhanced to allow continuous alpha adjustment.
+\par
+The optimal estimation filter is taken from an article ``Converting Dithered
+Images Back to Gray Scale'' by Allen Stenger, Dr Dobb's Journal, November
+1992, and this article references ``Digital Image Enhancement and Noise Filtering by
+Use of Local Statistics'', Jong-Sen Lee, IEEE Transactions on Pattern Analysis and
+Machine Intelligence, March 1980.
+\par
+The edge enhancement details are from pgmenhance(1),
+which is taken from Philip R. Thompson's ``xim''
+program, which in turn took it from section 6 of ``Digital Halftones by
+Dot Diffusion'', D. E. Knuth, ACM Transaction on Graphics Vol. 6, No. 4,
+October 1987, which in turn got it from two 1976 papers by J. F. Jarvis
+{\it et. al.}
+\shead{SEE ALSO}
+pgmenhance(1), pnmconvol(1), pnm(5)
+\shead{BUGS}
+Integers and tables may overflow if PPM\_MAXMAXVAL is greater than 255.
+\shead{AUTHOR}
+Graeme W. Gill    graeme@labtam.oz.au
+%
+% end of input file: pnmnlfilt.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:23 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmnoraw.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmnoraw.1
+%
+\phead{pnmnoraw}{1}{8 January 1991}{}{}
+
+%.IX pnmnoraw
+\shead{NAME}
+pnmnoraw - force a portable anymap into plain format
+\shead{SYNOPSIS}
+{\bf pnmnoraw}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Writes it out in plain (non-raw) format.
+This is fairly useless if you haven't defined the PBMPLUS\_RAWBITS
+compile-time option.
+%.IX RAWBITS
+\shead{SEE ALSO}
+pnm(5)
+\shead{AUTHOR}
+\copyright 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmnoraw.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:33 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmpad.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmpad.1
+%
+\phead{pnmpad}{12 Dec 1990}{}{}{}
+
+\shead{NAME}
+pnmpad - add borders to portable anymap
+\shead{SYNOPSIS}
+pnmpad [-white$|$-black] [-l\#] [-r\#] [-t\#] [-b\#] [pnmfile]
+\shead{DESCRIPTION}
+Reads a portable anymap as input. Outputs a portable anymap with extra
+borders of the sizes specified. The colour of the borders can be set to
+black or white (default black).
+
+\shead{SEE ALSO}
+pbmmake(1), pnmpaste(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1990 by Angus Duggan.
+\copyright 1989 by Jef Poskanzer.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted, provided
+that the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.  This software is provided "as is" without express or
+implied warranty.
+%
+% end of input file: pnmpad.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:23 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmpaste.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmpaste.1
+%
+\phead{pnmpaste}{1}{21 February 1991}{}{}
+
+%.IX pnmpaste
+\shead{NAME}
+pnmpaste - paste a rectangle into a portable anymap
+\shead{SYNOPSIS}
+{\bf pnmpaste}
+{\rm [}{\bf -replace}{\rm $|$}{\bf -or}{\rm $|$}{\bf -and}
+{\rm $|$}{\bf -xor}{\rm ]}
+{\it frompnmfile x y}
+{\rm [}{\it intopnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads two portable anymaps as input.
+Inserts the first anymap into the second at the specified location,
+and produces a portable anymap the same size as the second as output.
+%.IX paste
+If the second anymap is not specified, it is read from stdin.
+The
+{\it x}
+and
+{\it y}
+can be negative, in which case they are interpreted
+relative to the right and bottom of the anymap, respectively.
+\par
+This tool is most useful in combination with
+{\it pnmcut}{\rm .}
+%.IX pnmcut
+For instance, if you want to edit a small segment of a large
+image, and your image editor cannot edit the
+large image, you can cut out the segment you are interested in,
+edit it, and then paste it back in.
+\par
+Another useful companion tool is
+{\it pbmmask}{\rm .}
+%.IX pnmmask
+%.OPTIONS
+\par
+The optional flag specifies the operation to use when doing the paste.
+The default is
+{\bf -replace}{\rm .}
+The other, logical operations are only allowed if both input images
+are bitmaps.
+%.IX "logical operations"
+These operations act as if white is TRUE and black is FALSE.
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+pnmcut(1), pnminvert(1), pnmarith(1), pnm(5), pbmmask(1)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmpaste.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:29 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmrotate.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmrotate.1
+%
+\phead{pnmrotate}{1}{12 January 1991}{}{}
+
+%.IX pnmrotate
+\shead{NAME}
+pnmrotate - rotate a portable anymap by some angle
+\shead{SYNOPSIS}
+{\bf pnmrotate}
+{\rm [}{\bf -noantialias}{\rm ]}
+{\it angle}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Rotates it by the specified angle
+and produces a portable anymap as output.
+%.IX rotation
+If the input file is in color, the output will be too,
+otherwise it will be grayscale.
+The angle is in degrees (floating point), measured counter-clockwise.
+It can be negative, but it should be between -90 and 90.
+Also, for rotations greater than 45 degrees you may get better results
+if you first use
+{\it pnmflip}
+%.IX pnmflip
+to do a 90 degree rotation and then
+{\it pnmrotate}
+less than 45 degrees back the other direction
+\par
+The rotation algorithm is Alan Paeth's three-shear method.
+Each shear is implemented by looping over the source pixels and distributing
+fractions to each of the destination pixels.
+This has an ``anti-aliasing'' effect - it avoids jagged edges and similar
+artifacts.
+%.IX anti-aliasing
+However, it also means that the original colors or gray levels in the image
+are modified.
+If you need to keep precisely the same set of colors, you can use the
+{\bf -noantialias}
+flag.  This does the shearing by moving pixels without changing their values.
+If you want anti-aliasing and don't care about the precise colors, but
+still need a limited *number* of colors, you can run the result through
+{\it ppmquant}{\rm .}
+%.IX ppmquant
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{REFERENCES}
+``A Fast Algorithm for General Raster Rotation'' by Alan Paeth,
+Graphics Interface '86, pp. 77-81.
+\shead{SEE ALSO}
+pnmshear(1), pnmflip(1), pnm(5), ppmquant(1)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmrotate.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:24 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmscale.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmscale.1
+%
+\phead{pnmscale}{1}{12 January 1991}{}{}
+
+%.IX pnmscale
+\shead{NAME}
+pnmscale - scale a portable anymap
+\shead{SYNOPSIS}
+{\bf pnmscale}
+{\it s}
+{\rm [}{\it pnmfile}{\rm ]}
+\nwl
+{\bf pnmscale}
+{\bf -xsize}{\rm $|$}{\bf -width}{\rm $|$}{\bf -ysize}{\rm $|$}
+{\bf -height}
+{\it s}
+{\rm [}{\it pnmfile}{\rm ]}
+\nwl
+{\bf pnmscale}
+{\bf -xscale}{\rm $|$}{\bf -yscale}
+{\it s}
+{\rm [}{\it pnmfile}{\rm ]}
+\nwl
+{\bf pnmscale}
+{\bf -xscale}{\rm $|$}{\bf -xsize}{\rm $|$}{\bf -width}
+{\it s}
+{\bf -yscale}{\rm $|$}{\bf -ysize}{\rm $|$}{\bf -height}
+{\it s}
+{\rm [}{\it pnmfile}{\rm ]}
+\nwl
+{\bf pnmscale -xysize}
+{\it x y}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Scales it by the specified factor or factors and produces a portable
+anymap as output.
+%.IX shrinking
+%.IX enlarging
+If the input file is in color, the output will be too,
+otherwise it will be grayscale.
+You can both enlarge (scale factor $>$ 1) and reduce (scale factor $<$ 1).
+\par
+You can specify one dimension as a pixel size, and the other dimension
+will be scaled correspondingly.
+\par
+You can specify one dimension as a scale, and the other dimension
+will not be scaled.
+\par
+You can specify different sizes or scales for each axis.
+\par
+Or, you can use the special
+{\bf -xysize}
+flag, which fits the image into
+the specified size without changing the aspect ratio.
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\par
+If you enlarge by a factor of 3 or more, you should probably add a
+{\it pnmsmooth}
+%.IX pnmsmooth
+step; otherwise, you can see the original pixels in the resulting image.
+\shead{SEE ALSO}
+pbmreduce(1), pnmenlarge(1), pnmsmooth(1), pnm(5)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmscale.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:30 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmshear.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmshear.1
+%
+\phead{pnmshear}{1}{12 January 1991}{}{}
+
+%.IX pnmshear
+\shead{NAME}
+pnmshear - shear a portable anymap by some angle
+\shead{SYNOPSIS}
+{\bf pnmshear}
+{\rm [}{\bf -noantialias}{\rm ]}
+{\it angle}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Shears it by the specified angle and produces a portable
+anymap as output.
+%.IX shearing
+If the input file is in color, the output will be too,
+otherwise it will be grayscale.
+The angle is in degrees (floating point), and measures this:
+\nofill
+    +-------+  +-------+
+    $|$       $|$  $|$\bs        \bs 
+    $|$  OLD  $|$  $|$ \bs   NEW  \bs 
+    $|$       $|$  $|$an\bs        \bs 
+    +-------+  $|$gle+-------+
+\fill
+If the angle is negative, it shears the other way:
+\nofill
+    +-------+  $|$-an+-------+
+    $|$       $|$  $|$gl/       /
+    $|$  OLD  $|$  $|$e/  NEW  /
+    $|$       $|$  $|$/       /
+    +-------+  +-------+
+\fill
+The angle should not get too close to 90 or -90, or the resulting
+anymap will be unreasonably wide.
+\par
+The shearing is implemented by looping over the source pixels and distributing
+fractions to each of the destination pixels.
+This has an ``anti-aliasing'' effect - it avoids jagged edges and similar
+artifacts.
+%.IX anti-aliasing
+However, it also means that the original colors or gray levels in the image
+are modified.
+If you need to keep precisely the same set of colors, you can use
+the
+{\bf -noantialias}
+flag.  This does the shearing by moving pixels without changing their values.
+If you want anti-aliasing and don't care about the precise colors, but
+still need a limited *number* of colors, you can run the result through
+{\it ppmquant}{\rm .}
+%.IX ppmquant
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+pnmrotate(1), pnmflip(1), pnm(5), ppmquant(1)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmshear.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:24 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmtile.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmtile.1
+%
+\phead{pnmtile}{1}{13 May 1989}{}{}
+
+%.IX pnmtile
+\shead{NAME}
+pnmtile - replicate a portable anymap into a specified size
+\shead{SYNOPSIS}
+{\bf pnmtile}
+{\it width height}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Replicates it until it is the specified size,
+and produces a portable anymap as output.
+%.IX tiling
+\shead{SEE ALSO}
+pnm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmtile.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Mon Feb  7 08:49:06 1994
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmtofits.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmtofits.1
+%
+\phead{pnmtofits}{1}{5 Dec 1992}{}{}
+\shead{NAME}
+pnmtofits - convert a portable anymap into FITS format
+\shead{SYNOPSIS}
+{\bf pnmtofits}
+{\rm [}{\bf --max}
+{\it f}{\rm ]}
+{\rm [}{\bf --min}
+{\it f}{\rm ]}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Produces a FITS (Flexible Image Transport System) file as output.
+The resolution of the output file is either 8 bits/pixel,
+or 16 bits/pixel, depending on the value of maxval in the input file.
+If the input file is a portable bitmap or a portable graymap, the output file
+consists of a single plane image (NAXIS = 2). If instead the input file is
+a portable pixmap, the output file will consist of a three-plane image
+(NAXIS = 3, NAXIS3 = 3).
+A full description of the FITS format
+can be found in Astronomy \& Astrophysics Supplement Series 44 (1981), page 363.
+\shead{OPTIONS}
+\par
+Flags 
+{\bf --min}
+and 
+{\bf --max}
+can be used to set DATAMAX, DATAMIN, BSCALE and BZERO in the FITS
+header, but do not cause the data to be rescaled.
+\shead{SEE ALSO}
+fitstopnm(1), pgm(5)
+\shead{AUTHOR}
+Copyright (C) 1989 by Wilson H. Bent (whb@hoh-2.att.com), with
+modifications
+by Alberto Accomazzi (alberto@cfa.harvard.edu).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmtofits.1
+%--------------------------------------------------
+
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:25 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmtops.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmtops.1
+%
+\phead{pnmtops}{1}{26 October 1991}{}{}
+
+%.IX pnmtops
+\shead{NAME}
+pnmtops - convert portable anymap to PostScript
+\shead{SYNOPSIS}
+{\bf pnmtops}
+{\rm [}{\bf -scale}
+{\it s}{\rm ]}
+{\rm [}{\bf -turn}{\rm $|$}{\bf -noturn}{\rm ]}
+{\rm [}{\bf -rle}{\rm $|$}{\bf -runlength}{\rm ]}
+{\rm [}{\bf -dpi}
+{\it n}{\rm ]}
+{\rm [}{\bf -width}
+{\it n}{\rm ]}
+{\rm [}{\bf -height}
+{\it n}{\rm ]}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Produces Encapsulated PostScript as output.
+%.IX PostScript
+\par
+If the input file is in color (PPM), a color PostScript file gets
+written.
+Some PostScript interpreters can't handle color PostScript.
+If you have one of these you will need to run your image through
+{\it ppmtopgm}
+first.
+\par
+Note that there is no pstopnm
+tool - this transformation is one-way, because a pstopnm tool would
+be a full-fledged PostScript interpreter, which is beyond the scope
+of this package.
+However, see the
+{\it psidtopgm}
+tool, which can read grayscale non-runlength PostScript image data.
+Also, if you're willing to install the fairly large GhostScript package,
+it comes with a pstoppm script.
+\shead{OPTIONS}
+\par
+The
+{\bf -scale}
+flag controls the scale of the result.  The default scale is 1,
+which on a 300 dpi printer such as the Apple LaserWriter makes
+the output look about the same size as the input would if it was displayed
+on a typical 72 dpi screen.
+To get one PNM pixel per 300 dpi printer pixel, use ``-scale 0.25''.
+\par
+The
+{\bf -turn}
+and
+{\bf -noturn}
+flags control whether the image gets turned 90 degrees.
+Normally, if an image is wider than it is tall, it gets turned
+automatically to better fit the page.
+If the
+{\bf -turn}
+flag is specified, it will be turned no matter what its shape; and if the
+{\bf -noturn}
+flag is specified, it will
+{\it not}
+be turned no matter what its shape.
+\par
+The
+{\bf -rle}
+or
+{\bf -runlength}
+flag specifies run-length compression.  This may save
+time if the host-to-printer link is slow; but normally the printer's processing
+time dominates, so
+{\bf -rle}
+makes things slower.
+\par
+The
+{\bf -dpi}
+flag lets you specify the dots per inch of your output device.
+The default is 300 dpi.
+In theory PostScript is device-independent and you don't have to
+worry about this, but in practice its raster rendering can have
+unsightly bands if the device pixels and the image pixels aren't
+in sync.
+\par
+The
+{\bf -width}
+and
+{\bf -height}
+flags let you specify the size of the page.
+The default is 8.5 inches by 11 inches.
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+pnm(5), psidtopgm(1)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmtops.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:26 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmtorast.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmtorast.1
+%
+\phead{pnmtorast}{1}{12 January 1991}{}{}
+
+%.IX pnmtorast
+\shead{NAME}
+pnmtorast - convert a portable pixmap into a Sun rasterfile
+\shead{SYNOPSIS}
+{\bf pnmtorast}
+{\rm [}{\bf -standard}{\rm $|$}{\bf -rle}{\rm ]}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Produces a Sun rasterfile as output.
+%.IX Sun
+%.IX rasterfile
+\par
+Color values in Sun rasterfiles are eight bits wide, so
+{\it pnmtorast}
+will automatically scale colors to have a maxval of 255.
+An extra
+{\it pnmdepth}
+step is not necessary.
+\shead{OPTIONS}
+\par
+The
+{\bf -standard}
+flag forces the result to be in RT\_STANDARD form; the
+{\bf -rle}
+flag, RT\_BYTE\_ENCODED, which is smaller but, well, less standard.
+The default is
+{\bf -rle}{\rm .}
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+rasttopnm(1), pnm(5)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmtorast.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:31 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmtosir.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmtosir.1
+%
+\phead{pnmtosir}{1}{20 March 1991}{}{}
+
+\shead{NAME}
+pnmtosir - convert a portable anymap into a Solitaire format
+\shead{SYNOPSIS}
+{\bf pnmtosir}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Produces a Solitaire Image Recorder format.
+\par
+pnmtosir produces an MGI TYPE 17 file for
+{\it pbm}
+and
+{\it pgm}
+files.
+For
+{\it ppm}{\rm ,}
+it writes a MGI TYPE 11 file.
+\shead{SEE ALSO}
+sirtopnm(1), pnm(5)
+\shead{BUGS}
+
+\shead{AUTHOR}
+\copyright 1991 by Marvin Landis.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+
+%
+% end of input file: pnmtosir.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:36 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmtotiff.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmtotiff.1
+%
+\phead{pnmtotiff}{1}{13 January 1991}{}{}
+
+%.IX pnmtotiff
+\shead{NAME}
+pnmtotiff - convert a a portable anymap into a TIFF file
+\shead{SYNOPSIS}
+{\bf pnmtotiff}
+{\rm [}{\bf -none}{\rm $|$}{\bf -packbits}{\rm $|$}
+{\bf -lzw}{\rm $|$}{\bf -g3}{\rm $|$}{\bf -g4}{\rm ]}
+{\rm [}{\bf -2d}{\rm ]}
+{\rm [}{\bf -fill}{\rm ]}
+{\rm [}{\bf -predictor}
+{\it n}{\rm ]}
+{\rm [}{\bf -msb2lsb}{\rm $|$}{\bf -lsb2msb}{\rm ]}
+{\rm [}{\bf -rowsperstrip}
+{\it n}{\rm ]}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Produces a TIFF file as output.
+%.IX TIFF
+\shead{OPTIONS}
+\par
+By default,
+{\it pnmtotiff}
+creates a TIFF file with LZW compression.
+This is your best bet most of the time.
+However, some TIFF readers can't deal with it.
+If you want to try another compression scheme or tweak some of the
+other even more obscure output options, there are a number of
+flags to play with.
+\par
+The
+{\bf -none}{\rm ,}
+{\bf -packbits}{\rm ,}
+{\bf -lzw}{\rm ,}
+{\bf -g3}{\rm ,}
+and
+{\bf -g4}
+options are used to override the default and set the compression
+scheme used in creating the output file.  The CCITT Group 3 and Group
+4 compression algorithms can only be used with bilevel data.  The
+{\bf -2d}
+and
+{\bf -fill}
+options are meaningful only with Group 3 compression:
+{\bf -2d}
+requests 2-dimensional encoding, while
+{\bf -fill}
+requests that each encoded scanline be zero-filled to a byte boundry.
+The
+{\bf -predictor}
+option is only meaningful with LZW compression: a predictor value of 2
+causes each scanline of the output image to undergo horizontal
+differencing before it is encoded; a value of 1 forces each scanline
+to be encoded without differencing.
+\rm
+By default,
+{\it pnmtotiff}
+creates a TIFF file with msb-to-lsb fill order.
+The
+{\bf -msb2lsb}
+and
+{\bf -lsb2msb}
+options are used to override the default and set the fill order used
+in creating the file.
+\rm
+The
+{\bf -rowsperstrip}
+option can be used to set the number of rows (scanlines) in each
+strip of data in the output file.  By default, the output file has
+the number of rows per strip set to a value that will ensure each
+strip is no more than 8 kilobytes long.
+\shead{BUGS}
+This program is not self-contained.  To use it you must fetch the
+TIFF Software package listed in the OTHER.SYSTEMS file and configure
+PBMPLUS to use libtiff.  See PBMPLUS's Makefile for details on this
+configuration.
+\shead{SEE ALSO}
+tifftopnm(1), pnm(5)
+\shead{AUTHOR}
+Derived by Jef Poskanzer from ras2tiff.c, which is
+\copyright 1990 by Sun Microsystems, Inc.\hfil\break
+Author: Patrick J. Naughton (naughton@wind.sun.com).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted,
+% provided that the above copyright notice appear in all copies and that
+% both that copyright notice and this permission notice appear in
+% supporting documentation.
+% 
+% This file is provided AS IS with no warranties of any kind.  The author
+% shall have no liability with respect to the infringement of copyrights,
+% trade secrets or any patents by this file or any part thereof.  In no
+% event will the author be liable for any lost revenue or profits or
+% other special, indirect and consequential damages.
+%
+% end of input file: pnmtotiff.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:26 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: pnmtoxwd.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: pnmtoxwd.1
+%
+\phead{pnmtoxwd}{1}{24 September 1991}{}{}
+
+%.IX pnmtoxwd
+\shead{NAME}
+pnmtoxwd - convert a portable anymap into an X11 window dump
+\shead{SYNOPSIS}
+{\bf pnmtoxwd}
+{\rm [}{\bf -pseudodepth}
+{\it n}{\rm ]}
+{\rm [}{\bf -directcolor}{\rm ]}
+{\rm [}{\it pnmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable anymap as input.
+Produces an X11 window dump as output.
+%.IX XWD
+%.IX "X window system"
+This window dump can be displayed using the xwud tool.
+\par
+Normally, pnmtoxwd produces a StaticGray dump file for
+{\it pbm}
+and
+{\it pgm}
+files.
+For
+{\it ppm}{\rm ,}
+it writes a PseudoColor dump file if there are up
+to 256 colors in the input, and a DirectColor dump file otherwise.
+The
+{\bf -directcolor}
+flag can be used to force a DirectColor dump.
+And the
+{\bf -pseudodepth}
+flag can be used to change the depth of PseudoColor dumps from the default
+of 8 bits / 256 colors.
+\shead{SEE ALSO}
+xwdtopnm(1), pnm(5), xwud(1)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: pnmtoxwd.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:13 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppm.5
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppm.5
+%
+\phead{ppm}{5}{27 September 1991}{}{}
+
+\shead{NAME}
+ppm - portable pixmap file format
+\shead{DESCRIPTION}
+The portable pixmap format is a lowest common denominator color image
+file format.
+%.IX "PPM file format"
+The definition is as follows:
+\begin{IPlist}
+\IPitem{{-}}
+A ``magic number'' for identifying the file type.
+A ppm file's magic number is the two characters ``P3''.
+%.IX "magic numbers"
+\IPitem{{-}}
+Whitespace (blanks, TABs, CRs, LFs).
+\IPitem{{-}}
+A width, formatted as ASCII characters in decimal.
+\IPitem{{-}}
+Whitespace.
+\IPitem{{-}}
+A height, again in ASCII decimal.
+\IPitem{{-}}
+Whitespace.
+\IPitem{{-}}
+The maximum color-component value, again in ASCII decimal.
+\IPitem{{-}}
+Whitespace.
+\IPitem{{-}}
+Width * height pixels, each three ASCII decimal values between 0 and the
+specified maximum value, starting at the top-left
+corner of the pixmap, proceeding in normal English reading order.
+The three values for each pixel represent red, green, and blue, respectively;
+a value of 0 means that color is off, and the maximum value means that color
+is maxxed out.
+\IPitem{{-}}
+Characters from a ``\#'' to the next end-of-line are ignored (comments).
+\IPitem{{-}}
+No line should be longer than 70 characters.
+\end{IPlist}
+
+\par
+Here is an example of a small pixmap in this format:
+\nofill
+P3
+\# feep.ppm
+4 4
+15
+ 0  0  0    0  0  0    0  0  0   15  0 15
+ 0  0  0    0 15  7    0  0  0    0  0  0
+ 0  0  0    0  0  0    0 15  7    0  0  0
+15  0 15    0  0  0    0  0  0    0  0  0
+\fill
+\par
+Programs that read this format should be as lenient as possible,
+accepting anything that looks remotely like a pixmap.
+\par
+There is also a variant on the format, available
+by setting the RAWBITS option at compile time.  This variant is
+different in the following ways:
+%.IX RAWBITS
+\begin{IPlist}
+\IPitem{{-}}
+The ``magic number'' is ``P6'' instead of ``P3''.
+\IPitem{{-}}
+The pixel values are stored as plain bytes, instead of ASCII decimal.
+\IPitem{{-}}
+Whitespace is not allowed in the pixels area, and only a single character
+of whitespace (typically a newline) is allowed after the maxval.
+\IPitem{{-}}
+The files are smaller and many times faster to read and write.
+\end{IPlist}
+
+\par
+Note that this raw format can only be used for maxvals less than
+or equal to 255.
+If you use the
+{\it ppm}
+library and try to write a file with a larger maxval,
+it will automatically fall back on the slower but more general plain
+format.
+\shead{SEE ALSO}
+giftoppm(1), gouldtoppm(1), ilbmtoppm(1), imgtoppm(1), mtvtoppm(1),
+pcxtoppm(1), pgmtoppm(1), pi1toppm(1), picttoppm(1), pjtoppm(1), qrttoppm(1),
+rawtoppm(1), rgb3toppm(1), sldtoppm(1), spctoppm(1), sputoppm(1), tgatoppm(1),
+ximtoppm(1), xpmtoppm(1), yuvtoppm(1),
+ppmtoacad(1), ppmtogif(1), ppmtoicr(1), ppmtoilbm(1), ppmtopcx(1), ppmtopgm(1),
+ppmtopi1(1), ppmtopict(1), ppmtopj(1), ppmtopuzz(1), ppmtorgb3(1),
+ppmtosixel(1), ppmtotga(1), ppmtouil(1), ppmtoxpm(1), ppmtoyuv(1),
+ppmdither(1), ppmforge(1), ppmhist(1), ppmmake(1), ppmpat(1), ppmquant(1),
+ppmquantall(1), ppmrelief(1),
+pnm(5), pgm(5), pbm(5)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppm.5
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:08 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmbrighten.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmbrighten.1
+%
+\phead{ppmbrighten}{1}{20 Nov 1990}{}{}
+
+\shead{NAME}
+ppmbrighten - change an images Saturation and Value from an HSV map
+\shead{SYNOPSIS}
+ppmbrighten [-n] [-s $<$+- saturation$>$] [-v $<$+- value$>$] $<$ppmfile$>$
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Converts the image from RGB space to HSV space and changes
+the Value by $<$+- value$>$ as a percentage.
+Likewise with the Saturation.
+Doubling the Value would involve
+\par\vspace{1.0\baselineskip}
+ppmbrighten -v 100
+\par\vspace{1.0\baselineskip}
+to add 100 percent to the Value.
+\par
+The 'n' option normalizes the Value to exist between 0 and 1
+(normalized).
+\shead{SEE ALSO}
+pgmnorm(1), ppm(5)
+\shead{AUTHOR}
+\copyright 1990 by Brian Moffet.
+\copyright 1989 by Jef Poskanzer.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted, provided
+that the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.  This software is provided "as is" without express or
+implied warranty.
+\shead{NOTES}
+This program does not change the number of colors.
+%
+% end of input file: ppmbrighten.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Mon Feb  7 08:50:59 1994
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmchange.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmchange.1
+%
+\phead{ppmchange}{1}{3 December 1993}{}{}
+
+\shead{NAME}
+ppmchange - change all pixels of one color to another in a portable pixmap
+\shead{SYNOPSIS}
+{\bf ppmchange}
+{\it oldcolor newcolor [...]}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Changes all pixels of 
+{\it oldcolor}
+to 
+{\it newcolor}{\rm ,}
+leaving all others unchanged.
+Up to 256 colors may be replaced by specifying couples of colors on
+the command line.  
+\par
+The colors can be specified in five ways:
+\begin{TPlist}{o}
+\item[{o}]
+A name, assuming
+that a pointer to an X11-style color names file was compiled in.
+\item[{o}]
+An X11-style hexadecimal specifier: rgb:r/g/b, where r g and b are
+each 1- to 4-digit hexadecimal numbers.
+\item[{o}]
+An X11-style decimal specifier: rgbi:r/g/b, where r g and b are
+floating point numbers between 0 and 1.
+\item[{o}]
+For backwards compatibility, an old-X11-style hexadecimal
+number: \#rgb, \#rrggbb, \#rrrgggbbb, or \#rrrrggggbbbb.
+\item[{o}]
+For backwards compatibility, a triplet of numbers
+separated by commas: r,g,b, where r g and b are
+floating point numbers between 0 and 1.
+(This style was added before MIT came up with the similar rgbi style.)
+\end{TPlist}
+\shead{SEE ALSO}
+pgmtoppm(1), ppm(5)
+\shead{AUTHOR}
+Wilson H. Bent. Jr. (whb@usc.edu)
+with modifications by Alberto Accomazzi (alberto@cfa.harvard.edu)
+%
+% end of input file: ppmchange.1
+%--------------------------------------------------
+
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Mon Nov 29 13:33:23 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmdim.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmdim.1
+%
+\phead{ppmdim}{1}{16 November 1993}{}{}
+
+%.IX ppmdim
+\shead{NAME}
+ppmdim - dim a portable pixmap down to total blackness
+\shead{SYNOPSIS}
+ppmdim
+{\it dimfactor}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input. Diminishes its brightness by
+the specified dimfactor down to total blackness.
+The dimfactor may be in the range from 0.0 (total blackness,
+deep night, nada, null, nothing) to 1.0 (original picture's
+brightness).
+\par
+As
+{\it pnmgamma}
+does not do the brightness correction in the way I
+wanted it, this small program was written.
+\par
+ppmdim is similar to
+{\it ppmbrighten}
+, but not exactly the same.
+\shead{SEE ALSO}
+ppm(5), ppmflash(1), pnmgamma(1), ppmbrighten(1)
+\shead{AUTHOR}
+Copyright (C) 1993 by Frank Neumann
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmdim.1
+%--------------------------------------------------
+
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:08 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmdist.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmdist.1
+%
+\phead{ppmdist}{1}{22 July 1992}{}{}
+
+%.IX ppmdist
+\shead{NAME}
+ppmdist - simplistic grayscale assignment for machine generated, color images
+\shead{SYNOPSIS}
+{\bf ppmdist}
+{\rm [}{\bf -intensity}{\rm $|$}{\bf -frequency}{\rm ]}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input, performs a simplistic grayscale
+assignment intended for use with grayscale or bitmap printers.
+
+Often conversion from ppm to pgm will yield an image with contrast too
+low for good printer output.  The program maximizes contrast between
+the gray levels output.
+
+A ppm input of n colors is read, and a pgm of n gray levels is written.
+The gray levels take on the values 0..n-1, while maxval takes on n-1.
+
+The mapping from color to stepped grayscale can be performed in order
+of input pixel intensity, or input pixel frequency (number of repetitions).
+\shead{OPTIONS}
+\begin{TPlist}{}
+\item[{}]
+{\bf -frequency}
+Sort input colors by the number of times a color appears in the input,
+before mapping to evenly distributed graylevels of output.
+{\bf -intensity}
+Sort input colors by their grayscale intensity, before mapping to evenly
+distributed graylevels of output.  This is the default.
+\end{TPlist}
+
+\shead{BUGS}
+Helpful only for images with a very small number of colors.
+Perhaps should have been an option to ppmtopgm(1).
+\shead{SEE ALSO}
+ppmtopgm(1), ppmhist(1), ppm(5)
+\shead{AUTHOR}
+\copyright 1993 by Dan Stromberg.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmdist.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:45 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmdither.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmdither.1
+%
+\phead{ppmdither}{1}{14 July 1991}{}{}
+
+%.IX ppmdither
+\shead{NAME}
+ppmdither - ordered dither for color images
+\shead{SYNOPSIS}
+{\bf ppmdither}
+{\rm [}{\bf -dim}
+{\it dimension}{\rm ]}
+{\rm [}{\bf -red}
+{\it shades}{\rm ]}
+{\rm [}{\bf -green}
+{\it shades}{\rm ]}
+{\rm [}{\bf -blue}
+{\it shades}{\rm ]}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input, and applies dithering to it to reduce
+the number of colors used down to the specified number of shades for
+each primary.
+The default number of shades is red=5, green=9, blue=5, for
+a total of 225 colors.
+To convert the image to a binary rgb format 
+suitable for color printers, use -red 2 -green 2 -blue 2.
+The maximum
+number of colors that can be used is 256 and can be computed as the 
+product of the number of red, green and blue shades.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -dim}{\it \ dimension}
+}
+\item[{{\bf -dim}{\it \ dimension}
+}]
+The size of the dithering matrix.
+Must be a power of 2.
+\item[{{\bf -red}{\it \ shades}
+}]
+The number of red shades to be used; minimum of 2.
+\item[{{\bf -green}{\it \ shades}
+}]
+The number of green shades to be used; minimum of 2.
+\item[{{\bf -blue}{\it \ shades}
+}]
+The number of blue shades to be used; minimum of 2.
+\end{TPlist}
+
+\shead{SEE ALSO}
+pnmdepth(1), ppmquant(1), ppm(5)
+\shead{AUTHOR}
+\copyright 1991 by Christos Zoulas.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmdither.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Mon Nov 29 13:33:29 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmflash.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmflash.1
+%
+\phead{ppmflash}{1}{16 November 1993}{}{}
+
+%.IX ppmflash
+\shead{NAME}
+ppmflash - brighten a picture up to complete white-out
+\shead{SYNOPSIS}
+ppmflash 
+{\it flashfactor}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input. Increases its brightness by
+the specified flashfactor up to a total white-out image.
+The flashfactor may be in the range from 0.0 (original picture's
+brightness) to 1.0 (full white-out, The Second After).
+\par
+As
+{\it pnmgamma}
+does not do the brightness correction in the way I
+wanted it, this small program was written.
+\par
+This program is similar to
+{\it ppmbrighten}
+, but not exactly the same.
+\shead{SEE ALSO}
+ppm(5), ppmdim(1), pnmgamma(1), ppmbrighten(1)
+\shead{AUTHOR}
+Copyright (C) 1993 by Frank Neumann
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmflash.1
+%--------------------------------------------------
+
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:46 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmforge.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmforge.1
+%
+\phead{ppmforge}{1}{25 October 1991}{}{}
+
+%.IX ppmforge
+%.IX fractals
+%.IX clouds
+%.IX planets
+%.IX stars
+\shead{NAME}
+ppmforge - fractal forgeries of clouds, planets, and starry skies
+\shead{SYNOPSIS}
+\raggedright
+%.nh
+{\bf ppmforge}
+{\rm [}{\bf -clouds}{\rm ]}
+'in +9n
+{\rm [}{\bf -night}{\rm ]}
+{\rm [}{\bf -dimension}
+{\it dimen}{\rm ]}
+{\rm [}{\bf -hour}
+{\it hour}{\rm ]}
+{\rm [}{\bf -inclination$|$-tilt}
+{\it angle}{\rm ]}
+{\rm [}{\bf -mesh}
+{\it size}{\rm ]}
+{\rm [}{\bf -power}
+{\it factor}{\rm ]}
+{\rm [}{\bf -glaciers}
+{\it level}{\rm ]}
+{\rm [}{\bf -ice}
+{\it level}{\rm ]}
+{\rm [}{\bf -saturation}
+{\it sat}{\rm ]}
+{\rm [}{\bf -seed}
+{\it seed}{\rm ]}
+{\rm [}{\bf -stars}
+{\it fraction}{\rm ]}
+{\rm [}{\bf -xsize$|$-width}
+{\it width}{\rm ]}
+{\rm [}{\bf -ysize$|$-height}
+{\it height}{\rm ]}
+\ind{-4.5em}
+%.ad
+%.hy
+\shead{DESCRIPTION}
+{\bf ppmforge}
+generates three kinds of ``random fractal forgeries,'' the term coined
+by Richard F. Voss of the IBM Thomas J. Watson Research Center for
+seemingly realistic pictures of natural objects generated by simple
+algorithms embodying randomness and fractal self-similarity.  The
+techniques used by
+{\bf ppmforge}
+are essentially those
+given by Voss[1], particularly the technique of spectral synthesis
+explained in more detail by Dietmar Saupe[2].
+\par
+The program generates two varieties of pictures: planets and clouds,
+which are just different renderings of data generated in an identical
+manner, illustrating the unity of the fractal structure of these very
+different objects.  A third type of picture, a starry sky, is
+synthesised directly from pseudorandom numbers.
+\par
+The generation of planets or clouds begins with the preparation of an
+array of random data in the frequency domain.  The size of this
+array, the ``mesh size,'' can be set with the
+{\bf -mesh}
+option; the larger the mesh the more realistic the pictures but the
+calculation time and memory requirement increases as the square of the
+mesh size.  The fractal dimension, which you can specify with the
+{\bf -dimension}
+option, determines the roughness of the terrain on the planet or the
+scale of detail in the clouds.  As the fractal dimension is increased,
+more high frequency components are added into the random mesh.
+\par
+Once the mesh is generated, an inverse two dimensional Fourier
+transform is performed upon it.  This converts the original random
+frequency domain data into spatial amplitudes.  We scale the real
+components that result from the Fourier transform into numbers from 0
+to 1 associated with each point on the mesh.  You can further
+modify this number by applying a ``power law scale'' to it with the
+{\bf -power}
+option.   Unity scale
+leaves the numbers unmodified; a power scale of 0.5 takes the square
+root of the numbers in the mesh, while a power scale of 3 replaces the
+numbers in the mesh with their cubes.  Power law scaling is best
+envisioned by thinking of the data as representing the elevation of
+terrain; powers less than 1 yield landscapes with vertical scarps that
+look like glacially-carved valleys; powers greater than one make
+fairy-castle spires (which require large mesh sizes and high
+resolution for best results).
+\par
+After these calculations, we have a array of the specified size
+containing numbers that range from 0 to 1.  The pixmaps are generated as
+follows:
+\begin{TPlist}{{\bf Clouds}}
+\item[{{\bf Clouds}}]
+A colour map is created that ranges from pure blue to white by
+increasing admixture (desaturation) of blue with white.  Numbers less
+than 0.5 are coloured blue, numbers between 0.5 and 1.0 are coloured
+with corresponding levels of white, with 1.0 being pure white.
+\item[{{\bf Planet}}]
+The mesh is projected onto a sphere.  Values less than 0.5 are treated
+as water and values between 0.5 and 1.0 as land.  The water areas are
+coloured based upon the water depth, and land based on its elevation.
+The random depth data are used to create clouds over the oceans.  An
+atmosphere approximately like the Earth's is simulated; its light
+absorption is calculated to create a blue cast around the limb of the
+planet.  A function that rises from 0 to 1 based on latitude is
+modulated by the local elevation to generate polar ice caps--high
+altitude terrain carries glaciers farther from the pole.  Based on the
+position of the star with respect to the observer, the apparent colour
+of each pixel of the planet is calculated by ray-tracing from the star
+to the planet to the observer and applying a lighting model that sums
+ambient light and diffuse reflection (for most planets ambient light
+is zero, as their primary star is the only source of illumination).
+Additional random data are used to generate stars around the planet.
+\item[{{\bf Night}}]
+A sequence of pseudorandom numbers is used to generate stars with a
+user specified density.
+\end{TPlist}
+
+\par
+Cloud pictures always contain 256 or fewer colours and may be
+displayed on most colour mapped devices without further processing.
+Planet pictures often contain tens of thousands of colours which
+must be compressed with
+{\bf ppmquant}
+or
+{\bf ppmdither}
+before encoding in a colour mapped format.  If the display resolution is
+high enough,
+{\bf ppmdither}
+generally produces better looking planets.
+{\bf ppmquant}
+tends to create discrete colour bands, particularly in the oceans,
+which are unrealistic and distracting.  The number of colours in starry
+sky pictures generated with the
+{\bf -night}
+option depends on the value specified for
+{\bf -saturation}{\rm .}
+Small values limit the colour temperature distribution of the stars
+and reduce the number of colours in the image.
+If the
+{\bf -saturation}
+is set to 0, none of the stars will be coloured and the resulting
+image will never contain more than 256 colours.
+Night sky pictures with many different star colours often look
+best when colour compressed by
+{\bf pnmdepth}
+rather than
+{\bf ppmquant}
+or
+{\bf ppmdither}{\rm .}
+Try
+{\it newmaxval}
+settings of 63, 31, or 15 with
+{\bf pnmdepth}
+to reduce the number of colours in the picture to 256 or fewer.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -clouds}}
+\item[{{\bf -clouds}}]
+Generate clouds.  A pixmap of fractal clouds is generated.  Selecting clouds
+sets the default for fractal dimension to 2.15 and power scale factor
+to 0.75.
+\item[{{\bf -dimension}{\it \ dimen}
+}]
+Sets the fractal dimension to the specified
+{\it dimen}{\rm ,}
+which may be any floating point value between 0 and 3.  Higher fractal
+dimensions create more ``chaotic'' images, which require higher
+resolution output and a larger FFT mesh size to look good.  If no
+dimension is specified, 2.4 is used when generating planets and 2.15
+for clouds.
+\item[{{\bf -glaciers}{\it \ level}
+}]
+The floating point
+{\it level}
+setting controls the extent to which terrain elevation causes ice to
+appear at lower latitudes.  The default value of 0.75 makes the polar
+caps extend toward the equator across high terrain and forms glaciers
+in the highest mountains, as on Earth.  Higher values make ice sheets
+that cover more and more of the land surface, simulating planets in the
+midst of an ice age.  Lower values tend to be boring, resulting in
+unrealistic geometrically-precise ice cap boundaries.
+\item[{{\bf -hour}{\it \ hour}
+}]
+When generating a planet,
+{\it hour}
+is used as the ``hour angle at the central meridian.''  If you specify
+{\bf -hour\ 12}{\rm ,}
+for example, the planet will be fully illuminated, corresponding to
+high noon at the longitude at the centre of the screen.  You can
+specify any floating point value between 0 and 24 for
+{\it hour}{\rm ,}
+but values which place most of the planet in darkness (0 to 4 and 20
+to 24) result in crescents which, while pretty, don't give you many
+illuminated pixels for the amount of computing that's required.  If no
+{\bf -hour}
+option is specified, a random hour angle is chosen, biased so that
+only 25\% of the images generated will be crescents.
+\item[{{\bf -ice}{\it \ level}
+}]
+Sets the extent of the polar ice caps to the given floating point
+{\it level}{\rm .}
+The default level of 0.4 produces ice caps similar to those of the Earth.
+Smaller values reduce the amount of ice, while larger
+{\bf -ice}
+settings create more prominent ice caps.  Sufficiently large values,
+such as 100 or more, in conjunction with small settings for
+{\bf -glaciers}
+(try 0.1) create ``ice balls'' like Europa.
+\item[{{\bf -inclination$|$-tilt}{\it \ angle}
+}]
+The inclination angle of the planet with regard to its primary star is
+set to
+{\it angle}{\rm ,}
+which can be any floating point value from -90 to 90.  The inclination
+angle can be thought of as specifying, in degrees, the ``season'' the
+planet is presently experiencing or, more precisely, the latitude at
+which the star transits the zenith at local noon.  If 0, the planet
+is at equinox; the star is directly overhead at the equator.
+Positive values represent summer in the northern hemisphere, negative
+values summer in the southern hemisphere.  The Earth's inclination
+angle, for example, is about 23.5 at the June solstice, 0 at the
+equinoxes in March and September, and -23.5 at the December solstice.
+If no inclination angle is specified, a random value between -21.6 and
+21.6 degrees is chosen.
+\item[{{\bf -mesh}{\it \ size}
+}]
+A mesh of
+{\it size}{\rm \ by\ }{\it size}
+will be used for the fast Fourier transform (FFT).  Note that memory
+requirements and computation speed increase as the square of
+{\it size}{\rm ;}
+if you double the mesh size, the program will use four times the
+memory and run four times as long.  The default mesh is 256x256, which
+produces reasonably good looking pictures while using half a megabyte
+for the 256x256 array of single precision complex numbers
+required by the FFT.  On machines with limited memory capacity, you
+may have to reduce the mesh size to avoid running out of RAM.
+Increasing the mesh size produces better looking pictures; the
+difference becomes particularly noticeable when generating high
+resolution images with relatively high fractal dimensions (between 2.2
+and 3).
+\item[{{\bf -night}}]
+A starry sky is generated.  The stars are created by the same algorithm
+used for the stars that surround planet pictures, but the output
+consists exclusively of stars.
+\item[{{\bf -power}{\it \ factor}
+}]
+Sets the ``power factor'' used to scale elevations synthesised from
+the FFT to
+{\it factor}{\rm ,}
+which can be any floating point number greater than zero.  If no
+factor is specified a default of 1.2 is used if a planet is being
+generated, or 0.75 if clouds are selected by the
+{\bf -clouds}
+option.  The result of the FFT image synthesis is an array of elevation
+values between 0 and 1.  A non-unity power factor exponentiates each
+of these elevations to the specified power.  For example, a power
+factor of 2 squares each value, while a power factor of 0.5 replaces
+each with its square root.  (Note that exponentiating values between 0
+and 1 yields values that remain within that range.)  Power factors
+less than 1 emphasise large-scale elevation changes at the expense of
+small variations.  Power factors greater than 1 increase the roughness
+of the terrain and, like high fractal dimensions, may require a larger
+FFT mesh size and/or higher screen resolution to look good.
+\item[{{\bf -saturation}{\it \ sat}
+}]
+Controls the degree of colour saturation of the stars that surround planet
+pictures and fill starry skies created with the
+{\bf -night}
+option.  The default value of 125 creates stars which resemble the sky
+as seen by the human eye from Earth's surface.  Stars are dim; only
+the brightest activate the cones in the human retina, causing colour
+to be perceived.  Higher values of
+{\it sat}
+approximate the appearance of stars from Earth orbit, where better
+dark adaptation, absence of skyglow, and the concentration of light
+from a given star onto a smaller area of the retina thanks to the lack
+of atmospheric turbulence enhances the perception of colour.  Values
+greater than 250 create ``science fiction'' skies that, while pretty,
+don't occur in this universe.
+\item[{\ }]
+Thanks to the inverse square law combined with Nature's love of
+mediocrity, there are many, many dim stars for every bright one.
+This population relationship is accurately reflected in the skies
+created by
+{\bf ppmforge}{\rm .}
+Dim, low mass stars live much longer than bright massive stars,
+consequently there are many reddish stars for every blue giant.  This
+relationship is preserved by
+{\bf ppmforge}{\rm .}
+You can reverse the proportion, simulating the sky as seen in a starburst
+galaxy, by specifying a negative
+{\it sat}
+value.
+\item[{{\bf -seed}{\it \ num}
+}]
+Sets the seed for the random number generator to the integer
+{\it num}{\rm .}
+The seed used to create each picture is displayed on standard output (unless
+suppressed with the
+{\bf -quiet}
+option).  Pictures generated with the same seed will be identical.  If no
+{\bf -seed}
+is specified, a random seed derived from the date and time will be
+chosen.  Specifying an explicit seed allows you to re-render a picture
+you particularly like at a higher resolution or with different viewing
+parameters.
+\item[{{\bf -stars}{\it \ fraction}
+}]
+Specifies the percentage of pixels, in tenths of a percent, which will
+appear as stars, either surrounding a planet or filling the entire
+frame if
+{\bf -night}
+is specified.  The default
+{\it fraction}
+is 100.
+\item[{{\bf -xsize$|$-width}{\it \ width}
+}]
+Sets the width of the generated image to
+{\it width}
+pixels.  The default width is 256 pixels.  Images must be at least as
+wide as they are high; if a width less than the height is specified,
+it will be increased to equal the height.  If you must have a long
+skinny pixmap, make a square one with
+{\bf ppmforge}{\rm ,}
+then use
+{\bf pnmcut}
+to extract a portion of the shape and size you require.
+\item[{{\bf -ysize$|$-height}{\it \ height}
+}]
+Sets the height of the generated image to
+{\it height}
+pixels.  The default height is 256 pixels.  If the height specified
+exceeds the width, the width will be increased to equal the height.
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{BUGS}
+\par
+The algorithms require the output pixmap to be at least as wide as it
+is high, and the width to be an even number of pixels.  These
+constraints are enforced by increasing the size of the requested
+pixmap if necessary.
+\par
+You may have to reduce the FFT mesh size on machines with 16 bit
+integers and segmented pointer architectures.
+\shead{SEE ALSO}
+{\bf pnmcut}{\rm (1),}
+{\bf pnmdepth}{\rm (1),}
+{\bf ppmdither}{\rm (1),}
+{\bf ppmquant}{\rm (1),}
+{\bf ppm}{\rm (5)}
+\begin{TPlist}{[1] }
+\item[{[1] }]
+Voss, Richard F., ``Random Fractal Forgeries,'' in Earnshaw
+{\it et. al.}, Fundamental Algorithms for Computer Graphics, Berlin:
+Springer-Verlag, 1985.
+\item[{[2]}]
+Peitgen, H.-O., and Saupe, D. eds., The Science Of Fractal Images,
+New York: Springer Verlag, 1988.
+%.ne 10
+\end{TPlist}
+
+\shead{AUTHOR}
+\ind{1\parindent}{\nofill
+    John Walker
+    Autodesk SA
+    Avenue des Champs-Montants 14b
+    CH-2074 MARIN
+    Suisse/Schweiz/Svizzera/Svizra/Switzerland
+\fill}
+\begin{TPlist}{Usenet:}
+\item[{Usenet:}]
+kelvin@Autodesk.com
+\item[{Fax:}]
+038/33 88 15
+\item[{Voice:}]
+038/33 76 33
+\end{TPlist}
+
+\par
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+without any conditions or restrictions.  This software is provided ``as
+is'' without express or implied warranty.
+\par
+{\bf PLUGWARE!}
+If you like this kind of stuff, you may also enjoy ``James Gleick's
+Chaos--The Software'' for MS-DOS, available for \$59.95 from your
+local software store or directly from Autodesk, Inc., Attn: Science
+Series, 2320 Marinship Way, Sausalito, CA 94965, USA.  Telephone:
+(800) 688-2344 toll-free or, outside the U.S. (415) 332-2344 Ext
+4886.  Fax: (415) 289-4718.  ``Chaos--The Software'' includes a more
+comprehensive fractal forgery generator which creates
+three-dimensional landscapes as well as clouds and planets, plus five
+more modules which explore other aspects of Chaos.  The user guide of
+more than 200 pages includes an introduction by James Gleick and
+detailed explanations by Rudy Rucker of the mathematics and algorithms
+used by each program.
+%
+% end of input file: ppmforge.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:47 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmhist.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmhist.1
+%
+\phead{ppmhist}{1}{03 April 1989}{}{}
+
+%.IX ppmhist
+\shead{NAME}
+ppmhist - print a histogram of a portable pixmap
+\shead{SYNOPSIS}
+{\bf ppmhist}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Generates a histogram of the colors in the pixmap.
+\shead{SEE ALSO}
+ppm(5), pgmhist(1)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmhist.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:48 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmmake.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmmake.1
+%
+\phead{ppmmake}{1}{24 September 1991}{}{}
+
+%.IX ppmmake
+\shead{NAME}
+ppmmake - create a pixmap of a specified size and color
+\shead{SYNOPSIS}
+{\bf ppmmake}
+{\it color width height}
+\shead{DESCRIPTION}
+Produces a portable pixmap of the specified color, width, and height.
+%.IX "generating pixmaps"
+\par
+The color can be specified in five ways:
+%.IX "specifying colors"
+\begin{TPlist}{o}
+\item[{o}]
+A name, assuming
+that a pointer to an X11-style color names file was compiled in.
+\item[{o}]
+An X11-style hexadecimal specifier: rgb:r/g/b, where r g and b are
+each 1- to 4-digit hexadecimal numbers.
+\item[{o}]
+An X11-style decimal specifier: rgbi:r/g/b, where r g and b are
+floating point numbers between 0 and 1.
+\item[{o}]
+For backwards compatibility, an old-X11-style hexadecimal
+number: \#rgb, \#rrggbb, \#rrrgggbbb, or \#rrrrggggbbbb.
+\item[{o}]
+For backwards compatibility, a triplet of numbers
+separated by commas: r,g,b, where r g and b are
+floating point numbers between 0 and 1.
+(This style was added before MIT came up with the similar rgbi style.)
+\end{TPlist}
+
+\shead{SEE ALSO}
+ppm(5), pbmmake(1)
+\shead{AUTHOR}
+\copyright 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmmake.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Mon Nov 29 13:23:43 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmmix.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmmix.1
+%
+\phead{ppmmix}{1}{16 November 1993}{}{}
+
+%.IX ppmmix
+\shead{NAME}
+ppmmix - blend together two portable pixmaps
+\shead{SYNOPSIS}
+ppmmix 
+{\it fadefactor}
+{\it ppmfile1 ppmfile2}
+\shead{DESCRIPTION}
+Reads two portable pixmaps as input. Mixes them together using the
+specified fade factor. The fade factor may be in the range from 0.0
+(only ppmfile1's image data) to 1.0 (only ppmfile2's image data).
+Anything in between gains a smooth blend between the two images.
+\par
+The two pixmaps must have the same size.
+\shead{SEE ALSO}
+ppm(5)
+\shead{AUTHOR}
+\copyright 1993 by Frank Neumann.
+% Permission to use, , modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmmix.1
+%--------------------------------------------------
+
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:06 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmpat.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmpat.1
+%
+\phead{ppmpat}{1}{04 September 1989}{}{}
+
+%.IX ppmpat
+\shead{NAME}
+ppmpat - make a pretty pixmap
+\shead{SYNOPSIS}
+{\bf ppmpat}
+{\bf -gingham2}{\rm $|$}{\bf -g2}{\rm $|$}{\bf -gingham3}{\rm $|$}
+{\bf -g3}{\rm $|$}{\bf -madras}{\rm $|$}{\bf -tartan}{\rm $|$}
+{\bf -poles}{\rm $|$}{\bf -squig}{\rm $|$}{\bf -camo}{\rm $|$}
+{\bf -anticamo}
+{\it width height}
+\shead{DESCRIPTION}
+Produces a portable pixmap of the specified width and height,
+with a pattern in it.
+%.IX "generating pixmaps"
+\par
+This program is mainly to demonstrate use of the ppmdraw routines, a
+simple but powerful drawing library.
+See the ppmdraw.h include file for more info on using these routines.
+Still, some of the patterns can be rather pretty.
+If you have a color workstation, something like
+{\bf ppmpat\ -squig\ 300\ 300 $|$ ppmquant\ 128}
+should generate a nice background.
+\shead{OPTIONS}
+\par
+The different flags specify various different pattern types:
+\begin{TPlist}{{\bf -gingham2}}
+\item[{{\bf -gingham2}}]
+A gingham check pattern.  Can be tiled.
+\item[{{\bf -gingham3}}]
+A slightly more complicated gingham.  Can be tiled.
+\item[{{\bf -madras}}]
+A madras plaid.  Can be tiled.
+\item[{{\bf -tartan}}]
+A tartan plaid.  Can be tiled.
+\item[{{\bf -poles}}]
+Color gradients centered on randomly-placed poles.
+May need to be run through
+{\it ppmquant}{\rm .}
+\item[{{\bf -squig}}]
+Squiggley tubular pattern.  Can be tiled.
+May need to be run through
+{\it ppmquant}{\rm .}
+\item[{{\bf -camo}}]
+Camouflage pattern.
+May need to be run through
+{\it ppmquant}{\rm .}
+\item[{{\bf -anticamo}}]
+Anti-camouflage pattern - like -camo, but ultra-bright colors.
+May need to be run through
+{\it ppmquant}{\rm .}
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{REFERENCES}
+Some of the patterns are from ``Designer's Guide to Color 3'' by Jeanne Allen.
+\shead{SEE ALSO}
+pnmtile(1), ppmquant(1), ppm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmpat.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:48 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmquant.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmquant.1
+%
+\phead{ppmquant}{1}{12 January 1991}{}{}
+
+%.IX ppmquant
+\shead{NAME}
+ppmquant - quantize the colors in a portable pixmap down to a specified number
+
+\shead{SYNOPSIS}
+{\bf ppmquant}
+{\rm [}{\bf -floyd}{\rm $|$}{\bf -fs}{\rm ]}
+{\it ncolors}
+{\rm [}{\it ppmfile}{\rm ]}
+\nwl
+{\bf ppmquant}
+{\rm [}{\bf -floyd}{\rm $|$}{\bf -fs}{\rm ]}
+{\bf -map}
+{\it mapfile}
+{\rm [}{\it ppmfile}{\rm ]}
+
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Chooses
+{\it ncolors}
+colors to best represent the image, maps the existing colors
+to the new ones, and writes a portable pixmap as output.
+%.IX "colormap reduction"
+\par
+The quantization method is Heckbert's ``median cut''.
+%.IX "median cut"
+\par
+Alternately, you can skip the color-choosing step by
+specifying your own set of colors with the
+{\bf -map}
+flag.  The
+{\it mapfile}
+is just a
+{\it ppm}
+file; it can be any shape, all that matters is the colors in it.
+For instance, to quantize down to the 8-color IBM TTL color set, you
+might use:
+{\tt\nofill
+    P3
+    8 1
+    255
+      0   0   0
+    255   0   0
+      0 255   0
+      0   0 255
+    255 255   0
+    255   0 255
+      0 255 255
+    255 255 255
+\fill}
+
+If you want to quantize one pixmap to use the colors in another one,
+just use the second one as the mapfile.
+You don't have to reduce it down to only one pixel of each color,
+just use it as is.
+\par
+The
+{\bf -floyd}{\rm /}{\bf -fs}
+flag enables a Floyd-Steinberg error diffusion step.
+%.IX Floyd-Steinberg
+%.IX "error diffusion"
+Floyd-Steinberg gives vastly better results on images where the unmodified
+quantization has banding or other artifacts, especially when going to a
+small number of colors such as the above IBM set.
+However, it does take substantially more CPU time, so the default is off.
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{REFERENCES}
+``Color Image Quantization for Frame Buffer Display'' by Paul Heckbert,
+SIGGRAPH '82 Proceedings, page 297.
+\shead{SEE ALSO}
+ppmquantall(1), pnmdepth(1), ppmdither(1), ppm(5)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmquant.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Mon Nov 29 13:33:41 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmquantall.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmquantall.1
+%
+\phead{ppmquantall}{1}{27 July 1990}{}{}
+
+%.IX ppmquantall
+\shead{NAME}
+ppmquantall - run ppmquant on a bunch of files all at once, so they share a common colormap
+\shead{SYNOPSIS}
+{\bf ppmquantall}
+{\it ncolors ppmfile}
+{\rm ...}
+\shead{DESCRIPTION}
+Takes a bunch of portable pixmap as input.
+Chooses
+{\it ncolors}
+colors to best represent all of the images, maps the
+existing colors to the new ones, and
+{\bf overwrites the input files}
+with the new quantized versions.
+%.IX "colormap reduction"
+\par
+Verbose explanation: Let's say you've got a dozen pixmaps that you want
+to display on the screen all at the same time.  Your screen can only
+display 256 different colors, but the pixmaps have a total of a thousand
+or so different colors.  For a single pixmap you solve this problem with
+{\it ppmquant}{\rm ;}
+%.IX ppmquant
+this script solves it for multiple pixmaps.  All it does is
+concatenate them together into one big pixmap, run
+{\it ppmquant}
+on that, and then split it up into little pixmaps again.
+\par
+(Note that another way to solve this problem is to pre-select a set of
+colors and then use
+{\it ppmquant}{\rm 's}
+{\bf -map}
+option to separately quantize each pixmap to that set.)
+\shead{SEE ALSO}
+ppmquant(1), ppm(5)
+\shead{BUGS}
+It's a csh script.
+Csh scripts are not portable to System V.
+Scripts in general are not portable to non-Unix environments.
+\shead{AUTHOR}
+Copyright (C) 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmquantall.1
+%--------------------------------------------------
+
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:09 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmqvga.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmqvga.1
+%
+\phead{PPMQVGA}{1}{local}{}{}
+
+'% Heading: name(sect)    center (paren)    name(sect)
+\shead{NAME}
+ppmqvga - 8 plane quantization
+'% name [, name] ... \- brief description on a single line of \s-1INPUT\s+1
+\shead{SYNOPSIS}
+ppmqvga [ options ] [ input file ]
+'% foo [ options ] files1 [ file2 [ ... ] ]
+\shead{DESCRIPTION}
+{\bf ppmqvga}
+quantizes PPM files to 8 planes, with optional Floyd-Steinberg dithering.
+Input is a PPM file from the file named, or standard input of no file is
+provided.
+%.SS Options
+{\bf -d}
+dither. Apply Floyd-Steinberg dithering to the data
+
+{\bf -q}
+quiet. Produces no progress reporting, and no terminal output unless
+and error occurs.
+
+{\bf -v}
+verbose. Produces additional output describing the number of colors found,
+and some information on the resulting mapping. May be repeated to generate
+loads of internal table output, but generally only useful once.
+\shead{EXAMPLES}
+ppmqvga -d my\_image.ppm $|$ ppmtogif $>$my\_image.gif
+
+tgatoppm zombie.tga $|$ ppmqvga $|$ ppmtotif $>$ zombie.tif
+\shead{SEE ALSO}
+ppmquant
+\shead{DIAGNOSTICS}
+Error messages if problems, various levels of optional progress reporting.
+\shead{LIMITATIONS}
+none known.
+\shead{AUTHOR}
+Original by Lyle Rains (lrains@netcom.com) as ppmq256 and ppmq256fs
+combined, documented, and enhanced by Bill Davidsen (davidsen@crd.ge.com)
+\shead{Copyright}
+Copyright 1991,1992 by Bill Davidsen, all rights reserved.
+The program and documentation may be freely distributed by anyone in source
+or binary format. Please clearly note any changes.
+%
+% end of input file: ppmqvga.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:49 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmrelief.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmrelief.1
+%
+\phead{ppmrelief}{1}{11 January 1991}{}{}
+
+%.IX ppmrelief
+\shead{NAME}
+ppmrelief - run a Laplacian relief filter on a portable pixmap
+\shead{SYNOPSIS}
+{\bf ppmrelief}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Does a Laplacian relief filter, and writes a portable pixmap as output.
+%.IX "Laplacian relief"
+\par
+The Laplacian relief filter is described in ``Beyond Photography'' by Holzmann,
+equation 3.19.
+It's a sort of edge-detection.
+%.IX "edge detection"
+\shead{SEE ALSO}
+pgmbentley(1), pgmoil(1), ppm(5)
+\shead{AUTHOR}
+\copyright 1990 by Wilson Bent (whb@hoh-2.att.com).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmrelief.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Mon Nov 29 13:24:20 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmshift.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmshift.1
+%
+\phead{ppmshift}{1}{16 November 1993}{}{}
+
+%.IX ppmshift
+\shead{NAME}
+ppmshift - shift lines of a portable pixmap left or right by a random amount
+\shead{SYNOPSIS}
+ppmshift 
+{\it shift}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input. Shifts every row of image data to the
+left or right by a certain amount. The 'shift' parameter determines by how 
+many pixels a row is to be shifted at most.
+\par
+Another one of those effects I intended to use for MPEG tests.
+Unfortunately, this program will not help me here - it creates too random
+patterns to be used for animations. Still, it might give interesting
+results on still images.
+\shead{EXAMPLE}
+Check this out: Save your favourite model's picture from something like
+alt.binaries.pictures.supermodels (ok, or from any other picture source),
+convert it to ppm, and process it e.g. like this, assuming the picture is 
+800x600 pixels:
+\nofill\ind{1\parindent}\tt{}  \# take the upper half, and leave it like it is
+  pnmcut 0 0 800 300 cs.ppm $>$upper.ppm
+
+  \# take the lower half, flip it upside down, dim it and distort it a little
+  pnmcut 0 300 800 300 cs.ppm $|$ pnmflip -tb $|$ ppmdim 0.7 $|$ 
+     ppmshift 10 $>$lower.ppm
+
+  \# and concatenate the two pieces
+  pnmcat -tb upper.ppm lower.ppm $>$newpic.ppm
+\fill 
+The resulting picture looks like the image being reflected on a water 
+surface with slight ripples.
+\shead{SEE ALSO}
+ppm(5), pnmcut(1), pnmflip(1), ppmdim(1), pnmcat(1)
+\shead{AUTHOR}
+\copyright 1993 by Frank Neumann
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmshift.1
+%--------------------------------------------------
+
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Mon Nov 29 13:24:10 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmspread.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmspread.1
+%
+\phead{ppmspread}{1}{16 November 1993}{}{}
+
+%.IX ppmspread
+\shead{NAME}
+ppmspread - displace a portable pixmap's pixels by a random amount
+\shead{SYNOPSIS}
+ppmspread
+{\it amount}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input. Moves every pixel around a bit
+relative to its original position. amount determines by how many
+pixels a pixel is to be moved around at most.
+\par
+Pictures processed with this filter will seem to be somewhat 
+dissolved or unfocussed (although they appear more coarse than
+images processed by something like
+{\it pnmconvol}
+).
+\shead{SEE ALSO}
+ppm(5), pnmconvol(1)
+\shead{AUTHOR}
+\copyright 1993 by Frank Neumann
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty
+%
+% end of input file: ppmspread.1
+%--------------------------------------------------
+
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:50 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtoacad.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtoacad.1
+%
+\phead{ppmtoacad}{1}{10 October 1991}{}{}
+
+%.IX ppmtoacad
+%.IX AutoCAD
+\shead{NAME}
+ppmtoacad - convert portable pixmap to AutoCAD database or slide
+\shead{SYNOPSIS}
+\raggedright
+{\bf ppmtoacad}
+'in 15n
+{\rm [}{\bf -dxb}{\rm ]}
+{\rm [}{\bf -poly}{\rm ]}
+{\rm [}{\bf -background}
+{\it colour}{\rm ]}
+{\rm [}{\bf -white}{\rm ]}
+{\rm [}{\bf -aspect}
+{\it ratio}{\rm ]}
+{\rm [}{\bf -8}{\rm ]}
+{\rm [}{\it ppmfile}{\rm ]}
+\ind{-7.5em}
+%.ad
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.  Produces an AutoCAD{l}ide file or
+binary database import (.dxb) file as output.
+If no
+{\it ppmfile}
+is specified, input is read from standard input.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -dxb}}
+\item[{{\bf -dxb}}]
+An AutoCAD binary database import (.dxb) file is written.  This file
+is read with the DXBIN command and, once loaded, becomes part of
+the AutoCAD geometrical database and can be viewed and edited like
+any other object.  Each sequence of identical pixels becomes a
+separate object in the database; this can result in very large AutoCAD
+drawing files.  However, if you want to trace over a bitmap, it lets
+you zoom and pan around the bitmap as you wish.
+\item[{{\bf -poly}}]
+If the
+{\bf -dxb}
+option is not specified, the output of
+{\bf ppmtoacad}
+is an AutoCAD slide file.  Normally each row of pixels is
+represented by an AutoCAD line entity.  If
+{\bf -poly}
+is selected, the pixels are rendered as filled polygons.  If the
+slide is viewed on a display with higher resolution than the source
+pixmap, this will cause the pixels to expand instead of appearing as
+discrete lines against the screen background colour.  Regrettably,
+this representation yields slide files which occupy more disc space
+and take longer to display.
+\item[{{\bf -background}{\it \ colour}
+}]
+Most AutoCAD display drivers can be configured to use any available
+colour as the screen background.  Some users perfer a black screen
+background, others white, while splinter groups advocate burnt ocher,
+tawny puce, and shocking grey.  Discarding pixels whose closest
+AutoCAD colour representation is equal to the background colour can
+substantially reduce the size of the AutoCAD database or slide file
+needed to represent a bitmap.  If no
+{\bf -background}
+colour is specified, the screen background colour is assumed to be
+black.  Any AutoCAD colour number may be specified as the screen
+background; colour numbers are assumed to specify the hues defined
+in the standard AutoCAD 256 colour palette.
+\item[{{\bf -white}}]
+Since many AutoCAD users choose a white screen background, this option
+is provided as a short-cut.  Specifying
+{\bf -white}
+is identical in effect to
+{\bf -background\ 7}{\rm .}
+\item[{{\bf -aspect}{\it \ ratio}
+}]
+If the source pixmap had non-square pixels, the ratio of the pixel
+width to pixel height should be specified as
+{\it ratio}{\rm .}
+The resulting slide or .dxb file will be corrected so that pixels on
+the AutoCAD screen will be square.  For example, to correct an image made
+for a 320x200 VGA/MCGA screen, specify
+{\bf -aspect\ 0.8333}{\rm .}
+\item[{{\bf -8}}]
+Restricts the colours in the output file to the 8 RGB shades.
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{BUGS}
+AutoCAD has a fixed palette of 256 colours, distributed along the hue,
+lightness, and saturation axes.  Pixmaps which contain many
+nearly-identical colours, or colours not closely approximated by
+AutoCAD's palette, may be poorly rendered.
+\par
+{\bf ppmtoacad}
+works best if the system displaying its output supports the full 256
+colour AutoCAD palette.  Monochrome, 8 colour, and 16 colour
+configurations will produce less than optimal results.
+\par
+When creating a .dxb file or a slide file with the
+{\bf -poly}
+option,
+{\bf ppmtoacad}
+finds both vertical and horizontal runs of identical pixels and
+consolidates them into rectangular regions to reduce the size of the
+output file.  This is effective for images with large areas of
+constant colour but it's no substitute for true raster to vector
+conversion.  In particular, thin diagonal lines are not optimised at
+all by this process.
+\par
+Output files can be huge.
+\shead{SEE ALSO}
+AutoCAD Reference Manual:
+{\it Slide File Format}
+and
+{\it Binary\ Drawing\ Interchange\ (DXB)\ Files}{\rm ,}
+{\bf ppm}{\rm (5)}
+\shead{AUTHOR}
+\ind{1\parindent}{\nofill
+    John Walker
+    Autodesk SA
+    Avenue des Champs-Montants 14b
+    CH-2074 MARIN
+    Suisse/Schweiz/Svizzera/Svizra/Switzerland
+\fill}
+\begin{TPlist}{Usenet:}
+\item[{Usenet:}]
+kelvin@Autodesk.com
+\item[{Fax:}]
+038/33 88 15
+\item[{Voice:}]
+038/33 76 33
+\end{TPlist}
+
+\par
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+without any conditions or restrictions.  This software is provided ``as
+is'' without express or implied warranty.
+\par
+AutoCAD and Autodesk are registered trademarks of Autodesk, Inc.
+%
+% end of input file: ppmtoacad.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:09 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtobmp.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtobmp.1
+%
+\phead{ppmtobmp}{1}{26 Oct 1992}{}{}
+
+%.IX ppmtobmp
+\shead{NAME}
+ppmtobmp -- convert a portable pixmap into a BMP file
+\shead{SYNOPSIS}
+{\bf ppmtobmp}
+{\rm [}{\it --windows}{\rm ]}
+{\rm [}{\it --os2}{\rm ]}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Produces a Microsoft Windows or OS/2 BMP file as output.
+%.IX BMP
+\shead{OPTIONS}
+\begin{TPlist}{{\bf --windows}}
+\item[{{\bf --windows}}]
+Tells the program to produce a Microsoft Windows BMP file.
+\item[{{\bf --os2}}]
+Tells the program to produce an OS/2 BMP file.
+(This is the default.)
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+bmptoppm(1),
+ppm(5)
+\shead{AUTHOR}
+\copyright 1992 by David W. Sanderson.
+% Permission to use, copy, modify, and distribute this software and
+% its documentation for any purpose and without fee is hereby granted,
+% provided that the above copyright notice appear in all copies and
+% that both that copyright notice and this permission notice appear in
+% supporting documentation.  This software is provided "as is" without
+% express or implied warranty.
+%
+% end of input file: ppmtobmp.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:50 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtogif.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtogif.1
+%
+\phead{ppmtogif}{1}{30 June 1993}{}{}
+
+%.IX ppmtogif
+\shead{NAME}
+ppmtogif - convert a portable pixmap into a GIF file
+\shead{SYNOPSIS}
+{\bf ppmtogif}
+{\rm [}{\bf -interlace}{\rm ]}
+{\rm [}{\bf -sort}{\rm ]}
+{\rm [}{\bf -map}
+{\it mapfile ]}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Produces a GIF file as output.
+%.IX GIF
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -interlace}}
+\item[{{\bf -interlace}}]
+Tells the program to produce an interlaced GIF file.
+\item[{{\bf -sort}}]
+Produces a GIF file with a sorted color map.
+\item[{{\bf -map}}]
+{\it mapfile}
+
+Uses the colors found in the
+{\it mapfile}
+to create the colormap in the GIF file, instead of the colors from
+{\it ppmfile.}
+The
+{\it mapfile}
+can be any
+{\it ppm}
+file; all that matters is the colors in it. If the colors in
+{\it ppmfile}
+do not match those in
+{\it mapfile}
+, they are matched to a ``best match''. A (much) better result can be obtained by
+using the following filter in advance:
+
+{\it ppmquant}
+-floyd -map
+{\it mapfile}
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+giftoppm(1), ppmquant(1), ppm(5)
+\shead{AUTHOR}
+Based on GIFENCOD by David Rowley (mgardi@watdcsu.waterloo.edu).
+Lempel-Ziv compression based on ``compress''.
+
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% The Graphics Interchange Format(c) is the Copyright property of
+% CompuServe Incorporated.  GIF(sm) is a Service Mark property of
+% CompuServe Incorporated.
+%
+% end of input file: ppmtogif.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:51 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtoicr.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtoicr.1
+%
+\phead{ppmtoicr}{1}{30 July 1990}{}{}
+
+%.IX ppmtoicr
+\shead{NAME}
+ppmtoicr - convert a portable pixmap into NCSA ICR format 
+\shead{SYNOPSIS}
+{\bf ppmtoicr}
+{\rm [}{\bf -windowname}
+{\it name}{\rm ]}
+{\rm [}{\bf -expand}
+{\it expand}{\rm ]}
+{\rm [}{\bf -display}
+{\it display}{\rm ]}
+{\rm [}{\bf -rle}{\rm ]}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap file as input.
+Produces an NCSA Telnet Interactive Color Raster graphic file as output.
+%.IX "NCSA ICR"
+If
+{\it ppmfile}
+is not supplied, 
+{\it ppmtoicr}
+will read from standard input.
+\par
+Interactive Color Raster (ICR) is a protocol for displaying raster
+graphics on workstation screens. The protocol is implemented in NCSA
+Telnet for the Macintosh version 2.3.
+%.IX Macintosh
+The ICR protocol shares
+characteristics of the Tektronix graphics terminal emulation protocol.
+%.IX Tektronix
+For example, escape sequences are used to control the display.
+\par
+{\it ppmtoicr}
+will output the appropriate sequences to create a window of the
+dimensions of the input pixmap,
+create a colormap of up to 256
+colors on the display, then load the picture data into the window.
+\par
+Note that there is no icrtoppm tool - this transformation is one way.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -windowname}{\it name}
+}
+\item[{{\bf -windowname}{\it name}
+}]
+Output will be displayed in
+{\it name}
+(Default is to use
+{\it ppmfile}
+or ``untitled'' if standard input is read.)
+\item[{{\bf -expand}{\it expand}
+}]
+Output will be expanded on display by factor 
+{\it expand}
+(For example, a value of 2 will cause four pixels to be displayed for
+every input pixel.)
+\item[{{\bf -display}{\it display}
+}]
+Output will be displayed on screen numbered 
+{\it display}
+\item[{{\bf -rle}}]
+Use run-length encoded format for display. (This will nearly always
+result in a quicker display, but may skew the colormap.)
+\end{TPlist}
+
+\shead{EXAMPLES}
+To display a
+{\it ppm}
+file using the protocol:
+\nofill
+    ppmtoicr ppmfile
+\fill
+This will create a window named 
+{\it ppmfile}
+on the display with the correct dimensions for
+{\it ppmfile,}
+create and download a colormap of up
+to 256 colors, and download the picture into the window. The same effect
+may be achieved by the following sequence:
+\nofill
+    ppmtoicr ppmfile $>$ filename
+    cat filename
+\fill
+To display a GIF 
+file using the protocol in a window titled after the input file, zoom
+the displayed image by a factor of 2, and
+run-length encode the data:
+\nofill
+    giftoppm giffile $|$ ppmtoicr -w giffile -r -e 2
+\fill
+\shead{BUGS}
+\par
+The protocol uses frequent 
+{\it fflush}
+calls to speed up display. If the
+output is saved to a file for later display via
+{\it cat,}
+drawing will be
+much slower. In either case, increasing the Blocksize limit on the
+display will speed up transmission substantially.
+\shead{SEE ALSO}
+{\bf ppm(5)}
+\par\noindent
+{\it NCSA\ Telnet\ for\ the\ Macintosh}{\rm ,}
+University of Illinois at Urbana-Champaign (1989)
+\shead{AUTHOR}
+\copyright 1990 by Kanthan Pillay (svpillay@Princeton.EDU),
+Princeton University Computing and Information Technology.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmtoicr.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:52 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtoilbm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtoilbm.1
+%
+\phead{ppmtoilbm}{1}{29 August 1993}{}{}
+
+%.IX ppmtoilbm
+\shead{NAME}
+ppmtoilbm - convert a portable pixmap into an ILBM file
+\shead{SYNOPSIS}
+{\bf ppmtoilbm}
+{\rm [}{\bf -maxplanes}{\rm $|$}{\bf -mp}
+{\it N}{\rm ]}
+{\rm [}{\bf -fixplanes}{\rm $|$}{\bf -fp}
+{\it N}{\rm ]}
+{\rm [}{\bf -ham6}{\rm $|$}{\bf -ham8}{\rm ]}
+{\rm [}{\bf -dcbits}{\rm $|$}{\bf -dcplanes}{\rm r}{\bf g}{\rm b}{\bf ]}
+{\rm [}{\bf -normal}{\rm $|$}{\bf -hamif}{\rm $|$}{\bf -hamforce}{\rm $|$}{\bf -24if}{\rm $|$}{\bf -24force}{\rm $|$}
+{\rm -dcif}{\bf $|$}{\rm -dcforce}{\bf $|$}{\rm -cmaponly}{\bf ]}
+{\rm [}{\bf -ecs}{\rm $|$}{\bf -aga}{\rm ]}
+{\rm [}{\bf -map}{\rm ppmfile}{\bf ]}
+{\rm [}{\bf ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+%.IX "ILBM"
+Produces an ILBM file as output.
+Supported ILBM types are:
+\begin{TPlist}{Normal ILBMs with 1-16 planes.}
+\item[{Normal ILBMs with 1-16 planes.}]
+\item[{Amiga Hold-and-Modify (HAM) with 3-16 planes.}]
+%.IX "Amiga"
+%.IX "HAM"
+\item[{24 bit.}]
+\item[{Color map (BMHD + CMAP chunk only, nPlanes = 0).}]
+\item[{Unofficial direct color.}]
+1-16 planes for each color component.
+\item[{Chunks written:}]
+BMHD, CMAP, CAMG (only for HAM), BODY (not for colormap files)
+unofficial DCOL chunk for direct color ILBM
+\item[{Chunks ignored:}]
+GRAB, DEST, SPRT, CRNG, CCRT, CLUT, DPPV, DRNG, EPSF
+\item[{Other chunks (ignored but displayed in verbose mode):}]
+NAME, AUTH, (c), ANNO, DPI
+\end{TPlist}
+
+\shead{OPTIONS}
+Options marked with (*) can be prefixed with a ``no'',
+{\it e.g.}, ``-nohamif''. All options can be abbreviated to
+their shortest unique prefix.
+\begin{TPlist}{{\bf -maxplanes $|$ -mp n}}
+\item[{{\bf -maxplanes $|$ -mp n}}]
+(default 5, minimum 1, maximum 16)
+Maximum planes to write in a normal ILBM.  If the pixmap
+does not fit into $<$n$>$ planes, ppmtoilbm writes a HAM file
+(if -hamif is used), a 24bit file (if -24if is used) or a
+direct color file (if -dcif is used) or aborts with an error.
+\item[{{\bf -fixplanes $|$ -fp n}}]
+(min 1, max 16)
+If a normal ILBM is written, it will have exactly $<$n$>$ planes.
+\item[{{\bf -hambits $|$ -hamplanes n}}]
+(default 6, min 3, max 16)
+Select number of planes for HAM picture.  The current Amiga
+hardware supports 6 and 8 planes, so for now you should
+only use this values.
+\item[{{\bf -normal (default)}}]
+Turns off -hamif/-24if/-dcif, -hamforce/-24force/-dcforce and
+-cmaponly.
+\item[{{\bf -hamif (*)}}]
+\item[{{\bf -24if (*)}}]
+\item[{{\bf -dcif (*)}}]
+Write a HAM/24bit/direct color file if the pixmap does not
+fit into $<$maxplanes$>$ planes.
+\item[{{\bf -hamforce (*)}}]
+\item[{{\bf -24force (*)}}]
+\item[{{\bf -dcforce (*)}}]
+Write a HAM/24bit/direct color file.
+\item[{{\bf -dcbits $|$ -dcplanes r g b}}]
+(default 5, min 1, max 16).
+Select number of bits for red, green \& blue in a direct
+color ILBM.
+\item[{{\bf -ecs (default)}}]
+Shortcut for: -hamplanes 6 -maxplanes 5
+\item[{{\bf -aga}}]
+\item[{{\bf Shortcut for: -hamplanes 8 -maxplanes 8}}]
+\item[{{\bf -ham6}}]
+\item[{{\bf Shortcut for: -hamplanes 6 -hamforce}}]
+\item[{{\bf -ham8}}]
+Shortcut for: -hamplanes 8 -hamforce
+\item[{{\bf -map ppmfile}}]
+Write a normal ILBM using the colors in $<$ppmfile$>$ as the
+colormap. The colormap file also determines the number of
+planes, a -maxplanes or -fixplanes option is ignored.
+\item[{{\bf -cmaponly}}]
+Write a colormap file: only BMHD and CMAP chunks, no BODY
+chunk, nPlanes = 0.
+\end{TPlist}
+
+\shead{BUGS}
+Needs a real colormap selection algorithm for HAM pictures,
+instead of using a grayscale colormap.
+\shead{REFERENCES}
+Amiga ROM Kernel Reference Manual - Devices (3rd Ed.)
+Addison Wesley, ISBN 0--201--56775--X
+\shead{SEE ALSO}
+ppm(5), ilbmtoppm(1)
+\shead{AUTHORS}
+\copyright 1989 by Jef Poskanzer.
+\nwl
+Modified August 1993 by Ingo Wilken
+(Ingo.Wilken@informatik.uni-oldenburg.de).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmtoilbm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Tue Nov 30 08:30:37 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtomitsu.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtomitsu.1
+%
+\phead{ppmtomitsu}{1}{29 Jan 1992}{}{}
+
+%.IX ppmtomitsu
+\shead{NAME}
+ppmtomitsu - convert a portable pixmap to a Mitsubishi S340-10 file
+\shead{SYNOPSIS}
+{\bf ppmtomitsu}
+{\rm [-sharpness}
+{\it val}{\rm ]}
+{\rm [}{\bf -enlarge}
+{\it val}{\rm ]}
+{\rm [}{\bf -media}
+{\it string}{\rm ]}
+{\rm [}{\bf -copy}
+{\it val}{\rm ]}
+{\rm [}{\bf -dpi300}{\rm ]}
+{\rm [}{\bf -tiny}{\rm ]}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input and converts it into a format suitable
+to be printed by a Mitsubishi S340-10 printer, or any other Mitsubishi
+color sublimation printer.
+\par
+The Mitsubishi S340-10 Color Sublimation printer supports 24bit color.
+Images of the available sizes take so long to transfer that there is a
+fast method, employing a lookuptable, that ppmtomitsu will use if there
+is a maximum of 256 colors in the pixmap.
+ppmtomitsu will try to position your image to the center of the paper,
+and will rotate your image for you if xsize is larger than ysize.
+If your image is larger than the media allows, ppmtomitsu will quit
+with an error message. (We decided that the media were too expensive
+to have careless users produce misprints.) 
+Once data transmission has started, the job can't be stopped in a
+sane way without resetting the printer.
+The printer understands putting together images in the printers memory;
+ppmtomitsu doesn't utilize this as pnmcat etc provide the same functionality
+and let you view the result on-screen, too.
+The S340-10 is the lowest common denominator printer; for higher
+resolution printers there's the dpi300 option. The other printers also
+support higher values for enlarge eg, but I don't think that's essential
+enough to warrant a change in the program.
+\begin{TPlist}{{\bf -sharpness}{\it \ 1-4}
+}
+\item[{{\bf -sharpness}{\it \ 1-4}
+}]
+ 'sharpness' designation. Default is to use the current sharpness.
+\item[{{\bf -enlarge}{\it \ 1-3}
+}]
+Enlarge by a factor; Default is 1 (no enlarge)
+\item[{{\bf -media}{\it \ A,\ A4,\ AS,\ A4S}
+}]
+Designate the media you're using. Default is 1184 x 1350, which will fit
+on any media. A is 1216 x 1350, A4 is 1184 x 1452, AS is 1216 x 1650 and 
+A4S is 1184 x 1754. A warning: If you specify a different media than the
+printer currently has, the printer will wait until you put in the correct
+media or switch it off.
+\item[{{\bf -copy}{\it \ 1-9}
+}]
+The number of copies to produce. Default is 1.
+\item[{{\bf -dpi300}
+}]
+Double the number of allowed pixels for a S3600-30 Printer in S340-10
+compatibility mode. (The S3600-30 has 300 dpi).
+\item[{{\bf -tiny}
+}]
+Memory-safing, but always slow. The printer will get the data line-by-line
+in 24bit. It's probably a good idea to use this if your machine starts
+paging a lot without this option.
+\end{TPlist}
+
+\shead{REFERENCES}
+Mitsubishi Sublimation Full Color Printer S340-10 Specifications of
+Parallel Interface LSP-F0232F
+\shead{SEE ALSO}
+ppmquant(1), pnmscale(1), ppm(5)
+\shead{BUGS}
+We didn't find any - yet. (Besides, they're called features anyway :-)
+If you should find one, my email-adress is below.
+\shead{AUTHOR}
+\copyright 1992, 93 by S.Petra Zeidler, MPIfR Bonn, Germany.
+(spz@specklec.mpifr-bonn.mpg.de)
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmtomitsu.1
+%--------------------------------------------------
+
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:10 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtomap.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtomap.1
+%
+\phead{ppmtomap}{1}{11 August 1993}{}{}
+
+%.IX ppmtomap
+\shead{NAME}
+ppmtomap - extract all colors from a portable pixmap
+\shead{SYNOPSIS}
+{\bf ppmtomap}
+{\rm [}{\bf -sort}{\rm ]}
+{\rm [}{\bf -square}{\rm ]}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Produces a portable pixmap as output, representing a color map of the
+input file. All N different colors found are put in an Nx1 portable
+pixmap.
+This color map file can be used as a mapfile for
+{\it ppmquant}
+or
+{\it ppmtogif.}
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -sort}}
+\item[{{\bf -sort}}]
+Produces a portable pixmap with the colors in some sorted order.
+\item[{{\bf -square}}]
+Produces a (more or less) square output file, instead of putting all
+colors on the top row.
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{WARNING}
+If you want to use the output file as a mapfile for
+{\it ppmtogif,}
+you first have to do a
+{\it ppmquant 256,}
+since
+{\it ppmtomap}
+is not limited to 256 colors (but to 65536).
+\shead{SEE ALSO}
+ppmtogif(1), ppmquant(1), ppm(5)
+\shead{AUTHOR}
+Marcel Wijkstra (wijkstra@fwi.uva.nl).
+
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% The Graphics Interchange Format(c) is the Copyright property of
+% CompuServe Incorporated.  GIF(sm) is a Service Mark property of
+% CompuServe Incorporated.
+%
+% end of input file: ppmtomap.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:53 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtopcx.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtopcx.1
+%
+\phead{ppmtopcx}{1}{09 April 1990}{}{}
+
+%.IX ppmtopcx
+\shead{NAME}
+ppmtopcx - convert a portable pixmap into a PCX file
+\shead{SYNOPSIS}
+{\bf ppmtopcx}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Produces a PCX file as output.
+%.IX PCX
+\shead{SEE ALSO}
+pcxtoppm(1), ppm(5)
+\shead{AUTHOR}
+\copyright 1990 by Michael Davidson.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmtopcx.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:53 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtopgm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtopgm.1
+%
+\phead{ppmtopgm}{1}{23 December 1988}{}{}
+
+%.IX ppmtopgm
+\shead{NAME}
+ppmtopgm - convert a portable pixmap into a portable graymap
+\shead{SYNOPSIS}
+{\bf ppmtopgm}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Produces a portable graymap as output.
+The quantization formula used is .299 r + .587 g + .114 b.
+\par
+Note that although there is a
+{\it pgmtoppm}
+program, it is not necessary
+for simple conversions from
+{\it pgm}
+to
+{\it ppm}{\rm ,}
+because any ppm program can
+read
+{\it pgm}
+(and
+{\it pbm}
+) files automagically.
+{\it pgmtoppm}
+is for colorizing a
+{\it pgm}
+file.  Also, see
+{\it ppmtorgb3}
+%.IX ppmtorgb3
+for a different way of converting color to gray.
+\shead{QUOTE}
+\nofill
+Cold-hearted orb that rules the night
+Removes the colors from our sight
+Red is gray, and yellow white
+But we decide which is right
+And which is a quantization error.
+\fill
+\shead{SEE ALSO}
+pgmtoppm(1), ppmtorgb3(1), rgb3toppm(1), ppm(5), pgm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmtopgm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:54 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtopi1.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtopi1.1
+%
+\phead{ppmtopi1}{1}{19 July 1990}{}{}
+
+%.IX ppmtopi1
+\shead{NAME}
+ppmtopi1 - convert a portable pixmap into an Atari Degas .pi1 file
+\shead{SYNOPSIS}
+{\bf ppmtopi1}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Produces an Atari Degas .pi1 file as output.
+%.IX Atari
+%.IX "Degas .pi1"
+\shead{SEE ALSO}
+pi1toppm(1), ppm(5), pbmtopi3(1), pi3topbm(1)
+\shead{AUTHOR}
+\copyright 1991 by Steve Belczyk (seb3@gte.com) and Jef Poskanzer.
+% Permission to use, copy, modify and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmtopi1.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:54 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtopict.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtopict.1
+%
+\phead{ppmtopict}{1}{15 April 1990}{}{}
+
+%.IX ppmtopict
+\shead{NAME}
+ppmtopict - convert a portable pixmap into a Macintosh PICT file
+\shead{SYNOPSIS}
+{\bf ppmtopict}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Produces a Macintosh PICT file as output.
+%.IX PICT
+%.IX Macintosh
+\par
+The generated file is only the data fork of a picture.
+You will need a program such as
+{\it mcvert}
+to generate a Macbinary or a BinHex file that contains the necessary
+information to identify the file as a PICT file to MacOS.
+\par
+Even though PICT supports 2 and 4 bits per pixel,
+{\it ppmtopict}
+always generates an 8 bits per pixel file.
+\shead{BUGS}
+The picture size field is only correct if the output is to a file
+since writing into this field requires seeking backwards on a file.
+However the PICT documentation seems to suggest that this field is
+not critical anyway since it is only the lower 16 bits of the picture size.
+\shead{SEE ALSO}
+picttoppm(1), ppm(5), mcvert(1)
+\shead{AUTHOR}
+\copyright 1990 by Ken Yap (ken@cs.rocester.edu).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmtopict.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:55 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtopj.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtopj.1
+%
+\phead{ppmtopj}{1}{13 July 1991}{}{}
+
+%.IX ppmtopj
+\shead{NAME}
+ppmtopj - convert a portable pixmap to an HP PaintJet file
+\shead{SYNOPSIS}
+{\bf ppmtopj}
+{\rm [-gamma}
+{\it val}{\rm ]}
+{\rm [}{\bf -xpos}
+{\it val}{\rm ]}
+{\rm [}{\bf -ypos}
+{\it val}{\rm ]}
+{\rm [}{\bf -back}
+{\bf dark}{\rm $|$}{\bf lite}{\rm ]}
+{\rm [}{\bf -rle}{\rm ]}
+{\rm [}{\bf -center}{\rm ]}
+{\rm [}{\bf -render}
+{\bf none$|$snap$|$bw$|$dither$|$diffuse$|$monodither$|$monodiffuse$|$clusterdither$|$monoclusterdither}{\rm ]}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input and converts it into a format suitable
+to be printed by an HP PaintJet printer.
+\par
+For best results, the input file should be in 8-color RGB form;
+{\it i.e.}, it should have only
+the 8 binary combinations of full-on and full-off primaries.
+You could get this by sending the input file through
+{\it ppmquant -map}
+with a map file such as:
+\nofill
+    P3
+    8 1
+    255
+    0 0 0      255 0 0    0 255 0    0 0 255
+    255 255 0  255 0 255  0 255 255  255 255 255
+\fill
+Or else you could use use
+{\it ppmdither -red 2 -green 2 -blue 2.}
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -rle}}
+\item[{{\bf -rle}}]
+Run length encode the image.
+(This can result in larger images)
+\item[{{\bf -back}}]
+Enhance the foreground by indicating if the background is light or
+dark compated to the foreground.
+\item[{{\bf -render}{\it \ alg}
+}]
+Use an internal rendering algorithm (default dither).
+\item[{{\bf -gamma}{\it \ int}
+}]
+Gamma correct the image using the integet parameter as a gamma (default 0).
+\item[{{\bf -center}}]
+Center the image to an 8.5 by 11 page
+\item[{{\bf -xpos}{\it \ pos}
+}]
+Move by pos pixels in the x direction.
+\item[{{\bf -ypos}{\it \ pos}
+}]
+Move by pos pixels in the y direction.
+\end{TPlist}
+
+\shead{REFERENCES}
+HP PaintJet XL Color Graphics Printer User's Guide
+\shead{SEE ALSO}
+pnmdepth(1), ppmquant(1), ppmdither(1), ppm(5)
+\shead{BUGS}
+Most of the options have not been tested because of the price of the paper.
+\shead{AUTHOR}
+\copyright 1991 by Christos Zoulas.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmtopj.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:10 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtopjxl.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtopjxl.1
+%
+\phead{ppmtopjxl}{1}{14 March 1991}{}{}
+
+\shead{NAME}
+ppmtopjxl - convert a portable pixmap into an HP PaintJet XL PCL file
+\shead{SYNOPSIS}
+ppmtopjxl [-nopack] [-gamma
+{\it $<$n$>$}
+] [-presentation] [-dark] [-diffuse] [-cluster] [-dither] [-xshift
+{\it $<$s$>$}
+] [-yshift
+{\it $<$s$>$}
+] [-xshift
+{\it $<$s$>$}
+] [-yshift
+{\it $<$s$>$}
+] [-xsize$|$-width$|$-xscale
+{\it $<$s$>$}
+] [-ysize$|$-height$|$-yscale
+{\it $<$s$>$}
+] [ppmfile]
+
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Produces a PCL file suitable for printing on an HP PaintJet XL printer as
+output.
+\par
+The generated file is not suitable for printing on a normal PrintJet printer.
+The
+{\bf --nopack}
+option generates a file which does not use the normal TIFF 4.0 compression
+method. This file might be printable on a normal PaintJet printer (not an XL).
+\par
+The
+{\bf --gamma}
+option sets the gamma correction for the image. The useful range for the
+PaintJet XL is approximately 0.6 to 1.5.
+\par
+The rendering algorithm used for images can be altered with the
+{\bf -dither,}
+{\bf -cluster,}
+and
+{\bf -diffuse}
+options. These options select ordered dithering, clustered ordered dithering,
+or error diffusion respectively.
+The
+{\bf --dark}
+option can be used to enhance images with a dark background when they are
+reduced in size.
+The
+{\bf --presentation}
+option turns on presentation mode, in which two passes are made over the paper
+to increase ink density. This should be used only for images where quality is
+critical.
+
+\par
+The image can be resized by setting the 
+{\bf --xsize}
+and 
+{\bf --ysize}
+options. The parameter to either of these options is interpreted as the
+number of dots to set the width or height to, but an optional dimension of
+`%
+\bf pt%
+\rm ' (points), `%
+\bf dp%
+\rm ' (decipoints), `%
+\bf in%
+\rm ' (inches), or
+`%
+\bf cm%
+\rm ' (centimetres) may be appended.
+If only one dimension is specified, the other will be scaled appropriately.
+
+The options
+{\bf --width}
+and
+{\bf --height}
+are synonyms of
+{\bf --xsize}
+and
+{\bf --ysize.}
+
+The
+{\bf --xscale}
+and
+{\bf --yscale}
+options can alternatively be used to scale the image by a simple factor.
+
+\par
+The image can be shifted on the page by using the
+{\bf --xshift}
+and
+{\bf --yshift}
+options. These move the image the specified dimensions right and down.
+
+\shead{SEE ALSO}
+ppm(5)
+\shead{AUTHOR}
+Angus Duggan
+%
+% end of input file: ppmtopjxl.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:56 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtopuzz.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtopuzz.1
+%
+\phead{ppmtopuzz}{1}{22 August 1990}{}{}
+
+%.IX ppmtopuzz
+\shead{NAME}
+ppmtopuzz - convert a portable pixmap into an X11 ``puzzle'' file
+\shead{SYNOPSIS}
+{\bf ppmtopuzz}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Produces an X11 ``puzzle'' file as output.
+A ``puzzle'' file is for use with the
+{\it puzzle}
+%.IX puzzle
+%.IX "X window system"
+program included with the X11 distribution -
+{\it puzzle}{\rm 's}
+{\bf -picture}
+flag lets you specify an image file.
+\shead{SEE ALSO}
+ppm(5), puzzle(1)
+\shead{AUTHOR}
+\copyright 1991 by Jef Poskanzer.
+% Permission to use, copy, modify and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmtopuzz.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:57 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtorgb3.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtorgb3.1
+%
+\phead{ppmtorgb3}{1}{10 January 1991}{}{}
+
+%.IX ppmtorgb3
+\shead{NAME}
+ppmtorgb3 - separate a portable pixmap into three portable graymaps
+\shead{SYNOPSIS}
+{\bf ppmtorgb3}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Writes three portable graymaps as output, one each for red, green, and blue.
+%.IX "color separation"
+\par
+The output filenames are constructed by taking the input filename,
+stripping off any extension, and appending ``.red'', ``.grn'', and
+``.blu''.
+For example, separating lenna.ppm would result in lenna.red, lenna.grn,
+and lenna.blu.
+If the input comes from stdin, the names are noname.red, noname.grn,
+and noname.blu.
+\shead{SEE ALSO}
+rgb3toppm(1), ppmtopgm(1), pgmtoppm(1), ppm(5), pgm(5)
+\shead{AUTHOR}
+\copyright 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmtorgb3.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:57 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtosixel.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtosixel.1
+%
+\phead{ppmtosixel}{1}{26 April 1991}{}{}
+
+%.IX ppmtosixel
+\shead{NAME}
+ppmtosixel - convert a portable pixmap into DEC sixel format
+\shead{SYNOPSIS}
+{\bf ppmtosixel}
+{\rm [}{\bf -raw}{\rm ]}
+{\rm [}{\bf -margin}{\rm ]}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Produces sixel commands (SIX) as output.
+The output is formatted for color printing, {\it e.g.}, for a DEC LJ250 color
+inkjet printer. 
+\par
+If RGB values from the PPM file do not have maxval=100,
+the RGB values are rescaled.
+A printer control header and a color assignment table begin the SIX file.
+Image data is written in a compressed format by default.
+A printer control footer ends the image file.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -raw}}
+\item[{{\bf -raw}}]
+If specified, each pixel will be explicitly described in the image file.
+If
+{\bf -raw}
+is not specified, output will default to compressed format in which
+identical adjacent pixels are replaced by ``repeat pixel'' commands.
+A raw file is often an order of magnitude larger than a compressed
+file and prints much slower.
+\item[{{\bf -margin}}]
+If
+{\bf -margin}
+is not specified, the image will be start at the left margin
+(of the window, paper, or whatever).
+If
+{\bf -margin}
+is specified, a 1.5 inch left margin will offset the image.
+\end{TPlist}
+
+\shead{PRINTING}
+Generally, sixel files must reach the printer unfiltered.
+Use the lpr -x option or cat filename $>$ /dev/tty0?.
+\shead{BUGS}
+Upon rescaling, truncation of the least significant bits of RGB values
+may result in poor color conversion.
+If the original PPM maxval was greater than 100, rescaling also
+reduces the image depth.
+While the actual RGB values from the ppm file are more or less
+retained, the color palette of the LJ250 may not match the colors
+on your screen.
+This seems to be a printer limitation.
+\shead{SEE ALSO}
+ppm(5)
+\shead{AUTHOR}
+\copyright 1991 by Rick Vinci.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmtosixel.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:58 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtotga.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtotga.1
+%
+\phead{ppmtotga}{1}{28 October 1991}{}{}
+
+%.IX ppmtotga
+\shead{NAME}
+ppmtotga - convert portable pixmap into a TrueVision Targa file
+\shead{SYNOPSIS}
+{\bf ppmtotga}
+{\rm [}{\bf -mono$|$-cmap$|$-rgb}{\rm ]}
+{\rm [}{\bf -norle}{\rm ]}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Produces a TrueVision Targa file as output.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -mono}}
+\item[{{\bf -mono}}]
+Forces Targa file to be of type 8 bit monochrome.  Input must be a portable
+bitmap or a portable graymap.
+\item[{{\bf -cmap}}]
+Forces Targa file to be of type 24 bit colormapped.  Input must be a portable
+bitmap, a portable graymap or a portable pixmap containing no more than
+256 distinct colors.
+\item[{{\bf -rgb}}]
+Forces Targa file to be of type 24 bit unmapped color.
+\item[{{\bf -norle}}]
+Disables run-length encoding, in case you have a Targa reader which
+can't read run-length encoded files.
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.  If no
+file type is specified the most highly constained compatible type is
+used, where monochrome is more constained than colormapped which is in
+turn more constained than unmapped.
+\shead{BUGS}
+Does not support all possible Targa file types.
+Should really be in PNM, not PPM.
+\shead{SEE ALSO}
+tgatoppm(1), ppm(5)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Mark Shand and Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmtotga.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:59 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtouil.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtouil.1
+%
+\phead{ppmtouil}{1}{31 August 1990}{}{}
+
+%.IX ppmtouil
+\shead{NAME}
+ppmtouil - convert a portable pixmap into a Motif UIL icon file
+\shead{SYNOPSIS}
+{\bf ppmtouil}
+{\rm [}{\bf -name}
+{\it uilname}{\rm ]}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Produces a Motif UIL icon file as output.
+%.IX Motif
+%.IX UIL
+%.IX "X window system"
+\par
+If the program was compiled with an rgb database specified, and
+a RGB value from the ppm input matches a RGB value from the database,
+then the corresponding color name mnemonic is printed in the UIL's colormap.
+If no rgb database was compiled in, or if the RGB values don't match,
+then the color
+will be printed with the \#RGB, \#RRGGBB, \#RRRGGGBBB, or \#RRRRGGGGBBBB
+hexadecimal format.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -name}}
+\item[{{\bf -name}}]
+Allows you to specify the prefix string which is printed
+in the resulting UIL output.  If not specified, will default to the
+filename (without extension) of the ppmfile argument.
+If
+{\bf -name}
+is not specified and no ppmfile
+is specified ({\it i.e.}, piped input), the prefix string will default to
+the string ``noname''.
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+ppm(5)
+\shead{AUTHOR}
+Converted by Jef Poskanzer from ppmtoxpm.c, which is
+\copyright 1990 by Mark W. Snitily.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmtouil.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:59 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtoxpm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtoxpm.1
+%
+\phead{ppmtoxpm}{1}{Tue Apr 9 1991}{}{}
+
+\shead{NAME}
+ppmtoxpm - convert a portable pixmap into an X11 pixmap
+\shead{SYNOPSIS}
+ppmtoxpm [-name $<$xpmname$>$] [-rgb $<$rgb-textfile$>$] [$<$ppmfile$>$]
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Produces X11 pixmap  (version 3) as output which
+can be loaded directly by the XPM library.
+\par
+The {\bf -nameoption} allows one to specify the prefix string which is printed
+in the resulting XPM output.  If not specified, will default to the
+filename (without extension) of the $<$ppmfile$>$ argument.
+If {\bf -nameis} not specified and $<$ppmfile$>$
+is not specified ({\it i.e.}, piped input), the prefix string will default to
+the string ``noname''.
+\par
+The {\bf -rgboption} allows one to specify an X11 rgb text file for the
+lookup of color name mnemonics.  This rgb text file is typically the
+/usr/lib/X11/rgb.txt of the MIT X11 distribution, but any file using the
+same format may be used.  When specified and
+a RGB value from the ppm input matches a RGB value from the $<$rgb-textfile$>$,
+then the corresponding color name mnemonic is printed in the XPM's colormap.
+If {\bf -rgbis} not specified, or if the RGB values don't match, then the color
+will be printed with the \#RGB, \#RRGGBB, \#RRRGGGBBB, or \#RRRRGGGGBBBB
+hexadecimal format.
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\par
+For example, to convert the file ``dot'' (found in /usr/include/X11/bitmaps),
+from xbm to xpm one could specify
+\begin{IPlist}
+\IPitem{{}}
+xbmtopbm dot $|$ ppmtoxpm -name dot
+\end{IPlist}
+
+\par
+or, with a rgb text file (in the local directory)
+\begin{IPlist}
+\IPitem{{}}
+xbmtopbm dot $|$ ppmtoxpm -name dot -rgb rgb.txt
+\end{IPlist}
+
+\shead{BUGS}
+An option to match the closest (rather than exact) color name mnemonic
+from the rgb text would be a desirable enhancement.
+\par
+Truncation of the least significant bits of a RGB value may result in
+nonexact matches when performing color name mnemonic lookups.
+\shead{SEE ALSO}
+ppm(5)
+\nwl
+XPM Manual by Arnaud Le Hors (lehors@mirsa.inria.fr).
+\shead{AUTHOR}
+\copyright 1990 by Mark W. Snitily.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted, provided
+that the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.  This software is provided "as is" without express or
+implied warranty.
+
+This tool was developed for Schlumberger Technologies, ATE Division, and
+with their permission is being made available to the public with the above
+copyright notice and permission notice.
+
+Upgraded to XPM2 by
+   Paul Breslaw, Mecasoft SA, Zurich, Switzerland (paul@mecazh.uu.ch)
+   Thu Nov  8 16:01:17 1990
+
+Upgraded to XPM version 3 by
+   Arnaud Le Hors (lehors@mirsa.inria.fr)
+   Tue Apr 9 1991
+
+
+%
+% end of input file: ppmtoxpm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:00 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtoyuv.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtoyuv.1
+%
+\phead{ppmtoyuv}{1}{25 March 91}{}{}
+
+%.IX ppmtoyuv
+\shead{NAME}
+ppmtoyuv - convert a portable pixmap into an Abekas YUV file
+\shead{SYNOPSIS}
+{\bf ppmtoyuv}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Produces an Abekas YUV file as output.
+%.IX Abekas
+\shead{SEE ALSO}
+yuvtoppm(1), ppm(5)
+\shead{AUTHOR}
+Marc Boucher (marc@PostImage.COM),
+based on Example Conversion Program, A60/A64 Digital Video Interface
+Manual, page 69.
+\par
+\copyright 1991 by DHD PostImage Inc.
+\par
+\copyright 1987 by Abekas Video Systems Inc.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmtoyuv.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:11 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ppmtoyuvsplit.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ppmtoyuvsplit.1
+%
+\phead{ppmtoyuvsplit}{1}{9 September 1993}{}{}
+
+%.IX ppmtoyuvsplit
+\shead{NAME}
+ppmtoyuvsplit - convert a portable pixmap into 3 subsampled raw YUV files
+\shead{SYNOPSIS}
+{\bf ppmtoyuvsplit}
+{\it basename}
+{\rm [}{\it ppmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a portable pixmap as input.
+Produces 3 raw files basename.Y, basename.U and basename.V as output.
+These files are the subsampled raw YUV representation of the input
+pixmap, as required by the Stanford MPEG codec. The subsampling is done
+by arithmetic mean of 4 pixels colors into one. The YUV values are scaled
+according to CCIR.601, as assumed by MPEG.
+\shead{SEE ALSO}
+mpeg(1), ppm(5)
+\shead{AUTHOR}
+\copyright 1993 by Andre Beck. (Andre\_Beck@IRS.Inf.TU-Dresden.de).
+\par
+Based on ppmtoyuv.c.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ppmtoyuvsplit.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:33 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: psidtopgm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: psidtopgm.1
+%
+\phead{psidtopgm}{1}{02 August 89}{}{}
+
+%.IX psidtopgm
+\shead{NAME}
+psidtopgm - convert PostScript ``image'' data into a portable graymap
+\shead{SYNOPSIS}
+{\bf psidtopgm}
+{\it width height bits/sample}
+{\rm [}{\it imagedata}{\rm ]}
+\shead{DESCRIPTION}
+Reads the ``image'' data from a PostScript file as input.
+%.IX PostScript
+Produces a portable graymap as output.
+\par
+This is a very simple and limited program, and is here only because
+so many people have asked for it.
+To use it you have to
+{\bf manually}
+extract the readhexstring data portion from your PostScript file, and then
+give the width, height, and bits/sample on the command line.
+Before you attempt this, you should
+{\bf at least}
+read the description of the ``image'' operator in the PostScript Language
+Reference Manual.
+\par
+It would probably not be too hard to write a script that uses this filter
+to read a specific variety of PostScript image, but the variation is too
+great to make a general-purpose reader.
+Unless, of course, you want to write a full-fledged PostScript interpreter...
+\shead{SEE ALSO}
+pnmtops(1), pgm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: psidtopgm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:00 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: qrttoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: qrttoppm.1
+%
+\phead{qrttoppm}{1}{25 August 1989}{}{}
+
+%.IX qrttoppm
+\shead{NAME}
+qrttoppm - convert output from the QRT ray tracer into a portable pixmap
+\shead{SYNOPSIS}
+{\bf qrttoppm}
+{\rm [}{\it qrtfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a QRT file as input.
+%.IX "QRT raytracer"
+Produces a portable pixmap as output.
+\shead{SEE ALSO}
+ppm(5)
+\shead{AUTHOR}
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: qrttoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:28 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: rasttopnm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: rasttopnm.1
+%
+\phead{rasttopnm}{1}{13 January 1991}{}{}
+
+%.IX rasttopnm
+\shead{NAME}
+rasttopnm - convert a Sun rasterfile into a portable anymap
+\shead{SYNOPSIS}
+{\bf rasttopnm}
+{\rm [}{\it rastfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a Sun rasterfile as input.
+%.IX Sun
+%.IX rasterfile
+Produces a portable anymap as output.
+The type of the output file depends on the input file - if it's
+black \& white, a
+{\it pbm}
+file is written, else if it's grayscale a
+{\it pgm}
+file, else a
+{\it ppm}
+file.  The program tells you which type it is writing.
+\shead{SEE ALSO}
+pnmtorast(1), pnm(5)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: rasttopnm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:34 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: rawtopgm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: rawtopgm.1
+%
+\phead{rawtopgm}{1}{15 June 1993}{}{}
+
+%.IX rawtopgm
+\shead{NAME}
+rawtopgm - convert raw grayscale bytes into a portable graymap
+\shead{SYNOPSIS}
+{\bf rawtopgm}
+{\rm [}{\bf -headerskip}
+{\it N}{\rm ]}
+{\rm [}{\bf -rowskip}
+{\it N}{\rm ]}
+{\rm [}{\bf -tb}{\rm $|$}{\bf -topbottom}{\rm ]}
+{\rm [}{\it width}
+{\it height}{\rm ]}
+{\rm [}{\it imagedata}{\rm ]}
+\shead{DESCRIPTION}
+Reads raw grayscale bytes as input.
+%.IX "raw grayscale"
+Produces a portable graymap as output.
+The input file is just grayscale bytes.
+If you don't specify the width and height on the command line,
+the program will check the size of the image and try to make
+a quadratic image of it. It is an error to supply a non
+quadratic image without specifying width and height.
+The maxval is assumed to be 255.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -headerskip}}
+\item[{{\bf -headerskip}}]
+If the file has a header, you can use this flag to
+skip over it.
+\item[{{\bf -rowskip}}]
+If there is padding at the ends of the rows, you can skip it with this flag.
+Note that rowskip can be a real number.
+Amazingly, I once had an image with 0.376 bytes of padding per row.
+This turned out to be due to a file-transfer problem, but I was still
+able to read the image.
+\item[{{\bf -tb -topbottom}}]
+Flips the image upside down.
+The first pixel in a pgm file is in the lower left corner of the image.
+For conversion from images with the first pixel in the upper left corner
+({\it e.g.}, the Molecular Dynamics and Leica confocal formats) this flips the
+image right.
+This is equivalent to
+{\bf rawtopgm\ [file]\ $|$\ pnmflip\ -tb .}
+\end{TPlist}
+
+\shead{BUGS}
+If you don't specify the image width and height, the program will
+try to read the entire image to a memory buffer. If you get a
+message that states that you are out of memory, try to specify the width
+and height on the command line. Also, the -tb option consumes much
+memory.
+\shead{SEE ALSO}
+pgm(5), rawtoppm(1), pnmflip(1)
+\shead{AUTHORS}
+\copyright 1989 by Jef Poskanzer.
+\nwl
+Modified June 1993 by Oliver Trepte (oliver@fysik4.kth.se).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: rawtopgm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:01 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: rawtoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: rawtoppm.1
+%
+\phead{rawtoppm}{1}{06 February 1991}{}{}
+
+%.IX rawtoppm
+\shead{NAME}
+rawtoppm - convert raw RGB bytes into a portable pixmap
+\shead{SYNOPSIS}
+{\bf rawtoppm}
+{\rm [}{\bf -headerskip}
+{\it N}{\rm ]}
+{\rm [}{\bf -rowskip}
+{\it N}{\rm ]}
+{\rm [}{\bf -rgb}{\rm $|$}{\bf -rbg}{\rm $|$}{\bf -grb}
+{\rm $|$}{\bf -gbr}{\rm $|$}{\bf -brg}{\rm $|$}{\bf -bgr}
+{\rm ]}
+{\rm [}{\bf -interpixel}{\rm $|$}{\bf -interrow}{\rm ]}
+{\it width height}
+{\rm [}{\it imagedata}{\rm ]}
+\shead{DESCRIPTION}
+Reads raw RGB bytes as input.
+%.IX "raw RGB"
+Produces a portable pixmap as output.
+The input file is just RGB bytes.
+You have to specify the width and height on the command line,
+since the program obviously can't get them from the file.
+The maxval is assumed to be 255.
+If the resulting image is upside down, run it through
+{\bf pnmflip\ -tb .}
+%.IX pnmflip
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -headerskip}}
+\item[{{\bf -headerskip}}]
+If the file has a header, you can use this flag to
+skip over it.
+\item[{{\bf -rowskip}}]
+If there is padding at the ends of the rows, you can skip it with this flag.
+\item[{{\bf -rgb -rbg -grb -gbr -brg -bgr}}]
+These flags let you specify alternate color orders.  The default is
+{\bf -rgb}{\rm .}
+\item[{{\bf -interpixel -interrow}}]
+These flags let you specify how the colors are interleaved.
+The default is
+{\bf -interpixel}{\rm ,}
+meaning interleaved by pixel.
+A byte of red, a byte of green, and a byte
+of blue, or whatever color order you specified.
+{\bf -interrow}
+means interleaved by row - a row of red, a row of green, a row of blue,
+assuming standard rgb color order.
+An
+{\bf -interplane}
+flag  - all the red pixels, then all the green, then all the blue - would
+be an obvious extension, but is not implemented.
+You could get the same effect by splitting the file into three parts
+(perhaps using
+{\it dd}{\rm ),}
+turning each part into a PGM file with rawtopgm, and then combining them
+with rgb3toppm.
+\end{TPlist}
+
+\shead{SEE ALSO}
+ppm(5), rawtopgm(1), rgb3toppm(1), pnmflip(1)
+\shead{AUTHOR}
+\copyright 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: rawtoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:02 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: rgb3toppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: rgb3toppm.1
+%
+\phead{rgb3toppm}{1}{15 February 1990}{}{}
+
+%.IX rgb3toppm
+\shead{NAME}
+rgb3toppm - combine three portable graymaps into one portable pixmap
+\shead{SYNOPSIS}
+{\bf rgb3toppm}
+{\it redpgmfile greenpgmfile bluepgmfile}
+\shead{DESCRIPTION}
+Reads three portable graymaps as input.
+Combines them and produces one portable pixmap as output.
+\shead{SEE ALSO}
+ppmtorgb3(1), pgmtoppm(1), ppmtopgm(1), ppm(5), pgm(5)
+\shead{AUTHOR}
+\copyright 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: rgb3toppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:32 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: sirtopnm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: sirtopnm.1
+%
+\phead{sirtopnm}{1}{20 March 1991}{}{}
+
+\shead{NAME}
+sirtopnm - convert a Solitaire file into a portable anymap
+\shead{SYNOPSIS}
+{\bf sirtopnm}
+{\rm [}{\it sirfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a Solitaire Image Recorder file as input.
+Produces a portable anymap as output.
+The type of the output file depends on the input file - if it's
+an MGI TYPE 17 file, a
+{\it pgm}
+file is written. If it's an MGI TYPE 11 file, a
+{\it ppm}
+file is written.  The program tells you which type it is writing.
+\shead{BUGS}
+
+\shead{SEE ALSO}
+pnmtosir(1), pnm(5)
+\shead{AUTHOR}
+\copyright 1991 by Marvin Landis.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+
+%
+% end of input file: sirtopnm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:02 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: sldtoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: sldtoppm.1
+%
+\phead{sldtoppm}{1}{10 October 1991}{}{}
+
+%.IX sldtoppm
+%.IX AutoCAD
+\shead{NAME}
+sldtoppm - convert an AutoCAD slide file into a portable pixmap
+\shead{SYNOPSIS}
+\raggedright
+{\bf sldtoppm}
+'in 14n
+{\rm [}{\bf -adjust}{\rm ]}
+{\rm [}{\bf -dir}{\rm ]}
+{\rm [}{\bf -height}{\rm $|$}{\bf -ysize}
+{\it s}{\rm ]}
+{\rm [}{\bf -info}{\rm ]}
+{\rm [}{\bf -lib}{\rm $|$}{\bf -Lib}
+{\it name}{\rm ]}
+{\rm [}{\bf -scale}
+{\it s}{\rm ]}
+{\rm [}{\bf -verbose}{\rm ]}
+{\rm [}{\bf -width}{\rm $|$}{\bf -xsize}
+{\it s}{\rm ]}
+{\rm [}{\it slidefile}{\rm ]}
+\ind{-7.0em}
+%.ad
+\shead{DESCRIPTION}
+Reads an AutoCAD{l}ide file and outputs a portable pixmap.
+If no
+{\it slidefile}
+is specified, input is read from standard input.
+The ppmdraw library is used to convert the vector and polygon
+information in the slide file to a pixmap; see the file ppmdraw.h for
+details on this package.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -adjust}}
+\item[{{\bf -adjust}}]
+If the display on which the slide file was created had non-square
+pixels, when the slide is processed with
+{\bf sldtoppm}
+and the
+{\bf -adjust}
+option is not present, the following warning will appear:
+\ind{-6.8in}
+Warning - pixels on source screen were non-square.
+\nwl
+Specifying
+{\bf -adjust}
+will correct image width to compensate.
+\ind{-7.0in}
+Specifying the
+{\bf -adjust}
+option causes
+{\bf sldtoppm}
+to scale the width of the image so that pixels in the resulting
+portable pixmap are square (and hence circles appear as true circles,
+not ellipses).  The scaling is performed in the vector domain, before
+scan converting the objects.  The results are, therefore, superior in
+appearance to what you'd obtain were you to perform the equivalent
+scaling with
+{\bf pnmscale}
+after the bitmap had been created.
+\item[{{\bf -dir}}]
+The input is assumed to be an AutoCAD slide library file.  A directory
+listing each slide in the library is printed on standard error.
+\item[{{\bf -height}{\it \ size}
+}]
+Scales the image in the vector domain so it is
+{\it size}
+pixels in height.  If no
+{\bf -width}
+or
+{\bf -xsize}
+option is specified, the width will be adjusted to preserve the
+pixel aspect ratio. 
+\item[{{\bf -info}}]
+Dump the slide file header on standard error, displaying the original
+screen size and aspect ratio among other information.
+\item[{{\bf -lib}{\it \ name}
+}]
+Extracts the slide with the given
+{\it name}
+from the slide library given as input.  The specified
+{\it name}
+is converted to upper case.
+\item[{{\bf -Lib}{\it \ name}
+}]
+Extracts the slide with the given
+{\it name}
+from the slide library given as input.  The
+{\it name}
+is used exactly as specified; it is not converted to upper case.
+\item[{{\bf -scale}{\it \ s}
+}]
+Scales the image by factor
+{\it s}{\rm ,}
+which may be any floating point value greater than zero.  Scaling is
+done after aspect ratio adjustment, if any.  Since scaling is
+performed in the vector domain, before rasterisation, the results look
+much better than running the output of
+{\bf sldtoppm}
+through
+{\bf pnmscale}{\rm .}
+\item[{{\bf -verbose}}]
+Dumps the slide file header and lists every vector and polygon in the
+file on standard error.
+\item[{{\bf -width}{\it \ size}
+}]
+Scales the image in the vector domain so it is
+{\it size}
+pixels wide.  If no
+{\bf -height}
+or
+{\bf -ysize}
+option is specified, the height will be adjusted to preserve the
+pixel aspect ratio. 
+\item[{{\bf -xsize}{\it \ size}
+}]
+Scales the image in the vector domain so it is
+{\it size}
+pixels wide.  If no
+{\bf -height}
+or
+{\bf -ysize}
+option is specified, the height will be adjusted to preserve the
+pixel aspect ratio. 
+\item[{{\bf -ysize}{\it \ size}
+}]
+Scales the image in the vector domain so it is
+{\it size}
+pixels in height.  If no
+{\bf -width}
+or
+{\bf -xsize}
+option is specified, the width will be adjusted to preserve the
+pixel aspect ratio. 
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{BUGS}
+Only Level 2 slides are converted.  Level 1 format has been obsolete
+since the advent of AutoCAD Release 9 in 1987, and was not portable
+across machine architectures.
+\par
+Slide library items with names containing 8 bit (such as ISO) or 16
+bit (Kanji, for example) characters may not be found when chosen with the
+{\bf -lib}
+option unless
+{\bf sldtoppm}
+has been built with character set conversion functions appropriate to
+the locale.  You can always retrieve slides from libraries regardless
+of the character set by using the
+{\bf -Lib}
+option and specifying the precise name of library member.  Use the
+{\bf -dir}
+option to list the slides in a library if you're unsure of the
+exact name.
+\shead{SEE ALSO}
+AutoCAD Reference Manual:
+{\it Slide\ File\ Format}{\rm ,}
+{\bf pnmscale}{\rm (1),}
+{\bf ppm}{\rm (5)}
+\shead{AUTHOR}
+\ind{1\parindent}{\nofill
+    John Walker
+    Autodesk SA
+    Avenue des Champs-Montants 14b
+    CH-2074 MARIN
+    Suisse/Schweiz/Svizzera/Svizra/Switzerland
+\fill}
+\begin{TPlist}{Usenet:}
+\item[{Usenet:}]
+kelvin@Autodesk.com
+\item[{Fax:}]
+038/33 88 15
+\item[{Voice:}]
+038/33 76 33
+\end{TPlist}
+
+\par
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+without any conditions or restrictions.  This software is provided ``as
+is'' without express or implied warranty.
+\par
+AutoCAD and Autodesk are registered trademarks of Autodesk, Inc.
+%
+% end of input file: sldtoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:03 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: spctoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: spctoppm.1
+%
+\phead{spctoppm}{1}{19 July 1990}{}{}
+
+%.IX spctoppm
+\shead{NAME}
+spctoppm - convert an Atari compressed Spectrum file into a portable pixmap
+\shead{SYNOPSIS}
+{\bf spctoppm}
+{\rm [}{\it spcfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads an Atari compressed Spectrum file as input.
+%.IX Atari
+%.IX Spectrum
+Produces a portable pixmap as output.
+\shead{SEE ALSO}
+sputoppm(1), ppm(5)
+\shead{AUTHOR}
+\copyright 1991 by Steve Belczyk (seb3@gte.com) and Jef Poskanzer.
+% Permission to use, copy, modify and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: spctoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:37 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: spottopgm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: spottopgm.1
+%
+\phead{spottopgm}{1L}{}{}{}
+
+\shead{NAME}
+spottopgm -- convert SPOT satellite images to Portable Greymap format
+\shead{SYNTAX}
+spottopgm [--1$|$2$|$3] [Firstcol Firstline Lastcol Lastline] inputfile
+\shead{OPTIONS}
+\begin{TPlist}{{\bf --1$|$2$|$3}}
+\item[{{\bf --1$|$2$|$3}}]
+Extract the given colour from the SPOT image. The colours are infra-red,
+visible light and ultra-violet, although I don't know which corresponds
+to which number. If the image is in colour, this will be announced on
+standard error. The default colour is 1.
+\item[{{\bf Firstcol Firstline Lastcol Lastline}}]
+Extract the specified rectangle from the SPOT image. Most SPOT images are
+3000 lines long and 3000 or more columns wide. Unfortunately the SPOT format
+only gives the width and not the length. The width is printed on standard
+error. The default rectangle is the width of the input image by 3000 lines.
+\end{TPlist}
+
+\shead{DESCRIPTION}
+{\it Spottopgm}
+converts the named
+{\bf inputfile}
+into Portable Greymap format, defaulting to the first color and the whole
+SPOT image unless specified by the options.
+\shead{INSTALLATION}
+You
+{\bf must}
+edit the source program and either define BIGENDIAN or LITTLEENDIAN,
+and fix the typedefs for uint32t, uint16t and uint8t appropriately.
+\shead{BUGS}
+Currently
+{\it spottopgm}
+doesn't determine the length of the input file; this would involve two
+passes over the input file. It defaults to 3000 lines instead.
+\par
+{\it Spottopgm}
+could extract a three-color image (ppm), but I didn't feel like making the
+program more complicated than it is now. Besides, there is no one-to-one
+correspondence between red, green, blue and infra-red, visible and
+ultra-violet.
+\par
+I've only had a limited number of SPOT images to play with, and therefore
+wouldn't guarantee that this will work on any other images.
+\shead{AUTHOR}
+Warren Toomey  wkt@csadfa.cs.adfa.oz.au
+\shead{SEE ALSO}
+The rest of the Pbmplus suite.
+%
+% end of input file: spottopgm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:03 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: sputoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: sputoppm.1
+%
+\phead{sputoppm}{1}{19 July 1990}{}{}
+
+%.IX sputoppm
+\shead{NAME}
+sputoppm - convert an Atari uncompressed Spectrum file into a portable pixmap
+\shead{SYNOPSIS}
+{\bf sputoppm}
+{\rm [}{\it spufile}{\rm ]}
+\shead{DESCRIPTION}
+Reads an Atari uncompressed Spectrum file as input.
+%.IX Atari
+%.IX Spectrum
+Produces a portable pixmap as output.
+\shead{SEE ALSO}
+spctoppm(1), ppm(5)
+\shead{AUTHOR}
+\copyright 1991 by Steve Belczyk (seb3@gte.com) and Jef Poskanzer.
+% Permission to use, copy, modify and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: sputoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:04 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: tgatoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: tgatoppm.1
+%
+\phead{tgatoppm}{1}{26 August 1989}{}{}
+
+%.IX tgatoppm
+\shead{NAME}
+tgatoppm - convert TrueVision Targa file into a portable pixmap
+\shead{SYNOPSIS}
+{\bf tgatoppm}
+{\rm [}{\bf -debug}{\rm ]}
+{\rm [}{\it tgafile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a TrueVision Targa file as input.
+%.IX TrueVision
+%.IX Targa
+Produces a portable pixmap as output.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -debug}}
+\item[{{\bf -debug}}]
+Causes the header information to be dumped to stderr.
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.
+%.BUGS
+Should really be in PNM, not PPM.
+\shead{SEE ALSO}
+ppmtotga(1), ppm(5)
+\shead{AUTHOR}
+Partially based on tga2rast, version 1.0, by Ian J. MacPhedran.
+
+\copyright 1989 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: tgatoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:36 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: tifftopnm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: tifftopnm.1
+%
+\phead{tifftopnm}{1}{13 January 1991}{}{}
+
+%.IX tifftopnm
+\shead{NAME}
+tifftopnm - convert a TIFF file into a portable anymap
+\shead{SYNOPSIS}
+{\bf tifftopnm}
+{\rm [}{\bf -headerdump}{\rm ]}
+{\rm tifffile}
+\shead{DESCRIPTION}
+Reads a TIFF file as input.
+%.IX TIFF
+Produces a portable anymap as output.
+The type of the output file depends on the input file - if it's
+black \& white, a
+{\it pbm}
+file is written, else if it's grayscale a
+{\it pgm}
+file, else a
+{\it ppm}
+file.  The program tells you which type it is writing.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -headerdump}}
+\item[{{\bf -headerdump}}]
+Dump TIFF file information to stderr.  This information may be useful 
+in debugging TIFF file conversion problems.  
+\end{TPlist}
+
+\par
+All flags can be abbreviated to their shortest unique prefix.
+\shead{SEE ALSO}
+pnmtotiff(1), pnm(5)
+\shead{BUGS}
+This program is not self-contained.  To use it you must fetch the
+TIFF Software package listed in the OTHER.SYSTEMS file and configure
+PBMPLUS to use libtiff.  See PBMPLUS's Makefile for details on this
+configuration.
+\shead{AUTHOR}
+Derived by Jef Poskanzer from tif2ras.c, which is
+\copyright 1990 by Sun Microsystems, Inc.\hfil\break
+Author: Patrick J. Naughton (naughton@wind.sun.com).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted,
+% provided that the above copyright notice appear in all copies and that
+% both that copyright notice and this permission notice appear in
+% supporting documentation.
+% 
+% This file is provided AS IS with no warranties of any kind.  The author
+% shall have no liability with respect to the infringement of copyrights,
+% trade secrets or any patents by this file or any part thereof.  In no
+% event will the author be liable for any lost revenue or profits or
+% other special, indirect and consequential damages.
+%
+% end of input file: tifftopnm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:14 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: xbmtopbm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: xbmtopbm.1
+%
+\phead{xbmtopbm}{1}{31 August 1988}{}{}
+
+%.IX xbmtopbm
+\shead{NAME}
+xbmtopbm - convert an X11 or X10 bitmap into a portable bitmap
+\shead{SYNOPSIS}
+{\bf xbmtopbm}
+{\rm [}{\it bitmapfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads an X11 or X10 bitmap as input.
+Produces a portable bitmap as output.
+%.IX "X bitmap"
+%.IX "X window system"
+\shead{SEE ALSO}
+pbmtoxbm(1), pbmtox10bm(1), pbm(5)
+\shead{AUTHOR}
+\copyright 1988 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: xbmtopbm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:05 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ximtoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ximtoppm.1
+%
+\phead{ximtoppm}{1}{25 March 1990}{}{}
+
+%.IX ximtoppm
+\shead{NAME}
+ximtoppm - convert an Xim file into a portable pixmap
+\shead{SYNOPSIS}
+{\bf ximtoppm}
+{\rm [}{\it ximfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads an Xim file as input.
+%.IX Xim
+%.IX "X window system"
+Produces a portable pixmap as output.
+The Xim toolkit is included in the contrib tree of the X.V11R4 release.
+\shead{SEE ALSO}
+ppm(5)
+\shead{AUTHOR}
+\copyright 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ximtoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:05 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: xpmtoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: xpmtoppm.1
+%
+\phead{xpmtoppm}{1}{16 August 1990}{}{}
+
+\shead{NAME}
+xpmtoppm - convert an X11 pixmap into a portable pixmap
+\shead{SYNOPSIS}
+{\bf xpmtoppm}
+{\rm [}{\it xpmfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads an X11 pixmap (XPM version 1 or 3) as input.
+Produces a portable pixmap as output.
+\shead{KNOWN BUGS}
+The support to XPM version 3 is limited. Comments can only be single lines
+and there must be for every pixel a default colorname for a color type visual.
+\shead{SEE ALSO}
+ppmtoxpm(1), ppm(5)
+\nwl
+XPM Manual by Arnaud Le Hors (lehors@mirsa.inria.fr).
+\shead{AUTHOR}
+\copyright 1991 by Jef Poskanzer.
+Upgraded to support XPM version 3 by Arnaud Le Hors
+(lehors@mirsa.inria.fr) Tue Apr 9 1991.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: xpmtoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Mon Feb  7 08:46:26 1994
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: xvminitoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: xvminitoppm.1
+%
+\phead{xvminitoppm}{1}{14 December 1993}{}{}
+
+\shead{NAME}
+xvminitoppm - convert a XV ``thumbnail'' picture to PPM
+\shead{SYNOPSIS}
+{\bf xvminitoppm}
+{\rm [}{\it xvminipic}{\rm ]}
+\shead{DESCRIPTION}
+Reads a XV ``thumbnail'' picture (a miniature picture generated by
+the ``VisualSchnauzer'' browser) as input.
+Produces a portable pixmap as output.
+\shead{SEE ALSO}
+ppm(5), xv(1)
+\shead{AUTHOR}
+Copyright (C) 1993 by Ingo Wilken
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: xvminitoppm.1
+%--------------------------------------------------
+
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:28 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: xwdtopnm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: xwdtopnm.1
+%
+\phead{xwdtopnm}{1}{11 January 1991}{}{}
+
+%.IX xwdtopnm
+\shead{NAME}
+xwdtopnm - convert a X11 or X10 window dump file into a portable anymap
+\shead{SYNOPSIS}
+{\bf xwdtopnm}
+{\rm [}{\it xwdfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a X11 or X10 window dump file as input.
+%.IX XWD
+%.IX "X window system"
+Produces a portable anymap as output.
+The type of the output file depends on the input file - if it's
+black \& white, a
+{\it pbm}
+file is written, else if it's grayscale a
+{\it pgm}
+file, else a
+{\it ppm}
+file.  The program tells you which type it is writing.
+\par
+Using this program, you can convert anything on an X workstation's screen
+into an anymap.
+Just display whatever you're interested in, do an xwd, run it through
+xwdtopnm, and then use pnmcut to select the part you want.
+\shead{BUGS}
+I haven't tested this tool with very many configurations, so there are
+probably bugs.
+Please let me know if you find any.
+\shead{SEE ALSO}
+pnmtoxwd(1), pnm(5), xwd(1)
+\shead{AUTHOR}
+\copyright 1989, 1991 by Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: xwdtopnm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:08:14 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: ybmtopbm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: ybmtopbm.1
+%
+\phead{ybmtopbm}{1}{06 March 1990}{}{}
+
+%.IX ybmtopbm
+\shead{NAME}
+ybmtopbm - convert a Bennet Yee ``face'' file into a portable bitmap
+\shead{SYNOPSIS}
+{\bf ybmtopbm}
+{\rm [}{\it facefile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a file acceptable to the
+{\it face}
+and
+{\it xbm}
+programs by Bennet Yee (bsy+@cs.cmu.edu).
+%.IX face
+Writes a portable bitmap as output.
+\shead{SEE ALSO}
+pbmtoybm(1), pbm(5), face(1), face(5), xbm(1)
+\shead{AUTHOR}
+\copyright 1991 by Jamie Zawinski and Jef Poskanzer.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: ybmtopbm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:12 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: yuvsplittoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: yuvsplittoppm.1
+%
+\phead{yuvsplittoppm}{1}{26 August 93}{}{}
+
+%.IX yuvsplittoppm
+\shead{NAME}
+yuvplittoppm - convert a Y- an U- and a V-file into a portable pixmap.
+\shead{SYNOPSIS}
+{\bf yuvsplittoppm}
+{\it basename width height}
+[-ccir601]
+\shead{DESCRIPTION}
+Reads three files, containing the YUV components, as input.
+These files are
+{\it basename}
+Y,
+{\it basename}
+U
+and
+{\it basename}
+.V .
+Produces a portable pixmap on stdout.
+
+Since the YUV files are raw files, the dimensions
+{\it width}
+and
+{\it height}
+must be specified on the command line.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -ccir601}}
+\item[{{\bf -ccir601}}]
+Assumes that the YUV triplets are scaled into the smaller range of the
+CCIR 601 (MPEG) standard. Else, the JFIF (JPEG) standard is assumed.
+\end{TPlist}
+
+\shead{SEE ALSO}
+ppmtoyuvsplit(1), yuvtoppm(1), ppm(5)
+\shead{AUTHOR}
+Marcel Wijkstra (wijkstra@fwi.uva.nl), based on {\it ppmtoyuvsplit.}
+%
+% end of input file: yuvsplittoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:06 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: yuvtoppm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: yuvtoppm.1
+%
+\phead{yuvtoppm}{1}{25 March 91}{}{}
+
+%.IX yuvtoppm
+\shead{NAME}
+yuvtoppm - convert Abekas YUV bytes into a portable pixmap
+\shead{SYNOPSIS}
+{\bf yuvtoppm}
+{\it width height}
+{\rm [}{\it imagedata}{\rm ]}
+\shead{DESCRIPTION}
+Reads raw Abekas YUV bytes as input.
+%.IX Abekas
+Produces a portable pixmap as output.
+The input file is just YUV bytes.
+You have to specify the width and height on the command line,
+since the program obviously can't get them from the file.
+The maxval is assumed to be 255.
+\shead{SEE ALSO}
+ppmtoyuv(1), ppm(5)
+\shead{AUTHOR}
+Marc Boucher (marc@PostImage.COM),
+based on Example Conversion Program, A60/A64 Digital Video Interface
+Manual, page 69.
+\par
+\copyright 1991 by DHD PostImage Inc.
+\par
+\copyright 1987 by Abekas Video Systems Inc.
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: yuvtoppm.1
+%--------------------------------------------------
+ 
+% -*-LaTeX-*-
+% Converted automatically from troff to LaTeX
+% by tr2latex ($Revision: 2.2 $$Date: 1992/04/27 15:13:26 $ by C. Engel)
+% on Fri Oct  8 16:09:34 1993
+% tr2latex was written by Kamal Al-Yahya at Stanford University
+% (Kamal%Hanauma@SU-SCORE.ARPA)
+% and substantially enhanced by Christian Engel at RWTH Aachen
+% (krischan@informatik.rwth-aachen.de).
+%
+% troff input file: zeisstopnm.1
+
+\newpage
+%--------------------------------------------------
+% start of input file: zeisstopnm.1
+%
+\phead{zeisstopnm}{1}{15 June 1993}{}{}
+
+%.IX zeisstopnm
+\shead{NAME}
+zeisstopnm - convert a Zeiss confocal file into a portable anymap
+\shead{SYNOPSIS}
+{\bf zeisstopnm}
+{\rm [}{\it -pgm}
+$|$
+{\it -ppm}{\rm ]}
+{\rm [}{\it zeissfile}{\rm ]}
+\shead{DESCRIPTION}
+Reads a Zeiss confocal file as input.
+Produces a portable anymap as output.
+The type of the output file depends on the input file -
+if it's grayscale a
+{\it pgm}
+file, else a
+{\it ppm}
+file will be produced.
+The program tells you which type it is writing.
+\shead{OPTIONS}
+\begin{TPlist}{{\bf -pgm}}
+\item[{{\bf -pgm}}]
+Force the output to be a
+{\it pgm}
+file.
+\item[{{\bf -ppm}}]
+Force the output to be a
+{\it ppm}
+file.
+\end{TPlist}
+
+\shead{SEE ALSO}
+pnm(5)
+\shead{AUTHOR}
+\copyright 1993 by Oliver Trepte (oliver@fysik4.kth.se).
+% Permission to use, copy, modify, and distribute this software and its
+% documentation for any purpose and without fee is hereby granted, provided
+% that the above copyright notice appear in all copies and that both that
+% copyright notice and this permission notice appear in supporting
+% documentation.  This software is provided "as is" without express or
+% implied warranty.
+%
+% end of input file: zeisstopnm.1
+%--------------------------------------------------
+\end{document}
diff --git a/vms/PBMplus.hlp b/vms/PBMplus.hlp
new file mode 100644
index 00000000..52ba0f33
--- /dev/null
+++ b/vms/PBMplus.hlp
@@ -0,0 +1,6814 @@
+1 HELP
+        See PBMplus and OVERVIEW.
+
+1 OVERVIEW
+        Enhanced portable bitmap  toolkit.  The PBMPLUS toolkit allows
+    conversions between image files  of different format.  By means of
+    using common intermediate formats, only  2  * N conversion filters
+    are required to support N distinct  formats,  instead  of the N**2
+    which would be required to convert directly between any one format
+    and  any  other.    The  package also includes  simple  tools  for
+    manipulating portable bitmaps.
+
+        The package consists of four upwardly compatible sections:
+        
+    pbm     Supports monochrome bitmaps (1 bit per pixel).
+
+    pgm     Supports grayscale  images.    Reads  either  PBM  or  PGM
+            formats and writes PGM format.
+
+    ppm     Supports full-color images.  Reads either PBM, PGM, or PPM
+            formats, writes PPM format.
+
+    pnm     Supports content-independent manipulations on any  of  the
+            three formats listed above, as well  as  external  formats
+            having  multiple  types.  Reads either PBM,  PGM,  or  PPM
+            formats,  and  generally writes the same type as  it  read
+            (whenever a PNM tool makes an exception and ``promotes'' a
+            file to a higher format, it informs the user).
+
+        See PBMplus for more infomation.
+
+1 PBMplus
+        Enhanced portable bitmap  toolkit.  The PBMPLUS toolkit allows
+    conversions between image files  of different format.  By means of
+    using common intermediate formats, only  2  * N conversion filters
+    are required to support N distinct  formats,  instead  of the N**2
+    which would be required to convert directly between any one format
+    and  any  other.    The  package also includes  simple  tools  for
+    manipulating portable bitmaps.
+
+        The package consists of four upwardly compatible sections:
+        
+    pbm     Supports monochrome bitmaps (1 bit per pixel).
+
+    pgm     Supports grayscale  images.    Reads  either  PBM  or  PGM
+            formats and writes PGM format.
+
+    ppm     Supports full-color images.  Reads either PBM, PGM, or PPM
+            formats, writes PPM format.
+
+    pnm     Supports content-independent manipulations on any  of  the
+            three formats listed above, as well  as  external  formats
+            having  multiple  types.  Reads either PBM,  PGM,  or  PPM
+            formats,  and  generally writes the same type as  it  read
+            (whenever a PNM tool makes an exception and ``promotes'' a
+            file to a higher format, it informs the user).
+
+2 Description_of_Contents
+        A brief,  one-line  description  of  each  of  the  individual
+    programs in the PBMplus package.  They are sorted by general type.
+
+3 pbm
+    atktopbm    convert Andrew Toolkit raster object to portable bitmap
+    brushtopbm  convert Xerox doodle brushes to portable bitmap
+    cmuwmtopbm  convert CMU window manager format to portable bitmap
+    g3topbm     convert Group 3 FAX to portable bitmap
+    icontopbm   convert Sun icon to portable bitmap
+    gemtopbm    convert GEM .img format to portable bitmap
+    macptopbm   convert MacPaint to portable bitmap
+    mgrtopbm    convert MGR format to portable bitmap
+    pktopbm     convert packed (PK) format font into portable bitmap(s)
+    pbmmerge    merge wrapper routine
+    pbmto10x    convert portable bitmap to Gemini 10x printer graphics
+    pbmto4425   convert portable bitmap to AT&T 4425 terminal
+    pbmtoascii  convert portable bitmap to ASCII graphic form
+    pbmtoatk    convert portable bitmap to Andrew Toolkit raster object
+    pbmtobbnbg  convert portable bitmap to BBN BitGraph graphics
+    pbmtocmuwm  convert portable bitmap to CMU window manager format
+    pbmtoepson  convert portable bitmap to Epson printer graphics
+    pbmtog3     convert portable bitmap to Group 3 FAX
+    pbmtogem    convert portable bitmap into GEM .img file
+    pbmtogo     convert portable bitmap to GraphOn graphics
+    pbmtoicon   convert portable bitmap to Sun icon
+    pbmtolj     convert portable bitmap to HP LaserJet graphics
+    pbmtoln03   convert portable bitmap to DEC LN03+ Laserprinter
+    pbmtolps    convert portable bitmap to PostScript
+    pbmtomacp   convert portable bitmap to MacPaint
+    pbmtomgr    convert portable bitmap to MGR format
+    pbmtopgm    convert portable bitmap to portable graymap by ave. areas
+    pbmtopi3    convert portable bitmap to Atari Degas .pi3
+    pbmtopk     convert portable bitmap into a packed (PK) format font
+    pbmtoplot   convert portable bitmap into Unix plot(5) file
+    pbmtoptx    convert portable bitmap to Printronix graphics
+    pbmtoxbm    convert portable bitmap to X11 bitmap
+    pbmtox10bm  convert portable bitmap to X10 bitmap
+    pbmtoybm    convert portable bitmap into Bennet Yee "face" file
+    pbmtozinc   convert portable bitmap to Zinc Interface Library icon
+    pbmtoepsi   convert portable bitmap into an encapsulated PostScript
+    pi3topbm    convert Atari Degas .pi3 to portable bitmap
+    xbmtopbm    convert X10 or X11 bitmap to portable bitmap
+    ybmtopbm    convert Bennet Yee "face" file into portable bitmap
+
+    pbmclean    flip isolated pixels in portable bitmap
+    pbmlife     apply Conway's rules of Life to a portable bitmap
+    pbmmake     create a blank bitmap of a specified size and color
+    pbmmask     create a mask bitmap from a regular bitmap
+    pbmreduce   reduce a portable bitmap N times, using Floyd-Steinberg
+    pbmspcale   enlarge a portable bitmap with edge smoothing
+    pbmtext     render text into a bitmap
+    pbmupc      create a Universal Product Code bitmap
+
+3 pgm
+    asciitopgm  convert ASCII graphics into a portable graymap
+    fstopgm     convert Usenix FaceSaver format to portable graymap
+    hipstopgm   convert HIPS format to portable graymap
+    lispmtopgm  convert a Lisp Machine bitmap file into pgm format
+    bioradtopgm convert a Biorad confocal file into a portable graymap
+    psidtopgm   convert PostScript "image" data to portable graymap
+    rawtopgm    convert raw grayscale bytes to portable graymap
+    spottopgm   convert SPOT satellite images to Portable Greymap format
+    pgmtofs     convert portable graymap to Usenix FaceSaver format
+    pgmtolispm  convert a portable graymap into Lisp Machine format
+    pgmtopbm    convert portable graymap to portable bitmap
+
+    pgmbentley  Bentleyize a portable graymap
+    pgmcrater   create cratered terrain by fractal forgery
+    pgmedge     edge-detect a portable graymap
+    pgmenhance  edge-enhance a portable graymap
+    pgmhist     print a histogram of the values in a portable graymap
+    pgmkernel   generate a convolution kernel
+    pgmmerge    merge wrapper routine
+    pgmnoise    create a graymap made up of white noise
+    pgmnorm     normalize contrast in a portable graymap
+    pgmoil      turn a portable graymap into an oil painting
+    pgmramp     generate a grayscale ramp
+    pgmtexture  calculate textural features on a portable graymap
+
+3 ppm
+    bmptoppm    convert BMP file to portable pixmap
+    gouldtoppm  convert Gould scanner file to portable pixmap
+    ilbmtoppm   convert IFF ILBM to portable pixmap
+    imgtoppm    convert Img-whatnot to portable pixmap
+    mtvtoppm    convert MTV ray-tracer output to portable pixmap
+    pcxtoppm    convert PC Paintbrush format to portable pixmap
+    pgmtoppm    colorize a portable graymap into a portable pixmap
+    pi1toppm    convert Atari Degas .pi1 to portable pixmap
+    picttoppm   convert Macintosh PICT to portable pixmap
+    pjtoppm     convert HP PaintJet file to portable pixmap
+    ppmtoacad   convert portable pixmap to AutoCAD database or slide
+    ppmtobmp    convert portable pixmap to BMP file
+    ppmtogif    convert portable pixmap to GIF
+    ppmtoicr    convert portable pixmap to NCSA ICR graphics
+    ppmtoilbm   convert portable pixmap to IFF ILBM
+    ppmtomitsu  convert a portable pixmap to a Mitsubishi S340-10 file
+    ppmtomap    extract all colors from a portable pixmap
+    ppmtopcx    convert portable pixmap to PC Paintbrush format
+    ppmtopgm    convert portable pixmap to portable graymap
+    ppmtopi1    convert portable pixmap to Atari Degas .pi1
+    ppmtopict   convert portable pixmap to Macintosh PICT
+    ppmtopj     convert portable pixmap to HP PaintJet file
+    ppmtopjxl   convert portable pixmap to HP PaintJet XL PCL file
+    ppmtopuzz   convert portable pixmap to X11 "puzzle" file
+    ppmtorgb3   separate a portable pixmap to three portable graymaps
+    ppmtosixel  convert portable pixmap to DEC sixel format
+    ppmtotga    convert portable pixmap to TrueVision Targa file
+    ppmtouil    convert portable pixmap to Motif UIL icon file
+    ppmtoxpm    convert portable pixmap to XPM format
+    ppmtoyuv    convert portable pixmap to Abekas YUV format
+    qrttoppm    convert QRT ray-tracer output to portable pixmap
+    rawtoppm    convert raw RGB bytes to portable pixmap
+    rgb3toppm   combine three portable graymaps to one portable pixmap
+    sldtoppm    convert an AutoCAD slide file into a portable pixmap
+    spctoppm    convert Atari compressed Spectrum to portable pixmap
+    sputoppm    convert Atari uncompressed Spectrum to portable pixmap
+    tgatoppm    convert TrueVision Targa file to portable pixmap
+    ximtoppm    convert Xim to portable pixmap
+    xpmtoppm    convert XPM format to portable pixmap
+    xvminitoppm convert a XV "thumbnail" picture to PPM
+    yuvtoppm    convert Abekas YUV format to portable pixmap
+
+    ppm3d       convert 2 portable pixmap to a red/blue 3d glasses pixmap
+    ppmbrighten change images Saturation and Value from an HSV map
+    ppmchange   change pixels of one color to another in a portable pixmap
+    ppmdim      dim a portable pixmap down to total blackness
+    ppmdist     simple grayscale for machine generated, color images
+    ppmdither   ordered dither for color images
+    ppmflash    brighten a picture up to complete white-out
+    ppmforge    fractal forgeries of clouds, planets, and starry skies
+    ppmhist     print a histogram of a portable pixmap
+    ppmmake     create a pixmap of a specified size and color
+    ppmmix      blend together two portable pixmaps
+    ppmpat      create a pretty pixmap
+    ppmquant    quantize colors down to a specified number
+    ppmqvga     8 plane quantization
+    ppmrelief   run a Laplacian Relief filter on a portable pixmap
+    ppmshift    shift lines of a portable pixmap left or right by a
+                random amount
+    ppmspread   displace a portable pixmap's pixels by a random amount
+
+3 pnm
+    pnmtoddif   convert portable anymap to DDIF format
+    pnmtofits   convert a portable anymap into FITS format
+    pnmtops     convert portable anymap to PostScript
+    pnmtorast   convert portable anymap to Sun raster file
+    pnmtotiff   convert portable anymap to TIFF file
+    pnmtoxwd    convert portable anymap to X11 window dump
+    fitstopnm   convert a FITS file into a portable anymap
+    rasttopnm   convert Sun raster file to portable anymap
+    tifftopnm   convert TIFF file to portable anymap
+    xwdtopnm    convert X10 or X11 window dump to portable anymap
+    pnmtosir    convert a portable anymap into a Solitaire format
+    sirtopnm    convert a Solitaire file into a portable anymap
+    zeisstopnm  convert a Zeiss confocal file into a portable anymap
+
+    pnmalias    antialias a portable anyumap.
+    pnmarith    perform arithmetic on two portable anymaps
+    pnmcat      concatenate portable anymaps
+    pnmcomp     composite two portable anymap files together
+    pnmconvol   general MxN convolution on a portable anymap
+    pnmcrop     crop all like-colored borders off a portable anymap
+    pnmcut      select a rectangular region from a portable anymap
+    pnmdepth    change the maxval in a portable anymap
+    pnmenlarge  enlarge a portable anymap N times
+    pnmfile     describe a portable anymap
+    pnmflip     perform one or more flip operations on a portable anymap
+    pnmgamma    perform gamma correction on a portable anymap
+    pnmhistmap  draw a histogram for a PGM or PPM file
+    pnminvert   invert a portable anymap
+    pnmnlfilt   non-linear filters: smooth, alpha trim mean,
+                optimal estimation smoothing, edge enhancement
+    pnmnoraw    force a portable anymap into ASCII format
+    pnmpad      add borders to portable anymap
+    pnmpaste    paste a rectangle into a portable anymap
+    pnmrotate   rotate a portable anymap
+    pnmscale    scale a portable anymap
+    pnmshear    shear a portable anymap
+    pnmtile     replicate a portable anymap into a specified size
+
+2 See_Also
+        There are a number of related image-manipulation tools:
+
+    IM Raster Toolkit
+        A portable and efficient format toolkit.   The format supports
+    pixels  of  arbitrary  channels,  components, and bit  precisions,
+    while  allowing  compression  and machine byte-order independence.
+    Support for  image  manipulation,  digital  halftoning, and format
+    conversion.  Previously  distributed on tape c/o the University of
+    Waterloo (an ftp version is to appear later).  Author:  Alan Paeth
+    (awpaeth@watcgl.uwaterloo.ca).
+
+    Utah RLE Toolkit
+        Conversion  and  manipulation  package,  similar  to  PBMPLUS.
+    Available  via  ftp  as  cs.utah.edu:   pub/toolkit-2.0.tar.Z  and
+    ucsd.edu:  graphics/utah-raster-toolkit.tar.Z.
+
+    Fuzzy Pixmap Manipulation
+        Conversion  and  manipulation  package,  similar  to  PBMPLUS.
+    Version     1.0    available    via    ftp    as    nl.cs.cmu.edu:
+    /usr/mlm/ftp/fbm.tar.Z,  uunet.uu.net:        pub/fbm.tar.Z,   and
+    ucsd.edu:    graphics/fbm.tar.Z.       Author:    Michael  Mauldin
+    (mlm@nl.cs.cmu.edu).
+
+    Img Software Set
+        Reads and  writes  its own image format, displaying results on
+    an X11 screen,  and does some image manipulations.  Version 1.3 is
+    available  via  ftp  as    ftp.x.org:contrib/img_1.3.tar.Z,    and
+    venera.isi.edu:pub/img_1.3.tar.Z, along with a large collection of
+    color images.  Author:  Paul Raveling (raveling@venera.isi.edu).
+
+    Xim
+        Reads and writes its own image  format,  displays  on  an  X11
+    screen,  and  does some image manipulations.   Available  in  your
+    nearest  X11R4  source  tree  as it contrib/clients/xim.   A  more
+    recent version is available via ftp from video.mit.edu.   It  uses
+    X11R4 and the OSF/Motif toolkit to provide basic interactive image
+    manipulation  and  reads/writes GIF, xwd, xbm, tiff, rle, xim, and
+    other formats.  Author:  Philip R.  Thompson.
+
+    xloadimage
+        Reads in images in various formats and displays them on an X11
+    screen.  Available  via  ftp as ftp.x.org:contrib/xloadimage*, and
+    in  your nearest comp.sources.x  archive.    Author:    Jim  Frost
+    (madd@std.com).
+
+    TIFF Software
+        Nice portable library for reading and writing TIFF files, plus
+    a few tools for  manipulating  them  and  reading  other  formats.
+    Available    via    ftp    as   sgi.com:pub/graphics/*.tar.Z    or
+    uunet.uu.net:graphics/tiff.tar.Z. Author: Sam Leffler (sam@sgi.com).
+
+    ALV
+        A  Sun-specific  image  toolkit.    Version  2.0.6  posted  to
+    comp.sources.sun on 11 December 1989.  Also available via email to
+    alv-users-request@cs.bris.ac.uk.
+
+    popi
+        An  image  manipulation  language.    Version  2.1  posted  to
+    comp.sources.misc on 12 December 1989.
+
+    ImageMagick
+        X11  package  for  display  and  interactive  manipulation  of
+    images.  Uses its own format (MIFF), and includes some converters.
+    Available via ftp as ftp.x.org:contrib/ImageMagick.tar.Z.
+
+    Khoros
+        Huge  (~100  meg)  graphical  development environment based on
+    X11R4.    Components  include  a visual programming language, code
+    generators for  extending  the  visual  language  and  adding  new
+    application packages to  the system, an interactive user interface
+    editor, an interactive image display package, an extensive library
+    of  image  and  signal processing  routines,  and  2D/3D  plotting
+    packages.  Available via ftp as pprg.unm.edu:pub/khoros/*.
+
+    JPEG package
+        JPEG is a a standardized compression method for full-color and
+    gray-scale  images  of "real-world"  scenes;    this  experimental
+    package includes programs to compress  gif and ppm format files to
+    JPEG  format  ( cjpeg(1L)), and to  decompress  them  (djpeg(1L)).
+    Available by ftp as uunet.uu.net:graphics/jpeg/jpegsrc.v1.tar.Z.
+
+        libpbm(3L),  libpgm(3L),    libpnm(3L),  libppm(3L),  pbm(5L),
+    pgm(5L), pnm(5L), ppm(5L), rasterfile(1)
+
+2 Author
+        Distribution of 1 December 1991.   Copyright 1989, 1991 by Jef
+    Poskanzer.
+
+        Feedback and questions are welcome.  Please send them to:
+
+                             jef@well.sf.ca.us
+                              apple!well!jef
+
+        When  sending  bug  reports,  always  include  the output from
+    running  any  pbmplus  program  with  the -version flag, including
+    descriptions of  the  type  of system you are on, the compiler you
+    use, and whether you are using Makefiles or Imakefiles.
+
+        When  suggesting  new  formats  or  features,  please  include
+    whatever documentation you have,  and  a  uuencoded  sample.   The
+    response time will depend upon  my  schedule and the complexity of
+    the task;  if you need  it right away, or it is a complicated job,
+    you might consider paying me.
+
+        The  Usenet  newsgroup  alt.graphics.pixutils  is a forum  for
+    discussion  of  image  conversion  and editing packages.   Posting
+    queries  there  may be better than mailing them to  me,  since  it
+    allows other people to help provide answers.
+
+        Permission  to use, copy, modify, and distribute this software
+    and its  documentation  for  any purpose and without fee is hereby
+    granted, provided that  the  above  copyright notice appear in all
+    copies and that both  that  copyright  notice  and this permission
+    notice  appear  in supporting documentation.    This  software  is
+    provided "as is" without express or  implied  warranty.  Thus, you
+    may do what you want with this  software.    Build  it  into  your
+    package, steal code from it, whatever.  Just be sure to let people
+    know where it came from.
+
+1 asciitopgm
+        asciitopgm - convert ASCII graphics into a portable graymap
+
+2 Synopis
+        asciitopgm [-d divisor] height width [asciifile]
+
+2 Description
+        Reads  ASCII data as input.  Produces a portable graymap  with
+    pixel values which are an approximation of the "brightness" of the
+    ASCII  characters,  assuming black-on-white printing.    In  other
+    words, a capital M is very  dark,  a  period  is  ver light, and a
+    space is white.  Input lines which are fewer than width characters
+    are automatically padded with spaces.
+
+        The divisor argument is a floating-point number by  which  the
+    output pixels are divided;  the default value is 1.0.  This can be
+    used to adjust the brightness of the graymap:  for example, if the
+    image is too dim, reduce the divisor.
+
+        In keeping with  (I believe) Fortran line-printer conventions,
+    input lines beginning with  a  +  (plus)  character are assumed to
+    "overstrike" the previous line, allowing  a  larger  range of gray
+    values.
+
+        This tool contradicts the message in  the  pbmtoascii  manual:
+    "Note that there is no asciitopbm tool  -  this  transformation is
+    one-way."
+
+2 Bugs
+        The    table    of    ASCII-to-grey    values  is  subject  to
+    interpretation,  and,  of course, depends on the typeface intended
+    for the input.
+
+2 See_Also
+        pbmtoascii(1), pgm(5)
+
+2 Author
+        Wilson H. Bent. Jr. (whb@usc.edu)
+
+1 atktopbm
+     atktopbm  -  convert  Andrew  Toolkit  raster  object to portable
+     bitmap
+
+2 Synopsis
+     atktopbm [atkfile]
+
+2 Description
+     Reads an Andrew Toolkit raster object as input.  Produces  a
+     portable bitmap as output.
+
+2 See_Also
+     pbmtoatk, pbm
+
+2 Author
+     Copyright (C) 1991 by Bill Janssen.
+
+1 brushtopbm
+     brushtopbm - convert a doodle brush  file  into  a  portable
+     bitmap
+
+2 Synopsis
+     brushtopbm [brushfile]
+
+2 Description
+     Reads a Xerox doodle brush file as input.  Produces a  port-
+     able bitmap as output.
+
+     Note that there is currently no pbmtobrush tool.
+
+2 See_Also
+     pbm
+
+2 Author
+     Copyright (C) 1988 by Jef Poskanzer.
+
+1 cmuwmtopbm
+     cmuwmtopbm - convert a CMU  window  manager  bitmap  into  a
+     portable bitmap
+
+2 Synopsis
+     cmuwmtopbm [cmuwmfile]
+
+2 Description
+     Reads a CMU window manager  bitmap  as  input.   Produces  a
+     portable bitmap as output.
+
+2 See_Also
+     pbmtocmuwm, pbm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 g3topbm
+     g3topbm - convert a Group 3 fax file into a portable bitmap
+
+2 Synopsis
+     g3topbm [-kludge] [-reversebits] [-stretch] [g3file]
+
+2 Description
+     Reads a Group 3 fax file as input.  Produces a portable bit-
+     map as output.
+
+2 Options
+     -kludge
+          Tells g3topbm to ignore the  first  few  lines  of  the
+          file;  sometimes fax files have some junk at the begin-
+          ning.
+
+     -reversebits
+          Tells  g3topbm  to  interpret  bits   least-significant
+          first,  instead  of the default most-significant first.
+          Apparently some fax modems do it one way and others  do
+          it  the  other  way.   If you get a whole bunch of "bad
+          code word" messages, try using this flag.
+
+     -stretch
+          Tells g3topbm to stretch the image vertically by dupli-
+          cating each row.  This is for the low-quality transmis-
+          sion mode.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 References
+     The standard for Group 3 fax is defined in CCITT Recommenda-
+     tion T.4.
+
+2 Bugs
+     Probably.
+
+2 See_Also
+     pbmtog3, pbm
+
+2 Author
+     Copyright (C) 1989 by Paul Haeberli <paul@manray.sgi.com>.
+
+1 icontopbm
+     icontopbm - convert a Sun icon into a portable bitmap
+
+2 Synopsis
+     icontopbm [iconfile]
+
+2 Description
+     Reads a Sun icon as input.  Produces a  portable  bitmap  as
+     output.
+
+2 See_Also
+     pbmtoicon, pbm
+
+2 Author
+     Copyright (C) 1988 by Jef Poskanzer.
+
+1 gemtopbm
+     gemtopbm - convert a GEM .img file into a portable bitmap
+
+2 Synopsis
+     gemtopbm [-d] gemfile
+
+2 Description
+     Reads a GEM .img file as input.  Produces a portable  bitmap
+     as output.
+
+2 Options
+     -d   Produce output describing  the  contents  of  the  .img
+          file.
+
+2 Bugs
+     Does not support file containing more than one plane.  Can't
+     read from standard input.
+
+2 See_Also
+     pbmtogem, pbm
+
+2 Author
+     Copyright (C) 1988 Diomidis D. Spinellis (dds@cc.ic.ac.uk).
+
+1 macptopbm
+     macptopbm - convert a MacPaint file into a portable bitmap
+
+2 Synopsis
+     macptopbm [-extraskip N] [macpfile]
+
+2 Description
+     Reads a MacPaint file as input.  Produces a portable  bitmap
+     as output.
+
+2 Options
+     -extraskip
+          This flag is to get around a problem with some  methods
+          of  transferring  files  from the Mac world to the Unix
+          world.  Most of  these  methods  leave  the  Mac  files
+          alone, but a few of them add the "finderinfo" data onto
+          the front of the Unix file.  This means  an  extra  128
+          bytes  to skip over when reading the file.  The symptom
+          to watch for is  that  the  resulting  PBM  file  looks
+          shifted  to  one side.  If you get this, try -extraskip
+          128, and if that still doesn't look right  try  another
+          value.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     picttoppm, pbmtomacp, pbm
+
+2 Author
+     Copyright (C) 1988 by Jef Poskanzer.   The  MacPaint-reading
+     code   is   copyright   (c)  1987  by  Patrick  J.  Naughton
+     (naughton@wind.sun.com).
+
+1 mgrtopbm
+     mgrtopbm - convert a MGR bitmap into a portable bitmap
+
+2 Synopsis
+     mgrtopbm [mgrfile]
+
+2 Description
+     Reads a MGR bitmap as input.  Produces a portable bitmap  as
+     output.
+
+2 See_Also
+     pbmtomgr, pbm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pbmclean
+     pbmclean - flip isolated pixels in portable bitmap
+
+2 Synopsis
+     pbmclean [-connect] [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input. Outputs a portable  bitmap
+     with  every  pixel  which  has  less  than connect identical
+     neighbours inverted.  Pbmclean  can  be  used  to  clean  up
+     "snow" on bitmap images.
+
+2 See_Also
+     pbm
+
+2 Author
+     Copyright (C) 1990 by Angus Duggan Copyright (C) 1989 by Jef
+     Poskanzer.
+
+     Permission  to  use,  copy,  modify,  and  distribute   this
+     software  and  its documentation for any purpose and without
+     fee is hereby granted, provided  that  the  above  copyright
+     notice  appear  in  all  copies and that both that copyright
+     notice and this permission notice appear in supporting docu-
+     mentation.   This  software  is  provided  "as  is"  without
+     express or implied warranty.
+
+1 pbmlife
+     pbmlife - apply Conway's rules of Life to a portable bitmap
+
+2 Synopsis
+     pbmlife [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Applies the rules of Life
+     to  it for one generation, and produces a portable bitmap as
+     output.
+
+     A white pixel in the image is interpreted as a live beastie,
+     and a black pixel as an empty space.
+
+2 See_Also
+     pbm
+
+2 Author
+     Copyright (C) 1988, 1991 by Jef Poskanzer.
+
+1 pbmmake
+     pbmmake - create a blank bitmap of a specified size
+
+2 Synopsis
+     pbmmake [-white|-black|-gray ] width height
+
+2 Description
+     Produces a  portable  bitmap  of  the  specified  width  and
+     height.  The color defaults to white.
+
+2 Options
+     In addition to the usual -white  and  -black,  this  program
+     implements -gray.  This gives a simple 50% gray pattern with
+     1's and 0's alternating.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     pbm, ppmmake
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pbmmask
+     pbmmask - create a mask bitmap from a regular bitmap
+
+2 Synopsis
+     pbmmask [-expand] [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Creates  a  corresponding
+     mask bitmap and writes it out.
+
+     The color to be interpreted as  "background"  is  determined
+     automatically.  Regardless of which color is background, the
+     mask will be white where the background is and  black  where
+     the figure is.
+
+     This lets you do a masked paste like this, for objects  with
+     a black background:
+         pbmmask obj > objmask
+         pnmpaste < dest -and objmask <x> <y> | pnmpaste -or obj <x> <y>
+     For objects with a white background, you can  either  invert
+     them or add a step:
+         pbmmask obj > objmask
+         pnminvert objmask | pnmpaste -and obj 0 0 > blackback
+         pnmpaste < dest -and objmask <x> <y> | pnmpaste -or blackback <x> <y>
+     Note that this three-step version  works  for  objects  with
+     black  backgrounds  too,  if you don't care about the wasted
+     time.
+
+     You can also use masks with graymaps and pixmaps, using  the
+     pnmarith tool.  For instance:
+         ppmtopgm obj.ppm | pgmtopbm -threshold | pbmmask > objmask.pbm
+         pnmarith -multiply dest.ppm objmask.pbm > t1.ppm
+         pnminvert objmask.pbm | pnmarith -multiply obj.ppm - > t2.ppm
+         pnmarith -add t1.ppm t2.ppm
+     An interesting variation on this is to pipe the mask through
+     the  pnmsmooth script before using it.  This makes the boun-
+     dary between the two images less sharp.
+
+     -expand
+          Expands the mask by one pixel out from the image.  This
+          is useful if you want a little white border around your
+          image.  (A better solution might be to turn the pbmlife
+          tool into a general cellular automaton tool...)
+
+2 See_Also
+     pnmpaste, pnminvert, pbm, pnmarith, pnmsmooth
+
+2 Author
+     Copyright (C) 1988 by Jef Poskanzer.
+
+1 pbmpscale
+     pbmpscale - enlarge a portable bitmap with edge smoothing
+
+2 Synopsis
+     pbmpscale N [ pbmfile ]
+
+2 Description
+     Reads a portable bitmap as input,  and  outputs  a  portable
+     bitmap enlarged N times. Enlargement is done by pixel repli-
+     cation, with some additional smoothing of corners and edges.
+
+2 See_Also
+     pnmenlarge, ppmscale, pbm
+
+2 Author
+     Copyright (C) 1990 by Angus Duggan Copyright (C) 1989 by Jef
+     Poskanzer.
+
+     Permission  to  use,  copy,  modify,  and  distribute   this
+     software  and  its documentation for any purpose and without
+     fee is hereby granted, provided  that  the  above  copyright
+     notice  appear  in  all  copies and that both that copyright
+     notice and this permission notice appear in supporting docu-
+     mentation.   This  software  is  provided  "as  is"  without
+     express or implied warranty.
+
+2 Notes
+     pbmpscale works best for  enlargements  of  2.  Enlargements
+     greater  than  2 should be done by as many enlargements of 2
+     as possible, followed by an  enlargement  by  the  remaining
+     factor.
+
+1 pbmreduce
+     pbmreduce - read a portable bitmap and reduce it N times
+
+2 Synopsis
+     pbmreduce [-floyd|-fs|-threshold ] [-value val] N [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Reduces it by a factor of
+     N, and produces a portable bitmap as output.
+
+     pbmreduce duplicates a lot of the functionality of pgmtopbm;
+     you  could  do  something  like  pnmscale  |  pgmtopbm,  but
+     pbmreduce is a lot faster.
+
+     pbmreduce can be used to "re-halftone" an image.  Let's  say
+     you  have  a  scanner  that  only  produces black&white, not
+     grayscale, and it does a terrible job  of  halftoning  (most
+     b&w  scanners  fit  this  description).   One way to fix the
+     halftoning is to scan at the  highest  possible  resolution,
+     say  300  dpi,  and  then  reduce by a factor of three or so
+     using pbmreduce.  You can even correct the brightness of  an
+     image, by using the -value flag.
+
+2 Options
+     By default, the halftoning after the reduction is  done  via
+     boustrophedonic  Floyd-Steinberg  error  diffusion; however,
+     the -threshold flag can be used to specify simple threshold-
+     ing.  This gives better results when reducing line drawings.
+
+     The -value flag alters the thresholding value for all quant-
+     izations.   It  should  be  a  real  number between 0 and 1.
+     Above 0.5 means darker images; below 0.5 means lighter.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     pnmenlarge, pnmscale, pgmtopbm, pbm
+
+2 Author
+     Copyright (C) 1988 by Jef Poskanzer.
+
+1 pbmtext
+     pbmtext - render text into a bitmap
+
+2 Synopsis
+     pbmtext [-font fontfile] [text]
+
+2 Description
+     Takes the specified text, either a single line from the com-
+     mand line or multiple lines from standard input, and renders
+     it into a bitmap.
+
+2 Options
+     By default, pbmtext uses a  built-in  font.   You  can  also
+     specify  your own font with the -font flag.  The fontfile is
+     a pbm file, created in a very specific way.  In your  window
+     system  of choice, display the following text in the desired
+     (fixed-width) font:
+
+         M ",/^_[`jpqy| M
+
+         /  !"#$%&'()*+ /
+         < ,-./01234567 <
+         > 89:;<=>?@ABC >
+         @ DEFGHIJKLMNO @
+         _ PQRSTUVWXYZ[ _
+         { \]^_`abcdefg {
+         } hijklmnopqrs }
+         ~ tuvwxyz{|}~  ~
+
+         M ",/^_[`jpqy| M
+
+     Do a screen grab or window dump  of  that  text,  using  for
+     instance  xwd,  xgrabsc,  or screendump.  Convert the result
+     into a pbm file.  If necessary, use pnmcut to remove  every-
+     thing  except  the text.  Finally, run it through pnmcrop to
+     make sure the edges are right up against the text.   pbmtext
+     can figure out the sizes and spacings from that.
+
+2 See_Also
+     pbm, pnmcut, pnmcrop
+
+2 Author
+     Copyright (C) 1991 by Jef Poskanzer.
+
+1 pbmto4425
+     pbmto4425 - Display PBM images on an AT&T 4425 terminal
+
+2 Synopsis
+     pbmto4425 [pbmfile]
+
+2 Description
+     Pbmto4425 displays PBM format images on an AT&T  4425  ASCII
+     terminal  using  that  terminal's  mosaic graphics character
+     set.  The program should also  work  with  other  VT100-like
+     terminals with mosaic graphics character sets such as the C.
+     Itoh CIT-101, but it has not yet been  tested  on  terminals
+     other than the 4425.
+
+     Pbmto4425 puts the terminal into 132 column mode to  achieve
+     the  maximum  resolution  of the terminal.  In this mode the
+     terminal has a resolution of 264 columns by  69  rows.   The
+     pixels  have  an  aspect  ratio of 1:2.6, therefore an image
+     should be processed before being displayed in a manner  such
+     as this:
+
+          % pnmscale -xscale 2.6 pnmfile \
+               | pnmscale -xysize 264 69 \
+               | ppmtopgm \
+               | pgmtopbm \
+               | pbmto4425
+
+2 Author
+     Copyright (C) 1993 by Robert Perlberg
+
+1 pbmto10x
+     pbmto10x - convert a portable bitmap into Gemini 10X printer
+     graphics
+
+2 Synopsis
+     pbmto10x [-h] [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Produces a file of Gemini
+     10X printer graphics as output.  The 10x's printer codes are
+     alleged to be similar to the Epson codes.
+
+     Note that there is no 10xtopbm tool - this transformation is
+     one way.
+
+2 Options
+     The resolution is normally 60H by 72V.  If the  -h  flag  is
+     specified, resolution is 120H by 144V.  You may find it use-
+     ful to rotate landscape images before printing.
+
+2 See_Also
+     pbm
+
+2 Author
+     Copyright (C) 1990 by Ken Yap
+
+1 pbmtoascii
+     pbmtoascii - convert a portable bitmap into ASCII graphics
+
+2 Synopsis
+     pbmtoascii [-1x2|-2x4] [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Produces a somewhat crude
+     ASCII graphic as output.
+
+     Note that there is no asciitopbm tool - this  transformation
+     is one-way.
+
+2 Options
+     The -1x2 and -2x4 flags give you two alternate ways for  the
+     bits  to  get  mapped to characters.  With 1x2, the default,
+     each character represents a group of 1 bit across by 2  bits
+     down.  With -2x4, each character represents 2 bits across by
+     4 bits down.  With the 1x2 mode you can see  the  individual
+     bits,  so it's useful for previewing small bitmaps on a non-
+     graphics terminal.  The 2x4 mode  lets  you  display  larger
+     bitmaps  on  a  standard  80-column display, but it obscures
+     bit-level details.  2x4 mode is  also  good  for  displaying
+     graymaps  -  "pnmscale  -width  158  |  pgmnorm  |  pgmtopbm
+     -thresh" should give good results.
+
+2 See_Also
+     pbm
+
+2 Author
+     Copyright (C) 1988, 1992 by Jef Poskanzer.
+
+1 pbmtoatk
+     pbmtoatk - convert portable bitmap to Andrew Toolkit  raster
+     object
+
+2 Synopsis
+     pbmtoatk [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Produces a Andrew Toolkit
+     raster object as output.
+
+2 See_Also
+     atktopbm, pbm
+
+2 Author
+     Copyright (C) 1991 by Bill Janssen.
+
+1 pbmtobg
+     pbmtobg - convert a portable bitmap into BitGraph graphics
+
+2 Synopsis
+     pbmtobg [rasterop] [x y] < pbmfile
+
+2 Description
+     Reads a portable bitmap as  input.   Produces  BBN  BitGraph
+     terminal Display Pixel Data (DPD) sequence as output.
+
+     The rasterop can be specified on the command line.  If  this
+     is  omitted,  3 (replace) will be used.  A position in (x,y)
+     coordinates can also be specified.  If both are  given,  the
+     rasterop  comes  first.  The portable bitmap is always taken
+     from the standard input.
+
+     Note that there is no bgtopbm tool.
+
+2 See_Also
+     pbm
+
+2 Author
+     Copyright 1989 by Mike Parker.
+
+1 pbmtocmuwm
+     pbmtocmuwm - convert a portable bitmap  into  a  CMU  window
+     manager bitmap
+
+2 Synopsis
+     pbmtocmuwm [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.   Produces  a  CMU  window
+     manager bitmap as output.
+
+2 See_Also
+     cmuwmtopbm, pbm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pbmtoepsi
+     pbmtoepsi - convert a portable bitmap into  an  encapsulated
+     PostScript style preview bitmap
+
+2 Synopsis
+     pbmtoepsi [-bbonly] [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Produce  an  encapsulated
+     Postscript style bitmap as output. The output is not a stand
+     alone postscript file, it is only a  preview  bitmap,  which
+     can  be  included  in an encapsulated PostScript file.  Note
+     that there is no epsitopbm tool - this transformation is one
+     way.
+
+     This utility is a part of the pstoepsi tool by Doug  Crabill
+     (dgc@cs.purdue.edu).
+
+2 Options
+     -bbonly
+          Only create a boundary box,  don't  fill  it  with  the
+          image.
+
+2 See_Also
+     pbm, pnmtops, psidtopgm
+
+2 Author
+     Copyright (C) 1988 Jef Poskanzer, modified by  Doug  Crabill
+     1992
+
+1 pbmtoepson
+     pbmtoepson - convert a portable bitmap  into  Epson  printer
+     graphics
+
+2 Synopsis
+     pbmtoepson [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Produces a file of  Epson
+     printer graphics as output.
+
+     Note that there is no epsontopbm tool - this  transformation
+     is one way.
+
+2 See_Also
+     pbm
+
+2 Author
+     Copyright      (C)      1991      by       John       Tiller
+     (tiller@galois.msfc.nasa.gov) and Jef Poskanzer.
+
+1 pbmtog3
+     pbmtog3 - convert a portable bitmap into a Group 3 fax file
+
+2 Synopsis
+     pbmtog3 [pbmfile]
+
+2 Description
+     Reads a portable bitmap as output.  Produces a Group  3  fax
+     file as input.
+
+REFERENCES
+     The standard for Group 3 fax is defined in CCITT Recommenda-
+     tion T.4.
+
+2 Bugs
+     Probably.
+
+2 See_Also
+     g3topbm, pbm
+
+2 Author
+     Copyright (C) 1989 by Paul Haeberli <paul@manray.sgi.com>.
+
+1 pbmtogem
+     pbmtogem - convert a portable bitmap into a GEM .img file
+
+2 Synopsis
+     pbmtogem [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Produces a GEM .img  file
+     as output.
+
+2 Bugs
+     It does not support compression of the data.
+
+2 See_Also
+     gemtopbm, pbm
+
+2 Author
+     Copyright (C) 1988 by David Beckemeyer (bdt!david)  and  Jef
+     Poskanzer.
+
+1 pbmtogo
+     pbmtogo - convert a portable bitmap into compressed  GraphOn
+     graphics
+
+2 Synopsis
+     pbmtogo [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.   Produces  2D  compressed
+     GraphOn  graphics as output.  Be sure to set up your GraphOn
+     with the following modes: 8  bits  /  no  parity;  obeys  no
+     XON/XOFF;  NULs  are  accepted.   These  are all on the Comm
+     menu.  Also, remember to turn off tty post processing.  Note
+     that there is no gotopbm tool.
+
+2 See_Also
+     pbm
+
+2 Author
+     Copyright (C) 1988, 1989 by Jef Poskanzer, Michael Haberler,
+     and Bo Thide'.
+
+1 pbmtoicon
+     pbmtoicon - convert a portable bitmap into a Sun icon
+
+2 Synopsis
+     pbmtoicon [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Produces a  Sun  icon  as
+     output.
+
+2 See_Also
+     icontopbm, pbm
+
+2 Author
+     Copyright (C) 1988 by Jef Poskanzer.
+
+1 pbmtolj
+     pbmtolj - convert a portable bitmap into HP LaserJet format
+
+2 Synopsis
+     pbmtolj [-resolution N] [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Produces HP LaserJet data
+     as output.
+
+     Note that there is no ljtopbm tool.
+
+2 Options
+     -resolution
+          Specifies the resolution of the output device, in  dpi.
+          Typical  values  are 75, 100, 150, 300.  The default is
+          75.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     pbm
+
+2 Author
+     Copyright (C) 1988 by Jef Poskanzer and Michael Haberler.
+
+1 pbmtoln03
+     pbmtoln03 - convert protable bitmap to DEC LN03+ Sixel  out-
+     put
+ 
+2 Synopsis
+     pbmtoln03 [-rltbf] pbmfile
+ 
+2 Description
+     Reads a portable bitmap as  input.   Produces  a  DEC  LN03+
+     Sixel output file.
+ 
+2 Options
+     -l nn
+          Use "nn" as value for left margin (default 0).
+ 
+     -r nn
+          Use "nn" as value for right margin (default 2400).
+ 
+     -t nn
+          Use "nn" as value for top margin (default 0).
+ 
+     -b nn
+          Use "nn" as value for bottom margin (default 3400).
+ 
+     -f nn
+          Use "nn" as value for form length (default 3400).
+ 
+2 See_Also
+     pbm
+ 
+2 Author
+     Tim Cook, 26 Feb 1992
+ 
+1 pbmtolps
+     pbmtolps - convert portable bitmap to PostScript
+
+2 Synopsis
+     pbmtolps [ -dpi n ] [ pbmfile ]
+
+2 Description
+     Reads a portable bitmap as input,  and  outputs  PostScript.
+     The output Postscript uses lines instead of the image opera-
+     tor to generate a (device dependent) picture which  will  be
+     imaged much faster.
+
+     The Postscript path length is constrained to  be  less  that
+     1000  points  so  that  no  limits  are overrun on the Apple
+     Laserwriter and (presumably) no other printers.
+
+2 See_Also
+     pgmtops, ppmtops, pbm
+
+2 Author
+     George Phillips <phillips@cs.ubc.ca>
+
+1 pbmtomacp
+     pbmtomacp - convert a portable bitmap into a MacPaint file
+
+2 Synopsis
+     pbmtomacp  [-l  left]  [-r  right]  [-b  bottom]  [-t   top]
+     [pbmfile]
+
+2 Description
+     Reads a portable bitmap  as  input.   If  no  input-file  is
+     given,  standard input is assumed.  Produces a MacPaint file
+     as output.
+
+     The generated file is only the data fork of a picture.   You
+     will  need  a program such as mcvert to generate a Macbinary
+     or a BinHex file that contains the necessary information  to
+     identify the file as a PNTG file to MacOS.
+
+2 Options
+     Left, right, bottom & top let you define a square  into  the
+     pbm  file,  that  must  be  converted.  Default is the whole
+     file.  If the file is too large  for  a  MacPaint-file,  the
+     bitmap is cut to fit from ( left, top ).
+
+2 Bugs
+     The source code contains comments in a language  other  than
+     English.
+
+2 See_Also
+     ppmtopict, macptopbm, pbm, mcvert
+
+2 Author
+     Copyright   (C)   1988   by    Douwe    van    der    Schaaf
+     (...!mcvax!uvapsy!vdschaaf).
+
+1 pbmtomgr
+     pbmtomgr - convert a portable bitmap into a MGR bitmap
+
+2 Synopsis
+     pbmtomgr [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Produces a MGR bitmap  as
+     output.
+
+2 See_Also
+     mgrtopbm, pbm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pbmtopi3
+     pbmtopi3 - convert a portable bitmap  into  an  Atari  Degas
+     .pi3 file
+
+2 Synopsis
+     pbmtopi3 [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Produces an  Atari  Degas
+     .pi3 file as output.
+
+2 See_Also
+     pi3topbm, pbm, ppmtopi1, pi1toppm
+
+2 Author
+     Copyright (C) 1988 by David Beckemeyer (bdt!david)  and  Jef
+     Poskanzer.
+
+1 pbmtoplot
+     pbmtoplot - convert a portable bitmap into  a  Unix  plot
+     file
+
+2 Synopsis
+     pbmtoplot [pbmfile]
+
+2 Description
+     Reads a portable bitmap as  input.   Produces  a  Unix  plot
+     file.
+
+     Note that there is no plottopbm tool -  this  transformation
+     is one-way.
+
+2 See_Also
+     pbm, plot
+
+2 Author
+     Copyright (C) 1990 by Arthur David Olson.
+
+1 pbmtoptx
+     pbmtoptx - convert a portable bitmap into Printronix printer
+     graphics
+
+2 Synopsis
+     pbmtoptx [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Produces a file of  Prin-
+     tronix printer graphics as output.
+
+     Note that there is no ptxtopbm tool - this transformation is
+     one way.
+
+2 See_Also
+     pbm
+
+2 Author
+     Copyright (C) 1988 by Jef Poskanzer.
+
+1 pbmtox10bm
+     pbmtox10bm - convert a portable bitmap into an X10 bitmap
+
+2 Synopsis
+     pbmtox10bm [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Produces an X10 bitmap as
+     output.  This older format is maintained for compatibility.
+
+     Note that there is no x10bmtopbm tool, because xbmtopbm  can
+     read both X11 and X10 bitmaps.
+
+2 See_Also
+     pbmtoxbm, xbmtopbm, pbm
+
+2 Author
+     Copyright (C) 1988 by Jef Poskanzer.
+
+1 pbmtoxbm
+     pbmtoxbm - convert a portable bitmap into an X11 bitmap
+
+2 Synopsis
+     pbmtoxbm [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Produces an X11 bitmap as
+     output.
+
+2 See_Also
+     pbmtox10bm, xbmtopbm, pbm
+
+2 Author
+     Copyright (C) 1988 by Jef Poskanzer.
+
+1 pbmtoybm
+     pgmtoybm - convert a  portable  bitmap  into  a  Bennet  Yee
+     "face" file
+
+2 Synopsis
+     pbmtoybm [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Produces as output a file
+     acceptable  to  the  face  and  xbm  programs  by Bennet Yee
+     (bsy+@cs.cmu.edu).
+
+2 See_Also
+     ybmtopbm, pbm, face, face, xbm
+
+2 Author
+     Copyright (C) 1991 by Jamie Zawinski and Jef Poskanzer.
+
+1 pbmtozinc
+     pbmtozinc - convert a portable bitmap into a Zinc bitmap
+
+2 Synopsis
+     pbmtozinc [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input.  Produces a bitmap in  the
+     format  used by the Zinc Interface Library (ZIL) Version 1.0
+     as output.
+
+2 See_Also
+     pbm
+
+2 Author
+     Copyright   (C)   1988    by    James    Darrell    McCauley
+     (jdm5548@diamond.tamu.edu) and Jef Poskanzer.
+
+1 pbmupc
+     pbmupc - create a Universal Product Code bitmap
+
+2 Synopsis
+     pbmupc [-s1|-s2] type manufac product
+
+2 Description
+     Generates a Universal Product Code symbol.  The three  argu-
+     ments  are:  a one digit product type, a five digit manufac-
+     turer code, and a five digit product code.  For example,  "0
+     72890 00011" is the code for Heineken.
+
+     As presently configured, pbmupc produces a bitmap  230  bits
+     wide and 175 bits high.  The size can be altered by changing
+     the defines at the beginning of the program, or  by  running
+     the output through pnmenlarge or pnmscale.
+
+2 Options
+     The -s1 and -s2 flags select the style of UPC  to  generate.
+     The default, -s1, looks more or less like this:
+      ||||||||||||||||
+      ||||||||||||||||
+      ||||||||||||||||
+      ||||||||||||||||
+     0||12345||67890||5
+     The other style, -s2, puts the product type digit higher up,
+     and doesn't display the checksum digit:
+      ||||||||||||||||
+      ||||||||||||||||
+     0||||||||||||||||
+      ||||||||||||||||
+      ||12345||67890||
+
+2 See_Also
+     pbm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pi3topbm
+     pi3topbm - convert an Atari Degas .pi3 file into a  portable
+     bitmap
+
+2 Synopsis
+     pi3topbm [pi3file]
+
+2 Description
+     Reads an Atari Degas .pi3 file as input.  Produces  a  port-
+     able bitmap as output.
+
+2 See_Also
+     pbmtopi3, pbm, pi1toppm, ppmtopi1
+
+2 Author
+     Copyright (C)  1988  by  David  Beckemeyer  (bdt!david)  and
+     Diomidis D. Spinellis.
+
+1 pktopbm
+     pktopbm - convert packed  (PK)  format  font  into  portable
+     bitmap(s)
+
+2 Synopsis
+     pktopbm pkfile[.pk] [-c num] pbmfile ...
+
+2 Description
+     Reads a packed (PK) font file as input, and  produces  port-
+     able  bitmaps as output. If the filename "-" is used for any
+     of the filenames, the standard  input  stream  (or  standard
+     output where appropriate) will be used.
+
+2 Options
+     -c num
+          Sets the character number of the next bitmap written to
+          num.
+
+2 See_Also
+     pbmtopk, pbm
+
+2 Author
+     Adapted  from  Tom  Rokicki's   pxtopk   by   Angus   Duggan
+     <ajcd@uk.ac.ed.lfcs>.
+
+1 xbmtopbm
+     xbmtopbm - convert an X11 or X10 bitmap into a portable bit-
+     map
+
+2 Synopsis
+     xbmtopbm [bitmapfile]
+
+2 Description
+     Reads an X11 or X10 bitmap as input.   Produces  a  portable
+     bitmap as output.
+
+2 See_Also
+     pbmtoxbm, pbmtox10bm, pbm
+
+2 Author
+     Copyright (C) 1988 by Jef Poskanzer.
+
+1 ybmtopbm
+     ybmtopbm - convert a Bennet Yee "face" file into a  portable
+     bitmap
+
+2 Synopsis
+     ybmtopbm [facefile]
+
+2 Description
+     Reads a file acceptable to the face and xbm programs by Ben-
+     net Yee (bsy+@cs.cmu.edu).  Writes a portable bitmap as out-
+     put.
+
+2 See_Also
+     pbmtoybm, pbm, face, face, xbm
+
+2 Author
+     Copyright (C) 1991 by Jamie Zawinski and Jef Poskanzer.
+
+1 pbmtopk
+     pbmtopk - convert a portable bitmap into a packed (PK)  for-
+     mat font
+
+2 Synopsis
+     pbmtopk pkfile[.pk] tfmfile[.tfm] resolution [-s designsize]
+     [-p num param...] [-C codingscheme] [-F family] [-f optfile]
+     [-c num] [-W width] [-H height] [-D  depth]  [-I  ital]  [-h
+     horiz] [-v vert] [-x xoff] [-y yoff] [pbmfile]...
+
+2 Description
+     Reads portable bitmaps as input, and produces a packed  (PK)
+     font  file  and  a TFM (TeX font metric) file as output. The
+     resolution parameter indicates the resolution of  the  font,
+     in dots per inch. If the filename "-" is used for any of the
+     filenames, the standard input  stream  (or  standard  output
+     where appropriate) will be used.
+
+2 Options
+     -s designsize
+          Sets the design size  of  the  font,  in  TeX's  points
+          (72.27pt  to  the  inch). The default design size is 1.
+          The TFM parameters are given as multiples of the design
+          size.
+
+     -p num param...
+          Sets the first num font parameters for  the  font.  The
+          first  seven  parameters are the slant, interword spac-
+          ing, interword space  stretchability,  interword  space
+          shrinkability,  x-height, quad width, and post-sentence
+          extra space of the font. Math and symbol fonts may have
+          more  parameters;  see The TeXbook for a list of these.
+          Reasonable default values  are  chosen  for  parameters
+          which are not specified.
+
+     -C codingscheme
+          Sets the coding scheme comment in the TFM file.
+
+     -F family
+          Sets the font family comment in the TFM file.
+
+     -f optfile
+          Reads the file optfile, which should contain a lines of
+          the form:
+
+             filename xoff yoff horiz vert width height depth ital
+
+          The pbm files specified by the filename parameters  are
+          inserted  consecutively  in the font with the specified
+          attributes. If any of the attributes  are  omitted,  or
+          replaced  with  "*", a default value will be calculated
+          from the size of the bitmap. The settings  of  the  -W,
+          -H,  -D, -I, -h, -v, -x, and -y options do not affected
+          characters created in this way.  The  character  number
+          can  be  changed by including a line starting with "=",
+          followed by the new number.  Lines beginning  with  "%"
+          or "#" are ignored.
+
+     -c num
+          Sets the character number of the  next  bitmap  encoun-
+          tered to num.
+
+     -W width
+          Sets the TFM width of the next character to  width  (in
+          design size multiples).
+
+     -H height
+          Sets the TFM height of the next character to height (in
+          design size multiples).
+
+     -D depth
+          Sets the TFM depth of the next character to  depth  (in
+          design size multiples).
+
+     -I ital
+          Sets the italic correction of  the  next  character  to
+          ital (in design size multiples).
+
+     -h horiz
+          Sets the horizontal escapement of the next character to
+          horiz (in pixels).
+
+     -v vert
+          Sets the vertical escapement of the next  character  to
+          vert (in pixels).
+
+     -x xoff
+          Sets the horizontal offset of  the  next  character  to
+          xoff (in pixels).
+
+     -y yoff
+          Sets the vertical offset of the next character to  yoff
+          (in pixels, from the top row).
+
+2 See_Also
+     pktopbm, pbm
+
+2 Author
+     Adapted  from  Tom  Rokicki's   pxtopk   by   Angus   Duggan
+     <ajcd@uk.ac.ed.lfcs>.
+
+1 libpbm              C LIBRARY FUNCTIONS              libpbm
+     libpbm - functions to support portable bitmap programs
+
+2 Synopsis
+     #include <pbm.h>
+     cc ... libpbm.a
+
+
+2 Description - PACKAGE-WIDE ROUTINES
+  KEYWORD MATCHING
+     int pm_keymatch( char* str, char* keyword, int minchars )
+
+     Does a case-insensitive match of str against  keyword.   str
+     can be a leading sunstring of keyword, but at least minchars
+     must be present.
+
+  LOG BASE TWO
+     int pm_maxvaltobits( int maxval )
+     int pm_bitstomaxval( int bits )
+
+     Convert between a maxval and  the  minimum  number  of  bits
+     required to hold it.
+
+  MESSAGES AND ERRORS
+     void pm_message( char* fmt, ... )
+
+     printf() style routine to write an informational message.
+
+     void pm_error( char* fmt, ... )
+
+     printf() style routine to write an error message and abort.
+
+     void pm_usage( char* usage )
+
+     Write a usage message.   The  string  should  indicate  what
+     arguments are to be provided to the program.
+
+  GENERIC FILE MANAGEMENT
+     FILE* pm_openr( char* name )
+
+     Open the given file  for  reading,  with  appropriate  error
+     checking.   A  filename  of  "-"  is  taken as equivalent to
+     stdin.
+
+     FILE* pm_openw( char* name )
+
+     Open the given file  for  writing,  with  appropriate  error
+     checking.
+
+     void pm_close( FILE* fp )
+
+     Close the file descriptor, with appropriate error checking.
+
+  ENDIAN I/O
+     int pm_readbigshort( FILE* in, short* sP )
+     int pm_writebigshort( FILE* out, short s )
+     int pm_readbiglong( FILE* in, long* lP )
+     int pm_writebiglong( FILE* out, long l )
+     int pm_readlittleshort( FILE* in, short* sP )
+     int pm_writelittleshort( FILE* out, short s )
+     int pm_readlittlelong( FILE* in, long* lP )
+     int pm_writelittlelong( FILE* out, long l )
+
+     Routines to read and write short and  long  ints  in  either
+     big- or little-endian byte order.
+
+2 Description - PBM-SPECIFIC ROUTINES
+  TYPES AND CONSTANTS
+     typedef ... bit;
+     #define PBM_WHITE ...
+     #define PBM_BLACK ...
+
+     each bit should contain only  the  values  of  PBM_WHITE  or
+     PBM_BLACK.
+
+     #define PBM_FORMAT ...
+     #define RPBM_FORMAT ...
+     #define PBM_TYPE PBM_FORMAT
+     #define PBM_FORMAT_TYPE(f) ...
+
+     For distinguishing different file formats and types.
+
+  INITIALIZATION
+     void pbm_init( int* argcP, char* argv[] )
+
+     All PBM programs must call this routine.
+
+  MEMORY MANAGEMENT
+     bit** pbm_allocarray( int cols, int rows )
+
+     Allocate an array of bits.
+
+     bit* pbm_allocrow( int cols )
+
+     Allocate a row of the given number of bits.
+
+     void pbm_freearray( bit** bits, int rows )
+
+     Free the array allocated  with  pbm_allocarray()  containing
+     the given number of rows.
+
+     void pbm_freerow( bit* bitrow )
+
+     Free a row of bits.
+
+  READING FILES
+     void pbm_readpbminit( FILE* fp, int* colsP, int* rowsP, int* formatP )
+
+     Read the header from a PBM file, filling in the  rows,  cols
+     and format variables.
+
+     void pbm_readpbmrow( FILE* fp, bit* bitrow, int cols, int format )
+
+     Read a row of bits into the bitrow array.  Format  and  cols
+     were filled in by pbm_readpbminit().
+
+     bit** pbm_readpbm( FILE* fp, int* colsP, int* rowsP )
+
+     Read an entire bitmap file into memory, returning the  allo-
+     cated  array  and  filling  in  the rows and cols variables.
+     This function combines  pbm_readpbminit(),  pbm_allocarray()
+     and pbm_readpbmrow().
+
+     char* pm_read_unknown_size( FILE* fp, long* nread )
+
+     Read an entire file or input stream of  unknown  size  to  a
+     buffer.   Allocate memory more memory as needed. The calling
+     routine has  to  free  the  allocated  buffer  with  free().
+     pm_read_unknown_size()  returns  a  pointer to the allocated
+     buffer. The nread argument returns the number of bytes read.
+
+  WRITING FILES
+     void pbm_writepbminit( FILE* fp, int cols, int rows, int forceplain )
+
+     Write the header for a portable bitmap file.  The forceplain
+     flag forces a plain-format file to be written, as opposed to
+     a raw-format one.
+
+     void pbm_writepbmrow( FILE* fp, bit* bitrow, int cols, int forceplain )
+
+     Write a row from a portable bitmap.
+
+     void pbm_writepbm( FILE* fp, bit** bits, int cols, int rows, int forceplain )
+
+     Write the header and all data for a portable  bitmap.   This
+     function combines pbm_writepbminit() and pbm_writepbmrow().
+
+2 See_Also
+     libpgm, libppm, libpnm
+
+2 Author
+     Copyright (C) 1989, 1991 by Tony Hansen and Jef Poskanzer.
+
+1 pbm
+     pbm - portable bitmap file format
+
+2 Description
+     The portable bitmap format is a  lowest  common  denominator
+     monochrome  file format.  It was originally designed to make
+     it reasonable to mail bitmaps  between  different  types  of
+     machines  using  the  typical stupid network mailers we have
+     today.  Now it serves as the common language of a large fam-
+     ily of bitmap conversion filters.  The definition is as fol-
+     lows:
+
+     - A "magic number" for identifying the  file  type.   A  pbm
+       file's magic number is the two characters "P1".
+
+     - Whitespace (blanks, TABs, CRs, LFs).
+
+     - A width, formatted as ASCII characters in decimal.
+
+     - Whitespace.
+
+     - A height, again in ASCII decimal.
+
+     - Whitespace.
+
+     - Width * height bits, each either '1' or '0',  starting  at
+       the  top-left  corner  of the bitmap, proceeding in normal
+       English reading order.
+
+     - The character '1' means black, '0' means white.
+
+     - Whitespace in the bits section is ignored.
+
+     - Characters from a "#" to the next end-of-line are  ignored
+       (comments).
+
+     - No line should be longer than 70 characters.
+
+     Here is an example of a small bitmap in this format:
+     P1
+     # feep.pbm
+     24 7
+     0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+     0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0
+     0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0
+     0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 1 0
+     0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0
+     0 1 0 0 0 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 0 0 0 0
+     0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+
+     Programs that read this format should be as lenient as  pos-
+     sible, accepting anything that looks remotely like a bitmap.
+     There is also a variant on the format, available by  setting
+     the  RAWBITS  option  at compile time.  This variant is dif-
+     ferent in the following ways:
+
+     - The "magic number" is "P4" instead of "P1".
+
+     - The bits are stored eight per byte, high bit first low bit
+       last.
+
+     - No whitespace is allowed in the bits section, and  only  a
+       single  character  of  whitespace (typically a newline) is
+       allowed after the height.
+
+     - The files are eight times smaller and many times faster to
+       read and write.
+
+2 See_Also
+    atktopbm, brushtopbm,  cmuwmtopbm,  g3topbm,  gemtopbm, icontopbm,
+    macptopbm,  mgrtopbm,  pi3topbm,   xbmtopbm,  ybmtopbm,  pbmto10x,
+    pnmtoascii, pbmtoatk, pbmtobbnbg, pbmtocmuwm, pbmtoepson, pbmtog3,
+    pbmtogem,  pbmtogo,  pbmtoicon,  pbmtolj,  pbmtomacp,    pbmtomgr,
+    pbmtopi3,  pbmtoplot,  pbmtoptx,  pbmtox10bm,  pbmtoxbm, pbmtoybm,
+    pbmtozinc,  pbmlife, pbmmake, pbmmask, pbmreduce, pbmtext, pbmupc,
+    pnm, pgm, ppm
+
+2 Author
+     Copyright (C) 1989, 1991 by Jef Poskanzer.
+
+1 bioradtopgm
+     bioradtopgm - convert a Biorad confocal file into a portable
+     graymap
+
+2 Synopsis
+     bioradtopgm [-image#] [imagedata]
+
+2 Description
+     Reads a Biorad confocal file as input.  Produces a  portable
+     graymap  as  output.  If the resulting image is upside down,
+     run it through pnmflip -tb .
+
+2 Options
+     -image#
+          A Biorad image file may contain more  than  one  image.
+          With  this flag, you can specify which image to extract
+          (only one at a time). The first image in the  file  has
+          number  zero.  If  no  image  number  is supplied, only
+          information about the image  size  and  the  number  of
+          images  in  the input is printed out. No output is pro-
+          duced.
+
+2 Bugs
+     A Biorad image may be in word format. If PbmPlus is not com-
+     piled  with  the "BIGGRAYS" flag, word files can not be con-
+     verted. See the Makefile.
+
+2 See_Also
+     pgm, pnmflip
+
+2 Authors
+     Copyright (C) 1993 by Oliver Trepte
+
+1 fitstopgm
+     fitstopgm - convert a FITS file into a portable graymap
+
+2 Synopsis
+     fitstopgm [-image N] [FITSfile]
+
+2 Description
+     Reads a FITS file as input.  Produces a portable graymap  as
+     output.   The results may need to be flipped top for bottom;
+     if so, just pipe the output through pnmflip -tb.
+
+2 Options
+     The -image option is for FITS files with  three  axes.   The
+     assumption  is  that  the third axis is for multiple images,
+     and this option lets you select which one you want.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 References
+     FITS stands for Flexible Image  Transport  System.   A  full
+     description can be found in Astronomy & Astrophysics Supple-
+     ment Series 44 (1981), page 363.
+
+2 See_Also
+     pgmtofits, pgm, pnmflip
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 fstopgm
+     fstopgm - convert a Usenix FaceSaver(tm) file into  a  port-
+     able graymap
+
+2 Synopsis
+     fstopgm [fsfile]
+
+2 Description
+     Reads a Usenix FaceSaver(tm)  file  as  input.   Produces  a
+     portable graymap as output.
+
+     FaceSaver(tm)  files  sometimes  have  rectangular   pixels.
+     While  fstopgm  won't  re-scale  them into square pixels for
+     you, it will give you the precise pnmscale command that will
+     do  the job.  Because of this, reading a FaceSaver(tm) image
+     is a two-step process.  First you do:
+       fstopgm > /dev/null
+     This will tell you whether you need to  use  pnmscale.  Then
+     use one of the following pipelines:
+       fstopgm | pgmnorm
+       fstopgm | pnmscale -whatever | pgmnorm
+     To go to PBM, you want something more like one of these:
+       fstopgm | pnmenlarge 3 | pgmnorm | pgmtopbm
+       fstopgm | pnmenlarge 3 | pnmscale <whatever> | pgmnorm | pgmtopbm
+     You want to enlarge when going to a bitmap because otherwise
+     you  lose information; but enlarging by more than 3 does not
+     look good.
+
+     FaceSaver is a registered trademark of  Metron  Computerware
+     Ltd. of Oakland, CA.
+
+2 See_Also
+     pgmtofs, pgm, pgmnorm, pnmenlarge,  pnmscale,
+     pgmtopbm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 hipstopgm
+     hipstopgm - convert a HIPS file into a portable graymap
+
+2 Synopsis
+     hipstopgm [hipsfile]
+
+2 Description
+     Reads a HIPS file as input.  Produces a portable graymap  as
+     output.
+
+     If the HIPS file contains more than one frame  in  sequence,
+     hipstopgm will concatenate all the frames vertically.
+
+     HIPS is a format developed at the Human Information Process-
+     ing Laboratory, NYU.
+
+2 See_Also
+     pgm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 lispmtopgm
+     lispmtopgm - convert a Lisp Machine  bitmap  file  into  pgm
+     format
+
+2 Synopsis
+     lispmtopgm [lispmfile]
+
+2 Description
+     Reads a Lisp Machine bitmap as input.  Produces  a  portable
+     graymap as output.
+
+     This is the file format written by  the  tv:write-bit-array-
+     file function on TI Explorer and Symbolics lisp machines.
+
+     Multi-plane bitmaps on lisp  machines  are  color;  but  the
+     lispm  image file format does not include a color map, so we
+     must treat it as a graymap instead.  This is unfortunate.
+
+2 See_Also
+     pgmtolispm, pgm
+
+2 Bugs
+     The Lispm bitmap file format is a bit quirky;   Usually  the
+     image  in  the  file  has  its  width rounded up to the next
+     higher multiple of 32, but not always.  If the width is  not
+     a  multiple  of  32,  we  don't  deal  with it properly, but
+     because of the Lispm microcode, such arrays are probably not
+     image data anyway.
+
+     Also, the lispm code for saving bitmaps has a bug,  in  that
+     if  you  are writing a bitmap which is not mod32 across, the
+     file may be up to 7 bits too short!  They round down instead
+     of up, and we don't handle this bug gracefully.
+
+     No color.
+
+2 Author
+     Copyright (C) 1991 by Jamie Zawinski and Jef Poskanzer.
+
+1 pbmtopgm
+     pbmtopgm - convert portable bitmap to  portable  graymap  by
+     averaging areas
+
+2 Synopsis
+     pbmtopgm <width> <height> [pbmfile]
+
+2 Description
+     Reads a portable bitmap as input. Outputs a portable graymap
+     created  by  averaging  the number of pixels within a sample
+     area of width by height around each point. Pbmtopgm is simi-
+     lar  to a special case of ppmconvol. A ppmsmooth step may be
+     needed after pbmtopgm.
+
+     Pbmtopgm has the effect of anti-aliasing bitmaps which  con-
+     tain distinct line features.
+
+2 See_Also
+     pbm
+
+2 Author
+     Copyright (C) 1990 by Angus Duggan Copyright (C) 1989 by Jef
+     Poskanzer.
+
+     Permission  to  use,  copy,  modify,  and  distribute   this
+     software  and  its documentation for any purpose and without
+     fee is hereby granted, provided  that  the  above  copyright
+     notice  appear  in  all  copies and that both that copyright
+     notice and this permission notice appear in supporting docu-
+     mentation.   This  software  is  provided  "as  is"  without
+     express or implied warranty.
+
+2 Notes
+     Pbmtopgm works best with odd sample width and heights.
+
+1 pgmbentley
+     pgmbentley - Bentleyize a portable graymap
+
+2 Synopsis
+     pgmbentley [pgmfile]
+
+2 Description
+     Reads a portable graymap as  input.   Performs  The  Bentley
+     Effect, and writes a portable graymap as output.
+
+     The Bentley Effect is described in "Beyond  Photography"  by
+     Holzmann,  chapter  4,  photo  4.   It's a vertical smearing
+     based on brightness.
+
+2 See_Also
+     pgmoil, ppmrelief, pgm
+
+2 Author
+     Copyright (C) 1990 by Wilson Bent (whb@hoh-2.att.com)
+
+1 pgmenhance
+     pgmenhance - edge-enhance a portable graymap
+
+2 Synopsis
+     pgmenhance [-N] [pgmfile]
+
+2 Description
+     Reads a portable graymap as input.  Enhances the edges,  and
+     writes a portable graymap as output.
+
+     The  edge  enhancing  technique  is  taken  from  Philip  R.
+     Thompson's "xim" program, which in turn took it from section
+     6 of "Digital Halftones by Dot Diffusion", D. E. Knuth,  ACM
+     Transaction  on  Graphics Vol. 6, No. 4, October 1987, which
+     in turn got it from two 1976 papers by J. F. Jarvis et. al.
+
+2 Options
+     The optional -N flag should be a digit from 1 to  9.   1  is
+     the  lowest  level  of  enhancement,  9  is the highest, The
+     default is 9.
+
+2 See_Also
+     pgmedge, pgm, pbm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pgmhist
+     pgmhist - print a histogram of  the  values  in  a  portable
+     graymap
+
+2 Synopsis
+     pgmhist [pgmfile]
+
+2 Description
+     Reads a portable graymap as input.  Prints  a  histogram  of
+     the gray values.
+
+2 See_Also
+     pgmnorm, pgm, ppmhist
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pgmnoise
+     pgmnoise - create a graymap made up of white noise
+
+2 Synopsis
+     pgmnoise width height
+
+2 Description
+     Creates a portable graymap that is made up of random  pixels
+     with gray values in the range of 0 to PGM_MAXMAXVAL (depends
+     on the compilation, either 255 or 65535). The graymap has  a
+     size of width * height pixels.
+
+2 See_Also
+     pgm(5)
+
+2 Author
+     Copyright (C) 1993 by Frank Neumann
+
+1 pgmnorm
+     pgmnorm - normalize the contrast in a portable graymap
+
+2 Synopsis
+     pgmnorm [-bpercent N | -bvalue N] [-wpercent N | -wvalue  N]
+     [pgmfile]
+
+2 Description
+     Reads a portable graymap as input.  Normalizes the  contrast
+     by  forcing the lightest pixels to white, the darkest pixels
+     to black, and linearly rescaling the ones  in  between;  and
+     produces a portable graymap as output.
+
+2 Options
+     By default, the darkest 2 percent of all pixels  are  mapped
+     to  black,  and  the lightest 1 percent are mapped to white.
+     You can override these percentages by  using  the  -bpercent
+     and  -wpercent  flags,  or  you  can specify the exact pixel
+     values to be mapped by using the -bvalue and -wvalue  flags.
+     Appropriate  numbers  for  the  flags can be gotten from the
+     pgmhist tool.  If you just want  to  enhance  the  contrast,
+     then choose values at elbows in the histogram; e.g. if value
+     29 represents 3% of the image but value 30  represents  20%,
+     choose  30  for  bvalue.   If you want to lighten the image,
+     then set bvalue to 0 and just fiddle with wvalue; similarly,
+     to  darken  the  image,  set  wvalue to maxval and play with
+     bvalue.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     pgmhist, pgm
+
+2 Author
+     Partially based on the fbnorm filter  in  Michael  Mauldin's
+     "Fuzzy Pixmap" package.
+
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pgmoil
+     pgmoil - turn a portable graymap into an oil painting
+
+2 Synopsis
+     pgmoil [-n N] [pgmfile]
+
+2 Description
+     Reads a portable graymap as input.  Does an "oil  transfer",
+     and writes a portable graymap as output.
+
+     The oil transfer is described  in  "Beyond  Photography"  by
+     Holzmann,  chapter  4,  photo  7.   It's a sort of localized
+     smearing.
+
+2 Options
+     The optional -n flag controls the size of the area  smeared.
+     The default value is 3.
+
+2 Bugs
+     Takes a long time to run.
+
+2 See_Also
+     pgmbentley, ppmrelief, pgm
+
+2 Author
+     Copyright (C) 1990 by Wilson Bent (whb@hoh-2.att.com)
+
+1 pgmramp
+     pgmramp - generate a grayscale ramp
+
+2 Synopsis
+     pgmramp -lr|-tb | -rectangle|-ellipse width height
+
+2 Description
+     Generates a graymap  of  the  specified  size  containing  a
+     black-to-white ramp.  These ramps are useful for multiplying
+     with other images, using the pnmarith tool.
+
+2 Options
+     -lr  A left to right ramp.
+
+     -tb  A top to bottom ramp.
+
+     -rectangle
+          A rectangular ramp.
+
+     -ellipse
+          An elliptical ramp.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     pnmarith, pgm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pgmtofits
+     pgmtofits - convert a portable graymap into FITS format
+
+2 Synopsis
+     pgmtofits [pgmfile]
+
+2 Description
+     Reads a portable graymap as input.  Produces a FITS file  as
+     output.
+
+     FITS stands for Flexible Image  Transport  System.   A  full
+     description can be found in Astronomy & Astrophysics Supple-
+     ment Series 44 (1981), page 363.
+
+2 See_Also
+     fitstopgm, pgm
+
+2 Author
+     Copyright (C) 1989 by Wilson H. Bent (whb@hoh-2.att.com).
+
+1 pgmtofs
+     pgmtofs - convert portable graymap to  Usenix  FaceSaver(tm)
+     format
+
+2 Synopsis
+     pgmtofs [pgmfile]
+
+2 Description
+     Reads  a  portable  graymap  as  input.    Produces   Usenix
+     FaceSaver(tm) format as output.
+
+     FaceSaver is a registered trademark of  Metron  Computerware
+     Ltd. of Oakland, CA.
+
+2 See_Also
+     fstopgm, pgm
+
+2 Author
+     Copyright (C) 1991 by Jef Poskanzer.
+
+1 pgmtolispm
+     pgmtolispm - convert a portable graymap  into  Lisp  Machine
+     format
+
+2 Synopsis
+     pgmtolispm [pgmfile]
+
+2 Description
+     Reads a portable graymap as input.  Produces a Lisp  Machine
+     bitmap as output.
+
+     This is the file format read by  the  tv:read-bit-array-file
+     function on TI Explorer and Symbolics lisp machines.
+
+     Given a pgm (instead of a pbm) a multi-plane image  will  be
+     output.  This is probably not useful unless you have a color
+     lisp machine.
+
+     Multi-plane bitmaps on lisp  machines  are  color;  but  the
+     lispm  image file format does not include a color map, so we
+     must treat it as a graymap instead.  This is unfortunate.
+
+2 See_Also
+     lispmtopgm, pgm
+
+2 Bugs
+     Output width is always rounded up to the nearest multiple of
+     32;  this might not always be what you want, but it probably
+     is (arrays which are not modulo 32 cannot be passed  to  the
+     Lispm  BITBLT  function, and thus cannot easily be displayed
+     on the screen).
+
+     No color.
+
+2 Author
+     Copyright (C) 1991 by Jamie Zawinski and Jef Poskanzer.
+
+1 pgmtopbm
+     pgmtopbm - convert a portable graymap into a portable bitmap
+
+2 Synopsis
+     pgmtopbm [-floyd|-fs|-threshold  |-dither8|-d8|-cluster3  |-
+     c3|-cluster4|-c4 |-cluster8|-c8] [-value val] [pgmfile]
+
+2 Description
+     Reads a portable graymap as input.  Produces a portable bit-
+     map as output.
+
+     Note that there is no pbmtopgm converter,  because  any  pgm
+     program can read pbm files automagically.
+
+2 Options
+     The default quantization method  is  boustrophedonic  Floyd-
+     Steinberg  error  diffusion (-floyd or -fs).  Also available
+     are simple thresholding (-threshold); Bayer's ordered dither
+     (-dither8) with a 16x16 matrix; and three different sizes of
+     45-degree  clustered-dot  dither  (-cluster3,  -cluster4,  -
+     cluster8).
+
+     Floyd-Steinberg will almost always  give  the  best  looking
+     results;  however, looking good is not always what you want.
+     For instance, thresholding can be used in  a  pipeline  with
+     the  pnmconvol tool, for tasks like edge and peak detection.
+     And clustered-dot dithering gives a  newspaper-ish  look,  a
+     useful special effect.
+
+     The -value flag alters the  thresholding  value  for  Floyd-
+     Steinberg  and  simple  thresholding.   It  should be a real
+     number between 0 and 1.   Above  0.5  means  darker  images;
+     below 0.5 means lighter.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 References
+     The only reference you need for this stuff is "Digital Half-
+     toning" by Robert Ulichney, MIT Press, ISBN 0-262-21009-6.
+
+2 See_Also
+     pbmreduce, pgm, pbm, pnmconvol
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 psidtopgm
+     psidtopgm - convert PostScript "image" data into a  portable
+     graymap
+
+2 Synopsis
+     psidtopgm width height bits/sample [imagedata]
+
+2 Description
+     Reads the "image" data from  a  PostScript  file  as  input.
+     Produces a portable graymap as output.
+
+     This is a very simple and limited program, and is here  only
+     because  so  many  people  have asked for it.  To use it you
+     have to manually extract the readhexstring data portion from
+     your  PostScript  file, and then give the width, height, and
+     bits/sample on the command line.  Before you  attempt  this,
+     you  should  at  least  read  the description of the "image"
+     operator in the PostScript Language Reference Manual.
+
+     It would probably not be too hard to  write  a  script  that
+     uses  this  filter  to read a specific variety of PostScript
+     image, but the variation is too great  to  make  a  general-
+     purpose  reader.   Unless,  of  course,  you want to write a
+     full-fledged PostScript interpreter...
+
+2 See_Also
+     pnmtops, pgm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 spottopgm
+     spottopgm - convert SPOT satellite images to Portable  Grey-
+     map format
+
+2 Synopis
+     spottopgm [-1|2|3]  [Firstcol  Firstline  Lastcol  Lastline]
+     inputfile
+
+2 Options
+     -1|2|3  Extract the given colour from the  SPOT  image.  The
+             colours  are  infra-red,  visible  light  and ultra-
+             violet, although I don't know which  corresponds  to
+             which  number.  If the image is in colour, this will
+             be announced on standard error. The  default  colour
+             is 1.
+
+     Firstcol Firstline Lastcol Lastline
+             Extract the specified rectangle from the SPOT image.
+             Most  SPOT  images  are  3000 lines long and 3000 or
+             more columns wide.  Unfortunately  the  SPOT  format
+             only  gives  the width and not the length. The width
+             is printed on standard error. The default  rectangle
+             is the width of the input image by 3000 lines.
+
+2 Description
+     Spottopgm converts the named inputfile into Portable Greymap
+     format,  defaulting  to  the  first color and the whole SPOT
+     image unless specified by the options.
+
+2 INSTALLATION
+     You  must  edit  the  source  program  and   either   define
+     BIGNDIAN  or  LITTLENDIAN,  and  fix  the  typedefs  for
+     uint32, uint16 and uint8 appropriately.
+
+2 Bugs
+     Currently spottopgm doesn't  determine  the  length  of  the
+     input  file;  this  would  involve two passes over the input
+     file. It defaults to 3000 lines instead.
+
+     Spottopgm could extract a three-color  image  (ppm),  but  I
+     didn't feel like making the program more complicated than it
+     is now.  Besides,  there  is  no  one-to-one  correspondence
+     between  red,  green, blue and infra-red, visible and ultra-
+     violet.
+
+     I've only had a limited number of SPOT images to play  with,
+     and  therefore wouldn't guarantee that this will work on any
+     other images.
+
+2 Author
+     Warren Toomey  wkt@csadfa.cs.adfa.oz.au
+
+1 pgmcrater
+     pgmcrater - create cratered terrain by fractal forgery
+
+2 Synopsis
+     pgmcrater [-number n] [-height|-ysize s] [-width|-xsize s]
+               [-gamma g]
+
+2 Description
+     pgmcrater creates a portable graymap which  mimics  cratered
+     terrain.  The graymap is created by simulating the impact of
+     a given number of craters with  random  position  and  size,
+     then  rendering  the resulting terrain elevations based on a
+     light source shining from one side of the screen.  The  size
+     distribution  of  the  craters is based on a power law which
+     results in many more small craters  than  large  ones.   The
+     number  of  craters of a given size varies as the reciprocal
+     of the area as described on pages 31 and 32 of  Peitgen  and
+     Saupe[1];  cratered  bodies in the Solar System are observed
+     to obey this  relationship.   The  formula  used  to  obtain
+     crater  radii  governed by this law from a uniformly distri-
+     buted pseudorandom sequence was developed by Rudy Rucker.
+
+     High resolution images with large numbers of  craters  often
+     benefit  from  being piped through pnmsmooth.  The averaging
+     performed by this process eliminates some of the jagged pix-
+     els  and  lends  a  mellow  ``telescopic image'' feel to the
+     overall picture.
+
+2 Options
+     -number n Causes n craters to be generated.  If  no  -number
+               specification is given, 50000 craters will be gen-
+               erated.  Don't expect to see them all!  For  every
+               large  crater  there are many, many more tiny ones
+               which tend simply to erode the landscape.  In gen-
+               eral,  the more craters you specify the more real-
+               istic the result; ideally you want the entire ter-
+               rain  to  have  been extensively turned over again
+               and again by cratering.   High  resolution  images
+               containing  five  to ten million craters are stun-
+               ning but take quite a while to create.
+
+     -height height
+               Sets the height of the generated image  to  height
+               pixels.  The default height is 256 pixels.
+
+     -width width
+               Sets the width of the  generated  image  to  width
+               pixels.  The default width is 256 pixels.
+
+     -xsize width
+               Sets the width of the  generated  image  to  width
+               pixels.  The default width is 256 pixels.
+
+
+     -ysize height
+               Sets the height of the generated image  to  height
+               pixels.  The default height is 256 pixels.
+
+     -gamma factor
+               The specified factor is used to gamma correct  the
+               graymap   in  the  same  manner  as  performed  by
+               pnmgamma.  The default value is 1.0, which results
+               in  a medium contrast image.  Values larger than 1
+               lighten  the  image  and  reduce  contrast,  while
+               values  less  than  1 darken the image, increasing
+               contrast.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 Bugs
+     The -gamma option  isn't  really  necessary  since  you  can
+     achieve  the same effect by piping the output from pgmcrater
+     through pnmgamma.  However, pgmcrater performs  an  internal
+     gamma  map  anyway in the process of rendering the elevation
+     array into a graymap, so there's no additional  overhead  in
+     allowing a user-specified gamma.
+
+     Real craters  have  two  distinct  morphologies.   pgmcrater
+     simulates  only  small  craters,  which are hemispherical in
+     shape (regardless of the incidence angle  of  the  impacting
+     body,  as long as the velocity is sufficiently high).  Large
+     craters, such as Copernicus and Tycho on the  Moon,  have  a
+     ``walled plain'' shape with a cross-section more like:
+                /\                            /\
+          _____/  \____________/\____________/  \_____
+     Larger craters should really use this profile, including the
+     central  peak,  and totally obliterate the pre-existing ter-
+     rain.
+
+2 See_Also
+     pgm, pnmgamma, pnmsmooth
+
+     [1]  Peitgen, H.-O., and Saupe,  D.  eds.,  The  Science  Of
+          Fractal Images, New York: Springer Verlag, 1988.
+
+2 Author
+          John Walker
+          Autodesk SA
+          Avenue des Champs-Montants 14b
+          CH-2074 MARIN
+          Suisse/Schweiz/Svizzera/Svizra/Switzerland
+          Usenet:  kelvin@Autodesk.com
+          Fax:     038/33 88 15
+          Voice:   038/33 76 33
+
+     software  and  its documentation for any purpose and without
+     fee is hereby granted, without any  conditions  or  restric-
+     tions.   This software is provided ``as is'' without express
+     or implied warranty.
+
+     PLUGWARE! If you like this kind of stuff, you may also enjoy
+     ``James Gleick's Chaos--The Software'' for MS-DOS, available
+     for $59.95 from your local software store or  directly  from
+     Autodesk,  Inc.,  Attn:  Science Series, 2320 Marinship Way,
+     Sausalito, CA 94965, USA.  Telephone: (800)  688-2344  toll-
+     free  or,  outside  the  U.S. (415) 332-2344 Ext 4886.  Fax:
+     (415) 289-4718.  ``Chaos--The  Software''  includes  a  more
+     comprehensive   fractal   forgery  generator  which  creates
+     three-dimensional landscapes as well as clouds and  planets,
+     plus five more modules which explore other aspects of Chaos.
+     The user guide of more than 200 pages includes an  introduc-
+     tion  by  James  Gleick  and  detailed  explanations by Rudy
+     Rucker of the mathematics and algorithms used by  each  pro-
+     gram.
+
+
+1 pgmedge
+     pgmedge - edge-detect a portable graymap
+
+2 Synopsis
+     pgmedge [pgmfile]
+
+2 Description
+     Reads a portable graymap as input.  Outlines the edges,  and
+     writes  a  portable  graymap  as  output.  Piping the result
+     through pgmtopbm -threshold and playing with  the  threshold
+     value will give a bitmap of the edges.
+
+     The edge detection technique used is to take the Pythagorean
+     sum  of  two  Sobel gradient operators at 90 degrees to each
+     other.  For more details see "Digital Image  Processing"  by
+     Gonzalez and Wintz, chapter 7.
+
+2 See_Also
+     pgmenhance, pgmtopbm, pgm, pbm
+
+2 Author
+     Copyright (C) 1991 by Jef Poskanzer.
+
+1 pgmtexture
+     pgmtexture - calculate textural features on a portable gray-
+     map
+
+2 Synopsis
+     pgmtexture [-d d] [pgmfile]
+
+2 Description
+     Reads a portable  graymap  as  input.   Calculates  textural
+     features  based on spatial dependence matrices at 0, 45, 90,
+     and 135 degrees for a distance d (default  =  1).   Textural
+     features include:
+
+          (1) Angular Second Moment,
+          (2) Contrast,
+          (3) Correlation,
+          (4) Variance,
+          (5) Inverse Difference Moment,
+          (6) Sum Average,
+          (7) Sum Variance,
+          (8) Sum Entropy,
+          (9) Entropy,
+          (10) Difference Variance,
+          (11) Difference Entropy,
+          (12, 13) Information Measures of Correlation, and
+          (14) Maximal Correlation Coefficient.
+
+     Algorithm taken from:
+     Haralick, R.M., K. Shanmugam, and I.  Dinstein.  1973.  Tex-
+     tural  features  for image classification. IEEE Transactions
+     on Systems, Man, and Cybertinetics, SMC-3(6):610-621.
+
+2 Bugs
+     The program can run incredibly slow for large images (larger
+     than  64  x  64)  and command line options are limited.  The
+     method for finding (14) the maximal correlation coefficient,
+     which  requires  finding  the second largest eigenvalue of a
+     matrix Q, does not always converge.
+
+2 References
+     IEEE Transactions on Systems, Man, and  Cybertinetics,  SMC-
+     3(6):610-621.
+
+2 See_Also
+     pgm, pnmcut
+
+2 Author
+     Copyright (C) 1991 by Texas Agricultural Experiment Station,
+     employer for hire of James Darrell McCauley.
+
+1 rawtopgm
+     rawtopgm - convert raw grayscale bytes into a portable gray-
+     map
+
+2 Synopsis
+     rawtopgm  [-headerskip  N]  [-rowskip  N]   [-tb|-topbottom]
+     [width height] [imagedata]
+
+2 Description
+     Reads raw grayscale bytes as  input.   Produces  a  portable
+     graymap  as output.  The input file is just grayscale bytes.
+     If you don't specify the width and  height  on  the  command
+     line,  the  program will check the size of the image and try
+     to make a quadratic image of it. It is an error to supply  a
+     non  quadratic  image  without  specifying width and height.
+     The maxval is assumed to be 255.
+
+2 Options
+     -headerskip
+          If the file has a header, you can use this flag to skip
+          over it.
+
+     -rowskip
+          If there is padding at the ends of the  rows,  you  can
+          skip  it  with  this  flag.  Note that rowskip can be a
+          real number.  Amazingly, I once had an image with 0.376
+          bytes of padding per row.  This turned out to be due to
+          a file-transfer problem, but I was still able  to  read
+          the image.
+
+     -tb -topbottom
+          Flips the image upside down.  The first pixel in a  pgm
+          file  is  in  the  lower left corner of the image.  For
+          conversion from images with  the  first  pixel  in  the
+          upper  left  corner  (e.g.  the  Molecular Dynamics and
+          Leica confocal formats) this  flips  the  image  right.
+          This is equivalent to rawtopgm [file] | pnmflip -tb .
+
+2 Bugs
+     If you don't specify the image width and height, the program
+     will try to read the entire image to a memory buffer. If you
+     get a message that states that you are out of memory, try to
+     specify  the width and height on the command line. Also, the
+     -tb option consumes much memory.
+
+2 See_Also
+     pgm, rawtoppm, pnmflip
+
+2 Authors
+     Copyright (C) 1989 by Jef Poskanzer.
+     Modified June 1993 by Oliver Trepte, oliver@fysik4.kth.se
+
+1 pnmarith              
+     pnmarith - perform arithmetic on two portable anymaps
+
+2 Synopsis
+     pnmarith -add|-subtract|-multiply| pnmfile1 pnmfile2
+
+2 Description
+     Reads two portable anymaps as input.  Performs the specified
+     arithmetic operation, and produces a portable anymap as out-
+     put.  The two input anymaps  must  be  the  same  width  and
+     height.
+
+     The arithmetic is performed between corresponding pixels  in
+     the  two anymaps, as if maxval was 1.0, black was 0.0, and a
+     linear scale in  between.   Results  that  fall  outside  of
+     [0..1) are truncated.
+
+     The operator -difference calculates the  absolute  value  of
+     pnmarith  -subtract pnmfile1 pnmfile2, i.e. no truncation is
+     done.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     pbmmask, pnmpaste, pnminvert, pnm
+
+2 Author
+     Copyright (C) 1989, 1991 by Jef Poskanzer.  Lightly modified
+     by Marcel Wijkstra <wijkstra@fwi.uva.nl>
+
+1 pnmcat                
+     pnmcat - concatenate portable anymaps
+
+2 Synopsis
+     pnmcat   [-white|-black]   -leftright|-lr   [-jtop|-jbottom]
+     pnmfile pnmfile ...
+     pnmcat   [-white|-black]   -topbottom|-tb   [-jleft|-jright]
+     pnmfile pnmfile ...
+
+2 Description
+     Reads portable anymaps as input.  Concatenates  them  either
+     left  to  right  or  top  to bottom, and produces a portable
+     anymap as output.
+
+2 Options
+     If the anymaps are not all the same height  (left-right)  or
+     width  (top-bottom),  the  smaller ones have to be justified
+     with the largest.  By default, they get  centered,  but  you
+     can specify one side or the other with one of the -j* flags.
+     So, -topbottom -jleft would stack the anymaps on top of each
+     other, flush with the left edge.
+
+     The -white and -black flags specify what  color  to  use  to
+     fill  in  the extra space when doing this justification.  If
+     neither is specified, the program makes a guess.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     pnm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pnmcomp
+     pnmcomp - composite two portable anymap files together
+
+2 Synopsis
+     pnmcomp [-invert] [-xoffN] [-yoffN] [-alphapgmfile]  overlay
+     [pnm-input] [pnm-output]
+
+2 Description
+     Reads in a portable any map image and put a overlay upon it,
+     with  optional alpha mask.  The -alpha pgmfile allows you to
+     also add an alpha mask file to the compositing process,  the
+     range  of  max  and  min can be swapped by using the -invert
+     option.  The -xoff and  -yoff  arguments  can  be  negative,
+     allowing  you to shift the overlay off the top corner of the
+     screen.
+
+2 See_Also
+     pnm
+
+2 Author
+     Copyright (C) 1992 by David Koblas (koblas@mips.com).
+
+1 pnmconvol             
+     pnmconvol - general MxN convolution on a portable anymap
+
+2 Synopsis
+     pnmconvol convolutionfile [pnmfile]
+
+2 Description
+     Reads two portable anymaps as input.  Convolves  the  second
+     using the first, and writes a portable anymap as output.
+
+     Convolution means replacing each pixel with a weighted aver-
+     age of the nearby pixels.  The weights and the area to aver-
+     age are determined by the convolution matrix.  The  unsigned
+     numbers  in  the convolution file are offset by -maxval/2 to
+     make signed numbers, and  then  normalized,  so  the  actual
+     values in the convolution file are only relative.
+
+     Here is a sample convolution file; it does a simple  average
+     of  the  nine  immediate  neighbors, resulting in a smoothed
+     image:
+         P2
+         3 3
+         18
+         10 10 10
+         10 10 10
+         10 10 10
+
+     To see how this works, do the above-mentioned offset:  10  -
+     18/2 gives 1.  The possible range of values is from 0 to 18,
+     and after the offset that's -9 to 9.  The normalization step
+     makes  the  range  -1  to  1,  and  the  values  get  scaled
+     correspondingly so they become 1/9 - exactly what you  want.
+     The equivalent matrix for 5x5 smoothing would have maxval 50
+     and be filled with 26.
+
+     The convolution file will usually be a graymap, so that  the
+     same convolution gets applied to each color component.  How-
+     ever, if you want to use a pixmap and do a different  convo-
+     lution to different colors, you can certainly do that.
+
+2 See_Also
+     pnmsmooth, pnm
+
+2 Author
+     Copyright (C) 1989, 1991 by Jef Poskanzer.
+
+1 pnmcrop               
+     pnmcrop - crop a portable anymap
+
+2 Synopsis
+     pnmcrop [-white|-black] [pnmfile]
+
+2 Description
+     Reads a portable anymap as input.  Removes  edges  that  are
+     the background color, and produces a portable anymap as out-
+     put.
+
+2 Options
+     By default, it makes a guess as to what the background color
+     is.  You can override the default with the -white and -black
+     flags.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     pnmcut, pnm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pnmcut                
+     pnmcut - cut a rectangle out of a portable anymap
+
+2 Synopsis
+     pnmcut x y width height [pnmfile]
+
+2 Description
+     Reads a portable anymap as input.   Extracts  the  specified
+     rectangle,  and produces a portable anymap as output.  The x
+     and y can be negative, in which case  they  are  interpreted
+     relative  to  the  right  and  bottom of the anymap, respec-
+     tively.
+
+2 See_Also
+     pnm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pnmdepth              
+     pnmdepth - change the maxval in a portable anymap
+
+2 Synopsis
+     pnmdepth newmaxval [pnmfile]
+
+2 Description
+     Reads a portable anymap as  input.   Scales  all  the  pixel
+     values, and writes out the image with the new maxval.  Scal-
+     ing the colors down to a smaller maxval will result in  some
+     loss of information.
+
+     Be careful of off-by-one errors when choosing the  new  max-
+     val.   For instance, if you want the color values to be five
+     bits wide, use a maxval of 31, not 32.
+
+2 See_Also
+     pnm, ppmquant, ppmdither
+
+2 Author
+     Copyright (C) 1989, 1991 by Jef Poskanzer.
+
+1 pnmenlarge            
+     pnmenlarge - read a portable anymap and enlarge it N times
+
+2 Synopsis
+     pnmenlarge N [pnmfile]
+
+2 Description
+     Reads a portable anymap as input.  Replicates its  pixels  N
+     times, and produces a portable anymap as output.
+
+     pnmenlarge can only enlarge by integer factors.  The  slower
+     but more general pnmscale can enlarge or reduce by arbitrary
+     factors, and pbmreduce can reduce by  integer  factors,  but
+     only for bitmaps.
+
+     If you enlarge by a factor of 3 or more, you should probably
+     add  a  pnmsmooth  step; otherwise, you can see the original
+     pixels in the resulting image.
+
+2 See_Also
+     pbmreduce, pnmscale, pnmsmooth, pnm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pnmfile               
+     pnmfile - describe a portable anymap
+
+2 Synopsis
+     pnmfile [pnmfile] ...
+
+2 Description
+     Reads one or more portable anymaps  as  input.   Writes  out
+     short  descriptions  of  the image type, size, etc.  This is
+     mostly for use in shell scripts, so the format is  not  par-
+     ticularly pretty.
+
+2 See_Also
+     pnm, file
+
+2 Author
+     Copyright (C) 1991 by Jef Poskanzer.
+
+1 pnmflip               
+     pnmflip - perform one or more flip operations on a  portable
+     anymap
+
+2 Synopsis
+     pnmflip [-leftright|-lr]  [-topbottom|-tb]  [-transpose|-xy]
+     [-rotate90|-r90|-ccw    ]    [-rotate270|-r270|-cw    ]   [-
+     rotate180|-r180] [pnmfile]
+
+2 Description
+     Reads a portable anymap as input.  Performs one or more flip
+     operations,  in  the order specified, and writes out a port-
+     able anymap.
+
+2 Options
+     The  flip  operations  available  are:  left  for  right  (-
+     leftright  or  -lr); top for bottom (-topbottom or -tb); and
+     transposition (-transpose or -xy).  In addition, some canned
+     concatenations   are   available:   -rotate90   or  -ccw  is
+     equivalent to -transpose -topbottom; -rotate270  or  -cw  is
+     equivalent  to  -transpose  -leftright;  and  -rotate180  is
+     equivalent to -leftright -topbottom.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     pnmrotate, pnm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pnminvert             
+     pnminvert - invert a portable anymap
+
+2 Synopsis
+     pnminvert [pnmfile]
+
+2 Description
+     Reads a portable anymap as  input.   Inverts  it  black  for
+     white and produces a portable anymap as output.
+
+2 See_Also
+     pnm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pnmnoraw              
+     pnmnoraw - force a portable anymap into plain format
+
+2 Synopsis
+     pnmnoraw [pnmfile]
+
+2 Description
+     Reads a portable anymap as input.  Writes it  out  in  plain
+     (non-raw)  format.   This  is  fairly useless if you haven't
+     defined the PBMPLUSAWBITS compile-time option.
+
+2 See_Also
+     pnm
+
+2 Author
+     Copyright (C) 1991 by Jef Poskanzer.
+
+1 pnmpad
+     pnmpad - add borders to portable anymap
+
+2 Synopsis
+     pnmpad [-white|-black] [-l#] [-r#] [-t#] [-b#] [pnmfile]
+
+2 Description
+     Reads a portable anymap as input. Outputs a portable  anymap
+     with extra borders of the sizes specified. The colour of the
+     borders can be set to black or white (default black).
+
+
+2 See_Also
+     pbmmake, pnmpaste, pbm
+
+2 Author
+     Copyright (C) 1990 by Angus Duggan Copyright (C) 1989 by Jef
+     Poskanzer.
+
+     Permission  to  use,  copy,  modify,  and  distribute   this
+     software  and  its documentation for any purpose and without
+     fee is hereby granted, provided  that  the  above  copyright
+     notice  appear  in  all  copies and that both that copyright
+     notice and this permission notice appear in supporting docu-
+     mentation.   This  software  is  provided  "as  is"  without
+     express or implied warranty.
+
+1 pnmpaste              
+     pnmpaste - paste a rectangle into a portable anymap
+
+2 Synopsis
+     pnmpaste   [-replace|-or|-and   |-xor]   frompnmfile   x   y
+     [intopnmfile]
+
+2 Description
+     Reads two portable anymaps  as  input.   Inserts  the  first
+     anymap  into  the second at the specified location, and pro-
+     duces a portable anymap the same size as the second as  out-
+     put.  If the second anymap is not specified, it is read from
+     stdin.  The x and y can be negative, in which case they  are
+     interpreted  relative to the right and bottom of the anymap,
+     respectively.
+
+     This tool is most useful in combination  with  pnmcut.   For
+     instance,  if  you  want  to edit a small segment of a large
+     image, and your image editor cannot edit  the  large  image,
+     you  can cut out the segment you are interested in, edit it,
+     and then paste it back in.
+
+     Another useful companion tool is pbmmask.
+
+     The optional flag specifies the operation to use when  doing
+     the  paste.   The  default  is -replace.  The other, logical
+     operations are only allowed if both input  images  are  bit-
+     maps.  These operations act as if white is TRUE and black is
+     FALSE.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     pnmcut, pnminvert, pnmarith, pnm, pbmmask
+
+2 Author
+     Copyright (C) 1989, 1991 by Jef Poskanzer.
+
+1 pnmscale              
+     pnmscale - scale a portable anymap
+
+2 Synopsis
+     pnmscale s [pnmfile]
+     pnmscale -xsize|-width|-ysize| -height s [pnmfile]
+     pnmscale -xscale|-yscale s [pnmfile]
+     pnmscale -xscale|-xsize|-width  s  -yscale|-ysize|-height  s
+     [pnmfile]
+     pnmscale -xysize x y [pnmfile]
+
+2 Description
+     Reads a portable anymap as input.  Scales it by  the  speci-
+     fied  factor  or  factors  and produces a portable anymap as
+     output.  If the input file is in color, the output  will  be
+     too,  otherwise  it will be grayscale.  You can both enlarge
+     (scale factor > 1) and reduce (scale factor < 1).
+
+     You can specify one dimension as a pixel size, and the other
+     dimension will be scaled correspondingly.
+
+     You can specify one dimension as  a  scale,  and  the  other
+     dimension will not be scaled.
+
+     You can specify different sizes or scales for each axis.
+
+     Or, you can use the special -xysize  flag,  which  fits  the
+     image  into  the  specified size without changing the aspect
+     ratio.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+     If you enlarge by a factor of 3 or more, you should probably
+     add  a  pnmsmooth  step; otherwise, you can see the original
+     pixels in the resulting image.
+
+2 See_Also
+     pbmreduce, pnmenlarge, pnmsmooth, pnm
+
+2 Author
+     Copyright (C) 1989, 1991 by Jef Poskanzer.
+
+1 pnmtile               
+     pnmtile - replicate a portable anymap into a specified size
+
+2 Synopsis
+     pnmtile width height [pnmfile]
+
+2 Description
+     Reads a portable anymap as input.  Replicates it until it is
+     the  specified  size, and produces a portable anymap as out-
+     put.
+
+2 See_Also
+     pnm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pnmtoddif
+     pnmtoddif - Convert a portable anymap to DDIF format
+
+2 Synopis
+     pnmtoddif pnmtoddif [-resolution x y] [pnmfile [ddiffile]]
+
+2 Description
+     pnmtoddif takes a portable anymap from  standard  input  and
+     converts it into a DDIF image file on standard output or the
+     specified DDIF file.
+
+     pbm format (bitmap) data is written as 1 bit DDIF, pgm  for-
+     mat data (greyscale) as 8 bit greyscale DDIF, and ppm format
+     data is written as 8,8,8 bit  color  DDIF.  All  DDIF  image
+     files  are written as uncompressed. The data plane organiza-
+     tion is interleaved by pixel.
+
+     In addition to the number of pixels in the width and  height
+     dimension, DDIF images also carry information about the size
+     that the image should have, that is, the physical space that
+     a  pixel occupies. PBMPLUS images do not carry this informa-
+     tion, hence it has to be externally supplied.   The  default
+     of  78  dpi  has  the  beneficial  property of not causing a
+     resize on most Digital Equipment Corporation color monitors.
+
+2 Options
+     resolution
+          The horizontal and vertical resolution  of  the  output
+          image in dots per inch. Defaults to 78 dpi.
+
+     pnmfile        The filename for the image file in  pnm  for-
+                    mat.   If  this argument is omitted, input is
+                    read from stdin.
+
+     ddiffile       The filename for the image file to be created
+                    in DDIF format.  If this argument is omitted,
+                    the ddiffile is written to  standard  output.
+                    It  can  only  specified if a pnmfile is also
+                    specified.
+
+2 Author
+     Burkhard Neidecker-Lutz
+     Digital Equipment Corporation, CEC Karlsruhe
+     neideck@nestvx.enet.dec.com
+
+1 pnmtops               
+     pnmtops - convert portable anymap to PostScript
+
+2 Synopsis
+     pnmtops [-scale s] [-turn|-noturn]  [-rle|-runlength]  [-dpi
+     n] [-width n] [-height n] [pnmfile]
+
+2 Description
+     Reads a portable anymap  as  input.   Produces  Encapsulated
+     PostScript as output.
+
+     If the input file is in color (PPM), a color PostScript file
+     gets  written.   Some  PostScript  interpreters can't handle
+     color PostScript.  If you have one of these you will need to
+     run your image through ppmtopgm first.
+
+     Note that there is no pstopnm tool - this transformation  is
+     one-way,  because  a  pstopnm  tool  would be a full-fledged
+     PostScript interpreter, which is beyond the  scope  of  this
+     package.   However,  see  the psidtopgm tool, which can read
+     grayscale non-runlength PostScript  image  data.   Also,  if
+     you're willing to install the fairly large GhostScript pack-
+     age, it comes with a pstoppm script.
+
+2 Options
+     The -scale flag controls  the  scale  of  the  result.   The
+     default  scale  is 1, which on a 300 dpi printer such as the
+     Apple LaserWriter makes the output look about the same  size
+     as  the  input would if it was displayed on a typical 72 dpi
+     screen.  To get one PNM pixel per 300 dpi printer pixel, use
+     "-scale 0.25".
+
+     The -turn and -noturn flags control whether the  image  gets
+     turned  90  degrees.  Normally, if an image is wider than it
+     is tall, it gets turned  automatically  to  better  fit  the
+     page.   If the -turn flag is specified, it will be turned no
+     matter what its shape; and if the -noturn flag is specified,
+     it will not be turned no matter what its shape.
+
+     The -rle or -runlength flag  specifies  run-length  compres-
+     sion.   This  may  save  time if the host-to-printer link is
+     slow; but normally the printer's processing time  dominates,
+     so -rle makes things slower.
+
+     The -dpi flag lets you specify the dots  per  inch  of  your
+     output   device.    The  default  is  300  dpi.   In  theory
+     PostScript is device-independent and you don't have to worry
+     about  this,  but  in practice its raster rendering can have
+     unsightly bands if the device pixels and  the  image  pixels
+     aren't in sync.
+
+     The -width and -height flags let you specify the size of the
+     page.  The default is 8.5 inches by 11 inches.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     pnm, psidtopgm
+
+2 Author
+     Copyright (C) 1989, 1991 by Jef Poskanzer.
+
+1 pnmtorast             
+     pnmtorast - convert a portable pixmap into a Sun rasterfile
+
+2 Synopsis
+     pnmtorast [-standard|-rle] [pnmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Produces a Sun rasterfile
+     as output.
+
+     Color values in Sun rasterfiles  are  eight  bits  wide,  so
+     pnmtorast  will  automatically scale colors to have a maxval
+     of 255.  An extra pnmdepth step is not necessary.
+
+2 Options
+     The -standard flag forces the result to  be  in  RT_STANDARD
+     form;  the -rle flag, RT_BYTE_ENCODED, which is smaller but,
+     well, less standard.  The default is -rle.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     rasttopnm, pnm
+
+2 Author
+     Copyright (C) 1989, 1991 by Jef Poskanzer.
+
+1 pnmtoxwd              
+     pnmtoxwd - convert a portable anymap into an X11 window dump
+
+2 Synopsis
+     pnmtoxwd [-pseudodepth n] [-directcolor] [pnmfile]
+
+2 Description
+     Reads a portable anymap as input.  Produces  an  X11  window
+     dump as output.  This window dump can be displayed using the
+     xwud tool.
+
+     Normally, pnmtoxwd produces a StaticGray dump file  for  pbm
+     and  pgm  files.  For ppm, it writes a PseudoColor dump file
+     if  there  are  up  to  256  colors  in  the  input,  and  a
+     DirectColor  dump file otherwise.  The -directcolor flag can
+     be used to force a DirectColor dump.  And  the  -pseudodepth
+     flag  can  be  used to change the depth of PseudoColor dumps
+     from the default of 8 bits / 256 colors.
+
+2 See_Also
+     xwdtopnm, pnm, xwud
+
+2 Author
+     Copyright (C) 1989, 1991 by Jef Poskanzer.
+
+1 rasttopnm             
+     rasttopnm - convert a Sun rasterfile into a portable anymap
+
+2 Synopsis
+     rasttopnm [rastfile]
+
+2 Description
+     Reads a Sun rasterfile as input.  Produces a portable anymap
+     as output.  The type of the output file depends on the input
+     file - if it's black & white, a pbm file is written, else if
+     it's  grayscale  a  pgm  file, else a ppm file.  The program
+     tells you which type it is writing.
+
+2 See_Also
+     pnmtorast, pnm
+
+2 Author
+     Copyright (C) 1989, 1991 by Jef Poskanzer.
+
+1 xwdtopnm              
+     xwdtopnm - convert a X11 or X10  window  dump  file  into  a
+     portable anymap
+
+2 Synopsis
+     xwdtopnm [xwdfile]
+
+2 Description
+     Reads a X11 or X10 window dump file as  input.   Produces  a
+     portable  anymap  as  output.   The  type of the output file
+     depends on the input file - if it's black  &  white,  a  pbm
+     file  is  written, else if it's grayscale a pgm file, else a
+     ppm file.  The program tells you which type it is writing.
+
+     Using this  program,  you  can  convert  anything  on  an  X
+     workstation's  screen into an anymap.  Just display whatever
+     you're interested in, do an xwd, run  it  through  xwdtopnm,
+     and then use pnmcut to select the part you want.
+
+2 Bugs
+     I haven't tested this tool with very many configurations, so
+     there  are  probably  bugs.   Please let me know if you find
+     any.
+
+2 See_Also
+     pnmtoxwd, pnm, xwd
+
+2 Author
+     Copyright (C) 1989, 1991 by Jef Poskanzer.
+
+1 zeisstopnm            
+     zeisstopnm - convert a Zeiss confocal file into  a  portable
+     anymap
+
+2 Synopsis
+     zeisstopnm [-pgm | -ppm] [zeissfile]
+
+2 Description
+     Reads a Zeiss confocal file as input.  Produces  a  portable
+     anymap  as  output.   The type of the output file depends on
+     the input file - if it's grayscale a pgm file,  else  a  ppm
+     file  will be produced.  The program tells you which type it
+     is writing.
+
+2 Options
+     -pgm Force the output to be a pgm file.
+
+     -ppm Force the output to be a ppm file.
+
+2 See_Also
+     pnm
+
+2 Author
+     Copyright (C) 1993 by Oliver Trepte
+
+1 pnmgamma              
+     pnmgamma - perform gamma correction on a portable anymap
+
+2 Synopsis
+     pnmgamma value [pnmfile]
+     pnmgamma redvalue greenvalue bluevalue [pnmfile]
+
+2 Description
+     Reads a portable anymap as input.   Performs  gamma  correc-
+     tion, and produces a portable anymap as output.
+
+     The arguments specify what gamma value(s) to use.   A  value
+     of 1.0 leaves the image alone, less than one darkens it, and
+     greater than one lightens it.
+
+2 See_Also
+     pnm
+
+2 Author
+     Copyright (C) 1991 by Bill Davidson and Jef Poskanzer.
+
+1 pnmhistmap
+     pnmhistmap - draw a histogram for a PGM or PPM file
+
+2 Synopsis
+     pnmhistmap [-black] [-white] [-max N] [-verbose] [pnmfile]
+
+2 Description
+     Reads a portable anymap  as  input,  although  bitmap  (PBM)
+     input  produces  an error message and no image.  Produces an
+     image showing a histogram of the color (or gray)  values  in
+     the  input.  A graymap (PGM) input produces a bitmap output.
+     A pixmap (PPM) input produces pixmap output with three over-
+     laid  histograms:  a  red one for the red input, a green one
+     for the green input, and a blue one for the blue input.  The
+     output is fixed in size: 256 pixels wide by 200 pixels high.
+
+2 Options
+     -black
+          Ignores the count of black pixels when scaling the his-
+          togram.
+
+     -white
+          Ignores the count of white pixels when scaling the his-
+          togram.
+
+     The -black and -white options, which can be used  seperately
+     or  together,  are useful for images with a large percentage
+     of pixels whose value is zero or 255, which  can  cause  the
+     remaining  histogram  data to become unreadbaly small.  Note
+     that, for pixmap inputs, these options apply to all  colors;
+     if,  for example, the input has a large number of bright-red
+     areas, you will probably want to use the -white option.
+
+     -max N
+          Force the scaling of the histogram  to  use  N  as  the
+          largest-count  value.  This is useful for inputs with a
+          large percentage of single-color pixels which  are  not
+          black or white.
+
+     -verbose
+          Report the progress of making the histogram,  including
+          the largest-count value used to scale the output.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 Bugs
+     Assumes maxval is always 255.  Images with a smaller  maxval
+     will  only  use the lower-value side of the histogram.  This
+     can be overcome either by piping the input through "pnmdepth
+     255"  or  by cutting and scaling the lower-value side of the
+     histogram.  Neither is a particularly elegant solution.
+     Should allow the output size to be specified.
+
+2 See_Also
+     pgmhist(1), ppmhist(1), pgm(5), ppm(5)
+
+2 Author
+     Wilson H. Bent. Jr. (whb@usc.edu).
+
+1 pnmnlfilt             
+     pnmnlfilt - non-linear filters:  smooth,  alpha  trim  mean,
+     optimal estimation smoothing, edge enhancement.
+
+2 Synopsis
+     pnmnlfilt alpha radius [pnmfile]
+
+2 Description
+     This is something of a swiss army knife  filter.  It  has  3
+     distinct  operating modes. In all of the modes each pixel in
+     the image is examined and processed according to it and  its
+     surrounding pixels values. Rather than using the 9 pixels in
+     a 3x3 block, 7 hexagonal area samples are taken, the size of
+     the  hexagons  being  controlled  by the radius parameter. A
+     radius value of 0.3333 means that the 7 hexagons exactly fit
+     into  the  center  pixel  (ie.   there  will be no filtering
+     effect). A radius value of 1.0 means  that  the  7  hexagons
+     exactly fit a 3x3 pixel array.
+
+        Alpha trimmed mean filter.    (0.0 <= alpha
+     The value of the center pixel will be replaced by  the  mean
+     of the 7 hexagon values, but the 7 values are sorted by size
+     and the top and bottom alpha portion of the 7  are  excluded
+     from  the  mean.   This  implies  that an alpha value of 0.0
+     gives the same sort of output as a normal  convolution  (ie.
+     averaging  or smoothing filter), where radius will determine
+     the "strength" of the filter. A good value to start from for
+     subtle  filtering  is  alpha = 0.0, radius = 0.55 For a more
+     blatant effect, try alpha 0.0 and radius 1.0
+
+     An alpha value of 0.5 will cause the median value of  the  7
+     hexagons  to be used to replace the center pixel value. This
+     sort of filter is good for eliminating "pop" or single pixel
+     noise  from  an  image  without  spreading  the noise out or
+     smudging features on the image. Judicious use of the  radius
+     parameter  will fine tune the filtering. Intermediate values
+     of alpha give effects somewhere between smoothing and  "pop"
+     noise  reduction.  For  subtle  filtering  try starting with
+     values of alpha = 0.4, radius =  0.6   For  a  more  blatant
+     effect try alpha = 0.5, radius = 1.0
+
+        Optimal estimation smoothing. (1.0 <= alpha
+     This type of filter applies a  smoothing  filter  adaptively
+     over  the  image.   For  each pixel the variance of the sur-
+     rounding hexagon values is calculated,  and  the  amount  of
+     smoothing  is made inversely proportional to it. The idea is
+     that if the variance is small then it is due to noise in the
+     image,  while  if  the  variance  is large, it is because of
+     "wanted" image features. As usual the radius parameter  con-
+     trols  the  effective  radius,  but it probably advisable to
+     leave the radius between 0.8 and 1.0 for the variance calcu-
+     lation to be meaningful.  The alpha parameter sets the noise
+     threshold, over which less smoothing  will  be  done.   This
+     means  that  small values of alpha will give the most subtle
+     filtering effect, while large values will tend to smooth all
+     parts of the image. You could start with values like alpha =
+     1.2, radius = 1.0 and try increasing or decreasing the alpha
+     parameter  to get the desired effect. This type of filter is
+     best for filtering out dithering noise in  both  bitmap  and
+     color images.
+
+        Edge enhancement. (-0.1 >= alpha >=
+     This is the opposite type of filter to the smoothing filter.
+     It  enhances  edges. The alpha parameter controls the amount
+     of edge enhancement, from subtle (-0.1) to  blatant  (-0.9).
+     The radius parameter controls the effective radius as usual,
+     but useful values are between 0.5 and 0.9. Try starting with
+     values of alpha = 0.3, radius = 0.8
+
+        Combination use.
+     The various modes of pnmnlfilt can be  used  one  after  the
+     other  to  get  the  desired  result. For instance to turn a
+     monochrome dithered image into a grayscale image  you  could
+     try one or two passes of the smoothing filter, followed by a
+     pass of the optimal estimation filter, then some subtle edge
+     enhancement. Note that using edge enhancement is only likely
+     to be useful after one  of  the  non-linear  filters  (alpha
+     trimmed mean or optimal estimation filter), as edge enhance-
+     ment is the direct opposite of smoothing.
+
+     For reducing color quantization noise in images (ie. turning
+     .gif  files  back into 24 bit files) you could try a pass of
+     the optimal estimation filter (alpha  1.2,  radius  1.0),  a
+     pass of the median filter (alpha 0.5, radius 0.55), and pos-
+     sibly a pass of the edge enhancement filter.  Several passes
+     of the optimal estimation filter with declining alpha values
+     are more effective than a single pass  with  a  large  alpha
+     value.   As  usual,  there  is  a tradeoff between filtering
+     effectiveness  and  loosing   detail.   Experimentation   is
+     encouraged.
+
+2 References
+     The alpha-trimmed mean filter is based on the description in
+     IEEE  CG&A  May  1990  Page 23 by Mark E. Lee and Richard A.
+     Redner, and has been  enhanced  to  allow  continuous  alpha
+     adjustment.
+
+     The optimal estimation filter is taken from an article "Con-
+     verting  Dithered  Images  Back  to  Gray  Scale"  by  Allen
+     Stenger, Dr Dobb's Journal, November 1992, and this  article
+     references "Digital Image Enhancement and Noise Filtering by
+     Use of Local Statistics", Jong-Sen Lee, IEEE Transactions on
+     Pattern Analysis and Machine Intelligence, March 1980.
+     The edge enhancement details are from  pgmenhance,  which
+     is  taken  from Philip R. Thompson's "xim" program, which in
+     turn took it from section 6 of  "Digital  Halftones  by  Dot
+     Diffusion", D. E. Knuth, ACM Transaction on Graphics Vol. 6,
+     No. 4, October 1987, which in turn  got  it  from  two  1976
+     papers by J. F. Jarvis et. al.
+
+2 See_Also
+     pgmenhance, pnmconvol, pnm
+
+2 Bugs
+     Integers and tables may overflow if PPM_MAXMAXVAL is greater
+     than 255.
+
+2 Author
+     Graeme W. Gill    graeme@labtam.oz.au
+
+1 pnmrotate             
+     pnmrotate - rotate a portable anymap by some angle
+
+2 Synopsis
+     pnmrotate [-noantialias] angle [pnmfile]
+
+2 Description
+     Reads a portable anymap as input.  Rotates it by the  speci-
+     fied angle and produces a portable anymap as output.  If the
+     input file is in color, the output will be too, otherwise it
+     will  be  grayscale.   The  angle  is  in  degrees (floating
+     point), measured counter-clockwise.  It can be negative, but
+     it  should  be  between  -90  and  90.   Also, for rotations
+     greater than 45 degrees you may get better  results  if  you
+     first use pnmflip to do a 90 degree rotation and then pnmro-
+     tate less than 45 degrees back the other direction
+
+     The rotation algorithm is Alan Paeth's  three-shear  method.
+     Each  shear is implemented by looping over the source pixels
+     and distributing fractions to each of the  destination  pix-
+     els.   This has an "anti-aliasing" effect - it avoids jagged
+     edges and similar artifacts.  However, it  also  means  that
+     the  original  colors  or gray levels in the image are modi-
+     fied.  If you need to keep precisely the same set of colors,
+     you  can  use the -noantialias flag.  This does the shearing
+     by moving pixels without changing their values.  If you want
+     anti-aliasing  and  don't care about the precise colors, but
+     still need a limited *number* of colors,  you  can  run  the
+     result through ppmquant.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 References
+     "A Fast Algorithm  for  General  Raster  Rotation"  by  Alan
+     Paeth, Graphics Interface '86, pp. 77-81.
+
+2 See_Also
+     pnmshear, pnmflip, pnm, ppmquant
+
+2 Author
+     Copyright (C) 1989, 1991 by Jef Poskanzer.
+
+1 pnmshear              
+     pnmshear - shear a portable anymap by some angle
+
+2 Synopsis
+     pnmshear [-noantialias] angle [pnmfile]
+
+2 Description
+     Reads a portable anymap as input.  Shears it by  the  speci-
+     fied angle and produces a portable anymap as output.  If the
+     input file is in color, the output will be too, otherwise it
+     will  be  grayscale.   The  angle  is  in  degrees (floating
+     point), and measures this:
+         +-------+  +-------+
+         |       |  |\       \
+         |  OLD  |  | \  NEW  \
+         |       |  |an\       \
+         +-------+  |gle+-------+
+     If the angle is negative, it shears the other way:
+         +-------+  |-an+-------+
+         |       |  |gl/       /
+         |  OLD  |  |e/  NEW  /
+         |       |  |/       /
+         +-------+  +-------+
+     The angle should not get too close to  90  or  -90,  or  the
+     resulting anymap will be unreasonably wide.
+
+     The shearing is implemented by looping over the source  pix-
+     els  and  distributing  fractions to each of the destination
+     pixels.  This has an  "anti-aliasing"  effect  -  it  avoids
+     jagged  edges and similar artifacts.  However, it also means
+     that the original colors or gray levels  in  the  image  are
+     modified.   If  you  need  to keep precisely the same set of
+     colors, you can use the -noantialias flag.   This  does  the
+     shearing by moving pixels without changing their values.  If
+     you want anti-aliasing and  don't  care  about  the  precise
+     colors, but still need a limited *number* of colors, you can
+     run the result through ppmquant.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     pnmrotate, pnmflip, pnm, ppmquant
+
+2 Author
+     Copyright (C) 1989, 1991 by Jef Poskanzer.
+
+1 tifftopnm             
+     tifftopnm - convert a TIFF file into a portable anymap
+
+2 Synopsis
+     tifftopnm [-headerdump] tifffile
+
+2 Description
+     Reads a TIFF file as input.  Produces a portable  anymap  as
+     output.   The  type  of the output file depends on the input
+     file - if it's black & white, a pbm file is written, else if
+     it's  grayscale  a  pgm  file, else a ppm file.  The program
+     tells you which type it is writing.
+
+2 Options
+     -headerdump
+          Dump TIFF file information to stderr.  This information
+          may  be  useful in debugging TIFF file conversion prob-
+          lems.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     pnmtotiff, pnm
+
+2 Bugs
+     This program is not self-contained.   To  use  it  you  must
+     fetch  the TIFF Software package listed in the OTHER.SYSTEMS
+     file and configure PBMPLUS to use  libtiff.   See  PBMPLUS's
+     Makefile for details on this configuration.
+
+2 Author
+     Derived by Jef Poskanzer from tif2ras.c, which is  Copyright
+     (c)  1990  by  Sun  Microsystems,  Inc.   Author: Patrick J.
+     Naughton (naughton@wind.sun.com).
+
+1 pnmtotiff             
+     pnmtotiff - convert a a portable anymap into a TIFF file
+
+2 Synopsis
+     pnmtotiff [-none|-packbits| -lzw|-g3|-g4] [-2d]  [-fill]  [-
+     predictor n] [-msb2lsb|-lsb2msb] [-rowsperstrip n] [pnmfile]
+
+2 Description
+     Reads a portable anymap as input.  Produces a TIFF  file  as
+     output.
+
+2 Options
+     By default, pnmtotiff creates a TIFF file with LZW  compres-
+     sion.   This  is  your  best bet most of the time.  However,
+     some TIFF readers can't deal with it.  If you  want  to  try
+     another  compression  scheme or tweak some of the other even
+     more obscure output options, there are a number of flags  to
+     play with.
+
+     The -none, -packbits, -lzw, -g3, and -g4 options are used to
+     override  the default and set the compression scheme used in
+     creating the output file.  The CCITT Group  3  and  Group  4
+     compression  algorithms  can only be used with bilevel data.
+     The -2d and -fill options are meaningful only with  Group  3
+     compression:  -2d  requests  2-dimensional encoding, while -
+     fill requests that each encoded scanline be zero-filled to a
+     byte boundry.  The -predictor option is only meaningful with
+     LZW compression: a predictor value of 2 causes each scanline
+     of  the  output  image  to  undergo  horizontal differencing
+     before it is encoded; a value of 1 forces each  scanline  to
+     be  encoded  without  differencing.   By  default, pnmtotiff
+     creates a TIFF file  with  msb-to-lsb  fill  order.   The  -
+     msb2lsb  and  -lsb2msb  options  are  used  to  override the
+     default and set the fill order used in  creating  the  file.
+     The  -rowsperstrip  option  can be used to set the number of
+     rows (scanlines) in each strip of data in the  output  file.
+     By default, the output file has the number of rows per strip
+     set to a value that will ensure each strip is no more than 8
+     kilobytes long.
+
+2 Bugs
+     This program is not self-contained.   To  use  it  you  must
+     fetch  the TIFF Software package listed in the OTHER.SYSTEMS
+     file and configure PBMPLUS to use  libtiff.   See  PBMPLUS's
+     Makefile for details on this configuration.
+
+2 See_Also
+     tifftopnm, pnm
+
+2 Author
+     Derived by Jef Poskanzer from ras2tiff.c, which is Copyright
+     (c)  1990  by  Sun  Microsystems,  Inc.   Author: Patrick J.
+     Naughton (naughton@wind.sun.com).
+
+1 libpnm
+     libpnm - functions to support portable anymap programs
+
+2 Synopsis
+     #include <pnm.h>
+     cc ... libpnm.a libppm.a libpgm.a libpbm.a
+
+
+2 Description
+  TYPES AND CONSTANTS
+     typedef ... xel;
+     typedef ... xelval;
+     #define PNM_MAXMAXVAL ...
+     extern xelval pnm_pbmmaxval;
+
+     Each xel contains three xelvals, each of which  should  con-
+     tain   only   the   values   between  0  and  PNM_MAXMAXVAL.
+     pnm_pbmmaxval is the maxval used when a PNM program reads  a
+     PBM  file.   Normally it is 1; however, for some programs, a
+     larger value gives better results.
+
+  XEL MANIPULATIONS
+     xelval PNM_GET1( xel x )
+
+     This macro extracts a single value from  an  xel,  when  you
+     know  it's  from  a  PBM  or PGM file.  When it's from a PPM
+     file, use PPM_GETR(), PPM_GETG(), and PPM_GETB().
+
+     void PNM_ASSIGN1( xel x, xelval v )
+
+     This macro assigns a single value to an xel, when  you  know
+     it's from a PBM or PGM file.  When it's from a PPM file, use
+     PPM_ASSIGN().
+
+     int PNM_EQUAL( xel x, xel y )
+
+     This macro checks two xels for equality.
+
+     int PNM_FORMAT_TYPE( int format )
+
+     For distinguishing different file types.
+
+  INITIALIZATION
+     void pnm_init( int* argcP, char* argv[] )
+
+     All PNM programs must call this routine.
+
+  MEMORY MANAGEMENT
+     xel** pnm_allocarray( int cols, int rows )
+
+     Allocate an array of xels.
+     xel* pnm_allocrow( int cols )
+
+     Allocate a row of the given number of xels.
+
+     void pnm_freearray( xel** xels, int rows )
+
+     Free the array allocated  with  pnmllocarray()  containing
+     the given number of rows.
+
+     void pnm_freerow( xel* xelrow )
+
+     Free a row of xels.
+
+  READING FILES
+     void pnm_readpnminit( FILE* fp, int* colsP, int* rowsP, xelval* maxvalP, int* formatP )
+
+     Read the header from a PNM file, filling in the rows,  cols,
+     maxval and format variables.
+
+     void pnm_readpnmrow( FILE* fp, xel* xelrow, int cols, xelval maxval, int format )
+
+     Read a row of xels into the xelrow array.  Format, cols, and
+     maxval were filled in by pnm_readpnminit().
+
+     xel** pnm_readpnm( FILE* fp, int* colsP, int* rowsP, xelval* maxvalP, int* formatP )
+
+     Read an entire anymap file into memory, returning the  allo-
+     cated  array and filling in the rows, cols, maxval, and for-
+     mat variables.  This  function  combines  pnm_readpnminit(),
+     pnm_allocarray()    and    pnm_readpnmrow().    Unlike   the
+     equivalent functions in PBM, PGM, and PPM,  it  returns  the
+     format so you can tell what type the file is.
+
+  WRITING FILES
+     void pnm_writepnminit( FILE* fp, int cols, int rows, xelval maxval, int format, int forceplain )
+
+     Write the header for a portable  anymap  file.   Unlike  the
+     equivalent  functions  in  PBM,  PGM,  and  PPM, you have to
+     specify the output  type.   The  forceplain  flag  forces  a
+     plain-format  file to be written, as opposed to a raw-format
+     one.
+
+     void pnm_writepnmrow( FILE* fp, xel* xelrow, int cols, xelval maxval, int format, int forceplain )
+
+     Write a row from a portable anymap.
+
+     void pnm_writepnm( FILE* fp, xel** xels, int cols, int rows, xelval maxval, int format, int forceplain )
+
+     Write the header and all data for a portable  anymap.   This
+     function combines pnm_writepnminit() and pnm_writepnmrow().
+
+  FORMAT PROMOTION
+     void pnm_promoteformatrow( xel* xelrow, int cols, xelval maxval, int format, xelval newmaxval, int newformat )
+
+     Promote a row of xels from one maxval and format  to  a  new
+     set.   Used  when  combining  multiple  anymaps of different
+     types - just take the max of the maxvals and the max of  the
+     formats, and promote them all to that.
+
+     void pnm_promoteformat( xel** xels, int cols, int rows, xelval maxval, int format, xelval newmaxval, int newformat )
+
+     Promote an entire anymap.
+
+  XEL MANIPULATION
+     xel pnm_whitexel( xelval maxval, int format )
+     xel pnm_blackxel( xelval maxval, int format )
+
+     Return a white or black xel for the given maxval and format.
+
+     void pnm_invertxel( xel* x, xelval maxval, int format )
+
+     Invert an xel.
+
+     xel pnm_backgroundxelrow( xel* xelrow, int cols, xelval maxval, int format )
+
+     Figure out an appropriate background xel based on this row.
+
+     xel pnm_backgroundxel( xel** xels, int cols, int rows, xelval maxval, int format )
+
+     Figure out a background xel based on an entire anymap.  This
+     can do a slightly better job than pnm_backgroundxelrow().
+
+2 See_Also
+     pbm, pgm, ppm
+
+2 Author
+     Copyright (C) 1989, 1991 by Tony Hansen and Jef Poskanzer.
+
+1 pnm
+     pnm - portable anymap file format
+
+2 Description
+     The pnm programs operate on portable bitmaps, graymaps,  and
+     pixmaps,  produced by the pbm, pgm, and ppm segments.  There
+     is no file format associated with pnm itself.
+
+2 See_Also
+    anytopnm,    rasttopnm,  tifftopnm,  xwdtopnm,  pnmtops,  pnmtorast,
+    pnmtotiff, pnmtoxwd, pnmar- ith, pnmcat, pnmconvol, pnmcrop, pnmcut,
+    pnmdepth,  pnmenlarge,    pnmfile,    pnmflip,  pnmgamma,  pnmindex,
+    pnminvert,  pnmmargin,  pnmnoraw,   pnmpaste,  pnmrotate,  pnmscale,
+    pnmshear, pnmsmooth, pnmtile, ppm, pgm, pbm
+
+2 Author
+     Copyright (C) 1989, 1991 by Jef Poskanzer.
+1 bmptoppm
+     bmptoppm - convert a BMP file into a portable pixmap
+
+2 Synopsis
+     bmptoppm [bmpfile]
+
+2 Description
+     Reads a Microsoft Windows or OS/2 BMP file as  input.   Pro-
+     duces a portable pixmap as output.
+
+2 See_Also
+     ppmtobmp, ppm
+
+2 Author
+     Copyright (C) 1992 by David W. Sanderson.
+
+1 gouldtoppm
+     gouldtoppm - convert Gould scanner file into a portable pix-
+     map
+
+2 Synopsis
+     gouldtoppm [gouldfile]
+
+2 Description
+     Reads a file produced by the Gould scanner as  input.   Pro-
+     duces a portable pixmap as output.
+
+2 See_Also
+     ppm
+
+2 Author
+     Copyright(C) 1990 by Stephen Paul Lesniewski
+
+1 ilbmtoppm
+     ilbmtoppm - convert an ILBM file into a portable pixmap
+
+2 Synopsis
+     ilbmtoppm [-verbose] [ILBMfile]
+
+2 Description
+     Reads an IFF ILBM file as input.  Produces a portable pixmap
+     as output.  Supported ILBM types are:
+
+     Normal ILBMs with 1-16 planes.
+
+     Amiga Extra-Halfbrite (EHB)
+
+     Amiga Hold-and-modify (HAM) with 3-16 planes.
+
+     24 bit.
+
+     Color map (BMHD + CMAP chunk only, nPlanes = 0).
+
+     Unofficial direct color.
+          1-16 planes for each color component.
+
+     Chunks used:
+          BMHD, CMAP, CAMG (only HAM  &  EHB  flags  used),  BODY
+          unofficial DCOL chunk to identify direct color ILBM
+
+     Chunks ignored:
+          GRAB, DEST, SPRT, CRNG, CCRT, CLUT, DPPV, DRNG, EPSF
+
+     Other chunks (ignored but displayed in verbose mode):
+          NAME, AUTH, (c), ANNO, DPI
+
+     Unknown chunks are skipped.
+
+2 Options
+     -verbose
+          Give some informaton about the ILBM file.
+
+2 Bugs
+     Probably.
+
+2 References
+     Amiga ROM Kernel Reference Manual - Devices (3rd Ed.)
+     Addison Wesley, ISBN 0-201-56775-X
+
+2 See_Also
+     ppm(5), ppmtoilbm(1)
+
+2 Authors
+     Copyright (C) 1989 by Jef Poskanzer.
+     Modified June 1993 by Ingo Wilken
+     (Ingo.Wilken@informatik.uni-oldenburg.de)
+
+1 imgtoppm
+     imgtoppm - convert an Img-whatnot file into a portable  pix-
+     map
+
+2 Synopsis
+     imgtoppm [imgfile]
+
+2 Description
+     Reads an Img-whatnot file as  input.   Produces  a  portable
+     pixmap  as output.  The Img-whatnot toolkit is available for
+     FTP on venera.isi.edu, along with numerous  images  in  this
+     format.
+
+2 See_Also
+     ppm
+
+2 Author
+     Based on a simple conversion program posted to comp.graphics
+     by Ed Falk.
+
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 mtvtoppm
+     mtvtoppm - convert output from the MTV or  PRT  ray  tracers
+     into a portable pixmap
+
+2 Synopsis
+     mtvtoppm [mtvfile]
+
+2 Description
+     Reads an input  file  from  Mark  VanDeWettering's  MTV  ray
+     tracer.  Produces a portable pixmap as output.
+
+     The PRT raytracer also produces this format.
+
+2 See_Also
+     ppm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 pcxtoppm
+     pcxtoppm - convert a PCX file into a portable pixmap
+
+2 Synopsis
+     pcxtoppm [pcxfile]
+
+2 Description
+     Reads a PCX file as input.  Produces a  portable  pixmap  as
+     output.
+
+2 See_Also
+     ppmtopcx, ppm
+
+2 Author
+     Copyright (C) 1990 by Michael Davidson.
+
+1 pgmtoppm
+     pgmtoppm - colorize a portable graymap into a portable  pix-
+     map
+
+2 Synopsis
+     pgmtoppm colorspec [pgmfile]
+     pgmtoppm colorspec1-colorspec2 [pgmfile]
+     pgmtoppm -map mapfile [pgmfile]
+
+2 Description
+     Reads a portable graymap as input.  Colorizes it  by  multi-
+     plying the the gray values by specified color or colors, and
+     produces a portable pixmap as output.
+
+     If only one color is specified, black in the pgm file  stays
+     black  and  white  in  the pgm file turns into the specified
+     color in the ppm file.  If two colors (separated by a  dash)
+     are specified, then black gets mapped to the first color and
+     white gets mapped to the second.
+
+     The color can be specified in five ways:
+
+     o    A name, assuming that a pointer to an  X11-style  color
+          names file was compiled in.
+
+     o    An X11-style hexadecimal specifier: rgb:r/g/b, where  r
+          g and b are each 1- to 4-digit hexadecimal numbers.
+
+     o    An X11-style decimal specifier: rgbi:r/g/b, where  r  g
+          and b are floating point numbers between 0 and 1.
+
+     o    For backwards compatibility, an  old-X11-style  hexade-
+          cimal    number:    #rgb,   #rrggbb,   #rrrgggbbb,   or
+          #rrrrggggbbbb.
+
+     o    For  backwards  compatibility,  a  triplet  of  numbers
+          separated  by commas: r,g,b, where r g and b are float-
+          ing point numbers between 0 and  1.   (This  style  was
+          added before MIT came up with the similar rgbi style.)
+
+     Also, the -map flag lets you specify an entire  colormap  to
+     be  used.   The  mapfile  is  just a ppm file; it can be any
+     shape, all that matters is the colors in it and their order.
+     In  this case, black gets mapped into the first color in the
+     map file, and white gets mapped to the last.
+
+2 See_Also
+     rgb3toppm, ppmtopgm, ppmtorgb3, ppm, pgm
+
+2 Author
+     Copyright (C) 1991 by Jef Poskanzer.
+
+1 pi1toppm
+     pi1toppm - convert an Atari Degas .pi1 into a portable  pix-
+     map
+
+2 Synopsis
+     pi1toppm [pi1file]
+
+2 Description
+     Reads an Atari Degas .pi1 file as input.  Produces  a  port-
+     able pixmap as output.
+
+2 See_Also
+     ppmtopi1, ppm, pi3topbm, pbmtopi3
+
+2 Author
+     Copyright (C) 1991 by Steve Belczyk (seb3@gte.com)  and  Jef
+     Poskanzer.
+
+1 picttoppm
+     picttoppm - convert a Macintosh PICT file  into  a  portable
+     pixmap
+
+2 Synopsis
+     picttoppm [-verbose] [-fullres] [-noheader] [pictfile]
+
+2 Description
+     Reads a PICT file (version 1 or 2) and  outputs  a  portable
+     pixmap.   Useful  as  the first step in converting a scanned
+     image to something that can be displayed on Unix.
+
+2 Options
+     -fullres
+          Force any images in the PICT file to be output with  at
+          least  their full resolution.  A PICT file may indicate
+          that a contained image is to be scaled down before out-
+          put.   This  option forces images to retain their sizes
+          and prevent information loss.
+
+     -noheader
+          Do not skip the 512 byte header that is present on  all
+          PICT  files.   This  is  useful when you have PICT data
+          that was not stored in the data fork of a PICT file.
+
+     -verbose
+          Turns on verbose mode which prints a a whole  bunch  of
+          information  that  only  picttoppm  hackers really care
+          about.
+
+2 Bugs
+     The PICT file format is a general drawing format.  picttoppm
+     only  supports a small subset of its operations but is still
+     very useful for files produced  by  scanning  software.   In
+     particular,  text  added to a scanned image will be silently
+     ignored.
+
+2 See_Also
+     Inside Macintosh volume 5, ppmtopict, ppm
+
+2 Author
+     Copyright 1989 George Phillips
+
+1 pjtoppm
+     pjtoppm - convert an HP PaintJet file to a portable pixmap
+
+2 Synopsis
+     pjtoppm [paintjet]
+
+2 Description
+     Reads an HP PaintJet file as input and converts  it  into  a
+     portable  pixmap.  This was a quick hack to save some trees,
+     and it only handles a small subset of the paintjet commands.
+     In  particular,  it will only handle enough commands to con-
+     vert most raster image files.
+
+REFERENCES
+     HP PaintJet XL Color Graphics Printer User's Guide
+
+2 See_Also
+     ppmtopj
+
+2 Author
+     Copyright (C) 1991 by Christos Zoulas.
+
+1 ppm3d
+     ppm3d - convert two  portable  pixmap  into  a  red/blue  3d
+     glasses pixmap
+
+2 Synopsis
+     ppm3d leftppmfile rightppmfile [horizontal offset]
+
+2 Description
+     Reads two portable pixmaps as input.   Produces  a  portable
+     pixmap   as   output,   with   the   images  overlapping  by
+     horizontal offset
+
+     pixels in blue/red format.
+
+     horizontal offset defaults to 30 pixels.   Pixmaps  MUST  be
+     the same size.
+
+2 See_Also
+     ppm(5)
+
+2 Author
+     Copyright (C) 1993 by David K. Drum.
+
+1 ppmbrighten
+     ppmbrighten - change an images Saturation and Value from  an
+     HSV map
+
+2 Synopsis
+     ppmbrighten  [-n]  [-s  <+-  saturation>]  [-v  <+-  value>]
+     <ppmfile>
+
+2 Description
+     Reads a portable pixmap as input.  Converts the  image  from
+     RGB  space  to HSV space and changes the Value by <+- value>
+     as a percentage.  Likewise with  the  Saturation.   Doubling
+     the Value would involve
+
+     ppmbrighten -v 100
+
+     to add 100 percent to the Value.
+
+     The 'n' option normalizes the Value to exist between 0 and 1
+     (normalized).
+
+2 See_Also
+     pgmnorm, ppm
+
+2 Notes
+     This program does not change the number of colors.
+
+2 Author
+     Copyright (C) 1990 by Brian Moffet Copyright (C) 1989 by Jef
+     Poskanzer.
+
+     Permission  to  use,  copy,  modify,  and  distribute   this
+     software  and  its documentation for any purpose and without
+     fee is hereby granted, provided  that  the  above  copyright
+     notice  appear  in  all  copies and that both that copyright
+     notice and this permission notice appear in supporting docu-
+     mentation.   This  software  is  provided  "as  is"  without
+     express or implied warranty.
+
+1 ppmchange
+     ppmchange -  change  all  pixels  of  one  color  to another in a
+     portable pixmap
+
+2 Synopsis
+     ppmchange colorspec1 colorspec2 [ppmfile]
+
+2 Description
+     Reads  a portable  pixmap  as  input.    Changes  all  pixels  of
+     colorspec1 to colorspec2, leaving all others unchanged.
+
+     The color can be specified in five ways:
+
+     o    A name, assuming that a pointer to an  X11-style  color
+          names file was compiled in.
+
+     o    An X11-style hexadecimal specifier: rgb:r/g/b, where  r
+          g and b are each 1- to 4-digit hexadecimal numbers.
+
+     o    An X11-style decimal specifier: rgbi:r/g/b, where  r  g
+          and b are floating point numbers between 0 and 1.
+
+     o    For backwards compatibility, an  old-X11-style  hexade-
+          cimal    number:    #rgb,   #rrggbb,   #rrrgggbbb,   or
+          #rrrrggggbbbb.
+
+     o    For  backwards  compatibility,  a  triplet  of  numbers
+          separated  by commas: r,g,b, where r g and b are float-
+          ing point numbers between 0 and  1.   (This  style  was
+          added before MIT came up with the similar rgbi style.)
+
+2 See_Also
+     pgmtoppm(1), ppm(5)
+
+2 Author
+     Wilson H. Bent. Jr. (whb@usc.edu)
+
+1 ppmdim
+     ppmdim - dim a portable pixmap down to total blackness
+
+2 Synopsis
+     ppmdim dimfactor [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.    Diminishes its brightness by
+     the specified dimfactor down to total blackness.    The dimfactor
+     may be in the range from 0.0 (total  blackness, deep night, nada,
+     null, nothing) to 1.0 (original picture's brightness).
+
+     As pnmgamma does not do the brightness correction in  the  way  I
+     wanted it, this small program was written.
+
+     ppmdim is similar to ppmbrighten , but not exactly the same.
+
+2 See_Also
+     ppm(5), ppmflash(1), pnmgamma(1), ppmbrighten(1)
+
+2 Author
+     Copyright (C) 1993 by Frank Neumann
+
+1 ppmdist
+     ppmdist -  simplistic grayscale assignment for machine generated,
+     color images
+
+2 Synopsis
+     ppmdist [-intensity|-frequency] [ppmfile]
+
+2 Description
+     Reads a portable pixmap  as  input,  performs  a  simplistic
+     grayscale assignment intended for use with grayscale or bit-
+     map printers.
+
+     Often conversion from ppm to pgm will yield  an  image  with
+     contrast  too low for good printer output.  The program max-
+     imizes contrast between the gray levels output.
+
+     A ppm input of n colors is read, and a pgm of n gray  levels
+     is  written.   The  gray  levels  take on the values 0..n-1,
+     while maxval takes on n-1.
+
+     The mapping from color to stepped grayscale can be performed
+     in  order of input pixel intensity, or input pixel frequency
+     (number of repetitions).
+
+2 Options
+     Helpful only for images with a very small number of  colors.
+     Perhaps should have been an option to ppmtopgm.
+
+2 See_Also
+     ppmtopgm, ppmhist, ppm
+
+2 Author
+     Copyright (C) 1993 by Dan Stromberg.
+
+1 ppmdither
+     ppmdither - ordered dither for color images
+
+2 Synopsis
+     ppmdither [-dim dimension] [-red shades] [-green shades]  [-
+     blue shades] [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input, and applies  dithering  to
+     it to reduce the number of colors used down to the specified
+     number of shades for each primary.  The  default  number  of
+     shades is red=5, green=9, blue=5, for a total of 225 colors.
+     To convert the image to a binary  rgb  format  suitable  for
+     color  printers,  use  -red 2 -green 2 -blue 2.  The maximum
+     number of colors that can be used is 256 and can be computed
+     as the product of the number of red, green and blue shades.
+
+2 Options
+     -dim dimension
+                   The size of the dithering matrix.  Must  be  a
+                   power of 2.
+
+     -red shades   The number of red shades to be  used;  minimum
+                   of 2.
+
+     -green shades The number of green shades to be used; minimum
+                   of 2.
+
+     -blue shades  The number of blue shades to be used;  minimum
+                   of 2.
+
+2 See_Also
+     pnmdepth, ppmquant, ppm
+
+2 Author
+     Copyright (C) 1991 by Christos Zoulas.
+
+1 ppmflash
+     ppmflash - brighten a picture up to complete white-out
+
+2 Synopsis
+     ppmflash flashfactor [ppmfile]
+
+2 Description
+     Reads  a portable pixmap as input.  Increases its  brightness  by
+     the  specified  flashfactor  up to a total white-out image.   The
+     flashfactor  may  be  in  the  range from 0.0 (original picture's
+     brightness) to 1.0 (full white-out, The Second After).
+
+     As pnmgamma  does  not  do the brightness correction in the way I
+     wanted it, this small program was written.
+
+     This program is similar to ppmbrighten, but not exactly the same.
+
+2 See_Also
+     ppm(5), ppmdim(1), pnmgamma(1), ppmbrighten(1)
+
+2 Author
+     Copyright (C) 1993 by Frank Neumann
+
+1 ppmhist
+     ppmhist - print a histogram of a portable pixmap
+
+2 Synopsis
+     ppmhist [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Generates a histogram  of
+     the colors in the pixmap.
+
+2 See_Also
+     ppm, pgmhist
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 ppmmake
+     ppmmake - create a pixmap of a specified size and color
+
+2 Synopsis
+     ppmmake color width height
+
+2 Description
+     Produces a portable pixmap of the  specified  color,  width,
+     and height.
+
+     The color can be specified in five ways:
+
+     o    A name, assuming that a pointer to an  X11-style  color
+          names file was compiled in.
+
+     o    An X11-style hexadecimal specifier: rgb:r/g/b, where  r
+          g and b are each 1- to 4-digit hexadecimal numbers.
+
+     o    An X11-style decimal specifier: rgbi:r/g/b, where  r  g
+          and b are floating point numbers between 0 and 1.
+
+     o    For backwards compatibility, an  old-X11-style  hexade-
+          cimal    number:    #rgb,   #rrggbb,   #rrrgggbbb,   or
+          #rrrrggggbbbb.
+
+     o    For  backwards  compatibility,  a  triplet  of  numbers
+          separated  by commas: r,g,b, where r g and b are float-
+          ing point numbers between 0 and  1.   (This  style  was
+          added before MIT came up with the similar rgbi style.)
+
+2 See_Also
+     ppm, pbmmake
+
+2 Author
+     Copyright (C) 1991 by Jef Poskanzer.
+
+1 ppmmix
+     ppmmix - blend together two portable pixmaps
+
+2 Synopsis
+     ppmmix fadefactor ppmfile1 ppmfile2
+
+2 Description
+     Reads two portable pixmaps  as  input.  Mixes them together using
+     the specified fade factor.   The  fade factor may be in the range
+     from 0.0 (only ppmfile1's image data)  to  1.0  (only  ppmfile2's
+     image data).  Anything in between gains  a  smooth  blend between
+     the two images.
+
+     The two pixmaps must have the same size.
+
+2 See_Also
+     ppm(5)
+
+2 Author
+     Copyright (C) 1993 by Frank Neumann
+
+1 ppmquant
+     ppmquant - quantize the colors in  a  portable  pixmap  down to a
+     specified number
+
+2 Synopsis
+     ppmquant [-floyd|-fs] ncolors [ppmfile]
+     ppmquant [-floyd|-fs] -map mapfile [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Chooses ncolors colors to
+     best  represent  the  image, maps the existing colors to the
+     new ones, and writes a portable pixmap as output.
+
+     The quantization method is Heckbert's "median cut".
+
+     Alternately, you can skip the color-choosing step by  speci-
+     fying  your  own set of colors with the -map flag.  The map-
+     file is just a ppm file; it  can  be  any  shape,  all  that
+     matters is the colors in it.  For instance, to quantize down
+     to the 8-color IBM TTL color set, you might use:
+         P3
+         8 1
+         255
+           0   0   0
+         255   0   0
+           0 255   0
+           0   0 255
+         255 255   0
+         255   0 255
+           0 255 255
+         255 255 255
+     If you want to quantize one pixmap  to  use  the  colors  in
+     another  one,  just  use the second one as the mapfile.  You
+     don't have to reduce it down  to  only  one  pixel  of  each
+     color, just use it as is.
+
+     The -floyd/-fs flag enables a Floyd-Steinberg  error  diffu-
+     sion  step.   Floyd-Steinberg gives vastly better results on
+     images where the  unmodified  quantization  has  banding  or
+     other  artifacts, especially when going to a small number of
+     colors such as the above IBM set.   However,  it  does  take
+     substantially more CPU time, so the default is off.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 References
+     "Color Image Quantization for Frame Buffer Display" by  Paul
+     Heckbert, SIGGRAPH '82 Proceedings, page 297.
+
+2 See_Also
+     ppmquantall, pnmdepth, ppmdither, ppm
+
+2 Author
+     Copyright (C) 1989, 1991 by Jef Poskanzer.
+
+1 ppmrelief
+     ppmrelief - run a Laplacian relief filter on a portable pixmap
+
+2 Synopsis
+     ppmrelief [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Does a  Laplacian  relief
+     filter, and writes a portable pixmap as output.
+
+     The Laplacian relief filter is described in "Beyond  Photog-
+     raphy"  by  Holzmann,  equation  3.19.  It's a sort of edge-
+     detection.
+
+2 See_Also
+     pgmbentley, pgmoil, ppm
+
+2 Author
+     Copyright (C) 1990 by Wilson Bent (whb@hoh-2.att.com)
+
+1 ppmshift
+     ppmshift - shift lines of  a  portable  pixmap left or right by a
+     random amount
+
+2 Synopsis
+     ppmshift shift [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Shifts every row of image data
+     to the left or right by a certain  amount.  The 'shift' parameter
+     determines by how many pixels a row is to be shifted at most.
+
+     Another one of those effects I intended to use  for  MPEG  tests.
+     Unfortunately,  this  program will not help me here - it  creates
+     too  random patterns to be used for animations.  Still, it  might
+     give interesting results on still images.
+
+2 Example
+     Check  this  out:    Save your  favourite  model's  picture  from
+     something like alt.binaries.pictures.supermodels (ok, or from any
+     other  picture source), convert it to ppm, and  process  it  e.g.
+     like this, assuming the picture is 800x600 pixels:
+     
+       # take the upper half, and leave it like it is
+       pnmcut 0 0 800 300 cs.ppm >upper.ppm
+
+       # take the lower half, flip it upside down,  dim it and distort
+         it a little
+       pnmcut 0 300 800 300 cs.ppm | pnmflip -tb | ppmdim 0.7 |
+          ppmshift 10 >lower.ppm
+
+       # and concatenate the two pieces
+       pnmcat -tb upper.ppm lower.ppm >newpic.ppm  The  resulting
+     picture  looks  like the image being reflected on a water surface
+     with slight ripples.
+
+2 See_Also
+     ppm(5), pnmcut(1), pnmflip(1), ppmdim(1), pnmcat(1)
+
+2 Author
+     Copyright (C) 1993 by Frank Neumann
+
+1 ppmspread
+     ppmspread - displace  a  portable  pixmap's  pixels by  a  random
+     amount
+
+2 Synopsis
+     ppmspread amount [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Moves every pixel around a bit
+     relative to its original position.  amount determines by how many
+     pixels a pixel is to be moved around at most.
+
+     Pictures processed with this  filter  will  seem  to  be somewhat
+     dissolved or unfocussed (although they  appear  more  coarse than
+     images processed by something like pnmconvol ).
+
+2 See_Also
+     ppm(5), pnmconvol(1)
+
+2 Author
+     Copyright (C) 1993 by Frank Neumann
+
+1 ppmtoacad
+     ppmtoacad - convert portable pixmap to AutoCAD database or slide
+
+2 Synopsis
+     ppmtoacad [-dxb] [-poly] [-background colour] [-white] [-
+               aspect ratio] [-8] [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Produces an AutoCAD(Reg.)
+     slide  file or binary database import (.dxb) file as output.
+     If no ppmfile is specified,  input  is  read  from  standard
+     input.
+
+2 Options
+     -dxb An AutoCAD binary database import (.dxb) file is  writ-
+          ten.   This  file  is  read with the DXBIN command and,
+          once loaded, becomes part of  the  AutoCAD  geometrical
+          database  and  can  be viewed and edited like any other
+          object.  Each sequence of identical  pixels  becomes  a
+          separate  object  in  the  database; this can result in
+          very large AutoCAD drawing files.  However, if you want
+          to trace over a bitmap, it lets you zoom and pan around
+          the bitmap as you wish.
+
+     -poly
+          If the -dxb option is  not  specified,  the  output  of
+          ppmtoacad  is an AutoCAD slide file.  Normally each row
+          of pixels is represented by an AutoCAD line entity.  If
+          -poly  is  selected,  the pixels are rendered as filled
+          polygons.  If the slide is viewed  on  a  display  with
+          higher  resolution  than  the  source pixmap, this will
+          cause the pixels to  expand  instead  of  appearing  as
+          discrete  lines  against  the screen background colour.
+          Regrettably, this  representation  yields  slide  files
+          which  occupy  more  disc  space  and  take  longer  to
+          display.
+
+     -background colour
+          Most AutoCAD display drivers can be configured  to  use
+          any  available  colour  as the screen background.  Some
+          users perfer a black screen background,  others  white,
+          while splinter groups advocate burnt ocher, tawny puce,
+          and shocking grey.   Discarding  pixels  whose  closest
+          AutoCAD  colour  representation  is  equal to the back-
+          ground colour can substantially reduce the size of  the
+          AutoCAD  database  or  slide file needed to represent a
+          bitmap.  If no -background  colour  is  specified,  the
+          screen  background  colour is assumed to be black.  Any
+          AutoCAD colour number may be specified  as  the  screen
+          background;  colour  numbers are assumed to specify the
+          hues  defined  in  the  standard  AutoCAD  256   colour
+          palette.
+
+     -white
+          Since many AutoCAD users choose a  white  screen  back-
+          ground, this option is provided as a short-cut.  Speci-
+          fying -white is identical in effect to -background 7.
+
+     -aspect ratio
+          If the source pixmap had non-square pixels,  the  ratio
+          of  the pixel width to pixel height should be specified
+          as ratio.  The resulting slide or  .dxb  file  will  be
+          corrected  so that pixels on the AutoCAD screen will be
+          square.  For example, to correct an image  made  for  a
+          320x200 VGA/MCGA screen, specify -aspect 0.8333.
+
+     -8   Restricts the colours in the output file to the  8  RGB
+          shades.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 Bugs
+     AutoCAD has a fixed  palette  of  256  colours,  distributed
+     along  the  hue,  lightness,  and  saturation axes.  Pixmaps
+     which contain many nearly-identical colours, or colours  not
+     closely  approximated  by  AutoCAD's  palette, may be poorly
+     rendered.
+
+     ppmtoacad works best if the  system  displaying  its  output
+     supports the full 256 colour AutoCAD palette.  Monochrome, 8
+     colour, and 16 colour configurations will produce less  than
+     optimal results.
+
+     When creating a .dxb file or a slide  file  with  the  -poly
+     option, ppmtoacad finds both vertical and horizontal runs of
+     identical pixels  and  consolidates  them  into  rectangular
+     regions  to  reduce  the  size  of the output file.  This is
+     effective for images with large areas of constant colour but
+     it's no substitute for true raster to vector conversion.  In
+     particular, thin diagonal lines are not optimised at all  by
+     this process.
+
+     Output files can be huge.
+
+2 See_Also
+     AutoCAD Reference Manual: Slide File Format and Binary Draw-
+     ing Interchange (DXB) Files, ppm
+
+2 Author
+          John Walker
+          Autodesk SA
+          Avenue des Champs-Montants 14b
+          CH-2074 MARIN
+          Suisse/Schweiz/Svizzera/Svizra/Switzerland
+          Usenet:  kelvin@Autodesk.com
+          Fax:     038/33 88 15
+          Voice:   038/33 76 33
+
+     Permission  to  use,  copy,  modify,  and  distribute   this
+     software  and  its documentation for any purpose and without
+     fee is hereby granted, without any  conditions  or  restric-
+     tions.   This software is provided ``as is'' without express
+     or implied warranty.
+
+     AutoCAD and Autodesk are registered trademarks of  Autodesk,
+     Inc.
+
+1 ppmtobmp
+     ppmtobmp - convert a portable pixmap into a BMP file
+
+2 Synopsis
+     ppmtobmp [-windows] [-os2] [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Produces a Microsoft Win-
+     dows or OS/2 BMP file as output.
+
+2 Options
+     -windows
+          Tells the program to produce a  Microsoft  Windows  BMP
+          file.
+
+     -os2 Tells the program to produce an OS/2 BMP  file.   (This
+          is the default.)
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     bmptoppm, ppm
+
+2 Author
+     Copyright (C) 1992 by David W. Sanderson.
+
+1 ppmtogif
+     ppmtogif - convert a portable pixmap into a GIF file
+
+2 Synopsis
+     ppmtogif [-interlace] [-sort] [-map mapfile ] [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Produces a  GIF  file  as
+     output.
+
+2 Options
+     -interlace
+          Tells the program to produce an interlaced GIF file.
+
+     -sort
+          Produces a GIF file with a sorted color map.
+
+     -map mapfile
+
+          Uses the colors found in  the  mapfile  to  create  the
+          colormap  in  the  GIF file, instead of the colors from
+          ppmfile. The mapfile can be  any  ppm  file;  all  that
+          matters  is  the colors in it. If the colors in ppmfile
+          do not match those in mapfile , they are matched  to  a
+          "best match". A (much) better result can be obtained by
+          using the following filter in advance:
+
+          ppmquant -floyd -map mapfile
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     giftopnm, ppmquant, ppm
+
+2 Author
+     Based      on      GIFENCOD      by       David       Rowley
+     <mgardi@watdcsu.waterloo.edu>.  Lempel-Ziv compression based
+     on "compress".
+
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 ppmtoicr
+     ppmtoicr - convert a portable pixmap into NCSA ICR format
+
+2 Synopsis
+     ppmtoicr  [-windowname  name]  [-expand  expand]   [-display
+     display] [-rle] [ppmfile]
+
+2 Description
+     Reads a portable pixmap file as  input.   Produces  an  NCSA
+     Telnet  Interactive Color Raster graphic file as output.  If
+     ppmfile is not supplied, ppmtoicr will  read  from  standard
+     input.
+
+     Interactive Color Raster (ICR) is a protocol for  displaying
+     raster  graphics  on  workstation  screens.  The protocol is
+     implemented in NCSA Telnet for the  Macintosh  version  2.3.
+     The  ICR  protocol  shares  characteristics of the Tektronix
+     graphics terminal emulation protocol.  For  example,  escape
+     sequences are used to control the display.
+
+     ppmtoicr will output the appropriate sequences to  create  a
+     window  of  the  dimensions  of  the  input pixmap, create a
+     colormap of up to 256 colors on the display, then  load  the
+     picture data into the window.
+
+     Note that there is no icrtoppm tool - this transformation is
+     one way.
+
+2 Options
+     -windownamename
+                   Output will be displayed in name  (Default  is
+                   to use ppmfile or "untitled" if standard input
+                   is read.)
+
+     -expandexpand Output will be expanded on display  by  factor
+                   expand  (For  example, a value of 2 will cause
+                   four pixels to be displayed  for  every  input
+                   pixel.)
+
+     -displaydisplay
+                   Output will be displayed  on  screen  numbered
+                   display
+
+     -rle          Use run-length  encoded  format  for  display.
+                   (This  will  nearly always result in a quicker
+                   display, but may skew the colormap.)
+
+2 Examples
+     To display a ppm file using the protocol:
+         ppmtoicr ppmfile
+     This will create a window named ppmfile on the display  with
+     the  correct  dimensions  for ppmfile, create and download a
+     colormap of up to 256 colors, and download the picture  into
+     the window. The same effect may be achieved by the following
+     sequence:
+         ppmtoicr ppmfile > filename
+         cat filename
+     To display a GIF file using the protocol in a window  titled
+     after  the  input file, zoom the displayed image by a factor
+     of 2, and run-length encode the data:
+         giftopnm giffile | ppmtoicr -w giffile -r -e 2
+
+2 Bugs
+     The protocol uses frequent fflush calls to speed up display.
+     If  the output is saved to a file for later display via cat,
+     drawing will be much slower. In either case, increasing  the
+     Blocksize  limit  on  the display will speed up transmission
+     substantially.
+
+2 See_Also
+     ppm
+
+     NCSA Telnet for the Macintosh,  University  of  Illinois  at
+     Urbana-Champaign (1989)
+
+2 Author
+     Copyright     (C)      1990      by      Kanthan      Pillay
+     (svpillay@Princeton.EDU), Princeton University Computing and
+     Information Technology.
+
+1 ppmtoilbm
+     ppmtoilbm - convert a portable pixmap into an ILBM file
+
+2 Synopsis
+     ppmtoilbm [-maxplanes|-mp  N]  [-fixplanes|-fp  N]  [-ham6|-
+     ham8]   [-dcbits|-dcplanesrg   [-normal|-hamif|-hamforce   -
+     dcif|-dcforce|-cmaponly] [-ecs|-aga] [-mapppmfile] [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Produces an ILBM file  as
+     output.  Supported ILBM types are:
+
+     Normal ILBMs with 1-16 planes.
+
+     Amiga Hold-and-modify (HAM) with 3-16 planes.
+
+     24 bit.
+
+     Color map (BMHD + CMAP chunk only, nPlanes = 0).
+
+     Unofficial direct color.
+          1-16 planes for each color component.
+
+     Chunks written:
+          BMHD, CMAP, CAMG (only for HAM), BODY (not for colormap
+          files) unofficial DCOL chunk for direct color ILBM
+
+2 Options
+     Options marked with (*) can be prefixed with  a  "no",  e.g.
+     "-nohamif". All options can be abbreviated to their shortest
+     unique prefix.
+
+     -maxplanes | -mp n
+          (default 5, minimum 1, maximum 16)  Maximum  planes  to
+          write  in  a  normal  ILBM.  If the pixmap does not fit
+          into <n> planes, ppmtoilbm writes a HAM file (if -hamif
+          is  used),  a 24bit file (if -24if is used) or a direct
+          color file (if -dcif is used) or aborts with an error.
+
+     -fixplanes | -fp n
+          (min 1, max 16) If a normal ILBM is  written,  it  will
+          have exactly <n> planes.
+
+     -hambits | -hamplanes n
+          (default 6, min 3, max 16) Select number of planes  for
+          HAM picture.  The current Amiga hardware supports 6 and
+          8 planes, so for now you should only use this values.
+
+     -normal (default)
+          Turns  off   -hamif/-24if/-dcif,   -hamforce/-24force/-
+          dcforce and -cmaponly.
+
+     -hamif (*)
+
+     -24if (*)
+
+     -dcif (*)
+          Write a HAM/24bit/direct color file if the pixmap  does
+          not fit into <maxplanes> planes.
+
+     -hamforce (*)
+
+     -24force (*)
+
+     -dcforce (*)
+          Write a HAM/24bit/direct color file.
+
+     -dcbits | -dcplanes r g b
+          (default 5, min 1, max 16).  Select number of bits  for
+          red, green & blue in a direct color ILBM.
+
+     -ecs (default)
+          Shortcut for: -hamplanes 6 -maxplanes 5
+
+     -aga
+
+     Shortcut for: -hamplanes 8 -maxplanes 8
+
+     -ham6
+
+     Shortcut for: -hamplanes 6 -hamforce
+
+     -ham8
+          Shortcut for: -hamplanes 8 -hamforce
+
+     -map ppmfile
+          Write a normal ILBM using the colors  in  <ppmfile>  as
+          the  colormap.  The  colormap  file also determines the
+          number of planes, a -maxplanes or -fixplanes option  is
+          ignored.
+
+     -cmaponly
+          Write a colormap file: only BMHD and  CMAP  chunks,  no
+          BODY chunk, nPlanes = 0.
+
+2 Bugs
+     Needs a real colormap selection algorithm for HAM  pictures,
+     instead of using a grayscale colormap.
+
+2 References
+     Amiga ROM Kernel Reference Manual - Devices (3rd Ed.)
+     Addison Wesley, ISBN 0-201-56775-X
+
+2 See_Also
+     ppm(5), ilbmtoppm(1)
+
+2 Authors
+     Copyright (C) 1989 by Jef Poskanzer.
+     Modified August 1993 by Ingo Wilken
+    (Ingo.Wilken@informatik.uni-oldenburg.de)
+
+1 ppmtomitsu
+     ppmtomitsu - convert a portable pixmap to a Mitsubishi S340-10 file
+
+2 Synopsis
+     ppmtomitsu [-sharpness val] [-enlarge val] [-media string]
+                [-copy val] [-dpi300] [-tiny] [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input and  converts  it  into  a  format
+     suitable  to  be printed by a Mitsubishi S340-10  printer,  or  any
+     other Mitsubishi color sublimation printer.
+
+     The  Mitsubishi  S340-10  Color  Sublimation printer supports 24bit
+     color.  Images of the available sizes take so long to transfer that
+     there is a  fast  method,  employing a lookuptable, that ppmtomitsu
+     will use if there  is  a  maximum  of  256  colors  in  the pixmap.
+     ppmtomitsu will try to position  your  image  to  the center of the
+     paper, and will rotate your image  for  you if xsize is larger than
+     ysize.  If your image is larger  than  the media allows, ppmtomitsu
+     will quit with an error message.  (We  decided  that the media were
+     too expensive to have careless users produce misprints.) Once  data
+     transmission  has started, the job can't be stopped in a  sane  way
+     without resetting the  printer.    The  printer understands putting
+     together images in the printers memory;  ppmtomitsu doesn't utilize
+     this as pnmcat etc provide  the same functionality and let you view
+     the  result  on-screen, too.  The  S340-10  is  the  lowest  common
+     denominator  printer;  for higher resolution printers  there's  the
+     dpi300 option.  The other printers also support  higher  values for
+     enlarge eg., but I don't think that's essential enough to warrant a
+     change in the program.
+
+     -sharpness 1-4
+          'sharpness' designation.    Default  is  to  use  the  current
+          sharpness.
+
+     -enlarge 1-3
+          Enlarge by a factor; Default is 1 (no enlarge)
+
+     -media A, A4, AS, A4S
+          Designate the media  you're  using.    Default is 1184 x 1350,
+          which will fit on any media.  A  is  1216 x 1350, A4 is 1184 x
+          1452, AS is 1216 x 1650 and A4S is 1184 x 1754.    A  warning:
+          If you specify a  different  media  than the printer currently
+          has, the printer will wait until  you put in the correct media
+          or switch it off.
+
+     -copy 1-9
+          The number of copies to produce. Default is 1.
+
+     -dpi300
+          Double the number of allowed pixels for  a S3600-30 Printer in
+          S340-10 compatibility mode.  (The S3600-30 has 300 dpi).
+
+     -tiny
+          Memory-safing, but always slow.  The printer will get the data
+          line-by-line in 24bit.  It's probably a good idea to  use this
+          if your machine starts paging a lot without this option.
+
+2 References
+     Mitsubishi Sublimation Full Color Printer S340-10 Specifications of
+     Parallel Interface LSP-F0232F
+
+2 See_Also
+     ppmquant(1), pnmscale(1), ppm(5)
+
+2 Bugs
+     We didn't find any - yet.  (Besides, they're called features anyway
+     :-) If you should find one, my email-adress is below.
+
+2 Author
+     Copyright (C) 1992, 93  by  S.Petra  Zeidler,  MPIfR Bonn, Germany.
+     (spz@specklec.mpifr-bonn.mpg.de)
+
+1 ppmtopcx
+     ppmtopcx - convert a portable pixmap into a PCX file
+
+2 Synopsis
+     ppmtopcx [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Produces a  PCX  file  as
+     output.
+
+2 See_Also
+     pcxtoppm, ppm
+
+2 Author
+     Copyright (C) 1990 by Michael Davidson.
+
+1 ppmtopgm
+     ppmtopgm - convert a portable pixmap into a portable graymap
+
+2 Synopsis
+     ppmtopgm [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Produces a portable gray-
+     map  as  output.   The quantization formula used is .299 r +
+     .587 g + .114 b.
+
+     Note that although there is a pgmtoppm program,  it  is  not
+     necessary  for  simple  conversions from pgm to ppm, because
+     any ppm program can read pgm (and pbm ) files automagically.
+     pgmtoppm  is for colorizing a pgm file.  Also, see ppmtorgb3
+     for a different way of converting color to gray.
+
+2 QUOTE
+     Cold-hearted orb that rules the night
+     Removes the colors from our sight
+     Red is gray, and yellow white
+     But we decide which is right
+     And which is a quantization error.
+
+2 See_Also
+     pgmtoppm, ppmtorgb3, rgb3toppm, ppm, pgm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 ppmtopi1
+     ppmtopi1 - convert a portable pixmap  into  an  Atari  Degas
+     .pi1 file
+
+2 Synopsis
+     ppmtopi1 [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Produces an  Atari  Degas
+     .pi1 file as output.
+
+2 See_Also
+     pi1toppm, ppm, pbmtopi3, pi3topbm
+
+2 Author
+     Copyright (C) 1991 by Steve Belczyk (seb3@gte.com)  and  Jef
+     Poskanzer.
+
+1 ppmtopict
+     ppmtopict - convert a portable pixmap into a Macintosh  PICT
+     file
+
+2 Synopsis
+     ppmtopict [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Produces a Macintosh PICT
+     file as output.
+
+     The generated file is only the data fork of a picture.   You
+     will  need  a program such as mcvert to generate a Macbinary
+     or a BinHex file that contains the necessary information  to
+     identify the file as a PICT file to MacOS.
+
+     Even though PICT supports 2 and 4 bits per pixel,  ppmtopict
+     always generates an 8 bits per pixel file.
+
+2 Bugs
+     The picture size field is only correct if the output is to a
+     file  since  writing  into this field requires seeking back-
+     wards on a file.  However the PICT  documentation  seems  to
+     suggest  that  this field is not critical anyway since it is
+     only the lower 16 bits of the picture size.
+
+2 See_Also
+     picttoppm, ppm, mcvert
+
+2 Author
+     Copyright (C) 1990 by Ken Yap <ken@cs.rocester.edu>.
+
+1 ppmtopj
+     ppmtopj - convert a portable pixmap to an HP PaintJet file
+
+2 Synopsis
+     ppmtopj  [-gamma  val]  [-xpos  val]  [-ypos   val]   [-back
+     dark|lite]         [-rle]         [-center]         [-render
+     none|snap|bw|dither|diffuse|monodither|monodiffuse|clusterdither|monoclusterdither]
+     [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input and converts it into a for-
+     mat suitable to be printed by an HP PaintJet printer.
+
+     For best results, the input file should be  in  8-color  RGB
+     form;  i.e. it should have only the 8 binary combinations of
+     full-on and full-off primaries.  You could get this by send-
+     ing  the  input  file  through ppmquant -map with a map file
+     such as:
+         P3
+         8 1
+         255
+         0 0 0      255 0 0    0 255 0    0 0 255
+         255 255 0  255 0 255  0 255 255  255 255 255
+     Or else you could use use ppmdither -red 2 -green 2 -blue
+
+2 Options
+     -rle          Run length encode the image.  (This can result
+                   in larger images)
+
+     -back         Enhance the foreground by  indicating  if  the
+                   background  is  light  or dark compated to the
+                   foreground.
+
+     -render alg   Use an internal rendering  algorithm  (default
+                   dither).
+
+     -gamma int    Gamma correct  the  image  using  the  integet
+                   parameter as a gamma (default 0).
+
+     -center       Center the image to an 8.5 by 11 page
+
+     -xpos pos     Move by pos pixels in the x direction.
+
+     -ypos pos     Move by pos pixels in the y direction.
+
+2 References
+     HP PaintJet XL Color Graphics Printer User's Guide
+
+2 See_Also
+     pnmdepth, ppmquant, ppmdither, ppm
+
+2 Bugs
+     Most of the options have not  been  tested  because  of  the
+     price of the paper.
+
+2 Author
+     Copyright (C) 1991 by Christos Zoulas.
+
+1 ppmtopuzz
+     ppmtopuzz - convert a portable pixmap into an  X11  "puzzle"
+     file
+
+2 Synopsis
+     ppmtopuzz [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Produces an X11  "puzzle"
+     file  as output.  A "puzzle" file is for use with the puzzle
+     program included with the  X11  distribution  -  puzzle's  -
+     picture flag lets you specify an image file.
+
+2 See_Also
+     ppm, puzzle
+
+2 Author
+     Copyright (C) 1991 by Jef Poskanzer.
+
+1 ppmtorgb3
+     ppmtorgb3 - separate a portable pixmap into  three  portable
+     graymaps
+
+2 Synopsis
+     ppmtorgb3 [ppmfile]
+
+2 Description
+     Reads a portable pixmap as  input.   Writes  three  portable
+     graymaps as output, one each for red, green, and blue.
+
+     The output filenames are constructed  by  taking  the  input
+     filename, stripping off any extension, and appending ".red",
+     ".grn", and ".blu".  For example, separating lenna.ppm would
+     result in lenna.red, lenna.grn, and lenna.blu.  If the input
+     comes from stdin, the names are noname.red, noname.grn,  and
+     noname.blu.
+
+2 See_Also
+     rgb3toppm, ppmtopgm, pgmtoppm, ppm, pgm
+
+2 Author
+     Copyright (C) 1991 by Jef Poskanzer.
+
+1 ppmtosixel
+     ppmtosixel - convert a portable pixmap into DEC sixel format
+
+2 Synopsis
+     ppmtosixel [-raw] [-margin] [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Produces  sixel  commands
+     (SIX)  as  output.  The output is formatted for color print-
+     ing, e.g. for a DEC LJ250 color inkjet printer.
+
+     If RGB values from the PPM file do not have maxval=100,  the
+     RGB  values  are  rescaled.   A printer control header and a
+     color assignment table begin the SIX file.   Image  data  is
+     written  in  a compressed format by default.  A printer con-
+     trol footer ends the image file.
+
+2 Options
+     -raw If specified, each pixel will be  explicitly  described
+          in  the  image  file.  If -raw is not specified, output
+          will default to compressed format  in  which  identical
+          adjacent  pixels  are  replaced  by "repeat pixel" com-
+          mands.  A raw file  is  often  an  order  of  magnitude
+          larger than a compressed file and prints much slower.
+
+     -margin
+          If -margin is not specified, the image will be start at
+          the  left  margin  (of the window, paper, or whatever).
+          If -margin is specified, a 1.5 inch  left  margin  will
+          offset the image.
+
+2 Printing
+     Generally, sixel files must reach  the  printer  unfiltered.
+     Use the lpr -x option or cat filename > /dev/tty0?.
+
+2 Bugs
+     Upon rescaling, truncation of the least significant bits  of
+     RGB values may result in poor color conversion.  If the ori-
+     ginal PPM  maxval  was  greater  than  100,  rescaling  also
+     reduces  the  image depth.  While the actual RGB values from
+     the ppm file are more or less retained, the color palette of
+     the  LJ250  may  not  match the colors on your screen.  This
+     seems to be a printer limitation.
+
+2 See_Also
+     ppm
+
+2 Author
+     Copyright (C) 1991 by Rick Vinci.
+
+1 ppmtotga
+     ppmtotga - convert portable pixmap into a  TrueVision  Targa
+     file
+
+2 Synopsis
+     ppmtotga [-mono|-cmap|-rgb] [-norle] [ppmfile]
+
+2 Description
+     Reads a portable pixmap as  input.   Produces  a  TrueVision
+     Targa file as output.
+
+2 Options
+     -mono
+          Forces Targa file to  be  of  type  8  bit  monochrome.
+          Input must be a portable bitmap or a portable graymap.
+
+     -cmap
+          Forces Targa file to be of  type  24  bit  colormapped.
+          Input  must be a portable bitmap, a portable graymap or
+          a portable pixmap containing no more than 256  distinct
+          colors.
+
+     -rgb Forces Targa file to be of type 24 bit unmapped color.
+
+     -norle
+          Disables run-length encoding, in case you have a  Targa
+          reader which can't read run-length encoded files.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.   If  no  file  type  is specified the most highly con-
+     stained compatible type is used, where  monochrome  is  more
+     constained than colormapped which is in turn more constained
+     than unmapped.
+
+2 Bugs
+     Does not support all  possible  Targa  file  types.   Should
+     really be in PNM, not PPM.
+
+2 See_Also
+     tgatoppm, ppm
+
+2 Author
+     Copyright (C) 1989, 1991 by Mark Shand and Jef Poskanzer.
+
+1 ppmtouil
+     ppmtouil - convert a portable pixmap into a Motif  UIL  icon
+     file
+
+2 Synopsis
+     ppmtouil [-name uilname] [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Produces a Motif UIL icon
+     file as output.
+
+     If the program was compiled with an rgb database  specified,
+     and  a RGB value from the ppm input matches a RGB value from
+     the database, then the corresponding color name mnemonic  is
+     printed  in the UIL's colormap.  If no rgb database was com-
+     piled in, or if the RGB values don't match, then  the  color
+     will  be  printed  with  the  #RGB,  #RRGGBB, #RRRGGGBBB, or
+     #RRRRGGGGBBBB hexadecimal format.
+
+2 Options
+     -name
+          Allows you  to  specify  the  prefix  string  which  is
+          printed in the resulting UIL output.  If not specified,
+          will default to the filename (without extension) of the
+          ppmfile  argument.   If  -name  is not specified and no
+          ppmfile is specified (i.e.  piped  input),  the  prefix
+          string will default to the string "noname".
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 See_Also
+     ppm
+
+2 Author
+     Converted by Jef Poskanzer from ppmtoxpm.c, which  is  Copy-
+     right (C) 1990 by Mark W. Snitily
+
+1 ppmtoxpm
+     ppmtoxpm - convert a portable pixmap into an X11 pixmap
+
+2 Synopsis
+     ppmtoxpm [-name xpmname] [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Produces X11 pixmap (XPM)
+     as output.
+
+     If the program was compiled with an rgb database  specified,
+     and  a RGB value from the ppm input matches a RGB value from
+     the database, then the corresponding color name mnemonic  is
+     printed  in the XPM's colormap.  If no rgb database was com-
+     piled in, or if the RGB values don't match, then  the  color
+     will  be  printed  with  the  #RGB,  #RRGGBB, #RRRGGGBBB, or
+     #RRRRGGGGBBBB hexadecimal format.
+
+2 Options
+     -name
+          Allows you  to  specify  the  prefix  string  which  is
+          printed in the resulting XPM output.  If not specified,
+          will default to the filename (without extension) of the
+          ppmfile  argument.   If  -name  is not specified and no
+          ppmfile is specified (i.e.  piped  input),  the  prefix
+          string will default to the string "noname".
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 Example
+     To     convert     the     file     "dot"     (found      in
+     /usr/include/X11/bitmaps), from xbm to xpm one could specify
+
+          xbmtopbm dot | ppmtoxpm -name dot
+
+2 Bugs
+     An option to match the closest  (rather  than  exact)  color
+     name  mnemonic  from  the  rgb  text  would  be  a desirable
+     enhancement.
+
+     Truncation of the least significant bits of a RGB value  may
+     result  in  nonexact  matches  when  performing  color  name
+     mnemonic lookups.
+
+2 See_Also
+     xpmtoppm, ppm
+
+2 Author
+     Copyright (C) 1990 by Mark W. Snitily.
+
+1 ppmtoyuv
+     ppmtoyuv - convert a portable pixmap into an Abekas YUV file
+
+2 Synopsis
+     ppmtoyuv [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Produces  an  Abekas  YUV
+     file as output.
+
+2 See_Also
+     yuvtoppm, ppm
+
+2 Author
+     Marc Boucher <marc@PostImage.COM>, based on Example  Conver-
+     sion  Program,  A60/A64 Digital Video Interface Manual, page
+     69.
+
+     Copyright (C) 1991 by DHD PostImage Inc.
+
+     Copyright (C) 1987 by Abekas Video Systems Inc.
+
+1 ppmtoyuvsplit
+     ppmtoyuvsplit - convert a portable pixmap into 3  subsampled
+     raw YUV files
+
+2 Synopsis
+     ppmtoyuvsplit basename [ppmfile]
+
+2 Description
+     Reads a portable pixmap as  input.   Produces  3  raw  files
+     basename.Y,  basename.U  and  basename.V  as  output.  These
+     files are the subsampled raw YUV representation of the input
+     pixmap,  as required by the Stanford MPEG codec. The subsam-
+     pling is done by arithmetic mean of  4  pixels  colors  into
+     one.  The  YUV  values  are scaled according to CCIR.601, as
+     assumed by MPEG.
+
+2 See_Also
+     mpeg, ppm
+
+2 Author
+     Copyright (C) 1993 by  Andre  Beck.  (Andreeck@IRS.Inf.TU-
+     Dresden.de)
+
+     Based on ppmtoyuv.c
+
+1 qrttoppm
+     qrttoppm - convert output from the QRT  ray  tracer  into  a
+     portable pixmap
+
+2 Synopsis
+     qrttoppm [qrtfile]
+
+2 Description
+     Reads a QRT file as input.  Produces a  portable  pixmap  as
+     output.
+
+2 See_Also
+     ppm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 rawtoppm
+     rawtoppm - convert raw RGB bytes into a portable pixmap
+
+2 Synopsis
+     rawtoppm [-headerskip N]  [-rowskip  N]  [-rgb|-rbg|-grb  |-
+     gbr|-brg|-bgr ] [-interpixel|-interrow] width height [image-
+     data]
+
+2 Description
+     Reads raw RGB bytes as input.  Produces a portable pixmap as
+     output.   The  input  file  is  just RGB bytes.  You have to
+     specify the width and height on the command line, since  the
+     program  obviously can't get them from the file.  The maxval
+     is assumed to be 255.  If  the  resulting  image  is  upside
+     down, run it through pnmflip -tb .
+
+2 Options
+     -headerskip
+          If the file has a header, you can use this flag to skip
+          over it.
+
+     -rowskip
+          If there is padding at the ends of the  rows,  you  can
+          skip it with this flag.
+
+     -rgb -rbg -grb -gbr -brg -bgr
+          These flags let you  specify  alternate  color  orders.
+          The default is -rgb.
+
+     -interpixel -interrow
+          These flags let you specify how the colors  are  inter-
+          leaved.   The  default  is  -interpixel, meaning inter-
+          leaved by pixel.  A byte of red, a byte of green, and a
+          byte  of  blue,  or whatever color order you specified.
+          -interrow means interleaved by row - a row  of  red,  a
+          row  of  green,  a  row  of blue, assuming standard rgb
+          color order.  An -interplane flag  - all the  red  pix-
+          els,  then  all the green, then all the blue - would be
+          an obvious extension,  but  is  not  implemented.   You
+          could  get  the  same effect by splitting the file into
+          three parts (perhaps using dd), turning each part  into
+          a  PGM file with rawtopgm, and then combining them with
+          rgb3toppm.
+
+2 See_Also
+     ppm, rawtopgm, rgb3toppm, pnmflip
+
+2 Author
+     Copyright (C) 1991 by Jef Poskanzer.
+
+1 rgb3toppm
+     rgb3toppm - combine three portable graymaps into  one  port-
+     able pixmap
+
+2 Synopsis
+     rgb3toppm redpgmfile greenpgmfile bluepgmfile
+
+2 Description
+     Reads three portable graymaps as input.  Combines  them  and
+     produces one portable pixmap as output.
+
+2 See_Also
+     ppmtorgb3, pgmtoppm, ppmtopgm, ppm, pgm
+
+2 Author
+     Copyright (C) 1991 by Jef Poskanzer.
+
+1 sldtoppm
+     sldtoppm - convert an AutoCAD slide  file  into  a  portable
+     pixmap
+
+2 Synopsis
+     sldtoppm [-adjust] [-dir] [-height|-ysize s] [-info] [-
+              lib|-Lib name] [-scale s] [-verbose] [-width|-xsize
+              s] [slidefile]
+
+2 Description
+     Reads an AutoCAD(Reg.) slide file  and  outputs  a  portable
+     pixmap.   If  no  slidefile is specified, input is read from
+     standard input.  The ppmdraw library is used to convert  the
+     vector  and  polygon information in the slide file to a pix-
+     map; see the file ppmdraw.h for details on this package.
+
+2 Options
+     -adjust
+          If the display on which the slide file was created  had
+          non-square  pixels,  when  the  slide is processed with
+          sldtoppm and the -adjust option  is  not  present,  the
+          following warning will appear:
+            Warning - pixels on source screen were non-square.
+            Specifying -adjust will correct image width  to  com-
+            pensate.
+          Specifying the -adjust option causes sldtoppm to  scale
+          the  width of the image so that pixels in the resulting
+          portable pixmap are square (and hence circles appear as
+          true  circles, not ellipses).  The scaling is performed
+          in  the  vector  domain,  before  scan  converting  the
+          objects.   The  results  are,  therefore,  superior  in
+          appearance to what you'd obtain were you to perform the
+          equivalent  scaling  with pnmscale after the bitmap had
+          been created.
+
+     -dir The input is assumed to be  an  AutoCAD  slide  library
+          file.  A directory listing each slide in the library is
+          printed on standard error.
+
+     -height size
+          Scales the image in the vector domain  so  it  is  size
+          pixels  in  height.   If  no -width or -xsize option is
+          specified, the width will be adjusted to  preserve  the
+          pixel aspect ratio.
+
+     -info
+          Dump the slide file header on standard error,  display-
+          ing  the  original  screen  size and aspect ratio among
+          other information.
+
+     -lib name
+          Extracts the slide with the given name from  the  slide
+          library  given  as  input.   The specified name is con-
+          verted to upper case.
+
+     -Lib name
+          Extracts the slide with the given name from  the  slide
+          library  given  as  input.  The name is used exactly as
+          specified; it is not converted to upper case.
+
+     -scale s
+          Scales the image by factor s, which may be any floating
+          point  value  greater than zero.  Scaling is done after
+          aspect ratio adjustment, if any.  Since scaling is per-
+          formed  in the vector domain, before rasterisation, the
+          results look much better than  running  the  output  of
+          sldtoppm through pnmscale.
+
+     -verbose
+          Dumps the slide file header and lists every vector  and
+          polygon in the file on standard error.
+
+     -width size
+          Scales the image in the vector domain  so  it  is  size
+          pixels  wide.  If no -height or -ysize option is speci-
+          fied, the height will be adjusted to preserve the pixel
+          aspect ratio.
+
+     -xsize size
+          Scales the image in the vector domain  so  it  is  size
+          pixels  wide.  If no -height or -ysize option is speci-
+          fied, the height will be adjusted to preserve the pixel
+          aspect ratio.
+
+     -ysize size
+          Scales the image in the vector domain  so  it  is  size
+          pixels  in  height.   If  no -width or -xsize option is
+          specified, the width will be adjusted to  preserve  the
+          pixel aspect ratio.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 Bugs
+     Only Level 2 slides are converted.  Level 1 format has  been
+     obsolete  since the advent of AutoCAD Release 9 in 1987, and
+     was not portable across machine architectures.
+
+     Slide library items with names containing  8  bit  (such  as
+     ISO)  or  16  bit (Kanji, for example) characters may not be
+     found when chosen with the -lib option unless  sldtoppm  has
+     been built with character set conversion functions appropri-
+     ate to the locale.  You  can  always  retrieve  slides  from
+     libraries  regardless of the character set by using the -Lib
+     option and specifying the precise name  of  library  member.
+     Use  the  -dir  option  to  list  the slides in a library if
+     you're unsure of the exact name.
+
+2 See_Also
+     AutoCAD Reference Manual: Slide  File  Format,  pnmscale,
+     ppm
+
+2 Author
+          John Walker
+          Autodesk SA
+          Avenue des Champs-Montants 14b
+          CH-2074 MARIN
+          Suisse/Schweiz/Svizzera/Svizra/Switzerland
+          Usenet:  kelvin@Autodesk.com
+          Fax:     038/33 88 15
+          Voice:   038/33 76 33
+
+     Permission  to  use,  copy,  modify,  and  distribute   this
+     software  and  its documentation for any purpose and without
+     fee is hereby granted, without any  conditions  or  restric-
+     tions.   This software is provided ``as is'' without express
+     or implied warranty.
+
+     AutoCAD and Autodesk are registered trademarks of  Autodesk,
+     Inc.
+
+1 spctoppm
+     spctoppm - convert an Atari compressed Spectrum file into  a
+     portable pixmap
+
+2 Synopsis
+     spctoppm [spcfile]
+
+2 Description
+     Reads an Atari compressed Spectrum file as input.   Produces
+     a portable pixmap as output.
+
+2 See_Also
+     sputoppm, ppm
+
+2 Author
+     Copyright (C) 1991 by Steve Belczyk (seb3@gte.com)  and  Jef
+     Poskanzer.
+
+1 sputoppm
+     sputoppm - convert an Atari uncompressed Spectrum file  into
+     a portable pixmap
+
+2 Synopsis
+     sputoppm [spufile]
+
+2 Description
+     Reads an Atari uncompressed Spectrum file  as  input.   Pro-
+     duces a portable pixmap as output.
+
+2 See_Also
+     spctoppm, ppm
+
+2 Author
+     Copyright (C) 1991 by Steve Belczyk (seb3@gte.com)  and  Jef
+     Poskanzer.
+
+1 tgatoppm
+     tgatoppm - convert TrueVision Targa  file  into  a  portable
+     pixmap
+
+2 Synopsis
+     tgatoppm [-debug] [tgafile]
+
+2 Description
+     Reads a TrueVision Targa file as input.  Produces a portable
+     pixmap as output.
+
+2 Options
+     -debug
+          Causes the header information to be dumped to stderr.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.  Should really be in PNM, not PPM.
+
+2 See_Also
+     ppmtotga, ppm
+
+2 Author
+     Partially based on tga2rast, version 1.0,  by  Ian  J.  Mac-
+     Phedran.
+
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 ximtoppm
+     ximtoppm - convert an Xim file into a portable pixmap
+
+2 Synopsis
+     ximtoppm [ximfile]
+
+2 Description
+     Reads an Xim file as input.  Produces a portable  pixmap  as
+     output.   The Xim toolkit is included in the contrib tree of
+     the X.V11R4 release.
+
+2 See_Also
+     ppm
+
+2 Author
+     Copyright (C) 1991 by Jef Poskanzer.
+
+1 xpmtoppm
+     xpmtoppm - convert an X11 pixmap into a portable pixmap
+
+2 Synopsis
+     xpmtoppm [xpmfile]
+
+2 Description
+     Reads an X11 pixmap (XPM) as  input.   Produces  a  portable
+     pixmap as output.
+
+2 See_Also
+     ppmtoxpm, ppm
+
+2 Author
+     Copyright (C) 1991 by Jef Poskanzer.
+
+1 yuvtoppm
+     yuvtoppm - convert Abekas YUV bytes into a portable pixmap
+
+2 Synopsis
+     yuvtoppm width height [imagedata]
+
+2 Description
+     Reads raw Abekas YUV bytes as input.   Produces  a  portable
+     pixmap  as  output.   The input file is just YUV bytes.  You
+     have to specify the width and height on  the  command  line,
+     since  the  program  obviously can't get them from the file.
+     The maxval is assumed to be 255.
+
+2 See_Also
+     ppmtoyuv, ppm
+
+2 Author
+     Marc Boucher <marc@PostImage.COM>, based on Example  Conver-
+     sion  Program,  A60/A64 Digital Video Interface Manual, page
+     69.
+
+     Copyright (C) 1991 by DHD PostImage Inc.
+
+     Copyright (C) 1987 by Abekas Video Systems Inc.
+
+1 yuvsplittoppm
+     yuvplittoppm - convert a Y- an U- and a V-file into a  port-
+     able pixmap.
+
+2 Synopsis
+     yuvsplittoppm basename width height [-ccir601]
+
+2 Description
+     Reads three files, containing the YUV components, as  input.
+     These  files  are  basename .Y,  basename.U and basename.V .
+     Produces a portable pixmap on stdout.
+
+     Since the YUV files are raw files, the dimensions width  and
+     height must be specified on the command line.
+
+2 Options
+     -ccir601
+          Assumes that the  YUV  triplets  are  scaled  into  the
+          smaller  range  of  the CCIR 601 (MPEG) standard. Else,
+          the JFIF (JPEG) standard is assumed.
+
+2 See_Also
+     ppmtoyuvsplit, yuvtoppm, ppm
+
+2 Author
+     Marcel    Wijkstra    <wijkstra@fwi.uva.nl>,    based     on
+     ppmtoyuvsplit.
+
+1 ppmforge
+     ppmforge - fractal forgeries of clouds, planets, and  starry
+     skies
+
+2 Synopsis
+     ppmforge [-clouds] [-night] [-dimension dimen] [-hour hour]
+              [-inclination|-tilt angle] [-mesh size] [-power
+              factor] [-glaciers level] [-ice level] [-saturation
+              sat] [-seed seed] [-stars fraction] [-xsize|-width
+              width] [-ysize|-height height]
+
+2 Description
+     ppmforge generates three  kinds  of  ``random  fractal  for-
+     geries,'' the term coined by Richard F. Voss of the IBM Tho-
+     mas J. Watson Research Center for seemingly  realistic  pic-
+     tures  of natural objects generated by simple algorithms em-
+     bodying randomness and fractal self-similarity.   The  tech-
+     niques  used  by  ppmforge  are  essentially  those given by
+     Voss[1], particularly the technique  of  spectral  synthesis
+     explained in more detail by Dietmar Saupe[2].
+
+     The program generates two varieties of pictures: planets and
+     clouds, which are just different renderings of data generat-
+     ed in an identical manner, illustrating  the  unity  of  the
+     fractal  structure of these very different objects.  A third
+     type of picture, a starry sky, is synthesised directly  from
+     pseudorandom numbers.
+
+     The generation of planets or clouds begins with the prepara-
+     tion  of  an  array  of random data in the frequency domain.
+     The size of this array, the ``mesh size,'' can be  set  with
+     the -mesh option; the larger the mesh the more realistic the
+     pictures but the calculation time and memory requirement in-
+     creases  as the square of the mesh size.  The fractal dimen-
+     sion, which you can  specify  with  the  -dimension  option,
+     determines the roughness of the terrain on the planet or the
+     scale of detail in the clouds.  As the fractal dimension  is
+     increased, more high frequency components are added into the
+     random mesh.
+
+     Once the mesh  is  generated,  an  inverse  two  dimensional
+     Fourier  transform  is performed upon it.  This converts the
+     original random frequency domain data  into  spatial  ampli-
+     tudes.   We  scale  the real components that result from the
+     Fourier transform into numbers from 0 to 1  associated  with
+     each  point on the mesh.  You can further modify this number
+     by applying a ``power law scale'' to it with the -power  op-
+     tion.    Unity  scale leaves the numbers unmodified; a power
+     scale of 0.5 takes the square root of  the  numbers  in  the
+     mesh,  while  a power scale of 3 replaces the numbers in the
+     mesh with their cubes.  Power law scaling is best envisioned
+     by  thinking  of  the  data as representing the elevation of
+     terrain; powers less than 1 yield landscapes  with  vertical
+     scarps  that  look  like  glacially-carved  valleys;  powers
+     greater than one make  fairy-castle  spires  (which  require
+     large mesh sizes and high resolution for best results).
+
+     After these calculations, we have a array of  the  specified
+     size containing numbers that range from 0 to 1.  The pixmaps
+     are generated as follows:
+
+     Clouds    A colour map is created that ranges from pure blue
+               to white by increasing admixture (desaturation) of
+               blue  with  white.   Numbers  less  than  0.5  are
+               coloured  blue,  numbers  between  0.5 and 1.0 are
+               coloured with corresponding levels of white,  with
+               1.0 being pure white.
+
+     Planet    The mesh is projected onto a sphere.  Values  less
+               than  0.5  are treated as water and values between
+               0.5 and 1.0 as land.  The water areas are coloured
+               based  upon the water depth, and land based on its
+               elevation.  The random  depth  data  are  used  to
+               create  clouds over the oceans.  An atmosphere ap-
+               proximately like the  Earth's  is  simulated;  its
+               light  absorption  is  calculated to create a blue
+               cast around the limb of the  planet.   A  function
+               that  rises from 0 to 1 based on latitude is modu-
+               lated by the local elevation to generate polar ice
+               caps--high   altitude   terrain  carries  glaciers
+               farther from the pole.  Based on the  position  of
+               the  star  with  respect  to the observer, the ap-
+               parent colour of each pixel of the planet is  cal-
+               culated by ray-tracing from the star to the planet
+               to the observer and applying a lighting model that
+               sums  ambient  light  and  diffuse reflection (for
+               most planets ambient light is zero, as their  pri-
+               mary  star  is  the  only source of illumination).
+               Additional random data are used to generate  stars
+               around the planet.
+
+     Night     A sequence of pseudorandom numbers is used to gen-
+               erate stars with a user specified density.
+
+     Cloud pictures always contain 256 or fewer colours  and  may
+     be  displayed  on most colour mapped devices without further
+     processing.  Planet pictures often contain tens of thousands
+     of  colours  which  must  be  compressed  with  ppmquant  or
+     ppmdither before encoding in a colour mapped format.  If the
+     display  resolution is high enough, ppmdither generally pro-
+     duces better looking  planets.   ppmquant  tends  to  create
+     discrete colour bands, particularly in the oceans, which are
+     unrealistic and distracting.  The number of colours in star-
+     ry  sky pictures generated with the -night option depends on
+     the value specified for -saturation.  Small values limit the
+     colour  temperature distribution of the stars and reduce the
+     number of colours in the image.  If the -saturation  is  set
+     to  0,  none of the stars will be coloured and the resulting
+     image will never contain more than 256 colours.   Night  sky
+     pictures  with  many  different star colours often look best
+     when colour compressed by pnmdepth rather than  ppmquant  or
+     ppmdither.   Try  newmaxval  settings  of 63, 31, or 15 with
+     pnmdepth to reduce the number of colours in the  picture  to
+     256 or fewer.
+
+2 Options
+     -clouds   Generate clouds.  A pixmap of  fractal  clouds  is
+               generated.   Selecting clouds sets the default for
+               fractal dimension to 2.15 and power  scale  factor
+               to 0.75.
+
+     -dimension dimen
+               Sets the fractal dimension to the specified dimen,
+               which  may  be  any floating point value between 0
+               and 3.   Higher  fractal  dimensions  create  more
+               ``chaotic''  images,  which require higher resolu-
+               tion output and a larger FFT  mesh  size  to  look
+               good.   If  no dimension is specified, 2.4 is used
+               when generating planets and 2.15 for clouds.
+
+     -glaciers level
+               The floating point level setting controls the  ex-
+               tent  to which terrain elevation causes ice to ap-
+               pear at lower latitudes.   The  default  value  of
+               0.75  makes the polar caps extend toward the equa-
+               tor across high terrain and forms glaciers in  the
+               highest  mountains,  as  on  Earth.  Higher values
+               make ice sheets that cover more and  more  of  the
+               land  surface,  simulating planets in the midst of
+               an ice age.   Lower  values  tend  to  be  boring,
+               resulting in unrealistic geometrically-precise ice
+               cap boundaries.
+
+     -hour hour
+               When generating a planet,  hour  is  used  as  the
+               ``hour  angle  at  the central meridian.''  If you
+               specify -hour 12, for example, the planet will  be
+               fully  illuminated,  corresponding to high noon at
+               the longitude at the centre of  the  screen.   You
+               can specify any floating point value between 0 and
+               24 for hour, but values which place  most  of  the
+               planet in darkness (0 to 4 and 20 to 24) result in
+               crescents which, while pretty, don't give you many
+               illuminated  pixels  for  the  amount of computing
+               that's required.  If no -hour option is specified,
+               a random hour angle is chosen, biased so that only
+               25% of the images generated will be crescents.
+
+     -ice level
+               Sets the extent of the polar ice caps to the given
+               floating  point  level.   The default level of 0.4
+               produces ice caps similar to those of  the  Earth.
+               Smaller  values  reduce  the  amount of ice, while
+               larger -ice settings  create  more  prominent  ice
+               caps.   Sufficiently  large values, such as 100 or
+               more, in conjunction with  small  settings  for  -
+               glaciers (try 0.1) create ``ice balls'' like Euro-
+               pa.
+
+     -inclination|-tilt angle
+               The inclination angle of the planet with regard to
+               its primary star is set to angle, which can be any
+               floating point value from -90 to 90.  The inclina-
+               tion angle can be thought of as specifying, in de-
+               grees, the ``season'' the planet is presently  ex-
+               periencing  or,  more  precisely,  the latitude at
+               which the star transits the zenith at local  noon.
+               If  0,  the  planet  is  at  equinox;  the star is
+               directly overhead at the equator.  Positive values
+               represent summer in the northern hemisphere, nega-
+               tive values summer  in  the  southern  hemisphere.
+               The  Earth's  inclination  angle,  for example, is
+               about  23.5  at  the  June  solstice,  0  at   the
+               equinoxes in March and September, and -23.5 at the
+               December solstice.  If  no  inclination  angle  is
+               specified,  a  random value between -21.6 and 21.6
+               degrees is chosen.
+
+     -mesh size
+               A mesh of size by size will be used for  the  fast
+               Fourier  transform  (FFT).   Note  that memory re-
+               quirements and computation speed increase  as  the
+               square  of  size; if you double the mesh size, the
+               program will use four times  the  memory  and  run
+               four  times as long.  The default mesh is 256x256,
+               which produces reasonably  good  looking  pictures
+               while  using half a megabyte for the 256x256 array
+               of single precision complex  numbers  required  by
+               the  FFT.  On machines with limited memory capaci-
+               ty, you may have to reduce the mesh size to  avoid
+               running out of RAM.  Increasing the mesh size pro-
+               duces better looking pictures; the difference  be-
+               comes particularly noticeable when generating high
+               resolution images with relatively high fractal di-
+               mensions (between 2.2 and 3).
+
+     -night    A starry sky is generated.  The stars are  created
+               by the same algorithm used for the stars that sur-
+               round planet pictures, but the output consists ex-
+               clusively of stars.
+
+     -power factor
+               Sets the ``power factor'' used to scale elevations
+               synthesised  from  the FFT to factor, which can be
+               any floating point number greater than  zero.   If
+               no factor is specified a default of 1.2 is used if
+               a planet is being generated, or 0.75 if clouds are
+               selected by the -clouds option.  The result of the
+               FFT image  synthesis  is  an  array  of  elevation
+               values  between 0 and 1.  A non-unity power factor
+               exponentiates each  of  these  elevations  to  the
+               specified power.  For example, a power factor of 2
+               squares each value, while a power  factor  of  0.5
+               replaces  each  with  its square root.  (Note that
+               exponentiating  values  between  0  and  1  yields
+               values that remain within that range.)  Power fac-
+               tors less than 1 emphasise  large-scale  elevation
+               changes at the expense of small variations.  Power
+               factors greater than 1 increase the  roughness  of
+               the terrain and, like high fractal dimensions, may
+               require a  larger  FFT  mesh  size  and/or  higher
+               screen resolution to look good.
+
+     -saturation sat
+               Controls the degree of colour  saturation  of  the
+               stars that surround planet pictures and fill star-
+               ry skies created with the -night option.  The  de-
+               fault  value  of  125 creates stars which resemble
+               the sky as seen by the human eye from Earth's sur-
+               face.   Stars are dim; only the brightest activate
+               the cones in the human retina, causing  colour  to
+               be  perceived.   Higher  values of sat approximate
+               the appearance of stars from  Earth  orbit,  where
+               better  dark  adaptation,  absence of skyglow, and
+               the concentration of light from a given star  onto
+               a smaller area of the retina thanks to the lack of
+               atmospheric turbulence enhances the perception  of
+               colour.   Values greater than 250 create ``science
+               fiction'' skies that, while pretty, don't occur in
+               this universe.
+
+               Thanks to the inverse  square  law  combined  with
+               Nature's  love of mediocrity, there are many, many
+               dim stars for every bright one.   This  population
+               relationship  is accurately reflected in the skies
+               created by ppmforge.  Dim,  low  mass  stars  live
+               much longer than bright massive stars, consequent-
+               ly there are many reddish stars for every blue gi-
+               ant.   This relationship is preserved by ppmforge.
+               You can reverse the proportion, simulating the sky
+               as  seen  in  a  starburst galaxy, by specifying a
+               negative sat value.
+
+     -seed num Sets the seed for the random number  generator  to
+               the  integer  num.   The  seed used to create each
+               picture is displayed on  standard  output  (unless
+               suppressed with the -quiet option).  Pictures gen-
+               erated with the same seed will be  identical.   If
+               no  -seed is specified, a random seed derived from
+               the date and time will be chosen.   Specifying  an
+               explicit  seed  allows  you to re-render a picture
+               you particularly like at a  higher  resolution  or
+               with different viewing parameters.
+
+     -stars fraction
+               Specifies the percentage of pixels, in tenths of a
+               percent,  which  will appear as stars, either sur-
+               rounding a planet or filling the entire  frame  if
+               -night is specified.  The default fraction is 100.
+
+     -xsize|-width width
+               Sets the width of the  generated  image  to  width
+               pixels.   The default width is 256 pixels.  Images
+               must be at least as wide as they are  high;  if  a
+               width  less  than the height is specified, it will
+               be increased to equal the  height.   If  you  must
+               have  a long skinny pixmap, make a square one with
+               ppmforge, then use pnmcut to extract a portion  of
+               the shape and size you require.
+
+     -ysize|-height height
+               Sets the height of the generated image  to  height
+               pixels.  The default height is 256 pixels.  If the
+               height specified exceeds the width, the width will
+               be increased to equal the height.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 Bugs
+     The algorithms require the output pixmap to be at  least  as
+     wide  as  it  is high, and the width to be an even number of
+     pixels.  These constraints are enforced  by  increasing  the
+     size of the requested pixmap if necessary.
+
+     You may have to reduce the FFT mesh size on machines with 16
+     bit integers and segmented pointer architectures.
+
+2 See_Also
+     pnmcut, pnmdepth, ppmdither, ppmquant, ppm
+
+     [1]  Voss, Richard  F.,  ``Random  Fractal  Forgeries,''  in
+          Earnshaw  et.  al., Fundamental Algorithms for Computer
+          Graphics, Berlin: Springer-Verlag, 1985.
+
+     [2]  Peitgen, H.-O., and Saupe,  D.  eds.,  The  Science  Of
+          Fractal Images, New York: Springer Verlag, 1988.
+
+2 Author
+          John Walker
+          Autodesk SA
+          Avenue des Champs-Montants 14b
+          CH-2074 MARIN
+          Suisse/Schweiz/Svizzera/Svizra/Switzerland
+          Usenet:  kelvin@Autodesk.com
+          Fax:     038/33 88 15
+          Voice:   038/33 76 33
+
+     Permission  to  use,  copy,  modify,  and  distribute   this
+     software  and  its documentation for any purpose and without
+     fee is hereby granted, without any  conditions  or  restric-
+     tions.   This software is provided ``as is'' without express
+     or implied warranty.
+
+     PLUGWARE! If you like this kind of stuff, you may also enjoy
+     ``James Gleick's Chaos--The Software'' for MS-DOS, available
+     for $59.95 from your local software store or  directly  from
+     Autodesk,  Inc.,  Attn:  Science Series, 2320 Marinship Way,
+     Sausalito, CA 94965, USA.  Telephone: (800)  688-2344  toll-
+     free  or,  outside  the  U.S. (415) 332-2344 Ext 4886.  Fax:
+     (415) 289-4718.  ``Chaos--The  Software''  includes  a  more
+     comprehensive   fractal   forgery  generator  which  creates
+     three-dimensional landscapes as well as clouds and  planets,
+     plus five more modules which explore other aspects of Chaos.
+     The user guide of more than 200 pages includes an  introduc-
+     tion by James Gleick and detailed explanations by Rudy Ruck-
+     er of the mathematics and algorithms used by each program.
+
+1 ppmpat
+     ppmpat - make a pretty pixmap
+
+2 Synopsis
+     ppmpat   -gingham2|-g2|-gingham3|   -g3|-madras|-tartan|   -
+     poles|-squig|-camo| -anticamo width height
+
+2 Description
+     Produces a  portable  pixmap  of  the  specified  width  and
+     height, with a pattern in it.
+
+     This program is mainly to demonstrate  use  of  the  ppmdraw
+     routines,  a  simple  but powerful drawing library.  See the
+     ppmdraw.h include file for more info  on  using  these  rou-
+     tines.   Still,  some  of the patterns can be rather pretty.
+     If you have  a  color  workstation,  something  like  ppmpat
+     -squig  300  300 | ppmquant 128 should generate a nice back-
+     ground.
+
+2 Options
+     The different flags specify various different pattern types:
+
+     -gingham2
+          A gingham check pattern.  Can be tiled.
+
+     -gingham3
+          A slightly more complicated gingham.  Can be tiled.
+
+     -madras
+          A madras plaid.  Can be tiled.
+
+     -tartan
+          A tartan plaid.  Can be tiled.
+
+     -poles
+          Color gradients centered on randomly-placed poles.  May
+          need to be run through ppmquant.
+
+     -squig
+          Squiggley tubular pattern.  Can be tiled.  May need  to
+          be run through ppmquant.
+
+     -camo
+          Camouflage  pattern.   May  need  to  be  run   through
+          ppmquant.
+
+     -anticamo
+          Anti-camouflage pattern - like -camo, but  ultra-bright
+          colors.  May need to be run through ppmquant.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 References
+     Some of the patterns are from "Designer's Guide to Color  3"
+     by Jeanne Allen.
+
+2 See_Also
+     pnmtile, ppmquant, ppm
+
+2 Author
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 ppmqvga
+     ppmqvga - 8 plane quantization
+
+2 Synopsis
+     ppmqvga [ options ] [ input file ]
+
+2 Description
+     ppmqvga quantizes PPM  files  to  8  planes,  with  optional
+     Floyd-Steinberg  dithering.   Input  is  a PPM file from the
+     file named, or standard input of no file is provided.
+
+2 Options
+     -d dither. Apply Floyd-Steinberg dithering to the data
+
+     -q quiet. Produces no progress reporting,  and  no  terminal
+     output unless and error occurs.
+
+     -v verbose. Produces additional output describing the number
+     of  colors found, and some information on the resulting map-
+     ping. May be repeated to generate loads  of  internal  table
+     output, but generally only useful once.
+
+2 Examples
+     ppmqvga -d mymage.ppm | ppmtogif >mymage.gif
+
+     tgatoppm zombie.tga | ppmqvga | ppmtotif > zombie.tif
+
+2 See_Also
+     ppmquant
+
+2 Diagnostics
+     Error messages if problems, various levels of optional  pro-
+     gress reporting.
+
+2 Limitations
+     none known.
+
+2 Author
+     Original by Lyle Rains (lrains@netcom.com)  as  ppmq256  and
+     ppmq256fs  combined, documented, and enhanced by Bill David-
+     sen (davidsen@crd.ge.com)
+
+     Copyright 1991,1992 by Bill Davidsen, all  rights  reserved.
+     The  program  and documentation may be freely distributed by
+     anyone in source or binary format. Please clearly  note  any
+     changes.
+
+1 ppmtomap
+     ppmtomap - extract all colors from a portable pixmap
+
+2 Synopsis
+     ppmtomap [-sort] [-square] [ppmfile]
+
+2 Description
+     Reads a portable pixmap as input.  Produces a portable  pix-
+     map  as  output, representing a color map of the input file.
+     All N different colors found are put in an Nx1 portable pix-
+     map.   This  color  map  file  can  be used as a mapfile for
+     ppmquant or ppmtogif.
+
+2 Options
+     -sort
+          Produces a portable pixmap  with  the  colors  in  some
+          sorted order.
+
+     -square
+          Produces a (more or less) square output  file,  instead
+          of putting all colors on the top row.
+
+     All flags can be abbreviated to their shortest  unique  pre-
+     fix.
+
+2 WARNING
+     If you want to use the output file as a mapfile  for  ppmto-
+     gif,  you first have to do a ppmquant 256, since ppmtomap is
+     not limited to 256 colors (but to 65536).
+
+2 See_Also
+     ppmtogif, ppmquant, ppm
+
+2 Author
+     Marcel Wijkstra (wijkstra@fwi.uva.nl).
+
+     Copyright (C) 1989 by Jef Poskanzer.
+
+1 ppmtopjxl
+     ppmtopjxl - convert a portable pixmap into an HP PaintJet XL
+     PCL file
+
+2 Synopsis
+     ppmtopjxl [-nopack] [-gamma <n>  ]  [-presentation]  [-dark]
+     [-diffuse]  [-cluster] [-dither] [-xshift <s> ] [-yshift <s>
+     ] [-xshift <s> ] [-yshift <s> ] [-xsize|-width|-xscale <s> ]
+     [-ysize|-height|-yscale <s> ] [ppmfile]
+
+
+2 Description
+     Reads a portable pixmap as input.  Produces a PCL file suit-
+     able for printing on an HP PaintJet XL printer as output.
+
+     The generated file is not suitable for printing on a  normal
+     PrintJet printer.  The -nopack option generates a file which
+     does not use the normal TIFF 4.0  compression  method.  This
+     file might be printable on a normal PaintJet printer (not an
+     XL).
+
+     The -gamma option sets the gamma correction for  the  image.
+     The useful range for the PaintJet XL is approximately 0.6 to
+     1.5.
+
+     The rendering algorithm used for images can be altered  with
+     the  -dither,  -cluster, and -diffuse options. These options
+     select ordered dithering, clustered  ordered  dithering,  or
+     error  diffusion respectively.  The -dark option can be used
+     to enhance images with  a  dark  background  when  they  are
+     reduced  in size.  The -presentation option turns on presen-
+     tation mode, in which two passes are made over the paper  to
+     increase  ink  density.  This should be used only for images
+     where quality is critical.
+
+
+     The image can be resized by setting the  -xsize  and  -ysize
+     options.  The parameter to either of these options is inter-
+     preted as the number of dots to set the width or height  to,
+     but  an  optional  dimension  of  `pt' (points), `dp' (deci-
+     points),  `in'  (inches),  or  `cm'  (centimetres)  may   be
+     appended.   If  only  one  dimension is specified, the other
+     will be scaled appropriately.
+
+     The options -width and -height are synonyms  of  -xsize  and
+     -ysize.
+
+     The -xscale and -yscale options can alternatively be used to
+     scale the image by a simple factor.
+
+     The image can be shifted on the page by  using  the  -xshift
+     and  -yshift  options.  These  move  the image the specified
+     dimensions right and down.
+
+
+2 See_Also
+     ppm
+
+2 Author
+     Angus Duggan
+
+1 libppm
+     libppm - functions to support portable pixmap programs
+
+2 Synopsis
+     #include <ppm.h>
+     cc ... libppm.a libpgm.a libpbm.a
+
+
+2 Description
+  TYPES AND CONSTANTS
+     typedef ... pixel;
+     typedef ... pixval;
+     #define PPM_MAXMAXVAL ...
+     extern pixval ppm_pbmmaxval;
+
+     Each pixel contains three pixvals, each of which should con-
+     tain   only   the   values   between  0  and  PPM_MAXMAXVAL.
+     ppm_pbmmaxval is the maxval used when a PPM program reads  a
+     PBM  file.   Normally it is 1; however, for some programs, a
+     larger value gives better results.
+
+     #define PPM_FORMAT ...
+     #define RPPM_FORMAT ...
+     #define PPM_TYPE PPM_FORMAT
+     int PPM_FORMAT_TYPE( int format )
+
+     For distinguishing different file formats and types.
+
+     pixval PPM_GETR( pixel p )
+     pixval PPM_GETG( pixel p )
+     pixval PPM_GETB( pixel p )
+
+     These three macros retrieve the red,  green  or  blue  value
+     from the given pixel.
+
+     void PPM_ASSIGN( pixel p, pixval red, pixval grn, pixval blu )
+
+     This macro assigns the given red, green and blue  values  to
+     the pixel.
+
+     int PPM_EQUAL( pixel p, pixel q )
+
+     This macro checks two pixels for equality.
+
+     void PPM_DEPTH( pixel newp, pixel p, pixval oldmaxval, pixval newmaxval )
+
+     This macro scales the colors of pixel p  according  the  old
+     and  new  maximum values and assigns the new values to newp.
+     It is intended to make writing ppmtowhatever easier.
+
+     float PPM_LUMIN( pixel p )
+     This macro determines the luminance of the pixel p.
+
+  MEMORY MANAGEMENT
+     pixel** ppm_allocarray( int cols, int rows )
+
+     Allocate an array of pixels.
+
+     pixel* ppm_allocrow( int cols )
+
+     Allocate a row of the given number of pixels.
+
+     void ppm_freearray( pixel** pixels, int rows )
+
+     Free the array allocated  with  ppm_allocarray()  containing
+     the given number of rows.
+
+     void pbm_freerow( pixel* pixelrow )
+
+     Free a row of pixels.
+
+  READING PBM FILES
+     void ppm_readppminit( FILE* fp, int* colsP, int* rowsP, pixval* maxvalP, int* formatP )
+
+     Read the header from a PPM file, filling in the rows,  cols,
+     maxval and format variables.
+
+     void ppm_readppmrow( FILE* fp, pixel* pixelrow, int cols, pixval maxval, int format )
+
+     Read a row of pixels into the pixelrow array.  Format, cols,
+     and maxval were filled in by ppm_readppminit().
+
+     pixel** ppm_readppm( FILE* fp, int* colsP, int* rowsP, pixval* maxvalP )
+
+     Read an entire pixmap file into memory, returning the  allo-
+     cated  array  and filling in the rows, cols and maxval vari-
+     ables.    This    function    combines    ppm_readppminit(),
+     ppm_allocarray() and ppm_readppmrow().
+
+  WRITING FILES
+     void ppm_writeppminit( FILE* fp, int cols, int rows, pixval maxval, int forceplain )
+
+     Write the header for a portable pixmap file.  The forceplain
+     flag forces a plain-format file to be written, as opposed to
+     a raw-format one.
+
+     void ppm_writeppmrow( FILE* fp, pixel* pixelrow, int cols, pixval maxval, int forceplain )
+
+     Write a row from a portable pixmap.
+
+     void ppm_writeppm( FILE* fp, pixel** pixels, int cols, int rows, pixval maxval, int forceplain )
+
+     Write the header and all data for a portable  pixmap.   This
+     function combines ppm_writeppminit() and ppm_writeppmrow().
+
+  COLOR NAMES
+     pixel ppm_parsecolor( char* colorname, pixval maxval )
+
+     Parses an ASCII color name into a pixel.  The color  can  be
+     specified  in  three  ways.  One, as a name, assuming that a
+     pointer to an X11-style color names file  was  compiled  in.
+     Two,  as  an  X11-style  hexadecimal  number: #rgb, #rrggbb,
+     #rrrgggbbb,  or  #rrrrggggbbbb.   Three,  as  a  triplet  of
+     decimal   floating   point   numbers  separated  by  commas:
+     r.r,g.g,b.b.
+
+     char* ppm_colorname( pixel* colorP, pixval maxval, int hexok )
+
+     Returns a pointer to a string describing  the  given  color.
+     If  the  X11  color  names  file  is available and the color
+     appears in it, that name is  returned.   Otherwise,  if  the
+     hexok flag is true then a hexadecimal colorspec is returned;
+     if hexok is false and the X11 color names file is available,
+     then the closest matching color is returned; otherwise, it's
+     an error.
+
+2 See_Also
+     pbm, pgm
+
+2 Author
+     Copyright (C) 1989, 1991 by Tony Hansen and Jef Poskanzer.
+
+1 ppm
+     ppm - portable pixmap file format
+
+2 Description
+     The portable pixmap format is a  lowest  common  denominator
+     color image file format.  The definition is as follows:
+
+     - A "magic number" for identifying the  file  type.   A  ppm
+       file's magic number is the two characters "P3".
+
+     - Whitespace (blanks, TABs, CRs, LFs).
+
+     - A width, formatted as ASCII characters in decimal.
+
+     - Whitespace.
+
+     - A height, again in ASCII decimal.
+
+     - Whitespace.
+
+     - The maximum color-component value, again in ASCII decimal.
+
+     - Whitespace.
+
+     - Width * height pixels, each  three  ASCII  decimal  values
+       between 0 and the specified maximum value, starting at the
+       top-left  corner  of  the  pixmap,  proceeding  in  normal
+       English  reading  order.   The three values for each pixel
+       represent red, green, and blue, respectively; a value of 0
+       means  that color is off, and the maximum value means that
+       color is maxxed out.
+
+     - Characters from a "#" to the next end-of-line are  ignored
+       (comments).
+
+     - No line should be longer than 70 characters.
+
+     Here is an example of a small pixmap in this format:
+     P3
+     # feep.ppm
+     4 4
+     15
+      0  0  0    0  0  0    0  0  0   15  0 15
+      0  0  0    0 15  7    0  0  0    0  0  0
+      0  0  0    0  0  0    0 15  7    0  0  0
+     15  0 15    0  0  0    0  0  0    0  0  0
+
+     Programs that read this format should be as lenient as  pos-
+     sible, accepting anything that looks remotely like a pixmap.
+
+     There is also a variant on the format, available by  setting
+     the  RAWBITS  option  at  compile  time.   This  variant  is
+     different in the following ways:
+
+     - The "magic number" is "P6" instead of "P3".
+
+     - The pixel values are stored as  plain  bytes,  instead  of
+       ASCII decimal.
+
+     - Whitespace is not allowed in the pixels area, and  only  a
+       single  character  of  whitespace (typically a newline) is
+       allowed after the maxval.
+
+     - The files are smaller and many times faster  to  read  and
+       write.
+
+     Note that this raw format can only be used for maxvals  less
+     than or equal to 255.  If you use the ppm library and try to
+     write a file with a larger  maxval,  it  will  automatically
+     fall back on the slower but more general plain format.
+
+2 See_Also
+     giftopnm, gouldtoppm, ilbmtoppm, imgtoppm, mtvtoppm, pcxtoppm,
+     pgmtoppm, pi1toppm, picttoppm, pjtoppm, qrttoppm, rawtoppm,
+     rgb3toppm, sldtoppm, spctoppm, sputoppm, tgatoppm, ximtoppm,
+     xpmtoppm, yuvtoppm, ppmtoacad, ppmtogif, ppmtoicr, ppmtoilbm,
+     ppmtopcx, ppmtopgm, ppmtopi1, ppmtopict, ppmtopj, ppmtopuzz,
+     ppmtorgb3, ppmtosixel, ppmtotga, ppmtouil, ppmtoxpm, ppmtoyuv,
+     ppmdither, ppmforge, ppmhist, ppmmake, ppmpat, ppmquant,
+     ppmquantall, ppmrelief, pnm, pgm, pbm
+
+2 Author
+     Copyright (C) 1989, 1991 by Jef Poskanzer.
+
+1 pgmkernel
+        pgmkernel - generate a convolution kernel
+
+2 Synopis
+        pgmkernel [-weight w] width [height]
+
+2 Description
+        Generates a portable graymap array of size width x height  (or
+    width  x  width  if  height  is  not  specified) to be used  as  a
+    convolution file  by pnmconvol.  The data in the convolution array
+    K are computed according to the formula:
+
+    K(i,j) = 1 / ( 1 + w * sqrt((i-width/2)\^{}2 + (j-height/2)\^{}2)) 
+
+    where w is a coefficient specified via the -weight flag, and width
+    and height are the X and Y filter sizes.
+
+        The output PGM file is always written out in ASCII format.
+
+2 Options
+        The optional -weight flag should be a real number greater than
+    -1.  The default value is 6.0.
+
+2 Bugs
+        The computation time is proportional  to width * height.  This
+    increases rapidly with the increase of  the kernel size.  A better
+    approach could be using a FFT in these cases.
+
+2 See_Also
+        pnmconvol(1), pnmsmooth(1)
+
+2 Author
+        Alberto Accomazzi (alberto@cfa.harvard.edu).
+
+1 fitstopnm
+        fitstopnm - convert a FITS file into a portable anymap
+
+2 Synopis
+        fitstopnm [-image N] [-noraw] [-scanmax] [-printmax] [-min f]
+                  [-max f] [FITSfile]
+
+2 Description
+        Reads a FITS file as input.  Produces a portable pixmap if the
+    FITS file consists of 3 image planes (NAXIS = 3 and NAXIS3 = 3), a
+    portable  graymap  if  the  FITS file consists of 2  image  planes
+    (NAXIS  =  2),  or  whenever  the -image flag is specified.    The
+    results may need to be flipped top for bottom;  if  so,  just pipe
+    the output through pnmflip -tb.
+
+2 Options
+        The  -image  option  is  for  FITS files with three axes.  The
+    assumption is that the third axis is for multiple images, and this
+    option lets you select which one you want.
+
+        Flags -min and  -max  can  be used to override the min and max
+    values as read from  the  FITS  header  or  the  image  data if no
+    DATAMIN and DATAMAX keywords are found.  Flag -scanmax can be used
+    to  force the program to scan  the  data  even  when  DATAMIN  and
+    DATAMAX are found in the header.   If  -printmax is specified, the
+    program will just print the min and max  values  and  quit.   Flag
+    -noraw  can  be  used  to force the program to  produce  an  ASCII
+    portable anymap.
+
+        The  program  will tell what kind of anymap is writing.    All
+    flags can be abbreviated to their shortest unique prefix.
+
+2 References
+        FITS stands for  Flexible  Image  Transport  System.    A full
+    description can be found  in  Astronomy  & Astrophysics Supplement
+    Series 44 (1981), page 363.
+
+2 See_Also
+        pnmtofits(1), pgm(5), pnmflip(1)
+
+2 Author
+        Copyright (C) 1989 by Jef  Poskanzer,  with  modifications  by
+    Daniel    Briggs    (dbriggs@nrao.edu)    and   Alberto  Accomazzi
+    (alberto@cfa.harvard.edu).
+
+1 pnmalias
+        pnmalias - antialias a portable anyumap.
+
+2 Synopis
+        pnmalias  [-bgcolor  color] [-fgcolor color] [-bonly] [-fonly]
+                  [-balias] [-falias] [-weight w] [pnmfile]
+
+2 Description
+        Reads a portable anymap as input, and applies anti-aliasing to
+    background and foreground pixels.  If the input file is a portable
+    bitmap,  the  output  anti-aliased image is promoted to a graymap,
+    and  a  message  is  printed  informing the user of the change  in
+    format.
+
+2 Options
+        -bgcolor colorb,
+        -fgcolor colorf
+        set the  background  color  to  colorb,  and the foreground to
+    color to colorf.    Pixels with these values will be anti-aliased.
+    by  default, the background  color  is  taken  to  be  black,  and
+    foreground color is assumed to  be  white.    The  colors  can  be
+    specified in five ways:
+
+            o A name,  assuming  that  a pointer to an X11-style color
+            names file was compiled in.
+
+            o An X11-style hexadecimal  specifier:  rgb:r/g/b, where r
+            g and b are each 1- to 4-digit hexadecimal numbers.
+
+            o An X11-style decimal specifier:    rgbi:r/g/b, where r g
+            and b are floating point numbers between 0 and 1.
+
+            o    For    backwards    compatibility,  an  old-X11-style
+            hexadecimal    number:    #rgb,  #rrggbb,  #rrrgggbbb,  or
+            #rrrrggggbbbb.
+
+            o  For  backwards  compatibility,  a  triplet  of  numbers
+            separated by commas:   r,g,b, where r g and b are floating
+            point numbers between 0  and  1.    (This  style was added
+            before MIT came up with the similar rgbi style.)
+
+        Note  that  even when dealing with  graymaps,  background  and
+    foreground colors need to be specified in  the  fashion  described
+    above.  In this case, background and foreground  pixel  values are
+    taken to be the value of the red component for the given color.
+
+        -bonly,
+        -fonly
+        Apply anti-aliasing only to background (-bonly), or foreground
+    (-fonly) pixels.
+
+        -balias,
+        -falias 
+        Apply  anti-aliasing  to  all  pixels  surrounding  background
+    (-balias),  or  foreground    (-falias)    pixels.    By  default,
+    anti-aliasing takes place only  among  neighboring  background and
+    foreground pixels.
+
+        -weight w
+        Use w as the central  weight  for the aliasing filter.  W must
+    be a real number in the range 0 < w < 1.  The lower the value of w
+    is, the "blurrier" the output image is.  The default is w = 1/3.
+
+2 See_Also
+        pbmtext(1), pnmsmooth(1), pnm(5)
+
+2 Author
+        Copyright    (C)    1992  by  Alberto  Accomazzi,  Smithsonian
+    Astrophysical Observatory.
+
+1 pnmtofits
+        pnmtofits - convert a portable anymap into FITS format
+
+2 Synopis
+        pnmtofits [-max f] [-min f] [pnmfile]
+
+2 Description
+        Reads a portable anymap as input.  Produces  a  FITS (Flexible
+    Image  Transport  System) file as output.  The resolution  of  the
+    output file is either 8 bits/pixel, or 16 bits/pixel, depending on
+    the value of maxval in the input file.  If the  input  file  is  a
+    portable bitmap or a portable graymap, the output file consists of
+    a single plane image (NAXIS = 2).   If instead the input file is a
+    portable  pixmap,  the output file will consist of  a  three-plane
+    image (NAXIS = 3, NAXIS3 = 3).  A  full  description  of  the FITS
+    format  can be found in Astronomy & Astrophysics Supplement Series
+    44 (1981), page 363.
+
+2 Options
+        Flags  -min  and  -max  can  be  used to set DATAMAX, DATAMIN,
+    BSCALE and BZERO in the FITS header, but do not cause the  data to
+    be rescaled.
+
+2 See_Also
+        fitstopnm(1), pgm(5)
+
+2 Author
+        Copyright (C) 1989 by Wilson  H.    Bent  (whb@hoh-2.att.com),
+    with modifications by Alberto Accomazzi (alberto@cfa.harvard.edu).
+
+1 ppmchange
+        ppmchange -  change  all  pixels  of one color to another in a
+    portable pixmap
+
+2 Synopis
+        ppmchange oldcolor newcolor [...] [ppmfile]
+
+2 Description
+        Reads  a portable pixmap as input.    Changes  all  pixels  of
+    oldcolor to newcolor, leaving all others unchanged.    Up  to  256
+    colors  may  be replaced by specifying couples of  colors  on  the
+    command line.  
+
+        The colors can be specified in five ways:
+
+            o A name, assuming that a  pointer  to  an X11-style color
+            names file was compiled in.
+
+            o An X11-style hexadecimal specifier:  rgb:r/g/b,  where r
+            g and b are each 1- to 4-digit hexadecimal numbers.
+
+            o An X11-style decimal specifier:  rgbi:r/g/b, where  r  g
+            and b are floating point numbers between 0 and 1.
+
+            o    For    backwards  compatibility,  an    old-X11-style
+            hexadecimal  number:    #rgb,  #rrggbb,  #rrrgggbbb,    or
+            #rrrrggggbbbb.
+
+            o  For  backwards  compatibility,  a  triplet  of  numbers
+            separated by commas:  r,g,b, where r g  and b are floating
+            point  numbers  between  0 and 1.  (This style  was  added
+            before MIT came up with the similar rgbi style.)
+
+2 See_Also
+        pgmtoppm(1), ppm(5)
+
+2 Author
+        Wilson  H.    Bent.   Jr.  (whb@usc.edu) with modifications by
+    Alberto Accomazzi (alberto@cfa.harvard.edu)
+
+1 xvminitoppm
+        xvminitoppm - convert a XV "thumbnail" picture to PPM
+
+2 Synopis
+        xvminitoppm [xvminipic]
+
+2 Description
+        Reads a  XV "thumbnail" picture (a miniature picture generated
+    by the "VisualSchnauzer"  browser)  as input.  Produces a portable
+    pixmap as output.
+
+2 See_Also
+        ppm(5), xv(1)
+
+2 Author
+        Copyright (C) 1993 by Ingo Wilken
+
diff --git a/vms/RGB.txt b/vms/RGB.txt
new file mode 100644
index 00000000..e5f61889
--- /dev/null
+++ b/vms/RGB.txt
@@ -0,0 +1,738 @@
+255 250 250 snow
+248 248 255 ghost white
+248 248 255 GhostWhite
+245 245 245 white smoke
+245 245 245 WhiteSmoke
+220 220 220 gainsboro
+255 250 240 floral white
+255 250 240 FloralWhite
+253 245 230 old lace
+253 245 230 OldLace
+250 240 230 linen
+250 235 215 antique white
+250 235 215 AntiqueWhite
+255 239 213 papaya whip
+255 239 213 PapayaWhip
+255 235 205 blanched almond
+255 235 205 BlanchedAlmond
+255 228 196 bisque
+255 218 185 peach puff
+255 218 185 PeachPuff
+255 222 173 navajo white
+255 222 173 NavajoWhite
+255 228 181 moccasin
+255 248 220 cornsilk
+255 255 240 ivory
+255 250 205 lemon chiffon
+255 250 205 LemonChiffon
+255 245 238 seashell
+240 255 240 honeydew
+245 255 250 mint cream
+245 255 250 MintCream
+240 255 255 azure
+240 248 255 alice blue
+240 248 255 AliceBlue
+230 230 250 lavender
+255 240 245 lavender blush
+255 240 245 LavenderBlush
+255 228 225 misty rose
+255 228 225 MistyRose
+255 255 255 white
+  0   0   0 black
+ 47  79  79 dark slate gray
+ 47  79  79 DarkSlateGray
+ 47  79  79 dark slate grey
+ 47  79  79 DarkSlateGrey
+105 105 105 dim gray
+105 105 105 DimGray
+105 105 105 dim grey
+105 105 105 DimGrey
+112 128 144 slate gray
+112 128 144 SlateGray
+112 128 144 slate grey
+112 128 144 SlateGrey
+119 136 153 light slate gray
+119 136 153 LightSlateGray
+119 136 153 light slate grey
+119 136 153 LightSlateGrey
+190 190 190 gray
+190 190 190 grey
+211 211 211 light grey
+211 211 211 LightGrey
+211 211 211 light gray
+211 211 211 LightGray
+ 25  25 112 midnight blue
+ 25  25 112 MidnightBlue
+  0   0 128 navy
+  0   0 128 navy blue
+  0   0 128 NavyBlue
+100 149 237 cornflower blue
+100 149 237 CornflowerBlue
+ 72  61 139 dark slate blue
+ 72  61 139 DarkSlateBlue
+106  90 205 slate blue
+106  90 205 SlateBlue
+123 104 238 medium slate blue
+123 104 238 MediumSlateBlue
+132 112 255 light slate blue
+132 112 255 LightSlateBlue
+  0   0 205 medium blue
+  0   0 205 MediumBlue
+ 65 105 225 royal blue
+ 65 105 225 RoyalBlue
+  0   0 255 blue
+ 30 144 255 dodger blue
+ 30 144 255 DodgerBlue
+  0 191 255 deep sky blue
+  0 191 255 DeepSkyBlue
+135 206 235 sky blue
+135 206 235 SkyBlue
+135 206 250 light sky blue
+135 206 250 LightSkyBlue
+ 70 130 180 steel blue
+ 70 130 180 SteelBlue
+176 196 222 light steel blue
+176 196 222 LightSteelBlue
+173 216 230 light blue
+173 216 230 LightBlue
+176 224 230 powder blue
+176 224 230 PowderBlue
+175 238 238 pale turquoise
+175 238 238 PaleTurquoise
+  0 206 209 dark turquoise
+  0 206 209 DarkTurquoise
+ 72 209 204 medium turquoise
+ 72 209 204 MediumTurquoise
+ 64 224 208 turquoise
+  0 255 255 cyan
+224 255 255 light cyan
+224 255 255 LightCyan
+ 95 158 160 cadet blue
+ 95 158 160 CadetBlue
+102 205 170 medium aquamarine
+102 205 170 MediumAquamarine
+127 255 212 aquamarine
+  0 100   0 dark green
+  0 100   0 DarkGreen
+ 85 107  47 dark olive green
+ 85 107  47 DarkOliveGreen
+143 188 143 dark sea green
+143 188 143 DarkSeaGreen
+ 46 139  87 sea green
+ 46 139  87 SeaGreen
+ 60 179 113 medium sea green
+ 60 179 113 MediumSeaGreen
+ 32 178 170 light sea green
+ 32 178 170 LightSeaGreen
+152 251 152 pale green
+152 251 152 PaleGreen
+  0 255 127 spring green
+  0 255 127 SpringGreen
+124 252   0 lawn green
+124 252   0 LawnGreen
+  0 255   0 green
+127 255   0 chartreuse
+  0 250 154 medium spring green
+  0 250 154 MediumSpringGreen
+173 255  47 green yellow
+173 255  47 GreenYellow
+ 50 205  50 lime green
+ 50 205  50 LimeGreen
+154 205  50 yellow green
+154 205  50 YellowGreen
+ 34 139  34 forest green
+ 34 139  34 ForestGreen
+107 142  35 olive drab
+107 142  35 OliveDrab
+189 183 107 dark khaki
+189 183 107 DarkKhaki
+240 230 140 khaki
+238 232 170 pale goldenrod
+238 232 170 PaleGoldenrod
+250 250 210 light goldenrod yellow
+250 250 210 LightGoldenrodYellow
+255 255 224 light yellow
+255 255 224 LightYellow
+255 255   0 yellow
+255 215   0 gold
+238 221 130 light goldenrod
+238 221 130 LightGoldenrod
+218 165  32 goldenrod
+184 134  11 dark goldenrod
+184 134  11 DarkGoldenrod
+188 143 143 rosy brown
+188 143 143 RosyBrown
+205  92  92 indian red
+205  92  92 IndianRed
+139  69  19 saddle brown
+139  69  19 SaddleBrown
+160  82  45 sienna
+205 133  63 peru
+222 184 135 burlywood
+245 245 220 beige
+245 222 179 wheat
+244 164  96 sandy brown
+244 164  96 SandyBrown
+210 180 140 tan
+210 105  30 chocolate
+178  34  34 firebrick
+165  42  42 brown
+233 150 122 dark salmon
+233 150 122 DarkSalmon
+250 128 114 salmon
+255 160 122 light salmon
+255 160 122 LightSalmon
+255 165   0 orange
+255 140   0 dark orange
+255 140   0 DarkOrange
+255 127  80 coral
+240 128 128 light coral
+240 128 128 LightCoral
+255  99  71 tomato
+255  69   0 orange red
+255  69   0 OrangeRed
+255   0   0 red
+255 105 180 hot pink
+255 105 180 HotPink
+255  20 147 deep pink
+255  20 147 DeepPink
+255 192 203 pink
+255 182 193 light pink
+255 182 193 LightPink
+219 112 147 pale violet red
+219 112 147 PaleVioletRed
+176  48  96 maroon
+199  21 133 medium violet red
+199  21 133 MediumVioletRed
+208  32 144 violet red
+208  32 144 VioletRed
+255   0 255 magenta
+238 130 238 violet
+221 160 221 plum
+218 112 214 orchid
+186  85 211 medium orchid
+186  85 211 MediumOrchid
+153  50 204 dark orchid
+153  50 204 DarkOrchid
+148   0 211 dark violet
+148   0 211 DarkViolet
+138  43 226 blue violet
+138  43 226 BlueViolet
+160  32 240 purple
+147 112 219 medium purple
+147 112 219 MediumPurple
+216 191 216 thistle
+255 250 250 snow1
+238 233 233 snow2
+205 201 201 snow3
+139 137 137 snow4
+255 245 238 seashell1
+238 229 222 seashell2
+205 197 191 seashell3
+139 134 130 seashell4
+255 239 219 AntiqueWhite1
+238 223 204 AntiqueWhite2
+205 192 176 AntiqueWhite3
+139 131 120 AntiqueWhite4
+255 228 196 bisque1
+238 213 183 bisque2
+205 183 158 bisque3
+139 125 107 bisque4
+255 218 185 PeachPuff1
+238 203 173 PeachPuff2
+205 175 149 PeachPuff3
+139 119 101 PeachPuff4
+255 222 173 NavajoWhite1
+238 207 161 NavajoWhite2
+205 179 139 NavajoWhite3
+139 121  94 NavajoWhite4
+255 250 205 LemonChiffon1
+238 233 191 LemonChiffon2
+205 201 165 LemonChiffon3
+139 137 112 LemonChiffon4
+255 248 220 cornsilk1
+238 232 205 cornsilk2
+205 200 177 cornsilk3
+139 136 120 cornsilk4
+255 255 240 ivory1
+238 238 224 ivory2
+205 205 193 ivory3
+139 139 131 ivory4
+240 255 240 honeydew1
+224 238 224 honeydew2
+193 205 193 honeydew3
+131 139 131 honeydew4
+255 240 245 LavenderBlush1
+238 224 229 LavenderBlush2
+205 193 197 LavenderBlush3
+139 131 134 LavenderBlush4
+255 228 225 MistyRose1
+238 213 210 MistyRose2
+205 183 181 MistyRose3
+139 125 123 MistyRose4
+240 255 255 azure1
+224 238 238 azure2
+193 205 205 azure3
+131 139 139 azure4
+131 111 255 SlateBlue1
+122 103 238 SlateBlue2
+105  89 205 SlateBlue3
+ 71  60 139 SlateBlue4
+ 72 118 255 RoyalBlue1
+ 67 110 238 RoyalBlue2
+ 58  95 205 RoyalBlue3
+ 39  64 139 RoyalBlue4
+  0   0 255 blue1
+  0   0 238 blue2
+  0   0 205 blue3
+  0   0 139 blue4
+ 30 144 255 DodgerBlue1
+ 28 134 238 DodgerBlue2
+ 24 116 205 DodgerBlue3
+ 16  78 139 DodgerBlue4
+ 99 184 255 SteelBlue1
+ 92 172 238 SteelBlue2
+ 79 148 205 SteelBlue3
+ 54 100 139 SteelBlue4
+  0 191 255 DeepSkyBlue1
+  0 178 238 DeepSkyBlue2
+  0 154 205 DeepSkyBlue3
+  0 104 139 DeepSkyBlue4
+135 206 255 SkyBlue1
+126 192 238 SkyBlue2
+108 166 205 SkyBlue3
+ 74 112 139 SkyBlue4
+176 226 255 LightSkyBlue1
+164 211 238 LightSkyBlue2
+141 182 205 LightSkyBlue3
+ 96 123 139 LightSkyBlue4
+198 226 255 SlateGray1
+185 211 238 SlateGray2
+159 182 205 SlateGray3
+108 123 139 SlateGray4
+202 225 255 LightSteelBlue1
+188 210 238 LightSteelBlue2
+162 181 205 LightSteelBlue3
+110 123 139 LightSteelBlue4
+191 239 255 LightBlue1
+178 223 238 LightBlue2
+154 192 205 LightBlue3
+104 131 139 LightBlue4
+224 255 255 LightCyan1
+209 238 238 LightCyan2
+180 205 205 LightCyan3
+122 139 139 LightCyan4
+187 255 255 PaleTurquoise1
+174 238 238 PaleTurquoise2
+150 205 205 PaleTurquoise3
+102 139 139 PaleTurquoise4
+152 245 255 CadetBlue1
+142 229 238 CadetBlue2
+122 197 205 CadetBlue3
+ 83 134 139 CadetBlue4
+  0 245 255 turquoise1
+  0 229 238 turquoise2
+  0 197 205 turquoise3
+  0 134 139 turquoise4
+  0 255 255 cyan1
+  0 238 238 cyan2
+  0 205 205 cyan3
+  0 139 139 cyan4
+151 255 255 DarkSlateGray1
+141 238 238 DarkSlateGray2
+121 205 205 DarkSlateGray3
+ 82 139 139 DarkSlateGray4
+127 255 212 aquamarine1
+118 238 198 aquamarine2
+102 205 170 aquamarine3
+ 69 139 116 aquamarine4
+193 255 193 DarkSeaGreen1
+180 238 180 DarkSeaGreen2
+155 205 155 DarkSeaGreen3
+105 139 105 DarkSeaGreen4
+ 84 255 159 SeaGreen1
+ 78 238 148 SeaGreen2
+ 67 205 128 SeaGreen3
+ 46 139  87 SeaGreen4
+154 255 154 PaleGreen1
+144 238 144 PaleGreen2
+124 205 124 PaleGreen3
+ 84 139  84 PaleGreen4
+  0 255 127 SpringGreen1
+  0 238 118 SpringGreen2
+  0 205 102 SpringGreen3
+  0 139  69 SpringGreen4
+  0 255   0 green1
+  0 238   0 green2
+  0 205   0 green3
+  0 139   0 green4
+127 255   0 chartreuse1
+118 238   0 chartreuse2
+102 205   0 chartreuse3
+ 69 139   0 chartreuse4
+192 255  62 OliveDrab1
+179 238  58 OliveDrab2
+154 205  50 OliveDrab3
+105 139  34 OliveDrab4
+202 255 112 DarkOliveGreen1
+188 238 104 DarkOliveGreen2
+162 205  90 DarkOliveGreen3
+110 139  61 DarkOliveGreen4
+255 246 143 khaki1
+238 230 133 khaki2
+205 198 115 khaki3
+139 134  78 khaki4
+255 236 139 LightGoldenrod1
+238 220 130 LightGoldenrod2
+205 190 112 LightGoldenrod3
+139 129  76 LightGoldenrod4
+255 255 224 LightYellow1
+238 238 209 LightYellow2
+205 205 180 LightYellow3
+139 139 122 LightYellow4
+255 255   0 yellow1
+238 238   0 yellow2
+205 205   0 yellow3
+139 139   0 yellow4
+255 215   0 gold1
+238 201   0 gold2
+205 173   0 gold3
+139 117   0 gold4
+255 193  37 goldenrod1
+238 180  34 goldenrod2
+205 155  29 goldenrod3
+139 105  20 goldenrod4
+255 185  15 DarkGoldenrod1
+238 173  14 DarkGoldenrod2
+205 149  12 DarkGoldenrod3
+139 101   8 DarkGoldenrod4
+255 193 193 RosyBrown1
+238 180 180 RosyBrown2
+205 155 155 RosyBrown3
+139 105 105 RosyBrown4
+255 106 106 IndianRed1
+238  99  99 IndianRed2
+205  85  85 IndianRed3
+139  58  58 IndianRed4
+255 130  71 sienna1
+238 121  66 sienna2
+205 104  57 sienna3
+139  71  38 sienna4
+255 211 155 burlywood1
+238 197 145 burlywood2
+205 170 125 burlywood3
+139 115  85 burlywood4
+255 231 186 wheat1
+238 216 174 wheat2
+205 186 150 wheat3
+139 126 102 wheat4
+255 165  79 tan1
+238 154  73 tan2
+205 133  63 tan3
+139  90  43 tan4
+255 127  36 chocolate1
+238 118  33 chocolate2
+205 102  29 chocolate3
+139  69  19 chocolate4
+255  48  48 firebrick1
+238  44  44 firebrick2
+205  38  38 firebrick3
+139  26  26 firebrick4
+255  64  64 brown1
+238  59  59 brown2
+205  51  51 brown3
+139  35  35 brown4
+255 140 105 salmon1
+238 130  98 salmon2
+205 112  84 salmon3
+139  76  57 salmon4
+255 160 122 LightSalmon1
+238 149 114 LightSalmon2
+205 129  98 LightSalmon3
+139  87  66 LightSalmon4
+255 165   0 orange1
+238 154   0 orange2
+205 133   0 orange3
+139  90   0 orange4
+255 127   0 DarkOrange1
+238 118   0 DarkOrange2
+205 102   0 DarkOrange3
+139  69   0 DarkOrange4
+255 114  86 coral1
+238 106  80 coral2
+205  91  69 coral3
+139  62  47 coral4
+255  99  71 tomato1
+238  92  66 tomato2
+205  79  57 tomato3
+139  54  38 tomato4
+255  69   0 OrangeRed1
+238  64   0 OrangeRed2
+205  55   0 OrangeRed3
+139  37   0 OrangeRed4
+255   0   0 red1
+238   0   0 red2
+205   0   0 red3
+139   0   0 red4
+255  20 147 DeepPink1
+238  18 137 DeepPink2
+205  16 118 DeepPink3
+139  10  80 DeepPink4
+255 110 180 HotPink1
+238 106 167 HotPink2
+205  96 144 HotPink3
+139  58  98 HotPink4
+255 181 197 pink1
+238 169 184 pink2
+205 145 158 pink3
+139  99 108 pink4
+255 174 185 LightPink1
+238 162 173 LightPink2
+205 140 149 LightPink3
+139  95 101 LightPink4
+255 130 171 PaleVioletRed1
+238 121 159 PaleVioletRed2
+205 104 137 PaleVioletRed3
+139  71  93 PaleVioletRed4
+255  52 179 maroon1
+238  48 167 maroon2
+205  41 144 maroon3
+139  28  98 maroon4
+255  62 150 VioletRed1
+238  58 140 VioletRed2
+205  50 120 VioletRed3
+139  34  82 VioletRed4
+255   0 255 magenta1
+238   0 238 magenta2
+205   0 205 magenta3
+139   0 139 magenta4
+255 131 250 orchid1
+238 122 233 orchid2
+205 105 201 orchid3
+139  71 137 orchid4
+255 187 255 plum1
+238 174 238 plum2
+205 150 205 plum3
+139 102 139 plum4
+224 102 255 MediumOrchid1
+209  95 238 MediumOrchid2
+180  82 205 MediumOrchid3
+122  55 139 MediumOrchid4
+191  62 255 DarkOrchid1
+178  58 238 DarkOrchid2
+154  50 205 DarkOrchid3
+104  34 139 DarkOrchid4
+155  48 255 purple1
+145  44 238 purple2
+125  38 205 purple3
+ 85  26 139 purple4
+171 130 255 MediumPurple1
+159 121 238 MediumPurple2
+137 104 205 MediumPurple3
+ 93  71 139 MediumPurple4
+255 225 255 thistle1
+238 210 238 thistle2
+205 181 205 thistle3
+139 123 139 thistle4
+  0   0   0 gray0
+  0   0   0 grey0
+  3   3   3 gray1
+  3   3   3 grey1
+  5   5   5 gray2
+  5   5   5 grey2
+  8   8   8 gray3
+  8   8   8 grey3
+ 10  10  10 gray4
+ 10  10  10 grey4
+ 13  13  13 gray5
+ 13  13  13 grey5
+ 15  15  15 gray6
+ 15  15  15 grey6
+ 18  18  18 gray7
+ 18  18  18 grey7
+ 20  20  20 gray8
+ 20  20  20 grey8
+ 23  23  23 gray9
+ 23  23  23 grey9
+ 26  26  26 gray10
+ 26  26  26 grey10
+ 28  28  28 gray11
+ 28  28  28 grey11
+ 31  31  31 gray12
+ 31  31  31 grey12
+ 33  33  33 gray13
+ 33  33  33 grey13
+ 36  36  36 gray14
+ 36  36  36 grey14
+ 38  38  38 gray15
+ 38  38  38 grey15
+ 41  41  41 gray16
+ 41  41  41 grey16
+ 43  43  43 gray17
+ 43  43  43 grey17
+ 46  46  46 gray18
+ 46  46  46 grey18
+ 48  48  48 gray19
+ 48  48  48 grey19
+ 51  51  51 gray20
+ 51  51  51 grey20
+ 54  54  54 gray21
+ 54  54  54 grey21
+ 56  56  56 gray22
+ 56  56  56 grey22
+ 59  59  59 gray23
+ 59  59  59 grey23
+ 61  61  61 gray24
+ 61  61  61 grey24
+ 64  64  64 gray25
+ 64  64  64 grey25
+ 66  66  66 gray26
+ 66  66  66 grey26
+ 69  69  69 gray27
+ 69  69  69 grey27
+ 71  71  71 gray28
+ 71  71  71 grey28
+ 74  74  74 gray29
+ 74  74  74 grey29
+ 77  77  77 gray30
+ 77  77  77 grey30
+ 79  79  79 gray31
+ 79  79  79 grey31
+ 82  82  82 gray32
+ 82  82  82 grey32
+ 84  84  84 gray33
+ 84  84  84 grey33
+ 87  87  87 gray34
+ 87  87  87 grey34
+ 89  89  89 gray35
+ 89  89  89 grey35
+ 92  92  92 gray36
+ 92  92  92 grey36
+ 94  94  94 gray37
+ 94  94  94 grey37
+ 97  97  97 gray38
+ 97  97  97 grey38
+ 99  99  99 gray39
+ 99  99  99 grey39
+102 102 102 gray40
+102 102 102 grey40
+105 105 105 gray41
+105 105 105 grey41
+107 107 107 gray42
+107 107 107 grey42
+110 110 110 gray43
+110 110 110 grey43
+112 112 112 gray44
+112 112 112 grey44
+115 115 115 gray45
+115 115 115 grey45
+117 117 117 gray46
+117 117 117 grey46
+120 120 120 gray47
+120 120 120 grey47
+122 122 122 gray48
+122 122 122 grey48
+125 125 125 gray49
+125 125 125 grey49
+127 127 127 gray50
+127 127 127 grey50
+130 130 130 gray51
+130 130 130 grey51
+133 133 133 gray52
+133 133 133 grey52
+135 135 135 gray53
+135 135 135 grey53
+138 138 138 gray54
+138 138 138 grey54
+140 140 140 gray55
+140 140 140 grey55
+143 143 143 gray56
+143 143 143 grey56
+145 145 145 gray57
+145 145 145 grey57
+148 148 148 gray58
+148 148 148 grey58
+150 150 150 gray59
+150 150 150 grey59
+153 153 153 gray60
+153 153 153 grey60
+156 156 156 gray61
+156 156 156 grey61
+158 158 158 gray62
+158 158 158 grey62
+161 161 161 gray63
+161 161 161 grey63
+163 163 163 gray64
+163 163 163 grey64
+166 166 166 gray65
+166 166 166 grey65
+168 168 168 gray66
+168 168 168 grey66
+171 171 171 gray67
+171 171 171 grey67
+173 173 173 gray68
+173 173 173 grey68
+176 176 176 gray69
+176 176 176 grey69
+179 179 179 gray70
+179 179 179 grey70
+181 181 181 gray71
+181 181 181 grey71
+184 184 184 gray72
+184 184 184 grey72
+186 186 186 gray73
+186 186 186 grey73
+189 189 189 gray74
+189 189 189 grey74
+191 191 191 gray75
+191 191 191 grey75
+194 194 194 gray76
+194 194 194 grey76
+196 196 196 gray77
+196 196 196 grey77
+199 199 199 gray78
+199 199 199 grey78
+201 201 201 gray79
+201 201 201 grey79
+204 204 204 gray80
+204 204 204 grey80
+207 207 207 gray81
+207 207 207 grey81
+209 209 209 gray82
+209 209 209 grey82
+212 212 212 gray83
+212 212 212 grey83
+214 214 214 gray84
+214 214 214 grey84
+217 217 217 gray85
+217 217 217 grey85
+219 219 219 gray86
+219 219 219 grey86
+222 222 222 gray87
+222 222 222 grey87
+224 224 224 gray88
+224 224 224 grey88
+227 227 227 gray89
+227 227 227 grey89
+229 229 229 gray90
+229 229 229 grey90
+232 232 232 gray91
+232 232 232 grey91
+235 235 235 gray92
+235 235 235 grey92
+237 237 237 gray93
+237 237 237 grey93
+240 240 240 gray94
+240 240 240 grey94
+242 242 242 gray95
+242 242 242 grey95
+245 245 245 gray96
+245 245 245 grey96
+247 247 247 gray97
+247 247 247 grey97
+250 250 250 gray98
+250 250 250 grey98
+252 252 252 gray99
+252 252 252 grey99
+255 255 255 gray100
+255 255 255 grey100
diff --git a/vms/SetUp.com b/vms/SetUp.com
new file mode 100755
index 00000000..077318b5
--- /dev/null
+++ b/vms/SetUp.com
@@ -0,0 +1,37 @@
+$ VERIFY = F$Verify (0)
+$ On Error Then GoTo EXIT
+$ Write Sys$Output "SETting UP PBMplus (ver netpbm-VMS)..."
+$! 
+$!  Keep this proc in the top directory of the PBMPLUS tree. Execute it from 
+$!  anywhere and it will set up command symbols for all executables in the
+$!  PBMplus_Root:[EXE] directory.
+$!  There is a problem if this directory is located in a "rooted"
+$!  directory structure already.  It is not possible to define a "rooted"
+$!  directory twice, i.e.:
+$! BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD
+$! BAD                                                                 BAD
+$! BAD     Define /Trans=conceal Public Disk$:[Dir.]                   BAD
+$! BAD     Define /Trans=conceal PBMplus_Root Public:[PBMplus.]        BAD
+$! BAD                                                                 BAD
+$! BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD
+$!  THIS WILL NOT WORK!  In this case, you will have to manually define
+$!  PBMplus_Root instead of the autosensing feature below....
+$! 
+$ PBMPLUS_PATH = F$Element (0, "]", F$Environment ("PROCEDURE")) + ".]"
+$ Define /Translation_Attributes = Concealed PBMplus_Root "''PBMPLUS_PATH'"
+$ Define PBMplus_Dir PBMplus_Root:[000000]
+$ Define PBMplusShr PBMplus_Dir:PBMplusShr
+$ NAME = "PBMplus_Root:[Exe]*.EXE"
+$LOOP:
+$   PROG = F$Search (NAME)
+$   If PROG .nes. ""
+$       Then
+$           PROG = PROG - F$Parse (PROG, , , "VERSION")
+$           CMD = F$Parse (PROG, , , "NAME")
+$           'CMD' :== $ 'PROG'
+$       GoTo LOOP
+$   EndIf
+$   @ PBMplus_Dir:ADD_LIST Hlp$Library PBMplus_Dir:PBMPLUS.HLB
+$EXIT:
+$   VERIFY = F$Verify (VERIFY)
+$   Exit
diff --git a/vms/stamp-date.com b/vms/stamp-date.com
new file mode 100755
index 00000000..349f6626
--- /dev/null
+++ b/vms/stamp-date.com
@@ -0,0 +1,25 @@
+$!# Copyright (C) 1993 by Oliver Trepte.
+$!#
+$!# Permission to use, copy, modify, and distribute this software and its
+$!# documentation for any purpose and without fee is hereby granted, provided
+$!# that the above copyright notice appear in all copies and that both that
+$!# copyright notice and this permission notice appear in supporting
+$!# documentation.  This software is provided "as is" without express or
+$!# implied warranty.
+$!#
+$!# This shell script creates a file called "compile_time.h" which holds
+$!# a define stating the compilation time. This is used for the -version
+$!# flag.
+$!#
+$!# modified to a VMS command procedure by Rick Dyson 29-NOV-1993
+
+$ OUTFILE   := "compile.h"
+$ DATE      := "''F$Time ()'"
+$ USER      := "''F$GetJPI (F$GetJPI ("", "PID"), "USERNAME")'"
+$ Open /Write OUTFILE compile.h
+$ Write OUTFILE "/* compile_time.h  This file tells the package when it was compiled */"
+$ Write OUTFILE "/* DO NOT EDIT - THIS FILE IS MAINTAINED AUTOMATICALLY              */"
+$ Write OUTFILE "#define COMPILE_TIME ""''DATE'"""
+$ Write OUTFILE "#define COMPILED_BY ""''USER'"""
+$ Close OUTFILE
+$ Exit